arcane-os 0.5.9 → 0.5.11

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +117 -26
  3. package/browser-runtime/ai/browser-speech-providers.mjs +1 -1
  4. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +63 -39
  5. package/docs/architecture.md +303 -0
  6. package/docs/compatibility.md +38 -0
  7. package/docs/event-manager.md +263 -0
  8. package/docs/platform-targets.md +104 -0
  9. package/docs/publishing.md +126 -0
  10. package/docs/reference/README.md +206 -0
  11. package/docs/reference/ai/browser-speech.md +813 -0
  12. package/docs/reference/ai/browser-wasm.md +637 -0
  13. package/docs/reference/ai/twin-cloud.md +156 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +224 -0
  16. package/docs/reference/behavioral-testing.md +129 -0
  17. package/docs/reference/cli.md +820 -0
  18. package/docs/reference/core/README.md +61 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +907 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +59 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +1409 -0
  32. package/docs/reference/inventory/package-api.json +3194 -0
  33. package/docs/reference/inventory/runtime-components.json +1015 -0
  34. package/docs/reference/inventory/runtime-entities.json +25 -0
  35. package/docs/reference/inventory/runtime-modules.json +1367 -0
  36. package/docs/reference/mail.md +309 -0
  37. package/docs/reference/protocols.md +749 -0
  38. package/docs/reference/runtime-components.md +1529 -0
  39. package/docs/reference/runtime-entities.md +305 -0
  40. package/docs/reference/runtime-modules.md +3275 -0
  41. package/docs/reference/sdk-api.md +6733 -0
  42. package/docs/roadmap.md +79 -0
  43. package/docs/work-amplification.md +66 -0
  44. package/examples/wasm-ai-demo/README.md +80 -0
  45. package/examples/wasm-ai-demo/app.js +787 -0
  46. package/examples/wasm-ai-demo/index.html +343 -0
  47. package/examples/wasm-ai-demo/profile-tools.js +217 -0
  48. package/examples/wasm-ai-demo/profiles/BOSS.Modelfile +106 -0
  49. package/examples/wasm-ai-demo/profiles/PreCrisis.Modelfile +693 -0
  50. package/examples/wasm-ai-demo/rag/boss-library.json +3006 -0
  51. package/examples/wasm-ai-demo/rag.js +295 -0
  52. package/examples/wasm-ai-demo/server.mjs +71 -0
  53. package/package.json +11 -2
  54. package/runtime/arcane/modules/AI.js +1 -1
  55. package/runtime/arcane/modules/AIProviderRuntime.js +26 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.11
4
+
5
+ - Default browser Kokoro TTS to four concurrent synthesis slots in both the
6
+ high-level AI configuration and direct provider. Explicit capacities 1–4
7
+ remain supported; LLM and Whisper/STT capacity remains one. Overflow waits
8
+ in the provider-neutral FIFO queue, and playback retains exact input order
9
+ with contiguous AudioContext scheduling. Each slot owns a Worker/model
10
+ session, trading memory for latency without promising physical GPU overlap.
11
+ - Add opt-in live execution details through
12
+ `ai.providerRuntime.status('tts', {execution: true}).execution`, including
13
+ requested and selected device, capacity, and active requests. Existing
14
+ no-option status snapshots keep their behavior; automatic WASM fallback is
15
+ observable without private provider access.
16
+ - Front-load basic browser speech and TWiN examples, document exact saved
17
+ preference migration, refresh current reference inventories and generated
18
+ site content, and include maintained `docs/` and the browser AI demo source
19
+ in the installed package. Documentation deployment now uses an explicit
20
+ selected-main Pages workflow.
21
+
22
+ ## 0.5.10
23
+
24
+ - Show one Windows performance-GPU flag advisory for the current Chromium
25
+ browser, including Edge, Brave, Opera, and Vivaldi. Browsers that conceal
26
+ their identity use their own flags page through the generic `about://` address.
27
+ - Make the notice conditional on having multiple GPUs, cover every observed
28
+ GPU vendor, and omit the notice on Firefox, Safari, mobile, and non-Windows
29
+ platforms. No additional GPU flags or automatic settings changes are included.
30
+
3
31
  ## 0.5.9
4
32
 
5
33
  - Renamed the built-in TWiN Cloud provider and default-model preference sentinel
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.5.9` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.5.11` 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
 
@@ -29,16 +29,99 @@ The app repository's exact dependency and lockfile select the SDK; changing that
29
29
  selection is an explicit repository update. Tests and checks run only when the
30
30
  user expressly selects them, or when required for a selected release output.
31
31
 
32
+ ## Beginner quick start
33
+
34
+ Create one browser application, install its pinned SDK, and start its source
35
+ server:
36
+
37
+ ```bash
38
+ npx arcane-os@0.5.11 new hello-speech --path ./hello-speech --target browser
39
+ cd hello-speech
40
+ npm install
41
+ npm run dev
42
+ ```
43
+
44
+ Open the URL printed by the server. The generated page owns its import map and
45
+ Arcane theme; its application module is `apps/hello-speech/modules/App.js`.
46
+ `arcane/AI` is a managed **browser import**, not an npm-exported Node inference
47
+ module.
48
+
49
+ For a first spoken sentence, copy the application-owned
50
+ [`speech-selection.js` configuration from the speech quick start](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/ai/browser-speech.md#quick-start-say-one-sentence)
51
+ beside `App.js`, then use this module. That one configuration file defines the
52
+ upstream runtime, model, dtype, and voice. This module creates the application's
53
+ DBOPFS store; the SDK creates and manages its speech providers and Workers.
54
+
55
+ ```javascript
56
+ import arcaneThemeReady from 'arcane/ThemeBootstrap';
57
+ import AI, { AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL } from 'arcane/AI';
58
+ import DBOPFS from 'arcane/DBOPFS';
59
+ import { speechSelection } from './speech-selection.js';
60
+
61
+ await arcaneThemeReady;
62
+ const dbopfs = new DBOPFS();
63
+ await dbopfs.readyPromise;
64
+ const ai = new AI();
65
+ await ai.configureBrowserSpeech({
66
+ protocol: AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL,
67
+ id: 'hello-speech',
68
+ dbopfs,
69
+ tts: {
70
+ providerId: 'hello-kokoro',
71
+ model: speechSelection.model,
72
+ runtime: speechSelection.runtime,
73
+ offline: false
74
+ }
75
+ });
76
+
77
+ const button = document.createElement('button');
78
+ button.textContent = 'Load voice and say hello';
79
+ document.body.append(button);
80
+ button.addEventListener('click', async function sayHello() {
81
+ button.disabled = true;
82
+ try {
83
+ await ai.setSpeechMuted(false);
84
+ console.log(ai.providerRuntime.status('tts', { execution: true }).execution);
85
+ const prepared = await ai.streamTTS('Hello from Arcane. ', true);
86
+ console.log('Speech preparation completed:', prepared);
87
+ } catch (error) {
88
+ console.error(error.code, error.message);
89
+ } finally {
90
+ button.disabled = false;
91
+ }
92
+ });
93
+ ```
94
+
95
+ The button explicitly loads/unmutes the selected model and requests speech.
96
+ The first load can download the app-selected runtime/model/voice; the browser
97
+ may need another audio-unlock gesture after a long load. Keep the complete
98
+ visual response available alongside speech.
99
+
100
+ Capacity 4 means up to four segments synthesize at once. Segment 5 and later
101
+ wait in the SDK's FIFO queue; they are not dropped. Synthesis may finish out of
102
+ order, but playback waits for earlier segments and plays exact input order.
103
+ Each slot owns a Worker/model session, so raising capacity trades memory for
104
+ latency.
105
+
106
+ Continue with [streaming chunks, device selection, cancellation, status, and cleanup](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/ai/browser-speech.md),
107
+ the [tiny TWiN Cloud request and saved-preference migration](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/ai/twin-cloud.md),
108
+ or the [maintained WASM voice-chat example](https://github.com/TheWizardNexus/arcane-os-sdk/tree/main/examples/wasm-ai-demo).
109
+ The installed package also includes `docs/` and the maintained example source:
110
+ open `node_modules/arcane-os/docs/reference/ai/browser-speech.md`,
111
+ `node_modules/arcane-os/docs/reference/ai/twin-cloud.md`, or
112
+ `node_modules/arcane-os/examples/wasm-ai-demo/README.md` locally. Public links
113
+ above work from both npm and GitHub.
114
+
32
115
  ## Developer API reference
33
116
 
34
- Start with the [capability-first developer reference](docs/reference/README.md).
117
+ Start with the [capability-first developer reference](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/README.md).
35
118
  It follows Arcane's MDN-style model and covers every public package export, CLI
36
119
  command, synchronized runtime module, entity, component, Arcane Core member,
37
- and Arcane Ollama method. Use the [availability and normalization matrix](docs/reference/availability-and-normalization.md)
120
+ and Arcane Ollama method. Use the [availability and normalization matrix](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/availability-and-normalization.md)
38
121
  to distinguish Node, browser, native, cloud, and cross-host behavior; protocol
39
- mechanics are kept in the folded/deep-linked [protocol guide](docs/reference/protocols.md).
40
- The [behavioral-testing guide](docs/reference/behavioral-testing.md) explains the
41
- executable contract, while the [machine-readable inventories](docs/reference/inventory/)
122
+ mechanics are kept in the folded/deep-linked [protocol guide](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/protocols.md).
123
+ The [behavioral-testing guide](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/behavioral-testing.md) explains the
124
+ executable contract, while the [machine-readable inventories](https://github.com/TheWizardNexus/arcane-os-sdk/tree/main/docs/reference/inventory/)
42
125
  make completeness independently checkable.
43
126
 
44
127
  ## Central event instrumentation
@@ -54,7 +137,7 @@ off-by-default diagnostic option.
54
137
 
55
138
  Recording is off by default. Once explicitly enabled, a session retains its
56
139
  complete recorded content until the caller clears it or disables recording.
57
- Review [the EventManager guide](docs/event-manager.md)
140
+ Review [the EventManager guide](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/event-manager.md)
58
141
  before enabling DOM values, node content, event details, source stacks, or live
59
142
  event redispatch. Password targets, text-entry details, clipboard data, URL
60
143
  attributes, and common credential keys are excluded or redacted by default.
@@ -90,10 +173,18 @@ Kokoro (`LOCAL_SPEACH` / `kokoro`) owns speech synthesis. Neither audio route
90
173
  uses the TWiN key, and neither requires a cloud audio key.
91
174
 
92
175
  The built-in provider and default-model preference sentinel are both `TWIN`.
93
- Applications upgrading saved `OPENAI` LLM selections must explicitly update
94
- those app-owned settings to `TWIN`. The SDK provides no built-in `OPENAI` alias
95
- and does not rewrite saved preferences. Actual upstream model identifiers remain
96
- unchanged.
176
+ Applications upgrading saved preference tuples must replace only uppercase
177
+ `OPENAI` in slot 0 (LLM provider) and slot 3 (default-model sentinel) with
178
+ `TWIN` through the existing application preference owner before importing
179
+ `AI.js` or any module that imports it, and before AI preference hydration.
180
+ Use dynamic import after migration; module evaluation can instantiate AI
181
+ immediately when the user is already ready. Keep every other
182
+ value unchanged, including `openai-gpt-oss-120b` and `openai-gpt-oss-20b`.
183
+ The SDK provides no built-in `OPENAI` alias and does not rewrite saved
184
+ preferences. OpenAI-compatible wire terminology and the separate Core
185
+ `provider:'openai'` contract are unchanged. The
186
+ [TWiN quick start and migration recipe](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/ai/twin-cloud.md)
187
+ show the exact application-owned operation.
97
188
 
98
189
  `fetchRequest()` and `streamRequest()` accept the provider-neutral
99
190
  `reasoningEffort` option with `none`, `low`, `medium`, `high`, or `max`. TWiN
@@ -110,6 +201,8 @@ profile. Each descriptor uses one ordered `files` array, so monolithic and
110
201
  split GGUF models share the same contract:
111
202
 
112
203
  ```js
204
+ import { createBrowserModelSource } from 'arcane-os/ai/browser-wasm';
205
+
113
206
  const source=createBrowserModelSource({
114
207
  id:'app-model',
115
208
  files:[
@@ -157,7 +250,7 @@ provider factories. The package contains the plain-JavaScript provider and
157
250
  Worker machinery, not speech runtimes, models, voices, or a CDN default. An app
158
251
  must supply each runtime/model selection explicitly. Speech roles
159
252
  load, cancel, unload, fail, and recover independently, so speech failure never
160
- silently falls back or prevents text chat. Kokoro defaults to a two-slot Worker
253
+ silently falls back or prevents text chat. Kokoro defaults to a four-slot Worker
161
254
  and model-session pool: it selects WebGPU when the browser can load the complete
162
255
  pool and otherwise recreates that pool on WASM. Apps may select `webgpu` or
163
256
  `wasm` explicitly and may set the bounded TTS capacity from one through four.
@@ -204,10 +297,9 @@ uses the same controller for automatic memory extraction.
204
297
  Create a new repository-shaped Arcane application with the exact stable SDK:
205
298
 
206
299
  ```bash
207
- npx arcane-os@0.5.9 new my-app --path ./my-app --target portable --git
300
+ npx arcane-os@0.5.11 new my-app --path ./my-app --target portable --git
208
301
  cd my-app
209
302
  npm install
210
- npm run check
211
303
  npm run dev
212
304
  ```
213
305
 
@@ -215,7 +307,7 @@ To enroll an existing repository, install the exact SDK and initialize only
215
307
  missing Arcane files:
216
308
 
217
309
  ```bash
218
- npm install --save-dev --save-exact arcane-os@0.5.9
310
+ npm install --save-dev --save-exact arcane-os@0.5.11
219
311
  npm exec -- arcane init my-app --target portable
220
312
  ```
221
313
 
@@ -231,7 +323,7 @@ npm exec -- arcane-os targets
231
323
  No global SDK install or standalone Arcane CLI is required. The application
232
324
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
233
325
 
234
- Use `npx arcane-os@0.5.9` for the initial bootstrap because it names this npm
326
+ Use `npx arcane-os@0.5.11` for the initial bootstrap because it names this npm
235
327
  package explicitly; bare `npx arcane` outside an installed project could resolve
236
328
  a different package. Both installed commands invoke the same headless toolchain.
237
329
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -246,14 +338,12 @@ tarball install in the app's package manifest and lock:
246
338
  ```bash
247
339
  # From the arcane-os-sdk checkout
248
340
  npm ci
249
- npm run check
250
341
  npm run pack:local
251
342
  node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
252
343
 
253
344
  # From the generated app repository
254
345
  cd ../local-app
255
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.9.tgz
256
- npm run check
346
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.11.tgz
257
347
  npm ci
258
348
  ```
259
349
 
@@ -262,7 +352,7 @@ same location. The lockfile retains the selected package dependency while
262
352
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
263
353
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
264
354
  runner also needs that tarball at the locked path. After publication, replace
265
- the local declaration with the exact `arcane-os@0.5.9` registry package and
355
+ the local declaration with the exact `arcane-os@0.5.11` registry package and
266
356
  commit the regenerated lock.
267
357
 
268
358
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -393,14 +483,15 @@ npm run test:integration
393
483
  npm run test:regression
394
484
  ```
395
485
 
396
- Use a named set while iterating or `npm test` for the complete suite. Large
486
+ Run a named set or `npm test` only when explicitly selecting that verification,
487
+ or when required for a selected release output. Large
397
488
  fixtures stay inside one isolated file process and expose smaller nested cases,
398
489
  so the report shows the individual behaviors without repeating setup, builds,
399
490
  package installation, or assertions.
400
491
 
401
492
  ## Current target support
402
493
 
403
- Version `0.5.9` exposes one browser target and five explicitly paired
494
+ Version `0.5.11` exposes one browser target and five explicitly paired
404
495
  native development targets: a non-runnable portable directory, a
405
496
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
406
497
  unsigned-local-test DEBs, and an Android development-signed APK. The
@@ -502,11 +593,11 @@ artifact. Portable output is never represented as an executable, and unsigned
502
593
  or development-signed evidence is never represented as production signing or
503
594
  release acceptance.
504
595
 
505
- See [docs/platform-targets.md](docs/platform-targets.md) for the matrix and
506
- [docs/architecture.md](docs/architecture.md) for the boundary. The exact
596
+ See [docs/platform-targets.md](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/platform-targets.md) for the matrix and
597
+ [docs/architecture.md](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/architecture.md) for the boundary. The exact
507
598
  minimum-version and required-contract compatibility rule is documented in
508
- [docs/compatibility.md](docs/compatibility.md). The issue-ready
509
- extraction sequence is tracked in [docs/roadmap.md](docs/roadmap.md).
599
+ [docs/compatibility.md](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/compatibility.md). The issue-ready
600
+ extraction sequence is tracked in [docs/roadmap.md](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/roadmap.md).
510
601
 
511
602
  ## Canonical app descriptor
512
603
 
@@ -21,7 +21,7 @@ const TTS_SAMPLE_RATE = 24_000;
21
21
  const TTS_RESPONSE_FORMAT = "wav";
22
22
  const TTS_EXECUTION_DEVICES = new Set(["auto", "webgpu", "wasm"]);
23
23
  const DEFAULT_TTS_EXECUTION_DEVICE = "auto";
24
- const DEFAULT_TTS_MAX_CONCURRENT_REQUESTS = 2;
24
+ const DEFAULT_TTS_MAX_CONCURRENT_REQUESTS = 4;
25
25
  const MAX_TTS_CONCURRENT_REQUESTS = 4;
26
26
  const ROLE_REQUEST_REASON = completeValue({
27
27
  stt: "stt-transcription-cancelled",
@@ -20,15 +20,12 @@ const AI_PROVIDER_PROTOCOL = "arcane-ai-provider/2";
20
20
  const AI_MODEL_AUTHORITY_PROTOCOL = "arcane-ai-model-authority/1";
21
21
  const WEBGPU_ADAPTER_SELECTED_EVENT = "arcane.ai.browser-wasm.webgpu.adapter.selected";
22
22
  const WEBGPU_ADAPTER_SELECTION_PROTOCOL = "arcane-ai-webgpu-adapter-selection/1";
23
- const CHROME_HIGH_PERFORMANCE_GPU_FLAG_URL =
24
- "chrome://flags/#force-high-performance-gpu";
25
23
  const MODEL_LOAD_HEARTBEAT_MS = 5_000;
26
24
  const DEFAULT_MODEL_DOWNLOAD_CONCURRENCY = 4;
27
25
  const MODEL_DOWNLOAD_PROGRESS_INTERVAL_MS = 250;
28
26
  const MODEL_DOWNLOAD_SPEED_WINDOW_MS = 5_000;
29
27
  const MODEL_DOWNLOAD_MAX_RANGE_PARTS = 4_096;
30
28
  const MODEL_DOWNLOAD_TARGET_RANGE_BYTES = 4_000_000;
31
- const INTEL_VENDOR_ID = 0x8086;
32
29
  const CAPABILITY_POLICY_PROTOCOL = "arcane-ai-browser-capability-policy/1";
33
30
  let highPerformanceGpuNoticeShown = false;
34
31
 
@@ -263,44 +260,71 @@ function publicDescriptor(source) {
263
260
  return modelDescriptor(source);
264
261
  }
265
262
 
266
- function isChromeBrowser() {
267
- const userAgent = String(globalThis.navigator?.userAgent ?? "");
268
- return /\b(?:Chrome|Chromium)\//u.test(userAgent)
269
- && !/\b(?:Edg|OPR)\//u.test(userAgent);
270
- }
263
+ function highPerformanceGpuBrowser() {
264
+ const navigatorObject = globalThis.navigator;
265
+ const userAgent = String(navigatorObject?.userAgent ?? "");
266
+ const clientHints = navigatorObject?.userAgentData;
267
+ const platform = String(clientHints?.platform || navigatorObject?.platform || userAgent);
268
+ if (
269
+ !/\b(?:Windows|Win32|Win64)\b/iu.test(platform)
270
+ || clientHints?.mobile === true
271
+ || /\b(?:Android|iPhone|iPad|iPod|Mobile)\b/iu.test(userAgent)
272
+ ) {
273
+ return null;
274
+ }
271
275
 
272
- function isLowerPowerIntelAdapter(adapter) {
273
- const identity = [
274
- adapter.vendor,
275
- adapter.architecture,
276
- adapter.name,
277
- adapter.description,
278
- ].filter(Boolean).join(" ");
279
- if (adapter?.vendorId !== INTEL_VENDOR_ID && !/\bintel\b/iu.test(identity)) return false;
280
- return /(?:intel|integrated|xe-lp)/iu.test(identity);
276
+ const brands = new Set();
277
+ for (const entry of clientHints?.brands ?? []) {
278
+ brands.add(entry.brand);
279
+ }
280
+ // Vivaldi can append its identity while also identifying as Edge or Chrome.
281
+ if (brands.has("Vivaldi") || /\bVivaldi\//u.test(userAgent)) {
282
+ return { name: "Vivaldi", url: "vivaldi://flags/#force-high-performance-gpu" };
283
+ }
284
+ if (brands.has("Brave") || typeof navigatorObject?.brave?.isBrave === "function") {
285
+ return { name: "Brave", url: "brave://flags/#force-high-performance-gpu" };
286
+ }
287
+ if (brands.has("Opera") || /\bOPR\//u.test(userAgent)) {
288
+ return { name: "Opera", url: "opera://flags/#force-high-performance-gpu" };
289
+ }
290
+ if (brands.has("Microsoft Edge") || /\bEdg\//u.test(userAgent)) {
291
+ return { name: "Microsoft Edge", url: "edge://flags/#force-high-performance-gpu" };
292
+ }
293
+ if (
294
+ brands.has("Chromium")
295
+ || brands.has("Google Chrome")
296
+ || /\b(?:Chrome|Chromium)\//u.test(userAgent)
297
+ ) {
298
+ // Some Chromium browsers mask their brand. about:// uses their own flags page.
299
+ return { name: "your browser", url: "about://flags/#force-high-performance-gpu" };
300
+ }
301
+ return null;
281
302
  }
282
303
 
283
- function notifyChromeHighPerformanceGpu(adapter) {
284
- if (
285
- highPerformanceGpuNoticeShown
286
- || !isChromeBrowser()
287
- || !isLowerPowerIntelAdapter(adapter)
288
- ) return;
289
- highPerformanceGpuNoticeShown = true;
290
- try {
291
- globalThis.open?.(CHROME_HIGH_PERFORMANCE_GPU_FLAG_URL, "_blank", "noopener,noreferrer");
292
- } catch {
293
- // Chrome may reject internal-page navigation from web content.
294
- }
295
- const adapterName = adapter.description || adapter.name
296
- || [adapter.vendor, adapter.architecture].filter(Boolean).join(" ")
297
- || "a lower-power Intel adapter";
298
- globalThis.alert?.(
299
- `Arcane selected the lower-power WebGPU adapter: ${adapterName}.\n\n`
300
- + "Enable “Force High Performance GPU” in the Chrome flags window. "
301
- + "Then completely close every Chrome window and reopen Chrome before loading the model again.\n\n"
302
- + `If the flags window did not open, paste ${CHROME_HIGH_PERFORMANCE_GPU_FLAG_URL} into Chrome.`,
303
- );
304
+ function notifyHighPerformanceGpu(adapter) {
305
+ if (highPerformanceGpuNoticeShown) {
306
+ return;
307
+ }
308
+ const browser = highPerformanceGpuBrowser();
309
+ if (!browser) {
310
+ return;
311
+ }
312
+ highPerformanceGpuNoticeShown = true;
313
+ try {
314
+ globalThis.open?.(browser.url, "_blank", "noopener,noreferrer");
315
+ } catch {
316
+ // Browsers may reject internal-page navigation from web content.
317
+ }
318
+ const adapterName = adapter.description || adapter.name
319
+ || [adapter.vendor, adapter.architecture].filter(Boolean).join(" ")
320
+ || "the available WebGPU adapter";
321
+ globalThis.alert?.(
322
+ `Selected WebGPU adapter: ${adapterName}.\n\n`
323
+ + `If this computer has multiple GPUs, enable “Force High Performance GPU” in ${browser.name} `
324
+ + "to request the high-performance GPU when available. "
325
+ + `Then completely close and reopen ${browser.name} before loading the model again.\n\n`
326
+ + `If the flags page did not open, paste ${browser.url} into the address bar.`,
327
+ );
304
328
  }
305
329
 
306
330
  function emitWebgpuAdapterSelection(source, runtime) {
@@ -322,7 +346,7 @@ function emitWebgpuAdapterSelection(source, runtime) {
322
346
  category: "capability",
323
347
  }));
324
348
  } finally {
325
- notifyChromeHighPerformanceGpu(webgpu.adapter);
349
+ notifyHighPerformanceGpu(webgpu.adapter);
326
350
  }
327
351
  }
328
352