303 lines
9.6 KiB
TypeScript
303 lines
9.6 KiB
TypeScript
'use client';
|
|
|
|
import { Fragment, useCallback, useEffect, useState } from 'react';
|
|
import styles from "./page.module.css";
|
|
import { CTAButton, H1, Notification, NotificationContainer, Stack, Text, UploadInput, useBoolean, useNotifications } from 'ohmy-ui';
|
|
import useDatasets from '@/modules/ingestion/useDatasets';
|
|
import DataView, { Data } from '@/modules/ingestion/DataView';
|
|
import DatasetsView from '@/modules/ingestion/DatasetsView';
|
|
import classNames from 'classnames';
|
|
import { TextLogo, LoadingIndicator } from '@/modules/app';
|
|
import { IFrameView } from '@/ui';
|
|
|
|
export default function Home() {
|
|
const {
|
|
datasets,
|
|
refreshDatasets,
|
|
} = useDatasets();
|
|
|
|
const [datasetData, setDatasetData] = useState<Data[]>([]);
|
|
const [selectedDataset, setSelectedDataset] = useState<string | null>(null);
|
|
|
|
const {
|
|
value: isWizardShown,
|
|
setFalse: hideWizard,
|
|
} = useBoolean(true);
|
|
const [wizardStep, setWizardStep] = useState<'add' | 'upload' | 'cognify' | 'explore'>('add');
|
|
const [wizardData, setWizardData] = useState<File[] | null>(null);
|
|
|
|
// useEffect(() => {
|
|
// if (datasets.length > 0) {
|
|
// hideWizard();
|
|
// }
|
|
// }, [datasets, hideWizard]);
|
|
|
|
useEffect(() => {
|
|
refreshDatasets();
|
|
}, [refreshDatasets]);
|
|
|
|
const openDatasetData = (dataset: { id: string }) => {
|
|
fetch(`http://0.0.0.0:8000/datasets/${dataset.id}/data`)
|
|
.then((response) => response.json())
|
|
.then(setDatasetData)
|
|
.then(() => setSelectedDataset(dataset.id));
|
|
};
|
|
|
|
const closeDatasetData = () => {
|
|
setDatasetData([]);
|
|
setSelectedDataset(null);
|
|
};
|
|
|
|
const { notifications, showNotification } = useNotifications();
|
|
|
|
const handleDataAdd = useCallback((dataset: { id: string }, files: File[]) => {
|
|
const formData = new FormData();
|
|
formData.append('datasetId', dataset.id);
|
|
const file = files[0];
|
|
formData.append('data', file, file.name);
|
|
|
|
return fetch('http://0.0.0.0:8000/add', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
.then(() => {
|
|
showNotification("Data added successfully.", 5000);
|
|
openDatasetData(dataset);
|
|
});
|
|
}, [showNotification])
|
|
|
|
const addWizardData = useCallback((files: File[]) => {
|
|
setWizardData(files);
|
|
setWizardStep('upload');
|
|
}, []);
|
|
|
|
const {
|
|
value: isUploadRunning,
|
|
setTrue: disableUploadRun,
|
|
setFalse: enableUploadRun,
|
|
} = useBoolean(false);
|
|
const uploadWizardData = useCallback(() => {
|
|
disableUploadRun()
|
|
handleDataAdd({ id: 'main' }, wizardData!)
|
|
.then(() => {
|
|
setWizardStep('cognify')
|
|
})
|
|
.finally(() => enableUploadRun());
|
|
}, [disableUploadRun, enableUploadRun, handleDataAdd, wizardData]);
|
|
|
|
const cognifyDataset = useCallback((dataset: { id: string }) => {
|
|
showNotification(`Cognification started for dataset "${dataset.id}".`, 5000);
|
|
|
|
return fetch('http://0.0.0.0:8000/cognify', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
datasets: [dataset.id],
|
|
}),
|
|
})
|
|
.then(() => {
|
|
showNotification(`Dataset "${dataset.id}" cognified.`, 5000);
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|
|
}, [showNotification]);
|
|
|
|
const {
|
|
value: isCognifyRunning,
|
|
setTrue: disableCognifyRun,
|
|
setFalse: enableCognifyRun,
|
|
} = useBoolean(false);
|
|
const cognifyWizardData = useCallback(() => {
|
|
disableCognifyRun();
|
|
cognifyDataset({ id: 'main' })
|
|
.then(() => {
|
|
setWizardStep('explore');
|
|
})
|
|
.finally(() => enableCognifyRun());
|
|
}, [cognifyDataset, disableCognifyRun, enableCognifyRun]);
|
|
|
|
const deleteDataset = useCallback((dataset: { id: string }) => {
|
|
fetch(`http://0.0.0.0:8000/datasets/${dataset.id}`, {
|
|
method: 'DELETE',
|
|
})
|
|
.then(() => {
|
|
showNotification(`Dataset "${dataset.id}" deleted.`, 5000);
|
|
refreshDatasets();
|
|
})
|
|
}, [refreshDatasets, showNotification]);
|
|
|
|
interface ExplorationWindowProps {
|
|
url: string;
|
|
title: string;
|
|
}
|
|
const [explorationWindowProps, setExplorationWindowProps] = useState<ExplorationWindowProps | null>(null);
|
|
const {
|
|
value: isExplorationWindowShown,
|
|
setTrue: showExplorationWindow,
|
|
setFalse: hideExplorationWindow,
|
|
} = useBoolean(false);
|
|
|
|
const openExplorationWindow = useCallback((explorationWindowProps: ExplorationWindowProps) => {
|
|
setExplorationWindowProps(explorationWindowProps);
|
|
showExplorationWindow();
|
|
}, [showExplorationWindow]);
|
|
|
|
const exploreDataset = useCallback((dataset: { id: string }) => {
|
|
fetch(`http://0.0.0.0:8000/datasets/${dataset.id}/graph`)
|
|
.then((response) => response.text())
|
|
.then((text) => text.replace('"', ''))
|
|
.then((graphUrl: string) => {
|
|
openExplorationWindow({
|
|
url: graphUrl,
|
|
title: dataset.id,
|
|
});
|
|
});
|
|
}, [openExplorationWindow]);
|
|
|
|
const exploreWizardData = useCallback(() => {
|
|
exploreDataset({ id: 'main' });
|
|
}, [exploreDataset]);
|
|
|
|
const closeWizard = useCallback(() => {
|
|
hideExplorationWindow();
|
|
hideWizard();
|
|
}, [hideExplorationWindow, hideWizard]);
|
|
|
|
if (isWizardShown) {
|
|
return (
|
|
<main className={classNames(styles.main, styles.noData)}>
|
|
<TextLogo />
|
|
<Stack gap="4" orientation="vertical" align="center/center" className={styles.noDataWizardContainer}>
|
|
<H1>Add Knowledge</H1>
|
|
<Stack gap="4" orientation="vertical" align="center/center">
|
|
{wizardStep === 'upload' && wizardData && (
|
|
<div className={styles.wizardDataset}>
|
|
{wizardData.map((file, index) => (
|
|
<Fragment key={index}>
|
|
<Text bold>{file.name}</Text>
|
|
<Text className={styles.fileSize} size="small">
|
|
{getBiggestUnitSize(file.size)}
|
|
</Text>
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
)}
|
|
{(wizardStep === 'add' || wizardStep === 'upload') && (
|
|
<Text>No data in the system. Let's add your data.</Text>
|
|
)}
|
|
{wizardStep === 'cognify' && (
|
|
<Text>Process data and make it explorable.</Text>
|
|
)}
|
|
{wizardStep === 'add' && (
|
|
<UploadInput onChange={addWizardData}>
|
|
<Text>Add data</Text>
|
|
</UploadInput>
|
|
)}
|
|
{wizardStep === 'upload' && (
|
|
<CTAButton disabled={isUploadRunning} onClick={uploadWizardData}>
|
|
<Stack gap="2" orientation="horizontal" align="center/center">
|
|
<Text>Upload</Text>
|
|
{isUploadRunning && (
|
|
<LoadingIndicator />
|
|
)}
|
|
</Stack>
|
|
</CTAButton>
|
|
)}
|
|
{wizardStep === 'cognify' && (
|
|
<>
|
|
{isCognifyRunning && (
|
|
<Text>Processing may take a minute, depending on data size.</Text>
|
|
)}
|
|
<CTAButton disabled={isCognifyRunning} onClick={cognifyWizardData}>
|
|
<Stack gap="2" orientation="horizontal" align="center/center">
|
|
<Text>Cognify</Text>
|
|
{isCognifyRunning && (
|
|
<LoadingIndicator />
|
|
)}
|
|
</Stack>
|
|
</CTAButton>
|
|
</>
|
|
)}
|
|
{wizardStep === 'explore' && (
|
|
<>
|
|
{!isExplorationWindowShown && (
|
|
<CTAButton onClick={exploreWizardData}>
|
|
<Text>Start exploring the data</Text>
|
|
</CTAButton>
|
|
)}
|
|
{isExplorationWindowShown && (
|
|
<IFrameView
|
|
src={explorationWindowProps!.url}
|
|
title={explorationWindowProps!.title}
|
|
onClose={closeWizard}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</Stack>
|
|
</Stack>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<main className={styles.main}>
|
|
<div className={classNames(styles.datasetsView, {
|
|
[styles.openDatasetData]: datasetData.length > 0,
|
|
})}>
|
|
<DatasetsView
|
|
datasets={datasets}
|
|
onDataAdd={handleDataAdd}
|
|
onDatasetClick={openDatasetData}
|
|
onDatasetCognify={cognifyDataset}
|
|
onDatasetDelete={deleteDataset}
|
|
onDatasetExplore={exploreDataset}
|
|
/>
|
|
{isExplorationWindowShown && (
|
|
<IFrameView
|
|
src={explorationWindowProps!.url}
|
|
title={explorationWindowProps!.title}
|
|
onClose={hideExplorationWindow}
|
|
/>
|
|
)}
|
|
</div>
|
|
{datasetData.length > 0 && selectedDataset && (
|
|
<div className={styles.dataView}>
|
|
<DataView
|
|
data={datasetData}
|
|
datasetId={selectedDataset}
|
|
onClose={closeDatasetData}
|
|
onDataAdd={handleDataAdd}
|
|
/>
|
|
</div>
|
|
)}
|
|
<NotificationContainer gap="1" bottom right>
|
|
{notifications.map((notification, index) => (
|
|
<Notification
|
|
key={notification.id}
|
|
isOpen={notification.isOpen}
|
|
style={{ top: `${index * 60}px` }}
|
|
expireIn={notification.expireIn}
|
|
onClose={notification.delete}
|
|
>
|
|
<Text>{notification.message}</Text>
|
|
</Notification>
|
|
))}
|
|
</NotificationContainer>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
function getBiggestUnitSize(sizeInBytes: number): string {
|
|
const units = ['B', 'KB', 'MB', 'GB'];
|
|
|
|
let i = 0;
|
|
while (sizeInBytes >= 1024 && i < units.length - 1) {
|
|
sizeInBytes /= 1024;
|
|
i++;
|
|
}
|
|
return `${sizeInBytes.toFixed(2)} ${units[i]}`;
|
|
}
|