feat(merge): 以官方 main 为基底融合 PR#530 多维表格与本地定制
- 多维表格(canvas-batch-table-node/use-canvas-batch-table/canvas-batch-table 等)以 PR#530 实现为主 - 保留官方 main 全部新功能:banner/画布版本历史/提示词编辑/方舟 Agent Plan/支付导出 - 移植本地定制:ddcat 多参考图校验、万有引力 lxmone 连接错误提示、Description v20(v24)、自建插件(a6api/metaso-h3/antigravity-proxy/lxmone)、seccomp、Windows 脚本 - 迁移版本升至 v24(官方 v20-23 banner/版本历史 + 我们的 Description 列) - 修复官方缺失的 markdownPlainText import
This commit is contained in:
1 parent
4eaa28b807
commit
9b9a7f4a72
63 files changed
+9087
-892
No files matched your search
@@ -9,6 +9,7 @@ export type ChannelModelFormValues = {
|
||||
providerModelKey?: string;
|
||||
displayName?: string;
|
||||
icon?: string;
|
||||
description?: string;
|
||||
capability: ModelCapabilityChoice;
|
||||
protocol?: string;
|
||||
priceTiers: PriceTierFormValues[];
|
||||
@@ -31,6 +32,7 @@ export function initialChannelModelValues(item: ChannelModel | null, protocols:
|
||||
providerModelKey: upstreamModel,
|
||||
displayName: item?.displayName || "",
|
||||
icon: item?.icon || "",
|
||||
description: item?.description || "",
|
||||
capability,
|
||||
protocol,
|
||||
priceTiers: item ? (item.priceTiers?.length ? item.priceTiers.map(priceTierToForm) : [legacyPriceTierToForm(item)]) : [defaultPriceTier()],
|
||||
|
||||
@@ -107,6 +107,7 @@ export function ChannelModelEditor({
|
||||
providerModelKey: upstreamModel,
|
||||
displayName: values.displayName?.trim() || values.modelKey.trim(),
|
||||
icon: values.icon?.trim() || "",
|
||||
description: values.description?.trim() || "",
|
||||
capability: values.capability,
|
||||
protocol: values.protocol,
|
||||
priceTiers: values.priceTiers.map((tier) => ({
|
||||
@@ -263,6 +264,9 @@ export function ChannelModelEditor({
|
||||
<Form.Item name="icon" label="模型 Logo">
|
||||
<ModelIconPicker />
|
||||
</Form.Item>
|
||||
<Form.Item name="description" label="模型说明" tooltip="展示在前台模型选择器里,用来说明该模型的能力边界与计费口径。">
|
||||
<Input.TextArea rows={2} maxLength={600} showCount placeholder="例如:固定 720P,支持 5/10/15 秒,最多 9 张参考图,按次计费。" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
{tiersWithOwnUpstream.length ? (
|
||||
<Alert
|
||||
|
||||
@@ -68,7 +68,6 @@ import { CanvasZoomControls } from "@/components/canvas/canvas-zoom-controls";
|
||||
import { CanvasShareModal } from "@/components/canvas/canvas-share-modal";
|
||||
import { CanvasScriptEditor, CanvasScriptNodeContent } from "@/components/canvas/canvas-script-node";
|
||||
import { CanvasBatchTableNodeContent } from "@/components/canvas/canvas-batch-table-node";
|
||||
import { promoteLegacyBatchTableSize } from "@/lib/canvas/canvas-batch-table";
|
||||
import { STORYBOARD_HEADER_HEIGHT, STORYBOARD_ROW_HEIGHT, storyboardMinNodeHeight, storyboardTableHeight } from "@/lib/canvas/canvas-storyboard-layout";
|
||||
import { CanvasDirectorNodePanel } from "@/components/canvas/director/canvas-director-node-panel";
|
||||
import { CanvasVersionCompareModal } from "@/components/canvas/canvas-version-compare-modal";
|
||||
@@ -97,6 +96,7 @@ import { createCanvasNode, getInputSummary, isHiddenBatchChild } from "@/lib/can
|
||||
import { stampCanvasNodeChanges, updateCanvasNode, updateCanvasNodes } from "@/lib/canvas/canvas-node-timestamps";
|
||||
import { canvasAssetHandoffAttempt, finalizeCanvasAssetHandoff, uninsertedCanvasAssetHandoffPayloads } from "@/lib/canvas/canvas-asset-handoff";
|
||||
import { batchSourceRestriction } from "@/lib/canvas/canvas-batch-connection";
|
||||
import { batchReferenceColumns } from "@/lib/canvas/canvas-batch-table";
|
||||
import { deriveStoryboardPipelineProgress } from "@/lib/canvas/canvas-storyboard-progress";
|
||||
import { CanvasOperationChangeToast, CanvasMergeStatusToast, CanvasUploadStatusToast } from "./canvas-project-feedback";
|
||||
import { backendProviderConfig, getGenerationCount } from "@/lib/canvas/canvas-project-generation";
|
||||
@@ -1888,7 +1888,7 @@ function InfiniteCanvasPage() {
|
||||
handleGenerateNode,
|
||||
});
|
||||
|
||||
const { addReferenceColumn: addBatchReferenceColumn, addRow: addBatchRow, fillRowsFromConnections, generateRows: generateBatchRows, moveReferenceCell: moveBatchReferenceCell, patchTable: patchBatchTable, removeReferenceColumn: removeBatchReferenceColumn, removeRow: removeBatchRow, reorderReferenceColumns: reorderBatchReferenceColumns, syncRowsFromConnections, updateRow: updateBatchRow } = useCanvasBatchTable({
|
||||
const { addReferenceColumn: addBatchReferenceColumn, addTextColumn: addBatchTextColumn, addRow: addBatchRow, fillRowsFromConnections, generateRows: generateBatchRows, moveReferenceCell: moveBatchReferenceCell, patchTable: patchBatchTable, removeRow: removeBatchRow, reorderReferenceColumns: reorderBatchReferenceColumns, syncRowsFromConnections, updateRow: updateBatchRow } = useCanvasBatchTable({
|
||||
nodesRef,
|
||||
connectionsRef,
|
||||
setNodes,
|
||||
@@ -1899,19 +1899,27 @@ function InfiniteCanvasPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectLoaded) return;
|
||||
setNodes((current) => {
|
||||
let changed = false;
|
||||
const next = current.map((node) => {
|
||||
const promoted = promoteLegacyBatchTableSize(node);
|
||||
if (promoted !== node) changed = true;
|
||||
return promoted;
|
||||
});
|
||||
return changed ? next : current;
|
||||
});
|
||||
nodesRef.current.filter((node) => node.type === CanvasNodeType.BatchTable).forEach((node) => {
|
||||
syncRowsFromConnections(node.id, true);
|
||||
});
|
||||
}, [connections, projectLoaded, setNodes, syncRowsFromConnections]);
|
||||
}, [connections, projectLoaded, syncRowsFromConnections]);
|
||||
|
||||
const handleUploadBatchReference = useCallback(async (tableNodeId: string, rowId: string, columnIndex: number, file: File) => {
|
||||
const tableNode = nodesRef.current.find((item) => item.id === tableNodeId);
|
||||
const table = tableNode?.metadata?.batchTable;
|
||||
const row = table?.rows.find((item) => item.id === rowId);
|
||||
if (!tableNode || !table || !row) return;
|
||||
const rowIndex = table.rows.findIndex((item) => item.id === rowId);
|
||||
const uploadedNodeId = await createFileNode(file, {
|
||||
x: tableNode.position.x - 240,
|
||||
y: tableNode.position.y + 170 + Math.max(0, rowIndex) * 220,
|
||||
});
|
||||
if (!uploadedNodeId) return;
|
||||
const inputNodeIds = Array.from({ length: Math.max(batchReferenceColumns(table).length, columnIndex + 1) }, (_, index) => row.inputNodeIds[index] || "");
|
||||
inputNodeIds[columnIndex] = uploadedNodeId;
|
||||
updateBatchRow(tableNodeId, rowId, { inputNodeIds });
|
||||
message.success(`已上传并填入参考图 ${columnIndex + 1}`);
|
||||
}, [createFileNode, message, nodesRef, updateBatchRow]);
|
||||
|
||||
const { addScriptRow, createAndGenerateScriptVideos, createScriptActionBoards, createScriptImageNodes, createScriptVideoNodes, generateScriptImages, generateScriptRows, generateScriptVideos, removeScriptRow, replaceScriptRows, updateScriptRow } =
|
||||
useCanvasStoryboard({
|
||||
@@ -2123,26 +2131,11 @@ function InfiniteCanvasPage() {
|
||||
onGenerate={(rowIds) => void generateBatchRows(contentNode.id, rowIds)}
|
||||
onRetryItem={(batchId, itemId) => retryFailedBatchItems(contentNode.id, batchId, itemId)}
|
||||
onAddReferenceColumn={() => addBatchReferenceColumn(contentNode.id)}
|
||||
onRemoveReferenceColumn={() => removeBatchReferenceColumn(contentNode.id)}
|
||||
onFocusOutput={(nodeId) => focusCanvasImageNode(nodeId)}
|
||||
onAddTextColumn={() => addBatchTextColumn(contentNode.id)}
|
||||
onReorderReferenceColumns={(fromColumnId, toColumnId) => reorderBatchReferenceColumns(contentNode.id, fromColumnId, toColumnId)}
|
||||
onMoveReferenceCell={(sourceRowId, sourceColumnIndex, targetRowId, targetColumnIndex) => moveBatchReferenceCell(contentNode.id, sourceRowId, sourceColumnIndex, targetRowId, targetColumnIndex)}
|
||||
onUploadReference={(rowId, columnIndex, file) => {
|
||||
const row = contentNode.metadata?.batchTable?.rows.find((item) => item.id === rowId);
|
||||
const existingId = row?.inputNodeIds[columnIndex];
|
||||
if (existingId) {
|
||||
void replaceNodeMedia(existingId, file);
|
||||
return;
|
||||
}
|
||||
void createFileNode(file, { x: contentNode.position.x - 180, y: contentNode.position.y + columnIndex * 90 }).then((insertedId) => {
|
||||
if (!insertedId) return;
|
||||
const currentRow = nodesRef.current.find((item) => item.id === contentNode.id)?.metadata?.batchTable?.rows.find((item) => item.id === rowId);
|
||||
const ids = [...(currentRow?.inputNodeIds || [])];
|
||||
while (ids.length <= columnIndex) ids.push("");
|
||||
ids[columnIndex] = insertedId;
|
||||
updateBatchRow(contentNode.id, rowId, { inputNodeIds: ids });
|
||||
});
|
||||
}}
|
||||
onReplaceReference={replaceCanvasNodeMedia}
|
||||
onUploadReference={(rowId, columnIndex, file) => { void handleUploadBatchReference(contentNode.id, rowId, columnIndex, file); }}
|
||||
onConnectStart={(event, handleId) => handleConnectStart(event, contentNode.id, "target", handleId)}
|
||||
onConnectDrop={(event, handleId) => handleConnectDrop(event, contentNode.id, handleId)}
|
||||
/>
|
||||
@@ -2217,6 +2210,7 @@ function InfiniteCanvasPage() {
|
||||
},
|
||||
[
|
||||
addBatchReferenceColumn,
|
||||
addBatchTextColumn,
|
||||
addBatchRow,
|
||||
addScriptRow,
|
||||
configInputsById,
|
||||
@@ -2227,11 +2221,12 @@ function InfiniteCanvasPage() {
|
||||
createScriptVideoNodes,
|
||||
currentProject?.directorScenes,
|
||||
fillRowsFromConnections,
|
||||
focusCanvasImageNode,
|
||||
generateBatchRows,
|
||||
generateScriptImages,
|
||||
generateScriptRows,
|
||||
generateScriptVideos,
|
||||
handleUploadBatchReference,
|
||||
moveBatchReferenceCell,
|
||||
handleConfigNodeChange,
|
||||
handleConnectDrop,
|
||||
handleConnectStart,
|
||||
@@ -2242,8 +2237,9 @@ function InfiniteCanvasPage() {
|
||||
openDirectorWorkbench,
|
||||
openStoryInput,
|
||||
patchBatchTable,
|
||||
removeBatchReferenceColumn,
|
||||
removeBatchRow,
|
||||
reorderBatchReferenceColumns,
|
||||
replaceCanvasNodeMedia,
|
||||
removeScriptRow,
|
||||
retryFailedBatchItems,
|
||||
runningNodeId,
|
||||
|
||||
@@ -238,7 +238,7 @@ export default function SharedCanvasPage() {
|
||||
}));
|
||||
const renderSharedNode = useCallback((node: CanvasNodeData): ReactNode => {
|
||||
if (node.type === CanvasNodeType.Script) return <SharedScriptNode node={node} onUnauthorized={unauthorized} />;
|
||||
if (node.type === CanvasNodeType.BatchTable) return <CanvasBatchTableNodeContent node={node} nodes={nodes} connections={connections} batch={node.metadata?.generationBatches?.at(-1)} theme={theme} readOnly onPatchTable={() => {}} onAddRow={() => {}} onRemoveRow={() => {}} onUpdateRow={() => {}} onFillRows={() => {}} onGenerate={() => {}} onRetryItem={() => {}} onAddReferenceColumn={() => {}} onConnectStart={() => {}} />;
|
||||
if (node.type === CanvasNodeType.BatchTable) return <CanvasBatchTableNodeContent node={node} nodes={nodes} connections={connections} batch={node.metadata?.generationBatches?.at(-1)} theme={theme} readOnly onPatchTable={() => {}} onAddRow={() => {}} onRemoveRow={() => {}} onUpdateRow={() => {}} onFillRows={() => {}} onGenerate={() => {}} onRetryItem={() => {}} onAddReferenceColumn={() => {}} onAddTextColumn={() => {}} onReorderReferenceColumns={() => {}} onMoveReferenceCell={() => {}} onReplaceReference={() => {}} onUploadReference={() => {}} onConnectStart={() => {}} />;
|
||||
return <SharedConfigNode node={node} onUnauthorized={unauthorized} />;
|
||||
}, [connections, nodes, theme, unauthorized]);
|
||||
const toolbarNodeKey = selectedNodeId;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { App } from "antd";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
|
||||
import { batchInputColumns, batchPromptForRow, batchReferenceColumns, batchReferenceHandleId, createBatchRowsFromColumns, createInheritedBatchRow, moveBatchReferenceCell, removeLastBatchReferenceColumn, reorderBatchReferenceColumns } from "@/lib/canvas/canvas-batch-table";
|
||||
import { batchInputColumns, batchPromptForRow, batchReferenceColumns, batchTextInputColumns, createBatchRow, createBatchRowsFromColumns, moveBatchReferenceCell, reorderBatchReferenceColumns } from "@/lib/canvas/canvas-batch-table";
|
||||
import { createCanvasNode } from "@/lib/canvas/canvas-project-domain";
|
||||
import { buildGenerationConfig, resetGenerationTaskMetadata } from "@/lib/canvas/canvas-project-generation";
|
||||
import { navigateToSettings } from "@/lib/settings-navigation";
|
||||
@@ -25,7 +25,7 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
const isAiConfigReady = useConfigStore((state) => state.isAiConfigReady);
|
||||
|
||||
const patchTable = useCallback((nodeId: string, patch: Partial<CanvasBatchTableData>) => {
|
||||
setNodes((current) => current.map((node) => node.id !== nodeId ? node : { ...node, metadata: { ...node.metadata, batchTable: { operation: "try_on", concurrency: 10, rows: [], ...node.metadata?.batchTable, ...patch } } }));
|
||||
setNodes((current) => current.map((node) => node.id !== nodeId ? node : { ...node, metadata: { ...node.metadata, batchTable: { operation: "try_on", concurrency: 3, rows: [], ...node.metadata?.batchTable, ...patch } } }));
|
||||
}, [setNodes]);
|
||||
|
||||
const updateRow = useCallback((nodeId: string, rowId: string, patch: Partial<CanvasBatchRow>) => {
|
||||
@@ -37,7 +37,7 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
const addRow = useCallback((nodeId: string) => {
|
||||
const table = nodesRef.current.find((item) => item.id === nodeId)?.metadata?.batchTable;
|
||||
if (!table) return;
|
||||
patchTable(nodeId, { rows: [...table.rows, createInheritedBatchRow(table.operation, table.rows)] });
|
||||
patchTable(nodeId, { rows: [...table.rows, createBatchRow(table.operation)] });
|
||||
}, [nodesRef, patchTable]);
|
||||
|
||||
const removeRow = useCallback((nodeId: string, rowId: string) => {
|
||||
@@ -55,19 +55,13 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
patchTable(nodeId, { referenceColumns: [...columns, { id: `reference-${nanoid()}`, label: `参考图 ${nextIndex}` }] });
|
||||
}, [message, nodesRef, patchTable]);
|
||||
|
||||
const removeReferenceColumn = useCallback((nodeId: string) => {
|
||||
const addTextColumn = useCallback((nodeId: string) => {
|
||||
const table = nodesRef.current.find((item) => item.id === nodeId)?.metadata?.batchTable;
|
||||
if (!table) return;
|
||||
const columns = batchReferenceColumns(table);
|
||||
const nextTable = removeLastBatchReferenceColumn(table);
|
||||
if (!nextTable) return message.info("至少保留 1 组参考图");
|
||||
const removed = columns.at(-1);
|
||||
patchTable(nodeId, nextTable);
|
||||
if (removed) {
|
||||
const handleId = batchReferenceHandleId(removed.id);
|
||||
setConnections((current) => current.filter((connection) => !(connection.toNodeId === nodeId && connection.toHandleId === handleId)));
|
||||
}
|
||||
}, [message, nodesRef, patchTable, setConnections]);
|
||||
const columns = table.textColumns || [];
|
||||
if (columns.length >= 4) return message.info("最多支持 4 组文字");
|
||||
patchTable(nodeId, { textColumns: [...columns, { id: `text-${nanoid()}`, label: `文字 ${columns.length + 1}` }] });
|
||||
}, [message, nodesRef, patchTable]);
|
||||
|
||||
const syncRowsFromConnections = useCallback((nodeId: string, silent = false) => {
|
||||
const node = nodesRef.current.find((item) => item.id === nodeId);
|
||||
@@ -83,16 +77,25 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
return false;
|
||||
}
|
||||
const rows = createBatchRowsFromColumns(table.operation, columns, table.rows);
|
||||
const textColumns = batchTextInputColumns(node, connectionsRef.current).map((column) => column.filter((inputNodeId) => {
|
||||
const input = nodeById.get(inputNodeId);
|
||||
return input?.type === CanvasNodeType.Text && Boolean(input.metadata?.content || input.metadata?.prompt);
|
||||
}));
|
||||
const rowsWithText = rows.map((row, index) => ({
|
||||
...row,
|
||||
textNodeIds: textColumns.flatMap((column) => {
|
||||
const input = column.length === 1 ? column[0] : column[index];
|
||||
return input ? [input] : [];
|
||||
}),
|
||||
}));
|
||||
if (!rows.length) {
|
||||
if (!silent) message.warning("批量换装至少需要一张人物图和一张服装图");
|
||||
return false;
|
||||
}
|
||||
if (JSON.stringify(rows) === JSON.stringify(table.rows)) return true;
|
||||
patchTable(nodeId, { rows });
|
||||
if (!silent) {
|
||||
const added = Math.max(0, rows.length - table.rows.length);
|
||||
message.success(added ? `已同步连线并新增 ${added} 行,原有任务均已保留` : "已同步最新连线,原有任务均已保留");
|
||||
}
|
||||
const rowsChanged = JSON.stringify(table.rows) !== JSON.stringify(rowsWithText);
|
||||
if (!rowsChanged) return false;
|
||||
patchTable(nodeId, { rows: rowsWithText });
|
||||
if (!silent) message.success(`已按连线创建 ${rows.length} 行任务`);
|
||||
return true;
|
||||
}, [connectionsRef, message, nodesRef, patchTable]);
|
||||
|
||||
@@ -126,15 +129,11 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
const activeNodeIds = new Set((sourceNode.metadata?.generationBatches || []).filter((batch) => batch.mode === "batch_image").flatMap((batch) => batch.items.filter((item) => ["waiting", "submitting", "queued", "running"].includes(item.status)).map((item) => item.nodeId)));
|
||||
const requested = requestedRowIds?.length ? new Set(requestedRowIds) : null;
|
||||
const rows = table.rows.filter((row) => {
|
||||
const inputNodeIds = row.inputNodeIds.filter(Boolean);
|
||||
if (!row.enabled || (requested && !requested.has(row.id)) || !batchPromptForRow(table, row).trim()) return false;
|
||||
if (table.operation === "try_on" && inputNodeIds.length < 2) return false;
|
||||
if (!inputNodeIds.length || inputNodeIds.some((id) => !nodesRef.current.some((node) => node.id === id && node.type === CanvasNodeType.Image && Boolean(node.metadata?.content || node.metadata?.storageKey)))) return false;
|
||||
if (row.enabled === false || (requested && !requested.has(row.id)) || !batchPromptForRow(table, row).trim()) return false;
|
||||
if (table.operation === "try_on" && row.inputNodeIds.length < 2) return false;
|
||||
if (!row.inputNodeIds.length || row.inputNodeIds.some((id) => !nodesRef.current.some((node) => node.id === id && node.type === CanvasNodeType.Image && Boolean(node.metadata?.content || node.metadata?.storageKey)))) return false;
|
||||
const output = row.outputNodeId ? nodesRef.current.find((node) => node.id === row.outputNodeId) : undefined;
|
||||
if (output && activeNodeIds.has(output.id)) return false;
|
||||
// 操作列的单行生成允许对已完成结果重新生成;顶部批量按钮仍只提交未完成项。
|
||||
if (requested) return true;
|
||||
return !output?.metadata?.content;
|
||||
return !output?.metadata?.content && (!output || !activeNodeIds.has(output.id));
|
||||
});
|
||||
if (!rows.length) return message.info("没有可提交的未完成任务,请检查参考图和提示词");
|
||||
const confirmed = await new Promise<boolean>((resolve) => modal.confirm({
|
||||
@@ -159,7 +158,10 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
const metadata = {
|
||||
...(existingIndex >= 0 ? resetGenerationTaskMetadata(nextNodes[existingIndex].metadata) : {}),
|
||||
prompt,
|
||||
composerContent: prompt,
|
||||
composerContent: [prompt, ...(row.textNodeIds || []).map((textNodeId) => {
|
||||
const textNode = nodesRef.current.find((node) => node.id === textNodeId);
|
||||
return textNode?.metadata?.content || textNode?.metadata?.prompt || "";
|
||||
})].filter(Boolean).join("\n\n"),
|
||||
model: buildGenerationConfig(effectiveConfig, undefined, "image").model,
|
||||
generationMode: "image" as const,
|
||||
generationType: "edit" as const,
|
||||
@@ -178,7 +180,7 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
if (existingIndex >= 0) nextNodes[existingIndex] = output;
|
||||
else nextNodes.push(output);
|
||||
nextConnections = nextConnections.filter((connection) => connection.toNodeId !== output.id);
|
||||
row.inputNodeIds.filter(Boolean).forEach((inputNodeId) => nextConnections.push({ id: nanoid(), fromNodeId: inputNodeId, toNodeId: output.id }));
|
||||
row.inputNodeIds.forEach((inputNodeId) => nextConnections.push({ id: nanoid(), fromNodeId: inputNodeId, toNodeId: output.id }));
|
||||
nextConnections.push({ id: nanoid(), fromNodeId: sourceNode.id, toNodeId: output.id, relation: "batch-output", storyboardRowId: row.id });
|
||||
outputByRowId.set(row.id, output.id);
|
||||
targets.push({ rowId: row.id, nodeId: output.id });
|
||||
@@ -193,5 +195,5 @@ export function useCanvasBatchTable({ nodesRef, connectionsRef, setNodes, setCon
|
||||
if (enqueueGenerationBatch(nodeId, "batch_image", targets, { concurrency: table.concurrency })) message.success(`${targets.length} 个任务已加入并发队列`);
|
||||
}, [connectionsRef, effectiveConfig, enqueueGenerationBatch, isAiConfigReady, message, modal, nodesRef, setConnections, setNodes, setSelectedNodeIds]);
|
||||
|
||||
return { addReferenceColumn, addRow, fillRowsFromConnections, generateRows, moveReferenceCell, patchTable, removeReferenceColumn, removeRow, reorderReferenceColumns, syncRowsFromConnections, updateRow };
|
||||
return { addReferenceColumn, addTextColumn, addRow, fillRowsFromConnections, generateRows, moveReferenceCell, patchTable, removeRow, reorderReferenceColumns, syncRowsFromConnections, updateRow };
|
||||
}
|
||||
@@ -46,7 +46,6 @@ const NODE_STATUS_SUCCESS = "success" as const;
|
||||
const BATCH_UPLOAD_COLUMNS = 3;
|
||||
const BATCH_UPLOAD_COLUMN_GAP = 380;
|
||||
const BATCH_UPLOAD_ROW_GAP = 300;
|
||||
|
||||
const CANVAS_BATCH_TABLE_SELECTOR = "[data-canvas-batch-table]";
|
||||
|
||||
function isBatchTableDragEvent(event: DragEvent<HTMLElement>) {
|
||||
@@ -544,11 +543,13 @@ export function useCanvasUpload({
|
||||
const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => {
|
||||
if (isBatchTableDragEvent(event)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fileDragDepthRef.current = 0;
|
||||
setFileDropActive(false);
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fileDragDepthRef.current = 0;
|
||||
setFileDropActive(false);
|
||||
const chapterPayload = parseProjectChapterPayload(event.dataTransfer.getData(CANVAS_PROJECT_CHAPTER_DND_TYPE));
|
||||
@@ -590,12 +591,14 @@ export function useCanvasUpload({
|
||||
const handleFileDragEnter = useCallback((event: DragEvent<HTMLDivElement>) => {
|
||||
if (isBatchTableDragEvent(event)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fileDragDepthRef.current = 0;
|
||||
setFileDropActive(false);
|
||||
return;
|
||||
}
|
||||
if (!hasDraggedFiles(event)) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fileDragDepthRef.current += 1;
|
||||
setFileDropActive(true);
|
||||
}, []);
|
||||
@@ -603,18 +606,21 @@ export function useCanvasUpload({
|
||||
const handleFileDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {
|
||||
if (isBatchTableDragEvent(event)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fileDragDepthRef.current = 0;
|
||||
setFileDropActive(false);
|
||||
return;
|
||||
}
|
||||
if (!hasDraggedFiles(event) && !Array.from(event.dataTransfer.types).includes(CANVAS_PROJECT_CHAPTER_DND_TYPE)) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
}, []);
|
||||
|
||||
const handleFileDragLeave = useCallback((event: DragEvent<HTMLDivElement>) => {
|
||||
if (isBatchTableDragEvent(event)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
fileDragDepthRef.current = 0;
|
||||
setFileDropActive(false);
|
||||
return;
|
||||
@@ -732,6 +738,7 @@ export function useCanvasUpload({
|
||||
return {
|
||||
assetPickerOpen,
|
||||
closeAssetPicker,
|
||||
createFileNode,
|
||||
createVideoNodeFromBlob,
|
||||
createAssetPayloadNode,
|
||||
createImageAssetNode,
|
||||
@@ -752,7 +759,6 @@ export function useCanvasUpload({
|
||||
pasteAssistantImage,
|
||||
pasteSystemClipboard,
|
||||
replaceNodeMedia,
|
||||
createFileNode,
|
||||
startUploadStatus,
|
||||
uploadModalOpen,
|
||||
uploadStatus,
|
||||
|
||||
Reference in new issue
Block a user