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 (
<div className="bg-gray-200 h-full max-w-[1920px] mx-auto">
<video
<div className="h-full max-w-[1920px] mx-auto">
{/* <video
autoPlay
loop
muted
@ -23,9 +23,9 @@ export default function Account() {
>
<source src="/videos/background-video-blur.mp4" type="video/mp4" />
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">
<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 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">
<CTAButton><span className="">Select a plan</span></CTAButton>
<CTAButton className="w-full"><span className="">Select a plan</span></CTAButton>
</Link>
</div>
</div>

View file

@ -2,7 +2,7 @@ import { FormEvent, useCallback, useState } from "react";
import { LoadingIndicator } from "@/ui/App";
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 addData from "@/modules/ingestion/addData";
@ -16,16 +16,22 @@ interface 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 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();
if (!filesForUpload) {
@ -41,7 +47,7 @@ export default function AddDataToCognee({ datasets, refreshDatasets, useCloud =
} : {
name: "main_dataset",
},
Array.from(filesForUpload),
filesForUpload,
useCloud
)
.then(({ dataset_id, dataset_name }) => {
@ -57,7 +63,7 @@ export default function AddDataToCognee({ datasets, refreshDatasets, useCloud =
useCloud,
)
.then(() => {
setFilesForUpload(null);
setFilesForUpload([]);
});
});
}, [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>
<form onSubmit={submitDataToCognee}>
<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.map((dataset: Dataset, index) => (
<option selected={index===0} key={dataset.id} value={dataset.id}>{dataset.name}</option>
{datasets.map((dataset: Dataset) => (
<option key={dataset.id} value={dataset.id}>{dataset.name}</option>
))}
</Select>
<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>
</NeutralButton>
{filesForUpload?.length && (
{!!filesForUpload.length && (
<div className="pt-4 mt-4 border-t-1 border-t-gray-100">
<div className="mb-1.5">selected files:</div>
{Array.from(filesForUpload || []).map((file) => (
<div key={file.name} className="py-1.5 pl-2">
{filesForUpload.map((file) => (
<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>
<IconButton onClick={removeFile.bind(null, file)}><MinusIcon /></IconButton>
</div>
))}
</div>

View file

@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { Header } from "@/ui/Layout";
import { SearchIcon } from "@/ui/Icons";
import { Notebook } from "@/ui/elements";
import { CTAButton, Notebook } from "@/ui/elements";
import { fetch, isCloudEnvironment } from "@/utils";
import { Notebook as NotebookType } from "@/ui/elements/Notebook/types";
import { useAuthenticatedUser } from "@/modules/auth";
@ -111,8 +111,8 @@ export default function Dashboard({ accessToken }: DashboardProps) {
const isCloudEnv = isCloudEnvironment();
return (
<div className="h-full flex flex-col bg-gray-200">
<video
<div className="h-full flex flex-col">
{/* <video
autoPlay
loop
muted
@ -121,12 +121,12 @@ export default function Dashboard({ accessToken }: DashboardProps) {
>
<source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</video> */}
<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="px-5 py-4 lg:w-96 bg-white rounded-xl min-h-full">
<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 h-[calc(100%-2.75rem)]">
<div className="relative mb-2">
<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..." />
@ -152,6 +152,12 @@ export default function Dashboard({ accessToken }: DashboardProps) {
/>
</CogneeInstancesAccordion>
</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 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();
}, [setCloudCogneeConnected, setLocalCogneeConnected]);
checkConnectionToCloudCognee();
}, [checkConnectionToCloudCognee, setCloudCogneeConnected, setLocalCogneeConnected]);
const {
value: isCloudConnectedModalOpen,

View file

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

View file

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

View file

@ -1,12 +1,17 @@
"use client";
import Link from "next/link";
import { BackIcon, CheckIcon } from "@/ui/Icons";
import { CTAButton, NeutralButton } from "@/ui/elements";
import Header from "@/ui/Layout/Header";
import { useAuthenticatedUser } from "@/modules/auth";
export default function Plan() {
const { user } = useAuthenticatedUser();
return (
<div className="bg-gray-200 h-full max-w-[1920px] mx-auto">
<video
<div className="h-full max-w-[1920px] mx-auto">
{/* <video
autoPlay
loop
muted
@ -15,88 +20,104 @@ export default function Plan() {
>
<source src="/videos/background-video-blur.mp4" type="video/mp4" />
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="flex-1/5 h-full">
<div className="flex flex-col justify-between">
<Link href="/dashboard" className="py-4 px-5 flex flex-row items-center gap-5">
<BackIcon />
<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 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
</div>
<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 className="text-3xl mb-4 font-bold">Free</div>
<div className="text-[1.75rem] mb-4 font-bold">Free</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 className="mb-4"><span className="text-3xl 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="mb-2"><span className="text-[1.75rem] font-bold">$2470</span><span className="text-gray-400"> /per month</span></div>
<div className=""><span className="font-black">Save 20% </span>yearly</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 className="mb-4"><span className="text-3xl 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="mb-2"><span className="text-[1.75rem] font-bold">$25</span><span className="text-gray-400"> /per month</span></div>
<div className=" text-gray-400">(beta pricing)</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="mb-1 invisible">Everything in the free plan, plus...</div>
<div className="bg-white rounded-bl-xl rounded-br-xl h-full py-4 px-5">
<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-row gap-2"><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"><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"><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" />License to use Cognee open source</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 leading-5"><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" />Integrated evaluations</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 className="bg-[rgba(255,255,255,0.7)] 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="flex flex-col gap-3 mb-10">
<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"><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"><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"><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="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-2 text-gray-400">Everything in the free plan, plus...</div>
<div className="flex flex-col gap-3 mb-4">
<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 leading-5"><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" />On-prem deployment</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 leading-5"><CheckIcon className="mt-1 shrink-0" />Architecture review</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 leading-5"><CheckIcon className="mt-1 shrink-0" />Knowledge transfer</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="mb-1 text-gray-400">Everything in the free plan, plus...</div>
<div className="flex flex-col gap-3 mb-10">
<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"><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"><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"><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="bg-white rounded-bl-xl rounded-br-xl h-full py-4 px-5">
<div className="mb-2 text-gray-400">Everything in the free plan, plus...</div>
<div className="flex flex-col gap-3 mb-4">
<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 leading-5"><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" />Comprehensive API endpoints</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 leading-5"><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" />Automatic updates and priority support</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 className="pt-4 pb-14 mb-2.5">
<a href="https://www.github.com/topoteretes/cognee" target="_blank">
<NeutralButton className="w-full">Try for free</NeutralButton>
</a>
</div>
<div className="pt-4 pb-14 mb-2.5">
<a href="https://www.cognee.ai/contact-us" target="_blank">
<CTAButton className="w-full">Talk to us</CTAButton>
</a>
</div>
<div className="pt-4 pb-14 mb-2.5">
<a href="https://platform.cognee.ai" target="_blank">
<NeutralButton className="w-full">Sign up for Cogwit Beta</NeutralButton>
</a>
</div>
</div>
@ -106,7 +127,7 @@ export default function Plan() {
<div className="text-center">On-prem</div>
<div className="text-center">Cloud</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="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 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 className="text-gray-500 mt-6">Yes, you can upgrade or downgrade your plan at any time. Changes take effect immediately.</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 className="text-gray-500 mt-6">Your data is preserved, but features may be limited based on your new plan constraints.</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 className="text-gray-500 mt-6">Yes, we offer special pricing for educational institutions and students. Contact us for details.</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 className="text-gray-500 mt-6">All new accounts start with a 14-day free trial of our Pro plan features.</div>
</div>

View file

@ -32,20 +32,21 @@ export default function Header({ user }: HeaderProps) {
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">
<CogneeIcon />
<div className="text-lg">Cognee Local</div>
</div>
<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 />
<div>Sync</div>
</GhostButton>
<a href="/plan">
<GhostButton className="text-indigo-700 pl-4 pr-4">Premium</GhostButton>
<a href="/plan" className="!text-indigo-600 pl-4 pr-4">
Premium
</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">
<SettingsIcon />
</div> */}

View file

@ -1,7 +1,7 @@
import classNames from "classnames";
import { ButtonHTMLAttributes } from "react";
export default function CTAButton({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
export default function GhostButton({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
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>
);

View file

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

View file

@ -2,10 +2,11 @@
import { v4 as uuid4 } from "uuid";
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 { IconButton, PopupMenu, TextArea } from "@/ui/elements";
import { useModal } from "@/ui/elements/Modal";
import { CaretIcon, CloseIcon, PlusIcon } from "@/ui/Icons";
import { IconButton, PopupMenu, TextArea, Modal, GhostButton, CTAButton } from "@/ui/elements";
import { GraphControlsAPI } from "@/app/(graph)/GraphControls";
import GraphVisualization, { GraphVisualizationAPI } from "@/app/(graph)/GraphVisualization";
@ -60,13 +61,26 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
updateNotebook(newNotebook);
}, [notebook, updateNotebook]);
const handleCellRemove = useCallback((cell: Cell) => {
const removeCell = useCallback((cell: Cell, event?: MouseEvent) => {
event?.preventDefault();
updateNotebook({
...notebook,
cells: notebook.cells.filter((c: Cell) => c.id !== cell.id),
});
}, [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 newCell = {...cell, content: value };
@ -134,6 +148,7 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
};
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>
@ -188,7 +203,23 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
)}
</>
) : (
openCells.has(cell.id) && (
<>
<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
value={cell.content}
onChange={handleCellInputChange.bind(null, notebook, cell)}
@ -199,7 +230,8 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
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>
@ -228,6 +260,21 @@ export default function Notebook({ notebook, updateNotebook, runCell }: Notebook
</Fragment>
))}
</div>
<Modal isOpen={isRemoveCellConfirmModalOpen}>
<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 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">
<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)}
ref={graphRef as MutableRefObject<GraphVisualizationAPI>}
graphControls={graphControls}
className="min-h-48"
className="min-h-80"
/>
</div>
);

View file

@ -10,7 +10,7 @@ import { Cell } from "./types";
interface NotebookCellHeaderProps {
cell: Cell;
runCell: (cell: Cell, cogneeInstance: string) => Promise<void>;
runCell?: (cell: Cell, cogneeInstance: string) => Promise<void>;
renameCell: (cell: Cell) => void;
removeCell: (cell: Cell) => void;
moveCellUp: (cell: Cell) => void;
@ -36,21 +36,28 @@ export default function NotebookCellHeader({
const [runInstance, setRunInstance] = useState<string>(isCloudEnvironment() ? "cloud" : "local");
const handleCellRun = () => {
if (runCell) {
setIsRunningCell();
runCell(cell, runInstance)
.then(() => {
setIsNotRunningCell();
});
}
};
return (
<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">
{runCell && (
<>
{isRunningCell ? <LoadingIndicator /> : <IconButton onClick={handleCellRun}><PlayIcon /></IconButton>}
</>
)}
<span className="ml-4">{cell.name}</span>
</div>
<div className="pr-4 flex flex-row items-center gap-8">
{isCloudEnvironment() ? (
{runCell && (
isCloudEnvironment() ? (
<div>
cloud cognee
</div>
@ -58,6 +65,7 @@ export default function NotebookCellHeader({
<div>
local cognee
</div>
)
)}
{/* <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">

View file

@ -5,11 +5,11 @@ let numberOfRetries = 0;
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;
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(
(useCloud ? cloudApiUrl : backendApiUrl) + (useCloud ? url.replace("/v1", "") : url),
(useCloud ? cloudApiUrl : backendApiUrl) + "/api" + (useCloud ? url.replace("/v1", "") : url),
{
...options,
headers: {
...options.headers,
...(useCloud && !isCloudEnvironment()
? {"X-Api-Key": apiKey!}
: {"Authorization": `Bearer ${accessToken}`}
),
},
...authHeaders,
} as HeadersInit,
credentials: "include",
},
)
.then((response) => handleServerErrors(response, retry))
.then((response) => {
numberOfRetries = 0;
return response;
})
.then((response) => handleServerErrors(response, retry, useCloud))
.catch((error) => {
if (error.detail === undefined) {
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);
})
.finally(() => {
numberOfRetries = 0;
});
}

View file

@ -1,8 +1,8 @@
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) => {
if (response.status === 401) {
if (response.status === 401 && !useCloud) {
if (retry) {
return retry(response)
.catch(() => {
@ -13,7 +13,10 @@ export default function handleServerErrors(response: Response, retry?: (response
}
}
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) {

View file

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