feat: init sproutclaw-web — Go+Gin backend + React frontend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
123
frontend/src/utils/exportConversation.ts
Normal file
123
frontend/src/utils/exportConversation.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { ChatMessage } from "../types/message";
|
||||
import { escHtml } from "./format";
|
||||
|
||||
function roleLabel(role: ChatMessage["role"]): string {
|
||||
switch (role) {
|
||||
case "user":
|
||||
return "用户";
|
||||
case "assistant":
|
||||
return "助手";
|
||||
case "thinking":
|
||||
return "思考";
|
||||
case "tool_call":
|
||||
return "工具";
|
||||
case "system":
|
||||
return "系统";
|
||||
case "bash":
|
||||
return "Shell";
|
||||
case "compaction_summary":
|
||||
return "压缩摘要";
|
||||
case "branch_summary":
|
||||
return "分支摘要";
|
||||
default:
|
||||
return "消息";
|
||||
}
|
||||
}
|
||||
|
||||
function exportableMessages(messages: ChatMessage[]): ChatMessage[] {
|
||||
return messages.filter((m) => !m.streaming && m.content.trim());
|
||||
}
|
||||
|
||||
export function messagesToMarkdown(title: string, messages: ChatMessage[]): string {
|
||||
const lines = [`# ${title}`, ""];
|
||||
for (const m of exportableMessages(messages)) {
|
||||
switch (m.role) {
|
||||
case "user":
|
||||
lines.push("## 用户", "", m.content, "");
|
||||
break;
|
||||
case "assistant":
|
||||
lines.push("## 助手", "", m.content, "");
|
||||
break;
|
||||
case "thinking":
|
||||
lines.push("## 思考", "", m.content, "");
|
||||
break;
|
||||
case "tool_call":
|
||||
lines.push("## 工具", "", "```", m.content, "```", "");
|
||||
break;
|
||||
case "system":
|
||||
lines.push(`> ${m.content}`, "");
|
||||
break;
|
||||
case "bash": {
|
||||
const cmd = m.bashCommand ? `$ ${m.bashCommand}` : "Shell";
|
||||
lines.push(`## Shell (${cmd})`, "", "```", m.content, "```", "");
|
||||
break;
|
||||
}
|
||||
case "compaction_summary":
|
||||
lines.push("## 压缩摘要", "", m.content, "");
|
||||
break;
|
||||
case "branch_summary":
|
||||
lines.push("## 分支摘要", "", m.content, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `${lines.join("\n").trimEnd()}\n`;
|
||||
}
|
||||
|
||||
export function messagesToMinimalHtml(title: string, messages: ChatMessage[]): string {
|
||||
const sections = exportableMessages(messages)
|
||||
.map((m) => {
|
||||
const label = roleLabel(m.role);
|
||||
return `<section class="block"><h2>${escHtml(label)}</h2><pre>${escHtml(m.content)}</pre></section>`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${escHtml(title)}</title>
|
||||
<style>
|
||||
body{font-family:"Maple Mono CN",ui-monospace,monospace;letter-spacing:-0.05em;max-width:720px;margin:2rem auto;padding:0 1rem;line-height:1.55;color:#111;background:#fff}
|
||||
h1{font-size:1.25rem;font-weight:600;margin:0 0 1.25rem}
|
||||
.block{margin:0 0 1rem}
|
||||
h2{font-size:.75rem;font-weight:600;color:#6b7280;margin:0 0 .35rem;text-transform:uppercase;letter-spacing:.04em}
|
||||
pre{margin:0;white-space:pre-wrap;word-break:break-word;font-size:.9375rem;font-family:inherit}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${escHtml(title)}</h1>
|
||||
${sections}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
export function safeExportFilename(title: string): string {
|
||||
const cleaned = title.replace(/[\\/:*?"<>|]/g, "_").trim();
|
||||
return cleaned || "conversation";
|
||||
}
|
||||
|
||||
export function downloadTextFile(filename: string, content: string, mime: string): void {
|
||||
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function singleAssistantMarkdown(content: string): string {
|
||||
return content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
||||
}
|
||||
|
||||
export function messageMarkdownFilename(content: string): string {
|
||||
const snippet =
|
||||
content
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.slice(0, 24)
|
||||
.replace(/[\\/:*?"<>|]/g, "_") || "message";
|
||||
return `${snippet}.md`;
|
||||
}
|
||||
Reference in New Issue
Block a user