anentrypoint-design 0.0.147 → 0.0.150
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/app-shell.css +19 -1
- package/community.css +2 -0
- package/dist/247420.css +21 -1
- package/dist/247420.js +12 -12
- package/package.json +1 -1
- package/src/components/content.js +11 -3
- package/src/components/freddie.js +70 -18
- package/src/kits/os/theme.css +28 -0
- package/src/kits/os/wm.css +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.150",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -154,8 +154,15 @@ export function Kpi({ items = [] }) {
|
|
|
154
154
|
h('div', { class: 'lbl' }, l))));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothing here yet' }) {
|
|
157
|
+
export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothing here yet', rowLabels }) {
|
|
158
158
|
if (!rows || rows.length === 0) return h('div', { class: 'empty' }, emptyText);
|
|
159
|
+
// rowLabels lets callers supply a plain-text label per row when the first
|
|
160
|
+
// cell is a vnode (so the aria-label is meaningful, not the literal 'row').
|
|
161
|
+
const labelFor = (row, i) => {
|
|
162
|
+
if (Array.isArray(rowLabels) && rowLabels[i] != null) return String(rowLabels[i]);
|
|
163
|
+
const c = row[0];
|
|
164
|
+
return c == null ? 'row' : (typeof c === 'object' ? 'row' : String(c));
|
|
165
|
+
};
|
|
159
166
|
return h('table', { role: 'table' },
|
|
160
167
|
h('thead', {}, h('tr', { role: 'row' }, ...headers.map((hd, i) => h('th', { key: i, scope: 'col', role: 'columnheader' }, hd)))),
|
|
161
168
|
h('tbody', {}, ...rows.map((row, i) => h('tr', {
|
|
@@ -163,7 +170,7 @@ export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothin
|
|
|
163
170
|
class: onRowClick ? 'clickable' : '',
|
|
164
171
|
role: 'row',
|
|
165
172
|
onclick: onRowClick ? () => onRowClick(i) : null,
|
|
166
|
-
...(onRowClick ? { tabindex: '0', onkeydown: (e) => { if (e.key === 'Enter') onRowClick(i); } } : {})
|
|
173
|
+
...(onRowClick ? { tabindex: '0', 'aria-label': 'open ' + labelFor(row, i), onkeydown: (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onRowClick(i); } } } : {})
|
|
167
174
|
}, ...row.map((c, j) => h('td', { key: j, role: 'cell' }, c == null ? '' : (typeof c === 'object' ? c : String(c))))))));
|
|
168
175
|
}
|
|
169
176
|
|
|
@@ -235,13 +242,14 @@ export function PageHeader({ title, lede, eyebrow, right }) {
|
|
|
235
242
|
);
|
|
236
243
|
}
|
|
237
244
|
|
|
238
|
-
export function SearchInput({ value = '', placeholder = 'search…', onInput, onSubmit, name = 'q', key }) {
|
|
245
|
+
export function SearchInput({ value = '', placeholder = 'search…', onInput, onSubmit, name = 'q', key, label }) {
|
|
239
246
|
return h('input', {
|
|
240
247
|
key,
|
|
241
248
|
type: 'search',
|
|
242
249
|
name,
|
|
243
250
|
class: 'ds-search-input',
|
|
244
251
|
placeholder,
|
|
252
|
+
'aria-label': label || placeholder,
|
|
245
253
|
value,
|
|
246
254
|
oninput: onInput ? (e) => onInput(e.target.value, e) : null,
|
|
247
255
|
onkeydown: onSubmit ? (e) => { if (e.key === 'Enter') onSubmit(e.target.value, e); } : null
|
|
@@ -28,6 +28,19 @@ const section = (title, ...children) => Panel({ title, children: children.flat()
|
|
|
28
28
|
const noteAlert = (note) => note ? h('div', { class: 'ds-alert ds-alert-' + note.kind, role: 'alert' },
|
|
29
29
|
h('span', { class: 'ds-alert-icon' }, '!'),
|
|
30
30
|
h('div', { class: 'ds-alert-content' }, note.msg)) : null;
|
|
31
|
+
// Manual refresh button for non-polling pages — parity with auto-refreshing ones.
|
|
32
|
+
const refreshBtn = (onClick, busy) => Btn({ children: busy ? 'refreshing…' : '↻ refresh', disabled: !!busy, onClick, 'aria-label': 'refresh' });
|
|
33
|
+
// Non-blocking refresh-error banner: keep last-good content, surface the failure.
|
|
34
|
+
const refreshError = (err) => err ? h('div', { class: 'ds-alert ds-alert-warn', role: 'status', 'aria-live': 'polite' },
|
|
35
|
+
h('span', { class: 'ds-alert-icon' }, '!'),
|
|
36
|
+
h('div', { class: 'ds-alert-content' }, 'refresh failed: ' + String(err.message || err))) : null;
|
|
37
|
+
// Polite live region announcing async busy/done state to screen readers.
|
|
38
|
+
const liveRegion = (msg) => h('div', { class: 'fd-sr-live', role: 'status', 'aria-live': 'polite' }, msg || '');
|
|
39
|
+
// Truncate with a title tooltip carrying the full text.
|
|
40
|
+
const trunc = (s, n = 90) => { const str = String(s || ''); return str.length > n ? { text: str.slice(0, n) + '…', title: str } : { text: str, title: null }; };
|
|
41
|
+
// Autoscroll a thread only when the user is already near the bottom, so
|
|
42
|
+
// scrolling up to read history is not yanked back down on the next render.
|
|
43
|
+
const stickyScroll = (el) => { if (!el) return; const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80; if (nearBottom) el.scrollTop = el.scrollHeight; };
|
|
31
44
|
|
|
32
45
|
// ---- home ------------------------------------------------------------------
|
|
33
46
|
|
|
@@ -37,9 +50,10 @@ export const home = makePage((ctx) => {
|
|
|
37
50
|
const [health, agents, sessions] = await Promise.all([
|
|
38
51
|
api('/api/health').catch(() => null),
|
|
39
52
|
api('/api/agents').catch(() => null),
|
|
40
|
-
api('/api/sessions').catch(() =>
|
|
53
|
+
api('/api/sessions').catch((e) => ({ _err: e })),
|
|
41
54
|
]);
|
|
42
|
-
|
|
55
|
+
const sessFailed = sessions && sessions._err;
|
|
56
|
+
ctx.set({ loading: false, health, agents, sessions: Array.isArray(sessions) ? sessions : [], sessFailed, error: null });
|
|
43
57
|
} catch (e) { ctx.set({ loading: false, error: e }); }
|
|
44
58
|
}
|
|
45
59
|
load();
|
|
@@ -61,12 +75,14 @@ export const home = makePage((ctx) => {
|
|
|
61
75
|
[agents.count ?? 0, 'active agents'],
|
|
62
76
|
] }),
|
|
63
77
|
section('recent sessions',
|
|
64
|
-
|
|
65
|
-
?
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
s.sessFailed
|
|
79
|
+
? errorState(new Error('could not load sessions'))
|
|
80
|
+
: sessions.length
|
|
81
|
+
? Table({
|
|
82
|
+
headers: ['session', 'platform', 'updated'],
|
|
83
|
+
rows: sessions.slice(0, 8).map(x => { const t = trunc(x.title || x.id, 60); return [h('span', { title: t.title }, t.text), x.platform || '—', fmtAgo(x.updated_at)]; }),
|
|
84
|
+
})
|
|
85
|
+
: emptyState('no sessions yet')),
|
|
70
86
|
section('health',
|
|
71
87
|
s.health ? Table({ headers: ['check', 'status'], rows: Object.entries(s.health).map(([k, v]) => [k, typeof v === 'object' ? JSON.stringify(v) : String(v)]) })
|
|
72
88
|
: emptyState('health endpoint unavailable')),
|
|
@@ -96,8 +112,9 @@ export const chat = makePage((ctx) => {
|
|
|
96
112
|
const s = ctx.state;
|
|
97
113
|
return h('div', { class: 'fd-chat' },
|
|
98
114
|
PageHeader({ eyebrow: 'freddie', title: 'chat', lede: 'one-shot agent turns · POST /api/chat' }),
|
|
115
|
+
liveRegion(s.sending ? 'waiting for assistant reply' : ''),
|
|
99
116
|
h('div', { class: 'chat-thread fd-chat-thread', role: 'log', 'aria-label': 'chat messages',
|
|
100
|
-
ref:
|
|
117
|
+
ref: stickyScroll },
|
|
101
118
|
s.messages.length ? s.messages.map((m, i) => ChatMessage({ ...m, key: i }))
|
|
102
119
|
: emptyState('send a prompt to start', '✎'),
|
|
103
120
|
s.sending ? ChatMessage({ role: 'assistant', typing: true, key: '_typing' }) : null),
|
|
@@ -114,11 +131,25 @@ export const chat = makePage((ctx) => {
|
|
|
114
131
|
// ---- voice -----------------------------------------------------------------
|
|
115
132
|
|
|
116
133
|
export const voice = makePage((ctx) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
async function load() {
|
|
135
|
+
// Probe for a voice backend; the endpoint is optional, so a 404/!ok
|
|
136
|
+
// means "not wired" rather than an error to surface.
|
|
137
|
+
try { const v = await api('/api/voice').catch(() => null); ctx.set({ loading: false, voice: v, error: null }); }
|
|
138
|
+
catch (e) { ctx.set({ loading: false, error: e }); }
|
|
139
|
+
}
|
|
140
|
+
load();
|
|
141
|
+
return () => {
|
|
142
|
+
const s = ctx.state;
|
|
143
|
+
if (s.loading) return loadingState();
|
|
144
|
+
const v = s.voice;
|
|
145
|
+
const enabled = v && (v.enabled || v.transcription || v.tts);
|
|
146
|
+
return [
|
|
147
|
+
PageHeader({ eyebrow: 'freddie', title: 'voice', lede: 'voice surfaces', right: enabled ? Chip({ tone: 'ok', children: 'enabled' }) : Chip({ tone: 'neutral', children: 'not configured' }) }),
|
|
148
|
+
enabled
|
|
149
|
+
? section('backends', Table({ headers: ['capability', 'status'], rows: [['transcription', v.transcription ? Chip({ tone: 'ok', children: 'on' }) : Chip({ tone: 'neutral', children: 'off' })], ['tts', v.tts ? Chip({ tone: 'ok', children: 'on' }) : Chip({ tone: 'neutral', children: 'off' })]] }))
|
|
150
|
+
: section('status', emptyState('no voice backend wired in this build. configure a transcription/tts plugin to enable.', '🎙')),
|
|
151
|
+
];
|
|
152
|
+
};
|
|
122
153
|
});
|
|
123
154
|
|
|
124
155
|
// ---- sessions --------------------------------------------------------------
|
|
@@ -134,6 +165,7 @@ export const sessions = makePage((ctx) => {
|
|
|
134
165
|
try { ctx.set({ loading: false, list: await api('/api/search?q=' + encodeURIComponent(q)), error: null }); }
|
|
135
166
|
catch (e) { ctx.set({ loading: false, error: e }); }
|
|
136
167
|
}
|
|
168
|
+
async function refresh() { ctx.set({ refreshing: true }); try { ctx.set({ list: await api('/api/sessions'), error: null }); } catch (e) { ctx.set({ error: e }); } ctx.set({ refreshing: false }); }
|
|
137
169
|
async function open(id) {
|
|
138
170
|
ctx.set({ selected: id, msgLoading: true });
|
|
139
171
|
try { ctx.set({ messages: await api('/api/sessions/' + encodeURIComponent(id) + '/messages'), msgLoading: false }); }
|
|
@@ -146,12 +178,14 @@ export const sessions = makePage((ctx) => {
|
|
|
146
178
|
if (s.error && !s.list) return errorState(s.error, load);
|
|
147
179
|
const list = Array.isArray(s.list) ? s.list : [];
|
|
148
180
|
return [
|
|
149
|
-
PageHeader({ eyebrow: 'freddie', title: 'sessions', lede: list.length + ' sessions' }),
|
|
150
|
-
|
|
181
|
+
PageHeader({ eyebrow: 'freddie', title: 'sessions', lede: list.length + ' sessions', right: refreshBtn(refresh, s.refreshing) }),
|
|
182
|
+
s.error && s.list ? refreshError(s.error) : null,
|
|
183
|
+
SearchInput({ value: s.q, label: 'search sessions', placeholder: 'search messages…', onInput: (v) => { s.q = v; }, onSubmit: (v) => search(v) }),
|
|
151
184
|
section('sessions',
|
|
152
185
|
list.length
|
|
153
186
|
? Table({ headers: ['session', 'platform', 'updated'], onRowClick: (i) => open(list[i].id),
|
|
154
|
-
|
|
187
|
+
rowLabels: list.map(x => x.title || x.id),
|
|
188
|
+
rows: list.map(x => { const t = trunc(x.title || x.id, 60); return [h('span', { title: t.title }, t.text), x.platform || '—', fmtAgo(x.updated_at)]; }) })
|
|
155
189
|
: emptyState('no sessions match')),
|
|
156
190
|
s.selected ? section('messages · ' + s.selected,
|
|
157
191
|
s.msgLoading ? loadingState()
|
|
@@ -282,6 +316,7 @@ export const models = makePage((ctx) => {
|
|
|
282
316
|
const status = s.sampler?.status || {};
|
|
283
317
|
return [
|
|
284
318
|
PageHeader({ eyebrow: 'freddie', title: 'models', lede: providers.length + ' providers', right: Btn({ primary: true, disabled: s.discovering, children: s.discovering ? 'discovering…' : 'discover', onClick: discover }) }),
|
|
319
|
+
liveRegion(s.discovering ? 'discovering models' : ''),
|
|
285
320
|
section('providers', providers.length ? Table({
|
|
286
321
|
headers: ['provider', 'sampler', 'cached models'],
|
|
287
322
|
rows: providers.map(p => {
|
|
@@ -381,11 +416,16 @@ export const config = makePage((ctx) => {
|
|
|
381
416
|
if (s.error) return errorState(s.error, load);
|
|
382
417
|
const cfg = s.cfg || {};
|
|
383
418
|
const flat = Object.entries(cfg).filter(([, v]) => typeof v !== 'object' || v === null);
|
|
419
|
+
const nested = Object.entries(cfg).filter(([, v]) => typeof v === 'object' && v !== null);
|
|
384
420
|
const skinList = Array.isArray(s.skins) ? s.skins : (s.skins?.skins || s.skins?.available || []);
|
|
385
421
|
const activeSkin = cfg.skin || s.skins?.active || '';
|
|
386
422
|
return [
|
|
387
423
|
PageHeader({ eyebrow: 'freddie', title: 'config', lede: 'runtime configuration' }),
|
|
388
424
|
noteAlert(s.note),
|
|
425
|
+
liveRegion(s.busy ? 'saving configuration' : ''),
|
|
426
|
+
nested.length ? h('div', { class: 'ds-alert ds-alert-info', role: 'note' },
|
|
427
|
+
h('span', { class: 'ds-alert-icon' }, 'i'),
|
|
428
|
+
h('div', { class: 'ds-alert-content' }, nested.length + ' nested config ' + (nested.length === 1 ? 'object is' : 'objects are') + ' read-only here (' + nested.map(([k]) => k).join(', ') + ') — edit via the config file or raw view below.')) : null,
|
|
389
429
|
skinList.length ? section('skin',
|
|
390
430
|
Select({ label: 'active skin', value: activeSkin, options: skinList, onChange: (v) => setSkin(v) })
|
|
391
431
|
) : null,
|
|
@@ -463,7 +503,19 @@ export const batch = makePage((ctx) => {
|
|
|
463
503
|
TextField({ label: 'prompts (one per line)', value: s.prompts, multiline: true, rows: 6, onInput: (v) => { s.prompts = v; } }),
|
|
464
504
|
TextField({ label: 'concurrency', type: 'number', value: String(s.concurrency), onInput: (v) => { s.concurrency = v; } }),
|
|
465
505
|
Btn({ primary: true, disabled: s.busy, children: s.busy ? 'running…' : 'run batch', onClick: run })),
|
|
466
|
-
s.result ? section('result',
|
|
506
|
+
s.result ? section('result', (() => {
|
|
507
|
+
const r = s.result;
|
|
508
|
+
const items = Array.isArray(r.results) ? r.results : (Array.isArray(r) ? r : null);
|
|
509
|
+
if (!items) return h('pre', { class: 'fd-pre' }, JSON.stringify(r, null, 2));
|
|
510
|
+
return [
|
|
511
|
+
Kpi({ items: [[items.length, 'prompts'], [items.filter(x => !x.error).length, 'ok'], [items.filter(x => x.error).length, 'errors']] }),
|
|
512
|
+
Table({ headers: ['#', 'prompt', 'status', 'output'], rows: items.map((x, i) => {
|
|
513
|
+
const p = trunc(x.prompt || x.input || '', 50);
|
|
514
|
+
const out = trunc(x.error || x.result || x.content || x.output || '', 70);
|
|
515
|
+
return [String(i + 1), h('span', { title: p.title }, p.text), x.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: 'ok', children: 'ok' }), h('span', { title: out.title }, out.text)];
|
|
516
|
+
}) }),
|
|
517
|
+
];
|
|
518
|
+
})()) : null,
|
|
467
519
|
];
|
|
468
520
|
};
|
|
469
521
|
});
|
package/src/kits/os/theme.css
CHANGED
|
@@ -55,6 +55,11 @@ html, body {
|
|
|
55
55
|
.os-menubar > *, .os-taskbar > * { flex-shrink: 0; }
|
|
56
56
|
.os-menubar .os-spacer { flex: 1 1 auto; min-width: 0; }
|
|
57
57
|
.os-menubar .os-tray { margin-left: auto; }
|
|
58
|
+
/* Many open windows must scroll within the bar, not overflow it. Mobile
|
|
59
|
+
already had this; promote it to all widths so a crowded desktop taskbar
|
|
60
|
+
scrolls horizontally instead of pushing buttons off-screen. */
|
|
61
|
+
.os-taskbar { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }
|
|
62
|
+
.os-taskbar::-webkit-scrollbar { display: none; }
|
|
58
63
|
|
|
59
64
|
.os-brand {
|
|
60
65
|
color: var(--os-fg);
|
|
@@ -738,3 +743,26 @@ html.ds-247420 { touch-action: pan-x pan-y; overscroll-behavior: none; -webkit-t
|
|
|
738
743
|
.ds-247420 .tb-sessions-card-actions button:hover { background: color-mix(in oklab, var(--fg, #1a1a1a) 6%, transparent); }
|
|
739
744
|
.ds-247420 .tb-sessions-card-actions button.danger:hover { background: #d33; color: #fff; border-color: #d33; }
|
|
740
745
|
.ds-247420 .tb-sessions-empty-mid { padding: 40px; text-align: center; opacity: 0.6; font-size: 13px; }
|
|
746
|
+
|
|
747
|
+
/* ---- prefers-contrast: more — strengthen borders + focus so the translucent
|
|
748
|
+
chrome stays legible under forced/high-contrast user settings. ---- */
|
|
749
|
+
@media (prefers-contrast: more) {
|
|
750
|
+
.ds-247420 .wm-win { border-color: var(--fg, #1a1a1a); }
|
|
751
|
+
.ds-247420 .wm-bar { border-bottom: 1px solid var(--fg, #1a1a1a); }
|
|
752
|
+
.ds-247420 .os-menubar,
|
|
753
|
+
.ds-247420 .os-taskbar { border-color: var(--fg, #1a1a1a); }
|
|
754
|
+
.ds-247420 .tb-sess-chip,
|
|
755
|
+
.ds-247420 .tb-sessions-card,
|
|
756
|
+
.ds-247420 .tb-sessions-btn { border-color: var(--fg, #1a1a1a); }
|
|
757
|
+
.ds-247420 :focus-visible { outline: 2px solid var(--fg, #1a1a1a); outline-offset: 2px; }
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/* ---- print: a web-OS desktop has no meaningful print form. Suppress the
|
|
761
|
+
live chrome so a Ctrl+P does not waste pages on translucent panels. ---- */
|
|
762
|
+
@media print {
|
|
763
|
+
.os-menubar, .os-taskbar, .wm-snap-preview,
|
|
764
|
+
.tb-sess-overlay, .tb-switching, .wm-switcher { display: none !important; }
|
|
765
|
+
.wm-root, .wm-canvas { position: static !important; transform: none !important; overflow: visible !important; }
|
|
766
|
+
.wm-win { position: static !important; box-shadow: none !important; border: 1px solid #000 !important; page-break-inside: avoid; margin: 0 0 12px; width: auto !important; height: auto !important; }
|
|
767
|
+
.wm-win.wm-min { display: none !important; }
|
|
768
|
+
}
|
package/src/kits/os/wm.css
CHANGED
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
overflow: hidden;
|
|
26
26
|
}
|
|
27
27
|
.wm-win.wm-focused { box-shadow: inset 4px 0 0 var(--os-accent); }
|
|
28
|
+
/* Promote to a compositor layer only for the duration of a drag/resize so the
|
|
29
|
+
per-frame left/top/width/height writes don't force a main-thread layout each
|
|
30
|
+
pointermove. Released on pointerup (class removed) to avoid the permanent
|
|
31
|
+
will-change memory/perf anti-pattern. */
|
|
32
|
+
.wm-win.wm-dragging { will-change: left, top; }
|
|
33
|
+
.wm-win.wm-resizing { will-change: width, height; }
|
|
28
34
|
|
|
29
35
|
.wm-bar {
|
|
30
36
|
display: flex;
|