fix(*): 画布与任务详情 - 优化生成参数展示、批量上传和默认模型状态
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react";
|
||||
import { App, Button, Dropdown, Input, Modal, Segmented, Tag } from "antd";
|
||||
import { useEffect, useLayoutEffect, useRef, useState, type ReactNode, type RefObject } from "react";
|
||||
import { App, Button, Dropdown, Input, Modal, Tag } from "antd";
|
||||
import type { MenuProps } from "antd";
|
||||
import { ChevronDown, Ellipsis, Lock, Plus, Unlock } from "lucide-react";
|
||||
|
||||
@@ -346,32 +346,12 @@ function NodeDockMenuButton({ menuId, label, icon, tools, openMenuId, onOpenChan
|
||||
|
||||
export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, readOnly = false, onUnauthorized }: { node: CanvasNodeData | null; open: boolean; onClose: () => void; onMetadataChange?: (nodeId: string, metadata: Partial<CanvasNodeMetadata>) => void; readOnly?: boolean; onUnauthorized?: () => void }) {
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const [view, setView] = useState<"info" | "json">("info");
|
||||
const [assetTags, setAssetTags] = useState<string[]>([]);
|
||||
const [assetTagInput, setAssetTagInput] = useState("");
|
||||
const [assetCategory, setAssetCategory] = useState<CanvasAssetCategory>("other");
|
||||
const imageBytes = node?.type === CanvasNodeType.Image && node.metadata?.content ? getDataUrlByteSize(node.metadata.content) : 0;
|
||||
const batchCount = node?.type === CanvasNodeType.Image ? node.metadata?.batchChildIds?.length || 0 : 0;
|
||||
const nodeTypeLabel = node?.type === CanvasNodeType.Text ? "文本" : node?.type === CanvasNodeType.Script ? "分镜脚本" : node?.type === CanvasNodeType.Skill ? "技能" : node?.type === CanvasNodeType.Image ? "图片" : node?.type === CanvasNodeType.Video ? "视频" : node?.type === CanvasNodeType.Audio ? "音频" : node?.type === CanvasNodeType.Drawing ? "绘图" : node?.type === CanvasNodeType.Frame ? "背板" : "生成配置";
|
||||
const json = useMemo(() => {
|
||||
if (!node) return "";
|
||||
return JSON.stringify(
|
||||
node,
|
||||
(key, value) => {
|
||||
if (key === "title") return undefined;
|
||||
if (key === "content" && typeof value === "string" && value.startsWith("data:image/")) {
|
||||
return "[base64 image]";
|
||||
}
|
||||
return value;
|
||||
},
|
||||
2,
|
||||
);
|
||||
}, [node]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setView("info");
|
||||
}, [node?.id, open]);
|
||||
|
||||
useEffect(() => {
|
||||
setAssetTags(node?.metadata?.assetTags || []);
|
||||
setAssetTagInput("");
|
||||
@@ -411,15 +391,6 @@ export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, rea
|
||||
<div className="text-[var(--fs-heading-lg)] font-semibold">节点信息</div>
|
||||
{node ? <div className="canvas-node-inspector-id">{node.id}</div> : null}
|
||||
</div>
|
||||
<Segmented
|
||||
size="small"
|
||||
value={view}
|
||||
onChange={(value) => setView(value as "info" | "json")}
|
||||
options={[
|
||||
{ label: "信息", value: "info" },
|
||||
{ label: "JSON", value: "json" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -431,11 +402,11 @@ export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, rea
|
||||
centered
|
||||
footer={null}
|
||||
onCancel={onClose}
|
||||
width="min(920px, calc(100vw - 32px))"
|
||||
styles={{ body: { paddingTop: 4 } }}
|
||||
>
|
||||
{node ? (
|
||||
<div className="canvas-node-inspector" style={{ color: theme.node.text }}>
|
||||
{view === "info" ? (
|
||||
<div className="thin-scrollbar canvas-node-inspector-scroll">
|
||||
<section className="canvas-node-inspector-section">
|
||||
<div className="canvas-node-inspector-section-heading"><span>基础信息</span><em>{node.metadata?.status || "idle"}</em></div>
|
||||
@@ -464,7 +435,16 @@ export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, rea
|
||||
{node.metadata?.prompt ? (
|
||||
<section className="canvas-node-inspector-section">
|
||||
<div className="canvas-node-inspector-section-heading"><span>提示词</span></div>
|
||||
<div className="canvas-node-inspector-copy">{node.metadata.prompt}</div>
|
||||
<div className="canvas-node-inspector-copy canvas-node-inspector-prompt">{node.metadata.prompt}</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{nodeGenerationRows(node).length ? (
|
||||
<section className="canvas-node-inspector-section">
|
||||
<div className="canvas-node-inspector-section-heading"><span>生成信息</span></div>
|
||||
<div className="canvas-node-inspector-facts">
|
||||
{nodeGenerationRows(node).map((item) => <InfoRow key={item.label} label={item.label} value={item.value} />)}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
@@ -520,11 +500,6 @@ export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, rea
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<pre className="thin-scrollbar canvas-node-inspector-json" style={{ color: theme.node.text }}>
|
||||
{json}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
@@ -539,3 +514,41 @@ function InfoRow({ label, value }: { label: string; value: ReactNode }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function nodeGenerationRows(node: CanvasNodeData) {
|
||||
const metadata = node.metadata;
|
||||
if (!metadata) return [] as Array<{ label: string; value: string }>;
|
||||
const rows: Array<{ label: string; value: string }> = [];
|
||||
const add = (label: string, value: unknown) => {
|
||||
if (value === undefined || value === null || value === "") return;
|
||||
rows.push({ label, value: String(value) });
|
||||
};
|
||||
const addTime = (label: string, value?: string) => {
|
||||
if (!value) return;
|
||||
const timestamp = Date.parse(value);
|
||||
add(label, Number.isFinite(timestamp) ? new Date(timestamp).toLocaleString() : value);
|
||||
};
|
||||
const addDuration = (value?: number) => {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
||||
const totalSeconds = Math.max(0, Math.round(value / 1000));
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
add("耗时", minutes ? `${minutes}分 ${seconds}秒` : `${seconds}秒`);
|
||||
};
|
||||
|
||||
add("模型", metadata.model);
|
||||
add("生成尺寸", metadata.size);
|
||||
add("分辨率", metadata.vquality || metadata.quality);
|
||||
add("秒数", metadata.seconds ? `${metadata.seconds} 秒` : undefined);
|
||||
add("生成声音", metadata.generateAudio === undefined ? undefined : metadata.generateAudio === "true" ? "开启" : "关闭");
|
||||
add("水印", metadata.watermark === undefined ? undefined : metadata.watermark === "true" ? "开启" : "关闭");
|
||||
if (metadata.references?.length) {
|
||||
const referenceNames = metadata.references.slice(0, 3).map((reference) => reference.split("/").pop() || reference).join("、");
|
||||
add("引用素材", `${metadata.references.length} 个${referenceNames ? `(${referenceNames}${metadata.references.length > 3 ? "…" : ""})` : ""}`);
|
||||
}
|
||||
addTime("创建时间", metadata.taskCreatedAt);
|
||||
addTime("开始时间", metadata.taskStartedAt);
|
||||
addTime("完成时间", metadata.taskCompletedAt);
|
||||
addDuration(metadata.taskDurationMs);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -188,7 +188,9 @@ export function videoResolutionLabel(value: string) {
|
||||
export function videoSizeLabel(value: string) {
|
||||
const ratio = normalizeSeedanceRatio(value);
|
||||
if (value === "adaptive" || value === "auto") return "自适应";
|
||||
if (ratio === value) return seedanceRatioOptions.find((item) => item.value === ratio)?.label || ratio;
|
||||
// The compact summary must mirror the selected value (for example 16:9),
|
||||
// while the settings panel can still use semantic labels such as 横屏.
|
||||
if (ratio === value) return ratio;
|
||||
const size = normalizeVideoSizeValue(value);
|
||||
return sizeOptions.find((item) => item.value === size)?.label || size;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user