docs/archives/127-multi-turn-dialogue-mode-optimization/design.md
文档创建时间: 2025-01-04 最后更新: 2025-01-05(测试面板组件重构实施记录) 状态: ✅ 设计方案 + 实施记录(基于消息 ID + 极简映射 + 自动应用 + 全自动保存) 相关功能: 上下文模式 Pro 子模式重构 版本变更: v1 → v2 → v3 → v3.1(从"基于索引"改为"基于消息 ID") 实施进度: 第十三章 - UI层测试面板组件已完成
旧理解(误导性):
新理解(准确定义):
变量模式(原"上下文-用户")
写一首{{风格}}的诗多轮对话模式(原"上下文-系统")
❌ 错误理解:
✅ 正确理解:
┌─────────────────────────────────────────────────────┐
│ 📋 会话管理器 (ConversationManager) │
│ ┌───────────────────────────────────────────────┐ │
│ │ 💬 system: 你是一个专业的诗人 [选中/高亮] │ │
│ ├───────────────────────────────────────────────┤ │
│ │ 👤 user: 写一首关于春天的诗 │ │
│ ├───────────────────────────────────────────────┤ │
│ │ 🤖 assistant: [回复内容] │ │
│ ├───────────────────────────────────────────────┤ │
│ │ 👤 user: 再写一首夏天的 │ │
│ └───────────────────────────────────────────────┘ │
│ [+ 添加消息] [🗑️ 删除] [📤 导入] [💾 导出] │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ ✨ 优化结果区域 │
│ ┌───────────────────────────────────────────────┐ │
│ │ 版本选择:[v0 原始] [v1] [v2] [v3 当前] ▼ │ │
│ ├───────────────────────────────────────────────┤ │
│ │ 优化后的内容: │ │
│ │ 你是一位拥有深厚文学底蕴的资深诗人... │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ [🔄 应用到会话] [📜 查看历史记录] │
│ 💡 提示:所有优化自动保存到历史记录 │
└─────────────────────────────────────────────────────┘
统一使用现有的历史记录系统(PromptRecordChain),不引入新的数据结构。
// packages/core/src/services/prompt/types.ts
export interface ConversationMessage {
id: string; // 🆕 唯一标识(用于 messageChainMap 映射)
role: "system" | "user" | "assistant" | "tool";
content: string; // 当前内容(可能是优化后的)
originalContent?: string; // 🆕 原始内容(首次创建时的内容)
// 工具调用支持(保留)
name?: string;
tool_calls?: ToolCall[];
tool_call_id?: string;
}
新增字段说明:
id: 消息的唯一标识(UUID),用于稳定的映射关系
originalContent: 保存原始内容
// packages/ui/src/composables/conversation/useConversationOptimization.ts
export interface ConversationOptimizationState {
/** 实际消息数据 */
messages: ConversationMessage[];
/** 当前选中的消息 ID */
selectedMessageId: string | null;
/** 🆕 消息 ID → 工作链 ID 的映射表(核心数据结构) */
messageChainMap: Map<string, string>;
/** 当前选中的版本记录 ID */
currentRecordId: string | null;
/** 当前链的所有版本(用于版本选择器) */
versions: PromptRecord[];
}
关键字段说明:
messageChainMap: 纯粹的临时索引(不持久化)
message.id)chainId)// packages/core/src/services/history/types.ts
// ✅ 无需任何修改,直接使用现有的 PromptRecord 和 PromptRecordChain
// 工作链示例
const chain: PromptRecordChain = {
chainId: "chain-123",
rootRecord: { version: 0, optimizedPrompt: "原始内容" },
currentRecord: { version: 3, optimizedPrompt: "第3次优化" },
versions: [
{ version: 0, optimizedPrompt: "原始内容" },
{ version: 1, optimizedPrompt: "第1次优化" },
{ version: 2, optimizedPrompt: "第2次优化" },
{ version: 3, optimizedPrompt: "第3次优化" },
]
};
特点:
🆕 每条消息的优化历史是独立的链:
独立链的优势:
// 多轮对话场景
const conversation = [
{ id: "msg-001", role: "system", content: "你是一个诗人" },
{ id: "msg-002", role: "user", content: "写一首春天的诗" },
{ id: "msg-003", role: "assistant", content: "[诗歌内容]" },
];
// 用户优化消息 1(system)
// 历史记录中创建 Chain-A:
{
chainId: "chain-A",
versions: [
{ version: 0, optimizedPrompt: "你是一个诗人" },
{ version: 1, optimizedPrompt: "你是一位拥有深厚文学底蕴的资深诗人" },
{ version: 2, optimizedPrompt: "你是一位经验丰富、文笔优美的诗歌创作者" },
]
}
// 用户优化消息 2(user)
// 历史记录中创建 Chain-B(独立于 Chain-A):
{
chainId: "chain-B",
versions: [
{ version: 0, optimizedPrompt: "写一首春天的诗" },
{ version: 1, optimizedPrompt: "请创作一首描绘春天美景的诗歌" },
]
}
查看方式:
使用方式:
为什么不保存完整上下文?
采用方案 B(简单设计)的原因:
未来可选功能(不在当前范围):
用户操作: 选择要优化的消息
系统行为:
const selectMessage = async (messageId: string) => {
// 1. 找到消息对象
const message = messages.value.find(m => m.id === messageId);
if (!message) return;
// 2. 切换到新消息
selectedMessageId.value = messageId;
// 3. 🆕 检查是否已有关联的工作链
const existingChainId = messageChainMap.value.get(messageId);
if (existingChainId) {
// 4a. ✅ 复用现有链(继续之前的优化)
const chain = await historyService.getChain(existingChainId);
currentRecordId.value = chain.currentRecord.id;
versions.value = chain.versions;
console.log(`复用消息的工作链: ${existingChainId}, 当前版本: v${chain.currentRecord.version}`);
} else {
// 4b. ✅ 创建新链(首次选择此消息)
const chain = await historyService.createNewChain({
id: generateId(),
originalPrompt: message.originalContent || message.content, // 使用原始内容
optimizedPrompt: message.content, // 当前内容(可能已优化)
type: 'contextSystemOptimize',
timestamp: Date.now(),
modelKey: currentModel.value,
templateId: '',
});
// 5. 建立映射关系
messageChainMap.value.set(messageId, chain.chainId);
currentRecordId.value = chain.rootRecord.id;
versions.value = [chain.rootRecord];
console.log(`为消息创建新链: ${chain.chainId}`);
}
};
关键改进:
originalContent 作为 v0(保证原始内容不丢失)用户操作: 点击"优化"按钮
系统行为:
const optimizeMessage = async (result: string, reasoning?: string) => {
if (!selectedMessageId.value) return;
// 1. 🆕 获取当前消息的工作链 ID(基于消息 ID)
const chainId = messageChainMap.value.get(selectedMessageId.value);
if (!chainId) return;
// 2. 🆕 查找消息对象
const message = messages.value.find(m => m.id === selectedMessageId.value);
if (!message) return;
// 3. 添加新版本到工作链
const chain = await historyService.addIteration({
chainId,
originalPrompt: message.content,
optimizedPrompt: result,
modelKey: currentModel.value,
templateId: currentTemplate.value,
iterationNote: reasoning,
});
// 4. 更新当前版本
currentRecordId.value = chain.currentRecord.id;
versions.value = chain.versions;
// 5. ✨ 自动应用到消息
message.content = result;
message.success(`已优化并应用到消息 (v${chain.currentRecord.version})`);
};
关键改进:
用户操作: 点击版本选择器中的某个版本
系统行为:
const switchVersion = async (recordId: string) => {
// 仅更新当前记录 ID,不修改消息内容
currentRecordId.value = recordId;
};
效果:
用户操作: 点击"应用到会话"按钮
系统行为:
const applyToConversation = async () => {
if (!selectedMessageId.value || !currentRecordId.value) return;
// 🆕 查找消息对象(基于消息 ID)
const message = messages.value.find(m => m.id === selectedMessageId.value);
if (!message) return;
const record = await historyService.getRecord(currentRecordId.value);
message.content = record.optimizedPrompt;
};
效果:
用户操作: 点击"查看历史记录"按钮
系统行为:
const openHistoryPanel = () => {
// 打开历史记录面板(已有功能)
// 用户可以查看所有优化链
// 用户可以对比不同版本
// 用户可以复制或应用任何版本
};
效果:
用户操作: 点击版本选择器中的 v0
系统行为:
const restoreOriginal = async () => {
if (versions.value.length === 0) return;
switchVersion(versions.value[0].id); // v0 = 根记录
await applyToConversation(); // 自动应用
};
效果:
用户操作: 删除某条消息
系统行为:
const deleteMessage = async (messageId: string) => {
// 1. 🆕 从映射表中移除消息 ID(不删除工作链)
messageChainMap.value.delete(messageId);
// 2. 🆕 删除消息(基于 ID)
const index = messages.value.findIndex(m => m.id === messageId);
if (index !== -1) {
messages.value.splice(index, 1);
}
// 3. ✨ 无需重建映射表(ID 稳定,不受索引变化影响)
};
关键改进:
效果:
用户操作: 切换到其他功能模式、刷新页面
系统行为:
onUnmounted(() => {
// 仅清空索引,不删除任何工作链
messageChainMap.value.clear();
console.log('组件卸载,索引已清空(工作链保留在历史记录中)');
});
效果:
// 假设消息 ID:
// message1.id = "msg-001"
// message2.id = "msg-002"
// 1. 选择"消息1-系统-你是一个诗人"
await selectMessage("msg-001");
// messageChainMap: { "msg-001" → "chain-A" }
// ✅ 历史记录:Chain-A [v0: "你是一个诗人"]
// 2. 优化一次
await optimizeMessage("你是一位拥有深厚文学底蕴的资深诗人");
// ✨ 自动应用:message1.content = v1 的内容
// ✅ 历史记录:Chain-A [v0, v1]
// 3. 优化两次
await optimizeMessage("你是一位经验丰富、文笔优美的诗歌创作者");
// ✨ 自动应用:message1.content = v2 的内容
// ✅ 历史记录:Chain-A [v0, v1, v2]
// 4. 切换到"消息2-用户-写一首诗"
await selectMessage("msg-002");
// messageChainMap: { "msg-001" → "chain-A", "msg-002" → "chain-B" }
// ✅ 历史记录:
// - Chain-A [v0, v1, v2] ✅ 保留
// - Chain-B [v0: "写一首诗"]
// 5. 切换回"消息1"
await selectMessage("msg-001");
// ✅ 复用 Chain-A,显示最后的版本 v2
// messageChainMap: { "msg-001" → "chain-A", "msg-002" → "chain-B" }
// 6. 继续优化"消息1"
await optimizeMessage("你是一位才华横溢的诗歌大师");
// ✨ 在 Chain-A 上继续添加 v3
// ✨ 自动应用:message1.content = v3 的内容
// ✅ 历史记录:Chain-A [v0, v1, v2, v3] ✅
// 7. 🆕 插入新消息到中间(message3)
messages.value.splice(1, 0, {
id: "msg-003",
role: "user",
content: "描述一下春天",
});
// ✨ messageChainMap 依然有效(基于 ID 不受索引变化影响)
// messageChainMap: { "msg-001" → "chain-A", "msg-002" → "chain-B" }
// 8. 组件卸载(切换模式或刷新页面)
onUnmounted(() => {
messageChainMap.value.clear();
});
// ❌ 映射表清空
// ✅ Chain-A, Chain-B 全部保留在历史记录中
// 假设消息 ID:message1.id = "msg-001"
// 1. 选择"消息1-系统-你是一个诗人"
await selectMessage("msg-001");
// messageChainMap: { "msg-001" → "chain-A" }
// ✅ 历史记录:Chain-A [v0: "你是一个诗人"]
// 2. 优化多次
await optimizeMessage("v1");
// ✨ 自动应用:message1.content = "v1"
// ✅ 历史记录:Chain-A [v0, v1]
await optimizeMessage("v2");
// ✨ 自动应用:message1.content = "v2"
// ✅ 历史记录:Chain-A [v0, v1, v2]
await optimizeMessage("v3");
// ✨ 自动应用:message1.content = "v3"
// ✅ 历史记录:Chain-A [v0, v1, v2, v3]
// 3. 对 v3 不满意,想恢复到 v2
await switchVersion(v2.id);
// 预览区域显示 v2 的内容
// 4. 应用 v2 到会话
await applyToConversation();
// message1.content = v2 的内容 ✅
// 5. 用户刷新页面(意外或主动)
// ❌ messageChainMap 清空
// ✅ 历史记录:Chain-A [v0, v1, v2, v3] 仍然保留
// 6. 用户重新打开多轮对话模式
// messageChainMap: {} (空)
// 用户可以重新构建对话,或...
// 7. 用户打开历史记录面板
// 看到所有之前的优化:
// - Chain-A:
// - v0: "你是一个诗人"
// - v1: "你是一位拥有深厚文学底蕴的资深诗人"
// - v2: "你是一位经验丰富、文笔优美的诗歌创作者"
// - v3: "你是一位才华横溢的诗歌大师"
// 8. 用户点击 v2 的"应用"或"复制"
// 🆕 将 v2 的内容填入当前消息(基于消息 ID)
// 可以继续基于此优化
// 9. 🆕 即使消息顺序改变,历史记录依然可以正确关联
// 因为历史记录保存的是消息 ID 而非索引
// packages/ui/src/composables/conversation/useConversationOptimization.ts
import { ref, computed, onUnmounted } from 'vue';
import type { ConversationMessage } from '@prompt-optimizer/core';
import type { IHistoryManager, PromptRecord } from '@prompt-optimizer/core';
import { message } from 'naive-ui';
export function useConversationOptimization(
historyService: IHistoryManager,
currentModel: Ref<string>,
currentTemplate: Ref<string>
) {
const messages = ref<ConversationMessage[]>([]);
const selectedMessageId = ref<string | null>(null); // 🆕 使用消息 ID
// 🆕 纯粹的临时索引(基于消息 ID,不持久化)
const messageChainMap = ref<Map<string, string>>(new Map());
const currentRecordId = ref<string | null>(null);
const versions = ref<PromptRecord[]>([]);
/**
* 🆕 选择消息(智能复用版 - 基于消息 ID)
*/
const selectMessage = async (messageId: string) => {
// 1. 找到消息对象
const message = messages.value.find(m => m.id === messageId);
if (!message) return;
// 2. 切换到新消息
selectedMessageId.value = messageId;
// 3. 🆕 检查是否已有关联的工作链(基于消息 ID)
const existingChainId = messageChainMap.value.get(messageId);
if (existingChainId) {
// 4a. ✅ 复用现有链
const chain = await historyService.getChain(existingChainId);
currentRecordId.value = chain.currentRecord.id;
versions.value = chain.versions;
console.log(`复用消息 ${messageId} 的工作链: ${existingChainId}`);
} else {
// 4b. ✅ 创建新链(自动保存到历史记录)
const chain = await historyService.createNewChain({
id: generateId(),
originalPrompt: message.originalContent || message.content, // 🆕 使用原始内容
optimizedPrompt: message.content, // 当前内容(可能已优化)
type: 'contextSystemOptimize',
timestamp: Date.now(),
modelKey: currentModel.value,
templateId: '',
});
// 5. 建立映射关系(消息 ID → 工作链 ID)
messageChainMap.value.set(messageId, chain.chainId);
currentRecordId.value = chain.rootRecord.id;
versions.value = [chain.rootRecord];
console.log(`为消息 ${messageId} 创建新链: ${chain.chainId}`);
}
};
/**
* 🆕 优化消息(自动应用版 - 基于消息 ID)
*/
const optimizeMessage = async (result: string, reasoning?: string) => {
if (!selectedMessageId.value) return;
// 1. 🆕 获取当前消息的工作链 ID(基于消息 ID)
const chainId = messageChainMap.value.get(selectedMessageId.value);
if (!chainId) return;
// 2. 🆕 查找消息对象
const message = messages.value.find(m => m.id === selectedMessageId.value);
if (!message) return;
// 3. ✅ 添加版本(自动保存到历史记录)
const chain = await historyService.addIteration({
chainId,
originalPrompt: message.content,
optimizedPrompt: result,
modelKey: currentModel.value,
templateId: currentTemplate.value,
iterationNote: reasoning,
});
currentRecordId.value = chain.currentRecord.id;
versions.value = chain.versions;
// 4. ✨ 自动应用到消息
message.content = result;
message.success(`已优化并应用 (v${chain.currentRecord.version})`);
};
/**
* 切换版本(仅预览)
*/
const switchVersion = (recordId: string) => {
currentRecordId.value = recordId;
};
/**
* 🆕 应用到会话(版本回退用 - 基于消息 ID)
*/
const applyToConversation = async () => {
if (!selectedMessageId.value || !currentRecordId.value) return;
// 🆕 查找消息对象(基于消息 ID)
const message = messages.value.find(m => m.id === selectedMessageId.value);
if (!message) return;
const record = await historyService.getRecord(currentRecordId.value);
message.content = record.optimizedPrompt;
const version = versions.value.find(v => v.id === currentRecordId.value)?.version ?? 0;
message.success(`已应用 v${version} 到消息`);
};
/**
* 🆕 删除消息(仅移除 ID 映射 - 无需重建映射表)
*/
const deleteMessage = (messageId: string) => {
// 1. 🆕 从映射表中移除消息 ID(不删除工作链)
messageChainMap.value.delete(messageId);
// 2. 🆕 删除消息(基于 ID)
const index = messages.value.findIndex(m => m.id === messageId);
if (index !== -1) {
messages.value.splice(index, 1);
}
// 3. ✨ 无需重建映射表(ID 稳定,不受索引变化影响)
};
/**
* 快捷还原到原始内容
*/
const restoreOriginal = async () => {
if (versions.value.length === 0) return;
currentRecordId.value = versions.value[0].id;
await applyToConversation();
};
/**
* 当前版本号
*/
const currentVersion = computed(() => {
if (!currentRecordId.value) return 0;
const record = versions.value.find(v => v.id === currentRecordId.value);
return record?.version ?? 0;
});
/**
* 当前显示的内容
*/
const displayContent = computed(() => {
if (!currentRecordId.value) return '';
const record = versions.value.find(v => v.id === currentRecordId.value);
return record?.optimizedPrompt ?? '';
});
/**
* 组件卸载:清空索引
*/
onUnmounted(() => {
// ✅ 仅清空索引,工作链保留在历史记录中
messageChainMap.value.clear();
console.log('组件卸载,索引已清空(工作链保留在历史记录中)');
});
return {
// 状态
messages,
selectedMessageId, // 🆕 返回消息 ID
messageChainMap,
currentRecordId,
versions,
currentVersion,
displayContent,
// 操作方法
selectMessage,
optimizeMessage,
switchVersion,
applyToConversation,
deleteMessage,
restoreOriginal,
};
}
代码行数统计:约 62 行(v3: ~60 行,v2: ~90 行)
v3.1 核心改进:
selectedMessageId 而非 selectedMessageIndex)messageChainMap 改为 Map<string, string>(消息 ID → 工作链 ID)originalContent 保证原始内容不丢失文件位置:
packages/ui/src/
├── composables/
│ └── conversation/
│ └── useConversationOptimization.ts # 核心状态管理(60 行)
└── components/context-mode/
├── ContextSystemWorkspace.vue # 多轮对话模式主界面
├── ConversationManager.vue # 会话管理器组件(扩展消息选择)
└── OptimizationResultPanel.vue # 优化结果展示面板(新增)
├── VersionSelector.vue # 版本选择器
└── ActionButtons.vue # 应用/保存按钮
/**
* 🆕 调用 LLM 优化当前选中的消息(基于消息 ID)
*/
const handleOptimize = async () => {
if (!selectedMessageId.value) return;
// 🆕 查找消息对象(基于消息 ID)
const message = messages.value.find(m => m.id === selectedMessageId.value);
if (!message) return;
// 构造优化请求(使用标准 ConversationMessage)
const request: OptimizationRequest = {
targetPrompt: message.content,
optimizationMode: 'system',
contextMode: 'system', // 多轮对话模式
modelKey: currentModel.value,
templateId: currentTemplate.value,
// 传递完整会话上下文
advancedContext: {
messages: messages.value, // ✅ ConversationMessage[] - 直接可用
variables: {},
tools: [],
},
};
// 调用优化服务
const result = await promptService.optimizePrompt(request);
// 自动保存为新版本
await optimizeMessage(result);
};
改进说明:
selectedMessageId.value)| 限制 | 说明 | 理由 |
|---|---|---|
| ❌ 不支持同时优化多条消息 | 单选模式,一次只能优化一条 | 简化交互逻辑,避免状态管理复杂度 |
| ✅ messageChainMap 不持久化 | 刷新页面后索引清空 | 仅作为临时索引,数据在历史记录中 |
| ✅ 所有工作链自动保存 | 无需用户手动操作 | 完全依赖历史记录系统 |
| 功能 | 说明 | 实现方式 |
|---|---|---|
| ✅ 智能复用工作链 | 切换消息时自动定位已有链 | messageChainMap 临时索引 |
| ✅ 自动应用优化结果 | 优化即生效 | 减少用户操作步骤 |
| ✅ 版本管理与回退 | 支持多次优化和版本切换 | PromptRecordChain 系统 |
| ✅ 完整历史记录 | 所有优化自动保存 | 历史记录系统(已有) |
目标: 实现基础优化流程
useConversationOptimization composableConversationManager 支持消息选择OptimizationResultPanel 组件ContextSystemWorkspace交付物:
目标: 完善版本管理 UI
VersionSelector 组件交付物:
目标: 实现保存到收藏
saveToFavorite 方法交付物:
目标: 确保功能稳定性
行为说明:
messageChainMap 索引清空设计理由:
可选增强(未来):
当前方案: 保持现有的模板选择功能
context-general-optimize、context-professional-optimize 等)扩展方案(未来):
packages/core/src/services/prompt/types.ts - ConversationMessage 定义packages/core/src/services/history/types.ts - PromptRecord 定义packages/ui/src/components/context-mode/ConversationManager.vue - 会话管理器packages/ui/src/composables/mode/useProSubMode.ts - 子模式管理docs/workspace/multi-turn-design-compatibility-analysis.md - 兼容性分析报告93c3709 - 临时禁用系统模式e2a62d8 - 修复跨功能模式切换时的 subMode 设置错误PromptRecordChain 系统messageChainMap 临时索引,自动复用已有工作链onUnmounted(() => messageChainMap.value.clear())| 维度 | v1(直接删除) | v2(智能复用) | v3(极简全自动) | v3.1(稳定映射) |
|---|---|---|---|---|
| 核心变量 | 1 个 | 1 个 | 1 个 | 1 个(messageChainMap) |
| 映射键 | 索引 | 索引 | 索引 | 🆕 消息 ID |
| 映射稳定性 | ❌ 低 | ❌ 低 | ❌ 低 | ✅ 高 |
| 用户体验 | ⭐⭐⭐☆☆ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 数据安全 | ⭐⭐☆☆☆ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 自动应用 | ❌ | ✅ | ✅ | ✅ |
| 自动保存 | ❌ | ⚠️ 需手动 | ✅ 全自动 | ✅ 全自动 |
| 切换行为 | 删除旧链 | 保留旧链 | 保留旧链 | 保留旧链 |
| 清理逻辑 | 立即删除 | 会话结束清理 | 不清理 | 不清理 |
| 操作步骤 | 3步 | 1步 | 1步 | 1步 |
| 用户负担 | 需记得保存 | 需记得保存 | 零负担 | 零负担 |
| 代码量 | ~60 行 | ~90 行 | ~60 行 | ~62 行 |
| 插入/删除影响 | ❌ 映射失效 | ❌ 映射失效 | ❌ 映射失效 | ✅ 无影响 |
| 重建映射表 | ❌ | ❌ | ❌ | ✅ 无需 |
极致简单
零心智负担
完全依赖现有系统
🆕 稳定的映射关系
originalContent 字段)| 方案 | 复杂度 | 代码量 | 用户体验 | 数据安全 | 映射稳定性 | YAGNI | 推荐度 |
|---|---|---|---|---|---|---|---|
| 最初设计(双层结构) | 高 | ~500 行 | ⭐⭐⭐☆☆ | ⭐⭐⭐⭐☆ | ⭐⭐⭐☆☆ | ❌ | ❌ |
| v1(直接删除) | 低 | ~60 行 | ⭐⭐⭐☆☆ | ⭐⭐☆☆☆ | ⭐⭐☆☆☆ | ✅ | ⚠️ |
| v2(智能复用) | 中 | ~90 行 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐☆☆☆ | ⚠️ | ✅ |
| v3(极简全自动) | 极低 | ~60 行 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐☆☆☆ | ✅ | ✅✅ |
| v3.1(稳定映射) | 极低 | ~62 行 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ | ✅✅✅ |
回归本质
用户视角
开发视角
🆕 技术优势
originalContent 字段)最后更新: 2025-01-05 作者: Development Team 状态: ✅ v3.1 方案(基于消息 ID 的稳定映射 + 极简全自动,KISS 原则的完美实践) 版本: v1 → v2 → v3 → v3.1(从"手动管理"演进为"全自动保存",再到"稳定映射")
🆕 ConversationMessage 新增字段:
id: string - 唯一标识(用于稳定的映射关系)originalContent?: string - 原始内容(保证不丢失)🆕 messageChainMap 改为基于消息 ID:
Map<number, string> 改为 Map<string, string>message.id)而非索引chainId)🆕 历史记录为独立链:
originalContent 字段保证原始内容永不丢失| 特性 | v3 | v3.1 |
|---|---|---|
| 映射键 | 索引 | 🆕 消息 ID |
| 插入消息后映射 | ❌ 失效 | ✅ 依然有效 |
| 删除消息需要 | 重建映射表 | 🆕 无需重建 |
| 原始内容 | 可能丢失 | 🆕 永不丢失 |
| 代码量 | ~60 行 | ~62 行 |
结论:v3.1 是 v3 的完美进化,在保持极简设计的同时,解决了索引不稳定的根本问题,是真正适合生产环境的最佳方案。
在多轮对话模式下,原有的 TestAreaPanel 组件包含了不必要的UI元素:
这些冗余元素违背了设计原则中的"没有独立的'原始提示词输入框'",需要简化。
采用开放封闭原则(Open-Closed Principle),创建专用组件而非修改现有组件:
ConversationTestPanel 组件(多轮对话模式专用)TestAreaPanel 组件(变量模式和基础模式继续使用)1. 新增组件
packages/ui/src/components/context-mode/ConversationTestPanel.vue
组件特性:
show-compare-toggle="false")TestAreaPanelInstance 接口,确保系统兼容性2. 接口兼容性设计
// 兼容 TestAreaPanelInstance 接口,但忽略对比模式相关参数
handleToolCall(toolCall: ToolCallResult, _testType?: 'original' | 'optimized')
clearToolCalls(_testType?: 'original' | 'optimized' | 'both')
getToolCalls() => { original: [], optimized: toolCalls.value }
3. 组件集成
修改 ContextSystemWorkspace.vue:
<!-- 替换 TestAreaPanel 为 ConversationTestPanel -->
<ConversationTestPanel
ref="testAreaPanelRef"
:optimization-mode="optimizationMode"
:is-test-running="isTestRunning"
:global-variables="globalVariables"
:predefined-variables="predefinedVariables"
:input-mode="inputMode"
:control-bar-layout="controlBarLayout"
:button-size="buttonSize"
:result-vertical-layout="resultVerticalLayout"
@test="handleTestWithVariables"
@open-variable-manager="emit('open-variable-manager')"
@variable-change="(name, value) => emit('variable-change', name, value)"
@save-to-global="(name, value) => emit('save-to-global', name, value)"
>
<template #model-select>
<slot name="test-model-select"></slot>
</template>
<template #single-result>
<slot name="single-result"></slot>
</template>
</ConversationTestPanel>
Props 变更:
testContent: stringisCompareMode: booleanEmits 变更:
update:testContentupdate:isCompareModecompare-toggle4. 测试处理逻辑优化
修改 App.vue 中的 handleTestAreaTest 方法:
const handleTestAreaTest = async (testVariables?: Record<string, string>) => {
// 多轮对话模式(context-system)下,不使用 testContent 和 isCompareMode
// 因为测试内容来自会话消息,且不支持对比模式
const actualTestContent = contextMode.value === 'system' ? '' : testContent.value;
const actualIsCompareMode = contextMode.value === 'system' ? false : isCompareMode.value;
await promptTester.executeTest(
optimizer.prompt,
optimizer.optimizedPrompt,
actualTestContent,
actualIsCompareMode,
testVariables,
getActiveTestPanelInstance()
);
};
5. 类型定义更新
修改 packages/ui/src/components/types/test-area.ts:
// TestAreaPanelInstance 同时兼容 TestAreaPanel 和 ConversationTestPanel
export interface TestAreaPanelInstance {
clearToolCalls: (testType?: 'original' | 'optimized' | 'both') => void
handleToolCall: (toolCall: ToolCallResult, testType: 'original' | 'optimized') => void
getToolCalls: () => TestAreaToolCallState
getVariableValues: () => Record<string, string>
setVariableValues: (values: Record<string, string>) => void
showPreview: () => void
hidePreview: () => void
}
6. Bug 修复
修复 ContextSystemWorkspace.vue 中的 Vue 警告:
<!-- 移除不存在的 prompt 属性 -->
<PromptPanelUI
:optimized-prompt="displayedOptimizedPrompt"
@update:optimizedPrompt="emit('update:optimizedPrompt', $event)"
:reasoning="optimizedReasoning"
<!-- ❌ 移除: :original-prompt="prompt" -->
:is-optimizing="displayedIsOptimizing"
...
/>
新增文件:
packages/ui/src/components/context-mode/ConversationTestPanel.vue (600+ lines)修改文件:
packages/ui/src/components/context-mode/ContextSystemWorkspace.vue
packages/web/src/App.vue
packages/ui/src/components/types/test-area.ts
本次实施严格遵循了设计原则:
✅ KISS(简单至上)
✅ YAGNI(精益求精)
✅ 开放封闭原则
✅ 依赖倒置原则
开发环境测试:
功能验证:
多轮对话模式:
其他模式:
无新增技术债务。本次重构:
实施日期: 2025-01-05 实施者: Development Team 状态: ✅ 已完成并验证 影响范围: 多轮对话模式(上下文-系统模式)UI 层