/* * Version: 1.0.0-lite * ChatStudio Lite - Command Handlers * Handles /search, /fetch, /explain, /help, /status, /clear, /modules, etc. */ // Fetch with timeout — AbortController-based, 30s default function _csFetch(url, opts, timeout) { timeout = timeout || 30000; opts = opts || {}; var ctrl = new AbortController(); opts.signal = ctrl.signal; var timer = setTimeout(function() { ctrl.abort(); }, timeout); return fetch(url, opts).finally(function() { clearTimeout(timer); }); } async function handleCommand(cmd) { var parts = cmd.split(' '); var command = parts[0].toLowerCase(); var args = parts.slice(1).join(' '); // === /help === if (command === '/help' || command === '?') { addMessage('ai', '**ChatStudio Lite v' + CHATSTUDIO_VERSION + '**\n\n' + '**Built-in Commands:**\n' + '`/help` or `?` — Show this help\n' + '`/module [name]` — Switch AI module\n' + '`/modules` — List all modules\n' + '`/search [query]` — Web search via DuckDuckGo (with AI overview)\n' + '`/fetch [url]` — Fetch and display a web page\n' + '`/extract [url]` — Extract readable content from a URL\n' + '`/summarize [url]` — AI summarization of a URL\n' + '`/explain [code]` — Get AI code explanation\n' + '`/status` — Check Ollama connection status\n' + '`/test` — Ping the AI API\n' + '`/models` — List all available AI models\n' + '`/clear` or `/purge` — Clear chat history\n\n' + '**Modules:**\n' + 'Modules in the sidebar customize AI behavior. Each module has a system prompt and commands.\n' + 'Use `/module [name]` to switch, or edit them from the sidebar.\n\n' + '**Links:**\n' + 'Full ChatStudio (Pro)\n' + 'User Dashboard\n\n' + 'Type any question to chat with the AI!' ); return; } // === /module or /switch === if (command === '/module' || command === '/switch') { var target = args.toLowerCase(); var found = modules.find(function (m) { return m.id === target || m.name.toLowerCase().includes(target); }); if (found) { switchModule(found.id); addMessage('ai', 'Switched to ' + found.name + ' module!'); } else { addMessage('ai', 'Module not found. Available: ' + modules.map(function (m) { return m.name; }).join(', ')); } return; } // === /modules or /list === if (command === '/modules' || command === '/list' || command === '/mods') { addMessage('ai', '**Available Modules:**\n\n' + modules.map(function (m) { return '- **' + m.name + '** - ' + m.desc; }).join('\n')); return; } // === /explain === if (command === '/explain' && args) { showTyping(); try { var headers = { 'Content-Type': 'application/json' }; if (typeof OLLAMA_API_KEY !== 'undefined' && OLLAMA_API_KEY) { headers['Authorization'] = 'Bearer ' + OLLAMA_API_KEY; } var response = await _csFetch((typeof OLLAMA_URL !== 'undefined' ? OLLAMA_URL : '/ollama') + '/api/generate', { method: 'POST', headers: headers, body: JSON.stringify({ model: document.getElementById('modelSelect').value, prompt: 'Explain this code clearly, line by line, what it does:\n\n' + args, stream: false }) }); hideTyping(); var data = await response.json(); addMessage('ai', '**Code Explanation:**\n\n' + (data.response || 'No response')); } catch (e) { hideTyping(); addMessage('ai', 'Failed to explain code: ' + e.message); } return; } // === /search or /find === if (command === '/search' || command === '/find') { var query = args || cmd.split(' ').slice(1).join(' '); if (!query) { addMessage('ai', 'Usage: /search [query]\n\nExample: /search Python tutorials'); return; } showTyping(); try { var response = await _csFetch('/api/search?q=' + encodeURIComponent(query) + '&summarize=true'); var data = await response.json(); hideTyping(); if (data.error) { addMessage('ai', 'Search failed: ' + data.error); return; } if (data.results && data.results.length > 0) { var source = data.source === 'searxng' ? '🔍 (SearXNG)' : '🔍 (DDG)'; var msg = ''; if (data.ai_overview) { msg += '🌿 **AI Overview:**\n' + data.ai_overview + '\n\n---\n\n'; } msg += source + ' **Search results for "' + query + '":**\n\n'; data.results.slice(0, 10).forEach(function(r) { msg += '• **' + (r.title || 'Untitled') + '**\n'; msg += ' ' + (r.url || '') + '\n'; if (r.content && r.content.length > 5) { msg += ' _' + r.content.substring(0, 200) + (r.content.length > 200 ? '...' : '') + '_\n'; } msg += '\n'; }); if (data.count > 10) { msg += '_... and ' + (data.count - 10) + ' more results_\n'; } addMessage('ai', msg); } else { addMessage('ai', '🔍 No results found for "' + query + '".'); } } catch (e) { hideTyping(); addMessage('ai', 'Search failed: ' + e.message); } return; } // === /fetch or /get === if (command === '/fetch' || command === '/get') { var url = args || (cmd.match(/(https?:\/\/[^\s]+)/) ? cmd.match(/(https?:\/\/[^\s]+)/)[1] : null); if (!url) { addMessage('ai', 'Usage: /fetch [url]\n\nExample: /fetch https://example.com'); return; } showTyping(); try { var extractResp = await _csFetch('/api/extract?url=' + encodeURIComponent(url)); var extractOk = extractResp.ok; var extractData = extractOk ? await extractResp.json() : null; var htmlResp = await _csFetch('/api/fetch?url=' + encodeURIComponent(url)); if (!htmlResp.ok) throw new Error('HTTP ' + htmlResp.status); var html = await htmlResp.text(); var htmlPreview = html.substring(0, 262144) + (html.length > 262144 ? '\n\n... (truncated)' : ''); hideTyping(); var msg = ''; if (extractData && !extractData.error) { msg += '📄 **Fetched:** ' + extractData.url + '\n\n'; if (extractData.title) msg += '**Title:** ' + extractData.title + '\n'; msg += '**Words:** ' + (extractData.word_count || '?') + '\n'; msg += '**Method:** ' + (extractData.method || '?') + '\n'; if (extractData.excerpt) msg += '\n**Preview:**\n' + extractData.excerpt + '\n'; } else { msg += '📄 **Fetched:** ' + url + '\n'; } msg += '\n--- Raw HTML (truncated) ---\n```html\n' + htmlPreview + '\n```'; addMessage('ai', msg); } catch (e) { hideTyping(); addMessage('ai', 'Fetch failed: ' + e.message); } return; } // === /summarize === if (command === '/summarize') { var url = args || (cmd.match(/(https?:\/\/[^\s]+)/) ? cmd.match(/(https?:\/\/[^\s]+)/)[1] : null); if (!url) { addMessage('ai', 'Usage: /summarize [url]\n\nExample: /summarize https://example.com'); return; } showTyping(); try { var response = await _csFetch('/api/summarize?url=' + encodeURIComponent(url)); if (!response.ok) throw new Error('HTTP ' + response.status); var data = await response.json(); hideTyping(); var msg = '🤖 **AI Summary**\n'; if (data.summary) { msg += '\n' + data.summary + '\n'; } msg += '\n_Model: ' + (data.model || 'gemma4:31b-cloud') + ' | Processed in ' + (data.processing_time_ms || '?') + 'ms_'; addMessage('ai', msg); } catch (e) { hideTyping(); addMessage('ai', 'Summarize failed: ' + e.message); } return; } // === /extract === if (command === '/extract') { var url = args || (cmd.match(/(https?:\/\/[^\s]+)/) ? cmd.match(/(https?:\/\/[^\s]+)/)[1] : null); if (!url) { addMessage('ai', 'Usage: /extract [url]\n\nExample: /extract https://example.com'); return; } showTyping(); try { var response = await _csFetch('/api/extract?url=' + encodeURIComponent(url)); if (!response.ok) throw new Error('HTTP ' + response.status); var data = await response.json(); hideTyping(); var msg = '📄 **Extracted:** ' + (data.url || url) + '\n\n'; if (data.title) msg += '**Title:** ' + data.title + '\n'; msg += '**Words:** ' + (data.word_count || '?') + '\n'; msg += '**Method:** ' + (data.method || '?') + '\n'; if (data.excerpt) msg += '\n**Preview:**\n' + data.excerpt + '\n'; if (data.content) msg += '\n**Full Content:** (first 2000 chars)\n' + data.content.substring(0, 2000) + (data.content.length > 2000 ? '...' : '') + '\n'; addMessage('ai', msg); } catch (e) { hideTyping(); addMessage('ai', 'Extract failed: ' + e.message); } return; } // === /status === if (command === '/status') { try { var response = await _csFetch((typeof OLLAMA_URL !== 'undefined' ? OLLAMA_URL : '/ollama') + '/api/tags'); var data = await response.json(); var modelCount = data.models ? data.models.length : 0; var current = document.getElementById('modelSelect').value; addMessage('ai', '**Ollama Status**\n\n- Status: Online\n- Available models: ' + modelCount + '\n- Current model: ' + current + '\n- API: ' + (typeof OLLAMA_URL !== 'undefined' ? OLLAMA_URL : '/ollama')); } catch (e) { addMessage('ai', '**Ollama Status**\n\n- Status: Offline\n- Error: ' + e.message); } return; } // === /test === if (command === '/test') { showTyping(); try { var start = Date.now(); var response = await _csFetch((typeof OLLAMA_URL !== 'undefined' ? OLLAMA_URL : '/ollama') + '/api/tags'); var ms = Date.now() - start; hideTyping(); addMessage('ai', '**Connection Test**\n\n- Response time: ' + ms + 'ms\n- Status: OK'); } catch (e) { hideTyping(); addMessage('ai', '**Connection Test Failed**\n\n- Error: ' + e.message); } return; } // === /models or /list models === if (command === '/models') { try { var response = await _csFetch((typeof OLLAMA_URL !== 'undefined' ? OLLAMA_URL : '/ollama') + '/api/tags'); var data = await response.json(); var list = data.models ? data.models.map(function (m) { return '- ' + m.name; }).join('\n') : 'No models'; addMessage('ai', '**Available Models:**\n\n' + list); } catch (e) { addMessage('ai', 'Failed to list models: ' + e.message); } return; } // === /clear, /purge === if (command === '/clear' || command === '/purge') { clearChat(); return; } // Unknown command addMessage('ai', 'Unknown command. Try /help, /modules, /explain [code], /search [query], /summarize [url], /extract [url], /fetch [url], /status, /test, /models, or /switch [name]'); }