mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	I think this is close
This commit is contained in:
		
							parent
							
								
									91c31d279b
								
							
						
					
					
						commit
						16604c85a6
					
				| @ -27,16 +27,6 @@ const TPL = ` | |||||||
|                 <span class="visually-hidden">Loading...</span> |                 <span class="visually-hidden">Loading...</span> | ||||||
|             </div> |             </div> | ||||||
|             <span class="ms-2">${t('ai_llm.agent.processing')}</span> |             <span class="ms-2">${t('ai_llm.agent.processing')}</span> | ||||||
|             <div class="tool-execution-info mt-2" style="display: none;"> |  | ||||||
|                 <!-- Tool execution status will be shown here --> |  | ||||||
|                 <div class="tool-execution-status small p-2 bg-light rounded" style="max-height: 150px; overflow-y: auto;"> |  | ||||||
|                     <div class="d-flex align-items-center"> |  | ||||||
|                         <i class="bx bx-code-block text-primary me-2"></i> |  | ||||||
|                         <span class="fw-bold">Tool Execution:</span> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="tool-execution-steps ps-3 pt-1"></div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
| @ -98,8 +88,6 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|     private noteContextChatSendButton!: HTMLButtonElement; |     private noteContextChatSendButton!: HTMLButtonElement; | ||||||
|     private chatContainer!: HTMLElement; |     private chatContainer!: HTMLElement; | ||||||
|     private loadingIndicator!: HTMLElement; |     private loadingIndicator!: HTMLElement; | ||||||
|     private toolExecutionInfo!: HTMLElement; |  | ||||||
|     private toolExecutionSteps!: HTMLElement; |  | ||||||
|     private sourcesList!: HTMLElement; |     private sourcesList!: HTMLElement; | ||||||
|     private useAdvancedContextCheckbox!: HTMLInputElement; |     private useAdvancedContextCheckbox!: HTMLInputElement; | ||||||
|     private showThinkingCheckbox!: HTMLInputElement; |     private showThinkingCheckbox!: HTMLInputElement; | ||||||
| @ -157,8 +145,6 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|         this.noteContextChatSendButton = element.querySelector('.note-context-chat-send-button') as HTMLButtonElement; |         this.noteContextChatSendButton = element.querySelector('.note-context-chat-send-button') as HTMLButtonElement; | ||||||
|         this.chatContainer = element.querySelector('.note-context-chat-container') as HTMLElement; |         this.chatContainer = element.querySelector('.note-context-chat-container') as HTMLElement; | ||||||
|         this.loadingIndicator = element.querySelector('.loading-indicator') as HTMLElement; |         this.loadingIndicator = element.querySelector('.loading-indicator') as HTMLElement; | ||||||
|         this.toolExecutionInfo = element.querySelector('.tool-execution-info') as HTMLElement; |  | ||||||
|         this.toolExecutionSteps = element.querySelector('.tool-execution-steps') as HTMLElement; |  | ||||||
|         this.sourcesList = element.querySelector('.sources-list') as HTMLElement; |         this.sourcesList = element.querySelector('.sources-list') as HTMLElement; | ||||||
|         this.useAdvancedContextCheckbox = element.querySelector('.use-advanced-context-checkbox') as HTMLInputElement; |         this.useAdvancedContextCheckbox = element.querySelector('.use-advanced-context-checkbox') as HTMLInputElement; | ||||||
|         this.showThinkingCheckbox = element.querySelector('.show-thinking-checkbox') as HTMLInputElement; |         this.showThinkingCheckbox = element.querySelector('.show-thinking-checkbox') as HTMLInputElement; | ||||||
| @ -210,12 +196,16 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|  |             // Extract current tool execution steps if any exist
 | ||||||
|  |             const toolSteps = this.extractInChatToolSteps(); | ||||||
|  | 
 | ||||||
|             const dataToSave = { |             const dataToSave = { | ||||||
|                 messages: this.messages, |                 messages: this.messages, | ||||||
|                 sessionId: this.sessionId |                 sessionId: this.sessionId, | ||||||
|  |                 toolSteps: toolSteps // Save tool execution steps alongside messages
 | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             console.log(`Saving chat data with sessionId: ${this.sessionId}`); |             console.log(`Saving chat data with sessionId: ${this.sessionId} and ${toolSteps.length} tool steps`); | ||||||
| 
 | 
 | ||||||
|             await this.onSaveData(dataToSave); |             await this.onSaveData(dataToSave); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
| @ -223,6 +213,61 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Extract tool execution steps from the DOM that are within the chat flow | ||||||
|  |      */ | ||||||
|  |     private extractInChatToolSteps(): Array<{type: string, name?: string, content: string}> { | ||||||
|  |         const steps: Array<{type: string, name?: string, content: string}> = []; | ||||||
|  | 
 | ||||||
|  |         // Look for tool execution in the chat flow
 | ||||||
|  |         const toolExecutionElement = this.noteContextChatMessages.querySelector('.chat-tool-execution'); | ||||||
|  | 
 | ||||||
|  |         if (toolExecutionElement) { | ||||||
|  |             // Find all tool step elements
 | ||||||
|  |             const stepElements = toolExecutionElement.querySelectorAll('.tool-step'); | ||||||
|  | 
 | ||||||
|  |             stepElements.forEach(stepEl => { | ||||||
|  |                 const stepHtml = stepEl.innerHTML; | ||||||
|  | 
 | ||||||
|  |                 // Determine the step type based on icons or classes present
 | ||||||
|  |                 let type = 'info'; | ||||||
|  |                 let name: string | undefined; | ||||||
|  |                 let content = ''; | ||||||
|  | 
 | ||||||
|  |                 if (stepHtml.includes('bx-code-block')) { | ||||||
|  |                     type = 'executing'; | ||||||
|  |                     content = 'Executing tools...'; | ||||||
|  |                 } else if (stepHtml.includes('bx-terminal')) { | ||||||
|  |                     type = 'result'; | ||||||
|  |                     // Extract the tool name from the step
 | ||||||
|  |                     const nameMatch = stepHtml.match(/<span[^>]*>Tool: ([^<]+)<\/span>/); | ||||||
|  |                     name = nameMatch ? nameMatch[1] : 'unknown'; | ||||||
|  | 
 | ||||||
|  |                     // Extract the content from the div with class mt-1 ps-3
 | ||||||
|  |                     const contentEl = stepEl.querySelector('.mt-1.ps-3'); | ||||||
|  |                     content = contentEl ? contentEl.innerHTML : ''; | ||||||
|  |                 } else if (stepHtml.includes('bx-error-circle')) { | ||||||
|  |                     type = 'error'; | ||||||
|  |                     const nameMatch = stepHtml.match(/<span[^>]*>Tool: ([^<]+)<\/span>/); | ||||||
|  |                     name = nameMatch ? nameMatch[1] : 'unknown'; | ||||||
|  | 
 | ||||||
|  |                     const contentEl = stepEl.querySelector('.mt-1.ps-3.text-danger'); | ||||||
|  |                     content = contentEl ? contentEl.innerHTML : ''; | ||||||
|  |                 } else if (stepHtml.includes('bx-message-dots')) { | ||||||
|  |                     type = 'generating'; | ||||||
|  |                     content = 'Generating response with tool results...'; | ||||||
|  |                 } else if (stepHtml.includes('bx-loader-alt')) { | ||||||
|  |                     // Skip the initializing spinner
 | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 steps.push({ type, name, content }); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return steps; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Load saved chat data from the note attribute |      * Load saved chat data from the note attribute | ||||||
|      */ |      */ | ||||||
| @ -246,6 +291,12 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                     this.addMessageToChat(role, message.content); |                     this.addMessageToChat(role, message.content); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  |                 // Restore tool execution steps if they exist
 | ||||||
|  |                 if (savedData.toolSteps && Array.isArray(savedData.toolSteps) && savedData.toolSteps.length > 0) { | ||||||
|  |                     console.log(`Restoring ${savedData.toolSteps.length} saved tool steps`); | ||||||
|  |                     this.restoreInChatToolSteps(savedData.toolSteps); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 // Load session ID if available
 |                 // Load session ID if available
 | ||||||
|                 if (savedData.sessionId) { |                 if (savedData.sessionId) { | ||||||
|                     try { |                     try { | ||||||
| @ -261,7 +312,7 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                             await this.createChatSession(); |                             await this.createChatSession(); | ||||||
|                         } |                         } | ||||||
|                     } catch (error) { |                     } catch (error) { | ||||||
|                         console.log(`Error checking saved session ${savedData.sessionId}, will create new one`); |                         console.log(`Error checking saved session ${savedData.sessionId}, creating a new one`); | ||||||
|                         this.sessionId = null; |                         this.sessionId = null; | ||||||
|                         await this.createChatSession(); |                         await this.createChatSession(); | ||||||
|                     } |                     } | ||||||
| @ -280,6 +331,54 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Restore tool execution steps in the chat UI | ||||||
|  |      */ | ||||||
|  |     private restoreInChatToolSteps(steps: Array<{type: string, name?: string, content: string}>) { | ||||||
|  |         if (!steps || steps.length === 0) return; | ||||||
|  | 
 | ||||||
|  |         // Create the tool execution element
 | ||||||
|  |         const toolExecutionElement = document.createElement('div'); | ||||||
|  |         toolExecutionElement.className = 'chat-tool-execution mb-3'; | ||||||
|  | 
 | ||||||
|  |         // Insert before the assistant message if it exists
 | ||||||
|  |         const assistantMessage = this.noteContextChatMessages.querySelector('.assistant-message:last-child'); | ||||||
|  |         if (assistantMessage) { | ||||||
|  |             this.noteContextChatMessages.insertBefore(toolExecutionElement, assistantMessage); | ||||||
|  |         } else { | ||||||
|  |             // Otherwise append to the end
 | ||||||
|  |             this.noteContextChatMessages.appendChild(toolExecutionElement); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Fill with tool execution content
 | ||||||
|  |         toolExecutionElement.innerHTML = ` | ||||||
|  |             <div class="tool-execution-container p-2 rounded mb-2"> | ||||||
|  |                 <div class="tool-execution-header d-flex align-items-center justify-content-between mb-2"> | ||||||
|  |                     <div> | ||||||
|  |                         <i class="bx bx-code-block text-primary me-2"></i> | ||||||
|  |                         <span class="fw-bold">Tool Execution</span> | ||||||
|  |                     </div> | ||||||
|  |                     <button type="button" class="btn btn-sm btn-link p-0 text-muted tool-execution-chat-clear" title="Clear tool execution history"> | ||||||
|  |                         <i class="bx bx-x"></i> | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="tool-execution-chat-steps"> | ||||||
|  |                     ${this.renderToolStepsHtml(steps)} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         `;
 | ||||||
|  | 
 | ||||||
|  |         // Add event listener for the clear button
 | ||||||
|  |         const clearButton = toolExecutionElement.querySelector('.tool-execution-chat-clear'); | ||||||
|  |         if (clearButton) { | ||||||
|  |             clearButton.addEventListener('click', (e) => { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 e.stopPropagation(); | ||||||
|  |                 toolExecutionElement.remove(); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async refresh() { |     async refresh() { | ||||||
|         if (!this.isVisible()) { |         if (!this.isVisible()) { | ||||||
|             return; |             return; | ||||||
| @ -731,32 +830,83 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Check if we already have an assistant message element to update
 |         // Extract the tool execution steps and final response
 | ||||||
|         const assistantElement = this.noteContextChatMessages.querySelector('.assistant-message:last-child .message-content'); |         const toolSteps = this.extractToolExecutionSteps(assistantResponse); | ||||||
|  |         const finalResponseText = this.extractFinalResponse(assistantResponse); | ||||||
| 
 | 
 | ||||||
|         if (assistantElement) { |         // Find existing assistant message or create one if needed
 | ||||||
|             console.log(`[${logId}] Found existing assistant message element, updating content`); |         let assistantElement = this.noteContextChatMessages.querySelector('.assistant-message:last-child .message-content'); | ||||||
|             try { |  | ||||||
|                 // Format markdown and update the element
 |  | ||||||
|                 const formattedContent = this.formatMarkdown(assistantResponse); |  | ||||||
| 
 | 
 | ||||||
|                 // Ensure content is properly formatted
 |         // First, check if we need to add the tool execution steps to the chat flow
 | ||||||
|                 if (!formattedContent || formattedContent.trim() === '') { |         if (toolSteps.length > 0) { | ||||||
|                     console.warn(`[${logId}] Formatted content is empty, using original content`); |             // Look for an existing tool execution element in the chat flow
 | ||||||
|                     assistantElement.textContent = assistantResponse; |             let toolExecutionElement = this.noteContextChatMessages.querySelector('.chat-tool-execution'); | ||||||
|  | 
 | ||||||
|  |             if (!toolExecutionElement) { | ||||||
|  |                 // Create a new tool execution element in the chat flow
 | ||||||
|  |                 // Place it right before the assistant message if it exists, or at the end of chat
 | ||||||
|  |                 toolExecutionElement = document.createElement('div'); | ||||||
|  |                 toolExecutionElement.className = 'chat-tool-execution mb-3'; | ||||||
|  | 
 | ||||||
|  |                 // If there's an assistant message, insert before it
 | ||||||
|  |                 const assistantMessage = this.noteContextChatMessages.querySelector('.assistant-message:last-child'); | ||||||
|  |                 if (assistantMessage) { | ||||||
|  |                     this.noteContextChatMessages.insertBefore(toolExecutionElement, assistantMessage); | ||||||
|                 } else { |                 } else { | ||||||
|                     assistantElement.innerHTML = formattedContent; |                     // Otherwise append to the end
 | ||||||
|  |                     this.noteContextChatMessages.appendChild(toolExecutionElement); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Update the tool execution content
 | ||||||
|  |             toolExecutionElement.innerHTML = ` | ||||||
|  |                 <div class="tool-execution-container p-2 rounded mb-2"> | ||||||
|  |                     <div class="tool-execution-header d-flex align-items-center justify-content-between mb-2"> | ||||||
|  |                         <div> | ||||||
|  |                             <i class="bx bx-code-block text-primary me-2"></i> | ||||||
|  |                             <span class="fw-bold">Tool Execution</span> | ||||||
|  |                         </div> | ||||||
|  |                         <button type="button" class="btn btn-sm btn-link p-0 text-muted tool-execution-chat-clear" title="Clear tool execution history"> | ||||||
|  |                             <i class="bx bx-x"></i> | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="tool-execution-chat-steps"> | ||||||
|  |                         ${this.renderToolStepsHtml(toolSteps)} | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             `;
 | ||||||
|  | 
 | ||||||
|  |             // Add event listener for the clear button
 | ||||||
|  |             const clearButton = toolExecutionElement.querySelector('.tool-execution-chat-clear'); | ||||||
|  |             if (clearButton) { | ||||||
|  |                 clearButton.addEventListener('click', (e) => { | ||||||
|  |                     e.preventDefault(); | ||||||
|  |                     e.stopPropagation(); | ||||||
|  |                     toolExecutionElement?.remove(); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Now update or create the assistant message with the final response
 | ||||||
|  |         if (finalResponseText) { | ||||||
|  |             if (assistantElement) { | ||||||
|  |                 console.log(`[${logId}] Found existing assistant message element, updating with final response`); | ||||||
|  |                 try { | ||||||
|  |                     // Format the final response with markdown
 | ||||||
|  |                     const formattedResponse = this.formatMarkdown(finalResponseText); | ||||||
|  | 
 | ||||||
|  |                     // Update the content
 | ||||||
|  |                     assistantElement.innerHTML = formattedResponse || ''; | ||||||
| 
 | 
 | ||||||
|                     // Apply syntax highlighting to any code blocks in the updated content
 |                     // Apply syntax highlighting to any code blocks in the updated content
 | ||||||
|                     applySyntaxHighlight($(assistantElement as HTMLElement)); |                     applySyntaxHighlight($(assistantElement as HTMLElement)); | ||||||
| 
 | 
 | ||||||
|                 console.log(`[${logId}] Successfully updated existing element with ${formattedContent.length} chars of HTML`); |                     console.log(`[${logId}] Successfully updated existing element with final response`); | ||||||
|                 } catch (err) { |                 } catch (err) { | ||||||
|                     console.error(`[${logId}] Error updating existing element:`, err); |                     console.error(`[${logId}] Error updating existing element:`, err); | ||||||
|                     // Fallback to text content if HTML update fails
 |                     // Fallback to text content if HTML update fails
 | ||||||
|                     try { |                     try { | ||||||
|                     assistantElement.textContent = assistantResponse; |                         assistantElement.textContent = finalResponseText; | ||||||
|                         console.log(`[${logId}] Fallback to text content successful`); |                         console.log(`[${logId}] Fallback to text content successful`); | ||||||
|                     } catch (fallbackErr) { |                     } catch (fallbackErr) { | ||||||
|                         console.error(`[${logId}] Even fallback update failed:`, fallbackErr); |                         console.error(`[${logId}] Even fallback update failed:`, fallbackErr); | ||||||
| @ -765,7 +915,28 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             } else { |             } else { | ||||||
|                 console.log(`[${logId}] No existing assistant message element found, creating new one`); |                 console.log(`[${logId}] No existing assistant message element found, creating new one`); | ||||||
|                 try { |                 try { | ||||||
|                 this.addMessageToChat('assistant', assistantResponse); |                     // Create new message element
 | ||||||
|  |                     const messageElement = document.createElement('div'); | ||||||
|  |                     messageElement.className = 'chat-message assistant-message mb-3 d-flex'; | ||||||
|  | 
 | ||||||
|  |                     const avatarElement = document.createElement('div'); | ||||||
|  |                     avatarElement.className = 'message-avatar d-flex align-items-center justify-content-center me-2 assistant-avatar'; | ||||||
|  |                     avatarElement.innerHTML = '<i class="bx bx-bot"></i>'; | ||||||
|  | 
 | ||||||
|  |                     const contentElement = document.createElement('div'); | ||||||
|  |                     contentElement.className = 'message-content p-3 rounded flex-grow-1 assistant-content'; | ||||||
|  | 
 | ||||||
|  |                     // Only show the final response in the message content
 | ||||||
|  |                     contentElement.innerHTML = this.formatMarkdown(finalResponseText) || ''; | ||||||
|  | 
 | ||||||
|  |                     messageElement.appendChild(avatarElement); | ||||||
|  |                     messageElement.appendChild(contentElement); | ||||||
|  | 
 | ||||||
|  |                     this.noteContextChatMessages.appendChild(messageElement); | ||||||
|  | 
 | ||||||
|  |                     // Apply syntax highlighting to any code blocks in the message
 | ||||||
|  |                     applySyntaxHighlight($(contentElement)); | ||||||
|  | 
 | ||||||
|                     console.log(`[${logId}] Successfully added new assistant message`); |                     console.log(`[${logId}] Successfully added new assistant message`); | ||||||
|                 } catch (err) { |                 } catch (err) { | ||||||
|                     console.error(`[${logId}] Error adding new message:`, err); |                     console.error(`[${logId}] Error adding new message:`, err); | ||||||
| @ -780,7 +951,7 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                                 <i class="bx bx-bot"></i> |                                 <i class="bx bx-bot"></i> | ||||||
|                             </div> |                             </div> | ||||||
|                             <div class="message-content p-3 rounded flex-grow-1 assistant-content"> |                             <div class="message-content p-3 rounded flex-grow-1 assistant-content"> | ||||||
|                             ${assistantResponse} |                                 ${finalResponseText} | ||||||
|                             </div> |                             </div> | ||||||
|                         `;
 |                         `;
 | ||||||
|                         this.noteContextChatMessages.appendChild(emergencyElement); |                         this.noteContextChatMessages.appendChild(emergencyElement); | ||||||
| @ -790,6 +961,7 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         // Always try to scroll to the latest content
 |         // Always try to scroll to the latest content
 | ||||||
|         try { |         try { | ||||||
| @ -802,6 +974,131 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Render tool steps as HTML for display in chat | ||||||
|  |      */ | ||||||
|  |     private renderToolStepsHtml(steps: Array<{type: string, name?: string, content: string}>): string { | ||||||
|  |         if (!steps || steps.length === 0) return ''; | ||||||
|  | 
 | ||||||
|  |         let html = ''; | ||||||
|  | 
 | ||||||
|  |         steps.forEach(step => { | ||||||
|  |             let icon, labelClass, content; | ||||||
|  | 
 | ||||||
|  |             switch (step.type) { | ||||||
|  |                 case 'executing': | ||||||
|  |                     icon = 'bx-code-block text-primary'; | ||||||
|  |                     labelClass = ''; | ||||||
|  |                     content = `<div class="d-flex align-items-center">
 | ||||||
|  |                         <i class="bx ${icon} me-1"></i> | ||||||
|  |                         <span>${step.content}</span> | ||||||
|  |                     </div>`;
 | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case 'result': | ||||||
|  |                     icon = 'bx-terminal text-success'; | ||||||
|  |                     labelClass = 'fw-bold'; | ||||||
|  |                     content = `<div class="d-flex align-items-center">
 | ||||||
|  |                         <i class="bx ${icon} me-1"></i> | ||||||
|  |                         <span class="${labelClass}">Tool: ${step.name || 'unknown'}</span> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="mt-1 ps-3">${step.content}</div>`;
 | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case 'error': | ||||||
|  |                     icon = 'bx-error-circle text-danger'; | ||||||
|  |                     labelClass = 'fw-bold text-danger'; | ||||||
|  |                     content = `<div class="d-flex align-items-center">
 | ||||||
|  |                         <i class="bx ${icon} me-1"></i> | ||||||
|  |                         <span class="${labelClass}">Tool: ${step.name || 'unknown'}</span> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="mt-1 ps-3 text-danger">${step.content}</div>`;
 | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case 'generating': | ||||||
|  |                     icon = 'bx-message-dots text-info'; | ||||||
|  |                     labelClass = ''; | ||||||
|  |                     content = `<div class="d-flex align-items-center">
 | ||||||
|  |                         <i class="bx ${icon} me-1"></i> | ||||||
|  |                         <span>${step.content}</span> | ||||||
|  |                     </div>`;
 | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 default: | ||||||
|  |                     icon = 'bx-info-circle text-muted'; | ||||||
|  |                     labelClass = ''; | ||||||
|  |                     content = `<div class="d-flex align-items-center">
 | ||||||
|  |                         <i class="bx ${icon} me-1"></i> | ||||||
|  |                         <span>${step.content}</span> | ||||||
|  |                     </div>`;
 | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             html += `<div class="tool-step my-1">${content}</div>`; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return html; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Extract tool execution steps from the response | ||||||
|  |      */ | ||||||
|  |     private extractToolExecutionSteps(content: string): Array<{type: string, name?: string, content: string}> { | ||||||
|  |         if (!content) return []; | ||||||
|  | 
 | ||||||
|  |         const steps = []; | ||||||
|  | 
 | ||||||
|  |         // Check for executing tools marker
 | ||||||
|  |         if (content.includes('[Executing tools...]')) { | ||||||
|  |             steps.push({ | ||||||
|  |                 type: 'executing', | ||||||
|  |                 content: 'Executing tools...' | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Extract tool results with regex
 | ||||||
|  |         const toolResultRegex = /\[Tool: ([^\]]+)\]([\s\S]*?)(?=\[|$)/g; | ||||||
|  |         let match; | ||||||
|  | 
 | ||||||
|  |         while ((match = toolResultRegex.exec(content)) !== null) { | ||||||
|  |             const toolName = match[1]; | ||||||
|  |             const toolContent = match[2].trim(); | ||||||
|  | 
 | ||||||
|  |             steps.push({ | ||||||
|  |                 type: toolContent.includes('Error:') ? 'error' : 'result', | ||||||
|  |                 name: toolName, | ||||||
|  |                 content: toolContent | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Check for generating response marker
 | ||||||
|  |         if (content.includes('[Generating response with tool results...]')) { | ||||||
|  |             steps.push({ | ||||||
|  |                 type: 'generating', | ||||||
|  |                 content: 'Generating response with tool results...' | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return steps; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Extract the final response without tool execution steps | ||||||
|  |      */ | ||||||
|  |     private extractFinalResponse(content: string): string { | ||||||
|  |         if (!content) return ''; | ||||||
|  | 
 | ||||||
|  |         // Remove all tool execution markers and their content
 | ||||||
|  |         let finalResponse = content | ||||||
|  |             .replace(/\[Executing tools\.\.\.\]\n*/g, '') | ||||||
|  |             .replace(/\[Tool: [^\]]+\][\s\S]*?(?=\[|$)/g, '') | ||||||
|  |             .replace(/\[Generating response with tool results\.\.\.\]\n*/g, ''); | ||||||
|  | 
 | ||||||
|  |         // Trim any extra whitespace
 | ||||||
|  |         finalResponse = finalResponse.trim(); | ||||||
|  | 
 | ||||||
|  |         return finalResponse; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Handle general errors in the send message flow |      * Handle general errors in the send message flow | ||||||
|      */ |      */ | ||||||
| @ -903,40 +1200,22 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|     private showLoadingIndicator() { |     private showLoadingIndicator() { | ||||||
|         const logId = `ui-${Date.now()}`; |         const logId = `ui-${Date.now()}`; | ||||||
|         console.log(`[${logId}] Showing loading indicator and preparing tool execution display`); |         console.log(`[${logId}] Showing loading indicator`); | ||||||
| 
 | 
 | ||||||
|         // Ensure elements exist before trying to modify them
 |         // Ensure the loading indicator element exists
 | ||||||
|         if (!this.loadingIndicator || !this.toolExecutionInfo || !this.toolExecutionSteps) { |         if (!this.loadingIndicator) { | ||||||
|             console.error(`[${logId}] UI elements not properly initialized`); |             console.error(`[${logId}] Loading indicator element not properly initialized`); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Force display of loading indicator
 |         // Show the loading indicator
 | ||||||
|         try { |         try { | ||||||
|             this.loadingIndicator.style.display = 'flex'; |             this.loadingIndicator.style.display = 'flex'; | ||||||
| 
 | 
 | ||||||
|             // Make sure tool execution info area is always visible even before we get the first event
 |             // Force a UI update
 | ||||||
|             // This helps avoid the UI getting stuck in "Processing..." state
 |  | ||||||
|             this.toolExecutionInfo.style.display = 'block'; |  | ||||||
| 
 |  | ||||||
|             // Clear previous tool steps but add a placeholder
 |  | ||||||
|             this.toolExecutionSteps.innerHTML = ` |  | ||||||
|                 <div class="tool-step my-1"> |  | ||||||
|                     <div class="d-flex align-items-center"> |  | ||||||
|                         <i class="bx bx-loader-alt bx-spin text-primary me-1"></i> |  | ||||||
|                         <span>Initializing...</span> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             `;
 |  | ||||||
| 
 |  | ||||||
|             // Force a UI update by accessing element properties
 |  | ||||||
|             const forceUpdate = this.loadingIndicator.offsetHeight; |             const forceUpdate = this.loadingIndicator.offsetHeight; | ||||||
| 
 | 
 | ||||||
|             // Verify display states
 |             console.log(`[${logId}] Loading indicator initialized`); | ||||||
|             console.log(`[${logId}] Loading indicator display state: ${this.loadingIndicator.style.display}`); |  | ||||||
|             console.log(`[${logId}] Tool execution info display state: ${this.toolExecutionInfo.style.display}`); |  | ||||||
| 
 |  | ||||||
|             console.log(`[${logId}] Loading indicator and tool execution area initialized`); |  | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|             console.error(`[${logId}] Error showing loading indicator:`, err); |             console.error(`[${logId}] Error showing loading indicator:`, err); | ||||||
|         } |         } | ||||||
| @ -944,47 +1223,24 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|     private hideLoadingIndicator() { |     private hideLoadingIndicator() { | ||||||
|         const logId = `ui-${Date.now()}`; |         const logId = `ui-${Date.now()}`; | ||||||
|         console.log(`[${logId}] Hiding loading indicator and tool execution area`); |         console.log(`[${logId}] Hiding loading indicator`); | ||||||
| 
 | 
 | ||||||
|         // Ensure elements exist before trying to modify them
 |         // Ensure elements exist before trying to modify them
 | ||||||
|         if (!this.loadingIndicator || !this.toolExecutionInfo) { |         if (!this.loadingIndicator) { | ||||||
|             console.error(`[${logId}] UI elements not properly initialized`); |             console.error(`[${logId}] Loading indicator element not properly initialized`); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Properly reset DOM elements
 |         // Properly reset DOM elements
 | ||||||
|         try { |         try { | ||||||
|             // First hide the tool execution info area
 |             // Hide just the loading indicator but NOT the tool execution info
 | ||||||
|             this.toolExecutionInfo.style.display = 'none'; |             this.loadingIndicator.style.display = 'none'; | ||||||
| 
 | 
 | ||||||
|             // Force a UI update by accessing element properties
 |             // Force a UI update by accessing element properties
 | ||||||
|             const forceUpdate1 = this.toolExecutionInfo.offsetHeight; |             const forceUpdate = this.loadingIndicator.offsetHeight; | ||||||
| 
 | 
 | ||||||
|             // Then hide the loading indicator
 |             // Tool execution info is now independent and may remain visible
 | ||||||
|             this.loadingIndicator.style.display = 'none'; |             console.log(`[${logId}] Loading indicator hidden, tool execution info remains visible if needed`); | ||||||
| 
 |  | ||||||
|             // Force another UI update
 |  | ||||||
|             const forceUpdate2 = this.loadingIndicator.offsetHeight; |  | ||||||
| 
 |  | ||||||
|             // Verify display states immediately
 |  | ||||||
|             console.log(`[${logId}] Loading indicator display state: ${this.loadingIndicator.style.display}`); |  | ||||||
|             console.log(`[${logId}] Tool execution info display state: ${this.toolExecutionInfo.style.display}`); |  | ||||||
| 
 |  | ||||||
|             // Add a delay to double-check that UI updates are complete
 |  | ||||||
|             setTimeout(() => { |  | ||||||
|                 console.log(`[${logId}] Verification after hide timeout: loading indicator display=${this.loadingIndicator.style.display}, tool execution info display=${this.toolExecutionInfo.style.display}`); |  | ||||||
| 
 |  | ||||||
|                 // Force display none again in case something changed it
 |  | ||||||
|                 if (this.loadingIndicator.style.display !== 'none') { |  | ||||||
|                     console.log(`[${logId}] Loading indicator still visible after timeout, forcing hidden`); |  | ||||||
|                     this.loadingIndicator.style.display = 'none'; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if (this.toolExecutionInfo.style.display !== 'none') { |  | ||||||
|                     console.log(`[${logId}] Tool execution info still visible after timeout, forcing hidden`); |  | ||||||
|                     this.toolExecutionInfo.style.display = 'none'; |  | ||||||
|                 } |  | ||||||
|             }, 100); |  | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|             console.error(`[${logId}] Error hiding loading indicator:`, err); |             console.error(`[${logId}] Error hiding loading indicator:`, err); | ||||||
|         } |         } | ||||||
| @ -996,62 +1252,22 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|     private showToolExecutionInfo(toolExecutionData: any) { |     private showToolExecutionInfo(toolExecutionData: any) { | ||||||
|         console.log(`Showing tool execution info: ${JSON.stringify(toolExecutionData)}`); |         console.log(`Showing tool execution info: ${JSON.stringify(toolExecutionData)}`); | ||||||
| 
 | 
 | ||||||
|         // Make sure tool execution info section is visible
 |         // We'll update the in-chat tool execution area in the updateStreamingUI method
 | ||||||
|         this.toolExecutionInfo.style.display = 'block'; |         // This method is now just a legacy hook for the WebSocket handlers
 | ||||||
|         this.loadingIndicator.style.display = 'flex'; // Ensure loading indicator is shown during tool execution
 |  | ||||||
| 
 | 
 | ||||||
|         // Create a new step element to show the tool being executed
 |         // Make sure the loading indicator is shown during tool execution
 | ||||||
|         const stepElement = document.createElement('div'); |         this.loadingIndicator.style.display = 'flex'; | ||||||
|         stepElement.className = 'tool-step my-1'; |  | ||||||
| 
 |  | ||||||
|         // Basic styling for the step
 |  | ||||||
|         let stepHtml = ''; |  | ||||||
| 
 |  | ||||||
|         if (toolExecutionData.action === 'start') { |  | ||||||
|             // Tool execution starting
 |  | ||||||
|             stepHtml = ` |  | ||||||
|                 <div class="d-flex align-items-center"> |  | ||||||
|                     <i class="bx bx-play-circle text-primary me-1"></i> |  | ||||||
|                     <span class="fw-bold">${this.escapeHtml(toolExecutionData.tool || 'Unknown tool')}</span> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="tool-args small text-muted ps-3"> |  | ||||||
|                     ${this.formatToolArgs(toolExecutionData.args || {})} |  | ||||||
|                 </div> |  | ||||||
|             `;
 |  | ||||||
|         } else if (toolExecutionData.action === 'complete') { |  | ||||||
|             // Tool execution completed
 |  | ||||||
|             const resultPreview = this.formatToolResult(toolExecutionData.result); |  | ||||||
|             stepHtml = ` |  | ||||||
|                 <div class="d-flex align-items-center"> |  | ||||||
|                     <i class="bx bx-check-circle text-success me-1"></i> |  | ||||||
|                     <span>${this.escapeHtml(toolExecutionData.tool || 'Unknown tool')} completed</span> |  | ||||||
|                 </div> |  | ||||||
|                 ${resultPreview ? `<div class="tool-result small ps-3 text-muted">${resultPreview}</div>` : ''} |  | ||||||
|             `;
 |  | ||||||
|         } else if (toolExecutionData.action === 'error') { |  | ||||||
|             // Tool execution error
 |  | ||||||
|             stepHtml = ` |  | ||||||
|                 <div class="d-flex align-items-center"> |  | ||||||
|                     <i class="bx bx-error-circle text-danger me-1"></i> |  | ||||||
|                     <span class="text-danger">${this.escapeHtml(toolExecutionData.tool || 'Unknown tool')} error</span> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="tool-error small text-danger ps-3"> |  | ||||||
|                     ${this.escapeHtml(toolExecutionData.error || 'Unknown error')} |  | ||||||
|                 </div> |  | ||||||
|             `;
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         if (stepHtml) { |     /** | ||||||
|             stepElement.innerHTML = stepHtml; |      * Show thinking state in the UI | ||||||
|             this.toolExecutionSteps.appendChild(stepElement); |      */ | ||||||
|  |     private showThinkingState(thinkingData: string) { | ||||||
|  |         // Thinking state is now updated via the in-chat UI in updateStreamingUI
 | ||||||
|  |         // This method is now just a legacy hook for the WebSocket handlers
 | ||||||
| 
 | 
 | ||||||
|             // Scroll to bottom of tool execution steps
 |         // Show the loading indicator
 | ||||||
|             this.toolExecutionSteps.scrollTop = this.toolExecutionSteps.scrollHeight; |         this.loadingIndicator.style.display = 'flex'; | ||||||
| 
 |  | ||||||
|             console.log(`Added new tool execution step to UI`); |  | ||||||
|         } else { |  | ||||||
|             console.log(`No HTML generated for tool execution data:`, toolExecutionData); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1081,49 +1297,6 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|             .join(', '); |             .join(', '); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Format tool results for display |  | ||||||
|      */ |  | ||||||
|     private formatToolResult(result: any): string { |  | ||||||
|         if (result === undefined || result === null) return ''; |  | ||||||
| 
 |  | ||||||
|         // Try to format as JSON if it's an object
 |  | ||||||
|         if (typeof result === 'object') { |  | ||||||
|             try { |  | ||||||
|                 // Get a preview of structured data
 |  | ||||||
|                 const entries = Object.entries(result); |  | ||||||
|                 if (entries.length === 0) return 'Empty result'; |  | ||||||
| 
 |  | ||||||
|                 // Just show first 2 key-value pairs if there are many
 |  | ||||||
|                 const preview = entries.slice(0, 2).map(([key, val]) => { |  | ||||||
|                     let valPreview; |  | ||||||
|                     if (typeof val === 'string') { |  | ||||||
|                         valPreview = val.length > 30 ? `"${val.substring(0, 27)}..."` : `"${val}"`; |  | ||||||
|                     } else if (Array.isArray(val)) { |  | ||||||
|                         valPreview = `[${val.length} items]`; |  | ||||||
|                     } else if (typeof val === 'object' && val !== null) { |  | ||||||
|                         valPreview = '{...}'; |  | ||||||
|                     } else { |  | ||||||
|                         valPreview = String(val); |  | ||||||
|                     } |  | ||||||
|                     return `${key}: ${valPreview}`; |  | ||||||
|                 }).join(', '); |  | ||||||
| 
 |  | ||||||
|                 return entries.length > 2 ? `${preview}, ... (${entries.length} properties)` : preview; |  | ||||||
|             } catch (e) { |  | ||||||
|                 return String(result).substring(0, 100) + (String(result).length > 100 ? '...' : ''); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // For string results
 |  | ||||||
|         if (typeof result === 'string') { |  | ||||||
|             return result.length > 100 ? result.substring(0, 97) + '...' : result; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Default formatting
 |  | ||||||
|         return String(result).substring(0, 100) + (String(result).length > 100 ? '...' : ''); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Simple HTML escaping for safer content display |      * Simple HTML escaping for safer content display | ||||||
|      */ |      */ | ||||||
| @ -1200,26 +1373,6 @@ export default class LlmChatPanel extends BasicWidget { | |||||||
|         return processedContent; |         return processedContent; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Show thinking state in the UI |  | ||||||
|      */ |  | ||||||
|     private showThinkingState(thinkingData: string) { |  | ||||||
|         // Update the UI to show thinking indicator
 |  | ||||||
|         const thinking = typeof thinkingData === 'string' ? thinkingData : 'Thinking...'; |  | ||||||
|         const toolExecutionStep = document.createElement('div'); |  | ||||||
|         toolExecutionStep.className = 'tool-step my-1'; |  | ||||||
|         toolExecutionStep.innerHTML = ` |  | ||||||
|             <div class="d-flex align-items-center"> |  | ||||||
|                 <i class="bx bx-bulb text-warning me-1"></i> |  | ||||||
|                 <span>${this.escapeHtml(thinking)}</span> |  | ||||||
|             </div> |  | ||||||
|         `;
 |  | ||||||
| 
 |  | ||||||
|         this.toolExecutionInfo.style.display = 'block'; |  | ||||||
|         this.toolExecutionSteps.appendChild(toolExecutionStep); |  | ||||||
|         this.toolExecutionSteps.scrollTop = this.toolExecutionSteps.scrollHeight; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Validate embedding providers configuration |      * Validate embedding providers configuration | ||||||
|      * Check if there are issues with the embedding providers that might affect LLM functionality |      * Check if there are issues with the embedding providers that might affect LLM functionality | ||||||
|  | |||||||
| @ -43,6 +43,55 @@ | |||||||
|     border: 1px solid var(--subtle-border-color, var(--main-border-color)); |     border: 1px solid var(--subtle-border-color, var(--main-border-color)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Tool Execution Styling */ | ||||||
|  | .tool-execution-info { | ||||||
|  |     margin-top: 0.75rem; | ||||||
|  |     margin-bottom: 1.5rem; | ||||||
|  |     border: 1px solid var(--subtle-border-color); | ||||||
|  |     border-radius: 0.5rem; | ||||||
|  |     overflow: hidden; | ||||||
|  |     box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); | ||||||
|  |     background-color: var(--main-background-color); | ||||||
|  |     /* Add a subtle transition effect */ | ||||||
|  |     transition: all 0.2s ease-in-out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-execution-status { | ||||||
|  |     background-color: var(--accented-background-color, rgba(0, 0, 0, 0.03)) !important; | ||||||
|  |     border-radius: 0 !important; | ||||||
|  |     padding: 0.5rem !important; | ||||||
|  |     max-height: 250px !important; | ||||||
|  |     overflow-y: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-execution-status .d-flex { | ||||||
|  |     border-bottom: 1px solid var(--subtle-border-color); | ||||||
|  |     padding-bottom: 0.5rem; | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-step { | ||||||
|  |     padding: 0.5rem; | ||||||
|  |     margin-bottom: 0.75rem; | ||||||
|  |     border-radius: 0.375rem; | ||||||
|  |     background-color: var(--main-background-color); | ||||||
|  |     border: 1px solid var(--subtle-border-color); | ||||||
|  |     transition: background-color 0.2s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-step:hover { | ||||||
|  |     background-color: rgba(0, 0, 0, 0.01); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-step:last-child { | ||||||
|  |     margin-bottom: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Make error text more visible */ | ||||||
|  | .text-danger { | ||||||
|  |     color: #dc3545 !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* Sources Styling */ | /* Sources Styling */ | ||||||
| .sources-container { | .sources-container { | ||||||
|     background-color: var(--accented-background-color, var(--main-background-color)); |     background-color: var(--accented-background-color, var(--main-background-color)); | ||||||
| @ -117,3 +166,30 @@ | |||||||
|     padding: 1rem; |     padding: 1rem; | ||||||
|     color: var(--muted-text-color); |     color: var(--muted-text-color); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /* Tool Execution in Chat Styling */ | ||||||
|  | .chat-tool-execution { | ||||||
|  |     padding: 0 0 0 36px; /* Aligned with message content, accounting for avatar width */ | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-execution-container { | ||||||
|  |     background-color: var(--accented-background-color, rgba(245, 247, 250, 0.7)); | ||||||
|  |     border: 1px solid var(--subtle-border-color); | ||||||
|  |     border-radius: 0.375rem; | ||||||
|  |     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); | ||||||
|  |     overflow: hidden; | ||||||
|  |     max-width: calc(100% - 20px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-execution-header { | ||||||
|  |     border-bottom: 1px solid var(--subtle-border-color); | ||||||
|  |     padding-bottom: 0.5rem; | ||||||
|  |     color: var(--muted-text-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tool-execution-chat-steps { | ||||||
|  |     padding: 0.5rem; | ||||||
|  |     max-height: 300px; | ||||||
|  |     overflow-y: auto; | ||||||
|  | }  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 perf3ct
						perf3ct