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 be1eb2a9367573173a2aa5c59d773c77eeae733f..47c7854c4547aa4222b5708f7e2f032919e0d6ae 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.forEach((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 4685f4b37b35329aa7034a5af022b18f117e59e4..5058f014df830ce7f8c0d2dea09eb8b0eca1611a 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
@@ -57,7 +57,8 @@ const IoItem = ({ id }: IoItemProps) => {
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"
+ "group-hover:text-blue-800 dark:group-hover:text-blue-300",
+ "copy cursor-pointer"
)
const contentStyle = joinCls(
@@ -66,7 +67,8 @@ const IoItem = ({ id }: IoItemProps) => {
"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"
+ "dark:group-hover:from-purple-800 dark:group-hover:to-violet-800",
+ "copy cursor-pointer"
)
return
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 aaee6f9a4ab90ad5283e876d05ef4d963ba9a1ab..360b69e728b2a1a2f65857636f7aefb90d5282af 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
@@ -30,7 +30,7 @@ const MetadataItem = ({ i18nKey, value, tooltip }: MetadataItemProps) => {
const valueLabel =
{value}
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 464585055a849ef8019ada88dd69cd848f772273..06f42254c8678af751447e1f2af6a1fe3abe975f 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
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { useI18n } from "hooks"
+import { useCopyToClipboard, useI18n, useLanguage } from "hooks"
import { useAtomValue } from "jotai"
import { joinCls } from "libs"
import {
@@ -30,9 +30,11 @@ const Properties = () => {
const model = useAtomValue(modelDataAtom)
const current = useAtomValue(selectionAtom)
const t = useI18n()
+ const { copyToClipboard } = useCopyToClipboard()
const [isShow, setIsShow] = useState
(false)
const [propertyContent, setPropertyContent] = useState()
const [contextValue, setContextValue] = useState({ nodes: {}, parameters: {}, t })
+ const [appLanguage] = useLanguage()
const initProperties = () => {
if (!current || !model) {
@@ -57,9 +59,16 @@ const Properties = () => {
setIsShow(true)
}
+ const clickHandle = async (e: React.MouseEvent) => {
+ const dom = e.target as HTMLElement
+ if (dom.classList.contains('copy')) {
+ copyToClipboard(dom.innerText.trim())
+ }
+ }
+
useEffect(() => {
initProperties()
- }, [model, current, t])
+ }, [model, current, appLanguage.code])
return
{
"glass-effect border border-white/20 dark:border-slate-700/50",
)}
style={{ display: isShow ? '' : 'none' }}
+ onClick={clickHandle}
>
{propertyContent}
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Project.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Project.tsx
index e3459551c44bf09743bff44e2b15c2c393f50658..3169fc4b926511123bdc5415e746ef971be533dc 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Project.tsx
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Project.tsx
@@ -129,6 +129,9 @@ export const RecentProj = ({ path, toggle }: ProjectLineProps) => {
toggle(recentProjectList[0])
}
}
+ const getFileType = (path:string)=> {
+ return path.split(".").pop()?.[0].toUpperCase() ?? "A"
+ }
return
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Toolbar.tsx b/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Toolbar.tsx
index 7b2bec969f94ec981a13a092db4a0b72632d148d..907bb6a59f09846054abf5333f18632a99e520ac 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Toolbar.tsx
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/features/Toolbar.tsx
@@ -27,9 +27,11 @@ export const Toolbar = () =>
-
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/index.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/index.ts
index 8d1aabb61bf3f28d4e48bb5a8a51b9062fbe4012..d2b9d6922588b02a5517bdcfbfad8c0dfbcc65b2 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/index.ts
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/index.ts
@@ -20,3 +20,4 @@ export * from "./useNewPathForLayout"
export * from "./useAnimatedTranslate"
export * from "./useCleanup"
export * from "./useMessage"
+export * from "./useCopyToClipboard"
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useCopyToClipboard.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useCopyToClipboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1b5d013e7ab2a8fd388e3a72796ebe9a2df958e9
--- /dev/null
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useCopyToClipboard.ts
@@ -0,0 +1,38 @@
+// 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 { message } from "antd"
+import { useI18n } from "./useI18n"
+
+export const useCopyToClipboard = () => {
+ const t = useI18n()
+
+ const copyToClipboard = async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(typeof text === 'string' ? text : JSON.stringify(text))
+ message.success({
+ content: t('copy.success'),
+ className: 'dark:text-white dark:[&_div]:!bg-dark-light',
+ })
+ } catch (err) {
+ message.error({
+ content: `${t('copy.error')}: ${String(err)}`,
+ className: 'dark:text-white dark:[&_div]:!bg-dark-light',
+ })
+ }
+ }
+
+ return { copyToClipboard }
+}
\ No newline at end of file
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useNewPathForLayout.ts b/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useNewPathForLayout.ts
index 6c33986aef60f0f97c8be0bedb48e3b3e3d9139e..016ea2e31760d8908e297c28e49a768fb2e55c15 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useNewPathForLayout.ts
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/hooks/useNewPathForLayout.ts
@@ -54,6 +54,12 @@ export const useNewPathForLayout = (): LayoutNewPath => {
const start = performance.now()
setLoading(true)
+ history.clear()
+ setSubgraphs({})
+ setCurrentGraphAtom({ children: [], name: '', paths: [] })
+ setAllGraphAtom({ children: [], name: '', paths: [] })
+ setDynamicVisible(false)
+ setFsgsVisible(false)
let res
try {
res = await invoke
("layout_bin", { path })
@@ -69,12 +75,9 @@ export const useNewPathForLayout = (): LayoutNewPath => {
})
if (res) {
- history.clear()
- setSubgraphs({})
setCurrentGraphAtom(res)
setAllGraphAtom(res)
- setDynamicVisible(false)
- setFsgsVisible(false)
+
if (translate.x !== 0 || translate.y !== 0) setTranslate({
x: 0,
y: 0
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/en.json b/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/en.json
index dbe0df9df3250b1774e78a60e897e836b6dff335..e0055d72c437fd5d50c51e90ec9f95a7e4fb183a 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/en.json
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/en.json
@@ -50,5 +50,9 @@
"totalMteTime": "Total Mte Time(us)",
"importCSV": "Import CSV",
"allStructures": "All Structures"
+ },
+ "copy": {
+ "success": "Copy Successful!",
+ "error": "Copy Failed"
}
}
\ No newline at end of file
diff --git a/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/zh-CN.json b/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/zh-CN.json
index 6300c8d1835b3372fb3e71360940119c78f26f0e..06b327d8d9913f2bd5c1c0507230405da50f4f5c 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/zh-CN.json
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/locales/zh-CN.json
@@ -50,5 +50,9 @@
"totalMteTime": "Mte总时间(us)",
"importCSV": "导入CSV",
"allStructures": "全部结构"
+ },
+ "copy": {
+ "success": "复制成功!",
+ "error": "复制失败"
}
}
\ No newline at end of file
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 0ae68c8182a62d948076d096180657fb6d92a42c..8d0a539767a3b0f3cd114f855f1e0706063247c9 100644
--- a/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx
+++ b/plugins/mindstudio-insight-plugins/ModelVis/app/src/ui/VirtualScroll.tsx
@@ -36,30 +36,37 @@ type ItemSize = {
measured: boolean // 是否已测量
}
-const ESTIMATED_ITEM_HEIGHT = 20;
+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 [totalHeight, setTotalHeight] = useState(0)
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;
+ const getOffset = useCallback((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;
- }
+ return offset
+ }, [])
// 总高度 = 所有 item 高度之和
- const getTotalHeight = useCallback((): number => {
- return getOffset(items.length)
+ const updateTotalHeight = useCallback(() => {
+ const newTotal = getOffset(items.length)
+ setTotalHeight(prev => {
+ if (Math.abs(newTotal - prev) > 0.1) {
+ return newTotal
+ }
+ return prev
+ })
}, [items, getOffset])
// 二分查找:找到第一个 `offset >= target` 的索引
@@ -67,8 +74,8 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
let start = 0
let end = items.length
while (start < end) {
- const mid = Math.floor((start + end) / 2);
- const midOffset = getOffset(mid);
+ const mid = Math.floor((start + end) / 2)
+ const midOffset = getOffset(mid)
if (midOffset < target) {
start = mid + 1
} else {
@@ -76,24 +83,26 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
}
}
return start
- }, [items, getOffset])
+ },
+ [items, getOffset]
+ )
// 测量指定索引的元素高度
- const measureItem = (index: number) => {
- if (index >= sizesRef.current.length) {
+ const measureItem = useCallback((index: number): boolean => {
+ if (index < 0 || index >= items.length) {
return false
}
const ele = itemElementsRef.current[index]
if (ele) {
const actualHeight = ele.offsetHeight
const current = sizesRef.current[index]
- if (!current.measured || current.size !== actualHeight) { // 高度变化
+ if (!current.measured || Math.abs(current.size - actualHeight) > 0.1) {
sizesRef.current[index] = { size: actualHeight, measured: true }
return true
}
}
return false
- }
+ }, [items])
// 更新可见范围
const updateVisibleRange = useCallback(() => {
@@ -105,19 +114,15 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
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 })
+ setVisibleRange(prev => (prev.start !== start || prev.end !== end ? { start, end } : prev))
}, [items, buffer, findIndex])
// 滚动处理
const handleScroll = useCallback(() => {
- requestAnimationFrame(() => {
- updateVisibleRange()
- })
+ requestAnimationFrame(updateVisibleRange)
}, [updateVisibleRange])
// 初始渲染 & 滚动监听
@@ -135,7 +140,8 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
// 如果有高度更新,重新计算可见范围
if (didUpdate) {
- updateVisibleRange()
+ updateTotalHeight()
+ requestAnimationFrame(updateVisibleRange)
}
container.addEventListener('scroll', handleScroll, { passive: true })
@@ -145,30 +151,21 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
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
- }, [])
+ }, [visibleRange, handleScroll, measureItem, updateTotalHeight, updateVisibleRange])
+ // 监听父容器尺寸变化
useLayoutEffect(() => {
const parent = containerRef.current?.parentElement
if (parent === undefined || parent === null) {
return
}
- const resizeObserver = new ResizeObserver((entries) => {
- for (let entry of entries) {
+ const resizeObserver = new ResizeObserver(entries => {
+ for (const entry of entries) {
const newHeight = entry.contentRect.height
- if (newHeight !== height) {
+ if (Math.abs(newHeight - height) > 1) {
setHeight(newHeight)
- requestAnimationFrame(() => {
- updateVisibleRange()
- })
+ requestAnimationFrame(updateVisibleRange)
}
}
})
@@ -183,15 +180,27 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
}
}, [updateVisibleRange])
+ // items 变化时重置测量数据
useEffect(() => {
- const container = containerRef.current
- if (!container) {
- return
- }
+ // 重置尺寸测量
+ sizesRef.current = items.map(() => ({ size: ESTIMATED_ITEM_HEIGHT, measured: false }))
+ updateTotalHeight()
+
+ // 重置可见范围
+ setVisibleRange({
+ start: 0,
+ end: Math.min(items.length, Math.ceil(height / ESTIMATED_ITEM_HEIGHT) + buffer * 2),
+ })
// 滚动到顶部
- container.scrollTop = 0
- }, [items])
+ const container = containerRef.current
+ if (container) container.scrollTop = 0
+ }, [items, height, buffer, updateTotalHeight])
+
+ // 设置 ref
+ const setItemRef = useCallback((el: HTMLDivElement | null, index: number) => {
+ itemElementsRef.current[index] = el
+ }, [])
const { start, end } = visibleRange
const visibleItems = items.slice(start, end)
@@ -200,37 +209,34 @@ export const VirtualScroll = ({ items, containerHeight, buffer = 10, className =
return (
{/* 占位,撑起总高度 */}
-
+
{/* 可见项容器 */}
{visibleItems.map((item, index) => {
- const globalIndex = start + index;
+ const globalIndex = start + index
return (
setItemRef(el, globalIndex)}
- style={{
- contain: 'layout',
- overflowAnchor: 'none',
- }}
+ style={{ contain: "layout" }}
>
{item}
- );
+ )
})}