feat: streamline agent mode selection flow
This commit is contained in:
parent
0fab83251c
commit
043bbab84d
@ -8,6 +8,8 @@ import { motion, AnimatePresence } from "framer-motion";
|
|||||||
import JSZip from "jszip";
|
import JSZip from "jszip";
|
||||||
import {
|
import {
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
Download,
|
Download,
|
||||||
ImagePlus,
|
ImagePlus,
|
||||||
Loader2,
|
Loader2,
|
||||||
@ -20,6 +22,11 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from "@/components/ui/collapsible";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -411,6 +418,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
: null
|
: null
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const [isAttachmentSlotExpanded, setIsAttachmentSlotExpanded] = useState(false);
|
||||||
const [slotUploading, setSlotUploading] = useState<number | null>(null);
|
const [slotUploading, setSlotUploading] = useState<number | null>(null);
|
||||||
const [selectedModelId, setSelectedModelId] = useState<string>(
|
const [selectedModelId, setSelectedModelId] = useState<string>(
|
||||||
normalizedIncomingWorkspaceState.selectedModelId || "auto"
|
normalizedIncomingWorkspaceState.selectedModelId || "auto"
|
||||||
@ -533,6 +541,16 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
);
|
);
|
||||||
const selectedModelAttachment =
|
const selectedModelAttachment =
|
||||||
modelSlotIndex >= 0 && attachments[modelSlotIndex] ? attachments[modelSlotIndex] : null;
|
modelSlotIndex >= 0 && attachments[modelSlotIndex] ? attachments[modelSlotIndex] : null;
|
||||||
|
const filledAttachmentCount = attachments.filter(Boolean).length;
|
||||||
|
const missingAttachmentCount = Math.max(requiredUploadCount - filledAttachmentCount, 0);
|
||||||
|
const productAttachmentSummary = `${selectedProductAttachments.length}/${productSlotCount} 张产品图`;
|
||||||
|
const modelAttachmentSummary = modelSlotEnabled
|
||||||
|
? selectedModelAttachment
|
||||||
|
? "模特图已带入"
|
||||||
|
: "模特图待补充"
|
||||||
|
: null;
|
||||||
|
const attachmentSummaryReady =
|
||||||
|
hasAgentUploadSlots && (requiredUploadCount === 0 || filledAttachmentCount >= requiredUploadCount);
|
||||||
const resolvedAgentModeState = agentModeState || localAgentModeState;
|
const resolvedAgentModeState = agentModeState || localAgentModeState;
|
||||||
const emitAgentModeState = onAgentModeStateChange || setLocalAgentModeState;
|
const emitAgentModeState = onAgentModeStateChange || setLocalAgentModeState;
|
||||||
const detailReplacementState = resolvedAgentModeState.detailReplacement;
|
const detailReplacementState = resolvedAgentModeState.detailReplacement;
|
||||||
@ -869,6 +887,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
(image) => image.source === "primary" || image.source === "secondary"
|
(image) => image.source === "primary" || image.source === "secondary"
|
||||||
);
|
);
|
||||||
const hasSatisfiedSlotRequirement = !hasAgentUploadSlots || attachmentCount >= requiredUploadCount;
|
const hasSatisfiedSlotRequirement = !hasAgentUploadSlots || attachmentCount >= requiredUploadCount;
|
||||||
|
const slotRequirementMessage =
|
||||||
|
hasAgentUploadSlots && !hasSatisfiedSlotRequirement
|
||||||
|
? `当前还缺少 ${missingAttachmentCount} 张输入图,请先在“选择 SPU / SKU”步骤补齐后再继续。`
|
||||||
|
: null;
|
||||||
const mergedSettingsStepLabel = hasMaterialStep ? "选图与生成" : "生成参数";
|
const mergedSettingsStepLabel = hasMaterialStep ? "选图与生成" : "生成参数";
|
||||||
const mergedSettingsStepDescription = hasMaterialStep
|
const mergedSettingsStepDescription = hasMaterialStep
|
||||||
? "搜索素材、补充要求、确认槽位并开始生成"
|
? "搜索素材、补充要求、确认槽位并开始生成"
|
||||||
@ -1077,6 +1099,26 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
}
|
}
|
||||||
}, [latestGeneratedGroup?.imageUrls, selectedResultUrl]);
|
}, [latestGeneratedGroup?.imageUrls, selectedResultUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedAgentPresetId || !hasAgentUploadSlots) {
|
||||||
|
setIsAttachmentSlotExpanded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (filledAttachmentCount < requiredUploadCount) {
|
||||||
|
setIsAttachmentSlotExpanded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasMaterialStep) {
|
||||||
|
setIsAttachmentSlotExpanded(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
filledAttachmentCount,
|
||||||
|
hasMaterialStep,
|
||||||
|
hasAgentUploadSlots,
|
||||||
|
requiredUploadCount,
|
||||||
|
selectedAgentPresetId,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedAgentPresetId) {
|
if (!selectedAgentPresetId) {
|
||||||
setActiveToolStep("preset");
|
setActiveToolStep("preset");
|
||||||
@ -1392,6 +1434,19 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
[toolSteps]
|
[toolSteps]
|
||||||
);
|
);
|
||||||
const scrollToSection = useCallback((sectionId: string) => {
|
const scrollToSection = useCallback((sectionId: string) => {
|
||||||
|
if (
|
||||||
|
(sectionId === "agent-anchor-generate" || sectionId === "agent-anchor-results") &&
|
||||||
|
hasAgentUploadSlots &&
|
||||||
|
!hasSatisfiedSlotRequirement
|
||||||
|
) {
|
||||||
|
toast.error(slotRequirementMessage || "请先补齐输入图后再继续");
|
||||||
|
const fallbackTarget = hasMaterialStep ? "agent-anchor-materials" : "agent-anchor-generate";
|
||||||
|
const fallbackElement = typeof document !== "undefined"
|
||||||
|
? document.getElementById(fallbackTarget)
|
||||||
|
: null;
|
||||||
|
fallbackElement?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof document === "undefined") {
|
if (typeof document === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1400,7 +1455,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
}, []);
|
}, [hasAgentUploadSlots, hasMaterialStep, hasSatisfiedSlotRequirement, slotRequirementMessage]);
|
||||||
const updateDetailReplacementState = useCallback(
|
const updateDetailReplacementState = useCallback(
|
||||||
(
|
(
|
||||||
updater:
|
updater:
|
||||||
@ -1610,90 +1665,160 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
}, [updateSkuReplacementState]);
|
}, [updateSkuReplacementState]);
|
||||||
|
|
||||||
const attachmentSlotPanel = (
|
const attachmentSlotPanel = (
|
||||||
<div className="rounded-[28px] border border-white/12 bg-white/[0.02] p-4">
|
<Collapsible
|
||||||
<h3 className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
open={isAttachmentSlotExpanded}
|
||||||
<ImagePlus className="h-4 w-4 text-emerald-300" />
|
onOpenChange={setIsAttachmentSlotExpanded}
|
||||||
输入图槽位
|
>
|
||||||
</h3>
|
<div className="rounded-[28px] border border-white/12 bg-white/[0.02] p-4">
|
||||||
<div className="mb-4 rounded-[20px] border border-emerald-400/15 bg-emerald-500/[0.06] px-4 py-3 text-xs leading-6 text-emerald-100/80">
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||||
上一步已选的白底图和品牌模特图会自动带入对应槽位,这里只保留最终检查与单张上传替换,不再重复展示已选素材列表。
|
<div className="space-y-3">
|
||||||
</div>
|
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||||
{agentUploadSlots.length > 0 ? (
|
<ImagePlus className="h-4 w-4 text-emerald-300" />
|
||||||
<div className="space-y-4">
|
输入图槽位
|
||||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
</div>
|
||||||
{agentUploadSlots.map((slot, index) => {
|
|
||||||
return (
|
{agentUploadSlots.length > 0 ? (
|
||||||
<motion.div
|
<>
|
||||||
key={slot.label}
|
<div className="flex flex-wrap gap-2">
|
||||||
whileHover={{ scale: 1.02 }}
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative aspect-square overflow-hidden rounded-[24px] transition-all",
|
"rounded-full border px-3 py-1 text-xs",
|
||||||
attachments[index]
|
attachmentSummaryReady
|
||||||
? "ring-2 ring-emerald-400/45"
|
? "border-emerald-400/25 bg-emerald-500/[0.10] text-emerald-100"
|
||||||
: "border-2 border-dashed border-white/15 bg-black/25 hover:border-emerald-400/35"
|
: "border-amber-400/25 bg-amber-500/[0.10] text-amber-100"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<input
|
{productAttachmentSummary}
|
||||||
ref={(el) => {
|
</span>
|
||||||
fileInputRefs.current[index] = el;
|
{modelAttachmentSummary ? (
|
||||||
}}
|
<span
|
||||||
type="file"
|
className={cn(
|
||||||
accept="image/*"
|
"rounded-full border px-3 py-1 text-xs",
|
||||||
className="hidden"
|
selectedModelAttachment
|
||||||
onChange={(e) => handleSlotFileChange(index, e)}
|
? "border-emerald-400/25 bg-emerald-500/[0.10] text-emerald-100"
|
||||||
/>
|
: "border-amber-400/25 bg-amber-500/[0.10] text-amber-100"
|
||||||
{attachments[index] ? (
|
)}
|
||||||
<>
|
>
|
||||||
<img
|
{modelAttachmentSummary}
|
||||||
src={buildPreviewImageUrl(attachments[index]!.url, 256)}
|
</span>
|
||||||
alt={attachments[index]!.name || slot.label}
|
) : null}
|
||||||
className="h-full w-full object-cover"
|
<span className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs text-white/60">
|
||||||
/>
|
共 {agentUploadSlots.length} 个输入槽位
|
||||||
<div className="absolute inset-x-0 bottom-0 bg-black/70 px-3 py-2 text-xs font-medium text-white">
|
</span>
|
||||||
{slot.label}
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs leading-6 text-white/55">
|
||||||
|
{missingAttachmentCount > 0
|
||||||
|
? `当前还需补充 ${missingAttachmentCount} 张输入素材,展开后可继续检查或替换。`
|
||||||
|
: "素材已自动带入并准备完成,默认折叠显示;需要时再展开检查或替换即可。"}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs leading-6 text-white/45">
|
||||||
|
先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{agentUploadSlots.length > 0 ? (
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
className="border-white/12 bg-black/25 text-white/82 hover:border-white/22 hover:bg-white/[0.06] hover:text-white"
|
||||||
|
>
|
||||||
|
{isAttachmentSlotExpanded ? "收起素材槽位" : "检查/替换素材"}
|
||||||
|
{isAttachmentSlotExpanded ? (
|
||||||
|
<ChevronUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ChevronDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{agentUploadSlots.length > 0 ? (
|
||||||
|
<CollapsibleContent className="mt-4 space-y-4">
|
||||||
|
<div className="rounded-[20px] border border-emerald-400/15 bg-emerald-500/[0.06] px-4 py-3 text-xs leading-6 text-emerald-100/80">
|
||||||
|
上一步已选的白底图和品牌模特图会自动带入对应槽位,这里只保留最终检查与单张上传替换,不再重复展示已选素材列表。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||||
|
{agentUploadSlots.map((slot, index) => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={slot.label}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
className={cn(
|
||||||
|
"group relative aspect-square overflow-hidden rounded-[24px] transition-all",
|
||||||
|
attachments[index]
|
||||||
|
? "ring-2 ring-emerald-400/45"
|
||||||
|
: "border-2 border-dashed border-white/15 bg-black/25 hover:border-emerald-400/35"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={(el) => {
|
||||||
|
fileInputRefs.current[index] = el;
|
||||||
|
}}
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
className="hidden"
|
||||||
|
onChange={(e) => handleSlotFileChange(index, e)}
|
||||||
|
/>
|
||||||
|
{attachments[index] ? (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
src={buildPreviewImageUrl(attachments[index]!.url, 256)}
|
||||||
|
alt={attachments[index]!.name || slot.label}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-x-0 bottom-0 bg-black/70 px-3 py-2 text-xs font-medium text-white">
|
||||||
|
{slot.label}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-white/20 text-white transition-opacity hover:bg-white/30"
|
||||||
|
onClick={() => updateAttachmentAtSlot(index, null)}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center gap-2 text-white/55">
|
||||||
|
{slotUploading === index ? (
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin text-emerald-400" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Upload className="h-6 w-6" />
|
||||||
|
<span className="text-sm font-medium text-white/72">{slot.label}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute inset-x-0 top-0 flex items-center justify-between gap-2 p-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-white/20 text-white transition-opacity hover:bg-white/30"
|
onClick={() => fileInputRefs.current[index]?.click()}
|
||||||
onClick={() => updateAttachmentAtSlot(index, null)}
|
className="rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/80 transition-colors hover:border-white/20 hover:text-white"
|
||||||
>
|
>
|
||||||
<X className="h-3 w-3" />
|
{attachments[index] ? "更换" : "上传"}
|
||||||
</button>
|
</button>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="flex h-full flex-col items-center justify-center gap-2 text-white/55">
|
|
||||||
{slotUploading === index ? (
|
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-emerald-400" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Upload className="h-6 w-6" />
|
|
||||||
<span className="text-sm font-medium text-white/72">{slot.label}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</motion.div>
|
||||||
|
);
|
||||||
<div className="absolute inset-x-0 top-0 flex items-center justify-between gap-2 p-2">
|
})}
|
||||||
<button
|
</div>
|
||||||
type="button"
|
</CollapsibleContent>
|
||||||
onClick={() => fileInputRefs.current[index]?.click()}
|
) : (
|
||||||
className="rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/80 transition-colors hover:border-white/20 hover:text-white"
|
<div className="mt-4 flex min-h-[132px] flex-col items-center justify-center rounded-[24px] border border-dashed border-white/12 bg-black/20 px-4 text-center text-sm text-white/45">
|
||||||
>
|
<ImagePlus className="mb-2 h-5 w-5 opacity-60" />
|
||||||
{attachments[index] ? "更换" : "上传"}
|
先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
) : (
|
</div>
|
||||||
<div className="flex min-h-[132px] flex-col items-center justify-center rounded-[24px] border border-dashed border-white/12 bg-black/20 px-4 text-center text-sm text-white/45">
|
</Collapsible>
|
||||||
<ImagePlus className="mb-2 h-5 w-5 opacity-60" />
|
|
||||||
先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1734,22 +1859,42 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
>
|
>
|
||||||
<div className="mb-5 border-b border-white/10 pb-4">
|
<div className="mb-5 border-b border-white/10 pb-4">
|
||||||
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
||||||
{unifiedAnchorSteps.map((step) => (
|
{unifiedAnchorSteps.map((step) => {
|
||||||
<button
|
const isGenerateAnchor = step.id === "agent-anchor-generate";
|
||||||
key={step.id}
|
const isBlockedGenerateAnchor =
|
||||||
type="button"
|
isGenerateAnchor && hasAgentUploadSlots && !hasSatisfiedSlotRequirement;
|
||||||
onClick={() => scrollToSection(step.id)}
|
|
||||||
className="flex min-w-max items-center gap-3 rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-left transition-colors hover:border-white/20 hover:bg-white/[0.06]"
|
return (
|
||||||
>
|
<button
|
||||||
<span className="rounded-full bg-white text-[10px] font-semibold text-black px-2 py-0.5">
|
key={step.id}
|
||||||
{step.badge}
|
type="button"
|
||||||
</span>
|
onClick={() => scrollToSection(step.id)}
|
||||||
<span className="flex flex-col">
|
className={cn(
|
||||||
<span className="text-sm font-medium text-white">{step.label}</span>
|
"flex min-w-max items-center gap-3 rounded-full border px-4 py-2 text-left transition-colors",
|
||||||
<span className="text-[11px] text-white/45">{step.description}</span>
|
isBlockedGenerateAnchor
|
||||||
</span>
|
? "border-amber-400/20 bg-amber-500/[0.08] text-amber-100/90"
|
||||||
</button>
|
: "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.06]"
|
||||||
))}
|
)}
|
||||||
|
>
|
||||||
|
<span className="rounded-full bg-white px-2 py-0.5 text-[10px] font-semibold text-black">
|
||||||
|
{step.badge}
|
||||||
|
</span>
|
||||||
|
<span className="flex flex-col">
|
||||||
|
<span className="text-sm font-medium text-white">{step.label}</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-[11px]",
|
||||||
|
isBlockedGenerateAnchor ? "text-amber-100/70" : "text-white/45"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isBlockedGenerateAnchor && slotRequirementMessage
|
||||||
|
? slotRequirementMessage
|
||||||
|
: step.description}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1890,7 +2035,30 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{attachmentSlotPanel}
|
{hasAgentUploadSlots && hasMaterialStep && hasSatisfiedSlotRequirement ? (
|
||||||
|
<div className="flex flex-col gap-3 rounded-[24px] border border-white/10 bg-black/20 px-4 py-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="text-sm font-medium text-white">输入图已在上一步确认</div>
|
||||||
|
<div className="text-xs text-white/45">
|
||||||
|
{productAttachmentSummary}
|
||||||
|
{modelAttachmentSummary ? `,${modelAttachmentSummary}` : ""}
|
||||||
|
。如需微调,再手动展开编辑即可。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsAttachmentSlotExpanded(true)}
|
||||||
|
className="border-white/12 bg-black/25 text-white/82 hover:border-white/22 hover:bg-white/[0.06] hover:text-white"
|
||||||
|
>
|
||||||
|
手动调整输入图
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{(!hasMaterialStep || !hasSatisfiedSlotRequirement || isAttachmentSlotExpanded)
|
||||||
|
? attachmentSlotPanel
|
||||||
|
: null}
|
||||||
|
|
||||||
<div className="rounded-[28px] border border-white/12 bg-white/[0.02] p-4">
|
<div className="rounded-[28px] border border-white/12 bg-white/[0.02] p-4">
|
||||||
<h3 className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
<h3 className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
||||||
@ -1945,7 +2113,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={isGenerating || !selectedAgentPresetId}
|
disabled={
|
||||||
|
isGenerating ||
|
||||||
|
!selectedAgentPresetId ||
|
||||||
|
(hasAgentUploadSlots && !hasSatisfiedSlotRequirement)
|
||||||
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-11 w-full gap-2 rounded-[24px]",
|
"h-11 w-full gap-2 rounded-[24px]",
|
||||||
"bg-gradient-to-r from-emerald-500 to-teal-500",
|
"bg-gradient-to-r from-emerald-500 to-teal-500",
|
||||||
@ -1967,6 +2139,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
{slotRequirementMessage ? (
|
||||||
|
<div className="rounded-[18px] border border-amber-400/20 bg-amber-500/[0.08] px-4 py-3 text-xs leading-6 text-amber-100/90">
|
||||||
|
{slotRequirementMessage}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -42,6 +42,8 @@ interface AgentPresetPanelDarkV2Props {
|
|||||||
embeddedEntityLabel?: string;
|
embeddedEntityLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AGENT_PRESET_LIBRARY_ALL_MODES_ID = "__all_modes__";
|
||||||
|
|
||||||
export function AgentPresetPanelDarkV2({
|
export function AgentPresetPanelDarkV2({
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
onSelectPreset,
|
onSelectPreset,
|
||||||
@ -50,7 +52,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
embeddedEntityLabel = "智能体",
|
embeddedEntityLabel = "智能体",
|
||||||
}: AgentPresetPanelDarkV2Props) {
|
}: AgentPresetPanelDarkV2Props) {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const [activeTab, setActiveTab] = useState<string>(AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID);
|
const [activeTab, setActiveTab] = useState<string>(AGENT_PRESET_LIBRARY_ALL_MODES_ID);
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||||
const [editingPreset, setEditingPreset] = useState<AgentPreset | null>(null);
|
const [editingPreset, setEditingPreset] = useState<AgentPreset | null>(null);
|
||||||
@ -87,24 +89,33 @@ export function AgentPresetPanelDarkV2({
|
|||||||
[presetLibraryConfig]
|
[presetLibraryConfig]
|
||||||
);
|
);
|
||||||
const navTabs = useMemo(() => {
|
const navTabs = useMemo(() => {
|
||||||
|
const allModesTab = {
|
||||||
|
id: AGENT_PRESET_LIBRARY_ALL_MODES_ID,
|
||||||
|
label: "所有模式",
|
||||||
|
icon: Grid3X3,
|
||||||
|
isMyWorks: false,
|
||||||
|
isAllModes: true,
|
||||||
|
};
|
||||||
const dynamicTabs = presetLibrarySections.map((section) => ({
|
const dynamicTabs = presetLibrarySections.map((section) => ({
|
||||||
id: section.id,
|
id: section.id,
|
||||||
label: section.label,
|
label: section.label,
|
||||||
icon: LayoutGrid,
|
icon: LayoutGrid,
|
||||||
isMyWorks: false,
|
isMyWorks: false,
|
||||||
|
isAllModes: false,
|
||||||
}));
|
}));
|
||||||
const myWorksTab = {
|
const myWorksTab = {
|
||||||
id: AGENT_PRESET_LIBRARY_MY_WORKS_ID,
|
id: AGENT_PRESET_LIBRARY_MY_WORKS_ID,
|
||||||
label: "我的模式",
|
label: "我的模式",
|
||||||
icon: FolderOpen,
|
icon: FolderOpen,
|
||||||
isMyWorks: true,
|
isMyWorks: true,
|
||||||
|
isAllModes: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dynamicTabs.length === 0) {
|
if (dynamicTabs.length === 0) {
|
||||||
return [myWorksTab];
|
return [allModesTab, myWorksTab];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...dynamicTabs, myWorksTab];
|
return [allModesTab, ...dynamicTabs, myWorksTab];
|
||||||
}, [presetLibrarySections]);
|
}, [presetLibrarySections]);
|
||||||
const activeSection = useMemo(
|
const activeSection = useMemo(
|
||||||
() => presetLibrarySections.find((section) => section.id === activeTab) || null,
|
() => presetLibrarySections.find((section) => section.id === activeTab) || null,
|
||||||
@ -130,6 +141,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
() => getEnabledAgentPresetLibraryNodes(selectedLevel2?.children || []),
|
() => getEnabledAgentPresetLibraryNodes(selectedLevel2?.children || []),
|
||||||
[selectedLevel2]
|
[selectedLevel2]
|
||||||
);
|
);
|
||||||
|
const isAllModesTab = activeTab === AGENT_PRESET_LIBRARY_ALL_MODES_ID;
|
||||||
const isMyWorksTab = activeTab === AGENT_PRESET_LIBRARY_MY_WORKS_ID;
|
const isMyWorksTab = activeTab === AGENT_PRESET_LIBRARY_MY_WORKS_ID;
|
||||||
const canManagePublicPresets =
|
const canManagePublicPresets =
|
||||||
session?.user?.type === "admin" || session?.user?.type === "agent_admin";
|
session?.user?.type === "admin" || session?.user?.type === "agent_admin";
|
||||||
@ -168,7 +180,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
const filteredPresets = useMemo(() => {
|
const filteredPresets = useMemo(() => {
|
||||||
let presets: AgentPreset[] = isMyWorksTab ? data?.private || [] : data?.public || [];
|
let presets: AgentPreset[] = isMyWorksTab ? data?.private || [] : data?.public || [];
|
||||||
|
|
||||||
if (!isMyWorksTab) {
|
if (!isMyWorksTab && !isAllModesTab) {
|
||||||
const fallbackSectionId =
|
const fallbackSectionId =
|
||||||
presetLibrarySections.find((section) => section.id === AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID)?.id ||
|
presetLibrarySections.find((section) => section.id === AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID)?.id ||
|
||||||
presetLibrarySections[0]?.id ||
|
presetLibrarySections[0]?.id ||
|
||||||
@ -210,6 +222,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
}, [
|
}, [
|
||||||
activeTab,
|
activeTab,
|
||||||
data,
|
data,
|
||||||
|
isAllModesTab,
|
||||||
isMyWorksTab,
|
isMyWorksTab,
|
||||||
keyword,
|
keyword,
|
||||||
presetLibraryConfig,
|
presetLibraryConfig,
|
||||||
@ -248,6 +261,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
|
|
||||||
// 获取当前标签标题
|
// 获取当前标签标题
|
||||||
const getCurrentTitle = () => {
|
const getCurrentTitle = () => {
|
||||||
|
if (isAllModesTab) return "所有模式";
|
||||||
if (isMyWorksTab) return "我的模式";
|
if (isMyWorksTab) return "我的模式";
|
||||||
return activeSection?.label || "生图模式";
|
return activeSection?.label || "生图模式";
|
||||||
};
|
};
|
||||||
@ -303,7 +317,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isMyWorksTab ? (
|
{!isMyWorksTab && !isAllModesTab ? (
|
||||||
<div className="mb-4 space-y-2 rounded-2xl border border-white/8 bg-white/[0.02] p-3">
|
<div className="mb-4 space-y-2 rounded-2xl border border-white/8 bg-white/[0.02] p-3">
|
||||||
<CategoryFilterRow
|
<CategoryFilterRow
|
||||||
label="一级"
|
label="一级"
|
||||||
@ -515,7 +529,7 @@ export function AgentPresetPanelDarkV2({
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{!isMyWorksTab ? (
|
{!isMyWorksTab && !isAllModesTab ? (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="mb-6 space-y-2 rounded-2xl border border-white/8 bg-white/[0.02] p-4"
|
className="mb-6 space-y-2 rounded-2xl border border-white/8 bg-white/[0.02] p-4"
|
||||||
initial={{ opacity: 0, y: 16 }}
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user