arcane-os 0.62.0 → 0.64.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 +40 -0
- package/README.md +1 -1
- package/browser-runtime/core/client.mjs +7 -2
- package/docs/reference/README.md +7 -0
- package/docs/reference/cli.md +5 -1
- package/docs/reference/core-client.md +11 -2
- package/docs/reference/core-native-packaging.md +57 -8
- package/docs/reference/core-repositories.md +149 -0
- package/docs/reference/inventory/package-api.json +163 -9
- package/docs/reference/local-ai.md +51 -7
- package/docs/reference/local-image-flux.md +154 -0
- package/docs/reference/local-image-generation.md +12 -0
- package/docs/reference/local-image-sdxl.md +139 -0
- package/docs/reference/local-whisper.md +219 -0
- package/docs/reference/native-speech.md +163 -0
- package/docs/reference/runtime-modules.md +16 -2
- package/docs/reference/sdk-api.md +335 -6
- package/package.json +5 -1
- package/runtime/arcane/components/speech.html +15 -3
- package/runtime/arcane/components/voice-transcription.html +19 -3
- package/runtime/arcane/modules/AI.js +250 -27
- package/runtime/arcane/modules/AIProviderRuntime.js +20 -3
- package/runtime/arcane/modules/AIRuntimeState.js +15 -2
- package/runtime/arcane/modules/ComponentContracts.js +3 -0
- package/schemas/arcane-app.schema.json +53 -2
- package/schemas/arcane-package.schema.json +53 -2
- package/src/core/repositories.mjs +210 -0
- package/src/core/services/speech.mjs +361 -0
- package/src/local-ai/config.mjs +9 -3
- package/src/local-ai/dev.mjs +32 -2
- package/src/local-ai/image-library.mjs +9 -0
- package/src/local-ai/image-models/flux.mjs +31 -0
- package/src/local-ai/image-models/sdxl.mjs +18 -0
- package/src/local-ai/install.mjs +26 -7
- package/src/local-ai/whisper/build.mjs +48 -0
- package/src/local-ai/whisper/config.mjs +61 -0
- package/src/local-ai/whisper/index.mjs +515 -0
- package/src/local-ai/whisper/install.mjs +274 -0
- package/src/local-ai/whisper/native/CMakeLists.txt +98 -0
- package/src/local-ai/whisper/native/NOTICE +18 -0
- package/src/local-ai/whisper/native/README.md +104 -0
- package/src/local-ai/whisper/native/main.cpp +489 -0
- package/src/native/portable-layout.mjs +37 -7
- package/src/native/windows-provider.mjs +39 -15
|
@@ -1615,17 +1615,205 @@ class AI {
|
|
|
1615
1615
|
const expectedOperation=role==='stt'?'transcribe':'synthesize';
|
|
1616
1616
|
let state='unloaded';
|
|
1617
1617
|
let busy=false;
|
|
1618
|
+
let nativeWatch=null;
|
|
1619
|
+
let nativeRequest=null;
|
|
1620
|
+
|
|
1621
|
+
function continuingNativeSpeechRequest(){
|
|
1622
|
+
const selected=nativeWatch?.snapshot?.roles?.[role];
|
|
1623
|
+
return role==='stt'&&busy&&nativeRequest?.requestId
|
|
1624
|
+
&&nativeRequest.watch===nativeWatch&&!nativeRequest.signal?.aborted
|
|
1625
|
+
&&nativeWatch.speech===runtime.#nativeSpeech(providerId,role)
|
|
1626
|
+
&&nativeWatch.modelId===runtime.#builtInSpeechModel(role)
|
|
1627
|
+
&&nativeWatch.snapshot.status==='ok'&&!nativeWatch.snapshot.closed
|
|
1628
|
+
&&selected?.modelId===nativeWatch.modelId
|
|
1629
|
+
&&selected.providerId===nativeWatch.engineId
|
|
1630
|
+
&&selected.requestId===nativeRequest.requestId
|
|
1631
|
+
&&selected.loaded===false
|
|
1632
|
+
&&((selected.state==='recovering'&&selected.busy===true)
|
|
1633
|
+
||selected.state==='error');
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function nativeSpeechReady(){
|
|
1637
|
+
if(!nativeWatch
|
|
1638
|
+
||nativeWatch.speech!==runtime.#nativeSpeech(providerId,role)
|
|
1639
|
+
||nativeWatch.modelId!==runtime.#builtInSpeechModel(role)){
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
const snapshot=nativeWatch.snapshot;
|
|
1643
|
+
const selected=snapshot?.roles?.[role];
|
|
1644
|
+
if(selected){
|
|
1645
|
+
return selected.modelId===nativeWatch.modelId
|
|
1646
|
+
&&selected.providerId===nativeWatch.engineId
|
|
1647
|
+
&&selected.loaded===true
|
|
1648
|
+
&&selected.available===true
|
|
1649
|
+
&&['ready','running'].includes(selected.state);
|
|
1650
|
+
}
|
|
1651
|
+
// Older fixed-model hosts expose independent health, not engine lifecycle.
|
|
1652
|
+
return snapshot?.status==='ok'
|
|
1653
|
+
&&nativeWatch.modelId===(role==='stt'?'whisper-small':'kokoro')
|
|
1654
|
+
&&snapshot[role==='stt'?'sttEngine':'ttsEngine']
|
|
1655
|
+
===(role==='stt'?'whisper.cpp':'kokoro-onnx')
|
|
1656
|
+
&&snapshot[role==='stt'?'transcriptionAvailable':'synthesisAvailable']===true;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function nativeSpeechReadinessError(){
|
|
1660
|
+
const snapshot=nativeWatch?.snapshot;
|
|
1661
|
+
if(nativeWatch?.error){
|
|
1662
|
+
return nativeWatch.error;
|
|
1663
|
+
}
|
|
1664
|
+
if(!snapshot){
|
|
1665
|
+
return aiProviderError(
|
|
1666
|
+
'The native speech API returned no readiness status.',
|
|
1667
|
+
'AI_NATIVE_LOCAL_REQUIRED'
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
const selected=snapshot.roles?.[role];
|
|
1671
|
+
if(selected?.modelId&&selected.modelId!==nativeWatch.modelId){
|
|
1672
|
+
return aiProviderError(
|
|
1673
|
+
`The native ${role.toUpperCase()} model differs from the selected AI model.`,
|
|
1674
|
+
'ARCANE_AI_MODEL_AUTHORITY_REQUIRED'
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
if(selected?.providerId&&selected.providerId!==nativeWatch.engineId){
|
|
1678
|
+
return aiProviderError(
|
|
1679
|
+
`The native ${role.toUpperCase()} engine was replaced.`,
|
|
1680
|
+
'ARCANE_AI_MODEL_AUTHORITY_REQUIRED'
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if(snapshot.closed===true
|
|
1684
|
+
||['closing','closed'].includes(snapshot.status)
|
|
1685
|
+
||['error','unavailable','closed','disposed'].includes(selected?.state)
|
|
1686
|
+
||(!selected&&!nativeSpeechReady())){
|
|
1687
|
+
return aiProviderError(
|
|
1688
|
+
`The native ${role.toUpperCase()} model is not ready.`,
|
|
1689
|
+
'ARCANE_AI_ROLE_NOT_READY',
|
|
1690
|
+
selected?.error
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function stopNativeSpeechWatch(){
|
|
1697
|
+
const previous=nativeWatch;
|
|
1698
|
+
nativeWatch=null;
|
|
1699
|
+
previous?.unsubscribe?.();
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function waitForNativeSpeech(context){
|
|
1703
|
+
stopNativeSpeechWatch();
|
|
1704
|
+
const watch={
|
|
1705
|
+
speech:runtime.#nativeSpeech(providerId,role),
|
|
1706
|
+
modelId:context.selection.modelId,
|
|
1707
|
+
engineId:null,
|
|
1708
|
+
snapshot:null,
|
|
1709
|
+
error:null,
|
|
1710
|
+
revision:0,
|
|
1711
|
+
unsubscribe:null
|
|
1712
|
+
};
|
|
1713
|
+
nativeWatch=watch;
|
|
1714
|
+
return new Promise(function waitForNativeSpeechState(resolve,reject){
|
|
1715
|
+
let settled=false;
|
|
1716
|
+
function finish(error){
|
|
1717
|
+
if(settled){
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
settled=true;
|
|
1721
|
+
context.signal?.removeEventListener('abort',cancel);
|
|
1722
|
+
if(error){
|
|
1723
|
+
reject(error);
|
|
1724
|
+
}else{
|
|
1725
|
+
resolve();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
function cancel(){
|
|
1729
|
+
finish(normalizeAIRequestAbort(context.signal.reason));
|
|
1730
|
+
}
|
|
1731
|
+
function reportObservationFailure(error){
|
|
1732
|
+
finish(error);
|
|
1733
|
+
arcaneLogging.error('Native speech readiness observation failed.',error);
|
|
1734
|
+
}
|
|
1735
|
+
function observe(snapshot,error=null){
|
|
1736
|
+
if(nativeWatch!==watch){
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
watch.snapshot=snapshot;
|
|
1740
|
+
watch.error=error;
|
|
1741
|
+
watch.engineId??=snapshot?.roles?.[role]?.providerId??null;
|
|
1742
|
+
if(context.signal?.aborted&&!settled){
|
|
1743
|
+
cancel();
|
|
1744
|
+
}else if(nativeSpeechReady()){
|
|
1745
|
+
finish();
|
|
1746
|
+
nativeRequest?.refreshState?.();
|
|
1747
|
+
}else if(continuingNativeSpeechRequest()){
|
|
1748
|
+
nativeRequest.refreshState?.();
|
|
1749
|
+
}else{
|
|
1750
|
+
const failure=nativeSpeechReadinessError();
|
|
1751
|
+
if(failure){
|
|
1752
|
+
finish(failure);
|
|
1753
|
+
}
|
|
1754
|
+
if(state==='ready'){
|
|
1755
|
+
state='unloaded';
|
|
1756
|
+
runtime.#retainBuiltInSpeechReadiness(
|
|
1757
|
+
runtime.#reconcileBuiltInSpeechReadiness()
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
context.signal?.addEventListener('abort',cancel,{once:true});
|
|
1763
|
+
try{
|
|
1764
|
+
if(context.signal?.aborted){
|
|
1765
|
+
cancel();
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
if(!is.function(watch.speech?.status)){
|
|
1769
|
+
throw aiProviderError(
|
|
1770
|
+
'The native speech API does not expose readiness status.',
|
|
1771
|
+
'AI_NATIVE_LOCAL_REQUIRED'
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
if(is.function(globalThis.Arcane?.events?.on)){
|
|
1775
|
+
watch.unsubscribe=globalThis.Arcane.events.on(
|
|
1776
|
+
'speech.state',
|
|
1777
|
+
function observeNativeSpeechState(snapshot){
|
|
1778
|
+
watch.revision+=1;
|
|
1779
|
+
try{
|
|
1780
|
+
observe(snapshot);
|
|
1781
|
+
}catch(error){
|
|
1782
|
+
reportObservationFailure(error);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
const revision=watch.revision;
|
|
1788
|
+
Promise.resolve(watch.speech.status()).then(
|
|
1789
|
+
function receiveNativeSpeechStatus(snapshot){
|
|
1790
|
+
if(watch.revision===revision){
|
|
1791
|
+
observe(snapshot);
|
|
1792
|
+
}
|
|
1793
|
+
},
|
|
1794
|
+
function rejectNativeSpeechStatus(error){
|
|
1795
|
+
if(watch.revision===revision){
|
|
1796
|
+
observe(null,error);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
).catch(reportObservationFailure);
|
|
1800
|
+
}catch(error){
|
|
1801
|
+
finish(error);
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1618
1805
|
|
|
1619
1806
|
function statusBuiltInSpeechProvider(){
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
state
|
|
1624
|
-
}
|
|
1807
|
+
const requestPending=Boolean(continuingNativeSpeechRequest());
|
|
1808
|
+
const selected=nativeWatch?.snapshot?.roles?.[role];
|
|
1809
|
+
const current=state==='ready'&&!nativeSpeechReady()
|
|
1810
|
+
?requestPending?selected.state:'unloaded':state;
|
|
1625
1811
|
return completeValue({
|
|
1626
|
-
state,
|
|
1627
|
-
loaded:
|
|
1628
|
-
busy
|
|
1812
|
+
state:current,
|
|
1813
|
+
loaded:current==='ready',
|
|
1814
|
+
busy,
|
|
1815
|
+
requestPending,
|
|
1816
|
+
...(current==='error'?{error:selected.error}:{})
|
|
1629
1817
|
});
|
|
1630
1818
|
}
|
|
1631
1819
|
|
|
@@ -1646,6 +1834,11 @@ class AI {
|
|
|
1646
1834
|
|
|
1647
1835
|
function releaseBuiltInSpeechRequest(){
|
|
1648
1836
|
busy=false;
|
|
1837
|
+
nativeRequest=null;
|
|
1838
|
+
if(state==='ready'&&!nativeSpeechReady()){
|
|
1839
|
+
state='unloaded';
|
|
1840
|
+
stopNativeSpeechWatch();
|
|
1841
|
+
}
|
|
1649
1842
|
}
|
|
1650
1843
|
|
|
1651
1844
|
return completeValue({
|
|
@@ -1676,7 +1869,7 @@ class AI {
|
|
|
1676
1869
|
return runtime.#builtInSpeechInspection(role,providerId,selection);
|
|
1677
1870
|
},
|
|
1678
1871
|
status:statusBuiltInSpeechProvider,
|
|
1679
|
-
load:function loadBuiltInSpeechProvider(context={}){
|
|
1872
|
+
load:async function loadBuiltInSpeechProvider(context={}){
|
|
1680
1873
|
if(context.signal?.aborted){
|
|
1681
1874
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1682
1875
|
}
|
|
@@ -1706,22 +1899,35 @@ class AI {
|
|
|
1706
1899
|
unit:'items',
|
|
1707
1900
|
heartbeat:false
|
|
1708
1901
|
});
|
|
1709
|
-
|
|
1902
|
+
try{
|
|
1903
|
+
await waitForNativeSpeech(context);
|
|
1904
|
+
if(context.signal?.aborted){
|
|
1905
|
+
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1906
|
+
}
|
|
1907
|
+
assertBuiltInSpeechSelection(context.selection);
|
|
1908
|
+
if(!nativeSpeechReady()){
|
|
1909
|
+
throw nativeSpeechReadinessError()??aiProviderError(
|
|
1910
|
+
`The native ${role.toUpperCase()} model lost readiness.`,
|
|
1911
|
+
'ARCANE_AI_ROLE_NOT_READY'
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
state='ready';
|
|
1915
|
+
context.progress({
|
|
1916
|
+
phase:'capability',
|
|
1917
|
+
completed:1,
|
|
1918
|
+
total:1,
|
|
1919
|
+
unit:'items',
|
|
1920
|
+
heartbeat:false
|
|
1921
|
+
});
|
|
1922
|
+
return completeValue({
|
|
1923
|
+
authority:inspection.authority,
|
|
1924
|
+
status:statusBuiltInSpeechProvider()
|
|
1925
|
+
});
|
|
1926
|
+
}catch(error){
|
|
1710
1927
|
state='unloaded';
|
|
1711
|
-
|
|
1928
|
+
stopNativeSpeechWatch();
|
|
1929
|
+
throw error;
|
|
1712
1930
|
}
|
|
1713
|
-
state='ready';
|
|
1714
|
-
context.progress({
|
|
1715
|
-
phase:'capability',
|
|
1716
|
-
completed:1,
|
|
1717
|
-
total:1,
|
|
1718
|
-
unit:'items',
|
|
1719
|
-
heartbeat:false
|
|
1720
|
-
});
|
|
1721
|
-
return completeValue({
|
|
1722
|
-
authority:inspection.authority,
|
|
1723
|
-
status:statusBuiltInSpeechProvider()
|
|
1724
|
-
});
|
|
1725
1931
|
},
|
|
1726
1932
|
request:function requestBuiltInSpeechProvider(context={}){
|
|
1727
1933
|
if(context.signal?.aborted){
|
|
@@ -1748,10 +1954,18 @@ class AI {
|
|
|
1748
1954
|
);
|
|
1749
1955
|
}
|
|
1750
1956
|
busy=true;
|
|
1957
|
+
const operation=role==='stt'?{
|
|
1958
|
+
requestId:null,watch:nativeWatch,signal:context.signal,
|
|
1959
|
+
refreshState:context.refreshState
|
|
1960
|
+
}:null;
|
|
1961
|
+
nativeRequest=operation;
|
|
1751
1962
|
const request=role==='stt'
|
|
1752
1963
|
?runtime.#requestBuiltInSpeechTranscription(
|
|
1753
1964
|
context.payload,
|
|
1754
|
-
context.signal
|
|
1965
|
+
context.signal,
|
|
1966
|
+
function ownNativeSpeechRequest({requestId}){
|
|
1967
|
+
if(nativeRequest===operation)operation.requestId=requestId;
|
|
1968
|
+
}
|
|
1755
1969
|
)
|
|
1756
1970
|
:runtime.#requestBuiltInSpeechSynthesis(
|
|
1757
1971
|
context.payload,
|
|
@@ -1770,6 +1984,7 @@ class AI {
|
|
|
1770
1984
|
);
|
|
1771
1985
|
}
|
|
1772
1986
|
state='unloaded';
|
|
1987
|
+
stopNativeSpeechWatch();
|
|
1773
1988
|
return statusBuiltInSpeechProvider();
|
|
1774
1989
|
},
|
|
1775
1990
|
dispose:function disposeBuiltInSpeechProvider(context={}){
|
|
@@ -1783,6 +1998,7 @@ class AI {
|
|
|
1783
1998
|
);
|
|
1784
1999
|
}
|
|
1785
2000
|
state='disposed';
|
|
2001
|
+
stopNativeSpeechWatch();
|
|
1786
2002
|
return statusBuiltInSpeechProvider();
|
|
1787
2003
|
}
|
|
1788
2004
|
});
|
|
@@ -1837,6 +2053,7 @@ class AI {
|
|
|
1837
2053
|
continue;
|
|
1838
2054
|
}
|
|
1839
2055
|
if(record.unregister()){
|
|
2056
|
+
record.provider.dispose();
|
|
1840
2057
|
this.#builtInSpeechProviders.delete(key);
|
|
1841
2058
|
}
|
|
1842
2059
|
}
|
|
@@ -1916,7 +2133,13 @@ class AI {
|
|
|
1916
2133
|
return runtime.#providerRuntime.status(role);
|
|
1917
2134
|
}
|
|
1918
2135
|
const status=runtime.#providerRuntime.status(role);
|
|
1919
|
-
|
|
2136
|
+
const record=runtime.#builtInSpeechProviders.get(
|
|
2137
|
+
runtime.#builtInSpeechProviderKey(role,selection.providerId)
|
|
2138
|
+
);
|
|
2139
|
+
const providerStatus=record?.provider.status();
|
|
2140
|
+
if(runtime.#builtInSpeechCapability(role,selection.providerId)
|
|
2141
|
+
&&(!(status.loaded===true||status.busy===true)
|
|
2142
|
+
||providerStatus?.loaded===true||providerStatus?.requestPending===true)){
|
|
1920
2143
|
return status;
|
|
1921
2144
|
}
|
|
1922
2145
|
if(status.loaded===true
|
|
@@ -4068,7 +4291,7 @@ class AI {
|
|
|
4068
4291
|
return null;
|
|
4069
4292
|
}
|
|
4070
4293
|
|
|
4071
|
-
async #requestBuiltInSpeechTranscription(payload={},signal=null){
|
|
4294
|
+
async #requestBuiltInSpeechTranscription(payload={},signal=null,onRequest=undefined){
|
|
4072
4295
|
const audio=payload?.audio;
|
|
4073
4296
|
if(!audio||!is.function(audio.arrayBuffer)){
|
|
4074
4297
|
throw new TypeError('Speech transcription requires an audio Blob or File.');
|
|
@@ -4090,7 +4313,7 @@ class AI {
|
|
|
4090
4313
|
audioBase64:this.#arrayBufferToBase64(audioBytes),
|
|
4091
4314
|
mimeType,
|
|
4092
4315
|
model
|
|
4093
|
-
},{signal});
|
|
4316
|
+
},{signal,onRequest});
|
|
4094
4317
|
}catch(error){
|
|
4095
4318
|
if(isAIRequestAbort(error,signal)){
|
|
4096
4319
|
throw normalizeAIRequestAbort(error);
|
|
@@ -4037,6 +4037,15 @@ export class AIProviderRuntime {
|
|
|
4037
4037
|
payload: options.payload,
|
|
4038
4038
|
signal: controller.signal
|
|
4039
4039
|
};
|
|
4040
|
+
if (role === 'stt') {
|
|
4041
|
+
providerRequest.refreshState = function refreshActiveProviderState() {
|
|
4042
|
+
if (slot.generation === generation && !slot.unloadPromise
|
|
4043
|
+
&& !slot.disposePromise && !controller.signal.aborted
|
|
4044
|
+
&& slot.activeRequests.get(requestSequence) === requestRecord) {
|
|
4045
|
+
runtime.#publishRoleRequestState(slot);
|
|
4046
|
+
}
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4040
4049
|
if (SPEECH_ROLES.includes(role)) {
|
|
4041
4050
|
arcaneLogging.debug('[Arcane speech runtime] provider.request', {generation, requestSequence, operationId}, providerRequest);
|
|
4042
4051
|
}
|
|
@@ -4579,16 +4588,24 @@ export class AIProviderRuntime {
|
|
|
4579
4588
|
if (!requestRecord && slot.requestQueue.length === 0) {
|
|
4580
4589
|
return false;
|
|
4581
4590
|
}
|
|
4591
|
+
const provider = this.#providerFor(slot);
|
|
4592
|
+
const providerState = slot.role === 'stt' && provider
|
|
4593
|
+
? validateProviderStatus(provider.status()) : null;
|
|
4594
|
+
const pendingRecovery = requestRecord && providerState?.loaded === false
|
|
4595
|
+
&& ['recovering', 'error'].includes(providerState.state);
|
|
4582
4596
|
publishAIRuntimeRoleState(
|
|
4583
4597
|
slot.role,
|
|
4584
4598
|
roleRecord(
|
|
4585
4599
|
slot.role,
|
|
4586
4600
|
slot.selection,
|
|
4587
4601
|
{
|
|
4588
|
-
state: 'ready',
|
|
4589
|
-
loaded:
|
|
4602
|
+
state: pendingRecovery ? providerState.state : 'ready',
|
|
4603
|
+
loaded: !pendingRecovery,
|
|
4590
4604
|
busy: true,
|
|
4591
|
-
operationId: requestRecord?.operationId ?? null
|
|
4605
|
+
operationId: requestRecord?.operationId ?? null,
|
|
4606
|
+
...(pendingRecovery && providerState.state === 'error'
|
|
4607
|
+
? {error: stateError(providerState.error, 'ARCANE_AI_PROVIDER_REQUEST_FAILED')}
|
|
4608
|
+
: {})
|
|
4592
4609
|
}
|
|
4593
4610
|
)
|
|
4594
4611
|
);
|
|
@@ -21,12 +21,22 @@ export const AI_RUNTIME_STATES = completeValue([
|
|
|
21
21
|
'unavailable',
|
|
22
22
|
'unloaded',
|
|
23
23
|
'loading',
|
|
24
|
+
'recovering',
|
|
24
25
|
'ready',
|
|
25
26
|
'unloading',
|
|
26
27
|
'error',
|
|
27
28
|
'disposed'
|
|
28
29
|
]);
|
|
29
30
|
|
|
31
|
+
/** Keep only an already-owned operation through its truthful recovery/error state. */
|
|
32
|
+
export function continuesAIRuntimeOperation(previous, current, operationId) {
|
|
33
|
+
return is.string(operationId) && operationId.length > 0
|
|
34
|
+
&& previous?.operationId === operationId && current?.operationId === operationId
|
|
35
|
+
&& previous.providerId === current.providerId && previous.modelId === current.modelId
|
|
36
|
+
&& current.loaded === false && current.busy === true
|
|
37
|
+
&& ['recovering', 'error'].includes(current.state);
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
const ROLE_KEYS = completeValue([
|
|
31
41
|
'role',
|
|
32
42
|
'state',
|
|
@@ -77,6 +87,7 @@ const MUST_BE_UNLOADED = new Set([
|
|
|
77
87
|
'unavailable',
|
|
78
88
|
'unloaded',
|
|
79
89
|
'loading',
|
|
90
|
+
'recovering',
|
|
80
91
|
'disposed'
|
|
81
92
|
]);
|
|
82
93
|
const STARTUP_TERMINAL_STATES = new Set([
|
|
@@ -291,8 +302,10 @@ function copyRoleRecord(role, record) {
|
|
|
291
302
|
&& (record.providerId === null || record.modelId === null)) {
|
|
292
303
|
fail('a loaded role must identify its provider and model.');
|
|
293
304
|
}
|
|
294
|
-
|
|
295
|
-
|
|
305
|
+
const pendingRecovery = ['recovering', 'error'].includes(record.state)
|
|
306
|
+
&& record.operationId !== null;
|
|
307
|
+
if (record.busy && !pendingRecovery && (record.state !== 'ready' || !record.loaded)) {
|
|
308
|
+
fail('a busy role must be ready and loaded or retain its recovering/error operation.');
|
|
296
309
|
}
|
|
297
310
|
if (record.state === 'error' && error === null) {
|
|
298
311
|
fail('an error role state must include error details.');
|
|
@@ -607,6 +607,9 @@ function createSTTActivationController({
|
|
|
607
607
|
if(role?.state==='loading'){
|
|
608
608
|
return `Transcription ${formatAIRuntimeProgress(role.progress,'loading')}; Cancel is available.`;
|
|
609
609
|
}
|
|
610
|
+
if(role?.state==='recovering'){
|
|
611
|
+
return 'Transcription is recovering the current request.';
|
|
612
|
+
}
|
|
610
613
|
if(role?.state==='unloading'){
|
|
611
614
|
return `Transcription ${formatAIRuntimeProgress(role.progress,'releasing')}.`;
|
|
612
615
|
}
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
"type": "array",
|
|
263
263
|
"items": {
|
|
264
264
|
"oneOf": [
|
|
265
|
-
{ "enum": ["llama.cpp", "ollama", "onnx", "stable-diffusion.cpp"] },
|
|
265
|
+
{ "enum": ["llama.cpp", "ollama", "onnx", "stable-diffusion.cpp", "whisper.cpp"] },
|
|
266
266
|
{
|
|
267
267
|
"type": "object",
|
|
268
268
|
"required": ["id"],
|
|
@@ -271,7 +271,8 @@
|
|
|
271
271
|
"version": { "type": "string" },
|
|
272
272
|
"url": { "type": "string" }
|
|
273
273
|
}
|
|
274
|
-
}
|
|
274
|
+
},
|
|
275
|
+
{ "$ref": "#/$defs/whisperRuntime" }
|
|
275
276
|
]
|
|
276
277
|
}
|
|
277
278
|
},
|
|
@@ -294,6 +295,56 @@
|
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
},
|
|
298
|
+
"whisperRuntime": {
|
|
299
|
+
"type": "object",
|
|
300
|
+
"required": ["id"],
|
|
301
|
+
"description": "Native Whisper transcription. Models are explicitly selected; describing a runtime acquires nothing. Automatic Windows x64 preparation selects intact CUDA and CPU distributions, an SDK helper, and FFmpeg. Other hosts supply their selected complete runtime and decoder trees.",
|
|
302
|
+
"properties": {
|
|
303
|
+
"id": { "const": "whisper.cpp" },
|
|
304
|
+
"version": { "type": "string", "default": "1.9.4" },
|
|
305
|
+
"backend": { "enum": ["auto", "cuda", "metal", "cpu"], "default": "auto" },
|
|
306
|
+
"helperRoot": { "type": "string", "description": "Complete helper directory copied into the native runtime, including its app-local libraries. Defaults to the directory containing helperExecutable." },
|
|
307
|
+
"helperExecutable": { "type": "string" },
|
|
308
|
+
"helperUrl": { "type": "string" },
|
|
309
|
+
"helperVersion": { "type": "string" },
|
|
310
|
+
"decoderRoot": { "type": "string" },
|
|
311
|
+
"decoderExecutable": { "type": "string" },
|
|
312
|
+
"decoderUrl": { "type": "string" },
|
|
313
|
+
"modelId": { "type": "string" },
|
|
314
|
+
"variants": {
|
|
315
|
+
"type": "array",
|
|
316
|
+
"items": {
|
|
317
|
+
"type": "object",
|
|
318
|
+
"required": ["backend"],
|
|
319
|
+
"properties": {
|
|
320
|
+
"backend": { "enum": ["cuda", "metal", "cpu"] },
|
|
321
|
+
"root": { "type": "string" },
|
|
322
|
+
"url": { "type": "string" },
|
|
323
|
+
"libraryDirectory": { "type": "string" }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"models": {
|
|
328
|
+
"type": "array",
|
|
329
|
+
"default": [],
|
|
330
|
+
"items": {
|
|
331
|
+
"oneOf": [
|
|
332
|
+
{ "const": "whisper-small" },
|
|
333
|
+
{
|
|
334
|
+
"type": "object",
|
|
335
|
+
"required": ["id"],
|
|
336
|
+
"properties": {
|
|
337
|
+
"id": { "type": "string" },
|
|
338
|
+
"path": { "type": "string" },
|
|
339
|
+
"url": { "type": "string" },
|
|
340
|
+
"filename": { "type": "string" }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
},
|
|
297
348
|
"pwa": {
|
|
298
349
|
"type": "object",
|
|
299
350
|
"additionalProperties": false,
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"type": "array",
|
|
157
157
|
"items": {
|
|
158
158
|
"oneOf": [
|
|
159
|
-
{ "enum": ["llama.cpp", "ollama", "onnx", "stable-diffusion.cpp"] },
|
|
159
|
+
{ "enum": ["llama.cpp", "ollama", "onnx", "stable-diffusion.cpp", "whisper.cpp"] },
|
|
160
160
|
{
|
|
161
161
|
"type": "object",
|
|
162
162
|
"required": ["id"],
|
|
@@ -165,7 +165,8 @@
|
|
|
165
165
|
"version": { "type": "string" },
|
|
166
166
|
"url": { "type": "string" }
|
|
167
167
|
}
|
|
168
|
-
}
|
|
168
|
+
},
|
|
169
|
+
{ "$ref": "#/$defs/whisperRuntime" }
|
|
169
170
|
]
|
|
170
171
|
}
|
|
171
172
|
},
|
|
@@ -188,6 +189,56 @@
|
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
},
|
|
192
|
+
"whisperRuntime": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"required": ["id"],
|
|
195
|
+
"description": "Native Whisper transcription. Models are explicitly selected; describing a runtime acquires nothing. Automatic Windows x64 preparation selects intact CUDA and CPU distributions, an SDK helper, and FFmpeg. Other hosts supply their selected complete runtime and decoder trees.",
|
|
196
|
+
"properties": {
|
|
197
|
+
"id": { "const": "whisper.cpp" },
|
|
198
|
+
"version": { "type": "string", "default": "1.9.4" },
|
|
199
|
+
"backend": { "enum": ["auto", "cuda", "metal", "cpu"], "default": "auto" },
|
|
200
|
+
"helperRoot": { "type": "string", "description": "Complete helper directory copied into the native runtime, including its app-local libraries. Defaults to the directory containing helperExecutable." },
|
|
201
|
+
"helperExecutable": { "type": "string" },
|
|
202
|
+
"helperUrl": { "type": "string" },
|
|
203
|
+
"helperVersion": { "type": "string" },
|
|
204
|
+
"decoderRoot": { "type": "string" },
|
|
205
|
+
"decoderExecutable": { "type": "string" },
|
|
206
|
+
"decoderUrl": { "type": "string" },
|
|
207
|
+
"modelId": { "type": "string" },
|
|
208
|
+
"variants": {
|
|
209
|
+
"type": "array",
|
|
210
|
+
"items": {
|
|
211
|
+
"type": "object",
|
|
212
|
+
"required": ["backend"],
|
|
213
|
+
"properties": {
|
|
214
|
+
"backend": { "enum": ["cuda", "metal", "cpu"] },
|
|
215
|
+
"root": { "type": "string" },
|
|
216
|
+
"url": { "type": "string" },
|
|
217
|
+
"libraryDirectory": { "type": "string" }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"models": {
|
|
222
|
+
"type": "array",
|
|
223
|
+
"default": [],
|
|
224
|
+
"items": {
|
|
225
|
+
"oneOf": [
|
|
226
|
+
{ "const": "whisper-small" },
|
|
227
|
+
{
|
|
228
|
+
"type": "object",
|
|
229
|
+
"required": ["id"],
|
|
230
|
+
"properties": {
|
|
231
|
+
"id": { "type": "string" },
|
|
232
|
+
"path": { "type": "string" },
|
|
233
|
+
"url": { "type": "string" },
|
|
234
|
+
"filename": { "type": "string" }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
191
242
|
"pwa": {
|
|
192
243
|
"type": "object",
|
|
193
244
|
"additionalProperties": false,
|