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 {
|
||||
ArrowUp,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Download,
|
||||
ImagePlus,
|
||||
Loader2,
|
||||
@ -20,6 +22,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Select,
|
||||
@ -411,6 +418,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
: null
|
||||
)
|
||||
);
|
||||
const [isAttachmentSlotExpanded, setIsAttachmentSlotExpanded] = useState(false);
|
||||
const [slotUploading, setSlotUploading] = useState<number | null>(null);
|
||||
const [selectedModelId, setSelectedModelId] = useState<string>(
|
||||
normalizedIncomingWorkspaceState.selectedModelId || "auto"
|
||||
@ -533,6 +541,16 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
);
|
||||
const selectedModelAttachment =
|
||||
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 emitAgentModeState = onAgentModeStateChange || setLocalAgentModeState;
|
||||
const detailReplacementState = resolvedAgentModeState.detailReplacement;
|
||||
@ -869,6 +887,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
(image) => image.source === "primary" || image.source === "secondary"
|
||||
);
|
||||
const hasSatisfiedSlotRequirement = !hasAgentUploadSlots || attachmentCount >= requiredUploadCount;
|
||||
const slotRequirementMessage =
|
||||
hasAgentUploadSlots && !hasSatisfiedSlotRequirement
|
||||
? `当前还缺少 ${missingAttachmentCount} 张输入图,请先在“选择 SPU / SKU”步骤补齐后再继续。`
|
||||
: null;
|
||||
const mergedSettingsStepLabel = hasMaterialStep ? "选图与生成" : "生成参数";
|
||||
const mergedSettingsStepDescription = hasMaterialStep
|
||||
? "搜索素材、补充要求、确认槽位并开始生成"
|
||||
@ -1077,6 +1099,26 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
}
|
||||
}, [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(() => {
|
||||
if (!selectedAgentPresetId) {
|
||||
setActiveToolStep("preset");
|
||||
@ -1392,6 +1434,19 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
[toolSteps]
|
||||
);
|
||||
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") {
|
||||
return;
|
||||
}
|
||||
@ -1400,7 +1455,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
return;
|
||||
}
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}, []);
|
||||
}, [hasAgentUploadSlots, hasMaterialStep, hasSatisfiedSlotRequirement, slotRequirementMessage]);
|
||||
const updateDetailReplacementState = useCallback(
|
||||
(
|
||||
updater:
|
||||
@ -1610,90 +1665,160 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
}, [updateSkuReplacementState]);
|
||||
|
||||
const attachmentSlotPanel = (
|
||||
<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">
|
||||
<ImagePlus className="h-4 w-4 text-emerald-300" />
|
||||
输入图槽位
|
||||
</h3>
|
||||
<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>
|
||||
{agentUploadSlots.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<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}
|
||||
<Collapsible
|
||||
open={isAttachmentSlotExpanded}
|
||||
onOpenChange={setIsAttachmentSlotExpanded}
|
||||
>
|
||||
<div className="rounded-[28px] border border-white/12 bg-white/[0.02] p-4">
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<ImagePlus className="h-4 w-4 text-emerald-300" />
|
||||
输入图槽位
|
||||
</div>
|
||||
|
||||
{agentUploadSlots.length > 0 ? (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full border px-3 py-1 text-xs",
|
||||
attachmentSummaryReady
|
||||
? "border-emerald-400/25 bg-emerald-500/[0.10] text-emerald-100"
|
||||
: "border-amber-400/25 bg-amber-500/[0.10] text-amber-100"
|
||||
)}
|
||||
>
|
||||
{productAttachmentSummary}
|
||||
</span>
|
||||
{modelAttachmentSummary ? (
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full border px-3 py-1 text-xs",
|
||||
selectedModelAttachment
|
||||
? "border-emerald-400/25 bg-emerald-500/[0.10] text-emerald-100"
|
||||
: "border-amber-400/25 bg-amber-500/[0.10] text-amber-100"
|
||||
)}
|
||||
>
|
||||
{modelAttachmentSummary}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs text-white/60">
|
||||
共 {agentUploadSlots.length} 个输入槽位
|
||||
</span>
|
||||
</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 className="absolute inset-x-0 top-0 flex items-center justify-between gap-2 p-2">
|
||||
<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)}
|
||||
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"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
{attachments[index] ? "更换" : "上传"}
|
||||
</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 className="absolute inset-x-0 top-0 flex items-center justify-between gap-2 p-2">
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
{attachments[index] ? "更换" : "上传"}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
) : (
|
||||
<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" />
|
||||
先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。
|
||||
</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">
|
||||
<ImagePlus className="mb-2 h-5 w-5 opacity-60" />
|
||||
先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
|
||||
return (
|
||||
@ -1734,22 +1859,42 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
>
|
||||
<div className="mb-5 border-b border-white/10 pb-4">
|
||||
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
||||
{unifiedAnchorSteps.map((step) => (
|
||||
<button
|
||||
key={step.id}
|
||||
type="button"
|
||||
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]"
|
||||
>
|
||||
<span className="rounded-full bg-white text-[10px] font-semibold text-black px-2 py-0.5">
|
||||
{step.badge}
|
||||
</span>
|
||||
<span className="flex flex-col">
|
||||
<span className="text-sm font-medium text-white">{step.label}</span>
|
||||
<span className="text-[11px] text-white/45">{step.description}</span>
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
{unifiedAnchorSteps.map((step) => {
|
||||
const isGenerateAnchor = step.id === "agent-anchor-generate";
|
||||
const isBlockedGenerateAnchor =
|
||||
isGenerateAnchor && hasAgentUploadSlots && !hasSatisfiedSlotRequirement;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={step.id}
|
||||
type="button"
|
||||
onClick={() => scrollToSection(step.id)}
|
||||
className={cn(
|
||||
"flex min-w-max items-center gap-3 rounded-full border px-4 py-2 text-left transition-colors",
|
||||
isBlockedGenerateAnchor
|
||||
? "border-amber-400/20 bg-amber-500/[0.08] text-amber-100/90"
|
||||
: "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>
|
||||
|
||||
@ -1890,7 +2035,30 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
/>
|
||||
</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">
|
||||
<h3 className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
||||
@ -1945,7 +2113,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
disabled={isGenerating || !selectedAgentPresetId}
|
||||
disabled={
|
||||
isGenerating ||
|
||||
!selectedAgentPresetId ||
|
||||
(hasAgentUploadSlots && !hasSatisfiedSlotRequirement)
|
||||
}
|
||||
className={cn(
|
||||
"h-11 w-full gap-2 rounded-[24px]",
|
||||
"bg-gradient-to-r from-emerald-500 to-teal-500",
|
||||
@ -1967,6 +2139,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
|
||||
@ -42,6 +42,8 @@ interface AgentPresetPanelDarkV2Props {
|
||||
embeddedEntityLabel?: string;
|
||||
}
|
||||
|
||||
const AGENT_PRESET_LIBRARY_ALL_MODES_ID = "__all_modes__";
|
||||
|
||||
export function AgentPresetPanelDarkV2({
|
||||
selectedPresetId,
|
||||
onSelectPreset,
|
||||
@ -50,7 +52,7 @@ export function AgentPresetPanelDarkV2({
|
||||
embeddedEntityLabel = "智能体",
|
||||
}: AgentPresetPanelDarkV2Props) {
|
||||
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 [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||
const [editingPreset, setEditingPreset] = useState<AgentPreset | null>(null);
|
||||
@ -87,24 +89,33 @@ export function AgentPresetPanelDarkV2({
|
||||
[presetLibraryConfig]
|
||||
);
|
||||
const navTabs = useMemo(() => {
|
||||
const allModesTab = {
|
||||
id: AGENT_PRESET_LIBRARY_ALL_MODES_ID,
|
||||
label: "所有模式",
|
||||
icon: Grid3X3,
|
||||
isMyWorks: false,
|
||||
isAllModes: true,
|
||||
};
|
||||
const dynamicTabs = presetLibrarySections.map((section) => ({
|
||||
id: section.id,
|
||||
label: section.label,
|
||||
icon: LayoutGrid,
|
||||
isMyWorks: false,
|
||||
isAllModes: false,
|
||||
}));
|
||||
const myWorksTab = {
|
||||
id: AGENT_PRESET_LIBRARY_MY_WORKS_ID,
|
||||
label: "我的模式",
|
||||
icon: FolderOpen,
|
||||
isMyWorks: true,
|
||||
isAllModes: false,
|
||||
};
|
||||
|
||||
if (dynamicTabs.length === 0) {
|
||||
return [myWorksTab];
|
||||
return [allModesTab, myWorksTab];
|
||||
}
|
||||
|
||||
return [...dynamicTabs, myWorksTab];
|
||||
return [allModesTab, ...dynamicTabs, myWorksTab];
|
||||
}, [presetLibrarySections]);
|
||||
const activeSection = useMemo(
|
||||
() => presetLibrarySections.find((section) => section.id === activeTab) || null,
|
||||
@ -130,6 +141,7 @@ export function AgentPresetPanelDarkV2({
|
||||
() => getEnabledAgentPresetLibraryNodes(selectedLevel2?.children || []),
|
||||
[selectedLevel2]
|
||||
);
|
||||
const isAllModesTab = activeTab === AGENT_PRESET_LIBRARY_ALL_MODES_ID;
|
||||
const isMyWorksTab = activeTab === AGENT_PRESET_LIBRARY_MY_WORKS_ID;
|
||||
const canManagePublicPresets =
|
||||
session?.user?.type === "admin" || session?.user?.type === "agent_admin";
|
||||
@ -168,7 +180,7 @@ export function AgentPresetPanelDarkV2({
|
||||
const filteredPresets = useMemo(() => {
|
||||
let presets: AgentPreset[] = isMyWorksTab ? data?.private || [] : data?.public || [];
|
||||
|
||||
if (!isMyWorksTab) {
|
||||
if (!isMyWorksTab && !isAllModesTab) {
|
||||
const fallbackSectionId =
|
||||
presetLibrarySections.find((section) => section.id === AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID)?.id ||
|
||||
presetLibrarySections[0]?.id ||
|
||||
@ -210,6 +222,7 @@ export function AgentPresetPanelDarkV2({
|
||||
}, [
|
||||
activeTab,
|
||||
data,
|
||||
isAllModesTab,
|
||||
isMyWorksTab,
|
||||
keyword,
|
||||
presetLibraryConfig,
|
||||
@ -248,6 +261,7 @@ export function AgentPresetPanelDarkV2({
|
||||
|
||||
// 获取当前标签标题
|
||||
const getCurrentTitle = () => {
|
||||
if (isAllModesTab) return "所有模式";
|
||||
if (isMyWorksTab) return "我的模式";
|
||||
return activeSection?.label || "生图模式";
|
||||
};
|
||||
@ -303,7 +317,7 @@ export function AgentPresetPanelDarkV2({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!isMyWorksTab ? (
|
||||
{!isMyWorksTab && !isAllModesTab ? (
|
||||
<div className="mb-4 space-y-2 rounded-2xl border border-white/8 bg-white/[0.02] p-3">
|
||||
<CategoryFilterRow
|
||||
label="一级"
|
||||
@ -515,7 +529,7 @@ export function AgentPresetPanelDarkV2({
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{!isMyWorksTab ? (
|
||||
{!isMyWorksTab && !isAllModesTab ? (
|
||||
<motion.div
|
||||
className="mb-6 space-y-2 rounded-2xl border border-white/8 bg-white/[0.02] p-4"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user