import {
Code2Icon,
CopyIcon,
DownloadIcon,
ExternalLinkIcon,
EyeIcon,
SquareArrowOutUpRightIcon,
XIcon,
} from "lucide-react";
import * as React from "react";
import { useEffect, useMemo, useState } from "react";
import rehypeKatex from "rehype-katex";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { toast } from "sonner";
import { Streamdown } from "streamdown";
import {
Artifact,
ArtifactAction,
ArtifactActions,
ArtifactContent,
ArtifactHeader,
ArtifactTitle,
} from "@/components/ai-elements/artifact";
import {
InlineCitationCard,
InlineCitationCardBody,
InlineCitationSource,
} from "@/components/ai-elements/inline-citation";
import { Badge } from "@/components/ui/badge";
import { HoverCardTrigger } from "@/components/ui/hover-card";
import { Select, SelectItem } from "@/components/ui/select";
import {
SelectContent,
SelectGroup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { CodeEditor } from "@/components/workspace/code-editor";
import { useArtifactContent } from "@/core/artifacts/hooks";
import { urlOfArtifact } from "@/core/artifacts/utils";
import {
buildCitationMap,
extractDomainFromUrl,
parseCitations,
type Citation,
} from "@/core/citations";
import { useI18n } from "@/core/i18n/hooks";
import { streamdownPlugins } from "@/core/streamdown";
import { checkCodeFile, getFileName } from "@/core/utils/files";
import { cn } from "@/lib/utils";
import { useArtifacts } from "./context";
export function ArtifactFileDetail({
className,
filepath: filepathFromProps,
threadId,
}: {
className?: string;
filepath: string;
threadId: string;
}) {
const { t } = useI18n();
const { artifacts, setOpen, select } = useArtifacts();
const isWriteFile = useMemo(() => {
return filepathFromProps.startsWith("write-file:");
}, [filepathFromProps]);
const filepath = useMemo(() => {
if (isWriteFile) {
const url = new URL(filepathFromProps);
return decodeURIComponent(url.pathname);
}
return filepathFromProps;
}, [filepathFromProps, isWriteFile]);
const { isCodeFile, language } = useMemo(() => {
if (isWriteFile) {
let language = checkCodeFile(filepath).language;
language ??= "text";
return { isCodeFile: true, language };
}
return checkCodeFile(filepath);
}, [filepath, isWriteFile]);
const previewable = useMemo(() => {
return (language === "html" && !isWriteFile) || language === "markdown";
}, [isWriteFile, language]);
const { content } = useArtifactContent({
threadId,
filepath: filepathFromProps,
enabled: isCodeFile && !isWriteFile,
});
// Parse citations and get clean content for code editor
const cleanContent = useMemo(() => {
if (language === "markdown" && content) {
return parseCitations(content).cleanContent;
}
return content;
}, [content, language]);
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
useEffect(() => {
if (previewable) {
setViewMode("preview");
} else {
setViewMode("code");
}
}, [previewable]);
return (