diff options
| author | Adam Malczewski <[email protected]> | 2026-06-22 15:14:31 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-22 15:14:31 +0900 |
| commit | 0c7e7ceae36930e87fc30993f18e30cf54888295 (patch) | |
| tree | deae0cde3105e6cd22c1cd13f59b5524edad611c /src/features/chat/ui/CompactionView.svelte | |
| parent | b7ea4b7325c02bf29046ab232411c053b36a99bd (diff) | |
| download | dispatch-web-0c7e7ceae36930e87fc30993f18e30cf54888295.tar.gz dispatch-web-0c7e7ceae36930e87fc30993f18e30cf54888295.zip | |
feat: consume context window + percentage-based compact handoff
1. Real context window: GET /models now returns modelInfo[model].contextWindow.
The Composer uses this instead of the hardcoded MAX_CONTEXT = 1,000,000.
Falls back to 1M when modelInfo is absent or the model has no contextWindow.
2. Percentage-based auto-compact: the compact-threshold endpoint is renamed
to compact-percent. The CompactionView now shows a percent input (0-100,
default 85, 0 = manual) instead of a token count input. Types renamed:
CompactThresholdResponse → CompactPercentResponse,
SetCompactThresholdRequest → SetCompactPercentRequest.
Note: the field name in the backend types is still 'threshold' (not
'percent') — the FE maps between them.
Re-mirrored .dispatch/transport-contract.reference.md.
686 tests green. 0 svelte-check errors + warnings.
Diffstat (limited to 'src/features/chat/ui/CompactionView.svelte')
| -rw-r--r-- | src/features/chat/ui/CompactionView.svelte | 101 |
1 files changed, 51 insertions, 50 deletions
diff --git a/src/features/chat/ui/CompactionView.svelte b/src/features/chat/ui/CompactionView.svelte index ce2a0a0..7bec984 100644 --- a/src/features/chat/ui/CompactionView.svelte +++ b/src/features/chat/ui/CompactionView.svelte @@ -3,54 +3,54 @@ | { readonly ok: true; readonly messagesSummarized: number; readonly messagesKept: number } | { readonly ok: false; readonly error: string }; - export type SaveCompactThresholdResult = - | { readonly ok: true; readonly threshold: number } + export type SaveCompactPercentResult = + | { readonly ok: true; readonly percent: number } | { readonly ok: false; readonly error: string }; let { - threshold, + percent, canCompact, compactNow, - saveThreshold, + savePercent, }: { - /** The conversation's auto-compact threshold, or null when not yet fetched. 0 = disabled. */ - threshold: number | null; + /** The conversation's auto-compact percent (0-100), or null when not yet fetched. 0 = disabled. */ + percent: number | null; /** Whether a real conversation is focused (a draft has nothing to compact). */ canCompact: boolean; compactNow: () => Promise<CompactNowResult | null>; - saveThreshold: (threshold: number) => Promise<SaveCompactThresholdResult | null>; + savePercent: (percent: number) => Promise<SaveCompactPercentResult | null>; } = $props(); - const DEFAULT_THRESHOLD = 350000; + const DEFAULT_PERCENT = 85; let compacting = $state(false); let compactError = $state<string | null>(null); let compactResult = $state<{ summarized: number; kept: number } | null>(null); - let thresholdInput = $state(""); - let savingThreshold = $state(false); - let thresholdError = $state<string | null>(null); - let thresholdSaved = $state(false); + let percentInput = $state(""); + let savingPercent = $state(false); + let percentError = $state<string | null>(null); + let percentSaved = $state(false); // Sync the input from the prop when it changes (focus switch / initial load). - let lastThreshold = $state<number | null>(null); + let lastPercent = $state<number | null>(null); $effect(() => { - if (threshold !== lastThreshold) { - lastThreshold = threshold; - thresholdInput = threshold !== null ? String(threshold) : ""; - thresholdError = null; - thresholdSaved = false; + if (percent !== lastPercent) { + lastPercent = percent; + percentInput = percent !== null ? String(percent) : ""; + percentError = null; + percentSaved = false; } }); - const thresholdLabel = $derived( - threshold == null + const percentLabel = $derived( + percent == null ? "Loading…" - : threshold === 0 + : percent === 0 ? "Disabled (manual only)" - : threshold === DEFAULT_THRESHOLD - ? `${threshold.toLocaleString("en-US")} (default)` - : threshold.toLocaleString("en-US"), + : percent === DEFAULT_PERCENT + ? `${percent}% (default)` + : `${percent}%`, ); async function handleCompact() { @@ -68,22 +68,22 @@ } } - async function handleSaveThreshold() { - const value = Number.parseInt(thresholdInput, 10); - if (Number.isNaN(value) || value < 0) { - thresholdError = "Must be a non-negative number"; + async function handleSavePercent() { + const value = Number.parseInt(percentInput, 10); + if (Number.isNaN(value) || value < 0 || value > 100) { + percentError = "Must be 0-100"; return; } - savingThreshold = true; - thresholdError = null; - thresholdSaved = false; - const result = await saveThreshold(value); - savingThreshold = false; + savingPercent = true; + percentError = null; + percentSaved = false; + const result = await savePercent(value); + savingPercent = false; if (result === null) return; if (result.ok) { - thresholdSaved = true; + percentSaved = true; } else { - thresholdError = result.error; + percentError = result.error; } } </script> @@ -120,33 +120,34 @@ {/if} </section> - <!-- Auto-compact threshold --> + <!-- Auto-compact percent --> <section class="flex flex-col gap-1"> - <span class="text-xs font-semibold uppercase opacity-60">Auto-compact threshold</span> + <span class="text-xs font-semibold uppercase opacity-60">Auto-compact percent</span> <div class="flex items-center gap-2"> <input type="number" - class="input input-bordered input-sm w-32" + class="input input-bordered input-sm w-24" min="0" - placeholder={DEFAULT_THRESHOLD.toLocaleString("en-US")} - value={thresholdInput} - disabled={savingThreshold} - onchange={handleSaveThreshold} - aria-label="Compact threshold (tokens)" + max="100" + placeholder={String(DEFAULT_PERCENT)} + value={percentInput} + disabled={savingPercent} + onchange={handleSavePercent} + aria-label="Compact percent (0-100)" /> - <span class="text-xs opacity-60">tokens</span> - {#if savingThreshold} + <span class="text-xs opacity-60">%</span> + {#if savingPercent} <span class="loading loading-spinner loading-xs"></span> {/if} </div> <p class="text-xs opacity-50"> - Current: {thresholdLabel} + Current: {percentLabel} <br /> - 0 disables auto-compact. Default is {DEFAULT_THRESHOLD.toLocaleString("en-US")}. + 0 disables auto-compact. Default is {DEFAULT_PERCENT}%. </p> - {#if thresholdError} - <p class="text-xs text-error">{thresholdError}</p> - {:else if thresholdSaved} + {#if percentError} + <p class="text-xs text-error">{percentError}</p> + {:else if percentSaved} <p class="text-xs text-success">Saved.</p> {/if} </section> |
