summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/ModelSelector.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-03 01:26:16 +0900
committerAdam Malczewski <[email protected]>2026-06-03 01:26:16 +0900
commitb26821ead97b986f886065b20d3dbde8283daa64 (patch)
tree3c41419afb805d88080392e1c97d233af910b79d /packages/frontend/src/lib/components/ModelSelector.svelte
parent4b45d33c256cf580a53054078be6fd7148fa6302 (diff)
downloaddispatch-b26821ead97b986f886065b20d3dbde8283daa64.tar.gz
dispatch-b26821ead97b986f886065b20d3dbde8283daa64.zip
feat(compaction): add UI-driven conversation compaction
Summarize a conversation's older "head" into a structured anchored Markdown summary while preserving the most recent turns verbatim, shrinking context size while keeping the information needed to continue coherently. Triggered by a "Compact conversation" button in Chat Settings (not an agent tool). Approach informed by OpenCode's session/compaction.ts: - Ported SUMMARY_TEMPLATE (Goal / Constraints / Progress / Key Decisions / Next Steps / Critical Context / Relevant Files) and the anchored-summary buildPrompt (re-summarizes a prior summary when present). - Ported the TOOL_OUTPUT_MAX_CHARS (2000) cap on tool results in the summary request. - Simplified tail selection to a fixed recent-turn count (DEFAULT_TAIL_TURNS=2) instead of OpenCode's token-budget splitTurn. core: - New src/compaction/ module (pure, DB-free): template, prompt builder, head/tail selection, transcript renderer with tool-output capping, prior summary extraction. Generic over ChatMessage so callers keep turnId/seq. - db/chunks.ts: rekeyChunks(from,to) relocates a tab's full history to a backup tab (reversible — nothing is deleted). - AgentEvent: compaction-started / -complete / -error variants. api: - AgentManager.compactTab(tempTabId, sourceTabId): side-effect-free resolveConnection() for the compactor model (configured compaction_model_*, else the source tab's own key+model), one-shot tool-less summary generation via a transient Agent, then relocate full history to a fresh backup tab and re-seed the canonical source id with [summary turn + preserved tail]. Source tab is locked (messages queue) during the run; queue drains afterward. - Routes: POST /tabs/:id/compact, GET/PUT /tabs/settings/compaction-model. frontend: - "Compact conversation" button in ModelSelector (Chat Settings), between Working Directory and the agent toggle; idle-gated. - Compaction-model key+model selector in Settings, beside the title model. - Transient placeholder tab shows a large, non-faded "Please wait, compacting conversation…" screen; closing it cancels. Source input locked while running. - Handle compaction-* events: reload compacted source, insert backup tab, refocus source, discard placeholder. tests: core compaction unit tests, rekeyChunks DB test, AgentManager.compactTab orchestration tests, and compaction route tests. All green (713 tests), biome clean, all typechecks pass, frontend builds.
Diffstat (limited to 'packages/frontend/src/lib/components/ModelSelector.svelte')
-rw-r--r--packages/frontend/src/lib/components/ModelSelector.svelte24
1 files changed, 24 insertions, 0 deletions
diff --git a/packages/frontend/src/lib/components/ModelSelector.svelte b/packages/frontend/src/lib/components/ModelSelector.svelte
index 8601795..3b43ff8 100644
--- a/packages/frontend/src/lib/components/ModelSelector.svelte
+++ b/packages/frontend/src/lib/components/ModelSelector.svelte
@@ -63,6 +63,9 @@ const modelCache = new Map<string, string[]>();
onReasoningChange,
onAgentChange = (_agent: AgentInfo | null) => {},
onWorkingDirectoryChange = (_dir: string | null) => {},
+ onCompact = () => {},
+ canCompact = false,
+ compacting = false,
}: {
keys?: KeyInfo[];
activeKeyId?: string | null;
@@ -77,6 +80,9 @@ const modelCache = new Map<string, string[]>();
onReasoningChange: (effort: string) => void;
onAgentChange?: (agent: AgentInfo | null) => void;
onWorkingDirectoryChange?: (dir: string | null) => void;
+ onCompact?: () => void;
+ canCompact?: boolean;
+ compacting?: boolean;
} = $props();
let showKeyModal = $state(false);
@@ -224,6 +230,24 @@ const modelCache = new Map<string, string[]>();
</div>
</div>
+ <!-- Compact conversation -->
+ <div class="mb-3">
+ <button
+ type="button"
+ class="btn btn-sm btn-outline w-full"
+ disabled={!canCompact || compacting}
+ onclick={onCompact}
+ title="Summarize older turns into a compact anchor, preserving the most recent turns. Opens a new tab while it works; the conversation continues here once done."
+ >
+ {#if compacting}
+ <span class="loading loading-spinner loading-xs"></span>
+ Compacting…
+ {:else}
+ Compact conversation
+ {/if}
+ </button>
+ </div>
+
<!-- Toggle -->
<div class="flex items-center gap-2 mb-3">
<button