From 606783cb8815d826b5bd9ca10fa8263f4d046173 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 28 Jan 2025 22:23:10 +0530 Subject: [PATCH 01/80] add pearai provider --- .gitignore | 2 + src/api/index.ts | 3 + src/api/providers/pearai.ts | 19 ++++++ src/core/webview/ClineProvider.ts | 25 ++++++++ src/shared/api.ts | 5 ++ .../src/components/settings/ApiOptions.tsx | 58 ++++++++++++++++++- 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/api/providers/pearai.ts diff --git a/.gitignore b/.gitignore index 211d06aa199..3c4ac33b812 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ docs/_site/ #Logging logs +.aider* +.env diff --git a/src/api/index.ts b/src/api/index.ts index f68c9acd1fb..bf40b5c2990 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -16,6 +16,7 @@ import { VsCodeLmHandler } from "./providers/vscode-lm" import { ApiStream } from "./transform/stream" import { UnboundHandler } from "./providers/unbound" import { RequestyHandler } from "./providers/requesty" +import { PearAiHandler } from "./providers/pearai" export interface SingleCompletionHandler { completePrompt(prompt: string): Promise @@ -59,6 +60,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { return new UnboundHandler(options) case "requesty": return new RequestyHandler(options) + case "pearai": + return new PearAiHandler(options) default: return new AnthropicHandler(options) } diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts new file mode 100644 index 00000000000..6b39106eb2e --- /dev/null +++ b/src/api/providers/pearai.ts @@ -0,0 +1,19 @@ +import { OpenAiHandler } from "./openai" +import { ApiHandlerOptions } from "../../shared/api" + +export class PearAiHandler extends OpenAiHandler { + constructor(options: ApiHandlerOptions) { + if (!options.pearaiApiKey) { + throw new Error("PearAI API key is required. Please provide it in the settings.") + } + super({ + ...options, + // Map PearAI specific options to OpenAI options for compatibility + openAiApiKey: options.pearaiApiKey, + openAiBaseUrl: + options.pearaiBaseUrl ?? + "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline", + openAiStreamingEnabled: true, + }) + } +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index eeefc7578cc..de50429b613 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -60,6 +60,7 @@ type SecretKey = | "mistralApiKey" | "unboundApiKey" | "requestyApiKey" + | "pearaiApiKey" type GlobalStateKey = | "apiProvider" | "apiModelId" @@ -94,6 +95,10 @@ type GlobalStateKey = | "openRouterModelId" | "openRouterModelInfo" | "openRouterBaseUrl" + | "pearaiModelId" + | "pearaiModelInfo" + | "pearaiBaseUrl" + | "pearaiApiKey" | "openRouterUseMiddleOutTransform" | "allowedCommands" | "soundEnabled" @@ -1638,6 +1643,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { requestyModelId, requestyModelInfo, modelTemperature, + pearaiApiKey, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, } = apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) @@ -1683,6 +1692,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("requestyModelId", requestyModelId) await this.updateGlobalState("requestyModelInfo", requestyModelInfo) await this.updateGlobalState("modelTemperature", modelTemperature) + await this.updateGlobalState("pearaiModelId", pearaiModelId) + await this.updateGlobalState("pearaiModelInfo", pearaiModelInfo) + await this.storeSecret("pearaiApiKey", pearaiApiKey) + await this.updateGlobalState("pearaiBaseUrl", pearaiBaseUrl) if (this.cline) { this.cline.api = buildApiHandler(apiConfiguration) } @@ -2513,6 +2526,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { openAiNativeApiKey, deepSeekApiKey, mistralApiKey, + pearaiApiKey, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, azureApiVersion, openAiStreamingEnabled, openRouterModelId, @@ -2593,6 +2610,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("openAiNativeApiKey") as Promise, this.getSecret("deepSeekApiKey") as Promise, this.getSecret("mistralApiKey") as Promise, + this.getSecret("pearaiApiKey") as Promise, + this.getGlobalState("pearaiBaseUrl") as Promise, + this.getGlobalState("pearaiModelId") as Promise, + this.getGlobalState("pearaiModelInfo") as Promise, this.getGlobalState("azureApiVersion") as Promise, this.getGlobalState("openAiStreamingEnabled") as Promise, this.getGlobalState("openRouterModelId") as Promise, @@ -2690,6 +2711,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { openAiNativeApiKey, deepSeekApiKey, mistralApiKey, + pearaiApiKey, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, azureApiVersion, openAiStreamingEnabled, openRouterModelId, diff --git a/src/shared/api.ts b/src/shared/api.ts index 7e926b09cfa..91f99219d8d 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -16,6 +16,7 @@ export type ApiProvider = | "mistral" | "unbound" | "requesty" + | "pearai" export interface ApiHandlerOptions { apiModelId?: string @@ -66,6 +67,10 @@ export interface ApiHandlerOptions { requestyModelId?: string requestyModelInfo?: ModelInfo modelTemperature?: number + pearaiApiKey?: string + pearaiBaseUrl?: string + pearaiModelId?: string + pearaiModelInfo?: ModelInfo } export type ApiConfiguration = ApiHandlerOptions & { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 0d8bb43bb5b..9cebb272732 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,7 +1,7 @@ -import { memo, useCallback, useEffect, useMemo, useState } from "react" +import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react" import { useEvent, useInterval } from "react-use" import { Checkbox, Dropdown, Pane, type DropdownOption } from "vscrui" -import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" +import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { TemperatureControl } from "./TemperatureControl" import * as vscodemodels from "vscode" @@ -45,6 +45,8 @@ import { ModelInfoView } from "./ModelInfoView" import { DROPDOWN_Z_INDEX } from "./styles" import { RequestyModelPicker } from "./RequestyModelPicker" +const PEARAI_DEFAULT_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" + interface ApiOptionsProps { apiErrorMessage?: string modelIdErrorMessage?: string @@ -59,6 +61,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage, fromWelcomeView }: A const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) const [openRouterBaseUrlSelected, setOpenRouterBaseUrlSelected] = useState(!!apiConfiguration?.openRouterBaseUrl) + const [pearaiBaseUrlSelected, setPearaiBaseUrlSelected] = useState(!!apiConfiguration?.pearaiBaseUrl) const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => { @@ -153,6 +156,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage, fromWelcomeView }: A }} style={{ minWidth: 130, position: "relative", zIndex: DROPDOWN_Z_INDEX + 1 }} options={[ + { value: "pearai", label: "PearAI" }, { value: "openrouter", label: "OpenRouter" }, { value: "anthropic", label: "Anthropic" }, { value: "gemini", label: "Google Gemini" }, @@ -172,6 +176,47 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage, fromWelcomeView }: A /> + {selectedProvider === "pearai" && ( +
+ + PearAI API Key + + + Base URL + + {apiConfiguration?.pearaiBaseUrl && apiConfiguration.pearaiBaseUrl !== PEARAI_DEFAULT_URL && ( + { + handleInputChange("pearaiBaseUrl")({ + target: { + value: PEARAI_DEFAULT_URL, + }, + }) + }}> + Reset to default URL + + )} +

+ This key is stored locally and only used to make API requests from this extension. +

+
+ )} + {selectedProvider === "anthropic" && (
)} @@ -1569,7 +1545,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { case "pearai": return { selectedProvider: provider, - selectedModelId: apiConfiguration?.pearaiModelId || "", + selectedModelId: apiConfiguration?.pearaiModelId || "pearai_model", selectedModelInfo: apiConfiguration?.pearaiModelInfo || openAiModelInfoSaneDefaults, } default: From 322d776e80721515e7b1c91f038a7a0095195b49 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Tue, 28 Jan 2025 17:52:12 -0500 Subject: [PATCH 03/80] Added pearai api config --- src/shared/checkExistApiConfig.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/checkExistApiConfig.ts b/src/shared/checkExistApiConfig.ts index 0570f6118a9..3684eaf49cc 100644 --- a/src/shared/checkExistApiConfig.ts +++ b/src/shared/checkExistApiConfig.ts @@ -4,6 +4,7 @@ export function checkExistKey(config: ApiConfiguration | undefined) { return config ? [ config.apiKey, + config.pearaiApiKey, config.glamaApiKey, config.openRouterApiKey, config.awsRegion, From 2f59dd8101fd7c96700f86c61ec468c96398e577 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Tue, 28 Jan 2025 18:25:01 -0500 Subject: [PATCH 04/80] Added pearai welcome default --- src/core/webview/ClineProvider.ts | 2 +- .../src/components/welcome/WelcomeView.tsx | 40 +++++++------------ 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index f6bf86c7fbb..1aa15acc75e 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1596,7 +1596,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { // Update mode's default config const { mode } = await this.getState() if (mode) { - const currentApiConfigName = await this.getGlobalState("currentApiConfigName") + const currentApiConfigName = (await this.getGlobalState("currentApiConfigName")) ?? "default" const listApiConfig = await this.configManager.listConfig() const config = listApiConfig?.find((c) => c.name === currentApiConfigName) if (config?.id) { diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx index db52612b90f..f7eb6ac9577 100644 --- a/webview-ui/src/components/welcome/WelcomeView.tsx +++ b/webview-ui/src/components/welcome/WelcomeView.tsx @@ -1,28 +1,23 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" -import { validateApiConfiguration } from "../../utils/validate" import { vscode } from "../../utils/vscode" -import ApiOptions from "../settings/ApiOptions" const WelcomeView = () => { - const { apiConfiguration } = useExtensionState() - - const [errorMessage, setErrorMessage] = useState(undefined) - const handleSubmit = () => { - const error = validateApiConfiguration(apiConfiguration) - if (error) { - setErrorMessage(error) - return - } - setErrorMessage(undefined) - vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) + vscode.postMessage({ + type: "apiConfiguration", + apiConfiguration: { + apiProvider: "pearai", + pearaiApiKey: "temp", // TODO: Change this to use api-key + pearaiBaseUrl: "http://localhost:8000/integrations/cline", + }, + }) } return ( -
-

Hi, I'm Roo!

+
+

Welcome to PearAI's Coding Agent (Powered by Roo Code / Cline)!

I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access to tools that let me create & edit files, explore complex projects, use the browser, and execute @@ -30,17 +25,10 @@ const WelcomeView = () => { my own capabilities.

- To get started, this extension needs an API provider. - -
- -
- -
-
- Let's go! - {errorMessage && {errorMessage}} -
+
+ + Let's go! +
) From fa96656ddb14e623de816e2f52ac70b9a65be68f Mon Sep 17 00:00:00 2001 From: nang-dev Date: Tue, 28 Jan 2025 20:22:36 -0500 Subject: [PATCH 05/80] Added pearai with anthropic --- src/api/providers/pearai.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts index 09b9bed4720..0931f7fe265 100644 --- a/src/api/providers/pearai.ts +++ b/src/api/providers/pearai.ts @@ -1,7 +1,8 @@ import { OpenAiHandler } from "./openai" import { ApiHandlerOptions, PEARAI_URL } from "../../shared/api" +import { AnthropicHandler } from "./anthropic" -export class PearAiHandler extends OpenAiHandler { +export class PearAiHandler extends AnthropicHandler { constructor(options: ApiHandlerOptions) { if (!options.pearaiApiKey) { throw new Error("PearAI API key not found. Please login to PearAI.") From 688004394102cb9074ec77538112ddfd9bdc2c9c Mon Sep 17 00:00:00 2001 From: Nang Date: Wed, 29 Jan 2025 11:43:58 -0500 Subject: [PATCH 06/80] Added working anthropic server --- src/api/providers/anthropic.ts | 5 ++++- src/api/providers/pearai.ts | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index 9a14756f5d2..366c4005e0f 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -85,7 +85,10 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { case "claude-3-opus-20240229": case "claude-3-haiku-20240307": return { - headers: { "anthropic-beta": "prompt-caching-2024-07-31" }, + headers: { + "anthropic-beta": "prompt-caching-2024-07-31", + "authorization": `Bearer ${this.options.apiKey}`, + }, } default: return undefined diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts index 0931f7fe265..9fe9e48ff85 100644 --- a/src/api/providers/pearai.ts +++ b/src/api/providers/pearai.ts @@ -9,10 +9,8 @@ export class PearAiHandler extends AnthropicHandler { } super({ ...options, - // Map PearAI specific options to OpenAI options for compatibility - openAiApiKey: options.pearaiApiKey, - openAiBaseUrl: PEARAI_URL, - openAiStreamingEnabled: true, + apiKey: options.pearaiApiKey, + anthropicBaseUrl: PEARAI_URL, }) } } From 7c06f7c1b425f0621c5224068113e031f813fc17 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Thu, 30 Jan 2025 20:50:42 -0500 Subject: [PATCH 07/80] Added login logout p2p call working --- package-lock.json | 4 +- package.json | 10 +-- src/core/webview/ClineProvider.ts | 24 ++++-- src/extension.ts | 85 +++++++++++++++++++ src/shared/ExtensionMessage.ts | 3 +- src/shared/WebviewMessage.ts | 1 + src/shared/checkExistApiConfig.ts | 2 +- .../src/components/settings/ApiOptions.tsx | 43 +++++++--- .../src/components/welcome/WelcomeView.tsx | 13 +-- 9 files changed, 146 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8af12ad90c6..ba4352cd850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "roo-cline", + "name": "pearai-roo-cline", "version": "3.3.19", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "roo-cline", + "name": "pearai-roo-cline", "version": "3.3.19", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", diff --git a/package.json b/package.json index 9842df55c8b..c98e3e08c0f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "roo-cline", - "displayName": "Roo Code (prev. Roo Cline)", - "description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.", - "publisher": "RooVeterinaryInc", + "name": "pearai-roo-cline", + "displayName": "PearAI Roo Code / Cline", + "description": "PearAI's integration of Roo Code / Cline, a coding agent.", + "publisher": "PearAI", "version": "3.3.19", "icon": "assets/icons/rocket.png", "galleryBanner": { @@ -13,7 +13,7 @@ "vscode": "^1.84.0" }, "author": { - "name": "Roo Vet" + "name": "PearAI" }, "repository": { "type": "git", diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 1aa15acc75e..bb60c499db1 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -40,9 +40,6 @@ import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt import { ACTION_NAMES } from "../CodeActionProvider" import { McpServerManager } from "../../services/mcp/McpServerManager" -// Todo: Remove -const PEARAI_TOKEN = "temp" - /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -64,6 +61,8 @@ type SecretKey = | "unboundApiKey" | "requestyApiKey" | "pearaiApiKey" + | "pearai-token" + | "pearai-refresh" // Array of custom modes type GlobalStateKey = | "apiProvider" | "apiModelId" @@ -136,8 +135,6 @@ type GlobalStateKey = | "unboundModelInfo" | "modelTemperature" | "customModes" - | "pearai-token" - | "pearai-refresh" // Array of custom modes export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -1503,6 +1500,19 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("mode", defaultModeSlug) await this.postStateToWebview() } + break + case "openPearAiAuth": + const extensionUrl = `${vscode.env.uriScheme}://pearai.pearai/auth` + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(extensionUrl)) + + await vscode.env.openExternal( + await vscode.env.asExternalUri( + vscode.Uri.parse( + `https://trypear.ai/signin?callback=${callbackUri.toString()}`, // Change to localhost if running locally + ), + ), + ) + break } }, null, @@ -2617,8 +2627,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("openAiNativeApiKey") as Promise, this.getSecret("deepSeekApiKey") as Promise, this.getSecret("mistralApiKey") as Promise, - this.getGlobalState("pearai-token") as Promise, - this.getGlobalState("pearai-refresh") as Promise, + this.getSecret("pearai-token") as Promise, + this.getSecret("pearai-refresh") as Promise, this.getGlobalState("pearaiBaseUrl") as Promise, this.getGlobalState("pearaiModelId") as Promise, this.getGlobalState("pearaiModelInfo") as Promise, diff --git a/src/extension.ts b/src/extension.ts index a05afa46512..fba1aa2f5b7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -61,6 +61,91 @@ export function activate(context: vscode.ExtensionContext) { * * https://code.visualstudio.com/api/extension-guides/virtual-documents */ + context.subscriptions.push( + vscode.commands.registerCommand("pearai-roo-cline.pearaiLogin", async (data) => { + console.dir("Logged in to PearAI:") + console.dir(data) + context.secrets.store("pearai-token", data.accessToken) + context.secrets.store("pearai-refresh", data.refreshToken) + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand("pearai-roo-cline.pearAILogout", async () => { + console.dir("Logged out of PearAI:") + context.secrets.delete("pearai-token") + context.secrets.delete("pearai-refresh") + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand("roo-cline.mcpButtonClicked", () => { + sidebarProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" }) + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand("roo-cline.promptsButtonClicked", () => { + sidebarProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" }) + }), + ) + + const openClineInNewTab = async () => { + outputChannel.appendLine("Opening Roo Code 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 tabProvider = new ClineProvider(context, outputChannel) + //const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined + const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0)) + + // Check if there are any visible text editors, otherwise open a new group to the right + const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0 + if (!hasVisibleEditors) { + await vscode.commands.executeCommand("workbench.action.newGroupRight") + } + const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two + + const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [context.extensionUri], + }) + // TODO: use better svg icon with light and dark variants (see https://stackoverflow.com/questions/58365687/vscode-extension-iconpath) + + panel.iconPath = { + light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"), + dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"), + } + tabProvider.resolveWebviewView(panel) + + // Lock the editor group so clicking on files doesn't open them over the panel + await delay(100) + await vscode.commands.executeCommand("workbench.action.lockEditorGroup") + } + + context.subscriptions.push(vscode.commands.registerCommand("roo-cline.popoutButtonClicked", openClineInNewTab)) + context.subscriptions.push(vscode.commands.registerCommand("roo-cline.openInNewTab", openClineInNewTab)) + + context.subscriptions.push( + vscode.commands.registerCommand("roo-cline.settingsButtonClicked", () => { + //vscode.window.showInformationMessage(message) + sidebarProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand("roo-cline.historyButtonClicked", () => { + sidebarProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" }) + }), + ) + + /* + We use the text document content provider API to show the left side for diff view by creating a virtual document for the original content. This makes it readonly so users know to edit the right side if they want to keep their changes. + + - This API allows you to create readonly documents in VSCode from arbitrary sources, and works by claiming an uri-scheme for which your provider then returns text contents. The scheme must be provided when registering a provider and cannot change afterwards. + - Note how the provider doesn't create uris for virtual documents - its role is to provide contents given such an uri. In return, content providers are wired into the open document logic so that providers are always considered. + https://code.visualstudio.com/api/extension-guides/virtual-documents + */ const diffContentProvider = new (class implements vscode.TextDocumentContentProvider { provideTextDocumentContent(uri: vscode.Uri): string { return Buffer.from(uri.query, "base64").toString("utf-8") diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 76a029d487b..cecd4cd206b 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -54,7 +54,8 @@ export interface ExtensionMessage { | "historyButtonClicked" | "promptsButtonClicked" | "didBecomeVisible" - invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage" + | "updatePearAIAuth" + invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" state?: ExtensionState images?: string[] ollamaModels?: string[] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 33da2a117f4..9adf767a1d5 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -92,6 +92,7 @@ export interface WebviewMessage { | "openCustomModesSettings" | "checkpointDiff" | "checkpointRestore" + | "openPearAiAuth" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/src/shared/checkExistApiConfig.ts b/src/shared/checkExistApiConfig.ts index 3684eaf49cc..17c8eb9b461 100644 --- a/src/shared/checkExistApiConfig.ts +++ b/src/shared/checkExistApiConfig.ts @@ -4,7 +4,6 @@ export function checkExistKey(config: ApiConfiguration | undefined) { return config ? [ config.apiKey, - config.pearaiApiKey, config.glamaApiKey, config.openRouterApiKey, config.awsRegion, @@ -19,6 +18,7 @@ export function checkExistKey(config: ApiConfiguration | undefined) { config.vsCodeLmModelSelector, config.requestyApiKey, config.unboundApiKey, + config.pearaiBaseUrl, ].some((key) => key !== undefined) : false } diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index a2071d14e72..1a321c226c6 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -176,20 +176,35 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage, fromWelcomeView }: A {selectedProvider === "pearai" && (
- - PearAI API Key - -

+ {!apiConfiguration?.pearaiApiKey ? ( + <> + { + vscode.postMessage({ + type: "openPearAiAuth", + }) + }}> + Login to PearAI + +

+ Connect your PearAI account to use servers. +

+ + ) : ( +

+ User already logged in to PearAI. Click 'Done' to proceed! +

+ )}
)} diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx index f7eb6ac9577..5467b1d35e5 100644 --- a/webview-ui/src/components/welcome/WelcomeView.tsx +++ b/webview-ui/src/components/welcome/WelcomeView.tsx @@ -2,6 +2,7 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" +import { PEARAI_URL } from "../../../../src/shared/api" const WelcomeView = () => { const handleSubmit = () => { @@ -9,8 +10,7 @@ const WelcomeView = () => { type: "apiConfiguration", apiConfiguration: { apiProvider: "pearai", - pearaiApiKey: "temp", // TODO: Change this to use api-key - pearaiBaseUrl: "http://localhost:8000/integrations/cline", + pearaiBaseUrl: `${PEARAI_URL}/integrations/cline`, }, }) } @@ -18,16 +18,11 @@ const WelcomeView = () => { return (

Welcome to PearAI's Coding Agent (Powered by Roo Code / Cline)!

-

- I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access - to tools that let me create & edit files, explore complex projects, use the browser, and execute - terminal commands (with your permission, of course). I can even use MCP to create new tools and extend - my own capabilities. -

+

Ask me to code new features or fix bugs!

- Let's go! + Proceed
From d2cf5f0159d5adf88786876d4564601dc5a998a1 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Thu, 30 Jan 2025 22:39:38 -0500 Subject: [PATCH 08/80] Fixed capitalization --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index fba1aa2f5b7..18d5ea88ef0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -71,7 +71,7 @@ export function activate(context: vscode.ExtensionContext) { ) context.subscriptions.push( - vscode.commands.registerCommand("pearai-roo-cline.pearAILogout", async () => { + vscode.commands.registerCommand("pearai-roo-cline.pearaiLogout", async () => { console.dir("Logged out of PearAI:") context.secrets.delete("pearai-token") context.secrets.delete("pearai-refresh") From 4fc7e99069795121e0259406ee9f7753bc4c7cae Mon Sep 17 00:00:00 2001 From: nang-dev Date: Fri, 31 Jan 2025 14:51:53 -0500 Subject: [PATCH 09/80] Plus button working after login --- src/extension.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extension.ts b/src/extension.ts index 18d5ea88ef0..200332bbc02 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -67,6 +67,7 @@ export function activate(context: vscode.ExtensionContext) { console.dir(data) context.secrets.store("pearai-token", data.accessToken) context.secrets.store("pearai-refresh", data.refreshToken) + vscode.commands.executeCommand("roo-cline.plusButtonClicked") }), ) From 3d62dc718e700a60e93aff73f284c3c24e1d8c65 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Fri, 31 Jan 2025 16:55:26 -0500 Subject: [PATCH 10/80] Added client side error --- src/api/providers/pearai.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts index 9fe9e48ff85..ba79fb2149e 100644 --- a/src/api/providers/pearai.ts +++ b/src/api/providers/pearai.ts @@ -1,10 +1,25 @@ import { OpenAiHandler } from "./openai" +import * as vscode from "vscode" import { ApiHandlerOptions, PEARAI_URL } from "../../shared/api" import { AnthropicHandler } from "./anthropic" export class PearAiHandler extends AnthropicHandler { constructor(options: ApiHandlerOptions) { if (!options.pearaiApiKey) { + vscode.window.showErrorMessage("PearAI API key not found.", "Login to PearAI").then(async (selection) => { + if (selection === "Login to PearAI") { + const extensionUrl = `${vscode.env.uriScheme}://pearai.pearai/auth` + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(extensionUrl)) + + vscode.env.openExternal( + await vscode.env.asExternalUri( + vscode.Uri.parse( + `https://trypear.ai/signin?callback=${callbackUri.toString()}`, // Change to localhost if running locally + ), + ), + ) + } + }) throw new Error("PearAI API key not found. Please login to PearAI.") } super({ From e34d5daf47b8df26a1c19b681cda7767067d8c5d Mon Sep 17 00:00:00 2001 From: nang-dev Date: Fri, 31 Jan 2025 17:43:22 -0500 Subject: [PATCH 11/80] Added wording tweak --- webview-ui/src/components/chat/ChatView.tsx | 9 +++------ webview-ui/src/components/welcome/WelcomeView.tsx | 9 ++++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index b9fc215a1c7..b76a984e51a 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1000,13 +1000,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie }}> {showAnnouncement && }
-

What can Roo do for you?

+

PearAI Coding Agent (Powered by Roo Code / Cline)

- Thanks to the latest breakthroughs in agentic coding capabilities, I can handle complex - software development tasks step-by-step. With tools that let me create & edit files, explore - complex projects, use the browser, and execute terminal commands (after you grant - permission), I can assist you in ways that go beyond code completion or tech support. I can - even use MCP to create new tools and extend my own capabilities. + Ask me to create a new feature, fix a bug, anything else. I can create & edit files, explore + complex projects, use the browser, and execute terminal commands!

{taskHistory.length > 0 && } diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx index 5467b1d35e5..c3d5f4074f8 100644 --- a/webview-ui/src/components/welcome/WelcomeView.tsx +++ b/webview-ui/src/components/welcome/WelcomeView.tsx @@ -17,12 +17,15 @@ const WelcomeView = () => { return (
-

Welcome to PearAI's Coding Agent (Powered by Roo Code / Cline)!

-

Ask me to code new features or fix bugs!

+

Welcome to PearAI Coding Agent (Powered by Roo Code / Cline)!

+

+ Ask me to create a new feature, fix a bug, anything else. I can create & edit files, explore complex + projects, use the browser, and execute terminal commands! +

- Proceed + Next
From 3b8411b01d5224edc00c8e42df6fd7de1cde215b Mon Sep 17 00:00:00 2001 From: nang-dev Date: Fri, 7 Feb 2025 12:35:23 -0500 Subject: [PATCH 12/80] Added package.json --- .husky/pre-push | 8 ++++---- package.json | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.husky/pre-push b/.husky/pre-push index a4fea4a34ae..773cedb6c02 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,9 +1,9 @@ branch="$(git rev-parse --abbrev-ref HEAD)" -if [ "$branch" = "main" ]; then - echo "You can't push directly to main - please check out a branch." - exit 1 -fi +# if [ "$branch" = "main" ]; then +# echo "You can't push directly to main - please check out a branch." +# exit 1 +# fi npm run compile diff --git a/package.json b/package.json index c98e3e08c0f..3d90e8804ac 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ }, "repository": { "type": "git", - "url": "https://github.com/RooVetGit/Roo-Code" + "url": "https://github.com/trypear/PearAI-Roo-Code" }, - "homepage": "https://github.com/RooVetGit/Roo-Code", + "homepage": "https://github.com/trypear/PearAI-Roo-Code", "categories": [ "AI", "Chat", @@ -51,16 +51,16 @@ "main": "./dist/extension.js", "contributes": { "viewsContainers": { - "activitybar": [ + "auxiliarybar": [ { - "id": "roo-cline-ActivityBar", - "title": "Roo Code", + "id": "pearai-roo-cline", + "title": "PearAI Agent", "icon": "$(rocket)" } ] }, "views": { - "roo-cline-ActivityBar": [ + "pearai-roo-cline": [ { "type": "webview", "id": "roo-cline.SidebarProvider", From 9075548a995a59f400dca19e7e28d0a257e3684d Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 1 Feb 2025 12:50:23 +0530 Subject: [PATCH 13/80] add aider in gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3c4ac33b812..73f9aaa6c05 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ docs/_site/ logs .aider* .env +# aider +.aider* From 255dfb4800d73c31d8f8a73aacb9f76f1f0c3557 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Mon, 3 Feb 2025 23:56:17 +0530 Subject: [PATCH 14/80] input box - recent chats --- package-lock.json | 86 +++++ package.json | 2 + webview-ui/package-lock.json | 207 +++++++++- webview-ui/package.json | 2 + .../src/components/chat/ChatTextArea.tsx | 272 +++++++------ webview-ui/src/components/chat/ChatView.tsx | 68 ++-- .../src/components/history/HistoryPreview.tsx | 20 +- .../src/components/ui/ShortcutsButton.tsx | 55 +++ .../src/components/ui/button-pear-scn.tsx | 48 +++ webview-ui/src/components/ui/index.tsx | 364 ++++++++++++++++++ webview-ui/src/components/ui/select.tsx | 135 +++++++ 11 files changed, 1104 insertions(+), 155 deletions(-) create mode 100644 webview-ui/src/components/ui/ShortcutsButton.tsx create mode 100644 webview-ui/src/components/ui/button-pear-scn.tsx create mode 100644 webview-ui/src/components/ui/index.tsx create mode 100644 webview-ui/src/components/ui/select.tsx diff --git a/package-lock.json b/package-lock.json index ba4352cd850..7c78cbb7c3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@anthropic-ai/vertex-sdk": "^0.4.1", "@aws-sdk/client-bedrock-runtime": "^3.706.0", "@google/generative-ai": "^0.18.0", + "@heroicons/react": "^2.2.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", @@ -43,6 +44,7 @@ "pretty-bytes": "^6.1.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", + "react-tooltip": "^5.28.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", "sound-play": "^1.1.0", @@ -3239,6 +3241,31 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@google/generative-ai": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.18.0.tgz", @@ -3247,6 +3274,15 @@ "node": ">=18.0.0" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -7530,6 +7566,12 @@ "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", "dev": true }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -13646,12 +13688,49 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/react-tooltip": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz", + "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -14101,6 +14180,13 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", diff --git a/package.json b/package.json index 3d90e8804ac..7e013c92c0c 100644 --- a/package.json +++ b/package.json @@ -306,6 +306,7 @@ "@anthropic-ai/vertex-sdk": "^0.4.1", "@aws-sdk/client-bedrock-runtime": "^3.706.0", "@google/generative-ai": "^0.18.0", + "@heroicons/react": "^2.2.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", @@ -336,6 +337,7 @@ "pretty-bytes": "^6.1.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", + "react-tooltip": "^5.28.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", "sound-play": "^1.1.0", diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index b3dc15147fc..585a73d5fdc 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -12,6 +12,8 @@ "@radix-ui/react-dropdown-menu": "^2.1.5", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-popover": "^1.1.6", + "@headlessui/react": "^2.2.0", + "@radix-ui/react-select": "^2.1.5", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.0", @@ -2720,6 +2722,21 @@ "@floating-ui/utils": "^0.2.9" } }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", @@ -2733,12 +2750,37 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/react/node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/@floating-ui/utils": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@headlessui/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", + "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@tanstack/react-virtual": "^3.8.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -3409,6 +3451,12 @@ "node": ">= 8" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", @@ -4137,12 +4185,13 @@ } } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", - "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==", + "node_modules/@radix-ui/react-select": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.5.tgz", + "integrity": "sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA==", "license": "MIT", "dependencies": { + "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", @@ -4151,7 +4200,18 @@ "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.4", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.2" }, "peerDependencies": { "@types/react": "*", @@ -4433,6 +4493,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", @@ -4539,6 +4614,92 @@ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", "license": "MIT" }, + "node_modules/@react-aria/focus": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.1.tgz", + "integrity": "sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.23.0.tgz", + "integrity": "sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz", + "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz", + "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", @@ -5490,6 +5651,15 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.0.tgz", @@ -5723,6 +5893,33 @@ "vite": "^5.2.0 || ^6" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.12.0.tgz", + "integrity": "sha512-6krceiPN07kpxXmU6m8AY7EL0X1gHLu8m3nJdh4phvktzVNxkQfBmSwnRUpoUjGQO1PAn8wSAhYaL8hY1cS1vw==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.12.0.tgz", + "integrity": "sha512-7mDINtua3v/pOnn6WUmuT9dPXYSO7WidFej7JzoAfqEOcbbpt/iZ1WPqd+eg+FnrL9nUJK8radqj4iAU51Zchg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 59ea5693b6e..919b3916333 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -19,6 +19,8 @@ "@radix-ui/react-dropdown-menu": "^2.1.5", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-popover": "^1.1.6", + "@headlessui/react": "^2.2.0", + "@radix-ui/react-select": "^2.1.5", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.0", diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 98d57965b72..9fd39a4f674 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -16,6 +16,97 @@ import { vscode } from "../../utils/vscode" import { WebviewMessage } from "../../../../src/shared/WebviewMessage" import { Mode, getAllModes } from "../../../../src/shared/modes" import { CaretIcon } from "../common/CaretIcon" +import { Button } from "../ui/button-pear-scn" +import { ArrowTurnDownLeftIcon, TrashIcon } from "@heroicons/react/16/solid" +import { getFontSize, lightGray, vscEditorBackground, vscFocusBorder, vscInputBackground } from "../ui" +import styled from "styled-components" +import { Listbox } from "@headlessui/react" +import { ImageIcon } from "@radix-ui/react-icons" + +const StyledListboxButton = styled(Listbox.Button)` + border: solid 1px ${lightGray}30; + background-color: ${vscEditorBackground}; + border-radius: 4px; + padding: 4px 8px; + display: flex; + align-items: center; + gap: 2px; + user-select: none; + cursor: pointer; + font-size: ${getFontSize() - 3}px; + color: ${lightGray}; + &:focus { + outline: none; + } +` + +const StyledListboxOptions = styled(Listbox.Options)<{ newSession: boolean }>` + position: absolute; + bottom: 100%; + left: 0; + margin-bottom: 4px; + list-style: none; + padding: 6px; + white-space: nowrap; + cursor: default; + z-index: 50; + border: 1px solid ${lightGray}30; + border-radius: 10px; + background-color: ${vscEditorBackground}; + max-height: 300px; + min-width: 100px; + overflow-y: auto; + font-size: ${getFontSize() - 2}px; + user-select: none; + outline: none; + + &::-webkit-scrollbar { + display: none; + } + + scrollbar-width: none; + -ms-overflow-style: none; + + & > * { + margin: 4px 0; + } +` + +interface ListboxOptionProps { + isCurrentModel?: boolean +} + +const StyledListboxOption = styled(Listbox.Option)` + cursor: pointer; + border-radius: 6px; + padding: 5px 4px; + + &:hover { + background: ${(props) => (props.isCurrentModel ? `${lightGray}66` : `${lightGray}33`)}; + } + + background: ${(props) => (props.isCurrentModel ? `${lightGray}66` : "transparent")}; +` + +const StyledTrashIcon = styled(TrashIcon)` + cursor: pointer; + flex-shrink: 0; + margin-left: 8px; + &:hover { + color: red; + } +` + +const Divider = styled.div` + height: 2px; + background-color: ${lightGray}35; + margin: 0px 4px; +` + +const ListboxWrapper = styled.div` + position: relative; + display: inline-block; +` interface ChatTextAreaProps { inputValue: string @@ -535,35 +626,6 @@ const ChatTextArea = forwardRef( [updateCursorPosition], ) - const selectStyle = { - fontSize: "11px", - cursor: textAreaDisabled ? "not-allowed" : "pointer", - backgroundColor: "transparent", - border: "none", - color: "var(--vscode-foreground)", - opacity: textAreaDisabled ? 0.5 : 0.8, - outline: "none", - paddingLeft: "20px", - paddingRight: "6px", - WebkitAppearance: "none" as const, - MozAppearance: "none" as const, - appearance: "none" as const, - } - - const optionStyle = { - backgroundColor: "var(--vscode-dropdown-background)", - color: "var(--vscode-dropdown-foreground)", - } - - const caretContainerStyle = { - position: "absolute" as const, - left: 6, - top: "50%", - transform: "translateY(-45%)", - pointerEvents: "none" as const, - opacity: textAreaDisabled ? 0.5 : 0.8, - } - return (
( display: "flex", flexDirection: "column", gap: "8px", - backgroundColor: "var(--vscode-input-background)", + backgroundColor: vscEditorBackground, margin: "10px 15px", padding: "8px", outline: "none", border: "1px solid", - borderColor: isFocused ? "var(--vscode-focusBorder)" : "transparent", - borderRadius: "2px", + borderColor: "transparent", + borderRadius: "12px", }} onDrop={async (e) => { e.preventDefault() @@ -706,7 +768,7 @@ const ChatTextArea = forwardRef( onHeightChange?.(height) }} placeholder={placeholderText} - minRows={3} + minRows={2} maxRows={15} autoFocus={true} style={{ @@ -724,6 +786,8 @@ const ChatTextArea = forwardRef( overflowY: "auto", border: "none", padding: "2px", + paddingTop: "8px", + paddingBottom: "8px", paddingRight: "8px", marginBottom: thumbnailsHeight > 0 ? `${thumbnailsHeight + 16}px` : 0, cursor: textAreaDisabled ? "not-allowed" : undefined, @@ -762,13 +826,12 @@ const ChatTextArea = forwardRef( style={{ display: "flex", alignItems: "center", + gap: "8px", }}> -
- -
- -
-
- -
- -
- -
-
+ disabled={textAreaDisabled}> + + {currentApiConfigName} + + + + {(listApiConfigMeta || []).map((config) => ( + + {config.name} + + ))} + + + Edit... + + + +
( /> )}
- !shouldDisableImages && onSelectImages()} - style={{ fontSize: 16.5 }} - /> - !textAreaDisabled && onSend()} - style={{ fontSize: 15 }} /> +
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index b76a984e51a..1ea2344fc9e 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -976,6 +976,27 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flexDirection: "column", overflow: "hidden", }}> + {messages.length === 0 && ( + handleSendMessage(inputValue, selectedImages)} + onSelectImages={selectImages} + shouldDisableImages={shouldDisableImages} + onHeightChange={() => { + if (isAtBottom) { + scrollToBottomAuto() + } + }} + mode={mode} + setMode={setMode} + /> + )} {task ? ( {showAnnouncement && } -
+ {/*

PearAI Coding Agent (Powered by Roo Code / Cline)

Ask me to create a new feature, fix a bug, anything else. I can create & edit files, explore complex projects, use the browser, and execute terminal commands!

-
+
*/} {taskHistory.length > 0 && } )} @@ -1119,28 +1140,27 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie )} )} - - handleSendMessage(inputValue, selectedImages)} - onSelectImages={selectImages} - shouldDisableImages={shouldDisableImages} - onHeightChange={() => { - if (isAtBottom) { - scrollToBottomAuto() - } - }} - mode={mode} - setMode={setMode} - /> - -
+ {messages.length > 0 && ( + handleSendMessage(inputValue, selectedImages)} + onSelectImages={selectImages} + shouldDisableImages={shouldDisableImages} + onHeightChange={() => { + if (isAtBottom) { + scrollToBottomAuto() + } + }} + mode={mode} + setMode={setMode} + /> + )}
) } diff --git a/webview-ui/src/components/history/HistoryPreview.tsx b/webview-ui/src/components/history/HistoryPreview.tsx index b2898fc6a8d..95a7e596a16 100644 --- a/webview-ui/src/components/history/HistoryPreview.tsx +++ b/webview-ui/src/components/history/HistoryPreview.tsx @@ -4,6 +4,8 @@ import { vscode } from "../../utils/vscode" import { memo } from "react" import { formatLargeNumber } from "../../utils/format" import { useCopyToClipboard } from "../../utils/clipboard" +import { vscEditorBackground } from "../ui" +import { CounterClockwiseClockIcon } from "@radix-ui/react-icons" type HistoryPreviewProps = { showHistoryView: () => void @@ -32,7 +34,7 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => { } return ( -
+
{showCopyFeedback &&
Prompt Copied to Clipboard
}