fix: improve agent project previews and step wrapping
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-03 14:14:55 +08:00
parent a197a21337
commit 5037a1a1eb
2 changed files with 107 additions and 43 deletions

View File

@ -17,23 +17,85 @@ import {
} from "@/components/ui/sheet";
import { cn } from "@/lib/utils";
import { buildImageProxyUrl } from "@/lib/image-delivery";
import type { AgentGenerationProject } from "@/types/agent";
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 = String(project.thumbnail || "").trim();
const explicitThumbnail = normalizePreviewUrl(project.thumbnail);
if (explicitThumbnail) {
return explicitThumbnail;
}
const resultCollections = project.projectState?.resultCollections;
const fallbackUrl =
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;
}
return String(fallbackUrl || "").trim() || null;
const materials = project.projectState?.workflowState?.materials;
return (
getMaterialSelectionPreviewUrl(materials?.primary) ||
getMaterialSelectionPreviewUrl(materials?.secondary) ||
null
);
}
interface AgentProjectListSheetProps {

View File

@ -1,6 +1,6 @@
"use client";
import { Fragment, useEffect, useMemo, useState, type ReactNode } from "react";
import { useEffect, useMemo, useState, type ReactNode } from "react";
import { motion } from "framer-motion";
import { ArrowRight, Check, Copy, Sparkles, ChevronRight } from "lucide-react";
import { toast } from "sonner";
@ -958,7 +958,7 @@ export function AgentWorkflowPanelDarkV2({
>
<div className="mb-5 space-y-4">
<div className="mt-3">
<div className="agent-dark-scrollbar flex items-center gap-4 overflow-x-auto pb-2">
<div className="flex flex-wrap items-stretch gap-3 pb-1">
{flattenedResolvedSteps.map(({ step }, stepIndex) => {
const completed = isAgentWorkflowStepSelectionComplete({
step,
@ -975,8 +975,18 @@ export function AgentWorkflowPanelDarkV2({
});
return (
<Fragment key={step.key}>
<div className="group flex shrink-0 items-center gap-3 text-left">
<div
key={step.key}
className={cn(
"group flex min-w-[220px] max-w-full flex-1 basis-[280px] flex-col gap-3 rounded-[20px] border px-3 py-3 text-left shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]",
active
? "border-emerald-300/28 bg-emerald-400/[0.08]"
: completed
? "border-white/14 bg-white/[0.05]"
: "border-white/10 bg-white/[0.025]"
)}
>
<div className="flex flex-wrap items-start gap-3 text-left">
<button
type="button"
onClick={() => {
@ -985,11 +995,11 @@ export function AgentWorkflowPanelDarkV2({
setActiveEmbeddedStepKey(step.key);
setActiveEmbeddedOptionDepth(1);
}}
className="flex shrink-0 items-center gap-3 self-center text-left transition-transform duration-200 hover:-translate-y-[1px]"
className="flex min-w-0 flex-1 items-start gap-2.5 text-left transition-transform duration-200 hover:-translate-y-[1px]"
>
<span
className={cn(
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-[11px] font-semibold transition-colors",
"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border text-[10px] font-semibold transition-colors",
active
? "border-emerald-300 bg-emerald-300 text-black shadow-[0_0_18px_rgba(52,211,153,0.28)]"
: completed
@ -999,27 +1009,29 @@ export function AgentWorkflowPanelDarkV2({
>
{stepIndex + 1}
</span>
<span
className={cn(
"truncate text-[13px] font-semibold leading-none transition-colors",
active
? "text-white"
: completed
? "text-white/82"
: "text-white/52 group-hover:text-white/76"
)}
>
{step.title}
</span>
{active ? (
<span className="inline-flex h-7 shrink-0 items-center rounded-full border border-emerald-300/30 bg-emerald-400/10 px-2.5 text-[10px] font-medium leading-none text-emerald-200">
当前步骤
</span>
) : null}
<div className="min-w-0 flex-1 space-y-1">
<div
className={cn(
"text-[12px] font-semibold leading-5 transition-colors",
active
? "text-white"
: completed
? "text-white/82"
: "text-white/52 group-hover:text-white/76"
)}
>
{step.title}
</div>
{active ? (
<span className="inline-flex min-h-[24px] items-center rounded-full border border-emerald-300/30 bg-emerald-400/10 px-2.5 py-1 text-[10px] font-medium leading-none text-emerald-200">
当前步骤
</span>
) : null}
</div>
</button>
<div className="flex shrink-0 items-center gap-2">
<div className="flex min-w-0 flex-1 items-start">
{stepSelectedPath.length > 0 ? (
<div className="flex shrink-0 items-center gap-1.5">
<div className="flex min-w-0 flex-wrap items-start gap-1.5">
{stepSelectedPath.map((option, optionIndex) => {
const optionDepth = optionIndex + 1;
const optionLevelActive =
@ -1037,7 +1049,7 @@ export function AgentWorkflowPanelDarkV2({
setActiveEmbeddedOptionDepth(optionDepth);
}}
className={cn(
"inline-flex h-7 max-w-full shrink-0 items-center gap-1 rounded-full border px-2.5 text-[10px] leading-none transition-colors",
"inline-flex min-h-[24px] max-w-full items-center gap-1 rounded-full border px-2 py-1 text-[10px] leading-[1.15] transition-colors",
optionLevelActive
? "border-emerald-300/35 bg-emerald-400/12 text-emerald-100"
: active || completed
@ -1049,7 +1061,7 @@ export function AgentWorkflowPanelDarkV2({
<span className="text-[9px] opacity-70">
{getWorkflowOptionDepthShortLabel(optionDepth)}
</span>
<span className="max-w-[9rem] truncate whitespace-nowrap">
<span className="max-w-[11rem] break-words whitespace-normal">
{option.buttonName}
</span>
</button>
@ -1057,23 +1069,13 @@ export function AgentWorkflowPanelDarkV2({
})}
</div>
) : (
<span className="shrink-0 text-[10px] leading-none text-white/32">
<span className="text-[10px] leading-none text-white/32">
待选择
</span>
)}
</div>
</div>
{stepIndex < flattenedResolvedSteps.length - 1 ? (
<div
className={cn(
"h-px w-8 shrink-0 rounded-full",
stepIndex < activeEmbeddedStepIndex
? "bg-emerald-400/35"
: "bg-white/10"
)}
/>
) : null}
</Fragment>
</div>
);
})}
</div>