diff --git a/packages/types/src/__tests__/context-management.test.ts b/packages/types/src/__tests__/context-management.test.ts new file mode 100644 index 00000000000..a2e91f8346b --- /dev/null +++ b/packages/types/src/__tests__/context-management.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from "vitest" +import { CONTEXT_MANAGEMENT_EVENTS, isContextManagementEvent } from "../context-management.js" + +describe("context-management", () => { + describe("CONTEXT_MANAGEMENT_EVENTS", () => { + it("should contain all expected event types", () => { + expect(CONTEXT_MANAGEMENT_EVENTS).toContain("condense_context") + expect(CONTEXT_MANAGEMENT_EVENTS).toContain("condense_context_error") + expect(CONTEXT_MANAGEMENT_EVENTS).toContain("sliding_window_truncation") + expect(CONTEXT_MANAGEMENT_EVENTS).toHaveLength(3) + }) + }) + + describe("isContextManagementEvent", () => { + it("should return true for valid context management events", () => { + expect(isContextManagementEvent("condense_context")).toBe(true) + expect(isContextManagementEvent("condense_context_error")).toBe(true) + expect(isContextManagementEvent("sliding_window_truncation")).toBe(true) + }) + + it("should return false for non-context-management events", () => { + expect(isContextManagementEvent("text")).toBe(false) + expect(isContextManagementEvent("error")).toBe(false) + expect(isContextManagementEvent(null)).toBe(false) + expect(isContextManagementEvent(undefined)).toBe(false) + }) + }) +}) diff --git a/packages/types/src/context-management.ts b/packages/types/src/context-management.ts new file mode 100644 index 00000000000..aba06a22db5 --- /dev/null +++ b/packages/types/src/context-management.ts @@ -0,0 +1,34 @@ +/** + * Context Management Types + * + * This module provides type definitions for context management events. + * These events are used to handle different strategies for managing conversation context + * when approaching token limits. + * + * Event Types: + * - `condense_context`: Context was condensed using AI summarization + * - `condense_context_error`: An error occurred during context condensation + * - `sliding_window_truncation`: Context was truncated using sliding window strategy + */ + +/** + * Array of all context management event types. + * Used for runtime type checking. + */ +export const CONTEXT_MANAGEMENT_EVENTS = [ + "condense_context", + "condense_context_error", + "sliding_window_truncation", +] as const + +/** + * Union type representing all possible context management event types. + */ +export type ContextManagementEvent = (typeof CONTEXT_MANAGEMENT_EVENTS)[number] + +/** + * Type guard function to check if a value is a valid context management event. + */ +export function isContextManagementEvent(value: unknown): value is ContextManagementEvent { + return typeof value === "string" && (CONTEXT_MANAGEMENT_EVENTS as readonly string[]).includes(value) +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 32505ede7bc..4ab60899df8 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,6 +1,7 @@ export * from "./api.js" export * from "./cloud.js" export * from "./codebase-index.js" +export * from "./context-management.js" export * from "./cookie-consent.js" export * from "./events.js" export * from "./experiment.js" diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index 9186da62491..82f58f29f28 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -197,8 +197,17 @@ export type ToolProgressStatus = z.infer /** * ContextCondense + * + * Data associated with a successful context condensation event. + * This is attached to messages with `say: "condense_context"` when + * the condensation operation completes successfully. + * + * @property cost - The API cost incurred for the condensation operation + * @property prevContextTokens - Token count before condensation + * @property newContextTokens - Token count after condensation + * @property summary - The condensed summary that replaced the original context + * @property condenseId - Optional unique identifier for this condensation operation */ - export const contextCondenseSchema = z.object({ cost: z.number(), prevContextTokens: z.number(), @@ -212,21 +221,39 @@ export type ContextCondense = z.infer /** * ContextTruncation * - * Used to track sliding window truncation events for the UI. + * Data associated with a sliding window truncation event. + * This is attached to messages with `say: "sliding_window_truncation"` when + * messages are removed from the conversation history to stay within token limits. + * + * Unlike condensation, truncation simply removes older messages without + * summarizing them. This is a faster but less context-preserving approach. + * + * @property truncationId - Unique identifier for this truncation operation + * @property messagesRemoved - Number of conversation messages that were removed + * @property prevContextTokens - Token count before truncation occurred + * @property newContextTokens - Token count after truncation occurred */ - export const contextTruncationSchema = z.object({ truncationId: z.string(), messagesRemoved: z.number(), prevContextTokens: z.number(), + newContextTokens: z.number(), }) export type ContextTruncation = z.infer /** * ClineMessage + * + * The main message type used for communication between the extension and webview. + * Messages can either be "ask" (requiring user response) or "say" (informational). + * + * Context Management Fields: + * - `contextCondense`: Present when `say: "condense_context"` and condensation succeeded + * - `contextTruncation`: Present when `say: "sliding_window_truncation"` and truncation occurred + * + * Note: These fields are mutually exclusive - a message will have at most one of them. */ - export const clineMessageSchema = z.object({ ts: z.number(), type: z.union([z.literal("ask"), z.literal("say")]), @@ -239,7 +266,15 @@ export const clineMessageSchema = z.object({ conversationHistoryIndex: z.number().optional(), checkpoint: z.record(z.string(), z.unknown()).optional(), progressStatus: toolProgressStatusSchema.optional(), + /** + * Data for successful context condensation. + * Present when `say: "condense_context"` and `partial: false`. + */ contextCondense: contextCondenseSchema.optional(), + /** + * Data for sliding window truncation. + * Present when `say: "sliding_window_truncation"`. + */ contextTruncation: contextTruncationSchema.optional(), isProtected: z.boolean().optional(), apiProtocol: z.union([z.literal("openai"), z.literal("anthropic")]).optional(), diff --git a/src/core/context-management/__tests__/context-management.spec.ts b/src/core/context-management/__tests__/context-management.spec.ts index 674cbeb176f..0ed8f94ed05 100644 --- a/src/core/context-management/__tests__/context-management.spec.ts +++ b/src/core/context-management/__tests__/context-management.spec.ts @@ -9,7 +9,13 @@ import { BaseProvider } from "../../../api/providers/base-provider" import { ApiMessage } from "../../task-persistence/apiMessages" import * as condenseModule from "../../condense" -import { TOKEN_BUFFER_PERCENTAGE, estimateTokenCount, truncateConversation, manageContext } from "../index" +import { + TOKEN_BUFFER_PERCENTAGE, + estimateTokenCount, + truncateConversation, + manageContext, + willManageContext, +} from "../index" // Create a mock ApiHandler for testing class MockApiHandler extends BaseProvider { @@ -1280,4 +1286,125 @@ describe("Context Management", () => { expect(result2.truncationId).toBeDefined() }) }) + + /** + * Tests for the willManageContext helper function + */ + describe("willManageContext", () => { + it("should return true when context percent exceeds threshold", () => { + const result = willManageContext({ + totalTokens: 60000, + contextWindow: 100000, // 60% of context window + maxTokens: 30000, + autoCondenseContext: true, + autoCondenseContextPercent: 50, // 50% threshold + profileThresholds: {}, + currentProfileId: "default", + lastMessageTokens: 0, + }) + expect(result).toBe(true) + }) + + it("should return false when context percent is below threshold", () => { + const result = willManageContext({ + totalTokens: 40000, + contextWindow: 100000, // 40% of context window + maxTokens: 30000, + autoCondenseContext: true, + autoCondenseContextPercent: 50, // 50% threshold + profileThresholds: {}, + currentProfileId: "default", + lastMessageTokens: 0, + }) + expect(result).toBe(false) + }) + + it("should return true when tokens exceed allowedTokens even if autoCondenseContext is false", () => { + // allowedTokens = contextWindow * (1 - 0.1) - reservedTokens = 100000 * 0.9 - 30000 = 60000 + const result = willManageContext({ + totalTokens: 60001, // Exceeds allowedTokens + contextWindow: 100000, + maxTokens: 30000, + autoCondenseContext: false, // Even with auto-condense disabled + autoCondenseContextPercent: 50, + profileThresholds: {}, + currentProfileId: "default", + lastMessageTokens: 0, + }) + expect(result).toBe(true) + }) + + it("should return false when autoCondenseContext is false and tokens are below allowedTokens", () => { + // allowedTokens = contextWindow * (1 - 0.1) - reservedTokens = 100000 * 0.9 - 30000 = 60000 + const result = willManageContext({ + totalTokens: 59999, // Below allowedTokens + contextWindow: 100000, + maxTokens: 30000, + autoCondenseContext: false, + autoCondenseContextPercent: 50, // This shouldn't matter since autoCondenseContext is false + profileThresholds: {}, + currentProfileId: "default", + lastMessageTokens: 0, + }) + expect(result).toBe(false) + }) + + it("should use profile-specific threshold when available", () => { + const result = willManageContext({ + totalTokens: 55000, + contextWindow: 100000, // 55% of context window + maxTokens: 30000, + autoCondenseContext: true, + autoCondenseContextPercent: 80, // Global threshold 80% + profileThresholds: { "test-profile": 50 }, // Profile threshold 50% + currentProfileId: "test-profile", + lastMessageTokens: 0, + }) + // Should trigger because 55% > 50% (profile threshold) + expect(result).toBe(true) + }) + + it("should fall back to global threshold when profile threshold is -1", () => { + const result = willManageContext({ + totalTokens: 55000, + contextWindow: 100000, // 55% of context window + maxTokens: 30000, + autoCondenseContext: true, + autoCondenseContextPercent: 80, // Global threshold 80% + profileThresholds: { "test-profile": -1 }, // Profile uses global + currentProfileId: "test-profile", + lastMessageTokens: 0, + }) + // Should NOT trigger because 55% < 80% (global threshold) + expect(result).toBe(false) + }) + + it("should include lastMessageTokens in the calculation", () => { + // Without lastMessageTokens: 49000 tokens = 49% + // With lastMessageTokens: 49000 + 2000 = 51000 tokens = 51% + const resultWithoutLastMessage = willManageContext({ + totalTokens: 49000, + contextWindow: 100000, + maxTokens: 30000, + autoCondenseContext: true, + autoCondenseContextPercent: 50, // 50% threshold + profileThresholds: {}, + currentProfileId: "default", + lastMessageTokens: 0, + }) + expect(resultWithoutLastMessage).toBe(false) + + const resultWithLastMessage = willManageContext({ + totalTokens: 49000, + contextWindow: 100000, + maxTokens: 30000, + autoCondenseContext: true, + autoCondenseContextPercent: 50, // 50% threshold + profileThresholds: {}, + currentProfileId: "default", + lastMessageTokens: 2000, // Pushes total to 51% + }) + expect(resultWithLastMessage).toBe(true) + }) + }) }) diff --git a/src/core/context-management/index.ts b/src/core/context-management/index.ts index ff92cb3ca56..993c69a3657 100644 --- a/src/core/context-management/index.ts +++ b/src/core/context-management/index.ts @@ -133,6 +133,68 @@ export function truncateConversation(messages: ApiMessage[], fracToRemove: numbe } } +/** + * Options for checking if context management will likely run. + * A subset of ContextManagementOptions with only the fields needed for threshold calculation. + */ +export type WillManageContextOptions = { + totalTokens: number + contextWindow: number + maxTokens?: number | null + autoCondenseContext: boolean + autoCondenseContextPercent: number + profileThresholds: Record + currentProfileId: string + lastMessageTokens: number +} + +/** + * Checks whether context management (condensation or truncation) will likely run based on current token usage. + * + * This is useful for showing UI indicators before `manageContext` is actually called, + * without duplicating the threshold calculation logic. + * + * @param {WillManageContextOptions} options - The options for threshold calculation + * @returns {boolean} True if context management will likely run, false otherwise + */ +export function willManageContext({ + totalTokens, + contextWindow, + maxTokens, + autoCondenseContext, + autoCondenseContextPercent, + profileThresholds, + currentProfileId, + lastMessageTokens, +}: WillManageContextOptions): boolean { + if (!autoCondenseContext) { + // When auto-condense is disabled, only truncation can occur + const reservedTokens = maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS + const prevContextTokens = totalTokens + lastMessageTokens + const allowedTokens = contextWindow * (1 - TOKEN_BUFFER_PERCENTAGE) - reservedTokens + return prevContextTokens > allowedTokens + } + + const reservedTokens = maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS + const prevContextTokens = totalTokens + lastMessageTokens + const allowedTokens = contextWindow * (1 - TOKEN_BUFFER_PERCENTAGE) - reservedTokens + + // Determine the effective threshold to use + let effectiveThreshold = autoCondenseContextPercent + const profileThreshold = profileThresholds[currentProfileId] + if (profileThreshold !== undefined) { + if (profileThreshold === -1) { + effectiveThreshold = autoCondenseContextPercent + } else if (profileThreshold >= MIN_CONDENSE_THRESHOLD && profileThreshold <= MAX_CONDENSE_THRESHOLD) { + effectiveThreshold = profileThreshold + } + // Invalid values fall back to global setting (effectiveThreshold already set) + } + + const contextPercent = (100 * prevContextTokens) / contextWindow + return contextPercent >= effectiveThreshold || prevContextTokens > allowedTokens +} + /** * Context Management: Conditionally manages the conversation context when approaching limits. * @@ -164,6 +226,7 @@ export type ContextManagementResult = SummarizeResponse & { prevContextTokens: number truncationId?: string messagesRemoved?: number + newContextTokensAfterTruncation?: number } /** @@ -254,6 +317,25 @@ export async function manageContext({ // Fall back to sliding window truncation if needed if (prevContextTokens > allowedTokens) { const truncationResult = truncateConversation(messages, 0.5, taskId) + + // Calculate new context tokens after truncation by counting non-truncated messages + // Messages with truncationParent are hidden, so we count only those without it + const effectiveMessages = truncationResult.messages.filter( + (msg) => !msg.truncationParent && !msg.isTruncationMarker, + ) + let newContextTokensAfterTruncation = 0 + for (const msg of effectiveMessages) { + const content = msg.content + if (Array.isArray(content)) { + newContextTokensAfterTruncation += await estimateTokenCount(content, apiHandler) + } else if (typeof content === "string") { + newContextTokensAfterTruncation += await estimateTokenCount( + [{ type: "text", text: content }], + apiHandler, + ) + } + } + return { messages: truncationResult.messages, prevContextTokens, @@ -262,6 +344,7 @@ export async function manageContext({ error, truncationId: truncationResult.truncationId, messagesRemoved: truncationResult.messagesRemoved, + newContextTokensAfterTruncation, } } // No truncation or condensation needed diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index fc3184d8255..d084bf4b924 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -99,7 +99,7 @@ import { RooProtectedController } from "../protect/RooProtectedController" import { type AssistantMessageContent, presentAssistantMessage } from "../assistant-message" import { AssistantMessageParser } from "../assistant-message/AssistantMessageParser" import { NativeToolCallParser } from "../assistant-message/NativeToolCallParser" -import { manageContext } from "../context-management" +import { manageContext, willManageContext } from "../context-management" import { ClineProvider } from "../webview/ClineProvider" import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" import { MultiFileSearchReplaceDiffStrategy } from "../diff/strategies/multi-file-search-replace" @@ -3366,6 +3366,9 @@ export class Task extends EventEmitter implements TaskLike { const protocol = resolveToolProtocol(this.apiConfiguration, modelInfo) const useNativeTools = isNativeProtocol(protocol) + // Send condenseTaskContextStarted to show in-progress indicator + await this.providerRef.deref()?.postMessageToWebview({ type: "condenseTaskContextStarted", text: this.taskId }) + // Force aggressive truncation by keeping only 75% of the conversation history const truncateResult = await manageContext({ messages: this.apiConversationHistory, @@ -3405,6 +3408,7 @@ export class Task extends EventEmitter implements TaskLike { truncationId: truncateResult.truncationId, messagesRemoved: truncateResult.messagesRemoved ?? 0, prevContextTokens: truncateResult.prevContextTokens, + newContextTokens: truncateResult.newContextTokensAfterTruncation ?? 0, } await this.say( "sliding_window_truncation", @@ -3418,6 +3422,9 @@ export class Task extends EventEmitter implements TaskLike { contextTruncation, ) } + + // Notify webview that context management is complete (removes in-progress spinner) + await this.providerRef.deref()?.postMessageToWebview({ type: "condenseTaskContextResponse", text: this.taskId }) } public async *attemptApiRequest(retryAttempt: number = 0): ApiStream { @@ -3505,6 +3512,38 @@ export class Task extends EventEmitter implements TaskLike { const protocol = resolveToolProtocol(this.apiConfiguration, modelInfoForProtocol) const useNativeTools = isNativeProtocol(protocol) + // Check if context management will likely run (threshold check) + // This allows us to show an in-progress indicator to the user + // We use the centralized willManageContext helper to avoid duplicating threshold logic + const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1] + const lastMessageContent = lastMessage?.content + let lastMessageTokens = 0 + if (lastMessageContent) { + lastMessageTokens = Array.isArray(lastMessageContent) + ? await this.api.countTokens(lastMessageContent) + : await this.api.countTokens([{ type: "text", text: lastMessageContent as string }]) + } + + const contextManagementWillRun = willManageContext({ + totalTokens: contextTokens, + contextWindow, + maxTokens, + autoCondenseContext, + autoCondenseContextPercent, + profileThresholds, + currentProfileId, + lastMessageTokens, + }) + + // Send condenseTaskContextStarted BEFORE manageContext to show in-progress indicator + // This notification must be sent here (not earlier) because the early check uses stale token count + // (before user message is added to history), which could incorrectly skip showing the indicator + if (contextManagementWillRun && autoCondenseContext) { + await this.providerRef + .deref() + ?.postMessageToWebview({ type: "condenseTaskContextStarted", text: this.taskId }) + } + const truncateResult = await manageContext({ messages: this.apiConversationHistory, totalTokens: contextTokens, @@ -3551,6 +3590,7 @@ export class Task extends EventEmitter implements TaskLike { truncationId: truncateResult.truncationId, messagesRemoved: truncateResult.messagesRemoved ?? 0, prevContextTokens: truncateResult.prevContextTokens, + newContextTokens: truncateResult.newContextTokensAfterTruncation ?? 0, } await this.say( "sliding_window_truncation", @@ -3564,6 +3604,14 @@ export class Task extends EventEmitter implements TaskLike { contextTruncation, ) } + + // Notify webview that context management is complete (sets isCondensing = false) + // This removes the in-progress spinner and allows the completed result to show + if (contextManagementWillRun && autoCondenseContext) { + await this.providerRef + .deref() + ?.postMessageToWebview({ type: "condenseTaskContextResponse", text: this.taskId }) + } } // Get the effective API history by filtering out condensed messages diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 0d50f0ed487..00b4d6c42d3 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -110,6 +110,7 @@ export interface ExtensionMessage { | "mcpExecutionStatus" | "vsCodeSetting" | "authenticatedUser" + | "condenseTaskContextStarted" | "condenseTaskContextResponse" | "singleRouterModelFetchResponse" | "rooCreditBalance" diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 98544f1b4be..5379a4fe5a2 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -38,7 +38,7 @@ import { Markdown } from "./Markdown" import { CommandExecution } from "./CommandExecution" import { CommandExecutionError } from "./CommandExecutionError" import { AutoApprovedRequestLimitWarning } from "./AutoApprovedRequestLimitWarning" -import { CondensingContextRow, ContextCondenseRow } from "./ContextCondenseRow" +import { InProgressRow, CondensationResultRow, CondensationErrorRow, TruncationResultRow } from "./context-management" import CodebaseSearchResultsDisplay from "./CodebaseSearchResultsDisplay" import { appendImages } from "@src/utils/imageUtils" import { McpExecution } from "./McpExecution" @@ -1280,18 +1280,27 @@ export const ChatRowContent = ({ /> ) case "condense_context": + // In-progress state if (message.partial) { - return + return } - return message.contextCondense ? : null + // Completed state + if (message.contextCondense) { + return + } + return null case "condense_context_error": - return ( - - ) + return + case "sliding_window_truncation": + // In-progress state + if (message.partial) { + return + } + // Completed state + if (message.contextTruncation) { + return + } + return null case "codebase_search_result": let parsed: { content: { diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 9a94d86ea3a..6ee163fe41b 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -826,8 +826,21 @@ const ChatViewComponent: React.ForwardRefRenderFunction ({ default: () => null, })) +// Mock react-virtuoso to render items directly without virtualization +// This allows tests to verify items rendered in the chat list +vi.mock("react-virtuoso", () => ({ + Virtuoso: function MockVirtuoso({ + data, + itemContent, + }: { + data: ClineMessage[] + itemContent: (index: number, item: ClineMessage) => React.ReactNode + }) { + return ( +
+ {data.map((item, index) => ( +
+ {itemContent(index, item)} +
+ ))} +
+ ) + }, +})) + // Mock VersionIndicator - returns null by default to prevent rendering in tests vi.mock("../../common/VersionIndicator", () => ({ default: vi.fn(() => null), @@ -468,6 +490,11 @@ describe("ChatView - Focus Grabbing Tests", () => { expect(getByTestId("chat-textarea")).toBeInTheDocument() }) + // Wait for the debounced focus effect to fire (50ms debounce + buffer for CI variability) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)) + }) + // Clear any initial calls after state has settled mockFocus.mockClear() @@ -1055,3 +1082,73 @@ describe("ChatView - Message Queueing Tests", () => { ) }) }) + +describe("ChatView - Context Condensing Indicator Tests", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("should add a condensing message to groupedMessages when isCondensing is true", async () => { + // This test verifies that when the condenseTaskContextStarted message is received, + // the isCondensing state is set to true and a synthetic condensing message is added + // to the grouped messages list + const { getByTestId, container } = renderChatView() + + // First hydrate state with an active task + mockPostMessage({ + clineMessages: [ + { + type: "say", + say: "task", + ts: Date.now() - 2000, + text: "Initial task", + }, + { + type: "say", + say: "api_req_started", + ts: Date.now() - 1000, + text: JSON.stringify({ apiProtocol: "anthropic" }), + }, + ], + }) + + // Wait for component to render + await waitFor(() => { + expect(getByTestId("chat-view")).toBeInTheDocument() + }) + + // Allow time for useEvent hook to register message listener + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 10)) + }) + + // Dispatch a MessageEvent directly to trigger the message handler + // This simulates the VSCode extension sending a message to the webview + await act(async () => { + const event = new MessageEvent("message", { + data: { + type: "condenseTaskContextStarted", + text: "test-task-id", + }, + }) + window.dispatchEvent(event) + // Wait for React state updates + await new Promise((resolve) => setTimeout(resolve, 0)) + }) + + // Check that groupedMessages now includes a condensing message + // With Virtuoso mocked, items render directly and we can find the ChatRow with partial condense_context message + await waitFor( + () => { + const rows = container.querySelectorAll('[data-testid="chat-row"]') + // Check for the actual message structure: partial condense_context message + const condensingRow = Array.from(rows).find((row) => { + const text = row.textContent || "" + return text.includes('"say":"condense_context"') && text.includes('"partial":true') + }) + expect(condensingRow).toBeTruthy() + }, + { timeout: 2000 }, + ) + }) +}) diff --git a/webview-ui/src/components/chat/context-management/CondensationErrorRow.tsx b/webview-ui/src/components/chat/context-management/CondensationErrorRow.tsx new file mode 100644 index 00000000000..62d2d8fd101 --- /dev/null +++ b/webview-ui/src/components/chat/context-management/CondensationErrorRow.tsx @@ -0,0 +1,25 @@ +import { useTranslation } from "react-i18next" + +interface CondensationErrorRowProps { + errorText?: string +} + +/** + * Displays an error message when context condensation fails. + * Shows a warning icon with the error header and optional error details. + */ +export function CondensationErrorRow({ errorText }: CondensationErrorRowProps) { + const { t } = useTranslation() + + return ( +
+
+ + + {t("chat:contextManagement.condensation.errorHeader")} + +
+ {errorText && {errorText}} +
+ ) +} diff --git a/webview-ui/src/components/chat/ContextCondenseRow.tsx b/webview-ui/src/components/chat/context-management/CondensationResultRow.tsx similarity index 54% rename from webview-ui/src/components/chat/ContextCondenseRow.tsx rename to webview-ui/src/components/chat/context-management/CondensationResultRow.tsx index c495927215e..99343acdaa6 100644 --- a/webview-ui/src/components/chat/ContextCondenseRow.tsx +++ b/webview-ui/src/components/chat/context-management/CondensationResultRow.tsx @@ -1,16 +1,26 @@ import { useState } from "react" import { useTranslation } from "react-i18next" import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react" +import { FoldVertical } from "lucide-react" import type { ContextCondense } from "@roo-code/types" -import { Markdown } from "./Markdown" -import { ProgressIndicator } from "./ProgressIndicator" +import { Markdown } from "../Markdown" -export const ContextCondenseRow = ({ cost, prevContextTokens, newContextTokens, summary }: ContextCondense) => { +interface CondensationResultRowProps { + data: ContextCondense +} + +/** + * Displays the result of a successful context condensation operation. + * Shows token reduction, cost, and an expandable summary section. + */ +export function CondensationResultRow({ data }: CondensationResultRowProps) { const { t } = useTranslation() const [isExpanded, setIsExpanded] = useState(false) + const { cost, prevContextTokens, newContextTokens, summary } = data + // Handle null/undefined token values to prevent crashes const prevTokens = prevContextTokens ?? 0 const newTokens = newContextTokens ?? 0 @@ -21,24 +31,14 @@ export const ContextCondenseRow = ({ cost, prevContextTokens, newContextTokens,
setIsExpanded(!isExpanded)}> -
- -
- - {t("chat:contextCondense.title")} + + + {t("chat:contextManagement.condensation.title")} + - {prevTokens.toLocaleString()} → {newTokens.toLocaleString()} {t("tokens")} + {prevTokens.toLocaleString()} → {newTokens.toLocaleString()}{" "} + {t("chat:contextManagement.tokens")} 0 ? "opacity-100" : "opacity-0"}> ${displayCost.toFixed(2)} @@ -55,14 +55,3 @@ export const ContextCondenseRow = ({ cost, prevContextTokens, newContextTokens,
) } - -export const CondensingContextRow = () => { - const { t } = useTranslation() - return ( -
- - - {t("chat:contextCondense.condensing")} -
- ) -} diff --git a/webview-ui/src/components/chat/context-management/InProgressRow.tsx b/webview-ui/src/components/chat/context-management/InProgressRow.tsx new file mode 100644 index 00000000000..fbe260d9b62 --- /dev/null +++ b/webview-ui/src/components/chat/context-management/InProgressRow.tsx @@ -0,0 +1,27 @@ +import { useTranslation } from "react-i18next" + +import { ProgressIndicator } from "../ProgressIndicator" + +interface InProgressRowProps { + eventType: "condense_context" | "sliding_window_truncation" +} + +/** + * Displays an in-progress indicator for context management operations. + * Shows a spinner with operation-specific text based on the event type. + */ +export function InProgressRow({ eventType }: InProgressRowProps) { + const { t } = useTranslation() + + const progressText = + eventType === "condense_context" + ? t("chat:contextManagement.condensation.inProgress") + : t("chat:contextManagement.truncation.inProgress") + + return ( +
+ + {progressText} +
+ ) +} diff --git a/webview-ui/src/components/chat/context-management/TruncationResultRow.tsx b/webview-ui/src/components/chat/context-management/TruncationResultRow.tsx new file mode 100644 index 00000000000..f31a6752c1b --- /dev/null +++ b/webview-ui/src/components/chat/context-management/TruncationResultRow.tsx @@ -0,0 +1,64 @@ +import { useState } from "react" +import { useTranslation } from "react-i18next" +import { FoldVertical } from "lucide-react" + +import type { ContextTruncation } from "@roo-code/types" + +interface TruncationResultRowProps { + data: ContextTruncation +} + +/** + * Displays the result of a sliding window truncation operation. + * Shows information about how many messages were removed and the + * token count before and after truncation. + * + * This component provides visual feedback for truncation events which + * were previously silent, addressing a gap in the context management UI. + */ +export function TruncationResultRow({ data }: TruncationResultRowProps) { + const { t } = useTranslation() + const [isExpanded, setIsExpanded] = useState(false) + + const { messagesRemoved, prevContextTokens, newContextTokens } = data + + // Handle null/undefined values to prevent crashes + const removedCount = messagesRemoved ?? 0 + const prevTokens = prevContextTokens ?? 0 + const newTokens = newContextTokens ?? 0 + + return ( +
+
setIsExpanded(!isExpanded)}> +
+ + + {t("chat:contextManagement.truncation.title")} + + + {prevTokens.toLocaleString()} → {newTokens.toLocaleString()}{" "} + {t("chat:contextManagement.tokens")} + +
+ +
+ + {isExpanded && ( +
+
+
+ + {t("chat:contextManagement.truncation.messagesRemoved", { count: removedCount })} + +
+

+ {t("chat:contextManagement.truncation.description")} +

+
+
+ )} +
+ ) +} diff --git a/webview-ui/src/components/chat/context-management/index.ts b/webview-ui/src/components/chat/context-management/index.ts new file mode 100644 index 00000000000..4150a58d97f --- /dev/null +++ b/webview-ui/src/components/chat/context-management/index.ts @@ -0,0 +1,13 @@ +/** + * Context Management UI Components + * + * Components for displaying context management events in the ChatView: + * - Context Condensation: AI-powered summarization to reduce token usage + * - Context Truncation: Sliding window removal of older messages + * - Error States: When context management operations fail + */ + +export { InProgressRow } from "./InProgressRow" +export { CondensationResultRow } from "./CondensationResultRow" +export { CondensationErrorRow } from "./CondensationErrorRow" +export { TruncationResultRow } from "./TruncationResultRow" diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index efb9403ff84..f640833d592 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -283,11 +283,20 @@ "thinking": "Pensant", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Context condensat", - "condensing": "Condensant context...", - "errorHeader": "Error en condensar el context", - "tokens": "tokens" + "contextManagement": { + "tokens": "tokens", + "condensation": { + "title": "Context condensat", + "inProgress": "Condensant context...", + "errorHeader": "Error en condensar el context" + }, + "truncation": { + "title": "Context truncat", + "inProgress": "Truncant context...", + "messagesRemoved": "{{count}} missatge eliminat", + "messagesRemoved_other": "{{count}} missatges eliminats", + "description": "S'han eliminat missatges més antics de la conversa per mantenir-se dins del límit de la finestra de context. Aquest és un enfocament ràpid però menys conservador del context en comparació amb la condensació." + } }, "followUpSuggest": { "copyToInput": "Copiar a l'entrada (o Shift + clic)", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index f93eaa450a9..f496d833396 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -283,11 +283,20 @@ "thinking": "Denke nach", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Kontext komprimiert", - "condensing": "Kontext wird komprimiert...", - "errorHeader": "Kontext konnte nicht komprimiert werden", - "tokens": "Tokens" + "contextManagement": { + "tokens": "Tokens", + "condensation": { + "title": "Kontext komprimiert", + "inProgress": "Kontext wird komprimiert...", + "errorHeader": "Kontext konnte nicht komprimiert werden" + }, + "truncation": { + "title": "Kontext gekürzt", + "inProgress": "Kontext wird gekürzt...", + "messagesRemoved": "{{count}} Nachricht entfernt", + "messagesRemoved_other": "{{count}} Nachrichten entfernt", + "description": "Ältere Nachrichten wurden aus der Konversation entfernt, um innerhalb des Kontextfenster-Limits zu bleiben. Dies ist ein schnellerer, aber weniger kontexterhaltender Ansatz im Vergleich zur Komprimierung." + } }, "followUpSuggest": { "copyToInput": "In Eingabefeld kopieren (oder Shift + Klick)", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 67fb7f2a51a..b3100fbb991 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -174,11 +174,20 @@ }, "current": "Current" }, - "contextCondense": { - "title": "Context Condensed", - "condensing": "Condensing context...", - "errorHeader": "Failed to condense context", - "tokens": "tokens" + "contextManagement": { + "tokens": "tokens", + "condensation": { + "title": "Context Condensed", + "inProgress": "Condensing context...", + "errorHeader": "Failed to condense context" + }, + "truncation": { + "title": "Context Truncated", + "inProgress": "Truncating context...", + "messagesRemoved": "{{count}} message removed", + "messagesRemoved_other": "{{count}} messages removed", + "description": "Older messages were removed from the conversation to stay within the context window limit. This is a fast but less context-preserving approach compared to condensation." + } }, "instructions": { "wantsToFetch": "Roo wants to fetch detailed instructions to assist with the current task" diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index f6714aa1561..a501fe22c0c 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -283,11 +283,20 @@ "thinking": "Pensando", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Contexto condensado", - "condensing": "Condensando contexto...", - "errorHeader": "Error al condensar el contexto", - "tokens": "tokens" + "contextManagement": { + "tokens": "tokens", + "condensation": { + "title": "Contexto condensado", + "inProgress": "Condensando contexto...", + "errorHeader": "Error al condensar el contexto" + }, + "truncation": { + "title": "Contexto truncado", + "inProgress": "Truncando contexto...", + "messagesRemoved": "{{count}} mensaje eliminado", + "messagesRemoved_other": "{{count}} mensajes eliminados", + "description": "Se eliminaron mensajes más antiguos de la conversación para mantenerse dentro del límite de la ventana de contexto. Este es un enfoque rápido pero menos conservador del contexto en comparación con la condensación." + } }, "followUpSuggest": { "copyToInput": "Copiar a la entrada (o Shift + clic)", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index caac3aa2a01..8b98b42777b 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -283,11 +283,20 @@ "thinking": "Réflexion", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Contexte condensé", - "condensing": "Condensation du contexte...", - "errorHeader": "Échec de la condensation du contexte", - "tokens": "tokens" + "contextManagement": { + "tokens": "tokens", + "condensation": { + "title": "Contexte condensé", + "inProgress": "Condensation du contexte...", + "errorHeader": "Échec de la condensation du contexte" + }, + "truncation": { + "title": "Contexte tronqué", + "inProgress": "Troncature du contexte...", + "messagesRemoved": "{{count}} message supprimé", + "messagesRemoved_other": "{{count}} messages supprimés", + "description": "Les messages plus anciens ont été supprimés de la conversation pour rester dans la limite de la fenêtre de contexte. C'est une approche rapide mais moins conservatrice du contexte par rapport à la condensation." + } }, "followUpSuggest": { "copyToInput": "Copier vers l'entrée (ou Shift + clic)", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 52401e451f6..6496077bc94 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -283,11 +283,20 @@ "thinking": "विचार कर रहा है", "seconds": "{{count}} सेकंड" }, - "contextCondense": { - "title": "संदर्भ संक्षिप्त किया गया", - "condensing": "संदर्भ संघनित कर रहा है...", - "errorHeader": "संदर्भ संघनित करने में विफल", - "tokens": "टोकन" + "contextManagement": { + "tokens": "टोकन", + "condensation": { + "title": "संदर्भ संक्षिप्त किया गया", + "inProgress": "संदर्भ संघनित कर रहा है...", + "errorHeader": "संदर्भ संघनित करने में विफल" + }, + "truncation": { + "title": "संदर्भ छोटा किया गया", + "inProgress": "संदर्भ छोटा कर रहा है...", + "messagesRemoved": "{{count}} संदेश हटाया गया", + "messagesRemoved_other": "{{count}} संदेश हटाए गए", + "description": "संदर्भ विंडो सीमा के भीतर रहने के लिए बातचीत से पुराने संदेश हटा दिए गए। संघनन की तुलना में यह एक तेज़ लेकिन कम संदर्भ-संरक्षित दृष्टिकोण है।" + } }, "followUpSuggest": { "copyToInput": "इनपुट में कॉपी करें (या Shift + क्लिक)", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 48908844dd4..4e27affd807 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -177,11 +177,20 @@ }, "current": "Saat Ini" }, - "contextCondense": { - "title": "Konteks Dikondensasi", - "condensing": "Mengondensasi konteks...", - "errorHeader": "Gagal mengondensasi konteks", - "tokens": "token" + "contextManagement": { + "tokens": "token", + "condensation": { + "title": "Konteks Dikondensasi", + "inProgress": "Mengondensasi konteks...", + "errorHeader": "Gagal mengondensasi konteks" + }, + "truncation": { + "title": "Konteks Dipotong", + "inProgress": "Memotong konteks...", + "messagesRemoved": "{{count}} pesan dihapus", + "messagesRemoved_other": "{{count}} pesan dihapus", + "description": "Pesan lama telah dihapus dari percakapan untuk tetap dalam batas jendela konteks. Ini adalah pendekatan yang cepat tetapi kurang mempertahankan konteks dibandingkan dengan kondensasi." + } }, "instructions": { "wantsToFetch": "Roo ingin mengambil instruksi detail untuk membantu tugas saat ini" diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 3dc734abded..6710d998fda 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -283,11 +283,20 @@ "thinking": "Sto pensando", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Contesto condensato", - "condensing": "Condensazione del contesto...", - "errorHeader": "Impossibile condensare il contesto", - "tokens": "token" + "contextManagement": { + "tokens": "token", + "condensation": { + "title": "Contesto condensato", + "inProgress": "Condensazione del contesto...", + "errorHeader": "Impossibile condensare il contesto" + }, + "truncation": { + "title": "Contesto troncato", + "inProgress": "Troncamento del contesto...", + "messagesRemoved": "{{count}} messaggio rimosso", + "messagesRemoved_other": "{{count}} messaggi rimossi", + "description": "I messaggi più vecchi sono stati rimossi dalla conversazione per rimanere entro il limite della finestra di contesto. Questo è un approccio veloce ma meno conservativo del contesto rispetto alla condensazione." + } }, "followUpSuggest": { "copyToInput": "Copia nell'input (o Shift + clic)", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index b6c0ed865d1..427bf1f00e6 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -283,11 +283,20 @@ "thinking": "考え中", "seconds": "{{count}}秒" }, - "contextCondense": { - "title": "コンテキスト要約", - "condensing": "コンテキストを圧縮中...", - "errorHeader": "コンテキストの圧縮に失敗しました", - "tokens": "トークン" + "contextManagement": { + "tokens": "トークン", + "condensation": { + "title": "コンテキスト要約", + "inProgress": "コンテキストを圧縮中...", + "errorHeader": "コンテキストの圧縮に失敗しました" + }, + "truncation": { + "title": "コンテキスト切り詰め", + "inProgress": "コンテキストを切り詰め中...", + "messagesRemoved": "{{count}}件のメッセージを削除", + "messagesRemoved_other": "{{count}}件のメッセージを削除", + "description": "コンテキストウィンドウの制限内に収めるため、古いメッセージが会話から削除されました。これは圧縮と比較して高速ですが、コンテキストの保持性が低いアプローチです。" + } }, "followUpSuggest": { "copyToInput": "入力欄にコピー(またはShift + クリック)", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 40b93e8a63d..9d5cbcbbc3d 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -283,11 +283,20 @@ "thinking": "생각 중", "seconds": "{{count}}초" }, - "contextCondense": { - "title": "컨텍스트 요약됨", - "condensing": "컨텍스트 압축 중...", - "errorHeader": "컨텍스트 압축 실패", - "tokens": "토큰" + "contextManagement": { + "tokens": "토큰", + "condensation": { + "title": "컨텍스트 요약됨", + "inProgress": "컨텍스트 압축 중...", + "errorHeader": "컨텍스트 압축 실패" + }, + "truncation": { + "title": "컨텍스트 잘림", + "inProgress": "컨텍스트 자르는 중...", + "messagesRemoved": "{{count}}개 메시지 제거됨", + "messagesRemoved_other": "{{count}}개 메시지 제거됨", + "description": "컨텍스트 윈도우 제한 내에 유지하기 위해 대화에서 오래된 메시지가 제거되었습니다. 이것은 압축에 비해 빠르지만 컨텍스트 보존 능력이 낮은 접근 방식입니다." + } }, "followUpSuggest": { "copyToInput": "입력창에 복사 (또는 Shift + 클릭)", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index f56910afabd..599af9f6337 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -306,11 +306,20 @@ "thinking": "Denkt na", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Context samengevat", - "condensing": "Context aan het samenvatten...", - "errorHeader": "Context samenvatten mislukt", - "tokens": "tokens" + "contextManagement": { + "tokens": "tokens", + "condensation": { + "title": "Context samengevat", + "inProgress": "Context aan het samenvatten...", + "errorHeader": "Context samenvatten mislukt" + }, + "truncation": { + "title": "Context ingekort", + "inProgress": "Context aan het inkorten...", + "messagesRemoved": "{{count}} bericht verwijderd", + "messagesRemoved_other": "{{count}} berichten verwijderd", + "description": "Oudere berichten zijn uit het gesprek verwijderd om binnen de limiet van het contextvenster te blijven. Dit is een snelle maar minder contextbehoudende aanpak in vergelijking met samenvoeging." + } }, "followUpSuggest": { "copyToInput": "Kopiëren naar invoer (zelfde als shift + klik)", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index f28ad6e57e4..9becca000c3 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -283,11 +283,20 @@ "thinking": "Myślenie", "seconds": "{{count}} s" }, - "contextCondense": { - "title": "Kontekst skondensowany", - "condensing": "Kondensowanie kontekstu...", - "errorHeader": "Nie udało się skondensować kontekstu", - "tokens": "tokeny" + "contextManagement": { + "tokens": "tokeny", + "condensation": { + "title": "Kontekst skondensowany", + "inProgress": "Kondensowanie kontekstu...", + "errorHeader": "Nie udało się skondensować kontekstu" + }, + "truncation": { + "title": "Kontekst obcięty", + "inProgress": "Obcinanie kontekstu...", + "messagesRemoved": "{{count}} wiadomość usunięta", + "messagesRemoved_other": "{{count}} wiadomości usunięte", + "description": "Starsze wiadomości zostały usunięte z konwersacji, aby pozostać w granicach okna kontekstu. To szybsze, ale mniej zachowujące kontekst podejście w porównaniu z kondensacją." + } }, "followUpSuggest": { "copyToInput": "Kopiuj do pola wprowadzania (lub Shift + kliknięcie)", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 7cea9df6f70..7144da7f9df 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -283,11 +283,20 @@ "thinking": "Pensando", "seconds": "{{count}}s" }, - "contextCondense": { - "title": "Contexto condensado", - "condensing": "Condensando contexto...", - "errorHeader": "Falha ao condensar contexto", - "tokens": "tokens" + "contextManagement": { + "tokens": "tokens", + "condensation": { + "title": "Contexto condensado", + "inProgress": "Condensando contexto...", + "errorHeader": "Falha ao condensar contexto" + }, + "truncation": { + "title": "Contexto truncado", + "inProgress": "Truncando contexto...", + "messagesRemoved": "{{count}} mensagem removida", + "messagesRemoved_other": "{{count}} mensagens removidas", + "description": "Mensagens mais antigas foram removidas da conversa para permanecer dentro do limite da janela de contexto. Esta é uma abordagem rápida, mas menos preservadora de contexto em comparação com a condensação." + } }, "followUpSuggest": { "copyToInput": "Copiar para entrada (ou Shift + clique)", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 21d7e32c289..f44cbf487dc 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -307,11 +307,20 @@ "thinking": "Обдумывание", "seconds": "{{count}}с" }, - "contextCondense": { - "title": "Контекст сжат", - "condensing": "Сжатие контекста...", - "errorHeader": "Не удалось сжать контекст", - "tokens": "токены" + "contextManagement": { + "tokens": "токены", + "condensation": { + "title": "Контекст сжат", + "inProgress": "Сжатие контекста...", + "errorHeader": "Не удалось сжать контекст" + }, + "truncation": { + "title": "Контекст усечён", + "inProgress": "Усечение контекста...", + "messagesRemoved": "{{count}} сообщение удалено", + "messagesRemoved_other": "{{count}} сообщений удалено", + "description": "Более старые сообщения были удалены из разговора, чтобы остаться в пределах контекстного окна. Это быстрый, но менее сохраняющий контекст подход по сравнению со сжатием." + } }, "followUpSuggest": { "copyToInput": "Скопировать во ввод (то же, что shift + клик)", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 23d102a9619..bf1a16d6b35 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -284,11 +284,20 @@ "thinking": "Düşünüyor", "seconds": "{{count}}sn" }, - "contextCondense": { - "title": "Bağlam Özetlendi", - "condensing": "Bağlam yoğunlaştırılıyor...", - "errorHeader": "Bağlam yoğunlaştırılamadı", - "tokens": "token" + "contextManagement": { + "tokens": "token", + "condensation": { + "title": "Bağlam Özetlendi", + "inProgress": "Bağlam yoğunlaştırılıyor...", + "errorHeader": "Bağlam yoğunlaştırılamadı" + }, + "truncation": { + "title": "Bağlam Kısaltıldı", + "inProgress": "Bağlam kısaltılıyor...", + "messagesRemoved": "{{count}} mesaj kaldırıldı", + "messagesRemoved_other": "{{count}} mesaj kaldırıldı", + "description": "Bağlam penceresi sınırında kalmak için eski mesajlar konuşmadan kaldırıldı. Bu, yoğunlaştırmaya kıyasla hızlı ancak daha az bağlam koruyucu bir yaklaşımdır." + } }, "followUpSuggest": { "copyToInput": "Giriş alanına kopyala (veya Shift + tıklama)", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 637eba5d8e7..ec276454bdb 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -284,11 +284,20 @@ "thinking": "Đang suy nghĩ", "seconds": "{{count}} giây" }, - "contextCondense": { - "title": "Ngữ cảnh đã tóm tắt", - "condensing": "Đang cô đọng ngữ cảnh...", - "errorHeader": "Không thể cô đọng ngữ cảnh", - "tokens": "token" + "contextManagement": { + "tokens": "token", + "condensation": { + "title": "Ngữ cảnh đã tóm tắt", + "inProgress": "Đang cô đọng ngữ cảnh...", + "errorHeader": "Không thể cô đọng ngữ cảnh" + }, + "truncation": { + "title": "Ngữ cảnh đã cắt bớt", + "inProgress": "Đang cắt bớt ngữ cảnh...", + "messagesRemoved": "{{count}} tin nhắn đã xóa", + "messagesRemoved_other": "{{count}} tin nhắn đã xóa", + "description": "Các tin nhắn cũ hơn đã bị xóa khỏi cuộc trò chuyện để giữ trong giới hạn cửa sổ ngữ cảnh. Đây là cách tiếp cận nhanh nhưng ít bảo toàn ngữ cảnh hơn so với cô đọng." + } }, "followUpSuggest": { "copyToInput": "Sao chép vào ô nhập liệu (hoặc Shift + nhấp chuột)", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 41e4ab52fba..1eb05152ba3 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -284,11 +284,20 @@ "thinking": "思考中", "seconds": "{{count}}秒" }, - "contextCondense": { - "title": "上下文已压缩", - "condensing": "正在压缩上下文...", - "errorHeader": "上下文压缩失败", - "tokens": "tokens" + "contextManagement": { + "tokens": "Token", + "condensation": { + "title": "上下文已压缩", + "inProgress": "正在压缩上下文...", + "errorHeader": "上下文压缩失败" + }, + "truncation": { + "title": "上下文已截断", + "inProgress": "正在截断上下文...", + "messagesRemoved": "已移除 {{count}} 条消息", + "messagesRemoved_other": "已移除 {{count}} 条消息", + "description": "为保持在上下文窗口限制内,已从对话中移除较旧的消息。与压缩相比,这是一种快速但上下文保留较少的方法。" + } }, "followUpSuggest": { "copyToInput": "复制到输入框(或按住Shift点击)", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index fe9cb951aff..5715db26ed4 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -174,11 +174,20 @@ }, "current": "目前" }, - "contextCondense": { - "title": "上下文已壓縮", - "condensing": "正在壓縮上下文...", - "errorHeader": "壓縮上下文失敗", - "tokens": "Token" + "contextManagement": { + "tokens": "Token", + "condensation": { + "title": "上下文已壓縮", + "inProgress": "正在壓縮上下文...", + "errorHeader": "壓縮上下文失敗" + }, + "truncation": { + "title": "上下文已截斷", + "inProgress": "正在截斷上下文...", + "messagesRemoved": "已移除 {{count}} 則訊息", + "messagesRemoved_other": "已移除 {{count}} 則訊息", + "description": "為保持在上下文視窗限制內,已從對話中移除較舊的訊息。與壓縮相比,這是一種快速但上下文保留較少的方法。" + } }, "instructions": { "wantsToFetch": "Roo 想要取得詳細指示以協助目前工作"