/*
* 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]');
}