mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): use lightbox for computer tool screenshots in PWA mode (#23529)
This commit is contained in:
@@ -849,9 +849,9 @@ export const ComputerScreenshot: Story = {
|
||||
});
|
||||
expect(img).toBeInTheDocument();
|
||||
expect(img.getAttribute("src")).toContain("data:image/jpeg;base64,");
|
||||
// Image should be wrapped in a link that opens in a new tab.
|
||||
const link = img.closest("a");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
// Image should be wrapped in a button that opens the lightbox.
|
||||
const button = img.closest("button");
|
||||
expect(button).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { CircleAlertIcon, LoaderIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { ImageLightbox } from "#/pages/AgentsPage/components/ImageLightbox";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
/**
|
||||
* Renders screenshots returned by Anthropic's computer use tool.
|
||||
* When the result contains base64 image data, the actual image is
|
||||
* displayed instead of raw JSON. The image is clickable and opens
|
||||
* in a new tab at full resolution.
|
||||
* displayed instead of raw JSON. Clicking the image opens it in an
|
||||
* in-app lightbox overlay rather than a new tab so that it works
|
||||
* correctly in PWA / standalone mode on iOS.
|
||||
*/
|
||||
export const ComputerTool: React.FC<{
|
||||
imageData: string;
|
||||
@@ -23,10 +26,12 @@ export const ComputerTool: React.FC<{
|
||||
isError: boolean;
|
||||
errorMessage?: string;
|
||||
}> = ({ imageData, mimeType, text, status, isError, errorMessage }) => {
|
||||
const [showLightbox, setShowLightbox] = useState(false);
|
||||
const isRunning = status === "running";
|
||||
const hasImage = imageData.length > 0;
|
||||
const hasText = text.length > 0;
|
||||
const hasContent = hasImage || hasText;
|
||||
const imageSrc = hasImage ? `data:${mimeType};base64,${imageData}` : "";
|
||||
|
||||
return (
|
||||
<ToolCollapsible
|
||||
@@ -60,19 +65,27 @@ export const ComputerTool: React.FC<{
|
||||
}
|
||||
>
|
||||
{hasImage ? (
|
||||
<div className="mt-1.5 overflow-hidden rounded-md border border-solid border-border-default">
|
||||
<a
|
||||
href={`data:${mimeType};base64,${imageData}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src={`data:${mimeType};base64,${imageData}`}
|
||||
alt="Screenshot from computer tool"
|
||||
className="max-h-96 w-auto object-contain"
|
||||
<>
|
||||
<div className="mt-1.5 overflow-hidden rounded-md border border-solid border-border-default">
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer bg-transparent p-0 border-none"
|
||||
onClick={() => setShowLightbox(true)}
|
||||
>
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt="Screenshot from computer tool"
|
||||
className="max-h-96 w-auto object-contain"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{showLightbox && (
|
||||
<ImageLightbox
|
||||
src={imageSrc}
|
||||
onClose={() => setShowLightbox(false)}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : hasText ? (
|
||||
<div className="mt-1.5 rounded-md border border-solid border-border-default px-3 py-2">
|
||||
<pre className="whitespace-pre-wrap text-xs text-content-secondary">
|
||||
|
||||
Reference in New Issue
Block a user