arcane-os 0.7.0 → 0.7.2
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/CHANGELOG.md +23 -0
- package/NOTICE +6 -11
- package/README.md +8 -8
- package/browser-runtime/ai/browser-device-settings.mjs +24 -20
- package/browser-runtime/ai/browser-speech-artifacts.mjs +86 -0
- package/browser-runtime/ai/browser-speech-providers.mjs +71 -7
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +15 -12
- package/browser-runtime/ai/browser-wllama-runtime.mjs +41 -2
- package/browser-runtime/ai/speech-worker-client.mjs +10 -2
- package/browser-runtime/ai/speech-worker-runtime.mjs +67 -3
- package/browser-runtime/ai/wllama/index.mjs +9 -9
- package/docs/architecture.md +1 -1
- package/docs/reference/ai/browser-speech.md +33 -15
- package/docs/reference/ai/browser-wasm.md +36 -28
- package/docs/reference/inventory/runtime-components.json +4 -4
- package/docs/reference/protocols.md +12 -5
- package/docs/reference/runtime-components.md +33 -22
- package/docs/reference/runtime-modules.md +7 -1
- package/docs/reference/sdk-api.md +5 -3
- package/package.json +1 -1
- package/runtime/arcane/components/browser-ai-setup.html +189 -179
- package/runtime/arcane/components/speech.html +34 -3
- package/runtime/arcane/components/voice-transcription.html +30 -3
- package/runtime/arcane/modules/ComponentContracts.js +86 -15
|
@@ -1,18 +1,46 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>
|
|
3
3
|
:host{display:block;min-width:0}
|
|
4
|
-
.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.flag-choice{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
.flag-choice {
|
|
5
|
+
margin-block: var(--arcane-space-2);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.flag-choice h4 {
|
|
9
|
+
margin-block: var(--arcane-space-1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
p,
|
|
13
|
+
button,
|
|
14
|
+
a {
|
|
15
|
+
overflow-wrap: anywhere;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
button {
|
|
19
|
+
white-space: normal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.arcane-help:empty {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.gpu-status {
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
max-width: 100%;
|
|
29
|
+
line-height: 1.4;
|
|
30
|
+
white-space: normal;
|
|
31
|
+
overflow-wrap: anywhere;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.gpu-status[data-status="unknown"] {
|
|
35
|
+
background: var(--arcane-action);
|
|
36
|
+
border-color: var(--arcane-action);
|
|
37
|
+
color: var(--arcane-action-text);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
summary:focus-visible {
|
|
41
|
+
outline: .18em solid var(--arcane-focus);
|
|
42
|
+
outline-offset: .18em;
|
|
43
|
+
}
|
|
16
44
|
</style>
|
|
17
45
|
|
|
18
46
|
<section class="arcane-card" aria-labelledby="setupTitle">
|
|
@@ -20,37 +48,28 @@
|
|
|
20
48
|
<h2 id="setupTitle" class="arcane-section-heading">Browser AI acceleration</h2>
|
|
21
49
|
</header>
|
|
22
50
|
<div class="arcane-card__body">
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<h3>
|
|
30
|
-
<p id="
|
|
31
|
-
<div id="flagAddresses"></div>
|
|
32
|
-
<ol id="setupSteps"></ol>
|
|
33
|
-
<p>Browser API availability does not confirm that an NPU or a particular model is supported. You choose whether to change the browser setting.</p>
|
|
34
|
-
<p><a href="https://onnxruntime.ai/docs/tutorials/web/ep-webnn.html">ONNX Runtime WebNN setup documentation</a></p>
|
|
35
|
-
<h3>GPU performance</h3>
|
|
36
|
-
<p><span id="gpuAvailability" class="arcane-badge" role="status" aria-live="polite"></span></p>
|
|
51
|
+
<h3>NPU</h3>
|
|
52
|
+
<p id="webnnAvailability" role="status" aria-live="polite"></p>
|
|
53
|
+
<div id="npuSetup">
|
|
54
|
+
<p id="browserInstructions"></p>
|
|
55
|
+
<div id="flagAddresses"></div>
|
|
56
|
+
</div>
|
|
57
|
+
<h3>GPU</h3>
|
|
58
|
+
<p><span id="gpuAvailability" class="arcane-badge gpu-status" role="status" aria-live="polite"></span></p>
|
|
37
59
|
<p id="gpuAdapterName" class="arcane-help"></p>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<a id="gpuFlagsLink" class="flag-address"></a>
|
|
42
|
-
<button id="copyGpuAddress" class="arcane-button arcane-button--secondary" type="button">Copy GPU flags address</button>
|
|
43
|
-
<ol>
|
|
44
|
-
<li>Open the address above. If the browser blocks the link, copy the address and paste it into the address bar.</li>
|
|
45
|
-
<li>Find “Force High Performance GPU” and choose Enabled.</li>
|
|
46
|
-
<li>Save your work, then completely close and reopen the browser before loading the model again.</li>
|
|
47
|
-
</ol>
|
|
60
|
+
<div id="gpuSetup" hidden>
|
|
61
|
+
<p>“Force High Performance GPU” prefers your faster GPU.</p>
|
|
62
|
+
<button id="copyGpuAddress" class="arcane-button arcane-button--secondary" type="button">Copy GPU flag address</button>
|
|
48
63
|
</div>
|
|
49
|
-
<p
|
|
64
|
+
<p>Paste a copied address into your browser's address bar. Enable the flag, save your work, and relaunch.</p>
|
|
65
|
+
<details>
|
|
66
|
+
<summary>Setup help</summary>
|
|
67
|
+
<p><a href="https://onnxruntime.ai/docs/tutorials/web/ep-webnn.html">WebNN setup documentation</a></p>
|
|
68
|
+
</details>
|
|
50
69
|
<p id="copyStatus" class="arcane-help" role="status" aria-live="polite"></p>
|
|
51
70
|
</div>
|
|
52
71
|
<footer class="arcane-card__footer">
|
|
53
|
-
<button id="refreshAvailability" class="arcane-button arcane-button--secondary" type="button">Refresh
|
|
72
|
+
<button id="refreshAvailability" class="arcane-button arcane-button--secondary" type="button">Refresh</button>
|
|
54
73
|
<p id="setupStatus" class="arcane-help" role="status" aria-live="polite"></p>
|
|
55
74
|
</footer>
|
|
56
75
|
</section>
|
|
@@ -63,9 +82,8 @@
|
|
|
63
82
|
const {
|
|
64
83
|
getBrowserDeviceSettings,
|
|
65
84
|
detectBrowserGpu,
|
|
66
|
-
openBrowserDeviceSettings,
|
|
67
85
|
WEBNN_BROWSER_SETTINGS
|
|
68
|
-
}=await import('../sdk/ai/browser-device-settings.mjs');
|
|
86
|
+
} = await import('../sdk/ai/browser-device-settings.mjs');
|
|
69
87
|
const {arcaneLogging}=await import('arcane-os/logging');
|
|
70
88
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
71
89
|
const events=createArcaneEventSource(host,{
|
|
@@ -77,18 +95,12 @@
|
|
|
77
95
|
const copyStatus=root.querySelector('#copyStatus');
|
|
78
96
|
const gpuAvailability=root.querySelector('#gpuAvailability');
|
|
79
97
|
const gpuAdapterName=root.querySelector('#gpuAdapterName');
|
|
80
|
-
const
|
|
81
|
-
const gpuFlagsLink=root.querySelector('#gpuFlagsLink');
|
|
98
|
+
const refreshButton = root.querySelector('#refreshAvailability');
|
|
82
99
|
const copyGpuButton=root.querySelector('#copyGpuAddress');
|
|
83
100
|
const copyButtons=[copyGpuButton];
|
|
84
101
|
const choices=[];
|
|
85
|
-
const setupSteps=[
|
|
86
|
-
'If the flags page does not open, copy the matching address into your browser\'s address bar and open it.',
|
|
87
|
-
'Find “Enables WebNN API” and choose Enabled.',
|
|
88
|
-
'Save your work, then use the browser\'s Relaunch control.',
|
|
89
|
-
'Return to this page and select Refresh browser availability.'
|
|
90
|
-
];
|
|
91
102
|
let copyTask=null;
|
|
103
|
+
let copyingAddress = null;
|
|
92
104
|
let gpuTask=null;
|
|
93
105
|
let destroyed=false;
|
|
94
106
|
|
|
@@ -97,152 +109,154 @@
|
|
|
97
109
|
host.open=open;
|
|
98
110
|
host.destroy=destroy;
|
|
99
111
|
|
|
100
|
-
function refresh(){
|
|
101
|
-
if(destroyed)return false;
|
|
102
|
-
const settings=getBrowserDeviceSettings();
|
|
103
|
-
root.querySelector('#
|
|
104
|
-
?'
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
?`Open the ${settings.name} WebNN flags link below to set up NPU access.`
|
|
114
|
-
:'A matching WebNN flags address is not known for this browser. Choose Google Chrome or Microsoft Edge below if that is the browser you are using.';
|
|
115
|
-
for(const choice of choices){
|
|
116
|
-
choice.row.hidden=Boolean(settings.webnnFlagsURL)
|
|
117
|
-
&&choice.target.url!==settings.webnnFlagsURL;
|
|
118
|
-
}
|
|
119
|
-
const gpuTarget=settings.highPerformanceGpu;
|
|
120
|
-
root.querySelector('#gpuFlagAddress').hidden=!gpuTarget;
|
|
121
|
-
root.querySelector('#gpuInstructions').textContent=gpuTarget
|
|
122
|
-
?`If this computer has multiple GPUs, use the ${gpuTarget.name} flags link to request the high-performance GPU when available.`
|
|
123
|
-
:'The Force High Performance GPU flag is documented here for desktop Windows Chromium browsers. No matching flags link is known for this browser and platform.';
|
|
124
|
-
if(gpuTarget){
|
|
125
|
-
gpuFlagsLink.href=gpuTarget.url;
|
|
126
|
-
gpuFlagsLink.textContent=gpuTarget.url;
|
|
127
|
-
gpuFlagsLink.setAttribute('aria-label',`${gpuTarget.name} Force High Performance GPU flags address: ${gpuTarget.url}`);
|
|
128
|
-
}else{
|
|
129
|
-
gpuFlagsLink.removeAttribute('href');
|
|
130
|
-
gpuFlagsLink.textContent='';
|
|
112
|
+
function refresh() {
|
|
113
|
+
if (destroyed) return false;
|
|
114
|
+
const settings = getBrowserDeviceSettings();
|
|
115
|
+
root.querySelector('#webnnAvailability').textContent = settings.webnnAvailable
|
|
116
|
+
? 'WebNN available.'
|
|
117
|
+
: 'WebNN unavailable.';
|
|
118
|
+
const unknownBrowser = settings.browserId === 'unknown';
|
|
119
|
+
root.querySelector('#browserInstructions').textContent = settings.webnnFlagsURL || unknownBrowser
|
|
120
|
+
? '“Enables WebNN API” allows NPU use when supported.'
|
|
121
|
+
: 'See Setup help for this browser.';
|
|
122
|
+
for (const choice of choices) {
|
|
123
|
+
choice.row.hidden = !unknownBrowser && choice.target.url !== settings.webnnFlagsURL;
|
|
124
|
+
choice.heading.hidden = !unknownBrowser;
|
|
131
125
|
}
|
|
126
|
+
renderGpuGuidance(settings);
|
|
132
127
|
return settings;
|
|
133
128
|
}
|
|
134
129
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
130
|
+
function renderGpuGuidance(settings = getBrowserDeviceSettings()) {
|
|
131
|
+
root.querySelector('#gpuSetup').hidden = !settings.highPerformanceGpu;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function checkGpu() {
|
|
135
|
+
if (destroyed) return Promise.resolve(false);
|
|
136
|
+
if (gpuTask) return gpuTask;
|
|
137
|
+
gpuAvailability.textContent = 'Detecting GPU...';
|
|
138
|
+
gpuAvailability.dataset.status = 'pending';
|
|
139
|
+
gpuAdapterName.textContent = '';
|
|
140
|
+
refreshButton.disabled = true;
|
|
141
|
+
gpuTask = detect().finally(
|
|
142
|
+
function finishGpuDetection() {
|
|
143
|
+
gpuTask = null;
|
|
144
|
+
if (!destroyed) refreshButton.disabled = false;
|
|
145
|
+
}
|
|
146
|
+
);
|
|
146
147
|
return gpuTask;
|
|
147
148
|
|
|
148
|
-
async function detect(){
|
|
149
|
-
try{
|
|
150
|
-
const result=await detectBrowserGpu();
|
|
151
|
-
if(destroyed)return false;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
async function detect() {
|
|
150
|
+
try {
|
|
151
|
+
const result = await detectBrowserGpu();
|
|
152
|
+
if (destroyed) return false;
|
|
153
|
+
if (!result.available) {
|
|
154
|
+
gpuAvailability.textContent = 'GPU unavailable.';
|
|
155
|
+
gpuAvailability.dataset.status = 'warning';
|
|
156
|
+
} else if (result.performanceStatus === 'discrete') {
|
|
157
|
+
gpuAvailability.textContent = 'Already using the performance GPU.';
|
|
158
|
+
gpuAvailability.dataset.status = 'success';
|
|
159
|
+
} else if (result.performanceStatus === 'integrated') {
|
|
160
|
+
gpuAvailability.textContent = 'Integrated GPU available.';
|
|
161
|
+
gpuAvailability.dataset.status = 'warning';
|
|
162
|
+
} else if (result.performanceStatus === 'fallback') {
|
|
163
|
+
gpuAvailability.textContent = 'Software adapter available.';
|
|
164
|
+
gpuAvailability.dataset.status = 'warning';
|
|
165
|
+
} else {
|
|
166
|
+
gpuAvailability.textContent = 'GPU available.';
|
|
167
|
+
gpuAvailability.dataset.status = 'unknown';
|
|
168
|
+
}
|
|
169
|
+
gpuAdapterName.textContent = result.available ? result.name : '';
|
|
160
170
|
return result;
|
|
161
|
-
}catch(error){
|
|
162
|
-
if(!destroyed){
|
|
163
|
-
gpuAvailability.textContent='GPU detection failed. Select
|
|
164
|
-
gpuAvailability.dataset.status='error';
|
|
165
|
-
arcaneLogging.error('Unable to detect the browser GPU adapter.',error);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (!destroyed) {
|
|
173
|
+
gpuAvailability.textContent = 'GPU detection failed. Select Refresh to retry.';
|
|
174
|
+
gpuAvailability.dataset.status = 'error';
|
|
175
|
+
arcaneLogging.error('Unable to detect the browser GPU adapter.', error);
|
|
166
176
|
}
|
|
167
177
|
return false;
|
|
168
178
|
}
|
|
169
179
|
}
|
|
170
180
|
}
|
|
171
181
|
|
|
172
|
-
function open(browserId){
|
|
173
|
-
if(destroyed)return false;
|
|
174
|
-
const current=refresh();
|
|
175
|
-
const selectedId=browserId??current.browserId;
|
|
176
|
-
const target=WEBNN_BROWSER_SETTINGS.find(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
status.textContent='Choose the Google Chrome or Microsoft Edge flags link that matches your browser.';
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
const instructions=setupSteps.map(function numberSetupStep(step,index){
|
|
184
|
-
return `${index+1}. ${step}`;
|
|
185
|
-
}).join('\n');
|
|
186
|
-
openBrowserDeviceSettings(
|
|
187
|
-
target,
|
|
188
|
-
`NPU setup for ${target.name}\n\n${instructions}\n\n${target.url}`
|
|
182
|
+
function open(browserId) {
|
|
183
|
+
if (destroyed) return Promise.resolve(false);
|
|
184
|
+
const current = refresh();
|
|
185
|
+
const selectedId = browserId ?? current.browserId;
|
|
186
|
+
const target = WEBNN_BROWSER_SETTINGS.find(
|
|
187
|
+
function matchSetupBrowser(browser) {
|
|
188
|
+
return browser.browserId === selectedId;
|
|
189
|
+
}
|
|
189
190
|
);
|
|
190
|
-
|
|
191
|
+
if (!target) {
|
|
192
|
+
status.textContent = current.browserId === 'unknown'
|
|
193
|
+
? 'Choose Chrome or Edge to copy its NPU flag address.'
|
|
194
|
+
: 'No NPU flag address for this browser.';
|
|
195
|
+
return Promise.resolve(false);
|
|
196
|
+
}
|
|
197
|
+
return copyAddress(target.url);
|
|
191
198
|
}
|
|
192
199
|
|
|
193
|
-
function copyAddress(address){
|
|
194
|
-
if(destroyed)return Promise.resolve(false);
|
|
195
|
-
if(copyTask)return copyTask;
|
|
196
|
-
copyStatus.textContent='Copying address...';
|
|
197
|
-
for(const button of copyButtons)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
function copyAddress(address) {
|
|
201
|
+
if (destroyed) return Promise.resolve(false);
|
|
202
|
+
if (copyTask) return copyingAddress === address ? copyTask : Promise.resolve(false);
|
|
203
|
+
copyStatus.textContent = 'Copying address...';
|
|
204
|
+
for (const button of copyButtons) {
|
|
205
|
+
button.disabled = true;
|
|
206
|
+
}
|
|
207
|
+
copyingAddress = address;
|
|
208
|
+
copyTask = writeAddress().finally(
|
|
209
|
+
function finishAddressCopy() {
|
|
210
|
+
copyTask = null;
|
|
211
|
+
copyingAddress = null;
|
|
212
|
+
if (!destroyed) {
|
|
213
|
+
for (const button of copyButtons) {
|
|
214
|
+
button.disabled = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
202
217
|
}
|
|
203
|
-
|
|
218
|
+
);
|
|
204
219
|
return copyTask;
|
|
205
220
|
|
|
206
|
-
async function writeAddress(){
|
|
207
|
-
try{
|
|
221
|
+
async function writeAddress() {
|
|
222
|
+
try {
|
|
208
223
|
await globalThis.navigator.clipboard.writeText(address);
|
|
209
|
-
if(destroyed)return false;
|
|
210
|
-
copyStatus.textContent='Address copied. Paste it into your browser\'s address bar.';
|
|
224
|
+
if (destroyed) return false;
|
|
225
|
+
copyStatus.textContent = 'Address copied. Paste it into your browser\'s address bar.';
|
|
211
226
|
return true;
|
|
212
|
-
}catch(error){
|
|
213
|
-
if(!destroyed){
|
|
214
|
-
copyStatus.textContent=
|
|
215
|
-
arcaneLogging.error('Unable to copy the browser flags address.',error);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (!destroyed) {
|
|
229
|
+
copyStatus.textContent = `Copy failed. Paste this address into your browser's address bar: ${address}`;
|
|
230
|
+
arcaneLogging.error('Unable to copy the browser flags address.', error);
|
|
216
231
|
}
|
|
217
232
|
return false;
|
|
218
233
|
}
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
|
|
222
|
-
const addressList=root.querySelector('#flagAddresses');
|
|
223
|
-
for(const target of WEBNN_BROWSER_SETTINGS){
|
|
224
|
-
const row=document.createElement('section');
|
|
225
|
-
const heading=document.createElement('
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
address
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
},{signal:lifecycleController.signal});
|
|
242
|
-
actions.append(copyButton);
|
|
243
|
-
row.append(heading,address,actions);
|
|
237
|
+
const addressList = root.querySelector('#flagAddresses');
|
|
238
|
+
for (const target of WEBNN_BROWSER_SETTINGS) {
|
|
239
|
+
const row = document.createElement('section');
|
|
240
|
+
const heading = document.createElement('h4');
|
|
241
|
+
const copyButton = document.createElement('button');
|
|
242
|
+
row.className = 'flag-choice';
|
|
243
|
+
heading.textContent = target.name;
|
|
244
|
+
copyButton.type = 'button';
|
|
245
|
+
copyButton.className = 'arcane-button arcane-button--secondary';
|
|
246
|
+
copyButton.textContent = 'Copy NPU flag address';
|
|
247
|
+
copyButton.setAttribute('aria-label', `Copy NPU flag address for ${target.name}`);
|
|
248
|
+
copyButton.addEventListener(
|
|
249
|
+
'click',
|
|
250
|
+
function copySelectedBrowserAddress() {
|
|
251
|
+
return copyAddress(target.url);
|
|
252
|
+
},
|
|
253
|
+
{signal: lifecycleController.signal}
|
|
254
|
+
);
|
|
255
|
+
row.append(heading, copyButton);
|
|
244
256
|
addressList.append(row);
|
|
245
|
-
choices.push(
|
|
257
|
+
choices.push(
|
|
258
|
+
{row, heading, target}
|
|
259
|
+
);
|
|
246
260
|
copyButtons.push(copyButton);
|
|
247
261
|
}
|
|
248
262
|
|
|
@@ -250,19 +264,15 @@
|
|
|
250
264
|
const target=getBrowserDeviceSettings().highPerformanceGpu;
|
|
251
265
|
if(target)return copyAddress(target.url);
|
|
252
266
|
},{signal:lifecycleController.signal});
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
refresh();
|
|
263
|
-
status.textContent='Browser API availability refreshed.';
|
|
264
|
-
return checkGpu();
|
|
265
|
-
},{signal:lifecycleController.signal});
|
|
267
|
+
refreshButton.addEventListener(
|
|
268
|
+
'click',
|
|
269
|
+
function refreshBrowserAvailability() {
|
|
270
|
+
refresh();
|
|
271
|
+
status.textContent = '';
|
|
272
|
+
return checkGpu();
|
|
273
|
+
},
|
|
274
|
+
{signal: lifecycleController.signal}
|
|
275
|
+
);
|
|
266
276
|
host.ownerDocument.defaultView.addEventListener('pagehide',function disposeBeforePageUnload(event){
|
|
267
277
|
if(!event.persisted)destroy();
|
|
268
278
|
},{
|
|
@@ -80,9 +80,12 @@
|
|
|
80
80
|
display: none;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
.
|
|
83
|
+
.speech_feedback {
|
|
84
84
|
grid-column: 2;
|
|
85
85
|
min-width: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.speech_status {
|
|
86
89
|
margin: 0;
|
|
87
90
|
color: var(--arcane-muted, var(--text-color));
|
|
88
91
|
font-size: .86rem;
|
|
@@ -94,6 +97,18 @@
|
|
|
94
97
|
color: var(--arcane-danger, var(--danger-color));
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
.stt_activation_progress {
|
|
101
|
+
display: block;
|
|
102
|
+
width: 100%;
|
|
103
|
+
height: .6rem;
|
|
104
|
+
margin-block-start: .7rem;
|
|
105
|
+
accent-color: var(--arcane-action, var(--primary-color));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.stt_activation_progress[hidden] {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
#muteButton {
|
|
98
113
|
grid-column: 3;
|
|
99
114
|
justify-self: end;
|
|
@@ -112,7 +127,7 @@
|
|
|
112
127
|
grid-row: 1 / span 2;
|
|
113
128
|
}
|
|
114
129
|
|
|
115
|
-
.
|
|
130
|
+
.speech_feedback {
|
|
116
131
|
grid-row: 2;
|
|
117
132
|
grid-column: 2 / span 2;
|
|
118
133
|
}
|
|
@@ -128,7 +143,15 @@
|
|
|
128
143
|
<button id="recordButton" type="button" aria-label="Transcription unavailable" disabled>Hold to talk</button>
|
|
129
144
|
<button id="sttActivationButton" type="button" hidden disabled>Start transcription</button>
|
|
130
145
|
<button id="stopButton" type="button" class="hidden" disabled>Send</button>
|
|
131
|
-
<
|
|
146
|
+
<div class="speech_feedback">
|
|
147
|
+
<p id="speechStatus" class="speech_status" role="status" aria-live="polite"></p>
|
|
148
|
+
<progress
|
|
149
|
+
id="sttActivationProgress"
|
|
150
|
+
class="stt_activation_progress"
|
|
151
|
+
aria-label="Transcription model activation progress"
|
|
152
|
+
hidden
|
|
153
|
+
></progress>
|
|
154
|
+
</div>
|
|
132
155
|
<button id="muteButton" type="button" class="mute" aria-pressed="true" aria-label="Voice unavailable" disabled>Voice unavailable</button>
|
|
133
156
|
|
|
134
157
|
<script type="module">
|
|
@@ -179,6 +202,7 @@
|
|
|
179
202
|
const shadowRoot = host.shadowRoot;
|
|
180
203
|
const recordButton = shadowRoot.querySelector('#recordButton');
|
|
181
204
|
const sttActivationButton = shadowRoot.querySelector('#sttActivationButton');
|
|
205
|
+
const sttActivationProgress = shadowRoot.querySelector('#sttActivationProgress');
|
|
182
206
|
const stopButton = shadowRoot.querySelector('#stopButton');
|
|
183
207
|
const muteButton = shadowRoot.querySelector('#muteButton');
|
|
184
208
|
const speechStatus = shadowRoot.querySelector('#speechStatus');
|
|
@@ -319,6 +343,7 @@
|
|
|
319
343
|
{
|
|
320
344
|
host,
|
|
321
345
|
button: sttActivationButton,
|
|
346
|
+
progress: sttActivationProgress,
|
|
322
347
|
onChange: renderSTTActivationState,
|
|
323
348
|
eventSource: events
|
|
324
349
|
}
|
|
@@ -668,6 +693,12 @@
|
|
|
668
693
|
if (localStatus?.message) {
|
|
669
694
|
messages.push(localStatus.message);
|
|
670
695
|
tone = localStatus.tone;
|
|
696
|
+
if (sttActivationController.pending
|
|
697
|
+
|| ['loading', 'unloading'].includes(sttRole.state)) {
|
|
698
|
+
messages.push(
|
|
699
|
+
describeSTTRole()
|
|
700
|
+
);
|
|
701
|
+
}
|
|
671
702
|
} else {
|
|
672
703
|
messages.push(describeSTTRole());
|
|
673
704
|
}
|
|
@@ -42,15 +42,40 @@
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
.description,
|
|
45
|
-
.status{
|
|
46
|
-
margin:0;
|
|
45
|
+
.status {
|
|
46
|
+
margin: 0;
|
|
47
|
+
overflow-wrap: anywhere;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.transcription-feedback {
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.stt-activation-progress {
|
|
55
|
+
display: block;
|
|
56
|
+
width: 100%;
|
|
57
|
+
height: .6rem;
|
|
58
|
+
margin-block-start: .7rem;
|
|
59
|
+
accent-color: var(--arcane-action, var(--primary-color));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.stt-activation-progress[hidden] {
|
|
63
|
+
display: none;
|
|
47
64
|
}
|
|
48
65
|
</style>
|
|
49
66
|
|
|
50
67
|
<section class="voice-transcription">
|
|
51
68
|
<p id="description" class="description"></p>
|
|
52
69
|
<section id="transcript" class="transcript"></section>
|
|
53
|
-
<
|
|
70
|
+
<div class="transcription-feedback">
|
|
71
|
+
<p id="status" class="status" role="status" aria-live="polite"></p>
|
|
72
|
+
<progress
|
|
73
|
+
id="sttActivationProgress"
|
|
74
|
+
class="stt-activation-progress"
|
|
75
|
+
aria-label="Transcription model activation progress"
|
|
76
|
+
hidden
|
|
77
|
+
></progress>
|
|
78
|
+
</div>
|
|
54
79
|
<section class="controls">
|
|
55
80
|
<button id="start" type="button" disabled></button>
|
|
56
81
|
<button id="sttActivation" type="button" hidden disabled>Start transcription</button>
|
|
@@ -104,6 +129,7 @@
|
|
|
104
129
|
const status=host.shadowRoot.querySelector('#status');
|
|
105
130
|
const startButton=host.shadowRoot.querySelector('#start');
|
|
106
131
|
const sttActivationButton=host.shadowRoot.querySelector('#sttActivation');
|
|
132
|
+
const sttActivationProgress = host.shadowRoot.querySelector('#sttActivationProgress');
|
|
107
133
|
const stopButton=host.shadowRoot.querySelector('#stop');
|
|
108
134
|
const completeButton=host.shadowRoot.querySelector('#complete');
|
|
109
135
|
const runtimeStateAbortController=new AbortController();
|
|
@@ -225,6 +251,7 @@
|
|
|
225
251
|
{
|
|
226
252
|
host,
|
|
227
253
|
button:sttActivationButton,
|
|
254
|
+
progress: sttActivationProgress,
|
|
228
255
|
onChange:renderState,
|
|
229
256
|
eventSource:events
|
|
230
257
|
}
|