"use client"; import { useEffect, useState } from "react"; import { formatDistanceToNow } from "date-fns"; import { zhCN } from "date-fns/locale"; import { FolderOpen, Loader2, Pencil, Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { agentProjectService } from "@/services/agent-projects"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { cn } from "@/lib/utils"; import { buildImageProxyUrl } from "@/lib/image-delivery"; import type { AgentGenerationProject, AgentWorkflowMaterialImage, AgentWorkflowMaterialSelectionState, } from "@/types/agent"; function normalizePreviewUrl(value?: string | null) { const normalizedValue = String(value || "").trim(); return normalizedValue || null; } function sortMaterialImages(images: AgentWorkflowMaterialImage[]) { return [...images].sort((left, right) => { const sortOrderDiff = (left.sortOrder || 0) - (right.sortOrder || 0); if (sortOrderDiff !== 0) { return sortOrderDiff; } return left.id.localeCompare(right.id); }); } function getImageUrlFromCandidates(images: AgentWorkflowMaterialImage[]) { for (const image of images) { const imageUrl = normalizePreviewUrl(image.imageUrl); if (imageUrl) { return imageUrl; } } return null; } function getMaterialSelectionPreviewUrl(selection?: AgentWorkflowMaterialSelectionState | null) { const images = selection?.result?.images ?? []; if (!images.length) { return null; } const sortedImages = sortMaterialImages(images); const selectedImageIds = new Set(selection?.selectedImageIds ?? []); const selectedImages = sortedImages.filter((image) => selectedImageIds.has(image.id)); const selectedUploadedImages = selectedImages.filter((image) => image.sourceType === "upload"); const uploadedImages = sortedImages.filter((image) => image.sourceType === "upload"); const searchImages = sortedImages.filter((image) => image.sourceType !== "upload"); return ( getImageUrlFromCandidates(selectedUploadedImages) || getImageUrlFromCandidates(selectedImages) || getImageUrlFromCandidates(uploadedImages) || getImageUrlFromCandidates(searchImages) || getImageUrlFromCandidates(sortedImages) ); } function getProjectPreviewUrl(project: AgentGenerationProject) { const explicitThumbnail = normalizePreviewUrl(project.thumbnail); if (explicitThumbnail) { return explicitThumbnail; } const resultCollections = project.projectState?.resultCollections; const resultPreviewUrl = resultCollections?.scene_generation?.[0] || resultCollections?.detail_generation?.[0] || resultCollections?.detail_replacement?.[0] || resultCollections?.sku_replacement?.[0] || ""; const normalizedResultPreviewUrl = normalizePreviewUrl(resultPreviewUrl); if (normalizedResultPreviewUrl) { return normalizedResultPreviewUrl; } const materials = project.projectState?.workflowState?.materials; return ( getMaterialSelectionPreviewUrl(materials?.primary) || getMaterialSelectionPreviewUrl(materials?.secondary) || null ); } interface AgentProjectListSheetProps { open: boolean; onOpenChange: (open: boolean) => void; currentProjectId?: string | null; onCreateProject: () => Promise | void; onSelectProject: (projectId: string) => Promise | void; onDeleteProject: (projectId: string) => Promise | void; onRenameProject: (projectId: string, currentTitle?: string | null) => Promise | void; } export function AgentProjectListSheet({ open, onOpenChange, currentProjectId, onCreateProject, onSelectProject, onDeleteProject, onRenameProject, }: AgentProjectListSheetProps) { const [projects, setProjects] = useState([]); const [isLoading, setIsLoading] = useState(false); const [pendingDeleteId, setPendingDeleteId] = useState(null); const [pendingRenameId, setPendingRenameId] = useState(null); const loadProjects = async () => { setIsLoading(true); try { const result = await agentProjectService.getProjects(); if (!result.success || !result.data) { throw new Error(result.error || "加载失败"); } setProjects(result.data); } catch (error) { toast.error(error instanceof Error ? error.message : "加载生图项目失败"); } finally { setIsLoading(false); } }; useEffect(() => { if (!open) return; void loadProjects(); }, [open, currentProjectId]); return ( 生图项目列表 当前工作台会自动保存到项目中,这里可以随时切换、重命名或继续生成。
{isLoading ? (
) : projects.length > 0 ? ( projects.map((project) => { const previewUrl = getProjectPreviewUrl(project); return ( ); }) ) : (
还没有生图项目
点击上方按钮创建一个空白项目开始工作。
)}
); }