anentrypoint-design 0.0.102 → 0.0.103

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.
Files changed (78) hide show
  1. package/app-shell.css +464 -2005
  2. package/colors_and_type.css +140 -626
  3. package/dist/247420.css +578 -2749
  4. package/dist/247420.js +23 -62
  5. package/package.json +22 -23
  6. package/src/bootstrap.js +5 -18
  7. package/src/components/chat.js +15 -15
  8. package/src/components/community.js +9 -7
  9. package/src/components/content.js +71 -54
  10. package/src/components/files-modals.js +4 -2
  11. package/src/components/files.js +6 -14
  12. package/src/components/freddie/helpers.js +38 -77
  13. package/src/components/freddie.js +32 -16
  14. package/src/components/shell.js +22 -15
  15. package/src/components.js +7 -3
  16. package/src/debug.js +21 -30
  17. package/src/deck-stage.js +10 -6
  18. package/src/highlight.js +25 -11
  19. package/src/index.js +29 -5
  20. package/src/{desktop → kits/os}/freddie/helpers.js +1 -1
  21. package/src/{desktop → kits/os}/freddie/pages-chat.js +2 -2
  22. package/src/{desktop → kits/os}/freddie/pages-core.js +2 -2
  23. package/src/{desktop → kits/os}/freddie/pages-os.js +1 -1
  24. package/src/{desktop → kits/os}/freddie/pages-tools.js +2 -2
  25. package/src/{desktop → kits/os}/freddie-dashboard.js +2 -2
  26. package/src/markdown.js +27 -49
  27. package/src/motion.js +30 -17
  28. package/src/page-html.js +18 -116
  29. package/src/styles.js +14 -22
  30. package/src/web-components/ds-chat.js +24 -56
  31. package/dist/247420.app.js +0 -5
  32. package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTT0zRp8A.ttf +0 -0
  33. package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTT6jRp8A.ttf +0 -0
  34. package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTBjNp8A.ttf +0 -0
  35. package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTNDNp8A.ttf +0 -0
  36. package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTnTRp8A.ttf +0 -0
  37. package/dist/fonts/archivo_v25_k3k6o8UDI-1M0wlSV9XAw6lQkqWY8Q82sJaRE-NWIDdgffTTtDRp8A.ttf +0 -0
  38. package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhv8laKpA.ttf +0 -0
  39. package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhvHlGKpA.ttf +0 -0
  40. package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhvLFGKpA.ttf +0 -0
  41. package/dist/fonts/archivonarrow_v35_tss5ApVBdCYD5Q7hcxTE1ArZ0Zz8oY2KRmwvKhhvy1aKpA.ttf +0 -0
  42. package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8-qxjPQ.ttf +0 -0
  43. package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8FqtjPQ.ttf +0 -0
  44. package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8L6tjPQ.ttf +0 -0
  45. package/dist/fonts/jetbrainsmono_v24_tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxjPQ.ttf +0 -0
  46. package/dist/fonts/jetbrainsmono_v24_tDba2o-flEEny0FZhsfKu5WU4xD-IQ-PuZJJXxfpAO-LflOQ.ttf +0 -0
  47. package/dist/fonts/jetbrainsmono_v24_tDba2o-flEEny0FZhsfKu5WU4xD-IQ-PuZJJXxfpAO9seVOQ.ttf +0 -0
  48. package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj42Vksj.ttf +0 -0
  49. package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj4PVksj.ttf +0 -0
  50. package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj7aUUsj.ttf +0 -0
  51. package/dist/fonts/spacegrotesk_v22_V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj7oUUsj.ttf +0 -0
  52. package/src/app.js +0 -156
  53. package/src/components/freddie/pages-chains.js +0 -168
  54. package/src/components/freddie/pages-chat.js +0 -122
  55. package/src/components/freddie/pages-config-edit.js +0 -174
  56. package/src/components/freddie/pages-config.js +0 -193
  57. package/src/components/freddie/pages-core.js +0 -170
  58. package/src/components/freddie/pages-runtime.js +0 -69
  59. package/src/components/freddie/pages-voice.js +0 -109
  60. package/src/web-components/freddie-chat.js +0 -34
  61. /package/src/{desktop → kits/os}/about-app.js +0 -0
  62. /package/src/{desktop → kits/os}/app-panes.css +0 -0
  63. /package/src/{desktop → kits/os}/browser-app.js +0 -0
  64. /package/src/{desktop → kits/os}/files-app.js +0 -0
  65. /package/src/{desktop → kits/os}/freddie/routes.js +0 -0
  66. /package/src/{desktop → kits/os}/freddie-dashboard.css +0 -0
  67. /package/src/{desktop → kits/os}/icons.js +0 -0
  68. /package/src/{desktop → kits/os}/index.js +0 -0
  69. /package/src/{desktop → kits/os}/launcher.css +0 -0
  70. /package/src/{desktop → kits/os}/launcher.js +0 -0
  71. /package/src/{desktop → kits/os}/monitor-app.js +0 -0
  72. /package/src/{desktop → kits/os}/shell.js +0 -0
  73. /package/src/{desktop → kits/os}/terminal-app.js +0 -0
  74. /package/src/{desktop → kits/os}/theme.css +0 -0
  75. /package/src/{desktop → kits/os}/validate.css +0 -0
  76. /package/src/{desktop → kits/os}/validator-app.js +0 -0
  77. /package/src/{desktop → kits/os}/wm.css +0 -0
  78. /package/src/{desktop → kits/os}/wm.js +0 -0
@@ -1,174 +0,0 @@
1
- import * as webjsx from '../../../vendor/webjsx/index.js';
2
- import { Panel, Hero, Kpi, Form, Table } from '../content.js';
3
- import { Chip } from '../shell.js';
4
- import { renderConfigSections } from './helpers.js';
5
- const h = webjsx.createElement;
6
-
7
- const KNOWN_FIELDS = [
8
- { key: 'display.skin', label: 'skin', kind: 'skin' },
9
- { key: 'display.tool_progress_command', label: 'tool progress command', kind: 'bool' },
10
- { key: 'display.background_process_notifications', label: 'bg notifications', kind: 'enum', options: ['all','errors','none'] },
11
- { key: 'providers.freddie.baseUrl', label: 'freddie URL (thebird → freddie)', kind: 'string', nullable: true },
12
- { key: 'providers.openai.baseUrl', label: 'acptoapi URL (freddie → acptoapi)', kind: 'string', nullable: true },
13
- { key: 'providers.openai.model', label: 'default LLM model', kind: 'string', nullable: true },
14
- { key: 'agent.provider', label: 'agent provider', kind: 'string' },
15
- { key: 'agent.model', label: 'agent model', kind: 'modelDropdown' },
16
- { key: 'agent.max_iterations', label: 'max iterations', kind: 'number' },
17
- { key: 'agent.fallback_model', label: 'fallback model', kind: 'string', nullable: true },
18
- { key: 'agent.save_trajectories', label: 'save trajectories', kind: 'bool' },
19
- { key: 'agent.model_preference', label: 'model preference (json array)', kind: 'json' },
20
- { key: 'memory.provider', label: 'memory provider', kind: 'string', nullable: true },
21
- { key: 'gateway.timeout', label: 'gateway timeout (s)', kind: 'number' },
22
- { key: 'terminal.cwd', label: 'terminal cwd', kind: 'string', nullable: true },
23
- ];
24
-
25
- function getDot(o, p) { return p.split('.').reduce((c, k) => (c && k in c) ? c[k] : undefined, o); }
26
-
27
- function registerPicker(key, modelCount) {
28
- const reg = window.__fd_modelPickers = window.__fd_modelPickers || {};
29
- reg[key] = { key, modelCount, mountedAt: Date.now() };
30
- if (typeof window !== 'undefined') {
31
- window.__debug = window.__debug || {};
32
- window.__debug.modelPickers = () => Object.values(window.__fd_modelPickers || {});
33
- }
34
- }
35
-
36
- function commit(h0, key, value) {
37
- const prev = window.__fd_cfg_status = window.__fd_cfg_status || {};
38
- prev[key] = 'saving…';
39
- Promise.resolve(h0.pi.config.saveValue(key, value))
40
- .then(() => { prev[key] = 'saved ✓'; rerender(); setTimeout(() => { delete prev[key]; rerender(); }, 1500); })
41
- .catch(e => { prev[key] = 'err: ' + (e.message || e); rerender(); });
42
- }
43
-
44
- function rerender() { if (typeof window.__fd_nav === 'function') window.__fd_nav('config'); }
45
-
46
- function editor(field, cur, defVal, h0, skins, v1Models, sampler) {
47
- const status = (window.__fd_cfg_status || {})[field.key];
48
- let control;
49
- if (field.kind === 'modelDropdown') {
50
- const models = Array.isArray(v1Models) ? v1Models : [];
51
- const grouped = models.reduce((a, m) => { const idx = String(m.id || '').indexOf('/'); const g = idx > 0 ? m.id.slice(0, idx) : (m.owned_by || 'other'); (a[g] = a[g] || []).push(m); return a; }, {});
52
- const samplerStatus = sampler && sampler.status ? sampler.status : {};
53
- const isUnavailable = id => { const s = samplerStatus[id]; return s && s.available === false; };
54
- control = h('select', { onchange: ev => commit(h0, field.key, ev.target.value === '' && field.nullable ? null : ev.target.value) },
55
- h('option', { value: '', selected: !cur ? 'true' : null }, '— auto —'),
56
- ...Object.entries(grouped).map(([g, ms]) => h('optgroup', { label: g + ' · ' + ms.length },
57
- ...ms.map(m => h('option', { value: m.id, selected: cur === m.id ? 'true' : null, disabled: isUnavailable(m.id) ? 'true' : null }, m.id + (isUnavailable(m.id) ? ' ✕' : ''))))));
58
- registerPicker(field.key, models.length);
59
- } else if (field.kind === 'skin') {
60
- const list = skins.length ? skins : ['default'];
61
- control = h('select', { onchange: ev => commit(h0, field.key, ev.target.value) },
62
- ...list.map(s => h('option', { value: s, selected: s === cur ? 'true' : null }, s)));
63
- } else if (field.kind === 'bool') {
64
- control = h('select', { onchange: ev => commit(h0, field.key, ev.target.value === 'true') },
65
- h('option', { value: 'false', selected: !cur ? 'true' : null }, 'false'),
66
- h('option', { value: 'true', selected: cur ? 'true' : null }, 'true'));
67
- } else if (field.kind === 'enum') {
68
- control = h('select', { onchange: ev => commit(h0, field.key, ev.target.value) },
69
- ...field.options.map(o => h('option', { value: o, selected: o === cur ? 'true' : null }, o)));
70
- } else if (field.kind === 'number') {
71
- control = h('input', { type: 'number', value: cur == null ? '' : String(cur),
72
- onchange: ev => commit(h0, field.key, ev.target.value === '' ? null : Number(ev.target.value)) });
73
- } else if (field.kind === 'json') {
74
- const text = cur == null ? '[]' : JSON.stringify(cur);
75
- control = h('input', { type: 'text', value: text, placeholder: '[] or [{"provider":"…"}]',
76
- onchange: ev => { try { commit(h0, field.key, JSON.parse(ev.target.value || 'null')); } catch (e) { (window.__fd_cfg_status = window.__fd_cfg_status || {})[field.key] = 'invalid json'; rerender(); } } });
77
- } else {
78
- control = h('input', { type: 'text', value: cur == null ? '' : String(cur), placeholder: field.nullable ? '(empty = unset)' : '',
79
- onchange: ev => commit(h0, field.key, ev.target.value === '' && field.nullable ? null : ev.target.value) });
80
- }
81
- const isDefault = JSON.stringify(cur) === JSON.stringify(defVal);
82
- const meta = h('span', { class: 'fd-cfg-meta' },
83
- status ? Chip({ tone: status.startsWith('err') || status === 'invalid json' ? 'miss' : status === 'saved ✓' ? 'ok' : 'warn', children: status }) : null,
84
- !isDefault && defVal !== undefined ? h('span', { class: 'fd-muted', title: 'default: ' + JSON.stringify(defVal) }, ' default: ' + (defVal === '' ? '""' : JSON.stringify(defVal))) : null);
85
- return h('div', { class: 'fd-cfg-row', key: field.key },
86
- h('label', { class: 'fd-cfg-label' }, field.label, h('code', { class: 'fd-cfg-key' }, field.key)),
87
- h('div', { class: 'fd-cfg-control' }, control, meta));
88
- }
89
-
90
- function prefRow(h0, idx, pref, providers, cached, total) {
91
- const provider = pref.provider || '';
92
- const model = pref.model || '';
93
- const models = (cached[provider] && cached[provider].models) || [];
94
- const onProv = ev => { const list = getPrefList(); list[idx] = { ...list[idx], provider: ev.target.value, model: '' }; commit(h0, 'agent.model_preference', list); };
95
- const onModel = ev => { const list = getPrefList(); list[idx] = { ...list[idx], model: ev.target.value }; commit(h0, 'agent.model_preference', list); };
96
- const onMove = dir => { const list = getPrefList(); const j = idx + dir; if (j < 0 || j >= list.length) return; const t = list[idx]; list[idx] = list[j]; list[j] = t; commit(h0, 'agent.model_preference', list); };
97
- const onDel = () => { const list = getPrefList(); list.splice(idx, 1); commit(h0, 'agent.model_preference', list); };
98
- return h('div', { class: 'fd-cfg-row', key: 'pref-' + idx, 'data-idx': idx },
99
- h('label', { class: 'fd-cfg-label' }, '#' + (idx+1) + ' / ' + total),
100
- h('div', { class: 'fd-cfg-control' },
101
- h('select', { onchange: onProv },
102
- h('option', { value: '' }, '(select provider)'),
103
- ...providers.map(p => h('option', { value: p, selected: p === provider ? 'true' : null }, p))),
104
- models.length > 0
105
- ? h('select', { onchange: onModel },
106
- h('option', { value: '' }, '(any/default)'),
107
- ...models.map(m => h('option', { value: m, selected: m === model ? 'true' : null }, m)))
108
- : h('input', { type: 'text', placeholder: 'model (run discover for dropdown)', value: model, onchange: onModel }),
109
- h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); onMove(-1); } }, '↑'),
110
- h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); onMove(1); } }, '↓'),
111
- h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); onDel(); } }, '✕')));
112
- }
113
-
114
- function getPrefList() {
115
- return (window.__fd_cfg_pref || []).slice();
116
- }
117
-
118
- function modelPrefPanel(h0, cfg, providers, cached) {
119
- const list = Array.isArray(cfg?.agent?.model_preference) ? cfg.agent.model_preference : [];
120
- window.__fd_cfg_pref = list.slice();
121
- const rows = list.map((p, i) => prefRow(h0, i, p, providers, cached, list.length));
122
- const addBtn = h('button', { class: 'btn-primary', onclick: ev => { ev.preventDefault(); const next = getPrefList(); next.push({ provider: '', model: '' }); commit(h0, 'agent.model_preference', next); } }, '+ add row');
123
- const discoverBtn = h('button', { class: 'btn', onclick: async ev => {
124
- ev.preventDefault();
125
- (window.__fd_cfg_status = window.__fd_cfg_status || {})['agent.discovered_models'] = 'discovering…'; rerender();
126
- try { await fetch('/api/models/discover', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }); window.__fd_cfg_status['agent.discovered_models'] = 'discovered ✓'; }
127
- catch (e) { window.__fd_cfg_status['agent.discovered_models'] = 'err: ' + (e.message || e); }
128
- rerender();
129
- } }, 'discover models');
130
- const status = (window.__fd_cfg_status || {})['agent.discovered_models'];
131
- return Panel({ title: 'model preference (ordered fallback chain)', count: list.length,
132
- right: h('span', {}, discoverBtn, ' ', addBtn, status ? Chip({ tone: status.startsWith('err') ? 'miss' : status === 'discovered ✓' ? 'ok' : 'warn', children: status }) : null),
133
- children: list.length === 0
134
- ? h('div', { class: 'fd-muted' }, 'no preferences — first available provider key will be used. add a row to override.')
135
- : h('div', { class: 'fd-cfg-prefs' }, ...rows) });
136
- }
137
-
138
- export async function config(h0) {
139
- const cfg = typeof h0.pi.config?.load === 'function' ? await h0.pi.config.load() : {};
140
- const defaults = await fetch('/api/config/defaults').then(r => r.json()).catch(() => ({}));
141
- const skins = await fetch('/api/skins').then(r => r.json()).catch(() => ['default']);
142
- const provInfo = await fetch('/api/models/providers').then(r => r.json()).catch(() => ({ providers: [] }));
143
- const cached = await fetch('/api/models/cached').then(r => r.json()).catch(() => ({}));
144
- const v1Models = await fetch('/v1/models').then(r => r.json()).then(j => j.data || []).catch(() => []);
145
- const sampler = await fetch('/api/models/sampler').then(r => r.json()).catch(() => ({ status: {} }));
146
- const commands = typeof h0.pi.cli?.values === 'function' ? [...h0.pi.cli.values()] : [];
147
- const settingsPanel = Panel({ title: 'settings', count: KNOWN_FIELDS.length,
148
- children: KNOWN_FIELDS.filter(f => f.key !== 'agent.model_preference').map(f => editor(f, getDot(cfg, f.key), getDot(defaults, f.key), h0, skins, v1Models, sampler)) });
149
- const prefPanel = modelPrefPanel(h0, cfg, provInfo.providers || [], cached || {});
150
- const rawForm = Panel({ title: 'set arbitrary key (power-user)', children: Form({ fields: [
151
- { name: 'key', placeholder: 'dotted.key', required: true },
152
- { name: 'value', placeholder: 'value (json or string)', required: true }
153
- ], submit: 'save', onSubmit: async ev => {
154
- let v = ev.target.elements.value.value;
155
- try { v = JSON.parse(v); } catch {}
156
- await h0.pi.config.saveValue(ev.target.elements.key.value, v);
157
- rerender();
158
- } }) });
159
- const allRaw = h('details', { class: 'fd-cfg-details' },
160
- h('summary', {}, 'all config (read-only)'),
161
- ...renderConfigSections(cfg));
162
- const cmds = h('details', { class: 'fd-cfg-details' },
163
- h('summary', {}, 'commands · ' + commands.length),
164
- Table({ headers: ['name','description'], rows: commands.map(c => [c.name, c.description||'']) }));
165
- return [
166
- Hero({ title: 'config', body: 'inline editors for known keys. raw setter is the fallback.', accent: 'v'+(cfg._config_version||0) }),
167
- Kpi({ items: [[KNOWN_FIELDS.length,'editable'],[(cfg?.agent?.model_preference||[]).length,'pref-rows'],[commands.length,'commands'],[cfg._config_version||0,'version']] }),
168
- settingsPanel,
169
- prefPanel,
170
- rawForm,
171
- allRaw,
172
- cmds,
173
- ];
174
- }
@@ -1,193 +0,0 @@
1
- import * as webjsx from '../../../vendor/webjsx/index.js';
2
- import { Panel, Hero, Receipt, Kpi, Table, Form } from '../content.js';
3
- import { Chip } from '../shell.js';
4
- import { EmptyState } from '../files.js';
5
- import { skillLabel } from './helpers.js';
6
- const h = webjsx.createElement;
7
-
8
- export async function models(h0) {
9
- const cfg = typeof h0.pi.config?.load === 'function' ? await h0.pi.config.load() : {};
10
- const providers = await fetch('/api/providers').then(r => r.json()).catch(() => []);
11
- const configured = providers.filter(p => p.configured);
12
- const queues = await fetch('/api/models/queues').then(r => r.json()).catch(() => ({}));
13
- const sampler = await fetch('/api/models/sampler').then(r => r.json()).catch(() => ({ status: {} }));
14
- const v1Models = await fetch('/v1/models').then(r => r.json()).then(j => j.data || []).catch(() => []);
15
- const pref = Array.isArray(cfg.agent?.model_preference) ? cfg.agent.model_preference.slice() : [];
16
- window.__debug = window.__debug || {};
17
- window.__debug.models = () => ({ providers, queues, ranking: pref, sampler: sampler.status || {}, v1Models: v1Models.length });
18
- const samplerStatus = sampler.status || {};
19
- const samplerEntries = Object.entries(samplerStatus);
20
- const samplerOk = samplerEntries.filter(([, s]) => s && s.available !== false).length;
21
- const samplerBad = samplerEntries.length - samplerOk;
22
- const saveAndNav = async (next) => { await h0.pi.config.saveValue('agent.model_preference', next); if (typeof window.__fd_nav === 'function') window.__fd_nav('models'); };
23
- const movePref = (idx, dir) => { const j = idx + dir; if (j < 0 || j >= pref.length) return; const t = pref[idx]; pref[idx] = pref[j]; pref[j] = t; saveAndNav(pref); };
24
- const delPref = idx => { pref.splice(idx, 1); saveAndNav(pref); };
25
- const onDragStart = (idx, ev) => { ev.dataTransfer.setData('text/plain', String(idx)); ev.dataTransfer.effectAllowed = 'move'; };
26
- const onDragOver = ev => { ev.preventDefault(); ev.dataTransfer.dropEffect = 'move'; };
27
- const onDrop = (target, ev) => { ev.preventDefault(); const src = Number(ev.dataTransfer.getData('text/plain')); if (!Number.isFinite(src) || src === target) return; const item = pref.splice(src, 1)[0]; pref.splice(target, 0, item); saveAndNav(pref); };
28
- const probeState = window.__fd_probeState = window.__fd_probeState || {};
29
- async function probeAll() {
30
- await Promise.allSettled(configured.map(async p => {
31
- probeState[p.name] = 'loading';
32
- try { const r = await fetch('/api/providers/'+p.name+'/probe', { method: 'POST' }).then(x => x.json()); probeState[p.name] = r.models || r.error || '?'; }
33
- catch (e) { probeState[p.name] = 'error: '+e.message; }
34
- }));
35
- if (typeof window.__fd_nav === 'function') window.__fd_nav('models');
36
- }
37
- const probedPanels = [];
38
- const unprobedRows = [];
39
- for (const p of configured) {
40
- const ms = Array.isArray(probeState[p.name]) ? probeState[p.name] : p.models;
41
- const loading = probeState[p.name] === 'loading';
42
- if (loading) probedPanels.push(Panel({ title: p.name + ' ⏳', children: h('span', { class: 'fd-muted' }, 'probing…') }));
43
- else if (ms && ms.length > 0) probedPanels.push(Panel({ title: p.name + (p.available ? ' ●' : ' ○'), count: ms.length, children: h('div', { class: 'fd-list fd-list-compact' }, ...ms.map((m, i) => h('div', { key: m, class: 'fd-list-row', 'data-cat': 'preview' },
44
- h('span', { class: 'fd-list-code' }, String(i+1).padStart(2,'0')),
45
- h('div', { class: 'fd-list-main' }, h('div', { class: 'fd-list-title fd-mono' }, m))
46
- ))) }));
47
- else unprobedRows.push([p.name, p.available ? 'available' : 'unavailable', p.modelsError ? 'error: '+p.modelsError : '—']);
48
- }
49
- const modelPanels = [
50
- ...probedPanels,
51
- unprobedRows.length ? Panel({ title: 'unprobed providers', count: unprobedRows.length, children: Table({ headers: ['provider','status','note'], rows: unprobedRows }) }) : null
52
- ].filter(Boolean);
53
- const samplerTile = Panel({ title: 'sampler status', count: samplerEntries.length,
54
- right: h('span', {}, Chip({ tone: 'ok', children: samplerOk + ' ok' }), ' ', Chip({ tone: samplerBad > 0 ? 'miss' : 'neutral', children: samplerBad + ' down' })),
55
- children: samplerEntries.length === 0
56
- ? h('span', { class: 'fd-muted' }, 'no sampler entries yet — probe a provider to populate')
57
- : h('div', { class: 'fd-list fd-list-compact' }, ...samplerEntries.map(([k, s]) => h('div', { key: k, class: 'fd-list-row', 'data-cat': s.available === false ? 'external' : 'kit' },
58
- h('span', { class: 'fd-list-code' }, s.available === false ? '✕' : '●'),
59
- h('div', { class: 'fd-list-main' }, h('div', { class: 'fd-list-title fd-mono' }, k), h('div', { class: 'fd-list-sub' }, JSON.stringify(s).slice(0, 120)))))) });
60
- const addFromPicker = ev => { const v = ev.target.value; if (!v) return; const slash = v.indexOf('/'); const next = pref.slice(); next.push(slash > 0 ? { provider: v.slice(0, slash), model: v.slice(slash+1) } : { provider: v, model: '' }); saveAndNav(next); };
61
- const prefTile = Panel({ title: 'model preference (drag to reorder · pick from /v1/models including queue/*)', count: pref.length,
62
- right: h('span', {}, h('select', { class: 'fd-search', onchange: addFromPicker }, h('option', { value: '' }, '+ add from /v1/models'), ...v1Models.map(m => h('option', { key: m.id, value: m.id }, m.id))), ' ', h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); pref.push({ provider: '', model: '' }); saveAndNav(pref); } }, '+ blank')),
63
- children: pref.length === 0
64
- ? h('span', { class: 'fd-muted' }, 'no preference — pick from the dropdown above to build a priority chain')
65
- : h('div', { class: 'fd-list' }, ...pref.map((p, i) => h('div', {
66
- key: 'p'+i, class: 'fd-list-row', 'data-cat': 'kit', draggable: 'true',
67
- ondragstart: ev => onDragStart(i, ev), ondragover: onDragOver, ondrop: ev => onDrop(i, ev), style: 'cursor:move'
68
- },
69
- h('span', { class: 'fd-list-code' }, '⋮⋮'),
70
- h('div', { class: 'fd-list-main' }, h('div', { class: 'fd-list-title fd-mono' }, '#' + (i+1) + ' ' + (p.provider || '?') + (p.model ? ' / ' + p.model : '')),
71
- h('div', { class: 'fd-list-sub' }, 'drag, or use arrows')),
72
- h('div', { class: 'fd-list-meta' },
73
- h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); movePref(i, -1); } }, '↑'),
74
- h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); movePref(i, 1); } }, '↓'),
75
- h('button', { class: 'btn', onclick: ev => { ev.preventDefault(); delPref(i); } }, '✕')))))
76
- });
77
- return [
78
- Hero({ title: 'models', body: 'pick a provider, pick a model. probe to list available models.', accent: configured.length+' configured · ' + v1Models.length + ' models' }),
79
- Kpi({ items: [[configured.length,'configured'],[probedPanels.length,'probed'],[providers.length-configured.length,'unconfigured'],[v1Models.length,'/v1/models']] }),
80
- samplerTile,
81
- prefTile,
82
- Panel({ title: 'change active model', children: Form({ fields: [
83
- { name: 'provider', placeholder: 'provider', value: cfg.agent?.provider || '' },
84
- { name: 'model', placeholder: 'model id', value: cfg.agent?.model || '' }
85
- ], submit: 'update', onSubmit: async ev => {
86
- await h0.pi.config.saveValue('agent.provider', ev.target.elements.provider.value);
87
- await h0.pi.config.saveValue('agent.model', ev.target.elements.model.value);
88
- } }) }),
89
- Panel({ title: 'providers', right: h('button', { class: 'btn-primary', onclick: ev => { ev.preventDefault(); probeAll(); } }, 'probe all'),
90
- children: h('div', { class: 'fd-chips' }, ...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.name + (p.configured ? (p.available ? ' ●' : ' ○') : ' ·') })))
91
- }),
92
- ...modelPanels
93
- ];
94
- }
95
-
96
- export async function cron(h0) {
97
- const list = await h0.pi.cron.list();
98
- return [
99
- Hero({ title: 'cron', body: 'scheduled prompts. cron syntax, fired by freddie.', accent: list.length+' jobs' }),
100
- Kpi({ items: [[list.length,'jobs'],[list.filter(j => j.enabled).length,'enabled']] }),
101
- Panel({ title: 'add job', children: Form({ fields: [
102
- { name: 'cron', placeholder: '0 * * * * (m h dom mon dow)', required: true },
103
- { name: 'prompt', placeholder: 'prompt to run', required: true }
104
- ], submit: 'create', onSubmit: async ev => { await h0.pi.cron.create({ cron: ev.target.elements.cron.value, prompt: ev.target.elements.prompt.value }); if (typeof window.__fd_nav === 'function') window.__fd_nav('cron'); } }) }),
105
- Panel({ title: 'cron syntax', children: Receipt({ rows: [
106
- ['every minute', '* * * * *'],
107
- ['every hour (top)', '0 * * * *'],
108
- ['daily 09:00', '0 9 * * *'],
109
- ['weekdays 18:00', '0 18 * * 1-5'],
110
- ['every 15 min', '*/15 * * * *']
111
- ] }) }),
112
- Panel({ title: 'jobs', count: list.length, children: list.length === 0
113
- ? EmptyState({ text: 'no cron jobs — add one with the form above', glyph: '◷' })
114
- : h('div', { class: 'fd-list' }, ...list.map(j => h('div', { key: j.id, class: 'fd-list-row', 'data-cat': j.enabled ? 'kit' : 'external' },
115
- h('span', { class: 'fd-list-code' }, j.enabled ? '●' : '○'),
116
- h('div', { class: 'fd-list-main' },
117
- h('div', { class: 'fd-list-title' }, (j.prompt||'(no prompt)').slice(0,80)),
118
- h('div', { class: 'fd-list-sub' }, j.cron + ' · ' + j.id)
119
- ),
120
- h('div', { class: 'fd-list-meta' }, h('span', { class: 'fd-list-meta-mono' }, j.enabled ? 'enabled' : 'disabled'))
121
- ))) })
122
- ];
123
- }
124
-
125
- export async function skills(h0) {
126
- const list = [...h0.pi.skills.values()];
127
- const byCat = list.reduce((a, s) => { (a[s.category||'other'] = a[s.category||'other'] || []).push(s); return a; }, {});
128
- return [
129
- Hero({ title: 'skills', body: 'SKILL.md bundles loaded from ~/.freddie/skills + plugins.', accent: list.length+' loaded' }),
130
- Kpi({ items: [[list.length,'skills'],[Object.keys(byCat).length,'categories']] }),
131
- list.length === 0 ? EmptyState({ text: 'no skills — add SKILL.md files to ~/.freddie/skills/', glyph: '◈' }) : null,
132
- ...Object.entries(byCat).map(([cat, ss]) => Panel({ title: cat, count: ss.length, children: h('div', { class: 'fd-list' }, ...ss.map((s, i) => h('div', { key: s.name, class: 'fd-list-row', 'data-cat': cat === 'creative' ? 'preview' : cat === 'software-development' ? 'kit' : cat === 'planning' ? 'doc' : cat === 'ops' ? 'external' : 'doc' },
133
- h('span', { class: 'fd-list-code' }, String(i+1).padStart(2,'0')),
134
- h('div', { class: 'fd-list-main' },
135
- h('div', { class: 'fd-list-title' }, skillLabel(s)),
136
- s.description ? h('div', { class: 'fd-list-sub' }, (s.description||'').slice(0,140)) : null
137
- )
138
- ))) }))
139
- ].filter(Boolean);
140
- }
141
-
142
- export async function env(h0) {
143
- const list = typeof h0.pi.env?.list === 'function' ? h0.pi.env.list() : [];
144
- const setCount = list.filter(k => k.set).length;
145
- const groups = {};
146
- for (const k of list) {
147
- const idx = k.key.indexOf('_');
148
- const g = idx > 0 ? k.key.slice(0, idx) : 'OTHER';
149
- (groups[g] = groups[g] || []).push(k);
150
- }
151
- const sortedGroups = Object.entries(groups).sort((a,b) => b[1].length - a[1].length);
152
- return [
153
- Hero({ title: 'keys', body: 'env vars freddie reads. grouped by service prefix. click a chip to set.', accent: setCount+' / '+list.length+' set' }),
154
- Kpi({ items: [[setCount,'set'],[list.length-setCount,'missing'],[list.length,'total'],[sortedGroups.length,'groups']] }),
155
- list.length === 0 ? EmptyState({ text: 'no env keys registered', glyph: '⚿' }) : null,
156
- ...sortedGroups.map(([g, keys]) => {
157
- const setN = keys.filter(k => k.set).length;
158
- return Panel({ title: g.toLowerCase(), count: keys.length, right: setN === keys.length ? Chip({ tone: 'ok', children: 'all set' }) : setN === 0 ? Chip({ tone: 'miss', children: 'none set' }) : Chip({ tone: 'warn', children: setN+'/'+keys.length }),
159
- children: h('div', { class: 'fd-chips' }, ...keys.map(k => h('span', { key: k.key, onclick: () => { const v = prompt('set '+k.key+' (empty to unset):'); if (v == null) return; if (typeof h0.pi.env.set === 'function') { h0.pi.env.set(k.key, v); } }, class: 'fd-chip-wrap' }, Chip({ tone: k.set ? 'ok' : 'miss', children: k.key + (k.set ? ' ✓' : ' ·') }))))
160
- });
161
- })
162
- ].filter(Boolean);
163
- }
164
-
165
- export async function tools(h0) {
166
- const list = [...h0.pi.tools.values()];
167
- const envIsSet = k => typeof h0.pi.env?.isSet === 'function' ? h0.pi.env.isSet(k) : false;
168
- const f = window.__fd_toolFilter = window.__fd_toolFilter || { q: '' };
169
- const q = (f.q || '').toLowerCase();
170
- const filtered = q ? list.filter(t => (t.name||'').toLowerCase().includes(q) || (t.description||'').toLowerCase().includes(q) || (t.toolset||'').toLowerCase().includes(q)) : list;
171
- const bySet = filtered.reduce((a, t) => { (a[t.toolset||'core'] = a[t.toolset||'core'] || []).push(t); return a; }, {});
172
- const search = h('input', { type: 'search', placeholder: 'filter tools…', value: f.q, oninput: ev => { f.q = ev.target.value; if (typeof window.__fd_nav === 'function') window.__fd_nav('tools'); }, class: 'fd-search' });
173
- return [
174
- Hero({ title: 'tools', body: 'every tool the agent can call. param count + required env per row.', accent: list.length+' tools' }),
175
- Kpi({ items: [[list.length,'tools'],[filtered.length,'shown'],[Object.keys(bySet).length,'toolsets']] }),
176
- Panel({ title: 'filter', right: search, children: filtered.length === 0 ? EmptyState({ text: 'no matches for "'+f.q+'"', glyph: '⌕' }) : h('span', { class: 'fd-muted' }, filtered.length+' / '+list.length+' tools shown') }),
177
- ...Object.entries(bySet).map(([ts, items]) => Panel({ title: 'toolset · '+ts, count: items.length, children: h('div', { class: 'fd-list' }, ...items.map(t => {
178
- const params = t.schema?.parameters?.properties ? Object.keys(t.schema.parameters.properties).length : 0;
179
- const reqEnv = Array.isArray(t.requiresEnv) ? t.requiresEnv : [];
180
- return h('div', { key: t.name, class: 'fd-list-row', 'data-cat': 'kit' },
181
- h('span', { class: 'fd-list-code' }, '⚒'),
182
- h('div', { class: 'fd-list-main' },
183
- h('div', { class: 'fd-list-title fd-mono' }, t.name),
184
- h('div', { class: 'fd-list-sub' }, (t.description || (t.schema && t.schema.description) || '').slice(0,120))
185
- ),
186
- h('div', { class: 'fd-list-meta' },
187
- params > 0 ? Chip({ tone: 'neutral', children: params+' params' }) : null,
188
- ...reqEnv.map(k => Chip({ tone: envIsSet(k) ? 'ok' : 'miss', children: k }))
189
- ));
190
- })) }))
191
- ];
192
- }
193
-
@@ -1,170 +0,0 @@
1
- import * as webjsx from '../../../vendor/webjsx/index.js';
2
- import { Panel, Hero, Receipt, Kpi } from '../content.js';
3
- import { Chip } from '../shell.js';
4
- import { EmptyState } from '../files.js';
5
- import { skillLabel } from './helpers.js';
6
- const h = webjsx.createElement;
7
-
8
- export async function home(h0) {
9
- const sessions = await h0.pi.sessions.list();
10
- const health = h0.pi.health();
11
- const fmt = (k, v) => {
12
- if (k === 'ts' && typeof v === 'number') { const d = new Date(v); return d.toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; }
13
- if (typeof v === 'boolean') return v ? '✓' : '✗';
14
- return String(v);
15
- };
16
- return [
17
- Hero({ title: 'freddie', body: 'open js agent harness.', accent: h0.version || 'web' }),
18
- Kpi({ items: [[sessions.length,'sessions'],[h0.pi.tools.size,'tools'],[h0.pi.skills.size,'skills']] }),
19
- Panel({ title: 'quick start', children: Receipt({ rows: [
20
- ['open chat',"click 'chat' — set a working directory"],
21
- ['pick skill','software dev, research, planning'],
22
- ['set api key','keys tab → click chip'],
23
- ['add cron','cron tab → form']
24
- ] }) }),
25
- Panel({ title: 'system status', children: Receipt({ rows: Object.entries(health).map(([k,v]) => [k, fmt(k, v)]) }) })
26
- ];
27
- }
28
-
29
- function relTime(ts) {
30
- if (!ts) return '';
31
- const t = typeof ts === 'string' ? Date.parse(ts) : Number(ts);
32
- if (!Number.isFinite(t)) return '';
33
- const diff = (Date.now() - t) / 1000;
34
- if (diff < 60) return Math.floor(diff)+'s ago';
35
- if (diff < 3600) return Math.floor(diff/60)+'m ago';
36
- if (diff < 86400) return Math.floor(diff/3600)+'h ago';
37
- if (diff < 86400*30) return Math.floor(diff/86400)+'d ago';
38
- return new Date(t).toISOString().slice(0,10);
39
- }
40
-
41
- const PLATFORM_CAT = { web: 'kit', cli: 'doc', telegram: 'preview', slack: 'preview', discord: 'preview', api_server: 'external', webhook: 'external' };
42
-
43
- export async function sessions(h0) {
44
- const list = await h0.pi.sessions.list();
45
- const f = window.__fd_sessFilter = window.__fd_sessFilter || { q: '' };
46
- const q = (f.q || '').toLowerCase();
47
- const filtered = q ? list.filter(s => (s.title||'').toLowerCase().includes(q) || (s.id||'').includes(q) || (s.platform||'').toLowerCase().includes(q) || (s.cwd||'').toLowerCase().includes(q)) : list;
48
- const openSession = async s => {
49
- const msgs = await h0.pi.sessions.getMessages(s.id);
50
- const cs = window.__fd_chatState = window.__fd_chatState || { messages: [], busy: false, sessionId: null, cwd: '', skill: '', provider: '', model: '' };
51
- cs.sessionId = s.id;
52
- cs.messages = msgs.map(m => ({ role: m.role, content: String(m.content || '') }));
53
- if (s.cwd) cs.cwd = s.cwd;
54
- if (s.skill) cs.skill = s.skill;
55
- if (typeof window.__fd_nav === 'function') window.__fd_nav('chat');
56
- };
57
- const items = filtered.map(s => {
58
- const cat = PLATFORM_CAT[s.platform] || 'doc';
59
- const subParts = [s.platform || '—', s.model || '—', s.skill ? skillLabel({name:s.skill}) : null].filter(Boolean);
60
- const cwdMeta = s.cwd ? '…'+s.cwd.slice(-28) : '';
61
- const tsMeta = relTime(s.updated_at || s.created_at || s.ts);
62
- return h('div', { key: s.id, class: 'fd-list-row', 'data-cat': cat, role: 'button', tabindex: '0', onclick: () => openSession(s), onkeydown: ev => { if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openSession(s); } } },
63
- h('span', { class: 'fd-list-code' }, (s.id||'').slice(0,8)),
64
- h('div', { class: 'fd-list-main' },
65
- h('div', { class: 'fd-list-title' }, s.title || '(untitled)'),
66
- h('div', { class: 'fd-list-sub' }, subParts.join(' · '))
67
- ),
68
- h('div', { class: 'fd-list-meta' },
69
- cwdMeta ? h('span', { class: 'fd-list-meta-mono' }, cwdMeta) : null,
70
- tsMeta ? h('span', { class: 'fd-list-meta-rel' }, tsMeta) : null
71
- )
72
- );
73
- });
74
- const search = h('input', { type: 'search', 'aria-label': 'filter sessions', placeholder: 'filter by title / id / platform / cwd…', value: f.q, oninput: ev => { f.q = ev.target.value; if (typeof window.__fd_nav === 'function') window.__fd_nav('sessions'); }, class: 'fd-search' });
75
- const body = list.length === 0
76
- ? EmptyState({ text: 'no sessions yet — start one in /chat', glyph: '✉' })
77
- : filtered.length === 0
78
- ? EmptyState({ text: 'no matches for "'+f.q+'"', glyph: '⌕' })
79
- : h('div', { class: 'fd-list' }, ...items);
80
- return [
81
- Hero({ title: 'sessions', body: 'every chat turn lives here. click a row to continue.', accent: list.length+' total' }),
82
- Kpi({ items: [[list.length,'sessions'],[filtered.length,'shown']] }),
83
- Panel({ title: 'sessions', count: filtered.length, right: search, children: body })
84
- ];
85
- }
86
-
87
- export async function projects(h0) {
88
- const list = h0.pi.projects.list();
89
- const active = h0.pi.projects.active();
90
- const rows = h('div', { class: 'fd-list' }, ...list.map(p => {
91
- const isActive = p.name === active?.name;
92
- return h('div', { key: p.name, class: 'fd-list-row', 'data-cat': isActive ? 'kit' : 'doc', role: 'button', tabindex: '0',
93
- onclick: () => { if (!isActive) h0.pi.projects.setActive(p.name); },
94
- onkeydown: ev => { if ((ev.key === 'Enter' || ev.key === ' ') && !isActive) { ev.preventDefault(); h0.pi.projects.setActive(p.name); } } },
95
- h('span', { class: 'fd-list-code' }, isActive ? '●' : '○'),
96
- h('div', { class: 'fd-list-main' },
97
- h('div', { class: 'fd-list-title' }, p.name + (isActive ? ' (active)' : '')),
98
- h('div', { class: 'fd-list-sub' }, p.path)
99
- ));
100
- }));
101
- return [
102
- Hero({ title: 'projects', body: 'each project is its own ~/.freddie home.', accent: active ? 'active · '+active.name : 'no active project' }),
103
- Kpi({ items: [[list.length,'projects'],[active?.name||'—','active']] }),
104
- Panel({ title: 'add project', children: h('form', { class: 'row-form', onsubmit: ev => { ev.preventDefault(); h0.pi.projects.create({ name: ev.target.elements.name.value, path: ev.target.elements.path.value }); } },
105
- h('input', { name: 'name', placeholder: 'name', required: 'true' }),
106
- h('input', { name: 'path', placeholder: '/abs/path' }),
107
- h('button', { type: 'submit', class: 'btn-primary' }, 'add')
108
- ) }),
109
- Panel({ title: 'all projects', count: list.length, children: list.length ? rows : EmptyState({ text: 'no projects', glyph: '◆' }) })
110
- ];
111
- }
112
-
113
- export async function agents(h0) {
114
- const a = typeof h0.pi.agents === 'function' ? await h0.pi.agents() : { count: 0, turns: 0, active: null };
115
- const sList = await h0.pi.sessions.list();
116
- const recent = sList.slice(0, 10);
117
- const idle = !a.count;
118
- return [
119
- Hero({ title: 'agents', body: 'agent state machine snapshot. one xstate per turn.', accent: idle ? 'idle' : (a.count+' active') }),
120
- Kpi({ items: [[a.count||0,'active'],[a.turns||0,'turns total'],[sList.length,'sessions in store']] }),
121
- idle
122
- ? Panel({ title: 'no agent running', children: EmptyState({ text: 'start an agent by sending a prompt in /chat', glyph: '◌' }) })
123
- : Panel({ title: 'current agent', children: Receipt({ rows: [
124
- ['active session', a.active || '(none)'],
125
- ['total turns', String(a.turns || 0)],
126
- ['last activity', a.last_activity ? new Date(a.last_activity).toISOString().replace('T',' ').slice(0,19)+' UTC' : '—']
127
- ] }) }),
128
- Panel({ title: 'recent sessions', count: recent.length, children: recent.length === 0
129
- ? EmptyState({ text: 'no recent sessions', glyph: '✉' })
130
- : h('div', { class: 'fd-list' }, ...recent.map(s => h('div', { key: s.id, class: 'fd-list-row', 'data-cat': PLATFORM_CAT[s.platform] || 'doc' },
131
- h('span', { class: 'fd-list-code' }, (s.id||'').slice(0,8)),
132
- h('div', { class: 'fd-list-main' },
133
- h('div', { class: 'fd-list-title' }, s.title || '(untitled)'),
134
- h('div', { class: 'fd-list-sub' }, [s.platform||'—', s.model||'—'].join(' · '))
135
- ),
136
- h('div', { class: 'fd-list-meta' },
137
- h('span', { class: 'fd-list-meta-mono' }, String(s.turns ?? s.message_count ?? 0)+' turns')
138
- )
139
- )))
140
- })
141
- ];
142
- }
143
-
144
- export async function analytics(h0) {
145
- const list = await h0.pi.sessions.list();
146
- const tools = [...h0.pi.tools.values()];
147
- const skills = [...h0.pi.skills.values()];
148
- const byPlat = list.reduce((a,s) => { const k = s.platform||'(unset)'; a[k] = (a[k]||0)+1; return a; }, {});
149
- const byModel = list.reduce((a,s) => { const k = s.model||'(unset)'; a[k] = (a[k]||0)+1; return a; }, {});
150
- const byToolset = tools.reduce((a,t) => { const k = t.toolset||'core'; a[k] = (a[k]||0)+1; return a; }, {});
151
- const bySkillCat = skills.reduce((a,s) => { const k = s.category||'other'; a[k] = (a[k]||0)+1; return a; }, {});
152
- const sortDesc = obj => Object.entries(obj).sort((a,b) => b[1]-a[1]);
153
- const CAT_BY_TITLE = { 'by platform': 'kit', 'by model': 'preview', 'by toolset': 'doc', 'by skill category': 'external' };
154
- const mkPanel = (title, obj, glyph) => Panel({ title, count: Object.keys(obj).length, children: Object.keys(obj).length === 0
155
- ? EmptyState({ text: 'no data', glyph })
156
- : h('div', { class: 'fd-list fd-list-compact' }, ...sortDesc(obj).map(([k, n], i) => h('div', { key: k, class: 'fd-list-row', 'data-cat': CAT_BY_TITLE[title] || 'doc' },
157
- h('span', { class: 'fd-list-code' }, String(i+1).padStart(2,'0')),
158
- h('div', { class: 'fd-list-main' }, h('div', { class: 'fd-list-title' }, k)),
159
- h('div', { class: 'fd-list-meta' }, h('span', { class: 'fd-list-meta-mono' }, String(n)))
160
- )))
161
- });
162
- return [
163
- Hero({ title: 'analytics', body: 'shape of work — what platforms, what models, what tools.' }),
164
- Kpi({ items: [[list.length,'sessions'],[tools.length,'tools'],[skills.length,'skills']] }),
165
- mkPanel('by platform', byPlat, '◉'),
166
- mkPanel('by model', byModel, '◎'),
167
- mkPanel('by toolset', byToolset, '⚒'),
168
- mkPanel('by skill category', bySkillCat, '◈')
169
- ];
170
- }
@@ -1,69 +0,0 @@
1
- import * as webjsx from '../../../vendor/webjsx/index.js';
2
- import { Panel, Hero, Kpi, Form } from '../content.js';
3
- import { Chip } from '../shell.js';
4
- import { EmptyState } from '../files.js';
5
- const h = webjsx.createElement;
6
-
7
- export async function batch(h0) {
8
- const results = window.__fd_batchResults = window.__fd_batchResults || [];
9
- const status = window.__fd_batchStatus = window.__fd_batchStatus || { running: false, error: null };
10
- const onSubmit = async ev => {
11
- const prompts = ev.target.elements.prompts.value.split('\n').map(s => s.trim()).filter(Boolean);
12
- if (!prompts.length) return;
13
- status.running = true; status.error = null; window.__fd_batchResults = [];
14
- if (typeof window.__fd_nav === 'function') window.__fd_nav('batch');
15
- try {
16
- const r = await h0.pi.batch.run(prompts, Number(ev.target.elements.concurrency.value) || 4);
17
- const arr = Array.isArray(r) ? r : (r && typeof r === 'object' ? Object.entries(r).map(([k, v]) => ({ prompt: k, result: v })) : []);
18
- window.__fd_batchResults = arr;
19
- } catch (e) { status.error = e.message || String(e); }
20
- status.running = false;
21
- if (typeof window.__fd_nav === 'function') window.__fd_nav('batch');
22
- };
23
- const resPanel = status.running ? Panel({ title: 'results', children: h('span', {}, 'running…') })
24
- : status.error ? Panel({ title: 'results', children: h('span', { class: 'fd-muted' }, 'error: '+status.error) })
25
- : results.length === 0 ? Panel({ title: 'results', children: EmptyState({ text: 'no results yet', glyph: '⊞' }) })
26
- : Panel({ title: 'results', count: results.length, children: h('div', { class: 'fd-list' }, ...results.map((it, i) => h('div', { key: i, class: 'fd-list-row', 'data-cat': it.error ? 'external' : 'kit' },
27
- h('span', { class: 'fd-list-code' }, String(i+1).padStart(2,'0')),
28
- h('div', { class: 'fd-list-main' },
29
- h('div', { class: 'fd-list-title' }, (it.prompt||'').slice(0,80)),
30
- h('div', { class: 'fd-list-sub' }, (it.result||it.error||'').slice(0,160))
31
- ),
32
- h('div', { class: 'fd-list-meta' }, h('span', { class: 'fd-list-meta-mono' }, it.error ? 'error' : 'ok'))
33
- ))) });
34
- return [
35
- Hero({ title: 'batch', body: 'run many prompts in parallel. one per line.', accent: results.length ? results.length+' results' : 'idle' }),
36
- Panel({ title: 'run batch', children: Form({ fields: [
37
- { name: 'prompts', kind: 'textarea', placeholder: 'one prompt per line', rows: 6 },
38
- { name: 'concurrency', type: 'number', value: '4' }
39
- ], submit: 'run', onSubmit }) }),
40
- resPanel
41
- ];
42
- }
43
-
44
- export async function gateway(h0) {
45
- const platforms = typeof h0.pi.gateway?.platforms === 'function' ? h0.pi.gateway.platforms() : [];
46
- const active = platforms.filter(p => p.enabled);
47
- const envIsSet = k => typeof h0.pi.env?.isSet === 'function' ? h0.pi.env.isSet(k) : false;
48
- return [
49
- Hero({ title: 'gateway', body: 'webhook + bot platforms. each adapter declares required env keys.', accent: active.length+' / '+platforms.length+' active' }),
50
- Kpi({ items: [[platforms.length,'platforms'],[active.length,'active']] }),
51
- Panel({ title: 'platforms', count: platforms.length, right: active.length > 0 ? Chip({ tone: 'ok', children: active.length+' active' }) : Chip({ tone: 'miss', children: 'none active' }),
52
- children: platforms.length === 0 ? EmptyState({ text: 'no platforms registered', glyph: '⇌' }) : h('div', { class: 'fd-list' }, ...platforms.map(p => {
53
- const reqEnv = Array.isArray(p.requiresEnv) ? p.requiresEnv : [];
54
- const setN = reqEnv.filter(envIsSet).length;
55
- const envSummary = reqEnv.length === 0 ? '' : setN === reqEnv.length ? '✓ env ready' : 'missing '+(reqEnv.length-setN)+' / '+reqEnv.length;
56
- return h('div', { key: p.name, class: 'fd-list-row', 'data-cat': p.enabled ? 'kit' : 'external' },
57
- h('span', { class: 'fd-list-code' }, p.enabled ? '●' : '○'),
58
- h('div', { class: 'fd-list-main' },
59
- h('div', { class: 'fd-list-title' }, p.name),
60
- h('div', { class: 'fd-list-sub' }, p.note || '—')
61
- ),
62
- h('div', { class: 'fd-list-meta' },
63
- h('span', { class: 'fd-list-meta-mono' }, p.enabled ? 'enabled' : 'disabled'),
64
- envSummary ? h('span', { class: 'fd-list-meta-rel' }, envSummary) : null
65
- ));
66
- }))
67
- })
68
- ];
69
- }