From 14d9a1246472430b4186d3fbb291a0fb2240b958 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Tue, 13 May 2025 11:51:44 -0700 Subject: [PATCH] Max chat turns settings (default to 5) (#152) --- src/components/MaxChatTurnsSelector.tsx | 92 ++++++++++++++++++++++++ src/constants/settings_constants.ts | 1 + src/ipc/handlers/chat_stream_handlers.ts | 42 ++++++++++- src/ipc/handlers/proposal_handlers.ts | 4 +- src/lib/schemas.ts | 1 + src/pages/settings.tsx | 5 ++ 6 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 src/components/MaxChatTurnsSelector.tsx create mode 100644 src/constants/settings_constants.ts diff --git a/src/components/MaxChatTurnsSelector.tsx b/src/components/MaxChatTurnsSelector.tsx new file mode 100644 index 0000000..d6d0387 --- /dev/null +++ b/src/components/MaxChatTurnsSelector.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { useSettings } from "@/hooks/useSettings"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { MAX_CHAT_TURNS_IN_CONTEXT } from "@/constants/settings_constants"; + +interface OptionInfo { + value: string; + label: string; + description: string; +} + +const defaultValue = "default"; + +const options: OptionInfo[] = [ + { + value: "3", + label: "Economy (3)", + description: + "Minimal context to reduce token usage and improve response times.", + }, + { + value: defaultValue, + label: `Default (${MAX_CHAT_TURNS_IN_CONTEXT}) `, + description: "Balanced context size for most conversations.", + }, + { + value: "10", + label: "High (10)", + description: + "Extended context for complex conversations requiring more history.", + }, + { + value: "100", + label: "Max (100)", + description: "Maximum context (not recommended due to cost and speed).", + }, +]; + +export const MaxChatTurnsSelector: React.FC = () => { + const { settings, updateSettings } = useSettings(); + + const handleValueChange = (value: string) => { + if (value === "default") { + updateSettings({ maxChatTurnsInContext: undefined }); + } else { + const numValue = parseInt(value, 10); + updateSettings({ maxChatTurnsInContext: numValue }); + } + }; + + // Determine the current value + const currentValue = + settings?.maxChatTurnsInContext?.toString() || defaultValue; + + // Find the current option to display its description + const currentOption = + options.find((opt) => opt.value === currentValue) || options[1]; + + return ( +
+
+ + +
+
+ {currentOption.description} +
+
+ ); +}; diff --git a/src/constants/settings_constants.ts b/src/constants/settings_constants.ts new file mode 100644 index 0000000..2bb4455 --- /dev/null +++ b/src/constants/settings_constants.ts @@ -0,0 +1 @@ +export const MAX_CHAT_TURNS_IN_CONTEXT = 5; diff --git a/src/ipc/handlers/chat_stream_handlers.ts b/src/ipc/handlers/chat_stream_handlers.ts index 9d66953..ae50789 100644 --- a/src/ipc/handlers/chat_stream_handlers.ts +++ b/src/ipc/handlers/chat_stream_handlers.ts @@ -28,6 +28,7 @@ import * as os from "os"; import * as crypto from "crypto"; import { readFile, writeFile, unlink } from "fs/promises"; import { getMaxTokens } from "../utils/token_utils"; +import { MAX_CHAT_TURNS_IN_CONTEXT } from "@/constants/settings_constants"; const logger = log.scope("chat_stream_handlers"); @@ -241,6 +242,45 @@ export function registerChatStreamHandlers() { role: message.role as "user" | "assistant" | "system", content: message.content, })); + + // Limit chat history based on maxChatTurnsInContext setting + // We add 1 because the current prompt counts as a turn. + const maxChatTurns = + (settings.maxChatTurnsInContext || MAX_CHAT_TURNS_IN_CONTEXT) + 1; + + // If we need to limit the context, we take only the most recent turns + let limitedMessageHistory = messageHistory; + if (messageHistory.length > maxChatTurns * 2) { + // Each turn is a user + assistant pair + // Calculate how many messages to keep (maxChatTurns * 2) + let recentMessages = messageHistory + .filter((msg) => msg.role !== "system") + .slice(-maxChatTurns * 2); + + // Ensure the first message is a user message + if (recentMessages.length > 0 && recentMessages[0].role !== "user") { + // Find the first user message + const firstUserIndex = recentMessages.findIndex( + (msg) => msg.role === "user", + ); + if (firstUserIndex > 0) { + // Drop assistant messages before the first user message + recentMessages = recentMessages.slice(firstUserIndex); + } else if (firstUserIndex === -1) { + logger.warn( + "No user messages found in recent history, set recent messages to empty", + ); + recentMessages = []; + } + } + + limitedMessageHistory = [...recentMessages]; + + logger.log( + `Limiting chat history from ${messageHistory.length} to ${limitedMessageHistory.length} messages (max ${maxChatTurns} turns)`, + ); + } + let systemPrompt = SYSTEM_PROMPT; if ( updatedChat.app?.supabaseProjectId && @@ -292,7 +332,7 @@ This conversation includes one or more image attachments. When the user uploads role: "assistant", content: "OK, got it. I'm ready to help", }, - ...messageHistory.map((msg) => ({ + ...limitedMessageHistory.map((msg) => ({ role: msg.role as "user" | "assistant" | "system", content: msg.content, })), diff --git a/src/ipc/handlers/proposal_handlers.ts b/src/ipc/handlers/proposal_handlers.ts index 9a5bd87..6d94495 100644 --- a/src/ipc/handlers/proposal_handlers.ts +++ b/src/ipc/handlers/proposal_handlers.ts @@ -285,9 +285,9 @@ const getProposalHandler = async ( ); // If we're using more than 80% of the context window, suggest summarizing - if (totalTokens > contextWindow * 0.8) { + if (totalTokens > contextWindow * 0.8 || chat.messages.length > 10) { logger.log( - `Token usage high (${totalTokens}/${contextWindow}), suggesting summarize action`, + `Token usage is high (${totalTokens}/${contextWindow}) OR long chat history (${chat.messages.length} messages), suggesting summarize action`, ); actions.push({ id: "summarize-in-new-chat", diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index 485253f..22c20c3 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -117,6 +117,7 @@ export const UserSettingsSchema = z.object({ dyadProBudget: DyadProBudgetSchema.optional(), experiments: ExperimentsSchema.optional(), lastShownReleaseNotesVersion: z.string().optional(), + maxChatTurnsInContext: z.number().optional(), // DEPRECATED. runtimeMode: RuntimeModeSchema.optional(), }); diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 962ebed..3183c04 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -6,6 +6,7 @@ import { IpcClient } from "@/ipc/ipc_client"; import { showSuccess, showError } from "@/lib/toast"; import { AutoApproveSwitch } from "@/components/AutoApproveSwitch"; import { TelemetrySwitch } from "@/components/TelemetrySwitch"; +import { MaxChatTurnsSelector } from "@/components/MaxChatTurnsSelector"; import { useSettings } from "@/hooks/useSettings"; import { useAppVersion } from "@/hooks/useAppVersion"; import { Button } from "@/components/ui/button"; @@ -106,6 +107,10 @@ export default function SettingsPage() { This will automatically approve code changes and run them. + +
+ +