export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "/api/v1"; const TOKEN_KEY = "cyber_mister_token"; const CLIENT_KEY = "cyber_mister_client_id"; export type HandSide = "left" | "right" | "unknown"; export type ReadingType = "palm" | "face" | "bazi"; export type Dimension = { name: string; observations: string[]; interpretation: string; confidence: number; advice: string; }; export type ReportData = { quality_check: { can_analyze: boolean; reason: string; confidence: number; }; overall_summary: string; dimensions: Dimension[]; strengths: string[]; challenges: string[]; suggestions: string[]; lucky_keywords: string[]; disclaimer: string; }; export type Reading = { id: string; reading_type: ReadingType; status: "pending" | "processing" | "completed" | "failed"; input_data: Record; error_message?: string | null; report_data?: ReportData | null; created_at: string; updated_at: string; }; export type ReadingSummary = { id: string; reading_type: ReadingType; status: Reading["status"]; created_at: string; overall_summary?: string | null; }; export type Quota = { limit: number; used: number; remaining: number; reset_at: string; }; export type Report = Reading & { hand_side?: HandSide }; export type ReportSummary = ReadingSummary & { hand_side?: HandSide }; export function getStoredToken() { if (typeof window === "undefined") return ""; return localStorage.getItem(TOKEN_KEY) || ""; } export async function ensureToken() { const existing = getStoredToken(); if (existing) return existing; let clientId = localStorage.getItem(CLIENT_KEY); if (!clientId) { clientId = crypto.randomUUID(); localStorage.setItem(CLIENT_KEY, clientId); } const response = await fetch(`${API_BASE_URL}/auth/anonymous-login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ client_id: clientId }), }); if (!response.ok) throw new Error("匿名会话创建失败"); const data = await response.json(); localStorage.setItem(TOKEN_KEY, data.access_token); return data.access_token as string; } export async function apiFetch(path: string, options: RequestInit = {}): Promise { const token = await ensureToken(); const headers = new Headers(options.headers); if (!(options.body instanceof FormData) && !headers.has("content-type")) { headers.set("content-type", "application/json"); } headers.set("authorization", `Bearer ${token}`); const response = await fetch(`${API_BASE_URL}${path}`, { ...options, headers }); if (!response.ok) { const data = await response.json().catch(() => ({})); throw new Error(data.detail || "请求失败"); } return response.json() as Promise; } export async function uploadPalmImage(file: File) { return uploadImage(file, "palm"); } export async function uploadFaceImage(file: File) { return uploadImage(file, "face"); } async function uploadImage(file: File, type: "palm" | "face") { const token = await ensureToken(); const formData = new FormData(); formData.append("file", file); const response = await fetch(`${API_BASE_URL}/uploads/${type}`, { method: "POST", headers: { authorization: `Bearer ${token}` }, body: formData, }); if (!response.ok) { const data = await response.json().catch(() => ({})); throw new Error(data.detail || "上传失败"); } return response.json() as Promise<{ image_id: string; quality_check: Record; expires_at: string }>; }