2026-01-17 15:19:53 +08:00
|
|
|
import {
|
|
|
|
|
CopyIcon,
|
|
|
|
|
DownloadIcon,
|
|
|
|
|
SquareArrowOutUpRightIcon,
|
|
|
|
|
XIcon,
|
|
|
|
|
} from "lucide-react";
|
2026-01-17 15:09:44 +08:00
|
|
|
import { useMemo } from "react";
|
|
|
|
|
import { toast } from "sonner";
|
2026-01-17 00:05:19 +08:00
|
|
|
|
2026-01-17 15:09:44 +08:00
|
|
|
import {
|
|
|
|
|
Artifact,
|
|
|
|
|
ArtifactAction,
|
|
|
|
|
ArtifactActions,
|
|
|
|
|
ArtifactContent,
|
|
|
|
|
ArtifactHeader,
|
|
|
|
|
ArtifactTitle,
|
|
|
|
|
} from "@/components/ai-elements/artifact";
|
2026-01-17 17:21:37 +08:00
|
|
|
import { Select, SelectItem } from "@/components/ui/select";
|
|
|
|
|
import {
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectGroup,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from "@/components/ui/select";
|
2026-01-17 15:09:44 +08:00
|
|
|
import { useArtifactContent } from "@/core/artifacts/hooks";
|
|
|
|
|
import { urlOfArtifact } from "@/core/artifacts/utils";
|
2026-01-17 17:21:37 +08:00
|
|
|
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
2026-01-17 11:02:33 +08:00
|
|
|
import { cn } from "@/lib/utils";
|
2026-01-17 00:05:19 +08:00
|
|
|
|
2026-01-17 15:09:44 +08:00
|
|
|
import { useArtifacts } from "./context";
|
|
|
|
|
import { FileViewer } from "./file-viewer";
|
|
|
|
|
|
2026-01-17 11:02:33 +08:00
|
|
|
export function ArtifactFileDetail({
|
|
|
|
|
className,
|
|
|
|
|
filepath,
|
2026-01-17 15:09:44 +08:00
|
|
|
threadId,
|
2026-01-17 11:02:33 +08:00
|
|
|
}: {
|
|
|
|
|
className?: string;
|
|
|
|
|
filepath: string;
|
2026-01-17 15:09:44 +08:00
|
|
|
threadId: string;
|
2026-01-17 11:02:33 +08:00
|
|
|
}) {
|
2026-01-17 17:21:37 +08:00
|
|
|
const { artifacts, setOpen, select } = useArtifacts();
|
2026-01-17 15:09:44 +08:00
|
|
|
const { isCodeFile } = useMemo(() => checkCodeFile(filepath), [filepath]);
|
|
|
|
|
const { content } = useArtifactContent({
|
|
|
|
|
threadId,
|
|
|
|
|
filepath,
|
|
|
|
|
enabled: isCodeFile,
|
|
|
|
|
});
|
2026-01-17 00:02:03 +08:00
|
|
|
return (
|
2026-01-17 15:09:44 +08:00
|
|
|
<Artifact className={cn("rounded-none", className)}>
|
2026-01-17 17:21:37 +08:00
|
|
|
<ArtifactHeader className="px-2">
|
2026-01-17 00:02:03 +08:00
|
|
|
<div>
|
2026-01-17 17:21:37 +08:00
|
|
|
<ArtifactTitle>
|
|
|
|
|
<Select value={filepath} onValueChange={select}>
|
2026-01-17 20:32:27 +08:00
|
|
|
<SelectTrigger className="border-none bg-transparent! shadow-none select-none focus:outline-0 active:outline-0">
|
2026-01-17 17:21:37 +08:00
|
|
|
<SelectValue placeholder="Select a file" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent className="select-none">
|
|
|
|
|
<SelectGroup>
|
|
|
|
|
{(artifacts ?? []).map((filepath) => (
|
|
|
|
|
<SelectItem key={filepath} value={filepath}>
|
|
|
|
|
{getFileName(filepath)}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectGroup>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</ArtifactTitle>
|
2026-01-17 15:09:44 +08:00
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<ArtifactActions>
|
2026-01-17 15:19:53 +08:00
|
|
|
<a href={urlOfArtifact({ filepath, threadId })} target="_blank">
|
|
|
|
|
<ArtifactAction
|
|
|
|
|
icon={SquareArrowOutUpRightIcon}
|
|
|
|
|
label="Open in new window"
|
|
|
|
|
tooltip="Open in new window"
|
|
|
|
|
/>
|
|
|
|
|
</a>
|
2026-01-17 15:09:44 +08:00
|
|
|
{isCodeFile && (
|
|
|
|
|
<ArtifactAction
|
|
|
|
|
icon={CopyIcon}
|
|
|
|
|
label="Copy"
|
|
|
|
|
disabled={!content}
|
|
|
|
|
onClick={async () => {
|
|
|
|
|
try {
|
|
|
|
|
await navigator.clipboard.writeText(content ?? "");
|
|
|
|
|
toast.success("Copied to clipboard");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
toast.error("Failed to copy to clipboard");
|
|
|
|
|
console.error(error);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
tooltip="Copy content to clipboard"
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<a
|
|
|
|
|
href={urlOfArtifact({ filepath, threadId, download: true })}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<ArtifactAction
|
|
|
|
|
icon={DownloadIcon}
|
|
|
|
|
label="Download"
|
|
|
|
|
onClick={() => console.log("Download")}
|
|
|
|
|
tooltip="Download file"
|
|
|
|
|
/>
|
|
|
|
|
</a>
|
|
|
|
|
<ArtifactAction
|
|
|
|
|
icon={XIcon}
|
|
|
|
|
label="Close"
|
|
|
|
|
onClick={() => setOpen(false)}
|
|
|
|
|
tooltip="Close"
|
|
|
|
|
/>
|
|
|
|
|
</ArtifactActions>
|
2026-01-17 00:02:03 +08:00
|
|
|
</div>
|
2026-01-17 15:09:44 +08:00
|
|
|
</ArtifactHeader>
|
|
|
|
|
<ArtifactContent className="p-0">
|
|
|
|
|
<FileViewer
|
|
|
|
|
className="size-full"
|
|
|
|
|
threadId={threadId}
|
|
|
|
|
filepath={filepath}
|
|
|
|
|
/>
|
|
|
|
|
</ArtifactContent>
|
|
|
|
|
</Artifact>
|
2026-01-17 00:02:03 +08:00
|
|
|
);
|
|
|
|
|
}
|