diff options
| author | Adam Malczewski <[email protected]> | 2026-06-22 01:31:29 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-22 01:31:29 +0900 |
| commit | 8a04c21b718f5e88583f82d0604902e0cf411041 (patch) | |
| tree | 28508f5697c35a9d0c8b71b71d71f95dd7a50297 /src/app/App.svelte | |
| parent | 250575df308469c58477a53865f8763084612e5f (diff) | |
| download | dispatch-web-8a04c21b718f5e88583f82d0604902e0cf411041.tar.gz dispatch-web-8a04c21b718f5e88583f82d0604902e0cf411041.zip | |
feat(compaction): conversation compacting + auto-compact threshold
Consume the compaction handoff ([email protected], [email protected]).
Re-pinned file: deps + re-mirrored .dispatch/*.reference.md.
- New 'Compaction' sidebar view (CompactionView.svelte):
- 'Compact now' button → POST /conversations/:id/compact (loading indicator
+ result: 'N messages summarized, M kept')
- Auto-compact threshold number input → GET/PUT
/conversations/:id/compact-threshold (0 = disabled, default 350000)
- Re-mounts per conversation via {#key}
- App store: compactNow() + compactThreshold reactive state +
setCompactThreshold(), seeded on focus change (like reasoning-effort + cwd)
- conversation.compacted WS handler: reloads the SAME conversation's history
(ID unchanged — old history forked to an archive, not a tab switch)
- WS adapter parses newConversationId field on ConversationCompactedMessage
- conformance guards + tests cover the new type
686 tests green.
Diffstat (limited to 'src/app/App.svelte')
| -rw-r--r-- | src/app/App.svelte | 49 |
1 files changed, 47 insertions, 2 deletions
diff --git a/src/app/App.svelte b/src/app/App.svelte index e065759..350db49 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -10,11 +10,14 @@ } from "../features/cache-warming"; import { ChatView, + CompactionView, Composer, manifest as chatManifest, ModelSelector, ReasoningEffortSelector, + type CompactNowResult, type ReasoningEffortSaveResult, + type SaveCompactThresholdResult, } from "../features/chat"; import { manifest as conversationCacheManifest } from "../features/conversation-cache"; import { manifest as markdownManifest } from "../features/markdown"; @@ -65,11 +68,20 @@ { id: "extensions", label: "Extensions" }, { id: "cache-warming", label: "Cache Warming" }, { id: "tasks", label: "Tasks" }, + { id: "compaction", label: "Compaction" }, { id: "settings", label: "Settings" }, ] as const; - // Default sidebar layout: Model, Language Servers, Extensions, Cache Warming, Tasks, Settings. - const initialViews = ["model", "lsp", "extensions", "cache-warming", "tasks", "settings"] as const; + // Default sidebar layout: Model, Language Servers, Extensions, Cache Warming, Tasks, Compaction, Settings. + const initialViews = [ + "model", + "lsp", + "extensions", + "cache-warming", + "tasks", + "compaction", + "settings", + ] as const; // Frontend module list for the "Loaded Modules" view, AGGREGATED from each // feature's public `manifest` export so it can't drift from what's actually @@ -211,6 +223,29 @@ : { ok: false, error: result.error }; } + // Adapt the store's compact result to the compaction view's port. + async function compactNow(): Promise<CompactNowResult | null> { + const result = await store.compactNow(); + if (result === null) return null; + return result.ok + ? { + ok: true, + messagesSummarized: result.response.messagesSummarized, + messagesKept: result.response.messagesKept, + } + : { ok: false, error: result.error }; + } + + async function saveCompactThreshold( + threshold: number, + ): Promise<SaveCompactThresholdResult | null> { + const result = await store.setCompactThreshold(threshold); + if (result === null) return null; + return result.ok + ? { ok: true, threshold: result.threshold } + : { ok: false, error: result.error }; + } + // Adapt the store's chat-limit result to the settings feature's port. On a // raise the active chat refills (prepends older history); preserve the // reader's viewport over the prepend (the manual analogue of CSS scroll @@ -426,6 +461,16 @@ <p class="text-xs opacity-60">No tasks yet.</p> {/if} {/key} + {:else if kind === "compaction"} + <!-- Re-mount per conversation so the threshold + feedback can't bleed across tabs. --> + {#key store.currentConversationId} + <CompactionView + threshold={store.compactThreshold} + canCompact={store.activeConversationId !== null} + {compactNow} + saveThreshold={saveCompactThreshold} + /> + {/key} {:else if kind === "settings"} <!-- FE-local settings. Not conversation-scoped (no {#key}: the chat limit is global), so the field stays mounted across tab switches. --> |
