feat: add settings and search features

This commit is contained in:
Boris Arzentar 2024-05-21 10:27:19 +02:00
parent aa2d6e97f0
commit c50cf1b096
30 changed files with 1374 additions and 1094 deletions

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
"dependencies": {
"classnames": "^2.5.1",
"next": "14.2.3",
"ohmy-ui": "^0.0.1",
"ohmy-ui": "^0.0.2",
"react": "^18",
"react-dom": "^18",
"uuid": "^9.0.1"

View file

@ -1,14 +1,20 @@
.main {
display: flex;
flex-direction: row;
flex-direction: column;
padding: 32px;
min-height: 100vh;
gap: 32px;
}
.main.noData {
flex-direction: column;
}
.data {
display: flex;
flex-direction: row;
flex: 1;
gap: 32px;
}
.datasetsView {
width: 50%;
transition: width 0.3s ease-in-out;
@ -33,19 +39,3 @@
width: 70%;
}
}
.noDataWizardContainer {
width: 100%;
margin-top: 96px;
}
.wizardDataset {
border: 2px solid white;
border-radius: var(--border-radius);
padding: 24px;
min-width: 350px;
}
.fileSize {
display: block;
}

View file

@ -1,14 +1,18 @@
'use client';
import { Fragment, useCallback, useEffect, useState } from 'react';
import { 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 { Notification, NotificationContainer, Text, 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';
import addData from '@/modules/ingestion/addData';
import cognifyDataset from '@/modules/datasets/cognifyDataset';
import deleteDataset from '@/modules/datasets/deleteDataset';
import getDatasetData from '@/modules/datasets/getDatasetData';
import getExplorationGraphUrl from '@/modules/exploration/getExplorationGraphUrl';
import { Footer } from '@/ui/Partials';
export default function Home() {
const {
@ -19,26 +23,12 @@ export default function Home() {
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())
getDatasetData(dataset)
.then(setDatasetData)
.then(() => setSelectedDataset(dataset.id));
};
@ -50,232 +40,67 @@ export default function Home() {
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,
})
const onDataAdd = useCallback((dataset: { id: string }, files: File[]) => {
return addData(dataset, files)
.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 }) => {
const onDatasetCognify = 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],
}),
})
return cognifyDataset(dataset)
.then(() => {
showNotification(`Dataset "${dataset.id}" cognified.`, 5000);
})
.catch((error) => {
console.error(error);
.catch(() => {
showNotification(`Dataset "${dataset.id}" cognification failed. Please try again.`, 5000);
});
}, [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',
})
const onDatasetDelete = useCallback((dataset: { id: string }) => {
deleteDataset(dataset)
.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&apos;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>
);
}
const onDatasetExplore = useCallback((dataset: { id: string }) => {
return getExplorationGraphUrl(dataset);
}, []);
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 className={styles.data}>
<div className={classNames(styles.datasetsView, {
[styles.openDatasetData]: datasetData.length > 0,
})}>
<DatasetsView
datasets={datasets}
onDataAdd={onDataAdd}
onDatasetClick={openDatasetData}
onDatasetCognify={onDatasetCognify}
onDatasetDelete={onDatasetDelete}
onDatasetExplore={onDatasetExplore}
/>
</div>
)}
{datasetData.length > 0 && selectedDataset && (
<div className={styles.dataView}>
<DataView
data={datasetData}
datasetId={selectedDataset}
onClose={closeDatasetData}
onDataAdd={onDataAdd}
/>
</div>
)}
</div>
<Footer />
<NotificationContainer gap="1" bottom right>
{notifications.map((notification, index) => (
{notifications.map((notification, index: number) => (
<Notification
key={notification.id}
isOpen={notification.isOpen}
@ -290,14 +115,3 @@ export default function Home() {
</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]}`;
}

View file

@ -0,0 +1,24 @@
.main {
display: flex;
flex-direction: row;
flex-direction: column;
padding: 32px;
min-height: 100vh;
gap: 32px;
}
.wizardContainer {
flex: 1;
width: 100%;
}
.wizardDataset {
border: 2px solid white;
border-radius: var(--border-radius);
padding: 24px;
min-width: 350px;
}
.fileSize {
display: block;
}

View file

@ -0,0 +1,198 @@
import { useCallback, useState } from 'react';
import { IFrameView } from '@/ui';
import { SettingsIcon } from '@/ui/Icons';
import { LoadingIndicator, TextLogo } from '@/modules/app';
import { CTAButton, GhostButton, H1, Stack, Text, UploadInput, useBoolean } from 'ohmy-ui';
import { Footer, SettingsModal } from '@/ui/Partials';
import styles from './WizardPage.module.css';
interface ExplorationWindowConfig {
url: string;
title: string;
}
interface WizardPageProps {
onDataAdd: (dataset: { id: string }, files: File[]) => Promise<void>;
onDataCognify: (dataset: { id: string }) => Promise<void>;
onDataExplore: (dataset: { id: string }) => Promise<ExplorationWindowConfig>;
onFinish: () => void;
}
export default function WizardPage({
onDataAdd,
onDataCognify,
onDataExplore,
onFinish,
}: WizardPageProps) {
const [wizardStep, setWizardStep] = useState<'add' | 'upload' | 'cognify' | 'explore'>('add');
const [wizardData, setWizardData] = useState<File[] | null>(null);
const addWizardData = useCallback((files: File[]) => {
setWizardData(files);
setWizardStep('upload');
}, []);
const {
value: isUploadRunning,
setTrue: disableUploadRun,
setFalse: enableUploadRun,
} = useBoolean(false);
const uploadWizardData = useCallback(() => {
disableUploadRun()
onDataAdd({ id: 'main' }, wizardData!)
.then(() => {
setWizardStep('cognify')
})
.finally(() => enableUploadRun());
}, [disableUploadRun, enableUploadRun, onDataAdd, wizardData]);
const {
value: isCognifyRunning,
setTrue: disableCognifyRun,
setFalse: enableCognifyRun,
} = useBoolean(false);
const cognifyWizardData = useCallback(() => {
disableCognifyRun();
onDataCognify({ id: 'main' })
.then(() => {
setWizardStep('explore');
})
.finally(() => enableCognifyRun());
}, [onDataCognify, disableCognifyRun, enableCognifyRun]);
const {
value: isExploreLoading,
setTrue: startLoadingExplore,
setFalse: finishLoadingExplore,
} = useBoolean(false);
const [explorationWindowProps, setExplorationWindowProps] = useState<ExplorationWindowConfig | null>(null);
const {
value: isExplorationWindowShown,
setTrue: showExplorationWindow,
} = useBoolean(false);
const openExplorationWindow = useCallback((explorationWindowProps: ExplorationWindowConfig) => {
setExplorationWindowProps(explorationWindowProps);
showExplorationWindow();
}, [showExplorationWindow]);
const exploreWizardData = useCallback(() => {
startLoadingExplore();
onDataExplore({ id: 'main' })
.then((exploreWindowConfig) => {
openExplorationWindow(exploreWindowConfig);
})
.finally(() => {
finishLoadingExplore();
});
}, [finishLoadingExplore, onDataExplore, openExplorationWindow, startLoadingExplore]);
const {
value: isSettingsModalOpen,
setTrue: openSettingsModal,
setFalse: closeSettingsModal,
} = useBoolean(false);
return (
<main className={styles.main}>
<Stack orientation="horizontal" gap="between" align="center">
<TextLogo />
<GhostButton onClick={openSettingsModal}>
<SettingsIcon />
</GhostButton>
</Stack>
<SettingsModal isOpen={isSettingsModalOpen} onClose={closeSettingsModal} />
<Stack gap="4" orientation="vertical" align="center/center" className={styles.wizardContainer}>
{wizardStep === 'explore'
? (<H1>Explore the Knowledge</H1>)
: (<H1>Add Knowledge</H1>)}
<Stack gap="4" orientation="vertical" align="center/center">
{wizardStep === 'upload' && wizardData && (
<Stack gap="4" className={styles.wizardDataset}>
{wizardData.map((file, index) => (
<div key={index}>
<Text bold>{file.name}</Text>
<Text className={styles.fileSize} size="small">
{getBiggestUnitSize(file.size)}
</Text>
</div>
))}
</Stack>
)}
{(wizardStep === 'add' || wizardStep === 'upload') && (
<Text>No data in the system. Let&apos;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}>
<Stack gap="2" orientation="horizontal" align="center/center">
<Text>Start exploring</Text>
{isExploreLoading && (
<LoadingIndicator />
)}
</Stack>
</CTAButton>
)}
{isExplorationWindowShown && (
<IFrameView
src={explorationWindowProps!.url}
title={explorationWindowProps!.title}
onClose={onFinish}
/>
)}
</>
)}
</Stack>
</Stack>
<Footer />
</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]}`;
}

View file

@ -0,0 +1,32 @@
'use client';
import { useCallback } from 'react';
import WizardPage from './WizardPage';
import addData from '@/modules/ingestion/addData';
import cognifyDataset from '@/modules/datasets/cognifyDataset';
import getExplorationGraphUrl from '@/modules/exploration/getExplorationGraphUrl';
export default function Page() {
const onDataExplore = useCallback((dataset: { id: string }) => {
return getExplorationGraphUrl(dataset)
.then((explorationWindowUrl) => {
return {
url: explorationWindowUrl,
title: dataset.id,
};
});
}, []);
const finishWizard = useCallback(() => {
window.location.href = '/';
}, []);
return (
<WizardPage
onDataAdd={addData}
onDataCognify={cognifyDataset}
onDataExplore={onDataExplore}
onFinish={finishWizard}
/>
);
}

View file

@ -0,0 +1,11 @@
export default function cognifyDataset(dataset: { id: string }) {
return fetch('http://0.0.0.0:8000/cognify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
datasets: [dataset.id],
}),
}).then((response) => response.json());
}

View file

@ -0,0 +1,5 @@
export default function deleteDataset(dataset: { id: string }) {
return fetch(`http://0.0.0.0:8000/datasets/${dataset.id}`, {
method: 'DELETE',
})
}

View file

@ -0,0 +1,4 @@
export default function getDatasetData(dataset: { id: string }) {
return fetch(`http://0.0.0.0:8000/datasets/${dataset.id}/data`)
.then((response) => response.json());
}

View file

@ -0,0 +1,5 @@
export default function getExplorationGraphUrl(dataset: { id: string }) {
return fetch(`http://0.0.0.0:8000/datasets/${dataset.id}/graph`)
.then((response) => response.text())
.then((text) => text.replace('"', ''));
}

View file

@ -1,6 +1,10 @@
import { DropdownMenu, GhostButton, Stack, Text, UploadInput, CTAButton } from "ohmy-ui";
import styles from "./DatasetsView.module.css";
import { useCallback, useState } from 'react';
import { IFrameView } from '@/ui';
import StatusIcon from './StatusIcon';
import { LoadingIndicator } from '@/modules/app';
import { DropdownMenu, GhostButton, Stack, Text, UploadInput, CTAButton, useBoolean, NeutralButton } from "ohmy-ui";
import styles from "./DatasetsView.module.css";
import { SearchView } from '@/ui/Partials';
interface Dataset {
id: string;
@ -10,13 +14,18 @@ interface Dataset {
const DatasetItem = GhostButton.mixClassName()("div")
interface ExplorationWindowConfig {
url: string;
title: string;
}
interface DatasetsViewProps {
datasets: Dataset[];
onDataAdd: (dataset: Dataset, files: File[]) => void;
onDatasetClick: (dataset: Dataset) => void;
onDatasetDelete: (dataset: Dataset) => void;
onDatasetCognify: (dataset: Dataset) => void;
onDatasetExplore: (dataset: Dataset) => void;
onDatasetCognify: (dataset: Dataset) => Promise<void>;
onDatasetExplore: (dataset: Dataset) => Promise<string>;
}
export default function DatasetsView({
@ -27,9 +36,19 @@ export default function DatasetsView({
onDatasetDelete,
onDatasetExplore,
}: DatasetsViewProps) {
const {
value: isCognifyRunning,
setTrue: disableCognifyRun,
setFalse: enableCognifyRun,
} = useBoolean(false);
const handleCognifyDataset = (event: React.MouseEvent<HTMLButtonElement>, dataset: Dataset) => {
event.stopPropagation();
onDatasetCognify(dataset);
disableCognifyRun();
onDatasetCognify(dataset)
.finally(() => enableCognifyRun());
}
// const handleDatasetDelete = (event: React.MouseEvent<HTMLButtonElement>, dataset: Dataset) => {
@ -37,52 +56,118 @@ export default function DatasetsView({
// onDatasetDelete(dataset);
// }
const [explorationWindowProps, setExplorationWindowProps] = useState<ExplorationWindowConfig | null>(null);
const {
value: isExplorationWindowShown,
setTrue: showExplorationWindow,
setFalse: hideExplorationWindow,
} = useBoolean(false);
const openExplorationWindow = useCallback((explorationWindowProps: ExplorationWindowConfig) => {
setExplorationWindowProps(explorationWindowProps);
showExplorationWindow();
}, [showExplorationWindow]);
const {
value: isExploreLoading,
setTrue: startLoadingExplore,
setFalse: finishLoadingExplore,
} = useBoolean(false);
const handleExploreDataset = (event: React.MouseEvent<HTMLButtonElement>, dataset: Dataset) => {
event.stopPropagation();
onDatasetExplore(dataset);
startLoadingExplore();
onDatasetExplore(dataset)
.then((explorationWindowUrl) => {
openExplorationWindow({
url: explorationWindowUrl,
title: dataset.id,
});
})
.finally(() => finishLoadingExplore());
}
const handleDataAdd = (dataset: Dataset, files: File[]) => {
onDataAdd(dataset, files);
}
const {
value: isSearchWindowOpen,
setTrue: openSearchWindow,
setFalse: closeSearchWindow,
} = useBoolean(false);
const handleSearchDataset = (event: React.MouseEvent<HTMLButtonElement>, dataset: Dataset) => {
event.stopPropagation();
openSearchWindow();
}
return (
<Stack orientation="vertical" gap="4">
{datasets.map((dataset) => (
<DatasetItem key={dataset.id} onClick={() => onDatasetClick(dataset)}>
<Stack orientation="horizontal" gap="between" align="start/center">
<Text>{dataset.name}</Text>
<Stack orientation="horizontal" gap="2" align="center">
<StatusIcon status={dataset.status} />
<DropdownMenu>
<Stack gap="1" className={styles.datasetMenu} orientation="vertical">
{dataset.status === 'DATASET_PROCESSING_FINISHED' ? (
<CTAButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleExploreDataset(event, dataset)}
<>
<Stack orientation="vertical" gap="4">
{datasets.map((dataset) => (
<DatasetItem key={dataset.id} onClick={() => onDatasetClick(dataset)}>
<Stack orientation="horizontal" gap="between" align="start/center">
<Text>{dataset.name}</Text>
<Stack orientation="horizontal" gap="2" align="center">
<StatusIcon status={dataset.status} />
<DropdownMenu>
<Stack gap="1" className={styles.datasetMenu} orientation="vertical">
{dataset.status === 'DATASET_PROCESSING_FINISHED' ? (
<>
<CTAButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleExploreDataset(event, dataset)}
>
<Stack gap="2" orientation="horizontal" align="center/center">
<Text>Explore</Text>
{isExploreLoading && (
<LoadingIndicator />
)}
</Stack>
</CTAButton>
<NeutralButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleSearchDataset(event, dataset)}
>
<Text>Search</Text>
</NeutralButton>
</>
) : (
<CTAButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleCognifyDataset(event, dataset)}
>
<Stack gap="2" orientation="horizontal" align="center/center">
<Text>Cognify</Text>
{isCognifyRunning && (
<LoadingIndicator />
)}
</Stack>
</CTAButton>
)}
<UploadInput as={GhostButton} onChange={(files: File[]) => handleDataAdd(dataset, files)}>
<Text>Add data</Text>
</UploadInput>
{/* <NegativeButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleDatasetDelete(event, dataset)}
>
<Text>Explore</Text>
</CTAButton>
) : (
<CTAButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleCognifyDataset(event, dataset)}
>
<Text>Cognify</Text>
</CTAButton>
)}
<UploadInput as={GhostButton} onChange={(files: File[]) => handleDataAdd(dataset, files)}>
<Text>Add data</Text>
</UploadInput>
{/* <NegativeButton
onClick={(event: React.MouseEvent<HTMLButtonElement>) => handleDatasetDelete(event, dataset)}
>
<Text>Delete</Text>
</NegativeButton> */}
</Stack>
</DropdownMenu>
<Text>Delete</Text>
</NegativeButton> */}
</Stack>
</DropdownMenu>
</Stack>
</Stack>
</Stack>
</DatasetItem>
))}
</Stack>
</DatasetItem>
))}
</Stack>
{isSearchWindowOpen && (
<SearchView onClose={closeSearchWindow} />
)}
{isExplorationWindowShown && (
<IFrameView
src={explorationWindowProps!.url}
title={explorationWindowProps!.title}
onClose={hideExplorationWindow}
/>
)}
</>
);
}

View file

@ -0,0 +1,11 @@
export default function addData(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((response) => response.json());
}

View file

@ -70,7 +70,10 @@ function useDatasets() {
.then((datasets) => datasets.map((dataset: string) => ({ id: dataset, name: dataset })))
.then((datasets) => {
setDatasets(datasets);
checkDatasetStatuses(datasets);
if (datasets.length > 0) {
checkDatasetStatuses(datasets);
}
});
}, [checkDatasetStatuses]);

View file

@ -0,0 +1,7 @@
export default function DiscordIcon({ width = 26, height = 20, color = 'currentColor', className = '' }) {
return (
<svg width={width} height={height} viewBox="0 0 26 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M21.8932 1.67459C20.1988 0.882007 18.4062 0.31885 16.5629 0C16.3116 0.460879 16.0836 0.934131 15.8798 1.41796C13.9218 1.11898 11.9297 1.11898 9.97166 1.41796C9.76656 0.933656 9.53639 0.460364 9.28207 0C7.43679 0.319052 5.64255 0.883808 3.9472 1.67921C0.93192 6.05499 -0.425956 11.3618 0.117059 16.6481C2.08567 18.1347 4.29746 19.2682 6.65387 19.9982C7.18384 19.269 7.65226 18.4971 8.05428 17.6903C7.28899 17.3987 6.55124 17.0394 5.84981 16.6167C6.03444 16.48 6.21907 16.3397 6.39077 16.1892C8.42961 17.1708 10.6634 17.6806 12.9262 17.6806C15.189 17.6806 17.4228 17.1708 19.4616 16.1892C19.6389 16.3369 19.8189 16.48 20.0026 16.6167C19.2998 17.0404 18.5605 17.4004 17.7935 17.6921C18.1952 18.4991 18.6637 19.271 19.1939 20C21.5524 19.2708 23.7659 18.1365 25.7353 16.6481C26.278 11.3587 24.9156 6.04922 21.8932 1.67459ZM8.6331 13.634C7.97676 13.5863 7.36544 13.2826 6.93095 12.7884C6.49646 12.2942 6.27361 11.649 6.31046 10.9919C6.26895 10.3336 6.4901 9.68562 6.92546 9.19004C7.36083 8.69446 7.97489 8.39167 8.6331 8.34803C8.95965 8.36785 9.27905 8.45212 9.57288 8.59599C9.8667 8.73985 10.1291 8.94046 10.3451 9.18624C10.561 9.43202 10.7261 9.71812 10.8309 10.028C10.9357 10.3379 10.9781 10.6655 10.9557 10.9919C10.9967 11.65 10.7754 12.2974 10.34 12.7926C9.90474 13.2878 9.29097 13.5903 8.6331 13.634ZM17.2184 13.634C16.562 13.5863 15.9507 13.2826 15.5162 12.7884C15.0817 12.2942 14.8589 11.649 14.8957 10.9919C14.8542 10.3336 15.0754 9.68562 15.5107 9.19004C15.9461 8.69446 16.5602 8.39167 17.2184 8.34803C17.5449 8.36785 17.8643 8.45212 18.1582 8.59599C18.452 8.73985 18.7144 8.94046 18.9303 9.18624C19.1463 9.43202 19.3114 9.71812 19.4162 10.028C19.521 10.3379 19.5634 10.6655 19.541 10.9919C19.582 11.6501 19.3605 12.2977 18.925 12.7929C18.4895 13.2881 17.8764 13.5906 17.2184 13.634Z" fill={color} />
</svg>
);
}

View file

@ -0,0 +1,10 @@
export default function GitHubIcon({ width = 24, height = 24, color = 'currentColor', className = '' }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 28 28" className={className}>
<g transform="translate(-1477 -38)">
<rect width="28" height="28" transform="translate(1477 38)" fill={color} opacity="0" />
<path d="M16.142,1.9A13.854,13.854,0,0,0,11.78,28.966c.641.128,1.155-.577,1.155-1.154v-1.86c-3.848.834-5.067-1.86-5.067-1.86a4.169,4.169,0,0,0-1.411-2.052c-1.283-.9.064-.834.064-.834a2.758,2.758,0,0,1,2.117,1.283c1.09,1.86,3.528,1.668,4.3,1.347a3.463,3.463,0,0,1,.321-1.86c-4.361-.77-6.735-3.335-6.735-6.8A6.863,6.863,0,0,1,8.381,10.3a3.977,3.977,0,0,1,.192-4.1,5.708,5.708,0,0,1,4.1,1.86,9.685,9.685,0,0,1,3.463-.513,10.968,10.968,0,0,1,3.463.449,5.773,5.773,0,0,1,4.1-1.8,4.169,4.169,0,0,1,.257,4.1,6.863,6.863,0,0,1,1.8,4.875c0,3.463-2.373,6.029-6.735,6.8a3.464,3.464,0,0,1,.321,1.86v3.977a1.155,1.155,0,0,0,1.219,1.155A13.918,13.918,0,0,0,16.142,1.9Z" transform="translate(1474.913 36.102)" fill="#fdfdfd"/>
</g>
</svg>
);
}

View file

@ -0,0 +1,66 @@
export default function SettingsIcon({ width = "32px", height = "32px", color = "white" }) {
return (
<svg fill={color} height={height} width={width} version="1.1" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 397.516 397.516" xmlSpace="preserve">
<g>
<path d="M126.32,156.454c-37.993,0-68.901,30.911-68.901,68.905c0,37.991,30.909,68.9,68.901,68.9s68.9-30.909,68.9-68.9
C195.22,187.365,164.311,156.454,126.32,156.454z M126.32,279.641c-29.932,0-54.283-24.351-54.283-54.281
c0-29.934,24.352-54.286,54.283-54.286s54.282,24.353,54.282,54.286C180.602,255.29,156.251,279.641,126.32,279.641z"/>
<path d="M241.133,193.697l-9.568-2.638c-1.085-0.299-2.955-2.038-3.333-3.102l-2.717-6.683l-0.152-0.346
c-0.483-1.028-0.382-3.607,0.179-4.597l4.819-8.491c3.36-5.921,2.264-14.015-2.549-18.824l-23.776-23.779
c-2.852-2.848-6.952-4.482-11.248-4.482c-2.723,0-5.341,0.669-7.57,1.935l-8.038,4.561c-0.324,0.184-1.251,0.458-2.478,0.458
c-1.061,0-1.766-0.207-1.991-0.316l-8.275-3.484l-0.348-0.136c-1.068-0.385-2.818-2.276-3.121-3.375l-2.719-9.851
c-1.81-6.563-8.307-11.511-15.113-11.511h-33.629c-6.807,0-13.303,4.949-15.11,11.508l-2.723,9.855
c-0.303,1.101-2.06,3.003-3.132,3.393l-8.905,3.768l-0.378,0.173c-0.223,0.11-0.926,0.318-1.988,0.318
c-1.202,0.001-2.109-0.267-2.429-0.448l-7.565-4.295c-2.231-1.266-4.851-1.936-7.575-1.936c-4.3,0-8.4,1.636-11.247,4.484
l-23.782,23.778c-4.812,4.813-5.906,12.904-2.546,18.822l4.736,8.343c0.565,0.998,0.677,3.584,0.198,4.613
c-1.323,2.844-4.967,8.298-6.713,9.848l-8.841,2.438C4.946,195.509,0,202.006,0,208.812v33.626c0,6.803,4.946,13.3,11.506,15.112
l9.568,2.641c1.088,0.3,2.96,2.038,3.338,3.101l2.945,7.17l0.149,0.338c0.484,1.024,0.39,3.586-0.169,4.568l-4.362,7.68
c-3.356,5.916-2.261,14.006,2.55,18.822l23.78,23.777c2.85,2.85,6.95,4.484,11.248,4.484l0,0c2.723,0,5.342-0.669,7.576-1.936
l7.361-4.177c0.327-0.186,1.26-0.461,2.492-0.461c1.062,0,1.769,0.206,1.995,0.315l8.39,3.522l0.357,0.139
c1.065,0.382,2.81,2.264,3.112,3.358l2.56,9.276c1.808,6.561,8.305,11.511,15.111,11.511h33.629
c6.806,0,13.303-4.948,15.113-11.511l2.558-9.279c0.3-1.087,2.038-2.957,3.099-3.335l7.735-3.188l0.355-0.158
c0.225-0.107,0.931-0.311,1.99-0.311c1.259,0,2.214,0.282,2.548,0.472l7.823,4.443c2.232,1.267,4.851,1.936,7.576,1.936
c4.3,0,8.4-1.636,11.248-4.485l23.778-23.777c4.814-4.812,5.91-12.904,2.549-18.825l-4.441-7.82
c-0.556-0.979-0.647-3.525-0.163-4.541l3.188-7.659l0.134-0.347c0.379-1.064,2.253-2.805,3.343-3.105l9.57-2.64
c6.559-1.812,11.505-8.309,11.505-15.112v-33.623C252.641,202.006,247.695,195.508,241.133,193.697z M237.247,243.459
l-9.568,2.64c-5.615,1.549-11.11,6.61-13.151,12.086l-2.914,7.023c-2.439,5.314-2.139,12.778,0.738,17.851l4.422,7.782
c0.124,0.31,0.021,1.075-0.152,1.31L192.875,315.9c-0.096,0.073-0.467,0.233-0.944,0.233c-0.22,0-0.366-0.046-0.357-0.03
l-7.824-4.443c-2.702-1.534-6.17-2.379-9.766-2.379c-2.072,0-5.137,0.288-8.082,1.641l-7.098,2.934
c-5.479,2.037-10.544,7.533-12.093,13.151l-2.544,9.234c-0.13,0.305-0.73,0.766-1.066,0.82l-33.553,0.002
c-0.331-0.045-0.946-0.513-1.064-0.78l-2.56-9.276c-1.546-5.609-6.598-11.106-12.064-13.157l-7.725-3.232
c-2.97-1.383-6.063-1.678-8.155-1.678c-3.572,0-7.02,0.841-9.707,2.366l-7.32,4.155c-0.036,0.015-0.178,0.053-0.402,0.053
c-0.478,0-0.85-0.161-0.913-0.204l-23.747-23.741c-0.204-0.268-0.309-1.036-0.206-1.304l4.36-7.676
c2.873-5.058,3.185-12.52,0.766-17.839l-2.701-6.555c-2.037-5.48-7.535-10.548-13.153-12.097l-9.521-2.625
c-0.309-0.132-0.778-0.748-0.822-1.035l-0.002-33.581c0.045-0.333,0.514-0.949,0.777-1.067l9.563-2.637
c8.015-2.207,15.287-17.422,15.357-17.572c2.473-5.313,2.164-12.878-0.737-17.994l-4.718-8.307
c-0.124-0.312-0.021-1.076,0.15-1.309l23.749-23.748c0.096-0.073,0.467-0.232,0.943-0.232c0.222,0,0.363,0.041,0.359,0.03
l7.562,4.292c2.674,1.52,6.101,2.357,9.649,2.357c2.116,0,5.241-0.303,8.236-1.722l8.238-3.494
c5.445-2.071,10.479-7.573,12.021-13.166l2.709-9.813c0.131-0.308,0.746-0.776,1.032-0.819l33.584-0.002
c0.333,0.045,0.948,0.514,1.066,0.781l2.719,9.85c1.545,5.604,6.591,11.105,12.048,13.164l7.61,3.193
c2.975,1.39,6.073,1.686,8.17,1.686c3.568,0,7.012-0.84,9.694-2.363l7.995-4.538c0.036-0.015,0.176-0.051,0.396-0.051
c0.48,0,0.853,0.161,0.914,0.202l23.744,23.744c0.203,0.267,0.306,1.032,0.201,1.304l-4.819,8.493
c-2.868,5.056-3.189,12.511-0.79,17.823l2.489,6.102c2.034,5.487,7.535,10.562,13.154,12.11l9.523,2.623
c0.309,0.132,0.777,0.748,0.82,1.036l0.002,33.581C237.98,242.726,237.511,243.342,237.247,243.459z"/>
<path d="M393.377,112.81l-6.573-1.953c-2.321-0.688-4.846-3.132-5.611-5.428l-1.713-4.439c-0.983-2.211-0.778-5.725,0.459-7.805
l3.443-5.806c1.236-2.08,0.875-5.212-0.8-6.958L366.48,63.675c-1.679-1.746-4.794-2.232-6.922-1.076l-5.609,3.038
c-2.13,1.154-5.636,1.198-7.793,0.097l-5.418-2.399c-2.262-0.866-4.599-3.496-5.199-5.843l-1.745-6.844
c-0.598-2.345-3.066-4.304-5.487-4.352l-23.232-0.457c-2.42-0.048-4.965,1.814-5.654,4.133l-2.013,6.77
c-0.691,2.321-3.129,4.861-5.42,5.645l-5.954,2.389c-2.19,1.027-5.692,0.856-7.772-0.38l-5.166-3.07
c-2.083-1.237-5.215-0.876-6.96,0.805l-16.751,16.1c-1.742,1.676-2.226,4.793-1.073,6.921l3.159,5.831
c1.153,2.13,1.23,5.645,0.169,7.813c-1.061,2.167-5.21,8.66-7.557,9.256l-6.643,1.693c-2.345,0.599-4.305,3.07-4.353,5.49
l-0.456,23.228c-0.047,2.422,1.814,4.965,4.134,5.655l6.573,1.954c2.322,0.688,4.849,3.132,5.616,5.43l1.852,4.759
c0.992,2.211,0.795,5.721-0.444,7.802l-3.113,5.241c-1.238,2.084-0.875,5.215,0.803,6.961l16.104,16.746
c1.678,1.747,4.793,2.232,6.924,1.078l5.14-2.785c2.128-1.155,5.638-1.197,7.796-0.101l5.501,2.428
c2.261,0.864,4.605,3.488,5.2,5.837l1.642,6.442c0.598,2.348,3.067,4.307,5.488,4.354l23.231,0.455
c2.422,0.049,4.964-1.811,5.654-4.133l1.894-6.373c0.687-2.323,3.131-4.851,5.43-5.617l5.146-2.013
c2.207-0.997,5.719-0.802,7.798,0.436l5.342,3.172c2.082,1.238,5.215,0.876,6.958-0.804l16.751-16.1
c1.744-1.68,2.229-4.794,1.074-6.921l-2.962-5.467c-1.152-2.129-1.21-5.644-0.123-7.808l2.192-5.01
c0.86-2.266,3.482-4.609,5.829-5.206l6.645-1.693c2.343-0.599,4.305-3.066,4.352-5.488l0.457-23.229
C397.557,116.047,395.695,113.5,393.377,112.81z M314.236,170.826c-23.495-0.462-42.171-19.886-41.709-43.381
c0.462-23.5,19.886-42.176,43.381-41.715c23.497,0.463,42.172,19.889,41.71,43.387
C357.156,152.614,337.733,171.288,314.236,170.826z"/>
</g>
</svg>
);
}

View file

@ -0,0 +1,3 @@
export { default as GithubIcon } from './GitHubIcon';
export { default as DiscordIcon } from './DiscordIcon';
export { default as SettingsIcon } from './SettingsIcon';

View file

@ -0,0 +1,16 @@
.footer {
padding: 24px 0;
}
.leftSide {
display: flex;
flex-direction: column;
gap: 12px;
}
.rightSide {
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
}

View file

@ -0,0 +1,25 @@
import Link from 'next/link';
import { Stack } from 'ohmy-ui';
import { DiscordIcon, GithubIcon } from '@/ui/Icons';
import { TextLogo } from '@/modules/app';
import styles from './Footer.module.css';
export default function Footer() {
return (
<footer className={styles.footer}>
<Stack orientation="horizontal" gap="between">
<div className={styles.leftSide}>
<TextLogo width={92} height={24} />
</div>
<div className={styles.rightSide}>
<Link target="_blank" href="https://github.com/topoteretes/cognee">
<GithubIcon />
</Link>
<Link target="_blank" href="https://discord.gg/m63hxKsp4p">
<DiscordIcon />
</Link>
</div>
</Stack>
</footer>
);
}

View file

@ -0,0 +1,35 @@
.searchViewContainer {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 30%;
background: var(--global-background-default);
border-radius: var(--border-radius);
border: 1px solid white;
z-index: 10;
}
.searchContainer {
flex: 1;
padding: 16px;
border-top: 2px solid white;
}
.messages {
flex: 1;
padding-top: 24px;
padding-bottom: 24px;
}
.message {
padding: 16px;
border-radius: var(--border-radius);
width: max-content;
}
.userMessage {
align-self: flex-end;
background-color: #5858ff;
}

View file

@ -0,0 +1,95 @@
import { CTAButton, CloseIcon, GhostButton, Input, Spacer, Stack, Text } from 'ohmy-ui';
import styles from './SearchView.module.css';
import { useCallback, useState } from 'react';
import { v4 } from 'uuid';
import classNames from 'classnames';
interface SearchViewProps {
onClose: () => void;
}
interface Message {
id: string;
user: 'user' | 'system';
text: string;
}
export default function SearchView({ onClose }: SearchViewProps) {
const [messages, setMessages] = useState<Message[]>([]);
const [inputValue, setInputValue] = useState<string>("");
const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
}, []);
const handleSearchSubmit = useCallback((event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setMessages((currentMessages) => [
...currentMessages,
{
id: v4(),
user: 'user',
text: inputValue,
},
]);
fetch('http://localhost:8000/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query_params: {
query: inputValue,
},
}),
})
.then((response) => response.json())
.then((systemMessage) => {
setMessages((currentMessages) => [
...currentMessages,
{
id: v4(),
user: 'system',
text: systemMessage,
},
]);
setInputValue('');
})
}, [inputValue]);
return (
<Stack className={styles.searchViewContainer}>
<Stack gap="between" align="center/" orientation="horizontal">
<Spacer horizontal="2">
<Text>Search</Text>
</Spacer>
<GhostButton onClick={onClose}>
<CloseIcon />
</GhostButton>
</Stack>
<Stack className={styles.searchContainer}>
<Stack gap="2" className={styles.messages} align="end">
{messages.map((message) => (
<Text
key={message.id}
className={classNames(styles.message, {
[styles.userMessage]: message.user === "user",
})}
>
{message.text}
</Text>
))}
</Stack>
<form onSubmit={handleSearchSubmit}>
<Stack orientation="horizontal" gap="2">
<Input value={inputValue} onChange={handleInputChange} name="searchInput" placeholder="Search" />
<CTAButton type="submit">Search</CTAButton>
</Stack>
</form>
</Stack>
</Stack>
);
}

View file

@ -0,0 +1,122 @@
import { CTAButton, DropdownSelect, FormGroup, FormInput, FormLabel, H2, H3, Input, Modal, Spacer, Stack, useBoolean } from 'ohmy-ui';
import { useCallback, useEffect, useState } from 'react';
interface SelectOption {
label: string;
value: string;
}
export default function SettingsModal({ isOpen = false, onClose = () => {} }) {
const [llmConfig, setLLMConfig] = useState<{ openAIApiKey: string }>();
const [vectorDBConfig, setVectorDBConfig] = useState<{
choice: SelectOption;
options: SelectOption[];
url: string;
apiKey: string;
}>();
const {
value: isSaving,
setTrue: startSaving,
setFalse: stopSaving,
} = useBoolean(false);
const saveConfig = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const newOpenAIApiKey = event.target.openAIApiKey.value;
const newVectorDBChoice = vectorDBConfig?.choice.value;
const newVectorDBUrl = event.target.vectorDBUrl.value;
const newVectorDBApiKey = event.target.vectorDBApiKey.value;
startSaving();
fetch('http://0.0.0.0:8000/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
llm: {
openAIApiKey: newOpenAIApiKey,
},
vectorDB: {
choice: newVectorDBChoice,
url: newVectorDBUrl,
apiKey: newVectorDBApiKey,
},
}),
})
.then(() => {
onClose();
})
.finally(() => stopSaving());
};
const handleVectorDBChange = useCallback((newChoice: SelectOption) => {
setVectorDBConfig((config) => {
if (config?.choice !== newChoice) {
return {
...config,
choice: newChoice,
url: '',
apiKey: '',
};
}
return config;
});
}, []);
useEffect(() => {
const fetchVectorDBChoices = async () => {
const response = await fetch('http://0.0.0.0:8000/settings');
const settings = await response.json();
setLLMConfig(settings.llm);
setVectorDBConfig(settings.vectorDB);
};
isOpen && fetchVectorDBChoices();
}, [isOpen]);
return (
<Modal isOpen={isOpen} onClose={onClose}>
<Stack gap="4" orientation="vertical" align="center/">
<H2>Settings</H2>
<form onSubmit={saveConfig} style={{ width: '100%' }}>
<Stack gap="2" orientation="vertical">
<H3>LLM Config</H3>
<FormGroup orientation="vertical" align="center/" gap="1">
<FormLabel>OpenAI API Key</FormLabel>
<FormInput>
<Input defaultValue={llmConfig?.openAIApiKey} name="openAIApiKey" placeholder="OpenAI API Key" />
</FormInput>
</FormGroup>
<H3>Vector Database Config</H3>
<DropdownSelect
value={vectorDBConfig?.choice}
options={vectorDBConfig?.options}
onChange={handleVectorDBChange}
/>
<FormGroup orientation="vertical" align="center/" gap="1">
<FormLabel>Vector DB url</FormLabel>
<FormInput>
<Input defaultValue={vectorDBConfig?.url} name="vectorDBUrl" placeholder="Vector DB API url" />
</FormInput>
</FormGroup>
<FormGroup orientation="vertical" align="center/" gap="1">
<FormLabel>Vector DB API key</FormLabel>
<FormInput>
<Input defaultValue={vectorDBConfig?.apiKey} name="vectorDBApiKey" placeholder="Vector DB API key" />
</FormInput>
</FormGroup>
<Stack align="/end">
<Spacer top="2">
<CTAButton type="submit">Save</CTAButton>
</Spacer>
</Stack>
</Stack>
</form>
</Stack>
</Modal>
)
}

View file

@ -0,0 +1,3 @@
export { default as Footer } from './Footer/Footer';
export { default as SettingsModal } from './SettingsModal/SettingsModal';
export { default as SearchView } from './SearchView/SearchView';

View file

@ -16,7 +16,7 @@ from cognee.config import Config
config = Config()
config.load()
from typing import Dict, Any, List, Union, Annotated
from typing import Dict, Any, List, Union, Annotated, Literal
from fastapi import FastAPI, HTTPException, Form, File, UploadFile, Query
from fastapi.responses import JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
@ -229,6 +229,38 @@ async def search(payload: SearchPayload):
)
@app.get("/settings", response_model=dict)
async def get_settings():
from cognee.modules.settings import get_settings
return get_settings()
class LLMConfig(BaseModel):
openAIApiKey: str
class VectorDBConfig(BaseModel):
choice: Union[Literal["lancedb"], Literal["qdrant"], Literal["weaviate"]]
url: str
apiKey: str
class SettingsPayload(BaseModel):
llm: LLMConfig | None = None
vectorDB: VectorDBConfig | None = None
@app.post("/settings", response_model=dict)
async def save_config(new_settings: SettingsPayload):
from cognee.modules.settings import save_llm_config, save_vector_db_config
if hasattr(new_settings, "llm"):
await save_llm_config(new_settings.llm)
if hasattr(new_settings, "vectorDB"):
await save_vector_db_config(new_settings.vectorDB)
return JSONResponse(
status_code = 200,
content = "OK",
)
def start_api_server(host: str = "0.0.0.0", port: int = 8000):
"""

View file

@ -18,6 +18,7 @@ class InfrastructureConfig():
llm_provider: str = config.llm_provider
database_engine: DatabaseEngine = None
vector_engine: VectorDBInterface = None
vector_engine_choice: str = None
graph_engine: GraphDBType = None
llm_engine: LLMInterface = None
classification_model = None
@ -89,6 +90,7 @@ class InfrastructureConfig():
if (config_entity is None or config_entity == "vector_engine") and self.vector_engine is None:
try:
from .databases.vector.weaviate_db import WeaviateAdapter
config.load()
if config.weaviate_url is None and config.weaviate_api_key is None:
raise EnvironmentError("Weaviate is not configured!")
@ -98,7 +100,10 @@ class InfrastructureConfig():
config.weaviate_api_key,
embedding_engine = self.embedding_engine
)
self.vector_engine_choice = "weaviate"
except (EnvironmentError, ModuleNotFoundError):
config.load()
if config.qdrant_url and config.qdrant_api_key:
from .databases.vector.qdrant.QDrantAdapter import QDrantAdapter
@ -107,8 +112,10 @@ class InfrastructureConfig():
qdrant_api_key = config.qdrant_api_key,
embedding_engine = self.embedding_engine
)
self.vector_engine_choice = "qdrant"
else:
from .databases.vector.lancedb.LanceDBAdapter import LanceDBAdapter
config.load()
lance_db_path = self.database_directory_path + "/cognee.lancedb"
LocalStorage.ensure_directory_exists(lance_db_path)
@ -117,6 +124,8 @@ class InfrastructureConfig():
api_key = None,
embedding_engine = self.embedding_engine,
)
self.lance_db_path = lance_db_path
self.vector_engine_choice = "lancedb"
if config_entity is not None:
return getattr(self, config_entity)
@ -124,6 +133,7 @@ class InfrastructureConfig():
return {
"llm_engine": self.llm_engine,
"vector_engine": self.vector_engine,
"vector_engine_choice": self.vector_engine_choice,
"database_engine": self.database_engine,
"system_root_directory": self.system_root_directory,
"data_root_directory": self.data_root_directory,

View file

@ -0,0 +1,3 @@
from .get_settings import get_settings
from .save_llm_config import save_llm_config
from .save_vector_db_config import save_vector_db_config

View file

@ -0,0 +1,41 @@
from cognee.config import Config
from cognee.infrastructure import infrastructure_config
config = Config()
config.load()
def get_settings():
vector_engine_choice = infrastructure_config.get_config()["vector_engine_choice"]
vector_db_options = [{
"value": "weaviate",
"label": "Weaviate",
}, {
"value": "qdrant",
"label": "Qdrant",
}, {
"value": "lancedb",
"label": "LanceDB",
}]
vector_db_config = dict(
url = config.weaviate_url,
apiKey = config.weaviate_api_key,
choice = vector_db_options[0],
options = vector_db_options,
) if vector_engine_choice == "weaviate" else dict(
url = config.qdrant_url,
apiKey = config.qdrant_api_key,
choice = vector_db_options[1],
options = vector_db_options,
) if vector_engine_choice == "qdrant" else dict(
url = infrastructure_config.get_config("lance_db_path"),
choice = vector_db_options[2],
options = vector_db_options,
)
return dict(
llm = dict(
openAIApiKey = config.openai_key[:-10] + "**********",
),
vectorDB = vector_db_config,
)

View file

@ -0,0 +1,15 @@
import os
from pydantic import BaseModel
from cognee.config import Config
config = Config()
class LLMConfig(BaseModel):
openAIApiKey: str
async def save_llm_config(llm_config: LLMConfig):
if "*" in llm_config.openAIApiKey:
return
os.environ["OPENAI_API_KEY"] = llm_config.openAIApiKey
config.load()

View file

@ -0,0 +1,44 @@
import os
from typing import Union, Literal
from pydantic import BaseModel
from cognee.config import Config
from cognee.infrastructure import infrastructure_config
config = Config()
class VectorDBConfig(BaseModel):
choice: Union[Literal["lancedb"], Literal["qdrant"], Literal["weaviate"]]
url: str
apiKey: str
async def save_vector_db_config(vector_db_config: VectorDBConfig):
if vector_db_config.choice == "weaviate":
os.environ["WEAVIATE_URL"] = vector_db_config.url
os.environ["WEAVIATE_API_KEY"] = vector_db_config.apiKey
remove_qdrant_config()
if vector_db_config.choice == "qdrant":
os.environ["QDRANT_URL"] = vector_db_config.url
os.environ["QDRANT_API_KEY"] = vector_db_config.apiKey
remove_weaviate_config()
if vector_db_config.choice == "lancedb":
remove_qdrant_config()
remove_weaviate_config()
config.load()
infrastructure_config.vector_engine = None
def remove_weaviate_config():
if "WEAVIATE_URL" in os.environ:
del os.environ["WEAVIATE_URL"]
if "WEAVIATE_API_KEY" in os.environ:
del os.environ["WEAVIATE_API_KEY"]
def remove_qdrant_config():
if "QDRANT_URL" in os.environ:
del os.environ["QDRANT_URL"]
if "QDRANT_API_KEY" in os.environ:
del os.environ["QDRANT_API_KEY"]