/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all",
- "last 2 chrome version",
- "last 2 firefox version",
- "last 2 safari version"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
}
}
diff --git a/webview-ui/public/.gitkeep b/webview-ui/public/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/webview-ui/public/index.html b/webview-ui/public/index.html
deleted file mode 100644
index bd3562a6874..00000000000
--- a/webview-ui/public/index.html
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- React App
-
-
-
-
-
-
diff --git a/webview-ui/public/manifest.json b/webview-ui/public/manifest.json
deleted file mode 100644
index 918a3f825c4..00000000000
--- a/webview-ui/public/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "short_name": "React App",
- "name": "Create React App Sample",
- "icons": [
- {
- "src": "",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- },
- {
- "src": "",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/webview-ui/public/robots.txt b/webview-ui/public/robots.txt
deleted file mode 100644
index e9e57dc4d41..00000000000
--- a/webview-ui/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index ffa340e2f0b..cb4649a9151 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -183,26 +183,28 @@ export const ChatRowContent = ({
)
return [
- apiReqCancelReason !== null ? (
+ apiReqCancelReason ? (
apiReqCancelReason === "user_cancelled" ? (
getIconSpan("error", cancelledColor)
) : (
getIconSpan("error", errorColor)
)
- ) : cost !== null ? (
+ ) : cost ? (
getIconSpan("check", successColor)
) : apiRequestFailedMessage ? (
getIconSpan("error", errorColor)
) : (
),
- apiReqCancelReason !== null ? (
+ apiReqCancelReason ? (
apiReqCancelReason === "user_cancelled" ? (
API Request Cancelled
) : (
- API Streaming Failed
+
+ API Streaming Failed ({JSON.stringify(apiReqCancelReason)})
+
)
- ) : cost !== null ? (
+ ) : cost ? (
API Request
) : apiRequestFailedMessage ? (
API Request Failed
@@ -510,9 +512,7 @@ export const ChatRowContent = ({
style={{
...headerStyle,
marginBottom:
- (cost === null && apiRequestFailedMessage) || apiReqStreamingFailedMessage
- ? 10
- : 0,
+ (!cost && apiRequestFailedMessage) || apiReqStreamingFailedMessage ? 10 : 0,
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
@@ -530,7 +530,7 @@ export const ChatRowContent = ({
- {((cost === null && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && (
+ {((!cost && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && (
<>
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
diff --git a/webview-ui/src/components/ui/button.tsx b/webview-ui/src/components/ui/button.tsx
new file mode 100644
index 00000000000..db39bd26455
--- /dev/null
+++ b/webview-ui/src/components/ui/button.tsx
@@ -0,0 +1,47 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline: "border border-input bg-foreground shadow-sm hover:bg-foreground/80",
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return
+ },
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css
index 8f537baedb6..1ab12041c1d 100644
--- a/webview-ui/src/index.css
+++ b/webview-ui/src/index.css
@@ -1,44 +1,71 @@
-@import "tailwindcss";
+/* @import "tailwindcss"; */
+
+@layer theme, base, components, utilities;
+
+@import "tailwindcss/theme.css" layer(theme);
+/* https://tailwindcss.com/docs/preflight */
+/* @import "tailwindcss/preflight.css" layer(base); */
+@import "tailwindcss/utilities.css" layer(utilities);
+
+@plugin "tailwindcss-animate";
+
+@theme {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-lg: var(--radius);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-sm: calc(var(--radius) - 4px);
+}
@layer base {
- /* Theme Variables - VSCode Integration */
:root {
- /* Base Colors */
--background: var(--vscode-editor-background);
--foreground: var(--vscode-editor-foreground);
-
- /* Component Colors */
--card: var(--vscode-editor-background);
--card-foreground: var(--vscode-editor-foreground);
--popover: var(--vscode-menu-background, var(--vscode-editor-background));
--popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground));
-
- /* Button Colors */
--primary: var(--vscode-button-background);
--primary-foreground: var(--vscode-button-foreground);
--secondary: var(--vscode-button-secondaryBackground);
--secondary-foreground: var(--vscode-button-secondaryForeground);
- --accent: var(--vscode-focusBorder);
- --accent-foreground: var(--vscode-button-foreground);
-
- /* State Colors */
--muted: var(--vscode-disabledForeground);
--muted-foreground: var(--vscode-descriptionForeground);
+ --accent: var(--vscode-input-border);
+ --accent-foreground: var(--vscode-button-foreground);
--destructive: var(--vscode-errorForeground);
- --destructive-foreground: var(--vscode-editor-background);
-
- /* UI Elements */
+ --destructive-foreground: var(--vscode-button-foreground);
--border: var(--vscode-widget-border);
--input: var(--vscode-input-background);
- --ring: var(--vscode-focusBorder);
- --radius: 0.5rem;
-
- /* Chart Colors - Using VSCode's chart colors */
+ --ring: var(--vscode-input-border);
--chart-1: var(--vscode-charts-red);
--chart-2: var(--vscode-charts-blue);
--chart-3: var(--vscode-charts-yellow);
--chart-4: var(--vscode-charts-orange);
--chart-5: var(--vscode-charts-green);
+ --radius: 0.5rem;
}
}
@@ -70,10 +97,10 @@ vscode-button::part(control):focus {
outline: none;
}
-/*
-Use vscode native scrollbar styles
-https://github.com/gitkraken/vscode-gitlens/blob/b1d71d4844523e8b2ef16f9e007068e91f46fd88/src/webviews/apps/home/home.scss
-*/
+/**
+ * Use vscode native scrollbar styles
+ * https://github.com/gitkraken/vscode-gitlens/blob/b1d71d4844523e8b2ef16f9e007068e91f46fd88/src/webviews/apps/home/home.scss
+ */
html {
height: 100%;
@@ -163,10 +190,11 @@ The above scrollbar styling uses some transparent background color magic to acco
background-color: transparent;
}
-/*
-Dropdown label
-https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#with-label
-*/
+/**
+ * Dropdown label
+ * https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#with-label
+ */
+
.dropdown-container {
box-sizing: border-box;
display: flex;
@@ -174,6 +202,7 @@ https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#wi
align-items: flex-start;
justify-content: flex-start;
}
+
.dropdown-container label {
display: block;
color: var(--vscode-foreground);
@@ -184,6 +213,7 @@ https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#wi
}
/* Fix dropdown double scrollbar overflow */
+
#api-provider > div > ul {
overflow: unset;
}
@@ -197,18 +227,20 @@ vscode-dropdown::part(listbox) {
}
/* Faded icon buttons in textfields */
-
.input-icon-button {
cursor: pointer;
opacity: 0.65;
}
+
.input-icon-button:hover {
opacity: 1;
}
+
.input-icon-button.disabled {
cursor: not-allowed;
opacity: 0.4;
}
+
.input-icon-button.disabled:hover {
opacity: 0.4;
}
@@ -220,10 +252,6 @@ vscode-dropdown::part(listbox) {
border-radius: 3px;
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
color: transparent;
- /* padding: 0.5px;
- margin: -0.5px;
- position: relative;
- bottom: -0.5px; */
}
.mention-context-highlight {
diff --git a/webview-ui/src/index.tsx b/webview-ui/src/index.tsx
index 1237d2abd5c..030a291e977 100644
--- a/webview-ui/src/index.tsx
+++ b/webview-ui/src/index.tsx
@@ -1,12 +1,12 @@
-import React from "react"
-import ReactDOM from "react-dom/client"
+import { StrictMode } from "react"
+import { createRoot } from "react-dom/client"
+
import "./index.css"
import App from "./App"
import "../../node_modules/@vscode/codicons/dist/codicon.css"
-const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
-root.render(
-
+createRoot(document.getElementById("root")!).render(
+
- ,
+ ,
)
diff --git a/webview-ui/src/lib/utils.ts b/webview-ui/src/lib/utils.ts
new file mode 100644
index 00000000000..9a7122c3056
--- /dev/null
+++ b/webview-ui/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/webview-ui/src/stories/Button.stories.ts b/webview-ui/src/stories/Button.stories.ts
new file mode 100644
index 00000000000..c6d6bb6ff82
--- /dev/null
+++ b/webview-ui/src/stories/Button.stories.ts
@@ -0,0 +1,53 @@
+import type { Meta, StoryObj } from "@storybook/react"
+import { fn } from "@storybook/test"
+
+import { Button } from "@/components/ui/button"
+
+const meta = {
+ title: "Example/Button",
+ component: Button,
+ parameters: { layout: "centered" },
+ tags: ["autodocs"],
+ argTypes: {},
+ args: { onClick: fn(), children: "Button" },
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
+ args: {
+ variant: "default",
+ },
+}
+
+export const Secondary: Story = {
+ args: {
+ variant: "secondary",
+ },
+}
+
+export const Outline: Story = {
+ args: {
+ variant: "outline",
+ },
+}
+
+export const Ghost: Story = {
+ args: {
+ variant: "ghost",
+ },
+}
+
+export const Link: Story = {
+ args: {
+ variant: "link",
+ },
+}
+
+export const Destructive: Story = {
+ args: {
+ variant: "destructive",
+ },
+}
diff --git a/webview-ui/src/stories/Welcome.mdx b/webview-ui/src/stories/Welcome.mdx
new file mode 100644
index 00000000000..a89bd6864fa
--- /dev/null
+++ b/webview-ui/src/stories/Welcome.mdx
@@ -0,0 +1,7 @@
+import { Meta } from "@storybook/blocks";
+
+
+
+# Welcome
+
+This Roo Code storybook is used to independently develop components for the Roo Code webview UI.
diff --git a/webview-ui/src/stories/assets/.gitkeep b/webview-ui/src/stories/assets/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/webview-ui/src/vite-env.d.ts b/webview-ui/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/webview-ui/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/webview-ui/tsconfig.json b/webview-ui/tsconfig.json
index 8a9f4596848..c725fcff3e3 100644
--- a/webview-ui/tsconfig.json
+++ b/webview-ui/tsconfig.json
@@ -14,7 +14,11 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "react-jsx"
+ "jsx": "react-jsx",
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
},
"include": ["src", "../src/shared"]
}
diff --git a/webview-ui/vite.config.mts b/webview-ui/vite.config.mts
deleted file mode 100644
index 17782a30f99..00000000000
--- a/webview-ui/vite.config.mts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-import tailwindcss from "@tailwindcss/vite";
-
-export default defineConfig({
- plugins: [
- react(),
- tailwindcss(),
- ],
- build: {
- outDir: "build",
- rollupOptions: {
- output: {
- entryFileNames: `assets/[name].js`,
- chunkFileNames: `assets/[name].js`,
- assetFileNames: `assets/[name].[ext]`,
- },
- },
- },
- server: {
- port: 3000,
- },
-});
\ No newline at end of file
diff --git a/webview-ui/vite.config.ts b/webview-ui/vite.config.ts
new file mode 100644
index 00000000000..978a6e5e89a
--- /dev/null
+++ b/webview-ui/vite.config.ts
@@ -0,0 +1,36 @@
+import path from "path"
+
+import { defineConfig } from "vite"
+import react from "@vitejs/plugin-react"
+import tailwindcss from "@tailwindcss/vite"
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+ build: {
+ outDir: "build",
+ rollupOptions: {
+ output: {
+ entryFileNames: `assets/[name].js`,
+ chunkFileNames: `assets/[name].js`,
+ assetFileNames: `assets/[name].[ext]`,
+ },
+ },
+ },
+ server: {
+ hmr: {
+ host: "localhost",
+ protocol: "ws",
+ },
+ cors: {
+ origin: "*",
+ methods: "*",
+ allowedHeaders: "*",
+ },
+ },
+})