diff --git a/.changeset/cyan-insects-marry.md b/.changeset/cyan-insects-marry.md deleted file mode 100644 index 98dc450f7e..0000000000 --- a/.changeset/cyan-insects-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Add a copy button to the recent tasks diff --git a/.changeset/dirty-coins-exist.md b/.changeset/dirty-coins-exist.md deleted file mode 100644 index d01a3ba76e..0000000000 --- a/.changeset/dirty-coins-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Improve the user experience for adding a new configuration profile diff --git a/.changeset/red-badgers-matter.md b/.changeset/red-badgers-matter.md deleted file mode 100644 index baab6ba27d..0000000000 --- a/.changeset/red-badgers-matter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Style and copy updates to advanced settings diff --git a/.changeset/spicy-pugs-guess.md b/.changeset/spicy-pugs-guess.md new file mode 100644 index 0000000000..e93267e9d1 --- /dev/null +++ b/.changeset/spicy-pugs-guess.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Support Volcano Ark platform through OpenAI compatible diff --git a/.changeset/stupid-masks-pay.md b/.changeset/stupid-masks-pay.md new file mode 100644 index 0000000000..021be2a11b --- /dev/null +++ b/.changeset/stupid-masks-pay.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix jumpiness while entering API config by updating on blur instead of input diff --git a/.changeset/violet-rockets-fetch.md b/.changeset/violet-rockets-fetch.md deleted file mode 100644 index 9da0face9a..0000000000 --- a/.changeset/violet-rockets-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -v3.3.15 diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b6309797..2662411e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Roo Code Changelog +## [3.3.15] + +- Improvements to MCP initialization and server restarts (thanks @MuriloFP and @hannesrudolph!) +- Add a copy button to the recent tasks (thanks @hannesrudolph!) +- Improve the user experience for adding a new API profile +- Another significant fix to API profile switching on the settings screen +- Opt-in experimental version of checkpoints in the advanced settings + ## [3.3.14] - Should have skipped floor 13 like an elevator. This fixes the broken 3.3.13 release by reverting some changes to the deployment scripts. diff --git a/package-lock.json b/package-lock.json index 7c91b7e18b..0fb24a87b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.3.14", + "version": "3.3.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.3.14", + "version": "3.3.15", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", diff --git a/package.json b/package.json index b3a8afcb88..a525cde4c0 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Code (prev. Roo Cline)", "description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.", "publisher": "RooVeterinaryInc", - "version": "3.3.14", + "version": "3.3.15", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 408f4e5cc3..203eb44040 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -10,6 +10,7 @@ import { import { ApiHandler, SingleCompletionHandler } from "../index" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" +import { convertToSimpleMessages } from "../transform/simple-format" import { ApiStream } from "../transform/stream" export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { @@ -46,21 +47,31 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const modelInfo = this.getModel().info + const modelUrl = this.options.openAiBaseUrl ?? "" const modelId = this.options.openAiModelId ?? "" const deepseekReasoner = modelId.includes("deepseek-reasoner") + const ark = modelUrl.includes(".volces.com") if (this.options.openAiStreamingEnabled ?? true) { const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = { role: "system", content: systemPrompt, } + + let convertedMessages + if (deepseekReasoner) { + convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) + } else if (ark) { + convertedMessages = [systemMessage, ...convertToSimpleMessages(messages)] + } else { + convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)] + } + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, temperature: 0, - messages: deepseekReasoner - ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) - : [systemMessage, ...convertToOpenAiMessages(messages)], + messages: convertedMessages, stream: true as const, stream_options: { include_usage: true }, } diff --git a/src/api/transform/__tests__/simple-format.test.ts b/src/api/transform/__tests__/simple-format.test.ts new file mode 100644 index 0000000000..bf1de1927f --- /dev/null +++ b/src/api/transform/__tests__/simple-format.test.ts @@ -0,0 +1,138 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { convertToSimpleContent, convertToSimpleMessages } from "../simple-format" + +describe("simple-format", () => { + describe("convertToSimpleContent", () => { + it("returns string content as-is", () => { + const content = "Hello world" + expect(convertToSimpleContent(content)).toBe("Hello world") + }) + + it("extracts text from text blocks", () => { + const content = [ + { type: "text", text: "Hello" }, + { type: "text", text: "world" }, + ] as Anthropic.Messages.TextBlockParam[] + expect(convertToSimpleContent(content)).toBe("Hello\nworld") + }) + + it("converts image blocks to descriptive text", () => { + const content = [ + { type: "text", text: "Here's an image:" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "base64data", + }, + }, + ] as Array + expect(convertToSimpleContent(content)).toBe("Here's an image:\n[Image: image/png]") + }) + + it("converts tool use blocks to descriptive text", () => { + const content = [ + { type: "text", text: "Using a tool:" }, + { + type: "tool_use", + id: "tool-1", + name: "read_file", + input: { path: "test.txt" }, + }, + ] as Array + expect(convertToSimpleContent(content)).toBe("Using a tool:\n[Tool Use: read_file]") + }) + + it("handles string tool result content", () => { + const content = [ + { type: "text", text: "Tool result:" }, + { + type: "tool_result", + tool_use_id: "tool-1", + content: "Result text", + }, + ] as Array + expect(convertToSimpleContent(content)).toBe("Tool result:\nResult text") + }) + + it("handles array tool result content with text and images", () => { + const content = [ + { + type: "tool_result", + tool_use_id: "tool-1", + content: [ + { type: "text", text: "Result 1" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/jpeg", + data: "base64data", + }, + }, + { type: "text", text: "Result 2" }, + ], + }, + ] as Anthropic.Messages.ToolResultBlockParam[] + expect(convertToSimpleContent(content)).toBe("Result 1\n[Image: image/jpeg]\nResult 2") + }) + + it("filters out empty strings", () => { + const content = [ + { type: "text", text: "Hello" }, + { type: "text", text: "" }, + { type: "text", text: "world" }, + ] as Anthropic.Messages.TextBlockParam[] + expect(convertToSimpleContent(content)).toBe("Hello\nworld") + }) + }) + + describe("convertToSimpleMessages", () => { + it("converts messages with string content", () => { + const messages = [ + { role: "user", content: "Hello" }, + { role: "assistant", content: "Hi there" }, + ] as Anthropic.Messages.MessageParam[] + expect(convertToSimpleMessages(messages)).toEqual([ + { role: "user", content: "Hello" }, + { role: "assistant", content: "Hi there" }, + ]) + }) + + it("converts messages with complex content", () => { + const messages = [ + { + role: "user", + content: [ + { type: "text", text: "Look at this:" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "base64data", + }, + }, + ], + }, + { + role: "assistant", + content: [ + { type: "text", text: "I see the image" }, + { + type: "tool_use", + id: "tool-1", + name: "analyze_image", + input: { data: "base64data" }, + }, + ], + }, + ] as Anthropic.Messages.MessageParam[] + expect(convertToSimpleMessages(messages)).toEqual([ + { role: "user", content: "Look at this:\n[Image: image/png]" }, + { role: "assistant", content: "I see the image\n[Tool Use: analyze_image]" }, + ]) + }) + }) +}) diff --git a/src/api/transform/simple-format.ts b/src/api/transform/simple-format.ts new file mode 100644 index 0000000000..c1e4895bba --- /dev/null +++ b/src/api/transform/simple-format.ts @@ -0,0 +1,67 @@ +import { Anthropic } from "@anthropic-ai/sdk" + +/** + * Convert complex content blocks to simple string content + */ +export function convertToSimpleContent( + content: + | string + | Array< + | Anthropic.Messages.TextBlockParam + | Anthropic.Messages.ImageBlockParam + | Anthropic.Messages.ToolUseBlockParam + | Anthropic.Messages.ToolResultBlockParam + >, +): string { + if (typeof content === "string") { + return content + } + + // Extract text from content blocks + return content + .map((block) => { + if (block.type === "text") { + return block.text + } + if (block.type === "image") { + return `[Image: ${block.source.media_type}]` + } + if (block.type === "tool_use") { + return `[Tool Use: ${block.name}]` + } + if (block.type === "tool_result") { + if (typeof block.content === "string") { + return block.content + } + if (Array.isArray(block.content)) { + return block.content + .map((part) => { + if (part.type === "text") { + return part.text + } + if (part.type === "image") { + return `[Image: ${part.source.media_type}]` + } + return "" + }) + .join("\n") + } + return "" + } + return "" + }) + .filter(Boolean) + .join("\n") +} + +/** + * Convert Anthropic messages to simple format with string content + */ +export function convertToSimpleMessages( + messages: Anthropic.Messages.MessageParam[], +): Array<{ role: "user" | "assistant"; content: string }> { + return messages.map((message) => ({ + role: message.role, + content: convertToSimpleContent(message.content), + })) +} diff --git a/src/services/checkpoints/CheckpointService.ts b/src/services/checkpoints/CheckpointService.ts index 6df220e115..56cdd10fb2 100644 --- a/src/services/checkpoints/CheckpointService.ts +++ b/src/services/checkpoints/CheckpointService.ts @@ -5,10 +5,6 @@ import path from "path" import debug from "debug" import simpleGit, { SimpleGit, CleanOptions } from "simple-git" -if (process.env.NODE_ENV !== "test") { - debug.enable("simple-git") -} - export type CheckpointServiceOptions = { taskId: string git?: SimpleGit diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index b44ade2851..b3dc15147f 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -13,6 +13,7 @@ "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.0", "@vscode/webview-ui-toolkit": "^1.4.0", "class-variance-authority": "^0.7.1", @@ -4185,6 +4186,187 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", + "integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", + "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", + "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -4287,6 +4469,70 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 3317551f20..59ea5693b6 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -20,6 +20,7 @@ "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.0", "@vscode/webview-ui-toolkit": "^1.4.0", "class-variance-authority": "^0.7.1", diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx index 954dc2e8f3..8fee305a47 100644 --- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx +++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx @@ -3,7 +3,16 @@ import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons" import { vscode } from "../../../utils/vscode" -import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui" +import { + Button, + Popover, + PopoverContent, + PopoverTrigger, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui" type CheckpointMenuProps = { ts: number @@ -43,9 +52,16 @@ export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: Checkp return (
- + + + + + + View Diff + + { @@ -53,9 +69,16 @@ export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: Checkp setIsConfirming(false) }}> - + + + + + + Restore Checkpoint + +
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 3ba6b245f2..3f5dd7698b 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -41,7 +41,6 @@ import { UnboundModelPicker } from "./UnboundModelPicker" import { ModelInfoView } from "./ModelInfoView" import { DROPDOWN_Z_INDEX } from "./styles" - interface ApiOptionsProps { apiErrorMessage?: string modelIdErrorMessage?: string @@ -164,7 +163,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.apiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("apiKey")} + onBlur={handleInputChange("apiKey")} placeholder="Enter API Key..."> Anthropic API Key @@ -189,7 +188,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.anthropicBaseUrl || ""} style={{ width: "100%", marginTop: 3 }} type="url" - onInput={handleInputChange("anthropicBaseUrl")} + onBlur={handleInputChange("anthropicBaseUrl")} placeholder="Default: https://api.anthropic.com" /> )} @@ -218,7 +217,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.glamaApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("glamaApiKey")} + onBlur={handleInputChange("glamaApiKey")} placeholder="Enter API Key..."> Glama API Key @@ -247,7 +246,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.openAiNativeApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("openAiNativeApiKey")} + onBlur={handleInputChange("openAiNativeApiKey")} placeholder="Enter API Key..."> OpenAI API Key @@ -275,7 +274,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.mistralApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("mistralApiKey")} + onBlur={handleInputChange("mistralApiKey")} placeholder="Enter API Key..."> Mistral API Key @@ -306,7 +305,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.openRouterApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("openRouterApiKey")} + onBlur={handleInputChange("openRouterApiKey")} placeholder="Enter API Key..."> OpenRouter API Key @@ -340,7 +339,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.openRouterBaseUrl || ""} style={{ width: "100%", marginTop: 3 }} type="url" - onInput={handleInputChange("openRouterBaseUrl")} + onBlur={handleInputChange("openRouterBaseUrl")} placeholder="Default: https://openrouter.ai/api/v1" /> )} @@ -391,7 +390,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = AWS Profile Name @@ -402,7 +401,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.awsAccessKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("awsAccessKey")} + onBlur={handleInputChange("awsAccessKey")} placeholder="Enter Access Key..."> AWS Access Key @@ -410,7 +409,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.awsSecretKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("awsSecretKey")} + onBlur={handleInputChange("awsSecretKey")} placeholder="Enter Secret Key..."> AWS Secret Key @@ -418,7 +417,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.awsSessionToken || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("awsSessionToken")} + onBlur={handleInputChange("awsSessionToken")} placeholder="Enter Session Token..."> AWS Session Token @@ -486,7 +485,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = Google Cloud Project ID @@ -544,7 +543,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.geminiApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("geminiApiKey")} + onBlur={handleInputChange("geminiApiKey")} placeholder="Enter API Key..."> Gemini API Key @@ -572,7 +571,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.openAiBaseUrl || ""} style={{ width: "100%" }} type="url" - onInput={handleInputChange("openAiBaseUrl")} + onBlur={handleInputChange("openAiBaseUrl")} placeholder={"Enter base URL..."}> Base URL @@ -580,7 +579,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.openAiApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("openAiApiKey")} + onBlur={handleInputChange("openAiApiKey")} placeholder="Enter API Key..."> API Key @@ -623,7 +622,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = )} @@ -1073,14 +1072,14 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.lmStudioBaseUrl || ""} style={{ width: "100%" }} type="url" - onInput={handleInputChange("lmStudioBaseUrl")} + onBlur={handleInputChange("lmStudioBaseUrl")} placeholder={"Default: http://localhost:1234"}> Base URL (optional) Model ID @@ -1142,7 +1141,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.deepSeekApiKey || ""} style={{ width: "100%" }} type="password" - onInput={handleInputChange("deepSeekApiKey")} + onBlur={handleInputChange("deepSeekApiKey")} placeholder="Enter API Key..."> DeepSeek API Key @@ -1232,14 +1231,14 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = value={apiConfiguration?.ollamaBaseUrl || ""} style={{ width: "100%" }} type="url" - onInput={handleInputChange("ollamaBaseUrl")} + onBlur={handleInputChange("ollamaBaseUrl")} placeholder={"Default: http://localhost:11434"}> Base URL (optional) Model ID diff --git a/webview-ui/src/components/ui/index.ts b/webview-ui/src/components/ui/index.ts index 24843c1211..0de5e7eab9 100644 --- a/webview-ui/src/components/ui/index.ts +++ b/webview-ui/src/components/ui/index.ts @@ -3,3 +3,4 @@ export * from "./command" export * from "./dialog" export * from "./dropdown-menu" export * from "./popover" +export * from "./tooltip" diff --git a/webview-ui/src/components/ui/tooltip.tsx b/webview-ui/src/components/ui/tooltip.tsx new file mode 100644 index 0000000000..7818d9b125 --- /dev/null +++ b/webview-ui/src/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index 026d27c5f3..facc2db084 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -76,6 +76,13 @@ --color-vscode-input-background: var(--vscode-input-background); --color-vscode-input-border: var(--vscode-input-border); + + --color-vscode-badge-foreground: var(--vscode-badge-foreground); + --color-vscode-badge-background: var(--vscode-badge-background); + + --color-vscode-notifications-foreground: var(--vscode-notifications-foreground); + --color-vscode-notifications-background: var(--vscode-notifications-background); + --color-vscode-notifications-border: var(--vscode-notifications-border); } @layer base {