mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-18 12:04:45 +08:00
Resolved conflicts: - backend/src/gateway/routers/artifacts.py: Keep citations block removal for markdown downloads - frontend/src/components/workspace/messages/message-list-item.tsx: Keep improved citation handling with rehypePlugins, humanMessagePlugins, and CitationsLoadingIndicator Co-authored-by: Cursor <cursoragent@cursor.com>
175 lines
5.6 KiB
TypeScript
175 lines
5.6 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 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(
|
|
[
|
|
`### ${t.settings.memory.markdown.work}`,
|
|
memory.user.workContext.summary || "-",
|
|
"",
|
|
memory.user.workContext.updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.user.workContext.updatedAt)}\``,
|
|
].join("\n"),
|
|
);
|
|
parts.push(
|
|
[
|
|
`### ${t.settings.memory.markdown.personal}`,
|
|
memory.user.personalContext.summary || "-",
|
|
"",
|
|
memory.user.personalContext.updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.user.personalContext.updatedAt)}\``,
|
|
].join("\n"),
|
|
);
|
|
parts.push(
|
|
[
|
|
`### ${t.settings.memory.markdown.topOfMind}`,
|
|
memory.user.topOfMind.summary || "-",
|
|
"",
|
|
memory.user.topOfMind.updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.user.topOfMind.updatedAt)}\``,
|
|
].join("\n"),
|
|
);
|
|
|
|
parts.push(`\n## ${t.settings.memory.markdown.historyBackground}`);
|
|
parts.push(
|
|
[
|
|
`### ${t.settings.memory.markdown.recentMonths}`,
|
|
memory.history.recentMonths.summary || "-",
|
|
"",
|
|
memory.history.recentMonths.updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.history.recentMonths.updatedAt)}\``,
|
|
].join("\n"),
|
|
);
|
|
parts.push(
|
|
[
|
|
`### ${t.settings.memory.markdown.earlierContext}`,
|
|
memory.history.earlierContext.summary || "-",
|
|
"",
|
|
memory.history.earlierContext.updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.history.earlierContext.updatedAt)}\``,
|
|
].join("\n"),
|
|
);
|
|
parts.push(
|
|
[
|
|
`### ${t.settings.memory.markdown.longTermBackground}`,
|
|
memory.history.longTermBackground.summary || "-",
|
|
"",
|
|
memory.history.longTermBackground.updatedAt &&
|
|
`> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.history.longTermBackground.updatedAt)}\``,
|
|
].join("\n"),
|
|
);
|
|
|
|
parts.push(`\n## ${t.settings.memory.markdown.facts}`);
|
|
if (memory.facts.length === 0) {
|
|
parts.push(`_${t.settings.memory.markdown.empty}_`);
|
|
} 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);
|
|
}
|