diff --git a/frontend/app/components/common/Icon.tsx b/frontend/app/components/common/Icon.tsx index 048cc3b..6e4a11d 100644 --- a/frontend/app/components/common/Icon.tsx +++ b/frontend/app/components/common/Icon.tsx @@ -23,7 +23,8 @@ type IconName = | "menu" | "external" | "release" - | "play"; + | "play" + | "grip-vertical"; type IconProps = { name: IconName; @@ -181,6 +182,16 @@ export default function Icon({ ), play: , + "grip-vertical": ( + <> + + + + + + + + ), }; return ( diff --git a/frontend/app/features/schedules/ArtifactList.tsx b/frontend/app/features/schedules/ArtifactList.tsx index 0399e57..09279f6 100644 --- a/frontend/app/features/schedules/ArtifactList.tsx +++ b/frontend/app/features/schedules/ArtifactList.tsx @@ -1,4 +1,4 @@ -import { memo } from "react"; +import { memo, useMemo, useState } from "react"; import { type ScheduleArtifact } from "../../services/api"; import Icon from "../../components/common/Icon"; @@ -29,10 +29,41 @@ function ArtifactListImpl({ )) : artifacts; + const [expandedScriptNames, setExpandedScriptNames] = useState>(new Set()); + + const groups = useMemo(() => { + const map = new Map(); + for (const item of filtered) { + const list = map.get(item.script_name); + if (list) list.push(item); + else map.set(item.script_name, [item]); + } + // 组内按 created_at desc,version_label desc 兜底 + for (const list of map.values()) { + list.sort((a, b) => { + if (a.created_at !== b.created_at) return a.created_at < b.created_at ? 1 : -1; + return a.version_label < b.version_label ? 1 : -1; + }); + } + return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b)); + }, [filtered]); + + function toggle(scriptName: string) { + setExpandedScriptNames((prev) => { + const next = new Set(prev); + if (next.has(scriptName)) next.delete(scriptName); + else next.add(scriptName); + return next; + }); + } + return (
-
稳定版本脚本{artifacts.length}
+
+ 稳定版本脚本 + {artifacts.length} +
-

拖动卡片到画布,节点将保存 versions_id

- {filtered.length ? filtered.map((artifact) => ( -
{ - event.dataTransfer.effectAllowed = "copy"; - event.dataTransfer.setData( - "application/x-model-platform-version", - artifact.versions_id, - ); - event.dataTransfer.setData("text/plain", artifact.versions_id); - }} - onContextMenu={(event) => onContextMenu(event, artifact)} - onDoubleClick={() => onDoubleClick(artifact)} - > - - - - - {artifact.script_name} - - {artifact.version_label} · {shortHash(artifact.content_hash)} - - -
- )) : ( + {groups.length === 0 ? (

暂无稳定版本,请先在"构建脚本"中发布

+ ) : ( + groups.map(([scriptName, items]) => { + const isExpanded = expandedScriptNames.has(scriptName); + const scriptType = items[0].script_type; + const iconName = scriptType === "notebook" ? "notebook" : "python"; + return ( +
+ + {isExpanded && ( +
+ {items.map((artifact) => ( +
{ + event.dataTransfer.effectAllowed = "copy"; + event.dataTransfer.setData( + "application/x-model-platform-version", + artifact.versions_id, + ); + event.dataTransfer.setData("text/plain", artifact.versions_id); + }} + onContextMenu={(event) => onContextMenu(event, artifact)} + onDoubleClick={() => onDoubleClick(artifact)} + > + + + + + + {artifact.version_label} + + + {shortHash(artifact.content_hash)} + + + + + +
+ ))} +
+ )} +
+ ); + }) )}
+

+ 按住 + + + + 拖拽卡片到画布即可绑定节点 +

); } -export const ArtifactList = memo(ArtifactListImpl); +export const ArtifactList = memo(ArtifactListImpl); \ No newline at end of file diff --git a/frontend/app/features/schedules/SchedulePage.tsx b/frontend/app/features/schedules/SchedulePage.tsx index c30403d..7053bd8 100644 --- a/frontend/app/features/schedules/SchedulePage.tsx +++ b/frontend/app/features/schedules/SchedulePage.tsx @@ -653,14 +653,8 @@ export default function SchedulePage({ )} {contextMenu.kind === "artifact" && ( - + // 移出调度列表 入口已移除:右键 artifact 只剩查看/绑定等只读操作。 + null )} {contextMenu.kind === "node" && (