├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── cortexon_arch.png └── cortexon_flow.png ├── cortex_on ├── .gitignore ├── Dockerfile ├── README.md ├── __init__.py ├── agents │ ├── code_agent.py │ ├── orchestrator_agent.py │ ├── planner_agent.py │ └── web_surfer.py ├── instructor.py ├── main.py ├── requirements.txt └── utils │ ├── __init__.py │ ├── ant_client.py │ ├── calculate_md5_hash_of_file.py │ ├── cancellation_token.py │ ├── convert_messages.py │ ├── executors │ ├── __init__.py │ ├── executor_utils │ │ ├── __init__.py │ │ ├── _base.py │ │ ├── _common.py │ │ ├── _func_with_reqs.py │ │ └── extract_command_line_args.py │ └── local_code_executor.py │ ├── get_openai_format_json_messages_from_pydantic_message_response.py │ ├── image.py │ ├── markdown_browser │ ├── __init__.py │ ├── abstract_markdown_browser.py │ ├── markdown_search.py │ ├── mdconvert.py │ └── requests_markdown_browser.py │ ├── message_handler.py │ ├── models.py │ ├── prompts.py │ ├── stream_response_format.py │ └── types.py ├── docker-compose.yaml ├── frontend ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ ├── CortexON_logo_dark.svg │ │ ├── Favicon-contexton.svg │ │ └── react.svg │ ├── components │ │ ├── home │ │ │ ├── Chat.tsx │ │ │ ├── ChatList.tsx │ │ │ ├── CodeBlock.tsx │ │ │ ├── ErrorAlert.tsx │ │ │ ├── Header.tsx │ │ │ ├── Landing.tsx │ │ │ ├── Loading.tsx │ │ │ └── TerminalBlock.tsx │ │ ├── theme-provider.tsx │ │ ├── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── passwordInput.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── skeleton.tsx │ │ │ └── textarea.tsx │ │ └── vault │ │ │ ├── AddCreds.tsx │ │ │ ├── DeleteAlert.tsx │ │ │ └── VaultList.tsx │ ├── constants │ │ └── Landing.ts │ ├── dataStore │ │ ├── messagesSlice.ts │ │ └── store.ts │ ├── index.css │ ├── lib │ │ └── utils.ts │ ├── main.tsx │ ├── pages │ │ ├── Home.tsx │ │ ├── Layout.tsx │ │ └── Vault.tsx │ ├── services │ │ ├── platformConfig.ts │ │ └── vaultApi.ts │ ├── types │ │ ├── chatTypes.ts │ │ └── vaultTypes.ts │ └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts └── ta-browser ├── .dockerignore ├── .gitignore ├── Dockerfile ├── TheAgenticBrowser.png ├── config.py ├── core ├── agents │ ├── browser_agent.py │ ├── critique_agent.py │ ├── explainer_agent.py │ └── planner_agent.py ├── browser_manager.py ├── main.py ├── orchestrator.py ├── server │ ├── __init__.py │ ├── check_status.py │ ├── constants.py │ ├── main.py │ ├── models │ │ └── web.py │ ├── routes │ │ ├── vault.py │ │ └── web.py │ └── utils │ │ ├── server_logger.py │ │ ├── session_tracker.py │ │ ├── timeout.py │ │ ├── vault_exceptions.py │ │ └── vault_operations.py ├── skills │ ├── click_using_selector.py │ ├── enter_text_and_click.py │ ├── enter_text_using_selector.py │ ├── final_response.py │ ├── get_dom_with_content_type.py │ ├── get_url.py │ ├── google_search.py │ ├── hashicorp.py │ ├── open_url.py │ ├── pdf_text_extractor.py │ └── press_key_combination.py └── utils │ ├── anthropic_client.py │ ├── convert_openai.py │ ├── custom_exceptions.py │ ├── dom_helper.py │ ├── dom_mutation_observer.py │ ├── get_detailed_accessibility_tree.py │ ├── init_client.py │ ├── js_helper.py │ ├── logger.py │ ├── message_type.py │ ├── notification.py │ ├── open_ai_verfication_script.py │ ├── openai_client.py │ ├── openai_msg_parser.py │ ├── ui_manager.py │ └── ui_messagetype.py └── requirements.txt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: 7 | 8 | --- 9 | 10 | --- 11 | name: 🐞 Bug Report 12 | about: Report a reproducible bug or unexpected behavior 13 | --- 14 | 15 | ### Description of the issue: 16 | 17 | ### Steps to reproduce: 18 | 19 | 1. 20 | 2. 21 | 3. 22 | 23 | ### Expected behavior: 24 | 25 | ### Actual behavior: 26 | 27 | ### Screenshots/Logs [Mandatory]: 28 | Include screenshots and Logfire logs to help us identify and debug the issue. 29 | 30 | ### Environment (OS, Browser): 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | !.vscode/extensions.json 3 | .idea 4 | .DS_Store 5 | 6 | .env 7 | 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | product@theagentic.ai. 64 | 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CortexON 2 | 3 | Welcome to the CortexON community! We're thrilled that you're interested in contributing to our project. This document provides guidelines and instructions to help you contribute effectively. Every contribution matters, whether it's fixing a typo, improving documentation, or implementing new features. 4 | 5 | ## Table of Contents 6 | - [Introduction](#introduction) 7 | - [How to Contribute](#how-to-contribute) 8 | - [Code Style & Standards](#code-style--standards) 9 | - [Documentation](#documentation) 10 | - [Pull Request Process](#pull-request-process) 11 | - [Code of Conduct](#code-of-conduct) 12 | - [Additional Resources](#additional-resources) 13 | 14 | ## Introduction 15 | 16 | The project aims to democratize a generalized AI agent, and make capabilities of the likes of OpenAI DeepResearch and ManusAI open-source for all. We believe in the power of community-driven development and welcome contributions from developers of all skill levels and backgrounds. Your contributions help make this project better for everyone. 17 | 18 | ## How to Contribute 19 | 20 | ### Types of Contributions 21 | We welcome various types of contributions, including: 22 | - Bug fixes 23 | - New features 24 | - Documentation improvements 25 | - UI/UX enhancements 26 | - Test coverage improvements 27 | - Performance optimizations 28 | 29 | ### Getting Started 30 | 31 | 1. **Fork the Repository** 32 | ```bash 33 | # Clone your fork locally 34 | git clone https://github.com/your-username/CortexOn.git 35 | cd CortexOn 36 | 37 | # Add the upstream repository 38 | git remote add upstream https://github.com/TheAgenticAI/CortexOn.git 39 | ``` 40 | 41 | 2. **Create a Branch** 42 | ```bash 43 | # Ensure you're up to date 44 | git checkout main 45 | git pull upstream main 46 | 47 | # Create and switch to a new branch 48 | git checkout -b feature/your-feature-name 49 | ``` 50 | 51 | 3. **Make Your Changes** 52 | - Write your code 53 | - Add or update tests as needed 54 | - Update documentation if required 55 | 56 | 4. **Submit Your Contribution** 57 | ```bash 58 | # Stage and commit your changes 59 | git add . 60 | git commit -m "feat: add new feature" 61 | 62 | # Push to your fork 63 | git push origin feature/your-feature-name 64 | ``` 65 | 66 | ### Reporting Issues 67 | - Use the GitHub issue tracker to report bugs or suggest features 68 | - Check if a similar issue already exists before creating a new one 69 | - Use the provided issue templates when available 70 | - Include as much relevant information as possible: 71 | - Steps to reproduce the issue 72 | - Expected vs actual behavior 73 | - Screenshots if applicable 74 | - Environment details 75 | 76 | ## Code Style & Standards 77 | 78 | ### Coding Conventions 79 | - Use consistent indentation (2 or 4 spaces) 80 | - Follow the existing code style 81 | - Write clear, descriptive variable and function names 82 | - Add comments for complex logic 83 | - Keep functions focused and concise 84 | 85 | ### Commit Message Guidelines 86 | We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: 87 | 88 | ``` 89 | (): 90 | 91 | [optional body] 92 | 93 | [optional footer] 94 | ``` 95 | 96 | Types include: 97 | - feat: New feature 98 | - fix: Bug fix 99 | - docs: Documentation changes 100 | - style: Code style changes 101 | - refactor: Code refactoring 102 | - test: Adding or updating tests 103 | - chore: Maintenance tasks 104 | 105 | Example: 106 | ``` 107 | feat(auth): add OAuth2 authentication support 108 | 109 | Implement OAuth2 authentication using Google provider. 110 | Includes user profile fetching and token refresh logic. 111 | 112 | Closes #123 113 | ``` 114 | 115 | ## Documentation 116 | 117 | - Update README.md if adding new features 118 | - Add JSDoc comments for new functions and classes 119 | - Update API documentation if changing interfaces 120 | - Include examples for new functionality 121 | 122 | ## Pull Request Process 123 | 124 | 1. **Before Submitting** 125 | - Ensure all tests pass 126 | - Update documentation as needed 127 | - Rebase your branch on the latest main 128 | 129 | 2. **PR Guidelines** 130 | - Link related issues 131 | - Provide a clear description of changes 132 | - Include screenshots for UI changes 133 | - List any breaking changes 134 | 135 | 3. **Review Process** 136 | - Two approvals required for merge 137 | - Address review feedback promptly 138 | - Keep the PR focused and reasonable in size 139 | - Be open to suggestions and improvements 140 | 141 | 4. **After Merge** 142 | - Delete your branch 143 | - Update any related issues 144 | - Help review other PRs 145 | 146 | ## Code of Conduct 147 | 148 | We take our open source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). 149 | 150 | The full Code of Conduct document details: 151 | - Our pledge to make participation a harassment-free experience 152 | - Specific examples of encouraged positive behavior 153 | - Unacceptable behavior that will not be tolerated 154 | - The scope of applicability 155 | - Enforcement and reporting procedures 156 | - Detailed enforcement guidelines and consequences 157 | 158 | If you witness or experience any violations of our Code of Conduct, please report them to our project team at product@theagentic.ai. All reports will be handled with discretion and confidentiality. 159 | 160 | ## Additional Resources 161 | 162 | - [Project Documentation](docs/README.md) 163 | - [API Reference](docs/api.md) 164 | - Community Channel: 165 | - [Discord Server](https://discord.gg/f6pXswy2h6) 166 | 167 | 168 | ### License 169 | By contributing to this project, you agree that your contributions will be licensed under its [LICENSE](LICENSE) terms. 170 | 171 | --- 172 | 173 | Thank you for considering contributing to our project! If you have any questions, feel free to reach out to the maintainers or ask in our community channels. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CortexON Open Source License Agreement 2 | 3 | Version 1.0 4 | Effective Date: March 13, 2025 5 | 6 | This Open Source License Agreement (the "Agreement") governs the use, modification, 7 | and distribution of CortexON (the "AI Agent"). 8 | 9 | 1. Grant of License 10 | 11 | Subject to the terms of this Agreement, the Licensor grants to you ("Licensee") 12 | a worldwide, royalty-free, non-exclusive, irrevocable (except as stated herein) 13 | license to use, modify, distribute, and reproduce CortexON, subject to the 14 | restrictions set forth below. 15 | 16 | 2. Permitted Uses 17 | 18 | Licensee may: 19 | 20 | (a) Use CortexON for research, commercial, and non-commercial applications. 21 | 22 | (b) Modify, adapt, and improve CortexON, provided that any modifications are 23 | documented and attributed to the modifying party. 24 | 25 | (c) Distribute CortexON and derivative works under the terms of this Agreement. 26 | 27 | (d) Commercially use or resell CortexON, provided it is branded as CortexON 28 | and includes a mention of any modifications made by the Licensee. 29 | 30 | 3. Prohibited Uses 31 | 32 | No Rebranding or Competing Branded Projects 33 | 34 | Licensee may not: 35 | 36 | (a) Rebrand CortexON under a different name. 37 | 38 | (b) Create or promote a similar open-source or commercial product using 39 | CortexON under a different brand name. 40 | 41 | 4. Compliance with Third-Party Licenses 42 | 43 | CortexON may incorporate open-source components subject to separate licensing 44 | terms. Licensee must comply with the terms of any third-party licenses when 45 | using such components. 46 | 47 | 5. Attribution 48 | 49 | If Licensee distributes or publicly uses CortexON or derivative works, they must 50 | include appropriate attribution to CortexON and a copy of this Agreement. Any 51 | modifications must be clearly documented and attributed to the modifying party. 52 | 53 | 6. Enforcement & Termination 54 | 55 | 6.1 Violation of Terms 56 | If Licensee breaches this Agreement, their rights under this license are 57 | automatically terminated. 58 | 59 | 6.2 Reinstatement 60 | Licensor may, at its discretion, reinstate Licensee’s rights if the violation 61 | is remedied. 62 | 63 | 7. Disclaimer of Warranties 64 | 65 | CORTEXON IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. 66 | LICENSOR DISCLAIMS ALL WARRANTIES, INCLUDING MERCHANTABILITY AND FITNESS FOR A 67 | PARTICULAR PURPOSE. 68 | 69 | 8. Limitation of Liability 70 | 71 | LICENSOR SHALL NOT BE LIABLE FOR ANY DAMAGES ARISING FROM THE USE OF CORTEXON, 72 | INCLUDING DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES. 73 | 74 | 9. Governing Law 75 | 76 | This Agreement shall be governed by the laws of the State of California, US, 77 | without regard to conflict of law principles. 78 | 79 | -------------------------------------------------------------------------------- /assets/cortexon_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAgenticAI/CortexON/3b00aab495861d6a7826c317101f8e069a3677f0/assets/cortexon_arch.png -------------------------------------------------------------------------------- /assets/cortexon_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAgenticAI/CortexON/3b00aab495861d6a7826c317101f8e069a3677f0/assets/cortexon_flow.png -------------------------------------------------------------------------------- /cortex_on/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | terminal_logs/* 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # UV 99 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 100 | # This is especially recommended for binary packages to ensure reproducibility, and is more 101 | # commonly ignored for libraries. 102 | #uv.lock 103 | 104 | # poetry 105 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 106 | # This is especially recommended for binary packages to ensure reproducibility, and is more 107 | # commonly ignored for libraries. 108 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 109 | #poetry.lock 110 | 111 | # pdm 112 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 113 | #pdm.lock 114 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 115 | # in version control. 116 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 117 | .pdm.toml 118 | .pdm-python 119 | .pdm-build/ 120 | 121 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 122 | __pypackages__/ 123 | 124 | # Celery stuff 125 | celerybeat-schedule 126 | celerybeat.pid 127 | 128 | # SageMath parsed files 129 | *.sage.py 130 | 131 | # Environments 132 | ../.env 133 | .venv 134 | env/ 135 | venv/ 136 | ENV/ 137 | env.bak/ 138 | venv.bak/ 139 | 140 | # Spyder project settings 141 | .spyderproject 142 | .spyproject 143 | 144 | # Rope project settings 145 | .ropeproject 146 | 147 | # mkdocs documentation 148 | /site 149 | 150 | # mypy 151 | .mypy_cache/ 152 | .dmypy.json 153 | dmypy.json 154 | 155 | # Pyre type checker 156 | .pyre/ 157 | 158 | # pytype static type analyzer 159 | .pytype/ 160 | 161 | # Cython debug symbols 162 | cython_debug/ 163 | 164 | # PyCharm 165 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 166 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 167 | # and can be added to the global gitignore or merged into this file. For a more nuclear 168 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 169 | #.idea/ 170 | 171 | # PyPI configuration file 172 | .pypirc 173 | 174 | .logfire 175 | code_files 176 | planner -------------------------------------------------------------------------------- /cortex_on/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt . 6 | RUN pip install uv 7 | RUN apt-get update && apt-get install -y \ 8 | build-essential \ 9 | cmake \ 10 | g++ \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | RUN export PYTHONPATH=/app 14 | RUN apt-get update -y && apt-get install build-essential -y 15 | 16 | # Add the --system flag to uv pip install 17 | RUN uv pip install --system --no-cache-dir -r requirements.txt 18 | 19 | COPY . . 20 | 21 | EXPOSE 8081 22 | 23 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8081"] -------------------------------------------------------------------------------- /cortex_on/README.md: -------------------------------------------------------------------------------- 1 | # TheCortexOn 2 | - install dependencies using `pip install -r requirements.txt` 3 | - configure `.env` (using example `.env.copy`) 4 | - either run `python -m src.main` in root folder 5 | - or run `uvicorn --reload --access-log --host 0.0.0.0 --port 8001 src.main:app` to use with frontend 6 | -------------------------------------------------------------------------------- /cortex_on/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAgenticAI/CortexON/3b00aab495861d6a7826c317101f8e069a3677f0/cortex_on/__init__.py -------------------------------------------------------------------------------- /cortex_on/instructor.py: -------------------------------------------------------------------------------- 1 | # Standard library imports 2 | import json 3 | import os 4 | import traceback 5 | from dataclasses import asdict 6 | from datetime import datetime 7 | from typing import Any, Dict, List, Optional, Tuple, Union 8 | 9 | # Third-party imports 10 | from dotenv import load_dotenv 11 | from fastapi import WebSocket 12 | import logfire 13 | from pydantic import BaseModel 14 | from pydantic_ai import Agent 15 | from pydantic_ai.messages import ModelMessage 16 | from pydantic_ai.models.anthropic import AnthropicModel 17 | 18 | # Local application imports 19 | from agents.code_agent import coder_agent 20 | from agents.orchestrator_agent import orchestrator_agent, orchestrator_deps 21 | from agents.planner_agent import planner_agent 22 | from agents.web_surfer import WebSurfer 23 | from utils.ant_client import get_client 24 | from utils.stream_response_format import StreamResponse 25 | 26 | load_dotenv() 27 | 28 | 29 | 30 | 31 | class DateTimeEncoder(json.JSONEncoder): 32 | """Custom JSON encoder that can handle datetime objects""" 33 | def default(self, obj): 34 | if isinstance(obj, datetime): 35 | return obj.isoformat() 36 | return super().default(obj) 37 | 38 | 39 | # Main Orchestrator Class 40 | class SystemInstructor: 41 | def __init__(self): 42 | self.websocket: Optional[WebSocket] = None 43 | self.stream_output: Optional[StreamResponse] = None 44 | self.orchestrator_response: List[StreamResponse] = [] 45 | self._setup_logging() 46 | 47 | def _setup_logging(self) -> None: 48 | """Configure logging with proper formatting""" 49 | logfire.configure( 50 | send_to_logfire='if-token-present', 51 | token=os.getenv("LOGFIRE_TOKEN"), 52 | scrubbing=False, 53 | ) 54 | 55 | async def _safe_websocket_send(self, message: Any) -> bool: 56 | """Safely send message through websocket with error handling""" 57 | try: 58 | if self.websocket and self.websocket.client_state.CONNECTED: 59 | await self.websocket.send_text(json.dumps(asdict(message))) 60 | logfire.debug(f"WebSocket message sent: {message}") 61 | return True 62 | return False 63 | except Exception as e: 64 | logfire.error(f"WebSocket send failed: {str(e)}") 65 | return False 66 | 67 | async def run(self, task: str, websocket: WebSocket) -> List[Dict[str, Any]]: 68 | """Main orchestration loop with comprehensive error handling""" 69 | self.websocket = websocket 70 | stream_output = StreamResponse( 71 | agent_name="Orchestrator", 72 | instructions=task, 73 | steps=[], 74 | output="", 75 | status_code=0 76 | ) 77 | self.orchestrator_response.append(stream_output) 78 | 79 | # Create dependencies with list to track agent responses 80 | deps_for_orchestrator = orchestrator_deps( 81 | websocket=self.websocket, 82 | stream_output=stream_output, 83 | agent_responses=self.orchestrator_response # Pass reference to collection 84 | ) 85 | 86 | try: 87 | # Initialize system 88 | await self._safe_websocket_send(stream_output) 89 | stream_output.steps.append("Agents initialized successfully") 90 | await self._safe_websocket_send(stream_output) 91 | 92 | orchestrator_response = await orchestrator_agent.run( 93 | user_prompt=task, 94 | deps=deps_for_orchestrator 95 | ) 96 | stream_output.output = orchestrator_response.data 97 | stream_output.status_code = 200 98 | logfire.debug(f"Orchestrator response: {orchestrator_response.data}") 99 | await self._safe_websocket_send(stream_output) 100 | 101 | logfire.info("Task completed successfully") 102 | return [json.loads(json.dumps(asdict(i), cls=DateTimeEncoder)) for i in self.orchestrator_response] 103 | 104 | except Exception as e: 105 | error_msg = f"Critical orchestration error: {str(e)}\n{traceback.format_exc()}" 106 | logfire.error(error_msg) 107 | 108 | if stream_output: 109 | stream_output.output = error_msg 110 | stream_output.status_code = 500 111 | self.orchestrator_response.append(stream_output) 112 | await self._safe_websocket_send(stream_output) 113 | 114 | # Even in case of critical error, return what we have 115 | return [asdict(i) for i in self.orchestrator_response] 116 | 117 | finally: 118 | logfire.info("Orchestration process complete") 119 | # Clear any sensitive data 120 | async def shutdown(self): 121 | """Clean shutdown of orchestrator""" 122 | try: 123 | # Close websocket if open 124 | if self.websocket: 125 | await self.websocket.close() 126 | 127 | # Clear all responses 128 | self.orchestrator_response = [] 129 | 130 | logfire.info("Orchestrator shutdown complete") 131 | 132 | except Exception as e: 133 | logfire.error(f"Error during shutdown: {str(e)}") 134 | raise -------------------------------------------------------------------------------- /cortex_on/main.py: -------------------------------------------------------------------------------- 1 | # Standard library imports 2 | from typing import List, Optional 3 | 4 | # Third-party imports 5 | from fastapi import FastAPI, WebSocket 6 | 7 | # Local application imports 8 | from instructor import SystemInstructor 9 | 10 | 11 | app: FastAPI = FastAPI() 12 | 13 | async def generate_response(task: str, websocket: Optional[WebSocket] = None): 14 | orchestrator: SystemInstructor = SystemInstructor() 15 | return await orchestrator.run(task, websocket) 16 | 17 | @app.get("/agent/chat") 18 | async def agent_chat(task: str) -> List: 19 | final_agent_response = await generate_response(task) 20 | return final_agent_response 21 | 22 | @app.websocket("/ws") 23 | async def websocket_endpoint(websocket: WebSocket): 24 | await websocket.accept() 25 | while True: 26 | data = await websocket.receive_text() 27 | await generate_response(data, websocket) 28 | -------------------------------------------------------------------------------- /cortex_on/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohappyeyeballs==2.4.4 2 | aiohttp==3.11.11 3 | aiosignal==1.3.2 4 | annotated-types==0.7.0 5 | anthropic==0.42.0 6 | anyio==4.7.0 7 | asyncio-atexit==1.0.1 8 | attrs==24.3.0 9 | beautifulsoup4==4.12.3 10 | cachetools==5.5.0 11 | certifi==2024.12.14 12 | cffi==1.17.1 13 | charset-normalizer==3.4.1 14 | click==8.1.8 15 | cobble==0.1.4 16 | colorama==0.4.6 17 | cryptography==44.0.0 18 | Deprecated==1.2.15 19 | distro==1.9.0 20 | docker==7.1.0 21 | eval_type_backport==0.2.2 22 | executing==2.1.0 23 | fastapi==0.115.6 24 | frozenlist==1.5.0 25 | google-auth==2.37.0 26 | googleapis-common-protos==1.66.0 27 | griffe==1.5.4 28 | groq==0.13.1 29 | h11==0.14.0 30 | httpcore==1.0.7 31 | httpx==0.27.2 32 | idna==3.10 33 | importlib_metadata==8.5.0 34 | jiter==0.8.2 35 | jsonpath-python==1.0.6 36 | logfire==2.11.0 37 | logfire-api==2.11.0 38 | lxml==5.3.0 39 | mammoth==1.8.0 40 | markdown-it-py==3.0.0 41 | markdownify==0.14.1 42 | mdurl==0.1.2 43 | mistralai==1.2.5 44 | multidict==6.1.0 45 | mypy-extensions==1.0.0 46 | numpy==2.2.1 47 | openai==1.58.1 48 | opentelemetry-api==1.29.0 49 | opentelemetry-exporter-otlp-proto-common==1.29.0 50 | opentelemetry-exporter-otlp-proto-http==1.29.0 51 | opentelemetry-instrumentation==0.50b0 52 | opentelemetry-proto==1.29.0 53 | opentelemetry-sdk==1.29.0 54 | opentelemetry-semantic-conventions==0.50b0 55 | packaging==24.2 56 | pandas==2.2.3 57 | pathvalidate==3.2.1 58 | pdfminer.six==20240706 59 | pillow==11.0.0 60 | propcache==0.2.1 61 | protobuf==5.29.2 62 | puremagic==1.28 63 | pyasn1==0.6.1 64 | pyasn1_modules==0.4.1 65 | pycparser==2.22 66 | pycryptodome==3.21.0 67 | pydantic==2.10.4 68 | pydantic-ai==0.0.17 69 | pydantic-ai-slim==0.0.17 70 | pydantic_core==2.27.2 71 | Pygments==2.18.0 72 | python-dateutil==2.9.0.post0 73 | python-dotenv==1.0.1 74 | python-pptx==1.0.2 75 | pytz==2024.2 76 | requests==2.32.3 77 | rich==13.9.4 78 | rsa==4.9 79 | six==1.17.0 80 | sniffio==1.3.1 81 | soupsieve==2.6 82 | starlette==0.41.3 83 | tqdm==4.67.1 84 | typing-inspect==0.9.0 85 | typing_extensions==4.12.2 86 | tzdata==2024.2 87 | urllib3==2.3.0 88 | uvicorn==0.34.0 89 | websockets==14.1 90 | wrapt==1.17.0 91 | XlsxWriter==3.2.0 92 | yarl==1.18.3 93 | zipp==3.21.0 94 | fast-graphrag==0.0.4 95 | llama_parse==0.5.19 -------------------------------------------------------------------------------- /cortex_on/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .cancellation_token import CancellationToken 2 | -------------------------------------------------------------------------------- /cortex_on/utils/ant_client.py: -------------------------------------------------------------------------------- 1 | from pydantic_ai.models.anthropic import AnthropicModel 2 | from anthropic import AsyncAnthropic 3 | import os 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | def get_client(): 9 | api_key = os.getenv("ANTHROPIC_API_KEY") 10 | 11 | client = AsyncAnthropic(api_key=api_key, 12 | max_retries=3, 13 | timeout=10000) 14 | return client 15 | -------------------------------------------------------------------------------- /cortex_on/utils/calculate_md5_hash_of_file.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | def calculate_md5(file_path): 4 | # Create an MD5 hash object 5 | md5_hash = hashlib.md5() 6 | 7 | # Open the file in binary mode 8 | with open(file_path, "rb") as f: 9 | # Read and update the hash in chunks to handle large files 10 | for chunk in iter(lambda: f.read(4096), b""): 11 | md5_hash.update(chunk) 12 | 13 | # Return the hexadecimal digest of the hash 14 | return md5_hash.hexdigest() -------------------------------------------------------------------------------- /cortex_on/utils/cancellation_token.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from asyncio import Future 3 | from typing import Any, Callable, List 4 | 5 | class CancellationToken: 6 | """A token used to cancel pending async calls""" 7 | 8 | def __init__(self) -> None: 9 | self._cancelled: bool = False 10 | self._lock: threading.Lock = threading.Lock() 11 | self._callbacks: List[Callable[[], None]] = [] 12 | 13 | def cancel(self) -> None: 14 | """Cancel pending async calls linked to this cancellation token.""" 15 | with self._lock: 16 | if not self._cancelled: 17 | self._cancelled = True 18 | for callback in self._callbacks: 19 | callback() 20 | 21 | def is_cancelled(self) -> bool: 22 | """Check if the CancellationToken has been used""" 23 | with self._lock: 24 | return self._cancelled 25 | 26 | def add_callback(self, callback: Callable[[], None]) -> None: 27 | """Attach a callback that will be called when cancel is invoked""" 28 | with self._lock: 29 | if self._cancelled: 30 | callback() 31 | else: 32 | self._callbacks.append(callback) 33 | 34 | def link_future(self, future: Future[Any]) -> Future[Any]: 35 | """Link a pending async call to a token to allow its cancellation""" 36 | with self._lock: 37 | if self._cancelled: 38 | future.cancel() 39 | else: 40 | 41 | def _cancel() -> None: 42 | future.cancel() 43 | self._callbacks.append(_cancel) 44 | return future 45 | -------------------------------------------------------------------------------- /cortex_on/utils/convert_messages.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | from utils.types import FunctionCall, FunctionExecutionResult 3 | from utils.image import Image 4 | 5 | # Convenience type 6 | UserContent = Union[str, List[Union[str, Image]]] 7 | AssistantContent = Union[str, List[FunctionCall]] 8 | FunctionExecutionContent = List[FunctionExecutionResult] 9 | SystemContent = str 10 | 11 | # Convert UserContent to a string 12 | def message_content_to_str( 13 | message_content: ( 14 | UserContent | AssistantContent | SystemContent | FunctionExecutionContent 15 | ), 16 | ) -> str: 17 | if isinstance(message_content, str): 18 | return message_content 19 | elif isinstance(message_content, List): 20 | converted: List[str] = list() 21 | for item in message_content: 22 | if isinstance(item, str): 23 | converted.append(item.rstrip()) 24 | elif isinstance(item, Image): 25 | converted.append("") 26 | else: 27 | converted.append(str(item).rstrip()) 28 | return "\n".join(converted) 29 | else: 30 | raise AssertionError("Unexpected response type.") 31 | -------------------------------------------------------------------------------- /cortex_on/utils/executors/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .local_code_executor import LocalCommandLineCodeExecutor 3 | 4 | __all__ = ["LocalCommandLineCodeExecutor"] 5 | -------------------------------------------------------------------------------- /cortex_on/utils/executors/executor_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._base import CodeBlock, CodeExecutor, CodeResult 2 | from ._func_with_reqs import ( 3 | Alias, 4 | FunctionWithRequirements, 5 | FunctionWithRequirementsStr, 6 | Import, 7 | ImportFromModule, 8 | with_requirements, 9 | ) 10 | 11 | __all__ = [ 12 | "CodeBlock", 13 | "CodeExecutor", 14 | "CodeResult", 15 | "Alias", 16 | "ImportFromModule", 17 | "Import", 18 | "FunctionWithRequirements", 19 | "FunctionWithRequirementsStr", 20 | "with_requirements", 21 | ] 22 | -------------------------------------------------------------------------------- /cortex_on/utils/executors/executor_utils/_base.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import List, Protocol, runtime_checkable 5 | 6 | from utils import CancellationToken 7 | 8 | @dataclass 9 | class CodeBlock: 10 | """A code block extracted fromm an agent message.""" 11 | 12 | code: str 13 | packages: List 14 | language: str 15 | human_input_or_command_line_args:str 16 | 17 | @dataclass 18 | class CodeResult: 19 | """Result of a code execution.""" 20 | 21 | exit_code: int 22 | output: str 23 | 24 | @runtime_checkable 25 | class CodeExecutor(Protocol): 26 | """Executes code blocks and returns the result.""" 27 | 28 | async def execute_code_blocks( 29 | self, code_blocks: List[CodeBlock], cancellation_token: CancellationToken 30 | ) -> CodeResult: 31 | """Execute code blocks and return the result. 32 | 33 | This method should be implemented by the code executor. 34 | 35 | Args: 36 | code_blocks (List[CodeBlock]): The code blocks to execute. 37 | 38 | Returns: 39 | CodeResult: The result of the code execution. 40 | 41 | Raises: 42 | ValueError: Errors in user inputs 43 | asyncio.TimeoutError: Code execution timeouts 44 | asyncio.CancelledError: CancellationToken evoked during execution 45 | """ 46 | ... 47 | 48 | async def restart(self) -> None: 49 | """Restart the code executor. 50 | 51 | This method should be implemented by the code executor. 52 | 53 | This method is called when the agent is reset. 54 | """ 55 | ... 56 | -------------------------------------------------------------------------------- /cortex_on/utils/executors/executor_utils/extract_command_line_args.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def extract_command_line_args(lang, filename, human_input_or_command_line_args): 4 | human_input_or_command_line_args = " ".join(human_input_or_command_line_args).strip() 5 | 6 | extension = filename.split('.')[-1] if '.' in filename else 'py' if lang.startswith('python') else lang 7 | 8 | # Define prefixes to remove 9 | prefixes = [f"{lang} {filename}", f"{lang}", f"{filename}"] 10 | for prefix in prefixes: 11 | if human_input_or_command_line_args.startswith(prefix): 12 | human_input_or_command_line_args = human_input_or_command_line_args[len(prefix):].strip() 13 | break 14 | 15 | # Split into arguments and filter out matches of *.extension 16 | args = human_input_or_command_line_args.split() 17 | args = [arg for arg in args if not re.fullmatch(rf".*\.{extension}", arg)] 18 | 19 | return args -------------------------------------------------------------------------------- /cortex_on/utils/get_openai_format_json_messages_from_pydantic_message_response.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List, Optional, Tuple 3 | from pydantic_ai.messages import ModelMessage 4 | 5 | def get_openai_format_json_messages_from_pydantic_message_response( 6 | messages: List[ModelMessage], 7 | ): 8 | def get_role(kind, part_kind): 9 | if kind == "request": 10 | if part_kind == "system-prompt": 11 | return "system" 12 | elif part_kind == "user-prompt": 13 | return "user" 14 | elif part_kind == "tool-return": 15 | return "user" 16 | elif part_kind == "retry-prompt": 17 | return "user" 18 | elif kind == "response": 19 | if part_kind == "tool-call": 20 | return "assistant" 21 | elif part_kind == "retry-prompt": 22 | return "user" 23 | elif part_kind == "text": 24 | return "assistant" 25 | json_formatted_messages = [] 26 | 27 | for message in messages: 28 | kind = message.kind 29 | parts = message.parts 30 | for part in parts: 31 | part_dict = part.__dict__ 32 | content = part.content if "content" in part_dict else "" 33 | tool_name = part.tool_name if "tool_name" in part_dict else "" 34 | arguments = part.args if "args" in part_dict else "" 35 | part_kind = part.part_kind 36 | 37 | if content: 38 | json_formatted_messages.append( 39 | {"role": get_role(kind, part_kind), "content": content} 40 | ) 41 | if tool_name: 42 | json_formatted_messages.append( 43 | { 44 | "role": get_role(kind, part_kind), 45 | "tool_name": tool_name, 46 | "arguments": str(arguments), 47 | } 48 | ) 49 | 50 | return json_formatted_messages 51 | 52 | def convert_json_to_string_messages(json_messages): 53 | string_messages = "" 54 | for message in json_messages: 55 | string_messages += ( 56 | f"\n{message['role']}: {message['content']}\n" 57 | if "content" in message 58 | else f"{message['role']}: {message['tool_name']}\nArguments: {message['arguments']}\n" 59 | ) 60 | return string_messages 61 | -------------------------------------------------------------------------------- /cortex_on/utils/image.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import base64 4 | import re 5 | from io import BytesIO 6 | from pathlib import Path 7 | from typing import Any, cast 8 | 9 | import aiohttp 10 | from openai.types.chat import ChatCompletionContentPartImageParam 11 | from PIL import Image as PILImage 12 | from pydantic import GetCoreSchemaHandler, ValidationInfo 13 | from pydantic_core import core_schema 14 | from typing_extensions import Literal 15 | 16 | class Image: 17 | def __init__(self, image: PILImage.Image): 18 | self.image: PILImage.Image = image.convert("RGB") 19 | 20 | @classmethod 21 | def from_pil(cls, pil_image: PILImage.Image) -> Image: 22 | return cls(pil_image) 23 | 24 | @classmethod 25 | def from_uri(cls, uri: str) -> Image: 26 | if not re.match(r"data:image/(?:png|jpeg);base64,", uri): 27 | raise ValueError( 28 | "Invalid URI format. It should be a base64 encoded image URI." 29 | ) 30 | 31 | # A URI. Remove the prefix and decode the base64 string. 32 | base64_data = re.sub(r"data:image/(?:png|jpeg);base64,", "", uri) 33 | return cls.from_base64(base64_data) 34 | 35 | @classmethod 36 | async def from_url(cls, url: str) -> Image: 37 | async with aiohttp.ClientSession() as session: 38 | async with session.get(url) as response: 39 | content = await response.read() 40 | return cls(PILImage.open(content)) 41 | 42 | @classmethod 43 | def from_base64(cls, base64_str: str) -> Image: 44 | return cls(PILImage.open(BytesIO(base64.b64decode(base64_str)))) 45 | 46 | def to_base64(self) -> str: 47 | buffered = BytesIO() 48 | self.image.save(buffered, format="PNG") 49 | content = buffered.getvalue() 50 | return base64.b64encode(content).decode("utf-8") 51 | 52 | @classmethod 53 | def from_file(cls, file_path: Path) -> Image: 54 | return cls(PILImage.open(file_path)) 55 | 56 | def _repr_html_(self) -> str: 57 | # Show the image in Jupyter notebook 58 | return f'' 59 | 60 | @property 61 | def data_uri(self) -> str: 62 | return _convert_base64_to_data_uri(self.to_base64()) 63 | 64 | def to_openai_format( 65 | self, detail: Literal["auto", "low", "high"] = "auto" 66 | ) -> ChatCompletionContentPartImageParam: 67 | return { 68 | "type": "image_url", 69 | "image_url": {"url": self.data_uri, "detail": detail}, 70 | } 71 | 72 | @classmethod 73 | def __get_pydantic_core_schema__( 74 | cls, source_type: Any, handler: GetCoreSchemaHandler 75 | ) -> core_schema.CoreSchema: 76 | # Custom validation 77 | def validate(value: Any, validation_info: ValidationInfo) -> Image: 78 | if isinstance(value, dict): 79 | base_64 = cast(str | None, value.get("data")) # type: ignore 80 | if base_64 is None: 81 | raise ValueError("Expected 'data' key in the dictionary") 82 | return cls.from_base64(base_64) 83 | elif isinstance(value, cls): 84 | return value 85 | else: 86 | raise TypeError( 87 | f"Expected dict or {cls.__name__} instance, got {type(value)}" 88 | ) 89 | # Custom serialization 90 | def serialize(value: Image) -> dict[str, Any]: 91 | return {"data": value.to_base64()} 92 | return core_schema.with_info_after_validator_function( 93 | validate, 94 | core_schema.any_schema(), # Accept any type; adjust if needed 95 | serialization=core_schema.plain_serializer_function_ser_schema(serialize), 96 | ) 97 | 98 | def _convert_base64_to_data_uri(base64_image: str) -> str: 99 | def _get_mime_type_from_data_uri(base64_image: str) -> str: 100 | # Decode the base64 string 101 | image_data = base64.b64decode(base64_image) 102 | # Check the first few bytes for known signatures 103 | if image_data.startswith(b"\xff\xd8\xff"): 104 | return "image/jpeg" 105 | elif image_data.startswith(b"\x89PNG\r\n\x1a\n"): 106 | return "image/png" 107 | elif image_data.startswith(b"GIF87a") or image_data.startswith(b"GIF89a"): 108 | return "image/gif" 109 | elif image_data.startswith(b"RIFF") and image_data[8:12] == b"WEBP": 110 | return "image/webp" 111 | return "image/jpeg" # use jpeg for unknown formats, best guess. 112 | mime_type = _get_mime_type_from_data_uri(base64_image) 113 | data_uri = f"data:{mime_type};base64,{base64_image}" 114 | return data_uri 115 | -------------------------------------------------------------------------------- /cortex_on/utils/markdown_browser/__init__.py: -------------------------------------------------------------------------------- 1 | from .abstract_markdown_browser import AbstractMarkdownBrowser 2 | from .markdown_search import AbstractMarkdownSearch, BingMarkdownSearch 3 | 4 | # TODO: Fix mdconvert 5 | from .mdconvert import ( # type: ignore 6 | DocumentConverterResult, 7 | FileConversionException, 8 | MarkdownConverter, 9 | UnsupportedFormatException, 10 | ) 11 | from .requests_markdown_browser import RequestsMarkdownBrowser 12 | 13 | __all__ = ( 14 | "AbstractMarkdownBrowser", 15 | "RequestsMarkdownBrowser", 16 | "AbstractMarkdownSearch", 17 | "BingMarkdownSearch", 18 | "MarkdownConverter", 19 | "UnsupportedFormatException", 20 | "FileConversionException", 21 | "DocumentConverterResult", 22 | ) 23 | -------------------------------------------------------------------------------- /cortex_on/utils/markdown_browser/abstract_markdown_browser.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Union 3 | 4 | class AbstractMarkdownBrowser(ABC): 5 | """ 6 | An abstract class for a Markdown web browser. 7 | 8 | All MarkdownBrowers work by: 9 | 10 | (1) fetching a web page by URL (via requests, Selenium, Playwright, etc.) 11 | (2) converting the page's HTML or DOM to Markdown 12 | (3) operating on the Markdown 13 | 14 | Such browsers are simple, and suitable for read-only agentic use. 15 | They cannot be used to interact with complex web applications. 16 | """ 17 | 18 | @abstractmethod 19 | def __init__(self) -> None: 20 | pass 21 | 22 | @property 23 | @abstractmethod 24 | def address(self) -> str: 25 | pass 26 | 27 | @abstractmethod 28 | def set_address(self, uri_or_path: str) -> None: 29 | pass 30 | 31 | @property 32 | @abstractmethod 33 | def viewport(self) -> str: 34 | pass 35 | 36 | @property 37 | @abstractmethod 38 | def page_content(self) -> str: 39 | pass 40 | 41 | @abstractmethod 42 | def page_down(self) -> None: 43 | pass 44 | 45 | @abstractmethod 46 | def page_up(self) -> None: 47 | pass 48 | 49 | @abstractmethod 50 | def visit_page(self, path_or_uri: str) -> str: 51 | pass 52 | 53 | @abstractmethod 54 | def open_local_file(self, local_path: str) -> str: 55 | pass 56 | 57 | @abstractmethod 58 | def find_on_page(self, query: str) -> Union[str, None]: 59 | pass 60 | 61 | @abstractmethod 62 | def find_next(self) -> Union[str, None]: 63 | pass 64 | -------------------------------------------------------------------------------- /cortex_on/utils/message_handler.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | class BroadcastMessage(BaseModel): 4 | message: str 5 | request_halt: bool = False 6 | -------------------------------------------------------------------------------- /cortex_on/utils/models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Dict, Optional 3 | 4 | class FactModel(BaseModel): 5 | facts: str 6 | 7 | class PlanModel(BaseModel): 8 | plan: str 9 | 10 | class LedgerAnswer(BaseModel): 11 | """Model for individual ledger answers""" 12 | 13 | answer: bool | str 14 | explanation: Optional[str] = None 15 | 16 | class LedgerModel(BaseModel): 17 | """Main ledger state model""" 18 | 19 | is_request_satisfied: LedgerAnswer 20 | is_in_loop: LedgerAnswer 21 | is_progress_being_made: LedgerAnswer 22 | next_speaker: LedgerAnswer 23 | instruction_or_question: LedgerAnswer 24 | -------------------------------------------------------------------------------- /cortex_on/utils/stream_response_format.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Optional 3 | 4 | @dataclass 5 | class StreamResponse: 6 | agent_name: str 7 | instructions: str 8 | steps: List[str] 9 | status_code: int 10 | output: str 11 | live_url: Optional[str] = None 12 | -------------------------------------------------------------------------------- /cortex_on/utils/types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from pydantic import BaseModel 3 | from dataclasses import dataclass 4 | 5 | @dataclass 6 | class FunctionCall: 7 | id: str 8 | # JSON args 9 | arguments: str 10 | # Function to call 11 | name: str 12 | 13 | class FunctionExecutionResult(BaseModel): 14 | content: str 15 | call_id: str 16 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | cortex_on: 5 | build: 6 | context: ./cortex_on 7 | dockerfile: Dockerfile 8 | volumes: 9 | - ./cortex_on:/app 10 | env_file: 11 | - .env 12 | restart: always 13 | network_mode: host 14 | 15 | agentic_browser: 16 | build: 17 | context: ./ta-browser 18 | dockerfile: Dockerfile 19 | volumes: 20 | - ./ta-browser:/app 21 | env_file: 22 | - .env 23 | restart: always 24 | network_mode: host 25 | 26 | frontend: 27 | build: 28 | context: ./frontend 29 | dockerfile: Dockerfile 30 | volumes: 31 | - ./frontend:/app 32 | - /app/node_modules 33 | env_file: 34 | - .env 35 | depends_on: 36 | - cortex_on 37 | - agentic_browser 38 | restart: always 39 | network_mode: host 40 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json . 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 3000 12 | 13 | CMD [ "npm", "run", "dev" ] -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 13 | 14 | ```js 15 | export default tseslint.config({ 16 | extends: [ 17 | // Remove ...tseslint.configs.recommended and replace with this 18 | ...tseslint.configs.recommendedTypeChecked, 19 | // Alternatively, use this for stricter rules 20 | ...tseslint.configs.strictTypeChecked, 21 | // Optionally, add this for stylistic rules 22 | ...tseslint.configs.stylisticTypeChecked, 23 | ], 24 | languageOptions: { 25 | // other options... 26 | parserOptions: { 27 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 28 | tsconfigRootDir: import.meta.dirname, 29 | }, 30 | }, 31 | }) 32 | ``` 33 | 34 | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 35 | 36 | ```js 37 | // eslint.config.js 38 | import reactX from 'eslint-plugin-react-x' 39 | import reactDom from 'eslint-plugin-react-dom' 40 | 41 | export default tseslint.config({ 42 | plugins: { 43 | // Add the react-x and react-dom plugins 44 | 'react-x': reactX, 45 | 'react-dom': reactDom, 46 | }, 47 | rules: { 48 | // other rules... 49 | // Enable its recommended typescript rules 50 | ...reactX.configs['recommended-typescript'].rules, 51 | ...reactDom.configs.recommended.rules, 52 | }, 53 | }) 54 | ``` 55 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/index.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 17 | CortexON 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cortexon-fe", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-accordion": "^1.2.3", 14 | "@radix-ui/react-alert-dialog": "^1.1.6", 15 | "@radix-ui/react-dialog": "^1.1.6", 16 | "@radix-ui/react-label": "^2.1.2", 17 | "@radix-ui/react-scroll-area": "^1.2.3", 18 | "@radix-ui/react-slot": "^1.1.2", 19 | "@reduxjs/toolkit": "^2.6.1", 20 | "@tailwindcss/vite": "^4.0.12", 21 | "class-variance-authority": "^0.7.1", 22 | "clsx": "^2.1.1", 23 | "lucide-react": "^0.479.0", 24 | "react": "^19.0.0", 25 | "react-dom": "^19.0.0", 26 | "react-markdown": "^10.1.0", 27 | "react-redux": "^9.2.0", 28 | "react-router-dom": "^7.3.0", 29 | "react-syntax-highlighter": "^15.6.1", 30 | "react-use-websocket": "^4.13.0", 31 | "rehype-raw": "^7.0.0", 32 | "remark-breaks": "^4.0.0", 33 | "tailwind-merge": "^3.0.2", 34 | "tailwindcss": "^4.0.12", 35 | "tailwindcss-animate": "^1.0.7", 36 | "vite-plugin-svgr": "^4.3.0" 37 | }, 38 | "devDependencies": { 39 | "@eslint/js": "^9.21.0", 40 | "@types/node": "^22.13.10", 41 | "@types/react": "^19.0.10", 42 | "@types/react-dom": "^19.0.4", 43 | "@types/react-syntax-highlighter": "^15.5.13", 44 | "@vitejs/plugin-react-swc": "^3.8.0", 45 | "eslint": "^9.21.0", 46 | "eslint-plugin-react-hooks": "^5.1.0", 47 | "eslint-plugin-react-refresh": "^0.4.19", 48 | "globals": "^15.15.0", 49 | "typescript": "~5.7.2", 50 | "typescript-eslint": "^8.24.1", 51 | "vite": "^6.2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import {BrowserRouter, Route, Routes} from "react-router-dom"; 2 | import Home from "./pages/Home"; 3 | import {Layout} from "./pages/Layout"; 4 | import {Vault} from "./pages/Vault"; 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | }> 11 | } /> 12 | } /> 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /frontend/src/assets/Favicon-contexton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/home/Chat.tsx: -------------------------------------------------------------------------------- 1 | import {RootState} from "@/dataStore/store"; 2 | import {useState} from "react"; 3 | import {useSelector} from "react-redux"; 4 | import ChatList from "./ChatList"; 5 | import Landing from "./Landing"; 6 | 7 | const Chat = () => { 8 | const [isLoading, setIsLoading] = useState(false); 9 | 10 | const messages = useSelector( 11 | (state: RootState) => state.messagesState.messages 12 | ); 13 | 14 | return ( 15 |
16 | {messages.length === 0 ? ( 17 | 18 | ) : ( 19 | 20 | )} 21 |
22 | ); 23 | }; 24 | 25 | export default Chat; 26 | -------------------------------------------------------------------------------- /frontend/src/components/home/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import {useRef, useState} from "react"; 2 | import Markdown from "react-markdown"; 3 | import {Prism as SyntaxHighlighter} from "react-syntax-highlighter"; 4 | import {tomorrow} from "react-syntax-highlighter/dist/esm/styles/prism"; 5 | import rehypeRaw from "rehype-raw"; 6 | import remarkBreaks from "remark-breaks"; 7 | 8 | export const CodeBlock = ({content}: {content: string}) => { 9 | const codeBlock = content.includes("content='") 10 | ? content.split("content='")[1] 11 | : content; 12 | 13 | const [isCopied, setIsCopied] = useState(false); 14 | const codeRef = useRef(null); 15 | 16 | const handleCopyClick = () => { 17 | if (codeRef.current) { 18 | navigator.clipboard 19 | .writeText(codeRef.current.innerText) 20 | .then(() => { 21 | setIsCopied(true); 22 | // Add a visual pulse effect 23 | if (codeRef.current) { 24 | codeRef.current.classList.add("copy-pulse"); 25 | setTimeout(() => { 26 | if (codeRef.current) { 27 | codeRef.current.classList.remove("copy-pulse"); 28 | } 29 | }, 1000); 30 | } 31 | setTimeout(() => setIsCopied(false), 2000); 32 | }) 33 | .catch((err) => console.error("Failed to copy text: ", err)); 34 | } 35 | }; 36 | 37 | return ( 38 |
39 | 45 |
46 | 61 | {String(children).replace(/\n$/, "")} 62 | 63 | ) : ( 64 | 65 | {children} 66 | 67 | ); 68 | }, 69 | }} 70 | /> 71 |
72 |
73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /frontend/src/components/home/ErrorAlert.tsx: -------------------------------------------------------------------------------- 1 | import {AlertCircle} from "lucide-react"; 2 | 3 | import {Alert, AlertDescription} from "@/components/ui/alert"; 4 | 5 | export const ErrorAlert = ({errorMessage}: {errorMessage?: string}) => { 6 | return ( 7 | 8 | 12 | 13 | {errorMessage || "Something went wrong."} 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/components/home/Header.tsx: -------------------------------------------------------------------------------- 1 | import {setMessages} from "@/dataStore/messagesSlice"; 2 | import {MessageCirclePlus} from "lucide-react"; 3 | import {useDispatch} from "react-redux"; 4 | import {useLocation, useNavigate} from "react-router-dom"; 5 | import Logo from "../../assets/CortexON_logo_dark.svg"; 6 | import {Button} from "../ui/button"; 7 | 8 | const Header = () => { 9 | const nav = useNavigate(); 10 | const location = useLocation().pathname; 11 | const dispatch = useDispatch(); 12 | 13 | return ( 14 |
15 |
{ 18 | dispatch(setMessages([])); 19 | nav("/"); 20 | }} 21 | > 22 | Logo 23 |
24 |
25 |
nav("/vault")} 27 | className={`w-[10%] h-full flex justify-center items-center cursor-pointer border-b-2 hover:border-[#BD24CA] ${ 28 | location.includes("/vault") 29 | ? "border-[#BD24CA]" 30 | : "border-background" 31 | }`} 32 | > 33 |

Vault

34 |
35 |
36 | 47 |
48 | ); 49 | }; 50 | 51 | export default Header; 52 | -------------------------------------------------------------------------------- /frontend/src/components/home/Loading.tsx: -------------------------------------------------------------------------------- 1 | // import {Loader2} from "lucide-react"; 2 | import favicon from "../../assets/Favicon-contexton.svg"; 3 | 4 | const LoadingView = () => { 5 | return ( 6 |
7 | {/* Top part with logo and animated dots */} 8 |
9 |
10 | 11 |
12 |
13 |

CortexOn is working on your task

14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | {/* Middle part with animated steps 22 |
23 |
24 |
25 | 29 | 30 | Analyzing input data... 31 | 32 |
33 |
34 | 38 | 39 | Generating solution steps... 40 | 41 |
42 |
43 | 47 | 48 | Running code executor... 49 | 50 |
51 |
52 |
53 | 54 | Bottom part with progress bar 55 |
56 |
57 |
58 |
59 |
*/} 60 |
61 | ); 62 | }; 63 | 64 | export default LoadingView; 65 | -------------------------------------------------------------------------------- /frontend/src/components/home/TerminalBlock.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef, useState} from "react"; 2 | 3 | export const TerminalBlock = ({content}: {content: string}) => { 4 | const [displayedContent, setDisplayedContent] = useState(""); 5 | const [isTyping, setIsTyping] = useState(true); 6 | const terminalRef = useRef(null); 7 | 8 | useEffect(() => { 9 | let timer: ReturnType; 10 | let index = 0; 11 | const normalizedContent = content.replace(/\\n/g, "\n"); 12 | 13 | setIsTyping(true); 14 | setDisplayedContent(""); // Reset content when content prop changes 15 | 16 | // Terminal typing effect 17 | const typeEffect = () => { 18 | if (index < normalizedContent.length) { 19 | setDisplayedContent((prev) => prev + normalizedContent.charAt(index)); 20 | index++; 21 | 22 | // Auto-scroll to bottom as content is being typed 23 | if (terminalRef.current) { 24 | terminalRef.current.scrollTop = terminalRef.current.scrollHeight; 25 | } 26 | 27 | timer = setTimeout(typeEffect, 5); 28 | } else { 29 | setIsTyping(false); 30 | } 31 | }; 32 | 33 | timer = setTimeout(typeEffect, 10); 34 | 35 | return () => clearTimeout(timer); 36 | }, [content]); 37 | 38 | // Function to convert plain text to HTML (handling line breaks and basic formatting) 39 | const formatTerminalOutput = (text: string) => { 40 | // Add styling for command prompts to match your UI theme 41 | return text 42 | .replace(/\n/g, "
") 43 | .replace(/\*\*(.*?)\*\*/g, "$1") 44 | .replace(/\*(.*?)\*/g, "$1") 45 | .replace(/\$(.*?)$/gm, "$ $1"); 46 | }; 47 | 48 | // Custom CSS for blinking cursor 49 | const blinkingCursorStyle = { 50 | display: "inline-block", 51 | width: "2px", 52 | height: "16px", 53 | backgroundColor: "#10B981", // green-500 54 | position: "absolute" as const, 55 | bottom: "16px", 56 | marginLeft: "2px", 57 | animation: "blink 1s step-end infinite", 58 | }; 59 | 60 | // Adding keyframes for the blinking animation to the document 61 | useEffect(() => { 62 | // Create and append the style element for the blinking animation 63 | const styleElement = document.createElement("style"); 64 | styleElement.textContent = ` 65 | @keyframes blink { 66 | 0%, 100% { opacity: 1; } 67 | 50% { opacity: 0; } 68 | } 69 | `; 70 | document.head.appendChild(styleElement); 71 | 72 | // Clean up when component unmounts 73 | return () => { 74 | document.head.removeChild(styleElement); 75 | }; 76 | }, []); 77 | 78 | return ( 79 |
80 |
92 | 
93 |       {isTyping && }
94 |     
95 | ); 96 | }; 97 | -------------------------------------------------------------------------------- /frontend/src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | import {createContext, useContext, useEffect, useState} from "react"; 2 | 3 | type Theme = "dark" | "light" | "system"; 4 | 5 | type ThemeProviderProps = { 6 | children: React.ReactNode; 7 | defaultTheme?: Theme; 8 | storageKey?: string; 9 | }; 10 | 11 | type ThemeProviderState = { 12 | theme: Theme; 13 | setTheme: (theme: Theme) => void; 14 | }; 15 | 16 | const initialState: ThemeProviderState = { 17 | theme: "system", 18 | setTheme: () => null, 19 | }; 20 | 21 | const ThemeProviderContext = createContext(initialState); 22 | 23 | export function ThemeProvider({ 24 | children, 25 | defaultTheme = "system", 26 | storageKey = "vite-ui-theme", 27 | ...props 28 | }: ThemeProviderProps) { 29 | const [theme, setTheme] = useState( 30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme 31 | ); 32 | 33 | useEffect(() => { 34 | const root = window.document.documentElement; 35 | 36 | root.classList.remove("light", "dark"); 37 | 38 | if (theme === "system") { 39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") 40 | .matches 41 | ? "dark" 42 | : "light"; 43 | 44 | root.classList.add(systemTheme); 45 | return; 46 | } 47 | 48 | root.classList.add(theme); 49 | }, [theme]); 50 | 51 | const value = { 52 | theme, 53 | setTheme: (theme: Theme) => { 54 | localStorage.setItem(storageKey, theme); 55 | setTheme(theme); 56 | }, 57 | }; 58 | 59 | return ( 60 | 61 | {children} 62 | 63 | ); 64 | } 65 | 66 | // eslint-disable-next-line react-refresh/only-export-components 67 | export const useTheme = () => { 68 | const context = useContext(ThemeProviderContext); 69 | 70 | if (context === undefined) 71 | throw new Error("useTheme must be used within a ThemeProvider"); 72 | 73 | return context; 74 | }; 75 | -------------------------------------------------------------------------------- /frontend/src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 2 | import {ChevronDownIcon} from "lucide-react"; 3 | import * as React from "react"; 4 | 5 | import {cn} from "@/lib/utils"; 6 | 7 | function Accordion({ 8 | ...props 9 | }: React.ComponentProps) { 10 | return ; 11 | } 12 | 13 | function AccordionItem({ 14 | className, 15 | ...props 16 | }: React.ComponentProps) { 17 | return ( 18 | 23 | ); 24 | } 25 | 26 | function AccordionTrigger({ 27 | className, 28 | children, 29 | ...props 30 | }: React.ComponentProps) { 31 | return ( 32 | 33 | svg]:rotate-180", 37 | className 38 | )} 39 | {...props} 40 | > 41 | 42 | {children} 43 | 44 | 45 | ); 46 | } 47 | 48 | function AccordionContent({ 49 | className, 50 | children, 51 | ...props 52 | }: React.ComponentProps) { 53 | return ( 54 | 59 |
{children}
60 |
61 | ); 62 | } 63 | 64 | export {Accordion, AccordionContent, AccordionItem, AccordionTrigger}; 65 | -------------------------------------------------------------------------------- /frontend/src/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 3 | 4 | import { cn } from "@/lib/utils" 5 | import { buttonVariants } from "@/components/ui/button" 6 | 7 | function AlertDialog({ 8 | ...props 9 | }: React.ComponentProps) { 10 | return 11 | } 12 | 13 | function AlertDialogTrigger({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return ( 17 | 18 | ) 19 | } 20 | 21 | function AlertDialogPortal({ 22 | ...props 23 | }: React.ComponentProps) { 24 | return ( 25 | 26 | ) 27 | } 28 | 29 | function AlertDialogOverlay({ 30 | className, 31 | ...props 32 | }: React.ComponentProps) { 33 | return ( 34 | 42 | ) 43 | } 44 | 45 | function AlertDialogContent({ 46 | className, 47 | ...props 48 | }: React.ComponentProps) { 49 | return ( 50 | 51 | 52 | 60 | 61 | ) 62 | } 63 | 64 | function AlertDialogHeader({ 65 | className, 66 | ...props 67 | }: React.ComponentProps<"div">) { 68 | return ( 69 |
74 | ) 75 | } 76 | 77 | function AlertDialogFooter({ 78 | className, 79 | ...props 80 | }: React.ComponentProps<"div">) { 81 | return ( 82 |
90 | ) 91 | } 92 | 93 | function AlertDialogTitle({ 94 | className, 95 | ...props 96 | }: React.ComponentProps) { 97 | return ( 98 | 103 | ) 104 | } 105 | 106 | function AlertDialogDescription({ 107 | className, 108 | ...props 109 | }: React.ComponentProps) { 110 | return ( 111 | 116 | ) 117 | } 118 | 119 | function AlertDialogAction({ 120 | className, 121 | ...props 122 | }: React.ComponentProps) { 123 | return ( 124 | 128 | ) 129 | } 130 | 131 | function AlertDialogCancel({ 132 | className, 133 | ...props 134 | }: React.ComponentProps) { 135 | return ( 136 | 140 | ) 141 | } 142 | 143 | export { 144 | AlertDialog, 145 | AlertDialogPortal, 146 | AlertDialogOverlay, 147 | AlertDialogTrigger, 148 | AlertDialogContent, 149 | AlertDialogHeader, 150 | AlertDialogFooter, 151 | AlertDialogTitle, 152 | AlertDialogDescription, 153 | AlertDialogAction, 154 | AlertDialogCancel, 155 | } 156 | -------------------------------------------------------------------------------- /frontend/src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "text-destructive-foreground [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-foreground/80", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | function Alert({ 23 | className, 24 | variant, 25 | ...props 26 | }: React.ComponentProps<"div"> & VariantProps) { 27 | return ( 28 |
34 | ) 35 | } 36 | 37 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { 38 | return ( 39 |
47 | ) 48 | } 49 | 50 | function AlertDescription({ 51 | className, 52 | ...props 53 | }: React.ComponentProps<"div">) { 54 | return ( 55 |
63 | ) 64 | } 65 | 66 | export { Alert, AlertTitle, AlertDescription } 67 | -------------------------------------------------------------------------------- /frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import {Slot} from "@radix-ui/react-slot"; 2 | import {cva, type VariantProps} from "class-variance-authority"; 3 | import * as React from "react"; 4 | 5 | import {cn} from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-sm text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", 16 | outline: 17 | "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 25 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 26 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 27 | icon: "size-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ); 36 | 37 | function Button({ 38 | className, 39 | variant, 40 | size, 41 | asChild = false, 42 | ...props 43 | }: React.ComponentProps<"button"> & 44 | VariantProps & { 45 | asChild?: boolean; 46 | }) { 47 | const Comp = asChild ? Slot : "button"; 48 | 49 | return ( 50 | 55 | ); 56 | } 57 | 58 | // eslint-disable-next-line react-refresh/only-export-components 59 | export {Button, buttonVariants}; 60 | -------------------------------------------------------------------------------- /frontend/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
25 | ) 26 | } 27 | 28 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 29 | return ( 30 |
35 | ) 36 | } 37 | 38 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 39 | return ( 40 |
45 | ) 46 | } 47 | 48 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 49 | return ( 50 |
55 | ) 56 | } 57 | 58 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 59 | return ( 60 |
65 | ) 66 | } 67 | 68 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 69 | -------------------------------------------------------------------------------- /frontend/src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DialogPrimitive from "@radix-ui/react-dialog" 3 | import { XIcon } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function Dialog({ 8 | ...props 9 | }: React.ComponentProps) { 10 | return 11 | } 12 | 13 | function DialogTrigger({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return 17 | } 18 | 19 | function DialogPortal({ 20 | ...props 21 | }: React.ComponentProps) { 22 | return 23 | } 24 | 25 | function DialogClose({ 26 | ...props 27 | }: React.ComponentProps) { 28 | return 29 | } 30 | 31 | function DialogOverlay({ 32 | className, 33 | ...props 34 | }: React.ComponentProps) { 35 | return ( 36 | 44 | ) 45 | } 46 | 47 | function DialogContent({ 48 | className, 49 | children, 50 | ...props 51 | }: React.ComponentProps) { 52 | return ( 53 | 54 | 55 | 63 | {children} 64 | 65 | 66 | Close 67 | 68 | 69 | 70 | ) 71 | } 72 | 73 | function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { 74 | return ( 75 |
80 | ) 81 | } 82 | 83 | function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { 84 | return ( 85 |
93 | ) 94 | } 95 | 96 | function DialogTitle({ 97 | className, 98 | ...props 99 | }: React.ComponentProps) { 100 | return ( 101 | 106 | ) 107 | } 108 | 109 | function DialogDescription({ 110 | className, 111 | ...props 112 | }: React.ComponentProps) { 113 | return ( 114 | 119 | ) 120 | } 121 | 122 | export { 123 | Dialog, 124 | DialogClose, 125 | DialogContent, 126 | DialogDescription, 127 | DialogFooter, 128 | DialogHeader, 129 | DialogOverlay, 130 | DialogPortal, 131 | DialogTitle, 132 | DialogTrigger, 133 | } 134 | -------------------------------------------------------------------------------- /frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /frontend/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Label({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ) 20 | } 21 | 22 | export { Label } 23 | -------------------------------------------------------------------------------- /frontend/src/components/ui/passwordInput.tsx: -------------------------------------------------------------------------------- 1 | import {Eye, EyeOff} from "lucide-react"; 2 | import {useState} from "react"; 3 | import {Input} from "./input"; 4 | 5 | export const PasswordInput = ({ 6 | password, 7 | setPassword, 8 | ...props 9 | }: { 10 | password: string; 11 | setPassword?: (password: string) => void; 12 | }) => { 13 | const [showPassword, setShowPassword] = useState(false); 14 | 15 | return ( 16 |
17 | setPassword?.(e.target.value)} 22 | {...props} 23 | /> 24 | {showPassword ? ( 25 | setShowPassword(!showPassword)} 28 | /> 29 | ) : ( 30 | setShowPassword(!showPassword)} 33 | /> 34 | )} 35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function ScrollArea({ 7 | className, 8 | children, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 17 | 21 | {children} 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | function ScrollBar({ 30 | className, 31 | orientation = "vertical", 32 | ...props 33 | }: React.ComponentProps) { 34 | return ( 35 | 48 | 52 | 53 | ) 54 | } 55 | 56 | export { ScrollArea, ScrollBar } 57 | -------------------------------------------------------------------------------- /frontend/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import {cn} from "@/lib/utils"; 4 | 5 | function Textarea({className, ...props}: React.ComponentProps<"textarea">) { 6 | return ( 7 |