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/app-shell.css +15 -0
- package/dist/247420.app.js +4 -4
- package/dist/247420.css +15 -0
- package/dist/247420.js +33 -33
- package/package.json +1 -1
- package/src/components/freddie/pages-voice.js +108 -0
- package/src/components/freddie.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
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
|
-
|
|
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 };
|