📜
chat_copy2.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// chat.js - AI Chat Interface with Scope Targeting (function() { console.log("[chat] Loading AI Chat interface..."); // --- Utility Functions --- function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // --- Scope Management Functions --- function getAvailableScopes(activeFile) { if (!activeFile || !window.StorageEditorScopes) { return []; } const parsed = window.StorageEditorScopes.parseScopes(activeFile.content); const scopeNames = new Set(); // Extract base scope name (first part before underscore or hyphen) parsed.scopes.forEach(scope => { const baseName = scope.name.split(/[_-]/)[0]; scopeNames.add(baseName); }); return Array.from(scopeNames).sort(); } function updateScopeSelector(scopeSelector, currentValue) { const files = window.FilesManager ? window.FilesManager.getFiles() : []; const activeFile = files.find(f => f.active); const scopes = getAvailableScopes(activeFile); // Clear existing options except first two while (scopeSelector.options.length > 2) { scopeSelector.remove(2); } // Add scope options scopes.forEach(scope => { const option = document.createElement('option'); option.value = scope; option.textContent = `📦 ${scope}`; scopeSelector.appendChild(option); }); // Restore selection if it still exists if (currentValue && (currentValue === '' || currentValue === '__new__' || scopes.includes(currentValue))) { scopeSelector.value = currentValue; } else { scopeSelector.value = ''; } return scopeSelector.value; } function updateScopeIndicator(scopeIndicator, currentScope) { if (currentScope === '') { scopeIndicator.textContent = 'NO SCOPE'; scopeIndicator.style.background = '#374151'; scopeIndicator.style.color = '#9ca3af'; } else if (currentScope === '__new__') { scopeIndicator.textContent = 'NEW SCOPE'; scopeIndicator.style.background = '#7c3aed'; scopeIndicator.style.color = '#fff'; } else { scopeIndicator.textContent = currentScope.toUpperCase(); scopeIndicator.style.background = '#16a34a'; scopeIndicator.style.color = '#fff'; } } // --- Message Display Functions --- function addUserMessage(messagesContainer, message, currentScope, messagesPlaceholder) { if (messagesPlaceholder && messagesPlaceholder.parentNode) { messagesPlaceholder.remove(); } const userMsg = document.createElement('div'); userMsg.style.cssText = ` margin-bottom: 16px; padding: 12px 16px; background: #1e3a5f; border-radius: 8px; max-width: 80%; margin-left: auto; color: #e0e0e0; font-size: 14px; line-height: 1.5; `; let scopeBadge = ''; if (currentScope) { const badgeColor = currentScope === '__new__' ? '#7c3aed' : '#16a34a'; const badgeText = currentScope === '__new__' ? 'NEW SCOPE' : currentScope; scopeBadge = ` <div style=" display: inline-block; background: ${badgeColor}; color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 700; margin-bottom: 6px; font-family: monospace; ">🎯 ${badgeText}</div><br> `; } userMsg.innerHTML = scopeBadge + escapeHtml(message); messagesContainer.appendChild(userMsg); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function addAIMessage(messagesContainer, message, currentScope) { const aiMsg = document.createElement('div'); aiMsg.style.cssText = ` margin-bottom: 16px; padding: 12px 16px; background: #2a2a2a; border-radius: 8px; max-width: 80%; margin-right: auto; color: #e0e0e0; font-size: 14px; line-height: 1.5; `; aiMsg.textContent = `🤖 ${message} ${currentScope ? `(working on scope: ${currentScope})` : ''}`; messagesContainer.appendChild(aiMsg); messagesContainer.scrollTop = messagesContainer.scrollHeight; } // --- Tab Switching --- function switchTab(tabName, tabs, contents) { const [chatTab, activeFileTab, filesTab] = tabs; const [chatContent, activeFileContent, filesContent] = contents; tabs.forEach(tab => { tab.style.background = '#1a1a1a'; tab.style.borderBottomColor = 'transparent'; tab.style.color = '#666'; }); contents.forEach(content => { content.style.display = 'none'; }); if (tabName === 'chat') { chatTab.style.background = '#2a2a2a'; chatTab.style.borderBottomColor = '#16a34a'; chatTab.style.color = '#fff'; chatContent.style.display = 'flex'; } else if (tabName === 'activeFile') { activeFileTab.style.background = '#2a2a2a'; activeFileTab.style.borderBottomColor = '#3b82f6'; activeFileTab.style.color = '#fff'; activeFileContent.style.display = 'block'; } else if (tabName === 'files') { filesTab.style.background = '#2a2a2a'; filesTab.style.borderBottomColor = '#8b5cf6'; filesTab.style.color = '#fff'; filesContent.style.display = 'block'; } } // --- AI Chat Slide Configuration --- const aiChatSlide = { title: 'AI Chat', html: ` <div style="display: flex; flex-direction: column; height: 100%;"> <!-- Tab Navigation --> <div style=" display: flex; background: #1a1a1a; border-bottom: 2px solid #2a2a2a; "> <button id="chatTab" class="ai-tab active" style=" flex: 1; padding: 14px 20px; background: #2a2a2a; border: none; border-bottom: 3px solid #16a34a; color: #fff; cursor: pointer; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; ">💬 Chat</button> <button id="activeFileTab" class="ai-tab" style=" flex: 1; padding: 14px 20px; background: #1a1a1a; border: none; border-bottom: 3px solid transparent; color: #666; cursor: pointer; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; ">📄 Active File</button> <button id="filesTab" class="ai-tab" style=" flex: 1; padding: 14px 20px; background: #1a1a1a; border: none; border-bottom: 3px solid transparent; color: #666; cursor: pointer; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; ">📁 Files</button> </div> <!-- Tab Content Container --> <div style="flex: 1; overflow: hidden; display: flex; flex-direction: column;"> <!-- Chat Tab Content --> <div id="chatContent" class="tab-content" style=" flex: 1; display: flex; flex-direction: column; "> <div id="aiChatMessages" style=" flex: 1; overflow-y: auto; padding: 20px; background: #0a0a0a; "> <div id="aiChatMessagesPlaceholder" style="color: #666; text-align: center; padding: 40px;"> 💬 No messages yet. Start a conversation! </div> </div> <div style=" padding: 16px; background: #1a1a1a; border-top: 1px solid #2a2a2a; "> <!-- Scope Selector --> <div style=" display: flex; align-items: center; gap: 12px; margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #2a2a2a; "> <label style=" color: #888; font-size: 13px; font-weight: 600; white-space: nowrap; ">🎯 Target Scope:</label> <select id="scopeSelector" style=" flex: 1; padding: 8px 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 13px; font-family: monospace; cursor: pointer; outline: none; "> <option value="">❌ None (General Chat)</option> <option value="__new__">✨ Create New Scope</option> </select> <div id="scopeIndicator" style=" padding: 6px 12px; background: #374151; border-radius: 4px; color: #9ca3af; font-size: 11px; font-weight: 600; white-space: nowrap; ">NO SCOPE</div> </div> <!-- Message Input --> <div style="display: flex; gap: 8px;"> <textarea id="aiChatInput" placeholder="Type your message..." style=" flex: 1; padding: 12px; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; color: #e0e0e0; font-size: 14px; font-family: 'Segoe UI', sans-serif; resize: vertical; min-height: 60px; max-height: 200px; " ></textarea> <button id="aiChatSend" style=" padding: 12px 24px; background: #16a34a; border: 1px solid #15803d; border-radius: 4px; color: #fff; cursor: pointer; font-size: 14px; font-weight: 600; align-self: flex-end; transition: all 0.2s; " >Send</button> </div> </div> </div> <!-- Active File Tab Content --> <div id="activeFileContent" class="tab-content" style=" flex: 1; overflow-y: auto; padding: 20px; background: #0a0a0a; display: none; "> <div id="aiChatActiveFileContainer"></div> </div> <!-- Files Tab Content --> <div id="filesContent" class="tab-content" style=" flex: 1; overflow-y: auto; padding: 20px; background: #0a0a0a; display: none; "> <div id="aiChatFilesContainer"></div> </div> </div> </div> `, onRender(el) { console.log('[chat] Rendering AI chat interface with tabs'); const chatTab = el.querySelector('#chatTab'); const activeFileTab = el.querySelector('#activeFileTab'); const filesTab = el.querySelector('#filesTab'); const chatContent = el.querySelector('#chatContent'); const activeFileContent = el.querySelector('#activeFileContent'); const filesContent = el.querySelector('#filesContent'); const messagesContainer = el.querySelector('#aiChatMessages'); const activeFileContainer = el.querySelector('#aiChatActiveFileContainer'); const filesContainer = el.querySelector('#aiChatFilesContainer'); const messagesPlaceholder = el.querySelector('#aiChatMessagesPlaceholder'); const input = el.querySelector('#aiChatInput'); const sendBtn = el.querySelector('#aiChatSend'); const scopeSelector = el.querySelector('#scopeSelector'); const scopeIndicator = el.querySelector('#scopeIndicator'); let currentScope = ''; // Initial scope selector population currentScope = updateScopeSelector(scopeSelector, currentScope); updateScopeIndicator(scopeIndicator, currentScope); // Scope selector change handler scopeSelector.addEventListener('change', () => { const value = scopeSelector.value; if (value === '__new__') { const newScopeName = prompt('Enter new scope name (base name only):'); if (newScopeName && newScopeName.trim()) { const baseName = newScopeName.trim().toLowerCase().replace(/[^a-z0-9-]/g, ''); if (baseName) { currentScope = baseName; updateScopeIndicator(scopeIndicator, currentScope); // Add it to the selector const option = document.createElement('option'); option.value = baseName; option.textContent = `📦 ${baseName}`; scopeSelector.appendChild(option); scopeSelector.value = baseName; } else { scopeSelector.value = ''; currentScope = ''; updateScopeIndicator(scopeIndicator, currentScope); } } else { scopeSelector.value = ''; currentScope = ''; updateScopeIndicator(scopeIndicator, currentScope); } } else { currentScope = value; updateScopeIndicator(scopeIndicator, currentScope); } }); // Tab switching const tabs = [chatTab, activeFileTab, filesTab]; const contents = [chatContent, activeFileContent, filesContent]; chatTab.addEventListener('click', () => switchTab('chat', tabs, contents)); activeFileTab.addEventListener('click', () => switchTab('activeFile', tabs, contents)); filesTab.addEventListener('click', () => switchTab('files', tabs, contents)); // Tab hover effects tabs.forEach(tab => { tab.addEventListener('mouseenter', () => { if (tab.style.background === 'rgb(26, 26, 26)') { tab.style.background = '#242424'; } }); tab.addEventListener('mouseleave', () => { if (tab.style.background === 'rgb(36, 36, 36)') { tab.style.background = '#1a1a1a'; } }); }); // Function to update active file display const updateActiveFile = () => { if (activeFileContainer && window.ActiveFileDisplay) { window.ActiveFileDisplay.render(activeFileContainer); } // Update scope selector when file changes currentScope = updateScopeSelector(scopeSelector, currentScope); updateScopeIndicator(scopeIndicator, currentScope); }; // Function to render files list const updateFilesList = () => { if (filesContainer && window.FilesManager) { window.FilesManager.render(filesContainer, updateActiveFile); } }; // Initial renders updateActiveFile(); updateFilesList(); // Listen for file changes window.addEventListener('activeFilesUpdated', () => { updateActiveFile(); updateFilesList(); }); // Send message handler const sendMessage = () => { const message = input.value.trim(); if (!message) return; console.log('[chat] User message:', message); console.log('[chat] Target scope:', currentScope || 'none'); input.value = ''; addUserMessage(messagesContainer, message, currentScope, messagesPlaceholder); // TODO: Add AI API call here with scope context setTimeout(() => { addAIMessage(messagesContainer, 'AI response will go here...', currentScope); }, 500); }; if (sendBtn) { sendBtn.addEventListener('click', sendMessage); sendBtn.addEventListener('mouseenter', () => { sendBtn.style.background = '#15803d'; }); sendBtn.addEventListener('mouseleave', () => { sendBtn.style.background = '#16a34a'; }); } if (input) { input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); input.focus(); } } }; // --- Expose AI Chat API --- window.AIChat = { open: () => { if (window.AppOverlay) { window.AppOverlay.open([aiChatSlide]); } else { console.error('[chat] AppOverlay not available'); } }, slide: aiChatSlide }; // --- Register as AppItems component --- if (window.AppItems) { window.AppItems.push({ title: 'AI Chat', html: aiChatSlide.html, onRender: aiChatSlide.onRender }); console.log('[chat] Registered as AppItems component'); } console.log('[chat] AI Chat interface loaded'); })();