diff --git a/.changeset/tired-dogs-worry.md b/.changeset/tired-dogs-worry.md new file mode 100644 index 0000000000..96bc50f854 --- /dev/null +++ b/.changeset/tired-dogs-worry.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Adds a button to intelligently condense the context window diff --git a/src/core/condense/__tests__/index.test.ts b/src/core/condense/__tests__/index.test.ts index e06ea4197d..86afe2a8c4 100644 --- a/src/core/condense/__tests__/index.test.ts +++ b/src/core/condense/__tests__/index.test.ts @@ -97,13 +97,16 @@ describe("summarizeConversation", () => { } as unknown as ApiHandler }) + // Default system prompt for tests + const defaultSystemPrompt = "You are a helpful assistant." + it("should not summarize when there are not enough messages", async () => { const messages: ApiMessage[] = [ { role: "user", content: "Hello", ts: 1 }, { role: "assistant", content: "Hi there", ts: 2 }, ] - const result = await summarizeConversation(messages, mockApiHandler) + const result = await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt) expect(result.messages).toEqual(messages) expect(result.cost).toBe(0) expect(result.summary).toBe("") @@ -122,7 +125,7 @@ describe("summarizeConversation", () => { { role: "user", content: "Tell me more", ts: 7 }, ] - const result = await summarizeConversation(messages, mockApiHandler) + const result = await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt) expect(result.messages).toEqual(messages) expect(result.cost).toBe(0) expect(result.summary).toBe("") @@ -141,7 +144,7 @@ describe("summarizeConversation", () => { { role: "user", content: "Tell me more", ts: 7 }, ] - const result = await summarizeConversation(messages, mockApiHandler) + const result = await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt) // Check that the API was called correctly expect(mockApiHandler.createMessage).toHaveBeenCalled() @@ -199,7 +202,7 @@ describe("summarizeConversation", () => { return messages.map(({ role, content }: { role: string; content: any }) => ({ role, content })) }) - const result = await summarizeConversation(messages, mockApiHandler) + const result = await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt) // Should return original messages when summary is empty expect(result.messages).toEqual(messages) @@ -222,7 +225,7 @@ describe("summarizeConversation", () => { { role: "user", content: "Tell me more", ts: 7 }, ] - await summarizeConversation(messages, mockApiHandler) + await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt) // Verify the final request message const expectedFinalMessage = { diff --git a/src/core/condense/index.ts b/src/core/condense/index.ts index 5ec839d05c..c5ab931069 100644 --- a/src/core/condense/index.ts +++ b/src/core/condense/index.ts @@ -57,12 +57,13 @@ export type SummarizeResponse = { * * @param {ApiMessage[]} messages - The conversation messages * @param {ApiHandler} apiHandler - The API handler to use for token counting. + * @param {string} systemPrompt - The system prompt for API requests, which should be considered in the context token count * @returns {SummarizeResponse} - The result of the summarization operation (see above) */ export async function summarizeConversation( messages: ApiMessage[], apiHandler: ApiHandler, - systemPrompt?: string, + systemPrompt: string, ): Promise { const response: SummarizeResponse = { messages, cost: 0, summary: "" } const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(0, -N_MESSAGES_TO_KEEP)) @@ -111,10 +112,10 @@ export async function summarizeConversation( // Count the tokens in the context for the next API request // We only estimate the tokens in summaryMesage if outputTokens is 0, otherwise we use outputTokens - const contextMessages = outputTokens ? [...keepMessages] : [summaryMessage, ...keepMessages] - if (systemPrompt) { - contextMessages.unshift({ role: "user", content: systemPrompt }) - } + const systemPromptMessage: ApiMessage = { role: "user", content: systemPrompt } + const contextMessages = outputTokens + ? [systemPromptMessage, ...keepMessages] + : [systemPromptMessage, summaryMessage, ...keepMessages] const contextBlocks = contextMessages.flatMap((message) => typeof message.content === "string" ? [{ text: message.content, type: "text" as const }] : message.content, ) diff --git a/src/core/sliding-window/__tests__/sliding-window.test.ts b/src/core/sliding-window/__tests__/sliding-window.test.ts index fe3b71f4eb..810d899ae3 100644 --- a/src/core/sliding-window/__tests__/sliding-window.test.ts +++ b/src/core/sliding-window/__tests__/sliding-window.test.ts @@ -248,6 +248,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) // Check the new return type @@ -276,6 +277,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result).toEqual({ @@ -302,6 +304,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo1.contextWindow, maxTokens: modelInfo1.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) const result2 = await truncateConversationIfNeeded({ @@ -310,6 +313,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo2.contextWindow, maxTokens: modelInfo2.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result1.messages).toEqual(result2.messages) @@ -325,6 +329,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo1.contextWindow, maxTokens: modelInfo1.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) const result4 = await truncateConversationIfNeeded({ @@ -333,6 +338,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo2.contextWindow, maxTokens: modelInfo2.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result3.messages).toEqual(result4.messages) @@ -363,6 +369,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo.contextWindow, maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(resultWithSmall).toEqual({ messages: messagesWithSmallContent, @@ -392,6 +399,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo.contextWindow, maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(resultWithLarge.messages).not.toEqual(messagesWithLargeContent) // Should truncate expect(resultWithLarge.summary).toBe("") @@ -414,6 +422,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo.contextWindow, maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(resultWithVeryLarge.messages).not.toEqual(messagesWithVeryLargeContent) // Should truncate expect(resultWithVeryLarge.summary).toBe("") @@ -439,6 +448,7 @@ describe("truncateConversationIfNeeded", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result).toEqual({ messages: expectedResult, @@ -524,6 +534,7 @@ describe("truncateConversationIfNeeded", () => { maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, autoCondenseContext: true, + systemPrompt: "System prompt", }) // Verify summarizeConversation was called @@ -559,6 +570,7 @@ describe("truncateConversationIfNeeded", () => { maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, autoCondenseContext: false, + systemPrompt: "System prompt", }) // Verify summarizeConversation was not called @@ -612,6 +624,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result1).toEqual({ messages: messagesWithSmallContent, @@ -627,6 +640,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result2.messages).not.toEqual(messagesWithSmallContent) expect(result2.messages.length).toBe(3) // Truncated with 0.5 fraction @@ -650,6 +664,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result1).toEqual({ messages: messagesWithSmallContent, @@ -665,6 +680,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result2.messages).not.toEqual(messagesWithSmallContent) expect(result2.messages.length).toBe(3) // Truncated with 0.5 fraction @@ -687,6 +703,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result1.messages).toEqual(messagesWithSmallContent) @@ -697,6 +714,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.messages.length).toBe(3) // Truncated with 0.5 fraction @@ -717,6 +735,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result1.messages).toEqual(messagesWithSmallContent) @@ -727,6 +746,7 @@ describe("getMaxTokens", () => { contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, + systemPrompt: "System prompt", }) expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.messages.length).toBe(3) // Truncated with 0.5 fraction diff --git a/src/core/sliding-window/index.ts b/src/core/sliding-window/index.ts index 6b42783c44..4c2f17e816 100644 --- a/src/core/sliding-window/index.ts +++ b/src/core/sliding-window/index.ts @@ -64,7 +64,7 @@ type TruncateOptions = { maxTokens?: number | null apiHandler: ApiHandler autoCondenseContext?: boolean - systemPrompt?: string + systemPrompt: string } type TruncateResponse = SummarizeResponse & { prevContextTokens: number } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 4c12d5f311..74cc6cad47 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -76,7 +76,7 @@ import { } from "../checkpoints" import { processUserContentMentions } from "../mentions/processUserContentMentions" import { ApiMessage } from "../task-persistence/apiMessages" -import { getMessagesSinceLastSummary } from "../condense" +import { getMessagesSinceLastSummary, summarizeConversation } from "../condense" import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning" export type ClineEvents = { @@ -481,6 +481,39 @@ export class Task extends EventEmitter { } } + public async condenseContext(): Promise { + const systemPrompt = await this.getSystemPrompt() + const { + messages, + summary, + cost, + newContextTokens = 0, + } = await summarizeConversation(this.apiConversationHistory, this.api, systemPrompt) + if (!summary) { + return + } + const lastMessageContent = this.apiConversationHistory.at(-1)?.content + await this.overwriteApiConversationHistory(messages) + const { contextTokens } = this.getTokenUsage() + const lastContent = + typeof lastMessageContent === "string" + ? [{ type: "text" as const, text: lastMessageContent }] + : lastMessageContent + const lastMessageTokens = lastContent ? await this.api.countTokens(lastContent) : 0 + const prevContextTokens = contextTokens + lastMessageTokens + const contextCondense: ContextCondense = { summary, cost, newContextTokens, prevContextTokens } + await this.say( + "condense_context", + undefined /* text */, + undefined /* images */, + false /* partial */, + undefined /* checkpoint */, + undefined /* progressStatus */, + { isNonInteractive: true } /* options */, + contextCondense, + ) + } + async say( type: ClineSay, text?: string, @@ -1368,35 +1401,9 @@ export class Task extends EventEmitter { } } - public async *attemptApiRequest(retryAttempt: number = 0): ApiStream { + private async getSystemPrompt(): Promise { + const { mcpEnabled } = (await this.providerRef.deref()?.getState()) ?? {} let mcpHub: McpHub | undefined - - const { apiConfiguration, mcpEnabled, autoApprovalEnabled, alwaysApproveResubmit, requestDelaySeconds } = - (await this.providerRef.deref()?.getState()) ?? {} - - let rateLimitDelay = 0 - - // Only apply rate limiting if this isn't the first request - if (this.lastApiRequestTime) { - const now = Date.now() - const timeSinceLastRequest = now - this.lastApiRequestTime - const rateLimit = apiConfiguration?.rateLimitSeconds || 0 - rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000) - } - - // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there. - if (rateLimitDelay > 0 && retryAttempt === 0) { - // Show countdown timer - for (let i = rateLimitDelay; i > 0; i--) { - const delayMessage = `Rate limiting for ${i} seconds...` - await this.say("api_req_retry_delayed", delayMessage, undefined, true) - await delay(1000) - } - } - - // Update last request time before making the request - this.lastApiRequestTime = Date.now() - if (mcpEnabled ?? true) { const provider = this.providerRef.deref() @@ -1432,7 +1439,7 @@ export class Task extends EventEmitter { const { customModes } = (await this.providerRef.deref()?.getState()) ?? {} - const systemPrompt = await (async () => { + return await (async () => { const provider = this.providerRef.deref() if (!provider) { @@ -1457,6 +1464,36 @@ export class Task extends EventEmitter { rooIgnoreInstructions, ) })() + } + + public async *attemptApiRequest(retryAttempt: number = 0): ApiStream { + const { apiConfiguration, autoApprovalEnabled, alwaysApproveResubmit, requestDelaySeconds, experiments } = + (await this.providerRef.deref()?.getState()) ?? {} + + let rateLimitDelay = 0 + + // Only apply rate limiting if this isn't the first request + if (this.lastApiRequestTime) { + const now = Date.now() + const timeSinceLastRequest = now - this.lastApiRequestTime + const rateLimit = apiConfiguration?.rateLimitSeconds || 0 + rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000) + } + + // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there. + if (rateLimitDelay > 0 && retryAttempt === 0) { + // Show countdown timer + for (let i = rateLimitDelay; i > 0; i--) { + const delayMessage = `Rate limiting for ${i} seconds...` + await this.say("api_req_retry_delayed", delayMessage, undefined, true) + await delay(1000) + } + } + + // Update last request time before making the request + this.lastApiRequestTime = Date.now() + + const systemPrompt = await this.getSystemPrompt() const { contextTokens } = this.getTokenUsage() if (contextTokens) { @@ -1495,7 +1532,7 @@ export class Task extends EventEmitter { false /* partial */, undefined /* checkpoint */, undefined /* progressStatus */, - undefined /* options */, + { isNonInteractive: true } /* options */, contextCondense, ) } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 7e22dbbbcc..0ddd071393 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1111,6 +1111,22 @@ export class ClineProvider extends EventEmitter implements await downloadTask(historyItem.ts, apiConversationHistory) } + /* Condenses a task's message history to use fewer tokens. */ + async condenseTaskContext(taskId: string) { + let task: Task | undefined + for (let i = this.clineStack.length - 1; i >= 0; i--) { + if (this.clineStack[i].taskId === taskId) { + task = this.clineStack[i] + break + } + } + if (!task) { + throw new Error(`Task with id ${taskId} not found in stack`) + } + await task.condenseContext() + await this.postMessageToWebview({ type: "condenseTaskContextResponse", text: taskId }) + } + // this function deletes a task from task hidtory, and deletes it's checkpoints and delete the task folder async deleteTaskWithId(id: string) { try { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index dc067e3dd9..c96615edd1 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -197,6 +197,9 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We case "showTaskWithId": provider.showTaskWithId(message.text!) break + case "condenseTaskContextRequest": + provider.condenseTaskContext(message.text!) + break case "deleteTaskWithId": provider.deleteTaskWithId(message.text!) break diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 11cdfde987..ce23437537 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -69,6 +69,7 @@ export interface ExtensionMessage { | "setHistoryPreviewCollapsed" | "commandExecutionStatus" | "vsCodeSetting" + | "condenseTaskContextResponse" text?: string action?: | "chatButtonClicked" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 49a6b42f24..6d6c832e75 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -131,6 +131,7 @@ export interface WebviewMessage { | "searchFiles" | "toggleApiConfigPin" | "setHistoryPreviewCollapsed" + | "condenseTaskContextRequest" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/webview-ui/src/__tests__/ContextWindowProgress.test.tsx b/webview-ui/src/__tests__/ContextWindowProgress.test.tsx index 3e8373842a..15c6f4be20 100644 --- a/webview-ui/src/__tests__/ContextWindowProgress.test.tsx +++ b/webview-ui/src/__tests__/ContextWindowProgress.test.tsx @@ -56,6 +56,8 @@ describe("ContextWindowProgress", () => { totalCost: 0.001, contextTokens: 1000, onClose: jest.fn(), + buttonsDisabled: false, + handleCondenseContext: jest.fn((_taskId: string) => {}), } return render( diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index fbffa439ba..4c1ce902b2 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -69,6 +69,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction("") const [wasStreaming, setWasStreaming] = useState(false) const [showCheckpointWarning, setShowCheckpointWarning] = useState(false) + const [isCondensing, setIsCondensing] = useState(false) // UI layout depends on the last 2 messages // (since it relies on the content of these messages, we are deep comparing. i.e. the button state after hitting button sets enableButtons to false, and this effect otherwise would have to true again even if messages didn't change @@ -619,15 +621,26 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (isCondensing || sendingDisabled) { + return + } + setIsCondensing(true) + setSendingDisabled(true) + vscode.postMessage({ type: "condenseTaskContextRequest", text: taskId }) + } + return (
{showAnnouncement && } @@ -1256,6 +1278,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction diff --git a/webview-ui/src/components/chat/ContextCondenseRow.tsx b/webview-ui/src/components/chat/ContextCondenseRow.tsx index 52045df7f7..a2133b7165 100644 --- a/webview-ui/src/components/chat/ContextCondenseRow.tsx +++ b/webview-ui/src/components/chat/ContextCondenseRow.tsx @@ -27,10 +27,7 @@ export const ContextCondenseRow = ({ cost, prevContextTokens, newContextTokens, {isExpanded && (
-

{t("chat:contextCondense.conversationSummary")}

-
- -
+
)}
diff --git a/webview-ui/src/components/chat/TaskActions.tsx b/webview-ui/src/components/chat/TaskActions.tsx index de6f7c2810..2496cb585f 100644 --- a/webview-ui/src/components/chat/TaskActions.tsx +++ b/webview-ui/src/components/chat/TaskActions.tsx @@ -3,43 +3,54 @@ import prettyBytes from "pretty-bytes" import { useTranslation } from "react-i18next" import { vscode } from "@/utils/vscode" -import { Button } from "@/components/ui" - import { HistoryItem } from "@roo/shared/HistoryItem" import { DeleteTaskDialog } from "../history/DeleteTaskDialog" +import { IconButton } from "./IconButton" -export const TaskActions = ({ item }: { item: HistoryItem | undefined }) => { +interface TaskActionsProps { + item?: HistoryItem + buttonsDisabled: boolean + handleCondenseContext: (taskId: string) => void +} + +export const TaskActions = ({ item, buttonsDisabled, handleCondenseContext }: TaskActionsProps) => { const [deleteTaskId, setDeleteTaskId] = useState(null) const { t } = useTranslation() return (
- + disabled={buttonsDisabled} + onClick={() => vscode.postMessage({ type: "exportCurrentTask" })} + /> {!!item?.size && item.size > 0 && ( <> - + if (e.shiftKey) { + vscode.postMessage({ type: "deleteTaskWithId", text: item.id }) + } else { + setDeleteTaskId(item.id) + } + }} + /> + {prettyBytes(item.size)} +
{deleteTaskId && ( void onClose: () => void } @@ -40,6 +42,8 @@ const TaskHeader = ({ cacheReads, totalCost, contextTokens, + buttonsDisabled, + handleCondenseContext, onClose, }: TaskHeaderProps) => { const { t } = useTranslation() @@ -152,7 +156,13 @@ const TaskHeader = ({ )} - {!totalCost && } + {!totalCost && ( + + )} {doesModelSupportPromptCache && @@ -181,7 +191,11 @@ const TaskHeader = ({ {t("chat:task.apiCost")} ${totalCost?.toFixed(2)} - + )} diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx index f9fb71b066..27704a2ce5 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx @@ -40,6 +40,8 @@ describe("TaskHeader", () => { doesModelSupportPromptCache: true, totalCost: 0.05, contextTokens: 200, + buttonsDisabled: false, + handleCondenseContext: jest.fn(), onClose: jest.fn(), } diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 73c861063a..bff7eb1efb 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Finestra de context:", "closeAndStart": "Tancar tasca i iniciar-ne una de nova", "export": "Exportar historial de tasques", - "delete": "Eliminar tasca (Shift + Clic per ometre confirmació)" + "delete": "Eliminar tasca (Shift + Clic per ometre confirmació)", + "condenseContext": "Condensar context de la tasca" }, "unpin": "Desfixar", "pin": "Fixar", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Context condensat", - "conversationSummary": "Resum de la conversa", "tokens": "tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 388459b0c8..89145300ce 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Kontextfenster:", "closeAndStart": "Aufgabe schließen und neue starten", "export": "Aufgabenverlauf exportieren", - "delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)" + "delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)", + "condenseContext": "Task-Kontext komprimieren" }, "unpin": "Lösen von oben", "pin": "Anheften", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Kontext komprimiert", - "conversationSummary": "Gesprächszusammenfassung", "tokens": "Tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index b984cd8568..d443df39aa 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -7,6 +7,7 @@ "tokens": "Tokens:", "cache": "Cache:", "apiCost": "API Cost:", + "condenseContext": "Condense task context", "contextWindow": "Context Length:", "closeAndStart": "Close task and start a new one", "export": "Export task history", @@ -131,7 +132,6 @@ }, "contextCondense": { "title": "Context Condensed", - "conversationSummary": "Conversation Summary", "tokens": "tokens" }, "instructions": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index f632bc188e..3b2576ac0e 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Longitud del contexto:", "closeAndStart": "Cerrar tarea e iniciar una nueva", "export": "Exportar historial de tareas", - "delete": "Eliminar tarea (Shift + Clic para omitir confirmación)" + "delete": "Eliminar tarea (Shift + Clic para omitir confirmación)", + "condenseContext": "Condensar contexto de la tarea" }, "unpin": "Desfijar", "pin": "Fijar", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Contexto condensado", - "conversationSummary": "Resumen de la conversación", "tokens": "tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 0dee8d706d..860eb98947 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Durée du contexte :", "closeAndStart": "Fermer la tâche et en commencer une nouvelle", "export": "Exporter l'historique des tâches", - "delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)" + "delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)", + "condenseContext": "Condenser le contexte de la tâche" }, "unpin": "Désépingler", "pin": "Épingler", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Contexte condensé", - "conversationSummary": "Résumé de la conversation", "tokens": "tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 730e769d5c..21886ad0a5 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -10,7 +10,8 @@ "contextWindow": "संदर्भ लंबाई:", "closeAndStart": "कार्य बंद करें और नया शुरू करें", "export": "कार्य इतिहास निर्यात करें", - "delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)" + "delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)", + "condenseContext": "कार्य संदर्भ संघनित करें" }, "unpin": "पिन करें", "pin": "अवपिन करें", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "संदर्भ संक्षिप्त किया गया", - "conversationSummary": "वार्तालाप का सारांश", "tokens": "टोकन" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index eac3f6e4f2..34b737a423 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Lunghezza del contesto:", "closeAndStart": "Chiudi attività e iniziane una nuova", "export": "Esporta cronologia attività", - "delete": "Elimina attività (Shift + Clic per saltare la conferma)" + "delete": "Elimina attività (Shift + Clic per saltare la conferma)", + "condenseContext": "Condensa contesto attività" }, "unpin": "Rilascia", "pin": "Fissa", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Contesto condensato", - "conversationSummary": "Riepilogo della conversazione", "tokens": "token" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index e388075c23..3b91864ac8 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -10,7 +10,8 @@ "contextWindow": "コンテキストウィンドウ:", "closeAndStart": "タスクを閉じて新しいタスクを開始", "export": "タスク履歴をエクスポート", - "delete": "タスクを削除(Shift + クリックで確認をスキップ)" + "delete": "タスクを削除(Shift + クリックで確認をスキップ)", + "condenseContext": "タスクコンテキストを圧縮" }, "unpin": "ピン留めを解除", "pin": "ピン留め", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "コンテキスト要約", - "conversationSummary": "会話の要約", "tokens": "トークン" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 86850770a7..40222b5a37 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -10,7 +10,8 @@ "contextWindow": "컨텍스트 창:", "closeAndStart": "작업 닫고 새 작업 시작", "export": "작업 기록 내보내기", - "delete": "작업 삭제 (Shift + 클릭으로 확인 생략)" + "delete": "작업 삭제 (Shift + 클릭으로 확인 생략)", + "condenseContext": "작업 컨텍스트 압축" }, "unpin": "고정 해제하기", "pin": "고정하기", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "컨텍스트 요약됨", - "conversationSummary": "대화 요약", "tokens": "토큰" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 6befd41eb8..f37cbfe491 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Contextlengte:", "closeAndStart": "Taak sluiten en een nieuwe starten", "export": "Taakgeschiedenis exporteren", - "delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)" + "delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)", + "condenseContext": "Taakcontext samenvatten" }, "unpin": "Losmaken", "pin": "Vastmaken", @@ -210,7 +211,6 @@ }, "contextCondense": { "title": "Context samengevat", - "conversationSummary": "Gespreksoverzicht", "tokens": "tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 29499ed943..22bdbbea4a 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Okno kontekstu:", "closeAndStart": "Zamknij zadanie i rozpocznij nowe", "export": "Eksportuj historię zadań", - "delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)" + "delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)", + "condenseContext": "Skondensuj kontekst zadania" }, "unpin": "Odepnij", "pin": "Przypnij", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Kontekst skondensowany", - "conversationSummary": "Podsumowanie rozmowy", "tokens": "tokeny" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 54da897f72..63d7b205e0 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Janela de contexto:", "closeAndStart": "Fechar tarefa e iniciar nova", "export": "Exportar histórico de tarefas", - "delete": "Excluir tarefa (Shift + Clique para pular confirmação)" + "delete": "Excluir tarefa (Shift + Clique para pular confirmação)", + "condenseContext": "Condensar contexto da tarefa" }, "unpin": "Desfixar", "pin": "Fixar", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Contexto condensado", - "conversationSummary": "Resumo da conversa", "tokens": "tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index f61c83a9a6..4489224452 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Длина контекста:", "closeAndStart": "Закрыть задачу и начать новую", "export": "Экспортировать историю задач", - "delete": "Удалить задачу (Shift + клик для пропуска подтверждения)" + "delete": "Удалить задачу (Shift + клик для пропуска подтверждения)", + "condenseContext": "Сжать контекст задачи" }, "unpin": "Открепить", "pin": "Закрепить", @@ -210,7 +211,6 @@ }, "contextCondense": { "title": "Контекст сжат", - "conversationSummary": "Сводка разговора", "tokens": "токены" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index bb753b0ecd..a7ee2117c7 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Bağlam Uzunluğu:", "closeAndStart": "Görevi kapat ve yeni bir görev başlat", "export": "Görev geçmişini dışa aktar", - "delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)" + "delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)", + "condenseContext": "Görev bağlamını yoğunlaştır" }, "unpin": "Sabitlemeyi iptal et", "pin": "Sabitle", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Bağlam Özetlendi", - "conversationSummary": "Konuşma Özeti", "tokens": "token" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 31ae044b04..46a7dfd7bc 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -10,7 +10,8 @@ "contextWindow": "Chiều dài bối cảnh:", "closeAndStart": "Đóng nhiệm vụ và bắt đầu nhiệm vụ mới", "export": "Xuất lịch sử nhiệm vụ", - "delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)" + "delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)", + "condenseContext": "Cô đọng ngữ cảnh tác vụ" }, "unpin": "Bỏ ghim khỏi đầu", "pin": "Ghim lên đầu", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "Ngữ cảnh đã tóm tắt", - "conversationSummary": "Tóm tắt cuộc hội thoại", "tokens": "token" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 048e25c344..05a7d389ca 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -10,7 +10,8 @@ "contextWindow": "上下文长度:", "closeAndStart": "关闭任务并开始新任务", "export": "导出任务历史", - "delete": "删除任务(Shift + 点击跳过确认)" + "delete": "删除任务(Shift + 点击跳过确认)", + "condenseContext": "压缩任务上下文" }, "unpin": "取消置顶", "pin": "置顶", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "上下文已压缩", - "conversationSummary": "对话摘要", "tokens": "tokens" }, "followUpSuggest": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 42b8f78347..aca6da7412 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -10,7 +10,8 @@ "contextWindow": "上下文長度:", "closeAndStart": "關閉現有工作並開始一項新的工作", "export": "匯出工作紀錄", - "delete": "刪除工作(按住 Shift 並點選可跳過確認)" + "delete": "刪除工作(按住 Shift 並點選可跳過確認)", + "condenseContext": "壓縮工作上下文" }, "unpin": "取消置頂", "pin": "置頂", @@ -200,7 +201,6 @@ }, "contextCondense": { "title": "上下文已壓縮", - "conversationSummary": "對話摘要", "tokens": "tokens" }, "followUpSuggest": {