@memoryblock/web 0.1.0-beta
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/LICENSE +21 -0
- package/package.json +39 -0
- package/public/app.js +205 -0
- package/public/components/archive.js +110 -0
- package/public/components/auth.js +70 -0
- package/public/components/blocks.js +444 -0
- package/public/components/chat.js +394 -0
- package/public/components/create-block.js +108 -0
- package/public/components/settings.js +363 -0
- package/public/components/setup.js +301 -0
- package/public/index.html +16 -0
- package/public/style.css +1871 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blocks component — block list and detail views.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { api, connectWs } from '../app.js';
|
|
6
|
+
import { showToast } from './create-block.js';
|
|
7
|
+
|
|
8
|
+
let activeWs = null;
|
|
9
|
+
|
|
10
|
+
export function renderBlocksList(container) {
|
|
11
|
+
if (activeWs) { activeWs.close(); activeWs = null; }
|
|
12
|
+
container.innerHTML = '<div class="loading">loading blocks...</div>';
|
|
13
|
+
|
|
14
|
+
loadBlocks(container);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function loadBlocks(container) {
|
|
18
|
+
try {
|
|
19
|
+
const data = await api('/api/blocks');
|
|
20
|
+
|
|
21
|
+
if (!data.blocks || data.blocks.length === 0) {
|
|
22
|
+
container.innerHTML = `
|
|
23
|
+
<div class="empty-state">
|
|
24
|
+
<span class="empty-icon">⬡</span>
|
|
25
|
+
<p>No blocks yet.</p>
|
|
26
|
+
<button class="action-btn primary" id="empty-create-btn">Create Your First Block</button>
|
|
27
|
+
</div>
|
|
28
|
+
`;
|
|
29
|
+
document.getElementById('empty-create-btn').addEventListener('click', () => {
|
|
30
|
+
window.location.hash = '#/create';
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
container.innerHTML = `
|
|
36
|
+
<div class="blocks-header">
|
|
37
|
+
<button class="action-btn primary" id="create-block-btn">+ Create Block</button>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="blocks-grid">
|
|
40
|
+
${data.blocks.map(block => {
|
|
41
|
+
const status = block.pulse?.status?.toLowerCase() || 'sleeping';
|
|
42
|
+
const cost = block.costs?.totalCost || 0;
|
|
43
|
+
const emoji = block.monitorEmoji || '⬡';
|
|
44
|
+
const monitorName = block.monitorName || 'default monitor';
|
|
45
|
+
const name = block.name;
|
|
46
|
+
const desc = block.description || '';
|
|
47
|
+
|
|
48
|
+
return `
|
|
49
|
+
<div class="block-card" data-block="${name}">
|
|
50
|
+
<div class="block-info">
|
|
51
|
+
<span class="block-emoji">${emoji}</span>
|
|
52
|
+
<div class="block-name">${name}</div>
|
|
53
|
+
${desc ? `<div class="block-desc">${desc}</div>` : ''}
|
|
54
|
+
<div class="block-details-tags">
|
|
55
|
+
<span class="tag"><span class="tag-icon">${emoji}</span> ${monitorName}</span>
|
|
56
|
+
<span class="tag"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tag-svg" style="margin-right:4px;"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg> ${block.adapter?.provider || 'unknown'}</span>
|
|
57
|
+
${(block.channel || 'cli').split(',').map(c => `<span class="tag"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tag-svg" style="margin-right:4px;"><path d="m3 21 1.9-5.7a8.5 8.5 0 1 1 3.8 3.8z"/></svg> ${c.trim()}</span>`).join('')}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="block-meta">
|
|
61
|
+
<div class="status-indicator"><span class="status-dot ${status}"></span>${status}</div>
|
|
62
|
+
<div style="font-size:0.75rem; opacity:0.6">${formatTokens((block.costs?.totalInput || 0) + (block.costs?.totalOutput || 0))} tokens</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
}).join('')}
|
|
67
|
+
</div>`;
|
|
68
|
+
|
|
69
|
+
// Create button
|
|
70
|
+
document.getElementById('create-block-btn').addEventListener('click', () => {
|
|
71
|
+
window.location.hash = '#/create';
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Attach click handlers
|
|
75
|
+
container.querySelectorAll('.block-card').forEach(card => {
|
|
76
|
+
card.addEventListener('click', () => {
|
|
77
|
+
const blockName = card.dataset.block;
|
|
78
|
+
window.dispatchEvent(new CustomEvent('navigate', { detail: { view: 'detail', block: blockName } }));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
} catch {
|
|
82
|
+
container.innerHTML = '<div class="loading">error loading blocks</div>';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function renderBlockDetail(container, blockName) {
|
|
87
|
+
container.innerHTML = '<div class="loading">loading...</div>';
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const data = await api(`/api/blocks/${blockName}`);
|
|
91
|
+
const config = data.config;
|
|
92
|
+
const costs = data.costs || {};
|
|
93
|
+
const pulse = data.pulse || {};
|
|
94
|
+
const memory = data.memory || '(no memory yet)';
|
|
95
|
+
const monitor = data.monitor || '(no monitor log yet)';
|
|
96
|
+
const model = config.adapter?.model?.split('.').pop()?.replace(/-v\d.*$/, '') || 'unknown';
|
|
97
|
+
|
|
98
|
+
container.innerHTML = `
|
|
99
|
+
<div class="detail-layout">
|
|
100
|
+
<div class="detail-main">
|
|
101
|
+
<div class="detail-top-nav">
|
|
102
|
+
<button class="back-btn" id="back-btn">
|
|
103
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:2px; vertical-align:text-bottom;"><path d="m15 18-6-6 6-6"/></svg>
|
|
104
|
+
back to blocks
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="detail-header-block">
|
|
109
|
+
<div class="detail-header-info">
|
|
110
|
+
<div class="detail-title">${config.name}</div>
|
|
111
|
+
${config.description ? `<div class="detail-subtitle">${config.description}</div>` : ''}
|
|
112
|
+
|
|
113
|
+
<div class="block-details-tags" style="margin-top: 14px;">
|
|
114
|
+
<span class="tag"><span class="tag-icon">${config.monitorEmoji || '⬡'}</span> ${config.monitorName || 'default monitor'}</span>
|
|
115
|
+
<span class="tag"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tag-svg" style="margin-right:4px;"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg> ${config.adapter?.provider || 'unknown'} ${model}</span>
|
|
116
|
+
${(config.channel?.type || 'cli').split(',').map(c => `<span class="tag"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tag-svg" style="margin-right:4px;"><path d="m3 21 1.9-5.7a8.5 8.5 0 1 1 3.8 3.8z"/></svg> ${c.trim()}</span>`).join('')}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="action-bar">
|
|
121
|
+
<button class="action-btn" id="chat-btn" title="Open Web Chat"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="action-svg" style="margin-right:6px;"><path d="m3 21 1.9-5.7a8.5 8.5 0 1 1 3.8 3.8z"/></svg> Chat</button>
|
|
122
|
+
<button class="action-btn primary" id="start-btn" title="Start monitor"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="action-svg" style="margin-right:6px;"><polygon points="5 3 19 12 5 21 5 3"/></svg> Start</button>
|
|
123
|
+
<button class="action-btn" id="stop-btn" title="Stop monitor"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="action-svg" style="margin-right:6px;"><rect width="18" height="18" x="3" y="3" rx="2"/></svg> Stop</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="settings-tabs">
|
|
128
|
+
<button class="settings-tab-btn active" data-tab="overview">Overview</button>
|
|
129
|
+
<button class="settings-tab-btn" data-tab="settings">Settings</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div id="tab-overview">
|
|
133
|
+
<div class="detail-section">
|
|
134
|
+
<h4>memory.md</h4>
|
|
135
|
+
<div class="memory-content" id="val-memory">${escapeHtml(memory.slice(0, 1500))}</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="detail-section">
|
|
139
|
+
<h4>monitor.md (agent logic tree)</h4>
|
|
140
|
+
<div class="memory-content" id="val-monitor">${escapeHtml(monitor.slice(0, 1500))}</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="detail-section">
|
|
144
|
+
<h4>active goals</h4>
|
|
145
|
+
<div class="memory-content" style="background:transparent; border:none; padding:0;">${(config.goals || []).map(g => '• ' + g).join('<br>') || '(none set)'}</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div id="tab-settings" style="display:none;">
|
|
150
|
+
<div class="settings-panel">
|
|
151
|
+
<h3>General Options</h3>
|
|
152
|
+
<div class="form-group" style="margin-bottom:16px;">
|
|
153
|
+
<label>Description</label>
|
|
154
|
+
<input type="text" id="cfg-desc" class="form-input" value="${escapeHtml(config.description || '')}" placeholder="What does this block do?" />
|
|
155
|
+
</div>
|
|
156
|
+
<div class="form-group" style="margin-bottom:16px;">
|
|
157
|
+
<label>System Prompt (Persona)</label>
|
|
158
|
+
<textarea id="cfg-prompt" class="form-input" style="min-height:90px; resize:vertical;" placeholder="You are a helpful assistant...">${escapeHtml(config.systemPrompt || '')}</textarea>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="settings-panel">
|
|
163
|
+
<h3>Monitor Details</h3>
|
|
164
|
+
<div class="stat-grid">
|
|
165
|
+
<div class="form-group">
|
|
166
|
+
<label>Display Name</label>
|
|
167
|
+
<input type="text" id="cfg-mon-name" class="form-input" value="${escapeHtml(config.monitorName || '')}" placeholder="e.g. Chief Editor" />
|
|
168
|
+
</div>
|
|
169
|
+
<div class="form-group">
|
|
170
|
+
<label>Monitor Emoji</label>
|
|
171
|
+
<input type="text" id="cfg-mon-emoji" class="form-input" value="${escapeHtml(config.monitorEmoji || '')}" maxlength="4" placeholder="⬡" />
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="settings-panel">
|
|
177
|
+
<h3>Model & Adapter</h3>
|
|
178
|
+
<div class="stat-grid">
|
|
179
|
+
<div class="form-group">
|
|
180
|
+
<label>Provider Runtime</label>
|
|
181
|
+
<select id="cfg-provider" class="form-input">
|
|
182
|
+
<option value="bedrock" ${config.adapter?.provider === 'bedrock' ? 'selected' : ''}>AWS Bedrock</option>
|
|
183
|
+
<option value="openai" ${config.adapter?.provider === 'openai' ? 'selected' : ''}>OpenAI</option>
|
|
184
|
+
<option value="anthropic" ${config.adapter?.provider === 'anthropic' ? 'selected' : ''}>Anthropic</option>
|
|
185
|
+
<option value="gemini" ${config.adapter?.provider === 'gemini' ? 'selected' : ''}>Google Gemini</option>
|
|
186
|
+
<option value="ollama" ${config.adapter?.provider === 'ollama' ? 'selected' : ''}>Ollama</option>
|
|
187
|
+
</select>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="form-group">
|
|
190
|
+
<label>Model String</label>
|
|
191
|
+
<input type="text" id="cfg-model" class="form-input" value="${escapeHtml(config.adapter?.model || '')}" placeholder="e.g. gpt-4o" />
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="settings-panel">
|
|
197
|
+
<h3>Performance & Memory</h3>
|
|
198
|
+
<div class="stat-grid">
|
|
199
|
+
<div class="form-group">
|
|
200
|
+
<label>Max Context Tokens</label>
|
|
201
|
+
<input type="number" id="cfg-mem-tokens" class="form-input" value="${config.memory?.maxContextTokens || 120000}" />
|
|
202
|
+
</div>
|
|
203
|
+
<div class="form-group">
|
|
204
|
+
<label>Wake Interval (seconds)</label>
|
|
205
|
+
<input type="number" id="cfg-pulse-interval" class="form-input" value="${config.pulse?.intervalSeconds || 60}" />
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="settings-panel">
|
|
211
|
+
<h3>Permissions & Security <span style="font-size:0.7rem; font-weight:normal; color:#f44;">(Requires restart)</span></h3>
|
|
212
|
+
<div class="stat-grid">
|
|
213
|
+
<div class="form-group">
|
|
214
|
+
<label>Allow Shell Execution</label>
|
|
215
|
+
<select id="cfg-perm-shell" class="form-input">
|
|
216
|
+
<option value="false" ${!config.permissions?.allowShell ? 'selected' : ''}>Disabled (Secure)</option>
|
|
217
|
+
<option value="true" ${config.permissions?.allowShell ? 'selected' : ''}>Enabled (Danger)</option>
|
|
218
|
+
</select>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="form-group">
|
|
221
|
+
<label>Allow Network Access</label>
|
|
222
|
+
<select id="cfg-perm-network" class="form-input">
|
|
223
|
+
<option value="true" ${config.permissions?.allowNetwork ? 'selected' : ''}>Enabled</option>
|
|
224
|
+
<option value="false" ${!config.permissions?.allowNetwork ? 'selected' : ''}>Disabled</option>
|
|
225
|
+
</select>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="form-group">
|
|
228
|
+
<label>Max Execution Timeout (ms)</label>
|
|
229
|
+
<input type="number" id="cfg-perm-timeout" class="form-input" value="${config.permissions?.maxTimeout || 120000}" />
|
|
230
|
+
</div>
|
|
231
|
+
<div class="form-group">
|
|
232
|
+
<label>Plugin Sandbox (Isolated)</label>
|
|
233
|
+
<select id="cfg-tool-sandbox" class="form-input">
|
|
234
|
+
<option value="true" ${config.tools?.sandbox ? 'selected' : ''}>Enabled</option>
|
|
235
|
+
<option value="false" ${!config.tools?.sandbox ? 'selected' : ''}>Disabled</option>
|
|
236
|
+
</select>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="form-actions" style="margin-top: 16px; display: flex; justify-content: space-between; align-items: center;">
|
|
242
|
+
<div style="display: flex; gap: 8px;">
|
|
243
|
+
<button class="action-btn" id="reset-btn" title="Reset state"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="action-svg" style="margin-right:6px;"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg> Reset</button>
|
|
244
|
+
<button class="action-btn danger" id="archive-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="action-svg" style="margin-right:6px;"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg> Archive</button>
|
|
245
|
+
</div>
|
|
246
|
+
<button id="save-config-btn" class="action-btn primary">Save Config Changes</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="detail-sidebar">
|
|
253
|
+
<div class="detail-section" style="margin-bottom:0; height: 100%;">
|
|
254
|
+
<h4>costs & tokens</h4>
|
|
255
|
+
<div class="stat-grid" style="grid-template-columns: 1fr;">
|
|
256
|
+
<div class="stat">
|
|
257
|
+
<div class="stat-value" id="val-status" style="font-size:1rem; color:var(--text);">${pulse.status || 'SLEEPING'}</div>
|
|
258
|
+
<div class="stat-label">status</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="stat">
|
|
261
|
+
<div class="stat-value" id="val-in">${formatTokens(costs.totalInput || 0)}</div>
|
|
262
|
+
<div class="stat-label">input tokens</div>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="stat">
|
|
265
|
+
<div class="stat-value" id="val-out">${formatTokens(costs.totalOutput || 0)}</div>
|
|
266
|
+
<div class="stat-label">output tokens</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
// Tab switching
|
|
275
|
+
const tabs = container.querySelectorAll('.settings-tab-btn');
|
|
276
|
+
tabs.forEach(tab => {
|
|
277
|
+
tab.addEventListener('click', () => {
|
|
278
|
+
tabs.forEach(t => t.classList.remove('active'));
|
|
279
|
+
tab.classList.add('active');
|
|
280
|
+
if (tab.dataset.tab === 'overview') {
|
|
281
|
+
container.querySelector('#tab-overview').style.display = 'block';
|
|
282
|
+
container.querySelector('#tab-settings').style.display = 'none';
|
|
283
|
+
} else {
|
|
284
|
+
container.querySelector('#tab-overview').style.display = 'none';
|
|
285
|
+
container.querySelector('#tab-settings').style.display = 'block';
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Connect WebSocket for live updates
|
|
291
|
+
if (activeWs) activeWs.close();
|
|
292
|
+
activeWs = connectWs(blockName, async () => {
|
|
293
|
+
try {
|
|
294
|
+
const live = await api(`/api/blocks/${blockName}`);
|
|
295
|
+
document.getElementById('val-in').textContent = formatTokens(live.costs?.totalInput || 0);
|
|
296
|
+
document.getElementById('val-out').textContent = formatTokens(live.costs?.totalOutput || 0);
|
|
297
|
+
document.getElementById('val-status').textContent = live.pulse?.status || 'SLEEPING';
|
|
298
|
+
if (document.getElementById('val-memory')) document.getElementById('val-memory').innerHTML = escapeHtml(live.memory?.slice(0, 1500) || '');
|
|
299
|
+
if (document.getElementById('val-monitor')) document.getElementById('val-monitor').innerHTML = escapeHtml(live.monitor?.slice(0, 1500) || '');
|
|
300
|
+
} catch {}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
document.getElementById('back-btn').addEventListener('click', () => {
|
|
304
|
+
window.dispatchEvent(new CustomEvent('navigate', { detail: { view: 'blocks' } }));
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
document.getElementById('archive-btn').addEventListener('click', async () => {
|
|
308
|
+
if (confirm(`Archive ${blockName}? It will be stored safely but removed from active rotation.`)) {
|
|
309
|
+
document.getElementById('archive-btn').textContent = 'archiving...';
|
|
310
|
+
document.getElementById('archive-btn').disabled = true;
|
|
311
|
+
try {
|
|
312
|
+
await api(`/api/blocks/${blockName}`, { method: 'DELETE' });
|
|
313
|
+
showToast(`${blockName} archived.`);
|
|
314
|
+
window.dispatchEvent(new CustomEvent('navigate', { detail: { view: 'blocks' } }));
|
|
315
|
+
} catch (e) {
|
|
316
|
+
showToast('Failed to archive: ' + e.message);
|
|
317
|
+
document.getElementById('archive-btn').textContent = 'Archive';
|
|
318
|
+
document.getElementById('archive-btn').disabled = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Chat View Open
|
|
324
|
+
document.getElementById('chat-btn').addEventListener('click', () => {
|
|
325
|
+
window.dispatchEvent(new CustomEvent('navigate', { detail: { view: 'chat', block: blockName } }));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Start button
|
|
329
|
+
document.getElementById('start-btn').addEventListener('click', async () => {
|
|
330
|
+
const btn = document.getElementById('start-btn');
|
|
331
|
+
btn.disabled = true;
|
|
332
|
+
btn.textContent = 'Starting...';
|
|
333
|
+
try {
|
|
334
|
+
await api(`/api/blocks/${blockName}/start`, { method: 'POST' });
|
|
335
|
+
showToast(`${blockName} started.`);
|
|
336
|
+
btn.textContent = '▶ Start';
|
|
337
|
+
btn.disabled = false;
|
|
338
|
+
} catch (e) {
|
|
339
|
+
showToast('Start failed: ' + e.message);
|
|
340
|
+
btn.textContent = '▶ Start';
|
|
341
|
+
btn.disabled = false;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Stop button
|
|
346
|
+
document.getElementById('stop-btn').addEventListener('click', async () => {
|
|
347
|
+
const btn = document.getElementById('stop-btn');
|
|
348
|
+
btn.disabled = true;
|
|
349
|
+
btn.textContent = 'Stopping...';
|
|
350
|
+
try {
|
|
351
|
+
await api(`/api/blocks/${blockName}/stop`, { method: 'POST' });
|
|
352
|
+
showToast(`${blockName} stopped.`);
|
|
353
|
+
btn.textContent = '⏹ Stop';
|
|
354
|
+
btn.disabled = false;
|
|
355
|
+
} catch (e) {
|
|
356
|
+
showToast('Stop failed: ' + e.message);
|
|
357
|
+
btn.textContent = '⏹ Stop';
|
|
358
|
+
btn.disabled = false;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Reset button
|
|
363
|
+
document.getElementById('reset-btn').addEventListener('click', async () => {
|
|
364
|
+
if (!confirm(`Reset ${blockName}? This clears memory, costs, and session data.`)) return;
|
|
365
|
+
const btn = document.getElementById('reset-btn');
|
|
366
|
+
btn.disabled = true;
|
|
367
|
+
btn.textContent = 'Resetting...';
|
|
368
|
+
try {
|
|
369
|
+
await api(`/api/blocks/${blockName}/reset`, { method: 'POST' });
|
|
370
|
+
showToast(`${blockName} reset.`);
|
|
371
|
+
btn.textContent = '↺ Reset';
|
|
372
|
+
btn.disabled = false;
|
|
373
|
+
// Refresh the detail view
|
|
374
|
+
renderBlockDetail(container, blockName);
|
|
375
|
+
} catch (e) {
|
|
376
|
+
showToast('Reset failed: ' + e.message);
|
|
377
|
+
btn.textContent = '↺ Reset';
|
|
378
|
+
btn.disabled = false;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
// Save Settings button
|
|
382
|
+
document.getElementById('save-config-btn').addEventListener('click', async () => {
|
|
383
|
+
const btn = document.getElementById('save-config-btn');
|
|
384
|
+
btn.disabled = true;
|
|
385
|
+
btn.textContent = 'Saving...';
|
|
386
|
+
|
|
387
|
+
const updates = {
|
|
388
|
+
description: document.getElementById('cfg-desc').value,
|
|
389
|
+
systemPrompt: document.getElementById('cfg-prompt').value,
|
|
390
|
+
monitorName: document.getElementById('cfg-mon-name').value,
|
|
391
|
+
monitorEmoji: document.getElementById('cfg-mon-emoji').value,
|
|
392
|
+
adapter: {
|
|
393
|
+
...(config.adapter || {}),
|
|
394
|
+
provider: document.getElementById('cfg-provider').value,
|
|
395
|
+
model: document.getElementById('cfg-model').value
|
|
396
|
+
},
|
|
397
|
+
memory: {
|
|
398
|
+
...(config.memory || {}),
|
|
399
|
+
maxContextTokens: parseInt(document.getElementById('cfg-mem-tokens').value) || 120000
|
|
400
|
+
},
|
|
401
|
+
pulse: {
|
|
402
|
+
...(config.pulse || {}),
|
|
403
|
+
intervalSeconds: parseInt(document.getElementById('cfg-pulse-interval').value) || 60
|
|
404
|
+
},
|
|
405
|
+
permissions: {
|
|
406
|
+
...(config.permissions || {}),
|
|
407
|
+
allowShell: document.getElementById('cfg-perm-shell').value === 'true',
|
|
408
|
+
allowNetwork: document.getElementById('cfg-perm-network').value === 'true',
|
|
409
|
+
maxTimeout: parseInt(document.getElementById('cfg-perm-timeout').value) || 120000
|
|
410
|
+
},
|
|
411
|
+
tools: {
|
|
412
|
+
...(config.tools || {}),
|
|
413
|
+
sandbox: document.getElementById('cfg-tool-sandbox').value === 'true'
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
await api(`/api/blocks/${blockName}/config`, {
|
|
419
|
+
method: 'PUT',
|
|
420
|
+
body: JSON.stringify(updates)
|
|
421
|
+
});
|
|
422
|
+
showToast('Settings saved successfully.');
|
|
423
|
+
renderBlockDetail(container, blockName);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
showToast('Save failed: ' + err.message);
|
|
426
|
+
btn.textContent = 'Save Config Changes';
|
|
427
|
+
btn.disabled = false;
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
} catch {
|
|
432
|
+
container.innerHTML = '<div class="loading">error loading block details</div>';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function formatTokens(n) {
|
|
437
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
|
|
438
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';
|
|
439
|
+
return n.toString();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function escapeHtml(str) {
|
|
443
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
444
|
+
}
|