diff --git a/apps/examples/desktop-app/webview/app/page.tsx b/apps/examples/desktop-app/webview/app/page.tsx index 9406775719..72cbc48571 100644 --- a/apps/examples/desktop-app/webview/app/page.tsx +++ b/apps/examples/desktop-app/webview/app/page.tsx @@ -201,6 +201,18 @@ const DiffView = dynamic( { loading: viewLoading, ssr: false }, ); +// Dev-only scripted preview of cloud computer use; open with Cmd/Ctrl+Shift+D or #computer-use-demo. +const ComputerUseDemo = + process.env.NODE_ENV === "production" + ? null + : dynamic( + () => + import( + "@/components/views/chat/computer-use-demo/computer-use-demo" + ).then((module) => module.ComputerUseDemo), + { loading: viewLoading, ssr: false }, + ); + function makeThreadId(): string { return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`; } @@ -318,6 +330,10 @@ export default function Home() { const [whatsNew, setWhatsNew] = useState(null); const [commandBarOpen, setCommandBarOpen] = useState(false); const [exportDiagnosticsOpen, setExportDiagnosticsOpen] = useState(false); + // The demo replaces the thread it was opened over, so navigating away closes it. + const [computerUseDemoThreadId, setComputerUseDemoThreadId] = useState< + string | null + >(null); // Shared by the sidebar search icon and the Cmd/Ctrl+P shortcut. const handleOpenCommandBar = useCallback(() => setCommandBarOpen(true), []); // "welcome" for the full first-run flow; "connect" when re-entered from @@ -765,6 +781,20 @@ export default function Home() { if (showOnboarding) { return; } + if ( + ComputerUseDemo && + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.key.toLowerCase() === "d" + ) { + event.preventDefault(); + const threadId = activeThread?.id ?? null; + setComputerUseDemoThreadId((current) => + current === threadId ? null : threadId, + ); + navigateWith({ view: "chat" }); + return; + } if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) { return; } @@ -781,7 +811,18 @@ export default function Home() { }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [handleNewThread, handleViewChange, showOnboarding]); + }, [ + activeThread?.id, + handleNewThread, + handleViewChange, + navigateWith, + showOnboarding, + ]); + useEffect(() => { + if (ComputerUseDemo && window.location.hash === "#computer-use-demo") { + setComputerUseDemoThreadId(initialThreadId); + } + }, [initialThreadId]); const handleThreadStarted = useCallback( (threadId: string, sessionId?: string) => { dispatchApp({ type: "thread-started", threadId, sessionId }); @@ -960,66 +1001,75 @@ export default function Home() { className="flex min-h-0 flex-1 flex-col" inert={view === "settings" ? true : undefined} > - handleSettingsSectionChange("Remote")} - onPickRemoteWorkspaceDirectory={ - pickRemoteWorkspaceDirectory - } - onSelectEnvironment={handleSelectEnvironment} - remoteEnvironment={ - activeRemoteEnvironment?.id === - activeThread.environmentId - ? activeRemoteEnvironment - : null - } - historySession={activeThread.historySession} - liveHistorySession={ - activeHistorySessionId - ? sessionHistory.sessionById.get( - activeHistorySessionId, - ) - : undefined - } - initialPromptDraft={activeThread.initialPromptDraft} - promptDrafts={promptDrafts} - knownWorkspacePaths={historyWorkspacePaths} - onInitialPromptDraftConsumed={ - handleInitialPromptDraftConsumed - } - onUpdateSessionMetadata={(sessionId, metadata) => - handleUpdateSessionMetadata( - sessionId, - metadata, - activeThread.environmentId, - ) - } - threadId={activeThread.id} - onDeleteSession={(sessionId, threadId) => - handleDeleteSession( - sessionId, - threadId, - activeThread.environmentId, - ) - } - onNewThread={handleNewThread} - onOpenSession={handleOpenSession} - onOpenSessionById={handleOpenSessionById} - onOpenSetup={handleOpenSetup} - onOpenModelSettings={() => - handleSettingsSectionChange("Providers") - } - onOpenAccountSettings={() => - handleSettingsSectionChange("Account") - } - parentSession={activeParentSession} - onThreadStarted={handleThreadStarted} - /> + {ComputerUseDemo && + computerUseDemoThreadId === activeThread.id ? ( + setComputerUseDemoThreadId(null)} + /> + ) : ( + + handleSettingsSectionChange("Remote") + } + onPickRemoteWorkspaceDirectory={ + pickRemoteWorkspaceDirectory + } + onSelectEnvironment={handleSelectEnvironment} + remoteEnvironment={ + activeRemoteEnvironment?.id === + activeThread.environmentId + ? activeRemoteEnvironment + : null + } + historySession={activeThread.historySession} + liveHistorySession={ + activeHistorySessionId + ? sessionHistory.sessionById.get( + activeHistorySessionId, + ) + : undefined + } + initialPromptDraft={activeThread.initialPromptDraft} + promptDrafts={promptDrafts} + knownWorkspacePaths={historyWorkspacePaths} + onInitialPromptDraftConsumed={ + handleInitialPromptDraftConsumed + } + onUpdateSessionMetadata={(sessionId, metadata) => + handleUpdateSessionMetadata( + sessionId, + metadata, + activeThread.environmentId, + ) + } + threadId={activeThread.id} + onDeleteSession={(sessionId, threadId) => + handleDeleteSession( + sessionId, + threadId, + activeThread.environmentId, + ) + } + onNewThread={handleNewThread} + onOpenSession={handleOpenSession} + onOpenSessionById={handleOpenSessionById} + onOpenSetup={handleOpenSetup} + onOpenModelSettings={() => + handleSettingsSectionChange("Providers") + } + onOpenAccountSettings={() => + handleSettingsSectionChange("Account") + } + parentSession={activeParentSession} + onThreadStarted={handleThreadStarted} + /> + )} ) : null} {view === "settings" ? ( diff --git a/apps/examples/desktop-app/webview/components/views/chat/chat-messages.tsx b/apps/examples/desktop-app/webview/components/views/chat/chat-messages.tsx index 907de45364..ebd1f43f31 100644 --- a/apps/examples/desktop-app/webview/components/views/chat/chat-messages.tsx +++ b/apps/examples/desktop-app/webview/components/views/chat/chat-messages.tsx @@ -1,6 +1,10 @@ "use client"; -import { AgentAskQuestion } from "@cline/ui"; +import { + AgentAskQuestion, + type AgentScreenshot, + AgentScreenshotCarousel, +} from "@cline/ui"; import { MessageScroller, MessageScrollerButton, @@ -91,6 +95,8 @@ type ChatMessagesProps = { ) => void | Promise; /** Opens the settings page that fixes a credential failure. */ onFixCredentials?: (target: "account" | "models") => void; + /** Screenshots the agent captured while verifying its work, shown after the transcript. */ + verification?: { screenshots: AgentScreenshot[]; subtitle?: string }; }; type AskQuestionRequestItem = { @@ -128,6 +134,7 @@ function ChatMessagesImpl({ errorAction, onProceedWhileRunning, onFixCredentials, + verification, }: ChatMessagesProps) { const hasMessages = messages.length > 0; // Scanned from the tail without copying: this component re-renders on @@ -214,14 +221,14 @@ function ChatMessagesImpl({ const [forkErrors, setForkErrors] = useState>({}); const [expandedImage, setExpandedImage] = useState<{ sessionId: string | null; - image: ChatMessageImage; + src: string; } | null>(null); const sessionVersioningPending = editingMessageId !== null || forkingMessageId !== null || Object.values(checkpointActions).includes("undoing"); const visibleExpandedImage = - expandedImage?.sessionId === sessionId ? expandedImage.image : null; + expandedImage?.sessionId === sessionId ? expandedImage.src : null; const showIdleDetails = !hasMessages && !isSessionSwitching && !showSwitchTransition; // The live run keeps rendering its rows while the session is active, and an @@ -487,7 +494,10 @@ function ChatMessagesImpl({ const handleExpandImage = useCallback( (image: ChatMessageImage) => { - setExpandedImage({ sessionId, image }); + setExpandedImage({ + sessionId, + src: `data:${image.mediaType};base64,${image.data}`, + }); }, [sessionId], ); @@ -727,6 +737,17 @@ function ChatMessagesImpl({ ); })} + {verification?.screenshots.length ? ( + + + setExpandedImage({ sessionId, src: screenshot.src }) + } + screenshots={verification.screenshots} + subtitle={verification.subtitle} + /> + + ) : null} {/* Lives inside the transcript column and mirrors a reasoning/tool trigger's geometry exactly (icon slot, min-height, padding), so the first real row replaces it @@ -843,7 +864,7 @@ function ChatMessagesImpl({ {visibleExpandedImage ? ( setExpandedImage(null)} /> ) : null} diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/computer-use-demo.tsx b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/computer-use-demo.tsx new file mode 100644 index 0000000000..8980a09db5 --- /dev/null +++ b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/computer-use-demo.tsx @@ -0,0 +1,526 @@ +"use client"; + +import { + AgentComposer, + AgentComposerBody, + AgentConversationLayout, + AgentPullRequestBar, + type AgentPullRequestData, + type AgentScreenshot, +} from "@cline/ui"; +import { RotateCcw, ScanEye, X } from "lucide-react"; +import { useEffect, useState } from "react"; +import { AgentHeader } from "@/components/agent-header"; +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { WindowTitleBarContent } from "@/components/window-title-bar"; +import type { ChatMessage, ChatSessionStatus } from "@/lib/chat-schema"; +import { ChatMessages } from "../chat-messages"; +import board from "./screenshots/01-board.webp"; +import palette from "./screenshots/02-palette.webp"; +import search from "./screenshots/03-search.webp"; +import filtered from "./screenshots/04-filtered.webp"; +import dark from "./screenshots/05-dark.webp"; +import mobile from "./screenshots/06-mobile.webp"; + +/* + * Scripted preview of computer use ("close the loop") in cloud sessions. The + * sandbox cannot stream screenshots to clients yet, so this replays a canned + * session through the real transcript components. The screenshots were + * captured from a fixture app with Xvfb, xdotool, and ImageMagick. + */ + +const SESSION_ID = "computer-use-demo"; +const TITLE = "Add a ⌘K command palette with quick filters"; +const PROMPT = + "Add a ⌘K / Ctrl+K command palette to the Tidepool board with quick filters for overdue, assigned to me, and high priority cards. Build it, run it in the browser, and verify it works before opening a PR."; + +const SCREENSHOTS: AgentScreenshot[] = [ + { + id: "board", + src: board.src, + title: "Board loads with the new Ctrl K hint", + action: "launch", + detail: "localhost:5173", + status: "passed", + }, + { + id: "palette", + src: palette.src, + title: "Ctrl+K opens the command palette", + action: "key", + detail: "Ctrl+K", + status: "passed", + }, + { + id: "search", + src: search.src, + title: "Fuzzy search narrows to “Show overdue”", + action: "type", + detail: "over", + pointer: { x: 0.5, y: 0.285 }, + status: "passed", + }, + { + id: "filtered", + src: filtered.src, + title: "Overdue filter applied: 4 cards and a removable chip", + action: "click", + detail: "Show overdue", + status: "passed", + }, + { + id: "dark", + src: dark.src, + title: "Palette follows the dark theme", + action: "key", + detail: "Ctrl+Shift+L", + status: "passed", + }, + { + id: "mobile", + src: mobile.src, + title: "Palette fits a 390px mobile viewport", + action: "resize", + detail: "390×844", + status: "passed", + }, +]; + +const PULL_REQUEST: AgentPullRequestData = { + repository: "acme/tidepool", + branch: "cline/command-palette", + pullRequest: { + number: 128, + title: "Add ⌘K command palette with quick filters", + url: "https://github.com/acme/tidepool/pull/128", + state: "OPEN", + isDraft: false, + mergeable: "MERGEABLE", + mergeStateStatus: "CLEAN", + additions: 312, + deletions: 18, + checks: [ + { name: "test", state: "success" }, + { name: "lint", state: "success" }, + { name: "preview-deploy", state: "success" }, + ], + }, +}; + +const FINAL_ANSWER = `Added a **command palette** to Tidepool and verified it end to end in the sandbox. + +- \`Ctrl K\` / \`⌘K\` opens it from anywhere and \`Esc\` closes it +- Quick filters for **overdue**, **assigned to me**, and **high priority**, applied as a removable chip +- Fuzzy search with match highlighting, plus theme and clear-filter actions +- 5 new tests, 26 passing + +I ran the app in the sandbox's browser and drove it with the keyboard and mouse across light, dark, and a 390px viewport. The screenshots are below, and the PR is [#128](https://github.com/acme/tidepool/pull/128).`; + +type Beat = + | { wait: number; status: ChatSessionStatus; label?: string } + | { wait: number; say: string; stream?: boolean } + | { + wait: number; + tool: string; + input: unknown; + result: unknown; + runMs: number; + }; + +const commandResult = (query: string, result: string) => [{ query, result }]; +const screenshotResult = (data: string) => [ + { type: "image", mediaType: "image/webp", data }, +]; + +function buildBeats(images: string[]): Beat[] { + const [boardImg, paletteImg, searchImg, filteredImg, darkImg, mobileImg] = + images; + return [ + { wait: 400, status: "starting", label: "Provisioning cloud sandbox…" }, + { wait: 1500, status: "starting", label: "Cloning acme/tidepool…" }, + { wait: 1300, status: "running" }, + { + wait: 900, + say: "I'll look at how the board handles shortcuts and filtering, then build the palette on top of it.", + }, + { + wait: 500, + tool: "read_files", + input: { + files: [ + "src/App.tsx", + "src/board/Board.tsx", + "src/hooks/useHotkeys.ts", + ], + }, + result: "ok", + runMs: 700, + }, + { + wait: 300, + tool: "search_codebase", + input: { queries: ["applyFilter"] }, + result: "src/board/filters.ts:12", + runMs: 600, + }, + { + wait: 600, + tool: "editor", + input: { + path: "src/components/CommandPalette.tsx", + new_text: `export function CommandPalette() { + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); + const { commands, run } = useBoardCommands(); + useHotkeys("mod+k", () => setOpen(true)); + useHotkeys("escape", () => setOpen(false), { enabled: open }); + const results = useMemo(() => fuzzyMatch(commands, query), [commands, query]); + if (!open) return null; + return ( + setOpen(false)}> + + { run(command); setOpen(false); }} /> + + ); +}`, + }, + result: "Created src/components/CommandPalette.tsx", + runMs: 900, + }, + { + wait: 400, + tool: "editor", + input: { + path: "src/App.tsx", + old_text: `\n\t\t\t`, + new_text: `\n\t\t\t\n\t\t\t`, + }, + result: "Edited src/App.tsx", + runMs: 700, + }, + { + wait: 500, + tool: "run_commands", + input: { commands: ["bun test"] }, + result: commandResult( + "bun test", + "src/components/CommandPalette.test.tsx:\n✓ opens with mod+k and closes with escape\n✓ highlights fuzzy matches\n✓ running a filter applies the chip\n\n 26 pass\n 0 fail\nRan 26 tests across 7 files. [412.00ms]", + ), + runMs: 1600, + }, + { + wait: 700, + say: "Tests pass. Next I'll run the app in the sandbox's browser and check the palette the way a user would.", + }, + { + wait: 500, + tool: "run_commands", + input: { + commands: [ + "bun run dev & Xvfb :99 -screen 0 1280x800x24 & DISPLAY=:99 google-chrome --app=http://localhost:5173 &", + ], + }, + result: commandResult( + "bun run dev", + "VITE v6.3.5 ready in 312 ms\n➜ Local: http://localhost:5173/\nChrome attached to display :99", + ), + runMs: 1400, + }, + { + wait: 400, + tool: "computer", + input: { action: "screenshot" }, + result: screenshotResult(boardImg), + runMs: 700, + }, + { + wait: 900, + tool: "computer", + input: { action: "key", text: "ctrl+k" }, + result: screenshotResult(paletteImg), + runMs: 600, + }, + { + wait: 900, + tool: "computer", + input: { action: "type", text: "over" }, + result: screenshotResult(searchImg), + runMs: 800, + }, + { + wait: 900, + tool: "computer", + input: { action: "left_click", coordinate: [640, 228] }, + result: screenshotResult(filteredImg), + runMs: 600, + }, + { + wait: 900, + tool: "computer", + input: { action: "key", text: "ctrl+shift+l" }, + result: screenshotResult(darkImg), + runMs: 600, + }, + { + wait: 900, + tool: "run_commands", + input: { commands: ["xdotool getactivewindow windowsize 390 844"] }, + result: commandResult("xdotool", ""), + runMs: 500, + }, + { + wait: 300, + tool: "computer", + input: { action: "screenshot" }, + result: screenshotResult(mobileImg), + runMs: 600, + }, + { + wait: 900, + tool: "run_commands", + input: { + commands: [ + "git push -u origin cline/command-palette && gh pr create --fill", + ], + }, + result: commandResult( + "gh pr create", + "https://github.com/acme/tidepool/pull/128", + ), + runMs: 1300, + }, + { wait: 700, say: FINAL_ANSWER, stream: true }, + { wait: 400, status: "completed" }, + ]; +} + +async function toBase64(src: string): Promise { + const blob = await (await fetch(src)).blob(); + return await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result).split(",")[1] ?? ""); + reader.onerror = () => reject(reader.error); + reader.readAsDataURL(blob); + }); +} + +type DemoState = { + messages: ChatMessage[]; + status: ChatSessionStatus; + label?: string; + streamingMessageId: string | null; +}; + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +function useDemoPlayback(run: number): DemoState { + const [state, setState] = useState({ + messages: [], + status: "starting", + streamingMessageId: null, + }); + useEffect(() => { + void run; + let cancelled = false; + // Timestamps advance faster than playback so "Worked for…" reads like a real run. + let clock = Date.now() - 4 * 60_000; + const tick = (ms: number) => { + clock += ms * 6; + return clock; + }; + const message = ( + id: string, + role: ChatMessage["role"], + content: string, + meta?: ChatMessage["meta"], + ): ChatMessage => ({ + id, + sessionId: SESSION_ID, + role, + content, + createdAt: clock, + meta, + }); + const push = (next: ChatMessage) => + setState((current) => ({ + ...current, + messages: [...current.messages, next], + })); + const replace = (next: ChatMessage) => + setState((current) => ({ + ...current, + messages: current.messages.map((item) => + item.id === next.id ? next : item, + ), + })); + + void (async () => { + const images = await Promise.all( + SCREENSHOTS.map((shot) => toBase64(shot.src)), + ); + if (cancelled) return; + setState({ + messages: [message("prompt", "user", PROMPT)], + status: "starting", + label: undefined, + streamingMessageId: null, + }); + let index = 0; + for (const beat of buildBeats(images)) { + await sleep(beat.wait); + if (cancelled) return; + tick(beat.wait); + const id = `beat-${index++}`; + if ("status" in beat) { + setState((current) => ({ + ...current, + status: beat.status, + label: beat.label, + })); + } else if ("say" in beat) { + if (!beat.stream) { + push(message(id, "assistant", beat.say)); + continue; + } + const words = beat.say.split(/(?<=\s)/); + push(message(id, "assistant", "")); + setState((current) => ({ ...current, streamingMessageId: id })); + for (let count = 3; count < words.length + 3; count += 3) { + await sleep(45); + if (cancelled) return; + replace(message(id, "assistant", words.slice(0, count).join(""))); + } + setState((current) => ({ ...current, streamingMessageId: null })); + } else { + const meta = { toolName: beat.tool, toolCallId: id }; + push( + message( + id, + "tool", + JSON.stringify({ + toolName: beat.tool, + input: beat.input, + result: null, + }), + { + ...meta, + hookEventName: "tool_call_start", + }, + ), + ); + await sleep(beat.runMs); + if (cancelled) return; + tick(beat.runMs); + replace( + message( + id, + "tool", + JSON.stringify({ + toolName: beat.tool, + input: beat.input, + result: beat.result, + }), + meta, + ), + ); + } + } + })(); + return () => { + cancelled = true; + }; + }, [run]); + return state; +} + +export function ComputerUseDemo({ onExit }: { onExit: () => void }) { + const [run, setRun] = useState(0); + const { messages, status, label, streamingMessageId } = useDemoPlayback(run); + const completed = status === "completed"; + + return ( +
+ +
+ +
+
+ {}} + onApproveToolApproval={() => {}} + onRejectToolApproval={() => {}} + pendingAskQuestions={[]} + pendingToolApprovals={[]} + sessionId={SESSION_ID} + startingLabel={status === "starting" ? label : undefined} + status={status} + streamingMessageId={streamingMessageId} + verification={ + completed + ? { + screenshots: SCREENSHOTS, + subtitle: + "Chrome 1280×800 in the cloud sandbox · captured by the agent", + } + : undefined + } + /> + } + bodyClassName="cline-view-enter" + composer={ + + {completed ? ( + {}} + renderChecks={(trigger, content) => ( + + {trigger} + + {content} + + + )} + /> + ) : null} + + +

+ Scripted preview of computer use in cloud sessions. The agent + builds, runs, and screenshots its own work. +

+ + +
+
+ } + welcome={false} + /> +
+ ); +} diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/01-board.webp b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/01-board.webp new file mode 100644 index 0000000000..81d363455f Binary files /dev/null and b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/01-board.webp differ diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/02-palette.webp b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/02-palette.webp new file mode 100644 index 0000000000..5571b30263 Binary files /dev/null and b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/02-palette.webp differ diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/03-search.webp b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/03-search.webp new file mode 100644 index 0000000000..d9b088a42d Binary files /dev/null and b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/03-search.webp differ diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/04-filtered.webp b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/04-filtered.webp new file mode 100644 index 0000000000..418664ee65 Binary files /dev/null and b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/04-filtered.webp differ diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/05-dark.webp b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/05-dark.webp new file mode 100644 index 0000000000..07c6f99811 Binary files /dev/null and b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/05-dark.webp differ diff --git a/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/06-mobile.webp b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/06-mobile.webp new file mode 100644 index 0000000000..1dab25439a Binary files /dev/null and b/apps/examples/desktop-app/webview/components/views/chat/computer-use-demo/screenshots/06-mobile.webp differ diff --git a/apps/examples/desktop-app/webview/components/views/chat/messages/image-lightbox.tsx b/apps/examples/desktop-app/webview/components/views/chat/messages/image-lightbox.tsx index d37a796872..396a753cef 100644 --- a/apps/examples/desktop-app/webview/components/views/chat/messages/image-lightbox.tsx +++ b/apps/examples/desktop-app/webview/components/views/chat/messages/image-lightbox.tsx @@ -1,13 +1,12 @@ "use client"; import { AgentImageLightboxContent } from "@cline/ui"; -import type { ChatMessageImage } from "@/lib/chat-schema"; export function ChatImageLightbox({ - image, + src, onClose, }: { - image: ChatMessageImage; + src: string; onClose: () => void; }) { return ( @@ -18,7 +17,7 @@ export function ChatImageLightbox({ role="dialog" > diff --git a/apps/examples/desktop-app/webview/components/views/chat/messages/tool-icons.ts b/apps/examples/desktop-app/webview/components/views/chat/messages/tool-icons.ts index 43615c7702..5891624e91 100644 --- a/apps/examples/desktop-app/webview/components/views/chat/messages/tool-icons.ts +++ b/apps/examples/desktop-app/webview/components/views/chat/messages/tool-icons.ts @@ -10,6 +10,7 @@ import { LibraryIcon, type LucideIcon, MessageCircleQuestionMarkIcon, + MousePointerClickIcon, PanelsTopLeftIcon, PencilIcon, SearchCodeIcon, @@ -22,6 +23,7 @@ import { // Overrides for tools whose kind-based icon would be misleading. export const TOOL_NAME_ICONS: Record = { + computer: MousePointerClickIcon, plugins: BlocksIcon, submit_and_exit: SquareArrowRightIcon, };