arcane-os 0.6.2 → 0.7.1
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 +28 -0
- package/NOTICE +6 -11
- package/README.md +25 -10
- 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 +2 -2
- package/docs/reference/ai/browser-speech.md +32 -15
- package/docs/reference/ai/browser-wasm.md +36 -28
- package/docs/reference/cli.md +22 -6
- package/docs/reference/inventory/runtime-components.json +4 -4
- package/docs/reference/protocols.md +12 -5
- package/docs/reference/runtime-components.md +32 -22
- package/docs/reference/runtime-modules.md +7 -1
- package/docs/reference/sdk-api.md +21 -15
- package/package.json +1 -1
- package/runtime/arcane/components/browser-ai-setup.html +199 -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
- package/src/cli/main.mjs +14 -3
- package/src/dev-server.mjs +32 -3
|
@@ -3451,7 +3451,7 @@ async function useselectApp(...arguments_) {
|
|
|
3451
3451
|
|
|
3452
3452
|
### Overview
|
|
3453
3453
|
|
|
3454
|
-
Starts one owned browser development server with exact runtime/app route mappings and
|
|
3454
|
+
Starts one owned browser development server with exact runtime/app route mappings and a caller-selected bind address.
|
|
3455
3455
|
|
|
3456
3456
|
### Signature, modes, and result
|
|
3457
3457
|
|
|
@@ -3464,19 +3464,23 @@ Import it from `arcane-os`. Source mode accepts
|
|
|
3464
3464
|
signal, onEvent}` and serves one validated workspace application plus its
|
|
3465
3465
|
complete SDK or integrated runtime. Packaged mode uses
|
|
3466
3466
|
`{mode:'packaged', releaseRoot, host, port, signal, onEvent}` and serves the
|
|
3467
|
-
complete selected release files. `host`
|
|
3468
|
-
|
|
3469
|
-
|
|
3467
|
+
complete selected release files. `host` defaults to `127.0.0.1` and accepts an
|
|
3468
|
+
explicit network address or hostname. Use `0.0.0.0` for all IPv4 interfaces or
|
|
3469
|
+
`::` for the platform's IPv6 wildcard listener; port `0` asks the operating
|
|
3470
|
+
system for an available port.
|
|
3470
3471
|
|
|
3471
3472
|
The promise settles after the listener is ready and resolves to
|
|
3472
|
-
`{server, mode, workspaceRoot, appId, host, port, origin, cleanUrl, url,
|
|
3473
|
-
closed, lifecycle}`. `server` is the raw Node HTTP server.
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3473
|
+
`{server, mode, workspaceRoot, appId, host, port, origin, cleanUrl, url,
|
|
3474
|
+
networkUrls, close, closed, lifecycle}`. `server` is the raw Node HTTP server.
|
|
3475
|
+
`url` and `cleanUrl` are the same application URL. Wildcard listeners use
|
|
3476
|
+
`localhost` in that local URL; `host` retains the actual bound address.
|
|
3477
|
+
`networkUrls` lists application URLs for applicable non-loopback interface
|
|
3478
|
+
addresses discovered once at startup. These URLs are connection candidates,
|
|
3479
|
+
not evidence of reachability from another device. The server adds no session
|
|
3480
|
+
capability or authentication. In packaged mode, `workspaceRoot` and `appId`
|
|
3481
|
+
are `null`.
|
|
3482
|
+
|
|
3483
|
+
Starting the server opens the selected listener and emits awaited,
|
|
3480
3484
|
backpressured `server.starting` and `server.started` events. Request failures
|
|
3481
3485
|
emit `server.request.failed`; shutdown emits `server.stopped` after owned
|
|
3482
3486
|
requests and event delivery drain. Call `await result.close()` in a `finally`
|
|
@@ -6391,9 +6395,11 @@ and materializes object URLs, then returns
|
|
|
6391
6395
|
`{cache,runtime,model,release}`. Call `release()` when the Worker no longer
|
|
6392
6396
|
needs those URLs. `offline:true` never fetches and rejects a miss with
|
|
6393
6397
|
`ARCANE_AI_ARTIFACT_OFFLINE_MISS`. `remove(authority)` deletes that exact
|
|
6394
|
-
authority's files and selection metadata. `onProgress`
|
|
6395
|
-
|
|
6396
|
-
|
|
6398
|
+
authority's files and selection metadata. Optional `onProgress` receives
|
|
6399
|
+
preparation and download records with `phase`, `stage`, `message`, and the
|
|
6400
|
+
current `file` when available. File operations report actual `completed` and
|
|
6401
|
+
`total` inventory counts with `unit:'files'`; module preparation uses
|
|
6402
|
+
`total:null`. Cancellation stops progress along with the owning operation.
|
|
6397
6403
|
|
|
6398
6404
|
### Availability and normalization
|
|
6399
6405
|
|
package/package.json
CHANGED
|
@@ -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,27 @@
|
|
|
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>If you have a discrete GPU, enable “Force High Performance GPU”, save your work, and restart the browser.</p>
|
|
62
|
+
<button id="copyGpuAddress" class="arcane-button arcane-button--secondary" type="button">Copy GPU flag address</button>
|
|
48
63
|
</div>
|
|
49
|
-
<
|
|
64
|
+
<details>
|
|
65
|
+
<summary>Setup help</summary>
|
|
66
|
+
<p><a href="https://onnxruntime.ai/docs/tutorials/web/ep-webnn.html">WebNN setup documentation</a></p>
|
|
67
|
+
</details>
|
|
50
68
|
<p id="copyStatus" class="arcane-help" role="status" aria-live="polite"></p>
|
|
51
69
|
</div>
|
|
52
70
|
<footer class="arcane-card__footer">
|
|
53
|
-
<button id="refreshAvailability" class="arcane-button arcane-button--secondary" type="button">Refresh
|
|
71
|
+
<button id="refreshAvailability" class="arcane-button arcane-button--secondary" type="button">Refresh</button>
|
|
54
72
|
<p id="setupStatus" class="arcane-help" role="status" aria-live="polite"></p>
|
|
55
73
|
</footer>
|
|
56
74
|
</section>
|
|
@@ -63,9 +81,8 @@
|
|
|
63
81
|
const {
|
|
64
82
|
getBrowserDeviceSettings,
|
|
65
83
|
detectBrowserGpu,
|
|
66
|
-
openBrowserDeviceSettings,
|
|
67
84
|
WEBNN_BROWSER_SETTINGS
|
|
68
|
-
}=await import('../sdk/ai/browser-device-settings.mjs');
|
|
85
|
+
} = await import('../sdk/ai/browser-device-settings.mjs');
|
|
69
86
|
const {arcaneLogging}=await import('arcane-os/logging');
|
|
70
87
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
71
88
|
const events=createArcaneEventSource(host,{
|
|
@@ -77,19 +94,14 @@
|
|
|
77
94
|
const copyStatus=root.querySelector('#copyStatus');
|
|
78
95
|
const gpuAvailability=root.querySelector('#gpuAvailability');
|
|
79
96
|
const gpuAdapterName=root.querySelector('#gpuAdapterName');
|
|
80
|
-
const
|
|
81
|
-
const gpuFlagsLink=root.querySelector('#gpuFlagsLink');
|
|
97
|
+
const refreshButton = root.querySelector('#refreshAvailability');
|
|
82
98
|
const copyGpuButton=root.querySelector('#copyGpuAddress');
|
|
83
99
|
const copyButtons=[copyGpuButton];
|
|
84
100
|
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
101
|
let copyTask=null;
|
|
102
|
+
let copyingAddress = null;
|
|
92
103
|
let gpuTask=null;
|
|
104
|
+
let gpuResult = null;
|
|
93
105
|
let destroyed=false;
|
|
94
106
|
|
|
95
107
|
host.refresh=refresh;
|
|
@@ -97,152 +109,164 @@
|
|
|
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
|
-
root.querySelector('#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
root.querySelector('#npuSetup').hidden = settings.webnnAvailable;
|
|
119
|
+
const unknownBrowser = settings.browserId === 'unknown';
|
|
120
|
+
root.querySelector('#browserInstructions').textContent = settings.webnnFlagsURL || unknownBrowser
|
|
121
|
+
? 'Enable “Enables WebNN API”, save your work, and relaunch the browser.'
|
|
122
|
+
: 'See Setup help for this browser.';
|
|
123
|
+
for (const choice of choices) {
|
|
124
|
+
choice.row.hidden = !unknownBrowser && choice.target.url !== settings.webnnFlagsURL;
|
|
125
|
+
choice.heading.hidden = !unknownBrowser;
|
|
131
126
|
}
|
|
127
|
+
renderGpuGuidance(settings);
|
|
132
128
|
return settings;
|
|
133
129
|
}
|
|
134
130
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
131
|
+
function renderGpuGuidance(settings = getBrowserDeviceSettings()) {
|
|
132
|
+
const gpuTarget = settings.highPerformanceGpu;
|
|
133
|
+
const nonperformance = gpuResult?.available && (
|
|
134
|
+
gpuResult.performanceStatus === 'integrated'
|
|
135
|
+
|| gpuResult.performanceStatus === 'fallback'
|
|
136
|
+
);
|
|
137
|
+
root.querySelector('#gpuSetup').hidden = !nonperformance || !gpuTarget;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function checkGpu() {
|
|
141
|
+
if (destroyed) return Promise.resolve(false);
|
|
142
|
+
if (gpuTask) return gpuTask;
|
|
143
|
+
gpuAvailability.textContent = 'Detecting GPU...';
|
|
144
|
+
gpuAvailability.dataset.status = 'pending';
|
|
145
|
+
gpuAdapterName.textContent = '';
|
|
146
|
+
refreshButton.disabled = true;
|
|
147
|
+
gpuTask = detect().finally(
|
|
148
|
+
function finishGpuDetection() {
|
|
149
|
+
gpuTask = null;
|
|
150
|
+
if (!destroyed) refreshButton.disabled = false;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
146
153
|
return gpuTask;
|
|
147
154
|
|
|
148
|
-
async function detect(){
|
|
149
|
-
try{
|
|
150
|
-
const result=await detectBrowserGpu();
|
|
151
|
-
if(destroyed)return false;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
async function detect() {
|
|
156
|
+
try {
|
|
157
|
+
const result = await detectBrowserGpu();
|
|
158
|
+
if (destroyed) return false;
|
|
159
|
+
gpuResult = result;
|
|
160
|
+
if (!result.available) {
|
|
161
|
+
gpuAvailability.textContent = 'GPU unavailable.';
|
|
162
|
+
gpuAvailability.dataset.status = 'warning';
|
|
163
|
+
} else if (result.performanceStatus === 'discrete') {
|
|
164
|
+
gpuAvailability.textContent = 'Already using the performance GPU.';
|
|
165
|
+
gpuAvailability.dataset.status = 'success';
|
|
166
|
+
} else if (result.performanceStatus === 'integrated') {
|
|
167
|
+
gpuAvailability.textContent = 'Integrated GPU available.';
|
|
168
|
+
gpuAvailability.dataset.status = 'warning';
|
|
169
|
+
} else if (result.performanceStatus === 'fallback') {
|
|
170
|
+
gpuAvailability.textContent = 'Software adapter available.';
|
|
171
|
+
gpuAvailability.dataset.status = 'warning';
|
|
172
|
+
} else {
|
|
173
|
+
gpuAvailability.textContent = 'GPU available.';
|
|
174
|
+
gpuAvailability.dataset.status = 'unknown';
|
|
175
|
+
}
|
|
176
|
+
gpuAdapterName.textContent = result.available ? result.name : '';
|
|
177
|
+
renderGpuGuidance();
|
|
160
178
|
return result;
|
|
161
|
-
}catch(error){
|
|
162
|
-
if(!destroyed){
|
|
163
|
-
|
|
164
|
-
gpuAvailability.
|
|
165
|
-
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (!destroyed) {
|
|
181
|
+
gpuResult = null;
|
|
182
|
+
gpuAvailability.textContent = 'GPU detection failed. Select Refresh to retry.';
|
|
183
|
+
gpuAvailability.dataset.status = 'error';
|
|
184
|
+
renderGpuGuidance();
|
|
185
|
+
arcaneLogging.error('Unable to detect the browser GPU adapter.', error);
|
|
166
186
|
}
|
|
167
187
|
return false;
|
|
168
188
|
}
|
|
169
189
|
}
|
|
170
190
|
}
|
|
171
191
|
|
|
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}`
|
|
192
|
+
function open(browserId) {
|
|
193
|
+
if (destroyed) return Promise.resolve(false);
|
|
194
|
+
const current = refresh();
|
|
195
|
+
const selectedId = browserId ?? current.browserId;
|
|
196
|
+
const target = WEBNN_BROWSER_SETTINGS.find(
|
|
197
|
+
function matchSetupBrowser(browser) {
|
|
198
|
+
return browser.browserId === selectedId;
|
|
199
|
+
}
|
|
189
200
|
);
|
|
190
|
-
|
|
201
|
+
if (!target) {
|
|
202
|
+
status.textContent = current.browserId === 'unknown'
|
|
203
|
+
? 'Choose Chrome or Edge to copy its NPU flag address.'
|
|
204
|
+
: 'No NPU flag address for this browser.';
|
|
205
|
+
return Promise.resolve(false);
|
|
206
|
+
}
|
|
207
|
+
return copyAddress(target.url);
|
|
191
208
|
}
|
|
192
209
|
|
|
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
|
-
|
|
210
|
+
function copyAddress(address) {
|
|
211
|
+
if (destroyed) return Promise.resolve(false);
|
|
212
|
+
if (copyTask) return copyingAddress === address ? copyTask : Promise.resolve(false);
|
|
213
|
+
copyStatus.textContent = 'Copying address...';
|
|
214
|
+
for (const button of copyButtons) {
|
|
215
|
+
button.disabled = true;
|
|
216
|
+
}
|
|
217
|
+
copyingAddress = address;
|
|
218
|
+
copyTask = writeAddress().finally(
|
|
219
|
+
function finishAddressCopy() {
|
|
220
|
+
copyTask = null;
|
|
221
|
+
copyingAddress = null;
|
|
222
|
+
if (!destroyed) {
|
|
223
|
+
for (const button of copyButtons) {
|
|
224
|
+
button.disabled = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
202
227
|
}
|
|
203
|
-
|
|
228
|
+
);
|
|
204
229
|
return copyTask;
|
|
205
230
|
|
|
206
|
-
async function writeAddress(){
|
|
207
|
-
try{
|
|
231
|
+
async function writeAddress() {
|
|
232
|
+
try {
|
|
208
233
|
await globalThis.navigator.clipboard.writeText(address);
|
|
209
|
-
if(destroyed)return false;
|
|
210
|
-
copyStatus.textContent='Address copied. Paste it into your browser\'s address bar.';
|
|
234
|
+
if (destroyed) return false;
|
|
235
|
+
copyStatus.textContent = 'Address copied. Paste it into your browser\'s address bar.';
|
|
211
236
|
return true;
|
|
212
|
-
}catch(error){
|
|
213
|
-
if(!destroyed){
|
|
214
|
-
copyStatus.textContent=
|
|
215
|
-
arcaneLogging.error('Unable to copy the browser flags address.',error);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (!destroyed) {
|
|
239
|
+
copyStatus.textContent = `Copy failed. Paste this address into your browser's address bar: ${address}`;
|
|
240
|
+
arcaneLogging.error('Unable to copy the browser flags address.', error);
|
|
216
241
|
}
|
|
217
242
|
return false;
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
245
|
}
|
|
221
246
|
|
|
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);
|
|
247
|
+
const addressList = root.querySelector('#flagAddresses');
|
|
248
|
+
for (const target of WEBNN_BROWSER_SETTINGS) {
|
|
249
|
+
const row = document.createElement('section');
|
|
250
|
+
const heading = document.createElement('h4');
|
|
251
|
+
const copyButton = document.createElement('button');
|
|
252
|
+
row.className = 'flag-choice';
|
|
253
|
+
heading.textContent = target.name;
|
|
254
|
+
copyButton.type = 'button';
|
|
255
|
+
copyButton.className = 'arcane-button arcane-button--secondary';
|
|
256
|
+
copyButton.textContent = 'Copy NPU flag address';
|
|
257
|
+
copyButton.setAttribute('aria-label', `Copy NPU flag address for ${target.name}`);
|
|
258
|
+
copyButton.addEventListener(
|
|
259
|
+
'click',
|
|
260
|
+
function copySelectedBrowserAddress() {
|
|
261
|
+
return copyAddress(target.url);
|
|
262
|
+
},
|
|
263
|
+
{signal: lifecycleController.signal}
|
|
264
|
+
);
|
|
265
|
+
row.append(heading, copyButton);
|
|
244
266
|
addressList.append(row);
|
|
245
|
-
choices.push(
|
|
267
|
+
choices.push(
|
|
268
|
+
{row, heading, target}
|
|
269
|
+
);
|
|
246
270
|
copyButtons.push(copyButton);
|
|
247
271
|
}
|
|
248
272
|
|
|
@@ -250,19 +274,15 @@
|
|
|
250
274
|
const target=getBrowserDeviceSettings().highPerformanceGpu;
|
|
251
275
|
if(target)return copyAddress(target.url);
|
|
252
276
|
},{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});
|
|
277
|
+
refreshButton.addEventListener(
|
|
278
|
+
'click',
|
|
279
|
+
function refreshBrowserAvailability() {
|
|
280
|
+
refresh();
|
|
281
|
+
status.textContent = '';
|
|
282
|
+
return checkGpu();
|
|
283
|
+
},
|
|
284
|
+
{signal: lifecycleController.signal}
|
|
285
|
+
);
|
|
266
286
|
host.ownerDocument.defaultView.addEventListener('pagehide',function disposeBeforePageUnload(event){
|
|
267
287
|
if(!event.persisted)destroy();
|
|
268
288
|
},{
|
|
@@ -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
|
}
|