Compare commits

...

44 Commits

Author SHA1 Message Date
abeatrix fe41da67b6 feat: replace announcement with interactive What's New component
Refactor WelcomeSection to remove static Announcement and banner components, introducing a new WhatsNew component for navigating release news. This improves user onboarding by providing an engaging, interactive way to view updates and features.
2025-10-24 18:36:53 -07:00
abeatrix 08cef66956 seperate what's news 2025-10-24 18:28:39 -07:00
abeatrix 25e1e0a58b Include description 2025-10-24 17:22:44 -07:00
abeatrix 1b0a63f1cc Merge branch 'bee/tooltip-upgrade' into bee/onboarding 2025-10-24 14:57:40 -07:00
abeatrix 89ecf79c19 Merge branch 'main' into bee/tooltip-upgrade 2025-10-24 14:57:17 -07:00
abeatrix 302bc4f1b4 feat(onboarding): add whats new card and close welcome
Introduce WhatsNew card to replace legacy info and CLI banners.
Call onboarding completion hooks to dismiss welcome view when finished.
2025-10-23 16:07:28 -07:00
abeatrix 4da7817b7e (wip)feat: replace WelcomeView with new OnboardingView flow
- Introduce multi-step onboarding with user type selection (free, power, BYOK)
- Add model selection and API configuration steps
- Integrate new Radix UI components (Item, Dialog) for consistent UX
- Remove legacy WelcomeView component
2025-10-23 10:05:27 -07:00
abeatrix fd9ecb1c7f set 2025-10-22 12:26:24 -07:00
abeatrix cf41695823 update data-testid 2025-10-22 12:25:04 -07:00
abeatrix 54fc95dbe8 Merge branch 'main' into bee/tooltip-upgrade 2025-10-22 12:08:03 -07:00
abeatrix 2969810a44 Align chat toolbox icon stylings 2025-10-22 12:05:19 -07:00
abeatrix c1b526c154 Apply feedback - hide arrow for autoapprove menu 2025-10-22 10:00:11 -07:00
abeatrix ed58c05dc2 Fix tooltip trigger in settings 2025-10-22 09:37:00 -07:00
abeatrix 2c2f1bc3e1 size 2025-10-21 23:03:21 -07:00
abeatrix b5890d341f update test with clear test id 2025-10-21 12:02:16 -07:00
abeatrix 5e29e66aca Merge branch 'bee/tooltip-upgrade' of https://github.com/cline/cline into bee/tooltip-upgrade 2025-10-21 11:50:37 -07:00
abeatrix a83354a7e4 Merge branch 'main' into bee/tooltip-upgrade 2025-10-21 11:50:34 -07:00
abeatrix 7b5400805a clean up 2025-10-21 10:45:39 -07:00
abeatrix 2b97a097e4 Merge branch 'main' into bee/tooltip-upgrade 2025-10-21 09:46:55 -07:00
abeatrix cfe94739ef unify font size 2025-10-20 11:24:58 -07:00
abeatrix bdfb7ff9c5 Update TaskHeader classnames 2025-10-15 12:20:13 +08:00
abeatrix 57b0763b8b Remove DRY code and update descriptionForeground class name 2025-10-15 11:55:14 +08:00
abeatrix 6745bdc79b Merge branch 'bee/tailwind-v4-upgrade' into bee/tooltip-upgrade 2025-10-15 11:32:12 +08:00
abeatrix 899af48677 clean up 2025-10-15 11:31:41 +08:00
abeatrix 4e645ac853 clean up 2025-10-15 11:29:50 +08:00
abeatrix dc99753a59 Merge branch 'bee/webview-tailwind-v4' into bee/tooltip-upgrade 2025-10-15 10:03:43 +08:00
abeatrix 6e57263eca npx @tailwindcss/upgrade
≈ tailwindcss v4.1.14

│ ↳ Upgrading from Tailwind CSS `v4.1.14`

│ Searching for CSS files in the current directory and its subdirectories…

│ Migrating stylesheets…

│ ↳ Migrated stylesheet: `./webview-ui/src/index.css`

│ Updating dependencies…

│ ↳ Updated package: `tailwindcss`

│ ↳ Updated package: `@tailwindcss/vite`

│ Migrating templates…

│ ↳ Migrated templates for: `./webview-ui/src/index.css`

│ Verify the changes and commit them to your repository.
2025-10-15 05:49:00 +08:00
abeatrix 801310222e npm install tailwindcss @tailwindcss/vite 2025-10-15 05:48:26 +08:00
abeatrix cab5183297 Merge branch 'main' into bee/webview-tailwind-v4 2025-10-15 05:46:08 +08:00
abeatrix 870e58280d Clean up 2025-10-14 12:39:58 +08:00
abeatrix 010a23c795 Fix e2e test 2025-10-14 12:38:19 +08:00
abeatrix da9dd52bf2 Merge branch 'main' into bee/webview-tailwind-v4 2025-10-14 12:28:26 +08:00
Beatrix Woo ff5b6584bc update logo id 2025-10-03 14:19:18 +08:00
Beatrix Woo 04204c14bf fix spacing and sizes 2025-10-03 13:59:38 +08:00
Beatrix Woo 40a5f5ffad Merge branch 'main' into bee/webview-tailwind-v4 2025-10-03 09:08:37 +08:00
abeatrix 9fff0bd34d Merge branch 'main' into bee/webview-tailwind-v4 2025-10-02 09:13:48 +08:00
abeatrix 2e7dd7f841 Unify styles with theme 2025-10-02 09:12:20 +08:00
abeatrix 18cf97431c Fix auto approve modal z-index number 2025-10-02 06:35:07 +08:00
abeatrix 805fb85371 Update mode switch test 2025-09-30 07:27:14 +08:00
abeatrix 488e377a44 clean up 2025-09-30 07:02:41 +08:00
abeatrix 16a378840b Update e2e test text 2025-09-30 06:56:27 +08:00
abeatrix 0bbc718766 import main.css 2025-09-30 06:47:12 +08:00
abeatrix 74c6d1d2bc Migrate HeroUITooltip to radix-ui shadcn components 2025-09-30 06:33:27 +08:00
abeatrix 82b549c2ca Set up Tailwind v4
npx @tailwindcss/upgrade                                                                                                                                   1 ↵
≈ tailwindcss v4.1.13

│ Searching for CSS files in the current directory and its subdirectories…

│ Migrating stylesheets…

│ ↳ Migrated stylesheet: `./src/index.css`

│ Updating dependencies…

│ ↳ Updated package: `tailwindcss`

│ ↳ Updated package: `@tailwindcss/vite`

│ Migrating templates…

│ ↳ Migrated templates for: `./src/index.css`

│ Verify the changes and commit them to your repository.
2025-09-30 05:50:41 +08:00
130 changed files with 5081 additions and 1726 deletions
+2 -2
View File
@@ -122,9 +122,9 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
title="Previous Updates:"
classNames={{
trigger: "bg-transparent border-0 pl-0 pb-0 w-fit",
title: "font-bold text-[var(--vscode-foreground)]",
title: "font-bold text-(--vscode-foreground)",
indicator:
"text-[var(--vscode-foreground)] mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
"text-(--vscode-foreground) mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
}}>
<ul style={ulStyle}>
<li>
+13 -13
View File
@@ -70,7 +70,7 @@
"noControlCharactersInRegex": "off",
"noShadowRestrictedNames": "off",
"noArrayIndexKey": "info",
"noAssignInExpressions": "warn"
"noAssignInExpressions": "info"
},
"complexity": {
"noUselessConstructor": "off",
@@ -82,7 +82,7 @@
"noStaticOnlyClass": "off"
},
"security": {
"noDangerouslySetInnerHtml": "warn"
"noDangerouslySetInnerHtml": "info"
}
}
},
@@ -114,17 +114,17 @@
"files": {
"includes": [
"**",
"!**/dist/**",
"!**/dist-*/**",
"!**/out/**",
"!**/evals/**",
"!**/playwright/**",
"!**/test-results/**",
"!**/node_modules/**",
"!**/webview-ui/build/**",
"!**/generated/**",
"!**/proto/**",
"!**/tests/specs/**"
"!**/dist",
"!**/dist-*",
"!**/out",
"!**/evals",
"!**/playwright",
"!**/test-results",
"!**/node_modules",
"!**/webview-ui/build",
"!**/generated",
"!**/proto",
"!**/tests/specs"
]
},
"plugins": [
+2 -1
View File
@@ -42,7 +42,8 @@ h5,
h6,
img {
opacity: 1 !important;
font-family: "Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
font-family:
"Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
}
/* Also apply to any h1 elements within content areas */
+36 -33
View File
@@ -1,7 +1,7 @@
import * as path from "path"
import * as fs from "fs"
import execa from "execa"
import chalk from "chalk"
import execa from "execa"
import * as fs from "fs"
import * as path from "path"
import { BenchmarkAdapter, Task, VerificationResult } from "./types"
const EVALS_DIR = path.resolve(__dirname, "../../../")
@@ -23,7 +23,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
console.log(`Cloning Exercism repository to ${exercismDir}...`)
await execa("git", ["clone", "https://github.com/Aider-AI/polyglot-benchmark.git", exercismDir])
console.log("Exercism repository cloned successfully")
// Unskip all JavaScript and Java tests after cloning
this.unskipAllJavaScriptTests(exercismDir)
this.unskipAllJavaTests(exercismDir)
@@ -34,7 +34,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
console.log("Pulling latest changes...")
await execa("git", ["pull"], { cwd: exercismDir })
console.log("Repository updated successfully")
// Unskip tests again after pulling
this.unskipAllJavaScriptTests(exercismDir)
this.unskipAllJavaTests(exercismDir)
@@ -137,7 +137,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
// Read config.json to get solution and test files
const configPath = path.join(task.workspacePath, ".meta", "config.json")
let config: any = { files: { solution: [], test: [] } }
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, "utf-8"))
}
@@ -159,7 +159,8 @@ export class ExercismAdapter implements BenchmarkAdapter {
const solutionFiles = config.files.solution || []
const fileList = solutionFiles.join(", ")
description += `\n\nUse the above instructions to modify the supplied files: ${fileList}. Don't change the names of existing functions or classes, as they may be referenced from other code like unit tests, etc. Only use standard libraries, don't suggest installing any packages.`
description += " You should ignore all test or test related files in this directory. The final test file has been removed and will be used to evaluate your work after your implementation is complete. Think deeply about the problem prior to working on the implementation. Consider all edge cases and test your solution prior to finalizing."
description +=
" You should ignore all test or test related files in this directory. The final test file has been removed and will be used to evaluate your work after your implementation is complete. Think deeply about the problem prior to working on the implementation. Consider all edge cases and test your solution prior to finalizing."
// Move test files to temp directory
if (config.files.test) {
@@ -313,7 +314,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
const cppAllPassedMatch = output.match(/All tests passed \(.*?(\d+) test cases?\)/)
const cppTestCasesMatch = output.match(/test cases?: (\d+) \| (\d+) passed/)
const cppFailedMatch = output.match(/(\d+) failed/)
if (cppAllPassedMatch) {
// All tests passed - extract total test cases
testsPassed = parseInt(cppAllPassedMatch[1])
@@ -322,7 +323,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
// Mixed results - extract passed count and calculate failed
const totalTests = parseInt(cppTestCasesMatch[1])
testsPassed = parseInt(cppTestCasesMatch[2])
testsFailed = cppFailedMatch ? parseInt(cppFailedMatch[1]) : (totalTests - testsPassed)
testsFailed = cppFailedMatch ? parseInt(cppFailedMatch[1]) : totalTests - testsPassed
}
break
@@ -434,14 +435,14 @@ export class ExercismAdapter implements BenchmarkAdapter {
*/
private unskipAllJavaScriptTests(repoPath: string): void {
const jsDir = path.join(repoPath, "javascript", "exercises", "practice")
if (!fs.existsSync(jsDir)) {
console.log("JavaScript exercises directory not found, skipping test unskipping")
return
}
// Walk through all exercise directories
const exercises = fs.readdirSync(jsDir).filter(dir => {
const exercises = fs.readdirSync(jsDir).filter((dir) => {
const fullPath = path.join(jsDir, dir)
return fs.statSync(fullPath).isDirectory()
})
@@ -449,25 +450,25 @@ export class ExercismAdapter implements BenchmarkAdapter {
let filesModified = 0
for (const exercise of exercises) {
const exerciseDir = path.join(jsDir, exercise)
// Find all .spec.js files
const files = fs.readdirSync(exerciseDir).filter(file => file.endsWith('.spec.js'))
const files = fs.readdirSync(exerciseDir).filter((file) => file.endsWith(".spec.js"))
for (const file of files) {
const filePath = path.join(exerciseDir, file)
let content = fs.readFileSync(filePath, 'utf-8')
let content = fs.readFileSync(filePath, "utf-8")
const originalContent = content
// Replace xtest with test to unskip tests
content = content.replace(/xtest\(/g, 'test(')
content = content.replace(/xtest\(/g, "test(")
if (content !== originalContent) {
fs.writeFileSync(filePath, content)
filesModified++
}
}
}
console.log(`Unskipped tests in ${filesModified} JavaScript test files`)
}
@@ -477,14 +478,14 @@ export class ExercismAdapter implements BenchmarkAdapter {
*/
private unskipAllJavaTests(repoPath: string): void {
const javaDir = path.join(repoPath, "java", "exercises", "practice")
if (!fs.existsSync(javaDir)) {
console.log("Java exercises directory not found, skipping test unskipping")
return
}
// Walk through all exercise directories
const exercises = fs.readdirSync(javaDir).filter(dir => {
const exercises = fs.readdirSync(javaDir).filter((dir) => {
const fullPath = path.join(javaDir, dir)
return fs.statSync(fullPath).isDirectory()
})
@@ -492,29 +493,29 @@ export class ExercismAdapter implements BenchmarkAdapter {
let filesModified = 0
for (const exercise of exercises) {
const testDir = path.join(javaDir, exercise, "src", "test", "java")
if (!fs.existsSync(testDir)) {
continue
}
// Find all .java test files
const files = fs.readdirSync(testDir).filter(file => file.endsWith('.java'))
const files = fs.readdirSync(testDir).filter((file) => file.endsWith(".java"))
for (const file of files) {
const filePath = path.join(testDir, file)
let content = fs.readFileSync(filePath, 'utf-8')
let content = fs.readFileSync(filePath, "utf-8")
const originalContent = content
// Remove @Disabled("Remove to run test") annotations
content = content.replace(/@Disabled\("Remove to run test"\)\s*\n/g, '')
content = content.replace(/@Disabled\("Remove to run test"\)\s*\n/g, "")
if (content !== originalContent) {
fs.writeFileSync(filePath, content)
filesModified++
}
}
}
console.log(`Unskipped tests in ${filesModified} Java test files`)
}
@@ -597,7 +598,9 @@ export class ExercismAdapter implements BenchmarkAdapter {
const duration = Date.now() - startTime
console.log(
chalk.green(`Task completed in ${(duration / 1000).toFixed(1)}s after ${attempts} attempt${attempts > 1 ? "s" : ""}`),
chalk.green(
`Task completed in ${(duration / 1000).toFixed(1)}s after ${attempts} attempt${attempts > 1 ? "s" : ""}`,
),
)
return finalVerification
+1 -1
View File
@@ -1,5 +1,5 @@
import { BenchmarkAdapter } from "./types"
import { ExercismAdapter } from "./exercism"
import { BenchmarkAdapter } from "./types"
// Registry of all available adapters
const adapters: Record<string, BenchmarkAdapter> = {
+2 -2
View File
@@ -1,7 +1,7 @@
import * as fs from "fs"
import * as path from "path"
import chalk from "chalk"
import * as fs from "fs"
import ora from "ora"
import * as path from "path"
import { ResultsDatabase } from "../db"
import { generateMarkdownReport } from "../utils/markdown"
+3 -7
View File
@@ -1,6 +1,6 @@
import { v4 as uuidv4 } from "uuid"
import chalk from "chalk"
import ora from "ora"
import { v4 as uuidv4 } from "uuid"
import { getAdapter } from "../adapters"
import { ResultsDatabase } from "../db"
import { storeTaskResult } from "../utils/results"
@@ -73,15 +73,11 @@ export async function runHandler(options: RunOptions): Promise<void> {
if (verification.success) {
console.log(
chalk.green(
`Tests passed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal}`,
),
chalk.green(`Tests passed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal}`),
)
} else {
console.log(
chalk.red(
`Tests failed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal}`,
),
chalk.red(`Tests failed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal}`),
)
}
+2 -2
View File
@@ -1,6 +1,6 @@
import * as path from "path"
import * as fs from "fs"
import Database from "better-sqlite3"
import * as fs from "fs"
import * as path from "path"
import { SCHEMA } from "./schema"
const EVALS_DIR = path.resolve(__dirname, "../../../")
+9 -9
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env node
import { Command } from "commander"
import chalk from "chalk"
import { setupHandler } from "./commands/setup"
import { runHandler } from "./commands/run"
import { Command } from "commander"
import { reportHandler } from "./commands/report"
import { runHandler } from "./commands/run"
import { runDiffEvalHandler } from "./commands/runDiffEval"
import { setupHandler } from "./commands/setup"
// Create the CLI program
const program = new Command()
@@ -16,11 +16,7 @@ program.name("cline-eval").description("CLI tool for orchestrating Cline evaluat
program
.command("setup")
.description("Clone and set up benchmark repositories")
.option(
"-b, --benchmarks <benchmarks>",
"Comma-separated list of benchmarks to set up",
"exercism",
)
.option("-b, --benchmarks <benchmarks>", "Comma-separated list of benchmarks to set up", "exercism")
.action(async (options) => {
try {
await setupHandler(options)
@@ -68,7 +64,11 @@ program
.option("--output-path <path>", "Path to the directory to save the test output JSON files")
.option("--model-ids <model_ids>", "Comma-separated list of model IDs to test")
.option("--system-prompt-name <name>", "The name of the system prompt to use", "basicSystemPrompt")
.option("-n, --valid-attempts-per-case <number>", "Number of valid attempts per test case per model (will retry until this many valid attempts are collected)", "1")
.option(
"-n, --valid-attempts-per-case <number>",
"Number of valid attempts per test case per model (will retry until this many valid attempts are collected)",
"1",
)
.option("--max-attempts-per-case <number>", "Maximum total attempts per test case (default: 10x valid attempts)")
.option("--max-cases <number>", "Maximum number of test cases to run (limits total cases loaded)")
.option("--parsing-function <name>", "The parsing function to use", "parseAssistantMessageV2")
+1 -5
View File
@@ -6,11 +6,7 @@ import * as fs from "fs"
* @param benchmarkReports Benchmark-specific reports
* @param outputPath Output file path
*/
export function generateMarkdownReport(
summary: any,
benchmarkReports: Record<string, any>,
outputPath: string,
): void {
export function generateMarkdownReport(summary: any, benchmarkReports: Record<string, any>, outputPath: string): void {
let markdown = `# Cline Evaluation Report\n\n`
// Generate summary section
+43 -43
View File
@@ -1,45 +1,45 @@
{
"name": "cline-evals",
"version": "0.1.0",
"description": "Evaluation scripts and tools for Cline",
"main": "cli/dist/index.js",
"scripts": {
"build:cli": "cd cli && tsc",
"start:cli": "cd cli && node dist/index.js",
"dev:cli": "cd cli && ts-node src/index.ts",
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cline",
"evaluation",
"benchmark",
"diff-edits"
],
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^1.12.0",
"better-sqlite3": "^11.10.0",
"chalk": "5.6.2",
"dotenv": "^16.5.0",
"commander": "^9.4.1",
"execa": "^5.1.1",
"node-fetch": "^2.7.0",
"ora": "^5.4.1",
"sqlite": "^4.1.2",
"tiktoken": "^1.0.21",
"uuid": "^9.0.0",
"yargs": "^17.6.2",
"cline": "^1.0.1"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.3",
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.12",
"@types/uuid": "^9.0.0",
"@types/yargs": "^17.0.19",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
"name": "cline-evals",
"version": "0.1.0",
"description": "Evaluation scripts and tools for Cline",
"main": "cli/dist/index.js",
"scripts": {
"build:cli": "cd cli && tsc",
"start:cli": "cd cli && node dist/index.js",
"dev:cli": "cd cli && ts-node src/index.ts",
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cline",
"evaluation",
"benchmark",
"diff-edits"
],
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^1.12.0",
"better-sqlite3": "^11.10.0",
"chalk": "5.6.2",
"dotenv": "^16.5.0",
"commander": "^9.4.1",
"execa": "^5.1.1",
"node-fetch": "^2.7.0",
"ora": "^5.4.1",
"sqlite": "^4.1.2",
"tiktoken": "^1.0.21",
"uuid": "^9.0.0",
"yargs": "^17.6.2",
"cline": "^1.0.1"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.3",
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.12",
"@types/uuid": "^9.0.0",
"@types/yargs": "^17.0.19",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
}
+1173 -14
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -437,6 +437,7 @@
"@sap-ai-sdk/orchestration": "^1.17.0",
"@sentry/browser": "^9.12.0",
"@streamparser/json": "^0.0.22",
"@tailwindcss/vite": "^4.1.14",
"@types/uuid": "^10.0.0",
"@vscode/codicons": "^0.0.36",
"archiver": "^7.0.1",
@@ -481,6 +482,7 @@
"serialize-error": "^11.0.3",
"simple-git": "^3.27.0",
"strip-ansi": "^7.1.2",
"tailwindcss": "^4.1.14",
"tree-sitter-wasms": "^0.1.11",
"ts-morph": "^25.0.1",
"turndown": "^7.2.0",
Binary file not shown.
+2 -2
View File
@@ -221,8 +221,8 @@ export class AuthService {
})
}
async createAuthRequest(): Promise<String> {
if (this._authenticated) {
async createAuthRequest(force = true): Promise<String> {
if (this._authenticated && !force) {
this.sendAuthStatusUpdate()
return String.create({ value: "Already authenticated" })
}
+3 -2
View File
@@ -43,7 +43,8 @@ e2e("Views - can set up API keys and navigate to Settings from Chat", async ({ s
await expect(providerSelectorInput).not.toBeVisible()
// Verify you are now in the chat page after setup was completed
const clineLogo = sidebar.getByRole("img").filter({ hasText: /^$/ }).locator("path")
// cline logo has the name "clline-logo"
const clineLogo = sidebar.locator(".size-20 > path")
await expect(clineLogo).toBeVisible()
const chatInputBox = sidebar.getByTestId("chat-input")
await expect(chatInputBox).toBeVisible()
@@ -53,6 +54,6 @@ e2e("Views - can set up API keys and navigate to Settings from Chat", async ({ s
name: /^🎉 New in v\d/,
})
await expect(releaseBanner).toBeVisible()
await sidebar.getByTestId("close-button").locator("span").first().click()
await sidebar.getByTestId("close-announcement-button").click()
await expect(releaseBanner).not.toBeVisible()
})
+7 -6
View File
@@ -17,7 +17,7 @@ e2e("Chat - can send messages and switch between modes", async ({ helper, sideba
await expect(sidebar.getByText("API Request...")).toBeVisible()
// Starting a new task should clear the current chat view and show the recent tasks
await sidebar.getByRole("button", { name: "New Task" }).click()
await sidebar.getByRole("button", { name: "New Task", exact: true }).first().click()
await expect(sidebar.getByText("Recent Tasks")).toBeVisible()
await expect(sidebar.getByText("Hello, Cline!")).toBeVisible()
@@ -26,12 +26,13 @@ e2e("Chat - can send messages and switch between modes", async ({ helper, sideba
const actButton = sidebar.getByRole("switch", { name: "Act" })
const planButton = sidebar.getByRole("switch", { name: "Plan" })
await expect(actButton).toBeChecked()
await expect(planButton).not.toBeChecked()
// Act button should be active. It doesn't have c
await expect(actButton).toHaveAttribute("aria-checked", "true")
await expect(planButton).not.toHaveAttribute("aria-checked", "true")
await actButton.click()
await expect(actButton).not.toBeChecked()
await expect(planButton).toBeChecked()
await planButton.click()
await expect(planButton).toHaveAttribute("aria-checked", "true")
await expect(actButton).not.toHaveAttribute("aria-checked", "true")
// === slash commands preserve following text ===
await expect(inputbox).toHaveValue("")
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.mjs",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
+918 -24
View File
File diff suppressed because it is too large Load Diff
+15 -2
View File
@@ -20,7 +20,18 @@
"@fontsource/azeret-mono": "^5.2.9",
"@heroui/react": "^2.8.0-beta.2",
"@paper-design/shaders-react": "^0.0.46",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.8",
"@vscode/webview-ui-toolkit": "^1.4.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"debounce": "^2.1.1",
"dompurify": "^3.2.4",
"fast-deep-equal": "^3.1.3",
@@ -43,12 +54,14 @@
"rehype-remark": "^10.0.1",
"remark-stringify": "^11.0.0",
"styled-components": "^6.1.15",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5",
"uuid": "^9.0.1"
},
"devDependencies": {
"@storybook/react-vite": "^9.1.6",
"@tailwindcss/vite": "^4.1.4",
"@tailwindcss/vite": "^4.1.13",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
@@ -64,7 +77,7 @@
"globals": "^15.14.0",
"jsdom": "^26.0.0",
"storybook": "^9.1.6",
"tailwindcss": "^4.1.5",
"tailwindcss": "^4.1.13",
"typescript": "^5.7.3",
"vite": "^6.3.6",
"vitest": "^3.0.5"
+3 -2
View File
@@ -4,8 +4,8 @@ import AccountView from "./components/account/AccountView"
import ChatView from "./components/chat/ChatView"
import HistoryView from "./components/history/HistoryView"
import McpView from "./components/mcp/configuration/McpConfigurationView"
import OnboardingView from "./components/onboarding/OnboardingView"
import SettingsView from "./components/settings/SettingsView"
import WelcomeView from "./components/welcome/WelcomeView"
import { useClineAuth } from "./context/ClineAuthContext"
import { useExtensionState } from "./context/ExtensionStateContext"
import { Providers } from "./Providers"
@@ -22,6 +22,7 @@ const AppContent = () => {
showHistory,
showAccount,
showAnnouncement,
setShowWelcome,
setShowAnnouncement,
setShouldShowAnnouncement,
closeMcpView,
@@ -54,7 +55,7 @@ const AppContent = () => {
}
if (showWelcome) {
return <WelcomeView />
return <OnboardingView onDone={hideAccount} showOnboarding={setShowWelcome} />
}
return (
@@ -6,11 +6,12 @@ import { VSCodeButton, VSCodeDivider, VSCodeDropdown, VSCodeOption, VSCodeTag }
import deepEqual from "fast-deep-equal"
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useInterval } from "react-use"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { type ClineUser, handleSignOut } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { cn } from "@/lib/utils"
import { AccountServiceClient } from "@/services/grpc-client"
import { getEnvironmentColor } from "@/utils/environmentColors"
import HeroTooltip from "../common/HeroTooltip"
import { getClineEnvironmentClassname } from "@/utils/environmentColors"
import VSCodeButtonLink from "../common/VSCodeButtonLink"
import { updateSetting } from "../settings/utils/settingsHandlers"
import { AccountWelcomeView } from "./AccountWelcomeView"
@@ -43,17 +44,17 @@ const ClineEnvOptions = ["Production", "Staging", "Local"] as const
const AccountView = ({ onDone, clineUser, organizations, activeOrganization }: AccountViewProps) => {
const { environment } = useExtensionState()
const titleColor = getEnvironmentColor(environment)
const titleColor = getClineEnvironmentClassname(environment)
return (
<div className="fixed inset-0 flex flex-col overflow-hidden pt-[10px] pl-[20px]">
<div className="flex justify-between items-center mb-[17px] pr-[17px]">
<h3 className="m-0" style={{ color: titleColor }}>
<h3 className={cn("text-(--vscode-foreground) m-0", titleColor)}>
Account {environment !== "production" ? ` - ${environment} environment` : ""}
</h3>
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
</div>
<div className="flex-grow overflow-hidden pr-[8px] flex flex-col">
<div className="grow overflow-hidden pr-[8px] flex flex-col">
<div className="h-full mb-1.5">
{clineUser?.uid ? (
<ClineAccountView
@@ -317,21 +318,19 @@ export const ClineAccountView = ({ clineUser, userOrganizations, activeOrganizat
{/* {user.photoUrl ? (
<img src={user.photoUrl} alt="Profile" className="size-16 rounded-full mr-4" />
) : ( */}
<div className="size-16 rounded-full bg-button-background flex items-center justify-center text-2xl text-[var(--vscode-button-foreground)] mr-4">
<div className="size-16 rounded-full bg-button-background flex items-center justify-center text-2xl text-button-foreground mr-4">
{displayName?.[0] || email?.[0] || "?"}
</div>
{/* )} */}
<div className="flex flex-col">
{displayName && (
<h2 className="text-foreground m-0 text-lg font-medium flex items-center">{displayName}</h2>
)}
{displayName && <h2 className="text-foreground m-0 text-lg font-medium">{displayName}</h2>}
{email && <div className="text-sm text-[var(--vscode-descriptionForeground)]">{email}</div>}
{email && <div className="text-sm text-description">{email}</div>}
<div className="flex gap-2 items-center mt-1">
{isLockedByRemoteConfig ? (
<HeroTooltip content="This cannot be changed while your organization has remote configuration enabled.">
<Tooltip>
<TooltipTrigger>
<VSCodeDropdown
className="w-full"
currentValue={dropdownValue}
@@ -346,23 +345,11 @@ export const ClineAccountView = ({ clineUser, userOrganizations, activeOrganizat
</VSCodeOption>
))}
</VSCodeDropdown>
</HeroTooltip>
) : (
<VSCodeDropdown
className="w-full"
currentValue={dropdownValue}
disabled={isLoading}
onChange={handleOrganizationChange}>
<VSCodeOption key="personal" value={uid}>
Personal
</VSCodeOption>
{userOrganizations?.map((org: UserOrganization) => (
<VSCodeOption key={org.organizationId} value={org.organizationId}>
{org.name}
</VSCodeOption>
))}
</VSCodeDropdown>
)}
</TooltipTrigger>
<TooltipContent hidden={isLockedByRemoteConfig}>
This cannot be changed while your organization has remote configuration enabled.
</TooltipContent>
</Tooltip>
{activeOrganization && (
<VSCodeTag className="text-xs p-2" title="Role">
{getMainRole(activeOrganization.roles)}
@@ -396,7 +383,7 @@ export const ClineAccountView = ({ clineUser, userOrganizations, activeOrganizat
<VSCodeDivider className="mt-6 mb-3 w-full" />
<div className="flex-grow flex flex-col min-h-0 pb-[0px]">
<div className="grow flex flex-col min-h-0 pb-[0px]">
<CreditsHistoryTable
isLoading={isLoading}
paymentsData={paymentsData}
@@ -7,7 +7,7 @@ export const AccountWelcomeView = () => {
const { environment } = useExtensionState()
return (
<div className="flex flex-col items-center pr-3">
<div className="flex flex-col items-center pr-3 gap-2.5">
<ClineLogoVariable className="size-16 mb-4" environment={environment} />
<p>
@@ -19,7 +19,7 @@ export const AccountWelcomeView = () => {
Sign up with Cline
</VSCodeButton>
<p className="text-[var(--vscode-descriptionForeground)] text-xs text-center m-0">
<p className="text-description text-xs text-center m-0">
By continuing, you agree to the <VSCodeLink href="https://cline.bot/tos">Terms of Service</VSCodeLink> and{" "}
<VSCodeLink href="https://cline.bot/privacy">Privacy Policy.</VSCodeLink>
</p>
@@ -15,11 +15,9 @@ export const CreditBalance = ({ balance, fetchCreditBalance, creditUrl, lastFetc
<div
className="w-full flex flex-col items-center"
title={`Last updated: ${new Date(lastFetchTime).toLocaleTimeString()}`}>
<div className="text-sm text-[var(--vscode-descriptionForeground)] mb-3 font-azeret-mono font-light">
CURRENT BALANCE
</div>
<div className="text-sm text-(--vscode-descriptionForeground) mb-3 font-azeret-mono font-light">CURRENT BALANCE</div>
<div className="text-4xl font-bold text-[var(--vscode-foreground)] mb-6 flex items-center gap-2">
<div className="font-bold text-2xl mb-6 flex items-center gap-2">
{balance === null ? <span>----</span> : <StyledCreditDisplay balance={balance} />}
<VSCodeButton
appearance="icon"
@@ -15,9 +15,9 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments
const [activeTab, setActiveTab] = useState<"usage" | "payments">("usage")
return (
<div className="flex flex-col flex-grow h-full">
<div className="flex flex-col grow h-full">
{/* Tabs container */}
<div className="flex border-b border-[var(--vscode-panel-border)]">
<div className="flex border-b border-(--vscode-panel-border)">
<TabButton isActive={activeTab === "usage"} onClick={() => setActiveTab("usage")}>
USAGE HISTORY
</TabButton>
@@ -29,10 +29,10 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments
</div>
{/* Content container */}
<div className="mt-[15px] mb-[0px] rounded-md overflow-auto flex-grow">
<div className="mt-[15px] mb-[0px] rounded-md overflow-auto grow">
{isLoading ? (
<div className="flex justify-center items-center p-4">
<div className="text-[var(--vscode-descriptionForeground)]">Loading...</div>
<div className="text-(--vscode-descriptionForeground)">Loading...</div>
</div>
) : (
<>
@@ -55,6 +55,7 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments
</VSCodeDataGridRow>
{usageData.map((row, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: use index as key
<VSCodeDataGridRow key={index}>
<VSCodeDataGridCell grid-column="1">
{formatTimestamp(row.createdAt)}
@@ -67,7 +68,7 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments
</VSCodeDataGrid>
) : (
<div className="flex justify-center items-center p-4">
<div className="text-[var(--vscode-descriptionForeground)]">No usage history</div>
<div className="text-(--vscode-descriptionForeground)">No usage history</div>
</div>
))}
@@ -88,6 +89,7 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments
</VSCodeDataGridRow>
{paymentsData.map((row, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: use index as key
<VSCodeDataGridRow key={index}>
<VSCodeDataGridCell grid-column="1">{formatTimestamp(row.paidAt)}</VSCodeDataGridCell>
<VSCodeDataGridCell grid-column="2">{`$${formatDollars(row.amountCents)}`}</VSCodeDataGridCell>
@@ -97,7 +99,7 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData, showPayments
</VSCodeDataGrid>
) : (
<div className="flex justify-center items-center p-4">
<div className="text-[var(--vscode-descriptionForeground)]">No payment history</div>
<div className="text-(--vscode-descriptionForeground)">No payment history</div>
</div>
))}
</>
@@ -52,9 +52,9 @@ export const StyledCreditDisplay = ({ balance }: { balance: number }) => {
const lastTwoDecimals = decimalPart.slice(2)
return (
<span className="font-azeret-mono font-light tabular-nums">
<span className="font-azeret-mono font-light tabular-nums text-2xl">
{wholePart}.{firstTwoDecimals}
<span className="text-[var(--vscode-descriptionForeground)]">{lastTwoDecimals}</span>
<span className="text-description text-2xl">{lastTwoDecimals}</span>
</span>
)
}
@@ -163,7 +163,7 @@ export const BrowserSettingsMenu = () => {
{showInfoPopover && (
// InfoPopover - Dropdown container with connection details
<div
className="absolute top-[30px] right-0 z-[100] w-[60dvw] max-w-[250px] rounded p-2.5 shadow-lg"
className="absolute top-[30px] right-0 z-100 w-[60dvw] max-w-[250px] rounded p-2.5 shadow-lg"
ref={popoverRef}
style={{
backgroundColor: "var(--vscode-editorWidget-background)",
+16 -63
View File
@@ -1,12 +1,10 @@
import { EmptyRequest } from "@shared/proto/cline/common"
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { CSSProperties, memo, useState } from "react"
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { XIcon } from "lucide-react"
import { CSSProperties, memo } from "react"
import { useMount } from "react-use"
import { useClineAuth } from "@/context/ClineAuthContext"
import { Button } from "@/components/ui/button"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient } from "@/services/grpc-client"
import { getAsVar, VSC_DESCRIPTION_FOREGROUND, VSC_INACTIVE_SELECTION_BACKGROUND } from "@/utils/vscStyles"
import { useApiConfigurationHandlers } from "../settings/utils/useApiConfigurationHandlers"
interface AnnouncementProps {
version: string
@@ -21,8 +19,7 @@ const containerStyle: CSSProperties = {
position: "relative",
flexShrink: 0,
}
const closeIconStyle: CSSProperties = { position: "absolute", top: "8px", right: "8px" }
const h3TitleStyle: CSSProperties = { margin: "0 0 8px", fontWeight: "bold" }
const h4TitleStyle: CSSProperties = { margin: "0 0 8px", fontWeight: "bold" }
const ulStyle: CSSProperties = { margin: "0 0 8px", paddingLeft: "12px", listStyleType: "disc" }
const _accountIconStyle: CSSProperties = { fontSize: 11 }
const hrStyle: CSSProperties = {
@@ -41,67 +38,23 @@ Patch releases (3.19.1 → 3.19.2) will not trigger new announcements.
*/
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
const { clineUser } = useClineAuth()
const { openRouterModels, setShowChatModelSelector, refreshOpenRouterModels } = useExtensionState()
const user = clineUser || undefined
const { handleFieldsChange } = useApiConfigurationHandlers()
const [didClickGrokCodeButton, setDidClickGrokCodeButton] = useState(false)
const [didClickCodeSupernovaButton, setDidClickCodeSupernovaButton] = useState(false)
const { refreshOpenRouterModels } = useExtensionState()
// Need to get latest model list in case user hits shortcut button to set model
useMount(refreshOpenRouterModels)
const setGrokCodeFast1 = () => {
const modelId = "x-ai/grok-code-fast-1"
// set both plan and act modes to use grok-code-fast-1
handleFieldsChange({
planModeOpenRouterModelId: modelId,
actModeOpenRouterModelId: modelId,
planModeOpenRouterModelInfo: openRouterModels[modelId],
actModeOpenRouterModelInfo: openRouterModels[modelId],
planModeApiProvider: "cline",
actModeApiProvider: "cline",
})
setTimeout(() => {
setDidClickGrokCodeButton(true)
setShowChatModelSelector(true)
}, 10)
}
const setCodeSupernova = () => {
const modelId = "cline/code-supernova-1-million"
// set both plan and act modes to use code-supernova-1-million
handleFieldsChange({
planModeOpenRouterModelId: modelId,
actModeOpenRouterModelId: modelId,
planModeOpenRouterModelInfo: openRouterModels[modelId],
actModeOpenRouterModelInfo: openRouterModels[modelId],
planModeApiProvider: "cline",
actModeApiProvider: "cline",
})
setTimeout(() => {
setDidClickCodeSupernovaButton(true)
setShowChatModelSelector(true)
}, 10)
}
const handleShowAccount = () => {
AccountServiceClient.accountLoginClicked(EmptyRequest.create()).catch((err) =>
console.error("Failed to get login URL:", err),
)
}
return (
<div style={containerStyle}>
<VSCodeButton appearance="icon" data-testid="close-button" onClick={hideAnnouncement} style={closeIconStyle}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
<h3 style={h3TitleStyle}>
<Button
className="absolute top-2.5 right-2"
data-testid="close-announcement-button"
onClick={hideAnnouncement}
size="icon"
variant="icon">
<XIcon />
</Button>
<h4 style={h4TitleStyle}>
🎉{" "}New in v{minorVersion}
</h3>
</h4>
<ul style={ulStyle}>
<li>
Cline Teams is now free through the end of the year for unlimited users. Includes Jetbrains, RBAC, centralized
@@ -8,7 +8,6 @@ import { useSize } from "react-use"
import styled from "styled-components"
import { BrowserSettingsMenu } from "@/components/browser/BrowserSettingsMenu"
import { ChatRowContent, ProgressIndicator } from "@/components/chat/ChatRow"
import { CheckpointControls } from "@/components/common/CheckpointControls"
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { FileServiceClient } from "@/services/grpc-client"
@@ -630,10 +629,6 @@ const BrowserCursor: React.FC<{ style?: CSSProperties }> = ({ style }) => {
const BrowserSessionRowContainer = styled.div`
padding: 10px 6px 10px 15px;
position: relative;
&:hover ${CheckpointControls} {
opacity: 1;
}
`
export default BrowserSessionRow
+8 -8
View File
@@ -30,9 +30,9 @@ import McpResourceRow from "@/components/mcp/configuration/tabs/installed/server
import McpToolRow from "@/components/mcp/configuration/tabs/installed/server-row/McpToolRow"
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { cn } from "@/lib/utils"
import { FileServiceClient, TaskServiceClient, UiServiceClient } from "@/services/grpc-client"
import { findMatchingResourceOrTemplate, getMcpServerDisplayName } from "@/utils/mcp"
import { CheckpointControls } from "../common/CheckpointControls"
import CodeAccordian, { cleanPathPrefix } from "../common/CodeAccordian"
import { ErrorBlockTitle } from "./ErrorBlockTitle"
import ErrorRow from "./ErrorRow"
@@ -50,10 +50,6 @@ const _cancelledColor = "var(--vscode-descriptionForeground)"
const ChatRowContainer = styled.div`
padding: 10px 6px 10px 15px;
position: relative;
&:hover ${CheckpointControls} {
opacity: 1;
}
`
interface ChatRowProps {
@@ -1239,6 +1235,10 @@ export const ChatRowContent = memo(
{title}
{/* Need to render this every time since it affects height of row by 2px */}
<VSCodeBadge
className={cn("text-sm", {
"opacity-100": cost != null && cost > 0,
"opacity-0": cost == null || cost <= 0,
})}
style={{
opacity: cost != null && cost > 0 ? 1 : 0,
}}>
@@ -1794,7 +1794,7 @@ export const ChatRowContent = memo(
}
return (
<>
<div>
{title && (
<div style={headerStyle}>
{icon}
@@ -1802,10 +1802,10 @@ export const ChatRowContent = memo(
</div>
)}
<WithCopyButton
className="pt-2.5"
onMouseUp={handleMouseUp}
position="bottom-right"
ref={contentRef}
style={{ paddingTop: 10 }}
textToCopy={question}>
<Markdown markdown={question} />
<OptionsButtons
@@ -1827,7 +1827,7 @@ export const ChatRowContent = memo(
/>
)}
</WithCopyButton>
</>
</div>
)
case "new_task":
return (
+72 -73
View File
@@ -1,4 +1,3 @@
import { cn } from "@heroui/react"
import { PulsingBorder } from "@paper-design/shaders-react"
import { mentionRegex, mentionRegexGlobal } from "@shared/context-mentions"
import { EmptyRequest, StringRequest } from "@shared/proto/cline/common"
@@ -19,12 +18,13 @@ import { CHAT_CONSTANTS } from "@/components/chat/chat-view/constants"
import SlashCommandMenu from "@/components/chat/SlashCommandMenu"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import Thumbnails from "@/components/common/Thumbnails"
import Tooltip from "@/components/common/Tooltip"
import ApiOptions from "@/components/settings/ApiOptions"
import { getModeSpecificFields, normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useClineAuth } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { usePlatform } from "@/context/PlatformContext"
import { cn } from "@/lib/utils"
import { FileServiceClient, ModelsServiceClient, StateServiceClient } from "@/services/grpc-client"
import {
ContextMenuOptionType,
@@ -102,22 +102,6 @@ interface GitCommit {
const PLAN_MODE_COLOR = "var(--vscode-activityWarningBadge-background)"
const ACT_MODE_COLOR = "var(--vscode-focusBorder)"
const SwitchOption = styled.div.withConfig({
shouldForwardProp: (prop) => !["isActive"].includes(prop),
})<{ isActive: boolean }>`
padding: 2px 8px;
color: ${(props) => (props.isActive ? "white" : "var(--vscode-input-foreground)")};
z-index: 1;
transition: color 0.2s ease;
font-size: 12px;
width: 50%;
text-align: center;
&:hover {
background-color: ${(props) => (!props.isActive ? "var(--vscode-toolbar-hoverBackground)" : "transparent")};
}
`
const SwitchContainer = styled.div<{ disabled: boolean }>`
display: flex;
align-items: center;
@@ -589,13 +573,13 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}
// Find the index of the next selectable option
const currentSelectableIndex = selectableOptions.findIndex((option) => option === options[prevIndex])
const currentSelectableIndex = selectableOptions.indexOf(options[prevIndex])
const newSelectableIndex =
(currentSelectableIndex + direction + selectableOptions.length) % selectableOptions.length
// Find the index of the selected option in the original options array
return options.findIndex((option) => option === selectableOptions[newSelectableIndex])
return options.indexOf(selectableOptions[newSelectableIndex])
})
return
}
@@ -1622,7 +1606,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
value={inputValue}
/>
{!inputValue && selectedImages.length === 0 && selectedFiles.length === 0 && (
<div className="text-[10px] absolute bottom-5 left-5 right-16 text-[var(--vscode-input-placeholderForeground)]/50 whitespace-nowrap overflow-hidden text-ellipsis pointer-events-none z-1">
<div className="text-xs absolute bottom-5 left-6.5 right-16 text-(--vscode-input-placeholderForeground)/50 whitespace-nowrap overflow-hidden text-ellipsis pointer-events-none z-1">
Type @ for context, / for slash commands & workflows, hold shift to drag in files/images
</div>
)}
@@ -1708,38 +1692,47 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
<div className="relative flex-1 min-w-0 h-5">
{/* ButtonGroup - always in DOM but visibility controlled */}
<ButtonGroup className="absolute top-0 left-0 right-0 ease-in-out w-full h-5 z-10 flex items-center">
<Tooltip style={{ left: 0 }} tipText="Add Context">
<VSCodeButton
appearance="icon"
aria-label="Add Context"
className="p-0 m-0 flex items-center"
data-testid="context-button"
onClick={handleContextButtonClick}>
<ButtonContainer>
<AtSignIcon size={12} />
</ButtonContainer>
</VSCodeButton>
<Tooltip>
<TooltipContent>Add Context</TooltipContent>
<TooltipTrigger>
<VSCodeButton
appearance="icon"
aria-label="Add Context"
className="p-0 m-0 flex items-center mt-0.5"
data-testid="context-button"
onClick={handleContextButtonClick}>
<ButtonContainer>
<AtSignIcon size={12} />
</ButtonContainer>
</VSCodeButton>
</TooltipTrigger>
</Tooltip>
<Tooltip tipText="Add Files & Images">
<VSCodeButton
appearance="icon"
aria-label="Add Files & Images"
className="p-0 m-0 flex items-center"
data-testid="files-button"
disabled={shouldDisableFilesAndImages}
onClick={() => {
if (!shouldDisableFilesAndImages) {
onSelectFilesAndImages()
}
}}>
<ButtonContainer>
<PlusIcon size={13} />
</ButtonContainer>
</VSCodeButton>
<Tooltip>
<TooltipContent>Add Files & Images</TooltipContent>
<TooltipTrigger>
<VSCodeButton
appearance="icon"
aria-label="Add Files & Images"
className="p-0 m-0 flex items-center mt-0.5"
data-testid="files-button"
disabled={shouldDisableFilesAndImages}
onClick={() => {
if (!shouldDisableFilesAndImages) {
onSelectFilesAndImages()
}
}}>
<ButtonContainer>
<PlusIcon size={13} />
</ButtonContainer>
</VSCodeButton>
</TooltipTrigger>
</Tooltip>
<ServersToggleModal />
<ClineRulesToggleModal />
<ModelContainer ref={modelSelectorRef}>
<ModelButtonWrapper ref={buttonRef}>
<ModelDisplayButton
@@ -1749,7 +1742,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
role="button"
tabIndex={0}
title="Select Model / API Provider">
<ModelButtonContent>{modelDisplayName}</ModelButtonContent>
<ModelButtonContent className="text-xs">{modelDisplayName}</ModelButtonContent>
</ModelDisplayButton>
</ModelButtonWrapper>
{showModelSelector && (
@@ -1772,30 +1765,36 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</ButtonGroup>
</div>
{/* Tooltip for Plan/Act toggle remains outside the conditional rendering */}
<Tooltip
hintText={`Toggle w/ ${togglePlanActKeys}`}
style={{ zIndex: 1000 }}
tipText={`In ${shownTooltipMode === "act" ? "Act" : "Plan"} mode, Cline will ${shownTooltipMode === "act" ? "complete the task immediately" : "gather information to architect a plan"}`}
visible={shownTooltipMode !== null}>
<SwitchContainer data-testid="mode-switch" disabled={false} onClick={onModeToggle}>
<Slider isAct={mode === "act"} isPlan={mode === "plan"} />
<SwitchOption
aria-checked={mode === "plan"}
isActive={mode === "plan"}
onMouseLeave={() => setShownTooltipMode(null)}
onMouseOver={() => setShownTooltipMode("plan")}
role="switch">
Plan
</SwitchOption>
<SwitchOption
aria-checked={mode === "act"}
isActive={mode === "act"}
onMouseLeave={() => setShownTooltipMode(null)}
onMouseOver={() => setShownTooltipMode("act")}
role="switch">
Act
</SwitchOption>
</SwitchContainer>
<Tooltip>
<TooltipContent
className="text-xs px-2 flex flex-col gap-1"
hidden={shownTooltipMode === null}
side="top">
{`In ${shownTooltipMode === "act" ? "Act" : "Plan"} mode, Cline will ${shownTooltipMode === "act" ? "complete the task immediately" : "gather information to architect a plan"}`}
<p className="text-description/80 text-xs mb-0">
Toggle w/ <kbd className="text-muted-foreground mx-1">{togglePlanActKeys}</kbd>
</p>
</TooltipContent>
<TooltipTrigger>
<SwitchContainer data-testid="mode-switch" disabled={false} onClick={onModeToggle}>
<Slider isAct={mode === "act"} isPlan={mode === "plan"} />
{["Plan", "Act"].map((m) => (
<div
aria-checked={mode === m.toLowerCase()}
// isActive={mode === m.toLowerCase()}
className={cn(
"py-0.5 px-2 z-10 text-xs w-1/2 text-center",
mode === m.toLowerCase() ? "text-white" : "text-input-foreground",
mode === m.toLowerCase() ? "bg-transparent" : "bg-toolbar-hover",
)}
onMouseLeave={() => setShownTooltipMode(null)}
onMouseOver={() => setShownTooltipMode(m.toLowerCase() === "plan" ? "plan" : "act")}
role="switch">
{m}
</div>
))}
</SwitchContainer>
</TooltipTrigger>
</Tooltip>
</div>
</div>
+1 -1
View File
@@ -366,7 +366,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
/>
)}
</div>
<footer className="bg-[var(--vscode-sidebar-background)]" style={{ gridRow: "2" }}>
<footer className="bg-(--vscode-sidebar-background)" style={{ gridRow: "2" }}>
<AutoApproveBar />
<ActionButtons
chatState={chatState}
@@ -50,7 +50,7 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
// We have to divide because the balance is stored in microcredits
return (
<div className="p-2 border-none rounded-md mb-2 bg-[var(--vscode-textBlockQuote-background)]">
<div className="p-2 border-none rounded-md mb-2 bg-(--vscode-textBlockQuote-background)">
<div className="mb-3 font-azeret-mono">
<div className="text-error mb-2">{message}</div>
<div className="mb-3">
@@ -29,14 +29,14 @@ export const ErrorBlockTitle = ({
const icon =
apiReqCancelReason != null ? (
apiReqCancelReason === "user_cancelled" ? (
getIconSpan("error", "text-[var(--vscode-descriptionForeground)]")
getIconSpan("error", "text-(--vscode-descriptionForeground)")
) : (
getIconSpan("error", "text-[var(--vscode-errorForeground)]")
getIconSpan("error", "text-(--vscode-errorForeground)")
)
) : cost != null ? (
getIconSpan("check", "text-[var(--vscode-charts-green)]")
getIconSpan("check", "text-(--vscode-charts-green)")
) : apiRequestFailedMessage ? (
getIconSpan("error", "text-[var(--vscode-errorForeground)]")
getIconSpan("error", "text-(--vscode-errorForeground)")
) : (
<ProgressIndicator />
)
@@ -47,24 +47,24 @@ export const ErrorBlockTitle = ({
// Handle cancellation states first
if (apiReqCancelReason === "user_cancelled") {
details.title = "API Request Cancelled"
details.classNames.push("text-[var(--vscode-foreground)]")
details.classNames.push("text-(--vscode-foreground)")
} else if (apiReqCancelReason != null) {
details.title = "API Request Failed"
details.classNames.push("text-[var(--vscode-errorForeground)]")
details.classNames.push("text-(--vscode-errorForeground)")
} else if (cost != null) {
// Handle completed request
details.title = "API Request"
details.classNames.push("text-[var(--vscode-foreground)]")
details.classNames.push("text-(--vscode-foreground)")
} else if (apiRequestFailedMessage) {
// Handle failed request
const clineError = ClineError.parse(apiRequestFailedMessage)
const titleText = clineError?.isErrorType(ClineErrorType.Balance) ? "Credit Limit Reached" : "API Request Failed"
details.title = titleText
details.classNames.push("font-bold text-[var(--vscode-errorForeground)]")
details.classNames.push("font-bold text-(--vscode-errorForeground)")
} else if (retryStatus) {
// Handle retry state
details.title = "API Request"
details.classNames.push("text-[var(--vscode-foreground)]")
details.classNames.push("text-(--vscode-foreground)")
}
return <span className={details.classNames.join(" ")}>{details.title}</span>
+6 -10
View File
@@ -47,7 +47,7 @@ const ErrorRow = memo(({ message, errorType, apiRequestFailedMessage, apiReqStre
if (clineError?.isErrorType(ClineErrorType.RateLimit)) {
return (
<p className="m-0 whitespace-pre-wrap text-[var(--vscode-errorForeground)] wrap-anywhere">
<p className="m-0 whitespace-pre-wrap text-(--vscode-errorForeground) wrap-anywhere">
{clineErrorMessage}
{requestId && <div>Request ID: {requestId}</div>}
</p>
@@ -61,7 +61,7 @@ const ErrorRow = memo(({ message, errorType, apiRequestFailedMessage, apiReqStre
// Default error display
return (
<p className="m-0 whitespace-pre-wrap text-[var(--vscode-errorForeground)] wrap-anywhere">
<p className="m-0 whitespace-pre-wrap text-(--vscode-errorForeground) wrap-anywhere">
{errorMessageToDisplay}
{requestId && <div>Request ID: {requestId}</div>}
{clineErrorMessage?.toLowerCase()?.includes("powershell") && (
@@ -83,9 +83,7 @@ const ErrorRow = memo(({ message, errorType, apiRequestFailedMessage, apiReqStre
<br />
{/* The user is signed in or not using cline provider */}
{clineUser && !isClineProvider ? (
<span className="mb-4 text-[var(--vscode-descriptionForeground)]">
(Click "Retry" below)
</span>
<span className="mb-4 text-(--vscode-descriptionForeground)">(Click "Retry" below)</span>
) : (
<VSCodeButton className="w-full mb-4" onClick={handleSignIn}>
Sign in to Cline
@@ -98,20 +96,18 @@ const ErrorRow = memo(({ message, errorType, apiRequestFailedMessage, apiReqStre
}
// Regular error message
return (
<p className="m-0 whitespace-pre-wrap text-[var(--vscode-errorForeground)] wrap-anywhere">{message.text}</p>
)
return <p className="m-0 whitespace-pre-wrap text-(--vscode-errorForeground) wrap-anywhere">{message.text}</p>
case "diff_error":
return (
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-[var(--vscode-textBlockQuote-background)] text-[var(--vscode-foreground)]">
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-(--vscode-textBlockQuote-background) text-(--vscode-foreground)">
<div>The model used search patterns that don't match anything in the file. Retrying...</div>
</div>
)
case "clineignore_error":
return (
<div className="flex flex-col p-2 rounded text-xs bg-[var(--vscode-textBlockQuote-background)] text-[var(--vscode-foreground)] opacity-80">
<div className="flex flex-col p-2 rounded text-xs bg-(--vscode-textBlockQuote-background) text-(--vscode-foreground) opacity-80">
<div>
Cline tried to access <code>{message.text}</code> which is blocked by the <code>.clineignore</code>
file.
@@ -7,7 +7,7 @@ interface NewTaskPreviewProps {
const NewTaskPreview: React.FC<NewTaskPreviewProps> = ({ context }) => {
return (
<div className="bg-[var(--vscode-badge-background)] text-[var(--vscode-badge-foreground)] rounded-[3px] p-[14px] pb-[6px]">
<div className="bg-(--vscode-badge-background) text-(--vscode-badge-foreground) rounded-[3px] p-[14px] pb-[6px]">
<span style={{ fontWeight: "bold" }}>Task</span>
<MarkdownBlock markdown={context} />
</div>
@@ -28,7 +28,7 @@ const ContentRow = styled.div`
`
const TextContainer = styled.div`
flex-grow: 1;
grow: 1;
margin: 0 2px; /* Further reduced space around text */
white-space: pre-wrap;
word-break: break-word;
@@ -45,7 +45,7 @@ const TextContainer = styled.div`
const DismissButton = styled(VSCodeButton)`
/* margin-left: auto; */ /* Removed as ContentRow handles spacing */
flex-shrink: 0; /* Prevent button from shrinking */
shrink: 0; /* Prevent button from shrinking */
min-width: 22px;
height: 22px;
padding: 0;
@@ -58,7 +58,7 @@ const DismissButton = styled(VSCodeButton)`
const ReplyIcon = styled.span`
color: var(--vscode-descriptionForeground);
margin-right: 2px; /* Further reduced space between icon and text */
flex-shrink: 0;
shrink: 0;
font-size: 13px; /* Make icon even smaller */
/* transform: translateY(-1px); */ /* Removed vertical transform */
`
@@ -17,7 +17,7 @@ const ReportBugPreview: React.FC<ReportBugPreviewProps> = ({ data }) => {
}, [data])
return (
<div className="bg-[var(--vscode-badge-background)] text-[var(--vscode-badge-foreground)] rounded-[3px] p-[14px]">
<div className="bg-(--vscode-badge-background) text-(--vscode-badge-foreground) rounded-[3px] p-[14px]">
<h3 className="font-bold text-base mb-3 mt-0">{bugData.title || "Bug Report"}</h3>
<div className="space-y-3 text-sm">
@@ -2,39 +2,16 @@ import { EmptyRequest } from "@shared/proto/cline/common"
import { McpServers } from "@shared/proto/cline/mcp"
import { convertProtoMcpServersToMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import React, { useEffect, useRef, useState } from "react"
import { useClickAway, useWindowSize } from "react-use"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import Tooltip from "@/components/common/Tooltip"
import React, { useEffect, useState } from "react"
import ServersToggleList from "@/components/mcp/configuration/tabs/installed/ServersToggleList"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { McpServiceClient } from "@/services/grpc-client"
const ServersToggleModal: React.FC = () => {
const { mcpServers, navigateToMcp, setMcpServers } = useExtensionState()
const [isVisible, setIsVisible] = useState(false)
const buttonRef = useRef<HTMLDivElement>(null)
const modalRef = useRef<HTMLDivElement>(null)
const { width: viewportWidth, height: viewportHeight } = useWindowSize()
const [arrowPosition, setArrowPosition] = useState(0)
const [menuPosition, setMenuPosition] = useState(0)
// Close modal when clicking outside
useClickAway(modalRef, () => {
setIsVisible(false)
})
// Calculate positions for modal and arrow
useEffect(() => {
if (isVisible && buttonRef.current) {
const buttonRect = buttonRef.current.getBoundingClientRect()
const buttonCenter = buttonRect.left + buttonRect.width / 2
const rightPosition = document.documentElement.clientWidth - buttonCenter - 5
setArrowPosition(rightPosition)
setMenuPosition(buttonRect.top + 1)
}
}, [isVisible, viewportWidth, viewportHeight])
useEffect(() => {
if (isVisible) {
@@ -52,61 +29,49 @@ const ServersToggleModal: React.FC = () => {
}, [isVisible])
return (
<div ref={modalRef}>
<div className="inline-flex min-w-0 max-w-full" ref={buttonRef}>
<Tooltip tipText="Manage MCP Servers" visible={isVisible ? false : undefined}>
<VSCodeButton
appearance="icon"
aria-label={isVisible ? "Hide MCP Servers" : "Show MCP Servers"}
onClick={() => setIsVisible(!isVisible)}
style={{ padding: "0px 0px", height: "20px" }}>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<span
className="codicon codicon-server flex items-center"
style={{ fontSize: "12.5px", marginBottom: 1 }}
/>
</div>
</VSCodeButton>
</Tooltip>
</div>
<Tooltip>
<TooltipContent hidden={isVisible} side="top">
Manage MCP Servers
</TooltipContent>
<TooltipTrigger>
<VSCodeButton
appearance="icon"
aria-label={isVisible ? "Hide MCP Servers" : "Show MCP Servers"}
className="p-0 m-0 flex items-center mt-1 cursor-pointer hover:cursor-pointer">
<Popover onOpenChange={(open) => setIsVisible(open)}>
<PopoverTrigger>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<i className="codicon codicon-server flex items-center" style={{ fontSize: "12.5px" }} />
</div>
</PopoverTrigger>
{isVisible && (
<div
className="fixed left-[15px] right-[15px] border border-[var(--vscode-editorGroup-border)] p-3 rounded z-[1000] overflow-y-auto"
style={{
bottom: `calc(100vh - ${menuPosition}px + 6px)`,
background: CODE_BLOCK_BG_COLOR,
maxHeight: "calc(100vh - 100px)",
overscrollBehavior: "contain",
}}>
<div
className="fixed w-[10px] h-[10px] z-[-1] rotate-45 border-r border-b border-[var(--vscode-editorGroup-border)]"
style={{
bottom: `calc(100vh - ${menuPosition}px)`,
right: arrowPosition,
background: CODE_BLOCK_BG_COLOR,
}}
/>
<PopoverContent className="mx-3 h-full wrap-break-word text-foreground w-[94vw]" side="top">
<div className="flex justify-between items-center mb-2.5">
<div className="m-0 text-base font-semibold">MCP Servers</div>
<VSCodeButton
appearance="icon"
aria-label="Go to MCP server settings"
onClick={() => {
setIsVisible(false)
navigateToMcp("configure")
}}>
<span className="codicon codicon-gear text-[10px]"></span>
</VSCodeButton>
</div>
<div className="flex justify-between items-center mb-2.5">
<div className="m-0 text-base font-semibold">MCP Servers</div>
<VSCodeButton
appearance="icon"
aria-label="Go to MCP server settings"
onClick={() => {
setIsVisible(false)
navigateToMcp("configure")
}}>
<span className="codicon codicon-gear text-[10px]"></span>
</VSCodeButton>
</div>
<div style={{ marginBottom: -10 }}>
<ServersToggleList hasTrashIcon={false} isExpandable={false} listGap="small" servers={mcpServers} />
</div>
</div>
)}
</div>
<div style={{ marginBottom: -10 }}>
<ServersToggleList
hasTrashIcon={false}
isExpandable={false}
listGap="small"
servers={mcpServers}
/>
</div>
</PopoverContent>
</Popover>
</VSCodeButton>{" "}
</TooltipTrigger>
</Tooltip>
)
}
@@ -58,18 +58,18 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
return (
<>
<div className="text-xs text-[var(--vscode-descriptionForeground)] px-3 py-1 font-bold border-b border-[var(--vscode-editorGroup-border)]">
<div className="text-xs text-(--vscode-descriptionForeground) px-3 py-1 font-bold border-b border-(--vscode-editorGroup-border)">
{title}
</div>
{commands.map((command, index) => {
const itemIndex = index + indexOffset
return (
<div
className={`slash-command-menu-item py-2 px-3 cursor-pointer flex flex-col border-b border-[var(--vscode-editorGroup-border)] ${
className={`slash-command-menu-item py-2 px-3 cursor-pointer flex flex-col border-b border-(--vscode-editorGroup-border) ${
itemIndex === selectedIndex
? "bg-[var(--vscode-quickInputList-focusBackground)] text-[var(--vscode-quickInputList-focusForeground)]"
? "bg-(--vscode-quickInputList-focusBackground) text-(--vscode-quickInputList-focusForeground)"
: ""
} hover:bg-[var(--vscode-list-hoverBackground)]`}
} hover:bg-(--vscode-list-hoverBackground)`}
id={`slash-command-menu-item-${itemIndex}`}
key={command.name}
onClick={() => handleClick(command)}
@@ -78,7 +78,7 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
<span className="ph-no-capture">/{command.name}</span>
</div>
{showDescriptions && command.description && (
<div className="text-[0.85em] text-[var(--vscode-descriptionForeground)] whitespace-normal overflow-hidden text-ellipsis">
<div className="text-[0.85em] text-(--vscode-descriptionForeground) whitespace-normal overflow-hidden text-ellipsis">
<span className="ph-no-capture">{command.description}</span>
</div>
)}
@@ -91,10 +91,10 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
return (
<div
className="absolute bottom-[calc(100%-10px)] left-[15px] right-[15px] overflow-x-hidden z-[1000]"
className="absolute bottom-[calc(100%-10px)] left-[15px] right-[15px] overflow-x-hidden z-1000"
onMouseDown={onMouseDown}>
<div
className="bg-[var(--vscode-dropdown-background)] border border-[var(--vscode-editorGroup-border)] rounded-[3px] shadow-[0_4px_10px_rgba(0,0,0,0.25)] flex flex-col overflow-y-auto"
className="bg-(--vscode-dropdown-background) border border-(--vscode-editorGroup-border) rounded-[3px] shadow-[0_4px_10px_rgba(0,0,0,0.25)] flex flex-col overflow-y-auto"
ref={menuRef}
style={{ maxHeight: "min(200px, calc(50vh))", overscrollBehavior: "contain" }}>
{filteredCommands.length > 0 ? (
@@ -104,7 +104,7 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
</>
) : (
<div className="py-2 px-3 cursor-default flex flex-col">
<div className="text-[0.85em] text-[var(--vscode-descriptionForeground)]">No matching commands found</div>
<div className="text-[0.85em] text-(--vscode-descriptionForeground)">No matching commands found</div>
</div>
)}
</div>
@@ -1,10 +1,12 @@
import { cn } from "@heroui/react"
import { TranscribeAudioRequest } from "@shared/proto/cline/dictation"
import { EmptyRequest } from "@shared/proto/index.cline"
import { SquareIcon, StopCircleIcon } from "lucide-react"
import React, { useCallback, useEffect, useRef, useState } from "react"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { DictationServiceClient } from "@/services/grpc-client"
import { formatSeconds } from "@/utils/format"
import HeroTooltip from "../common/HeroTooltip"
interface VoiceRecorderProps {
onTranscription: (text: string) => void
@@ -222,7 +224,6 @@ const VoiceRecorder: React.FC<VoiceRecorderProps> = ({
stopRecording()
}, [stopRecording, disabled, isProcessing])
const iconAnimation = isProcessing || isStarting ? "animate-spin" : ""
const iconAdjustment = isProcessing || isStarting ? "mt-0" : error ? "mt-1" : "mt-0.5"
// When not recording, show single mic button
if (!isRecording) {
@@ -240,40 +241,64 @@ const VoiceRecorder: React.FC<VoiceRecorderProps> = ({
? "Starting recording..."
: error
? `Error: ${error}`
: null
: "Voice Input"
return (
<HeroTooltip content={tooltipContent} placement="top">
<div
className={`input-icon-button mr-1.5 text-base ${iconAdjustment} ${iconAnimation} ${disabled || isProcessing || isStarting ? "disabled" : ""}`}
onClick={handleStartClick}
style={{ color: iconColor }}>
<span className={`codicon ${iconClass}`} />
</div>
</HeroTooltip>
<Tooltip>
<TooltipTrigger asChild>
<div
className={cn("pt-1 input-icon-button mr-1.5 text-base", iconAdjustment, {
disabled: disabled || isProcessing || isStarting,
"animate-spin": isProcessing || isStarting,
})}
data-testid="voice-recorder-start-button"
onClick={handleStartClick}
style={{ color: iconColor }}>
<span className={`codicon ${iconClass}`} />
</div>
</TooltipTrigger>
<TooltipContent side="top">{tooltipContent}</TooltipContent>
</Tooltip>
)
}
return (
<div className={`flex items-center ${isRecording ? "mr-0.5" : "mr-1.5"}`}>
<HeroTooltip
content={`Stop Recording (${formatSeconds(recordingDuration)}/${formatSeconds(MAX_DURATION)})`}
placement="top">
<div
className={cn("input-icon-button mr-1.5 text-error", iconAdjustment, iconAnimation, {
disabled: disabled || isProcessing,
})}
onClick={handleStopClick}>
<span className="codicon codicon-stop-circle" />
</div>
</HeroTooltip>
<HeroTooltip content="Cancel Recording" placement="top">
<div
className={`input-icon-button text-base ${isRecording ? "mt-0.5" : "mt-1"} text-foreground ${disabled || isProcessing ? "disabled" : ""}`}
onClick={handleCancelClick}>
<span className="codicon codicon-close" />
</div>
</HeroTooltip>
<div className={cn("flex items-center mb-2", { "mr-0.5": isRecording, "mr-1.5": !isRecording })}>
<Tooltip>
<TooltipTrigger asChild>
<Button
className={cn("input-icon-button p-1 m-0 mr-1.5 text-base", iconAdjustment, {
disabled: disabled || isProcessing,
"animate-spin": isProcessing || isStarting,
})}
data-testid="stop-recording-button"
onClick={handleStopClick}
size="sm"
variant="icon">
<StopCircleIcon />
</Button>
</TooltipTrigger>
<TooltipContent side="top">
Stop Recording ({formatSeconds(recordingDuration)}/{formatSeconds(MAX_DURATION)})
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
className={cn("input-icon-button p-1 m-0 text-base text-error", iconAdjustment, {
"animate-spin": isProcessing || isStarting,
disabled: disabled || isProcessing,
})}
data-testid="cancel-recording-button"
onClick={handleCancelClick}
size="sm"
variant="icon">
<SquareIcon />
</Button>
</TooltipTrigger>
<TooltipContent side="top">Cancel Recording</TooltipContent>
</Tooltip>
</div>
)
}
@@ -85,7 +85,7 @@ describe("Announcement", () => {
<Announcement hideAnnouncement={hideAnnouncement} version="2.0.0" />
</TestWrapper>,
)
fireEvent.click(screen.getByTestId("close-button"))
fireEvent.click(screen.getByTestId("close-announcement-button"))
expect(hideAnnouncement).toHaveBeenCalled()
})
})
@@ -58,12 +58,12 @@ const AutoApproveBar = ({ style }: AutoApproveBarProps) => {
return [
...favorites.map((favId) => renderFavoritedItem(favId)),
minusFavorites.length > 0 ? (
<span className="text-[color:var(--vscode-foreground-muted)] pl-[10px] opacity-60" key="separator">
<span className="text-muted opacity-60" key="separator">
</span>
) : null,
...minusFavorites.map((action, index) => (
<span className="text-[color:var(--vscode-foreground-muted)] opacity-60" key={action?.id}>
<span className="text-muted opacity-60" key={action?.id}>
{action?.shortName}
{index < minusFavorites.length - 1 && ","}
</span>
@@ -73,21 +73,20 @@ const AutoApproveBar = ({ style }: AutoApproveBarProps) => {
return (
<div
className="px-[10px] mx-[15px] select-none rounded-[10px_10px_0_0]"
className="px-3.5 mx-3.5 select-none rounded-tl-sm rounded-tr-sm overflow-y-auto break-words"
style={{
borderTop: `0.5px solid color-mix(in srgb, ${getAsVar(VSC_TITLEBAR_INACTIVE_FOREGROUND)} 20%, transparent)`,
overflowY: "auto",
backgroundColor: isModalVisible ? CODE_BLOCK_BG_COLOR : "transparent",
...style,
}}>
<div
className="cursor-pointer py-[8px] pr-[2px] flex items-center justify-between gap-[8px]"
className="cursor-pointer py-1 pr-1 flex items-center justify-between gap-2"
onClick={() => {
setIsModalVisible((prev) => !prev)
}}
ref={buttonRef}>
<div
className="flex flex-nowrap items-center overflow-x-auto gap-[4px] whitespace-nowrap"
className="flex flex-nowrap items-center overflow-x-auto gap-1 whitespace-nowrap"
style={{
msOverflowStyle: "none",
scrollbarWidth: "none",
@@ -1,7 +1,8 @@
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import React from "react"
import styled from "styled-components"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { ActionMetadata } from "./types"
interface AutoApproveMenuItemProps {
@@ -14,46 +15,6 @@ interface AutoApproveMenuItemProps {
showIcon?: boolean
}
const CheckboxContainer = styled.div.withConfig({
shouldForwardProp: (prop) => !["isFavorited"].includes(prop),
})<{ isFavorited?: boolean; onClick?: (e: MouseEvent) => void; onMouseDown?: (e: React.MouseEvent) => void }>`
display: flex;
align-items: center;
justify-content: space-between; /* Push content to edges */
padding-left: 4px;
padding-right: 1px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background-color: var(--vscode-textBlockQuote-background);
}
.left-content {
display: flex;
align-items: center;
gap: 8px;
}
.icon {
color: var(--vscode-foreground);
font-size: 14px;
}
.label {
color: var(--vscode-foreground);
font-size: 12px;
font-weight: 500;
}
.star {
color: ${(props) => (props.isFavorited ? "var(--vscode-terminal-ansiYellow)" : "var(--vscode-descriptionForeground)")};
opacity: ${(props) => (props.isFavorited ? 1 : 0.6)};
font-size: 14px;
}
`
const SubOptionAnimateIn = styled.div<{ show: boolean }>`
position: relative;
transform: ${(props) => (props.show ? "scaleY(1)" : "scaleY(0)")};
@@ -87,36 +48,44 @@ const AutoApproveMenuItem = ({
}
const content = (
<>
<ActionButtonContainer>
<HeroTooltip content={action.description} delay={500}>
<CheckboxContainer isFavorited={favorited} onClick={onChange}>
<div className="left-content">
<div className="w-full">
<ActionButtonContainer className="w-full">
<Tooltip>
<TooltipContent>{action.description}</TooltipContent>
<TooltipTrigger asChild>
<Button
className={cn("w-full flex text-sm items-center justify-start text-foreground gap-2")}
onClick={(e) => onChange(e as unknown as Event)}
size="icon"
variant="icon">
{onToggleFavorite && !condensed && (
<HeroTooltip
content={favorited ? "Remove from quick-access menu" : "Add to quick-access menu"}
delay={500}>
<span
className={`p-0.5 codicon codicon-${favorited ? "star-full" : "star-empty"} star`}
onClick={async (e) => {
e.stopPropagation()
if (action.id === "enableAll") {
return
}
await onToggleFavorite?.(action.id)
}}
style={{
cursor: "pointer",
}}
/>
</HeroTooltip>
<Tooltip>
<TooltipContent showArrow={false}>
{favorited ? "Remove from quick-access menu" : "Add to quick-access menu"}
</TooltipContent>
<TooltipTrigger asChild>
<span
className={cn("p-0.5 codicon", {
"codicon-star-full text-(--vscode-terminal-ansiYellow)": favorited,
"codicon-star-empty text-description opacity-60": !favorited,
})}
onClick={async (e) => {
e.stopPropagation()
if (action.id === "enableAll") {
return
}
await onToggleFavorite?.(action.id)
}}
/>
</TooltipTrigger>
</Tooltip>
)}
<VSCodeCheckbox checked={checked} />
{showIcon && <span className={`codicon ${action.icon} icon`}></span>}
<span className="label">{condensed ? action.shortName : action.label}</span>
</div>
</CheckboxContainer>
</HeroTooltip>
</Button>
</TooltipTrigger>
</Tooltip>
</ActionButtonContainer>
{action.subAction && !condensed && (
<SubOptionAnimateIn show={checked}>
@@ -129,7 +98,7 @@ const AutoApproveMenuItem = ({
/>
</SubOptionAnimateIn>
)}
</>
</div>
)
return content
@@ -1,8 +1,10 @@
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { XIcon } from "lucide-react"
import React, { useEffect, useRef, useState } from "react"
import { useClickAway, useWindowSize } from "react-use"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAutoApproveActions } from "@/hooks/useAutoApproveActions"
import { getAsVar, VSC_TITLEBAR_INACTIVE_FOREGROUND } from "@/utils/vscStyles"
@@ -125,35 +127,38 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({
return (
<div className="overflow-hidden" ref={modalRef}>
<div
className="fixed left-[15px] right-[15px] border border-[var(--vscode-editorGroup-border)] rounded z-[1000] flex flex-col"
className="fixed left-3.5 right-3.5 border border-(--vscode-editorGroup-border) rounded z-20 flex flex-col"
style={calculateModalStyle()}>
<div
className="fixed w-[10px] h-[10px] z-[-1] rotate-45 border-r border-b border-[var(--vscode-editorGroup-border)]"
className="fixed w-3.5 h-3.5 z-0 rotate-45 border-r border-b border-(--vscode-editorGroup-border) bg-code"
style={{
bottom: `calc(100vh - ${menuPosition}px)`,
right: arrowPosition,
background: CODE_BLOCK_BG_COLOR,
}}
/>
{/* Scrollable content container */}
<div className="overflow-y-auto p-3 flex-1 min-h-0 overscroll-contain">
<div className="flex justify-between items-center mb-3">
<HeroTooltip
content="Auto-approve allows Cline to perform the following actions without asking for permission. Please use with caution and only enable if you understand the risks."
placement="top">
<div className="text-base font-semibold mb-1">Auto-approve Settings</div>
</HeroTooltip>
<VSCodeButton appearance="icon" onClick={() => setIsVisible(false)}>
<span className="codicon codicon-close text-[10px]"></span>
</VSCodeButton>
<div className="overflow-y-auto p-3 flex-1 min-h-0 overscroll-contain w-full">
<div className="flex justify-between items-center mb-3 w-full">
<Tooltip>
<TooltipContent side="top">
Auto-approve allows Cline to perform the following actions without asking for permission. Please
use with caution and only enable if you understand the risks.
</TooltipContent>
<TooltipTrigger>
<div className="text-md font-semibold mb-1">Auto-approve Settings</div>
</TooltipTrigger>
</Tooltip>
<Button onClick={() => setIsVisible(false)} size="icon" variant="icon">
<XIcon />
</Button>
</div>
<div className="mb-2.5">
<span className="text-[color:var(--vscode-foreground)] font-medium">Actions:</span>
<span className="text-foreground font-medium">Actions:</span>
</div>
<div
className="relative mb-6"
className="relative mb-6 w-full"
ref={itemsContainerRef}
style={{
columnCount: containerWidth > breakpoint ? 2 : 1,
@@ -162,7 +167,7 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({
{/* Vertical separator line - only visible in two-column mode */}
{containerWidth > breakpoint && (
<div
className="absolute left-1/2 top-0 bottom-0 w-[0.5px] opacity-20"
className="absolute left-1/2 top-0 bottom-0 opacity-20"
style={{
background: getAsVar(VSC_TITLEBAR_INACTIVE_FOREGROUND),
transform: "translateX(-50%)", // Center the line
@@ -184,7 +189,7 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({
</div>
<div className="mb-2.5">
<span className="text-[color:var(--vscode-foreground)] font-medium">Quick Settings:</span>
<span className="font-medium">Quick Settings:</span>
</div>
<AutoApproveMenuItem
@@ -196,36 +201,40 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({
onToggleFavorite={toggleFavorite}
/>
<HeroTooltip
content="Cline will automatically make this many API requests before asking for approval to proceed with the task."
placement="top">
<div className="flex items-center pl-1.5 my-2">
<span className="codicon codicon-settings text-[#CCCCCC] text-[14px]" />
<span className="text-[#CCCCCC] text-xs font-medium ml-2">Max Requests:</span>
<VSCodeTextField
className="flex-1 w-full pr-[35px] ml-4"
onInput={async (e) => {
const input = e.target as HTMLInputElement
// Remove any non-numeric characters
input.value = input.value.replace(/[^0-9]/g, "")
const value = parseInt(input.value)
if (!Number.isNaN(value) && value > 0) {
await updateMaxRequests(value)
}
}}
onKeyDown={(e) => {
// Prevent non-numeric keys (except for backspace, delete, arrows)
if (
!/^\d$/.test(e.key) &&
!["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(e.key)
) {
e.preventDefault()
}
}}
value={autoApprovalSettings.maxRequests.toString()}
/>
</div>
</HeroTooltip>
<Tooltip>
<TooltipContent side="top">
Cline will automatically make this many API requests before asking for approval to proceed with the
task.
</TooltipContent>
<TooltipTrigger>
<div className="flex items-center pl-1.5 my-2">
<span className="codicon codicon-settings text-[#CCCCCC] text-[14px]" />
<span className="text-base font-medium ml-2">Max Requests:</span>
<VSCodeTextField
className="flex-1 w-full pr-[35px] ml-4"
onInput={async (e) => {
const input = e.target as HTMLInputElement
// Remove any non-numeric characters
input.value = input.value.replace(/[^0-9]/g, "")
const value = parseInt(input.value)
if (!Number.isNaN(value) && value > 0) {
await updateMaxRequests(value)
}
}}
onKeyDown={(e) => {
// Prevent non-numeric keys (except for backspace, delete, arrows)
if (
!/^\d$/.test(e.key) &&
!["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(e.key)
) {
e.preventDefault()
}
}}
value={autoApprovalSettings.maxRequests.toString()}
/>
</div>
</TooltipTrigger>
</Tooltip>
</div>
</div>
</div>
@@ -117,11 +117,11 @@ export const ActionButtons: React.FC<ActionButtonsProps> = ({
}
return (
<div className="flex px-[15px]">
<div className="flex px-3.5">
<VSCodeButton
appearance="icon"
aria-label={showScrollToBottom ? "Scroll to bottom" : "Scroll to top"}
className="text-lg text-[var(--vscode-primaryButton-foreground)] bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_55%,transparent)] rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_90%,transparent)] active:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_70%,transparent)] border-0"
className="text-lg text-(--vscode-primaryButton-foreground) bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_55%,transparent)] rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_90%,transparent)] active:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_70%,transparent)] border-0"
onClick={showScrollToBottom ? handleScrollToBottom : handleScrollToTop}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
@@ -146,11 +146,11 @@ export const ActionButtons: React.FC<ActionButtonsProps> = ({
const opacity = canInteract || isStreaming ? 1 : 0.5
return (
<div className="flex px-[15px]" style={{ opacity }}>
<div className="flex px-3.5" style={{ opacity }}>
{primaryText && primaryAction && (
<VSCodeButton
appearance="primary"
className={secondaryText ? "flex-1 mr-[6px]" : "flex-[2]"}
className={secondaryText ? "flex-1 mr-[6px]" : "flex-2"}
disabled={!canInteract}
onClick={() => handleActionClick(primaryAction, inputValue, selectedImages, selectedFiles)}>
{primaryText}
@@ -159,7 +159,7 @@ export const ActionButtons: React.FC<ActionButtonsProps> = ({
{secondaryText && secondaryAction && (
<VSCodeButton
appearance="secondary"
className={primaryText ? "flex-1" : "flex-[2]"}
className={primaryText ? "flex-1" : "flex-2"}
disabled={!canInteract}
onClick={() => handleActionClick(secondaryAction, inputValue, selectedImages, selectedFiles)}>
{secondaryText}
@@ -62,7 +62,7 @@ export const MessagesArea: React.FC<MessagesAreaProps> = ({
return (
<div className="overflow-hidden flex flex-col h-full">
<div className="flex-grow flex" ref={scrollContainerRef}>
<div className="grow flex" ref={scrollContainerRef}>
<Virtuoso
atBottomStateChange={(isAtBottom) => {
setIsAtBottom(isAtBottom)
@@ -1,13 +1,8 @@
import React from "react"
import Announcement from "@/components/chat/Announcement"
import CliInstallBanner, { CURRENT_CLI_BANNER_VERSION } from "@/components/common/CliInstallBanner"
import InfoBanner, { CURRENT_INFO_BANNER_VERSION } from "@/components/common/InfoBanner"
import HistoryPreview from "@/components/history/HistoryPreview"
import { WhatsNew } from "@/components/onboarding/WhatsNew"
import HomeHeader from "@/components/welcome/HomeHeader"
import { SuggestedTasks } from "@/components/welcome/SuggestedTasks"
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { isMacOSOrLinux } from "@/utils/platformUtils"
import { WelcomeSectionProps } from "../../types/chatTypes"
/**
@@ -22,25 +17,11 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
taskHistory,
shouldShowQuickWins,
}) => {
const { lastDismissedInfoBannerVersion, lastDismissedCliBannerVersion } = useExtensionState()
const shouldShowInfoBanner = lastDismissedInfoBannerVersion < CURRENT_INFO_BANNER_VERSION
// const shouldShowNewModelBanner = lastDismissedModelBannerVersion < CURRENT_MODEL_BANNER_VERSION
// Show CLI banner if not dismissed and platform is VSCode (not JetBrains/standalone)
const shouldShowCliBanner =
isMacOSOrLinux() &&
PLATFORM_CONFIG.type === PlatformType.VSCODE &&
lastDismissedCliBannerVersion < CURRENT_CLI_BANNER_VERSION
return (
<div className="flex flex-col flex-1 w-full h-full p-0 m-0">
<div className="overflow-y-auto flex flex-col pb-2.5">
{shouldShowInfoBanner && <InfoBanner />}
{showAnnouncement && <Announcement hideAnnouncement={hideAnnouncement} version={version} />}
{/* {shouldShowNewModelBanner && <NewModelBanner />} */}
{shouldShowCliBanner && <CliInstallBanner />}
<HomeHeader shouldShowQuickWins={shouldShowQuickWins} />
{showAnnouncement && <WhatsNew hideAnnouncement={hideAnnouncement} version={version} />}
{!shouldShowQuickWins && taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
</div>
<SuggestedTasks shouldShowQuickWins={shouldShowQuickWins} />
@@ -1,7 +1,8 @@
import { Alert } from "@heroui/react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { XIcon } from "lucide-react"
import { useMemo, useState } from "react"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
interface CheckpointErrorProps {
checkpointManagerErrorMessage?: string
@@ -28,7 +29,7 @@ export const CheckpointError: React.FC<CheckpointErrorProps> = ({
return (
<div className="flex items-center justify-center w-full">
<Alert
className="rounded-sm border-0 bg-[var(--vscode-inputValidation-errorBackground)] text-[var(--vscode-inputValidation-errorForeground)] pl-1 pr-1.5 py-1"
className="rounded-sm text-base h-fit bg-input-error-background text-input-error-foreground px-2 py-0 border border-foreground/30"
color="warning"
description={
<div className="flex gap-2">
@@ -49,16 +50,25 @@ export const CheckpointError: React.FC<CheckpointErrorProps> = ({
</div>
}
endContent={
<VSCodeButton
appearance="icon"
aria-label="Dismiss"
className="inline-flex opacity-100 hover:bg-transparent hover:opacity-60 p-0"
onClick={() => setDismissed(true)}
title="Dismiss Checkpoint Error">
<XIcon size={12} />
</VSCodeButton>
<Tooltip>
<TooltipContent side="left">Dismiss</TooltipContent>
<TooltipTrigger>
<Button
aria-label="Dismiss"
className="inline-flex"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setDismissed(true)
}}
size="icon"
variant="icon">
<XIcon />
</Button>
</TooltipTrigger>
</Tooltip>
}
hideIconWrapper={true}
hideIcon={true}
isVisible={!dismissed}
title={messages.message}
variant="faded"
@@ -1,8 +1,9 @@
import { cn, Progress, Tooltip } from "@heroui/react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import debounce from "debounce"
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { updateSetting } from "@/components/settings/utils/settingsHandlers"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Progress } from "@/components/ui/progress"
import { formatLargeNumber as formatTokenNumber } from "@/utils/format"
import { AutoCondenseMarker } from "./AutoCondenseMarker"
import CompactTaskButton from "./buttons/CompactTaskButton"
@@ -29,12 +30,12 @@ const ConfirmationDialog = memo<{
onConfirm: (e: React.MouseEvent) => void
onCancel: (e: React.MouseEvent) => void
}>(({ onConfirm, onCancel }) => (
<div className="text-xs my-2 flex items-center gap-0 justify-between">
<div className="text-sm my-2 flex items-center gap-0 justify-between">
<span className="font-semibold text-sm">Compact the current task?</span>
<span className="flex gap-1">
<VSCodeButton
appearance="secondary"
className="text-xs"
className="text-sm"
onClick={onCancel}
title="No, keep the task as is"
type="button">
@@ -43,7 +44,7 @@ const ConfirmationDialog = memo<{
<VSCodeButton
appearance="primary"
autoFocus={true}
className="text-xs"
className="text-sm"
onClick={onConfirm}
title="Yes, compact the task"
type="button">
@@ -205,14 +206,13 @@ const ContextWindow: React.FC<ContextWindowProgressProps> = ({
return (
<div className="flex flex-col my-1.5" onMouseLeave={debounceCloseHover}>
<div className="flex gap-1 flex-row @max-xs:flex-col @max-xs:items-start items-center text-sm">
<div className="flex items-center gap-1.5 flex-1 whitespace-nowrap text-xs">
<span className="cursor-pointer" title="Current tokens used in this request">
<div className="flex items-center gap-1.5 flex-1 whitespace-nowrap">
<span className="cursor-pointer text-sm" title="Current tokens used in this request">
{formatTokenNumber(tokenData.used)}
</span>
<div className="flex relative items-center gap-1 flex-1 w-full h-full" onMouseEnter={() => setIsOpened(true)}>
<Tooltip
closeDelay={0}
content={
<HoverCard>
<HoverCardContent className="bg-menu rounded-xs shadow-sm">
<ContextWindowSummary
autoCompactThreshold={useAutoCondense ? threshold : undefined}
cacheReads={cacheReads}
@@ -223,54 +223,40 @@ const ContextWindow: React.FC<ContextWindowProgressProps> = ({
tokensOut={tokensOut}
tokenUsed={tokenData.used}
/>
}
disableAnimation={true}
isOpen={isOpened}
offset={-2}
placement="bottom"
shouldCloseOnBlur={false}
shouldCloseOnInteractOutside={() => false}
showArrow={true}>
<div
aria-label="Auto condense threshold"
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={Math.round(threshold * 100)}
aria-valuetext={`${Math.round(threshold * 100)}% threshold`}
className="relative w-full text-foreground context-window-progress brightness-100"
onFocus={handleFocus}
onKeyDown={handleKeyDown}
ref={progressBarRef}
role="slider"
tabIndex={useAutoCondense ? 0 : -1}>
<Progress
aria-label="Context window usage progress"
classNames={{
base: "drop-shadow-md w-full cursor-pointer",
track: cn("rounded max-h-2 h-3 bg-foreground/10"),
indicator: "bg-foreground rounded-r",
label: "tracking-wider font-medium text-foreground/80",
value: "text-description",
}}
color="success"
disableAnimation={true}
onClick={handleContextWindowBarClick}
size="md"
value={tokenData.percentage}
/>
{useAutoCondense && (
<AutoCondenseMarker
isContextWindowHoverOpen={isOpened}
shouldAnimate={shouldAnimateMarker}
threshold={threshold}
usage={tokenData.percentage}
</HoverCardContent>
<HoverCardTrigger asChild>
<div
aria-label="Auto condense threshold"
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={Math.round(threshold * 100)}
aria-valuetext={`${Math.round(threshold * 100)}% threshold`}
className="relative w-full text-foreground context-window-progress brightness-100"
onFocus={handleFocus}
onKeyDown={handleKeyDown}
ref={progressBarRef}
role="slider"
tabIndex={useAutoCondense ? 0 : -1}>
<Progress
aria-label="Context window usage progress"
color="success"
onClick={handleContextWindowBarClick}
value={tokenData.percentage}
/>
)}
{isOpened}
</div>
</Tooltip>
{useAutoCondense && (
<AutoCondenseMarker
isContextWindowHoverOpen={isOpened}
shouldAnimate={shouldAnimateMarker}
threshold={threshold}
usage={tokenData.percentage}
/>
)}
{isOpened}
</div>
</HoverCardTrigger>
</HoverCard>
</div>
<span className="cursor-pointer" title="Maximum context window size for this model">
<span className="cursor-pointer text-sm" title="Maximum context window size for this model">
{formatTokenNumber(tokenData.max)}
</span>
</div>
@@ -42,17 +42,17 @@ const AccordionItem = memo<{
)
return (
<div className="flex flex-col">
<div className="flex flex-col w-full">
<div
className="flex justify-between items-center gap-3 cursor-pointer hover:bg-foreground/5 rounded px-1 py-0.5 transition-colors"
className="flex justify-between items-center gap-1 cursor-pointer hover:bg-foreground/5 rounded p-0.5 transition-colors w-full"
onClick={handleClick}>
<div className="flex items-center gap-1">
{isExpanded ? <ChevronDownIcon size={12} /> : <ChevronRightIcon size={12} />}
<div className="font-semibold text-sm">{title}</div>
<div className="font-semibold">{title}</div>
</div>
<div className="text-muted-foreground text-sm">{value}</div>
<div className="text-muted-foreground">{value}</div>
</div>
{isExpanded && children && <div className="ml-4 mt-2 mb-1 text-xs text-muted-foreground">{children}</div>}
{isExpanded && children && <div className="ml-5 my-1 text-xs text-muted-foreground">{children}</div>}
</div>
)
})
@@ -77,13 +77,10 @@ const TokenUsageDetails = memo<TokenUsageInfoProps>(({ tokensIn, tokensOut, cach
}
return (
<div className="space-y-2">
<div className="space-y-1">
{contextTokenDetails.map((item) => (
<div className="flex items-center justify-between" key={item.icon}>
<div className="flex items-center gap-1">
<i className={`codicon ${item.icon} text-xs`} />
<span>{item.title}</span>
</div>
<div className="flex justify-between">
<span>{item.title}</span>
<span className="font-mono">{formatTokenNumber(item.value || 0)}</span>
</div>
))}
@@ -124,7 +121,7 @@ export const ContextWindowSummary: React.FC<TaskContextWindowButtonsProps> = ({
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
return (
<div className="context-window-tooltip-content flex flex-col gap-2 bg-menu rounded shadow-sm border border-menu-border z-100 w-60 p-4">
<div className="context-window-tooltip-content flex flex-col gap-2 bg-menu rounded shadow-sm z-100 w-60 p-1">
{autoCompactThreshold > 0 && (
<AccordionItem
isExpanded={expandedSections.has("threshold")}
@@ -146,7 +143,7 @@ export const ContextWindowSummary: React.FC<TaskContextWindowButtonsProps> = ({
isExpanded={expandedSections.has("context")}
onToggle={(event) => toggleSection("context", event)}
title="Context Window"
value={percentage ? `${percentage.toFixed(1)}% used` : formatTokenNumber(contextWindow)}>
value={percentage ? `${percentage.toFixed(1)}%` : formatTokenNumber(contextWindow)}>
<div className="space-y-1">
<div className="flex justify-between">
<span>Used:</span>
@@ -168,7 +165,7 @@ export const ContextWindowSummary: React.FC<TaskContextWindowButtonsProps> = ({
isExpanded={expandedSections.has("tokens")}
onToggle={(event) => toggleSection("tokens", event)}
title="Token Usage"
value={`${formatTokenNumber(totalTokens)} total`}>
value={`${formatTokenNumber(totalTokens)}`}>
<TokenUsageDetails
cacheReads={cacheReads}
cacheWrites={cacheWrites}
@@ -55,17 +55,17 @@ const ToDoListHeader = memo<{
}}
/>
<div className="flex items-center justify-between gap-2 z-10 py-2.5 px-1.5">
<div className="flex items-center gap-1.5 flex-1 min-w-0">
<div className="flex items-center gap-1.5 flex-1 min-w-0 text-sm">
<span
className={cn(
"rounded-lg px-2 py-0.25 text-xs inline-block shrink-0 bg-badge-foreground/20 text-foreground",
"rounded-lg px-2 py-0.25 inline-block shrink-0 bg-badge-foreground/20 text-foreground text-sm",
{
"bg-success text-black": isCompleted,
},
)}>
{currentIndex}/{totalCount}
</span>
<div className="header-text text-xs font-medium break-words overflow-hidden text-ellipsis whitespace-nowrap max-w-[calc(100%-60px)]">
<div className="header-text text-sm font-medium break-words overflow-hidden text-ellipsis whitespace-nowrap max-w-[calc(100%-60px)]">
<LightMarkdown compact text={displayText} />
</div>
</div>
@@ -1,4 +1,3 @@
import { cn } from "@heroui/react"
import { ClineMessage } from "@shared/ExtensionMessage"
import { StringRequest } from "@shared/proto/cline/common"
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react"
@@ -6,6 +5,7 @@ import React, { useCallback, useMemo, useState } from "react"
import Thumbnails from "@/components/common/Thumbnails"
import { getModeSpecificFields, normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { cn } from "@/lib/utils"
import { UiServiceClient } from "@/services/grpc-client"
import { getEnvironmentColor } from "@/utils/environmentColors"
import CopyTaskButton from "./buttons/CopyTaskButton"
@@ -122,10 +122,10 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
{/* Task Header */}
<div
className={cn(
"relative overflow-hidden cursor-pointer rounded-sm flex flex-col gap-1.5 z-10 pt-2 pb-2 px-2 hover:opacity-100 bg-[var(--vscode-toolbar-hoverBackground)]/65",
"relative overflow-hidden cursor-pointer rounded-sm flex flex-col gap-1.5 z-10 pt-2 pb-2 px-2 hover:opacity-100 bg-(--vscode-toolbar-hoverBackground)/65",
{
"opacity-100 border-1": isTaskExpanded, // No hover effects when expanded, add border
"hover:bg-[var(--vscode-toolbar-hoverBackground)] border-1": !isTaskExpanded, // Hover effects only when collapsed
"hover:bg-(--vscode-toolbar-hoverBackground) border-1": !isTaskExpanded, // Hover effects only when collapsed
},
)}
style={{
@@ -136,7 +136,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
<div className="flex justify-between items-center">
{isTaskExpanded ? <ChevronDownIcon size="16" /> : <ChevronRightIcon size="16" />}
{isTaskExpanded && (
<div className="mt-1 max-h-3 flex justify-end flex-wrap cursor-pointer opacity-80">
<div className="mt-1 max-h-3 flex justify-end flex-wrap cursor-pointer opacity-80 gap-2 mx-2">
<CopyTaskButton className={BUTTON_CLASS} taskText={task.text} />
<DeleteTaskButton
className={BUTTON_CLASS}
@@ -150,19 +150,19 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
</div>
)}
</div>
<div className="flex items-center select-none flex-grow min-w-0 gap-1 justify-between">
<div className="flex items-center select-none grow min-w-0 gap-1 justify-between">
{!isTaskExpanded && (
<div className="text-sm whitespace-nowrap overflow-hidden text-ellipsis flex-grow min-w-0">
<span className="ph-no-capture">{highlightText(task.text, false)}</span>
<div className="whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
<span className="ph-no-capture text-base">{highlightText(task.text, false)}</span>
</div>
)}
</div>
<div className="inline-flex items-center justify-end select-none flex-shrink-0">
<div className="inline-flex items-center justify-end select-none shrink-0">
{isCostAvailable && (
<div
className="mr-1 px-1 py-0.25 rounded-full inline-flex shrink-0 text-badge-background bg-badge-foreground/80 items-center"
className="mx-1 px-1 py-0.25 rounded-full inline-flex shrink-0 text-badge-background bg-badge-foreground/80 items-center"
id="price-tag">
<span className="text-xs">${totalCost?.toFixed(4)}</span>
<span className="text-sm">${totalCost?.toFixed(4)}</span>
</div>
)}
<NewTaskButton className={BUTTON_CLASS} onClick={onClose} />
@@ -9,9 +9,8 @@ import { getColor } from "./util"
// Timeline dimensions and spacing
const TIMELINE_HEIGHT = "12px"
const BLOCK_WIDTH = "11px"
const BLOCK_WIDTH = "10px"
const BLOCK_GAP = "4px"
const _TOOLTIP_MARGIN = 32 // 32px margin on each side
interface TaskTimelineProps {
messages: ClineMessage[]
@@ -108,7 +107,7 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages, onBlockClick }) =
return (
<TaskTimelineTooltip message={message}>
<div
className="hover:brightness-120"
className="rounded-xs hover:brightness-120"
onClick={handleClick}
style={{
width: BLOCK_WIDTH,
@@ -117,7 +116,6 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages, onBlockClick }) =
flexShrink: 0,
cursor: "pointer",
marginRight: BLOCK_GAP,
borderRadius: 1.5,
}}
/>
</TaskTimelineTooltip>
@@ -154,7 +154,7 @@ const TaskTimelineTooltip = ({ message, children }: TaskTimelineTooltipProps) =>
return (
<Tooltip
classNames={{
base: "bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--vscode-widget-border)] py-1 rounded-[3px] max-w-[calc(100dvw-2rem)] text-xs",
base: "bg-(--vscode-editor-background) text-(--vscode-editor-foreground) border-(--vscode-widget-border) py-1 rounded-[3px] max-w-[calc(100dvw-2rem)] text-xs",
}}
closeDelay={100}
content={
@@ -1,34 +1,29 @@
import { cn, Tooltip } from "@heroui/react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { FoldVerticalIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
const CompactTaskButton: React.FC<{
className?: string
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
}> = ({ onClick, className }) => {
return (
<Tooltip
content={
<div className="flex flex-col gap-1 bg-menu rounded shadow-sm border border-menu-border z-100 max-w-xs py-1 px-2">
<div className="text-xs font-medium">Compact Task</div>
<div className="text-xs text-muted-foreground">
Reduces the number of tokens used by summarizing the task.
</div>
</div>
}
delay={0}
disableAnimation={true}
placement="bottom">
<VSCodeButton
appearance="icon"
className={cn(
"text-foreground flex items-center text-sm font-bold hover:bg-transparent hover:opacity-80",
className,
)}
onClick={onClick}
type="button">
<FoldVerticalIcon size={12} />
</VSCodeButton>
<Tooltip>
<TooltipContent side="left">Compact Task</TooltipContent>
<TooltipTrigger className={cn("flex items-center", className)}>
<Button
aria-label="Compact Task"
className="[&_svg]:size-3"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onClick(e)
}}
size="icon"
variant="icon">
<FoldVerticalIcon />
</Button>
</TooltipTrigger>
</Tooltip>
)
}
@@ -1,8 +1,8 @@
import { Button } from "@heroui/button"
import { cn } from "@heroui/react"
import { CheckIcon, CopyIcon } from "lucide-react"
import { useCallback, useState } from "react"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
const CopyTaskButton: React.FC<{
taskText?: string
@@ -22,17 +22,14 @@ const CopyTaskButton: React.FC<{
}, [taskText])
return (
<HeroTooltip content="Copy Text" placement="right">
<Button
aria-label="Copy"
className={cn("bg-transparent hover:opacity-100", className)}
isIconOnly={true}
onPress={() => handleCopy()}
radius="sm"
size="sm">
{copied ? <CheckIcon size="13" /> : <CopyIcon size="13" />}
</Button>
</HeroTooltip>
<Tooltip>
<TooltipContent side="bottom">Copy Text</TooltipContent>
<TooltipTrigger className={cn("flex items-center", className)}>
<Button aria-label="Copy" onClick={() => handleCopy()} size="icon" variant="icon">
{copied ? <CheckIcon /> : <CopyIcon />}
</Button>
</TooltipTrigger>
</Tooltip>
)
}
@@ -1,7 +1,8 @@
import { Button, cn } from "@heroui/react"
import { StringArrayRequest } from "@shared/proto/cline/common"
import { TrashIcon } from "lucide-react"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { TaskServiceClient } from "@/services/grpc-client"
import { formatSize } from "@/utils/format"
@@ -10,19 +11,19 @@ const DeleteTaskButton: React.FC<{
taskSize?: number
className?: string
}> = ({ taskId, className, taskSize }) => (
<HeroTooltip content={`Delete Task (size: ${taskSize ? formatSize(taskSize) : "--"})`} placement="right">
<Button
aria-label="Delete Task"
className={cn("flex items-center border-0 text-sm font-bold bg-transparent hover:opacity-100 p-0", className)}
isIconOnly={true}
onPress={() => {
taskId && TaskServiceClient.deleteTasksWithIds(StringArrayRequest.create({ value: [taskId] }))
}}
radius="sm"
size="sm">
<TrashIcon size="13" />
</Button>
</HeroTooltip>
<Tooltip>
<TooltipContent>{`Delete Task (size: ${taskSize ? formatSize(taskSize) : "--"})`}</TooltipContent>
<TooltipTrigger className={cn("flex items-center", className)}>
<Button
aria-label="Delete Task"
disabled={!taskId}
onClick={() => taskId && TaskServiceClient.deleteTasksWithIds(StringArrayRequest.create({ value: [taskId] }))}
size="xs"
variant="icon">
<TrashIcon />
</Button>
</TooltipTrigger>
</Tooltip>
)
DeleteTaskButton.displayName = "DeleteTaskButton"
@@ -1,29 +1,29 @@
import { cn } from "@heroui/react"
import { XIcon } from "lucide-react"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
const NewTaskButton: React.FC<{
onClick: () => void
className?: string
}> = ({ className, onClick }) => {
return (
<HeroTooltip content="Close current task to start new one" delay={1000} placement="bottom">
<button
aria-label="Start a New Task"
className={cn(
"flex items-center border-0 text-sm font-bold bg-transparent opacity-70 hover:opacity-100",
className,
"hover:bg-muted/10 px-0 cursor-pointer",
)}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onClick()
}}
type="button">
<XIcon size="14" />
</button>
</HeroTooltip>
<Tooltip>
<TooltipContent side="left">Start a New Task</TooltipContent>
<TooltipTrigger className={cn("flex items-center", className)}>
<Button
aria-label="New Task"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onClick()
}}
size="icon"
variant="icon">
<XIcon />
</Button>
</TooltipTrigger>
</Tooltip>
)
}
@@ -1,7 +1,8 @@
import { Button, cn } from "@heroui/react"
import { StringRequest } from "@shared/proto/cline/common"
import { ArrowDownToLineIcon } from "lucide-react"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { FileServiceClient } from "@/services/grpc-client"
const OpenDiskConversationHistoryButton: React.FC<{
@@ -19,18 +20,20 @@ const OpenDiskConversationHistoryButton: React.FC<{
}
return (
<HeroTooltip content="Open Conversation History File" placement="right">
<Button
aria-label="Open Disk Conversation History"
className={cn("flex items-center border-0 text-sm font-bold bg-transparent hover:opacity-100", className)}
isIconOnly={true}
onPress={() => handleOpenDiskConversationHistory()}
radius="sm"
size="sm">
<ArrowDownToLineIcon size="14" />
</Button>
</HeroTooltip>
<Tooltip>
<TooltipContent>Open Conversation History File</TooltipContent>
<TooltipTrigger className={cn("flex items-center", className)}>
<Button
aria-label="Open Disk Conversation History"
onClick={() => handleOpenDiskConversationHistory()}
size="icon"
variant="icon">
<ArrowDownToLineIcon />
</Button>
</TooltipTrigger>
</Tooltip>
)
}
OpenDiskConversationHistoryButton.displayName = "OpenDiskConversationHistoryButton"
export default OpenDiskConversationHistoryButton
@@ -12,7 +12,7 @@ import React, { useEffect, useRef, useState } from "react"
import { useClickAway, useWindowSize } from "react-use"
import styled from "styled-components"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import Tooltip from "@/components/common/Tooltip"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { FileServiceClient } from "@/services/grpc-client"
import RulesToggleList from "./RulesToggleList"
@@ -196,25 +196,25 @@ const ClineRulesToggleModal: React.FC = () => {
return (
<div ref={modalRef}>
<div className="inline-flex min-w-0 max-w-full" ref={buttonRef}>
<Tooltip tipText="Manage Cline Rules & Workflows" visible={isVisible ? false : undefined}>
<VSCodeButton
appearance="icon"
aria-label={isVisible ? "Hide Cline Rules & Workflows" : "Show Cline Rules & Workflows"}
onClick={() => setIsVisible(!isVisible)}
style={{ padding: "0px 0px", height: "20px" }}>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<span
className="codicon codicon-law flex items-center"
style={{ fontSize: "12.5px", marginBottom: 1 }}
/>
</div>
</VSCodeButton>
<Tooltip>
{!isVisible && <TooltipContent>Manage Cline Rules & Workflows</TooltipContent>}
<TooltipTrigger>
<VSCodeButton
appearance="icon"
aria-label={isVisible ? "Hide Cline Rules & Workflows" : "Show Cline Rules & Workflows"}
className="h-6"
onClick={() => setIsVisible(!isVisible)}>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<span className="codicon codicon-law flex items-center" style={{ fontSize: "12.5px" }} />
</div>
</VSCodeButton>
</TooltipTrigger>
</Tooltip>
</div>
{isVisible && (
<div
className="fixed left-[15px] right-[15px] border border-[var(--vscode-editorGroup-border)] p-3 rounded z-[1000] overflow-y-auto"
className="fixed left-[15px] right-[15px] border border-(--vscode-editorGroup-border) p-3 rounded z-1000 overflow-y-auto"
style={{
bottom: `calc(100vh - ${menuPosition}px + 6px)`,
background: CODE_BLOCK_BG_COLOR,
@@ -222,7 +222,7 @@ const ClineRulesToggleModal: React.FC = () => {
overscrollBehavior: "contain",
}}>
<div
className="fixed w-[10px] h-[10px] z-[-1] rotate-45 border-r border-b border-[var(--vscode-editorGroup-border)]"
className="fixed w-[10px] h-[10px] z-[-1] rotate-45 border-r border-b border-(--vscode-editorGroup-border)"
style={{
bottom: `calc(100vh - ${menuPosition}px)`,
right: arrowPosition,
@@ -253,7 +253,7 @@ const ClineRulesToggleModal: React.FC = () => {
</div>
{/* Description text */}
<div className="text-xs text-[var(--vscode-descriptionForeground)] mb-4">
<div className="text-xs text-(--vscode-descriptionForeground) mb-4">
{currentView === "rules" ? (
<p>
Rules allow you to provide Cline with system-level guidance. Think of them as a persistent way to
@@ -271,7 +271,7 @@ const ClineRulesToggleModal: React.FC = () => {
such as deploying a service or submitting a PR. To invoke a workflow, type{" "}
<span
className="
text-[var(--vscode-foreground)] font-bold">
text-(--vscode-foreground) font-bold">
/workflow-name
</span>{" "}
in the chat.{" "}
@@ -92,13 +92,13 @@ const NewRuleRow: React.FC<NewRuleRowProps> = ({ isGlobal, ruleType }) => {
onClick={() => !isExpanded && setIsExpanded(true)}
ref={componentRef}>
<div
className={`flex items-center p-2 rounded bg-[var(--vscode-input-background)] transition-all duration-300 ease-in-out h-[18px] ${
className={`flex items-center p-2 py-4 rounded bg-input-background transition-all duration-300 ease-in-out h-[18px] ${
isExpanded ? "shadow-sm" : ""
}`}>
{isExpanded ? (
<form className="flex flex-1 items-center" onSubmit={handleSubmit}>
<input
className="flex-1 bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border-0 outline-0 rounded focus:outline-none focus:ring-0 focus:border-transparent"
className="flex-1 bg-input-background text-(--vscode-input-foreground) border-0 outline-0 rounded focus:outline-none focus:ring-0 focus:border-transparent"
onChange={(e) => setFilename(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={
@@ -127,7 +127,7 @@ const NewRuleRow: React.FC<NewRuleRowProps> = ({ isGlobal, ruleType }) => {
</form>
) : (
<>
<span className="flex-1 text-[var(--vscode-descriptionForeground)] bg-[var(--vscode-input-background)] italic text-xs">
<span className="flex-1 text-(--vscode-descriptionForeground) bg-input-background italic text-xs">
{ruleType === "workflow" ? "New workflow file..." : "New rule file..."}
</span>
<div className="flex items-center ml-2 space-x-2">
@@ -146,7 +146,7 @@ const NewRuleRow: React.FC<NewRuleRowProps> = ({ isGlobal, ruleType }) => {
</>
)}
</div>
{isExpanded && error && <div className="text-[var(--vscode-errorForeground)] text-xs mt-1 ml-2">{error}</div>}
{isExpanded && error && <div className="text-(--vscode-errorForeground) text-xs mt-1 ml-2">{error}</div>}
</div>
)
}
@@ -75,7 +75,7 @@ const RuleRow: React.FC<{
return (
<div className="mb-2.5">
<div
className={`flex items-center p-2 rounded bg-[var(--vscode-textCodeBlock-background)] h-[18px] ${
className={`flex items-center p-2 py-4 rounded bg-(--vscode-textCodeBlock-background) h-[18px] ${
enabled ? "opacity-100" : "opacity-60"
}`}>
<span className="flex-1 overflow-hidden break-all whitespace-normal flex items-center mr-1" title={rulePath}>
@@ -89,8 +89,8 @@ const RuleRow: React.FC<{
aria-checked={enabled}
className={`w-[20px] h-[10px] rounded-[5px] relative cursor-pointer transition-colors duration-200 ${
enabled
? "bg-[var(--vscode-testing-iconPassed)] opacity-90"
: "bg-[var(--vscode-titleBar-inactiveForeground)] opacity-50"
? "bg-(--vscode-testing-iconPassed) opacity-90"
: "bg-(--vscode-titleBar-inactiveForeground) opacity-50"
}`}
onClick={() => toggleRule(rulePath, !enabled)}
onKeyDown={(e) => {
@@ -102,7 +102,7 @@ const RuleRow: React.FC<{
role="switch"
tabIndex={0}>
<div
className={`w-[6px] h-[6px] bg-white border border-[#66666699] rounded-full absolute top-[1px] transition-all duration-200 ${
className={`w-[6px] h-[6px] bg-white border border-[#66666699] rounded-full absolute top-px transition-all duration-200 ${
enabled ? "left-[12px]" : "left-[2px]"
}`}
/>
@@ -45,7 +45,7 @@ const RulesToggleList = ({
) : (
<>
{showNoRules && (
<div className="flex flex-col items-center gap-3 my-3 text-[var(--vscode-descriptionForeground)]">
<div className="flex flex-col items-center gap-3 my-3 text-(--vscode-descriptionForeground)">
{ruleType === "workflow" ? "No workflows found" : "No rules found"}
</div>
)}
@@ -37,7 +37,7 @@ export function AlertDialogContent({ className, children, ...props }: React.HTML
className={`fixed top-[50%] left-[50%] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] ${className}`}
onClick={(e) => e.stopPropagation()}
{...props}>
<div className="bg-[var(--vscode-editor-background)] rounded-sm gap-3 border border-[var(--vscode-panel-border)] p-6 shadow-lg sm:max-w-lg">
<div className="bg-(--vscode-editor-background) rounded-sm gap-3 border border-(--vscode-panel-border) p-6 shadow-lg sm:max-w-lg">
{children}
</div>
</div>
@@ -55,14 +55,14 @@ export function AlertDialogFooter({ className, ...props }: React.HTMLAttributes<
export function AlertDialogTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
return (
<h2
className={`text-base font-medium text-[var(--vscode-editor-foreground)] flex items-center gap-2 text-left ${className}`}
className={`text-base font-medium text-(--vscode-editor-foreground) flex items-center gap-2 text-left ${className}`}
{...props}
/>
)
}
export function AlertDialogDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
return <p className={`text-[var(--vscode-descriptionForeground)] text-sm text-left ${className}`} {...props} />
return <p className={`text-(--vscode-descriptionForeground) text-sm text-left ${className}`} {...props} />
}
export function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof VSCodeButton>) {
@@ -101,7 +101,7 @@ export function UnsavedChangesDialog({
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<AlertTriangle className="w-5 h-5 text-[var(--vscode-errorForeground)]" />
<AlertTriangle className="w-5 h-5 text-(--vscode-errorForeground)" />
{title}
</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
@@ -93,29 +93,23 @@ const ChecklistRenderer: React.FC<ChecklistRendererProps> = ({ text }) => {
return (
<div
className={cn("text-sm flex flex-col gap-0.5", items.length >= 10 ? "max-h-52 overflow-y-auto" : "h-auto visible")}
onScroll={handleScroll}
ref={containerRef}
style={{
display: "flex",
flexDirection: "column",
gap: "2px",
fontSize: "12px",
lineHeight: "1.3",
maxHeight: items.length >= 10 ? "200px" : "auto",
overflowY: items.length >= 10 ? "auto" : "visible",
}}>
{items.map((item, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: Using index as key for checklist items
<div className="flex items-start gap-1.5 p-0.5" key={`checklist-item-${index}`}>
<span className={cn("text-xs shrink-0 mt-0.5", item.checked ? "text-success" : "text-foreground")}>
<span className={cn("text-sm shrink-0 mt-0.5", item.checked ? "text-success" : "text-foreground")}>
{item.checked ? <CheckIcon size={10} /> : <CircleIcon size={10} />}
</span>
<div
className={cn("text-xs break-words flex-1", item.checked ? "text-description" : "text-foreground")}
style={{
textDecoration: item.checked ? "line-through" : "none",
lineHeight: "1.3",
}}>
className={cn(
"text-sm break-words flex-1 leading-5",
item.checked ? "text-description line-through" : "text-foreground",
)}>
<LightMarkdown compact text={item.text} />
</div>
</div>
@@ -284,7 +284,7 @@ const Container = styled.div<{ isMenuOpen?: boolean; $isCheckedOut?: boolean }>`
margin-top: -10px;
margin-bottom: -10px;
opacity: ${(props) => (props.$isCheckedOut ? 1 : props.isMenuOpen ? 1 : 0.5)};
height: 0.5rem;
&:hover {
opacity: 1;
}
@@ -313,7 +313,7 @@ const Container = styled.div<{ isMenuOpen?: boolean; $isCheckedOut?: boolean }>`
const Label = styled.span<{ $isCheckedOut?: boolean }>`
color: ${(props) => (props.$isCheckedOut ? "var(--vscode-textLink-foreground)" : "var(--vscode-descriptionForeground)")};
font-size: 9px;
flex-shrink: 0;
shrink: 0;
`
const DottedLine = styled.div<{ small?: boolean; $isCheckedOut?: boolean }>`
@@ -333,7 +333,7 @@ const ButtonGroup = styled.div`
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
shrink: 0;
`
const CustomButton = styled.button<{ disabled?: boolean; isActive?: boolean; $isCheckedOut?: boolean }>`
@@ -1,8 +1,9 @@
import { StringRequest } from "@shared/proto/cline/common"
import { EmptyRequest, Int64Request } from "@shared/proto/index.cline"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { Terminal } from "lucide-react"
import { Terminal, XIcon } from "lucide-react"
import { useCallback, useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { StateServiceClient, UiServiceClient } from "@/services/grpc-client"
import { isMacOSOrLinux } from "@/utils/platformUtils"
@@ -200,13 +201,14 @@ export const CliInstallBanner: React.FC = () => {
</div>
{/* Close button */}
<VSCodeButton
appearance="icon"
<Button
className="absolute top-2.5 right-2"
data-testid="cli-banner-close-button"
onClick={handleClose}
style={{ position: "absolute", top: "8px", right: "8px" }}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
size="icon"
variant="icon">
<XIcon />
</Button>
</div>
)
}
@@ -156,7 +156,7 @@ const CodeBlock = memo(({ source, forceWrap = false }: CodeBlockProps) => {
maxHeight: forceWrap ? "none" : "100%",
backgroundColor: CODE_BLOCK_BG_COLOR,
}}>
<StyledMarkdown className="ph-no-capture" forceWrap={forceWrap}>
<StyledMarkdown className="ph-no-capture markdown" forceWrap={forceWrap}>
{reactContent}
</StyledMarkdown>
</div>
@@ -7,13 +7,13 @@ const DangerButton: React.FC<DangerButtonProps> = (props) => {
<VSCodeButton
{...props}
className={`
!bg-[#c42b2b]
!border-[#c42b2b]
!text-white
hover:!bg-[#a82424]
hover:!border-[#a82424]
active:!bg-[#8f1f1f]
active:!border-[#8f1f1f]
bg-[#c42b2b]!
border-[#c42b2b]!
text-white!
hover:bg-[#a82424]!
hover:border-[#a82424]!
active:bg-[#8f1f1f]!
active:border-[#8f1f1f]!
${props.className || ""}
`}
/>
@@ -1,63 +0,0 @@
import { cn, Tooltip } from "@heroui/react"
import React, { useMemo } from "react"
interface HeroTooltipProps {
content: React.ReactNode
children: React.ReactNode
className?: string
delay?: number
closeDelay?: number
placement?: "top" | "bottom" | "left" | "right"
showArrow?: boolean
disabled?: boolean
}
/**
* HeroTooltip component that wraps the HeroUI tooltip with styling
* similar to TaskTimelineTooltip
*/
const HeroTooltip: React.FC<HeroTooltipProps> = ({
content,
children,
className,
showArrow = false,
delay = 0,
closeDelay = 500,
placement = "top",
disabled = false,
}) => {
// If content is a simple string, wrap it in the tailwind styled divs
const formattedContent = useMemo(() => {
return typeof content === "string" ? (
<div
className={cn(
"bg-code-background text-code-foreground border border-code-foreground/20 rounded shadow-md max-w-[250px] text-sm",
className,
"p-2",
)}>
<span className="whitespace-pre-wrap break-words overflow-y-auto">{content}</span>
</div>
) : (
// If content is already a React node, assume it's pre-formatted
content
)
}, [content, className])
return (
<Tooltip
classNames={{
content: "hero-tooltip-content pointer-events-none", // Prevent hovering over tooltip
}}
closeDelay={closeDelay}
content={formattedContent} // Immediate close when cursor moves away
delay={delay}
disableAnimation={true}
isDisabled={disabled}
placement={placement} // Disable animation for immediate appearance/disappearance
showArrow={showArrow}>
{children}
</Tooltip>
)
}
export default HeroTooltip
+10 -10
View File
@@ -1,5 +1,6 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { XIcon } from "lucide-react"
import { useCallback } from "react"
import { Button } from "@/components/ui/button"
import { PlatformType } from "@/config/platform.config"
import { usePlatform } from "@/context/PlatformContext"
import { StateServiceClient } from "@/services/grpc-client"
@@ -13,27 +14,26 @@ export const InfoBanner: React.FC = () => {
if (usePlatform().type === PlatformType.VSCODE) {
return (
<a
className="bg-banner-background px-3 py-2 flex flex-col gap-1 shrink-0 mb-1 relative text-sm m-4 no-underline transition-colors hover:brightness-120"
className="bg-banner-background px-3 py-2 flex flex-col gap-1 shrink-0 mb-1 relative text-sm m-4 no-underline transition-colors hover:brightness-120 rounded-sm"
href="https://docs.cline.bot/features/customization/opening-cline-in-sidebar"
rel="noopener noreferrer"
style={{ color: "var(--vscode-foreground)", outline: "none" }}
target="_blank">
<h4 className="m-0" style={{ paddingRight: "18px" }}>
💡 Cline in the Right Sidebar
</h4>
<h4 className="m-0">💡 Cline in the Right Sidebar</h4>
<p className="m-0">
Keep your files visible when chatting with Cline. Drag the Cline icon to the right sidebar panel for a better
experience. <span className="text-link cursor-pointer">See how </span>
</p>
{/* Close button */}
<VSCodeButton
appearance="icon"
<Button
className="absolute top-2.5 right-2"
data-testid="info-banner-close-button"
onClick={handleClose}
style={{ position: "absolute", top: "6px", right: "6px" }}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
size="icon"
variant="icon">
<XIcon />
</Button>
</a>
)
}
@@ -19,7 +19,7 @@ const ActModeHighlight: React.FC = () => {
return (
<span
className={`text-[var(--vscode-textLink-foreground)] inline-flex items-center gap-1 ${
className={`text-(--vscode-textLink-foreground) inline-flex items-center gap-1 ${
mode === "plan" ? "hover:opacity-90 cursor-pointer" : "cursor-default opacity-60"
}`}
onClick={() => {
@@ -33,8 +33,8 @@ const ActModeHighlight: React.FC = () => {
}
}}
title={mode === "plan" ? "Click to toggle to Act Mode" : "Already in Act Mode"}>
<div className="p-1 rounded-[12px] bg-[var(--vscode-editor-background)] flex items-center justify-end w-4 border-[1px] border-[var(--vscode-input-border)]">
<div className="rounded-full bg-[var(--vscode-textLink-foreground)] w-2 h-2" />
<div className="p-1 rounded-[12px] bg-(--vscode-editor-background) flex items-center justify-end w-4 border border-(--vscode-input-border)">
<div className="rounded-full bg-(--vscode-textLink-foreground) w-2 h-2" />
</div>
Act Mode (A)
</span>
@@ -60,13 +60,17 @@ const remarkUrlToLink = () => {
visit(tree, "text", (node: any, index, parent) => {
const urlRegex = /https?:\/\/[^\s<>)"]+/g
const matches = node.value.match(urlRegex)
if (!matches) return
if (!matches) {
return
}
const parts = node.value.split(urlRegex)
const children: any[] = []
parts.forEach((part: string, i: number) => {
if (part) children.push({ type: "text", value: part })
if (part) {
children.push({ type: "text", value: part })
}
if (matches[i]) {
children.push({
type: "link",
@@ -97,19 +101,25 @@ const remarkHighlightActMode = () => {
// Added negative lookahead to avoid matching if already followed by the shortcut
const actModeRegex = /\bto\s+Act\s+Mode\b(?!\s*\(⌘⇧A\))/i
if (!node.value.match(actModeRegex)) return
if (!node.value.match(actModeRegex)) {
return
}
// Split the text by the matches
const parts = node.value.split(actModeRegex)
const matches = node.value.match(actModeRegex)
if (!matches || parts.length <= 1) return
if (!matches || parts.length <= 1) {
return
}
const children: any[] = []
parts.forEach((part: string, i: number) => {
// Add the text before the match
if (part) children.push({ type: "text", value: part })
if (part) {
children.push({ type: "text", value: part })
}
// Add the match, but only make "Act Mode" bold (not the "to" part)
if (matches[i]) {
@@ -157,22 +167,32 @@ const remarkPreventBoldFilenames = () => {
return (tree: any) => {
visit(tree, "strong", (node: any, index: number | undefined, parent: any) => {
// Only process if there's a next node (potential file extension)
if (!parent || typeof index === "undefined" || index === parent.children.length - 1) return
if (!parent || typeof index === "undefined" || index === parent.children.length - 1) {
return
}
const nextNode = parent.children[index + 1]
// Check if next node is text and starts with . followed by extension
if (nextNode.type !== "text" || !nextNode.value.match(/^\.[a-zA-Z0-9]+/)) return
if (nextNode.type !== "text" || !nextNode.value.match(/^\.[a-zA-Z0-9]+/)) {
return
}
// If the strong node has multiple children, something weird is happening
if (node.children?.length !== 1) return
if (node.children?.length !== 1) {
return
}
// Get the text content from inside the strong node
const strongContent = node.children?.[0]?.value
if (!strongContent || typeof strongContent !== "string") return
if (!strongContent || typeof strongContent !== "string") {
return
}
// Validate that the strong content is a valid filename
if (!strongContent.match(/^[a-zA-Z0-9_-]+$/)) return
if (!strongContent.match(/^[a-zA-Z0-9_-]+$/)) {
return
}
// Combine into a single text node
const newNode = {
@@ -285,7 +305,9 @@ const PreWithCopyButton = ({ children, ...preProps }: React.HTMLAttributes<HTMLP
const codeElement = preRef.current.querySelector("code")
const textToCopy = codeElement ? codeElement.textContent : preRef.current.textContent
if (!textToCopy) return
if (!textToCopy) {
return
}
return textToCopy
}
return null
@@ -307,7 +329,7 @@ const PreWithCopyButton = ({ children, ...preProps }: React.HTMLAttributes<HTMLP
const remarkFilePathDetection = () => {
return async (tree: Node) => {
const fileNameRegex = /^(?!\/)[\w\-./]+(?<!\/)$/
const inlineCodeNodes: any[] = []
const _inlineCodeNodes: any[] = []
const filePathPromises: Promise<void>[] = []
// Collect all inline code nodes that might be file paths
@@ -385,7 +407,7 @@ const MarkdownBlock = memo(({ markdown, compact }: MarkdownBlockProps) => {
<>
<code {...props} />
<button
className="codicon codicon-link-external bg-transparent border-0 appearance-none p-0 ml-0.5 leading-none align-middle opacity-70 hover:opacity-100 transition-opacity text-[1em] relative top-[1px] text-[var(--vscode-textPreformat-foreground)] translate-y-[-2px]"
className="codicon codicon-link-external bg-transparent border-0 appearance-none p-0 ml-0.5 leading-none align-middle opacity-70 hover:opacity-100 transition-opacity text-[1em] relative top-px text-(--vscode-textPreformat-foreground) translate-y-[-2px]"
onClick={() => FileServiceClient.openFileRelativePath({ value: filePath })}
title={`Open ${filePath} in editor`}
type="button"
@@ -401,9 +423,12 @@ const MarkdownBlock = memo(({ markdown, compact }: MarkdownBlockProps) => {
// Handle both string children and array of children cases
const childrenText = React.Children.toArray(props.children)
.map((child) => {
if (typeof child === "string") return child
if (typeof child === "object" && "props" in child && child.props.children)
if (typeof child === "string") {
return child
}
if (typeof child === "object" && "props" in child && child.props.children) {
return String(child.props.children)
}
return ""
})
.join("")
@@ -1,8 +1,8 @@
import { EmptyRequest, Int64Request } from "@shared/proto/index.cline"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { Megaphone } from "lucide-react"
import { Megaphone, XIcon } from "lucide-react"
import { useCallback } from "react"
import { useMount } from "react-use"
import { Button } from "@/components/ui/button"
import { useClineAuth } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient, StateServiceClient } from "@/services/grpc-client"
@@ -67,14 +67,12 @@ export const NewModelBanner: React.FC = () => {
return (
<div
className="px-3 py-2 flex flex-col gap-1 shrink-0 mb-1 relative text-sm mt-1.5 m-4 no-underline transition-colors hover:brightness-120 border-0 cursor-pointer text-left w-auto"
className="px-3 py-2 flex flex-col gap-1 shrink-0 mb-1 relative text-sm mt-1.5 m-4 no-underline transition-colors hover:brightness-120 border-0 cursor-pointer text-left w-auto rounded-sm"
onClick={handleBannerClick}
style={{
backgroundColor: getAsVar(VSC_INACTIVE_SELECTION_BACKGROUND),
borderRadius: "3px",
color: "var(--vscode-foreground)",
}}>
<h4 className="m-0 flex items-center gap-2" style={{ paddingRight: "18px" }}>
<h4 className="m-0 flex items-center gap-2">
<Megaphone className="w-4 h-4" />
Claude Haiku 4.5
</h4>
@@ -84,13 +82,14 @@ export const NewModelBanner: React.FC = () => {
</p>
{/* Close button */}
<VSCodeButton
appearance="icon"
<Button
className="absolute top-2.5 right-2"
data-testid="info-banner-close-button"
onClick={handleClose}
style={{ position: "absolute", top: "6px", right: "6px" }}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
size="icon"
variant="icon">
<XIcon />
</Button>
</div>
)
}
@@ -22,7 +22,7 @@ const StyledButton = styled(VSCodeButton)`
i.codicon {
margin-right: 6px;
flex-shrink: 0;
shrink: 0;
font-size: 16px !important;
}
`
@@ -7,13 +7,13 @@ const SuccessButtonTW: React.FC<SuccessButtonTWProps> = (props) => {
<VSCodeButton
{...props}
className={`
!bg-[#176f2c]
!border-[#176f2c]
!text-white
hover:!bg-[#197f31]
hover:!border-[#197f31]
active:!bg-[#156528]
active:!border-[#156528]
bg-[#176f2c]!
border-[#176f2c]!
text-white!
hover:bg-[#197f31]!
hover:border-[#197f31]!
active:bg-[#156528]!
active:border-[#156528]!
${props.className || ""}
`
.replace(/\s+/g, " ")
+1 -1
View File
@@ -9,7 +9,7 @@ export const Tab = ({ className, children, ...props }: TabProps) => (
)
export const TabHeader = ({ className, children, ...props }: TabProps) => (
<div className={`px-5 py-2.5 border-b border-[var(--vscode-panel-border)] ${className}`} {...props}>
<div className={`px-5 py-2.5 border-b border-(--vscode-panel-border) ${className}`} {...props}>
{children}
</div>
)
@@ -1,65 +0,0 @@
import React, { useState } from "react"
import styled from "styled-components"
import {
getAsVar,
VSC_DESCRIPTION_FOREGROUND,
VSC_INPUT_BORDER,
VSC_INPUT_PLACEHOLDER_FOREGROUND,
VSC_SIDEBAR_BACKGROUND,
} from "@/utils/vscStyles"
interface TooltipProps {
visible?: boolean
hintText?: string
tipText: string
children: React.ReactNode
style?: React.CSSProperties
}
// add styled component for tooltip
const TooltipBody = styled.div<Pick<TooltipProps, "style">>`
position: absolute;
background-color: ${getAsVar(VSC_SIDEBAR_BACKGROUND)};
color: ${getAsVar(VSC_DESCRIPTION_FOREGROUND)};
padding: 5px;
border-radius: 5px;
bottom: 100%;
left: ${(props) => props.style?.left ?? -180}%;
z-index: ${(props) => props.style?.zIndex ?? 1000};
white-space: wrap;
max-width: 200px;
border: 1px solid ${getAsVar(VSC_INPUT_BORDER)};
pointer-events: none;
font-size: 0.9em;
`
const Hint = styled.div`
font-size: 0.8em;
color: ${getAsVar(VSC_INPUT_PLACEHOLDER_FOREGROUND)};
opacity: 0.8;
margin-top: 2px;
`
const Tooltip: React.FC<TooltipProps> = ({ visible, tipText, hintText, children, style }) => {
const [isHovered, setIsHovered] = useState(false)
// Determine final visibility based on prop or internal state
const shouldShow = visible !== undefined ? visible : isHovered
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ position: "relative", display: "inline-block" }}>
{children}
{shouldShow && (
<TooltipBody style={style}>
{tipText}
{hintText && <Hint>{hintText}</Hint>}
</TooltipBody>
)}
</div>
)
}
export default Tooltip
@@ -98,7 +98,7 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
</div>
{isExpanded && (
<div style={{ padding: "0px 20px 0 20px" }}>
<div className="px-5">
{taskHistory.filter((item) => item.ts && item.task).length > 0 ? (
<>
{taskHistory
@@ -134,45 +134,28 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
)}
<div
className="history-preview-task"
className="history-preview-task text-base text-description mb-2 overflow-hidden whitespace-pre-wrap wrap-anywhere"
id={`history-preview-task-${item.id}`}
style={{
fontSize: "var(--vscode-font-size)",
color: "var(--vscode-descriptionForeground)",
marginBottom: "8px",
display: "-webkit-box",
WebkitLineClamp: 3,
WebkitBoxOrient: "vertical",
overflow: "hidden",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
overflowWrap: "anywhere",
}}>
<span className="ph-no-capture">{item.task}</span>
</div>
<div
style={{
fontSize: "0.85em",
color: "var(--vscode-descriptionForeground)",
}}>
<span>
<div className="text-sm text-description">
<span className="mr-1">
Tokens: {formatLargeNumber(item.tokensIn || 0)}
{formatLargeNumber(item.tokensOut || 0)}
</span>
{!!item.cacheWrites && (
<>
{" "}
<span>
Cache: +{formatLargeNumber(item.cacheWrites || 0)} {" "}
{formatLargeNumber(item.cacheReads || 0)}
</span>
</>
<span className="mr-1">
Cache: +{formatLargeNumber(item.cacheWrites || 0)} {" "}
{formatLargeNumber(item.cacheReads || 0)}
</span>
)}
{!!item.totalCost && (
<>
{" • "}
<span>API Cost: ${item.totalCost?.toFixed(4)}</span>
</>
<span className="mr-1"> API Cost: ${item.totalCost?.toFixed(4)}</span>
)}
</div>
</div>
@@ -29,16 +29,16 @@ interface CustomFilterRadioProps {
const CustomFilterRadio = ({ checked, onChange, icon, label }: CustomFilterRadioProps) => {
return (
<div
className="flex items-center cursor-pointer py-[0.3em] px-0 mr-[10px] text-[var(--vscode-font-size)] select-none"
className="flex items-center cursor-pointer py-[0.3em] px-0 mr-[10px] text-(--vscode-font-size) select-none"
onClick={onChange}>
<div
className={`w-[14px] h-[14px] rounded-full border border-[var(--vscode-checkbox-border)] relative flex justify-center items-center mr-[6px] ${
checked ? "bg-[var(--vscode-checkbox-background)]" : "bg-transparent"
className={`w-[14px] h-[14px] rounded-full border border-(--vscode-checkbox-border) relative flex justify-center items-center mr-[6px] ${
checked ? "bg-(--vscode-checkbox-background)" : "bg-transparent"
}`}>
{checked && <div className="w-[6px] h-[6px] rounded-full bg-[var(--vscode-checkbox-foreground)]" />}
{checked && <div className="w-[6px] h-[6px] rounded-full bg-(--vscode-checkbox-foreground)" />}
</div>
<span className="flex items-center gap-[3px]">
<div className={`codicon codicon-${icon} text-[var(--vscode-button-background)] text-base`} />
<div className={`codicon codicon-${icon} text-(--vscode-button-background) text-base`} />
{label}
</span>
</div>
@@ -298,17 +298,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
}
`}
</style>
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}>
<div className="fixed overflow-hidden inset-0 flex flex-col">
<div
style={{
display: "flex",
@@ -326,12 +316,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
<VSCodeButton onClick={() => onDone()}>Done</VSCodeButton>
</div>
<div style={{ padding: "5px 17px 6px 17px" }}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "6px",
}}>
<div className="flex flex-col gap-3">
<VSCodeTextField
onInput={(e) => {
const newValue = (e.target as HTMLInputElement)?.value
@@ -368,8 +353,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
)}
</VSCodeTextField>
<VSCodeRadioGroup
className="flex flex-wrap"
onChange={(e) => setSortOption((e.target as HTMLInputElement).value as SortOption)}
style={{ display: "flex", flexWrap: "wrap" }}
value={sortOption}>
<VSCodeRadio value="newest">Newest</VSCodeRadio>
<VSCodeRadio value="oldest">Oldest</VSCodeRadio>
@@ -8,10 +8,10 @@ type AddLocalServerFormProps = {
onServerAdded: () => void
}
const AddLocalServerForm = ({ onServerAdded }: AddLocalServerFormProps) => {
const AddLocalServerForm = ({}: AddLocalServerFormProps) => {
return (
<FormContainer>
<div className="text-[var(--vscode-foreground)]">
<div className="text-(--vscode-foreground)">
Add a local MCP server by configuring it in <code>cline_mcp_settings.json</code>. You'll need to specify the
server name, command, arguments, and any required environment variables in the JSON configuration. Learn more
<VSCodeLink href={LINKS.DOCUMENTATION.LOCAL_MCP_SERVER_DOCS} style={{ display: "inline" }}>
@@ -65,7 +65,7 @@ const AddRemoteServerForm = ({ onServerAdded }: { onServerAdded: () => void }) =
return (
<div className="p-4 px-5">
<div className="text-[var(--vscode-foreground)] mb-2">
<div className="text-(--vscode-foreground) mb-2">
Add a remote MCP server by providing a name and its URL endpoint. Learn more{" "}
<VSCodeLink href={LINKS.DOCUMENTATION.REMOTE_MCP_SERVER_DOCS} style={{ display: "inline" }}>
here.
@@ -101,7 +101,7 @@ const AddRemoteServerForm = ({ onServerAdded }: { onServerAdded: () => void }) =
</VSCodeTextField>
</div>
{error && <div className="mb-3 text-[var(--vscode-errorForeground)]">{error}</div>}
{error && <div className="mb-3 text-(--vscode-errorForeground)">{error}</div>}
<div className="flex items-center mt-3 w-full">
<VSCodeButton className="w-full" disabled={isSubmitting} type="submit">
@@ -109,7 +109,7 @@ const AddRemoteServerForm = ({ onServerAdded }: { onServerAdded: () => void }) =
</VSCodeButton>
{showConnectingMessage && (
<div className="ml-3 text-[var(--vscode-notificationsInfoIcon-foreground)] text-sm">
<div className="ml-3 text-(--vscode-notificationsInfoIcon-foreground) text-sm">
Connecting to server... This may take a few seconds.
</div>
)}
@@ -27,7 +27,7 @@ const ServersToggleList = ({
))}
</div>
) : (
<div className="flex flex-col items-center gap-3 my-5 text-[var(--vscode-descriptionForeground)]">
<div className="flex flex-col items-center gap-3 my-5 text-(--vscode-descriptionForeground)">
No MCP servers installed
</div>
)
+20 -17
View File
@@ -1,9 +1,10 @@
import { TooltipContent } from "@radix-ui/react-tooltip"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { HistoryIcon, PlusIcon, SettingsIcon, UserCircleIcon } from "lucide-react"
import { useMemo } from "react"
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip"
import { TaskServiceClient } from "@/services/grpc-client"
import { useExtensionState } from "../../context/ExtensionStateContext"
import HeroTooltip from "../common/HeroTooltip"
// Custom MCP Server Icon component using VSCode codicon
const McpServerIcon = ({ className, size }: { className?: string; size?: number }) => (
@@ -66,23 +67,25 @@ export const Navbar = () => {
return (
<nav
className="flex-none inline-flex justify-end bg-transparent gap-2 mb-1 z-10 border-none items-center mr-4!"
id="cline-navbar-container"
style={{ gap: "4px" }}>
className="flex-none inline-flex justify-end bg-transparent gap-1 mb-1 z-10 border-none items-center mr-4!"
id="cline-navbar-container">
{SETTINGS_TABS.map((tab) => (
<HeroTooltip content={tab.tooltip} key={`navbar-tooltip-${tab.id}`} placement="bottom">
<VSCodeButton
appearance="icon"
aria-label={tab.tooltip}
data-testid={`tab-${tab.id}`}
key={`navbar-button-${tab.id}`}
onClick={() => tab.navigate()}
style={{ padding: "0px", height: "20px" }}>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<tab.icon className="text-[var(--vscode-foreground)]" size={18} strokeWidth={1} />
</div>
</VSCodeButton>
</HeroTooltip>
<Tooltip key={`navbar-tooltip-${tab.id}`}>
<TooltipContent side="bottom">{tab.tooltip}</TooltipContent>
<TooltipTrigger asChild>
<VSCodeButton
appearance="icon"
aria-label={tab.tooltip}
data-testid={`tab-${tab.id}`}
key={`navbar-button-${tab.id}`}
onClick={() => tab.navigate()}
style={{ padding: "0px", height: "20px" }}>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<tab.icon className="text-(--vscode-foreground)" size={18} strokeWidth={1} />
</div>
</VSCodeButton>
</TooltipTrigger>
</Tooltip>
))}
</nav>
)
@@ -0,0 +1,289 @@
import { BooleanRequest } from "@shared/proto/index.cline"
import { AlertCircleIcon, CheckIcon, CircleCheckIcon, CircleIcon } from "lucide-react"
import { useCallback, useEffect, useMemo, useState } from "react"
import ClineLogoWhite from "@/assets/ClineLogoWhite"
import { Button } from "@/components/ui/button"
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@/components/ui/item"
import { handleSignIn } from "@/context/ClineAuthContext"
import { cn } from "@/lib/utils"
import { StateServiceClient } from "@/services/grpc-client"
import ApiConfigurationSection from "../settings/sections/ApiConfigurationSection"
import { ONBOARDING_MODEL_SELECTIONS } from "./models"
enum USER_TYPE {
FREE = "free",
POWER = "power",
BYOK = "byok",
}
type UserTypeSelection = {
title: string
description: string
type: USER_TYPE
}
const STEP_CONFIG = {
0: {
title: "Become a CLINE user!",
description:
"Cline is free for individual developers. Pay only for AI inference on a usage basis - no subscriptions, no vendor lock-in. You can change this later!",
buttons: [
{ text: "Continue", action: "next", variant: "default" },
{ text: "Login", action: "auth", variant: "secondary" },
],
},
[USER_TYPE.FREE]: {
title: "Select a free model",
buttons: [
{ text: "Sign Up for Cline", action: "auth", variant: "default" },
{ text: "Back", action: "back", variant: "secondary" },
],
},
[USER_TYPE.POWER]: {
title: "Select your model",
buttons: [
{ text: "Sign Up for Cline", action: "auth", variant: "default" },
{ text: "Back", action: "back", variant: "secondary" },
],
},
[USER_TYPE.BYOK]: {
title: "Configure your provider",
buttons: [
{ text: "Ready", action: "done", variant: "default" },
{ text: "Back", action: "back", variant: "secondary" },
],
},
} as const
const USER_TYPE_SELECTIONS: UserTypeSelection[] = [
{ title: "Absolutely Free", description: "More context of this key feature", type: USER_TYPE.FREE },
{ title: "Power User", description: "Unlock advanced features and capabilities", type: USER_TYPE.POWER },
{ title: "I have my own key", description: "Use your own API credentials", type: USER_TYPE.BYOK },
]
type ModelSelectionProps = {
userType: USER_TYPE.FREE | USER_TYPE.POWER
selectedModelId: string
onSelectModel: (modelId: string) => void
}
const ModelSelection = ({ userType, selectedModelId, onSelectModel }: ModelSelectionProps) => {
const modelGroups = ONBOARDING_MODEL_SELECTIONS[userType === USER_TYPE.FREE ? "free" : "power"]
const selectedModel = useMemo(() => {
for (const group of modelGroups) {
const model = group.models.find((m) => `${group.group}-${m.title}` === selectedModelId)
if (model) {
return model
}
}
return modelGroups[0].models[0]
}, [modelGroups, selectedModelId])
return (
<div className="flex flex-col w-full items-center">
<div className="flex w-full max-w-lg flex-col gap-6 my-4">
{modelGroups.map((group) => (
<div className="flex flex-col gap-3" key={group.group}>
<h4 className="text-sm font-semibold text-foreground/70 uppercase mb-2">{group.group}</h4>
{group.models.map((model) => {
const modelId = `${group.group}-${model.title}`
const isSelected = selectedModelId === modelId
return (
<Item
className={cn("cursor-pointer hover:cursor-pointer", {
"bg-input-background/30 border border-button-background": isSelected,
})}
key={modelId}
onClick={() => onSelectModel(modelId)}
variant="outline">
<ItemContent>
<ItemTitle className="flex w-full justify-between">
{model.title}
<span className="text-button-background uppercase text-xs">{model.badge}</span>
</ItemTitle>
<ItemDescription>{model.description}</ItemDescription>
</ItemContent>
</Item>
)
})}
</div>
))}
<div className="text-sm text-foreground mt-2">
<div className="uppercase font-semibold">SUPPORTS</div>
<div className="flex flex-col gap-1 mt-2">
{selectedModel.capabilities.map((capability) => (
<div
className="text-success inline-flex gap-1 [&_svg]:stroke-success [&_svg]:size-3 items-center"
key={capability}>
<CheckIcon />
{capability}
</div>
))}
</div>
</div>
</div>
</div>
)
}
type UserTypeSelectionProps = {
userType: USER_TYPE | undefined
onSelectUserType: (type: USER_TYPE) => void
}
const UserTypeSelectionStep = ({ userType, onSelectUserType }: UserTypeSelectionProps) => (
<div className="flex flex-col w-full items-center">
<div className="flex w-full max-w-lg flex-col gap-6 my-4">
<h3 className="text-base text-left self-start font-semibold">LETS GET STARTED</h3>
{USER_TYPE_SELECTIONS.map((option) => {
const isSelected = userType === option.type
return (
<Item
className={cn("cursor-pointer hover:cursor-pointer w-full", {
"bg-input-background/70 border border-input-foreground/10": isSelected,
})}
key={option.type}
onClick={() => onSelectUserType(option.type)}>
<ItemMedia className="[&_svg]:stroke-button-background" variant="icon">
{isSelected ? <CircleCheckIcon strokeWidth={1.5} /> : <CircleIcon strokeWidth={1.5} />}
</ItemMedia>
<ItemContent className="w-full">
<ItemTitle>{option.title}</ItemTitle>
<ItemDescription>{option.description}</ItemDescription>
</ItemContent>
</Item>
)
})}
</div>
</div>
)
type OnboardingStepContentProps = {
step: number
userType: USER_TYPE | undefined
selectedModelId: string
onSelectUserType: (type: USER_TYPE) => void
onSelectModel: (modelId: string) => void
}
const OnboardingStepContent = ({
step,
userType,
selectedModelId,
onSelectUserType,
onSelectModel,
}: OnboardingStepContentProps) => {
if (step === 0) {
return <UserTypeSelectionStep onSelectUserType={onSelectUserType} userType={userType} />
}
if (userType === USER_TYPE.BYOK) {
return <ApiConfigurationSection />
}
if (userType === USER_TYPE.FREE || userType === USER_TYPE.POWER) {
return <ModelSelection onSelectModel={onSelectModel} selectedModelId={selectedModelId} userType={userType} />
}
return null
}
type OnboardingViewProps = {
showOnboarding: (value: boolean) => void
onDone: () => void
}
const OnboardingView = ({ showOnboarding, onDone }: OnboardingViewProps) => {
const [stepNumber, setStepNumber] = useState(0)
const [userType, setUserType] = useState<USER_TYPE>(USER_TYPE.FREE)
const [selectedModelId, setSelectedModelId] = useState("")
useEffect(() => {
const userGroup = userType === USER_TYPE.POWER ? USER_TYPE.POWER : USER_TYPE.FREE
const modelGroup = ONBOARDING_MODEL_SELECTIONS[userGroup][0]
const userGroupInitModel = modelGroup.models[0]
setSelectedModelId(modelGroup.group + "-" + userGroupInitModel.title)
}, [userType])
const finishOnboarding = useCallback(async () => {
showOnboarding(false)
onDone()
await StateServiceClient.setWelcomeViewCompleted(BooleanRequest.create({ value: true })).catch((err) =>
console.error("Failed to set welcome view completed:", err),
)
}, [])
const handleFooterAction = useCallback(
(action: "auth" | "next" | "back" | "done") => {
switch (action) {
case "auth":
handleSignIn()
finishOnboarding()
break
case "next":
setStepNumber(stepNumber + 1)
break
case "back":
setStepNumber(stepNumber - 1)
break
case "done":
finishOnboarding()
break
}
},
[handleSignIn, stepNumber],
)
const stepDisplayInfo = useMemo(() => {
const title = stepNumber === 0 ? STEP_CONFIG[0].title : userType ? STEP_CONFIG[userType].title : STEP_CONFIG[0].title
const description = stepNumber === 0 ? STEP_CONFIG[0].description : null
const buttons =
stepNumber === 0 ? STEP_CONFIG[0].buttons : userType ? STEP_CONFIG[userType].buttons : STEP_CONFIG[0].buttons
return { title, description, buttons }
}, [stepNumber, userType])
return (
<div className="fixed inset-0 p-0 flex flex-col">
<div className="h-full px-5 mx-10 overflow-auto flex flex-col gap-7 items-center justify-center">
<div className="flex justify-center my-5">
<ClineLogoWhite className="size-16" />
</div>
<h2 className="text-lg font-semibold">{stepDisplayInfo.title}</h2>
<p className="text-foreground text-center max-w-lg m-0 p-0">{stepDisplayInfo.description}</p>
<div className="flex-1 w-full flex justify-center overflow-y-scroll">
<OnboardingStepContent
onSelectModel={setSelectedModelId}
onSelectUserType={setUserType}
selectedModelId={selectedModelId}
step={stepNumber}
userType={userType}
/>
</div>
<footer className="flex w-full max-w-lg flex-col gap-3 my-2">
{stepDisplayInfo.buttons.map((btn) => (
<Button
className="w-full rounded-xs"
onClick={() => handleFooterAction(btn.action)}
size="lg"
variant={btn.variant}>
{btn.text}
</Button>
))}
<div className="items-center justify-center flex text-sm text-muted-foreground gap-2 mb-3">
<AlertCircleIcon className="size-2" /> You can change this later in settings
</div>
</footer>
</div>
</div>
)
}
export default OnboardingView
@@ -0,0 +1,119 @@
import { Int64Request } from "@shared/proto/index.cline"
import { ArrowLeftIcon, ArrowRightIcon, CopyIcon, RssIcon } from "lucide-react"
import { useCallback, useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { StateServiceClient } from "@/services/grpc-client"
import { RELEASE_NEWS } from "./news"
type WhatsNewProps = {
version?: string
hideAnnouncement: () => void
}
export const CURRENT_MODEL_BANNER_VERSION = 3
export const WhatsNew = (_props: WhatsNewProps) => {
const [currentIndex, setCurrentIndex] = useState(0)
const news = RELEASE_NEWS[currentIndex]
const handleNext = useCallback(() => {
setCurrentIndex((prev) => (prev + 1) % RELEASE_NEWS.length)
StateServiceClient.updateModelBannerVersion(Int64Request.create({ value: CURRENT_MODEL_BANNER_VERSION })).catch(
console.error,
)
}, [])
const handlePrev = useCallback(() => {
setCurrentIndex((prev) => (prev - 1 + RELEASE_NEWS.length) % RELEASE_NEWS.length)
}, [])
const NewsIcon = news.icon
return (
<div className="flex flex-col w-full h-full p-5">
<div className="flex justify-between align-center">
<div className="text-description flex items-center font-medium mb-3 text-xs [&_svg]:size-2 gap-1">
<RssIcon />
<span className="font-medium uppercase">What's New</span>
</div>
<Button
className="font-medium uppercase text-xs text-description hover:text-foreground"
size="text"
variant="icon">
View All
</Button>
</div>
<Card className="pt-4">
<CardHeader className="flex gap-4 flex-col">
<CardTitle className="[&_svg]:size-2 flex items-center gap-2">
{NewsIcon && <NewsIcon />} {news.title}
</CardTitle>
<CardDescription>
{news.description}
{news.action?.link && (
<Button size="text" variant="link">
{news.action.text}
</Button>
)}
</CardDescription>
</CardHeader>
<CardContent className="w-full">
{news.command && (
<div className="mb-3 p-2 rounded flex items-center justify-between bg-code font-mono text-sm">
{news.command}
<Button size="icon" variant="icon">
<CopyIcon />
</Button>
</div>
)}
{news.buttons && news.buttons.length > 0 && (
<div className="gap-3 flex">
{news.buttons.map((button) => {
const ButtonIcon = button.icon
return (
<Button
className="w-full [&_svg]:size-2"
disabled={button.disabled}
key={button.text}
variant={button.variant === "secondary" ? "secondary" : "default"}>
{ButtonIcon && <ButtonIcon />} {button.text}
</Button>
)
})}
</div>
)}
</CardContent>
<CardFooter className="flex justify-between align-center">
<div className="text-xs">
<span>{currentIndex + 1}</span>
<span className="text-muted-foreground">/{RELEASE_NEWS.length}</span>
</div>
<div>
<Button
aria-label="Previous"
className="text-muted-foreground"
disabled={currentIndex === 0}
onClick={handlePrev}
size="icon"
variant="icon">
<ArrowLeftIcon />
</Button>
<Button
aria-label="Next"
className="text-muted-foreground"
disabled={RELEASE_NEWS.length === currentIndex + 1}
onClick={handleNext}
size="icon"
variant="icon">
<ArrowRightIcon />
</Button>
</div>
</CardFooter>
</Card>
</div>
)
}
@@ -0,0 +1,63 @@
type ModelOption = {
title: string
description: string
badge: string
capabilities: string[]
}
type ModelGroup = {
group: string
models: ModelOption[]
}
export const ONBOARDING_MODEL_SELECTIONS: Record<"free" | "power", ModelGroup[]> = {
free: [
{
group: "free",
models: [
{
title: "xAI: Grok Code Fast 1",
description: "Leading model for agentic code",
badge: "Best",
capabilities: ["Images", "Tool Calling", "Prompt Caching"],
},
{
title: "Code Supernova 1 million",
description: "Large 1M context window, great value",
badge: "Trending",
capabilities: ["Tool Calling", "Prompt Caching"],
},
],
},
],
power: [
{
group: "frontier",
models: [
{
title: "Anthropic: Claude Sonnet 4.5",
description: "Leading model for agentic coding",
badge: "Best",
capabilities: ["Images", "Tool Calling", "Prompt Caching"],
},
],
},
{
group: "open source",
models: [
{
title: "Z.AI: GLM 4.6",
description: "Leading model for agentic code",
badge: "Trending",
capabilities: ["Images", "Tool Calling", "Prompt Caching"],
},
{
title: "Moonshot AI: Kimi Dev 72B",
description: "Leading model for agentic coding",
badge: "Value",
capabilities: ["Tool Calling", "Prompt Caching"],
},
],
},
],
}
@@ -0,0 +1,71 @@
import { CheckIcon, LightbulbIcon, LucideProps, TerminalIcon } from "lucide-react"
import { ForwardRefExoticComponent, RefAttributes } from "react"
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
import { isMacOSOrLinux } from "@/utils/platformUtils"
type ButtonIcon = ForwardRefExoticComponent<Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>>
type ReleaseNew = {
icon: ButtonIcon
title: string
description: string
action: { text: string; link: string }
command?: string
buttons: {
text: string
type: "link" | "view"
value: string
variant: "default" | "secondary"
disabled?: boolean
icon?: ButtonIcon
}[]
hidden?: boolean
hideFromStandalone?: boolean
}
export const RELEASE_NOTES = [
{
title: "Cline CLI (Preview)",
description: "Run Cline from the command line with experimental Subagent support.",
link: "http://cline.bot/blog/cline-cli-my-undying-love-of-cline-core",
},
{
title: "Multi-Root Workspaces",
description: "Work across multiple projects simultaneously (Enable in feature settings)",
},
{
title: "Auto-Retry Failed API Requests",
description: "No more interrupted auto-approved tasks due to server errors",
},
]
export const RELEASE_NEWS: ReleaseNew[] = [
{
icon: TerminalIcon,
title: "Cline for CLI is here!",
description:
"Install to use Cline directly in your terminal and enable subagent capabilities. Cline can spawn cline commands to handle focused tasks like exploring large codebases for information. This keeps your main context window clean by running these operations in separate subprocesses.",
action: { text: "Learn More", link: "http://cline.bot/blog/cline-cli-my-undying-love-of-cline-core" },
command: "npm install -g cline",
buttons: [
{
text: "Installed",
type: "link",
value: "https://example.com/feature-a",
variant: "secondary",
icon: CheckIcon,
disabled: true,
},
{ text: "Enable Subagents", type: "view", value: "settings", variant: "default" },
],
hidden: !isMacOSOrLinux() && PLATFORM_CONFIG.type === PlatformType.VSCODE,
},
{
icon: LightbulbIcon,
title: "Cline in the Right Sidebar",
description:
"Keep your files visible when chatting with Cline. Drag the Cline icon to the right sidebar panel for a better experience.",
action: { text: "See how →", link: "https://docs.cline.bot/features/customization/opening-cline-in-sidebar" },
buttons: [],
},
]
@@ -2,15 +2,17 @@ import { StringRequest } from "@shared/proto/cline/common"
import { Mode } from "@shared/storage/types"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import Fuse from "fuse.js"
import { ChevronsUpDownIcon } from "lucide-react"
import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useInterval } from "react-use"
import styled from "styled-components"
import HeroTooltip from "@/components/common/HeroTooltip"
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { highlight } from "../history/HistoryView"
import { Button } from "../ui/button"
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
import { AnthropicProvider } from "./providers/AnthropicProvider"
import { AskSageProvider } from "./providers/AskSageProvider"
@@ -297,20 +299,29 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
</style>
<DropdownContainer className="dropdown-container">
{remoteConfigSettings?.planModeApiProvider !== undefined ? (
<HeroTooltip content="This setting is managed by your organization's remote configuration">
<div className="flex items-center gap-2 mb-1">
<label htmlFor="api-provider">
<span style={{ fontWeight: 500 }}>API Provider</span>
</label>
<i className="codicon codicon-lock text-[var(--vscode-descriptionForeground)] text-sm" />
</div>
</HeroTooltip>
<Tooltip>
<TooltipTrigger>
<div className="flex items-center gap-2 mb-1">
<label htmlFor="api-provider">
<span style={{ fontWeight: 500 }}>API Provider</span>
</label>
<i className="codicon codicon-lock text-description text-sm" />
</div>
</TooltipTrigger>
<TooltipContent>This setting is managed by your organization's remote configuration</TooltipContent>
</Tooltip>
) : (
<label htmlFor="api-provider">
<span style={{ fontWeight: 500 }}>API Provider</span>
</label>
)}
<ProviderDropdownWrapper ref={dropdownRef}>
<Button
className="right-2 absolute pb-1 top-1 z-2000 w-4 shrink-0 opacity-50"
onClick={() => setIsDropdownVisible(true)}
variant="icon">
<ChevronsUpDownIcon />
</Button>
<VSCodeTextField
data-testid="provider-selector-input"
disabled={remoteConfigSettings?.planModeApiProvider !== undefined}
@@ -242,7 +242,7 @@ const BasetenModelPicker: React.FC<BasetenModelPickerProps> = ({ isPopup, curren
</VSCodeTextField>
{isDropdownVisible && (
<div
className="absolute top-[calc(100%-3px)] left-0 w-[calc(100%-2px)] max-h-[200px] overflow-y-auto border border-[var(--vscode-list-activeSelectionBackground)] rounded-b-[3px]"
className="absolute top-[calc(100%-3px)] left-0 w-[calc(100%-2px)] max-h-[200px] overflow-y-auto border border-(--vscode-list-activeSelectionBackground) rounded-b-[3px]"
ref={dropdownListRef}
style={{
backgroundColor: "var(--vscode-dropdown-background)",
@@ -250,8 +250,8 @@ const BasetenModelPicker: React.FC<BasetenModelPickerProps> = ({ isPopup, curren
}}>
{modelSearchResults.map((item, index) => (
<div
className={`px-2.5 py-1.5 cursor-pointer break-all whitespace-normal hover:bg-[var(--vscode-list-activeSelectionBackground)] ${
index === selectedIndex ? "bg-[var(--vscode-list-activeSelectionBackground)]" : ""
className={`px-2.5 py-1.5 cursor-pointer break-all whitespace-normal hover:bg-(--vscode-list-activeSelectionBackground) ${
index === selectedIndex ? "bg-(--vscode-list-activeSelectionBackground)" : ""
}`}
key={item.id}
onClick={() => {
@@ -273,7 +273,7 @@ const BasetenModelPicker: React.FC<BasetenModelPickerProps> = ({ isPopup, curren
{hasInfo ? (
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
) : (
<p className="text-xs mt-0 text-[var(--vscode-descriptionForeground)]">
<p className="text-xs mt-0 text-(--vscode-descriptionForeground)">
The extension automatically fetches the latest list of models available on{" "}
<VSCodeLink className="inline text-inherit" href="https://www.baseten.co/products/model-apis/">
Baseten.
@@ -224,7 +224,7 @@ const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup, currentMode
</VSCodeTextField>
{isDropdownVisible && (
<div
className="absolute top-[calc(100%-3px)] left-0 w-[calc(100%-2px)] max-h-[200px] overflow-y-auto border border-[var(--vscode-list-activeSelectionBackground)] rounded-b-[3px]"
className="absolute top-[calc(100%-3px)] left-0 w-[calc(100%-2px)] max-h-[200px] overflow-y-auto border border-(--vscode-list-activeSelectionBackground) rounded-b-[3px]"
ref={dropdownListRef}
style={{
backgroundColor: "var(--vscode-dropdown-background)",
@@ -232,8 +232,8 @@ const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup, currentMode
}}>
{modelSearchResults.map((item, index) => (
<div
className={`px-2.5 py-1.5 cursor-pointer break-all whitespace-normal hover:bg-[var(--vscode-list-activeSelectionBackground)] ${
index === selectedIndex ? "bg-[var(--vscode-list-activeSelectionBackground)]" : ""
className={`px-2.5 py-1.5 cursor-pointer break-all whitespace-normal hover:bg-(--vscode-list-activeSelectionBackground) ${
index === selectedIndex ? "bg-(--vscode-list-activeSelectionBackground)" : ""
}`}
dangerouslySetInnerHTML={{
__html: item.html,
@@ -255,7 +255,7 @@ const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup, currentMode
{hasInfo ? (
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
) : (
<p className="text-xs mt-0 text-[var(--vscode-descriptionForeground)]">
<p className="text-xs mt-0 text-(--vscode-descriptionForeground)">
The extension automatically fetches the latest list of models available on{" "}
<VSCodeLink className="inline text-inherit" href="https://console.groq.com/docs/models">
Groq.
@@ -171,7 +171,7 @@ const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup
<div className="relative w-full" ref={dropdownRef}>
<VSCodeTextField
className="w-full relative z-[1000]"
className="w-full relative z-1000"
id="hf-model-search"
onFocus={() => setIsDropdownVisible(true)}
onInput={(e: any) => {
@@ -204,13 +204,13 @@ const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup
<div
className={`absolute top-[calc(100%-3px)] left-0 w-[calc(100%-2px)] ${
isPopup ? "max-h-[90px]" : "max-h-[200px]"
} overflow-y-auto bg-[var(--vscode-dropdown-background)] border border-[var(--vscode-list-activeSelectionBackground)] z-[999] rounded-b-[3px]`}
} overflow-y-auto bg-(--vscode-dropdown-background) border border-(--vscode-list-activeSelectionBackground) z-999 rounded-b-[3px]`}
ref={dropdownListRef}>
{modelSearchResults.map((result, index) => (
<div
className={`p-[5px_10px] cursor-pointer break-all whitespace-normal ${
index === selectedIndex ? "bg-[var(--vscode-list-activeSelectionBackground)]" : ""
} hover:bg-[var(--vscode-list-activeSelectionBackground)]`}
index === selectedIndex ? "bg-(--vscode-list-activeSelectionBackground)" : ""
} hover:bg-(--vscode-list-activeSelectionBackground)`}
key={result.id}
onClick={() => {
handleModelChange(result.id)
@@ -219,7 +219,7 @@ const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup
onMouseEnter={() => setSelectedIndex(index)}
ref={(el: HTMLDivElement | null) => (itemRefs.current[index] = el)}>
<div
className="[&_.model-item-highlight]:bg-[var(--vscode-editor-findMatchHighlightBackground)] [&_.model-item-highlight]:text-inherit"
className="[&_.model-item-highlight]:bg-(--vscode-editor-findMatchHighlightBackground) [&_.model-item-highlight]:text-inherit"
dangerouslySetInnerHTML={{ __html: result.html }}
/>
</div>
@@ -12,7 +12,7 @@ const PreferredLanguageSetting: React.FC = () => {
return (
<div style={{}}>
<label className="block mb-1 text-sm font-medium" htmlFor="preferred-language-dropdown">
<label className="block mb-1 text-base font-medium" htmlFor="preferred-language-dropdown">
Preferred Language
</label>
<VSCodeDropdown
@@ -41,9 +41,7 @@ const PreferredLanguageSetting: React.FC = () => {
<VSCodeOption value="Traditional Chinese - 繁體中文">Traditional Chinese - </VSCodeOption>
<VSCodeOption value="Turkish - Türkçe">Turkish - Türkçe</VSCodeOption>
</VSCodeDropdown>
<p className="text-xs text-[var(--vscode-descriptionForeground)] mt-1">
The language that Cline should use for communication.
</p>
<p className="text-sm text-description mt-1">The language that Cline should use for communication.</p>
</div>
)
}
@@ -9,7 +9,7 @@ type SectionHeaderProps = HTMLAttributes<HTMLDivElement> & {
export const SectionHeader = ({ description, children, className, ...props }: SectionHeaderProps) => {
return (
<div className={cn("text-foreground px-5 py-3", className)} {...props}>
<h4 className="m-0">{children}</h4>
<h2 className="m-0 text-base">{children}</h2>
{description && <p className="text-description text-sm mt-2 mb-0">{description}</p>}
</div>
)
@@ -14,7 +14,7 @@ import {
} from "lucide-react"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useEvent } from "react-use"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { StateServiceClient } from "@/services/grpc-client"
import { getEnvironmentColor } from "@/utils/environmentColors"
@@ -33,11 +33,11 @@ const IS_DEV = process.env.IS_DEV
// Styles for the tab system
const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
const settingsTabList =
"w-48 data-[compact=true]:w-12 flex-shrink-0 flex flex-col overflow-y-auto overflow-x-hidden border-r border-[var(--vscode-sideBar-background)]"
"w-48 data-[compact=true]:w-12 shrink-0 flex flex-col overflow-y-auto overflow-x-hidden border-r border-(--vscode-sideBar-background)"
const settingsTabTrigger =
"whitespace-nowrap overflow-hidden min-w-0 h-12 px-4 py-3 box-border flex items-center border-l-2 border-transparent text-[var(--vscode-foreground)] opacity-70 bg-transparent hover:bg-[var(--vscode-list-hoverBackground)] data-[compact=true]:w-12 data-[compact=true]:p-4 cursor-pointer"
"whitespace-nowrap overflow-hidden min-w-0 h-12 px-4 py-3 box-border flex items-center border-l-2 border-transparent text-(--vscode-foreground) opacity-70 bg-transparent hover:bg-(--vscode-list-hoverBackground) data-[compact=true]:w-12 data-[compact=true]:p-4 cursor-pointer"
const settingsTabTriggerActive =
"opacity-100 border-l-2 border-l-[var(--vscode-focusBorder)] border-t-0 border-r-0 border-b-0 bg-[var(--vscode-list-activeSelectionBackground)]"
"opacity-100 border-l-2 border-l-(--vscode-focusBorder) border-t-0 border-r-0 border-b-0 bg-(--vscode-list-activeSelectionBackground)"
// Tab definitions
interface SettingsTab {
@@ -251,16 +251,19 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
if (isCompactMode) {
return (
<HeroTooltip content={tab.tooltipText} key={tab.id} placement="right">
<div
className={tabClassName}
data-compact={isCompactMode}
data-testid={`tab-${tab.id}`}
data-value={tab.id}
onClick={() => handleTabChange(tab.id)}>
{tabContent}
</div>
</HeroTooltip>
<Tooltip key={tab.id}>
<TooltipTrigger>
<div
className={tabClassName}
data-compact={isCompactMode}
data-testid={`tab-${tab.id}`}
data-value={tab.id}
onClick={() => handleTabChange(tab.id)}>
{tabContent}
</div>
</TooltipTrigger>
<TooltipContent side="right">{tab.tooltipText}</TooltipContent>
</Tooltip>
)
}
@@ -302,7 +305,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
<Tab>
<TabHeader className="flex justify-between items-center gap-2">
<div className="flex items-center gap-1">
<h3 className="m-0" style={{ color: titleColor }}>
<h3 className="text-md m-0" style={{ color: titleColor }}>
Settings
</h3>
</div>
@@ -43,7 +43,7 @@ export const BaseUrlField = ({
<VSCodeCheckbox checked={isEnabled} disabled={disabled} onChange={handleToggle}>
{label}
</VSCodeCheckbox>
{showLockIcon && <i className="codicon codicon-lock text-[var(--vscode-descriptionForeground)] text-sm" />}
{showLockIcon && <i className="codicon codicon-lock text-(--vscode-descriptionForeground) text-sm" />}
</div>
{isEnabled && (
@@ -50,19 +50,12 @@ export const ModelSelector = ({ models, selectedModelId, onChange, zIndex, label
return (
<DropdownContainer className="dropdown-container" zIndex={zIndex}>
<label htmlFor="model-id">
<span style={{ fontWeight: 500 }}>{label}</span>
<span className="font-medium">{label}</span>
</label>
<VSCodeDropdown id="model-id" onChange={onChange} style={{ width: "100%" }} value={selectedModelId}>
<VSCodeDropdown className="w-full" id="model-id" onChange={onChange} value={selectedModelId}>
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(models).map((modelId) => (
<VSCodeOption
key={modelId}
style={{
whiteSpace: "normal",
wordWrap: "break-word",
maxWidth: "100%",
}}
value={modelId}>
<VSCodeOption className="break-words whitespace-normal max-w-full" key={modelId} value={modelId}>
{modelId}
</VSCodeOption>
))}
@@ -2,7 +2,7 @@ import { bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX } from "@
import { Mode } from "@shared/storage/types"
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption, VSCodeRadio, VSCodeRadioGroup } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import HeroTooltip from "@/components/common/HeroTooltip"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
@@ -69,12 +69,7 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpoint)
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 5,
}}>
<div className="flex flex-col gap-1">
<VSCodeRadioGroup
onChange={(e) => {
const value = (e.target as HTMLInputElement)?.value
@@ -136,40 +131,44 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
</>
)}
<HeroTooltip
content="This setting is managed by your organization's remote configuration"
disabled={remoteConfigSettings?.awsRegion === undefined}>
<DropdownContainer className="dropdown-container mb-2.5" zIndex={DROPDOWN_Z_INDEX - 1}>
<div className="flex items-center gap-2 mb-1">
<label htmlFor="aws-region-dropdown">
<span className="font-medium">AWS Region</span>
</label>
{remoteConfigSettings?.awsRegion !== undefined && (
<i className="codicon codicon-lock text-description text-sm flex items-center" />
)}
</div>
<VSCodeDropdown
className="w-full"
disabled={remoteConfigSettings?.awsRegion !== undefined}
id="aws-region-dropdown"
onChange={(e: any) => handleFieldChange("awsRegion", e.target.value)}
value={apiConfiguration?.awsRegion || ""}>
<VSCodeOption value="">Select a region...</VSCodeOption>
{/* The user will have to choose a region that supports the model they use, but this shouldn't be a problem since they'd have to request access for it in that region in the first place. */}
{AWS_REGIONS.map((region) => (
<VSCodeOption key={region} value={region}>
{region}
</VSCodeOption>
))}
</VSCodeDropdown>
</DropdownContainer>
</HeroTooltip>
<Tooltip>
<TooltipContent hidden={remoteConfigSettings?.awsRegion === undefined}>
This setting is managed by your organization's remote configuration
</TooltipContent>
<TooltipTrigger>
<DropdownContainer className="dropdown-container mb-2.5" zIndex={DROPDOWN_Z_INDEX - 1}>
<div className="flex items-center gap-2 mb-1">
<label htmlFor="aws-region-dropdown">
<span className="font-medium">AWS Region</span>
</label>
{remoteConfigSettings?.awsRegion !== undefined && (
<i className="codicon codicon-lock text-description text-sm flex items-center" />
)}
</div>
<VSCodeDropdown
className="w-full"
disabled={remoteConfigSettings?.awsRegion !== undefined}
id="aws-region-dropdown"
onChange={(e: any) => handleFieldChange("awsRegion", e.target.value)}
value={apiConfiguration?.awsRegion || ""}>
<VSCodeOption value="">Select a region...</VSCodeOption>
{/* The user will have to choose a region that supports the model they use, but this shouldn't be a problem since they'd have to request access for it in that region in the first place. */}
{AWS_REGIONS.map((region) => (
<VSCodeOption key={region} value={region}>
{region}
</VSCodeOption>
))}
</VSCodeDropdown>
</DropdownContainer>
</TooltipTrigger>
</Tooltip>
<div className="flex flex-col">
<HeroTooltip
content="This setting is managed by your organization's remote configuration"
disabled={remoteConfigSettings?.awsBedrockEndpoint === undefined}>
<div>
<Tooltip>
<TooltipContent hidden={remoteConfigSettings?.awsBedrockEndpoint === undefined}>
This setting is managed by your organization's remote configuration
</TooltipContent>
<TooltipTrigger>
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={awsEndpointSelected}
@@ -190,86 +189,90 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
{awsEndpointSelected && (
<DebouncedTextField
className="mt-0.5 mb-1 text-sm text-description"
disabled={remoteConfigSettings?.awsBedrockEndpoint !== undefined}
initialValue={apiConfiguration?.awsBedrockEndpoint || ""}
onChange={(value) => handleFieldChange("awsBedrockEndpoint", value)}
placeholder="Enter VPC Endpoint URL (optional)"
style={{ width: "100%", marginTop: 3, marginBottom: 5 }}
type="text"
/>
)}
</div>
</HeroTooltip>
</TooltipTrigger>
</Tooltip>
<HeroTooltip
content="This setting is managed by your organization's remote configuration"
disabled={remoteConfigSettings?.awsUseCrossRegionInference === undefined}>
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={apiConfiguration?.awsUseCrossRegionInference || false}
disabled={remoteConfigSettings?.awsUseCrossRegionInference !== undefined}
onChange={(e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsUseCrossRegionInference", isChecked)
}}>
Use cross-region inference
</VSCodeCheckbox>
{remoteConfigSettings?.awsUseCrossRegionInference !== undefined && (
<i className="codicon codicon-lock text-description text-sm flex items-center" />
)}
</div>
</HeroTooltip>
{apiConfiguration?.awsUseCrossRegionInference && selectedModelInfo.supportsGlobalEndpoint && (
<HeroTooltip
content="This setting is managed by your organization's remote configuration"
disabled={remoteConfigSettings?.awsUseGlobalInference === undefined}>
<Tooltip>
<TooltipContent hidden={remoteConfigSettings?.awsUseCrossRegionInference === undefined}>
This setting is managed by your organization's remote configuration
</TooltipContent>
<TooltipTrigger>
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={apiConfiguration?.awsUseGlobalInference || false}
disabled={remoteConfigSettings?.awsUseGlobalInference !== undefined}
checked={apiConfiguration?.awsUseCrossRegionInference || false}
disabled={remoteConfigSettings?.awsUseCrossRegionInference !== undefined}
onChange={(e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsUseGlobalInference", isChecked)
handleFieldChange("awsUseCrossRegionInference", isChecked)
}}>
Use global inference profile
Use cross-region inference
</VSCodeCheckbox>
{remoteConfigSettings?.awsUseGlobalInference !== undefined && (
{remoteConfigSettings?.awsUseCrossRegionInference !== undefined && (
<i className="codicon codicon-lock text-description text-sm" />
)}
</div>
</HeroTooltip>
</TooltipTrigger>
</Tooltip>
{apiConfiguration?.awsUseCrossRegionInference && selectedModelInfo.supportsGlobalEndpoint && (
<Tooltip>
<TooltipContent hidden={remoteConfigSettings?.awsUseGlobalInference === undefined}>
This setting is managed by your organization's remote configuration
</TooltipContent>
<TooltipTrigger>
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={apiConfiguration?.awsUseGlobalInference || false}
disabled={remoteConfigSettings?.awsUseGlobalInference !== undefined}
onChange={(e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsUseGlobalInference", isChecked)
}}>
Use global inference profile
</VSCodeCheckbox>
{remoteConfigSettings?.awsUseGlobalInference !== undefined && (
<i className="codicon codicon-lock text-description text-sm" />
)}
</div>
</TooltipTrigger>
</Tooltip>
)}
{selectedModelInfo.supportsPromptCache && (
<HeroTooltip
content="This setting is managed by your organization's remote configuration"
disabled={remoteConfigSettings?.awsBedrockUsePromptCache === undefined}>
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={apiConfiguration?.awsBedrockUsePromptCache || false}
disabled={remoteConfigSettings?.awsBedrockUsePromptCache !== undefined}
onChange={(e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsBedrockUsePromptCache", isChecked)
}}>
Use prompt caching
</VSCodeCheckbox>{" "}
{remoteConfigSettings?.awsBedrockUsePromptCache !== undefined && (
<i className="codicon codicon-lock text-description text-sm" />
)}
</div>
</HeroTooltip>
<Tooltip>
<TooltipContent hidden={remoteConfigSettings?.awsBedrockUsePromptCache === undefined}>
This setting is managed by your organization's remote configuration
</TooltipContent>
<TooltipTrigger>
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={apiConfiguration?.awsBedrockUsePromptCache || false}
disabled={remoteConfigSettings?.awsBedrockUsePromptCache !== undefined}
onChange={(e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsBedrockUsePromptCache", isChecked)
}}>
Use prompt caching
</VSCodeCheckbox>
{remoteConfigSettings?.awsBedrockUsePromptCache !== undefined && (
<i className="codicon codicon-lock text-description text-sm" />
)}
</div>
</TooltipTrigger>
</Tooltip>
)}
</div>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
<p className="mt-1 text-sm text-description">
{apiConfiguration?.awsUseProfile
? "Using AWS Profile credentials from ~/.aws/credentials. Leave profile name empty to use the default profile. These credentials are only used locally to make API requests from this extension."
: "Authenticate by either providing the keys above or use the default AWS credential providers, i.e. ~/.aws/credentials or environment variables. These credentials are only used locally to make API requests from this extension."}
@@ -311,12 +314,8 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(bedrockModels).map((modelId) => (
<VSCodeOption
className="whitespace-normal wrap-break-word max-w-full"
key={modelId}
style={{
whiteSpace: "normal",
wordWrap: "break-word",
maxWidth: "100%",
}}
value={modelId}>
{modelId}
</VSCodeOption>
@@ -327,16 +326,12 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
{modeFields.awsBedrockCustomSelected && (
<div>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
<p className="mt-1 text-sm text-description">
Select "Custom" when using the Application Inference Profile in Bedrock. Enter the Application
Inference Profile ARN in the Model ID field.
</p>
<DebouncedTextField
className="w-full mt-0.5"
id="bedrock-model-input"
initialValue={modeFields.apiModelId || ""}
onChange={(value) =>
@@ -346,8 +341,7 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
currentMode,
)
}
placeholder="Enter custom model ID..."
style={{ width: "100%", marginTop: 3 }}>
placeholder="Enter custom model ID...">
<span className="font-medium">Model ID</span>
</DebouncedTextField>
<label htmlFor="bedrock-base-model-dropdown">
@@ -371,12 +365,8 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(bedrockModels).map((modelId) => (
<VSCodeOption
className="whitespace-normal wrap-break-word max-w-full"
key={modelId}
style={{
whiteSpace: "normal",
wordWrap: "break-word",
maxWidth: "100%",
}}
value={modelId}>
{modelId}
</VSCodeOption>
@@ -110,7 +110,7 @@ const OcaModelPicker: React.FC<OcaModelPickerProps> = ({
}
`}</style>
<label className="font-medium text-[12px] mt-[10px] mb-[2px]">Model</label>
<div className="relative z-[100] flex items-center gap-2 mb-1">
<div className="relative z-100 flex items-center gap-2 mb-1">
<VSCodeDropdown
className="flex-1 text-[12px] min-h-[24px]"
id="model-id"
@@ -151,7 +151,7 @@ const OcaModelPicker: React.FC<OcaModelPickerProps> = ({
</VSCodeButton>
</div>
{lastRefreshedText ? (
<div className="text-[11px] text-[var(--vscode-descriptionForeground)] mt-0 mb-2">
<div className="text-[11px] text-(--vscode-descriptionForeground) mt-0 mb-2">
Last refreshed at {lastRefreshedText}
</div>
) : null}
@@ -171,7 +171,7 @@ const OcaRestrictivePopup: React.FC<{
onAcknowledge: () => void
bannerText?: string | null
}> = React.memo(({ onAcknowledge, bannerText }) => (
<div className="fixed top-0 left-0 w-screen h-screen z-[2000] [background:rgba(0,0,0,0.25)] flex items-center justify-center">
<div className="fixed top-0 left-0 w-screen h-screen z-2000 [background:rgba(0,0,0,0.25)] flex items-center justify-center">
<div
aria-labelledby="oca-popup-title"
aria-modal="true"
@@ -183,7 +183,7 @@ const OcaRestrictivePopup: React.FC<{
<h4 className={`mb-2 [color:var(${VSC_DESCRIPTION_FOREGROUND},#b3b3b3)] font-semibold`}>
Disclaimer: Prohibited Data Submission
</h4>
<div className="overflow-y-auto flex-1 pr-2 mb-4 text-[13px] leading-[1.5] [color:var(--vscode-foreground,#222)] [mask-image:linear-gradient(to_bottom,black_96%,transparent_100%)]">
<div className="overflow-y-auto flex-1 pr-2 mb-4 text-[13px] leading-normal text-(--vscode-foreground,#222) mask-[linear-gradient(to_bottom,black_96%,transparent_100%)]">
{bannerText && <div dangerouslySetInnerHTML={{ __html: bannerText }} />}
</div>
<div className="text-right">
@@ -6,13 +6,7 @@ import { VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodeProgressRing } from "@v
import React, { useCallback, useEffect, useRef, useState } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient, OcaAccountServiceClient } from "@/services/grpc-client"
import {
VSC_BUTTON_BACKGROUND,
VSC_BUTTON_FOREGROUND,
VSC_DESCRIPTION_FOREGROUND,
VSC_INPUT_BACKGROUND,
VSC_INPUT_BORDER,
} from "@/utils/vscStyles"
import { VSC_BUTTON_BACKGROUND, VSC_BUTTON_FOREGROUND, VSC_DESCRIPTION_FOREGROUND } from "@/utils/vscStyles"
import { BaseUrlField } from "../common/BaseUrlField"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import OcaModelPicker from "./OcaModelPicker"
@@ -28,8 +22,8 @@ interface OcaProviderProps {
function InfoCard({ icon, children }: { icon: React.ReactNode; children: React.ReactNode }) {
return (
<div
className={`mt-2 mb-2 flex items-start gap-3 rounded-none px-5 py-4 pb-8 border shadow-sm min-w-[40%] max-w-[90%] w-full box-border
bg-[var(${VSC_INPUT_BACKGROUND})] border-[var(${VSC_INPUT_BORDER})]`}>
className="mt-2 mb-2 flex items-start gap-3 rounded-none px-5 py-4 pb-8 border shadow-sm min-w-[40%] max-w-[90%] w-full box-border
bg-input-background border-input-border">
<div className="min-w-[22px] h-[22px] flex items-center justify-center shrink-0 mt-2">{icon}</div>
<div className="flex-1">{children}</div>
</div>
@@ -306,7 +300,7 @@ export const OcaProvider = ({ isPopup, currentMode }: OcaProviderProps) => {
}}>
Sign in with Oracle Code Assist
</VSCodeButton>
<p className="text-xs mt-0 text-[var(--vscode-descriptionForeground)]">
<p className="text-xs mt-0 text-(--vscode-descriptionForeground)">
Please ask your IT administrator to set up Oracle Code Assist as a model provider. Oracle Employees,
please see the{" "}
<VSCodeLink

Some files were not shown because too many files have changed in this diff Show More