@openpalm/ui 0.13.0-beta.6 → 0.13.0-beta.8
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/README.md +3 -2
- package/build/.openpalm-ui-version +1 -1
- package/build/client/_app/immutable/assets/4.Cd3Y_8BZ.css +1 -0
- package/build/client/_app/immutable/assets/4.Cd3Y_8BZ.css.br +3 -0
- package/build/client/_app/immutable/assets/4.Cd3Y_8BZ.css.gz +0 -0
- package/build/client/_app/immutable/assets/6.BM7A1dW5.css +1 -0
- package/build/client/_app/immutable/assets/6.BM7A1dW5.css.br +0 -0
- package/build/client/_app/immutable/assets/6.BM7A1dW5.css.gz +0 -0
- package/build/client/_app/immutable/assets/7.DHMKw-HL.css +1 -0
- package/build/client/_app/immutable/assets/7.DHMKw-HL.css.br +0 -0
- package/build/client/_app/immutable/assets/7.DHMKw-HL.css.gz +0 -0
- package/build/client/_app/immutable/assets/9.CmCaAuiy.css +1 -0
- package/build/client/_app/immutable/assets/9.CmCaAuiy.css.br +0 -0
- package/build/client/_app/immutable/assets/9.CmCaAuiy.css.gz +0 -0
- package/build/client/_app/immutable/assets/QuestionCard.CSfD3z_X.css +1 -0
- package/build/client/_app/immutable/assets/QuestionCard.CSfD3z_X.css.br +0 -0
- package/build/client/_app/immutable/assets/QuestionCard.CSfD3z_X.css.gz +0 -0
- package/build/client/_app/immutable/chunks/1F1QVf3N.js +1 -0
- package/build/client/_app/immutable/chunks/1F1QVf3N.js.br +0 -0
- package/build/client/_app/immutable/chunks/1F1QVf3N.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DmFbAjwW.js → 92accNiq.js} +1 -1
- package/build/client/_app/immutable/chunks/92accNiq.js.br +0 -0
- package/build/client/_app/immutable/chunks/92accNiq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B9uMOgDZ.js +3 -0
- package/build/client/_app/immutable/chunks/B9uMOgDZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/B9uMOgDZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{SXdvPq3_.js → BBXQKGHA.js} +1 -1
- package/build/client/_app/immutable/chunks/BBXQKGHA.js.br +1 -0
- package/build/client/_app/immutable/chunks/BBXQKGHA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CUa5ff3d.js → BGx9HO1u.js} +1 -1
- package/build/client/_app/immutable/chunks/BGx9HO1u.js.br +0 -0
- package/build/client/_app/immutable/chunks/BGx9HO1u.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BwKrb5qD.js +1 -0
- package/build/client/_app/immutable/chunks/BwKrb5qD.js.br +2 -0
- package/build/client/_app/immutable/chunks/BwKrb5qD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BDK50g8a.js → ByiipSaR.js} +1 -1
- package/build/client/_app/immutable/chunks/ByiipSaR.js.br +3 -0
- package/build/client/_app/immutable/chunks/ByiipSaR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C092q_Hl.js +1 -0
- package/build/client/_app/immutable/chunks/C092q_Hl.js.br +0 -0
- package/build/client/_app/immutable/chunks/C092q_Hl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{D5FtEngX.js → C4qTU5YY.js} +1 -1
- package/build/client/_app/immutable/chunks/C4qTU5YY.js.br +0 -0
- package/build/client/_app/immutable/chunks/C4qTU5YY.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C6rMFkms.js +14 -0
- package/build/client/_app/immutable/chunks/C6rMFkms.js.br +0 -0
- package/build/client/_app/immutable/chunks/C6rMFkms.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C7g46Pjm.js +1 -0
- package/build/client/_app/immutable/chunks/C7g46Pjm.js.br +0 -0
- package/build/client/_app/immutable/chunks/C7g46Pjm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{mG6HnNbi.js → CJc9QEdG.js} +1 -1
- package/build/client/_app/immutable/chunks/CJc9QEdG.js.br +2 -0
- package/build/client/_app/immutable/chunks/CJc9QEdG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CR3nChHO.js +3 -0
- package/build/client/_app/immutable/chunks/CR3nChHO.js.br +0 -0
- package/build/client/_app/immutable/chunks/CR3nChHO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CXv_deBx.js +1 -0
- package/build/client/_app/immutable/chunks/CXv_deBx.js.br +0 -0
- package/build/client/_app/immutable/chunks/CXv_deBx.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CzKYnjEX.js +1 -0
- package/build/client/_app/immutable/chunks/CzKYnjEX.js.br +0 -0
- package/build/client/_app/immutable/chunks/CzKYnjEX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-DB6zTo.js +9 -0
- package/build/client/_app/immutable/chunks/D-DB6zTo.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-DB6zTo.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D3d8Mm8X.js +1 -0
- package/build/client/_app/immutable/chunks/D3d8Mm8X.js.br +1 -0
- package/build/client/_app/immutable/chunks/D3d8Mm8X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DDSqq1hR.js +1 -0
- package/build/client/_app/immutable/chunks/DDSqq1hR.js.br +0 -0
- package/build/client/_app/immutable/chunks/DDSqq1hR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{NR-JVJz-.js → DTAO9S6h.js} +1 -1
- package/build/client/_app/immutable/chunks/DTAO9S6h.js.br +0 -0
- package/build/client/_app/immutable/chunks/DTAO9S6h.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DTLQuSQp.js +3 -0
- package/build/client/_app/immutable/chunks/DTLQuSQp.js.br +0 -0
- package/build/client/_app/immutable/chunks/DTLQuSQp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{hSjQday7.js → _zkFsY4j.js} +1 -1
- package/build/client/_app/immutable/chunks/_zkFsY4j.js.br +0 -0
- package/build/client/_app/immutable/chunks/_zkFsY4j.js.gz +0 -0
- package/build/client/_app/immutable/chunks/d1cxvUgj.js +1 -0
- package/build/client/_app/immutable/chunks/d1cxvUgj.js.br +0 -0
- package/build/client/_app/immutable/chunks/d1cxvUgj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/qrqTdjh7.js +1 -0
- package/build/client/_app/immutable/chunks/qrqTdjh7.js.br +0 -0
- package/build/client/_app/immutable/chunks/qrqTdjh7.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CXHi3StM.js +2 -0
- package/build/client/_app/immutable/entry/app.CXHi3StM.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CXHi3StM.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.eS_-xLIC.js +1 -0
- package/build/client/_app/immutable/entry/start.eS_-xLIC.js.br +1 -0
- package/build/client/_app/immutable/entry/start.eS_-xLIC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Bfv0aXoG.js +1 -0
- package/build/client/_app/immutable/nodes/0.Bfv0aXoG.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Bfv0aXoG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.B8Ebge5I.js +1 -0
- package/build/client/_app/immutable/nodes/1.B8Ebge5I.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.B8Ebge5I.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.Di4b61Rb.js +1 -0
- package/build/client/_app/immutable/nodes/10.Di4b61Rb.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.Di4b61Rb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.CUlazdDv.js +8 -0
- package/build/client/_app/immutable/nodes/11.CUlazdDv.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.CUlazdDv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.tmtk1Bjc.js +1 -0
- package/build/client/_app/immutable/nodes/2.tmtk1Bjc.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.tmtk1Bjc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.BEaeucAu.js +1 -0
- package/build/client/_app/immutable/nodes/3.BEaeucAu.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BEaeucAu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.DQOL0qY_.js +1 -0
- package/build/client/_app/immutable/nodes/4.DQOL0qY_.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.DQOL0qY_.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{5.CIMkqYzP.js → 5.BGqmDWpO.js} +2 -2
- package/build/client/_app/immutable/nodes/5.BGqmDWpO.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.BGqmDWpO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.Cf2kRGl-.js +2 -0
- package/build/client/_app/immutable/nodes/6.Cf2kRGl-.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.Cf2kRGl-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.CvaILUo5.js +16 -0
- package/build/client/_app/immutable/nodes/7.CvaILUo5.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.CvaILUo5.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.BANf4x1v.js +1 -0
- package/build/client/_app/immutable/nodes/8.BANf4x1v.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.BANf4x1v.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.DvOR66Q0.js +128 -0
- package/build/client/_app/immutable/nodes/9.DvOR66Q0.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.DvOR66Q0.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +1 -1
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-BtezztJz.js → 0-D8UAPThv.js} +13 -7
- package/build/server/chunks/0-D8UAPThv.js.map +1 -0
- package/build/server/chunks/1-bS23Eg-d.js +9 -0
- package/build/server/chunks/{1-CvGA_TOi.js.map → 1-bS23Eg-d.js.map} +1 -1
- package/build/server/chunks/{10-D_ipl62-.js → 10-C5TUqG1t.js} +3 -3
- package/build/server/chunks/{10-D_ipl62-.js.map → 10-C5TUqG1t.js.map} +1 -1
- package/build/server/chunks/11-DSjZgp33.js +9 -0
- package/build/server/chunks/11-DSjZgp33.js.map +1 -0
- package/build/server/chunks/{2-CQSBaE_w.js → 2-KPMSLSlE.js} +2 -2
- package/build/server/chunks/{2-CQSBaE_w.js.map → 2-KPMSLSlE.js.map} +1 -1
- package/build/server/chunks/{3-CZdWulB3.js → 3-D0iTJ9v2.js} +10 -11
- package/build/server/chunks/3-D0iTJ9v2.js.map +1 -0
- package/build/server/chunks/4-CHJRF8qs.js +9 -0
- package/build/server/chunks/4-CHJRF8qs.js.map +1 -0
- package/build/server/chunks/{5-CeJvfCdk.js → 5-CwCiwnFt.js} +3 -3
- package/build/server/chunks/{5-CeJvfCdk.js.map → 5-CwCiwnFt.js.map} +1 -1
- package/build/server/chunks/6-raswytzE.js +9 -0
- package/build/server/chunks/6-raswytzE.js.map +1 -0
- package/build/server/chunks/7-NC4-KlW9.js +9 -0
- package/build/server/chunks/7-NC4-KlW9.js.map +1 -0
- package/build/server/chunks/{8-DiHmrGkL.js → 8-DFD8sNRI.js} +5 -7
- package/build/server/chunks/8-DFD8sNRI.js.map +1 -0
- package/build/server/chunks/9-rIsHxfiY.js +9 -0
- package/build/server/chunks/9-rIsHxfiY.js.map +1 -0
- package/build/server/chunks/{IconActivity-DrgUhxxi.js → ChatMessage-fWnwEF6b.js} +3 -57
- package/build/server/chunks/ChatMessage-fWnwEF6b.js.map +1 -0
- package/build/server/chunks/{ChatNavbar-FJW5r496.js → ChatNavbar-DqdxDR5U.js} +9 -9
- package/build/server/chunks/{ChatNavbar-FJW5r496.js.map → ChatNavbar-DqdxDR5U.js.map} +1 -1
- package/build/server/chunks/IconActivity-BU1QGLAZ.js +58 -0
- package/build/server/chunks/IconActivity-BU1QGLAZ.js.map +1 -0
- package/build/server/chunks/IconMic-CZN60P6B.js +10 -0
- package/build/server/chunks/IconMic-CZN60P6B.js.map +1 -0
- package/build/server/chunks/{IconServer-w-1K5jNR.js → IconServer-BwcRjcsS.js} +5 -5
- package/build/server/chunks/{IconServer-w-1K5jNR.js.map → IconServer-BwcRjcsS.js.map} +1 -1
- package/build/server/chunks/{IconSoundOn-DZGaQz_p.js → IconSoundOn-DTZuBn2-.js} +9 -12
- package/build/server/chunks/IconSoundOn-DTZuBn2-.js.map +1 -0
- package/build/server/chunks/{IconStop-BPbovgch.js → IconStop-BxUfqTaV.js} +173 -214
- package/build/server/chunks/IconStop-BxUfqTaV.js.map +1 -0
- package/build/server/chunks/QuestionCard-DOLuSSbI.js +128 -0
- package/build/server/chunks/QuestionCard-DOLuSSbI.js.map +1 -0
- package/build/server/chunks/Spinner-2WZW6r_I.js +12 -0
- package/build/server/chunks/Spinner-2WZW6r_I.js.map +1 -0
- package/build/server/chunks/{_layout.svelte-B-_IlE2E.js → _layout.svelte-CBBWJ3m3.js} +3 -4
- package/build/server/chunks/{_layout.svelte-B-_IlE2E.js.map → _layout.svelte-CBBWJ3m3.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-K6kU7jIW.js → _page.svelte-CYzntFY4.js} +45 -136
- package/build/server/chunks/_page.svelte-CYzntFY4.js.map +1 -0
- package/build/server/chunks/_page.svelte-CysG6Te2.js +192 -0
- package/build/server/chunks/_page.svelte-CysG6Te2.js.map +1 -0
- package/build/server/chunks/{_page.svelte-CPZBh4-z.js → _page.svelte-D-_cPU8e.js} +3 -3
- package/build/server/chunks/{_page.svelte-CPZBh4-z.js.map → _page.svelte-D-_cPU8e.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-CPo3vFZf.js → _page.svelte-D4hOWjfm.js} +64 -353
- package/build/server/chunks/_page.svelte-D4hOWjfm.js.map +1 -0
- package/build/server/chunks/_page.svelte-D9au75wl.js +183 -0
- package/build/server/chunks/_page.svelte-D9au75wl.js.map +1 -0
- package/build/server/chunks/{_page.svelte-GcuuPiXq.js → _page.svelte-DjldYxQn.js} +28 -136
- package/build/server/chunks/_page.svelte-DjldYxQn.js.map +1 -0
- package/build/server/chunks/{_page.svelte-DM96lwQ1.js → _page.svelte-DsJgu4mW.js} +3 -3
- package/build/server/chunks/{_page.svelte-DM96lwQ1.js.map → _page.svelte-DsJgu4mW.js.map} +1 -1
- package/build/server/chunks/_server.ts-1qbl_1NY.js +87 -0
- package/build/server/chunks/_server.ts-1qbl_1NY.js.map +1 -0
- package/build/server/chunks/{_server.ts-BLXzfOzW.js → _server.ts-2VPdIZFH.js} +7 -7
- package/build/server/chunks/{_server.ts-BLXzfOzW.js.map → _server.ts-2VPdIZFH.js.map} +1 -1
- package/build/server/chunks/{_server.ts-513m4Yqs.js → _server.ts-68MuZYx_.js} +8 -8
- package/build/server/chunks/{_server.ts-513m4Yqs.js.map → _server.ts-68MuZYx_.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BaVwMXGu.js → _server.ts-8AfutA1j.js} +7 -7
- package/build/server/chunks/{_server.ts-BaVwMXGu.js.map → _server.ts-8AfutA1j.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Cp8YnPjm.js → _server.ts-B-5y1hA4.js} +9 -9
- package/build/server/chunks/{_server.ts-Cp8YnPjm.js.map → _server.ts-B-5y1hA4.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CVvUFmDd.js → _server.ts-B1CunPF7.js} +7 -7
- package/build/server/chunks/{_server.ts-CVvUFmDd.js.map → _server.ts-B1CunPF7.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Cgdl0C1I.js → _server.ts-B1KGSkdl.js} +7 -7
- package/build/server/chunks/{_server.ts-Cgdl0C1I.js.map → _server.ts-B1KGSkdl.js.map} +1 -1
- package/build/server/chunks/{_server.ts-WZl_x3WY.js → _server.ts-B2NALEcR.js} +7 -7
- package/build/server/chunks/{_server.ts-WZl_x3WY.js.map → _server.ts-B2NALEcR.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DnonNBWq.js → _server.ts-B2iUBxXh.js} +9 -9
- package/build/server/chunks/{_server.ts-DnonNBWq.js.map → _server.ts-B2iUBxXh.js.map} +1 -1
- package/build/server/chunks/{_server.ts-rVmsBymo.js → _server.ts-BN8EkPAZ.js} +8 -8
- package/build/server/chunks/{_server.ts-rVmsBymo.js.map → _server.ts-BN8EkPAZ.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Cf_ZLxIl.js → _server.ts-BQJfEfl9.js} +12 -13
- package/build/server/chunks/_server.ts-BQJfEfl9.js.map +1 -0
- package/build/server/chunks/{_server.ts-CPiybiLB.js → _server.ts-BQSMacks.js} +10 -10
- package/build/server/chunks/{_server.ts-CPiybiLB.js.map → _server.ts-BQSMacks.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DwGTXfsS.js → _server.ts-BYt64wi5.js} +3 -3
- package/build/server/chunks/{_server.ts-DwGTXfsS.js.map → _server.ts-BYt64wi5.js.map} +1 -1
- package/build/server/chunks/{_server.ts-ykET4T6s.js → _server.ts-Ba0MJSs9.js} +9 -9
- package/build/server/chunks/_server.ts-Ba0MJSs9.js.map +1 -0
- package/build/server/chunks/{_server.ts-CMOAhXEC.js → _server.ts-BdIM_pWg.js} +9 -9
- package/build/server/chunks/{_server.ts-CMOAhXEC.js.map → _server.ts-BdIM_pWg.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DbvC6vbj.js → _server.ts-BfxTJHa-.js} +7 -7
- package/build/server/chunks/{_server.ts-DbvC6vbj.js.map → _server.ts-BfxTJHa-.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Dw7F066C.js → _server.ts-BiVyFRNv.js} +7 -7
- package/build/server/chunks/{_server.ts-Dw7F066C.js.map → _server.ts-BiVyFRNv.js.map} +1 -1
- package/build/server/chunks/{_server.ts-WLMG4R7D.js → _server.ts-BrgEQhDo.js} +7 -7
- package/build/server/chunks/{_server.ts-WLMG4R7D.js.map → _server.ts-BrgEQhDo.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Bwt_pOeg.js → _server.ts-BtZN_8lV.js} +7 -7
- package/build/server/chunks/{_server.ts-Bwt_pOeg.js.map → _server.ts-BtZN_8lV.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CFrPK6QO.js → _server.ts-Bx0PXf1w.js} +7 -7
- package/build/server/chunks/{_server.ts-CFrPK6QO.js.map → _server.ts-Bx0PXf1w.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DpnNS7XE.js → _server.ts-Byaj9iKd.js} +7 -7
- package/build/server/chunks/{_server.ts-DpnNS7XE.js.map → _server.ts-Byaj9iKd.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DbWhKo2R.js → _server.ts-C1kDLq6m.js} +7 -7
- package/build/server/chunks/{_server.ts-DbWhKo2R.js.map → _server.ts-C1kDLq6m.js.map} +1 -1
- package/build/server/chunks/{_server.ts-JhxhiGGc.js → _server.ts-C28zIW49.js} +7 -7
- package/build/server/chunks/{_server.ts-JhxhiGGc.js.map → _server.ts-C28zIW49.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Dim_lnRX.js → _server.ts-C2F80x6V.js} +8 -8
- package/build/server/chunks/{_server.ts-Dim_lnRX.js.map → _server.ts-C2F80x6V.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Blmg0cXL.js → _server.ts-C70sKEjH.js} +8 -8
- package/build/server/chunks/{_server.ts-Blmg0cXL.js.map → _server.ts-C70sKEjH.js.map} +1 -1
- package/build/server/chunks/{_server.ts-iFah_o1y.js → _server.ts-C754EzJA.js} +7 -7
- package/build/server/chunks/{_server.ts-iFah_o1y.js.map → _server.ts-C754EzJA.js.map} +1 -1
- package/build/server/chunks/{_server.ts-D3G8ICxb.js → _server.ts-C8n83Xl1.js} +7 -7
- package/build/server/chunks/{_server.ts-D3G8ICxb.js.map → _server.ts-C8n83Xl1.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BvtdTKYv.js → _server.ts-CC6Bt-9t.js} +8 -8
- package/build/server/chunks/{_server.ts-BvtdTKYv.js.map → _server.ts-CC6Bt-9t.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DgoPOMEf.js → _server.ts-CJ76gcOX.js} +7 -7
- package/build/server/chunks/{_server.ts-DgoPOMEf.js.map → _server.ts-CJ76gcOX.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CZMdC1Kd.js → _server.ts-CMW6wbwP.js} +7 -7
- package/build/server/chunks/{_server.ts-CZMdC1Kd.js.map → _server.ts-CMW6wbwP.js.map} +1 -1
- package/build/server/chunks/{_server.ts-m7rCPG1J.js → _server.ts-CNjG4x1a.js} +8 -8
- package/build/server/chunks/{_server.ts-m7rCPG1J.js.map → _server.ts-CNjG4x1a.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CjfZfEOL.js → _server.ts-CRl7GqvU.js} +7 -7
- package/build/server/chunks/{_server.ts-CjfZfEOL.js.map → _server.ts-CRl7GqvU.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DMUseo0C.js → _server.ts-CUBWjRlp.js} +8 -8
- package/build/server/chunks/{_server.ts-DMUseo0C.js.map → _server.ts-CUBWjRlp.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DjIJCqkv.js → _server.ts-CYoKjlbj.js} +7 -7
- package/build/server/chunks/{_server.ts-DjIJCqkv.js.map → _server.ts-CYoKjlbj.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DgtgUx0V.js → _server.ts-CafJDVBQ.js} +3 -3
- package/build/server/chunks/{_server.ts-DgtgUx0V.js.map → _server.ts-CafJDVBQ.js.map} +1 -1
- package/build/server/chunks/{_server.ts-C4Pel5kC.js → _server.ts-Cd9hDiKk.js} +7 -7
- package/build/server/chunks/{_server.ts-C4Pel5kC.js.map → _server.ts-Cd9hDiKk.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BfL_49xD.js → _server.ts-CgQfVR3g.js} +8 -8
- package/build/server/chunks/{_server.ts-BfL_49xD.js.map → _server.ts-CgQfVR3g.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CzEoO4z1.js → _server.ts-CilbVIRO.js} +3 -3
- package/build/server/chunks/{_server.ts-CzEoO4z1.js.map → _server.ts-CilbVIRO.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BXPj75Y1.js → _server.ts-Ck25Xm32.js} +8 -8
- package/build/server/chunks/{_server.ts-BXPj75Y1.js.map → _server.ts-Ck25Xm32.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CGTCLl7g.js → _server.ts-Cp-_qWC_.js} +8 -8
- package/build/server/chunks/{_server.ts-CGTCLl7g.js.map → _server.ts-Cp-_qWC_.js.map} +1 -1
- package/build/server/chunks/{_server.ts-ygmDTh5G.js → _server.ts-CuEFmJ07.js} +8 -8
- package/build/server/chunks/{_server.ts-ygmDTh5G.js.map → _server.ts-CuEFmJ07.js.map} +1 -1
- package/build/server/chunks/{_server.ts-B_dmjFtu.js → _server.ts-D5vc4lGC.js} +8 -8
- package/build/server/chunks/_server.ts-D5vc4lGC.js.map +1 -0
- package/build/server/chunks/{_server.ts-CqK78x99.js → _server.ts-DDLE66QT.js} +8 -8
- package/build/server/chunks/{_server.ts-CqK78x99.js.map → _server.ts-DDLE66QT.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CfLVH8TI.js → _server.ts-DEF1ChPD.js} +7 -7
- package/build/server/chunks/{_server.ts-CfLVH8TI.js.map → _server.ts-DEF1ChPD.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CU4SIH7_.js → _server.ts-DHUtPRWK.js} +5 -5
- package/build/server/chunks/{_server.ts-CU4SIH7_.js.map → _server.ts-DHUtPRWK.js.map} +1 -1
- package/build/server/chunks/{_server.ts-w4ncTcy1.js → _server.ts-DHoTyKXL.js} +15 -11
- package/build/server/chunks/_server.ts-DHoTyKXL.js.map +1 -0
- package/build/server/chunks/{_server.ts-CtGn47WI.js → _server.ts-DSB9Fzwa.js} +5 -5
- package/build/server/chunks/{_server.ts-CtGn47WI.js.map → _server.ts-DSB9Fzwa.js.map} +1 -1
- package/build/server/chunks/{_server.ts-D3FpBEaY.js → _server.ts-DW36Vbiq.js} +9 -9
- package/build/server/chunks/_server.ts-DW36Vbiq.js.map +1 -0
- package/build/server/chunks/{_server.ts-B5wBYGeP.js → _server.ts-DWG4CKWG.js} +7 -7
- package/build/server/chunks/{_server.ts-B5wBYGeP.js.map → _server.ts-DWG4CKWG.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BcqSTHN5.js → _server.ts-DXxQIT2o.js} +8 -8
- package/build/server/chunks/{_server.ts-BcqSTHN5.js.map → _server.ts-DXxQIT2o.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BcO0Zf4D.js → _server.ts-Da8_J1a4.js} +8 -8
- package/build/server/chunks/{_server.ts-BcO0Zf4D.js.map → _server.ts-Da8_J1a4.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BrQJNRtC.js → _server.ts-DcH5edss.js} +7 -7
- package/build/server/chunks/{_server.ts-BrQJNRtC.js.map → _server.ts-DcH5edss.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CsI99fk3.js → _server.ts-DiHLYNbQ.js} +8 -8
- package/build/server/chunks/{_server.ts-CsI99fk3.js.map → _server.ts-DiHLYNbQ.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Dy7MOoFx.js → _server.ts-Dio2CslE.js} +8 -8
- package/build/server/chunks/_server.ts-Dio2CslE.js.map +1 -0
- package/build/server/chunks/{_server.ts-CZ3VVtAj.js → _server.ts-Dlt1obi9.js} +7 -7
- package/build/server/chunks/{_server.ts-CZ3VVtAj.js.map → _server.ts-Dlt1obi9.js.map} +1 -1
- package/build/server/chunks/{_server.ts-YcvtqJwd.js → _server.ts-Ds0U_zKR.js} +8 -8
- package/build/server/chunks/{_server.ts-YcvtqJwd.js.map → _server.ts-Ds0U_zKR.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CuLEqJd2.js → _server.ts-DsVsFcf8.js} +16 -11
- package/build/server/chunks/_server.ts-DsVsFcf8.js.map +1 -0
- package/build/server/chunks/{_server.ts-V-MiUhz4.js → _server.ts-Du3PIYf8.js} +7 -7
- package/build/server/chunks/{_server.ts-V-MiUhz4.js.map → _server.ts-Du3PIYf8.js.map} +1 -1
- package/build/server/chunks/{_server.ts-s2Seq_VN.js → _server.ts-Duzj6Wm_.js} +7 -19
- package/build/server/chunks/_server.ts-Duzj6Wm_.js.map +1 -0
- package/build/server/chunks/{_server.ts-Df7bUUvM.js → _server.ts-DyaoaWIS.js} +8 -8
- package/build/server/chunks/{_server.ts-Df7bUUvM.js.map → _server.ts-DyaoaWIS.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Dixj3o5E.js → _server.ts-EYtSo6HZ.js} +7 -7
- package/build/server/chunks/{_server.ts-Dixj3o5E.js.map → _server.ts-EYtSo6HZ.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CoVlX74M.js → _server.ts-JvnkkOhX.js} +8 -8
- package/build/server/chunks/{_server.ts-CoVlX74M.js.map → _server.ts-JvnkkOhX.js.map} +1 -1
- package/build/server/chunks/{_server.ts-MBgNxLF2.js → _server.ts-N0u5ruZe.js} +7 -7
- package/build/server/chunks/{_server.ts-MBgNxLF2.js.map → _server.ts-N0u5ruZe.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Bnzq20kw.js → _server.ts-NdCztCBP.js} +7 -7
- package/build/server/chunks/{_server.ts-Bnzq20kw.js.map → _server.ts-NdCztCBP.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BNOepqUw.js → _server.ts-OGUKP5Tu.js} +7 -7
- package/build/server/chunks/{_server.ts-BNOepqUw.js.map → _server.ts-OGUKP5Tu.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DZrKSBmK.js → _server.ts-Q1KEyBck.js} +7 -7
- package/build/server/chunks/{_server.ts-DZrKSBmK.js.map → _server.ts-Q1KEyBck.js.map} +1 -1
- package/build/server/chunks/{_server.ts-B2BUFCOJ.js → _server.ts-TIRuudir.js} +3 -3
- package/build/server/chunks/{_server.ts-B2BUFCOJ.js.map → _server.ts-TIRuudir.js.map} +1 -1
- package/build/server/chunks/{_server.ts-HHMqD_RA.js → _server.ts-WTZtC5sX.js} +12 -7
- package/build/server/chunks/_server.ts-WTZtC5sX.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bvb9c_At.js → _server.ts-WsWoJE5V.js} +9 -9
- package/build/server/chunks/{_server.ts-Bvb9c_At.js.map → _server.ts-WsWoJE5V.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Do0cbyyq.js → _server.ts-X8GX4ChJ.js} +8 -8
- package/build/server/chunks/_server.ts-X8GX4ChJ.js.map +1 -0
- package/build/server/chunks/{_server.ts-BCrEvrVb.js → _server.ts-XIc2PDRd.js} +8 -8
- package/build/server/chunks/{_server.ts-BCrEvrVb.js.map → _server.ts-XIc2PDRd.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CPNIDSmd.js → _server.ts-YsFgaaVd.js} +7 -7
- package/build/server/chunks/{_server.ts-CPNIDSmd.js.map → _server.ts-YsFgaaVd.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BFRCkpIB.js → _server.ts-bOcb37D9.js} +8 -8
- package/build/server/chunks/{_server.ts-BFRCkpIB.js.map → _server.ts-bOcb37D9.js.map} +1 -1
- package/build/server/chunks/{_server.ts-D3CUgai-.js → _server.ts-e_8LykNe.js} +8 -8
- package/build/server/chunks/{_server.ts-D3CUgai-.js.map → _server.ts-e_8LykNe.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DVNN4NHW.js → _server.ts-hLimfy8i.js} +7 -7
- package/build/server/chunks/{_server.ts-DVNN4NHW.js.map → _server.ts-hLimfy8i.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BJGOO9Nn.js → _server.ts-khI1sqja.js} +3 -3
- package/build/server/chunks/{_server.ts-BJGOO9Nn.js.map → _server.ts-khI1sqja.js.map} +1 -1
- package/build/server/chunks/{_server.ts-6QSsgi7N.js → _server.ts-p1Z3DBlr.js} +7 -7
- package/build/server/chunks/{_server.ts-6QSsgi7N.js.map → _server.ts-p1Z3DBlr.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BQdhHsGb.js → _server.ts-uzB454LO.js} +8 -8
- package/build/server/chunks/{_server.ts-BQdhHsGb.js.map → _server.ts-uzB454LO.js.map} +1 -1
- package/build/server/chunks/{_server.ts-D3EHXo-L.js → _server.ts-wKSTgSwf.js} +7 -7
- package/build/server/chunks/{_server.ts-D3EHXo-L.js.map → _server.ts-wKSTgSwf.js.map} +1 -1
- package/build/server/chunks/addon-helpers-DaC3GR9u.js +153 -0
- package/build/server/chunks/addon-helpers-DaC3GR9u.js.map +1 -0
- package/build/server/chunks/{admin-update-lock-ZHLAwYiV.js → admin-update-lock-Bu3VvAoN.js} +3 -3
- package/build/server/chunks/{admin-update-lock-ZHLAwYiV.js.map → admin-update-lock-Bu3VvAoN.js.map} +1 -1
- package/build/server/chunks/{akm-DU_4Aa4m.js → akm-DsJ8_uuW.js} +3 -3
- package/build/server/chunks/{akm-DU_4Aa4m.js.map → akm-DsJ8_uuW.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CT0jVfA6.js → bring-up-BBKMJltU.js} +30 -228
- package/build/server/chunks/bring-up-BBKMJltU.js.map +1 -0
- package/build/server/chunks/{catalog-B4QoA_I8.js → catalog-8d-U0FbY.js} +5 -5
- package/build/server/chunks/{catalog-B4QoA_I8.js.map → catalog-8d-U0FbY.js.map} +1 -1
- package/build/server/chunks/{client-Dn-H0hgG.js → client-CHoZA_w9.js} +2 -2
- package/build/server/chunks/{client-Dn-H0hgG.js.map → client-CHoZA_w9.js.map} +1 -1
- package/build/server/chunks/{compose-args2-BFOGDQne.js → compose-args2-C9sLS6QR.js} +3 -3
- package/build/server/chunks/compose-args2-C9sLS6QR.js.map +1 -0
- package/build/server/chunks/{config-DoYNsM1_.js → config-ChxZZk6h.js} +2 -2
- package/build/server/chunks/{config-DoYNsM1_.js.map → config-ChxZZk6h.js.map} +1 -1
- package/build/server/chunks/{constants-1vPYIIwP.js → constants-C8L-P-CF.js} +2 -2
- package/build/server/chunks/{constants-1vPYIIwP.js.map → constants-C8L-P-CF.js.map} +1 -1
- package/build/server/chunks/{docker2-BYFlumV1.js → docker2-DGq6J3Kp.js} +3 -3
- package/build/server/chunks/docker2-DGq6J3Kp.js.map +1 -0
- package/build/server/chunks/{docker3-CtXDh00O.js → docker3-DFlJ-o_d.js} +2 -2
- package/build/server/chunks/{docker3-CtXDh00O.js.map → docker3-DFlJ-o_d.js.map} +1 -1
- package/build/server/chunks/environment-BYp8JQYM.js.map +1 -1
- package/build/server/chunks/{error.svelte-DWK4R1_M.js → error.svelte-BXxIEYd4.js} +4 -4
- package/build/server/chunks/{error.svelte-DWK4R1_M.js.map → error.svelte-BXxIEYd4.js.map} +1 -1
- package/build/server/chunks/features-D8Ez2vwC.js +101 -0
- package/build/server/chunks/features-D8Ez2vwC.js.map +1 -0
- package/build/server/chunks/{helpers-DxodR6tq.js → helpers-BrD1e__f.js} +17 -22
- package/build/server/chunks/helpers-BrD1e__f.js.map +1 -0
- package/build/server/chunks/{hooks.server-Bzj8OieV.js → hooks.server-Blba-Qw7.js} +12 -12
- package/build/server/chunks/hooks.server-Blba-Qw7.js.map +1 -0
- package/build/server/chunks/{http-CfHMreaI.js → http-B4Us23Mo.js} +3 -3
- package/build/server/chunks/http-B4Us23Mo.js.map +1 -0
- package/build/server/chunks/{internal-BcbUGF7Z.js → internal-CSnzBDeT.js} +8 -4
- package/build/server/chunks/{internal-BcbUGF7Z.js.map → internal-CSnzBDeT.js.map} +1 -1
- package/build/server/chunks/{landing-DhaVkN2f.js → landing-DxFOhZxH.js} +10 -12
- package/build/server/chunks/landing-DxFOhZxH.js.map +1 -0
- package/build/server/chunks/{network-preset-CJ1RRnki.js → network-preset-BvVAoagy.js} +14 -13
- package/build/server/chunks/network-preset-BvVAoagy.js.map +1 -0
- package/build/server/chunks/opencode-target-D4BxXYHJ.js +203 -0
- package/build/server/chunks/opencode-target-D4BxXYHJ.js.map +1 -0
- package/build/server/chunks/{paths-Bgos728b.js → paths-6f-5gYkd.js} +2 -2
- package/build/server/chunks/{paths-Bgos728b.js.map → paths-6f-5gYkd.js.map} +1 -1
- package/build/server/chunks/{runtime-context.svelte-C5zNEC6u.js → runtime-context.svelte-CRT-b6Ae.js} +8 -11
- package/build/server/chunks/runtime-context.svelte-CRT-b6Ae.js.map +1 -0
- package/build/server/chunks/{session-cookie-C8DcD6bt.js → session-cookie-5J3xCH94.js} +2 -2
- package/build/server/chunks/{session-cookie-C8DcD6bt.js.map → session-cookie-5J3xCH94.js.map} +1 -1
- package/build/server/chunks/{setup-deploy-D9j0EGGd.js → setup-deploy-DUbX3fHm.js} +2 -2
- package/build/server/chunks/{setup-deploy-D9j0EGGd.js.map → setup-deploy-DUbX3fHm.js.map} +1 -1
- package/build/server/chunks/{src-DbArH65P.js → src-B2wHibme.js} +118 -143
- package/build/server/chunks/src-B2wHibme.js.map +1 -0
- package/build/server/chunks/{state-Bq8fep3F.js → state-JIy0tegD.js} +2 -2
- package/build/server/chunks/{state-Bq8fep3F.js.map → state-JIy0tegD.js.map} +1 -1
- package/build/server/chunks/{state2-Cna2OZrd.js → state2-Br2snAqo.js} +2 -2
- package/build/server/chunks/{state2-Cna2OZrd.js.map → state2-Br2snAqo.js.map} +1 -1
- package/build/server/chunks/voice-state.svelte-DU9g_xEh.js +1610 -0
- package/build/server/chunks/voice-state.svelte-DU9g_xEh.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/manifest.js +98 -140
- package/build/server/manifest.js.map +1 -1
- package/package.json +3 -3
- package/build/client/_app/immutable/assets/4.B2m7mOkq.css +0 -1
- package/build/client/_app/immutable/assets/4.B2m7mOkq.css.br +0 -0
- package/build/client/_app/immutable/assets/4.B2m7mOkq.css.gz +0 -0
- package/build/client/_app/immutable/assets/6.CmRPKjbq.css +0 -1
- package/build/client/_app/immutable/assets/6.CmRPKjbq.css.br +0 -0
- package/build/client/_app/immutable/assets/6.CmRPKjbq.css.gz +0 -0
- package/build/client/_app/immutable/assets/7.BTWb8aLW.css +0 -1
- package/build/client/_app/immutable/assets/7.BTWb8aLW.css.br +0 -0
- package/build/client/_app/immutable/assets/7.BTWb8aLW.css.gz +0 -0
- package/build/client/_app/immutable/assets/9.D8zTZB7v.css +0 -1
- package/build/client/_app/immutable/assets/9.D8zTZB7v.css.br +0 -0
- package/build/client/_app/immutable/assets/9.D8zTZB7v.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BDK50g8a.js.br +0 -0
- package/build/client/_app/immutable/chunks/BDK50g8a.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BX_cLEyR.js +0 -1
- package/build/client/_app/immutable/chunks/BX_cLEyR.js.br +0 -0
- package/build/client/_app/immutable/chunks/BX_cLEyR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ByFgZDEl.js +0 -1
- package/build/client/_app/immutable/chunks/ByFgZDEl.js.br +0 -0
- package/build/client/_app/immutable/chunks/ByFgZDEl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BzIvC31O.js +0 -3
- package/build/client/_app/immutable/chunks/BzIvC31O.js.br +0 -0
- package/build/client/_app/immutable/chunks/BzIvC31O.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C718MJXm.js +0 -5
- package/build/client/_app/immutable/chunks/C718MJXm.js.br +0 -0
- package/build/client/_app/immutable/chunks/C718MJXm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CUa5ff3d.js.br +0 -0
- package/build/client/_app/immutable/chunks/CUa5ff3d.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cnz20Kyu.js +0 -3
- package/build/client/_app/immutable/chunks/Cnz20Kyu.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cnz20Kyu.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CoOosFtk.js +0 -3
- package/build/client/_app/immutable/chunks/CoOosFtk.js.br +0 -0
- package/build/client/_app/immutable/chunks/CoOosFtk.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D5FtEngX.js.br +0 -0
- package/build/client/_app/immutable/chunks/D5FtEngX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DBHgGGCD.js +0 -1
- package/build/client/_app/immutable/chunks/DBHgGGCD.js.br +0 -0
- package/build/client/_app/immutable/chunks/DBHgGGCD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DBaSfL95.js +0 -1
- package/build/client/_app/immutable/chunks/DBaSfL95.js.br +0 -0
- package/build/client/_app/immutable/chunks/DBaSfL95.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DfZbBavE.js +0 -1
- package/build/client/_app/immutable/chunks/DfZbBavE.js.br +0 -1
- package/build/client/_app/immutable/chunks/DfZbBavE.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DmFbAjwW.js.br +0 -3
- package/build/client/_app/immutable/chunks/DmFbAjwW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/NR-JVJz-.js.br +0 -0
- package/build/client/_app/immutable/chunks/NR-JVJz-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/R6GPcDBF.js +0 -14
- package/build/client/_app/immutable/chunks/R6GPcDBF.js.br +0 -0
- package/build/client/_app/immutable/chunks/R6GPcDBF.js.gz +0 -0
- package/build/client/_app/immutable/chunks/SXdvPq3_.js.br +0 -0
- package/build/client/_app/immutable/chunks/SXdvPq3_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/c0lkYAgh.js +0 -5
- package/build/client/_app/immutable/chunks/c0lkYAgh.js.br +0 -0
- package/build/client/_app/immutable/chunks/c0lkYAgh.js.gz +0 -0
- package/build/client/_app/immutable/chunks/hSjQday7.js.br +0 -0
- package/build/client/_app/immutable/chunks/hSjQday7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/mG6HnNbi.js.br +0 -0
- package/build/client/_app/immutable/chunks/mG6HnNbi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ra5AJhOl.js +0 -1
- package/build/client/_app/immutable/chunks/ra5AJhOl.js.br +0 -2
- package/build/client/_app/immutable/chunks/ra5AJhOl.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.SHrJgM4c.js +0 -2
- package/build/client/_app/immutable/entry/app.SHrJgM4c.js.br +0 -0
- package/build/client/_app/immutable/entry/app.SHrJgM4c.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CM0OuHkF.js +0 -1
- package/build/client/_app/immutable/entry/start.CM0OuHkF.js.br +0 -0
- package/build/client/_app/immutable/entry/start.CM0OuHkF.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.mcDF2Cfv.js +0 -1
- package/build/client/_app/immutable/nodes/0.mcDF2Cfv.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.mcDF2Cfv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BRotfm1G.js +0 -1
- package/build/client/_app/immutable/nodes/1.BRotfm1G.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.BRotfm1G.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.CtHRQW64.js +0 -1
- package/build/client/_app/immutable/nodes/10.CtHRQW64.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.CtHRQW64.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.DViPDhJG.js +0 -8
- package/build/client/_app/immutable/nodes/11.DViPDhJG.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.DViPDhJG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.C-PzaHZx.js +0 -1
- package/build/client/_app/immutable/nodes/2.C-PzaHZx.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.C-PzaHZx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.26Z_fr6N.js +0 -1
- package/build/client/_app/immutable/nodes/3.26Z_fr6N.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.26Z_fr6N.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.B1PIKbh6.js +0 -6
- package/build/client/_app/immutable/nodes/4.B1PIKbh6.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.B1PIKbh6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.CIMkqYzP.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.CIMkqYzP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.U9QsdKdj.js +0 -2
- package/build/client/_app/immutable/nodes/6.U9QsdKdj.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.U9QsdKdj.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.Dvpg6Lol.js +0 -14
- package/build/client/_app/immutable/nodes/7.Dvpg6Lol.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.Dvpg6Lol.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.NHp_bSVx.js +0 -1
- package/build/client/_app/immutable/nodes/8.NHp_bSVx.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.NHp_bSVx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.zlIImD-K.js +0 -128
- package/build/client/_app/immutable/nodes/9.zlIImD-K.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.zlIImD-K.js.gz +0 -0
- package/build/server/chunks/0-BtezztJz.js.map +0 -1
- package/build/server/chunks/1-CvGA_TOi.js +0 -9
- package/build/server/chunks/11-Bp7zXIv8.js +0 -9
- package/build/server/chunks/11-Bp7zXIv8.js.map +0 -1
- package/build/server/chunks/3-CZdWulB3.js.map +0 -1
- package/build/server/chunks/4-4bobZFC9.js +0 -9
- package/build/server/chunks/4-4bobZFC9.js.map +0 -1
- package/build/server/chunks/6-sGxxEzTB.js +0 -9
- package/build/server/chunks/6-sGxxEzTB.js.map +0 -1
- package/build/server/chunks/7-mi0k2tJn.js +0 -9
- package/build/server/chunks/7-mi0k2tJn.js.map +0 -1
- package/build/server/chunks/8-DiHmrGkL.js.map +0 -1
- package/build/server/chunks/9-DB22e_yA.js +0 -9
- package/build/server/chunks/9-DB22e_yA.js.map +0 -1
- package/build/server/chunks/IconActivity-DrgUhxxi.js.map +0 -1
- package/build/server/chunks/IconMic-BbjkE_nG.js +0 -18
- package/build/server/chunks/IconMic-BbjkE_nG.js.map +0 -1
- package/build/server/chunks/IconSoundOn-DZGaQz_p.js.map +0 -1
- package/build/server/chunks/IconStop-BPbovgch.js.map +0 -1
- package/build/server/chunks/_page.svelte-CB1qUSpk.js +0 -95
- package/build/server/chunks/_page.svelte-CB1qUSpk.js.map +0 -1
- package/build/server/chunks/_page.svelte-CPo3vFZf.js.map +0 -1
- package/build/server/chunks/_page.svelte-F3gki1Uz.js +0 -155
- package/build/server/chunks/_page.svelte-F3gki1Uz.js.map +0 -1
- package/build/server/chunks/_page.svelte-GcuuPiXq.js.map +0 -1
- package/build/server/chunks/_page.svelte-K6kU7jIW.js.map +0 -1
- package/build/server/chunks/_server.ts-BQZ6mdSh.js +0 -93
- package/build/server/chunks/_server.ts-BQZ6mdSh.js.map +0 -1
- package/build/server/chunks/_server.ts-B_dmjFtu.js.map +0 -1
- package/build/server/chunks/_server.ts-BmRwgrKB.js +0 -67
- package/build/server/chunks/_server.ts-BmRwgrKB.js.map +0 -1
- package/build/server/chunks/_server.ts-CT0jVfA6.js.map +0 -1
- package/build/server/chunks/_server.ts-Cf_ZLxIl.js.map +0 -1
- package/build/server/chunks/_server.ts-CixURe5F.js +0 -49
- package/build/server/chunks/_server.ts-CixURe5F.js.map +0 -1
- package/build/server/chunks/_server.ts-CuLEqJd2.js.map +0 -1
- package/build/server/chunks/_server.ts-CvkF5YMt.js +0 -97
- package/build/server/chunks/_server.ts-CvkF5YMt.js.map +0 -1
- package/build/server/chunks/_server.ts-CyIO3STA.js +0 -73
- package/build/server/chunks/_server.ts-CyIO3STA.js.map +0 -1
- package/build/server/chunks/_server.ts-CzIZ-UYy.js +0 -113
- package/build/server/chunks/_server.ts-CzIZ-UYy.js.map +0 -1
- package/build/server/chunks/_server.ts-D3FpBEaY.js.map +0 -1
- package/build/server/chunks/_server.ts-Do0cbyyq.js.map +0 -1
- package/build/server/chunks/_server.ts-Dy7MOoFx.js.map +0 -1
- package/build/server/chunks/_server.ts-HHMqD_RA.js.map +0 -1
- package/build/server/chunks/_server.ts-s2Seq_VN.js.map +0 -1
- package/build/server/chunks/_server.ts-w4ncTcy1.js.map +0 -1
- package/build/server/chunks/_server.ts-ykET4T6s.js.map +0 -1
- package/build/server/chunks/addon-helpers-tZ_tAgFZ.js +0 -64
- package/build/server/chunks/addon-helpers-tZ_tAgFZ.js.map +0 -1
- package/build/server/chunks/compose-args2-BFOGDQne.js.map +0 -1
- package/build/server/chunks/connections-api-DlEoE3-k.js +0 -35
- package/build/server/chunks/connections-api-DlEoE3-k.js.map +0 -1
- package/build/server/chunks/docker2-BYFlumV1.js.map +0 -1
- package/build/server/chunks/endpoints-IFTldlrt.js +0 -473
- package/build/server/chunks/endpoints-IFTldlrt.js.map +0 -1
- package/build/server/chunks/features-BW-bVsl8.js +0 -118
- package/build/server/chunks/features-BW-bVsl8.js.map +0 -1
- package/build/server/chunks/helpers-DxodR6tq.js.map +0 -1
- package/build/server/chunks/hooks.server-Bzj8OieV.js.map +0 -1
- package/build/server/chunks/http-CfHMreaI.js.map +0 -1
- package/build/server/chunks/landing-DhaVkN2f.js.map +0 -1
- package/build/server/chunks/network-preset-CJ1RRnki.js.map +0 -1
- package/build/server/chunks/runtime-context.svelte-C5zNEC6u.js.map +0 -1
- package/build/server/chunks/speakable-text-BflxDDdY.js +0 -65
- package/build/server/chunks/speakable-text-BflxDDdY.js.map +0 -1
- package/build/server/chunks/src-DbArH65P.js.map +0 -1
- package/build/server/chunks/voice-state.svelte-2hAkP5Di.js +0 -539
- package/build/server/chunks/voice-state.svelte-2hAkP5Di.js.map +0 -1
- /package/build/client/_app/immutable/assets/{IconActivity.BdXOPwBm.css → ChatMessage.BdXOPwBm.css} +0 -0
- /package/build/client/_app/immutable/assets/{IconActivity.BdXOPwBm.css.br → ChatMessage.BdXOPwBm.css.br} +0 -0
- /package/build/client/_app/immutable/assets/{IconActivity.BdXOPwBm.css.gz → ChatMessage.BdXOPwBm.css.gz} +0 -0
- /package/build/client/_app/immutable/chunks/{DD22UF1L.js → DD22UF1L2.js} +0 -0
- /package/build/client/_app/immutable/chunks/{DD22UF1L.js.br → DD22UF1L2.js.br} +0 -0
- /package/build/client/_app/immutable/chunks/{DD22UF1L.js.gz → DD22UF1L2.js.gz} +0 -0
|
@@ -0,0 +1,1610 @@
|
|
|
1
|
+
//#region src/lib/notifications.svelte.ts
|
|
2
|
+
var DEFAULT_TIMEOUTS = {
|
|
3
|
+
info: 4e3,
|
|
4
|
+
success: 5e3,
|
|
5
|
+
error: 8e3
|
|
6
|
+
};
|
|
7
|
+
var Notifications = class {
|
|
8
|
+
toasts = [];
|
|
9
|
+
timers = /* @__PURE__ */ new Map();
|
|
10
|
+
push(kind, message, opts = {}) {
|
|
11
|
+
const id = opts.replaceId ?? (typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `t-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
12
|
+
const ttlMs = opts.sticky ? null : opts.ttlMs ?? DEFAULT_TIMEOUTS[kind];
|
|
13
|
+
const toast = {
|
|
14
|
+
id,
|
|
15
|
+
kind,
|
|
16
|
+
message,
|
|
17
|
+
ttlMs
|
|
18
|
+
};
|
|
19
|
+
const existingTimer = this.timers.get(id);
|
|
20
|
+
if (existingTimer) {
|
|
21
|
+
clearTimeout(existingTimer);
|
|
22
|
+
this.timers.delete(id);
|
|
23
|
+
}
|
|
24
|
+
const existingIdx = this.toasts.findIndex((t) => t.id === id);
|
|
25
|
+
if (existingIdx >= 0) {
|
|
26
|
+
const next = [...this.toasts];
|
|
27
|
+
next[existingIdx] = toast;
|
|
28
|
+
this.toasts = next;
|
|
29
|
+
} else this.toasts = [...this.toasts, toast];
|
|
30
|
+
if (ttlMs !== null) {
|
|
31
|
+
const timer = setTimeout(() => this.dismiss(id), ttlMs);
|
|
32
|
+
this.timers.set(id, timer);
|
|
33
|
+
}
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
36
|
+
dismiss(id) {
|
|
37
|
+
const timer = this.timers.get(id);
|
|
38
|
+
if (timer) {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
this.timers.delete(id);
|
|
41
|
+
}
|
|
42
|
+
this.toasts = this.toasts.filter((t) => t.id !== id);
|
|
43
|
+
}
|
|
44
|
+
clear() {
|
|
45
|
+
for (const timer of this.timers.values()) clearTimeout(timer);
|
|
46
|
+
this.timers.clear();
|
|
47
|
+
this.toasts = [];
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var notifications = new Notifications();
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/lib/theme-state.svelte.ts
|
|
53
|
+
var THEME_STORAGE_KEY = "openpalm.theme";
|
|
54
|
+
var THEME_ATTR = "data-theme";
|
|
55
|
+
var DARK_THEME_COLOR = "#161c22";
|
|
56
|
+
var LIGHT_THEME_COLOR = "#f9fafb";
|
|
57
|
+
function isThemePreference(value) {
|
|
58
|
+
return value === "light" || value === "dark" || value === "system";
|
|
59
|
+
}
|
|
60
|
+
var ThemeService = class {
|
|
61
|
+
preference = "system";
|
|
62
|
+
resolved = "light";
|
|
63
|
+
initialized = false;
|
|
64
|
+
#mediaQuery = null;
|
|
65
|
+
#mediaChangeHandler = () => {
|
|
66
|
+
if (this.preference !== "system") return;
|
|
67
|
+
this.resolved = this.#resolvePreference("system");
|
|
68
|
+
this.#applyResolvedTheme();
|
|
69
|
+
};
|
|
70
|
+
init() {
|
|
71
|
+
if (typeof window === "undefined" || this.initialized) return;
|
|
72
|
+
this.preference = this.#readStoredPreference();
|
|
73
|
+
this.resolved = this.#resolvePreference(this.preference);
|
|
74
|
+
this.#subscribeToSystemTheme();
|
|
75
|
+
this.#applyResolvedTheme();
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
}
|
|
78
|
+
setPreference(preference) {
|
|
79
|
+
this.preference = preference;
|
|
80
|
+
this.resolved = this.#resolvePreference(preference);
|
|
81
|
+
this.#writeStoredPreference(preference);
|
|
82
|
+
this.#applyResolvedTheme();
|
|
83
|
+
}
|
|
84
|
+
toggle() {
|
|
85
|
+
const next = this.preference === "system" ? "light" : this.preference === "light" ? "dark" : "system";
|
|
86
|
+
this.setPreference(next);
|
|
87
|
+
}
|
|
88
|
+
dispose() {
|
|
89
|
+
if (this.#mediaQuery) if (typeof this.#mediaQuery.removeEventListener === "function") this.#mediaQuery.removeEventListener("change", this.#mediaChangeHandler);
|
|
90
|
+
else this.#mediaQuery.removeListener(this.#mediaChangeHandler);
|
|
91
|
+
this.#mediaQuery = null;
|
|
92
|
+
this.initialized = false;
|
|
93
|
+
}
|
|
94
|
+
#readStoredPreference() {
|
|
95
|
+
if (typeof window === "undefined") return "system";
|
|
96
|
+
try {
|
|
97
|
+
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
98
|
+
return isThemePreference(stored) ? stored : "system";
|
|
99
|
+
} catch {
|
|
100
|
+
return "system";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
#writeStoredPreference(preference) {
|
|
104
|
+
if (typeof window === "undefined") return;
|
|
105
|
+
try {
|
|
106
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, preference);
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
#resolvePreference(preference) {
|
|
110
|
+
if (preference === "light" || preference === "dark") return preference;
|
|
111
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
|
|
112
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
113
|
+
}
|
|
114
|
+
#subscribeToSystemTheme() {
|
|
115
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
116
|
+
const nextQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
117
|
+
if (this.#mediaQuery === nextQuery) return;
|
|
118
|
+
if (this.#mediaQuery) if (typeof this.#mediaQuery.removeEventListener === "function") this.#mediaQuery.removeEventListener("change", this.#mediaChangeHandler);
|
|
119
|
+
else this.#mediaQuery.removeListener(this.#mediaChangeHandler);
|
|
120
|
+
this.#mediaQuery = nextQuery;
|
|
121
|
+
if (typeof nextQuery.addEventListener === "function") nextQuery.addEventListener("change", this.#mediaChangeHandler);
|
|
122
|
+
else nextQuery.addListener(this.#mediaChangeHandler);
|
|
123
|
+
}
|
|
124
|
+
#applyResolvedTheme() {
|
|
125
|
+
if (typeof document === "undefined") return;
|
|
126
|
+
const root = document.documentElement;
|
|
127
|
+
root.setAttribute(THEME_ATTR, this.resolved);
|
|
128
|
+
root.style.colorScheme = this.resolved;
|
|
129
|
+
const themeColorMeta = document.querySelector("meta[name=\"theme-color\"]");
|
|
130
|
+
if (themeColorMeta) themeColorMeta.content = this.resolved === "dark" ? DARK_THEME_COLOR : LIGHT_THEME_COLOR;
|
|
131
|
+
const colorSchemeMeta = document.querySelector("meta[name=\"color-scheme\"]");
|
|
132
|
+
if (colorSchemeMeta) colorSchemeMeta.content = this.resolved;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var themeService = new ThemeService();
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/lib/random-id.ts
|
|
138
|
+
/**
|
|
139
|
+
* Random id generation that works in EVERY browsing context.
|
|
140
|
+
*
|
|
141
|
+
* `crypto.randomUUID()` is secure-context-only: on a plain-http LAN origin
|
|
142
|
+
* (the LAN-served tier) it is undefined and calling it throws. Every
|
|
143
|
+
* browser-side id mint (connection ids, secret refs, request ids, local chat
|
|
144
|
+
* entry ids) must go through this guard instead of bare `crypto.randomUUID()`
|
|
145
|
+
* — the fallback builds a v4 UUID from `crypto.getRandomValues`, which IS
|
|
146
|
+
* available in insecure contexts.
|
|
147
|
+
*
|
|
148
|
+
* Zero-import leaf module so any $lib code can use it without cycles.
|
|
149
|
+
*/
|
|
150
|
+
function randomId() {
|
|
151
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
152
|
+
const b = crypto.getRandomValues(new Uint8Array(16));
|
|
153
|
+
b[6] = b[6] & 15 | 64;
|
|
154
|
+
b[8] = b[8] & 63 | 128;
|
|
155
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
156
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/lib/connections/url-policy.ts
|
|
160
|
+
/**
|
|
161
|
+
* Browser-owned connection URL policy (Phase 3a — "One UI, delete the split").
|
|
162
|
+
*
|
|
163
|
+
* A leaf module with zero imports so both the direct transport
|
|
164
|
+
* (`probeHealth()` short-circuit) and the connections form can consult it
|
|
165
|
+
* without pulling in the IndexedDB store module. There is no guardian
|
|
166
|
+
* `/oc`-appending helper — Guardian is a transparent OpenCode proxy now, so
|
|
167
|
+
* the user provides the exact baseUrl and no path inference is performed.
|
|
168
|
+
*
|
|
169
|
+
* The ONLY refusal trigger: a plain-http, non-loopback connection URL is
|
|
170
|
+
* refused iff the app itself runs on an https: origin. This is exactly the
|
|
171
|
+
* platform mixed-content rule (an https origin cannot `fetch()` a plain-http
|
|
172
|
+
* cross-origin target — the request would never leave the browser), not a
|
|
173
|
+
* broader "https or non-loopback origin" check. Preserves:
|
|
174
|
+
* - loopback TARGETS from any origin (mixed-content loopback exemption —
|
|
175
|
+
* `http://127.0.0.1`/`localhost`/`::1` are "potentially trustworthy"),
|
|
176
|
+
* - plain-http LAN targets from a loopback-origin client (the zero-TLS
|
|
177
|
+
* desktop default — a `http://127.0.0.1` origin is a secure context),
|
|
178
|
+
* - plain-http LAN targets from a non-loopback http ORIGIN (the LAN-served
|
|
179
|
+
* tier: `rewriteLoopbackUrlForBrowserHost` in `./store.ts`).
|
|
180
|
+
*/
|
|
181
|
+
/** Canonical deep link to the TLS integration guide. Pinned by a test that
|
|
182
|
+
* the path segment names a real file, so a docs rename breaks CI instead of
|
|
183
|
+
* shipping a dead link. */
|
|
184
|
+
var TLS_GUIDE_URL = "https://github.com/itlackey/openpalm/blob/main/docs/remote-access-tls.md";
|
|
185
|
+
function isLoopbackHost(hostname) {
|
|
186
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
187
|
+
}
|
|
188
|
+
/** Remove legacy URL-embedded credentials before a URL reaches UI or storage. */
|
|
189
|
+
function redactUrlUserinfo(rawUrl) {
|
|
190
|
+
try {
|
|
191
|
+
const url = new URL(rawUrl);
|
|
192
|
+
url.username = "";
|
|
193
|
+
url.password = "";
|
|
194
|
+
return url.toString().replace(/\/$/, url.pathname === "/" && !rawUrl.endsWith("/") ? "" : "/");
|
|
195
|
+
} catch {
|
|
196
|
+
return rawUrl;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Refuse a plain-http non-loopback connection URL iff the app's own origin is
|
|
201
|
+
* https: (mixed content — the request could never succeed, so refuse up front
|
|
202
|
+
* with guidance instead of a generic fetch failure).
|
|
203
|
+
*
|
|
204
|
+
* `origin` defaults to `globalThis.location`; a null/undefined origin
|
|
205
|
+
* (non-browser context — tests, SSR) always allows, since there is no browser
|
|
206
|
+
* mixed-content policy to enforce there.
|
|
207
|
+
*/
|
|
208
|
+
function validateConnectionUrl(rawUrl, origin = globalThis.location ?? null) {
|
|
209
|
+
let url;
|
|
210
|
+
try {
|
|
211
|
+
url = new URL(rawUrl);
|
|
212
|
+
} catch {
|
|
213
|
+
return {
|
|
214
|
+
ok: false,
|
|
215
|
+
reason: "invalid-url",
|
|
216
|
+
message: "Enter a valid URL."
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return {
|
|
220
|
+
ok: false,
|
|
221
|
+
reason: "invalid-url",
|
|
222
|
+
message: "The URL must use http:// or https://."
|
|
223
|
+
};
|
|
224
|
+
if (url.username || url.password) return {
|
|
225
|
+
ok: false,
|
|
226
|
+
reason: "userinfo-not-allowed",
|
|
227
|
+
message: "Do not put credentials in the URL. Use the Authentication fields instead."
|
|
228
|
+
};
|
|
229
|
+
if (url.protocol === "https:") return { ok: true };
|
|
230
|
+
if (isLoopbackHost(url.hostname)) return { ok: true };
|
|
231
|
+
if (origin?.protocol !== "https:") return { ok: true };
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
reason: "insecure-remote",
|
|
235
|
+
message: "This app runs on a secure (https) origin, so browsers block plain-HTTP connections to remote servers. Use an https:// URL — see the TLS setup guide.",
|
|
236
|
+
guideUrl: TLS_GUIDE_URL
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/lib/connections/store.ts
|
|
241
|
+
/**
|
|
242
|
+
* Browser-owned connection store (Phase 3a — "One UI, delete the split").
|
|
243
|
+
*
|
|
244
|
+
* Connection records persist behind a storage abstraction so the same store
|
|
245
|
+
* logic runs over IndexedDB in the browser (offline-readable) and over an
|
|
246
|
+
* in-memory backend in tests. The store API is async throughout (IndexedDB is
|
|
247
|
+
* async) and keeps NO state in store-instance fields — a page reload
|
|
248
|
+
* constructs a new store over the same backend and must see identical state.
|
|
249
|
+
*
|
|
250
|
+
* Locked/default entries are seeded from a runtime-config.json fetched from
|
|
251
|
+
* the app's own origin at boot. Absent file = no default connection.
|
|
252
|
+
*
|
|
253
|
+
* The Connection shape is `{ id, label, baseUrl, auth }` with auth narrowed to
|
|
254
|
+
* none | basic (Guardian is a transparent OpenCode proxy — Basic is the only
|
|
255
|
+
* connection credential model).
|
|
256
|
+
*/
|
|
257
|
+
function rewriteLoopbackUrlForBrowserHost(rawUrl) {
|
|
258
|
+
const locationLike = globalThis.location;
|
|
259
|
+
const redactedUrl = redactUrlUserinfo(rawUrl);
|
|
260
|
+
if (!locationLike || isLoopbackHost(locationLike.hostname)) return redactedUrl;
|
|
261
|
+
try {
|
|
262
|
+
const url = new URL(redactedUrl);
|
|
263
|
+
if (!isLoopbackHost(url.hostname)) return redactedUrl;
|
|
264
|
+
url.hostname = locationLike.hostname;
|
|
265
|
+
return url.toString();
|
|
266
|
+
} catch {
|
|
267
|
+
return redactedUrl;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function redactEntryUrl(entry) {
|
|
271
|
+
const baseUrl = redactUrlUserinfo(entry.baseUrl);
|
|
272
|
+
return baseUrl === entry.baseUrl ? entry : {
|
|
273
|
+
...entry,
|
|
274
|
+
baseUrl
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function assertUrlHasNoUserinfo(rawUrl) {
|
|
278
|
+
let url;
|
|
279
|
+
try {
|
|
280
|
+
url = new URL(rawUrl);
|
|
281
|
+
} catch {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (url.username || url.password) throw new Error("Credentials must use the Authentication fields, not the connection URL.");
|
|
285
|
+
}
|
|
286
|
+
function adaptRuntimeConfigForBrowser(config) {
|
|
287
|
+
return { connections: config.connections.map((entry) => ({
|
|
288
|
+
...redactEntryUrl(entry),
|
|
289
|
+
baseUrl: entry.locked ? rewriteLoopbackUrlForBrowserHost(entry.baseUrl) : redactUrlUserinfo(entry.baseUrl)
|
|
290
|
+
})) };
|
|
291
|
+
}
|
|
292
|
+
var ACTIVE_ID_KEY = "activeId";
|
|
293
|
+
function clone(value) {
|
|
294
|
+
return structuredClone(value);
|
|
295
|
+
}
|
|
296
|
+
/** In-memory storage backend — same semantics as the IndexedDB one. */
|
|
297
|
+
function createMemoryStorage() {
|
|
298
|
+
const entries = /* @__PURE__ */ new Map();
|
|
299
|
+
const meta = /* @__PURE__ */ new Map();
|
|
300
|
+
let cryptoKey = null;
|
|
301
|
+
return {
|
|
302
|
+
async getAll() {
|
|
303
|
+
return [...entries.values()].map(clone);
|
|
304
|
+
},
|
|
305
|
+
async get(id) {
|
|
306
|
+
const entry = entries.get(id);
|
|
307
|
+
return entry ? clone(entry) : null;
|
|
308
|
+
},
|
|
309
|
+
async put(entry) {
|
|
310
|
+
entries.set(entry.id, clone(redactEntryUrl(entry)));
|
|
311
|
+
},
|
|
312
|
+
async delete(id) {
|
|
313
|
+
entries.delete(id);
|
|
314
|
+
},
|
|
315
|
+
async getMeta(key) {
|
|
316
|
+
return meta.get(key) ?? null;
|
|
317
|
+
},
|
|
318
|
+
async setMeta(key, value) {
|
|
319
|
+
if (value === null) meta.delete(key);
|
|
320
|
+
else meta.set(key, value);
|
|
321
|
+
},
|
|
322
|
+
async getCryptoKey() {
|
|
323
|
+
return cryptoKey;
|
|
324
|
+
},
|
|
325
|
+
async setCryptoKey(key) {
|
|
326
|
+
cryptoKey = key;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
var IDB_NAME = "openpalm-ui-connections";
|
|
331
|
+
var IDB_VERSION = 1;
|
|
332
|
+
var STORE_CONNECTIONS = "connections";
|
|
333
|
+
var STORE_META = "meta";
|
|
334
|
+
var STORE_KEYS = "keys";
|
|
335
|
+
var CRYPTO_KEY_ID = "secret-store-aes-gcm-key";
|
|
336
|
+
function idbRequest(request) {
|
|
337
|
+
return new Promise((resolve, reject) => {
|
|
338
|
+
request.onsuccess = () => resolve(request.result);
|
|
339
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB request failed"));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* F11 hardening: an IDB version bump with no `onblocked` handler and no
|
|
344
|
+
* `versionchange` listener can leave a boot hung forever with no error
|
|
345
|
+
* surfaced anywhere. `onblocked` rejects the open promise; `onVersionChange`
|
|
346
|
+
* is invoked once a successfully-opened connection later receives its OWN
|
|
347
|
+
* `versionchange` event (some other tab needs an upgrade): the connection
|
|
348
|
+
* closes itself immediately so it can't go on to block that upgrade in turn,
|
|
349
|
+
* and the caller uses the callback to drop its cached db promise so the NEXT
|
|
350
|
+
* storage operation reopens fresh.
|
|
351
|
+
*/
|
|
352
|
+
function openDatabase(onVersionChange) {
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
const request = indexedDB.open(IDB_NAME, IDB_VERSION);
|
|
355
|
+
request.onupgradeneeded = () => {
|
|
356
|
+
const db = request.result;
|
|
357
|
+
if (!db.objectStoreNames.contains(STORE_CONNECTIONS)) db.createObjectStore(STORE_CONNECTIONS, { keyPath: "id" });
|
|
358
|
+
if (!db.objectStoreNames.contains(STORE_META)) db.createObjectStore(STORE_META);
|
|
359
|
+
if (!db.objectStoreNames.contains(STORE_KEYS)) db.createObjectStore(STORE_KEYS);
|
|
360
|
+
};
|
|
361
|
+
request.onsuccess = () => {
|
|
362
|
+
const db = request.result;
|
|
363
|
+
db.onversionchange = () => {
|
|
364
|
+
db.close();
|
|
365
|
+
onVersionChange();
|
|
366
|
+
};
|
|
367
|
+
resolve(db);
|
|
368
|
+
};
|
|
369
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB open failed"));
|
|
370
|
+
request.onblocked = () => reject(/* @__PURE__ */ new Error("IndexedDB upgrade blocked by another open connection (close other tabs of this app and retry)"));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* IndexedDB storage backend (raw IDB API, no deps). Lazily opens the database
|
|
375
|
+
* on first use; each operation runs in its own short transaction.
|
|
376
|
+
*/
|
|
377
|
+
function createIndexedDbStorage() {
|
|
378
|
+
let db = null;
|
|
379
|
+
const invalidate = () => {
|
|
380
|
+
db = null;
|
|
381
|
+
};
|
|
382
|
+
const database = () => {
|
|
383
|
+
db ??= openDatabase(invalidate);
|
|
384
|
+
return db;
|
|
385
|
+
};
|
|
386
|
+
const store = async (name, mode) => (await database()).transaction(name, mode).objectStore(name);
|
|
387
|
+
return {
|
|
388
|
+
async getAll() {
|
|
389
|
+
return idbRequest((await store(STORE_CONNECTIONS, "readonly")).getAll());
|
|
390
|
+
},
|
|
391
|
+
async get(id) {
|
|
392
|
+
return await idbRequest((await store(STORE_CONNECTIONS, "readonly")).get(id)) ?? null;
|
|
393
|
+
},
|
|
394
|
+
async put(entry) {
|
|
395
|
+
await idbRequest((await store(STORE_CONNECTIONS, "readwrite")).put(redactEntryUrl(entry)));
|
|
396
|
+
},
|
|
397
|
+
async delete(id) {
|
|
398
|
+
await idbRequest((await store(STORE_CONNECTIONS, "readwrite")).delete(id));
|
|
399
|
+
},
|
|
400
|
+
async getMeta(key) {
|
|
401
|
+
return await idbRequest((await store(STORE_META, "readonly")).get(key)) ?? null;
|
|
402
|
+
},
|
|
403
|
+
async setMeta(key, value) {
|
|
404
|
+
const metaStore = await store(STORE_META, "readwrite");
|
|
405
|
+
if (value === null) await idbRequest(metaStore.delete(key));
|
|
406
|
+
else await idbRequest(metaStore.put(value, key));
|
|
407
|
+
},
|
|
408
|
+
async getCryptoKey() {
|
|
409
|
+
return await idbRequest((await store(STORE_KEYS, "readonly")).get(CRYPTO_KEY_ID)) ?? null;
|
|
410
|
+
},
|
|
411
|
+
async setCryptoKey(key) {
|
|
412
|
+
await idbRequest((await store(STORE_KEYS, "readwrite")).put(key, CRYPTO_KEY_ID));
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function createConnectionStore(options) {
|
|
417
|
+
const { storage } = options;
|
|
418
|
+
async function readEntry(id) {
|
|
419
|
+
const stored = await storage.get(id);
|
|
420
|
+
if (!stored) return null;
|
|
421
|
+
const entry = redactEntryUrl(stored);
|
|
422
|
+
if (entry.baseUrl !== stored.baseUrl) await storage.put(entry);
|
|
423
|
+
return entry;
|
|
424
|
+
}
|
|
425
|
+
async function requireEntry(id) {
|
|
426
|
+
const entry = await readEntry(id);
|
|
427
|
+
if (!entry) throw new Error(`Unknown connection: ${id}`);
|
|
428
|
+
return entry;
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
async list() {
|
|
432
|
+
const stored = await storage.getAll();
|
|
433
|
+
const entries = stored.map(redactEntryUrl);
|
|
434
|
+
await Promise.all(entries.map((entry, index) => entry.baseUrl === stored[index]?.baseUrl ? Promise.resolve() : storage.put(entry)));
|
|
435
|
+
return entries;
|
|
436
|
+
},
|
|
437
|
+
get(id) {
|
|
438
|
+
return readEntry(id);
|
|
439
|
+
},
|
|
440
|
+
async add(input) {
|
|
441
|
+
assertUrlHasNoUserinfo(input.baseUrl);
|
|
442
|
+
const id = input.id ?? randomId();
|
|
443
|
+
if (await readEntry(id)) throw new Error(`Connection already exists: ${id}`);
|
|
444
|
+
const entry = {
|
|
445
|
+
...input,
|
|
446
|
+
id
|
|
447
|
+
};
|
|
448
|
+
await storage.put(entry);
|
|
449
|
+
return clone(entry);
|
|
450
|
+
},
|
|
451
|
+
async update(id, patch) {
|
|
452
|
+
if (patch.baseUrl !== void 0) assertUrlHasNoUserinfo(patch.baseUrl);
|
|
453
|
+
const entry = await requireEntry(id);
|
|
454
|
+
if (entry.locked) throw new Error(`Connection is locked (config-owned): ${id}`);
|
|
455
|
+
const updated = {
|
|
456
|
+
...entry,
|
|
457
|
+
...patch,
|
|
458
|
+
id
|
|
459
|
+
};
|
|
460
|
+
await storage.put(updated);
|
|
461
|
+
return clone(updated);
|
|
462
|
+
},
|
|
463
|
+
async remove(id) {
|
|
464
|
+
if ((await requireEntry(id)).locked) throw new Error(`Connection is locked (config-owned): ${id}`);
|
|
465
|
+
await storage.delete(id);
|
|
466
|
+
if (await storage.getMeta(ACTIVE_ID_KEY) === id) await storage.setMeta(ACTIVE_ID_KEY, null);
|
|
467
|
+
},
|
|
468
|
+
async setSecretRef(id, auth) {
|
|
469
|
+
const updated = {
|
|
470
|
+
...await requireEntry(id),
|
|
471
|
+
auth,
|
|
472
|
+
id
|
|
473
|
+
};
|
|
474
|
+
await storage.put(updated);
|
|
475
|
+
return clone(updated);
|
|
476
|
+
},
|
|
477
|
+
getActiveId() {
|
|
478
|
+
return storage.getMeta(ACTIVE_ID_KEY);
|
|
479
|
+
},
|
|
480
|
+
async getActive() {
|
|
481
|
+
const id = await storage.getMeta(ACTIVE_ID_KEY);
|
|
482
|
+
return id === null ? null : readEntry(id);
|
|
483
|
+
},
|
|
484
|
+
async setActive(id) {
|
|
485
|
+
await requireEntry(id);
|
|
486
|
+
await storage.setMeta(ACTIVE_ID_KEY, id);
|
|
487
|
+
},
|
|
488
|
+
async clearActive() {
|
|
489
|
+
await storage.setMeta(ACTIVE_ID_KEY, null);
|
|
490
|
+
},
|
|
491
|
+
async seedFromRuntimeConfig(config) {
|
|
492
|
+
if (!config) return;
|
|
493
|
+
const activeId = await storage.getMeta(ACTIVE_ID_KEY);
|
|
494
|
+
const configIds = new Set(config.connections.map((entry) => entry.id));
|
|
495
|
+
for (const existing of await storage.getAll()) {
|
|
496
|
+
if (!existing.locked || configIds.has(existing.id)) continue;
|
|
497
|
+
await storage.delete(existing.id);
|
|
498
|
+
if (activeId === existing.id) await storage.setMeta(ACTIVE_ID_KEY, null);
|
|
499
|
+
}
|
|
500
|
+
for (const entry of config.connections) {
|
|
501
|
+
const seededEntry = redactEntryUrl(entry);
|
|
502
|
+
const existing = await readEntry(entry.id);
|
|
503
|
+
if (!existing) await storage.put(clone(seededEntry));
|
|
504
|
+
else if (existing.locked) {
|
|
505
|
+
const seeded = clone(seededEntry);
|
|
506
|
+
const preserveAuth = existing.auth && existing.auth.mode !== "none";
|
|
507
|
+
await storage.put(preserveAuth ? {
|
|
508
|
+
...seeded,
|
|
509
|
+
auth: existing.auth
|
|
510
|
+
} : seeded);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (await storage.getMeta(ACTIVE_ID_KEY) !== null) return;
|
|
514
|
+
const fallback = config.connections.find((entry) => entry.isDefault);
|
|
515
|
+
if (fallback && await storage.get(fallback.id)) await storage.setMeta(ACTIVE_ID_KEY, fallback.id);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Fetch '/runtime-config.json' from the app's OWN origin (relative URL — the
|
|
521
|
+
* static server ships the file beside the build; the app holds no other
|
|
522
|
+
* trusted origin at boot). Absent (404), unreachable (offline), or malformed
|
|
523
|
+
* -> null; never throws, so an offline PWA boot still reaches the stored
|
|
524
|
+
* connection list.
|
|
525
|
+
*/
|
|
526
|
+
async function loadRuntimeConfig(fetchImpl = globalThis.fetch) {
|
|
527
|
+
try {
|
|
528
|
+
const response = await fetchImpl("/runtime-config.json", {
|
|
529
|
+
method: "GET",
|
|
530
|
+
headers: { accept: "application/json" },
|
|
531
|
+
credentials: "omit"
|
|
532
|
+
});
|
|
533
|
+
if (!response.ok) return null;
|
|
534
|
+
const parsed = await response.json();
|
|
535
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.connections)) return null;
|
|
536
|
+
return adaptRuntimeConfigForBrowser(parsed);
|
|
537
|
+
} catch {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/lib/connections/secrets.ts
|
|
543
|
+
var META_PREFIX = "secret:";
|
|
544
|
+
var ENCRYPTED_MARKER = 2;
|
|
545
|
+
var AES_GCM_LENGTH = 256;
|
|
546
|
+
var IV_BYTES = 12;
|
|
547
|
+
/**
|
|
548
|
+
* WebCrypto's `SubtleCrypto` is secure-context-only: on a plain-http LAN origin
|
|
549
|
+
* (the LAN-served tier) `crypto.subtle` is undefined, so encryption is simply
|
|
550
|
+
* not available there. Callers degrade to the legacy plaintext record shape —
|
|
551
|
+
* which `readMaterial` already reads — rather than throwing and blocking the
|
|
552
|
+
* user from saving a credential at all.
|
|
553
|
+
*/
|
|
554
|
+
function subtleAvailable() {
|
|
555
|
+
return typeof crypto !== "undefined" && !!crypto.subtle;
|
|
556
|
+
}
|
|
557
|
+
function isEncryptedRecord(value) {
|
|
558
|
+
return !!value && typeof value === "object" && value.v === ENCRYPTED_MARKER && typeof value.iv === "string" && typeof value.ciphertext === "string";
|
|
559
|
+
}
|
|
560
|
+
function bytesToBase64(bytes) {
|
|
561
|
+
let binary = "";
|
|
562
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
563
|
+
return btoa(binary);
|
|
564
|
+
}
|
|
565
|
+
function base64ToBytes(b64) {
|
|
566
|
+
const binary = atob(b64);
|
|
567
|
+
const bytes = new Uint8Array(binary.length);
|
|
568
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
569
|
+
return bytes;
|
|
570
|
+
}
|
|
571
|
+
var pendingKeys = /* @__PURE__ */ new WeakMap();
|
|
572
|
+
/** Generate-once, reuse-forever non-extractable AES-GCM key for this storage backend. */
|
|
573
|
+
async function getOrCreateKey(storage) {
|
|
574
|
+
const existing = await storage.getCryptoKey();
|
|
575
|
+
if (existing) return existing;
|
|
576
|
+
let pending = pendingKeys.get(storage);
|
|
577
|
+
if (!pending) {
|
|
578
|
+
pending = (async () => {
|
|
579
|
+
const key = await crypto.subtle.generateKey({
|
|
580
|
+
name: "AES-GCM",
|
|
581
|
+
length: AES_GCM_LENGTH
|
|
582
|
+
}, false, ["encrypt", "decrypt"]);
|
|
583
|
+
await storage.setCryptoKey(key);
|
|
584
|
+
return key;
|
|
585
|
+
})();
|
|
586
|
+
pendingKeys.set(storage, pending);
|
|
587
|
+
pending.catch(() => {
|
|
588
|
+
pendingKeys.delete(storage);
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return pending;
|
|
592
|
+
}
|
|
593
|
+
async function encryptMaterial(key, material) {
|
|
594
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
595
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(material));
|
|
596
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
|
|
597
|
+
name: "AES-GCM",
|
|
598
|
+
iv
|
|
599
|
+
}, key, plaintext));
|
|
600
|
+
return {
|
|
601
|
+
v: ENCRYPTED_MARKER,
|
|
602
|
+
iv: bytesToBase64(iv),
|
|
603
|
+
ciphertext: bytesToBase64(ciphertext)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
async function decryptRecord(key, record) {
|
|
607
|
+
const iv = base64ToBytes(record.iv);
|
|
608
|
+
const ciphertext = base64ToBytes(record.ciphertext);
|
|
609
|
+
const plaintext = await crypto.subtle.decrypt({
|
|
610
|
+
name: "AES-GCM",
|
|
611
|
+
iv
|
|
612
|
+
}, key, ciphertext);
|
|
613
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
614
|
+
}
|
|
615
|
+
async function writeMaterial(storage, ref, material) {
|
|
616
|
+
if (!subtleAvailable()) {
|
|
617
|
+
await storage.setMeta(META_PREFIX + ref, JSON.stringify(material));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const record = await encryptMaterial(await getOrCreateKey(storage), material);
|
|
621
|
+
await storage.setMeta(META_PREFIX + ref, JSON.stringify(record));
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Read+decrypt a stored secret. Transparently migrates a legacy plaintext
|
|
625
|
+
* record by re-encrypting it in place before returning. Returns null for a
|
|
626
|
+
* missing ref or anything that can't be parsed/decrypted — the caller degrades
|
|
627
|
+
* gracefully rather than throwing.
|
|
628
|
+
*/
|
|
629
|
+
async function readMaterial(storage, ref) {
|
|
630
|
+
const raw = await storage.getMeta(META_PREFIX + ref);
|
|
631
|
+
if (raw === null) return null;
|
|
632
|
+
let parsed;
|
|
633
|
+
try {
|
|
634
|
+
parsed = JSON.parse(raw);
|
|
635
|
+
} catch {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
if (isEncryptedRecord(parsed)) try {
|
|
639
|
+
return await decryptRecord(await getOrCreateKey(storage), parsed);
|
|
640
|
+
} catch {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
const material = parsed;
|
|
644
|
+
if (subtleAvailable()) try {
|
|
645
|
+
await writeMaterial(storage, ref, material);
|
|
646
|
+
} catch {}
|
|
647
|
+
return material;
|
|
648
|
+
}
|
|
649
|
+
function createSecretStore(storage) {
|
|
650
|
+
return {
|
|
651
|
+
async set(ref, material) {
|
|
652
|
+
await writeMaterial(storage, ref, material);
|
|
653
|
+
},
|
|
654
|
+
async delete(ref) {
|
|
655
|
+
await storage.setMeta(META_PREFIX + ref, null);
|
|
656
|
+
},
|
|
657
|
+
get(ref) {
|
|
658
|
+
return readMaterial(storage, ref);
|
|
659
|
+
},
|
|
660
|
+
async resolveAuth(entry) {
|
|
661
|
+
if (entry.auth.mode !== "basic" || !entry.auth.secretRef) return { mode: "none" };
|
|
662
|
+
const material = await readMaterial(storage, entry.auth.secretRef);
|
|
663
|
+
if (!material || typeof material.password !== "string") return { mode: "none" };
|
|
664
|
+
const username = entry.auth.username || material.username;
|
|
665
|
+
return username ? {
|
|
666
|
+
mode: "basic",
|
|
667
|
+
username,
|
|
668
|
+
password: material.password
|
|
669
|
+
} : {
|
|
670
|
+
mode: "basic",
|
|
671
|
+
password: material.password
|
|
672
|
+
};
|
|
673
|
+
},
|
|
674
|
+
async updateUsername(ref, username) {
|
|
675
|
+
const material = await readMaterial(storage, ref);
|
|
676
|
+
if (!material) return;
|
|
677
|
+
const updated = { ...material };
|
|
678
|
+
if (username === void 0) delete updated.username;
|
|
679
|
+
else updated.username = username;
|
|
680
|
+
await writeMaterial(storage, ref, updated);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/lib/chat/session-events.ts
|
|
686
|
+
/**
|
|
687
|
+
* SSE consumer for OpenCode's `/event` stream.
|
|
688
|
+
*
|
|
689
|
+
* Phase 3b ("One UI, delete the split"): the underlying stream is now opened by
|
|
690
|
+
* the browser-owned direct transport (`$lib/connections/boot.getTransport()`
|
|
691
|
+
* → `subscribeEvents(onFrame, signal)`), which talks to the active connection's
|
|
692
|
+
* OpenCode/Guardian base URL directly (no host proxy, no admin cookie) and
|
|
693
|
+
* REUSES this module's `parseFrame`. Reconnect/backoff, session-scoped dispatch,
|
|
694
|
+
* and abort semantics stay here — the chat layer's concern, not the transport's.
|
|
695
|
+
*
|
|
696
|
+
* Phase D of docs/technical/multi-endpoint-session-ux.md.
|
|
697
|
+
*/
|
|
698
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
699
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
700
|
+
/**
|
|
701
|
+
* Parse one `\n\n`-delimited SSE frame. Multi-line `data:` fields are
|
|
702
|
+
* concatenated with `\n` per the SSE spec. Reused by the direct transport.
|
|
703
|
+
*/
|
|
704
|
+
function parseFrame(chunk) {
|
|
705
|
+
const frame = {};
|
|
706
|
+
const dataLines = [];
|
|
707
|
+
for (const rawLine of chunk.split("\n")) {
|
|
708
|
+
if (!rawLine || rawLine.startsWith(":")) continue;
|
|
709
|
+
if (rawLine.startsWith("data:")) dataLines.push(rawLine.replace(/^data:\s?/, ""));
|
|
710
|
+
else if (rawLine.startsWith("event:")) frame.event = rawLine.replace(/^event:\s?/, "");
|
|
711
|
+
else if (rawLine.startsWith("id:")) frame.id = rawLine.replace(/^id:\s?/, "");
|
|
712
|
+
}
|
|
713
|
+
if (dataLines.length > 0) frame.data = dataLines.join("\n");
|
|
714
|
+
return frame;
|
|
715
|
+
}
|
|
716
|
+
function dispatch(handlers, payload) {
|
|
717
|
+
const info = payload.properties?.info;
|
|
718
|
+
const id = info?.id ?? payload.sessionID;
|
|
719
|
+
if (!id) return;
|
|
720
|
+
switch (payload.type) {
|
|
721
|
+
case "session.created":
|
|
722
|
+
handlers.onCreated(id);
|
|
723
|
+
return;
|
|
724
|
+
case "session.updated":
|
|
725
|
+
handlers.onUpdated(id, {
|
|
726
|
+
title: info?.title,
|
|
727
|
+
updatedAt: info?.time?.updated
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
case "session.deleted":
|
|
731
|
+
handlers.onDeleted(id);
|
|
732
|
+
return;
|
|
733
|
+
default: return;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Open the active connection's `/event` SSE stream (via the direct transport)
|
|
738
|
+
* and dispatch session-scoped events. Returns an unsubscribe function that
|
|
739
|
+
* aborts the stream and prevents reconnection.
|
|
740
|
+
*
|
|
741
|
+
* `onConnect` fires on the first frame of each (re)connected stream — the point
|
|
742
|
+
* at which live updates are demonstrably flowing. The transport owns the fetch;
|
|
743
|
+
* this loop owns exponential-backoff reconnect and abort.
|
|
744
|
+
*/
|
|
745
|
+
function subscribeSessionEvents(handlers) {
|
|
746
|
+
let stopped = false;
|
|
747
|
+
let controller = new AbortController();
|
|
748
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
749
|
+
const timer = setTimeout(resolve, ms);
|
|
750
|
+
controller.signal.addEventListener("abort", () => {
|
|
751
|
+
clearTimeout(timer);
|
|
752
|
+
resolve();
|
|
753
|
+
}, { once: true });
|
|
754
|
+
});
|
|
755
|
+
async function readStream() {
|
|
756
|
+
let connected = false;
|
|
757
|
+
await getTransport().subscribeEvents((event) => {
|
|
758
|
+
if (!connected) {
|
|
759
|
+
connected = true;
|
|
760
|
+
handlers.onConnect?.();
|
|
761
|
+
}
|
|
762
|
+
const payload = event;
|
|
763
|
+
handlers.onEvent?.(payload);
|
|
764
|
+
dispatch(handlers, payload);
|
|
765
|
+
}, controller.signal);
|
|
766
|
+
}
|
|
767
|
+
(async () => {
|
|
768
|
+
let attempt = 0;
|
|
769
|
+
while (!stopped) try {
|
|
770
|
+
attempt++;
|
|
771
|
+
await readStream();
|
|
772
|
+
attempt = 0;
|
|
773
|
+
if (!stopped) await sleep(500);
|
|
774
|
+
} catch (err) {
|
|
775
|
+
if (stopped) return;
|
|
776
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
777
|
+
if (error.name !== "AbortError") {
|
|
778
|
+
console.warn("[session-events] SSE error, reconnecting", error);
|
|
779
|
+
handlers.onDisconnect?.(error);
|
|
780
|
+
}
|
|
781
|
+
await sleep(Math.min(INITIAL_BACKOFF_MS * 2 ** Math.max(0, attempt - 1), MAX_BACKOFF_MS));
|
|
782
|
+
if (controller.signal.aborted && !stopped) controller = new AbortController();
|
|
783
|
+
}
|
|
784
|
+
})();
|
|
785
|
+
return () => {
|
|
786
|
+
stopped = true;
|
|
787
|
+
try {
|
|
788
|
+
controller.abort();
|
|
789
|
+
} catch {}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region src/lib/transport/direct.ts
|
|
794
|
+
/**
|
|
795
|
+
* Minimal browser-owned direct transport (Phase 3a — "One UI, delete the
|
|
796
|
+
* split").
|
|
797
|
+
*
|
|
798
|
+
* Talks to a connection's OpenCode/Guardian base URL directly from the
|
|
799
|
+
* browser — no proxy, NO COOKIES (`credentials: 'omit'`). This is deliberately
|
|
800
|
+
* small: it exposes only the calls the ui chat layer needs and REUSES ui's
|
|
801
|
+
* existing SSE frame parser (`$lib/chat/session-events.ts` `parseFrame`) and
|
|
802
|
+
* event envelope type (`$lib/chat/oc-events.ts` `RawEvent`) rather than
|
|
803
|
+
* re-implementing them. Reconnect/backoff and session-scoped filtering are the
|
|
804
|
+
* chat layer's concern, not this transport's.
|
|
805
|
+
*/
|
|
806
|
+
var PROBE_TIMEOUT_MS = 5e3;
|
|
807
|
+
/**
|
|
808
|
+
* UTF-8-encode before base64ing: `btoa()` alone only accepts Latin-1 code
|
|
809
|
+
* points and throws a synchronous `InvalidCharacterError` for any password
|
|
810
|
+
* containing e.g. Cyrillic/CJK/emoji characters, before any network I/O.
|
|
811
|
+
*/
|
|
812
|
+
function base64Utf8(value) {
|
|
813
|
+
const bytes = new TextEncoder().encode(value);
|
|
814
|
+
let binary = "";
|
|
815
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
816
|
+
return btoa(binary);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Build the `Authorization` header value for a resolved auth, or null for
|
|
820
|
+
* `{ mode: 'none' }`. Basic username defaults to `'opencode'` (OpenCode's own
|
|
821
|
+
* server default and what the host app sends).
|
|
822
|
+
*/
|
|
823
|
+
function authorizationHeader(auth) {
|
|
824
|
+
if (auth.mode === "basic") return `Basic ${base64Utf8(`${auth.username ?? "opencode"}:${auth.password}`)}`;
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
function createDirectTransport(getConnection, resolveAuth, fetchImpl = globalThis.fetch) {
|
|
828
|
+
function requireConnection() {
|
|
829
|
+
const connection = getConnection();
|
|
830
|
+
if (!connection) throw new Error("No active connection");
|
|
831
|
+
return connection;
|
|
832
|
+
}
|
|
833
|
+
/** Trailing-slash and path-prefix safe: reverse-proxied instances keep their prefix. */
|
|
834
|
+
function baseFor(connection) {
|
|
835
|
+
return connection.baseUrl.replace(/\/+$/, "");
|
|
836
|
+
}
|
|
837
|
+
async function authHeaders(connection) {
|
|
838
|
+
const { authorization } = await resolveAuth(connection);
|
|
839
|
+
return authorization ? { authorization } : {};
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
async request(method, path, body) {
|
|
843
|
+
const connection = requireConnection();
|
|
844
|
+
const init = {
|
|
845
|
+
method,
|
|
846
|
+
headers: {
|
|
847
|
+
...body === void 0 ? {} : { "content-type": "application/json" },
|
|
848
|
+
...await authHeaders(connection)
|
|
849
|
+
},
|
|
850
|
+
credentials: "omit"
|
|
851
|
+
};
|
|
852
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
853
|
+
const response = await fetchImpl(`${baseFor(connection)}${path}`, init);
|
|
854
|
+
if (!response.ok) throw Object.assign(/* @__PURE__ */ new Error(`HTTP ${response.status}`), { status: response.status });
|
|
855
|
+
return response;
|
|
856
|
+
},
|
|
857
|
+
async subscribeEvents(onFrame, signal) {
|
|
858
|
+
const connection = requireConnection();
|
|
859
|
+
const response = await fetchImpl(`${baseFor(connection)}/event`, {
|
|
860
|
+
method: "GET",
|
|
861
|
+
headers: {
|
|
862
|
+
accept: "text/event-stream",
|
|
863
|
+
...await authHeaders(connection)
|
|
864
|
+
},
|
|
865
|
+
credentials: "omit",
|
|
866
|
+
signal
|
|
867
|
+
});
|
|
868
|
+
if (!response.ok || !response.body) throw Object.assign(/* @__PURE__ */ new Error(`SSE stream failed: HTTP ${response.status}`), { status: response.status });
|
|
869
|
+
const reader = response.body.getReader();
|
|
870
|
+
const decoder = new TextDecoder();
|
|
871
|
+
let buffer = "";
|
|
872
|
+
try {
|
|
873
|
+
while (true) {
|
|
874
|
+
const { done, value } = await reader.read();
|
|
875
|
+
if (done) break;
|
|
876
|
+
buffer += decoder.decode(value, { stream: true });
|
|
877
|
+
const chunks = buffer.split("\n\n");
|
|
878
|
+
buffer = chunks.pop() ?? "";
|
|
879
|
+
for (const chunk of chunks) {
|
|
880
|
+
if (!chunk) continue;
|
|
881
|
+
const frame = parseFrame(chunk);
|
|
882
|
+
if (!frame.data) continue;
|
|
883
|
+
let event;
|
|
884
|
+
try {
|
|
885
|
+
event = JSON.parse(frame.data);
|
|
886
|
+
} catch {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
onFrame(event);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} finally {
|
|
893
|
+
try {
|
|
894
|
+
reader.releaseLock();
|
|
895
|
+
} catch {}
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
async probeHealth() {
|
|
899
|
+
const connection = getConnection();
|
|
900
|
+
if (!connection) return { status: "unreachable" };
|
|
901
|
+
const base = baseFor(connection);
|
|
902
|
+
const verdict = validateConnectionUrl(base);
|
|
903
|
+
if (!verdict.ok && verdict.reason === "insecure-remote") return { status: "insecure" };
|
|
904
|
+
try {
|
|
905
|
+
const response = await fetchImpl(`${base}/`, {
|
|
906
|
+
method: "GET",
|
|
907
|
+
headers: await authHeaders(connection),
|
|
908
|
+
credentials: "omit",
|
|
909
|
+
cache: "no-store",
|
|
910
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
911
|
+
});
|
|
912
|
+
if (response.status === 401 || response.status === 403) return { status: "unauthorized" };
|
|
913
|
+
if (response.ok || response.status >= 300 && response.status < 400) return { status: "accessible" };
|
|
914
|
+
return { status: "unreachable" };
|
|
915
|
+
} catch {
|
|
916
|
+
return { status: "unreachable" };
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
//#endregion
|
|
922
|
+
//#region src/lib/connections/boot.ts
|
|
923
|
+
/**
|
|
924
|
+
* Browser connection boot (Phase 3b — "One UI, delete the split").
|
|
925
|
+
*
|
|
926
|
+
* One storage backend + one ConnectionStore + one SecretStore + one direct
|
|
927
|
+
* transport per browsing session, created lazily on first access. The chat
|
|
928
|
+
* layer (`$lib/api/chat.ts`, `$lib/chat/session-events.ts`) and the connection
|
|
929
|
+
* switcher (`$lib/endpoints-state.svelte.ts`) all share these singletons so the
|
|
930
|
+
* browser owns connections and talks to OpenCode/Guardian DIRECTLY — no host
|
|
931
|
+
* proxy, no admin cookie.
|
|
932
|
+
*
|
|
933
|
+
* The transport reads the active connection SYNCHRONOUSLY from a snapshot the
|
|
934
|
+
* connection store owner (`endpoints-state`) keeps in sync via
|
|
935
|
+
* `setActiveConnection()`. The store's own `getActive()` is async (IndexedDB),
|
|
936
|
+
* so it can't back the transport's synchronous `getConnection` directly.
|
|
937
|
+
*
|
|
938
|
+
* IndexedDB is the persistent backend; private-browsing modes that refuse it
|
|
939
|
+
* degrade to an in-memory backend (session-only) rather than a blank page —
|
|
940
|
+
* mirroring the client's `pickStorage`.
|
|
941
|
+
*/
|
|
942
|
+
var store = null;
|
|
943
|
+
var secrets = null;
|
|
944
|
+
var transport = null;
|
|
945
|
+
/** Synchronous snapshot of the active connection for the transport. */
|
|
946
|
+
var activeConnection = null;
|
|
947
|
+
/**
|
|
948
|
+
* Some private-browsing modes refuse IndexedDB entirely; degrade to a
|
|
949
|
+
* session-only in-memory backend rather than a blank page (mirrors the
|
|
950
|
+
* client's pickStorage).
|
|
951
|
+
*/
|
|
952
|
+
function pickStorage() {
|
|
953
|
+
try {
|
|
954
|
+
if (typeof indexedDB !== "undefined") return createIndexedDbStorage();
|
|
955
|
+
} catch {}
|
|
956
|
+
return createMemoryStorage();
|
|
957
|
+
}
|
|
958
|
+
function ensure() {
|
|
959
|
+
if (store && secrets && transport) return;
|
|
960
|
+
const storage = pickStorage();
|
|
961
|
+
const connectionStore = createConnectionStore({ storage });
|
|
962
|
+
const secretStore = createSecretStore(storage);
|
|
963
|
+
const directTransport = createDirectTransport(() => activeConnection, async (connection) => {
|
|
964
|
+
return { authorization: authorizationHeader(await secretStore.resolveAuth(connection)) ?? void 0 };
|
|
965
|
+
});
|
|
966
|
+
store = connectionStore;
|
|
967
|
+
secrets = secretStore;
|
|
968
|
+
transport = directTransport;
|
|
969
|
+
}
|
|
970
|
+
function getConnectionStore() {
|
|
971
|
+
ensure();
|
|
972
|
+
return store;
|
|
973
|
+
}
|
|
974
|
+
function getSecretStore() {
|
|
975
|
+
ensure();
|
|
976
|
+
return secrets;
|
|
977
|
+
}
|
|
978
|
+
function getTransport() {
|
|
979
|
+
ensure();
|
|
980
|
+
return transport;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Point the direct transport at `connection` (or clear it). Called by the
|
|
984
|
+
* connection-store owner (`endpoints-state`) on load/activate — a plain
|
|
985
|
+
* function, no `$effect`, so the transport's synchronous `getConnection`
|
|
986
|
+
* always sees the current active connection.
|
|
987
|
+
*/
|
|
988
|
+
function setActiveConnection(connection) {
|
|
989
|
+
activeConnection = connection;
|
|
990
|
+
}
|
|
991
|
+
//#endregion
|
|
992
|
+
//#region src/lib/voice/settings-store.ts
|
|
993
|
+
var STORAGE_KEY = "openpalm.voice.settings";
|
|
994
|
+
var PROVIDER_IDS = [
|
|
995
|
+
"disabled",
|
|
996
|
+
"browser",
|
|
997
|
+
"openpalm-voice",
|
|
998
|
+
"openai-compatible"
|
|
999
|
+
];
|
|
1000
|
+
function safeLocalStorage() {
|
|
1001
|
+
try {
|
|
1002
|
+
return globalThis.localStorage ?? null;
|
|
1003
|
+
} catch {
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function readSection(raw) {
|
|
1008
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1009
|
+
const r = raw;
|
|
1010
|
+
if (!PROVIDER_IDS.includes(r.provider)) return null;
|
|
1011
|
+
const section = { provider: r.provider };
|
|
1012
|
+
for (const key of [
|
|
1013
|
+
"baseURL",
|
|
1014
|
+
"model",
|
|
1015
|
+
"language",
|
|
1016
|
+
"voice",
|
|
1017
|
+
"secretRef"
|
|
1018
|
+
]) if (typeof r[key] === "string" && r[key]) section[key] = r[key];
|
|
1019
|
+
return section;
|
|
1020
|
+
}
|
|
1021
|
+
/** Read + validate the persisted settings. Null = never saved / unreadable
|
|
1022
|
+
* (callers fall back to capability-based defaults). */
|
|
1023
|
+
function loadVoiceSettings() {
|
|
1024
|
+
const raw = safeLocalStorage()?.getItem(STORAGE_KEY);
|
|
1025
|
+
if (!raw) return null;
|
|
1026
|
+
try {
|
|
1027
|
+
const parsed = JSON.parse(raw);
|
|
1028
|
+
const stt = readSection(parsed.stt);
|
|
1029
|
+
const tts = readSection(parsed.tts);
|
|
1030
|
+
if (!stt || !tts) return null;
|
|
1031
|
+
return {
|
|
1032
|
+
version: 1,
|
|
1033
|
+
stt,
|
|
1034
|
+
tts
|
|
1035
|
+
};
|
|
1036
|
+
} catch {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
//#endregion
|
|
1041
|
+
//#region src/lib/voice/providers.ts
|
|
1042
|
+
/**
|
|
1043
|
+
* Browser-side speech-provider transport.
|
|
1044
|
+
*
|
|
1045
|
+
* The chat client owns its TTS/STT provider choice and calls the provider's
|
|
1046
|
+
* OpenAI-shaped /v1/audio endpoints from the browser — there is no
|
|
1047
|
+
* config-holding relay anymore (the old /api/speak and /api/transcribe
|
|
1048
|
+
* proxies read host-global stack.env config, which conflated per-device
|
|
1049
|
+
* preference with host state). Two transports:
|
|
1050
|
+
*
|
|
1051
|
+
* - 'openpalm-voice' → the SAME-ORIGIN `/voice` pass-through the host
|
|
1052
|
+
* advertises in the runtime handshake. Same-origin
|
|
1053
|
+
* means no CORS and no exposed container port; the
|
|
1054
|
+
* session cookie rides along automatically
|
|
1055
|
+
* (fetch's default same-origin credentials mode).
|
|
1056
|
+
* - 'openai-compatible' → the user's configured endpoint, called directly
|
|
1057
|
+
* with the API key held in the browser's encrypted
|
|
1058
|
+
* secret store — the same trust model as
|
|
1059
|
+
* connection passwords.
|
|
1060
|
+
*
|
|
1061
|
+
* "OpenPalm Voice" resolution: the handshake's `voice.url` (present when the
|
|
1062
|
+
* host process can serve the pass-through and the addon is enabled). We
|
|
1063
|
+
* fetch the handshake lazily (once per page load; `refreshAdvertisedVoiceUrl`
|
|
1064
|
+
* re-probes) instead of reading the reactive runtime-context store, so
|
|
1065
|
+
* resolution works regardless of layout-mount ordering and in non-component
|
|
1066
|
+
* callers.
|
|
1067
|
+
*/
|
|
1068
|
+
var OPENPALM_VOICE_TTS_MODEL = "kokoro";
|
|
1069
|
+
var OPENPALM_VOICE_STT_MODEL = "whisper-1";
|
|
1070
|
+
var UPSTREAM_TIMEOUT_MS = 6e4;
|
|
1071
|
+
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
1072
|
+
var advertisedUrl;
|
|
1073
|
+
var advertisedFetch = null;
|
|
1074
|
+
async function fetchAdvertisedUrl() {
|
|
1075
|
+
try {
|
|
1076
|
+
const res = await fetch("/api/runtime", {
|
|
1077
|
+
headers: { accept: "application/json" },
|
|
1078
|
+
credentials: "omit",
|
|
1079
|
+
cache: "no-store",
|
|
1080
|
+
signal: AbortSignal.timeout(HANDSHAKE_TIMEOUT_MS)
|
|
1081
|
+
});
|
|
1082
|
+
if (!res.ok) return null;
|
|
1083
|
+
const data = await res.json();
|
|
1084
|
+
return typeof data.voice?.url === "string" && data.voice.url ? data.voice.url : null;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function startAdvertisedFetch() {
|
|
1090
|
+
const inFlight = fetchAdvertisedUrl().then((url) => {
|
|
1091
|
+
{
|
|
1092
|
+
advertisedUrl = url;
|
|
1093
|
+
advertisedFetch = null;
|
|
1094
|
+
}
|
|
1095
|
+
return url;
|
|
1096
|
+
});
|
|
1097
|
+
advertisedFetch = inFlight;
|
|
1098
|
+
return inFlight;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* The host's advertised voice pass-through URL, or null when the voice addon
|
|
1102
|
+
* is disabled / the handshake is unreachable. Cached between refreshes;
|
|
1103
|
+
* concurrent callers share one fetch. `initVoice` refreshes on every run (a
|
|
1104
|
+
* cheap same-origin GET), so an addon toggle is picked up on the next chat
|
|
1105
|
+
* mount without a hard reload.
|
|
1106
|
+
*/
|
|
1107
|
+
function advertisedVoiceUrl() {
|
|
1108
|
+
if (advertisedUrl !== void 0) return Promise.resolve(advertisedUrl);
|
|
1109
|
+
return advertisedFetch ?? startAdvertisedFetch();
|
|
1110
|
+
}
|
|
1111
|
+
async function apiKeyFor(secretRef) {
|
|
1112
|
+
if (!secretRef) return void 0;
|
|
1113
|
+
try {
|
|
1114
|
+
return (await getSecretStore().get(secretRef))?.password || void 0;
|
|
1115
|
+
} catch {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* The advertised OpenPalm Voice pass-through target, or null when the host
|
|
1121
|
+
* offers none. Model is fixed by the container; `voice` is deliberately
|
|
1122
|
+
* omitted so the host's configured default (OP_VOICE_KOKORO_VOICE) applies.
|
|
1123
|
+
*/
|
|
1124
|
+
async function openpalmVoiceTarget(section, kind) {
|
|
1125
|
+
const url = await advertisedVoiceUrl();
|
|
1126
|
+
if (!url) return null;
|
|
1127
|
+
return {
|
|
1128
|
+
baseURL: url,
|
|
1129
|
+
model: kind === "tts" ? OPENPALM_VOICE_TTS_MODEL : OPENPALM_VOICE_STT_MODEL,
|
|
1130
|
+
language: kind === "stt" ? section?.language : void 0
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
async function resolveTarget(section, kind) {
|
|
1134
|
+
const provider = section?.provider;
|
|
1135
|
+
if (!provider) return openpalmVoiceTarget(section, kind);
|
|
1136
|
+
if (provider === "openpalm-voice") return openpalmVoiceTarget(section, kind);
|
|
1137
|
+
if (provider === "openai-compatible") {
|
|
1138
|
+
const baseURL = section?.baseURL?.trim();
|
|
1139
|
+
if (!baseURL) return null;
|
|
1140
|
+
return {
|
|
1141
|
+
baseURL,
|
|
1142
|
+
model: section?.model?.trim() || (kind === "tts" ? "tts-1" : "whisper-1"),
|
|
1143
|
+
voice: kind === "tts" ? section?.voice?.trim() : void 0,
|
|
1144
|
+
language: kind === "stt" ? section?.language : void 0,
|
|
1145
|
+
apiKey: await apiKeyFor(section?.secretRef)
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
function resolveTtsTarget() {
|
|
1151
|
+
return resolveTarget(loadVoiceSettings()?.tts, "tts");
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Resolve the OpenAI-shaped API base from the user's configured endpoint.
|
|
1155
|
+
*
|
|
1156
|
+
* OpenAI and every compatible service (OpenRouter, Groq, LM Studio, vLLM,
|
|
1157
|
+
* Ollama, …) document a `base_url` that already ends in a version segment —
|
|
1158
|
+
* almost always `/v1`. We respect a version the user supplied and add `/v1`
|
|
1159
|
+
* only when absent, so pasting the provider's documented base URL Just
|
|
1160
|
+
* Works whether or not it includes `/v1` (no double `/v1/v1`, no hint to
|
|
1161
|
+
* remember). The openpalm-voice pass-through base (`/voice`) has no version,
|
|
1162
|
+
* so it also gets `/v1`, matching the container's `/v1/audio/*` surface.
|
|
1163
|
+
*
|
|
1164
|
+
* Sub-paths are then appended WITHOUT a leading `/v1` (e.g. `/audio/speech`).
|
|
1165
|
+
*/
|
|
1166
|
+
function openaiApiBase(baseURL) {
|
|
1167
|
+
const trimmed = baseURL.replace(/\/+$/, "");
|
|
1168
|
+
return /\/v\d+$/.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Synthesize speech for `text` against the configured TTS provider
|
|
1172
|
+
* (OpenAI-shape POST /v1/audio/speech). Returns the raw Response (audio
|
|
1173
|
+
* bytes on 200) or null when no server-side TTS provider is configured;
|
|
1174
|
+
* network failures reject.
|
|
1175
|
+
*/
|
|
1176
|
+
async function synthesize(text, opts) {
|
|
1177
|
+
const target = await resolveTtsTarget();
|
|
1178
|
+
if (!target) return null;
|
|
1179
|
+
const headers = { "content-type": "application/json" };
|
|
1180
|
+
if (target.apiKey) headers.authorization = `Bearer ${target.apiKey}`;
|
|
1181
|
+
return fetch(`${openaiApiBase(target.baseURL)}/audio/speech`, {
|
|
1182
|
+
method: "POST",
|
|
1183
|
+
headers,
|
|
1184
|
+
body: JSON.stringify({
|
|
1185
|
+
model: target.model,
|
|
1186
|
+
input: text,
|
|
1187
|
+
...target.voice ? { voice: target.voice } : {},
|
|
1188
|
+
response_format: "wav"
|
|
1189
|
+
}),
|
|
1190
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
//#endregion
|
|
1194
|
+
//#region src/lib/voice/speakable-text.ts
|
|
1195
|
+
/**
|
|
1196
|
+
* Deterministic markdown-to-speech text stripping. Pure and dependency-free
|
|
1197
|
+
* so both the browser TTS fallback (audio-playback.ts) and the server TTS
|
|
1198
|
+
* provider transport (voice/providers.ts) can run the exact same rules without
|
|
1199
|
+
* ever handing raw markdown syntax to a speech engine.
|
|
1200
|
+
*
|
|
1201
|
+
* Order matters: fenced code blocks and inline code are handled before any
|
|
1202
|
+
* other markdown stripping so their contents are never mistaken for
|
|
1203
|
+
* headings/emphasis/list markers. Whitespace collapsing runs last.
|
|
1204
|
+
*/
|
|
1205
|
+
var HTML_ENTITIES = {
|
|
1206
|
+
"&": "&",
|
|
1207
|
+
"<": "<",
|
|
1208
|
+
">": ">",
|
|
1209
|
+
""": "\"",
|
|
1210
|
+
"'": "'"
|
|
1211
|
+
};
|
|
1212
|
+
function decodeEntities(text) {
|
|
1213
|
+
return text.replace(/&|<|>|"|'/g, (match) => HTML_ENTITIES[match] ?? match);
|
|
1214
|
+
}
|
|
1215
|
+
function urlToHostname(url) {
|
|
1216
|
+
try {
|
|
1217
|
+
return new URL(url).hostname;
|
|
1218
|
+
} catch {
|
|
1219
|
+
return url;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function toSpeakableText(text) {
|
|
1223
|
+
let out = text;
|
|
1224
|
+
out = out.replace(/```[\s\S]*?```/g, " Code omitted. ");
|
|
1225
|
+
out = out.replace(/`([^`]+)`/g, "$1");
|
|
1226
|
+
out = out.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
|
|
1227
|
+
out = out.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1");
|
|
1228
|
+
out = out.replace(/https?:\/\/[^\s)<>"']+/g, (match) => urlToHostname(match));
|
|
1229
|
+
out = out.replace(/<[^>]+>/g, "");
|
|
1230
|
+
out = out.replace(/^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/gm, "");
|
|
1231
|
+
const keptLines = [];
|
|
1232
|
+
for (const line of out.split("\n")) {
|
|
1233
|
+
let l = line;
|
|
1234
|
+
if (/^\s*\|?[\s:|-]+\|?\s*$/.test(l) && l.includes("-") && l.includes("|")) continue;
|
|
1235
|
+
l = l.replace(/^\s*>\s?/, "");
|
|
1236
|
+
l = l.replace(/^\s*#{1,6}\s+/, "");
|
|
1237
|
+
const wasListItem = /^\s*(?:[-*+]|\d+\.)\s+/.test(l);
|
|
1238
|
+
l = l.replace(/^\s*(?:[-*+]|\d+\.)\s+/, "");
|
|
1239
|
+
if (l.includes("|")) l = l.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((cell) => cell.trim()).join(", ");
|
|
1240
|
+
l = l.trim();
|
|
1241
|
+
if (wasListItem && l && !/[.!?:]$/.test(l)) l += ".";
|
|
1242
|
+
keptLines.push(l);
|
|
1243
|
+
}
|
|
1244
|
+
out = keptLines.join("\n");
|
|
1245
|
+
out = out.replace(/~~(.+?)~~/g, "$1");
|
|
1246
|
+
out = out.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
1247
|
+
out = out.replace(/__(.+?)__/g, "$1");
|
|
1248
|
+
out = out.replace(/\*(.+?)\*/g, "$1");
|
|
1249
|
+
out = out.replace(/(?<![A-Za-z0-9_])_(.+?)_(?![A-Za-z0-9_])/g, "$1");
|
|
1250
|
+
out = decodeEntities(out);
|
|
1251
|
+
out = out.replace(/<\/?[A-Za-z][A-Za-z0-9-]*(?:\s[^<>]*)?\/?>/g, "");
|
|
1252
|
+
out = out.replace(/\n{3,}/g, "\n\n");
|
|
1253
|
+
out = out.replace(/[^\S\n]{2,}/g, " ");
|
|
1254
|
+
return out.trim();
|
|
1255
|
+
}
|
|
1256
|
+
//#endregion
|
|
1257
|
+
//#region src/lib/voice/audio-playback.ts
|
|
1258
|
+
/**
|
|
1259
|
+
* AudioPlaybackController — the imperative TTS audio engine extracted from
|
|
1260
|
+
* voice-state.svelte.ts. Owns the speak queue, the HTMLAudioElement +
|
|
1261
|
+
* blob-URL lifecycle, the browser autoplay-policy fallback, and the
|
|
1262
|
+
* per-utterance playback (playOne). The reactive `voiceState` store
|
|
1263
|
+
* composes an instance of this and delegates speak/resume/stop to it.
|
|
1264
|
+
*
|
|
1265
|
+
* The controller never holds reactive `$state` itself — it mutates the
|
|
1266
|
+
* host store's reactive fields (status / errorMessage / autoplayBlocked)
|
|
1267
|
+
* through the `AudioPlaybackHost` it is constructed with, exactly as the
|
|
1268
|
+
* old module-global engine mutated `voiceState` directly.
|
|
1269
|
+
*
|
|
1270
|
+
* Only touch browser APIs (window, Audio, AudioContext, speechSynthesis)
|
|
1271
|
+
* from methods — never at construction — for SSR safety.
|
|
1272
|
+
*/
|
|
1273
|
+
var SPEAK_QUEUE_MAX = 20;
|
|
1274
|
+
var AudioPlaybackController = class {
|
|
1275
|
+
host;
|
|
1276
|
+
/**
|
|
1277
|
+
* Optional hook fired when the speak queue fully drains (the last
|
|
1278
|
+
* utterance reached a terminal state with nothing queued behind it).
|
|
1279
|
+
* Conversation mode uses this to re-arm listening — an explicit
|
|
1280
|
+
* callback instead of polling reactive status. Deliberately NOT fired
|
|
1281
|
+
* from stop(): an explicit stop (barge-in, toggle off) must not re-arm
|
|
1282
|
+
* anything by itself.
|
|
1283
|
+
*/
|
|
1284
|
+
onQueueDrained = null;
|
|
1285
|
+
activeAudio = null;
|
|
1286
|
+
activeAudioUrl = null;
|
|
1287
|
+
speakQueue = [];
|
|
1288
|
+
busy = false;
|
|
1289
|
+
generation = 0;
|
|
1290
|
+
overflowNoticed = false;
|
|
1291
|
+
pendingAutoplayAudio = null;
|
|
1292
|
+
pendingAutoplayUrl = null;
|
|
1293
|
+
constructor(host) {
|
|
1294
|
+
this.host = host;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Play a 1-frame silent AudioBuffer through a transient AudioContext to
|
|
1298
|
+
* register the current user gesture with the browser's autoplay policy.
|
|
1299
|
+
* Safe to call repeatedly; failures are swallowed.
|
|
1300
|
+
*/
|
|
1301
|
+
primeForAutoplay() {
|
|
1302
|
+
if (typeof window === "undefined") return;
|
|
1303
|
+
const Ctor = window.AudioContext ?? window.webkitAudioContext;
|
|
1304
|
+
if (!Ctor) return;
|
|
1305
|
+
try {
|
|
1306
|
+
const ctx = new Ctor();
|
|
1307
|
+
const buffer = ctx.createBuffer(1, 1, 22050);
|
|
1308
|
+
const source = ctx.createBufferSource();
|
|
1309
|
+
source.buffer = buffer;
|
|
1310
|
+
source.connect(ctx.destination);
|
|
1311
|
+
source.start(0);
|
|
1312
|
+
setTimeout(() => {
|
|
1313
|
+
ctx.close().catch(() => {});
|
|
1314
|
+
}, 100);
|
|
1315
|
+
} catch {}
|
|
1316
|
+
}
|
|
1317
|
+
teardownActiveAudio() {
|
|
1318
|
+
if (this.activeAudio) {
|
|
1319
|
+
this.activeAudio.onerror = null;
|
|
1320
|
+
this.activeAudio.onended = null;
|
|
1321
|
+
try {
|
|
1322
|
+
this.activeAudio.pause();
|
|
1323
|
+
} catch {}
|
|
1324
|
+
this.activeAudio.src = "";
|
|
1325
|
+
this.activeAudio = null;
|
|
1326
|
+
}
|
|
1327
|
+
if (this.activeAudioUrl) {
|
|
1328
|
+
URL.revokeObjectURL(this.activeAudioUrl);
|
|
1329
|
+
this.activeAudioUrl = null;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
teardownPendingAutoplay() {
|
|
1333
|
+
if (this.pendingAutoplayAudio) {
|
|
1334
|
+
this.pendingAutoplayAudio.onerror = null;
|
|
1335
|
+
this.pendingAutoplayAudio.onended = null;
|
|
1336
|
+
try {
|
|
1337
|
+
this.pendingAutoplayAudio.pause();
|
|
1338
|
+
} catch {}
|
|
1339
|
+
this.pendingAutoplayAudio.src = "";
|
|
1340
|
+
this.pendingAutoplayAudio = null;
|
|
1341
|
+
}
|
|
1342
|
+
if (this.pendingAutoplayUrl) {
|
|
1343
|
+
URL.revokeObjectURL(this.pendingAutoplayUrl);
|
|
1344
|
+
this.pendingAutoplayUrl = null;
|
|
1345
|
+
}
|
|
1346
|
+
this.host.autoplayBlocked = false;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* User clicked the "click to resume" banner — promote the stashed audio
|
|
1350
|
+
* to the active slot and play. Called from VoiceControl's banner button;
|
|
1351
|
+
* the click on the button itself satisfies the autoplay-policy gesture
|
|
1352
|
+
* requirement.
|
|
1353
|
+
*/
|
|
1354
|
+
resume() {
|
|
1355
|
+
const a = this.pendingAutoplayAudio;
|
|
1356
|
+
if (!a) {
|
|
1357
|
+
this.host.autoplayBlocked = false;
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
this.host.autoplayBlocked = false;
|
|
1361
|
+
this.host.errorMessage = "";
|
|
1362
|
+
this.host.status = "speaking";
|
|
1363
|
+
this.busy = true;
|
|
1364
|
+
this.activeAudio = a;
|
|
1365
|
+
this.activeAudioUrl = this.pendingAutoplayUrl;
|
|
1366
|
+
this.pendingAutoplayAudio = null;
|
|
1367
|
+
this.pendingAutoplayUrl = null;
|
|
1368
|
+
a.play().catch(() => {
|
|
1369
|
+
this.host.status = "idle";
|
|
1370
|
+
this.host.errorMessage = "Audio playback failed.";
|
|
1371
|
+
this.teardownActiveAudio();
|
|
1372
|
+
this.drainNext();
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Read text aloud. Tries server-side TTS via the provider transport first (when the
|
|
1377
|
+
* configured engine is openpalm-voice or remote); falls back to browser
|
|
1378
|
+
* speech synthesis. Silent no-op if neither path is available.
|
|
1379
|
+
*
|
|
1380
|
+
* If a previous utterance is still synthesizing, playing, or stashed
|
|
1381
|
+
* behind an autoplay block, queues this one (FIFO, cap 20) instead of
|
|
1382
|
+
* cutting it off mid-sentence.
|
|
1383
|
+
*/
|
|
1384
|
+
async speak(text) {
|
|
1385
|
+
if (typeof window === "undefined" || !text.trim()) return;
|
|
1386
|
+
if (this.busy) {
|
|
1387
|
+
this.speakQueue.push(text);
|
|
1388
|
+
let dropped = 0;
|
|
1389
|
+
while (this.speakQueue.length > SPEAK_QUEUE_MAX) {
|
|
1390
|
+
this.speakQueue.shift();
|
|
1391
|
+
dropped += 1;
|
|
1392
|
+
}
|
|
1393
|
+
if (dropped > 0 && !this.overflowNoticed) {
|
|
1394
|
+
this.overflowNoticed = true;
|
|
1395
|
+
notifications.push("info", `Skipped ${dropped} spoken ${dropped === 1 ? "reply" : "replies"} — too much overlap. Lower the auto-speak chat rate.`);
|
|
1396
|
+
}
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
await this.playOne(text);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Advance the pump: play the next queued utterance, or release the busy
|
|
1403
|
+
* flag so a future speak() can start a fresh burst. Every terminal path
|
|
1404
|
+
* of an utterance must land here (or hold `busy` deliberately, as the
|
|
1405
|
+
* autoplay-block stash does).
|
|
1406
|
+
*/
|
|
1407
|
+
drainNext() {
|
|
1408
|
+
const next = this.speakQueue.shift();
|
|
1409
|
+
if (next) this.playOne(next);
|
|
1410
|
+
else {
|
|
1411
|
+
this.busy = false;
|
|
1412
|
+
this.overflowNoticed = false;
|
|
1413
|
+
this.onQueueDrained?.();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/** Internal: actually trigger the audio for one text chunk. */
|
|
1417
|
+
async playOne(text) {
|
|
1418
|
+
if (typeof window === "undefined") return;
|
|
1419
|
+
this.busy = true;
|
|
1420
|
+
const gen = this.generation;
|
|
1421
|
+
const speakableText = toSpeakableText(text);
|
|
1422
|
+
if (!speakableText) {
|
|
1423
|
+
this.drainNext();
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
this.teardownActiveAudio();
|
|
1427
|
+
if ("speechSynthesis" in window) window.speechSynthesis.cancel();
|
|
1428
|
+
this.host.errorMessage = "";
|
|
1429
|
+
const engine = this.host.ttsEngine;
|
|
1430
|
+
const useServer = engine === "openpalm-voice" || engine === "remote";
|
|
1431
|
+
if (useServer) {
|
|
1432
|
+
let res;
|
|
1433
|
+
try {
|
|
1434
|
+
res = await synthesize(speakableText);
|
|
1435
|
+
} catch {}
|
|
1436
|
+
if (gen !== this.generation) return;
|
|
1437
|
+
if (res?.ok && res.headers.get("content-type")?.startsWith("audio/")) {
|
|
1438
|
+
const blob = await res.blob();
|
|
1439
|
+
if (gen !== this.generation) return;
|
|
1440
|
+
const url = URL.createObjectURL(blob);
|
|
1441
|
+
const audio = new Audio(url);
|
|
1442
|
+
this.activeAudio = audio;
|
|
1443
|
+
this.activeAudioUrl = url;
|
|
1444
|
+
audio.onended = () => {
|
|
1445
|
+
this.host.status = "idle";
|
|
1446
|
+
this.teardownActiveAudio();
|
|
1447
|
+
this.drainNext();
|
|
1448
|
+
};
|
|
1449
|
+
audio.onerror = () => {
|
|
1450
|
+
this.host.status = "idle";
|
|
1451
|
+
this.host.errorMessage = "Audio playback failed.";
|
|
1452
|
+
this.teardownActiveAudio();
|
|
1453
|
+
this.drainNext();
|
|
1454
|
+
};
|
|
1455
|
+
this.host.status = "speaking";
|
|
1456
|
+
try {
|
|
1457
|
+
await audio.play();
|
|
1458
|
+
} catch {
|
|
1459
|
+
this.host.status = "idle";
|
|
1460
|
+
this.pendingAutoplayAudio = audio;
|
|
1461
|
+
this.pendingAutoplayUrl = url;
|
|
1462
|
+
this.activeAudio = null;
|
|
1463
|
+
this.activeAudioUrl = null;
|
|
1464
|
+
this.host.autoplayBlocked = true;
|
|
1465
|
+
}
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
if (res && !res.ok) {
|
|
1469
|
+
const errMsg = await this.extractSpeakError(res);
|
|
1470
|
+
this.host.errorMessage = errMsg;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (!("speechSynthesis" in window)) {
|
|
1474
|
+
this.drainNext();
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (useServer) this.host.errorMessage = "";
|
|
1478
|
+
const utterance = new SpeechSynthesisUtterance(speakableText);
|
|
1479
|
+
utterance.onstart = () => {
|
|
1480
|
+
if (gen !== this.generation) return;
|
|
1481
|
+
this.host.status = "speaking";
|
|
1482
|
+
};
|
|
1483
|
+
utterance.onend = () => {
|
|
1484
|
+
if (gen !== this.generation) return;
|
|
1485
|
+
this.host.status = "idle";
|
|
1486
|
+
this.drainNext();
|
|
1487
|
+
};
|
|
1488
|
+
utterance.onerror = () => {
|
|
1489
|
+
if (gen !== this.generation) return;
|
|
1490
|
+
this.host.status = "idle";
|
|
1491
|
+
this.drainNext();
|
|
1492
|
+
};
|
|
1493
|
+
window.speechSynthesis.speak(utterance);
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Convert a non-OK synthesis response into a human-readable string.
|
|
1497
|
+
* Recognises the /voice pass-through's structured codes; any other 5xx
|
|
1498
|
+
* (a provider whose model is still loading) reads as "warming up".
|
|
1499
|
+
*/
|
|
1500
|
+
async extractSpeakError(res) {
|
|
1501
|
+
let code;
|
|
1502
|
+
try {
|
|
1503
|
+
const data = await res.clone().json();
|
|
1504
|
+
if (typeof data.error === "string") code = data.error;
|
|
1505
|
+
} catch {}
|
|
1506
|
+
if (code === "voice_unavailable") return "OpenPalm Voice is not enabled on this host.";
|
|
1507
|
+
if (code === "voice_unreachable" || res.status >= 500) return "Voice engine is warming up — try again in a moment.";
|
|
1508
|
+
return `TTS failed (HTTP ${res.status}).`;
|
|
1509
|
+
}
|
|
1510
|
+
/** Cancel speech synthesis. Drops the entire queue. */
|
|
1511
|
+
stop() {
|
|
1512
|
+
this.speakQueue.length = 0;
|
|
1513
|
+
this.busy = false;
|
|
1514
|
+
this.generation += 1;
|
|
1515
|
+
this.overflowNoticed = false;
|
|
1516
|
+
this.teardownPendingAutoplay();
|
|
1517
|
+
this.teardownActiveAudio();
|
|
1518
|
+
if (typeof window !== "undefined" && "speechSynthesis" in window) window.speechSynthesis.cancel();
|
|
1519
|
+
this.host.status = "idle";
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
var VoiceState = class {
|
|
1523
|
+
status = "idle";
|
|
1524
|
+
/** True when the configured STT engine is actually usable from this browser. */
|
|
1525
|
+
sttSupported = false;
|
|
1526
|
+
ttsSupported = false;
|
|
1527
|
+
errorMessage = "";
|
|
1528
|
+
/** Partial transcript text while browser STT is mid-utterance. Cleared on stop/error. */
|
|
1529
|
+
interimTranscript = "";
|
|
1530
|
+
/** Active engine resolved from the client-owned voice settings. */
|
|
1531
|
+
sttEngine = "disabled";
|
|
1532
|
+
ttsEngine = "disabled";
|
|
1533
|
+
/** Optional language hint (forwarded to the STT provider). */
|
|
1534
|
+
sttLanguage = "";
|
|
1535
|
+
/** Global toggle: when true, assistant chat replies are spoken automatically. */
|
|
1536
|
+
ttsAutoEnabled = false;
|
|
1537
|
+
/**
|
|
1538
|
+
* True when an audio.play() was rejected by the browser's autoplay
|
|
1539
|
+
* policy and we have a pending utterance waiting for a user gesture.
|
|
1540
|
+
* The chat UI renders a small "click to resume" banner; clicking it
|
|
1541
|
+
* calls resumeAutoplay() to actually play.
|
|
1542
|
+
*/
|
|
1543
|
+
autoplayBlocked = false;
|
|
1544
|
+
/**
|
|
1545
|
+
* True while hands-free conversation mode owns the mic (the chat page's
|
|
1546
|
+
* listen → deliver → speak → listen loop). `status` keeps cycling
|
|
1547
|
+
* through the normal VoiceStatus values while this is set.
|
|
1548
|
+
*/
|
|
1549
|
+
conversationActive = false;
|
|
1550
|
+
/**
|
|
1551
|
+
* Set when the configured STT engine is technically present in the
|
|
1552
|
+
* window but known to fail at runtime on this UA (e.g. iOS Safari
|
|
1553
|
+
* exposes SpeechRecognition but `start()` immediately errors with
|
|
1554
|
+
* `service-not-allowed`). The Voice settings tab uses this to hide or
|
|
1555
|
+
* disable the Browser STT card with the reason as a tooltip.
|
|
1556
|
+
*/
|
|
1557
|
+
browserSttUnsupportedReason = "";
|
|
1558
|
+
};
|
|
1559
|
+
var voiceState = new VoiceState();
|
|
1560
|
+
/**
|
|
1561
|
+
* The imperative TTS audio engine (speak queue, blob-URL lifecycle,
|
|
1562
|
+
* autoplay fallback, playOne). The reactive store composes it and
|
|
1563
|
+
* delegates the exported speak/resume/stop wrappers below to it; the
|
|
1564
|
+
* controller mutates `voiceState`'s reactive fields in place.
|
|
1565
|
+
*/
|
|
1566
|
+
var audioPlayback = new AudioPlaybackController(voiceState);
|
|
1567
|
+
audioPlayback.onQueueDrained = () => {
|
|
1568
|
+
if (voiceState.conversationActive && voiceState.status === "idle") voiceState.status = "recording";
|
|
1569
|
+
};
|
|
1570
|
+
/** Exit conversation mode and tear down the listening loop (whichever engine). */
|
|
1571
|
+
function stopConversation() {
|
|
1572
|
+
if (!voiceState.conversationActive) return;
|
|
1573
|
+
voiceState.conversationActive = false;
|
|
1574
|
+
voiceState.interimTranscript = "";
|
|
1575
|
+
if (voiceState.status === "recording" || voiceState.status === "transcribing") voiceState.status = "idle";
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Read text aloud. Tries server-side TTS via the provider transport first (when the
|
|
1579
|
+
* configured engine is openpalm-voice or remote); falls back to browser
|
|
1580
|
+
* speech synthesis. Silent no-op if neither path is available.
|
|
1581
|
+
*
|
|
1582
|
+
* If a previous utterance is still playing, queues this one (FIFO, cap 20)
|
|
1583
|
+
* instead of cutting it off mid-sentence.
|
|
1584
|
+
*/
|
|
1585
|
+
function speakText(text) {
|
|
1586
|
+
return audioPlayback.speak(text);
|
|
1587
|
+
}
|
|
1588
|
+
/** Cancel speech synthesis. Drops the entire queue. */
|
|
1589
|
+
function stopSpeaking() {
|
|
1590
|
+
audioPlayback.stop();
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Tear down per-component voice resources on unmount. Only cancels
|
|
1594
|
+
* any in-flight recording / recognition (including the conversation
|
|
1595
|
+
* loop).
|
|
1596
|
+
*
|
|
1597
|
+
* Deliberately does NOT cancel `speechSynthesis` — that queue is a
|
|
1598
|
+
* window-level singleton and the user's auto-TTS toggle is persistent,
|
|
1599
|
+
* so a page navigation must not interrupt the assistant mid-utterance.
|
|
1600
|
+
* Explicit user actions (logout, mic click, toggle off) call
|
|
1601
|
+
* `stopSpeaking` directly.
|
|
1602
|
+
*/
|
|
1603
|
+
function destroyVoice() {
|
|
1604
|
+
stopConversation();
|
|
1605
|
+
voiceState.interimTranscript = "";
|
|
1606
|
+
voiceState.status = "idle";
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
export { getTransport as a, speakText as b, stopSpeaking as c, destroyVoice as d, subscribeSessionEvents as e, getConnectionStore as g, isLoopbackHost as i, loadRuntimeConfig as l, notifications as n, randomId as r, setActiveConnection as s, themeService as t, voiceState as v };
|
|
1610
|
+
//# sourceMappingURL=voice-state.svelte-DU9g_xEh.js.map
|