/** * API 工具类 * 统一管理后端 API 调用 */ /** * 获取 API Key(从环境变量读取) */ const getApiKey = (): string => { return import.meta.env.VITE_API_CLIENT_KEY || ''; }; /** * 获取统一的请求头(包含 API Key) */ const getApiHeaders = (): Record => { const headers: Record = { 'Content-Type': 'application/json', }; const apiKey = getApiKey(); if (apiKey) { headers['X-Client-Key'] = apiKey; } return headers; }; /** * 获取后端 API 基础 URL */ const getApiBaseUrl = (): string => { // 开发环境:使用相对路径,通过 Vite 代理访问后端 // 这样可以避免 Safari 对自签名证书的跨域限制 if (import.meta.env.DEV) { return ''; // 使用相对路径,Vite 会代理到后端 } // 生产环境:使用完整 URL // 优先使用环境变量指定的 API 地址 if (import.meta.env.VITE_API_BASE_URL) { return import.meta.env.VITE_API_BASE_URL; } // 从 WebSocket URL 推导 API 基础 URL const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:8000/ws'; // 将 ws:// 或 wss:// 转换为 http:// 或 https:// const apiUrl = wsUrl .replace(/^wss:\/\//, 'https://') .replace(/^ws:\/\//, 'http://') .replace(/\/ws$/, ''); return apiUrl; }; /** * 创建用户 */ export interface CreateUserResponse { success: boolean; data: { user_id: string; username: string; user_type: string; user_level: number; current_background_image_id?: string; created_at: string; }; } export const createUser = async (username: string = ''): Promise => { const apiBaseUrl = getApiBaseUrl(); const url = `${apiBaseUrl}/api/users/create`; console.log('[API] 创建用户请求:', { url, apiBaseUrl, isDev: import.meta.env.DEV, }); try { const response = await fetch(url, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ username }), }); console.log('[API] 创建用户响应:', { status: response.status, statusText: response.statusText, ok: response.ok, }); if (!response.ok) { const errorText = await response.text().catch(() => '无法读取错误信息'); console.error('[API] 创建用户失败:', { status: response.status, statusText: response.statusText, errorText, }); throw new Error(`创建用户失败: ${response.status} ${response.statusText} - ${errorText}`); } const data = await response.json(); console.log('[API] 创建用户成功:', data); // 同步背景图片到 localStorage(包括metadata中的透明度配置) if (data.success && data.data?.current_background_image_id) { const { syncBackgroundImageToStorage } = await import('./storage'); syncBackgroundImageToStorage( data.data.current_background_image_id, data.data.current_background_image_metadata ); } return data; } catch (error) { console.error('[API] 创建用户异常:', error); // 如果是网络错误,提供更友好的提示 if (error instanceof TypeError && error.message.includes('fetch')) { throw new Error('网络请求失败,请检查后端服务是否运行'); } throw error; } }; /** * 获取用户信息 */ export interface GetUserResponse { success: boolean; data: { user_id: string; username: string; user_type: string; bot_name?: string; total_speech_duration_ms?: number; user_level?: number; current_background_image_id?: string; current_background_image_metadata?: Record; created_at: string; }; } export const getUserInfo = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw new Error(`获取用户信息失败: ${response.statusText}`); } const data = await response.json(); // 同步背景图片到 localStorage(包括metadata中的透明度配置) if (data.success && data.data?.current_background_image_id) { const { syncBackgroundImageToStorage } = await import('./storage'); syncBackgroundImageToStorage( data.data.current_background_image_id, data.data.current_background_image_metadata ); } return data; }; /** * 更新用户名 */ export interface UpdateUsernameResponse { success: boolean; message: string; } export const updateUsername = async (user_id: string, username: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/username`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ username }), }); if (!response.ok) { throw new Error(`更新用户名失败: ${response.statusText}`); } return response.json(); }; /** * 更新背景图片ID */ export interface UpdateBackgroundImageIdResponse { success: boolean; message: string; } export const updateBackgroundImageId = async ( user_id: string, background_image_id: string, ): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/background-image-id`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ background_image_id }), }); if (!response.ok) { throw new Error(`更新背景图片ID失败: ${response.statusText}`); } return response.json(); }; /** * 更新机器人名字 */ export interface UpdateBotNameResponse { success: boolean; message: string; } export const updateBotName = async (user_id: string, bot_name: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/bot-name`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ bot_name }), }); if (!response.ok) { throw new Error(`更新机器人名字失败: ${response.statusText}`); } return response.json(); }; /** * 用户 UI 状态相关接口 */ export interface UserUIState { current_page: string | null; visited_routes: string[]; has_shown_tap_to_chat: boolean; has_shown_cancel_tooltip: boolean; has_shown_send_tooltip: boolean; has_shown_progress_bar_tooltip: boolean; has_shown_mission_stone_modal: boolean; has_shown_conversation_stone_modal: boolean; has_clicked_mission_stone: boolean; has_clicked_conversation_stone: boolean; avatar_c_unlocked: boolean; has_shared_avatar: boolean; current_avatar_id?: string | null; } export interface GetUserUIStateResponse { success: boolean; data: { user_id: string; } & UserUIState; } export const getUserUIState = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/ui-state`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw new Error(`获取用户UI状态失败: ${response.statusText}`); } return response.json(); }; export interface UpdateUserUIStateResponse { success: boolean; data: { user_id: string; } & UserUIState; } export type UpdateUserUIStatePayload = Partial; export const updateUserUIState = async ( user_id: string, payload: UpdateUserUIStatePayload, ): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/ui-state`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify(payload), }); if (!response.ok) { throw new Error(`更新用户UI状态失败: ${response.statusText}`); } return response.json(); }; /** * 注册/登录相关接口 */ // 手机号注册 export interface RegisterPhoneResponse { success: boolean; data: { user_id: string; username: string; area_code?: string; phone: string; user_type: string; user_level?: number; created_at: string; }; } export const registerWithPhone = async ( phone: string, username: string = '', user_id?: string | null, area_code?: string, ): Promise => { const apiBaseUrl = getApiBaseUrl(); const body: { phone: string; username: string; user_id?: string; area_code?: string } = { phone, username }; if (user_id) { body.user_id = user_id; } if (area_code) { body.area_code = area_code; } const response = await fetch(`${apiBaseUrl}/api/auth/register/phone`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(body), }); if (!response.ok) { const errorText = await response.text().catch(() => '注册失败'); throw new Error(errorText); } return response.json(); }; // 邮箱注册 export interface RegisterEmailResponse { success: boolean; data: { user_id: string; username: string; email: string; user_type: string; user_level?: number; created_at: string; }; } export const registerWithEmail = async (email: string, username: string = '', user_id?: string | null): Promise => { const apiBaseUrl = getApiBaseUrl(); const body: { email: string; username: string; user_id?: string } = { email, username }; if (user_id) { body.user_id = user_id; } const response = await fetch(`${apiBaseUrl}/api/auth/register/email`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(body), }); if (!response.ok) { const errorText = await response.text().catch(() => '注册失败'); throw new Error(errorText); } return response.json(); }; // 微信登录 export interface LoginWechatResponse { success: boolean; data: { user_id: string; username: string; wechat_openid?: string; user_type: string; user_level?: number; created_at: string; }; } export const loginWithWechat = async (openid: string, username: string = ''): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/wechat`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ openid, username }), }); if (!response.ok) { const errorText = await response.text().catch(() => '登录失败'); throw new Error(errorText); } return response.json(); }; // QQ登录 export interface LoginQQResponse { success: boolean; data: { user_id: string; username: string; qq_openid?: string; user_type: string; user_level?: number; created_at: string; }; } export const loginWithQQ = async (openid: string, username: string = ''): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/qq`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ openid, username }), }); if (!response.ok) { const errorText = await response.text().catch(() => '登录失败'); throw new Error(errorText); } return response.json(); }; // 手机号登录 export interface LoginPhoneResponse { success: boolean; data: { user_id: string; username: string; area_code?: string; phone: string; user_type: string; user_level?: number; created_at: string; }; } export const loginWithPhone = async (phone: string, area_code?: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/phone`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ phone, area_code }), }); if (!response.ok) { const errorText = await response.text().catch(() => '登录失败'); throw new Error(errorText); } return response.json(); }; // 邮箱登录 export interface LoginEmailResponse { success: boolean; data: { user_id: string; username: string; email: string; user_type: string; user_level?: number; created_at: string; }; } export const loginWithEmail = async (email: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/email`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ email }), }); if (!response.ok) { const errorText = await response.text().catch(() => '登录失败'); throw new Error(errorText); } return response.json(); }; // 发送验证码 export interface SendCodeRequest { area_code?: string; // 手机区号(如 +86) phone?: string; email?: string; send_type: 'domestic_sms' | 'international_sms' | 'email'; } export interface SendCodeResponse { success: boolean; data: { code_id: string; message: string; }; } export const sendCode = async (request: SendCodeRequest): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/send-code`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(request), }); if (!response.ok) { const errorText = await response.text().catch(() => '发送验证码失败'); throw new Error(errorText); } return response.json(); }; // 验证验证码 export interface VerifyCodeRequest { phone?: string; email?: string; code: string; send_type: 'domestic_sms' | 'international_sms' | 'email'; } export interface VerifyCodeResponse { success: boolean; data: { valid: boolean; message: string; }; } export const verifyCode = async (request: VerifyCodeRequest): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/verify-code`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(request), }); if (!response.ok) { const errorText = await response.text().catch(() => '验证验证码失败'); throw new Error(errorText); } return response.json(); }; /** * OAuth 授权相关接口 */ // 获取微信授权URL export const getWechatAuthUrl = (): string => { const apiBaseUrl = getApiBaseUrl(); return `${apiBaseUrl}/api/auth/wechat/authorize`; }; // 获取QQ授权URL export const getQQAuthUrl = (from?: string): string => { const apiBaseUrl = getApiBaseUrl(); const url = `${apiBaseUrl}/api/auth/qq/authorize`; if (from) { return `${url}?from=${encodeURIComponent(from)}`; } return url; }; /** * 头像 GIF 配置 */ export interface AvatarGifSet { default: string; positive: string; negative: string; talking: string; } export interface AvatarGifConfigResponse { success: boolean; data: { avatar_default?: Partial; avatar_registered_cap?: Partial; }; } export const getAvatarGifs = async (): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/avatar-gifs`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw new Error(`获取头像 GIF 配置失败: ${response.statusText}`); } return response.json(); }; /** * 分享图片 */ export interface ShareImageItem { id: string; url: string; title?: string; sort_order?: number; is_active?: boolean; } export interface GetShareImagesResponse { success: boolean; data: ShareImageItem[]; } export const getShareImages = async (): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/share-images`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw new Error(`获取分享图片失败: ${response.statusText}`); } return response.json(); }; /** * 领取分享奖励 */ export interface ClaimShareRewardResponse { success: boolean; data?: { user_id: string; background_image_id: string; background_image_metadata?: Record; message: string; }; message?: string; } export const claimShareReward = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/claim-share-reward`, { method: 'POST', headers: getApiHeaders(), }); if (!response.ok) { const errorText = await response.text().catch(() => '领取分享奖励失败'); throw new Error(errorText); } const data = await response.json(); // 如果成功,同步背景图片到 localStorage if (data.success && data.data?.background_image_id) { const { syncBackgroundImageToStorage } = await import('./storage'); syncBackgroundImageToStorage( data.data.background_image_id, data.data.background_image_metadata || { opacity: 0.5 } // 使用后端返回的metadata,或默认透明度 ); } return data; }; /** * 获取所有背景图片配置 */ export interface BackgroundImageConfig { image_id: string; image_name: string; image_url: string; thumbnail_url?: string; unlock_type: 'default' | 'level' | 'purchase' | 'mission'; unlock_level?: number; unlock_mission_type?: string; display_order: number; is_active: boolean; description?: string; metadata?: Record; created_at?: string; updated_at?: string; } export interface GetBackgroundImagesResponse { success: boolean; data: BackgroundImageConfig[]; } export const getBackgroundImages = async (): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/background-images`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw new Error(`获取背景图片配置失败: ${response.statusText}`); } return response.json(); };