4e2224e9dc
- /voice 页面:中间圆球 + 左右按钮 + 状态图标 + 右下角气泡(进场/淡出/阶梯字号/上限8)+ 噪声门弧线电平,结构类名与原版一致 - s2s WebSocket 客户端 + worklet 音频(mic-capture/audio-playback)+ orb 可视化 - 摄像头实时预览(默认开启)+ 视觉识别状态条(后端 YOLO 推理) - 底部园区概览条(复用 ParkOverviewStrip),orb-wrap 视窗居中 - 页眉复用 ScreenLayout;左右键导航加入 /voice - 文档:docs/voice-integration-plan.md
58 lines
1.8 KiB
JavaScript
58 lines
1.8 KiB
JavaScript
// @ts-check
|
|
/**
|
|
* Pure, stateless helpers for the WebSocket realtime client: base64 <-> PCM
|
|
* conversion for the audio frames on the wire, transcript extraction from a
|
|
* `response.done` payload, and a tiny URL helper. Kept separate from the client
|
|
* so the protocol/state logic stays readable.
|
|
*/
|
|
|
|
/** @param {string} url */
|
|
export function trimTrailingSlash(url) {
|
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
}
|
|
|
|
/**
|
|
* Pull the assistant transcript out of a `response.done` payload. The text
|
|
* lives in `response.output[].content[].transcript` (audio) or `.text`. Used as
|
|
* the source of truth for interrupted replies, where the dedicated
|
|
* `*.transcript.done` event may never arrive.
|
|
* @param {any} response
|
|
* @returns {string}
|
|
*/
|
|
export function extractResponseTranscript(response) {
|
|
const output = response?.output;
|
|
if (!Array.isArray(output)) return "";
|
|
/** @type {string[]} */
|
|
const parts = [];
|
|
for (const item of output) {
|
|
for (const part of item?.content ?? []) {
|
|
const text = part?.transcript ?? part?.text;
|
|
if (typeof text === "string" && text.trim()) parts.push(text.trim());
|
|
}
|
|
}
|
|
return parts.join(" ").trim();
|
|
}
|
|
|
|
/** @param {ArrayBuffer} buf */
|
|
export function base64FromArrayBuffer(buf) {
|
|
const bytes = new Uint8Array(buf);
|
|
// Chunked encoding so we don't blow up the call stack on long buffers.
|
|
let binary = "";
|
|
const chunk = 0x8000;
|
|
for (let i = 0; i < bytes.length; i += chunk) {
|
|
binary += String.fromCharCode.apply(null, /** @type {number[]} */ (
|
|
/** @type {unknown} */ (bytes.subarray(i, i + chunk))
|
|
));
|
|
}
|
|
return btoa(binary);
|
|
}
|
|
|
|
/** @param {string} b64 */
|
|
export function base64ToBytes(b64) {
|
|
const binary = atob(b64);
|
|
const len = binary.length;
|
|
const out = new Uint8Array(len);
|
|
for (let i = 0; i < len; i++) out[i] = binary.charCodeAt(i);
|
|
return out;
|
|
}
|