anentrypoint-design 0.0.145 → 0.0.147
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 +157 -22
- package/colors_and_type.css +21 -1
- package/community.css +106 -4
- package/dist/247420.css +339 -30
- package/dist/247420.js +12 -11
- package/package.json +1 -1
- package/src/components/chat.js +2 -2
- package/src/components/community.js +104 -4
- package/src/components/content.js +17 -8
- package/src/components/editor-primitives.js +2 -2
- package/src/components/form-primitives.js +25 -12
- package/src/components/freddie/runtime.js +101 -0
- package/src/components/freddie.js +614 -27
- package/src/components/overlay-primitives.js +91 -2
- package/src/components/shell.js +40 -5
- package/src/components/voice.js +25 -0
- package/src/components.js +7 -5
- package/src/kits/os/freddie/pages-chat.js +1 -1
- package/src/kits/os/freddie/pages-core.js +1 -1
- package/src/kits/os/freddie-dashboard.js +3 -3
- package/src/kits/os/shell.js +2 -0
|
@@ -303,11 +303,12 @@ export function CommandPalette({ open, items = [], onSelect, onClose } = {}) {
|
|
|
303
303
|
h('div', { class: 'ov-cmd-panel', role: 'dialog', 'aria-label': 'Command palette', onkeydown: onKey },
|
|
304
304
|
h('input', {
|
|
305
305
|
type: 'text', class: 'ov-cmd-input', placeholder: 'Type a command…',
|
|
306
|
-
'aria-label': '
|
|
306
|
+
'aria-label': 'command search',
|
|
307
|
+
'aria-controls': 'ov-cmd-list',
|
|
307
308
|
oninput: (e) => { filterText = e.target.value; active = 0; renderInner(); },
|
|
308
309
|
ref: (el) => { if (!el || el._ovCmdIn) return; el._ovCmdIn = true; inputEl = el; queueMicrotask(() => el.focus()); },
|
|
309
310
|
}),
|
|
310
|
-
h('div', { class: 'ov-cmd-list', role: 'listbox',
|
|
311
|
+
h('div', { class: 'ov-cmd-list', id: 'ov-cmd-list', role: 'listbox',
|
|
311
312
|
ref: (el) => { if (!el) return; listEl = el; queueMicrotask(renderInner); } })
|
|
312
313
|
)
|
|
313
314
|
);
|
|
@@ -462,3 +463,91 @@ export function SettingsPopover({ title = 'Settings', open, anchorX = 0, anchorY
|
|
|
462
463
|
}))
|
|
463
464
|
);
|
|
464
465
|
}
|
|
466
|
+
|
|
467
|
+
// AuthModal — centered login dialog: extension / generate / import (nsec) modes.
|
|
468
|
+
export function AuthModal({ mode = 'extension', error = '', busy = false, open = false, onModeChange, onConnectExtension, onGenerate, onImport, onClose } = {}) {
|
|
469
|
+
if (!open) return null;
|
|
470
|
+
const close = () => onClose && onClose();
|
|
471
|
+
const modes = [
|
|
472
|
+
{ id: 'extension', label: 'Extension' },
|
|
473
|
+
{ id: 'generate', label: 'Generate' },
|
|
474
|
+
{ id: 'import', label: 'Import key' },
|
|
475
|
+
];
|
|
476
|
+
let nsec = '';
|
|
477
|
+
const body = () => {
|
|
478
|
+
if (mode === 'generate') {
|
|
479
|
+
return [
|
|
480
|
+
h('p', { class: 'ov-auth-hint' }, 'Create a fresh Nostr identity. Back up the key after.'),
|
|
481
|
+
h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
|
|
482
|
+
onclick: () => onGenerate && onGenerate() }, busy ? 'Working…' : 'Generate new key'),
|
|
483
|
+
];
|
|
484
|
+
}
|
|
485
|
+
if (mode === 'import') {
|
|
486
|
+
return [
|
|
487
|
+
h('p', { class: 'ov-auth-hint' }, 'Paste an existing nsec / hex secret key.'),
|
|
488
|
+
h('input', {
|
|
489
|
+
type: 'password', class: 'ov-auth-input', placeholder: 'nsec1…',
|
|
490
|
+
'aria-label': 'secret key', disabled: busy ? true : null,
|
|
491
|
+
oninput: (e) => { nsec = e.target.value; },
|
|
492
|
+
onkeydown: (e) => { if (e.key === 'Enter') { e.preventDefault(); onImport && onImport(nsec); } },
|
|
493
|
+
}),
|
|
494
|
+
h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
|
|
495
|
+
onclick: () => onImport && onImport(nsec) }, busy ? 'Working…' : 'Import'),
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
return [
|
|
499
|
+
h('p', { class: 'ov-auth-hint' }, 'Connect a NIP-07 browser extension (Alby, nos2x…).'),
|
|
500
|
+
h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
|
|
501
|
+
onclick: () => onConnectExtension && onConnectExtension() }, busy ? 'Connecting…' : 'Connect extension'),
|
|
502
|
+
];
|
|
503
|
+
};
|
|
504
|
+
return h('div', {
|
|
505
|
+
class: 'ov-auth-backdrop', role: 'presentation',
|
|
506
|
+
ref: (el) => {
|
|
507
|
+
if (!el || el._ovAuth) return; el._ovAuth = true;
|
|
508
|
+
el.addEventListener('mousedown', (e) => {
|
|
509
|
+
const panel = el.querySelector('.ov-auth-panel');
|
|
510
|
+
if (panel && !panel.contains(e.target)) close();
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
h('div', {
|
|
515
|
+
class: 'ov-auth-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Sign in',
|
|
516
|
+
onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
|
|
517
|
+
},
|
|
518
|
+
h('div', { class: 'ov-auth-head' },
|
|
519
|
+
h('h2', { class: 'ov-auth-title' }, 'Sign in'),
|
|
520
|
+
h('button', { type: 'button', class: 'ov-auth-x', 'aria-label': 'close', onclick: close }, '×')
|
|
521
|
+
),
|
|
522
|
+
h('div', { class: 'ov-auth-tabs', role: 'tablist' },
|
|
523
|
+
...modes.map(m => h('button', {
|
|
524
|
+
type: 'button', role: 'tab', key: 'am-' + m.id,
|
|
525
|
+
class: 'ov-auth-tab' + (m.id === mode ? ' is-active' : ''),
|
|
526
|
+
'aria-selected': m.id === mode ? 'true' : 'false',
|
|
527
|
+
onclick: () => onModeChange && onModeChange(m.id),
|
|
528
|
+
}, m.label))
|
|
529
|
+
),
|
|
530
|
+
h('div', { class: 'ov-auth-body' }, ...body()),
|
|
531
|
+
error ? h('div', { class: 'ov-auth-error', role: 'alert' }, String(error)) : null
|
|
532
|
+
)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// VideoLightbox — fullscreen video player overlay with backdrop dismiss.
|
|
537
|
+
export function VideoLightbox({ src, label = '', open = false, onClose } = {}) {
|
|
538
|
+
if (!open || !src) return null;
|
|
539
|
+
const close = () => onClose && onClose();
|
|
540
|
+
return h('div', {
|
|
541
|
+
class: 'ov-lightbox-backdrop', role: 'dialog', 'aria-modal': 'true', 'aria-label': label || 'Video',
|
|
542
|
+
tabindex: '-1',
|
|
543
|
+
onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
|
|
544
|
+
ref: (el) => { if (el && !el._ovLb) { el._ovLb = true; queueMicrotask(() => el.focus()); } },
|
|
545
|
+
onmousedown: (e) => { if (e.target === e.currentTarget) close(); },
|
|
546
|
+
},
|
|
547
|
+
h('button', { type: 'button', class: 'ov-lightbox-x', 'aria-label': 'close', onclick: close }, '×'),
|
|
548
|
+
h('div', { class: 'ov-lightbox-stage' },
|
|
549
|
+
h('video', { class: 'ov-lightbox-video', src, controls: true, autoplay: true, playsinline: true }),
|
|
550
|
+
label ? h('div', { class: 'ov-lightbox-label' }, label) : null
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
}
|
package/src/components/shell.js
CHANGED
|
@@ -43,8 +43,26 @@ export function Btn({ href = '#', variant = 'default', children, onClick, 'aria-
|
|
|
43
43
|
}, children);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export function
|
|
47
|
-
|
|
46
|
+
export function IconButton({ icon, onClick, title, size = 'base', variant = 'ghost', disabled = false }) {
|
|
47
|
+
const cls = 'ds-icon-btn ds-icon-btn-' + variant + ' ds-icon-btn-' + size + (disabled ? ' is-disabled' : '');
|
|
48
|
+
return h('button', {
|
|
49
|
+
type: 'button',
|
|
50
|
+
class: cls,
|
|
51
|
+
title,
|
|
52
|
+
'aria-label': title,
|
|
53
|
+
disabled: disabled ? true : null,
|
|
54
|
+
onclick: (e) => { if (disabled) { e.preventDefault(); return; } if (onClick) onClick(e); }
|
|
55
|
+
}, Glyph({ children: icon, size }));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Badge({ children, variant = 'default', tone = 'neutral' }) {
|
|
59
|
+
return h('span', { class: 'ds-badge ds-badge-' + variant + ' tone-' + tone }, children);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function Glyph({ children, color, size = 'base' }) {
|
|
63
|
+
const fontSize = size === 'sm' ? '11px' : (size === 'lg' ? '16px' : '13px');
|
|
64
|
+
const style = `font-size:${fontSize}` + (color ? `;color:${color}` : '');
|
|
65
|
+
return h('span', { class: 'glyph', style }, children);
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
export function Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search } = {}) {
|
|
@@ -113,23 +131,40 @@ export function Status({ left = [], right = [] } = {}) {
|
|
|
113
131
|
);
|
|
114
132
|
}
|
|
115
133
|
|
|
134
|
+
// Toggle the mobile sidebar drawer. Pure-DOM because AppShell is stateless
|
|
135
|
+
// chrome; the class lives on .app-body and is read by the ≤900px media query.
|
|
136
|
+
function toggleSide(open) {
|
|
137
|
+
const body = document.querySelector('.app-body');
|
|
138
|
+
if (!body) return;
|
|
139
|
+
const next = open != null ? open : !body.classList.contains('side-open');
|
|
140
|
+
body.classList.toggle('side-open', next);
|
|
141
|
+
const btn = document.querySelector('.app-side-toggle');
|
|
142
|
+
if (btn) btn.setAttribute('aria-expanded', next ? 'true' : 'false');
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
|
|
117
146
|
const hasSide = Boolean(side);
|
|
118
147
|
const sideNode = hasSide ? side : h('aside', { class: 'app-side', 'aria-hidden': 'true' });
|
|
119
148
|
return h('div', { class: 'app' },
|
|
120
149
|
h('a', { href: '#app-main', class: 'skip-link' }, 'skip to main content'),
|
|
150
|
+
hasSide ? h('button', {
|
|
151
|
+
class: 'app-side-toggle', type: 'button',
|
|
152
|
+
'aria-label': 'toggle navigation', 'aria-expanded': 'false', 'aria-controls': 'app-main',
|
|
153
|
+
onclick: () => toggleSide(),
|
|
154
|
+
}, '☰') : null,
|
|
121
155
|
topbar || null,
|
|
122
156
|
crumb || null,
|
|
123
157
|
h('div', { class: 'app-body' + (hasSide ? '' : ' no-side') },
|
|
124
|
-
h('div', { class: 'app-side-
|
|
158
|
+
h('div', { class: 'app-side-scrim', 'aria-hidden': 'true', onclick: () => toggleSide(false) }),
|
|
159
|
+
h('div', { class: 'app-side-shell', onclick: (e) => { if (e.target.closest('a')) toggleSide(false); } }, sideNode),
|
|
125
160
|
h('main', { class: 'app-main' + (narrow ? ' narrow' : ''), id: 'app-main' }, ...(Array.isArray(main) ? main : [main]))
|
|
126
161
|
),
|
|
127
162
|
status || null
|
|
128
163
|
);
|
|
129
164
|
}
|
|
130
165
|
|
|
131
|
-
export function Heading({ level = 1, children, style = '' }) {
|
|
132
|
-
return h('h' + level, { style }, children);
|
|
166
|
+
export function Heading({ level = 1, children, style = '', 'aria-level': ariaLevel }) {
|
|
167
|
+
return h('h' + level, { style, 'aria-level': ariaLevel != null ? String(ariaLevel) : null }, children);
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
export function Lede({ children }) {
|
package/src/components/voice.js
CHANGED
|
@@ -192,6 +192,31 @@ export function VoiceSettingsModal({ open = false, mode = 'ptt', inputId, output
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
export function VoiceControls({ muted = false, deafened = false, cameraOn = false, screenShareOn = false, onMic, onDeafen, onCamera, onScreenShare, onSettings, onLeave } = {}) {
|
|
196
|
+
const btn = (cls, on, label, glyph, handler) => h('button', {
|
|
197
|
+
type: 'button',
|
|
198
|
+
class: 'vx-vc-btn ' + cls + (on ? ' vx-vc-on' : '') + (handler ? '' : ' vx-vc-disabled'),
|
|
199
|
+
'aria-pressed': on ? 'true' : 'false',
|
|
200
|
+
'aria-label': label,
|
|
201
|
+
title: label,
|
|
202
|
+
disabled: handler ? null : true,
|
|
203
|
+
onclick: handler ? (e) => handler(e) : null
|
|
204
|
+
},
|
|
205
|
+
h('span', { class: 'vx-vc-glyph', 'aria-hidden': 'true' }, glyph)
|
|
206
|
+
);
|
|
207
|
+
return h('div', { class: 'vx-vc', role: 'toolbar', 'aria-label': 'voice controls' },
|
|
208
|
+
btn('vx-vc-mic', !muted, muted ? 'Unmute' : 'Mute', muted ? '🔇' : '🎙', onMic),
|
|
209
|
+
btn('vx-vc-deafen', !deafened, deafened ? 'Undeafen' : 'Deafen', deafened ? '🔕' : '🔊', onDeafen),
|
|
210
|
+
btn('vx-vc-camera', cameraOn, cameraOn ? 'Stop camera' : 'Start camera', '📷', onCamera),
|
|
211
|
+
btn('vx-vc-screen', screenShareOn, screenShareOn ? 'Stop sharing' : 'Share screen', '🖥', onScreenShare),
|
|
212
|
+
btn('vx-vc-settings', false, 'Voice settings', '⚙', onSettings),
|
|
213
|
+
h('button', {
|
|
214
|
+
type: 'button', class: 'vx-vc-btn vx-vc-leave', 'aria-label': 'Leave voice', title: 'Leave voice',
|
|
215
|
+
onclick: onLeave ? (e) => onLeave(e) : null
|
|
216
|
+
}, h('span', { class: 'vx-vc-glyph', 'aria-hidden': 'true' }, '📞'))
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
195
220
|
export function AudioQueue({ segments = [], currentSegmentId = null, paused = false, onReplay, onSkip, onResume, onPause } = {}) {
|
|
196
221
|
if (!segments || !segments.length) {
|
|
197
222
|
return h('div', { class: 'vx-queue vx-queue-empty' },
|
package/src/components.js
CHANGED
|
@@ -4,13 +4,13 @@ import * as webjsx from '../vendor/webjsx/index.js';
|
|
|
4
4
|
export const h = webjsx.createElement;
|
|
5
5
|
|
|
6
6
|
export {
|
|
7
|
-
Brand, Chip, Btn, Glyph,
|
|
7
|
+
Brand, Chip, Btn, Glyph, IconButton, Badge,
|
|
8
8
|
Topbar, Crumb, Side, Status, AppShell,
|
|
9
9
|
Heading, Lede, Dot, Rail
|
|
10
10
|
} from './components/shell.js';
|
|
11
11
|
|
|
12
12
|
export {
|
|
13
|
-
Panel, Row, RowLink,
|
|
13
|
+
Panel, Card, Row, RowLink,
|
|
14
14
|
Hero, Install, Receipt, Changelog,
|
|
15
15
|
WorksList, WritingList, Manifesto, Section, PageHeader,
|
|
16
16
|
Kpi, Table, SearchInput, TextField, Select, EventList,
|
|
@@ -41,11 +41,12 @@ export {
|
|
|
41
41
|
VoiceUser, UserPanel, ChannelSidebar,
|
|
42
42
|
MemberItem, MemberList,
|
|
43
43
|
ChatHeader, VoiceStrip, CommunityShell,
|
|
44
|
-
MobileHeader, ReplyBar, Banner
|
|
44
|
+
MobileHeader, ReplyBar, Banner,
|
|
45
|
+
ThreadPanel, ForumView, PageView
|
|
45
46
|
} from './components/community.js';
|
|
46
47
|
|
|
47
48
|
export {
|
|
48
|
-
PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue
|
|
49
|
+
PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue, VoiceControls
|
|
49
50
|
} from './components/voice.js';
|
|
50
51
|
|
|
51
52
|
export { ThemeToggle } from './components/theme-toggle.js';
|
|
@@ -75,7 +76,8 @@ export {
|
|
|
75
76
|
|
|
76
77
|
export {
|
|
77
78
|
Tooltip, Popover, Dropdown, useLongPress, useFloating,
|
|
78
|
-
CommandPalette, EmojiPicker, BootOverlay, SettingsPopover
|
|
79
|
+
CommandPalette, EmojiPicker, BootOverlay, SettingsPopover,
|
|
80
|
+
AuthModal, VideoLightbox
|
|
79
81
|
} from './components/overlay-primitives.js';
|
|
80
82
|
|
|
81
83
|
export {
|
|
@@ -134,7 +134,7 @@ export function makeChatPage(ctx) {
|
|
|
134
134
|
? Panel({ title: 'no providers configured', children: Receipt({ rows: [
|
|
135
135
|
['set API key', 'go to keys tab, click a provider chip to set its key'],
|
|
136
136
|
['then reload', 'refresh this page to see providers here'],
|
|
137
|
-
['or use
|
|
137
|
+
['or use a gateway', 'run a gateway server on localhost:4800 for local LLMs'],
|
|
138
138
|
] }) })
|
|
139
139
|
: Panel({ title: 'configured providers', children: h('div', { class: 'fd-chip-wrap' },
|
|
140
140
|
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.name + (p.configured ? (p.available ? ' ●' : ' ○') : '') }))) }),
|
|
@@ -45,7 +45,7 @@ export function makeCorePages(ctx) {
|
|
|
45
45
|
const skills = h0.pi.skills.size;
|
|
46
46
|
const health = (typeof h0.pi.health === 'function') ? h0.pi.health() : { ok: true };
|
|
47
47
|
return [
|
|
48
|
-
Hero({ title: '
|
|
48
|
+
Hero({ title: 'assistant', body: 'open js agent harness — in-page agent runtime.', accent: h0.version || 'web' }),
|
|
49
49
|
Kpi({ items: [[sessions.length, 'sessions'], [tools, 'tools'], [skills, 'skills']] }),
|
|
50
50
|
Panel({ title: 'quick start', children: Receipt({ rows: [
|
|
51
51
|
['open chat', "click 'chat' in sidebar — set a working directory and pick a skill"],
|
|
@@ -42,7 +42,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces, loading
|
|
|
42
42
|
|
|
43
43
|
function buildSide() {
|
|
44
44
|
const sections = [{
|
|
45
|
-
group: '
|
|
45
|
+
group: 'ASSISTANT',
|
|
46
46
|
items: ROUTES.map(r => ({
|
|
47
47
|
glyph: r.glyph, label: r.label, href: '#fd-' + r.path,
|
|
48
48
|
active: state.active === r.path,
|
|
@@ -63,8 +63,8 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces, loading
|
|
|
63
63
|
function view() {
|
|
64
64
|
const route = allRoutes.find(r => r.path === state.active) || ROUTES[1];
|
|
65
65
|
return AppShell({
|
|
66
|
-
topbar: Topbar({ brand: '
|
|
67
|
-
crumb: Crumb({ trail: ['
|
|
66
|
+
topbar: Topbar({ brand: 'assistant', leaf: 'dashboard', items: [], active: '' }),
|
|
67
|
+
crumb: Crumb({ trail: ['assistant', instance.id], leaf: route.path, right: state.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: 'ok', children: 'live' }) }),
|
|
68
68
|
side: buildSide(),
|
|
69
69
|
main: state.body || EmptyState({ text: loadingText || 'loading…', glyph: '◌' }),
|
|
70
70
|
status: Status({ left: ['ds-247420 · webjsx · ' + allRoutes.length + ' routes', 'instance=' + instance.id], right: [state.ts] }),
|
package/src/kits/os/shell.js
CHANGED
|
@@ -39,6 +39,8 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
|
|
|
39
39
|
|
|
40
40
|
const menubar = document.createElement('div');
|
|
41
41
|
menubar.className = 'os-menubar';
|
|
42
|
+
menubar.setAttribute('role', 'menubar');
|
|
43
|
+
menubar.setAttribute('aria-label', 'Desktop menu bar');
|
|
42
44
|
|
|
43
45
|
const homeBtn = makeBtn(icons.home, '', 'home');
|
|
44
46
|
homeBtn.title = 'apps';
|