import { apiFetch } from "@/lib/apiFetch"; import { config } from "@config/config"; import type { ChatSessionSummary, LoadedChatState, Message, } from "./GlobalChatbox.types"; import { cloneMessages } from "./globalChatboxUtils"; type BackendSessionPayload = { id?: string; title?: string; created_at?: string | number; updated_at?: string | number; is_streaming?: boolean; run_status?: string; }; export const createEmptyChatState = (): LoadedChatState => ({ title: undefined, isTitleManuallyEdited: false, messages: [], sessionId: undefined, }); const sanitizeMessages = (messages: Message[] | undefined) => Array.isArray(messages) ? cloneMessages(messages) : []; const compareSessionsByAnchorTime = ( left: Pick, right: Pick, ) => { const createdAtDiff = right.createdAt - left.createdAt; if (createdAtDiff !== 0) return createdAtDiff; const updatedAtDiff = right.updatedAt - left.updatedAt; if (updatedAtDiff !== 0) return updatedAtDiff; return right.id.localeCompare(left.id); }; const toMillis = (value: string | number | undefined) => typeof value === "number" ? value : value ? new Date(value).getTime() : Date.now(); const normalizeTitle = (value?: string) => value?.trim() || "新对话"; const fetchBackendChatSessions = async (): Promise => { const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/chat/sessions`, { method: "GET", projectHeaderMode: "include", skipAuthRedirect: true, }); if (!response.ok) { throw new Error(await response.text()); } const payload = (await response.json()) as { sessions?: BackendSessionPayload[]; }; return (payload.sessions ?? []) .map((session) => ({ id: session.id ?? "", title: normalizeTitle(session.title), createdAt: toMillis(session.created_at), updatedAt: toMillis(session.updated_at), isStreaming: session.is_streaming, runStatus: session.run_status, })) .filter((session) => Boolean(session.id)) .sort(compareSessionsByAnchorTime); }; const fetchBackendChatSession = async (sessionId: string): Promise => { const response = await apiFetch( `${config.AGENT_URL}/api/v1/agent/chat/session/${encodeURIComponent(sessionId)}`, { method: "GET", projectHeaderMode: "include", skipAuthRedirect: true, }, ); if (!response.ok) { if (response.status === 404) { return createEmptyChatState(); } throw new Error(await response.text()); } const payload = (await response.json()) as { id: string; title?: string; is_title_manually_edited?: boolean; session_id?: string; messages?: Message[]; is_streaming?: boolean; run_status?: string; }; return { title: normalizeTitle(payload.title), isTitleManuallyEdited: payload.is_title_manually_edited ?? false, messages: sanitizeMessages(payload.messages), sessionId: payload.session_id ?? payload.id, isStreaming: payload.is_streaming ?? false, runStatus: payload.run_status, }; }; const updateBackendChatSessionTitle = async ( sessionId: string, title: string, isTitleManuallyEdited?: boolean, ) => { const response = await apiFetch( `${config.AGENT_URL}/api/v1/agent/chat/session/${encodeURIComponent(sessionId)}/title`, { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title, is_title_manually_edited: isTitleManuallyEdited, }), projectHeaderMode: "include", skipAuthRedirect: true, }, ); if (!response.ok) { throw new Error(await response.text()); } }; const deleteBackendChatSession = async (sessionId: string) => { const response = await apiFetch( `${config.AGENT_URL}/api/v1/agent/chat/session/${encodeURIComponent(sessionId)}`, { method: "DELETE", projectHeaderMode: "include", skipAuthRedirect: true, }, ); if (!response.ok && response.status !== 404) { throw new Error(await response.text()); } }; export const listChatSessions = async (): Promise => { if (typeof window === "undefined") return []; return await fetchBackendChatSessions(); }; export const updateChatSessionTitle = async ( sessionId: string, title: string, options?: { isTitleManuallyEdited?: boolean; }, ): Promise => { if (typeof window === "undefined") return; const normalizedTitle = title.trim(); if (!normalizedTitle) return; await updateBackendChatSessionTitle( sessionId, normalizedTitle, options?.isTitleManuallyEdited, ); }; export const loadChatSessionById = async ( sessionId: string, ): Promise => { if (typeof window === "undefined") return createEmptyChatState(); return await fetchBackendChatSession(sessionId); }; export const deleteChatSession = async ( sessionId: string, ): Promise => { if (typeof window === "undefined") return undefined; await deleteBackendChatSession(sessionId); const nextActiveSession = (await listChatSessions())[0]; return nextActiveSession?.id; };