summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/ui/CompactionView.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-22 15:14:31 +0900
committerAdam Malczewski <[email protected]>2026-06-22 15:14:31 +0900
commit0c7e7ceae36930e87fc30993f18e30cf54888295 (patch)
treedeae0cde3105e6cd22c1cd13f59b5524edad611c /src/features/chat/ui/CompactionView.svelte
parentb7ea4b7325c02bf29046ab232411c053b36a99bd (diff)
downloaddispatch-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.svelte101
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>