Files
TJWaterFrontend_Refine/src/components/chat/chatStorage.ts
T

186 lines
5.2 KiB
TypeScript

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<ChatSessionSummary, "id" | "createdAt" | "updatedAt">,
right: Pick<ChatSessionSummary, "id" | "createdAt" | "updatedAt">,
) => {
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<ChatSessionSummary[]> => {
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<LoadedChatState> => {
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<ChatSessionSummary[]> => {
if (typeof window === "undefined") return [];
return await fetchBackendChatSessions();
};
export const updateChatSessionTitle = async (
sessionId: string,
title: string,
options?: {
isTitleManuallyEdited?: boolean;
},
): Promise<void> => {
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<LoadedChatState> => {
if (typeof window === "undefined") return createEmptyChatState();
return await fetchBackendChatSession(sessionId);
};
export const deleteChatSession = async (
sessionId: string,
): Promise<string | undefined> => {
if (typeof window === "undefined") return undefined;
await deleteBackendChatSession(sessionId);
const nextActiveSession = (await listChatSessions())[0];
return nextActiveSession?.id;
};