Files
Jyotisha/frontend/src/lib/chat-session-write-contract.ts
T
2026-07-22 11:04:36 +08:00

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);
}