| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 |
- /**
- * API 工具类
- * 统一管理后端 API 调用
- */
- /**
- * 获取 API Key(从环境变量读取)
- */
- const getApiKey = (): string => {
- return import.meta.env.VITE_API_CLIENT_KEY || '';
- };
- /**
- * 获取统一的请求头(包含 API Key)
- */
- const getApiHeaders = (): Record<string, string> => {
- const headers: Record<string, string> = {
- '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<CreateUserResponse> => {
- 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<string, any>;
- created_at: string;
- };
- }
- export const getUserInfo = async (user_id: string): Promise<GetUserResponse> => {
- 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<UpdateUsernameResponse> => {
- 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<UpdateBackgroundImageIdResponse> => {
- 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<UpdateBotNameResponse> => {
- 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<GetUserUIStateResponse> => {
- 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<UserUIState>;
- export const updateUserUIState = async (
- user_id: string,
- payload: UpdateUserUIStatePayload,
- ): Promise<UpdateUserUIStateResponse> => {
- 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<RegisterPhoneResponse> => {
- 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<RegisterEmailResponse> => {
- 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<LoginWechatResponse> => {
- 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<LoginQQResponse> => {
- 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<LoginPhoneResponse> => {
- 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<LoginEmailResponse> => {
- 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<SendCodeResponse> => {
- 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<VerifyCodeResponse> => {
- 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<AvatarGifSet>;
- avatar_registered_cap?: Partial<AvatarGifSet>;
- };
- }
- export const getAvatarGifs = async (): Promise<AvatarGifConfigResponse> => {
- 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<GetShareImagesResponse> => {
- 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<string, any>;
- message: string;
- };
- message?: string;
- }
- export const claimShareReward = async (user_id: string): Promise<ClaimShareRewardResponse> => {
- 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<string, any>;
- created_at?: string;
- updated_at?: string;
- }
- export interface GetBackgroundImagesResponse {
- success: boolean;
- data: BackgroundImageConfig[];
- }
- export const getBackgroundImages = async (): Promise<GetBackgroundImagesResponse> => {
- 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();
- };
|