@hera-al/server 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/prompt-builder.js +1 -1
- package/dist/agent/session-agent.d.ts +26 -0
- package/dist/agent/session-agent.js +1 -1
- package/dist/commands/model.d.ts +11 -2
- package/dist/commands/model.js +1 -1
- package/dist/commands/models.d.ts +4 -2
- package/dist/commands/models.js +1 -1
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +1 -1
- package/dist/config.d.ts +61 -4
- package/dist/config.js +1 -1
- package/dist/gateway/channels/telegram.js +1 -1
- package/dist/nostromo/nostromo.js +1 -1
- package/dist/nostromo/ui-html-layout.js +1 -1
- package/dist/nostromo/ui-js-agent.js +1 -1
- package/dist/nostromo/ui-js-config.js +1 -1
- package/dist/nostromo/ui-js-core.js +1 -1
- package/dist/nostromo/ui-styles.js +1 -1
- package/dist/pi-agent-provider/index.d.ts +115 -0
- package/dist/pi-agent-provider/index.js +1 -0
- package/dist/pi-agent-provider/integration-example.d.ts +83 -0
- package/dist/pi-agent-provider/integration-example.js +1 -0
- package/dist/pi-agent-provider/pi-context-compactor.d.ts +116 -0
- package/dist/pi-agent-provider/pi-context-compactor.js +1 -0
- package/dist/pi-agent-provider/pi-mcp-bridge.d.ts +38 -0
- package/dist/pi-agent-provider/pi-mcp-bridge.js +1 -0
- package/dist/pi-agent-provider/pi-message-adapter.d.ts +93 -0
- package/dist/pi-agent-provider/pi-message-adapter.js +1 -0
- package/dist/pi-agent-provider/pi-query.d.ts +49 -0
- package/dist/pi-agent-provider/pi-query.js +1 -0
- package/dist/pi-agent-provider/pi-skill-loader.d.ts +15 -0
- package/dist/pi-agent-provider/pi-skill-loader.js +1 -0
- package/dist/pi-agent-provider/pi-tool-adapter.d.ts +105 -0
- package/dist/pi-agent-provider/pi-tool-adapter.js +1 -0
- package/dist/pi-agent-provider/pi-tool-executor.d.ts +95 -0
- package/dist/pi-agent-provider/pi-tool-executor.js +1 -0
- package/dist/pi-agent-provider/pi-types.d.ts +179 -0
- package/dist/pi-agent-provider/pi-types.js +1 -0
- package/dist/server.js +1 -1
- package/dist/stt/stt-loader.js +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export function agentJS(){return"\nvar SA_TOOL_LIST = ['Read','Write','Edit','Bash','Glob','Grep','WebSearch','WebFetch'];\n\nvar _editModelIdx = -1;\n\n/* ---- Models ---- */\nfunction showAddModel(){\n document.getElementById('addModelForm').style.display='';\n // Reset form\n document.getElementById('newModelId').value='';\n document.getElementById('newModelName').value='';\n document.getElementById('newModelBaseURL').value='https://api.openai.com/v1';\n document.getElementById('newModelApiKey').value='';\n document.getElementById('newModelEnvVar').value='';\n document.getElementById('newModelType').value='external';\n document.getElementById('newModelProxy').value='not-used';\n document.getElementById('newModelFastUrl').value='';\n document.getElementById('newModelFastProxyApiKey').value='';\n updateNewModelApiFields();\n}\nfunction hideAddModel(){ document.getElementById('addModelForm').style.display='none'; }\nfunction sanitizeEnvVarInput(el){\n var v = el.value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'');\n if(v !== el.value) el.value = v;\n}\nfunction updateNewModelApiFields(){\n var type = document.getElementById('newModelType').value;\n document.getElementById('newModelApiFields').style.display = type!=='internal' ? '' : 'none';\n var baseField = document.getElementById('newModelBaseURLField');\n if(baseField) baseField.style.display = type==='external' ? '' : 'none';\n var proxyField = document.getElementById('newModelProxyField');\n if(proxyField) proxyField.style.display = type==='external' ? '' : 'none';\n updateNewModelProxyFields();\n}\nfunction updateNewModelProxyFields(){\n var proxy = document.getElementById('newModelProxy').value;\n var enabled = proxy !== 'not-used';\n var fu = document.getElementById('newModelFastUrl');\n var fk = document.getElementById('newModelFastProxyApiKey');\n if(fu) fu.disabled = !enabled;\n if(fk) fk.disabled = !enabled;\n}\nfunction updateEditModelApiFields(){\n var type = document.getElementById('editModelType').value;\n document.getElementById('editModelApiFields').style.display = type!=='internal' ? '' : 'none';\n var baseField = document.getElementById('editModelBaseURLField');\n if(baseField) baseField.style.display = type==='external' ? '' : 'none';\n var proxyField = document.getElementById('editModelProxyField');\n if(proxyField) proxyField.style.display = type==='external' ? '' : 'none';\n updateEditModelProxyFields();\n}\nfunction updateEditModelProxyFields(){\n var proxy = document.getElementById('editModelProxy').value;\n var enabled = proxy !== 'not-used';\n var fu = document.getElementById('editModelFastUrl');\n var fk = document.getElementById('editModelFastProxyApiKey');\n if(fu) fu.disabled = !enabled;\n if(fk) fk.disabled = !enabled;\n}\nasync function loadModels(){\n currentConfig = await fetchAPI('/config');\n if(!currentConfig.models) currentConfig.models=[];\n renderModelsTable();\n}\nfunction renderModelsTable(){\n var models = currentConfig.models||[];\n var tbody = document.getElementById('modelsBody');\n tbody.innerHTML='';\n var hasVisible = false;\n for(var i=0;i<models.length;i++){\n var m = models[i];\n var types = m.types||['external'];\n if(types.indexOf('env-var')!==-1) continue;\n hasVisible = true;\n var typeBadges = types.map(function(t){ return '<span class=\"badge badge-blue\" style=\"font-size:11px;padding:1px 6px;margin-right:2px\">'+esc(t)+'</span>'; }).join('');\n var proxyBadge = (m.proxy && m.proxy !== 'not-used') ? ' <span class=\"badge badge-green\" style=\"font-size:11px;padding:1px 6px\">'+esc(m.proxy)+'</span>' : '';\n tbody.innerHTML += '<tr data-model-idx=\"'+i+'\"><td>'+esc(m.name)+'</td><td style=\"font-family:monospace;font-size:13px\">'+esc(m.id)+'</td><td>'+typeBadges+proxyBadge+'</td><td style=\"white-space:nowrap\"><button class=\"btn-ghost btn-sm\" onclick=\"startEditModel('+i+')\">Edit</button> <button class=\"btn-danger btn-sm\" onclick=\"confirmDeleteModel('+i+')\">Delete</button></td></tr>';\n }\n if(!hasVisible){\n tbody.innerHTML='<tr><td colspan=\"4\" style=\"text-align:center;color:var(--text-muted);padding:20px\">No models in registry</td></tr>';\n }\n // Hide edit form when re-rendering\n document.getElementById('editModelForm').style.display='none';\n _editModelIdx = -1;\n}\nfunction addModel(){\n var id = document.getElementById('newModelId').value.trim();\n var name = document.getElementById('newModelName').value.trim();\n if(!id){ toast('Model ID required','err'); return; }\n if(!name) name = id;\n var type = document.getElementById('newModelType').value;\n var types = [type];\n var needsApi = type!=='internal';\n var useEnvVar = needsApi ? document.getElementById('newModelEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim() : '';\n var apiKey = needsApi ? document.getElementById('newModelApiKey').value.trim() : '';\n var baseURL = type==='external' ? document.getElementById('newModelBaseURL').value.trim() : '';\n var proxy = type==='external' ? document.getElementById('newModelProxy').value : 'not-used';\n var fastUrl = (type==='external' && proxy!=='not-used') ? document.getElementById('newModelFastUrl').value.trim() : '';\n var fastProxyApiKey = (type==='external' && proxy!=='not-used') ? document.getElementById('newModelFastProxyApiKey').value.trim() : '';\n if(!currentConfig.models) currentConfig.models=[];\n currentConfig.models.push({id:id, name:name, types:types, proxy:proxy, fastUrl:fastUrl, fastProxyApiKey:fastProxyApiKey, apiKey:apiKey, baseURL:baseURL, useEnvVar:useEnvVar});\n hideAddModel();\n renderModelsTable();\n populateModelSelects();\n saveConfig();\n}\nvar _deleteModelIdx = -1;\nfunction confirmDeleteModel(idx){\n _deleteModelIdx = idx;\n var models = (currentConfig&¤tConfig.models)||[];\n var name = models[idx] ? models[idx].name : 'this model';\n document.getElementById('modelDeleteName').textContent = name;\n document.getElementById('modelDeleteModal').classList.add('open');\n}\nfunction closeModelDeleteModal(){\n document.getElementById('modelDeleteModal').classList.remove('open');\n _deleteModelIdx = -1;\n}\nfunction doDeleteModel(){\n if(_deleteModelIdx>=0 && currentConfig.models){\n currentConfig.models.splice(_deleteModelIdx,1);\n renderModelsTable();\n populateModelSelects();\n saveConfig();\n }\n closeModelDeleteModal();\n}\nfunction startEditModel(idx){\n if(!currentConfig.models||!currentConfig.models[idx]) return;\n _editModelIdx = idx;\n var m = currentConfig.models[idx];\n document.getElementById('editModelId').value = m.id||'';\n document.getElementById('editModelName').value = m.name||'';\n document.getElementById('editModelBaseURL').value = m.baseURL||'';\n document.getElementById('editModelApiKey').value = m.apiKey||'';\n document.getElementById('editModelEnvVar').value = m.useEnvVar||'';\n document.getElementById('editModelProxy').value = m.proxy||'not-used';\n document.getElementById('editModelFastUrl').value = m.fastUrl||'';\n document.getElementById('editModelFastProxyApiKey').value = m.fastProxyApiKey||'';\n var types = m.types||['external'];\n document.getElementById('editModelType').value = types[0]||'external';\n updateEditModelApiFields();\n // Position edit form after the table\n document.getElementById('editModelForm').style.display='';\n}\nfunction finishEditModel(){\n if(_editModelIdx<0 || !currentConfig.models||!currentConfig.models[_editModelIdx]) return;\n var id = document.getElementById('editModelId').value.trim();\n var name = document.getElementById('editModelName').value.trim();\n if(!id){ toast('Model ID required','err'); return; }\n if(!name) name = id;\n var type = document.getElementById('editModelType').value;\n var types = [type];\n var needsApi = type!=='internal';\n var useEnvVar = needsApi ? document.getElementById('editModelEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim() : '';\n var apiKey = needsApi ? document.getElementById('editModelApiKey').value.trim() : '';\n var baseURL = type==='external' ? document.getElementById('editModelBaseURL').value.trim() : '';\n var proxy = type==='external' ? document.getElementById('editModelProxy').value : 'not-used';\n var fastUrl = (type==='external' && proxy!=='not-used') ? document.getElementById('editModelFastUrl').value.trim() : '';\n var fastProxyApiKey = (type==='external' && proxy!=='not-used') ? document.getElementById('editModelFastProxyApiKey').value.trim() : '';\n currentConfig.models[_editModelIdx] = {id:id, name:name, types:types, proxy:proxy, fastUrl:fastUrl, fastProxyApiKey:fastProxyApiKey, apiKey:apiKey, baseURL:baseURL, useEnvVar:useEnvVar};\n _editModelIdx = -1;\n renderModelsTable();\n populateModelSelects();\n saveConfig();\n}\nfunction cancelEditModel(){\n _editModelIdx = -1;\n document.getElementById('editModelForm').style.display='none';\n}\nfunction populateModelSelects(){\n var models = (currentConfig&¤tConfig.models)||[];\n var internalModels = models.filter(function(m){ var t=m.types||['external']; return t.indexOf('internal')!==-1 || (t.indexOf('external')!==-1 && m.proxy && m.proxy!=='not-used'); });\n var selects = {\n agentModel: {val:'', allowNone:false},\n agentMainFallback: {val:'', allowNone:true}\n };\n for(var key in selects){\n var el = document.getElementById(key);\n if(!el) continue;\n selects[key].val = el.value;\n el.innerHTML='';\n if(selects[key].allowNone) el.innerHTML += '<option value=\"\">None</option>';\n for(var i=0;i<internalModels.length;i++){\n el.innerHTML += '<option value=\"'+esc(internalModels[i].name)+'\">'+esc(internalModels[i].name)+'</option>';\n }\n el.value = selects[key].val;\n }\n populateSTTModelSelect();\n populateMemSearchModelSelect();\n}\nfunction populateSTTModelSelect(){\n var models = (currentConfig&¤tConfig.models)||[];\n var el = document.getElementById('sttModelRef');\n if(!el) return;\n var prev = el.value;\n el.innerHTML = '<option value=\"\">-- select --</option>';\n for(var i=0;i<models.length;i++){\n var types = models[i].types||['external'];\n if(types.indexOf('external')===-1) continue;\n el.innerHTML += '<option value=\"'+esc(models[i].name)+'\">'+esc(models[i].name)+' ('+esc(models[i].id)+')</option>';\n }\n el.value = prev;\n}\nfunction populateMemSearchModelSelect(){\n var models = (currentConfig&¤tConfig.models)||[];\n var el = document.getElementById('memSearchModelRef');\n if(!el) return;\n var prev = el.value;\n el.innerHTML = '<option value=\"\">-- select --</option>';\n for(var i=0;i<models.length;i++){\n var types = models[i].types||['external'];\n if(types.indexOf('external')===-1) continue;\n el.innerHTML += '<option value=\"'+esc(models[i].name)+'\">'+esc(models[i].name)+' ('+esc(models[i].id)+')</option>';\n }\n el.value = prev;\n}\n\n/* ---- Agent ---- */\nasync function loadAgent(){\n currentConfig = await fetchAPI('/config');\n const a = currentConfig.agent||{};\n populateModelSelects();\n document.getElementById('agentModel').value = a.model||'';\n document.getElementById('agentMainFallback').value = a.mainFallback||'';\n document.getElementById('agentMaxTurns').value = a.maxTurns||10;\n document.getElementById('agentPermMode').value = a.permissionMode||'default';\n document.getElementById('agentSessionTTL').value = a.sessionTTL||3600;\n document.getElementById('agentSettingSources').value = a.settingSources||'project';\n document.getElementById('agentCoderSkill').checked = !!a.builtinCoderSkill;\n document.getElementById('agentAutoRenew').value = a.autoRenew||0;\n var allowed = a.allowedTools||[];\n document.querySelectorAll('#agentToolsGrid [data-tool]').forEach(function(cb){cb.checked = allowed.indexOf(cb.dataset.tool)!==-1;});\n document.getElementById('agentQueueMode').value = a.queueMode||'collect';\n document.getElementById('agentDebounceMs').value = a.queueDebounceMs!=null ? a.queueDebounceMs : 1500;\n document.getElementById('agentQueueCap').value = a.queueCap!=null ? a.queueCap : 20;\n document.getElementById('agentDropPolicy').value = a.queueDropPolicy||'summarize';\n document.getElementById('agentInflightTyping').checked = a.inflightTyping!==false;\n document.getElementById('agentAutoApprove').checked = a.autoApproveTools!==false;\n updateQueueFields();\n sectionsLoaded.agent = true;\n}\n\n/* ---- Queue fields visibility ---- */\nfunction updateQueueFields(){\n var mode = document.getElementById('agentQueueMode').value;\n document.getElementById('queueCollectFields').style.display = mode==='collect' ? '' : 'none';\n}\n\n/* ---- SubAgents ---- */\nvar _saDeleteIdx = -1;\nasync function loadSubAgents(){\n currentConfig = await fetchAPI('/config');\n renderSubAgentCards();\n}\nfunction toggleSaAccordion(idx){\n var el = document.querySelector('.sa-acc[data-sa-idx=\"'+idx+'\"]');\n if(el) el.classList.toggle('open');\n}\nfunction renderSubAgentCards(){\n var sas = (currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents) || [];\n var container = document.getElementById('subAgentCards');\n var empty = document.getElementById('subAgentEmpty');\n if(sas.length === 0){\n container.innerHTML = '';\n empty.style.display = '';\n return;\n }\n empty.style.display = 'none';\n var order = [];\n for(var k=0;k<sas.length;k++) order.push(k);\n order.sort(function(a,b){ return (sas[a].name||'').localeCompare(sas[b].name||''); });\n var html = '';\n for(var oi = 0; oi < order.length; oi++){\n var i = order[oi];\n var sa = sas[i];\n html += '<div class=\"sa-acc\" data-sa-idx=\"'+i+'\">';\n html += '<div class=\"sa-acc-header\" onclick=\"toggleSaAccordion('+i+')\">';\n html += '<svg class=\"sa-acc-chevron\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>';\n html += '<span class=\"sa-acc-name\">' + esc(sa.name) + '</span>';\n if(sa.expandContext) html += '<span class=\"badge badge-blue\" style=\"font-size:10px;padding:1px 6px\">expanded</span>';\n html += '<label class=\"toggle\" onclick=\"event.stopPropagation()\"><input type=\"checkbox\" data-sa-toggle=\"'+i+'\" '+(sa.enabled?'checked':'')+'><span></span></label>';\n html += '<button class=\"btn-danger btn-sm\" onclick=\"event.stopPropagation();confirmDeleteSubAgent('+i+')\">Delete</button>';\n html += '</div>';\n html += '<div class=\"sa-acc-body\">';\n html += '<div class=\"field\"><label>Description</label><textarea data-sa-field=\"'+i+'.description\" rows=\"2\" oninput=\"updateSaField('+i+',"description",this.value)\">'+esc(sa.description)+'</textarea></div>';\n html += '<div class=\"field\"><label>Prompt</label><textarea data-sa-field=\"'+i+'.prompt\" rows=\"3\" oninput=\"updateSaField('+i+',"prompt",this.value)\">'+esc(sa.prompt)+'</textarea></div>';\n html += '<div class=\"field\"><label>Model</label><select data-sa-field=\"'+i+'.model\" onchange=\"updateSaField('+i+',"model",this.value)\">';\n var saModels = ['inherit','sonnet','opus','haiku'];\n for(var j=0;j<saModels.length;j++){\n html += '<option value=\"'+saModels[j]+'\"'+(sa.model===saModels[j]?' selected':'')+'>'+saModels[j]+'</option>';\n }\n html += '</select></div>';\n var saTools = sa.tools||[];\n html += '<div class=\"field\"><label>Tools</label><div style=\"display:flex;flex-wrap:wrap;gap:4px;margin-top:4px\">';\n for(var t=0;t<SA_TOOL_LIST.length;t++){\n var tn = SA_TOOL_LIST[t];\n var checked = saTools.indexOf(tn)!==-1;\n html += '<label class=\"tool-toggle-sm\"><label class=\"toggle-sm\"><input type=\"checkbox\" data-sa-tool=\"'+i+'\" data-tool-name=\"'+tn+'\"'+(checked?' checked':'')+'><span></span></label> '+tn+'</label>';\n }\n html += '</div></div>';\n html += '<div style=\"display:flex;align-items:center;gap:12px;margin-top:8px\"><span style=\"font-size:13px;font-weight:500\">Expand Context</span><label class=\"toggle\"><input type=\"checkbox\" data-sa-expand=\"'+i+'\"'+(sa.expandContext?' checked':'')+'><span></span></label></div>';\n html += '</div></div>';\n }\n container.innerHTML = html;\n // Bind expandContext listeners\n container.querySelectorAll('[data-sa-expand]').forEach(function(cb){\n cb.addEventListener('change', function(){\n var idx = parseInt(cb.dataset.saExpand);\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n currentConfig.agent.customSubAgents[idx].expandContext = cb.checked;\n markDirty();\n renderSubAgentCards();\n }\n });\n });\n // Bind toggle listeners\n container.querySelectorAll('[data-sa-toggle]').forEach(function(cb){\n cb.addEventListener('change', function(){\n var idx = parseInt(cb.dataset.saToggle);\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents){\n currentConfig.agent.customSubAgents[idx].enabled = cb.checked;\n markDirty();\n }\n });\n });\n // Bind tool toggle listeners\n container.querySelectorAll('[data-sa-tool]').forEach(function(cb){\n cb.addEventListener('change', function(){\n var idx = parseInt(cb.dataset.saTool);\n var toolName = cb.dataset.toolName;\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n var tools = currentConfig.agent.customSubAgents[idx].tools || [];\n if(cb.checked){\n if(tools.indexOf(toolName)===-1) tools.push(toolName);\n } else {\n tools = tools.filter(function(t){return t!==toolName;});\n }\n currentConfig.agent.customSubAgents[idx].tools = tools;\n markDirty();\n }\n });\n });\n}\nfunction confirmDeleteSubAgent(idx){\n _saDeleteIdx = idx;\n var sas = (currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents) || [];\n var name = sas[idx] ? sas[idx].name : 'this subagent';\n document.getElementById('saDeleteName').textContent = name;\n document.getElementById('saDeleteModal').classList.add('open');\n}\nfunction closeSaDeleteModal(){\n document.getElementById('saDeleteModal').classList.remove('open');\n _saDeleteIdx = -1;\n}\nfunction doDeleteSubAgent(){\n if(_saDeleteIdx >= 0) deleteSubAgent(_saDeleteIdx);\n closeSaDeleteModal();\n saveConfig();\n}\nfunction updateSaField(idx, field, value){\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n currentConfig.agent.customSubAgents[idx][field] = value;\n markDirty();\n }\n}\nfunction updateSaTools(idx, value){\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n currentConfig.agent.customSubAgents[idx].tools = value.split(',').map(function(s){return s.trim()}).filter(Boolean);\n markDirty();\n }\n}\nfunction showAddSubAgent(){\n document.getElementById('addSubAgentForm').style.display = '';\n document.getElementById('newSaName').value = '';\n document.getElementById('newSaDesc').value = '';\n document.getElementById('newSaPrompt').value = '';\n document.getElementById('newSaModel').value = 'inherit';\n document.querySelectorAll('#newSaToolsGrid [data-new-sa-tool]').forEach(function(cb){cb.checked = cb.dataset.newSaTool!=='Bash';});\n document.getElementById('newSaExpandContext').checked = false;\n document.getElementById('newSaName').focus();\n}\nfunction hideAddSubAgent(){\n document.getElementById('addSubAgentForm').style.display = 'none';\n}\nasync function addSubAgent(){\n var name = document.getElementById('newSaName').value.trim();\n var desc = document.getElementById('newSaDesc').value.trim();\n var prompt = document.getElementById('newSaPrompt').value.trim();\n var model = document.getElementById('newSaModel').value;\n var tools = [];\n document.querySelectorAll('#newSaToolsGrid [data-new-sa-tool]').forEach(function(cb){if(cb.checked) tools.push(cb.dataset.newSaTool);});\n if(!name){ toast('Name is required','err'); return; }\n if(!/^[a-zA-Z0-9_\\-\\[\\]!]+$/.test(name)){ toast('Name may only contain a-z A-Z 0-9 - _ [ ] !','err'); return; }\n if(!desc || desc.length < 10){ toast('Description must be at least 10 characters','err'); return; }\n if(!prompt || prompt.length < 10){ toast('Prompt must be at least 10 characters','err'); return; }\n if(!currentConfig){ currentConfig = await fetchAPI('/config'); }\n if(!currentConfig.agent) currentConfig.agent = {};\n if(!currentConfig.agent.customSubAgents) currentConfig.agent.customSubAgents = [];\n var expandContext = document.getElementById('newSaExpandContext').checked;\n currentConfig.agent.customSubAgents.push({ name:name, description:desc, prompt:prompt, model:model, tools:tools, expandContext:expandContext, enabled:false });\n hideAddSubAgent();\n renderSubAgentCards();\n saveConfig();\n}\nfunction deleteSubAgent(idx){\n if(!currentConfig || !currentConfig.agent || !currentConfig.agent.customSubAgents) return;\n currentConfig.agent.customSubAgents.splice(idx, 1);\n renderSubAgentCards();\n markDirty();\n}\n\n/* ---- Vars ---- */\nvar _editVarIdx = -1;\nvar _deleteVarIdx = -1;\n\nfunction showAddVar(){\n document.getElementById('addVarForm').style.display='';\n document.getElementById('newVarName').value='';\n document.getElementById('newVarEnvVar').value='';\n document.getElementById('newVarApiKey').value='';\n}\nfunction hideAddVar(){ document.getElementById('addVarForm').style.display='none'; }\nfunction addVar(){\n var name = document.getElementById('newVarName').value.trim();\n var useEnvVar = document.getElementById('newVarEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim();\n var apiKey = document.getElementById('newVarApiKey').value.trim();\n if(!name){ toast('Display Name required','err'); return; }\n if(!useEnvVar){ toast('Env Var required','err'); return; }\n if(!currentConfig.models) currentConfig.models=[];\n currentConfig.models.push({id:useEnvVar, name:name, types:['env-var'], apiKey:apiKey, baseURL:'', useEnvVar:useEnvVar});\n hideAddVar();\n renderVarsTable();\n saveConfig();\n}\nasync function loadVars(){\n currentConfig = await fetchAPI('/config');\n if(!currentConfig.models) currentConfig.models=[];\n renderVarsTable();\n}\nfunction renderVarsTable(){\n var models = currentConfig.models||[];\n var tbody = document.getElementById('varsBody');\n tbody.innerHTML='';\n var hasVisible = false;\n for(var i=0;i<models.length;i++){\n var m = models[i];\n var types = m.types||['external'];\n if(types.indexOf('env-var')===-1) continue;\n hasVisible = true;\n var envDisplay = m.useEnvVar||m.id||'';\n tbody.innerHTML += '<tr data-var-idx=\"'+i+'\"><td>'+esc(m.name)+'</td><td style=\"font-family:monospace;font-size:13px\">'+esc(envDisplay)+'</td><td style=\"white-space:nowrap\"><button class=\"btn-ghost btn-sm\" onclick=\"startEditVar('+i+')\">Edit</button> <button class=\"btn-danger btn-sm\" onclick=\"confirmDeleteVar('+i+')\">Delete</button></td></tr>';\n }\n if(!hasVisible){\n tbody.innerHTML='<tr><td colspan=\"3\" style=\"text-align:center;color:var(--text-muted);padding:20px\">No vars in registry</td></tr>';\n }\n document.getElementById('editVarForm').style.display='none';\n _editVarIdx = -1;\n}\nfunction startEditVar(idx){\n if(!currentConfig.models||!currentConfig.models[idx]) return;\n _editVarIdx = idx;\n var m = currentConfig.models[idx];\n document.getElementById('editVarName').value = m.name||'';\n document.getElementById('editVarEnvVar').value = m.useEnvVar||'';\n document.getElementById('editVarApiKey').value = m.apiKey||'';\n document.getElementById('editVarForm').style.display='';\n}\nfunction finishEditVar(){\n if(_editVarIdx<0 || !currentConfig.models||!currentConfig.models[_editVarIdx]) return;\n var name = document.getElementById('editVarName').value.trim();\n var useEnvVar = document.getElementById('editVarEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim();\n var apiKey = document.getElementById('editVarApiKey').value.trim();\n if(!name){ toast('Display Name required','err'); return; }\n if(!useEnvVar){ toast('Env Var required','err'); return; }\n currentConfig.models[_editVarIdx] = {id:useEnvVar, name:name, types:['env-var'], apiKey:apiKey, baseURL:'', useEnvVar:useEnvVar};\n _editVarIdx = -1;\n renderVarsTable();\n saveConfig();\n}\nfunction cancelEditVar(){\n _editVarIdx = -1;\n document.getElementById('editVarForm').style.display='none';\n}\nfunction confirmDeleteVar(idx){\n _deleteVarIdx = idx;\n var models = (currentConfig&¤tConfig.models)||[];\n var name = models[idx] ? models[idx].name : 'this var';\n document.getElementById('varDeleteName').textContent = name;\n document.getElementById('varDeleteModal').classList.add('open');\n}\nfunction closeVarDeleteModal(){\n document.getElementById('varDeleteModal').classList.remove('open');\n _deleteVarIdx = -1;\n}\nfunction doDeleteVar(){\n if(_deleteVarIdx>=0 && currentConfig.models){\n currentConfig.models.splice(_deleteVarIdx,1);\n renderVarsTable();\n saveConfig();\n }\n closeVarDeleteModal();\n}\n\n/* ---- Internal Tools discovery ---- */\nvar _internalToolsLoaded = false;\nasync function openInternalToolsModal(){\n document.getElementById('internalToolsModal').classList.add('open');\n if(_internalToolsLoaded) return;\n try {\n var data = await fetchAPI('/internal-tools');\n var wrap = document.getElementById('internalToolsBody');\n if(!data || !data.length){ wrap.innerHTML = '<p style=\"color:var(--text-muted)\">No internal tool servers registered.</p>'; _internalToolsLoaded = true; return; }\n var html = '<table class=\"tbl\" style=\"font-size:13px\"><thead><tr><th style=\"width:18%\">Server</th><th style=\"width:20%\">Tool</th><th>Parameters</th></tr></thead><tbody>';\n for(var s = 0; s < data.length; s++){\n var srv = data[s];\n var toolCount = srv.tools.length || 1;\n for(var t = 0; t < srv.tools.length; t++){\n var tl = srv.tools[t];\n html += '<tr>';\n if(t === 0) html += '<td rowspan=\"'+toolCount+'\" style=\"vertical-align:top\"><strong>'+esc(srv.server)+'</strong></td>';\n html += '<td style=\"vertical-align:top\"><code>'+esc(tl.name)+'</code><div style=\"color:var(--text-muted);font-size:11px;margin-top:4px\">'+esc(tl.description)+'</div></td>';\n if(tl.params.length === 0){\n html += '<td style=\"color:var(--text-muted);font-style:italic\">none</td>';\n } else {\n html += '<td>';\n for(var p = 0; p < tl.params.length; p++){\n var pm = tl.params[p];\n if(p > 0) html += '<br>';\n html += '<code>'+esc(pm.name)+'</code> <span style=\"color:var(--text-muted)\">('+esc(pm.type)+(pm.required?'':',opt')+')</span>';\n if(pm.description) html += ' — <span style=\"font-size:12px\">'+esc(pm.description)+'</span>';\n }\n html += '</td>';\n }\n html += '</tr>';\n }\n if(srv.tools.length === 0){\n html += '<tr><td><strong>'+esc(srv.server)+'</strong></td><td colspan=\"2\" style=\"color:var(--text-muted);font-style:italic\">No tools</td></tr>';\n }\n }\n html += '</tbody></table>';\n wrap.innerHTML = html;\n _internalToolsLoaded = true;\n } catch(err) {\n document.getElementById('internalToolsBody').innerHTML = '<p style=\"color:var(--danger)\">Failed to load: '+esc(String(err))+'</p>';\n }\n}\n"}
|
|
1
|
+
export function agentJS(){return"\nvar SA_TOOL_LIST = ['Read','Write','Edit','Bash','Glob','Grep','WebSearch','WebFetch'];\n\nvar _editModelIdx = -1;\n\n/* ---- Models ---- */\nfunction showAddModel(){\n document.getElementById('addModelForm').style.display='';\n // Reset form\n document.getElementById('newModelId').value='';\n document.getElementById('newModelName').value='';\n document.getElementById('newModelBaseURL').value='https://api.openai.com/v1';\n document.getElementById('newModelApiKey').value='';\n document.getElementById('newModelEnvVar').value='';\n document.getElementById('newModelType').value='external';\n document.getElementById('newModelProxy').value='not-used';\n document.getElementById('newModelFastUrl').value='';\n document.getElementById('newModelFastProxyApiKey').value='';\n document.getElementById('newModelContextWindow').value='200000';\n document.getElementById('newModelCostInput').value='0';\n document.getElementById('newModelCostOutput').value='0';\n document.getElementById('newModelCostCacheRead').value='0';\n document.getElementById('newModelCostCacheWrite').value='0';\n updateNewModelApiFields();\n}\nfunction hideAddModel(){ document.getElementById('addModelForm').style.display='none'; }\nfunction sanitizeEnvVarInput(el){\n var v = el.value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'');\n if(v !== el.value) el.value = v;\n}\nfunction updateNewModelApiFields(){\n var type = document.getElementById('newModelType').value;\n document.getElementById('newModelApiFields').style.display = type!=='internal' ? '' : 'none';\n var baseField = document.getElementById('newModelBaseURLField');\n if(baseField) baseField.style.display = type==='external' ? '' : 'none';\n var proxyField = document.getElementById('newModelProxyField');\n if(proxyField) proxyField.style.display = type==='external' ? '' : 'none';\n var extraFields = document.getElementById('newModelExtraFields');\n if(extraFields) extraFields.style.display = type==='external' ? '' : 'none';\n updateNewModelProxyFields();\n}\nfunction updateNewModelProxyFields(){\n var proxy = document.getElementById('newModelProxy').value;\n var enabled = proxy !== 'not-used';\n var fu = document.getElementById('newModelFastUrl');\n var fk = document.getElementById('newModelFastProxyApiKey');\n if(fu) fu.disabled = !enabled;\n if(fk) fk.disabled = !enabled;\n}\nfunction updateEditModelApiFields(){\n var type = document.getElementById('editModelType').value;\n document.getElementById('editModelApiFields').style.display = type!=='internal' ? '' : 'none';\n var baseField = document.getElementById('editModelBaseURLField');\n if(baseField) baseField.style.display = type==='external' ? '' : 'none';\n var proxyField = document.getElementById('editModelProxyField');\n if(proxyField) proxyField.style.display = type==='external' ? '' : 'none';\n var extraFields = document.getElementById('editModelExtraFields');\n if(extraFields) extraFields.style.display = type==='external' ? '' : 'none';\n updateEditModelProxyFields();\n}\nfunction updateEditModelProxyFields(){\n var proxy = document.getElementById('editModelProxy').value;\n var enabled = proxy !== 'not-used';\n var fu = document.getElementById('editModelFastUrl');\n var fk = document.getElementById('editModelFastProxyApiKey');\n if(fu) fu.disabled = !enabled;\n if(fk) fk.disabled = !enabled;\n}\nasync function loadModels(){\n currentConfig = await fetchAPI('/config');\n if(!currentConfig.models) currentConfig.models=[];\n renderModelsTable();\n}\nfunction renderModelsTable(){\n var models = currentConfig.models||[];\n var tbody = document.getElementById('modelsBody');\n tbody.innerHTML='';\n var hasVisible = false;\n for(var i=0;i<models.length;i++){\n var m = models[i];\n var types = m.types||['external'];\n if(types.indexOf('env-var')!==-1) continue;\n hasVisible = true;\n var typeBadges = types.map(function(t){ return '<span class=\"badge badge-blue\" style=\"font-size:11px;padding:1px 6px;margin-right:2px\">'+esc(t)+'</span>'; }).join('');\n var proxyBadge = (m.proxy && m.proxy !== 'not-used') ? ' <span class=\"badge badge-green\" style=\"font-size:11px;padding:1px 6px\">'+esc(m.proxy)+'</span>' : '';\n tbody.innerHTML += '<tr data-model-idx=\"'+i+'\"><td>'+esc(m.name)+'</td><td style=\"font-family:monospace;font-size:13px\">'+esc(m.id)+'</td><td>'+typeBadges+proxyBadge+'</td><td style=\"white-space:nowrap\"><button class=\"btn-ghost btn-sm\" onclick=\"startEditModel('+i+')\">Edit</button> <button class=\"btn-danger btn-sm\" onclick=\"confirmDeleteModel('+i+')\">Delete</button></td></tr>';\n }\n if(!hasVisible){\n tbody.innerHTML='<tr><td colspan=\"4\" style=\"text-align:center;color:var(--text-muted);padding:20px\">No models in registry</td></tr>';\n }\n // Hide edit form when re-rendering\n document.getElementById('editModelForm').style.display='none';\n _editModelIdx = -1;\n}\nfunction addModel(){\n var id = document.getElementById('newModelId').value.trim();\n var name = document.getElementById('newModelName').value.trim();\n if(!id){ toast('Model ID required','err'); return; }\n if(!name) name = id;\n var type = document.getElementById('newModelType').value;\n var types = [type];\n var needsApi = type!=='internal';\n var useEnvVar = needsApi ? document.getElementById('newModelEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim() : '';\n var apiKey = needsApi ? document.getElementById('newModelApiKey').value.trim() : '';\n var baseURL = type==='external' ? document.getElementById('newModelBaseURL').value.trim() : '';\n var proxy = type==='external' ? document.getElementById('newModelProxy').value : 'not-used';\n var fastUrl = (type==='external' && proxy!=='not-used') ? document.getElementById('newModelFastUrl').value.trim() : '';\n var fastProxyApiKey = (type==='external' && proxy!=='not-used') ? document.getElementById('newModelFastProxyApiKey').value.trim() : '';\n var contextWindow = type==='external' ? (parseInt(document.getElementById('newModelContextWindow').value)||200000) : 200000;\n var costInput = type==='external' ? (parseFloat(document.getElementById('newModelCostInput').value)||0) : 0;\n var costOutput = type==='external' ? (parseFloat(document.getElementById('newModelCostOutput').value)||0) : 0;\n var costCacheRead = type==='external' ? (parseFloat(document.getElementById('newModelCostCacheRead').value)||0) : 0;\n var costCacheWrite = type==='external' ? (parseFloat(document.getElementById('newModelCostCacheWrite').value)||0) : 0;\n if(!currentConfig.models) currentConfig.models=[];\n var dup = currentConfig.models.some(function(m){ return m.name===name && m.id===id; });\n if(dup){ toast('A model configuration with the same Model Name and Model ID already exists','err'); return; }\n currentConfig.models.push({id:id, name:name, types:types, proxy:proxy, fastUrl:fastUrl, fastProxyApiKey:fastProxyApiKey, apiKey:apiKey, baseURL:baseURL, useEnvVar:useEnvVar, contextWindow:contextWindow, costInput:costInput, costOutput:costOutput, costCacheRead:costCacheRead, costCacheWrite:costCacheWrite});\n hideAddModel();\n renderModelsTable();\n populateModelSelects();\n saveConfig();\n}\nvar _deleteModelIdx = -1;\nfunction confirmDeleteModel(idx){\n _deleteModelIdx = idx;\n var models = (currentConfig&¤tConfig.models)||[];\n var name = models[idx] ? models[idx].name : 'this model';\n document.getElementById('modelDeleteName').textContent = name;\n document.getElementById('modelDeleteModal').classList.add('open');\n}\nfunction closeModelDeleteModal(){\n document.getElementById('modelDeleteModal').classList.remove('open');\n _deleteModelIdx = -1;\n}\nfunction doDeleteModel(){\n if(_deleteModelIdx>=0 && currentConfig.models){\n currentConfig.models.splice(_deleteModelIdx,1);\n renderModelsTable();\n populateModelSelects();\n saveConfig();\n }\n closeModelDeleteModal();\n}\nfunction startEditModel(idx){\n if(!currentConfig.models||!currentConfig.models[idx]) return;\n _editModelIdx = idx;\n var m = currentConfig.models[idx];\n document.getElementById('editModelId').value = m.id||'';\n document.getElementById('editModelName').value = m.name||'';\n document.getElementById('editModelBaseURL').value = m.baseURL||'';\n document.getElementById('editModelApiKey').value = m.apiKey||'';\n document.getElementById('editModelEnvVar').value = m.useEnvVar||'';\n document.getElementById('editModelProxy').value = m.proxy||'not-used';\n document.getElementById('editModelFastUrl').value = m.fastUrl||'';\n document.getElementById('editModelFastProxyApiKey').value = m.fastProxyApiKey||'';\n document.getElementById('editModelContextWindow').value = m.contextWindow||200000;\n document.getElementById('editModelCostInput').value = m.costInput||0;\n document.getElementById('editModelCostOutput').value = m.costOutput||0;\n document.getElementById('editModelCostCacheRead').value = m.costCacheRead||0;\n document.getElementById('editModelCostCacheWrite').value = m.costCacheWrite||0;\n var types = m.types||['external'];\n document.getElementById('editModelType').value = types[0]||'external';\n updateEditModelApiFields();\n // Position edit form after the table\n document.getElementById('editModelForm').style.display='';\n}\nfunction finishEditModel(){\n if(_editModelIdx<0 || !currentConfig.models||!currentConfig.models[_editModelIdx]) return;\n var id = document.getElementById('editModelId').value.trim();\n var name = document.getElementById('editModelName').value.trim();\n if(!id){ toast('Model ID required','err'); return; }\n if(!name) name = id;\n var type = document.getElementById('editModelType').value;\n var types = [type];\n var needsApi = type!=='internal';\n var useEnvVar = needsApi ? document.getElementById('editModelEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim() : '';\n var apiKey = needsApi ? document.getElementById('editModelApiKey').value.trim() : '';\n var baseURL = type==='external' ? document.getElementById('editModelBaseURL').value.trim() : '';\n var proxy = type==='external' ? document.getElementById('editModelProxy').value : 'not-used';\n var fastUrl = (type==='external' && proxy!=='not-used') ? document.getElementById('editModelFastUrl').value.trim() : '';\n var fastProxyApiKey = (type==='external' && proxy!=='not-used') ? document.getElementById('editModelFastProxyApiKey').value.trim() : '';\n var contextWindow = type==='external' ? (parseInt(document.getElementById('editModelContextWindow').value)||200000) : 200000;\n var costInput = type==='external' ? (parseFloat(document.getElementById('editModelCostInput').value)||0) : 0;\n var costOutput = type==='external' ? (parseFloat(document.getElementById('editModelCostOutput').value)||0) : 0;\n var costCacheRead = type==='external' ? (parseFloat(document.getElementById('editModelCostCacheRead').value)||0) : 0;\n var costCacheWrite = type==='external' ? (parseFloat(document.getElementById('editModelCostCacheWrite').value)||0) : 0;\n var dup = currentConfig.models.some(function(m, i){ return i!==_editModelIdx && m.name===name && m.id===id; });\n if(dup){ toast('A model configuration with the same Model Name and Model ID already exists','err'); return; }\n currentConfig.models[_editModelIdx] = {id:id, name:name, types:types, proxy:proxy, fastUrl:fastUrl, fastProxyApiKey:fastProxyApiKey, apiKey:apiKey, baseURL:baseURL, useEnvVar:useEnvVar, contextWindow:contextWindow, costInput:costInput, costOutput:costOutput, costCacheRead:costCacheRead, costCacheWrite:costCacheWrite};\n _editModelIdx = -1;\n renderModelsTable();\n populateModelSelects();\n saveConfig();\n}\nfunction cancelEditModel(){\n _editModelIdx = -1;\n document.getElementById('editModelForm').style.display='none';\n}\nfunction populateModelSelects(){\n var models = (currentConfig&¤tConfig.models)||[];\n var picoEnabled = document.getElementById('picoEnabled') && document.getElementById('picoEnabled').checked;\n var picoNames = {};\n if (picoEnabled) { for (var pi=0; pi<_picoModels.length; pi++) picoNames[_picoModels[pi].name] = true; }\n var internalModels = models.filter(function(m){ var t=m.types||['external']; return t.indexOf('internal')!==-1 || (t.indexOf('external')!==-1 && m.proxy && m.proxy!=='not-used') || (picoEnabled && t.indexOf('external')!==-1 && picoNames[m.name]); });\n var selects = {\n agentModel: {val:'', allowNone:false},\n agentMainFallback: {val:'', allowNone:true}\n };\n for(var key in selects){\n var el = document.getElementById(key);\n if(!el) continue;\n selects[key].val = el.value;\n el.innerHTML='';\n if(selects[key].allowNone) el.innerHTML += '<option value=\"\">None</option>';\n for(var i=0;i<internalModels.length;i++){\n el.innerHTML += '<option value=\"'+esc(internalModels[i].name+':'+internalModels[i].id)+'\">'+esc(internalModels[i].name)+' ('+esc(internalModels[i].id)+')</option>';\n }\n el.value = selects[key].val;\n }\n populateSTTModelSelect();\n populateMemSearchModelSelect();\n populatePicoModelSelect();\n populateRollingModelSelect();\n}\nfunction populateSTTModelSelect(){\n var models = (currentConfig&¤tConfig.models)||[];\n var el = document.getElementById('sttModelRef');\n if(!el) return;\n var prev = el.value;\n el.innerHTML = '<option value=\"\">-- select --</option>';\n for(var i=0;i<models.length;i++){\n var types = models[i].types||['external'];\n if(types.indexOf('external')===-1) continue;\n el.innerHTML += '<option value=\"'+esc(models[i].name+':'+models[i].id)+'\">'+esc(models[i].name)+' ('+esc(models[i].id)+')</option>';\n }\n el.value = prev;\n}\nfunction populateMemSearchModelSelect(){\n var models = (currentConfig&¤tConfig.models)||[];\n var el = document.getElementById('memSearchModelRef');\n if(!el) return;\n var prev = el.value;\n el.innerHTML = '<option value=\"\">-- select --</option>';\n for(var i=0;i<models.length;i++){\n var types = models[i].types||['external'];\n if(types.indexOf('external')===-1) continue;\n el.innerHTML += '<option value=\"'+esc(models[i].name+':'+models[i].id)+'\">'+esc(models[i].name)+' ('+esc(models[i].id)+')</option>';\n }\n el.value = prev;\n}\n\n/* ---- Pico Agent ---- */\nvar _picoModels = []; // [{name, piProvider, piModelId, contextWindow}]\nvar _agentLoading = false; // suppress markDirty during initial load\n\nfunction detectPiProvider(model) {\n var url = (model.baseURL || '').toLowerCase();\n if (url.includes('openrouter.ai')) return 'openrouter';\n if (url.includes('openai.com')) return 'openai';\n if (url.includes('x.ai')) return 'xai';\n if (url.includes('googleapis.com')) return 'google';\n if (url.includes('groq.com')) return 'groq';\n if (url.includes('mistral.ai')) return 'mistral';\n return 'openai';\n}\n\nfunction loadPicoAgent() {\n var a = (currentConfig && currentConfig.agent) || {};\n var pico = a.picoAgent || {};\n // Fallback: migrate from engine if picoAgent absent and engine.type === \"pi\"\n if (!a.picoAgent && a.engine && a.engine.type === 'pi') {\n pico = { enabled: true, modelRefs: [], rollingMemoryModel: '' };\n if (a.engine.piModelRef) {\n pico.modelRefs = [a.engine.piModelRef];\n }\n }\n var el = document.getElementById('picoEnabled');\n if (el) el.checked = !!pico.enabled;\n // Parse modelRefs into _picoModels\n _picoModels = [];\n var refs = pico.modelRefs || [];\n var models = (currentConfig && currentConfig.models) || [];\n for (var i = 0; i < refs.length; i++) {\n var parts = refs[i].split(':');\n var name = parts[0] || '';\n var piProvider = parts.length >= 3 ? parts[1] : '';\n var piModelId = parts.length >= 3 ? parts.slice(2).join(':') : (parts[1] || '');\n // Find matching model entry for contextWindow\n var matched = models.filter(function(m){ return m.name === name; })[0];\n var ctx = (matched && matched.contextWindow) || 200000;\n if (!piProvider && matched) piProvider = detectPiProvider(matched);\n _picoModels.push({ name: name, piProvider: piProvider, piModelId: piModelId, contextWindow: ctx });\n }\n updatePicoFields();\n renderPicoModelList();\n // Set rolling model AFTER populateRollingModelSelect() has built the options\n var rollingEl = document.getElementById('picoRollingModel');\n if (rollingEl) rollingEl.value = pico.rollingMemoryModel || '';\n}\n\nfunction updatePicoFields() {\n var enabled = document.getElementById('picoEnabled').checked;\n var fields = document.getElementById('picoFields');\n if (fields) fields.style.display = enabled ? '' : 'none';\n if (enabled) {\n populatePicoModelSelect();\n populateRollingModelSelect();\n }\n populateModelSelects();\n if (!_agentLoading) markDirty();\n}\n\nfunction populatePicoModelSelect() {\n var models = (currentConfig && currentConfig.models) || [];\n var el = document.getElementById('picoModelSelect');\n if (!el) return;\n el.innerHTML = '<option value=\"\" disabled selected>Select a model...</option>';\n var alreadyAdded = {};\n for (var j = 0; j < _picoModels.length; j++) alreadyAdded[_picoModels[j].name] = true;\n var hasOptions = false;\n for (var i = 0; i < models.length; i++) {\n var types = models[i].types || ['external'];\n if (types.indexOf('external') === -1) continue;\n if (alreadyAdded[models[i].name]) continue;\n el.innerHTML += '<option value=\"' + esc(models[i].name) + '\">' + esc(models[i].name) + ' (' + esc(models[i].id) + ')</option>';\n hasOptions = true;\n }\n if (!hasOptions) {\n el.innerHTML = '<option value=\"\" disabled selected>-- no available models --</option>';\n }\n}\n\nfunction populateRollingModelSelect() {\n var models = (currentConfig && currentConfig.models) || [];\n var el = document.getElementById('picoRollingModel');\n if (!el) return;\n var prev = el.value;\n el.innerHTML = '<option value=\"\">-- none --</option>';\n for (var i = 0; i < models.length; i++) {\n var types = models[i].types || ['external'];\n if (types.indexOf('external') === -1) continue;\n el.innerHTML += '<option value=\"' + esc(models[i].name) + '\">' + esc(models[i].name) + ' (' + esc(models[i].id) + ')</option>';\n }\n el.value = prev;\n}\n\nfunction addPicoModel() {\n var sel = document.getElementById('picoModelSelect');\n var name = sel.value;\n if (!name) return;\n var models = (currentConfig && currentConfig.models) || [];\n var matched = models.filter(function(m){ return m.name === name; })[0];\n if (!matched) return;\n var piProvider = detectPiProvider(matched);\n var piModelId = matched.id || '';\n // Strip registry namespace prefix (e.g. \"openrouter:openai/gpt-5.2\" → \"openai/gpt-5.2\")\n var nsIdx = piModelId.indexOf(':');\n if (nsIdx > 0) piModelId = piModelId.substring(nsIdx + 1);\n var ctx = matched.contextWindow || 200000;\n _picoModels.push({ name: name, piProvider: piProvider, piModelId: piModelId, contextWindow: ctx });\n renderPicoModelList();\n populatePicoModelSelect();\n populateModelSelects();\n markDirty();\n}\n\nfunction removePicoModel(idx) {\n _picoModels.splice(idx, 1);\n renderPicoModelList();\n populatePicoModelSelect();\n populateModelSelects();\n markDirty();\n}\n\nfunction clearRollingModel() {\n var el = document.getElementById('picoRollingModel');\n if (el) el.value = '';\n markDirty();\n}\n\nfunction renderPicoModelList() {\n var container = document.getElementById('picoModelList');\n if (!container) return;\n if (_picoModels.length === 0) {\n container.innerHTML = '<div style=\"color:var(--text-muted);font-size:13px;padding:8px 0\">No models added. Use the select above to add models.</div>';\n return;\n }\n var html = '';\n for (var i = 0; i < _picoModels.length; i++) {\n var m = _picoModels[i];\n var ctxLabel = m.contextWindow >= 1000 ? Math.round(m.contextWindow / 1000) + 'k ctx' : m.contextWindow + ' ctx';\n html += '<div class=\"pico-model-item\" draggable=\"true\" data-pico-idx=\"' + i + '\">';\n html += '<span class=\"pico-drag-handle\">⠇</span>';\n html += '<span class=\"pico-model-name\">' + esc(m.name) + '</span>';\n html += '<span class=\"pico-model-ctx\">' + esc(ctxLabel) + '</span>';\n if (i === 0) html += '<span class=\"badge badge-blue\" style=\"font-size:11px;padding:1px 6px\">default</span>';\n html += '<button class=\"pico-model-remove\" onclick=\"removePicoModel(' + i + ')\">×</button>';\n html += '</div>';\n }\n container.innerHTML = html;\n // Attach drag events\n var items = container.querySelectorAll('.pico-model-item');\n items.forEach(function(item) {\n item.addEventListener('dragstart', function(e) {\n e.dataTransfer.setData('text/plain', item.dataset.picoIdx);\n item.classList.add('dragging');\n });\n item.addEventListener('dragend', function() {\n item.classList.remove('dragging');\n });\n item.addEventListener('dragover', function(e) {\n e.preventDefault();\n });\n item.addEventListener('drop', function(e) {\n e.preventDefault();\n var fromIdx = parseInt(e.dataTransfer.getData('text/plain'));\n var toIdx = parseInt(item.dataset.picoIdx);\n if (fromIdx === toIdx) return;\n var moved = _picoModels.splice(fromIdx, 1)[0];\n _picoModels.splice(toIdx, 0, moved);\n renderPicoModelList();\n markDirty();\n });\n });\n}\n\n/* ---- Model ref upgrade ---- */\nfunction upgradeModelRef(val, models){\n if(!val) return val;\n if(val.indexOf(':')!==-1) return val;\n var m = models.filter(function(x){ return x.name===val; })[0];\n return m ? m.name+':'+m.id : val;\n}\n\n/* ---- Agent ---- */\nasync function loadAgent(){\n currentConfig = await fetchAPI('/config');\n const a = currentConfig.agent||{};\n var models = currentConfig.models||[];\n // Load pico agent FIRST so _picoModels is populated before populateModelSelects()\n _agentLoading = true;\n loadPicoAgent();\n populateModelSelects();\n document.getElementById('agentModel').value = upgradeModelRef(a.model||'', models);\n document.getElementById('agentMainFallback').value = upgradeModelRef(a.mainFallback||'', models);\n document.getElementById('agentMaxTurns').value = a.maxTurns||10;\n document.getElementById('agentPermMode').value = a.permissionMode||'default';\n document.getElementById('agentSessionTTL').value = a.sessionTTL||3600;\n document.getElementById('agentSettingSources').value = a.settingSources||'project';\n document.getElementById('agentCoderSkill').checked = !!a.builtinCoderSkill;\n document.getElementById('agentAutoRenew').value = a.autoRenew||0;\n var allowed = a.allowedTools||[];\n document.querySelectorAll('#agentToolsGrid [data-tool]').forEach(function(cb){cb.checked = allowed.indexOf(cb.dataset.tool)!==-1;});\n document.getElementById('agentQueueMode').value = a.queueMode||'collect';\n document.getElementById('agentDebounceMs').value = a.queueDebounceMs!=null ? a.queueDebounceMs : 1500;\n document.getElementById('agentQueueCap').value = a.queueCap!=null ? a.queueCap : 20;\n document.getElementById('agentDropPolicy').value = a.queueDropPolicy||'summarize';\n document.getElementById('agentInflightTyping').checked = a.inflightTyping!==false;\n document.getElementById('agentAutoApprove').checked = a.autoApproveTools!==false;\n updateQueueFields();\n _agentLoading = false;\n sectionsLoaded.agent = true;\n}\n\n/* ---- Queue fields visibility ---- */\nfunction updateQueueFields(){\n var mode = document.getElementById('agentQueueMode').value;\n document.getElementById('queueCollectFields').style.display = mode==='collect' ? '' : 'none';\n}\n\n/* ---- SubAgents ---- */\nvar _saDeleteIdx = -1;\nasync function loadSubAgents(){\n currentConfig = await fetchAPI('/config');\n renderSubAgentCards();\n}\nfunction toggleSaAccordion(idx){\n var el = document.querySelector('.sa-acc[data-sa-idx=\"'+idx+'\"]');\n if(el) el.classList.toggle('open');\n}\nfunction renderSubAgentCards(){\n var sas = (currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents) || [];\n var container = document.getElementById('subAgentCards');\n var empty = document.getElementById('subAgentEmpty');\n if(sas.length === 0){\n container.innerHTML = '';\n empty.style.display = '';\n return;\n }\n empty.style.display = 'none';\n var order = [];\n for(var k=0;k<sas.length;k++) order.push(k);\n order.sort(function(a,b){ return (sas[a].name||'').localeCompare(sas[b].name||''); });\n var html = '';\n for(var oi = 0; oi < order.length; oi++){\n var i = order[oi];\n var sa = sas[i];\n html += '<div class=\"sa-acc\" data-sa-idx=\"'+i+'\">';\n html += '<div class=\"sa-acc-header\" onclick=\"toggleSaAccordion('+i+')\">';\n html += '<svg class=\"sa-acc-chevron\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>';\n html += '<span class=\"sa-acc-name\">' + esc(sa.name) + '</span>';\n if(sa.expandContext) html += '<span class=\"badge badge-blue\" style=\"font-size:10px;padding:1px 6px\">expanded</span>';\n html += '<label class=\"toggle\" onclick=\"event.stopPropagation()\"><input type=\"checkbox\" data-sa-toggle=\"'+i+'\" '+(sa.enabled?'checked':'')+'><span></span></label>';\n html += '<button class=\"btn-danger btn-sm\" onclick=\"event.stopPropagation();confirmDeleteSubAgent('+i+')\">Delete</button>';\n html += '</div>';\n html += '<div class=\"sa-acc-body\">';\n html += '<div class=\"field\"><label>Description</label><textarea data-sa-field=\"'+i+'.description\" rows=\"2\" oninput=\"updateSaField('+i+',"description",this.value)\">'+esc(sa.description)+'</textarea></div>';\n html += '<div class=\"field\"><label>Prompt</label><textarea data-sa-field=\"'+i+'.prompt\" rows=\"3\" oninput=\"updateSaField('+i+',"prompt",this.value)\">'+esc(sa.prompt)+'</textarea></div>';\n html += '<div class=\"field\"><label>Model</label><select data-sa-field=\"'+i+'.model\" onchange=\"updateSaField('+i+',"model",this.value)\">';\n var saModels = ['inherit','sonnet','opus','haiku'];\n for(var j=0;j<saModels.length;j++){\n html += '<option value=\"'+saModels[j]+'\"'+(sa.model===saModels[j]?' selected':'')+'>'+saModels[j]+'</option>';\n }\n html += '</select></div>';\n var saTools = sa.tools||[];\n html += '<div class=\"field\"><label>Tools</label><div style=\"display:flex;flex-wrap:wrap;gap:4px;margin-top:4px\">';\n for(var t=0;t<SA_TOOL_LIST.length;t++){\n var tn = SA_TOOL_LIST[t];\n var checked = saTools.indexOf(tn)!==-1;\n html += '<label class=\"tool-toggle-sm\"><label class=\"toggle-sm\"><input type=\"checkbox\" data-sa-tool=\"'+i+'\" data-tool-name=\"'+tn+'\"'+(checked?' checked':'')+'><span></span></label> '+tn+'</label>';\n }\n html += '</div></div>';\n html += '<div style=\"display:flex;align-items:center;gap:12px;margin-top:8px\"><span style=\"font-size:13px;font-weight:500\">Expand Context</span><label class=\"toggle\"><input type=\"checkbox\" data-sa-expand=\"'+i+'\"'+(sa.expandContext?' checked':'')+'><span></span></label></div>';\n html += '</div></div>';\n }\n container.innerHTML = html;\n // Bind expandContext listeners\n container.querySelectorAll('[data-sa-expand]').forEach(function(cb){\n cb.addEventListener('change', function(){\n var idx = parseInt(cb.dataset.saExpand);\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n currentConfig.agent.customSubAgents[idx].expandContext = cb.checked;\n markDirty();\n renderSubAgentCards();\n }\n });\n });\n // Bind toggle listeners\n container.querySelectorAll('[data-sa-toggle]').forEach(function(cb){\n cb.addEventListener('change', function(){\n var idx = parseInt(cb.dataset.saToggle);\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents){\n currentConfig.agent.customSubAgents[idx].enabled = cb.checked;\n markDirty();\n }\n });\n });\n // Bind tool toggle listeners\n container.querySelectorAll('[data-sa-tool]').forEach(function(cb){\n cb.addEventListener('change', function(){\n var idx = parseInt(cb.dataset.saTool);\n var toolName = cb.dataset.toolName;\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n var tools = currentConfig.agent.customSubAgents[idx].tools || [];\n if(cb.checked){\n if(tools.indexOf(toolName)===-1) tools.push(toolName);\n } else {\n tools = tools.filter(function(t){return t!==toolName;});\n }\n currentConfig.agent.customSubAgents[idx].tools = tools;\n markDirty();\n }\n });\n });\n}\nfunction confirmDeleteSubAgent(idx){\n _saDeleteIdx = idx;\n var sas = (currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents) || [];\n var name = sas[idx] ? sas[idx].name : 'this subagent';\n document.getElementById('saDeleteName').textContent = name;\n document.getElementById('saDeleteModal').classList.add('open');\n}\nfunction closeSaDeleteModal(){\n document.getElementById('saDeleteModal').classList.remove('open');\n _saDeleteIdx = -1;\n}\nfunction doDeleteSubAgent(){\n if(_saDeleteIdx >= 0) deleteSubAgent(_saDeleteIdx);\n closeSaDeleteModal();\n saveConfig();\n}\nfunction updateSaField(idx, field, value){\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n currentConfig.agent.customSubAgents[idx][field] = value;\n markDirty();\n }\n}\nfunction updateSaTools(idx, value){\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents && currentConfig.agent.customSubAgents[idx]){\n currentConfig.agent.customSubAgents[idx].tools = value.split(',').map(function(s){return s.trim()}).filter(Boolean);\n markDirty();\n }\n}\nfunction showAddSubAgent(){\n document.getElementById('addSubAgentForm').style.display = '';\n document.getElementById('newSaName').value = '';\n document.getElementById('newSaDesc').value = '';\n document.getElementById('newSaPrompt').value = '';\n document.getElementById('newSaModel').value = 'inherit';\n document.querySelectorAll('#newSaToolsGrid [data-new-sa-tool]').forEach(function(cb){cb.checked = cb.dataset.newSaTool!=='Bash';});\n document.getElementById('newSaExpandContext').checked = false;\n document.getElementById('newSaName').focus();\n}\nfunction hideAddSubAgent(){\n document.getElementById('addSubAgentForm').style.display = 'none';\n}\nasync function addSubAgent(){\n var name = document.getElementById('newSaName').value.trim();\n var desc = document.getElementById('newSaDesc').value.trim();\n var prompt = document.getElementById('newSaPrompt').value.trim();\n var model = document.getElementById('newSaModel').value;\n var tools = [];\n document.querySelectorAll('#newSaToolsGrid [data-new-sa-tool]').forEach(function(cb){if(cb.checked) tools.push(cb.dataset.newSaTool);});\n if(!name){ toast('Name is required','err'); return; }\n if(!/^[a-zA-Z0-9_\\-\\[\\]!]+$/.test(name)){ toast('Name may only contain a-z A-Z 0-9 - _ [ ] !','err'); return; }\n if(!desc || desc.length < 10){ toast('Description must be at least 10 characters','err'); return; }\n if(!prompt || prompt.length < 10){ toast('Prompt must be at least 10 characters','err'); return; }\n if(!currentConfig){ currentConfig = await fetchAPI('/config'); }\n if(!currentConfig.agent) currentConfig.agent = {};\n if(!currentConfig.agent.customSubAgents) currentConfig.agent.customSubAgents = [];\n var expandContext = document.getElementById('newSaExpandContext').checked;\n currentConfig.agent.customSubAgents.push({ name:name, description:desc, prompt:prompt, model:model, tools:tools, expandContext:expandContext, enabled:false });\n hideAddSubAgent();\n renderSubAgentCards();\n saveConfig();\n}\nfunction deleteSubAgent(idx){\n if(!currentConfig || !currentConfig.agent || !currentConfig.agent.customSubAgents) return;\n currentConfig.agent.customSubAgents.splice(idx, 1);\n renderSubAgentCards();\n markDirty();\n}\n\n/* ---- Vars ---- */\nvar _editVarIdx = -1;\nvar _deleteVarIdx = -1;\n\nfunction showAddVar(){\n document.getElementById('addVarForm').style.display='';\n document.getElementById('newVarName').value='';\n document.getElementById('newVarEnvVar').value='';\n document.getElementById('newVarApiKey').value='';\n}\nfunction hideAddVar(){ document.getElementById('addVarForm').style.display='none'; }\nfunction addVar(){\n var name = document.getElementById('newVarName').value.trim();\n var useEnvVar = document.getElementById('newVarEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim();\n var apiKey = document.getElementById('newVarApiKey').value.trim();\n if(!name){ toast('Display Name required','err'); return; }\n if(!useEnvVar){ toast('Env Var required','err'); return; }\n if(!currentConfig.models) currentConfig.models=[];\n currentConfig.models.push({id:useEnvVar, name:name, types:['env-var'], apiKey:apiKey, baseURL:'', useEnvVar:useEnvVar});\n hideAddVar();\n renderVarsTable();\n saveConfig();\n}\nasync function loadVars(){\n currentConfig = await fetchAPI('/config');\n if(!currentConfig.models) currentConfig.models=[];\n renderVarsTable();\n}\nfunction renderVarsTable(){\n var models = currentConfig.models||[];\n var tbody = document.getElementById('varsBody');\n tbody.innerHTML='';\n var hasVisible = false;\n for(var i=0;i<models.length;i++){\n var m = models[i];\n var types = m.types||['external'];\n if(types.indexOf('env-var')===-1) continue;\n hasVisible = true;\n var envDisplay = m.useEnvVar||m.id||'';\n tbody.innerHTML += '<tr data-var-idx=\"'+i+'\"><td>'+esc(m.name)+'</td><td style=\"font-family:monospace;font-size:13px\">'+esc(envDisplay)+'</td><td style=\"white-space:nowrap\"><button class=\"btn-ghost btn-sm\" onclick=\"startEditVar('+i+')\">Edit</button> <button class=\"btn-danger btn-sm\" onclick=\"confirmDeleteVar('+i+')\">Delete</button></td></tr>';\n }\n if(!hasVisible){\n tbody.innerHTML='<tr><td colspan=\"3\" style=\"text-align:center;color:var(--text-muted);padding:20px\">No vars in registry</td></tr>';\n }\n document.getElementById('editVarForm').style.display='none';\n _editVarIdx = -1;\n}\nfunction startEditVar(idx){\n if(!currentConfig.models||!currentConfig.models[idx]) return;\n _editVarIdx = idx;\n var m = currentConfig.models[idx];\n document.getElementById('editVarName').value = m.name||'';\n document.getElementById('editVarEnvVar').value = m.useEnvVar||'';\n document.getElementById('editVarApiKey').value = m.apiKey||'';\n document.getElementById('editVarForm').style.display='';\n}\nfunction finishEditVar(){\n if(_editVarIdx<0 || !currentConfig.models||!currentConfig.models[_editVarIdx]) return;\n var name = document.getElementById('editVarName').value.trim();\n var useEnvVar = document.getElementById('editVarEnvVar').value.toUpperCase().replace(/[^A-Z0-9_\\-\\+\\[\\]]/g,'').trim();\n var apiKey = document.getElementById('editVarApiKey').value.trim();\n if(!name){ toast('Display Name required','err'); return; }\n if(!useEnvVar){ toast('Env Var required','err'); return; }\n currentConfig.models[_editVarIdx] = {id:useEnvVar, name:name, types:['env-var'], apiKey:apiKey, baseURL:'', useEnvVar:useEnvVar};\n _editVarIdx = -1;\n renderVarsTable();\n saveConfig();\n}\nfunction cancelEditVar(){\n _editVarIdx = -1;\n document.getElementById('editVarForm').style.display='none';\n}\nfunction confirmDeleteVar(idx){\n _deleteVarIdx = idx;\n var models = (currentConfig&¤tConfig.models)||[];\n var name = models[idx] ? models[idx].name : 'this var';\n document.getElementById('varDeleteName').textContent = name;\n document.getElementById('varDeleteModal').classList.add('open');\n}\nfunction closeVarDeleteModal(){\n document.getElementById('varDeleteModal').classList.remove('open');\n _deleteVarIdx = -1;\n}\nfunction doDeleteVar(){\n if(_deleteVarIdx>=0 && currentConfig.models){\n currentConfig.models.splice(_deleteVarIdx,1);\n renderVarsTable();\n saveConfig();\n }\n closeVarDeleteModal();\n}\n\n/* ---- Internal Tools discovery ---- */\nvar _internalToolsLoaded = false;\nasync function openInternalToolsModal(){\n document.getElementById('internalToolsModal').classList.add('open');\n if(_internalToolsLoaded) return;\n try {\n var data = await fetchAPI('/internal-tools');\n var wrap = document.getElementById('internalToolsBody');\n if(!data || !data.length){ wrap.innerHTML = '<p style=\"color:var(--text-muted)\">No internal tool servers registered.</p>'; _internalToolsLoaded = true; return; }\n var html = '<table class=\"tbl\" style=\"font-size:13px\"><thead><tr><th style=\"width:18%\">Server</th><th style=\"width:20%\">Tool</th><th>Parameters</th></tr></thead><tbody>';\n for(var s = 0; s < data.length; s++){\n var srv = data[s];\n var toolCount = srv.tools.length || 1;\n for(var t = 0; t < srv.tools.length; t++){\n var tl = srv.tools[t];\n html += '<tr>';\n if(t === 0) html += '<td rowspan=\"'+toolCount+'\" style=\"vertical-align:top\"><strong>'+esc(srv.server)+'</strong></td>';\n html += '<td style=\"vertical-align:top\"><code>'+esc(tl.name)+'</code><div style=\"color:var(--text-muted);font-size:11px;margin-top:4px\">'+esc(tl.description)+'</div></td>';\n if(tl.params.length === 0){\n html += '<td style=\"color:var(--text-muted);font-style:italic\">none</td>';\n } else {\n html += '<td>';\n for(var p = 0; p < tl.params.length; p++){\n var pm = tl.params[p];\n if(p > 0) html += '<br>';\n html += '<code>'+esc(pm.name)+'</code> <span style=\"color:var(--text-muted)\">('+esc(pm.type)+(pm.required?'':',opt')+')</span>';\n if(pm.description) html += ' — <span style=\"font-size:12px\">'+esc(pm.description)+'</span>';\n }\n html += '</td>';\n }\n html += '</tr>';\n }\n if(srv.tools.length === 0){\n html += '<tr><td><strong>'+esc(srv.server)+'</strong></td><td colspan=\"2\" style=\"color:var(--text-muted);font-style:italic\">No tools</td></tr>';\n }\n }\n html += '</tbody></table>';\n wrap.innerHTML = html;\n _internalToolsLoaded = true;\n } catch(err) {\n document.getElementById('internalToolsBody').innerHTML = '<p style=\"color:var(--danger)\">Failed to load: '+esc(String(err))+'</p>';\n }\n}\n"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function configJS(){return"\n/* ---- Env var ref markers ---- */\nfunction markEnvRefs(container){\n if(!container) return;\n container.querySelectorAll('input').forEach(function(inp){\n if(/^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$/.test(inp.value)){\n inp.classList.add('env-ref');\n inp.title='Stored as environment variable in .env';\n } else {\n inp.classList.remove('env-ref');\n inp.title='';\n }\n });\n}\n\n/* ---- STT ---- */\nasync function loadSTT(){\n currentConfig = await fetchAPI('/config');\n const s = currentConfig.stt||{};\n document.getElementById('sttEnabled').checked = !!s.enabled;\n document.getElementById('sttProvider').value = s.provider||'openai-whisper';\n const oai = s['openai-whisper']||{};\n populateSTTModelSelect();\n document.getElementById('sttModelRef').value = oai.modelRef||'';\n document.getElementById('sttOAILang').value = oai.language||'';\n const loc = s['local-whisper']||{};\n document.getElementById('sttLocalBin').value = loc.binaryPath||'whisper';\n document.getElementById('sttLocalModel').value = loc.model||'base';\n updateSTTFields();\n if(!loadSTT._bound){\n document.getElementById('sttProvider').addEventListener('change', updateSTTFields);\n loadSTT._bound = true;\n }\n sectionsLoaded.stt = true;\n}\nfunction updateSTTFields(){\n const prov = document.getElementById('sttProvider').value;\n document.getElementById('sttOpenAI').style.display = prov==='openai-whisper'?'':'none';\n document.getElementById('sttLocal').style.display = prov==='local-whisper'?'':'none';\n}\n\n/* ---- Memory ---- */\nasync function loadMemory(){\n currentConfig = await fetchAPI('/config');\n const m = currentConfig.memory||{};\n document.getElementById('memEnabled').checked = !!m.enabled;\n document.getElementById('memDir').value = m.dir||'./memory';\n document.getElementById('memStrategy').value = m.recallStrategy||'builtin-only';\n // Search settings\n const s = m.search||{};\n populateMemSearchModelSelect();\n document.getElementById('memSearchModelRef').value = s.modelRef||'';\n document.getElementById('memSearchEmbModel').value = s.embeddingModel||'text-embedding-3-small';\n document.getElementById('memSearchPrefixQuery').value = s.prefixQuery||'';\n document.getElementById('memSearchPrefixDocument').value = s.prefixDocument||'';\n var dimsVal = String(s.embeddingDimensions||1536);\n document.getElementById('memSearchDims').value = dimsVal;\n document.getElementById('memSearchDimsValue').textContent = dimsVal;\n document.getElementById('memSearchMaxResults').value = s.maxResults||6;\n document.getElementById('memSearchDebounce').value = s.updateDebounceMs||3000;\n document.getElementById('memSearchEmbedInterval').value = s.embedIntervalMs||300000;\n document.getElementById('memSearchMaxSnippet').value = s.maxSnippetChars||700;\n document.getElementById('memSearchMaxInjected').value = s.maxInjectedChars||4000;\n document.getElementById('memSearchRrfK').value = s.rrfK||60;\n updateMemSearchFields();\n // Intercept toggle-off\n document.getElementById('memEnabled').addEventListener('change', function(){\n if(!this.checked){\n this.checked = true; // revert until confirmed\n document.getElementById('memDisableModal').classList.add('open');\n }\n });\n sectionsLoaded.memory = true;\n}\nfunction updateMemSearchFields(){\n var strategy = document.getElementById('memStrategy').value;\n document.getElementById('memSearchSettings').style.display = strategy==='search'?'':'none';\n}\nasync function testEmbedding(){\n var btn = document.getElementById('memSearchTestBtn');\n var result = document.getElementById('memSearchTestResult');\n btn.disabled = true;\n btn.textContent = 'Testing...';\n result.style.color = 'var(--text-muted)';\n result.textContent = '';\n try {\n var body = {\n modelRef: document.getElementById('memSearchModelRef').value,\n embeddingModel: document.getElementById('memSearchEmbModel').value || 'text-embedding-3-small',\n embeddingDimensions: parseInt(document.getElementById('memSearchDims').value) || 1536\n };\n var resp = await fetch(API+'/memory-search/test-embedding', {method:'POST', headers:{'Content-Type':'application/json','X-CSRF-Token':_csrfToken}, credentials:'include', body:JSON.stringify(body)});\n var data = await resp.json();\n if(data.ok){\n result.style.color = 'var(--success, #22c55e)';\n result.textContent = '✓ OK — model: '+data.model+', dims: '+data.dimensions+', latency: '+data.latencyMs+'ms';\n } else {\n result.style.color = 'var(--error, #ef4444)';\n result.textContent = '✗ '+data.error;\n }\n } catch(err){\n result.style.color = 'var(--error, #ef4444)';\n result.textContent = '✗ Connection error: '+err.message;\n } finally {\n btn.disabled = false;\n btn.textContent = 'Test Embedding';\n }\n}\nfunction closeMemDisableModal(confirmed){\n document.getElementById('memDisableModal').classList.remove('open');\n if(confirmed){\n document.getElementById('memEnabled').checked = false;\n saveConfig();\n }\n}\n\n/* ---- Settings ---- */\nasync function loadSettings(){\n currentConfig = await fetchAPI('/config');\n document.getElementById('settingsHost').value = currentConfig.host || '127.0.0.1';\n document.getElementById('settingsUiPort').value = (currentConfig.nostromo && currentConfig.nostromo.port) || '3001';\n document.getElementById('settingsTimezone').value = currentConfig.timezone || '';\n document.getElementById('settingsAutoRestart').checked = !!(currentConfig.nostromo && currentConfig.nostromo.autoRestart);\n document.getElementById('settingsConfigCheckInterval').value = (currentConfig.nostromo && currentConfig.nostromo.configCheckInterval) || 5;\n sectionsLoaded.settings = true;\n}\n\n/* ---- Access key display ---- */\nvar _currentKey = '';\nvar _keyRevealed = false;\n\nfunction maskKey(key){\n // Show first 4 chars, mask the rest: \"ABCD-****-****-****\"\n if(key.length<=4) return key;\n return key.substring(0,4) + key.substring(4).replace(/[A-Za-z0-9]/g, '*');\n}\n\nasync function loadCurrentKey(){\n try{\n const res = await fetch(API+'/key');\n const data = await res.json();\n _currentKey = data.key || '';\n _keyRevealed = false;\n var row = document.getElementById('currentKeyRow');\n var el = document.getElementById('currentKeyValue');\n if(_currentKey && _currentKey !== '0000'){\n el.textContent = maskKey(_currentKey);\n row.style.display = 'flex';\n } else {\n row.style.display = 'none';\n }\n document.getElementById('keyEyeOff').style.display = '';\n document.getElementById('keyEyeOn').style.display = 'none';\n }catch(e){}\n}\n\nfunction toggleKeyVisibility(){\n _keyRevealed = !_keyRevealed;\n var el = document.getElementById('currentKeyValue');\n el.textContent = _keyRevealed ? _currentKey : maskKey(_currentKey);\n document.getElementById('keyEyeOff').style.display = _keyRevealed ? 'none' : '';\n document.getElementById('keyEyeOn').style.display = _keyRevealed ? '' : 'none';\n}\n\nasync function copyKey(){\n try{\n await navigator.clipboard.writeText(_currentKey);\n document.getElementById('keyCopyIcon').style.display = 'none';\n document.getElementById('keyCheckIcon').style.display = '';\n setTimeout(function(){\n document.getElementById('keyCopyIcon').style.display = '';\n document.getElementById('keyCheckIcon').style.display = 'none';\n }, 1500);\n }catch(e){ toast('Copy failed','err'); }\n}\n\n/* ---- Regenerate key ---- */\nfunction confirmRegenKey(){\n document.getElementById('regenKeyModal').classList.add('open');\n}\nfunction closeRegenKeyModal(){\n document.getElementById('regenKeyModal').classList.remove('open');\n}\nasync function regenKey(){\n try{\n const res = await fetch(API+'/key/regenerate',{method:'POST',headers:{'X-CSRF-Token':_csrfToken}});\n const data = await res.json();\n if(data.key){\n showNewKey(data.key);\n _currentKey = data.key;\n _keyRevealed = false;\n var el = document.getElementById('currentKeyValue');\n el.textContent = maskKey(data.key);\n document.getElementById('currentKeyRow').style.display = 'flex';\n document.getElementById('keyEyeOff').style.display = '';\n document.getElementById('keyEyeOn').style.display = 'none';\n }\n }catch(e){ toast('Failed','err'); }\n}\n\n/* ---- Save config ---- */\nfunction gatherConfig(){\n if(!currentConfig){ toast('Config not loaded yet — cannot save','err'); return null; }\n const cfg = JSON.parse(JSON.stringify(currentConfig));\n // Verbose debug logs\n var logVerboseEl = document.getElementById('logVerbose');\n if(logVerboseEl) cfg.verboseDebugLogs = logVerboseEl.checked;\n // Channels\n const wrap = document.getElementById('channelCards');\n if(wrap){\n if(!cfg.channels) cfg.channels={};\n for(const ch of CHANNEL_LIST){\n const toggle = wrap.querySelector('[data-ch-toggle=\"'+ch+'\"]');\n if(!toggle) continue;\n if(!cfg.channels[ch]) cfg.channels[ch]={};\n cfg.channels[ch].enabled = toggle.checked;\n }\n // Collect all channel fields via data-ch-field=\"channel.account.key\" or \"channel.key\"\n wrap.querySelectorAll('[data-ch-field]').forEach(inp=>{\n const parts = inp.dataset.chField.split('.');\n const chName = parts[0];\n if(!cfg.channels[chName]) cfg.channels[chName]={};\n if(parts.length === 3){\n // channel.account.key\n const [, acct, key] = parts;\n if(!cfg.channels[chName].accounts) cfg.channels[chName].accounts={};\n if(!cfg.channels[chName].accounts[acct]) cfg.channels[chName].accounts[acct]={};\n var val;\n if(key==='allowFrom') val = inp.value.split(',').map(function(s){return s.trim()}).filter(Boolean);\n else if(inp.type==='number') val = parseInt(inp.value)||0;\n else val = inp.value;\n cfg.channels[chName].accounts[acct][key] = val;\n } else if(parts.length === 2){\n // channel.key (e.g. responses.port)\n const key = parts[1];\n cfg.channels[chName][key] = inp.type==='number' ? (parseInt(inp.value)||0) : inp.value;\n }\n });\n }\n // Models\n if(currentConfig&¤tConfig.models) cfg.models = currentConfig.models;\n // Agent — only gather from DOM if loadAgent() has populated the fields\n if(sectionsLoaded.agent){\n cfg.agent = cfg.agent||{};\n cfg.agent.model = document.getElementById('agentModel').value;\n cfg.agent.mainFallback = document.getElementById('agentMainFallback').value;\n cfg.agent.maxTurns = parseInt(document.getElementById('agentMaxTurns').value)||10;\n cfg.agent.permissionMode = document.getElementById('agentPermMode').value;\n cfg.agent.sessionTTL = parseInt(document.getElementById('agentSessionTTL').value)||3600;\n cfg.agent.settingSources = document.getElementById('agentSettingSources').value;\n cfg.agent.builtinCoderSkill = document.getElementById('agentCoderSkill').checked;\n cfg.agent.autoRenew = parseInt(document.getElementById('agentAutoRenew').value)||0;\n cfg.agent.allowedTools = [];\n document.querySelectorAll('#agentToolsGrid [data-tool]').forEach(function(cb){if(cb.checked) cfg.agent.allowedTools.push(cb.dataset.tool);});\n cfg.agent.disallowedTools = [];\n cfg.agent.queueMode = document.getElementById('agentQueueMode').value;\n cfg.agent.queueDebounceMs = parseInt(document.getElementById('agentDebounceMs').value)||0;\n cfg.agent.queueCap = parseInt(document.getElementById('agentQueueCap').value)||0;\n cfg.agent.queueDropPolicy = document.getElementById('agentDropPolicy').value;\n cfg.agent.inflightTyping = document.getElementById('agentInflightTyping').checked;\n cfg.agent.autoApproveTools = document.getElementById('agentAutoApprove').checked;\n }\n // Custom SubAgents (preserved from currentConfig, mutated in-place by the UI)\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents){\n if(!cfg.agent) cfg.agent = {};\n cfg.agent.customSubAgents = currentConfig.agent.customSubAgents;\n }\n // Plugins (preserved from currentConfig, mutated in-place by the UI)\n if(currentConfig && currentConfig.agent && currentConfig.agent.plugins){\n if(!cfg.agent) cfg.agent = {};\n cfg.agent.plugins = currentConfig.agent.plugins;\n }\n // STT — only gather from DOM if loadSTT() has populated the fields\n if(sectionsLoaded.stt){\n cfg.stt = cfg.stt||{};\n cfg.stt.enabled = document.getElementById('sttEnabled').checked;\n cfg.stt.provider = document.getElementById('sttProvider').value;\n var sttModelName = document.getElementById('sttModelRef').value;\n cfg.stt['openai-whisper'] = {\n modelRef: sttModelName,\n model: 'whisper-1',\n language: document.getElementById('sttOAILang').value\n };\n cfg.stt['local-whisper'] = {\n binaryPath: document.getElementById('sttLocalBin').value,\n model: document.getElementById('sttLocalModel').value\n };\n }\n // TTS — only gather from DOM if loadTTS() has populated the fields\n if(sectionsLoaded.tts){\n cfg.tts = cfg.tts||{};\n cfg.tts.enabled = document.getElementById('ttsEnabled').checked;\n cfg.tts.provider = document.getElementById('ttsProvider').value;\n cfg.tts.maxTextLength = parseInt(document.getElementById('ttsMaxTextLength').value)||4096;\n cfg.tts.timeoutMs = parseInt(document.getElementById('ttsTimeoutMs').value)||30000;\n cfg.tts.edge = {\n voice: document.getElementById('ttsEdgeVoice').value || 'en-US-MichelleNeural'\n };\n cfg.tts.openai = {\n modelRef: document.getElementById('ttsOAIModelRef').value,\n model: document.getElementById('ttsOAIModel').value || 'gpt-4o-mini-tts',\n voice: document.getElementById('ttsOAIVoice').value || 'alloy'\n };\n cfg.tts.elevenlabs = {\n modelRef: document.getElementById('ttsELModelRef').value,\n voiceId: document.getElementById('ttsELVoiceId').value || 'pMsXgVXv3BLzUgSXRplE',\n modelId: document.getElementById('ttsELModelId').value || 'eleven_multilingual_v2'\n };\n }\n // Memory — only gather from DOM if loadMemory() has populated the fields\n if(sectionsLoaded.memory){\n cfg.memory = cfg.memory||{};\n cfg.memory.enabled = document.getElementById('memEnabled').checked;\n cfg.memory.dir = document.getElementById('memDir').value;\n cfg.memory.recallStrategy = document.getElementById('memStrategy').value;\n cfg.memory.search = {\n enabled: cfg.memory.recallStrategy === 'search',\n modelRef: document.getElementById('memSearchModelRef').value,\n embeddingModel: document.getElementById('memSearchEmbModel').value || 'text-embedding-3-small',\n prefixQuery: document.getElementById('memSearchPrefixQuery').value || '',\n prefixDocument: document.getElementById('memSearchPrefixDocument').value || '',\n embeddingDimensions: parseInt(document.getElementById('memSearchDims').value) || 1536,\n updateDebounceMs: parseInt(document.getElementById('memSearchDebounce').value) || 3000,\n embedIntervalMs: parseInt(document.getElementById('memSearchEmbedInterval').value) || 300000,\n maxResults: parseInt(document.getElementById('memSearchMaxResults').value) || 6,\n maxSnippetChars: parseInt(document.getElementById('memSearchMaxSnippet').value) || 700,\n maxInjectedChars: parseInt(document.getElementById('memSearchMaxInjected').value) || 4000,\n rrfK: parseInt(document.getElementById('memSearchRrfK').value) || 60\n };\n }\n // Cron — only gather from DOM if loadCron() has populated the fields\n if(sectionsLoaded.cron){\n cfg.cron = cfg.cron||{};\n cfg.cron.enabled = document.getElementById('cronEnabled').checked;\n cfg.cron.isolated = document.getElementById('cronIsolated').checked;\n cfg.cron.broadcastEvents = document.getElementById('cronBroadcast').checked;\n cfg.cron.heartbeat = cfg.cron.heartbeat||{};\n cfg.cron.heartbeat.enabled = document.getElementById('hbEnabled').checked;\n cfg.cron.heartbeat.channel = document.getElementById('hbChannel').value;\n cfg.cron.heartbeat.chatId = document.getElementById('hbChatId').value;\n cfg.cron.heartbeat.every = parseInt(document.getElementById('hbEvery').value)||1800000;\n cfg.cron.heartbeat.message = document.getElementById('hbMessage').value;\n cfg.cron.heartbeat.ackMaxChars = parseInt(document.getElementById('hbAckMaxChars').value)||300;\n }\n // Settings — only gather from DOM if loadSettings() has populated the fields\n if(sectionsLoaded.settings){\n cfg.host = document.getElementById('settingsHost').value || '127.0.0.1';\n cfg.timezone = document.getElementById('settingsTimezone').value || '';\n cfg.nostromo = cfg.nostromo||{};\n cfg.nostromo.port = parseInt(document.getElementById('settingsUiPort').value)||3001;\n cfg.nostromo.autoRestart = document.getElementById('settingsAutoRestart').checked;\n cfg.nostromo.configCheckInterval = parseInt(document.getElementById('settingsConfigCheckInterval').value)||5;\n }\n return cfg;\n}\nasync function saveConfig(){\n const cfg = gatherConfig();\n if(!cfg) return;\n // Validate memory search prefix fields contain {content} if non-empty\n if(cfg.memory && cfg.memory.search){\n var pq = (cfg.memory.search.prefixQuery||'').trim();\n var pd = (cfg.memory.search.prefixDocument||'').trim();\n if(pq && pq.indexOf('{content}')===-1){ toast('Prefix Query must contain the {content} placeholder. Save aborted.','err'); return; }\n if(pd && pd.indexOf('{content}')===-1){ toast('Prefix Document must contain the {content} placeholder. Save aborted.','err'); return; }\n }\n // Block save if heartbeat is enabled but message is too short\n if(cfg.cron && cfg.cron.heartbeat && cfg.cron.heartbeat.enabled){\n var hbMsg = (cfg.cron.heartbeat.message||'').trim();\n if(hbMsg.length < 15){\n toast('Heartbeat message is too short (minimum 15 characters). Save aborted.','err');\n return;\n }\n }\n try{\n const res = await fetch(API+'/config',{method:'PUT',headers:{'Content-Type':'application/json','X-CSRF-Token':_csrfToken},body:JSON.stringify(cfg)});\n if(!res.ok){ const d=await res.json(); toast(d.error||'Save failed','err'); return; }\n currentConfig = await fetchAPI('/config');\n clearDirty();\n toast('Configuration saved','ok');\n updateConfigCheckPolling();\n // Re-render current section with protected values from server\n var activeSec = document.querySelector('.section.active');\n if(activeSec){\n var id = activeSec.id.replace('sec-','');\n if(id==='models') renderModelsTable();\n if(id==='vars') renderVarsTable();\n }\n }catch(e){ console.error('Save failed:',e); toast('Save failed: '+(e.message||e),'err'); }\n}\n"}
|
|
1
|
+
export function configJS(){return"\n/* ---- Env var ref markers ---- */\nfunction markEnvRefs(container){\n if(!container) return;\n container.querySelectorAll('input').forEach(function(inp){\n if(/^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$/.test(inp.value)){\n inp.classList.add('env-ref');\n inp.title='Stored as environment variable in .env';\n } else {\n inp.classList.remove('env-ref');\n inp.title='';\n }\n });\n}\n\n/* ---- STT ---- */\nasync function loadSTT(){\n currentConfig = await fetchAPI('/config');\n const s = currentConfig.stt||{};\n document.getElementById('sttEnabled').checked = !!s.enabled;\n document.getElementById('sttProvider').value = s.provider||'openai-whisper';\n const oai = s['openai-whisper']||{};\n populateSTTModelSelect();\n var sttModels = (currentConfig&¤tConfig.models)||[];\n document.getElementById('sttModelRef').value = typeof upgradeModelRef==='function' ? upgradeModelRef(oai.modelRef||'', sttModels) : (oai.modelRef||'');\n document.getElementById('sttOAILang').value = oai.language||'';\n const loc = s['local-whisper']||{};\n document.getElementById('sttLocalBin').value = loc.binaryPath||'whisper';\n document.getElementById('sttLocalModel').value = loc.model||'base';\n updateSTTFields();\n if(!loadSTT._bound){\n document.getElementById('sttProvider').addEventListener('change', updateSTTFields);\n loadSTT._bound = true;\n }\n sectionsLoaded.stt = true;\n}\nfunction updateSTTFields(){\n const prov = document.getElementById('sttProvider').value;\n document.getElementById('sttOpenAI').style.display = prov==='openai-whisper'?'':'none';\n document.getElementById('sttLocal').style.display = prov==='local-whisper'?'':'none';\n}\n\n/* ---- Memory ---- */\nasync function loadMemory(){\n currentConfig = await fetchAPI('/config');\n const m = currentConfig.memory||{};\n document.getElementById('memEnabled').checked = !!m.enabled;\n document.getElementById('memDir').value = m.dir||'./memory';\n document.getElementById('memStrategy').value = m.recallStrategy||'builtin-only';\n // Search settings\n const s = m.search||{};\n populateMemSearchModelSelect();\n var memModels = (currentConfig&¤tConfig.models)||[];\n document.getElementById('memSearchModelRef').value = typeof upgradeModelRef==='function' ? upgradeModelRef(s.modelRef||'', memModels) : (s.modelRef||'');\n document.getElementById('memSearchEmbModel').value = s.embeddingModel||'text-embedding-3-small';\n document.getElementById('memSearchPrefixQuery').value = s.prefixQuery||'';\n document.getElementById('memSearchPrefixDocument').value = s.prefixDocument||'';\n var dimsVal = String(s.embeddingDimensions||1536);\n document.getElementById('memSearchDims').value = dimsVal;\n document.getElementById('memSearchDimsValue').textContent = dimsVal;\n document.getElementById('memSearchMaxResults').value = s.maxResults||6;\n document.getElementById('memSearchDebounce').value = s.updateDebounceMs||3000;\n document.getElementById('memSearchEmbedInterval').value = s.embedIntervalMs||300000;\n document.getElementById('memSearchMaxSnippet').value = s.maxSnippetChars||700;\n document.getElementById('memSearchMaxInjected').value = s.maxInjectedChars||4000;\n document.getElementById('memSearchRrfK').value = s.rrfK||60;\n updateMemSearchFields();\n // Intercept toggle-off\n document.getElementById('memEnabled').addEventListener('change', function(){\n if(!this.checked){\n this.checked = true; // revert until confirmed\n document.getElementById('memDisableModal').classList.add('open');\n }\n });\n sectionsLoaded.memory = true;\n}\nfunction updateMemSearchFields(){\n var strategy = document.getElementById('memStrategy').value;\n document.getElementById('memSearchSettings').style.display = strategy==='search'?'':'none';\n}\nasync function testEmbedding(){\n var btn = document.getElementById('memSearchTestBtn');\n var result = document.getElementById('memSearchTestResult');\n btn.disabled = true;\n btn.textContent = 'Testing...';\n result.style.color = 'var(--text-muted)';\n result.textContent = '';\n try {\n var body = {\n modelRef: document.getElementById('memSearchModelRef').value,\n embeddingModel: document.getElementById('memSearchEmbModel').value || 'text-embedding-3-small',\n embeddingDimensions: parseInt(document.getElementById('memSearchDims').value) || 1536\n };\n var resp = await fetch(API+'/memory-search/test-embedding', {method:'POST', headers:{'Content-Type':'application/json','X-CSRF-Token':_csrfToken}, credentials:'include', body:JSON.stringify(body)});\n var data = await resp.json();\n if(data.ok){\n result.style.color = 'var(--success, #22c55e)';\n result.textContent = '✓ OK — model: '+data.model+', dims: '+data.dimensions+', latency: '+data.latencyMs+'ms';\n } else {\n result.style.color = 'var(--error, #ef4444)';\n result.textContent = '✗ '+data.error;\n }\n } catch(err){\n result.style.color = 'var(--error, #ef4444)';\n result.textContent = '✗ Connection error: '+err.message;\n } finally {\n btn.disabled = false;\n btn.textContent = 'Test Embedding';\n }\n}\nfunction closeMemDisableModal(confirmed){\n document.getElementById('memDisableModal').classList.remove('open');\n if(confirmed){\n document.getElementById('memEnabled').checked = false;\n saveConfig();\n }\n}\n\n/* ---- Settings ---- */\nasync function loadSettings(){\n currentConfig = await fetchAPI('/config');\n document.getElementById('settingsHost').value = currentConfig.host || '127.0.0.1';\n document.getElementById('settingsUiPort').value = (currentConfig.nostromo && currentConfig.nostromo.port) || '3001';\n document.getElementById('settingsTimezone').value = currentConfig.timezone || '';\n document.getElementById('settingsAutoRestart').checked = !!(currentConfig.nostromo && currentConfig.nostromo.autoRestart);\n document.getElementById('settingsConfigCheckInterval').value = (currentConfig.nostromo && currentConfig.nostromo.configCheckInterval) || 5;\n sectionsLoaded.settings = true;\n}\n\n/* ---- Access key display ---- */\nvar _currentKey = '';\nvar _keyRevealed = false;\n\nfunction maskKey(key){\n // Show first 4 chars, mask the rest: \"ABCD-****-****-****\"\n if(key.length<=4) return key;\n return key.substring(0,4) + key.substring(4).replace(/[A-Za-z0-9]/g, '*');\n}\n\nasync function loadCurrentKey(){\n try{\n const res = await fetch(API+'/key');\n const data = await res.json();\n _currentKey = data.key || '';\n _keyRevealed = false;\n var row = document.getElementById('currentKeyRow');\n var el = document.getElementById('currentKeyValue');\n if(_currentKey && _currentKey !== '0000'){\n el.textContent = maskKey(_currentKey);\n row.style.display = 'flex';\n } else {\n row.style.display = 'none';\n }\n document.getElementById('keyEyeOff').style.display = '';\n document.getElementById('keyEyeOn').style.display = 'none';\n }catch(e){}\n}\n\nfunction toggleKeyVisibility(){\n _keyRevealed = !_keyRevealed;\n var el = document.getElementById('currentKeyValue');\n el.textContent = _keyRevealed ? _currentKey : maskKey(_currentKey);\n document.getElementById('keyEyeOff').style.display = _keyRevealed ? 'none' : '';\n document.getElementById('keyEyeOn').style.display = _keyRevealed ? '' : 'none';\n}\n\nasync function copyKey(){\n try{\n await navigator.clipboard.writeText(_currentKey);\n document.getElementById('keyCopyIcon').style.display = 'none';\n document.getElementById('keyCheckIcon').style.display = '';\n setTimeout(function(){\n document.getElementById('keyCopyIcon').style.display = '';\n document.getElementById('keyCheckIcon').style.display = 'none';\n }, 1500);\n }catch(e){ toast('Copy failed','err'); }\n}\n\n/* ---- Regenerate key ---- */\nfunction confirmRegenKey(){\n document.getElementById('regenKeyModal').classList.add('open');\n}\nfunction closeRegenKeyModal(){\n document.getElementById('regenKeyModal').classList.remove('open');\n}\nasync function regenKey(){\n try{\n const res = await fetch(API+'/key/regenerate',{method:'POST',headers:{'X-CSRF-Token':_csrfToken}});\n const data = await res.json();\n if(data.key){\n showNewKey(data.key);\n _currentKey = data.key;\n _keyRevealed = false;\n var el = document.getElementById('currentKeyValue');\n el.textContent = maskKey(data.key);\n document.getElementById('currentKeyRow').style.display = 'flex';\n document.getElementById('keyEyeOff').style.display = '';\n document.getElementById('keyEyeOn').style.display = 'none';\n }\n }catch(e){ toast('Failed','err'); }\n}\n\n/* ---- Save config ---- */\nfunction gatherConfig(){\n if(!currentConfig){ toast('Config not loaded yet — cannot save','err'); return null; }\n const cfg = JSON.parse(JSON.stringify(currentConfig));\n // Verbose debug logs\n var logVerboseEl = document.getElementById('logVerbose');\n if(logVerboseEl) cfg.verboseDebugLogs = logVerboseEl.checked;\n // Channels\n const wrap = document.getElementById('channelCards');\n if(wrap){\n if(!cfg.channels) cfg.channels={};\n for(const ch of CHANNEL_LIST){\n const toggle = wrap.querySelector('[data-ch-toggle=\"'+ch+'\"]');\n if(!toggle) continue;\n if(!cfg.channels[ch]) cfg.channels[ch]={};\n cfg.channels[ch].enabled = toggle.checked;\n }\n // Collect all channel fields via data-ch-field=\"channel.account.key\" or \"channel.key\"\n wrap.querySelectorAll('[data-ch-field]').forEach(inp=>{\n const parts = inp.dataset.chField.split('.');\n const chName = parts[0];\n if(!cfg.channels[chName]) cfg.channels[chName]={};\n if(parts.length === 3){\n // channel.account.key\n const [, acct, key] = parts;\n if(!cfg.channels[chName].accounts) cfg.channels[chName].accounts={};\n if(!cfg.channels[chName].accounts[acct]) cfg.channels[chName].accounts[acct]={};\n var val;\n if(key==='allowFrom') val = inp.value.split(',').map(function(s){return s.trim()}).filter(Boolean);\n else if(inp.type==='number') val = parseInt(inp.value)||0;\n else val = inp.value;\n cfg.channels[chName].accounts[acct][key] = val;\n } else if(parts.length === 2){\n // channel.key (e.g. responses.port)\n const key = parts[1];\n cfg.channels[chName][key] = inp.type==='number' ? (parseInt(inp.value)||0) : inp.value;\n }\n });\n }\n // Models\n if(currentConfig&¤tConfig.models) cfg.models = currentConfig.models;\n // Agent — only gather from DOM if loadAgent() has populated the fields\n if(sectionsLoaded.agent){\n cfg.agent = cfg.agent||{};\n cfg.agent.model = document.getElementById('agentModel').value;\n cfg.agent.mainFallback = document.getElementById('agentMainFallback').value;\n cfg.agent.maxTurns = parseInt(document.getElementById('agentMaxTurns').value)||10;\n cfg.agent.permissionMode = document.getElementById('agentPermMode').value;\n cfg.agent.sessionTTL = parseInt(document.getElementById('agentSessionTTL').value)||3600;\n cfg.agent.settingSources = document.getElementById('agentSettingSources').value;\n cfg.agent.builtinCoderSkill = document.getElementById('agentCoderSkill').checked;\n cfg.agent.autoRenew = parseInt(document.getElementById('agentAutoRenew').value)||0;\n cfg.agent.allowedTools = [];\n document.querySelectorAll('#agentToolsGrid [data-tool]').forEach(function(cb){if(cb.checked) cfg.agent.allowedTools.push(cb.dataset.tool);});\n cfg.agent.disallowedTools = [];\n cfg.agent.queueMode = document.getElementById('agentQueueMode').value;\n cfg.agent.queueDebounceMs = parseInt(document.getElementById('agentDebounceMs').value)||0;\n cfg.agent.queueCap = parseInt(document.getElementById('agentQueueCap').value)||0;\n cfg.agent.queueDropPolicy = document.getElementById('agentDropPolicy').value;\n cfg.agent.inflightTyping = document.getElementById('agentInflightTyping').checked;\n cfg.agent.autoApproveTools = document.getElementById('agentAutoApprove').checked;\n // Pico Agent\n cfg.agent.picoAgent = {\n enabled: document.getElementById('picoEnabled').checked,\n modelRefs: _picoModels.map(function(m){ return m.name + ':' + m.piProvider + ':' + m.piModelId; }),\n rollingMemoryModel: document.getElementById('picoRollingModel').value\n };\n delete cfg.agent.engine; // remove legacy\n }\n // Custom SubAgents (preserved from currentConfig, mutated in-place by the UI)\n if(currentConfig && currentConfig.agent && currentConfig.agent.customSubAgents){\n if(!cfg.agent) cfg.agent = {};\n cfg.agent.customSubAgents = currentConfig.agent.customSubAgents;\n }\n // Plugins (preserved from currentConfig, mutated in-place by the UI)\n if(currentConfig && currentConfig.agent && currentConfig.agent.plugins){\n if(!cfg.agent) cfg.agent = {};\n cfg.agent.plugins = currentConfig.agent.plugins;\n }\n // STT — only gather from DOM if loadSTT() has populated the fields\n if(sectionsLoaded.stt){\n cfg.stt = cfg.stt||{};\n cfg.stt.enabled = document.getElementById('sttEnabled').checked;\n cfg.stt.provider = document.getElementById('sttProvider').value;\n var sttModelName = document.getElementById('sttModelRef').value;\n cfg.stt['openai-whisper'] = {\n modelRef: sttModelName,\n model: 'whisper-1',\n language: document.getElementById('sttOAILang').value\n };\n cfg.stt['local-whisper'] = {\n binaryPath: document.getElementById('sttLocalBin').value,\n model: document.getElementById('sttLocalModel').value\n };\n }\n // TTS — only gather from DOM if loadTTS() has populated the fields\n if(sectionsLoaded.tts){\n cfg.tts = cfg.tts||{};\n cfg.tts.enabled = document.getElementById('ttsEnabled').checked;\n cfg.tts.provider = document.getElementById('ttsProvider').value;\n cfg.tts.maxTextLength = parseInt(document.getElementById('ttsMaxTextLength').value)||4096;\n cfg.tts.timeoutMs = parseInt(document.getElementById('ttsTimeoutMs').value)||30000;\n cfg.tts.edge = {\n voice: document.getElementById('ttsEdgeVoice').value || 'en-US-MichelleNeural'\n };\n cfg.tts.openai = {\n modelRef: document.getElementById('ttsOAIModelRef').value,\n model: document.getElementById('ttsOAIModel').value || 'gpt-4o-mini-tts',\n voice: document.getElementById('ttsOAIVoice').value || 'alloy'\n };\n cfg.tts.elevenlabs = {\n modelRef: document.getElementById('ttsELModelRef').value,\n voiceId: document.getElementById('ttsELVoiceId').value || 'pMsXgVXv3BLzUgSXRplE',\n modelId: document.getElementById('ttsELModelId').value || 'eleven_multilingual_v2'\n };\n }\n // Memory — only gather from DOM if loadMemory() has populated the fields\n if(sectionsLoaded.memory){\n cfg.memory = cfg.memory||{};\n cfg.memory.enabled = document.getElementById('memEnabled').checked;\n cfg.memory.dir = document.getElementById('memDir').value;\n cfg.memory.recallStrategy = document.getElementById('memStrategy').value;\n cfg.memory.search = {\n enabled: cfg.memory.recallStrategy === 'search',\n modelRef: document.getElementById('memSearchModelRef').value,\n embeddingModel: document.getElementById('memSearchEmbModel').value || 'text-embedding-3-small',\n prefixQuery: document.getElementById('memSearchPrefixQuery').value || '',\n prefixDocument: document.getElementById('memSearchPrefixDocument').value || '',\n embeddingDimensions: parseInt(document.getElementById('memSearchDims').value) || 1536,\n updateDebounceMs: parseInt(document.getElementById('memSearchDebounce').value) || 3000,\n embedIntervalMs: parseInt(document.getElementById('memSearchEmbedInterval').value) || 300000,\n maxResults: parseInt(document.getElementById('memSearchMaxResults').value) || 6,\n maxSnippetChars: parseInt(document.getElementById('memSearchMaxSnippet').value) || 700,\n maxInjectedChars: parseInt(document.getElementById('memSearchMaxInjected').value) || 4000,\n rrfK: parseInt(document.getElementById('memSearchRrfK').value) || 60\n };\n }\n // Cron — only gather from DOM if loadCron() has populated the fields\n if(sectionsLoaded.cron){\n cfg.cron = cfg.cron||{};\n cfg.cron.enabled = document.getElementById('cronEnabled').checked;\n cfg.cron.isolated = document.getElementById('cronIsolated').checked;\n cfg.cron.broadcastEvents = document.getElementById('cronBroadcast').checked;\n cfg.cron.heartbeat = cfg.cron.heartbeat||{};\n cfg.cron.heartbeat.enabled = document.getElementById('hbEnabled').checked;\n cfg.cron.heartbeat.channel = document.getElementById('hbChannel').value;\n cfg.cron.heartbeat.chatId = document.getElementById('hbChatId').value;\n cfg.cron.heartbeat.every = parseInt(document.getElementById('hbEvery').value)||1800000;\n cfg.cron.heartbeat.message = document.getElementById('hbMessage').value;\n cfg.cron.heartbeat.ackMaxChars = parseInt(document.getElementById('hbAckMaxChars').value)||300;\n }\n // Settings — only gather from DOM if loadSettings() has populated the fields\n if(sectionsLoaded.settings){\n cfg.host = document.getElementById('settingsHost').value || '127.0.0.1';\n cfg.timezone = document.getElementById('settingsTimezone').value || '';\n cfg.nostromo = cfg.nostromo||{};\n cfg.nostromo.port = parseInt(document.getElementById('settingsUiPort').value)||3001;\n cfg.nostromo.autoRestart = document.getElementById('settingsAutoRestart').checked;\n cfg.nostromo.configCheckInterval = parseInt(document.getElementById('settingsConfigCheckInterval').value)||5;\n }\n return cfg;\n}\nasync function saveConfig(){\n const cfg = gatherConfig();\n if(!cfg) return;\n // Validate memory search prefix fields contain {content} if non-empty\n if(cfg.memory && cfg.memory.search){\n var pq = (cfg.memory.search.prefixQuery||'').trim();\n var pd = (cfg.memory.search.prefixDocument||'').trim();\n if(pq && pq.indexOf('{content}')===-1){ toast('Prefix Query must contain the {content} placeholder. Save aborted.','err'); return; }\n if(pd && pd.indexOf('{content}')===-1){ toast('Prefix Document must contain the {content} placeholder. Save aborted.','err'); return; }\n }\n // Block save if heartbeat is enabled but message is too short\n if(cfg.cron && cfg.cron.heartbeat && cfg.cron.heartbeat.enabled){\n var hbMsg = (cfg.cron.heartbeat.message||'').trim();\n if(hbMsg.length < 15){\n toast('Heartbeat message is too short (minimum 15 characters). Save aborted.','err');\n return;\n }\n }\n try{\n const res = await fetch(API+'/config',{method:'PUT',headers:{'Content-Type':'application/json','X-CSRF-Token':_csrfToken},body:JSON.stringify(cfg)});\n if(!res.ok){ const d=await res.json(); toast(d.error||'Save failed','err'); return; }\n currentConfig = await fetchAPI('/config');\n clearDirty();\n toast('Configuration saved','ok');\n updateConfigCheckPolling();\n // Re-render current section with protected values from server\n var activeSec = document.querySelector('.section.active');\n if(activeSec){\n var id = activeSec.id.replace('sec-','');\n if(id==='models') renderModelsTable();\n if(id==='vars') renderVarsTable();\n }\n }catch(e){ console.error('Save failed:',e); toast('Save failed: '+(e.message||e),'err'); }\n}\n"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function coreJS(e,n=""){return`\nconst API = '${n}/api';\nconst IS_FIRST_RUN = ${e};\n\n// Override fetch to include credentials and CSRF token\nvar _origFetch = window.fetch;\nvar _csrfToken = sessionStorage.getItem('csrfToken') || '';\nwindow.fetch = function(url, opts) {\n opts = opts || {};\n opts.credentials = 'include';\n // Add CSRF token header on mutative requests\n if(opts.method && opts.method !== 'GET') {\n opts.headers = opts.headers || {};\n if(opts.headers instanceof Headers) {\n opts.headers.set('X-CSRF-Token', _csrfToken);\n } else {\n opts.headers['X-CSRF-Token'] = _csrfToken;\n }\n }\n return _origFetch.call(window, url, opts);\n};\nlet currentConfig = null;\nvar sectionsLoaded = {};\n\n/* ---- Helpers ---- */\nasync function fetchAPI(path){\n const res = await fetch(API+path);\n if(res.status===401){ location.reload(); return; }\n return res.json();\n}\nfunction esc(s){ const d=document.createElement('div'); d.textContent=s||''; return d.innerHTML; }\nfunction toggleHelp(btn){ const el=document.getElementById(btn.dataset.help); if(el) el.classList.toggle('open'); }\n\nfunction toast(msg,type){\n const el=document.getElementById('toast');\n el.textContent=msg;\n el.className='toast toast-'+(type||'ok')+' show';\n clearTimeout(el._t);\n el._t=setTimeout(()=>{ el.classList.remove('show'); },2500);\n}\n\n/* ---- First-run setup ---- */\nconst KEY_BOXES = [document.getElementById('k0'),document.getElementById('k1'),document.getElementById('k2'),document.getElementById('k3')];\n(function initLogin(){\n if(IS_FIRST_RUN){\n KEY_BOXES.forEach(b=>{ b.value='0000'; b.disabled=true; });\n document.getElementById('loginSubtitle').textContent='';\n document.getElementById('loginBtn').textContent='Welcome! Press to continue';\n }\n KEY_BOXES.forEach((b,i)=>{\n b.addEventListener('input',()=>{\n b.value = b.value.toUpperCase().replace(/[^A-Z0-9]/g,'');\n if(b.value.length===4 && i<3) KEY_BOXES[i+1].focus();\n });\n b.addEventListener('keydown',e=>{\n if(e.key==='Backspace' && b.value==='' && i>0){ KEY_BOXES[i-1].focus(); }\n if(e.key==='Enter') doLogin();\n });\n b.addEventListener('paste',e=>{\n e.preventDefault();\n var raw = (e.clipboardData||window.clipboardData).getData('text');\n raw = raw.trim().toUpperCase();\n raw = raw.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015\\uFE58\\uFE63\\uFF0D]/g,'-');\n raw = raw.replace(/-/g,'');\n raw = raw.replace(/[^A-Z0-9]/g,'');\n raw = raw.slice(0,16);\n for(var j=0;j<4;j++){\n KEY_BOXES[j].value = raw.slice(j*4, j*4+4);\n }\n });\n });\n})();\n\n/* ---- Eye toggle ---- */\nvar keyVisible = false;\nfunction toggleKeyVis(){\n keyVisible = !keyVisible;\n KEY_BOXES.forEach(b=>{ b.type = keyVisible ? 'text' : 'password'; });\n document.getElementById('eyeOn').style.display = keyVisible ? '' : 'none';\n document.getElementById('eyeOff').style.display = keyVisible ? 'none' : '';\n}\n\n/* ---- Auth ---- */\nasync function doLogin(){\n const key = IS_FIRST_RUN ? '0000' : [document.getElementById('k0').value,document.getElementById('k1').value,document.getElementById('k2').value,document.getElementById('k3').value].join('-');\n const errEl = document.getElementById('loginError');\n errEl.style.display = 'none';\n try {\n const res = await fetch(API+'/auth/login', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({key})});\n const data = await res.json();\n if(!res.ok){ errEl.textContent = data.error||'Invalid key'; errEl.style.display='block'; return; }\n if(data.csrfToken){ _csrfToken = data.csrfToken; sessionStorage.setItem('csrfToken', _csrfToken); }\n currentConfig = await fetchAPI('/config');\n document.getElementById('loginView').style.display='none';\n document.getElementById('appShell').style.display='flex';\n startSessionCheck();\n if(data.newKey){\n navigate('settings');\n showNewKey(data.newKey);\n } else {\n loadDashboard();\n }\n } catch(e){ errEl.textContent='Connection failed'; errEl.style.display='block'; }\n}\nasync function doLogout(){\n await fetch(API+'/auth/logout',{method:'POST',headers:{'X-CSRF-Token':_csrfToken}});\n location.reload();\n}\nfunction showNewKey(key){\n document.getElementById('newKeyDisplay').innerHTML =\n '<div class="key-card"><div class="key-label">Your new access key (save it now)</div><div class="key-value">'+esc(key)+'</div><div class="key-label">This key will not be shown again</div></div>';\n}\n\n/* ---- Navigation ---- */\nfunction toggleNavGroup(name){\n var el = document.getElementById('navGroup'+name);\n if(el) el.classList.toggle('collapsed');\n}\nfunction navigate(section){\n // Clean up logs auto-refresh when navigating away\n if(logAutoTimer){ clearInterval(logAutoTimer); logAutoTimer=null; }\n var arCb = document.getElementById('logAutoRefresh');\n if(arCb) arCb.checked = false;\n // Discard any unsaved in-memory changes: force fresh config on next load\n currentConfig = null;\n sectionsLoaded = {};\n if(typeof _internalToolsLoaded !== 'undefined') _internalToolsLoaded = false;\n clearDirty();\n document.querySelectorAll('.section').forEach(s=>s.classList.remove('active'));\n document.querySelectorAll('#navMenu a').forEach(a=>a.classList.remove('active'));\n const sec = document.getElementById('sec-'+section);\n if(sec) sec.classList.add('active');\n const link = document.querySelector('#navMenu a[data-section="'+section+'"]');\n if(link) link.classList.add('active');\n // Auto-expand parent group if navigating to a child section\n var groupMap = {\n channels:'Interactions', stt:'Interactions', tts:'Interactions',\n models:'Engine', vars:'Engine', agent:'Engine', subagents:'Engine',\n skills:'Competences', commands:'Competences', plugins:'Competences',\n memory:'Identity', prompts:'Identity',\n logs:'Operations', cron:'Operations', nodes:'Operations', tokens:'Operations'\n };\n if(groupMap[section]){\n var grp = document.getElementById('navGroup'+groupMap[section]);\n if(grp) grp.classList.remove('collapsed');\n }\n if(section==='dashboard') loadDashboard();\n if(section==='channels') loadChannels();\n if(section==='models') loadModels();\n if(section==='vars') loadVars();\n if(section==='agent') loadAgent();\n if(section==='subagents') loadSubAgents();\n if(section==='skills') loadSkills();\n if(section==='commands') loadCommands();\n if(section==='plugins') loadPlugins();\n if(section==='stt') loadSTT();\n if(section==='tts') loadTTS();\n if(section==='memory') loadMemory();\n if(section==='logs') loadLogs();\n if(section==='cron') loadCron();\n if(section==='nodes') loadNodes();\n if(section==='tokens') loadTokens();\n if(section==='prompts') loadPrompts();\n if(section==='settings'){ loadSettings(); loadCurrentKey(); }\n}\ndocument.getElementById('navMenu').addEventListener('click',e=>{\n const a = e.target.closest('a[data-section]');\n if(!a) return;\n e.preventDefault();\n navigate(a.dataset.section);\n});\n\n/* ---- Dashboard ---- */\nasync function loadDashboard(){\n try{\n const [cfgRes,statusRes] = await Promise.all([fetchAPI('/config'),fetchAPI('/status')]);\n currentConfig = cfgRes;\n const st = statusRes;\n document.getElementById('statusUptime').textContent = st.uptime||'--';\n document.getElementById('statusModel').textContent = cfgRes.agent?.model||'--';\n document.getElementById('statusFallback').textContent = cfgRes.agent?.mainFallback||'--';\n var arEnabled = !!(cfgRes.nostromo && cfgRes.nostromo.autoRestart);\n var arEl = document.getElementById('statusAutoRestart');\n if(arEnabled){\n arEl.innerHTML = '<span class="badge badge-green">On</span>';\n } else {\n arEl.innerHTML = '<span class="badge badge-red">Off</span>';\n }\n const ch = document.getElementById('dashChannels');\n ch.innerHTML='';\n const channels = cfgRes.channels||{};\n for(const[name,cfg]of Object.entries(channels)){\n if(cfg.enabled){\n ch.innerHTML += '<span class="badge badge-green">'+esc(name)+'</span>';\n }\n }\n if(!ch.innerHTML) ch.innerHTML='<span style="font-size:14px;color:var(--text-muted)">No channels enabled</span>';\n updateConfigCheckPolling();\n }catch(e){ toast('Failed to load dashboard','err'); }\n}\n\n/* ---- WhatsApp QR Modal ---- */\nvar qrPollTimer = null;\nfunction openWhatsAppQr(){\n var modal = document.getElementById('qrModal');\n var img = document.getElementById('qrImg');\n var msg = document.getElementById('qrMsg');\n var spinner = document.getElementById('qrSpinner');\n var status = document.getElementById('qrStatus');\n img.style.display='none';\n msg.textContent='Waiting for QR code...';\n spinner.style.display='block';\n status.innerHTML='';\n modal.classList.add('open');\n pollWhatsAppQr();\n}\nfunction closeQrModal(){\n document.getElementById('qrModal').classList.remove('open');\n if(qrPollTimer){ clearTimeout(qrPollTimer); qrPollTimer=null; }\n}\nfunction pollWhatsAppQr(){\n fetch(API+'/whatsapp/qr').then(function(r){ return r.json(); }).then(function(data){\n var img = document.getElementById('qrImg');\n var msg = document.getElementById('qrMsg');\n var spinner = document.getElementById('qrSpinner');\n var status = document.getElementById('qrStatus');\n if(data.connected){\n img.style.display='none';\n spinner.style.display='none';\n msg.textContent='';\n status.innerHTML='<div class="status-msg ok">WhatsApp connected successfully!</div>';\n return;\n }\n if(data.error){\n img.style.display='none';\n spinner.style.display='none';\n msg.textContent='';\n status.innerHTML='<div class="status-msg err">'+data.error+'</div>';\n return;\n }\n if(data.dataUrl){\n img.src=data.dataUrl;\n img.style.display='block';\n spinner.style.display='none';\n msg.textContent='Scan this QR code in WhatsApp';\n } else {\n img.style.display='none';\n spinner.style.display='block';\n msg.textContent='Waiting for QR code...';\n }\n qrPollTimer = setTimeout(pollWhatsAppQr, 2000);\n }).catch(function(){\n qrPollTimer = setTimeout(pollWhatsAppQr, 3000);\n });\n}\n\n/* ---- Restart ---- */\nfunction showRestartModal(){\n document.getElementById('restartModal').classList.add('open');\n}\nfunction closeRestartModal(){\n document.getElementById('restartModal').classList.remove('open');\n}\nasync function doRestart(){\n closeRestartModal();\n try{\n const res = await fetch(API+'/server/restart',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':_csrfToken}});\n const data = await res.json();\n if(data.ok){\n toast('Server restarted successfully','ok');\n setRestartIndicators(false);\n loadDashboard();\n } else {\n toast(data.error||'Restart failed','err');\n }\n }catch(e){\n toast('Restart failed: '+e,'err');\n }\n}\n\n/* ---- Theme ---- */\nfunction initTheme(){\n const saved = localStorage.getItem('nostromo-theme')||'light';\n document.documentElement.setAttribute('data-theme', saved);\n const toggle = document.getElementById('themeToggle');\n if(toggle) toggle.checked = saved==='dark';\n updateThemeLabel();\n}\nfunction toggleTheme(){\n const isDark = document.getElementById('themeToggle').checked;\n const theme = isDark?'dark':'light';\n document.documentElement.setAttribute('data-theme', theme);\n localStorage.setItem('nostromo-theme', theme);\n updateThemeLabel();\n if(typeof monaco !== 'undefined') updateAllMonacoThemes();\n}\nfunction updateThemeLabel(){\n const el = document.getElementById('themeLabel');\n if(el) el.textContent = document.documentElement.getAttribute('data-theme')==='dark'?'Dark':'Light';\n}\n\n/* ---- Auto-save & dirty tracking ---- */\nvar autoSaveTimer = null;\n// IDs of elements that should NOT trigger auto-save\nvar AUTOSAVE_SKIP = ['themeToggle','logAutoRefresh','logLinesSelect','logLevelSelect'];\n\nfunction markDirty(){\n // Enable the save button in the currently active section\n var sec = document.querySelector('.section.active');\n if(!sec) return;\n var btn = sec.querySelector('.save-btn');\n if(btn) btn.disabled = false;\n}\nfunction clearDirty(){\n document.querySelectorAll('.save-btn').forEach(function(b){ b.disabled = true; });\n}\n\n// Event delegation: auto-save on toggle/select change, mark dirty on text input\ndocument.querySelector('.main').addEventListener('change', function(e){\n var el = e.target;\n // Skip non-config elements\n if(AUTOSAVE_SKIP.indexOf(el.id)!==-1) return;\n if(el.closest('#addModelForm')||el.closest('#editModelForm')||el.closest('#addVarForm')||el.closest('#editVarForm')||el.closest('#createTokenForm')||el.closest('#addJobForm')) return;\n if(el.closest('#sec-tokens')||el.closest('#sec-logs')||el.closest('#sec-prompts')) return;\n // Toggles and selects: auto-save immediately\n if(el.type==='checkbox'||el.tagName==='SELECT'){\n clearTimeout(autoSaveTimer);\n autoSaveTimer = setTimeout(function(){ saveConfig(); }, 300);\n }\n});\ndocument.querySelector('.main').addEventListener('input', function(e){\n var el = e.target;\n if(el.closest('#addModelForm')||el.closest('#editModelForm')||el.closest('#addVarForm')||el.closest('#editVarForm')||el.closest('#createTokenForm')||el.closest('#addJobForm')) return;\n if(el.closest('#sec-tokens')||el.closest('#sec-logs')||el.closest('#sec-prompts')) return;\n // Text inputs, textareas, number inputs: mark dirty\n if(el.tagName==='INPUT'||el.tagName==='TEXTAREA'){\n var t = el.type||'text';\n if(t==='text'||t==='password'||t==='number'||el.tagName==='TEXTAREA'){\n markDirty();\n }\n }\n});\n\n/* ---- Init ---- */\nasync function checkSession(){\n try{\n const res = await fetch(API+'/auth/session');\n if(res.ok){\n const data = await res.json();\n if(data.csrfToken){ _csrfToken = data.csrfToken; sessionStorage.setItem('csrfToken', _csrfToken); }\n currentConfig = await fetchAPI('/config');\n document.getElementById('loginView').style.display='none';\n document.getElementById('appShell').style.display='flex';\n startSessionCheck();\n loadDashboard();\n }\n }catch(e){}\n}\n/* ---- Config change polling ---- */\nvar _configCheckTimer = null;\nfunction setRestartIndicators(visible){\n var fl = document.getElementById('floatingRestart'); if(fl) fl.style.display = visible ? 'flex' : 'none';\n var rp = document.getElementById('restartPending'); if(rp) rp.style.display = visible ? '' : 'none';\n}\nfunction startConfigCheck(){\n stopConfigCheck();\n _configCheckTimer = setInterval(async function(){\n try{\n var res = await fetch(API+'/config/check');\n if(!res.ok) return;\n var data = await res.json();\n setRestartIndicators(data.restartNeeded);\n }catch(e){}\n }, 5000);\n}\nfunction stopConfigCheck(){\n if(_configCheckTimer){ clearInterval(_configCheckTimer); _configCheckTimer=null; }\n setRestartIndicators(false);\n}\nfunction updateConfigCheckPolling(){\n if(currentConfig && currentConfig.nostromo && currentConfig.nostromo.autoRestart){\n stopConfigCheck();\n } else {\n startConfigCheck();\n }\n}\n\n/* ---- Session heartbeat ---- */\nvar _sessionCheckTimer = null;\nfunction startSessionCheck(){\n if(_sessionCheckTimer) return;\n _sessionCheckTimer = setInterval(async function(){\n try {\n var res = await _origFetch(API+'/auth/session', { credentials:'include' });\n if(res.status === 401){\n clearInterval(_sessionCheckTimer);\n _sessionCheckTimer = null;\n document.getElementById('sessionExpiredModal').classList.add('open');\n }\n } catch(e) {\n // Server unreachable — show expired modal\n clearInterval(_sessionCheckTimer);\n _sessionCheckTimer = null;\n document.getElementById('sessionExpiredModal').classList.add('open');\n }\n }, 10000);\n}\n\ninitTheme();\ncheckSession();\n`}
|
|
1
|
+
export function coreJS(e,n=""){return`\nconst API = '${n}/api';\nconst IS_FIRST_RUN = ${e};\n\n// Override fetch to include credentials and CSRF token\nvar _origFetch = window.fetch;\nvar _csrfToken = sessionStorage.getItem('csrfToken') || '';\nwindow.fetch = function(url, opts) {\n opts = opts || {};\n opts.credentials = 'include';\n // Add CSRF token header on mutative requests\n if(opts.method && opts.method !== 'GET') {\n opts.headers = opts.headers || {};\n if(opts.headers instanceof Headers) {\n opts.headers.set('X-CSRF-Token', _csrfToken);\n } else {\n opts.headers['X-CSRF-Token'] = _csrfToken;\n }\n }\n return _origFetch.call(window, url, opts);\n};\nlet currentConfig = null;\nvar sectionsLoaded = {};\n\n/* ---- Helpers ---- */\nasync function fetchAPI(path){\n const res = await fetch(API+path);\n if(res.status===401){ location.reload(); return; }\n return res.json();\n}\nfunction esc(s){ const d=document.createElement('div'); d.textContent=s||''; return d.innerHTML; }\nfunction toggleHelp(btn){ const el=document.getElementById(btn.dataset.help); if(el) el.classList.toggle('open'); }\n\nfunction toast(msg,type){\n const el=document.getElementById('toast');\n el.textContent=msg;\n el.className='toast toast-'+(type||'ok')+' show';\n clearTimeout(el._t);\n el._t=setTimeout(()=>{ el.classList.remove('show'); },2500);\n}\n\n/* ---- First-run setup ---- */\nconst KEY_BOXES = [document.getElementById('k0'),document.getElementById('k1'),document.getElementById('k2'),document.getElementById('k3')];\n(function initLogin(){\n if(IS_FIRST_RUN){\n KEY_BOXES.forEach(b=>{ b.value='0000'; b.disabled=true; });\n document.getElementById('loginSubtitle').textContent='';\n document.getElementById('loginBtn').textContent='Welcome! Press to continue';\n }\n KEY_BOXES.forEach((b,i)=>{\n b.addEventListener('input',()=>{\n b.value = b.value.toUpperCase().replace(/[^A-Z0-9]/g,'');\n if(b.value.length===4 && i<3) KEY_BOXES[i+1].focus();\n });\n b.addEventListener('keydown',e=>{\n if(e.key==='Backspace' && b.value==='' && i>0){ KEY_BOXES[i-1].focus(); }\n if(e.key==='Enter') doLogin();\n });\n b.addEventListener('paste',e=>{\n e.preventDefault();\n var raw = (e.clipboardData||window.clipboardData).getData('text');\n raw = raw.trim().toUpperCase();\n raw = raw.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015\\uFE58\\uFE63\\uFF0D]/g,'-');\n raw = raw.replace(/-/g,'');\n raw = raw.replace(/[^A-Z0-9]/g,'');\n raw = raw.slice(0,16);\n for(var j=0;j<4;j++){\n KEY_BOXES[j].value = raw.slice(j*4, j*4+4);\n }\n });\n });\n})();\n\n/* ---- Eye toggle ---- */\nvar keyVisible = false;\nfunction toggleKeyVis(){\n keyVisible = !keyVisible;\n KEY_BOXES.forEach(b=>{ b.type = keyVisible ? 'text' : 'password'; });\n document.getElementById('eyeOn').style.display = keyVisible ? '' : 'none';\n document.getElementById('eyeOff').style.display = keyVisible ? 'none' : '';\n}\n\n/* ---- Auth ---- */\nasync function doLogin(){\n const key = IS_FIRST_RUN ? '0000' : [document.getElementById('k0').value,document.getElementById('k1').value,document.getElementById('k2').value,document.getElementById('k3').value].join('-');\n const errEl = document.getElementById('loginError');\n errEl.style.display = 'none';\n try {\n const res = await fetch(API+'/auth/login', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({key})});\n const data = await res.json();\n if(!res.ok){ errEl.textContent = data.error||'Invalid key'; errEl.style.display='block'; return; }\n if(data.csrfToken){ _csrfToken = data.csrfToken; sessionStorage.setItem('csrfToken', _csrfToken); }\n currentConfig = await fetchAPI('/config');\n document.getElementById('loginView').style.display='none';\n document.getElementById('appShell').style.display='flex';\n startSessionCheck();\n if(data.newKey){\n navigate('settings');\n showNewKey(data.newKey);\n } else {\n loadDashboard();\n }\n } catch(e){ errEl.textContent='Connection failed'; errEl.style.display='block'; }\n}\nasync function doLogout(){\n await fetch(API+'/auth/logout',{method:'POST',headers:{'X-CSRF-Token':_csrfToken}});\n location.reload();\n}\nfunction showNewKey(key){\n document.getElementById('newKeyDisplay').innerHTML =\n '<div class="key-card"><div class="key-label">Your new access key (save it now)</div><div class="key-value">'+esc(key)+'</div><div class="key-label">This key will not be shown again</div></div>';\n}\n\n/* ---- Navigation ---- */\nfunction toggleNavGroup(name){\n var el = document.getElementById('navGroup'+name);\n if(el) el.classList.toggle('collapsed');\n}\nfunction navigate(section){\n // Clean up logs auto-refresh when navigating away\n if(logAutoTimer){ clearInterval(logAutoTimer); logAutoTimer=null; }\n var arCb = document.getElementById('logAutoRefresh');\n if(arCb) arCb.checked = false;\n // Discard any unsaved in-memory changes: force fresh config on next load\n currentConfig = null;\n sectionsLoaded = {};\n if(typeof _internalToolsLoaded !== 'undefined') _internalToolsLoaded = false;\n clearDirty();\n document.querySelectorAll('.section').forEach(s=>s.classList.remove('active'));\n document.querySelectorAll('#navMenu a').forEach(a=>a.classList.remove('active'));\n const sec = document.getElementById('sec-'+section);\n if(sec) sec.classList.add('active');\n const link = document.querySelector('#navMenu a[data-section="'+section+'"]');\n if(link) link.classList.add('active');\n // Auto-expand parent group if navigating to a child section\n var groupMap = {\n channels:'Interactions', stt:'Interactions', tts:'Interactions',\n models:'Engine', vars:'Engine', agent:'Engine', subagents:'Engine',\n skills:'Competences', commands:'Competences', plugins:'Competences',\n memory:'Identity', prompts:'Identity',\n logs:'Operations', cron:'Operations', nodes:'Operations', tokens:'Operations'\n };\n if(groupMap[section]){\n var grp = document.getElementById('navGroup'+groupMap[section]);\n if(grp) grp.classList.remove('collapsed');\n }\n if(section==='dashboard') loadDashboard();\n if(section==='channels') loadChannels();\n if(section==='models') loadModels();\n if(section==='vars') loadVars();\n if(section==='agent') loadAgent();\n if(section==='subagents') loadSubAgents();\n if(section==='skills') loadSkills();\n if(section==='commands') loadCommands();\n if(section==='plugins') loadPlugins();\n if(section==='stt') loadSTT();\n if(section==='tts') loadTTS();\n if(section==='memory') loadMemory();\n if(section==='logs') loadLogs();\n if(section==='cron') loadCron();\n if(section==='nodes') loadNodes();\n if(section==='tokens') loadTokens();\n if(section==='prompts') loadPrompts();\n if(section==='settings'){ loadSettings(); loadCurrentKey(); }\n}\ndocument.getElementById('navMenu').addEventListener('click',e=>{\n const a = e.target.closest('a[data-section]');\n if(!a) return;\n e.preventDefault();\n navigate(a.dataset.section);\n});\n\n/* ---- Dashboard ---- */\nasync function loadDashboard(){\n try{\n const [cfgRes,statusRes] = await Promise.all([fetchAPI('/config'),fetchAPI('/status')]);\n currentConfig = cfgRes;\n const st = statusRes;\n document.getElementById('statusUptime').textContent = st.uptime||'--';\n document.getElementById('statusModel').textContent = (cfgRes.agent?.model||'--').split(':')[0];\n document.getElementById('statusFallback').textContent = (cfgRes.agent?.mainFallback||'--').split(':')[0];\n var arEnabled = !!(cfgRes.nostromo && cfgRes.nostromo.autoRestart);\n var arEl = document.getElementById('statusAutoRestart');\n if(arEnabled){\n arEl.innerHTML = '<span class="badge badge-green">On</span>';\n } else {\n arEl.innerHTML = '<span class="badge badge-red">Off</span>';\n }\n const ch = document.getElementById('dashChannels');\n ch.innerHTML='';\n const channels = cfgRes.channels||{};\n for(const[name,cfg]of Object.entries(channels)){\n if(cfg.enabled){\n ch.innerHTML += '<span class="badge badge-green">'+esc(name)+'</span>';\n }\n }\n if(!ch.innerHTML) ch.innerHTML='<span style="font-size:14px;color:var(--text-muted)">No channels enabled</span>';\n updateConfigCheckPolling();\n }catch(e){ toast('Failed to load dashboard','err'); }\n}\n\n/* ---- WhatsApp QR Modal ---- */\nvar qrPollTimer = null;\nfunction openWhatsAppQr(){\n var modal = document.getElementById('qrModal');\n var img = document.getElementById('qrImg');\n var msg = document.getElementById('qrMsg');\n var spinner = document.getElementById('qrSpinner');\n var status = document.getElementById('qrStatus');\n img.style.display='none';\n msg.textContent='Waiting for QR code...';\n spinner.style.display='block';\n status.innerHTML='';\n modal.classList.add('open');\n pollWhatsAppQr();\n}\nfunction closeQrModal(){\n document.getElementById('qrModal').classList.remove('open');\n if(qrPollTimer){ clearTimeout(qrPollTimer); qrPollTimer=null; }\n}\nfunction pollWhatsAppQr(){\n fetch(API+'/whatsapp/qr').then(function(r){ return r.json(); }).then(function(data){\n var img = document.getElementById('qrImg');\n var msg = document.getElementById('qrMsg');\n var spinner = document.getElementById('qrSpinner');\n var status = document.getElementById('qrStatus');\n if(data.connected){\n img.style.display='none';\n spinner.style.display='none';\n msg.textContent='';\n status.innerHTML='<div class="status-msg ok">WhatsApp connected successfully!</div>';\n return;\n }\n if(data.error){\n img.style.display='none';\n spinner.style.display='none';\n msg.textContent='';\n status.innerHTML='<div class="status-msg err">'+data.error+'</div>';\n return;\n }\n if(data.dataUrl){\n img.src=data.dataUrl;\n img.style.display='block';\n spinner.style.display='none';\n msg.textContent='Scan this QR code in WhatsApp';\n } else {\n img.style.display='none';\n spinner.style.display='block';\n msg.textContent='Waiting for QR code...';\n }\n qrPollTimer = setTimeout(pollWhatsAppQr, 2000);\n }).catch(function(){\n qrPollTimer = setTimeout(pollWhatsAppQr, 3000);\n });\n}\n\n/* ---- Restart ---- */\nfunction showRestartModal(){\n document.getElementById('restartModal').classList.add('open');\n}\nfunction closeRestartModal(){\n document.getElementById('restartModal').classList.remove('open');\n}\nasync function doRestart(){\n closeRestartModal();\n try{\n const res = await fetch(API+'/server/restart',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':_csrfToken}});\n const data = await res.json();\n if(data.ok){\n toast('Server restarted successfully','ok');\n setRestartIndicators(false);\n loadDashboard();\n } else {\n toast(data.error||'Restart failed','err');\n }\n }catch(e){\n toast('Restart failed: '+e,'err');\n }\n}\n\n/* ---- Theme ---- */\nfunction initTheme(){\n const saved = localStorage.getItem('nostromo-theme')||'light';\n document.documentElement.setAttribute('data-theme', saved);\n const toggle = document.getElementById('themeToggle');\n if(toggle) toggle.checked = saved==='dark';\n updateThemeLabel();\n}\nfunction toggleTheme(){\n const isDark = document.getElementById('themeToggle').checked;\n const theme = isDark?'dark':'light';\n document.documentElement.setAttribute('data-theme', theme);\n localStorage.setItem('nostromo-theme', theme);\n updateThemeLabel();\n if(typeof monaco !== 'undefined') updateAllMonacoThemes();\n}\nfunction updateThemeLabel(){\n const el = document.getElementById('themeLabel');\n if(el) el.textContent = document.documentElement.getAttribute('data-theme')==='dark'?'Dark':'Light';\n}\n\n/* ---- Auto-save & dirty tracking ---- */\nvar autoSaveTimer = null;\n// IDs of elements that should NOT trigger auto-save\nvar AUTOSAVE_SKIP = ['themeToggle','logAutoRefresh','logLinesSelect','logLevelSelect'];\n\nfunction markDirty(){\n // Enable the save button in the currently active section\n var sec = document.querySelector('.section.active');\n if(!sec) return;\n var btn = sec.querySelector('.save-btn');\n if(btn) btn.disabled = false;\n}\nfunction clearDirty(){\n document.querySelectorAll('.save-btn').forEach(function(b){ b.disabled = true; });\n}\n\n// Event delegation: auto-save on toggle/select change, mark dirty on text input\ndocument.querySelector('.main').addEventListener('change', function(e){\n var el = e.target;\n // Skip non-config elements\n if(AUTOSAVE_SKIP.indexOf(el.id)!==-1) return;\n if(el.closest('#addModelForm')||el.closest('#editModelForm')||el.closest('#addVarForm')||el.closest('#editVarForm')||el.closest('#createTokenForm')||el.closest('#addJobForm')) return;\n if(el.closest('#sec-tokens')||el.closest('#sec-logs')||el.closest('#sec-prompts')) return;\n // Toggles and selects: auto-save immediately\n if(el.type==='checkbox'||el.tagName==='SELECT'){\n clearTimeout(autoSaveTimer);\n autoSaveTimer = setTimeout(function(){ saveConfig(); }, 300);\n }\n});\ndocument.querySelector('.main').addEventListener('input', function(e){\n var el = e.target;\n if(el.closest('#addModelForm')||el.closest('#editModelForm')||el.closest('#addVarForm')||el.closest('#editVarForm')||el.closest('#createTokenForm')||el.closest('#addJobForm')) return;\n if(el.closest('#sec-tokens')||el.closest('#sec-logs')||el.closest('#sec-prompts')) return;\n // Text inputs, textareas, number inputs: mark dirty\n if(el.tagName==='INPUT'||el.tagName==='TEXTAREA'){\n var t = el.type||'text';\n if(t==='text'||t==='password'||t==='number'||el.tagName==='TEXTAREA'){\n markDirty();\n }\n }\n});\n\n/* ---- Init ---- */\nasync function checkSession(){\n try{\n const res = await fetch(API+'/auth/session');\n if(res.ok){\n const data = await res.json();\n if(data.csrfToken){ _csrfToken = data.csrfToken; sessionStorage.setItem('csrfToken', _csrfToken); }\n currentConfig = await fetchAPI('/config');\n document.getElementById('loginView').style.display='none';\n document.getElementById('appShell').style.display='flex';\n startSessionCheck();\n loadDashboard();\n }\n }catch(e){}\n}\n/* ---- Config change polling ---- */\nvar _configCheckTimer = null;\nfunction setRestartIndicators(visible){\n var fl = document.getElementById('floatingRestart'); if(fl) fl.style.display = visible ? 'flex' : 'none';\n var rp = document.getElementById('restartPending'); if(rp) rp.style.display = visible ? '' : 'none';\n}\nfunction startConfigCheck(){\n stopConfigCheck();\n _configCheckTimer = setInterval(async function(){\n try{\n var res = await fetch(API+'/config/check');\n if(!res.ok) return;\n var data = await res.json();\n setRestartIndicators(data.restartNeeded);\n }catch(e){}\n }, 5000);\n}\nfunction stopConfigCheck(){\n if(_configCheckTimer){ clearInterval(_configCheckTimer); _configCheckTimer=null; }\n setRestartIndicators(false);\n}\nfunction updateConfigCheckPolling(){\n if(currentConfig && currentConfig.nostromo && currentConfig.nostromo.autoRestart){\n stopConfigCheck();\n } else {\n startConfigCheck();\n }\n}\n\n/* ---- Session heartbeat ---- */\nvar _sessionCheckTimer = null;\nfunction startSessionCheck(){\n if(_sessionCheckTimer) return;\n _sessionCheckTimer = setInterval(async function(){\n try {\n var res = await _origFetch(API+'/auth/session', { credentials:'include' });\n if(res.status === 401){\n clearInterval(_sessionCheckTimer);\n _sessionCheckTimer = null;\n document.getElementById('sessionExpiredModal').classList.add('open');\n }\n } catch(e) {\n // Server unreachable — show expired modal\n clearInterval(_sessionCheckTimer);\n _sessionCheckTimer = null;\n document.getElementById('sessionExpiredModal').classList.add('open');\n }\n }, 10000);\n}\n\ninitTheme();\ncheckSession();\n`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function renderStyles(){return'\n:root{\n --bg:#fff;--bg-card:#f3f4f6;--text:#111827;--text-muted:#6b7280;\n --accent:#6366f1;--accent-hover:#4f46e5;--accent-light:#eef2ff;\n --border:#e5e7eb;--danger:#ef4444;--success:#22c55e;\n --radius:8px;--font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;\n}\n[data-theme="dark"]{\n --bg:#0b0b11;--bg-card:#16161f;--text:#e5e5ea;--text-muted:#9ca3af;\n --accent:#818cf8;--accent-hover:#6366f1;--accent-light:#1e1e2e;\n --border:#2d2d3a;--danger:#f87171;--success:#4ade80;\n}\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nbody{font-family:var(--font);font-size:16px;line-height:1.5;color:var(--text);background:var(--bg);min-height:100vh}\na{color:var(--accent);text-decoration:none}\nbutton{cursor:pointer;font:inherit;border:none;border-radius:var(--radius);padding:8px 16px;transition:background .15s,opacity .15s}\ninput,textarea,select{font:inherit;border:1px solid var(--border);border-radius:var(--radius);padding:8px 12px;background:var(--bg);color:var(--text);width:100%;transition:border-color .15s}\ninput:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)}\ntextarea{resize:vertical;min-height:80px}\n\n/* Layout */\n.shell{display:flex;min-height:100vh}\n.sidebar{width:220px;background:var(--bg-card);border-right:1px solid var(--border);padding:20px 0;display:flex;flex-direction:column;position:fixed;top:0;left:0;bottom:0;z-index:10}\n.sidebar .logo{padding:0 20px 20px;font-size:18px;font-weight:700;letter-spacing:-.02em;color:var(--text);border-bottom:1px solid var(--border);margin-bottom:8px;display:flex;align-items:center;gap:8px}\n.sidebar .logo svg{color:var(--accent)}\n.sidebar nav{flex:1;display:flex;flex-direction:column;gap:2px;padding:0 8px;overflow-y:auto}\n.sidebar nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius);color:var(--text-muted);font-size:14px;font-weight:500;transition:background .12s,color .12s}\n.sidebar nav a:hover{background:var(--accent-light);color:var(--text)}\n.sidebar nav a.active{background:var(--accent-light);color:var(--accent);font-weight:600}\n.sidebar nav a svg{flex-shrink:0}\n.nav-group{margin:0}\n.nav-group-header{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius);color:var(--text-muted);font-size:14px;font-weight:500;cursor:pointer;transition:background .12s,color .12s;user-select:none}\n.nav-group-header:hover{background:var(--accent-light);color:var(--text)}\n.nav-group-header svg:first-child{flex-shrink:0}\n.nav-group-chevron{margin-left:auto;transition:transform .2s;flex-shrink:0}\n.nav-group.collapsed .nav-group-chevron{transform:rotate(-90deg)}\n.nav-group-items{display:flex;flex-direction:column;gap:2px;padding-left:12px;overflow:hidden;transition:max-height .2s ease}\n.nav-group.collapsed .nav-group-items{max-height:0 !important;overflow:hidden}\n.nav-group-items a{font-size:13px;padding:7px 10px}\n.nav-group-items a svg{width:18px;height:18px}\n.main{margin-left:220px;flex:1;padding:32px 40px;max-width:900px}\n.main h1{font-size:22px;font-weight:700;margin-bottom:0}\n.main h2{font-size:17px;font-weight:600;margin-bottom:12px;margin-top:24px}\n.section-header{position:sticky;top:0;z-index:5;background:var(--bg);padding:16px 0 14px;display:flex;align-items:center;justify-content:space-between}\n.save-btn:disabled{opacity:.4;cursor:default;pointer-events:none;animation:none}\n.save-btn:not(:disabled){animation:pulse-save 1.5s ease-in-out infinite;background:#d32f2f;border-color:#d32f2f;color:#fff}\n.save-btn:not(:disabled):hover{background:#b71c1c;border-color:#b71c1c}\n@keyframes pulse-save{0%,100%{opacity:1}50%{opacity:.5}}\n\n/* Cards */\n.card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:16px}\n.card.disabled{opacity:.45;pointer-events:none;user-select:none}\n.card.disabled .card-header::after{content:\'Coming soon\';font-size:12px;color:var(--text-muted);font-weight:400;margin-left:auto;margin-right:12px}\n.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}\n.card-title{font-weight:600;font-size:15px}\n\n/* Accordion */\n.sa-acc{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}\n.sa-acc:first-child{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}\n.sa-acc:last-child{border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius)}\n.sa-acc+.sa-acc{border-top:none}\n.sa-acc-header{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none;transition:background .15s}\n.sa-acc-header:hover{background:var(--accent-light)}\n.sa-acc:nth-child(odd) .sa-acc-header{background:var(--bg-card)}\n.sa-acc:nth-child(even) .sa-acc-header{background:var(--bg)}\n.sa-acc:nth-child(odd) .sa-acc-header:hover,.sa-acc:nth-child(even) .sa-acc-header:hover{background:var(--accent-light)}\n.sa-acc-name{flex:1;font-weight:600;font-size:14px;color:var(--text)}\n.sa-acc-chevron{width:16px;height:16px;color:var(--text-muted);transition:transform .2s;flex-shrink:0}\n.sa-acc.open .sa-acc-chevron{transform:rotate(180deg)}\n.sa-acc-body{display:none;padding:12px 14px 16px;border-top:1px solid var(--border);background:var(--bg)}\n.sa-acc.open .sa-acc-body{display:block}\n\n/* Buttons */\n.btn{background:var(--accent);color:#fff;font-weight:500;font-size:14px}\n.btn:hover{background:var(--accent-hover)}\n.btn-danger{background:var(--danger);color:#fff}\n.btn-danger:hover{opacity:.85}\n.btn-ghost{background:transparent;color:var(--text-muted);border:1px solid var(--border)}\n.btn-ghost:hover{background:var(--bg-card);color:var(--text)}\n.btn-sm{padding:5px 10px;font-size:13px}\n\n/* Modal */\n.modal-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.55);z-index:1000;align-items:center;justify-content:center}\n.modal-overlay.open{display:flex}\n.modal{background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:28px;max-width:400px;width:90%;text-align:center;position:relative}\n.modal h3{margin:0 0 8px;font-size:18px}\n.modal p{margin:0 0 16px;color:var(--text-muted);font-size:14px}\n.modal img{display:block;margin:0 auto 16px;border-radius:8px;max-width:260px}\n.modal .close-btn{position:absolute;top:10px;right:14px;background:none;border:none;font-size:22px;cursor:pointer;color:var(--text-muted);line-height:1}\n.modal .status-msg{font-size:14px;padding:10px;border-radius:6px;margin-bottom:12px}\n.modal .status-msg.ok{background:rgba(34,197,94,.12);color:#16a34a}\n.modal .status-msg.err{background:rgba(239,68,68,.12);color:#ef4444}\n.modal .spinner{display:inline-block;width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;vertical-align:middle;margin-right:8px}\n@keyframes spin{to{transform:rotate(360deg)}}\n\n/* Env var ref indicator */\ninput.env-ref{border-left:3px solid var(--accent);background:rgba(59,130,246,0.04)}\n[data-theme="dark"] input.env-ref{background:rgba(59,130,246,0.08)}\n\n/* Form */\n.field{margin-bottom:14px}\n.field label{display:block;font-size:13px;font-weight:500;margin-bottom:4px;color:var(--text-muted)}\n.field-row{display:flex;gap:10px;align-items:flex-end}\n\n/* Range slider */\ninput[type="range"]{-webkit-appearance:none;appearance:none;width:100%;height:6px;border:none;border-radius:3px;background:var(--border);outline:none;padding:0;margin:8px 0 0;cursor:pointer}\ninput[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:var(--accent);border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.2);cursor:pointer;transition:background .15s}\ninput[type="range"]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--accent);border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.2);cursor:pointer}\ninput[type="range"]::-webkit-slider-thumb:hover{background:var(--accent-hover)}\ninput[type="range"]::-moz-range-thumb:hover{background:var(--accent-hover)}\ninput[type="range"]:focus{border-color:transparent}\n\n/* Toggle */\n.toggle{position:relative;display:inline-block;width:42px;height:24px;flex-shrink:0}\n.toggle input{opacity:0;width:0;height:0}\n.toggle span{position:absolute;inset:0;background:var(--border);border-radius:24px;transition:background .2s;cursor:pointer}\n.toggle span::before{content:\'\';position:absolute;width:18px;height:18px;left:3px;top:3px;background:#fff;border-radius:50%;transition:transform .2s}\n.toggle input:checked+span{background:var(--accent)}\n.toggle input:checked+span::before{transform:translateX(18px)}\n\n/* Tool toggles */\n.tool-toggle{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:500;color:var(--text);cursor:pointer;padding:6px 10px;border-radius:8px;background:var(--card);border:1px solid var(--border)}\n\n/* Small toggle (subagent tools) */\n.toggle-sm{position:relative;display:inline-block;width:30px;height:16px;flex-shrink:0}\n.toggle-sm input{opacity:0;width:0;height:0}\n.toggle-sm span{position:absolute;inset:0;background:var(--border);border-radius:16px;transition:background .2s;cursor:pointer}\n.toggle-sm span::before{content:\'\';position:absolute;width:12px;height:12px;left:2px;top:2px;background:#fff;border-radius:50%;transition:transform .2s}\n.toggle-sm input:checked+span{background:var(--accent)}\n.toggle-sm input:checked+span::before{transform:translateX(14px)}\n.tool-toggle-sm{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:var(--text);padding:3px 6px;border-radius:6px;background:var(--bg-card);border:1px solid var(--border)}\n\n/* Table */\n.tbl{width:100%;border-collapse:collapse;font-size:14px}\n.tbl th{text-align:left;font-weight:500;color:var(--text-muted);padding:8px 12px;border-bottom:1px solid var(--border);font-size:13px}\n.tbl td{padding:8px 12px;border-bottom:1px solid var(--border)}\n.tbl tr:last-child td{border-bottom:none}\n.tbl tbody tr:nth-child(odd){background:var(--bg-card)}\n.tbl tbody tr:nth-child(even){background:var(--bg)}\n\n/* Badges */\n.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}\n.badge-green{background:#dcfce7;color:#166534}\n.badge-red{background:#fef2f2;color:#991b1b}\n[data-theme="dark"] .badge-green{background:#14532d;color:#86efac}\n[data-theme="dark"] .badge-red{background:#450a0a;color:#fca5a5}\n\n/* Key card */\n.key-card{background:var(--accent-light);border:2px solid var(--accent);border-radius:var(--radius);padding:20px;text-align:center;margin:16px 0}\n.key-card .key-value{font-family:monospace;font-size:22px;font-weight:700;letter-spacing:2px;color:var(--accent);margin:10px 0}\n.key-card .key-label{font-size:13px;color:var(--text-muted)}\n\n/* Current key display */\n.current-key-row{display:flex;align-items:center;gap:8px;margin-bottom:14px}\n.current-key-value{font-family:monospace;font-size:18px;font-weight:600;letter-spacing:2px;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:8px 14px;user-select:all}\n.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg);color:var(--text-muted);cursor:pointer;transition:color .15s,border-color .15s}\n.icon-btn:hover{color:var(--text);border-color:var(--text-muted)}\n\n/* Login */\n.login-wrap{display:flex;justify-content:center;align-items:center;min-height:100vh;background:var(--bg)}\n.login-card{width:480px;max-width:95vw;padding:32px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius)}\n.login-card h1{text-align:center;margin-bottom:6px}\n.login-card .subtitle{text-align:center;font-size:14px;color:var(--text-muted);margin-bottom:24px}\n.login-card .login-error{color:var(--danger);font-size:13px;margin-top:8px;display:none}\n.key-inputs{display:flex;align-items:center;gap:6px}\n.key-inputs input{width:72px;text-align:center;font-family:monospace;font-size:18px;font-weight:600;letter-spacing:2px;text-transform:uppercase;padding:10px 6px}\n.key-inputs input:disabled{background:var(--bg-card);color:var(--text-muted);opacity:.6;cursor:default}\n.key-sep{color:var(--text-muted);font-size:20px;font-weight:300;user-select:none}\n.key-row{display:flex;align-items:center;gap:8px;justify-content:center}\n.eye-btn{background:none;border:none;padding:6px;color:var(--text-muted);cursor:pointer;flex-shrink:0;display:flex;align-items:center;border-radius:var(--radius);transition:color .15s,background .15s}\n.eye-btn:hover{color:var(--accent);background:var(--accent-light)}\n\n/* Toast */\n.toast{position:fixed;bottom:20px;right:20px;padding:12px 20px;border-radius:var(--radius);color:#fff;font-size:14px;font-weight:500;z-index:999;opacity:0;transform:translateY(10px);transition:opacity .3s,transform .3s;pointer-events:none}\n.toast.show{opacity:1;transform:translateY(0)}\n.toast-ok{background:var(--success)}\n.toast-err{background:var(--danger)}\n\n/* Restart pending */\n.restart-pending{font-size:12px;font-weight:600;color:var(--danger);animation:pulse-save 1.5s ease-in-out infinite}\n\n/* Status dot */\n.dot{width:8px;height:8px;border-radius:50%;display:inline-block}\n.dot-green{background:var(--success)}\n.dot-red{background:var(--danger)}\n\n/* Responsive */\n@media(max-width:700px){\n .sidebar{width:60px}\n .sidebar .logo span,.sidebar nav a span,.nav-group-header span,.nav-group-chevron{display:none}\n .sidebar nav a{justify-content:center;padding:10px}\n .nav-group-header{justify-content:center;padding:10px}\n .nav-group-items{padding-left:0}\n .main{margin-left:60px;padding:20px 16px}\n}\n\n/* Section visibility */\n.section{display:none}\n.section.active{display:block}\n\n\n/* Channel help */\n.help-toggle{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;border:1px solid var(--border);background:transparent;color:var(--text-muted);font-size:13px;font-weight:600;cursor:pointer;margin-left:8px;flex-shrink:0;transition:background .15s,color .15s,border-color .15s}\n.help-toggle:hover{background:var(--accent-light);color:var(--accent);border-color:var(--accent)}\n.help-panel{display:none;margin:0 0 14px;padding:14px 16px;background:var(--accent-light);border:1px solid var(--accent);border-radius:var(--radius);font-size:13px;line-height:1.7;color:var(--text)}\n.help-panel.open{display:block}\n.help-panel ol{margin:6px 0 0 18px;padding:0}\n.help-panel li{margin-bottom:2px}\n.help-panel a{color:var(--accent);text-decoration:underline}\n.help-panel code{background:var(--bg);padding:1px 5px;border-radius:4px;font-size:12px}\n/* Monaco editor containers */\n.monaco-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;min-height:350px;position:relative}\n.monaco-wrap.readonly{opacity:.95}\n.file-select-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}\n.file-select-row select{flex:1;max-width:320px}\n.file-select-row .file-badge{font-size:12px;padding:2px 8px;border-radius:10px;font-weight:500}\n.file-select-row .file-badge.template{background:#dbeafe;color:#1e40af}\n.file-select-row .file-badge.workspace{background:#dcfce7;color:#166534}\n.file-select-row .file-badge.missing{background:#fef2f2;color:#991b1b}\n[data-theme="dark"] .file-select-row .file-badge.template{background:#1e3a5f;color:#93c5fd}\n[data-theme="dark"] .file-select-row .file-badge.workspace{background:#14532d;color:#86efac}\n[data-theme="dark"] .file-select-row .file-badge.missing{background:#450a0a;color:#fca5a5}\n\n/* Simulate modal — wider for Monaco */\n.sim-modal{max-width:900px;width:95%;max-height:90vh;text-align:left;display:flex;flex-direction:column}\n.sim-modal h3{text-align:center;margin-bottom:16px}\n.sim-tabs{display:flex;gap:4px;margin-bottom:12px;border-bottom:1px solid var(--border);padding-bottom:0}\n.sim-tab{padding:8px 16px;font-size:14px;font-weight:500;background:none;color:var(--text-muted);border:none;border-bottom:2px solid transparent;cursor:pointer;transition:color .15s,border-color .15s}\n.sim-tab:hover{color:var(--text)}\n.sim-tab.active{color:var(--accent);border-bottom-color:var(--accent)}\n.sim-info{display:flex;justify-content:space-between;align-items:center;margin-top:8px;font-size:12px;color:var(--text-muted)}\n\n/* Drop zone */\n.drop-zone{padding:40px 20px;border:2px dashed var(--border);border-radius:var(--radius);text-align:center;cursor:pointer;transition:border-color .2s,background .2s}\n.drop-zone.drag-over{border-color:var(--accent);background:var(--accent-light)}\n.drop-zone.uploading{pointer-events:none;opacity:.6}\n\n/* Floating restart indicator */\n.floating-restart{position:fixed;top:16px;right:16px;width:48px;height:48px;border-radius:50%;background:var(--danger);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:900;box-shadow:0 2px 12px rgba(0,0,0,.25);animation:blink-restart 2s ease-in-out infinite;transition:transform .15s}\n.floating-restart:hover{transform:scale(1.1);animation:none;opacity:1}\n@keyframes blink-restart{0%,100%{opacity:1;box-shadow:0 2px 12px rgba(239,68,68,.3)}50%{opacity:.45;box-shadow:0 2px 20px rgba(239,68,68,.6)}}\n'}
|
|
1
|
+
export function renderStyles(){return'\n:root{\n --bg:#fff;--bg-card:#f3f4f6;--text:#111827;--text-muted:#6b7280;\n --accent:#6366f1;--accent-hover:#4f46e5;--accent-light:#eef2ff;\n --border:#e5e7eb;--danger:#ef4444;--success:#22c55e;\n --radius:8px;--font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;\n}\n[data-theme="dark"]{\n --bg:#0b0b11;--bg-card:#16161f;--text:#e5e5ea;--text-muted:#9ca3af;\n --accent:#818cf8;--accent-hover:#6366f1;--accent-light:#1e1e2e;\n --border:#2d2d3a;--danger:#f87171;--success:#4ade80;\n}\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nbody{font-family:var(--font);font-size:16px;line-height:1.5;color:var(--text);background:var(--bg);min-height:100vh}\na{color:var(--accent);text-decoration:none}\nbutton{cursor:pointer;font:inherit;border:none;border-radius:var(--radius);padding:8px 16px;transition:background .15s,opacity .15s}\ninput,textarea,select{font:inherit;border:1px solid var(--border);border-radius:var(--radius);padding:8px 12px;background:var(--bg);color:var(--text);width:100%;transition:border-color .15s}\ninput:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)}\ntextarea{resize:vertical;min-height:80px}\n\n/* Layout */\n.shell{display:flex;min-height:100vh}\n.sidebar{width:220px;background:var(--bg-card);border-right:1px solid var(--border);padding:20px 0;display:flex;flex-direction:column;position:fixed;top:0;left:0;bottom:0;z-index:10}\n.sidebar .logo{padding:0 20px 20px;font-size:18px;font-weight:700;letter-spacing:-.02em;color:var(--text);border-bottom:1px solid var(--border);margin-bottom:8px;display:flex;align-items:center;gap:8px}\n.sidebar .logo svg{color:var(--accent)}\n.sidebar nav{flex:1;display:flex;flex-direction:column;gap:2px;padding:0 8px;overflow-y:auto}\n.sidebar nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius);color:var(--text-muted);font-size:14px;font-weight:500;transition:background .12s,color .12s}\n.sidebar nav a:hover{background:var(--accent-light);color:var(--text)}\n.sidebar nav a.active{background:var(--accent-light);color:var(--accent);font-weight:600}\n.sidebar nav a svg{flex-shrink:0}\n.nav-group{margin:0}\n.nav-group-header{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius);color:var(--text-muted);font-size:14px;font-weight:500;cursor:pointer;transition:background .12s,color .12s;user-select:none}\n.nav-group-header:hover{background:var(--accent-light);color:var(--text)}\n.nav-group-header svg:first-child{flex-shrink:0}\n.nav-group-chevron{margin-left:auto;transition:transform .2s;flex-shrink:0}\n.nav-group.collapsed .nav-group-chevron{transform:rotate(-90deg)}\n.nav-group-items{display:flex;flex-direction:column;gap:2px;padding-left:12px;overflow:hidden;transition:max-height .2s ease}\n.nav-group.collapsed .nav-group-items{max-height:0 !important;overflow:hidden}\n.nav-group-items a{font-size:13px;padding:7px 10px}\n.nav-group-items a svg{width:18px;height:18px}\n.main{margin-left:220px;flex:1;padding:32px 40px;max-width:900px}\n.main h1{font-size:22px;font-weight:700;margin-bottom:0}\n.main h2{font-size:17px;font-weight:600;margin-bottom:12px;margin-top:24px}\n.section-header{position:sticky;top:0;z-index:5;background:var(--bg);padding:16px 0 14px;display:flex;align-items:center;justify-content:space-between}\n.save-btn:disabled{opacity:.4;cursor:default;pointer-events:none;animation:none}\n.save-btn:not(:disabled){animation:pulse-save 1.5s ease-in-out infinite;background:#d32f2f;border-color:#d32f2f;color:#fff}\n.save-btn:not(:disabled):hover{background:#b71c1c;border-color:#b71c1c}\n@keyframes pulse-save{0%,100%{opacity:1}50%{opacity:.5}}\n\n/* Cards */\n.card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:16px}\n.card.disabled{opacity:.45;pointer-events:none;user-select:none}\n.card.disabled .card-header::after{content:\'Coming soon\';font-size:12px;color:var(--text-muted);font-weight:400;margin-left:auto;margin-right:12px}\n.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}\n.card-title{font-weight:600;font-size:15px}\n\n/* Accordion */\n.sa-acc{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}\n.sa-acc:first-child{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}\n.sa-acc:last-child{border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius)}\n.sa-acc+.sa-acc{border-top:none}\n.sa-acc-header{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none;transition:background .15s}\n.sa-acc-header:hover{background:var(--accent-light)}\n.sa-acc:nth-child(odd) .sa-acc-header{background:var(--bg-card)}\n.sa-acc:nth-child(even) .sa-acc-header{background:var(--bg)}\n.sa-acc:nth-child(odd) .sa-acc-header:hover,.sa-acc:nth-child(even) .sa-acc-header:hover{background:var(--accent-light)}\n.sa-acc-name{flex:1;font-weight:600;font-size:14px;color:var(--text)}\n.sa-acc-chevron{width:16px;height:16px;color:var(--text-muted);transition:transform .2s;flex-shrink:0}\n.sa-acc.open .sa-acc-chevron{transform:rotate(180deg)}\n.sa-acc-body{display:none;padding:12px 14px 16px;border-top:1px solid var(--border);background:var(--bg)}\n.sa-acc.open .sa-acc-body{display:block}\n\n/* Buttons */\n.btn{background:var(--accent);color:#fff;font-weight:500;font-size:14px}\n.btn:hover{background:var(--accent-hover)}\n.btn-danger{background:var(--danger);color:#fff}\n.btn-danger:hover{opacity:.85}\n.btn-ghost{background:transparent;color:var(--text-muted);border:1px solid var(--border)}\n.btn-ghost:hover{background:var(--bg-card);color:var(--text)}\n.btn-sm{padding:5px 10px;font-size:13px}\n\n/* Modal */\n.modal-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.55);z-index:1000;align-items:center;justify-content:center}\n.modal-overlay.open{display:flex}\n.modal{background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:28px;max-width:400px;width:90%;text-align:center;position:relative}\n.modal h3{margin:0 0 8px;font-size:18px}\n.modal p{margin:0 0 16px;color:var(--text-muted);font-size:14px}\n.modal img{display:block;margin:0 auto 16px;border-radius:8px;max-width:260px}\n.modal .close-btn{position:absolute;top:10px;right:14px;background:none;border:none;font-size:22px;cursor:pointer;color:var(--text-muted);line-height:1}\n.modal .status-msg{font-size:14px;padding:10px;border-radius:6px;margin-bottom:12px}\n.modal .status-msg.ok{background:rgba(34,197,94,.12);color:#16a34a}\n.modal .status-msg.err{background:rgba(239,68,68,.12);color:#ef4444}\n.modal .spinner{display:inline-block;width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;vertical-align:middle;margin-right:8px}\n@keyframes spin{to{transform:rotate(360deg)}}\n\n/* Env var ref indicator */\ninput.env-ref{border-left:3px solid var(--accent);background:rgba(59,130,246,0.04)}\n[data-theme="dark"] input.env-ref{background:rgba(59,130,246,0.08)}\n\n/* Form */\n.field{margin-bottom:14px}\n.field label{display:block;font-size:13px;font-weight:500;margin-bottom:4px;color:var(--text-muted)}\n.field-row{display:flex;gap:10px;align-items:flex-end}\n\n/* Range slider */\ninput[type="range"]{-webkit-appearance:none;appearance:none;width:100%;height:6px;border:none;border-radius:3px;background:var(--border);outline:none;padding:0;margin:8px 0 0;cursor:pointer}\ninput[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:var(--accent);border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.2);cursor:pointer;transition:background .15s}\ninput[type="range"]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--accent);border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.2);cursor:pointer}\ninput[type="range"]::-webkit-slider-thumb:hover{background:var(--accent-hover)}\ninput[type="range"]::-moz-range-thumb:hover{background:var(--accent-hover)}\ninput[type="range"]:focus{border-color:transparent}\n\n/* Toggle */\n.toggle{position:relative;display:inline-block;width:42px;height:24px;flex-shrink:0}\n.toggle input{opacity:0;width:0;height:0}\n.toggle span{position:absolute;inset:0;background:var(--border);border-radius:24px;transition:background .2s;cursor:pointer}\n.toggle span::before{content:\'\';position:absolute;width:18px;height:18px;left:3px;top:3px;background:#fff;border-radius:50%;transition:transform .2s}\n.toggle input:checked+span{background:var(--accent)}\n.toggle input:checked+span::before{transform:translateX(18px)}\n\n/* Tool toggles */\n.tool-toggle{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:500;color:var(--text);cursor:pointer;padding:6px 10px;border-radius:8px;background:var(--card);border:1px solid var(--border)}\n\n/* Small toggle (subagent tools) */\n.toggle-sm{position:relative;display:inline-block;width:30px;height:16px;flex-shrink:0}\n.toggle-sm input{opacity:0;width:0;height:0}\n.toggle-sm span{position:absolute;inset:0;background:var(--border);border-radius:16px;transition:background .2s;cursor:pointer}\n.toggle-sm span::before{content:\'\';position:absolute;width:12px;height:12px;left:2px;top:2px;background:#fff;border-radius:50%;transition:transform .2s}\n.toggle-sm input:checked+span{background:var(--accent)}\n.toggle-sm input:checked+span::before{transform:translateX(14px)}\n.tool-toggle-sm{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:var(--text);padding:3px 6px;border-radius:6px;background:var(--bg-card);border:1px solid var(--border)}\n\n/* Table */\n.tbl{width:100%;border-collapse:collapse;font-size:14px}\n.tbl th{text-align:left;font-weight:500;color:var(--text-muted);padding:8px 12px;border-bottom:1px solid var(--border);font-size:13px}\n.tbl td{padding:8px 12px;border-bottom:1px solid var(--border)}\n.tbl tr:last-child td{border-bottom:none}\n.tbl tbody tr:nth-child(odd){background:var(--bg-card)}\n.tbl tbody tr:nth-child(even){background:var(--bg)}\n\n/* Badges */\n.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}\n.badge-green{background:#dcfce7;color:#166534}\n.badge-red{background:#fef2f2;color:#991b1b}\n[data-theme="dark"] .badge-green{background:#14532d;color:#86efac}\n[data-theme="dark"] .badge-red{background:#450a0a;color:#fca5a5}\n\n/* Key card */\n.key-card{background:var(--accent-light);border:2px solid var(--accent);border-radius:var(--radius);padding:20px;text-align:center;margin:16px 0}\n.key-card .key-value{font-family:monospace;font-size:22px;font-weight:700;letter-spacing:2px;color:var(--accent);margin:10px 0}\n.key-card .key-label{font-size:13px;color:var(--text-muted)}\n\n/* Current key display */\n.current-key-row{display:flex;align-items:center;gap:8px;margin-bottom:14px}\n.current-key-value{font-family:monospace;font-size:18px;font-weight:600;letter-spacing:2px;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:8px 14px;user-select:all}\n.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg);color:var(--text-muted);cursor:pointer;transition:color .15s,border-color .15s}\n.icon-btn:hover{color:var(--text);border-color:var(--text-muted)}\n\n/* Login */\n.login-wrap{display:flex;justify-content:center;align-items:center;min-height:100vh;background:var(--bg)}\n.login-card{width:480px;max-width:95vw;padding:32px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius)}\n.login-card h1{text-align:center;margin-bottom:6px}\n.login-card .subtitle{text-align:center;font-size:14px;color:var(--text-muted);margin-bottom:24px}\n.login-card .login-error{color:var(--danger);font-size:13px;margin-top:8px;display:none}\n.key-inputs{display:flex;align-items:center;gap:6px}\n.key-inputs input{width:72px;text-align:center;font-family:monospace;font-size:18px;font-weight:600;letter-spacing:2px;text-transform:uppercase;padding:10px 6px}\n.key-inputs input:disabled{background:var(--bg-card);color:var(--text-muted);opacity:.6;cursor:default}\n.key-sep{color:var(--text-muted);font-size:20px;font-weight:300;user-select:none}\n.key-row{display:flex;align-items:center;gap:8px;justify-content:center}\n.eye-btn{background:none;border:none;padding:6px;color:var(--text-muted);cursor:pointer;flex-shrink:0;display:flex;align-items:center;border-radius:var(--radius);transition:color .15s,background .15s}\n.eye-btn:hover{color:var(--accent);background:var(--accent-light)}\n\n/* Toast */\n.toast{position:fixed;bottom:20px;right:20px;padding:12px 20px;border-radius:var(--radius);color:#fff;font-size:14px;font-weight:500;z-index:999;opacity:0;transform:translateY(10px);transition:opacity .3s,transform .3s;pointer-events:none}\n.toast.show{opacity:1;transform:translateY(0)}\n.toast-ok{background:var(--success)}\n.toast-err{background:var(--danger)}\n\n/* Restart pending */\n.restart-pending{font-size:12px;font-weight:600;color:var(--danger);animation:pulse-save 1.5s ease-in-out infinite}\n\n/* Status dot */\n.dot{width:8px;height:8px;border-radius:50%;display:inline-block}\n.dot-green{background:var(--success)}\n.dot-red{background:var(--danger)}\n\n/* Responsive */\n@media(max-width:700px){\n .sidebar{width:60px}\n .sidebar .logo span,.sidebar nav a span,.nav-group-header span,.nav-group-chevron{display:none}\n .sidebar nav a{justify-content:center;padding:10px}\n .nav-group-header{justify-content:center;padding:10px}\n .nav-group-items{padding-left:0}\n .main{margin-left:60px;padding:20px 16px}\n}\n\n/* Section visibility */\n.section{display:none}\n.section.active{display:block}\n\n\n/* Channel help */\n.help-toggle{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;border:1px solid var(--border);background:transparent;color:var(--text-muted);font-size:13px;font-weight:600;cursor:pointer;margin-left:8px;flex-shrink:0;transition:background .15s,color .15s,border-color .15s}\n.help-toggle:hover{background:var(--accent-light);color:var(--accent);border-color:var(--accent)}\n.help-panel{display:none;margin:0 0 14px;padding:14px 16px;background:var(--accent-light);border:1px solid var(--accent);border-radius:var(--radius);font-size:13px;line-height:1.7;color:var(--text)}\n.help-panel.open{display:block}\n.help-panel ol{margin:6px 0 0 18px;padding:0}\n.help-panel li{margin-bottom:2px}\n.help-panel a{color:var(--accent);text-decoration:underline}\n.help-panel code{background:var(--bg);padding:1px 5px;border-radius:4px;font-size:12px}\n/* Monaco editor containers */\n.monaco-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;min-height:350px;position:relative}\n.monaco-wrap.readonly{opacity:.95}\n.file-select-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}\n.file-select-row select{flex:1;max-width:320px}\n.file-select-row .file-badge{font-size:12px;padding:2px 8px;border-radius:10px;font-weight:500}\n.file-select-row .file-badge.template{background:#dbeafe;color:#1e40af}\n.file-select-row .file-badge.workspace{background:#dcfce7;color:#166534}\n.file-select-row .file-badge.missing{background:#fef2f2;color:#991b1b}\n[data-theme="dark"] .file-select-row .file-badge.template{background:#1e3a5f;color:#93c5fd}\n[data-theme="dark"] .file-select-row .file-badge.workspace{background:#14532d;color:#86efac}\n[data-theme="dark"] .file-select-row .file-badge.missing{background:#450a0a;color:#fca5a5}\n\n/* Simulate modal — wider for Monaco */\n.sim-modal{max-width:900px;width:95%;max-height:90vh;text-align:left;display:flex;flex-direction:column}\n.sim-modal h3{text-align:center;margin-bottom:16px}\n.sim-tabs{display:flex;gap:4px;margin-bottom:12px;border-bottom:1px solid var(--border);padding-bottom:0}\n.sim-tab{padding:8px 16px;font-size:14px;font-weight:500;background:none;color:var(--text-muted);border:none;border-bottom:2px solid transparent;cursor:pointer;transition:color .15s,border-color .15s}\n.sim-tab:hover{color:var(--text)}\n.sim-tab.active{color:var(--accent);border-bottom-color:var(--accent)}\n.sim-info{display:flex;justify-content:space-between;align-items:center;margin-top:8px;font-size:12px;color:var(--text-muted)}\n\n/* Drop zone */\n.drop-zone{padding:40px 20px;border:2px dashed var(--border);border-radius:var(--radius);text-align:center;cursor:pointer;transition:border-color .2s,background .2s}\n.drop-zone.drag-over{border-color:var(--accent);background:var(--accent-light)}\n.drop-zone.uploading{pointer-events:none;opacity:.6}\n\n/* Pico model list */\n.pico-model-list{min-height:20px}\n.pico-model-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius);margin-bottom:4px;background:var(--bg-card);cursor:grab;transition:opacity .15s}\n.pico-model-item.dragging{opacity:.4}\n.pico-model-item .pico-drag-handle{color:var(--text-muted);cursor:grab;user-select:none}\n.pico-model-item .pico-model-name{flex:1;font-weight:500}\n.pico-model-item .pico-model-ctx{color:var(--text-muted);font-size:12px}\n.pico-model-item .pico-model-remove{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;padding:2px 6px}\n.pico-model-item .pico-model-remove:hover{color:var(--danger)}\n\n/* Floating restart indicator */\n.floating-restart{position:fixed;top:16px;right:16px;width:48px;height:48px;border-radius:50%;background:var(--danger);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:900;box-shadow:0 2px 12px rgba(0,0,0,.25);animation:blink-restart 2s ease-in-out infinite;transition:transform .15s}\n.floating-restart:hover{transform:scale(1.1);animation:none;opacity:1}\n@keyframes blink-restart{0%,100%{opacity:1;box-shadow:0 2px 12px rgba(239,68,68,.3)}50%{opacity:.45;box-shadow:0 2px 20px rgba(239,68,68,.6)}}\n'}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hera-al/pi-agent-provider
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for the Claude Agent SDK's query() function using
|
|
5
|
+
* @mariozechner/pi-ai (from the pi-mono repository).
|
|
6
|
+
*
|
|
7
|
+
* This library allows Hera's SessionAgent to run with ANY LLM provider
|
|
8
|
+
* supported by pi-ai (OpenAI, Anthropic, Google, OpenRouter, xAI, Groq,
|
|
9
|
+
* Mistral, Cerebras, HuggingFace, etc.) instead of being locked to the
|
|
10
|
+
* Claude Agent SDK.
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { piQuery, createToolRegistryFromOptions } from "@hera-al/pi-agent-provider";
|
|
16
|
+
* import type { PiProviderConfig } from "@hera-al/pi-agent-provider";
|
|
17
|
+
*
|
|
18
|
+
* // Configure the LLM provider
|
|
19
|
+
* const providerConfig: PiProviderConfig = {
|
|
20
|
+
* provider: "openai",
|
|
21
|
+
* modelId: "gpt-4o",
|
|
22
|
+
* apiKey: process.env.OPENAI_API_KEY,
|
|
23
|
+
* };
|
|
24
|
+
*
|
|
25
|
+
* // Create tool registry from existing MCP servers
|
|
26
|
+
* const toolRegistry = await createToolRegistryFromOptions(options);
|
|
27
|
+
*
|
|
28
|
+
* // Use as drop-in replacement for query()
|
|
29
|
+
* const handle = piQuery(
|
|
30
|
+
* { prompt: messageQueue, options: sdkOptions },
|
|
31
|
+
* providerConfig,
|
|
32
|
+
* toolRegistry,
|
|
33
|
+
* );
|
|
34
|
+
*
|
|
35
|
+
* // Consume exactly like the Claude SDK's query()
|
|
36
|
+
* for await (const message of handle) {
|
|
37
|
+
* // Same message types as the Claude SDK:
|
|
38
|
+
* // system(init), assistant, tool_progress, result
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* // Same control methods
|
|
42
|
+
* await handle.interrupt();
|
|
43
|
+
* await handle.setModel("anthropic/claude-sonnet-4-20250514");
|
|
44
|
+
* handle.close();
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ## Integration with SessionAgent
|
|
48
|
+
*
|
|
49
|
+
* To use this in Hera's SessionAgent, replace the query() import:
|
|
50
|
+
*
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Before:
|
|
53
|
+
* import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
54
|
+
*
|
|
55
|
+
* // After:
|
|
56
|
+
* import { piQuery as query } from "@hera-al/pi-agent-provider";
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* Then in ensureInitialized():
|
|
60
|
+
*
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // Before:
|
|
63
|
+
* this.queryHandle = query({ prompt: this.queue, options: this.opts });
|
|
64
|
+
*
|
|
65
|
+
* // After:
|
|
66
|
+
* const toolRegistry = await createToolRegistryFromOptions(this.opts);
|
|
67
|
+
* this.queryHandle = query(
|
|
68
|
+
* { prompt: this.queue, options: this.opts },
|
|
69
|
+
* this.piProviderConfig,
|
|
70
|
+
* toolRegistry,
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* ## Architecture
|
|
75
|
+
*
|
|
76
|
+
* ```
|
|
77
|
+
* SessionAgent.send()
|
|
78
|
+
* → MessageQueue.push()
|
|
79
|
+
* → piQuery() consumes from MessageQueue
|
|
80
|
+
* → For each user message:
|
|
81
|
+
* → pi-ai stream(model, context, options)
|
|
82
|
+
* → LLM responds (text + tool calls)
|
|
83
|
+
* → If tool calls:
|
|
84
|
+
* → Permission check (canUseTool callback)
|
|
85
|
+
* → Execute tools (built-in or MCP)
|
|
86
|
+
* → Add results to context
|
|
87
|
+
* → Loop back to stream()
|
|
88
|
+
* → When done:
|
|
89
|
+
* → Yield SDKResultMessage
|
|
90
|
+
* → SessionAgent.processOutput() consumes SDKMessages
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* ## Supported Providers
|
|
94
|
+
*
|
|
95
|
+
* All providers supported by @mariozechner/pi-ai:
|
|
96
|
+
* - Anthropic (Claude)
|
|
97
|
+
* - OpenAI (GPT-4, GPT-4o, o1, etc.)
|
|
98
|
+
* - Google (Gemini)
|
|
99
|
+
* - Amazon Bedrock
|
|
100
|
+
* - OpenRouter
|
|
101
|
+
* - xAI (Grok)
|
|
102
|
+
* - Groq
|
|
103
|
+
* - Cerebras
|
|
104
|
+
* - Mistral
|
|
105
|
+
* - HuggingFace
|
|
106
|
+
* - And more via custom provider registration
|
|
107
|
+
*/
|
|
108
|
+
export { piQuery, type PiQueryHandle, type PiQueryInput } from "./pi-query.js";
|
|
109
|
+
export type { SDKMessage, SDKSystemMessage, SDKAssistantMessage, SDKToolProgressMessage, SDKResultMessage, SDKUserMessage, SDKContentBlock, SDKUserContentBlock, PiQueryOptions, PiProviderConfig, ModelUsageEntry, } from "./pi-types.js";
|
|
110
|
+
export { sdkUserToPiUser, piAssistantToSdk, piUsageToModelUsage, extractTextFromSdkAssistant, type PiUserMessage, type PiAssistantMessage, type PiToolResultMessage, type PiMessage, type PiUsage, type PiToolCall, type PiTextContent, type PiImageContent, type PiThinkingContent, } from "./pi-message-adapter.js";
|
|
111
|
+
export { ToolRegistry, type PiTool, type PiToolResult, type ToolExecutor, type ToolPermissionChecker, BUILTIN_TOOL_DEFINITIONS, } from "./pi-tool-adapter.js";
|
|
112
|
+
export { executeBuiltinTool } from "./pi-tool-executor.js";
|
|
113
|
+
export { extractToolsFromMcpServer, executeMcpTool, createToolRegistryFromOptions, } from "./pi-mcp-bridge.js";
|
|
114
|
+
export { compactContext, applySummarization, estimateSessionTokens, maskOldObservations, compactToReferences, buildSummarizationPrompt, DEFAULT_COMPACTION_CONFIG, type CompactionConfig, type CompactionResult, } from "./pi-context-compactor.js";
|
|
115
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{piQuery}from"./pi-query.js";export{sdkUserToPiUser,piAssistantToSdk,piUsageToModelUsage,extractTextFromSdkAssistant}from"./pi-message-adapter.js";export{ToolRegistry,BUILTIN_TOOL_DEFINITIONS}from"./pi-tool-adapter.js";export{executeBuiltinTool}from"./pi-tool-executor.js";export{extractToolsFromMcpServer,executeMcpTool,createToolRegistryFromOptions}from"./pi-mcp-bridge.js";export{compactContext,applySummarization,estimateSessionTokens,maskOldObservations,compactToReferences,buildSummarizationPrompt,DEFAULT_COMPACTION_CONFIG}from"./pi-context-compactor.js";
|