mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-22 07:56:48 +00:00
feat: support memory import and export (#1521)
* feat: support memory import and export * fix(memory): address review feedback * style: format memory settings page --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
@@ -44,6 +44,7 @@ export const enUS: Translations = {
|
||||
save: "Save",
|
||||
install: "Install",
|
||||
create: "Create",
|
||||
import: "Import",
|
||||
export: "Export",
|
||||
exportAsMarkdown: "Export as Markdown",
|
||||
exportAsJSON: "Export as JSON",
|
||||
@@ -313,6 +314,17 @@ export const enUS: Translations = {
|
||||
"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",
|
||||
exportButton: "Export memory",
|
||||
exportSuccess: "Memory exported",
|
||||
importButton: "Import memory",
|
||||
importConfirmTitle: "Import memory?",
|
||||
importConfirmDescription:
|
||||
"This will overwrite your current memory with the selected JSON backup.",
|
||||
importFileLabel: "Selected file",
|
||||
importInvalidFile:
|
||||
"Failed to read the selected memory file. Please choose a valid JSON export.",
|
||||
importSuccess: "Memory imported",
|
||||
manualFactSource: "Manual",
|
||||
addFact: "Add fact",
|
||||
addFactTitle: "Add memory fact",
|
||||
editFactTitle: "Edit memory fact",
|
||||
@@ -336,7 +348,6 @@ export const enUS: Translations = {
|
||||
factSave: "Save fact",
|
||||
factValidationContent: "Fact content cannot be empty.",
|
||||
factValidationConfidence: "Confidence must be a number between 0 and 1.",
|
||||
manualFactSource: "Manual",
|
||||
noFacts: "No saved facts yet.",
|
||||
summaryReadOnly:
|
||||
"Summary sections are read-only for now. You can currently add, edit, or delete individual facts, or clear all memory.",
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface Translations {
|
||||
save: string;
|
||||
install: string;
|
||||
create: string;
|
||||
import: string;
|
||||
export: string;
|
||||
exportAsMarkdown: string;
|
||||
exportAsJSON: string;
|
||||
@@ -248,6 +249,15 @@ export interface Translations {
|
||||
description: string;
|
||||
empty: string;
|
||||
rawJson: string;
|
||||
exportButton: string;
|
||||
exportSuccess: string;
|
||||
importButton: string;
|
||||
importConfirmTitle: string;
|
||||
importConfirmDescription: string;
|
||||
importFileLabel: string;
|
||||
importInvalidFile: string;
|
||||
importSuccess: string;
|
||||
manualFactSource: string;
|
||||
addFact: string;
|
||||
addFactTitle: string;
|
||||
editFactTitle: string;
|
||||
@@ -269,7 +279,6 @@ export interface Translations {
|
||||
factSave: string;
|
||||
factValidationContent: string;
|
||||
factValidationConfidence: string;
|
||||
manualFactSource: string;
|
||||
noFacts: string;
|
||||
summaryReadOnly: string;
|
||||
memoryFullyEmpty: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
import {
|
||||
CompassIcon,
|
||||
GraduationCapIcon,
|
||||
ImageIcon,
|
||||
@@ -44,6 +44,7 @@ export const zhCN: Translations = {
|
||||
save: "保存",
|
||||
install: "安装",
|
||||
create: "创建",
|
||||
import: "导入",
|
||||
export: "导出",
|
||||
exportAsMarkdown: "导出为 Markdown",
|
||||
exportAsJSON: "导出为 JSON",
|
||||
@@ -299,6 +300,15 @@ export const zhCN: Translations = {
|
||||
"DeerFlow 会在后台不断从你的对话中自动学习。这些记忆能帮助 DeerFlow 更好地理解你,并提供更个性化的体验。",
|
||||
empty: "暂无可展示的记忆数据。",
|
||||
rawJson: "原始 JSON",
|
||||
exportButton: "导出记忆",
|
||||
exportSuccess: "记忆已导出",
|
||||
importButton: "导入记忆",
|
||||
importConfirmTitle: "导入记忆?",
|
||||
importConfirmDescription: "这会用选中的 JSON 备份覆盖当前记忆。",
|
||||
importFileLabel: "已选择文件",
|
||||
importInvalidFile: "读取记忆文件失败,请选择有效的 JSON 导出文件。",
|
||||
importSuccess: "记忆已导入",
|
||||
manualFactSource: "手动添加",
|
||||
addFact: "添加事实",
|
||||
addFactTitle: "添加记忆事实",
|
||||
editFactTitle: "编辑记忆事实",
|
||||
@@ -322,10 +332,9 @@ export const zhCN: Translations = {
|
||||
factSave: "保存事实",
|
||||
factValidationContent: "事实内容不能为空。",
|
||||
factValidationConfidence: "置信度必须是 0 到 1 之间的数字。",
|
||||
manualFactSource: "手动添加",
|
||||
noFacts: "还没有保存的事实。",
|
||||
summaryReadOnly:
|
||||
"摘要分区当前仍为只读。你可以在下方添加、编辑或删除事实,或清空全部记忆。",
|
||||
"摘要分区当前仍为只读。现在你可以清空全部记忆或删除单条事实。",
|
||||
memoryFullyEmpty: "还没有保存任何记忆。",
|
||||
factPreviewLabel: "即将删除的事实",
|
||||
searchPlaceholder: "搜索记忆",
|
||||
|
||||
@@ -10,12 +10,69 @@ async function readMemoryResponse(
|
||||
response: Response,
|
||||
fallbackMessage: string,
|
||||
): Promise<UserMemory> {
|
||||
function formatErrorDetail(detail: unknown): string | null {
|
||||
if (typeof detail === "string") {
|
||||
return detail;
|
||||
}
|
||||
|
||||
if (Array.isArray(detail)) {
|
||||
const parts = detail
|
||||
.map((item) => {
|
||||
if (typeof item === "string") {
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item && typeof item === "object") {
|
||||
const record = item as Record<string, unknown>;
|
||||
if (typeof record.msg === "string") {
|
||||
return record.msg;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(record);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return String(item);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return parts.length > 0 ? parts.join("; ") : null;
|
||||
}
|
||||
|
||||
if (detail && typeof detail === "object") {
|
||||
try {
|
||||
return JSON.stringify(detail);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof detail === "string" ||
|
||||
typeof detail === "number" ||
|
||||
typeof detail === "boolean" ||
|
||||
typeof detail === "bigint"
|
||||
) {
|
||||
return String(detail);
|
||||
}
|
||||
|
||||
if (typeof detail === "symbol") {
|
||||
return detail.description ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = (await response.json().catch(() => ({}))) as {
|
||||
detail?: string;
|
||||
detail?: unknown;
|
||||
};
|
||||
const detailMessage = formatErrorDetail(errorData.detail);
|
||||
throw new Error(
|
||||
errorData.detail ?? `${fallbackMessage}: ${response.statusText}`,
|
||||
detailMessage ?? `${fallbackMessage}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +101,22 @@ export async function deleteMemoryFact(factId: string): Promise<UserMemory> {
|
||||
return readMemoryResponse(response, "Failed to delete memory fact");
|
||||
}
|
||||
|
||||
export async function exportMemory(): Promise<UserMemory> {
|
||||
const response = await fetch(`${getBackendBaseURL()}/api/memory/export`);
|
||||
return readMemoryResponse(response, "Failed to export memory");
|
||||
}
|
||||
|
||||
export async function importMemory(memory: UserMemory): Promise<UserMemory> {
|
||||
const response = await fetch(`${getBackendBaseURL()}/api/memory/import`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(memory),
|
||||
});
|
||||
return readMemoryResponse(response, "Failed to import memory");
|
||||
}
|
||||
|
||||
export async function createMemoryFact(
|
||||
input: MemoryFactInput,
|
||||
): Promise<UserMemory> {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
clearMemory,
|
||||
createMemoryFact,
|
||||
deleteMemoryFact,
|
||||
importMemory,
|
||||
loadMemory,
|
||||
updateMemoryFact,
|
||||
} from "./api";
|
||||
@@ -43,6 +44,17 @@ export function useDeleteMemoryFact() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useImportMemory() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (memory: UserMemory) => importMemory(memory),
|
||||
onSuccess: (memory) => {
|
||||
queryClient.setQueryData<UserMemory>(["memory"], memory);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateMemoryFact() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user