people-reading/web/lib/api.ts
2026-05-12 17:05:32 +08:00

120 lines
3.4 KiB
TypeScript

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<string, unknown>;
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 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<T>(path: string, options: RequestInit = {}): Promise<T> {
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<T>;
}
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<string, unknown>; expires_at: string }>;
}