92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const chatMessageSchema = z.object({
|
|
role: z.enum(["user", "assistant"]),
|
|
text: z.string().max(100_000),
|
|
suggestions: z.array(z.string().max(200)).max(3).optional(),
|
|
techniqueTruth: z.string().max(120).optional(),
|
|
workflowReceipt: z.object({
|
|
route: z.string().max(120),
|
|
status: z.string().max(120),
|
|
preciseTiming: z.string().max(120),
|
|
missingLayers: z.array(z.string().max(120)).max(30),
|
|
}).strict().optional(),
|
|
}).strict();
|
|
|
|
export const chatSessionWriteSchema = z.object({
|
|
title: z.string().trim().min(1).max(160),
|
|
theme: z.enum(["career", "marriage", "wealth", "timing", "general"]),
|
|
model_id: z.string().trim().min(1).max(64),
|
|
messages: z.array(chatMessageSchema).max(500),
|
|
session_type: z.enum(["consultation", "birth_time_rectification"]),
|
|
rectification_case_id: z.string().uuid().nullable(),
|
|
updated_at: z.string().datetime(),
|
|
}).strict();
|
|
|
|
export const chatSessionCreateSchema = chatSessionWriteSchema.extend({
|
|
id: z.string().uuid(),
|
|
}).strict();
|
|
|
|
export const chatSessionModelPatchSchema = z.object({
|
|
model_id: z.string().trim().min(1).max(64),
|
|
}).strict();
|
|
|
|
export type ChatSessionWrite = Readonly<{
|
|
title: string;
|
|
theme: "career" | "marriage" | "wealth" | "timing" | "general";
|
|
model_id: string;
|
|
messages: readonly Readonly<{
|
|
role: "user" | "assistant";
|
|
text: string;
|
|
suggestions?: readonly string[];
|
|
techniqueTruth?: string;
|
|
workflowReceipt?: Readonly<{
|
|
route: string;
|
|
status: string;
|
|
preciseTiming: string;
|
|
missingLayers: readonly string[];
|
|
}>;
|
|
}>[];
|
|
session_type: "consultation" | "birth_time_rectification";
|
|
rectification_case_id: string | null;
|
|
updated_at: string;
|
|
}>;
|
|
|
|
function retryableStatus(status: number) {
|
|
return status === 408 || status === 429 || status >= 500;
|
|
}
|
|
|
|
class TerminalChatSessionWriteError extends Error {}
|
|
|
|
export async function writeChatSession(
|
|
id: string,
|
|
values: ChatSessionWrite,
|
|
mode: "create" | "update",
|
|
fetcher: typeof fetch = fetch,
|
|
): Promise<void> {
|
|
const url = mode === "create" ? "/api/sessions" : `/api/sessions/${encodeURIComponent(id)}`;
|
|
const body = mode === "create" ? { id, ...values } : values;
|
|
let lastError = "云端同步暂时不可用";
|
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
try {
|
|
const response = await fetcher(url, {
|
|
method: mode === "create" ? "POST" : "PATCH",
|
|
headers: { "content-type": "application/json" },
|
|
credentials: "same-origin",
|
|
body: JSON.stringify(body),
|
|
});
|
|
const payload = await response.json().catch(() => null) as { error?: string } | null;
|
|
if (response.ok) return;
|
|
lastError = payload?.error || "云端同步暂时不可用";
|
|
if (!retryableStatus(response.status)) throw new TerminalChatSessionWriteError(lastError);
|
|
} catch (error) {
|
|
if (error instanceof TerminalChatSessionWriteError) throw error;
|
|
lastError = error instanceof TypeError
|
|
? "网络暂时不可用,云端记录尚未更新"
|
|
: error instanceof Error ? error.message : lastError;
|
|
}
|
|
if (attempt === 0) await new Promise((resolve) => setTimeout(resolve, 250));
|
|
}
|
|
throw new Error(lastError);
|
|
}
|