mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-25 22:12:31 +08: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() {
|
function initComponents() {
|
||||||
// ... existing code ...
|
// ... existing code ...
|
||||||
|
|
||||||
@ -12,50 +6,6 @@ function initComponents() {
|
|||||||
// ... existing code ...
|
// ... 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 the functions to make them available to other modules
|
||||||
export default {
|
export default {
|
||||||
initComponents,
|
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