fix agent workflow prompt state sync
This commit is contained in:
parent
243e12cc0e
commit
c16862bd90
@ -74,6 +74,7 @@ import {
|
||||
AgentWorkflowBrandStorePanel,
|
||||
AgentWorkflowPanelDarkV2,
|
||||
} from "@/components/agents/AgentWorkflowPanelDarkV2";
|
||||
import { AgentCanvasTopNavDark } from "@/components/agents/AgentCanvasTopNavDark";
|
||||
import { AgentGenerationWorkspaceDarkV2 } from "@/components/agents/AgentGenerationWorkspaceDarkV2";
|
||||
import { AgentProjectListSheet } from "@/components/agents/AgentProjectListSheet";
|
||||
import { CanvasWorkspace } from "@/components/canvas";
|
||||
@ -86,7 +87,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { cn, fetcher } from "@/lib/utils";
|
||||
import {
|
||||
defaultAgentWorkflowUiConfig,
|
||||
flattenVisibleAgentWorkflowSteps,
|
||||
normalizeAgentWorkflowUiConfig,
|
||||
sanitizeAgentWorkflowSelections,
|
||||
} from "@/lib/agents/workflow-ui-config";
|
||||
import {
|
||||
createEmptyAgentWorkflowMaterialSelections,
|
||||
@ -411,6 +414,18 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
isAgentWorkspaceEnabled && requestedAgentMode;
|
||||
const hasLoadedCurrentAgentProject =
|
||||
!requestedAgentMode || !agentProjectId || loadedAgentProjectId === agentProjectId;
|
||||
const resolvedWorkflowUiConfig = useMemo(
|
||||
() => normalizeAgentWorkflowUiConfig(siteSettingsData?.agentWorkflowUiConfig),
|
||||
[siteSettingsData?.agentWorkflowUiConfig]
|
||||
);
|
||||
const sanitizedWorkflowSelections = useMemo(
|
||||
() =>
|
||||
sanitizeAgentWorkflowSelections({
|
||||
config: resolvedWorkflowUiConfig || defaultAgentWorkflowUiConfig,
|
||||
selections: workflowSelections,
|
||||
}),
|
||||
[resolvedWorkflowUiConfig, workflowSelections]
|
||||
);
|
||||
const resolvedBrandModelConfig = useMemo(
|
||||
() => normalizeAgentWorkflowBrandModelConfig(siteSettingsData?.agentWorkflowBrandModelConfig),
|
||||
[siteSettingsData?.agentWorkflowBrandModelConfig]
|
||||
@ -474,7 +489,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
priceBand: workflowInputs.priceBand.trim(),
|
||||
},
|
||||
materials: workflowMaterials,
|
||||
selections: workflowSelections,
|
||||
selections: sanitizedWorkflowSelections,
|
||||
selectedBrandModel,
|
||||
selectedAgentPresetId: selectedAgentPresetId || null,
|
||||
currentStage: currentAgentStage,
|
||||
@ -487,7 +502,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
selectedBrandModel,
|
||||
workflowInputs,
|
||||
workflowMaterials,
|
||||
workflowSelections,
|
||||
sanitizedWorkflowSelections,
|
||||
]);
|
||||
const currentWorkflowPersistedStateRef = useRef<AgentWorkflowPersistedState>(
|
||||
buildCurrentWorkflowPersistedState()
|
||||
@ -658,7 +673,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
priceBand: workflowInputs.priceBand.trim(),
|
||||
},
|
||||
materials: workflowMaterials,
|
||||
selections: workflowSelections,
|
||||
selections: sanitizedWorkflowSelections,
|
||||
selectedBrandModel,
|
||||
selectedAgentPresetId: selectedAgentPresetId || null,
|
||||
currentStage: currentAgentStage,
|
||||
@ -715,7 +730,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
selectedBrandModel,
|
||||
workflowInputs,
|
||||
workflowMaterials,
|
||||
workflowSelections,
|
||||
sanitizedWorkflowSelections,
|
||||
]);
|
||||
const persistedAgentProjectStateRef = useRef(buildPersistedAgentProjectState());
|
||||
|
||||
@ -781,7 +796,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
JSON.stringify({
|
||||
inputs: workflowInputs,
|
||||
materials: workflowMaterials,
|
||||
selections: workflowSelections,
|
||||
selections: sanitizedWorkflowSelections,
|
||||
selectedBrandModel,
|
||||
selectedAgentPresetId,
|
||||
currentAgentStage,
|
||||
@ -796,7 +811,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
workflowHydrationSource,
|
||||
workflowInputs,
|
||||
workflowMaterials,
|
||||
workflowSelections,
|
||||
sanitizedWorkflowSelections,
|
||||
workflowStorageKey,
|
||||
selectedBrandModel,
|
||||
selectedAgentPresetId,
|
||||
@ -841,48 +856,6 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!requestedAgentMode || !isAgentWorkspaceEnabled || agentProjectId) return;
|
||||
|
||||
let cancelled = false;
|
||||
const createAgentProject = async () => {
|
||||
setIsAgentProjectLoading(true);
|
||||
try {
|
||||
const result = await agentProjectService.createProject({
|
||||
title: buildDefaultAgentProjectTitle(),
|
||||
projectState: createEmptyAgentGenerationProjectPersistedState(),
|
||||
});
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error(result.error || "创建生图项目失败");
|
||||
}
|
||||
if (cancelled) return;
|
||||
toast.success("已为你创建新项目,可直接开始生图");
|
||||
updateCanvasSearchParams({
|
||||
mode: "agent",
|
||||
agentProjectId: result.data.id,
|
||||
});
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
toast.error(error instanceof Error ? error.message : "创建生图项目失败");
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsAgentProjectLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void createAgentProject();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
agentProjectId,
|
||||
isAgentWorkspaceEnabled,
|
||||
requestedAgentMode,
|
||||
updateCanvasSearchParams,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!requestedAgentMode || !agentProjectId) return;
|
||||
|
||||
@ -1132,16 +1105,11 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
agentProjectId: nextProject.id,
|
||||
});
|
||||
} else {
|
||||
const createResult = await agentProjectService.createProject({
|
||||
title: buildDefaultAgentProjectTitle(),
|
||||
projectState: createEmptyAgentGenerationProjectPersistedState(),
|
||||
updateCanvasSearchParams({
|
||||
mode: "agent",
|
||||
agentProjectId: null,
|
||||
});
|
||||
if (createResult.success && createResult.data) {
|
||||
updateCanvasSearchParams({
|
||||
mode: "agent",
|
||||
agentProjectId: createResult.data.id,
|
||||
});
|
||||
}
|
||||
setIsAgentProjectSheetOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1705,10 +1673,16 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const resolvedWorkflowUiConfig = useMemo(
|
||||
() => normalizeAgentWorkflowUiConfig(siteSettingsData?.agentWorkflowUiConfig),
|
||||
[siteSettingsData?.agentWorkflowUiConfig]
|
||||
);
|
||||
useEffect(() => {
|
||||
const currentSerialized = JSON.stringify(workflowSelections);
|
||||
const sanitizedSerialized = JSON.stringify(sanitizedWorkflowSelections);
|
||||
if (currentSerialized === sanitizedSerialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
workflowSelectionsRef.current = sanitizedWorkflowSelections;
|
||||
setWorkflowSelections(sanitizedWorkflowSelections);
|
||||
}, [sanitizedWorkflowSelections, workflowSelections]);
|
||||
|
||||
const workflowContext = useMemo<AgentWorkflowComposerContext | null>(() => {
|
||||
return buildAgentWorkflowComposerContext({
|
||||
@ -1716,14 +1690,14 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
inputs: workflowInputs,
|
||||
materials: workflowMaterials,
|
||||
selectedBrandModel,
|
||||
selections: workflowSelections,
|
||||
selections: sanitizedWorkflowSelections,
|
||||
});
|
||||
}, [
|
||||
resolvedWorkflowUiConfig,
|
||||
selectedBrandModel,
|
||||
sanitizedWorkflowSelections,
|
||||
workflowInputs,
|
||||
workflowMaterials,
|
||||
workflowSelections,
|
||||
]);
|
||||
useEffect(() => {
|
||||
workflowInputsRef.current = workflowInputs;
|
||||
@ -1732,8 +1706,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
workflowMaterialsRef.current = workflowMaterials;
|
||||
}, [workflowMaterials]);
|
||||
useEffect(() => {
|
||||
workflowSelectionsRef.current = workflowSelections;
|
||||
}, [workflowSelections]);
|
||||
workflowSelectionsRef.current = sanitizedWorkflowSelections;
|
||||
}, [sanitizedWorkflowSelections]);
|
||||
useEffect(() => {
|
||||
selectedBrandModelRef.current = selectedBrandModel;
|
||||
}, [selectedBrandModel]);
|
||||
@ -1747,10 +1721,39 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
}, []);
|
||||
const handleWorkflowSelectionsChange = useCallback(
|
||||
(next: Partial<Record<AgentWorkflowStepKey, string>>) => {
|
||||
workflowSelectionsRef.current = next;
|
||||
setWorkflowSelections(next);
|
||||
const normalizedCurrentSelections = sanitizeAgentWorkflowSelections({
|
||||
config: resolvedWorkflowUiConfig || defaultAgentWorkflowUiConfig,
|
||||
selections: workflowSelectionsRef.current,
|
||||
});
|
||||
const normalizedNextSelections = sanitizeAgentWorkflowSelections({
|
||||
config: resolvedWorkflowUiConfig || defaultAgentWorkflowUiConfig,
|
||||
selections: next,
|
||||
});
|
||||
|
||||
const orderedStepKeys = flattenVisibleAgentWorkflowSteps(
|
||||
(resolvedWorkflowUiConfig || defaultAgentWorkflowUiConfig).steps,
|
||||
normalizedCurrentSelections
|
||||
).map(({ step }) => step.key);
|
||||
const earliestChangedIndex = orderedStepKeys.findIndex((stepKey) => {
|
||||
return String(normalizedCurrentSelections[stepKey] || "") !== String(normalizedNextSelections[stepKey] || "");
|
||||
});
|
||||
|
||||
let clearedSelections = { ...normalizedNextSelections };
|
||||
if (earliestChangedIndex >= 0) {
|
||||
orderedStepKeys.slice(earliestChangedIndex + 1).forEach((stepKey) => {
|
||||
delete clearedSelections[stepKey];
|
||||
});
|
||||
}
|
||||
|
||||
clearedSelections = sanitizeAgentWorkflowSelections({
|
||||
config: resolvedWorkflowUiConfig || defaultAgentWorkflowUiConfig,
|
||||
selections: clearedSelections,
|
||||
});
|
||||
|
||||
workflowSelectionsRef.current = clearedSelections;
|
||||
setWorkflowSelections(clearedSelections);
|
||||
},
|
||||
[]
|
||||
[resolvedWorkflowUiConfig]
|
||||
);
|
||||
const handleSelectedBrandModelChange = useCallback(
|
||||
(next: AgentWorkflowSelectedBrandModel | null) => {
|
||||
@ -1802,12 +1805,14 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
() =>
|
||||
getAgentWorkflowProgress({
|
||||
config: resolvedWorkflowUiConfig || defaultAgentWorkflowUiConfig,
|
||||
selections: workflowSelections,
|
||||
selections: sanitizedWorkflowSelections,
|
||||
}),
|
||||
[resolvedWorkflowUiConfig, workflowSelections]
|
||||
[resolvedWorkflowUiConfig, sanitizedWorkflowSelections]
|
||||
);
|
||||
const resolvedAgentProjectTitle =
|
||||
String(agentProjectTitle || "").trim() || "未命名生图项目";
|
||||
hasActiveAgentProject
|
||||
? String(agentProjectTitle || "").trim() || "未命名生图项目"
|
||||
: "未选择项目";
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@ -1949,11 +1954,74 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const agentProjectSelectionScreen = isAgentMode && !hasActiveAgentProject ? (
|
||||
<div className="min-h-screen bg-[#050607] text-white">
|
||||
<AgentCanvasTopNavDark
|
||||
siteName={siteSettingsData?.siteName}
|
||||
siteLogo={siteSettingsData?.siteLogo}
|
||||
siteLogoDark={siteSettingsData?.siteLogoDark}
|
||||
rightSlot={agentProjectNavSlot}
|
||||
activeKey="agent"
|
||||
/>
|
||||
<main className="flex min-h-screen items-center justify-center px-6 pt-24 pb-8">
|
||||
<div className="w-full max-w-[860px] rounded-[36px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.02)_100%)] p-8 shadow-[0_24px_80px_rgba(0,0,0,0.35)] md:p-10">
|
||||
<div className="mx-auto max-w-[620px] text-center">
|
||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-3xl border border-emerald-400/20 bg-emerald-500/[0.10] text-emerald-200">
|
||||
<FolderOpen className="h-8 w-8" />
|
||||
</div>
|
||||
<h1 className="mt-6 text-3xl font-semibold tracking-[0.02em] text-white md:text-4xl">
|
||||
先选择一个生图项目
|
||||
</h1>
|
||||
<p className="mt-4 text-sm leading-7 text-white/55 md:text-base">
|
||||
打开历史项目可以继续沿用之前的生图结果、模式和工作流。只有在你明确点击新建项目时,系统才会创建一个全新的智能体项目。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid gap-4 md:grid-cols-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsAgentProjectSheetOpen(true)}
|
||||
className="rounded-[28px] border border-white/10 bg-black/30 p-5 text-left transition-colors hover:border-white/18 hover:bg-white/[0.04]"
|
||||
>
|
||||
<div className="flex items-center gap-3 text-emerald-200">
|
||||
<Grid3X3 className="h-5 w-5" />
|
||||
<span className="text-sm font-medium">打开历史项目</span>
|
||||
</div>
|
||||
<div className="mt-3 text-sm leading-6 text-white/55">
|
||||
从项目列表里继续昨天或今天的智能体项目,避免生成结果分散在多个空项目中。
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void handleCreateAgentProject();
|
||||
}}
|
||||
className="rounded-[28px] border border-emerald-400/20 bg-emerald-500/[0.10] p-5 text-left transition-colors hover:border-emerald-300/35 hover:bg-emerald-500/[0.16]"
|
||||
>
|
||||
<div className="flex items-center gap-3 text-emerald-100">
|
||||
<Plus className="h-5 w-5" />
|
||||
<span className="text-sm font-medium">新建空白项目</span>
|
||||
</div>
|
||||
<div className="mt-3 text-sm leading-6 text-emerald-50/70">
|
||||
仅在你要开始一条全新的场景生图流程时再创建,避免项目列表里自动冒出一堆无效项目。
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
if (isAgentMode) {
|
||||
if (!hasActiveAgentProject) {
|
||||
return agentProjectSelectionScreen;
|
||||
}
|
||||
|
||||
return (
|
||||
<AgentGenerationWorkspaceDarkV2
|
||||
chatId={agentModeChatId}
|
||||
projectId={agentProjectId || id}
|
||||
projectId={agentProjectId}
|
||||
selectedAgentPresetId={isAgentWorkspaceEnabled ? selectedAgentPresetId : null}
|
||||
selectedBrandModel={selectedBrandModel}
|
||||
workflowContext={workflowContext}
|
||||
@ -1994,7 +2062,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
onInputsChange={handleWorkflowInputsChange}
|
||||
materials={workflowMaterials}
|
||||
onMaterialsChange={handleWorkflowMaterialsChange}
|
||||
selections={workflowSelections}
|
||||
selections={sanitizedWorkflowSelections}
|
||||
onSelectionsChange={handleWorkflowSelectionsChange}
|
||||
showBrandStoreSection={false}
|
||||
/>
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
Download,
|
||||
ImagePlus,
|
||||
Loader2,
|
||||
Maximize2,
|
||||
Sparkles,
|
||||
Upload,
|
||||
UserRound,
|
||||
@ -144,11 +145,6 @@ interface GeneratedImageGroup {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface SceneResultToolbarTarget {
|
||||
url: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface AgentModeReferenceSearchResponse {
|
||||
success?: boolean;
|
||||
data?: {
|
||||
@ -730,6 +726,7 @@ interface LocalGenerationPendingState {
|
||||
|
||||
const AGENT_PENDING_GENERATION_STORAGE_KEY = "ai-chatbot-agent-pending-generations";
|
||||
const AGENT_PENDING_GENERATION_EXPIRY_MS = 30 * 60 * 1000;
|
||||
const AGENT_SCENE_PENDING_FAILURE_GRACE_MS = 3 * 60 * 1000;
|
||||
|
||||
function getStoredAgentPendingGenerations(): LocalGenerationPendingState[] {
|
||||
if (typeof window === "undefined") {
|
||||
@ -942,8 +939,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
const [activeResultModeType, setActiveResultModeType] =
|
||||
useState<AgentGenerationResultModeType>("scene_generation");
|
||||
const [fullscreenPreview, setFullscreenPreview] = useState<string | null>(null);
|
||||
const [sceneResultToolbarTarget, setSceneResultToolbarTarget] =
|
||||
useState<SceneResultToolbarTarget | null>(null);
|
||||
const [sceneResizeEditorTargetUrl, setSceneResizeEditorTargetUrl] = useState<string | null>(null);
|
||||
const [batchDownloadState, setBatchDownloadState] = useState<null | "scene" | "detail" | "sku">(null);
|
||||
const [detailReplacementSearchState, setDetailReplacementSearchState] = useState<{
|
||||
@ -2194,6 +2189,14 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
? detailGenerationRunResults.length > 0
|
||||
: latestReplacementResults.length > 0;
|
||||
|
||||
if (
|
||||
pendingGeneration.modeType === "scene_generation" &&
|
||||
!hasProgress &&
|
||||
Date.now() - pendingGeneration.timestamp < AGENT_SCENE_PENDING_FAILURE_GRACE_MS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasProgress) {
|
||||
setPendingGeneration(null);
|
||||
}
|
||||
@ -2226,17 +2229,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
}
|
||||
}, [activeResultModeUrls, selectedResultUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
sceneResultToolbarTarget?.url &&
|
||||
!normalizeResultCollectionUrls(mergedResultCollections.scene_generation).includes(
|
||||
sceneResultToolbarTarget.url
|
||||
)
|
||||
) {
|
||||
setSceneResultToolbarTarget(null);
|
||||
}
|
||||
}, [mergedResultCollections.scene_generation, sceneResultToolbarTarget]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAgentPresetId || !hasAgentUploadSlots) {
|
||||
setIsAttachmentSlotExpanded(false);
|
||||
@ -2642,7 +2634,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
setActiveResultModeType("scene_generation");
|
||||
setSelectedResultUrl(finalUrl);
|
||||
setSceneResizeEditorTargetUrl(null);
|
||||
setSceneResultToolbarTarget(null);
|
||||
toast.success("尺寸调整结果已加入场景结果区");
|
||||
},
|
||||
[mergedResultCollections.scene_generation, onResultPreviewUrlsChange, uploadBase64ResultToStorage]
|
||||
@ -2660,9 +2651,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
if (fullscreenPreview === normalizedUrl) {
|
||||
setFullscreenPreview(null);
|
||||
}
|
||||
if (sceneResultToolbarTarget?.url === normalizedUrl) {
|
||||
setSceneResultToolbarTarget(null);
|
||||
}
|
||||
if (sceneResizeEditorTargetUrl === normalizedUrl) {
|
||||
setSceneResizeEditorTargetUrl(null);
|
||||
}
|
||||
@ -2670,13 +2658,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
onDeleteResultImage(modeType, normalizedUrl);
|
||||
toast.success("结果图已删除");
|
||||
},
|
||||
[
|
||||
fullscreenPreview,
|
||||
onDeleteResultImage,
|
||||
sceneResizeEditorTargetUrl,
|
||||
sceneResultToolbarTarget,
|
||||
selectedResultUrl,
|
||||
]
|
||||
[fullscreenPreview, onDeleteResultImage, sceneResizeEditorTargetUrl, selectedResultUrl]
|
||||
);
|
||||
|
||||
const handleToolStepChange = useCallback(
|
||||
@ -5481,12 +5463,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
onClick={() => {
|
||||
if (!card.url) return;
|
||||
setSelectedResultUrl(card.url);
|
||||
if (isSceneResultCard) {
|
||||
setSceneResultToolbarTarget({
|
||||
url: card.url,
|
||||
index: card.index,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onDoubleClick={() => {
|
||||
if (!card.url) return;
|
||||
@ -5498,12 +5474,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
setSelectedResultUrl(card.url);
|
||||
if (isSceneResultCard) {
|
||||
setSceneResultToolbarTarget({
|
||||
url: card.url,
|
||||
index: card.index,
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
@ -5524,9 +5494,23 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
loading="lazy"
|
||||
/>
|
||||
{isSceneResultCard ? (
|
||||
<div className="pointer-events-none absolute inset-x-3 bottom-3 rounded-2xl border border-white/10 bg-black/55 px-3 py-2 text-[11px] text-white/78 backdrop-blur">
|
||||
单击打开工具栏,双击查看大图
|
||||
</div>
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setSelectedResultUrl(card.url!);
|
||||
setSceneResizeEditorTargetUrl(card.url!);
|
||||
}}
|
||||
className="absolute left-3 top-3 flex h-9 w-9 items-center justify-center rounded-full border border-emerald-400/28 bg-black/60 text-emerald-100 transition-colors hover:border-emerald-300/45 hover:bg-emerald-500/[0.16]"
|
||||
aria-label="调整尺寸"
|
||||
>
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
</button>
|
||||
<div className="pointer-events-none absolute inset-x-3 bottom-3 rounded-2xl border border-white/10 bg-black/55 px-3 py-2 text-[11px] text-white/78 backdrop-blur">
|
||||
双击查看大图
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
@ -5622,97 +5606,6 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<AnimatePresence>
|
||||
{sceneResultToolbarTarget && activeResultModeType === "scene_generation" ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[9998] flex items-center justify-center bg-black/75 px-4 backdrop-blur-sm"
|
||||
onClick={() => setSceneResultToolbarTarget(null)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.96, y: 16 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.96, y: 16 }}
|
||||
transition={{ duration: 0.18 }}
|
||||
className="w-full max-w-[760px] rounded-[30px] border border-white/12 bg-[#090c0d]/96 p-5 shadow-[0_30px_80px_rgba(0,0,0,0.45)]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-xs uppercase tracking-[0.28em] text-emerald-200/60">
|
||||
场景图工具栏
|
||||
</div>
|
||||
<div className="mt-2 text-lg font-semibold text-white">
|
||||
场景结果图 {sceneResultToolbarTarget.index + 1}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-white/42">
|
||||
这里可以快速预览、下载,或直接进入尺寸调整弹窗。
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSceneResultToolbarTarget(null)}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full border border-white/10 bg-white/[0.03] text-white/72 transition-colors hover:border-white/20 hover:bg-white/[0.06]"
|
||||
aria-label="关闭工具栏"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 overflow-hidden rounded-[26px] border border-white/10 bg-[linear-gradient(180deg,#050607_0%,#101417_100%)]">
|
||||
<img
|
||||
src={buildPreviewImageUrl(sceneResultToolbarTarget.url, 1280)}
|
||||
alt={`场景结果图 ${sceneResultToolbarTarget.index + 1}`}
|
||||
className="max-h-[58vh] w-full object-contain"
|
||||
onError={(event) =>
|
||||
handlePreviewImageLoadError(event, sceneResultToolbarTarget.url)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-3 sm:grid-cols-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedResultUrl(sceneResultToolbarTarget.url);
|
||||
setSceneResultToolbarTarget(null);
|
||||
setFullscreenPreview(sceneResultToolbarTarget.url);
|
||||
}}
|
||||
className="flex min-h-[56px] items-center justify-center gap-2 rounded-[20px] border border-white/10 bg-white/[0.04] px-4 text-sm font-medium text-white transition-colors hover:border-white/20 hover:bg-white/[0.07]"
|
||||
>
|
||||
<ImagePlus className="h-4 w-4" />
|
||||
查看大图
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void handleDownloadOriginalImage(sceneResultToolbarTarget.url);
|
||||
}}
|
||||
className="flex min-h-[56px] items-center justify-center gap-2 rounded-[20px] border border-white/10 bg-white/[0.04] px-4 text-sm font-medium text-white transition-colors hover:border-white/20 hover:bg-white/[0.07]"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
下载原图
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedResultUrl(sceneResultToolbarTarget.url);
|
||||
setSceneResizeEditorTargetUrl(sceneResultToolbarTarget.url);
|
||||
setSceneResultToolbarTarget(null);
|
||||
}}
|
||||
className="flex min-h-[56px] items-center justify-center gap-2 rounded-[20px] border border-emerald-400/28 bg-emerald-500/[0.10] px-4 text-sm font-medium text-emerald-50 transition-colors hover:border-emerald-300/45 hover:bg-emerald-500/[0.16]"
|
||||
>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
尺寸调整
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
|
||||
{sceneResizeEditorTargetUrl ? (
|
||||
<ImageAnnotator
|
||||
key={`agent-scene-resize-${sceneResizeEditorTargetUrl}`}
|
||||
|
||||
@ -93,6 +93,8 @@ function buildGroupedSelectionLines(params: {
|
||||
selections: AgentWorkflowComposerSelection[];
|
||||
valueKey: "optionName" | "promptFragment";
|
||||
separator: string;
|
||||
leafOnly?: boolean;
|
||||
dedupeAcrossSteps?: boolean;
|
||||
}) {
|
||||
const grouped = new Map<
|
||||
string,
|
||||
@ -102,8 +104,16 @@ function buildGroupedSelectionLines(params: {
|
||||
valueSet: Set<string>;
|
||||
}
|
||||
>();
|
||||
const globalValueSet = new Set<string>();
|
||||
|
||||
params.selections.forEach((item) => {
|
||||
const sourceSelections = params.leafOnly
|
||||
? params.selections.filter((item, index, items) => {
|
||||
const nextItem = items[index + 1];
|
||||
return !nextItem || nextItem.stepKey !== item.stepKey;
|
||||
})
|
||||
: params.selections;
|
||||
|
||||
sourceSelections.forEach((item) => {
|
||||
const rawValue =
|
||||
params.valueKey === "promptFragment"
|
||||
? item.promptFragment || item.optionName
|
||||
@ -121,8 +131,14 @@ function buildGroupedSelectionLines(params: {
|
||||
};
|
||||
|
||||
const dedupeKey = normalizedValue.toLowerCase();
|
||||
if (params.dedupeAcrossSteps && globalValueSet.has(dedupeKey)) {
|
||||
return;
|
||||
}
|
||||
if (!currentGroup.valueSet.has(dedupeKey)) {
|
||||
currentGroup.valueSet.add(dedupeKey);
|
||||
if (params.dedupeAcrossSteps) {
|
||||
globalValueSet.add(dedupeKey);
|
||||
}
|
||||
currentGroup.values.push(normalizedValue);
|
||||
}
|
||||
|
||||
@ -312,6 +328,7 @@ export function buildAgentWorkflowComposerContext(params: {
|
||||
selections,
|
||||
valueKey: "promptFragment",
|
||||
separator: ";",
|
||||
dedupeAcrossSteps: true,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -701,6 +701,42 @@ export function getAgentWorkflowSelectedOptionPath(params: {
|
||||
return selectedOptions;
|
||||
}
|
||||
|
||||
export function sanitizeAgentWorkflowSelections(params: {
|
||||
config?: AgentWorkflowUiConfig | null;
|
||||
selections: Partial<Record<AgentWorkflowStepKey, string>>;
|
||||
}) {
|
||||
const config = params.config || defaultAgentWorkflowUiConfig;
|
||||
const enabledSteps = flattenAgentWorkflowSteps(
|
||||
getEnabledAgentWorkflowStepTree(Array.isArray(config.steps) ? config.steps : [])
|
||||
).map(({ step }) => step);
|
||||
|
||||
const sanitized: Partial<Record<AgentWorkflowStepKey, string>> = {};
|
||||
|
||||
enabledSteps.forEach((step) => {
|
||||
const rawSelectionValue = String(params.selections[step.key] || "").trim();
|
||||
if (!rawSelectionValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedOptions = getAgentWorkflowSelectedOptionPath({
|
||||
step,
|
||||
selectionValue: rawSelectionValue,
|
||||
});
|
||||
if (selectedOptions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serialized = serializeAgentWorkflowSelectionPath(
|
||||
selectedOptions.map((option) => option.key)
|
||||
);
|
||||
if (serialized) {
|
||||
sanitized[step.key] = serialized;
|
||||
}
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
export function getAgentWorkflowBlockedOptionKeys(params: {
|
||||
step: AgentWorkflowStepUiConfig;
|
||||
selectionValue?: string | null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user