|
| 1 | +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" |
| 2 | +import { useState, useEffect } from "react" |
| 3 | +import { Trans } from "react-i18next" |
| 4 | + |
| 5 | +import { useAppTranslation } from "@src/i18n/TranslationContext" |
| 6 | +import { useCopyToClipboard } from "@src/utils/clipboard" |
| 7 | +import RooHero from "@src/components/welcome/RooHero" |
| 8 | +// Unused import but needed for UI rendering |
| 9 | +import TelemetryBanner from "../common/TelemetryBanner" |
| 10 | + |
| 11 | +interface TaskItem { |
| 12 | + id: string |
| 13 | + title: string |
| 14 | + date: string |
| 15 | + tokensIn: string |
| 16 | + tokensOut: string |
| 17 | + cost: string |
| 18 | +} |
| 19 | + |
| 20 | +interface BentoGridProps { |
| 21 | + tasks: any[] |
| 22 | + isExpanded: boolean |
| 23 | + toggleExpanded: () => void |
| 24 | + telemetrySetting: string |
| 25 | +} |
| 26 | + |
| 27 | +const BentoGrid = ({ telemetrySetting }: BentoGridProps) => { |
| 28 | + const _t = useAppTranslation() |
| 29 | + |
| 30 | + // Dummy tasks for demonstration |
| 31 | + const dummyTasks: TaskItem[] = [ |
| 32 | + { |
| 33 | + title: "Please create a red fish game.", |
| 34 | + date: "Apr 26, 6:09 PM", |
| 35 | + tokensIn: "3.4k", |
| 36 | + tokensOut: "33.7k", |
| 37 | + cost: "$0.54", |
| 38 | + id: "dummy1", |
| 39 | + }, |
| 40 | + { |
| 41 | + title: "Refactor the authentication module.", |
| 42 | + date: "Apr 26, 5:30 PM", |
| 43 | + tokensIn: "10.2k", |
| 44 | + tokensOut: "55.1k", |
| 45 | + cost: "$1.15", |
| 46 | + id: "dummy2", |
| 47 | + }, |
| 48 | + { |
| 49 | + title: "Write unit tests for the API client.", |
| 50 | + date: "Apr 25, 11:15 AM", |
| 51 | + tokensIn: "5.8k", |
| 52 | + tokensOut: "21.9k", |
| 53 | + cost: "$0.38", |
| 54 | + id: "dummy3", |
| 55 | + }, |
| 56 | + ] |
| 57 | + |
| 58 | + // Feature cards with title and subtitle |
| 59 | + const featureCards = [ |
| 60 | + { |
| 61 | + title: "Customizable Modes", |
| 62 | + subtitle: "Specialized personas with their own behaviors and assigned models", |
| 63 | + id: "feature1", |
| 64 | + }, |
| 65 | + { |
| 66 | + title: "Smart Context", |
| 67 | + subtitle: "Automatically includes relevant files and code for better assistance", |
| 68 | + id: "feature2", |
| 69 | + }, |
| 70 | + { |
| 71 | + title: "Integrated Tools", |
| 72 | + subtitle: "Access to file operations, terminal commands, and browser interactions", |
| 73 | + id: "feature3", |
| 74 | + }, |
| 75 | + ] |
| 76 | + |
| 77 | + // Agent quick start options |
| 78 | + const agents = [ |
| 79 | + { |
| 80 | + name: "Code", |
| 81 | + emoji: "💻", |
| 82 | + description: "Write, edit, and improve your code", |
| 83 | + id: "agent1", |
| 84 | + }, |
| 85 | + { |
| 86 | + name: "Debug", |
| 87 | + emoji: "🪲", |
| 88 | + description: "Find and fix issues in your code", |
| 89 | + id: "agent2", |
| 90 | + }, |
| 91 | + { |
| 92 | + name: "Architect", |
| 93 | + emoji: "🏗️", |
| 94 | + description: "Design systems and plan implementations", |
| 95 | + id: "agent3", |
| 96 | + }, |
| 97 | + { |
| 98 | + name: "Ask", |
| 99 | + emoji: "❓", |
| 100 | + description: "Get answers to your technical questions", |
| 101 | + id: "agent4", |
| 102 | + }, |
| 103 | + { |
| 104 | + name: "Orchestrator", |
| 105 | + emoji: "🪃", |
| 106 | + description: "Coordinate complex tasks across modes", |
| 107 | + id: "agent5", |
| 108 | + }, |
| 109 | + ] |
| 110 | + |
| 111 | + return ( |
| 112 | + <div className="flex-1 min-h-0 overflow-y-auto p-5"> |
| 113 | + {/* Modern Bento Grid Layout */} |
| 114 | + <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> |
| 115 | + {/* Box 1: Logo Card */} |
| 116 | + <div className="col-span-full md:col-span-1 row-span-1 bg-[var(--vscode-editorWidget-background)] rounded-2xl overflow-hidden"> |
| 117 | + <div className="p-5 flex flex-col items-center justify-center h-full"> |
| 118 | + <h2 className="text-lg font-bold mb-3 text-vscode-editor-foreground">Roo</h2> |
| 119 | + <div className="flex items-center justify-center w-full"> |
| 120 | + <RooHero /> |
| 121 | + </div> |
| 122 | + </div> |
| 123 | + </div> |
| 124 | + |
| 125 | + {/* Box 2: Intro Text Card */} |
| 126 | + <div className="col-span-full md:col-span-2 row-span-1 bg-[var(--vscode-editorWidget-background)] rounded-2xl overflow-hidden"> |
| 127 | + <div className="p-5 flex flex-col items-start text-left h-full"> |
| 128 | + <h2 className="text-lg font-bold mb-3 text-vscode-editor-foreground">About</h2> |
| 129 | + <p className="text-vscode-editor-foreground leading-relaxed text-sm"> |
| 130 | + <Trans |
| 131 | + i18nKey="chat:about" |
| 132 | + components={{ |
| 133 | + DocsLink: ( |
| 134 | + <a |
| 135 | + href="https://docs.roocode.com/" |
| 136 | + target="_blank" |
| 137 | + rel="noopener noreferrer" |
| 138 | + className="text-vscode-textLink-foreground hover:underline font-medium"> |
| 139 | + the docs |
| 140 | + </a> |
| 141 | + ), |
| 142 | + }} |
| 143 | + /> |
| 144 | + </p> |
| 145 | + </div> |
| 146 | + </div> |
| 147 | + |
| 148 | + {/* Box 3: Agents Quick Start Card */} |
| 149 | + <div className="col-span-full md:col-span-2 row-span-2 bg-[var(--vscode-editorWidget-background)] rounded-2xl overflow-hidden"> |
| 150 | + <div className="p-5 flex flex-col h-full"> |
| 151 | + <h2 className="text-xs font-bold mb-4 text-vscode-descriptionForeground tracking-widest uppercase"> |
| 152 | + Agents |
| 153 | + </h2> |
| 154 | + <p className="text-base mb-4 text-vscode-editor-foreground">Start a conversation with:</p> |
| 155 | + |
| 156 | + <div className="flex-1 overflow-y-auto"> |
| 157 | + <div className="grid grid-cols-2 gap-2"> |
| 158 | + {agents.map((agent) => ( |
| 159 | + <div |
| 160 | + key={agent.id} |
| 161 | + className="flex items-center p-2 hover:bg-[var(--vscode-list-hoverBackground)] rounded cursor-pointer transition-colors duration-200"> |
| 162 | + <span className="text-lg mr-2">{agent.emoji}</span> |
| 163 | + <div className="overflow-hidden"> |
| 164 | + <h3 className="text-sm font-bold text-vscode-editor-foreground truncate"> |
| 165 | + {agent.name} |
| 166 | + </h3> |
| 167 | + <p className="text-xs text-vscode-descriptionForeground truncate"> |
| 168 | + {agent.description} |
| 169 | + </p> |
| 170 | + </div> |
| 171 | + </div> |
| 172 | + ))} |
| 173 | + </div> |
| 174 | + </div> |
| 175 | + </div> |
| 176 | + </div> |
| 177 | + |
| 178 | + {/* Box 4: Feature Carousel Card */} |
| 179 | + <div className="col-span-full md:col-span-1 row-span-1 bg-[var(--vscode-editorWidget-background)] rounded-2xl overflow-hidden"> |
| 180 | + <FeatureCarousel features={featureCards} /> |
| 181 | + </div> |
| 182 | + |
| 183 | + {/* Box 6: Telemetry Banner (Conditional) */} |
| 184 | + {telemetrySetting === "unset" && ( |
| 185 | + <div className="col-span-full bg-[var(--vscode-editorWidget-background)] rounded-2xl overflow-hidden"> |
| 186 | + <TelemetryBanner /> |
| 187 | + </div> |
| 188 | + )} |
| 189 | + |
| 190 | + {/* Task Cards */} |
| 191 | + {dummyTasks.map((task) => ( |
| 192 | + <TaskCard key={task.id} task={task} /> |
| 193 | + ))} |
| 194 | + </div> |
| 195 | + </div> |
| 196 | + ) |
| 197 | +} |
| 198 | + |
| 199 | +// Helper component for task cards |
| 200 | +const TaskCard = ({ task }: { task: TaskItem }) => { |
| 201 | + const [showCopySuccess, setShowCopySuccess] = useState(false) |
| 202 | + const { copyWithFeedback } = useCopyToClipboard(1000) |
| 203 | + |
| 204 | + const handleCopy = (e: React.MouseEvent) => { |
| 205 | + e.stopPropagation() |
| 206 | + copyWithFeedback(task.title).then((success: boolean) => { |
| 207 | + if (success) { |
| 208 | + setShowCopySuccess(true) |
| 209 | + setTimeout(() => setShowCopySuccess(false), 1000) |
| 210 | + } |
| 211 | + }) |
| 212 | + } |
| 213 | + |
| 214 | + return ( |
| 215 | + <div className="col-span-full md:col-span-1 bg-[var(--vscode-editorWidget-background)] rounded-2xl overflow-hidden"> |
| 216 | + <div className="p-5 relative flex flex-col justify-between h-full min-h-[140px]"> |
| 217 | + {/* Copy Button */} |
| 218 | + <VSCodeButton |
| 219 | + appearance="icon" |
| 220 | + onClick={handleCopy} |
| 221 | + title="Copy Task" |
| 222 | + className="absolute top-3 right-3 text-vscode-descriptionForeground hover:text-vscode-foreground transition-colors duration-200"> |
| 223 | + <span className={`codicon ${showCopySuccess ? "codicon-check" : "codicon-copy"}`}></span> |
| 224 | + </VSCodeButton> |
| 225 | + |
| 226 | + {/* Content */} |
| 227 | + <div> |
| 228 | + <h3 className="text-xs font-bold mb-3 text-vscode-descriptionForeground tracking-widest uppercase"> |
| 229 | + Recent Task |
| 230 | + </h3> |
| 231 | + <p className="text-base font-bold mb-2 text-vscode-editor-foreground leading-tight">{task.title}</p> |
| 232 | + </div> |
| 233 | + |
| 234 | + {/* Footer */} |
| 235 | + <div className="flex justify-between items-center text-xs text-vscode-descriptionForeground mt-auto pt-3 border-t border-[var(--vscode-panel-border)]"> |
| 236 | + <span>{task.date}</span> |
| 237 | + <div className="flex gap-3"> |
| 238 | + <span className="flex items-center"> |
| 239 | + <span className="codicon codicon-arrow-up text-xs mr-1"></span> |
| 240 | + {task.tokensIn} |
| 241 | + </span> |
| 242 | + <span className="flex items-center"> |
| 243 | + <span className="codicon codicon-arrow-down text-xs mr-1"></span> |
| 244 | + {task.tokensOut} |
| 245 | + </span> |
| 246 | + <span className="font-medium">{task.cost}</span> |
| 247 | + </div> |
| 248 | + </div> |
| 249 | + </div> |
| 250 | + </div> |
| 251 | + ) |
| 252 | +} |
| 253 | + |
| 254 | +// Carousel component for features |
| 255 | +const FeatureCarousel = ({ features }: { features: { title: string; subtitle: string; id: string }[] }) => { |
| 256 | + const [currentIndex, setCurrentIndex] = useState(0) |
| 257 | + |
| 258 | + // Auto-advance the carousel every 5 seconds |
| 259 | + useEffect(() => { |
| 260 | + const interval = setInterval(() => { |
| 261 | + setCurrentIndex((prevIndex) => (prevIndex + 1) % features.length) |
| 262 | + }, 5000) |
| 263 | + |
| 264 | + return () => clearInterval(interval) |
| 265 | + }, [features.length]) |
| 266 | + |
| 267 | + const nextSlide = () => { |
| 268 | + setCurrentIndex((prevIndex) => (prevIndex + 1) % features.length) |
| 269 | + } |
| 270 | + |
| 271 | + const prevSlide = () => { |
| 272 | + setCurrentIndex((prevIndex) => (prevIndex - 1 + features.length) % features.length) |
| 273 | + } |
| 274 | + |
| 275 | + return ( |
| 276 | + <div className="p-5 flex flex-col h-full relative"> |
| 277 | + <h2 className="text-xs font-bold mb-4 text-vscode-descriptionForeground tracking-widest uppercase"> |
| 278 | + Features |
| 279 | + </h2> |
| 280 | + |
| 281 | + <div className="flex-1 flex flex-col justify-center"> |
| 282 | + <div className="transition-opacity duration-300 min-h-[100px]"> |
| 283 | + <h3 className="text-lg font-bold mb-2 text-vscode-editor-foreground"> |
| 284 | + {features[currentIndex].title} |
| 285 | + </h3> |
| 286 | + <p className="text-sm text-vscode-descriptionForeground">{features[currentIndex].subtitle}</p> |
| 287 | + </div> |
| 288 | + </div> |
| 289 | + |
| 290 | + <div className="flex justify-between mt-4"> |
| 291 | + <button |
| 292 | + onClick={prevSlide} |
| 293 | + className="text-vscode-descriptionForeground hover:text-vscode-foreground transition-colors duration-200"> |
| 294 | + <span className="codicon codicon-chevron-left"></span> |
| 295 | + </button> |
| 296 | + |
| 297 | + <div className="flex gap-2"> |
| 298 | + {features.map((_, index) => ( |
| 299 | + <span |
| 300 | + key={index} |
| 301 | + className={`h-1.5 w-1.5 rounded-full ${index === currentIndex ? "bg-vscode-foreground" : "bg-vscode-descriptionForeground opacity-50"}`}></span> |
| 302 | + ))} |
| 303 | + </div> |
| 304 | + |
| 305 | + <button |
| 306 | + onClick={nextSlide} |
| 307 | + className="text-vscode-descriptionForeground hover:text-vscode-foreground transition-colors duration-200"> |
| 308 | + <span className="codicon codicon-chevron-right"></span> |
| 309 | + </button> |
| 310 | + </div> |
| 311 | + </div> |
| 312 | + ) |
| 313 | +} |
| 314 | + |
| 315 | +export default BentoGrid |
0 commit comments