@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/react/useVoiceSession.ts","../../src/logger.ts","../../src/audio.ts","../../src/state-machine.ts","../../src/session-state.ts","../../src/client.ts"],"sourcesContent":["\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport { VoiceLoopClient, type BrainSendFrame, type VoiceLoopClientOptions } from \"../client\"\nimport { log, logError, warn } from \"../logger\"\nimport type { VoiceSessionState } from \"../session-state\"\nimport type { BrainRequestEvent } from \"../types\"\n\nexport type UseVoiceSessionOptions = VoiceLoopClientOptions & {\n /**\n * Path to the server-side LLM proxy endpoint.\n * Defaults to \"/api/brain\".\n */\n brainApiPath?: string\n}\n\nfunction makeDefaultBrainHandler(brainApiPath: string) {\n return async (evt: BrainRequestEvent, sendFrame: BrainSendFrame) => {\n const { transcript, history, generation_id, system_append } = evt\n const gen8 = generation_id.slice(0, 8)\n const t0 = Date.now()\n log(\"brain\", `request gen=${gen8} transcript=\"${transcript.slice(0, 40)}\"`,\n `history turns=${history.length}${system_append ? \" filler=yes\" : \"\"}\\ngeneration_id=${generation_id}`)\n\n try {\n const res = await fetch(brainApiPath, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ transcript, history, system_append: system_append ?? \"\" }),\n })\n if (!res.ok) throw new Error(`${brainApiPath} ${res.status}`)\n\n const contentType = res.headers.get(\"content-type\") ?? \"\"\n if (!contentType.includes(\"text/event-stream\")) {\n warn(\"brain\", `接口 ${brainApiPath} 返回的 Content-Type 为 \"${contentType}\",不是 \"text/event-stream\"。请确认服务端已开启流式返回(stream: true),否则 AI 回答将延迟到 LLM 全部完成后才开口`)\n }\n\n const reader = res.body!.getReader()\n const dec = new TextDecoder()\n let buf = \"\"\n let deltaCount = 0\n let ttfb: number | null = null\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buf += dec.decode(value, { stream: true })\n const lines = buf.split(\"\\n\")\n buf = lines.pop() ?? \"\"\n for (const line of lines) {\n if (!line.startsWith(\"data:\")) continue\n const data = line.slice(5).trim()\n if (data === \"[DONE]\") break\n try {\n type Chunk = { choices?: Array<{ delta?: { content?: string } }> }\n const delta = (JSON.parse(data) as Chunk)?.choices?.[0]?.delta?.content\n if (delta) {\n if (ttfb === null) {\n ttfb = Date.now() - t0\n log(\"brain\", `ttfb=${ttfb}ms gen=${gen8}`)\n }\n deltaCount++\n sendFrame({ type: \"brain.delta\", text: delta, generation_id })\n }\n } catch { /* skip malformed chunk */ }\n }\n }\n if (deltaCount === 0) {\n warn(\"brain\", `gen=${gen8} 流结束但未收到任何文字 delta。可能原因:1) 服务端未开启流式(stream: true);2) SSE 格式不符合 OpenAI 规范(应为 data: {\"choices\":[{\"delta\":{\"content\":\"...\"}}]});3) LLM 返回了空内容`)\n }\n sendFrame({ type: \"brain.done\", generation_id })\n log(\"brain\", `done gen=${gen8} deltas=${deltaCount} total=${Date.now() - t0}ms`)\n } catch (e) {\n logError(\"brain\", `error gen=${gen8} ${e}`)\n sendFrame({ type: \"brain.error\", message: String(e), generation_id })\n }\n }\n}\n\nexport function useVoiceSession(opts: UseVoiceSessionOptions = {}) {\n const brainApiPath = opts.brainApiPath ?? \"/api/brain\"\n\n // Resolve onBrainRequest: user-supplied > default for client mode > undefined\n const resolvedOpts: VoiceLoopClientOptions = {\n ...opts,\n onBrainRequest: opts.onBrainRequest ?? makeDefaultBrainHandler(brainApiPath),\n }\n\n const clientRef = useRef<VoiceLoopClient | null>(null)\n\n // Client is created once on first render. opts changes after mount are ignored.\n if (!clientRef.current) {\n clientRef.current = new VoiceLoopClient(resolvedOpts)\n }\n\n const [state, setState] = useState<VoiceSessionState>(() => clientRef.current!.state)\n\n useEffect(() => {\n const client = clientRef.current!\n const handler = (s: VoiceSessionState) => setState(s)\n client.on(\"state\", handler)\n return () => {\n client.off(\"state\", handler)\n client.disconnect()\n }\n }, [])\n\n const connect = useCallback(() => clientRef.current!.connect(), [])\n const disconnect = useCallback(() => clientRef.current!.disconnect(), [])\n\n return { state, connect, disconnect, client: clientRef.current }\n}\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/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voiceloop/web-sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "VoiceLoop Web SDK — framework-agnostic client + optional React hook",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./react": {
|
|
17
|
+
"types": "./dist/react/index.d.ts",
|
|
18
|
+
"import": "./dist/react/index.js",
|
|
19
|
+
"require": "./dist/react/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.cjs",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"react": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/react": "^19",
|
|
40
|
+
"react": "^19",
|
|
41
|
+
"tsup": "^8",
|
|
42
|
+
"typescript": "^5"
|
|
43
|
+
}
|
|
44
|
+
}
|