From ea093df5eaebfe3ddcd0a7a9e48fc143382ed318 Mon Sep 17 00:00:00 2001 From: heiheihei <1395202740@qq.com> Date: Tue, 19 Aug 2025 20:00:49 +0800 Subject: [PATCH 1/4] =?UTF-8?q?Properties=E6=A8=A1=E5=9D=97=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=95=B4=E6=94=B9=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ModelVis/app/src/App.tsx | 2 +- .../Node/Attribute/StringLikeArray.tsx | 30 --- .../Properties/Node/Attribute/TensorVals.tsx | 29 --- .../Node/Attribute/TensorsTuple.tsx | 40 --- .../Properties/Node/Attribute/index.tsx | 44 ---- .../Attribute.tsx} | 81 +++++- .../attributes.d.ts} | 7 + .../{Attributes.tsx => Attributes/index.tsx} | 30 +-- .../Properties/Node/Initializer.tsx | 25 +- .../Properties/Node/InputOutput.tsx | 74 +++++- .../ModelStructure/Properties/Node/IoItem.tsx | 75 ------ .../Properties/Node/Metadata.tsx | 2 +- .../ModelStructure/Properties/Node/index.tsx | 27 +- .../src/ModelStructure/Properties/index.tsx | 12 +- .../ModelVis/app/src/ModelStructure/index.tsx | 15 +- .../ModelVis/app/src/src-worker/rerender.ts | 2 +- .../ModelVis/app/src/ui/VirtualScroll.tsx | 238 ++++++++++++++++++ .../ModelVis/app/src/ui/index.ts | 1 + 18 files changed, 421 insertions(+), 313 deletions(-) delete mode 100644 plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLikeArray.tsx delete mode 100644 plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorVals.tsx delete mode 100644 plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorsTuple.tsx delete mode 100644 plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/index.tsx rename plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/{Attribute/StringLike.tsx => Attributes/Attribute.tsx} (50%) rename plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/{Attribute/attribute.d.ts => Attributes/attributes.d.ts} (87%) rename plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/{Attributes.tsx => Attributes/index.tsx} (55%) delete mode 100644 plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/IoItem.tsx create mode 100644 plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx index 54ee45b6a3..d0d61ed1ad 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx @@ -35,7 +35,7 @@ const App = () => { const [nodesEdges, setNodesEdges] = useAtom(nodesEdgesAtom) const currentGraph = useAtomValue(currentGraphAtom) const subgraphs = useAtomValue(subgraphesAtom) - + useWorkerMessage() useEffect(() => { diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLikeArray.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLikeArray.tsx deleted file mode 100644 index e8fbdfb42b..0000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLikeArray.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const StringLikeArray = ({ name, value }: AttrProps<"string-like-array">) => -
-
- {name} -
- -
- {value.map((t, i) =>
- {t} -
)} -
-
- -export default StringLikeArray diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorVals.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorVals.tsx deleted file mode 100644 index 0db027196d..0000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorVals.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const TensorVals = ({ name, value }: AttrProps<"tensor-vals">) =>
-
- {name} -
- -
- {value.map((t, i) =>
- {t} -
)} -
-
- -export default TensorVals diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorsTuple.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorsTuple.tsx deleted file mode 100644 index 785dfd80d9..0000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/TensorsTuple.tsx +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const TensorsTuple = ({ name, value }: AttrProps<"tensors-tuple">) =>
-
- {name} -
- -
- {value.map((tensors, i) => -
-
- - -
- - {tensors.map((tensor, j) => -
-
-
- {tensor} -
-
)} -
)} -
-
- -export default TensorsTuple diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/index.tsx deleted file mode 100644 index 0f96c5b882..0000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import StringLike from "./StringLike" -import StringLikeArray from "./StringLikeArray" -import TensorVals from "./TensorVals" -import TensorsTuple from "./TensorsTuple" - -type Props = { - name: string - attr: AttrValue -} - -const Attribute = ({ name, attr }: Props) => { - const { type, value } = attr - - switch (type) { - case "string-like": - case "tensor-val": - return - case "string-like-array": - return - case "tensor-vals": - return - case "tensors-tuple": - return - default: - break - } -} - -export default Attribute diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLike.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx similarity index 50% rename from plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLike.tsx rename to plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx index cba36f63cf..3b6e0042e6 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/StringLike.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx @@ -16,14 +16,30 @@ import { joinCls } from "libs" import { Tooltip } from "ui" -type Props = AttrProps<"string-like" | "tensor-val"> +export const Attribute = ({ name, attr }: Props) => { + const { type, value } = attr -const StringLike = ({name, value}: Props) => { + switch (type) { + case "string-like": + case "tensor-val": + return + case "string-like-array": + return + case "tensor-vals": + return + case "tensors-tuple": + return + default: + break + } +} + +const StringLike = ({ name, value }: StringLikeProps) => { const containerStyle = joinCls( "group relative overflow-hidden rounded-xl", "bg-gradient-to-br from-blue-500/10 to-purple-600/10 backdrop-blur-sm", "border border-blue-500/20 dark:border-purple-600/30", - "px-2 py-1.5" + "px-2 py-1.5 my-[5px]" ) const nameLabelStyle = joinCls( @@ -48,9 +64,9 @@ const StringLike = ({name, value}: Props) => { content={value || "EMPTY_STR"} className="w-full truncate" > - - {value || "EMPTY_STR"} - + + {value || "EMPTY_STR"} +
@@ -58,8 +74,57 @@ const StringLike = ({name, value}: Props) => { "absolute top-0 right-0 h-full w-1 opacity-0", "bg-gradient-to-b from-blue-500/60 to-purple-600/60", "transition-opacity duration-300 group-hover:opacity-100" - )}/> + )} /> } -export default StringLike +const StringLikeArray = ({ name, value }: AttrProps<"string-like-array">) => +
+
+ {name} +
+ +
+ {value.map((t, i) =>
+ {t} +
)} +
+
+ +const TensorVals = ({ name, value }: AttrProps<"tensor-vals">) =>
+
+ {name} +
+ +
+ {value.map((t, i) =>
+ {t} +
)} +
+
+ +const TensorsTuple = ({ name, value }: AttrProps<"tensors-tuple">) =>
+
+ {name} +
+ +
+ {value.map((tensors, i) => +
+
+ - +
+ + {tensors.map((tensor, j) => +
+
+
+ {tensor} +
+
)} +
)} +
+
\ No newline at end of file diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/attribute.d.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/attributes.d.ts similarity index 87% rename from plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/attribute.d.ts rename to plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/attributes.d.ts index 1bba304c68..3d4e5a8636 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attribute/attribute.d.ts +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/attributes.d.ts @@ -21,3 +21,10 @@ type AttrProps = { name: string value: AttrMap[T]["value"] } + +type Props = { + name: string + attr: AttrValue +} + +type StringLikeProps = AttrProps<"string-like" | "tensor-val"> diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx similarity index 55% rename from plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes.tsx rename to plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx index e40dea744b..4ffaaf48bc 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx @@ -14,28 +14,16 @@ // limitations under the License. import { useContext } from "react" -import Divider from "../Divider" -import PropertiesContext from "../context" -import Attribute from "./Attribute" +import Divider from "../../Divider" +import PropertiesContext from "../../context" +import { Attribute } from "./Attribute" -type Props = { - attributes: [string, AttrValue][] -} - -const Attributes = ({ attributes }: Props) => { +export const getAttributes = (attributes: [string, AttrValue][], renderList: JSX.Element[]) => { const { t } = useContext(PropertiesContext) - if (!attributes.length) return null - - return <> -

{t("panel.attrs")}

-
- {attributes.map(([name, attr]) => )} -
- - - + if (!attributes.length) { + return + } + renderList.push(,

{t("panel.attrs")}

) + attributes.map(([name, attr]) => renderList.push()) } - -export default Attributes diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx index e1c9833d3d..ecba958ba6 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx @@ -15,23 +15,10 @@ import Divider from "../Divider" -type Props = { - data?: Tensor[] +export const getInitializer = (data: Tensor[], renderList: JSX.Element[]) => { + if (!data) { + return + } + renderList.push(,
Initializer
) + data.map((t, i) => renderList.push(
【{t.category}】{t.repr}
)) } - -const Initializer = ({data}: Props) => { - if (!data) return null - - return <> -
Initializer
-
- {data.map((t, i) =>
- 【{t.category}】{t.repr} -
)} -
- - - -} - -export default Initializer diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx index 4f21fbb9eb..b806ed6aba 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx @@ -13,30 +13,80 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { joinCls } from "libs" +import { Tooltip } from "ui" import { useContext } from "react" import PropertiesContext from "../context" -import IoItem from "./IoItem" +import Divider from "../Divider" type Props = { i18key: string data: string[] } -const InputOutput = ({ i18key, data }: Props) => { +type IoItemProps = { + id: string +} + +export const getInputOutput = ({ i18key, data }: Props, renderList: JSX.Element[]) => { const { t } = useContext(PropertiesContext) const processed = data.filter(t => t.length) - if (!processed.length) return null + if (!processed.length) { + return + } + renderList.push(,

{t(i18key)}

) + data.map((i) => renderList.push()) +} + + +const IoItem = ({ id }: IoItemProps) => { + const { nodes, parameters } = useContext(PropertiesContext) + + const content = nodes[id]?.opType ?? parameters[id] - return <> -

{t(i18key)}

-
-
- {data.map(i => )} -
+ const containerStyle = joinCls( + "group relative overflow-hidden rounded-xl my-[5px]", + "bg-gradient-to-br from-blue-500/10 to-purple-600/10 backdrop-blur-sm", + "border border-blue-500/20 dark:border-purple-600/30", + "px-2 py-1.5 transition-all duration-200 hover:shadow-md", + "hover:bg-gradient-to-br hover:from-blue-500/15 hover:to-purple-600/15" + ) + + const idStyle = joinCls( + "font-mono text-xs text-blue-600 dark:text-blue-400", + "truncate max-w-[140px] transition-colors", + "group-hover:text-blue-800 dark:group-hover:text-blue-300" + ) + + const contentStyle = joinCls( + "font-mono text-xs text-purple-600 dark:text-purple-400", + "truncate max-w-[160px] px-2 py-0.5 rounded", + "bg-gradient-to-r from-purple-200/70 to-violet-200/70", + "dark:from-purple-900/40 dark:to-violet-900/40", + "group-hover:from-purple-300 group-hover:to-violet-300", + "dark:group-hover:from-purple-800 dark:group-hover:to-violet-800" + ) + + return
+
+ + {id} + + {content && ( + + {content} + + )}
- -} -export default InputOutput +
+
+} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/IoItem.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/IoItem.tsx deleted file mode 100644 index b55da59907..0000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/IoItem.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { joinCls } from "libs" -import { useContext } from "react" -import { Tooltip } from "ui" -import PropertiesContext from "../context" - -type Props = { - id: string -} - -const IoItem = ({id}: Props) => { - const {nodes, parameters} = useContext(PropertiesContext) - - const content = nodes[id]?.opType ?? parameters[id] - - const containerStyle = joinCls( - "group relative overflow-hidden rounded-xl", - "bg-gradient-to-br from-blue-500/10 to-purple-600/10 backdrop-blur-sm", - "border border-blue-500/20 dark:border-purple-600/30", - "px-2 py-1.5 transition-all duration-200 hover:shadow-md", - "hover:bg-gradient-to-br hover:from-blue-500/15 hover:to-purple-600/15" - ) - - const idStyle = joinCls( - "font-mono text-xs text-blue-600 dark:text-blue-400", - "truncate max-w-[140px] transition-colors", - "group-hover:text-blue-800 dark:group-hover:text-blue-300" - ) - - const contentStyle = joinCls( - "font-mono text-xs text-purple-600 dark:text-purple-400", - "truncate max-w-[160px] px-2 py-0.5 rounded", - "bg-gradient-to-r from-purple-200/70 to-violet-200/70", - "dark:from-purple-900/40 dark:to-violet-900/40", - "group-hover:from-purple-300 group-hover:to-violet-300", - "dark:group-hover:from-purple-800 dark:group-hover:to-violet-800" - ) - - return
-
- - {id} - - {content && ( - - {content} - - )} -
- -
-
-} - -export default IoItem diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx index 4054a01479..1a44afa55f 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx @@ -21,7 +21,7 @@ import PropertiesContext from "../context" type MetadataItemProps = { i18nKey: string value: string - tooltip: boolean + tooltip?: boolean } const MetadataItem = ({i18nKey, value, tooltip}: MetadataItemProps) => { diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/index.tsx index 4d7e84058d..622422729a 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/index.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/index.tsx @@ -13,11 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import Divider from "../Divider" -import Attributes from "./Attributes" -import Initializer from "./Initializer" -import InputOutput from "./InputOutput" +import { getAttributes } from "./Attributes" +import { getInitializer } from "./Initializer" +import { getInputOutput } from "./InputOutput" import Metadata from "./Metadata" +import { VirtualScroll } from "ui" type Props = { node: ModelNode @@ -27,18 +27,17 @@ const NodeProperty = ({ node }: Props) => { if (!node) return null const attributes = Object.entries(node.attributes ?? {}) + const renderList: JSX.Element[] = [] - const ioDivider = node.input.length > 0 && node.output.length > 0 + getAttributes(attributes, renderList) + getInitializer(node.tensors, renderList) + getInputOutput({ i18key: "panel.inputs", data: node.input }, renderList) + getInputOutput({ i18key: "panel.outputs", data: node.output }, renderList) - return <> - - - - - - {ioDivider && } - - + return } export default NodeProperty diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/index.tsx index a9fc53e65a..fcfc1494ee 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/index.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/index.tsx @@ -50,17 +50,7 @@ const Properties = () => { "glass-effect border border-white/20 dark:border-slate-700/50", )}>
-
-
- {content} -
-
+ {content}
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/index.tsx index 8ca4389cc5..59bf2d54a7 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/index.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/index.tsx @@ -223,20 +223,21 @@ const GraphPath = () => {
} -const ModelStructure = () => +const ModelStructure = () => <> {props => ( <> - - - - - - )} + + + + + + + export default ModelStructure diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts index 63306205f0..f9ade50353 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts @@ -24,7 +24,7 @@ import { Zoom } from "./worker" -const rerender = () => { +const rerender = async () => { Ctx.clearRect(0, 0, Width, Height) Ctx.resetTransform() diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx new file mode 100644 index 0000000000..bf301a1979 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx @@ -0,0 +1,238 @@ +// Copyright (c) 2025, Huawei Technologies Co., Ltd. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { clsx } from 'libs' +import { + useState, + useLayoutEffect, + useRef, + ReactNode, + useCallback, + useEffect, +} from 'react' + +type VirtualScrollProps = { + items: ReactNode[] + containerHeight?: number + className?: string + buffer?: number // 缓冲数量 +} + +// 每个项的尺寸信息 +type ItemSize = { + size: number // 高度 + measured: boolean // 是否已测量 +} + +const ESTIMATED_ITEM_HEIGHT = 20; + +export const VirtualScroll = ({ items, containerHeight, buffer = 10, className = '' }: VirtualScrollProps) => { + const containerRef = useRef(null) + const itemElementsRef = useRef<(HTMLDivElement | null)[]>([]) + const sizesRef = useRef(items.map(() => ({ size: ESTIMATED_ITEM_HEIGHT, measured: false }))) + const [height, setHeight] = useState(containerHeight || 400) + const [visibleRange, setVisibleRange] = useState({ + start: 0, + end: Math.min(items.length, Math.ceil(height / ESTIMATED_ITEM_HEIGHT) + buffer * 2) + }) + + // 获取累计偏移量(用于 transform 和定位) + const getOffset = (index: number): number => { + let offset = 0; + for (let i = 0; i < index; i++) { + offset += sizesRef.current[i]?.size || ESTIMATED_ITEM_HEIGHT; + } + return offset; + } + + // 总高度 = 所有 item 高度之和 + const getTotalHeight = useCallback((): number => { + return getOffset(items.length) + }, [items, getOffset]) + + // 二分查找:找到第一个 `offset >= target` 的索引 + const findIndex = useCallback((target: number): number => { + let start = 0 + let end = items.length + while (start < end) { + const mid = Math.floor((start + end) / 2); + const midOffset = getOffset(mid); + if (midOffset < target) { + start = mid + 1 + } else { + end = mid + } + } + return start + }, [items, getOffset]) + + // 测量指定索引的元素高度 + const measureItem = (index: number) => { + const ele = itemElementsRef.current[index] + if (ele) { + const actualHeight = ele.offsetHeight + const current = sizesRef.current[index] + if (!current.measured || current.size !== actualHeight) { // 高度变化 + sizesRef.current[index] = { size: actualHeight, measured: true } + return true + } + } + return false + } + + // 更新可见范围 + const updateVisibleRange = useCallback(() => { + const container = containerRef.current + if (container === null || items.length === 0) { + return + } + + const scrollTop = container.scrollTop + const containerHeight = container.clientHeight + + // 找到第一个在视口内的项 + const start = Math.max(0, findIndex(scrollTop) - buffer) + // 找到最后一个在视口内的项 + const end = Math.min(items.length, findIndex(scrollTop + containerHeight) + buffer) + + setVisibleRange({ start, end }) + }, [items, buffer, findIndex]) + + // 滚动处理 + const handleScroll = useCallback(() => { + requestAnimationFrame(() => { + updateVisibleRange() + }) + }, [updateVisibleRange]) + + // 初始渲染 & 滚动监听 + useLayoutEffect(() => { + const container = containerRef.current + if (!container) return + + // 初始测量所有已渲染的项 + let didUpdate = false + for (let i = visibleRange.start; i < visibleRange.end; i++) { + if (measureItem(i)) { + didUpdate = true + } + } + + // 如果有高度更新,重新计算可见范围 + if (didUpdate) { + updateVisibleRange() + } + + container.addEventListener('scroll', handleScroll, { passive: true }) + // 强制触发一次 layout + handleScroll() + + return () => { + container.removeEventListener('scroll', handleScroll) + } + }, [visibleRange.start, visibleRange.end, handleScroll, updateVisibleRange]) + + // 动态设置 ref 并测量 + const setItemRef = useCallback((el: HTMLDivElement | null, index: number) => { + if (el === null) { + return + } + itemElementsRef.current[index] = el + // 立即尝试测量 + if (measureItem(index)) { + // 可以考虑触发一次 re-render 或 updateVisibleRange + } + }, []) + useLayoutEffect(() => { + const parent = containerRef.current?.parentElement + if (parent === undefined || parent === null) { + return + } + + const resizeObserver = new ResizeObserver((entries) => { + for (let entry of entries) { + const newHeight = entry.contentRect.height + if (newHeight !== height) { + setHeight(newHeight) + requestAnimationFrame(() => { + updateVisibleRange() + }) + } + } + }) + + resizeObserver.observe(parent) + + // 初始设置 + setHeight(parent.clientHeight) + + return () => { + resizeObserver.disconnect() + } + }, [updateVisibleRange]) + + useEffect(() => { + const container = containerRef.current + if (!container) { + return + } + + // 滚动到顶部 + container.scrollTop = 0 + }, [items]) + + const { start, end } = visibleRange + const visibleItems = items.slice(start, end) + const startOffset = getOffset(start) + + return (
+ {/* 占位,撑起总高度 */} +
+ {/* 可见项容器 */} +
+ {visibleItems.map((item, index) => { + const globalIndex = start + index; + return ( +
setItemRef(el, globalIndex)} + style={{ + contain: 'layout', + overflowAnchor: 'none', + }} + > + {item} +
+ ); + })} +
+
+
) +} \ No newline at end of file diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/index.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/index.ts index f7ccca2cb7..148615a5e0 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/index.ts +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/index.ts @@ -16,3 +16,4 @@ export * from "./Tooltip" export * from "./Switch" export * from "./Loading" +export * from "./VirtualScroll" -- Gitee From e7c4c48b7bce51beb6a1fb2c6168e15b9dd9d361 Mon Sep 17 00:00:00 2001 From: heiheihei <1395202740@qq.com> Date: Tue, 19 Aug 2025 20:51:48 +0800 Subject: [PATCH 2/4] =?UTF-8?q?Properties=E6=A8=A1=E5=9D=97=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=95=B4=E6=94=B9=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=BB=9A=E5=8A=A8,=E6=BC=8F=E6=94=B9?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ModelVis/app/src/App.tsx | 12 ++++++------ .../ModelVis/app/src/ModelStructure/Fsg/index.tsx | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx index d0d61ed1ad..7853b34d23 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/App.tsx @@ -35,7 +35,7 @@ const App = () => { const [nodesEdges, setNodesEdges] = useAtom(nodesEdgesAtom) const currentGraph = useAtomValue(currentGraphAtom) const subgraphs = useAtomValue(subgraphesAtom) - + useWorkerMessage() useEffect(() => { @@ -72,16 +72,16 @@ const App = () => { }, [currentGraph, JSON.stringify(subgraphs)]); return
- - + + {modelPath ? <> - +
- +
- : + : }
} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx index 926aebb29d..5a77cfd742 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx @@ -144,7 +144,7 @@ const Fsg = () => { const containerRef = useRef(null) const [loading, setLoading] = useState(false) const [width, setWidth] = useState(690) - const [top, setTop] = useState(5) + const [top, setTop] = useState(60) const [left, setLeft] = useState(10) const [isLock, setIsLock] = useState(false) const [fsgVis, setFsgVis] = useAtom(fsgVisibleAtom) @@ -227,6 +227,9 @@ const Fsg = () => { }) setTop(oTop => { const newTop = oTop + moveY + if (newTop < 50) { + return 50 + } if (newTop > window.innerHeight - 80) { return window.innerHeight - 80 } -- Gitee From 0290749c5a658d7aa6f9a6463eef780b2db2aaee Mon Sep 17 00:00:00 2001 From: heiheihei <1395202740@qq.com> Date: Wed, 20 Aug 2025 09:22:30 +0800 Subject: [PATCH 3/4] =?UTF-8?q?Properties=E6=A8=A1=E5=9D=97=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=95=B4=E6=94=B9=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=BB=9A=E5=8A=A8,=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E7=99=BD=E5=B1=8F=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ModelVis/app/src/ui/VirtualScroll.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx index bf301a1979..433dac41b0 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx @@ -80,6 +80,9 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className = // 测量指定索引的元素高度 const measureItem = (index: number) => { + if (index >= sizesRef.current.length) { + return false + } const ele = itemElementsRef.current[index] if (ele) { const actualHeight = ele.offsetHeight -- Gitee From 8720e9b2e7760a695ddcdf19a6e490bcd16d49d9 Mon Sep 17 00:00:00 2001 From: heiheihei <1395202740@qq.com> Date: Wed, 20 Aug 2025 16:26:29 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=A3=80=E8=A7=86=E6=84=8F=E8=A7=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/ModelStructure/Fsg/Filter.tsx | 2 +- .../app/src/ModelStructure/Fsg/index.tsx | 17 ++++++++++------- .../Properties/Node/Attributes/Attribute.tsx | 8 ++++---- .../Properties/Node/Attributes/index.tsx | 2 +- .../Properties/Node/Initializer.tsx | 2 +- .../Properties/Node/InputOutput.tsx | 2 +- .../ModelStructure/Properties/Node/Metadata.tsx | 3 ++- .../ModelVis/app/src/src-worker/rerender.ts | 2 +- .../ModelVis/app/src/ui/VirtualScroll.tsx | 9 +++------ 9 files changed, 24 insertions(+), 23 deletions(-) diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/Filter.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/Filter.tsx index 5ddcb71a72..badca84eeb 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/Filter.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/Filter.tsx @@ -35,7 +35,7 @@ const Filter = (props: { const t = useI18n() const [repeatTimes, setRepeatTimes] = useState(2) const [minNodes, setMinNodes] = useState(2) - const [maxNodes, setMaxNodes] = useState(8) + const [maxNodes, setMaxNodes] = useState(3) const [loadingExportBtn, setLoadingExportBtn] = useState(false) const [loadingQueryBtn, setLoadingQueryBtn] = useState(false) const visible = useAtomValue(fsgVisibleAtom) diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx index 5a77cfd742..152b6774ca 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Fsg/index.tsx @@ -137,6 +137,9 @@ const getColumns = (t: I18nFunc): ColumnsType => { ] } +const DEFAULT_TOP = 60 +const DEFAULT_MARGIN = 80 + const Fsg = () => { const [visible, setVisible] = useAtom(fsgPanelVisibleAtom) const t = useI18n() @@ -144,7 +147,7 @@ const Fsg = () => { const containerRef = useRef(null) const [loading, setLoading] = useState(false) const [width, setWidth] = useState(690) - const [top, setTop] = useState(60) + const [top, setTop] = useState(DEFAULT_TOP) const [left, setLeft] = useState(10) const [isLock, setIsLock] = useState(false) const [fsgVis, setFsgVis] = useAtom(fsgVisibleAtom) @@ -220,18 +223,18 @@ const Fsg = () => { const moveContainer: ResizerType['callback'] = (moveX, moveY) => { setLeft(oLeft => { const newLeft = oLeft + moveX - if (newLeft > window.innerWidth - 80) { - return window.innerWidth - 80 + if (newLeft > window.innerWidth - DEFAULT_MARGIN) { + return window.innerWidth - DEFAULT_MARGIN } return newLeft > 0 ? newLeft : 0 }) setTop(oTop => { const newTop = oTop + moveY - if (newTop < 50) { - return 50 + if (newTop < DEFAULT_TOP) { + return DEFAULT_TOP } - if (newTop > window.innerHeight - 80) { - return window.innerHeight - 80 + if (newTop > window.innerHeight - DEFAULT_MARGIN) { + return window.innerHeight - DEFAULT_MARGIN } return newTop > 0 ? newTop : 0 }) diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx index 3b6e0042e6..5f277b6512 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/Attribute.tsx @@ -86,8 +86,8 @@ const StringLikeArray = ({ name, value }: AttrProps<"string-like-array">) =>
- {value.map((t, i) =>
- {t} + {value.map((str, i) =>
+ {str}
)}
@@ -99,8 +99,8 @@ const TensorVals = ({ name, value }: AttrProps<"tensor-vals">) =>
- {value.map((t, i) =>
- {t} + {value.map((Tensor, i) =>
+ {Tensor}
)}
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx index 4ffaaf48bc..1e26860240 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Attributes/index.tsx @@ -25,5 +25,5 @@ export const getAttributes = (attributes: [string, AttrValue][], renderList: JSX return } renderList.push(,

{t("panel.attrs")}

) - attributes.map(([name, attr]) => renderList.push()) + attributes.forEach(([name, attr]) => renderList.push()) } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx index ecba958ba6..be1eb2a936 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Initializer.tsx @@ -20,5 +20,5 @@ export const getInitializer = (data: Tensor[], renderList: JSX.Element[]) => { return } renderList.push(,
Initializer
) - data.map((t, i) => renderList.push(
【{t.category}】{t.repr}
)) + data.forEach((t, i) => renderList.push(
【{t.category}】{t.repr}
)) } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx index b806ed6aba..4685f4b37b 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/InputOutput.tsx @@ -37,7 +37,7 @@ export const getInputOutput = ({ i18key, data }: Props, renderList: JSX.Element[ return } renderList.push(,

{t(i18key)}

) - data.map((i) => renderList.push()) + data.forEach((i) => renderList.push()) } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx index 1a44afa55f..0d0260cda6 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ModelStructure/Properties/Node/Metadata.tsx @@ -29,7 +29,8 @@ const MetadataItem = ({i18nKey, value, tooltip}: MetadataItemProps) => { const valueLabel =
{value}
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts index f9ade50353..63306205f0 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/src-worker/rerender.ts @@ -24,7 +24,7 @@ import { Zoom } from "./worker" -const rerender = async () => { +const rerender = () => { Ctx.clearRect(0, 0, Width, Height) Ctx.resetTransform() diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx index 433dac41b0..0ae68c8182 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx +++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx @@ -42,7 +42,7 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className = const containerRef = useRef(null) const itemElementsRef = useRef<(HTMLDivElement | null)[]>([]) const sizesRef = useRef(items.map(() => ({ size: ESTIMATED_ITEM_HEIGHT, measured: false }))) - const [height, setHeight] = useState(containerHeight || 400) + const [height, setHeight] = useState(containerHeight ?? 400) const [visibleRange, setVisibleRange] = useState({ start: 0, end: Math.min(items.length, Math.ceil(height / ESTIMATED_ITEM_HEIGHT) + buffer * 2) @@ -52,7 +52,7 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className = const getOffset = (index: number): number => { let offset = 0; for (let i = 0; i < index; i++) { - offset += sizesRef.current[i]?.size || ESTIMATED_ITEM_HEIGHT; + offset += sizesRef.current[i]?.size ?? ESTIMATED_ITEM_HEIGHT; } return offset; } @@ -153,11 +153,8 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className = return } itemElementsRef.current[index] = el - // 立即尝试测量 - if (measureItem(index)) { - // 可以考虑触发一次 re-render 或 updateVisibleRange - } }, []) + useLayoutEffect(() => { const parent = containerRef.current?.parentElement if (parent === undefined || parent === null) { -- Gitee