deerflow-code/frontend-web/src/core/memory/api.ts
2026-09-07 18:24:55 +08:00

200 lines
5.9 KiB
TypeScript

// Memory V2 API 客户端。对应后端 /api/memory/v2 系列端点。
import { apiFetch } from "../api";
import { getBackendBaseURL } from "../config";
import { OptType, writeYLog } from "../log/y-log";
import type {
MemorySearchMode,
MemorySearchResult,
MemoryTarget,
MemoryV2ConfigResponse,
MemoryV2Data,
} from "./types";
/** 从响应里提取后端的错误详情文本。 */
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;
}
return null;
})
.filter(Boolean);
return parts.length > 0 ? parts.join("; ") : null;
}
return null;
}
async function readJson<T>(response: Response, fallbackMessage: string): Promise<T> {
if (!response.ok) {
const errorData = (await response.json().catch(() => ({}))) as {
detail?: unknown;
};
const detailMessage = formatErrorDetail(errorData.detail);
throw new Error(detailMessage ?? `${fallbackMessage}: ${response.statusText}`);
}
return response.json() as Promise<T>;
}
/** 拼接可选的 ?agent= 查询串。 */
function agentQuery(agent?: string): string {
return agent ? `?agent=${encodeURIComponent(agent)}` : "";
}
// --- 本地记忆数据 (USER.md / MEMORY.md) ---
export async function loadMemoryV2(agent?: string): Promise<MemoryV2Data> {
const response = await apiFetch(
`${getBackendBaseURL()}/api/memory/v2${agentQuery(agent)}`,
);
const data = await readJson<MemoryV2Data>(response, "Failed to fetch memory");
writeYLog({
optType: OptType.view,
optDetail: "CM助手-全量-记忆-查看",
url: `/api/memory/v2${agentQuery(agent)}`,
reqParam: { agent },
returnParam: data,
});
return data;
}
export async function addMemoryEntry(
target: MemoryTarget,
content: string,
agent?: string,
): Promise<MemoryV2Data> {
const response = await apiFetch(
`${getBackendBaseURL()}/api/memory/v2/entries${agentQuery(agent)}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ target, content }),
},
);
const data = await readJson<MemoryV2Data>(response, "Failed to add memory entry");
writeYLog({
optType: OptType.create,
optDetail: "CM助手-全量-记忆-新增",
url: `/api/memory/v2/entries${agentQuery(agent)}`,
reqParam: { target, content, agent },
returnParam: data,
});
return data;
}
export async function replaceMemoryEntry(
target: MemoryTarget,
oldText: string,
content: string,
agent?: string,
): Promise<MemoryV2Data> {
const response = await apiFetch(
`${getBackendBaseURL()}/api/memory/v2/entries${agentQuery(agent)}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ target, old_text: oldText, content }),
},
);
const data = await readJson<MemoryV2Data>(response, "Failed to replace memory entry");
writeYLog({
optType: OptType.edit,
optDetail: "CM助手-全量-记忆-编辑",
url: `/api/memory/v2/entries${agentQuery(agent)}`,
reqParam: { target, old_text: oldText, content, agent },
returnParam: data,
});
return data;
}
export async function deleteMemoryEntry(
target: MemoryTarget,
oldText: string,
agent?: string,
): Promise<MemoryV2Data> {
const params = new URLSearchParams({ target, old_text: oldText });
if (agent) {
params.set("agent", agent);
}
const response = await apiFetch(
`${getBackendBaseURL()}/api/memory/v2/entries?${params.toString()}`,
{ method: "DELETE" },
);
const data = await readJson<MemoryV2Data>(response, "Failed to delete memory entry");
writeYLog({
optType: OptType.delete,
optDetail: "CM助手-全量-记忆-删除",
url: `/api/memory/v2/entries?${params.toString()}`,
reqParam: { target, old_text: oldText, agent },
returnParam: data,
});
return data;
}
// --- 配置 ---
export async function loadMemoryConfig(): Promise<MemoryV2ConfigResponse> {
const response = await apiFetch(`${getBackendBaseURL()}/api/memory/v2/config`);
const data = await readJson<MemoryV2ConfigResponse>(response, "Failed to fetch memory config");
writeYLog({
optType: OptType.view,
optDetail: "CM助手-全量-记忆-配置查看",
url: "/api/memory/v2/config",
returnParam: data,
});
return data;
}
export async function patchMemoryConfig(
overrides: Record<string, unknown>,
): Promise<MemoryV2ConfigResponse> {
const response = await apiFetch(`${getBackendBaseURL()}/api/memory/v2/config`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ overrides }),
});
const data = await readJson<MemoryV2ConfigResponse>(response, "Failed to update memory config");
writeYLog({
optType: OptType.edit,
optDetail: "CM助手-全量-记忆-配置编辑",
url: "/api/memory/v2/config",
reqParam: { overrides },
returnParam: data,
});
return data;
}
// --- Hindsight 检索 ---
export async function searchMemory(
query: string,
mode: MemorySearchMode = "recall",
agent?: string,
): Promise<MemorySearchResult> {
const response = await apiFetch(
`${getBackendBaseURL()}/api/memory/v2/search${agentQuery(agent)}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, mode }),
},
);
const data = await readJson<MemorySearchResult>(response, "Failed to search memory");
writeYLog({
optType: OptType.search,
optDetail: "CM助手-全量-记忆-检索",
url: `/api/memory/v2/search${agentQuery(agent)}`,
reqParam: { query, mode, agent },
returnParam: data,
});
return data;
}