📜
chat_copy.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// ai_chat_scopes.js - Integration between AI Chat and Block Editor (function() { console.log("[ai_chat_scopes] Loading AI Chat with Scopes integration..."); // --- File Management Functions --- function getFilesFromLocalStorage() { try { return JSON.parse(localStorage.getItem('sftp_active_files') || '[]'); } catch { return []; } } function saveFilesToLocalStorage(files) { try { localStorage.setItem('sftp_active_files', JSON.stringify(files)); } catch (err) { console.error('[ai_chat_scopes] Failed to save files:', err); } } function setFileState(fileName, state) { const files = getFilesFromLocalStorage(); const file = files.find(f => f.name === fileName); if (!file) return; if (state === 'active') { files.forEach(f => { if (f.name === fileName) { f.active = true; f.read = false; } else { f.active = false; } }); } else if (state === 'read') { file.read = true; file.active = false; } else if (state === 'inactive') { file.read = false; file.active = false; } saveFilesToLocalStorage(files); } function renderFilesList(container, onFileChange) { const files = getFilesFromLocalStorage(); container.innerHTML = ''; if (files.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.style.cssText = ` color: #666; text-align: center; padding: 40px; font-size: 14px; `; emptyMsg.textContent = '📁 No files open - open files from Storage Editor'; container.appendChild(emptyMsg); return; } // Header with New File button const header = document.createElement('div'); header.style.cssText = ` margin-bottom: 20px; padding-bottom: 12px; border-bottom: 2px solid #2a2a2a; display: flex; justify-content: space-between; align-items: center; `; const headerLeft = document.createElement('div'); headerLeft.innerHTML = ` <h2 style="margin: 0; color: #e6edf3; font-size: 18px; font-weight: 700;"> 📁 All Files (${files.length}) </h2> <p style="margin: 8px 0 0 0; color: #64748b; font-size: 13px;"> Click file name to set state | Click versions to view history </p> `; const newFileBtn = document.createElement('button'); newFileBtn.style.cssText = ` padding: 10px 20px; background: #16a34a; border: 1px solid #15803d; border-radius: 6px; color: #fff; cursor: pointer; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; `; newFileBtn.textContent = '+ New File'; newFileBtn.addEventListener('click', async () => { await showNewFileDialog(container, onFileChange); }); newFileBtn.addEventListener('mouseenter', () => { newFileBtn.style.background = '#15803d'; }); newFileBtn.addEventListener('mouseleave', () => { newFileBtn.style.background = '#16a34a'; }); header.appendChild(headerLeft); header.appendChild(newFileBtn); container.appendChild(header); // Files grid files.forEach(file => { const fileCard = document.createElement('div'); fileCard.style.cssText = ` background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; padding: 16px; margin-bottom: 12px; transition: all 0.2s; `; // Style based on state if (file.active) { fileCard.style.borderColor = '#16a34a'; fileCard.style.background = 'rgba(22, 163, 74, 0.1)'; } else if (file.read) { fileCard.style.borderColor = '#3b82f6'; fileCard.style.background = 'rgba(59, 130, 246, 0.1)'; } const statusBadge = file.active ? '🟢 ACTIVE' : file.read ? '🔵 READ' : '⚪ INACTIVE'; const statusColor = file.active ? '#16a34a' : file.read ? '#3b82f6' : '#666'; const versionCount = file.versions ? file.versions.length : 0; const fileHeader = document.createElement('div'); fileHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px; cursor: pointer; `; fileHeader.innerHTML = ` <div style=" font-weight: 700; color: #e6edf3; font-size: 15px; font-family: monospace; ">${escapeHtml(file.name)}</div> <div style=" color: ${statusColor}; font-size: 11px; font-weight: 700; padding: 4px 8px; background: rgba(0,0,0,0.3); border-radius: 4px; ">${statusBadge}</div> `; // Click file name to cycle states fileHeader.addEventListener('click', () => { if (!file.active && !file.read) { setFileState(file.name, 'read'); } else if (file.read) { setFileState(file.name, 'active'); } else if (file.active) { setFileState(file.name, 'inactive'); } renderFilesList(container, onFileChange); if (onFileChange) onFileChange(); }); fileCard.appendChild(fileHeader); // Path const pathDiv = document.createElement('div'); pathDiv.style.cssText = ` color: #64748b; font-size: 12px; margin-bottom: 8px; `; pathDiv.textContent = file.path || 'No path'; fileCard.appendChild(pathDiv); // Preview const previewDiv = document.createElement('div'); previewDiv.style.cssText = ` color: #888; font-size: 11px; font-family: monospace; background: #0a0a0a; padding: 8px; border-radius: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px; `; previewDiv.textContent = file.content ? file.content.substring(0, 80) + '...' : '(empty)'; fileCard.appendChild(previewDiv); // Versions section if (versionCount > 0) { const versionsHeader = document.createElement('div'); versionsHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-top: 1px solid #2a2a2a; margin-top: 8px; cursor: pointer; user-select: none; `; versionsHeader.innerHTML = ` <div style="color: #3b82f6; font-size: 12px; font-weight: 600;"> <span class="version-toggle">▶</span> 💾 ${versionCount} Version${versionCount > 1 ? 's' : ''} </div> `; const versionsBody = document.createElement('div'); versionsBody.style.cssText = ` max-height: 0; overflow: hidden; transition: max-height 0.3s ease; margin-top: 8px; `; // Render versions (newest first) const reversedVersions = [...file.versions].reverse(); reversedVersions.forEach((version, idx) => { const realIndex = file.versions.length - 1 - idx; const date = new Date(version.timestamp); const isCurrent = file.content === version.content; const versionItem = document.createElement('div'); versionItem.style.cssText = ` display: flex; align-items: stretch; margin-bottom: 6px; gap: 4px; `; const versionBtn = document.createElement('button'); versionBtn.style.cssText = ` flex: 1; text-align: left; padding: 8px 12px; background: ${isCurrent ? '#1e3a5f' : '#0a0a0a'}; border: 1px solid ${isCurrent ? '#3b82f6' : '#2a2a2a'}; border-radius: 4px; color: #e0e0e0; cursor: ${isCurrent ? 'default' : 'pointer'}; font-size: 12px; transition: all 0.2s; `; versionBtn.innerHTML = ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;"> <span style="font-weight: 700; color: ${isCurrent ? '#3b82f6' : '#fff'};"> ${version.label} ${isCurrent ? '(Current)' : ''} </span> <span style="color: #666; font-size: 10px;">${date.toLocaleString()}</span> </div> <div style="color: #888; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"> ${version.content.substring(0, 60)}${version.content.length > 60 ? '...' : ''} </div> `; if (!isCurrent) { versionBtn.addEventListener('click', () => { if (confirm(`Restore ${version.label}? This will replace the current content.`)) { restoreVersion(file.name, realIndex); renderFilesList(container, onFileChange); if (onFileChange) onFileChange(); } }); versionBtn.addEventListener('mouseenter', () => { versionBtn.style.background = '#1a1a1a'; versionBtn.style.borderColor = '#3a3a3a'; }); versionBtn.addEventListener('mouseleave', () => { versionBtn.style.background = '#0a0a0a'; versionBtn.style.borderColor = '#2a2a2a'; }); } // Delete button (only for non-current versions) const deleteBtn = document.createElement('button'); deleteBtn.style.cssText = ` width: 36px; padding: 8px; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 4px; color: #ef4444; cursor: pointer; font-size: 16px; transition: all 0.2s; display: flex; align-items: center; justify-content: center; `; deleteBtn.innerHTML = '🗑️'; deleteBtn.title = `Delete ${version.label}`; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); if (confirm(`Delete ${version.label}? This cannot be undone.`)) { deleteVersion(file.name, realIndex); renderFilesList(container, onFileChange); if (onFileChange) onFileChange(); } }); deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.background = '#ef4444'; deleteBtn.style.borderColor = '#dc2626'; deleteBtn.style.color = '#fff'; }); deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.background = '#1a1a1a'; deleteBtn.style.borderColor = '#2a2a2a'; deleteBtn.style.color = '#ef4444'; }); versionItem.appendChild(versionBtn); versionItem.appendChild(deleteBtn); versionsBody.appendChild(versionItem); }); // Toggle versions let versionsExpanded = false; versionsHeader.addEventListener('click', () => { versionsExpanded = !versionsExpanded; const toggle = versionsHeader.querySelector('.version-toggle'); if (versionsExpanded) { versionsBody.style.maxHeight = versionsBody.scrollHeight + 'px'; toggle.textContent = '▼'; } else { versionsBody.style.maxHeight = '0'; toggle.textContent = '▶'; } }); fileCard.appendChild(versionsHeader); fileCard.appendChild(versionsBody); } container.appendChild(fileCard); }); } function restoreVersion(fileName, versionIndex) { const files = getFilesFromLocalStorage(); const file = files.find(f => f.name === fileName); if (!file || !file.versions || !file.versions[versionIndex]) return; // Restore the selected version to current content file.content = file.versions[versionIndex].content; saveFilesToLocalStorage(files); // Trigger update event window.dispatchEvent(new Event('activeFilesUpdated')); } function deleteVersion(fileName, versionIndex) { const files = getFilesFromLocalStorage(); const file = files.find(f => f.name === fileName); if (!file || !file.versions || !file.versions[versionIndex]) return; // Remove the version file.versions.splice(versionIndex, 1); // Relabel remaining versions file.versions.forEach((version, idx) => { version.label = `v${idx + 1}`; }); saveFilesToLocalStorage(files); // Trigger update event window.dispatchEvent(new Event('activeFilesUpdated')); } // Show new file dialog with template selection async function showNewFileDialog(container, onFileChange) { // Fetch templates from server via templates.php let templates = []; try { const response = await fetch('templates.php?action=list'); const data = await response.json(); if (data.success && Array.isArray(data.templates)) { templates = data.templates.map(name => ({ name: name, path: name })); console.log('[ai_chat] Found templates:', templates); } else { console.error('[ai_chat] Failed to load templates:', data.error || 'Unknown error'); } } catch (error) { console.error('[ai_chat] Failed to load templates:', error); } // Create dialog overlay const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 2147483647; /* the maximum safe integer for z-index */ `; const dialog = document.createElement('div'); dialog.style.cssText = ` background: #1a1a1a; border: 2px solid #3a3a3a; border-radius: 12px; padding: 24px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; `; dialog.innerHTML = ` <h2 style="margin: 0 0 20px 0; color: #e6edf3; font-size: 20px; font-weight: 700;"> 📄 Create New File </h2> <div style="margin-bottom: 16px;"> <label style="display: block; color: #9ca3af; font-size: 13px; font-weight: 600; margin-bottom: 8px;"> File Name </label> <input id="newFileName" type="text" placeholder="my-file.php" style=" width: 100%; padding: 10px 12px; background: #0a0a0a; border: 1px solid #3a3a3a; border-radius: 6px; color: #e0e0e0; font-size: 14px; font-family: monospace; outline: none; " /> </div> <div style="margin-bottom: 20px;"> <label style="display: block; color: #9ca3af; font-size: 13px; font-weight: 600; margin-bottom: 8px;"> Start from Template </label> <div id="templateList" style=" max-height: 300px; overflow-y: auto; border: 1px solid #3a3a3a; border-radius: 6px; background: #0a0a0a; "> <div class="template-option" data-template="" style=" padding: 12px; border-bottom: 1px solid #2a2a2a; cursor: pointer; transition: background 0.2s; "> <div style="font-weight: 600; color: #e6edf3;">📝 Empty File</div> <div style="font-size: 12px; color: #666; margin-top: 4px;">Start with a blank file</div> </div> ${templates.length === 0 ? ` <div style="padding: 12px; color: #666; font-size: 13px; text-align: center;"> No templates found in /templates folder </div> ` : ''} </div> </div> <div style="display: flex; gap: 12px;"> <button id="createFileBtn" style=" flex: 1; padding: 12px; background: #16a34a; border: 1px solid #15803d; border-radius: 6px; color: #fff; cursor: pointer; font-size: 14px; font-weight: 700; transition: all 0.2s; ">Create File</button> <button id="cancelFileBtn" style=" flex: 1; padding: 12px; background: #374151; border: 1px solid #4b5563; border-radius: 6px; color: #e0e0e0; cursor: pointer; font-size: 14px; font-weight: 700; transition: all 0.2s; ">Cancel</button> </div> `; overlay.appendChild(dialog); document.body.appendChild(overlay); const fileNameInput = dialog.querySelector('#newFileName'); const templateList = dialog.querySelector('#templateList'); const createBtn = dialog.querySelector('#createFileBtn'); const cancelBtn = dialog.querySelector('#cancelFileBtn'); let selectedTemplate = ''; // Add template options templates.forEach(template => { const option = document.createElement('div'); option.className = 'template-option'; option.dataset.template = template.path; option.style.cssText = ` padding: 12px; border-bottom: 1px solid #2a2a2a; cursor: pointer; transition: background 0.2s; `; option.innerHTML = ` <div style="font-weight: 600; color: #e6edf3;">📄 ${escapeHtml(template.name)}</div> <div style="font-size: 11px; color: #666; margin-top: 4px; font-family: monospace;"> ${escapeHtml(template.path)} </div> `; templateList.appendChild(option); }); // Template selection templateList.addEventListener('click', (e) => { const option = e.target.closest('.template-option'); if (!option) return; templateList.querySelectorAll('.template-option').forEach(opt => { opt.style.background = '#0a0a0a'; opt.style.borderLeftWidth = '0'; }); option.style.background = '#1a1a1a'; option.style.borderLeft = '3px solid #16a34a'; selectedTemplate = option.dataset.template; }); // Hover effects for templates templateList.addEventListener('mouseover', (e) => { const option = e.target.closest('.template-option'); if (option && option.style.background !== 'rgb(26, 26, 26)') { option.style.background = '#151515'; } }); templateList.addEventListener('mouseout', (e) => { const option = e.target.closest('.template-option'); if (option && option.style.background !== 'rgb(26, 26, 26)') { option.style.background = '#0a0a0a'; } }); // Create file handler const createFile = async () => { const fileName = fileNameInput.value.trim(); if (!fileName) { alert('Please enter a file name'); return; } const files = getFilesFromLocalStorage(); if (files.find(f => f.name === fileName)) { alert('A file with this name already exists'); return; } let content = ''; // Load template content if selected if (selectedTemplate) { try { const response = await fetch('templates.php?action=read&file=' + encodeURIComponent(selectedTemplate)); const data = await response.json(); if (data.success) { content = data.content; console.log('[ai_chat] Loaded template content from:', selectedTemplate); } else { console.error('[ai_chat] Failed to load template:', data.error); alert('Failed to load template. Starting with empty file.'); } } catch (error) { console.error('[ai_chat] Failed to load template:', error); alert('Failed to load template. Starting with empty file.'); } } // Add new file files.push({ name: fileName, path: fileName, content: content, active: false, read: false, versions: [] }); saveFilesToLocalStorage(files); window.dispatchEvent(new Event('activeFilesUpdated')); // Close dialog document.body.removeChild(overlay); // Refresh file list renderFilesList(container, onFileChange); if (onFileChange) onFileChange(); }; createBtn.addEventListener('click', createFile); cancelBtn.addEventListener('click', () => { document.body.removeChild(overlay); }); // Enter to create fileNameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { createFile(); } }); // Close on overlay click overlay.addEventListener('click', (e) => { if (e.target === overlay) { document.body.removeChild(overlay); } }); // Focus input fileNameInput.focus(); } function renderFileBar(container, onFileChange) { const files = getFilesFromLocalStorage(); container.innerHTML = ''; container.style.cssText = ` display: flex; gap: 8px; padding: 12px; background: #1a1a1a; border-bottom: 1px solid #2a2a2a; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; `; if (files.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.style.cssText = ` color: #666; font-size: 13px; padding: 4px 12px; `; emptyMsg.textContent = 'No files open - open files from Storage Editor'; container.appendChild(emptyMsg); return; } files.forEach(file => { const fileBtn = document.createElement('button'); fileBtn.textContent = file.name; fileBtn.style.cssText = ` padding: 8px 16px; border: 1px solid #3a3a3a; border-radius: 4px; cursor: pointer; font-size: 13px; white-space: nowrap; flex-shrink: 0; transition: all 0.2s; `; if (file.active) { fileBtn.style.background = '#16a34a'; fileBtn.style.color = '#fff'; fileBtn.style.borderColor = '#15803d'; fileBtn.title = 'Active - Click to deactivate'; } else if (file.read) { fileBtn.style.background = '#3b82f6'; fileBtn.style.color = '#fff'; fileBtn.style.borderColor = '#2563eb'; fileBtn.title = 'Read - Click to set as active'; } else { fileBtn.style.background = '#2a2a2a'; fileBtn.style.color = '#888'; fileBtn.style.borderColor = '#3a3a3a'; fileBtn.title = 'Inactive - Click to mark as read'; } fileBtn.addEventListener('click', () => { if (!file.active && !file.read) { setFileState(file.name, 'read'); } else if (file.read) { setFileState(file.name, 'active'); } else if (file.active) { setFileState(file.name, 'inactive'); } renderFileBar(container, onFileChange); if (onFileChange) onFileChange(); }); container.appendChild(fileBtn); }); } // --- Active File Display with Scopes --- function renderActiveFileScopes(container) { const files = getFilesFromLocalStorage(); const activeFile = files.find(f => f.active); container.innerHTML = ''; if (!activeFile) { const emptyMsg = document.createElement('div'); emptyMsg.style.cssText = ` color: #666; text-align: center; padding: 40px; font-size: 14px; `; emptyMsg.textContent = '📄 No active file selected'; container.appendChild(emptyMsg); return; } // Check if StorageEditorScopes is available if (!window.StorageEditorScopes || typeof StorageEditorScopes.buildBlockStructure !== 'function') { const errorMsg = document.createElement('div'); errorMsg.style.cssText = ` color: #ef4444; text-align: center; padding: 40px; font-size: 14px; `; errorMsg.innerHTML = '⚠️ Scopes module not loaded<br><small style="color: #888;">Load storage-scopes.js to see block structure</small>'; container.appendChild(errorMsg); return; } // Build block structure const blocks = StorageEditorScopes.buildBlockStructure(activeFile.content); if (!blocks || blocks.length === 0) { const noScopesMsg = document.createElement('div'); noScopesMsg.style.cssText = ` color: #666; text-align: center; padding: 40px; font-size: 14px; `; noScopesMsg.innerHTML = ` <div style="font-size: 32px; margin-bottom: 12px;">📄</div> <div><strong>${activeFile.name}</strong></div> <div style="margin-top: 8px; font-size: 12px;">No scopes found in this file</div> `; container.appendChild(noScopesMsg); return; } // Wrapper for the entire file context section const wrapper = document.createElement('div'); wrapper.style.cssText = ` background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; margin-bottom: 20px; overflow: hidden; `; // File header (clickable to toggle) const header = document.createElement('div'); header.style.cssText = ` background: #1a1a1a; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #2a2a2a; `; header.innerHTML = ` <div> <div style="display: flex; align-items: center; gap: 8px;"> <span style="color: #16a34a; font-weight: 700; font-size: 16px;"> 📄 ${activeFile.name} </span> <span style="color: #64748b; font-size: 12px;"> ${blocks.length} blocks </span> </div> <div style="color: #64748b; font-size: 11px; margin-top: 4px;"> Active file context (editable) </div> </div> <button id="makeVersionBtn" style=" padding: 8px 16px; background: #3b82f6; border: 1px solid #2563eb; border-radius: 4px; color: #fff; cursor: pointer; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; ">💾 Make Version</button> `; // Content area const contentArea = document.createElement('div'); contentArea.style.cssText = ` overflow: hidden; background: #0a0a0a; padding: 16px; `; // Render blocks blocks.forEach((block, idx) => { const blockId = `ai-chat-block-${idx}`; if (block.type === 'container') { contentArea.appendChild(renderContainerBlockEditable(block, blockId)); } else if (block.type === 'scope') { contentArea.appendChild(renderScopeBlockEditable(block, blockId)); } else if (block.type === 'unmarked') { // Skip empty unmarked blocks const trimmed = block.content.trim(); if (trimmed.length > 0) { contentArea.appendChild(renderUnmarkedBlockEditable(block, blockId)); } } }); wrapper.appendChild(header); wrapper.appendChild(contentArea); container.appendChild(wrapper); // Make Version button handler const makeVersionBtn = header.querySelector('#makeVersionBtn'); if (makeVersionBtn) { makeVersionBtn.addEventListener('click', () => { saveBlocksAsVersion(activeFile, blocks, container); }); makeVersionBtn.addEventListener('mouseenter', () => { makeVersionBtn.style.background = '#2563eb'; }); makeVersionBtn.addEventListener('mouseleave', () => { makeVersionBtn.style.background = '#3b82f6'; }); } } // Save all blocks as a version function saveBlocksAsVersion(activeFile, blocks, containerElement) { // Collect all textarea values const textareas = containerElement.querySelectorAll('.block-content'); const updates = new Map(); textareas.forEach(ta => { const blockId = ta.dataset.blockId; updates.set(blockId, ta.value); }); // Reconstruct file content const lines = []; function processBlock(block, blockId) { const updatedContent = updates.get(blockId); if (block.type === 'container') { lines.push(activeFile.content.split('\n')[block.startLine]); // Opening marker block.children.forEach((child, idx) => { const childId = `${blockId}-child-${idx}`; processBlock(child, childId); }); lines.push(activeFile.content.split('\n')[block.endLine]); // Closing marker } else if (block.type === 'scope') { lines.push(activeFile.content.split('\n')[block.startLine]); // Opening marker lines.push(updatedContent); lines.push(activeFile.content.split('\n')[block.endLine]); // Closing marker } else if (block.type === 'unmarked') { lines.push(updatedContent); } } blocks.forEach((block, idx) => { processBlock(block, `ai-chat-block-${idx}`); }); const newContent = lines.join('\n'); // Add version to file addVersionToFile(activeFile.name, newContent); // Show success message const btn = containerElement.querySelector('#makeVersionBtn'); if (btn) { const originalText = btn.textContent; btn.textContent = '✅ VERSION SAVED'; btn.style.background = '#10b981'; setTimeout(() => { btn.textContent = originalText; btn.style.background = '#3b82f6'; }, 2000); } } // --- Version Management --- function addVersionToFile(fileName, content) { const files = getFilesFromLocalStorage(); const file = files.find(f => f.name === fileName); if (!file) return; // Initialize versions array if it doesn't exist if (!file.versions) { file.versions = []; } // Add new version with timestamp file.versions.push({ content: content, timestamp: Date.now(), label: `v${file.versions.length + 1}` }); // Update current content file.content = content; saveFilesToLocalStorage(files); // Trigger update event window.dispatchEvent(new Event('activeFilesUpdated')); } // Render editable scope block function renderScopeBlockEditable(block, blockId) { const style = window.StorageEditorScopes.getLanguageStyle(block.data.language); const lineRange = `Lines ${block.startLine + 1}-${block.endLine + 1}`; const lineCount = block.content.split('\n').length; const textareaHeight = Math.max(80, lineCount * 22 + 32); const wrapper = document.createElement('div'); wrapper.style.cssText = ` display: flex; margin-bottom: 12px; border-radius: 8px; overflow: hidden; background: ${style.bg}; `; const sidebar = document.createElement('div'); sidebar.className = 'scope-toggle'; sidebar.style.cssText = ` background: ${style.color}; width: 40px; display: flex; flex-direction: column; align-items: center; padding: 12px 0; gap: 8px; flex-shrink: 0; cursor: pointer; user-select: none; `; sidebar.innerHTML = ` <span class="toggle-icon" style="font-size: 12px;">▼</span> <span style="font-size: 20px;">${style.icon}</span> <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-weight: 700; color: #000; font-family: monospace; font-size: 13px; letter-spacing: 1px; ">${escapeHtml(block.data.name)}</div> <div style=" background: rgba(0,0,0,0.2); padding: 4px 6px; border-radius: 4px; font-size: 10px; font-weight: 700; color: #000; ">${style.label}</div> <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; color: rgba(0,0,0,0.6); font-weight: 600; margin-top: auto; ">${lineCount}L</div> `; const content = document.createElement('textarea'); content.className = 'block-content'; content.dataset.blockId = blockId; content.style.cssText = ` flex: 1; height: ${textareaHeight}px; background: #1e1e1e; color: #e6edf3; border: none; border-left: 2px solid ${style.color}; padding: 16px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; line-height: 1.6; resize: none; outline: none; overflow: hidden; transition: max-height 0.3s ease, padding 0.3s ease; `; content.value = block.content; // Toggle functionality let isExpanded = true; sidebar.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = sidebar.querySelector('.toggle-icon'); if (isExpanded) { content.style.maxHeight = 'none'; content.style.padding = '16px'; toggle.textContent = '▼'; } else { content.style.maxHeight = '0'; content.style.padding = '0 16px'; toggle.textContent = '▶'; } }); wrapper.appendChild(sidebar); wrapper.appendChild(content); return wrapper; } // Render editable unmarked block function renderUnmarkedBlockEditable(block, blockId) { const lineCount = block.endLine - block.startLine + 1; const textareaHeight = Math.max(60, lineCount * 20 + 24); const wrapper = document.createElement('div'); wrapper.style.cssText = ` display: flex; margin-bottom: 12px; border-radius: 8px; overflow: hidden; background: rgba(55, 65, 81, 0.05); opacity: 0.7; `; const sidebar = document.createElement('div'); sidebar.className = 'scope-toggle'; sidebar.style.cssText = ` background: #374151; width: 40px; display: flex; flex-direction: column; align-items: center; padding: 12px 0; gap: 8px; flex-shrink: 0; cursor: pointer; user-select: none; `; sidebar.innerHTML = ` <span class="toggle-icon" style="font-size: 12px;">▼</span> <span style="font-size: 18px;">📝</span> <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-weight: 600; color: #9ca3af; font-size: 11px; letter-spacing: 0.5px; ">UNMARKED</div> ${block.container ? ` <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; color: #8b5cf6; font-weight: 600; ">${escapeHtml(block.container)}</div> ` : ''} <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; color: #6b7280; margin-top: auto; ">${lineCount}L</div> `; const content = document.createElement('textarea'); content.className = 'block-content'; content.dataset.blockId = blockId; content.style.cssText = ` flex: 1; height: ${textareaHeight}px; background: #1a1a1a; color: #9ca3af; border: none; border-left: 2px dashed #374151; padding: 12px; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; line-height: 1.5; resize: none; outline: none; overflow: hidden; transition: max-height 0.3s ease, padding 0.3s ease; `; content.value = block.content; // Toggle functionality let isExpanded = true; sidebar.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = sidebar.querySelector('.toggle-icon'); if (isExpanded) { content.style.maxHeight = 'none'; content.style.padding = '12px'; toggle.textContent = '▼'; } else { content.style.maxHeight = '0'; content.style.padding = '0 12px'; toggle.textContent = '▶'; } }); wrapper.appendChild(sidebar); wrapper.appendChild(content); return wrapper; } // Render editable container block function renderContainerBlockEditable(block, blockId) { const lineRange = `Lines ${block.startLine + 1}-${block.endLine + 1}`; const wrapper = document.createElement('div'); wrapper.style.cssText = ` background: rgba(139, 92, 246, 0.05); border: 3px solid #8b5cf6; border-radius: 12px; margin-bottom: 20px; overflow: hidden; `; const header = document.createElement('div'); header.style.cssText = ` background: #7c3aed; padding: 14px 20px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; `; header.innerHTML = ` <div style="font-weight: 700; color: #fff; display: flex; align-items: center; gap: 12px; font-size: 16px;"> <span class="container-toggle">▼</span> <span style="font-size: 20px;">📦</span> <span style="font-family: monospace; text-transform: uppercase; letter-spacing: 0.5px;"> ${escapeHtml(block.data.name)} </span> <span style=" background: rgba(255,255,255,0.2); padding: 3px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; ">${block.children.length} blocks</span> </div> <div style="font-size: 11px; color: rgba(255,255,255,0.7); font-weight: 600;"> ${lineRange} </div> `; const body = document.createElement('div'); body.style.cssText = ` padding: 16px; transition: max-height 0.3s ease; overflow: hidden; `; block.children.forEach((child, idx) => { const childId = `${blockId}-child-${idx}`; if (child.type === 'scope') { body.appendChild(renderScopeBlockEditable(child, childId)); } else if (child.type === 'unmarked') { const trimmed = child.content.trim(); if (trimmed.length > 0) { body.appendChild(renderUnmarkedBlockEditable(child, childId)); } } }); // Toggle functionality let isExpanded = true; header.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = header.querySelector('.container-toggle'); if (isExpanded) { body.style.maxHeight = 'none'; toggle.textContent = '▼'; } else { body.style.maxHeight = '0'; body.style.padding = '0 16px'; toggle.textContent = '▶'; } }); wrapper.appendChild(header); wrapper.appendChild(body); return wrapper; } // Render read-only scope block function renderScopeBlockReadOnly(block, blockId) { const style = window.StorageEditorScopes.getLanguageStyle(block.data.language); const lineRange = `Lines ${block.startLine + 1}-${block.endLine + 1}`; const lineCount = block.content.split('\n').length; const wrapper = document.createElement('div'); wrapper.style.cssText = ` display: flex; margin-bottom: 12px; border-radius: 8px; overflow: hidden; background: ${style.bg}; `; const sidebar = document.createElement('div'); sidebar.className = 'scope-toggle'; sidebar.style.cssText = ` background: ${style.color}; width: 40px; display: flex; flex-direction: column; align-items: center; padding: 12px 0; gap: 8px; flex-shrink: 0; cursor: pointer; user-select: none; `; sidebar.innerHTML = ` <span class="toggle-icon" style="font-size: 12px;">▼</span> <span style="font-size: 20px;">${style.icon}</span> <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-weight: 700; color: #000; font-family: monospace; font-size: 13px; letter-spacing: 1px; ">${escapeHtml(block.data.name)}</div> <div style=" background: rgba(0,0,0,0.2); padding: 4px 6px; border-radius: 4px; font-size: 10px; font-weight: 700; color: #000; ">${style.label}</div> <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; color: rgba(0,0,0,0.6); font-weight: 600; margin-top: auto; ">${lineCount}L</div> `; const content = document.createElement('pre'); content.style.cssText = ` flex: 1; margin: 0; background: #1e1e1e; color: #e6edf3; border: none; border-left: 2px solid ${style.color}; padding: 16px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; transition: max-height 0.3s ease, padding 0.3s ease; `; content.textContent = block.content; // Toggle functionality let isExpanded = true; sidebar.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = sidebar.querySelector('.toggle-icon'); if (isExpanded) { content.style.maxHeight = 'none'; content.style.padding = '16px'; toggle.textContent = '▼'; } else { content.style.maxHeight = '0'; content.style.padding = '0 16px'; toggle.textContent = '▶'; } }); wrapper.appendChild(sidebar); wrapper.appendChild(content); return wrapper; } // Render read-only unmarked block function renderUnmarkedBlockReadOnly(block, blockId) { const lineCount = block.endLine - block.startLine + 1; const wrapper = document.createElement('div'); wrapper.style.cssText = ` display: flex; margin-bottom: 12px; border-radius: 8px; overflow: hidden; background: rgba(55, 65, 81, 0.05); opacity: 0.7; `; const sidebar = document.createElement('div'); sidebar.className = 'scope-toggle'; sidebar.style.cssText = ` background: #374151; width: 40px; display: flex; flex-direction: column; align-items: center; padding: 12px 0; gap: 8px; flex-shrink: 0; cursor: pointer; user-select: none; `; sidebar.innerHTML = ` <span class="toggle-icon" style="font-size: 12px;">▼</span> <span style="font-size: 18px;">📝</span> <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-weight: 600; color: #9ca3af; font-size: 11px; letter-spacing: 0.5px; ">UNMARKED</div> ${block.container ? ` <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; color: #8b5cf6; font-weight: 600; ">${escapeHtml(block.container)}</div> ` : ''} <div style=" writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; color: #6b7280; margin-top: auto; ">${lineCount}L</div> `; const content = document.createElement('pre'); content.style.cssText = ` flex: 1; margin: 0; background: #1a1a1a; color: #9ca3af; border: none; border-left: 2px dashed #374151; padding: 12px; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; line-height: 1.5; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; transition: max-height 0.3s ease, padding 0.3s ease; `; content.textContent = block.content; // Toggle functionality let isExpanded = true; sidebar.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = sidebar.querySelector('.toggle-icon'); if (isExpanded) { content.style.maxHeight = 'none'; content.style.padding = '12px'; toggle.textContent = '▼'; } else { content.style.maxHeight = '0'; content.style.padding = '0 12px'; toggle.textContent = '▶'; } }); wrapper.appendChild(sidebar); wrapper.appendChild(content); return wrapper; } // Render read-only container block function renderContainerBlockReadOnly(block, blockId) { const lineRange = `Lines ${block.startLine + 1}-${block.endLine + 1}`; const wrapper = document.createElement('div'); wrapper.style.cssText = ` background: rgba(139, 92, 246, 0.05); border: 3px solid #8b5cf6; border-radius: 12px; margin-bottom: 20px; overflow: hidden; `; const header = document.createElement('div'); header.style.cssText = ` background: #7c3aed; padding: 14px 20px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; `; header.innerHTML = ` <div style="font-weight: 700; color: #fff; display: flex; align-items: center; gap: 12px; font-size: 16px;"> <span class="container-toggle">▼</span> <span style="font-size: 20px;">📦</span> <span style="font-family: monospace; text-transform: uppercase; letter-spacing: 0.5px;"> ${escapeHtml(block.data.name)} </span> <span style=" background: rgba(255,255,255,0.2); padding: 3px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; ">${block.children.length} blocks</span> </div> <div style="font-size: 11px; color: rgba(255,255,255,0.7); font-weight: 600;"> ${lineRange} </div> `; const body = document.createElement('div'); body.style.cssText = ` padding: 16px; transition: max-height 0.3s ease; overflow: hidden; `; block.children.forEach((child, idx) => { const childId = `${blockId}-child-${idx}`; if (child.type === 'scope') { body.appendChild(renderScopeBlockReadOnly(child, childId)); } else if (child.type === 'unmarked') { const trimmed = child.content.trim(); if (trimmed.length > 0) { body.appendChild(renderUnmarkedBlockReadOnly(child, childId)); } } }); // Toggle functionality let isExpanded = true; header.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = header.querySelector('.container-toggle'); if (isExpanded) { body.style.maxHeight = 'none'; toggle.textContent = '▼'; } else { body.style.maxHeight = '0'; body.style.padding = '0 16px'; toggle.textContent = '▶'; } }); wrapper.appendChild(header); wrapper.appendChild(body); return wrapper; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // --- 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('[ai_chat_scopes] 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 = ''; // Function to get unique scope names from active file function getAvailableScopes() { const files = getFilesFromLocalStorage(); const activeFile = files.find(f => f.active); 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 => { // Get the base name - everything before first _ or - const baseName = scope.name.split(/[_-]/)[0]; scopeNames.add(baseName); }); return Array.from(scopeNames).sort(); } // Function to populate scope selector function updateScopeSelector() { const scopes = getAvailableScopes(); const currentValue = scopeSelector.value; // 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 = ''; currentScope = ''; updateScopeIndicator(); } } // Function to update scope indicator function updateScopeIndicator() { 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'; } } // 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(); // 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(); } } else { scopeSelector.value = ''; currentScope = ''; updateScopeIndicator(); } } else { currentScope = value; updateScopeIndicator(); } }); // Initial scope selector population updateScopeSelector(); // Tab switching function function switchTab(tabName) { const tabs = [chatTab, activeFileTab, filesTab]; const contents = [chatContent, activeFileContent, filesContent]; 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'; } } // Tab click handlers chatTab.addEventListener('click', () => switchTab('chat')); activeFileTab.addEventListener('click', () => switchTab('activeFile')); filesTab.addEventListener('click', () => switchTab('files')); // Tab hover effects [chatTab, activeFileTab, filesTab].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) { renderActiveFileScopes(activeFileContainer); } // Update scope selector when file changes updateScopeSelector(); }; // Function to render files list const updateFilesList = () => { if (filesContainer) { renderFilesList(filesContainer, updateActiveFile); } }; // Initial renders updateActiveFile(); updateFilesList(); // Listen for file changes from Storage Editor window.addEventListener('activeFilesUpdated', () => { updateActiveFile(); updateFilesList(); }); // Send message handler const sendMessage = () => { const message = input.value.trim(); if (!message) return; console.log('[ai_chat_scopes] User message:', message); console.log('[ai_chat_scopes] Target scope:', currentScope || 'none'); input.value = ''; if (messagesPlaceholder && messagesPlaceholder.parentNode) { messagesPlaceholder.remove(); } // Add user message with scope indicator 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; // TODO: Add AI API call here with scope context setTimeout(() => { 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 = `🤖 AI response will go here... ${currentScope ? `(working on scope: ${currentScope})` : ''}`; messagesContainer.appendChild(aiMsg); messagesContainer.scrollTop = messagesContainer.scrollHeight; }, 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.AIChatScopes = { open: () => { if (window.AppOverlay) { window.AppOverlay.open([aiChatSlide]); } else { console.error('[ai_chat_scopes] 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('[ai_chat_scopes] Registered as AppItems component'); } console.log('[ai_chat_scopes] AI Chat with Scopes integration loaded'); })();