fix: UI fixes (#1435)

<!-- .github/pull_request_template.md -->

## Description
<!-- 
Please provide a clear, human-generated description of the changes in
this PR.
DO NOT use AI-generated descriptions. We want to understand your thought
process and reasoning.
-->

## Type of Change
<!-- Please check the relevant option -->
- [x] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Code refactoring
- [ ] Performance improvement
- [ ] Other (please specify):

## Changes Made
<!-- List the specific changes made in this PR -->
- 
- 
- 

## Testing
<!-- Describe how you tested your changes -->

## Screenshots/Videos (if applicable)
<!-- Add screenshots or videos to help explain your changes -->

## Pre-submission Checklist
<!-- Please check all boxes that apply before submitting your PR -->
- [ ] **I have tested my changes thoroughly before submitting this PR**
- [ ] **This PR contains minimal changes necessary to address the
issue/feature**
- [ ] My code follows the project's coding standards and style
guidelines
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have added necessary documentation (if applicable)
- [ ] All new and existing tests pass
- [ ] I have searched existing PRs to ensure this change hasn't been
submitted already
- [ ] I have linked any relevant issues in the description
- [ ] My commits have clear and descriptive messages

## Related Issues
<!-- Link any related issues using "Fixes #issue_number" or "Relates to
#issue_number" -->

## Additional Notes
<!-- Add any additional notes, concerns, or context for reviewers -->

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.
This commit is contained in:
Boris 2025-09-18 13:53:27 +02:00 committed by GitHub
parent 6f60ac76fd
commit f567098743
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 311 additions and 216 deletions

View file

@ -13,8 +13,8 @@ export default function Account() {
}; };
return ( return (
<div className="bg-gray-200 h-full max-w-[1920px] mx-auto"> <div className="h-full max-w-[1920px] mx-auto">
<video {/* <video
autoPlay autoPlay
loop loop
muted muted
@ -23,9 +23,9 @@ export default function Account() {
> >
<source src="/videos/background-video-blur.mp4" type="video/mp4" /> <source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video> */}
<Header /> <Header user={user} />
<div className="relative flex flex-row items-start gap-2.5"> <div className="relative flex flex-row items-start gap-2.5">
<Link href="/dashboard" className="flex-1/5 py-4 px-5 flex flex-row items-center gap-5"> <Link href="/dashboard" className="flex-1/5 py-4 px-5 flex flex-row items-center gap-5">
@ -42,7 +42,7 @@ export default function Account() {
<div>Plan</div> <div>Plan</div>
<div className="text-sm text-gray-400 mb-8">You are using open-source version. Subscribe to get access to hosted cognee with your data!</div> <div className="text-sm text-gray-400 mb-8">You are using open-source version. Subscribe to get access to hosted cognee with your data!</div>
<Link href="/plan"> <Link href="/plan">
<CTAButton><span className="">Select a plan</span></CTAButton> <CTAButton className="w-full"><span className="">Select a plan</span></CTAButton>
</Link> </Link>
</div> </div>
</div> </div>

View file

@ -2,7 +2,7 @@ import { FormEvent, useCallback, useState } from "react";
import { LoadingIndicator } from "@/ui/App"; import { LoadingIndicator } from "@/ui/App";
import { useModal } from "@/ui/elements/Modal"; import { useModal } from "@/ui/elements/Modal";
import { CloseIcon, PlusIcon } from "@/ui/Icons"; import { CloseIcon, MinusIcon, PlusIcon } from "@/ui/Icons";
import { CTAButton, GhostButton, IconButton, Modal, NeutralButton, Select } from "@/ui/elements"; import { CTAButton, GhostButton, IconButton, Modal, NeutralButton, Select } from "@/ui/elements";
import addData from "@/modules/ingestion/addData"; import addData from "@/modules/ingestion/addData";
@ -16,16 +16,22 @@ interface AddDataToCogneeProps {
} }
export default function AddDataToCognee({ datasets, refreshDatasets, useCloud = false }: AddDataToCogneeProps) { export default function AddDataToCognee({ datasets, refreshDatasets, useCloud = false }: AddDataToCogneeProps) {
const [filesForUpload, setFilesForUpload] = useState<FileList | null>(null); const [filesForUpload, setFilesForUpload] = useState<File[]>([]);
const prepareFiles = useCallback((event: FormEvent<HTMLInputElement>) => { const addFiles = useCallback((event: FormEvent<HTMLInputElement>) => {
const formElements = event.currentTarget; const formElements = event.currentTarget;
const files = formElements.files; const newFiles = formElements.files;
setFilesForUpload(files); if (newFiles?.length) {
setFilesForUpload((oldFiles) => [...oldFiles, ...Array.from(newFiles)]);
}
}, []); }, []);
const processDataWithCognee = useCallback((state: object, event?: FormEvent<HTMLFormElement>) => { const removeFile = useCallback((file: File) => {
setFilesForUpload((oldFiles) => oldFiles.filter((f) => f !== file));
}, []);
const processDataWithCognee = useCallback((state?: object, event?: FormEvent<HTMLFormElement>) => {
event!.preventDefault(); event!.preventDefault();
if (!filesForUpload) { if (!filesForUpload) {
@ -41,7 +47,7 @@ export default function AddDataToCognee({ datasets, refreshDatasets, useCloud =
} : { } : {
name: "main_dataset", name: "main_dataset",
}, },
Array.from(filesForUpload), filesForUpload,
useCloud useCloud
) )
.then(({ dataset_id, dataset_name }) => { .then(({ dataset_id, dataset_name }) => {
@ -57,7 +63,7 @@ export default function AddDataToCognee({ datasets, refreshDatasets, useCloud =
useCloud, useCloud,
) )
.then(() => { .then(() => {
setFilesForUpload(null); setFilesForUpload([]);
}); });
}); });
}, [filesForUpload, refreshDatasets, useCloud]); }, [filesForUpload, refreshDatasets, useCloud]);
@ -86,24 +92,25 @@ export default function AddDataToCognee({ datasets, refreshDatasets, useCloud =
<div className="mt-8 mb-6">Please select a {useCloud ? "cloud" : "local"} dataset to add data in.<br/> If you don&apos;t have any, don&apos;t worry, we will create one for you.</div> <div className="mt-8 mb-6">Please select a {useCloud ? "cloud" : "local"} dataset to add data in.<br/> If you don&apos;t have any, don&apos;t worry, we will create one for you.</div>
<form onSubmit={submitDataToCognee}> <form onSubmit={submitDataToCognee}>
<div className="max-w-md flex flex-col gap-4"> <div className="max-w-md flex flex-col gap-4">
<Select name="datasetName"> <Select defaultValue={datasets.length ? datasets[0].id : ""} name="datasetName">
{!datasets.length && <option value="">main_dataset</option>} {!datasets.length && <option value="">main_dataset</option>}
{datasets.map((dataset: Dataset, index) => ( {datasets.map((dataset: Dataset) => (
<option selected={index===0} key={dataset.id} value={dataset.id}>{dataset.name}</option> <option key={dataset.id} value={dataset.id}>{dataset.name}</option>
))} ))}
</Select> </Select>
<NeutralButton className="w-full relative justify-start pl-4"> <NeutralButton className="w-full relative justify-start pl-4">
<input onChange={prepareFiles} required name="files" tabIndex={-1} type="file" multiple className="absolute w-full h-full cursor-pointer opacity-0" /> <input onChange={addFiles} required name="files" tabIndex={-1} type="file" multiple className="absolute w-full h-full cursor-pointer opacity-0" />
<span>select files</span> <span>select files</span>
</NeutralButton> </NeutralButton>
{filesForUpload?.length && ( {!!filesForUpload.length && (
<div className="pt-4 mt-4 border-t-1 border-t-gray-100"> <div className="pt-4 mt-4 border-t-1 border-t-gray-100">
<div className="mb-1.5">selected files:</div> <div className="mb-1.5">selected files:</div>
{Array.from(filesForUpload || []).map((file) => ( {filesForUpload.map((file) => (
<div key={file.name} className="py-1.5 pl-2"> <div key={file.name} className="py-1.5 pl-2 flex flex-row items-center justify-between w-full">
<span className="text-sm">{file.name}</span> <span className="text-sm">{file.name}</span>
<IconButton onClick={removeFile.bind(null, file)}><MinusIcon /></IconButton>
</div> </div>
))} ))}
</div> </div>

View file

@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { Header } from "@/ui/Layout"; import { Header } from "@/ui/Layout";
import { SearchIcon } from "@/ui/Icons"; import { SearchIcon } from "@/ui/Icons";
import { Notebook } from "@/ui/elements"; import { CTAButton, Notebook } from "@/ui/elements";
import { fetch, isCloudEnvironment } from "@/utils"; import { fetch, isCloudEnvironment } from "@/utils";
import { Notebook as NotebookType } from "@/ui/elements/Notebook/types"; import { Notebook as NotebookType } from "@/ui/elements/Notebook/types";
import { useAuthenticatedUser } from "@/modules/auth"; import { useAuthenticatedUser } from "@/modules/auth";
@ -111,8 +111,8 @@ export default function Dashboard({ accessToken }: DashboardProps) {
const isCloudEnv = isCloudEnvironment(); const isCloudEnv = isCloudEnvironment();
return ( return (
<div className="h-full flex flex-col bg-gray-200"> <div className="h-full flex flex-col">
<video {/* <video
autoPlay autoPlay
loop loop
muted muted
@ -121,12 +121,12 @@ export default function Dashboard({ accessToken }: DashboardProps) {
> >
<source src="/videos/background-video-blur.mp4" type="video/mp4" /> <source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video> */}
<Header user={user} /> <Header user={user} />
<div className="relative flex-1 flex flex-row gap-2.5 items-start w-full max-w-[1920px] max-h-[calc(100% - 3.5rem)] overflow-hidden mx-auto px-2.5 py-2.5"> <div className="relative flex-1 flex flex-row gap-2.5 items-start w-full max-w-[1920px] max-h-[calc(100% - 3.5rem)] overflow-hidden mx-auto px-2.5 pb-2.5">
<div className="px-5 py-4 lg:w-96 bg-white rounded-xl min-h-full"> <div className="px-5 py-4 lg:w-96 bg-white rounded-xl h-[calc(100%-2.75rem)]">
<div className="relative mb-2"> <div className="relative mb-2">
<label htmlFor="search-input"><SearchIcon className="absolute left-3 top-[10px] cursor-text" /></label> <label htmlFor="search-input"><SearchIcon className="absolute left-3 top-[10px] cursor-text" /></label>
<input id="search-input" className="text-xs leading-3 w-full h-8 flex flex-row items-center gap-2.5 rounded-3xl pl-9 placeholder-gray-300 border-gray-300 border-[1px] focus:outline-indigo-600" placeholder="Search datasets..." /> <input id="search-input" className="text-xs leading-3 w-full h-8 flex flex-row items-center gap-2.5 rounded-3xl pl-9 placeholder-gray-300 border-gray-300 border-[1px] focus:outline-indigo-600" placeholder="Search datasets..." />
@ -152,6 +152,12 @@ export default function Dashboard({ accessToken }: DashboardProps) {
/> />
</CogneeInstancesAccordion> </CogneeInstancesAccordion>
</div> </div>
<div className="fixed bottom-2.5 w-[calc(min(1920px,100%)/5)] lg:w-96 ml-[-1.25rem] mx-auto">
<a href="/plan">
<CTAButton className="w-full">Select a plan</CTAButton>
</a>
</div>
</div> </div>
<div className="flex-1 flex flex-col justify-between h-full overflow-y-auto"> <div className="flex-1 flex flex-col justify-between h-full overflow-y-auto">

View file

@ -36,7 +36,8 @@ export default function InstanceDatasetsAccordion({ onDatasetsChange }: Instance
}; };
checkConnectionToLocalCognee(); checkConnectionToLocalCognee();
}, [setCloudCogneeConnected, setLocalCogneeConnected]); checkConnectionToCloudCognee();
}, [checkConnectionToCloudCognee, setCloudCogneeConnected, setLocalCogneeConnected]);
const { const {
value: isCloudConnectedModalOpen, value: isCloudConnectedModalOpen,

View file

@ -5,8 +5,8 @@ import { useBoolean } from "@/utils";
import { Accordion, CTAButton, GhostButton, IconButton, Input, Modal } from "@/ui/elements"; import { Accordion, CTAButton, GhostButton, IconButton, Input, Modal } from "@/ui/elements";
import { CloseIcon, MinusIcon, NotebookIcon, PlusIcon } from "@/ui/Icons"; import { CloseIcon, MinusIcon, NotebookIcon, PlusIcon } from "@/ui/Icons";
import { Notebook } from "@/ui/elements/Notebook/types"; import { Notebook } from "@/ui/elements/Notebook/types";
import { LoadingIndicator } from "@/ui/App";
import { useModal } from "@/ui/elements/Modal"; import { useModal } from "@/ui/elements/Modal";
import { LoadingIndicator } from "@/ui/App";
interface NotebooksAccordionProps { interface NotebooksAccordionProps {
notebooks: Notebook[]; notebooks: Notebook[];
@ -60,7 +60,7 @@ export default function NotebooksAccordion({
.finally(() => setNotebookToRemove(null)); .finally(() => setNotebookToRemove(null));
}; };
const handleNotebookAdd = useCallback((_: object, formEvent?: FormEvent<HTMLFormElement>) => { const handleNotebookAdd = useCallback((_: Notebook, formEvent?: FormEvent<HTMLFormElement>) => {
if (!formEvent) { if (!formEvent) {
return; return;
} }
@ -71,6 +71,7 @@ export default function NotebooksAccordion({
const notebookName = formElements.notebookName.value.trim(); const notebookName = formElements.notebookName.value.trim();
return addNotebook(notebookName) return addNotebook(notebookName)
.then(() => {});
}, [addNotebook]); }, [addNotebook]);
const { const {
@ -79,7 +80,7 @@ export default function NotebooksAccordion({
closeModal: closeNewNotebookModal, closeModal: closeNewNotebookModal,
confirmAction: handleNewNotebookSubmit, confirmAction: handleNewNotebookSubmit,
isActionLoading: isNewDatasetLoading, isActionLoading: isNewDatasetLoading,
} = useModal<Notebook | void>(false, handleNotebookAdd); } = useModal<Notebook>(false, handleNotebookAdd);
return ( return (
<> <>
@ -91,7 +92,7 @@ export default function NotebooksAccordion({
tools={isNewDatasetLoading ? ( tools={isNewDatasetLoading ? (
<LoadingIndicator /> <LoadingIndicator />
) : ( ) : (
<IconButton onClick={openNewNotebookModal}><PlusIcon /></IconButton> <IconButton onClick={() => openNewNotebookModal()}><PlusIcon /></IconButton>
)} )}
> >
{notebooks.length === 0 && ( {notebooks.length === 0 && (

View file

@ -11,7 +11,7 @@
--global-color-primary-active: #500cc5 !important; --global-color-primary-active: #500cc5 !important;
--global-color-primary-text: white !important; --global-color-primary-text: white !important;
--global-color-secondary: #0DFF00 !important; --global-color-secondary: #0DFF00 !important;
--global-background-default: #0D051C; --global-background-default: #F4F4F4;
--textarea-default-color: #0D051C !important; --textarea-default-color: #0D051C !important;
} }
@ -20,6 +20,7 @@ body {
height: 100%; height: 100%;
max-width: 100vw; max-width: 100vw;
overflow-x: hidden; overflow-x: hidden;
background-color: var(--global-background-default);
} }
a { a {

View file

@ -1,12 +1,17 @@
"use client";
import Link from "next/link"; import Link from "next/link";
import { BackIcon, CheckIcon } from "@/ui/Icons"; import { BackIcon, CheckIcon } from "@/ui/Icons";
import { CTAButton, NeutralButton } from "@/ui/elements"; import { CTAButton, NeutralButton } from "@/ui/elements";
import Header from "@/ui/Layout/Header"; import Header from "@/ui/Layout/Header";
import { useAuthenticatedUser } from "@/modules/auth";
export default function Plan() { export default function Plan() {
const { user } = useAuthenticatedUser();
return ( return (
<div className="bg-gray-200 h-full max-w-[1920px] mx-auto"> <div className="h-full max-w-[1920px] mx-auto">
<video {/* <video
autoPlay autoPlay
loop loop
muted muted
@ -15,88 +20,104 @@ export default function Plan() {
> >
<source src="/videos/background-video-blur.mp4" type="video/mp4" /> <source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video> */}
<Header /> <Header user={user} />
<div className="relative flex flex-row items-start justify-stretch gap-2.5"> <div className="relative flex flex-row items-start justify-stretch gap-2.5">
<div className="flex-1/5 h-full"> <div className="flex-1/5 h-full">
<Link href="/dashboard" className="py-4 px-5 flex flex-row items-center gap-5"> <div className="flex flex-col justify-between">
<BackIcon /> <Link href="/dashboard" className="py-4 px-5 flex flex-row items-center gap-5">
<span>back</span> <BackIcon />
</Link> <span>back</span>
</Link>
{/* <div className="fixed bottom-6 w-[calc(min(1920px,100%)/5)] mx-auto">
<div className="text-sm mb-2"></div>
<a href="/plan">
<CTAButton className="w-full">Select a plan</CTAButton>
</a>
</div> */}
</div>
</div> </div>
<div className="flex-3/5"> <div className="flex-3/5">
<div className="bg-[rgba(255,255,255,0.7)] rounded-xl px-5 py-4 mb-2"> <div className="bg-white rounded-xl px-5 py-5 mb-2">
Affordable and transparent pricing Affordable and transparent pricing
</div> </div>
<div className="grid grid-cols-3 gap-x-2.5"> <div className="grid grid-cols-3 gap-x-2.5">
<div className="pt-13 py-4 px-5 mb-2.5 rounded-tl-xl rounded-tr-xl bg-[rgba(255,255,255,0.7)] h-full"> <div className="pt-13 py-4 px-5 mb-2.5 rounded-tl-xl rounded-tr-xl bg-white h-full">
<div>Basic</div> <div>Basic</div>
<div className="text-3xl mb-4 font-bold">Free</div> <div className="text-[1.75rem] mb-4 font-bold">Free</div>
</div> </div>
<div className="pt-13 py-4 px-5 mb-2.5 rounded-tl-xl rounded-tr-xl bg-[rgba(255,255,255,0.7)] h-full"> <div className="pt-5 py-4 px-5 mb-2.5 rounded-tl-xl rounded-tr-xl bg-white h-full border-indigo-600 border-1 border-b-0">
<div className="text-indigo-600 mb-5 text-xs font-black">Most Popular</div>
<div>On-prem Subscription</div> <div>On-prem Subscription</div>
<div className="mb-4"><span className="text-3xl font-bold">$2470</span><span className="text-gray-400"> /per month</span></div> <div className="mb-2"><span className="text-[1.75rem] font-bold">$2470</span><span className="text-gray-400"> /per month</span></div>
<div className="mb-9"><span className="font-bold">Save 20% </span>yearly</div> <div className=""><span className="font-black">Save 20% </span>yearly</div>
</div> </div>
<div className="pt-13 py-4 px-5 mb-2.5 rounded-tl-xl rounded-tr-xl bg-[rgba(255,255,255,0.7)] h-full"> <div className="pt-13 py-4 px-5 mb-2.5 rounded-tl-xl rounded-tr-xl bg-white h-full">
<div>Cloud Subscription</div> <div>Cloud Subscription</div>
<div className="mb-4"><span className="text-3xl font-bold">$25</span><span className="text-gray-400"> /per month</span></div> <div className="mb-2"><span className="text-[1.75rem] font-bold">$25</span><span className="text-gray-400"> /per month</span></div>
<div className="mb-9 text-gray-400">(beta pricing)</div> <div className=" text-gray-400">(beta pricing)</div>
</div> </div>
<div className="bg-[rgba(255,255,255,0.7)] rounded-bl-xl rounded-br-xl h-full py-4 px-5"> <div className="bg-white rounded-bl-xl rounded-br-xl h-full py-4 px-5">
<div className="mb-1 invisible">Everything in the free plan, plus...</div> <div className="mb-2 invisible">Everything in the free plan, plus...</div>
<div className="flex flex-col gap-3 mb-28"> <div className="flex flex-col gap-3 mb-28">
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />License to use Cognee open source</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />License to use Cognee open source</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Cognee tasks and pipelines</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Cognee tasks and pipelines</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Custom schema and ontology generation</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Custom schema and ontology generation</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Integrated evaluations</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Integrated evaluations</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />More than 28 data sources supported</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />More than 28 data sources supported</div>
</div> </div>
</div> </div>
<div className="bg-[rgba(255,255,255,0.7)] rounded-bl-xl rounded-br-xl h-full py-4 px-5"> <div className="bg-white rounded-bl-xl rounded-br-xl border-indigo-600 border-1 border-t-0 h-full py-4 px-5">
<div className="mb-1 text-gray-400">Everything in the free plan, plus...</div> <div className="mb-2 text-gray-400">Everything in the free plan, plus...</div>
<div className="flex flex-col gap-3 mb-10"> <div className="flex flex-col gap-3 mb-4">
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />License to use Cognee open source and Cognee Platform</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />License to use Cognee open source and Cognee Platform</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />1 day SLA</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />1 day SLA</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />On-prem deployment</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />On-prem deployment</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Hands-on support</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Hands-on support</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Architecture review</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Architecture review</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Roadmap prioritization</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Roadmap prioritization</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Knowledge transfer</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Knowledge transfer</div>
</div> </div>
</div> </div>
<div className="bg-[rgba(255,255,255,0.7)] rounded-bl-xl rounded-br-xl h-full py-4 px-5"> <div className="bg-white rounded-bl-xl rounded-br-xl h-full py-4 px-5">
<div className="mb-1 text-gray-400">Everything in the free plan, plus...</div> <div className="mb-2 text-gray-400">Everything in the free plan, plus...</div>
<div className="flex flex-col gap-3 mb-10"> <div className="flex flex-col gap-3 mb-4">
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Fully hosted cloud platform</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Fully hosted cloud platform</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Multi-tenant architecture</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Multi-tenant architecture</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Comprehensive API endpoints</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Comprehensive API endpoints</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Automated scaling and parallel processing</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Automated scaling and parallel processing</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Ability to group memories per user and domain</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Ability to group memories per user and domain</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />Automatic updates and priority support</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />Automatic updates and priority support</div>
<div className="flex flex-row gap-2"><CheckIcon className="mt-1 shrink-0" />1 GB ingestion + 10,000 API calls</div> <div className="flex flex-row gap-2 leading-5"><CheckIcon className="mt-1 shrink-0" />1 GB ingestion + 10,000 API calls</div>
</div> </div>
</div> </div>
<div className="pt-4 pb-14 mb-2.5"> <div className="pt-4 pb-14 mb-2.5">
<NeutralButton className="w-full">Try for free</NeutralButton> <a href="https://www.github.com/topoteretes/cognee" target="_blank">
<NeutralButton className="w-full">Try for free</NeutralButton>
</a>
</div> </div>
<div className="pt-4 pb-14 mb-2.5"> <div className="pt-4 pb-14 mb-2.5">
<CTAButton className="w-full">Talk to us</CTAButton> <a href="https://www.cognee.ai/contact-us" target="_blank">
<CTAButton className="w-full">Talk to us</CTAButton>
</a>
</div> </div>
<div className="pt-4 pb-14 mb-2.5"> <div className="pt-4 pb-14 mb-2.5">
<NeutralButton className="w-full">Sign up for Cogwit Beta</NeutralButton> <a href="https://platform.cognee.ai" target="_blank">
<NeutralButton className="w-full">Sign up for Cogwit Beta</NeutralButton>
</a>
</div> </div>
</div> </div>
@ -106,7 +127,7 @@ export default function Plan() {
<div className="text-center">On-prem</div> <div className="text-center">On-prem</div>
<div className="text-center">Cloud</div> <div className="text-center">Cloud</div>
</div> </div>
<div className="grid grid-cols-4 py-1 px-5 mb-12 bg-[rgba(255,255,255,0.7)] rounded-xl"> <div className="grid grid-cols-4 py-1 px-5 mb-12 bg-white rounded-xl leading-[1]">
<div className="border-b-[1px] border-b-gray-100 py-3">Data Sources</div> <div className="border-b-[1px] border-b-gray-100 py-3">Data Sources</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">28+</div> <div className="text-center border-b-[1px] border-b-gray-100 py-3">28+</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">28+</div> <div className="text-center border-b-[1px] border-b-gray-100 py-3">28+</div>
@ -134,19 +155,19 @@ export default function Plan() {
</div> </div>
<div className="grid grid-cols-2 gap-x-2.5 gap-y-2.5 mb-12"> <div className="grid grid-cols-2 gap-x-2.5 gap-y-2.5 mb-12">
<div className="bg-[rgba(255,255,255,0.5)] py-4 px-5 rounded-xl"> <div className="bg-white py-4 px-5 rounded-xl">
<div>Can I change my plan anytime?</div> <div>Can I change my plan anytime?</div>
<div className="text-gray-500 mt-6">Yes, you can upgrade or downgrade your plan at any time. Changes take effect immediately.</div> <div className="text-gray-500 mt-6">Yes, you can upgrade or downgrade your plan at any time. Changes take effect immediately.</div>
</div> </div>
<div className="bg-[rgba(255,255,255,0.5)] py-4 px-5 rounded-xl"> <div className="bg-white py-4 px-5 rounded-xl">
<div>What happens to my data if I downgrade?</div> <div>What happens to my data if I downgrade?</div>
<div className="text-gray-500 mt-6">Your data is preserved, but features may be limited based on your new plan constraints.</div> <div className="text-gray-500 mt-6">Your data is preserved, but features may be limited based on your new plan constraints.</div>
</div> </div>
<div className="bg-[rgba(255,255,255,0.5)] py-4 px-5 rounded-xl"> <div className="bg-white py-4 px-5 rounded-xl">
<div>Do you offer educational discounts?</div> <div>Do you offer educational discounts?</div>
<div className="text-gray-500 mt-6">Yes, we offer special pricing for educational institutions and students. Contact us for details.</div> <div className="text-gray-500 mt-6">Yes, we offer special pricing for educational institutions and students. Contact us for details.</div>
</div> </div>
<div className="bg-[rgba(255,255,255,0.5)] py-4 px-5 rounded-xl"> <div className="bg-white py-4 px-5 rounded-xl">
<div>Is there a free trial for paid plans?</div> <div>Is there a free trial for paid plans?</div>
<div className="text-gray-500 mt-6">All new accounts start with a 14-day free trial of our Pro plan features.</div> <div className="text-gray-500 mt-6">All new accounts start with a 14-day free trial of our Pro plan features.</div>
</div> </div>

View file

@ -32,20 +32,21 @@ export default function Header({ user }: HeaderProps) {
return ( return (
<> <>
<header className="relative bg-[rgba(244,244,244,0.3)] flex flex-row h-14 min-h-14 px-5 items-center justify-between w-full max-w-[1920px] mx-auto"> <header className="relative flex flex-row h-14 min-h-14 px-5 items-center justify-between w-full max-w-[1920px] mx-auto">
<div className="flex flex-row gap-4 items-center"> <div className="flex flex-row gap-4 items-center">
<CogneeIcon /> <CogneeIcon />
<div className="text-lg">Cognee Local</div> <div className="text-lg">Cognee Local</div>
</div> </div>
<div className="flex flex-row items-center gap-2.5"> <div className="flex flex-row items-center gap-2.5">
<GhostButton onClick={openSyncModal} className="text-indigo-700 gap-3 pl-4 pr-4"> <GhostButton onClick={openSyncModal} className="text-indigo-600 gap-3 pl-4 pr-4">
<CloudIcon /> <CloudIcon />
<div>Sync</div> <div>Sync</div>
</GhostButton> </GhostButton>
<a href="/plan"> <a href="/plan" className="!text-indigo-600 pl-4 pr-4">
<GhostButton className="text-indigo-700 pl-4 pr-4">Premium</GhostButton> Premium
</a> </a>
<a href="https://platform.cognee.ai" className="!text-indigo-600 pl-4 pr-4">API keys</a>
{/* <div className="px-2 py-2 mr-3"> {/* <div className="px-2 py-2 mr-3">
<SettingsIcon /> <SettingsIcon />
</div> */} </div> */}

View file

@ -1,7 +1,7 @@
import classNames from "classnames"; import classNames from "classnames";
import { ButtonHTMLAttributes } from "react"; import { ButtonHTMLAttributes } from "react";
export default function CTAButton({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) { export default function GhostButton({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
return ( return (
<button className={classNames("flex flex-row justify-center items-center gap-2 cursor-pointer rounded-3xl bg-transparent px-10 h-8 text-black hover:bg-gray-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", className)} {...props}>{children}</button> <button className={classNames("flex flex-row justify-center items-center gap-2 cursor-pointer rounded-3xl bg-transparent px-10 h-8 text-black hover:bg-gray-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", className)} {...props}>{children}</button>
); );

View file

@ -1,8 +1,8 @@
import { FormEvent, useCallback, useState } from "react"; import { FormEvent, useCallback, useState } from "react";
import { useBoolean } from "@/utils"; import { useBoolean } from "@/utils";
export default function useModal<ConfirmActionReturnType = void>(initiallyOpen?: boolean, confirmCallback?: (state: object, event?: FormEvent<HTMLFormElement>) => Promise<ConfirmActionReturnType> | ConfirmActionReturnType) { export default function useModal<ModalState extends object, ConfirmActionEvent = FormEvent<HTMLFormElement>>(initiallyOpen?: boolean, confirmCallback?: (state: ModalState, event?: ConfirmActionEvent) => Promise<void> | void) {
const [modalState, setModalState] = useState<object>({}); const [modalState, setModalState] = useState<ModalState>();
const [isActionLoading, setLoading] = useState(false); const [isActionLoading, setLoading] = useState(false);
const { const {
@ -11,7 +11,7 @@ export default function useModal<ConfirmActionReturnType = void>(initiallyOpen?:
setFalse: closeModalInternal, setFalse: closeModalInternal,
} = useBoolean(initiallyOpen || false); } = useBoolean(initiallyOpen || false);
const openModal = useCallback((state?: object) => { const openModal = useCallback((state?: ModalState) => {
if (state) { if (state) {
setModalState(state); setModalState(state);
} }
@ -20,20 +20,21 @@ export default function useModal<ConfirmActionReturnType = void>(initiallyOpen?:
const closeModal = useCallback(() => { const closeModal = useCallback(() => {
closeModalInternal(); closeModalInternal();
setModalState({}); setModalState({} as ModalState);
}, [closeModalInternal]); }, [closeModalInternal]);
const confirmAction = useCallback((event?: FormEvent<HTMLFormElement>) => { const confirmAction = useCallback((event?: ConfirmActionEvent) => {
if (confirmCallback) { if (confirmCallback) {
setLoading(true); setLoading(true);
const maybePromise = confirmCallback(modalState, event); const maybePromise = confirmCallback(modalState as ModalState, event);
if (maybePromise instanceof Promise) { if (maybePromise instanceof Promise) {
return maybePromise return maybePromise
.finally(closeModal) .finally(closeModal)
.finally(() => setLoading(false)); .finally(() => setLoading(false));
} else { } else {
closeModal();
return maybePromise; // Not a promise. return maybePromise; // Not a promise.
} }
} }

View file

@ -2,10 +2,11 @@
import { v4 as uuid4 } from "uuid"; import { v4 as uuid4 } from "uuid";
import classNames from "classnames"; import classNames from "classnames";
import { Fragment, MutableRefObject, useCallback, useEffect, useRef, useState } from "react"; import { Fragment, MouseEvent, MutableRefObject, useCallback, useEffect, useRef, useState } from "react";
import { CaretIcon, PlusIcon } from "@/ui/Icons"; import { useModal } from "@/ui/elements/Modal";
import { IconButton, PopupMenu, TextArea } from "@/ui/elements"; import { CaretIcon, CloseIcon, PlusIcon } from "@/ui/Icons";
import { IconButton, PopupMenu, TextArea, Modal, GhostButton, CTAButton } from "@/ui/elements";
import { GraphControlsAPI } from "@/app/(graph)/GraphControls"; import { GraphControlsAPI } from "@/app/(graph)/GraphControls";
import GraphVisualization, { GraphVisualizationAPI } from "@/app/(graph)/GraphVisualization"; import GraphVisualization, { GraphVisualizationAPI } from "@/app/(graph)/GraphVisualization";
@ -60,13 +61,26 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
updateNotebook(newNotebook); updateNotebook(newNotebook);
}, [notebook, updateNotebook]); }, [notebook, updateNotebook]);
const handleCellRemove = useCallback((cell: Cell) => { const removeCell = useCallback((cell: Cell, event?: MouseEvent) => {
event?.preventDefault();
updateNotebook({ updateNotebook({
...notebook, ...notebook,
cells: notebook.cells.filter((c: Cell) => c.id !== cell.id), cells: notebook.cells.filter((c: Cell) => c.id !== cell.id),
}); });
}, [notebook, updateNotebook]); }, [notebook, updateNotebook]);
const {
isModalOpen: isRemoveCellConfirmModalOpen,
openModal: openCellRemoveConfirmModal,
closeModal: closeCellRemoveConfirmModal,
confirmAction: handleCellRemoveConfirm,
} = useModal<Cell, MouseEvent>(false, removeCell);
const handleCellRemove = useCallback((cell: Cell) => {
openCellRemoveConfirmModal(cell);
}, [openCellRemoveConfirmModal]);
const handleCellInputChange = useCallback((notebook: NotebookType, cell: Cell, value: string) => { const handleCellInputChange = useCallback((notebook: NotebookType, cell: Cell, value: string) => {
const newCell = {...cell, content: value }; const newCell = {...cell, content: value };
@ -134,100 +148,133 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
}; };
return ( return (
<div className="bg-white rounded-xl flex flex-col gap-0.5 px-7 py-5 flex-1"> <>
<div className="mb-5">{notebook.name}</div> <div className="bg-white rounded-xl flex flex-col gap-0.5 px-7 py-5 flex-1">
<div className="mb-5">{notebook.name}</div>
{notebook.cells.map((cell: Cell, index) => ( {notebook.cells.map((cell: Cell, index) => (
<Fragment key={cell.id}> <Fragment key={cell.id}>
<div key={cell.id} className="flex flex-row rounded-xl border-1 border-gray-100"> <div key={cell.id} className="flex flex-row rounded-xl border-1 border-gray-100">
<div className="flex flex-col flex-1 relative"> <div className="flex flex-col flex-1 relative">
{cell.type === "code" ? ( {cell.type === "code" ? (
<> <>
<div className="absolute left-[-1.35rem] top-2.5"> <div className="absolute left-[-1.35rem] top-2.5">
<IconButton className="p-[0.25rem] m-[-0.25rem]" onClick={toggleCellOpen.bind(null, cell.id)}> <IconButton className="p-[0.25rem] m-[-0.25rem]" onClick={toggleCellOpen.bind(null, cell.id)}>
<CaretIcon className={classNames("transition-transform", openCells.has(cell.id) ? "rotate-0" : "rotate-180")} /> <CaretIcon className={classNames("transition-transform", openCells.has(cell.id) ? "rotate-0" : "rotate-180")} />
</IconButton> </IconButton>
</div> </div>
<NotebookCellHeader <NotebookCellHeader
cell={cell} cell={cell}
runCell={handleCellRun} runCell={handleCellRun}
renameCell={handleCellRename} renameCell={handleCellRename}
removeCell={handleCellRemove} removeCell={handleCellRemove}
moveCellUp={handleCellUp} moveCellUp={handleCellUp}
moveCellDown={handleCellDown} moveCellDown={handleCellDown}
className="rounded-tl-xl rounded-tr-xl" className="rounded-tl-xl rounded-tr-xl"
/> />
{openCells.has(cell.id) && ( {openCells.has(cell.id) && (
<> <>
<TextArea
value={cell.content}
onChange={handleCellInputChange.bind(null, notebook, cell)}
// onKeyUp={handleCellRunOnEnter}
isAutoExpanding
name="cellInput"
placeholder="Type your code here..."
contentEditable={true}
className="resize-none min-h-36 max-h-96 overflow-y-auto rounded-tl-none rounded-tr-none rounded-bl-xl rounded-br-xl border-0 !outline-0"
/>
<div className="flex flex-col bg-gray-100 overflow-x-auto max-w-full">
{cell.result && (
<div className="px-2 py-2">
output: <CellResult content={cell.result} />
</div>
)}
{cell.error && (
<div className="px-2 py-2">
error: {cell.error}
</div>
)}
</div>
</>
)}
</>
) : (
<>
<div className="absolute left-[-1.35rem] top-2.5">
<IconButton className="p-[0.25rem] m-[-0.25rem]" onClick={toggleCellOpen.bind(null, cell.id)}>
<CaretIcon className={classNames("transition-transform", openCells.has(cell.id) ? "rotate-0" : "rotate-180")} />
</IconButton>
</div>
<NotebookCellHeader
cell={cell}
renameCell={handleCellRename}
removeCell={handleCellRemove}
moveCellUp={handleCellUp}
moveCellDown={handleCellDown}
className="rounded-tl-xl rounded-tr-xl"
/>
{openCells.has(cell.id) && (
<TextArea <TextArea
value={cell.content} value={cell.content}
onChange={handleCellInputChange.bind(null, notebook, cell)} onChange={handleCellInputChange.bind(null, notebook, cell)}
// onKeyUp={handleCellRunOnEnter} // onKeyUp={handleCellRunOnEnter}
isAutoExpanding isAutoExpanding
name="cellInput" name="cellInput"
placeholder="Type your code here..." placeholder="Type your text here..."
contentEditable={true} contentEditable={true}
className="resize-none min-h-36 max-h-96 overflow-y-auto rounded-tl-none rounded-tr-none rounded-bl-xl rounded-br-xl border-0 !outline-0" className="resize-none min-h-24 max-h-96 overflow-y-auto rounded-tl-none rounded-tr-none rounded-bl-xl rounded-br-xl border-0 !outline-0"
/> />
)}
<div className="flex flex-col bg-gray-100 overflow-x-auto max-w-full"> </>
{cell.result && ( )}
<div className="px-2 py-2"> </div>
output: <CellResult content={cell.result} />
</div>
)}
{cell.error && (
<div className="px-2 py-2">
error: {cell.error}
</div>
)}
</div>
</>
)}
</>
) : (
openCells.has(cell.id) && (
<TextArea
value={cell.content}
onChange={handleCellInputChange.bind(null, notebook, cell)}
// onKeyUp={handleCellRunOnEnter}
isAutoExpanding
name="cellInput"
placeholder="Type your text here..."
contentEditable={true}
className="resize-none min-h-24 max-h-96 overflow-y-auto rounded-tl-none rounded-tr-none rounded-bl-xl rounded-br-xl border-0 !outline-0"
/>
)
)}
</div> </div>
</div> <div className="ml-[-1.35rem]">
<div className="ml-[-1.35rem]"> <PopupMenu
<PopupMenu openToRight={true}
openToRight={true} triggerElement={<PlusIcon />}
triggerElement={<PlusIcon />} triggerClassName="p-[0.25rem] m-[-0.25rem]"
triggerClassName="p-[0.25rem] m-[-0.25rem]" >
> <div className="flex flex-col gap-0.5">
<div className="flex flex-col gap-0.5"> <button
<button onClick={() => handleCellAdd(index, "markdown")}
onClick={() => handleCellAdd(index, "markdown")} className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer"
>
<span>text</span>
</button>
</div>
<div
onClick={() => handleCellAdd(index, "code")}
className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer" className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer"
> >
<span>text</span> <span>code</span>
</button> </div>
</div> </PopupMenu>
<div </div>
onClick={() => handleCellAdd(index, "code")} </Fragment>
className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer" ))}
> </div>
<span>code</span>
</div> <Modal isOpen={isRemoveCellConfirmModalOpen}>
</PopupMenu> <div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Delete notebook cell?</span>
<IconButton onClick={closeCellRemoveConfirmModal}><CloseIcon /></IconButton>
</div> </div>
</Fragment> <div className="mt-8 mb-6">Are you sure you want to delete a notebook cell? This action cannot be undone.</div>
))} <div className="flex flex-row gap-4 mt-4 justify-end">
</div> <GhostButton type="button" onClick={closeCellRemoveConfirmModal}>cancel</GhostButton>
<CTAButton onClick={handleCellRemoveConfirm} type="submit">delete</CTAButton>
</div>
</div>
</Modal>
</>
); );
} }
@ -253,7 +300,7 @@ function CellResult({ content }: { content: [] }) {
data={transformInsightsGraphData(line)} data={transformInsightsGraphData(line)}
ref={graphRef as MutableRefObject<GraphVisualizationAPI>} ref={graphRef as MutableRefObject<GraphVisualizationAPI>}
graphControls={graphControls} graphControls={graphControls}
className="min-h-48" className="min-h-80"
/> />
</div> </div>
); );

View file

@ -10,7 +10,7 @@ import { Cell } from "./types";
interface NotebookCellHeaderProps { interface NotebookCellHeaderProps {
cell: Cell; cell: Cell;
runCell: (cell: Cell, cogneeInstance: string) => Promise<void>; runCell?: (cell: Cell, cogneeInstance: string) => Promise<void>;
renameCell: (cell: Cell) => void; renameCell: (cell: Cell) => void;
removeCell: (cell: Cell) => void; removeCell: (cell: Cell) => void;
moveCellUp: (cell: Cell) => void; moveCellUp: (cell: Cell) => void;
@ -36,28 +36,36 @@ export default function NotebookCellHeader({
const [runInstance, setRunInstance] = useState<string>(isCloudEnvironment() ? "cloud" : "local"); const [runInstance, setRunInstance] = useState<string>(isCloudEnvironment() ? "cloud" : "local");
const handleCellRun = () => { const handleCellRun = () => {
setIsRunningCell(); if (runCell) {
runCell(cell, runInstance) setIsRunningCell();
.then(() => { runCell(cell, runInstance)
setIsNotRunningCell(); .then(() => {
}); setIsNotRunningCell();
});
}
}; };
return ( return (
<div className={classNames("flex flex-row justify-between items-center h-9 bg-gray-100", className)}> <div className={classNames("flex flex-row justify-between items-center h-9 bg-gray-100", className)}>
<div className="flex flex-row items-center px-3.5"> <div className="flex flex-row items-center px-3.5">
{isRunningCell ? <LoadingIndicator /> : <IconButton onClick={handleCellRun}><PlayIcon /></IconButton>} {runCell && (
<>
{isRunningCell ? <LoadingIndicator /> : <IconButton onClick={handleCellRun}><PlayIcon /></IconButton>}
</>
)}
<span className="ml-4">{cell.name}</span> <span className="ml-4">{cell.name}</span>
</div> </div>
<div className="pr-4 flex flex-row items-center gap-8"> <div className="pr-4 flex flex-row items-center gap-8">
{isCloudEnvironment() ? ( {runCell && (
<div> isCloudEnvironment() ? (
cloud cognee <div>
</div> cloud cognee
) : ( </div>
<div> ) : (
local cognee <div>
</div> local cognee
</div>
)
)} )}
{/* <Select name="cogneeInstance" onChange={(event) => setRunInstance(event.currentTarget.value)} className="!bg-transparent outline-none cursor-pointer !hover:bg-gray-50"> {/* <Select name="cogneeInstance" onChange={(event) => setRunInstance(event.currentTarget.value)} className="!bg-transparent outline-none cursor-pointer !hover:bg-gray-50">
<option value="local" className="flex flex-row items-center gap-2"> <option value="local" className="flex flex-row items-center gap-2">

View file

@ -5,11 +5,11 @@ let numberOfRetries = 0;
const isAuth0Enabled = process.env.USE_AUTH0_AUTHORIZATION?.toLowerCase() === "true"; const isAuth0Enabled = process.env.USE_AUTH0_AUTHORIZATION?.toLowerCase() === "true";
const backendApiUrl = process.env.NEXT_PUBLIC_BACKEND_API_URL || "http://localhost:8000/api"; const backendApiUrl = process.env.NEXT_PUBLIC_BACKEND_API_URL || "http://localhost:8000";
const cloudApiUrl = process.env.NEXT_PUBLIC_CLOUD_API_URL || "http://localhost:8001/api"; const cloudApiUrl = process.env.NEXT_PUBLIC_CLOUD_API_URL || "http://localhost:8001";
let apiKey: string | null = null; let apiKey: string | null = process.env.NEXT_PUBLIC_COGWIT_API_KEY || null;
let accessToken: string | null = null; let accessToken: string | null = null;
export default async function fetch(url: string, options: RequestInit = {}, useCloud = false): Promise<Response> { export default async function fetch(url: string, options: RequestInit = {}, useCloud = false): Promise<Response> {
@ -30,26 +30,24 @@ export default async function fetch(url: string, options: RequestInit = {}, useC
}); });
} }
const authHeaders = useCloud && (!isCloudEnvironment() || !accessToken) ? {
"X-Api-Key": apiKey,
} : {
"Authorization": `Bearer ${accessToken}`,
}
return global.fetch( return global.fetch(
(useCloud ? cloudApiUrl : backendApiUrl) + (useCloud ? url.replace("/v1", "") : url), (useCloud ? cloudApiUrl : backendApiUrl) + "/api" + (useCloud ? url.replace("/v1", "") : url),
{ {
...options, ...options,
headers: { headers: {
...options.headers, ...options.headers,
...(useCloud && !isCloudEnvironment() ...authHeaders,
? {"X-Api-Key": apiKey!} } as HeadersInit,
: {"Authorization": `Bearer ${accessToken}`}
),
},
credentials: "include", credentials: "include",
}, },
) )
.then((response) => handleServerErrors(response, retry)) .then((response) => handleServerErrors(response, retry, useCloud))
.then((response) => {
numberOfRetries = 0;
return response;
})
.catch((error) => { .catch((error) => {
if (error.detail === undefined) { if (error.detail === undefined) {
return Promise.reject( return Promise.reject(
@ -57,10 +55,10 @@ export default async function fetch(url: string, options: RequestInit = {}, useC
); );
} }
if (error.status === 401) {
return retry(error);
}
return Promise.reject(error); return Promise.reject(error);
})
.finally(() => {
numberOfRetries = 0;
}); });
} }

View file

@ -1,8 +1,8 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
export default function handleServerErrors(response: Response, retry?: (response: Response) => Promise<Response>): Promise<Response> { export default function handleServerErrors(response: Response, retry?: (response: Response) => Promise<Response>, useCloud?: boolean): Promise<Response> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (response.status === 401) { if (response.status === 401 && !useCloud) {
if (retry) { if (retry) {
return retry(response) return retry(response)
.catch(() => { .catch(() => {
@ -13,7 +13,10 @@ export default function handleServerErrors(response: Response, retry?: (response
} }
} }
if (!response.ok) { if (!response.ok) {
return response.json().then(error => reject(error)); return response.json().then(error => {
error.status = response.status;
reject(error);
});
} }
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {

View file

@ -16,7 +16,7 @@ def get_checks_router():
api_token = request.headers.get("X-Api-Key") api_token = request.headers.get("X-Api-Key")
if api_token is None: if api_token is None:
return CloudApiKeyMissingError() raise CloudApiKeyMissingError()
return await check_api_key(api_token) return await check_api_key(api_token)