未验证 提交 4913fdbb 编写于 作者: R RotPublic 提交者: GitHub

x2paddle转换前端代码 (#1153)

* finlys

* finlys2

* finlys3
Co-authored-by: Nchenjian <chenjian26@baidu.com>
上级 c6f47c6b
......@@ -44,8 +44,10 @@
"@tippyjs/react": "4.2.5",
"@visualdl/icons": "2.2.1",
"@visualdl/netron": "2.2.1",
"@visualdl/netron2": "2.2.1",
"@visualdl/wasm": "2.2.1",
"antd": "^4.21.0",
"axios": "^1.1.3",
"bignumber.js": "9.0.1",
"classnames": "2.3.1",
"d3": "7.0.1",
......@@ -66,6 +68,7 @@
"polished": "4.1.3",
"query-string": "7.0.1",
"react": "17.0.2",
"react-activation": "^0.12.1",
"react-content-loader": "6.0.3",
"react-dnd": "14.0.3",
"react-dnd-html5-backend": "14.0.1",
......@@ -74,9 +77,10 @@
"react-i18next": "11.11.4",
"react-input-range": "1.3.0",
"react-rangeslider": "2.2.0",
"react-redux": "7.2.5",
"react-redux": "7.2.9",
"react-router-dom": "5.3.0",
"react-spinners": "0.11.0",
"react-stillness-component": "^0.9.0",
"react-table": "7.7.0",
"react-table-sticky": "1.1.3",
"react-toastify": "8.0.2",
......
......@@ -11,6 +11,7 @@
"error": "Error occurred",
"graph": "Graphs",
"graphDynamic": "dynamic",
"ToggleGraph": "X2Paddle",
"graphStatic": "static",
"high-dimensional": "High Dimensional",
"profiler":"performance analysis",
......
......@@ -53,8 +53,10 @@
"supported-model": "Supported models: ",
"Choose-model": "Choose a model",
"supported-model-list": "PaddlePaddle, ONNX, Keras, Core ML, Caffe, Caffe2, Darknet, MXNet, ncnn, TensorFlow Lite",
"supported-model-list-xpaddle": "ONNX、Caffe、Caffe2",
"upload-model": "Upload Model",
"upload-tip": "Click or Drop file here to view neural network models",
"upload-tip2": "Click or drag the file to the page to upload the model for model conversion",
"vertical": "Vertical",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
......
{
"transformation": "transformation",
"download": "download",
"convert_before": "Please convert before viewing",
"warin-info": "Please package the model description file. prototxt and parameter file. caffemodel into. tar to upload",
"warin-info2": "The model file does not support X2Paddle conversion temporarily",
"warin-info3": "Please convert before viewing",
"warin-info4": "The model has been converted, please do not click again",
"warin-info5": "Please upload the model file and convert it",
"warin-info6": "Model file has been converted, please do not click again",
"warin-info7": "Please upload the model file"
}
......@@ -11,6 +11,7 @@
"error": "发生错误",
"graph": "网络结构",
"graphDynamic": "动态",
"ToggleGraph": "X2Paddle",
"graphStatic": "静态",
"high-dimensional": "数据降维",
"profiler": "性能分析",
......
......@@ -14,6 +14,7 @@
},
"experimental-supported-model": "VisualDL实验性支持:",
"experimental-supported-model-list": "TorchScript、PyTorch、Torch、 ArmNN、BigDL、Chainer、CNTK、Deeplearning4j、MediaPipe、ML.NET、MNN、OpenVINO、Scikit-learn、Tengine、TensorFlow.js、TensorFlow",
"export-file": "导出文件",
"export-png": "PNG",
"export-svg": "SVG",
......@@ -52,8 +53,10 @@
"keep-expanded": "保持展开",
"supported-model": "VisualDL支持:",
"supported-model-list": "PaddlePaddle、ONNX、Keras、Core ML、Caffe、Caffe2、Darknet、MXNet、ncnn、TensorFlow Lite",
"supported-model-list-xpaddle": "ONNX、Caffe、Caffe2",
"upload-model": "上传模型",
"upload-tip": "点击或拖拽文件到页面上传模型,进行结构展示",
"upload-tip2": "点击或拖拽文件到页面上传模型,进行模型转换",
"vertical": "垂直",
"Choose-model": "选择模型",
"zoom-in": "放大",
......
{
"transformation": "转换",
"download": "下载",
"convert_before": "Please convert before viewing",
"warin-info": "请将模型描述文件.prototxt和参数文件.caffemodel打包成.tar上传",
"warin-info2": "该模型文件暂不支持X2Paddle转换",
"warin-info3": "请先进行转换,再查看",
"warin-info4": "模型已转换,请勿再次点击",
"warin-info5": "请上传模型文件并转换",
"warin-info6": "模型文件已转换,请勿再次点击",
"warin-info7": "请上传模型文件"
}
......@@ -17,7 +17,6 @@
// cspell:words pnpify svgs entrypoints
import * as env from './builder/env.js';
import {fileURLToPath} from 'url';
import fs from 'fs';
import path from 'path';
......@@ -37,7 +36,8 @@ function isWorkspace() {
const iconsPath = path.dirname(resolve.sync(cwd, '@visualdl/icons'));
const netronPath = path.dirname(resolve.sync(cwd, '@visualdl/netron'));
const TracePath = path.dirname(resolve.sync(cwd, './public/static'));
const netronPath2 = path.dirname(resolve.sync(cwd, '@visualdl/netron2'));
const wasmPath = path.dirname(resolve.sync(cwd, '@visualdl/wasm'));
const dest = path.resolve(cwd, './dist/__snowpack__/link/packages');
......@@ -66,8 +66,6 @@ export default {
plugins: [
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-dotenv',
'snowpack-plugin-less',
'@snowpack/plugin-sass',
[
'@snowpack/plugin-typescript',
{
......@@ -101,8 +99,8 @@ export default {
destination: path.join(dest, 'netron/dist')
},
{
source: [path.join(TracePath, '**/*')],
destination: path.join(dest, 'trace/dist')
source: [path.join(netronPath2, '**/*')],
destination: path.join(dest, 'netron2/dist')
},
{
source: [path.join(wasmPath, '*.{js,wasm}')],
......@@ -118,8 +116,9 @@ export default {
},
packageOptions: {
polyfillNode: true,
// knownEntrypoints: ['chai', '@testing-library/react', 'fetch-mock/esm/client', 'react-is','rc-util/es/hooks/useId','rc-util/es/Portal','rc-util/es/Dom/contains','rc-util/es/Dom/css','rc-util/es/getScrollBarSize','rc-util/es/PortalWrapper','rc-select/es/hooks/useId','rc-util/es/Dom/isVisible','rc-util/es/Dom/focus','rc-util/es/Dom/focus']
knownEntrypoints: ['chai', '@testing-library/react', 'fetch-mock/esm/client', 'react-is', 'antd']
namedExports: ['gl-vec2', 'dagre'],
// knownEntrypoints: ['chai', '@testing-library/react', 'fetch-mock/esm/client']
knownEntrypoints: ['chai', '@testing-library/react']
},
buildOptions: {
out: 'dist',
......
......@@ -141,7 +141,12 @@ const App: FunctionComponent = () => {
</ErrorBoundary>
</Router>
</Main>
<ToastContainer />
<ToastContainer
autoClose={100000}
style={{wordBreak: 'break-all'}}
draggable={false}
closeOnClick={false}
/>
</SWRConfig>
</div>
);
......
......@@ -45,7 +45,7 @@ const Title = styled.div`
const Content = styled.div`
padding: ${rem(20)};
height: calc(100% - ${rem(60)});
height: ${rem(600)};
overflow: auto;
`;
......
......@@ -208,7 +208,9 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
};
}, [handler, dispatch]);
useEffect(() => (ready && dispatch('change-files', files)) || undefined, [dispatch, files, ready]);
useEffect(() => {
(ready && dispatch('change-files', files)) || undefined;
}, [dispatch, files, ready]);
useEffect(
() => (ready && dispatch('toggle-attributes', showAttributes)) || undefined,
[dispatch, showAttributes, ready]
......
/**
* Copyright 2020 Baidu Inc. 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 type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {contentHeight, position, primaryColor, rem, size, transitionProps} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import HashLoader from 'react-spinners/HashLoader';
import logo from '~/assets/images/netron.png';
import netron2 from '@visualdl/netron2';
import styled from 'styled-components';
import {toast} from 'react-toastify';
import useTheme from '~/hooks/useTheme';
import {useTranslation} from 'react-i18next';
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
let IFRAME_HOST = `${window.location.protocol}//${window.location.host}`;
if (PUBLIC_PATH.startsWith('http')) {
const url = new URL(PUBLIC_PATH);
IFRAME_HOST = `${url.protocol}//${url.host}`;
}
const toolboxHeight = rem(40);
const Wrapper = styled.div`
position: relative;
height: ${contentHeight};
background-color: var(--background-color);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
${transitionProps('background-color')}
`;
const RenderContent = styled.div<{show: boolean}>`
position: absolute;
top: 0;
left: 0;
${size('100%', '100%')}
opacity: ${props => (props.show ? 1 : 0)};
z-index: ${props => (props.show ? 0 : -1)};
pointer-events: ${props => (props.show ? 'auto' : 'none')};
`;
const Toolbox = styled(ChartToolbox)`
height: ${toolboxHeight};
border-bottom: 1px solid var(--border-color);
padding: 0 ${rem(20)};
${transitionProps('border-color')}
`;
const Content = styled.div`
position: relative;
height: calc(100% - ${toolboxHeight});
> iframe {
${size('100%', '100%')}
border: none;
}
> .powered-by {
display: block;
${position('absolute', null, null, rem(20), rem(30))}
color: var(--graph-copyright-color);
font-size: ${rem(14)};
user-select: none;
img {
height: 1em;
filter: var(--graph-copyright-logo-filter);
vertical-align: middle;
}
}
`;
const Loading = styled.div`
${size('100%', '100%')}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
font-size: ${rem(16)};
line-height: ${rem(60)};
`;
export type GraphRef = {
export(type: 'svg' | 'png'): void;
changeGraph(name: string): void;
search(value: string): void;
select(item: SearchItem): void;
showModelProperties(): void;
showNodeDocumentation(data: Properties): void;
show2(): void;
};
type GraphProps = {
files: FileList | File[] | null;
uploader: JSX.Element;
showAttributes: boolean;
showInitializers: boolean;
showNames: boolean;
horizontal: boolean;
onRendered?: () => unknown;
onOpened?: (data: OpenedResult) => unknown;
onSearch?: (data: SearchResult) => unknown;
onShowModelProperties?: (data: Properties) => unknown;
onShowNodeProperties?: (data: Properties) => unknown;
onShowNodeDocumentation?: (data: Documentation) => unknown;
};
const Graph = React.forwardRef<GraphRef, GraphProps>(
(
{
files,
uploader,
showAttributes,
showInitializers,
showNames,
horizontal,
onRendered,
onOpened,
onSearch,
onShowModelProperties,
onShowNodeProperties,
onShowNodeDocumentation
},
ref
) => {
const {t} = useTranslation('graph');
const theme = useTheme();
const [ready, setReady] = useState(false);
const [loading, setLoading] = useState(false);
const [rendered, setRendered] = useState(false);
const iframe = useRef<HTMLIFrameElement>(null);
const handler = useCallback(
(event: MessageEvent) => {
if (event.data) {
const {type, data} = event.data;
switch (type) {
case 'status':
switch (data) {
case 'ready':
return setReady(true);
case 'loading':
return setLoading(true);
case 'rendered':
setLoading(false);
setRendered(true);
console.log('函数执行了');
onRendered?.();
return;
}
return;
case 'opened':
return onOpened?.(data);
case 'search':
return onSearch?.(data);
case 'cancel':
return setLoading(false);
case 'error':
toast.error(data);
setLoading(false);
return;
case 'show-model-properties':
return onShowModelProperties?.(data);
case 'show-node-properties':
return onShowNodeProperties?.(data);
case 'show-node-documentation':
return onShowNodeDocumentation?.(data);
}
}
},
[onRendered, onOpened, onSearch, onShowModelProperties, onShowNodeProperties, onShowNodeDocumentation]
);
const dispatch = useCallback((type: string, data?: unknown) => {
iframe.current?.contentWindow?.postMessage(
{
type,
data
},
IFRAME_HOST
);
}, []);
useEffect(() => {
window.addEventListener('message', handler);
dispatch('ready');
return () => {
window.removeEventListener('message', handler);
};
}, [handler, dispatch]);
useEffect(() => {
console.log('GraphStatic2', files, ready);
(files && ready && dispatch('change-files', files)) || undefined;
}, [dispatch, files, ready]);
useEffect(
() => (ready && dispatch('toggle-attributes', showAttributes)) || undefined,
[dispatch, showAttributes, ready]
);
useEffect(
() => (ready && dispatch('toggle-initializers', showInitializers)) || undefined,
[dispatch, showInitializers, ready]
);
useEffect(() => (ready && dispatch('toggle-names', showNames)) || undefined, [dispatch, showNames, ready]);
useEffect(
() => (ready && dispatch('toggle-direction', horizontal)) || undefined,
[dispatch, horizontal, ready]
);
useEffect(() => (ready && dispatch('toggle-theme', theme)) || undefined, [dispatch, theme, ready]);
useImperativeHandle(ref, () => ({
export(type) {
dispatch('export', type);
},
changeGraph(name) {
dispatch('change-graph', name);
},
search(value) {
dispatch('search', value);
},
select(item) {
dispatch('select', item);
},
showModelProperties() {
dispatch('show-model-properties');
},
showNodeDocumentation(data) {
dispatch('show-node-documentation', data);
},
show2() {
dispatch('show2');
}
}));
const content = useMemo(() => {
if (!ready || loading) {
return (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
);
}
if (ready && !rendered) {
return (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
);
}
return null;
}, [ready, loading, rendered, uploader]);
return (
<Wrapper>
{content}
<RenderContent show={!loading && rendered}>
<Toolbox
items={[
{
icon: 'zoom-in',
tooltip: t('graph:zoom-in'),
onClick: () => dispatch('zoom-in')
},
{
icon: 'zoom-out',
tooltip: t('graph:zoom-out'),
onClick: () => dispatch('zoom-out')
},
{
icon: 'restore-size',
tooltip: t('graph:restore-size'),
onClick: () => dispatch('zoom-reset')
}
]}
reversed
tooltipPlacement="bottom"
/>
<Content>
<iframe
ref={iframe}
src={PUBLIC_PATH + netron2}
frameBorder={0}
scrolling="no"
marginWidth={0}
marginHeight={0}
></iframe>
<a
className="powered-by"
href="https://github.com/lutzroeder/netron"
target="_blank"
rel="noreferrer"
>
Powered by <img src={PUBLIC_PATH + logo} alt="netron" />
</a>
</Content>
</RenderContent>
</Wrapper>
);
}
);
Graph.displayName = 'Graph';
export default Graph;
......@@ -77,9 +77,10 @@ const SupportTable = styled.table`
type UploaderProps = {
onClickUpload?: () => unknown;
onDropFiles?: (files: FileList) => unknown;
Xpaddlae?: boolean;
};
const Uploader: FunctionComponent<UploaderProps> = ({onClickUpload, onDropFiles}) => {
const Uploader: FunctionComponent<UploaderProps> = ({onClickUpload, onDropFiles, Xpaddlae}) => {
const {t} = useTranslation('graph');
const [active, setActive] = useState(false);
......@@ -112,22 +113,31 @@ const Uploader: FunctionComponent<UploaderProps> = ({onClickUpload, onDropFiles}
onDragLeave={onDragLeave}
>
<Icon type="upload" className="upload-icon" />
<span>{t('graph:upload-tip')}</span>
{Xpaddlae ? <span>{t('graph:upload-tip2')}</span> : <span>{t('graph:upload-tip')}</span>}
<Button type="primary" rounded className="upload-button" onClick={onClick}>
{t('graph:upload-model')}
</Button>
</DropZone>
<SupportTable>
<tbody>
<tr>
<td>{t('graph:supported-model')}</td>
<td>{t('graph:supported-model-list')}</td>
</tr>
<tr>
<td>{t('graph:experimental-supported-model')}</td>
<td>{t('graph:experimental-supported-model-list')}</td>
</tr>
</tbody>
{Xpaddlae ? (
<tbody>
<tr>
<td>{t('graph:supported-model')}</td>
<td>{t('graph:supported-model-list-xpaddle')}</td>
</tr>
</tbody>
) : (
<tbody>
<tr>
<td>{t('graph:supported-model')}</td>
<td>{t('graph:supported-model-list')}</td>
</tr>
<tr>
<td>{t('graph:experimental-supported-model')}</td>
<td>{t('graph:experimental-supported-model-list')}</td>
</tr>
</tbody>
)}
</SupportTable>
</>
);
......
import React from 'react';
function index() {
return <div>index</div>;
}
export default index;
......@@ -17,7 +17,17 @@
import Aside, {AsideSection} from '~/components/Aside';
import type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import GraphComponent, {GraphRef} from '~/components/GraphPage/GraphStatic';
import React, {FunctionComponent, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import React, {
FunctionComponent,
ForwardRefRenderFunction,
useImperativeHandle,
forwardRef,
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import Select, {SelectProps} from '~/components/Select';
import {actions, selectors} from '~/store';
import {primaryColor, rem, size} from '~/utils/style';
......@@ -84,8 +94,17 @@ const Loading = styled.div`
font-size: ${rem(16)};
line-height: ${rem(60)};
`;
const Graph: FunctionComponent = () => {
type GraphProps = {
changeName: (name: string) => void;
show?: boolean;
changeshowdata?: () => void;
Xpaddlae?: boolean;
};
type pageRef = {
files: FileList | File[] | null;
setNodeDocumentations: () => void;
};
const Graph = React.forwardRef<pageRef, GraphProps>(({changeName, changeshowdata, Xpaddlae, show = true}, ref) => {
const {t} = useTranslation(['graph', 'common']);
const storeDispatch = useDispatch();
......@@ -97,7 +116,10 @@ const Graph: FunctionComponent = () => {
const setModelFile = useCallback(
(f: FileList | File[]) => {
storeDispatch(actions.graph.setModel(f));
const name = f[0].name.substring(f[0].name.lastIndexOf('.') + 1);
changeName && changeName(name);
setFiles(f);
changeshowdata && changeshowdata();
},
[storeDispatch]
);
......@@ -116,14 +138,13 @@ const Graph: FunctionComponent = () => {
},
[setModelFile]
);
const {data, loading} = useRequest<BlobResponse>(files ? null : '/graph/graph');
const {data, loading} = useRequest<BlobResponse>(files ? null : '/graph/static_graph');
useEffect(() => {
if (data?.data?.size) {
setFiles([new File([data.data], data.filename || 'unknown_model')]);
}
}, [data]);
// useEffect(() => {
// if (data?.data?.size) {
// setFiles([new File([data.data], data.filename || 'unknown_model')]);
// }
// }, [data]);
const [modelGraphs, setModelGraphs] = useState<OpenedResult['graphs']>([]);
const [selectedGraph, setSelectedGraph] = useState<NonNullable<OpenedResult['selected']>>('');
......@@ -156,12 +177,20 @@ const Graph: FunctionComponent = () => {
const [modelData, setModelData] = useState<Properties | null>(null);
const [nodeData, setNodeData] = useState<Properties | null>(null);
const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null);
const [renderedflag3, setRenderedflag3] = useState(true);
useEffect(() => {
setSearch('');
setSearchResult({text: '', result: []});
}, [files, showAttributes, showInitializers, showNames]);
useEffect(() => {
if (!show) {
setRenderedflag3(false);
} else {
setRenderedflag3(true);
setNodeData(null);
}
}, [show]);
const bottom = useMemo(
() =>
searching ? null : (
......@@ -173,7 +202,12 @@ const Graph: FunctionComponent = () => {
);
const [rendered, setRendered] = useState(false);
useImperativeHandle(ref, () => ({
files,
setNodeDocumentations: () => {
setRenderedflag3(false);
}
}));
const aside = useMemo(() => {
if (!rendered || loading) {
return null;
......@@ -185,7 +219,8 @@ const Graph: FunctionComponent = () => {
</Aside>
);
}
if (nodeData) {
console.log('nodeData && renderedflag3', nodeData, renderedflag3);
if (nodeData && renderedflag3) {
return (
<Aside width={rem(360)}>
<NodePropertiesSidebar
......@@ -283,14 +318,13 @@ const Graph: FunctionComponent = () => {
rendered,
loading,
nodeData,
nodeDocumentation
nodeDocumentation,
renderedflag3
]);
const uploader = useMemo(
() => <Uploader onClickUpload={onClickFile} onDropFiles={setModelFile} />,
() => <Uploader onClickUpload={onClickFile} onDropFiles={setModelFile} Xpaddlae={Xpaddlae} />,
[onClickFile, setModelFile]
);
return (
<>
<Title>{t('common:graph')}</Title>
......@@ -334,6 +368,6 @@ const Graph: FunctionComponent = () => {
</Content>
</>
);
};
});
export default Graph;
/**
* Copyright 2020 Baidu Inc. 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 Aside, {AsideSection} from '~/components/Aside';
import type {Documentation, OpenedResult, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import GraphComponent, {GraphRef} from '~/components/GraphPage/GraphStatic2';
import React, {FunctionComponent, useImperativeHandle, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import Select, {SelectProps} from '~/components/Select';
import {actions, selectors} from '~/store';
import {primaryColor, rem, size} from '~/utils/style';
import {useDispatch, useSelector} from 'react-redux';
import type {BlobResponse} from '~/utils/fetch';
import Button from '~/components/Button';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Field from '~/components/Field';
import HashLoader from 'react-spinners/HashLoader';
import ModelPropertiesDialog from '~/components/GraphPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphPage/NodePropertiesSidebar';
import RadioButton from '~/components/RadioButton';
import RadioGroup from '~/components/RadioGroup';
import Search from '~/components/GraphPage/Search';
import Title from '~/components/Title';
import Uploader from '~/components/GraphPage/Uploader';
import styled from 'styled-components';
import useRequest from '~/hooks/useRequest';
import {useTranslation} from 'react-i18next';
const FullWidthButton = styled(Button)`
width: 100%;
`;
const FullWidthSelect = styled<React.FunctionComponent<SelectProps<NonNullable<OpenedResult['selected']>>>>(Select)`
width: 100%;
`;
const ExportButtonWrapper = styled.div`
display: flex;
justify-content: space-between;
> * {
flex: 1 1 auto;
&:not(:last-child) {
margin-right: ${rem(20)};
}
}
`;
// TODO: better way to auto fit height
const SearchSection = styled(AsideSection)`
max-height: calc(100% - ${rem(40)});
display: flex;
flex-direction: column;
&:not(:last-child) {
padding-bottom: 0;
}
`;
const Loading = styled.div`
${size('100%', '100%')}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
font-size: ${rem(16)};
line-height: ${rem(60)};
`;
type GraphProps = {
changeRendered?: () => void;
files?: FileList | File[] | null;
changeName?: () => void;
show?: boolean;
};
type pageRef = {
setModelFiles: (f: FileList | File[]) => void;
setNodeDocumentations: () => void;
rendered: boolean;
};
const Graph = React.forwardRef<pageRef, GraphProps>(({changeRendered, show = true}, ref) => {
const {t} = useTranslation(['graph', 'common']);
const storeDispatch = useDispatch();
const storeModel = useSelector(selectors.graph.model);
const graph = useRef<GraphRef>(null);
const file = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<FileList | File[] | null>(storeModel);
const [modelGraphs, setModelGraphs] = useState<OpenedResult['graphs']>([]);
const [selectedGraph, setSelectedGraph] = useState<NonNullable<OpenedResult['selected']>>('');
const {data, loading} = useRequest<BlobResponse>(files ? null : '/graph/graph');
const setModelFile = useCallback(
(f: FileList | File[]) => {
storeDispatch(actions.graph.setModel(f));
setFiles(f);
},
[storeDispatch]
);
const onClickFile = useCallback(() => {
if (file.current) {
file.current.value = '';
file.current.click();
}
}, []);
// const onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
// const target = e.target as EventTarget & HTMLInputElement;
// const file: FileList | null = target.files as FileList;
// if (file[0].name.split('.')[1] === 'pdmodel') {
// alert('该页面只能解析paddle的模型,如需解析请跳转网络结构静态图页面');
// return;
// }
// if (target && target.files && target.files.length) {
// fileUploader(target.files);
// }
// };
const onChangeFile = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target;
if (target && target.files && target.files.length) {
setModelFile(target.files);
}
},
[setModelFile]
);
const setOpenedModel = useCallback((data: OpenedResult) => {
setModelGraphs(data.graphs);
setSelectedGraph(data.selected || '');
}, []);
const changeGraph = useCallback((name: string) => {
setSelectedGraph(name);
graph.current?.changeGraph(name);
}, []);
const [search, setSearch] = useState('');
const [searching, setSearching] = useState(false);
const [searchResult, setSearchResult] = useState<SearchResult>({text: '', result: []});
const onSearch = useCallback((value: string) => {
setSearch(value);
graph.current?.search(value);
}, []);
const onSelect = useCallback((item: SearchItem) => {
setSearch(item.name);
graph.current?.select(item);
}, []);
const [showAttributes, setShowAttributes] = useState(false);
const [showInitializers, setShowInitializers] = useState(true);
const [showNames, setShowNames] = useState(false);
const [horizontal, setHorizontal] = useState(false);
const [modelData, setModelData] = useState<Properties | null>(null);
const [nodeData, setNodeData] = useState<Properties | null>(null);
const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null);
const [rendered, setRendered] = useState(false);
const [renderedflag, setRenderedflag] = useState(0);
const [renderedflag2, setRenderedflag2] = useState(0);
const [renderedflag3, setRenderedflag3] = useState(true);
useEffect(() => {
setSearch('');
setSearchResult({text: '', result: []});
}, [files, showAttributes, showInitializers, showNames]);
useEffect(() => {
if (renderedflag > 1 && renderedflag2 === 0) {
changeRendered && changeRendered();
setRenderedflag2(1);
setNodeDocumentation(null);
}
}, [renderedflag]);
useEffect(() => {
if (!show) {
setRenderedflag3(false);
} else {
setRenderedflag3(true);
setNodeData(null);
}
}, [show]);
const bottom = useMemo(
() =>
searching ? null : (
<FullWidthButton type="primary" rounded onClick={onClickFile}>
{t('graph:change-model')}
</FullWidthButton>
),
[t, onClickFile, searching]
);
useImperativeHandle(ref, () => ({
setModelFiles: file => {
setModelFile(file);
},
setNodeDocumentations: () => {
setRenderedflag3(false);
},
rendered: rendered
}));
const aside = useMemo(() => {
if (!rendered || loading) {
return null;
}
if (nodeDocumentation) {
return (
<Aside width={rem(360)}>
<NodeDocumentationSidebar data={nodeDocumentation} onClose={() => setNodeDocumentation(null)} />
</Aside>
);
}
console.log('nodeData && renderedflag3', nodeData, renderedflag3);
if (nodeData && renderedflag3) {
return (
<Aside width={rem(360)}>
<NodePropertiesSidebar
data={nodeData}
onClose={() => setNodeData(null)}
showNodeDocumentation={() => graph.current?.showNodeDocumentation(nodeData)}
/>
</Aside>
);
}
return (
<Aside>
<SearchSection>
<Search
text={search}
data={searchResult}
onChange={onSearch}
onSelect={onSelect}
onActive={() => setSearching(true)}
onDeactive={() => setSearching(false)}
/>
</SearchSection>
{!searching && (
<>
<AsideSection>
<FullWidthButton onClick={() => graph.current?.showModelProperties()}>
{t('graph:model-properties')}
</FullWidthButton>
</AsideSection>
{modelGraphs.length > 1 && (
<AsideSection>
<Field label={t('graph:subgraph')}>
<FullWidthSelect list={modelGraphs} value={selectedGraph} onChange={changeGraph} />
</Field>
</AsideSection>
)}
<AsideSection>
<Field label={t('graph:display-data')}>
<div>
<Checkbox checked={showAttributes} onChange={setShowAttributes}>
{t('graph:show-attributes')}
</Checkbox>
</div>
<div>
<Checkbox checked={showInitializers} onChange={setShowInitializers}>
{t('graph:show-initializers')}
</Checkbox>
</div>
<div>
<Checkbox checked={showNames} onChange={setShowNames}>
{t('graph:show-node-names')}
</Checkbox>
</div>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graph:direction')}>
<RadioGroup value={horizontal} onChange={setHorizontal}>
<RadioButton value={false}>{t('graph:vertical')}</RadioButton>
<RadioButton value={true}>{t('graph:horizontal')}</RadioButton>
</RadioGroup>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graph:export-file')}>
<ExportButtonWrapper>
<Button onClick={() => graph.current?.export('png')}>
{t('graph:export-png')}
</Button>
<Button onClick={() => graph.current?.export('svg')}>
{t('graph:export-svg')}
</Button>
</ExportButtonWrapper>
</Field>
</AsideSection>
</>
)}
</Aside>
);
}, [
t,
bottom,
search,
searching,
searchResult,
modelGraphs,
selectedGraph,
changeGraph,
onSearch,
onSelect,
showAttributes,
showInitializers,
showNames,
horizontal,
rendered,
loading,
nodeData,
nodeDocumentation,
renderedflag3
]);
const uploader = useMemo(
() => <Uploader onClickUpload={onClickFile} onDropFiles={setModelFile} />,
[onClickFile, setModelFile]
);
return (
<>
<Title>{t('common:graph')}</Title>
<ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} />
<Content aside={aside}>
{loading ? (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
) : (
<GraphComponent
ref={graph}
files={files}
uploader={uploader}
showAttributes={showAttributes}
showInitializers={showInitializers}
showNames={showNames}
horizontal={horizontal}
onRendered={() => {
setRendered(true);
setRenderedflag(flag => {
return flag + 1;
});
}}
onOpened={setOpenedModel}
onSearch={data => {
setSearchResult(data);
}}
onShowModelProperties={data => setModelData(data)}
onShowNodeProperties={data => {
setNodeData(data);
setNodeDocumentation(null);
}}
onShowNodeDocumentation={data => setNodeDocumentation(data)}
/>
)}
<input
ref={file}
type="file"
multiple={false}
onChange={onChangeFile}
style={{
display: 'none'
}}
/>
</Content>
</>
);
});
export default Graph;
import React, {useState, useEffect, useRef, useCallback, useMemo} from 'react';
import {rem, primaryColor, size} from '~/utils/style';
import Content from '~/components/Content';
import {toast} from 'react-toastify';
import {fetcher} from '~/utils/fetch';
import GraphStatic from '~/pages/graphStatic';
import GraphStatic2 from '~/pages/graphStatic2';
import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components';
import {useTranslation} from 'react-i18next';
const ButtonContent = styled.section`
display: flex;
.active {
background-color: #2932e1;
color: white;
}
.un_active {
background-color: white;
color: #2932e1;
}
.disabled {
background: #ccc;
color: white;
cursor: not-allowed;
}
`;
type Fn = (data: FcResponse<any>) => unknown;
interface IAnyObj {
[index: string]: unknown;
}
interface FcResponse<T> {
errno: string;
errmsg: string;
data: T;
}
const Article = styled.article`
flex: auto;
display: flex;
min-width: 0;
margin: ${10};
min-height: ${10};
`;
const Buttons = styled.div`
width: 49%;
height: ${rem(40)};
line-height: ${rem(40)};
text-align: center;
font-size: 16px;
`;
const Contents = styled.div`
height: 100%;
`;
const Loading = styled.div`
${size('100%', '100%')}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
font-size: ${rem(16)};
line-height: ${rem(60)};
`;
const Aside = styled.aside`
width: ${rem(260)};
display: flex;
`;
function App() {
const {t} = useTranslation(['togglegraph']);
const [show, setShow] = useState({
show: true,
show2: false
});
const [showData, setshowData] = useState<any>(null);
const [baseId, setBaseId] = useState<any>(false);
const [loading, setLoading] = useState<any>(false);
const [file_names, setfile_names] = useState<any>(false);
const [names, setNames] = useState('');
const file = useRef<HTMLInputElement>(null);
const Graph = useRef(null);
const Graph2 = useRef(null);
// 创建 axios 实例
const blobToFile = function (theBlob: any, fileName: any, type: any) {
theBlob.lastModifiedDate = new Date();
theBlob.name = fileName;
return new window.File([theBlob], theBlob.name, {type: type});
};
const base64UrlToFile = (base64Url: any, filename: any) => {
// const arr = base64Url.split(',');
// const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(base64Url);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename);
};
const downloadEvt = (url: any, fileName = '未知文件') => {
const el = document.createElement('a');
el.style.display = 'none';
el.setAttribute('target', '_blank');
/**
* download的属性是HTML5新增的属性
* href属性的地址必须是非跨域的地址,如果引用的是第三方的网站或者说是前后端分离的项目(调用后台的接口),这时download就会不起作用。
* 此时,如果是下载浏览器无法解析的文件,例如.exe,.xlsx..那么浏览器会自动下载,但是如果使用浏览器可以解析的文件,比如.txt,.png,.pdf....浏览器就会采取预览模式
* 所以,对于.txt,.png,.pdf等的预览功能我们就可以直接不设置download属性(前提是后端响应头的Content-Type: application/octet-stream,如果为application/pdf浏览器则会判断文件为 pdf ,自动执行预览的策略)
*/
fileName && el.setAttribute('download', fileName);
const href = URL.createObjectURL(url);
el.href = href;
console.log(el, href);
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
const fileUploader = (files: FileList, formats = 'caffe') => {
if (!files) {
toast.warning('请上传模型文件模型文件');
return;
}
setLoading(true);
const formData = new FormData();
// // 将文件转二进制
formData.append('file', files[0]);
formData.append('filename', files[0].name);
formData.append('format', formats);
fetcher(`/inference/convert?format=${formats}`, {
method: 'POST',
body: formData
}).then(
(res: any) => {
// debugger
const name2: string = files[0].name.substring(files[0].name.lastIndexOf('.') + 1) + '.paddle';
console.log('res', res, name2);
const file = base64UrlToFile(res.pdmodel, name2);
console.log('file', file);
setshowData(file);
setBaseId(res.request_id);
const name3 = files[0].name.substring(0, files[0].name.lastIndexOf('.'));
setfile_names(name3 + '.tar');
setLoading(false);
},
res => {
// debugger
setLoading(false);
// const newFilesId = filesId + 1;
// setFilesId(newFilesId);
}
);
// fetcher('/graph/graph').then((res: any) => {
// console.log('res', res);
// setTimeout(() => {
// // const file = blobToFile(res.data, res.filename, res.type);
// const file = blobToFile(res.data, res.filename, res.type);
// console.log('bolbfile', file);
// downloadEvt(res.data, res.filename);
// setshowData(file);
// setLoading(false);
// }, 5000);
// // setShow2(true);
// });
};
const onClickFile = useCallback(() => {
// 这里为.prototxt, 用户点击转换按钮,弹出提示框,
// 『请将模型描述文件.prototxt和参数文件.caffemodel打包成.tar上传』。
// 弹出文件选择框,让用户重新进行选择.tar文件上传。
if (showData) {
// toast.warning('模型文件已转换,请勿再次点击');
toast.warning(t('warin-info6'));
return;
}
console.log('Graph.current.filess', Graph);
const Graphs: any = Graph;
const files: FileList | null = Graphs?.current?.files as FileList;
const name = files[0].name.split('.')[1];
if (name === 'prototxt') {
toast.warning(t('togglegraph:warin-info'));
if (file.current) {
file.current.value = '';
file.current.click();
}
return;
}
if (name === 'pb' || name === 'onnx') {
fileUploader(files, name);
return;
}
// toast.warning('该模型文件暂不支持X2Paddle转换');
toast.warning(t('togglegraph:warin-info2'));
// 用户上传的文件为.pb和.onnx格式,直接发动转换数据 //fileUploader
}, [fileUploader, showData, t]);
const onChangeFile = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target;
if (target && target.files && target.files.length) {
fileUploader(target.files);
}
},
[fileUploader]
);
//将base64转换为blob
const dataURLtoBlob = (base64Url: any) => {
const bstr = atob(base64Url);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr]);
};
// * desc: 下载方法
// * @param url :返回数据的blob对象或链接
// * @param fileName :下载后文件名标记
// const downloadFile = (url: any, name = "What's the fuvk") => {
// const a = document.createElement('a');
// a.setAttribute('href', url);
// a.setAttribute('download', name);
// a.click();
// };
const downloadFileByBase64 = (baseId: any, fileName: string) => {
console.log('baseId', baseId, fileName);
if (baseId === undefined || !fileName) return;
setLoading(true);
fetcher(`/inference/download?request_id=${baseId}`, {
method: 'GET'
}).then(
(res: any) => {
console.log('blobres', res, res.data);
downloadEvt(res.data, fileName);
setLoading(false);
},
res => {
setLoading(false);
}
);
};
useEffect(() => {
// const Graphs: any = Graph;
const Graphs2: any = Graph2;
if (showData) {
console.log('Graph2', showData);
const files = [showData];
Graphs2?.current?.setModelFiles(files);
}
}, [showData]);
const Graphs2 = useMemo(() => {
return (
<div
style={{
height: show.show2 ? 'auto' : '0px',
overflowY: 'hidden'
}}
>
<GraphStatic2
ref={Graph2}
changeRendered={() => {
setShow({
show: false,
show2: true
});
}}
show={show.show2}
/>
</div>
);
}, [show.show2]);
return (
<Content>
{loading && (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
)}
<Contents
style={{
height: !loading ? 'auto' : '0px',
overflow: 'hidden'
}}
>
<div
style={{
height: show.show ? 'auto' : '0px',
// opacity: show2 ? 1 : 0
overflowY: 'hidden'
}}
>
<GraphStatic
ref={Graph}
changeName={setNames}
show={show.show}
changeshowdata={() => {
setshowData(null);
}}
Xpaddlae={true}
/>
</div>
{Graphs2}
</Contents>
{names && !loading && (
<ButtonContent style={{marginTop: '20px'}}>
<Article>
<Buttons
style={{marginRight: '3px'}}
className={show.show ? 'active' : 'un_active'}
onClick={() => {
setShow({
show: true,
show2: false
});
}}
>
{names ? names : 'Toggle'}
</Buttons>
<Buttons
className={!showData ? 'disabled' : show.show2 ? 'active' : 'un_active'}
onClick={() => {
if (!showData) {
// toast.warning('请先进行转换,再查看');
toast.warning(t('warin-info3'));
return;
}
setShow({
show: false,
show2: true
});
}}
>
paddle
</Buttons>
</Article>
<Aside>
<Buttons
style={{marginRight: '3px'}}
className={!showData && names ? 'active' : 'disabled'}
onClick={() => {
if (showData) {
toast.warning(t('warin-info4'));
// toast.warning('模型已转换,请勿再次点击');
return;
} else {
if (!names) {
toast.warning(t('warin-info7'));
} else {
onClickFile();
}
}
}}
>
{t('togglegraph:transformation')}
</Buttons>
<Buttons
className={showData ? 'active' : 'disabled'}
onClick={() => {
console.log('showData', showData);
if (!showData) {
// toast.warning('请上传模型文件并转换');
toast.warning(t('warin-info5'));
return;
}
downloadFileByBase64(baseId, file_names);
}}
>
{t('togglegraph:download')}
</Buttons>
</Aside>
</ButtonContent>
)}
<input
ref={file}
type="file"
multiple={false}
onChange={onChangeFile}
style={{
display: 'none'
}}
/>
</Content>
);
}
export default App;
......@@ -25,6 +25,7 @@ export enum Pages {
Audio = 'audio',
Text = 'text',
Graph = 'graph',
ToggleGraph = 'ToggleGraph',
HighDimensional = 'high-dimensional',
PRCurve = 'pr-curve',
ROCCurve = 'roc-curve',
......@@ -37,7 +38,8 @@ export interface Route {
default?: boolean;
visible?: boolean;
path?: string;
component?: LazyExoticComponent<FunctionComponent>;
// component?: LazyExoticComponent<FunctionComponent>;
component?: any;
children?: Pick<Route, 'id' | 'path' | 'component'>[];
}
......@@ -89,6 +91,11 @@ const routes: Route[] = [
}
]
},
{
id: Pages.ToggleGraph,
path: '/x2paddle',
component: React.lazy(() => import('~/pages/x2paddle'))
},
{
id: Pages.Histogram,
path: '/histogram',
......
......@@ -17,9 +17,10 @@
import type {TFunction} from 'i18next';
import i18next from 'i18next';
import queryString from 'query-string';
import {toast} from 'react-toastify';
const API_TOKEN_KEY: string = import.meta.env.SNOWPACK_PUBLIC_API_TOKEN_KEY;
const API_URL: string = import.meta.env.SNOWPACK_PUBLIC_API_URL;
console.log('API_URL', API_TOKEN_KEY);
const API_TOKEN_HEADER = 'X-VisualDL-Instance-ID';
......@@ -85,6 +86,8 @@ export function fetcher<T = unknown>(url: string, options?: RequestInit): Promis
export async function fetcher<T = unknown>(url: string, options?: RequestInit): Promise<BlobResponse | string | T> {
let res: Response;
try {
// res = await fetch('http://10.181.196.14:8040/app/api/deploy/convert?format=onnx', addApiToken(options));
res = await fetch(API_URL + url, addApiToken(options));
} catch (e) {
const t = await logErrorAndReturnT(e);
......@@ -108,6 +111,7 @@ export async function fetcher<T = unknown>(url: string, options?: RequestInit):
if (response && 'status' in response) {
if (response.status !== 0) {
const t = await logErrorAndReturnT(response);
toast.error((response as ErrorData).msg);
throw new Error((response as ErrorData).msg || t('errors:error'));
} else {
return (response as SuccessData<T>).data;
......@@ -126,6 +130,7 @@ export async function fetcher<T = unknown>(url: string, options?: RequestInit):
} else {
let data: Blob;
try {
console.log('datas', res);
data = await res.blob();
} catch (e) {
const t = await logErrorAndReturnT(e);
......@@ -134,6 +139,7 @@ export async function fetcher<T = unknown>(url: string, options?: RequestInit):
const disposition = res.headers.get('Content-Disposition');
// support safari
if (!data.arrayBuffer) {
console.log('arrayBuffer', data);
data.arrayBuffer = async () =>
new Promise<ArrayBuffer>((resolve, reject) => {
const fileReader = new FileReader();
......@@ -143,6 +149,7 @@ export async function fetcher<T = unknown>(url: string, options?: RequestInit):
fileReader.readAsArrayBuffer(data);
});
}
console.log('datas', data);
let filename: string | null = null;
if (disposition && disposition.indexOf('attachment') !== -1) {
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
......
......@@ -18,3 +18,7 @@ declare module '@visualdl/netron' {
const ref: string;
export default ref;
}
declare module '@visualdl/netron2' {
const ref: string;
export default ref;
}
......@@ -20,7 +20,7 @@ import graph from '../../assets/graph/yolov3.cfg';
export default async () => {
const result = await fetch(graph);
console.log('result', result);
return new Response(await result.arrayBuffer(), {
status: 200,
headers: {
......
......@@ -107,6 +107,7 @@ host.BrowserHost = class {
return this._view.showNodeDocumentation(data);
case 'ready':
if (this._ready) {
debugger;
return this.status('ready');
}
return;
......@@ -196,7 +197,9 @@ host.BrowserHost = class {
}
_changeFiles(files) {
if (files && files.length) {
console.log('files', files);
if (files && files?.length) {
console.log('files.length', files.length);
files = Array.from(files);
const file = files.find(file => this._view.accept(file.name));
if (!file) {
......@@ -524,7 +527,7 @@ function getCaption(obj) {
return newObj;
}
const hash = getCaption(document.referrer);
if (hash === 'graphStatic') {
if (hash === 'graphStatic' || hash === 'x2paddle') {
window.__view__ = new view2.View(new host.BrowserHost());
} else {
window.__view__ = new view.View(new host.BrowserHost());
......
......@@ -261,12 +261,13 @@ view.View = class {
return this._timeout(2).then(() => {
return this._modelFactoryService.open(context).then(model => {
return this._timeout(20).then(() => {
console.log('model.graphs.length', model.graphs.length);
const graph = model.graphs.length > 0 ? model.graphs[0] : null;
this._host.message('opened', {
graphs: model.graphs.map(g => g.name || ''),
selected: graph && (graph.name || '')
});
return this._updateGraph2(model, graph);
return this._updateGraph2(graph);
});
});
});
......@@ -366,6 +367,9 @@ view.View = class {
if (graph && graph != this._activeGraph) {
this._selectItem = null;
const nodes = graph.nodes;
console.log('graphs', graph);
console.log('nodes.length', nodes);
if (nodes.length > 1400) {
if (
!this._host.confirm(
......@@ -478,6 +482,7 @@ view.View = class {
for (const node of nodes) {
let path = node.name.split('/');
path.pop();
console.log('path.length', path.length);
while (path.length > 0) {
const name = path.join('/');
path.pop();
......
last 2 Chrome versions
last 2 Firefox versions
last 1 Safari version
last 1 iOS version
# VisualDL FrontEnd Server
A fork version of [Netron](https://github.com/lutzroeder/netron).
## Build
```bash
yarn build
```
{
"name": "@visualdl/netron2",
"version": "2.2.1",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
"paddlepaddle",
"visualization",
"deep learning"
],
"homepage": "https://github.com/PaddlePaddle/VisualDL",
"bugs": {
"url": "https://github.com/PaddlePaddle/VisualDL/issues"
},
"license": "Apache-2.0",
"author": "PeterPanZH <littlepanzh@gmail.com> (https://github.com/PeterPanZH)",
"contributors": [
"Niandalu <littlepanzh@gmail.com> (https://github.com/Niandalu)"
],
"repository": {
"type": "git",
"url": "https://github.com/PaddlePaddle/VisualDL.git",
"directory": "frontend/packages/netron"
},
"scripts": {
"build": "rimraf dist && webpack",
"test": "echo \"Error: no test specified\" && exit 0"
},
"files": [
"dist"
],
"main": "dist/index.html",
"dependencies": {
"d3": "5.16.0",
"dagre": "0.8.5",
"flatbuffers": "1.12.0",
"long": "4.0.0",
"marked": "2.0.7",
"netron": "PeterPanZH/netron",
"pako": "1.0.11"
},
"devDependencies": {
"autoprefixer": "10.3.4",
"copy-webpack-plugin": "9.0.1",
"css-loader": "6.2.0",
"html-webpack-plugin": "5.3.2",
"mini-css-extract-plugin": "2.2.2",
"postcss": "8.3.6",
"postcss-loader": "6.1.1",
"rimraf": "3.0.2",
"sass": "1.39.0",
"sass-loader": "12.1.0",
"terser": "5.7.2",
"webpack": "5.52.0",
"webpack-cli": "4.8.0"
},
"engines": {
"node": ">=12",
"npm": ">=6"
},
"publishConfig": {
"access": "public"
}
}
/**
* Copyright 2020 Baidu Inc. 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.
*/
module.exports = {
plugins: [require('autoprefixer')]
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
</head>
<body>
<div id="graph" class="graph">
<svg id="canvas" class="canvas" preserveaspectratio="xMidYMid meet" width="100%" height="100%"></svg>
</div>
</body>
</html>
/**
* Copyright 2020 Baidu Inc. 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.
*/
// cSpell:words actived nextcode
const view = require('./view');
const view2 = require('./view2');
const host = {};
host.BrowserHost = class {
constructor() {
window.eval = () => {
throw new Error('window.eval() not supported.');
};
this._document = window.document;
this._meta = {};
for (const element of Array.from(this._document.getElementsByTagName('meta'))) {
if (element.content) {
this._meta[element.name] = this._meta[element.name] || [];
this._meta[element.name].push(element.content);
}
}
this._type = this._meta.type ? this._meta.type[0] : 'Browser';
this._version = this._meta.version ? this._meta.version[0] : null;
this._ready = false;
}
get document() {
return this._document;
}
get version() {
return this._version;
}
get type() {
return this._type;
}
initialize(view) {
this._view = view;
return Promise.resolve();
}
start() {
window.addEventListener(
'message',
event => {
const originalData = event.data;
if (originalData) {
const type = originalData.type;
const data = originalData.data;
switch (type) {
// 在此书添加一个this._view的事件传递Graph页面过来的数据
case 'change-files':
return this._changeFiles(data);
case 'zoom-in':
return this._view.zoomIn();
case 'zoom-out':
return this._view.zoomOut();
case 'select-item':
return this._view.selectItem(data);
case 'toggle-Language':
return this._view.toggleLanguage(data);
case 'isAlt':
return this._view.changeAlt(data);
case 'zoom-reset':
return this._view.resetZoom();
case 'toggle-attributes':
return this._view.toggleAttributes(data);
case 'toggle-initializers':
return this._view.toggleInitializers(data);
case 'toggle-names':
return this._view.toggleNames(data);
case 'toggle-KeepData':
return this._view.toggleKeepData(data);
case 'toggle-direction':
return this._view.toggleDirection(data);
case 'toggle-theme':
return this._view.toggleTheme(data);
case 'export':
return this._view.export(`${document.title}.${data}`);
case 'change-graph':
return this._view.changeGraph(data);
case 'change-allGraph':
return this._view.changeAllGrap(data);
case 'change-select':
return this._view.changeSelect(data);
case 'search':
return this._view.find(data);
case 'select':
return this._view.select(data);
case 'show-model-properties':
return this._view.showModelProperties();
case 'show-node-documentation':
return this._view.showNodeDocumentation(data);
case 'show2':
return this._view._reload();
case 'ready':
if (this._ready) {
debugger;
return this.status('ready');
}
return;
}
}
},
false
);
this._ready = true;
this.status('ready');
}
message(type, data) {
if (window.parent) {
window.parent.postMessage({type: type, data: data}, '*');
}
}
status(status) {
// 反传回去
this.message('status', status);
}
selectNodeId(nodeInfo) {
// 反传回去
console.log('节点点击事件触发了', nodeInfo);
this.message('nodeId', nodeInfo);
}
selectItems(item) {
// 反传回去
console.log('节点点击事件触发了', item);
this.message('selectItem', item);
}
error(message, detail) {
this.message('error', (message === 'Error' ? '' : message + ' ') + detail);
}
reload() {
this.view._reload();
}
confirm(message, detail) {
const result = confirm(message + ' ' + detail);
if (!result) {
this.message('cancel');
}
return result;
}
require(id) {
const url = this._url(id + '.js');
window.__modules__ = window.__modules__ || {};
if (window.__modules__[url]) {
return Promise.resolve(window.__exports__[url]);
}
return new Promise((resolve, reject) => {
window.module = {exports: {}};
const script = document.createElement('script');
script.setAttribute('id', id);
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', url);
script.onload = () => {
const exports = window.module.exports;
delete window.module;
window.__modules__[id] = exports;
resolve(exports);
};
script.onerror = e => {
delete window.module;
reject(new Error("The script '" + e.target.src + "' failed to load."));
};
this.document.head.appendChild(script);
});
}
save(name, extension, defaultPath, callback) {
callback(defaultPath + '.' + extension);
}
export(file, blob) {
const element = this.document.createElement('a');
element.download = file;
element.href = URL.createObjectURL(blob);
this.document.body.appendChild(element);
element.click();
this.document.body.removeChild(element);
}
request(base, file, encoding) {
const url = base ? base + '/' + file : this._url(file);
return this._request(url, null, encoding);
}
_changeFiles(files) {
console.log('files2', files);
if (files && files?.length) {
console.log('files.length', files.length);
files = Array.from(files);
const file = files.find(file => this._view.accept(file.name));
if (!file) {
this.error('Error opening file.', 'Cannot open file ' + files[0].name);
return;
}
this._open(
files.find(file => this._view.accept(file.name)),
files
);
}
}
_request(url, headers, encoding, timeout) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
if (!encoding) {
request.responseType = 'arraybuffer';
}
if (timeout) {
request.timeout = timeout;
}
const error = status => {
const err = new Error('The web request failed with status code ' + status + " at '" + url + "'.");
err.type = 'error';
err.url = url;
return err;
};
request.onload = () => {
if (request.status == 200) {
if (request.responseType == 'arraybuffer') {
resolve(new Uint8Array(request.response));
} else {
resolve(request.responseText);
}
} else {
reject(error(request.status));
}
};
request.onerror = e => {
const err = error(request.status);
err.type = e.type;
reject(err);
};
request.ontimeout = () => {
request.abort();
const err = new Error("The web request timed out in '" + url + "'.");
err.type = 'timeout';
err.url = url;
reject(err);
};
request.open('GET', url, true);
if (headers) {
for (const name of Object.keys(headers)) {
request.setRequestHeader(name, headers[name]);
}
}
request.send();
});
}
_url(file) {
let url = file;
if (window && window.location && window.location.href) {
let location = window.location.href.split('?').shift();
if (location.endsWith('.html')) {
location = location.split('/').slice(0, -1).join('/');
}
if (location.endsWith('/')) {
location = location.slice(0, -1);
}
url = location + '/' + file;
}
return url;
}
_open(file, files) {
this.status('loading');
const context = new BrowserFileContext(file, files);
context
.open()
.then(() => {
return this._view.open(context).then(model => {
if (this._view.actived) {
this.status('rendered');
}
this.document.title = files[0].name;
return model;
});
})
.catch(error => {
this.error(error.name, error.message);
});
}
};
if (typeof TextDecoder === 'undefined') {
TextDecoder = function TextDecoder(encoding) {
this._encoding = encoding;
};
TextDecoder.prototype.decode = function decode(buffer) {
let result = '';
const length = buffer.length;
let i = 0;
switch (this._encoding) {
case 'utf-8':
while (i < length) {
const c = buffer[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7: {
result += String.fromCharCode(c);
break;
}
case 12:
case 13: {
const c2 = buffer[i++];
result += String.fromCharCode(((c & 0x1f) << 6) | (c2 & 0x3f));
break;
}
case 14: {
const c2 = buffer[i++];
const c3 = buffer[i++];
result += String.fromCharCode(((c & 0x0f) << 12) | ((c2 & 0x3f) << 6) | ((c3 & 0x3f) << 0));
break;
}
}
}
break;
case 'ascii':
while (i < length) {
result += String.fromCharCode(buffer[i++]);
}
break;
}
return result;
};
}
if (typeof TextEncoder === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-empty-function
TextEncoder = function TextEncoder() {};
TextEncoder.prototype.encode = function encode(str) {
'use strict';
const length = str.length;
let resPos = -1;
const resArr = typeof Uint8Array === 'undefined' ? new Array(length * 2) : new Uint8Array(length * 3);
for (let point = 0, nextcode = 0, i = 0; i !== length; ) {
point = str.charCodeAt(i);
i += 1;
if (point >= 0xd800 && point <= 0xdbff) {
if (i === length) {
resArr[(resPos += 1)] = 0xef;
resArr[(resPos += 1)] = 0xbf;
resArr[(resPos += 1)] = 0xbd;
break;
}
nextcode = str.charCodeAt(i);
if (nextcode >= 0xdc00 && nextcode <= 0xdfff) {
point = (point - 0xd800) * 0x400 + nextcode - 0xdc00 + 0x10000;
i += 1;
if (point > 0xffff) {
resArr[(resPos += 1)] = (0x1e << 3) | (point >>> 18);
resArr[(resPos += 1)] = (0x2 << 6) | ((point >>> 12) & 0x3f);
resArr[(resPos += 1)] = (0x2 << 6) | ((point >>> 6) & 0x3f);
resArr[(resPos += 1)] = (0x2 << 6) | (point & 0x3f);
continue;
}
} else {
resArr[(resPos += 1)] = 0xef;
resArr[(resPos += 1)] = 0xbf;
resArr[(resPos += 1)] = 0xbd;
continue;
}
}
if (point <= 0x007f) {
resArr[(resPos += 1)] = (0x0 << 7) | point;
} else if (point <= 0x07ff) {
resArr[(resPos += 1)] = (0x6 << 5) | (point >>> 6);
resArr[(resPos += 1)] = (0x2 << 6) | (point & 0x3f);
} else {
resArr[(resPos += 1)] = (0xe << 4) | (point >>> 12);
resArr[(resPos += 1)] = (0x2 << 6) | ((point >>> 6) & 0x3f);
resArr[(resPos += 1)] = (0x2 << 6) | (point & 0x3f);
}
}
if (typeof Uint8Array !== 'undefined') {
return new Uint8Array(resArr.buffer.slice(0, resPos + 1));
} else {
return resArr.length === resPos + 1 ? resArr : resArr.slice(0, resPos + 1);
}
};
TextEncoder.prototype.toString = function () {
return '[object TextEncoder]';
};
try {
Object.defineProperty(TextEncoder.prototype, 'encoding', {
get: function () {
if (Object.prototype.isPrototypeOf.call(TextEncoder.prototype, this)) {
return 'utf-8';
} else {
throw TypeError('Illegal invocation');
}
}
});
} catch (e) {
TextEncoder.prototype.encoding = 'utf-8';
}
if (typeof Symbol !== 'undefined') {
TextEncoder.prototype[Symbol.toStringTag] = 'TextEncoder';
}
}
if (typeof URLSearchParams === 'undefined') {
URLSearchParams = function URLSearchParams(search) {
const decode = str => {
return str.replace(/[ +]/g, '%20').replace(/(%[a-f0-9]{2})+/gi, match => {
return decodeURIComponent(match);
});
};
this._dict = {};
if (typeof search === 'string') {
search = search.indexOf('?') === 0 ? search.substring(1) : search;
const properties = search.split('&');
for (const property of properties) {
const index = property.indexOf('=');
const name = index > -1 ? decode(property.substring(0, index)) : decode(property);
const value = index > -1 ? decode(property.substring(index + 1)) : '';
if (!Object.prototype.hasOwnProperty.call(this._dict, name)) {
this._dict[name] = [];
}
this._dict[name].push(value);
}
}
};
URLSearchParams.prototype.get = function (name) {
return Object.prototype.hasOwnProperty.call(this._dict, name) ? this._dict[name][0] : null;
};
}
if (!HTMLCanvasElement.prototype.toBlob) {
HTMLCanvasElement.prototype.toBlob = function (callback, type, quality) {
setTimeout(() => {
const data = atob(this.toDataURL(type, quality).split(',')[1]);
const length = data.length;
const buffer = new Uint8Array(length);
for (let i = 0; i < length; i++) {
buffer[i] = data.charCodeAt(i);
}
callback(new Blob([buffer], {type: type || 'image/png'}));
}, 0);
};
}
class BrowserFileContext {
constructor(file, blobs) {
this._file = file;
this._blobs = {};
for (const blob of blobs) {
this._blobs[blob.name] = blob;
}
}
get identifier() {
return this._file.name;
}
get buffer() {
return this._buffer;
}
open() {
return this.request(this._file.name, null).then(data => {
this._buffer = data;
});
}
request(file, encoding) {
const blob = this._blobs[file];
if (!blob) {
return Promise.reject(new Error("File not found '" + file + "'."));
}
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => {
resolve(encoding ? e.target.result : new Uint8Array(e.target.result));
};
reader.onerror = e => {
e = e || window.event;
let message = '';
switch (e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
message = "File not found '" + file + "'.";
break;
case e.target.error.NOT_READABLE_ERR:
message = "File not readable '" + file + "'.";
break;
case e.target.error.SECURITY_ERR:
message = "File access denied '" + file + "'.";
break;
default:
message = "File read '" + e.target.error.code.toString() + "' error '" + file + "'.";
break;
}
reject(new Error(message));
};
if (encoding === 'utf-8') {
reader.readAsText(blob, encoding);
} else {
reader.readAsArrayBuffer(blob);
}
});
}
}
function getCaption(obj) {
let index = obj.lastIndexOf('/'); //获取-后边的字符串
let newObj = obj.substring(index + 1, obj.length);
return newObj;
}
const hash = getCaption(document.referrer);
console.log('hash', hash);
if (hash === 'graphStatic' || hash === 'x2paddle') {
window.__view__ = new view2.View(new host.BrowserHost());
} else {
window.__view__ = new view.View(new host.BrowserHost());
}
此差异已折叠。
/**
* Copyright 2020 Baidu Inc. 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.
*/
window.base = require('netron/src/base');
window.flatbuffers = require('netron/src/flatbuffers');
window.long = {
Long: require('long')
};
window.protobuf = require('netron/src/protobuf');
window.zip = require('netron/src/zip');
此差异已折叠。
html {
text-size-adjust: 100%;
text-rendering: optimizeLegibility;
}
body {
overflow: hidden;
margin: 0;
width: 100vw;
height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif,
'PingFang SC';
font-size: 12px;
text-rendering: geometricPrecision;
background-color: #fff;
&.dark {
background-color: #1d1d1f;
}
}
.graph {
overflow: auto;
width: 100%;
height: 100%;
}
.canvas {
display: block;
// position: absolute;
text-rendering: geometricPrecision;
user-select: none;
cursor: grab;
}
path {
stroke: #666;
stroke-width: 1px;
fill: none;
}
line {
stroke: #666;
stroke-width: 1px;
fill: #666;
}
text {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif,
'PingFang SC';
font-size: 11px;
text-rendering: geometricPrecision;
fill: #000;
.dark & {
fill: #cfcfd1;
}
}
.node-item {
path {
fill: #fff;
fill-opacity: 1;
stroke: none;
transition: fill 0.075s ease-in, fill-opacity 0.075s ease-in;
}
text {
transition: fill 0.075s ease-in;
}
&:hover {
path {
fill: #2932e1;
fill-opacity: 1;
}
text {
fill: #fff;
}
}
}
.node-item-function path {
fill: #9bb9e8;
fill-opacity: 0.7;
}
.node-item-type {
cursor: pointer;
path {
fill: #8bb8ff;
fill-opacity: 0.9;
}
}
.node-item-type-constant path {
fill: #b4ccb7;
}
.node-item-type-control path {
fill: #a8e9b8;
}
.node-item-type-layer path {
fill: #db989a;
fill-opacity: 0.7;
}
.node-item-type-container path {
fill: #db989a;
fill-opacity: 0.7;
}
.node-item-type-wrapper path {
fill: #6dcde4;
fill-opacity: 0.7;
}
.node-item-type-conv path {
fill: #6dcde4;
fill-opacity: 0.7;
}
.node-item-type-activation path {
fill: #93c2ca;
fill-opacity: 0.7;
}
.node-item-type-pool path {
fill: #de7cce;
fill-opacity: 0.7;
}
.node-item-type-normalization path {
fill: #da96bc;
fill-opacity: 0.7;
}
.node-item-type-dropout path {
fill: #309e51;
fill-opacity: 0.7;
}
.node-item-type-pad path {
fill: #309e51;
fill-opacity: 0.7;
}
.node-item-type-shape path {
fill: #d6c482;
fill-opacity: 0.7;
}
.node-item-type-tensor path {
fill: #6d7ce4;
fill-opacity: 0.7;
}
.node-item-type-transform path {
fill: #cdcb74;
}
.node-item-type-sequence path {
fill: #cdcb74;
}
.node-item-type-data path {
fill: #2576ad;
fill-opacity: 0.7;
}
.node-item-type-custom path {
fill: #e46d6d;
fill-opacity: 0.7;
}
.node-item-input {
cursor: pointer;
path {
fill: #fff;
}
}
.node-item-constant {
cursor: pointer;
path {
fill: #eee;
}
}
.node-item-undefined {
cursor: pointer;
path {
fill: #ca5353;
fill-opacity: 0.7;
}
}
.node-attribute {
cursor: pointer;
text {
font-size: 9px;
font-weight: normal;
}
}
.node-attribute path {
fill: #fff;
stroke-width: 0;
.dark & {
fill: #262629;
}
}
.graph-item-input {
cursor: pointer;
path {
fill: #e49d6d;
fill-opacity: 0.7;
}
}
.graph-item-output {
cursor: pointer;
path {
fill: #e4e06d;
fill-opacity: 0.9;
}
}
.edge-label text {
font-size: 10px;
}
.edge-path {
stroke: #666;
stroke-width: 1px;
fill: none;
}
#arrowhead-vee path {
fill: #666;
}
.edge-path-control-dependency {
stroke-dasharray: 3, 2;
}
.cluster .clusterGroup {
fill: #dce9ff;
stroke: #666;
stroke-width: 1px;
}
.node-item-function path {
fill: #9bb9e8;
fill-opacity: 0.7;
}
.cluster .clusterGroup-constant {
fill: #e8efe9;
}
.cluster .clusterGroup-control {
fill: #e4f8e9;
}
.cluster .clusterGroup-layer {
fill: #f4e0e0;
}
.cluster .clusterGroup-conv {
fill: #d3f0f6;
}
.cluster .clusterGroup-container {
fill: #f4e0e0;
}
.cluster .clusterGroup-wrapper {
fill: #d3f0f6;
}
.cluster .clusterGroup-activation {
fill: #deecef;
}
.cluster .clusterGroup-pool {
fill: #f5d7f0;
}
.cluster .clusterGroup-normalization {
fill: #f3dfea;
}
.cluster .clusterGroup-dropout {
fill: #c0e1ca;
}
.cluster .clusterGroup-pad {
fill: #c0e1ca;
}
.cluster .clusterGroup-shape {
fill: #f2edd9;
}
.cluster .clusterGroup-tensor {
fill: #d3d7f6;
}
.cluster .clusterGroup-transform {
fill: #f0efd5;
}
.cluster .clusterGroup-sequence {
fill: #f0efd5;
}
.cluster .clusterGroup-data {
fill: #bdd5e6;
}
.cluster .clusterGroup-custom {
fill: #f6d3d3;
}
.cluster .clusterButton {
fill-opacity: 0.3;
fill: #db989a;
stroke: #999;
cursor: pointer;
}
.cluster .button-text {
fill: #999;
}
.cluster.border {
display: none;
}
.select {
&.edge-path {
stroke: #1527c2;
stroke-width: 2px;
stroke-dasharray: 6px 3px;
stroke-dashoffset: 0;
animation: pulse 4s infinite linear;
}
.node.border {
stroke: #1527c2;
stroke-width: 2px;
stroke-dasharray: 6px 3px;
stroke-dashoffset: 0;
animation: pulse 4s infinite linear;
}
.cluster.border {
display: block;
stroke: #1527c2;
stroke-width: 2px;
stroke-dasharray: 6px 3px;
stroke-dashoffset: 0;
animation: pulse 4s infinite linear;
}
}
@keyframes pulse {
from {
stroke-dashoffset: 100px;
}
to {
stroke-dashoffset: 0;
}
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -62,6 +62,7 @@ async function start() {
const baseUri = snowpackEnv.SNOWPACK_PUBLIC_BASE_URI;
const apiUrl = snowpackEnv.SNOWPACK_PUBLIC_API_URL;
console.log('apiUrl', apiUrl, backend);
if (backend) {
const {createProxyMiddleware} = await import('http-proxy-middleware');
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册