mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-20 23:21:06 +00:00
f87d5678f3
- 添加 rehype-raw 依赖以支持在 markdown 中渲染 HTML Add rehype-raw dependency to support HTML rendering in markdown - 重构 memory-settings-page,提取 formatMemorySection 函数减少重复代码 Refactor memory-settings-page by extracting formatMemorySection function to reduce code duplication - 改进空状态显示,使用 HTML span 标签替代 markdown 斜体,提供更好的样式控制 Improve empty state display by using HTML span tags instead of markdown italics for better style control - 为 skill-settings-page 添加完整的国际化支持,替换硬编码的英文文本 Add complete i18n support for skill-settings-page, replacing hardcoded English text - 更新国际化文件,添加技能设置页面的空状态文本(中英文) Update i18n files with empty state text for skill settings page (both Chinese and English) - 在 streamdown 插件配置中添加 rehypeRaw 以支持 HTML 渲染 Add rehypeRaw to streamdown plugins configuration to support HTML rendering Co-authored-by: Cursor <cursoragent@cursor.com>
191 lines
5.4 KiB
TypeScript
191 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import { Streamdown } from "streamdown";
|
|
|
|
import { useI18n } from "@/core/i18n/hooks";
|
|
import { useMemory } from "@/core/memory/hooks";
|
|
import type { UserMemory } from "@/core/memory/types";
|
|
import { streamdownPlugins } from "@/core/streamdown/plugins";
|
|
import { pathOfThread } from "@/core/threads/utils";
|
|
import { formatTimeAgo } from "@/core/utils/datetime";
|
|
|
|
import { SettingsSection } from "./settings-section";
|
|
|
|
function confidenceToLevelKey(confidence: unknown): {
|
|
key: "veryHigh" | "high" | "normal" | "unknown";
|
|
value?: number;
|
|
} {
|
|
if (typeof confidence !== "number" || !Number.isFinite(confidence)) {
|
|
return { key: "unknown" };
|
|
}
|
|
|
|
// Clamp to [0, 1] since confidence is expected to be a probability-like score.
|
|
const value = Math.min(1, Math.max(0, confidence));
|
|
|
|
// 3 levels:
|
|
// - veryHigh: [0.85, 1]
|
|
// - high: [0.65, 0.85)
|
|
// - normal: [0, 0.65)
|
|
if (value >= 0.85) return { key: "veryHigh", value };
|
|
if (value >= 0.65) return { key: "high", value };
|
|
return { key: "normal", value };
|
|
}
|
|
|
|
function formatMemorySection(
|
|
title: string,
|
|
summary: string,
|
|
updatedAt: string | undefined,
|
|
t: ReturnType<typeof useI18n>["t"],
|
|
): string {
|
|
const content =
|
|
summary.trim() ||
|
|
`<span class="text-muted-foreground">${t.settings.memory.markdown.empty}</span>`;
|
|
return [
|
|
`### ${title}`,
|
|
content,
|
|
"",
|
|
updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(updatedAt)}\``,
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n");
|
|
}
|
|
|
|
function memoryToMarkdown(
|
|
memory: UserMemory,
|
|
t: ReturnType<typeof useI18n>["t"],
|
|
) {
|
|
const parts: string[] = [];
|
|
|
|
parts.push(`## ${t.settings.memory.markdown.overview}`);
|
|
parts.push(
|
|
`- **${t.common.lastUpdated}**: \`${formatTimeAgo(memory.lastUpdated)}\``,
|
|
);
|
|
|
|
parts.push(`\n## ${t.settings.memory.markdown.userContext}`);
|
|
parts.push(
|
|
formatMemorySection(
|
|
t.settings.memory.markdown.work,
|
|
memory.user.workContext.summary,
|
|
memory.user.workContext.updatedAt,
|
|
t,
|
|
),
|
|
);
|
|
parts.push(
|
|
formatMemorySection(
|
|
t.settings.memory.markdown.personal,
|
|
memory.user.personalContext.summary,
|
|
memory.user.personalContext.updatedAt,
|
|
t,
|
|
),
|
|
);
|
|
parts.push(
|
|
formatMemorySection(
|
|
t.settings.memory.markdown.topOfMind,
|
|
memory.user.topOfMind.summary,
|
|
memory.user.topOfMind.updatedAt,
|
|
t,
|
|
),
|
|
);
|
|
|
|
parts.push(`\n## ${t.settings.memory.markdown.historyBackground}`);
|
|
parts.push(
|
|
formatMemorySection(
|
|
t.settings.memory.markdown.recentMonths,
|
|
memory.history.recentMonths.summary,
|
|
memory.history.recentMonths.updatedAt,
|
|
t,
|
|
),
|
|
);
|
|
parts.push(
|
|
formatMemorySection(
|
|
t.settings.memory.markdown.earlierContext,
|
|
memory.history.earlierContext.summary,
|
|
memory.history.earlierContext.updatedAt,
|
|
t,
|
|
),
|
|
);
|
|
parts.push(
|
|
formatMemorySection(
|
|
t.settings.memory.markdown.longTermBackground,
|
|
memory.history.longTermBackground.summary,
|
|
memory.history.longTermBackground.updatedAt,
|
|
t,
|
|
),
|
|
);
|
|
|
|
parts.push(`\n## ${t.settings.memory.markdown.facts}`);
|
|
if (memory.facts.length === 0) {
|
|
parts.push(
|
|
`<span class="text-muted-foreground">${t.settings.memory.markdown.empty}</span>`,
|
|
);
|
|
} else {
|
|
parts.push(
|
|
[
|
|
`| ${t.settings.memory.markdown.table.category} | ${t.settings.memory.markdown.table.confidence} | ${t.settings.memory.markdown.table.content} | ${t.settings.memory.markdown.table.source} | ${t.settings.memory.markdown.table.createdAt} |`,
|
|
"|---|---|---|---|---|",
|
|
...memory.facts.map((f) => {
|
|
const { key, value } = confidenceToLevelKey(f.confidence);
|
|
const levelLabel =
|
|
t.settings.memory.markdown.table.confidenceLevel[key];
|
|
const confidenceText =
|
|
typeof value === "number" ? `${levelLabel}` : levelLabel;
|
|
return `| ${upperFirst(f.category)} | ${confidenceText} | ${f.content} | [${t.settings.memory.markdown.table.view}](${pathOfThread(f.source)}) | ${formatTimeAgo(f.createdAt)} |`;
|
|
}),
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const markdown = parts.join("\n\n");
|
|
|
|
// Ensure every level-2 heading (##) is preceded by a horizontal rule.
|
|
const lines = markdown.split("\n");
|
|
const out: string[] = [];
|
|
let i = 0;
|
|
for (const line of lines) {
|
|
i++;
|
|
if (i !== 1 && line.startsWith("## ")) {
|
|
if (out.length === 0 || out[out.length - 1] !== "---") {
|
|
out.push("---");
|
|
}
|
|
}
|
|
out.push(line);
|
|
}
|
|
|
|
return out.join("\n");
|
|
}
|
|
|
|
export function MemorySettingsPage() {
|
|
const { t } = useI18n();
|
|
const { memory, isLoading, error } = useMemory();
|
|
return (
|
|
<SettingsSection
|
|
title={t.settings.memory.title}
|
|
description={t.settings.memory.description}
|
|
>
|
|
{isLoading ? (
|
|
<div className="text-muted-foreground text-sm">{t.common.loading}</div>
|
|
) : error ? (
|
|
<div>Error: {error.message}</div>
|
|
) : !memory ? (
|
|
<div className="text-muted-foreground text-sm">
|
|
{t.settings.memory.empty}
|
|
</div>
|
|
) : (
|
|
<div className="rounded-lg border p-4">
|
|
<Streamdown
|
|
className="size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
|
|
{...streamdownPlugins}
|
|
>
|
|
{memoryToMarkdown(memory, t)}
|
|
</Streamdown>
|
|
</div>
|
|
)}
|
|
</SettingsSection>
|
|
);
|
|
}
|
|
|
|
function upperFirst(str: string) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|