summaryrefslogtreecommitdiffhomepage
path: root/packages/api/src/routes
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-21 19:52:35 +0900
committerAdam Malczewski <[email protected]>2026-05-21 19:52:35 +0900
commit4ba2673557c7cfd0bc31b03e2c35ab5e1efe60e7 (patch)
treedddae8fc37a12157f4c8e840475e9e5d6203464a /packages/api/src/routes
parent55633c90c0d96e62153a4b6655f3f833a3b46ad4 (diff)
downloaddispatch-4ba2673557c7cfd0bc31b03e2c35ab5e1efe60e7.tar.gz
dispatch-4ba2673557c7cfd0bc31b03e2c35ab5e1efe60e7.zip
feat: tab system with per-tab agents, DB persistence, and DaisyUI tabs-lift UI
- Add tabs, messages, and settings tables to SQLite database - Backend: refactor AgentManager to manage per-tab Agent instances via Map<tabId, TabAgent> - Backend: WebSocket events tagged with tabId for multiplexing - Backend: tab CRUD routes (create, list, update, archive, messages) - Backend: persist user and assistant messages to DB during chat - Frontend: new tabStore replaces single chatStore with multi-tab reactive state - Frontend: TabBar component using DaisyUI tabs-lift style with status dots - Frontend: Settings sidebar panel for title generation model selection - Frontend: wire ChatPanel, ChatInput, Header to use tabStore - Fix HMR listener accumulation via wsClient.clearCallbacks() - Delete old single-chat chatStore (chat.svelte.ts)
Diffstat (limited to 'packages/api/src/routes')
-rw-r--r--packages/api/src/routes/tabs.ts81
1 files changed, 81 insertions, 0 deletions
diff --git a/packages/api/src/routes/tabs.ts b/packages/api/src/routes/tabs.ts
new file mode 100644
index 0000000..7fe05d9
--- /dev/null
+++ b/packages/api/src/routes/tabs.ts
@@ -0,0 +1,81 @@
+import { Hono } from "hono";
+import {
+ createTab,
+ getTab,
+ listOpenTabs,
+ updateTabTitle,
+ updateTabModel,
+ updateTabStatus,
+ archiveTab,
+ getMessagesForTab,
+ getSetting,
+ setSetting,
+} from "@dispatch/core";
+
+export const tabsRoutes = new Hono();
+
+let getAgentManager: () => { stopTab(id: string): void; deleteTab(id: string): void } | null = () => null;
+
+export function setTabsAgentManager(getter: () => { stopTab(id: string): void; deleteTab(id: string): void } | null): void {
+ getAgentManager = getter;
+}
+
+tabsRoutes.get("/", (c) => {
+ const tabs = listOpenTabs();
+ return c.json({ tabs });
+});
+
+tabsRoutes.post("/", async (c) => {
+ const body = await c.req.json<{ id?: string; title?: string }>();
+ const id = body.id ?? crypto.randomUUID();
+ const title = body.title ?? "New Tab";
+ const tab = createTab(id, title);
+ return c.json(tab);
+});
+
+// Settings routes (must be before /:id to avoid conflict)
+tabsRoutes.get("/settings/title-model", (c) => {
+ const keyId = getSetting("title_model_key_id");
+ const modelId = getSetting("title_model_id");
+ return c.json({ keyId, modelId });
+});
+
+tabsRoutes.put("/settings/title-model", async (c) => {
+ const body = await c.req.json<{ keyId?: string; modelId?: string }>();
+ if (body.keyId) setSetting("title_model_key_id", body.keyId);
+ if (body.modelId) setSetting("title_model_id", body.modelId);
+ return c.json({ success: true });
+});
+
+tabsRoutes.get("/:id", (c) => {
+ const id = c.req.param("id");
+ const tab = getTab(id);
+ if (!tab) return c.json({ error: "tab not found" }, 404);
+ return c.json(tab);
+});
+
+tabsRoutes.get("/:id/messages", (c) => {
+ const id = c.req.param("id");
+ const messages = getMessagesForTab(id);
+ return c.json({ messages });
+});
+
+tabsRoutes.patch("/:id", async (c) => {
+ const id = c.req.param("id");
+ const body = await c.req.json<{ title?: string; keyId?: string; modelId?: string; status?: string }>();
+ if (body.title !== undefined) updateTabTitle(id, body.title);
+ if (body.keyId !== undefined || body.modelId !== undefined) {
+ updateTabModel(id, body.keyId ?? null, body.modelId ?? null);
+ }
+ if (body.status !== undefined) updateTabStatus(id, body.status);
+ const tab = getTab(id);
+ return c.json(tab);
+});
+
+tabsRoutes.delete("/:id", (c) => {
+ const id = c.req.param("id");
+ const mgr = getAgentManager();
+ if (mgr) mgr.deleteTab(id);
+ archiveTab(id);
+ return c.json({ success: true });
+});