mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 12:24:46 +08:00
feat: add memory settings page
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
DeerFlow is a LangGraph-based AI agent system that provides a powerful "super agent" with sandbox execution capabilities. The backend enables AI agents to execute code, browse the web, manage files, and perform complex multi-step tasks in isolated environments.
|
DeerFlow is a LangGraph-based AI agent system that provides a powerful "super agent" with sandbox execution capabilities. The backend enables AI agents to execute code, browse the web, manage files, and perform complex multi-step tasks in isolated environments.
|
||||||
|
|
||||||
|
---
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **LangGraph Agent Runtime**: Built on LangGraph for robust multi-agent workflow orchestration
|
- **LangGraph Agent Runtime**: Built on LangGraph for robust multi-agent workflow orchestration
|
||||||
@@ -13,6 +14,7 @@ DeerFlow is a LangGraph-based AI agent system that provides a powerful "super ag
|
|||||||
- **Context Summarization**: Automatic conversation summarization for long conversations
|
- **Context Summarization**: Automatic conversation summarization for long conversations
|
||||||
- **Plan Mode**: TodoList middleware for complex multi-step task tracking
|
- **Plan Mode**: TodoList middleware for complex multi-step task tracking
|
||||||
|
|
||||||
|
---
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -33,6 +35,7 @@ DeerFlow is a LangGraph-based AI agent system that provides a powerful "super ag
|
|||||||
- `/api/*` (other) → Gateway API (models, MCP, skills, artifacts, uploads)
|
- `/api/*` (other) → Gateway API (models, MCP, skills, artifacts, uploads)
|
||||||
- `/` (non-API) → Frontend (web interface)
|
- `/` (non-API) → Frontend (web interface)
|
||||||
|
|
||||||
|
---
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
@@ -101,6 +104,7 @@ Direct access:
|
|||||||
- LangGraph: http://localhost:2024
|
- LangGraph: http://localhost:2024
|
||||||
- Gateway: http://localhost:8001
|
- Gateway: http://localhost:8001
|
||||||
|
|
||||||
|
---
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -129,6 +133,7 @@ backend/
|
|||||||
└── Dockerfile # Container build
|
└── Dockerfile # Container build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### LangGraph API (via `/api/langgraph/*`)
|
### LangGraph API (via `/api/langgraph/*`)
|
||||||
@@ -164,6 +169,7 @@ backend/
|
|||||||
**Artifacts**:
|
**Artifacts**:
|
||||||
- `GET /api/threads/{thread_id}/artifacts/{path}` - Download generated artifacts
|
- `GET /api/threads/{thread_id}/artifacts/{path}` - Download generated artifacts
|
||||||
|
|
||||||
|
---
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Main Configuration (`config.yaml`)
|
### Main Configuration (`config.yaml`)
|
||||||
@@ -209,6 +215,7 @@ MCP servers and skills are configured in `extensions_config.json`:
|
|||||||
- Model API keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, etc.
|
- Model API keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, etc.
|
||||||
- Tool API keys: `TAVILY_API_KEY`, `GITHUB_TOKEN`, etc.
|
- Tool API keys: `TAVILY_API_KEY`, `GITHUB_TOKEN`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
@@ -234,6 +241,7 @@ make format # Format code (ruff)
|
|||||||
uv run pytest
|
uv run pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Configuration Guide](docs/CONFIGURATION.md) - Detailed configuration options
|
- [Configuration Guide](docs/CONFIGURATION.md) - Detailed configuration options
|
||||||
@@ -243,6 +251,7 @@ uv run pytest
|
|||||||
- [Summarization](docs/summarization.md) - Context summarization feature
|
- [Summarization](docs/summarization.md) - Context summarization feature
|
||||||
- [Plan Mode](docs/plan_mode_usage.md) - TodoList middleware usage
|
- [Plan Mode](docs/plan_mode_usage.md) - TodoList middleware usage
|
||||||
|
|
||||||
|
---
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
### Core Frameworks
|
### Core Frameworks
|
||||||
@@ -266,10 +275,12 @@ uv run pytest
|
|||||||
- `firecrawl-py` - Web scraping
|
- `firecrawl-py` - Web scraping
|
||||||
- `ddgs` - DuckDuckGo image search
|
- `ddgs` - DuckDuckGo image search
|
||||||
|
|
||||||
|
---
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See the [LICENSE](../LICENSE) file in the project root.
|
See the [LICENSE](../LICENSE) file in the project root.
|
||||||
|
|
||||||
|
---
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
||||||
|
|||||||
@@ -58,11 +58,12 @@ export function Hero({ className }: { className?: string }) {
|
|||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
className="mt-8 scale-105 text-center text-2xl text-shadow-sm"
|
className="mt-8 scale-105 text-center text-2xl text-shadow-sm"
|
||||||
style={{ color: "rgb(180,180,185)" }}
|
style={{ color: "rgb(182,182,188)" }}
|
||||||
>
|
>
|
||||||
DeerFlow is an open-source SuperAgent that researches, codes, and
|
DeerFlow is an open-source SuperAgent that researches, codes, and
|
||||||
|
creates.
|
||||||
<br />
|
<br />
|
||||||
creates. With the help of sandboxes, tools and skills, it handles
|
With the help of sandboxes, memories, tools and skills, it handles
|
||||||
<br />
|
<br />
|
||||||
different levels of tasks that could take minutes to hours.
|
different levels of tasks that could take minutes to hours.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -11,12 +11,7 @@ const features: BentoCardProps[] = [
|
|||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Context Engineering",
|
label: "Context Engineering",
|
||||||
title: "Long/Short-term Memory",
|
title: "Long/Short-term Memory",
|
||||||
description: (
|
description: "Now the agent can better understand you",
|
||||||
<div>
|
|
||||||
<div>Now the agent can better understand you</div>
|
|
||||||
<div className="text-muted-foreground">Coming soon</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
"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[] = [];
|
||||||
|
|
||||||
|
console.info(memory);
|
||||||
|
|
||||||
|
parts.push(`## ${t.settings.memory.markdown.overview}`);
|
||||||
|
parts.push(`- **${t.common.version}**: \`${memory.version}\``);
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { BellIcon, PaletteIcon, SparklesIcon, WrenchIcon } from "lucide-react";
|
import {
|
||||||
|
BellIcon,
|
||||||
|
BrainIcon,
|
||||||
|
PaletteIcon,
|
||||||
|
SparklesIcon,
|
||||||
|
WrenchIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +18,7 @@ import {
|
|||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { AcknowledgePage } from "@/components/workspace/settings/acknowledge-page";
|
import { AcknowledgePage } from "@/components/workspace/settings/acknowledge-page";
|
||||||
import { AppearanceSettingsPage } from "@/components/workspace/settings/appearance-settings-page";
|
import { AppearanceSettingsPage } from "@/components/workspace/settings/appearance-settings-page";
|
||||||
|
import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page";
|
||||||
import { NotificationSettingsPage } from "@/components/workspace/settings/notification-settings-page";
|
import { NotificationSettingsPage } from "@/components/workspace/settings/notification-settings-page";
|
||||||
import { SkillSettingsPage } from "@/components/workspace/settings/skill-settings-page";
|
import { SkillSettingsPage } from "@/components/workspace/settings/skill-settings-page";
|
||||||
import { ToolSettingsPage } from "@/components/workspace/settings/tool-settings-page";
|
import { ToolSettingsPage } from "@/components/workspace/settings/tool-settings-page";
|
||||||
@@ -20,6 +27,7 @@ import { cn } from "@/lib/utils";
|
|||||||
|
|
||||||
type SettingsSection =
|
type SettingsSection =
|
||||||
| "appearance"
|
| "appearance"
|
||||||
|
| "memory"
|
||||||
| "tools"
|
| "tools"
|
||||||
| "skills"
|
| "skills"
|
||||||
| "notification"
|
| "notification"
|
||||||
@@ -29,11 +37,8 @@ type SettingsDialogProps = React.ComponentProps<typeof Dialog> & {
|
|||||||
defaultSection?: SettingsSection;
|
defaultSection?: SettingsSection;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SettingsDialog({
|
export function SettingsDialog(props: SettingsDialogProps) {
|
||||||
defaultSection = "appearance",
|
const { defaultSection = "appearance", ...dialogProps } = props;
|
||||||
onOpenChange,
|
|
||||||
...dialogProps
|
|
||||||
}: SettingsDialogProps) {
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const [activeSection, setActiveSection] =
|
const [activeSection, setActiveSection] =
|
||||||
useState<SettingsSection>(defaultSection);
|
useState<SettingsSection>(defaultSection);
|
||||||
@@ -50,18 +55,27 @@ export function SettingsDialog({
|
|||||||
label: t.settings.sections.notification,
|
label: t.settings.sections.notification,
|
||||||
icon: BellIcon,
|
icon: BellIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "memory",
|
||||||
|
label: t.settings.sections.memory,
|
||||||
|
icon: BrainIcon,
|
||||||
|
},
|
||||||
{ id: "tools", label: t.settings.sections.tools, icon: WrenchIcon },
|
{ id: "tools", label: t.settings.sections.tools, icon: WrenchIcon },
|
||||||
{ id: "skills", label: t.settings.sections.skills, icon: SparklesIcon },
|
{ id: "skills", label: t.settings.sections.skills, icon: SparklesIcon },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
t.settings.sections.appearance,
|
t.settings.sections.appearance,
|
||||||
|
t.settings.sections.memory,
|
||||||
t.settings.sections.tools,
|
t.settings.sections.tools,
|
||||||
t.settings.sections.skills,
|
t.settings.sections.skills,
|
||||||
t.settings.sections.notification,
|
t.settings.sections.notification,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Dialog {...dialogProps} onOpenChange={onOpenChange}>
|
<Dialog
|
||||||
|
{...dialogProps}
|
||||||
|
onOpenChange={(open) => props.onOpenChange?.(open)}
|
||||||
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex h-[75vh] max-h-[calc(100vh-2rem)] flex-col sm:max-w-5xl md:max-w-6xl"
|
className="flex h-[75vh] max-h-[calc(100vh-2rem)] flex-col sm:max-w-5xl md:max-w-6xl"
|
||||||
aria-describedby={undefined}
|
aria-describedby={undefined}
|
||||||
@@ -100,10 +114,11 @@ export function SettingsDialog({
|
|||||||
<ScrollArea className="h-full min-h-0 rounded-lg border">
|
<ScrollArea className="h-full min-h-0 rounded-lg border">
|
||||||
<div className="space-y-8 p-6">
|
<div className="space-y-8 p-6">
|
||||||
{activeSection === "appearance" && <AppearanceSettingsPage />}
|
{activeSection === "appearance" && <AppearanceSettingsPage />}
|
||||||
|
{activeSection === "memory" && <MemorySettingsPage />}
|
||||||
{activeSection === "tools" && <ToolSettingsPage />}
|
{activeSection === "tools" && <ToolSettingsPage />}
|
||||||
{activeSection === "skills" && (
|
{activeSection === "skills" && (
|
||||||
<SkillSettingsPage
|
<SkillSettingsPage
|
||||||
onClose={() => onOpenChange?.(false)}
|
onClose={() => props.onOpenChange?.(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeSection === "notification" && <NotificationSettingsPage />}
|
{activeSection === "notification" && <NotificationSettingsPage />}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export const enUS: Translations = {
|
|||||||
custom: "Custom",
|
custom: "Custom",
|
||||||
notAvailableInDemoMode: "Not available in demo mode",
|
notAvailableInDemoMode: "Not available in demo mode",
|
||||||
loading: "Loading...",
|
loading: "Loading...",
|
||||||
|
version: "Version",
|
||||||
|
lastUpdated: "Last updated",
|
||||||
code: "Code",
|
code: "Code",
|
||||||
preview: "Preview",
|
preview: "Preview",
|
||||||
cancel: "Cancel",
|
cancel: "Cancel",
|
||||||
@@ -201,11 +203,47 @@ export const enUS: Translations = {
|
|||||||
description: "Adjust how DeerFlow looks and behaves for you.",
|
description: "Adjust how DeerFlow looks and behaves for you.",
|
||||||
sections: {
|
sections: {
|
||||||
appearance: "Appearance",
|
appearance: "Appearance",
|
||||||
|
memory: "Memory",
|
||||||
tools: "Tools",
|
tools: "Tools",
|
||||||
skills: "Skills",
|
skills: "Skills",
|
||||||
notification: "Notification",
|
notification: "Notification",
|
||||||
acknowledge: "Acknowledge",
|
acknowledge: "Acknowledge",
|
||||||
},
|
},
|
||||||
|
memory: {
|
||||||
|
title: "Memory",
|
||||||
|
description:
|
||||||
|
"DeerFlow automatically learns from your conversations in the background. These memories help DeerFlow understand you better and deliver a more personalized experience.",
|
||||||
|
empty: "No memory data to display.",
|
||||||
|
rawJson: "Raw JSON",
|
||||||
|
markdown: {
|
||||||
|
overview: "Overview",
|
||||||
|
userContext: "User context",
|
||||||
|
work: "Work",
|
||||||
|
personal: "Personal",
|
||||||
|
topOfMind: "Top of mind",
|
||||||
|
historyBackground: "History",
|
||||||
|
recentMonths: "Recent months",
|
||||||
|
earlierContext: "Earlier context",
|
||||||
|
longTermBackground: "Long-term background",
|
||||||
|
updatedAt: "Updated at",
|
||||||
|
facts: "Facts",
|
||||||
|
empty: "Empty",
|
||||||
|
table: {
|
||||||
|
category: "Category",
|
||||||
|
confidence: "Confidence",
|
||||||
|
confidenceLevel: {
|
||||||
|
veryHigh: "Very high",
|
||||||
|
high: "High",
|
||||||
|
normal: "Normal",
|
||||||
|
unknown: "Unknown",
|
||||||
|
},
|
||||||
|
content: "Content",
|
||||||
|
source: "Source",
|
||||||
|
createdAt: "CreatedAt",
|
||||||
|
view: "View",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
themeTitle: "Theme",
|
themeTitle: "Theme",
|
||||||
themeDescription:
|
themeDescription:
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface Translations {
|
|||||||
custom: string;
|
custom: string;
|
||||||
notAvailableInDemoMode: string;
|
notAvailableInDemoMode: string;
|
||||||
loading: string;
|
loading: string;
|
||||||
|
version: string;
|
||||||
|
lastUpdated: string;
|
||||||
code: string;
|
code: string;
|
||||||
preview: string;
|
preview: string;
|
||||||
cancel: string;
|
cancel: string;
|
||||||
@@ -149,11 +151,46 @@ export interface Translations {
|
|||||||
description: string;
|
description: string;
|
||||||
sections: {
|
sections: {
|
||||||
appearance: string;
|
appearance: string;
|
||||||
|
memory: string;
|
||||||
tools: string;
|
tools: string;
|
||||||
skills: string;
|
skills: string;
|
||||||
notification: string;
|
notification: string;
|
||||||
acknowledge: string;
|
acknowledge: string;
|
||||||
};
|
};
|
||||||
|
memory: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
empty: string;
|
||||||
|
rawJson: string;
|
||||||
|
markdown: {
|
||||||
|
overview: string;
|
||||||
|
userContext: string;
|
||||||
|
work: string;
|
||||||
|
personal: string;
|
||||||
|
topOfMind: string;
|
||||||
|
historyBackground: string;
|
||||||
|
recentMonths: string;
|
||||||
|
earlierContext: string;
|
||||||
|
longTermBackground: string;
|
||||||
|
updatedAt: string;
|
||||||
|
facts: string;
|
||||||
|
empty: string;
|
||||||
|
table: {
|
||||||
|
category: string;
|
||||||
|
confidence: string;
|
||||||
|
confidenceLevel: {
|
||||||
|
veryHigh: string;
|
||||||
|
high: string;
|
||||||
|
normal: string;
|
||||||
|
unknown: string;
|
||||||
|
};
|
||||||
|
content: string;
|
||||||
|
source: string;
|
||||||
|
createdAt: string;
|
||||||
|
view: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
appearance: {
|
appearance: {
|
||||||
themeTitle: string;
|
themeTitle: string;
|
||||||
themeDescription: string;
|
themeDescription: string;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export const zhCN: Translations = {
|
|||||||
custom: "自定义",
|
custom: "自定义",
|
||||||
notAvailableInDemoMode: "在演示模式下不可用",
|
notAvailableInDemoMode: "在演示模式下不可用",
|
||||||
loading: "加载中...",
|
loading: "加载中...",
|
||||||
|
version: "版本",
|
||||||
|
lastUpdated: "最后更新",
|
||||||
code: "代码",
|
code: "代码",
|
||||||
preview: "预览",
|
preview: "预览",
|
||||||
cancel: "取消",
|
cancel: "取消",
|
||||||
@@ -197,11 +199,47 @@ export const zhCN: Translations = {
|
|||||||
description: "根据你的偏好调整 DeerFlow 的界面和行为。",
|
description: "根据你的偏好调整 DeerFlow 的界面和行为。",
|
||||||
sections: {
|
sections: {
|
||||||
appearance: "外观",
|
appearance: "外观",
|
||||||
|
memory: "记忆",
|
||||||
tools: "工具",
|
tools: "工具",
|
||||||
skills: "技能",
|
skills: "技能",
|
||||||
notification: "通知",
|
notification: "通知",
|
||||||
acknowledge: "致谢",
|
acknowledge: "致谢",
|
||||||
},
|
},
|
||||||
|
memory: {
|
||||||
|
title: "记忆",
|
||||||
|
description:
|
||||||
|
"DeerFlow 会在后台不断从你的对话中自动学习。这些记忆能帮助 DeerFlow 更好地理解你,并提供更个性化的体验。",
|
||||||
|
empty: "暂无可展示的记忆数据。",
|
||||||
|
rawJson: "原始 JSON",
|
||||||
|
markdown: {
|
||||||
|
overview: "概览",
|
||||||
|
userContext: "用户上下文",
|
||||||
|
work: "工作",
|
||||||
|
personal: "个人",
|
||||||
|
topOfMind: "近期关注(Top of mind)",
|
||||||
|
historyBackground: "历史背景",
|
||||||
|
recentMonths: "近几个月",
|
||||||
|
earlierContext: "更早上下文",
|
||||||
|
longTermBackground: "长期背景",
|
||||||
|
updatedAt: "更新于",
|
||||||
|
facts: "事实",
|
||||||
|
empty: "(空)",
|
||||||
|
table: {
|
||||||
|
category: "类别",
|
||||||
|
confidence: "置信度",
|
||||||
|
confidenceLevel: {
|
||||||
|
veryHigh: "极高",
|
||||||
|
high: "较高",
|
||||||
|
normal: "一般",
|
||||||
|
unknown: "未知",
|
||||||
|
},
|
||||||
|
content: "内容",
|
||||||
|
source: "来源",
|
||||||
|
createdAt: "创建时间",
|
||||||
|
view: "查看",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
themeTitle: "主题",
|
themeTitle: "主题",
|
||||||
themeDescription: "跟随系统或选择固定的界面模式。",
|
themeDescription: "跟随系统或选择固定的界面模式。",
|
||||||
|
|||||||
9
frontend/src/core/memory/api.ts
Normal file
9
frontend/src/core/memory/api.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getBackendBaseURL } from "../config";
|
||||||
|
|
||||||
|
import type { UserMemory } from "./types";
|
||||||
|
|
||||||
|
export async function loadMemory() {
|
||||||
|
const memory = await fetch(`${getBackendBaseURL()}/api/memory`);
|
||||||
|
const json = await memory.json();
|
||||||
|
return json as UserMemory;
|
||||||
|
}
|
||||||
11
frontend/src/core/memory/hooks.ts
Normal file
11
frontend/src/core/memory/hooks.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { loadMemory } from "./api";
|
||||||
|
|
||||||
|
export function useMemory() {
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
queryKey: ["memory"],
|
||||||
|
queryFn: () => loadMemory(),
|
||||||
|
});
|
||||||
|
return { memory: data ?? null, isLoading, error };
|
||||||
|
}
|
||||||
2
frontend/src/core/memory/index.ts
Normal file
2
frontend/src/core/memory/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export * from "./types";
|
||||||
40
frontend/src/core/memory/types.ts
Normal file
40
frontend/src/core/memory/types.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
export interface UserMemory {
|
||||||
|
version: string;
|
||||||
|
lastUpdated: string;
|
||||||
|
user: {
|
||||||
|
workContext: {
|
||||||
|
summary: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
personalContext: {
|
||||||
|
summary: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
topOfMind: {
|
||||||
|
summary: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
history: {
|
||||||
|
recentMonths: {
|
||||||
|
summary: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
earlierContext: {
|
||||||
|
summary: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
longTermBackground: {
|
||||||
|
summary: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
facts: {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
category: string;
|
||||||
|
confidence: number;
|
||||||
|
createdAt: string;
|
||||||
|
source: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
@@ -1,7 +1,27 @@
|
|||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
|
import { enUS as dateFnsEnUS, zhCN as dateFnsZhCN } from "date-fns/locale";
|
||||||
|
|
||||||
export function formatTimeAgo(date: Date | string | number) {
|
import { detectLocale, type Locale } from "@/core/i18n";
|
||||||
|
import { getLocaleFromCookie } from "@/core/i18n/cookies";
|
||||||
|
|
||||||
|
function getDateFnsLocale(locale: Locale) {
|
||||||
|
switch (locale) {
|
||||||
|
case "zh-CN":
|
||||||
|
return dateFnsZhCN;
|
||||||
|
case "en-US":
|
||||||
|
default:
|
||||||
|
return dateFnsEnUS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTimeAgo(date: Date | string | number, locale?: Locale) {
|
||||||
|
const effectiveLocale =
|
||||||
|
locale ??
|
||||||
|
(getLocaleFromCookie() as Locale | null) ??
|
||||||
|
// Fallback when cookie is missing (or on first render)
|
||||||
|
detectLocale();
|
||||||
return formatDistanceToNow(date, {
|
return formatDistanceToNow(date, {
|
||||||
addSuffix: true,
|
addSuffix: true,
|
||||||
|
locale: getDateFnsLocale(effectiveLocale),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user