diff options
| author | Adam Malczewski <[email protected]> | 2026-05-19 19:40:21 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-19 19:40:21 +0900 |
| commit | f78a91c20f658dd404277919a0b872b352c99bb6 (patch) | |
| tree | 58cfffb655da4443f4b7a39543b86f988f15239f /packages/frontend/src/lib/ws.svelte.ts | |
| download | dispatch-main.tar.gz dispatch-main.zip | |
- Bun monorepo with @dispatch/core, @dispatch/api, @dispatch/frontend
- Agent runtime with Vercel AI SDK, streaming via WebSocket
- Tools: read_file, write_file, list_files (scoped to working directory)
- Hono API server with POST /chat, GET /status, GET /health, WS /ws
- Svelte 5 + DaisyUI frontend with chat UI, theme switcher, copy button
- OpenCode Go (Zen) as LLM provider, deepseek-v4-flash-free model
- Docker setup (dev + prod) with bin/ scripts and gopass secrets
- Biome v2 linting/formatting, Vitest tests (44 passing)
- Debug info attached to error messages for diagnostics
Diffstat (limited to 'packages/frontend/src/lib/ws.svelte.ts')
| -rw-r--r-- | packages/frontend/src/lib/ws.svelte.ts | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/packages/frontend/src/lib/ws.svelte.ts b/packages/frontend/src/lib/ws.svelte.ts new file mode 100644 index 0000000..76c7ef5 --- /dev/null +++ b/packages/frontend/src/lib/ws.svelte.ts @@ -0,0 +1,89 @@ +import { config } from "./config.js"; +import type { AgentEvent, ConnectionStatus } from "./types.js"; + +type EventCallback = (event: AgentEvent) => void; + +function createWebSocketClient(url: string) { + let connectionStatus: ConnectionStatus = $state("disconnected"); + let ws: WebSocket | null = null; + let reconnectDelay = 1000; + let reconnectTimer: ReturnType<typeof setTimeout> | null = null; + let manualDisconnect = false; + const callbacks: EventCallback[] = []; + + function connect() { + if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { + return; + } + manualDisconnect = false; + connectionStatus = "connecting"; + ws = new WebSocket(url); + + ws.onopen = () => { + connectionStatus = "connected"; + reconnectDelay = 1000; + }; + + ws.onmessage = (event: MessageEvent) => { + try { + const data = JSON.parse(event.data as string) as AgentEvent; + for (const cb of callbacks) { + cb(data); + } + } catch { + // ignore malformed messages + } + }; + + ws.onclose = () => { + connectionStatus = "disconnected"; + ws = null; + if (!manualDisconnect) { + scheduleReconnect(); + } + }; + + ws.onerror = () => { + ws?.close(); + }; + } + + function scheduleReconnect() { + if (reconnectTimer) clearTimeout(reconnectTimer); + reconnectTimer = setTimeout(() => { + reconnectTimer = null; + connect(); + }, reconnectDelay); + reconnectDelay = Math.min(reconnectDelay * 2, 10000); + } + + function disconnect() { + manualDisconnect = true; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = null; + } + ws?.close(); + ws = null; + connectionStatus = "disconnected"; + } + + function onEvent(callback: EventCallback) { + callbacks.push(callback); + return () => { + const idx = callbacks.indexOf(callback); + if (idx !== -1) callbacks.splice(idx, 1); + }; + } + + return { + get connectionStatus() { + return connectionStatus; + }, + connect, + disconnect, + onEvent, + }; +} + +export const wsClient = createWebSocketClient(config.wsUrl); |
