+
-
`;
+
+ await this.fetchAIConversations({ selectFirst: true });
+ if (this.aiChatState.activeConversationId) {
+ await this.fetchAIMessages(this.aiChatState.activeConversationId);
+ } else {
+ this.updateAIChatMessagesUI();
+ }
+ }
+
+ async fetchAIConversations({ selectFirst = false } = {}) {
+ const container = document.getElementById('ai-chat-conversations');
+ if (container && !this.aiChatState.conversations.length) {
+ container.innerHTML = '
Loading conversations...
';
+ }
+
+ try {
+ const conversations = await this.api.getConversations();
+ this.aiChatState.conversations = conversations;
+
+ if (selectFirst && conversations.length && !this.aiChatState.activeConversationId) {
+ this.aiChatState.activeConversationId = conversations[0].id;
+ }
+
+ if (this.aiChatState.activeConversationId) {
+ const exists = conversations.some(conv => conv.id === this.aiChatState.activeConversationId);
+ if (!exists) {
+ this.aiChatState.activeConversationId = conversations[0]?.id || null;
+ }
+ }
+
+ this.updateAIChatConversationsUI();
+ } catch (error) {
+ console.error('Failed to load AI chat conversations:', error);
+ if (container) {
+ container.innerHTML = `
${this.escapeHtml(error.message || 'Unable to load conversations')}
`;
+ }
+ }
+ }
+
+ updateAIChatConversationsUI() {
+ const container = document.getElementById('ai-chat-conversations');
+ if (!container) return;
+
+ const { conversations, activeConversationId } = this.aiChatState;
+
+ if (!conversations.length) {
+ container.innerHTML = '
No conversations yet. Create one to start chatting.
';
+ return;
+ }
+
+ container.innerHTML = conversations.map(convo => `
+
+
${this.escapeHtml(convo.title || 'Untitled')}
+
${convo.message_count || 0} messages
+
+ `).join('');
+ }
+
+ async selectAIConversation(conversationId) {
+ if (this.aiChatState.activeConversationId === conversationId && !this.aiChatState.loadingMessages) {
+ return;
+ }
+
+ this.aiChatState.activeConversationId = conversationId;
+ this.updateAIChatConversationsUI();
+ await this.fetchAIMessages(conversationId);
+ }
+
+ async createAIConversation() {
+ try {
+ const conversation = await this.api.createConversation('New Conversation');
+ this.aiChatState.conversations = [conversation, ...this.aiChatState.conversations];
+ this.aiChatState.activeConversationId = conversation.id;
+ this.aiChatState.messages = [];
+ this.updateAIChatConversationsUI();
+ this.updateAIChatMessagesUI();
+ const input = document.getElementById('ai-chat-input');
+ if (input) input.focus();
+ return conversation;
+ } catch (error) {
+ console.error('Failed to create AI conversation:', error);
+ const container = document.getElementById('ai-chat-conversations');
+ if (container) {
+ container.insertAdjacentHTML('afterbegin', `
${this.escapeHtml(error.message || 'Unable to create conversation')}
`);
+ }
+ return null;
+ }
+ }
+
+ async fetchAIMessages(conversationId) {
+ const messagesContainer = document.getElementById('ai-chat-messages');
+ if (messagesContainer) {
+ messagesContainer.innerHTML = '
Loading messages...
';
+ }
+
+ if (!conversationId) {
+ this.aiChatState.messages = [];
+ this.updateAIChatMessagesUI();
+ return;
+ }
+
+ this.aiChatState.loadingMessages = true;
+
+ try {
+ const messages = await this.api.getMessages(conversationId);
+ this.aiChatState.messages = messages;
+ this.updateAIChatMessagesUI();
+ } catch (error) {
+ console.error('Failed to load AI chat messages:', error);
+ if (messagesContainer) {
+ messagesContainer.innerHTML = `
${this.escapeHtml(error.message || 'Unable to load messages')}
`;
+ }
+ } finally {
+ this.aiChatState.loadingMessages = false;
+ }
+ }
+
+ updateAIChatMessagesUI() {
+ const container = document.getElementById('ai-chat-messages');
+ if (!container) return;
+
+ const { activeConversationId, messages } = this.aiChatState;
+
+ if (!activeConversationId) {
+ container.innerHTML = '
Select a conversation or create a new one to start chatting.
';
+ return;
+ }
+
+ if (!messages.length) {
+ container.innerHTML = '
No messages yet. Say hello!
';
+ return;
+ }
+
+ container.innerHTML = messages.map(message => `
+
+
+ ${message.role === 'assistant' ? 'AI Assistant' : 'You'}
+
+
+ ${this.escapeHtml(message.content)}
+
+
+ `).join('');
+
+ this.scrollAIChatToBottom();
+ }
+
+ scrollAIChatToBottom() {
+ const container = document.getElementById('ai-chat-messages');
+ if (container) {
+ container.scrollTop = container.scrollHeight;
+ }
}
async sendAIMessage() {
const input = document.getElementById('ai-chat-input');
- const message = input.value.trim();
- if (!message) return;
+ const sendBtn = document.getElementById('ai-chat-send-btn');
+ if (!input) return;
- console.log('Send AI message:', message);
- // TODO: Implement AI chat
- input.value = '';
+ const message = input.value.trim();
+ if (!message || this.aiChatState.sendingMessage) return;
+
+ this.aiChatState.sendingMessage = true;
+ if (sendBtn) {
+ sendBtn.disabled = true;
+ sendBtn.textContent = 'Sending...';
+ }
+
+ let conversationId = this.aiChatState.activeConversationId;
+
+ try {
+ if (!conversationId) {
+ const conversation = await this.createAIConversation();
+ conversationId = conversation?.id;
+ }
+
+ if (!conversationId) {
+ throw new Error('Unable to start a new conversation');
+ }
+
+ // Optimistic user message
+ this.aiChatState.messages = [
+ ...this.aiChatState.messages,
+ {
+ id: `temp-${Date.now()}`,
+ role: 'user',
+ content: message,
+ created_at: new Date().toISOString()
+ }
+ ];
+ this.updateAIChatMessagesUI();
+ input.value = '';
+
+ await this.api.sendMessage(conversationId, message);
+ await this.fetchAIMessages(conversationId);
+ await this.fetchAIConversations();
+ } catch (error) {
+ console.error('Failed to send AI chat message:', error);
+ const messagesContainer = document.getElementById('ai-chat-messages');
+ if (messagesContainer) {
+ messagesContainer.insertAdjacentHTML('beforeend', `
${this.escapeHtml(error.message || 'Failed to send message')}
`);
+ }
+ } finally {
+ this.aiChatState.sendingMessage = false;
+ if (sendBtn) {
+ sendBtn.disabled = false;
+ sendBtn.textContent = 'Send';
+ }
+ }
}
// ===== UTILITY FUNCTIONS =====
+ escapeHtml(value) {
+ if (typeof value !== 'string') return '';
+ const map = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ };
+ return value.replace(/[&<>"']/g, (char) => map[char]);
+ }
+
formatTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();