mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 19:49:01 +01:00 
			
		
		
		
	clean up silly chat_widget that was in the wrong place
This commit is contained in:
		
							parent
							
								
									f6afb1d963
								
							
						
					
					
						commit
						492c05bad4
					
				| @ -1,9 +1,3 @@ | ||||
| import options from "../../../services/options.js"; | ||||
| import ChatWidget from "../widgets/llm/chat_widget.js"; | ||||
| import TabContext from "../widgets/right_panel_tabs.js"; | ||||
| import rightPaneTabManager from "./right_pane_tab_manager.js"; | ||||
| import keyboardActionsService from "./keyboard_actions.js"; | ||||
| 
 | ||||
| function initComponents() { | ||||
|     // ... existing code ...
 | ||||
| 
 | ||||
| @ -12,50 +6,6 @@ function initComponents() { | ||||
|     // ... existing code ...
 | ||||
| } | ||||
| 
 | ||||
| function addChatTab() { | ||||
|     if (!options.getOptionBool('aiEnabled')) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const chatWidget = new ChatWidget(); | ||||
| 
 | ||||
|     // Add chat widget to the right pane
 | ||||
|     const chatTab = new TabContext("AI Chat", chatWidget); | ||||
|     chatTab.renderTitle = title => { | ||||
|         return $(`<span class="tab-title"><span class="bx bx-bot"></span> ${title}</span>`); | ||||
|     }; | ||||
| 
 | ||||
|     rightPaneTabManager.addTabContext(chatTab); | ||||
| 
 | ||||
|     // Add chat button to the global menu
 | ||||
|     const $button = $('<button class="button-widget global-menu-button" title="Open AI Chat (Ctrl+Shift+C)"><span class="bx bx-chat"></span></button>'); | ||||
| 
 | ||||
|     $button.on('click', () => { | ||||
|         chatTab.activate(); | ||||
|         chatWidget.refresh(); | ||||
|     }); | ||||
| 
 | ||||
|     $button.insertBefore($('.global-menu-button:first')); // Add to the beginning of global menu
 | ||||
| 
 | ||||
|     // Add keyboard shortcut
 | ||||
|     keyboardActionsService.setupActionsForScope('window', { | ||||
|         'openAiChat': { | ||||
|             'enabled': true, | ||||
|             'title': 'Open AI Chat', | ||||
|             'clickNote': true, | ||||
|             'shortcutKeys': { | ||||
|                 'keyCode': 'C', | ||||
|                 'ctrlKey': true, | ||||
|                 'shiftKey': true | ||||
|             }, | ||||
|             'handler': () => { | ||||
|                 chatTab.activate(); | ||||
|                 chatWidget.refresh(); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // Export the functions to make them available to other modules
 | ||||
| export default { | ||||
|     initComponents, | ||||
|  | ||||
| @ -1,365 +0,0 @@ | ||||
| import TabAwareWidget from "../tab_aware_widget.js"; | ||||
| import chatService from "../../../../services/llm/chat_service.js"; | ||||
| import options from "../../services/options.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <div class="chat-widget"> | ||||
|     <div class="chat-header"> | ||||
|         <div class="chat-title"></div> | ||||
|         <div class="chat-actions"> | ||||
|             <button class="btn btn-sm chat-new-btn" title="New Chat"> | ||||
|                 <span class="bx bx-plus"></span> | ||||
|             </button> | ||||
|             <button class="btn btn-sm chat-options-btn" title="Chat Options"> | ||||
|                 <span class="bx bx-cog"></span> | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="chat-messages"></div> | ||||
|     <div class="chat-controls"> | ||||
|         <div class="chat-input-container"> | ||||
|             <textarea class="chat-input form-control" placeholder="Type your message here..." rows="2"></textarea> | ||||
|         </div> | ||||
|         <div class="chat-buttons"> | ||||
|             <button class="btn btn-primary btn-sm chat-send-btn" title="Send Message"> | ||||
|                 <span class="bx bx-send"></span> | ||||
|             </button> | ||||
|             <button class="btn btn-outline-secondary btn-sm chat-add-context-btn" title="Add current note as context"> | ||||
|                 <span class="bx bx-link"></span> | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| `;
 | ||||
| 
 | ||||
| const MESSAGE_TPL = ` | ||||
| <div class="chat-message"> | ||||
|     <div class="chat-message-avatar"> | ||||
|         <span class="bx"></span> | ||||
|     </div> | ||||
|     <div class="chat-message-content"></div> | ||||
| </div> | ||||
| `;
 | ||||
| 
 | ||||
| export default class ChatWidget extends TabAwareWidget { | ||||
|     constructor() { | ||||
|         super(); | ||||
| 
 | ||||
|         this.activeSessionId = null; | ||||
|         this.$widget = $(TPL); | ||||
|         this.$title = this.$widget.find('.chat-title'); | ||||
|         this.$messagesContainer = this.$widget.find('.chat-messages'); | ||||
|         this.$input = this.$widget.find('.chat-input'); | ||||
|         this.$sendBtn = this.$widget.find('.chat-send-btn'); | ||||
|         this.$newChatBtn = this.$widget.find('.chat-new-btn'); | ||||
|         this.$optionsBtn = this.$widget.find('.chat-options-btn'); | ||||
|         this.$addContextBtn = this.$widget.find('.chat-add-context-btn'); | ||||
| 
 | ||||
|         this.initialized = false; | ||||
|         this.isActiveTab = false; | ||||
|     } | ||||
| 
 | ||||
|     isEnabled() { | ||||
|         return options.getOptionBool('aiEnabled'); | ||||
|     } | ||||
| 
 | ||||
|     doRender() { | ||||
|         this.$widget.on('click', '.chat-send-btn', async () => { | ||||
|             if (!this.activeSessionId) return; | ||||
| 
 | ||||
|             const message = this.$input.val().trim(); | ||||
|             if (!message) return; | ||||
| 
 | ||||
|             this.$input.val(''); | ||||
|             this.$input.prop('disabled', true); | ||||
|             this.$sendBtn.prop('disabled', true); | ||||
| 
 | ||||
|             await this.sendMessage(message); | ||||
| 
 | ||||
|             this.$input.prop('disabled', false); | ||||
|             this.$sendBtn.prop('disabled', false); | ||||
|             this.$input.focus(); | ||||
|         }); | ||||
| 
 | ||||
|         this.$input.on('keydown', async e => { | ||||
|             if (e.key === 'Enter' && !e.shiftKey) { | ||||
|                 e.preventDefault(); | ||||
|                 this.$sendBtn.click(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         this.$newChatBtn.on('click', async () => { | ||||
|             await this.startNewChat(); | ||||
|         }); | ||||
| 
 | ||||
|         this.$addContextBtn.on('click', async () => { | ||||
|             if (!this.activeSessionId || !this.noteId) return; | ||||
| 
 | ||||
|             this.$input.prop('disabled', true); | ||||
|             this.$sendBtn.prop('disabled', true); | ||||
|             this.$addContextBtn.prop('disabled', true); | ||||
| 
 | ||||
|             await this.addNoteContext(); | ||||
| 
 | ||||
|             this.$input.prop('disabled', false); | ||||
|             this.$sendBtn.prop('disabled', false); | ||||
|             this.$addContextBtn.prop('disabled', false); | ||||
|         }); | ||||
| 
 | ||||
|         this.$optionsBtn.on('click', () => { | ||||
|             this.triggerEvent('openOptions'); | ||||
|         }); | ||||
| 
 | ||||
|         return this.$widget; | ||||
|     } | ||||
| 
 | ||||
|     async refresh() { | ||||
|         if (!this.isEnabled()) { | ||||
|             this.toggleVisibility(false); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.toggleVisibility(true); | ||||
| 
 | ||||
|         if (!this.initialized) { | ||||
|             await this.initialize(); | ||||
|         } | ||||
| 
 | ||||
|         if (this.activeSessionId) { | ||||
|             await this.loadSession(this.activeSessionId); | ||||
|         } else { | ||||
|             await this.startNewChat(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     toggleVisibility(show) { | ||||
|         this.$widget.toggleClass('d-none', !show); | ||||
|     } | ||||
| 
 | ||||
|     async initialize() { | ||||
|         // Load last or create new chat session
 | ||||
|         const sessions = await chatService.getAllSessions(); | ||||
| 
 | ||||
|         if (sessions.length > 0) { | ||||
|             // Use the most recent session
 | ||||
|             this.activeSessionId = sessions[0].id; | ||||
|             await this.loadSession(this.activeSessionId); | ||||
|         } else { | ||||
|             await this.startNewChat(); | ||||
|         } | ||||
| 
 | ||||
|         this.initialized = true; | ||||
|     } | ||||
| 
 | ||||
|     async loadSession(sessionId) { | ||||
|         try { | ||||
|             const session = await chatService.getOrCreateSession(sessionId); | ||||
|             this.activeSessionId = session.id; | ||||
| 
 | ||||
|             // Update title
 | ||||
|             this.$title.text(session.title || 'New Chat'); | ||||
| 
 | ||||
|             // Clear and reload messages
 | ||||
|             this.$messagesContainer.empty(); | ||||
| 
 | ||||
|             for (const message of session.messages) { | ||||
|                 this.addMessageToUI(message); | ||||
|             } | ||||
| 
 | ||||
|             // Scroll to bottom
 | ||||
|             this.scrollToBottom(); | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             console.error('Failed to load chat session:', error); | ||||
|             await this.startNewChat(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async startNewChat() { | ||||
|         try { | ||||
|             const session = await chatService.createSession(); | ||||
|             this.activeSessionId = session.id; | ||||
| 
 | ||||
|             // Update title
 | ||||
|             this.$title.text(session.title || 'New Chat'); | ||||
| 
 | ||||
|             // Clear messages
 | ||||
|             this.$messagesContainer.empty(); | ||||
| 
 | ||||
|             // Add welcome message
 | ||||
|             const welcomeMessage = { | ||||
|                 role: 'assistant', | ||||
|                 content: 'Hello! How can I assist you today?' | ||||
|             }; | ||||
| 
 | ||||
|             this.addMessageToUI(welcomeMessage); | ||||
| 
 | ||||
|             // Focus input
 | ||||
|             this.$input.focus(); | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             console.error('Failed to create new chat session:', error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async sendMessage(content) { | ||||
|         if (!this.activeSessionId) return; | ||||
| 
 | ||||
|         // Add user message to UI immediately
 | ||||
|         const userMessage = { role: 'user', content }; | ||||
|         this.addMessageToUI(userMessage); | ||||
| 
 | ||||
|         // Prepare for streaming response
 | ||||
|         const $assistantMessage = this.createEmptyAssistantMessage(); | ||||
| 
 | ||||
|         // Send to service with streaming callback
 | ||||
|         try { | ||||
|             await chatService.sendMessage( | ||||
|                 this.activeSessionId, | ||||
|                 content, | ||||
|                 null, | ||||
|                 (content, isDone) => { | ||||
|                     // Update the message content as it streams
 | ||||
|                     $assistantMessage.find('.chat-message-content').html(this.formatMessageContent(content)); | ||||
|                     this.scrollToBottom(); | ||||
| 
 | ||||
|                     if (isDone) { | ||||
|                         // Update session title if it changed
 | ||||
|                         chatService.getOrCreateSession(this.activeSessionId).then(session => { | ||||
|                             this.$title.text(session.title); | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             ); | ||||
|         } catch (error) { | ||||
|             console.error('Error sending message:', error); | ||||
| 
 | ||||
|             // Show error in UI if not already shown by streaming
 | ||||
|             $assistantMessage.find('.chat-message-content').html( | ||||
|                 this.formatMessageContent(`Error: Failed to generate response. ${error.message || 'Please check AI settings and try again.'}`) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         this.scrollToBottom(); | ||||
|     } | ||||
| 
 | ||||
|     async addNoteContext() { | ||||
|         if (!this.activeSessionId || !this.noteId) return; | ||||
| 
 | ||||
|         try { | ||||
|             // Show loading message
 | ||||
|             const $loadingMessage = this.createEmptyAssistantMessage(); | ||||
|             $loadingMessage.find('.chat-message-content').html('Loading note context...'); | ||||
| 
 | ||||
|             await chatService.addNoteContext(this.activeSessionId, this.noteId); | ||||
| 
 | ||||
|             // Remove loading message
 | ||||
|             $loadingMessage.remove(); | ||||
| 
 | ||||
|             // Reload the session to show updated messages
 | ||||
|             await this.loadSession(this.activeSessionId); | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             console.error('Error adding note context:', error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     addMessageToUI(message) { | ||||
|         const $message = $(MESSAGE_TPL); | ||||
| 
 | ||||
|         // Set avatar icon based on role
 | ||||
|         if (message.role === 'user') { | ||||
|             $message.addClass('chat-message-user'); | ||||
|             $message.find('.chat-message-avatar .bx').addClass('bx-user'); | ||||
|         } else { | ||||
|             $message.addClass('chat-message-assistant'); | ||||
|             $message.find('.chat-message-avatar .bx').addClass('bx-bot'); | ||||
|         } | ||||
| 
 | ||||
|         // Set content
 | ||||
|         $message.find('.chat-message-content').html(this.formatMessageContent(message.content)); | ||||
| 
 | ||||
|         // Add to container
 | ||||
|         this.$messagesContainer.append($message); | ||||
| 
 | ||||
|         // Scroll to bottom
 | ||||
|         this.scrollToBottom(); | ||||
| 
 | ||||
|         return $message; | ||||
|     } | ||||
| 
 | ||||
|     createEmptyAssistantMessage() { | ||||
|         const $message = $(MESSAGE_TPL); | ||||
|         $message.addClass('chat-message-assistant'); | ||||
|         $message.find('.chat-message-avatar .bx').addClass('bx-bot'); | ||||
|         $message.find('.chat-message-content').html('<div class="chat-loading">●●●</div>'); | ||||
| 
 | ||||
|         this.$messagesContainer.append($message); | ||||
|         this.scrollToBottom(); | ||||
| 
 | ||||
|         return $message; | ||||
|     } | ||||
| 
 | ||||
|     formatMessageContent(content) { | ||||
|         if (!content) return ''; | ||||
| 
 | ||||
|         // First extract code blocks to protect them from HTML escaping
 | ||||
|         const codeBlocks = []; | ||||
|         let processedContent = content.replace(/```(\w+)?\n([\s\S]+?)\n```/g, (match, language, code) => { | ||||
|             const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`; | ||||
|             const languageClass = language ? ` language-${language}` : ''; | ||||
|             codeBlocks.push(`<pre class="code${languageClass}"><code>${utils.escapeHtml(code)}</code></pre>`); | ||||
|             return placeholder; | ||||
|         }); | ||||
| 
 | ||||
|         // Escape HTML in the remaining content
 | ||||
|         processedContent = utils.escapeHtml(processedContent); | ||||
| 
 | ||||
|         // Convert inline code - look for backticks that weren't part of a code block
 | ||||
|         processedContent = processedContent.replace(/`([^`]+)`/g, '<code>$1</code>'); | ||||
| 
 | ||||
|         // Convert line breaks
 | ||||
|         processedContent = processedContent.replace(/\n/g, '<br>'); | ||||
| 
 | ||||
|         // Restore code blocks
 | ||||
|         codeBlocks.forEach((block, index) => { | ||||
|             processedContent = processedContent.replace(`__CODE_BLOCK_${index}__`, block); | ||||
|         }); | ||||
| 
 | ||||
|         return processedContent; | ||||
|     } | ||||
| 
 | ||||
|     scrollToBottom() { | ||||
|         this.$messagesContainer.scrollTop(this.$messagesContainer[0].scrollHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param {string} noteId | ||||
|      */ | ||||
|     async noteSwitched(noteId) { | ||||
|         this.noteId = noteId; | ||||
| 
 | ||||
|         if (this.isActiveTab) { | ||||
|             // Only refresh if we're the active tab
 | ||||
|             await this.refresh(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param {boolean} active | ||||
|      */ | ||||
|     activeTabChanged(active) { | ||||
|         this.isActiveTab = active; | ||||
| 
 | ||||
|         if (active) { | ||||
|             this.refresh(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     entitiesReloaded() { | ||||
|         if (this.isActiveTab) { | ||||
|             this.refresh(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 perf3ct
						perf3ct