summaryrefslogtreecommitdiffhomepage
path: root/src/app/App.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-22 01:31:29 +0900
committerAdam Malczewski <[email protected]>2026-06-22 01:31:29 +0900
commit8a04c21b718f5e88583f82d0604902e0cf411041 (patch)
tree28508f5697c35a9d0c8b71b71d71f95dd7a50297 /src/app/App.svelte
parent250575df308469c58477a53865f8763084612e5f (diff)
downloaddispatch-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.svelte49
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. -->