arcane-os 0.6.0 → 0.6.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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.2
4
+
5
+ - Replace browser NPU setup buttons with direct browser-specific flags links
6
+ and retain copy-address controls and the public `open()` method.
7
+ - Add the missing GPU performance flags link, setup instructions, and GPU
8
+ detection control. Chrome uses its own `chrome://` flags address.
9
+ - Show adapter availability and its reported name after one shared detection
10
+ request. Keep fallback and failed detection visible without claiming that
11
+ the browser performance flag is enabled or a model is running on that GPU.
12
+ Detection creates no device, loads no model, and changes no browser settings.
13
+
14
+ ## 0.6.1
15
+
16
+ - Add a reusable profile component for browser NPU setup and WebNN/WebGPU API
17
+ availability. Its setup button uses the existing GPU guidance approach:
18
+ attempt the appropriate Chrome or Edge flags page, then show an alert with
19
+ instructions and the address to paste if the browser blocks navigation.
20
+ - Share browser identification and the flags-opening helper with the existing
21
+ high-performance GPU notice. Preserve that notice's targets and wording.
22
+ Setup does not change saved preferences, browser flags, selected models, or
23
+ inference; API availability is not reported as physical NPU execution.
24
+
3
25
  ## 0.6.0
4
26
 
5
27
  - Prefer WebNN NPU, then WebGPU, then CPU through WASM for browser Whisper
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
20
20
  event, cancellation, and browser run contracts.
21
21
 
22
- This checkout defines the `0.6.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.6.2` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
@@ -35,7 +35,7 @@ Create one browser application, install its pinned SDK, and start its source
35
35
  server:
36
36
 
37
37
  ```bash
38
- npx arcane-os@0.6.0 new hello-speech --path ./hello-speech --target browser
38
+ npx arcane-os@0.6.2 new hello-speech --path ./hello-speech --target browser
39
39
  cd hello-speech
40
40
  npm install
41
41
  npm run dev
@@ -363,7 +363,7 @@ uses the same controller for automatic memory extraction.
363
363
  Create a new repository-shaped Arcane application with the exact stable SDK:
364
364
 
365
365
  ```bash
366
- npx arcane-os@0.6.0 new my-app --path ./my-app --target portable --git
366
+ npx arcane-os@0.6.2 new my-app --path ./my-app --target portable --git
367
367
  cd my-app
368
368
  npm install
369
369
  npm run dev
@@ -373,7 +373,7 @@ To enroll an existing repository, install the exact SDK and initialize only
373
373
  missing Arcane files:
374
374
 
375
375
  ```bash
376
- npm install --save-dev --save-exact arcane-os@0.6.0
376
+ npm install --save-dev --save-exact arcane-os@0.6.2
377
377
  npm exec -- arcane init my-app --target portable
378
378
  ```
379
379
 
@@ -389,7 +389,7 @@ npm exec -- arcane-os targets
389
389
  No global SDK install or standalone Arcane CLI is required. The application
390
390
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
391
391
 
392
- Use `npx arcane-os@0.6.0` for the initial bootstrap because it names this npm
392
+ Use `npx arcane-os@0.6.2` for the initial bootstrap because it names this npm
393
393
  package explicitly; bare `npx arcane` outside an installed project could resolve
394
394
  a different package. Both installed commands invoke the same headless toolchain.
395
395
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -409,7 +409,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
409
409
 
410
410
  # From the generated app repository
411
411
  cd ../local-app
412
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.6.0.tgz
412
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.6.2.tgz
413
413
  npm ci
414
414
  ```
415
415
 
@@ -418,7 +418,7 @@ same location. The lockfile retains the selected package dependency while
418
418
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
419
419
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
420
420
  runner also needs that tarball at the locked path. After publication, replace
421
- the local declaration with the exact `arcane-os@0.6.0` registry package and
421
+ the local declaration with the exact `arcane-os@0.6.2` registry package and
422
422
  commit the regenerated lock.
423
423
 
424
424
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -557,7 +557,7 @@ package installation, or assertions.
557
557
 
558
558
  ## Current target support
559
559
 
560
- Version `0.6.0` exposes one browser target and five explicitly paired
560
+ Version `0.6.2` exposes one browser target and five explicitly paired
561
561
  native development targets: a non-runnable portable directory, a
562
562
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
563
563
  unsigned-local-test DEBs, and an Android development-signed APK. The
@@ -0,0 +1,104 @@
1
+ import Is from "../dependencies/strong-type/index.js";
2
+
3
+ const is = new Is(false);
4
+
5
+ export const WEBNN_BROWSER_SETTINGS = [
6
+ { browserId: "chrome", name: "Google Chrome", url: "chrome://flags/#web-machine-learning-neural-network" },
7
+ { browserId: "edge", name: "Microsoft Edge", url: "edge://flags/#web-machine-learning-neural-network" },
8
+ ];
9
+
10
+ // Internal-page navigation may be blocked. The instructions remain available
11
+ // regardless; returning from this function is not evidence that settings opened.
12
+ export function openBrowserDeviceSettings(target, message) {
13
+ try {
14
+ globalThis.open?.(target.url, "_blank", "noopener,noreferrer");
15
+ } catch {
16
+ // Continue to the address-bar instructions when the browser rejects the URL.
17
+ }
18
+ globalThis.alert?.(message);
19
+ }
20
+
21
+ // Browser identity selects setup instructions, never hardware or model readiness.
22
+ export function getBrowserDeviceSettings(navigatorObject = globalThis.navigator) {
23
+ const userAgent = String(navigatorObject?.userAgent ?? "");
24
+ const clientHints = navigatorObject?.userAgentData;
25
+ const platform = String(clientHints?.platform || navigatorObject?.platform || userAgent);
26
+ const brands = new Set();
27
+ for (const entry of clientHints?.brands ?? []) brands.add(entry.brand);
28
+ const desktopWindows = /\b(?:Windows|Win32|Win64)\b/iu.test(platform)
29
+ && clientHints?.mobile !== true
30
+ && !/\b(?:Android|iPhone|iPad|iPod|Mobile)\b/iu.test(userAgent);
31
+ let browserId = "unknown";
32
+ let name = "this browser";
33
+ let highPerformanceGpu = null;
34
+
35
+ // Preserve the existing GPU guidance for browsers that also identify as Chrome.
36
+ if (brands.has("Vivaldi") || /\bVivaldi\//u.test(userAgent)) {
37
+ browserId = "vivaldi";
38
+ name = "Vivaldi";
39
+ highPerformanceGpu = { name, url: "vivaldi://flags/#force-high-performance-gpu" };
40
+ } else if (brands.has("Brave") || is.function(navigatorObject?.brave?.isBrave)) {
41
+ browserId = "brave";
42
+ name = "Brave";
43
+ highPerformanceGpu = { name, url: "brave://flags/#force-high-performance-gpu" };
44
+ } else if (brands.has("Opera") || /\bOPR\//u.test(userAgent)) {
45
+ browserId = "opera";
46
+ name = "Opera";
47
+ highPerformanceGpu = { name, url: "opera://flags/#force-high-performance-gpu" };
48
+ } else if (brands.has("Microsoft Edge") || /\bEdg\//u.test(userAgent)) {
49
+ browserId = "edge";
50
+ name = "Microsoft Edge";
51
+ highPerformanceGpu = { name, url: "edge://flags/#force-high-performance-gpu" };
52
+ } else if (
53
+ brands.has("Chromium")
54
+ || brands.has("Google Chrome")
55
+ || /\b(?:Chrome|Chromium)\//u.test(userAgent)
56
+ ) {
57
+ // Keep the established generic GPU address for Chromium browsers that mask their brand.
58
+ highPerformanceGpu = { name: "your browser", url: "about://flags/#force-high-performance-gpu" };
59
+ if (
60
+ brands.has("Google Chrome")
61
+ || (!clientHints?.brands && /\bChrome\//u.test(userAgent)
62
+ && !/\b(?:SamsungBrowser|UCBrowser|YaBrowser)\//u.test(userAgent))
63
+ ) {
64
+ browserId = "chrome";
65
+ name = "Google Chrome";
66
+ highPerformanceGpu = { name, url: "chrome://flags/#force-high-performance-gpu" };
67
+ } else {
68
+ browserId = "chromium";
69
+ name = "your Chromium browser";
70
+ }
71
+ }
72
+
73
+ const webnnFlagsURL = WEBNN_BROWSER_SETTINGS.find(function matchesBrowser(entry) {
74
+ return entry.browserId === browserId;
75
+ })?.url ?? null;
76
+ return {
77
+ browserId,
78
+ name,
79
+ webnnFlagsURL,
80
+ highPerformanceGpu: desktopWindows ? highPerformanceGpu : null,
81
+ webnnAvailable: is.function(navigatorObject?.ml?.createContext),
82
+ webgpuAvailable: Boolean(navigatorObject?.gpu),
83
+ };
84
+ }
85
+
86
+ // Adapter selection is availability evidence, not the browser flag's state or
87
+ // proof that a model is executing on this GPU. The power preference is a hint.
88
+ export async function detectBrowserGpu(navigatorObject = globalThis.navigator) {
89
+ if (!is.function(navigatorObject?.gpu?.requestAdapter)) {
90
+ return { available: false, reason: "api-unavailable" };
91
+ }
92
+ const adapter = await navigatorObject.gpu.requestAdapter({ powerPreference: "high-performance" });
93
+ if (!adapter) return { available: false, reason: "adapter-unavailable" };
94
+ const info = adapter.info;
95
+ return {
96
+ available: true,
97
+ name: info?.description
98
+ || [info?.vendor, info?.architecture, info?.device].filter(Boolean).join(" ")
99
+ || "WebGPU adapter",
100
+ isFallbackAdapter: is.boolean(info?.isFallbackAdapter)
101
+ ? info.isFallbackAdapter
102
+ : is.boolean(adapter.isFallbackAdapter) ? adapter.isFallbackAdapter : null,
103
+ };
104
+ }
@@ -8,6 +8,7 @@ import {
8
8
  resolveModelSecurity,
9
9
  } from "./model-controller.mjs";
10
10
  import { createPackagedWllamaRuntime } from "./browser-wllama-runtime.mjs";
11
+ import { getBrowserDeviceSettings, openBrowserDeviceSettings } from "./browser-device-settings.mjs";
11
12
  import { arcaneEvents } from "../event-manager.mjs";
12
13
 
13
14
  const is = new Is(false);
@@ -264,65 +265,20 @@ function publicDescriptor(source) {
264
265
  return modelDescriptor(source);
265
266
  }
266
267
 
267
- function highPerformanceGpuBrowser() {
268
- const navigatorObject = globalThis.navigator;
269
- const userAgent = String(navigatorObject?.userAgent ?? "");
270
- const clientHints = navigatorObject?.userAgentData;
271
- const platform = String(clientHints?.platform || navigatorObject?.platform || userAgent);
272
- if (
273
- !/\b(?:Windows|Win32|Win64)\b/iu.test(platform)
274
- || clientHints?.mobile === true
275
- || /\b(?:Android|iPhone|iPad|iPod|Mobile)\b/iu.test(userAgent)
276
- ) {
277
- return null;
278
- }
279
-
280
- const brands = new Set();
281
- for (const entry of clientHints?.brands ?? []) {
282
- brands.add(entry.brand);
283
- }
284
- // Vivaldi can append its identity while also identifying as Edge or Chrome.
285
- if (brands.has("Vivaldi") || /\bVivaldi\//u.test(userAgent)) {
286
- return { name: "Vivaldi", url: "vivaldi://flags/#force-high-performance-gpu" };
287
- }
288
- if (brands.has("Brave") || is.function(navigatorObject?.brave?.isBrave)) {
289
- return { name: "Brave", url: "brave://flags/#force-high-performance-gpu" };
290
- }
291
- if (brands.has("Opera") || /\bOPR\//u.test(userAgent)) {
292
- return { name: "Opera", url: "opera://flags/#force-high-performance-gpu" };
293
- }
294
- if (brands.has("Microsoft Edge") || /\bEdg\//u.test(userAgent)) {
295
- return { name: "Microsoft Edge", url: "edge://flags/#force-high-performance-gpu" };
296
- }
297
- if (
298
- brands.has("Chromium")
299
- || brands.has("Google Chrome")
300
- || /\b(?:Chrome|Chromium)\//u.test(userAgent)
301
- ) {
302
- // Some Chromium browsers mask their brand. about:// uses their own flags page.
303
- return { name: "your browser", url: "about://flags/#force-high-performance-gpu" };
304
- }
305
- return null;
306
- }
307
-
308
268
  function notifyHighPerformanceGpu(adapter) {
309
269
  if (highPerformanceGpuNoticeShown) {
310
270
  return;
311
271
  }
312
- const browser = highPerformanceGpuBrowser();
272
+ const browser = getBrowserDeviceSettings().highPerformanceGpu;
313
273
  if (!browser) {
314
274
  return;
315
275
  }
316
276
  highPerformanceGpuNoticeShown = true;
317
- try {
318
- globalThis.open?.(browser.url, "_blank", "noopener,noreferrer");
319
- } catch {
320
- // Browsers may reject internal-page navigation from web content.
321
- }
322
277
  const adapterName = adapter.description || adapter.name
323
278
  || [adapter.vendor, adapter.architecture].filter(Boolean).join(" ")
324
279
  || "the available WebGPU adapter";
325
- globalThis.alert?.(
280
+ openBrowserDeviceSettings(
281
+ browser,
326
282
  `Selected WebGPU adapter: ${adapterName}.\n\n`
327
283
  + `If this computer has multiple GPUs, enable “Force High Performance GPU” in ${browser.name} `
328
284
  + "to request the high-performance GPU when available. "
@@ -279,7 +279,7 @@ paths are withheld from the native provider. The provider copies the complete
279
279
  selected release rather than accepting an unrelated source path. Verification
280
280
  is a separate explicit operation for a selected release artifact.
281
281
 
282
- The SDK `0.6.0` runtime requires Arcane `0.8.12` or newer. Compatibility
282
+ The SDK `0.6.2` runtime requires Arcane `0.8.12` or newer. Compatibility
283
283
  is contractual rather than exact-version pinning: the prepared Core must meet
284
284
  the highest minimum declared by the runtime, selected app, and bundled app
285
285
  dependencies; keep each app's Arcane protocol generation; and provide every
@@ -143,7 +143,7 @@ The synchronized browser payload exposes:
143
143
  ESM modules, classic vendor globals, one worker protocol, and one Node-oriented
144
144
  mail transport;
145
145
  - 14 shared entity modules under `runtime/arcane/entities/`;
146
- - 39 reusable HTML-import components under `runtime/arcane/components/`;
146
+ - 40 reusable HTML-import components under `runtime/arcane/components/`;
147
147
  - seven shared CSS artifacts, images, optional physical-workspace security
148
148
  files where present, and the vendored `strong-type` dependency.
149
149
 
@@ -130,6 +130,53 @@ window.addEventListener('ai-tts-failure', function reportSpeechFailure(event) {
130
130
 
131
131
  Call `speechEvents.abort()` when disposing that interface to remove the listener.
132
132
 
133
+ ## Browser NPU and GPU setup
134
+
135
+ Applications can place the shared `browser-ai-setup.html` component in their
136
+ profile or settings page. It reports whether this page exposes WebNN and
137
+ WebGPU, then requests one WebGPU adapter to display its availability and reported
138
+ name. It does not load a model or create a GPU device or WebNN context:
139
+
140
+ ```html
141
+ <html-import
142
+ id="browserAISetup"
143
+ href="/arcane/components/browser-ai-setup.html">
144
+ </html-import>
145
+ ```
146
+
147
+ The **NPU setup** section provides real browser-specific flags links. Chrome uses
148
+ `chrome://flags/#web-machine-learning-neural-network`; Edge uses
149
+ `edge://flags/#web-machine-learning-neural-network`. Unrecognized browsers
150
+ receive explicit Chrome and Edge choices. Full addresses and copy controls
151
+ remain available if the browser blocks internal-page links.
152
+ The [ONNX Runtime WebNN guide](https://onnxruntime.ai/docs/tutorials/web/ep-webnn.html)
153
+ documents the **Enables WebNN API** flag and model/operator requirements.
154
+
155
+ The **GPU performance** section shows adapter detection and, on desktop Windows
156
+ Chromium browsers, the matching Force High Performance GPU flags link. Identified
157
+ Chrome uses `chrome://flags/#force-high-performance-gpu`; other recognized
158
+ Chromium browsers use their corresponding internal scheme. **Detect GPU** or
159
+ **Refresh browser availability** requests a new availability result; concurrent
160
+ requests share the pending operation. The public `checkGpu()` method provides
161
+ the same promise, while `refresh()` remains a synchronous API-presence and
162
+ settings update. Component readiness does not wait for adapter detection.
163
+
164
+ Adapter selection uses `powerPreference: "high-performance"` as a hint, not proof
165
+ of the selected GPU's performance or the flag's current state. A returned
166
+ adapter establishes availability only; its fallback classification is reported
167
+ when exposed. The component cannot read the browser flag or prove that a model
168
+ is executing on the adapter. See the
169
+ [component contract](../runtime-components.md#browser-ai-setuphtml) for result
170
+ fields, failure handling, and disposal behavior.
171
+
172
+ This control does not save an execution preference, change browser settings,
173
+ restart the browser, or report that the NPU is active. A WebNN API presence
174
+ result is not proof of NPU hardware, a compatible model, or physical execution.
175
+ The profile also cannot report a different chat page's loaded runtime. The
176
+ existing automatic speech route remains NPU, then GPU, then CPU; upstream
177
+ sessions can still place unsupported operators on CPU. Use the owning model
178
+ runtime's evidence to determine actual accelerator execution.
179
+
133
180
  ## Developer diagnostics
134
181
 
135
182
  The shared logging API and speech traces are available in SDK `0.5.14`.
@@ -5,9 +5,9 @@
5
5
  "repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
6
6
  "branch": "main",
7
7
  "path": "runtime/arcane/components",
8
- "sdkVersion": "0.5.18"
8
+ "sdkVersion": "0.6.2"
9
9
  },
10
- "componentCount": 39,
10
+ "componentCount": 40,
11
11
  "loader": "/arcane/modules/HTMLImport.js",
12
12
  "artifacts": [
13
13
  {
@@ -76,6 +76,30 @@
76
76
  "transport": "HTMLImport + DOM; injected Arcane/provider modules where listed",
77
77
  "normalization": "DOM-normalized; caller/provider results remain external"
78
78
  },
79
+ {
80
+ "file": "runtime/arcane/components/browser-ai-setup.html",
81
+ "name": "browser-ai-setup.html",
82
+ "purpose": "Displays browser API and WebGPU adapter availability with browser-specific NPU and GPU flags links.",
83
+ "methods": [
84
+ "refresh()",
85
+ "checkGpu()",
86
+ "open()",
87
+ "destroy()",
88
+ "ready"
89
+ ],
90
+ "events": [
91
+ "browser-ai-setup-ready"
92
+ ],
93
+ "slots": [],
94
+ "dependencies": [
95
+ "browser-device-settings.mjs",
96
+ "arcane-os/logging",
97
+ "arcane-os/event-manager"
98
+ ],
99
+ "availability": "Browser and supported native WebViews",
100
+ "transport": "HTMLImport + DOM; browser flags links, Clipboard API, WebGPU adapter request, and preserved open() navigation attempt with alert",
101
+ "normalization": "API presence, adapter availability, and setup instructions normalized. Mounting starts one adapter request; checkGpu() coalesces concurrent calls, while refresh() remains synchronous. No GPU device, model, or WebNN context is created. Adapter availability does not establish browser flag state, high-performance selection, or model execution."
102
+ },
79
103
  {
80
104
  "file": "runtime/arcane/components/calculator.html",
81
105
  "name": "calculator.html",
@@ -60,6 +60,7 @@ appropriate.
60
60
  | --- | --- | --- | --- | --- |
61
61
  | [`app-bar.html`](#app-barhtml) | Responsive application navigation, route state, status, and trailing actions. | `setNavigation()`<br>`setActiveRoute()`<br>`setStatus()`<br>`refresh()`<br>`destroy()` | `app-bar-ready` | DOM-normalized |
62
62
  | [`assistant-panel.html`](#assistant-panelhtml) | Reusable assistant drawer, message area, composer, pending/streaming/empty/error state, and actions. | `open()`<br>`close()`<br>`toggle()`<br>`send()`<br>`clear()`<br>`setState()`<br>`focusComposer()`<br>`scrollToEnd()`<br>`destroy()` | `assistant-ready`<br>`assistant-opened`<br>`assistant-closed`<br>`assistant-send`<br>`assistant-clear` | DOM-normalized; caller/provider results remain external |
63
+ | [`browser-ai-setup.html`](#browser-ai-setuphtml) | Browser API and WebGPU adapter availability, with browser-specific NPU and GPU flags links. | `refresh()`<br>`checkGpu()`<br>`open()`<br>`destroy()`<br>`ready` | `browser-ai-setup-ready` | API and adapter availability; browser flag state and model execution remain browser-owned |
63
64
  | [`calculator.html`](#calculatorhtml) | Calculator keypad and result/error event surface backed by CalculatorEngine. | `calculate()`<br>`destroy()` | `calculator-ready`<br>`calculation-complete`<br>`calculation-error` | Normalized Calculation/error events |
64
65
  | [`chart.html`](#charthtml) | Accessible uPlot line, area, or point chart with normalized options and rows. | `configure()`<br>`populate()`<br>`setData()`<br>`addData()`<br>`update()`<br>`destroy()` | `chart-ready`<br>`chart-remove` | Options/rows normalized; uPlot rendering is vendor-native |
65
66
  | [`chat.html`](#chathtml) | Shared chat, visible selected-model activation request, file upload, streaming, structural tool settlement, speech, language, availability, and conversation-timebox surface. | `streamMessage()`<br>`setMessageProgress()`<br>`setAIAvailability()`<br>`setInitialSpeechMuted()`<br>`setConversationComplete()`<br>`bindConversationTimebox()`<br>`bindSession()`<br>`submitMessage()`<br>`submitToolResult()`<br>`submitToolResults()`<br>`sendMessage()`<br>`languageChanged()`<br>`requestAIActivation()`<br>`destroy()` | `chat-ready`<br>`chat-session-bound`<br>`chat-session-message`<br>`chat-session-error`<br>`chat-send-message`<br>`chat-send-error`<br>`chat-file-uploaded`<br>`chat-file-upload-error`<br>`chat-language-changed`<br>`chat-language-change-error`<br>`chat-ai-activation-request`<br>`chat-ai-activation-error`<br>`chat-speech-synthesis-error`<br>`conversation-timebox-error` | UI/runtime state, explicit user activation intent, and honest structural-call settlement normalized; AI/storage/media behavior mixed |
@@ -153,6 +154,81 @@ Slots: `title`, `subtitle`, `identity`, `messages/message`, `composer`, `actions
153
154
  </html-import>
154
155
  ```
155
156
 
157
+ ## browser-ai-setup.html
158
+
159
+ ### Overview
160
+
161
+ Displays WebNN and WebGPU API availability for the current page, detects an
162
+ available WebGPU adapter, and provides browser-specific NPU and GPU flags links.
163
+ The WebNN addresses are real links: Chrome and Edge receive their own addresses;
164
+ other or unidentified browsers show both explicit choices. Desktop Windows
165
+ Chromium browsers also receive the matching Force High Performance GPU link,
166
+ including `chrome://flags/#force-high-performance-gpu` for identified Chrome.
167
+ Copy controls and complete addresses remain available when the browser blocks
168
+ navigation to an internal page.
169
+
170
+ ### Public surface
171
+
172
+ Methods/properties: `refresh()`, `checkGpu()`, `open(browserId?)`, `destroy()`, `ready`.
173
+
174
+ Events: `browser-ai-setup-ready`.
175
+
176
+ `refresh()` synchronously updates API presence and browser guidance and returns
177
+ the current settings record: `browserId`, `name`, `webnnFlagsURL`,
178
+ `highPerformanceGpu`, `webnnAvailable`, and `webgpuAvailable`. It returns `false`
179
+ after destruction. Availability means that the browser exposes the API to this
180
+ page; it does not confirm a working adapter, loaded model, or hardware execution.
181
+
182
+ `checkGpu()` returns a promise for one adapter-availability record. Concurrent
183
+ calls share the pending promise. The component requests an adapter with the
184
+ `"high-performance"` power-preference hint; it does not create a GPU device or
185
+ load a model. An unavailable result contains `available: false` and a `reason`
186
+ of `"api-unavailable"` or `"adapter-unavailable"`. An available result contains
187
+ `available: true`, the reported adapter `name`, and `isFallbackAdapter` as a
188
+ boolean or `null` when the browser exposes no classification. Adapter selection
189
+ does not prove that a browser flag is enabled, that the fastest GPU was selected,
190
+ or that a model is using it. Failure is displayed and logged, and the promise
191
+ resolves to `false`; destruction also yields `false` and suppresses late UI
192
+ updates.
193
+
194
+ Mounting starts one `checkGpu()` operation. **Detect GPU** repeats it explicitly;
195
+ **Refresh browser availability** calls both `refresh()` and `checkGpu()`. Calling
196
+ `refresh()` directly remains synchronous and does not request an adapter.
197
+ `ready` and `browser-ai-setup-ready` describe component setup, which can complete
198
+ while adapter detection is still pending.
199
+
200
+ `open()` refreshes the display, attempts to open the detected Chrome or Edge flags
201
+ page, and presents an alert containing the full address and enable/relaunch
202
+ instructions. Pass `"chrome"` or `"edge"` to choose explicitly. Invoke it from a
203
+ user action. It returns `false` when destroyed or when no supported target is
204
+ selected, and otherwise returns `undefined`; it never reports that navigation
205
+ succeeded. Browsers may block internal-page navigation, so the full addresses
206
+ also remain visible and copyable in the component.
207
+
208
+ `destroy()` aborts owned listeners, disposes the event source, marks `ready`
209
+ false, and suppresses UI updates from pending clipboard and adapter operations.
210
+ It does not cancel the browser-owned adapter request. It returns `true` the
211
+ first time and `false` thereafter. A BFCache-persisted `pagehide` preserves the
212
+ component; nonpersisted `pagehide` destroys it.
213
+
214
+ ### Availability and normalization
215
+
216
+ **Browser and supported native WebViews.** API presence, adapter availability,
217
+ and setup instructions are normalized; browser flag state, clipboard support,
218
+ and model execution remain platform-owned. Mounting requests one WebGPU adapter
219
+ when that API is available. No GPU device, model, or WebNN context is created.
220
+ The component saves no preferences, changes no browser settings, and does not
221
+ restart the browser. It cannot report model state owned by another page.
222
+
223
+ ### Example
224
+
225
+ ```html
226
+ <html-import
227
+ id="browser-ai-setup"
228
+ href="/arcane/components/browser-ai-setup.html">
229
+ </html-import>
230
+ ```
231
+
156
232
  ## calculator.html
157
233
 
158
234
  ### Overview
@@ -3566,5 +3566,5 @@ console.log(Object.keys(module));
3566
3566
  ## Entity and component continuations
3567
3567
 
3568
3568
  - [Runtime entity modules](runtime-entities.md) explains all 14 modules, and [shared entity contracts](core/arcane-entities.md) owns all 29 exports.
3569
- - [Runtime components](runtime-components.md) owns all 39 HTML-import fragments, methods, slots, and events.
3569
+ - [Runtime components](runtime-components.md) owns all 40 HTML-import fragments, methods, slots, and events.
3570
3570
  - [Arcane Ollama](arcane-ollama.md) expands the raw-versus-normalized behavior of `Ollama.js`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -0,0 +1,288 @@
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
2
+ <style>
3
+ :host{display:block;min-width:0}
4
+ .availability{display:grid;gap:var(--arcane-space-2);margin:0}
5
+ .availability div{display:grid;gap:var(--arcane-space-1)}
6
+ .availability dt{font-weight:700}
7
+ .availability dd{margin:0}
8
+ .flag-choice{margin-block:var(--arcane-space-4)}
9
+ .flag-choice h3{margin-block:var(--arcane-space-2)}
10
+ .actions{display:flex;flex-wrap:wrap;gap:var(--arcane-space-2)}
11
+ p,dt,dd,li,button,a{overflow-wrap:anywhere}
12
+ button{white-space:normal}
13
+ .flag-address{display:block;white-space:pre-wrap;overflow-wrap:anywhere;user-select:text;margin-block:var(--arcane-space-2)}
14
+ #gpuAvailability{white-space:normal;overflow-wrap:anywhere;line-height:1.4;max-width:100%;box-sizing:border-box}
15
+ li{margin-block:var(--arcane-space-2)}
16
+ </style>
17
+
18
+ <section class="arcane-card" aria-labelledby="setupTitle">
19
+ <header class="arcane-card__header">
20
+ <h2 id="setupTitle" class="arcane-section-heading">Browser AI acceleration</h2>
21
+ </header>
22
+ <div class="arcane-card__body">
23
+ <p id="browserName"></p>
24
+ <dl class="availability" aria-live="polite">
25
+ <div><dt>WebNN API</dt><dd id="webnnAvailability"></dd></div>
26
+ <div><dt>WebGPU API</dt><dd id="webgpuAvailability"></dd></div>
27
+ </dl>
28
+ <p class="arcane-help">These checks show browser APIs only. They do not report whether a model is running on an NPU or GPU.</p>
29
+ <h3>NPU setup</h3>
30
+ <p id="browserInstructions"></p>
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>
37
+ <p id="gpuAdapterName" class="arcane-help"></p>
38
+ <button id="detectGpu" class="arcane-button arcane-button--secondary" type="button">Detect GPU</button>
39
+ <p id="gpuInstructions"></p>
40
+ <div id="gpuFlagAddress">
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>
48
+ </div>
49
+ <p class="arcane-help">GPU detection reports adapter availability. This page cannot read the flag's current setting or confirm that a model is using that adapter.</p>
50
+ <p id="copyStatus" class="arcane-help" role="status" aria-live="polite"></p>
51
+ </div>
52
+ <footer class="arcane-card__footer">
53
+ <button id="refreshAvailability" class="arcane-button arcane-button--secondary" type="button">Refresh browser availability</button>
54
+ <p id="setupStatus" class="arcane-help" role="status" aria-live="polite"></p>
55
+ </footer>
56
+ </section>
57
+
58
+ <script type="module">
59
+ const host=this;
60
+ const root=host.shadowRoot;
61
+ host.ready=false;
62
+
63
+ const {
64
+ getBrowserDeviceSettings,
65
+ detectBrowserGpu,
66
+ openBrowserDeviceSettings,
67
+ WEBNN_BROWSER_SETTINGS
68
+ }=await import('../sdk/ai/browser-device-settings.mjs');
69
+ const {arcaneLogging}=await import('arcane-os/logging');
70
+ const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
71
+ const events=createArcaneEventSource(host,{
72
+ source:'arcane.component.browser-ai-setup',
73
+ eventTypes:['browser-ai-setup-ready']
74
+ });
75
+ const lifecycleController=new AbortController();
76
+ const status=root.querySelector('#setupStatus');
77
+ const copyStatus=root.querySelector('#copyStatus');
78
+ const gpuAvailability=root.querySelector('#gpuAvailability');
79
+ const gpuAdapterName=root.querySelector('#gpuAdapterName');
80
+ const detectGpuButton=root.querySelector('#detectGpu');
81
+ const gpuFlagsLink=root.querySelector('#gpuFlagsLink');
82
+ const copyGpuButton=root.querySelector('#copyGpuAddress');
83
+ const copyButtons=[copyGpuButton];
84
+ 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
+ let copyTask=null;
92
+ let gpuTask=null;
93
+ let destroyed=false;
94
+
95
+ host.refresh=refresh;
96
+ host.checkGpu=checkGpu;
97
+ host.open=open;
98
+ host.destroy=destroy;
99
+
100
+ function refresh(){
101
+ if(destroyed)return false;
102
+ const settings=getBrowserDeviceSettings();
103
+ root.querySelector('#browserName').textContent=settings.browserId==='unknown'
104
+ ?'Browser not identified.'
105
+ :`Browser: ${settings.name}.`;
106
+ root.querySelector('#webnnAvailability').textContent=settings.webnnAvailable
107
+ ?'Exposed to this page.'
108
+ :'Unavailable to this page.';
109
+ root.querySelector('#webgpuAvailability').textContent=settings.webgpuAvailable
110
+ ?'Exposed to this page.'
111
+ :'Unavailable to this page.';
112
+ root.querySelector('#browserInstructions').textContent=settings.webnnFlagsURL
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='';
131
+ }
132
+ return settings;
133
+ }
134
+
135
+ function checkGpu(){
136
+ if(destroyed)return Promise.resolve(false);
137
+ if(gpuTask)return gpuTask;
138
+ gpuAvailability.textContent='Detecting GPU...';
139
+ gpuAvailability.dataset.status='pending';
140
+ gpuAdapterName.textContent='';
141
+ detectGpuButton.disabled=true;
142
+ gpuTask=detect().finally(function finishGpuDetection(){
143
+ gpuTask=null;
144
+ if(!destroyed)detectGpuButton.disabled=false;
145
+ });
146
+ return gpuTask;
147
+
148
+ async function detect(){
149
+ try{
150
+ const result=await detectBrowserGpu();
151
+ if(destroyed)return false;
152
+ gpuAvailability.textContent=result.available
153
+ ?result.isFallbackAdapter===true
154
+ ?'Fallback WebGPU adapter detected.'
155
+ :'GPU detected — WebGPU adapter available.'
156
+ :'No WebGPU adapter available to this page.';
157
+ gpuAvailability.dataset.status=result.available&&result.isFallbackAdapter!==true
158
+ ?'success':'warning';
159
+ gpuAdapterName.textContent=result.available?result.name:'';
160
+ return result;
161
+ }catch(error){
162
+ if(!destroyed){
163
+ gpuAvailability.textContent='GPU detection failed. Select Detect GPU to try again.';
164
+ gpuAvailability.dataset.status='error';
165
+ arcaneLogging.error('Unable to detect the browser GPU adapter.',error);
166
+ }
167
+ return false;
168
+ }
169
+ }
170
+ }
171
+
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(function matchSetupBrowser(browser){
177
+ return browser.browserId===selectedId;
178
+ });
179
+ if(!target){
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}`
189
+ );
190
+ status.textContent='If the flags page did not open, copy its address above and paste it into your browser\'s address bar.';
191
+ }
192
+
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)button.disabled=true;
198
+ copyTask=writeAddress().finally(function finishAddressCopy(){
199
+ copyTask=null;
200
+ if(!destroyed){
201
+ for(const button of copyButtons)button.disabled=false;
202
+ }
203
+ });
204
+ return copyTask;
205
+
206
+ async function writeAddress(){
207
+ try{
208
+ await globalThis.navigator.clipboard.writeText(address);
209
+ if(destroyed)return false;
210
+ copyStatus.textContent='Address copied. Paste it into your browser\'s address bar.';
211
+ return true;
212
+ }catch(error){
213
+ if(!destroyed){
214
+ copyStatus.textContent='The address could not be copied automatically. Select the complete address above, copy it manually, and paste it into your browser\'s address bar.';
215
+ arcaneLogging.error('Unable to copy the browser flags address.',error);
216
+ }
217
+ return false;
218
+ }
219
+ }
220
+ }
221
+
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('h3');
226
+ const address=document.createElement('a');
227
+ const actions=document.createElement('div');
228
+ const copyButton=document.createElement('button');
229
+ row.className='flag-choice';
230
+ heading.textContent=target.name;
231
+ address.className='flag-address';
232
+ address.href=target.url;
233
+ address.textContent=target.url;
234
+ address.setAttribute('aria-label',`${target.name} WebNN flags address: ${target.url}`);
235
+ actions.className='actions';
236
+ copyButton.type='button';
237
+ copyButton.className='arcane-button arcane-button--secondary';
238
+ copyButton.textContent=`Copy ${target.name} address`;
239
+ copyButton.addEventListener('click',function copySelectedBrowserAddress(){
240
+ return copyAddress(target.url);
241
+ },{signal:lifecycleController.signal});
242
+ actions.append(copyButton);
243
+ row.append(heading,address,actions);
244
+ addressList.append(row);
245
+ choices.push({row,target});
246
+ copyButtons.push(copyButton);
247
+ }
248
+
249
+ copyGpuButton.addEventListener('click',function copyGpuFlagsAddress(){
250
+ const target=getBrowserDeviceSettings().highPerformanceGpu;
251
+ if(target)return copyAddress(target.url);
252
+ },{signal:lifecycleController.signal});
253
+ detectGpuButton.addEventListener('click',checkGpu,{signal:lifecycleController.signal});
254
+ const stepList=root.querySelector('#setupSteps');
255
+ for(const step of setupSteps){
256
+ const item=document.createElement('li');
257
+ item.textContent=step;
258
+ stepList.append(item);
259
+ }
260
+
261
+ root.querySelector('#refreshAvailability').addEventListener('click',function refreshBrowserAvailability(){
262
+ refresh();
263
+ status.textContent='Browser API availability refreshed.';
264
+ return checkGpu();
265
+ },{signal:lifecycleController.signal});
266
+ host.ownerDocument.defaultView.addEventListener('pagehide',function disposeBeforePageUnload(event){
267
+ if(!event.persisted)destroy();
268
+ },{
269
+ signal:lifecycleController.signal
270
+ });
271
+
272
+ function destroy(){
273
+ if(destroyed)return false;
274
+ destroyed=true;
275
+ host.ready=false;
276
+ lifecycleController.abort('browser-ai-setup-destroyed');
277
+ events.dispose();
278
+ return true;
279
+ }
280
+
281
+ refresh();
282
+ checkGpu();
283
+ host.ready=true;
284
+ const publication=events.dispatch('browser-ai-setup-ready',{}, {
285
+ publicDetail:{ready:true}
286
+ });
287
+ projectArcaneDOMEvent(host,publication.occurrence);
288
+ </script>