@openchambery/web 2.1.6-beta.5 → 2.1.6-beta.6

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.
Files changed (78) hide show
  1. package/dist/assets/{AgentAvatar-Bq0x3CB4.js → AgentAvatar-Ah1nMt7y.js} +1 -1
  2. package/dist/assets/{AssistantView-D4k8Z-_-.js → AssistantView-6x876MsF.js} +3 -3
  3. package/dist/assets/{DiagramView-NLlU9san.js → DiagramView-DrZigobH.js} +1 -1
  4. package/dist/assets/{FadeInOnReveal-xE6S4uOf.js → FadeInOnReveal-BssISEuf.js} +1 -1
  5. package/dist/assets/{Icon-uG6OVJ55.js → Icon-DewMAW7N.js} +1 -1
  6. package/dist/assets/{ImageLoadingPlaceholder-C6Wg5C84.js → ImageLoadingPlaceholder-izjx-F-t.js} +1 -1
  7. package/dist/assets/{LoadingImage-DGsgPXji.js → LoadingImage-CCj65iXL.js} +1 -1
  8. package/dist/assets/{MarkdownRenderer-omYT-V2o.js → MarkdownRenderer-BgEm-_yw.js} +2 -2
  9. package/dist/assets/{MarkdownRendererImpl-jykBJkBt.js → MarkdownRendererImpl-pdTb-oQC.js} +1 -1
  10. package/dist/assets/{MarkstreamRendererImpl-Dsev1mCz.js → MarkstreamRendererImpl-OWmeMYDf.js} +1 -1
  11. package/dist/assets/{MobileDetailNavigation-CtU7Vwwy.js → MobileDetailNavigation-CuRszZM_.js} +1 -1
  12. package/dist/assets/{MobileShareBridge-3t1HF0B2.js → MobileShareBridge-CCoT00Ew.js} +1 -1
  13. package/dist/assets/{MobileTabPageHeader-D3_SRhh_.js → MobileTabPageHeader-DViceEtF.js} +1 -1
  14. package/dist/assets/{MultiRunWindow-DKFpsr-I.js → MultiRunWindow-D-VEzIPz.js} +1 -1
  15. package/dist/assets/{OnboardingScreen-5J8UYfpK.js → OnboardingScreen-BjikXNcy.js} +1 -1
  16. package/dist/assets/{OverlayScrollbar-DfUTPyw5.js → OverlayScrollbar-C24-p2ns.js} +1 -1
  17. package/dist/assets/{SettingsView-BkgklJla.js → SettingsView-F0rWygmN.js} +2 -2
  18. package/dist/assets/{SettingsWindow-CVa_AxkA.js → SettingsWindow-Ddc2m3jz.js} +1 -1
  19. package/dist/assets/{TerminalView-jYx7qdQp.js → TerminalView-1CAr8eS7.js} +1 -1
  20. package/dist/assets/{ToolOutputDialog-C1ilYJNm.js → ToolOutputDialog-Da3i_YEq.js} +1 -1
  21. package/dist/assets/{appThemeRegistry-DgMiqMEo.js → appThemeRegistry-DWSGe1yp.js} +1 -1
  22. package/dist/assets/{button-CsMXhErP.js → button-kJNzMb1G.js} +1 -1
  23. package/dist/assets/{collapsible-BtX_begd.js → collapsible-MInYZ7dA.js} +1 -1
  24. package/dist/assets/{context-menu-Bw39s9Z_.js → context-menu-BYq0LM7H.js} +1 -1
  25. package/dist/assets/{diagram-mYVe76Qf.js → diagram-BDSPghN-.js} +1 -1
  26. package/dist/assets/dropdown-menu.styles-BXvVFEkh.js +1 -0
  27. package/dist/assets/{es-CmsZ1faR.js → es-CLTWsqOO.js} +1 -1
  28. package/dist/assets/{fileMentionTouchSelection-CX0D5p6c.js → fileMentionTouchSelection-pm-K3d1E.js} +1 -1
  29. package/dist/assets/{fr-B2GXF-Ue.js → fr-BNyfwida.js} +1 -1
  30. package/dist/assets/{gitApi-DV3PvsZX.js → gitApi-DnJ7cOV6.js} +1 -1
  31. package/dist/assets/{input-BsPul3GT.js → input-02eTmC8w.js} +1 -1
  32. package/dist/assets/{insertionBoundaries-Dbc4ooMQ.js → insertionBoundaries-DZE8b22Y.js} +1 -1
  33. package/dist/assets/{ja-1_Xg7dxX.js → ja-D7MYkMo9.js} +1 -1
  34. package/dist/assets/{ko-DAxhN5z4.js → ko-CFNLrmLm.js} +1 -1
  35. package/dist/assets/{main-CFCPRzI_.js → main-BDgDYAf5.js} +4 -4
  36. package/dist/assets/{main-BLVIU-c3.js → main-BET9mrfn.js} +2 -2
  37. package/dist/assets/{markdownHeightCache-Pr8PsxpU.js → markdownHeightCache-CAVQhVdd.js} +1 -1
  38. package/dist/assets/{mcpQueries-ClqC1UPF.js → mcpQueries-BHUpFfJN.js} +1 -1
  39. package/dist/assets/{miniChat-CXuRsamJ.js → miniChat-Dgtznflo.js} +2 -2
  40. package/dist/assets/{mobile-D2So3pLV.js → mobile-BU1sv3Iy.js} +2 -2
  41. package/dist/assets/{multirun-BEsZpZwA.js → multirun-Con0kqvH.js} +1 -1
  42. package/dist/assets/{pl-wKtP4U2Y.js → pl-aKpW58gt.js} +1 -1
  43. package/dist/assets/{projectMeta-B5oNzoLn.js → projectMeta-DjqbA9mt.js} +1 -1
  44. package/dist/assets/{pt-BR-kKiTnPvR.js → pt-BR-MliQk6-s.js} +1 -1
  45. package/dist/assets/{rasterizePng-BJfBaYfr.js → rasterizePng-DCmIQQsI.js} +1 -1
  46. package/dist/assets/{renderElectronMiniChatApp-Cz3jK33M.js → renderElectronMiniChatApp-DJpkkxuR.js} +2 -2
  47. package/dist/assets/{renderMobileApp-g2X04N6I.js → renderMobileApp-Drm-pwtN.js} +4 -4
  48. package/dist/assets/{runtimeConfig-ChG-7Oj4.js → runtimeConfig-CPssH7z1.js} +1 -1
  49. package/dist/assets/{runtimeEndpointReset-Bym_kBom.js → runtimeEndpointReset-DWOa8aee.js} +1 -1
  50. package/dist/assets/{sessionChangeSummary-BdA1CAwI.js → sessionChangeSummary-DIpbRdgy.js} +1 -1
  51. package/dist/assets/{sessionLookup-CnIp-vIm.js → sessionLookup-DrhEF54c.js} +1 -1
  52. package/dist/assets/{sortable-tabs-strip-CRakLh3E.js → sortable-tabs-strip-DSGwZHAT.js} +1 -1
  53. package/dist/assets/{speechSettingsQueries-DUSsFC2o.js → speechSettingsQueries-Djxu09uM.js} +1 -1
  54. package/dist/assets/{store-BIZ0WqXh.js → store-D5OeM4wB.js} +2 -2
  55. package/dist/assets/{textarea-C0sYqMBG.js → textarea-yLPH5NYp.js} +1 -1
  56. package/dist/assets/{tooltip-BjLeu5Rm.js → tooltip-B3eYZnNS.js} +1 -1
  57. package/dist/assets/{uk-Du0sQBeG.js → uk-8Ovv1FvC.js} +1 -1
  58. package/dist/assets/{useAppFontEffects-CEQWKI_5.js → useAppFontEffects-CIqu60Nx.js} +5 -5
  59. package/dist/assets/{useDesktopWindowControlsLayout-DistjXOu.js → useDesktopWindowControlsLayout-DA98Miq8.js} +1 -1
  60. package/dist/assets/{useEffectiveDirectory-BZMMnK6z.js → useEffectiveDirectory-I_mgEcd7.js} +1 -1
  61. package/dist/assets/{useMobileNavigationStore-DMubNKfy.js → useMobileNavigationStore-BeUkwRP3.js} +6 -6
  62. package/dist/assets/{useWorkerHighlightedLines-BaJ2ZuGC.js → useWorkerHighlightedLines-DS0imjh2.js} +1 -1
  63. package/dist/assets/{zh-CN-CLZ_Iqof.js → zh-CN-DYC-d6-i.js} +1 -1
  64. package/dist/assets/{zh-TW-BNb1FeSs.js → zh-TW-OPu9_qbz.js} +1 -1
  65. package/dist/index.html +2 -2
  66. package/dist/mini-chat.html +2 -2
  67. package/dist/mobile.html +2 -2
  68. package/package.json +1 -1
  69. package/server/lib/dictation/DOCUMENTATION.md +3 -1
  70. package/server/lib/dictation/service.js +1 -0
  71. package/server/lib/dictation/volcengine-session.js +5 -2
  72. package/server/lib/dictation/volcengine-session.test.js +8 -2
  73. package/server/lib/tts/DOCUMENTATION.md +11 -0
  74. package/server/lib/tts/direct-session.js +1 -1
  75. package/server/lib/tts/direct-session.test.js +21 -0
  76. package/server/lib/tts/direct-speech.test.js +20 -0
  77. package/server/lib/tts/settings.js +2 -1
  78. package/dist/assets/dropdown-menu.styles-D_IF_a03.js +0 -1
package/dist/mobile.html CHANGED
@@ -63,7 +63,7 @@
63
63
  })();
64
64
  </script>
65
65
 
66
- <script type="module" crossorigin src="/assets/mobile-D2So3pLV.js"></script>
66
+ <script type="module" crossorigin src="/assets/mobile-BU1sv3Iy.js"></script>
67
67
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
68
68
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-C8w0nlKy.js">
69
69
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-3ad3yzM7.js">
@@ -83,7 +83,7 @@
83
83
  <link rel="modulepreload" crossorigin href="/assets/terminalApi-BnjU_lmF.js">
84
84
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
85
85
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
86
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-ChG-7Oj4.js">
86
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CPssH7z1.js">
87
87
  <link rel="stylesheet" crossorigin href="/assets/src-CBUXJkw-.css">
88
88
  </head>
89
89
  <body class="h-full bg-background text-foreground">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchambery/web",
3
- "version": "2.1.6-beta.5",
3
+ "version": "2.1.6-beta.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
@@ -74,7 +74,9 @@ provider, credentials, resource, language and local model from Host speech setti
74
74
  at session start (`../tts/settings.js`). Legacy `options` are ignored, including
75
75
  provider overrides. Saving during a session affects the next session; it cannot
76
76
  change an active upstream connection. Volcengine uses automatic language recognition
77
- and does not receive the Whisper language hint. Session copies are released on
77
+ and snapshots the optional Host `asrContext` description into `request.corpus.context`
78
+ as a serialized `dialog_ctx` object; empty text omits the corpus entirely. It
79
+ does not receive the Whisper language hint. Session copies are released on
78
80
  close, and credentials are never logged.
79
81
 
80
82
  ## Invariants
@@ -89,6 +89,7 @@ export function createDictationService({ modelsDir }) {
89
89
  const session = new VolcengineTranscriptionSession({
90
90
  apiKey: saved.apiKey,
91
91
  resourceId: saved.asrResourceId,
92
+ asrContext: saved.asrContext,
92
93
  });
93
94
  try {
94
95
  await session.connect();
@@ -44,10 +44,11 @@ function decodeFrame(input) {
44
44
  }
45
45
 
46
46
  export class VolcengineTranscriptionSession extends EventEmitter {
47
- constructor({ apiKey, resourceId = 'volc.seedasr.sauc.duration' }, { createSocket = (url, options) => new WebSocket(url, options) } = {}) {
47
+ constructor({ apiKey, resourceId = 'volc.seedasr.sauc.duration', asrContext = '' }, { createSocket = (url, options) => new WebSocket(url, options) } = {}) {
48
48
  super();
49
49
  this.apiKey = apiKey;
50
50
  this.resourceId = resourceId;
51
+ this.asrContext = asrContext;
51
52
  this.createSocket = createSocket;
52
53
  this.requiredSampleRate = 16000;
53
54
  this.autoCommitSeconds = 0;
@@ -91,7 +92,9 @@ export class VolcengineTranscriptionSession extends EventEmitter {
91
92
  socket.send(encodeFrame(1, Buffer.from(JSON.stringify({
92
93
  user: { uid: 'openchamber' },
93
94
  audio: { format: 'pcm', codec: 'raw', rate: 16000, bits: 16, channel: 1 },
94
- request: { model_name: 'bigmodel', result_type: 'full', enable_itn: true, enable_punc: true, enable_nonstream: true, show_utterances: true },
95
+ request: { model_name: 'bigmodel', result_type: 'full', enable_itn: true, enable_punc: true, enable_nonstream: true, show_utterances: true,
96
+ ...(this.asrContext.trim() ? { corpus: { context: JSON.stringify({ context_type: 'dialog_ctx', context_data: [{ text: this.asrContext.trim() }] }) } } : {}),
97
+ },
95
98
  }))), (error) => { if (error) fail(new Error('Volcengine ASR request could not be sent')); });
96
99
  });
97
100
  socket.on('message', (data) => {
@@ -17,7 +17,7 @@ const response = (text, last = false) => {
17
17
  return Buffer.concat([header, fields, payload]);
18
18
  };
19
19
 
20
- async function fixture({ fail = false, terminalWithoutText = false, disconnectOnFinish = false } = {}) {
20
+ async function fixture({ fail = false, terminalWithoutText = false, disconnectOnFinish = false, asrContext = '' } = {}) {
21
21
  const server = createServer();
22
22
  const wss = new WebSocketServer({ server });
23
23
  const frames = [];
@@ -39,7 +39,7 @@ async function fixture({ fail = false, terminalWithoutText = false, disconnectOn
39
39
  await new Promise((resolve) => wss.close(resolve));
40
40
  await new Promise((resolve) => server.close(resolve));
41
41
  });
42
- const session = new VolcengineTranscriptionSession({ apiKey: 'test-key' }, {
42
+ const session = new VolcengineTranscriptionSession({ apiKey: 'test-key', asrContext }, {
43
43
  createSocket: (url, options) => {
44
44
  expect(url).toBe('wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async');
45
45
  return new WebSocket(`ws://127.0.0.1:${server.address().port}`, options);
@@ -51,6 +51,12 @@ async function fixture({ fail = false, terminalWithoutText = false, disconnectOn
51
51
  }
52
52
 
53
53
  describe('Volcengine streaming dictation', () => {
54
+ it.each(['', 'Mixed Mandarin and English.\nReact and "OpenCode".'])('sends configured context and omits empty context: %s', async (asrContext) => {
55
+ const { frames } = await fixture({ asrContext });
56
+ const { request } = JSON.parse(frames[0].payload);
57
+ if (asrContext) expect(JSON.parse(request.corpus.context)).toEqual({ context_type: 'dialog_ctx', context_data: [{ text: asrContext }] });
58
+ else expect(request).not.toHaveProperty('corpus');
59
+ });
54
60
  it('retains the last full transcript when the successful terminal frame omits text', async () => {
55
61
  const { session } = await fixture({ terminalWithoutText: true });
56
62
  const partial = new Promise((resolve) => session.once('transcript', resolve));
@@ -129,6 +129,17 @@ unified file is first saved. Each section saves its own memory-only draft. A
129
129
  successful write emits `openchamber:speech-settings-changed`; startup and reconnect
130
130
  also fetch the authoritative snapshot without requiring Settings to be opened.
131
131
  Select Volcengine separately for read-aloud and dictation.
132
+ The Volcengine recognition context (`asrContext`) is a Host-owned editable English
133
+ scene description, defaulting to Mandarin/English software-development dictation.
134
+ It accepts multiline text up to 2,000 UTF-16 code units; empty text disables it.
135
+ Missing fields in existing Host files receive the default. New UI clients reading
136
+ older Hosts that omit the field treat it as disabled. Saves affect the next recording.
137
+ Direct ASR tickets snapshot this text; direct and legacy Host streaming requests
138
+ send it as `request.corpus.context`, a JSON string containing `context_type: dialog_ctx`
139
+ and one `context_data` text entry. No conversation history is collected automatically.
140
+ The provider additionally applies its 800-token context limit (not equivalent to
141
+ the local character bound). This setting does not affect TTS or the file ASR API.
142
+ Search terms lead to the stable recognition section without switching providers.
132
143
  Missing keys return HTTP 400, not a server failure.
133
144
  Enable `seed-tts-2.0`, `volc.seedasr.sauc.duration` (live dictation),
134
145
  and optionally `volc.bigasr.auc_turbo` (file API) in the
@@ -31,7 +31,7 @@ export async function createSpeechTicket(settings, kind, signal) {
31
31
  transport: 'volcengine-direct', token, appId: aud[0], expiresAt: exp * 1000, resourceId,
32
32
  ...(kind === 'tts' ? {
33
33
  voice: settings.voice, speed: settings.speechRate, pitch: settings.speechPitch, volume: settings.speechVolume,
34
- } : {}),
34
+ } : { asrContext: settings.asrContext ?? '' }),
35
35
  };
36
36
  }
37
37
 
@@ -5,6 +5,7 @@ import fs from 'node:fs';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { registerTtsRoutes } from './routes.js';
8
+ import { parseSpeechSettings, speechDefaults } from '../../../../ui/src/lib/voice/speechSettings.ts';
8
9
 
9
10
  let directory;
10
11
  let app;
@@ -43,6 +44,26 @@ it('does not mint tickets for other providers, missing keys or malformed session
43
44
  expect(fetch).not.toHaveBeenCalled();
44
45
  });
45
46
 
47
+ it('round-trips context, preserves it across unrelated saves and failed validation, and snapshots only saved ASR context', async () => {
48
+ vi.stubGlobal('fetch', vi.fn(async () => Response.json({ jwt_token: jwt() })));
49
+ const initial = (await request(app).get('/api/voice/settings').expect(200)).body;
50
+ expect(initial.asrContext).toBe(speechDefaults.asrContext);
51
+ const { asrContext: _context, ...legacy } = initial;
52
+ expect(parseSpeechSettings(legacy).asrContext).toBe('');
53
+ const context = 'Mixed Mandarin and English.\nReact, "OpenCode", and APIs.';
54
+ await request(app).put('/api/voice/settings').send({ asrContext: context }).expect(200);
55
+ await request(app).put('/api/voice/settings').send({ speechRate: 1.2 }).expect(200);
56
+ for (const asrContext of [42, null, 'x'.repeat(2001), 'bad\0text']) {
57
+ await request(app).put('/api/voice/settings').send({ asrContext }).expect(400);
58
+ }
59
+ expect((await request(app).get('/api/voice/settings')).body.asrContext).toBe(context);
60
+ expect(JSON.parse(fs.readFileSync(path.join(directory, 'speech/settings.json'), 'utf8')).asrContext).toBe(context);
61
+ expect((await request(app).post('/api/voice/session').send({ kind: 'asr', asrContext: 'ignored' })).body.asrContext).toBe(context);
62
+ expect((await request(app).post('/api/voice/session').send({ kind: 'tts' })).body).not.toHaveProperty('asrContext');
63
+ await request(app).put('/api/voice/settings').send({ asrContext: ' ' }).expect(200);
64
+ expect((await request(app).post('/api/voice/session').send({ kind: 'asr' })).body.asrContext).toBe('');
65
+ });
66
+
46
67
  it('rejects failed, oversized, malformed, expired and overlong tickets without leaking upstream details', async () => {
47
68
  for (const response of [
48
69
  new Response('fixture-key upstream-private', { status: 401 }),
@@ -18,6 +18,7 @@ let sockets;
18
18
  let mode;
19
19
  let audioChunks;
20
20
  let clients;
21
+ let asrRequests;
21
22
  const ticket = () => ({ transport: 'volcengine-direct', token: 'header.payload.signature', appId: 'fixture-app', expiresAt: Date.now() + 300000,
22
23
  resourceId: 'seed-tts-2.0', voice: 'zh_female_vv_uranus_bigtts', speed: 1, pitch: 1.1, volume: 0.8 });
23
24
  const waitFor = async (fn) => { for (let n = 0; n < 200; n++) { if (fn()) return; await new Promise((r) => setTimeout(r, 5)); } throw new Error('Fixture timed out'); };
@@ -40,6 +41,7 @@ function asrResponse(text, final = false) {
40
41
 
41
42
  beforeEach(async () => {
42
43
  urls = []; sockets = []; audioChunks = []; clients = []; mode = 'success'; mocks.identity = 'host-a';
44
+ asrRequests = [];
43
45
  mocks.openRuntimeWebSocket.mockReset();
44
46
  mocks.runtimeFetch.mockReset().mockImplementation(async () => Response.json(ticket()));
45
47
  server = new WebSocketServer({ port: 0, host: '127.0.0.1' }); await once(server, 'listening');
@@ -63,6 +65,8 @@ beforeEach(async () => {
63
65
  if (event === 102 && mode === 'success-close') socket.close();
64
66
  if (event === 102 && mode === 'failure') socket.send(ttsResponse(153, id, { message: 'private details' }));
65
67
  if (event === 102 && mode === 'truncated') socket.close();
68
+ } else if ((bytes[1] >> 4) === 1) {
69
+ asrRequests.push(JSON.parse(bytes.subarray(8).toString()));
66
70
  } else if ((bytes[1] >> 4) === 2) {
67
71
  if (bytes[1] & 2) { if (mode === 'success') socket.send(asrResponse(undefined, true)); else socket.close(); }
68
72
  else { audioChunks.push(bytes.subarray(8)); socket.send(asrResponse(`partial-${audioChunks.length}`)); }
@@ -102,6 +106,15 @@ it('streams 100 ASR chunks in order, publishes live partials, and accepts a term
102
106
  expect(mocks.openRuntimeWebSocket).not.toHaveBeenCalled();
103
107
  });
104
108
 
109
+ it.each([undefined, '', 'Mixed Mandarin and English.\nReact and "OpenCode".'])('sends the ticket context as serialized corpus or omits it: %s', async (asrContext) => {
110
+ mocks.runtimeFetch.mockResolvedValueOnce(Response.json({ ...ticket(), asrContext }));
111
+ const client = new DictationClient(); clients.push(client);
112
+ await client.startDictationStream('context', 'audio/pcm;rate=16000;bits=16');
113
+ await waitFor(() => asrRequests.length === 1);
114
+ if (asrContext) expect(JSON.parse(asrRequests[0].request.corpus.context)).toEqual({ context_type: 'dialog_ctx', context_data: [{ text: asrContext }] });
115
+ else expect(asrRequests[0].request).not.toHaveProperty('corpus');
116
+ });
117
+
105
118
  it.each(['cancel', 'runtime'])('fences a late ticket after %s without opening any socket', async (reason) => {
106
119
  let resolve;
107
120
  mocks.runtimeFetch.mockImplementationOnce(() => new Promise((r) => { resolve = r; }));
@@ -173,3 +186,10 @@ it('rejects stale or expired tickets rather than exposing them to an external so
173
186
  await expect(requestSpeechAudio('测试', new AbortController().signal)).rejects.toThrow('runtime changed');
174
187
  expect(urls).toHaveLength(0);
175
188
  });
189
+
190
+ it.each([123, 'x'.repeat(2001), 'bad\0context'])('rejects malformed ASR context before opening a provider socket', async (asrContext) => {
191
+ mocks.runtimeFetch.mockResolvedValueOnce(Response.json({ ...ticket(), asrContext }));
192
+ const client = new DictationClient(); clients.push(client);
193
+ await expect(client.startDictationStream('invalid', 'audio/pcm;rate=16000;bits=16')).rejects.toThrow('Invalid speech session');
194
+ expect(urls).toHaveLength(0);
195
+ });
@@ -8,6 +8,7 @@ const defaults = {
8
8
  voice: 'zh_female_vv_uranus_bigtts',
9
9
  resourceId: 'seed-tts-2.0',
10
10
  asrResourceId: 'volc.seedasr.sauc.duration',
11
+ asrContext: 'The speaker is discussing software development and technical tasks with an AI assistant. Speech may mix Mandarin Chinese and English within the same sentence. The conversation may cover requirements, system design, implementation, debugging, testing, deployment, and maintenance. Common software engineering terms include alpha, beta, release, stable, version, build, branch, commit, merge, pull request, code review, bug, fix, hotfix, patch, refactor, rollback, deploy, CI/CD, API, SDK, and CLI. Technical terms, product names, acronyms, and code identifiers may be spoken in English within otherwise Chinese sentences.',
11
12
  showMessageTTSButtons: false,
12
13
  voiceProvider: 'browser',
13
14
  speechRate: 1,
@@ -55,7 +56,7 @@ function normalizePatch(value) {
55
56
  if (!Object.hasOwn(value, field)) continue;
56
57
  const input = value[field];
57
58
  if (typeof input !== typeof defaults[field]
58
- || (typeof input === 'string' && (input.length > 4096 || /[\r\n\0]/.test(input) || (requiredStrings.has(field) && !input.trim())))
59
+ || (typeof input === 'string' && (input.length > (field === 'asrContext' ? 2000 : 4096) || (field === 'asrContext' ? /\0/ : /[\r\n\0]/).test(input) || (requiredStrings.has(field) && !input.trim())))
59
60
  || (enums[field] && !enums[field].includes(input))
60
61
  || (typeof input === 'number' && (!Number.isFinite(input) || input < ranges[field][0] || input > ranges[field][1]))) {
61
62
  throw new TypeError('Invalid speech settings');
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-hePW80VL.js";import{p as t}from"./runtime-auth-C8w0nlKy.js";import{c as n,f as r,i}from"./runtime-switch-3ad3yzM7.js";import{n as a}from"./runtime-fetch-QCWYlAx0.js";import{it as o}from"./vendor-base-ui-CdzJ6My_.js";import{St as s}from"./platform-z2erC6Bs.js";import{n as c}from"./vendor-react-BP2cRVqX.js";import{n as l}from"./vendor-reactuses-core-BpfIZ1tg.js";import{f as u}from"./button-CsMXhErP.js";var d=e(o(),1);async function f(e,t){let n=i(),r=await a(`/api/voice/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({kind:e}),signal:AbortSignal.any([t,AbortSignal.timeout(2e4)])});if(t.throwIfAborted(),n!==i())throw Error(`Speech runtime changed`);if(r.status===404||r.ok&&r.headers.get(`content-type`)?.includes(`text/html`))return await r.body?.cancel(),null;if(!r.ok)throw await r.body?.cancel(),Error(`Unable to create speech session`);let o=await r.json();if(t.throwIfAborted(),n!==i())throw Error(`Speech runtime changed`);if(o?.transport===`host`)return null;if(o?.transport!==`volcengine-direct`||typeof o.token!=`string`||o.token.length>16384||!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(o.token)||typeof o.appId!=`string`||!/^[A-Za-z0-9_-]{1,128}$/.test(o.appId)||typeof o.resourceId!=`string`||!/^[A-Za-z0-9._-]{1,128}$/.test(o.resourceId)||!Number.isFinite(o.expiresAt)||o.expiresAt<=Date.now()+5e3||o.expiresAt>Date.now()+36e4||e===`tts`&&(typeof o.voice!=`string`||!/^[A-Za-z0-9_-]{1,200}$/.test(o.voice)||!Number.isFinite(o.speed)||o.speed<.5||o.speed>2||!Number.isFinite(o.pitch)||o.pitch<.5||o.pitch>2||!Number.isFinite(o.volume)||o.volume<0||o.volume>1))throw Error(`Invalid speech session`);return o}function p(e,t){let n=new URL(t===`asr`?`wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async`:`wss://openspeech.bytedance.com/api/v3/tts/bidirection`);return n.search=new URLSearchParams({api_app_key:e.appId,api_access_key:`Jwt; ${e.token}`,api_resource_id:e.resourceId}).toString(),n.toString()}var m=new TextEncoder,h=new TextDecoder,g=1048576;function _(e,t=!1,n=!1){let r=new Uint8Array(8+e.length);return r.set([17,(t?2:1)<<4|(n?2:0),t?0:16,0]),new DataView(r.buffer).setUint32(4,e.length),r.set(e,8),r}function v(e,t=``,n={}){let r=m.encode(t),i=m.encode(JSON.stringify(n)),a=new Uint8Array(12+i.length+(t?4+r.length:0));a.set([17,20,16,0]);let o=new DataView(a.buffer);o.setInt32(4,e);let s=8;return t&&(o.setUint32(s,r.length),a.set(r,s+4),s+=4+r.length),o.setUint32(s,i.length),a.set(i,s+4),a}async function y(e,t){if(e.byteLength<8||e.byteLength>g)throw Error(`Invalid speech frame size`);let n=new Uint8Array(e),r=new DataView(e),i=n[1]>>4,a=n[1]&15,o=n[2]&15,s=(n[0]&15)*4;if(n[0]>>4!=1||s<4||s>n.length-4)throw Error(`Invalid speech frame`);let c=()=>{if(s+4>n.length)throw Error(`Truncated speech frame`);let e=r.getUint32(s);return s+=4,e};if(i===15)throw Error(`Speech provider failed (code ${c()})`);if(i!==9&&(t!==`tts`||i!==11))throw Error(`Unexpected speech frame`);a&1&&c();let l=0,u=``;if(a&4&&(l=c(),t===`tts`)){let e=c();if(e>256||s+e>n.length)throw Error(`Invalid speech session ID`);u=h.decode(n.subarray(s,s+e)),s+=e}let d=c();if(s+d!==n.length)throw Error(`Truncated speech payload`);let f=n.slice(s);if(o===1){let e=new Blob([f]).stream().pipeThrough(new DecompressionStream(`gzip`)).getReader(),t=[],n=0;try{for(;;){let r=await e.read();if(r.done)break;if(n+=r.value.length,n>g)throw Error(`Speech payload exceeds size limit`);t.push(r.value)}}finally{await e.cancel().catch(()=>void 0),e.releaseLock()}f=new Uint8Array(n);let r=0;for(let e of t)f.set(e,r),r+=e.length}else if(o!==0)throw Error(`Unsupported speech compression`);return{type:i,event:l,sessionId:u,final:!!(a&2),payload:f}}function b(e){let t=JSON.parse(h.decode(e));if(!t||typeof t!=`object`||Array.isArray(t))throw Error(`Invalid speech response`);return t}async function x(e,t){t.throwIfAborted();let n=new WebSocket(e);n.binaryType=`arraybuffer`;let r=[],i=0,a=null,o=null,s=null,c=(e=!0)=>{t.removeEventListener(`abort`,u),n.onmessage=null,n.onclose=null,n.onerror=null,n.onopen=null,e&&(r.length=0,i=0),n.close(),o?.()},l=(e,t=!0)=>{a??=e,s?.(a),c(t)},u=()=>l(new DOMException(`Speech cancelled`,`AbortError`));t.addEventListener(`abort`,u,{once:!0}),n.onmessage=e=>{if(!(e.data instanceof ArrayBuffer)||e.data.byteLength>g){l(Error(`Invalid speech message`));return}if(i+=e.data.byteLength,i>33554432||r.length>=4096){l(Error(`Speech receive buffer exceeded`));return}r.push(e.data),o?.()},n.onerror=()=>l(Error(`Speech connection failed`)),n.onclose=()=>l(Error(`Speech connection ended before completion`),!1);let d=setTimeout(()=>l(Error(`Speech connection timed out`)),1e4);try{await new Promise((e,t)=>{s=t,n.onopen=()=>e()})}finally{clearTimeout(d),s=null}return{send(e){if(t.throwIfAborted(),a)throw a;if(n.readyState!==WebSocket.OPEN||n.bufferedAmount+e.length>g)throw Error(`Speech send buffer exceeded`);n.send(e)},async read(){let e=setTimeout(()=>l(Error(`Speech response timed out`)),6e4);try{for(;!r.length&&!a;)await new Promise(e=>{o=e});if(t.throwIfAborted(),!r.length&&a)throw a;let e=r.shift();return i-=e.byteLength,e}finally{clearTimeout(e),o=null}},close(){a??=new DOMException(`Speech closed`,`AbortError`),c()}}}var S=class{abort;partial;disconnected;socket=null;failure=null;completion=null;text=``;final=!1;finishing=!1;sequence=0;constructor(e,t,n){this.abort=e,this.partial=t,this.disconnected=n}get connected(){return this.socket!==null&&!this.failure&&!this.abort.signal.aborted}async start(e,t){if(t!==`audio/pcm;rate=16000;bits=16`)throw Error(`Direct speech requires 16 kHz PCM16 audio`);let n=await x(p(e,`asr`),this.abort.signal);this.socket=n;try{n.send(_(new TextEncoder().encode(JSON.stringify({user:{uid:`openchamber`},audio:{format:`pcm`,codec:`raw`,rate:16e3,bits:16,channel:1},request:{model_name:`bigmodel`,result_type:`full`,enable_itn:!0,enable_punc:!0,enable_nonstream:!0,show_utterances:!0}}))))}catch(e){throw n.close(),e}this.completion=this.receive().catch(e=>{this.failure=e instanceof Error?e:Error(`Speech recognition failed`),n.close(),!this.abort.signal.aborted&&!this.finishing&&this.disconnected()})}send(e,t){if(!this.connected||this.finishing||this.final||e!==this.sequence)return!1;try{if(t.length>128e3)throw Error(`Speech audio chunk exceeds size limit`);let e=atob(t);if(e.length%2)throw Error(`Invalid PCM audio`);let n=Uint8Array.from(e,e=>e.charCodeAt(0));return this.socket.send(_(n,!0)),this.sequence++,!0}catch{return this.failure=Error(`Unable to send speech audio`),this.socket?.close(),!1}}async finish(e){if(this.failure)throw this.failure;if(!this.socket||e!==this.sequence-1)throw Error(`Incomplete speech audio`);this.finishing=!0,this.socket.send(_(new Uint8Array,!0,!0));let t=setTimeout(()=>{this.failure=Error(`Speech finalization timed out`),this.socket?.close()},3e4);try{if(await this.completion,this.abort.signal.throwIfAborted(),this.failure)throw this.failure;if(!this.final)throw Error(`Incomplete speech recognition`);return{text:this.text}}finally{clearTimeout(t),this.socket.close()}}cancel(){this.abort.abort(),this.socket?.close()}async receive(){for(;;){let e=await y(await this.socket.read(),`asr`);this.abort.signal.throwIfAborted();let t=b(e.payload);if(t.code!==void 0&&![0,1e3,2e7].includes(Number(t.code)))throw Error(`Speech recognition failed`);let n=t.result;if(n!==void 0&&(!n||typeof n!=`object`||Array.isArray(n)))throw Error(`Invalid speech transcript`);if(n&&typeof n==`object`&&`text`in n){if(typeof n.text!=`string`)throw Error(`Invalid speech transcript`);this.text=n.text,e.final||this.partial(this.text)}if(e.final){if(!this.finishing)throw Error(`Speech recognition ended unexpectedly`);this.final=!0;return}}}},C=(e,t,n)=>{let r=Error(e);return r.name=`DictationStreamError`,r.retryable=t,n&&(r.reasonCode=n),r},w=1e4,T=15e3,E=3e4,D=3e4,O=new class{socket=null;connectPromise=null;idleCloseTimer=null;pendingStarts=new Map;pendingFinishes=new Map;connectionListeners=new Set;partialListeners=new Set;activeDictations=0;directStarts=new Map;directSessions=new Map;get isConnected(){if(this.socket?.readyState===WebSocket.OPEN)return!0;for(let e of this.directSessions.values())if(e.connected)return!0;return!1}subscribeConnectionStatus(e){return this.connectionListeners.add(e),()=>{this.connectionListeners.delete(e)}}onPartial(e){return this.partialListeners.add(e),()=>{this.partialListeners.delete(e)}}async ensureConnected(){if(this.socket?.readyState!==WebSocket.OPEN){if(this.connectPromise){await this.connectPromise;return}try{await t()}catch{}this.connectPromise=new Promise((e,t)=>{let n=!1,i;try{let e=r().websocket(`/api/dictation/ws`);i=s(e)}catch(e){this.connectPromise=null,t(e instanceof Error?e:Error(String(e)));return}let a=setTimeout(()=>{if(!n){n=!0,this.connectPromise=null;try{i.close()}catch{}t(Error(`Dictation connection timed out`))}},w);i.onopen=()=>{},i.onmessage=t=>{let r;try{r=JSON.parse(String(t.data))}catch{return}if(!n&&r.type===`ready`){n=!0,clearTimeout(a),this.socket=i,this.connectPromise=null,this.notifyConnection(!0),e();return}this.handleMessage(r)},i.onerror=()=>{window.setTimeout(()=>{n||(n=!0,clearTimeout(a),this.connectPromise=null,t(Error(`Dictation connection failed`)))},250)},i.onclose=e=>{if(!n){n=!0,clearTimeout(a),this.connectPromise=null;let r=e?.reason?`: ${e.reason}`:``;t(Error(`Dictation connection failed${r}`));return}this.socket===i&&(this.socket=null,this.rejectAllPending(Error(`Dictation connection lost`)),this.notifyConnection(!1))}}),await this.connectPromise}}async startDictationStream(e,t){let n=new AbortController;this.directStarts.set(e,n);try{let r=await f(`asr`,n.signal);if(n.signal.throwIfAborted(),r){let i=new S(n,t=>{for(let n of this.partialListeners)n(e,t)},()=>{this.directSessions.delete(e),i.cancel(),this.notifyConnection(!1)});this.directSessions.set(e,i),await i.start(r,t),n.signal.throwIfAborted(),this.notifyConnection(!0);return}await this.ensureConnected(),n.signal.throwIfAborted()}catch(t){throw this.directSessions.get(e)?.cancel(),this.directSessions.delete(e),t}finally{this.directStarts.delete(e)}return this.activeDictations+=1,this.clearIdleCloseTimer(),new Promise((n,r)=>{let i=setTimeout(()=>{this.pendingStarts.delete(e),this.releaseDictation(),r(Error(`Dictation start timed out`))},T);this.pendingStarts.set(e,{resolve:()=>{clearTimeout(i),n()},reject:e=>{clearTimeout(i),this.releaseDictation(),r(e)},timeout:i}),this.send({type:`start`,dictationId:e,format:t})||(clearTimeout(i),this.pendingStarts.delete(e),this.releaseDictation(),r(Error(`Dictation connection lost`)))})}sendDictationStreamChunk(e,t,n){let r=this.directSessions.get(e);return r?r.send(t,n):this.send({type:`chunk`,dictationId:e,seq:t,audio:n})}finishDictationStream(e,t){let n=this.directSessions.get(e);return n?n.finish(t).finally(()=>{n.cancel(),this.directSessions.delete(e)}):new Promise((n,r)=>{let i={resolve:e=>{i.timeout&&clearTimeout(i.timeout),this.releaseDictation(),n(e)},reject:e=>{i.timeout&&clearTimeout(i.timeout),this.releaseDictation(),r(e)},timeout:null};i.timeout=setTimeout(()=>{this.pendingFinishes.delete(e),this.releaseDictation(),r(Error(`Timed out waiting for transcription`))},D),this.pendingFinishes.set(e,i),this.send({type:`finish`,dictationId:e,finalSeq:t})||(this.pendingFinishes.delete(e),i.reject(Error(`Dictation connection lost`)))})}cancelDictationStream(e){this.directStarts.get(e)?.abort(),this.directStarts.delete(e);let t=this.directSessions.get(e);if(t){t.cancel(),this.directSessions.delete(e);return}this.send({type:`cancel`,dictationId:e});let n=this.pendingStarts.get(e);n&&(this.pendingStarts.delete(e),n.reject(Error(`Dictation cancelled`)));let r=this.pendingFinishes.get(e);r&&(this.pendingFinishes.delete(e),r.reject(Error(`Dictation cancelled`))),this.releaseDictation(),this.scheduleIdleCloseIfReady()}handleMessage(e){let t=e.dictationId;if(t)switch(e.type){case`ack`:{let e=this.pendingStarts.get(t);e&&(this.pendingStarts.delete(t),e.resolve());return}case`partial`:for(let n of this.partialListeners)n(t,e.text??``);return;case`finish_accepted`:{let n=this.pendingFinishes.get(t);n&&typeof e.timeoutMs==`number`&&(n.timeout&&clearTimeout(n.timeout),n.timeout=setTimeout(()=>{this.pendingFinishes.delete(t),n.reject(Error(`Timed out waiting for transcription`))},e.timeoutMs+5e3));return}case`final`:{let n=this.pendingFinishes.get(t);n&&(this.pendingFinishes.delete(t),n.resolve({text:e.text??``})),this.scheduleIdleCloseIfReady();return}case`error`:{let n=C(e.error||`Dictation failed`,e.retryable!==!1,e.reasonCode),r=this.pendingStarts.get(t);r&&(this.pendingStarts.delete(t),r.reject(n));let i=this.pendingFinishes.get(t);i&&(this.pendingFinishes.delete(t),i.reject(n)),this.scheduleIdleCloseIfReady();return}}}send(e){if(!this.isConnected||!this.socket)return!1;try{return this.socket.send(JSON.stringify(e)),!0}catch{return!1}}notifyConnection(e){for(let t of this.connectionListeners)t(e)}rejectAllPending(e){for(let[t,n]of this.pendingStarts)this.pendingStarts.delete(t),n.reject(e);for(let[t,n]of this.pendingFinishes)this.pendingFinishes.delete(t),n.reject(e);this.activeDictations=0}releaseDictation(){this.activeDictations=Math.max(0,this.activeDictations-1),this.scheduleIdleCloseIfReady()}scheduleIdleCloseIfReady(){this.activeDictations>0||this.pendingFinishes.size>0||this.pendingStarts.size>0||(this.clearIdleCloseTimer(),this.idleCloseTimer=setTimeout(()=>{if(this.activeDictations===0&&this.pendingFinishes.size===0&&this.pendingStarts.size===0){let e=this.socket;if(this.socket=null,e){try{e.close(1e3,`idle`)}catch{}this.notifyConnection(!1)}}},E))}cancelAllForRuntimeSwitch(){for(let e of this.directStarts.values())e.abort();this.directStarts.clear();for(let e of this.directSessions.values())e.cancel();this.directSessions.clear(),this.clearIdleCloseTimer();let e=this.socket;if(this.socket=null,this.connectPromise=null,this.rejectAllPending(Error(`Runtime changed`)),e){try{e.close(1e3,`runtime switch`)}catch{}this.notifyConnection(!1)}}clearIdleCloseTimer(){this.idleCloseTimer&&=(clearTimeout(this.idleCloseTimer),null)}};typeof window<`u`&&window.addEventListener(`openchamber:runtime-endpoint-changed`,()=>{O.cancelAllForRuntimeSwitch()});var k=128,A=`audio/pcm;rate=16000;bits=16`,j=()=>new Promise(e=>setTimeout(e,0)),M=()=>{let e=Math.random().toString(36).slice(2,10);return`dic_${Date.now().toString(16)}${e}`},N=class{client;format;createDictationId;dictationId=null;sendSeq=0;segments=[];streamReady=!1;flushTimer=null;drainWaiters=[];startGeneration=0;startPromise=null;constructor(e){this.client=e.client,this.format=e.format??A,this.createDictationId=e.createDictationId??M}getDictationId(){return this.dictationId}getFinalSeq(){return this.segments.length-1}hasSegments(){return this.segments.length>0}clearAll(){this.dictationId&&this.client.cancelDictationStream(this.dictationId),this.clearScheduledFlush(),this.dictationId=null,this.sendSeq=0,this.segments=[],this.streamReady=!1,this.startPromise=null,this.startGeneration+=1}resetStreamForReplay(){this.dictationId&&this.client.cancelDictationStream(this.dictationId),this.clearScheduledFlush(),this.dictationId=null,this.sendSeq=0,this.streamReady=!1,this.startPromise=null,this.startGeneration+=1}enqueueSegment(e){if(this.segments.push(e),this.client.isConnected){if(!this.dictationId){this.startPromise||this.restartStream().catch(()=>{});return}this.flush()}}flush(){let e=this.dictationId;if(!this.client.isConnected||!e||!this.streamReady)return 0;let t=0;for(;this.sendSeq<this.segments.length&&t<k;){let n=this.sendSeq,r=this.segments[n];if(!this.client.sendDictationStreamChunk(e,n,r))break;this.sendSeq=n+1,t+=1}return this.hasPendingSegments()?this.scheduleFlush():this.resolveDrainWaiters(),t}async restartStream(){this.startGeneration+=1;let e=this.startGeneration,t=this.createDictationId();this.dictationId=t,this.sendSeq=0,this.streamReady=!1;let n=(async()=>{await this.client.startDictationStream(t,this.format),this.startGeneration===e&&this.dictationId===t&&(this.streamReady=!0,this.flush())})().catch(n=>{throw this.startGeneration===e&&this.dictationId===t&&(this.dictationId=null,this.streamReady=!1),n}).finally(()=>{this.startPromise===n&&(this.startPromise=null)});this.startPromise=n,await n}async finish(e){this.dictationId||await this.restartStream(),this.startPromise&&await this.startPromise;let t=this.dictationId;if(!t||!this.streamReady)throw Error(`Failed to start dictation stream`);return this.flush(),await this.waitForFlushDrain(),{dictationId:t,text:(await this.client.finishDictationStream(t,e)).text}}cancel(){this.resetStreamForReplay()}hasPendingSegments(){return this.sendSeq<this.segments.length}scheduleFlush(){this.flushTimer||=setTimeout(()=>{this.flushTimer=null,this.flush()},0)}clearScheduledFlush(){this.resolveDrainWaiters(),this.flushTimer&&=(clearTimeout(this.flushTimer),null)}async waitForFlushDrain(){for(;this.hasPendingSegments();){if(!this.client.isConnected||!this.dictationId||!this.streamReady)throw Error(`Failed to flush dictation stream`);await new Promise(e=>{this.drainWaiters.push(e)}),await j()}}resolveDrainWaiters(){let e=this.drainWaiters;this.drainWaiters=[];for(let t of e)t()}},P=c(),F=new Set;function I(e){return F.add(e),()=>{F.delete(e)}}function L(){for(let e of F)e();if(typeof window<`u`&&window.speechSynthesis?.cancel(),typeof navigator>`u`)return;let e=navigator.audioSession;if(e)try{e.type=`play-and-record`}catch{}}function R(){if(typeof navigator>`u`)return;let e=navigator.audioSession;if(e)try{e.type!==`playback`&&(e.type=`playback`)}catch{}}var z=16e3,B=z/5,V=()=>{if(typeof window>`u`)return null;let e=window;return e.AudioContext||e.webkitAudioContext||null},H=e=>{let t=Math.max(-1,Math.min(1,e));return Math.round(t<0?t*32768:t*32767)},U=(e,t,n)=>{if(e.length===0)return new Int16Array;if(t===n){let t=new Int16Array(e.length);for(let n=0;n<e.length;n++)t[n]=H(e[n]);return t}let r=t/n,i=Math.max(1,Math.round(e.length/r)),a=new Int16Array(i);for(let t=0;t<i;t++){let n=t*r,i=Math.floor(n),o=Math.min(e.length-1,i+1),s=n-i;a[t]=H(e[i]*(1-s)+e[o]*s)}return a},W=(e,t)=>{if(e.length===0)return t;if(t.length===0)return e;let n=new Int16Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n},G=e=>{let t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength),n=``;for(let e=0;e<t.length;e++)n+=String.fromCharCode(t[e]);return btoa(n)},K=()=>({stream:null,context:null,source:null,processor:null,gain:null,pending:new Int16Array,started:!1}),q=e=>{if(e)try{e.disconnect()}catch{}},J=()=>typeof navigator>`u`||typeof window>`u`||!navigator.mediaDevices||typeof navigator.mediaDevices.getUserMedia!=`function`?!1:V()!==null;function Y(e){let t=(0,P.c)(16),[n,r]=(0,d.useState)(0),i=(0,d.useRef)(e.onPcmSegment),a=(0,d.useRef)(e.onError),o,s;t[0]!==e.onError||t[1]!==e.onPcmSegment?(o=()=>{i.current=e.onPcmSegment,a.current=e.onError},s=[e.onPcmSegment,e.onError],t[0]=e.onError,t[1]=e.onPcmSegment,t[2]=o,t[3]=s):(o=t[2],s=t[3]),(0,d.useEffect)(o,s);let c;t[4]===Symbol.for(`react.memo_cache_sentinel`)?(c=K(),t[4]=c):c=t[4];let u=(0,d.useRef)(c),f=(0,d.useRef)(0),p;t[5]===Symbol.for(`react.memo_cache_sentinel`)?(p=async()=>{if(u.current.started)return;if(typeof navigator>`u`||!navigator.mediaDevices||typeof navigator.mediaDevices.getUserMedia!=`function`)throw Error(`Microphone capture is not supported in this environment`);let e=V();if(!e)throw Error(`AudioContext unavailable`);let t=f.current+=1;L();let n=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,noiseSuppression:!0,echoCancellation:!0,autoGainControl:!0}});if(t!==f.current){n.getTracks().forEach(ee);return}let a=new e;try{if(a.state===`suspended`&&await a.resume().catch($),t!==f.current){n.getTracks().forEach(Q),await a.close();return}let e=a.createMediaStreamSource(n),o=a.createScriptProcessor(4096,1,1),s=a.createGain();s.gain.value=0,u.current={stream:n,context:a,source:e,processor:o,gain:s,pending:new Int16Array,started:!0},o.onaudioprocess=e=>{let t=u.current;if(!t.started)return;let n=e.inputBuffer.getChannelData(0),o=0;for(let e=0;e<n.length;e++)o+=n[e]*n[e];let s=Math.sqrt(o/Math.max(1,n.length));r(Math.min(1,Math.max(0,s*2)));let c=U(n,a.sampleRate,z);for(t.pending=W(t.pending,c);t.pending.length>=B;){let e=t.pending.slice(0,B);t.pending=t.pending.slice(B),i.current(G(e))}},e.connect(o),o.connect(s),s.connect(a.destination)}catch(e){let t=e;n.getTracks().forEach(Z);try{await a.close()}catch{}throw u.current=K(),t instanceof Error?t:Error(String(t))}},t[5]=p):p=t[5];let m=l(p),h;t[6]===Symbol.for(`react.memo_cache_sentinel`)?(h=async()=>{f.current+=1;let e=u.current;if(e.started=!1,r(0),e.processor)try{e.processor.onaudioprocess=null}catch{}q(e.processor),q(e.source),q(e.gain),e.stream&&e.stream.getTracks().forEach(X);let t=e.pending;if(e.pending=new Int16Array,t.length>0&&i.current(G(t)),e.context)try{await e.context.close()}catch{}u.current===e&&(u.current=K())},t[6]=h):h=t[6];let g=l(h),_,v;t[7]===g?(_=t[8],v=t[9]):(_=()=>()=>{g().catch(e=>{a.current?.(e instanceof Error?e:Error(String(e)))})},v=[g],t[7]=g,t[8]=_,t[9]=v),(0,d.useEffect)(_,v);let y;t[10]===m?y=t[11]:(y=async()=>{try{await m()}catch(e){let t=e,n=t instanceof Error?t:Error(String(t));throw a.current?.(n),n}},t[10]=m,t[11]=y);let b;return t[12]!==g||t[13]!==y||t[14]!==n?(b={start:y,stop:g,volume:n},t[12]=g,t[13]=y,t[14]=n,t[15]=b):b=t[15],b}function X(e){try{e.stop()}catch{}}function Z(e){try{e.stop()}catch{}}function Q(e){return e.stop()}function $(){}function ee(e){return e.stop()}var te=1e3,ne=e=>e instanceof Error?e:Error(String(e));function re(e={}){let{t}=u(),{onTranscript:r,onError:i,canStart:a}=e,[o,s]=(0,d.useState)(`idle`),[c,f]=(0,d.useState)(!1),[p,m]=(0,d.useState)(``),[h,g]=(0,d.useState)(0),[_,v]=(0,d.useState)(null),[y,b]=(0,d.useState)(null),x=(0,d.useRef)(o);(0,d.useEffect)(()=>{x.current=o},[o]);let S=(0,d.useRef)(``),C=(0,d.useRef)(null),w=(0,d.useRef)({starting:!1,confirming:!1,cancelling:!1}),T=(0,d.useRef)(0),E=(0,d.useRef)(r),D=(0,d.useRef)(i);(0,d.useEffect)(()=>{E.current=r,D.current=i},[r,i]);let k=(0,d.useRef)(null);k.current||=new N({client:O});let A=l(()=>{C.current&&=(clearInterval(C.current),null)}),j=l(()=>{C.current||=setInterval(()=>{g(e=>e+1)},te)}),M=l(e=>{let n=ne(e),r=n.name===`NotFoundError`||n.name===`DevicesNotFoundError`?Error(t(`chat.dictation.error.microphoneNotFound`),{cause:n}):n;v(r.message);let i=n.reasonCode;b(typeof i==`string`?i:null),D.current?.(r)}),P=l(()=>{v(null),b(null)}),F=l(()=>{k.current?.clearAll(),S.current=``,m(``)}),I=l(t=>e.onPartialTranscript?.(t));(0,d.useEffect)(()=>O.onPartial((e,t)=>{let n=k.current?.getDictationId();!n||e!==n||(S.current=t,m(t),I(t))}),[I]),(0,d.useEffect)(()=>O.subscribeConnectionStatus(e=>{if(!e){x.current===`recording`&&k.current?.resetStreamForReplay();return}if(x.current!==`recording`||k.current?.getDictationId())return;let t=T.current;k.current?.restartStream().catch(e=>{T.current===t&&M(e)})}),[M]);let L=Y({onPcmSegment:e=>{x.current===`recording`&&k.current?.enqueueSegment(e)},onError:e=>{D.current?.(e)}}),R=(0,d.useRef)(L.stop);(0,d.useEffect)(()=>{R.current=L.stop},[L.stop]);let z=l(e=>{g(0),s(`idle`),x.current=`idle`;let t=e.trim();return F(),t?(E.current?.(t),t):null}),B=l(e=>{k.current?.hasSegments()?(s(`failed`),x.current=`failed`):(s(`idle`),x.current=`idle`),M(e)}),V=l(async()=>{let e=w.current;if(e.starting||e.confirming||e.cancelling||x.current!==`idle`||a&&!a())return;e.starting=!0,f(!0);let t=++T.current;P(),m(``),g(0),s(`recording`),x.current=`recording`,F();try{if(await L.start(),T.current!==t||x.current!==`recording`)return;f(!1),j(),await k.current?.restartStream().catch(e=>{T.current===t&&M(e)})}catch(e){if(T.current!==t)return;await L.stop().catch(()=>void 0),A(),s(`idle`),x.current=`idle`,M(e)}finally{T.current===t&&f(!1),e.starting=!1}}),H=l(async()=>{let e=w.current;if(!e.cancelling&&(x.current===`recording`||x.current===`uploading`)){e.cancelling=!0,f(!1),T.current+=1,A(),g(0),P(),s(`idle`),x.current=`idle`;try{k.current?.cancel()}catch{}F();try{await L.stop()}catch{}finally{e.cancelling=!1}}}),U=l(async()=>{let e=w.current;if(e.confirming||x.current!==`recording`)return null;e.confirming=!0,f(!1);let t=++T.current;P(),A();try{if(await L.stop(),T.current!==t)return null;s(`uploading`),x.current=`uploading`;let e=k.current?.getFinalSeq()??-1;if(e<0)return k.current?.cancel(),z(``);let n=await k.current.finish(e);return T.current===t?z(n.text):null}catch(e){return T.current===t&&B(e),null}finally{e.confirming=!1}}),W=l(async()=>{if(x.current!==`failed`||!k.current?.hasSegments())return null;P();let e=++T.current;s(`uploading`),x.current=`uploading`;try{k.current.resetStreamForReplay();let t=k.current.getFinalSeq(),n=await k.current.finish(t);return T.current===e?z(n.text):null}catch(t){return T.current===e&&B(t),null}}),G=l(()=>{if(x.current!==`failed`)return null;let e=S.current.trim();g(0),s(`idle`),x.current=`idle`,P();try{k.current?.cancel()}catch{}return F(),e?(E.current?.(e),e):null}),K=l(()=>{g(0),s(`idle`),x.current=`idle`,P(),F()});return(0,d.useEffect)(()=>n(()=>{x.current===`failed`?(T.current+=1,K()):H()}),[H,K]),(0,d.useEffect)(()=>{if(o!==`recording`||y!==`model_download_in_progress`)return;let e=setInterval(()=>{x.current!==`recording`||k.current?.getDictationId()||k.current?.restartStream().then(()=>{P()}).catch(e=>{M(e)})},3e3);return()=>clearInterval(e)},[o,y,P,M]),(0,d.useEffect)(()=>()=>{T.current+=1,A(),R.current().catch(()=>void 0),k.current?.cancel()},[A]),{status:o,isRecording:o===`recording`,isStarting:c,isProcessing:o===`uploading`,partialTranscript:p,volume:L.volume,duration:h,error:_,errorReason:y,startDictation:V,confirmDictation:U,cancelDictation:H,retryFailedDictation:W,acceptPartialTranscript:G,discardFailedDictation:K}}var ie=`app-region-no-drag transition-[opacity,transform] duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 z-50 max-h-[var(--available-height)] min-w-[8rem] origin-[var(--transform-origin)] overflow-visible rounded-xl p-1 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,ae=`data-[highlighted]:bg-interactive-hover hover:bg-interactive-hover active:bg-interactive-active data-[variant=destructive]:text-destructive data-[variant=destructive]:hover:bg-destructive/10 dark:data-[variant=destructive]:hover:bg-destructive/20 data-[variant=destructive]:hover:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1 typography-ui-label outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5`,oe=`data-[highlighted]:bg-interactive-hover hover:bg-interactive-hover active:bg-interactive-active [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1 typography-ui-label outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5`,se=`bg-border -mx-1 my-0.5 h-px`;export{re as a,R as c,x as d,b as f,p as h,oe as i,I as l,f as m,ie as n,J as o,v as p,se as r,L as s,ae as t,y as u};