diff --git a/.changeset/tired-dogs-worry.md b/.changeset/tired-dogs-worry.md new file mode 100644 index 00000000000..96bc50f8544 --- /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 e06ea4197d3..86afe2a8c4c 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 5ec839d05c5..c5ab9310694 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 fe3b71f4eb9..810d899ae37 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 6b42783c447..4c2f17e8161 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 b301904f7ac..f88812e0c66 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 = { @@ -480,6 +480,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, @@ -1367,35 +1400,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() @@ -1431,7 +1438,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) { @@ -1456,6 +1463,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) { @@ -1494,7 +1531,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 1c63c951968..22c03922f2a 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1109,6 +1109,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 9c8b90ea8a9..e808c255b58 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -195,6 +195,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 6330556024a..29cf57d3b49 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 22fe5c7d3e3..cb208fc6ee0 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -130,6 +130,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 3e8373842a2..15c6f4be200 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 90cdd147531..f2f205edf0e 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -64,6 +64,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 @@ -581,15 +583,26 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (isCondensing || sendingDisabled) { + return + } + setIsCondensing(true) + setSendingDisabled(true) + vscode.postMessage({ type: "condenseTaskContextRequest", text: taskId }) + } + return (
{showAnnouncement && } @@ -1218,6 +1240,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction diff --git a/webview-ui/src/components/chat/ContextCondenseRow.tsx b/webview-ui/src/components/chat/ContextCondenseRow.tsx index 52045df7f7e..a2133b7165b 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 de6f7c28100..2496cb585f9 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" + +interface TaskActionsProps { + item?: HistoryItem + buttonsDisabled: boolean + handleCondenseContext: (taskId: string) => void +} -export const TaskActions = ({ item }: { item: HistoryItem | undefined }) => { +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 f9fb71b0660..27704a2ce5f 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 dd0d89ced5f..186fefddf60 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 f683ada7d3d..a44247326a5 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 624381c8b0b..30e34bd4683 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 7de76382416..17af37ee6ce 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 bb1219e7ac1..fe60345a9d9 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 58bbd96b037..ee213d9093b 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 40a80ed188c..7becab5fe1f 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 8bb0b09a1ef..80549dc5ef7 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 b79038367ba..f9855ccf061 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 f69ce8b69cd..c38b933d81f 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 c21898d50a5..7816db0cf68 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 d25cc9962fc..03c940230e6 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 6e2345f8223..c7ec414e3a7 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 d0171862581..115fa849edd 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 62bed81fc43..31f4d243510 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 31993895ac6..d4fed459f64 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 b3f94b9d819..7e83228beeb 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": {