From b6c46e9064bc0f6cd8d2da3a6bd9362e42457d8f Mon Sep 17 00:00:00 2001 From: dimakorzhovnik Date: Sat, 21 Sep 2024 03:08:30 +0300 Subject: [PATCH 1/2] fix(inference): added standard inference page --- src/features/Inference/Inference.tsx | 50 +++++++++++++++++++ .../ActionBar/ActionBar.module.scss | 9 ++++ .../components/ActionBar/ActionBar.tsx | 33 ++++++++++++ .../components/RowItems/RowItems.module.scss | 11 ++++ .../components/RowItems/RowItems.tsx | 43 ++++++++++++++++ src/features/Inference/hooks/useInference.ts | 29 +++++++++++ src/features/Inference/type.d.ts | 6 +++ src/router.tsx | 3 ++ src/routes.ts | 4 ++ 9 files changed, 188 insertions(+) create mode 100644 src/features/Inference/Inference.tsx create mode 100644 src/features/Inference/components/ActionBar/ActionBar.module.scss create mode 100644 src/features/Inference/components/ActionBar/ActionBar.tsx create mode 100644 src/features/Inference/components/RowItems/RowItems.module.scss create mode 100644 src/features/Inference/components/RowItems/RowItems.tsx create mode 100644 src/features/Inference/hooks/useInference.ts create mode 100644 src/features/Inference/type.d.ts diff --git a/src/features/Inference/Inference.tsx b/src/features/Inference/Inference.tsx new file mode 100644 index 000000000..9a7b02466 --- /dev/null +++ b/src/features/Inference/Inference.tsx @@ -0,0 +1,50 @@ +import { useEffect, useState } from 'react'; +import { Display, MainContainer } from 'src/components'; +import { getSearchQuery } from 'src/utils/search/utils'; +import { useParams } from 'react-router-dom'; +import Loader2 from 'src/components/ui/Loader2'; +import useInference from './hooks/useInference'; +import RowItems from './components/RowItems/RowItems'; +import ActionBarContainer from './components/ActionBar/ActionBar'; + +function Inference() { + const { query } = useParams(); + + const [keywordHash, setKeywordHash] = useState(''); + + const { data, isFetching, error } = useInference(keywordHash); + + useEffect(() => { + (async () => { + const keywordHash = await getSearchQuery(query || ''); + + setKeywordHash(keywordHash); + })(); + }, [query]); + + return ( + <> + + {isFetching && !data ? ( + + ) : data && data.result.length > 0 ? ( + + ) : error ? ( + +

{error.message}

+
+ ) : ( + +

+ there are no answers or questions to this particle
be the + first and create one +

+
+ )} +
+ + + ); +} + +export default Inference; diff --git a/src/features/Inference/components/ActionBar/ActionBar.module.scss b/src/features/Inference/components/ActionBar/ActionBar.module.scss new file mode 100644 index 000000000..7a92828d5 --- /dev/null +++ b/src/features/Inference/components/ActionBar/ActionBar.module.scss @@ -0,0 +1,9 @@ +.inputContainer { + width: 80%; + text-align: center; +} + +.input { + text-align: center !important; + font-size: 20px !important; +} diff --git a/src/features/Inference/components/ActionBar/ActionBar.tsx b/src/features/Inference/components/ActionBar/ActionBar.tsx new file mode 100644 index 000000000..7b9200e03 --- /dev/null +++ b/src/features/Inference/components/ActionBar/ActionBar.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ActionBar, Color, Input } from 'src/components'; +import { routes } from 'src/routes'; +import { replaceSlash } from 'src/utils/utils'; +import styles from './ActionBar.module.scss'; + +function ActionBarContainer() { + const navigate = useNavigate(); + const [valueInput, setValueInput] = useState(''); + + function submit(event: React.FormEvent) { + event.preventDefault(); + + navigate(routes.inference.getLink(replaceSlash(valueInput))); + } + + return ( + +
+ setValueInput(e.target.value)} + className={styles.input} + /> +
+
+ ); +} + +export default ActionBarContainer; diff --git a/src/features/Inference/components/RowItems/RowItems.module.scss b/src/features/Inference/components/RowItems/RowItems.module.scss new file mode 100644 index 000000000..14f4e3b7c --- /dev/null +++ b/src/features/Inference/components/RowItems/RowItems.module.scss @@ -0,0 +1,11 @@ +.infiniteScroll { + margin-top: 12px; + + display: flex; + flex-direction: column; + align-items: center; + + > * { + width: 100%; + } +} diff --git a/src/features/Inference/components/RowItems/RowItems.tsx b/src/features/Inference/components/RowItems/RowItems.tsx new file mode 100644 index 000000000..5123fdada --- /dev/null +++ b/src/features/Inference/components/RowItems/RowItems.tsx @@ -0,0 +1,43 @@ +import { useMemo, useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import Spark from 'src/components/search/Spark/Spark'; +import { Dots } from 'src/components'; +import styles from './RowItems.module.scss'; +import { InferenceItem } from '../../type'; + +const LOAD_COUNT = 10; + +function RowItems({ dataItem }: { dataItem: InferenceItem[] }) { + const [itemsToShow, setItemsToShow] = useState(20); + + const loadMore = () => { + setTimeout(() => { + setItemsToShow((i) => i + LOAD_COUNT); + }, 2000); + }; + + const displayedPalettes = useMemo( + () => + dataItem.slice(0, itemsToShow).map((item) => { + return ( + + ); + }), + [itemsToShow, dataItem] + ); + + return ( + all loaded

} + hasMore={dataItem.length > itemsToShow} + loader={} + className={styles.infiniteScroll} + > + {displayedPalettes} +
+ ); +} + +export default RowItems; diff --git a/src/features/Inference/hooks/useInference.ts b/src/features/Inference/hooks/useInference.ts new file mode 100644 index 000000000..c8e672ebe --- /dev/null +++ b/src/features/Inference/hooks/useInference.ts @@ -0,0 +1,29 @@ +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { InferenceItem } from '../type'; + +export type InferenceResponse = { + result: InferenceItem[]; + time: number; +}; + +const inferenceFetcher = async (hash: string) => { + return axios({ + method: 'get', + url: `https://st-inference.cybernode.ai/standard_inference?particle=${hash}`, + }) + .then((response) => response.data as InferenceResponse) + .catch((e) => console.error(e)); +}; + +function useInference(hash: string) { + const { data, isFetching, error } = useQuery( + ['useInference', hash], + () => inferenceFetcher(hash), + { enabled: Boolean(hash.length) } + ); + + return { data, isFetching, error }; +} + +export default useInference; diff --git a/src/features/Inference/type.d.ts b/src/features/Inference/type.d.ts new file mode 100644 index 000000000..d4052309e --- /dev/null +++ b/src/features/Inference/type.d.ts @@ -0,0 +1,6 @@ +export type InferenceItem = { + particle: string; + balance: number; + rank: number; + inference: number; +}; diff --git a/src/router.tsx b/src/router.tsx index 8f73a59ae..023c3ea54 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -59,6 +59,7 @@ import Cybernet from './features/cybernet/ui/Cybernet'; import Settings from './pages/Settings/Settings'; import FreestyleIde from './pages/robot/Soul/RuneEditor/FreestyleIde/FreestyleIde'; import Map from './pages/Portal/Map/Map'; +import Inference from './features/Inference/Inference'; type WrappedRouterProps = { children: React.ReactNode; @@ -147,6 +148,8 @@ function AppRouter() { /> } /> + } /> + } /> `/oracle/ask/${search}`, }, + inference: { + path: '/inference', + getLink: (search: string) => `/oracle/inference/${search}`, + }, teleport: { path: '/teleport', send: { From e1c9997a87e2730bcd6229e770104cf6c8efc23e Mon Sep 17 00:00:00 2001 From: dimakorzhovnik Date: Wed, 25 Sep 2024 23:12:41 +0300 Subject: [PATCH 2/2] fix(inference): sorting by inference, rank, balance --- src/features/Inference/Inference.tsx | 23 +++++++- .../components/Filters/Filters.module.scss | 25 +++++++++ .../Inference/components/Filters/Filters.tsx | 56 +++++++++++++++++++ .../components/RowItems/RowItems.tsx | 10 +++- src/features/Inference/hooks/useInference.ts | 12 +++- src/features/Inference/{type.d.ts => type.ts} | 6 ++ src/features/Inference/utils/sortByKey.ts | 20 +++++++ 7 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 src/features/Inference/components/Filters/Filters.module.scss create mode 100644 src/features/Inference/components/Filters/Filters.tsx rename src/features/Inference/{type.d.ts => type.ts} (54%) create mode 100644 src/features/Inference/utils/sortByKey.ts diff --git a/src/features/Inference/Inference.tsx b/src/features/Inference/Inference.tsx index 9a7b02466..413e83c26 100644 --- a/src/features/Inference/Inference.tsx +++ b/src/features/Inference/Inference.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Display, MainContainer } from 'src/components'; import { getSearchQuery } from 'src/utils/search/utils'; import { useParams } from 'react-router-dom'; @@ -6,11 +6,15 @@ import Loader2 from 'src/components/ui/Loader2'; import useInference from './hooks/useInference'; import RowItems from './components/RowItems/RowItems'; import ActionBarContainer from './components/ActionBar/ActionBar'; +import Filters from './components/Filters/Filters'; +import { SortBy } from './type'; +import sortByKey from './utils/sortByKey'; function Inference() { const { query } = useParams(); const [keywordHash, setKeywordHash] = useState(''); + const [sortBy, setSortBy] = useState(SortBy.inference); const { data, isFetching, error } = useInference(keywordHash); @@ -22,16 +26,29 @@ function Inference() { })(); }, [query]); + const dataSortByKey = useMemo(() => { + if (!data) { + return []; + } + + return sortByKey(data.result, sortBy); + }, [data, sortBy]); + return ( <> + {isFetching && !data ? ( ) : data && data.result.length > 0 ? ( - + ) : error ? ( -

{error.message}

+

{error.toString()}

) : ( diff --git a/src/features/Inference/components/Filters/Filters.module.scss b/src/features/Inference/components/Filters/Filters.module.scss new file mode 100644 index 000000000..ccf84fd94 --- /dev/null +++ b/src/features/Inference/components/Filters/Filters.module.scss @@ -0,0 +1,25 @@ +@import '../../../../components/containerGradient/saber/index.module.scss'; + +.header { + margin: 10px 0 -5px; + padding: 17px 12px 20px; + + display: flex; + justify-content: space-between; + align-content: center; + + @include saber('blue', top); + + @media (max-width: 680px) { + gap: 40px 0; + flex-wrap: wrap; + } +} + +.total { + color: rgba(221, 255, 255, 0.38); + + span { + color: rgba(255, 255, 255, 0.78); + } +} \ No newline at end of file diff --git a/src/features/Inference/components/Filters/Filters.tsx b/src/features/Inference/components/Filters/Filters.tsx new file mode 100644 index 000000000..e663775b4 --- /dev/null +++ b/src/features/Inference/components/Filters/Filters.tsx @@ -0,0 +1,56 @@ +import ButtonsGroup from 'src/components/buttons/ButtonsGroup/ButtonsGroup'; +import AdviserHoverWrapper from 'src/features/adviser/AdviserHoverWrapper/AdviserHoverWrapper'; +import { SortBy } from '../../type'; +import styles from './Filters.module.scss'; + +const sortConfig = { + [SortBy.rank]: { + label: '⭐', + tooltip: 'sort particles by cyberrank', + }, + [SortBy.inference]: { + label: '🔥', + tooltip: 'sort particles by inference', + }, + [SortBy.balance]: { + label: '⚡️', + tooltip: 'sort particles by balance of volt', + }, +}; + +type Props = { + filter: SortBy; + setFilter: (item: SortBy) => void; + total?: number; +}; + +function Filters({ filter, setFilter, total }: Props) { + return ( +
+ { + return { + label: sortConfig[sortType].label, + name: sortType, + checked: filter === sortType, + tooltip: sortConfig[sortType].tooltip, + }; + })} + onChange={(sortType: SortBy) => { + setFilter(sortType); + }} + /> + + {total && ( + +
+ {total} particles +
+
+ )} +
+ ); +} + +export default Filters; diff --git a/src/features/Inference/components/RowItems/RowItems.tsx b/src/features/Inference/components/RowItems/RowItems.tsx index 5123fdada..4f6731b50 100644 --- a/src/features/Inference/components/RowItems/RowItems.tsx +++ b/src/features/Inference/components/RowItems/RowItems.tsx @@ -7,7 +7,7 @@ import { InferenceItem } from '../../type'; const LOAD_COUNT = 10; -function RowItems({ dataItem }: { dataItem: InferenceItem[] }) { +function RowItems({ dataItem, sortBy }: { dataItem: InferenceItem[]; sortBy: string }) { const [itemsToShow, setItemsToShow] = useState(20); const loadMore = () => { @@ -20,10 +20,14 @@ function RowItems({ dataItem }: { dataItem: InferenceItem[] }) { () => dataItem.slice(0, itemsToShow).map((item) => { return ( - + ); }), - [itemsToShow, dataItem] + [itemsToShow, dataItem, sortBy] ); return ( diff --git a/src/features/Inference/hooks/useInference.ts b/src/features/Inference/hooks/useInference.ts index c8e672ebe..0c9c58ec2 100644 --- a/src/features/Inference/hooks/useInference.ts +++ b/src/features/Inference/hooks/useInference.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; -import { InferenceItem } from '../type'; +import { InferenceItem, SortBy } from '../type'; export type InferenceResponse = { result: InferenceItem[]; @@ -13,7 +13,9 @@ const inferenceFetcher = async (hash: string) => { url: `https://st-inference.cybernode.ai/standard_inference?particle=${hash}`, }) .then((response) => response.data as InferenceResponse) - .catch((e) => console.error(e)); + .catch((e) => { + throw new Error(`useInference: ${e}`); + }); }; function useInference(hash: string) { @@ -23,7 +25,11 @@ function useInference(hash: string) { { enabled: Boolean(hash.length) } ); - return { data, isFetching, error }; + return { + data, + isFetching, + error, + }; } export default useInference; diff --git a/src/features/Inference/type.d.ts b/src/features/Inference/type.ts similarity index 54% rename from src/features/Inference/type.d.ts rename to src/features/Inference/type.ts index d4052309e..50750d715 100644 --- a/src/features/Inference/type.d.ts +++ b/src/features/Inference/type.ts @@ -4,3 +4,9 @@ export type InferenceItem = { rank: number; inference: number; }; + +export enum SortBy { + inference = 'inference', + rank = 'rank', + balance = 'balance', +} diff --git a/src/features/Inference/utils/sortByKey.ts b/src/features/Inference/utils/sortByKey.ts new file mode 100644 index 000000000..62a28543a --- /dev/null +++ b/src/features/Inference/utils/sortByKey.ts @@ -0,0 +1,20 @@ +import { InferenceItem, SortBy } from '../type'; + +const sortByKey = (data: InferenceItem[], sortBy: SortBy) => { + if (!data) { + return []; + } + + switch (sortBy) { + case SortBy.balance: + return data.sort((a, b) => b.balance - a.balance); + + case SortBy.rank: + return data.sort((a, b) => b.rank - a.rank); + + default: + return data.sort((a, b) => b.inference - a.inference); + } +}; + +export default sortByKey;