Merge dev into main (#1372)

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

## Description
<!-- Provide a clear description of the changes in this PR -->

## 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-11 17:02:48 +02:00 committed by GitHub
commit e591c53991
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
256 changed files with 10305 additions and 1272 deletions

View file

@ -16,7 +16,7 @@
STRUCTURED_OUTPUT_FRAMEWORK="instructor"
LLM_API_KEY="your_api_key"
LLM_MODEL="openai/gpt-5-mini"
LLM_MODEL="openai/gpt-4o-mini"
LLM_PROVIDER="openai"
LLM_ENDPOINT=""
LLM_API_VERSION=""
@ -33,7 +33,7 @@ EMBEDDING_MAX_TOKENS=8191
# If using BAML structured output these env variables will be used
BAML_LLM_PROVIDER=openai
BAML_LLM_MODEL="gpt-5-mini"
BAML_LLM_MODEL="gpt-4o-mini"
BAML_LLM_ENDPOINT=""
BAML_LLM_API_KEY="your_api_key"
BAML_LLM_API_VERSION=""
@ -124,6 +124,10 @@ ALLOW_HTTP_REQUESTS=True
# When set to False errors during data processing will be returned as info but not raised to allow handling of faulty documents
RAISE_INCREMENTAL_LOADING_ERRORS=True
# When set to True, the Cognee backend will require authentication for requests to the API.
# If you're disabling this, make sure to also disable ENABLE_BACKEND_ACCESS_CONTROL.
REQUIRE_AUTHENTICATION=False
# Set this variable to True to enforce usage of backend access control for Cognee
# Note: This is only currently supported by the following databases:
# Relational: SQLite, Postgres
@ -133,6 +137,14 @@ RAISE_INCREMENTAL_LOADING_ERRORS=True
# It enforces LanceDB and KuzuDB use and uses them to create databases per Cognee user + dataset
ENABLE_BACKEND_ACCESS_CONTROL=False
################################################################################
# ☁️ Cloud Sync Settings
################################################################################
# Cognee Cloud API settings for syncing data to/from cloud infrastructure
COGNEE_CLOUD_API_URL="http://localhost:8001"
COGNEE_CLOUD_AUTH_TOKEN="your-auth-token"
################################################################################
# 🛠️ DEV Settings
################################################################################

97
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,97 @@
name: 🐛 Bug Report
description: Report a bug or unexpected behavior
title: "[Bug]: "
labels: ["bug", "needs-triage"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please provide a clear and detailed description.
- type: textarea
id: description
attributes:
label: Bug Description
description: Please provide a clear and concise description of the bug. What happened vs what you expected?
placeholder: Describe the bug in detail...
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: Please provide detailed steps to reproduce the issue
placeholder: |
1. Go to...
2. Click on...
3. See error...
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
placeholder: Describe what you expected...
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: What actually happened?
placeholder: Describe what actually happened...
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Please provide your environment details
placeholder: |
- OS: [e.g. macOS 13.0, Ubuntu 20.04]
- Python version: [e.g. 3.9.0]
- Cognee version: [e.g. 0.1.0]
- LLM Provider: [e.g. OpenAI, Ollama]
- Database: [e.g. Neo4j, FalkorDB]
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs/Error Messages
description: Please include any relevant logs or error messages
placeholder: Paste logs here...
render: shell
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other context about the problem here
placeholder: Any additional information...
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Pre-submission Checklist
description: Please confirm the following before submitting
options:
- label: I have searched existing issues to ensure this bug hasn't been reported already
required: true
- label: I have provided a clear and detailed description of the bug
required: true
- label: I have included steps to reproduce the issue
required: true
- label: I have included my environment details
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord Community
url: https://discord.gg/NQtRemgQVD
about: Join our Discord community for questions, discussions, and support
- name: 📖 Documentation
url: https://docs.cognee.ai
about: Check our documentation for guides and API references

View file

@ -0,0 +1,73 @@
name: 📚 Documentation Issue
description: Report an issue with documentation or suggest documentation improvements
title: "[Docs]: "
labels: ["documentation", "needs-triage"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for helping improve our documentation! Please provide details about the documentation issue or improvement.
- type: dropdown
id: doc-type
attributes:
label: Documentation Type
description: What type of documentation issue is this?
options:
- Missing documentation
- Incorrect documentation
- Unclear documentation
- Documentation improvement
- New documentation request
validations:
required: true
- type: textarea
id: location
attributes:
label: Documentation Location
description: Where is the documentation issue located? (URL, file path, section, etc.)
placeholder: https://cognee.ai/docs/... or specific file/section
validations:
required: true
- type: textarea
id: issue
attributes:
label: Issue Description
description: Please describe the documentation issue or improvement needed
placeholder: The documentation is unclear about...
validations:
required: true
- type: textarea
id: suggestion
attributes:
label: Suggested Improvement
description: How would you improve this documentation?
placeholder: I suggest changing this to...
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other context about the documentation issue
placeholder: Additional context...
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Pre-submission Checklist
description: Please confirm the following before submitting
options:
- label: I have searched existing issues to ensure this documentation issue hasn't been reported already
required: true
- label: I have provided a clear description of the documentation issue
required: true
- label: I have specified the location of the documentation issue
required: true

View file

@ -0,0 +1,78 @@
name: 🚀 Feature Request
description: Suggest a new feature or enhancement
title: "[Feature]: "
labels: ["enhancement", "needs-triage"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a new feature! Please provide a clear and detailed description of your idea.
- type: textarea
id: problem
attributes:
label: Problem Statement
description: Is your feature request related to a problem? Please describe the problem you're trying to solve.
placeholder: I'm always frustrated when...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe the solution you'd like to see implemented
placeholder: I would like to see...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Describe any alternative solutions or features you've considered
placeholder: I have also considered...
validations:
required: false
- type: textarea
id: use-case
attributes:
label: Use Case
description: Describe your specific use case and how this feature would help
placeholder: This feature would help me...
validations:
required: true
- type: textarea
id: implementation
attributes:
label: Implementation Ideas
description: If you have ideas about how this could be implemented, please share them
placeholder: This could be implemented by...
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other context, screenshots, or examples about the feature request
placeholder: Additional context...
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Pre-submission Checklist
description: Please confirm the following before submitting
options:
- label: I have searched existing issues to ensure this feature hasn't been requested already
required: true
- label: I have provided a clear problem statement and proposed solution
required: true
- label: I have described my specific use case
required: true

View file

@ -1,7 +1,50 @@
<!-- .github/pull_request_template.md -->
## Description
<!-- Provide a clear description of the changes in this PR -->
<!--
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 -->
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] 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.

View file

@ -0,0 +1,224 @@
name: Temporal Graph Tests
permissions:
contents: read
on:
workflow_call:
inputs:
databases:
required: false
type: string
default: "all"
description: "Which vector databases to test (comma-separated list or 'all')"
jobs:
run_temporal_graph_kuzu_lance_sqlite:
name: Temporal Graph test Kuzu (lancedb + sqlite)
runs-on: ubuntu-22.04
if: ${{ inputs.databases == 'all' || contains(inputs.databases, 'kuzu/lance/sqlite') }}
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cognee Setup
uses: ./.github/actions/cognee_setup
with:
python-version: ${{ inputs.python-version }}
- name: Dependencies already installed
run: echo "Dependencies already installed in setup"
- name: Run Temporal Graph with Kuzu (lancedb + sqlite)
env:
ENV: 'dev'
LLM_MODEL: ${{ secrets.LLM_MODEL }}
LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_API_VERSION: ${{ secrets.LLM_API_VERSION }}
EMBEDDING_MODEL: ${{ secrets.EMBEDDING_MODEL }}
EMBEDDING_ENDPOINT: ${{ secrets.EMBEDDING_ENDPOINT }}
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
EMBEDDING_API_VERSION: ${{ secrets.EMBEDDING_API_VERSION }}
GRAPH_DATABASE_PROVIDER: 'kuzu'
VECTOR_DB_PROVIDER: 'lancedb'
DB_PROVIDER: 'sqlite'
run: uv run python ./cognee/tests/test_temporal_graph.py
run_temporal_graph_neo4j_lance_sqlite:
name: Temporal Graph test Neo4j (lancedb + sqlite)
runs-on: ubuntu-22.04
if: ${{ inputs.databases == 'all' || contains(inputs.databases, 'neo4j/lance/sqlite') }}
services:
neo4j:
image: neo4j:5.11
env:
NEO4J_AUTH: neo4j/pleaseletmein
NEO4J_PLUGINS: '["apoc","graph-data-science"]'
ports:
- 7474:7474
- 7687:7687
options: >-
--health-cmd="cypher-shell -u neo4j -p pleaseletmein 'RETURN 1'"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cognee Setup
uses: ./.github/actions/cognee_setup
with:
python-version: ${{ inputs.python-version }}
- name: Dependencies already installed
run: echo "Dependencies already installed in setup"
- name: Run Temporal Graph with Neo4j (lancedb + sqlite)
env:
ENV: 'dev'
LLM_MODEL: ${{ secrets.LLM_MODEL }}
LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_API_VERSION: ${{ secrets.LLM_API_VERSION }}
EMBEDDING_MODEL: ${{ secrets.EMBEDDING_MODEL }}
EMBEDDING_ENDPOINT: ${{ secrets.EMBEDDING_ENDPOINT }}
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
EMBEDDING_API_VERSION: ${{ secrets.EMBEDDING_API_VERSION }}
GRAPH_DATABASE_PROVIDER: 'neo4j'
VECTOR_DB_PROVIDER: 'lancedb'
DB_PROVIDER: 'sqlite'
GRAPH_DATABASE_URL: bolt://localhost:7687
GRAPH_DATABASE_USERNAME: neo4j
GRAPH_DATABASE_PASSWORD: pleaseletmein
run: uv run python ./cognee/tests/test_temporal_graph.py
run_temporal_graph_kuzu_postgres_pgvector:
name: Temporal Graph test Kuzu (postgres + pgvector)
runs-on: ubuntu-22.04
if: ${{ inputs.databases == 'all' || contains(inputs.databases, 'kuzu/pgvector/postgres') }}
services:
postgres:
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: cognee
POSTGRES_PASSWORD: cognee
POSTGRES_DB: cognee_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cognee Setup
uses: ./.github/actions/cognee_setup
with:
python-version: ${{ inputs.python-version }}
extra-dependencies: "postgres"
- name: Dependencies already installed
run: echo "Dependencies already installed in setup"
- name: Run Temporal Graph with Kuzu (postgres + pgvector)
env:
ENV: dev
LLM_MODEL: ${{ secrets.LLM_MODEL }}
LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_API_VERSION: ${{ secrets.LLM_API_VERSION }}
EMBEDDING_MODEL: ${{ secrets.EMBEDDING_MODEL }}
EMBEDDING_ENDPOINT: ${{ secrets.EMBEDDING_ENDPOINT }}
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
EMBEDDING_API_VERSION: ${{ secrets.EMBEDDING_API_VERSION }}
GRAPH_DATABASE_PROVIDER: 'kuzu'
VECTOR_DB_PROVIDER: 'pgvector'
DB_PROVIDER: 'postgres'
DB_NAME: 'cognee_db'
DB_HOST: '127.0.0.1'
DB_PORT: 5432
DB_USERNAME: cognee
DB_PASSWORD: cognee
run: uv run python ./cognee/tests/test_temporal_graph.py
run_temporal_graph_neo4j_postgres_pgvector:
name: Temporal Graph test Neo4j (postgres + pgvector)
runs-on: ubuntu-22.04
if: ${{ inputs.databases == 'all' || contains(inputs.databases, 'neo4j/pgvector/postgres') }}
services:
neo4j:
image: neo4j:5.11
env:
NEO4J_AUTH: neo4j/pleaseletmein
NEO4J_PLUGINS: '["apoc","graph-data-science"]'
ports:
- 7474:7474
- 7687:7687
options: >-
--health-cmd="cypher-shell -u neo4j -p pleaseletmein 'RETURN 1'"
--health-interval=10s
--health-timeout=5s
--health-retries=5
postgres:
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: cognee
POSTGRES_PASSWORD: cognee
POSTGRES_DB: cognee_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries=5
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cognee Setup
uses: ./.github/actions/cognee_setup
with:
python-version: ${{ inputs.python-version }}
extra-dependencies: "postgres"
- name: Dependencies already installed
run: echo "Dependencies already installed in setup"
- name: Run Temporal Graph with Neo4j (postgres + pgvector)
env:
ENV: dev
LLM_MODEL: ${{ secrets.LLM_MODEL }}
LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_API_VERSION: ${{ secrets.LLM_API_VERSION }}
EMBEDDING_MODEL: ${{ secrets.EMBEDDING_MODEL }}
EMBEDDING_ENDPOINT: ${{ secrets.EMBEDDING_ENDPOINT }}
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
EMBEDDING_API_VERSION: ${{ secrets.EMBEDDING_API_VERSION }}
GRAPH_DATABASE_PROVIDER: 'neo4j'
VECTOR_DB_PROVIDER: 'pgvector'
DB_PROVIDER: 'postgres'
GRAPH_DATABASE_URL: bolt://localhost:7687
GRAPH_DATABASE_USERNAME: neo4j
GRAPH_DATABASE_PASSWORD: pleaseletmein
DB_NAME: cognee_db
DB_HOST: 127.0.0.1
DB_PORT: 5432
DB_USERNAME: cognee
DB_PASSWORD: cognee
run: uv run python ./cognee/tests/test_temporal_graph.py

30
.github/workflows/test_openrouter.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: test | openrouter
on:
workflow_call:
jobs:
test-openrouter:
name: Run OpenRouter Test
runs-on: ubuntu-22.04
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Cognee Setup
uses: ./.github/actions/cognee_setup
with:
python-version: '3.11.x'
- name: Run OpenRouter Simple Example
env:
LLM_PROVIDER: "custom"
LLM_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
LLM_MODEL: "openrouter/x-ai/grok-code-fast-1"
LLM_ENDPOINT: "https://openrouter.ai/api/v1"
EMBEDDING_PROVIDER: "openai"
EMBEDDING_API_KEY: ${{ secrets.OPENAI_API_KEY }}
EMBEDDING_MODEL: "openai/text-embedding-3-large"
EMBEDDING_DIMENSIONS: "3072"
EMBEDDING_MAX_TOKENS: "8191"
run: uv run python ./examples/python/simple_example.py

View file

@ -50,6 +50,12 @@ jobs:
uses: ./.github/workflows/graph_db_tests.yml
secrets: inherit
temporal-graph-tests:
name: Temporal Graph Test
needs: [ basic-tests, e2e-tests, cli-tests, graph-db-tests ]
uses: ./.github/workflows/temporal_graph_tests.yml
secrets: inherit
search-db-tests:
name: Search Test on Different DBs
needs: [basic-tests, e2e-tests, cli-tests, graph-db-tests]
@ -115,6 +121,12 @@ jobs:
uses: ./.github/workflows/test_gemini.yml
secrets: inherit
openrouter-tests:
name: OpenRouter Tests
needs: [basic-tests, e2e-tests, cli-tests]
uses: ./.github/workflows/test_openrouter.yml
secrets: inherit
# Ollama tests moved to the end
ollama-tests:
name: Ollama Tests
@ -128,6 +140,7 @@ jobs:
vector-db-tests,
example-tests,
gemini-tests,
openrouter-tests,
mcp-test,
relational-db-migration-tests,
docker-compose-test,
@ -150,6 +163,7 @@ jobs:
db-examples-tests,
mcp-test,
gemini-tests,
openrouter-tests,
ollama-tests,
relational-db-migration-tests,
docker-compose-test,
@ -171,6 +185,7 @@ jobs:
"${{ needs.db-examples-tests.result }}" == "success" &&
"${{ needs.relational-db-migration-tests.result }}" == "success" &&
"${{ needs.gemini-tests.result }}" == "success" &&
"${{ needs.openrouter-tests.result }}" == "success" &&
"${{ needs.docker-compose-test.result }}" == "success" &&
"${{ needs.docker-ci-test.result }}" == "success" &&
"${{ needs.ollama-tests.result }}" == "success" ]]; then

View file

@ -43,7 +43,7 @@
**🚀 We launched Cogwit beta (Fully-hosted AI Memory): Sign up [here](https://platform.cognee.ai/)! 🚀**
**🚀 We launched Cogwit beta (Fully-hosted AI Memory): Sign up [here](https://platform.cognee.ai/)! 🚀**
Build dynamic memory for Agents and replace RAG using scalable, modular ECL (Extract, Cognify, Load) pipelines.
@ -174,9 +174,9 @@ Example output:
You can also cognify your files and query using cognee UI.
<img src="assets/cognee-ui-2.webp" width="100%" alt="Cognee UI 2"></a>
<img src="assets/cognee-new-ui.webp" width="100%" alt="Cognee UI 2"></a>
Try cognee UI out locally [here](https://docs.cognee.ai/how-to-guides/cognee-ui).
Try cognee UI by runnning ``` cognee -ui ``` command on your terminal.
## Understand our architecture

View file

@ -0,0 +1,98 @@
"""Add sync_operations table
Revision ID: 211ab850ef3d
Revises: 9e7a3cb85175
Create Date: 2025-09-10 20:11:13.534829
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "211ab850ef3d"
down_revision: Union[str, None] = "9e7a3cb85175"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# Check if table already exists (it might be created by Base.metadata.create_all() in initial migration)
connection = op.get_bind()
inspector = sa.inspect(connection)
if "sync_operations" not in inspector.get_table_names():
# Table doesn't exist, create it normally
op.create_table(
"sync_operations",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("run_id", sa.Text(), nullable=True),
sa.Column(
"status",
sa.Enum(
"STARTED",
"IN_PROGRESS",
"COMPLETED",
"FAILED",
"CANCELLED",
name="syncstatus",
create_type=False,
),
nullable=True,
),
sa.Column("progress_percentage", sa.Integer(), nullable=True),
sa.Column("dataset_ids", sa.JSON(), nullable=True),
sa.Column("dataset_names", sa.JSON(), nullable=True),
sa.Column("user_id", sa.UUID(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("total_records_to_sync", sa.Integer(), nullable=True),
sa.Column("total_records_to_download", sa.Integer(), nullable=True),
sa.Column("total_records_to_upload", sa.Integer(), nullable=True),
sa.Column("records_downloaded", sa.Integer(), nullable=True),
sa.Column("records_uploaded", sa.Integer(), nullable=True),
sa.Column("bytes_downloaded", sa.Integer(), nullable=True),
sa.Column("bytes_uploaded", sa.Integer(), nullable=True),
sa.Column("dataset_sync_hashes", sa.JSON(), nullable=True),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("retry_count", sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
op.f("ix_sync_operations_run_id"), "sync_operations", ["run_id"], unique=True
)
op.create_index(
op.f("ix_sync_operations_user_id"), "sync_operations", ["user_id"], unique=False
)
else:
# Table already exists, but we might need to add missing columns or indexes
# For now, just log that the table already exists
print("sync_operations table already exists, skipping creation")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# Only drop if table exists (might have been created by Base.metadata.create_all())
connection = op.get_bind()
inspector = sa.inspect(connection)
if "sync_operations" in inspector.get_table_names():
op.drop_index(op.f("ix_sync_operations_user_id"), table_name="sync_operations")
op.drop_index(op.f("ix_sync_operations_run_id"), table_name="sync_operations")
op.drop_table("sync_operations")
# Drop the enum type that was created (only if no other tables are using it)
sa.Enum(name="syncstatus").drop(op.get_bind(), checkfirst=True)
else:
print("sync_operations table doesn't exist, skipping downgrade")
# ### end Alembic commands ###

View file

@ -0,0 +1,46 @@
"""Add notebook table
Revision ID: 45957f0a9849
Revises: 9e7a3cb85175
Create Date: 2025-09-10 17:47:58.201319
"""
from datetime import datetime, timezone
from uuid import uuid4
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "45957f0a9849"
down_revision: Union[str, None] = "9e7a3cb85175"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
if "notebooks" not in inspector.get_table_names():
# Define table with all necessary columns including primary key
op.create_table(
"notebooks",
sa.Column("id", sa.UUID, primary_key=True, default=uuid4), # Critical for SQLite
sa.Column("owner_id", sa.UUID, index=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("cells", sa.JSON(), nullable=False),
sa.Column("deletable", sa.Boolean(), default=True),
sa.Column("created_at", sa.DateTime(), default=lambda: datetime.now(timezone.utc)),
)
def downgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
if "notebooks" in inspector.get_table_names():
op.drop_table("notebooks")

View file

@ -19,6 +19,7 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
db_engine = get_relational_engine()
# we might want to delete this
await_only(db_engine.create_database())

BIN
assets/cognee-new-ui.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

View file

@ -26,9 +26,10 @@ export interface NodesAndEdges {
interface CogneeAddWidgetProps {
onData: (data: NodesAndLinks) => void;
useCloud?: boolean;
}
export default function CogneeAddWidget({ onData }: CogneeAddWidgetProps) {
export default function CogneeAddWidget({ onData, useCloud = false }: CogneeAddWidgetProps) {
const {
datasets,
refreshDatasets,
@ -76,17 +77,18 @@ export default function CogneeAddWidget({ onData }: CogneeAddWidgetProps) {
return addData(dataset, files)
.then(() => {
const onUpdate = (data: NodesAndEdges) => {
onData({
nodes: data.nodes,
links: data.edges,
});
setProcessingFilesDone();
};
// const onUpdate = (data: NodesAndEdges) => {
// onData({
// nodes: data.nodes,
// links: data.edges,
// });
// setProcessingFilesDone();
// };
return cognifyDataset(dataset, onUpdate)
return cognifyDataset(dataset, useCloud)
.then(() => {
refreshDatasets();
setProcessingFilesDone();
});
});
};

View file

@ -1,5 +1,6 @@
"use client";
import classNames from "classnames";
import { MutableRefObject, useEffect, useImperativeHandle, useRef, useState, useCallback } from "react";
import { forceCollide, forceManyBody } from "d3-force-3d";
import ForceGraph, { ForceGraphMethods, GraphData, LinkObject, NodeObject } from "react-force-graph-2d";
@ -10,6 +11,7 @@ interface GraphVisuzaliationProps {
ref: MutableRefObject<GraphVisualizationAPI>;
data?: GraphData<NodeObject, LinkObject>;
graphControls: MutableRefObject<GraphControlsAPI>;
className?: string;
}
export interface GraphVisualizationAPI {
@ -17,7 +19,7 @@ export interface GraphVisualizationAPI {
setGraphShape: (shape: string) => void;
}
export default function GraphVisualization({ ref, data, graphControls }: GraphVisuzaliationProps) {
export default function GraphVisualization({ ref, data, graphControls, className }: GraphVisuzaliationProps) {
const textSize = 6;
const nodeSize = 15;
// const addNodeDistanceFromSourceNode = 15;
@ -201,7 +203,7 @@ export default function GraphVisualization({ ref, data, graphControls }: GraphVi
if (typeof window !== "undefined" && data && graphRef.current) {
// add collision force
graphRef.current.d3Force("collision", forceCollide(nodeSize * 1.5));
graphRef.current.d3Force("charge", forceManyBody().strength(-1500).distanceMin(300).distanceMax(900));
graphRef.current.d3Force("charge", forceManyBody().strength(-10).distanceMin(10).distanceMax(50));
}
}, [data, graphRef]);
@ -213,7 +215,7 @@ export default function GraphVisualization({ ref, data, graphControls }: GraphVi
}));
return (
<div ref={containerRef} className="w-full h-full" id="graph-container">
<div ref={containerRef} className={classNames("w-full h-full", className)} id="graph-container">
{(data && typeof window !== "undefined") ? (
<ForceGraph
ref={graphRef}

View file

@ -2,19 +2,19 @@ import colors from "tailwindcss/colors";
import { formatHex } from "culori";
const NODE_COLORS = {
TextDocument: formatHex(colors.blue[500]),
DocumentChunk: formatHex(colors.green[500]),
TextSummary: formatHex(colors.orange[500]),
Entity: formatHex(colors.yellow[300]),
EntityType: formatHex(colors.purple[800]),
NodeSet: formatHex(colors.indigo[300]),
GitHubUser: formatHex(colors.gray[300]),
Comment: formatHex(colors.amber[500]),
Issue: formatHex(colors.red[500]),
Repository: formatHex(colors.stone[400]),
Commit: formatHex(colors.teal[500]),
File: formatHex(colors.emerald[500]),
FileChange: formatHex(colors.sky[500]),
TextDocument: formatHex(colors.stone[200]),
DocumentChunk: formatHex(colors.stone[300]),
TextSummary: formatHex(colors.blue[300]),
Entity: formatHex(colors.indigo[300]),
EntityType: formatHex(colors.indigo[400]),
NodeSet: formatHex(colors.indigo[400]),
GitHubUser: formatHex(colors.gray[200]),
Comment: formatHex(colors.blue[300]),
Issue: formatHex(colors.red[200]),
Repository: formatHex(colors.stone[200]),
Commit: formatHex(colors.teal[300]),
File: formatHex(colors.emerald[300]),
FileChange: formatHex(colors.sky[300]),
};
export default function getColorForNodeType(type: string) {

View file

@ -0,0 +1,58 @@
"use client";
import Link from "next/link";
import { BackIcon } from "@/ui/Icons";
import { CTAButton } from "@/ui/elements";
import Header from "@/ui/Layout/Header";
import { useAuthenticatedUser } from "@/modules/auth";
export default function Account() {
const { user } = useAuthenticatedUser();
const account = {
name: user ? user.name || user.email : "NN",
};
return (
<div className="bg-gray-200 h-full max-w-[1920px] mx-auto">
<video
autoPlay
loop
muted
playsInline
className="fixed inset-0 z-0 object-cover w-full h-full"
>
<source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<Header />
<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">
<BackIcon />
<span>back</span>
</Link>
<div className="flex-1/5 flex flex-col gap-2.5">
<div className="py-4 px-5 rounded-xl bg-white">
<div>Account</div>
<div className="text-sm text-gray-400 mb-8">Manage your account&apos;s settings.</div>
<div>{account.name}</div>
</div>
<div className="py-4 px-5 rounded-xl bg-white">
<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>
</Link>
</div>
</div>
<div className="flex-1/5 py-4 px-5 rounded-xl">
</div>
<div className="flex-1/5 py-4 px-5 rounded-xl">
</div>
<div className="flex-1/5 py-4 px-5 rounded-xl">
</div>
</div>
</div>
);
}

View file

@ -0,0 +1 @@
export { default } from "./Account";

View file

@ -0,0 +1,116 @@
import { FormEvent, useCallback, useState } from "react";
import { CloseIcon, PlusIcon } from "@/ui/Icons";
import { useModal } from "@/ui/elements/Modal";
import { CTAButton, GhostButton, IconButton, Modal, NeutralButton, Select } from "@/ui/elements";
import addData from "@/modules/ingestion/addData";
import { Dataset } from "@/modules/ingestion/useDatasets";
import cognifyDataset from "@/modules/datasets/cognifyDataset";
interface AddDataToCogneeProps {
datasets: Dataset[];
refreshDatasets: () => void;
useCloud?: boolean;
}
export default function AddDataToCognee({ datasets, refreshDatasets, useCloud = false }: AddDataToCogneeProps) {
const [filesForUpload, setFilesForUpload] = useState<FileList | null>(null);
const prepareFiles = useCallback((event: FormEvent<HTMLInputElement>) => {
const formElements = event.currentTarget;
const files = formElements.files;
setFilesForUpload(files);
}, []);
const processDataWithCognee = useCallback((state: object, event?: FormEvent<HTMLFormElement>) => {
event!.preventDefault();
if (!filesForUpload) {
return;
}
const formElements = event!.currentTarget;
const datasetId = formElements.datasetName.value;
return addData(
datasetId ? {
id: datasetId,
} : {
name: "main_dataset",
},
Array.from(filesForUpload),
useCloud
)
.then(({ dataset_id, dataset_name }) => {
refreshDatasets();
setFilesForUpload(null);
return cognifyDataset({
id: dataset_id,
name: dataset_name,
data: [], // not important, just to mimick Dataset
status: "", // not important, just to mimick Dataset
}, useCloud);
});
}, [filesForUpload, refreshDatasets, useCloud]);
const {
isModalOpen: isAddDataModalOpen,
openModal: openAddDataModal,
closeModal: closeAddDataModal,
isActionLoading: isProcessingDataWithCognee,
confirmAction: submitDataToCognee,
} = useModal(false, processDataWithCognee);
return (
<>
<GhostButton onClick={openAddDataModal} className="mb-5 py-1.5 !px-2 text-sm w-full items-center justify-start">
<PlusIcon />
Add data to cognee
</GhostButton>
<Modal isOpen={isAddDataModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Add new data to a dataset?</span>
<IconButton disabled={isProcessingDataWithCognee} onClick={closeAddDataModal}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Please select a 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">
{!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>
))}
</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" />
<span>select files</span>
</NeutralButton>
{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">
<span className="text-sm">{file.name}</span>
</div>
))}
</div>
)}
</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton disabled={isProcessingDataWithCognee} type="button" onClick={() => closeAddDataModal()}>cancel</GhostButton>
<CTAButton disabled={isProcessingDataWithCognee} type="submit">
{isProcessingDataWithCognee ? "processing..." : "add"}
</CTAButton>
</div>
</form>
</div>
</Modal>
</>
);
}

View file

@ -0,0 +1,31 @@
"use client";
import { useBoolean } from "@/utils";
import { Accordion } from "@/ui/elements";
interface CogneeInstancesAccordionProps {
children: React.ReactNode;
}
export default function CogneeInstancesAccordion({
children,
}: CogneeInstancesAccordionProps) {
const {
value: isInstancesPanelOpen,
setTrue: openInstancesPanel,
setFalse: closeInstancesPanel,
} = useBoolean(true);
return (
<>
<Accordion
title={<span>Cognee Instances</span>}
isOpen={isInstancesPanelOpen}
openAccordion={openInstancesPanel}
closeAccordion={closeInstancesPanel}
>
{children}
</Accordion>
</>
);
}

View file

@ -0,0 +1,169 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Header } from "@/ui/Layout";
import { SearchIcon } from "@/ui/Icons";
import { Notebook } from "@/ui/elements";
import { fetch, isCloudEnvironment } from "@/utils";
import { Notebook as NotebookType } from "@/ui/elements/Notebook/types";
import { useAuthenticatedUser } from "@/modules/auth";
import { Dataset } from "@/modules/ingestion/useDatasets";
import useNotebooks from "@/modules/notebooks/useNotebooks";
import AddDataToCognee from "./AddDataToCognee";
import NotebooksAccordion from "./NotebooksAccordion";
import CogneeInstancesAccordion from "./CogneeInstancesAccordion";
import InstanceDatasetsAccordion from "./InstanceDatasetsAccordion";
interface DashboardProps {
user?: {
id: string;
name: string;
email: string;
picture: string;
};
accessToken: string;
}
export default function Dashboard({ accessToken }: DashboardProps) {
fetch.setAccessToken(accessToken);
const { user } = useAuthenticatedUser();
const {
notebooks,
refreshNotebooks,
runCell,
addNotebook,
updateNotebook,
saveNotebook,
removeNotebook,
} = useNotebooks();
useEffect(() => {
if (!notebooks.length) {
refreshNotebooks()
.then((notebooks) => {
if (notebooks[0]) {
setSelectedNotebookId(notebooks[0].id);
}
});
}
}, [notebooks.length, refreshNotebooks]);
const [selectedNotebookId, setSelectedNotebookId] = useState<string | null>(null);
const handleNotebookRemove = useCallback((notebookId: string) => {
setSelectedNotebookId((currentSelectedNotebookId) => (
currentSelectedNotebookId === notebookId ? null : currentSelectedNotebookId
));
return removeNotebook(notebookId);
}, [removeNotebook]);
const saveNotebookTimeoutRef = useRef<number | null>(null);
const saveNotebookThrottled = useCallback((notebook: NotebookType) => {
const throttleTime = 1000;
if (saveNotebookTimeoutRef.current) {
clearTimeout(saveNotebookTimeoutRef.current);
saveNotebookTimeoutRef.current = null;
}
saveNotebookTimeoutRef.current = setTimeout(() => {
saveNotebook(notebook);
}, throttleTime) as unknown as number;
}, [saveNotebook]);
useEffect(() => {
return () => {
if (saveNotebookTimeoutRef.current) {
clearTimeout(saveNotebookTimeoutRef.current);
saveNotebookTimeoutRef.current = null;
}
};
}, []);
const handleNotebookUpdate = useCallback((notebook: NotebookType) => {
updateNotebook(notebook);
saveNotebookThrottled(notebook);
}, [saveNotebookThrottled, updateNotebook]);
const selectedNotebook = notebooks.find((notebook) => notebook.id === selectedNotebookId);
// ############################
// Datasets logic
const [datasets, setDatasets] = useState<Dataset[]>([]);
const refreshDatasetsRef = useRef(() => {});
const handleDatasetsChange = useCallback((payload: { datasets: Dataset[], refreshDatasets: () => void }) => {
const {
datasets,
refreshDatasets,
} = payload;
refreshDatasetsRef.current = refreshDatasets;
setDatasets(datasets);
}, []);
const isCloudEnv = isCloudEnvironment();
return (
<div className="h-full flex flex-col bg-gray-200">
<video
autoPlay
loop
muted
playsInline
className="fixed inset-0 z-0 object-cover w-full h-full"
>
<source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag.
</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 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..." />
</div>
<AddDataToCognee
datasets={datasets}
refreshDatasets={refreshDatasetsRef.current}
useCloud={isCloudEnv}
/>
<NotebooksAccordion
notebooks={notebooks}
addNotebook={addNotebook}
removeNotebook={handleNotebookRemove}
openNotebook={setSelectedNotebookId}
/>
<div className="mt-7 mb-14">
<CogneeInstancesAccordion>
<InstanceDatasetsAccordion
onDatasetsChange={handleDatasetsChange}
/>
</CogneeInstancesAccordion>
</div>
</div>
<div className="flex-1 flex flex-col justify-between h-full overflow-y-auto">
{selectedNotebook && (
<Notebook
key={selectedNotebook.id}
notebook={selectedNotebook}
updateNotebook={handleNotebookUpdate}
saveNotebook={saveNotebook}
runCell={runCell}
/>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,346 @@
"use client";
import { ChangeEvent, useCallback, useEffect, useState } from "react";
import { useBoolean } from "@/utils";
import { Accordion, CTAButton, GhostButton, IconButton, Input, Modal, PopupMenu } from "@/ui/elements";
import { AccordionProps } from "@/ui/elements/Accordion";
import { CloseIcon, DatasetIcon, MinusIcon, PlusIcon } from "@/ui/Icons";
import useDatasets, { Dataset } from "@/modules/ingestion/useDatasets";
import addData from "@/modules/ingestion/addData";
import cognifyDataset from "@/modules/datasets/cognifyDataset";
import { DataFile } from "@/modules/ingestion/useData";
import { LoadingIndicator } from "@/ui/App";
interface DatasetsChangePayload {
datasets: Dataset[]
refreshDatasets: () => void;
}
export interface DatasetsAccordionProps extends Omit<AccordionProps, "isOpen" | "openAccordion" | "closeAccordion" | "children"> {
onDatasetsChange?: (payload: DatasetsChangePayload) => void;
useCloud?: boolean;
}
export default function DatasetsAccordion({
title,
tools,
switchCaretPosition = false,
className,
contentClassName,
onDatasetsChange,
useCloud = false,
}: DatasetsAccordionProps) {
const {
value: isDatasetsPanelOpen,
setTrue: openDatasetsPanel,
setFalse: closeDatasetsPanel,
} = useBoolean(true);
const {
datasets,
refreshDatasets,
addDataset,
removeDataset,
getDatasetData,
removeDatasetData,
} = useDatasets(useCloud);
useEffect(() => {
if (datasets.length === 0) {
refreshDatasets();
}
}, [datasets.length, refreshDatasets]);
const [openDatasets, openDataset] = useState<Set<string>>(new Set());
const toggleDataset = (id: string) => {
openDataset((prev) => {
const newState = new Set(prev);
if (newState.has(id)) {
newState.delete(id)
} else {
getDatasetData(id)
.then(() => {
newState.add(id);
});
}
return newState;
});
};
const refreshOpenDatasetsData = useCallback(() => {
return Promise.all(
openDatasets.values().map(
(datasetId) => getDatasetData(datasetId)
)
);
}, [getDatasetData, openDatasets]);
const refreshDatasetsAndData = useCallback(() => {
refreshDatasets()
.then(refreshOpenDatasetsData);
}, [refreshDatasets, refreshOpenDatasetsData]);
useEffect(() => {
onDatasetsChange?.({
datasets,
refreshDatasets: refreshDatasetsAndData,
});
}, [datasets, onDatasetsChange, refreshDatasets, refreshDatasetsAndData]);
const {
value: isNewDatasetModalOpen,
setTrue: openNewDatasetModal,
setFalse: closeNewDatasetModal,
} = useBoolean(false);
const handleDatasetAdd = () => {
openNewDatasetModal();
};
const [newDatasetError, setNewDatasetError] = useState("");
const handleNewDatasetSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setNewDatasetError("");
const formElements = event.currentTarget;
const datasetName = formElements.datasetName.value;
if (datasetName.trim().length === 0) {
setNewDatasetError("Dataset name cannot be empty.");
return;
}
if (datasetName.includes(" ") || datasetName.includes(".")) {
setNewDatasetError("Dataset name cannot contain spaces or periods.");
return;
}
addDataset(datasetName)
.then(() => {
closeNewDatasetModal();
refreshDatasetsAndData();
});
};
const {
value: isRemoveDatasetModalOpen,
setTrue: openRemoveDatasetModal,
setFalse: closeRemoveDatasetModal,
} = useBoolean(false);
const [datasetToRemove, setDatasetToRemove] = useState<Dataset | null>(null);
const handleDatasetRemove = (dataset: Dataset) => {
setDatasetToRemove(dataset);
openRemoveDatasetModal();
};
const handleDatasetRemoveCancel = () => {
setDatasetToRemove(null);
closeRemoveDatasetModal();
};
const handleRemoveDatasetConfirm = (event: React.FormEvent<HTMLButtonElement>) => {
event.preventDefault();
if (datasetToRemove) {
removeDataset(datasetToRemove.id)
.then(() => {
closeRemoveDatasetModal();
setDatasetToRemove(null);
refreshDatasetsAndData();
});
}
};
const [datasetInProcessing, setProcessingDataset] = useState<Dataset | null>(null);
const handleAddFiles = (dataset: Dataset, event: ChangeEvent<HTMLInputElement>) => {
event.stopPropagation();
if (datasetInProcessing) {
return;
}
setProcessingDataset(dataset);
if (!event.target.files) {
return;
}
const files: File[] = Array.from(event.target.files);
if (!files.length) {
return;
}
return addData(dataset, files, useCloud)
.then(async () => {
await getDatasetData(dataset.id);
return cognifyDataset(dataset, useCloud)
.finally(() => {
setProcessingDataset(null);
});
});
};
const [dataToRemove, setDataToRemove] = useState<DataFile | null>(null);
const {
value: isRemoveDataModalOpen,
setTrue: openRemoveDataModal,
setFalse: closeRemoveDataModal,
} = useBoolean(false);
const handleDataRemove = (data: DataFile) => {
setDataToRemove(data);
openRemoveDataModal();
};
const handleDataRemoveCancel = () => {
setDataToRemove(null);
closeRemoveDataModal();
};
const handleDataRemoveConfirm = (event: React.FormEvent<HTMLButtonElement>) => {
event.preventDefault();
if (dataToRemove) {
removeDatasetData(dataToRemove.datasetId, dataToRemove.id)
.then(() => {
closeRemoveDataModal();
setDataToRemove(null);
refreshDatasetsAndData();
});
}
}
return (
<>
<Accordion
title={title || <span>Datasets</span>}
isOpen={isDatasetsPanelOpen}
openAccordion={openDatasetsPanel}
closeAccordion={closeDatasetsPanel}
tools={(
<div className="flex flex-row gap-4 items-center">
{tools}
<IconButton onClick={handleDatasetAdd}><PlusIcon /></IconButton>
</div>
)}
switchCaretPosition={switchCaretPosition}
className={className}
contentClassName={contentClassName}
>
<div className="flex flex-col">
{datasets.length === 0 && (
<div className="flex flex-row items-baseline-last text-sm text-gray-400 mt-2 px-2">
<span>No datasets here, add one by clicking +</span>
</div>
)}
{datasets.map((dataset) => {
return (
<Accordion
key={dataset.id}
title={(
<div className="flex flex-row gap-2 items-center py-1.5 cursor-pointer">
{datasetInProcessing?.id == dataset.id ? <LoadingIndicator /> : <DatasetIcon />}
<span className="text-xs">{dataset.name}</span>
</div>
)}
isOpen={openDatasets.has(dataset.id)}
openAccordion={() => toggleDataset(dataset.id)}
closeAccordion={() => toggleDataset(dataset.id)}
tools={(
<IconButton className="relative">
<PopupMenu>
<div className="flex flex-col gap-0.5">
<div className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer relative">
<input tabIndex={-1} type="file" multiple onChange={handleAddFiles.bind(null, dataset)} className="absolute w-full h-full cursor-pointer opacity-0" />
<span>add data</span>
</div>
</div>
<div className="flex flex-col gap-0.5 items-start">
<div onClick={() => handleDatasetRemove(dataset)} className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer">delete</div>
</div>
</PopupMenu>
</IconButton>
)}
className="first:pt-1.5"
switchCaretPosition={true}
>
<>
{dataset.data?.length === 0 && (
<div className="flex flex-row items-baseline-last text-sm text-gray-400 mt-2 px-2">
<span>No data in a dataset, add by clicking &quot;add data&quot; in a dropdown menu</span>
</div>
)}
{dataset.data?.map((data) => (
<div key={data.id} className="flex flex-row gap-2 items-center justify-between py-1.5 pl-6 last:pb-2.5">
<span className="text-xs">{data.name}</span>
<div>
<IconButton onClick={() => handleDataRemove(data)}><MinusIcon /></IconButton>
</div>
</div>
))}
</>
</Accordion>
);
})}
</div>
</Accordion>
<Modal isOpen={isNewDatasetModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Create a new dataset?</span>
<IconButton onClick={closeNewDatasetModal}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Please provide a name for the dataset being created.</div>
<form onSubmit={handleNewDatasetSubmit}>
<div className="max-w-md">
<Input name="datasetName" type="text" placeholder="Dataset name" required />
{newDatasetError && <span className="text-sm pl-4 text-gray-400">{newDatasetError}</span>}
</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={() => closeNewDatasetModal()}>cancel</GhostButton>
<CTAButton type="submit">create</CTAButton>
</div>
</form>
</div>
</Modal>
<Modal isOpen={isRemoveDatasetModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Delete <span className="text-indigo-600">{datasetToRemove?.name}</span> dataset?</span>
<IconButton onClick={handleDatasetRemoveCancel}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Are you sure you want to delete <span className="text-indigo-600">{datasetToRemove?.name}</span>? This action cannot be undone.</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={handleDatasetRemoveCancel}>cancel</GhostButton>
<CTAButton onClick={handleRemoveDatasetConfirm} type="submit">delete</CTAButton>
</div>
</div>
</Modal>
<Modal isOpen={isRemoveDataModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Delete <span className="text-indigo-600">{dataToRemove?.name}</span> data?</span>
<IconButton onClick={handleDataRemoveCancel}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Are you sure you want to delete <span className="text-indigo-600">{dataToRemove?.name}</span>? This action cannot be undone.</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={handleDataRemoveCancel}>cancel</GhostButton>
<CTAButton onClick={handleDataRemoveConfirm} type="submit">delete</CTAButton>
</div>
</div>
</Modal>
</>
);
}

View file

@ -0,0 +1,130 @@
import classNames from "classnames";
import { useCallback, useEffect } from "react";
import { fetch, isCloudEnvironment, useBoolean } from "@/utils";
import { checkCloudConnection } from "@/modules/cloud";
import { CaretIcon, CloseIcon, CloudIcon, LocalCogneeIcon } from "@/ui/Icons";
import { CTAButton, GhostButton, IconButton, Input, Modal } from "@/ui/elements";
import DatasetsAccordion, { DatasetsAccordionProps } from "./DatasetsAccordion";
type InstanceDatasetsAccordionProps = Omit<DatasetsAccordionProps, "title">;
export default function InstanceDatasetsAccordion({ onDatasetsChange }: InstanceDatasetsAccordionProps) {
const {
value: isLocalCogneeConnected,
setTrue: setLocalCogneeConnected,
} = useBoolean(false);
const {
value: isCloudCogneeConnected,
setTrue: setCloudCogneeConnected,
} = useBoolean(isCloudEnvironment());
const checkConnectionToCloudCognee = useCallback((apiKey?: string) => {
if (apiKey) {
fetch.setApiKey(apiKey);
}
return checkCloudConnection()
.then(setCloudCogneeConnected)
}, [setCloudCogneeConnected]);
useEffect(() => {
const checkConnectionToLocalCognee = () => {
fetch.checkHealth()
.then(setLocalCogneeConnected)
};
checkConnectionToLocalCognee();
}, [checkConnectionToCloudCognee, setCloudCogneeConnected, setLocalCogneeConnected]);
const {
value: isCloudConnectedModalOpen,
setTrue: openCloudConnectionModal,
setFalse: closeCloudConnectionModal,
} = useBoolean(false);
const handleCloudConnectionConfirm = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const apiKeyValue = event.currentTarget.apiKey.value;
checkConnectionToCloudCognee(apiKeyValue)
.then(() => {
closeCloudConnectionModal();
});
};
const isCloudEnv = isCloudEnvironment();
return (
<div className={classNames("flex flex-col", {
"flex-col-reverse": isCloudEnv,
})}>
<DatasetsAccordion
title={(
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-2">
<LocalCogneeIcon className="text-indigo-700" />
<span className="text-xs">local cognee</span>
</div>
</div>
)}
tools={isLocalCogneeConnected ? <span className="text-xs text-indigo-600">Connected</span> : <span className="text-xs text-gray-400">Not connected</span>}
switchCaretPosition={true}
className="pt-3 pb-1.5"
contentClassName="pl-4"
onDatasetsChange={!isCloudEnv ? onDatasetsChange : () => {}}
/>
{isCloudCogneeConnected ? (
<DatasetsAccordion
title={(
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-2">
<LocalCogneeIcon className="text-indigo-700" />
<span className="text-xs">cloud cognee</span>
</div>
</div>
)}
tools={<span className="text-xs text-indigo-600">Connected</span>}
switchCaretPosition={true}
className="pt-3 pb-1.5"
contentClassName="pl-4"
onDatasetsChange={isCloudEnv ? onDatasetsChange : () => {}}
useCloud={true}
/>
) : (
<button className="w-full flex flex-row items-center justify-between py-1.5 cursor-pointer pt-3" onClick={!isCloudCogneeConnected ? openCloudConnectionModal : () => {}}>
<div className="flex flex-row items-center gap-1.5">
<CaretIcon className="rotate-[-90deg]" />
<div className="flex flex-row items-center gap-2">
<CloudIcon color="#000000" />
<span className="text-xs">cloud cognee</span>
</div>
</div>
<span className="text-xs text-gray-400">Not connected</span>
</button>
)}
<Modal isOpen={isCloudConnectedModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Connect to cloud?</span>
<IconButton onClick={closeCloudConnectionModal}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Please provide your API key. You can find it on <a className="!text-indigo-600" href="https://platform.cognee.ai">our platform.</a></div>
<form onSubmit={handleCloudConnectionConfirm}>
<div className="max-w-md">
<Input name="apiKey" type="text" placeholder="cloud API key" required />
</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={() => closeCloudConnectionModal()}>cancel</GhostButton>
<CTAButton type="submit">connect</CTAButton>
</div>
</form>
</div>
</Modal>
</div>
);
}

View file

@ -0,0 +1,150 @@
"use client";
import { FormEvent, useCallback, useState } from "react";
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";
interface NotebooksAccordionProps {
notebooks: Notebook[];
addNotebook: (name: string) => Promise<Notebook>;
removeNotebook: (id: string) => Promise<void>;
openNotebook: (id: string) => void;
}
export default function NotebooksAccordion({
notebooks,
addNotebook,
removeNotebook,
openNotebook,
}: NotebooksAccordionProps) {
const {
value: isNotebookPanelOpen,
setTrue: openNotebookPanel,
setFalse: closeNotebookPanel,
} = useBoolean(true);
const {
value: isNotebookLoading,
setTrue: notebookLoading,
setFalse: notebookLoaded,
} = useBoolean(false);
// Notebook removal modal
const [notebookToRemove, setNotebookToRemove] = useState<Notebook | null>(null);
const handleNotebookRemove = (notebook: Notebook) => {
setNotebookToRemove(notebook);
openRemoveNotebookModal();
};
const {
value: isRemoveNotebookModalOpen,
setTrue: openRemoveNotebookModal,
setFalse: closeRemoveNotebookModal,
} = useBoolean(false);
const handleNotebookRemoveCancel = () => {
closeRemoveNotebookModal();
setNotebookToRemove(null);
};
const handleNotebookRemoveConfirm = () => {
notebookLoading();
removeNotebook(notebookToRemove!.id)
.finally(notebookLoaded)
.finally(closeRemoveNotebookModal);
setNotebookToRemove(null);
};
const handleNotebookAdd = useCallback((_: object, formEvent?: FormEvent<HTMLFormElement>) => {
if (!formEvent) {
return;
}
formEvent.preventDefault();
const formElements = formEvent.currentTarget;
const notebookName = formElements.notebookName.value.trim();
return addNotebook(notebookName)
}, [addNotebook]);
const {
isModalOpen: isNewNotebookModalOpen,
openModal: openNewNotebookModal,
closeModal: closeNewNotebookModal,
confirmAction: handleNewNotebookSubmit,
isActionLoading: isNewDatasetLoading,
} = useModal<Notebook | void>(false, handleNotebookAdd);
return (
<>
<Accordion
title={<span>Notebooks</span>}
isOpen={isNotebookPanelOpen}
openAccordion={openNotebookPanel}
closeAccordion={closeNotebookPanel}
tools={isNewDatasetLoading ? (
<LoadingIndicator />
) : (
<IconButton onClick={openNewNotebookModal}><PlusIcon /></IconButton>
)}
>
{notebooks.length === 0 && (
<div className="flex flex-row items-baseline-last text-sm text-gray-400 mt-2 px-2">
<span>No notebooks here, add one by clicking +</span>
</div>
)}
{notebooks.map((notebook: Notebook) => (
<div key={notebook.id} className="flex flex-row gap-2.5 items-center justify-between py-1.5 first:pt-3">
<button onClick={() => openNotebook(notebook.id)} className="flex flex-row gap-2 items-center cursor-pointer">
{isNotebookLoading ? <LoadingIndicator /> : <NotebookIcon />}
<span className="text-xs">{notebook.name}</span>
</button>
<div>
{notebook.deletable && <IconButton onClick={() => handleNotebookRemove(notebook)}><MinusIcon /></IconButton>}
</div>
</div>
))}
</Accordion>
<Modal isOpen={isNewNotebookModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Create a new notebook?</span>
<IconButton onClick={closeNewNotebookModal}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Please provide a name for the notebook being created.</div>
<form onSubmit={handleNewNotebookSubmit}>
<div className="max-w-md">
<Input name="notebookName" type="text" placeholder="Notebook name" required />
{/* {newDatasetError && <span className="text-sm pl-4 text-gray-400">{newDatasetError}</span>} */}
</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={() => closeNewNotebookModal()}>cancel</GhostButton>
<CTAButton type="submit">create</CTAButton>
</div>
</form>
</div>
</Modal>
<Modal isOpen={isRemoveNotebookModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Delete <span className="text-indigo-600">{notebookToRemove?.name}</span> notebook?</span>
<IconButton onClick={handleNotebookRemoveCancel}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Are you sure you want to delete <span className="text-indigo-600">{notebookToRemove?.name}</span>? This action cannot be undone.</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={handleNotebookRemoveCancel}>cancel</GhostButton>
<CTAButton onClick={handleNotebookRemoveConfirm} type="submit">delete</CTAButton>
</div>
</div>
</Modal>
</>
);
}

View file

@ -0,0 +1,11 @@
"use server";
import Dashboard from "./Dashboard";
export default async function Page() {
const accessToken = "";
return (
<Dashboard accessToken={accessToken} />
);
}

View file

@ -1,3 +1,3 @@
export { default } from "./(graph)/GraphView";
export { default } from "./dashboard/page";
export const dynamic = "force-dynamic";
// export const dynamic = "force-dynamic";

View file

@ -0,0 +1,165 @@
import Link from "next/link";
import { BackIcon, CheckIcon } from "@/ui/Icons";
import { CTAButton, NeutralButton } from "@/ui/elements";
import Header from "@/ui/Layout/Header";
export default function Plan() {
return (
<div className="bg-gray-200 h-full max-w-[1920px] mx-auto">
<video
autoPlay
loop
muted
playsInline
className="fixed inset-0 z-0 object-cover w-full h-full"
>
<source src="/videos/background-video-blur.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<Header />
<div className="relative flex flex-row items-start justify-stretch gap-2.5">
<div className="flex-1/5 h-full">
<Link href="/dashboard" className="py-4 px-5 flex flex-row items-center gap-5">
<BackIcon />
<span>back</span>
</Link>
</div>
<div className="flex-3/5">
<div className="bg-[rgba(255,255,255,0.7)] rounded-xl px-5 py-4 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>Basic</div>
<div className="text-3xl 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>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>
<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>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>
<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="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>
</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>
</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>
</div>
<div className="pt-4 pb-14 mb-2.5">
<NeutralButton className="w-full">Try for free</NeutralButton>
</div>
<div className="pt-4 pb-14 mb-2.5">
<CTAButton className="w-full">Talk to us</CTAButton>
</div>
<div className="pt-4 pb-14 mb-2.5">
<NeutralButton className="w-full">Sign up for Cogwit Beta</NeutralButton>
</div>
</div>
<div className="grid grid-cols-4 py-4 px-5 mb-4">
<div>Feature Comparison</div>
<div className="text-center">Basic</div>
<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="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="border-b-[1px] border-b-gray-100 py-3">Deployment</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Self-hosted</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">On-premise</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Cloud</div>
<div className="border-b-[1px] border-b-gray-100 py-3">API Calls</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Limited</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Unlimited</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">10,000</div>
<div className="border-b-[1px] border-b-gray-100 py-3">Support</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Community</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Hands-on</div>
<div className="text-center border-b-[1px] border-b-gray-100 py-3">Priority</div>
<div className="py-3">SLA</div>
<div className="text-center py-3"></div>
<div className="text-center py-3">1 day</div>
<div className="text-center py-3">Standard</div>
</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>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>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>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>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>
</div>
</div>
<div className="flex-1/5 h-full text-center flex flex-col self-end mb-12 max-w-[1920px]">
<div className="fixed bottom-6 w-[calc(min(1920px,100%)/5)] mx-auto">
<div className="text-sm mb-2">Need a custom solution?</div>
<CTAButton className="w-full">Contact us</CTAButton>
</div>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1 @@
export { default } from "./Plan";

View file

@ -0,0 +1,6 @@
import fetch from "@/utils/fetch";
export default function getUser() {
return fetch("/v1/auth/me")
.then((response) => response.json());
}

View file

@ -0,0 +1,2 @@
export { type User } from "./types";
export { default as useAuthenticatedUser } from "./useAuthenticatedUser";

View file

@ -0,0 +1,6 @@
export interface User {
id: string;
name: string;
email: string;
picture: string;
}

View file

@ -0,0 +1,17 @@
import { useEffect, useState } from "react";
import { fetch } from "@/utils";
import { User } from "./types";
export default function useAuthenticatedUser() {
const [user, setUser] = useState<User>();
useEffect(() => {
if (!user) {
fetch("/v1/auth/me")
.then((response) => response.json())
.then((data) => setUser(data));
}
}, [user]);
return { user };
}

View file

@ -0,0 +1,7 @@
import { fetch } from "@/utils";
export default function checkCloudConnection() {
return fetch("/v1/checks/connection", {
method: "POST",
});
}

View file

@ -0,0 +1,2 @@
export { default as syncData } from "./syncData";
export { default as checkCloudConnection } from "./checkCloudConnection";

View file

@ -0,0 +1,11 @@
import { fetch } from "@/utils";
export default function syncData(datasetId?: string) {
return fetch("/v1/sync", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
...(datasetId ? { body: JSON.stringify({ datasetId }) } : { body: "{}" }),
});
}

View file

@ -1,13 +1,13 @@
import { fetch } from "@/utils";
import getDatasetGraph from "./getDatasetGraph";
// import getDatasetGraph from "./getDatasetGraph";
import { Dataset } from "../ingestion/useDatasets";
interface GraphData {
nodes: { id: string; label: string; properties?: object }[];
edges: { source: string; target: string; label: string }[];
}
// interface GraphData {
// nodes: { id: string; label: string; properties?: object }[];
// edges: { source: string; target: string; label: string }[];
// }
export default async function cognifyDataset(dataset: Dataset, onUpdate: (data: GraphData) => void) {
export default async function cognifyDataset(dataset: Dataset, useCloud: boolean = false) {
// const data = await (
return fetch("/v1/cognify", {
method: "POST",
@ -18,17 +18,17 @@ export default async function cognifyDataset(dataset: Dataset, onUpdate: (data:
datasetIds: [dataset.id],
runInBackground: false,
}),
})
.then((response) => response.json())
.then(() => {
return getDatasetGraph(dataset)
.then((data) => {
onUpdate({
nodes: data.nodes,
edges: data.edges,
});
});
});
}, useCloud)
.then((response) => response.json());
// .then(() => {
// return getDatasetGraph(dataset)
// .then((data) => {
// onUpdate({
// nodes: data.nodes,
// edges: data.edges,
// });
// });
// });
// )
// const websocket = new WebSocket(`ws://localhost:8000/api/v1/cognify/subscribe/${data.pipeline_run_id}`);

View file

@ -1,12 +1,12 @@
import { fetch } from "@/utils";
export default function createDataset(dataset: { name: string }) {
export default function createDataset(dataset: { name: string }, useCloud = false) {
return fetch(`/v1/datasets/`, {
method: "POST",
body: JSON.stringify(dataset),
headers: {
"Content-Type": "application/json",
}
})
},
}, useCloud)
.then((response) => response.json());
}

View file

@ -1,19 +1,35 @@
import { fetch } from "@/utils";
export default function addData(dataset: { id?: string, name?: string }, files: File[]) {
const formData = new FormData();
files.forEach((file) => {
formData.append("data", file, file.name);
})
if (dataset.id) {
formData.append("datasetId", dataset.id);
}
if (dataset.name) {
formData.append("datasetName", dataset.name);
}
export default async function addData(dataset: { id?: string, name?: string }, files: File[], useCloud = false) {
if (useCloud) {
const data = {
text_data: await Promise.all(files.map(async (file) => file.text())),
datasetId: dataset.id,
datasetName: dataset.name,
};
return fetch("/v1/add", {
method: "POST",
body: formData,
}).then((response) => response.json());
return fetch("/v1/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}, true).then((response) => response.json());
} else {
const formData = new FormData();
files.forEach((file) => {
formData.append("data", file, file.name);
})
if (dataset.id) {
formData.append("datasetId", dataset.id);
}
if (dataset.name) {
formData.append("datasetName", dataset.name);
}
return fetch("/v1/add", {
method: "POST",
body: formData,
}).then((response) => response.json());
}
}

View file

@ -5,6 +5,7 @@ export interface DataFile {
id: string;
name: string;
file: File;
datasetId: string;
}
const useData = () => {
@ -16,6 +17,7 @@ const useData = () => {
id: v4(),
name: file.name,
file,
datasetId: "",
}))
);
}, []);

View file

@ -1,7 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { v4 } from 'uuid';
import { DataFile } from './useData';
import { useCallback, useState } from 'react';
import { fetch } from '@/utils';
import { DataFile } from './useData';
import createDataset from "../datasets/createDataset";
export interface Dataset {
id: string;
@ -10,91 +11,129 @@ export interface Dataset {
status: string;
}
function useDatasets() {
function useDatasets(useCloud = false) {
const [datasets, setDatasets] = useState<Dataset[]>([]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const statusTimeout = useRef<any>(null);
// const statusTimeout = useRef<any>(null);
const fetchDatasetStatuses = useCallback((datasets: Dataset[]) => {
fetch(
`/v1/datasets/status?dataset=${datasets.map(d => d.id).join('&dataset=')}`,
{
headers: {
"Content-Type": "application/json",
},
},
)
.then((response) => response.json())
.then((statuses) => setDatasets(
(datasets) => (
datasets.map((dataset) => ({
...dataset,
status: statuses[dataset.id]
}))
)));
}, []);
// const fetchDatasetStatuses = useCallback((datasets: Dataset[]) => {
// fetch(
// `/v1/datasets/status?dataset=${datasets.map(d => d.id).join('&dataset=')}`,
// {
// headers: {
// "Content-Type": "application/json",
// },
// },
// useCloud,
// )
// .then((response) => response.json())
// .then((statuses) => setDatasets(
// (datasets) => (
// datasets.map((dataset) => ({
// ...dataset,
// status: statuses[dataset.id]
// }))
// )));
// }, [useCloud]);
const checkDatasetStatuses = useCallback((datasets: Dataset[]) => {
fetchDatasetStatuses(datasets);
// const checkDatasetStatuses = useCallback((datasets: Dataset[]) => {
// fetchDatasetStatuses(datasets);
if (statusTimeout.current !== null) {
clearTimeout(statusTimeout.current);
}
// if (statusTimeout.current !== null) {
// clearTimeout(statusTimeout.current);
// }
statusTimeout.current = setTimeout(() => {
checkDatasetStatuses(datasets);
}, 50000);
}, [fetchDatasetStatuses]);
// statusTimeout.current = setTimeout(() => {
// checkDatasetStatuses(datasets);
// }, 50000);
// }, [fetchDatasetStatuses]);
useEffect(() => {
return () => {
if (statusTimeout.current !== null) {
clearTimeout(statusTimeout.current);
statusTimeout.current = null;
}
};
}, []);
// useEffect(() => {
// return () => {
// if (statusTimeout.current !== null) {
// clearTimeout(statusTimeout.current);
// statusTimeout.current = null;
// }
// };
// }, []);
const addDataset = useCallback((datasetName: string) => {
setDatasets((datasets) => [
...datasets,
{
id: v4(),
name: datasetName,
data: [],
status: 'DATASET_INITIALIZED',
}
]);
}, []);
return createDataset({ name: datasetName }, useCloud)
.then((dataset) => {
setDatasets((datasets) => [
...datasets,
dataset,
]);
});
}, [useCloud]);
const removeDataset = useCallback((datasetId: string) => {
setDatasets((datasets) =>
datasets.filter((dataset) => dataset.id !== datasetId)
);
}, []);
return fetch(`/v1/datasets/${datasetId}`, {
method: 'DELETE',
}, useCloud)
.then(() => {
setDatasets((datasets) =>
datasets.filter((dataset) => dataset.id !== datasetId)
);
});
}, [useCloud]);
const fetchDatasets = useCallback(() => {
return fetch('/v1/datasets', {
headers: {
"Content-Type": "application/json",
},
})
}, useCloud)
.then((response) => response.json())
.then((datasets) => {
setDatasets(datasets);
if (datasets.length > 0) {
checkDatasetStatuses(datasets);
}
// if (datasets.length > 0) {
// checkDatasetStatuses(datasets);
// }
return datasets;
})
.catch((error) => {
console.error('Error fetching datasets:', error);
});
}, [checkDatasetStatuses]);
}, [useCloud]);
return { datasets, addDataset, removeDataset, refreshDatasets: fetchDatasets };
const getDatasetData = useCallback((datasetId: string) => {
return fetch(`/v1/datasets/${datasetId}/data`, {}, useCloud)
.then((response) => response.json())
.then((data) => {
const datasetIndex = datasets.findIndex((dataset) => dataset.id === datasetId);
if (datasetIndex >= 0) {
setDatasets((datasets) => [
...datasets.slice(0, datasetIndex),
{
...datasets[datasetIndex],
data,
},
...datasets.slice(datasetIndex + 1),
]);
}
return data;
});
}, [datasets, useCloud]);
const removeDatasetData = useCallback((datasetId: string, dataId: string) => {
return fetch(`/v1/datasets/${datasetId}/data/${dataId}`, {
method: 'DELETE',
}, useCloud);
}, [useCloud]);
return {
datasets,
addDataset,
removeDataset,
getDatasetData,
removeDatasetData,
refreshDatasets: fetchDatasets,
};
};
export default useDatasets;

View file

@ -0,0 +1,135 @@
import { useCallback, useState } from "react";
import { fetch, isCloudEnvironment } from "@/utils";
import { Cell, Notebook } from "@/ui/elements/Notebook/types";
function useNotebooks() {
const [notebooks, setNotebooks] = useState<Notebook[]>([]);
const addNotebook = useCallback((notebookName: string) => {
return fetch("/v1/notebooks", {
body: JSON.stringify({ name: notebookName }),
method: "POST",
headers: {
"Content-Type": "application/json",
},
}, isCloudEnvironment())
.then((response) => response.json())
.then((notebook) => {
setNotebooks((notebooks) => [
...notebooks,
notebook,
]);
return notebook;
});
}, []);
const removeNotebook = useCallback((notebookId: string) => {
return fetch(`/v1/notebooks/${notebookId}`, {
method: "DELETE",
}, isCloudEnvironment())
.then(() => {
setNotebooks((notebooks) =>
notebooks.filter((notebook) => notebook.id !== notebookId)
);
});
}, []);
const fetchNotebooks = useCallback(() => {
return fetch("/v1/notebooks", {
headers: {
"Content-Type": "application/json",
},
}, isCloudEnvironment())
.then((response) => response.json())
.then((notebooks) => {
setNotebooks(notebooks);
return notebooks;
})
.catch((error) => {
console.error("Error fetching notebooks:", error);
throw error
});
}, []);
const updateNotebook = useCallback((updatedNotebook: Notebook) => {
setNotebooks((existingNotebooks) =>
existingNotebooks.map((notebook) =>
notebook.id === updatedNotebook.id
? updatedNotebook
: notebook
)
);
}, []);
const saveNotebook = useCallback((notebook: Notebook) => {
return fetch(`/v1/notebooks/${notebook.id}`, {
body: JSON.stringify({
name: notebook.name,
cells: notebook.cells,
}),
method: "PUT",
headers: {
"Content-Type": "application/json",
},
}, isCloudEnvironment())
.then((response) => response.json())
}, []);
const runCell = useCallback((notebook: Notebook, cell: Cell, cogneeInstance: string) => {
setNotebooks((existingNotebooks) =>
existingNotebooks.map((existingNotebook) =>
existingNotebook.id === notebook.id ? {
...existingNotebook,
cells: existingNotebook.cells.map((existingCell) =>
existingCell.id === cell.id ? {
...existingCell,
result: undefined,
error: undefined,
} : existingCell
),
} : notebook
)
);
return fetch(`/v1/notebooks/${notebook.id}/${cell.id}/run`, {
body: JSON.stringify({
content: cell.content,
}),
method: "POST",
headers: {
"Content-Type": "application/json",
},
}, cogneeInstance === "cloud")
.then((response) => response.json())
.then((response) => {
setNotebooks((existingNotebooks) =>
existingNotebooks.map((existingNotebook) =>
existingNotebook.id === notebook.id ? {
...existingNotebook,
cells: existingNotebook.cells.map((existingCell) =>
existingCell.id === cell.id ? {
...existingCell,
result: response.result,
error: response.error,
} : existingCell
),
} : notebook
)
);
});
}, []);
return {
notebooks,
addNotebook,
saveNotebook,
updateNotebook,
removeNotebook,
refreshNotebooks: fetchNotebooks,
runCell,
};
};
export default useNotebooks;

View file

@ -3,7 +3,7 @@
width: 1rem;
height: 1rem;
border-radius: 50%;
border: 0.18rem solid white;
border: 0.18rem solid var(--color-indigo-600);;
border-top-color: transparent;
border-bottom-color: transparent;
animation: spin 2s linear infinite;

View file

@ -1,4 +1,4 @@
export default function SearchIcon({ width = 24, height = 24, color = 'currentColor', className = '' }) {
export default function AddIcon({ width = 24, height = 24, color = 'currentColor', className = '' }) {
return (
<svg width={width} height={height} viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M24.9999 46L24.9999 4M46.0049 25.005L4.00488 25.005" stroke={color} strokeWidth="8" strokeLinecap="round"/>

View file

@ -0,0 +1,8 @@
export default function BackIcon({ width = 16, height = 16, color = "#17191C", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99992 12.6666L3.33325 7.99998L7.99992 3.33331" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M12.6666 8H3.33325" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -1,8 +1,7 @@
export default function CaretIcon({ width = 50, height = 36, color = "currentColor", className = "" }) {
export default function CaretIcon({ width = 17, height = 16, color = "#000000", className = "" }) {
return (
<svg width={width} height={height} viewBox="0 0 50 36" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M4 32L25 5" stroke={color} strokeWidth="8" strokeLinecap="round"/>
<path d="M46 32L25 5" stroke={color} strokeWidth="8" strokeLinecap="round"/>
<svg className={className} width={width} height={height} viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.04877 6L8.09755 10L12.1463 6" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,7 @@
export default function CheckIcon({ width = 17, height = 18, color = "#5C10F4", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.1693 4.60767L6.41823 12.3587L2.89502 8.83551" stroke={color} strokeWidth="1.40928" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,8 @@
export default function CloseIcon({ width = 29, height = 29, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.02429 20.0913L20.5737 8.5419" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M9.02441 8.54199L20.5738 20.0914" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,7 @@
export default function CloudIcon({ width = 16, height = 12, color = "#5C10F4", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 10.6666H5.99994C5.13452 10.6664 4.28621 10.4256 3.54979 9.97096C2.81338 9.51636 2.21789 8.86595 1.82986 8.09239C1.44183 7.31883 1.27654 6.45261 1.35247 5.59053C1.4284 4.72844 1.74256 3.90445 2.25984 3.21063C2.77712 2.51682 3.47714 1.98051 4.28168 1.66164C5.08622 1.34277 5.96357 1.2539 6.81571 1.40496C7.66785 1.55602 8.4612 1.94106 9.1071 2.51705C9.753 3.09304 10.226 3.8373 10.4733 4.66665H11.6666C12.4623 4.66665 13.2253 4.98272 13.7879 5.54533C14.3505 6.10794 14.6666 6.871 14.6666 7.66665C14.6666 8.4623 14.3505 9.22536 13.7879 9.78797C13.2253 10.3506 12.4623 10.6666 11.6666 10.6666Z" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,7 @@
export default function CogneeIcon({ width = 21, height = 24, color="#6510F4", className="" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 21 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M10.2423 2.05148C10.1507 2.15398 10.083 2.30864 10.083 2.49201V21.508C10.083 22.7797 9.14692 24 7.77076 24C6.41621 24 5.45848 22.7869 5.45848 21.508V6.6326C5.45848 6.21738 5.18157 6.05752 5.04152 6.05752C4.97012 6.05752 4.87507 6.08986 4.78377 6.19205C4.69222 6.29455 4.62455 6.44919 4.62455 6.6326V15.9872C4.62455 17.2589 3.68844 18.4792 2.31227 18.4792C0.957707 18.4792 0 17.2661 0 15.9872V11.4632C0 10.0904 1.10659 8.97124 2.4639 8.97124C2.556 8.97124 2.64505 8.98455 2.72924 9.00931V6.6326C2.72924 5.35369 3.68695 4.14057 5.04152 4.14057C6.41768 4.14057 7.3538 5.3609 7.3538 6.6326V21.508C7.3538 21.6913 7.42147 21.846 7.51303 21.9485C7.60433 22.0507 7.69934 22.0831 7.77076 22.0831C7.91081 22.0831 8.18772 21.9232 8.18772 21.508V2.49201C8.18772 1.2131 9.14545 0 10.5 0C11.8762 0 12.8123 1.22033 12.8123 2.49201V21.508C12.8123 21.6913 12.8799 21.846 12.9715 21.9485C13.0628 22.0507 13.1579 22.0831 13.2292 22.0831C13.3693 22.0831 13.6462 21.9232 13.6462 21.508V6.6326C13.6462 5.35369 14.6039 4.14057 15.9585 4.14057C17.3346 4.14057 18.2708 5.3609 18.2708 6.6326V9.00931C18.355 8.98455 18.444 8.97124 18.5361 8.97124C19.8934 8.97124 21 10.0904 21 11.4632V15.9872C21 17.2589 20.0639 18.4792 18.6877 18.4792C17.3332 18.4792 16.3754 17.2661 16.3754 15.9872V6.6326C16.3754 6.21738 16.0986 6.05752 15.9585 6.05752C15.8871 6.05752 15.7921 6.08986 15.7007 6.19205C15.6092 6.29455 15.5415 6.44919 15.5415 6.6326V21.508C15.5415 22.7797 14.6054 24 13.2292 24C11.8747 24 10.917 22.7869 10.917 21.508V2.49201C10.917 2.07679 10.6401 1.91693 10.5 1.91693C10.4286 1.91693 10.3336 1.94928 10.2423 2.05148ZM18.2708 10.8501V15.9872C18.2708 16.1706 18.3384 16.3253 18.43 16.4278C18.5213 16.53 18.6163 16.5623 18.6877 16.5623C18.8278 16.5623 19.1047 16.4024 19.1047 15.9872V11.4632C19.1047 11.1492 18.8466 10.8882 18.5361 10.8882C18.444 10.8882 18.355 10.8749 18.2708 10.8501ZM2.72924 10.8501C2.64505 10.8749 2.556 10.8882 2.4639 10.8882C2.15334 10.8882 1.89531 11.1492 1.89531 11.4632V15.9872C1.89531 16.1706 1.96298 16.3253 2.05453 16.4278C2.14582 16.53 2.24088 16.5623 2.31227 16.5623C2.45235 16.5623 2.72924 16.4024 2.72924 15.9872V10.8501Z" fill={color}/>
</svg>
);
}

View file

@ -0,0 +1,9 @@
export default function DatasetIcon({ width = 16, height = 16, color = "#000000", className = '' }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99998 6.55042C10.932 6.55042 13.3088 5.53177 13.3088 4.27521C13.3088 3.01865 10.932 2 7.99998 2C5.068 2 2.69116 3.01865 2.69116 4.27521C2.69116 5.53177 5.068 6.55042 7.99998 6.55042Z" stroke={color} strokeWidth="1.17679"/>
<path d="M2.69116 8.82568C2.69116 8.82568 2.69116 10.6027 2.69116 11.8593C2.69116 13.1159 5.06801 14.1345 7.99998 14.1345C10.932 14.1345 13.3088 13.1159 13.3088 11.8593C13.3088 11.2321 13.3088 8.82568 13.3088 8.82568" stroke={color} strokeWidth="1.17679" strokeLinecap="square"/>
<path d="M2.69116 4.27515C2.69116 4.27515 2.69116 6.81056 2.69116 8.06716C2.69116 9.32376 5.06801 10.3424 7.99998 10.3424C10.932 10.3424 13.3088 9.32376 13.3088 8.06716C13.3088 7.43996 13.3088 4.27515 13.3088 4.27515" stroke={color} strokeWidth="1.17679"/>
</svg>
);
}

View file

@ -0,0 +1,10 @@
export default function LocalCogneeIcon({ width = 16, height = 16, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6667 8H1.33334" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M3.63334 3.40663L1.33334 7.99996V12C1.33334 12.3536 1.47382 12.6927 1.72387 12.9428C1.97392 13.1928 2.31305 13.3333 2.66668 13.3333H13.3333C13.687 13.3333 14.0261 13.1928 14.2762 12.9428C14.5262 12.6927 14.6667 12.3536 14.6667 12V7.99996L12.3667 3.40663C12.2563 3.18448 12.0861 2.99754 11.8753 2.86681C11.6645 2.73608 11.4214 2.66676 11.1733 2.66663H4.82668C4.57862 2.66676 4.33552 2.73608 4.12471 2.86681C3.91389 2.99754 3.74373 3.18448 3.63334 3.40663Z" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M4 10.6666H4.00667" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M6.66666 10.6666H6.67332" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,9 @@
export default function AddIcon({ width = 16, height = 16, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="4" r="1" fill={color} />
<circle cx="8" cy="8" r="1" fill={color} />
<circle cx="8" cy="12" r="1" fill={color} />
</svg>
);
}

View file

@ -0,0 +1,7 @@
export default function MinusIcon({ width = 16, height = 16, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.09637 8H12.8675" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,8 @@
export default function NotebookIcon({ width = 16, height = 16, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2V14" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,7 @@
export default function PlayIcon({ width = 11, height = 14, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 11 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L10.3333 7L1 13V1Z" stroke={color} strokeWidth="1.33" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -0,0 +1,8 @@
export default function PlusIcon({ width = 16, height = 16, color = "#000000", className = "" }) {
return (
<svg className={className} width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.09637 8H12.8675" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8.48193 3.33331V12.6666" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -1,9 +1,8 @@
export default function SearchIcon({ width = 24, height = 24, color = 'currentColor', className = '' }) {
export default function SearchIcon({ width = 12, height = 12, color = "#D8D8D8", className = "" }) {
return (
<svg width={width} height={height} viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle cx="19.5" cy="19.5" r="17" stroke={color} strokeWidth="5"/>
<path d="M8 19.5C8 13.1487 13.1487 8 19.5 8" stroke={color}/>
<path d="M43.2782 48.9312C44.897 50.4344 47.428 50.3406 48.9312 48.7218C50.4344 47.103 50.3406 44.572 48.7218 43.0688L43.2782 48.9312ZM46 46L48.7218 43.0688L34.7218 30.0688L32 33L29.2782 35.9312L43.2782 48.9312L46 46Z" fill={color}/>
<svg className={className} width={width} height={height} viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 9.5C7.70914 9.5 9.5 7.70914 9.5 5.5C9.5 3.29086 7.70914 1.5 5.5 1.5C3.29086 1.5 1.5 3.29086 1.5 5.5C1.5 7.70914 3.29086 9.5 5.5 9.5Z" stroke={color} strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10.5 10.5L8.35001 8.34998" stroke={color} strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -1,7 +1,8 @@
export default function SettingsIcon({ width = 32, height = 33, color = "#E8EAED" }) {
export default function SettingsIcon({ width = 16, height = 17, color = "#000000" }) {
return (
<svg width={width} height={height} viewBox="0 0 54 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.2482 55.75L20.1336 46.8322C19.1495 46.5357 18.0853 46.0691 16.9408 45.4324C15.7964 44.7962 14.8231 44.1145 14.0209 43.3874L5.79611 46.8854L0.0449219 36.8646L7.15432 31.5042C7.06336 30.9472 6.98833 30.3731 6.92923 29.7822C6.86962 29.1912 6.83982 28.6169 6.83982 28.0594C6.83982 27.5414 6.86962 26.9969 6.92923 26.426C6.98833 25.8545 7.06336 25.2111 7.15432 24.4958L0.0449219 19.1354L5.79611 9.23329L13.9615 12.672C14.8824 11.9053 15.8786 11.2136 16.9501 10.5969C18.021 9.98023 19.0624 9.50385 20.0743 9.16777L21.2482 0.25H32.7522L33.8668 9.22713C35.0487 9.64235 36.0932 10.1187 37.0002 10.6562C37.9072 11.1938 38.8412 11.8657 39.8022 12.672L48.2043 9.23329L53.9555 19.1354L46.6087 24.6739C46.7788 25.31 46.8738 25.8941 46.8939 26.426C46.9134 26.9573 46.9232 27.482 46.9232 28C46.9232 28.4784 46.9034 28.9833 46.8638 29.5147C46.8242 30.0466 46.7333 30.69 46.5909 31.4449L53.819 36.8646L48.0678 46.8854L39.8022 43.328C38.8412 44.1343 37.8746 44.826 36.9023 45.4031C35.93 45.9802 34.9182 46.4368 33.8668 46.7729L32.7522 55.75H21.2482ZM23.9169 52.6667H29.9471L31.0856 44.3178C32.6391 43.9067 34.0374 43.3424 35.2805 42.625C36.5241 41.9076 37.7901 40.9243 39.0784 39.675L46.769 42.9542L49.8346 37.7125L43.0867 32.6427C43.3437 31.765 43.5138 30.9577 43.597 30.2208C43.6797 29.4833 43.7211 28.7431 43.7211 28C43.7211 27.2173 43.6797 26.4771 43.597 25.7792C43.5138 25.0819 43.3437 24.3141 43.0867 23.476L49.9533 18.2875L46.8877 13.0458L39.019 16.3427C38.0863 15.319 36.8599 14.3593 35.3398 13.4636C33.8203 12.5684 32.3824 11.9746 31.0263 11.6822L30.0835 3.33333H23.9346L22.9741 11.6229C21.4206 11.9548 19.9925 12.4895 18.6898 13.227C17.3876 13.9639 16.0921 14.9768 14.8033 16.2656L7.11269 13.0458L4.04709 18.2875L10.7356 23.2802C10.4787 23.9719 10.2988 24.7229 10.196 25.5333C10.0932 26.3437 10.0419 27.1857 10.0419 28.0594C10.0419 28.842 10.0932 29.6187 10.196 30.3896C10.2988 31.1604 10.4589 31.9115 10.6763 32.6427L4.04709 37.7125L7.11269 42.9542L14.7439 39.7167C15.9536 40.9382 17.2096 41.9177 18.5118 42.6551C19.8145 43.392 21.2822 43.966 22.9148 44.3771L23.9169 52.6667ZM26.9169 35.7083C29.0676 35.7083 30.8901 34.9611 32.3845 33.4668C33.8783 31.9729 34.6253 30.1506 34.6253 28C34.6253 25.8494 33.8783 24.0271 32.3845 22.5333C30.8901 21.0389 29.0676 20.2917 26.9169 20.2917C24.755 20.2917 22.9297 21.0389 21.4409 22.5333C19.9527 24.0271 19.2086 25.8494 19.2086 28C19.2086 30.1506 19.9527 31.9729 21.4409 33.4668C22.9297 34.9611 24.755 35.7083 26.9169 35.7083Z" fill={color} />
<svg width={width} height={height} viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.14667 1.35278H7.85333C7.49971 1.35278 7.16057 1.49326 6.91053 1.74331C6.66048 1.99336 6.52 2.33249 6.52 2.68612V2.80612C6.51976 3.03993 6.45804 3.26958 6.34103 3.47201C6.22401 3.67444 6.05583 3.84254 5.85333 3.95945L5.56667 4.12612C5.36398 4.24314 5.13405 4.30475 4.9 4.30475C4.66595 4.30475 4.43603 4.24314 4.23333 4.12612L4.13333 4.07278C3.82738 3.89629 3.46389 3.84841 3.12267 3.93966C2.78145 4.0309 2.49037 4.25381 2.31333 4.55945L2.16667 4.81278C1.99018 5.11874 1.9423 5.48223 2.03354 5.82345C2.12478 6.16467 2.34769 6.45575 2.65333 6.63278L2.75333 6.69945C2.95485 6.81579 3.12241 6.98284 3.23937 7.184C3.35632 7.38517 3.4186 7.61343 3.42 7.84612V8.18612C3.42093 8.42106 3.35977 8.65209 3.2427 8.85579C3.12563 9.05949 2.95681 9.22864 2.75333 9.34612L2.65333 9.40612C2.34769 9.58315 2.12478 9.87423 2.03354 10.2155C1.9423 10.5567 1.99018 10.9202 2.16667 11.2261L2.31333 11.4795C2.49037 11.7851 2.78145 12.008 3.12267 12.0992C3.46389 12.1905 3.82738 12.1426 4.13333 11.9661L4.23333 11.9128C4.43603 11.7958 4.66595 11.7342 4.9 11.7342C5.13405 11.7342 5.36398 11.7958 5.56667 11.9128L5.85333 12.0795C6.05583 12.1964 6.22401 12.3645 6.34103 12.5669C6.45804 12.7693 6.51976 12.999 6.52 13.2328V13.3528C6.52 13.7064 6.66048 14.0455 6.91053 14.2956C7.16057 14.5456 7.49971 14.6861 7.85333 14.6861H8.14667C8.50029 14.6861 8.83943 14.5456 9.08948 14.2956C9.33953 14.0455 9.48 13.7064 9.48 13.3528V13.2328C9.48024 12.999 9.54196 12.7693 9.65898 12.5669C9.77599 12.3645 9.94418 12.1964 10.1467 12.0795L10.4333 11.9128C10.636 11.7958 10.866 11.7342 11.1 11.7342C11.3341 11.7342 11.564 11.7958 11.7667 11.9128L11.8667 11.9661C12.1726 12.1426 12.5361 12.1905 12.8773 12.0992C13.2186 12.008 13.5096 11.7851 13.6867 11.4795L13.8333 11.2194C14.0098 10.9135 14.0577 10.55 13.9665 10.2088C13.8752 9.86756 13.6523 9.57648 13.3467 9.39945L13.2467 9.34612C13.0432 9.22864 12.8744 9.05949 12.7573 8.85579C12.6402 8.65209 12.5791 8.42106 12.58 8.18612V7.85278C12.5791 7.61784 12.6402 7.38682 12.7573 7.18311C12.8744 6.97941 13.0432 6.81026 13.2467 6.69278L13.3467 6.63278C13.6523 6.45575 13.8752 6.16467 13.9665 5.82345C14.0577 5.48223 14.0098 5.11874 13.8333 4.81278L13.6867 4.55945C13.5096 4.25381 13.2186 4.0309 12.8773 3.93966C12.5361 3.84841 12.1726 3.89629 11.8667 4.07278L11.7667 4.12612C11.564 4.24314 11.3341 4.30475 11.1 4.30475C10.866 4.30475 10.636 4.24314 10.4333 4.12612L10.1467 3.95945C9.94418 3.84254 9.77599 3.67444 9.65898 3.47201C9.54196 3.26958 9.48024 3.03993 9.48 2.80612V2.68612C9.48 2.33249 9.33953 1.99336 9.08948 1.74331C8.83943 1.49326 8.50029 1.35278 8.14667 1.35278Z" stroke={color} strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8 10.0195C9.10457 10.0195 10 9.12404 10 8.01947C10 6.9149 9.10457 6.01947 8 6.01947C6.89543 6.01947 6 6.9149 6 8.01947C6 9.12404 6.89543 10.0195 8 10.0195Z" stroke="black" strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}

View file

@ -1,7 +1,19 @@
export { default as AddIcon } from './AddIcon';
export { default as CaretIcon } from './CaretIcon';
export { default as SearchIcon } from './SearchIcon';
export { default as DeleteIcon } from './DeleteIcon';
export { default as GithubIcon } from './GitHubIcon';
export { default as DiscordIcon } from './DiscordIcon';
export { default as SettingsIcon } from './SettingsIcon';
export { default as AddIcon } from "./AddIcon";
export { default as BackIcon } from "./BackIcon";
export { default as PlayIcon } from "./PlayIcon";
export { default as MenuIcon } from "./MenuIcon";
export { default as PlusIcon } from "./PlusIcon";
export { default as MinusIcon } from "./MinusIcon";
export { default as CloseIcon } from "./CloseIcon";
export { default as CheckIcon } from "./CheckIcon";
export { default as CaretIcon } from "./CaretIcon";
export { default as CloudIcon } from "./CloudIcon";
export { default as SearchIcon } from "./SearchIcon";
export { default as DeleteIcon } from "./DeleteIcon";
export { default as GithubIcon } from "./GitHubIcon";
export { default as CogneeIcon } from "./CogneeIcon";
export { default as DiscordIcon } from "./DiscordIcon";
export { default as DatasetIcon } from "./DatasetIcon";
export { default as SettingsIcon } from "./SettingsIcon";
export { default as NotebookIcon } from "./NotebookIcon";
export { default as LocalCogneeIcon } from "./LocalCogneeIcon";

View file

@ -0,0 +1,79 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { useBoolean } from "@/utils";
import { CloseIcon, CloudIcon, CogneeIcon } from "../Icons";
import { CTAButton, GhostButton, IconButton, Modal } from "../elements";
import syncData from "@/modules/cloud/syncData";
interface HeaderProps {
user?: {
name: string;
email: string;
picture: string;
};
}
export default function Header({ user }: HeaderProps) {
const {
value: isSyncModalOpen,
setTrue: openSyncModal,
setFalse: closeSyncModal,
} = useBoolean(false);
const handleDataSyncConfirm = () => {
syncData()
.finally(() => {
closeSyncModal();
});
};
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">
<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">
<CloudIcon />
<div>Sync</div>
</GhostButton>
<a href="/plan">
<GhostButton className="text-indigo-700 pl-4 pr-4">Premium</GhostButton>
</a>
{/* <div className="px-2 py-2 mr-3">
<SettingsIcon />
</div> */}
<Link href="/account" className="bg-indigo-600 w-8 h-8 rounded-full overflow-hidden">
{user?.picture ? (
<Image width="32" height="32" alt="Name of the user" src={user.picture} />
) : (
<div className="w-8 h-8 rounded-full text-white flex items-center justify-center">
{user?.email?.charAt(0) || "C"}
</div>
)}
</Link>
</div>
</header>
<Modal isOpen={isSyncModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Sync local datasets with cloud datasets?</span>
<IconButton onClick={closeSyncModal}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Are you sure you want to sync local datasets to cloud?</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={closeSyncModal}>cancel</GhostButton>
<CTAButton onClick={handleDataSyncConfirm} type="submit">confirm</CTAButton>
</div>
</div>
</Modal>
</>
);
}

View file

@ -1 +1,2 @@
export { default as Divider } from './Divider/Divider';
export { default as Divider } from "./Divider/Divider";
export { default as Header } from "./Header";

View file

@ -0,0 +1,45 @@
import classNames from "classnames";
import { CaretIcon } from "../Icons";
export interface AccordionProps {
isOpen: boolean;
title: React.ReactNode;
openAccordion: () => void;
closeAccordion: () => void;
tools?: React.ReactNode;
children: React.ReactNode;
className?: string;
contentClassName?: string;
switchCaretPosition?: boolean;
}
export default function Accordion({ title, tools, children, isOpen, openAccordion, closeAccordion, className, contentClassName, switchCaretPosition = false }: AccordionProps) {
return (
<div className={classNames("flex flex-col", className)}>
<div className="flex flex-row justify-between items-center">
<button className={classNames("flex flex-row items-center pr-2", switchCaretPosition ? "gap-1.5" : "gap-4")} onClick={isOpen ? closeAccordion : openAccordion}>
{switchCaretPosition ? (
<>
<CaretIcon className={classNames("transition-transform", isOpen ? "rotate-360" : "rotate-270")} />
{title}
</>
) : (
<>
{title}
<CaretIcon className={classNames("transition-transform", isOpen ? "rotate-0" : "rotate-180")} />
</>
)}
</button>
{tools}
</div>
{isOpen && (
<div className={classNames("grid transition-[grid-template-rows] duration-300 ease-in-out [grid-template-rows:0fr]", contentClassName, {
"[grid-template-rows:1fr]": isOpen,
})}>
{children}
</div>
)}
</div>
);
}

View file

@ -1,8 +1,8 @@
import classNames from 'classnames';
import classNames from "classnames";
import { ButtonHTMLAttributes } from "react";
export default function CTAButton({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button className={classNames("flex flex-row justify-center items-center gap-2 cursor-pointer rounded-3xl bg-indigo-600 px-4 py-3 text-white hover:bg-indigo-500 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-indigo-600 px-10 h-8 text-white hover:bg-indigo-500 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 classNames from 'classnames';
import classNames from "classnames";
import { ButtonHTMLAttributes } from "react";
export default function CTAButton({ 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-4 py-3 text-white shadow-xs border-1 hover:bg-gray-400 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

@ -0,0 +1,14 @@
import classNames from "classnames";
import { ButtonHTMLAttributes } from "react";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
as?: React.ElementType;
}
export default function IconButton({ as, children, className, ...props }: ButtonProps) {
const Element = as || "button";
return (
<Element className={classNames("flex flex-row justify-center items-center gap-2 cursor-pointer rounded-xl bg-transparent p-[0.5rem] m-[-0.5rem] text-black hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", className)} {...props}>{children}</Element>
);
}

View file

@ -3,6 +3,6 @@ import { InputHTMLAttributes } from "react"
export default function Input({ className, ...props }: InputHTMLAttributes<HTMLInputElement>) {
return (
<input className={classNames("block w-full rounded-md bg-white px-4 py-4 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600", className)} {...props} />
<input className={classNames("block w-full rounded-3xl bg-white px-4 h-10 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600", className)} {...props} />
)
}

View file

@ -5,7 +5,7 @@ interface ModalProps {
export default function Modal({ isOpen, children }: ModalProps) {
return isOpen && (
<div className="fixed top-0 left-0 right-0 bottom-0 backdrop-blur-lg z-1 flex items-center justify-center">
<div className="fixed top-0 left-0 right-0 bottom-0 backdrop-blur-lg z-20 flex items-center justify-center">
{children}
</div>
);

View file

@ -0,0 +1,3 @@
export { default as Modal } from "./Modal";
export { default as useModal } from "./useModal";

View file

@ -0,0 +1,49 @@
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>({});
const [isActionLoading, setLoading] = useState(false);
const {
value: isModalOpen,
setTrue: openModalInternal,
setFalse: closeModalInternal,
} = useBoolean(initiallyOpen || false);
const openModal = useCallback((state?: object) => {
if (state) {
setModalState(state);
}
openModalInternal();
}, [openModalInternal]);
const closeModal = useCallback(() => {
closeModalInternal();
setModalState({});
}, [closeModalInternal]);
const confirmAction = useCallback((event?: FormEvent<HTMLFormElement>) => {
if (confirmCallback) {
setLoading(true);
const maybePromise = confirmCallback(modalState, event);
if (maybePromise instanceof Promise) {
return maybePromise
.finally(closeModal)
.finally(() => setLoading(false));
} else {
return maybePromise; // Not a promise.
}
}
}, [closeModal, confirmCallback, modalState]);
return {
isModalOpen,
openModal,
closeModal,
confirmAction,
isActionLoading,
};
}

View file

@ -1,8 +1,8 @@
import classNames from 'classnames';
import classNames from "classnames";
import { ButtonHTMLAttributes } from "react";
export default function CTAButton({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
export default function NeutralButton({ 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-4 py-3 text-white shadow-xs border-1 border-white hover:bg-gray-400 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 border-1 border-indigo-600 hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", className)} {...props}>{children}</button>
);
}

View file

@ -0,0 +1,361 @@
"use client";
import { v4 as uuid4 } from "uuid";
import classNames from "classnames";
import { Fragment, MutableRefObject, useCallback, useEffect, useRef, useState } from "react";
import { CaretIcon, PlusIcon } from "@/ui/Icons";
import { IconButton, PopupMenu, TextArea } from "@/ui/elements";
import { GraphControlsAPI } from "@/app/(graph)/GraphControls";
import GraphVisualization, { GraphVisualizationAPI } from "@/app/(graph)/GraphVisualization";
import NotebookCellHeader from "./NotebookCellHeader";
import { Cell, Notebook as NotebookType } from "./types";
interface NotebookProps {
notebook: NotebookType;
runCell: (notebook: NotebookType, cell: Cell, cogneeInstance: string) => Promise<void>;
updateNotebook: (updatedNotebook: NotebookType) => void;
saveNotebook: (notebook: NotebookType) => void;
}
export default function Notebook({ notebook, updateNotebook, saveNotebook, runCell }: NotebookProps) {
const saveCells = useCallback(() => {
saveNotebook(notebook);
}, [notebook, saveNotebook]);
useEffect(() => {
window.addEventListener("beforeunload", saveCells);
return () => {
window.removeEventListener("beforeunload", saveCells);
};
}, [saveCells]);
useEffect(() => {
if (notebook.cells.length === 0) {
const newCell: Cell = {
id: uuid4(),
name: "first cell",
type: "code",
content: "",
};
updateNotebook({
...notebook,
cells: [newCell],
});
}
}, [notebook, saveNotebook, updateNotebook]);
const handleCellRun = useCallback((cell: Cell, cogneeInstance: string) => {
return runCell(notebook, cell, cogneeInstance);
}, [notebook, runCell]);
const handleCellAdd = useCallback((afterCellIndex: number, cellType: "markdown" | "code") => {
const newCell: Cell = {
id: uuid4(),
name: "new cell",
type: cellType,
content: "",
};
const newNotebook = {
...notebook,
cells: [
...notebook.cells.slice(0, afterCellIndex + 1),
newCell,
...notebook.cells.slice(afterCellIndex + 1),
],
};
toggleCellOpen(newCell.id);
updateNotebook(newNotebook);
}, [notebook, updateNotebook]);
const handleCellRemove = useCallback((cell: Cell) => {
updateNotebook({
...notebook,
cells: notebook.cells.filter((c: Cell) => c.id !== cell.id),
});
}, [notebook, updateNotebook]);
const handleCellInputChange = useCallback((notebook: NotebookType, cell: Cell, value: string) => {
const newCell = {...cell, content: value };
updateNotebook({
...notebook,
cells: notebook.cells.map((cell: Cell) => (cell.id === newCell.id ? newCell : cell)),
});
}, [updateNotebook]);
const handleCellUp = useCallback((cell: Cell) => {
const index = notebook.cells.indexOf(cell);
if (index > 0) {
const newCells = [...notebook.cells];
newCells[index] = notebook.cells[index - 1];
newCells[index - 1] = cell;
updateNotebook({
...notebook,
cells: newCells,
});
}
}, [notebook, updateNotebook]);
const handleCellDown = useCallback((cell: Cell) => {
const index = notebook.cells.indexOf(cell);
if (index < notebook.cells.length - 1) {
const newCells = [...notebook.cells];
newCells[index] = notebook.cells[index + 1];
newCells[index + 1] = cell;
updateNotebook({
...notebook,
cells: newCells,
});
}
}, [notebook, updateNotebook]);
const handleCellRename = useCallback((cell: Cell) => {
const newName = prompt("Enter a new name for the cell:");
if (newName) {
updateNotebook({
...notebook,
cells: notebook.cells.map((c: Cell) => (c.id === cell.id ? {...c, name: newName } : c)),
});
}
}, [notebook, updateNotebook]);
const [openCells, setOpenCells] = useState(new Set(notebook.cells.map((c: Cell) => c.id)));
const toggleCellOpen = (id: string) => {
setOpenCells((prev) => {
const newState = new Set(prev);
if (newState.has(id)) {
newState.delete(id)
} else {
newState.add(id);
}
return newState;
});
};
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>
{notebook.cells.map((cell: Cell, index) => (
<Fragment key={cell.id}>
<div key={cell.id} className="flex flex-row rounded-xl border-1 border-gray-100">
<div className="flex flex-col flex-1 relative">
{cell.type === "code" ? (
<>
<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}
runCell={handleCellRun}
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)}
// 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>
</>
)}
</>
) : (
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 className="ml-[-1.35rem]">
<PopupMenu
openToRight={true}
triggerElement={<PlusIcon />}
triggerClassName="p-[0.25rem] m-[-0.25rem]"
>
<div className="flex flex-col gap-0.5">
<button
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"
>
<span>code</span>
</div>
</PopupMenu>
</div>
</Fragment>
))}
</div>
);
}
function CellResult({ content }: { content: [] }) {
const parsedContent = [];
const graphRef = useRef<GraphVisualizationAPI>();
const graphControls = useRef<GraphControlsAPI>({
setSelectedNode: () => {},
getSelectedNode: () => null,
});
for (const line of content) {
try {
if (Array.isArray(line)) {
// @ts-expect-error line can be Array or string
for (const item of line) {
if (typeof item === "string") {
parsedContent.push(
<pre key={item.slice(0, -10)}>
{item}
</pre>
);
}
if (typeof item === "object" && item["search_result"]) {
parsedContent.push(
<div className="w-full h-full bg-white">
<span className="text-sm pl-2 mb-4">query response (dataset: {item["dataset_name"]})</span>
<span className="block px-2 py-2">{item["search_result"]}</span>
</div>
);
}
if (typeof item === "object" && item["graph"] && typeof item["graph"] === "object") {
parsedContent.push(
<div className="w-full h-full bg-white">
<span className="text-sm pl-2 mb-4">reasoning graph</span>
<GraphVisualization
data={transformToVisualizationData(item["graph"])}
ref={graphRef as MutableRefObject<GraphVisualizationAPI>}
graphControls={graphControls}
className="min-h-48"
/>
</div>
);
}
}
}
if (typeof(line) === "object" && line["result"]) {
parsedContent.push(
<div className="w-full h-full bg-white">
<span className="text-sm pl-2 mb-4">query response (dataset: {line["dataset_name"]})</span>
<span className="block px-2 py-2">{line["result"]}</span>
</div>
);
if (line["graphs"]) {
parsedContent.push(
<div className="w-full h-full bg-white">
<span className="text-sm pl-2 mb-4">reasoning graph</span>
<GraphVisualization
data={transformToVisualizationData(line["graphs"]["*"])}
ref={graphRef as MutableRefObject<GraphVisualizationAPI>}
graphControls={graphControls}
className="min-h-48"
/>
</div>
);
}
}
} catch (error) {
console.error(error);
parsedContent.push(line);
}
}
return parsedContent.map((item, index) => (
<div key={index} className="px-2 py-1">
{item}
</div>
));
};
function transformToVisualizationData(graph: { nodes: [], edges: [] }) {
// Implementation to transform triplet to visualization data
return {
nodes: graph.nodes,
links: graph.edges,
};
// const nodes = {};
// const links = {};
// for (const triplet of triplets) {
// nodes[triplet.source.id] = {
// id: triplet.source.id,
// label: triplet.source.attributes.name,
// type: triplet.source.attributes.type,
// attributes: triplet.source.attributes,
// };
// nodes[triplet.destination.id] = {
// id: triplet.destination.id,
// label: triplet.destination.attributes.name,
// type: triplet.destination.attributes.type,
// attributes: triplet.destination.attributes,
// };
// links[`${triplet.source.id}_${triplet.attributes.relationship_name}_${triplet.destination.id}`] = {
// source: triplet.source.id,
// target: triplet.destination.id,
// label: triplet.attributes.relationship_name,
// }
// }
// return {
// nodes: Object.values(nodes),
// links: Object.values(links),
// };
}

View file

@ -0,0 +1,74 @@
import { useState } from "react";
import classNames from "classnames";
import { useBoolean } from "@/utils";
import { PlayIcon } from "@/ui/Icons";
import { PopupMenu, IconButton, Select } from "@/ui/elements";
import { LoadingIndicator } from "@/ui/App";
import { Cell } from "./types";
interface NotebookCellHeaderProps {
cell: Cell;
runCell: (cell: Cell, cogneeInstance: string) => Promise<void>;
renameCell: (cell: Cell) => void;
removeCell: (cell: Cell) => void;
moveCellUp: (cell: Cell) => void;
moveCellDown: (cell: Cell) => void;
className?: string;
}
export default function NotebookCellHeader({
cell,
runCell,
renameCell,
removeCell,
moveCellUp,
moveCellDown,
className,
}: NotebookCellHeaderProps) {
const {
value: isRunningCell,
setTrue: setIsRunningCell,
setFalse: setIsNotRunningCell,
} = useBoolean(false);
const [runInstance, setRunInstance] = useState<string>("local");
const handleCellRun = () => {
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">
{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">
<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">
local cognee
</option>
<option value="cloud" className="flex flex-row items-center gap-2">
cloud cognee
</option>
</Select>
<PopupMenu>
<div className="flex flex-col gap-0.5">
<button onClick={() => moveCellUp(cell)} className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer">move cell up</button>
<button onClick={() => moveCellDown(cell)} className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer">move cell down</button>
</div>
<div className="flex flex-col gap-0.5 items-start">
<button onClick={() => renameCell(cell)} className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer">rename</button>
<button onClick={() => removeCell(cell)} className="hover:bg-gray-100 w-full text-left px-2 cursor-pointer">delete</button>
</div>
</PopupMenu>
</div>
</div>
);
}

View file

@ -0,0 +1 @@
export { default } from "./Notebook";

View file

@ -0,0 +1,15 @@
export interface Cell {
id: string;
name: string;
type: "markdown" | "code";
content: string;
result?: [];
error?: string;
}
export interface Notebook {
id: string;
name: string;
cells: Cell[];
deletable?: boolean;
}

View file

@ -0,0 +1,48 @@
"use client";
import { useBoolean, useOutsideClick } from "@/utils";
import { MenuIcon } from "@/ui/Icons";
import { IconButton } from "@/ui/elements";
import classNames from 'classnames';
interface PopupMenuProps {
children: React.ReactNode;
triggerElement?: React.ReactNode;
triggerClassName?: string;
openToRight?: boolean;
}
export default function PopupMenu({ triggerElement, triggerClassName, children, openToRight = false }: PopupMenuProps) {
const {
value: isMenuOpen,
setFalse: closeMenu,
toggle: toggleMenu,
} = useBoolean(false);
const menuRootRef = useOutsideClick<HTMLDivElement>(closeMenu);
return (
<div className="relative inline-block" ref={menuRootRef}>
<IconButton as="div" className={triggerClassName} onClick={toggleMenu}>
{triggerElement || <MenuIcon />}
</IconButton>
{isMenuOpen && (
<div
className={
classNames(
"absolute top-full flex flex-col gap-4 pl-1 py-3 pr-4",
"whitespace-nowrap bg-white border-1 border-gray-100 z-10",
{
"left-0": openToRight,
"right-0": !openToRight,
},
)
}
>
{children}
</div>
)}
</div>
);
};

View file

@ -8,7 +8,7 @@ export default function Select({ children, className, ...props }: SelectHTMLAttr
<select
className={
classNames(
"block w-full appearance-none rounded-md bg-white pl-4 pr-8 py-4 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600",
"block w-full appearance-none rounded-3xl bg-white pl-4 pr-8 h-8 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600",
className,
)
}
@ -16,8 +16,8 @@ export default function Select({ children, className, ...props }: SelectHTMLAttr
>
{children}
</select>
<span className="pointer-events-none absolute top-1/2 -mt-0.5 right-3 text-indigo-600 rotate-180">
<CaretIcon height={8} width={12} />
<span className="pointer-events-none absolute top-1/3 -mt-0.5 right-3 text-indigo-600 rotate-180">
<CaretIcon />
</span>
</div>
);

View file

@ -24,10 +24,8 @@ export default function TextArea({
const fakeTextAreaElement = event.target as HTMLDivElement;
const newValue = fakeTextAreaElement.innerText;
if (newValue !== value) {
onChange?.(newValue);
}
}, [onChange, value]);
onChange?.(newValue);
}, [onChange]);
const handleKeyUp = useCallback((event: Event) => {
if (onKeyUp) {
@ -55,8 +53,15 @@ export default function TextArea({
useLayoutEffect(() => {
const fakeTextAreaElement = fakeTextAreaRef.current;
if (fakeTextAreaElement) {
if (fakeTextAreaElement && fakeTextAreaElement.innerText.trim() !== "") {
fakeTextAreaElement.innerText = placeholder;
}
}, [placeholder]);
useLayoutEffect(() => {
const fakeTextAreaElement = fakeTextAreaRef.current;
if (fakeTextAreaElement) {
fakeTextAreaElement.addEventListener("input", handleTextChange);
fakeTextAreaElement.addEventListener("keyup", handleKeyUp);
}
@ -67,15 +72,21 @@ export default function TextArea({
fakeTextAreaElement.removeEventListener("keyup", handleKeyUp);
}
};
}, []);
}, [handleKeyUp, handleTextChange]);
useEffect(() => {
const fakeTextAreaElement = fakeTextAreaRef.current;
const textAreaText = fakeTextAreaElement?.innerText;
if (fakeTextAreaElement && textAreaText !== value && textAreaText !== placeholder) {
if (fakeTextAreaElement && (value === "" || value === "\n")) {
fakeTextAreaElement.innerText = placeholder;
return;
}
if (fakeTextAreaElement && textAreaText !== value) {
fakeTextAreaElement.innerText = value;
}
}, [value]);
}, [placeholder, value]);
return isAutoExpanding ? (
<>

View file

@ -1,8 +1,12 @@
export { default as Modal } from "./Modal";
export { default as Modal } from "./Modal/Modal";
export { default as Input } from "./Input";
export { default as Select } from "./Select";
export { default as TextArea } from "./TextArea";
export { default as CTAButton } from "./CTAButton";
export { default as PopupMenu } from "./PopupMenu";
export { default as IconButton } from "./IconButton";
export { default as GhostButton } from "./GhostButton";
export { default as NeutralButton } from "./NeutralButton";
export { default as StatusIndicator } from "./StatusIndicator";
export { default as Accordion } from "./Accordion";
export { default as Notebook } from "./Notebook";

View file

@ -1,4 +1,5 @@
import handleServerErrors from "./handleServerErrors";
import isCloudEnvironment from "./isCloudEnvironment";
let numberOfRetries = 0;
@ -6,7 +7,12 @@ const isAuth0Enabled = process.env.USE_AUTH0_AUTHORIZATION?.toLowerCase() === "t
const backendApiUrl = process.env.NEXT_PUBLIC_BACKEND_API_URL || "http://localhost:8000/api";
export default async function fetch(url: string, options: RequestInit = {}): Promise<Response> {
const cloudApiUrl = process.env.NEXT_PUBLIC_CLOUD_API_URL || "http://localhost:8001/api";
let apiKey: string | null = null;
let accessToken: string | null = null;
export default async function fetch(url: string, options: RequestInit = {}, useCloud = false): Promise<Response> {
function retry(lastError: Response) {
if (!isAuth0Enabled) {
return Promise.reject(lastError);
@ -24,10 +30,20 @@ export default async function fetch(url: string, options: RequestInit = {}): Pro
});
}
return global.fetch(backendApiUrl + url, {
...options,
credentials: "include",
})
return global.fetch(
(useCloud ? cloudApiUrl : backendApiUrl) + (useCloud ? url.replace("/v1", "") : url),
{
...options,
headers: {
...options.headers,
...(useCloud && !isCloudEnvironment()
? {"X-Api-Key": apiKey!}
: {"Authorization": `Bearer ${accessToken}`}
),
},
credentials: "include",
},
)
.then((response) => handleServerErrors(response, retry))
.then((response) => {
numberOfRetries = 0;
@ -47,3 +63,15 @@ export default async function fetch(url: string, options: RequestInit = {}): Pro
return Promise.reject(error);
});
}
fetch.checkHealth = () => {
return global.fetch(`${backendApiUrl.replace("/api", "")}/health`);
};
fetch.setApiKey = (newApiKey: string) => {
apiKey = newApiKey;
};
fetch.setAccessToken = (newAccessToken: string) => {
accessToken = newAccessToken;
};

View file

@ -1,3 +1,5 @@
export { default as fetch } from "./fetch";
export { default as handleServerErrors } from "./handleServerErrors";
export { default as useBoolean } from "./useBoolean";
export { default as useOutsideClick } from "./useOutsideClick";
export { default as isCloudEnvironment } from "./isCloudEnvironment";

View file

@ -0,0 +1,4 @@
export default function isCloudEnvironment() {
return process.env.NEXT_PUBLIC_IS_CLOUD_ENVIRONMENT?.toLowerCase() === "true";
}

View file

@ -5,10 +5,12 @@ export default function useBoolean(initialValue: boolean) {
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
const toggle = useCallback(() => setValue((prevValue) => !prevValue), []);
return {
value,
setTrue,
setFalse,
toggle,
};
}

View file

@ -0,0 +1,25 @@
import { useEffect, useRef } from "react";
export default function useOutsideClick<ElementType extends HTMLElement>(callbackFn: () => void, isEnabled = true) {
const rootElementRef = useRef<ElementType>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
const clickedElement = event.target;
if (clickedElement && rootElementRef.current && !rootElementRef.current?.contains(clickedElement as Node)) {
callbackFn();
}
}
if (isEnabled) {
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
}
}, [callbackFn, isEnabled]);
return rootElementRef;
}

View file

@ -1,124 +1,265 @@
import os
import uuid
import json
"""Cognee demo with simplified structure."""
from __future__ import annotations
import asyncio
import pathlib
import json
import logging
from collections import defaultdict
from pathlib import Path
from typing import Any, Iterable, List, Mapping
from cognee import config, prune, search, SearchType, visualize_graph
from cognee.low_level import setup, DataPoint
from cognee.pipelines import run_tasks, Task
from cognee.tasks.storage import add_data_points
from cognee.tasks.storage.index_graph_edges import index_graph_edges
from cognee.modules.users.methods import get_default_user
from cognee.modules.data.methods import load_or_create_datasets
class Person(DataPoint):
"""Represent a person."""
name: str
metadata: dict = {"index_fields": ["name"]}
class Department(DataPoint):
"""Represent a department."""
name: str
employees: list[Person]
metadata: dict = {"index_fields": ["name"]}
class CompanyType(DataPoint):
"""Represent a company type."""
name: str = "Company"
class Company(DataPoint):
"""Represent a company."""
name: str
departments: list[Department]
is_type: CompanyType
metadata: dict = {"index_fields": ["name"]}
def ingest_files():
companies_file_path = os.path.join(os.path.dirname(__file__), "../data/companies.json")
companies = json.loads(open(companies_file_path, "r").read())
ROOT = Path(__file__).resolve().parent
DATA_DIR = ROOT.parent / "data"
COGNEE_DIR = ROOT / ".cognee_system"
ARTIFACTS_DIR = ROOT / ".artifacts"
GRAPH_HTML = ARTIFACTS_DIR / "graph_visualization.html"
COMPANIES_JSON = DATA_DIR / "companies.json"
PEOPLE_JSON = DATA_DIR / "people.json"
people_file_path = os.path.join(os.path.dirname(__file__), "../data/people.json")
people = json.loads(open(people_file_path, "r").read())
people_data_points = {}
departments_data_points = {}
def load_json_file(path: Path) -> Any:
"""Load a JSON file."""
if not path.exists():
raise FileNotFoundError(f"Missing required file: {path}")
return json.loads(path.read_text(encoding="utf-8"))
def remove_duplicates_preserve_order(seq: Iterable[Any]) -> list[Any]:
"""Return list with duplicates removed while preserving order."""
seen = set()
out = []
for x in seq:
if x in seen:
continue
seen.add(x)
out.append(x)
return out
def collect_people(payloads: Iterable[Mapping[str, Any]]) -> list[Mapping[str, Any]]:
"""Collect people from payloads."""
people = [person for payload in payloads for person in payload.get("people", [])]
return people
def collect_companies(payloads: Iterable[Mapping[str, Any]]) -> list[Mapping[str, Any]]:
"""Collect companies from payloads."""
companies = [company for payload in payloads for company in payload.get("companies", [])]
return companies
def build_people_nodes(people: Iterable[Mapping[str, Any]]) -> dict:
"""Build person nodes keyed by name."""
nodes = {p["name"]: Person(name=p["name"]) for p in people if p.get("name")}
return nodes
def group_people_by_department(people: Iterable[Mapping[str, Any]]) -> dict:
"""Group person names by department."""
groups = defaultdict(list)
for person in people:
new_person = Person(name=person["name"])
people_data_points[person["name"]] = new_person
name = person.get("name")
if not name:
continue
dept = person.get("department", "Unknown")
groups[dept].append(name)
return groups
if person["department"] not in departments_data_points:
departments_data_points[person["department"]] = Department(
name=person["department"], employees=[new_person]
)
else:
departments_data_points[person["department"]].employees.append(new_person)
companies_data_points = {}
# Create a single CompanyType node, so we connect all companies to it.
companyType = CompanyType()
def collect_declared_departments(
groups: Mapping[str, list[str]], companies: Iterable[Mapping[str, Any]]
) -> set:
"""Collect department names referenced anywhere."""
names = set(groups)
for company in companies:
new_company = Company(name=company["name"], departments=[], is_type=companyType)
companies_data_points[company["name"]] = new_company
for department_name in company["departments"]:
if department_name not in departments_data_points:
departments_data_points[department_name] = Department(
name=department_name, employees=[]
)
new_company.departments.append(departments_data_points[department_name])
return companies_data_points.values()
for dept in company.get("departments", []):
names.add(dept)
return names
async def main():
cognee_directory_path = str(
pathlib.Path(os.path.join(pathlib.Path(__file__).parent, ".cognee_system")).resolve()
)
# Set up the Cognee system directory. Cognee will store system files and databases here.
config.system_root_directory(cognee_directory_path)
def build_department_nodes(dept_names: Iterable[str]) -> dict:
"""Build department nodes keyed by name."""
nodes = {name: Department(name=name, employees=[]) for name in dept_names}
return nodes
# Prune system metadata before running, only if we want "fresh" state.
def build_company_nodes(companies: Iterable[Mapping[str, Any]], company_type: CompanyType) -> dict:
"""Build company nodes keyed by name."""
nodes = {
c["name"]: Company(name=c["name"], departments=[], is_type=company_type)
for c in companies
if c.get("name")
}
return nodes
def iterate_company_department_pairs(companies: Iterable[Mapping[str, Any]]):
"""Yield (company_name, department_name) pairs."""
for company in companies:
comp_name = company.get("name")
if not comp_name:
continue
for dept in company.get("departments", []):
yield comp_name, dept
def attach_departments_to_companies(
companies: Iterable[Mapping[str, Any]],
dept_nodes: Mapping[str, Department],
company_nodes: Mapping[str, Company],
) -> None:
"""Attach department nodes to companies."""
for comp_name in company_nodes:
company_nodes[comp_name].departments = []
for comp_name, dept_name in iterate_company_department_pairs(companies):
dept = dept_nodes.get(dept_name)
company = company_nodes.get(comp_name)
if not dept or not company:
continue
company.departments.append(dept)
def attach_employees_to_departments(
groups: Mapping[str, list[str]],
people_nodes: Mapping[str, Person],
dept_nodes: Mapping[str, Department],
) -> None:
"""Attach employees to departments."""
for dept in dept_nodes.values():
dept.employees = []
for dept_name, names in groups.items():
unique_names = remove_duplicates_preserve_order(names)
target = dept_nodes.get(dept_name)
if not target:
continue
employees = [people_nodes[n] for n in unique_names if n in people_nodes]
target.employees = employees
def build_companies(payloads: Iterable[Mapping[str, Any]]) -> list[Company]:
"""Build company nodes from payloads."""
people = collect_people(payloads)
companies = collect_companies(payloads)
people_nodes = build_people_nodes(people)
groups = group_people_by_department(people)
dept_names = collect_declared_departments(groups, companies)
dept_nodes = build_department_nodes(dept_names)
company_type = CompanyType()
company_nodes = build_company_nodes(companies, company_type)
attach_departments_to_companies(companies, dept_nodes, company_nodes)
attach_employees_to_departments(groups, people_nodes, dept_nodes)
result = list(company_nodes.values())
return result
def load_default_payload() -> list[Mapping[str, Any]]:
"""Load the default payload from data files."""
companies = load_json_file(COMPANIES_JSON)
people = load_json_file(PEOPLE_JSON)
payload = [{"companies": companies, "people": people}]
return payload
def ingest_payloads(data: List[Any] | None) -> list[Company]:
"""Ingest payloads and build company nodes."""
if not data or data == [None]:
data = load_default_payload()
companies = build_companies(data)
return companies
async def execute_pipeline() -> None:
"""Execute Cognee pipeline."""
# Configure system paths
logging.info("Configuring Cognee directories at %s", COGNEE_DIR)
config.system_root_directory(str(COGNEE_DIR))
ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
# Reset state and initialize
await prune.prune_system(metadata=True)
await setup()
# Generate a random dataset_id
dataset_id = uuid.uuid4()
# Get user and dataset
user = await get_default_user()
datasets = await load_or_create_datasets(["demo_dataset"], [], user)
dataset_id = datasets[0].id
pipeline = run_tasks(
[
Task(ingest_files),
Task(add_data_points),
],
dataset_id,
None,
user,
"demo_pipeline",
)
# Build and run pipeline
tasks = [Task(ingest_payloads), Task(add_data_points)]
pipeline = run_tasks(tasks, dataset_id, None, user, "demo_pipeline")
async for status in pipeline:
print(status)
logging.info("Pipeline status: %s", status)
# Post-process: index graph edges and visualize
await index_graph_edges()
await visualize_graph(str(GRAPH_HTML))
# Or use our simple graph preview
graph_file_path = str(
os.path.join(os.path.dirname(__file__), ".artifacts/graph_visualization.html")
)
await visualize_graph(graph_file_path)
# Completion query that uses graph data to form context.
# Run query against graph
completion = await search(
query_text="Who works for GreenFuture Solutions?",
query_type=SearchType.GRAPH_COMPLETION,
)
print("Graph completion result is:")
print(completion)
result = completion
logging.info("Graph completion result: %s", result)
def configure_logging() -> None:
"""Configure logging."""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
)
async def main() -> None:
"""Run main function."""
configure_logging()
try:
await execute_pipeline()
except Exception:
logging.exception("Run failed")
raise
if __name__ == "__main__":

View file

@ -18,6 +18,7 @@ logger = setup_logging()
from .api.v1.add import add
from .api.v1.delete import delete
from .api.v1.cognify import cognify
from .modules.memify import memify
from .api.v1.config.config import config
from .api.v1.datasets.datasets import datasets
from .api.v1.prune import prune
@ -26,6 +27,7 @@ from .api.v1.visualize import visualize_graph, start_visualization_server
from cognee.modules.visualization.cognee_network_visualization import (
cognee_network_visualization,
)
from .api.v1.ui import start_ui
# Pipelines
from .modules import pipelines

View file

@ -9,7 +9,7 @@ from contextlib import asynccontextmanager
from fastapi import Request
from fastapi import FastAPI, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse, Response
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError
from fastapi.openapi.utils import get_openapi
@ -17,14 +17,18 @@ from fastapi.openapi.utils import get_openapi
from cognee.exceptions import CogneeApiError
from cognee.shared.logging_utils import get_logger, setup_logging
from cognee.api.health import health_checker, HealthStatus
from cognee.api.v1.cloud.routers import get_checks_router
from cognee.api.v1.notebooks.routers import get_notebooks_router
from cognee.api.v1.permissions.routers import get_permissions_router
from cognee.api.v1.settings.routers import get_settings_router
from cognee.api.v1.datasets.routers import get_datasets_router
from cognee.api.v1.cognify.routers import get_code_pipeline_router, get_cognify_router
from cognee.api.v1.search.routers import get_search_router
from cognee.api.v1.memify.routers import get_memify_router
from cognee.api.v1.add.routers import get_add_router
from cognee.api.v1.delete.routers import get_delete_router
from cognee.api.v1.responses.routers import get_responses_router
from cognee.api.v1.sync.routers import get_sync_router
from cognee.api.v1.users.routers import (
get_auth_router,
get_register_router,
@ -33,6 +37,7 @@ from cognee.api.v1.users.routers import (
get_users_router,
get_visualize_router,
)
from cognee.modules.users.methods.get_authenticated_user import REQUIRE_AUTHENTICATION
logger = get_logger()
@ -83,7 +88,7 @@ app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins, # Now controlled by env var
allow_credentials=True,
allow_methods=["OPTIONS", "GET", "POST", "DELETE"],
allow_methods=["OPTIONS", "GET", "PUT", "POST", "DELETE"],
allow_headers=["*"],
)
# To allow origins, set CORS_ALLOWED_ORIGINS env variable to a comma-separated list, e.g.:
@ -110,7 +115,11 @@ def custom_openapi():
},
}
openapi_schema["security"] = [{"BearerAuth": []}, {"CookieAuth": []}]
if REQUIRE_AUTHENTICATION:
openapi_schema["security"] = [{"BearerAuth": []}, {"CookieAuth": []}]
# Remove global security requirement - let individual endpoints specify their own security
# openapi_schema["security"] = [{"BearerAuth": []}, {"CookieAuth": []}]
app.openapi_schema = openapi_schema
@ -230,6 +239,8 @@ app.include_router(get_add_router(), prefix="/api/v1/add", tags=["add"])
app.include_router(get_cognify_router(), prefix="/api/v1/cognify", tags=["cognify"])
app.include_router(get_memify_router(), prefix="/api/v1/memify", tags=["memify"])
app.include_router(get_search_router(), prefix="/api/v1/search", tags=["search"])
app.include_router(
@ -248,6 +259,8 @@ app.include_router(get_delete_router(), prefix="/api/v1/delete", tags=["delete"]
app.include_router(get_responses_router(), prefix="/api/v1/responses", tags=["responses"])
app.include_router(get_sync_router(), prefix="/api/v1/sync", tags=["sync"])
codegraph_routes = get_code_pipeline_router()
if codegraph_routes:
app.include_router(codegraph_routes, prefix="/api/v1/code-pipeline", tags=["code-pipeline"])
@ -258,6 +271,18 @@ app.include_router(
tags=["users"],
)
app.include_router(
get_notebooks_router(),
prefix="/api/v1/notebooks",
tags=["notebooks"],
)
app.include_router(
get_checks_router(),
prefix="/api/v1/checks",
tags=["checks"],
)
def start_api_server(host: str = "0.0.0.0", port: int = 8000):
"""

View file

@ -1,9 +1,10 @@
"""Health check system for cognee API."""
from io import BytesIO
import time
import asyncio
from datetime import datetime, timezone
from typing import Dict, Any, Optional
from typing import Dict
from enum import Enum
from pydantic import BaseModel
@ -53,7 +54,7 @@ class HealthChecker:
# Test connection by creating a session
session = engine.get_session()
if session:
await session.close()
session.close()
response_time = int((time.time() - start_time) * 1000)
return ComponentHealth(
@ -117,12 +118,9 @@ class HealthChecker:
engine = await get_graph_engine()
# Test basic operation with actual graph query
if hasattr(engine, "execute"):
# For SQL-like graph DBs (Neo4j, Memgraph)
await engine.execute("MATCH () RETURN count(*) LIMIT 1")
elif hasattr(engine, "query"):
if hasattr(engine, "query"):
# For other graph engines
engine.query("MATCH () RETURN count(*) LIMIT 1", {})
await engine.query("MATCH () RETURN count(*) LIMIT 1", {})
# If engine exists but no test method, consider it healthy
response_time = int((time.time() - start_time) * 1000)
@ -167,8 +165,8 @@ class HealthChecker:
else:
# For S3, test basic operations
test_path = "health_check_test"
await storage.store(test_path, b"test")
await storage.delete(test_path)
await storage.store(test_path, BytesIO(b"test"))
await storage.remove(test_path)
response_time = int((time.time() - start_time) * 1000)
return ComponentHealth(
@ -190,14 +188,13 @@ class HealthChecker:
"""Check LLM provider health (non-critical)."""
start_time = time.time()
try:
from cognee.infrastructure.llm.get_llm_client import get_llm_client
from cognee.infrastructure.llm.config import get_llm_config
from cognee.infrastructure.llm import LLMGateway
config = get_llm_config()
# Test actual API connection with minimal request
client = get_llm_client()
await client.show_prompt("test", "test")
LLMGateway.show_prompt("test", "test")
response_time = int((time.time() - start_time) * 1000)
return ComponentHealth(
@ -226,7 +223,7 @@ class HealthChecker:
# Test actual embedding generation with minimal text
engine = get_embedding_engine()
await engine.embed_text("test")
await engine.embed_text(["test"])
response_time = int((time.time() - start_time) * 1000)
return ComponentHealth(

View file

@ -150,7 +150,9 @@ async def add(
user, authorized_dataset = await resolve_authorized_user_dataset(dataset_id, dataset_name, user)
await reset_dataset_pipeline_run_status(authorized_dataset.id, user)
await reset_dataset_pipeline_run_status(
authorized_dataset.id, user, pipeline_names=["add_pipeline", "cognify_pipeline"]
)
pipeline_run_info = None

View file

@ -1,6 +1,3 @@
import os
import requests
import subprocess
from uuid import UUID
from fastapi import APIRouter
@ -24,7 +21,9 @@ def get_add_router() -> APIRouter:
async def add(
data: List[UploadFile] = File(default=None),
datasetName: Optional[str] = Form(default=None),
# Note: Literal is needed for Swagger use
datasetId: Union[UUID, Literal[""], None] = Form(default=None, examples=[""]),
node_set: Optional[List[str]] = Form(default=[""], example=[""]),
user: User = Depends(get_authenticated_user),
):
"""
@ -41,6 +40,8 @@ def get_add_router() -> APIRouter:
- Regular file uploads
- **datasetName** (Optional[str]): Name of the dataset to add data to
- **datasetId** (Optional[UUID]): UUID of an already existing dataset
- **node_set** Optional[list[str]]: List of node identifiers for graph organization and access control.
Used for grouping related data points in the knowledge graph.
Either datasetName or datasetId must be provided.
@ -57,17 +58,12 @@ def get_add_router() -> APIRouter:
## Notes
- To add data to datasets not owned by the user, use dataset_id (when ENABLE_BACKEND_ACCESS_CONTROL is set to True)
- GitHub repositories are cloned and all files are processed
- HTTP URLs are fetched and their content is processed
- The ALLOW_HTTP_REQUESTS environment variable controls URL processing
- datasetId value can only be the UUID of an already existing dataset
"""
send_telemetry(
"Add API Endpoint Invoked",
user.id,
additional_properties={
"endpoint": "POST /v1/add",
},
additional_properties={"endpoint": "POST /v1/add", "node_set": node_set},
)
from cognee.api.v1.add import add as cognee_add
@ -76,34 +72,13 @@ def get_add_router() -> APIRouter:
raise ValueError("Either datasetId or datasetName must be provided.")
try:
if (
isinstance(data, str)
and data.startswith("http")
and (os.getenv("ALLOW_HTTP_REQUESTS", "true").lower() == "true")
):
if "github" in data:
# Perform git clone if the URL is from GitHub
repo_name = data.split("/")[-1].replace(".git", "")
subprocess.run(["git", "clone", data, f".data/{repo_name}"], check=True)
# TODO: Update add call with dataset info
await cognee_add(
"data://.data/",
f"{repo_name}",
)
else:
# Fetch and store the data from other types of URL using curl
response = requests.get(data)
response.raise_for_status()
add_run = await cognee_add(
data, datasetName, user=user, dataset_id=datasetId, node_set=node_set
)
file_data = await response.content()
# TODO: Update add call with dataset info
return await cognee_add(file_data)
else:
add_run = await cognee_add(data, datasetName, user=user, dataset_id=datasetId)
if isinstance(add_run, PipelineRunErrored):
return JSONResponse(status_code=420, content=add_run.model_dump(mode="json"))
return add_run.model_dump()
if isinstance(add_run, PipelineRunErrored):
return JSONResponse(status_code=420, content=add_run.model_dump(mode="json"))
return add_run.model_dump()
except Exception as error:
return JSONResponse(status_code=409, content={"error": str(error)})

View file

@ -0,0 +1 @@
from .get_checks_router import get_checks_router

View file

@ -0,0 +1,23 @@
from fastapi import APIRouter, Depends, Request
from cognee.modules.users.models import User
from cognee.modules.users.methods import get_authenticated_user
from cognee.modules.cloud.operations import check_api_key
from cognee.modules.cloud.exceptions import CloudApiKeyMissingError
def get_checks_router():
router = APIRouter()
@router.post("/connection")
async def get_connection_check_endpoint(
request: Request, user: User = Depends(get_authenticated_user)
):
api_token = request.headers.get("X-Api-Key")
if api_token is None:
return CloudApiKeyMissingError()
return await check_api_key(api_token)
return router

View file

@ -1,6 +1,7 @@
import os
import pathlib
import asyncio
from typing import Optional
from cognee.shared.logging_utils import get_logger, setup_logging
from cognee.modules.observability.get_observe import get_observe
@ -28,7 +29,12 @@ logger = get_logger("code_graph_pipeline")
@observe
async def run_code_graph_pipeline(repo_path, include_docs=False):
async def run_code_graph_pipeline(
repo_path,
include_docs=False,
excluded_paths: Optional[list[str]] = None,
supported_languages: Optional[list[str]] = None,
):
import cognee
from cognee.low_level import setup
@ -40,13 +46,12 @@ async def run_code_graph_pipeline(repo_path, include_docs=False):
user = await get_default_user()
detailed_extraction = True
# Multi-language support: allow passing supported_languages
supported_languages = None # defer to task defaults
tasks = [
Task(
get_repo_file_dependencies,
detailed_extraction=detailed_extraction,
supported_languages=supported_languages,
excluded_paths=excluded_paths,
),
# Task(summarize_code, task_config={"batch_size": 500}), # This task takes a long time to complete
Task(add_data_points, task_config={"batch_size": 30}),
@ -95,7 +100,7 @@ if __name__ == "__main__":
async def main():
async for run_status in run_code_graph_pipeline("REPO_PATH"):
print(f"{run_status.pipeline_name}: {run_status.status}")
print(f"{run_status.pipeline_run_id}: {run_status.status}")
file_path = os.path.join(
pathlib.Path(__file__).parent, ".artifacts", "graph_visualization.html"

View file

@ -22,6 +22,11 @@ from cognee.tasks.graph import extract_graph_from_data
from cognee.tasks.storage import add_data_points
from cognee.tasks.summarization import summarize_text
from cognee.modules.pipelines.layers.pipeline_execution_mode import get_pipeline_executor
from cognee.tasks.temporal_graph.extract_events_and_entities import extract_events_and_timestamps
from cognee.tasks.temporal_graph.extract_knowledge_graph_from_events import (
extract_knowledge_graph_from_events,
)
logger = get_logger("cognify")
@ -40,6 +45,7 @@ async def cognify(
run_in_background: bool = False,
incremental_loading: bool = True,
custom_prompt: Optional[str] = None,
temporal_cognify: bool = False,
):
"""
Transform ingested data into a structured knowledge graph.
@ -182,9 +188,12 @@ async def cognify(
- LLM_RATE_LIMIT_ENABLED: Enable rate limiting (default: False)
- LLM_RATE_LIMIT_REQUESTS: Max requests per interval (default: 60)
"""
tasks = await get_default_tasks(
user, graph_model, chunker, chunk_size, ontology_file_path, custom_prompt
)
if temporal_cognify:
tasks = await get_temporal_tasks(user, chunker, chunk_size)
else:
tasks = await get_default_tasks(
user, graph_model, chunker, chunk_size, ontology_file_path, custom_prompt
)
# By calling get pipeline executor we get a function that will have the run_pipeline run in the background or a function that we will need to wait for
pipeline_executor_func = get_pipeline_executor(run_in_background=run_in_background)
@ -233,3 +242,41 @@ async def get_default_tasks( # TODO: Find out a better way to do this (Boris's
]
return default_tasks
async def get_temporal_tasks(
user: User = None, chunker=TextChunker, chunk_size: int = None
) -> list[Task]:
"""
Builds and returns a list of temporal processing tasks to be executed in sequence.
The pipeline includes:
1. Document classification.
2. Dataset permission checks (requires "write" access).
3. Document chunking with a specified or default chunk size.
4. Event and timestamp extraction from chunks.
5. Knowledge graph extraction from events.
6. Batched insertion of data points.
Args:
user (User, optional): The user requesting task execution, used for permission checks.
chunker (Callable, optional): A text chunking function/class to split documents. Defaults to TextChunker.
chunk_size (int, optional): Maximum token size per chunk. If not provided, uses system default.
Returns:
list[Task]: A list of Task objects representing the temporal processing pipeline.
"""
temporal_tasks = [
Task(classify_documents),
Task(check_permissions_on_dataset, user=user, permissions=["write"]),
Task(
extract_chunks_from_documents,
max_chunk_size=chunk_size or get_max_chunk_tokens(),
chunker=chunker,
),
Task(extract_events_and_timestamps, task_config={"chunk_size": 10}),
Task(extract_knowledge_graph_from_events),
Task(add_data_points, task_config={"batch_size": 10}),
]
return temporal_tasks

View file

@ -38,7 +38,7 @@ class CognifyPayloadDTO(InDTO):
dataset_ids: Optional[List[UUID]] = Field(default=None, examples=[[]])
run_in_background: Optional[bool] = Field(default=False)
custom_prompt: Optional[str] = Field(
default=None, description="Custom prompt for entity extraction and graph generation"
default="", description="Custom prompt for entity extraction and graph generation"
)

View file

@ -5,6 +5,7 @@ from typing import List, Optional
from typing_extensions import Annotated
from fastapi import status
from fastapi import APIRouter
from fastapi.encoders import jsonable_encoder
from fastapi import HTTPException, Query, Depends
from fastapi.responses import JSONResponse, FileResponse
@ -47,6 +48,7 @@ class DataDTO(OutDTO):
extension: str
mime_type: str
raw_data_location: str
dataset_id: UUID
class GraphNodeDTO(OutDTO):
@ -114,7 +116,8 @@ def get_datasets_router() -> APIRouter:
@router.post("", response_model=DatasetDTO)
async def create_new_dataset(
dataset_data: DatasetCreationPayload, user: User = Depends(get_authenticated_user)
dataset_data: DatasetCreationPayload,
user: User = Depends(get_authenticated_user),
):
"""
Create a new dataset or return existing dataset with the same name.
@ -327,7 +330,7 @@ def get_datasets_router() -> APIRouter:
},
)
from cognee.modules.data.methods import get_dataset_data, get_dataset
from cognee.modules.data.methods import get_dataset_data
# Verify user has permission to read dataset
dataset = await get_authorized_existing_datasets([dataset_id], "read", user)
@ -338,12 +341,20 @@ def get_datasets_router() -> APIRouter:
content=ErrorResponseDTO(f"Dataset ({str(dataset_id)}) not found."),
)
dataset_data = await get_dataset_data(dataset_id=dataset[0].id)
dataset_id = dataset[0].id
dataset_data = await get_dataset_data(dataset_id=dataset_id)
if dataset_data is None:
return []
return dataset_data
return [
dict(
**jsonable_encoder(data),
dataset_id=dataset_id,
)
for data in dataset_data
]
@router.get("/status", response_model=dict[str, PipelineRunStatus])
async def get_dataset_status(

Some files were not shown because too many files have changed in this diff Show more