summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/ws.svelte.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-19 19:40:21 +0900
committerAdam Malczewski <[email protected]>2026-05-19 19:40:21 +0900
commitf78a91c20f658dd404277919a0b872b352c99bb6 (patch)
tree58cfffb655da4443f4b7a39543b86f988f15239f /packages/frontend/src/lib/ws.svelte.ts
downloaddispatch-main.tar.gz
dispatch-main.zip
Phase 1: single agent + basic UIHEADmain
- 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.ts89
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);