@voiceloop/web-sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +642 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +312 -0
- package/dist/index.d.ts +312 -0
- package/dist/index.js +604 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +681 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +180 -0
- package/dist/react/index.d.ts +180 -0
- package/dist/react/index.js +659 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/audio.ts","../src/state-machine.ts","../src/session-state.ts","../src/client.ts"],"sourcesContent":["// Core (no React dependency)\nexport { VoiceLoopClient } from \"./client\"\nexport type { VoiceLoopClientOptions, BrainSendFrame } from \"./client\"\n\n// Protocol types\nexport * from \"./types\"\n\n// Session state types\nexport * from \"./session-state\"\n\n// Audio utilities\nexport { startMicCapture, createPcmPlayer, MIC_SAMPLE_RATE, TTS_SAMPLE_RATE } from \"./audio\"\nexport type { AudioCaptureHandle, PcmPlayerHandle } from \"./audio\"\n\n// Logger\nexport { log, warn, logError, getEntries, clearLog, subscribe, formatForCopy } from \"./logger\"\nexport type { LogLevel, LogEntry } from \"./logger\"\n\n// State machine (advanced use / testing)\nexport { reduceServerEvent, createStateMachineContext } from \"./state-machine\"\nexport type { StateMachineContext, SideEffect, ReduceResult } from \"./state-machine\"\n","// Module-level singleton — works outside React, zero dependencies.\n\nexport type LogLevel = \"info\" | \"warn\" | \"error\"\n\nexport type LogEntry = {\n id: number\n ts: number\n level: LogLevel\n tag: string\n msg: string\n detail?: string\n}\n\nconst MAX_ENTRIES = 300\nlet seq = 0\nconst entries: LogEntry[] = []\nconst listeners = new Set<() => void>()\n\nfunction emit() {\n listeners.forEach(fn => fn())\n}\n\nexport function log(tag: string, msg: string, detail?: string, level: LogLevel = \"info\") {\n entries.push({ id: seq++, ts: Date.now(), level, tag, msg, detail })\n if (entries.length > MAX_ENTRIES) entries.splice(0, entries.length - MAX_ENTRIES)\n emit()\n}\n\nexport function warn(tag: string, msg: string, detail?: string) {\n log(tag, msg, detail, \"warn\")\n}\n\nexport function logError(tag: string, msg: string, detail?: string) {\n log(tag, msg, detail, \"error\")\n}\n\nexport function getEntries(): readonly LogEntry[] {\n return entries\n}\n\nexport function clearLog() {\n entries.length = 0\n seq = 0\n emit()\n}\n\nexport function subscribe(fn: () => void): () => void {\n listeners.add(fn)\n return () => listeners.delete(fn)\n}\n\nexport function formatForCopy(): string {\n return entries.map(e => {\n const d = new Date(e.ts)\n const ts = [\n d.getHours().toString().padStart(2, \"0\"),\n d.getMinutes().toString().padStart(2, \"0\"),\n d.getSeconds().toString().padStart(2, \"0\"),\n ].join(\":\") + \".\" + d.getMilliseconds().toString().padStart(3, \"0\")\n const detail = e.detail ? ` ${e.detail}` : \"\"\n return `[${ts}] ${e.tag.padEnd(8)} ${e.msg}${detail}`\n }).join(\"\\n\")\n}\n","// Audio capture and PCM playback utilities.\n\nexport const MIC_SAMPLE_RATE = 16_000 // server expects 16 kHz\nexport const TTS_SAMPLE_RATE = 24_000 // server sends 24 kHz\nconst SCRIPT_PROCESSOR_FRAMES = 1024 // ~64 ms @ 16 kHz\n\n// ── Microphone capture ────────────────────────────────────────────────────────\n\nexport type AudioCaptureHandle = {\n stop: () => void\n getVolume: () => number // 0–1\n}\n\nexport async function startMicCapture(\n onPcm: (chunk: ArrayBuffer) => void\n): Promise<AudioCaptureHandle> {\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: MIC_SAMPLE_RATE,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n })\n\n const ctx = new AudioContext({ sampleRate: MIC_SAMPLE_RATE })\n const source = ctx.createMediaStreamSource(stream)\n const processor = ctx.createScriptProcessor(SCRIPT_PROCESSOR_FRAMES, 1, 1)\n\n let lastVolume = 0\n\n processor.onaudioprocess = (e) => {\n const f32 = e.inputBuffer.getChannelData(0)\n\n // RMS volume for meter\n let sum = 0\n for (let i = 0; i < f32.length; i++) sum += f32[i] * f32[i]\n lastVolume = Math.min(1, Math.sqrt(sum / f32.length) * 10)\n\n // Float32 → Int16 LE\n const i16 = new Int16Array(f32.length)\n for (let i = 0; i < f32.length; i++) {\n i16[i] = Math.max(-32768, Math.min(32767, f32[i] * 32768))\n }\n onPcm(i16.buffer)\n }\n\n source.connect(processor)\n processor.connect(ctx.destination)\n\n return {\n stop() {\n processor.disconnect()\n source.disconnect()\n ctx.close()\n stream.getTracks().forEach((t) => t.stop())\n lastVolume = 0\n },\n getVolume: () => lastVolume,\n }\n}\n\n// ── TTS PCM playback ──────────────────────────────────────────────────────────\n\nexport type PcmPlayerHandle = {\n enqueue: (pcm: Uint8Array) => void\n flush: () => void // discard buffered audio immediately (barge-in)\n resume: () => void // call on button click to unblock browser autoplay policy\n}\n\nexport function createPcmPlayer(): PcmPlayerHandle {\n let ctx: AudioContext | null = null\n let nextPlayTime = 0\n\n function ensureCtx() {\n if (!ctx) ctx = new AudioContext({ sampleRate: TTS_SAMPLE_RATE })\n if (ctx.state === \"suspended\") ctx.resume()\n return ctx\n }\n\n return {\n resume() {\n ensureCtx()\n },\n\n enqueue(pcm: Uint8Array) {\n const ac = ensureCtx()\n const byteLen = pcm.byteLength & ~1\n if (byteLen === 0) return\n\n const buf = new ArrayBuffer(byteLen)\n new Uint8Array(buf).set(pcm.subarray(0, byteLen))\n\n const i16 = new Int16Array(buf)\n const f32 = new Float32Array(i16.length)\n for (let i = 0; i < i16.length; i++) f32[i] = i16[i] / 32768\n\n const audioBuf = ac.createBuffer(1, f32.length, TTS_SAMPLE_RATE)\n audioBuf.copyToChannel(f32, 0)\n\n const src = ac.createBufferSource()\n src.buffer = audioBuf\n src.connect(ac.destination)\n\n const startAt = Math.max(ac.currentTime, nextPlayTime)\n src.start(startAt)\n nextPlayTime = startAt + audioBuf.duration\n },\n\n flush() {\n if (ctx) {\n ctx.close()\n ctx = null\n }\n nextPlayTime = 0\n },\n }\n}\n","import { log, logError, warn } from \"./logger\"\nimport type { ServerEvent } from \"./types\"\nimport type { TurnMetricsSummary, Utterance, VoiceSessionState } from \"./session-state\"\n\n// Mutable context that tracks cross-event state (previously useRef values in the hook).\nexport type StateMachineContext = {\n assistantBubbleOpen: boolean\n userTurnActive: boolean\n incompleteAccum: string\n hasIncompleteUserTurn: boolean\n preLLMText: string\n}\n\nexport function createStateMachineContext(): StateMachineContext {\n return {\n assistantBubbleOpen: false,\n userTurnActive: false,\n incompleteAccum: \"\",\n hasIncompleteUserTurn: false,\n preLLMText: \"\",\n }\n}\n\nexport type SideEffect = { kind: \"flush_player\" }\n\nexport type ReduceResult = {\n state: VoiceSessionState\n ctx: StateMachineContext\n effects: SideEffect[]\n}\n\n// ── Pure state helpers ────────────────────────────────────────────────────────\n\nfunction patch(state: VoiceSessionState, p: Partial<VoiceSessionState>): VoiceSessionState {\n return { ...state, ...p }\n}\n\nfunction finaliseLastUtterance(\n state: VoiceSessionState,\n role: \"user\" | \"assistant\",\n finalText?: string,\n): VoiceSessionState {\n const list = [...state.utterances]\n for (let i = list.length - 1; i >= 0; i--) {\n if (list[i].role === role && list[i].partial) {\n list[i] = {\n ...list[i],\n text: finalText ?? list[i].text,\n partial: false,\n fillerPrefix: undefined,\n }\n break\n }\n }\n return { ...state, utterances: list }\n}\n\nfunction upsertAssistantBubble(\n state: VoiceSessionState,\n update: (prev: Utterance | null) => Utterance,\n): VoiceSessionState {\n const list = [...state.utterances]\n for (let i = list.length - 1; i >= 0; i--) {\n if (list[i].role === \"assistant\" && list[i].partial) {\n list[i] = update(list[i])\n return { ...state, utterances: list }\n }\n }\n const created = update(null)\n return { ...state, utterances: [...list, created] }\n}\n\n// ── Core reducer ──────────────────────────────────────────────────────────────\n\nexport function reduceServerEvent(\n state: VoiceSessionState,\n ctx: StateMachineContext,\n evt: ServerEvent,\n): ReduceResult {\n // Clone ctx so callers can diff before/after\n ctx = { ...ctx }\n const effects: SideEffect[] = []\n\n switch (evt.type) {\n case \"session.config\":\n log(\"session\", `config brain=${evt.brain_mode} vad_stop=${evt.turn_policy.vad_stop_secs * 1000}ms eot=${evt.turn_policy.eot_strategy} smart_eot=${evt.capabilities.smart_eot}`)\n state = patch(state, { session: \"idle\", error: null })\n break\n\n case \"transcript.partial\": {\n state = patch(state, { session: \"listening\" })\n const list = [...state.utterances]\n const last = list.length > 0 ? list[list.length - 1] : null\n const prefix = ctx.incompleteAccum\n const display = prefix ? prefix + evt.text : evt.text\n if (last?.role === \"user\" && last.partial) {\n list[list.length - 1] = { ...last, text: display }\n } else {\n list.push({ role: \"user\", text: display, partial: true })\n }\n state = { ...state, utterances: list }\n break\n }\n\n case \"transcript.final\": {\n const prefix = ctx.incompleteAccum\n const display = prefix ? prefix + evt.text : evt.text\n log(\"asr\", `final \"${evt.text}\"`)\n ctx.preLLMText = ctx.preLLMText ? ctx.preLLMText + display : display\n ctx.userTurnActive = false\n break\n }\n\n case \"transcript.timing\":\n log(\"asr\", `timing wait=${evt.wait_ms}ms`)\n {\n const list = [...state.utterances]\n for (let i = list.length - 1; i >= 0; i--) {\n if (list[i].role === \"user\") {\n const prev = list[i]\n list[i] = { ...prev, waitTimes: [...(prev.waitTimes ?? []), evt.wait_ms] }\n break\n }\n }\n state = { ...state, utterances: list }\n }\n break\n\n case \"transcript.incomplete\":\n log(\"asr\", `incomplete \"${evt.text}\" wait=${evt.wait_ms}ms`)\n ctx.incompleteAccum = (ctx.incompleteAccum || \"\") + evt.text\n ctx.hasIncompleteUserTurn = true\n {\n const list = [...state.utterances]\n for (let i = list.length - 1; i >= 0; i--) {\n if (list[i].role === \"user\") {\n const prev = list[i]\n list[i] = {\n ...prev,\n text: ctx.incompleteAccum,\n partial: true,\n incomplete: true,\n waitTimes: [...(prev.waitTimes ?? []), ...(evt.wait_ms != null ? [evt.wait_ms] : [])],\n }\n break\n }\n }\n state = { ...state, utterances: list }\n }\n state = patch(state, { session: \"idle\" })\n break\n\n case \"turn.started\":\n log(\"turn\", \"started\")\n ctx.userTurnActive = true\n if (ctx.assistantBubbleOpen) {\n ctx.assistantBubbleOpen = false\n state = finaliseLastUtterance(state, \"assistant\")\n }\n if (!ctx.hasIncompleteUserTurn) {\n ctx.incompleteAccum = \"\"\n }\n state = patch(state, { session: \"listening\" })\n break\n\n case \"turn.ended\":\n log(\"turn\", `ended transcript=\"${evt.transcript ?? \"\"}\"`)\n if (!ctx.hasIncompleteUserTurn) {\n state = finaliseLastUtterance(state, \"user\", ctx.preLLMText || undefined)\n ctx.incompleteAccum = \"\"\n ctx.preLLMText = \"\"\n }\n state = patch(state, { session: \"thinking\" })\n break\n\n case \"filler.started\":\n log(\"filler\", `started id=${evt.id} text=\"${evt.text}\"`)\n state = patch(state, { session: \"speaking\" })\n state = upsertAssistantBubble(state, (prev) => {\n if (!prev) {\n return { role: \"assistant\", text: evt.text, partial: true, fillerPrefix: evt.text }\n }\n if (prev.fillerPrefix || prev.text.startsWith(evt.text)) {\n return prev\n }\n const body = prev.text\n return {\n ...prev,\n fillerPrefix: evt.text,\n text: body ? `${evt.text} ${body}` : evt.text,\n }\n })\n ctx.assistantBubbleOpen = true\n break\n\n case \"filler.stopped\":\n log(\"filler\", `stopped id=${evt.id}`)\n break\n\n case \"filler.skipped\":\n log(\"filler\", `skipped wait_ms=${evt.wait_ms}`)\n break\n\n case \"assistant.delta\":\n state = patch(state, { session: \"speaking\" })\n state = upsertAssistantBubble(state, (prev) => {\n if (!prev) {\n return { role: \"assistant\", text: evt.text, partial: true }\n }\n const prefix = prev.fillerPrefix ?? \"\"\n const body = prefix && prev.text.startsWith(prefix)\n ? prev.text.slice(prefix.length).trimStart()\n : prev.text\n const joined = body + evt.text\n return {\n ...prev,\n text: prefix ? `${prefix} ${joined}` : joined,\n }\n })\n ctx.assistantBubbleOpen = true\n break\n\n case \"tts.started\":\n log(\"tts\", \"started\")\n state = patch(state, { session: \"speaking\" })\n break\n\n case \"tts.stopped\":\n log(\"tts\", `stopped reason=${evt.reason}`)\n break\n\n case \"turn.metrics\": {\n const prevCurrent = state.latency.current\n const merged: TurnMetricsSummary =\n prevCurrent && prevCurrent.turn_id === evt.turn_id\n ? { turn_id: evt.turn_id, ftl: evt.ftl ?? prevCurrent.ftl, il: evt.il ?? prevCurrent.il, ts: Date.now() }\n : { turn_id: evt.turn_id, ftl: evt.ftl, il: evt.il, ts: Date.now() }\n const history =\n state.latency.history[0]?.turn_id === evt.turn_id\n ? [merged, ...state.latency.history.slice(1)]\n : [merged, ...state.latency.history].slice(0, 50)\n state = { ...state, latency: { current: merged, history } }\n log(\"turn\", `metrics ftl=${evt.ftl?.total_ms ?? \"-\"}ms il=${evt.il?.total_ms ?? \"-\"}ms`)\n break\n }\n\n case \"assistant.done\":\n log(\"turn\", \"assistant done\")\n ctx.assistantBubbleOpen = false\n state = finaliseLastUtterance(state, \"assistant\")\n if (ctx.hasIncompleteUserTurn) {\n state = finaliseLastUtterance(state, \"user\")\n ctx.incompleteAccum = \"\"\n ctx.hasIncompleteUserTurn = false\n }\n ctx.preLLMText = \"\"\n state = patch(state, { session: \"idle\" })\n break\n\n case \"interrupted\":\n log(\"turn\", \"interrupted\")\n effects.push({ kind: \"flush_player\" })\n ctx.assistantBubbleOpen = false\n state = finaliseLastUtterance(state, \"assistant\")\n if (ctx.hasIncompleteUserTurn) {\n ctx.incompleteAccum = \"\"\n ctx.hasIncompleteUserTurn = false\n }\n ctx.preLLMText = \"\"\n state = patch(state, { session: \"listening\" })\n break\n\n case \"error\":\n logError(\"ws\", evt.message)\n state = patch(state, { error: evt.message })\n break\n\n case \"brain.request\":\n // Handled by VoiceLoopClient before reaching the state machine\n warn(\"ws\", \"unexpected brain.request in state machine\")\n break\n }\n\n return { state, ctx, effects }\n}\n","import type { ConnectionState, FtlMetrics, IlMetrics, SessionState } from \"./types\"\n\nexport type Utterance = {\n role: \"user\" | \"assistant\"\n text: string\n partial?: boolean\n incomplete?: boolean // semantic EoT rejected at least once (user turns)\n fillerPrefix?: string // filler phrase prepended to this assistant turn\n waitTimes?: number[] // fallback wait durations (ms) per rejected pause\n}\n\nexport type TurnMetricsSummary = {\n turn_id: string\n ftl: FtlMetrics | null\n il: IlMetrics | null\n ts: number\n}\n\nexport type VoiceSessionState = {\n connection: ConnectionState\n session: SessionState\n utterances: Utterance[]\n volume: number // 0–1, microphone RMS\n error: string | null\n latency: {\n current: TurnMetricsSummary | null\n history: TurnMetricsSummary[]\n }\n}\n\nexport function createInitialState(): VoiceSessionState {\n return {\n connection: \"disconnected\",\n session: \"idle\",\n utterances: [],\n volume: 0,\n error: null,\n latency: { current: null, history: [] },\n }\n}\n","import { log, logError, warn, type LogEntry } from \"./logger\"\nimport { createPcmPlayer, startMicCapture, type AudioCaptureHandle, type PcmPlayerHandle } from \"./audio\"\nimport { reduceServerEvent, createStateMachineContext, type StateMachineContext } from \"./state-machine\"\nimport { createInitialState, type VoiceSessionState } from \"./session-state\"\nimport type { BrainDeltaFrame, BrainDoneFrame, BrainErrorFrame, BrainRequestEvent, ServerEvent, TurnPolicy } from \"./types\"\n\n// ── Minimal typed EventEmitter ────────────────────────────────────────────────\n\ntype EventMap = {\n state: (s: VoiceSessionState) => void\n log: (e: LogEntry) => void\n}\n\nclass TypedEmitter {\n private _listeners = new Map<string, Set<(...args: unknown[]) => void>>()\n\n on<K extends keyof EventMap>(event: K, fn: EventMap[K]): this {\n if (!this._listeners.has(event)) this._listeners.set(event, new Set())\n this._listeners.get(event)!.add(fn as (...args: unknown[]) => void)\n return this\n }\n\n off<K extends keyof EventMap>(event: K, fn: EventMap[K]): this {\n this._listeners.get(event)?.delete(fn as (...args: unknown[]) => void)\n return this\n }\n\n protected emit<K extends keyof EventMap>(event: K, ...args: Parameters<EventMap[K]>): void {\n this._listeners.get(event)?.forEach(fn => fn(...(args as unknown[])))\n }\n}\n\n// ── Options ───────────────────────────────────────────────────────────────────\n\nexport type BrainSendFrame = (frame: BrainDeltaFrame | BrainDoneFrame | BrainErrorFrame) => void\n\nexport type VoiceLoopClientOptions = {\n serverUrl?: string\n apiKey?: string\n /** 话轮策略与 TTS 参数,含垫词、打断、EoT、音色、语速等。 */\n turnPolicy?: TurnPolicy\n /** Called when the server delegates an LLM request to the client. */\n onBrainRequest?: (evt: BrainRequestEvent, sendFrame: BrainSendFrame) => void | Promise<void>\n /** Inject custom microphone capture (useful for testing). */\n micCapture?: (onPcm: (chunk: ArrayBuffer) => void) => Promise<AudioCaptureHandle>\n /** Inject custom PCM player factory (useful for testing). */\n playerFactory?: () => PcmPlayerHandle\n}\n\nconst DEFAULT_SERVER = \"http://localhost:9000\"\nconst VOLUME_INTERVAL_MS = 50\n\n// ── VoiceLoopClient ───────────────────────────────────────────────────────────\n\nexport class VoiceLoopClient extends TypedEmitter {\n private _state: VoiceSessionState = createInitialState()\n private _ctx: StateMachineContext = createStateMachineContext()\n\n private _ws: WebSocket | null = null\n private _mic: AudioCaptureHandle | null = null\n private _player: PcmPlayerHandle\n private _volTimer: ReturnType<typeof setInterval> | null = null\n\n private readonly _serverUrl: string\n private readonly _apiKey: string\n private readonly _turnPolicy: TurnPolicy | undefined\n private readonly _onBrainRequest: VoiceLoopClientOptions[\"onBrainRequest\"]\n private readonly _micCapture: (onPcm: (chunk: ArrayBuffer) => void) => Promise<AudioCaptureHandle>\n\n constructor(opts: VoiceLoopClientOptions = {}) {\n super()\n this._serverUrl = opts.serverUrl ?? DEFAULT_SERVER\n this._apiKey = opts.apiKey ?? \"dev-key\"\n this._turnPolicy = opts.turnPolicy\n this._onBrainRequest = opts.onBrainRequest\n this._micCapture = opts.micCapture ?? startMicCapture\n this._player = (opts.playerFactory ?? createPcmPlayer)()\n }\n\n get state(): VoiceSessionState {\n return this._state\n }\n\n // ── Connection ──────────────────────────────────────────────────────────────\n\n async connect(): Promise<void> {\n if (this._ws) return\n\n this._player.resume()\n\n log(\"session\", `create server=${this._serverUrl}`)\n this._applyPatch({ connection: \"connecting\", error: null, utterances: [] })\n this._ctx = createStateMachineContext()\n\n // 1. Create session\n let token: string\n try {\n let res: Response\n try {\n const { tts_options, ...policyRest } = this._turnPolicy ?? {}\n const sessionBody = {\n brain_mode: \"client\",\n ...(Object.keys(policyRest).length > 0 ? { turn_policy: policyRest } : {}),\n ...(tts_options ? { tts_options } : {}),\n }\n log(\"session\", `create body=${JSON.stringify(sessionBody)}`)\n res = await fetch(`${this._serverUrl}/v1/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify(sessionBody),\n })\n } catch {\n throw new Error(`无法连接服务器(${this._serverUrl}),请检查 serverUrl 是否正确,以及服务是否在运行`)\n }\n if (res.status === 401) throw new Error(`鉴权失败(401),请检查 apiKey 是否正确`)\n if (res.status === 403) throw new Error(`无访问权限(403),请联系管理员确认 apiKey 权限`)\n if (res.status === 404) throw new Error(`接口不存在(404),请检查 serverUrl 是否正确,不要带路径后缀`)\n if (!res.ok) throw new Error(`服务器错误(${res.status}),请联系管理员排查服务端日志`)\n const data = await res.json() as { session_token: string; session_id: string }\n token = data.session_token\n log(\"session\", `created id=${data.session_id}`)\n } catch (e) {\n logError(\"session\", `create failed ${e}`)\n this._applyPatch({ connection: \"disconnected\", error: String(e) })\n return\n }\n\n // 2. Open WebSocket\n const wsUrl = this._serverUrl.replace(/^http/, \"ws\") + `/ws?token=${token}`\n log(\"ws\", `connecting ${wsUrl}`)\n const ws = new WebSocket(wsUrl)\n ws.binaryType = \"arraybuffer\"\n this._ws = ws\n\n ws.onopen = async () => {\n log(\"ws\", \"open\")\n this._applyPatch({ connection: \"connected\" })\n\n // 3. Start microphone\n try {\n this._mic = await this._micCapture((chunk) => {\n if (ws.readyState === WebSocket.OPEN) ws.send(chunk)\n })\n log(\"mic\", \"capture started\")\n\n this._volTimer = setInterval(() => {\n this._applyPatch({ volume: this._mic?.getVolume() ?? 0 })\n }, VOLUME_INTERVAL_MS)\n } catch (e) {\n logError(\"mic\", `capture failed ${e}`)\n const name = e instanceof Error ? e.name : \"\"\n const msg = name === \"NotAllowedError\"\n ? \"麦克风权限被拒绝,请在浏览器地址栏允许麦克风访问\"\n : name === \"NotFoundError\"\n ? \"未找到麦克风设备,请确认设备已连接\"\n : `麦克风启动失败:${e}`\n this._applyPatch({ error: msg })\n ws.close()\n }\n }\n\n ws.onmessage = (e) => {\n if (e.data instanceof ArrayBuffer) {\n this._player.enqueue(new Uint8Array(e.data))\n return\n }\n try {\n const evt = JSON.parse(e.data) as ServerEvent\n if (evt.type === \"brain.request\") {\n if (this._onBrainRequest) {\n this._onBrainRequest(evt, (frame) => {\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(frame))\n })\n } else {\n warn(\"ws\", \"brain.request received but no onBrainRequest handler provided\")\n this._applyPatch({ error: \"收到 brain.request 但未提供 onBrainRequest 回调,AI 将无法回答。请在 VoiceLoopClient 初始化时传入 onBrainRequest\" })\n }\n return\n }\n this._handleEvent(evt)\n } catch {\n warn(\"ws\", \"malformed frame\", typeof e.data === \"string\" ? e.data.slice(0, 100) : \"binary\")\n }\n }\n\n ws.onclose = (e) => {\n log(\"ws\", `closed code=${e.code} reason=${e.reason || \"(none)\"}`, undefined,\n e.code >= 4000 ? \"warn\" : \"info\")\n this._cleanup()\n // Skip if disconnect() already handled the state update\n if (this._ws !== null || this._state.connection !== \"disconnected\") {\n this._ws = null\n this._applyPatch({ connection: \"disconnected\", session: \"idle\", volume: 0 })\n }\n }\n\n ws.onerror = () => {\n logError(\"ws\", \"error\")\n this._applyPatch({ error: `WebSocket 连接失败,请检查网络或联系管理员(serverUrl: ${this._serverUrl})` })\n }\n }\n\n disconnect(): void {\n log(\"session\", \"disconnect\")\n this._cleanup()\n const ws = this._ws\n this._ws = null // nullify first so onclose skips the duplicate patch\n ws?.close()\n this._applyPatch({ connection: \"disconnected\", session: \"idle\", volume: 0 })\n }\n\n // ── Control frames ──────────────────────────────────────────────────────────\n\n barge(): void {\n this._send({ type: \"barge\" })\n }\n\n endTurn(transcript?: string): void {\n this._send({ type: \"turn.end\", ...(transcript ? { transcript } : {}) })\n }\n\n say(text: string, voice?: string): void {\n this._send({ type: \"say\", text, ...(voice ? { voice } : {}) })\n }\n\n // ── Private ─────────────────────────────────────────────────────────────────\n\n private _send(frame: object): void {\n if (this._ws?.readyState === WebSocket.OPEN) {\n this._ws.send(JSON.stringify(frame))\n }\n }\n\n private _handleEvent(evt: ServerEvent): void {\n const { state, ctx, effects } = reduceServerEvent(this._state, this._ctx, evt)\n this._ctx = ctx\n if (state !== this._state) {\n this._state = state\n this.emit(\"state\", state)\n }\n for (const effect of effects) {\n if (effect.kind === \"flush_player\") {\n this._player.flush()\n }\n }\n }\n\n private _applyPatch(p: Partial<VoiceSessionState>): void {\n this._state = { ...this._state, ...p }\n this.emit(\"state\", this._state)\n }\n\n private _cleanup(): void {\n if (this._volTimer) {\n clearInterval(this._volTimer)\n this._volTimer = null\n }\n this._mic?.stop()\n this._mic = null\n this._player.flush()\n }\n}\n"],"mappings":"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","names":["_a","_a","_b","e"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
type LogLevel = "info" | "warn" | "error";
|
|
2
|
+
type LogEntry = {
|
|
3
|
+
id: number;
|
|
4
|
+
ts: number;
|
|
5
|
+
level: LogLevel;
|
|
6
|
+
tag: string;
|
|
7
|
+
msg: string;
|
|
8
|
+
detail?: string;
|
|
9
|
+
};
|
|
10
|
+
declare function log(tag: string, msg: string, detail?: string, level?: LogLevel): void;
|
|
11
|
+
declare function warn(tag: string, msg: string, detail?: string): void;
|
|
12
|
+
declare function logError(tag: string, msg: string, detail?: string): void;
|
|
13
|
+
declare function getEntries(): readonly LogEntry[];
|
|
14
|
+
declare function clearLog(): void;
|
|
15
|
+
declare function subscribe(fn: () => void): () => void;
|
|
16
|
+
declare function formatForCopy(): string;
|
|
17
|
+
|
|
18
|
+
declare const MIC_SAMPLE_RATE = 16000;
|
|
19
|
+
declare const TTS_SAMPLE_RATE = 24000;
|
|
20
|
+
type AudioCaptureHandle = {
|
|
21
|
+
stop: () => void;
|
|
22
|
+
getVolume: () => number;
|
|
23
|
+
};
|
|
24
|
+
declare function startMicCapture(onPcm: (chunk: ArrayBuffer) => void): Promise<AudioCaptureHandle>;
|
|
25
|
+
type PcmPlayerHandle = {
|
|
26
|
+
enqueue: (pcm: Uint8Array) => void;
|
|
27
|
+
flush: () => void;
|
|
28
|
+
resume: () => void;
|
|
29
|
+
};
|
|
30
|
+
declare function createPcmPlayer(): PcmPlayerHandle;
|
|
31
|
+
|
|
32
|
+
type SessionConfig = {
|
|
33
|
+
type: "session.config";
|
|
34
|
+
session_id: string;
|
|
35
|
+
audio_format: "pcm";
|
|
36
|
+
sample_rate: number;
|
|
37
|
+
brain_mode: "server" | "client";
|
|
38
|
+
turn_policy: {
|
|
39
|
+
vad_stop_secs: number;
|
|
40
|
+
vad_start_secs: number;
|
|
41
|
+
allow_interruptions: boolean;
|
|
42
|
+
eot_strategy: "vad" | "smart";
|
|
43
|
+
};
|
|
44
|
+
capabilities: {
|
|
45
|
+
server_vad: boolean;
|
|
46
|
+
client_barge: boolean;
|
|
47
|
+
client_turn_end: boolean;
|
|
48
|
+
client_say: boolean;
|
|
49
|
+
smart_eot: boolean;
|
|
50
|
+
filler: boolean;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
type FillerStartedEvent = {
|
|
54
|
+
type: "filler.started";
|
|
55
|
+
id: string;
|
|
56
|
+
text: string;
|
|
57
|
+
category?: string | null;
|
|
58
|
+
duration_ms: number;
|
|
59
|
+
};
|
|
60
|
+
type FillerStoppedEvent = {
|
|
61
|
+
type: "filler.stopped";
|
|
62
|
+
id: string;
|
|
63
|
+
};
|
|
64
|
+
type FillerSkippedEvent = {
|
|
65
|
+
type: "filler.skipped";
|
|
66
|
+
reason: "tts_fast";
|
|
67
|
+
wait_ms: number;
|
|
68
|
+
};
|
|
69
|
+
type BrainRequestEvent = {
|
|
70
|
+
type: "brain.request";
|
|
71
|
+
transcript: string;
|
|
72
|
+
history: Array<{
|
|
73
|
+
role: string;
|
|
74
|
+
content: string;
|
|
75
|
+
}>;
|
|
76
|
+
generation_id: string;
|
|
77
|
+
session_id: string;
|
|
78
|
+
/** Original filler phrase already spoken to the user (optional). */
|
|
79
|
+
filler_prefix?: string;
|
|
80
|
+
/** Server-built prompt append; concatenate to your system prompt. */
|
|
81
|
+
system_append?: string;
|
|
82
|
+
};
|
|
83
|
+
type AsrPartialEvent = {
|
|
84
|
+
type: "transcript.partial";
|
|
85
|
+
text: string;
|
|
86
|
+
};
|
|
87
|
+
type AsrFinalEvent = {
|
|
88
|
+
type: "transcript.final";
|
|
89
|
+
text: string;
|
|
90
|
+
};
|
|
91
|
+
type AsrIncompleteEvent = {
|
|
92
|
+
type: "transcript.incomplete";
|
|
93
|
+
text: string;
|
|
94
|
+
wait_ms?: number;
|
|
95
|
+
};
|
|
96
|
+
type AsrTimingEvent = {
|
|
97
|
+
type: "transcript.timing";
|
|
98
|
+
wait_ms: number;
|
|
99
|
+
};
|
|
100
|
+
type TurnStartedEvent = {
|
|
101
|
+
type: "turn.started";
|
|
102
|
+
};
|
|
103
|
+
type TurnEndedEvent = {
|
|
104
|
+
type: "turn.ended";
|
|
105
|
+
transcript?: string;
|
|
106
|
+
};
|
|
107
|
+
type AssistantDelta = {
|
|
108
|
+
type: "assistant.delta";
|
|
109
|
+
text: string;
|
|
110
|
+
phase?: "main";
|
|
111
|
+
generationId?: string;
|
|
112
|
+
generation_id?: string;
|
|
113
|
+
};
|
|
114
|
+
type TtsStartedEvent = {
|
|
115
|
+
type: "tts.started";
|
|
116
|
+
};
|
|
117
|
+
type TtsStoppedEvent = {
|
|
118
|
+
type: "tts.stopped";
|
|
119
|
+
reason: "completed" | "interrupted";
|
|
120
|
+
};
|
|
121
|
+
type AssistantDoneEvent = {
|
|
122
|
+
type: "assistant.done";
|
|
123
|
+
};
|
|
124
|
+
type InterruptedEvent = {
|
|
125
|
+
type: "interrupted";
|
|
126
|
+
};
|
|
127
|
+
type ErrorEvent = {
|
|
128
|
+
type: "error";
|
|
129
|
+
code?: number;
|
|
130
|
+
message: string;
|
|
131
|
+
};
|
|
132
|
+
type FtlMetrics = {
|
|
133
|
+
total_ms: number;
|
|
134
|
+
asr_tail_ms: number | null;
|
|
135
|
+
svad_infer_ms: number | null;
|
|
136
|
+
svad_wait_ms: number | null;
|
|
137
|
+
lftl_ms: number | null;
|
|
138
|
+
tfal_ms: number | null;
|
|
139
|
+
};
|
|
140
|
+
type IlMetrics = {
|
|
141
|
+
total_ms: number | null;
|
|
142
|
+
vsl_ms: number;
|
|
143
|
+
aftl_ms: number | null;
|
|
144
|
+
};
|
|
145
|
+
type TurnMetricsEvent = {
|
|
146
|
+
type: "turn.metrics";
|
|
147
|
+
turn_id: string;
|
|
148
|
+
ftl: FtlMetrics | null;
|
|
149
|
+
il: IlMetrics | null;
|
|
150
|
+
};
|
|
151
|
+
type ServerEvent = AssistantDoneEvent | TurnMetricsEvent | SessionConfig | AsrPartialEvent | AsrFinalEvent | AsrIncompleteEvent | AsrTimingEvent | TurnStartedEvent | TurnEndedEvent | AssistantDelta | FillerStartedEvent | FillerStoppedEvent | FillerSkippedEvent | TtsStartedEvent | TtsStoppedEvent | InterruptedEvent | ErrorEvent | BrainRequestEvent;
|
|
152
|
+
type BargeFrame = {
|
|
153
|
+
type: "barge";
|
|
154
|
+
};
|
|
155
|
+
type TurnEndFrame = {
|
|
156
|
+
type: "turn.end";
|
|
157
|
+
transcript?: string;
|
|
158
|
+
};
|
|
159
|
+
type SayFrame = {
|
|
160
|
+
type: "say";
|
|
161
|
+
text: string;
|
|
162
|
+
voice?: string;
|
|
163
|
+
};
|
|
164
|
+
type ClientControlFrame = BargeFrame | TurnEndFrame | SayFrame;
|
|
165
|
+
type SessionState = "idle" | "listening" | "thinking" | "speaking" | "interrupted";
|
|
166
|
+
type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting";
|
|
167
|
+
type BrainDeltaFrame = {
|
|
168
|
+
type: "brain.delta";
|
|
169
|
+
text: string;
|
|
170
|
+
generation_id: string;
|
|
171
|
+
};
|
|
172
|
+
type BrainDoneFrame = {
|
|
173
|
+
type: "brain.done";
|
|
174
|
+
generation_id: string;
|
|
175
|
+
};
|
|
176
|
+
type BrainErrorFrame = {
|
|
177
|
+
type: "brain.error";
|
|
178
|
+
message: string;
|
|
179
|
+
generation_id: string;
|
|
180
|
+
};
|
|
181
|
+
type TtsOptions = {
|
|
182
|
+
/** 音色 ID,例如 "zh_male_yuanboxiaoshu_uranus_bigtts" */
|
|
183
|
+
speaker?: string;
|
|
184
|
+
/** 语速,范围 -50~100(100 = 2倍速,-50 = 0.5倍速) */
|
|
185
|
+
speech_rate?: number;
|
|
186
|
+
/** 音量,范围 -50~100(100 = 2倍音量,-50 = 0.5倍音量) */
|
|
187
|
+
loudness_rate?: number;
|
|
188
|
+
/**
|
|
189
|
+
* 方言。目前仅 zh_female_vv_uranus_bigtts 音色支持,可选值:
|
|
190
|
+
* - "dongbei" 东北话
|
|
191
|
+
* - "shaanxi" 陕西话
|
|
192
|
+
* - "sichuan" 四川话
|
|
193
|
+
*/
|
|
194
|
+
explicit_dialect?: "dongbei" | "shaanxi" | "sichuan";
|
|
195
|
+
/**
|
|
196
|
+
* 语音指令/情绪引导,传入参考文本让模型按指定风格合成,
|
|
197
|
+
* 例如 ["用特别特别痛心的语气说话"]
|
|
198
|
+
*/
|
|
199
|
+
context_texts?: string[];
|
|
200
|
+
};
|
|
201
|
+
type TurnPolicy = {
|
|
202
|
+
/** 连接后立即播报的欢迎语,传空字符串可禁用服务端默认欢迎语 */
|
|
203
|
+
welcome_text?: string;
|
|
204
|
+
/** 是否启用垫词,默认 true(仅 doubao TTS 后端生效) */
|
|
205
|
+
filler_enabled?: boolean;
|
|
206
|
+
tts_options?: TtsOptions;
|
|
207
|
+
};
|
|
208
|
+
type CreateSessionRequest = {
|
|
209
|
+
turn_policy?: TurnPolicy;
|
|
210
|
+
app_context?: Record<string, unknown>;
|
|
211
|
+
brain_mode?: "server" | "client";
|
|
212
|
+
};
|
|
213
|
+
type CreateSessionResponse = {
|
|
214
|
+
session_id: string;
|
|
215
|
+
session_token: string;
|
|
216
|
+
policy_profile: Record<string, unknown>;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
type Utterance = {
|
|
220
|
+
role: "user" | "assistant";
|
|
221
|
+
text: string;
|
|
222
|
+
partial?: boolean;
|
|
223
|
+
incomplete?: boolean;
|
|
224
|
+
fillerPrefix?: string;
|
|
225
|
+
waitTimes?: number[];
|
|
226
|
+
};
|
|
227
|
+
type TurnMetricsSummary = {
|
|
228
|
+
turn_id: string;
|
|
229
|
+
ftl: FtlMetrics | null;
|
|
230
|
+
il: IlMetrics | null;
|
|
231
|
+
ts: number;
|
|
232
|
+
};
|
|
233
|
+
type VoiceSessionState = {
|
|
234
|
+
connection: ConnectionState;
|
|
235
|
+
session: SessionState;
|
|
236
|
+
utterances: Utterance[];
|
|
237
|
+
volume: number;
|
|
238
|
+
error: string | null;
|
|
239
|
+
latency: {
|
|
240
|
+
current: TurnMetricsSummary | null;
|
|
241
|
+
history: TurnMetricsSummary[];
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
declare function createInitialState(): VoiceSessionState;
|
|
245
|
+
|
|
246
|
+
type EventMap = {
|
|
247
|
+
state: (s: VoiceSessionState) => void;
|
|
248
|
+
log: (e: LogEntry) => void;
|
|
249
|
+
};
|
|
250
|
+
declare class TypedEmitter {
|
|
251
|
+
private _listeners;
|
|
252
|
+
on<K extends keyof EventMap>(event: K, fn: EventMap[K]): this;
|
|
253
|
+
off<K extends keyof EventMap>(event: K, fn: EventMap[K]): this;
|
|
254
|
+
protected emit<K extends keyof EventMap>(event: K, ...args: Parameters<EventMap[K]>): void;
|
|
255
|
+
}
|
|
256
|
+
type BrainSendFrame = (frame: BrainDeltaFrame | BrainDoneFrame | BrainErrorFrame) => void;
|
|
257
|
+
type VoiceLoopClientOptions = {
|
|
258
|
+
serverUrl?: string;
|
|
259
|
+
apiKey?: string;
|
|
260
|
+
/** 话轮策略与 TTS 参数,含垫词、打断、EoT、音色、语速等。 */
|
|
261
|
+
turnPolicy?: TurnPolicy;
|
|
262
|
+
/** Called when the server delegates an LLM request to the client. */
|
|
263
|
+
onBrainRequest?: (evt: BrainRequestEvent, sendFrame: BrainSendFrame) => void | Promise<void>;
|
|
264
|
+
/** Inject custom microphone capture (useful for testing). */
|
|
265
|
+
micCapture?: (onPcm: (chunk: ArrayBuffer) => void) => Promise<AudioCaptureHandle>;
|
|
266
|
+
/** Inject custom PCM player factory (useful for testing). */
|
|
267
|
+
playerFactory?: () => PcmPlayerHandle;
|
|
268
|
+
};
|
|
269
|
+
declare class VoiceLoopClient extends TypedEmitter {
|
|
270
|
+
private _state;
|
|
271
|
+
private _ctx;
|
|
272
|
+
private _ws;
|
|
273
|
+
private _mic;
|
|
274
|
+
private _player;
|
|
275
|
+
private _volTimer;
|
|
276
|
+
private readonly _serverUrl;
|
|
277
|
+
private readonly _apiKey;
|
|
278
|
+
private readonly _turnPolicy;
|
|
279
|
+
private readonly _onBrainRequest;
|
|
280
|
+
private readonly _micCapture;
|
|
281
|
+
constructor(opts?: VoiceLoopClientOptions);
|
|
282
|
+
get state(): VoiceSessionState;
|
|
283
|
+
connect(): Promise<void>;
|
|
284
|
+
disconnect(): void;
|
|
285
|
+
barge(): void;
|
|
286
|
+
endTurn(transcript?: string): void;
|
|
287
|
+
say(text: string, voice?: string): void;
|
|
288
|
+
private _send;
|
|
289
|
+
private _handleEvent;
|
|
290
|
+
private _applyPatch;
|
|
291
|
+
private _cleanup;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
type StateMachineContext = {
|
|
295
|
+
assistantBubbleOpen: boolean;
|
|
296
|
+
userTurnActive: boolean;
|
|
297
|
+
incompleteAccum: string;
|
|
298
|
+
hasIncompleteUserTurn: boolean;
|
|
299
|
+
preLLMText: string;
|
|
300
|
+
};
|
|
301
|
+
declare function createStateMachineContext(): StateMachineContext;
|
|
302
|
+
type SideEffect = {
|
|
303
|
+
kind: "flush_player";
|
|
304
|
+
};
|
|
305
|
+
type ReduceResult = {
|
|
306
|
+
state: VoiceSessionState;
|
|
307
|
+
ctx: StateMachineContext;
|
|
308
|
+
effects: SideEffect[];
|
|
309
|
+
};
|
|
310
|
+
declare function reduceServerEvent(state: VoiceSessionState, ctx: StateMachineContext, evt: ServerEvent): ReduceResult;
|
|
311
|
+
|
|
312
|
+
export { type AsrFinalEvent, type AsrIncompleteEvent, type AsrPartialEvent, type AsrTimingEvent, type AssistantDelta, type AssistantDoneEvent, type AudioCaptureHandle, type BargeFrame, type BrainDeltaFrame, type BrainDoneFrame, type BrainErrorFrame, type BrainRequestEvent, type BrainSendFrame, type ClientControlFrame, type ConnectionState, type CreateSessionRequest, type CreateSessionResponse, type ErrorEvent, type FillerSkippedEvent, type FillerStartedEvent, type FillerStoppedEvent, type FtlMetrics, type IlMetrics, type InterruptedEvent, type LogEntry, type LogLevel, MIC_SAMPLE_RATE, type PcmPlayerHandle, type ReduceResult, type SayFrame, type ServerEvent, type SessionConfig, type SessionState, type SideEffect, type StateMachineContext, TTS_SAMPLE_RATE, type TtsOptions, type TtsStartedEvent, type TtsStoppedEvent, type TurnEndFrame, type TurnEndedEvent, type TurnMetricsEvent, type TurnMetricsSummary, type TurnPolicy, type TurnStartedEvent, type Utterance, VoiceLoopClient, type VoiceLoopClientOptions, type VoiceSessionState, clearLog, createInitialState, createPcmPlayer, createStateMachineContext, formatForCopy, getEntries, log, logError, reduceServerEvent, startMicCapture, subscribe, warn };
|