feat(frontend): artifact 文件卡片支持右键引用

This commit is contained in:
肖应宇 2026-04-16 15:18:14 +08:00
parent 4dbe930775
commit 3d5006af48
1 changed files with 86 additions and 62 deletions

View File

@ -10,9 +10,16 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { urlOfArtifact } from "@/core/artifacts/utils";
import { useI18n } from "@/core/i18n/hooks";
import { installSkill } from "@/core/skills/api";
import { dispatchMentionReference } from "@/core/threads/reference-events";
import {
getFileExtensionDisplayName,
getFileIcon,
@ -78,69 +85,86 @@ export function ArtifactFileList({
data-testid="artifact-file-list"
>
{files.map((file) => (
<Card
key={file}
className="relative cursor-pointer p-4"
data-testid="artifact-file-card"
onClick={() => handleClick(file)}
>
<CardHeader className="pr-2 pl-1">
<CardTitle className="relative overflow-hidden pl-10">
<div
className="text-sm font-normal text-ellipsis whitespace-nowrap"
title={getFileName(file)}
>
{truncateMiddle(getFileName(file), 50)}
</div>
</CardTitle>
<div className="absolute top-5 left-4">
{getFileIcon(file, "size-9 stroke-[1px] stroke-[#333333]")}
</div>
<CardDescription className="pl-10 text-xs">
{getFileExtensionDisplayName(file)} file
</CardDescription>
<CardAction>
{file.endsWith(".skill") && (
<Button
variant="ghost"
disabled={!threadId || installingFile === file}
onClick={(e) => handleInstallSkill(e, file)}
>
{installingFile === file ? (
<LoaderIcon className="size-4 animate-spin" />
) : (
<PackageIcon className="size-4" />
)}
{t.common.install}
</Button>
)}
{threadId ? (
<a
href={urlOfArtifact({
filepath: file,
threadId,
download: true,
})}
target="_blank"
onClick={(e) => e.stopPropagation()}
>
<Button variant="ghost"
className="h-full! text-[var(--muted-foreground)]! hover:bg-transparent! hover:text-[#333333]!"
<ContextMenu key={file}>
<ContextMenuTrigger asChild>
<Card
className="relative cursor-pointer p-4"
data-testid="artifact-file-card"
onClick={() => handleClick(file)}
>
<CardHeader className="pr-2 pl-1">
<CardTitle className="relative overflow-hidden pl-10">
<div
className="text-sm font-normal text-ellipsis whitespace-nowrap"
title={getFileName(file)}
>
<DownloadIcon className="size-4" />
{t.common.download}
</Button>
</a>
) : (
<Button variant="ghost" disabled>
<DownloadIcon className="size-4" />
{t.common.download}
</Button>
)}
</CardAction>
</CardHeader>
</Card>
{truncateMiddle(getFileName(file), 50)}
</div>
</CardTitle>
<div className="absolute top-5 left-4">
{getFileIcon(file, "size-9 stroke-[1px] stroke-[#333333]")}
</div>
<CardDescription className="pl-10 text-xs">
{getFileExtensionDisplayName(file)} file
</CardDescription>
<CardAction>
{file.endsWith(".skill") && (
<Button
variant="ghost"
disabled={!threadId || installingFile === file}
onClick={(e) => handleInstallSkill(e, file)}
>
{installingFile === file ? (
<LoaderIcon className="size-4 animate-spin" />
) : (
<PackageIcon className="size-4" />
)}
{t.common.install}
</Button>
)}
{threadId ? (
<a
href={urlOfArtifact({
filepath: file,
threadId,
download: true,
})}
target="_blank"
onClick={(e) => e.stopPropagation()}
>
<Button
variant="ghost"
className="h-full! text-[var(--muted-foreground)]! hover:bg-transparent! hover:text-[#333333]!"
>
<DownloadIcon className="size-4" />
{t.common.download}
</Button>
</a>
) : (
<Button variant="ghost" disabled>
<DownloadIcon className="size-4" />
{t.common.download}
</Button>
)}
</CardAction>
</CardHeader>
</Card>
</ContextMenuTrigger>
<ContextMenuContent className="min-w-[120px] p-1">
<ContextMenuItem
onSelect={() => {
dispatchMentionReference({
threadId,
filename: getFileName(file),
path: file,
ref_source: "artifact",
});
}}
>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
))}
</ul>
);