anentrypoint-design 0.0.86 → 0.0.87

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.86",
3
+ "version": "0.0.87",
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",
@@ -0,0 +1,108 @@
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
+ const h = webjsx.createElement;
6
+
7
+ function getState() {
8
+ return window.__fd_voice = window.__fd_voice || {
9
+ listening: false, supported: null, transcript: [], partial: '',
10
+ ttsText: '', ttsBusy: false, ttsErr: null, voice: 'alloy', recogn: null
11
+ };
12
+ }
13
+
14
+ function rerender() { if (typeof window.__fd_nav === 'function') window.__fd_nav('voice'); }
15
+
16
+ function ensureRecognizer(s) {
17
+ if (s.recogn) return s.recogn;
18
+ const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
19
+ if (!SR) { s.supported = false; return null; }
20
+ s.supported = true;
21
+ const r = new SR();
22
+ r.continuous = true; r.interimResults = true; r.lang = 'en-US';
23
+ r.onresult = ev => {
24
+ let finals = '', partial = '';
25
+ for (let i = ev.resultIndex; i < ev.results.length; i++) {
26
+ const t = ev.results[i][0].transcript;
27
+ if (ev.results[i].isFinal) finals += t; else partial += t;
28
+ }
29
+ if (finals) s.transcript.push({ text: finals.trim(), ts: Date.now() });
30
+ s.partial = partial.trim();
31
+ rerender();
32
+ };
33
+ r.onend = () => { if (s.listening) { try { r.start(); } catch {} } };
34
+ r.onerror = e => { s.partial = '(' + (e.error || 'mic error') + ')'; rerender(); };
35
+ s.recogn = r; return r;
36
+ }
37
+
38
+ function toggleListen(s) {
39
+ const r = ensureRecognizer(s);
40
+ if (!r) return;
41
+ s.listening = !s.listening;
42
+ if (s.listening) { try { r.start(); } catch {} } else { try { r.stop(); } catch {} }
43
+ rerender();
44
+ }
45
+
46
+ async function speakText(s) {
47
+ const text = s.ttsText.trim(); if (!text) return;
48
+ s.ttsBusy = true; s.ttsErr = null; rerender();
49
+ try {
50
+ if ('speechSynthesis' in window) {
51
+ const u = new SpeechSynthesisUtterance(text);
52
+ u.onend = () => { s.ttsBusy = false; rerender(); };
53
+ u.onerror = e => { s.ttsBusy = false; s.ttsErr = String(e.error || 'tts error'); rerender(); };
54
+ window.speechSynthesis.speak(u);
55
+ } else { s.ttsBusy = false; s.ttsErr = 'no speechSynthesis API in this browser'; rerender(); }
56
+ } catch (e) { s.ttsBusy = false; s.ttsErr = e.message || String(e); rerender(); }
57
+ }
58
+
59
+ function clearLog(s) { s.transcript = []; s.partial = ''; rerender(); }
60
+
61
+ export async function voice(h0) {
62
+ const s = getState();
63
+ const supported = ('SpeechRecognition' in window) || ('webkitSpeechRecognition' in window);
64
+ const ttsSupported = 'speechSynthesis' in window;
65
+ const lines = s.transcript.slice(-50);
66
+ const tones = { listening: 'ok', idle: 'neutral', off: 'miss' };
67
+
68
+ const micPanel = !supported
69
+ ? Panel({ title: 'microphone', children: EmptyState({ text: 'this browser does not expose Web Speech API. try chrome or edge.', glyph: '⏚' }) })
70
+ : Panel({ title: 'microphone', right: Chip({ tone: s.listening ? tones.listening : tones.idle, children: s.listening ? 'listening ●' : 'idle ○' }),
71
+ children: [
72
+ h('div', { class: 'fd-voice-controls' },
73
+ h('button', { class: s.listening ? 'btn-primary' : 'btn-primary', 'aria-label': s.listening ? 'stop listening' : 'start listening', onclick: ev => { ev.preventDefault(); toggleListen(s); } }, s.listening ? 'stop' : 'start'),
74
+ h('button', { class: 'btn', 'aria-label': 'clear transcript', onclick: ev => { ev.preventDefault(); clearLog(s); }, disabled: lines.length === 0 ? 'true' : null }, 'clear'),
75
+ s.partial ? h('span', { class: 'fd-voice-partial' }, '… ' + s.partial) : null
76
+ ),
77
+ lines.length === 0 && !s.partial
78
+ ? EmptyState({ text: s.listening ? 'listening — speak into your mic' : 'press start to capture speech', glyph: '◌' })
79
+ : h('div', { class: 'fd-voice-log', role: 'log', 'aria-live': 'polite' },
80
+ ...lines.map((it, i) => h('div', { key: i, class: 'fd-voice-line' },
81
+ h('span', { class: 'fd-voice-ts' }, new Date(it.ts).toLocaleTimeString()),
82
+ h('span', { class: 'fd-voice-text' }, it.text))))
83
+ ] });
84
+
85
+ const ttsPanel = !ttsSupported
86
+ ? Panel({ title: 'speak', children: EmptyState({ text: 'no speechSynthesis API in this browser.', glyph: '⏚' }) })
87
+ : Panel({ title: 'speak', right: s.ttsErr ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: s.ttsBusy ? 'warn' : 'neutral', children: s.ttsBusy ? 'speaking' : 'idle' }),
88
+ children: h('form', { class: 'fd-voice-tts', onsubmit: ev => { ev.preventDefault(); speakText(s); } },
89
+ h('label', { class: 'fd-label', for: 'fd-tts-text' }, 'text to speak'),
90
+ h('textarea', { id: 'fd-tts-text', name: 'tts', rows: 3, placeholder: 'type something…', value: s.ttsText, oninput: ev => { s.ttsText = ev.target.value; } }),
91
+ h('div', { class: 'fd-voice-controls' },
92
+ h('button', { type: 'submit', class: 'btn-primary', disabled: s.ttsBusy ? 'true' : null }, s.ttsBusy ? '…' : 'speak'),
93
+ s.ttsErr ? h('span', { class: 'fd-muted' }, s.ttsErr) : null
94
+ )) });
95
+
96
+ return [
97
+ Hero({ title: 'voice', body: 'speak in, hear out. browser-native speech recognition + synthesis.', accent: s.listening ? 'listening' : (lines.length ? lines.length+' utterances' : 'idle') }),
98
+ Kpi({ items: [[lines.length,'utterances'],[supported ? '●' : '○','recognition'],[ttsSupported ? '●' : '○','synthesis']] }),
99
+ micPanel,
100
+ ttsPanel,
101
+ Panel({ title: 'how this works', children: Receipt({ rows: [
102
+ ['recognition', 'webkit Speech API · client-side, no server'],
103
+ ['synthesis', 'speechSynthesis API · client-side, OS voice'],
104
+ ['server tts tool', 'plugins/tts (OpenAI tts-1 / ElevenLabs) · runs in agent context'],
105
+ ['voice_mode tool', 'plugins/voice_mode · toggles full-duplex on a session']
106
+ ] }) })
107
+ ];
108
+ }
@@ -3,9 +3,11 @@ export { home, sessions, projects, agents, analytics } from './freddie/pages-cor
3
3
  export { chat } from './freddie/pages-chat.js';
4
4
  export { models, cron, skills, env, tools, batch, gateway } from './freddie/pages-config.js';
5
5
  export { config } from './freddie/pages-config-edit.js';
6
+ export { voice } from './freddie/pages-voice.js';
6
7
 
7
8
  import { home, sessions, projects, agents, analytics } from './freddie/pages-core.js';
8
9
  import { chat } from './freddie/pages-chat.js';
9
10
  import { models, cron, skills, env, tools, batch, gateway } from './freddie/pages-config.js';
10
11
  import { config } from './freddie/pages-config-edit.js';
11
- export const FREDDIE_PAGES = { home, chat, sessions, projects, agents, analytics, models, cron, skills, config, env, tools, batch, gateway };
12
+ import { voice } from './freddie/pages-voice.js';
13
+ export const FREDDIE_PAGES = { home, chat, voice, sessions, projects, agents, analytics, models, cron, skills, config, env, tools, batch, gateway };