|
1 | 1 | import { ipcMain } from 'electron' |
| 2 | +import { createParser } from 'eventsource-parser' |
2 | 3 |
|
3 | 4 | export interface LLMConfig { |
4 | 5 | apiHost: string |
@@ -97,58 +98,61 @@ class AIHandler { |
97 | 98 | let fullResponse = '' |
98 | 99 | let fullReasoning = '' |
99 | 100 |
|
| 101 | + // 使用 eventsource-parser 来解析 SSE 数据 |
| 102 | + const parser = createParser({ |
| 103 | + onEvent: (eventData) => { |
| 104 | + if (eventData.data === '[DONE]') { |
| 105 | + event.sender.send('ai-stream-data', { |
| 106 | + requestId: request.requestId, |
| 107 | + type: 'complete', |
| 108 | + content: fullResponse, |
| 109 | + reasoning_content: fullReasoning || undefined |
| 110 | + } as AIStreamChunk) |
| 111 | + return |
| 112 | + } |
| 113 | + |
| 114 | + try { |
| 115 | + const parsed = JSON.parse(eventData.data) |
| 116 | + const delta = parsed.choices?.[0]?.delta |
| 117 | + const content = delta?.content |
| 118 | + const reasoning_content = |
| 119 | + delta?.reasoning_content || |
| 120 | + delta?.reasoning || |
| 121 | + parsed.reasoning_content || |
| 122 | + parsed?.reasoning |
| 123 | + |
| 124 | + if (content) { |
| 125 | + fullResponse += content |
| 126 | + event.sender.send('ai-stream-data', { |
| 127 | + requestId: request.requestId, |
| 128 | + type: 'chunk', |
| 129 | + content: content |
| 130 | + } as AIStreamChunk) |
| 131 | + } |
| 132 | + |
| 133 | + if (reasoning_content) { |
| 134 | + fullReasoning += reasoning_content |
| 135 | + event.sender.send('ai-stream-data', { |
| 136 | + requestId: request.requestId, |
| 137 | + type: 'reasoning_content', |
| 138 | + reasoning_content: reasoning_content |
| 139 | + } as AIStreamChunk) |
| 140 | + } |
| 141 | + } catch (e) { |
| 142 | + // 忽略解析错误,继续处理下一行 |
| 143 | + console.warn('Failed to parse streaming data:', e) |
| 144 | + } |
| 145 | + } |
| 146 | + }) |
| 147 | + |
100 | 148 | try { |
101 | 149 | while (true) { |
102 | 150 | const { done, value } = await reader.read() |
103 | 151 |
|
104 | 152 | if (done) break |
105 | 153 |
|
106 | 154 | const chunk = decoder.decode(value, { stream: true }) |
107 | | - const lines = chunk.split('\n').filter((line) => line.trim() !== '') |
108 | | - |
109 | | - for (const line of lines) { |
110 | | - if (line.startsWith('data: ')) { |
111 | | - const data = line.slice(6).trim() |
112 | | - |
113 | | - if (data === '[DONE]') { |
114 | | - event.sender.send('ai-stream-data', { |
115 | | - requestId: request.requestId, |
116 | | - type: 'complete', |
117 | | - content: fullResponse, |
118 | | - reasoning_content: fullReasoning || undefined |
119 | | - } as AIStreamChunk) |
120 | | - return |
121 | | - } |
122 | | - |
123 | | - try { |
124 | | - const parsed = JSON.parse(data) |
125 | | - const delta = parsed.choices?.[0]?.delta |
126 | | - const content = delta?.content |
127 | | - const reasoning_content = delta?.reasoning_content || parsed.reasoning_content |
128 | | - |
129 | | - if (content) { |
130 | | - fullResponse += content |
131 | | - event.sender.send('ai-stream-data', { |
132 | | - requestId: request.requestId, |
133 | | - type: 'chunk', |
134 | | - content: content |
135 | | - } as AIStreamChunk) |
136 | | - } |
137 | | - |
138 | | - if (reasoning_content) { |
139 | | - fullReasoning += reasoning_content |
140 | | - event.sender.send('ai-stream-data', { |
141 | | - requestId: request.requestId, |
142 | | - type: 'reasoning_content', |
143 | | - reasoning_content: reasoning_content |
144 | | - } as AIStreamChunk) |
145 | | - } |
146 | | - } catch (e) { |
147 | | - // 忽略解析错误,继续处理下一行 |
148 | | - console.warn('Failed to parse streaming data:', e) |
149 | | - } |
150 | | - } |
151 | | - } |
| 155 | + parser.feed(chunk) |
152 | 156 | } |
153 | 157 |
|
154 | 158 | event.sender.send('ai-stream-data', { |
@@ -238,7 +242,8 @@ class AIHandler { |
238 | 242 |
|
239 | 243 | const data = await response.json() |
240 | 244 | const content = data.choices?.[0]?.message?.content || '' |
241 | | - const reasoning_content = data.choices?.[0]?.message?.reasoning_content |
| 245 | + const reasoning_content = |
| 246 | + data.choices?.[0]?.message?.reasoning_content || data.choices?.[0]?.message?.reasoning |
242 | 247 |
|
243 | 248 | return { |
244 | 249 | success: true, |
|
0 commit comments