Sync voice chatId session handling
This commit is contained in:
@@ -44,6 +44,8 @@ function defaultWsUrl() {
|
||||
|
||||
const els = {
|
||||
url: document.getElementById("ws-url"),
|
||||
chatId: document.getElementById("chat-id"),
|
||||
copyChatIdBtn: document.getElementById("copy-chat-id-btn"),
|
||||
connectBtn: document.getElementById("connect-btn"),
|
||||
statusDot: document.getElementById("status-dot"),
|
||||
statusText: document.getElementById("status-text"),
|
||||
@@ -69,10 +71,38 @@ const els = {
|
||||
sendBtn: document.getElementById("send-btn"),
|
||||
};
|
||||
|
||||
function generateChatId() {
|
||||
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
||||
return `voice_${crypto.randomUUID().replaceAll("-", "").slice(0, 16)}`;
|
||||
}
|
||||
return `voice_${Date.now().toString(36)}${Math.random()
|
||||
.toString(36)
|
||||
.slice(2, 10)}`;
|
||||
}
|
||||
|
||||
function currentChatIdInput() {
|
||||
return (els.chatId.value || "").trim();
|
||||
}
|
||||
|
||||
function wsUrlWithChatId(chatId) {
|
||||
const rawUrl = (els.url.value || "").trim();
|
||||
if (!rawUrl || !chatId) return rawUrl;
|
||||
|
||||
try {
|
||||
const url = new URL(rawUrl, location.href);
|
||||
url.searchParams.set("chatId", chatId);
|
||||
return url.href;
|
||||
} catch (_) {
|
||||
const separator = rawUrl.includes("?") ? "&" : "?";
|
||||
return `${rawUrl}${separator}chatId=${encodeURIComponent(chatId)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
ws: null,
|
||||
connected: false,
|
||||
connecting: false,
|
||||
chatId: "",
|
||||
|
||||
audioContext: null,
|
||||
micStream: null,
|
||||
@@ -110,6 +140,8 @@ function setStatus(kind, text) {
|
||||
}
|
||||
|
||||
function setConnectButton() {
|
||||
els.chatId.disabled = state.connected || state.connecting;
|
||||
els.copyChatIdBtn.disabled = !state.connected || !state.chatId;
|
||||
if (state.connecting) {
|
||||
els.connectBtn.textContent = "Connecting…";
|
||||
els.connectBtn.disabled = true;
|
||||
@@ -125,6 +157,26 @@ function setConnectButton() {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyChatId() {
|
||||
if (!state.connected || !state.chatId) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(state.chatId);
|
||||
} catch (_) {
|
||||
const selectionStart = els.chatId.selectionStart;
|
||||
const selectionEnd = els.chatId.selectionEnd;
|
||||
els.chatId.disabled = false;
|
||||
els.chatId.select();
|
||||
document.execCommand("copy");
|
||||
els.chatId.setSelectionRange(selectionStart, selectionEnd);
|
||||
els.chatId.disabled = true;
|
||||
}
|
||||
|
||||
els.copyChatIdBtn.classList.add("copied");
|
||||
window.setTimeout(() => {
|
||||
els.copyChatIdBtn.classList.remove("copied");
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
function setMicButton() {
|
||||
els.micBtn.disabled = !state.connected;
|
||||
els.micBtn.setAttribute("aria-pressed", state.micEnabled ? "true" : "false");
|
||||
@@ -874,13 +926,17 @@ function handleEvent(event) {
|
||||
|
||||
async function connect() {
|
||||
if (state.connected || state.connecting) return;
|
||||
const url = (els.url.value || "").trim();
|
||||
const inputChatId = currentChatIdInput();
|
||||
const chatId = inputChatId || generateChatId();
|
||||
const url = wsUrlWithChatId(chatId);
|
||||
if (!url) {
|
||||
setStatus("error", "Missing URL");
|
||||
return;
|
||||
}
|
||||
|
||||
state.connecting = true;
|
||||
state.chatId = chatId;
|
||||
els.chatId.value = chatId;
|
||||
setStatus("connecting", "Connecting…");
|
||||
setConnectButton();
|
||||
addWsLog("system", `connecting ${url}`);
|
||||
@@ -891,6 +947,8 @@ async function connect() {
|
||||
} catch (err) {
|
||||
console.error("AudioContext failed", err);
|
||||
state.connecting = false;
|
||||
state.chatId = "";
|
||||
if (!inputChatId) els.chatId.value = "";
|
||||
setStatus("error", "Audio init failed");
|
||||
setConnectButton();
|
||||
addWsLog("error", `audio init failed: ${err.message || err}`, "error");
|
||||
@@ -903,6 +961,8 @@ async function connect() {
|
||||
} catch (err) {
|
||||
console.error("WebSocket constructor failed", err);
|
||||
state.connecting = false;
|
||||
state.chatId = "";
|
||||
if (!inputChatId) els.chatId.value = "";
|
||||
setStatus("error", "Bad URL");
|
||||
setConnectButton();
|
||||
addWsLog("error", `bad websocket URL: ${err.message || err}`, "error");
|
||||
@@ -921,6 +981,7 @@ async function connect() {
|
||||
channels: CHANNELS,
|
||||
},
|
||||
};
|
||||
startMessage.chatId = state.chatId;
|
||||
|
||||
state.connecting = false;
|
||||
state.connected = true;
|
||||
@@ -974,6 +1035,7 @@ async function connect() {
|
||||
state.ws = null;
|
||||
state.connected = false;
|
||||
state.connecting = false;
|
||||
state.chatId = "";
|
||||
setAssistantState("");
|
||||
if (state.micEnabled) stopMic();
|
||||
stopPlaybackQueue();
|
||||
@@ -1026,6 +1088,8 @@ els.connectBtn.addEventListener("click", () => {
|
||||
else connect();
|
||||
});
|
||||
|
||||
els.copyChatIdBtn.addEventListener("click", copyChatId);
|
||||
|
||||
els.micBtn.addEventListener("click", async () => {
|
||||
if (!state.connected) return;
|
||||
els.micBtn.disabled = true;
|
||||
|
||||
@@ -25,6 +25,34 @@
|
||||
autocomplete="off"
|
||||
/>
|
||||
</label>
|
||||
<label class="connection__field connection__field--chat">
|
||||
<span>Chat ID</span>
|
||||
<div class="chat-id-control">
|
||||
<input
|
||||
id="chat-id"
|
||||
type="text"
|
||||
placeholder="optional chatId"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button
|
||||
id="copy-chat-id-btn"
|
||||
class="chat-id-control__copy"
|
||||
type="button"
|
||||
disabled
|
||||
title="Copy Chat ID"
|
||||
aria-label="Copy Chat ID"
|
||||
>
|
||||
<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"/>
|
||||
<path d="M3 11V3.5A1.5 1.5 0 0 1 4.5 2H11" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<svg class="copy-icon copy-icon--check" viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true">
|
||||
<path d="M3 8.5l3.5 3.5 6.5-7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<button id="connect-btn" class="btn btn--primary" type="button">
|
||||
Connect
|
||||
</button>
|
||||
|
||||
@@ -304,6 +304,10 @@ body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.connection__field--chat {
|
||||
flex: 0 1 220px;
|
||||
}
|
||||
|
||||
.connection__field span {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
@@ -324,11 +328,67 @@ body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.connection__field input:disabled {
|
||||
color: var(--text-dim);
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.connection__field input:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(79, 140, 255, 0.18);
|
||||
}
|
||||
|
||||
.chat-id-control {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.chat-id-control input {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.chat-id-control__copy {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
border: 1px solid var(--border);
|
||||
border-left: 0;
|
||||
border-radius: 0 10px 10px 0;
|
||||
background: var(--bg-soft);
|
||||
color: var(--text-dim);
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.chat-id-control__copy:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.chat-id-control__copy:not(:disabled):hover {
|
||||
color: var(--accent-strong);
|
||||
border-color: var(--accent);
|
||||
background: rgba(79, 140, 255, 0.08);
|
||||
}
|
||||
|
||||
.chat-id-control__copy .copy-icon--check {
|
||||
display: none;
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.chat-id-control__copy.copied .copy-icon--default {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-id-control__copy.copied .copy-icon--check {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1013,6 +1073,10 @@ body {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.connection__field--chat {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.ws-log__entry,
|
||||
.ws-log__group-header {
|
||||
grid-template-columns: 54px 38px minmax(0, 1fr);
|
||||
|
||||
Reference in New Issue
Block a user