From e81147a8f73a592012127f485180fd0fb8d84bac Mon Sep 17 00:00:00 2001 From: Trevor Hudson Date: Tue, 29 Apr 2025 20:10:42 -0700 Subject: [PATCH] onboarding screen iteration --- .vscode/launch.json | 26 +- .vscode/tasks.json | 6 + package.json | 7 +- src/core/controller/index.ts | 1 + src/core/webview/index.ts | 7 + src/core/welcome/WelcomeTabProvider.ts | 137 -- src/extension.ts | 42 +- src/shared/ExtensionMessage.ts | 1 + src/shared/WebviewMessage.ts | 2 + webview-ui/src/App.tsx | 6 +- webview-ui/src/assets/css/styles.css | 1272 ++++++++--------- webview-ui/src/assets/images/act-line.svg | 3 + .../src/assets/images/disabled-line.svg | 3 + webview-ui/src/assets/images/plan-line.svg | 3 + webview-ui/src/assets/js/script.js | 126 -- webview-ui/src/assets/welcome-tab.html | 406 ------ .../components/welcome/ApiOptionsSection.tsx | 42 + .../src/components/welcome/WelcomeTabView.tsx | 550 +++++++ .../src/components/welcome/WelcomeView.tsx | 37 +- .../src/components/welcome/WelcomeWrapper.tsx | 23 + .../src/context/ExtensionStateContext.tsx | 29 +- 21 files changed, 1258 insertions(+), 1471 deletions(-) delete mode 100644 src/core/welcome/WelcomeTabProvider.ts create mode 100644 webview-ui/src/assets/images/act-line.svg create mode 100644 webview-ui/src/assets/images/disabled-line.svg delete mode 100644 webview-ui/src/assets/js/script.js delete mode 100644 webview-ui/src/assets/welcome-tab.html create mode 100644 webview-ui/src/components/welcome/ApiOptionsSection.tsx create mode 100644 webview-ui/src/components/welcome/WelcomeTabView.tsx create mode 100644 webview-ui/src/components/welcome/WelcomeWrapper.tsx diff --git a/.vscode/launch.json b/.vscode/launch.json index bc1cfa5dd7..7a20202985 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,26 +21,22 @@ "name": "Run Extension (Fresh Install Mode)", "type": "extensionHost", "request": "launch", - "args": ["--extensionDevelopmentPath=${workspaceFolder} --profile-temp"], + "runtimeExecutable": "${execPath}", + "args": [ + "--profile-temp", + "--sync", "off", + "--disable-extensions", + "--extensionDevelopmentPath=${workspaceFolder}", + "${workspaceFolder}" + ], "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "${defaultBuildTask}", + "preLaunchTask": "clean-sandbox", + "internalConsoleOptions": "openOnSessionStart", + "postDebugTask": "stop", "env": { "IS_DEV": "true", "DEV_WORKSPACE_FOLDER": "${workspaceFolder}" } - }, - { - "name": "Run Extension (Test Mode)", - "type": "extensionHost", - "request": "launch", - "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "${defaultBuildTask}", - "env": { - "IS_DEV": "true", - "IS_TEST": "true", - "DEV_WORKSPACE_FOLDER": "${workspaceFolder}" - } } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7b15a58a1a..90906e7ba5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -185,6 +185,12 @@ "label": "stop", "command": "echo ${input:terminate}", "type": "shell" + }, + { + "label": "clean-sandbox", + "type": "shell", + "dependsOn": ["watch"], + "command": "rm -rf .vscode-dev" } ], "inputs": [ diff --git a/package.json b/package.json index 878511be0e..7cfe95cf79 100644 --- a/package.json +++ b/package.json @@ -285,11 +285,6 @@ "type": "boolean", "default": true, "description": "Controls whether the MCP Marketplace is enabled." - }, - "cline.showWelcomeTabOnInstall": { - "type": "boolean", - "default": true, - "description": "Controls whether the welcome tab is shown when the extension is installed." } } } @@ -419,4 +414,4 @@ "web-tree-sitter": "^0.22.6", "zod": "^3.24.2" } -} +} \ No newline at end of file diff --git a/src/core/controller/index.ts b/src/core/controller/index.ts index 917c3a61d7..b32f226cd6 100644 --- a/src/core/controller/index.ts +++ b/src/core/controller/index.ts @@ -1704,6 +1704,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont mcpMarketplaceEnabled, telemetrySetting, planActSeparateModelsSetting, + showWelcome: false, vscMachineId: vscode.env.machineId, globalClineRulesToggles: globalClineRulesToggles || {}, localClineRulesToggles: localClineRulesToggles || {}, diff --git a/src/core/webview/index.ts b/src/core/webview/index.ts index 116d921d45..e14779ceea 100644 --- a/src/core/webview/index.ts +++ b/src/core/webview/index.ts @@ -5,9 +5,13 @@ import { getUri } from "./getUri" import { getTheme } from "@integrations/theme/getTheme" import { Controller } from "@core/controller/index" import { findLast } from "@shared/array" +<<<<<<< HEAD import { readFile } from "fs/promises" import path from "node:path" +======= +import { WebviewType } from "@shared/WebviewMessage" +>>>>>>> 7d06d3ab (onboarding screen iteration) /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts @@ -24,6 +28,7 @@ export class WebviewProvider implements vscode.WebviewViewProvider { constructor( readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel, + private readonly webviewType: WebviewType = "sidebar", ) { WebviewProvider.activeInstances.add(this) this.controller = new Controller(context, outputChannel, (message) => this.view?.webview.postMessage(message)) @@ -65,6 +70,7 @@ export class WebviewProvider implements vscode.WebviewViewProvider { webviewView.webview.options = { // Allow scripts in the webview enableScripts: true, + localResourceRoots: [this.context.extensionUri], } @@ -331,6 +337,7 @@ export class WebviewProvider implements vscode.WebviewViewProvider { + diff --git a/src/core/welcome/WelcomeTabProvider.ts b/src/core/welcome/WelcomeTabProvider.ts deleted file mode 100644 index 0b8510a105..0000000000 --- a/src/core/welcome/WelcomeTabProvider.ts +++ /dev/null @@ -1,137 +0,0 @@ -import * as vscode from "vscode" -import * as path from "path" -import { Logger } from "../../services/logging/Logger" -import { getUri } from "../webview/getUri" - -export class WelcomeTabProvider { - private static readonly WELCOME_TAB_SHOWN_KEY = "cline.welcomeTabShown" - - constructor(private readonly context: vscode.ExtensionContext) {} - - /** - * Shows the welcome tab if it hasn't been shown before and is enabled in settings - */ - public async showWelcomeTabIfNeeded(): Promise { - // Check if we've already shown the welcome tab - const hasShownWelcomeTab = this.context.globalState.get(WelcomeTabProvider.WELCOME_TAB_SHOWN_KEY, false) - - // Check if showing the welcome tab on install is enabled in settings - const showWelcomeTabOnInstall = vscode.workspace.getConfiguration("cline").get("showWelcomeTabOnInstall", true) - - if (!hasShownWelcomeTab && showWelcomeTabOnInstall) { - await this.showWelcomeTab() - // Mark that we've shown the welcome tab - await this.context.globalState.update(WelcomeTabProvider.WELCOME_TAB_SHOWN_KEY, true) - } - } - - /** - * Shows the welcome tab - */ - public async showWelcomeTab(): Promise { - Logger.log("Showing welcome tab") - - // Create and show the webview panel - const panel = vscode.window.createWebviewPanel( - "clineWelcomeTab", // Unique ID - "Welcome to Cline", // Title displayed in the tab - vscode.ViewColumn.One, // Show in the first column - { - enableScripts: true, - localResourceRoots: [this.context.extensionUri], - }, - ) - - // Set the icon for the tab - panel.iconPath = { - light: vscode.Uri.joinPath(this.context.extensionUri, "assets", "icons", "robot_panel_light.png"), - dark: vscode.Uri.joinPath(this.context.extensionUri, "assets", "icons", "robot_panel_dark.png"), - } - - // Get the content of the welcome tab - panel.webview.html = await this.getWelcomeTabContent(panel.webview) - - // Handle messages from the webview - panel.webview.onDidReceiveMessage( - (message) => { - switch (message.command) { - case "openCline": - // Open Cline when the button is clicked - vscode.commands.executeCommand("cline.openInNewTab") - return - } - }, - undefined, - this.context.subscriptions, - ) - - return panel - } - - /** - * Gets the HTML content for the welcome tab - */ - private async getWelcomeTabContent(webview: vscode.Webview): Promise { - // Get the path to the welcome tab HTML file - const htmlPath = vscode.Uri.joinPath(this.context.extensionUri, "webview-ui", "src", "assets", "welcome-tab.html") - - // Read the HTML file - const htmlContent = await vscode.workspace.fs.readFile(htmlPath) - let htmlString = Buffer.from(htmlContent).toString("utf-8") - - // Get the base path for assets - const assetsPath = vscode.Uri.joinPath(this.context.extensionUri, "webview-ui", "src", "assets") - - // Convert resource paths to webview URIs - const cssUri = webview.asWebviewUri(vscode.Uri.joinPath(assetsPath, "css", "styles.css")) - const jsUri = webview.asWebviewUri(vscode.Uri.joinPath(assetsPath, "js", "script.js")) - - // Replace CSS and JS paths - htmlString = htmlString.replace("css/styles.css", cssUri.toString()) - htmlString = htmlString.replace("js/script.js", jsUri.toString()) - - // Replace image paths - const imageFiles = [ - "back-icon.svg", - "vector.svg", - "vector-1.svg", - "feature-icon-1.svg", - "feature-icon-2.svg", - "feature-icon-3.svg", - "feature-icon-4.svg", - "action-icon-1.svg", - "action-icon-2.svg", - "button-icon-1.svg", - "anthropic-model.jpg", - "google-model.jpg", - "meta-model.jpg", - "feature-bg.png", - "50.svg", - ] - - for (const imageFile of imageFiles) { - const imageUri = webview.asWebviewUri(vscode.Uri.joinPath(assetsPath, "images", imageFile)) - htmlString = htmlString.replace(`images/${imageFile}`, imageUri.toString()) - } - - // Add Content Security Policy - const csp = ` - - ` - - // Insert CSP meta tag into the head section - htmlString = htmlString.replace("", `${csp}`) - - // Get the logo path (if needed) - const logoUri = webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, "assets", "icons", "icon.png")) - - // Replace the placeholder with the actual logo path (if needed) - htmlString = htmlString.replace("{{logoPath}}", logoUri.toString()) - - return htmlString - } -} diff --git a/src/extension.ts b/src/extension.ts index 1d39780fdb..3d2adf33e3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,6 @@ import { telemetryService } from "./services/telemetry/TelemetryService" import { WebviewProvider } from "./core/webview" import { ErrorService } from "./services/error/ErrorService" import { initializeTestMode, cleanupTestMode } from "./services/test/TestMode" -import { WelcomeTabProvider } from "./core/welcome/WelcomeTabProvider" /* Built using https://github.com/microsoft/vscode-webview-ui-toolkit @@ -35,24 +34,7 @@ export function activate(context: vscode.ExtensionContext) { Logger.initialize(outputChannel) Logger.log("Cline extension activated") - // Create the welcome tab provider - const welcomeTabProvider = new WelcomeTabProvider(context) - - // Show the welcome tab if it hasn't been shown before - welcomeTabProvider.showWelcomeTabIfNeeded().catch((err) => { - Logger.error("Failed to show welcome tab", err) - }) - - // Register command to manually show the welcome tab - context.subscriptions.push( - vscode.commands.registerCommand("cline.showWelcomeTab", () => { - welcomeTabProvider.showWelcomeTab().catch((err) => { - Logger.error("Failed to show welcome tab", err) - }) - }), - ) - - const sidebarWebview = new WebviewProvider(context, outputChannel) + const sidebarWebview = new WebviewProvider(context, outputChannel, "sidebar") // Initialize test mode and add disposables to context context.subscriptions.push(...initializeTestMode(context, sidebarWebview)) @@ -104,7 +86,7 @@ export function activate(context: vscode.ExtensionContext) { Logger.log("Opening Cline in new tab") // (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event) // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts - const tabWebview = new WebviewProvider(context, outputChannel) + const tabWebview = new WebviewProvider(context, outputChannel, "tab") //const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0)) @@ -133,6 +115,8 @@ export function activate(context: vscode.ExtensionContext) { await vscode.commands.executeCommand("workbench.action.lockEditorGroup") } + checkInstallationStatus(context) + context.subscriptions.push(vscode.commands.registerCommand("cline.popoutButtonClicked", openClineInNewTab)) context.subscriptions.push(vscode.commands.registerCommand("cline.openInNewTab", openClineInNewTab)) @@ -441,6 +425,24 @@ export function activate(context: vscode.ExtensionContext) { ) return createClineAPI(outputChannel, sidebarWebview.controller) + + async function checkInstallationStatus(context: vscode.ExtensionContext) { + const VERSION_KEY = "cline.version" + const current = context.extension.packageJSON.version as string + const previous = context.globalState.get(VERSION_KEY) + + if (!previous) { + // ── first-ever install ── + WebviewProvider.getTabInstances().forEach(async (tab) => { + const state = await tab.controller.getStateToPostToWebview() + state.showWelcome = true + await tab.controller.postMessageToWebview({ type: "state", state }) + }) + openClineInNewTab() + } + // persist for the next activation + await context.globalState.update(VERSION_KEY, current) + } } // TODO: Find a solution for automatically removing DEV related content from production builds. diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 89a326c4e2..1edd97e46c 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -143,6 +143,7 @@ export interface ExtensionState { localClineRulesToggles: ClineRulesToggles localCursorRulesToggles: ClineRulesToggles localWindsurfRulesToggles: ClineRulesToggles + showWelcome: boolean } export interface ClineMessage { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index c70799b289..56497c25f1 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -125,3 +125,5 @@ export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "message export type ClineCheckpointRestore = "task" | "workspace" | "taskAndWorkspace" export type TaskFeedbackType = "thumbs_up" | "thumbs_down" + +export type WebviewType = "sidebar" | "tab" diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 6e7911ffe0..f55e217656 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -1,10 +1,10 @@ -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect, useMemo, useState } from "react" import { useEvent } from "react-use" import { ExtensionMessage } from "@shared/ExtensionMessage" +import { WebviewType } from "@shared/WebviewMessage" import ChatView from "./components/chat/ChatView" import HistoryView from "./components/history/HistoryView" import SettingsView from "./components/settings/SettingsView" -import WelcomeView from "./components/welcome/WelcomeView" import AccountView from "./components/account/AccountView" import { useExtensionState } from "./context/ExtensionStateContext" import { vscode } from "./utils/vscode" @@ -97,7 +97,7 @@ const AppContent = () => { return ( <> {showWelcome ? ( - + ) : ( <> {showSettings && } diff --git a/webview-ui/src/assets/css/styles.css b/webview-ui/src/assets/css/styles.css index 60886b5e45..9d58d96482 100644 --- a/webview-ui/src/assets/css/styles.css +++ b/webview-ui/src/assets/css/styles.css @@ -1,943 +1,811 @@ -/* Global Styles */ -:root { - /* Colores exactos del diseño Figma actualizado */ - --background-color: #181818; /* fill_8WL57W */ - --container-bg: #1c1c1c; /* fill_4TPDFH */ - --card-bg: #2b2b2b; /* fill_19R5B2 */ - --input-bg: #313131; /* fill_XT87QJ */ - --primary-color: #0078d4; /* fill_GFEHTQ */ - --secondary-color: #313131; /* fill_XT87QJ */ - --text-color: #ffffff; /* fill_UEF7SS */ - --text-secondary: #cccccc; - --text-placeholder: #c5c5c5; - --border-color: #333333; /* stroke_H0B5JR */ - --border-light: #3d3d3d; /* stroke_Y5VPFU */ - --border-card: #414141; /* stroke_H220VQ */ - --border-input: #3c3c3c; /* stroke_ZPDGQX */ - --border-primary: #2282d7; /* stroke_II32J1 */ - --border-tab: #4e4e4e; /* stroke_K3DJNL */ - --link-color: #4daafc; /* fill_AJL987 */ - --active-tab-color: #026ec1; /* fill_R3WUBX */ - --social-bg: #390e67; /* fill_DWNHUJ */ - --social-card-bg: #18181b; /* fill_73UY73 */ - --social-card-purple: #c8abff; /* fill_PRU1UJ */ - --social-card-purple-text: #8623de; /* fill_A5PYCT */ - --social-card-violet: #9535f2; /* fill_GCX7BL */ - --social-card-violet-text: #f7f3ff; /* fill_K63Y8F */ +/* Welcome Tab Styles */ - /* Espaciados exactos */ - --gap-small: 8px; - --gap-medium: 16px; - --gap-large: 24px; - --gap-xlarge: 40px; - --gap-xxlarge: 80px; - - /* Radios de borde */ - --radius-small: 4px; - --radius-medium: 8px; - --radius-large: 12px; - --radius-xlarge: 14px; - --radius-round: 9999px; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: "SF Pro", "Inter", sans-serif; - background-color: var(--background-color); - color: var(--text-color); - line-height: 1.3; - letter-spacing: -0.01em; -} - -/* Layout */ +/* General Styles */ .home { - display: flex; - flex-direction: column; - align-items: center; - padding: var(--gap-large); - min-height: 100vh; + font-family: 'Inter', sans-serif; + color: var(--vscode-foreground); + background-color: var(--vscode-editor-background); + height: 100vh; + overflow-y: auto; } .main-container { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--gap-xxlarge); - padding: 16px var(--gap-large); - width: 100%; - max-width: 1200px; + max-width: 1200px; + margin: 0 auto; + padding: 20px; } -/* Back Button */ -.back-button-container { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--gap-small); - width: 100%; - margin-bottom: 20px; +.divider { + height: 1px; + background-color: var(--vscode-panel-border); + margin: 40px 0; } -.back-icon { - display: flex; - align-items: center; - justify-content: center; +.divider-dashed { + height: 1px; + background: repeating-linear-gradient( + to right, + var(--vscode-panel-border), + var(--vscode-panel-border) 6px, + transparent 6px, + transparent 12px + ); + margin: 20px 0; } -.back-text { - font-size: 14px; - font-weight: 400; - color: var(--link-color); -} - -/* Account Info */ +/* Account Info Section */ .account-info { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--gap-xlarge); - width: 100%; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px 0; } .vector-icon { - width: 48px; - height: 48px; + width: 60px; + height: 60px; + margin-bottom: 20px; } .welcome-message-container { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--gap-large); + text-align: center; + margin-bottom: 30px; } .welcome-message { - font-size: 48px; - font-weight: 510; - text-align: center; - line-height: 1em; + font-size: 32px; + font-weight: 600; + margin-bottom: 10px; + color: var(--vscode-foreground); } .description { - font-family: "Inter", sans-serif; - font-size: 18px; - font-weight: 400; - text-align: center; - line-height: 1.3em; - letter-spacing: -1%; + font-size: 16px; + color: var(--vscode-descriptionForeground); } -/* Buttons */ .buttons-container { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: var(--gap-medium); - width: 100%; + display: flex; + gap: 10px; + margin-bottom: 30px; } .primary-button { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--gap-small); - padding: 0 16px; - height: 36px; - background-color: var(--primary-color); - border: 1px solid var(--border-primary); - border-radius: var(--radius-small); - color: var(--text-color); - font-size: 14px; - font-weight: 510; - line-height: 1.3em; - letter-spacing: -1%; - cursor: pointer; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; +} + +.primary-button:hover { + background-color: var(--vscode-button-hoverBackground); } .secondary-button { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: var(--gap-small); - padding: 0 16px; - width: 208px; - height: 36px; - background-color: var(--secondary-color); - border: 1px solid var(--border-light); - border-radius: var(--radius-small); - color: var(--text-color); - font-size: 14px; - font-weight: 510; - line-height: 1.3em; - letter-spacing: -1%; - cursor: pointer; + background-color: transparent; + color: var(--vscode-button-secondaryForeground); + border: 1px solid var(--vscode-button-secondaryBackground); + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; +} + +.secondary-button:hover { + background-color: var(--vscode-button-secondaryHoverBackground); } /* Features Container */ .features-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: var(--gap-medium); - padding: var(--gap-medium); - width: 100%; - background-color: var(--container-bg); - border: 1px solid var(--border-color); - border-radius: var(--radius-large); + width: 100%; + max-width: 800px; + margin: 0 auto; } .feature-row { - display: flex; - flex-direction: row; - justify-content: stretch; - align-items: stretch; - gap: var(--gap-medium); - width: 100%; - flex-wrap: wrap; -} - -@media (max-width: 768px) { - .feature-row { - flex-direction: column; - } + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; + margin-bottom: 30px; } .feature-card { - display: flex; - flex-direction: column; - gap: 12px; - padding: 10px; - flex: 1; - min-width: 200px; - background-color: var(--card-bg); - border: 1px solid var(--border-card); - border-radius: var(--radius-medium); + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 8px; + padding: 16px; + transition: all 0.3s ease; +} + +.feature-card:hover { + border-color: var(--vscode-focusBorder); + transform: translateY(-2px); } .feature-icon-container { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--gap-small); - width: 100%; + display: flex; + align-items: center; + margin-bottom: 12px; } .feature-icon { - width: 20px; - height: 20px; + width: 24px; + height: 24px; + margin-right: 10px; +} + +.feature-icon-1 { + background-image: url('../images/feature-icon-1.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.feature-icon-2 { + background-image: url('../images/feature-icon-2.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.feature-icon-3 { + background-image: url('../images/feature-icon-3.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.feature-icon-4 { + background-image: url('../images/feature-icon-4.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.action-icon-1 { + background-image: url('../images/action-icon-1.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.action-icon-2 { + background-image: url('../images/action-icon-2.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.button-icon-1 { + background-image: url('../images/button-icon-1.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; } .feature-title { - font-size: 13px; - font-weight: 400; - line-height: 1; + font-size: 16px; + font-weight: 500; } .feature-description { - font-size: 14px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; - color: var(--text-secondary); + font-size: 14px; + color: var(--vscode-descriptionForeground); + margin-top: 8px; } /* Footer */ .footer { - display: flex; - flex-direction: column; - justify-content: center; - width: 100%; - gap: var(--gap-medium); + margin-top: 30px; } .input-field { - display: flex; - flex-direction: column; - justify-content: space-between; - width: 100%; - gap: var(--gap-small); - padding: 12px; - height: 104px; - background-color: var(--input-bg); - border: 1px solid var(--border-input); - border-radius: 6px; + background-color: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + padding: 12px; + position: relative; + cursor: text; } .input-placeholder { - font-size: 14px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; - color: var(--text-placeholder); + color: var(--vscode-input-placeholderForeground); + font-size: 14px; } .input-actions-container { - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; - width: 100%; - gap: 10px; + margin-top: 12px; } .input-actions { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--gap-medium); + display: flex; + justify-content: flex-end; + gap: 10px; } .action-button { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: var(--gap-small); - padding: 7px 8px; - border-radius: var(--radius-small); - font-size: 14px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; - cursor: pointer; - opacity: 0.5; + display: flex; + align-items: center; + background: none; + border: none; + cursor: pointer; + padding: 6px 12px; + border-radius: 4px; + transition: all 0.2s ease; } .action-button:hover { - opacity: 1; + opacity: 1 !important; } .action-button-secondary { - background-color: var(--secondary-color); - border: 1px solid var(--border-light); - color: var(--text-color); + color: var(--vscode-button-secondaryForeground); +} + +.action-button-secondary:hover { + background-color: var(--vscode-button-secondaryHoverBackground); } .action-button-primary { - background-color: var(--primary-color); - border: 1px solid var(--border-primary); - color: var(--text-color); + color: var(--vscode-button-foreground); + background-color: var(--vscode-button-background); +} + +.action-button-primary:hover { + background-color: var(--vscode-button-hoverBackground); } .action-icon { - width: 14px; - height: 14px; + width: 16px; + height: 16px; + margin-right: 6px; } .action-text { - font-size: 14px; - font-weight: 400; -} - -/* Divider */ -.divider { - width: 100%; - height: 1px; - background-color: var(--border-color); - border: none; - border-top: 1px dashed var(--border-color); - margin: 20px 0; -} - -.divider-dashed { - width: 100%; - height: 1px; - background-color: transparent; - border: none; - border-top: 1px dashed var(--border-color); - margin: 0; + font-size: 12px; } /* Features Section */ .features-section-container { - display: grid; - grid-template-columns: 1fr 1fr; - width: 100%; - gap: var(--gap-xlarge); - margin-bottom: 40px; -} - -@media (max-width: 992px) { - .features-section-container { - grid-template-columns: 1fr; - justify-items: center; - } + display: grid; + grid-template-columns: 1fr 1fr; + gap: 40px; + margin-bottom: 40px; } .features-text-container { - display: flex; - flex-direction: column; - gap: var(--gap-large); - flex: 1; + padding-right: 20px; } .features-heading { - font-size: 32px; - font-weight: 510; - max-width: 320px; - line-height: 1.2em; + font-size: 32px; + font-weight: 600; + margin-bottom: 16px; + font-family: 'SF Pro', 'Inter', sans-serif; } .features-description { - font-family: "Inter", sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; + font-size: 16px; + color: var(--vscode-descriptionForeground); + margin-bottom: 20px; + line-height: 1.3; + letter-spacing: -0.01em; } .features-tabs-container { - display: flex; - flex-direction: row; - width: 100%; - flex-wrap: wrap; - gap: var(--gap-small); + display: flex; + gap: 8px; + margin-bottom: 24px; } .tab-button { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: var(--gap-small); - padding: 0 16px; - height: 32px; - background-color: transparent; - border: 1px solid var(--border-tab); - border-radius: var(--radius-round); - color: var(--text-color); - font-family: "Inter", sans-serif; - font-size: 14px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; - cursor: pointer; + background-color: #181818; + border: 1px solid #4E4E4E; + border-radius: 9999px; + padding: 0px 16px; + height: 32px; + font-size: 14px; + cursor: pointer; + color: var(--vscode-foreground); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; } -.tab-button.active { - background-color: var(--active-tab-color); - border: none; +.tab-button.active-tab { + background-color: #026EC1; + border: none; + font-weight: 500; } -.container { - display: flex; - flex-direction: column; - width: 100%; - gap: var(--gap-medium); +.feature-content { + margin-top: 24px; } -.additional-feedback-container { - display: flex; - flex-direction: row; - width: 100%; - gap: var(--gap-medium); - padding: var(--gap-medium); +.feature-selected { + display: flex; + align-items: center; + margin-bottom: 20px; } -.feature-highlight { - background-color: var(--card-bg); - border: 1px solid var(--border-card); - border-radius: var(--radius-large); +.feature-check-icon { + width: 20px; + height: 20px; + background-color: #0078D4; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + margin-right: 12px; +} + +.feature-info h3 { + font-size: 15px; + font-weight: 600; + margin: 0 0 8px 0; +} + +.feature-subtitle { + font-size: 14px; + color: #CCCCCC; + margin: 0; +} + +.feature-buttons-container { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 20px; + border-top: 1px dashed #3D3D3D; + border-bottom: 1px dashed #3D3D3D; + padding: 16px 0; +} + +.feature-action-button { + display: flex; + align-items: center; + background: none; + border: 1px solid #3D3D3D; + padding: 0 8px; + height: 28px; + cursor: pointer; + border-radius: 8px; + color: white; + font-size: 14px; + transition: background-color 0.2s; +} + +.feature-action-button:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.feature-action-icon { + width: 16px; + height: 16px; + margin-right: 8px; +} + +.feature-options { + display: flex; + flex-direction: column; + gap: 16px; } .feature-option { - border-radius: var(--radius-large); + display: flex; + align-items: center; + cursor: pointer; + padding: 8px 0; } -.icon-container { - display: flex; - justify-content: center; - align-items: center; - width: 20px; - height: 20px; - background-color: var(--primary-color); - border-radius: var(--radius-round); -} - -.vector-1-icon { - width: 12px; - height: 12px; - stroke: var(--text-color); - stroke-width: 1.5px; -} - -.prompt-container { - display: flex; - flex-direction: column; - gap: var(--gap-medium); - padding: 1px 0 0; - flex: 1; -} - -.prompt-text-container { - display: flex; - flex-direction: column; - width: 100%; - gap: var(--gap-small); -} - -h3.feature-title { - font-size: 15px; - font-weight: 510; - line-height: 1em; -} - -.prompt-buttons-container { - display: flex; - flex-direction: row; - width: 100%; - flex-wrap: wrap; - gap: var(--gap-small); -} - -.feature-button { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: var(--gap-small); - padding: 0 8px; - height: 28px; - background-color: transparent; - border: 1px solid var(--border-light); - border-radius: var(--radius-medium); - color: var(--text-color); - font-size: 14px; - font-weight: 400; - line-height: 1em; - cursor: pointer; -} - -.button-icon { - width: 14px; - height: 14px; -} - -.button-text { - font-size: 14px; - font-weight: 400; - line-height: 1; +.feature-option:hover { + opacity: 0.8; } .feature-circle { - width: 24px; - height: 24px; - border: 1.5px solid var(--primary-color); - border-radius: var(--radius-round); + width: 20px; + height: 20px; + border-radius: 50%; + border: 1.5px solid #0078D4; + margin-right: 12px; +} + +.feature-option span { + font-size: 15px; + font-weight: 500; } .feature-image-container { - position: relative; - width: 100%; - height: 100%; - display: block; - overflow: hidden; + display: flex; + align-items: center; + justify-content: center; } .feature-bg { - width: 100%; - height: 100%; - background-image: url("../images/feature-bg.png"); - background-size: cover; - background-position: center; - border-radius: var(--radius-xlarge); - display: block; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - object-fit: fill; + width: 100%; + height: 100%; + background-image: url('../images/feature-bg.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + border-radius: 14px; + position: relative; + overflow: hidden; + min-height: 400px; } -.feature-image { - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - height: 100%; - width: auto; - object-fit: contain; - z-index: 2; +.feature-diagram { + position: relative; + width: 100%; + height: 100%; } -@media (max-width: 992px) { - .feature-image-container { - margin-top: 30px; - width: 100%; - max-width: 500px; - height: 400px; - } +.feature-pill { + position: absolute; + background-color: white; + color: #0A4B8C; + border-radius: 50px; + padding: 8px 16px; + display: flex; + align-items: center; + font-weight: 500; + font-size: 14px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.feature-pill-icon { + width: 16px; + height: 16px; + margin-right: 8px; +} + +.terminal-icon, .code-icon, .browser-icon { + margin-right: 8px; + font-weight: bold; +} + +.file-editing { + top: 15%; + right: 10%; +} + +.terminal-commands { + top: 40%; + left: 10%; +} + +.code-analysis { + bottom: 30%; + right: 15%; +} + +.browser-integration { + bottom: 15%; + left: 20%; +} + +.feature-robot-container { + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 120px; +} + +.feature-robot { + width: 100%; + height: auto; } /* Models Section */ .models-container { - display: flex; - flex-direction: column; - width: 100%; - gap: var(--gap-xlarge); + padding: 20px 0; } .models-heading { - font-size: 32px; - font-weight: 510; - text-align: center; - line-height: 1.2em; + font-size: 24px; + font-weight: 600; + margin-bottom: 30px; + text-align: center; } .models-section-container { - display: flex; - flex-direction: row; - justify-content: stretch; - align-items: stretch; - width: 100%; - gap: var(--gap-large); - flex-wrap: wrap; -} - -@media (max-width: 992px) { - .models-section-container { - flex-direction: column; - } + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; } .model-card { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: var(--gap-medium); - padding: var(--gap-medium); - flex: 1; - min-width: 250px; - background-color: var(--container-bg); - border: 1px solid var(--border-color); - border-radius: 8px; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 8px; + padding: 20px; + display: flex; + flex-direction: column; } .model-header { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - width: 100%; - gap: var(--gap-large); + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; } .model-name { - font-size: 16px; - font-weight: 590; - text-transform: uppercase; - line-height: 1em; + font-size: 18px; + font-weight: 500; } .model-badge { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 1px 8px; - height: 20px; - border-radius: var(--radius-round); - font-size: 11px; - font-weight: 510; - line-height: 1.3em; - letter-spacing: -1%; - text-transform: uppercase; - filter: drop-shadow(0px 25px 50px rgba(0, 0, 0, 0.25)); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; } -.model-badge.best { - background-color: #8078d4; +.best { + background-color: #4caf50; + color: white; } -.model-badge.trending { - background-color: var(--text-color); - color: var(--background-color); +.trending { + background-color: #2196f3; + color: white; } -.model-badge.new { - background-color: var(--text-color); - color: var(--background-color); +.new { + background-color: #ff9800; + color: white; } .model-description { - font-size: 14px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; - width: 100%; + font-size: 14px; + color: var(--vscode-descriptionForeground); + margin-bottom: 16px; } .model-divider { - width: 100%; - height: 1px; - background-color: transparent; - border: none; - border-top: 1px dashed var(--border-light); - margin: 0; + height: 1px; + background-color: var(--vscode-panel-border); + margin: 16px 0; } .model-image { - width: 100%; - height: 200px; - border-radius: 8px; - object-fit: cover; + width: 100%; + height: 150px; + border-radius: 4px; + margin-bottom: 16px; + background-size: cover; + background-position: center; +} + +.anthropic-model { + background-image: url('../images/anthropic-model.jpg'); +} + +.google-model { + background-image: url('../images/google-model.jpg'); +} + +.meta-model { + background-image: url('../images/meta-model.jpg'); } .model-button { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: var(--gap-small); - padding: 7px 8px; - width: 100%; - background-color: var(--secondary-color); - border: 1px solid var(--border-light); - border-radius: var(--radius-small); - color: var(--text-color); - font-size: 14px; - font-weight: 510; - line-height: 1.3em; - letter-spacing: -1%; - cursor: pointer; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; + margin-top: auto; +} + +.model-button:hover { + background-color: var(--vscode-button-hoverBackground); } /* Plan/Act Mode Section */ .plan-act-container { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - gap: var(--gap-large); - margin-bottom: 40px; + padding: 20px 0; } .plan-act-heading { - font-size: 32px; - font-weight: 510; - text-align: center; - line-height: 1.2em; + font-size: 24px; + font-weight: 600; + margin-bottom: 10px; } .plan-act-description { - font-family: "Inter", sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; - text-align: center; + font-size: 14px; + color: var(--vscode-descriptionForeground); + margin-bottom: 20px; } .plan-act-content { - display: flex; - flex-direction: row; - justify-content: stretch; - align-items: stretch; - gap: 32px; - width: 100%; - max-width: 880px; - margin-top: 20px; - min-height: 500px; -} - -@media (max-width: 768px) { - .plan-act-content { - flex-direction: column; - } -} - -.plan-mode-container { - display: flex; - flex-direction: column; - padding: 4px; - flex: 1; - background-color: #f5a524; - border-radius: var(--radius-xlarge); - height: 500px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; } +.plan-mode-container, .act-mode-container { - display: flex; - flex-direction: column; - padding: 4px; - flex: 1; - background-color: #006fee; - border-radius: var(--radius-xlarge); - margin-top: 80px; - height: 500px; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 8px; + padding: 20px; } .mode-header { - display: flex; - flex-direction: row; - justify-content: stretch; - align-items: stretch; - width: 100%; - gap: var(--gap-small); - padding: var(--gap-medium); + margin-bottom: 20px; } .mode-title { - font-family: "Azeret Mono", monospace; - font-weight: 500; - font-size: 20px; - line-height: 1.4em; - color: var(--text-color); - filter: drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.1)) drop-shadow(0px 10px 15px rgba(0, 0, 0, 0.1)); + font-size: 18px; + font-weight: 500; } .mode-content { - display: flex; - flex-direction: column; - width: 100%; - gap: var(--gap-medium); - padding: var(--gap-large); - background-color: var(--text-color); - border-radius: var(--radius-xlarge); - height: 100%; - justify-content: space-around; - min-height: 450px; -} - -.plan-mode-container .mode-content { - border: 2px solid #f5a524; -} - -.act-mode-container .mode-content { - border: 2px solid #006fee; + display: flex; + flex-direction: column; + gap: 12px; } .mode-item { - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - gap: 10px; - height: 50px; - padding-left: 10px; + display: flex; + align-items: center; } .mode-item.disabled { - opacity: 0.5; + opacity: 0.5; } .mode-icon { - display: flex; - align-items: center; - justify-content: center; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; } -.mode-text { - font-family: "Azeret Mono", monospace; - font-weight: 400; - font-size: 18px; - line-height: 1.5em; - color: #18181b; - text-align: left; - margin-left: 10px; +.plan-icon { + color: #F5A524; +} + +.plan-icon-disabled { + color: #F31260; +} + +.act-icon { + color: #006FEE; } .line-icon { - width: 14px; - height: 14px; - display: block; + width: 14px; + height: 14px; +} + +.plan-line { + background-image: url('../images/plan-line.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.act-line { + background-image: url('../images/act-line.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.disabled-line { + background-image: url('../images/disabled-line.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.mode-text { + font-size: 14px; } /* MCP Servers Section */ .mcp-servers-container { - display: flex; - flex-direction: column; - width: 100%; - gap: var(--gap-large); - margin-bottom: 40px; + padding: 20px 0; } .mcp-content-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - width: 100%; - gap: 56px; -} - -@media (max-width: 992px) { - .mcp-content-container { - flex-direction: column; - align-items: center; - text-align: center; - } + display: flex; + flex-direction: column; + align-items: center; } .mcp-text-container { - display: flex; - flex-direction: column; - gap: var(--gap-large); - width: 100%; + text-align: center; + max-width: 600px; + margin-bottom: 30px; } .mcp-heading { - font-size: 32px; - font-weight: 510; - line-height: 1.2em; + font-size: 24px; + font-weight: 600; + margin-bottom: 16px; } .mcp-description { - font-family: "Inter", sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 1.3em; - letter-spacing: -1%; + font-size: 14px; + color: var(--vscode-descriptionForeground); + margin-bottom: 20px; } .mcp-button { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--gap-small); - padding: 0 16px; - height: 36px; - background-color: var(--primary-color); - border: 1px solid var(--border-primary); - border-radius: var(--radius-small); - color: var(--text-color); - font-size: 14px; - font-weight: 510; - line-height: 1.3em; - letter-spacing: -1%; - cursor: pointer; - width: fit-content; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; +} + +.mcp-button:hover { + background-color: var(--vscode-button-hoverBackground); } .mcp-count { - display: flex; - align-items: center; - justify-content: center; - width: 100%; + margin-top: 30px; + width: 100%; + max-width: 800px; } -.mcp-count svg { - width: auto; - height: auto; - max-width: 100%; +/* Responsive Styles */ +@media (max-width: 768px) { + .features-section-container { + grid-template-columns: 1fr; + } + + .plan-act-content { + grid-template-columns: 1fr; + } + + .models-section-container { + grid-template-columns: 1fr; + } + + .feature-row { + grid-template-columns: 1fr 1fr; + } } -@media (max-width: 992px) { - .mcp-count { - margin-top: 20px; - } +@media (max-width: 480px) { + .feature-row { + grid-template-columns: 1fr; + } - .mcp-count svg { - transform: scale(0.8); - } + .buttons-container { + flex-direction: column; + width: 100%; + } + + .primary-button, + .secondary-button { + width: 100%; + } } diff --git a/webview-ui/src/assets/images/act-line.svg b/webview-ui/src/assets/images/act-line.svg new file mode 100644 index 0000000000..486cefdd08 --- /dev/null +++ b/webview-ui/src/assets/images/act-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/webview-ui/src/assets/images/disabled-line.svg b/webview-ui/src/assets/images/disabled-line.svg new file mode 100644 index 0000000000..5f7e03414d --- /dev/null +++ b/webview-ui/src/assets/images/disabled-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/webview-ui/src/assets/images/plan-line.svg b/webview-ui/src/assets/images/plan-line.svg index e69de29bb2..faacbcd012 100644 --- a/webview-ui/src/assets/images/plan-line.svg +++ b/webview-ui/src/assets/images/plan-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/webview-ui/src/assets/js/script.js b/webview-ui/src/assets/js/script.js deleted file mode 100644 index d4c660b282..0000000000 --- a/webview-ui/src/assets/js/script.js +++ /dev/null @@ -1,126 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - // Tab switching functionality - const tabButtons = document.querySelectorAll(".tab-button") - - tabButtons.forEach((button) => { - button.addEventListener("click", function () { - // Remove active class from all tabs - tabButtons.forEach((btn) => { - btn.classList.remove("active") - }) - - // Add active class to clicked tab - this.classList.add("active") - - // Here you would typically show/hide content based on the selected tab - // For this demo, we're just changing the active state of the tab - }) - }) - - // Button hover effects - const allButtons = document.querySelectorAll("button") - - allButtons.forEach((button) => { - button.addEventListener("mouseenter", function () { - this.style.opacity = "1" - this.style.transform = "scale(1.02)" - this.style.transition = "all 0.2s ease" - }) - - button.addEventListener("mouseleave", function () { - this.style.transform = "scale(1)" - - // Only reset opacity for action buttons that have default opacity of 0.5 - if (this.classList.contains("action-button")) { - this.style.opacity = "0.5" - } - }) - }) - - // Feature card hover effects - const featureCards = document.querySelectorAll(".feature-card") - - featureCards.forEach((card) => { - card.addEventListener("mouseenter", function () { - this.style.transform = "translateY(-5px)" - this.style.boxShadow = "0 10px 20px rgba(0, 0, 0, 0.2)" - this.style.transition = "all 0.3s ease" - }) - - card.addEventListener("mouseleave", function () { - this.style.transform = "translateY(0)" - this.style.boxShadow = "none" - }) - }) - - // Feature option hover effects - const featureOptions = document.querySelectorAll(".feature-option") - - featureOptions.forEach((option) => { - option.addEventListener("mouseenter", function () { - this.style.backgroundColor = "rgba(43, 43, 43, 0.5)" - this.style.transition = "all 0.2s ease" - }) - - option.addEventListener("mouseleave", function () { - this.style.backgroundColor = "transparent" - }) - }) - - // Input field focus effect - const inputField = document.querySelector(".input-field") - const inputPlaceholder = document.querySelector(".input-placeholder") - - if (inputField && inputPlaceholder) { - inputField.addEventListener("click", function () { - inputPlaceholder.style.opacity = "0.7" - inputField.style.borderColor = "#4DAAFC" - - // Create a temporary input element for demonstration - if (!document.querySelector(".temp-input")) { - const tempInput = document.createElement("input") - tempInput.type = "text" - tempInput.className = "temp-input" - tempInput.style.width = "100%" - tempInput.style.background = "transparent" - tempInput.style.border = "none" - tempInput.style.outline = "none" - tempInput.style.color = "#FFFFFF" - tempInput.style.fontSize = "14px" - tempInput.style.fontFamily = "SF Pro, Inter, sans-serif" - - inputPlaceholder.style.display = "none" - inputField.insertBefore(tempInput, inputField.firstChild) - tempInput.focus() - - tempInput.addEventListener("blur", function () { - if (!this.value) { - inputPlaceholder.style.display = "block" - inputPlaceholder.style.opacity = "1" - this.remove() - } - inputField.style.borderColor = "#3C3C3C" - }) - } - }) - } - - // Back button hover effect - const backButton = document.querySelector(".back-button-container") - - if (backButton) { - backButton.addEventListener("mouseenter", function () { - this.style.opacity = "0.8" - this.style.cursor = "pointer" - }) - - backButton.addEventListener("mouseleave", function () { - this.style.opacity = "1" - }) - - backButton.addEventListener("click", function () { - // This would typically navigate back to the main Cline page - alert("Navigating back to Cline...") - }) - } -}) diff --git a/webview-ui/src/assets/welcome-tab.html b/webview-ui/src/assets/welcome-tab.html deleted file mode 100644 index efd1d38a66..0000000000 --- a/webview-ui/src/assets/welcome-tab.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - Cline - Extension v1.0 - - - - - -
-
- -
-
- Back Icon -
-
Back to Cline
-
- - - - -
- - -
-
-

Work better with the best features of Cline

-

- It's easy to lose control as they make rapid changes to your codebase. That's why we built safe - features to make the control on your code. -

- -
- -
- - - -
- -
- - - - - - - -
-
- -
-
-
-
- -
- - -
-

Top 3 models for coding

- -
- -
-
-
anthropic
-
Best
-
- -
Leading model for agentic coding
- -
- - Anthropic Model - - -
- - -
-
-
google
- -
- -
Large 1M context window, great value
- -
- - Google Model - - -
- - -
-
-
meta-llama
-
New
-
- -
Efficient performance at lower cost
- -
- - Meta-Llama Model - - -
-
-
- -
- - -
-

Plan/Act Mode

-

Lorem Ipsum about this topic

- -
- -
- -
-
-

Plan Mode

-
-
-
-
- Plan Icon -
-
Read files & Analysis
-
-
-
- Plan Icon -
-
Strategy Development
-
-
-
- Plan Icon -
-
Context Gathering
-
-
-
- Plan Icon -
-
Requirements Analysis
-
-
-
- Disabled Icon -
-
Code Modifications
-
-
-
- Disabled Icon -
-
File Creation/Deletion
-
-
-
- - -
-
-

Act Mode

-
-
-
-
- Act Icon -
-
Read files & Analysis
-
-
-
- Act Icon -
-
Code Modifications
-
-
-
- Act Icon -
-
File Creation/Deletion
-
-
-
- Act Icon -
-
Execute Commands
-
-
-
- Act Icon -
-
Run Tests
-
-
-
- Act Icon -
-
Deploy Changes
-
-
-
-
-
- -
- - -
-
-
-

The largest ecosystem of MCP Servers for VS Code

-

- The Model Context Protocol (MCP) allows seamless communication with locally running servers that - extend Cline's functionality with additional tools and context-aware resources. -

- -
-
- - - - - - - - -
-
-
-
-
- - - - diff --git a/webview-ui/src/components/welcome/ApiOptionsSection.tsx b/webview-ui/src/components/welcome/ApiOptionsSection.tsx new file mode 100644 index 0000000000..4b0ad9b662 --- /dev/null +++ b/webview-ui/src/components/welcome/ApiOptionsSection.tsx @@ -0,0 +1,42 @@ +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import { useEffect, useState } from "react" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { validateApiConfiguration } from "@/utils/validate" +import { vscode } from "@/utils/vscode" +import ApiOptions from "@/components/settings/ApiOptions" + +interface ApiOptionsSectionProps { + showApiOptions: boolean +} + +const ApiOptionsSection = ({ showApiOptions }: ApiOptionsSectionProps) => { + const { apiConfiguration } = useExtensionState() + const [apiErrorMessage, setApiErrorMessage] = useState(undefined) + + const disableLetsGoButton = apiErrorMessage != null + + const handleSubmit = () => { + vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) + } + + useEffect(() => { + setApiErrorMessage(validateApiConfiguration(apiConfiguration)) + }, [apiConfiguration]) + + if (!showApiOptions) { + return null + } + + return ( +
+
+ + + Let's go! + +
+
+ ) +} + +export default ApiOptionsSection diff --git a/webview-ui/src/components/welcome/WelcomeTabView.tsx b/webview-ui/src/components/welcome/WelcomeTabView.tsx new file mode 100644 index 0000000000..28b8c85122 --- /dev/null +++ b/webview-ui/src/components/welcome/WelcomeTabView.tsx @@ -0,0 +1,550 @@ +import { useState, useRef, useEffect, memo, Dispatch, SetStateAction } from "react" +import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { vscode } from "@/utils/vscode" +import { AccountServiceClient } from "@/services/grpc-client" +import { EmptyRequest } from "@shared/proto/common" +import ClineLogoWhite from "@/assets/ClineLogoWhite" +import ApiOptionsSection from "./ApiOptionsSection" + +// Import images +import featureIcon1 from "@/assets/images/feature-icon-1.svg" +import featureIcon2 from "@/assets/images/feature-icon-2.svg" +import featureIcon3 from "@/assets/images/feature-icon-3.svg" +import featureIcon4 from "@/assets/images/feature-icon-4.svg" +import actionIcon1 from "@/assets/images/action-icon-1.svg" +import actionIcon2 from "@/assets/images/action-icon-2.svg" +import vector1 from "@/assets/images/vector-1.svg" +import buttonIcon1 from "@/assets/images/button-icon-1.svg" +import anthropicModel from "@/assets/images/anthropic-model.jpg" +import googleModel from "@/assets/images/google-model.jpg" +import metaModel from "@/assets/images/meta-model.jpg" +import planLine from "@/assets/images/plan-line.svg" +import actLine from "@/assets/images/act-line.svg" +import disabledLine from "@/assets/images/disabled-line.svg" +import featureImage from "@/assets/images/feature-image.jpg" +import featureBg from "@/assets/images/feature-bg.png" + +// Import CSS styles +import "@/assets/css/styles.css" + +interface WelcomeTabViewProps { + onToggleView?: () => void + showApiOptions?: boolean + setShowApiOptions?: Dispatch> +} + +const WelcomeTabView = memo(({ onToggleView, showApiOptions, setShowApiOptions }: WelcomeTabViewProps = {}) => { + const { apiConfiguration } = useExtensionState() + const [activeTab, setActiveTab] = useState("Key Features") + const inputFieldRef = useRef(null) + const inputRef = useRef(null) + const [inputValue, setInputValue] = useState("") + const [showPlaceholder, setShowPlaceholder] = useState(true) + + const handleLogin = () => { + AccountServiceClient.accountLoginClicked(EmptyRequest.create()).catch((err) => + console.error("Failed to get login URL:", err), + ) + } + + const handleTabClick = (tabName: string) => { + setActiveTab(tabName) + } + + const handleInputFieldClick = () => { + setShowPlaceholder(false) + inputRef.current?.focus() + } + + const handleInputBlur = () => { + if (!inputValue) { + setShowPlaceholder(true) + } + } + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value) + } + + const handleCreateProject = (model: string) => { + console.log(`Creating project with ${model} model`) + // Implement project creation logic + } + + // Equivalent to document.addEventListener("DOMContentLoaded", ...) + useEffect(() => { + // No need to implement DOM manipulation here as we're using React state + }, []) + + return ( +
+ {/* Toggle button to switch between old and new welcome views */} + {onToggleView && ( +
+ +
+ )} +
+ {/* Account Info */} +
+ + +
+

Get Started on Cline

+

Let's start and learn the basics of Cline

+
+ +
+ + {showApiOptions !== undefined && setShowApiOptions !== undefined && !showApiOptions && ( + + )} +
+ {showApiOptions !== undefined && } + +
+
+
+
+
+
Snake game
+
+
> Build a Snake game...
+
+ +
+
+
+
To-do App
+
+
> Generate a to-do App...
+
+ +
+
+
+
Script
+
+
> Write a script and then...
+
+ +
+
+
+
New Feature
+
+
> Read my code & suggest...
+
+
+ +
+
+ {showPlaceholder &&
Type your prompt here...
} + {!showPlaceholder && ( + + )} + +
+
+ + + +
+
+
+
+
+
+ +
+ + {/* Features Section */} +
+
+

Work better with the best features of Cline

+

+ It's easy to lose control as they make rapid changes to your codebase. That's why we built safe + features to make the control on your code. +

+ +
+ +
+ + + +
+ + {activeTab === "Key Features" && ( +
+
+
+
+

File Editing

+

Cline can write your code for you

+
+
+ +
+ + + + + +
+ +
+
+
+ Terminal Commands +
+ +
+
+ Code Analysis +
+ +
+
+ Browser Integration +
+
+
+ )} + + {activeTab === "Tools" && ( +
+

Cline provides powerful tools to help you code faster

+
+ )} + + {activeTab === "Checkpoints" && ( +
+

Cline creates checkpoints to help you track changes

+
+ )} +
+
+ + Feature Background + +
+ + {/* Models Section */} +
+

Top 3 models for coding

+ +
+ {/* Anthropic Model Card */} +
+
+
anthropic
+
Best
+
+ +
Leading model for agentic coding
+ +
+ +
+ + +
+ + {/* Google Model Card */} +
+
+
google
+
Trending
+
+ +
Large 1M context window, great value
+ +
+ +
+ + +
+ + {/* Meta-Llama Model Card */} +
+
+
meta-llama
+
New
+
+ +
Efficient performance at lower cost
+ +
+ +
+ + +
+
+
+ +
+ + {/* Plan/Act Mode Section */} +
+

Plan/Act Mode

+

+ Switch between planning and execution modes to better control how Cline assists you. Plan mode helps you + strategize while Act mode implements your solutions. +

+ +
+ +
+ {/* Plan Mode Column */} +
+
+

Plan Mode

+
+
+
+
+
+
+
Read files & Analysis
+
+
+
+
+
+
Strategy Development
+
+
+
+
+
+
Context Gathering
+
+
+
+
+
+
Requirements Analysis
+
+
+
+
+
+
Code Modifications
+
+
+
+
+
+
File Creation/Deletion
+
+
+
+ + {/* Act Mode Column */} +
+
+

Act Mode

+
+
+
+
+
+
+
Read files & Analysis
+
+
+
+
+
+
Code Modifications
+
+
+
+
+
+
File Creation/Deletion
+
+
+
+
+
+
Execute Commands
+
+
+
+
+
+
Run Tests
+
+
+
+
+
+
Deploy Changes
+
+
+
+
+
+ +
+ + {/* MCP Servers Section */} +
+
+
+

The largest ecosystem of MCP Servers for VS Code

+

+ The Model Context Protocol (MCP) allows seamless communication with locally running servers that + extend Cline's functionality with additional tools and context-aware resources. +

+ +
+
+ + + + + + + + +
+
+
+
+
+ ) +}) + +export default WelcomeTabView diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx index fc0aaced50..169a6dab28 100644 --- a/webview-ui/src/components/welcome/WelcomeView.tsx +++ b/webview-ui/src/components/welcome/WelcomeView.tsx @@ -1,34 +1,22 @@ import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" -import { useEffect, useState, memo } from "react" -import { useExtensionState } from "@/context/ExtensionStateContext" -import { validateApiConfiguration } from "@/utils/validate" -import { vscode } from "@/utils/vscode" -import ApiOptions from "@/components/settings/ApiOptions" +import { memo, Dispatch, SetStateAction } from "react" import ClineLogoWhite from "@/assets/ClineLogoWhite" import { AccountServiceClient } from "@/services/grpc-client" import { EmptyRequest } from "@shared/proto/common" +import ApiOptionsSection from "./ApiOptionsSection" -const WelcomeView = memo(() => { - const { apiConfiguration } = useExtensionState() - const [apiErrorMessage, setApiErrorMessage] = useState(undefined) - const [showApiOptions, setShowApiOptions] = useState(false) - - const disableLetsGoButton = apiErrorMessage != null +interface WelcomeViewProps { + showApiOptions: boolean + setShowApiOptions: Dispatch> +} +const WelcomeView = memo(({ showApiOptions, setShowApiOptions }: WelcomeViewProps) => { const handleLogin = () => { AccountServiceClient.accountLoginClicked(EmptyRequest.create()).catch((err) => console.error("Failed to get login URL:", err), ) } - const handleSubmit = () => { - vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) - } - - useEffect(() => { - setApiErrorMessage(validateApiConfiguration(apiConfiguration)) - }, [apiConfiguration]) - return (
@@ -64,16 +52,7 @@ const WelcomeView = memo(() => { )} -
- {showApiOptions && ( -
- - - Let's go! - -
- )} -
+
) diff --git a/webview-ui/src/components/welcome/WelcomeWrapper.tsx b/webview-ui/src/components/welcome/WelcomeWrapper.tsx new file mode 100644 index 0000000000..6529c89905 --- /dev/null +++ b/webview-ui/src/components/welcome/WelcomeWrapper.tsx @@ -0,0 +1,23 @@ +import { useState, memo } from "react" +import { WebviewType } from "@shared/WebviewMessage" +import WelcomeView from "./WelcomeView" +import WelcomeTabView from "./WelcomeTabView" + +const WelcomeWrapper = memo(() => { + const [showApiOptions, setShowApiOptions] = useState(false) + + // Determine which welcome view to show based on webview type + const webviewType = (document.querySelector('meta[name="webview-type"]')?.getAttribute("content") || "sidebar") as WebviewType + + return ( + <> + {webviewType === "sidebar" ? ( + + ) : ( + + )} + + ) +}) + +export default WelcomeWrapper diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 731f99f56f..162fddc044 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -61,6 +61,7 @@ export const ExtensionStateContextProvider: React.FC<{ clineMessages: [], taskHistory: [], shouldShowAnnouncement: false, + showWelcome: false, autoApprovalSettings: DEFAULT_AUTO_APPROVAL_SETTINGS, browserSettings: DEFAULT_BROWSER_SETTINGS, chatSettings: DEFAULT_CHAT_SETTINGS, @@ -106,33 +107,7 @@ export const ExtensionStateContextProvider: React.FC<{ : prevState.autoApprovalSettings, } }) - const config = message.state?.apiConfiguration - const hasKey = config - ? [ - config.apiKey, - config.openRouterApiKey, - config.awsRegion, - config.vertexProjectId, - config.openAiApiKey, - config.ollamaModelId, - config.lmStudioModelId, - config.liteLlmApiKey, - config.geminiApiKey, - config.openAiNativeApiKey, - config.deepSeekApiKey, - config.requestyApiKey, - config.togetherApiKey, - config.qwenApiKey, - config.doubaoApiKey, - config.mistralApiKey, - config.vsCodeLmModelSelector, - config.clineApiKey, - config.asksageApiKey, - config.xaiApiKey, - config.sambanovaApiKey, - ].some((key) => key !== undefined) - : false - setShowWelcome(!hasKey) + setShowWelcome(true) setDidHydrateState(true) break }