mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 08:54:23 +08:00
131 lines
6.1 KiB
TypeScript
131 lines
6.1 KiB
TypeScript
import { App, Button, Empty, Modal, Space, Table, Tag } from "antd";
|
|
import { Copy, FolderPlus, RefreshCw } from "lucide-react";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
import { PromptDetailDialog } from "@/pages/prompts/components/prompt-detail-dialog";
|
|
import { useCopyText } from "@/hooks/use-copy-text";
|
|
import { useAssetStore } from "@/stores/use-asset-store";
|
|
import { fetchSourcePrompts, refreshSource, type Prompt } from "@/services/api/prompts";
|
|
import type { PromptSource } from "@/services/api/prompt-source-presets";
|
|
|
|
export function PromptSourceContentModal({ source, onClose }: { source: PromptSource | null; onClose: () => void }) {
|
|
const { message } = App.useApp();
|
|
const [items, setItems] = useState<Prompt[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [detail, setDetail] = useState<Prompt | null>(null);
|
|
const copyText = useCopyText();
|
|
const addAsset = useAssetStore((state) => state.addAsset);
|
|
|
|
const load = useCallback(
|
|
async (force: boolean) => {
|
|
if (!source) return;
|
|
setLoading(true);
|
|
try {
|
|
setItems(force ? await refreshSourceItems(source.id) : await fetchSourcePrompts(source.id));
|
|
} catch (error) {
|
|
message.error(error instanceof Error ? error.message : "拉取提示词失败");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[source, message],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (source) void load(false);
|
|
else setItems([]);
|
|
}, [source, load]);
|
|
|
|
const saveAsset = (item: Prompt) => {
|
|
addAsset({ kind: "text", title: item.title, coverUrl: item.coverUrl, tags: item.tags, source: item.category, data: { content: item.prompt }, metadata: { source: "prompt-library", promptId: item.id, githubUrl: item.githubUrl } });
|
|
message.success("已加入我的资产");
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Modal
|
|
open={Boolean(source)}
|
|
onCancel={onClose}
|
|
width={980}
|
|
footer={null}
|
|
title={
|
|
<div className="flex flex-wrap items-center justify-between gap-2 pr-6">
|
|
<div>
|
|
<div className="text-base font-semibold">{source?.name || ""} · 提示词内容</div>
|
|
<div className="mt-0.5 text-xs font-normal text-stone-500">共 {items.length} 条</div>
|
|
</div>
|
|
<Button size="small" icon={<RefreshCw className="size-3.5" />} loading={loading} onClick={() => void load(true)}>
|
|
立即更新
|
|
</Button>
|
|
</div>
|
|
}
|
|
>
|
|
<Table<Prompt>
|
|
rowKey="id"
|
|
size="small"
|
|
loading={loading}
|
|
dataSource={items}
|
|
pagination={{ pageSize: 10, showSizeChanger: false, size: "small" }}
|
|
scroll={{ y: "56vh" }}
|
|
locale={{ emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无提示词" /> }}
|
|
columns={[
|
|
{
|
|
title: "封面",
|
|
dataIndex: "coverUrl",
|
|
width: 72,
|
|
render: (coverUrl: string) => (coverUrl ? <img src={coverUrl} alt="" className="size-12 rounded object-cover" /> : <div className="size-12 rounded bg-stone-100 dark:bg-stone-800" />),
|
|
},
|
|
{
|
|
title: "标题",
|
|
dataIndex: "title",
|
|
render: (title: string, item) => (
|
|
<div className="min-w-0">
|
|
<div className="truncate font-medium">{title}</div>
|
|
<div className="mt-0.5 line-clamp-2 text-xs text-stone-500">{item.prompt}</div>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
title: "标签",
|
|
dataIndex: "tags",
|
|
width: 200,
|
|
render: (tags: string[]) => (
|
|
<div className="flex flex-wrap gap-1">
|
|
{tags.slice(0, 4).map((tag) => (
|
|
<Tag key={tag} className="m-0">
|
|
{tag}
|
|
</Tag>
|
|
))}
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
title: "操作",
|
|
width: 210,
|
|
render: (_, item) => (
|
|
<Space size={4} wrap>
|
|
<Button size="small" type="text" icon={<Copy className="size-3.5" />} onClick={() => copyText(item.prompt, "提示词已复制")}>
|
|
复制
|
|
</Button>
|
|
<Button size="small" type="text" onClick={() => setDetail(item)}>
|
|
详情
|
|
</Button>
|
|
<Button size="small" type="text" icon={<FolderPlus className="size-3.5" />} onClick={() => saveAsset(item)}>
|
|
加入资产
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
</Modal>
|
|
<PromptDetailDialog prompt={detail} onClose={() => setDetail(null)} onCopy={(prompt) => copyText(prompt, "提示词已复制")} onSaveAsset={saveAsset} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
async function refreshSourceItems(sourceId: string) {
|
|
await refreshSource(sourceId);
|
|
return fetchSourcePrompts(sourceId);
|
|
}
|