mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	saving chats finally works again, even if the UI is kinda...broken
wow
This commit is contained in:
		| @@ -42,7 +42,7 @@ export async function checkSessionExists(sessionId: string): Promise<boolean> { | |||||||
| export async function setupStreamingResponse( | export async function setupStreamingResponse( | ||||||
|     sessionId: string, |     sessionId: string, | ||||||
|     messageParams: any, |     messageParams: any, | ||||||
|     onContentUpdate: (content: string) => void, |     onContentUpdate: (content: string, isDone?: boolean) => void, | ||||||
|     onThinkingUpdate: (thinking: string) => void, |     onThinkingUpdate: (thinking: string) => void, | ||||||
|     onToolExecution: (toolData: any) => void, |     onToolExecution: (toolData: any) => void, | ||||||
|     onComplete: () => void, |     onComplete: () => void, | ||||||
| @@ -131,7 +131,7 @@ export async function setupStreamingResponse( | |||||||
|                 assistantResponse += message.content; |                 assistantResponse += message.content; | ||||||
|  |  | ||||||
|                 // Update the UI immediately with each chunk |                 // Update the UI immediately with each chunk | ||||||
|                 onContentUpdate(assistantResponse); |                 onContentUpdate(assistantResponse, false); | ||||||
|  |  | ||||||
|                 // Reset timeout since we got content |                 // Reset timeout since we got content | ||||||
|                 if (timeoutId !== null) { |                 if (timeoutId !== null) { | ||||||
| @@ -197,7 +197,7 @@ export async function setupStreamingResponse( | |||||||
|                         console.log(`[${responseId}] Content in done message is identical to existing response, not appending`); |                         console.log(`[${responseId}] Content in done message is identical to existing response, not appending`); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     onContentUpdate(assistantResponse); |                     onContentUpdate(assistantResponse, true); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Clean up and resolve |                 // Clean up and resolve | ||||||
|   | |||||||
| @@ -42,6 +42,31 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|     private onSaveData: ((data: any) => Promise<void>) | null = null; |     private onSaveData: ((data: any) => Promise<void>) | null = null; | ||||||
|     private onGetData: (() => Promise<any>) | null = null; |     private onGetData: (() => Promise<any>) | null = null; | ||||||
|     private messages: MessageData[] = []; |     private messages: MessageData[] = []; | ||||||
|  |     private sources: Array<{noteId: string; title: string; similarity?: number; content?: string}> = []; | ||||||
|  |     private metadata: { | ||||||
|  |         model?: string; | ||||||
|  |         provider?: string; | ||||||
|  |         temperature?: number; | ||||||
|  |         maxTokens?: number; | ||||||
|  |         toolExecutions?: Array<{ | ||||||
|  |             id: string; | ||||||
|  |             name: string; | ||||||
|  |             arguments: any; | ||||||
|  |             result: any; | ||||||
|  |             error?: string; | ||||||
|  |             timestamp: string; | ||||||
|  |         }>; | ||||||
|  |         lastUpdated?: string; | ||||||
|  |         usage?: { | ||||||
|  |             promptTokens?: number; | ||||||
|  |             completionTokens?: number; | ||||||
|  |             totalTokens?: number; | ||||||
|  |         }; | ||||||
|  |     } = { | ||||||
|  |         model: 'default', | ||||||
|  |         temperature: 0.7, | ||||||
|  |         toolExecutions: [] | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     // Public getters and setters for private properties |     // Public getters and setters for private properties | ||||||
|     public getCurrentNoteId(): string | null { |     public getCurrentNoteId(): string | null { | ||||||
| @@ -136,13 +161,92 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             // Extract current tool execution steps if any exist |             // Extract current tool execution steps if any exist | ||||||
|             const toolSteps = extractInChatToolSteps(this.noteContextChatMessages); |             const toolSteps = extractInChatToolSteps(this.noteContextChatMessages); | ||||||
|  |  | ||||||
|  |             // Get tool executions from both UI and any cached executions in metadata | ||||||
|  |             let toolExecutions: Array<{ | ||||||
|  |                 id: string; | ||||||
|  |                 name: string; | ||||||
|  |                 arguments: any; | ||||||
|  |                 result: any; | ||||||
|  |                 error?: string; | ||||||
|  |                 timestamp: string; | ||||||
|  |             }> = []; | ||||||
|  |              | ||||||
|  |             // First include any tool executions already in metadata (from streaming events) | ||||||
|  |             if (this.metadata?.toolExecutions && Array.isArray(this.metadata.toolExecutions)) { | ||||||
|  |                 toolExecutions = [...this.metadata.toolExecutions]; | ||||||
|  |                 console.log(`Including ${toolExecutions.length} tool executions from metadata`); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // Also extract any visible tool steps from the UI | ||||||
|  |             const extractedExecutions = toolSteps.map(step => { | ||||||
|  |                 // Parse tool execution information | ||||||
|  |                 if (step.type === 'tool-execution') { | ||||||
|  |                     try { | ||||||
|  |                         const content = JSON.parse(step.content); | ||||||
|  |                         return { | ||||||
|  |                             id: content.toolCallId || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, | ||||||
|  |                             name: content.tool || 'unknown', | ||||||
|  |                             arguments: content.args || {}, | ||||||
|  |                             result: content.result || {}, | ||||||
|  |                             error: content.error, | ||||||
|  |                             timestamp: new Date().toISOString() | ||||||
|  |                         }; | ||||||
|  |                     } catch (e) { | ||||||
|  |                         // If we can't parse it, create a basic record | ||||||
|  |                         return { | ||||||
|  |                             id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, | ||||||
|  |                             name: 'unknown', | ||||||
|  |                             arguments: {}, | ||||||
|  |                             result: step.content, | ||||||
|  |                             timestamp: new Date().toISOString() | ||||||
|  |                         }; | ||||||
|  |                     } | ||||||
|  |                 } else if (step.type === 'result' && step.name) { | ||||||
|  |                     // Handle result steps with a name | ||||||
|  |                     return { | ||||||
|  |                         id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, | ||||||
|  |                         name: step.name, | ||||||
|  |                         arguments: {}, | ||||||
|  |                         result: step.content, | ||||||
|  |                         timestamp: new Date().toISOString() | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|  |                 return { | ||||||
|  |                     id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, | ||||||
|  |                     name: 'unknown', | ||||||
|  |                     arguments: {}, | ||||||
|  |                     result: 'Unrecognized tool step', | ||||||
|  |                     timestamp: new Date().toISOString() | ||||||
|  |                 }; | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             // Merge the tool executions, keeping only unique IDs | ||||||
|  |             const existingIds = new Set(toolExecutions.map((t: {id: string}) => t.id)); | ||||||
|  |             for (const exec of extractedExecutions) { | ||||||
|  |                 if (!existingIds.has(exec.id)) { | ||||||
|  |                     toolExecutions.push(exec); | ||||||
|  |                     existingIds.add(exec.id); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             const dataToSave: ChatData = { |             const dataToSave: ChatData = { | ||||||
|                 messages: this.messages, |                 messages: this.messages, | ||||||
|                 sessionId: this.sessionId, |                 sessionId: this.sessionId, | ||||||
|                 toolSteps: toolSteps |                 toolSteps: toolSteps, | ||||||
|  |                 // Add sources if we have them | ||||||
|  |                 sources: this.sources || [], | ||||||
|  |                 // Add metadata | ||||||
|  |                 metadata: { | ||||||
|  |                     model: this.metadata?.model || 'default', | ||||||
|  |                     provider: this.metadata?.provider || undefined, | ||||||
|  |                     temperature: this.metadata?.temperature || 0.7, | ||||||
|  |                     lastUpdated: new Date().toISOString(), | ||||||
|  |                     // Add tool executions | ||||||
|  |                     toolExecutions: toolExecutions | ||||||
|  |                 } | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             console.log(`Saving chat data with sessionId: ${this.sessionId} and ${toolSteps.length} tool steps`); |             console.log(`Saving chat data with sessionId: ${this.sessionId}, ${toolSteps.length} tool steps, ${this.sources?.length || 0} sources, ${toolExecutions.length} tool executions`); | ||||||
|  |  | ||||||
|             await this.onSaveData(dataToSave); |             await this.onSaveData(dataToSave); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
| @@ -179,6 +283,39 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                     this.restoreInChatToolSteps(savedData.toolSteps); |                     this.restoreInChatToolSteps(savedData.toolSteps); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 // Load sources if available | ||||||
|  |                 if (savedData.sources && Array.isArray(savedData.sources)) { | ||||||
|  |                     this.sources = savedData.sources; | ||||||
|  |                     console.log(`Loaded ${this.sources.length} sources from saved data`); | ||||||
|  |                      | ||||||
|  |                     // Show sources in the UI if they exist | ||||||
|  |                     if (this.sources.length > 0) { | ||||||
|  |                         this.showSources(this.sources); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Load metadata if available | ||||||
|  |                 if (savedData.metadata) { | ||||||
|  |                     this.metadata = { | ||||||
|  |                         ...this.metadata, | ||||||
|  |                         ...savedData.metadata | ||||||
|  |                     }; | ||||||
|  |                      | ||||||
|  |                     // Ensure tool executions are loaded | ||||||
|  |                     if (savedData.metadata.toolExecutions && Array.isArray(savedData.metadata.toolExecutions)) { | ||||||
|  |                         console.log(`Loaded ${savedData.metadata.toolExecutions.length} tool executions from saved data`); | ||||||
|  |                          | ||||||
|  |                         if (!this.metadata.toolExecutions) { | ||||||
|  |                             this.metadata.toolExecutions = []; | ||||||
|  |                         } | ||||||
|  |                          | ||||||
|  |                         // Make sure we don't lose any tool executions | ||||||
|  |                         this.metadata.toolExecutions = savedData.metadata.toolExecutions; | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                     console.log(`Loaded metadata from saved data:`, this.metadata); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 // Load session ID if available |                 // Load session ID if available | ||||||
|                 if (savedData.sessionId) { |                 if (savedData.sessionId) { | ||||||
|                     try { |                     try { | ||||||
| @@ -188,6 +325,53 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                         if (sessionExists) { |                         if (sessionExists) { | ||||||
|                             console.log(`Restored session ${savedData.sessionId}`); |                             console.log(`Restored session ${savedData.sessionId}`); | ||||||
|                             this.sessionId = savedData.sessionId; |                             this.sessionId = savedData.sessionId; | ||||||
|  |                              | ||||||
|  |                             // If we successfully restored a session, also fetch the latest session data | ||||||
|  |                             try { | ||||||
|  |                                 const sessionData = await server.get<{ | ||||||
|  |                                     metadata?: { | ||||||
|  |                                         model?: string; | ||||||
|  |                                         provider?: string; | ||||||
|  |                                         temperature?: number; | ||||||
|  |                                         maxTokens?: number; | ||||||
|  |                                         toolExecutions?: Array<{ | ||||||
|  |                                             id: string; | ||||||
|  |                                             name: string; | ||||||
|  |                                             arguments: any; | ||||||
|  |                                             result: any; | ||||||
|  |                                             error?: string; | ||||||
|  |                                             timestamp: string; | ||||||
|  |                                         }>; | ||||||
|  |                                         lastUpdated?: string; | ||||||
|  |                                         usage?: { | ||||||
|  |                                             promptTokens?: number; | ||||||
|  |                                             completionTokens?: number; | ||||||
|  |                                             totalTokens?: number; | ||||||
|  |                                         }; | ||||||
|  |                                     }; | ||||||
|  |                                     sources?: Array<{ | ||||||
|  |                                         noteId: string; | ||||||
|  |                                         title: string; | ||||||
|  |                                         similarity?: number; | ||||||
|  |                                         content?: string; | ||||||
|  |                                     }>; | ||||||
|  |                                 }>(`llm/sessions/${savedData.sessionId}`); | ||||||
|  |                                 if (sessionData && sessionData.metadata) { | ||||||
|  |                                     // Update our metadata with the latest from the server | ||||||
|  |                                     this.metadata = { | ||||||
|  |                                         ...this.metadata, | ||||||
|  |                                         ...sessionData.metadata | ||||||
|  |                                     }; | ||||||
|  |                                     console.log(`Updated metadata from server for session ${savedData.sessionId}`); | ||||||
|  |                                      | ||||||
|  |                                     // If server has sources, update those too | ||||||
|  |                                     if (sessionData.sources && sessionData.sources.length > 0) { | ||||||
|  |                                         this.sources = sessionData.sources; | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } catch (fetchError) { | ||||||
|  |                                 console.warn(`Could not fetch latest session data: ${fetchError}`); | ||||||
|  |                             } | ||||||
|                         } else { |                         } else { | ||||||
|                             console.log(`Saved session ${savedData.sessionId} not found, will create new one`); |                             console.log(`Saved session ${savedData.sessionId} not found, will create new one`); | ||||||
|                             this.sessionId = null; |                             this.sessionId = null; | ||||||
| @@ -466,13 +650,25 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|  |  | ||||||
|             // If the POST request returned content directly, display it |             // If the POST request returned content directly, display it | ||||||
|             if (postResponse && postResponse.content) { |             if (postResponse && postResponse.content) { | ||||||
|                 this.processAssistantResponse(postResponse.content); |                 // Store metadata from the response | ||||||
|  |                 if (postResponse.metadata) { | ||||||
|  |                     console.log("Received metadata from response:", postResponse.metadata); | ||||||
|  |                     this.metadata = { | ||||||
|  |                         ...this.metadata, | ||||||
|  |                         ...postResponse.metadata | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|                  |                  | ||||||
|                 // If there are sources, show them |                 // Store sources from the response | ||||||
|                 if (postResponse.sources && postResponse.sources.length > 0) { |                 if (postResponse.sources && postResponse.sources.length > 0) { | ||||||
|  |                     console.log(`Received ${postResponse.sources.length} sources from response`); | ||||||
|  |                     this.sources = postResponse.sources; | ||||||
|                     this.showSources(postResponse.sources); |                     this.showSources(postResponse.sources); | ||||||
|                 } |                 } | ||||||
|                  |                  | ||||||
|  |                 // Process the assistant response | ||||||
|  |                 this.processAssistantResponse(postResponse.content, postResponse); | ||||||
|  |  | ||||||
|                 hideLoadingIndicator(this.loadingIndicator); |                 hideLoadingIndicator(this.loadingIndicator); | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
| @@ -487,7 +683,7 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|     /** |     /** | ||||||
|      * Process an assistant response - add to UI and save |      * Process an assistant response - add to UI and save | ||||||
|      */ |      */ | ||||||
|     private async processAssistantResponse(content: string) { |     private async processAssistantResponse(content: string, fullResponse?: any) { | ||||||
|         // Add the response to the chat UI |         // Add the response to the chat UI | ||||||
|         this.addMessageToChat('assistant', content); |         this.addMessageToChat('assistant', content); | ||||||
|  |  | ||||||
| @@ -498,6 +694,21 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             timestamp: new Date() |             timestamp: new Date() | ||||||
|         }); |         }); | ||||||
|          |          | ||||||
|  |         // If we received tool execution information, add it to metadata | ||||||
|  |         if (fullResponse?.metadata?.toolExecutions) { | ||||||
|  |             console.log(`Storing ${fullResponse.metadata.toolExecutions.length} tool executions from response`); | ||||||
|  |             // Make sure our metadata has toolExecutions | ||||||
|  |             if (!this.metadata.toolExecutions) { | ||||||
|  |                 this.metadata.toolExecutions = []; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // Add new tool executions | ||||||
|  |             this.metadata.toolExecutions = [ | ||||||
|  |                 ...this.metadata.toolExecutions,  | ||||||
|  |                 ...fullResponse.metadata.toolExecutions | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // Save to note |         // Save to note | ||||||
|         this.saveCurrentData().catch(err => { |         this.saveCurrentData().catch(err => { | ||||||
|             console.error("Failed to save assistant response to note:", err); |             console.error("Failed to save assistant response to note:", err); | ||||||
| @@ -512,12 +723,94 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             throw new Error("No session ID available"); |             throw new Error("No session ID available"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Store tool executions captured during streaming | ||||||
|  |         const toolExecutionsCache: Array<{ | ||||||
|  |             id: string; | ||||||
|  |             name: string; | ||||||
|  |             arguments: any; | ||||||
|  |             result: any; | ||||||
|  |             error?: string; | ||||||
|  |             timestamp: string; | ||||||
|  |         }> = []; | ||||||
|  |  | ||||||
|         return setupStreamingResponse( |         return setupStreamingResponse( | ||||||
|             this.sessionId, |             this.sessionId, | ||||||
|             messageParams, |             messageParams, | ||||||
|             // Content update handler |             // Content update handler | ||||||
|             (content: string) => { |             (content: string, isDone: boolean = false) => { | ||||||
|                 this.updateStreamingUI(content); |                 this.updateStreamingUI(content, isDone); | ||||||
|  |                  | ||||||
|  |                 // Update session data with additional metadata when streaming is complete | ||||||
|  |                 if (isDone) { | ||||||
|  |                     // Update our metadata with info from the server | ||||||
|  |                     server.get<{ | ||||||
|  |                         metadata?: { | ||||||
|  |                             model?: string; | ||||||
|  |                             provider?: string; | ||||||
|  |                             temperature?: number; | ||||||
|  |                             maxTokens?: number; | ||||||
|  |                             toolExecutions?: Array<{ | ||||||
|  |                                 id: string; | ||||||
|  |                                 name: string; | ||||||
|  |                                 arguments: any; | ||||||
|  |                                 result: any; | ||||||
|  |                                 error?: string; | ||||||
|  |                                 timestamp: string; | ||||||
|  |                             }>; | ||||||
|  |                             lastUpdated?: string; | ||||||
|  |                             usage?: { | ||||||
|  |                                 promptTokens?: number; | ||||||
|  |                                 completionTokens?: number; | ||||||
|  |                                 totalTokens?: number; | ||||||
|  |                             }; | ||||||
|  |                         }; | ||||||
|  |                         sources?: Array<{ | ||||||
|  |                             noteId: string; | ||||||
|  |                             title: string; | ||||||
|  |                             similarity?: number; | ||||||
|  |                             content?: string; | ||||||
|  |                         }>; | ||||||
|  |                     }>(`llm/sessions/${this.sessionId}`) | ||||||
|  |                         .then((sessionData) => { | ||||||
|  |                             console.log("Got updated session data:", sessionData); | ||||||
|  |                              | ||||||
|  |                             // Store metadata | ||||||
|  |                             if (sessionData.metadata) { | ||||||
|  |                                 this.metadata = { | ||||||
|  |                                     ...this.metadata, | ||||||
|  |                                     ...sessionData.metadata | ||||||
|  |                                 }; | ||||||
|  |                             } | ||||||
|  |                              | ||||||
|  |                             // Store sources | ||||||
|  |                             if (sessionData.sources && sessionData.sources.length > 0) { | ||||||
|  |                                 this.sources = sessionData.sources; | ||||||
|  |                                 this.showSources(sessionData.sources); | ||||||
|  |                             } | ||||||
|  |                              | ||||||
|  |                             // Make sure we include the cached tool executions | ||||||
|  |                             if (toolExecutionsCache.length > 0) { | ||||||
|  |                                 console.log(`Including ${toolExecutionsCache.length} cached tool executions in metadata`); | ||||||
|  |                                 if (!this.metadata.toolExecutions) { | ||||||
|  |                                     this.metadata.toolExecutions = []; | ||||||
|  |                                 } | ||||||
|  |                                  | ||||||
|  |                                 // Add any tool executions from our cache that aren't already in metadata | ||||||
|  |                                 const existingIds = new Set((this.metadata.toolExecutions || []).map((t: {id: string}) => t.id)); | ||||||
|  |                                 for (const toolExec of toolExecutionsCache) { | ||||||
|  |                                     if (!existingIds.has(toolExec.id)) { | ||||||
|  |                                         this.metadata.toolExecutions.push(toolExec); | ||||||
|  |                                         existingIds.add(toolExec.id); | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                              | ||||||
|  |                             // Save the updated data to the note | ||||||
|  |                             this.saveCurrentData() | ||||||
|  |                                 .catch(err => console.error("Failed to save data after streaming completed:", err)); | ||||||
|  |                         }) | ||||||
|  |                         .catch(err => console.error("Error fetching session data after streaming:", err)); | ||||||
|  |                 } | ||||||
|             }, |             }, | ||||||
|             // Thinking update handler |             // Thinking update handler | ||||||
|             (thinking: string) => { |             (thinking: string) => { | ||||||
| @@ -526,6 +819,38 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             // Tool execution handler |             // Tool execution handler | ||||||
|             (toolData: any) => { |             (toolData: any) => { | ||||||
|                 this.showToolExecutionInfo(toolData); |                 this.showToolExecutionInfo(toolData); | ||||||
|  |                  | ||||||
|  |                 // Cache tools we see during streaming to include them in the final saved data | ||||||
|  |                 if (toolData && toolData.action === 'result' && toolData.tool) { | ||||||
|  |                     // Create a tool execution record | ||||||
|  |                     const toolExec = { | ||||||
|  |                         id: toolData.toolCallId || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, | ||||||
|  |                         name: toolData.tool, | ||||||
|  |                         arguments: toolData.args || {}, | ||||||
|  |                         result: toolData.result || {}, | ||||||
|  |                         error: toolData.error, | ||||||
|  |                         timestamp: new Date().toISOString() | ||||||
|  |                     }; | ||||||
|  |                      | ||||||
|  |                     // Add to both our local cache for immediate saving and to metadata for later saving | ||||||
|  |                     toolExecutionsCache.push(toolExec); | ||||||
|  |                      | ||||||
|  |                     // Initialize toolExecutions array if it doesn't exist | ||||||
|  |                     if (!this.metadata.toolExecutions) { | ||||||
|  |                         this.metadata.toolExecutions = []; | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                     // Add tool execution to our metadata | ||||||
|  |                     this.metadata.toolExecutions.push(toolExec); | ||||||
|  |                      | ||||||
|  |                     console.log(`Cached tool execution for ${toolData.tool} to be saved later`); | ||||||
|  |                      | ||||||
|  |                     // Save immediately after receiving a tool execution | ||||||
|  |                     // This ensures we don't lose tool execution data if streaming fails | ||||||
|  |                     this.saveCurrentData().catch(err => { | ||||||
|  |                         console.error("Failed to save tool execution data:", err); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|             }, |             }, | ||||||
|             // Complete handler |             // Complete handler | ||||||
|             () => { |             () => { | ||||||
| @@ -541,9 +866,9 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|     /** |     /** | ||||||
|      * Update the UI with streaming content |      * Update the UI with streaming content | ||||||
|      */ |      */ | ||||||
|     private updateStreamingUI(assistantResponse: string) { |     private updateStreamingUI(assistantResponse: string, isDone: boolean = false) { | ||||||
|         const logId = `LlmChatPanel-${Date.now()}`; |         const logId = `LlmChatPanel-${Date.now()}`; | ||||||
|         console.log(`[${logId}] Updating UI with response text: ${assistantResponse.length} chars`); |         console.log(`[${logId}] Updating UI with response text: ${assistantResponse.length} chars, isDone=${isDone}`); | ||||||
|  |  | ||||||
|         if (!this.noteContextChatMessages) { |         if (!this.noteContextChatMessages) { | ||||||
|             console.error(`[${logId}] noteContextChatMessages element not available`); |             console.error(`[${logId}] noteContextChatMessages element not available`); | ||||||
| @@ -587,6 +912,31 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                 this.addMessageToChat('assistant', assistantResponse); |                 this.addMessageToChat('assistant', assistantResponse); | ||||||
|                 console.log(`[${logId}] Successfully added new assistant message`); |                 console.log(`[${logId}] Successfully added new assistant message`); | ||||||
|             } |             } | ||||||
|  |              | ||||||
|  |             // Update messages array only if this is the first update or the final update | ||||||
|  |             if (!this.messages.some(m => m.role === 'assistant') || isDone) { | ||||||
|  |                 // Add or update the assistant message in our local array | ||||||
|  |                 const existingIndex = this.messages.findIndex(m => m.role === 'assistant'); | ||||||
|  |                 if (existingIndex >= 0) { | ||||||
|  |                     // Update existing message | ||||||
|  |                     this.messages[existingIndex].content = assistantResponse; | ||||||
|  |                 } else { | ||||||
|  |                     // Add new message | ||||||
|  |                     this.messages.push({ | ||||||
|  |                         role: 'assistant', | ||||||
|  |                         content: assistantResponse, | ||||||
|  |                         timestamp: new Date() | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 // If this is the final update, save the data | ||||||
|  |                 if (isDone) { | ||||||
|  |                     console.log(`[${logId}] Streaming finished, saving data to note`); | ||||||
|  |                     this.saveCurrentData().catch(err => { | ||||||
|  |                         console.error(`[${logId}] Failed to save streaming response to note:`, err); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Always try to scroll to the latest content |         // Always try to scroll to the latest content | ||||||
|   | |||||||
| @@ -29,4 +29,25 @@ export interface ChatData { | |||||||
|     messages: MessageData[]; |     messages: MessageData[]; | ||||||
|     sessionId: string | null; |     sessionId: string | null; | ||||||
|     toolSteps: ToolExecutionStep[]; |     toolSteps: ToolExecutionStep[]; | ||||||
|  |     sources?: Array<{ | ||||||
|  |         noteId: string; | ||||||
|  |         title: string; | ||||||
|  |         similarity?: number; | ||||||
|  |         content?: string; | ||||||
|  |     }>; | ||||||
|  |     metadata?: { | ||||||
|  |         model?: string; | ||||||
|  |         provider?: string; | ||||||
|  |         temperature?: number; | ||||||
|  |         maxTokens?: number; | ||||||
|  |         lastUpdated?: string; | ||||||
|  |         toolExecutions?: Array<{ | ||||||
|  |             id: string; | ||||||
|  |             name: string; | ||||||
|  |             arguments: any; | ||||||
|  |             result: any; | ||||||
|  |             error?: string; | ||||||
|  |             timestamp: string; | ||||||
|  |         }>; | ||||||
|  |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -99,6 +99,7 @@ export interface ChatCompletionOptions { | |||||||
|     useAdvancedContext?: boolean; // Whether to use advanced context enrichment |     useAdvancedContext?: boolean; // Whether to use advanced context enrichment | ||||||
|     toolExecutionStatus?: any[]; // Status information about executed tools for feedback |     toolExecutionStatus?: any[]; // Status information about executed tools for feedback | ||||||
|     providerMetadata?: ModelMetadata; // Metadata about the provider and model capabilities |     providerMetadata?: ModelMetadata; // Metadata about the provider and model capabilities | ||||||
|  |     sessionId?: string; // Session ID for storing tool execution results | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Maximum number of tool execution iterations |      * Maximum number of tool execution iterations | ||||||
|   | |||||||
| @@ -314,7 +314,7 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|     async initializeAgentTools(): Promise<void> { |     async initializeAgentTools(): Promise<void> { | ||||||
|         // Agent tools are already initialized in the constructor |         // Agent tools are already initialized in the constructor | ||||||
|         // This method is kept for backward compatibility |         // This method is kept for backward compatibility | ||||||
|         log.debug("initializeAgentTools called, but tools are already initialized in constructor"); |         log.info("initializeAgentTools called, but tools are already initialized in constructor"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -139,10 +139,16 @@ export class ChatService { | |||||||
|             // Select pipeline to use |             // Select pipeline to use | ||||||
|             const pipeline = this.getPipeline(); |             const pipeline = this.getPipeline(); | ||||||
|  |  | ||||||
|  |             // Include sessionId in the options for tool execution tracking | ||||||
|  |             const pipelineOptions = { | ||||||
|  |                 ...(options || session.options || {}), | ||||||
|  |                 sessionId: session.id | ||||||
|  |             }; | ||||||
|  |              | ||||||
|             // Execute the pipeline |             // Execute the pipeline | ||||||
|             const response = await pipeline.execute({ |             const response = await pipeline.execute({ | ||||||
|                 messages: session.messages, |                 messages: session.messages, | ||||||
|                 options: options || session.options || {}, |                 options: pipelineOptions, | ||||||
|                 query: content, |                 query: content, | ||||||
|                 streamCallback |                 streamCallback | ||||||
|             }); |             }); | ||||||
| @@ -156,8 +162,21 @@ export class ChatService { | |||||||
|             session.messages.push(assistantMessage); |             session.messages.push(assistantMessage); | ||||||
|             session.isStreaming = false; |             session.isStreaming = false; | ||||||
|  |  | ||||||
|             // Save the complete conversation |             // Save metadata about the response | ||||||
|             await chatStorageService.updateChat(session.id, session.messages); |             const metadata = { | ||||||
|  |                 model: response.model, | ||||||
|  |                 provider: response.provider, | ||||||
|  |                 usage: response.usage | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             // If there are tool calls, make sure they're stored in metadata | ||||||
|  |             if (response.tool_calls && response.tool_calls.length > 0) { | ||||||
|  |                 // Let the storage service extract and save tool executions | ||||||
|  |                 // The tool results are already in the messages | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // Save the complete conversation with metadata | ||||||
|  |             await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); | ||||||
|  |  | ||||||
|             // If first message, update the title based on content |             // If first message, update the title based on content | ||||||
|             if (session.messages.length <= 2 && (!session.title || session.title === 'New Chat')) { |             if (session.messages.length <= 2 && (!session.title || session.title === 'New Chat')) { | ||||||
| @@ -228,10 +247,16 @@ export class ChatService { | |||||||
|             const pipelineType = showThinking ? 'agent' : 'default'; |             const pipelineType = showThinking ? 'agent' : 'default'; | ||||||
|             const pipeline = this.getPipeline(pipelineType); |             const pipeline = this.getPipeline(pipelineType); | ||||||
|  |  | ||||||
|  |             // Include sessionId in the options for tool execution tracking | ||||||
|  |             const pipelineOptions = { | ||||||
|  |                 ...(options || session.options || {}), | ||||||
|  |                 sessionId: session.id | ||||||
|  |             }; | ||||||
|  |  | ||||||
|             // Execute the pipeline with note context |             // Execute the pipeline with note context | ||||||
|             const response = await pipeline.execute({ |             const response = await pipeline.execute({ | ||||||
|                 messages: session.messages, |                 messages: session.messages, | ||||||
|                 options: options || session.options || {}, |                 options: pipelineOptions, | ||||||
|                 noteId, |                 noteId, | ||||||
|                 query: content, |                 query: content, | ||||||
|                 showThinking, |                 showThinking, | ||||||
| @@ -247,8 +272,22 @@ export class ChatService { | |||||||
|             session.messages.push(assistantMessage); |             session.messages.push(assistantMessage); | ||||||
|             session.isStreaming = false; |             session.isStreaming = false; | ||||||
|  |  | ||||||
|             // Save the complete conversation |             // Save metadata about the response | ||||||
|             await chatStorageService.updateChat(session.id, session.messages); |             const metadata = { | ||||||
|  |                 model: response.model, | ||||||
|  |                 provider: response.provider, | ||||||
|  |                 usage: response.usage, | ||||||
|  |                 contextNoteId: noteId // Store the note ID used for context | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             // If there are tool calls, make sure they're stored in metadata | ||||||
|  |             if (response.tool_calls && response.tool_calls.length > 0) { | ||||||
|  |                 // Let the storage service extract and save tool executions | ||||||
|  |                 // The tool results are already in the messages | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // Save the complete conversation with metadata | ||||||
|  |             await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); | ||||||
|  |  | ||||||
|             // If first message, update the title |             // If first message, update the title | ||||||
|             if (session.messages.length <= 2 && (!session.title || session.title === 'New Chat')) { |             if (session.messages.length <= 2 && (!session.title || session.title === 'New Chat')) { | ||||||
| @@ -312,7 +351,29 @@ export class ChatService { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         session.messages.push(contextMessage); |         session.messages.push(contextMessage); | ||||||
|         await chatStorageService.updateChat(session.id, session.messages); |          | ||||||
|  |         // Store the context note id in metadata | ||||||
|  |         const metadata = { | ||||||
|  |             contextNoteId: noteId | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         // Check if the context extraction result has sources | ||||||
|  |         // Note: We're adding a defensive check since TypeScript doesn't know about this property | ||||||
|  |         const contextSources = (contextResult as any).sources || []; | ||||||
|  |         if (contextSources && contextSources.length > 0) { | ||||||
|  |             // Convert the sources to the format expected by recordSources | ||||||
|  |             const sources = contextSources.map((source: any) => ({ | ||||||
|  |                 noteId: source.noteId, | ||||||
|  |                 title: source.title, | ||||||
|  |                 similarity: source.similarity, | ||||||
|  |                 content: source.content | ||||||
|  |             })); | ||||||
|  |              | ||||||
|  |             // Store these sources in metadata | ||||||
|  |             await chatStorageService.recordSources(session.id, sources); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); | ||||||
|  |  | ||||||
|         return session; |         return session; | ||||||
|     } |     } | ||||||
| @@ -343,7 +404,29 @@ export class ChatService { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         session.messages.push(contextMessage); |         session.messages.push(contextMessage); | ||||||
|         await chatStorageService.updateChat(session.id, session.messages); |          | ||||||
|  |         // Store the context note id and query in metadata | ||||||
|  |         const metadata = { | ||||||
|  |             contextNoteId: noteId | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         // Check if the semantic context extraction result has sources | ||||||
|  |         // Note: We're adding a defensive check since TypeScript doesn't know about this property | ||||||
|  |         const contextSources = (contextResult as any).sources || []; | ||||||
|  |         if (contextSources && contextSources.length > 0) { | ||||||
|  |             // Convert the sources to the format expected by recordSources | ||||||
|  |             const sources = contextSources.map((source: any) => ({ | ||||||
|  |                 noteId: source.noteId, | ||||||
|  |                 title: source.title, | ||||||
|  |                 similarity: source.similarity, | ||||||
|  |                 content: source.content | ||||||
|  |             })); | ||||||
|  |              | ||||||
|  |             // Store these sources in metadata | ||||||
|  |             await chatStorageService.recordSources(session.id, sources); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         await chatStorageService.updateChat(session.id, session.messages, undefined, metadata); | ||||||
|  |  | ||||||
|         return session; |         return session; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -2,7 +2,9 @@ import notes from '../notes.js'; | |||||||
| import sql from '../sql.js'; | import sql from '../sql.js'; | ||||||
| import attributes from '../attributes.js'; | import attributes from '../attributes.js'; | ||||||
| import type { Message } from './ai_interface.js'; | import type { Message } from './ai_interface.js'; | ||||||
|  | import type { ToolCall } from './tools/tool_interfaces.js'; | ||||||
| import { t } from 'i18next'; | import { t } from 'i18next'; | ||||||
|  | import log from '../log.js'; | ||||||
|  |  | ||||||
| interface StoredChat { | interface StoredChat { | ||||||
|     id: string; |     id: string; | ||||||
| @@ -11,6 +13,39 @@ interface StoredChat { | |||||||
|     noteId?: string; |     noteId?: string; | ||||||
|     createdAt: Date; |     createdAt: Date; | ||||||
|     updatedAt: Date; |     updatedAt: Date; | ||||||
|  |     metadata?: ChatMetadata; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | interface ChatMetadata { | ||||||
|  |     sources?: Array<{ | ||||||
|  |         noteId: string; | ||||||
|  |         title: string; | ||||||
|  |         similarity?: number; | ||||||
|  |         path?: string; | ||||||
|  |         branchId?: string; | ||||||
|  |         content?: string; | ||||||
|  |     }>; | ||||||
|  |     model?: string; | ||||||
|  |     provider?: string; | ||||||
|  |     contextNoteId?: string; | ||||||
|  |     toolExecutions?: Array<ToolExecution>; | ||||||
|  |     usage?: { | ||||||
|  |         promptTokens?: number; | ||||||
|  |         completionTokens?: number; | ||||||
|  |         totalTokens?: number; | ||||||
|  |     }; | ||||||
|  |     temperature?: number; | ||||||
|  |     maxTokens?: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | interface ToolExecution { | ||||||
|  |     id: string; | ||||||
|  |     name: string; | ||||||
|  |     arguments: Record<string, any> | string; | ||||||
|  |     result: string | Record<string, any>; | ||||||
|  |     error?: string; | ||||||
|  |     timestamp: Date; | ||||||
|  |     executionTime?: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -56,7 +91,7 @@ export class ChatStorageService { | |||||||
|     /** |     /** | ||||||
|      * Create a new chat |      * Create a new chat | ||||||
|      */ |      */ | ||||||
|     async createChat(title: string, messages: Message[] = []): Promise<StoredChat> { |     async createChat(title: string, messages: Message[] = [], metadata?: ChatMetadata): Promise<StoredChat> { | ||||||
|         const rootNoteId = await this.getOrCreateChatRoot(); |         const rootNoteId = await this.getOrCreateChatRoot(); | ||||||
|         const now = new Date(); |         const now = new Date(); | ||||||
|  |  | ||||||
| @@ -67,6 +102,7 @@ export class ChatStorageService { | |||||||
|             mime: ChatStorageService.CHAT_MIME, |             mime: ChatStorageService.CHAT_MIME, | ||||||
|             content: JSON.stringify({ |             content: JSON.stringify({ | ||||||
|                 messages, |                 messages, | ||||||
|  |                 metadata: metadata || {}, | ||||||
|                 createdAt: now, |                 createdAt: now, | ||||||
|                 updatedAt: now |                 updatedAt: now | ||||||
|             }, null, 2) |             }, null, 2) | ||||||
| @@ -84,7 +120,8 @@ export class ChatStorageService { | |||||||
|             messages, |             messages, | ||||||
|             noteId: note.noteId, |             noteId: note.noteId, | ||||||
|             createdAt: now, |             createdAt: now, | ||||||
|             updatedAt: now |             updatedAt: now, | ||||||
|  |             metadata: metadata || {} | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -104,9 +141,22 @@ export class ChatStorageService { | |||||||
|  |  | ||||||
|         return chats.map(chat => { |         return chats.map(chat => { | ||||||
|             let messages: Message[] = []; |             let messages: Message[] = []; | ||||||
|  |             let metadata: ChatMetadata = {}; | ||||||
|  |             let createdAt = new Date(chat.dateCreated); | ||||||
|  |             let updatedAt = new Date(chat.dateModified); | ||||||
|  |              | ||||||
|             try { |             try { | ||||||
|                 const content = JSON.parse(chat.content); |                 const content = JSON.parse(chat.content); | ||||||
|                 messages = content.messages || []; |                 messages = content.messages || []; | ||||||
|  |                 metadata = content.metadata || {}; | ||||||
|  |                  | ||||||
|  |                 // Use stored dates if available | ||||||
|  |                 if (content.createdAt) { | ||||||
|  |                     createdAt = new Date(content.createdAt); | ||||||
|  |                 } | ||||||
|  |                 if (content.updatedAt) { | ||||||
|  |                     updatedAt = new Date(content.updatedAt); | ||||||
|  |                 } | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|                 console.error('Failed to parse chat content:', e); |                 console.error('Failed to parse chat content:', e); | ||||||
|             } |             } | ||||||
| @@ -116,8 +166,9 @@ export class ChatStorageService { | |||||||
|                 title: chat.title, |                 title: chat.title, | ||||||
|                 messages, |                 messages, | ||||||
|                 noteId: chat.noteId, |                 noteId: chat.noteId, | ||||||
|                 createdAt: new Date(chat.dateCreated), |                 createdAt, | ||||||
|                 updatedAt: new Date(chat.dateModified) |                 updatedAt, | ||||||
|  |                 metadata | ||||||
|             }; |             }; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -139,9 +190,22 @@ export class ChatStorageService { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         let messages: Message[] = []; |         let messages: Message[] = []; | ||||||
|  |         let metadata: ChatMetadata = {}; | ||||||
|  |         let createdAt = new Date(chat.dateCreated); | ||||||
|  |         let updatedAt = new Date(chat.dateModified); | ||||||
|  |          | ||||||
|         try { |         try { | ||||||
|             const content = JSON.parse(chat.content); |             const content = JSON.parse(chat.content); | ||||||
|             messages = content.messages || []; |             messages = content.messages || []; | ||||||
|  |             metadata = content.metadata || {}; | ||||||
|  |              | ||||||
|  |             // Use stored dates if available | ||||||
|  |             if (content.createdAt) { | ||||||
|  |                 createdAt = new Date(content.createdAt); | ||||||
|  |             } | ||||||
|  |             if (content.updatedAt) { | ||||||
|  |                 updatedAt = new Date(content.updatedAt); | ||||||
|  |             } | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             console.error('Failed to parse chat content:', e); |             console.error('Failed to parse chat content:', e); | ||||||
|         } |         } | ||||||
| @@ -151,15 +215,21 @@ export class ChatStorageService { | |||||||
|             title: chat.title, |             title: chat.title, | ||||||
|             messages, |             messages, | ||||||
|             noteId: chat.noteId, |             noteId: chat.noteId, | ||||||
|             createdAt: new Date(chat.dateCreated), |             createdAt, | ||||||
|             updatedAt: new Date(chat.dateModified) |             updatedAt, | ||||||
|  |             metadata | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Update messages in a chat |      * Update messages in a chat | ||||||
|      */ |      */ | ||||||
|     async updateChat(chatId: string, messages: Message[], title?: string): Promise<StoredChat | null> { |     async updateChat( | ||||||
|  |         chatId: string,  | ||||||
|  |         messages: Message[],  | ||||||
|  |         title?: string,  | ||||||
|  |         metadata?: ChatMetadata | ||||||
|  |     ): Promise<StoredChat | null> { | ||||||
|         const chat = await this.getChat(chatId); |         const chat = await this.getChat(chatId); | ||||||
|  |  | ||||||
|         if (!chat) { |         if (!chat) { | ||||||
| @@ -167,12 +237,20 @@ export class ChatStorageService { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         const now = new Date(); |         const now = new Date(); | ||||||
|  |         const updatedMetadata = {...(chat.metadata || {}), ...(metadata || {})}; | ||||||
|  |  | ||||||
|  |         // Extract and store tool calls from the messages | ||||||
|  |         const toolExecutions = this.extractToolExecutionsFromMessages(messages, updatedMetadata.toolExecutions || []); | ||||||
|  |         if (toolExecutions.length > 0) { | ||||||
|  |             updatedMetadata.toolExecutions = toolExecutions; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // Update content directly using SQL since we don't have a method for this in the notes service |         // Update content directly using SQL since we don't have a method for this in the notes service | ||||||
|         await sql.execute( |         await sql.execute( | ||||||
|             `UPDATE note_contents SET content = ? WHERE noteId = ?`, |             `UPDATE note_contents SET content = ? WHERE noteId = ?`, | ||||||
|             [JSON.stringify({ |             [JSON.stringify({ | ||||||
|                 messages, |                 messages, | ||||||
|  |                 metadata: updatedMetadata, | ||||||
|                 createdAt: chat.createdAt, |                 createdAt: chat.createdAt, | ||||||
|                 updatedAt: now |                 updatedAt: now | ||||||
|             }, null, 2), chatId] |             }, null, 2), chatId] | ||||||
| @@ -190,7 +268,8 @@ export class ChatStorageService { | |||||||
|             ...chat, |             ...chat, | ||||||
|             title: title || chat.title, |             title: title || chat.title, | ||||||
|             messages, |             messages, | ||||||
|             updatedAt: now |             updatedAt: now, | ||||||
|  |             metadata: updatedMetadata | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -211,6 +290,160 @@ export class ChatStorageService { | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Record a new tool execution | ||||||
|  |      */ | ||||||
|  |     async recordToolExecution( | ||||||
|  |         chatId: string, | ||||||
|  |         toolName: string,  | ||||||
|  |         toolId: string, | ||||||
|  |         args: Record<string, any> | string, | ||||||
|  |         result: string | Record<string, any>, | ||||||
|  |         error?: string | ||||||
|  |     ): Promise<boolean> { | ||||||
|  |         try { | ||||||
|  |             const chat = await this.getChat(chatId); | ||||||
|  |             if (!chat) return false; | ||||||
|  |  | ||||||
|  |             const toolExecution: ToolExecution = { | ||||||
|  |                 id: toolId, | ||||||
|  |                 name: toolName, | ||||||
|  |                 arguments: args, | ||||||
|  |                 result, | ||||||
|  |                 error, | ||||||
|  |                 timestamp: new Date(), | ||||||
|  |                 executionTime: 0 // Could track this if we passed in a start time | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             const currentToolExecutions = chat.metadata?.toolExecutions || []; | ||||||
|  |             currentToolExecutions.push(toolExecution); | ||||||
|  |  | ||||||
|  |             await this.updateChat( | ||||||
|  |                 chatId, | ||||||
|  |                 chat.messages, | ||||||
|  |                 undefined, // Don't change title | ||||||
|  |                 { | ||||||
|  |                     ...chat.metadata, | ||||||
|  |                     toolExecutions: currentToolExecutions | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             return true; | ||||||
|  |         } catch (e) { | ||||||
|  |             log.error(`Failed to record tool execution: ${e}`); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Extract tool executions from messages | ||||||
|  |      * This helps maintain a record of all tool calls even if messages are truncated | ||||||
|  |      */ | ||||||
|  |     private extractToolExecutionsFromMessages( | ||||||
|  |         messages: Message[],  | ||||||
|  |         existingToolExecutions: ToolExecution[] = [] | ||||||
|  |     ): ToolExecution[] { | ||||||
|  |         const toolExecutions = [...existingToolExecutions]; | ||||||
|  |         const executedToolIds = new Set(existingToolExecutions.map(t => t.id)); | ||||||
|  |  | ||||||
|  |         // Process all messages to find tool calls and their results | ||||||
|  |         const assistantMessages = messages.filter(msg => msg.role === 'assistant' && msg.tool_calls); | ||||||
|  |         const toolMessages = messages.filter(msg => msg.role === 'tool'); | ||||||
|  |  | ||||||
|  |         // Create a map of tool responses by tool_call_id | ||||||
|  |         const toolResponseMap = new Map<string, string>(); | ||||||
|  |         for (const toolMsg of toolMessages) { | ||||||
|  |             if (toolMsg.tool_call_id) { | ||||||
|  |                 toolResponseMap.set(toolMsg.tool_call_id, toolMsg.content); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Extract all tool calls and pair with responses | ||||||
|  |         for (const assistantMsg of assistantMessages) { | ||||||
|  |             if (!assistantMsg.tool_calls || !Array.isArray(assistantMsg.tool_calls)) continue; | ||||||
|  |  | ||||||
|  |             for (const toolCall of assistantMsg.tool_calls as ToolCall[]) { | ||||||
|  |                 if (!toolCall.id || executedToolIds.has(toolCall.id)) continue; | ||||||
|  |  | ||||||
|  |                 const toolResponse = toolResponseMap.get(toolCall.id); | ||||||
|  |                 if (!toolResponse) continue; // Skip if no response found | ||||||
|  |  | ||||||
|  |                 // We found a tool call with a response, record it | ||||||
|  |                 let args: Record<string, any> | string; | ||||||
|  |                 if (typeof toolCall.function.arguments === 'string') { | ||||||
|  |                     try { | ||||||
|  |                         args = JSON.parse(toolCall.function.arguments); | ||||||
|  |                     } catch (e) { | ||||||
|  |                         args = toolCall.function.arguments; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     args = toolCall.function.arguments; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 let result: string | Record<string, any> = toolResponse; | ||||||
|  |                 try { | ||||||
|  |                     // Try to parse result as JSON if it starts with { or [ | ||||||
|  |                     if (toolResponse.trim().startsWith('{') || toolResponse.trim().startsWith('[')) { | ||||||
|  |                         result = JSON.parse(toolResponse); | ||||||
|  |                     } | ||||||
|  |                 } catch (e) { | ||||||
|  |                     // Keep as string if parsing fails | ||||||
|  |                     result = toolResponse; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 const isError = toolResponse.startsWith('Error:'); | ||||||
|  |                 const toolExecution: ToolExecution = { | ||||||
|  |                     id: toolCall.id, | ||||||
|  |                     name: toolCall.function.name, | ||||||
|  |                     arguments: args, | ||||||
|  |                     result, | ||||||
|  |                     error: isError ? toolResponse.substring('Error:'.length).trim() : undefined, | ||||||
|  |                     timestamp: new Date() | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 toolExecutions.push(toolExecution); | ||||||
|  |                 executedToolIds.add(toolCall.id); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return toolExecutions; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Store sources used in a chat | ||||||
|  |      */ | ||||||
|  |     async recordSources( | ||||||
|  |         chatId: string, | ||||||
|  |         sources: Array<{ | ||||||
|  |             noteId: string; | ||||||
|  |             title: string; | ||||||
|  |             similarity?: number; | ||||||
|  |             path?: string; | ||||||
|  |             branchId?: string; | ||||||
|  |             content?: string; | ||||||
|  |         }> | ||||||
|  |     ): Promise<boolean> { | ||||||
|  |         try { | ||||||
|  |             const chat = await this.getChat(chatId); | ||||||
|  |             if (!chat) return false; | ||||||
|  |  | ||||||
|  |             await this.updateChat( | ||||||
|  |                 chatId, | ||||||
|  |                 chat.messages, | ||||||
|  |                 undefined, // Don't change title | ||||||
|  |                 { | ||||||
|  |                     ...chat.metadata, | ||||||
|  |                     sources | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             return true; | ||||||
|  |         } catch (e) { | ||||||
|  |             log.error(`Failed to record sources: ${e}`); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Singleton instance | // Singleton instance | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import log from '../../../log.js'; | |||||||
| import type { StreamCallback, ToolExecutionInput } from '../interfaces.js'; | import type { StreamCallback, ToolExecutionInput } from '../interfaces.js'; | ||||||
| import { BasePipelineStage } from '../pipeline_stage.js'; | import { BasePipelineStage } from '../pipeline_stage.js'; | ||||||
| import toolRegistry from '../../tools/tool_registry.js'; | import toolRegistry from '../../tools/tool_registry.js'; | ||||||
|  | import chatStorageService from '../../chat_storage_service.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pipeline stage for handling LLM tool calling |  * Pipeline stage for handling LLM tool calling | ||||||
| @@ -172,6 +173,22 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re | |||||||
|                     const executionTime = Date.now() - executionStart; |                     const executionTime = Date.now() - executionStart; | ||||||
|                     log.info(`================ TOOL EXECUTION COMPLETED in ${executionTime}ms ================`); |                     log.info(`================ TOOL EXECUTION COMPLETED in ${executionTime}ms ================`); | ||||||
|                      |                      | ||||||
|  |                     // Record this successful tool execution if there's a sessionId available | ||||||
|  |                     if (input.options?.sessionId) { | ||||||
|  |                         try { | ||||||
|  |                             await chatStorageService.recordToolExecution( | ||||||
|  |                                 input.options.sessionId, | ||||||
|  |                                 toolCall.function.name, | ||||||
|  |                                 toolCall.id || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, | ||||||
|  |                                 args, | ||||||
|  |                                 result, | ||||||
|  |                                 undefined // No error for successful execution | ||||||
|  |                             ); | ||||||
|  |                         } catch (storageError) { | ||||||
|  |                             log.error(`Failed to record tool execution in chat storage: ${storageError}`); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|                     // Emit tool completion event if streaming is enabled |                     // Emit tool completion event if streaming is enabled | ||||||
|                     if (streamCallback) { |                     if (streamCallback) { | ||||||
|                         const toolExecutionData = { |                         const toolExecutionData = { | ||||||
| @@ -190,6 +207,22 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re | |||||||
|                     const executionTime = Date.now() - executionStart; |                     const executionTime = Date.now() - executionStart; | ||||||
|                     log.error(`================ TOOL EXECUTION FAILED in ${executionTime}ms: ${execError.message} ================`); |                     log.error(`================ TOOL EXECUTION FAILED in ${executionTime}ms: ${execError.message} ================`); | ||||||
|                      |                      | ||||||
|  |                     // Record this failed tool execution if there's a sessionId available | ||||||
|  |                     if (input.options?.sessionId) { | ||||||
|  |                         try { | ||||||
|  |                             await chatStorageService.recordToolExecution( | ||||||
|  |                                 input.options.sessionId, | ||||||
|  |                                 toolCall.function.name, | ||||||
|  |                                 toolCall.id || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, | ||||||
|  |                                 args, | ||||||
|  |                                 "", // No result for failed execution | ||||||
|  |                                 execError.message || String(execError) | ||||||
|  |                             ); | ||||||
|  |                         } catch (storageError) { | ||||||
|  |                             log.error(`Failed to record tool execution error in chat storage: ${storageError}`); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|                     // Emit tool error event if streaming is enabled |                     // Emit tool error event if streaming is enabled | ||||||
|                     if (streamCallback) { |                     if (streamCallback) { | ||||||
|                         const toolExecutionData = { |                         const toolExecutionData = { | ||||||
|   | |||||||
| @@ -476,7 +476,9 @@ class RestChatService { | |||||||
|                 model: session.metadata.model, |                 model: session.metadata.model, | ||||||
|                 // Set stream based on request type, but ensure it's explicitly a boolean value |                 // Set stream based on request type, but ensure it's explicitly a boolean value | ||||||
|                 // GET requests or format=stream parameter indicates streaming should be used |                 // GET requests or format=stream parameter indicates streaming should be used | ||||||
|                 stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true') |                 stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true'), | ||||||
|  |                 // Include sessionId for tracking tool executions | ||||||
|  |                 sessionId: sessionId | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             // Log the options to verify what's being sent to the pipeline |             // Log the options to verify what's being sent to the pipeline | ||||||
| @@ -491,6 +493,9 @@ class RestChatService { | |||||||
|             // Create a stream callback wrapper |             // Create a stream callback wrapper | ||||||
|             // This will ensure we properly handle all streaming messages |             // This will ensure we properly handle all streaming messages | ||||||
|             let messageContent = ''; |             let messageContent = ''; | ||||||
|  |              | ||||||
|  |             // Used to track tool call responses for metadata storage | ||||||
|  |             const toolResponseMap = new Map<string, string>(); | ||||||
|             let streamFinished = false; |             let streamFinished = false; | ||||||
|  |  | ||||||
|             // Prepare the pipeline input |             // Prepare the pipeline input | ||||||
| @@ -621,10 +626,27 @@ class RestChatService { | |||||||
|                     timestamp: new Date() |                     timestamp: new Date() | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|                 // Return the response |                 // Extract sources if they're available | ||||||
|  |                 const sources = (response as any).sources || []; | ||||||
|  |                  | ||||||
|  |                 // Store sources in the session metadata if they're present | ||||||
|  |                 if (sources.length > 0) { | ||||||
|  |                     session.metadata.sources = sources; | ||||||
|  |                     log.info(`Stored ${sources.length} sources in session metadata`); | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 // Return the response with complete metadata | ||||||
|                 return { |                 return { | ||||||
|                     content: response.text || '', |                     content: response.text || '', | ||||||
|                     sources: (response as any).sources || [] |                     sources: sources, | ||||||
|  |                     metadata: { | ||||||
|  |                         model: response.model || session.metadata.model, | ||||||
|  |                         provider: response.provider || session.metadata.provider, | ||||||
|  |                         temperature: session.metadata.temperature, | ||||||
|  |                         maxTokens: session.metadata.maxTokens, | ||||||
|  |                         lastUpdated: new Date().toISOString(), | ||||||
|  |                         toolExecutions: session.metadata.toolExecutions || [] | ||||||
|  |                     } | ||||||
|                 }; |                 }; | ||||||
|             } else { |             } else { | ||||||
|                 // For streaming requests, we've already sent the response |                 // For streaming requests, we've already sent the response | ||||||
| @@ -1132,12 +1154,19 @@ class RestChatService { | |||||||
|                         }); |                         }); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     // Store the response in the session |                     // Store the response in the session with tool_calls if present | ||||||
|                     session.messages.push({ |                     const assistantMessage: any = { | ||||||
|                         role: 'assistant', |                         role: 'assistant', | ||||||
|                         content: messageContent, |                         content: messageContent, | ||||||
|                         timestamp: new Date() |                         timestamp: new Date() | ||||||
|                     }); |                     }; | ||||||
|  |                      | ||||||
|  |                     // If there were tool calls, store them with the message | ||||||
|  |                     if (response.tool_calls && response.tool_calls.length > 0) { | ||||||
|  |                         assistantMessage.tool_calls = response.tool_calls; | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                     session.messages.push(assistantMessage); | ||||||
|  |  | ||||||
|                     return; |                     return; | ||||||
|                 } catch (toolError) { |                 } catch (toolError) { | ||||||
| @@ -1159,6 +1188,18 @@ class RestChatService { | |||||||
|             if (response.stream) { |             if (response.stream) { | ||||||
|                 log.info(`Provider ${service.getName()} supports streaming via stream() method`); |                 log.info(`Provider ${service.getName()} supports streaming via stream() method`); | ||||||
|                  |                  | ||||||
|  |                 // Store information about the model and provider in session metadata | ||||||
|  |                 session.metadata.model = response.model || session.metadata.model; | ||||||
|  |                 session.metadata.provider = response.provider || session.metadata.provider; | ||||||
|  |                 session.metadata.lastUpdated = new Date().toISOString(); | ||||||
|  |                  | ||||||
|  |                 // If response has tool_calls, capture those for later storage in metadata | ||||||
|  |                 if (response.tool_calls && response.tool_calls.length > 0) { | ||||||
|  |                     log.info(`Storing ${response.tool_calls.length} initial tool calls in session metadata`); | ||||||
|  |                     // We'll complete this information when we get the tool results | ||||||
|  |                     session.metadata.pendingToolCalls = response.tool_calls; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 try { |                 try { | ||||||
|                     await response.stream(async (chunk: StreamChunk) => { |                     await response.stream(async (chunk: StreamChunk) => { | ||||||
|                         if (chunk.text) { |                         if (chunk.text) { | ||||||
| @@ -1205,6 +1246,41 @@ class RestChatService { | |||||||
|                         if (chunk.done) { |                         if (chunk.done) { | ||||||
|                             log.info(`Stream completed from ${service.getName()}, total content: ${messageContent.length} chars`); |                             log.info(`Stream completed from ${service.getName()}, total content: ${messageContent.length} chars`); | ||||||
|                              |                              | ||||||
|  |                             // Store tool executions from the conversation into metadata | ||||||
|  |                             if (session.metadata.pendingToolCalls) { | ||||||
|  |                                 const toolExecutions = session.metadata.toolExecutions || []; | ||||||
|  |                                  | ||||||
|  |                                 // We don't have a toolResponseMap available at this scope | ||||||
|  |                                 // Just record the pending tool calls with minimal information | ||||||
|  |                                 for (const toolCall of session.metadata.pendingToolCalls) { | ||||||
|  |                                     if (!toolCall.id) continue; | ||||||
|  |                                      | ||||||
|  |                                     // Parse arguments | ||||||
|  |                                     let args = toolCall.function.arguments; | ||||||
|  |                                     if (typeof args === 'string') { | ||||||
|  |                                         try { | ||||||
|  |                                             args = JSON.parse(args); | ||||||
|  |                                         } catch { | ||||||
|  |                                             // Keep as string if not valid JSON | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                      | ||||||
|  |                                     // Add to tool executions with minimal info | ||||||
|  |                                     toolExecutions.push({ | ||||||
|  |                                         id: toolCall.id, | ||||||
|  |                                         name: toolCall.function.name, | ||||||
|  |                                         arguments: args, | ||||||
|  |                                         result: "Result not captured in streaming mode", | ||||||
|  |                                         timestamp: new Date().toISOString() | ||||||
|  |                                     }); | ||||||
|  |                                 } | ||||||
|  |                                  | ||||||
|  |                                 // Update session metadata | ||||||
|  |                                 session.metadata.toolExecutions = toolExecutions; | ||||||
|  |                                 delete session.metadata.pendingToolCalls; | ||||||
|  |                                 log.info(`Stored ${toolExecutions.length} tool executions in session metadata`); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|                             // Only send final done message if it wasn't already sent with content |                             // Only send final done message if it wasn't already sent with content | ||||||
|                             // This ensures we don't duplicate the content but still mark completion |                             // This ensures we don't duplicate the content but still mark completion | ||||||
|                             if (!chunk.text) { |                             if (!chunk.text) { | ||||||
| @@ -1397,6 +1473,40 @@ class RestChatService { | |||||||
|     /** |     /** | ||||||
|      * Build context from relevant notes |      * Build context from relevant notes | ||||||
|      */ |      */ | ||||||
|  |     /** | ||||||
|  |      * Record a tool execution in the session metadata | ||||||
|  |      */ | ||||||
|  |     private recordToolExecution(sessionId: string, tool: any, result: string, error?: string): void { | ||||||
|  |         if (!sessionId) return; | ||||||
|  |          | ||||||
|  |         const session = sessions.get(sessionId); | ||||||
|  |         if (!session) return; | ||||||
|  |          | ||||||
|  |         try { | ||||||
|  |             const toolExecutions = session.metadata.toolExecutions || []; | ||||||
|  |              | ||||||
|  |             // Format tool execution record | ||||||
|  |             const execution = { | ||||||
|  |                 id: tool.id || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, | ||||||
|  |                 name: tool.function?.name || 'unknown', | ||||||
|  |                 arguments: typeof tool.function?.arguments === 'string'  | ||||||
|  |                     ? (() => { try { return JSON.parse(tool.function.arguments); } catch { return tool.function.arguments; } })() | ||||||
|  |                     : tool.function?.arguments || {}, | ||||||
|  |                 result: result, | ||||||
|  |                 error: error, | ||||||
|  |                 timestamp: new Date().toISOString() | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             // Add to tool executions | ||||||
|  |             toolExecutions.push(execution); | ||||||
|  |             session.metadata.toolExecutions = toolExecutions; | ||||||
|  |              | ||||||
|  |             log.info(`Recorded tool execution for ${execution.name} in session ${sessionId}`); | ||||||
|  |         } catch (err) { | ||||||
|  |             log.error(`Failed to record tool execution: ${err}`); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|     buildContextFromNotes(sources: NoteSource[], query: string): string { |     buildContextFromNotes(sources: NoteSource[], query: string): string { | ||||||
|         if (!sources || sources.length === 0) { |         if (!sources || sources.length === 0) { | ||||||
|             return query || ''; |             return query || ''; | ||||||
| @@ -1466,7 +1576,10 @@ class RestChatService { | |||||||
|                     temperature: options.temperature, |                     temperature: options.temperature, | ||||||
|                     maxTokens: options.maxTokens, |                     maxTokens: options.maxTokens, | ||||||
|                     model: options.model, |                     model: options.model, | ||||||
|                     provider: options.provider |                     provider: options.provider, | ||||||
|  |                     sources: [], | ||||||
|  |                     toolExecutions: [], | ||||||
|  |                     lastUpdated: now.toISOString() | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
| @@ -1494,14 +1607,25 @@ class RestChatService { | |||||||
|                 throw new Error(`Session with ID ${sessionId} not found`); |                 throw new Error(`Session with ID ${sessionId} not found`); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Return session without internal metadata |             // Return session with metadata and additional fields | ||||||
|             return { |             return { | ||||||
|                 id: session.id, |                 id: session.id, | ||||||
|                 title: session.title, |                 title: session.title, | ||||||
|                 createdAt: session.createdAt, |                 createdAt: session.createdAt, | ||||||
|                 lastActive: session.lastActive, |                 lastActive: session.lastActive, | ||||||
|                 messages: session.messages, |                 messages: session.messages, | ||||||
|                 noteContext: session.noteContext |                 noteContext: session.noteContext, | ||||||
|  |                 // Include additional fields for the frontend | ||||||
|  |                 sources: session.metadata.sources || [], | ||||||
|  |                 metadata: { | ||||||
|  |                     model: session.metadata.model, | ||||||
|  |                     provider: session.metadata.provider, | ||||||
|  |                     temperature: session.metadata.temperature, | ||||||
|  |                     maxTokens: session.metadata.maxTokens, | ||||||
|  |                     lastUpdated: session.lastActive.toISOString(), | ||||||
|  |                     // Include simplified tool executions if available | ||||||
|  |                     toolExecutions: session.metadata.toolExecutions || [] | ||||||
|  |                 } | ||||||
|             }; |             }; | ||||||
|         } catch (error: any) { |         } catch (error: any) { | ||||||
|             log.error(`Error getting LLM session: ${error.message || 'Unknown error'}`); |             log.error(`Error getting LLM session: ${error.message || 'Unknown error'}`); | ||||||
| @@ -1532,7 +1656,7 @@ class RestChatService { | |||||||
|                 session.noteContext = updates.noteContext; |                 session.noteContext = updates.noteContext; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Update metadata |             // Update basic metadata | ||||||
|             if (updates.temperature !== undefined) { |             if (updates.temperature !== undefined) { | ||||||
|                 session.metadata.temperature = updates.temperature; |                 session.metadata.temperature = updates.temperature; | ||||||
|             } |             } | ||||||
| @@ -1549,13 +1673,39 @@ class RestChatService { | |||||||
|                 session.metadata.provider = updates.provider; |                 session.metadata.provider = updates.provider; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // Handle new extended metadata from the frontend | ||||||
|  |             if (updates.metadata) { | ||||||
|  |                 // Update various metadata fields but keep existing ones | ||||||
|  |                 session.metadata = { | ||||||
|  |                     ...session.metadata, | ||||||
|  |                     ...updates.metadata, | ||||||
|  |                     // Make sure timestamp is updated | ||||||
|  |                     lastUpdated: new Date().toISOString() | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Handle sources as a top-level field | ||||||
|  |             if (updates.sources && Array.isArray(updates.sources)) { | ||||||
|  |                 session.metadata.sources = updates.sources; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Handle tool executions from frontend | ||||||
|  |             if (updates.toolExecutions && Array.isArray(updates.toolExecutions)) { | ||||||
|  |                 session.metadata.toolExecutions = updates.toolExecutions; | ||||||
|  |             } else if (updates.metadata?.toolExecutions && Array.isArray(updates.metadata.toolExecutions)) { | ||||||
|  |                 session.metadata.toolExecutions = updates.metadata.toolExecutions; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // Update timestamp |             // Update timestamp | ||||||
|             session.lastActive = new Date(); |             session.lastActive = new Date(); | ||||||
|  |  | ||||||
|             return { |             return { | ||||||
|                 id: session.id, |                 id: session.id, | ||||||
|                 title: session.title, |                 title: session.title, | ||||||
|                 updatedAt: session.lastActive |                 updatedAt: session.lastActive, | ||||||
|  |                 // Include updated metadata in response | ||||||
|  |                 metadata: session.metadata, | ||||||
|  |                 sources: session.metadata.sources || [] | ||||||
|             }; |             }; | ||||||
|         } catch (error: any) { |         } catch (error: any) { | ||||||
|             log.error(`Error updating LLM session: ${error.message || 'Unknown error'}`); |             log.error(`Error updating LLM session: ${error.message || 'Unknown error'}`); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user