diff --git a/packages/client/src/pages/import/ImportPage.tsx b/packages/client/src/pages/import/ImportPage.tsx index 405b9e83f..d5f093083 100644 --- a/packages/client/src/pages/import/ImportPage.tsx +++ b/packages/client/src/pages/import/ImportPage.tsx @@ -27,6 +27,9 @@ import { Typography, Box, Grid, + Tabs, + Tab, + Stack, } from '@semoss/ui'; import { stepsOne } from './import.constants'; @@ -46,6 +49,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { CONNECTION_OPTIONS } from './import.constants'; import { EstablishConnectionPage, ImportConnectionPage } from './'; import { Help } from '@/components/help'; +import Tooltip from '@mui/material/Tooltip'; const StyledContainer = styled('div')(({ theme }) => ({ display: 'flex', @@ -99,13 +103,16 @@ const StyledBox = styled(Box)({ marginBottom: '32px', }); -const StyledInnerBox = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), -})); +const StyledInnerBox = styled('div')<{ isModel?: boolean }>( + ({ theme, isModel }) => ({ + display: 'flex', + alignItems: isModel ? 'flex-start' : 'center', + gap: theme.spacing(1), + flexDirection: isModel ? 'column' : 'row', + }), +); -const StyledCardImage = styled('img')({ +const StyledCardImage = styled('img')<{ isModel?: boolean }>(({ isModel }) => ({ display: 'flex', height: '30px', width: '30px', @@ -115,7 +122,8 @@ const StyledCardImage = styled('img')({ overflowClipMargin: 'content-box', overflow: 'clip', objectFit: 'cover', -}); + borderRadius: isModel ? '8px' : 'inherit', +})); const StyledCardText = styled('p')({ overflow: 'hidden', @@ -124,6 +132,30 @@ const StyledCardText = styled('p')({ margin: '0', }); +const StyledCardModelText = styled('p')({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + margin: '2px 0 0', + alignSelf: 'stretch', + fontSize: '14px', + fontWeight: '500', + lineHeight: '143%', + letterSpacing: '0.17px', + color: '#212121', +}); + +const StyledTypographyText = styled(Typography)((theme) => ({ + display: 'flex', + alignItems: 'center', + padding: '0 10px', + backgroundColor: '#EBEBEB', + borderRadius: '16px', + marginLeft: 'auto !important', + fontSize: '13px', + color: '#212121', +})); + const StyledFormTypeBox = styled(Box, { shouldForwardProp: (prop) => prop !== 'disabled', })<{ @@ -152,6 +184,31 @@ const StyledFormTypeBox = styled(Box, { }; }); +const StyledFormTypeModelBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'disabled', +})<{ + disabled: boolean; +}>(({ disabled }) => { + return { + maxWidth: '215px', + borderRadius: '8px', + cursor: 'pointer', + display: 'block', + justifyContent: 'center', + alignItems: 'center', + border: '1px solid #C4C4C4', + padding: '16px', + backgroundColor: '#fff', + opacity: disabled ? 0.6 : 1, + + '&:hover': { + cursor: disabled ? 'auto' : 'pointer', + border: disabled ? '1px solid #C4C4C4' : '1.5px solid #0471F0', + backgroundColor: disabled ? 'white' : '#F5F9FE', + }, + }; +}); + const StyledSpan = styled('span')({ '&:hover': { cursor: 'pointer', @@ -164,10 +221,11 @@ const StyledCategoryTitle = styled(Box)({ padding: '16px', }); -const StyledSubCategoryTitle = styled(Box)({ - fontSize: '16px', - fontWeight: 'bold', - padding: '16px', +const StyledTab = styled(Tab)({ + fontSize: '14px', + fontWeight: '500', + letterSpacing: '0.4px', + color: 'rgba(0, 0, 0, 0.60)', }); const IconMapper = { @@ -192,9 +250,144 @@ export const ImportPage = () => { const [connectionOptions, setConnectionOptions] = React.useState(CONNECTION_OPTIONS); + const [selectedTab, setSelectedTab] = React.useState(0); + + const modelOptions = connectionOptions.MODEL; const scrollToTopRef = useRef(null); + const isModelPage = steps.length > 0 && steps[0].data === 'MODEL'; + + const ModelCard = ({ model, setSteps, steps }) => { + const textRef = useRef(null); + const [isTruncated, setIsTruncated] = React.useState(false); + + useEffect(() => { + const el = textRef.current; + if (el) { + setIsTruncated(el.scrollWidth > el.clientWidth); + } + }, [model.name]); + + const cardContent = ( + { + if (!model.disable) { + setSteps( + [ + ...steps, + { + id: `${model.name}`, + title: model.name, + description: `Fill out ${ + model.name + } details in order to add ${steps[0].data.toLowerCase()} to catalog`, + data: model.fields, + }, + ], + steps.length + 1, + ); + } + }} + > + + {model.disable ? ( + + + + Coming Soon + + + ) : ( + + )} + + + {model.name} + + + + ); + + return isTruncated ? ( + + {cardContent} + + ) : ( + cardContent + ); + }; + + const getTabLabels = () => { + const tabs = new Set(); + tabs.add('All'); + + const commercial = modelOptions['Commercially Hosted']; + if (commercial && typeof commercial === 'object') { + Object.keys(commercial).forEach((key) => tabs.add(key)); + } + + ['Locally Hosted', 'Embedded', 'File Uploads'].forEach((key) => { + if (modelOptions[key]) { + tabs.add(key); + } + }); + + return Array.from(tabs); + }; + + const tabLabels = getTabLabels(); + + const getAllModels = () => { + let allModels: any[] = []; + + const commercial = modelOptions['Commercially Hosted']; + if (commercial && typeof commercial === 'object') { + Object.values(commercial).forEach((models: any) => { + allModels = allModels.concat(models); + }); + } + + ['Locally Hosted', 'Embedded', 'File Uploads'].forEach((key) => { + const models = modelOptions[key]; + if (Array.isArray(models)) { + allModels = allModels.concat(models); + } + }); + + return allModels; + }; + + const getModelsForTab = (tab: string) => { + if (tab === 'All') return getAllModels(); + + const commercial = modelOptions['Commercially Hosted']; + if (commercial && commercial[tab]) { + return commercial[tab]; + } + + if (modelOptions[tab]) { + return modelOptions[tab]; + } + + return []; + }; + useEffect(() => { const paramedStep = { title: '', @@ -308,6 +501,24 @@ export const ImportPage = () => { } } + const renderModelsGrid = (models) => ( + + {models + .filter((m) => + m.name.toLowerCase().includes(search.toLowerCase()), + ) + .map((model, idx) => ( + + + + ))} + + ); + const mapEngineOptions = () => { const entries = Object.values(connectionOptions[steps[0].data]); @@ -405,170 +616,21 @@ export const ImportPage = () => { return ( - - Commercially Hosted - - {Object.entries(entries[0]).map( - (kv: [string, any[]], i) => { - // TODO FIX ANY TYPE - return ( -
- - {kv[0]} - - - - - {kv[1].map((stage, idx) => { - if ( - stage.name - .toLowerCase() - .includes( - search.toLowerCase(), - ) - ) { - return ( - - { - if ( - !stage.disable - ) { - setSteps( - [ - ...steps, - { - id: `${kv[0]}.${stage.name}`, - title: stage.name, - description: `Fill out ${ - stage.name - } details in order to add ${steps[0].data.toLowerCase()} to catalog`, - data: stage.fields, - }, - ], - steps.length + - 1, - ); - } - }} - > - - - - { - stage.name - } - - - - - ); - } - })} - - -
- ); - }, - )} - {e.map((kv: [string, any[]], i) => { - return ( - - - {kv[0]} - - - - - {kv[1].map((stage, idx) => { - if ( - stage.name - .toLowerCase() - .includes( - search.toLowerCase(), - ) - ) { - return ( - - { - if ( - !stage.disable - ) { - setSteps( - [ - ...steps, - { - id: `${kv[0]}.${stage.name}`, - title: stage.name, - description: `Fill out ${ - stage.name - } details in order to add ${steps[0].data.toLowerCase()} to catalog`, - data: stage.fields, - }, - ], - steps.length + - 1, - ); - } - }} - > - - - - {stage.name} - - - - - ); - } - })} - - - - ); - })} + setSelectedTab(newValue)} + variant="scrollable" + sx={{ mt: 2, borderBottom: '2px solid #E0E0E0' }} + > + {tabLabels.map((label, i) => ( + + ))} + + + {renderModelsGrid( + getModelsForTab(tabLabels[selectedTab]), + )} +
); } @@ -580,6 +642,11 @@ export const ImportPage = () => { {steps.length ? ( { setSteps([], -1); navigate('/import'); @@ -608,12 +675,18 @@ export const ImportPage = () => { ) : (
 
)} - + {steps.length ? steps[steps.length - 1].title : 'Add Source'} - + {steps.length ? steps[steps.length - 1].description : "Welcome to our integrated data nexus, your gateway to a world of interconnected possibilities. This page empowers you with the freedom to effortlessly connect to diverse databases, wield versatile storage solutions, and tap into the transformative capabilities of Large Language Models (LLMs). Whether you're a developer, analyst, or visionary, our platform serves as a springboard for unified data orchestration, innovation, and insights."}