+
{index + 1}
+
+ onUpdateRow(row.id, { enabled })} aria-label={`任务 ${index + 1}`} />
+ {row.enabled === false ? "关" : "开"}
{referenceColumns.map((column, columnIndex) => (
-
-
pickReferenceFile(row.id, columnIndex)}
- onUploadFile={(file) => onUploadReference?.(row.id, columnIndex, file)}
- onPointerDown={(event) => startCellDrag(event, row.id, columnIndex)}
- />
+ { uploadTargetRef.current = { rowId: row.id, columnIndex }; uploadInputRef.current?.click(); }}
+ onPointerDown={handleReferencePointerDown}
+ onPointerMove={handleReferencePointerMove}
+ onPointerUp={handleReferencePointerUp}
+ onPointerCancel={handleReferencePointerCancel}
+ onLostPointerCapture={handleReferencePointerCancel}
+ />
+ ))}
+ {textColumns.map((column, columnIndex) => (
+
+
))}
-
-
onUpdateRow(row.id, { prompt })}
- onSubmit={!readOnly && ready && !status.loading ? () => onGenerate([row.id]) : undefined}
- onPointerDown={(event) => event.stopPropagation()}
- onWheel={(event) => event.stopPropagation()}
- />
-
- {readOnly ? "输入 @ 插入参考图" : "输入 @ 插入参考图 · ⌘/Ctrl + Enter 生成此行"}
+
onUpdateRow(row.id, { prompt: event.target.value })} />
+
+ {output?.metadata?.content || output?.metadata?.storageKey ? (
+
}
+ />
+ ) : (
+
+ )}
+
+
{status.label}
+ {!readOnly && status.retryable && batch && item ? (
+
} onClick={() => onRetryItem(batch.id, item.id)}>
+ 重试
+
+ ) : null}
-
-
- { if (row.outputNodeId) onFocusOutput?.(row.outputNodeId); }}
- />
+ {status.loading ? : null}
{!readOnly ? (
-
-
- : } onClick={() => onGenerate([row.id])} />
-
-
} onClick={() => onRemoveRow(row.id)} />
-
- ) : }
+
+ } onClick={() => onRemoveRow(row.id)} />
+
+ ) : (
+
+ )}
);
})
) : (
-
-
-
-
还没有批量任务
-
把图片连接到左侧参考图端口后同步连线,或先添加一行手工配置。
- {!readOnly ?
} onClick={onAddRow}>添加第一条任务 : null}
-
-
+ 连接图片后会自动生成任务;点击缩略图可替换素材,拖动参考图列可调整顺序。
)}
);
}
-function BatchChoiceGroup({ ariaLabel, options, value, onChange, theme, compact = false, disabled = false }: { ariaLabel: string; options: Array<{ value: string | number; label: string }>; value: string | number; onChange: (value: string | number) => void; theme: CanvasTheme; compact?: boolean; disabled?: boolean }) {
+function ReferenceColumnHeader({ column, theme, readOnly, dragging, onDragStart, onDragEnd, onDrop }: { column: { id: string; label: string }; theme: CanvasTheme; readOnly: boolean; dragging: boolean; onDragStart: () => void; onDragEnd: () => void; onDrop: (columnId: string) => void }) {
return (
-
- {options.map((option) => {
- const selected = option.value === value;
- return ;
- })}
-
+
{
+ event.dataTransfer.effectAllowed = "move";
+ event.dataTransfer.setData("text/plain", column.id);
+ onDragStart();
+ }}
+ onDragEnd={onDragEnd}
+ onDragOver={(event) => { if (!readOnly) { event.preventDefault(); event.dataTransfer.dropEffect = "move"; } }}
+ onDrop={(event) => { event.preventDefault(); onDrop(column.id); }}
+ >
+ {!readOnly ? : null}
+ {column.label}
+
);
}
-function BatchReferenceHandles({ columns, theme, onConnectStart, onConnectDrop }: { columns: ReturnType
; theme: CanvasTheme; onConnectStart: (event: ReactPointerEvent, handleId: string) => void; onConnectDrop?: (event: ReactPointerEvent, handleId: string) => void }) {
- const commonStyle = { left: 0, width: 36, height: 36, transform: "translate(-50%, -50%)", transformOrigin: "center" };
- return <>{columns.map((column, index) => )}>;
+function BatchTextHandle(props: Omit, "column"> & { column: { id: string; label: string } }) {
+ return ;
}
-function BatchReferenceHandle({ column, index, theme, commonStyle, onConnectStart, onConnectDrop }: { column: { id: string; label: string }; index: number; theme: CanvasTheme; commonStyle: { left: number; width: number; height: number; transform: string; transformOrigin: string }; onConnectStart: (event: ReactPointerEvent, handleId: string) => void; onConnectDrop?: (event: ReactPointerEvent, handleId: string) => void }) {
+function BatchReferenceHandles({
+ columns,
+ textColumns,
+ theme,
+ onAdd,
+ onAddText,
+ onConnectStart,
+ onConnectDrop,
+}: {
+ columns: ReturnType;
+ textColumns: ReturnType;
+ theme: CanvasTheme;
+ onAdd: () => void;
+ onAddText: () => void;
+ onConnectStart: (event: ReactPointerEvent, handleId: string) => void;
+ onConnectDrop?: (event: ReactPointerEvent, handleId: string) => void;
+}) {
+ const commonStyle = { left: 0, width: 44, height: 44, transform: "translate(-50%, -50%)", transformOrigin: "center" };
+ const referenceAddTop = BATCH_REFERENCE_HANDLE_TOP + columns.length * BATCH_REFERENCE_HANDLE_GAP;
+ const textTop = batchTextHandleTop(columns.length);
+ return (
+ <>
+ {columns.map((column, index) => (
+
+ ))}
+ {textColumns.map((column, index) => (
+
+ ))}
+ {columns.length < MAX_BATCH_REFERENCE_COLUMNS ? (
+
+
+
+ ) : null}
+ {textColumns.length < MAX_BATCH_TEXT_COLUMNS ? (
+
+
+
+ ) : null}
+ >
+ );
+}
+
+function BatchReferenceHandle({
+ column,
+ top,
+ badge,
+ theme,
+ commonStyle,
+ onConnectStart,
+ onConnectDrop,
+ handlePrefix = "reference",
+}: {
+ column: { id: string; label: string };
+ top: number;
+ badge: string;
+ theme: CanvasTheme;
+ commonStyle: { left: number; width: number; height: number; transform: string; transformOrigin: string };
+ onConnectStart: (event: ReactPointerEvent, handleId: string) => void;
+ onConnectDrop?: (event: ReactPointerEvent, handleId: string) => void;
+ handlePrefix?: "reference" | "text";
+}) {
const [hovered, setHovered] = useState(false);
const [offset, setOffset] = useState({ x: 0, y: 0 });
- const handleId = batchReferenceHandleId(column.id);
- const reset = useCallback(() => { setHovered(false); setOffset({ x: 0, y: 0 }); }, []);
+ const handleId = handlePrefix === "text" ? batchTextHandleId(column.id) : batchReferenceHandleId(column.id);
+ const reset = useCallback(() => {
+ setHovered(false);
+ setOffset({ x: 0, y: 0 });
+ }, []);
const update = useCallback((event: ReactPointerEvent) => {
const bounds = event.currentTarget.getBoundingClientRect();
const dx = event.clientX - (bounds.left + bounds.width / 2);
const dy = event.clientY - (bounds.top + bounds.height / 2);
- const limit = 10;
+ const limit = 16;
setOffset({ x: Math.max(-limit, Math.min(limit, dx)), y: Math.max(-limit, Math.min(limit, dy)) });
}, []);
return (
-
-