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:
commit
e591c53991
256 changed files with 10305 additions and 1272 deletions
|
|
@ -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
97
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
||||
73
.github/ISSUE_TEMPLATE/documentation.yml
vendored
Normal file
73
.github/ISSUE_TEMPLATE/documentation.yml
vendored
Normal 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
|
||||
78
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
78
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal 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
|
||||
|
||||
45
.github/pull_request_template.md
vendored
45
.github/pull_request_template.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
224
.github/workflows/temporal_graph_tests.yml
vendored
Normal file
224
.github/workflows/temporal_graph_tests.yml
vendored
Normal 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
30
.github/workflows/test_openrouter.yml
vendored
Normal 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
|
||||
15
.github/workflows/test_suites.yml
vendored
15
.github/workflows/test_suites.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
98
alembic/versions/211ab850ef3d_add_sync_operations_table.py
Normal file
98
alembic/versions/211ab850ef3d_add_sync_operations_table.py
Normal 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 ###
|
||||
46
alembic/versions/45957f0a9849_add_notebook_table.py
Normal file
46
alembic/versions/45957f0a9849_add_notebook_table.py
Normal 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")
|
||||
|
|
@ -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
BIN
assets/cognee-new-ui.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 446 KiB |
|
|
@ -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 |
|
|
@ -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 |
BIN
cognee-frontend/public/videos/background-video-blur.mp4
Normal file
BIN
cognee-frontend/public/videos/background-video-blur.mp4
Normal file
Binary file not shown.
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
58
cognee-frontend/src/app/account/Account.tsx
Normal file
58
cognee-frontend/src/app/account/Account.tsx
Normal 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'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>
|
||||
);
|
||||
}
|
||||
1
cognee-frontend/src/app/account/page.tsx
Normal file
1
cognee-frontend/src/app/account/page.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Account";
|
||||
116
cognee-frontend/src/app/dashboard/AddDataToCognee.tsx
Normal file
116
cognee-frontend/src/app/dashboard/AddDataToCognee.tsx
Normal 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't have any, don'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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
169
cognee-frontend/src/app/dashboard/Dashboard.tsx
Normal file
169
cognee-frontend/src/app/dashboard/Dashboard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
346
cognee-frontend/src/app/dashboard/DatasetsAccordion.tsx
Normal file
346
cognee-frontend/src/app/dashboard/DatasetsAccordion.tsx
Normal 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 "add data" 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
130
cognee-frontend/src/app/dashboard/InstanceDatasetsAccordion.tsx
Normal file
130
cognee-frontend/src/app/dashboard/InstanceDatasetsAccordion.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
150
cognee-frontend/src/app/dashboard/NotebooksAccordion.tsx
Normal file
150
cognee-frontend/src/app/dashboard/NotebooksAccordion.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
11
cognee-frontend/src/app/dashboard/page.tsx
Normal file
11
cognee-frontend/src/app/dashboard/page.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"use server";
|
||||
|
||||
import Dashboard from "./Dashboard";
|
||||
|
||||
export default async function Page() {
|
||||
const accessToken = "";
|
||||
|
||||
return (
|
||||
<Dashboard accessToken={accessToken} />
|
||||
);
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
export { default } from "./(graph)/GraphView";
|
||||
export { default } from "./dashboard/page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
// export const dynamic = "force-dynamic";
|
||||
|
|
|
|||
165
cognee-frontend/src/app/plan/Plan.tsx
Normal file
165
cognee-frontend/src/app/plan/Plan.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
1
cognee-frontend/src/app/plan/page.tsx
Normal file
1
cognee-frontend/src/app/plan/page.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Plan";
|
||||
6
cognee-frontend/src/modules/auth/getUser.ts
Normal file
6
cognee-frontend/src/modules/auth/getUser.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import fetch from "@/utils/fetch";
|
||||
|
||||
export default function getUser() {
|
||||
return fetch("/v1/auth/me")
|
||||
.then((response) => response.json());
|
||||
}
|
||||
2
cognee-frontend/src/modules/auth/index.ts
Normal file
2
cognee-frontend/src/modules/auth/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { type User } from "./types";
|
||||
export { default as useAuthenticatedUser } from "./useAuthenticatedUser";
|
||||
6
cognee-frontend/src/modules/auth/types.ts
Normal file
6
cognee-frontend/src/modules/auth/types.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
picture: string;
|
||||
}
|
||||
17
cognee-frontend/src/modules/auth/useAuthenticatedUser.ts
Normal file
17
cognee-frontend/src/modules/auth/useAuthenticatedUser.ts
Normal 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 };
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { fetch } from "@/utils";
|
||||
|
||||
export default function checkCloudConnection() {
|
||||
return fetch("/v1/checks/connection", {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
2
cognee-frontend/src/modules/cloud/index.ts
Normal file
2
cognee-frontend/src/modules/cloud/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { default as syncData } from "./syncData";
|
||||
export { default as checkCloudConnection } from "./checkCloudConnection";
|
||||
11
cognee-frontend/src/modules/cloud/syncData.ts
Normal file
11
cognee-frontend/src/modules/cloud/syncData.ts
Normal 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: "{}" }),
|
||||
});
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: "",
|
||||
}))
|
||||
);
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
135
cognee-frontend/src/modules/notebooks/useNotebooks.ts
Normal file
135
cognee-frontend/src/modules/notebooks/useNotebooks.ts
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
8
cognee-frontend/src/ui/Icons/BackIcon.tsx
Normal file
8
cognee-frontend/src/ui/Icons/BackIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
7
cognee-frontend/src/ui/Icons/CheckIcon.tsx
Normal file
7
cognee-frontend/src/ui/Icons/CheckIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
8
cognee-frontend/src/ui/Icons/CloseIcon.tsx
Normal file
8
cognee-frontend/src/ui/Icons/CloseIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
cognee-frontend/src/ui/Icons/CloudIcon.tsx
Normal file
7
cognee-frontend/src/ui/Icons/CloudIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
cognee-frontend/src/ui/Icons/CogneeIcon.tsx
Normal file
7
cognee-frontend/src/ui/Icons/CogneeIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
9
cognee-frontend/src/ui/Icons/DatasetIcon.tsx
Normal file
9
cognee-frontend/src/ui/Icons/DatasetIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
10
cognee-frontend/src/ui/Icons/LocalCogneeIcon.tsx
Normal file
10
cognee-frontend/src/ui/Icons/LocalCogneeIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
9
cognee-frontend/src/ui/Icons/MenuIcon.tsx
Normal file
9
cognee-frontend/src/ui/Icons/MenuIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
cognee-frontend/src/ui/Icons/MinusIcon.tsx
Normal file
7
cognee-frontend/src/ui/Icons/MinusIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
8
cognee-frontend/src/ui/Icons/NotebookIcon.tsx
Normal file
8
cognee-frontend/src/ui/Icons/NotebookIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
cognee-frontend/src/ui/Icons/PlayIcon.tsx
Normal file
7
cognee-frontend/src/ui/Icons/PlayIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
8
cognee-frontend/src/ui/Icons/PlusIcon.tsx
Normal file
8
cognee-frontend/src/ui/Icons/PlusIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
79
cognee-frontend/src/ui/Layout/Header.tsx
Normal file
79
cognee-frontend/src/ui/Layout/Header.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
export { default as Divider } from './Divider/Divider';
|
||||
export { default as Divider } from "./Divider/Divider";
|
||||
export { default as Header } from "./Header";
|
||||
|
|
|
|||
45
cognee-frontend/src/ui/elements/Accordion.tsx
Normal file
45
cognee-frontend/src/ui/elements/Accordion.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
0
cognee-frontend/src/ui/elements/AvatarImage.tsx
Normal file
0
cognee-frontend/src/ui/elements/AvatarImage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
14
cognee-frontend/src/ui/elements/IconButton.tsx
Normal file
14
cognee-frontend/src/ui/elements/IconButton.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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} />
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
3
cognee-frontend/src/ui/elements/Modal/index.ts
Normal file
3
cognee-frontend/src/ui/elements/Modal/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { default as Modal } from "./Modal";
|
||||
export { default as useModal } from "./useModal";
|
||||
|
||||
49
cognee-frontend/src/ui/elements/Modal/useModal.ts
Normal file
49
cognee-frontend/src/ui/elements/Modal/useModal.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
361
cognee-frontend/src/ui/elements/Notebook/Notebook.tsx
Normal file
361
cognee-frontend/src/ui/elements/Notebook/Notebook.tsx
Normal 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),
|
||||
// };
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
1
cognee-frontend/src/ui/elements/Notebook/index.ts
Normal file
1
cognee-frontend/src/ui/elements/Notebook/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./Notebook";
|
||||
15
cognee-frontend/src/ui/elements/Notebook/types.ts
Normal file
15
cognee-frontend/src/ui/elements/Notebook/types.ts
Normal 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;
|
||||
}
|
||||
48
cognee-frontend/src/ui/elements/PopupMenu.tsx
Normal file
48
cognee-frontend/src/ui/elements/PopupMenu.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
4
cognee-frontend/src/utils/isCloudEnvironment.ts
Normal file
4
cognee-frontend/src/utils/isCloudEnvironment.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export default function isCloudEnvironment() {
|
||||
return process.env.NEXT_PUBLIC_IS_CLOUD_ENVIRONMENT?.toLowerCase() === "true";
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
25
cognee-frontend/src/utils/useOutsideClick.ts
Normal file
25
cognee-frontend/src/utils/useOutsideClick.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)})
|
||||
|
||||
|
|
|
|||
1
cognee/api/v1/cloud/routers/__init__.py
Normal file
1
cognee/api/v1/cloud/routers/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .get_checks_router import get_checks_router
|
||||
23
cognee/api/v1/cloud/routers/get_checks_router.py
Normal file
23
cognee/api/v1/cloud/routers/get_checks_router.py
Normal 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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue