feat(webui): refactor chat page onto global client and Signal Deck

This commit is contained in:
xiaoxixi 2026-07-24 10:26:05 +08:00
parent 14ca5dd7b4
commit d9e3245031

View File

@ -2,13 +2,12 @@
import { onMount, tick } from "svelte";
import { Tooltip } from "bits-ui";
import { clientId, formatTime, randomId } from "../lib/api.js";
import { chat } from "../lib/chat.svelte.js";
import Markdown from "../lib/Markdown.svelte";
import ToolCallCard from "../lib/ToolCallCard.svelte";
import TurnView from "../lib/TurnView.svelte";
let { notify } = $props();
let socket = $state(null);
let connected = $state(false);
let sessions = $state([]);
let currentId = $state(null);
let messages = $state([]);
@ -27,8 +26,6 @@
let todoOpen = $state(false);
let messageBox;
let input;
let reconnectTimer;
let stopped = false;
const currentSession = $derived(sessions.find((item) => item.session_id === currentId));
const currentPlan = $derived(currentId ? plansBySession[currentId] || null : null);
const completedItems = $derived(currentPlan?.items?.filter((item) => item.status === "completed").length || 0);
@ -52,28 +49,7 @@
});
function send(frame) {
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify(frame));
else notify("聊天连接尚未就绪", true);
}
function connect() {
const scheme = location.protocol === "https:" ? "wss" : "ws";
const ws = new WebSocket(`${scheme}://${location.host}/ws?client_id=${encodeURIComponent(clientId())}`);
socket = ws;
ws.onopen = () => {
connected = true;
plansBySession = {};
unseenPlanSessions = {};
todoOpen = false;
send({ type: "list_sessions", include_archived: false });
send({ type: "get_slash_commands" });
};
ws.onerror = () => ws.close();
ws.onclose = () => {
connected = false;
if (!stopped) reconnectTimer = setTimeout(connect, 1800);
};
ws.onmessage = (event) => handleFrame(JSON.parse(event.data));
if (!chat.send(frame)) notify("聊天连接尚未就绪", true);
}
function handleFrame(frame) {
@ -218,7 +194,7 @@
function submit() {
const content = draft.trim();
const ready = pendingUploads.filter((upload) => upload.status === "ready");
if ((!content && !ready.length) || !connected || pendingUploads.some((upload) => upload.status === "uploading")) return;
if ((!content && !ready.length) || !chat.connected || pendingUploads.some((upload) => upload.status === "uploading")) return;
appendMessage("user", content, ready.map((upload, index) => ({
index,
name: upload.name,
@ -258,7 +234,7 @@
}
function uploadFile(file) {
if (!connected) return notify("聊天连接尚未就绪", true);
if (!chat.connected) return notify("聊天连接尚未就绪", true);
const localId = randomId();
const localUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : null;
pendingUploads = [...pendingUploads, {
@ -380,11 +356,20 @@
}
onMount(() => {
connect();
const unsubscribe = chat.subscribe(handleFrame);
const onOpen = (frame) => {
if (frame.type !== "_open") return;
plansBySession = {};
unseenPlanSessions = {};
todoOpen = false;
send({ type: "list_sessions", include_archived: false });
send({ type: "get_slash_commands" });
};
const unsubOpen = chat.subscribe(onOpen);
if (chat.connected) onOpen({ type: "_open" });
return () => {
stopped = true;
clearTimeout(reconnectTimer);
socket?.close();
unsubscribe();
unsubOpen();
clearPendingUploads();
};
});
@ -515,8 +500,8 @@
aria-activedescendant={commandSuggestions.length ? `slash-command-${selectedCommand}` : undefined}
placeholder="输入消息,输入 / 查看命令"
></textarea>
<button class="send" type="submit" aria-label="发送" disabled={!connected || (!draft.trim() && !pendingUploads.some((upload) => upload.status === "ready")) || pendingUploads.some((upload) => upload.status === "uploading")}>↑</button>
<small><span class:online={connected}>{connected ? "已连接" : "已断开,正在重连"}</span><span>/ 打开命令 · Shift+Enter 换行</span></small>
<button class="send" type="submit" aria-label="发送" disabled={!chat.connected || (!draft.trim() && !pendingUploads.some((upload) => upload.status === "ready")) || pendingUploads.some((upload) => upload.status === "uploading")}>↑</button>
<small><span class:online={chat.connected}>{chat.connected ? "已连接" : "已断开,正在重连"}</span><span>/ 打开命令 · Shift+Enter 换行</span></small>
</form>
</div>
{#if currentPlan}