Max chat turns settings (default to 5) (#152)
This commit is contained in:
92
src/components/MaxChatTurnsSelector.tsx
Normal file
92
src/components/MaxChatTurnsSelector.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label
|
||||||
|
htmlFor="max-chat-turns"
|
||||||
|
className="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Maximum number of chat turns used in context
|
||||||
|
</label>
|
||||||
|
<Select value={currentValue} onValueChange={handleValueChange}>
|
||||||
|
<SelectTrigger className="w-[180px]" id="max-chat-turns">
|
||||||
|
<SelectValue placeholder="Select turns" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{options.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{currentOption.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
src/constants/settings_constants.ts
Normal file
1
src/constants/settings_constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const MAX_CHAT_TURNS_IN_CONTEXT = 5;
|
||||||
@@ -28,6 +28,7 @@ import * as os from "os";
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import { readFile, writeFile, unlink } from "fs/promises";
|
import { readFile, writeFile, unlink } from "fs/promises";
|
||||||
import { getMaxTokens } from "../utils/token_utils";
|
import { getMaxTokens } from "../utils/token_utils";
|
||||||
|
import { MAX_CHAT_TURNS_IN_CONTEXT } from "@/constants/settings_constants";
|
||||||
|
|
||||||
const logger = log.scope("chat_stream_handlers");
|
const logger = log.scope("chat_stream_handlers");
|
||||||
|
|
||||||
@@ -241,6 +242,45 @@ export function registerChatStreamHandlers() {
|
|||||||
role: message.role as "user" | "assistant" | "system",
|
role: message.role as "user" | "assistant" | "system",
|
||||||
content: message.content,
|
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;
|
let systemPrompt = SYSTEM_PROMPT;
|
||||||
if (
|
if (
|
||||||
updatedChat.app?.supabaseProjectId &&
|
updatedChat.app?.supabaseProjectId &&
|
||||||
@@ -292,7 +332,7 @@ This conversation includes one or more image attachments. When the user uploads
|
|||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: "OK, got it. I'm ready to help",
|
content: "OK, got it. I'm ready to help",
|
||||||
},
|
},
|
||||||
...messageHistory.map((msg) => ({
|
...limitedMessageHistory.map((msg) => ({
|
||||||
role: msg.role as "user" | "assistant" | "system",
|
role: msg.role as "user" | "assistant" | "system",
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -285,9 +285,9 @@ const getProposalHandler = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
// If we're using more than 80% of the context window, suggest summarizing
|
// 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(
|
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({
|
actions.push({
|
||||||
id: "summarize-in-new-chat",
|
id: "summarize-in-new-chat",
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export const UserSettingsSchema = z.object({
|
|||||||
dyadProBudget: DyadProBudgetSchema.optional(),
|
dyadProBudget: DyadProBudgetSchema.optional(),
|
||||||
experiments: ExperimentsSchema.optional(),
|
experiments: ExperimentsSchema.optional(),
|
||||||
lastShownReleaseNotesVersion: z.string().optional(),
|
lastShownReleaseNotesVersion: z.string().optional(),
|
||||||
|
maxChatTurnsInContext: z.number().optional(),
|
||||||
// DEPRECATED.
|
// DEPRECATED.
|
||||||
runtimeMode: RuntimeModeSchema.optional(),
|
runtimeMode: RuntimeModeSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IpcClient } from "@/ipc/ipc_client";
|
|||||||
import { showSuccess, showError } from "@/lib/toast";
|
import { showSuccess, showError } from "@/lib/toast";
|
||||||
import { AutoApproveSwitch } from "@/components/AutoApproveSwitch";
|
import { AutoApproveSwitch } from "@/components/AutoApproveSwitch";
|
||||||
import { TelemetrySwitch } from "@/components/TelemetrySwitch";
|
import { TelemetrySwitch } from "@/components/TelemetrySwitch";
|
||||||
|
import { MaxChatTurnsSelector } from "@/components/MaxChatTurnsSelector";
|
||||||
import { useSettings } from "@/hooks/useSettings";
|
import { useSettings } from "@/hooks/useSettings";
|
||||||
import { useAppVersion } from "@/hooks/useAppVersion";
|
import { useAppVersion } from "@/hooks/useAppVersion";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -106,6 +107,10 @@ export default function SettingsPage() {
|
|||||||
This will automatically approve code changes and run them.
|
This will automatically approve code changes and run them.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<MaxChatTurnsSelector />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user