/*
* Version: 1.0.0-lite
* ChatStudio Lite - Module System
* Module CRUD, import/export, module list rendering, AI module generator.
* Module format: { id, name, desc, prompt, help, commands }
*/
const DEFAULT_MODULES = [
{
id: 'default',
name: 'AI Chat',
desc: 'General purpose AI assistant',
prompt: 'You are a helpful AI assistant on WebX11 newOS4. Current date is injected dynamically. You have access to these tools:\n\n1. **Search:** /api/search?q=QUERY - Searches the web via DuckDuckGo/SearXNG. Returns results with titles, URLs, and snippets. Use this for current events, facts, and web information.\n2. **Fetch:** /api/fetch?url=URL - Fetches raw HTML from any URL. Use this to read web pages, APIs, or documents.\n3. **Extract:** /api/extract?url=URL - Extracts readable content (title, word count, excerpt, full text) from a URL. Prefer this over /fetch for reading articles.\n4. **Summarize:** /api/summarize?url=URL - AI summarization of a URL.\n\n**Tool-Use Rules:**\n- NEVER announce that you are about to use a tool (no "Hold on...", "Let me search...", "Fetching...", or waiting indicators). Just use the tool and present the result immediately.\n- If a /search returns generic/portal results (e.g., google.com homepages), automatically retry with a more specific query using site: operators or exact phrases in quotes.\n- If a user types a malformed command (e.g., /extraxt instead of /extract, /serch instead of /search), recognize their intent and suggest the correct command immediately: "Did you mean /extract [url]? Here are the available commands: /search, /fetch, /extract, /summarize"\n- When a user shares a URL, proactively suggest /extract or /fetch with the URL pre-filled.\n- Be concise. Deliver results, not commentary about the process.\n\n**Available slash commands:** /help, /search [query], /fetch [url], /extract [url], /summarize [url], /explain [code], /status, /models, /clear',
help: 'Just type any question! I can help with:\n- Current events (I can search the web)\n- Web pages (I can extract/fetch URLs)\n- Coding questions\n- General knowledge\n- Problem solving\n\nCommands: /search, /fetch, /extract, /summarize, /explain, /status, /clear\n\nTry: /search latest news',
commands: '/help, /search, /fetch, /extract, /summarize, /explain, /status, /models, /clear'
},
{
id: 'search',
name: 'Search',
desc: 'Search the web for information',
prompt: 'You are a web search assistant. When asked about current events, recent information, or things you dont know, suggest searching the web. Provide helpful search suggestions.',
help: 'Use /search [query] to get search suggestions.\n\nExample: /search Python tutorials',
commands: '/search, /find'
},
{
id: 'fetch',
name: 'URL Fetch',
desc: 'Fetch content from URLs',
prompt: 'You can fetch content from URLs using the fetch tool. When a user provides a URL, use the fetch tool to get its content.',
help: 'Provide a URL and I can fetch its content.\n\nJust paste any URL in the chat!',
commands: '/fetch, /get'
},
{
id: 'summarize',
name: 'Summarizer',
desc: 'Summarize long text',
prompt: 'You are a text summarization expert. When given text, provide a clear summary highlighting the key points.',
help: 'Paste long text and Ill summarize it.\n\nUse /summary for a brief overview.',
commands: '/summarize, /summary'
},
{
id: 'plan',
name: 'Planner',
desc: 'Create plans and todo lists',
prompt: 'You are a planning assistant. Help users break down tasks into steps, create schedules, and organize work.',
help: 'Tell me what you want to accomplish and Ill help create a plan!\n\nCommands:\n- /plan [goal] - Create a plan\n- /todo - Show current todos',
commands: '/plan, /todo'
},
{
id: 'code',
name: 'Code Helper',
desc: 'Programming assistance',
prompt: 'You are a coding expert. Help with code explanations, debugging, writing, and best practices. Provide clear, working code examples.',
help: 'I can help with:\n- Explaining code\n- Debugging errors\n- Writing new code\n- Code reviews\n\nJust paste your code and ask!',
commands: '/code, /debug, /explain'
},
{
id: 'translate',
name: 'Translator',
desc: 'Translate between languages',
prompt: 'You are a multilingual translator. Translate text between languages accurately while preserving meaning.',
help: 'Translate text by specifying languages:\n\n- /translate [text] to [language]\n- /es Hello - translate to Spanish\n- /en Hola - translate to English',
commands: '/translate, /es, /fr, /de, /zh'
},
{
id: 'webx11-help',
name: 'WebX11 Help',
desc: 'Help with WebX11 Spaces',
prompt: 'You are a WebX11 Spaces help assistant. Help users with:\n- Creating and managing their web space\n- Using the dashboard\n- Uploading files\n- Custom domains\n- Module system\n- Any technical questions',
help: 'WebX11 Spaces Help:\n\n- Dashboard: Upload & manage files\n- Register: Create your space\n- Chat: AI assistant\n- Modules: Specialized tools\n\nType /help for more!',
commands: '/help, /webx11, /space'
}
];
// modules array lives on window.modules (set by _initLiteEngine in module-chatstudio.js)
// Do NOT redeclare here — just reference the global
var currentModule = 'default';
var editingModuleId = null;
var STORAGE_KEY = 'webx11_chat_modules';
function loadModules() {
try {
var saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
modules = JSON.parse(b64_to_utf8(saved));
} else {
modules = JSON.parse(JSON.stringify(DEFAULT_MODULES));
saveModules();
}
} catch (e) {
console.warn('Failed to load modules:', e);
modules = JSON.parse(JSON.stringify(DEFAULT_MODULES));
}
renderModuleList();
updateHelpPanel();
}
function saveModules() {
try {
var encoded = utf8_to_b64(JSON.stringify(modules));
localStorage.setItem(STORAGE_KEY, encoded);
} catch (e) {
console.warn('Failed to save modules:', e);
}
}
function renderModuleList() {
var list = document.getElementById('moduleList');
list.innerHTML = modules.map(function (m) {
var isActive = m.id === currentModule;
var actions = m.id !== 'default'
? '
' +
'' +
'' +
'
'
: '';
return '
' +
'
' + escapeHtml(m.name) + '
' +
'
' + escapeHtml(m.desc) + '
' +
actions +
'
';
}).join('');
}
function getCurrentModule() {
return modules.find(function (m) { return m.id === currentModule; });
}
function switchModule(id) {
currentModule = id;
renderModuleList();
updateHelpPanel();
var found = modules.find(function (m) { return m.id === id; });
showToast('Switched to ' + (found ? found.name : 'module'));
addLog('Module', 'Switched to ' + (found ? found.name : id));
}
function updateHelpPanel() {
var mod = modules.find(function (m) { return m.id === currentModule; });
if (mod) {
document.getElementById('helpTitle').textContent = mod.name;
document.getElementById('helpText').textContent = mod.help || 'No help available.';
var cmds = mod.commands ? mod.commands.split(',').map(function (c) { return c.trim(); }) : [];
document.getElementById('helpCommands').innerHTML = cmds.map(function (c) {
return '' + escapeHtml(c) + '';
}).join('');
}
}
function moduleContextMenu(e, id) {
e.preventDefault();
if (id === 'default') return;
var action = prompt('Module actions: edit, copy, delete (or cancel)');
if (!action) return;
var a = action.toLowerCase().trim();
if (a === 'edit') {
editModule(id);
} else if (a === 'copy') {
copyModule(id);
} else if (a === 'delete') {
deleteModule(id);
}
}
function copyModule(id) {
var mod = modules.find(function (m) { return m.id === id; });
if (!mod) return;
var newId = id + '_copy_' + Date.now();
var newModule = JSON.parse(JSON.stringify(mod));
newModule.id = newId;
newModule.name = mod.name + ' (Copy)';
modules.push(newModule);
saveModules();
renderModuleList();
showToast('Module copied!');
}
function deleteModule(id) {
if (!confirm('Delete this module?')) return;
modules = modules.filter(function (m) { return m.id !== id; });
if (currentModule === id) {
currentModule = 'default';
}
saveModules();
renderModuleList();
updateHelpPanel();
showToast('Module deleted');
}
function editModule(id) {
openModuleModal(id);
}
function openModuleModal(editId) {
editId = editId || null;
editingModuleId = editId;
document.getElementById('moduleModalTitle').textContent = editId ? 'Edit Module' : 'Add Module';
if (editId) {
var mod = modules.find(function (m) { return m.id === editId; });
if (mod) {
document.getElementById('moduleName').value = mod.name;
document.getElementById('moduleDesc').value = mod.desc;
document.getElementById('modulePrompt').value = mod.prompt;
document.getElementById('moduleHelp').value = mod.help;
document.getElementById('moduleCommands').value = mod.commands;
}
} else {
document.getElementById('moduleName').value = '';
document.getElementById('moduleDesc').value = '';
document.getElementById('modulePrompt').value = '';
document.getElementById('moduleHelp').value = '';
document.getElementById('moduleCommands').value = '';
}
document.getElementById('aiModuleDesc').value = '';
document.getElementById('moduleModal').classList.add('open');
}
function closeModuleModal() {
document.getElementById('moduleModal').classList.remove('open');
editingModuleId = null;
}
function saveModule() {
var name = document.getElementById('moduleName').value.trim();
var desc = document.getElementById('moduleDesc').value.trim();
var prompt = document.getElementById('modulePrompt').value.trim();
var help = document.getElementById('moduleHelp').value.trim();
var commands = document.getElementById('moduleCommands').value.trim();
if (!name) {
showToast('Please enter a module name');
return;
}
if (editingModuleId) {
var idx = modules.findIndex(function (m) { return m.id === editingModuleId; });
if (idx !== -1) {
modules[idx] = {
id: modules[idx].id,
name: name,
desc: desc,
prompt: prompt,
help: help,
commands: commands
};
}
} else {
var id = name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
modules.push({
id: id,
name: name,
desc: desc,
prompt: prompt,
help: help,
commands: commands
});
}
saveModules();
renderModuleList();
closeModuleModal();
showToast(editingModuleId ? 'Module updated!' : 'Module added!');
}
// === AI Module Generator ===
async function generateModuleWithAI(evt) {
var e = evt || window.event;
var desc = document.getElementById('aiModuleDesc').value.trim();
if (!desc) {
showToast('Please describe what the module should do');
return;
}
var btn = e ? e.target : null;
if (!btn) {
var modal = document.getElementById('moduleModal');
btn = modal ? modal.querySelector('button[onclick*="generateModule"]') : null;
}
btn.disabled = true;
btn.textContent = 'Generating...';
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: 'Generate a chat module specification for WebX11 AI Chat.\n\nModule description: ' + desc + '\n\nRespond with a JSON object containing:\n{\n "name": "Module Name",\n "desc": "Short description (5-10 words)",\n "prompt": "System prompt/instructions for the AI (2-3 sentences)",\n "help": "How to use this module (3-5 bullet points)",\n "commands": "comma-separated commands, at least 3"\n}\n\nOnly respond with valid JSON, no other text.',
stream: false
})
});
if (!response.ok) throw new Error('AI request failed');
var data = await response.json();
var result;
try {
result = JSON.parse(data.response);
} catch (e) {
showToast('AI response was not valid JSON. Try a different model.');
btn.disabled = false;
btn.textContent = 'Generate with AI';
return;
}
document.getElementById('moduleName').value = result.name || '';
document.getElementById('moduleDesc').value = result.desc || '';
document.getElementById('modulePrompt').value = result.prompt || '';
document.getElementById('moduleHelp').value = result.help || '';
document.getElementById('moduleCommands').value = result.commands || '';
showToast('Module generated! Review and save.');
} catch (e) {
console.error('Generation error:', e);
showToast('Failed to generate: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Generate with AI';
}
}
// === Import / Export ===
function exportModules() {
var data = JSON.stringify(modules, null, 2);
var blob = new Blob([data], { type: 'application/json' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'webx11-modules.json';
a.click();
URL.revokeObjectURL(url);
showToast('Modules exported!');
}
function importModules() {
var input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = async function (e) {
var file = e.target.files[0];
if (!file) return;
if (file.size > 1048576) { showToast('File too large (max 1MB)'); return; }
try {
var text = await file.text();
var imported = JSON.parse(text);
if (!Array.isArray(imported)) { showToast('Invalid format: expected array'); return; }
var valid = [];
for (var i = 0; i < imported.length; i++) {
var m = imported[i];
if (!m || typeof m !== 'object') continue;
if (typeof m.name !== 'string' || m.name.length > 80) continue;
if (typeof m.desc !== 'string' || m.desc.length > 200) continue;
if (typeof m.prompt !== 'string' || m.prompt.length > 10000) continue;
if (m.help && typeof m.help !== 'string') continue;
if (m.commands && typeof m.commands !== 'string') continue;
// Sanitize ID — only allow a-z0-9-
var rawId = typeof m.id === 'string' ? m.id : m.name.toLowerCase().replace(/\s+/g, '-');
var safeId = rawId.toLowerCase().replace(/[^a-z0-9-]/g, '').substring(0, 64);
if (!safeId) safeId = 'imported_' + Date.now();
valid.push({
id: safeId + '_imported',
name: m.name,
desc: m.desc || '',
prompt: m.prompt || '',
help: m.help || '',
commands: m.commands || ''
});
}
if (valid.length === 0) { showToast('No valid modules found in file'); return; }
for (var i = 0; i < valid.length; i++) {
modules.push(valid[i]);
}
saveModules();
renderModuleList();
updateHelpPanel();
showToast(valid.length + ' module' + (valid.length > 1 ? 's' : '') + ' imported!');
} catch (err) {
showToast('Import failed: ' + err.message);
}
};
input.click();
}
// Load modules from storage (runs after core.js)
// Auto-init — DOM is already ready when this script loads dynamically
if (document.getElementById('chat-overlay')) {
loadModules();
}