Update voice demo language problem
This commit is contained in:
@@ -170,7 +170,7 @@ function setConnectButton() {
|
|||||||
els.chatId.disabled = state.connected || state.connecting;
|
els.chatId.disabled = state.connected || state.connecting;
|
||||||
els.copyChatIdBtn.disabled = !state.connected || !state.chatId;
|
els.copyChatIdBtn.disabled = !state.connected || !state.chatId;
|
||||||
if (state.connecting) {
|
if (state.connecting) {
|
||||||
els.connectBtn.textContent = "Connecting…";
|
els.connectBtn.textContent = "连接中…";
|
||||||
els.connectBtn.disabled = true;
|
els.connectBtn.disabled = true;
|
||||||
els.connectBtn.classList.remove("is-disconnect");
|
els.connectBtn.classList.remove("is-disconnect");
|
||||||
} else if (state.connected) {
|
} else if (state.connected) {
|
||||||
@@ -207,8 +207,8 @@ async function copyChatId() {
|
|||||||
function setMicButton() {
|
function setMicButton() {
|
||||||
els.micBtn.disabled = !state.connected;
|
els.micBtn.disabled = !state.connected;
|
||||||
els.micBtn.setAttribute("aria-pressed", state.micEnabled ? "true" : "false");
|
els.micBtn.setAttribute("aria-pressed", state.micEnabled ? "true" : "false");
|
||||||
els.micBtn.title = state.micEnabled ? "Mute mic" : "Unmute mic";
|
els.micBtn.title = state.micEnabled ? "关闭麦克风" : "开启麦克风";
|
||||||
els.micLabel.textContent = state.micEnabled ? "Mute mic" : "Enable mic";
|
els.micLabel.textContent = state.micEnabled ? "关闭麦克风" : "开启麦克风";
|
||||||
els.micIndicator.classList.toggle("is-active", state.micEnabled);
|
els.micIndicator.classList.toggle("is-active", state.micEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +231,8 @@ function setAssistantState(value) {
|
|||||||
const label = text.length > 32 ? `${text.slice(0, 31)}…` : text;
|
const label = text.length > 32 ? `${text.slice(0, 31)}…` : text;
|
||||||
state.assistantState = text;
|
state.assistantState = text;
|
||||||
els.stateIndicator.classList.toggle("is-active", Boolean(text));
|
els.stateIndicator.classList.toggle("is-active", Boolean(text));
|
||||||
els.stateLabel.textContent = label ? `State ${label}` : "State -";
|
els.stateLabel.textContent = label ? `状态 ${label}` : "状态 -";
|
||||||
els.stateIndicator.title = label ? `Assistant state: ${text}` : "Assistant state";
|
els.stateIndicator.title = label ? `助手状态:${text}` : "助手状态";
|
||||||
syncCameraDrawer(text);
|
syncCameraDrawer(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ function syncCameraDrawer(value) {
|
|||||||
els.conversation.classList.toggle("has-camera", open);
|
els.conversation.classList.toggle("has-camera", open);
|
||||||
els.cameraDrawer.setAttribute("aria-hidden", open ? "false" : "true");
|
els.cameraDrawer.setAttribute("aria-hidden", open ? "false" : "true");
|
||||||
if (open) {
|
if (open) {
|
||||||
els.cameraState.textContent = `State ${value}`;
|
els.cameraState.textContent = `状态 ${value}`;
|
||||||
els.cameraQuestion.textContent = prompt;
|
els.cameraQuestion.textContent = prompt;
|
||||||
renderSampleThumbnails();
|
renderSampleThumbnails();
|
||||||
selectDefaultImage();
|
selectDefaultImage();
|
||||||
@@ -261,7 +261,7 @@ function syncCameraDrawer(value) {
|
|||||||
if (!state.cameraActive) populateDeviceSelect();
|
if (!state.cameraActive) populateDeviceSelect();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
els.cameraState.textContent = "State -";
|
els.cameraState.textContent = "状态 -";
|
||||||
els.cameraQuestion.textContent = "";
|
els.cameraQuestion.textContent = "";
|
||||||
if (wasOpen) resetCameraInput();
|
if (wasOpen) resetCameraInput();
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ function addBubble(role, text) {
|
|||||||
if (role !== "system") {
|
if (role !== "system") {
|
||||||
const tag = document.createElement("span");
|
const tag = document.createElement("span");
|
||||||
tag.className = "bubble__role";
|
tag.className = "bubble__role";
|
||||||
tag.textContent = role === "user" ? "You" : "Assistant";
|
tag.textContent = role === "user" ? "你" : "助手";
|
||||||
bubble.appendChild(tag);
|
bubble.appendChild(tag);
|
||||||
}
|
}
|
||||||
const body = document.createElement("span");
|
const body = document.createElement("span");
|
||||||
@@ -299,7 +299,7 @@ function addImageBubble(role, imageUrl, text) {
|
|||||||
if (role !== "system") {
|
if (role !== "system") {
|
||||||
const tag = document.createElement("span");
|
const tag = document.createElement("span");
|
||||||
tag.className = "bubble__role";
|
tag.className = "bubble__role";
|
||||||
tag.textContent = role === "user" ? "You" : "Assistant";
|
tag.textContent = role === "user" ? "你" : "助手";
|
||||||
bubble.appendChild(tag);
|
bubble.appendChild(tag);
|
||||||
}
|
}
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
@@ -334,7 +334,7 @@ function clearChat() {
|
|||||||
setAssistantState("");
|
setAssistantState("");
|
||||||
const empty = document.createElement("div");
|
const empty = document.createElement("div");
|
||||||
empty.className = "chat__empty";
|
empty.className = "chat__empty";
|
||||||
empty.innerHTML = "<p>Chat cleared.</p>";
|
empty.innerHTML = "<p>对话已清空。</p>";
|
||||||
els.chatLog.appendChild(empty);
|
els.chatLog.appendChild(empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +656,7 @@ function wsSend(data) {
|
|||||||
function clearWsLog() {
|
function clearWsLog() {
|
||||||
state.wsLogGroup = null;
|
state.wsLogGroup = null;
|
||||||
els.wsLog.innerHTML =
|
els.wsLog.innerHTML =
|
||||||
'<div class="ws-log__empty">No websocket events yet.</div>';
|
'<div class="ws-log__empty">暂无 WebSocket 事件。</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------- Audio */
|
/* ---------------------------------------------------------------- Audio */
|
||||||
@@ -679,7 +679,7 @@ function renderMicDevices() {
|
|||||||
|
|
||||||
const defaultOption = document.createElement("option");
|
const defaultOption = document.createElement("option");
|
||||||
defaultOption.value = "";
|
defaultOption.value = "";
|
||||||
defaultOption.textContent = "Default microphone";
|
defaultOption.textContent = "默认麦克风";
|
||||||
els.micSelect.appendChild(defaultOption);
|
els.micSelect.appendChild(defaultOption);
|
||||||
|
|
||||||
state.micDevices.forEach((device, index) => {
|
state.micDevices.forEach((device, index) => {
|
||||||
@@ -752,7 +752,7 @@ async function startMic() {
|
|||||||
|
|
||||||
state.micSourceNode.connect(state.recorderNode);
|
state.micSourceNode.connect(state.recorderNode);
|
||||||
state.micEnabled = true;
|
state.micEnabled = true;
|
||||||
addWsLog("system", "mic capture started (binary input.audio frames)");
|
addWsLog("system", "麦克风已开启(PCM 音频流)");
|
||||||
setMicButton();
|
setMicButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,7 +788,7 @@ function stopMic() {
|
|||||||
state.micEnabled = false;
|
state.micEnabled = false;
|
||||||
updateMeter(0);
|
updateMeter(0);
|
||||||
if (wasEnabled) {
|
if (wasEnabled) {
|
||||||
addWsLog("system", "mic capture stopped");
|
addWsLog("system", "麦克风已关闭");
|
||||||
}
|
}
|
||||||
setMicButton();
|
setMicButton();
|
||||||
}
|
}
|
||||||
@@ -902,7 +902,7 @@ function mediaToPayload(source) {
|
|||||||
try {
|
try {
|
||||||
dataUrl = canvas.toDataURL("image/jpeg", IMAGE_JPEG_QUALITY);
|
dataUrl = canvas.toDataURL("image/jpeg", IMAGE_JPEG_QUALITY);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addWsLog("system", `image encode failed: ${err.message || err}`);
|
addWsLog("system", `图片编码失败:${err.message || err}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return { dataUrl, mime: "image/jpeg", width: w, height: h };
|
return { dataUrl, mime: "image/jpeg", width: w, height: h };
|
||||||
@@ -952,7 +952,7 @@ function populateDeviceSelect(activeDeviceId) {
|
|||||||
|
|
||||||
async function startCamera(deviceId) {
|
async function startCamera(deviceId) {
|
||||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||||
addWsLog("system", "getUserMedia not available in this browser");
|
addWsLog("system", "该浏览器不支持摄像头访问");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopCameraStream();
|
stopCameraStream();
|
||||||
@@ -965,7 +965,7 @@ async function startCamera(deviceId) {
|
|||||||
audio: false,
|
audio: false,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addWsLog("system", `camera error: ${err.message || err}`);
|
addWsLog("system", `摄像头错误:${err.message || err}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
els.cameraVideo.srcObject = state.cameraStream;
|
els.cameraVideo.srcObject = state.cameraStream;
|
||||||
@@ -1029,7 +1029,7 @@ async function selectFileImage(file) {
|
|||||||
clearSampleSelection();
|
clearSampleSelection();
|
||||||
setPendingImage(payload);
|
setPendingImage(payload);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addWsLog("system", `upload error: ${err.message || err}`);
|
addWsLog("system", `上传错误:${err.message || err}`);
|
||||||
} finally {
|
} finally {
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl);
|
||||||
}
|
}
|
||||||
@@ -1045,7 +1045,7 @@ async function selectSampleImage(src, buttonEl) {
|
|||||||
if (buttonEl) buttonEl.classList.add("is-selected");
|
if (buttonEl) buttonEl.classList.add("is-selected");
|
||||||
setPendingImage(payload);
|
setPendingImage(payload);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addWsLog("system", `sample error: ${err.message || err}`);
|
addWsLog("system", `示例图加载错误:${err.message || err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1260,9 +1260,9 @@ async function connect() {
|
|||||||
state.connecting = true;
|
state.connecting = true;
|
||||||
state.chatId = chatId;
|
state.chatId = chatId;
|
||||||
els.chatId.value = chatId;
|
els.chatId.value = chatId;
|
||||||
setStatus("connecting", "Connecting…");
|
setStatus("connecting", "连接中…");
|
||||||
setConnectButton();
|
setConnectButton();
|
||||||
addWsLog("system", `connecting ${url}`);
|
addWsLog("system", `正在连接 ${url}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pre-warm audio context on user gesture so playback works on Safari.
|
// Pre-warm audio context on user gesture so playback works on Safari.
|
||||||
@@ -1309,15 +1309,15 @@ async function connect() {
|
|||||||
state.connecting = false;
|
state.connecting = false;
|
||||||
state.connected = true;
|
state.connected = true;
|
||||||
resetPlaybackClock();
|
resetPlaybackClock();
|
||||||
addWsLog("system", "websocket open");
|
addWsLog("system", "连接已建立");
|
||||||
setStatus("connected", "Connected");
|
setStatus("connected", "已连接");
|
||||||
setConnectButton();
|
setConnectButton();
|
||||||
setMicButton();
|
setMicButton();
|
||||||
setMicSelectEnabled();
|
setMicSelectEnabled();
|
||||||
refreshMicDevices();
|
refreshMicDevices();
|
||||||
|
|
||||||
wsSend(JSON.stringify(startMessage));
|
wsSend(JSON.stringify(startMessage));
|
||||||
addBubble("system", "Session started.");
|
addBubble("system", "会话已开始。");
|
||||||
setComposerEnabled(true);
|
setComposerEnabled(true);
|
||||||
setCameraButtonEnabled();
|
setCameraButtonEnabled();
|
||||||
els.textInput.focus();
|
els.textInput.focus();
|
||||||
@@ -1378,11 +1378,11 @@ async function connect() {
|
|||||||
if (wasConnected) {
|
if (wasConnected) {
|
||||||
addBubble(
|
addBubble(
|
||||||
"system",
|
"system",
|
||||||
`Session ended${event.reason ? ` — ${event.reason}` : ""}.`,
|
`会话已结束${event.reason ? `:${event.reason}` : ""}。`,
|
||||||
);
|
);
|
||||||
setStatus("idle", "Disconnected");
|
setStatus("idle", "未连接");
|
||||||
} else {
|
} else {
|
||||||
setStatus("error", "Connection closed");
|
setStatus("error", "连接已断开");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1424,7 +1424,7 @@ els.micBtn.addEventListener("click", async () => {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Mic error", err);
|
console.error("Mic error", err);
|
||||||
addBubble("system", `Mic error: ${err.message || err}`);
|
addBubble("system", `麦克风错误:${err.message || err}`);
|
||||||
} finally {
|
} finally {
|
||||||
els.micBtn.disabled = !state.connected;
|
els.micBtn.disabled = !state.connected;
|
||||||
}
|
}
|
||||||
@@ -1441,7 +1441,7 @@ els.micSelect.addEventListener("change", async () => {
|
|||||||
await startMic();
|
await startMic();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Mic switch error", err);
|
console.error("Mic switch error", err);
|
||||||
addBubble("system", `Mic switch error: ${err.message || err}`);
|
addBubble("system", `麦克风切换错误:${err.message || err}`);
|
||||||
} finally {
|
} finally {
|
||||||
setMicButton();
|
setMicButton();
|
||||||
setMicSelectEnabled();
|
setMicSelectEnabled();
|
||||||
@@ -1534,7 +1534,7 @@ window.addEventListener("beforeunload", () => {
|
|||||||
|
|
||||||
els.url.value = defaultWsUrl();
|
els.url.value = defaultWsUrl();
|
||||||
|
|
||||||
setStatus("idle", "Disconnected");
|
setStatus("idle", "未连接");
|
||||||
setConnectButton();
|
setConnectButton();
|
||||||
setMicButton();
|
setMicButton();
|
||||||
setMicSelectEnabled();
|
setMicSelectEnabled();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<div class="connection">
|
<div class="connection">
|
||||||
<label class="connection__field">
|
<label class="connection__field">
|
||||||
<span>WebSocket URL</span>
|
<span>服务器地址</span>
|
||||||
<input
|
<input
|
||||||
id="ws-url"
|
id="ws-url"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -26,12 +26,12 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label class="connection__field connection__field--chat">
|
<label class="connection__field connection__field--chat">
|
||||||
<span>Chat ID</span>
|
<span>会话 ID</span>
|
||||||
<div class="chat-id-control">
|
<div class="chat-id-control">
|
||||||
<input
|
<input
|
||||||
id="chat-id"
|
id="chat-id"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="optional chatId"
|
placeholder="可选"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
@@ -40,8 +40,8 @@
|
|||||||
class="chat-id-control__copy"
|
class="chat-id-control__copy"
|
||||||
type="button"
|
type="button"
|
||||||
disabled
|
disabled
|
||||||
title="Copy Chat ID"
|
title="复制会话 ID"
|
||||||
aria-label="Copy Chat ID"
|
aria-label="复制会话 ID"
|
||||||
>
|
>
|
||||||
<svg class="copy-icon copy-icon--default" viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true">
|
<svg class="copy-icon copy-icon--default" viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true">
|
||||||
<rect x="5" y="5" width="8" height="9" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
<rect x="5" y="5" width="8" height="9" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
||||||
@@ -54,13 +54,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<button id="connect-btn" class="btn btn--primary" type="button">
|
<button id="connect-btn" class="btn btn--primary" type="button">
|
||||||
Connect
|
连接
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<span id="status-dot" class="status__dot status__dot--idle"></span>
|
<span id="status-dot" class="status__dot status__dot--idle"></span>
|
||||||
<span id="status-text" class="status__text">Disconnected</span>
|
<span id="status-text" class="status__text">未连接</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -70,13 +70,13 @@
|
|||||||
<aside
|
<aside
|
||||||
id="camera-drawer"
|
id="camera-drawer"
|
||||||
class="camera-drawer"
|
class="camera-drawer"
|
||||||
aria-label="Camera capture step"
|
aria-label="拍照步骤"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<div class="camera-drawer__panel">
|
<div class="camera-drawer__panel">
|
||||||
<div class="camera-drawer__header">
|
<div class="camera-drawer__header">
|
||||||
<div>
|
<div>
|
||||||
<p class="camera-drawer__eyebrow">Camera</p>
|
<p class="camera-drawer__eyebrow">拍照</p>
|
||||||
<h2>拍照步骤</h2>
|
<h2>拍照步骤</h2>
|
||||||
</div>
|
</div>
|
||||||
<span id="camera-state" class="camera-drawer__state">State -</span>
|
<span id="camera-state" class="camera-drawer__state">State -</span>
|
||||||
@@ -109,21 +109,48 @@
|
|||||||
<p id="camera-question" class="camera-drawer__question"></p>
|
<p id="camera-question" class="camera-drawer__question"></p>
|
||||||
|
|
||||||
<div class="camera-drawer__sources">
|
<div class="camera-drawer__sources">
|
||||||
<button
|
<div class="camera-drawer__camera-row">
|
||||||
id="camera-start-btn"
|
<label class="device-picker">
|
||||||
class="btn btn--ghost camera-drawer__source"
|
<span class="device-picker__label">摄像头</span>
|
||||||
type="button"
|
<select
|
||||||
>
|
id="camera-device-select"
|
||||||
使用摄像头
|
class="device-picker__select"
|
||||||
</button>
|
disabled
|
||||||
<select
|
>
|
||||||
id="camera-device-select"
|
<option value="">默认摄像头</option>
|
||||||
class="camera-drawer__select"
|
</select>
|
||||||
aria-label="选择摄像头"
|
</label>
|
||||||
disabled
|
<button
|
||||||
>
|
id="camera-start-btn"
|
||||||
<option value="">默认摄像头</option>
|
class="mic-btn cam-btn"
|
||||||
</select>
|
type="button"
|
||||||
|
title="打开摄像头"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="mic-btn__icon"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4 8h3l1.2-1.6A1 1 0 0 1 9 6h6a1 1 0 0 1 .8.4L17 8h3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="12"
|
||||||
|
cy="13"
|
||||||
|
r="3.2"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span class="mic-btn__label">使用摄像头</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<label
|
<label
|
||||||
class="btn btn--ghost camera-drawer__source camera-drawer__source--upload"
|
class="btn btn--ghost camera-drawer__source camera-drawer__source--upload"
|
||||||
>
|
>
|
||||||
@@ -155,7 +182,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<section class="chat" aria-label="Conversation history">
|
<section class="chat" aria-label="对话记录">
|
||||||
<div id="chat-log" class="chat__log" role="log" aria-live="polite">
|
<div id="chat-log" class="chat__log" role="log" aria-live="polite">
|
||||||
<div class="chat__empty">
|
<div class="chat__empty">
|
||||||
<p>Connect to the engine, enable your mic, and start talking.</p>
|
<p>Connect to the engine, enable your mic, and start talking.</p>
|
||||||
@@ -168,7 +195,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="controls" aria-label="Chat controls">
|
<footer class="controls" aria-label="操作栏">
|
||||||
<div class="meter" aria-hidden="true">
|
<div class="meter" aria-hidden="true">
|
||||||
<div id="meter-fill" class="meter__fill"></div>
|
<div id="meter-fill" class="meter__fill"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,7 +205,7 @@
|
|||||||
id="text-input"
|
id="text-input"
|
||||||
class="composer__input"
|
class="composer__input"
|
||||||
rows="1"
|
rows="1"
|
||||||
placeholder="Type a message, or use the mic…"
|
placeholder="输入消息,或使用麦克风…"
|
||||||
disabled
|
disabled
|
||||||
></textarea>
|
></textarea>
|
||||||
<button
|
<button
|
||||||
@@ -186,17 +213,17 @@
|
|||||||
class="btn btn--primary composer__send"
|
class="btn btn--primary composer__send"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled
|
disabled
|
||||||
title="Send message (Enter)"
|
title="发送消息 (Enter)"
|
||||||
>
|
>
|
||||||
Send
|
发送
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="controls__row">
|
<div class="controls__row">
|
||||||
<label class="device-picker">
|
<label class="device-picker">
|
||||||
<span class="device-picker__label">Microphone</span>
|
<span class="device-picker__label">麦克风</span>
|
||||||
<select id="mic-select" class="device-picker__select" disabled>
|
<select id="mic-select" class="device-picker__select" disabled>
|
||||||
<option value="">Default microphone</option>
|
<option value="">默认麦克风</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -206,7 +233,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
disabled
|
disabled
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
title="Mic is off"
|
title="麦克风已关闭"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="mic-btn__icon"
|
class="mic-btn__icon"
|
||||||
@@ -224,52 +251,52 @@
|
|||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="mic-btn__label">Enable mic</span>
|
<span class="mic-btn__label">开启麦克风</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="indicators">
|
<div class="indicators">
|
||||||
<span id="mic-indicator" class="indicator">
|
<span id="mic-indicator" class="indicator">
|
||||||
<span class="indicator__dot indicator__dot--mic"></span>
|
<span class="indicator__dot indicator__dot--mic"></span>
|
||||||
<span class="indicator__label">Mic</span>
|
<span class="indicator__label">麦克风</span>
|
||||||
</span>
|
</span>
|
||||||
<span id="bot-indicator" class="indicator">
|
<span id="bot-indicator" class="indicator">
|
||||||
<span class="indicator__dot indicator__dot--bot"></span>
|
<span class="indicator__dot indicator__dot--bot"></span>
|
||||||
<span class="indicator__label">Bot</span>
|
<span class="indicator__label">助手</span>
|
||||||
</span>
|
</span>
|
||||||
<span id="state-indicator" class="indicator indicator--state">
|
<span id="state-indicator" class="indicator indicator--state">
|
||||||
<span class="indicator__dot indicator__dot--state"></span>
|
<span class="indicator__dot indicator__dot--state"></span>
|
||||||
<span id="state-label" class="indicator__label">State -</span>
|
<span id="state-label" class="indicator__label">状态 -</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="clear-btn" class="btn btn--ghost" type="button">
|
<button id="clear-btn" class="btn btn--ghost" type="button">
|
||||||
Clear
|
清空
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="hint">
|
<p class="hint">
|
||||||
Press <kbd>Enter</kbd> to send, <kbd>Shift</kbd>+<kbd>Enter</kbd>
|
按 <kbd>Enter</kbd> 发送,<kbd>Shift</kbd>+<kbd>Enter</kbd>
|
||||||
for newline. Sending text will interrupt the bot if it's speaking.
|
换行。发送文字会打断正在说话的助手。
|
||||||
Browser echo cancellation is on; use headphones if echo persists.
|
浏览器回声消除已开启,如有回音请使用耳机。
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="ws-log" aria-label="WebSocket log">
|
<section class="ws-log" aria-label="WebSocket 日志">
|
||||||
<div class="ws-log__header">
|
<div class="ws-log__header">
|
||||||
<div class="ws-log__header-left">
|
<div class="ws-log__header-left">
|
||||||
<h2>WebSocket Log</h2>
|
<h2>WebSocket 日志</h2>
|
||||||
<div class="ws-log__legend" aria-hidden="true">
|
<div class="ws-log__legend" aria-hidden="true">
|
||||||
<span class="ws-log__legend-item ws-log__legend-item--send">Send</span>
|
<span class="ws-log__legend-item ws-log__legend-item--send">发送</span>
|
||||||
<span class="ws-log__legend-item ws-log__legend-item--recv">Recv</span>
|
<span class="ws-log__legend-item ws-log__legend-item--recv">接收</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="clear-ws-log-btn" class="btn btn--ghost" type="button">
|
<button id="clear-ws-log-btn" class="btn btn--ghost" type="button">
|
||||||
Clear log
|
清空日志
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="ws-log" class="ws-log__body" role="log" aria-live="polite">
|
<div id="ws-log" class="ws-log__body" role="log" aria-live="polite">
|
||||||
<div class="ws-log__empty">No websocket events yet.</div>
|
<div class="ws-log__empty">暂无 WebSocket 事件。</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -271,11 +271,34 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.camera-drawer__sources {
|
.camera-drawer__sources {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mirror the mic controls: labeled device select + an action pill button. */
|
||||||
|
.camera-drawer__camera-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-drawer__camera-row .device-picker {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cam-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cam-btn.is-active {
|
||||||
|
background: var(--success);
|
||||||
|
border-color: var(--success);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 0 6px rgba(45, 210, 139, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.camera-drawer__source {
|
.camera-drawer__source {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -287,30 +310,6 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.camera-drawer__select {
|
|
||||||
appearance: none;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 38px;
|
|
||||||
background: var(--bg-soft);
|
|
||||||
color: var(--text);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 7px 28px 7px 10px;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 13px;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-drawer__select:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Upload spans the full width on its own row below camera + device list. */
|
|
||||||
.camera-drawer__source--upload {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-drawer__source.is-active {
|
.camera-drawer__source.is-active {
|
||||||
border-color: var(--success);
|
border-color: var(--success);
|
||||||
@@ -709,8 +708,8 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
text-transform: uppercase;
|
letter-spacing: 0.5px;
|
||||||
letter-spacing: 0.8px;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws-log__header-left {
|
.ws-log__header-left {
|
||||||
@@ -965,6 +964,12 @@ body {
|
|||||||
outline: none;
|
outline: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-picker__select:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-picker__select:focus {
|
.device-picker__select:focus {
|
||||||
|
|||||||
Reference in New Issue
Block a user