| 48 | } |
| 49 | |
| 50 | class AIHandler { |
| 51 | // 使用 Map 管理多个并行请求的 AbortController |
| 52 | private abortControllers = new Map<string, AbortController>() |
| 53 | |
| 54 | /** |
| 55 | * 准备 API 消息数组,处理 systemPrompt 和文件附件 |
| 56 | */ |
| 57 | private async prepareApiMessages( |
| 58 | messages: ChatMessage[], |
| 59 | modelConfig: ModelConfig |
| 60 | ): Promise<ChatMessage[]> { |
| 61 | const attachmentsDir = path.join(app.getPath('userData'), 'attachments') |
| 62 | |
| 63 | const apiMessages = await Promise.all( |
| 64 | messages.map(async (msg) => { |
| 65 | // 如果消息有附件,需要转换为多模态格式 |
| 66 | if (msg.attachments && msg.attachments.length > 0) { |
| 67 | const contentParts: Array<{ |
| 68 | type: 'text' | 'image_url' |
| 69 | text?: string |
| 70 | image_url?: { url: string } |
| 71 | }> = [] |
| 72 | |
| 73 | // 添加文本内容 |
| 74 | if (typeof msg.content === 'string' && msg.content.trim()) { |
| 75 | contentParts.push({ |
| 76 | type: 'text', |
| 77 | text: msg.content |
| 78 | }) |
| 79 | } |
| 80 | |
| 81 | // 添加图片附件 - 从文件系统读取 |
| 82 | for (const attachment of msg.attachments) { |
| 83 | if (attachment.type.startsWith('image/')) { |
| 84 | try { |
| 85 | const fullPath = path.join(attachmentsDir, attachment.localPath) |
| 86 | const buffer = await readFile(fullPath) |
| 87 | const base64Content = buffer.toString('base64') |
| 88 | |
| 89 | contentParts.push({ |
| 90 | type: 'image_url', |
| 91 | image_url: { |
| 92 | url: `data:${attachment.type};base64,${base64Content}` |
| 93 | } |
| 94 | }) |
| 95 | } catch (error) { |
| 96 | console.error('Failed to read attachment file:', attachment.localPath, error) |
| 97 | // 跳过无法读取的附件 |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | return { |
| 103 | role: msg.role, |
| 104 | content: contentParts |
| 105 | } |
| 106 | } |
| 107 |
nothing calls this directly
no outgoing calls
no test coverage detected