arcane-os 0.5.11 → 0.5.13
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 +33 -0
- package/README.md +8 -8
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +5 -5
- package/docs/reference/ai/browser-speech.md +73 -7
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/asset-versioning.md +69 -0
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +2 -3
- package/docs/reference/core/arcane-api.md +5 -5
- package/docs/reference/core/ollama-module.md +1 -1
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +2 -2
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +3 -3
- package/docs/reference/protocols.md +5 -5
- package/docs/reference/runtime-components.md +5 -2
- package/docs/reference/runtime-modules.md +39 -4
- package/docs/reference/sdk-api.md +6 -6
- package/package.json +1 -1
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chart.html +2 -7
- package/runtime/arcane/components/dashboard-config.html +3 -8
- package/runtime/arcane/components/file-manager.html +6 -6
- package/runtime/arcane/components/markdown-document.html +1 -6
- package/runtime/arcane/components/markdown-editor.html +2 -7
- package/runtime/arcane/components/media-embed.html +1 -1
- package/runtime/arcane/components/screen-capture.html +1 -1
- package/runtime/arcane/components/voice-transcription.html +4 -9
- package/runtime/arcane/modules/AI.js +59 -10
- package/runtime/arcane/modules/HTMLImport.js +184 -13
- package/src/dev-server.mjs +72 -5
- package/src/import-map.mjs +548 -16
- package/src/installed-sdk-runtime.mjs +1 -0
- package/src/packager/core.mjs +60 -1
- package/src/workspace-runtime.mjs +19 -7
|
@@ -18,7 +18,7 @@ This table is the Node `package.json#exports` map: it defines package
|
|
|
18
18
|
entrypoints for SDK/tooling code. It is distinct from the generated browser
|
|
19
19
|
import map that resolves application-facing `arcane/*` modules and the focused
|
|
20
20
|
EventManager entry. See [browser runtime delivery](protocols.md#browser-runtime-delivery)
|
|
21
|
-
for the installed-inventory-derived physical-runtime contract in SDK `0.5.
|
|
21
|
+
for the installed-inventory-derived physical-runtime contract in SDK `0.5.12`.
|
|
22
22
|
|
|
23
23
|
| Specifier | Purpose |
|
|
24
24
|
| --- | --- |
|
|
@@ -748,7 +748,7 @@ deterministic map. The package root also contains the public
|
|
|
748
748
|
{
|
|
749
749
|
schemaVersion: 1,
|
|
750
750
|
kind: 'arcane-app-runtime-projection',
|
|
751
|
-
sdkVersion: '0.5.
|
|
751
|
+
sdkVersion: '0.5.12',
|
|
752
752
|
pathPrefix: 'arcane/',
|
|
753
753
|
files: [{path}]
|
|
754
754
|
}
|
|
@@ -2667,7 +2667,7 @@ The import-map operation also reports the stable operation-specific strings
|
|
|
2667
2667
|
`ARCANE_IMPORT_MAP_INVALID`, `ARCANE_IMPORT_MAP_UNRESOLVED`, and
|
|
2668
2668
|
`ARCANE_IMPORT_MAP_COLLISION`; package assembly can additionally report
|
|
2669
2669
|
`ARCANE_IMPORT_MAP_CLEANUP_FAILED`. They are normalized `ArcaneError.code`
|
|
2670
|
-
values, but are not properties added to this general registry in SDK `0.5.
|
|
2670
|
+
values, but are not properties added to this general registry in SDK `0.5.12`.
|
|
2671
2671
|
|
|
2672
2672
|
### Value and import
|
|
2673
2673
|
|
|
@@ -3437,7 +3437,7 @@ workspace it additionally returns the exact installed package authority:
|
|
|
3437
3437
|
packageSource,
|
|
3438
3438
|
canonicalPackageRoot,
|
|
3439
3439
|
packageName: 'arcane-os',
|
|
3440
|
-
packageVersion: '0.5.
|
|
3440
|
+
packageVersion: '0.5.12',
|
|
3441
3441
|
runtimeRoot,
|
|
3442
3442
|
browserRuntimeRoot
|
|
3443
3443
|
}
|
|
@@ -3445,9 +3445,9 @@ workspace it additionally returns the exact installed package authority:
|
|
|
3445
3445
|
```
|
|
3446
3446
|
|
|
3447
3447
|
The dependency can be named `arcane-os` or be one exact npm alias for
|
|
3448
|
-
`npm:arcane-os@0.5.
|
|
3448
|
+
`npm:arcane-os@0.5.12`. The selected installation must still be one direct,
|
|
3449
3449
|
physical, non-link package directory whose manifest identifies exactly as
|
|
3450
|
-
`arcane-os@0.5.
|
|
3450
|
+
`arcane-os@0.5.12`; duplicate canonical/alias declarations reject.
|
|
3451
3451
|
`allowMissingManagedImportMap` is an internal packaging/development seam. An
|
|
3452
3452
|
ordinary caller should leave it `false`.
|
|
3453
3453
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>:host{display:block;max-width:34rem}.calculator{display:grid;gap:var(--arcane-space-3);padding:var(--arcane-space-4)}.display{display:grid;gap:var(--arcane-space-2)}.display input{font:600 1.3rem/1.4 ui-monospace,monospace;text-align:right}.result{min-height:2.5rem;margin:0;text-align:right;font-size:1.8rem;font-weight:750;overflow-wrap:anywhere}.keys{display:grid;gap:var(--arcane-space-2);grid-template-columns:repeat(5,1fr)}.keys button{min-width:0;padding:.7rem .35rem}.status{min-height:1.4rem;margin:0;color:var(--arcane-muted)}@media(max-width:28rem){.keys{grid-template-columns:repeat(4,1fr)}}</style>
|
|
3
3
|
<section class="calculator arcane-card"><div class="display arcane-field"><label for="expression">Expression</label><input id="expression" inputmode="decimal" autocomplete="off" spellcheck="false" value=""><p id="result" class="result" aria-live="polite">0</p></div><div id="keys" class="keys" aria-label="Calculator keypad"></div><p id="status" class="status" role="status"></p></section>
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,input=root.querySelector('#expression'),result=root.querySelector('#result'),status=root.querySelector('#status'),keys=root.querySelector('#keys');const
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,input=root.querySelector('#expression'),result=root.querySelector('#result'),status=root.querySelector('#status'),keys=root.querySelector('#keys');const {default:CalculatorEngine}=await import('../modules/CalculatorEngine.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.calculator',eventTypes:['calculation-complete','calculation-error','calculator-ready']}),lifecycleController=new AbortController();const engine=new CalculatorEngine(),definitions=['7','8','9','/','sqrt(','4','5','6','*','^','1','2','3','-','%','0','.','(',')','+','pi','e','C','⌫','='];let destroyed=false,operationSequence=0;
|
|
5
5
|
for(const value of definitions){const button=document.createElement('button');button.type='button';button.className=value==='='?'arcane-button':'arcane-button arcane-button--secondary';button.textContent=value;button.dataset.value=value;keys.append(button)}
|
|
6
6
|
keys.addEventListener('click',event=>{const value=event.target.closest('[data-value]')?.dataset.value;if(!value)return;if(value==='C'){input.value='';result.textContent='0';status.textContent='';return}if(value==='⌫'){input.value=input.value.slice(0,-1);return}if(value==='='){calculate();return}input.value+=value;input.focus()},{signal:lifecycleController.signal});input.addEventListener('keydown',event=>{if(event.key==='Enter'){event.preventDefault();calculate()}},{signal:lifecycleController.signal});
|
|
7
7
|
function nextOperationId(){operationSequence+=1;return `${events.descriptor.instanceId}:calculate:${operationSequence}`}
|
|
@@ -162,16 +162,11 @@
|
|
|
162
162
|
|
|
163
163
|
<script type="module">
|
|
164
164
|
const host=this;
|
|
165
|
-
const
|
|
166
|
-
host.getAttribute('href')||'./arcane/components/chart.html',
|
|
167
|
-
document.baseURI
|
|
168
|
-
);
|
|
169
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
170
|
-
const {default:loadChartLibrary}=await import(moduleURL('ChartLibrary.js'));
|
|
165
|
+
const {default:loadChartLibrary}=await import('../modules/ChartLibrary.js');
|
|
171
166
|
const {
|
|
172
167
|
normalizeChartOptions,
|
|
173
168
|
normalizeChartRows
|
|
174
|
-
}=await import(
|
|
169
|
+
}=await import('../modules/ComponentContracts.js');
|
|
175
170
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
176
171
|
const events=createArcaneEventSource(host,{source:'arcane.component.chart',eventTypes:['chart-remove','chart-ready']});
|
|
177
172
|
const lifecycleController=new AbortController();
|
|
@@ -11,18 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
<script type="module">
|
|
13
13
|
const host=this;
|
|
14
|
-
const
|
|
15
|
-
host.getAttribute('href')||'./arcane/components/dashboard-config.html',
|
|
16
|
-
document.baseURI
|
|
17
|
-
);
|
|
18
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
19
|
-
const {default:waitForComponent}=await import(moduleURL('WaitForComponent.js'));
|
|
14
|
+
const {default:waitForComponent}=await import('../modules/WaitForComponent.js');
|
|
20
15
|
const {
|
|
21
16
|
effectiveDashboardVisibility,
|
|
22
17
|
normalizeDashboardDefinitions,
|
|
23
18
|
normalizeDashboardOptions,
|
|
24
19
|
normalizeDashboardVisibility
|
|
25
|
-
}=await import(
|
|
20
|
+
}=await import('../modules/ComponentContracts.js');
|
|
26
21
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
27
22
|
const events=createArcaneEventSource(host,{source:'arcane.component.dashboard-config',eventTypes:['dashboard-config-closed','dashboard-config-opened','dashboard-config-change','dashboard-config-ready']});
|
|
28
23
|
const lifecycleController=new AbortController();
|
|
@@ -232,7 +227,7 @@
|
|
|
232
227
|
const items=document.createElement('section');
|
|
233
228
|
|
|
234
229
|
styles.rel='stylesheet';
|
|
235
|
-
styles.href='./arcane/css/dashboard-config.css?v=2';
|
|
230
|
+
styles.href=new URL('./arcane/css/dashboard-config.css?v=2',document.baseURI).href;
|
|
236
231
|
heading.dataset.dashboardConfigHeading='';
|
|
237
232
|
message.dataset.dashboardConfigDescription='';
|
|
238
233
|
empty.className='dashboard-config-empty';
|
|
@@ -825,8 +825,8 @@
|
|
|
825
825
|
icon.className='tree-entry-icon';
|
|
826
826
|
icon.alt='';
|
|
827
827
|
icon.src=entry.kind==='directory'
|
|
828
|
-
?'./arcane/img/folder.svg'
|
|
829
|
-
:'./arcane/img/doc.svg';
|
|
828
|
+
?new URL('./arcane/img/folder.svg',document.baseURI).href
|
|
829
|
+
:new URL('./arcane/img/doc.svg',document.baseURI).href;
|
|
830
830
|
name.className='tree-entry-name';
|
|
831
831
|
name.innerText=entry.name;
|
|
832
832
|
count.className='tree-file-count';
|
|
@@ -1404,7 +1404,7 @@
|
|
|
1404
1404
|
deleteButton.innerText='Delete File';
|
|
1405
1405
|
deleteIcon.alt='';
|
|
1406
1406
|
deleteIcon.className='action-icon';
|
|
1407
|
-
deleteIcon.src='./arcane/img/trash.svg';
|
|
1407
|
+
deleteIcon.src=new URL('./arcane/img/trash.svg',document.baseURI).href;
|
|
1408
1408
|
deleteButton.prepend(deleteIcon);
|
|
1409
1409
|
deleteButton.addEventListener(
|
|
1410
1410
|
'click',
|
|
@@ -1610,7 +1610,7 @@
|
|
|
1610
1610
|
|
|
1611
1611
|
fileIcon.alt='';
|
|
1612
1612
|
fileIcon.className='icon file-icon';
|
|
1613
|
-
fileIcon.src='./arcane/img/doc.svg';
|
|
1613
|
+
fileIcon.src=new URL('./arcane/img/doc.svg',document.baseURI).href;
|
|
1614
1614
|
fileDetails.className='file-details';
|
|
1615
1615
|
fileLabel.className='file-name';
|
|
1616
1616
|
fileLabel.innerText=fileName;
|
|
@@ -1633,7 +1633,7 @@
|
|
|
1633
1633
|
|
|
1634
1634
|
deleteIcon.alt='';
|
|
1635
1635
|
deleteIcon.className='action-icon';
|
|
1636
|
-
deleteIcon.src='./arcane/img/trash.svg';
|
|
1636
|
+
deleteIcon.src=new URL('./arcane/img/trash.svg',document.baseURI).href;
|
|
1637
1637
|
|
|
1638
1638
|
openButton.addEventListener(
|
|
1639
1639
|
'click',
|
|
@@ -1933,7 +1933,7 @@
|
|
|
1933
1933
|
directory.dataset.directory=tableName;
|
|
1934
1934
|
folder.className='file folder';
|
|
1935
1935
|
folderIcon.className='icon';
|
|
1936
|
-
folderIcon.src='./arcane/img/folder.svg';
|
|
1936
|
+
folderIcon.src=new URL('./arcane/img/folder.svg',document.baseURI).href;
|
|
1937
1937
|
folderName.innerText=tableName;
|
|
1938
1938
|
fileCount.className='file-count';
|
|
1939
1939
|
fileCount.innerText=`${files.length} ${files.length===1?'file':'files'}`;
|
|
@@ -214,12 +214,7 @@
|
|
|
214
214
|
const host=this;
|
|
215
215
|
host.ready=false;
|
|
216
216
|
|
|
217
|
-
const
|
|
218
|
-
host.getAttribute('href')||'./arcane/components/markdown-document.html',
|
|
219
|
-
document.baseURI
|
|
220
|
-
);
|
|
221
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
222
|
-
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
217
|
+
const {default:MD}=await import('../modules/MD.js?v=2');
|
|
223
218
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
224
219
|
const events=createArcaneEventSource(host,{source:'arcane.component.markdown-document',eventTypes:['markdown-document-state','markdown-document-empty','markdown-document-error','markdown-document-loading','markdown-document-rendered','markdown-document-navigate','markdown-document-ready']});
|
|
225
220
|
const lifecycleController=new AbortController();
|
|
@@ -75,16 +75,11 @@
|
|
|
75
75
|
|
|
76
76
|
<script type="module">
|
|
77
77
|
const host=this;
|
|
78
|
-
const
|
|
79
|
-
host.getAttribute('href')||'./arcane/components/markdown-editor.html',
|
|
80
|
-
document.baseURI
|
|
81
|
-
);
|
|
82
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
83
|
-
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
78
|
+
const {default:MD}=await import('../modules/MD.js?v=2');
|
|
84
79
|
const {
|
|
85
80
|
applyMarkdownFormat,
|
|
86
81
|
normalizeMarkdownOptions
|
|
87
|
-
}=await import(
|
|
82
|
+
}=await import('../modules/ComponentContracts.js');
|
|
88
83
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
89
84
|
const events=createArcaneEventSource(host,{source:'arcane.component.markdown-editor',eventTypes:['markdown-editor-change','markdown-editor-saved','markdown-editor-ready']});
|
|
90
85
|
const lifecycleController=new AbortController();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>:host{display:block}.player-shell{display:grid;gap:var(--arcane-space-4)}.loader{display:flex;gap:var(--arcane-space-2)}.loader input{min-width:0;width:100%}.player{aspect-ratio:16/9;display:grid;place-items:center;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}iframe{width:100%;height:100%;border:0}.empty{text-align:center;color:var(--arcane-muted);padding:var(--arcane-space-5)}.footer{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between}.status{margin:0;color:var(--arcane-muted)}@media(max-width:36rem){.loader,.footer{align-items:stretch;flex-direction:column}}</style>
|
|
3
3
|
<section class="player-shell"><form id="loader" class="loader"><label class="visually-hidden" for="mediaUrl">Video or playlist address</label><input id="mediaUrl" type="text" inputmode="url" autocomplete="off" spellcheck="false" placeholder="Paste a YouTube video or playlist URL"><button class="arcane-button" type="submit">Play</button></form><div class="player"><div id="empty" class="empty"><strong>Choose something to play</strong><p>Paste a YouTube or YouTube Music video or playlist address.</p></div><iframe id="frame" title="Embedded media player" hidden allow="autoplay; encrypted-media; picture-in-picture; fullscreen" allowfullscreen></iframe></div><div class="footer"><p id="status" class="status" role="status" aria-live="polite">Nothing loaded.</p><button id="platform" class="arcane-button arcane-button--secondary" type="button">Open platform</button></div></section>
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,form=root.querySelector('#loader'),input=root.querySelector('#mediaUrl'),frame=root.querySelector('#frame'),empty=root.querySelector('#empty'),status=root.querySelector('#status');const
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,form=root.querySelector('#loader'),input=root.querySelector('#mediaUrl'),frame=root.querySelector('#frame'),empty=root.querySelector('#empty'),status=root.querySelector('#status');const {parseYouTubeMedia,youtubeEmbedUrl}=await import('../modules/YouTubeMedia.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.media-embed',eventTypes:['media-open-platform','media-load','media-error','media-embed-ready']}),lifecycleController=new AbortController();let platformUrl='https://www.youtube.com/',privacyEnhanced=false,destroyed=false,operationSequence=0;
|
|
5
5
|
function nextOperationId(kind){operationSequence+=1;return `${events.descriptor.instanceId}:${kind}:${operationSequence}`}
|
|
6
6
|
function emit(type,detail={},publicDetail={},operationId=null){if(destroyed)return false;const publication=events.dispatch(type,detail,{publicDetail,operationId});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true})}
|
|
7
7
|
host.configure=options=>{if(destroyed)return false;platformUrl=String(options?.platformUrl||platformUrl);if(Object.hasOwn(options||{},'privacyEnhanced'))privacyEnhanced=options.privacyEnhanced===true;if(options?.placeholder)input.placeholder=String(options.placeholder);if(options?.openLabel)root.querySelector('#platform').textContent=String(options.openLabel);if(options?.initialValue){input.value=String(options.initialValue);load(input.value)}return host};host.load=load;host.destroy=destroy;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>:host{display:block}.capture{display:grid;gap:var(--arcane-space-4)}.actions{display:grid;gap:var(--arcane-space-3);grid-template-columns:repeat(3,minmax(0,1fr))}.action{display:grid;gap:var(--arcane-space-2);padding:var(--arcane-space-4);text-align:left}.action strong{font-size:1.05rem}.action span{color:var(--arcane-muted);font-size:.85rem}.recording{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between;padding:var(--arcane-space-3);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-medium)}.preview{display:grid;place-items:center;min-height:16rem;overflow:auto;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}.preview :where(img,video){display:block}.empty{color:var(--arcane-muted);text-align:center}.status{margin:0;color:var(--arcane-muted)}@media(max-width:44rem){.actions{grid-template-columns:1fr}}</style>
|
|
3
3
|
<section class="capture"><div class="actions"><button class="action arcane-card" type="button" data-capture="image"><strong>Screenshot</strong><span>Capture the selected display as PNG.</span></button><button class="action arcane-card" type="button" data-capture="video"><strong>Record video</strong><span>Record the selected display in a browser-supported video format.</span></button><button class="action arcane-card" type="button" data-capture="gif"><strong>Record GIF</strong><span>Record the selected display as an animated GIF.</span></button></div><div id="recording" class="recording" hidden><span id="recordingLabel">Recording…</span><button id="stop" class="arcane-button" type="button">Stop recording</button></div><div id="preview" class="preview"><div class="empty"><strong>No capture yet</strong><p>Your capture remains on this device unless you choose to save it.</p></div></div><p id="status" class="status" role="status" aria-live="polite">Choose a capture type. Your browser will ask which display to share.</p></section>
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,preview=root.querySelector('#preview'),recording=root.querySelector('#recording'),status=root.querySelector('#status');const
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,preview=root.querySelector('#preview'),recording=root.querySelector('#recording'),status=root.querySelector('#status');const {default:ScreenCapture}=await import('../modules/ScreenCapture.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.screen-capture',eventTypes:['screen-capture-result','screen-capture-ready']});const capture=new ScreenCapture(),listeners=new AbortController();let destroyed=false,operationGeneration=0,previewUrl='';
|
|
5
5
|
function emit(type,detail={},publicDetail={},operationId=null){if(destroyed)return false;const publication=events.dispatch(type,detail,{operationId,publicDetail});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true})}
|
|
6
6
|
function current(generation){return !destroyed&&generation===operationGeneration}
|
|
7
7
|
function cleanupCaptureStart(originalError=null){const cleanupErrors=[];if(capture.recorder&&capture.recorder.state!=='inactive'){try{capture.recorder.stop()}catch(error){cleanupErrors.push(error)}}try{capture.stopTracks(capture.stream)}catch(error){cleanupErrors.push(error)}try{capture.reset()}catch(error){cleanupErrors.push(error)}for(const error of cleanupErrors)console.error('Error cleaning up rejected screen capture start:',error);return originalError}
|
|
@@ -84,21 +84,16 @@
|
|
|
84
84
|
]
|
|
85
85
|
}
|
|
86
86
|
);
|
|
87
|
-
const
|
|
88
|
-
host.getAttribute('href')||'./arcane/components/voice-transcription.html',
|
|
89
|
-
document.baseURI
|
|
90
|
-
);
|
|
91
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
92
|
-
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
87
|
+
const {default:MD}=await import('../modules/MD.js?v=2');
|
|
93
88
|
const {
|
|
94
89
|
appendTranscription,
|
|
95
90
|
createSTTActivationController,
|
|
96
91
|
normalizeVoiceOptions
|
|
97
|
-
}=await import(
|
|
92
|
+
}=await import('../modules/ComponentContracts.js');
|
|
98
93
|
const {
|
|
99
94
|
requestAIRuntimeIntent,
|
|
100
95
|
subscribeAIRuntimeState
|
|
101
|
-
}=await import(
|
|
96
|
+
}=await import('../modules/AIRuntimeState.js');
|
|
102
97
|
const description=host.shadowRoot.querySelector('#description');
|
|
103
98
|
const transcriptElement=host.shadowRoot.querySelector('#transcript');
|
|
104
99
|
const status=host.shadowRoot.querySelector('#status');
|
|
@@ -598,7 +593,7 @@
|
|
|
598
593
|
return options.transcribe(file,context);
|
|
599
594
|
}
|
|
600
595
|
if(typeof globalThis.ai?.fetchSTT!=='function'){
|
|
601
|
-
await import(
|
|
596
|
+
await import('../modules/AI.js');
|
|
602
597
|
}
|
|
603
598
|
if(typeof globalThis.ai?.fetchSTT!=='function'){
|
|
604
599
|
const error=new Error('Speech transcription is not configured.');
|
|
@@ -5673,7 +5673,8 @@ class AI {
|
|
|
5673
5673
|
|
|
5674
5674
|
streamTTS(
|
|
5675
5675
|
text='',
|
|
5676
|
-
end=false
|
|
5676
|
+
end=false,
|
|
5677
|
+
options={}
|
|
5677
5678
|
){
|
|
5678
5679
|
if(this.muted){
|
|
5679
5680
|
if(end){
|
|
@@ -5682,6 +5683,12 @@ class AI {
|
|
|
5682
5683
|
return Promise.resolve(false);
|
|
5683
5684
|
}
|
|
5684
5685
|
|
|
5686
|
+
const {voice,speed,waitForPlayback=false}=options;
|
|
5687
|
+
const pauseAfterMs=Number(options.pauseAfterMs??0);
|
|
5688
|
+
if(!Number.isFinite(pauseAfterMs)||pauseAfterMs<0){
|
|
5689
|
+
throw new RangeError('Speech pauses must be a nonnegative number of milliseconds.');
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5685
5692
|
this.audioMessageChunks+=String(text||'');
|
|
5686
5693
|
const outputs=this.#extractSpeechSegments(end);
|
|
5687
5694
|
|
|
@@ -5702,8 +5709,13 @@ class AI {
|
|
|
5702
5709
|
const generation=this.speechGeneration;
|
|
5703
5710
|
const jobs=[];
|
|
5704
5711
|
|
|
5705
|
-
for(const output of outputs){
|
|
5706
|
-
jobs.push(this.#queueSpeechJob(output,generation
|
|
5712
|
+
for(const [index,output] of outputs.entries()){
|
|
5713
|
+
jobs.push(this.#queueSpeechJob(output,generation,{
|
|
5714
|
+
voice,
|
|
5715
|
+
speed,
|
|
5716
|
+
waitForPlayback,
|
|
5717
|
+
pauseAfterMs:index===outputs.length-1?pauseAfterMs:0
|
|
5718
|
+
}));
|
|
5707
5719
|
}
|
|
5708
5720
|
|
|
5709
5721
|
return Promise.all(jobs).then(
|
|
@@ -5857,7 +5869,7 @@ class AI {
|
|
|
5857
5869
|
return this.#speechAbbreviations.has(token);
|
|
5858
5870
|
}
|
|
5859
5871
|
|
|
5860
|
-
#queueSpeechJob(text,generation){
|
|
5872
|
+
#queueSpeechJob(text,generation,options={}){
|
|
5861
5873
|
const job={
|
|
5862
5874
|
abortController:null,
|
|
5863
5875
|
audioBuffer:null,
|
|
@@ -5867,13 +5879,22 @@ class AI {
|
|
|
5867
5879
|
scheduledStart:null,
|
|
5868
5880
|
sourceNode:null,
|
|
5869
5881
|
state:'queued',
|
|
5870
|
-
text
|
|
5882
|
+
text,
|
|
5883
|
+
voice:options.voice,
|
|
5884
|
+
speed:options.speed,
|
|
5885
|
+
pauseAfterMs:options.pauseAfterMs??0,
|
|
5886
|
+
resolvePlayback:null
|
|
5871
5887
|
};
|
|
5872
5888
|
const runtime=this;
|
|
5889
|
+
const playback=options.waitForPlayback===true
|
|
5890
|
+
?new Promise(function captureSpeechPlaybackCompletion(resolve){
|
|
5891
|
+
job.resolvePlayback=resolve;
|
|
5892
|
+
})
|
|
5893
|
+
:null;
|
|
5873
5894
|
|
|
5874
5895
|
this.speechJobs.push(job);
|
|
5875
5896
|
|
|
5876
|
-
|
|
5897
|
+
const preparation=Promise.resolve().then(
|
|
5877
5898
|
function synthesizeAvailableSpeech(){
|
|
5878
5899
|
return runtime.#prepareSpeechJob(job);
|
|
5879
5900
|
}
|
|
@@ -5886,6 +5907,7 @@ class AI {
|
|
|
5886
5907
|
);
|
|
5887
5908
|
}
|
|
5888
5909
|
);
|
|
5910
|
+
return playback||preparation;
|
|
5889
5911
|
}
|
|
5890
5912
|
|
|
5891
5913
|
async #prepareSpeechJob(job){
|
|
@@ -5914,16 +5936,18 @@ class AI {
|
|
|
5914
5936
|
async #requestSpeechAudio(job){
|
|
5915
5937
|
job.abortController=new AbortController();
|
|
5916
5938
|
const selection=this.#providerRuntime.selection('tts');
|
|
5917
|
-
const voice=
|
|
5939
|
+
const voice=job.voice===undefined
|
|
5940
|
+
?(selection?this.#providerSpeechVoice():null)
|
|
5941
|
+
:job.voice;
|
|
5918
5942
|
const response=await this.fetchTTS(
|
|
5919
5943
|
{
|
|
5920
5944
|
model:selection?.modelId||this.modelTTS,
|
|
5921
5945
|
input:job.text,
|
|
5922
|
-
...(voice?{voice}:{}),
|
|
5946
|
+
...(job.voice!==undefined||voice?{voice}:{}),
|
|
5923
5947
|
responseFormat:selection
|
|
5924
5948
|
?this.#providerSpeechResponseFormat()
|
|
5925
5949
|
:this.audioFormat,
|
|
5926
|
-
speed:this.voiceSpeed
|
|
5950
|
+
speed:job.speed===undefined?this.voiceSpeed:job.speed
|
|
5927
5951
|
},
|
|
5928
5952
|
job.abortController.signal
|
|
5929
5953
|
);
|
|
@@ -6294,6 +6318,8 @@ class AI {
|
|
|
6294
6318
|
for(const job of this.speechJobs){
|
|
6295
6319
|
job.abortController?.abort();
|
|
6296
6320
|
job.state='cancelled';
|
|
6321
|
+
job.resolvePlayback?.(false);
|
|
6322
|
+
job.resolvePlayback=null;
|
|
6297
6323
|
|
|
6298
6324
|
if(job.sourceNode){
|
|
6299
6325
|
job.sourceNode.onended=null;
|
|
@@ -6370,6 +6396,22 @@ class AI {
|
|
|
6370
6396
|
if(attempt===this.speechResumeAttempt){
|
|
6371
6397
|
this.speechResumePending=false;
|
|
6372
6398
|
}
|
|
6399
|
+
if(context?.state==='closed'){
|
|
6400
|
+
this.#clearSpeechUnlock();
|
|
6401
|
+
const jobs=this.speechJobs.filter(function usesClosedSpeechContext(job){
|
|
6402
|
+
return job.audioContext===context;
|
|
6403
|
+
});
|
|
6404
|
+
if(!jobs.length){
|
|
6405
|
+
this.#publishTTSFailure(error,{
|
|
6406
|
+
boundary:'playback-resume',
|
|
6407
|
+
generation:this.speechGeneration
|
|
6408
|
+
});
|
|
6409
|
+
}
|
|
6410
|
+
for(const job of jobs){
|
|
6411
|
+
this.#failSpeechJob(job,error,'playback-resume');
|
|
6412
|
+
}
|
|
6413
|
+
return false;
|
|
6414
|
+
}
|
|
6373
6415
|
this.#waitForSpeechGesture(error,context);
|
|
6374
6416
|
if(error?.name!=='NotAllowedError'){
|
|
6375
6417
|
this.#publishTTSFailure(error,{
|
|
@@ -6543,7 +6585,7 @@ class AI {
|
|
|
6543
6585
|
job.state='scheduled';
|
|
6544
6586
|
job.scheduledStart=scheduledStart;
|
|
6545
6587
|
job.scheduledEnd=hasKnownDuration
|
|
6546
|
-
?scheduledStart+duration
|
|
6588
|
+
?scheduledStart+duration+(job.pauseAfterMs??0)/1000
|
|
6547
6589
|
:null;
|
|
6548
6590
|
job.sourceNode.__arcaneStarted=true;
|
|
6549
6591
|
if(!this.currentSpeechJob){
|
|
@@ -6606,6 +6648,11 @@ class AI {
|
|
|
6606
6648
|
job.sourceNode.onended=null;
|
|
6607
6649
|
}
|
|
6608
6650
|
|
|
6651
|
+
if(job.scheduledEnd===null&&job.pauseAfterMs>0){
|
|
6652
|
+
this.speechScheduleContext=job.audioContext;
|
|
6653
|
+
this.speechScheduleTime=
|
|
6654
|
+
(Number(job.audioContext?.currentTime)||0)+job.pauseAfterMs/1000;
|
|
6655
|
+
}
|
|
6609
6656
|
job.state='complete';
|
|
6610
6657
|
this.#removeSpeechJob(job);
|
|
6611
6658
|
|
|
@@ -6727,6 +6774,8 @@ class AI {
|
|
|
6727
6774
|
}
|
|
6728
6775
|
|
|
6729
6776
|
#removeSpeechJob(job){
|
|
6777
|
+
job.resolvePlayback?.(job.state==='complete');
|
|
6778
|
+
job.resolvePlayback=null;
|
|
6730
6779
|
const jobIndex=this.speechJobs.indexOf(job);
|
|
6731
6780
|
|
|
6732
6781
|
if(jobIndex>=0){
|