arcane-os 0.50.0 → 0.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-speech-synthesis.mjs +21 -2
- package/browser-runtime/ai/twin-cloud.mjs +67 -0
- package/docs/reference/ai/browser-speech.md +23 -4
- package/docs/reference/ai/twin-cloud.md +63 -2
- package/docs/reference/availability-and-normalization.md +1 -0
- package/docs/reference/cli.md +4 -0
- package/docs/reference/inventory/package-api.json +15 -1
- package/docs/reference/runtime-components.md +13 -2
- package/docs/reference/sdk-api.md +60 -0
- package/package.json +1 -1
- package/runtime/arcane/components/speech.html +212 -22
- package/src/dev-server.mjs +3 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.51.1
|
|
4
|
+
|
|
5
|
+
- Restore the published `node-http-server` MIME map in development and packaged
|
|
6
|
+
preview serving. PDFs, Markdown and supported audio/video files retain their
|
|
7
|
+
declared content types; generated representations reuse the same public map.
|
|
8
|
+
Existing text charsets, unknown-extension binary fallback, complete file
|
|
9
|
+
contents, routing, conditional GET/HEAD and server lifecycle remain unchanged.
|
|
10
|
+
No new dependency, application setting or installed-file patch is required.
|
|
11
|
+
|
|
12
|
+
## 0.51.0
|
|
13
|
+
|
|
14
|
+
- Add synchronous native speech `provider.setDefaultVoice(voiceURI|null)`.
|
|
15
|
+
Update the live catalog and future preparations without replacing the
|
|
16
|
+
provider or altering explicit voices and prepared, queued or active speech.
|
|
17
|
+
Catalog subscriptions can apply an application-owned choice after voice
|
|
18
|
+
discovery; repeated values are silent and reentrant changes avoid stale
|
|
19
|
+
catalog delivery. Voice preference policy remains application-owned.
|
|
20
|
+
- Add `fetchSystemOneRequest` through `arcane-os/ai/twin-cloud` for explicit
|
|
21
|
+
model, complete caller-owned state and questions, and the complete native
|
|
22
|
+
System One response. Reuse the existing JSON transport, retry observers,
|
|
23
|
+
diagnostics and cancellation. Applications retain question definitions,
|
|
24
|
+
scoring policy, model selection and answer interpretation; no history,
|
|
25
|
+
fabricated tool calls, prompt conversion or new dependency is introduced.
|
|
26
|
+
- Keep all native recognition-language suggestions visible when the editable
|
|
27
|
+
picker opens or reopens. Filter only after text changes, show native language
|
|
28
|
+
and region labels, preserve exact custom BCP47 tags, and keep keyboard and
|
|
29
|
+
focus interaction in the shared speech control. The picker is available in
|
|
30
|
+
live-transcription mode before capture and disabled during capture or outside
|
|
31
|
+
that mode. Conversation language, Chat controls and capture payloads remain
|
|
32
|
+
unchanged.
|
|
33
|
+
|
|
3
34
|
## 0.50.0
|
|
4
35
|
|
|
5
36
|
- Add `createBrowserSpeechSynthesisProvider` through `arcane-os/ai/browser-speech`.
|
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 share the theme, packaging, event, cancellation,
|
|
20
20
|
and browser run contracts while retaining their selected application layout.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.51.1` 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
|
|
|
@@ -101,10 +101,11 @@ export function createBrowserSpeechSynthesisProvider({id, model, language} = {})
|
|
|
101
101
|
|
|
102
102
|
const modelId = model.id;
|
|
103
103
|
const modelName = model.name;
|
|
104
|
-
|
|
104
|
+
let defaultVoice = model.defaultVoice || null;
|
|
105
105
|
const playbacks = new Set();
|
|
106
106
|
const catalogObservers = new Set();
|
|
107
107
|
let catalogSource = null;
|
|
108
|
+
let catalogRevision = 0;
|
|
108
109
|
let state = 'unloaded';
|
|
109
110
|
let generation = 0;
|
|
110
111
|
let unloadOperation = null;
|
|
@@ -165,8 +166,25 @@ export function createBrowserSpeechSynthesisProvider({id, model, language} = {})
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
function publishCatalog() {
|
|
169
|
+
const revision = ++catalogRevision;
|
|
168
170
|
const current = catalog();
|
|
169
|
-
for (const observer of catalogObservers)
|
|
171
|
+
for (const observer of catalogObservers) {
|
|
172
|
+
// A catalog observer can select a new default and publish its replacement.
|
|
173
|
+
if (revision !== catalogRevision) return;
|
|
174
|
+
observeCallback(observer, current);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function setDefaultVoice(voice = null) {
|
|
179
|
+
assertOpen();
|
|
180
|
+
if (voice !== null && !is.string(voice)) {
|
|
181
|
+
throw new TypeError('Browser speech synthesis default voice must be a string.');
|
|
182
|
+
}
|
|
183
|
+
const nextVoice = voice || null;
|
|
184
|
+
if (defaultVoice === nextVoice) return defaultVoice;
|
|
185
|
+
defaultVoice = nextVoice;
|
|
186
|
+
publishCatalog();
|
|
187
|
+
return defaultVoice;
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
function detachCatalog() {
|
|
@@ -485,6 +503,7 @@ export function createBrowserSpeechSynthesisProvider({id, model, language} = {})
|
|
|
485
503
|
maxConcurrentRequests: 1,
|
|
486
504
|
catalog,
|
|
487
505
|
subscribeCatalog,
|
|
506
|
+
setDefaultVoice,
|
|
488
507
|
status,
|
|
489
508
|
prepare,
|
|
490
509
|
play(payload, options = {}) {
|
|
@@ -3,6 +3,7 @@ import {arcaneLogging} from '../logging.mjs';
|
|
|
3
3
|
|
|
4
4
|
const is = new Is(false);
|
|
5
5
|
const twinChatURL = 'https://inference.do-ai.run/v1/chat/completions';
|
|
6
|
+
const twinSystemOneURL = 'https://inference.do-ai.run/v1/systemone';
|
|
6
7
|
|
|
7
8
|
/** A stateless TWiN request; browser AI shares the HTTP and format owners below. */
|
|
8
9
|
export async function fetchRequest({
|
|
@@ -87,6 +88,72 @@ export async function fetchRequest({
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
/** Submit caller-owned state and questions without interpreting the provider's answers. */
|
|
92
|
+
export async function fetchSystemOneRequest({
|
|
93
|
+
twinKey,
|
|
94
|
+
model,
|
|
95
|
+
state,
|
|
96
|
+
questions,
|
|
97
|
+
signal = null,
|
|
98
|
+
id = Date.now(),
|
|
99
|
+
onRequest = function observeSystemOneRequest(){},
|
|
100
|
+
onResponse = function observeSystemOneResponse(){},
|
|
101
|
+
onRetry = null
|
|
102
|
+
} = {}){
|
|
103
|
+
if(signal?.aborted){
|
|
104
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
105
|
+
}
|
|
106
|
+
if(!is.string(twinKey) || !twinKey){
|
|
107
|
+
const error = new Error('AI provider is not configured.');
|
|
108
|
+
error.code = 'AI_PROVIDER_NOT_CONFIGURED';
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
if(!is.string(model) || !model){
|
|
112
|
+
throw new TypeError('TWiN fetchSystemOneRequest requires an explicit model.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const request = {model, state, questions};
|
|
116
|
+
// Capture the caller's complete payload before a diagnostic observer runs.
|
|
117
|
+
const body = JSON.stringify(request);
|
|
118
|
+
try{
|
|
119
|
+
await onRequest(
|
|
120
|
+
request,
|
|
121
|
+
id,
|
|
122
|
+
{operation:'systemone', transport:'http', destination:twinSystemOneURL}
|
|
123
|
+
);
|
|
124
|
+
if(signal?.aborted){
|
|
125
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
126
|
+
}
|
|
127
|
+
const response = await fetchJSONResponse(
|
|
128
|
+
twinSystemOneURL,
|
|
129
|
+
{
|
|
130
|
+
method:'POST',
|
|
131
|
+
credentials:'omit',
|
|
132
|
+
headers:{
|
|
133
|
+
'Content-Type':'application/json',
|
|
134
|
+
Authorization:`Bearer ${twinKey}`
|
|
135
|
+
},
|
|
136
|
+
body,
|
|
137
|
+
...(signal ? {signal} : {})
|
|
138
|
+
},
|
|
139
|
+
{onRetry}
|
|
140
|
+
);
|
|
141
|
+
if(signal?.aborted){
|
|
142
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
143
|
+
}
|
|
144
|
+
await onResponse(response, id, false);
|
|
145
|
+
if(signal?.aborted){
|
|
146
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
147
|
+
}
|
|
148
|
+
return response;
|
|
149
|
+
}catch(error){
|
|
150
|
+
if(isAIRequestAbort(error, signal)){
|
|
151
|
+
throw normalizeAIRequestAbort(error);
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
90
157
|
export function isAIRequestAbort(error, signal){
|
|
91
158
|
return signal?.aborted
|
|
92
159
|
|| error?.name === 'AbortError'
|
|
@@ -113,16 +113,35 @@ function playIntroduction(input, voice) {
|
|
|
113
113
|
`catalog()` synchronously returns an array of model records. Each model has
|
|
114
114
|
`speech:{playback:'native'}` and `voices:[{id,name,lang,default,localService}]`;
|
|
115
115
|
`id` is the exact native `voiceURI`. `subscribeCatalog(callback)` immediately
|
|
116
|
-
replays that same shape and updates it on `voiceschanged
|
|
117
|
-
unsubscribe function. An initially empty inventory is ordinary pending browser
|
|
116
|
+
replays that same shape and updates it on `voiceschanged` or a changed default,
|
|
117
|
+
returning an unsubscribe function. An initially empty inventory is ordinary pending browser
|
|
118
118
|
state. There is no polling, discovery utterance, model download, or page-load
|
|
119
119
|
barrier. Language selectors derive their choices from this actual inventory.
|
|
120
120
|
|
|
121
|
+
`provider.setDefaultVoice(voiceURI)` updates the provider's current
|
|
122
|
+
`catalog()[0].defaultVoice` synchronously and returns that current value.
|
|
123
|
+
The application may call it from its catalog subscription after asynchronous
|
|
124
|
+
voice discovery or when its saved selection changes. The SDK supplies no
|
|
125
|
+
voice-name preference: preserve an explicit saved choice in the application
|
|
126
|
+
and pass the exact native `voiceURI`. Repeating the current value is a no-op,
|
|
127
|
+
so a catalog subscription can apply the same choice without notification loops.
|
|
128
|
+
`null`, an empty string, or an omitted argument clears the configured default
|
|
129
|
+
and restores the existing browser-default behavior. A non-string, non-null
|
|
130
|
+
value throws `TypeError`; an unloading or disposed provider retains its
|
|
131
|
+
existing lifecycle error. A voice need not be present when setting the default:
|
|
132
|
+
the existing preparation/playback availability behavior below still applies.
|
|
133
|
+
|
|
134
|
+
Only future preparations use the new default. Explicit request voices and
|
|
135
|
+
already-prepared, queued, or active speech keep their selected voice. Updating
|
|
136
|
+
the default does not load, unload, replace, cancel, pause, resume, or speak.
|
|
137
|
+
AI's ordinary speech path reads this current catalog for new requests, so no
|
|
138
|
+
provider reconfiguration is necessary.
|
|
139
|
+
|
|
121
140
|
`prepare({input,voice?,language?,speed?},{signal?})` returns an inert
|
|
122
141
|
`{kind:'native-speech',input,voice,language,speed,play(options)}` descriptor.
|
|
123
142
|
`play(payload,{signal,onState})` is the explicit-preview convenience. Both
|
|
124
|
-
preserve complete input. Omitted voice uses the
|
|
125
|
-
or the actual browser default; a supplied voice is never replaced with a named
|
|
143
|
+
preserve complete input. Omitted voice uses the current default, initially
|
|
144
|
+
`model.defaultVoice`, or the actual browser default; a supplied voice is never replaced with a named
|
|
126
145
|
SDK alias. Language comes from that voice unless explicitly provided. Neither
|
|
127
146
|
API reads the conversation's language preference. A saved voice waits for
|
|
128
147
|
`voiceschanged` if the inventory is empty; a nonempty inventory missing it
|
|
@@ -116,11 +116,72 @@ managed import map. Browser Fetch and CORS behavior still apply. Importing it
|
|
|
116
116
|
does not instantiate `AI`, read saved preferences, configure speech, or change
|
|
117
117
|
the existing `arcane-os/ai` browser entry.
|
|
118
118
|
|
|
119
|
+
## Evaluate caller-owned state with System One
|
|
120
|
+
|
|
121
|
+
Use the named `fetchSystemOneRequest` export for the provider's native
|
|
122
|
+
state-and-questions operation. The application supplies the model and complete
|
|
123
|
+
provider-compatible `state` and `questions`; the SDK selects no model or scoring
|
|
124
|
+
policy and assigns no meaning to questions, answer keys, or scales.
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
import {fetchSystemOneRequest} from 'arcane-os/ai/twin-cloud';
|
|
128
|
+
|
|
129
|
+
const response = await fetchSystemOneRequest({
|
|
130
|
+
twinKey: applicationRuntime.twinKey,
|
|
131
|
+
model: 'typesafe-jev-1.13.0',
|
|
132
|
+
state: applicationState,
|
|
133
|
+
questions: applicationQuestions,
|
|
134
|
+
signal: controller.signal
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`applicationRuntime`, `applicationState`, `applicationQuestions`, and
|
|
139
|
+
`controller` are supplied by the application. The example's JEV identifier is
|
|
140
|
+
an explicit caller selection, not an SDK default. For OSS chat-completion models
|
|
141
|
+
such as `openai-gpt-oss-120b` and `openai-gpt-oss-20b`, use `fetchRequest` with
|
|
142
|
+
caller-owned messages and structured-output settings instead. The application
|
|
143
|
+
chooses the operation matching its model's provider contract.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
fetchSystemOneRequest({
|
|
147
|
+
twinKey,
|
|
148
|
+
model,
|
|
149
|
+
state,
|
|
150
|
+
questions,
|
|
151
|
+
signal,
|
|
152
|
+
id,
|
|
153
|
+
onRequest,
|
|
154
|
+
onResponse,
|
|
155
|
+
onRetry
|
|
156
|
+
})
|
|
157
|
+
// Promise<complete parsed provider JSON>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The SDK posts exactly `{model,state,questions}` as JSON to
|
|
161
|
+
`https://inference.do-ai.run/v1/systemone`, using `twinKey` only as bearer
|
|
162
|
+
authentication. It adds no prompt, chat envelope, tool call, output limit, or
|
|
163
|
+
answer conversion. Caller content stays unchanged within ordinary JSON
|
|
164
|
+
transport encoding. The request is serialized before `onRequest`, so that
|
|
165
|
+
diagnostic callback cannot rewrite the dispatched payload. The entire parsed
|
|
166
|
+
provider JSON reaches `onResponse` and the returned promise without selecting
|
|
167
|
+
or transforming answers.
|
|
168
|
+
|
|
169
|
+
The explicit key/model requirements, optional `signal`, `id`, callbacks,
|
|
170
|
+
retry observer, complete errors, and lack of retained history follow the shared
|
|
171
|
+
request behavior above. `onRequest(request,id,metadata)` receives
|
|
172
|
+
`{operation:'systemone',transport:'http',destination:'https://inference.do-ai.run/v1/systemone'}`
|
|
173
|
+
as metadata. `onResponse(response,id,false)` receives the complete parsed
|
|
174
|
+
response. Both callbacks are awaited; failures propagate and cancellation is
|
|
175
|
+
checked again after they settle. The existing `fetchJSONResponse` owner
|
|
176
|
+
provides the same HTTP retries and cancellation for this operation; no separate
|
|
177
|
+
transport or retry loop is created.
|
|
178
|
+
|
|
119
179
|
### Shared low-level integration helpers
|
|
120
180
|
|
|
121
181
|
The same module also exports the helpers used by browser `AI.js`. Ordinary
|
|
122
|
-
callers use `fetchRequest`; these exports let SDK
|
|
123
|
-
the existing implementation rather than maintain
|
|
182
|
+
callers use `fetchRequest` or `fetchSystemOneRequest`; these exports let SDK
|
|
183
|
+
transport integration share the existing implementation rather than maintain
|
|
184
|
+
another retry or body reader.
|
|
124
185
|
|
|
125
186
|
| Export | Contract |
|
|
126
187
|
| --- | --- |
|
|
@@ -80,6 +80,7 @@ version; WebKitGTK availability must not be generalized to macOS.
|
|
|
80
80
|
| Use local AI without coupling app code to Ollama HTTP | `Arcane.localAI`, `Arcane.ai`, or `/arcane/modules/Ollama.js` | Primarily **Native**; Android exposes a narrower admitted inference projection | Admission, errors, and managed-operation events are normalized. Direct Ollama response envelopes remain **Provider-native**. |
|
|
81
81
|
| Use TWiN Cloud from the renderer profile | `/arcane/modules/AI.js` | **Cloud** from an allowed browser/native renderer | High-level chat behavior is normalized by the module. The TWiN access key authenticates remote LLM chat; raw provider diagnostics remain provider-specific. No automatic cloud fallback is inferred from local failure. |
|
|
82
82
|
| Send one TWiN Cloud request with an explicit key and model | `fetchRequest` from `arcane-os/ai/twin-cloud` | **Node** and **Browser**, using standard Fetch and a remote HTTPS provider | Keeps complete messages and returns the full parsed provider JSON. Rejected Fetch and HTTP 529 share three retries after 3000 ms; HTTP 429 overload retains unlimited three-second retries. Optional `onRetry` reports waiting/requesting phases without blocking recovery. Shared structured-output mapping and cancellation match browser TWiN transport. No browser profile, AI/user singleton, DBOPFS, or retained request history is created. |
|
|
83
|
+
| Evaluate caller-owned state and questions with TWiN System One | `fetchSystemOneRequest` from `arcane-os/ai/twin-cloud` | **Node** and **Browser**, using standard Fetch and a remote HTTPS provider | Posts the explicit model and complete state/questions through the existing JSON transport. Returns the entire parsed provider result and shares its retry, diagnostic, and cancellation behavior. Model choice, question definitions, answer interpretation, and scoring policy stay with the caller; no conversation state is retained. |
|
|
83
84
|
| Use speech through one application helper | `/arcane/modules/AI.js` and `Arcane.speech` | **Browser** or **Native** | Whisper and Kokoro keep their on-device paths; explicitly selected Web Speech STT and TWiN Cloud TTS declare their non-local behavior. The helper automatically cleans only the outbound synthesis-input copy and normalizes application-facing audio/text behavior while request/response plumbing differs below that boundary. |
|
|
84
85
|
| Inspect or manage raw Ollama models | `Arcane.ollama` or `/arcane/modules/Ollama.js` | **Native** desktop Core for management; narrower Android inference only | Wrapper method names, errors, streaming correlation, and admission are Arcane-controlled. Direct Ollama success envelopes are intentionally provider-native. |
|
|
85
86
|
| Use native terminal, installation, user, provisioning, or machine controls | matching `Arcane.*` namespace | **Native** and app/capability restricted | Calls and errors use the common bridge contract. Platform results can be host-specific and are marked in the method guide. |
|
package/docs/reference/cli.md
CHANGED
|
@@ -374,6 +374,10 @@ HTTPS is the default for development and required for packaged browser previews.
|
|
|
374
374
|
The published `node-http-server` 10.0.0 integration negotiates HTTP/2 or HTTP/1.1
|
|
375
375
|
on the same HTTPS port using the configured PEM pair. Source routes, generated
|
|
376
376
|
PWA resources, and conditional responses use the same serving pipeline.
|
|
377
|
+
Static files retain the module's complete MIME map, including PDF, Markdown,
|
|
378
|
+
audio and video. Generated representations use the same published map;
|
|
379
|
+
unknown extensions retain `application/octet-stream`. No application MIME
|
|
380
|
+
override is needed for these supported types.
|
|
377
381
|
`--https` remains accepted but is no longer needed to select the transport.
|
|
378
382
|
`--port` selects the HTTPS application port. A second HTTP listener returns
|
|
379
383
|
`308` redirects to that HTTPS port, preserving the requested path and query.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"minimumVersion": "22.23.2 for Node entrypoints",
|
|
7
7
|
"moduleSystem": "ESM"
|
|
8
8
|
},
|
|
9
|
-
"memberCount":
|
|
9
|
+
"memberCount": 220,
|
|
10
10
|
"runtimeSubpathPatterns": {
|
|
11
11
|
"arcane-os/modules/*": "./runtime/arcane/modules/*",
|
|
12
12
|
"arcane-os/entities/*": "./runtime/arcane/entities/*"
|
|
@@ -3452,6 +3452,20 @@
|
|
|
3452
3452
|
"protocol": "TWiN Cloud complete-response chat",
|
|
3453
3453
|
"normalization": "Preserves complete messages and parsed provider JSON; maps explicit structured-output, tool, and reasoning options; shares overload-only HTTP 429 retry after 3000 ms and cancellation; retains no browser profile, storage, or request history"
|
|
3454
3454
|
},
|
|
3455
|
+
{
|
|
3456
|
+
"id": "twin-cloud:fetchSystemOneRequest",
|
|
3457
|
+
"name": "fetchSystemOneRequest",
|
|
3458
|
+
"displayName": "fetchSystemOneRequest()",
|
|
3459
|
+
"kind": "function",
|
|
3460
|
+
"signature": "async fetchSystemOneRequest({twinKey,model,state,questions,signal=null,id=Date.now(),onRequest,onResponse,onRetry=null}={})",
|
|
3461
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3462
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3463
|
+
"group": "TWiN Cloud state-and-questions requests",
|
|
3464
|
+
"summary": "Sends caller-owned model, state, and questions to the native System One endpoint with explicit credentials.",
|
|
3465
|
+
"availability": "Node and Browser; remote HTTPS provider",
|
|
3466
|
+
"protocol": "TWiN Cloud native System One request",
|
|
3467
|
+
"normalization": "Preserves complete state and questions within JSON transport and returns the full parsed provider result; shares existing JSON transport retry, diagnostics, and cancellation; leaves model choice, scales, and answer interpretation with the caller and retains no history"
|
|
3468
|
+
},
|
|
3455
3469
|
{
|
|
3456
3470
|
"id": "twin-cloud:fetchHTTPResponse",
|
|
3457
3471
|
"name": "fetchHTTPResponse",
|
|
@@ -1682,8 +1682,19 @@ On desktop native capture, an accessible editable BCP47 language control occupie
|
|
|
1682
1682
|
the feedback area instead of idle transcription/voice readiness boilerplate.
|
|
1683
1683
|
`recognitionLanguage` defaults to `navigator.language`, accepts an explicit
|
|
1684
1684
|
language tag without restricting it to the suggested choices, and is passed to
|
|
1685
|
-
the capture owner at start.
|
|
1686
|
-
|
|
1685
|
+
the capture owner at start. Opening or reopening the editable picker shows all
|
|
1686
|
+
suggestions regardless of its saved/current value. Filtering begins only after
|
|
1687
|
+
the user changes the input text; initialization, programmatic configuration,
|
|
1688
|
+
and choosing a suggestion leave the full list available on the next opening.
|
|
1689
|
+
Suggestions display their native language and region names alongside the exact
|
|
1690
|
+
BCP47 tags; they are suggestions, not an exhaustive provider support inventory.
|
|
1691
|
+
The selected or freely typed tag remains unchanged. Arrow keys navigate the
|
|
1692
|
+
suggestions, Enter chooses the active suggestion, and Escape or Tab closes the
|
|
1693
|
+
list. The picker is visible and enabled only for desktop live-transcription
|
|
1694
|
+
mode, including before recording begins. It is disabled during active capture
|
|
1695
|
+
so changes affect the next capture; leaving live-transcription mode closes and
|
|
1696
|
+
disables it. File/model transcription and conversation-language configuration
|
|
1697
|
+
remain separate. Native voice loading, errors, and speaking
|
|
1687
1698
|
remain visible. Model-based transcription retains its existing status, and
|
|
1688
1699
|
mobile speech retains its existing voice-output-only presentation.
|
|
1689
1700
|
|
|
@@ -123,6 +123,7 @@ browser map are cataloged separately in [Runtime modules](runtime-modules.md).
|
|
|
123
123
|
| Member | Kind | Import | Group | Availability |
|
|
124
124
|
| --- | --- | --- | --- | --- |
|
|
125
125
|
| `fetchRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud requests | Node and Browser; remote HTTPS provider |
|
|
126
|
+
| `fetchSystemOneRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud state-and-questions requests | Node and Browser; remote HTTPS provider |
|
|
126
127
|
| `fetchHTTPResponse()` | function | `arcane-os/ai/twin-cloud` | Shared AI transport integration | Node and Browser |
|
|
127
128
|
| `fetchJSONResponse()` | function | `arcane-os/ai/twin-cloud` | Shared AI transport integration | Node and Browser |
|
|
128
129
|
| `structuredOutputFormat()` | function | `arcane-os/ai/twin-cloud` | Shared AI transport integration | Node and Browser |
|
|
@@ -3731,6 +3732,12 @@ negotiate HTTP/2 or HTTP/1.1 on the same HTTPS port. Explicit raw `tls` options
|
|
|
3731
3732
|
retain native HTTPS transport with the same module response and static-file
|
|
3732
3733
|
operations.
|
|
3733
3734
|
|
|
3735
|
+
Static files retain the module's complete built-in MIME map, including PDF,
|
|
3736
|
+
Markdown, audio and video. Generated representations use the same published
|
|
3737
|
+
`node-http-server/mime-types` map. Existing text charsets and the
|
|
3738
|
+
`application/octet-stream` fallback for unknown extensions remain unchanged;
|
|
3739
|
+
supported document and media types need no application override.
|
|
3740
|
+
|
|
3734
3741
|
### Signature, modes, and result
|
|
3735
3742
|
|
|
3736
3743
|
```text
|
|
@@ -7953,6 +7960,59 @@ console.log(response);
|
|
|
7953
7960
|
`server-config.json` is an application-owned, ignored configuration file;
|
|
7954
7961
|
never commit its key or print it in diagnostics. The SDK does not read it.
|
|
7955
7962
|
|
|
7963
|
+
## fetchSystemOneRequest()
|
|
7964
|
+
|
|
7965
|
+
### Overview
|
|
7966
|
+
|
|
7967
|
+
Sends one native TWiN System One request with caller-owned credentials, model,
|
|
7968
|
+
state, and questions. Question definitions, scales, model selection, and answer
|
|
7969
|
+
interpretation remain application responsibilities.
|
|
7970
|
+
|
|
7971
|
+
### Signature and result
|
|
7972
|
+
|
|
7973
|
+
```text
|
|
7974
|
+
async fetchSystemOneRequest({twinKey,model,state,questions,signal=null,id=Date.now(),onRequest,onResponse,onRetry=null}={})
|
|
7975
|
+
```
|
|
7976
|
+
|
|
7977
|
+
Import the named function from `arcane-os/ai/twin-cloud`. It posts
|
|
7978
|
+
`{model,state,questions}` to `https://inference.do-ai.run/v1/systemone` and
|
|
7979
|
+
returns the entire parsed provider JSON. It adds no chat messages, tools,
|
|
7980
|
+
scoring rules, or output limit. Payload content is preserved within ordinary
|
|
7981
|
+
JSON transport encoding; serialization occurs before the diagnostic callback.
|
|
7982
|
+
|
|
7983
|
+
`onRequest(request,id,metadata)` receives the complete payload before dispatch,
|
|
7984
|
+
with `{operation:'systemone',transport:'http',destination:'https://inference.do-ai.run/v1/systemone'}`
|
|
7985
|
+
as metadata. `onResponse(response,id,false)` receives the entire parsed result
|
|
7986
|
+
before return. Both callbacks are awaited and failures propagate. The key is
|
|
7987
|
+
used only for transport authentication, outside the diagnostic payloads.
|
|
7988
|
+
|
|
7989
|
+
### Availability and normalization
|
|
7990
|
+
|
|
7991
|
+
**Node and Browser; Cloud transport.** Uses the existing `fetchJSONResponse`
|
|
7992
|
+
owner for complete errors, retries, `onRetry`, and `signal` cancellation, with
|
|
7993
|
+
no browser singleton, profile, storage, or retained history. A missing key uses
|
|
7994
|
+
`AI_PROVIDER_NOT_CONFIGURED`; a missing explicit model throws `TypeError`.
|
|
7995
|
+
Cancellation prevents successful return and uses `ARCANE_AI_REQUEST_ABORTED`,
|
|
7996
|
+
including checks after callback settlement. Import starts no request.
|
|
7997
|
+
|
|
7998
|
+
### Example
|
|
7999
|
+
|
|
8000
|
+
```javascript
|
|
8001
|
+
import {fetchSystemOneRequest} from 'arcane-os/ai/twin-cloud';
|
|
8002
|
+
|
|
8003
|
+
const response = await fetchSystemOneRequest({
|
|
8004
|
+
twinKey: applicationRuntime.twinKey,
|
|
8005
|
+
model: 'typesafe-jev-1.13.0',
|
|
8006
|
+
state: applicationState,
|
|
8007
|
+
questions: applicationQuestions
|
|
8008
|
+
});
|
|
8009
|
+
```
|
|
8010
|
+
|
|
8011
|
+
The runtime configuration, state, and questions in this example belong to the
|
|
8012
|
+
application. The JEV identifier is an explicit caller choice, not a default.
|
|
8013
|
+
See [TWiN Cloud](ai/twin-cloud.md#evaluate-caller-owned-state-with-system-one)
|
|
8014
|
+
for the complete transport and callback contract.
|
|
8015
|
+
|
|
7956
8016
|
## fetchHTTPResponse()
|
|
7957
8017
|
|
|
7958
8018
|
### Overview
|
package/package.json
CHANGED
|
@@ -99,10 +99,17 @@
|
|
|
99
99
|
display: none;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
.recognition_language_picker {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: flex;
|
|
105
|
+
width: 18rem;
|
|
106
|
+
max-width: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
102
109
|
#recognitionLanguage {
|
|
103
110
|
box-sizing: border-box;
|
|
104
|
-
width:
|
|
105
|
-
|
|
111
|
+
width: 100%;
|
|
112
|
+
min-width: 0;
|
|
106
113
|
min-height: 2.25rem;
|
|
107
114
|
padding: .35rem .5rem;
|
|
108
115
|
border: 1px solid var(--arcane-border, var(--border-color));
|
|
@@ -112,6 +119,52 @@
|
|
|
112
119
|
font: inherit;
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
#recognitionLanguageToggle {
|
|
123
|
+
min-height: 2.25rem;
|
|
124
|
+
padding: .35rem .6rem;
|
|
125
|
+
background: var(--arcane-surface, var(--background-color));
|
|
126
|
+
color: var(--arcane-text, var(--text-color));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#recognitionLanguages {
|
|
130
|
+
position: absolute;
|
|
131
|
+
z-index: 1;
|
|
132
|
+
inset-inline: 0;
|
|
133
|
+
bottom: 100%;
|
|
134
|
+
max-height: 18rem;
|
|
135
|
+
overflow: auto;
|
|
136
|
+
border: 1px solid var(--arcane-border, var(--border-color));
|
|
137
|
+
border-radius: var(--arcane-radius-small, .4rem);
|
|
138
|
+
background: var(--arcane-surface, var(--background-color));
|
|
139
|
+
box-shadow: var(--arcane-shadow-small, 0 .25rem .8rem var(--shadow-color));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#recognitionLanguages button {
|
|
143
|
+
display: block;
|
|
144
|
+
width: 100%;
|
|
145
|
+
border: 0;
|
|
146
|
+
border-radius: 0;
|
|
147
|
+
background: var(--arcane-surface, var(--background-color));
|
|
148
|
+
color: var(--arcane-text, var(--text-color));
|
|
149
|
+
font-weight: normal;
|
|
150
|
+
text-align: start;
|
|
151
|
+
overflow-wrap: anywhere;
|
|
152
|
+
touch-action: pan-y;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#recognitionLanguages button[hidden] {
|
|
156
|
+
display: none;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#recognitionLanguages button[data-active="true"] {
|
|
160
|
+
outline: .2rem solid var(--arcane-focus, var(--focus-color));
|
|
161
|
+
outline-offset: -.2rem;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#recognitionLanguages button[aria-selected="true"] {
|
|
165
|
+
font-weight: 700;
|
|
166
|
+
}
|
|
167
|
+
|
|
115
168
|
.speech_status {
|
|
116
169
|
margin: 0;
|
|
117
170
|
color: var(--arcane-muted, var(--text-color));
|
|
@@ -189,25 +242,28 @@
|
|
|
189
242
|
<button id="sttActivationButton" type="button" hidden disabled>Start transcription</button>
|
|
190
243
|
<button id="stopButton" type="button" class="hidden" disabled>Send</button>
|
|
191
244
|
<div class="speech_feedback">
|
|
192
|
-
<
|
|
193
|
-
Recognition language
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
245
|
+
<div id="recognitionLanguageControl" class="recognition_language" hidden>
|
|
246
|
+
<label for="recognitionLanguage">Recognition language</label>
|
|
247
|
+
<div class="recognition_language_picker">
|
|
248
|
+
<input id="recognitionLanguage" type="text" role="combobox" aria-controls="recognitionLanguages" aria-expanded="false" aria-autocomplete="list" aria-haspopup="listbox" placeholder="Browser default" title="Recognition language tag; type to filter suggestions or enter another BCP47 tag" autocomplete="off" spellcheck="false">
|
|
249
|
+
<button id="recognitionLanguageToggle" type="button" tabindex="-1" aria-label="Show all suggested recognition languages" aria-controls="recognitionLanguages" aria-expanded="false">▾</button>
|
|
250
|
+
<div id="recognitionLanguages" role="listbox" aria-label="Suggested recognition languages" hidden>
|
|
251
|
+
<button id="recognitionLanguage-en-US" type="button" role="option" data-language="en-US" tabindex="-1" lang="en-US" dir="auto">English (United States) — en-US</button>
|
|
252
|
+
<button id="recognitionLanguage-en-GB" type="button" role="option" data-language="en-GB" tabindex="-1" lang="en-GB" dir="auto">English (United Kingdom) — en-GB</button>
|
|
253
|
+
<button id="recognitionLanguage-es-MX" type="button" role="option" data-language="es-MX" tabindex="-1" lang="es-MX" dir="auto">Español (México) — es-MX</button>
|
|
254
|
+
<button id="recognitionLanguage-es-ES" type="button" role="option" data-language="es-ES" tabindex="-1" lang="es-ES" dir="auto">Español (España) — es-ES</button>
|
|
255
|
+
<button id="recognitionLanguage-fr-FR" type="button" role="option" data-language="fr-FR" tabindex="-1" lang="fr-FR" dir="auto">Français (France) — fr-FR</button>
|
|
256
|
+
<button id="recognitionLanguage-de-DE" type="button" role="option" data-language="de-DE" tabindex="-1" lang="de-DE" dir="auto">Deutsch (Deutschland) — de-DE</button>
|
|
257
|
+
<button id="recognitionLanguage-it-IT" type="button" role="option" data-language="it-IT" tabindex="-1" lang="it-IT" dir="auto">Italiano (Italia) — it-IT</button>
|
|
258
|
+
<button id="recognitionLanguage-pt-BR" type="button" role="option" data-language="pt-BR" tabindex="-1" lang="pt-BR" dir="auto">Português (Brasil) — pt-BR</button>
|
|
259
|
+
<button id="recognitionLanguage-ja-JP" type="button" role="option" data-language="ja-JP" tabindex="-1" lang="ja-JP" dir="auto">日本語(日本) — ja-JP</button>
|
|
260
|
+
<button id="recognitionLanguage-ko-KR" type="button" role="option" data-language="ko-KR" tabindex="-1" lang="ko-KR" dir="auto">한국어 (대한민국) — ko-KR</button>
|
|
261
|
+
<button id="recognitionLanguage-zh-CN" type="button" role="option" data-language="zh-CN" tabindex="-1" lang="zh-CN" dir="auto">中文(中国大陆,普通话) — zh-CN</button>
|
|
262
|
+
<button id="recognitionLanguage-hi-IN" type="button" role="option" data-language="hi-IN" tabindex="-1" lang="hi-IN" dir="auto">हिन्दी (भारत) — hi-IN</button>
|
|
263
|
+
<button id="recognitionLanguage-ar-SA" type="button" role="option" data-language="ar-SA" tabindex="-1" lang="ar-SA" dir="auto">العربية (المملكة العربية السعودية) — ar-SA</button>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
211
267
|
<p id="speechStatus" class="speech_status" role="status" aria-live="polite"></p>
|
|
212
268
|
<progress
|
|
213
269
|
id="sttActivationProgress"
|
|
@@ -281,6 +337,11 @@
|
|
|
281
337
|
const speechStatus = shadowRoot.querySelector('#speechStatus');
|
|
282
338
|
const recognitionLanguageControl = shadowRoot.querySelector('#recognitionLanguageControl');
|
|
283
339
|
const recognitionLanguageInput = shadowRoot.querySelector('#recognitionLanguage');
|
|
340
|
+
const recognitionLanguageToggle = shadowRoot.querySelector('#recognitionLanguageToggle');
|
|
341
|
+
const recognitionLanguages = shadowRoot.querySelector('#recognitionLanguages');
|
|
342
|
+
const recognitionLanguageOptions = Array.from(
|
|
343
|
+
recognitionLanguages.querySelectorAll('[data-language]')
|
|
344
|
+
);
|
|
284
345
|
const silenceDuration = 0;
|
|
285
346
|
const runtimeStateAbortController = new AbortController();
|
|
286
347
|
|
|
@@ -319,6 +380,9 @@
|
|
|
319
380
|
set: setRecognitionLanguage
|
|
320
381
|
});
|
|
321
382
|
recognitionLanguageInput.value = recognitionLanguage;
|
|
383
|
+
let recognitionLanguageInputValue = recognitionLanguage;
|
|
384
|
+
let activeRecognitionLanguageOption = null;
|
|
385
|
+
renderRecognitionLanguageOptions();
|
|
322
386
|
|
|
323
387
|
function nextSpeechOperationId(kind) {
|
|
324
388
|
eventOperationSequence += 1;
|
|
@@ -459,6 +523,16 @@
|
|
|
459
523
|
stopButton.addEventListener('click', stop, lifecycleListenerOptions);
|
|
460
524
|
muteButton.addEventListener('click', muteToggle, lifecycleListenerOptions);
|
|
461
525
|
recognitionLanguageInput.addEventListener('input', updateRecognitionLanguage, lifecycleListenerOptions);
|
|
526
|
+
recognitionLanguageInput.addEventListener('focus', openRecognitionLanguages, lifecycleListenerOptions);
|
|
527
|
+
recognitionLanguageInput.addEventListener('click', openRecognitionLanguages, lifecycleListenerOptions);
|
|
528
|
+
recognitionLanguageInput.addEventListener('blur', closeRecognitionLanguages, lifecycleListenerOptions);
|
|
529
|
+
recognitionLanguageInput.addEventListener('keydown', handleRecognitionLanguageKeyDown, lifecycleListenerOptions);
|
|
530
|
+
recognitionLanguageToggle.addEventListener('pointerdown', retainRecognitionLanguageFocus, lifecycleListenerOptions);
|
|
531
|
+
recognitionLanguageToggle.addEventListener('click', toggleRecognitionLanguages, lifecycleListenerOptions);
|
|
532
|
+
for (const option of recognitionLanguageOptions) {
|
|
533
|
+
option.addEventListener('pointerdown', retainRecognitionLanguageFocus, lifecycleListenerOptions);
|
|
534
|
+
option.addEventListener('click', selectRecognitionLanguage, lifecycleListenerOptions);
|
|
535
|
+
}
|
|
462
536
|
window.addEventListener('ai-ready', synchronizeAIReadiness, lifecycleListenerOptions);
|
|
463
537
|
window.addEventListener(
|
|
464
538
|
'pagehide',
|
|
@@ -556,10 +630,121 @@
|
|
|
556
630
|
}
|
|
557
631
|
recognitionLanguage = language;
|
|
558
632
|
recognitionLanguageInput.value = language;
|
|
633
|
+
recognitionLanguageInputValue = language;
|
|
634
|
+
renderRecognitionLanguageOptions();
|
|
559
635
|
}
|
|
560
636
|
|
|
561
637
|
function updateRecognitionLanguage() {
|
|
562
|
-
|
|
638
|
+
if (recognitionLanguageInput.disabled
|
|
639
|
+
|| recognitionLanguageInput.value === recognitionLanguageInputValue) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
recognitionLanguage = recognitionLanguageInput.value;
|
|
643
|
+
recognitionLanguageInputValue = recognitionLanguage;
|
|
644
|
+
openRecognitionLanguages();
|
|
645
|
+
renderRecognitionLanguageOptions(recognitionLanguage);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function renderRecognitionLanguageOptions(search = '') {
|
|
649
|
+
const query = search.toLocaleLowerCase();
|
|
650
|
+
activeRecognitionLanguageOption = null;
|
|
651
|
+
recognitionLanguageInput.removeAttribute('aria-activedescendant');
|
|
652
|
+
for (const option of recognitionLanguageOptions) {
|
|
653
|
+
option.hidden = !option.textContent.toLocaleLowerCase().includes(query);
|
|
654
|
+
option.dataset.active = 'false';
|
|
655
|
+
option.setAttribute('aria-selected', String(option.dataset.language === recognitionLanguage));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function openRecognitionLanguages() {
|
|
660
|
+
if (recognitionLanguageControl.hidden || recognitionLanguageInput.disabled
|
|
661
|
+
|| !recognitionLanguages.hidden) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
renderRecognitionLanguageOptions();
|
|
665
|
+
recognitionLanguages.hidden = false;
|
|
666
|
+
recognitionLanguageInput.setAttribute('aria-expanded', 'true');
|
|
667
|
+
recognitionLanguageToggle.setAttribute('aria-expanded', 'true');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function closeRecognitionLanguages() {
|
|
671
|
+
recognitionLanguages.hidden = true;
|
|
672
|
+
recognitionLanguageInput.setAttribute('aria-expanded', 'false');
|
|
673
|
+
recognitionLanguageToggle.setAttribute('aria-expanded', 'false');
|
|
674
|
+
recognitionLanguageInput.removeAttribute('aria-activedescendant');
|
|
675
|
+
activeRecognitionLanguageOption = null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function retainRecognitionLanguageFocus(event) {
|
|
679
|
+
event.preventDefault();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function toggleRecognitionLanguages() {
|
|
683
|
+
if (recognitionLanguageInput.disabled) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const wasOpen = !recognitionLanguages.hidden;
|
|
687
|
+
recognitionLanguageInput.focus();
|
|
688
|
+
if (wasOpen) {
|
|
689
|
+
closeRecognitionLanguages();
|
|
690
|
+
} else {
|
|
691
|
+
openRecognitionLanguages();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function selectRecognitionLanguage(event) {
|
|
696
|
+
if (recognitionLanguageInput.disabled) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
host.recognitionLanguage = event.currentTarget.dataset.language;
|
|
700
|
+
recognitionLanguageInput.focus();
|
|
701
|
+
closeRecognitionLanguages();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function handleRecognitionLanguageKeyDown(event) {
|
|
705
|
+
if (recognitionLanguageInput.disabled || event.isComposing) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
709
|
+
if (event.key === 'Escape' && !recognitionLanguages.hidden) {
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
}
|
|
712
|
+
closeRecognitionLanguages();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (event.key === 'Enter' && !recognitionLanguages.hidden) {
|
|
716
|
+
event.preventDefault();
|
|
717
|
+
if (activeRecognitionLanguageOption) {
|
|
718
|
+
host.recognitionLanguage = activeRecognitionLanguageOption.dataset.language;
|
|
719
|
+
}
|
|
720
|
+
closeRecognitionLanguages();
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (!['ArrowDown', 'ArrowUp'].includes(event.key)) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
event.preventDefault();
|
|
727
|
+
openRecognitionLanguages();
|
|
728
|
+
const visibleOptions = recognitionLanguageOptions.filter(
|
|
729
|
+
function visibleRecognitionLanguage(option) {
|
|
730
|
+
return !option.hidden;
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
if (!visibleOptions.length) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const previousIndex = visibleOptions.indexOf(activeRecognitionLanguageOption);
|
|
737
|
+
const nextIndex = previousIndex < 0
|
|
738
|
+
? (event.key === 'ArrowDown' ? 0 : visibleOptions.length - 1)
|
|
739
|
+
: (previousIndex + (event.key === 'ArrowDown' ? 1 : -1) + visibleOptions.length)
|
|
740
|
+
% visibleOptions.length;
|
|
741
|
+
if (activeRecognitionLanguageOption) {
|
|
742
|
+
activeRecognitionLanguageOption.dataset.active = 'false';
|
|
743
|
+
}
|
|
744
|
+
activeRecognitionLanguageOption = visibleOptions[nextIndex];
|
|
745
|
+
activeRecognitionLanguageOption.dataset.active = 'true';
|
|
746
|
+
recognitionLanguageInput.setAttribute('aria-activedescendant', activeRecognitionLanguageOption.id);
|
|
747
|
+
activeRecognitionLanguageOption.scrollIntoView({block: 'nearest'});
|
|
563
748
|
}
|
|
564
749
|
|
|
565
750
|
function updateMicrophoneAvailability(available) {
|
|
@@ -701,9 +886,14 @@
|
|
|
701
886
|
recognitionLanguageControl.hidden = !transcriptionEnabled
|
|
702
887
|
|| !providerRuntime.supportsTranscriptionCapture();
|
|
703
888
|
recognitionLanguageInput.disabled = destroyed
|
|
889
|
+
|| recognitionLanguageControl.hidden
|
|
704
890
|
|| recordingRequested
|
|
705
891
|
|| Boolean(nativeCaptureSession)
|
|
706
892
|
|| transcriptionActive;
|
|
893
|
+
recognitionLanguageToggle.disabled = recognitionLanguageInput.disabled;
|
|
894
|
+
if (recognitionLanguageInput.disabled) {
|
|
895
|
+
closeRecognitionLanguages();
|
|
896
|
+
}
|
|
707
897
|
if (!transcriptionEnabled) {
|
|
708
898
|
recordButton.hidden = true;
|
|
709
899
|
recordButton.disabled = true;
|
package/src/dev-server.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
2
|
import {Server} from 'node-http-server';
|
|
3
|
+
import contentTypes from 'node-http-server/mime-types';
|
|
3
4
|
import {constants as FS_CONSTANTS} from 'node:fs';
|
|
4
5
|
import {lstat,open,readFile,readdir,realpath} from 'node:fs/promises';
|
|
5
6
|
import https from 'node:https';
|
|
@@ -19,26 +20,6 @@ import {createPwaArtifacts,selectPwaFiles} from './pwa.mjs';
|
|
|
19
20
|
|
|
20
21
|
const is = new Is(false);
|
|
21
22
|
|
|
22
|
-
const MIME_TYPES=new Map([
|
|
23
|
-
['.css','text/css; charset=utf-8'],
|
|
24
|
-
['.gif','image/gif'],
|
|
25
|
-
['.html','text/html; charset=utf-8'],
|
|
26
|
-
['.htm','text/html; charset=utf-8'],
|
|
27
|
-
['.ico','image/x-icon'],
|
|
28
|
-
['.jpeg','image/jpeg'],
|
|
29
|
-
['.jpg','image/jpeg'],
|
|
30
|
-
['.js','text/javascript; charset=utf-8'],
|
|
31
|
-
['.json','application/json; charset=utf-8'],
|
|
32
|
-
['.mjs','text/javascript; charset=utf-8'],
|
|
33
|
-
['.png','image/png'],
|
|
34
|
-
['.svg','image/svg+xml; charset=utf-8'],
|
|
35
|
-
['.txt','text/plain; charset=utf-8'],
|
|
36
|
-
['.wasm','application/wasm'],
|
|
37
|
-
['.webmanifest','application/manifest+json; charset=utf-8'],
|
|
38
|
-
['.webp','image/webp'],
|
|
39
|
-
['.woff','font/woff'],
|
|
40
|
-
['.woff2','font/woff2']
|
|
41
|
-
]);
|
|
42
23
|
const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
|
|
43
24
|
const PRIVATE_SOURCE_SEGMENTS=new Set([
|
|
44
25
|
'arcane-app.json','arcane-package.json','test','tests','scripts','node_modules','dist','local'
|
|
@@ -366,7 +347,7 @@ async function serveSourceFile(fileServer, request, response, opened, {
|
|
|
366
347
|
response,
|
|
367
348
|
{
|
|
368
349
|
lastModified,
|
|
369
|
-
contentType:
|
|
350
|
+
contentType: contentTypes[extension.substring(1)] || 'application/octet-stream',
|
|
370
351
|
body: async function createSourceRepresentation() {
|
|
371
352
|
let content = opened.content ?? await readFile(opened.candidate);
|
|
372
353
|
if (rewrite) {
|
|
@@ -1187,7 +1168,7 @@ async function startOwnedDevServer({
|
|
|
1187
1168
|
// An older in-flight snapshot may finish after a newer
|
|
1188
1169
|
// selection. Its body cannot validate that newer output.
|
|
1189
1170
|
lastModified: selectedRoutes === currentSourceRoutes ? generated.lastModified : undefined,
|
|
1190
|
-
contentType:
|
|
1171
|
+
contentType: contentTypes[path.extname(generated.path).substring(1).toLowerCase()],
|
|
1191
1172
|
body: function generatedPwaBody() {
|
|
1192
1173
|
return generated.content;
|
|
1193
1174
|
}
|
|
@@ -1313,13 +1294,6 @@ async function startOwnedDevServer({
|
|
|
1313
1294
|
await task;
|
|
1314
1295
|
return true;
|
|
1315
1296
|
}
|
|
1316
|
-
const contentType = Object.fromEntries(
|
|
1317
|
-
[...MIME_TYPES].map(
|
|
1318
|
-
function serverContentType([extension, value]) {
|
|
1319
|
-
return [extension.substring(1), value];
|
|
1320
|
-
}
|
|
1321
|
-
)
|
|
1322
|
-
);
|
|
1323
1297
|
const fileServer = new Server(
|
|
1324
1298
|
{
|
|
1325
1299
|
root: mappings[0].root,
|
|
@@ -1338,7 +1312,6 @@ async function startOwnedDevServer({
|
|
|
1338
1312
|
})
|
|
1339
1313
|
}
|
|
1340
1314
|
);
|
|
1341
|
-
fileServer.config.contentType = contentType;
|
|
1342
1315
|
fileServer.onRawRequest = serveDevelopmentRequest;
|
|
1343
1316
|
// The module's public HTTPS configuration accepts PEM paths. Its HTTPS
|
|
1344
1317
|
// guide leaves advanced TLS inputs to application code; preserve that
|