antri_cli 1.26.1 → 1.28.0
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/README.md +89 -42
- package/assets/desktop_panel.png +0 -0
- package/assets/home.png +0 -0
- package/dist/cli/promptToolkit.d.ts.map +1 -1
- package/dist/cli/promptToolkit.js +2 -0
- package/dist/cli/promptToolkit.js.map +1 -1
- package/dist/cli/shortcuts.d.ts.map +1 -1
- package/dist/cli/shortcuts.js +13 -0
- package/dist/cli/shortcuts.js.map +1 -1
- package/dist/core/config.js +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/updater.d.ts +1 -1
- package/dist/core/updater.js +1 -1
- package/dist/desktop/public/app.js +699 -0
- package/dist/desktop/public/index.html +338 -0
- package/dist/desktop/public/style.css +962 -0
- package/dist/desktop/server.d.ts +11 -0
- package/dist/desktop/server.d.ts.map +1 -0
- package/dist/desktop/server.js +361 -0
- package/dist/desktop/server.js.map +1 -0
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/dist/memory/episodic.d.ts +1 -0
- package/dist/memory/episodic.d.ts.map +1 -1
- package/dist/memory/episodic.js +3 -0
- package/dist/memory/episodic.js.map +1 -1
- package/dist/memory/manager.d.ts +6 -0
- package/dist/memory/manager.d.ts.map +1 -1
- package/dist/memory/manager.js +8 -0
- package/dist/memory/manager.js.map +1 -1
- package/dist/mobile/public/app.js +449 -0
- package/dist/mobile/public/index.html +287 -0
- package/dist/mobile/public/manifest.json +16 -0
- package/dist/mobile/public/style.css +589 -0
- package/dist/mobile/server.d.ts +14 -0
- package/dist/mobile/server.d.ts.map +1 -0
- package/dist/mobile/server.js +232 -0
- package/dist/mobile/server.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
// ANTRI Desktop Control Plane Client Engine
|
|
2
|
+
|
|
3
|
+
let currentConfig = null;
|
|
4
|
+
let activeTab = 'chat';
|
|
5
|
+
let attachedFiles = [];
|
|
6
|
+
let availableCommands = [];
|
|
7
|
+
let activePaletteMatches = [];
|
|
8
|
+
let paletteSelectedIndex = 0;
|
|
9
|
+
let activePaletteMode = null; // 'slash' | 'file' | null
|
|
10
|
+
|
|
11
|
+
// Initialize on Load
|
|
12
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
13
|
+
await loadStatus();
|
|
14
|
+
await loadCommands();
|
|
15
|
+
await loadProfiles();
|
|
16
|
+
await loadSkills();
|
|
17
|
+
await loadMemory();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Load System Status & Config
|
|
21
|
+
async function loadStatus() {
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch('/api/status');
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
currentConfig = data.config;
|
|
26
|
+
|
|
27
|
+
// Update Mode buttons
|
|
28
|
+
switchMode(currentConfig.mode || 'vibe', false);
|
|
29
|
+
|
|
30
|
+
// Update Perms badge
|
|
31
|
+
updatePermsBadge(currentConfig.alwaysAllow);
|
|
32
|
+
|
|
33
|
+
// Update Provider selector
|
|
34
|
+
const provSelect = document.getElementById('select-provider');
|
|
35
|
+
if (provSelect) provSelect.value = currentConfig.provider;
|
|
36
|
+
|
|
37
|
+
await loadModels();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error('Failed to load status:', err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Load Slash Commands for Prompt Toolkit
|
|
44
|
+
async function loadCommands() {
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch('/api/commands');
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
availableCommands = data.commands || [];
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('Failed to load commands:', err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Load Models for Provider
|
|
55
|
+
async function loadModels() {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch('/api/models');
|
|
58
|
+
const data = await res.json();
|
|
59
|
+
const modelSelect = document.getElementById('select-model');
|
|
60
|
+
modelSelect.innerHTML = '';
|
|
61
|
+
|
|
62
|
+
data.models.forEach((m) => {
|
|
63
|
+
const opt = document.createElement('option');
|
|
64
|
+
opt.value = m.id;
|
|
65
|
+
opt.textContent = `${m.name} (${m.category})`;
|
|
66
|
+
if (m.id === currentConfig.model) opt.selected = true;
|
|
67
|
+
modelSelect.appendChild(opt);
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('Failed to load models:', err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Switch Mode (Plan / Vibe)
|
|
75
|
+
async function switchMode(mode, triggerSave = true) {
|
|
76
|
+
const btnVibe = document.getElementById('btn-mode-vibe');
|
|
77
|
+
const btnPlan = document.getElementById('btn-mode-plan');
|
|
78
|
+
|
|
79
|
+
if (mode === 'plan') {
|
|
80
|
+
btnPlan.classList.add('active');
|
|
81
|
+
btnVibe.classList.remove('active');
|
|
82
|
+
} else {
|
|
83
|
+
btnVibe.classList.add('active');
|
|
84
|
+
btnPlan.classList.remove('active');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (triggerSave && currentConfig) {
|
|
88
|
+
currentConfig.mode = mode;
|
|
89
|
+
await fetch('/api/config', {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: { 'Content-Type': 'application/json' },
|
|
92
|
+
body: JSON.stringify({ mode }),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Toggle Always-Allow Permissions
|
|
98
|
+
async function toggleAlwaysAllow() {
|
|
99
|
+
if (!currentConfig) return;
|
|
100
|
+
const next = !currentConfig.alwaysAllow;
|
|
101
|
+
currentConfig.alwaysAllow = next;
|
|
102
|
+
updatePermsBadge(next);
|
|
103
|
+
|
|
104
|
+
await fetch('/api/config', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ alwaysAllow: next }),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updatePermsBadge(alwaysAllow) {
|
|
112
|
+
const badge = document.getElementById('perms-text');
|
|
113
|
+
if (alwaysAllow) {
|
|
114
|
+
badge.textContent = 'Always-Allow';
|
|
115
|
+
} else {
|
|
116
|
+
badge.textContent = 'Ask-First';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Provider & Model Handlers
|
|
121
|
+
async function onProviderChange(provider) {
|
|
122
|
+
await fetch('/api/config', {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: { 'Content-Type': 'application/json' },
|
|
125
|
+
body: JSON.stringify({ provider }),
|
|
126
|
+
});
|
|
127
|
+
await loadStatus();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function onModelChange(model) {
|
|
131
|
+
await fetch('/api/config', {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { 'Content-Type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ model }),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Tab Switching
|
|
139
|
+
function showTab(tabName) {
|
|
140
|
+
activeTab = tabName;
|
|
141
|
+
document.querySelectorAll('.tab-panel').forEach((el) => el.classList.remove('active'));
|
|
142
|
+
document.querySelectorAll('.nav-item').forEach((el) => el.classList.remove('active'));
|
|
143
|
+
|
|
144
|
+
const targetPanel = document.getElementById(`tab-${tabName}`);
|
|
145
|
+
if (targetPanel) targetPanel.classList.add('active');
|
|
146
|
+
|
|
147
|
+
const navIndex = ['chat', 'dialectic', 'goal', 'profiles', 'skills', 'memory'].indexOf(tabName);
|
|
148
|
+
const navButtons = document.querySelectorAll('.nav-item');
|
|
149
|
+
if (navButtons[navIndex]) navButtons[navIndex].classList.add('active');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// File Upload Handler (Images & Files)
|
|
153
|
+
async function handleFileSelected(event) {
|
|
154
|
+
const files = event.target.files;
|
|
155
|
+
if (!files || files.length === 0) return;
|
|
156
|
+
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
const reader = new FileReader();
|
|
159
|
+
reader.onload = async (e) => {
|
|
160
|
+
const data = e.target.result;
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch('/api/upload', {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
headers: { 'Content-Type': 'application/json' },
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
fileName: file.name,
|
|
167
|
+
fileType: file.type || 'text/plain',
|
|
168
|
+
data,
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
const uploadRes = await res.json();
|
|
172
|
+
if (uploadRes.success) {
|
|
173
|
+
attachedFiles.push({
|
|
174
|
+
name: file.name,
|
|
175
|
+
path: uploadRes.filePath,
|
|
176
|
+
isImage: uploadRes.isImage,
|
|
177
|
+
dataUrl: uploadRes.isImage ? data : null,
|
|
178
|
+
});
|
|
179
|
+
renderAttachmentChips();
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error('File upload failed:', err);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
if (file.type.startsWith('image/')) {
|
|
186
|
+
reader.readAsDataURL(file);
|
|
187
|
+
} else {
|
|
188
|
+
reader.readAsText(file);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Reset input
|
|
193
|
+
event.target.value = '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderAttachmentChips() {
|
|
197
|
+
const tray = document.getElementById('attachment-preview-tray');
|
|
198
|
+
if (!tray) return;
|
|
199
|
+
|
|
200
|
+
if (attachedFiles.length === 0) {
|
|
201
|
+
tray.classList.add('hidden');
|
|
202
|
+
tray.innerHTML = '';
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
tray.classList.remove('hidden');
|
|
207
|
+
tray.innerHTML = '';
|
|
208
|
+
|
|
209
|
+
attachedFiles.forEach((file, index) => {
|
|
210
|
+
const chip = document.createElement('div');
|
|
211
|
+
chip.className = 'attachment-chip';
|
|
212
|
+
if (file.isImage && file.dataUrl) {
|
|
213
|
+
chip.innerHTML = `
|
|
214
|
+
<img src="${file.dataUrl}" alt="preview" />
|
|
215
|
+
<span>${file.name}</span>
|
|
216
|
+
<button class="remove-chip-btn" onclick="removeAttachment(${index})">×</button>
|
|
217
|
+
`;
|
|
218
|
+
} else {
|
|
219
|
+
chip.innerHTML = `
|
|
220
|
+
<span>${file.name}</span>
|
|
221
|
+
<button class="remove-chip-btn" onclick="removeAttachment(${index})">×</button>
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
tray.appendChild(chip);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function removeAttachment(index) {
|
|
229
|
+
attachedFiles.splice(index, 1);
|
|
230
|
+
renderAttachmentChips();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Prompt Toolkit Text & Key Handler
|
|
234
|
+
async function handleInputText(event) {
|
|
235
|
+
const val = event.target.value;
|
|
236
|
+
const cursorPos = event.target.selectionStart;
|
|
237
|
+
|
|
238
|
+
// 1. Slash command mode
|
|
239
|
+
if (val.startsWith('/')) {
|
|
240
|
+
const query = val.toLowerCase();
|
|
241
|
+
activePaletteMatches = availableCommands.filter((cmd) => {
|
|
242
|
+
const baseName = cmd.name.split(' ')[0].toLowerCase();
|
|
243
|
+
return baseName.startsWith(query) || cmd.name.toLowerCase().startsWith(query);
|
|
244
|
+
});
|
|
245
|
+
activePaletteMode = 'slash';
|
|
246
|
+
renderPalette('Commands', activePaletteMatches);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 2. Attachment file mode (@)
|
|
251
|
+
const lastAt = val.lastIndexOf('@', cursorPos - 1);
|
|
252
|
+
if (lastAt !== -1 && (lastAt === 0 || val[lastAt - 1] === ' ')) {
|
|
253
|
+
const query = val.slice(lastAt + 1, cursorPos);
|
|
254
|
+
if (!query.includes(' ')) {
|
|
255
|
+
try {
|
|
256
|
+
const res = await fetch(`/api/files?query=${encodeURIComponent(query)}`);
|
|
257
|
+
const data = await res.json();
|
|
258
|
+
activePaletteMatches = data.items.map((item) => ({
|
|
259
|
+
name: item.name,
|
|
260
|
+
description: item.isDirectory ? 'Directory' : item.relativePath,
|
|
261
|
+
relativePath: item.relativePath,
|
|
262
|
+
isDirectory: item.isDirectory,
|
|
263
|
+
}));
|
|
264
|
+
activePaletteMode = 'file';
|
|
265
|
+
renderPalette(`Files: ${data.currentDir}`, activePaletteMatches);
|
|
266
|
+
return;
|
|
267
|
+
} catch (e) {}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
hidePalette();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function renderPalette(title, items) {
|
|
275
|
+
const palette = document.getElementById('prompt-toolkit-palette');
|
|
276
|
+
const header = document.getElementById('palette-header');
|
|
277
|
+
const list = document.getElementById('palette-list');
|
|
278
|
+
|
|
279
|
+
if (!palette || !items || items.length === 0) {
|
|
280
|
+
hidePalette();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
header.textContent = title;
|
|
285
|
+
list.innerHTML = '';
|
|
286
|
+
paletteSelectedIndex = Math.min(paletteSelectedIndex, items.length - 1);
|
|
287
|
+
if (paletteSelectedIndex < 0) paletteSelectedIndex = 0;
|
|
288
|
+
|
|
289
|
+
items.slice(0, 10).forEach((item, idx) => {
|
|
290
|
+
const el = document.createElement('div');
|
|
291
|
+
el.className = `palette-item ${idx === paletteSelectedIndex ? 'active' : ''}`;
|
|
292
|
+
el.innerHTML = `
|
|
293
|
+
<span class="palette-name">${item.name}</span>
|
|
294
|
+
<span class="palette-desc">${item.description || ''}</span>
|
|
295
|
+
${item.isDirectory ? '<span class="palette-tag">dir</span>' : ''}
|
|
296
|
+
`;
|
|
297
|
+
el.onclick = () => selectPaletteItem(idx);
|
|
298
|
+
list.appendChild(el);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
palette.classList.remove('hidden');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function hidePalette() {
|
|
305
|
+
const palette = document.getElementById('prompt-toolkit-palette');
|
|
306
|
+
if (palette) palette.classList.add('hidden');
|
|
307
|
+
activePaletteMode = null;
|
|
308
|
+
activePaletteMatches = [];
|
|
309
|
+
paletteSelectedIndex = 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function selectPaletteItem(index) {
|
|
313
|
+
const item = activePaletteMatches[index];
|
|
314
|
+
if (!item) return;
|
|
315
|
+
|
|
316
|
+
const input = document.getElementById('prompt-input');
|
|
317
|
+
|
|
318
|
+
if (activePaletteMode === 'slash') {
|
|
319
|
+
const rawCmd = item.name.split(' ')[0];
|
|
320
|
+
input.value = rawCmd + ' ';
|
|
321
|
+
hidePalette();
|
|
322
|
+
input.focus();
|
|
323
|
+
} else if (activePaletteMode === 'file') {
|
|
324
|
+
const val = input.value;
|
|
325
|
+
const cursorPos = input.selectionStart;
|
|
326
|
+
const lastAt = val.lastIndexOf('@', cursorPos - 1);
|
|
327
|
+
if (lastAt !== -1) {
|
|
328
|
+
input.value = val.slice(0, lastAt) + '@' + item.relativePath + ' ' + val.slice(cursorPos);
|
|
329
|
+
}
|
|
330
|
+
hidePalette();
|
|
331
|
+
input.focus();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function handleInputKey(event) {
|
|
336
|
+
const palette = document.getElementById('prompt-toolkit-palette');
|
|
337
|
+
const isPaletteVisible = palette && !palette.classList.contains('hidden');
|
|
338
|
+
|
|
339
|
+
if (isPaletteVisible && activePaletteMatches.length > 0) {
|
|
340
|
+
if (event.key === 'ArrowUp') {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
paletteSelectedIndex = (paletteSelectedIndex - 1 + activePaletteMatches.length) % activePaletteMatches.length;
|
|
343
|
+
renderPalette(document.getElementById('palette-header').textContent, activePaletteMatches);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (event.key === 'ArrowDown') {
|
|
347
|
+
event.preventDefault();
|
|
348
|
+
paletteSelectedIndex = (paletteSelectedIndex + 1) % activePaletteMatches.length;
|
|
349
|
+
renderPalette(document.getElementById('palette-header').textContent, activePaletteMatches);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (event.key === 'Tab' || event.key === 'Enter') {
|
|
353
|
+
if (!event.ctrlKey && !event.metaKey) {
|
|
354
|
+
event.preventDefault();
|
|
355
|
+
selectPaletteItem(paletteSelectedIndex);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (event.key === 'Escape') {
|
|
360
|
+
hidePalette();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Ctrl + Enter to submit prompt
|
|
366
|
+
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
submitPrompt();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Chat Prompt Submission with SSE Streaming
|
|
373
|
+
async function submitPrompt() {
|
|
374
|
+
const input = document.getElementById('prompt-input');
|
|
375
|
+
let prompt = input.value.trim();
|
|
376
|
+
|
|
377
|
+
// Attach any uploaded files to prompt
|
|
378
|
+
if (attachedFiles.length > 0) {
|
|
379
|
+
const attachmentsText = attachedFiles.map((f) => `\n[Attached File: ${f.name} (${f.path})]`).join('');
|
|
380
|
+
prompt = prompt + '\n' + attachmentsText;
|
|
381
|
+
attachedFiles = [];
|
|
382
|
+
renderAttachmentChips();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!prompt) return;
|
|
386
|
+
|
|
387
|
+
input.value = '';
|
|
388
|
+
hidePalette();
|
|
389
|
+
|
|
390
|
+
// Intercept /debate or /goal inside chat
|
|
391
|
+
if (prompt.startsWith('/debate')) {
|
|
392
|
+
showTab('dialectic');
|
|
393
|
+
document.getElementById('debate-query-input').value = prompt.replace('/debate', '').trim();
|
|
394
|
+
startDebate();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (prompt.startsWith('/goal') || prompt.startsWith('/loop')) {
|
|
398
|
+
showTab('goal');
|
|
399
|
+
document.getElementById('goal-objective-input').value = prompt.replace(/^\/(goal|loop)/, '').trim();
|
|
400
|
+
startGoalLoop();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
appendMessage('user', prompt);
|
|
405
|
+
|
|
406
|
+
const assistantMsgEl = appendMessage('assistant', '');
|
|
407
|
+
const contentEl = assistantMsgEl.querySelector('.msg-content');
|
|
408
|
+
|
|
409
|
+
const sendBtn = document.getElementById('send-btn');
|
|
410
|
+
sendBtn.disabled = true;
|
|
411
|
+
sendBtn.textContent = 'Thinking...';
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const response = await fetch('/api/chat', {
|
|
415
|
+
method: 'POST',
|
|
416
|
+
headers: { 'Content-Type': 'application/json' },
|
|
417
|
+
body: JSON.stringify({ prompt }),
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const reader = response.body.getReader();
|
|
421
|
+
const decoder = new TextDecoder();
|
|
422
|
+
let accumulated = '';
|
|
423
|
+
|
|
424
|
+
while (true) {
|
|
425
|
+
const { done, value } = await reader.read();
|
|
426
|
+
if (done) break;
|
|
427
|
+
|
|
428
|
+
const chunk = decoder.decode(value);
|
|
429
|
+
const lines = chunk.split('\n');
|
|
430
|
+
|
|
431
|
+
for (const line of lines) {
|
|
432
|
+
if (line.startsWith('data: ')) {
|
|
433
|
+
try {
|
|
434
|
+
const data = JSON.parse(line.slice(6));
|
|
435
|
+
if (data.token) {
|
|
436
|
+
accumulated += data.token;
|
|
437
|
+
contentEl.textContent = accumulated;
|
|
438
|
+
scrollToBottom();
|
|
439
|
+
} else if (data.name && data.arguments) {
|
|
440
|
+
// Tool call badge
|
|
441
|
+
const toolBadge = document.createElement('div');
|
|
442
|
+
toolBadge.className = 'tool-badge-pill';
|
|
443
|
+
toolBadge.textContent = `Tool: ${data.name}`;
|
|
444
|
+
assistantMsgEl.insertBefore(toolBadge, contentEl);
|
|
445
|
+
}
|
|
446
|
+
} catch (e) {}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} catch (err) {
|
|
451
|
+
contentEl.textContent = `Error: ${err.message}`;
|
|
452
|
+
} finally {
|
|
453
|
+
sendBtn.disabled = false;
|
|
454
|
+
sendBtn.innerHTML = '<span>Send</span>';
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function appendMessage(role, text) {
|
|
459
|
+
const container = document.getElementById('chat-messages');
|
|
460
|
+
const row = document.createElement('div');
|
|
461
|
+
row.className = `msg-row ${role}`;
|
|
462
|
+
const content = document.createElement('div');
|
|
463
|
+
content.className = 'msg-content';
|
|
464
|
+
content.textContent = text;
|
|
465
|
+
row.appendChild(content);
|
|
466
|
+
container.appendChild(row);
|
|
467
|
+
scrollToBottom();
|
|
468
|
+
return row;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function scrollToBottom() {
|
|
472
|
+
const container = document.getElementById('chat-messages');
|
|
473
|
+
container.scrollTop = container.scrollHeight;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function setPrompt(text) {
|
|
477
|
+
const input = document.getElementById('prompt-input');
|
|
478
|
+
input.value = text;
|
|
479
|
+
input.focus();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Dialectic Debate Runner
|
|
483
|
+
async function startDebate() {
|
|
484
|
+
const input = document.getElementById('debate-query-input');
|
|
485
|
+
const query = input.value.trim();
|
|
486
|
+
if (!query) return;
|
|
487
|
+
|
|
488
|
+
const depth = document.getElementById('debate-depth-select').value;
|
|
489
|
+
|
|
490
|
+
document.getElementById('dialectic-thesis').textContent = 'Generating initial thesis & hypothesis...';
|
|
491
|
+
document.getElementById('dialectic-antithesis').textContent = 'Awaiting thesis to challenge assumptions...';
|
|
492
|
+
document.getElementById('dialectic-verification').textContent = 'Researcher standby for fact-checking...';
|
|
493
|
+
document.getElementById('dialectic-synthesis').textContent = 'Synthesizer awaiting debate completion...';
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const res = await fetch('/api/debate', {
|
|
497
|
+
method: 'POST',
|
|
498
|
+
headers: { 'Content-Type': 'application/json' },
|
|
499
|
+
body: JSON.stringify({ query, depth }),
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const reader = res.body.getReader();
|
|
503
|
+
const decoder = new TextDecoder();
|
|
504
|
+
|
|
505
|
+
while (true) {
|
|
506
|
+
const { done, value } = await reader.read();
|
|
507
|
+
if (done) break;
|
|
508
|
+
|
|
509
|
+
const chunk = decoder.decode(value);
|
|
510
|
+
const lines = chunk.split('\n');
|
|
511
|
+
|
|
512
|
+
for (const line of lines) {
|
|
513
|
+
if (line.startsWith('data: ')) {
|
|
514
|
+
try {
|
|
515
|
+
const data = JSON.parse(line.slice(6));
|
|
516
|
+
if (data.thesis) document.getElementById('dialectic-thesis').textContent = data.thesis;
|
|
517
|
+
if (data.antithesis) document.getElementById('dialectic-antithesis').textContent = data.antithesis;
|
|
518
|
+
if (data.verification) document.getElementById('dialectic-verification').textContent = data.verification;
|
|
519
|
+
if (data.synthesis) document.getElementById('dialectic-synthesis').textContent = data.synthesis;
|
|
520
|
+
} catch (e) {}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} catch (err) {
|
|
525
|
+
document.getElementById('dialectic-synthesis').textContent = `Debate error: ${err.message}`;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Goal Loop Runner
|
|
530
|
+
async function startGoalLoop() {
|
|
531
|
+
const input = document.getElementById('goal-objective-input');
|
|
532
|
+
const objective = input.value.trim();
|
|
533
|
+
if (!objective) return;
|
|
534
|
+
|
|
535
|
+
document.getElementById('goal-stage-1-content').textContent = 'Stage 1 executing: Drafting initial plan and solution...';
|
|
536
|
+
document.getElementById('goal-stage-2-content').textContent = 'Stage 2 standby: Awaiting draft for adversarial critique...';
|
|
537
|
+
document.getElementById('goal-stage-3-content').textContent = 'Stage 3 standby: Awaiting synthesis for hardened output...';
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const res = await fetch('/api/goal', {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
headers: { 'Content-Type': 'application/json' },
|
|
543
|
+
body: JSON.stringify({ objective }),
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const reader = res.body.getReader();
|
|
547
|
+
const decoder = new TextDecoder();
|
|
548
|
+
|
|
549
|
+
while (true) {
|
|
550
|
+
const { done, value } = await reader.read();
|
|
551
|
+
if (done) break;
|
|
552
|
+
|
|
553
|
+
const chunk = decoder.decode(value);
|
|
554
|
+
const lines = chunk.split('\n');
|
|
555
|
+
|
|
556
|
+
for (const line of lines) {
|
|
557
|
+
if (line.startsWith('data: ')) {
|
|
558
|
+
try {
|
|
559
|
+
const data = JSON.parse(line.slice(6));
|
|
560
|
+
if (data.draft) document.getElementById('goal-stage-1-content').textContent = data.draft;
|
|
561
|
+
if (data.critique) document.getElementById('goal-stage-2-content').textContent = data.critique;
|
|
562
|
+
if (data.finalOutput) document.getElementById('goal-stage-3-content').textContent = data.finalOutput;
|
|
563
|
+
} catch (e) {}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
} catch (err) {
|
|
568
|
+
document.getElementById('goal-stage-3-content').textContent = `Goal execution error: ${err.message}`;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Profile Management
|
|
573
|
+
async function loadProfiles() {
|
|
574
|
+
try {
|
|
575
|
+
const res = await fetch('/api/profiles');
|
|
576
|
+
const data = await res.json();
|
|
577
|
+
|
|
578
|
+
const select = document.getElementById('select-profile');
|
|
579
|
+
const list = document.getElementById('profile-items-list');
|
|
580
|
+
select.innerHTML = '';
|
|
581
|
+
list.innerHTML = '';
|
|
582
|
+
|
|
583
|
+
data.profiles.forEach((p) => {
|
|
584
|
+
// Dropdown option
|
|
585
|
+
const opt = document.createElement('option');
|
|
586
|
+
opt.value = p.name;
|
|
587
|
+
opt.textContent = p.name;
|
|
588
|
+
if (p.name === data.activeName) opt.selected = true;
|
|
589
|
+
select.appendChild(opt);
|
|
590
|
+
|
|
591
|
+
// List button
|
|
592
|
+
const btn = document.createElement('button');
|
|
593
|
+
btn.className = `profile-item-btn ${p.name === data.activeName ? 'active' : ''}`;
|
|
594
|
+
btn.textContent = `${p.name}.md`;
|
|
595
|
+
btn.onclick = () => selectProfile(p.name);
|
|
596
|
+
list.appendChild(btn);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
document.getElementById('active-profile-title').textContent = `${data.activeName}.md`;
|
|
600
|
+
document.getElementById('profile-editor').value = data.activeContent || '';
|
|
601
|
+
} catch (err) {
|
|
602
|
+
console.error('Failed to load profiles:', err);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function selectProfile(name) {
|
|
607
|
+
await fetch('/api/profile/select', {
|
|
608
|
+
method: 'POST',
|
|
609
|
+
headers: { 'Content-Type': 'application/json' },
|
|
610
|
+
body: JSON.stringify({ name }),
|
|
611
|
+
});
|
|
612
|
+
await loadProfiles();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function createProfile() {
|
|
616
|
+
const input = document.getElementById('new-profile-name');
|
|
617
|
+
const name = input.value.trim();
|
|
618
|
+
if (!name) return;
|
|
619
|
+
|
|
620
|
+
await fetch('/api/profile/create', {
|
|
621
|
+
method: 'POST',
|
|
622
|
+
headers: { 'Content-Type': 'application/json' },
|
|
623
|
+
body: JSON.stringify({ name }),
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
input.value = '';
|
|
627
|
+
await loadProfiles();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function saveActiveProfile() {
|
|
631
|
+
const content = document.getElementById('profile-editor').value;
|
|
632
|
+
await fetch('/api/profile/save', {
|
|
633
|
+
method: 'POST',
|
|
634
|
+
headers: { 'Content-Type': 'application/json' },
|
|
635
|
+
body: JSON.stringify({ content }),
|
|
636
|
+
});
|
|
637
|
+
alert('Profile saved.');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async function onProfileChange(name) {
|
|
641
|
+
await selectProfile(name);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Skills & Memory Loaders
|
|
645
|
+
async function loadSkills() {
|
|
646
|
+
try {
|
|
647
|
+
const res = await fetch('/api/skills');
|
|
648
|
+
const data = await res.json();
|
|
649
|
+
const grid = document.getElementById('skills-catalog-grid');
|
|
650
|
+
grid.innerHTML = '';
|
|
651
|
+
|
|
652
|
+
data.allTools.forEach((t) => {
|
|
653
|
+
const card = document.createElement('div');
|
|
654
|
+
card.className = 'skill-card';
|
|
655
|
+
card.innerHTML = `
|
|
656
|
+
<h4>${t.name}</h4>
|
|
657
|
+
<p>${t.description}</p>
|
|
658
|
+
<button class="skill-btn" onclick="testSkill('${t.name}')">Dry-Run Skill</button>
|
|
659
|
+
`;
|
|
660
|
+
grid.appendChild(card);
|
|
661
|
+
});
|
|
662
|
+
} catch (err) {
|
|
663
|
+
console.error('Failed to load skills:', err);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async function testSkill(skillName) {
|
|
668
|
+
try {
|
|
669
|
+
const res = await fetch('/api/skill/test', {
|
|
670
|
+
method: 'POST',
|
|
671
|
+
headers: { 'Content-Type': 'application/json' },
|
|
672
|
+
body: JSON.stringify({ skillName, args: {} }),
|
|
673
|
+
});
|
|
674
|
+
const data = await res.json();
|
|
675
|
+
alert(`Skill '${skillName}' Output:\n${data.output || 'Execution complete'}`);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
alert(`Execution failed: ${err.message}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async function loadMemory() {
|
|
682
|
+
try {
|
|
683
|
+
const res = await fetch('/api/memory');
|
|
684
|
+
const data = await res.json();
|
|
685
|
+
|
|
686
|
+
const semanticList = document.getElementById('semantic-memory-list');
|
|
687
|
+
const episodicList = document.getElementById('episodic-memory-list');
|
|
688
|
+
|
|
689
|
+
semanticList.innerHTML = (data.semanticItems || [])
|
|
690
|
+
.map((item) => `<div style="margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-light); padding-bottom: 0.5rem;"><b>[${item.category}]</b> ${item.text}</div>`)
|
|
691
|
+
.join('') || '<div>No semantic vectors stored yet.</div>';
|
|
692
|
+
|
|
693
|
+
episodicList.innerHTML = (data.episodes || [])
|
|
694
|
+
.map((ep) => `<div style="margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-light); padding-bottom: 0.5rem;"><b>Query:</b> ${ep.query}<br/><span style="color: var(--text-tertiary);">${ep.response.slice(0, 100)}...</span></div>`)
|
|
695
|
+
.join('') || '<div>No episodes recorded in current session.</div>';
|
|
696
|
+
} catch (err) {
|
|
697
|
+
console.error('Failed to load memory:', err);
|
|
698
|
+
}
|
|
699
|
+
}
|