diff --git a/frontend/src/lib/prompts/exportCompact.ts b/frontend/src/lib/prompts/exportCompact.ts index a828f23..0b93624 100644 --- a/frontend/src/lib/prompts/exportCompact.ts +++ b/frontend/src/lib/prompts/exportCompact.ts @@ -4,31 +4,61 @@ import type { PromptVersion, } from "./types"; -const EXPORT_COMPACT_SYSTEM = `You are Vesti's export compaction assistant. - -Your job is to compress one conversation into a high-fidelity markdown handoff for another AI or engineer who must continue the work with minimal context loss. - -Output must contain these exact headings: -## Background -## Key Questions -## Decisions And Answers -## Reusable Artifacts -## Unresolved - -Hard rules: -1) Use only evidence present in the provided transcript. -2) Prioritize transfer fidelity over aggressive shortening. Do not drop grounded decisions, commands, files, APIs, or next-step context just to be shorter. -3) Preserve concrete details such as filenames, function names, shell commands, URLs, APIs, and code blocks when grounded. -4) Keep chronological logic intact: if a later decision depends on earlier context, make that dependency explicit. -5) If a section has no grounded evidence, write a conservative placeholder instead of inventing details. -6) Respect the requested locale. -7) Output markdown only. Do not wrap the whole answer in code fences. -8) If the transcript contains reusable code or command snippets, preserve them in markdown-friendly form rather than paraphrasing them away.`; +const EXPORT_COMPACT_SYSTEM = `将技术对话压缩为其他AI可接续的格式。 + +输出必须包含以下标记: +[主题] 一句话概括 +[背景] 问题背景和约束 +[关键决策] 编号列表,每项包含"决策内容 - 选择理由" +[核心代码] \`\`\`语言\n完整代码\n\`\`\` +[待解决问题] 仍需处理的事项 +[来源] X轮对话 + +规则: +1. 使用对话中实际存在的信息,不编造 +2. 代码必须完整,禁止截断 +3. 决策必须说明"为什么选A而不是B" +4. 去除寒暄和重复内容 +5. 保留文件名、函数名、命令等具体细节 +6. 如果某部分无内容,使用占位符如"无代码"或"已解决" +7. 仅输出markdown,不要包裹代码块 +8. 用中文输出 + +示例: +[主题] React虚拟滚动实现 + +[背景] 需渲染10万+数据导致卡顿。使用react-window时遇到动态高度问题。 + +[关键决策] +1. 使用VariableSizeList替代FixedSizeList - Fixed无法处理动态高度,会导致布局错乱 +2. 设置estimatedItemSize=350 - 基于业务数据80%条目在300-400px + +[核心代码] +\`\`\`jsx +import { VariableSizeList } from 'react-window'; +function List({ items }) { + const getItemSize = (i) => items[i].height || 350; + return ( + + {({ index, style }) =>
{items[index].content}
} +
+ ); +} +\`\`\` + +[待解决问题] 需要测试边界情况:当列表为空时的处理 + +[来源] 8轮对话`; function formatDateTime(value: number): string { - return new Date(value).toLocaleString("en-US", { + return new Date(value).toLocaleString("zh-CN", { year: "numeric", - month: "short", + month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", @@ -36,7 +66,7 @@ function formatDateTime(value: number): string { } function formatTime(value: number): string { - return new Date(value).toLocaleTimeString("en-US", { + return new Date(value).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", }); @@ -44,95 +74,79 @@ function formatTime(value: number): string { function toTranscript(messages: Message[]): string { if (!messages.length) { - return "[No messages available]"; + return "[无消息]"; } return messages .map((message, index) => { - const role = message.role === "user" ? "User" : "AI"; + const role = message.role === "user" ? "用户" : "AI"; return `${index + 1}. [${formatTime(message.created_at)}] [${role}] ${message.content_text}`; }) .join("\n"); } function buildCompactPrompt(payload: ExportCompressionPromptPayload): string { - const isStepProfile = payload.profile === "step_flash_concise"; - const brevityRule = isStepProfile - ? "Keep bullets concise and prioritize grounded artifacts over narrative polish." - : "Preserve the full implementation trail when it is grounded, even if the output becomes longer."; + return `将以下技术对话压缩为AI Handoff格式。 - return `Create a high-fidelity export handoff for this conversation. - -Metadata: -- Title: ${payload.conversationTitle || "(untitled)"} -- Platform: ${payload.conversationPlatform || "unknown"} -- StartedAt: ${ +元数据: +- 标题: ${payload.conversationTitle || "(未命名)"} +- 平台: ${payload.conversationPlatform || "unknown"} +- 开始时间: ${ payload.conversationOriginAt ? formatDateTime(payload.conversationOriginAt) : "unknown" } -- Locale: ${payload.locale || "zh"} -- MessageCount: ${payload.messages.length} +- 对话轮数: ${payload.messages.length} -Transcript: +对话记录: ${toTranscript(payload.messages)} -Output requirements: -1) Use the exact headings listed in the system prompt. -2) Optimize for AI handoff, not for skimming: preserve background, key asks, decisions, constraints, artifacts, and unresolved work. -3) In ## Background, include the task frame, constraints, and any context another assistant would need before acting. -4) In ## Key Questions, keep only the questions that actually drove the work forward. -5) In ## Decisions And Answers, capture grounded resolutions, tradeoffs, and chosen implementation paths. -6) In ## Reusable Artifacts, preserve filenames, commands, APIs, functions, and code blocks when grounded. -7) In ## Unresolved, call out what remains open, risky, or needs continuation. -8) ${brevityRule} -9) If evidence is sparse, keep the structure and use conservative placeholders. -10) Write the final output in ${payload.locale === "en" ? "natural English" : "natural Chinese"}. -11) Output markdown only.`; +要求: +1. 严格使用系统提示中的标记格式 +2. [关键决策]必须包含选择理由(为什么选这个而不是其他方案) +3. [核心代码]保持完整可运行,包含必要的import +4. 去除寒暄、重复和与主题无关的内容 +5. 如果对话是中文,用中文输出;如果是英文,可以保留关键术语 +6. 输出纯markdown,不要包裹在代码块中`; } function buildCompactFallbackPrompt( payload: ExportCompressionPromptPayload ): string { - const fallbackNote = - payload.profile === "step_flash_concise" - ? "Keep it tight, but do not lose grounded files, commands, APIs, or unresolved work." - : "Preserve grounded files, commands, APIs, code, and unresolved work whenever they appear."; - - return `Write a shorter fallback markdown handoff for this conversation. -You must keep these exact headings: -## Background -## Key Questions -## Decisions And Answers -## Reusable Artifacts -## Unresolved - -Keep it shorter and more conservative than the main prompt. -${fallbackNote} -Use ${payload.locale === "en" ? "English" : "Chinese"}. -Output markdown only. - -Transcript: -${toTranscript(payload.messages)}`; + return `将以下对话压缩为简洁的AI交接格式: + +标题: ${payload.conversationTitle || "(未命名)"} +轮数: ${payload.messages.length} + +对话记录: +${toTranscript(payload.messages)} + +必须包含以下标记: +[主题] 一句话概括 +[背景] 关键背景 +[关键决策] 核心决策点 +[核心代码] 代码块(如有) +[来源] ${payload.messages.length}轮对话 + +保持简洁,但保留关键决策和代码。用中文输出。`; } export const CURRENT_EXPORT_COMPACT_PROMPT: PromptVersion = { - version: "v1.2.0-export-compact-kimi-step-profiled", - createdAt: "2026-03-16", - description: - "High-fidelity compact export handoff prompt with Kimi-rich and Step-concise profiles.", + version: "v1.2.0-export-compact-zh", + createdAt: "2026-03-17", + description: "中文AI Handoff格式,保留决策理由和完整代码", system: EXPORT_COMPACT_SYSTEM, - fallbackSystem: "You are a cautious technical export assistant. Output markdown only.", + fallbackSystem: "你是技术对话压缩助手,输出简洁的中文摘要。", userTemplate: buildCompactPrompt, fallbackTemplate: buildCompactFallbackPrompt, }; export const EXPERIMENTAL_EXPORT_COMPACT_PROMPT: PromptVersion = { - version: "v1.2.0-export-compact-kimi-step-profiled-exp", - createdAt: "2026-03-16", - description: "Experimental profiled compact export handoff variant.", + version: "v1.2.0-export-compact-zh-exp", + createdAt: "2026-03-17", + description: "Experimental Chinese compact export variant.", system: EXPORT_COMPACT_SYSTEM, - fallbackSystem: "You are a cautious technical export assistant. Output markdown only.", + fallbackSystem: "你是技术对话压缩助手,输出简洁的中文摘要。", userTemplate: buildCompactPrompt, fallbackTemplate: buildCompactFallbackPrompt, }; diff --git a/frontend/src/lib/prompts/exportSummary.ts b/frontend/src/lib/prompts/exportSummary.ts index b2d344c..3918f30 100644 --- a/frontend/src/lib/prompts/exportSummary.ts +++ b/frontend/src/lib/prompts/exportSummary.ts @@ -4,40 +4,89 @@ import type { PromptVersion, } from "./types"; -const EXPORT_SUMMARY_SYSTEM = `You are Vesti's export summary assistant. +const EXPORT_SUMMARY_SYSTEM = `将技术对话整理为可直接存入Notion/Obsidian的知识卡片。 -Your job is to compress one conversation into a note-ready markdown summary for future human recall. +输出格式(严格遵循Markdown): +\`\`\`markdown +# [标题] 技术主题 +> [日期] | [平台] | [对话轮数] -Output must contain these exact headings: ## TL;DR -## Problem Frame -## Important Moves -## Reusable Snippets -## Next Steps -## Tags - -Hard rules: -1) Use only evidence present in the provided transcript. -2) Prefer concrete phrasing over vague praise or filler. -3) Keep the summary readable by a human returning later with limited context. -4) Align the content structure with Vesti's conversation_summary.v2 mindset: core question, key moves, reusable insights, unresolved work, and next actions. -5) If evidence is missing, state that conservatively instead of inventing details. -6) Respect the requested locale. -7) Output markdown only. Do not wrap the whole answer in code fences. -8) Reusable snippets may include commands, files, APIs, or code only when grounded in the transcript.`; +[一句话总结核心内容,包含关键数据和结果] + +## 问题定义(如有) +**症状**:[问题表现] +**约束**:[限制条件] +**影响**:[影响范围] + +## 方案对比(如涉及技术选型) +| 方案 | 维度1 | 维度2 | 维度3 | 决策 | +|------|-------|-------|-------|------| +| 方案A | ... | ... | ... | ❌/✅ | +| 方案B | ... | ... | ... | ❌/✅ | + +## 可复用代码 +### 场景1:xxx +[完整可运行的代码块] + +### 场景2:xxx +[变体代码] + +## 关键决策依据 +1. **[决策点]**:[选择理由,包含"为什么选A而非B"] +2. **[决策点]**:[选择理由] + +## 踩坑记录(如有) +- ❌ [错误做法] → [后果] +- ✅ [正确做法] + +## 后续行动 +- [ ] [具体可执行的任务] +- [ ] [具体可执行的任务] + +## 相关资源 +- [链接描述](url) +- [内部文档](wiki-link) + +## 标签 +#标签1 #标签2 #标签3 +\`\`\` + +核心原则: +1. **可复用**:读者无需看原始对话,仅看笔记就能理解并应用 +2. **结构化**:清晰的层级,方便快速定位和检索 +3. **完整性**:包含问题、方案、代码、决策依据、后续行动 +4. **可执行**:代码可直接复制使用,步骤可立即执行 +5. **决策依据**:每个选择必须说明理由 + +特殊场景: +- 纯调研类(无代码):保留方案对比和决策依据,代码块写"无代码" +- 超长对话(>100轮):按子主题拆分多个##二级标题 +- 调试/排查类:必须保留 现象→排查步骤→根因→修复 的完整链条 + +质量自检(输出前必查): +- [ ] TL;DR是否包含关键数据/结果? +- [ ] 代码是否完整可运行?(包含必要import) +- [ ] 是否有"为什么选这个方案"的决策依据? +- [ ] 标签是否便于检索? +- [ ] 如果我是3个月后回看,能否快速理解?`; function formatDateTime(value: number): string { - return new Date(value).toLocaleString("en-US", { + return new Date(value).toLocaleString("zh-CN", { year: "numeric", - month: "short", + month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } +function formatDate(value: number): string { + return new Date(value).toISOString().split("T")[0]; +} + function formatTime(value: number): string { - return new Date(value).toLocaleTimeString("en-US", { + return new Date(value).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", }); @@ -45,101 +94,93 @@ function formatTime(value: number): string { function toTranscript(messages: Message[]): string { if (!messages.length) { - return "[No messages available]"; + return "[无消息]"; } return messages .map((message, index) => { - const role = message.role === "user" ? "User" : "AI"; + const role = message.role === "user" ? "用户" : "AI"; return `${index + 1}. [${formatTime(message.created_at)}] [${role}] ${message.content_text}`; }) .join("\n"); } function buildSummaryPrompt(payload: ExportCompressionPromptPayload): string { - const isStepProfile = payload.profile === "step_flash_concise"; - const profileInstruction = isStepProfile - ? "Favor structural coverage and concise grounded bullets over essay-like phrasing." - : "Favor richer problem framing and clearer reconstruction of the thread's actual progression."; - - return `Create a note-ready export summary for this conversation. - -Metadata: -- Title: ${payload.conversationTitle || "(untitled)"} -- Platform: ${payload.conversationPlatform || "unknown"} -- StartedAt: ${ - payload.conversationOriginAt - ? formatDateTime(payload.conversationOriginAt) - : "unknown" - } -- Locale: ${payload.locale || "zh"} -- MessageCount: ${payload.messages.length} + return `将以下技术对话整理为知识卡片格式。 + +元数据: +- 标题: ${payload.conversationTitle || "(未命名)"} +- 平台: ${payload.conversationPlatform || "unknown"} +- 日期: ${payload.conversationOriginAt ? formatDate(payload.conversationOriginAt) : new Date().toISOString().split("T")[0]} +- 对话轮数: ${payload.messages.length} -Transcript: +对话记录: ${toTranscript(payload.messages)} -Output requirements: -1) Use the exact headings listed in the system prompt. -2) Keep this optimized for future recall: crisp TL;DR, problem framing, key moves, reusable snippets, next steps, and tags. -3) Let ## Problem Frame align with the core question and constraints that shaped the thread. -4) Let ## Important Moves reflect the actual progression of the discussion, similar to a lightweight thinking_journey. -5) Let ## Next Steps reflect grounded actionable follow-ups, not generic advice. -6) Let ## Tags stay concrete and limited to 3-5 useful tags when evidence exists. -7) ${profileInstruction} -8) Keep bullets concise and grounded. -9) If evidence is sparse, keep the structure and use conservative placeholders. -10) Write the final output in ${payload.locale === "en" ? "natural English" : "natural Chinese"}. -11) Output markdown only.`; +输出要求: +1. 严格遵循系统提示中的知识卡片格式 +2. TL;DR必须包含关键数据或结果(如性能提升、问题解决等) +3. 代码块完整可运行,包含必要的import和类型定义 +4. 方案对比表格要包含决策列(✅/❌) +5. 后续行动使用复选框格式(- [ ]) +6. 标签使用#开头,便于后续检索 +7. 如果对话是中文,用中文输出;如果是英文,可以保留关键术语 +8. 输出纯markdown,不要包裹在代码块中`; } function buildSummaryFallbackPrompt( payload: ExportCompressionPromptPayload ): string { - const fallbackGuidance = - payload.profile === "step_flash_concise" - ? "Prefer compact bullets that preserve the thread's concrete actions and artifacts." - : "Prefer stronger problem framing and more explicit important moves when evidence exists."; + return `将以下对话整理为简化的知识卡片: + +标题: ${payload.conversationTitle || "(未命名)"} +平台: ${payload.conversationPlatform || "unknown"} +轮数: ${payload.messages.length} + +对话记录: +${toTranscript(payload.messages)} - return `Write a markdown export summary for this conversation. +必须包含以下章节: +# 标题 +> 日期 | 平台 | ${payload.messages.length}轮 -You must use these exact headings: ## TL;DR -## Problem Frame -## Important Moves -## Reusable Snippets -## Next Steps -## Tags - -Requirements: -1) Keep the structure aligned with a v2-style thinking summary, but do not output JSON. -2) Use grounded evidence only. -3) Preserve commands, files, APIs, and code references when they exist. -4) Even if the transcript is sparse, keep all headings and fill them conservatively. -5) ${fallbackGuidance} -6) Use ${payload.locale === "en" ? "English" : "Chinese"}. -7) Output markdown only. - -Transcript: -${toTranscript(payload.messages)}`; +一句话总结 + +## 问题定义 +关键问题描述 + +## 方案对比 +简要对比(如有) + +## 可复用代码 +代码块或"无代码" + +## 后续行动 +- [ ] 待办事项 + +## 标签 +#相关标签 + +保持简洁但结构完整。用中文输出。`; } export const CURRENT_EXPORT_SUMMARY_PROMPT: PromptVersion = { - version: "v1.2.0-export-summary-kimi-step-profiled", - createdAt: "2026-03-16", - description: - "Summary export prompt for human-readable notes with Kimi-rich and Step-concise profiles.", + version: "v1.2.0-export-summary-zh", + createdAt: "2026-03-17", + description: "中文知识卡片格式,适合存入Notion/Obsidian", system: EXPORT_SUMMARY_SYSTEM, - fallbackSystem: "You are a concise technical export assistant. Output markdown only.", + fallbackSystem: "你是知识整理助手,将对话转换为结构化的中文笔记。", userTemplate: buildSummaryPrompt, fallbackTemplate: buildSummaryFallbackPrompt, }; export const EXPERIMENTAL_EXPORT_SUMMARY_PROMPT: PromptVersion = { - version: "v1.2.0-export-summary-kimi-step-profiled-exp", - createdAt: "2026-03-16", - description: "Experimental profiled summary export variant.", + version: "v1.2.0-export-summary-zh-exp", + createdAt: "2026-03-17", + description: "Experimental Chinese knowledge export variant.", system: EXPORT_SUMMARY_SYSTEM, - fallbackSystem: "You are a concise technical export assistant. Output markdown only.", + fallbackSystem: "你是知识整理助手,将对话转换为结构化的中文笔记。", userTemplate: buildSummaryPrompt, fallbackTemplate: buildSummaryFallbackPrompt, }; diff --git a/frontend/src/sidepanel/components/BatchActionBar.tsx b/frontend/src/sidepanel/components/BatchActionBar.tsx index ad89ad0..e07433a 100644 --- a/frontend/src/sidepanel/components/BatchActionBar.tsx +++ b/frontend/src/sidepanel/components/BatchActionBar.tsx @@ -3,6 +3,9 @@ import { CheckSquare, Copy, Download, + FileCode, + FileJson, + FileText, Loader2, Square, Trash2, @@ -81,19 +84,17 @@ const EXPORT_MODE_OPTIONS: Array<{ { mode: "full", label: "Full", - description: "Keep the complete thread transcript locally.", + description: "Complete conversation history.", }, { mode: "compact", - label: "Compact", - description: - "AI handoff format. Tries current LLM settings first, then local fallback.", + label: "AI Handoff", + description: "For other AI assistants. Preserves decisions, code, and context.", }, { mode: "summary", - label: "Summary", - description: - "Human note format. Tries current LLM settings first, then local fallback.", + label: "Knowledge Export", + description: "For notes & docs. Structured knowledge card format.", }, ]; @@ -194,53 +195,63 @@ export function BatchActionBar({ -

Export mode

-
-
- {EXPORT_MODE_OPTIONS.map((option) => { - const active = exportMode === option.mode; - return ( - - ); - })} -
+ + + {option.description} + + + ); + })}
-

- {selectedMode.description} -

-

Export format

-
+

+ Export format +

+
{EXPORT_OPTIONS.map((option) => { const active = selectedExportFormat === option.format; + const Icon = option.format === "md" ? FileCode : option.format === "txt" ? FileText : FileJson; return ( ); })}
-

- Current format: {selectedFormat.name} -