Update web client layout
This commit is contained in:
@@ -192,7 +192,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.log {
|
.log {
|
||||||
height: 520px;
|
height: 320px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #0d0d14;
|
background: #0d0d14;
|
||||||
@@ -202,6 +202,33 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat {
|
||||||
|
height: 260px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 12px;
|
||||||
|
background: #0d0d14;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--grid);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-entry {
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-entry.user {
|
||||||
|
border-left: 3px solid var(--accent-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-entry.ai {
|
||||||
|
border-left: 3px solid var(--good);
|
||||||
|
}
|
||||||
|
|
||||||
.log-entry {
|
.log-entry {
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
border-bottom: 1px dashed rgba(255, 255, 255, 0.06);
|
border-bottom: 1px dashed rgba(255, 255, 255, 0.06);
|
||||||
@@ -256,6 +283,9 @@
|
|||||||
.log {
|
.log {
|
||||||
height: 360px;
|
height: 360px;
|
||||||
}
|
}
|
||||||
|
.chat {
|
||||||
|
height: 260px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -314,9 +344,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel stack">
|
<section class="stack">
|
||||||
|
<div class="panel stack">
|
||||||
|
<h2>Chat History</h2>
|
||||||
|
<div class="chat" id="chatHistory"></div>
|
||||||
|
</div>
|
||||||
|
<div class="panel stack">
|
||||||
<h2>Event Log</h2>
|
<h2>Event Log</h2>
|
||||||
<div class="log" id="log"></div>
|
<div class="log" id="log"></div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -342,6 +378,7 @@
|
|||||||
const clearLogBtn = document.getElementById("clearLogBtn");
|
const clearLogBtn = document.getElementById("clearLogBtn");
|
||||||
const chatInput = document.getElementById("chatInput");
|
const chatInput = document.getElementById("chatInput");
|
||||||
const logEl = document.getElementById("log");
|
const logEl = document.getElementById("log");
|
||||||
|
const chatHistory = document.getElementById("chatHistory");
|
||||||
const statusDot = document.getElementById("statusDot");
|
const statusDot = document.getElementById("statusDot");
|
||||||
const statusText = document.getElementById("statusText");
|
const statusText = document.getElementById("statusText");
|
||||||
const statusSub = document.getElementById("statusSub");
|
const statusSub = document.getElementById("statusSub");
|
||||||
@@ -381,12 +418,35 @@
|
|||||||
logEl.scrollTop = logEl.scrollHeight;
|
logEl.scrollTop = logEl.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addChat(role, text) {
|
||||||
|
const entry = document.createElement("div");
|
||||||
|
entry.className = `chat-entry ${role === "AI" ? "ai" : "user"}`;
|
||||||
|
entry.textContent = `${role}: ${text}`;
|
||||||
|
chatHistory.appendChild(entry);
|
||||||
|
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
function setStatus(connected, detail) {
|
function setStatus(connected, detail) {
|
||||||
statusDot.classList.toggle("on", connected);
|
statusDot.classList.toggle("on", connected);
|
||||||
statusText.textContent = connected ? "Connected" : "Disconnected";
|
statusText.textContent = connected ? "Connected" : "Disconnected";
|
||||||
statusSub.textContent = detail || "";
|
statusSub.textContent = detail || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureAudioContext() {
|
||||||
|
if (audioCtx) return;
|
||||||
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
playbackDest = audioCtx.createMediaStreamDestination();
|
||||||
|
audioOut.srcObject = playbackDest.stream;
|
||||||
|
try {
|
||||||
|
await audioOut.play();
|
||||||
|
} catch (err) {
|
||||||
|
logLine("sys", "Audio playback blocked (user gesture needed)", { err: String(err) });
|
||||||
|
}
|
||||||
|
if (outputSelect.value) {
|
||||||
|
await setOutputDevice(outputSelect.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function downsampleBuffer(buffer, inRate, outRate) {
|
function downsampleBuffer(buffer, inRate, outRate) {
|
||||||
if (outRate === inRate) return buffer;
|
if (outRate === inRate) return buffer;
|
||||||
const ratio = inRate / outRate;
|
const ratio = inRate / outRate;
|
||||||
@@ -443,6 +503,7 @@
|
|||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
setStatus(true, "Session open");
|
setStatus(true, "Session open");
|
||||||
logLine("sys", "WebSocket connected");
|
logLine("sys", "WebSocket connected");
|
||||||
|
ensureAudioContext();
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
@@ -486,6 +547,12 @@
|
|||||||
function handleEvent(event) {
|
function handleEvent(event) {
|
||||||
const type = event.event || "unknown";
|
const type = event.event || "unknown";
|
||||||
logLine("event", type, event);
|
logLine("event", type, event);
|
||||||
|
if (type === "transcript" && event.isFinal && event.text) {
|
||||||
|
addChat("You", event.text);
|
||||||
|
}
|
||||||
|
if (type === "llmResponse" && event.isFinal && event.text) {
|
||||||
|
addChat("AI", event.text);
|
||||||
|
}
|
||||||
if (type === "trackStart") {
|
if (type === "trackStart") {
|
||||||
discardAudio = false;
|
discardAudio = false;
|
||||||
playbackTime = audioCtx ? audioCtx.currentTime : 0;
|
playbackTime = audioCtx ? audioCtx.currentTime : 0;
|
||||||
@@ -500,12 +567,7 @@
|
|||||||
logLine("sys", "Connect before starting mic");
|
logLine("sys", "Connect before starting mic");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!audioCtx) {
|
await ensureAudioContext();
|
||||||
audioCtx = new AudioContext();
|
|
||||||
playbackDest = audioCtx.createMediaStreamDestination();
|
|
||||||
audioOut.srcObject = playbackDest.stream;
|
|
||||||
await audioOut.play().catch(() => {});
|
|
||||||
}
|
|
||||||
const deviceId = inputSelect.value || undefined;
|
const deviceId = inputSelect.value || undefined;
|
||||||
micStream = await navigator.mediaDevices.getUserMedia({
|
micStream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: deviceId ? { deviceId: { exact: deviceId } } : true,
|
audio: deviceId ? { deviceId: { exact: deviceId } } : true,
|
||||||
@@ -595,6 +657,8 @@
|
|||||||
sendChatBtn.addEventListener("click", () => {
|
sendChatBtn.addEventListener("click", () => {
|
||||||
const text = chatInput.value.trim();
|
const text = chatInput.value.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
ensureAudioContext();
|
||||||
|
addChat("You", text);
|
||||||
sendCommand({ command: "chat", text });
|
sendCommand({ command: "chat", text });
|
||||||
chatInput.value = "";
|
chatInput.value = "";
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user