Skip to content

Context window control and best reply badge#30

Open
sidharthmirch wants to merge 5 commits into
mainfrom
sidharthmirch/context-control
Open

Context window control and best reply badge#30
sidharthmirch wants to merge 5 commits into
mainfrom
sidharthmirch/context-control

Conversation

@sidharthmirch

Copy link
Copy Markdown
Owner

Implements two QoL features:

Feature 1: Context Window Control

Lets users limit how many previous turns are sent to the LLM per chat, with a global app_metadata fallback.

  • Migration 145: adds context_window_size INTEGER DEFAULT NULL to chats
  • ChatAPI: exposes contextWindowSize on Chat type; new useUpdateContextWindowSize mutation
  • ChatState: new applyContextWindow(messageSets, windowSize) utility that slices to the last N×2 message sets
  • MessageAPI: wires applyContextWindow at all three LLM call sites (useRestartMessageLegacy, useStreamToolsMessage, useAddMessageToCompareBlock), resolving window size from the chat's own setting or default_context_window_size in app_metadata
  • ContextWindowPill: pill button in the chat input bar; shows "N/T turns" when active; popover has a toggle switch + number input

Feature 2: Best Reply Badge

For historical tools-block message sets (not the last row):

  • Filled star icon (amber) appears next to the model name when message.selected === true
  • Star action button appears in the hover action bar; clicking it calls selectMessage; tooltip reads "Mark as best" / "Best reply"

Test Plan

  • Open a chat with several turns; the ContextWindowPill (layers icon) appears in the input bar
  • Click the pill → popover opens with "Full history" toggle off
  • Toggle on → defaults to 10 turns; "N/T turns" label appears on the pill
  • Change the number input → pill label updates
  • Toggle off → pill returns to icon-only; full history is restored
  • Regenerate a message and confirm the correct number of prior turns is sent
  • In a multi-model tools block with >1 turn of history, hover an older message card → star action button appears
  • Click the star → message becomes selected; filled star badge appears next to model name
  • Existing regenerate button still works on the last row

by-claude

…ply badge

- Migration 145: adds context_window_size column to chats table
- ChatAPI: adds contextWindowSize to Chat type, useUpdateContextWindowSize mutation
- ChatState: adds applyContextWindow() to slice message sets before LLM calls
- MessageAPI: wires applyContextWindow at 3 call sites (restartLegacy, streamTools, addToCompare), resolving window size from chat or app_metadata fallback
- ContextWindowPill: new pill component in ChatInput showing "N/T turns" when active, popover with toggle + number input
- MultiChat: adds filled star badge next to model name for historical selected messages, and star action button in hover bar for historical unselected messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 8, 2026 19:55
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds per-chat context window limiting (with a global app_metadata fallback) and introduces a “best reply” affordance for historical tools-block replies to improve QoL when working with longer/multi-model chats.

Changes:

  • Added context_window_size to chats (migration + Chat API typing/querying + mutation to update it).
  • Implemented context-window slicing via applyContextWindow(...) and wired it into 3 LLM conversation build paths.
  • Added UI for context window control (ContextWindowPill) and a star badge/action for “best reply” on older tools rows.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/ui/components/MultiChat.tsx Adds star badge + star action button for historical tools messages.
src/ui/components/ContextWindowPill.tsx New pill/popover UI to enable/disable and set per-chat context window size.
src/ui/components/ChatInput.tsx Surfaces ContextWindowPill in the input bar for non-reply chats.
src/core/chorus/ChatState.ts Adds applyContextWindow utility for slicing message sets by last N turns.
src/core/chorus/api/MessageAPI.ts Resolves window size (chat setting / app_metadata fallback) and applies it at LLM call sites.
src/core/chorus/api/ChatAPI.ts Adds contextWindowSize to Chat and a useUpdateContextWindowSize mutation.
src-tauri/src/migrations.rs Migration 145 adds context_window_size column to chats.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ui/components/MultiChat.tsx Outdated
Comment on lines +1417 to +1427
<button
className={`hover:text-foreground ${message.selected ? "text-amber-400" : ""}`}
onClick={() => {
selectMessage.mutate({
chatId: message.chatId,
messageSetId:
message.messageSetId,
messageId:
message.id,
});
}}

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectMessage.mutate is called without the required blockType argument. useSelectMessage expects { chatId, messageSetId, messageId, blockType }, and omitting it will either fail type-checking or result in a DB update with an undefined block type (no rows updated), so clicking the star won’t reliably mark the message as selected. Pass blockType: "tools" here (and consider no-op’ing the click when message.selected is already true).

Copilot uses AI. Check for mistakes.
Comment on lines +381 to +388
export function applyContextWindow(
messageSets: MessageSetDetail[],
windowSize: number | undefined,
): MessageSetDetail[] {
if (windowSize === undefined) return messageSets;
if (windowSize === 0) return [];
return messageSets.slice(-(windowSize * 2));
}

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyContextWindow should defensively handle invalid window sizes (e.g., negative numbers or NaN). With the current implementation, a negative value will slice from the front (unexpectedly dropping recent context), and NaN will effectively disable slicing in a non-obvious way. Suggest clamping/validating with something like: if windowSize is not a finite integer > 0, treat it as undefined (full history) or as 0 (empty), depending on intended semantics.

Copilot uses AI. Check for mistakes.
Comment thread src/core/chorus/api/MessageAPI.ts Outdated
Comment on lines +1093 to +1106
const chat = await queryClient.ensureQueryData(
chatQueries.detail(chatId),
);
const appMetadata = await queryClient.ensureQueryData({
queryKey: appMetadataKeys.appMetadata(),
queryFn: () => fetchAppMetadata(),
});
const windowSize =
chat?.contextWindowSize ??
(appMetadata["default_context_window_size"]
? parseInt(appMetadata["default_context_window_size"])
: undefined);
const conversation = [
...llmConversation(previousMessageSets),
...llmConversation(applyContextWindow(previousMessageSets, windowSize)),

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default_context_window_size fallback is parsed with parseInt(...) but never validated. If the metadata value is non-numeric, negative, or 0, windowSize becomes NaN/invalid and is still passed into applyContextWindow, producing surprising behavior. Consider centralizing window-size resolution into a small helper (used at all three call sites) that validates Number.isFinite, clamps to an integer, and ignores non-positive values.

Copilot uses AI. Check for mistakes.
Comment thread src/ui/components/ContextWindowPill.tsx Outdated
Comment on lines +99 to +110
{isActive && (
<div className="space-y-1.5">
<label className="text-xs text-muted-foreground">
Last N turns to include
</label>
<input
type="number"
min={1}
value={windowSize}
onChange={(e) => handleSizeChange(e.target.value)}
className="w-full rounded-md border border-input bg-background px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
/>

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context window size <input> is controlled by windowSize coming from the chat query, and onChange immediately triggers a mutation. This makes the field hard to edit (e.g., backspace produces '' → ignored → value snaps back; typing multi-digit values will fire multiple DB writes and can lag). Keep a local input state (string) while editing and commit via debounce/onBlur/Enter, then update the persisted value.

Copilot uses AI. Check for mistakes.
@sidharthmirch

Copy link
Copy Markdown
Owner Author

WARNING CHECK BEFORE MERGING #29 #30 #31 DUE TO CONFLICTING MIGRATION #'s

- Fix readChat converting contextWindowSize=0 to undefined (critical:
  Current Message mode was silently dropped on reload since ?? treats 0
  as falsy; now uses strict null check)
- Pre-compute dimming cutoff and message set slicing with useMemo in
  MultiChat to avoid per-render recalculations
- Add stable EMPTY_STRING_SET constant for minimizedModels fallback
- Add ARIA role=radiogroup, role=radio, aria-checked to ContextWindowPill
  toggle buttons for screen reader accessibility
- Add focus-visible ring styles to toggle buttons
- Add htmlFor/id association for number input label
- Disable number input when in Full History mode
- Add aria-hidden to dimmed MessageSetView containers
- Reduce dimming transition from 300ms to 200ms
- Add 15 new test cases for contextWindowState (edge cases: contextWindowSize
  0, NaN, Infinity, empty string, number inputs, undefined stored turns)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants