arcane-os 0.50.0 → 0.51.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 +22 -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/inventory/package-api.json +15 -1
- package/docs/reference/runtime-components.md +13 -2
- package/docs/reference/sdk-api.md +54 -0
- package/package.json +1 -1
- package/runtime/arcane/components/speech.html +212 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.51.0
|
|
4
|
+
|
|
5
|
+
- Add synchronous native speech `provider.setDefaultVoice(voiceURI|null)`.
|
|
6
|
+
Update the live catalog and future preparations without replacing the
|
|
7
|
+
provider or altering explicit voices and prepared, queued or active speech.
|
|
8
|
+
Catalog subscriptions can apply an application-owned choice after voice
|
|
9
|
+
discovery; repeated values are silent and reentrant changes avoid stale
|
|
10
|
+
catalog delivery. Voice preference policy remains application-owned.
|
|
11
|
+
- Add `fetchSystemOneRequest` through `arcane-os/ai/twin-cloud` for explicit
|
|
12
|
+
model, complete caller-owned state and questions, and the complete native
|
|
13
|
+
System One response. Reuse the existing JSON transport, retry observers,
|
|
14
|
+
diagnostics and cancellation. Applications retain question definitions,
|
|
15
|
+
scoring policy, model selection and answer interpretation; no history,
|
|
16
|
+
fabricated tool calls, prompt conversion or new dependency is introduced.
|
|
17
|
+
- Keep all native recognition-language suggestions visible when the editable
|
|
18
|
+
picker opens or reopens. Filter only after text changes, show native language
|
|
19
|
+
and region labels, preserve exact custom BCP47 tags, and keep keyboard and
|
|
20
|
+
focus interaction in the shared speech control. The picker is available in
|
|
21
|
+
live-transcription mode before capture and disabled during capture or outside
|
|
22
|
+
that mode. Conversation language, Chat controls and capture payloads remain
|
|
23
|
+
unchanged.
|
|
24
|
+
|
|
3
25
|
## 0.50.0
|
|
4
26
|
|
|
5
27
|
- 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.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
|
|
|
@@ -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. |
|
|
@@ -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 |
|
|
@@ -7953,6 +7954,59 @@ console.log(response);
|
|
|
7953
7954
|
`server-config.json` is an application-owned, ignored configuration file;
|
|
7954
7955
|
never commit its key or print it in diagnostics. The SDK does not read it.
|
|
7955
7956
|
|
|
7957
|
+
## fetchSystemOneRequest()
|
|
7958
|
+
|
|
7959
|
+
### Overview
|
|
7960
|
+
|
|
7961
|
+
Sends one native TWiN System One request with caller-owned credentials, model,
|
|
7962
|
+
state, and questions. Question definitions, scales, model selection, and answer
|
|
7963
|
+
interpretation remain application responsibilities.
|
|
7964
|
+
|
|
7965
|
+
### Signature and result
|
|
7966
|
+
|
|
7967
|
+
```text
|
|
7968
|
+
async fetchSystemOneRequest({twinKey,model,state,questions,signal=null,id=Date.now(),onRequest,onResponse,onRetry=null}={})
|
|
7969
|
+
```
|
|
7970
|
+
|
|
7971
|
+
Import the named function from `arcane-os/ai/twin-cloud`. It posts
|
|
7972
|
+
`{model,state,questions}` to `https://inference.do-ai.run/v1/systemone` and
|
|
7973
|
+
returns the entire parsed provider JSON. It adds no chat messages, tools,
|
|
7974
|
+
scoring rules, or output limit. Payload content is preserved within ordinary
|
|
7975
|
+
JSON transport encoding; serialization occurs before the diagnostic callback.
|
|
7976
|
+
|
|
7977
|
+
`onRequest(request,id,metadata)` receives the complete payload before dispatch,
|
|
7978
|
+
with `{operation:'systemone',transport:'http',destination:'https://inference.do-ai.run/v1/systemone'}`
|
|
7979
|
+
as metadata. `onResponse(response,id,false)` receives the entire parsed result
|
|
7980
|
+
before return. Both callbacks are awaited and failures propagate. The key is
|
|
7981
|
+
used only for transport authentication, outside the diagnostic payloads.
|
|
7982
|
+
|
|
7983
|
+
### Availability and normalization
|
|
7984
|
+
|
|
7985
|
+
**Node and Browser; Cloud transport.** Uses the existing `fetchJSONResponse`
|
|
7986
|
+
owner for complete errors, retries, `onRetry`, and `signal` cancellation, with
|
|
7987
|
+
no browser singleton, profile, storage, or retained history. A missing key uses
|
|
7988
|
+
`AI_PROVIDER_NOT_CONFIGURED`; a missing explicit model throws `TypeError`.
|
|
7989
|
+
Cancellation prevents successful return and uses `ARCANE_AI_REQUEST_ABORTED`,
|
|
7990
|
+
including checks after callback settlement. Import starts no request.
|
|
7991
|
+
|
|
7992
|
+
### Example
|
|
7993
|
+
|
|
7994
|
+
```javascript
|
|
7995
|
+
import {fetchSystemOneRequest} from 'arcane-os/ai/twin-cloud';
|
|
7996
|
+
|
|
7997
|
+
const response = await fetchSystemOneRequest({
|
|
7998
|
+
twinKey: applicationRuntime.twinKey,
|
|
7999
|
+
model: 'typesafe-jev-1.13.0',
|
|
8000
|
+
state: applicationState,
|
|
8001
|
+
questions: applicationQuestions
|
|
8002
|
+
});
|
|
8003
|
+
```
|
|
8004
|
+
|
|
8005
|
+
The runtime configuration, state, and questions in this example belong to the
|
|
8006
|
+
application. The JEV identifier is an explicit caller choice, not a default.
|
|
8007
|
+
See [TWiN Cloud](ai/twin-cloud.md#evaluate-caller-owned-state-with-system-one)
|
|
8008
|
+
for the complete transport and callback contract.
|
|
8009
|
+
|
|
7956
8010
|
## fetchHTTPResponse()
|
|
7957
8011
|
|
|
7958
8012
|
### 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;
|