@bittersmilezzz/dsh-asr-voice 0.3.1

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -0
  3. package/cordis.patch.yml +32 -0
  4. package/lib/asr-models.js +83 -0
  5. package/lib/asr-models.js.map +1 -0
  6. package/lib/client.js +6706 -0
  7. package/lib/client.js.map +1 -0
  8. package/lib/http.js +113 -0
  9. package/lib/http.js.map +1 -0
  10. package/lib/index.js +193 -0
  11. package/lib/index.js.map +1 -0
  12. package/lib/key-ref.js +25 -0
  13. package/lib/key-ref.js.map +1 -0
  14. package/lib/optimize.js +168 -0
  15. package/lib/optimize.js.map +1 -0
  16. package/lib/presets.js +98 -0
  17. package/lib/presets.js.map +1 -0
  18. package/lib/realtime-dashscope.js +211 -0
  19. package/lib/realtime-dashscope.js.map +1 -0
  20. package/lib/realtime-host.js +413 -0
  21. package/lib/realtime-host.js.map +1 -0
  22. package/lib/realtime-provider.js +143 -0
  23. package/lib/realtime-provider.js.map +1 -0
  24. package/lib/realtime-tts.js +179 -0
  25. package/lib/realtime-tts.js.map +1 -0
  26. package/lib/settings.js +155 -0
  27. package/lib/settings.js.map +1 -0
  28. package/lib/stats.js +30 -0
  29. package/lib/stats.js.map +1 -0
  30. package/lib/transcribe.js +274 -0
  31. package/lib/transcribe.js.map +1 -0
  32. package/lib/types/asr-models.d.ts +30 -0
  33. package/lib/types/asr-models.d.ts.map +1 -0
  34. package/lib/types/http.d.ts +41 -0
  35. package/lib/types/http.d.ts.map +1 -0
  36. package/lib/types/index.d.ts +75 -0
  37. package/lib/types/index.d.ts.map +1 -0
  38. package/lib/types/key-ref.d.ts +9 -0
  39. package/lib/types/key-ref.d.ts.map +1 -0
  40. package/lib/types/optimize.d.ts +41 -0
  41. package/lib/types/optimize.d.ts.map +1 -0
  42. package/lib/types/presets.d.ts +64 -0
  43. package/lib/types/presets.d.ts.map +1 -0
  44. package/lib/types/realtime-dashscope.d.ts +41 -0
  45. package/lib/types/realtime-dashscope.d.ts.map +1 -0
  46. package/lib/types/realtime-host.d.ts +95 -0
  47. package/lib/types/realtime-host.d.ts.map +1 -0
  48. package/lib/types/realtime-provider.d.ts +76 -0
  49. package/lib/types/realtime-provider.d.ts.map +1 -0
  50. package/lib/types/realtime-tts.d.ts +42 -0
  51. package/lib/types/realtime-tts.d.ts.map +1 -0
  52. package/lib/types/settings.d.ts +143 -0
  53. package/lib/types/settings.d.ts.map +1 -0
  54. package/lib/types/stats.d.ts +29 -0
  55. package/lib/types/stats.d.ts.map +1 -0
  56. package/lib/types/transcribe.d.ts +46 -0
  57. package/lib/types/transcribe.d.ts.map +1 -0
  58. package/lib/types/tsconfig.client.tsbuildinfo +1 -0
  59. package/package.json +146 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-dashscope.d.ts","sourceRoot":"","sources":["../../src/realtime-dashscope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAqD,MAAM,wBAAwB,CAAA;AAOjH,sCAAsC;AACtC,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,+BAA+B;IAC/B,GAAG,CAAC,EAAE;QACJ,4BAA4B;QAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,oCAAoC;QACpC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAC3B,CAAA;CACF;AA0LD,sDAAsD;AACtD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,wBAAwB,GAAG,gBAAgB,CAUhG"}
@@ -0,0 +1,95 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { RealtimeProviderConnection, RealtimeProviderEvent } from './realtime-provider.ts';
3
+ /** webserver register 的最小面(与 transcribe.ts 的 register 参数同构)。 */
4
+ export type RealtimeRouteRegister = (def: {
5
+ kind: 'exact';
6
+ path: string;
7
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
8
+ }) => () => void;
9
+ /** SSE 下行通道:带背压(partial 原位合并 / final 必达)与心跳。 */
10
+ export declare class SseChannel {
11
+ private readonly res;
12
+ private backedUp;
13
+ /** 背压期间排队待发的事件(有界)。partial 会被更新的 partial 原位替换(coalesce),
14
+ * final/speech-stopped 追加保序——drain 后按序冲刷,**任何 final 都不丢**。
15
+ * 此前单一 coalesce 槽会被后续 final/partial 直接覆盖:背压中连续两句收口时,
16
+ * 第一句的 final 被第二句顶掉,客户端永远只看到 partial(缺句)。 */
17
+ private pending;
18
+ /** pending 上限:与会话侧 pending 缓冲同量级;极端积压(客户端几乎不读)时丢最旧
19
+ * 事件降级,会话最终由 disconnect / 空闲守卫拆除。 */
20
+ private static readonly PENDING_CAP;
21
+ private closed;
22
+ /** 空闲心跳:防止中间代理把长连接掐掉(部分代理 30s 无数据即断)。 */
23
+ private heartbeat;
24
+ private readonly onDisconnect;
25
+ /** 当前挂着的 drain 监听(close/disconnect 时移除,避免监听随响应滞留到 GC)。 */
26
+ private drainHandler;
27
+ constructor(res: ServerResponse, opts?: {
28
+ heartbeatMs?: number;
29
+ onDisconnect?: () => void;
30
+ });
31
+ /** 排入一条事件:空闲直写;背压时 partial 原位合并、final/stopped 排队保序。
32
+ * 命中背压的那一条**已经写出**(`write` 返回 false 只是「别再写了」,不是「没写」),
33
+ * 所以队列只兜住背压期间新到的事件,等 drain 恢复后按序送出。 */
34
+ enqueue(ev: RealtimeProviderEvent): void;
35
+ /** 按序冲刷排队的事件(final 不丢、partial 保最新);缓冲满则挂 drain 等恢复。
36
+ * **`write()` 返回 false 只表示内核缓冲已超高水位,事件本身已被接受并会送出**——
37
+ * 因此出队与返回值无关:先出队再据返回值置背压标志。早先「返回 false 就不出队」的
38
+ * 写法会让同一条事件在 drain 后被再写一遍(partial 重放无害,final 重放会让客户端
39
+ * 把同一个回合提交两次)。 */
40
+ private flush;
41
+ /** 挂一次 drain 监听(同一时间只挂一个;close/disconnect 时摘掉)。 */
42
+ private armDrain;
43
+ /** 结束下行(幂等):清心跳、断 close/drain 监听。 */
44
+ close(): void;
45
+ private readonly disconnect;
46
+ }
47
+ /** RealtimeHost 构造参数(依赖注入,便于单测)。 */
48
+ export interface RealtimeHostOptions {
49
+ /** 每次建会话时创建一条 provider 连接。 */
50
+ createProvider(): Promise<RealtimeProviderConnection> | RealtimeProviderConnection;
51
+ /** 会话空闲超时(毫秒,默认 10 分钟)。 */
52
+ idleMs?: number;
53
+ /** SSE 心跳间隔(毫秒,默认 15s)。 */
54
+ heartbeatMs?: number;
55
+ /** 上游终态报错后的收尾宽限期(毫秒,默认 5s)。测试注入小值以确定性覆盖拆除路径。 */
56
+ errorLingerMs?: number;
57
+ /** 现在的时间(毫秒,测试注入)。 */
58
+ now?: () => number;
59
+ }
60
+ /** 实时转写会话注册表 + 路由。 */
61
+ export declare class RealtimeHost {
62
+ private readonly sessions;
63
+ private readonly opts;
64
+ private readonly createProvider;
65
+ constructor(options: RealtimeHostOptions);
66
+ /** 铸造新会话:host 生成 sid,建 provider 连接。 */
67
+ createSession(): Promise<{
68
+ sid: string;
69
+ }>;
70
+ /** 上游终态报错后的收尾:标记会话已终结 + 把空闲窗口收紧到 ERROR_LINGER_MS。
71
+ * 修的是「僵尸会话」:此前 provider 报错后 host 不拆会话,客户端仍在按 40ms 一帧
72
+ * 上行 PCM,每帧 `feedAudio` → `refreshIdle` 都把 lastActive 顶到现在——死连接的
73
+ * 会话因此永远等不到空闲过期,SSE 一直挂着、麦克风一直开。 */
74
+ private armErrorTeardown;
75
+ /** 空闲守卫:到点复查——期间有任何上行/下行活动会走 refreshIdle 重挂, 真正空闲满 windowMs 才拆会话防泄漏(默认 idleMs;上游终态报错后用更短的收尾宽限)。 */
76
+ private armIdle;
77
+ /** 刷新空闲计时(每次上行/下行活动调用)。已终结的会话不续命——否则死连接被
78
+ * 客户端的持续上行"续命",永远到不了过期点。 */
79
+ private refreshIdle;
80
+ /** 上行 PCM(16k int16 LE)到指定会话。会话不存在**或上游已终态报错**返回 false
81
+ * (路由据此回 404,客户端停止上行)。 */
82
+ feedAudio(sid: string, pcm: Uint8Array): boolean;
83
+ /** 挂起 SSE 下行(单消费者)。会话不存在 / 已有下行返回 false。 */
84
+ attachSse(sid: string, res: ServerResponse): boolean;
85
+ /** 关闭会话:拆 provider、拆 SSE、清定时器(幂等)。 */
86
+ closeSession(sid: string): void;
87
+ /** 会话是否存活(供测试/诊断)。 */
88
+ hasSession(sid: string): boolean;
89
+ /** 释放全部会话(插件卸载/热重载时由 fiber disposer 调用):逐个 closeSession
90
+ * (幂等),SSE 心跳、idle timer、provider 连接全部随之释放。 */
91
+ dispose(): void;
92
+ /** 注册 4 条 exact 路由(全部过 isTrusted)。 @returns 全部路由的 disposer(由 ctx.effect 挂载/回收)。 */
93
+ registerRoutes(register: RealtimeRouteRegister): () => void;
94
+ }
95
+ //# sourceMappingURL=realtime-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-host.d.ts","sourceRoot":"","sources":["../../src/realtime-host.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAahG,gEAAgE;AAChE,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC7E,KAAK,MAAM,IAAI,CAAA;AAEhB,gDAAgD;AAChD,qBAAa,UAAU;IAkBnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAjBtB,OAAO,CAAC,QAAQ,CAAQ;IACxB;;;kDAG8C;IAC9C,OAAO,CAAC,OAAO,CAA8B;IAC7C;0CACsC;IACtC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IACxC,OAAO,CAAC,MAAM,CAAQ;IACtB,yCAAyC;IACzC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,0DAA0D;IAC1D,OAAO,CAAC,YAAY,CAA4B;gBAG7B,GAAG,EAAE,cAAc,EACpC,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAchE;;4CAEwC;IACxC,OAAO,CAAC,EAAE,EAAE,qBAAqB,GAAG,IAAI;IAuCxC;;;;uBAImB;IACnB,OAAO,CAAC,KAAK;IAsBb,mDAAmD;IACnD,OAAO,CAAC,QAAQ;IAYhB,qCAAqC;IACrC,KAAK,IAAI,IAAI;IAWb,OAAO,CAAC,QAAQ,CAAC,UAAU,CAS1B;CACF;AAoBD,oCAAoC;AACpC,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,cAAc,IAAI,OAAO,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAA;IAClF,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sBAAsB;IACtB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,sBAAsB;AACtB,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyF;IAC9G,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuC;gBAE1D,OAAO,EAAE,mBAAmB;IAUxC,uCAAuC;IACjC,aAAa,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAyB/C;;;yCAGqC;IACrC,OAAO,CAAC,gBAAgB;IAQ1B,mGAAmG;IACjG,OAAO,CAAC,OAAO;IAaf;iCAC6B;IAC7B,OAAO,CAAC,WAAW;IAOnB;+BAC2B;IAC3B,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO;IAQhD,4CAA4C;IAC5C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO;IAiBpD,sCAAsC;IACtC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAY/B,sBAAsB;IACtB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIhC;oDACgD;IAChD,OAAO,IAAI,IAAI;IAIjB,mFAAmF;IACjF,cAAc,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;CAyE5D"}
@@ -0,0 +1,76 @@
1
+ /** dsh-asr-voice — host 半区:实时转写 provider 接缝 + 假 provider(I3 交付)。
2
+ * `RealtimeProvider` 是 host 与上游实时 ASR 之间的最小契约:浏览器 PCM 上行经
3
+ * `RealtimeHost`(realtime-host.ts)转发到这里,provider 回吐流式事件
4
+ * (speech-started / partial / final / speech-stopped / error),再由 host 经
5
+ * SSE 下行推给浏览器。
6
+ * 为什么要有接缝:I3 阶段没有真云端可连(真 provider 是 I5 的 qwen3-asr-flash-realtime,
7
+ * 走 OpenAI-compatible Realtime API over WebSocket)。接缝让 host 通道先于真实上游
8
+ * 建成并单测,I5 只新增一个 `RealtimeProvider` 实现,接缝与 host 通道一行不改。
9
+ * 假 provider 用能量 VAD 把进来的 PCM 切成「句」:句内周期吐 partial、句尾吐 final +
10
+ * speech-stopped——行为形状对齐真流式通道(先 partial 后 final,服务端 VAD 断句),
11
+ * 因此 I4 用它能端到端验证字幕与播放。纯 Node 标准库,macOS / Windows 双平台。
12
+ */
13
+ /** 上游实时 ASR 事件(host → 浏览器 SSE 行)。形状对齐 qwen3-asr-flash-realtime: 服务端 VAD 断句(speech_started / speech_stopped),流式转写(先 partial 后 final)。 */
14
+ export type RealtimeProviderEvent =
15
+ /** 一句话的中间结果:字幕更新用(可丢、可合并,背压时 coalesce)。 */
16
+ {
17
+ type: 'partial';
18
+ text: string;
19
+ }
20
+ /** 一句话转写完成:回合判定与最终字幕用(背压时不可丢)。 */
21
+ | {
22
+ type: 'final';
23
+ text: string;
24
+ }
25
+ /** 服务端 VAD 检测到语音起点(对齐 qwen 的 input_audio_buffer.speech_started)。 */
26
+ | {
27
+ type: 'speech-started';
28
+ }
29
+ /** 服务端 VAD 检测到语音终点(对齐 qwen 的 input_audio_buffer.speech_stopped)。 */
30
+ | {
31
+ type: 'speech-stopped';
32
+ }
33
+ /** 上游会话级错误(对齐 qwen 的 error)。
34
+ * `fatal` 区分**连接级终态**与**可继续的错误**——这是 host 决定要不要拆会话的唯一依据:
35
+ * - true(默认):连接已死(握手超时 / 对端关闭 / 不可达),此后再无 final 可能到达;
36
+ * - false:连接可能仍可用——单条音频转写失败(qwen 的 …transcription.failed,官方文档
37
+ * 明确它与通用 error 分开处理)与通用 error(含 invalid_request_error 等参数错)。
38
+ * 此时 host 不得拆会话,否则用户说错一句就把整场语音对话掐掉;是否结束交给客户端
39
+ * 既有的连败阈值。
40
+ * 省略等价于 true(旧实现/假 provider 的既有语义:报错即终态)。 */
41
+ | {
42
+ type: 'error';
43
+ code: string;
44
+ fatal?: boolean;
45
+ };
46
+ /** 一条与上游 provider 的实时连接。 */
47
+ export interface RealtimeProviderConnection {
48
+ /** 上行 PCM:16k 单声道 int16 小端字节(与 capture.ts 的 16k Float32 对齐后量化)。 */
49
+ send(pcm: Uint8Array): void;
50
+ /** 结束会话并释放连接(幂等)。 */
51
+ close(): void;
52
+ /** 上游事件回调(connect 后立即设置;close 后不再回调)。 */
53
+ onEvent: ((ev: RealtimeProviderEvent) => void) | null;
54
+ }
55
+ /** 实时 ASR provider 工厂:每次会话建立一条新连接。 */
56
+ export interface RealtimeProvider {
57
+ connect(): Promise<RealtimeProviderConnection>;
58
+ }
59
+ /** 假 provider 的 VAD 调参(仅测试/开发用,不进 settings:真 provider 的断句在服务端)。 */
60
+ export interface FakeRealtimeTuning {
61
+ /** RMS 判有声阈值(0~1,按 int16 全幅 32768 归一)。 */
62
+ rms: number;
63
+ /** 连续静音多久切一段(毫秒)。 */
64
+ silenceMs: number;
65
+ /** 实际语音短于此不成为一段(毫秒):杂音不该占一次「回合」。 */
66
+ minSpeechMs: number;
67
+ /** 单段语音长度上限(毫秒):说个不停也要轮换出 final。 */
68
+ maxSegmentMs: number;
69
+ /** 句内每隔多久吐一次 partial(毫秒)。 */
70
+ partialMs: number;
71
+ }
72
+ /** 假 provider 默认调参:阈值偏低(假数据通常安静),便于测试造简单波形。 */
73
+ export declare const FAKE_REALTIME_DEFAULTS: FakeRealtimeTuning;
74
+ /** 创建假 provider(I3 阶段 host 用它驱动整条通道;I5 换成真 provider 实现)。 */
75
+ export declare function createFakeRealtimeProvider(tuning?: FakeRealtimeTuning): RealtimeProvider;
76
+ //# sourceMappingURL=realtime-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-provider.d.ts","sourceRoot":"","sources":["../../src/realtime-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,wIAAwI;AACxI,MAAM,MAAM,qBAAqB;AAC/B,2CAA2C;AACzC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACnC,kCAAkC;GAChC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACjC,oEAAoE;GAClE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE;AAC5B,oEAAoE;GAClE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE;AAC5B;;;;;;;8CAO8C;GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAEpD,4BAA4B;AAC5B,MAAM,WAAW,0BAA0B;IACzC,mEAAmE;IACnE,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAA;IAC3B,qBAAqB;IACrB,KAAK,IAAI,IAAI,CAAA;IACb,yCAAyC;IACzC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;CACtD;AAED,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAA;CAC/C;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAA;IACnB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,+CAA+C;AAC/C,eAAO,MAAM,sBAAsB,EAAE,kBAMpC,CAAA;AAmHD,4DAA4D;AAC5D,wBAAgB,0BAA0B,CACxC,MAAM,GAAE,kBAA2C,GAClD,gBAAgB,CAIlB"}
@@ -0,0 +1,42 @@
1
+ /** dsh-asr-voice — I6:云端 TTS 通道(host 半区)。
2
+ * 浏览器不持 API key(与 ASR 同构):文本经本插件私有路由上行,host 用 DSH 凭据
3
+ * 打开阿里云百炼 Qwen-TTS-Realtime WebSocket,把 base64 PCM 转回浏览器播放。
4
+ * 协议(官方文档 help.aliyun.com/zh/model-studio/qwen-tts-realtime-api-reference):
5
+ * WebSocket URL = `wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=qwen3-tts-flash-realtime`
6
+ * 认证 = 握手 `Authorization: Bearer <api_key>`
7
+ * 客户端事件 = session.update(voice/response_format=pcm/sample_rate)→
8
+ * input_text_buffer.append(text) → input_text_buffer.commit →
9
+ * session.finish
10
+ * 服务端事件 = session.created/updated → response.created →
11
+ * response.audio.delta(base64 PCM,可多片)→ response.audio.done →
12
+ * response.done → session.finished;error
13
+ * 设计:一次 HTTP 请求 = 合成一整句。句子由 client 分句泵切成可念块(≤200 字符),
14
+ * 整段 PCM 一次返回即可(数秒语音 ≈ 几十 KB),不需要会话注册表与流式下行——
15
+ * 无状态、无泄漏面,比 TTS 版 RealtimeHost 简单得多。音色默认 Cherry。
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ import type { IncomingMessage, ServerResponse } from 'node:http';
19
+ import { type CloudAsrConfig } from './transcribe.ts';
20
+ /** TTS 请求体(浏览器上行,无密钥材料)。 */
21
+ export interface TtsRequest {
22
+ /** 要合成的文本(一整句;空或超长拒绝)。 */
23
+ text: string;
24
+ /** 音色(可选,默认 Cherry;只透传给上游,不校验)。 */
25
+ voice?: string;
26
+ }
27
+ /** 一次合成的结果。 */
28
+ export interface TtsResult {
29
+ /** 合成音频:16k 单声道 int16 LE 小端字节(与采集链路同采样率,播放端零重采样)。 */
30
+ pcm: Uint8Array;
31
+ /** 上游标称采样率(未用到,保留供诊断)。 */
32
+ sampleRate: number;
33
+ }
34
+ /** 建一条 DashScope TTS 连接并合成一整句。 */
35
+ export declare function synthesize(apiKey: string, text: string, voice?: string, wssUrl?: string): Promise<TtsResult>;
36
+ /** 注册 TTS 路由:POST /api/asr-voice/tts。签名与兄弟路由(transcribe/optimize)同构。 */
37
+ export declare function registerTtsRoute(register: (def: {
38
+ kind: 'exact';
39
+ path: string;
40
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
41
+ }) => () => void, getTtsConfig: () => CloudAsrConfig | undefined, ctx: Context): () => void;
42
+ //# sourceMappingURL=realtime-tts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-tts.d.ts","sourceRoot":"","sources":["../../src/realtime-tts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEpE,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,eAAe;AACf,MAAM,WAAW,SAAS;IACxB,qDAAqD;IACrD,GAAG,EAAE,UAAU,CAAA;IACf,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAA;CACnB;AAOD,kCAAkC;AAClC,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,SAAW,EAChB,MAAM,SAAoD,GACzD,OAAO,CAAC,SAAS,CAAC,CA4FpB;AAOD,wEAAwE;AACxE,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,KAAK,MAAM,IAAI,EAC5I,YAAY,EAAE,MAAM,cAAc,GAAG,SAAS,EAC9C,GAAG,EAAE,OAAO,GACX,MAAM,IAAI,CA8BZ"}
@@ -0,0 +1,143 @@
1
+ /** dsh-asr-voice — host 半区:settings namespace + schema(设置页配置的权威源)。
2
+ * **API key 不在这里**:云端 ASR 的 key 统一存 DSH credentials 服务,引用名由
3
+ * `src/key-ref.ts` 派生(预置供应商与官方 LLM provider 同名,配过 LLM 就直接复用,
4
+ * 用户一次都不用填);LLM 提示词优化不存 key,它走 DSH 官方通道 ctx.llm。settings
5
+ * 只存有 baseUrl / model / mode 等**无密钥**的供应商元数据,因此浏览器拿到整份
6
+ * 文档也拿不到 key,客户端整段回写也不可能顺手删掉谁的密钥。
7
+ * 两个 `apiKey` 字段是 v0.1/v0.2 的遗留位置,本版本保留只为让 `src/index.ts` 的
8
+ * 一次性迁移读到旧值并搬进 credentials:字段一旦从 schema 消失,schemastery 会把
9
+ * 旧文档里的值直接剥掉,迁移就再也看不见它。它们声明为 `role('secret')`,从此不经
10
+ * `settings.describe` 渡到浏览器(此前是明文过境的,迁移后请自行清理
11
+ * `~/.dsh/settings.yaml` 中的历史残留)。下一版本再删除字段本身。
12
+ * 云端 ASR 支持**多供应商**(v0.2):`asr.cloud.providers` 为供应商列表(每个含
13
+ * 自己的 name/baseUrl/model/mode),`asr.cloud.active` 指定当前使用的供应商 id。
14
+ * 兼容旧单配置:仍保留 preset/baseUrl/model/mode 顶层字段,读取时若无 providers
15
+ * 则回退到旧单配置(向后兼容,写回优先新 shape)。
16
+ */
17
+ import z from '@deepseek-ai/schemastery';
18
+ /** 插件配置页的 settings namespace:注册后出现在「设置 → 插件 → 配置」分派列表。 */
19
+ export declare const ASR_VOICE_SETTINGS_NAMESPACE = "asr-voice";
20
+ /** 单个云端 ASR 供应商配置(密钥不在此处,见文件头)。 */
21
+ export declare const CloudProviderSchema: z<Schemastery.ObjectS<{
22
+ /** 供应商唯一 id(新增时由前端生成,如 crypto.randomUUID)。 */
23
+ id: z<string, string>;
24
+ /** 预置 id(openai | groq | siliconflow | mimo | dashscope | custom)。 */
25
+ preset: z<string, string>;
26
+ /** 显示名;自定义供应商同时是凭据引用名的派生依据(见 src/key-ref.ts)。 */
27
+ name: z<string, string>;
28
+ /** OpenAI-compatible base URL(预置自动填充,可改)。 */
29
+ baseUrl: z<string, string>;
30
+ /** 遗留密钥位置:只由 src/index.ts 的一次性迁移读取并搬进 credentials,之后恒为空。 */
31
+ apiKey: z<string, string>;
32
+ /** ASR 模型(预置自动填充,可改;可经「获取模型」动态拉取)。 */
33
+ model: z<string, string>;
34
+ /** 调用通道:auto(按模型名判定)/ transcriptions(whisper 式)/ chat(MiMo/Qwen-ASR)。 */
35
+ mode: z<string, string>;
36
+ }>, Schemastery.ObjectT<{
37
+ /** 供应商唯一 id(新增时由前端生成,如 crypto.randomUUID)。 */
38
+ id: z<string, string>;
39
+ /** 预置 id(openai | groq | siliconflow | mimo | dashscope | custom)。 */
40
+ preset: z<string, string>;
41
+ /** 显示名;自定义供应商同时是凭据引用名的派生依据(见 src/key-ref.ts)。 */
42
+ name: z<string, string>;
43
+ /** OpenAI-compatible base URL(预置自动填充,可改)。 */
44
+ baseUrl: z<string, string>;
45
+ /** 遗留密钥位置:只由 src/index.ts 的一次性迁移读取并搬进 credentials,之后恒为空。 */
46
+ apiKey: z<string, string>;
47
+ /** ASR 模型(预置自动填充,可改;可经「获取模型」动态拉取)。 */
48
+ model: z<string, string>;
49
+ /** 调用通道:auto(按模型名判定)/ transcriptions(whisper 式)/ chat(MiMo/Qwen-ASR)。 */
50
+ mode: z<string, string>;
51
+ }>>;
52
+ /** 云端 ASR 配置:多供应商列表 + active(含旧单配置兼容字段)。 */
53
+ export declare const CloudSchema: any;
54
+ /** LLM 提示词优化目标(DSH 已配置模型的 provider/model;空 = 用当前所选 LLM)。 */
55
+ export declare const LlmSchema: z<Schemastery.ObjectS<{
56
+ provider: z<string, string>;
57
+ model: z<string, string>;
58
+ }>, Schemastery.ObjectT<{
59
+ provider: z<string, string>;
60
+ model: z<string, string>;
61
+ }>>;
62
+ /** 插件设置 schema(与 client 的 AsrVoiceConfig 结构一致)。 */
63
+ export declare const AsrVoiceSettingsSchema: any;
64
+ /** 业务侧类型(手写,不依赖 schema 推断——schema 已注解为 Schemastery.Schema 以便声明可移植,TypeT 会退化为 any)。 */
65
+ export interface AsrVoiceCloudProvider {
66
+ id: string;
67
+ preset: string;
68
+ /** 显示名(自定义行的凭据引用派生依据)。 */
69
+ name: string;
70
+ baseUrl: string;
71
+ /** 遗留密钥位置:只有 host 读得到(role('secret') 使它不上线),迁移后即恒为空。 */
72
+ apiKey: string;
73
+ model: string;
74
+ mode: string;
75
+ }
76
+ export interface AsrVoiceSettings {
77
+ asr: {
78
+ provider: string;
79
+ cloud: {
80
+ providers: AsrVoiceCloudProvider[];
81
+ active: string;
82
+ preset: string;
83
+ baseUrl: string;
84
+ apiKey: string;
85
+ model: string;
86
+ mode: string;
87
+ };
88
+ };
89
+ optimize: {
90
+ mode: string;
91
+ /** LLM 模式入框方式:false(默认)= 快速入框+后台优化替换;true = 预览卡确认。 */
92
+ preview: boolean;
93
+ llm: {
94
+ provider: string;
95
+ model: string;
96
+ };
97
+ };
98
+ language: string;
99
+ behavior: {
100
+ autoSend: boolean;
101
+ /** 静音自动停止(默认关 = 手动关麦)。 */
102
+ silenceStop: boolean;
103
+ holdToTalk: boolean;
104
+ hotkey: string;
105
+ textMode: string;
106
+ copyToClipboard: boolean;
107
+ /** 单次录音最长时长(毫秒)。 */
108
+ maxRecordMs: number;
109
+ /** 静音判定阈值(RMS,0~1)。 */
110
+ silenceRms: number;
111
+ /** 静音持续多久即自动停止(毫秒)。 */
112
+ silenceMs: number;
113
+ };
114
+ realtime: {
115
+ /** 实时语音对话总开关。 */
116
+ enabled: boolean;
117
+ /** 实时引擎:browser | segmented | cloud。 */
118
+ engine: string;
119
+ /** 云端实时 provider(engine=cloud 时;'' = 内置模拟)。 */
120
+ provider: string;
121
+ /** 回复播报:browser | off。 */
122
+ tts: string;
123
+ /** 云端 TTS 音色(仅 tts=cloud 生效)。 */
124
+ ttsVoice: string;
125
+ /** 进出实时模式的快捷键('' = 关闭)。 */
126
+ hotkey: string;
127
+ turn: {
128
+ /** 转写文字静默多久算「说完了」(毫秒)。 */
129
+ settleMs: number;
130
+ /** 静音窗口之后再宽限这么久才提交(毫秒)。 */
131
+ tailMs: number;
132
+ };
133
+ /** 单次对话上限(毫秒):到点自动结束,麦克风不无人值守常开。 */
134
+ maxSessionMs: number;
135
+ speech: {
136
+ /** 首句最少字数。 */
137
+ firstSentenceMinChars: number;
138
+ /** 朗读看门狗(毫秒)。 */
139
+ utteranceWatchdogMs: number;
140
+ };
141
+ };
142
+ }
143
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /** dsh-asr-voice — host 半区:ASR 用量统计(计费相关,最小实现)。
2
+ * 记录每次成功转写的次数、累计字符数与最近一次的时间/供应商,经
3
+ * /api/asr-voice/stats 暴露给设置页展示。进程内内存(低优先级特性,
4
+ * 不落盘——重启清零,仅作参考)。
5
+ */
6
+ import type { IncomingMessage, ServerResponse } from 'node:http';
7
+ /** 用量快照。 */
8
+ export interface AsrStats {
9
+ /** 成功转写次数。 */
10
+ count: number;
11
+ /** 累计转写字符数(近似计费量纲)。 */
12
+ chars: number;
13
+ /** 最近一次成功转写时间(ms 时间戳;null = 尚未使用)。 */
14
+ lastAt: number | null;
15
+ /** 最近一次使用的供应商 id。 */
16
+ lastProvider: string;
17
+ }
18
+ /** 创建用量计数器(进程内)。 */
19
+ export declare function createAsrStats(): {
20
+ record(text: string, providerId: string): void;
21
+ snapshot(): AsrStats;
22
+ };
23
+ /** 注册 /api/asr-voice/stats 路由(GET):返回用量快照。 */
24
+ export declare function registerStatsRoute(register: (def: {
25
+ kind: 'exact';
26
+ path: string;
27
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
28
+ }) => () => void, getStats: () => AsrStats): () => void;
29
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/stats.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,YAAY;AACZ,MAAM,WAAW,QAAQ;IACvB,cAAc;IACd,KAAK,EAAE,MAAM,CAAA;IACb,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,sCAAsC;IACtC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,qBAAqB;IACrB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,oBAAoB;AACpB,wBAAgB,cAAc,IAAI;IAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,IAAI,QAAQ,CAAA;CAAE,CAazG;AAED,8CAA8C;AAC9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,KAAK,MAAM,IAAI,EAC5I,QAAQ,EAAE,MAAM,QAAQ,GACvB,MAAM,IAAI,CAUZ"}
@@ -0,0 +1,46 @@
1
+ /** dsh-asr-voice — host 半区:云端 ASR 转写代理。
2
+ * 浏览器把原始音频字节 POST 到 /api/asr-voice/transcribe(raw body,
3
+ * Content-Type 为音频 MIME),host 读取配置里的云端 baseUrl/model/mode,按
4
+ * src/key-ref.ts 派生的引用名向 DSH credentials 服务解析 API key,转发到上游并返回
5
+ * { ok, text }。API key 全程只在服务端,不进浏览器。
6
+ * 两条通道(mode):
7
+ * - transcriptions:whisper 式 multipart /audio/transcriptions(OpenAI / Groq /
8
+ * 硅基流动 / 本地 OpenAI-compatible 部署)。
9
+ * - chat:chat.completions + input_audio(base64 data URI)——小米 MiMo-V2.5-ASR、
10
+ * 通义 qwen3-asr-flash 等「音频大模型」的 OpenAI 兼容姿势。
11
+ * - auto:按模型名自动判定(模型名含 asr/audio/omni/sensevoice 走 chat,否则 transcriptions)。
12
+ * key 复用:引用名与官方 LLM provider 同名(OPENAI_API_KEY / MIMO_API_KEY / …),
13
+ * 用户在 DSH 里配过对应 LLM 就自动共用同一把 key,无需在插件设置里重复粘贴。
14
+ * 纯 Node 全局 fetch/FormData/Blob(Node 18+),跨平台。
15
+ */
16
+ import type { Context } from '@deepseek-ai/cordis';
17
+ import type { IncomingMessage, ServerResponse } from 'node:http';
18
+ import type { KeyRefSource } from './key-ref.ts';
19
+ /** 云端 ASR 配置面(来自 settings scope 解析出的当前生效供应商)。 id / preset / name 由 KeyRefSource 提供,name 是自定义供应商的显示名。 */
20
+ export interface CloudAsrConfig extends KeyRefSource {
21
+ baseUrl: string;
22
+ /** 遗留密钥位置:一次性迁移完成前仍可直接生效,完成后再无人写入。 */
23
+ apiKey: string;
24
+ model: string;
25
+ mode: string;
26
+ }
27
+ /** 解析最终 API key:settings 里的遗留值优先(一次性迁移完成前的兼容路径),否则按 派生引用名向 DSH credentials 服务解析,最后退回同名环境变量。预置供应商的引用名与 官方 LLM provider 同名,因此配过对应 LLM 的用户在这里天然命中同一把 key。
28
+ * 凭据服务 `resolve` 的契约是「未配置 → undefined」,**抛错 = 服务真故障**(后端不可用 /
29
+ * 权限拒绝),两者含义完全不同:早先空 catch 把故障吞成「返回空串」,用户看到的是
30
+ * "no API key" 提示,于是去设置页反复确认凭据明明存在,真实故障被彻底掩盖。现在把故障
31
+ * 原因留到「环境变量也没兜到 key」时抛出——那时真相就是「无法判断有没有 key」,比谎报
32
+ * 「没配 key」诚实。 */
33
+ export declare function resolveApiKey(ctx: Context, cfg: CloudAsrConfig): Promise<string>;
34
+ /** 注册 /api/asr-voice/transcribe 路由。
35
+ * @param register - webserver 的 register 方法(由调用方从 ctx 传入)。
36
+ * @param getCloudConfig - 读取当前生效云端 ASR 配置的 thunk。
37
+ * @param ctx - host context(供 MiMo key 兜底走 credentials 服务)。
38
+ * @param recordStats - 成功转写后回调(记录用量;可选)。
39
+ * @returns 路由 disposer(由 ctx.effect 挂载/回收)。
40
+ */
41
+ export declare function registerTranscribeRoute(register: (def: {
42
+ kind: 'exact';
43
+ path: string;
44
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
45
+ }) => () => void, getCloudConfig: () => CloudAsrConfig, ctx: Context, recordStats?: (text: string, providerId: string) => void): () => void;
46
+ //# sourceMappingURL=transcribe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcribe.d.ts","sourceRoot":"","sources":["../../src/transcribe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAMjD,wGAAwG;AACxG,MAAM,WAAW,cAAe,SAAQ,YAAY;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAkKD;;;;;kBAKkB;AAClB,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtF;AAmBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,KAAK,MAAM,IAAI,EAC5I,cAAc,EAAE,MAAM,cAAc,EACpC,GAAG,EAAE,OAAO,EACZ,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,GACvD,MAAM,IAAI,CAgDZ"}
@@ -0,0 +1 @@
1
+ {"root":["../../src/client/animate.ts","../../src/client/capture.ts","../../src/client/config.ts","../../src/client/hotkey.ts","../../src/client/index.ts","../../src/client/locales.ts","../../src/client/optimize.ts","../../src/client/pcm.ts","../../src/client/realtime-cloud-transport.ts","../../src/client/realtime-cloud.ts","../../src/client/realtime.ts","../../src/client/recorder.ts","../../src/client/rms-floor.ts","../../src/client/speech-out.ts","../../src/client/styles.ts","../../src/client/turn-guard.ts","../../src/client/vad.ts","../../src/client/settings-card.tsx","../../src/client/voice-button.tsx","../../src/client/voice-chat-button.tsx","../../src/presets.ts","../../src/key-ref.ts"],"version":"6.0.3"}
package/package.json ADDED
@@ -0,0 +1,146 @@
1
+ {
2
+ "name": "@bittersmilezzz/dsh-asr-voice",
3
+ "version": "0.3.1",
4
+ "description": "开口即成文 · Speak-to-prompt for DeepSeek Harness:云端 ASR 语音识别 + 提示词优化 + 填入草稿/自动发送,跨平台 macOS / Windows。",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": {
14
+ "types": "./lib/types/client/index.d.ts",
15
+ "default": "./lib/client.js"
16
+ },
17
+ "./cordis.patch.yml": "./cordis.patch.yml",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "lib",
22
+ "cordis.patch.yml",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.host.json && tsdown",
28
+ "build:client": "tsdown",
29
+ "bundle": "tsdown",
30
+ "typecheck": "tsc -p tsconfig.host.json --noEmit && tsc -p tsconfig.client.json --noEmit",
31
+ "test": "node --test test/*.test.mjs"
32
+ },
33
+ "license": "MIT",
34
+ "keywords": [
35
+ "dsh",
36
+ "dsh-plugin",
37
+ "deepseek-harness",
38
+ "voice-input",
39
+ "speech-to-text",
40
+ "asr",
41
+ "whisper",
42
+ "voice",
43
+ "prompt",
44
+ "ai-assistant",
45
+ "speech",
46
+ "语音输入",
47
+ "语音识别",
48
+ "语音转文字",
49
+ "提示词优化",
50
+ "语音助手",
51
+ "语音插件"
52
+ ],
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/bitterSmilezzz/dsh-asr-voice.git"
56
+ },
57
+ "homepage": "https://github.com/bitterSmilezzz/dsh-asr-voice#readme",
58
+ "bugs": {
59
+ "url": "https://github.com/bitterSmilezzz/dsh-asr-voice/issues"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "registry": "https://registry.npmjs.org/"
64
+ },
65
+ "dsh": {
66
+ "bundle": {
67
+ "patch": "./cordis.patch.yml"
68
+ },
69
+ "searchTerms": [
70
+ "语音输入",
71
+ "语音识别",
72
+ "ASR",
73
+ "听写",
74
+ "voice",
75
+ "speech"
76
+ ],
77
+ "client": {
78
+ "platform": "web",
79
+ "inject": [
80
+ "@deepseek-ai/dsh-client-ui-slots",
81
+ "@deepseek-ai/dsh-client-ui-settings",
82
+ "@deepseek-ai/dsh-client-ui-conversation",
83
+ "@deepseek-ai/dsh-client-ui-primitives",
84
+ "@deepseek-ai/dsh-client-locale",
85
+ "@deepseek-ai/dsh-client-ui-input-trigger",
86
+ "@deepseek-ai/dsh-client-ui-theme"
87
+ ]
88
+ }
89
+ },
90
+ "peerDependencies": {
91
+ "@deepseek-ai/cordis": "^4.0.2",
92
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
93
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
94
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
95
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
96
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-rc.2",
97
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
98
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
99
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
100
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
101
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
102
+ "@deepseek-ai/dsh-client-ui-theme": "^0.1.5-rc.2",
103
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
104
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
105
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
106
+ "@deepseek-ai/schemastery": "^3.18.2",
107
+ "react": "^18.2.0",
108
+ "react-dom": "^18.2.0"
109
+ },
110
+ "peerDependenciesMeta": {
111
+ "@deepseek-ai/dsh-client-ui-primitives": {
112
+ "optional": true
113
+ },
114
+ "@deepseek-ai/dsh-client-ui-slots": {
115
+ "optional": true
116
+ },
117
+ "react": {
118
+ "optional": true
119
+ },
120
+ "react-dom": {
121
+ "optional": true
122
+ }
123
+ },
124
+ "devDependencies": {
125
+ "@deepseek-ai/cordis": "^4.0.2",
126
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
127
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
128
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-rc.2",
129
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
130
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
131
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
132
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
133
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
134
+ "@deepseek-ai/dsh-client-ui-theme": "^0.1.5-rc.2",
135
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
136
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
137
+ "@deepseek-ai/schemastery": "^3.18.2",
138
+ "@types/node": "^26.2.0",
139
+ "@types/react": "~18.3.31",
140
+ "@types/react-dom": "~18.3.7",
141
+ "react": "^18.3.1",
142
+ "react-dom": "^18.3.1",
143
+ "tsdown": "^0.22.14",
144
+ "typescript": "^6.0.3"
145
+ }
146
+ }