未验证 提交 d6b10994 编写于 作者: P Peter Pan 提交者: GitHub

Audio (#717)

* rename pages to remove trailing 's'

* feat: navbar support dropdown menu

* feat: audio sample page

* chore: update dependencies

* style: fix lint

* v2.0.0-beta.46

* chore: fix import method

* v2.0.0-beta.47

* chore: remove unused code

* v2.0.0-beta.48

* fix: navbar submenu overflow when hover

* v2.0.0-beta.49
上级 e449a624
......@@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "2.0.0-beta.45",
"version": "2.0.0-beta.49",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
......
......@@ -38,13 +38,13 @@
"version": "yarn format && git add -A"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "3.5.0",
"@typescript-eslint/parser": "3.5.0",
"eslint": "7.3.1",
"@typescript-eslint/eslint-plugin": "3.6.1",
"@typescript-eslint/parser": "3.6.1",
"eslint": "7.4.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-react": "7.20.3",
"eslint-plugin-react-hooks": "4.0.5",
"eslint-plugin-react-hooks": "4.0.8",
"husky": "4.2.5",
"lerna": "3.22.1",
"lint-staged": "10.2.11",
......
{
"name": "@visualdl/cli",
"version": "2.0.0-beta.45",
"version": "2.0.0-beta.49",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -34,14 +34,14 @@
"dist"
],
"dependencies": {
"@visualdl/server": "2.0.0-beta.45",
"@visualdl/server": "2.0.0-beta.49",
"open": "7.0.4",
"ora": "4.0.4",
"pm2": "4.4.0",
"yargs": "15.3.1"
"yargs": "15.4.1"
},
"devDependencies": {
"@types/node": "14.0.14",
"@types/node": "14.0.23",
"@types/yargs": "15.0.5",
"cross-env": "7.0.2",
"ts-node": "8.10.2",
......
import {BlobResponse, blobFetcher} from '~/utils/fetch';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {
WithStyled,
primaryActiveColor,
primaryBackgroundColor,
primaryColor,
primaryFocusedColor,
rem,
textLightColor,
textLighterColor
} from '~/utils/style';
import {AudioPlayer} from '~/utils/audio';
import Icon from '~/components/Icon';
import PuffLoader from 'react-spinners/PuffLoader';
import RangeSlider from '~/components/RangeSlider';
import SyncLoader from 'react-spinners/SyncLoader';
import Tippy from '@tippyjs/react';
import mime from 'mime-types';
import moment from 'moment';
import {saveAs} from 'file-saver';
import styled from 'styled-components';
import useRequest from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
const Container = styled.div`
background-color: ${primaryBackgroundColor};
border-radius: ${rem(8)};
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 ${rem(20)};
> .control {
font-size: ${rem(16)};
line-height: 1;
margin: 0 ${rem(10)};
color: ${primaryColor};
cursor: pointer;
&.volumn {
font-size: ${rem(20)};
}
&.disabled {
color: ${textLightColor};
cursor: not-allowed;
}
&:hover {
color: ${primaryFocusedColor};
}
&:active {
color: ${primaryActiveColor};
}
}
> .slider {
flex-grow: 1;
padding: 0 ${rem(10)};
}
> .time {
color: ${textLighterColor};
font-size: ${rem(12)};
margin: 0 ${rem(5)};
}
`;
const VolumnSlider = styled.input.attrs(() => ({
type: 'range',
orient: 'vertical',
min: 0,
max: 100,
step: 1
}))`
writing-mode: bt-lr;
-webkit-appearance: slider-vertical;
margin: ${rem(15)} ${rem(18)};
width: ${rem(4)};
height: ${rem(100)};
cursor: pointer;
`;
const SLIDER_MAX = 100;
function formatDuration(seconds: number) {
const duration = moment.duration(seconds, 'seconds');
return (
String(Math.floor(duration.asMinutes())).padStart(2, '0') + ':' + String(duration.seconds()).padStart(2, '0')
);
}
export type AudioRef = {
save(filename: string): void;
};
export type AudioProps = {
src?: string;
cache?: number;
onLoading?: () => unknown;
onLoad?: (audio: {sampleRate: number; duration: number}) => unknown;
};
const Audio = React.forwardRef<AudioRef, AudioProps & WithStyled>(({src, cache, onLoading, onLoad, className}, ref) => {
const {t} = useTranslation('common');
const {data, error, loading} = useRequest<BlobResponse>(src ?? null, blobFetcher, {
dedupingInterval: cache ?? 2000
});
useImperativeHandle(ref, () => ({
save: (filename: string) => {
if (data) {
const ext = data.type ? mime.extension(data.type) : null;
saveAs(data.data, filename.replace(/[/\\?%*:|"<>]/g, '_') + (ext ? `.${ext}` : ''));
}
}
}));
const timer = useRef<number | null>(null);
const player = useRef<AudioPlayer | null>(null);
const [sliderValue, setSliderValue] = useState(0);
const [offset, setOffset] = useState(0);
const [duration, setDuration] = useState('00:00');
const [decoding, setDecoding] = useState(false);
const [playing, setPlaying] = useState(false);
const [volumn, setVolumn] = useState(100);
const [playAfterSeek, setPlayAfterSeek] = useState(false);
const play = useCallback(() => player.current?.play(), []);
const pause = useCallback(() => player.current?.pause(), []);
const toggle = useCallback(() => player.current?.toggle(), []);
const change = useCallback((value: number) => {
if (!player.current) {
return;
}
setOffset((value / SLIDER_MAX) * player.current.duration);
setSliderValue(value);
}, []);
const startSeek = useCallback(() => {
setPlayAfterSeek(playing);
pause();
}, [playing, pause]);
const stopSeek = useCallback(() => {
if (!player.current) {
return;
}
player.current.seek(offset);
if (playAfterSeek && offset < player.current.duration) {
play();
}
}, [play, offset, playAfterSeek]);
const toggleMute = useCallback(() => {
if (player.current) {
player.current.toggleMute();
setVolumn(player.current.volumn);
}
}, []);
const tick = useCallback(() => {
if (player.current) {
const current = player.current.current;
setOffset(current);
setSliderValue(Math.floor((current / player.current.duration) * SLIDER_MAX));
}
}, []);
const startTimer = useCallback(() => {
tick();
timer.current = (globalThis.setInterval(tick, 250) as unknown) as number;
}, [tick]);
const stopTimer = useCallback(() => {
if (player.current) {
if (player.current.current >= player.current.duration) {
tick();
}
}
if (timer.current) {
globalThis.clearInterval(timer.current);
timer.current = null;
}
}, [tick]);
useEffect(() => {
if (player.current) {
player.current.volumn = volumn;
}
}, [volumn]);
useEffect(() => {
if (process.browser) {
let p: AudioPlayer | null = null;
if (data) {
(async () => {
setDecoding(true);
onLoading?.();
setOffset(0);
setSliderValue(0);
setDuration('00:00');
p = new AudioPlayer({
onplay: () => {
setPlaying(true);
startTimer();
},
onstop: () => {
setPlaying(false);
stopTimer();
}
});
const buffer = await data.data.arrayBuffer();
await p.load(buffer);
setDecoding(false);
setDuration(formatDuration(p.duration));
onLoad?.({sampleRate: p.sampleRate, duration: p.duration});
player.current = p;
})();
}
return () => {
if (p) {
setPlaying(false);
p.dispose();
player.current = null;
}
};
}
}, [data, startTimer, stopTimer, onLoading, onLoad]);
const volumnIcon = useMemo(() => {
if (volumn === 0) {
return 'mute';
}
if (volumn <= 50) {
return 'volumn-low';
}
return 'volumn';
}, [volumn]);
if (loading) {
return <SyncLoader color={primaryColor} size="15px" />;
}
if (error) {
return <div>{t('common:error')}</div>;
}
return (
<Container className={className}>
<a className={`control ${decoding ? 'disabled' : ''}`} onClick={toggle}>
{decoding ? <PuffLoader size="16px" /> : <Icon type={playing ? 'pause' : 'play'} />}
</a>
<div className="slider">
<RangeSlider
min={0}
max={SLIDER_MAX}
step={1}
value={sliderValue}
disabled={decoding}
onChange={change}
onChangeStart={startSeek}
onChangeComplete={stopSeek}
/>
</div>
<span className="time">
{formatDuration(offset)}/{duration}
</span>
<Tippy
placement="top"
animation="shift-away-subtle"
interactive
hideOnClick={false}
content={<VolumnSlider value={volumn} onChange={e => setVolumn(+e.target.value)} />}
>
<a className="control volumn" onClick={toggleMute}>
<Icon type={volumnIcon} />
</a>
</Tippy>
</Container>
);
});
export default Audio;
......@@ -115,6 +115,7 @@ const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({
item.toggle ? (activeStatus[index] && item.activeTooltip) || item.tooltip : item.tooltip
}
placement={tooltipPlacement || 'top'}
theme="tooltip"
key={index}
>
{getToolboxItem(item, index)}
......
import {Argument as ArgumentType, Property as PropertyType} from '~/resource/graphs/types';
import {Argument as ArgumentType, Property as PropertyType} from '~/resource/graph/types';
import React, {FunctionComponent, useMemo, useState} from 'react';
import {borderColor, em, sameBorder, textLightColor, textLighterColor} from '~/utils/style';
......
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graphs/types';
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {backgroundColor, borderColor, contentHeight, position, primaryColor, rem, size} from '~/utils/style';
......@@ -111,7 +111,7 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
},
ref
) => {
const {t} = useTranslation('graphs');
const {t} = useTranslation('graph');
const [ready, setReady] = useState(false);
const [loading, setLoading] = useState(false);
......@@ -229,17 +229,17 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
items={[
{
icon: 'restore-size',
tooltip: t('graphs:restore-size'),
tooltip: t('graph:restore-size'),
onClick: () => dispatch('zoom-reset')
},
{
icon: 'zoom-out',
tooltip: t('graphs:zoom-out'),
tooltip: t('graph:zoom-out'),
onClick: () => dispatch('zoom-out')
},
{
icon: 'zoom-in',
tooltip: t('graphs:zoom-in'),
tooltip: t('graph:zoom-in'),
onClick: () => dispatch('zoom-in')
}
]}
......
......@@ -2,8 +2,8 @@ import React, {FunctionComponent} from 'react';
import {backgroundColor, em, size} from '~/utils/style';
import Icon from '~/components/Icon';
import Properties from '~/components/GraphsPage/Properties';
import {Properties as PropertiesType} from '~/resource/graphs/types';
import Properties from '~/components/GraphPage/Properties';
import {Properties as PropertiesType} from '~/resource/graph/types';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
......@@ -62,7 +62,7 @@ type ModelPropertiesDialogProps = {
};
const ModelPropertiesDialog: FunctionComponent<ModelPropertiesDialogProps> = ({data, onClose}) => {
const {t} = useTranslation('graphs');
const {t} = useTranslation('graph');
if (!data) {
return null;
......@@ -72,7 +72,7 @@ const ModelPropertiesDialog: FunctionComponent<ModelPropertiesDialogProps> = ({d
<Dialog>
<div className="modal">
<div className="modal-header">
<span className="modal-title">{t('graphs:model-properties')}</span>
<span className="modal-title">{t('graph:model-properties')}</span>
<a className="modal-close" onClick={() => onClose?.()}>
<Icon type="close" />
</a>
......
......@@ -2,8 +2,8 @@ import React, {FunctionComponent, useCallback} from 'react';
import {Trans, useTranslation} from '~/utils/i18n';
import {borderRadius, em, textLightColor} from '~/utils/style';
import {Documentation as DocumentationType} from '~/resource/graphs/types';
import GraphSidebar from '~/components/GraphsPage/GraphSidebar';
import {Documentation as DocumentationType} from '~/resource/graph/types';
import GraphSidebar from '~/components/GraphPage/GraphSidebar';
import styled from 'styled-components';
const Documentation = styled.div`
......@@ -74,7 +74,7 @@ type NodeDocumentationSidebarProps = {
};
const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps> = ({data, onClose}) => {
const {t} = useTranslation('graphs');
const {t} = useTranslation('graph');
const list = useCallback(
(items: {name: string; type?: string | string[]; description: string}[]) =>
......@@ -105,21 +105,21 @@ const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps>
);
return (
<GraphSidebar title={t('graphs:node-documentation')} onClose={onClose}>
<GraphSidebar title={t('graph:node-documentation')} onClose={onClose}>
<Documentation>
<h1>{data?.name}</h1>
{data?.summary && <p dangerouslySetInnerHTML={{__html: data.summary}}></p>}
{data?.description && <p dangerouslySetInnerHTML={{__html: data.description}}></p>}
{data?.attributes && (
<>
<h2>{t('graphs:documentation.attributes')}</h2>
<h2>{t('graph:documentation.attributes')}</h2>
{list(data.attributes)}
</>
)}
{data?.inputs && (
<>
<h2>
{t('graphs:documentation.inputs')}
{t('graph:documentation.inputs')}
{data?.inputs_range && ` (${data.inputs_range.replace(/&#8734;/g, '')})`}
</h2>
{list(data.inputs)}
......@@ -128,7 +128,7 @@ const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps>
{data?.outputs && (
<>
<h2>
{t('graphs:documentation.outputs')}
{t('graph:documentation.outputs')}
{data?.outputs_range && ` (${data.outputs_range.replace(/&#8734;/g, '')})`}
</h2>
{list(data.outputs)}
......@@ -136,7 +136,7 @@ const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps>
)}
{data?.type_constraints && (
<>
<h2>{t('graphs:documentation.type-constraints')}</h2>
<h2>{t('graph:documentation.type-constraints')}</h2>
{list(
data.type_constraints.map(({type_param_str, allowed_type_strs, description}) => ({
name: type_param_str,
......@@ -148,7 +148,7 @@ const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps>
)}
{data?.examples && (
<>
<h2>{t('graphs:documentation.examples')}</h2>
<h2>{t('graph:documentation.examples')}</h2>
{data.examples.map((example, index) => (
<React.Fragment key={index}>
<h3>{example.summary}</h3>
......@@ -159,7 +159,7 @@ const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps>
)}
{data?.references && (
<>
<h2>{t('graphs:documentation.references')}</h2>
<h2>{t('graph:documentation.references')}</h2>
<ul>
{data.references.map((reference, index) => (
<li key={index} dangerouslySetInnerHTML={{__html: reference.description}}></li>
......@@ -169,10 +169,10 @@ const NodeDocumentationSidebar: FunctionComponent<NodeDocumentationSidebarProps>
)}
{data && data.domain && data.since_version && data.support_level && (
<>
<h2>{t('graphs:documentation.support')}</h2>
<h2>{t('graph:documentation.support')}</h2>
<dl>
{/* prettier-ignore */}
<Trans i18nKey="graphs:documentation.support-info">
<Trans i18nKey="graph:documentation.support-info">
In domain <code>{{domain: data.domain}}</code> since version <code>{{since_version: data.since_version}}</code> at support level <code>{{support_level: data.support_level}}</code>.
</Trans>
</dl>
......
import React, {FunctionComponent} from 'react';
import GraphSidebar from '~/components/GraphsPage/GraphSidebar';
import Properties from '~/components/GraphsPage/Properties';
import {Properties as PropertiesType} from '~/resource/graphs/types';
import GraphSidebar from '~/components/GraphPage/GraphSidebar';
import Properties from '~/components/GraphPage/Properties';
import {Properties as PropertiesType} from '~/resource/graph/types';
import {useTranslation} from '~/utils/i18n';
type NodePropertiesSidebarProps = {
......@@ -16,10 +16,10 @@ const NodePropertiesSidebar: FunctionComponent<NodePropertiesSidebarProps> = ({
onClose,
showNodeDodumentation
}) => {
const {t} = useTranslation('graphs');
const {t} = useTranslation('graph');
return (
<GraphSidebar title={t('graphs:node-properties')} onClose={onClose}>
<GraphSidebar title={t('graph:node-properties')} onClose={onClose}>
<Properties {...data} showNodeDodumentation={showNodeDodumentation} />
</GraphSidebar>
);
......
import React, {FunctionComponent} from 'react';
import {Properties as PropertiesType} from '~/resource/graphs/types';
import Property from '~/components/GraphsPage/Property';
import {Properties as PropertiesType} from '~/resource/graph/types';
import Property from '~/components/GraphPage/Property';
import {em} from '~/utils/style';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
......@@ -18,13 +18,13 @@ type PropertiesProps = PropertiesType & {
};
const Properties: FunctionComponent<PropertiesProps> = ({properties, groups, expand, showNodeDodumentation}) => {
const {t} = useTranslation('graphs');
const {t} = useTranslation('graph');
return (
<>
{properties?.map((property, index) => (
<Property
name={t(`graphs:properties.${property.name}`)}
name={t(`graph:properties.${property.name}`)}
values={property.values}
key={index}
showNodeDodumentation={showNodeDodumentation}
......@@ -32,7 +32,7 @@ const Properties: FunctionComponent<PropertiesProps> = ({properties, groups, exp
))}
{groups?.map((group, index) => (
<React.Fragment key={index}>
<Header>{t(`graphs:properties.${group.name}`)}</Header>
<Header>{t(`graph:properties.${group.name}`)}</Header>
{group.properties?.map((property, anotherIndex) => (
<Property
{...property}
......
import {Argument as ArgumentType, NameValues, Property as PropertyType} from '~/resource/graphs/types';
import {Argument as ArgumentType, NameValues, Property as PropertyType} from '~/resource/graph/types';
import React, {FunctionComponent} from 'react';
import {ellipsis, em, sameBorder} from '~/utils/style';
import Argument from '~/components/GraphsPage/Argument';
import Argument from '~/components/GraphPage/Argument';
import styled from 'styled-components';
const Wrapper = styled.div`
......
import React, {FunctionComponent, useCallback, useEffect, useState} from 'react';
import {SearchItem, SearchResult} from '~/resource/graphs/types';
import {SearchItem, SearchResult} from '~/resource/graph/types';
import {
backgroundColor,
backgroundFocusedColor,
......@@ -145,7 +145,7 @@ type SearchProps = {
};
const Search: FunctionComponent<SearchProps> = ({text, data, onChange, onSelect, onActive, onDeactive}) => {
const {t} = useTranslation(['graphs', 'common']);
const {t} = useTranslation(['graph', 'common']);
const [search, setSearch] = useState(text ?? '');
const [searching, setSearching] = useState(false);
......@@ -209,7 +209,7 @@ const Search: FunctionComponent<SearchProps> = ({text, data, onChange, onSelect,
</List>
</Wrapper>
) : (
<Empty>{t('graphs:nothing-matched')}</Empty>
<Empty>{t('graph:nothing-matched')}</Empty>
))}
</>
);
......
......@@ -60,7 +60,7 @@ type UploaderProps = {
};
const Uploader: FunctionComponent<UploaderProps> = ({onClickUpload, onDropFiles}) => {
const {t} = useTranslation('graphs');
const {t} = useTranslation('graph');
const [actived, setActived] = useState(false);
const onClick = useCallback(() => onClickUpload?.(), [onClickUpload]);
......@@ -92,20 +92,20 @@ const Uploader: FunctionComponent<UploaderProps> = ({onClickUpload, onDropFiles}
onDragLeave={onDragLeave}
>
<Icon type="upload" className="upload-icon" />
<span>{t('upload-tip')}</span>
<span>{t('graph:upload-tip')}</span>
<Button type="primary" rounded className="upload-button" onClick={onClick}>
{t('upload-model')}
{t('graph:upload-model')}
</Button>
</DropZone>
<SupportTable>
<tbody>
<tr>
<td>{t('supported-model')}</td>
<td>{t('supported-model-list')}</td>
<td>{t('graph:supported-model')}</td>
<td>{t('graph:supported-model-list')}</td>
</tr>
<tr>
<td>{t('experimental-supported-model')}</td>
<td>{t('experimental-supported-model-list')}</td>
<td>{t('graph:experimental-supported-model')}</td>
<td>{t('graph:experimental-supported-model-list')}</td>
</tr>
</tbody>
</SupportTable>
......
......@@ -61,7 +61,7 @@ const HighDimensionalChart: FunctionComponent<HighDimensionalChartProps> = ({
const {data, error, loading} = useRunningRequest<Data>(
run && tag
? `/embeddings/embedding?${queryString.stringify({
? `/embedding/embedding?${queryString.stringify({
run,
tag,
dimension: Number.parseInt(dimension, 10),
......
import {BlobResponse, blobFetcher} from '~/utils/fetch';
import React, {useImperativeHandle, useLayoutEffect, useState} from 'react';
import {WithStyled, primaryColor} from '~/utils/style';
import GridLoader from 'react-spinners/GridLoader';
import mime from 'mime-types';
import {primaryColor} from '~/utils/style';
import {saveAs} from 'file-saver';
import useRequest from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
......@@ -17,7 +17,7 @@ type ImageProps = {
cache?: number;
};
const Image = React.forwardRef<ImageRef, ImageProps>(({src, cache}, ref) => {
const Image = React.forwardRef<ImageRef, ImageProps & WithStyled>(({src, cache, className}, ref) => {
const {t} = useTranslation('common');
const [url, setUrl] = useState('');
......@@ -55,7 +55,7 @@ const Image = React.forwardRef<ImageRef, ImageProps>(({src, cache}, ref) => {
return <div>{t('common:error')}</div>;
}
return <img src={url} />;
return <img className={className} src={url} />;
});
export default Image;
import {Link, config, i18n, useTranslation} from '~/utils/i18n';
import React, {FunctionComponent, useMemo} from 'react';
import React, {FunctionComponent, useEffect, useMemo, useState} from 'react';
import {
backgroundFocusedColor,
border,
borderRadius,
navbarBackgroundColor,
navbarHighlightColor,
navbarHoverBackgroundColor,
primaryColor,
rem,
size,
textColor,
textInvertColor,
transitionProps
} from '~/utils/style';
import useNavItems, {NavItem as BaseNavItem} from '~/hooks/useNavItems';
import Icon from '~/components/Icon';
import {InitConfig} from '@visualdl/i18n';
import Language from '~/components/Language';
import Tippy from '@tippyjs/react';
import ee from '~/utils/event';
import {getApiToken} from '~/utils/fetch';
import queryString from 'query-string';
import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems';
import {useRouter} from 'next/router';
const API_TOKEN_KEY = process.env.API_TOKEN_KEY;
......@@ -87,6 +92,32 @@ const NavItem = styled.a<{active?: boolean}>`
}
`;
const SubNav = styled.div`
overflow: hidden;
border-radius: ${borderRadius};
`;
const NavItemChild = styled.a<{active?: boolean}>`
display: block;
padding: 0 ${rem(20)};
line-height: 3em;
&,
&:visited {
color: ${props => (props.active ? primaryColor : textColor)};
}
&:hover {
background-color: ${backgroundFocusedColor};
}
`;
interface NavItem extends BaseNavItem {
cid?: string;
active: boolean;
children?: ({active: boolean} & NonNullable<BaseNavItem['children']>[number])[];
}
const changeLanguage = () => {
const {language} = i18n;
const {allLanguages} = config;
......@@ -95,13 +126,75 @@ const changeLanguage = () => {
i18n.changeLanguage(nextLanguage);
};
const NavbarItem = React.forwardRef<HTMLAnchorElement, NavItem>(({path, id, cid, active}, ref) => {
const {t} = useTranslation('common');
const name = useMemo(() => (cid ? `${t(id)} - ${t(cid)}` : t(id)), [t, id, cid]);
if (path) {
// https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag
return (
<Link href={path} passHref>
<NavItem active={active} ref={ref}>
<span className="nav-text">{name}</span>
</NavItem>
</Link>
);
}
return (
<NavItem active={active} ref={ref}>
<span className="nav-text">{name}</span>
</NavItem>
);
});
NavbarItem.displayName = 'NavbarItem';
const Navbar: FunctionComponent = () => {
const {t, i18n} = useTranslation('common');
const {pathname, basePath} = useRouter();
const navItems = useNavItems();
const currentPath = useMemo(() => pathname.replace(basePath, ''), [pathname, basePath]);
const path = useMemo(() => pathname.replace(basePath, ''), [pathname, basePath]);
const navItems = useNavItems();
const [items, setItems] = useState<NavItem[]>([]);
useEffect(() => {
setItems(oldItems =>
navItems.map(item => {
const children = item.children?.map(child => ({
...child,
active: child.path === currentPath
}));
if (item.children && !item.path) {
const child = item.children.find(child => child.path === currentPath);
if (child) {
return {
...item,
cid: child.id,
path: currentPath,
active: true,
children
};
} else {
const oldItem = oldItems.find(oldItem => oldItem.id === item.id);
if (oldItem) {
return {
...item,
...oldItem,
active: false,
children
};
}
}
}
return {
...item,
active: currentPath === item.path,
children
};
})
);
}, [navItems, currentPath]);
const indexUrl = useMemo(() => {
// TODO: fix type
......@@ -129,16 +222,35 @@ const Navbar: FunctionComponent = () => {
<img alt="PaddlePaddle" src={`${PUBLIC_PATH}/images/logo.svg`} />
<span>VisualDL</span>
</Logo>
{navItems.map(name => {
const href = `/${name}`;
return (
// https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag
<Link href={href} key={name} passHref>
<NavItem active={path === href}>
<span className="nav-text">{t(name)}</span>
</NavItem>
</Link>
);
{items.map(item => {
if (item.children) {
return (
<Tippy
placement="bottom-start"
animation="shift-away-subtle"
interactive
arrow={false}
offset={[0, 0]}
hideOnClick={false}
role="menu"
content={
<SubNav>
{item.children.map(child => (
<Link href={child.path} key={child.id} passHref>
<NavItemChild active={child.active}>
{t(item.id)} - {t(child.id)}
</NavItemChild>
</Link>
))}
</SubNav>
}
key={item.active ? `${item.id}-activated` : item.id}
>
<NavbarItem {...item} />
</Tippy>
);
}
return <NavbarItem {...item} key={item.id} />;
})}
</div>
<div className="right">
......
......@@ -180,19 +180,19 @@ const PRCurveChart: FunctionComponent<PRCurveChartProps> = ({cid, runs, tag, run
{
icon: 'maximize',
activeIcon: 'minimize',
tooltip: t('scalars:maximize'),
activeTooltip: t('scalars:minimize'),
tooltip: t('pr-curve:maximize'),
activeTooltip: t('pr-curve:minimize'),
toggle: true,
onClick: toggleMaximized
},
{
icon: 'restore-size',
tooltip: t('scalars:restore'),
tooltip: t('pr-curve:restore'),
onClick: () => echart.current?.restore()
},
{
icon: 'download',
tooltip: t('scalars:download-image'),
tooltip: t('pr-curve:download-image'),
onClick: () => echart.current?.saveAsImage()
}
]}
......
......@@ -6,10 +6,13 @@ import {
em,
half,
position,
primaryActiveColor,
primaryColor,
primaryFocusedColor,
sameBorder,
size,
textLighterColor
textLighterColor,
transitionProps
} from '~/utils/style';
import styled from 'styled-components';
......@@ -30,8 +33,19 @@ const Wrapper = styled.div<{disabled?: boolean}>`
display: none;
}
--color: ${primaryColor};
&:hover {
--color: ${primaryFocusedColor};
}
&:active {
--color: ${primaryActiveColor};
}
&__track {
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
${transitionProps('width', {duration: '30ms'})}
&--background {
${size(railHeight, '100%')}
......@@ -44,7 +58,7 @@ const Wrapper = styled.div<{disabled?: boolean}>`
&--active {
height: ${railHeight};
position: absolute;
background-color: ${props => (props.disabled ? textLighterColor : primaryColor)};
background-color: ${props => (props.disabled ? textLighterColor : 'var(--color)')};
border-radius: ${half(railHeight)};
outline: none;
}
......@@ -53,6 +67,7 @@ const Wrapper = styled.div<{disabled?: boolean}>`
&__slider-container {
top: -${half(`${thumbSize} - ${railHeight}`)};
margin-left: -${half(thumbSize)};
${transitionProps('left', {duration: '30ms'})}
}
&__slider {
......@@ -60,7 +75,7 @@ const Wrapper = styled.div<{disabled?: boolean}>`
${props =>
sameBorder({
width: em(3),
color: props.disabled ? textLighterColor : primaryColor,
color: props.disabled ? textLighterColor : 'var(--color)',
radius: half(thumbSize)
})}
background-color: ${backgroundColor};
......@@ -75,11 +90,13 @@ type RangeSliderProps = {
value?: number;
disabled?: boolean;
onChange?: (value: number) => unknown;
onChangeStart?: () => unknown;
onChangeComplete?: () => unknown;
};
const RangeSlider: FunctionComponent<RangeSliderProps & WithStyled> = ({
onChange,
onChangeStart,
onChangeComplete,
className,
min,
......@@ -103,6 +120,7 @@ const RangeSlider: FunctionComponent<RangeSliderProps & WithStyled> = ({
disabled={disabled}
value={value as number}
onChange={onChangeRange}
onChangeStart={() => onChangeStart?.()}
onChangeComplete={() => onChangeComplete?.()}
/>
</Wrapper>
......
......@@ -42,7 +42,11 @@ const RunningToggle: FunctionComponent<RunningToggleProps & WithStyled> = ({runn
return (
<Wrapper className={className}>
<span>{t(state ? 'running' : 'stopped')}</span>
<Tippy content={t(state ? 'stop-realtime-refresh' : 'start-realtime-refresh') + ''} hideOnClick={false}>
<Tippy
theme="tooltip"
content={t(state ? 'stop-realtime-refresh' : 'start-realtime-refresh') + ''}
hideOnClick={false}
>
<div>
<StyledButton onClick={() => setState(s => !s)} type={state ? 'danger' : 'primary'} rounded>
{t(state ? 'stop' : 'run')}
......
import Audio, {AudioProps, AudioRef} from '~/components/Audio';
import React, {FunctionComponent, useCallback, useState} from 'react';
import SampleChart, {SampleChartBaseProps} from '~/components/SamplePage/SampleChart';
import {rem, size, textLighterColor} from '~/utils/style';
import {format} from 'd3-format';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const formatter = format('.5~s');
const StyledAudio = styled(Audio)`
${size('100%')}
flex-shrink: 1;
`;
const AudioInfo = styled.span`
color: ${textLighterColor};
font-size: ${rem(12)};
`;
const cache = 5 * 60 * 1000;
type AudioChartProps = SampleChartBaseProps;
const AudioChart: FunctionComponent<AudioChartProps> = ({...props}) => {
const {t} = useTranslation(['sample', 'common']);
const [sampleRate, setSampleRate] = useState<string>('--Hz');
const onLoading = useCallback(() => setSampleRate('--Hz'), []);
const onLoad = useCallback<NonNullable<AudioProps['onLoad']>>(
audio => setSampleRate(formatter(audio.sampleRate) + 'Hz'),
[]
);
const content = useCallback(
(ref: React.RefObject<AudioRef>, src: string) => (
<StyledAudio src={src} cache={cache} onLoading={onLoading} onLoad={onLoad} ref={ref} />
),
[onLoading, onLoad]
);
return (
<SampleChart
type="audio"
cache={cache}
footer={
<AudioInfo>
{t('sample:sample-rate')}
{t('common:colon')}
{sampleRate}
</AudioInfo>
}
content={content}
{...props}
/>
);
};
export default AudioChart;
import Image, {ImageRef} from '~/components/Image';
import React, {FunctionComponent, useCallback} from 'react';
import SampleChart, {SampleChartBaseProps} from '~/components/SamplePage/SampleChart';
import {size, transitionProps} from '~/utils/style';
import styled from 'styled-components';
const StyledImage = styled(Image)<{brightness?: number; contrast?: number; fit?: boolean}>`
${size('100%')}
filter: brightness(${props => props.brightness ?? 1}) contrast(${props => props.contrast ?? 1});
${transitionProps('filter')}
object-fit: ${props => (props.fit ? 'contain' : 'scale-down')};
flex-shrink: 1;
`;
const cache = 5 * 60 * 1000;
type ImageChartProps = {
brightness?: number;
contrast?: number;
fit?: boolean;
} & SampleChartBaseProps;
const ImageChart: FunctionComponent<ImageChartProps> = ({brightness, contrast, fit, ...props}) => {
const content = useCallback(
(ref: React.RefObject<ImageRef>, src: string) => (
<StyledImage src={src} cache={cache} ref={ref} brightness={brightness} contrast={contrast} fit={fit} />
),
[brightness, contrast, fit]
);
return <SampleChart type="image" cache={cache} content={content} {...props} />;
};
export default ImageChart;
import Image, {ImageRef} from '~/components/Image';
import React, {FunctionComponent, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {ellipsis, em, primaryColor, rem, size, textLightColor, transitionProps} from '~/utils/style';
import {ellipsis, em, primaryColor, rem, size, textLightColor} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import GridLoader from 'react-spinners/GridLoader';
import StepSlider from '~/components/SamplesPage/StepSlider';
import {Run} from '~/types';
import StepSlider from '~/components/SamplePage/StepSlider';
import {formatTime} from '~/utils';
import isEmpty from 'lodash/isEmpty';
import queryString from 'query-string';
......@@ -27,7 +27,7 @@ const Wrapper = styled.div`
}
`;
const Title = styled.div`
const Title = styled.div<{color: string}>`
display: flex;
justify-content: space-between;
align-items: center;
......@@ -48,6 +48,16 @@ const Title = styled.div`
flex-shrink: 0;
flex-grow: 0;
color: ${textLightColor};
&::before {
content: '';
display: inline-block;
${size(rem(5), rem(17))}
margin-right: ${rem(8)};
border-radius: ${rem(2.5)};
vertical-align: middle;
background-color: ${props => props.color};
}
}
`;
......@@ -59,46 +69,47 @@ const Container = styled.div<{brightness?: number; contrast?: number; fit?: bool
justify-content: center;
align-items: center;
overflow: hidden;
> img {
${size('100%')}
filter: brightness(${props => props.brightness ?? 1}) contrast(${props => props.contrast ?? 1});
${transitionProps('filter')}
object-fit: ${props => (props.fit ? 'contain' : 'scale-down')};
flex-shrink: 1;
}
`;
const Toolbox = styled(ChartToolbox)`
const Footer = styled.div`
margin-bottom: ${rem(18)};
display: flex;
align-items: center;
justify-content: space-between;
`;
type ImageData = {
type SampleData = {
step: number;
wallTime: number;
};
type SampleChartProps = {
run: string;
export type SampleChartBaseProps = {
run: Run;
tag: string;
brightness?: number;
contrast?: number;
fit?: boolean;
running?: boolean;
};
const getImageUrl = (index: number, run: string, tag: string, wallTime: number): string =>
`/images/image?${queryString.stringify({index, ts: wallTime, run, tag})}`;
type SampleChartRef = {
save: (filename: string) => void;
};
type SampleChartProps = {
type: 'image' | 'audio';
cache: number;
footer?: JSX.Element;
content: (ref: React.RefObject<SampleChartRef>, src: string) => JSX.Element;
} & SampleChartBaseProps;
const cacheValidity = 5 * 60 * 1000;
const getUrl = (type: string, index: number, run: string, tag: string, wallTime: number): string =>
`/${type}/${type}?${queryString.stringify({index, ts: wallTime, run, tag})}`;
const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness, contrast, fit, running}) => {
const {t, i18n} = useTranslation(['samples', 'common']);
const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, running, type, cache, footer, content}) => {
const {t, i18n} = useTranslation(['sample', 'common']);
const image = useRef<ImageRef>(null);
const sampleRef = useRef<SampleChartRef>(null);
const {data, error, loading} = useRunningRequest<ImageData[]>(
`/images/list?${queryString.stringify({run, tag})}`,
const {data, error, loading} = useRunningRequest<SampleData[]>(
`/${type}/list?${queryString.stringify({run: run.label, tag})}`,
!!running
);
......@@ -118,23 +129,23 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness,
const wallTime = useMemo(() => data?.[step].wallTime ?? 0, [data, step]);
const cacheImageSrc = useCallback(() => {
const cacheSrc = useCallback(() => {
if (!data) {
return;
}
const imageUrl = getImageUrl(step, run, tag, wallTime);
const url = getUrl(type, step, run.label, tag, wallTime);
cached.current[step] = {
src: imageUrl,
src: url,
timer: setTimeout(() => {
((s: number) => delete cached.current[s])(step);
}, cacheValidity)
}, cache)
};
setSrc(imageUrl);
}, [step, run, tag, wallTime, data]);
setSrc(url);
}, [type, step, run.label, tag, wallTime, data, cache]);
const saveImage = useCallback(() => {
image.current?.save(`${run}-${tag}-${steps[step]}-${wallTime.toString().replace(/\./, '_')}`);
}, [run, tag, steps, step, wallTime]);
const download = useCallback(() => {
sampleRef.current?.save(`${run.label}-${tag}-${steps[step]}-${wallTime.toString().replace(/\./, '_')}`);
}, [run.label, tag, steps, step, wallTime]);
useEffect(() => {
if (cached.current[step]) {
......@@ -142,14 +153,14 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness,
setSrc(cached.current[step].src);
} else if (isEmpty(cached.current)) {
// first load, return immediately
cacheImageSrc();
cacheSrc();
} else {
timer.current = setTimeout(cacheImageSrc, 500);
timer.current = setTimeout(cacheSrc, 500);
return () => {
timer.current && clearTimeout(timer.current);
};
}
}, [step, cacheImageSrc]);
}, [step, cacheSrc]);
const [viewed, setViewed] = useState<boolean>(false);
......@@ -184,30 +195,34 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness,
if (isEmpty(data)) {
return <span>{t('common:empty')}</span>;
}
return <Image ref={image} src={src} cache={cacheValidity} />;
}, [viewed, loading, error, data, step, src, t]);
if (src) {
return content(sampleRef, src);
}
return null;
}, [viewed, loading, error, data, step, src, t, content]);
return (
<Wrapper>
<Title>
<Title color={run.colors[0]}>
<h4>{tag}</h4>
<span>{run}</span>
<span>{run.label}</span>
</Title>
<StepSlider value={step} steps={steps} onChange={setStep} onChangeComplete={cacheImageSrc}>
<StepSlider value={step} steps={steps} onChange={setStep} onChangeComplete={cacheSrc}>
{formatTime(wallTime * 1000, i18n.language)}
</StepSlider>
<Container ref={container} brightness={brightness} contrast={contrast} fit={fit}>
{Content}
</Container>
<Toolbox
items={[
{
icon: 'download',
tooltip: t('samples:download-image'),
onClick: saveImage
}
]}
/>
<Container ref={container}>{Content}</Container>
<Footer>
<ChartToolbox
items={[
{
icon: 'download',
tooltip: t(`sample:download-${type}`),
onClick: download
}
]}
/>
{footer}
</Footer>
</Wrapper>
);
};
......
......@@ -29,7 +29,7 @@ type StepSliderProps = {
};
const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, onChangeComplete, value, steps, children}) => {
const {t} = useTranslation('samples');
const {t} = useTranslation('sample');
const [step, setStep] = useState(value);
useEffect(() => setStep(value), [value]);
......@@ -45,7 +45,7 @@ const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, onChangeCompl
return (
<>
<Label>
<span>{`${t('samples:step')}: ${steps[step] ?? '...'}`}</span>
<span>{`${t('sample:step')}: ${steps[step] ?? '...'}`}</span>
{children && <span>{children}</span>}
</Label>
<FullWidthRangeSlider
......
......@@ -13,7 +13,7 @@ import {
tooltip,
transform,
xAxisMap
} from '~/resource/scalars';
} from '~/resource/scalar';
import LineChart, {LineChartRef, XAxisType, YAxisType} from '~/components/LineChart';
import React, {FunctionComponent, useCallback, useMemo, useRef, useState} from 'react';
import {rem, size} from '~/utils/style';
......@@ -43,8 +43,8 @@ const rangeWasm = () =>
scalar_range(params.datasets, params.outlier)
);
const smoothWorker = () => new Worker('~/worker/scalars/smooth.worker.ts', {type: 'module'});
const rangeWorker = () => new Worker('~/worker/scalars/range.worker.ts', {type: 'module'});
const smoothWorker = () => new Worker('~/worker/scalar/smooth.worker.ts', {type: 'module'});
const rangeWorker = () => new Worker('~/worker/scalar/range.worker.ts', {type: 'module'});
const Wrapper = styled.div`
${size('100%', '100%')}
......@@ -92,12 +92,12 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
outlier,
running
}) => {
const {t, i18n} = useTranslation(['scalars', 'common']);
const {t, i18n} = useTranslation(['scalar', 'common']);
const echart = useRef<LineChartRef>(null);
const {data: datasets, error, loading} = useRunningRequest<(ScalarDataset | null)[]>(
runs.map(run => `/scalars/list?${queryString.stringify({run: run.label, tag})}`),
runs.map(run => `/scalar/list?${queryString.stringify({run: run.label, tag})}`),
!!running,
(...urls) => cycleFetcher(urls)
);
......@@ -220,25 +220,25 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
{
icon: 'maximize',
activeIcon: 'minimize',
tooltip: t('scalars:maximize'),
activeTooltip: t('scalars:minimize'),
tooltip: t('scalar:maximize'),
activeTooltip: t('scalar:minimize'),
toggle: true,
onClick: toggleMaximized
},
{
icon: 'restore-size',
tooltip: t('scalars:restore'),
tooltip: t('scalar:restore'),
onClick: () => echart.current?.restore()
},
{
icon: 'log-axis',
tooltip: t('scalars:axis'),
tooltip: t('scalar:axis'),
toggle: true,
onClick: toggleYAxisType
},
{
icon: 'download',
tooltip: t('scalars:download-image'),
tooltip: t('scalar:download-image'),
onClick: () => echart.current?.saveAsImage()
}
]}
......
import {useEffect, useState} from 'react';
import {useCallback, useEffect, useState} from 'react';
import ee from '~/utils/event';
import {fetcher} from '~/utils/fetch';
import intersection from 'lodash/intersection';
import useRequest from '~/hooks/useRequest';
enum Pages {
Scalars = 'scalars',
Scalar = 'scalar',
Histogram = 'histogram',
Samples = 'samples',
Graphs = 'graphs',
Image = 'image',
Audio = 'audio',
Graph = 'graph',
HighDimensional = 'high-dimensional',
PRCurve = 'pr-curve'
}
const pages = [
Pages.Scalars,
Pages.Histogram,
Pages.Samples,
Pages.Graphs,
Pages.HighDimensional,
Pages.PRCurve
] as const;
export interface NavItem {
id: Pages | string;
visible?: boolean;
path?: string;
children?: {
id: NavItem['id'];
path: string;
}[];
}
const pages: NavItem[] = [
{
id: Pages.Scalar,
path: `/${Pages.Scalar}`
},
{
id: Pages.Histogram,
path: `/${Pages.Histogram}`
},
{
id: 'sample',
visible: true,
children: [
{
id: Pages.Image,
path: `/sample/${Pages.Image}`
},
{
id: Pages.Audio,
path: `/sample/${Pages.Audio}`
}
]
},
{
id: Pages.Graph,
path: `/${Pages.Graph}`
},
{
id: Pages.HighDimensional,
path: `/${Pages.HighDimensional}`
},
{
id: Pages.PRCurve,
path: `/${Pages.PRCurve}`
}
];
export const navMap = {
scalar: Pages.Scalars,
scalar: Pages.Scalar,
histogram: Pages.Histogram,
image: Pages.Samples,
graph: Pages.Graphs,
image: Pages.Image,
audio: Pages.Audio,
graph: Pages.Graph,
embeddings: Pages.HighDimensional,
pr_curve: Pages.PRCurve
} as const;
const useNavItems = () => {
const [components, setComponents] = useState<Pages[]>([]);
const [components, setComponents] = useState<NavItem[]>([]);
const {data, mutate} = useRequest<(keyof typeof navMap)[]>('/components', fetcher, {
refreshInterval: components.length ? 61 * 1000 : 15 * 1000,
......@@ -53,9 +92,33 @@ const useNavItems = () => {
};
}, [mutate]);
const filterPages = useCallback(
(pages: NavItem[]) => {
const items: string[] = data?.map(item => navMap[item]) ?? [];
return pages.reduce<NavItem[]>((m, page) => {
if (!page.visible && !items.includes(page.id)) {
return m;
}
if (page.children) {
const children = filterPages(page.children);
if (children.length) {
m.push({
...page,
children: children as NavItem['children']
});
}
} else {
m.push(page);
}
return m;
}, []);
},
[data]
);
useEffect(() => {
setComponents(intersection(pages, data?.map(component => navMap[component]) ?? []));
}, [data]);
setComponents(filterPages(pages));
}, [data, filterPages]);
return components;
};
......
import {Run, Tag} from '~/types';
import {Run, Tag, TagWithSingleRun} from '~/types';
import {color, colorAlt} from '~/utils/chart';
import {useCallback, useEffect, useMemo, useReducer} from 'react';
......@@ -183,3 +183,13 @@ const useTagFilter = (type: string, running: boolean) => {
};
export default useTagFilter;
export function ungroup(tags: Tag[]) {
return tags.reduce<TagWithSingleRun[]>((prev, {runs, ...item}) => {
Array.prototype.push.apply(
prev,
runs.map(run => ({...item, run, id: `${item.label}-${run.label}`}))
);
return prev;
}, []);
}
{
"name": "@visualdl/core",
"version": "2.0.0-beta.44",
"version": "2.0.0-beta.49",
"title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
......@@ -33,10 +33,10 @@
"test:coverage": "NODE_OPTIONS=\"--max-old-space-size=4096\" jest --coverage"
},
"dependencies": {
"@tippyjs/react": "4.0.5",
"@visualdl/i18n": "2.0.0-beta.44",
"@visualdl/netron": "2.0.0-beta.44",
"@visualdl/wasm": "2.0.0-beta.44",
"@tippyjs/react": "4.1.0",
"@visualdl/i18n": "2.0.0-beta.49",
"@visualdl/netron": "2.0.0-beta.49",
"@visualdl/wasm": "2.0.0-beta.49",
"bignumber.js": "9.0.0",
"d3-format": "1.4.4",
"echarts": "4.8.0",
......@@ -44,7 +44,7 @@
"eventemitter3": "4.0.4",
"file-saver": "2.0.2",
"isomorphic-unfetch": "3.0.0",
"lodash": "4.17.15",
"lodash": "4.17.19",
"mime-types": "2.1.27",
"moment": "2.27.0",
"next": "9.4.4",
......@@ -57,28 +57,28 @@
"react-input-range": "1.3.0",
"react-is": "16.13.1",
"react-spinners": "0.9.0",
"react-toastify": "6.0.6",
"react-toastify": "6.0.8",
"save-svg-as-png": "1.4.17",
"styled-components": "5.1.1",
"swr": "0.2.3",
"tippy.js": "6.2.3"
"tippy.js": "6.2.5"
},
"devDependencies": {
"@babel/core": "7.10.4",
"@babel/core": "7.10.5",
"@types/d3-format": "1.3.1",
"@types/echarts": "4.6.3",
"@types/enzyme": "3.10.5",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/file-saver": "2.0.1",
"@types/jest": "26.0.3",
"@types/jest": "26.0.4",
"@types/lodash": "4.14.157",
"@types/mime-types": "2.1.0",
"@types/node": "14.0.14",
"@types/node": "14.0.23",
"@types/nprogress": "0.2.0",
"@types/react": "16.9.41",
"@types/react": "16.9.43",
"@types/react-dom": "16.9.8",
"@types/styled-components": "5.1.0",
"@visualdl/mock": "2.0.0-beta.44",
"@types/styled-components": "5.1.1",
"@visualdl/mock": "2.0.0-beta.49",
"babel-plugin-emotion": "10.0.33",
"babel-plugin-styled-components": "1.10.7",
"babel-plugin-typescript-to-proptypes": "1.3.2",
......@@ -86,13 +86,13 @@
"core-js": "3.6.5",
"cross-env": "7.0.2",
"css-loader": "3.6.0",
"enhanced-resolve": "4.2.0",
"enhanced-resolve": "4.3.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.2",
"enzyme-to-json": "3.5.0",
"jest": "26.1.0",
"ora": "4.0.4",
"ts-jest": "26.1.1",
"ts-jest": "26.1.2",
"typescript": "3.9.6",
"worker-plugin": "4.0.3"
},
......
import Aside, {AsideSection} from '~/components/Aside';
import {BlobResponse, blobFetcher} from '~/utils/fetch';
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graphs/types';
import Graph, {GraphRef} from '~/components/GraphsPage/Graph';
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graph/types';
import GraphComponent, {GraphRef} from '~/components/GraphPage/Graph';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {primaryColor, rem, size} from '~/utils/style';
......@@ -11,12 +11,12 @@ 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/GraphsPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphsPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphsPage/NodePropertiesSidebar';
import Search from '~/components/GraphsPage/Search';
import ModelPropertiesDialog from '~/components/GraphPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphPage/NodePropertiesSidebar';
import Search from '~/components/GraphPage/Search';
import Title from '~/components/Title';
import Uploader from '~/components/GraphsPage/Uploader';
import Uploader from '~/components/GraphPage/Uploader';
import styled from 'styled-components';
import useRequest from '~/hooks/useRequest';
......@@ -60,10 +60,10 @@ const Loading = styled.div`
line-height: ${rem(60)};
`;
const Graphs: NextI18NextPage = () => {
const {t} = useTranslation(['graphs', 'common']);
const Graph: NextI18NextPage = () => {
const {t} = useTranslation(['graph', 'common']);
const {data, loading} = useRequest<BlobResponse>('/graphs/graph', blobFetcher);
const {data, loading} = useRequest<BlobResponse>('/graph/graph', blobFetcher);
const graph = useRef<GraphRef>(null);
const file = useRef<HTMLInputElement>(null);
......@@ -115,7 +115,7 @@ const Graphs: NextI18NextPage = () => {
() =>
searching ? null : (
<FullWidthButton type="primary" rounded onClick={onClickFile}>
{t('graphs:change-model')}
{t('graph:change-model')}
</FullWidthButton>
),
[t, onClickFile, searching]
......@@ -161,36 +161,36 @@ const Graphs: NextI18NextPage = () => {
<>
<AsideSection>
<FullWidthButton onClick={() => graph.current?.showModelProperties()}>
{t('graphs:model-properties')}
{t('graph:model-properties')}
</FullWidthButton>
</AsideSection>
<AsideSection>
<Field label={t('graphs:display-data')}>
<Field label={t('graph:display-data')}>
<div>
<Checkbox value={showAttributes} onChange={setShowAttributes}>
{t('graphs:show-attributes')}
{t('graph:show-attributes')}
</Checkbox>
</div>
<div>
<Checkbox value={showInitializers} onChange={setShowInitializers}>
{t('graphs:show-initializers')}
{t('graph:show-initializers')}
</Checkbox>
</div>
<div>
<Checkbox value={showNames} onChange={setShowNames}>
{t('graphs:show-node-names')}
{t('graph:show-node-names')}
</Checkbox>
</div>
</Field>
</AsideSection>
<AsideSection>
<Field label={t('graphs:export-file')}>
<Field label={t('graph:export-file')}>
<ExportButtonWrapper>
<Button onClick={() => graph.current?.export('png')}>
{t('graphs:export-png')}
{t('graph:export-png')}
</Button>
<Button onClick={() => graph.current?.export('svg')}>
{t('graphs:export-svg')}
{t('graph:export-svg')}
</Button>
</ExportButtonWrapper>
</Field>
......@@ -220,7 +220,7 @@ const Graphs: NextI18NextPage = () => {
return (
<>
<Title>{t('common:graphs')}</Title>
<Title>{t('common:graph')}</Title>
<ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} />
<Content aside={aside}>
{loading ? (
......@@ -228,7 +228,7 @@ const Graphs: NextI18NextPage = () => {
<HashLoader size="60px" color={primaryColor} />
</Loading>
) : (
<Graph
<GraphComponent
ref={graph}
files={files}
uploader={uploader}
......@@ -259,8 +259,8 @@ const Graphs: NextI18NextPage = () => {
);
};
Graphs.getInitialProps = () => ({
namespacesRequired: ['graphs', 'common']
Graph.getInitialProps = () => ({
namespacesRequired: ['graph', 'common']
});
export default Graphs;
export default Graph;
......@@ -55,7 +55,7 @@ const HighDimensional: NextI18NextPage = () => {
const {data: runs, error: runsError, loading: runsLoading} = useRunningRequest<string[]>('/runs', running);
const {data: tags, error: tagsError, loading: tagsLoading} = useRunningRequest<Record<string, string[]>>(
'/embeddings/tags',
'/embedding/tags',
running
);
......
......@@ -19,15 +19,19 @@ const Loading = styled.div`
`;
const Index: NextI18NextPage = () => {
const navItmes = useNavItems();
const navItems = useNavItems();
const {t} = useTranslation('common');
useEffect(() => {
if (navItmes.length) {
Router.replace(`/${navItmes[0]}`);
if (navItems.length) {
if (navItems[0].path) {
Router.replace(navItems[0].path);
} else if (navItems[0].children?.length && navItems[0].children[0].path) {
Router.replace(navItems[0].children[0].path);
}
}
}, [navItmes]);
}, [navItems]);
return (
<Loading>
......
......@@ -141,7 +141,7 @@ const PRCurve: NextI18NextPage = () => {
return (
<>
<Preloader url="/runs" />
<Preloader url="/scalars/tags" />
<Preloader url="/scalar/tags" />
<Title>{t('common:pr-curve')}</Title>
<Content aside={aside} loading={loadingRuns}>
{!loadingRuns && !runs.length ? (
......
// cSpell:words ungrouped
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import useTagFilter, {ungroup} from '~/hooks/useTagFilter';
import AudioChart from '~/components/SamplePage/AudioChart';
import Content from '~/components/Content';
import Error from '~/components/Error';
import Preloader from '~/components/Preloader';
import RunAside from '~/components/RunAside';
import Title from '~/components/Title';
import {rem} from '~/utils/style';
const chartSize = {
height: rem(244)
};
const Audio: NextI18NextPage = () => {
const {t} = useTranslation(['sample', 'common']);
const [running, setRunning] = useState(true);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('audio', running);
const ungroupedSelectedTags = useMemo(() => ungroup(tags), [tags]);
const aside = useMemo(
() =>
runs.length ? (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
></RunAside>
) : null,
[onChangeRuns, running, runs, selectedRuns]
);
const withChart = useCallback<WithChart<typeof ungroupedSelectedTags[number]>>(
({run, label}) => <AudioChart run={run} tag={label} running={running} />,
[running]
);
return (
<>
<Preloader url="/runs" />
<Preloader url="/audio/tags" />
<Title>
{t('common:sample')} - {t('common:audio')}
</Title>
<Content aside={aside} loading={loadingRuns}>
{!loadingRuns && !runs.length ? (
<Error />
) : (
<ChartPage
items={ungroupedSelectedTags}
chartSize={chartSize}
withChart={withChart}
loading={loadingRuns || loadingTags}
/>
)}
</Content>
</>
);
};
Audio.getInitialProps = () => ({
namespacesRequired: ['sample', 'common']
});
export default Audio;
......@@ -3,47 +3,32 @@
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import useTagFilter, {ungroup} from '~/hooks/useTagFilter';
import {AsideSection} from '~/components/Aside';
import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content';
import Error from '~/components/Error';
import Field from '~/components/Field';
import ImageChart from '~/components/SamplePage/ImageChart';
import Preloader from '~/components/Preloader';
import RunAside from '~/components/RunAside';
import SampleChart from '~/components/SamplesPage/SampleChart';
import Slider from '~/components/Slider';
import Title from '~/components/Title';
import {rem} from '~/utils/style';
import useTagFilter from '~/hooks/useTagFilter';
const chartSize = {
height: rem(406)
};
type Item = {
run: string;
label: string;
};
const Samples: NextI18NextPage = () => {
const {t} = useTranslation(['samples', 'common']);
const Image: NextI18NextPage = () => {
const {t} = useTranslation(['sample', 'common']);
const [running, setRunning] = useState(true);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('images', running);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('image', running);
const ungroupedSelectedTags = useMemo(
() =>
tags.reduce<Item[]>((prev, {runs, ...item}) => {
Array.prototype.push.apply(
prev,
runs.map(run => ({...item, run: run.label, id: `${item.label}-${run.label}`}))
);
return prev;
}, []),
[tags]
);
const ungroupedSelectedTags = useMemo(() => ungroup(tags), [tags]);
const [showActualSize, setShowActualSize] = useState(false);
const [brightness, setBrightness] = useState(1);
......@@ -61,16 +46,16 @@ const Samples: NextI18NextPage = () => {
>
<AsideSection>
<Checkbox value={showActualSize} onChange={setShowActualSize}>
{t('samples:show-actual-size')}
{t('sample:show-actual-size')}
</Checkbox>
</AsideSection>
<AsideSection>
<Field label={t('samples:brightness')}>
<Field label={t('sample:brightness')}>
<Slider min={0} max={2} step={0.01} value={brightness} onChange={setBrightness} />
</Field>
</AsideSection>
<AsideSection>
<Field label={t('samples:contrast')}>
<Field label={t('sample:contrast')}>
<Slider min={0} max={2} step={0.01} value={contrast} onChange={setContrast} />
</Field>
</AsideSection>
......@@ -79,9 +64,9 @@ const Samples: NextI18NextPage = () => {
[t, brightness, contrast, onChangeRuns, running, runs, selectedRuns, showActualSize]
);
const withChart = useCallback<WithChart<Item>>(
const withChart = useCallback<WithChart<typeof ungroupedSelectedTags[number]>>(
({run, label}) => (
<SampleChart
<ImageChart
run={run}
tag={label}
fit={!showActualSize}
......@@ -96,8 +81,10 @@ const Samples: NextI18NextPage = () => {
return (
<>
<Preloader url="/runs" />
<Preloader url="/images/tags" />
<Title>{t('common:samples')}</Title>
<Preloader url="/image/tags" />
<Title>
{t('common:sample')} - {t('common:image')}
</Title>
<Content aside={aside} loading={loadingRuns}>
{!loadingRuns && !runs.length ? (
<Error />
......@@ -114,8 +101,8 @@ const Samples: NextI18NextPage = () => {
);
};
Samples.getInitialProps = () => ({
namespacesRequired: ['samples', 'common']
Image.getInitialProps = () => ({
namespacesRequired: ['sample', 'common']
});
export default Samples;
export default Image;
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import {SortingMethod, XAxis, sortingMethod as toolTipSortingValues} from '~/resource/scalars';
import {SortingMethod, XAxis, sortingMethod as toolTipSortingValues} from '~/resource/scalar';
import {AsideSection} from '~/components/Aside';
import Checkbox from '~/components/Checkbox';
......@@ -10,7 +10,7 @@ import Error from '~/components/Error';
import Field from '~/components/Field';
import Preloader from '~/components/Preloader';
import RunAside from '~/components/RunAside';
import ScalarChart from '~/components/ScalarsPage/ScalarChart';
import ScalarChart from '~/components/ScalarPage/ScalarChart';
import Select from '~/components/Select';
import Slider from '~/components/Slider';
import {Tag} from '~/types';
......@@ -32,12 +32,12 @@ const TooltipSortingDiv = styled.div`
}
`;
const Scalars: NextI18NextPage = () => {
const {t} = useTranslation(['scalars', 'common']);
const Scalar: NextI18NextPage = () => {
const {t} = useTranslation(['scalar', 'common']);
const [running, setRunning] = useState(true);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('scalars', running);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('scalar', running);
const [smoothing, setSmoothing] = useState(0.6);
......@@ -59,13 +59,13 @@ const Scalars: NextI18NextPage = () => {
>
<AsideSection>
<Checkbox value={ignoreOutliers} onChange={setIgnoreOutliers}>
{t('scalars:ignore-outliers')}
{t('scalar:ignore-outliers')}
</Checkbox>
<TooltipSortingDiv>
<span>{t('scalars:tooltip-sorting')}</span>
<span>{t('scalar:tooltip-sorting')}</span>
<Select
list={toolTipSortingValues.map(value => ({
label: t(`scalars:tooltip-sorting-value.${value}`),
label: t(`scalar:tooltip-sorting-value.${value}`),
value
}))}
value={tooltipSorting}
......@@ -74,12 +74,12 @@ const Scalars: NextI18NextPage = () => {
</TooltipSortingDiv>
</AsideSection>
<AsideSection>
<Field label={t('scalars:smoothing')}>
<Field label={t('scalar:smoothing')}>
<Slider min={0} max={0.99} step={0.01} value={smoothing} onChangeComplete={setSmoothing} />
</Field>
</AsideSection>
<AsideSection>
<Field label={t('scalars:x-axis')}>
<Field label={t('scalar:x-axis')}>
<TimeModeSelect value={xAxis} onChange={setXAxis} />
</Field>
</AsideSection>
......@@ -107,8 +107,8 @@ const Scalars: NextI18NextPage = () => {
return (
<>
<Preloader url="/runs" />
<Preloader url="/scalars/tags" />
<Title>{t('common:scalars')}</Title>
<Preloader url="/scalar/tags" />
<Title>{t('common:scalar')}</Title>
<Content aside={aside} loading={loadingRuns}>
{!loadingRuns && !runs.length ? (
<Error />
......@@ -120,8 +120,8 @@ const Scalars: NextI18NextPage = () => {
);
};
Scalars.getInitialProps = () => ({
namespacesRequired: ['scalars', 'common']
Scalar.getInitialProps = () => ({
namespacesRequired: ['scalar', 'common']
});
export default Scalars;
export default Scalar;
{
"audio": "Audio",
"cancel": "Cancel",
"close": "Close",
"colon": ": ",
"confirm": "Confirm",
"empty": "Nothing to display",
"error": "Error occurred",
"graphs": "Graphs",
"graph": "Graphs",
"high-dimensional": "High Dimensional",
"histogram": "Histogram",
"image": "Image",
"loading": "Please wait while loading data",
"next-page": "Next Page",
"pr-curve": "PR-Curve",
......@@ -14,8 +17,8 @@
"run": "Run",
"running": "Running",
"runs": "Runs",
"samples": "Samples",
"scalars": "Scalars",
"sample": "Samples",
"scalar": "Scalars",
"search": "Search",
"search-empty": "Nothing found. Please try again with another word. <1/>Or you can <3>see all charts</3>.",
"search-result": "Search Result",
......
{
"download-image": "Download image",
"false-negatives": "FN",
"false-positives": "FP",
"maximize": "Maximize",
"minimize": "Minimize",
"precision": "Precision",
"recall": "Recall",
"restore": "Restore",
"threshold": "Threshold",
"time-display-type": "Time Display Type",
"true-negatives": "TN",
......
......@@ -2,8 +2,10 @@
"audio": "audio",
"brightness": "Brightness",
"contrast": "Contrast",
"download-audio": "Download $t(audio)",
"download-image": "Download $t(image)",
"image": "image",
"sample-rate": "Sample Rate",
"show-actual-size": "Show Actual Image Size",
"step": "Step",
"text": "text"
......
{
"audio": "语音",
"cancel": "取消",
"close": "关闭",
"colon": ":",
"confirm": "确定",
"empty": "暂无数据",
"error": "发生错误",
"graphs": "网络结构",
"graph": "网络结构",
"high-dimensional": "高维数据映射",
"histogram": "直方图",
"image": "图像",
"loading": "数据载入中,请稍等",
"next-page": "下一页",
"pr-curve": "PR曲线",
......@@ -14,8 +17,8 @@
"run": "运行",
"running": "运行中",
"runs": "数据流",
"samples": "样本数据",
"scalars": "标量数据",
"sample": "样本数据",
"scalar": "标量数据",
"search": "搜索",
"search-empty": "没有找到您期望的内容,你可以尝试其他搜索词<1/>或者点击<3>查看全部图表</3>",
"search-result": "搜索结果",
......
{
"download-image": "下载图片",
"false-negatives": "FN",
"false-positives": "FP",
"maximize": "最大化",
"minimize": "最小化",
"precision": "Precision",
"recall": "Recall",
"restore": "还原",
"threshold": "Threshold",
"time-display-type": "时间显示类型",
"true-negatives": "TN",
......
......@@ -2,8 +2,10 @@
"audio": "音频",
"brightness": "亮度",
"contrast": "对比度",
"download-audio": "下载$t(audio)",
"download-image": "下载$t(image)",
"image": "图片",
"sample-rate": "采样率",
"show-actual-size": "按真实大小展示",
"step": "Step",
"text": "文本"
......
......@@ -31,4 +31,9 @@
<glyph unicode="&#xe915;" glyph-name="plus" d="M512 960c25.626 0 46.4-20.774 46.4-46.4v-419.2h419.2c25.626 0 46.4-20.774 46.4-46.4s-20.774-46.4-46.4-46.4h-419.2v-419.2c0-25.626-20.774-46.4-46.4-46.4s-46.4 20.774-46.4 46.4v0 419.2h-419.2c-25.626 0-46.4 20.774-46.4 46.4s20.774 46.4 46.4 46.4h419.2v419.2c0 25.626 20.774 46.4 46.4 46.4v0z" />
<glyph unicode="&#xe916;" glyph-name="close" d="M1024 928.32l-32 31.68-480-480.32-480.32 480.32-31.68-31.68 480.32-480.32-480.32-480 31.68-32 480.32 480 480-480 32 32-480 480 480 480.32z" />
<glyph unicode="&#xe917;" glyph-name="upload" horiz-adv-x="1260" d="M1014.475 637.659c-33.74 184.409-193.181 322.34-384.838 322.34s-351.098-137.93-384.473-319.941l-0.365-2.398c-138.917-31.415-241.092-153.795-241.092-300.039 0-11.903 0.677-23.649 1.994-35.199l-0.131 1.412c20.092-155.074 151.333-273.637 310.266-273.637 0.862 0 1.723 0.004 2.584 0.011l-0.132-0.001h95.769c21.589 0 39.090 17.501 39.090 39.090s-17.501 39.090-39.090 39.090v0 0h-97.725c-127.265 0-230.433 103.169-230.433 230.433s103.169 230.433 230.433 230.433v0c0 172.709 140.009 312.718 312.718 312.718s312.718-140.009 312.718-312.718v0c2.83 0.124 6.149 0.196 9.484 0.196 127.157 0 230.238-103.081 230.238-230.238s-103.081-230.238-230.238-230.238c-3.336 0-6.655 0.070-9.956 0.211l0.471-0.016h-117.269c-21.589 0-39.090-17.501-39.090-39.090s17.501-39.090 39.090-39.090v0h117.269c0.758-0.007 1.654-0.010 2.551-0.010 158.656 0 289.716 118.151 309.994 271.265l0.173 1.592c1.152 9.995 1.809 21.577 1.809 33.313 0 146.582-102.518 269.219-239.764 300.124l-2.055 0.389zM646.641 480.91c-7.001 6.684-16.506 10.797-26.972 10.797s-19.97-4.113-26.987-10.81l0.015 0.015-108.669-110.233c-6.968-7.024-11.29-16.684-11.336-27.354v-0.009c0.156-10.537 4.455-20.039 11.338-26.974l-0.002 0.002c7.001-6.684 16.506-10.797 26.972-10.797s19.97 4.113 26.987 10.81l-0.015-0.015 43.39 43.781v-385.033c0-21.589 17.501-39.090 39.090-39.090s39.090 17.501 39.090 39.090v0 384.643l42.999-43.781c6.7-6.992 16.115-11.338 26.546-11.338 0.15 0 0.3 0.001 0.449 0.003h-0.023c10.587 0.267 20.088 4.703 26.966 11.721l0.006 0.007c6.796 7.024 10.984 16.607 10.984 27.167s-4.188 20.144-10.994 27.178l0.010-0.011z" />
<glyph unicode="&#xe918;" glyph-name="mute" d="M224.293 639.273l233.472 187.392c10.973 8.808 26.025 10.54 38.711 4.454s20.753-18.91 20.753-32.98v-697.637c-0.006-14.050-8.060-26.854-20.721-32.943s-27.692-4.387-38.671 4.38l-239.909 191.488h-181.358c-20.198 0-36.572 16.374-36.572 36.572v302.702c0 20.198 16.374 36.572 36.572 36.572h187.721z" />
<glyph unicode="&#xe919;" glyph-name="pause" d="M224 960c70.692 0 128-57.308 128-128v-768c0-70.692-57.308-128-128-128s-128 57.308-128 128v768c0 70.692 57.308 128 128 128zM800 960c70.692 0 128-57.308 128-128v-768c0-70.692-57.308-128-128-128s-128 57.308-128 128v768c0 70.692 57.308 128 128 128z" />
<glyph unicode="&#xe91a;" glyph-name="play" d="M869.901 342.803l-574.736-385.335c-58.106-38.948-136.794-23.433-175.742 34.663-13.99 20.865-21.458 45.418-21.458 70.54v770.67c0 69.949 56.713 126.661 126.661 126.661 25.121 0 49.675-7.469 70.54-21.458l574.725-385.335c58.106-38.948 73.632-117.636 34.673-175.742-9.187-13.701-20.962-25.476-34.663-34.663z" />
<glyph unicode="&#xe91b;" glyph-name="volumn-low" d="M224.293 639.273l233.472 187.392c10.973 8.808 26.025 10.54 38.711 4.454s20.753-18.91 20.753-32.98v-697.637c-0.006-14.050-8.060-26.854-20.721-32.943s-27.692-4.387-38.671 4.38l-239.909 191.488h-181.358c-20.198 0-36.572 16.374-36.572 36.572v302.702c0 20.198 16.374 36.572 36.572 36.572h187.721zM693.467 223.785c-14.468-13.791-37.328-13.412-51.33 0.851s-13.961 37.126 0.093 51.337c47.107 46.117 73.611 109.291 73.509 175.214 0.112 61.057-22.627 119.946-63.744 165.084-8.974 9.64-12.071 23.36-8.11 35.921s14.371 22.020 27.252 24.766c12.88 2.746 26.243-1.645 34.984-11.498 53.352-58.599 82.871-135.024 82.762-214.272 0.1-85.553-34.299-167.534-95.415-227.401v0z" />
<glyph unicode="&#xe91c;" glyph-name="volumn" d="M224.293 639.273l233.472 187.392c10.973 8.808 26.025 10.54 38.711 4.454s20.753-18.91 20.753-32.98v-697.637c-0.006-14.050-8.060-26.854-20.721-32.943s-27.692-4.387-38.671 4.38l-239.909 191.488h-181.358c-20.198 0-36.572 16.374-36.572 36.572v302.702c0 20.198 16.374 36.572 36.572 36.572h187.721zM693.467 223.785c-14.468-13.791-37.328-13.412-51.33 0.851s-13.961 37.126 0.093 51.337c47.107 46.117 73.611 109.291 73.509 175.214 0.112 61.057-22.627 119.946-63.744 165.084-8.974 9.64-12.071 23.36-8.11 35.921s14.371 22.020 27.252 24.766c12.88 2.746 26.243-1.645 34.984-11.498 53.352-58.599 82.871-135.024 82.762-214.272 0.1-85.553-34.299-167.534-95.415-227.401v0zM867.327 65.212c-9.073-9.524-22.561-13.441-35.325-10.26s-22.835 12.972-26.376 25.64c-3.541 12.668-0.006 26.262 9.257 35.601 87.355 89.608 136.162 209.853 135.973 334.994 0 124.233-47.287 240.969-130.779 329.582-13.595 14.739-12.787 37.675 1.811 51.421s37.542 13.174 51.437-1.282c96.183-102.035 150.675-236.617 150.675-379.722 0-146.103-56.869-283.319-156.672-385.975z" />
</font></defs></svg>
\ No newline at end of file
.vdl-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'vdl-icon' !important;
speak: none;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
......@@ -13,6 +13,21 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-mute:before {
content: '\e918';
}
.icon-volumn-low:before {
content: '\e91b';
}
.icon-volumn:before {
content: '\e91c';
}
.icon-pause:before {
content: '\e919';
}
.icon-play:before {
content: '\e91a';
}
.icon-question-circle:before {
content: '\e913';
}
......
......@@ -68,11 +68,11 @@ export const tooltip = (data: Dataset, stepLength: number, i18n: I18n) => {
return {
columns: [
{
label: i18n.t('scalars:smoothed'),
label: i18n.t('scalar:smoothed'),
width: '5em'
},
{
label: i18n.t('scalars:value'),
label: i18n.t('scalar:value'),
width: '4.285714286em'
},
{
......
interface AudioPlayerOptions {
volumn: number;
onplay?: () => void;
onstop?: () => void;
}
export class AudioPlayer {
protected context: AudioContext;
private gain: GainNode;
private source: AudioBufferSourceNode | null = null;
private buffer: AudioBuffer | null = null;
private startAt = 0;
private stopAt = 0;
private offset = 0;
private toggleVolumn = 100;
public playing = false;
public readonly options: AudioPlayerOptions;
get current() {
if (this.playing) {
return this.context.currentTime - this.startAt + this.offset;
}
return this.offset;
}
get duration() {
if (!this.buffer) {
return Number.NaN;
}
return this.buffer.duration;
}
get sampleRate() {
if (!this.buffer) {
return Number.NaN;
}
return this.buffer.sampleRate;
}
get volumn() {
return this.gain.gain.value * 100;
}
set volumn(value: number) {
if (value > 100) {
value = 100;
} else if (value < 0) {
value = 0;
}
this.gain.gain.value = value / 100;
}
constructor(options?: Partial<AudioPlayerOptions>) {
this.options = {
volumn: 100,
...options
};
this.context = new AudioContext();
this.gain = this.context.createGain();
this.volumn = this.options.volumn;
}
private reset() {
if (this.buffer) {
if (this.playing) {
this.source?.stop(0);
}
this.startAt = 0;
this.stopAt = 0;
this.offset = 0;
this.buffer = null;
}
}
load(buffer: ArrayBuffer) {
this.reset();
return this.context.decodeAudioData(buffer, audioBuffer => {
this.buffer = audioBuffer;
});
}
play() {
if (!this.buffer) {
throw new Error('No audio loaded');
}
this.source = this.context.createBufferSource();
this.source.buffer = this.buffer;
this.source.connect(this.gain).connect(this.context.destination);
this.source.addEventListener('ended', () => {
this.stopAt = this.context.currentTime;
this.offset += this.stopAt - this.startAt;
this.playing = false;
this.options.onstop?.();
this.source = null;
});
if (this.offset >= this.duration) {
this.offset = 0;
}
this.source.start(0, this.offset);
this.startAt = this.context.currentTime;
this.playing = true;
this.options.onplay?.();
}
pause() {
if (!this.buffer) {
throw new Error('No audio loaded');
}
this.source?.stop(0);
}
toggle() {
this.playing ? this.pause() : this.play();
}
stop() {
this.pause();
this.startAt = 0;
this.stopAt = 0;
this.offset = 0;
}
seek(offset: number) {
if (offset != null && (offset < 0 || offset > this.duration)) {
throw new Error('Invalid offset');
}
this.offset = offset;
}
toggleMute() {
if (this.volumn === 0) {
this.volumn = this.toggleVolumn || 100;
} else {
this.toggleVolumn = this.volumn;
this.volumn = 0;
}
}
dispose() {
this.reset();
this.context.close();
this.source = null;
this.buffer = null;
}
}
......@@ -4,6 +4,7 @@ import {createGlobalStyle, keyframes} from 'styled-components';
import {css} from 'styled-components';
import tippy from '!!css-loader!tippy.js/dist/tippy.css';
import tippyAnimation from '!!css-loader!tippy.js/animations/shift-away-subtle.css';
import toast from '!!css-loader!react-toastify/dist/ReactToastify.css';
import vdlIcon from '!!css-loader!~/public/style/vdl-icon.css';
......@@ -47,6 +48,7 @@ export const textLightColor = '#666';
export const textLighterColor = '#999';
export const textInvertColor = '#FFF';
export const bodyBackgroundColor = '#F4F4F4';
export const primaryBackgroundColor = '#F2F6FF';
export const backgroundColor = '#FFF';
export const backgroundFocusedColor = '#F6F6F6';
export const borderColor = '#DDD';
......@@ -86,12 +88,12 @@ export const sameBorder = (
radius ? {borderRadius: radius === true ? borderRadius : radius} : undefined
);
};
export const transitionProps = (props: string | string[], args?: string) => {
export const transitionProps = (props: string | string[], args?: string | {duration?: string; easing?: string}) => {
if ('string' === typeof props) {
props = [props];
}
if (!args) {
args = `${duration} ${easing}`;
if ('string' !== typeof args) {
args = `${args?.duration ?? duration} ${args?.easing ?? easing}`;
}
return transitions(props, args);
};
......@@ -140,6 +142,7 @@ export const GlobalStyle = createGlobalStyle`
${vdlIcon.toString()}
${toast.toString()}
${tippy.toString()}
${tippyAnimation.toString()}
html {
font-size: ${fontSize};
......@@ -232,22 +235,51 @@ export const GlobalStyle = createGlobalStyle`
}
}
.tippy-box {
[data-tippy-root] .tippy-box {
z-index: 10002;
color: ${tooltipTextColor};
background-color: ${tooltipBackgroundColor};
color: ${textColor};
background-color: ${backgroundColor};
box-shadow: 0 0 10px 0 rgba(0,0,0,0.10);
border-radius: ${borderRadius};
> .tippy-content {
padding: 0;
}
&[data-placement^='top'] > .tippy-arrow::before {
border-top-color: ${tooltipBackgroundColor};
border-top-color: ${backgroundColor};
}
&[data-placement^='bottom'] > .tippy-arrow::before {
border-bottom-color: ${tooltipBackgroundColor};
border-bottom-color: ${backgroundColor};
}
&[data-placement^='left'] > .tippy-arrow::before {
border-left-color: ${tooltipBackgroundColor};
border-left-color: ${backgroundColor};
}
&[data-placement^='right'] > .tippy-arrow::before {
border-right-color: ${tooltipBackgroundColor};
border-right-color: ${backgroundColor};
}
&[data-theme~='tooltip'] {
color: ${tooltipTextColor};
background-color: ${tooltipBackgroundColor};
box-shadow: none;
> .tippy-content {
padding: ${rem(5)} ${rem(9)};
}
&[data-placement^='top'] > .tippy-arrow::before {
border-top-color: ${tooltipBackgroundColor};
}
&[data-placement^='bottom'] > .tippy-arrow::before {
border-bottom-color: ${tooltipBackgroundColor};
}
&[data-placement^='left'] > .tippy-arrow::before {
border-left-color: ${tooltipBackgroundColor};
}
&[data-placement^='right'] > .tippy-arrow::before {
border-right-color: ${tooltipBackgroundColor};
}
}
}
`;
import {range} from '~/resource/scalars';
import {range} from '~/resource/scalar';
import worker from '~/utils/worker';
worker(range);
import {transform} from '~/resource/scalars';
import {transform} from '~/resource/scalar';
import worker from '~/utils/worker';
worker(transform);
{
"name": "@visualdl/i18n",
"version": "2.0.0-beta.44",
"version": "2.0.0-beta.49",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -37,10 +37,10 @@
"dependencies": {
"detect-node": "2.0.4",
"hoist-non-react-statics": "3.3.2",
"i18next": "19.5.3",
"i18next": "19.6.1",
"i18next-browser-languagedetector": "5.0.0",
"i18next-fs-backend": "1.0.7",
"i18next-http-backend": "1.0.16",
"i18next-http-backend": "1.0.17",
"i18next-http-middleware": "3.0.2",
"path-match": "1.2.4",
"prop-types": "15.7.2",
......@@ -48,10 +48,10 @@
"url": "0.11.0"
},
"devDependencies": {
"@types/express": "4.17.6",
"@types/express": "4.17.7",
"@types/hoist-non-react-statics": "3.3.1",
"@types/node": "14.0.14",
"@types/react": "16.9.41",
"@types/node": "14.0.23",
"@types/react": "16.9.43",
"@types/react-dom": "16.9.8",
"typescript": "3.9.6"
},
......
import {Request, Response} from 'express';
import fs from 'fs/promises';
import mime from 'mime-types';
import path from 'path';
const audios = ['1.mp3', '2.wav', '3.mp3', '4.wav', '5.wav', '6.mp3', '7.wav'];
export default async (req: Request, res: Response) => {
const index = (+req.query.index ?? 0) % audios.length;
const file = path.resolve(__dirname, '../../assets/audio', audios[index]);
const result = await fs.readFile(file);
const contentType = mime.contentType(audios[index]);
if (contentType) {
res.setHeader('Content-Type', contentType);
}
return result;
};
export default {
test: [
'input_reshape/input/audio/7',
'input_reshape/input/audio/4',
'input_reshape/input/audio/5',
'hahaha/input/audio/2',
'hahaha/input/audio/3',
'hahaha/input/audio/0',
'ohehe/input/audio/1',
'😼/input/audio/8',
'😼/input/audio/9'
],
train: [
'input_reshape/input/audio/6',
'input_reshape/input/audio/7',
'input_reshape/input/audio/4',
'input_reshape/input/audio/5',
'hahaha/input/audio/2',
'hahaha/input/audio/3',
'oheihei/input/audio/0',
'oheihei/input/audio/1',
'😼/input/audio/8',
'😼/input/audio/9'
]
};
export default ['embeddings', 'scalar', 'image', 'graph', 'histogram', 'pr_curve'];
export default ['embeddings', 'scalar', 'image', 'audio', 'graph', 'histogram', 'pr_curve'];
import {Request, Response} from 'express';
import fs from 'fs/promises';
import mime from 'mime-types';
import path from 'path';
const images = ['1.jpeg', '2.jpeg', '3.jpeg', '4.jpeg', '5.gif', '6.gif', '7.gif'];
export default async (req: Request, res: Response) => {
const index = (+req.query.index ?? 0) % images.length;
const file = path.resolve(__dirname, '../../assets/image', images[index]);
const result = await fs.readFile(file);
const contentType = mime.contentType(images[index]);
if (contentType) {
res.setHeader('Content-Type', contentType);
}
return result;
};
export default [
{
wallTime: 1512549785.061623,
step: 60
},
{
wallTime: 1512886109.672786,
step: 60
},
{
wallTime: 1512886124.266915,
step: 210
},
{
wallTime: 1512886138.898628,
step: 330
},
{
wallTime: 1512886139.883663,
step: 340
},
{
wallTime: 1512886147.195567,
step: 410
},
{
wallTime: 1512886156.47856,
step: 500
},
{
wallTime: 1512886187.82793,
step: 810
},
{
wallTime: 1512886200.386198,
step: 950
},
{
wallTime: 1512886204.224405,
step: 990
}
];
import {Request, Response} from 'express';
import fetch from 'isomorphic-unfetch';
const images = [
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583866603102&di=13e63561829699f6eda8405ba5a84cad&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201704%2F30%2F20170430172141_YSjU4.jpeg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582889403799&di=bb4db115c1227e081852bbb95336150b&imgtype=0&src=http%3A%2F%2Fres.hpoi.net.cn%2Fgk%2Fcover%2Fn%2F2015%2F02%2Fff897b88ccd5417f91d4159a8ea343a6.jpg%3Fdate%3D1464606291000',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582889975692&di=cd91e6c70d07ef496bfcca20597eb5af&imgtype=0&src=http%3A%2F%2Fimg3.duitang.com%2Fuploads%2Fitem%2F201411%2F28%2F20141128211355_HPfYT.thumb.224_0.gif',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582890006236&di=9f030009422b91e8753f8c476426fc39&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201812%2F22%2F20181222172346_ykcdh.thumb.224_0.gif',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582890018944&di=d79e0ce4cef39f0ef81cb38c440ca858&imgtype=0&src=http%3A%2F%2Fgss3.bdstatic.com%2F7Po3dSag_xI4khGkpoWK1HF6hhy%2Fbaike%2Fw%3D150%2Fsign%3D074ebdb2367adab43dd01f46bbd5b36b%2F42166d224f4a20a4b974325d90529822730ed0c6.jpg'
];
export default async (req: Request, res: Response) => {
const index = (+req.query.index ?? 0) % images.length;
const result = await fetch(images[index]);
if (result.headers.has('Content-Type')) {
const ct = result.headers.get('Content-Type');
res.setHeader('Content-Type', ct);
}
return result.arrayBuffer();
};
......@@ -48,7 +48,9 @@ export default (options: Options) => {
await sleep(delay);
}
if (mock instanceof ArrayBuffer) {
if (Buffer.isBuffer(mock)) {
res.send(mock);
} else if (mock instanceof ArrayBuffer) {
res.send(Buffer.from(mock));
} else {
const result = JSON.parse(faker.fake(JSON.stringify(mock, null, 4)));
......
{
"name": "@visualdl/mock",
"version": "2.0.0-beta.44",
"version": "2.0.0-beta.49",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -28,17 +28,20 @@
"dist"
],
"scripts": {
"build": "tsc",
"build": "rimraf dist && tsc && cpy assets dist/ --parents",
"test": "echo \"Error: no test specified\" && exit 0; #"
},
"dependencies": {
"faker": "4.1.0",
"isomorphic-unfetch": "3.0.0"
"isomorphic-unfetch": "3.0.0",
"mime-types": "2.1.27"
},
"devDependencies": {
"@types/express": "4.17.6",
"@types/express": "4.17.7",
"@types/faker": "4.1.12",
"@types/node": "14.0.14",
"@types/node": "14.0.23",
"cpy-cli": "3.1.1",
"rimraf": "3.0.2",
"typescript": "3.9.6"
},
"peerDependencies": {
......
{
"name": "@visualdl/netron",
"version": "2.0.0-beta.44",
"version": "2.0.0-beta.49",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -35,20 +35,20 @@
"dagre": "0.8.5",
"flatbuffers": "1.12.0",
"long": "4.0.0",
"marked": "1.1.0",
"marked": "1.1.1",
"netron": "PeterPanZH/netron",
"pako": "1.0.11"
},
"devDependencies": {
"autoprefixer": "9.8.4",
"autoprefixer": "9.8.5",
"copy-webpack-plugin": "6.0.3",
"css-loader": "3.6.0",
"html-webpack-plugin": "4.3.0",
"mini-css-extract-plugin": "0.9.0",
"postcss-loader": "3.0.0",
"rimraf": "3.0.2",
"sass": "1.26.9",
"sass-loader": "9.0.0",
"sass": "1.26.10",
"sass-loader": "9.0.2",
"terser": "4.8.0",
"webpack": "4.43.0"
},
......
{
"name": "@visualdl/server",
"version": "2.0.0-beta.45",
"version": "2.0.0-beta.49",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -37,24 +37,24 @@
"ecosystem.config.d.ts"
],
"dependencies": {
"@visualdl/core": "2.0.0-beta.44",
"@visualdl/i18n": "2.0.0-beta.44",
"@visualdl/core": "2.0.0-beta.49",
"@visualdl/i18n": "2.0.0-beta.49",
"express": "4.17.1",
"http-proxy-middleware": "1.0.4",
"http-proxy-middleware": "1.0.5",
"next": "9.4.4",
"pm2": "4.4.0"
},
"devDependencies": {
"@types/express": "4.17.6",
"@types/node": "14.0.14",
"@types/express": "4.17.7",
"@types/node": "14.0.23",
"@types/shelljs": "0.8.8",
"@types/webpack": "4.41.18",
"@types/webpack": "4.41.21",
"@types/webpack-dev-middleware": "3.7.1",
"@visualdl/mock": "2.0.0-beta.44",
"@visualdl/mock": "2.0.0-beta.49",
"cross-env": "7.0.2",
"nodemon": "2.0.4",
"shelljs": "0.8.4",
"ts-loader": "7.0.5",
"ts-loader": "8.0.1",
"ts-node": "8.10.2",
"typescript": "3.9.6",
"webpack": "4.43.0",
......
{
"name": "@visualdl/serverless",
"version": "2.0.0-beta.44",
"version": "2.0.0-beta.49",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
......@@ -31,9 +31,9 @@
"test": "echo \"Error: no test specified\" && exit 0; #"
},
"devDependencies": {
"@types/node": "14.0.14",
"@types/node": "14.0.23",
"@types/rimraf": "3.0.0",
"@visualdl/core": "2.0.0-beta.44",
"@visualdl/core": "2.0.0-beta.49",
"cross-env": "7.0.2",
"rimraf": "3.0.2",
"ts-node": "8.10.2",
......
{
"name": "@visualdl/wasm",
"version": "2.0.0-beta.44",
"version": "2.0.0-beta.49",
"title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
......
此差异已折叠。
......@@ -94,11 +94,11 @@ class Api(object):
return self._get('data/logs', lib.get_logs)
@result()
def scalars_tags(self):
def scalar_tags(self):
return self._get_with_retry('data/plugin/scalars/tags', lib.get_scalar_tags)
@result()
def images_tags(self):
def image_tags(self):
return self._get_with_retry('data/plugin/images/tags', lib.get_image_tags)
@result()
......@@ -106,7 +106,7 @@ class Api(object):
return self._get_with_retry('data/plugin/audio/tags', lib.get_audio_tags)
@result()
def embeddings_tags(self):
def embedding_tags(self):
return self._get_with_retry('data/plugin/embeddings/tags', lib.get_embeddings_tags)
@result()
......@@ -114,17 +114,17 @@ class Api(object):
return self._get_with_retry('data/plugin/pr_curves/tags', lib.get_pr_curve_tags)
@result()
def scalars_list(self, run, tag):
def scalar_list(self, run, tag):
key = os.path.join('data/plugin/scalars/scalars', run, tag)
return self._get_with_retry(key, lib.get_scalar, run, tag)
@result()
def images_list(self, mode, tag):
def image_list(self, mode, tag):
key = os.path.join('data/plugin/images/images', mode, tag)
return self._get_with_retry(key, lib.get_image_tag_steps, mode, tag)
@result('image/png')
def images_image(self, mode, tag, index=0):
def image_image(self, mode, tag, index=0):
index = int(index)
key = os.path.join('data/plugin/images/individualImage', mode, tag, str(index))
return self._get_with_retry(key, lib.get_individual_image, mode, tag, index)
......@@ -134,14 +134,14 @@ class Api(object):
key = os.path.join('data/plugin/audio/audio', run, tag)
return self._get_with_retry(key, lib.get_audio_tag_steps, run, tag)
@result()
@result('audio/wav')
def audio_audio(self, run, tag, index=0):
index = int(index)
key = os.path.join('data/plugin/audio/individualAudio', run, tag, str(index))
return self._get_with_retry(key, lib.get_individual_audio, run, tag, index)
@result()
def embeddings_embedding(self, run, tag='default', reduction='pca', dimension=2):
def embedding_embedding(self, run, tag='default', reduction='pca', dimension=2):
dimension = int(dimension)
key = os.path.join('data/plugin/embeddings/embeddings', run, str(dimension), reduction)
return self._get_with_retry(key, lib.get_embeddings, run, tag, reduction, dimension)
......@@ -166,7 +166,7 @@ class Api(object):
return self._get_with_retry(key, lib.get_pr_curve_step, run)
@result('application/octet-stream', lambda s: {"Content-Disposition": 'attachment; filename="%s"' % s.model_name} if len(s.model_name) else None)
def graphs_graph(self):
def graph_graph(self):
key = os.path.join('data/plugin/graphs/graph')
return self._get_with_retry(key, lib.get_graph)
......@@ -178,20 +178,20 @@ def create_api_call(logdir, model, cache_timeout):
'runs': (api.runs, []),
'tags': (api.tags, []),
'logs': (api.logs, []),
'scalars/tags': (api.scalars_tags, []),
'images/tags': (api.images_tags, []),
'scalar/tags': (api.scalar_tags, []),
'image/tags': (api.image_tags, []),
'audio/tags': (api.audio_tags, []),
'embeddings/tags': (api.embeddings_tags, []),
'embedding/tags': (api.embedding_tags, []),
'histogram/tags': (api.histogram_tags, []),
'pr-curve/tags': (api.pr_curve_tags, []),
'scalars/list': (api.scalars_list, ['run', 'tag']),
'images/list': (api.images_list, ['run', 'tag']),
'images/image': (api.images_image, ['run', 'tag', 'index']),
'scalar/list': (api.scalar_list, ['run', 'tag']),
'image/list': (api.image_list, ['run', 'tag']),
'image/image': (api.image_image, ['run', 'tag', 'index']),
'audio/list': (api.audio_list, ['run', 'tag']),
'audio/audio': (api.audio_audio, ['run', 'tag', 'index']),
'embeddings/embedding': (api.embeddings_embedding, ['run', 'tag', 'reduction', 'dimension']),
'embedding/embedding': (api.embedding_embedding, ['run', 'tag', 'reduction', 'dimension']),
'histogram/list': (api.histogram_list, ['run', 'tag']),
'graphs/graph': (api.graphs_graph, []),
'graph/graph': (api.graph_graph, []),
'pr-curve/list': (api.pr_curves_pr_curve, ['run', 'tag']),
'pr-curve/steps': (api.pr_curves_steps, ['run'])
}
......
......@@ -102,7 +102,7 @@ def get_individual_audio(log_reader, run, tag, step_index):
log_reader.load_new_data()
records = log_reader.data_manager.get_reservoir("audio").get_items(
run, decode_tag(tag))
result = records[step_index].audio.encoded_image_string
result = records[step_index].audio.encoded_audio_string
return result
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册