feat: Enhance history management with versioning and chain tracking
- Implement comprehensive history management with record chains - Add support for tracking prompt optimization and iteration versions - Introduce new history types and interfaces for better record management - Update HistoryDrawer to display versioned records with detailed information - Integrate UUID for generating unique record and chain identifiers - Improve history persistence and retrieval mechanisms - Refactor history-related services and components to support new versioning system
This commit is contained in:
1 parent
fc0d8955fc
commit
2e72d2613e
11 files changed
+995
-293
No files matched your search
Generated
+44
-4
@@ -16,6 +16,7 @@
|
||||
"element-plus": "^2.9.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"langchain": "^0.3.12",
|
||||
"uuid": "^11.0.5",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -830,6 +831,19 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core/node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/google-genai": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.1.6.tgz",
|
||||
@@ -3024,6 +3038,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/langchain/node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/langsmith": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.2.tgz",
|
||||
@@ -3047,6 +3074,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/langsmith/node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@@ -4467,16 +4507,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
|
||||
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"element-plus": "^2.9.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"langchain": "^0.3.12",
|
||||
"uuid": "^11.0.5",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+134
-18
@@ -72,8 +72,11 @@
|
||||
v-model:optimized-prompt="optimizedPrompt"
|
||||
:is-iterating="isIterating"
|
||||
v-model:selected-iterate-template="selectedIterateTemplate"
|
||||
:versions="currentVersions"
|
||||
:current-version-id="currentVersionId"
|
||||
@iterate="handleIteratePrompt"
|
||||
@openTemplateManager="openTemplateManager"
|
||||
@switchVersion="handleSwitchVersion"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,12 +140,13 @@
|
||||
/>
|
||||
</Teleport>
|
||||
|
||||
<!-- 历史记录抽屉 -->
|
||||
<HistoryDrawer
|
||||
<!-- 历史记录弹窗 -->
|
||||
<HistoryModal
|
||||
:show="showHistory"
|
||||
:history="history"
|
||||
@close="showHistory = false"
|
||||
@reuse="handleSelectHistory"
|
||||
@clear="handleClearHistory"
|
||||
/>
|
||||
|
||||
<!-- 全局提示 -->
|
||||
@@ -159,12 +163,14 @@ import { templateManager } from './services/template/manager'
|
||||
import ModelManager from './components/ModelManager.vue'
|
||||
import TemplateManager from './components/TemplateManager.vue'
|
||||
import Toast from './components/Toast.vue'
|
||||
import HistoryDrawer from './components/HistoryDrawer.vue'
|
||||
import HistoryModal from './components/HistoryDrawer.vue'
|
||||
import PromptPanel from './components/PromptPanel.vue'
|
||||
import InputPanel from './components/InputPanel.vue'
|
||||
import OutputPanel from './components/OutputPanel.vue'
|
||||
import { useToast } from './composables/useToast'
|
||||
import TemplateSelect from './components/TemplateSelect.vue'
|
||||
import { historyManager } from './services/history/manager'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// 初始化服务
|
||||
const llmService = createLLMService(modelManager)
|
||||
@@ -196,6 +202,9 @@ const selectedIterateTemplate = ref(null)
|
||||
const history = ref([])
|
||||
const models = ref([])
|
||||
const outputPanelRef = ref(null)
|
||||
const currentChainId = ref('')
|
||||
const currentVersions = ref([])
|
||||
const currentVersionId = ref('')
|
||||
|
||||
// 流式输出处理器
|
||||
const streamHandler = {
|
||||
@@ -270,17 +279,42 @@ const handleOptimizePrompt = async () => {
|
||||
})
|
||||
|
||||
// 使用流式调用
|
||||
await promptService.optimizePromptStream(
|
||||
const result = await promptService.optimizePromptStream(
|
||||
prompt.value,
|
||||
optimizeModel.value,
|
||||
selectedOptimizeTemplate.value.content, // 直接使用当前选中的提示词内容
|
||||
selectedOptimizeTemplate.value.content,
|
||||
{
|
||||
onToken: (token) => {
|
||||
optimizedPrompt.value += token
|
||||
},
|
||||
onComplete: () => {
|
||||
// 更新历史记录
|
||||
history.value = promptService.getHistory()
|
||||
// 创建新记录链
|
||||
console.log('创建新的历史记录链...')
|
||||
const newRecord = historyManager.createNewChain({
|
||||
id: uuidv4(),
|
||||
originalPrompt: prompt.value,
|
||||
optimizedPrompt: optimizedPrompt.value,
|
||||
type: 'optimize',
|
||||
modelKey: optimizeModel.value,
|
||||
templateId: selectedOptimizeTemplate.value.id,
|
||||
timestamp: Date.now(),
|
||||
metadata: {}
|
||||
});
|
||||
|
||||
console.log('新记录链创建成功:', {
|
||||
chainId: newRecord.chainId,
|
||||
record: newRecord
|
||||
})
|
||||
|
||||
currentChainId.value = newRecord.chainId;
|
||||
// 更新版本信息
|
||||
currentVersions.value = newRecord.versions;
|
||||
currentVersionId.value = newRecord.currentRecord.id;
|
||||
history.value = historyManager.getAllChains();
|
||||
console.log('历史记录已更新:', {
|
||||
totalChains: history.value?.length
|
||||
})
|
||||
|
||||
toast.success('优化成功')
|
||||
isOptimizing.value = false
|
||||
},
|
||||
@@ -316,8 +350,7 @@ const handleIteratePrompt = async ({ originalPrompt, iterateInput }) => {
|
||||
|
||||
try {
|
||||
// 使用流式调用
|
||||
const template = selectedIterateTemplate.value;
|
||||
await promptService.iteratePromptStream(
|
||||
const result = await promptService.iteratePromptStream(
|
||||
originalPrompt,
|
||||
iterateInput,
|
||||
optimizeModel.value,
|
||||
@@ -326,15 +359,41 @@ const handleIteratePrompt = async ({ originalPrompt, iterateInput }) => {
|
||||
optimizedPrompt.value += token; // 直接更新到 optimizedPrompt
|
||||
},
|
||||
onComplete: () => {
|
||||
// 更新历史记录
|
||||
history.value = promptService.getHistory()
|
||||
// 添加迭代记录
|
||||
console.log('添加迭代记录...', {
|
||||
chainId: currentChainId.value,
|
||||
originalPrompt,
|
||||
iterateInput
|
||||
})
|
||||
|
||||
const updatedChain = historyManager.addIteration({
|
||||
chainId: currentChainId.value,
|
||||
originalPrompt: originalPrompt,
|
||||
optimizedPrompt: optimizedPrompt.value,
|
||||
iterationNote: iterateInput,
|
||||
modelKey: optimizeModel.value,
|
||||
templateId: selectedIterateTemplate.value.id
|
||||
});
|
||||
|
||||
console.log('迭代记录添加成功:', {
|
||||
chain: updatedChain
|
||||
})
|
||||
|
||||
// 更新版本信息
|
||||
currentVersions.value = updatedChain.versions;
|
||||
currentVersionId.value = updatedChain.currentRecord.id;
|
||||
history.value = historyManager.getAllChains();
|
||||
console.log('历史记录已更新:', {
|
||||
totalChains: history.value?.length
|
||||
})
|
||||
|
||||
toast.success('迭代优化成功')
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message || '迭代优化失败')
|
||||
}
|
||||
},
|
||||
template
|
||||
selectedIterateTemplate.value
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('迭代优化失败:', error)
|
||||
@@ -392,9 +451,38 @@ const handleTest = async () => {
|
||||
};
|
||||
|
||||
const handleSelectHistory = (item) => {
|
||||
prompt.value = item.prompt
|
||||
optimizedPrompt.value = item.result
|
||||
showHistory.value = false
|
||||
// 如果是迭代记录,需要找到原始的提示词
|
||||
if (item.type === 'iterate') {
|
||||
// 从历史记录中找到对应的链
|
||||
const chain = history.value.find(c => c.chainId === item.chainId);
|
||||
if (chain) {
|
||||
prompt.value = chain.rootRecord.originalPrompt;
|
||||
currentVersions.value = chain.versions;
|
||||
currentVersionId.value = item.id;
|
||||
}
|
||||
} else {
|
||||
prompt.value = item.originalPrompt;
|
||||
const chain = history.value.find(c => c.chainId === item.chainId);
|
||||
if (chain) {
|
||||
currentVersions.value = chain.versions;
|
||||
currentVersionId.value = item.id;
|
||||
}
|
||||
}
|
||||
optimizedPrompt.value = item.optimizedPrompt;
|
||||
showHistory.value = false;
|
||||
}
|
||||
|
||||
// 添加清空历史记录的处理函数
|
||||
const handleClearHistory = async () => {
|
||||
try {
|
||||
await historyManager.clearHistory()
|
||||
history.value = []
|
||||
console.log('历史记录已清空')
|
||||
toast.success('历史记录已清空')
|
||||
} catch (error) {
|
||||
console.error('清空历史记录失败:', error)
|
||||
toast.error('清空历史记录失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 保存提示词选择到本地存储
|
||||
@@ -424,14 +512,34 @@ const openTemplateManager = (type = 'optimize') => {
|
||||
showTemplates.value = true
|
||||
}
|
||||
|
||||
// 添加版本切换处理函数
|
||||
const handleSwitchVersion = (version) => {
|
||||
optimizedPrompt.value = version.optimizedPrompt;
|
||||
currentVersionId.value = version.id;
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(async () => {
|
||||
await initServices()
|
||||
loadModels()
|
||||
|
||||
// 加载历史记录
|
||||
if (promptService) {
|
||||
history.value = promptService.getHistory()
|
||||
// 初始化历史记录管理器
|
||||
try {
|
||||
console.log('初始化历史记录管理器...')
|
||||
await historyManager.init()
|
||||
history.value = historyManager.getAllChains()
|
||||
console.log('历史记录加载完成:', {
|
||||
recordCount: history.value?.length,
|
||||
chains: history.value?.map(chain => ({
|
||||
chainId: chain.chainId,
|
||||
versionsCount: chain.versions.length,
|
||||
rootRecord: chain.rootRecord,
|
||||
currentRecord: chain.currentRecord
|
||||
}))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载历史记录失败:', error)
|
||||
toast.error('加载历史记录失败')
|
||||
}
|
||||
|
||||
// 初始化提示词选择
|
||||
@@ -487,6 +595,14 @@ watch(showConfig, (newVal) => {
|
||||
loadModels()
|
||||
}
|
||||
})
|
||||
|
||||
// 添加历史记录显示状态监听
|
||||
watch(showHistory, (newVal) => {
|
||||
console.log('历史记录显示状态变更:', {
|
||||
show: newVal,
|
||||
currentHistory: history.value?.length
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,73 +1,212 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="show"
|
||||
class="fixed inset-0 bg-black bg-opacity-50 z-45"
|
||||
class="fixed inset-0 bg-black bg-opacity-50 z-45 flex items-center justify-center"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<div
|
||||
class="fixed right-0 top-[60px] sm:top-[68px] bottom-0 w-full max-w-md bg-gray-900/90 backdrop-blur-sm border-l border-purple-700/50 shadow-xl transition-transform duration-300 transform"
|
||||
class="w-full max-w-4xl h-[90vh] bg-gray-900/90 backdrop-blur-sm border border-purple-700/50 shadow-xl rounded-lg transform transition-all duration-300 ease-in-out"
|
||||
:class="show ? 'scale-100 opacity-100' : 'scale-95 opacity-0'"
|
||||
@click.stop
|
||||
>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="sticky top-0 z-10 p-4 sm:p-6 border-b border-purple-700/50 flex items-center justify-between bg-gray-900/95 backdrop-blur-sm">
|
||||
<h2 class="text-lg font-semibold text-white/90">历史记录</h2>
|
||||
<div class="flex-none p-4 sm:p-6 border-b border-purple-700/50 flex items-center justify-between bg-gray-900/95 backdrop-blur-sm rounded-t-lg">
|
||||
<div class="flex items-center gap-4">
|
||||
<h2 class="text-lg font-semibold text-white/90">历史记录</h2>
|
||||
<button
|
||||
v-if="history && history.length > 0"
|
||||
@click.stop="handleClear"
|
||||
class="text-sm text-white/60 hover:text-white/90 transition-colors px-2 py-1 rounded border border-white/20 hover:border-white/40"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@click="$emit('close')"
|
||||
@click.stop="$emit('close')"
|
||||
class="text-white/60 hover:text-white/90 transition-colors text-xl"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-4 sm:p-6 space-y-4">
|
||||
<div
|
||||
v-for="item in history"
|
||||
:key="item.id"
|
||||
class="bg-black/20 rounded-xl border border-purple-600/50 p-4 space-y-3 hover:border-purple-500/70 transition-colors"
|
||||
>
|
||||
<div class="text-sm text-white/50">
|
||||
{{ new Date(item.timestamp).toLocaleString() }}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="text-sm font-medium text-white/90">原始提示词:</div>
|
||||
<div class="text-sm text-white/70 bg-black/20 rounded-lg p-3 border border-purple-600/20">
|
||||
{{ item.prompt }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="text-sm font-medium text-white/90">优化结果:</div>
|
||||
<div class="text-sm text-white/70 bg-black/20 rounded-lg p-3 border border-purple-600/20 whitespace-pre-wrap">
|
||||
{{ item.result }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
@click="reuse(item)"
|
||||
class="px-4 py-1.5 text-sm rounded-lg bg-purple-600/20 text-purple-300 hover:bg-purple-600/30 transition-colors"
|
||||
<div class="flex-1 overflow-y-auto p-4 sm:p-6">
|
||||
<template v-if="history && history.length > 0">
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="chain in history"
|
||||
:key="chain.chainId"
|
||||
class="bg-black/20 rounded-xl border border-purple-600/50 overflow-hidden"
|
||||
>
|
||||
重新使用
|
||||
</button>
|
||||
<!-- 历史记录头部信息 -->
|
||||
<div class="p-4 border-b border-purple-600/30">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="text-sm text-white/50">
|
||||
创建于 {{ formatDate(chain.rootRecord.timestamp) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-white/90 break-all">
|
||||
{{ chain.rootRecord.originalPrompt }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版本列表 -->
|
||||
<div class="divide-y divide-purple-600/20">
|
||||
<div
|
||||
v-for="record in chain.versions.slice().reverse()"
|
||||
:key="record.id"
|
||||
class="relative"
|
||||
>
|
||||
<!-- 版本标题栏 -->
|
||||
<div
|
||||
class="p-3 flex items-center justify-between cursor-pointer hover:bg-purple-600/10 transition-colors"
|
||||
@click="toggleVersion(record.id)"
|
||||
>
|
||||
<div class="flex items-center gap-3 overflow-hidden">
|
||||
<span class="text-sm font-medium text-purple-300 flex-none">V{{ record.version }}</span>
|
||||
<span class="text-xs text-white/50 flex-none">{{ formatDate(record.timestamp) }}</span>
|
||||
<span v-if="record.type === 'iterate' && record.iterationNote" class="text-xs text-white/60 truncate">
|
||||
- {{ truncateText(record.iterationNote, 30) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-none">
|
||||
<span v-if="record.type === 'iterate'" class="text-xs text-purple-400 px-2 py-0.5 rounded-full bg-purple-400/10">迭代</span>
|
||||
<button class="text-purple-300 hover:text-purple-100 transition-colors text-sm">
|
||||
{{ expandedVersions[record.id] ? '收起' : '展开' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版本详细内容 -->
|
||||
<div
|
||||
v-show="expandedVersions[record.id]"
|
||||
class="p-4 bg-black/20 space-y-3"
|
||||
>
|
||||
<!-- 迭代说明 -->
|
||||
<div v-if="record.iterationNote" class="text-xs text-purple-300">
|
||||
迭代说明: <span class="text-white/60">{{ record.iterationNote }}</span>
|
||||
</div>
|
||||
<!-- 优化后的提示词 -->
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-white/50">优化后:</div>
|
||||
<div class="text-sm text-white/70 whitespace-pre-wrap">{{ record.optimizedPrompt }}</div>
|
||||
</div>
|
||||
<!-- 使用按钮 -->
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
@click="reuse(record)"
|
||||
class="text-xs text-purple-300 hover:text-purple-100 transition-colors px-3 py-1 rounded border border-purple-300/20 hover:border-purple-300/40"
|
||||
>
|
||||
使用此版本
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex flex-col items-center justify-center h-full py-12 text-white/60">
|
||||
<div class="text-4xl mb-4">📜</div>
|
||||
<div class="text-sm">暂无历史记录</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { PromptRecord, PromptRecordChain } from '../services/history/types'
|
||||
import { useToast } from '../composables/useToast'
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
history: {
|
||||
type: Array,
|
||||
type: Array as PropType<PromptRecordChain[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'reuse'])
|
||||
// 添加日志
|
||||
console.log('HistoryDrawer props:', {
|
||||
show: props.show,
|
||||
historyLength: props.history?.length
|
||||
})
|
||||
|
||||
const reuse = (item) => {
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void
|
||||
(e: 'reuse', item: PromptRecord): void
|
||||
(e: 'clear'): void
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
const expandedVersions = ref<Record<string, boolean>>({})
|
||||
|
||||
// 切换版本展开/收起状态
|
||||
const toggleVersion = (recordId: string) => {
|
||||
expandedVersions.value = {
|
||||
...expandedVersions.value,
|
||||
[recordId]: !expandedVersions.value[recordId]
|
||||
}
|
||||
}
|
||||
|
||||
// 清空历史记录
|
||||
const handleClear = async () => {
|
||||
if (confirm('确定要清空所有历史记录吗?此操作不可恢复。')) {
|
||||
emit('clear')
|
||||
toast.success('历史记录已清空')
|
||||
}
|
||||
}
|
||||
|
||||
// 监听历史记录变化
|
||||
watch(() => props.history, (newHistory) => {
|
||||
console.log('History updated:', {
|
||||
length: newHistory?.length,
|
||||
chains: newHistory?.map(chain => ({
|
||||
chainId: chain.chainId,
|
||||
versionsCount: chain.versions.length
|
||||
}))
|
||||
})
|
||||
|
||||
// 重置所有版本的展开状态为 false
|
||||
expandedVersions.value = {}
|
||||
|
||||
console.log('展开状态已重置:', expandedVersions.value)
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// 监听显示状态变化
|
||||
watch(() => props.show, (newShow) => {
|
||||
console.log('HistoryDrawer visibility changed:', newShow)
|
||||
if (!newShow) {
|
||||
// 关闭时重置所有展开状态
|
||||
expandedVersions.value = {}
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
console.log('HistoryDrawer mounted')
|
||||
})
|
||||
|
||||
const formatDate = (timestamp: number) => {
|
||||
return new Date(timestamp).toLocaleString()
|
||||
}
|
||||
|
||||
const reuse = (item: PromptRecord) => {
|
||||
emit('reuse', item)
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
// 添加文本截断函数
|
||||
const truncateText = (text: string, maxLength: number) => {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.slice(0, maxLength) + '...'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 删除之前的样式,使用Tailwind的transform和transition类 */
|
||||
</style>
|
||||
@@ -2,7 +2,24 @@
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- 标题和按钮区域 -->
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 sm:gap-0 mb-3 flex-none">
|
||||
<h3 class="text-lg font-semibold text-white/90">优化后的提示词</h3>
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-lg font-semibold text-white/90">优化后的提示词</h3>
|
||||
<div v-if="versions && versions.length > 0" class="flex items-center gap-1">
|
||||
<button
|
||||
v-for="version in versions.slice().reverse()"
|
||||
:key="version.id"
|
||||
@click="switchVersion(version)"
|
||||
class="px-2 py-1 text-xs rounded transition-colors"
|
||||
:class="[
|
||||
currentVersionId === version.id
|
||||
? 'bg-purple-600/30 text-purple-300 font-medium'
|
||||
: 'text-white/50 hover:text-white/70'
|
||||
]"
|
||||
>
|
||||
V{{ version.version }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button
|
||||
v-if="optimizedPrompt"
|
||||
@@ -106,10 +123,24 @@ const props = defineProps({
|
||||
selectedIterateTemplate: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
currentVersionId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:optimizedPrompt', 'iterate', 'openTemplateManager', 'update:selectedIterateTemplate'])
|
||||
const emit = defineEmits([
|
||||
'update:optimizedPrompt',
|
||||
'iterate',
|
||||
'openTemplateManager',
|
||||
'update:selectedIterateTemplate',
|
||||
'switchVersion'
|
||||
])
|
||||
|
||||
const showIterateInput = ref(false)
|
||||
const iterateInput = ref('')
|
||||
@@ -172,6 +203,12 @@ const submitIterate = () => {
|
||||
iterateInput.value = ''
|
||||
showIterateInput.value = false
|
||||
}
|
||||
|
||||
// 添加版本切换函数
|
||||
const switchVersion = (version) => {
|
||||
if (version.id === props.currentVersionId) return
|
||||
emit('switchVersion', version)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IHistoryManager, PromptRecord } from './types';
|
||||
import { IHistoryManager, PromptRecord, PromptRecordChain } from './types';
|
||||
import { HistoryError, RecordNotFoundError, StorageError, RecordValidationError } from './errors';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
/**
|
||||
* 历史记录管理器实现
|
||||
@@ -91,7 +92,7 @@ export class HistoryManager implements IHistoryManager {
|
||||
}
|
||||
records.splice(index, 1);
|
||||
try {
|
||||
this.saveToStorage(records);
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(records));
|
||||
} catch (error) {
|
||||
throw new StorageError('删除记录失败', 'delete');
|
||||
}
|
||||
@@ -112,7 +113,7 @@ export class HistoryManager implements IHistoryManager {
|
||||
if (!record) break;
|
||||
|
||||
chain.unshift(record);
|
||||
currentId = record.parentId ?? '';
|
||||
currentId = record.previousId ?? '';
|
||||
}
|
||||
|
||||
return chain;
|
||||
@@ -142,9 +143,11 @@ export class HistoryManager implements IHistoryManager {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!record.id) errors.push('缺少记录ID');
|
||||
if (!record.prompt) errors.push('缺少提示词');
|
||||
if (!record.result) errors.push('缺少结果');
|
||||
if (!record.originalPrompt) errors.push('缺少原始提示词');
|
||||
if (!record.optimizedPrompt) errors.push('缺少优化后的提示词');
|
||||
if (!record.type) errors.push('缺少记录类型');
|
||||
if (!record.chainId) errors.push('缺少记录链ID');
|
||||
if (!record.version) errors.push('缺少版本号');
|
||||
if (!record.timestamp) errors.push('缺少时间戳');
|
||||
if (!record.modelKey) errors.push('缺少模型标识');
|
||||
if (!record.templateId) errors.push('缺少提示词标识');
|
||||
@@ -164,6 +167,99 @@ export class HistoryManager implements IHistoryManager {
|
||||
throw new StorageError('保存历史记录失败', 'write');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的记录链(初始优化)
|
||||
*/
|
||||
createNewChain(record: Omit<PromptRecord, 'chainId' | 'version' | 'previousId'>): PromptRecordChain {
|
||||
const chainId = uuidv4();
|
||||
const newRecord: PromptRecord = {
|
||||
...record,
|
||||
chainId,
|
||||
version: 1,
|
||||
previousId: undefined
|
||||
};
|
||||
|
||||
this.addRecord(newRecord);
|
||||
return this.getChain(chainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加迭代记录
|
||||
*/
|
||||
addIteration(params: {
|
||||
chainId: string;
|
||||
originalPrompt: string;
|
||||
optimizedPrompt: string;
|
||||
iterationNote?: string;
|
||||
modelKey: string;
|
||||
templateId: string;
|
||||
}): PromptRecordChain {
|
||||
const chain = this.getChain(params.chainId);
|
||||
const latest = chain.currentRecord;
|
||||
|
||||
const newRecord: PromptRecord = {
|
||||
...params,
|
||||
id: uuidv4(),
|
||||
chainId: params.chainId,
|
||||
type: 'iterate',
|
||||
version: latest.version + 1,
|
||||
previousId: latest.id,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
this.addRecord(newRecord);
|
||||
return this.getChain(params.chainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整记录链
|
||||
*/
|
||||
getChain(chainId: string): PromptRecordChain {
|
||||
const allRecords = this.getRecords();
|
||||
const chainRecords = allRecords.filter(r => r.chainId === chainId);
|
||||
|
||||
if (chainRecords.length === 0) {
|
||||
throw new RecordNotFoundError('记录链不存在', chainId);
|
||||
}
|
||||
|
||||
const sorted = chainRecords.sort((a, b) => a.version - b.version);
|
||||
|
||||
return {
|
||||
chainId,
|
||||
rootRecord: sorted[0],
|
||||
currentRecord: sorted[sorted.length - 1],
|
||||
versions: sorted
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有记录链
|
||||
*/
|
||||
getAllChains(): PromptRecordChain[] {
|
||||
const chains = new Map<string, PromptRecord[]>();
|
||||
const allRecords = this.getRecords();
|
||||
|
||||
// 按chainId分组
|
||||
for (const record of allRecords) {
|
||||
const chain = chains.get(record.chainId) || [];
|
||||
chain.push(record);
|
||||
chains.set(record.chainId, chain);
|
||||
}
|
||||
|
||||
// 先按chainId排序,再按version排序
|
||||
return Array.from(chains.entries())
|
||||
.sort(([chainId1], [chainId2]) => chainId1.localeCompare(chainId2))
|
||||
.map(([_, records]) => {
|
||||
const sorted = records.sort((a, b) => a.version - b.version);
|
||||
return {
|
||||
chainId: sorted[0].chainId,
|
||||
rootRecord: sorted[0],
|
||||
currentRecord: sorted[sorted.length - 1],
|
||||
versions: sorted
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 提示词记录类型
|
||||
*/
|
||||
export type PromptRecordType = 'optimize' | 'iterate' | 'test';
|
||||
export type PromptRecordType = 'optimize' | 'iterate';
|
||||
|
||||
/**
|
||||
* 提示词记录接口
|
||||
@@ -10,19 +10,25 @@ export interface PromptRecord {
|
||||
/** 记录ID */
|
||||
id: string;
|
||||
/** 原始提示词 */
|
||||
prompt: string;
|
||||
/** 结果 */
|
||||
result: string;
|
||||
originalPrompt: string;
|
||||
/** 优化/迭代后的提示词 */
|
||||
optimizedPrompt: string;
|
||||
/** 记录类型 */
|
||||
type: PromptRecordType;
|
||||
/** 父记录ID(用于迭代链) */
|
||||
parentId?: string;
|
||||
/** 所属的提示词链ID */
|
||||
chainId: string;
|
||||
/** 在链中的版本号 */
|
||||
version: number;
|
||||
/** 前一个版本ID */
|
||||
previousId?: string;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 使用的模型key */
|
||||
modelKey: string;
|
||||
/** 使用的提示词ID */
|
||||
templateId: string;
|
||||
/** 迭代时的修改说明 */
|
||||
iterationNote?: string;
|
||||
/** 元数据 */
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
@@ -45,4 +51,12 @@ export interface IHistoryManager {
|
||||
getIterationChain(recordId: string): PromptRecord[];
|
||||
/** 清除所有记录 */
|
||||
clearHistory(): void;
|
||||
}
|
||||
|
||||
// 新增历史记录链类型
|
||||
export interface PromptRecordChain {
|
||||
chainId: string;
|
||||
rootRecord: PromptRecord;
|
||||
currentRecord: PromptRecord;
|
||||
versions: PromptRecord[];
|
||||
}
|
||||
@@ -100,13 +100,13 @@ export class PromptService implements IPromptService {
|
||||
throw new OptimizationError(`优化失败: ${error.message}`, prompt);
|
||||
}
|
||||
|
||||
if (!template?.template) {
|
||||
if (!template?.content) {
|
||||
throw new OptimizationError('优化失败: 提示词不存在或无效', prompt);
|
||||
}
|
||||
|
||||
// 构建消息
|
||||
const messages: Message[] = [
|
||||
{ role: 'system', content: template.template },
|
||||
{ role: 'system', content: template.content },
|
||||
{ role: 'user', content: prompt }
|
||||
];
|
||||
|
||||
@@ -122,9 +122,11 @@ export class PromptService implements IPromptService {
|
||||
// 保存历史记录
|
||||
this.historyManager.addRecord({
|
||||
id: Date.now().toString(),
|
||||
prompt,
|
||||
result,
|
||||
originalPrompt: prompt,
|
||||
optimizedPrompt: result,
|
||||
type: 'optimize',
|
||||
chainId: Date.now().toString(),
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey,
|
||||
templateId: 'optimize'
|
||||
@@ -167,13 +169,13 @@ export class PromptService implements IPromptService {
|
||||
throw new IterationError(`迭代失败: ${error.message}`, originalPrompt, iterateInput);
|
||||
}
|
||||
|
||||
if (!template?.template) {
|
||||
if (!template?.content) {
|
||||
throw new IterationError('迭代失败: 提示词不存在或无效', originalPrompt, iterateInput);
|
||||
}
|
||||
|
||||
// 构建消息
|
||||
const messages: Message[] = [
|
||||
{ role: 'system', content: template.template },
|
||||
{ role: 'system', content: template.content },
|
||||
{ role: 'user', content: `原始提示词:${originalPrompt}\n\n优化需求:${iterateInput}` }
|
||||
];
|
||||
|
||||
@@ -186,10 +188,12 @@ export class PromptService implements IPromptService {
|
||||
// 保存历史记录
|
||||
this.historyManager.addRecord({
|
||||
id: Date.now().toString(),
|
||||
prompt: iterateInput,
|
||||
result,
|
||||
originalPrompt: iterateInput,
|
||||
optimizedPrompt: result,
|
||||
type: 'iterate',
|
||||
parentId: originalPrompt,
|
||||
chainId: originalPrompt,
|
||||
version: 1,
|
||||
previousId: originalPrompt,
|
||||
timestamp: Date.now(),
|
||||
modelKey,
|
||||
templateId: 'iterate'
|
||||
@@ -238,10 +242,11 @@ export class PromptService implements IPromptService {
|
||||
// 保存历史记录
|
||||
this.historyManager.addRecord({
|
||||
id: Date.now().toString(),
|
||||
prompt: testInput,
|
||||
result,
|
||||
type: 'test',
|
||||
parentId: prompt,
|
||||
originalPrompt: prompt,
|
||||
optimizedPrompt: result,
|
||||
type: 'optimize',
|
||||
chainId: prompt,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey,
|
||||
templateId: 'test'
|
||||
@@ -296,18 +301,8 @@ export class PromptService implements IPromptService {
|
||||
];
|
||||
|
||||
await this.llmService.sendMessageStream(messages, modelKey, callbacks);
|
||||
|
||||
// 完成后保存历史记录
|
||||
this.historyManager.addRecord({
|
||||
id: Date.now().toString(),
|
||||
prompt: testInput,
|
||||
result: '流式响应', // 可以考虑在回调中收集完整响应
|
||||
type: 'test',
|
||||
parentId: prompt,
|
||||
timestamp: Date.now(),
|
||||
modelKey,
|
||||
templateId: 'test'
|
||||
});
|
||||
|
||||
// 移除历史记录相关操作
|
||||
} catch (error) {
|
||||
if (error instanceof TestError) {
|
||||
throw error;
|
||||
@@ -365,18 +360,6 @@ export class PromptService implements IPromptService {
|
||||
onComplete: () => {
|
||||
// 验证响应
|
||||
this.validateResponse(result, prompt);
|
||||
|
||||
// 保存历史记录
|
||||
this.historyManager.addRecord({
|
||||
id: Date.now().toString(),
|
||||
prompt,
|
||||
result,
|
||||
type: 'optimize',
|
||||
timestamp: Date.now(),
|
||||
modelKey,
|
||||
templateId: 'optimize'
|
||||
});
|
||||
|
||||
callbacks.onComplete();
|
||||
},
|
||||
onError: callbacks.onError
|
||||
@@ -404,7 +387,7 @@ export class PromptService implements IPromptService {
|
||||
iterateInput: string,
|
||||
modelKey: string,
|
||||
handlers: StreamHandlers,
|
||||
template?: string
|
||||
template: { content: string } | string
|
||||
): Promise<void> {
|
||||
try {
|
||||
// 获取模型配置
|
||||
@@ -414,22 +397,20 @@ export class PromptService implements IPromptService {
|
||||
}
|
||||
|
||||
// 获取迭代提示词
|
||||
let templateToUse;
|
||||
if (template) {
|
||||
templateToUse = template;
|
||||
let templateContent: string;
|
||||
if (typeof template === 'string') {
|
||||
templateContent = template;
|
||||
} else if (template && typeof template.content === 'string') {
|
||||
templateContent = template.content;
|
||||
} else {
|
||||
templateToUse = await this.templateManager.getTemplate('iterate');
|
||||
}
|
||||
|
||||
if (!templateToUse?.content) {
|
||||
throw new IterationError('迭代失败: 提示词不存在或无效', originalPrompt, iterateInput);
|
||||
throw new IterationError('迭代失败: 未提供有效的提示词模板', originalPrompt, iterateInput);
|
||||
}
|
||||
|
||||
// 构建消息
|
||||
const messages: Message[] = [
|
||||
{
|
||||
role: 'system',
|
||||
content: templateToUse.content
|
||||
content: templateContent
|
||||
},
|
||||
{ role: 'user', content: `原始提示词:${originalPrompt}\n\n优化需求:${iterateInput}` }
|
||||
];
|
||||
@@ -445,18 +426,6 @@ export class PromptService implements IPromptService {
|
||||
handlers.onToken(token);
|
||||
},
|
||||
onComplete: () => {
|
||||
// 保存历史记录
|
||||
this.historyManager.addRecord({
|
||||
id: Date.now().toString(),
|
||||
prompt: iterateInput,
|
||||
result,
|
||||
type: 'iterate',
|
||||
parentId: originalPrompt,
|
||||
timestamp: Date.now(),
|
||||
modelKey,
|
||||
templateId: 'iterate'
|
||||
});
|
||||
|
||||
handlers.onComplete();
|
||||
},
|
||||
onError: handlers.onError
|
||||
|
||||
+330
-63
@@ -9,6 +9,8 @@ import { templateManager } from '../../src/services/template/manager'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { ElInput, ElButton, ElSelect, ElOption } from 'element-plus'
|
||||
import { useToast } from '../../src/composables/useToast'
|
||||
import { historyManager } from '../../src/services/history/manager'
|
||||
|
||||
// Mock toast functions
|
||||
const mockSuccess = vi.fn()
|
||||
@@ -43,12 +45,49 @@ const mockLLMService = {
|
||||
|
||||
const mockPromptService = {
|
||||
optimizePrompt: vi.fn(() => Promise.resolve('Optimized prompt')),
|
||||
optimizePromptStream: vi.fn(() => Promise.resolve()),
|
||||
optimizePromptStream: vi.fn().mockImplementation(async (prompt, model, template, handlers) => {
|
||||
handlers.onToken('优化后的')
|
||||
handlers.onToken('提示词')
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId: 'test-chain-1',
|
||||
currentRecord: {
|
||||
id: '1',
|
||||
originalPrompt: '测试提示词',
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId: 'test-chain-1',
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
}
|
||||
}
|
||||
}),
|
||||
iteratePrompt: vi.fn(() => Promise.resolve('Iterated prompt')),
|
||||
iteratePromptStream: vi.fn(() => Promise.resolve()),
|
||||
iteratePromptStream: vi.fn().mockImplementation(async (prompt, input, model, handlers) => {
|
||||
handlers.onToken('迭代后的')
|
||||
handlers.onToken('提示词')
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId: 'test-chain-1',
|
||||
currentRecord: {
|
||||
id: '2',
|
||||
originalPrompt: prompt,
|
||||
optimizedPrompt: '迭代后的提示词',
|
||||
type: 'iterate',
|
||||
chainId: 'test-chain-1',
|
||||
version: 2,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-2',
|
||||
previousId: '1'
|
||||
}
|
||||
}
|
||||
}),
|
||||
testPrompt: vi.fn(() => Promise.resolve('Test response')),
|
||||
testPromptStream: vi.fn(() => Promise.resolve()),
|
||||
getHistory: vi.fn(() => [])
|
||||
getHistory: vi.fn().mockReturnValue([])
|
||||
}
|
||||
|
||||
vi.mock('../../src/services/llm/service', () => ({
|
||||
@@ -67,6 +106,17 @@ vi.mock('../../src/services/model/manager', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock historyManager
|
||||
vi.mock('../../src/services/history/manager', () => ({
|
||||
historyManager: {
|
||||
init: vi.fn(),
|
||||
clearHistory: vi.fn(),
|
||||
createNewChain: vi.fn(),
|
||||
addIteration: vi.fn(),
|
||||
getAllChains: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock components
|
||||
const createComponent = (name, template = '<div><slot></slot></div>') => ({
|
||||
name,
|
||||
@@ -92,6 +142,7 @@ const createComponent = (name, template = '<div><slot></slot></div>') => ({
|
||||
|
||||
describe('App.vue', () => {
|
||||
let wrapper
|
||||
let mockPromptService
|
||||
|
||||
beforeEach(async () => {
|
||||
// 重置所有mock
|
||||
@@ -103,9 +154,86 @@ describe('App.vue', () => {
|
||||
mockStreamHandler.onComplete.mockClear()
|
||||
mockStreamHandler.onError.mockClear()
|
||||
|
||||
// 初始化提示词管理器
|
||||
// 初始化提示词管理器和历史记录管理器
|
||||
await templateManager.init()
|
||||
await historyManager.init() // 先初始化
|
||||
await historyManager.clearHistory() // 再清理历史记录
|
||||
|
||||
mockPromptService = {
|
||||
optimizePromptStream: vi.fn().mockImplementation(async (prompt, model, template, handlers) => {
|
||||
handlers.onToken('优化后的')
|
||||
handlers.onToken('提示词')
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId: 'test-chain-1',
|
||||
currentRecord: {
|
||||
id: '1',
|
||||
originalPrompt: '测试提示词',
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId: 'test-chain-1',
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
}
|
||||
}
|
||||
}),
|
||||
iteratePromptStream: vi.fn().mockImplementation(async (prompt, input, model, handlers) => {
|
||||
handlers.onToken('迭代后的')
|
||||
handlers.onToken('提示词')
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId: 'test-chain-1',
|
||||
currentRecord: {
|
||||
id: '2',
|
||||
originalPrompt: prompt,
|
||||
optimizedPrompt: '迭代后的提示词',
|
||||
type: 'iterate',
|
||||
chainId: 'test-chain-1',
|
||||
version: 2,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-2',
|
||||
previousId: '1'
|
||||
}
|
||||
}
|
||||
}),
|
||||
testPromptStream: vi.fn().mockImplementation(async (prompt, input, model, handlers) => {
|
||||
handlers.onToken('测试')
|
||||
handlers.onToken('结果')
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId: 'test-chain-1',
|
||||
currentRecord: {
|
||||
id: '3',
|
||||
currentPrompt: input,
|
||||
optimizedPrompt: '测试结果',
|
||||
type: 'test',
|
||||
chainId: prompt,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: model,
|
||||
templateId: 'test'
|
||||
}
|
||||
}
|
||||
}),
|
||||
getHistory: vi.fn().mockReturnValue([
|
||||
{
|
||||
id: '1',
|
||||
currentPrompt: '测试提示词',
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId: 'test-chain-1',
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
}
|
||||
])
|
||||
}
|
||||
createPromptService.mockResolvedValue(mockPromptService)
|
||||
|
||||
// 设置组件
|
||||
wrapper = mount(App, {
|
||||
global: {
|
||||
@@ -146,68 +274,79 @@ describe('App.vue', () => {
|
||||
it('应该正确处理提示词优化', async () => {
|
||||
const testPrompt = '测试提示词'
|
||||
const optimizedPrompt = '优化后的提示词'
|
||||
const mockHistory = [{ id: '1', prompt: testPrompt, result: optimizedPrompt }]
|
||||
|
||||
// 设置提示词和提示词
|
||||
wrapper.vm.prompt = testPrompt
|
||||
wrapper.vm.selectedTemplate = {
|
||||
id: 'optimize', // 使用正确的提示词ID
|
||||
name: '提示词优化',
|
||||
content: 'template content'
|
||||
const chainId = 'test-chain-1'
|
||||
const recordId = '1'
|
||||
const mockRecord = {
|
||||
id: recordId,
|
||||
originalPrompt: testPrompt,
|
||||
optimizedPrompt: optimizedPrompt,
|
||||
type: 'optimize',
|
||||
chainId,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
}
|
||||
|
||||
// 设置模拟返回值
|
||||
wrapper.vm.prompt = testPrompt
|
||||
wrapper.vm.optimizeModel = 'test-model'
|
||||
wrapper.vm.selectedOptimizeTemplate = {
|
||||
id: 'template-1',
|
||||
content: '优化模板'
|
||||
}
|
||||
|
||||
// Mock historyManager
|
||||
historyManager.createNewChain.mockReturnValue({
|
||||
chainId,
|
||||
rootRecord: mockRecord,
|
||||
currentRecord: mockRecord,
|
||||
versions: [mockRecord]
|
||||
})
|
||||
|
||||
historyManager.getAllChains.mockReturnValue([{
|
||||
chainId,
|
||||
rootRecord: mockRecord,
|
||||
currentRecord: mockRecord,
|
||||
versions: [mockRecord]
|
||||
}])
|
||||
|
||||
mockPromptService.optimizePromptStream.mockImplementation(async (prompt, model, template, handlers) => {
|
||||
handlers.onToken(optimizedPrompt)
|
||||
await nextTick()
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId,
|
||||
currentRecord: mockRecord
|
||||
}
|
||||
})
|
||||
mockPromptService.getHistory.mockReturnValue(mockHistory)
|
||||
|
||||
// 触发提交
|
||||
const inputPanel = wrapper.findComponent({ name: 'InputPanel' })
|
||||
await inputPanel.vm.$emit('submit')
|
||||
await wrapper.vm.handleOptimizePrompt()
|
||||
|
||||
// 等待异步操作完成
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// 验证服务调用
|
||||
expect(mockPromptService.optimizePromptStream).toHaveBeenCalled()
|
||||
|
||||
// 验证结果更新
|
||||
expect(wrapper.vm.optimizedPrompt).toBe(optimizedPrompt)
|
||||
|
||||
// 验证成功提示
|
||||
expect(mockSuccess).toHaveBeenCalledWith('优化成功')
|
||||
})
|
||||
|
||||
it('应该处理优化失败的情况', async () => {
|
||||
const testPrompt = '测试提示词'
|
||||
const errorMessage = '优化失败'
|
||||
|
||||
mockPromptService.optimizePromptStream.mockImplementationOnce(
|
||||
async (prompt, model, template, handlers) => {
|
||||
handlers.onError(new Error(errorMessage))
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
)
|
||||
|
||||
// 设置提示词和提示词
|
||||
wrapper.vm.prompt = testPrompt
|
||||
wrapper.vm.selectedTemplate = {
|
||||
id: 'optimize', // 使用正确的提示词ID
|
||||
name: '提示词优化',
|
||||
content: 'template content'
|
||||
wrapper.vm.optimizeModel = 'test-model'
|
||||
wrapper.vm.selectedOptimizeTemplate = {
|
||||
id: 'template-1',
|
||||
content: '优化模板'
|
||||
}
|
||||
|
||||
// 设置模拟错误
|
||||
mockPromptService.optimizePromptStream.mockImplementation(async (prompt, model, template, handlers) => {
|
||||
handlers.onError(new Error(errorMessage))
|
||||
})
|
||||
|
||||
// 触发提交
|
||||
const inputPanel = wrapper.findComponent({ name: 'InputPanel' })
|
||||
await inputPanel.vm.$emit('submit')
|
||||
await wrapper.vm.handleOptimizePrompt()
|
||||
|
||||
// 等待异步操作
|
||||
await nextTick()
|
||||
|
||||
// 验证错误状态
|
||||
expect(wrapper.vm.isOptimizing).toBe(false)
|
||||
expect(mockError).toHaveBeenCalledWith(errorMessage)
|
||||
})
|
||||
})
|
||||
@@ -274,66 +413,194 @@ describe('App.vue', () => {
|
||||
it('应该在优化成功后立即更新历史记录', async () => {
|
||||
const testPrompt = '测试提示词'
|
||||
const optimizedPrompt = '优化后的提示词'
|
||||
const mockHistory = [{ id: '1', prompt: testPrompt, result: optimizedPrompt }]
|
||||
|
||||
// 设置提示词和提示词
|
||||
const chainId = 'test-chain-1'
|
||||
const recordId = '1'
|
||||
const mockRecord = {
|
||||
id: recordId,
|
||||
originalPrompt: testPrompt,
|
||||
optimizedPrompt: optimizedPrompt,
|
||||
type: 'optimize',
|
||||
chainId,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
}
|
||||
|
||||
// 设置提示词和模板
|
||||
wrapper.vm.prompt = testPrompt
|
||||
wrapper.vm.selectedTemplate = {
|
||||
id: 'optimize', // 使用正确的提示词ID
|
||||
wrapper.vm.selectedOptimizeTemplate = {
|
||||
id: 'template-1',
|
||||
name: '提示词优化',
|
||||
content: 'template content'
|
||||
}
|
||||
|
||||
// Mock historyManager
|
||||
historyManager.createNewChain.mockReturnValue({
|
||||
chainId,
|
||||
rootRecord: mockRecord,
|
||||
currentRecord: mockRecord,
|
||||
versions: [mockRecord]
|
||||
})
|
||||
|
||||
historyManager.getAllChains.mockReturnValue([{
|
||||
chainId,
|
||||
rootRecord: mockRecord,
|
||||
currentRecord: mockRecord,
|
||||
versions: [mockRecord]
|
||||
}])
|
||||
|
||||
// 设置模拟返回值
|
||||
mockPromptService.optimizePromptStream.mockImplementation(async (prompt, model, template, handlers) => {
|
||||
handlers.onToken(optimizedPrompt)
|
||||
await nextTick()
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId,
|
||||
currentRecord: mockRecord
|
||||
}
|
||||
})
|
||||
mockPromptService.getHistory.mockReturnValue(mockHistory)
|
||||
|
||||
// 触发优化
|
||||
const inputPanel = wrapper.findComponent({ name: 'InputPanel' })
|
||||
await inputPanel.vm.$emit('submit')
|
||||
await wrapper.vm.handleOptimizePrompt()
|
||||
|
||||
// 等待异步操作
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// 验证历史记录更新
|
||||
expect(mockPromptService.getHistory).toHaveBeenCalled()
|
||||
expect(wrapper.vm.history).toEqual(mockHistory)
|
||||
expect(historyManager.createNewChain).toHaveBeenCalled()
|
||||
expect(historyManager.getAllChains).toHaveBeenCalled()
|
||||
expect(wrapper.vm.history[0].chainId).toBe(chainId)
|
||||
expect(wrapper.vm.history[0].currentRecord.id).toBe(recordId)
|
||||
expect(wrapper.vm.history[0].currentRecord.originalPrompt).toBe(testPrompt)
|
||||
expect(wrapper.vm.history[0].currentRecord.optimizedPrompt).toBe(optimizedPrompt)
|
||||
})
|
||||
|
||||
it('应该在迭代优化成功后立即更新历史记录', async () => {
|
||||
const originalPrompt = '测试提示词'
|
||||
const iterateInput = '迭代输入'
|
||||
const iteratedPrompt = '迭代后的提示词'
|
||||
const mockHistory = [{ id: '1', prompt: originalPrompt, result: iteratedPrompt }]
|
||||
const chainId = 'test-chain-1'
|
||||
const recordId = '2'
|
||||
const previousId = '1'
|
||||
const mockRecord = {
|
||||
id: recordId,
|
||||
originalPrompt: originalPrompt,
|
||||
optimizedPrompt: iteratedPrompt,
|
||||
type: 'iterate',
|
||||
chainId,
|
||||
version: 2,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-2',
|
||||
previousId
|
||||
}
|
||||
|
||||
// 设置当前链ID和迭代模板
|
||||
wrapper.vm.currentChainId = chainId
|
||||
wrapper.vm.selectedIterateTemplate = {
|
||||
id: 'template-2',
|
||||
name: '迭代优化',
|
||||
content: 'template content'
|
||||
}
|
||||
|
||||
// Mock historyManager
|
||||
historyManager.addIteration.mockReturnValue({
|
||||
chainId,
|
||||
rootRecord: {
|
||||
id: previousId,
|
||||
originalPrompt: originalPrompt,
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
},
|
||||
currentRecord: mockRecord,
|
||||
versions: [
|
||||
{
|
||||
id: previousId,
|
||||
originalPrompt: originalPrompt,
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
},
|
||||
mockRecord
|
||||
]
|
||||
})
|
||||
|
||||
historyManager.getAllChains.mockReturnValue([{
|
||||
chainId,
|
||||
rootRecord: {
|
||||
id: previousId,
|
||||
originalPrompt: originalPrompt,
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
},
|
||||
currentRecord: mockRecord,
|
||||
versions: [
|
||||
{
|
||||
id: previousId,
|
||||
originalPrompt: originalPrompt,
|
||||
optimizedPrompt: '优化后的提示词',
|
||||
type: 'optimize',
|
||||
chainId,
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-1'
|
||||
},
|
||||
mockRecord
|
||||
]
|
||||
}])
|
||||
|
||||
mockPromptService.iteratePromptStream.mockImplementation(async (prompt, input, model, handlers) => {
|
||||
handlers.onToken(iteratedPrompt)
|
||||
handlers.onComplete()
|
||||
return {
|
||||
chainId,
|
||||
currentRecord: mockRecord
|
||||
}
|
||||
})
|
||||
mockPromptService.getHistory.mockReturnValue(mockHistory)
|
||||
|
||||
// 触发迭代
|
||||
const promptPanel = wrapper.findComponent({ name: 'PromptPanel' })
|
||||
await promptPanel.vm.$emit('iterate', { originalPrompt, iterateInput })
|
||||
await wrapper.vm.handleIteratePrompt({ originalPrompt, iterateInput })
|
||||
|
||||
// 等待异步操作
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// 验证历史记录更新
|
||||
expect(mockPromptService.getHistory).toHaveBeenCalled()
|
||||
expect(wrapper.vm.history).toEqual(mockHistory)
|
||||
expect(historyManager.addIteration).toHaveBeenCalled()
|
||||
expect(historyManager.getAllChains).toHaveBeenCalled()
|
||||
expect(wrapper.vm.history[0].chainId).toBe(chainId)
|
||||
expect(wrapper.vm.history[0].currentRecord.id).toBe(recordId)
|
||||
expect(wrapper.vm.history[0].currentRecord.originalPrompt).toBe(originalPrompt)
|
||||
expect(wrapper.vm.history[0].currentRecord.optimizedPrompt).toBe(iteratedPrompt)
|
||||
expect(wrapper.vm.history[0].currentRecord.previousId).toBe(previousId)
|
||||
})
|
||||
})
|
||||
|
||||
// 清理
|
||||
afterEach(async () => {
|
||||
wrapper.unmount()
|
||||
// 清理提示词管理器
|
||||
templateManager.clearCache()
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
// 清理提示词管理器和历史记录管理器
|
||||
await templateManager.clearCache()
|
||||
await historyManager.init() // 先初始化
|
||||
await historyManager.clearHistory() // 再清理历史记录
|
||||
})
|
||||
})
|
||||
@@ -8,13 +8,28 @@ describe('HistoryManager', () => {
|
||||
let mockStorage: Record<string, any> = {};
|
||||
|
||||
const mockRecord: PromptRecord = {
|
||||
id: 'test-1',
|
||||
prompt: 'Test prompt',
|
||||
timestamp: Date.now(),
|
||||
id: '1',
|
||||
originalPrompt: 'test prompt',
|
||||
optimizedPrompt: 'optimized prompt',
|
||||
type: 'optimize',
|
||||
result: 'Test result',
|
||||
chainId: 'test-chain',
|
||||
version: 1,
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'optimize'
|
||||
templateId: 'template-1'
|
||||
};
|
||||
|
||||
const mockIterationRecord: PromptRecord = {
|
||||
id: '2',
|
||||
originalPrompt: 'iteration input',
|
||||
optimizedPrompt: 'iterated prompt',
|
||||
type: 'iterate',
|
||||
chainId: 'test-chain',
|
||||
version: 2,
|
||||
previousId: '1',
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'template-2'
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -45,134 +60,138 @@ describe('HistoryManager', () => {
|
||||
});
|
||||
|
||||
it('should throw error when adding invalid record', () => {
|
||||
const invalidRecord = {
|
||||
prompt: 'Test prompt',
|
||||
timestamp: Date.now(),
|
||||
type: 'optimize',
|
||||
result: 'Test result',
|
||||
modelKey: 'test-model',
|
||||
templateId: 'optimize'
|
||||
};
|
||||
expect(() => manager.addRecord(invalidRecord as PromptRecord))
|
||||
.toThrow(HistoryError);
|
||||
const invalidRecord = { ...mockRecord, id: undefined };
|
||||
expect(() => manager.addRecord(invalidRecord as any)).toThrow('记录验证失败');
|
||||
});
|
||||
|
||||
it('should limit the number of records', () => {
|
||||
const maxRecords = 50;
|
||||
for (let i = 0; i < maxRecords + 10; i++) {
|
||||
for (let i = 0; i < 60; i++) {
|
||||
manager.addRecord({
|
||||
...mockRecord,
|
||||
id: `test-${i + 1}`
|
||||
id: `${i}`,
|
||||
timestamp: Date.now() + i
|
||||
});
|
||||
}
|
||||
expect(manager.getRecords()).toHaveLength(maxRecords);
|
||||
const records = manager.getRecords();
|
||||
expect(records).toHaveLength(50);
|
||||
});
|
||||
|
||||
it('should handle storage errors', () => {
|
||||
vi.mocked(localStorage.setItem).mockImplementation(() => {
|
||||
vi.spyOn(localStorage, 'setItem').mockImplementation(() => {
|
||||
throw new Error('Storage error');
|
||||
});
|
||||
expect(() => manager.addRecord(mockRecord))
|
||||
.toThrow(StorageError);
|
||||
expect(() => manager.addRecord(mockRecord)).toThrow('保存历史记录失败');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRecord', () => {
|
||||
beforeEach(() => {
|
||||
manager.addRecord(mockRecord);
|
||||
});
|
||||
|
||||
it('should get existing record', () => {
|
||||
const record = manager.getRecord('test-1');
|
||||
manager.addRecord(mockRecord);
|
||||
const record = manager.getRecord(mockRecord.id);
|
||||
expect(record).toEqual(mockRecord);
|
||||
});
|
||||
|
||||
it('should throw error when record not found', () => {
|
||||
expect(() => manager.getRecord('invalid'))
|
||||
.toThrow(RecordNotFoundError);
|
||||
expect(() => manager.getRecord('non-existent')).toThrow('记录不存在');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteRecord', () => {
|
||||
beforeEach(() => {
|
||||
manager.addRecord(mockRecord);
|
||||
});
|
||||
|
||||
it('should delete existing record', () => {
|
||||
manager.deleteRecord('test-1');
|
||||
expect(manager.getRecords()).toHaveLength(0);
|
||||
manager.addRecord(mockRecord);
|
||||
manager.deleteRecord(mockRecord.id);
|
||||
expect(() => manager.getRecord(mockRecord.id)).toThrow('记录不存在');
|
||||
});
|
||||
|
||||
it('should throw error when deleting non-existent record', () => {
|
||||
expect(() => manager.deleteRecord('invalid'))
|
||||
.toThrow(RecordNotFoundError);
|
||||
expect(() => manager.deleteRecord('non-existent')).toThrow('记录不存在');
|
||||
});
|
||||
|
||||
it('should handle storage errors', () => {
|
||||
vi.mocked(localStorage.setItem).mockImplementation(() => {
|
||||
// 先正常添加记录
|
||||
manager.addRecord(mockRecord);
|
||||
|
||||
// 然后模拟删除时的存储错误
|
||||
const setItemSpy = vi.spyOn(localStorage, 'setItem').mockImplementation(() => {
|
||||
throw new Error('Storage error');
|
||||
});
|
||||
expect(() => manager.deleteRecord('test-1'))
|
||||
.toThrow(StorageError);
|
||||
|
||||
expect(() => manager.deleteRecord(mockRecord.id)).toThrow('删除记录失败');
|
||||
|
||||
// 清理 spy
|
||||
setItemSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIterationChain', () => {
|
||||
beforeEach(() => {
|
||||
manager.addRecord({
|
||||
...mockRecord,
|
||||
id: 'record-1'
|
||||
});
|
||||
manager.addRecord({
|
||||
...mockRecord,
|
||||
id: 'record-2',
|
||||
parentId: 'record-1'
|
||||
});
|
||||
manager.addRecord({
|
||||
...mockRecord,
|
||||
id: 'record-3',
|
||||
parentId: 'record-2'
|
||||
});
|
||||
});
|
||||
|
||||
it('should get complete iteration chain', () => {
|
||||
const chain = manager.getIterationChain('record-3');
|
||||
expect(chain).toHaveLength(3);
|
||||
expect(chain[0].id).toBe('record-1');
|
||||
expect(chain[1].id).toBe('record-2');
|
||||
expect(chain[2].id).toBe('record-3');
|
||||
manager.addRecord(mockRecord);
|
||||
manager.addRecord(mockIterationRecord);
|
||||
const chain = manager.getIterationChain(mockIterationRecord.id);
|
||||
expect(chain).toHaveLength(2);
|
||||
expect(chain[0]).toEqual(mockRecord);
|
||||
expect(chain[1]).toEqual(mockIterationRecord);
|
||||
});
|
||||
|
||||
it('should handle broken chains gracefully', () => {
|
||||
const chain = manager.getIterationChain('record-2');
|
||||
expect(chain).toHaveLength(2);
|
||||
expect(chain[0].id).toBe('record-1');
|
||||
expect(chain[1].id).toBe('record-2');
|
||||
manager.addRecord(mockIterationRecord);
|
||||
const chain = manager.getIterationChain(mockIterationRecord.id);
|
||||
expect(chain).toHaveLength(1);
|
||||
expect(chain[0]).toEqual(mockIterationRecord);
|
||||
});
|
||||
|
||||
it('should return single record for no parent', () => {
|
||||
const chain = manager.getIterationChain('record-1');
|
||||
it('should return single record for no previous', () => {
|
||||
manager.addRecord(mockRecord);
|
||||
const chain = manager.getIterationChain(mockRecord.id);
|
||||
expect(chain).toHaveLength(1);
|
||||
expect(chain[0].id).toBe('record-1');
|
||||
expect(chain[0]).toEqual(mockRecord);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearHistory', () => {
|
||||
beforeEach(() => {
|
||||
manager.addRecord(mockRecord);
|
||||
});
|
||||
|
||||
it('should clear all records', () => {
|
||||
manager.addRecord(mockRecord);
|
||||
manager.clearHistory();
|
||||
expect(manager.getRecords()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle storage errors', () => {
|
||||
vi.mocked(localStorage.removeItem).mockImplementation(() => {
|
||||
vi.spyOn(localStorage, 'removeItem').mockImplementation(() => {
|
||||
throw new Error('Storage error');
|
||||
});
|
||||
expect(() => manager.clearHistory())
|
||||
.toThrow(StorageError);
|
||||
expect(() => manager.clearHistory()).toThrow('清除历史记录失败');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChain', () => {
|
||||
it('should get chain by chainId', () => {
|
||||
manager.addRecord(mockRecord);
|
||||
manager.addRecord(mockIterationRecord);
|
||||
const chain = manager.getChain(mockRecord.chainId);
|
||||
expect(chain.chainId).toBe(mockRecord.chainId);
|
||||
expect(chain.rootRecord).toEqual(mockRecord);
|
||||
expect(chain.currentRecord).toEqual(mockIterationRecord);
|
||||
expect(chain.versions).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should throw error when chain not found', () => {
|
||||
expect(() => manager.getChain('non-existent')).toThrow('记录链不存在');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllChains', () => {
|
||||
it('should get all chains', () => {
|
||||
manager.addRecord(mockRecord);
|
||||
manager.addRecord(mockIterationRecord);
|
||||
const chains = manager.getAllChains();
|
||||
expect(chains).toHaveLength(1);
|
||||
expect(chains[0].chainId).toBe(mockRecord.chainId);
|
||||
expect(chains[0].versions).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should return empty array when no chains exist', () => {
|
||||
const chains = manager.getAllChains();
|
||||
expect(chains).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { Template } from '../../../../src/services/template/types';
|
||||
import { PromptRecord } from '../../../../src/services/history/types';
|
||||
import { LLMService } from '../../../../src/services/llm/service';
|
||||
import { OptimizationError, IterationError, TestError } from '../../../../src/services/prompt/errors';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// 模拟 fetch API
|
||||
const mockFetch = vi.fn();
|
||||
@@ -42,8 +43,7 @@ describe('PromptService', () => {
|
||||
const mockTemplate: Template = {
|
||||
id: 'test',
|
||||
name: 'Test Template',
|
||||
template: 'test template content',
|
||||
description: 'test template',
|
||||
content: 'test template content',
|
||||
version: '1.0'
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ describe('PromptService', () => {
|
||||
it('当提示词内容为空时应抛出错误', async () => {
|
||||
const emptyTemplate = {
|
||||
...mockTemplate,
|
||||
template: ''
|
||||
content: ''
|
||||
};
|
||||
vi.spyOn(templateManager, 'getTemplate').mockResolvedValue(emptyTemplate);
|
||||
|
||||
@@ -163,12 +163,14 @@ describe('PromptService', () => {
|
||||
it('应该返回历史记录', () => {
|
||||
const mockHistory: PromptRecord[] = [{
|
||||
id: '1',
|
||||
prompt: 'test prompt',
|
||||
result: 'test result',
|
||||
originalPrompt: 'test prompt',
|
||||
optimizedPrompt: 'test result',
|
||||
type: 'optimize',
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'test'
|
||||
templateId: 'test',
|
||||
chainId: 'test-chain',
|
||||
version: 1
|
||||
}];
|
||||
vi.spyOn(historyManager, 'getRecords').mockReturnValue(mockHistory);
|
||||
|
||||
@@ -181,12 +183,14 @@ describe('PromptService', () => {
|
||||
it('应该返回迭代链', () => {
|
||||
const mockChain: PromptRecord[] = [{
|
||||
id: '1',
|
||||
prompt: 'test prompt',
|
||||
result: 'test result',
|
||||
originalPrompt: 'test prompt',
|
||||
optimizedPrompt: 'test result',
|
||||
type: 'iterate',
|
||||
timestamp: Date.now(),
|
||||
modelKey: 'test-model',
|
||||
templateId: 'test'
|
||||
templateId: 'test',
|
||||
chainId: 'test-chain',
|
||||
version: 1
|
||||
}];
|
||||
vi.spyOn(historyManager, 'getIterationChain').mockReturnValue(mockChain);
|
||||
|
||||
@@ -244,8 +248,8 @@ describe('PromptService', () => {
|
||||
|
||||
expect(addRecordSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'optimize',
|
||||
prompt: 'test prompt',
|
||||
result: '优化结果',
|
||||
originalPrompt: 'test prompt',
|
||||
optimizedPrompt: '优化结果',
|
||||
modelKey: 'test-model'
|
||||
}));
|
||||
});
|
||||
@@ -259,10 +263,11 @@ describe('PromptService', () => {
|
||||
|
||||
expect(addRecordSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'iterate',
|
||||
prompt: 'iteration input',
|
||||
result: '迭代结果',
|
||||
originalPrompt: 'iteration input',
|
||||
optimizedPrompt: '迭代结果',
|
||||
modelKey: 'test-model',
|
||||
parentId: 'original'
|
||||
chainId: 'original',
|
||||
previousId: 'original'
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -273,11 +278,11 @@ describe('PromptService', () => {
|
||||
await promptService.testPrompt('test prompt', 'test input', 'test-model');
|
||||
|
||||
expect(addRecordSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'test',
|
||||
prompt: 'test input',
|
||||
result: '测试结果',
|
||||
type: 'optimize',
|
||||
originalPrompt: 'test prompt',
|
||||
optimizedPrompt: '测试结果',
|
||||
modelKey: 'test-model',
|
||||
parentId: 'test prompt'
|
||||
chainId: 'test prompt'
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -303,8 +308,7 @@ describe('PromptService 提示词管理器初始化测试', () => {
|
||||
const mockTemplate: Template = {
|
||||
id: 'test',
|
||||
name: 'Test Template',
|
||||
template: 'test template content',
|
||||
description: 'test template',
|
||||
content: 'test template content',
|
||||
version: '1.0'
|
||||
};
|
||||
|
||||
@@ -353,7 +357,7 @@ describe('PromptService 提示词管理器初始化测试', () => {
|
||||
it('提示词管理器正确初始化但提示词内容为空时应抛出错误', async () => {
|
||||
const emptyTemplate = {
|
||||
...mockTemplate,
|
||||
template: ''
|
||||
content: ''
|
||||
};
|
||||
vi.spyOn(templateManager, 'getTemplate').mockResolvedValue(emptyTemplate);
|
||||
promptService = new PromptService(modelManager, llmService, templateManager, historyManager);
|
||||
|
||||
Reference in new issue
Block a user