feat: 改进设置页面UI和国际化支持 / Improve settings pages UI and i18n support

- 添加 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>
This commit is contained in:
LofiSu
2026-02-10 12:15:37 +08:00
parent 6109216d54
commit f87d5678f3
8 changed files with 82 additions and 49 deletions

View File

@@ -31,6 +31,26 @@ function confidenceToLevelKey(confidence: unknown): {
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"],
@@ -44,65 +64,61 @@ function memoryToMarkdown(
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"),
formatMemorySection(
t.settings.memory.markdown.work,
memory.user.workContext.summary,
memory.user.workContext.updatedAt,
t,
),
);
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"),
formatMemorySection(
t.settings.memory.markdown.personal,
memory.user.personalContext.summary,
memory.user.personalContext.updatedAt,
t,
),
);
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"),
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(
[
`### ${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"),
formatMemorySection(
t.settings.memory.markdown.recentMonths,
memory.history.recentMonths.summary,
memory.history.recentMonths.updatedAt,
t,
),
);
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"),
formatMemorySection(
t.settings.memory.markdown.earlierContext,
memory.history.earlierContext.summary,
memory.history.earlierContext.updatedAt,
t,
),
);
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"),
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(`_${t.settings.memory.markdown.empty}_`);
parts.push(
`<span class="text-muted-foreground">${t.settings.memory.markdown.empty}</span>`,
);
} else {
parts.push(
[

View File

@@ -115,20 +115,20 @@ function SkillSettingsList({
}
function EmptySkill({ onCreateSkill }: { onCreateSkill: () => void }) {
const { t } = useI18n();
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<SparklesIcon />
</EmptyMedia>
<EmptyTitle>No agent skill yet</EmptyTitle>
<EmptyTitle>{t.settings.skills.emptyTitle}</EmptyTitle>
<EmptyDescription>
Put your agent skill folders under the `/skills/custom` folder under
the root folder of DeerFlow.
{t.settings.skills.emptyDescription}
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button onClick={onCreateSkill}>Create Your First Skill</Button>
<Button onClick={onCreateSkill}>{t.settings.skills.emptyButton}</Button>
</EmptyContent>
</Empty>
);