From e6d2d997615b35ca6aa4af547312b5542f7efced Mon Sep 17 00:00:00 2001 From: Karo <44981312+KaroMourad@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:32:22 +0400 Subject: [PATCH] [feat] Add Images Explorer (#2947) --- .../ui/src/components/FigureBox/FigureBox.tsx | 2 +- .../ui/src/components/ImageBox/ImageBox.tsx | 7 +- .../BaseExplorer/components/Image/Image.tsx | 26 ++++++ .../BaseExplorer/components/Image/index.ts | 2 + .../src/pages/Board/components/AudiosList.tsx | 7 -- .../pages/Board/components/FiguresList.tsx | 9 -- .../src/pages/Board/components/ImagesList.tsx | 6 -- .../ProjectStatisticsStore.ts | 2 +- .../useProjectStatistics.tsx | 14 +-- .../pages/Explorers/ImagesExplorer/config.ts | 52 +++++++++++ .../ImagesExplorer/getStaticContent.tsx | 52 +++++++++++ .../pages/Explorers/ImagesExplorer/index.tsx | 33 +++++++ src/aimcore/web/ui/src/routes/routes.tsx | 91 +++++++------------ 13 files changed, 208 insertions(+), 95 deletions(-) create mode 100644 src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx create mode 100644 src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts create mode 100644 src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts create mode 100644 src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx create mode 100644 src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx diff --git a/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx b/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx index d7290aa4fb..be864f5dca 100644 --- a/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx +++ b/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx @@ -70,7 +70,7 @@ function FigureBox(props: FigureBoxProps) { onUpdate={onAutoSize} /> ) : ( - + )} diff --git a/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx b/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx index 797768d249..208b3cc705 100644 --- a/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx +++ b/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { useImageBlobURI } from 'hooks'; -import { Skeleton } from '@material-ui/lab'; - import ErrorBoundary from 'components/ErrorBoundary'; +import { Spinner } from 'components/kit'; import { ImageBoxProps } from './'; @@ -20,9 +19,7 @@ function ImageBox(props: ImageBoxProps) { alt={caption} /> ) : ( -
- -
+ )} diff --git a/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx new file mode 100644 index 0000000000..ea84aa4659 --- /dev/null +++ b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import ErrorBoundary from 'components/ErrorBoundary'; +import ImageBox from 'components/ImageBox'; + +import { IBoxContentProps } from 'modules/BaseExplorer/types'; + +function Image(props: IBoxContentProps) { + return ( + + + + ); +} + +export default React.memo(Image); diff --git a/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts new file mode 100644 index 0000000000..5e9fd8898b --- /dev/null +++ b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts @@ -0,0 +1,2 @@ +import Image from './Image'; +export default Image; diff --git a/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx b/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx index 89af5c93f8..4365eb8af2 100644 --- a/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx +++ b/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx @@ -12,12 +12,6 @@ function AudiosList(props: any) { ...audio.record, })); - const boxStyle = { - margin: '5px', - height: 50, - width: 'calc(100% - 10px)', - flex: 1, - }; const boxKey = (item: any) => `${item?.container?.hash}_${item.name}_${JSON.stringify(item.context)}_${ item.step @@ -27,7 +21,6 @@ function AudiosList(props: any) { {data.map((item: any) => ( `${item?.container?.hash}_${item.name}_${JSON.stringify(item.context)}_${ item.step @@ -29,7 +21,6 @@ function FiguresList(props: any) { {data.map((item: any) => ( `${item?.container?.hash}_${item.name}_${JSON.stringify(item.context)}_${ item.step @@ -26,7 +21,6 @@ function ImagesList(props: any) { {data.map((item: any) => ( = { iconBgColor: '#18AB6D', navLink: routes.FIGURES_EXPLORER.path, }, - // [SequenceType.Image]: { - // label: 'Images', - // icon: 'images', - // count: 0, - // iconBgColor: '#F17922', - // navLink: routes.IMAGES_EXPLORER.path, - // }, + [SequenceType.Image]: { + label: 'Images', + icon: 'images', + count: 0, + iconBgColor: '#F17922', + navLink: routes.IMAGES_EXPLORER.path, + }, [SequenceType.Audio]: { label: 'Audios', icon: 'audios', diff --git a/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts new file mode 100644 index 0000000000..48ebed0db6 --- /dev/null +++ b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts @@ -0,0 +1,52 @@ +import produce from 'immer'; + +import { getDefaultHydration } from 'modules/BaseExplorer'; +import { GroupType, Order } from 'modules/core/pipeline'; +import { defaultHydration } from 'modules/BaseExplorer/getDefaultHydration'; +import { CaptionProperties } from 'modules/BaseExplorer/components/Controls'; + +import { GetSequenceName, SequenceType } from 'types/core/enums'; + +import getImagesExplorerStaticContent from './getStaticContent'; + +export const getImagesDefaultConfig = (): typeof defaultHydration => { + const defaultConfig = getDefaultHydration(); + const sequenceName = GetSequenceName(SequenceType.Image); + + const groupings = produce(defaultConfig.groupings, (draft: any) => { + draft[GroupType.COLUMN].defaultApplications.orders = [Order.ASC, Order.ASC]; + draft[GroupType.COLUMN].defaultApplications.fields = [ + 'run.hash', + `${sequenceName}.name`, + ]; + draft[GroupType.ROW].defaultApplications.orders = [Order.DESC]; + draft[GroupType.ROW].defaultApplications.fields = ['record.step']; + + draft[GroupType.GRID].defaultApplications.orders = [Order.ASC]; + draft[GroupType.GRID].defaultApplications.fields = [`${sequenceName}.name`]; + }); + + const controls = produce(defaultConfig.controls, (draft: any) => { + draft.captionProperties = { + component: CaptionProperties, + state: { + initialState: { + displayBoxCaption: true, + selectedFields: [ + 'run.name', + `${sequenceName}.name`, + `${sequenceName}.context`, + ], + }, + persist: 'url', + }, + }; + }); + + return { + ...defaultConfig, + groupings, + controls, + getStaticContent: getImagesExplorerStaticContent, + }; +}; diff --git a/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx new file mode 100644 index 0000000000..8d2faf5151 --- /dev/null +++ b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; + +import { DOCUMENTATIONS } from 'config/references'; + +import getBaseExplorerStaticContent, { + STATIC_CONTENT_TYPES, +} from 'modules/BaseExplorer/utils/getBaseExplorerStaticContent'; +import { StaticContentType } from 'modules/BaseExplorer/types'; + +function getImagesExplorerStaticContent( + type: StaticContentType, +): React.ReactNode { + const illustrationContent = getImagesExplorerIllustrationContent(type); + return getBaseExplorerStaticContent(type, illustrationContent); +} + +function getImagesExplorerIllustrationContent( + type: StaticContentType, +): React.ReactNode { + const Never_Executed = ( + <> + It’s super easy to search Aim experiments. Just start typing your query in + the search bar above. +
+ Look up + + search docs + + to learn more. + + ); + const Failed = 'Incorrect Query'; + const Insufficient_Resources = "You don't have any tracked images"; + const Empty = 'No Results'; + const Empty_Bookmarks = "You don't have any saved bookmark"; + + const CONTENT = { + [STATIC_CONTENT_TYPES.Never_Executed]: Never_Executed, + [STATIC_CONTENT_TYPES.Failed]: Failed, + [STATIC_CONTENT_TYPES.Insufficient_Resources]: Insufficient_Resources, + [STATIC_CONTENT_TYPES.Empty]: Empty, + [STATIC_CONTENT_TYPES.Empty_Bookmarks]: Empty_Bookmarks, + }; + return CONTENT[type] || null; +} + +export default getImagesExplorerStaticContent; diff --git a/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx new file mode 100644 index 0000000000..65ec258808 --- /dev/null +++ b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx @@ -0,0 +1,33 @@ +import { isDEVModeOn } from 'config/config'; + +import renderer from 'modules/BaseExplorer'; +import Image from 'modules/BaseExplorer/components/Image'; + +import { SequenceType } from 'types/core/enums'; + +import { getImagesDefaultConfig } from './config'; + +const defaultConfig = getImagesDefaultConfig(); + +export const imagesExplorerConfig = { + name: 'Images Explorer', + sequenceType: SequenceType.Image, + basePath: 'images', + persist: true, + groupings: defaultConfig.groupings, + visualizations: { + vis1: { + component: defaultConfig.Visualizer, + controls: defaultConfig.controls, + box: { + ...defaultConfig.box, + component: Image, + }, + }, + }, + getStaticContent: defaultConfig.getStaticContent, +}; + +const ImagesExplorer = renderer(imagesExplorerConfig, isDEVModeOn); + +export default ImagesExplorer; diff --git a/src/aimcore/web/ui/src/routes/routes.tsx b/src/aimcore/web/ui/src/routes/routes.tsx index 6aeb30fe32..96cd244f3a 100644 --- a/src/aimcore/web/ui/src/routes/routes.tsx +++ b/src/aimcore/web/ui/src/routes/routes.tsx @@ -1,13 +1,6 @@ import React from 'react'; -import { - IconChartDots, - IconFileAnalytics, - IconFlag3, - IconLayout2, - IconTable, - IconBox, -} from '@tabler/icons-react'; +import { IconChartDots, IconFileAnalytics, IconBox } from '@tabler/icons-react'; import { PathEnum } from 'config/enums/routesEnum'; import { ExplorersCatsEnum } from 'config/enums/explorersCatsEnum'; @@ -76,6 +69,12 @@ const AudiosExplorer = React.lazy( /* webpackChunkName: "AudiosExplorer" */ 'pages/Explorers/AudiosExplorer' ), ); +const ImagesExplorer = React.lazy( + () => + import( + /* webpackChunkName: "ImagesExplorer" */ 'pages/Explorers/ImagesExplorer' + ), +); const MetricsExplorer = React.lazy( () => @@ -138,32 +137,18 @@ export enum RouteStatusEnum { } export const explorersRoutes: { [key: string]: IRoute } = { - // METRICS: { - // path: PathEnum.Metrics, - // component: Metrics, - // showInSidebar: false, - // displayName: 'Metrics', - // description: - // 'Metrics Explorer allows to filter, group, aggregate tracked metrics.', - // icon: 'metrics', - // isExact: true, - // title: pageTitlesEnum.METRICS_EXPLORER, - // color: '#7A4CE0', - // category: ExplorersCatsEnum.Trainings, - // }, - // PARAMS: { - // path: PathEnum.Params, - // component: Params, - // showInSidebar: false, - // displayName: 'Params', - // description: - // 'Params Explorer helps to visualize tracked h-params and metrics results via parallel coordinates plot.', - // icon: 'params', - // isExact: true, - // title: pageTitlesEnum.PARAMS_EXPLORER, - // color: '#AF4EAB', - // category: ExplorersCatsEnum.Trainings, - // }, + METRICS_EXPLORER: { + path: PathEnum.Metrics_Explorer, + component: MetricsExplorer, + showInSidebar: false, + icon: 'metrics', + displayName: 'Metrics', + description: 'Explore thousands of tracked metrics with Metrics Explorer.', + isExact: true, + title: pageTitlesEnum.METRICS_EXPLORER, + color: '#7A4CE0', + category: ExplorersCatsEnum.Trainings, + }, TEXT_EXPLORER: { path: PathEnum.Text_Explorer, component: TextExplorer, @@ -190,19 +175,19 @@ export const explorersRoutes: { [key: string]: IRoute } = { color: '#1473E6', category: ExplorersCatsEnum.Prompts, }, - // IMAGES_EXPLORER: { - // path: PathEnum.Images_Explorer, - // component: ImagesExplore, - // showInSidebar: false, - // displayName: 'Images', - // description: - // 'Images Explorer allows comparison of tracked images during training and evaluation.', - // icon: 'images', - // isExact: true, - // title: pageTitlesEnum.IMAGES_EXPLORER, - // color: '#F17922', - // category: ExplorersCatsEnum.Trainings, - // }, + IMAGES_EXPLORER: { + path: PathEnum.Images_Explorer, + component: ImagesExplorer, + showInSidebar: false, + displayName: 'Images', + description: + 'Images Explorer allows comparison of tracked images during training and evaluation.', + icon: 'images', + isExact: true, + title: pageTitlesEnum.IMAGES_EXPLORER, + color: '#F17922', + category: ExplorersCatsEnum.Trainings, + }, FIGURES_EXPLORER: { path: PathEnum.Figures_Explorer, component: FiguresExplorer, @@ -242,18 +227,6 @@ export const explorersRoutes: { [key: string]: IRoute } = { // color: '#606986', // category: ExplorersCatsEnum.Trainings, // }, - METRICS_EXPLORER: { - path: PathEnum.Metrics_Explorer, - component: MetricsExplorer, - showInSidebar: false, - icon: 'metrics', - displayName: 'Metrics', - description: 'Explore thousands of tracked metrics with Metrics Explorer.', - isExact: true, - title: pageTitlesEnum.METRICS_EXPLORER, - color: '#7A4CE0', - category: ExplorersCatsEnum.Trainings, - }, }; const routes: { [key: string]: any } = {