diff options
| author | Adam Malczewski <[email protected]> | 2026-05-21 19:52:35 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-21 19:52:35 +0900 |
| commit | 4ba2673557c7cfd0bc31b03e2c35ab5e1efe60e7 (patch) | |
| tree | dddae8fc37a12157f4c8e840475e9e5d6203464a /packages/api/src/routes | |
| parent | 55633c90c0d96e62153a4b6655f3f833a3b46ad4 (diff) | |
| download | dispatch-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.ts | 81 |
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 }); +}); |
