arcane-os 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +25 -10
- package/browser-runtime/ai/browser-device-settings.mjs +21 -0
- package/docs/architecture.md +2 -2
- package/docs/reference/README.md +1 -1
- package/docs/reference/ai/browser-speech.md +25 -8
- package/docs/reference/cli.md +22 -6
- package/docs/reference/inventory/runtime-components.json +26 -2
- package/docs/reference/runtime-components.md +38 -15
- package/docs/reference/runtime-modules.md +1 -1
- package/docs/reference/sdk-api.md +16 -12
- package/package.json +1 -1
- package/runtime/arcane/components/browser-ai-setup.html +97 -21
- package/src/cli/main.mjs +14 -3
- package/src/dev-server.mjs +32 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- Add `arcane dev --public`, including forwarding through
|
|
6
|
+
`npm run dev -- --app <id> --public`, to bind the selected app's existing
|
|
7
|
+
source server to all IPv4 interfaces. Keep the ordinary localhost default
|
|
8
|
+
and let an explicit `--host` select the bind address.
|
|
9
|
+
- Allow explicit network hosts in the shared development server. Report usable
|
|
10
|
+
local and network application URLs, including `networkUrls` in server results
|
|
11
|
+
and lifecycle events, while preserving selected routes and cancellation.
|
|
12
|
+
- Document LAN use and the browser HTTPS requirement for features that need a
|
|
13
|
+
secure context. No app-specific server, firewall rule, or tunnel is added.
|
|
14
|
+
|
|
15
|
+
## 0.6.2
|
|
16
|
+
|
|
17
|
+
- Replace browser NPU setup buttons with direct browser-specific flags links
|
|
18
|
+
and retain copy-address controls and the public `open()` method.
|
|
19
|
+
- Add the missing GPU performance flags link, setup instructions, and GPU
|
|
20
|
+
detection control. Chrome uses its own `chrome://` flags address.
|
|
21
|
+
- Show adapter availability and its reported name after one shared detection
|
|
22
|
+
request. Keep fallback and failed detection visible without claiming that
|
|
23
|
+
the browser performance flag is enabled or a model is running on that GPU.
|
|
24
|
+
Detection creates no device, loads no model, and changes no browser settings.
|
|
25
|
+
|
|
3
26
|
## 0.6.1
|
|
4
27
|
|
|
5
28
|
- Add a reusable profile component for browser NPU setup and WebNN/WebGPU API
|
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.
|
|
22
|
+
This checkout defines the `0.7.0` 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,12 +35,27 @@ 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.
|
|
38
|
+
npx arcane-os@0.7.0 new hello-speech --path ./hello-speech --target browser
|
|
39
39
|
cd hello-speech
|
|
40
40
|
npm install
|
|
41
41
|
npm run dev
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
To open a selected app from another device on the network, append `--public`
|
|
45
|
+
to the same command:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run dev -- --app hello-speech --public
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Replace `hello-speech` with any app in the workspace. The server binds to
|
|
52
|
+
`0.0.0.0` and prints network URLs to open on the other device. Plain `npm run dev`
|
|
53
|
+
keeps its localhost default. An explicit `--host` overrides the public bind
|
|
54
|
+
address, and `--port` selects the port. Network reachability depends on the
|
|
55
|
+
machine's firewall and network; the command does not configure internet hosting
|
|
56
|
+
or HTTPS. Browser features requiring a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts)
|
|
57
|
+
need HTTPS when opened through a LAN address.
|
|
58
|
+
|
|
44
59
|
Open the URL printed by the server. The generated page owns its import map and
|
|
45
60
|
Arcane theme; its application module is `apps/hello-speech/modules/App.js`.
|
|
46
61
|
`arcane/AI` is a managed **browser import**, not an npm-exported Node inference
|
|
@@ -363,7 +378,7 @@ uses the same controller for automatic memory extraction.
|
|
|
363
378
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
364
379
|
|
|
365
380
|
```bash
|
|
366
|
-
npx arcane-os@0.
|
|
381
|
+
npx arcane-os@0.7.0 new my-app --path ./my-app --target portable --git
|
|
367
382
|
cd my-app
|
|
368
383
|
npm install
|
|
369
384
|
npm run dev
|
|
@@ -373,7 +388,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
373
388
|
missing Arcane files:
|
|
374
389
|
|
|
375
390
|
```bash
|
|
376
|
-
npm install --save-dev --save-exact arcane-os@0.
|
|
391
|
+
npm install --save-dev --save-exact arcane-os@0.7.0
|
|
377
392
|
npm exec -- arcane init my-app --target portable
|
|
378
393
|
```
|
|
379
394
|
|
|
@@ -389,7 +404,7 @@ npm exec -- arcane-os targets
|
|
|
389
404
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
390
405
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
391
406
|
|
|
392
|
-
Use `npx arcane-os@0.
|
|
407
|
+
Use `npx arcane-os@0.7.0` for the initial bootstrap because it names this npm
|
|
393
408
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
394
409
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
395
410
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -409,7 +424,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
409
424
|
|
|
410
425
|
# From the generated app repository
|
|
411
426
|
cd ../local-app
|
|
412
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.
|
|
427
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.7.0.tgz
|
|
413
428
|
npm ci
|
|
414
429
|
```
|
|
415
430
|
|
|
@@ -418,7 +433,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
418
433
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
419
434
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
420
435
|
runner also needs that tarball at the locked path. After publication, replace
|
|
421
|
-
the local declaration with the exact `arcane-os@0.
|
|
436
|
+
the local declaration with the exact `arcane-os@0.7.0` registry package and
|
|
422
437
|
commit the regenerated lock.
|
|
423
438
|
|
|
424
439
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -475,7 +490,7 @@ arcane new <id> [--path <directory>] [--display-name <name>] [--target <target>]
|
|
|
475
490
|
arcane init [id] [--workspace <directory>] [--display-name <name>] [--target <target>]
|
|
476
491
|
arcane doctor [--workspace <directory>] [--arcane-root <directory>]
|
|
477
492
|
arcane import-map [--workspace <directory>] [--app <id>]
|
|
478
|
-
arcane dev [--app <id>] [--host
|
|
493
|
+
arcane dev [--app <id>] [--public] [--host <address>] [--port 8000]
|
|
479
494
|
arcane test [--app <id>] [--scope app]
|
|
480
495
|
arcane test --scope shared --test-file <repo-relative.test.mjs>
|
|
481
496
|
arcane check [--app <id>] [--scope app] [--skip-tests]
|
|
@@ -557,7 +572,7 @@ package installation, or assertions.
|
|
|
557
572
|
|
|
558
573
|
## Current target support
|
|
559
574
|
|
|
560
|
-
Version `0.
|
|
575
|
+
Version `0.7.0` exposes one browser target and five explicitly paired
|
|
561
576
|
native development targets: a non-runnable portable directory, a
|
|
562
577
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
563
578
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
|
@@ -683,7 +698,7 @@ read-only managed-service assessment where the host supports one. Native apps
|
|
|
683
698
|
use an app-scoped Arcane Core and `Arcane.localAI.status()` in this SDK version.
|
|
684
699
|
Ollama is optional for packaging and non-AI applications.
|
|
685
700
|
|
|
686
|
-
The
|
|
701
|
+
The development server serves complete selected files and does not
|
|
687
702
|
apply byte-count or truncation gates. Optional hardening is inactive unless the
|
|
688
703
|
caller explicitly selects `secure:true`. Required credential protection,
|
|
689
704
|
malformed-request rejection, and unavoidable browser or operating-system safety
|
|
@@ -63,6 +63,7 @@ export function getBrowserDeviceSettings(navigatorObject = globalThis.navigator)
|
|
|
63
63
|
) {
|
|
64
64
|
browserId = "chrome";
|
|
65
65
|
name = "Google Chrome";
|
|
66
|
+
highPerformanceGpu = { name, url: "chrome://flags/#force-high-performance-gpu" };
|
|
66
67
|
} else {
|
|
67
68
|
browserId = "chromium";
|
|
68
69
|
name = "your Chromium browser";
|
|
@@ -81,3 +82,23 @@ export function getBrowserDeviceSettings(navigatorObject = globalThis.navigator)
|
|
|
81
82
|
webgpuAvailable: Boolean(navigatorObject?.gpu),
|
|
82
83
|
};
|
|
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
|
+
}
|
package/docs/architecture.md
CHANGED
|
@@ -236,7 +236,7 @@ Packaging writes the complete schema-1 release inventory to
|
|
|
236
236
|
`ARCANE_APP_RELEASE.json`. Ordinary development, packaging, serving, and run
|
|
237
237
|
paths do not count, limit, hash, truncate, tail, clip, or identify content by
|
|
238
238
|
bytes, and they do not require provenance or verification receipts. The
|
|
239
|
-
|
|
239
|
+
development server reads and returns each complete selected source, runtime, or
|
|
240
240
|
packaged file. Required credential protection, malformed-input rejection,
|
|
241
241
|
applicable law, unavoidable protocol rules, and operating-system or browser
|
|
242
242
|
safety remain in effect.
|
|
@@ -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.
|
|
282
|
+
The SDK `0.7.0` 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
|
package/docs/reference/README.md
CHANGED
|
@@ -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
|
-
-
|
|
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,11 +130,12 @@ 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 setup
|
|
133
|
+
## Browser NPU and GPU setup
|
|
134
134
|
|
|
135
135
|
Applications can place the shared `browser-ai-setup.html` component in their
|
|
136
136
|
profile or settings page. It reports whether this page exposes WebNN and
|
|
137
|
-
WebGPU,
|
|
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:
|
|
138
139
|
|
|
139
140
|
```html
|
|
140
141
|
<html-import
|
|
@@ -143,15 +144,31 @@ WebGPU, without loading a model or creating an accelerator context:
|
|
|
143
144
|
</html-import>
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
browsers receive explicit Chrome and Edge choices instead of an assumed target.
|
|
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
152
|
The [ONNX Runtime WebNN guide](https://onnxruntime.ai/docs/tutorials/web/ep-webnn.html)
|
|
153
153
|
documents the **Enables WebNN API** flag and model/operator requirements.
|
|
154
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
|
+
|
|
155
172
|
This control does not save an execution preference, change browser settings,
|
|
156
173
|
restart the browser, or report that the NPU is active. A WebNN API presence
|
|
157
174
|
result is not proof of NPU hardware, a compatible model, or physical execution.
|
package/docs/reference/cli.md
CHANGED
|
@@ -49,6 +49,7 @@ meaning and cardinality rules:
|
|
|
49
49
|
| `--app` | app id | Workspace/app operations except shared scope and `verify-bundle`; also the exact `mail serve` caller id. |
|
|
50
50
|
| `--arcane-root` | directory | `doctor`, native `build`/`run`, `native-doctor`, `native-prepare` |
|
|
51
51
|
| `--host` / `--port` | host / integer 0–65535 | Browser `dev`/`run` default to `127.0.0.1:8000`; `mail serve` defaults to `127.0.0.1:8025` and admits numeric loopback only. |
|
|
52
|
+
| `--public` | flag | `dev`; binds to `0.0.0.0` unless `--host` explicitly selects another address. |
|
|
52
53
|
| `--target` | target id | `new`, `init`, native diagnostics, `build`, `run` |
|
|
53
54
|
| `--format` / `--signing` | target-supported values | Native diagnostics, `build`, `run` |
|
|
54
55
|
| `--output-root` | directory | Native `build` and `run` |
|
|
@@ -307,9 +308,9 @@ npm exec -- arcane upgrade --workspace . --app hello-world
|
|
|
307
308
|
|
|
308
309
|
### Overview
|
|
309
310
|
|
|
310
|
-
Starts one
|
|
311
|
-
workspace/runtime routes. It
|
|
312
|
-
|
|
311
|
+
Starts one development server for one selected app and maps the exact
|
|
312
|
+
workspace/runtime routes. It defaults to localhost; `--public` enables access
|
|
313
|
+
from other devices on the network.
|
|
313
314
|
|
|
314
315
|
For an external workspace, the server exposes the selected projected
|
|
315
316
|
`arcane/` root, including `arcane/sdk` and `arcane/dependencies`, alongside the
|
|
@@ -318,20 +319,35 @@ The explicit live-source SDK mapping remains unchanged and does not replace the
|
|
|
318
319
|
installed projection.
|
|
319
320
|
|
|
320
321
|
```text
|
|
321
|
-
arcane dev [--app <id>] [--host
|
|
322
|
+
arcane dev [--app <id>] [--public] [--host <address>] [--port 8000]
|
|
322
323
|
```
|
|
323
324
|
|
|
324
325
|
### Lifecycle
|
|
325
326
|
|
|
326
327
|
The command reports acceptance before bind/start work, emits the final URL,
|
|
327
328
|
owns the server until cancellation, and restores failure to the process exit.
|
|
328
|
-
The default host is
|
|
329
|
-
|
|
329
|
+
The default host is `127.0.0.1`. `--public` selects `0.0.0.0` (all IPv4
|
|
330
|
+
interfaces); an explicit `--host` takes precedence. The command prints the
|
|
331
|
+
local URL and available network URLs. Use a printed network URL on the other
|
|
332
|
+
device, since `localhost` refers to that device and `0.0.0.0` is a bind address.
|
|
333
|
+
Network URLs come from one interface snapshot at startup and do not establish
|
|
334
|
+
remote reachability through the machine's firewall or network.
|
|
335
|
+
|
|
336
|
+
This option changes the listener address. It does not configure a firewall,
|
|
337
|
+
router forwarding, an internet tunnel, authentication, or HTTPS. Browser
|
|
338
|
+
features that require a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts)
|
|
339
|
+
still require HTTPS when accessed through a LAN address.
|
|
330
340
|
|
|
331
341
|
### Example
|
|
332
342
|
|
|
333
343
|
```bash
|
|
334
344
|
npm exec -- arcane dev --app hello-world --port 8000
|
|
345
|
+
|
|
346
|
+
# Forward the public option through an existing dev script, for any app id.
|
|
347
|
+
npm run dev -- --app hello-world --public
|
|
348
|
+
|
|
349
|
+
# Equivalent direct CLI invocation, with an optional port.
|
|
350
|
+
npm exec -- arcane dev --app hello-world --public --port 8000
|
|
335
351
|
```
|
|
336
352
|
|
|
337
353
|
## `arcane test`
|
|
@@ -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.
|
|
8
|
+
"sdkVersion": "0.6.2"
|
|
9
9
|
},
|
|
10
|
-
"componentCount":
|
|
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,7 +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
|
|
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 |
|
|
64
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 |
|
|
65
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 |
|
|
66
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 |
|
|
@@ -158,15 +158,18 @@ Slots: `title`, `subtitle`, `identity`, `messages/message`, `composer`, `actions
|
|
|
158
158
|
|
|
159
159
|
### Overview
|
|
160
160
|
|
|
161
|
-
Displays WebNN and WebGPU API availability for the current page
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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.
|
|
166
169
|
|
|
167
170
|
### Public surface
|
|
168
171
|
|
|
169
|
-
Methods/properties: `refresh()`, `open(browserId?)`, `destroy()`, `ready`.
|
|
172
|
+
Methods/properties: `refresh()`, `checkGpu()`, `open(browserId?)`, `destroy()`, `ready`.
|
|
170
173
|
|
|
171
174
|
Events: `browser-ai-setup-ready`.
|
|
172
175
|
|
|
@@ -176,6 +179,24 @@ the current settings record: `browserId`, `name`, `webnnFlagsURL`,
|
|
|
176
179
|
after destruction. Availability means that the browser exposes the API to this
|
|
177
180
|
page; it does not confirm a working adapter, loaded model, or hardware execution.
|
|
178
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
|
+
|
|
179
200
|
`open()` refreshes the display, attempts to open the detected Chrome or Edge flags
|
|
180
201
|
page, and presents an alert containing the full address and enable/relaunch
|
|
181
202
|
instructions. Pass `"chrome"` or `"edge"` to choose explicitly. Invoke it from a
|
|
@@ -185,17 +206,19 @@ succeeded. Browsers may block internal-page navigation, so the full addresses
|
|
|
185
206
|
also remain visible and copyable in the component.
|
|
186
207
|
|
|
187
208
|
`destroy()` aborts owned listeners, disposes the event source, marks `ready`
|
|
188
|
-
false, and suppresses UI updates from
|
|
189
|
-
|
|
190
|
-
|
|
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.
|
|
191
213
|
|
|
192
214
|
### Availability and normalization
|
|
193
215
|
|
|
194
|
-
**Browser and supported native WebViews.** API presence
|
|
195
|
-
are normalized; browser
|
|
196
|
-
platform-owned. Mounting
|
|
197
|
-
|
|
198
|
-
|
|
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.
|
|
199
222
|
|
|
200
223
|
### Example
|
|
201
224
|
|
|
@@ -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
|
|
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`.
|
|
@@ -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`
|
package/package.json
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
.flag-choice{margin-block:var(--arcane-space-4)}
|
|
9
9
|
.flag-choice h3{margin-block:var(--arcane-space-2)}
|
|
10
10
|
.actions{display:flex;flex-wrap:wrap;gap:var(--arcane-space-2)}
|
|
11
|
-
p,dt,dd,li,button{overflow-wrap:anywhere}
|
|
11
|
+
p,dt,dd,li,button,a{overflow-wrap:anywhere}
|
|
12
12
|
button{white-space:normal}
|
|
13
|
-
|
|
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}
|
|
14
15
|
li{margin-block:var(--arcane-space-2)}
|
|
15
16
|
</style>
|
|
16
17
|
|
|
@@ -31,6 +32,21 @@
|
|
|
31
32
|
<ol id="setupSteps"></ol>
|
|
32
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>
|
|
33
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>
|
|
34
50
|
<p id="copyStatus" class="arcane-help" role="status" aria-live="polite"></p>
|
|
35
51
|
</div>
|
|
36
52
|
<footer class="arcane-card__footer">
|
|
@@ -46,6 +62,7 @@
|
|
|
46
62
|
|
|
47
63
|
const {
|
|
48
64
|
getBrowserDeviceSettings,
|
|
65
|
+
detectBrowserGpu,
|
|
49
66
|
openBrowserDeviceSettings,
|
|
50
67
|
WEBNN_BROWSER_SETTINGS
|
|
51
68
|
}=await import('../sdk/ai/browser-device-settings.mjs');
|
|
@@ -58,6 +75,12 @@
|
|
|
58
75
|
const lifecycleController=new AbortController();
|
|
59
76
|
const status=root.querySelector('#setupStatus');
|
|
60
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];
|
|
61
84
|
const choices=[];
|
|
62
85
|
const setupSteps=[
|
|
63
86
|
'If the flags page does not open, copy the matching address into your browser\'s address bar and open it.',
|
|
@@ -66,9 +89,11 @@
|
|
|
66
89
|
'Return to this page and select Refresh browser availability.'
|
|
67
90
|
];
|
|
68
91
|
let copyTask=null;
|
|
92
|
+
let gpuTask=null;
|
|
69
93
|
let destroyed=false;
|
|
70
94
|
|
|
71
95
|
host.refresh=refresh;
|
|
96
|
+
host.checkGpu=checkGpu;
|
|
72
97
|
host.open=open;
|
|
73
98
|
host.destroy=destroy;
|
|
74
99
|
|
|
@@ -85,18 +110,65 @@
|
|
|
85
110
|
?'Exposed to this page.'
|
|
86
111
|
:'Unavailable to this page.';
|
|
87
112
|
root.querySelector('#browserInstructions').textContent=settings.webnnFlagsURL
|
|
88
|
-
?`
|
|
113
|
+
?`Open the ${settings.name} WebNN flags link below to set up NPU access.`
|
|
89
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.';
|
|
90
115
|
for(const choice of choices){
|
|
91
116
|
choice.row.hidden=Boolean(settings.webnnFlagsURL)
|
|
92
117
|
&&choice.target.url!==settings.webnnFlagsURL;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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='';
|
|
96
131
|
}
|
|
97
132
|
return settings;
|
|
98
133
|
}
|
|
99
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
|
+
|
|
100
172
|
function open(browserId){
|
|
101
173
|
if(destroyed)return false;
|
|
102
174
|
const current=refresh();
|
|
@@ -105,7 +177,7 @@
|
|
|
105
177
|
return browser.browserId===selectedId;
|
|
106
178
|
});
|
|
107
179
|
if(!target){
|
|
108
|
-
status.textContent='Choose the Google Chrome or Microsoft Edge
|
|
180
|
+
status.textContent='Choose the Google Chrome or Microsoft Edge flags link that matches your browser.';
|
|
109
181
|
return false;
|
|
110
182
|
}
|
|
111
183
|
const instructions=setupSteps.map(function numberSetupStep(step,index){
|
|
@@ -122,11 +194,11 @@
|
|
|
122
194
|
if(destroyed)return Promise.resolve(false);
|
|
123
195
|
if(copyTask)return copyTask;
|
|
124
196
|
copyStatus.textContent='Copying address...';
|
|
125
|
-
for(const
|
|
197
|
+
for(const button of copyButtons)button.disabled=true;
|
|
126
198
|
copyTask=writeAddress().finally(function finishAddressCopy(){
|
|
127
199
|
copyTask=null;
|
|
128
200
|
if(!destroyed){
|
|
129
|
-
for(const
|
|
201
|
+
for(const button of copyButtons)button.disabled=false;
|
|
130
202
|
}
|
|
131
203
|
});
|
|
132
204
|
return copyTask;
|
|
@@ -140,7 +212,7 @@
|
|
|
140
212
|
}catch(error){
|
|
141
213
|
if(!destroyed){
|
|
142
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.';
|
|
143
|
-
arcaneLogging.error('Unable to copy the browser
|
|
215
|
+
arcaneLogging.error('Unable to copy the browser flags address.',error);
|
|
144
216
|
}
|
|
145
217
|
return false;
|
|
146
218
|
}
|
|
@@ -151,32 +223,34 @@
|
|
|
151
223
|
for(const target of WEBNN_BROWSER_SETTINGS){
|
|
152
224
|
const row=document.createElement('section');
|
|
153
225
|
const heading=document.createElement('h3');
|
|
154
|
-
const address=document.createElement('
|
|
226
|
+
const address=document.createElement('a');
|
|
155
227
|
const actions=document.createElement('div');
|
|
156
|
-
const setupButton=document.createElement('button');
|
|
157
228
|
const copyButton=document.createElement('button');
|
|
158
229
|
row.className='flag-choice';
|
|
159
230
|
heading.textContent=target.name;
|
|
231
|
+
address.className='flag-address';
|
|
232
|
+
address.href=target.url;
|
|
160
233
|
address.textContent=target.url;
|
|
161
|
-
address.
|
|
162
|
-
address.setAttribute('aria-label',`${target.name} flags address`);
|
|
234
|
+
address.setAttribute('aria-label',`${target.name} WebNN flags address: ${target.url}`);
|
|
163
235
|
actions.className='actions';
|
|
164
|
-
setupButton.type='button';
|
|
165
|
-
setupButton.className='arcane-button';
|
|
166
|
-
setupButton.addEventListener('click',function openSelectedBrowserSettings(){
|
|
167
|
-
return open(target.browserId);
|
|
168
|
-
},{signal:lifecycleController.signal});
|
|
169
236
|
copyButton.type='button';
|
|
170
237
|
copyButton.className='arcane-button arcane-button--secondary';
|
|
171
238
|
copyButton.textContent=`Copy ${target.name} address`;
|
|
172
239
|
copyButton.addEventListener('click',function copySelectedBrowserAddress(){
|
|
173
240
|
return copyAddress(target.url);
|
|
174
241
|
},{signal:lifecycleController.signal});
|
|
175
|
-
actions.append(
|
|
242
|
+
actions.append(copyButton);
|
|
176
243
|
row.append(heading,address,actions);
|
|
177
244
|
addressList.append(row);
|
|
178
|
-
choices.push({row,target
|
|
245
|
+
choices.push({row,target});
|
|
246
|
+
copyButtons.push(copyButton);
|
|
179
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});
|
|
180
254
|
const stepList=root.querySelector('#setupSteps');
|
|
181
255
|
for(const step of setupSteps){
|
|
182
256
|
const item=document.createElement('li');
|
|
@@ -187,6 +261,7 @@
|
|
|
187
261
|
root.querySelector('#refreshAvailability').addEventListener('click',function refreshBrowserAvailability(){
|
|
188
262
|
refresh();
|
|
189
263
|
status.textContent='Browser API availability refreshed.';
|
|
264
|
+
return checkGpu();
|
|
190
265
|
},{signal:lifecycleController.signal});
|
|
191
266
|
host.ownerDocument.defaultView.addEventListener('pagehide',function disposeBeforePageUnload(event){
|
|
192
267
|
if(!event.persisted)destroy();
|
|
@@ -204,6 +279,7 @@
|
|
|
204
279
|
}
|
|
205
280
|
|
|
206
281
|
refresh();
|
|
282
|
+
checkGpu();
|
|
207
283
|
host.ready=true;
|
|
208
284
|
const publication=events.dispatch('browser-ai-setup-ready',{}, {
|
|
209
285
|
publicDetail:{ready:true}
|
package/src/cli/main.mjs
CHANGED
|
@@ -35,6 +35,7 @@ const VALUE_OPTIONS=new Set([
|
|
|
35
35
|
]);
|
|
36
36
|
const FLAG_OPTIONS=new Set([
|
|
37
37
|
'git',
|
|
38
|
+
'public',
|
|
38
39
|
'skip-tests',
|
|
39
40
|
'dry-run',
|
|
40
41
|
'require-local-ai',
|
|
@@ -60,7 +61,7 @@ Usage:
|
|
|
60
61
|
${CLI_NAME} upgrade [--workspace <directory>] [--app <id>]
|
|
61
62
|
${CLI_NAME} doctor [--workspace <directory>] [--arcane-root <directory>]
|
|
62
63
|
${CLI_NAME} import-map [--workspace <directory>] [--app <id>]
|
|
63
|
-
${CLI_NAME} dev [--app <id>] [--host
|
|
64
|
+
${CLI_NAME} dev [--app <id>] [--public] [--host <address>] [--port 8000] [--sdk-runtime-source <sdk-root>]
|
|
64
65
|
${CLI_NAME} test [--app <id>] [--scope app]
|
|
65
66
|
${CLI_NAME} test --scope shared --test-file <repo-relative.test.mjs>
|
|
66
67
|
${CLI_NAME} check [--app <id>] [--scope app] [--skip-tests]
|
|
@@ -83,6 +84,8 @@ Usage:
|
|
|
83
84
|
${CLI_NAME} mail serve --profile <profile> --from <address> --app <id> --origin <origin> [--allow-to <addresses>] [--app-key-stdin] [--host 127.0.0.1] [--port 8025] [--request-timeout <ms>]
|
|
84
85
|
|
|
85
86
|
Development:
|
|
87
|
+
--public Bind all IPv4 interfaces (0.0.0.0) and print network URLs.
|
|
88
|
+
--host <address> Override the bind address; takes precedence over --public.
|
|
86
89
|
--sdk-runtime-source <sdk-root> Dev-only live SDK checkout; omitted preserves the workspace runtime mode.
|
|
87
90
|
|
|
88
91
|
Global:
|
|
@@ -445,6 +448,9 @@ function operationOptions(command,parsed,cwd){
|
|
|
445
448
|
if(values['sdk-runtime-source']!==undefined&&command!=='dev'){
|
|
446
449
|
usage('--sdk-runtime-source is supported only by dev.');
|
|
447
450
|
}
|
|
451
|
+
if(flags.has('public')&&command!=='dev'){
|
|
452
|
+
usage('--public is supported only by dev.');
|
|
453
|
+
}
|
|
448
454
|
if(flags.has('overwrite')&&command!=='bundle'){
|
|
449
455
|
usage('--overwrite is supported only by bundle.');
|
|
450
456
|
}
|
|
@@ -506,7 +512,7 @@ function operationOptions(command,parsed,cwd){
|
|
|
506
512
|
noExtraPositionals(command,positionals);
|
|
507
513
|
return {
|
|
508
514
|
...common,
|
|
509
|
-
host:values.host??'127.0.0.1',
|
|
515
|
+
host:values.host??(flags.has('public')?'0.0.0.0':'127.0.0.1'),
|
|
510
516
|
port:readPort(values.port,8000),
|
|
511
517
|
...(values['sdk-runtime-source']===undefined?{}:{
|
|
512
518
|
sdkRuntimeSourceRoot:path.resolve(cwd,values['sdk-runtime-source'])
|
|
@@ -831,6 +837,7 @@ function serverSummary(result){
|
|
|
831
837
|
host:result.host,
|
|
832
838
|
port:result.port,
|
|
833
839
|
url:result.url,
|
|
840
|
+
...(result.networkUrls===undefined?{}:{networkUrls:result.networkUrls}),
|
|
834
841
|
...(result.callerAuthentication
|
|
835
842
|
?{callerAuthentication:result.callerAuthentication}
|
|
836
843
|
:{}),
|
|
@@ -841,7 +848,11 @@ function serverSummary(result){
|
|
|
841
848
|
}
|
|
842
849
|
|
|
843
850
|
async function waitForServer(result,signal,reporter){
|
|
844
|
-
|
|
851
|
+
const readyMessage=[
|
|
852
|
+
`Development server ready at ${result.url}`,
|
|
853
|
+
...(result.networkUrls??[]).map(function networkAddress(url){return `Network: ${url}`;})
|
|
854
|
+
].join('\n');
|
|
855
|
+
reporter.emit('server.ready',serverSummary(result),readyMessage);
|
|
845
856
|
if(result.lifecycle&&is.function(result.lifecycle.then)){
|
|
846
857
|
const abort=()=>{
|
|
847
858
|
void Promise.resolve().then(()=>result.close?.()).catch(()=>{});
|
package/src/dev-server.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import Is from 'strong-type';
|
|
|
2
2
|
import {constants as FS_CONSTANTS} from 'node:fs';
|
|
3
3
|
import {lstat,open,readFile,realpath} from 'node:fs/promises';
|
|
4
4
|
import http from 'node:http';
|
|
5
|
+
import os from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import {resolveWorkspace} from './workspace.mjs';
|
|
7
8
|
import {createEventQueue} from './event-queue.mjs';
|
|
@@ -489,6 +490,29 @@ function listen(server,{host,port,signal}){
|
|
|
489
490
|
});
|
|
490
491
|
}
|
|
491
492
|
|
|
493
|
+
function browserHostname(host){
|
|
494
|
+
return host.includes(':')?`[${host}]`:host;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function networkUrlsForAddress(address,startPath){
|
|
498
|
+
if(address.address==='127.0.0.1'||address.address==='::1')return [];
|
|
499
|
+
const allIPv4=address.address==='0.0.0.0';
|
|
500
|
+
const allInterfaces=address.address==='::';
|
|
501
|
+
const urls=new Set();
|
|
502
|
+
for(const entries of Object.values(os.networkInterfaces())){
|
|
503
|
+
for(const entry of entries??[]){
|
|
504
|
+
if(entry.internal)continue;
|
|
505
|
+
// Scoped IPv6 addresses need the receiving device's interface,
|
|
506
|
+
// so they cannot provide a portable browser link for another device.
|
|
507
|
+
if(entry.family==='IPv6'&&entry.scopeid)continue;
|
|
508
|
+
if(allIPv4&&entry.family!=='IPv4')continue;
|
|
509
|
+
if(!allIPv4&&!allInterfaces&&entry.address!==address.address)continue;
|
|
510
|
+
urls.add(`http://${browserHostname(entry.address)}:${address.port}${startPath}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return [...urls];
|
|
514
|
+
}
|
|
515
|
+
|
|
492
516
|
async function startOwnedDevServer({
|
|
493
517
|
workspaceRoot=process.cwd(),
|
|
494
518
|
appId,
|
|
@@ -504,8 +528,8 @@ async function startOwnedDevServer({
|
|
|
504
528
|
if(sdkRuntimeSourceRoot!==undefined&&mode!=='source'){
|
|
505
529
|
fail('sdkRuntimeSourceRoot is supported only in source development mode.','ARCANE_USAGE');
|
|
506
530
|
}
|
|
507
|
-
if(
|
|
508
|
-
fail('Development server host must be a
|
|
531
|
+
if(!is.string(host)||!host.trim()){
|
|
532
|
+
fail('Development server host must be a nonempty string.','ARCANE_USAGE');
|
|
509
533
|
}
|
|
510
534
|
if(!is.integer(port)||port<0||port>65535)fail('port must be an integer from 0 through 65535.','ARCANE_USAGE');
|
|
511
535
|
const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
|
|
@@ -631,7 +655,9 @@ async function startOwnedDevServer({
|
|
|
631
655
|
server.close();
|
|
632
656
|
fail('Development server did not expose a TCP address.');
|
|
633
657
|
}
|
|
634
|
-
const visibleHost=address.
|
|
658
|
+
const visibleHost=address.address==='0.0.0.0'||address.address==='::'
|
|
659
|
+
?'localhost'
|
|
660
|
+
:browserHostname(address.address);
|
|
635
661
|
const endpoint=new URL(`http://${visibleHost}:${address.port}`);
|
|
636
662
|
const origin=endpoint.origin;
|
|
637
663
|
const cleanUrl=`${origin}${routeSet.startPath}`;
|
|
@@ -728,17 +754,20 @@ async function startOwnedDevServer({
|
|
|
728
754
|
origin,
|
|
729
755
|
cleanUrl,
|
|
730
756
|
url,
|
|
757
|
+
networkUrls:[],
|
|
731
758
|
close,
|
|
732
759
|
closed:lifecycle,
|
|
733
760
|
lifecycle
|
|
734
761
|
};
|
|
735
762
|
try{
|
|
763
|
+
result.networkUrls=networkUrlsForAddress(address,routeSet.startPath);
|
|
736
764
|
await events.send({
|
|
737
765
|
type:'server.started',
|
|
738
766
|
mode,
|
|
739
767
|
host:result.host,
|
|
740
768
|
port:result.port,
|
|
741
769
|
url,
|
|
770
|
+
networkUrls:result.networkUrls,
|
|
742
771
|
appId:result.appId,
|
|
743
772
|
...(routeSet.runtime?{
|
|
744
773
|
runtimeMode:routeSet.runtime.mode,
|