feat: add generation record detail workspace with AI conversation panel and canvas reset #14
@@ -1474,6 +1474,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
const [cloneVideoDuration, setCloneVideoDuration] = useState(10);
|
const [cloneVideoDuration, setCloneVideoDuration] = useState(10);
|
||||||
const [cloneVideoSmart, setCloneVideoSmart] = useState(true);
|
const [cloneVideoSmart, setCloneVideoSmart] = useState(true);
|
||||||
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
|
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
|
||||||
|
const [isCloneConversationCollapsed, setIsCloneConversationCollapsed] = useState(false);
|
||||||
const [previewZoom, setPreviewZoom] = useState(1);
|
const [previewZoom, setPreviewZoom] = useState(1);
|
||||||
const quickSetSelectTimerRef = useRef<number | null>(null);
|
const quickSetSelectTimerRef = useRef<number | null>(null);
|
||||||
const openQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null);
|
const openQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null);
|
||||||
@@ -4950,6 +4951,9 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
<button type="button" onClick={() => setPreviewZoom((z) => Math.max(0.25, z - 0.1))} disabled={previewZoom <= 0.25} aria-label="缩小">-</button>
|
<button type="button" onClick={() => setPreviewZoom((z) => Math.max(0.25, z - 0.1))} disabled={previewZoom <= 0.25} aria-label="缩小">-</button>
|
||||||
<span>{Math.round(previewZoom * 100)}%</span>
|
<span>{Math.round(previewZoom * 100)}%</span>
|
||||||
<button type="button" onClick={() => setPreviewZoom((z) => Math.min(2, z + 0.1))} disabled={previewZoom >= 2} aria-label="放大">+</button>
|
<button type="button" onClick={() => setPreviewZoom((z) => Math.min(2, z + 0.1))} disabled={previewZoom >= 2} aria-label="放大">+</button>
|
||||||
|
{activeHistoryRecordId ? (
|
||||||
|
<button type="button" onClick={() => { setPreviewZoom(1); setPreviewOffset({ x: 0, y: 0 }); }} aria-label="重置画布">重置</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -6657,10 +6661,15 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
)
|
)
|
||||||
: clonePreview
|
: clonePreview
|
||||||
: placeholderPreview;
|
: placeholderPreview;
|
||||||
|
const isMainCloneWorkspace = isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool;
|
||||||
|
const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId);
|
||||||
|
const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0);
|
||||||
|
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
|
||||||
|
const currentResultThumbs = canvasNodes.flatMap((node) => node.results).slice(0, 6);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isCloneTool && activeHistoryRecordId ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}`}
|
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-conversation-collapsed" : ""}${isRecordDetailWorkspace ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}`}
|
||||||
data-tool={activeTool}
|
data-tool={activeTool}
|
||||||
aria-label={pageLabel}
|
aria-label={pageLabel}
|
||||||
>
|
>
|
||||||
@@ -6698,6 +6707,80 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{isRecordDetailWorkspace ? (
|
||||||
|
<>
|
||||||
|
<aside className="clone-ai-conversation-panel" aria-label="AI 对话">
|
||||||
|
<header className="clone-ai-conversation-head">
|
||||||
|
<div>
|
||||||
|
<strong>{activeHistoryRecord?.title || "生成详情"}</strong>
|
||||||
|
<span>{selectedCloneOutput.label} · {platform} · {language}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsCloneConversationCollapsed(true)}
|
||||||
|
aria-label="收起对话"
|
||||||
|
title="收起对话"
|
||||||
|
>
|
||||||
|
<MenuFoldOutlined />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<div className="clone-ai-conversation-body">
|
||||||
|
<section className="clone-ai-chat-message clone-ai-chat-message--user">
|
||||||
|
<span>需求</span>
|
||||||
|
<p>{requirement.trim() || "上传商品素材,描述你想生成的商品图、详情图、模特图或短视频。"}</p>
|
||||||
|
{productImages.length ? (
|
||||||
|
<div className="clone-ai-chat-assets" aria-label="已上传素材">
|
||||||
|
{productImages.slice(0, 4).map((image) => (
|
||||||
|
<img key={image.id} src={image.src} alt={image.name || "商品素材"} />
|
||||||
|
))}
|
||||||
|
{productImages.length > 4 ? <em>+{productImages.length - 4}</em> : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
<section className={`clone-ai-chat-message clone-ai-chat-message--assistant is-${status}`}>
|
||||||
|
<span>电商图设计师</span>
|
||||||
|
<p>
|
||||||
|
{status === "done" || currentResultCount > 0
|
||||||
|
? `已生成 ${currentResultCount || results.length || productSetResultImages.filter(Boolean).length} 张结果,可在画布中拖拽、缩放和预览。`
|
||||||
|
: status === "generating"
|
||||||
|
? `正在为 ${platform} / ${market} 生成${selectedCloneOutput.label},结果会自动出现在中间画布。`
|
||||||
|
: status === "failed"
|
||||||
|
? "生成失败,请检查网络或参数后重试。"
|
||||||
|
: "我会根据商品图、平台规则和提示词整理生成任务。"}
|
||||||
|
</p>
|
||||||
|
{status === "generating" ? (
|
||||||
|
<EcommerceProgressBar status="generating" progress={generationProgress} onCancel={handleCancelGenerate} label={`${selectedCloneOutput.label}生成`} />
|
||||||
|
) : null}
|
||||||
|
{currentResultThumbs.length ? (
|
||||||
|
<div className="clone-ai-chat-results" aria-label="生成结果缩略图">
|
||||||
|
{currentResultThumbs.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => openProductSetPreview(item)}
|
||||||
|
aria-label={`预览${item.label}`}
|
||||||
|
>
|
||||||
|
<img src={item.src} alt={item.label} />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="clone-ai-conversation-toggle"
|
||||||
|
onClick={() => setIsCloneConversationCollapsed((current) => !current)}
|
||||||
|
aria-label={isCloneConversationCollapsed ? "展开对话" : "收起对话"}
|
||||||
|
title={isCloneConversationCollapsed ? "展开对话" : "收起对话"}
|
||||||
|
aria-expanded={!isCloneConversationCollapsed}
|
||||||
|
>
|
||||||
|
{isCloneConversationCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{activePreview}
|
{activePreview}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user