JeffJiang db5ad86381 feat: enhance chat history loading with new hooks and UI components (#2338)
* Refactor API fetch calls to use a unified fetch function; enhance chat history loading with new hooks and UI components

- Replaced `fetchWithAuth` with a generic `fetch` function across various API modules for consistency.
- Updated `useThreadStream` and `useThreadHistory` hooks to manage chat history loading, including loading states and pagination.
- Introduced `LoadMoreHistoryIndicator` component for better user experience when loading more chat history.
- Enhanced message handling in `MessageList` to accommodate new loading states and history management.
- Added support for run messages in the thread context, improving the overall message handling logic.
- Updated translations for loading indicators in English and Chinese.

* Fix test assertions for run ordering in RunManager tests

- Updated assertions in `test_list_by_thread` to reflect correct ordering of runs.
- Modified `test_list_by_thread_is_stable_when_timestamps_tie` to ensure stable ordering when timestamps are tied.
2026-04-26 11:20:17 +08:00

150 lines
3.8 KiB
TypeScript

import { fetch } from "../api/fetcher";
import { getBackendBaseURL } from "../config";
import type {
MemoryFactInput,
MemoryFactPatchInput,
UserMemory,
} from "./types";
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?: unknown;
};
const detailMessage = formatErrorDetail(errorData.detail);
throw new Error(
detailMessage ?? `${fallbackMessage}: ${response.statusText}`,
);
}
return response.json() as Promise<UserMemory>;
}
export async function loadMemory(): Promise<UserMemory> {
const response = await fetch(`${getBackendBaseURL()}/api/memory`);
return readMemoryResponse(response, "Failed to fetch memory");
}
export async function clearMemory(): Promise<UserMemory> {
const response = await fetch(`${getBackendBaseURL()}/api/memory`, {
method: "DELETE",
});
return readMemoryResponse(response, "Failed to clear memory");
}
export async function deleteMemoryFact(factId: string): Promise<UserMemory> {
const response = await fetch(
`${getBackendBaseURL()}/api/memory/facts/${encodeURIComponent(factId)}`,
{
method: "DELETE",
},
);
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> {
const response = await fetch(`${getBackendBaseURL()}/api/memory/facts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return readMemoryResponse(response, "Failed to create memory fact");
}
export async function updateMemoryFact(
factId: string,
input: MemoryFactPatchInput,
): Promise<UserMemory> {
const response = await fetch(
`${getBackendBaseURL()}/api/memory/facts/${encodeURIComponent(factId)}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
},
);
return readMemoryResponse(response, "Failed to update memory fact");
}