@newsails/veil-cli 1.0.1
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/.veil/agents/analyst/AGENT.md +21 -0
- package/.veil/agents/analyst/agent.json +23 -0
- package/.veil/agents/assistant/AGENT.md +15 -0
- package/.veil/agents/assistant/agent.json +19 -0
- package/.veil/agents/coder/AGENT.md +18 -0
- package/.veil/agents/coder/agent.json +19 -0
- package/.veil/agents/hello/AGENT.md +5 -0
- package/.veil/agents/hello/agent.json +13 -0
- package/.veil/agents/writer/AGENT.md +12 -0
- package/.veil/agents/writer/agent.json +17 -0
- package/.veil/memory/MEMORY.md +343 -0
- package/.veil/memory/agents/analyst/MEMORY.md +55 -0
- package/.veil/memory/agents/hello/MEMORY.md +12 -0
- package/.veil/runtime.pid +1 -0
- package/.veil/settings.json +10 -0
- package/.veil-studio/studio.db +0 -0
- package/.veil-studio/studio.db-shm +0 -0
- package/.veil-studio/studio.db-wal +0 -0
- package/PLAN/01-vision.md +26 -0
- package/PLAN/02-tech-stack.md +94 -0
- package/PLAN/03-agents.md +232 -0
- package/PLAN/04-runtime.md +171 -0
- package/PLAN/05-tools.md +211 -0
- package/PLAN/06-communication.md +243 -0
- package/PLAN/07-storage.md +218 -0
- package/PLAN/08-api-cli.md +153 -0
- package/PLAN/09-permissions.md +108 -0
- package/PLAN/10-ably.md +105 -0
- package/PLAN/11-file-formats.md +442 -0
- package/PLAN/12-folder-structure.md +205 -0
- package/PLAN/13-operations.md +212 -0
- package/PLAN/README.md +23 -0
- package/README.md +128 -0
- package/REPORT.md +174 -0
- package/TODO.md +45 -0
- package/ai-tests/FRONTEND_PROMPT.md +220 -0
- package/ai-tests/Research & Planning.md +814 -0
- package/ai-tests/prompt-001-basic-api.md +230 -0
- package/ai-tests/prompt-002-basic-flows.md +230 -0
- package/ai-tests/prompt-003-agent-behaviors.md +220 -0
- package/api/middleware.js +60 -0
- package/api/routes/agents.js +193 -0
- package/api/routes/chat.js +93 -0
- package/api/routes/completions.js +122 -0
- package/api/routes/daemons.js +80 -0
- package/api/routes/memory.js +169 -0
- package/api/routes/models.js +40 -0
- package/api/routes/remote-methods.js +74 -0
- package/api/routes/sessions.js +208 -0
- package/api/routes/settings.js +108 -0
- package/api/routes/system.js +50 -0
- package/api/routes/tasks.js +270 -0
- package/api/server.js +120 -0
- package/cli/formatter.js +70 -0
- package/cli/index.js +443 -0
- package/cli/parser.js +113 -0
- package/config/config.json +10 -0
- package/config/models.json +6826 -0
- package/core/agent.js +329 -0
- package/core/cancel.js +38 -0
- package/core/compaction.js +176 -0
- package/core/events.js +13 -0
- package/core/loop.js +564 -0
- package/core/memory.js +51 -0
- package/core/prompt.js +185 -0
- package/core/queue.js +96 -0
- package/core/registry.js +291 -0
- package/core/remote-methods.js +124 -0
- package/core/router.js +386 -0
- package/core/running-sessions.js +18 -0
- package/docs/api/01-system.md +84 -0
- package/docs/api/02-agents.md +374 -0
- package/docs/api/03-chat.md +269 -0
- package/docs/api/04-tasks.md +470 -0
- package/docs/api/05-sessions.md +444 -0
- package/docs/api/06-daemons.md +142 -0
- package/docs/api/07-memory.md +186 -0
- package/docs/api/08-settings.md +133 -0
- package/docs/api/09-models.md +119 -0
- package/docs/api/09-websocket.md +350 -0
- package/docs/api/10-completions.md +134 -0
- package/docs/api/README.md +116 -0
- package/docs/guide/01-quickstart.md +220 -0
- package/docs/guide/02-folder-structure.md +185 -0
- package/docs/guide/03-configuration.md +252 -0
- package/docs/guide/04-agents.md +267 -0
- package/docs/guide/05-cli.md +290 -0
- package/docs/guide/06-tools.md +643 -0
- package/docs/guide/07-permissions.md +236 -0
- package/docs/guide/08-memory.md +139 -0
- package/docs/guide/09-multi-agent.md +271 -0
- package/docs/guide/10-daemons.md +226 -0
- package/docs/guide/README.md +53 -0
- package/docs/index.html +623 -0
- package/examples/README.md +151 -0
- package/examples/agents/assistant/AGENT.md +31 -0
- package/examples/agents/assistant/SOUL.md +9 -0
- package/examples/agents/assistant/agent.json +74 -0
- package/examples/agents/hello/AGENT.md +15 -0
- package/examples/agents/hello/agent.json +14 -0
- package/examples/agents/monitor/AGENT.md +51 -0
- package/examples/agents/monitor/agent.json +33 -0
- package/examples/agents/monitor/heartbeats/monitor.md +24 -0
- package/examples/agents/orchestrator/AGENT.md +70 -0
- package/examples/agents/orchestrator/agent.json +30 -0
- package/examples/agents/researcher/AGENT.md +52 -0
- package/examples/agents/researcher/agent.json +49 -0
- package/examples/agents/researcher/skills/web-research.md +28 -0
- package/examples/skills/code-review.md +72 -0
- package/examples/skills/summarise.md +59 -0
- package/examples/skills/web-research.md +42 -0
- package/examples/tools/word-count/index.js +27 -0
- package/examples/tools/word-count/tool.json +18 -0
- package/infrastructure/database.js +563 -0
- package/infrastructure/scheduler.js +122 -0
- package/llm/client.js +206 -0
- package/migrations/001-initial.sql +121 -0
- package/migrations/002-debuggability.sql +13 -0
- package/migrations/003-drop-orphaned-columns.sql +72 -0
- package/migrations/004-session-message-token-fields.sql +78 -0
- package/migrations/005-session-thinking.sql +5 -0
- package/package.json +30 -0
- package/schemas/agent.json +143 -0
- package/schemas/settings.json +111 -0
- package/scripts/fetch-models.js +93 -0
- package/session-debug-scenario.md +248 -0
- package/settings/fields.js +52 -0
- package/system-prompts/base-core.md +7 -0
- package/system-prompts/environment.md +13 -0
- package/system-prompts/reminders/anti-drift.md +6 -0
- package/system-prompts/reminders/stall-recovery.md +10 -0
- package/system-prompts/safety-rules.md +25 -0
- package/system-prompts/task-heuristics.md +27 -0
- package/test/client.js +71 -0
- package/test/integration/01-health.test.js +25 -0
- package/test/integration/02-agents.test.js +80 -0
- package/test/integration/03-chat-hello.test.js +48 -0
- package/test/integration/04-chat-multiturn.test.js +61 -0
- package/test/integration/05-chat-writer.test.js +48 -0
- package/test/integration/06-task-basic.test.js +68 -0
- package/test/integration/07-task-tools.test.js +74 -0
- package/test/integration/08-task-code-analysis.test.js +69 -0
- package/test/integration/09-memory-analyst.test.js +63 -0
- package/test/integration/10-task-advanced.test.js +85 -0
- package/test/integration/11-sessions-advanced.test.js +84 -0
- package/test/integration/12-assistant-chat-tools.test.js +75 -0
- package/test/integration/13-edge-cases.test.js +99 -0
- package/test/integration/14-cancel.test.js +62 -0
- package/test/integration/15-debug.test.js +106 -0
- package/test/integration/16-memory-api.test.js +83 -0
- package/test/integration/17-settings-api.test.js +41 -0
- package/test/integration/18-tool-search-activation.test.js +119 -0
- package/test/results/.gitkeep +0 -0
- package/test/runner.js +206 -0
- package/test/smoke.js +216 -0
- package/tools/agent_message.js +85 -0
- package/tools/agent_send.js +80 -0
- package/tools/agent_spawn.js +44 -0
- package/tools/bash.js +49 -0
- package/tools/edit_file.js +41 -0
- package/tools/glob.js +64 -0
- package/tools/grep.js +82 -0
- package/tools/list_dir.js +63 -0
- package/tools/log_write.js +31 -0
- package/tools/memory_read.js +38 -0
- package/tools/memory_search.js +65 -0
- package/tools/memory_write.js +42 -0
- package/tools/read_file.js +48 -0
- package/tools/sleep.js +22 -0
- package/tools/task_create.js +41 -0
- package/tools/task_respond.js +37 -0
- package/tools/task_spawn.js +64 -0
- package/tools/task_status.js +39 -0
- package/tools/task_subscribe.js +37 -0
- package/tools/todo_read.js +26 -0
- package/tools/todo_write.js +38 -0
- package/tools/tool_activate.js +24 -0
- package/tools/tool_search.js +24 -0
- package/tools/web_fetch.js +50 -0
- package/tools/web_search.js +52 -0
- package/tools/write_file.js +28 -0
- package/ui/api.js +190 -0
- package/ui/app.js +281 -0
- package/ui/index.html +382 -0
- package/ui/views/agents.js +377 -0
- package/ui/views/chat.js +610 -0
- package/ui/views/connection.js +96 -0
- package/ui/views/daemons.js +129 -0
- package/ui/views/feed.js +194 -0
- package/ui/views/memory.js +263 -0
- package/ui/views/models.js +146 -0
- package/ui/views/sessions.js +314 -0
- package/ui/views/settings.js +142 -0
- package/ui/views/tasks.js +415 -0
- package/utils/context.js +49 -0
- package/utils/id.js +16 -0
- package/utils/models.js +88 -0
- package/utils/paths.js +213 -0
- package/utils/settings.js +172 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
window.Veil = window.Veil || {};
|
|
3
|
+
window.Veil.views = window.Veil.views || {};
|
|
4
|
+
|
|
5
|
+
window.Veil.views.memory = {
|
|
6
|
+
_tab: 'agent',
|
|
7
|
+
_agent: null,
|
|
8
|
+
_file: null,
|
|
9
|
+
_content: '',
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
return `
|
|
13
|
+
<div style="display:flex;height:100%;">
|
|
14
|
+
<!-- Left: agent/file selector -->
|
|
15
|
+
<div style="width:260px;flex-shrink:0;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;">
|
|
16
|
+
<!-- Tab strip -->
|
|
17
|
+
<div style="display:flex;border-bottom:1px solid var(--border);">
|
|
18
|
+
<button id="mem-tab-agent" class="btn btn-secondary" style="flex:1;border-radius:0;border:none;border-bottom:2px solid ${this._tab==='agent'?'var(--accent)':'transparent'};padding:9px;font-size:13px;">Agent</button>
|
|
19
|
+
<button id="mem-tab-global" class="btn btn-secondary" style="flex:1;border-radius:0;border:none;border-bottom:2px solid ${this._tab==='global'?'var(--accent)':'transparent'};padding:9px;font-size:13px;">Global</button>
|
|
20
|
+
</div>
|
|
21
|
+
<!-- Agent selector (agent tab only) -->
|
|
22
|
+
<div id="mem-agent-sel-wrap" style="${this._tab==='agent'?'':'display:none;'}padding:10px 12px;border-bottom:1px solid var(--border);">
|
|
23
|
+
<select id="mem-agent-sel" class="input" style="padding:5px 10px;font-size:13px;cursor:pointer;"></select>
|
|
24
|
+
</div>
|
|
25
|
+
<!-- File list -->
|
|
26
|
+
<div id="mem-file-list" style="flex:1;overflow-y:auto;">
|
|
27
|
+
<div style="padding:16px;text-align:center;color:var(--text3);font-size:13px;">${window.Veil.utils.spinner()}</div>
|
|
28
|
+
</div>
|
|
29
|
+
<!-- New file button -->
|
|
30
|
+
<div style="padding:10px 12px;border-top:1px solid var(--border);">
|
|
31
|
+
<button id="btn-mem-newfile" class="btn btn-secondary" style="width:100%;justify-content:center;">+ New File</button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<!-- Right: editor -->
|
|
35
|
+
<div id="mem-editor-panel" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
|
36
|
+
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">
|
|
37
|
+
Select a file to edit
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>`;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async mount() {
|
|
44
|
+
await this._populateAgentSel();
|
|
45
|
+
if (this._tab === 'agent') await this._loadFiles();
|
|
46
|
+
|
|
47
|
+
document.getElementById('mem-tab-agent').addEventListener('click', async () => {
|
|
48
|
+
this._tab = 'agent'; this._file = null; this._content = '';
|
|
49
|
+
this._updateTabs();
|
|
50
|
+
document.getElementById('mem-agent-sel-wrap').style.display = '';
|
|
51
|
+
await this._loadFiles();
|
|
52
|
+
this._showEditorPlaceholder();
|
|
53
|
+
});
|
|
54
|
+
document.getElementById('mem-tab-global').addEventListener('click', async () => {
|
|
55
|
+
this._tab = 'global'; this._file = null; this._content = '';
|
|
56
|
+
this._updateTabs();
|
|
57
|
+
document.getElementById('mem-agent-sel-wrap').style.display = 'none';
|
|
58
|
+
await this._loadFiles();
|
|
59
|
+
this._showEditorPlaceholder();
|
|
60
|
+
});
|
|
61
|
+
document.getElementById('mem-agent-sel').addEventListener('change', async (e) => {
|
|
62
|
+
this._agent = e.target.value;
|
|
63
|
+
this._file = null;
|
|
64
|
+
await this._loadFiles();
|
|
65
|
+
this._showEditorPlaceholder();
|
|
66
|
+
});
|
|
67
|
+
document.getElementById('btn-mem-newfile').addEventListener('click', () => this._showNewFileModal());
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
unmount() {},
|
|
71
|
+
|
|
72
|
+
_updateTabs() {
|
|
73
|
+
const agentBtn = document.getElementById('mem-tab-agent');
|
|
74
|
+
const globalBtn = document.getElementById('mem-tab-global');
|
|
75
|
+
if (agentBtn) agentBtn.style.borderBottomColor = this._tab === 'agent' ? 'var(--accent)' : 'transparent';
|
|
76
|
+
if (globalBtn) globalBtn.style.borderBottomColor = this._tab === 'global' ? 'var(--accent)' : 'transparent';
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async _populateAgentSel() {
|
|
80
|
+
const sel = document.getElementById('mem-agent-sel');
|
|
81
|
+
if (!sel) return;
|
|
82
|
+
try {
|
|
83
|
+
const data = await window.Veil.state.api.listAgents();
|
|
84
|
+
const preferred = window.Veil.state.currentAgent || this._agent;
|
|
85
|
+
sel.innerHTML = (data.agents || []).map(a =>
|
|
86
|
+
`<option value="${window.Veil.utils.esc(a.name)}" ${a.name === preferred ? 'selected' : ''}>${window.Veil.utils.esc(a.name)}</option>`
|
|
87
|
+
).join('') || '<option value="">No agents</option>';
|
|
88
|
+
this._agent = sel.value;
|
|
89
|
+
} catch {}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async _loadFiles() {
|
|
93
|
+
const el = document.getElementById('mem-file-list');
|
|
94
|
+
if (!el) return;
|
|
95
|
+
el.innerHTML = `<div style="padding:14px;text-align:center;color:var(--text3);">${window.Veil.utils.spinner()}</div>`;
|
|
96
|
+
try {
|
|
97
|
+
let files = [];
|
|
98
|
+
if (this._tab === 'agent') {
|
|
99
|
+
if (!this._agent) { el.innerHTML = `<div style="padding:14px;color:var(--text3);font-size:13px;">Select an agent</div>`; return; }
|
|
100
|
+
const data = await window.Veil.state.api.getAgentMemory(this._agent);
|
|
101
|
+
files = data.files || [];
|
|
102
|
+
} else {
|
|
103
|
+
const data = await window.Veil.state.api.listMemory();
|
|
104
|
+
files = data.files || [];
|
|
105
|
+
}
|
|
106
|
+
if (files.length === 0) {
|
|
107
|
+
el.innerHTML = `<div style="padding:16px;text-align:center;color:var(--text3);font-size:13px;">No memory files.<br>Use <b>+ New File</b> to create one.</div>`;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const U = window.Veil.utils;
|
|
111
|
+
el.innerHTML = files.map(f => `
|
|
112
|
+
<div class="list-item ${this._file === f.name ? 'selected' : ''}" data-mem-file="${U.esc(f.name)}">
|
|
113
|
+
<div style="font-size:13.5px;color:var(--text);font-weight:500;">${U.esc(f.name)}</div>
|
|
114
|
+
<div style="font-size:12px;color:var(--text3);margin-top:2px;">${Math.ceil((f.size || 0) / 1024 * 10) / 10} KB · ${U.relTime(f.modified)}</div>
|
|
115
|
+
</div>`).join('');
|
|
116
|
+
el.querySelectorAll('[data-mem-file]').forEach(row => {
|
|
117
|
+
row.addEventListener('click', () => {
|
|
118
|
+
const name = row.getAttribute('data-mem-file');
|
|
119
|
+
el.querySelectorAll('.list-item').forEach(r => r.classList.remove('selected'));
|
|
120
|
+
row.classList.add('selected');
|
|
121
|
+
this._file = name;
|
|
122
|
+
this._openFile(name);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async _openFile(name) {
|
|
131
|
+
const el = document.getElementById('mem-editor-panel');
|
|
132
|
+
if (!el) return;
|
|
133
|
+
el.innerHTML = `<div style="padding:20px;color:var(--text3);">${window.Veil.utils.spinner()} Loading…</div>`;
|
|
134
|
+
try {
|
|
135
|
+
let content = '';
|
|
136
|
+
if (this._tab === 'agent') {
|
|
137
|
+
const data = await window.Veil.state.api.getAgentMemoryFile(this._agent, name);
|
|
138
|
+
content = data.content || '';
|
|
139
|
+
} else {
|
|
140
|
+
const data = await window.Veil.state.api.getMemoryFile(name);
|
|
141
|
+
content = data.content || '';
|
|
142
|
+
}
|
|
143
|
+
this._content = content;
|
|
144
|
+
this._renderEditor(name, content);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
_renderEditor(name, content) {
|
|
151
|
+
const el = document.getElementById('mem-editor-panel');
|
|
152
|
+
if (!el) return;
|
|
153
|
+
const U = window.Veil.utils;
|
|
154
|
+
el.innerHTML = `
|
|
155
|
+
<div style="padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;">
|
|
156
|
+
<span style="font-weight:600;font-size:14px;color:var(--text);">${U.esc(name)}</span>
|
|
157
|
+
<span style="font-size:12px;color:var(--text3);">${this._tab === 'agent' ? U.esc(this._agent) : 'global'}</span>
|
|
158
|
+
<div style="margin-left:auto;display:flex;gap:8px;">
|
|
159
|
+
<button class="btn btn-success btn-sm" id="btn-mem-save">Save</button>
|
|
160
|
+
<button class="btn btn-danger btn-sm" id="btn-mem-delete">Delete</button>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div style="flex:1;overflow:hidden;display:flex;flex-direction:column;">
|
|
164
|
+
<textarea id="mem-editor-area" class="input mono" style="flex:1;border:none;border-radius:0;resize:none;padding:14px 16px;font-size:13px;line-height:1.6;background:var(--bg);"></textarea>
|
|
165
|
+
</div>
|
|
166
|
+
<div style="padding:8px 16px;border-top:1px solid var(--border);font-size:12px;color:var(--text3);display:flex;justify-content:space-between;">
|
|
167
|
+
<span id="mem-save-status"></span>
|
|
168
|
+
<span>Markdown file · changes take effect on next session</span>
|
|
169
|
+
</div>`;
|
|
170
|
+
|
|
171
|
+
document.getElementById('btn-mem-save').addEventListener('click', async () => {
|
|
172
|
+
const textarea = document.getElementById('mem-editor-area');
|
|
173
|
+
if (!textarea) return;
|
|
174
|
+
const newContent = textarea.value;
|
|
175
|
+
const statusEl = document.getElementById('mem-save-status');
|
|
176
|
+
const btn = document.getElementById('btn-mem-save');
|
|
177
|
+
btn.disabled = true; btn.innerHTML = U.spinner() + ' Saving…';
|
|
178
|
+
try {
|
|
179
|
+
if (this._tab === 'agent') {
|
|
180
|
+
await window.Veil.state.api.putAgentMemoryFile(this._agent, name, newContent);
|
|
181
|
+
} else {
|
|
182
|
+
await window.Veil.state.api.putMemoryFile(name, newContent);
|
|
183
|
+
}
|
|
184
|
+
if (statusEl) { statusEl.textContent = 'Saved ✓'; statusEl.style.color = 'var(--green)'; }
|
|
185
|
+
window.Veil.utils.toast('Memory file saved', 'success');
|
|
186
|
+
this._content = newContent;
|
|
187
|
+
btn.disabled = false; btn.textContent = 'Save';
|
|
188
|
+
this._loadFiles();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
btn.disabled = false; btn.textContent = 'Save';
|
|
191
|
+
if (statusEl) { statusEl.textContent = 'Error: ' + err.message; statusEl.style.color = 'var(--red)'; }
|
|
192
|
+
window.Veil.utils.toast('Save failed: ' + err.message, 'error');
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
document.getElementById('btn-mem-delete').addEventListener('click', async () => {
|
|
197
|
+
if (!confirm(`Delete "${name}"?`)) return;
|
|
198
|
+
try {
|
|
199
|
+
if (this._tab === 'agent') {
|
|
200
|
+
await window.Veil.state.api.deleteAgentMemoryFile(this._agent, name);
|
|
201
|
+
} else {
|
|
202
|
+
await window.Veil.state.api.deleteMemoryFile(name);
|
|
203
|
+
}
|
|
204
|
+
window.Veil.utils.toast('File deleted', 'success');
|
|
205
|
+
this._file = null;
|
|
206
|
+
this._showEditorPlaceholder();
|
|
207
|
+
await this._loadFiles();
|
|
208
|
+
} catch (err) { window.Veil.utils.toast('Delete failed: ' + err.message, 'error'); }
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
_showEditorPlaceholder() {
|
|
213
|
+
const el = document.getElementById('mem-editor-panel');
|
|
214
|
+
if (el) el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">Select a memory file to edit</div>`;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
_showNewFileModal() {
|
|
218
|
+
window.Veil.modal.show(`
|
|
219
|
+
<div>
|
|
220
|
+
<h3 style="font-size:1rem;font-weight:700;color:var(--text);margin-bottom:16px;">New Memory File</h3>
|
|
221
|
+
<div id="nm-err" style="display:none;background:var(--red-dim);color:var(--red);border:1px solid var(--surface2);border-radius:6px;padding:8px 12px;margin-bottom:12px;font-size:13px;"></div>
|
|
222
|
+
<div style="margin-bottom:12px;">
|
|
223
|
+
<label style="display:block;font-size:12.5px;color:var(--text2);margin-bottom:5px;">Filename <span style="color:var(--text3);">(e.g. MEMORY.md)</span></label>
|
|
224
|
+
<input id="nm-filename" class="input" placeholder="MEMORY.md" />
|
|
225
|
+
</div>
|
|
226
|
+
<div style="margin-bottom:20px;">
|
|
227
|
+
<label style="display:block;font-size:12.5px;color:var(--text2);margin-bottom:5px;">Initial content</label>
|
|
228
|
+
<textarea id="nm-content" class="input mono" rows="6" placeholder="## Notes\n\n" style="resize:vertical;"></textarea>
|
|
229
|
+
</div>
|
|
230
|
+
<div style="display:flex;gap:10px;justify-content:flex-end;">
|
|
231
|
+
<button class="btn btn-secondary" onclick="window.Veil.modal.hide()">Cancel</button>
|
|
232
|
+
<button class="btn btn-primary" id="nm-submit">Create</button>
|
|
233
|
+
</div>
|
|
234
|
+
</div>`);
|
|
235
|
+
|
|
236
|
+
document.getElementById('nm-submit').addEventListener('click', async () => {
|
|
237
|
+
const filename = document.getElementById('nm-filename').value.trim();
|
|
238
|
+
const content = document.getElementById('nm-content').value;
|
|
239
|
+
const errEl = document.getElementById('nm-err');
|
|
240
|
+
if (!filename) { errEl.textContent = 'Filename is required'; errEl.style.display = 'block'; return; }
|
|
241
|
+
if (!filename.match(/^[a-zA-Z0-9_-]+\.md$/)) {
|
|
242
|
+
errEl.textContent = 'Filename must match [a-z0-9A-Z_-]+.md'; errEl.style.display = 'block'; return;
|
|
243
|
+
}
|
|
244
|
+
const btn = document.getElementById('nm-submit');
|
|
245
|
+
btn.disabled = true;
|
|
246
|
+
try {
|
|
247
|
+
if (this._tab === 'agent' && this._agent) {
|
|
248
|
+
await window.Veil.state.api.putAgentMemoryFile(this._agent, filename, content);
|
|
249
|
+
} else {
|
|
250
|
+
await window.Veil.state.api.putMemoryFile(filename, content);
|
|
251
|
+
}
|
|
252
|
+
window.Veil.modal.hide();
|
|
253
|
+
window.Veil.utils.toast('File created', 'success');
|
|
254
|
+
this._file = filename;
|
|
255
|
+
await this._loadFiles();
|
|
256
|
+
this._openFile(filename);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
btn.disabled = false;
|
|
259
|
+
errEl.textContent = err.message; errEl.style.display = 'block';
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
window.Veil = window.Veil || {};
|
|
3
|
+
window.Veil.views = window.Veil.views || {};
|
|
4
|
+
|
|
5
|
+
window.Veil.views.models = {
|
|
6
|
+
_models: [],
|
|
7
|
+
_filter: '',
|
|
8
|
+
|
|
9
|
+
render() {
|
|
10
|
+
return `
|
|
11
|
+
<div style="padding:20px;max-width:1100px;">
|
|
12
|
+
<div style="display:flex;align-items:center;gap:12px;margin-bottom:20px;flex-wrap:wrap;">
|
|
13
|
+
<h1 style="font-size:16px;font-weight:700;color:var(--text);flex:none;">Models</h1>
|
|
14
|
+
<input id="models-search" class="input" style="flex:1;min-width:200px;max-width:360px;padding:6px 12px;" placeholder="Search by name or provider…" value="${window.Veil.utils.esc(this._filter)}" />
|
|
15
|
+
<button id="btn-refresh-models" class="btn btn-secondary btn-sm" style="flex:none;">↻ Refresh from OpenRouter</button>
|
|
16
|
+
<span id="models-count" style="font-size:12.5px;color:var(--text3);flex:none;"></span>
|
|
17
|
+
</div>
|
|
18
|
+
<div id="models-table-wrap">
|
|
19
|
+
<div style="text-align:center;padding:40px;color:var(--text3);">${window.Veil.utils.spinner()}</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>`;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async mount() {
|
|
25
|
+
document.getElementById('models-search').addEventListener('input', (e) => {
|
|
26
|
+
this._filter = e.target.value;
|
|
27
|
+
this._renderTable();
|
|
28
|
+
});
|
|
29
|
+
document.getElementById('btn-refresh-models').addEventListener('click', () => this._refresh());
|
|
30
|
+
await this._load();
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
unmount() {},
|
|
34
|
+
|
|
35
|
+
async _load() {
|
|
36
|
+
const wrap = document.getElementById('models-table-wrap');
|
|
37
|
+
if (!wrap) return;
|
|
38
|
+
try {
|
|
39
|
+
const data = await window.Veil.state.api.listModels();
|
|
40
|
+
this._models = data.models || [];
|
|
41
|
+
this._renderTable();
|
|
42
|
+
const updated = data.updated_at ? `Updated: ${window.Veil.utils.relTime(data.updated_at)}` : '';
|
|
43
|
+
const cnt = document.getElementById('models-count');
|
|
44
|
+
if (cnt) cnt.textContent = `${this._models.length} models · ${updated}`;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (wrap) wrap.innerHTML = `<div style="color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
_renderTable() {
|
|
51
|
+
const wrap = document.getElementById('models-table-wrap');
|
|
52
|
+
if (!wrap) return;
|
|
53
|
+
const U = window.Veil.utils;
|
|
54
|
+
const q = (this._filter || '').toLowerCase();
|
|
55
|
+
const models = q
|
|
56
|
+
? this._models.filter(m => m.id.toLowerCase().includes(q) || (m.name||'').toLowerCase().includes(q))
|
|
57
|
+
: this._models;
|
|
58
|
+
|
|
59
|
+
if (models.length === 0) {
|
|
60
|
+
wrap.innerHTML = `<div style="text-align:center;padding:30px;color:var(--text3);font-size:13.5px;">${q ? 'No models match "' + U.esc(q) + '"' : 'No models loaded'}</div>`;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const cnt = document.getElementById('models-count');
|
|
65
|
+
if (cnt && q) cnt.textContent = `${models.length} / ${this._models.length} models shown`;
|
|
66
|
+
|
|
67
|
+
wrap.innerHTML = `
|
|
68
|
+
<div style="overflow-x:auto;">
|
|
69
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr style="border-bottom:1px solid var(--border);">
|
|
72
|
+
<th style="text-align:left;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Model ID</th>
|
|
73
|
+
<th style="text-align:left;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Name</th>
|
|
74
|
+
<th style="text-align:right;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Context</th>
|
|
75
|
+
<th style="text-align:right;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Input $/M</th>
|
|
76
|
+
<th style="text-align:right;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Output $/M</th>
|
|
77
|
+
<th style="text-align:left;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Modalities</th>
|
|
78
|
+
</tr>
|
|
79
|
+
</thead>
|
|
80
|
+
<tbody>
|
|
81
|
+
${models.map(m => `
|
|
82
|
+
<tr class="model-row" data-model-id="${U.esc(m.id)}" style="border-bottom:1px solid var(--border);cursor:pointer;transition:background 0.1s;">
|
|
83
|
+
<td style="padding:8px 12px;font-family:monospace;font-size:12.5px;color:var(--accent);">${U.esc(m.id)}</td>
|
|
84
|
+
<td style="padding:8px 12px;color:var(--text);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${U.esc(m.name || '')}</td>
|
|
85
|
+
<td style="padding:8px 12px;text-align:right;color:var(--text2);">${m.context_length ? (m.context_length/1000).toFixed(0) + 'k' : '—'}</td>
|
|
86
|
+
<td style="padding:8px 12px;text-align:right;color:var(--text2);">${m.pricing?.prompt != null ? '$' + (m.pricing.prompt * 1e6).toFixed(2) : '—'}</td>
|
|
87
|
+
<td style="padding:8px 12px;text-align:right;color:var(--text2);">${m.pricing?.completion != null ? '$' + (m.pricing.completion * 1e6).toFixed(2) : '—'}</td>
|
|
88
|
+
<td style="padding:8px 12px;color:var(--text3);font-size:12px;">${(m.input_modalities||[]).join('+') || '—'}</td>
|
|
89
|
+
</tr>`).join('')}
|
|
90
|
+
</tbody>
|
|
91
|
+
</table>
|
|
92
|
+
</div>`;
|
|
93
|
+
|
|
94
|
+
wrap.querySelectorAll('.model-row').forEach(row => {
|
|
95
|
+
row.addEventListener('mouseenter', () => row.style.background = 'var(--surface2)');
|
|
96
|
+
row.addEventListener('mouseleave', () => row.style.background = '');
|
|
97
|
+
row.addEventListener('click', () => {
|
|
98
|
+
const id = row.getAttribute('data-model-id');
|
|
99
|
+
const m = this._models.find(x => x.id === id);
|
|
100
|
+
if (m) this._showDetail(m);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
_showDetail(m) {
|
|
106
|
+
const U = window.Veil.utils;
|
|
107
|
+
window.Veil.modal.show(`
|
|
108
|
+
<div>
|
|
109
|
+
<h3 style="font-size:15px;font-weight:700;color:var(--text);margin-bottom:4px;">${U.esc(m.name || m.id)}</h3>
|
|
110
|
+
<div style="display:flex;align-items:center;gap:6px;">
|
|
111
|
+
<code style="font-size:12.5px;color:var(--accent);">${U.esc(m.id)}</code>
|
|
112
|
+
<button onclick="navigator.clipboard.writeText('${U.esc(m.id)}');this.textContent='✓';setTimeout(()=>this.textContent='⎘',1000)" style="background:none;border:none;color:white;cursor:pointer;font-size:14px;padding:2px 4px;" title="Copy model ID">⎘</button>
|
|
113
|
+
</div>
|
|
114
|
+
<p style="font-size:13px;color:var(--text2);margin:8px 0 12px 0;">${U.esc(m.description || '(no description)')}</p>
|
|
115
|
+
<hr class="divider" style="margin:16px 0;">
|
|
116
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;">
|
|
117
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Context Length</div><div style="color:var(--text);">${m.context_length ? m.context_length.toLocaleString() + ' tokens' : '—'}</div></div>
|
|
118
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Max Completion</div><div style="color:var(--text);">${m.max_completion_tokens ? m.max_completion_tokens.toLocaleString() + ' tokens' : '—'}</div></div>
|
|
119
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Input ($/M tokens)</div><div style="color:var(--text);">${m.pricing?.prompt != null ? '$' + (m.pricing.prompt * 1e6).toFixed(4) : '—'}</div></div>
|
|
120
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Output ($/M tokens)</div><div style="color:var(--text);">${m.pricing?.completion != null ? '$' + (m.pricing.completion * 1e6).toFixed(4) : '—'}</div></div>
|
|
121
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Cache Read ($/M)</div><div style="color:var(--text);">${m.pricing?.cache_read != null ? '$' + (m.pricing.cache_read * 1e6).toFixed(4) : '—'}</div></div>
|
|
122
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Cache Write ($/M)</div><div style="color:var(--text);">${m.pricing?.cache_write != null ? '$' + (m.pricing.cache_write * 1e6).toFixed(4) : '—'}</div></div>
|
|
123
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Input Modalities</div><div style="color:var(--text);">${(m.input_modalities||[]).join(', ') || '—'}</div></div>
|
|
124
|
+
<div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Output Modalities</div><div style="color:var(--text);">${(m.output_modalities||[]).join(', ') || '—'}</div></div>
|
|
125
|
+
</div>
|
|
126
|
+
<div style="text-align:right;"><button class="btn btn-secondary" onclick="window.Veil.modal.hide()">Close</button></div>
|
|
127
|
+
</div>`);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async _refresh() {
|
|
131
|
+
const btn = document.getElementById('btn-refresh-models');
|
|
132
|
+
if (btn) { btn.disabled = true; btn.innerHTML = window.Veil.utils.spinner() + ' Refreshing…'; }
|
|
133
|
+
try {
|
|
134
|
+
const data = await window.Veil.state.api.refreshModels();
|
|
135
|
+
this._models = data.models || [];
|
|
136
|
+
this._renderTable();
|
|
137
|
+
window.Veil.utils.toast(`Models refreshed: ${this._models.length} loaded`, 'success');
|
|
138
|
+
const cnt = document.getElementById('models-count');
|
|
139
|
+
if (cnt) cnt.textContent = `${this._models.length} models · Updated just now`;
|
|
140
|
+
} catch (err) {
|
|
141
|
+
window.Veil.utils.toast('Refresh failed: ' + err.message, 'error');
|
|
142
|
+
} finally {
|
|
143
|
+
if (btn) { btn.disabled = false; btn.textContent = '↻ Refresh from OpenRouter'; }
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
};
|