1 line
29 KiB
JSON
1 line
29 KiB
JSON
{"repo": "614liu/gymeatapp", "files": {"src/lib/gemini.ts": {"b56ddb711ab5502ae23bd913f775cae2f78f1a2a": {"t": 1785745575.615535, "n": 6403, "h": "fb095eff6f7069d1", "c": "export interface NutritionResult {\n isFood: boolean;\n reason?: string;\n name: string;\n calories: number;\n protein: number;\n carbs: number;\n fat: number;\n amount: string; // e.g., \"100g\", \"1 piece\"\n}\n\nexport function parseAiError(err: any): string {\n const rawError = err?.message || String(err || '');\n console.log(\"Parsing AI error:\", rawError);\n\n const lowerError = rawError.toLowerCase();\n\n if (\n lowerError.includes('missing or invalid api key') ||\n lowerError.includes('missing api key') ||\n lowerError.includes('api key not valid') || \n lowerError.includes('api_key_invalid') || \n lowerError.includes('invalid_argument') ||\n /status[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n /code[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n (/\\b400\\b/.test(rawError) && !rawError.includes('ms') && !rawError.includes('px'))\n ) {\n return \"API Key 校验失败。请确认在「设置」中填写的 Gemini API Key 是否正确(以 AIzaSy 开头且无前后空格),或点击「清除记录」以重新尝试系统默认渠道。\";\n }\n\n if (\n lowerError.includes('503') || \n lowerError.includes('unavailable') || \n lowerError.includes('high demand') || \n lowerError.includes('temporary') ||\n lowerError.includes('overloaded')\n ) {\n return \"AI 估算服务暂时繁忙(Gemini 模型当前请求量过载),请稍后重试。您也可以在右上角「设置」中配置您专属的 Gemini API Key 解锁专属通道。\";\n }\n\n if (\n lowerError.includes('429') || \n lowerError.includes('rate limit') || \n lowerError.includes('quota')\n ) {\n return \"已达到当前的免费调用频率限制,请一分钟后再试。您可以在右上角「设置」中配置您专属的 API Key 以解决频率限制。\";\n }\n\n if (\n lowerError.includes('model not found') || \n lowerError.includes('404')\n ) {\n return \"模型服务接口暂时不可用(404),请稍后再试。\";\n }\n\n if (\n lowerError.includes('failed to fetch') || \n lowerError.includes('network') || \n lowerError.includes('fetch')\n ) {\n return \"无法连接至 AI 服务端,请检查您的网络。如果是连接超时,在「设置」中填写您的专属 API Key 可帮助顺利访问。\";\n }\n\n return `识别出错,请重试: ${rawError.substring(0, 150)}`;\n}\n\nfunction getProvider(): 'gemini' | 'deepseek' {\n if (typeof window === 'undefined') return 'gemini';\n return localStorage.getItem('ai_provider') === 'deepseek' ? 'deepseek' : 'gemini';\n}\n\n// In a web deployment the app and the API share an origin, so a relative\n// path works. Inside a Capacitor native build the app runs from a local\n// origin (capacitor://localhost), so we must call the deployed backend by\n// its full URL. VITE_API_BASE is injected at build time for native builds.\nconst API_BASE = (import.meta as any).env?.VITE_API_BASE || '';\n\nfunction apiUrl(path: string): string {\n return `${API_BASE}${path}`;\n}\n\nfunction getCustomApiKey() {\n if (typeof window === 'undefined') return null;\n // Use the key matching the selected provider\n const provider = getProvider();\n return provider === 'deepseek'\n ? localStorage.getItem('user_deepseek_api_key')\n : localStorage.getItem('user_gemini_api_key');\n}\n\nexport async function estimateLogNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: false,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function estimateLibraryNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: true,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\n// ===== AI Coach: daily tip + next-meal recommendation =====\n\nexport interface MealSuggestion {\n name: string;\n calories: number;\n protein: number;\n reason: string;\n}\n\nasync function callCoach(kind: 'tip' | 'meal', data: any): Promise<any> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/coach'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ kind, data, customApiKey, provider }),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error(err.error || `请求失败: ${response.statusText}`);\n }\n return response.json();\n}\n\nexport async function getDailyTip(data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n}): Promise<string> {\n const res = await callCoach('tip', data);\n return res.tip || '';\n}\n\nexport async function getMealSuggestions(data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n}): Promise<MealSuggestion[]> {\n const res = await callCoach('meal', data);\n return res.suggestions || [];\n}\n\n// ===== AI Chat: personalized nutrition Q&A =====\n\nexport interface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n}\n\nexport async function sendChatMessage(\n messages: ChatMessage[],\n data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n recentFoods?: { name: string; calories: number }[];\n }\n): Promise<string> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/chat'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages, data, customApiKey, provider }),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error(err.error || `请求失败: ${response.statusText}`);\n }\n const res = await response.json();\n return res.reply || '';\n}\n"}, "57cdbc16088f0512b00b56b66df8ba7e644f656d": {"t": 1785754387.6181378, "n": 6403, "h": "fb095eff6f7069d1", "c": "export interface NutritionResult {\n isFood: boolean;\n reason?: string;\n name: string;\n calories: number;\n protein: number;\n carbs: number;\n fat: number;\n amount: string; // e.g., \"100g\", \"1 piece\"\n}\n\nexport function parseAiError(err: any): string {\n const rawError = err?.message || String(err || '');\n console.log(\"Parsing AI error:\", rawError);\n\n const lowerError = rawError.toLowerCase();\n\n if (\n lowerError.includes('missing or invalid api key') ||\n lowerError.includes('missing api key') ||\n lowerError.includes('api key not valid') || \n lowerError.includes('api_key_invalid') || \n lowerError.includes('invalid_argument') ||\n /status[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n /code[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n (/\\b400\\b/.test(rawError) && !rawError.includes('ms') && !rawError.includes('px'))\n ) {\n return \"API Key 校验失败。请确认在「设置」中填写的 Gemini API Key 是否正确(以 AIzaSy 开头且无前后空格),或点击「清除记录」以重新尝试系统默认渠道。\";\n }\n\n if (\n lowerError.includes('503') || \n lowerError.includes('unavailable') || \n lowerError.includes('high demand') || \n lowerError.includes('temporary') ||\n lowerError.includes('overloaded')\n ) {\n return \"AI 估算服务暂时繁忙(Gemini 模型当前请求量过载),请稍后重试。您也可以在右上角「设置」中配置您专属的 Gemini API Key 解锁专属通道。\";\n }\n\n if (\n lowerError.includes('429') || \n lowerError.includes('rate limit') || \n lowerError.includes('quota')\n ) {\n return \"已达到当前的免费调用频率限制,请一分钟后再试。您可以在右上角「设置」中配置您专属的 API Key 以解决频率限制。\";\n }\n\n if (\n lowerError.includes('model not found') || \n lowerError.includes('404')\n ) {\n return \"模型服务接口暂时不可用(404),请稍后再试。\";\n }\n\n if (\n lowerError.includes('failed to fetch') || \n lowerError.includes('network') || \n lowerError.includes('fetch')\n ) {\n return \"无法连接至 AI 服务端,请检查您的网络。如果是连接超时,在「设置」中填写您的专属 API Key 可帮助顺利访问。\";\n }\n\n return `识别出错,请重试: ${rawError.substring(0, 150)}`;\n}\n\nfunction getProvider(): 'gemini' | 'deepseek' {\n if (typeof window === 'undefined') return 'gemini';\n return localStorage.getItem('ai_provider') === 'deepseek' ? 'deepseek' : 'gemini';\n}\n\n// In a web deployment the app and the API share an origin, so a relative\n// path works. Inside a Capacitor native build the app runs from a local\n// origin (capacitor://localhost), so we must call the deployed backend by\n// its full URL. VITE_API_BASE is injected at build time for native builds.\nconst API_BASE = (import.meta as any).env?.VITE_API_BASE || '';\n\nfunction apiUrl(path: string): string {\n return `${API_BASE}${path}`;\n}\n\nfunction getCustomApiKey() {\n if (typeof window === 'undefined') return null;\n // Use the key matching the selected provider\n const provider = getProvider();\n return provider === 'deepseek'\n ? localStorage.getItem('user_deepseek_api_key')\n : localStorage.getItem('user_gemini_api_key');\n}\n\nexport async function estimateLogNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: false,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function estimateLibraryNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: true,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\n// ===== AI Coach: daily tip + next-meal recommendation =====\n\nexport interface MealSuggestion {\n name: string;\n calories: number;\n protein: number;\n reason: string;\n}\n\nasync function callCoach(kind: 'tip' | 'meal', data: any): Promise<any> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/coach'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ kind, data, customApiKey, provider }),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error(err.error || `请求失败: ${response.statusText}`);\n }\n return response.json();\n}\n\nexport async function getDailyTip(data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n}): Promise<string> {\n const res = await callCoach('tip', data);\n return res.tip || '';\n}\n\nexport async function getMealSuggestions(data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n}): Promise<MealSuggestion[]> {\n const res = await callCoach('meal', data);\n return res.suggestions || [];\n}\n\n// ===== AI Chat: personalized nutrition Q&A =====\n\nexport interface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n}\n\nexport async function sendChatMessage(\n messages: ChatMessage[],\n data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n recentFoods?: { name: string; calories: number }[];\n }\n): Promise<string> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/chat'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages, data, customApiKey, provider }),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error(err.error || `请求失败: ${response.statusText}`);\n }\n const res = await response.json();\n return res.reply || '';\n}\n"}, "ea5e3e23ab72cb7e8e3690682693df3803e108aa": {"t": 1785754388.814874, "n": 5442, "h": "7a671f930c4053a5", "c": "export interface NutritionResult {\n isFood: boolean;\n reason?: string;\n name: string;\n calories: number;\n protein: number;\n carbs: number;\n fat: number;\n amount: string; // e.g., \"100g\", \"1 piece\"\n}\n\nexport function parseAiError(err: any): string {\n const rawError = err?.message || String(err || '');\n console.log(\"Parsing AI error:\", rawError);\n\n const lowerError = rawError.toLowerCase();\n\n if (\n lowerError.includes('missing or invalid api key') ||\n lowerError.includes('missing api key') ||\n lowerError.includes('api key not valid') || \n lowerError.includes('api_key_invalid') || \n lowerError.includes('invalid_argument') ||\n /status[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n /code[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n (/\\b400\\b/.test(rawError) && !rawError.includes('ms') && !rawError.includes('px'))\n ) {\n return \"API Key 校验失败。请确认在「设置」中填写的 Gemini API Key 是否正确(以 AIzaSy 开头且无前后空格),或点击「清除记录」以重新尝试系统默认渠道。\";\n }\n\n if (\n lowerError.includes('503') || \n lowerError.includes('unavailable') || \n lowerError.includes('high demand') || \n lowerError.includes('temporary') ||\n lowerError.includes('overloaded')\n ) {\n return \"AI 估算服务暂时繁忙(Gemini 模型当前请求量过载),请稍后重试。您也可以在右上角「设置」中配置您专属的 Gemini API Key 解锁专属通道。\";\n }\n\n if (\n lowerError.includes('429') || \n lowerError.includes('rate limit') || \n lowerError.includes('quota')\n ) {\n return \"已达到当前的免费调用频率限制,请一分钟后再试。您可以在右上角「设置」中配置您专属的 API Key 以解决频率限制。\";\n }\n\n if (\n lowerError.includes('model not found') || \n lowerError.includes('404')\n ) {\n return \"模型服务接口暂时不可用(404),请稍后再试。\";\n }\n\n if (\n lowerError.includes('failed to fetch') || \n lowerError.includes('network') || \n lowerError.includes('fetch')\n ) {\n return \"无法连接至 AI 服务端,请检查您的网络。如果是连接超时,在「设置」中填写您的专属 API Key 可帮助顺利访问。\";\n }\n\n return `识别出错,请重试: ${rawError.substring(0, 150)}`;\n}\n\nfunction getProvider(): 'gemini' | 'deepseek' {\n if (typeof window === 'undefined') return 'gemini';\n return localStorage.getItem('ai_provider') === 'deepseek' ? 'deepseek' : 'gemini';\n}\n\n// In a web deployment the app and the API share an origin, so a relative\n// path works. Inside a Capacitor native build the app runs from a local\n// origin (capacitor://localhost), so we must call the deployed backend by\n// its full URL. VITE_API_BASE is injected at build time for native builds.\nconst API_BASE = (import.meta as any).env?.VITE_API_BASE || '';\n\nfunction apiUrl(path: string): string {\n return `${API_BASE}${path}`;\n}\n\nfunction getCustomApiKey() {\n if (typeof window === 'undefined') return null;\n // Use the key matching the selected provider\n const provider = getProvider();\n return provider === 'deepseek'\n ? localStorage.getItem('user_deepseek_api_key')\n : localStorage.getItem('user_gemini_api_key');\n}\n\nexport async function estimateLogNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: false,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function estimateLibraryNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: true,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\n// ===== AI Coach: daily tip + next-meal recommendation =====\n\nexport interface MealSuggestion {\n name: string;\n calories: number;\n protein: number;\n reason: string;\n}\n\nasync function callCoach(kind: 'tip' | 'meal', data: any): Promise<any> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/coach'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ kind, data, customApiKey, provider }),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error(err.error || `请求失败: ${response.statusText}`);\n }\n return response.json();\n}\n\nexport async function getDailyTip(data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n}): Promise<string> {\n const res = await callCoach('tip', data);\n return res.tip || '';\n}\n\nexport async function getMealSuggestions(data: {\n goal: string;\n totals: { calories: number; protein: number; carbs: number; fat: number };\n goals: { calories: number; protein: number; carbs: number; fat: number };\n}): Promise<MealSuggestion[]> {\n const res = await callCoach('meal', data);\n return res.suggestions || [];\n}\n"}, "22c521daf3602ee25650e739941af0a6caec9f2a": {"t": 1785754390.0259233, "n": 4107, "h": "6b2e733be467ef98", "c": "export interface NutritionResult {\n isFood: boolean;\n reason?: string;\n name: string;\n calories: number;\n protein: number;\n carbs: number;\n fat: number;\n amount: string; // e.g., \"100g\", \"1 piece\"\n}\n\nexport function parseAiError(err: any): string {\n const rawError = err?.message || String(err || '');\n console.log(\"Parsing AI error:\", rawError);\n\n const lowerError = rawError.toLowerCase();\n\n if (\n lowerError.includes('missing or invalid api key') ||\n lowerError.includes('missing api key') ||\n lowerError.includes('api key not valid') || \n lowerError.includes('api_key_invalid') || \n lowerError.includes('invalid_argument') ||\n /status[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n /code[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n (/\\b400\\b/.test(rawError) && !rawError.includes('ms') && !rawError.includes('px'))\n ) {\n return \"API Key 校验失败。请确认在「设置」中填写的 Gemini API Key 是否正确(以 AIzaSy 开头且无前后空格),或点击「清除记录」以重新尝试系统默认渠道。\";\n }\n\n if (\n lowerError.includes('503') || \n lowerError.includes('unavailable') || \n lowerError.includes('high demand') || \n lowerError.includes('temporary') ||\n lowerError.includes('overloaded')\n ) {\n return \"AI 估算服务暂时繁忙(Gemini 模型当前请求量过载),请稍后重试。您也可以在右上角「设置」中配置您专属的 Gemini API Key 解锁专属通道。\";\n }\n\n if (\n lowerError.includes('429') || \n lowerError.includes('rate limit') || \n lowerError.includes('quota')\n ) {\n return \"已达到当前的免费调用频率限制,请一分钟后再试。您可以在右上角「设置」中配置您专属的 API Key 以解决频率限制。\";\n }\n\n if (\n lowerError.includes('model not found') || \n lowerError.includes('404')\n ) {\n return \"模型服务接口暂时不可用(404),请稍后再试。\";\n }\n\n if (\n lowerError.includes('failed to fetch') || \n lowerError.includes('network') || \n lowerError.includes('fetch')\n ) {\n return \"无法连接至 AI 服务端,请检查您的网络。如果是连接超时,在「设置」中填写您的专属 API Key 可帮助顺利访问。\";\n }\n\n return `识别出错,请重试: ${rawError.substring(0, 150)}`;\n}\n\nfunction getProvider(): 'gemini' | 'deepseek' {\n if (typeof window === 'undefined') return 'gemini';\n return localStorage.getItem('ai_provider') === 'deepseek' ? 'deepseek' : 'gemini';\n}\n\n// In a web deployment the app and the API share an origin, so a relative\n// path works. Inside a Capacitor native build the app runs from a local\n// origin (capacitor://localhost), so we must call the deployed backend by\n// its full URL. VITE_API_BASE is injected at build time for native builds.\nconst API_BASE = (import.meta as any).env?.VITE_API_BASE || '';\n\nfunction apiUrl(path: string): string {\n return `${API_BASE}${path}`;\n}\n\nfunction getCustomApiKey() {\n if (typeof window === 'undefined') return null;\n // Use the key matching the selected provider\n const provider = getProvider();\n return provider === 'deepseek'\n ? localStorage.getItem('user_deepseek_api_key')\n : localStorage.getItem('user_gemini_api_key');\n}\n\nexport async function estimateLogNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: false,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function estimateLibraryNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch(apiUrl('/api/estimate'), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: true,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n"}, "7d33b71d6e920fdd34fe0bd6f51b7880c8323db9": {"t": 1785754391.2649314, "n": 3654, "h": "d32cd77b594b6de3", "c": "export interface NutritionResult {\n isFood: boolean;\n reason?: string;\n name: string;\n calories: number;\n protein: number;\n carbs: number;\n fat: number;\n amount: string; // e.g., \"100g\", \"1 piece\"\n}\n\nexport function parseAiError(err: any): string {\n const rawError = err?.message || String(err || '');\n console.log(\"Parsing AI error:\", rawError);\n\n const lowerError = rawError.toLowerCase();\n\n if (\n lowerError.includes('missing or invalid api key') ||\n lowerError.includes('missing api key') ||\n lowerError.includes('api key not valid') || \n lowerError.includes('api_key_invalid') || \n lowerError.includes('invalid_argument') ||\n /status[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n /code[\"'\\s]*:[\\s]*400/i.test(rawError) ||\n (/\\b400\\b/.test(rawError) && !rawError.includes('ms') && !rawError.includes('px'))\n ) {\n return \"API Key 校验失败。请确认在「设置」中填写的 Gemini API Key 是否正确(以 AIzaSy 开头且无前后空格),或点击「清除记录」以重新尝试系统默认渠道。\";\n }\n\n if (\n lowerError.includes('503') || \n lowerError.includes('unavailable') || \n lowerError.includes('high demand') || \n lowerError.includes('temporary') ||\n lowerError.includes('overloaded')\n ) {\n return \"AI 估算服务暂时繁忙(Gemini 模型当前请求量过载),请稍后重试。您也可以在右上角「设置」中配置您专属的 Gemini API Key 解锁专属通道。\";\n }\n\n if (\n lowerError.includes('429') || \n lowerError.includes('rate limit') || \n lowerError.includes('quota')\n ) {\n return \"已达到当前的免费调用频率限制,请一分钟后再试。您可以在右上角「设置」中配置您专属的 API Key 以解决频率限制。\";\n }\n\n if (\n lowerError.includes('model not found') || \n lowerError.includes('404')\n ) {\n return \"模型服务接口暂时不可用(404),请稍后再试。\";\n }\n\n if (\n lowerError.includes('failed to fetch') || \n lowerError.includes('network') || \n lowerError.includes('fetch')\n ) {\n return \"无法连接至 AI 服务端,请检查您的网络。如果是连接超时,在「设置」中填写您的专属 API Key 可帮助顺利访问。\";\n }\n\n return `识别出错,请重试: ${rawError.substring(0, 150)}`;\n}\n\nfunction getProvider(): 'gemini' | 'deepseek' {\n if (typeof window === 'undefined') return 'gemini';\n return localStorage.getItem('ai_provider') === 'deepseek' ? 'deepseek' : 'gemini';\n}\n\nfunction getCustomApiKey() {\n if (typeof window === 'undefined') return null;\n // Use the key matching the selected provider\n const provider = getProvider();\n return provider === 'deepseek'\n ? localStorage.getItem('user_deepseek_api_key')\n : localStorage.getItem('user_gemini_api_key');\n}\n\nexport async function estimateLogNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch('/api/estimate', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: false,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function estimateLibraryNutrition(query: string, imageBase64?: string): Promise<NutritionResult> {\n const customApiKey = getCustomApiKey();\n const provider = getProvider();\n const response = await fetch('/api/estimate', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n query,\n imageBase64,\n isLibrary: true,\n customApiKey,\n provider\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || `Failed to fetch: ${response.statusText}`);\n }\n\n return response.json();\n}\n"}}}} |