mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe41da67b6 | |||
| 08cef66956 | |||
| 25e1e0a58b | |||
| 1b0a63f1cc | |||
| 89ecf79c19 | |||
| 302bc4f1b4 | |||
| 4da7817b7e | |||
| fd9ecb1c7f | |||
| cf41695823 | |||
| 54fc95dbe8 | |||
| 2969810a44 | |||
| c1b526c154 | |||
| ed58c05dc2 | |||
| 2c2f1bc3e1 | |||
| b5890d341f | |||
| 5e29e66aca | |||
| a83354a7e4 | |||
| 7b5400805a | |||
| 2b97a097e4 | |||
| cfe94739ef | |||
| bdfb7ff9c5 | |||
| 57b0763b8b | |||
| 6745bdc79b | |||
| 899af48677 | |||
| 4e645ac853 | |||
| dc99753a59 | |||
| 6e57263eca | |||
| 801310222e | |||
| cab5183297 | |||
| 870e58280d | |||
| 010a23c795 | |||
| da9dd52bf2 | |||
| ff5b6584bc | |||
| 04204c14bf | |||
| 40a5f5ffad | |||
| 9fff0bd34d | |||
| 2e7dd7f841 | |||
| 18cf97431c | |||
| 805fb85371 | |||
| 488e377a44 | |||
| 16a378840b | |||
| 0bbc718766 | |||
| 74c6d1d2bc | |||
| 82b549c2ca |
@@ -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
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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,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> = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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}`),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "../../../")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1173
-14
File diff suppressed because it is too large
Load Diff
@@ -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.
@@ -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" })
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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("")
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
Generated
+918
-24
File diff suppressed because it is too large
Load Diff
+15
-2
@@ -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"
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+16
-13
@@ -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
|
||||
@@ -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, " ")
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user