11import { globalShortcut , ipcMain } from 'electron'
2+ import type { ModelMessage } from 'ai'
23import { takeScreenshot } from './take-screenshot'
3- import { getSolutionStream } from './ai'
4+ import { getSolutionStream , getFollowUpStream } from './ai'
45import { state } from './state'
56import { settings } from './settings'
67
@@ -29,6 +30,9 @@ interface StreamContext {
2930
3031let currentStreamContext : StreamContext | null = null
3132
33+ // Conversation history tracking
34+ let conversationMessages : ModelMessage [ ] = [ ]
35+
3236function abortCurrentStream ( reason : AbortReason ) {
3337 if ( ! currentStreamContext ) return
3438 currentStreamContext . reason = reason
@@ -53,6 +57,22 @@ const callbacks: Record<string, () => void> = {
5357 abortCurrentStream ( 'new-request' )
5458 const screenshotData = await takeScreenshot ( )
5559 if ( screenshotData && mainWindow && ! mainWindow . isDestroyed ( ) ) {
60+ conversationMessages = [
61+ {
62+ role : 'user' ,
63+ content : [
64+ {
65+ type : 'text' ,
66+ text : `这是屏幕截图`
67+ } ,
68+ {
69+ type : 'image' ,
70+ image : screenshotData
71+ }
72+ ]
73+ }
74+ ]
75+
5676 const streamContext : StreamContext = {
5777 controller : new AbortController ( ) ,
5878 reason : null
@@ -61,6 +81,7 @@ const callbacks: Record<string, () => void> = {
6181 mainWindow . webContents . send ( 'screenshot-taken' , screenshotData )
6282 let endedNaturally = true
6383 let streamStarted = false
84+ let assistantResponse = ''
6485 try {
6586 const solutionStream = getSolutionStream ( screenshotData , streamContext . controller . signal )
6687 streamStarted = true
@@ -70,6 +91,7 @@ const callbacks: Record<string, () => void> = {
7091 endedNaturally = false
7192 break
7293 }
94+ assistantResponse += chunk
7395 mainWindow . webContents . send ( 'solution-chunk' , chunk )
7496 }
7597 } catch ( error ) {
@@ -88,6 +110,13 @@ const callbacks: Record<string, () => void> = {
88110 mainWindow . webContents . send ( 'solution-stopped' )
89111 }
90112 } else if ( endedNaturally ) {
113+ // Add assistant response to conversation history
114+ if ( assistantResponse ) {
115+ conversationMessages . push ( {
116+ role : 'assistant' ,
117+ content : assistantResponse
118+ } )
119+ }
91120 mainWindow . webContents . send ( 'solution-complete' )
92121 }
93122 } catch ( error ) {
@@ -202,3 +231,102 @@ ipcMain.handle('stopSolutionStream', () => {
202231 abortCurrentStream ( 'user' )
203232 return true
204233} )
234+
235+ ipcMain . handle ( 'sendFollowUpQuestion' , async ( _event , question : string ) => {
236+ const mainWindow = global . mainWindow
237+ if ( ! mainWindow || mainWindow . isDestroyed ( ) || ! state . inCoderPage || ! settings . apiKey ) {
238+ return { success : false , error : 'Invalid state' }
239+ }
240+
241+ // Validate that there's an active conversation
242+ if ( conversationMessages . length === 0 ) {
243+ return { success : false , error : 'No active conversation' }
244+ }
245+
246+ abortCurrentStream ( 'new-request' )
247+ const streamContext : StreamContext = {
248+ controller : new AbortController ( ) ,
249+ reason : null
250+ }
251+ currentStreamContext = streamContext
252+
253+ // Add a separator before the follow-up response
254+ mainWindow . webContents . send ( 'solution-chunk' , '\n\n---\n\n' )
255+
256+ let endedNaturally = true
257+ let streamStarted = false
258+ let assistantResponse = ''
259+
260+ try {
261+ const followUpStream = getFollowUpStream (
262+ conversationMessages ,
263+ question ,
264+ streamContext . controller . signal
265+ )
266+ streamStarted = true
267+
268+ try {
269+ for await ( const chunk of followUpStream ) {
270+ if ( streamContext . controller . signal . aborted ) {
271+ endedNaturally = false
272+ break
273+ }
274+ assistantResponse += chunk
275+ mainWindow . webContents . send ( 'solution-chunk' , chunk )
276+ }
277+ } catch ( error ) {
278+ if ( ! streamContext . controller . signal . aborted ) {
279+ endedNaturally = false
280+ console . error ( 'Error streaming follow-up solution:' , error )
281+ const errorMessage = error instanceof Error ? error . message : 'Unknown error'
282+ mainWindow . webContents . send ( 'solution-error' , errorMessage )
283+ } else {
284+ endedNaturally = false
285+ }
286+ }
287+
288+ if ( streamContext . controller . signal . aborted ) {
289+ if ( streamContext . reason === 'user' ) {
290+ mainWindow . webContents . send ( 'solution-stopped' )
291+ }
292+ } else if ( endedNaturally ) {
293+ // Update conversation history with user question and assistant response
294+ conversationMessages . push ( {
295+ role : 'user' ,
296+ content : [
297+ {
298+ type : 'text' ,
299+ text : question
300+ }
301+ ]
302+ } )
303+ if ( assistantResponse ) {
304+ conversationMessages . push ( {
305+ role : 'assistant' ,
306+ content : assistantResponse
307+ } )
308+ }
309+ mainWindow . webContents . send ( 'solution-complete' )
310+ }
311+ } catch ( error ) {
312+ if ( streamContext . controller . signal . aborted ) {
313+ if ( streamContext . reason === 'user' ) {
314+ mainWindow . webContents . send ( 'solution-stopped' )
315+ }
316+ } else {
317+ endedNaturally = false
318+ const errorMessage = error instanceof Error ? error . message : 'Unknown error'
319+ console . error ( 'Error streaming follow-up solution:' , error )
320+ mainWindow . webContents . send ( 'solution-error' , errorMessage )
321+ }
322+ } finally {
323+ if ( currentStreamContext === streamContext ) {
324+ currentStreamContext = null
325+ }
326+ if ( ! streamStarted && streamContext . reason === 'user' ) {
327+ mainWindow . webContents . send ( 'solution-stopped' )
328+ }
329+ }
330+
331+ return { success : true }
332+ } )
0 commit comments