1 line
26 KiB
JSON
1 line
26 KiB
JSON
{"repo": "brucey-rgb/picturemanagementsystem", "files": {"src/lib/ai/deepseek.ts": {"9a6e6671eb63155f48bc2ba91cc530f6497e1235": {"t": 1785745603.6860864, "n": 12573, "h": "203f59bbbe459679", "c": "/**\n * DeepSeek AI Service\n * Provides image analysis functionality using DeepSeek Vision API\n */\n\nexport interface AIAnalysisResult {\n scenes: string[]\n objects: string[]\n emotions: string[]\n confidence: number\n rawResponse?: any\n}\n\ninterface DeepSeekResponse {\n choices?: Array<{\n message: {\n content: string\n }\n }>\n}\n\nconst DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY\nconst DEEPSEEK_BASE_URL = process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com'\nconst API_TIMEOUT = parseInt(process.env.AI_ANALYSIS_TIMEOUT || '30000')\n\nconst ANALYSIS_PROMPT = `Analyze this image and provide detailed information in the following categories:\n\n1. SCENES: What type of scene is this? (e.g., beach, city, mountains, indoor, outdoor, park, forest, ocean, sunset, sunrise, night, etc.)\n2. OBJECTS: What objects or subjects are visible? (e.g., people, person, animal, dog, cat, building, car, tree, food, flower, etc.)\n3. EMOTIONS: What emotion or atmosphere does this image convey? (e.g., happy, peaceful, sad, tense, energetic, calm, joyful, romantic, mysterious, etc.)\n\nProvide your analysis in the following JSON format only, without any additional text:\n{\n \"scenes\": [\"scene1\", \"scene2\"],\n \"objects\": [\"object1\", \"object2\"],\n \"emotions\": [\"emotion1\", \"emotion2\"]\n}\n\nBe specific and list multiple values for each category if applicable. Use lowercase for all values.`\n\n/**\n * Analyzes an image using DeepSeek Vision API\n * @param imageBuffer The image buffer to analyze\n * @returns Analysis result with scenes, objects, emotions, and confidence\n */\nexport async function analyzeImage(imageBuffer: Buffer): Promise<AIAnalysisResult> {\n try {\n // Validate API key\n if (!DEEPSEEK_API_KEY) {\n console.error('[DeepSeek] API key not configured')\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n }\n }\n\n // Convert buffer to base64\n const base64Image = imageBuffer.toString('base64')\n const mimeType = detectMimeType(imageBuffer)\n\n // Create abort controller for timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT)\n\n console.log('[DeepSeek] Starting image analysis...')\n\n // Call DeepSeek API\n const response = await fetch(`${DEEPSEEK_BASE_URL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: `data:${mimeType};base64,${base64Image}`,\n },\n },\n {\n type: 'text',\n text: ANALYSIS_PROMPT,\n },\n ],\n },\n ],\n temperature: 0.3,\n max_tokens: 500,\n }),\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[DeepSeek] API error: ${response.status}`, errorText)\n throw new Error(`DeepSeek API error: ${response.status}`)\n }\n\n const data: DeepSeekResponse = await response.json()\n console.log('[DeepSeek] API response received')\n\n // Parse response\n const result = parseDeepSeekResponse(data)\n console.log('[DeepSeek] Analysis completed:', {\n scenes: result.scenes.length,\n objects: result.objects.length,\n emotions: result.emotions.length,\n confidence: result.confidence.toFixed(2),\n })\n\n return result\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n console.error('[DeepSeek] Analysis timeout after', API_TIMEOUT, 'ms')\n } else {\n console.error('[DeepSeek] Analysis failed:', error.message)\n }\n } else {\n console.error('[DeepSeek] Unknown error:', error)\n }\n\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n }\n }\n}\n\n/**\n * Parse DeepSeek API response and extract structured data\n */\nfunction parseDeepSeekResponse(data: DeepSeekResponse): AIAnalysisResult {\n try {\n const content = data.choices?.[0]?.message?.content\n\n if (!content) {\n console.warn('[DeepSeek] No content in response')\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n rawResponse: data,\n }\n }\n\n // Try to extract JSON from response\n // DeepSeek might wrap JSON in markdown code blocks\n let jsonContent = content.trim()\n\n // Remove markdown code blocks if present\n if (jsonContent.startsWith('```')) {\n jsonContent = jsonContent.replace(/```json?\\n?/g, '').replace(/```$/g, '').trim()\n }\n\n // Parse JSON\n const parsed = JSON.parse(jsonContent)\n\n // Normalize data\n const scenes = Array.isArray(parsed.scenes)\n ? parsed.scenes.map((s: string) => s.toLowerCase().trim()).filter(Boolean)\n : []\n const objects = Array.isArray(parsed.objects)\n ? parsed.objects.map((o: string) => o.toLowerCase().trim()).filter(Boolean)\n : []\n const emotions = Array.isArray(parsed.emotions)\n ? parsed.emotions.map((e: string) => e.toLowerCase().trim()).filter(Boolean)\n : []\n\n // Calculate confidence based on number of detected items\n const totalItems = scenes.length + objects.length + emotions.length\n const confidence = totalItems > 0\n ? Math.min(0.95, 0.5 + (totalItems * 0.05)) // 0.5 base + 0.05 per item, max 0.95\n : 0\n\n return {\n scenes: scenes.slice(0, 10), // Limit to 10 items per category\n objects: objects.slice(0, 10),\n emotions: emotions.slice(0, 5), // Fewer emotions\n confidence,\n rawResponse: data,\n }\n } catch (error) {\n console.error('[DeepSeek] Failed to parse response:', error)\n console.error('[DeepSeek] Raw content:', data.choices?.[0]?.message?.content)\n\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n rawResponse: data,\n }\n }\n}\n\n/**\n * Detect MIME type from buffer\n */\nfunction detectMimeType(buffer: Buffer): string {\n // Check magic numbers\n if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {\n return 'image/jpeg'\n }\n if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {\n return 'image/png'\n }\n if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {\n return 'image/gif'\n }\n if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) {\n return 'image/webp'\n }\n\n // Default to JPEG\n return 'image/jpeg'\n}\n\n// ==================== Chat Completion API ====================\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant'\n content: string\n}\n\nexport interface ChatCompletionResponse {\n id: string\n object: string\n created: number\n model: string\n choices: Array<{\n index: number\n message: {\n role: string\n content: string\n }\n finish_reason: string\n }>\n usage: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n }\n}\n\nconst CHAT_TIMEOUT = 60000 // 60 seconds for chat\n\n/**\n * Send a chat completion request to DeepSeek API\n * @param messages Array of chat messages\n * @param options Optional configuration\n * @returns The assistant's reply\n */\nexport async function chatCompletion(\n messages: ChatMessage[],\n options?: {\n model?: string\n temperature?: number\n maxTokens?: number\n }\n): Promise<string> {\n if (!DEEPSEEK_API_KEY) {\n throw new Error('DeepSeek API key not configured')\n }\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), CHAT_TIMEOUT)\n\n try {\n console.log('[DeepSeek Chat] Sending chat completion request...')\n\n const response = await fetch(`${DEEPSEEK_BASE_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,\n },\n body: JSON.stringify({\n model: options?.model || 'deepseek-chat',\n messages,\n temperature: options?.temperature ?? 0.7,\n max_tokens: options?.maxTokens ?? 1024,\n }),\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error('[DeepSeek Chat] API error:', response.status, errorText)\n throw new Error(`DeepSeek API error: ${response.status} - ${errorText}`)\n }\n\n const data: ChatCompletionResponse = await response.json()\n console.log('[DeepSeek Chat] Response received, tokens used:', data.usage?.total_tokens)\n\n const reply = data.choices?.[0]?.message?.content\n if (!reply) {\n throw new Error('No reply content in response')\n }\n\n return reply\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n console.error('[DeepSeek Chat] Request timeout after', CHAT_TIMEOUT, 'ms')\n throw new Error('请求超时,请稍后重试')\n }\n console.error('[DeepSeek Chat] Chat completion failed:', error.message)\n throw error\n }\n\n console.error('[DeepSeek Chat] Unknown error:', error)\n throw new Error('未知错误')\n }\n}\n\n/**\n * Image context for building system prompt\n */\nexport interface ImageContext {\n labels?: string[]\n tags?: string[]\n title?: string | null\n description?: string | null\n takenAt?: string | null\n cameraModel?: string | null\n cameraMake?: string | null\n focalLength?: number | null\n aperture?: number | null\n shutterSpeed?: string | null\n iso?: number | null\n width?: number\n height?: number\n}\n\n/**\n * Build system prompt for image Q&A\n * @param imageContext Information about the image\n * @returns System prompt string\n */\nexport function buildImageSystemPrompt(imageContext: ImageContext): string {\n const parts: string[] = [\n '你是一个专业的图片分析助手。用户会向你提问关于一张图片的问题,你需要根据以下图片信息来回答。',\n '',\n '## 重要规则',\n '1. 只根据提供的图片信息回答,不要编造不存在的内容',\n '2. 如果无法从给定信息中得出答案,请诚实说明',\n '3. 回答要简洁、有帮助',\n '4. 使用中文回答',\n '',\n '## 图片信息',\n ]\n\n // Add image metadata\n if (imageContext.title) {\n parts.push(`- 标题: ${imageContext.title}`)\n }\n\n if (imageContext.description) {\n parts.push(`- 描述: ${imageContext.description}`)\n }\n\n if (imageContext.width && imageContext.height) {\n parts.push(`- 尺寸: ${imageContext.width} × ${imageContext.height} 像素`)\n }\n\n // Add AI-detected content\n if (imageContext.labels && imageContext.labels.length > 0) {\n parts.push(`- AI识别内容: ${imageContext.labels.join('、')}`)\n }\n\n if (imageContext.tags && imageContext.tags.length > 0) {\n parts.push(`- 标签: ${imageContext.tags.join('、')}`)\n }\n\n // Add EXIF data\n if (imageContext.takenAt) {\n parts.push(`- 拍摄时间: ${imageContext.takenAt}`)\n }\n\n if (imageContext.cameraMake || imageContext.cameraModel) {\n const camera = [imageContext.cameraMake, imageContext.cameraModel].filter(Boolean).join(' ')\n parts.push(`- 拍摄设备: ${camera}`)\n }\n\n // Add shooting parameters\n const shootingParams: string[] = []\n if (imageContext.focalLength) {\n shootingParams.push(`焦距 ${imageContext.focalLength}mm`)\n }\n if (imageContext.aperture) {\n shootingParams.push(`光圈 f/${imageContext.aperture}`)\n }\n if (imageContext.shutterSpeed) {\n shootingParams.push(`快门 ${imageContext.shutterSpeed}`)\n }\n if (imageContext.iso) {\n shootingParams.push(`ISO ${imageContext.iso}`)\n }\n\n if (shootingParams.length > 0) {\n parts.push(`- 拍摄参数: ${shootingParams.join('、')}`)\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Generate a reply for an image-related question\n * @param question User's question\n * @param imageContext Image information\n * @param conversationHistory Previous messages in the conversation\n * @returns AI-generated reply\n */\nexport async function generateImageReply(\n question: string,\n imageContext: ImageContext,\n conversationHistory: Array<{ role: 'user' | 'assistant'; content: string }> = []\n): Promise<string> {\n const systemPrompt = buildImageSystemPrompt(imageContext)\n\n const messages: ChatMessage[] = [\n { role: 'system', content: systemPrompt },\n ...conversationHistory.map(msg => ({\n role: msg.role as 'user' | 'assistant',\n content: msg.content,\n })),\n { role: 'user', content: question },\n ]\n\n return chatCompletion(messages)\n}\n"}, "cae8857c5376957d566c87797a699badd25a03c9": {"t": 1785754703.0646966, "n": 12573, "h": "203f59bbbe459679", "c": "/**\n * DeepSeek AI Service\n * Provides image analysis functionality using DeepSeek Vision API\n */\n\nexport interface AIAnalysisResult {\n scenes: string[]\n objects: string[]\n emotions: string[]\n confidence: number\n rawResponse?: any\n}\n\ninterface DeepSeekResponse {\n choices?: Array<{\n message: {\n content: string\n }\n }>\n}\n\nconst DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY\nconst DEEPSEEK_BASE_URL = process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com'\nconst API_TIMEOUT = parseInt(process.env.AI_ANALYSIS_TIMEOUT || '30000')\n\nconst ANALYSIS_PROMPT = `Analyze this image and provide detailed information in the following categories:\n\n1. SCENES: What type of scene is this? (e.g., beach, city, mountains, indoor, outdoor, park, forest, ocean, sunset, sunrise, night, etc.)\n2. OBJECTS: What objects or subjects are visible? (e.g., people, person, animal, dog, cat, building, car, tree, food, flower, etc.)\n3. EMOTIONS: What emotion or atmosphere does this image convey? (e.g., happy, peaceful, sad, tense, energetic, calm, joyful, romantic, mysterious, etc.)\n\nProvide your analysis in the following JSON format only, without any additional text:\n{\n \"scenes\": [\"scene1\", \"scene2\"],\n \"objects\": [\"object1\", \"object2\"],\n \"emotions\": [\"emotion1\", \"emotion2\"]\n}\n\nBe specific and list multiple values for each category if applicable. Use lowercase for all values.`\n\n/**\n * Analyzes an image using DeepSeek Vision API\n * @param imageBuffer The image buffer to analyze\n * @returns Analysis result with scenes, objects, emotions, and confidence\n */\nexport async function analyzeImage(imageBuffer: Buffer): Promise<AIAnalysisResult> {\n try {\n // Validate API key\n if (!DEEPSEEK_API_KEY) {\n console.error('[DeepSeek] API key not configured')\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n }\n }\n\n // Convert buffer to base64\n const base64Image = imageBuffer.toString('base64')\n const mimeType = detectMimeType(imageBuffer)\n\n // Create abort controller for timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT)\n\n console.log('[DeepSeek] Starting image analysis...')\n\n // Call DeepSeek API\n const response = await fetch(`${DEEPSEEK_BASE_URL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: `data:${mimeType};base64,${base64Image}`,\n },\n },\n {\n type: 'text',\n text: ANALYSIS_PROMPT,\n },\n ],\n },\n ],\n temperature: 0.3,\n max_tokens: 500,\n }),\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[DeepSeek] API error: ${response.status}`, errorText)\n throw new Error(`DeepSeek API error: ${response.status}`)\n }\n\n const data: DeepSeekResponse = await response.json()\n console.log('[DeepSeek] API response received')\n\n // Parse response\n const result = parseDeepSeekResponse(data)\n console.log('[DeepSeek] Analysis completed:', {\n scenes: result.scenes.length,\n objects: result.objects.length,\n emotions: result.emotions.length,\n confidence: result.confidence.toFixed(2),\n })\n\n return result\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n console.error('[DeepSeek] Analysis timeout after', API_TIMEOUT, 'ms')\n } else {\n console.error('[DeepSeek] Analysis failed:', error.message)\n }\n } else {\n console.error('[DeepSeek] Unknown error:', error)\n }\n\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n }\n }\n}\n\n/**\n * Parse DeepSeek API response and extract structured data\n */\nfunction parseDeepSeekResponse(data: DeepSeekResponse): AIAnalysisResult {\n try {\n const content = data.choices?.[0]?.message?.content\n\n if (!content) {\n console.warn('[DeepSeek] No content in response')\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n rawResponse: data,\n }\n }\n\n // Try to extract JSON from response\n // DeepSeek might wrap JSON in markdown code blocks\n let jsonContent = content.trim()\n\n // Remove markdown code blocks if present\n if (jsonContent.startsWith('```')) {\n jsonContent = jsonContent.replace(/```json?\\n?/g, '').replace(/```$/g, '').trim()\n }\n\n // Parse JSON\n const parsed = JSON.parse(jsonContent)\n\n // Normalize data\n const scenes = Array.isArray(parsed.scenes)\n ? parsed.scenes.map((s: string) => s.toLowerCase().trim()).filter(Boolean)\n : []\n const objects = Array.isArray(parsed.objects)\n ? parsed.objects.map((o: string) => o.toLowerCase().trim()).filter(Boolean)\n : []\n const emotions = Array.isArray(parsed.emotions)\n ? parsed.emotions.map((e: string) => e.toLowerCase().trim()).filter(Boolean)\n : []\n\n // Calculate confidence based on number of detected items\n const totalItems = scenes.length + objects.length + emotions.length\n const confidence = totalItems > 0\n ? Math.min(0.95, 0.5 + (totalItems * 0.05)) // 0.5 base + 0.05 per item, max 0.95\n : 0\n\n return {\n scenes: scenes.slice(0, 10), // Limit to 10 items per category\n objects: objects.slice(0, 10),\n emotions: emotions.slice(0, 5), // Fewer emotions\n confidence,\n rawResponse: data,\n }\n } catch (error) {\n console.error('[DeepSeek] Failed to parse response:', error)\n console.error('[DeepSeek] Raw content:', data.choices?.[0]?.message?.content)\n\n return {\n scenes: [],\n objects: [],\n emotions: [],\n confidence: 0,\n rawResponse: data,\n }\n }\n}\n\n/**\n * Detect MIME type from buffer\n */\nfunction detectMimeType(buffer: Buffer): string {\n // Check magic numbers\n if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {\n return 'image/jpeg'\n }\n if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {\n return 'image/png'\n }\n if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {\n return 'image/gif'\n }\n if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) {\n return 'image/webp'\n }\n\n // Default to JPEG\n return 'image/jpeg'\n}\n\n// ==================== Chat Completion API ====================\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant'\n content: string\n}\n\nexport interface ChatCompletionResponse {\n id: string\n object: string\n created: number\n model: string\n choices: Array<{\n index: number\n message: {\n role: string\n content: string\n }\n finish_reason: string\n }>\n usage: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n }\n}\n\nconst CHAT_TIMEOUT = 60000 // 60 seconds for chat\n\n/**\n * Send a chat completion request to DeepSeek API\n * @param messages Array of chat messages\n * @param options Optional configuration\n * @returns The assistant's reply\n */\nexport async function chatCompletion(\n messages: ChatMessage[],\n options?: {\n model?: string\n temperature?: number\n maxTokens?: number\n }\n): Promise<string> {\n if (!DEEPSEEK_API_KEY) {\n throw new Error('DeepSeek API key not configured')\n }\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), CHAT_TIMEOUT)\n\n try {\n console.log('[DeepSeek Chat] Sending chat completion request...')\n\n const response = await fetch(`${DEEPSEEK_BASE_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,\n },\n body: JSON.stringify({\n model: options?.model || 'deepseek-chat',\n messages,\n temperature: options?.temperature ?? 0.7,\n max_tokens: options?.maxTokens ?? 1024,\n }),\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error('[DeepSeek Chat] API error:', response.status, errorText)\n throw new Error(`DeepSeek API error: ${response.status} - ${errorText}`)\n }\n\n const data: ChatCompletionResponse = await response.json()\n console.log('[DeepSeek Chat] Response received, tokens used:', data.usage?.total_tokens)\n\n const reply = data.choices?.[0]?.message?.content\n if (!reply) {\n throw new Error('No reply content in response')\n }\n\n return reply\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n console.error('[DeepSeek Chat] Request timeout after', CHAT_TIMEOUT, 'ms')\n throw new Error('请求超时,请稍后重试')\n }\n console.error('[DeepSeek Chat] Chat completion failed:', error.message)\n throw error\n }\n\n console.error('[DeepSeek Chat] Unknown error:', error)\n throw new Error('未知错误')\n }\n}\n\n/**\n * Image context for building system prompt\n */\nexport interface ImageContext {\n labels?: string[]\n tags?: string[]\n title?: string | null\n description?: string | null\n takenAt?: string | null\n cameraModel?: string | null\n cameraMake?: string | null\n focalLength?: number | null\n aperture?: number | null\n shutterSpeed?: string | null\n iso?: number | null\n width?: number\n height?: number\n}\n\n/**\n * Build system prompt for image Q&A\n * @param imageContext Information about the image\n * @returns System prompt string\n */\nexport function buildImageSystemPrompt(imageContext: ImageContext): string {\n const parts: string[] = [\n '你是一个专业的图片分析助手。用户会向你提问关于一张图片的问题,你需要根据以下图片信息来回答。',\n '',\n '## 重要规则',\n '1. 只根据提供的图片信息回答,不要编造不存在的内容',\n '2. 如果无法从给定信息中得出答案,请诚实说明',\n '3. 回答要简洁、有帮助',\n '4. 使用中文回答',\n '',\n '## 图片信息',\n ]\n\n // Add image metadata\n if (imageContext.title) {\n parts.push(`- 标题: ${imageContext.title}`)\n }\n\n if (imageContext.description) {\n parts.push(`- 描述: ${imageContext.description}`)\n }\n\n if (imageContext.width && imageContext.height) {\n parts.push(`- 尺寸: ${imageContext.width} × ${imageContext.height} 像素`)\n }\n\n // Add AI-detected content\n if (imageContext.labels && imageContext.labels.length > 0) {\n parts.push(`- AI识别内容: ${imageContext.labels.join('、')}`)\n }\n\n if (imageContext.tags && imageContext.tags.length > 0) {\n parts.push(`- 标签: ${imageContext.tags.join('、')}`)\n }\n\n // Add EXIF data\n if (imageContext.takenAt) {\n parts.push(`- 拍摄时间: ${imageContext.takenAt}`)\n }\n\n if (imageContext.cameraMake || imageContext.cameraModel) {\n const camera = [imageContext.cameraMake, imageContext.cameraModel].filter(Boolean).join(' ')\n parts.push(`- 拍摄设备: ${camera}`)\n }\n\n // Add shooting parameters\n const shootingParams: string[] = []\n if (imageContext.focalLength) {\n shootingParams.push(`焦距 ${imageContext.focalLength}mm`)\n }\n if (imageContext.aperture) {\n shootingParams.push(`光圈 f/${imageContext.aperture}`)\n }\n if (imageContext.shutterSpeed) {\n shootingParams.push(`快门 ${imageContext.shutterSpeed}`)\n }\n if (imageContext.iso) {\n shootingParams.push(`ISO ${imageContext.iso}`)\n }\n\n if (shootingParams.length > 0) {\n parts.push(`- 拍摄参数: ${shootingParams.join('、')}`)\n }\n\n return parts.join('\\n')\n}\n\n/**\n * Generate a reply for an image-related question\n * @param question User's question\n * @param imageContext Image information\n * @param conversationHistory Previous messages in the conversation\n * @returns AI-generated reply\n */\nexport async function generateImageReply(\n question: string,\n imageContext: ImageContext,\n conversationHistory: Array<{ role: 'user' | 'assistant'; content: string }> = []\n): Promise<string> {\n const systemPrompt = buildImageSystemPrompt(imageContext)\n\n const messages: ChatMessage[] = [\n { role: 'system', content: systemPrompt },\n ...conversationHistory.map(msg => ({\n role: msg.role as 'user' | 'assistant',\n content: msg.content,\n })),\n { role: 'user', content: question },\n ]\n\n return chatCompletion(messages)\n}\n"}}}} |