import { hostApiFetch } from '@/lib/host-api'; import type { AgentSessionData } from '../../shared/agent-session'; export type AgentProfileGender = 'male' | 'female' | 'other' | null; export type AgentProfileRead = { display_name: string | null; age: number | null; gender: AgentProfileGender; avatar_url?: string | null; share_age_with_agents: boolean; share_gender_with_agents: boolean; analysis_enabled: boolean; completed: boolean; version: number; updated_at: string | null; }; export type AgentProfileUpdate = { display_name: string; age: number | null; gender: AgentProfileGender; share_age_with_agents: boolean; share_gender_with_agents: boolean; analysis_enabled: boolean; version: number; session_data?: AgentSessionData; }; type AgentProfileEnvelope = { success?: boolean; status?: number; code?: string; profile?: AgentProfileRead; error?: string; detail?: unknown; }; export type AgentAvatarUpload = { fileName: string; mimeType: string; dataBase64: string; }; type AgentAvatarEnvelope = { success?: boolean; status?: number; code?: string; avatar_url?: string | null; profile?: Pick; error?: string; detail?: unknown; }; export class AgentProfileApiError extends Error { readonly status: number; readonly detail: unknown; readonly code: string | null; constructor(status: number, message: string, detail?: unknown, code?: string | null) { super(message); this.name = 'AgentProfileApiError'; this.status = status; this.detail = detail; this.code = code?.trim() || readDetailCode(detail); } } export function getAgentProfileApiErrorMessage(error: AgentProfileApiError): string { if (error.status === 401) return '登录已过期,请重新登录后再同步个人资料。'; if (error.code === 'avatar_unsupported_type') return '头像仅支持 PNG、JPEG 或 WebP 图片。'; if (error.code === 'avatar_file_too_large') return '头像文件超过服务端大小限制。'; if (error.code === 'avatar_invalid_file') return '头像文件无效或无法读取。'; if (error.code === 'avatar_storage_unavailable') return '头像存储服务暂时不可用,请稍后重试。'; if (error.status === 503) { return readDetailMessage(error.detail) ? error.message : '个人资料服务暂时不可用,请稍后重试。'; } return error.message; } function readDetailCode(detail: unknown): string | null { if (!detail || typeof detail !== 'object' || Array.isArray(detail)) return null; const value = (detail as Record).code; return typeof value === 'string' && value.trim() ? value.trim() : null; } function readDetailMessage(detail: unknown): string | null { if (typeof detail === 'string' && detail.trim()) return detail.trim(); if (!detail || typeof detail !== 'object' || Array.isArray(detail)) return null; const record = detail as Record; for (const field of ['message', 'msg', 'error_description', 'error']) { const value = record[field]; if (typeof value === 'string' && value.trim()) return value.trim(); } return null; } async function requestAgentProfile( accessToken: string, init: RequestInit = {}, ): Promise { const response = await hostApiFetch('/api/works/user/agent-profile', { ...init, headers: { 'X-NianCode-Access-Token': accessToken, ...init.headers, }, }); if (!response?.success || !response.profile) { const status = typeof response?.status === 'number' ? response.status : 502; const detailMessage = readDetailMessage(response?.detail); const code = response?.code?.trim() || readDetailCode(response?.detail); throw new AgentProfileApiError( status, response?.error || detailMessage || `Agent Profile request failed (${status})`, response?.detail, code, ); } return response.profile; } export function getAgentProfile(accessToken: string): Promise { return requestAgentProfile(accessToken); } export function putAgentProfile( accessToken: string, update: AgentProfileUpdate, ): Promise { return requestAgentProfile(accessToken, { method: 'PUT', body: JSON.stringify(update), }); } function readAvatarUrl(response: AgentAvatarEnvelope): string | null { const value = response.avatar_url ?? response.profile?.avatar_url; return typeof value === 'string' && value.trim() ? value.trim() : null; } async function requestAgentAvatar( accessToken: string, init: RequestInit = {}, ): Promise { const response = await hostApiFetch('/api/works/user/avatar', { ...init, headers: { 'X-NianCode-Access-Token': accessToken, ...init.headers, }, }); if (!response?.success) { const status = typeof response?.status === 'number' ? response.status : 502; const detailMessage = readDetailMessage(response?.detail); const code = response?.code?.trim() || readDetailCode(response?.detail); throw new AgentProfileApiError( status, response?.error || detailMessage || `Agent Avatar request failed (${status})`, response?.detail, code, ); } return readAvatarUrl(response); } export function uploadAgentAvatar( accessToken: string, upload: AgentAvatarUpload, ): Promise { return requestAgentAvatar(accessToken, { method: 'POST', body: JSON.stringify(upload), }); } export function deleteAgentAvatar(accessToken: string): Promise { return requestAgentAvatar(accessToken, { method: 'DELETE' }); }