diff --git a/webui/src/pages/ChatPage.svelte b/webui/src/pages/ChatPage.svelte index 353865b..7cbb61c 100644 --- a/webui/src/pages/ChatPage.svelte +++ b/webui/src/pages/ChatPage.svelte @@ -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="输入消息,输入 / 查看命令" > - - {connected ? "已连接" : "已断开,正在重连"}/ 打开命令 · Shift+Enter 换行 + + {chat.connected ? "已连接" : "已断开,正在重连"}/ 打开命令 · Shift+Enter 换行 {#if currentPlan}