Merge remote-tracking branch 'origin/master'

# Conflicts:
#	docker-compose.yml
This commit is contained in:
2026-09-13 15:37:38 +08:00
11 changed files with 676 additions and 3 deletions
+38
View File
@@ -31,6 +31,7 @@ export default function ExportPanel({
const [exportW, setExportW] = useState('1920');
const [exportH, setExportH] = useState('1080');
const [isSavingSticker, setIsSavingSticker] = useState(false);
const [isExportingAi, setIsExportingAi] = useState(false);
const [stroke, setStroke] = useState(false);
const [fillMode, setFillMode] = useState<FillMode>('fill');
@@ -77,6 +78,27 @@ export default function ExportPanel({
triggerDownload(url, 'wordcloud.svg');
};
const handleExportAi = async () => {
if (isExportingAi) return;
const url = buildCustomSvgUrl();
if (!url) return;
setIsExportingAi(true);
try {
const svgResponse = await ensureOk(await fetch(url), '读取 SVG 失败');
const svg = await svgResponse.text();
const aiResponse = await ensureOk(await fetch(apiUrl('/api/exports/ai'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ svg, filename: 'wordcloud.ai' }),
}), '导出 AI 失败');
triggerDownloadBlob(await aiResponse.blob(), 'wordcloud.ai');
} catch (error) {
alert(error instanceof Error ? error.message : '导出 AI 失败');
} finally {
setIsExportingAi(false);
}
};
const handleSaveAsSticker = async (mode: 'add' | 'replace' = 'add') => {
if (isSavingSticker) return;
if (mode === 'replace' && !replaceTarget) {
@@ -145,6 +167,12 @@ export default function ExportPanel({
document.body.removeChild(a);
};
const triggerDownloadBlob = (blob: Blob, filename: string) => {
const url = URL.createObjectURL(blob);
triggerDownload(url, filename);
URL.revokeObjectURL(url);
};
const hasResult = !!jobId;
return (
@@ -339,6 +367,16 @@ export default function ExportPanel({
>
SVG
</button>
<button
className="btn btn-secondary btn-sm btn-block"
disabled={!hasResult || isExportingAi || fillMode === 'dot' || fillMode === 'ring'}
onClick={handleExportAi}
>
{isExportingAi ? '正在生成 AI...' : '导出 AI'}
</button>
{(fillMode === 'dot' || fillMode === 'ring') && (
<div className="note-text">AI 线</div>
)}
{replaceTarget ? (
<>
<div className="settings-note" style={{ marginTop: 8 }}>
+32
View File
@@ -470,6 +470,16 @@ export default function CanvasStudio({
);
}, [normalizedDocument, stickerById]);
const exportAi = useCallback(async (addRegistrationMarks: boolean) => {
const svg = await serializeDocument(normalizedDocument, stickerById, { addRegistrationMarks });
const response = await ensureOk(await fetch(apiUrl('/api/exports/ai'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ svg, filename: 'canvas-design.ai' }),
}), '导出 AI 失败');
downloadBlob(await response.blob(), 'canvas-design.ai');
}, [normalizedDocument, stickerById]);
const exportLayerZip = async (layerIds: string[], folderIds: string[], addRegistrationMarks: boolean) => {
const blob = await createLayerExportZip(normalizedDocument, stickerById, layerIds, folderIds, { addRegistrationMarks });
downloadBlob(blob, 'canvas-layers.zip');
@@ -908,6 +918,7 @@ export default function CanvasStudio({
stickerById={stickerById}
onUpdateDocument={updateDocument}
onExportSvg={exportSvg}
onExportAi={exportAi}
onExportLayerZip={exportLayerZip}
onReset={resetDesign}
/>
@@ -1447,6 +1458,7 @@ function CanvasExportPanel({
stickerById,
onUpdateDocument,
onExportSvg,
onExportAi,
onExportLayerZip,
onReset,
}: {
@@ -1454,6 +1466,7 @@ function CanvasExportPanel({
stickerById: Map<string, StickerAsset>;
onUpdateDocument: (partial: Partial<CanvasDocument>) => void;
onExportSvg: (addRegistrationMarks: boolean) => void;
onExportAi: (addRegistrationMarks: boolean) => Promise<void>;
onExportLayerZip: (layerIds: string[], folderIds: string[], addRegistrationMarks: boolean) => void;
onReset: () => void;
}) {
@@ -1464,6 +1477,7 @@ function CanvasExportPanel({
const [templateDescription, setTemplateDescription] = useState('');
const [referenceFiles, setReferenceFiles] = useState<File[]>([]);
const [addRegistrationMarks, setAddRegistrationMarks] = useState(false);
const [exportingAi, setExportingAi] = useState(false);
const [exportingPackage, setExportingPackage] = useState(false);
const layers = documentModel.layers || [];
const folders = documentModel.layerFolders || [];
@@ -1589,6 +1603,24 @@ function CanvasExportPanel({
<div className="note-text"></div>
</div>
<button className="btn btn-primary btn-block" onClick={() => onExportSvg(addRegistrationMarks)}> SVG</button>
<button
className="btn btn-secondary btn-block"
disabled={exportingAi}
onClick={async () => {
if (exportingAi) return;
setExportingAi(true);
try {
await onExportAi(addRegistrationMarks);
} catch (error) {
alert(error instanceof Error ? error.message : '导出 AI 失败');
} finally {
setExportingAi(false);
}
}}
>
{exportingAi ? '正在生成 AI...' : '导出总图 AI'}
</button>
<div className="note-text">AI </div>
<div className="section-divider" />
<div className="section-title"></div>