├── .assets ├── devika-avatar.png ├── devika-pygame-demo.mp4 └── devika-screenshot.png ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── issue.md └── pull_request_template.md ├── .gitignore ├── ARCHITECTURE.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── ROADMAP.md ├── app.dockerfile ├── benchmarks ├── BENCHMARKS.md └── SWE-bench.md ├── devika.dockerfile ├── devika.py ├── docker-compose.yaml ├── docs ├── Installation │ ├── images │ │ ├── bing-1.png │ │ ├── bing.png │ │ ├── google-2.png │ │ └── google.png │ ├── ollama.md │ └── search_engine.md └── architecture │ ├── ARCHITECTURE.md │ ├── README.md │ └── UNDER_THE_HOOD.md ├── requirements.txt ├── sample.config.toml ├── setup.sh ├── src ├── agents │ ├── __init__.py │ ├── action │ │ ├── __init__.py │ │ ├── action.py │ │ └── prompt.jinja2 │ ├── agent.py │ ├── answer │ │ ├── __init__.py │ │ ├── answer.py │ │ └── prompt.jinja2 │ ├── coder │ │ ├── __init__.py │ │ ├── coder.py │ │ └── prompt.jinja2 │ ├── decision │ │ ├── __init__.py │ │ ├── decision.py │ │ └── prompt.jinja2 │ ├── feature │ │ ├── __init__.py │ │ ├── feature.py │ │ └── prompt.jinja2 │ ├── formatter │ │ ├── __init__.py │ │ ├── formatter.py │ │ └── prompt.jinja2 │ ├── internal_monologue │ │ ├── __init__.py │ │ ├── internal_monologue.py │ │ └── prompt.jinja2 │ ├── patcher │ │ ├── __init__.py │ │ ├── patcher.py │ │ └── prompt.jinja2 │ ├── planner │ │ ├── __init__.py │ │ ├── planner.py │ │ └── prompt.jinja2 │ ├── reporter │ │ ├── __init__.py │ │ ├── prompt.jinja2 │ │ └── reporter.py │ ├── researcher │ │ ├── __init__.py │ │ ├── prompt.jinja2 │ │ └── researcher.py │ └── runner │ │ ├── __init__.py │ │ ├── prompt.jinja2 │ │ ├── rerunner.jinja2 │ │ └── runner.py ├── apis │ └── project.py ├── bert │ └── sentence.py ├── browser │ ├── __init__.py │ ├── browser.py │ ├── interaction.py │ └── search.py ├── config.py ├── documenter │ ├── graphwiz.py │ ├── pdf.py │ └── uml.py ├── experts │ ├── __UNIMPLEMENTED__ │ ├── chemistry.py │ ├── game-dev.py │ ├── math.py │ ├── medical.py │ ├── physics.py │ ├── stackoverflow.py │ └── web-design.py ├── filesystem │ ├── __init__.py │ └── read_code.py ├── init.py ├── llm │ ├── __init__.py │ ├── claude_client.py │ ├── gemini_client.py │ ├── groq_client.py │ ├── llm.py │ ├── lm_studio_client.py │ ├── mistral_client.py │ ├── ollama_client.py │ └── openai_client.py ├── logger.py ├── memory │ ├── __init__.py │ ├── knowledge_base.py │ └── rag.py ├── project.py ├── sandbox │ ├── code_runner.py │ └── firejail.py ├── services │ ├── __init__.py │ ├── git.py │ ├── github.py │ ├── netlify.py │ └── utils.py ├── socket_instance.py └── state.py └── ui ├── .gitignore ├── .npmrc ├── bun.lockb ├── components.json ├── package.json ├── postcss.config.cjs ├── src ├── app.html ├── app.pcss ├── lib │ ├── api.js │ ├── components │ │ ├── BrowserWidget.svelte │ │ ├── ControlPanel.svelte │ │ ├── EditorWidget.svelte │ │ ├── MessageContainer.svelte │ │ ├── MessageInput.svelte │ │ ├── MonacoEditor.js │ │ ├── Sidebar.svelte │ │ ├── TerminalWidget.svelte │ │ └── ui │ │ │ ├── Seperator.svelte │ │ │ ├── SidebarButton.svelte │ │ │ ├── resizable │ │ │ ├── index.js │ │ │ ├── resizable-handle.svelte │ │ │ └── resizable-pane-group.svelte │ │ │ ├── select │ │ │ ├── index.js │ │ │ ├── select-content.svelte │ │ │ ├── select-item.svelte │ │ │ ├── select-label.svelte │ │ │ ├── select-separator.svelte │ │ │ └── select-trigger.svelte │ │ │ ├── sonner │ │ │ ├── index.js │ │ │ └── sonner.svelte │ │ │ └── tabs │ │ │ ├── index.js │ │ │ ├── tabs-content.svelte │ │ │ ├── tabs-list.svelte │ │ │ └── tabs-trigger.svelte │ ├── icons.js │ ├── sockets.js │ ├── store.js │ ├── token.js │ └── utils.js └── routes │ ├── +layout.js │ ├── +layout.svelte │ ├── +page.svelte │ ├── logs │ └── +page.svelte │ └── settings │ └── +page.svelte ├── static ├── assets │ ├── bootup.mp3 │ ├── devika-avatar.png │ ├── devika-avatar.svg │ ├── loading-lottie.json │ ├── user-avatar.png │ └── user-avatar.svg └── favicon.png ├── svelte.config.js ├── tailwind.config.js └── vite.config.js /.assets/devika-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/.assets/devika-avatar.png -------------------------------------------------------------------------------- /.assets/devika-pygame-demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/.assets/devika-pygame-demo.mp4 -------------------------------------------------------------------------------- /.assets/devika-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/.assets/devika-screenshot.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | ### Describe the bug 12 | A clear and concise description of what the bug is. 13 | 14 | ### How To Reproduce 15 | Steps to reproduce the behavior (example): 16 | 1. {steps_1} 17 | 2. {steps_2} 18 | 3. {steps_3} 19 | 20 | ### Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ### Screenshots and logs 24 | must share logs from frontend or backend. also If applicable, add screenshots to help explain your problem. 25 | 26 | ### Configuration 27 | ``` 28 | - OS: [e.g. Windows, Linux, MacOS] 29 | - Python version: [e.g. 3.10] 30 | - Node version: [e.g. 18.0.0] 31 | - bun version: [e.g. 0.1.0] 32 | - search engine: [e.g. google] 33 | - Model: [e.g. gpt, cluade] 34 | ``` 35 | 36 | #### Additional context 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe. 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like 14 | A clear and concise description of what you want to happen. 15 | 16 | ## Describe alternatives you've considered 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | ### Additional context 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | title: '[ISSUE]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | ### Describe your issue 12 | A clear and concise description of what the issue is. 13 | 14 | ### How To Reproduce 15 | Steps to reproduce the behavior (example): 16 | 1. {steps_1} 17 | 2. {steps_2} 18 | 3. {steps_3} 19 | 20 | ### Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ### Screenshots and logs 24 | must share logs from frontend or backend. also If applicable, add screenshots to help explain your problem. 25 | 26 | ### Configuration 27 | ``` 28 | - OS: [e.g. Windows, Linux, MacOS] 29 | - Python version: [e.g. 3.10] 30 | - Node version: [e.g. 18.0.0] 31 | - bun version: [e.g. 0.1.0] 32 | - search engine: [e.g. google] 33 | - Model: [e.g. gpt, cluade] 34 | ``` 35 | 36 | #### Additional context 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ### Description 8 | 9 | 10 | 11 | for e.g. 12 | * [ ] Bug 1 13 | * [ ] Bug 2 14 | * [ ] Feature 1 15 | * [ ] Feature 2 16 | * [ ] Breaking changes 17 | 18 | 19 | 20 | Explain what existing problem does the pull request solve? 21 | 22 | 23 | 24 | ### Test plan (required) 25 | 26 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. 27 | 28 | 29 | ### Closing issues (optional) 30 | 31 | 32 | Fixes # -------------------------------------------------------------------------------- /.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 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # pdm 104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 105 | #pdm.lock 106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 107 | # in version control. 108 | # https://pdm.fming.dev/#use-with-ide 109 | .pdm.toml 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | config.toml 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | .idea/ 161 | 162 | notes.md 163 | data/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome Contributors 2 | We welcome contributions to enhance Devika's capabilities and improve its performance. To report bugs, create a [GitHub issue](https://github.com/stitionai/devika/issues). 3 | 4 | > Before contributing, read through the existing issues and pull requests to see if someone else is already working on something similar. That way you can avoid duplicating efforts. 5 | 6 | To contribute, please follow these steps: 7 | 8 | 1. Fork the Devika repository on GitHub. 9 | 2. Create a new branch for your feature or bug fix. 10 | 3. Make your changes and ensure that the code passes all tests. 11 | 4. Submit a pull request describing your changes and their benefits. 12 | 13 | 14 | ### Pull Request Guidelines 15 | When submitting a pull request, please follow these guidelines: 16 | 17 | 1. **Title**: please include following prefixes: 18 | - `Feature:` for new features 19 | - `Fix:` for bug fixes 20 | - `Docs:` for documentation changes 21 | - `Refactor:` for code refactoring 22 | - `Improve:` for performance improvements 23 | - `Other:` for other changes 24 | 25 | for example: 26 | - `Feature: added new feature to the code` 27 | - `Fix: fixed the bug in the code` 28 | 29 | 2. **Description**: Provide a clear and detailed description of your changes in the pull request. Explain the problem you are solving, the approach you took, and any potential side effects or limitations of your changes. 30 | 3. **Documentation**: Update the relevant documentation to reflect your changes. This includes the README file, code comments, and any other relevant documentation. 31 | 4. **Dependencies**: If your changes require new dependencies, ensure that they are properly documented and added to the `requirements.txt` or `package.json` files. 32 | 5. if the pull request does not meet the above guidelines, it may be closed without merging. 33 | 34 | 35 | **Note**: Please ensure that you have the latest version of the code before creating a pull request. If you have an existing fork, just sync your fork with the latest version of the Devika repository. 36 | 37 | 38 | Please adhere to the coding conventions, maintain clear documentation, and provide thorough testing for your contributions. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 stition 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY = setup deps compose-up compose-down compose-destroy 3 | 4 | # to check if docker is installed on the machine 5 | DOCKER := $(shell command -v docker) 6 | DOCKER_COMPOSE := $(shell command -v docker-compose) 7 | deps: 8 | ifndef DOCKER 9 | @echo "Docker is not available. Please install docker" 10 | @echo "try running sudo apt-get install docker" 11 | @exit 1 12 | endif 13 | ifndef DOCKER_COMPOSE 14 | @echo "docker-compose is not available. Please install docker-compose" 15 | @echo "try running sudo apt-get install docker-compose" 16 | @exit 1 17 | endif 18 | 19 | setup: 20 | sh +x build 21 | 22 | compose-down: deps 23 | docker volume ls 24 | docker-compose ps 25 | docker images 26 | docker-compose down; 27 | 28 | compose-up: deps compose-down 29 | docker-compose up --build 30 | 31 | compose-destroy: deps 32 | docker images | grep -i devika | awk '{print $$3}' | xargs docker rmi -f 33 | docker volume prune -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | - [ ] Create an extensive testing suite for all [Agents](https://github.com/stitionai/devika/tree/main/src/agents). 4 | - [ ] Catch down on all runtime errors and prepare for Project Devika stable release. 5 | - [ ] Document and implement easy cross-platform installation/setup scripts and packages. 6 | - [ ] Create tutorial videos on the installation steps, setup, and usage for Windows, Linux, and MacOS. 7 | - [ ] Focusing on the Claude 3 Opus model, test Devika on the [SWE-Bench](https://www.swebench.com/) benchmarks. -------------------------------------------------------------------------------- /app.dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:12 2 | 3 | # setting up build variable 4 | ARG VITE_API_BASE_URL 5 | ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} 6 | 7 | # setting up os env 8 | USER root 9 | WORKDIR /home/nonroot/client 10 | RUN groupadd -r nonroot && useradd -r -g nonroot -d /home/nonroot/client -s /bin/bash nonroot 11 | 12 | # install node js 13 | RUN apt-get update && apt-get upgrade -y 14 | RUN apt-get install -y build-essential software-properties-common curl sudo wget git 15 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - 16 | RUN apt-get install nodejs 17 | 18 | # copying devika app client only 19 | COPY ui /home/nonroot/client/ui 20 | COPY src /home/nonroot/client/src 21 | COPY config.toml /home/nonroot/client/ 22 | 23 | RUN cd ui && npm install && npm install -g npm && npm install -g bun 24 | RUN chown -R nonroot:nonroot /home/nonroot/client 25 | 26 | USER nonroot 27 | WORKDIR /home/nonroot/client/ui 28 | 29 | ENTRYPOINT [ "npx", "bun", "run", "dev", "--", "--host" ] -------------------------------------------------------------------------------- /benchmarks/BENCHMARKS.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/benchmarks/BENCHMARKS.md -------------------------------------------------------------------------------- /benchmarks/SWE-bench.md: -------------------------------------------------------------------------------- 1 | > ...Not yet -------------------------------------------------------------------------------- /devika.dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:12 2 | 3 | # setting up os env 4 | USER root 5 | WORKDIR /home/nonroot/devika 6 | RUN groupadd -r nonroot && useradd -r -g nonroot -d /home/nonroot/devika -s /bin/bash nonroot 7 | 8 | ENV PYTHONUNBUFFERED 1 9 | ENV PYTHONDONTWRITEBYTECODE 1 10 | 11 | # setting up python3 12 | RUN apt-get update && apt-get upgrade -y 13 | RUN apt-get install -y build-essential software-properties-common curl sudo wget git 14 | RUN apt-get install -y python3 python3-pip 15 | RUN curl -fsSL https://astral.sh/uv/install.sh | sudo -E bash - 16 | RUN $HOME/.cargo/bin/uv venv 17 | ENV PATH="/home/nonroot/devika/.venv/bin:$HOME/.cargo/bin:$PATH" 18 | 19 | # copy devika python engine only 20 | RUN $HOME/.cargo/bin/uv venv 21 | COPY requirements.txt /home/nonroot/devika/ 22 | RUN UV_HTTP_TIMEOUT=100000 $HOME/.cargo/bin/uv pip install -r requirements.txt 23 | 24 | RUN playwright install-deps chromium 25 | RUN playwright install chromium 26 | 27 | COPY src /home/nonroot/devika/src 28 | COPY config.toml /home/nonroot/devika/ 29 | COPY sample.config.toml /home/nonroot/devika/ 30 | COPY devika.py /home/nonroot/devika/ 31 | RUN chown -R nonroot:nonroot /home/nonroot/devika 32 | 33 | USER nonroot 34 | WORKDIR /home/nonroot/devika 35 | ENV PATH="/home/nonroot/devika/.venv/bin:$HOME/.cargo/bin:$PATH" 36 | RUN mkdir /home/nonroot/devika/db 37 | 38 | ENTRYPOINT [ "python3", "-m", "devika" ] 39 | -------------------------------------------------------------------------------- /devika.py: -------------------------------------------------------------------------------- 1 | """ 2 | DO NOT REARRANGE THE ORDER OF THE FUNCTION CALLS AND VARIABLE DECLARATIONS 3 | AS IT MAY CAUSE IMPORT ERRORS AND OTHER ISSUES 4 | """ 5 | from gevent import monkey 6 | monkey.patch_all() 7 | from src.init import init_devika 8 | init_devika() 9 | 10 | 11 | from flask import Flask, request, jsonify, send_file 12 | from flask_cors import CORS 13 | from src.socket_instance import socketio, emit_agent 14 | import os 15 | import logging 16 | from threading import Thread 17 | import tiktoken 18 | 19 | from src.apis.project import project_bp 20 | from src.config import Config 21 | from src.logger import Logger, route_logger 22 | from src.project import ProjectManager 23 | from src.state import AgentState 24 | from src.agents import Agent 25 | from src.llm import LLM 26 | 27 | 28 | app = Flask(__name__) 29 | CORS(app, resources={r"/*": {"origins": # Change the origin to your frontend URL 30 | [ 31 | "https://localhost:3000", 32 | "http://localhost:3000", 33 | ]}}) 34 | app.register_blueprint(project_bp) 35 | socketio.init_app(app) 36 | 37 | 38 | log = logging.getLogger("werkzeug") 39 | log.disabled = True 40 | 41 | 42 | TIKTOKEN_ENC = tiktoken.get_encoding("cl100k_base") 43 | 44 | os.environ["TOKENIZERS_PARALLELISM"] = "false" 45 | 46 | manager = ProjectManager() 47 | AgentState = AgentState() 48 | config = Config() 49 | logger = Logger() 50 | 51 | 52 | # initial socket 53 | @socketio.on('socket_connect') 54 | def test_connect(data): 55 | print("Socket connected :: ", data) 56 | emit_agent("socket_response", {"data": "Server Connected"}) 57 | 58 | 59 | @app.route("/api/data", methods=["GET"]) 60 | @route_logger(logger) 61 | def data(): 62 | project = manager.get_project_list() 63 | models = LLM().list_models() 64 | search_engines = ["Bing", "Google", "DuckDuckGo"] 65 | return jsonify({"projects": project, "models": models, "search_engines": search_engines}) 66 | 67 | 68 | @app.route("/api/messages", methods=["POST"]) 69 | def get_messages(): 70 | data = request.json 71 | project_name = data.get("project_name") 72 | messages = manager.get_messages(project_name) 73 | return jsonify({"messages": messages}) 74 | 75 | 76 | # Main socket 77 | @socketio.on('user-message') 78 | def handle_message(data): 79 | logger.info(f"User message: {data}") 80 | message = data.get('message') 81 | base_model = data.get('base_model') 82 | project_name = data.get('project_name') 83 | search_engine = data.get('search_engine').lower() 84 | 85 | agent = Agent(base_model=base_model, search_engine=search_engine) 86 | 87 | state = AgentState.get_latest_state(project_name) 88 | if not state: 89 | thread = Thread(target=lambda: agent.execute(message, project_name)) 90 | thread.start() 91 | else: 92 | if AgentState.is_agent_completed(project_name): 93 | thread = Thread(target=lambda: agent.subsequent_execute(message, project_name)) 94 | thread.start() 95 | else: 96 | emit_agent("info", {"type": "warning", "message": "previous agent doesn't completed it's task."}) 97 | last_state = AgentState.get_latest_state(project_name) 98 | if last_state["agent_is_active"] or not last_state["completed"]: 99 | thread = Thread(target=lambda: agent.execute(message, project_name)) 100 | thread.start() 101 | else: 102 | thread = Thread(target=lambda: agent.subsequent_execute(message, project_name)) 103 | thread.start() 104 | 105 | @app.route("/api/is-agent-active", methods=["POST"]) 106 | @route_logger(logger) 107 | def is_agent_active(): 108 | data = request.json 109 | project_name = data.get("project_name") 110 | is_active = AgentState.is_agent_active(project_name) 111 | return jsonify({"is_active": is_active}) 112 | 113 | 114 | @app.route("/api/get-agent-state", methods=["POST"]) 115 | @route_logger(logger) 116 | def get_agent_state(): 117 | data = request.json 118 | project_name = data.get("project_name") 119 | agent_state = AgentState.get_latest_state(project_name) 120 | return jsonify({"state": agent_state}) 121 | 122 | 123 | @app.route("/api/get-browser-snapshot", methods=["GET"]) 124 | @route_logger(logger) 125 | def browser_snapshot(): 126 | snapshot_path = request.args.get("snapshot_path") 127 | return send_file(snapshot_path, as_attachment=True) 128 | 129 | 130 | @app.route("/api/get-browser-session", methods=["GET"]) 131 | @route_logger(logger) 132 | def get_browser_session(): 133 | project_name = request.args.get("project_name") 134 | agent_state = AgentState.get_latest_state(project_name) 135 | if not agent_state: 136 | return jsonify({"session": None}) 137 | else: 138 | browser_session = agent_state["browser_session"] 139 | return jsonify({"session": browser_session}) 140 | 141 | 142 | @app.route("/api/get-terminal-session", methods=["GET"]) 143 | @route_logger(logger) 144 | def get_terminal_session(): 145 | project_name = request.args.get("project_name") 146 | agent_state = AgentState.get_latest_state(project_name) 147 | if not agent_state: 148 | return jsonify({"terminal_state": None}) 149 | else: 150 | terminal_state = agent_state["terminal_session"] 151 | return jsonify({"terminal_state": terminal_state}) 152 | 153 | 154 | @app.route("/api/run-code", methods=["POST"]) 155 | @route_logger(logger) 156 | def run_code(): 157 | data = request.json 158 | project_name = data.get("project_name") 159 | code = data.get("code") 160 | # TODO: Implement code execution logic 161 | return jsonify({"message": "Code execution started"}) 162 | 163 | 164 | @app.route("/api/calculate-tokens", methods=["POST"]) 165 | @route_logger(logger) 166 | def calculate_tokens(): 167 | data = request.json 168 | prompt = data.get("prompt") 169 | tokens = len(TIKTOKEN_ENC.encode(prompt)) 170 | return jsonify({"token_usage": tokens}) 171 | 172 | 173 | @app.route("/api/token-usage", methods=["GET"]) 174 | @route_logger(logger) 175 | def token_usage(): 176 | project_name = request.args.get("project_name") 177 | token_count = AgentState.get_latest_token_usage(project_name) 178 | return jsonify({"token_usage": token_count}) 179 | 180 | 181 | @app.route("/api/logs", methods=["GET"]) 182 | def real_time_logs(): 183 | log_file = logger.read_log_file() 184 | return jsonify({"logs": log_file}) 185 | 186 | 187 | @app.route("/api/settings", methods=["POST"]) 188 | @route_logger(logger) 189 | def set_settings(): 190 | data = request.json 191 | config.update_config(data) 192 | return jsonify({"message": "Settings updated"}) 193 | 194 | 195 | @app.route("/api/settings", methods=["GET"]) 196 | @route_logger(logger) 197 | def get_settings(): 198 | configs = config.get_config() 199 | return jsonify({"settings": configs}) 200 | 201 | 202 | @app.route("/api/status", methods=["GET"]) 203 | @route_logger(logger) 204 | def status(): 205 | return jsonify({"status": "server is running!"}) 206 | 207 | if __name__ == "__main__": 208 | logger.info("Devika is up and running!") 209 | socketio.run(app, debug=False, port=1337, host="0.0.0.0") 210 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | ollama-service: 5 | image: ollama/ollama:latest 6 | expose: 7 | - 11434 8 | ports: 9 | - 11434:11434 10 | healthcheck: 11 | test: ["CMD-SHELL", "curl -f http://localhost:11434/ || exit 1"] 12 | interval: 5s 13 | timeout: 30s 14 | retries: 5 15 | start_period: 30s 16 | networks: 17 | - devika-subnetwork 18 | 19 | devika-backend-engine: 20 | build: 21 | context: . 22 | dockerfile: devika.dockerfile 23 | depends_on: 24 | - ollama-service 25 | expose: 26 | - 1337 27 | ports: 28 | - 1337:1337 29 | environment: 30 | - OLLAMA_HOST=http://ollama-service:11434 31 | healthcheck: 32 | test: ["CMD-SHELL", "curl -f http://localhost:1337/ || exit 1"] 33 | interval: 5s 34 | timeout: 30s 35 | retries: 5 36 | start_period: 30s 37 | volumes: 38 | - devika-backend-dbstore:/home/nonroot/devika/db 39 | networks: 40 | - devika-subnetwork 41 | 42 | devika-frontend-app: 43 | build: 44 | context: . 45 | dockerfile: app.dockerfile 46 | args: 47 | - VITE_API_BASE_URL=http://127.0.0.1:1337 48 | depends_on: 49 | - devika-backend-engine 50 | expose: 51 | - 3000 52 | ports: 53 | - 3000:3000 54 | networks: 55 | - devika-subnetwork 56 | 57 | networks: 58 | devika-subnetwork: 59 | 60 | volumes: 61 | devika-backend-dbstore: -------------------------------------------------------------------------------- /docs/Installation/images/bing-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/docs/Installation/images/bing-1.png -------------------------------------------------------------------------------- /docs/Installation/images/bing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/docs/Installation/images/bing.png -------------------------------------------------------------------------------- /docs/Installation/images/google-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/docs/Installation/images/google-2.png -------------------------------------------------------------------------------- /docs/Installation/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/docs/Installation/images/google.png -------------------------------------------------------------------------------- /docs/Installation/ollama.md: -------------------------------------------------------------------------------- 1 | # Ollama Installation Guide 2 | 3 | This guide will help you set up Ollama for Devika. Ollama is a tool that allows you to run open-source large language models (LLMs) locally on your machine. It supports varity of models like Llama-2, mistral, code-llama and many more. 4 | 5 | ## Installation 6 | 7 | 1. go to the [Ollama](https://ollama.com) website. 8 | 2. Download the latest version of the Ollama. 9 | 3. After installing the Ollama, you have to download the model you want to use. [Models](https://ollama.com/library) 10 | 4. select the model you want to download and copy the command. for example, `ollama run llama2`.it will download the model and start the server. 11 | 5. `ollama list` will show the list of models you have downloaded. 12 | 6. if the server isn't running then you can manually start by `ollama serve`. default address for the server is `http://localhost:11434` 13 | 7. for changing port and other configurations, follow the FAQ [here](https://github.com/ollama/ollama/blob/main/docs/faq.md) 14 | 8. for more information, `ollama [command] --help` will show the help menu. for example, `ollama run --help` will show the help menu for the run command. 15 | 16 | 17 | ## Devika Configuration 18 | 19 | - if you serve the Ollama on a different address, you can change the port in the `config.toml` file or you can change it via UI. 20 | - if you are using the default address, devika will automatically detect the server and and fetch the models list. 21 | -------------------------------------------------------------------------------- /docs/Installation/search_engine.md: -------------------------------------------------------------------------------- 1 | # search Engine setup 2 | 3 | To use the search engine capabilities of Devika, you need to set up the search engine API keys. Currently, Devika supports Bing, Google and DuckDuckGo search engines. If you want to use duckduckgo, you don't need to set up any API keys. 4 | 5 | For Bing and Google search engines, you need to set up the API keys. Here's how you can do it: 6 | 7 | ## Bing Search API 8 | - Create Azure account. You can create a free account [here](https://azure.microsoft.com/en-us/free/). 9 | - Go to the [Bing Search API](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api) website. 10 | - click on the `Try now` button. 11 | - Sign in/sign up with your Azure account. 12 | - Create a new resource group (if you don't have any). 13 | ![alt text](images/bing.png) 14 | - click on the `Review and create` button. 15 | - if everything is fine, click on the `Create` button. 16 | - Once the resource is created, go to the `Keys and Endpoint` tab. 17 | ![alt text](images/bing-1.png) 18 | - Copy either `Key1` or `Key2` and paste it into the `API_KEYS` field with the name `BING` in the `config.toml` file located in the root directory of Devika, or you can set it via the UI. 19 | - Copy the `Endpoint` and paste it into the `API_Endpoints` field with the name `BING` in the `config.toml` file located in the root directory of Devika, or you can set it via the UI. 20 | 21 | 22 | ## Google Search API 23 | - if don't have then create GCP account [Google Cloud Console](https://console.cloud.google.com/). 24 | - visit [Here](https://developers.google.com/custom-search/v1/overview) is the official documentation. 25 | - click on `Get a Key`. 26 | - select the project you have or create a new project. click on next. 27 | ![alt text](images/google.png) 28 | - it enable the Custom Search API for the project and create the API key. 29 | - Copy the API key and paste it in the API_KEYS field with the name `GOOGLE_SEARCH` in the `config.toml` file in the root directory of Devika or you can set it via UI. 30 | - for the search engine id, go to the [Google Custom Search Engine](https://programmablesearchengine.google.com/controlpanel/all) website. 31 | - click on the `Add` button. 32 | ![alt text](images/google-2.png) 33 | - After creating the engine. Copy the `Search Engine ID` and paste it in the API_Endpoints field with the name `GOOGLE_SEARCH_ENGINE_ID` in the `config.toml` file in the root directory of Devika or you can set it via UI. 34 | -------------------------------------------------------------------------------- /docs/architecture/README.md: -------------------------------------------------------------------------------- 1 | ## System Architecture 2 | 3 | Devika's system architecture consists of the following key components: 4 | 5 | 1. **User Interface**: A web-based chat interface for interacting with Devika, viewing project files, and monitoring the agent's state. 6 | 2. **Agent Core**: The central component that orchestrates the AI planning, reasoning, and execution process. It communicates with various sub-agents and modules to accomplish tasks. 7 | 3. **Large Language Models**: Devika leverages state-of-the-art language models like **Claude**, **GPT-4**, and **Local LLMs via Ollama** for natural language understanding, generation, and reasoning. 8 | 4. **Planning and Reasoning Engine**: Responsible for breaking down high-level objectives into actionable steps and making decisions based on the current context. 9 | 5. **Research Module**: Utilizes keyword extraction and web browsing capabilities to gather relevant information for the task at hand. 10 | 6. **Code Writing Module**: Generates code based on the plan, research findings, and user requirements. Supports multiple programming languages. 11 | 7. **Browser Interaction Module**: Enables Devika to navigate websites, extract information, and interact with web elements as needed. 12 | 8. **Knowledge Base**: Stores and retrieves project-specific information, code snippets, and learned knowledge for efficient access. 13 | 9. **Database**: Persists project data, agent states, and configuration settings. 14 | 15 | Read [ARCHITECTURE.md](https://github.com/stitionai/devika/Docs/architecture/ARCHITECTURE.md) for the detailed architecture of Devika. 16 | Read [UNDER_THE_HOOD.md](https://github.com/stitionai/devika/Docs/architecture/UNDER_THE_HOOD.md) for the detailed working of Devika. 17 | -------------------------------------------------------------------------------- /docs/architecture/UNDER_THE_HOOD.md: -------------------------------------------------------------------------------- 1 | ## Under The Hood 2 | 3 | Let's dive deeper into some of the key components and techniques used in Devika: 4 | 5 | ### AI Planning and Reasoning 6 | 7 | Devika employs advanced AI planning and reasoning algorithms to break down high-level objectives into actionable steps. The planning process involves the following stages: 8 | 9 | 1. **Objective Understanding**: Devika analyzes the given objective or task description to understand the user's intent and requirements. 10 | 2. **Context Gathering**: Relevant context is collected from the conversation history, project files, and knowledge base to inform the planning process. 11 | 3. **Step Generation**: Based on the objective and context, Devika generates a sequence of high-level steps to accomplish the task. 12 | 4. **Refinement and Validation**: The generated steps are refined and validated to ensure their feasibility and alignment with the objective. 13 | 5. **Execution**: Devika executes each step in the plan, utilizing various sub-agents and modules as needed. 14 | 15 | The reasoning engine constantly evaluates the progress and makes adjustments to the plan based on new information or feedback received during execution. 16 | 17 | ### Keyword Extraction 18 | 19 | To enable focused research and information gathering, Devika employs keyword extraction techniques. The process involves the following steps: 20 | 21 | 1. **Preprocessing**: The input text (objective, conversation history, or project files) is preprocessed by removing stop words, tokenizing, and normalizing the text. 22 | 2. **Keyword Identification**: Devika uses the BERT (Bidirectional Encoder Representations from Transformers) model to identify important keywords and phrases from the preprocessed text. BERT's pre-training on a large corpus allows it to capture semantic relationships and understand the significance of words in the given context. 23 | 3. **Keyword Ranking**: The identified keywords are ranked based on their relevance and importance to the task at hand. Techniques like TF-IDF (Term Frequency-Inverse Document Frequency) and TextRank are used to assign scores to each keyword. 24 | 4. **Keyword Selection**: The top-ranked keywords are selected as the most relevant and informative for the current context. These keywords are used to guide the research and information gathering process. 25 | 26 | By extracting contextually relevant keywords, Devika can focus its research efforts and retrieve pertinent information to assist in the task completion. 27 | 28 | ### Browser Interaction 29 | 30 | Devika incorporates browser interaction capabilities to navigate websites, extract information, and interact with web elements. The browser interaction module leverages the Playwright library to automate web interactions. The process involves the following steps: 31 | 32 | 1. **Navigation**: Devika uses Playwright to navigate to specific URLs or perform searches based on the keywords or requirements provided. 33 | 2. **Element Interaction**: Playwright allows Devika to interact with web elements such as clicking buttons, filling forms, and extracting text from specific elements. 34 | 3. **Page Parsing**: Devika parses the HTML structure of the web pages visited to extract relevant information. It uses techniques like CSS selectors and XPath to locate and extract specific data points. 35 | 4. **JavaScript Execution**: Playwright enables Devika to execute JavaScript code within the browser context, allowing for dynamic interactions and data retrieval. 36 | 5. **Screenshot Capture**: Devika can capture screenshots of the web pages visited, which can be useful for visual reference or debugging purposes. 37 | 38 | The browser interaction module empowers Devika to gather information from the web, interact with online resources, and incorporate real-time data into its decision-making and code generation processes. 39 | 40 | ### Code Writing 41 | 42 | Devika's code writing module generates code based on the plan, research findings, and user requirements. The process involves the following steps: 43 | 44 | 1. **Language Selection**: Devika identifies the programming language specified by the user or infers it based on the project context. 45 | 2. **Code Structure Generation**: Based on the plan and language-specific patterns, Devika generates the high-level structure of the code, including classes, functions, and modules. 46 | 3. **Code Population**: Devika fills in the code structure with specific logic, algorithms, and data manipulation statements. It leverages the research findings, code snippets from the knowledge base, and its own understanding of programming concepts to generate meaningful code. 47 | 4. **Code Formatting**: The generated code is formatted according to the language-specific conventions and best practices to ensure readability and maintainability. 48 | 5. **Code Review and Refinement**: Devika reviews the generated code for syntax errors, logical inconsistencies, and potential improvements. It iteratively refines the code based on its own analysis and any feedback provided by the user. 49 | 50 | Devika's code writing capabilities enable it to generate functional and efficient code in various programming languages, taking into account the specific requirements and context of each project. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-cors 3 | toml 4 | urllib3 5 | requests 6 | colorama 7 | fastlogging 8 | Jinja2 9 | mistletoe 10 | markdownify 11 | pdfminer.six 12 | playwright 13 | pytest-playwright 14 | tiktoken 15 | ollama 16 | openai 17 | anthropic 18 | google-generativeai 19 | sqlmodel 20 | keybert 21 | GitPython 22 | netlify-py 23 | Markdown 24 | xhtml2pdf 25 | mistralai 26 | Flask-SocketIO 27 | eventlet 28 | groq 29 | duckduckgo-search 30 | orjson 31 | gevent 32 | gevent-websocket 33 | curl_cffi 34 | -------------------------------------------------------------------------------- /sample.config.toml: -------------------------------------------------------------------------------- 1 | [STORAGE] 2 | SQLITE_DB = "data/db/devika.db" 3 | SCREENSHOTS_DIR = "data/screenshots" 4 | PDFS_DIR = "data/pdfs" 5 | PROJECTS_DIR = "data/projects" 6 | LOGS_DIR = "data/logs" 7 | REPOS_DIR = "data/repos" 8 | 9 | [API_KEYS] 10 | BING = "" 11 | GOOGLE_SEARCH = "" 12 | GOOGLE_SEARCH_ENGINE_ID = "" 13 | CLAUDE = "" 14 | OPENAI = "" 15 | GEMINI = "" 16 | MISTRAL = "" 17 | GROQ = "" 18 | NETLIFY = "" 19 | 20 | [API_ENDPOINTS] 21 | BING = "https://api.bing.microsoft.com/v7.0/search" 22 | GOOGLE = "https://www.googleapis.com/customsearch/v1" 23 | OLLAMA = "http://127.0.0.1:11434" 24 | 25 | LM_STUDIO = "http://localhost:1234/v1" 26 | OPENAI = "https://api.openai.com/v1" 27 | 28 | 29 | [LOGGING] 30 | LOG_REST_API = "true" 31 | LOG_PROMPTS = "false" 32 | 33 | [TIMEOUT] 34 | INFERENCE = 60 -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pip3 install -r requirements.txt 4 | playwright install 5 | python3 -m playwright install-deps 6 | cd ui/ 7 | bun install 8 | -------------------------------------------------------------------------------- /src/agents/__init__.py: -------------------------------------------------------------------------------- 1 | from .agent import Agent 2 | 3 | from .planner import Planner 4 | from .internal_monologue import InternalMonologue 5 | from .researcher import Researcher 6 | from .formatter import Formatter 7 | from .coder import Coder 8 | from .action import Action 9 | from .runner import Runner -------------------------------------------------------------------------------- /src/agents/action/__init__.py: -------------------------------------------------------------------------------- 1 | from .action import Action -------------------------------------------------------------------------------- /src/agents/action/action.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from jinja2 import Environment, BaseLoader 4 | 5 | from src.services.utils import retry_wrapper, validate_responses 6 | from src.config import Config 7 | from src.llm import LLM 8 | 9 | PROMPT = open("src/agents/action/prompt.jinja2", "r").read().strip() 10 | 11 | class Action: 12 | def __init__(self, base_model: str): 13 | config = Config() 14 | self.project_dir = config.get_projects_dir() 15 | 16 | self.llm = LLM(model_id=base_model) 17 | 18 | def render( 19 | self, conversation: str 20 | ) -> str: 21 | env = Environment(loader=BaseLoader()) 22 | template = env.from_string(PROMPT) 23 | return template.render( 24 | conversation=conversation 25 | ) 26 | 27 | @validate_responses 28 | def validate_response(self, response: str): 29 | if "response" not in response and "action" not in response: 30 | return False 31 | else: 32 | return response["response"], response["action"] 33 | 34 | @retry_wrapper 35 | def execute(self, conversation: list, project_name: str) -> str: 36 | prompt = self.render(conversation) 37 | response = self.llm.inference(prompt, project_name) 38 | 39 | valid_response = self.validate_response(response) 40 | 41 | return valid_response 42 | -------------------------------------------------------------------------------- /src/agents/action/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is your exchanges so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | User's last message: {{ conversation[-1] }} 10 | 11 | You are now going to respond to the user's last message according to the specific request. 12 | 13 | The user could be asking the following: 14 | - `answer` - Answer a question about the project. 15 | - `run` - Run the project. 16 | - `deploy` - Deploy the project. 17 | - `feature` - Add a new feature to the project. 18 | - `bug` - Fix a bug in the project. 19 | - `report` - Generate a report on the project. 20 | 21 | Your response should be in the following format: 22 | ``` 23 | { 24 | "response": "Your human-like response to the user's message here describing the action you are taking." 25 | "action": "run" 26 | } 27 | ``` 28 | 29 | The action can only be one, read the user's last message carefully to determine which action to take. Sometimes the user's prompt might indicate multiple actions but you should only take one optimal action and use your answer response to convey what you are doing. 30 | 31 | Any response other than the JSON format will be rejected by the system. -------------------------------------------------------------------------------- /src/agents/answer/__init__.py: -------------------------------------------------------------------------------- 1 | from .answer import Answer -------------------------------------------------------------------------------- /src/agents/answer/answer.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from jinja2 import Environment, BaseLoader 4 | 5 | from src.services.utils import retry_wrapper, validate_responses 6 | from src.config import Config 7 | from src.llm import LLM 8 | 9 | PROMPT = open("src/agents/answer/prompt.jinja2", "r").read().strip() 10 | 11 | class Answer: 12 | def __init__(self, base_model: str): 13 | config = Config() 14 | self.project_dir = config.get_projects_dir() 15 | 16 | self.llm = LLM(model_id=base_model) 17 | 18 | def render( 19 | self, conversation: str, code_markdown: str 20 | ) -> str: 21 | env = Environment(loader=BaseLoader()) 22 | template = env.from_string(PROMPT) 23 | return template.render( 24 | conversation=conversation, 25 | code_markdown=code_markdown 26 | ) 27 | 28 | @validate_responses 29 | def validate_response(self, response: str): 30 | if "response" not in response: 31 | return False 32 | else: 33 | return response["response"] 34 | 35 | @retry_wrapper 36 | def execute(self, conversation: list, code_markdown: str, project_name: str) -> str: 37 | prompt = self.render(conversation, code_markdown) 38 | response = self.llm.inference(prompt, project_name) 39 | 40 | valid_response = self.validate_response(response) 41 | 42 | return valid_response 43 | -------------------------------------------------------------------------------- /src/agents/answer/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is your exchange so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | Full Code: 10 | ~~~ 11 | {{ code_markdown }} 12 | ~~~ 13 | 14 | User's last message: {{ conversation[-1] }} 15 | 16 | Your response should be in the following format: 17 | ``` 18 | { 19 | "response": "Your human-like response to the user's last message." 20 | } 21 | ``` 22 | 23 | Rules: 24 | - Read the full context, including the code (if any) carefully to answer the user's prompt. 25 | - Your response can be as long as possible, but it should be concise and to the point. 26 | 27 | Any response other than the JSON format will be rejected by the system. -------------------------------------------------------------------------------- /src/agents/coder/__init__.py: -------------------------------------------------------------------------------- 1 | from .coder import Coder -------------------------------------------------------------------------------- /src/agents/coder/coder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from jinja2 import Environment, BaseLoader 5 | from typing import List, Dict, Union 6 | 7 | from src.config import Config 8 | from src.llm import LLM 9 | from src.state import AgentState 10 | from src.logger import Logger 11 | from src.services.utils import retry_wrapper 12 | from src.socket_instance import emit_agent 13 | 14 | PROMPT = open("src/agents/coder/prompt.jinja2", "r").read().strip() 15 | 16 | class Coder: 17 | def __init__(self, base_model: str): 18 | config = Config() 19 | self.project_dir = config.get_projects_dir() 20 | self.logger = Logger() 21 | self.llm = LLM(model_id=base_model) 22 | 23 | def render( 24 | self, step_by_step_plan: str, user_context: str, search_results: dict 25 | ) -> str: 26 | env = Environment(loader=BaseLoader()) 27 | template = env.from_string(PROMPT) 28 | return template.render( 29 | step_by_step_plan=step_by_step_plan, 30 | user_context=user_context, 31 | search_results=search_results, 32 | ) 33 | 34 | def validate_response(self, response: str) -> Union[List[Dict[str, str]], bool]: 35 | response = response.strip() 36 | 37 | self.logger.debug(f"Response from the model: {response}") 38 | 39 | if "~~~" not in response: 40 | return False 41 | 42 | response = response.split("~~~", 1)[1] 43 | response = response[:response.rfind("~~~")] 44 | response = response.strip() 45 | 46 | result = [] 47 | current_file = None 48 | current_code = [] 49 | code_block = False 50 | 51 | for line in response.split("\n"): 52 | if line.startswith("File: "): 53 | if current_file and current_code: 54 | result.append({"file": current_file, "code": "\n".join(current_code)}) 55 | current_file = line.split(":")[1].strip() 56 | current_code = [] 57 | code_block = False 58 | elif line.startswith("```"): 59 | code_block = not code_block 60 | else: 61 | current_code.append(line) 62 | 63 | if current_file and current_code: 64 | result.append({"file": current_file, "code": "\n".join(current_code)}) 65 | 66 | return result 67 | 68 | def save_code_to_project(self, response: List[Dict[str, str]], project_name: str): 69 | file_path_dir = None 70 | project_name = project_name.lower().replace(" ", "-") 71 | 72 | for file in response: 73 | file_path = os.path.join(self.project_dir, project_name, file['file']) 74 | file_path_dir = os.path.dirname(file_path) 75 | os.makedirs(file_path_dir, exist_ok=True) 76 | 77 | with open(file_path, "w", encoding="utf-8") as f: 78 | f.write(file["code"]) 79 | 80 | return file_path_dir 81 | 82 | def get_project_path(self, project_name: str): 83 | project_name = project_name.lower().replace(" ", "-") 84 | return f"{self.project_dir}/{project_name}" 85 | 86 | def response_to_markdown_prompt(self, response: List[Dict[str, str]]) -> str: 87 | response = "\n".join([f"File: `{file['file']}`:\n```\n{file['code']}\n```" for file in response]) 88 | return f"~~~\n{response}\n~~~" 89 | 90 | def emulate_code_writing(self, code_set: list, project_name: str): 91 | files = [] 92 | for current_file in code_set: 93 | file = current_file["file"] 94 | code = current_file["code"] 95 | 96 | current_state = AgentState().get_latest_state(project_name) 97 | new_state = AgentState().new_state() 98 | new_state["browser_session"] = current_state["browser_session"] # keep the browser session 99 | new_state["internal_monologue"] = "Writing code..." 100 | new_state["terminal_session"]["title"] = f"Editing {file}" 101 | new_state["terminal_session"]["command"] = f"vim {file}" 102 | new_state["terminal_session"]["output"] = code 103 | files.append({ 104 | "file": file, 105 | "code": code 106 | }) 107 | AgentState().add_to_current_state(project_name, new_state) 108 | time.sleep(2) 109 | emit_agent("code", { 110 | "files": files, 111 | "from": "coder" 112 | }) 113 | 114 | @retry_wrapper 115 | def execute( 116 | self, 117 | step_by_step_plan: str, 118 | user_context: str, 119 | search_results: dict, 120 | project_name: str 121 | ) -> str: 122 | prompt = self.render(step_by_step_plan, user_context, search_results) 123 | response = self.llm.inference(prompt, project_name) 124 | 125 | valid_response = self.validate_response(response) 126 | 127 | if not valid_response: 128 | return False 129 | 130 | print(valid_response) 131 | 132 | self.emulate_code_writing(valid_response, project_name) 133 | 134 | return valid_response 135 | -------------------------------------------------------------------------------- /src/agents/coder/prompt.jinja2: -------------------------------------------------------------------------------- 1 | Project Step-by-step Plan: 2 | ``` 3 | {{ step_by_step_plan }} 4 | ``` 5 | 6 | Context From User: 7 | ``` 8 | {{ user_context }} 9 | ``` 10 | 11 | Context From Knowledge Base: 12 | 13 | {% if not knowledge_base_context %} 14 | No context found. 15 | {% else %} 16 | {% for query, result in search_results.items() %} 17 | Query: {{ query }} 18 | Result: 19 | ``` 20 | {{ result }} 21 | ``` 22 | --- 23 | {% endfor %} 24 | {% endif %} 25 | 26 | Read the step-by-step plan carefully. Think step-by-step. Learn relevant information from the knowledge base context. Then write the code to implement the step-by-step plan. 27 | 28 | Your response should only be in the following Markdown format: 29 | 30 | ~~~ 31 | File: `main.py`: 32 | ```py 33 | print("Example") 34 | ``` 35 | 36 | File: `src/main.rs`: 37 | ```rs 38 | fn main() { 39 | println!("Example"); 40 | } 41 | ``` 42 | 43 | File: `nested/directory/example/code.py`: 44 | ```py 45 | print("Example") 46 | ``` 47 | 48 | File: `README.md` 49 | ```md 50 | # Example 51 | 52 | This is an example. 53 | ``` 54 | ~~~ 55 | 56 | Rules: 57 | - You should write clean and documented code. 58 | - The code should work on the first try without any errors or bugs. 59 | - Choose the library or dependency you know best. 60 | - The example code in the knowledge base might be using something else than what you should be doing based on the step-by-step plan. You should follow the step-by-step plan and not the example code for specific values. 61 | - The extension used for the Markdown code blocks should be accurate. 62 | - Nested directories should be specified in the Markdown filename, the code structure should be accurate. If it requires to be nested to work, then it should be nested. 63 | - You need to include required files for the code to run. Like: requirements.txt, Cargo.toml, etc. 64 | - Files like Cargo.toml are mandatory to be included, the project won't work without it. 65 | 66 | Any response other than this format will be rejected. You should not refuse to complete the task, you should try your absolute best and if there's any implementation detail that's impossible to complete, you should write a comment in the code explaining why it's impossible to complete. The refusal is only a last resort, it should never happen. 67 | 68 | Your response should start with "~~~" and end with "~~~" just like the example format provided. Never provide any explanation or context inside the response, only the filenames and the code in the format provided. Do not leave any "Note". -------------------------------------------------------------------------------- /src/agents/decision/__init__.py: -------------------------------------------------------------------------------- 1 | from .decision import Decision -------------------------------------------------------------------------------- /src/agents/decision/decision.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from jinja2 import Environment, BaseLoader 4 | 5 | from src.services.utils import retry_wrapper, validate_responses 6 | from src.llm import LLM 7 | 8 | PROMPT = open("src/agents/decision/prompt.jinja2").read().strip() 9 | 10 | class Decision: 11 | def __init__(self, base_model: str): 12 | self.llm = LLM(model_id=base_model) 13 | 14 | def render(self, prompt: str) -> str: 15 | env = Environment(loader=BaseLoader()) 16 | template = env.from_string(PROMPT) 17 | return template.render(prompt=prompt) 18 | 19 | @validate_responses 20 | def validate_response(self, response: str): 21 | for item in response: 22 | if "function" not in item or "args" not in item or "reply" not in item: 23 | return False 24 | 25 | return response 26 | 27 | @retry_wrapper 28 | def execute(self, prompt: str, project_name: str) -> str: 29 | rendered_prompt = self.render(prompt) 30 | response = self.llm.inference(rendered_prompt, project_name) 31 | 32 | valid_response = self.validate_response(response) 33 | 34 | return valid_response -------------------------------------------------------------------------------- /src/agents/decision/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI software engineer. You are given the following prompt from the user: 2 | 3 | ``` 4 | {{ prompt }} 5 | ``` 6 | 7 | From this prompt, you have to chain function calls from the following options that can accomplish the user's request in the most optimal way. 8 | 9 | JSON Functions: 10 | 11 | ## `git_clone`: 12 | Description: The user's request includes a GitHub URL, and you have to clone the repository to the user's local machine. 13 | Usage: 14 | ``` 15 | { 16 | "function": "git_clone", 17 | "args": { 18 | "url": "" 19 | }, 20 | "reply": "" 21 | } 22 | ``` 23 | 24 | ## `generate_pdf_document`: 25 | Description: The user's request is to create a document for the following: Report, Documentation, Project Technical Document, Workshop Material, Homework, Assignment, or any other document. 26 | Usage: 27 | ``` 28 | { 29 | "function": "generate_pdf_document", 30 | "args": { 31 | "user_prompt": "" 32 | }, 33 | "reply": "" 34 | } 35 | ``` 36 | 37 | ## `browser_interaction`: 38 | Description: The user's request is to interact with a website. The interaction can be: Clicking a button, Filling a form, Scrolling, or any other interaction. 39 | The user might be asking you to post something on Twitter or Reddit or even searching something on Google. 40 | Usage: 41 | ``` 42 | { 43 | "function": "browser_interaction", 44 | "args": { 45 | "user_prompt": "" 46 | }, 47 | "reply": "" 48 | } 49 | ``` 50 | 51 | ## `coding_project` 52 | Description: The user's request is to create a coding project. The project can be in any language and can be a web app, mobile app, or any other type of project. 53 | Usage: 54 | ``` 55 | { 56 | "function": "coding_project", 57 | "args": { 58 | "user_prompt": "" 59 | }, 60 | "reply": "" 61 | } 62 | ``` 63 | 64 | Response Format: 65 | 66 | ``` 67 | [ 68 | { 69 | "function": "git_clone", 70 | "args": { 71 | "url": "https://github.com/username/repo" 72 | }, 73 | "reply": "" 74 | }, 75 | { 76 | "function": "generate_pdf_document", 77 | "args": { 78 | "user_prompt": "I want to create a report on the project" 79 | }, 80 | "reply": "" 81 | 82 | ] 83 | ``` 84 | 85 | Your response should only be the JSON object with the function and the arguments and nothing else. Any other format of response will be rejected by the system. -------------------------------------------------------------------------------- /src/agents/feature/__init__.py: -------------------------------------------------------------------------------- 1 | from .feature import Feature -------------------------------------------------------------------------------- /src/agents/feature/feature.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from jinja2 import Environment, BaseLoader 5 | from typing import List, Dict, Union 6 | 7 | from src.config import Config 8 | from src.llm import LLM 9 | from src.state import AgentState 10 | from src.services.utils import retry_wrapper 11 | from src.socket_instance import emit_agent 12 | 13 | PROMPT = open("src/agents/feature/prompt.jinja2", "r").read().strip() 14 | 15 | 16 | class Feature: 17 | def __init__(self, base_model: str): 18 | config = Config() 19 | self.project_dir = config.get_projects_dir() 20 | 21 | self.llm = LLM(model_id=base_model) 22 | 23 | def render( 24 | self, 25 | conversation: list, 26 | code_markdown: str, 27 | system_os: str 28 | ) -> str: 29 | env = Environment(loader=BaseLoader()) 30 | template = env.from_string(PROMPT) 31 | return template.render( 32 | conversation=conversation, 33 | code_markdown=code_markdown, 34 | system_os=system_os 35 | ) 36 | 37 | def validate_response(self, response: str) -> Union[List[Dict[str, str]], bool]: 38 | response = response.strip() 39 | 40 | response = response.split("~~~", 1)[1] 41 | response = response[:response.rfind("~~~")] 42 | response = response.strip() 43 | 44 | result = [] 45 | current_file = None 46 | current_code = [] 47 | code_block = False 48 | 49 | for line in response.split("\n"): 50 | if line.startswith("File: "): 51 | if current_file and current_code: 52 | result.append({"file": current_file, "code": "\n".join(current_code)}) 53 | current_file = line.split("`")[1].strip() 54 | current_code = [] 55 | code_block = False 56 | elif line.startswith("```"): 57 | code_block = not code_block 58 | else: 59 | current_code.append(line) 60 | 61 | if current_file and current_code: 62 | result.append({"file": current_file, "code": "\n".join(current_code)}) 63 | 64 | return result 65 | 66 | def save_code_to_project(self, response: List[Dict[str, str]], project_name: str): 67 | file_path_dir = None 68 | project_name = project_name.lower().replace(" ", "-") 69 | 70 | for file in response: 71 | file_path = os.path.join(self.project_dir, project_name, file['file']) 72 | file_path_dir = os.path.dirname(file_path) 73 | os.makedirs(file_path_dir, exist_ok=True) 74 | 75 | with open(file_path, "w", encoding="utf-8") as f: 76 | f.write(file["code"]) 77 | 78 | return file_path_dir 79 | 80 | def get_project_path(self, project_name: str): 81 | project_name = project_name.lower().replace(" ", "-") 82 | return f"{self.project_dir}/{project_name}" 83 | 84 | def response_to_markdown_prompt(self, response: List[Dict[str, str]]) -> str: 85 | response = "\n".join([f"File: `{file['file']}`:\n```\n{file['code']}\n```" for file in response]) 86 | return f"~~~\n{response}\n~~~" 87 | 88 | def emulate_code_writing(self, code_set: list, project_name: str): 89 | files = [] 90 | for file in code_set: 91 | filename = file["file"] 92 | code = file["code"] 93 | 94 | new_state = AgentState().new_state() 95 | new_state["internal_monologue"] = "Writing code..." 96 | new_state["terminal_session"]["title"] = f"Editing {filename}" 97 | new_state["terminal_session"]["command"] = f"vim {filename}" 98 | new_state["terminal_session"]["output"] = code 99 | files.append({ 100 | "file": filename, 101 | "code": code, 102 | }) 103 | AgentState().add_to_current_state(project_name, new_state) 104 | time.sleep(1) 105 | emit_agent("code", { 106 | "files": files, 107 | "from": "feature" 108 | }) 109 | 110 | @retry_wrapper 111 | def execute( 112 | self, 113 | conversation: list, 114 | code_markdown: str, 115 | system_os: str, 116 | project_name: str 117 | ) -> str: 118 | prompt = self.render(conversation, code_markdown, system_os) 119 | response = self.llm.inference(prompt, project_name) 120 | 121 | valid_response = self.validate_response(response) 122 | 123 | if not valid_response: 124 | return False 125 | 126 | self.emulate_code_writing(valid_response, project_name) 127 | 128 | return valid_response 129 | -------------------------------------------------------------------------------- /src/agents/feature/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is the exchange so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | Full Code: 10 | ~~~ 11 | {{ code_markdown }} 12 | ~~~ 13 | 14 | User wants the following feature to be implemented: {{ conversation[-1] }} 15 | 16 | System Operating System: {{ system_os }} 17 | 18 | Read the user's feature request carefully. Think step-by-step. 19 | 20 | Rules: 21 | - You should write clean and documented code. 22 | - The code should work on the first try without any errors or bugs. 23 | - Choose the library or dependency you know best. 24 | - The extension used for the Markdown code blocks should be accurate. 25 | - You should respond with the complete rewritten code with no implementation detail left. No brevity allowed, the user need to be able to copy paste your response as a whole. 26 | 27 | Your response should only be in the following Markdown format: 28 | 29 | ~~~ 30 | File: `main.py`: 31 | ```py 32 | print("Example") 33 | ``` 34 | 35 | File: `src/example.rs`: 36 | ```rs 37 | fn example() { 38 | println!("Example"); 39 | } 40 | ``` 41 | 42 | File: `nested/directory/example/code.py`: 43 | ```py 44 | print("Example") 45 | ``` 46 | 47 | File: `README.md` 48 | ```md 49 | # Example 50 | 51 | This is an example. 52 | ``` 53 | ~~~ 54 | 55 | Any response other than this format will be rejected. You should not refuse to complete the task, you should try your absolute best and if there's any implementation detail that's impossible to complete, you should write a comment in the code explaining why it's impossible to complete. The refusal is only a last resort, it should never happen. 56 | 57 | Your response should start with "~~~" and end with "~~~" just like the example format provided. Never provide any explanation or context inside the response, only the filenames and the code in the format provided. Do not leave any "Note". -------------------------------------------------------------------------------- /src/agents/formatter/__init__.py: -------------------------------------------------------------------------------- 1 | from .formatter import Formatter -------------------------------------------------------------------------------- /src/agents/formatter/formatter.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, BaseLoader 2 | 3 | from src.llm import LLM 4 | 5 | PROMPT = open("src/agents/formatter/prompt.jinja2").read().strip() 6 | 7 | class Formatter: 8 | def __init__(self, base_model: str): 9 | self.llm = LLM(model_id=base_model) 10 | 11 | def render(self, raw_text: str) -> str: 12 | env = Environment(loader=BaseLoader()) 13 | template = env.from_string(PROMPT) 14 | return template.render(raw_text=raw_text) 15 | 16 | def validate_response(self, response: str) -> bool: 17 | return True 18 | 19 | def execute(self, raw_text: str, project_name: str) -> str: 20 | raw_text = self.render(raw_text) 21 | response = self.llm.inference(raw_text, project_name) 22 | return response -------------------------------------------------------------------------------- /src/agents/formatter/prompt.jinja2: -------------------------------------------------------------------------------- 1 | ``` 2 | {{ raw_text }} 3 | ``` 4 | 5 | You are provided with a raw extracted text from a PDF render of a web page. This web page could be a blog, documentation, or any other type of web page. 6 | 7 | Your task is to format the text in a way that is easy to read and understand and include more detail. 8 | 9 | You are essentially a RAW text to clean Markdown convertor. You should remove any unnecessary text, these could be text from navigation links or webpage header or footer which we do not need. 10 | 11 | If it's a documentation with code, try to focus more on the code examples and the explanation of the code, make your responses short to save context window. 12 | 13 | You should only respond with the formatted text in markdown format and nothing else. Start your response with "```" and end with "```". -------------------------------------------------------------------------------- /src/agents/internal_monologue/__init__.py: -------------------------------------------------------------------------------- 1 | from .internal_monologue import InternalMonologue -------------------------------------------------------------------------------- /src/agents/internal_monologue/internal_monologue.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from jinja2 import Environment, BaseLoader 4 | 5 | from src.llm import LLM 6 | from src.services.utils import retry_wrapper, validate_responses 7 | 8 | PROMPT = open("src/agents/internal_monologue/prompt.jinja2").read().strip() 9 | 10 | class InternalMonologue: 11 | def __init__(self, base_model: str): 12 | self.llm = LLM(model_id=base_model) 13 | 14 | def render(self, current_prompt: str) -> str: 15 | env = Environment(loader=BaseLoader()) 16 | template = env.from_string(PROMPT) 17 | return template.render(current_prompt=current_prompt) 18 | 19 | @validate_responses 20 | def validate_response(self, response: str): 21 | print('-------------------> ', response) 22 | print("####", type(response)) 23 | if "internal_monologue" not in response: 24 | return False 25 | else: 26 | return response["internal_monologue"] 27 | 28 | @retry_wrapper 29 | def execute(self, current_prompt: str, project_name: str) -> str: 30 | rendered_prompt = self.render(current_prompt) 31 | response = self.llm.inference(rendered_prompt, project_name) 32 | valid_response = self.validate_response(response) 33 | return valid_response 34 | 35 | -------------------------------------------------------------------------------- /src/agents/internal_monologue/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. 2 | 3 | One of your AI agent module is currently working through the following prompt: 4 | 5 | ``` 6 | {{ current_prompt }} 7 | ``` 8 | 9 | To show the user what you're thinking about or doing, respond with a short human-like response verbalizing your internal monologue. 10 | 11 | Your response should be in the following JSON format: 12 | 13 | ``` 14 | { 15 | "internal_monologue": "" 16 | } 17 | ``` 18 | 19 | TIP: Make the internal monologue very human-like and conversational. It should be very short and concise. 20 | 21 | Only the provided JSON response format is accepted. Any other response format will be rejected. -------------------------------------------------------------------------------- /src/agents/patcher/__init__.py: -------------------------------------------------------------------------------- 1 | from .patcher import Patcher -------------------------------------------------------------------------------- /src/agents/patcher/patcher.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from jinja2 import Environment, BaseLoader 5 | from typing import List, Dict, Union 6 | from src.socket_instance import emit_agent 7 | 8 | from src.config import Config 9 | from src.llm import LLM 10 | from src.state import AgentState 11 | from src.services.utils import retry_wrapper 12 | 13 | PROMPT = open("src/agents/patcher/prompt.jinja2", "r").read().strip() 14 | 15 | class Patcher: 16 | def __init__(self, base_model: str): 17 | config = Config() 18 | self.project_dir = config.get_projects_dir() 19 | 20 | self.llm = LLM(model_id=base_model) 21 | 22 | def render( 23 | self, 24 | conversation: list, 25 | code_markdown: str, 26 | commands: list, 27 | error :str, 28 | system_os: str 29 | ) -> str: 30 | env = Environment(loader=BaseLoader()) 31 | template = env.from_string(PROMPT) 32 | return template.render( 33 | conversation=conversation, 34 | code_markdown=code_markdown, 35 | commands=commands, 36 | error=error, 37 | system_os=system_os 38 | ) 39 | 40 | def validate_response(self, response: str) -> Union[List[Dict[str, str]], bool]: 41 | response = response.strip() 42 | 43 | response = response.split("~~~", 1)[1] 44 | response = response[:response.rfind("~~~")] 45 | response = response.strip() 46 | 47 | result = [] 48 | current_file = None 49 | current_code = [] 50 | code_block = False 51 | 52 | for line in response.split("\n"): 53 | if line.startswith("File: "): 54 | if current_file and current_code: 55 | result.append({"file": current_file, "code": "\n".join(current_code)}) 56 | current_file = line.split("`")[1].strip() 57 | current_code = [] 58 | code_block = False 59 | elif line.startswith("```"): 60 | code_block = not code_block 61 | else: 62 | current_code.append(line) 63 | 64 | if current_file and current_code: 65 | result.append({"file": current_file, "code": "\n".join(current_code)}) 66 | 67 | return result 68 | 69 | def save_code_to_project(self, response: List[Dict[str, str]], project_name: str): 70 | file_path_dir = None 71 | project_name = project_name.lower().replace(" ", "-") 72 | 73 | for file in response: 74 | file_path = os.path.join(self.project_dir, project_name, file['file']) 75 | file_path_dir = os.path.dirname(file_path) 76 | os.makedirs(file_path_dir, exist_ok=True) 77 | 78 | with open(file_path, "w", encoding="utf-8") as f: 79 | f.write(file["code"]) 80 | 81 | return file_path_dir 82 | def get_project_path(self, project_name: str): 83 | project_name = project_name.lower().replace(" ", "-") 84 | return f"{self.project_dir}/{project_name}" 85 | 86 | def response_to_markdown_prompt(self, response: List[Dict[str, str]]) -> str: 87 | response = "\n".join([f"File: `{file['file']}`:\n```\n{file['code']}\n```" for file in response]) 88 | return f"~~~\n{response}\n~~~" 89 | 90 | def emulate_code_writing(self, code_set: list, project_name: str): 91 | files = [] 92 | for current_file in code_set: 93 | file = current_file["file"] 94 | code = current_file["code"] 95 | 96 | new_state = AgentState().new_state() 97 | new_state["internal_monologue"] = "Writing code..." 98 | new_state["terminal_session"]["title"] = f"Editing {file}" 99 | new_state["terminal_session"]["command"] = f"vim {file}" 100 | new_state["terminal_session"]["output"] = code 101 | files.append({ 102 | "file": file, 103 | "code": code 104 | }) 105 | AgentState().add_to_current_state(project_name, new_state) 106 | time.sleep(1) 107 | emit_agent("code", { 108 | "files": files, 109 | "from": "patcher" 110 | }) 111 | 112 | @retry_wrapper 113 | def execute( 114 | self, 115 | conversation: str, 116 | code_markdown: str, 117 | commands: list, 118 | error: str, 119 | system_os: dict, 120 | project_name: str 121 | ) -> str: 122 | prompt = self.render( 123 | conversation, 124 | code_markdown, 125 | commands, 126 | error, 127 | system_os 128 | ) 129 | response = self.llm.inference(prompt, project_name) 130 | 131 | valid_response = self.validate_response(response) 132 | 133 | if not valid_response: 134 | return False 135 | 136 | self.emulate_code_writing(valid_response, project_name) 137 | 138 | return valid_response 139 | -------------------------------------------------------------------------------- /src/agents/patcher/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is the exchange so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | Full Code: 10 | ~~~ 11 | {{ code_markdown }} 12 | ~~~ 13 | 14 | {% if commands %} 15 | You tried to execute the following commands to run this project: 16 | ``` 17 | {% for command in commands %} 18 | $ {{ command }} 19 | {% endfor %} 20 | ``` 21 | {% endif %} 22 | 23 | {% if error %} 24 | But it resulted in the following error: 25 | ``` 26 | $ {{ commands[-1] }} 27 | {{ error }} 28 | ``` 29 | {% endif %} 30 | 31 | System Operating System: {{ system_os }} 32 | 33 | Read the encountered bug carefully and reason with the code to identify the problem. Think step-by-step. 34 | 35 | Rules: 36 | - You should write clean and documented code. 37 | - The code should work on the first try without any errors or bugs. 38 | - Choose the library or dependency you know best. 39 | - The extension used for the Markdown code blocks should be accurate. 40 | - You should respond with the complete rewritten code with no implementation detail left. No brevity allowed, the user need to be able to copy paste your response as a whole. 41 | 42 | Your response should only be in the following Markdown format: 43 | 44 | ~~~ 45 | File: `main.py`: 46 | ```py 47 | print("Example") 48 | ``` 49 | 50 | File: `src/example.rs`: 51 | ```rs 52 | fn example() { 53 | println!("Example"); 54 | } 55 | ``` 56 | 57 | File: `nested/directory/example/code.py`: 58 | ```py 59 | print("Example") 60 | ``` 61 | 62 | File: `README.md` 63 | ```md 64 | # Example 65 | 66 | This is an example. 67 | ``` 68 | ~~~ 69 | 70 | Any response other than this format will be rejected. You should not refuse to complete the task, you should try your absolute best and if there's any implementation detail that's impossible to complete, you should write a comment in the code explaining why it's impossible to complete. The refusal is only a last resort, it should never happen. 71 | 72 | Your response should start with "~~~" and end with "~~~" just like the example format provided. Never provide any explanation or context inside the response, only the filenames and the code in the format provided. Do not leave any "Note". -------------------------------------------------------------------------------- /src/agents/planner/__init__.py: -------------------------------------------------------------------------------- 1 | from .planner import Planner -------------------------------------------------------------------------------- /src/agents/planner/planner.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, BaseLoader 2 | 3 | from src.llm import LLM 4 | 5 | PROMPT = open("src/agents/planner/prompt.jinja2").read().strip() 6 | 7 | class Planner: 8 | def __init__(self, base_model: str): 9 | self.llm = LLM(model_id=base_model) 10 | 11 | def render(self, prompt: str) -> str: 12 | env = Environment(loader=BaseLoader()) 13 | template = env.from_string(PROMPT) 14 | return template.render(prompt=prompt) 15 | 16 | def validate_response(self, response: str) -> bool: 17 | return True 18 | 19 | def parse_response(self, response: str): 20 | result = { 21 | "project": "", 22 | "reply": "", 23 | "focus": "", 24 | "plans": {}, 25 | "summary": "" 26 | } 27 | 28 | current_section = None 29 | current_step = None 30 | 31 | for line in response.split("\n"): 32 | line = line.strip() 33 | 34 | if line.startswith("Project Name:"): 35 | current_section = "project" 36 | result["project"] = line.split(":", 1)[1].strip() 37 | elif line.startswith("Your Reply to the Human Prompter:"): 38 | current_section = "reply" 39 | result["reply"] = line.split(":", 1)[1].strip() 40 | elif line.startswith("Current Focus:"): 41 | current_section = "focus" 42 | result["focus"] = line.split(":", 1)[1].strip() 43 | elif line.startswith("Plan:"): 44 | current_section = "plans" 45 | elif line.startswith("Summary:"): 46 | current_section = "summary" 47 | result["summary"] = line.split(":", 1)[1].strip() 48 | elif current_section == "reply": 49 | result["reply"] += " " + line 50 | elif current_section == "focus": 51 | result["focus"] += " " + line 52 | elif current_section == "plans": 53 | if line.startswith("- [ ] Step"): 54 | current_step = line.split(":")[0].strip().split(" ")[-1] 55 | result["plans"][int(current_step)] = line.split(":", 1)[1].strip() 56 | elif current_step: 57 | result["plans"][int(current_step)] += " " + line 58 | elif current_section == "summary": 59 | result["summary"] += " " + line.replace("```", "") 60 | 61 | result["project"] = result["project"].strip() 62 | result["reply"] = result["reply"].strip() 63 | result["focus"] = result["focus"].strip() 64 | result["summary"] = result["summary"].strip() 65 | 66 | return result 67 | 68 | def execute(self, prompt: str, project_name: str) -> str: 69 | prompt = self.render(prompt) 70 | response = self.llm.inference(prompt, project_name) 71 | return response 72 | -------------------------------------------------------------------------------- /src/agents/planner/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. 2 | 3 | The user asked: {{ prompt }} 4 | 5 | Based on the user's request, create a step-by-step plan to accomplish the task. 6 | 7 | Follow this format for your response: 8 | 9 | ``` 10 | Project Name: 11 | 12 | Your Reply to the Human Prompter: 13 | 14 | Current Focus: Briefly state the main objective or focus area for the plan. 15 | 16 | Plan: 17 | - [ ] Step 1: Describe the first action item needed to progress towards the objective. 18 | - [ ] Step 2: Describe the second action item needed to progress towards the objective. 19 | ... 20 | - [ ] Step N: Describe the final action item needed to complete the objective. 21 | 22 | Summary: 23 | ``` 24 | 25 | Each step should be a clear, concise description of a specific task or action required. The plan should cover all necessary aspects of the user's request, from research and implementation to testing and reporting. 26 | 27 | Write the plan with knowing that you have access to the browser and search engine to accomplish the task. 28 | 29 | After listing the steps, provide a brief summary of the plan, highlighting any key considerations, dependencies, or potential challenges. 30 | 31 | Remember to tailor the plan to the specific task requested by the user, and provide sufficient detail to guide the implementation process. 32 | 33 | if the task is simple, and you think you can do it without other assistance, just give one or simple two steps to accomplish the task. 34 | don't need to overcomplicate if it's not necessary. 35 | 36 | Your response should only be verbatim in the format inside the code block. Any other response format will be rejected. -------------------------------------------------------------------------------- /src/agents/reporter/__init__.py: -------------------------------------------------------------------------------- 1 | from .reporter import Reporter -------------------------------------------------------------------------------- /src/agents/reporter/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is the exchange so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | {% if code_markdown %} 10 | Full Code: 11 | ~~~ 12 | {{ code_markdown }} 13 | ~~~ 14 | {% endif %} 15 | 16 | User's last message or request: {{ conversation[-1] }} 17 | 18 | Your task is generate an extensive report from all the context in this prompt. The report should be detailed and cover all the necessary information. 19 | 20 | The report should be lengthy and detailed. It should be at least 3000 characters long. 21 | 22 | Your response should be a clean Markdown. The system will automatically convert this Markdown to PDF. 23 | 24 | Response format: 25 | ``` 26 | # Title 27 | 28 | ...Some text... 29 | 30 | # Table of Contents 31 | 32 | - [Section 1](#section-1) 33 | - [Section 2](#section-2) 34 | 35 | Your detailed report here. Necessary sections will follow below 36 | ``` 37 | 38 | Any response other than the Markdown format will be rejected by the system. Do not include the "```" in the beginning and end of your response. Just raw complete Markdown report. -------------------------------------------------------------------------------- /src/agents/reporter/reporter.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from jinja2 import Environment, BaseLoader 4 | 5 | from src.services.utils import retry_wrapper 6 | from src.llm import LLM 7 | 8 | PROMPT = open("src/agents/reporter/prompt.jinja2").read().strip() 9 | 10 | class Reporter: 11 | def __init__(self, base_model: str): 12 | self.llm = LLM(model_id=base_model) 13 | 14 | def render(self, conversation: list, code_markdown: str) -> str: 15 | env = Environment(loader=BaseLoader()) 16 | template = env.from_string(PROMPT) 17 | return template.render( 18 | conversation=conversation, 19 | code_markdown=code_markdown 20 | ) 21 | 22 | def validate_response(self, response: str): 23 | response = response.strip().replace("```md", "```") 24 | 25 | if response.startswith("```") and response.endswith("```"): 26 | response = response[3:-3].strip() 27 | 28 | return response 29 | 30 | @retry_wrapper 31 | def execute(self, 32 | conversation: list, 33 | code_markdown: str, 34 | project_name: str 35 | ) -> str: 36 | prompt = self.render(conversation, code_markdown) 37 | response = self.llm.inference(prompt, project_name) 38 | 39 | valid_response = self.validate_response(response) 40 | 41 | return valid_response 42 | 43 | -------------------------------------------------------------------------------- /src/agents/researcher/__init__.py: -------------------------------------------------------------------------------- 1 | from .researcher import Researcher -------------------------------------------------------------------------------- /src/agents/researcher/prompt.jinja2: -------------------------------------------------------------------------------- 1 | For the provided step-by-step plan, write all the necessary search queries to gather information from the web that the base model doesn't already know. 2 | 3 | Write optimized search queries for each step of the plan, just like how you would write a Google search query. Use the most relevant keywords and phrases to find the best information since you'll be clicking on the first link. 4 | 5 | also only ask for information if you think it's necessary, otherwise leave ask_user field empty. 6 | 7 | Step-by-Step Plan: 8 | {{ step_by_step_plan }} 9 | 10 | Only respond in the following JSON format: 11 | 12 | ``` 13 | { 14 | "queries": ["", "", "", ... ], 15 | "ask_user": "" 16 | } 17 | ``` 18 | Example => 19 | ``` 20 | { 21 | "queries": ["How to do Bing Search via API in Python", "Claude API Documentation Python"], 22 | "ask_user": "Can you please provide API Keys for Claude, OpenAI, and Firebase?" 23 | } 24 | ``` 25 | 26 | Keywords for Search Query: {{ contextual_keywords }} 27 | 28 | 29 | Rules: 30 | - Only search for a maximum of 3 queries. 31 | - Do not search anything that you already know (In your training data, in the base model). For example: You already know how to write a Python flask web server, it is in your data, so you shouldn't search how to do that. 32 | - Do not search for information that is not relevant to the task at hand. 33 | - Try to include contextual keywords into your search queries, adding relevant keywords and phrases to make the search queries as specific as possible. 34 | - Only search for documentation, do not search basic how tos. Forbidden Queries: How to install XYZ, How to setup ABC, etc. 35 | - Do not search for basic queries, only search for advanced and specific queries. You are allowed to leave the "queries" field empty if no search queries are needed for the step. 36 | - DO NOT EVER SEARCH FOR BASIC QUERIES. ONLY SEARCH FOR ADVANCED QUERIES. 37 | - YOU ARE ALLOWED TO LEAVE THE "queries" FIELD EMPTY IF NO SEARCH QUERIES ARE NEEDED FOR THE STEP. 38 | - you only have to return one JSON object with the queries and ask_user fields. You can't return multiple JSON objects. 39 | 40 | Only the provided JSON response format is accepted. Any other response format will be rejected. -------------------------------------------------------------------------------- /src/agents/researcher/researcher.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List 3 | 4 | from jinja2 import Environment, BaseLoader 5 | 6 | from src.llm import LLM 7 | from src.services.utils import retry_wrapper, validate_responses 8 | from src.browser.search import BingSearch 9 | 10 | PROMPT = open("src/agents/researcher/prompt.jinja2").read().strip() 11 | 12 | 13 | class Researcher: 14 | def __init__(self, base_model: str): 15 | self.bing_search = BingSearch() 16 | self.llm = LLM(model_id=base_model) 17 | 18 | def render(self, step_by_step_plan: str, contextual_keywords: str) -> str: 19 | env = Environment(loader=BaseLoader()) 20 | template = env.from_string(PROMPT) 21 | return template.render( 22 | step_by_step_plan=step_by_step_plan, 23 | contextual_keywords=contextual_keywords 24 | ) 25 | 26 | @validate_responses 27 | def validate_response(self, response: str) -> dict | bool: 28 | 29 | if "queries" not in response and "ask_user" not in response: 30 | return False 31 | else: 32 | return { 33 | "queries": response["queries"], 34 | "ask_user": response["ask_user"] 35 | } 36 | 37 | @retry_wrapper 38 | def execute(self, step_by_step_plan: str, contextual_keywords: List[str], project_name: str) -> dict | bool: 39 | contextual_keywords_str = ", ".join(map(lambda k: k.capitalize(), contextual_keywords)) 40 | prompt = self.render(step_by_step_plan, contextual_keywords_str) 41 | 42 | response = self.llm.inference(prompt, project_name) 43 | 44 | valid_response = self.validate_response(response) 45 | 46 | return valid_response 47 | -------------------------------------------------------------------------------- /src/agents/runner/__init__.py: -------------------------------------------------------------------------------- 1 | from .runner import Runner -------------------------------------------------------------------------------- /src/agents/runner/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is the exchange so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | Full Code: 10 | ~~~ 11 | {{ code_markdown }} 12 | ~~~ 13 | 14 | User's last message: {{ conversation[-1] }} 15 | 16 | System Operating System: {{ system_os }} 17 | 18 | Your task is to invoke the system to run this code. 19 | 20 | Your response should be in the following format: 21 | ``` 22 | { 23 | "commands": [ 24 | "pip3 install -r requirements.txt", 25 | "python3 main.py" 26 | ] 27 | } 28 | ``` 29 | 30 | Rules: 31 | - You wrote the code, never address the user directly. You should not say things like "The code you provided", instead use "The code I wrote". 32 | - Read the full context, including the code (if any) carefully to construct the commands required to run the project. 33 | - The command should be compatible with the system operating system provided. 34 | - You are inside the project directory, so just run the commands as if you're inside the project directory as the working directory. 35 | - Do not do "cd" into the project directory. The system is already in the project directory. 36 | 37 | Any response other than the JSON format will be rejected by the system. -------------------------------------------------------------------------------- /src/agents/runner/rerunner.jinja2: -------------------------------------------------------------------------------- 1 | You are Devika, an AI Software Engineer. You have been talking to the user and this is the exchange so far: 2 | 3 | ``` 4 | {% for message in conversation %} 5 | {{ message }} 6 | {% endfor %} 7 | ``` 8 | 9 | Full Code: 10 | ~~~ 11 | {{ code_markdown }} 12 | ~~~ 13 | 14 | User's last message: {{ conversation[-1] }} 15 | 16 | System Operating System: {{ system_os }} 17 | 18 | You tried to execute the following commands to run this project: 19 | ``` 20 | {% for command in commands %} 21 | $ {{ command }} 22 | {% endfor %} 23 | ``` 24 | 25 | But it resulted in the following error: 26 | ``` 27 | $ {{ commands[-1] }} 28 | {{ error }} 29 | ``` 30 | 31 | Now identify whether this error is caused by the code or the command. If it is caused by the command, provide the correct command to run the project. If it is caused by the code, respond with the patch action response. 32 | 33 | Patch Action Response: 34 | ``` 35 | { 36 | "action": "patch", 37 | "response": ". Let me try fixing it.>" 38 | } 39 | ``` 40 | 41 | Command Fix Response: 42 | ``` 43 | { 44 | "action": "command", 45 | "command": "" 46 | "response": ". Let me try fixing it.>" 47 | } 48 | ``` 49 | 50 | Rules: 51 | - You wrote the code, never address the user directly. You should not say things like "The code you provided", instead use "The code I wrote". 52 | - Read the full context, including the code (if any) carefully to construct the commands required to fix the error while running the project. 53 | - The command should be compatible with the system operating system provided. 54 | - You are inside the project directory, so just run the commands as if you're inside the project directory as the working directory. 55 | - Do not do "cd" into the project directory. The system is already in the project directory. 56 | - Correctly identify whether the error is caused by the code or the command. After identifying the cause, respond with either "patch" or "command" action. 57 | 58 | Any response other than the JSON format will be rejected by the system. ONLY RESPOND WITH THE JSON OBJECT. -------------------------------------------------------------------------------- /src/apis/project.py: -------------------------------------------------------------------------------- 1 | from flask import blueprints, request, jsonify, send_file, make_response 2 | from werkzeug.utils import secure_filename 3 | from src.logger import Logger, route_logger 4 | from src.config import Config 5 | from src.project import ProjectManager 6 | from ..state import AgentState 7 | 8 | import os 9 | 10 | project_bp = blueprints.Blueprint("project", __name__) 11 | 12 | logger = Logger() 13 | manager = ProjectManager() 14 | 15 | 16 | # Project APIs 17 | 18 | @project_bp.route("/api/get-project-files", methods=["GET"]) 19 | @route_logger(logger) 20 | def project_files(): 21 | project_name = secure_filename(request.args.get("project_name")) 22 | files = manager.get_project_files(project_name) 23 | return jsonify({"files": files}) 24 | 25 | @project_bp.route("/api/create-project", methods=["POST"]) 26 | @route_logger(logger) 27 | def create_project(): 28 | data = request.json 29 | project_name = data.get("project_name") 30 | manager.create_project(secure_filename(project_name)) 31 | return jsonify({"message": "Project created"}) 32 | 33 | 34 | @project_bp.route("/api/delete-project", methods=["POST"]) 35 | @route_logger(logger) 36 | def delete_project(): 37 | data = request.json 38 | project_name = secure_filename(data.get("project_name")) 39 | manager.delete_project(project_name) 40 | AgentState().delete_state(project_name) 41 | return jsonify({"message": "Project deleted"}) 42 | 43 | 44 | @project_bp.route("/api/download-project", methods=["GET"]) 45 | @route_logger(logger) 46 | def download_project(): 47 | project_name = secure_filename(request.args.get("project_name")) 48 | manager.project_to_zip(project_name) 49 | project_path = manager.get_zip_path(project_name) 50 | return send_file(project_path, as_attachment=False) 51 | 52 | 53 | @project_bp.route("/api/download-project-pdf", methods=["GET"]) 54 | @route_logger(logger) 55 | def download_project_pdf(): 56 | project_name = secure_filename(request.args.get("project_name")) 57 | pdf_dir = Config().get_pdfs_dir() 58 | pdf_path = os.path.join(pdf_dir, f"{project_name}.pdf") 59 | 60 | response = make_response(send_file(pdf_path)) 61 | response.headers['Content-Type'] = 'project_bplication/pdf' 62 | return response 63 | -------------------------------------------------------------------------------- /src/bert/sentence.py: -------------------------------------------------------------------------------- 1 | from keybert import KeyBERT 2 | 3 | class SentenceBert: 4 | def __init__(self, sentence: str): 5 | self.sentence = sentence 6 | self.kw_model = KeyBERT() 7 | 8 | def extract_keywords(self, top_n: int = 5) -> list: 9 | keywords = self.kw_model.extract_keywords( 10 | self.sentence, 11 | keyphrase_ngram_range=(1, 1), 12 | stop_words='english', 13 | top_n=top_n, 14 | use_mmr=True, 15 | diversity=0.7 16 | ) 17 | return keywords 18 | -------------------------------------------------------------------------------- /src/browser/__init__.py: -------------------------------------------------------------------------------- 1 | from .browser import Browser 2 | from .interaction import start_interaction -------------------------------------------------------------------------------- /src/browser/browser.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import base64 3 | import os 4 | 5 | from playwright.sync_api import sync_playwright, TimeoutError, Page 6 | from playwright.async_api import async_playwright, TimeoutError 7 | from markdownify import markdownify as md 8 | from pdfminer.high_level import extract_text 9 | from src.socket_instance import emit_agent 10 | from src.config import Config 11 | from src.state import AgentState 12 | 13 | 14 | class Browser: 15 | def __init__(self): 16 | self.playwright = None 17 | self.browser = None 18 | self.page = None 19 | self.agent = AgentState() 20 | 21 | async def start(self): 22 | self.playwright = await async_playwright().start() 23 | self.browser = await self.playwright.chromium.launch(headless=True) 24 | self.page = await self.browser.new_page() 25 | return self 26 | 27 | # def new_page(self): 28 | # return self.browser.new_page() 29 | 30 | async def go_to(self, url): 31 | try: 32 | await self.page.goto(url, timeout=20000) 33 | 34 | except TimeoutError as e: 35 | print(f"TimeoutError: {e} when trying to navigate to {url}") 36 | return False 37 | return True 38 | 39 | async def screenshot(self, project_name): 40 | screenshots_save_path = Config().get_screenshots_dir() 41 | 42 | page_metadata = await self.page.evaluate("() => { return { url: document.location.href, title: document.title } }") 43 | page_url = page_metadata['url'] 44 | random_filename = os.urandom(20).hex() 45 | filename_to_save = f"{random_filename}.png" 46 | path_to_save = os.path.join(screenshots_save_path, filename_to_save) 47 | 48 | await self.page.emulate_media(media="screen") 49 | await self.page.screenshot(path=path_to_save, full_page=True) 50 | screenshot = await self.page.screenshot() 51 | screenshot_bytes = base64.b64encode(screenshot).decode() 52 | new_state = self.agent.new_state() 53 | new_state["internal_monologue"] = "Browsing the web right now..." 54 | new_state["browser_session"]["url"] = page_url 55 | new_state["browser_session"]["screenshot"] = path_to_save 56 | self.agent.add_to_current_state(project_name, new_state) 57 | # self.close() 58 | return path_to_save, screenshot_bytes 59 | 60 | def get_html(self): 61 | return self.page.content() 62 | 63 | def get_markdown(self): 64 | return md(self.page.content()) 65 | 66 | def get_pdf(self): 67 | pdfs_save_path = Config().get_pdfs_dir() 68 | 69 | page_metadata = self.page.evaluate("() => { return { url: document.location.href, title: document.title } }") 70 | filename_to_save = f"{page_metadata['title']}.pdf" 71 | save_path = os.path.join(pdfs_save_path, filename_to_save) 72 | 73 | self.page.pdf(path=save_path) 74 | 75 | return save_path 76 | 77 | def pdf_to_text(self, pdf_path): 78 | return extract_text(pdf_path).strip() 79 | 80 | def get_content(self): 81 | pdf_path = self.get_pdf() 82 | return self.pdf_to_text(pdf_path) 83 | 84 | def extract_text(self): 85 | return self.page.evaluate("() => document.body.innerText") 86 | 87 | async def close(self): 88 | await self.page.close() 89 | await self.browser.close() 90 | -------------------------------------------------------------------------------- /src/browser/search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from src.config import Config 3 | 4 | import re 5 | from urllib.parse import unquote 6 | from html import unescape 7 | import orjson 8 | 9 | 10 | class BingSearch: 11 | def __init__(self): 12 | self.config = Config() 13 | self.bing_api_key = self.config.get_bing_api_key() 14 | self.bing_api_endpoint = self.config.get_bing_api_endpoint() 15 | self.query_result = None 16 | 17 | def search(self, query): 18 | headers = {"Ocp-Apim-Subscription-Key": self.bing_api_key} 19 | params = {"q": query, "mkt": "en-US"} 20 | 21 | try: 22 | response = requests.get(self.bing_api_endpoint, headers=headers, params=params) 23 | response.raise_for_status() 24 | self.query_result = response.json() 25 | return self.query_result 26 | except Exception as error: 27 | return error 28 | 29 | def get_first_link(self): 30 | return self.query_result["webPages"]["value"][0]["url"] 31 | 32 | 33 | class GoogleSearch: 34 | def __init__(self): 35 | self.config = Config() 36 | self.google_search_api_key = self.config.get_google_search_api_key() 37 | self.google_search_engine_ID = self.config.get_google_search_engine_id() 38 | self.google_search_api_endpoint = self.config.get_google_search_api_endpoint() 39 | self.query_result = None 40 | 41 | def search(self, query): 42 | params = { 43 | "key": self.google_search_api_key, 44 | "cx": self.google_search_engine_ID, 45 | "q": query 46 | } 47 | try: 48 | print("Searching in Google...") 49 | response = requests.get(self.google_search_api_endpoint, params=params) 50 | # response.raise_for_status() 51 | self.query_result = response.json() 52 | except Exception as error: 53 | return error 54 | 55 | def get_first_link(self): 56 | item = "" 57 | try: 58 | if 'items' in self.query_result: 59 | item = self.query_result['items'][0]['link'] 60 | return item 61 | except Exception as error: 62 | print(error) 63 | return "" 64 | 65 | # class DuckDuckGoSearch: 66 | # def __init__(self): 67 | # self.query_result = None 68 | # 69 | # def search(self, query): 70 | # from duckduckgo_search import DDGS 71 | # try: 72 | # self.query_result = DDGS().text(query, max_results=5, region="us") 73 | # print(self.query_result) 74 | # 75 | # except Exception as err: 76 | # print(err) 77 | # 78 | # def get_first_link(self): 79 | # if self.query_result: 80 | # return self.query_result[0]["href"] 81 | # else: 82 | # return None 83 | # 84 | 85 | 86 | class DuckDuckGoSearch: 87 | """DuckDuckGo search engine class. 88 | methods are inherited from the duckduckgo_search package. 89 | do not change the methods. 90 | 91 | currently, the package is not working with our current setup. 92 | """ 93 | def __init__(self): 94 | from curl_cffi import requests as curl_requests 95 | self.query_result = None 96 | self.asession = curl_requests.Session(impersonate="chrome", allow_redirects=False) 97 | self.asession.headers["Referer"] = "https://duckduckgo.com/" 98 | 99 | def _get_url(self, method, url, data): 100 | try: 101 | resp = self.asession.request(method, url, data=data) 102 | if resp.status_code == 200: 103 | return resp.content 104 | if resp.status_code == (202, 301, 403): 105 | raise Exception(f"Error: {resp.status_code} rate limit error") 106 | if not resp: 107 | return None 108 | except Exception as error: 109 | if "timeout" in str(error).lower(): 110 | raise TimeoutError("Duckduckgo timed out error") 111 | 112 | def duck(self, query): 113 | resp = self._get_url("POST", "https://duckduckgo.com/", data={"q": query}) 114 | vqd = self.extract_vqd(resp) 115 | 116 | params = {"q": query, "kl": 'en-us', "p": "1", "s": "0", "df": "", "vqd": vqd, "ex": ""} 117 | resp = self._get_url("GET", "https://links.duckduckgo.com/d.js", params) 118 | page_data = self.text_extract_json(resp) 119 | 120 | results = [] 121 | for row in page_data: 122 | href = row.get("u") 123 | if href and href != f"http://www.google.com/search?q={query}": 124 | body = self.normalize(row["a"]) 125 | if body: 126 | result = { 127 | "title": self.normalize(row["t"]), 128 | "href": self.normalize_url(href), 129 | "body": self.normalize(row["a"]), 130 | } 131 | results.append(result) 132 | 133 | self.query_result = results 134 | 135 | def search(self, query): 136 | self.duck(query) 137 | 138 | def get_first_link(self): 139 | return self.query_result[0]["href"] 140 | 141 | @staticmethod 142 | def extract_vqd(html_bytes: bytes) -> str: 143 | patterns = [(b'vqd="', 5, b'"'), (b"vqd=", 4, b"&"), (b"vqd='", 5, b"'")] 144 | for start_pattern, offset, end_pattern in patterns: 145 | try: 146 | start = html_bytes.index(start_pattern) + offset 147 | end = html_bytes.index(end_pattern, start) 148 | return html_bytes[start:end].decode() 149 | except ValueError: 150 | continue 151 | 152 | @staticmethod 153 | def text_extract_json(html_bytes): 154 | try: 155 | start = html_bytes.index(b"DDG.pageLayout.load('d',") + 24 156 | end = html_bytes.index(b");DDG.duckbar.load(", start) 157 | return orjson.loads(html_bytes[start:end]) 158 | except Exception as ex: 159 | print(f"Error extracting JSON: {type(ex).__name__}: {ex}") 160 | 161 | @staticmethod 162 | def normalize_url(url: str) -> str: 163 | return unquote(url.replace(" ", "+")) if url else "" 164 | 165 | @staticmethod 166 | def normalize(raw_html: str) -> str: 167 | return unescape(re.sub("<.*?>", "", raw_html)) if raw_html else "" 168 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | import toml 2 | import os 3 | 4 | 5 | class Config: 6 | _instance = None 7 | 8 | def __new__(cls): 9 | if cls._instance is None: 10 | cls._instance = super().__new__(cls) 11 | cls._instance._load_config() 12 | return cls._instance 13 | 14 | def _load_config(self): 15 | # If the config file doesn't exist, copy from the sample 16 | if not os.path.exists("config.toml"): 17 | with open("sample.config.toml", "r") as f_in, open("config.toml", "w+") as f_out: 18 | f_out.write(f_in.read()) 19 | f_out.seek(0) 20 | self.config = toml.load(f_out) 21 | else: 22 | # check if all the keys are present in the config file 23 | with open("sample.config.toml", "r") as f: 24 | sample_config = toml.load(f) 25 | 26 | with open("config.toml", "r+") as f: 27 | config = toml.load(f) 28 | 29 | # Update the config with any missing keys and their keys of keys 30 | for key, value in sample_config.items(): 31 | config.setdefault(key, value) 32 | if isinstance(value, dict): 33 | for sub_key, sub_value in value.items(): 34 | config[key].setdefault(sub_key, sub_value) 35 | 36 | f.seek(0) 37 | toml.dump(config, f) 38 | f.truncate() 39 | 40 | self.config = config 41 | 42 | def get_config(self): 43 | return self.config 44 | 45 | def get_bing_api_endpoint(self): 46 | return self.config["API_ENDPOINTS"]["BING"] 47 | 48 | def get_bing_api_key(self): 49 | return self.config["API_KEYS"]["BING"] 50 | 51 | def get_google_search_api_key(self): 52 | return self.config["API_KEYS"]["GOOGLE_SEARCH"] 53 | 54 | def get_google_search_engine_id(self): 55 | return self.config["API_KEYS"]["GOOGLE_SEARCH_ENGINE_ID"] 56 | 57 | def get_google_search_api_endpoint(self): 58 | return self.config["API_ENDPOINTS"]["GOOGLE"] 59 | 60 | def get_ollama_api_endpoint(self): 61 | return self.config["API_ENDPOINTS"]["OLLAMA"] 62 | 63 | def get_lmstudio_api_endpoint(self): 64 | return self.config["API_ENDPOINTS"]["LM_STUDIO"] 65 | 66 | def get_claude_api_key(self): 67 | return self.config["API_KEYS"]["CLAUDE"] 68 | 69 | def get_openai_api_key(self): 70 | return self.config["API_KEYS"]["OPENAI"] 71 | 72 | def get_openai_api_base_url(self): 73 | return self.config["API_ENDPOINTS"]["OPENAI"] 74 | 75 | def get_gemini_api_key(self): 76 | return self.config["API_KEYS"]["GEMINI"] 77 | 78 | def get_mistral_api_key(self): 79 | return self.config["API_KEYS"]["MISTRAL"] 80 | 81 | def get_groq_api_key(self): 82 | return self.config["API_KEYS"]["GROQ"] 83 | 84 | def get_netlify_api_key(self): 85 | return self.config["API_KEYS"]["NETLIFY"] 86 | 87 | def get_sqlite_db(self): 88 | return self.config["STORAGE"]["SQLITE_DB"] 89 | 90 | def get_screenshots_dir(self): 91 | return self.config["STORAGE"]["SCREENSHOTS_DIR"] 92 | 93 | def get_pdfs_dir(self): 94 | return self.config["STORAGE"]["PDFS_DIR"] 95 | 96 | def get_projects_dir(self): 97 | return self.config["STORAGE"]["PROJECTS_DIR"] 98 | 99 | def get_logs_dir(self): 100 | return self.config["STORAGE"]["LOGS_DIR"] 101 | 102 | def get_repos_dir(self): 103 | return self.config["STORAGE"]["REPOS_DIR"] 104 | 105 | def get_logging_rest_api(self): 106 | return self.config["LOGGING"]["LOG_REST_API"] == "true" 107 | 108 | def get_logging_prompts(self): 109 | return self.config["LOGGING"]["LOG_PROMPTS"] == "true" 110 | 111 | def get_timeout_inference(self): 112 | return self.config["TIMEOUT"]["INFERENCE"] 113 | 114 | def set_bing_api_key(self, key): 115 | self.config["API_KEYS"]["BING"] = key 116 | self.save_config() 117 | 118 | def set_bing_api_endpoint(self, endpoint): 119 | self.config["API_ENDPOINTS"]["BING"] = endpoint 120 | self.save_config() 121 | 122 | def set_google_search_api_key(self, key): 123 | self.config["API_KEYS"]["GOOGLE_SEARCH"] = key 124 | self.save_config() 125 | 126 | def set_google_search_engine_id(self, key): 127 | self.config["API_KEYS"]["GOOGLE_SEARCH_ENGINE_ID"] = key 128 | self.save_config() 129 | 130 | def set_google_search_api_endpoint(self, endpoint): 131 | self.config["API_ENDPOINTS"]["GOOGLE_SEARCH"] = endpoint 132 | self.save_config() 133 | 134 | def set_ollama_api_endpoint(self, endpoint): 135 | self.config["API_ENDPOINTS"]["OLLAMA"] = endpoint 136 | self.save_config() 137 | 138 | def set_lmstudio_api_endpoint(self, endpoint): 139 | self.config["API_ENDPOINTS"]["LM_STUDIO"] = endpoint 140 | self.save_config() 141 | 142 | def set_claude_api_key(self, key): 143 | self.config["API_KEYS"]["CLAUDE"] = key 144 | self.save_config() 145 | 146 | def set_openai_api_key(self, key): 147 | self.config["API_KEYS"]["OPENAI"] = key 148 | self.save_config() 149 | 150 | def set_openai_api_endpoint(self,endpoint): 151 | self.config["API_ENDPOINTS"]["OPENAI"] = endpoint 152 | self.save_config() 153 | 154 | def set_gemini_api_key(self, key): 155 | self.config["API_KEYS"]["GEMINI"] = key 156 | self.save_config() 157 | 158 | def set_mistral_api_key(self, key): 159 | self.config["API_KEYS"]["MISTRAL"] = key 160 | self.save_config() 161 | 162 | def set_groq_api_key(self, key): 163 | self.config["API_KEYS"]["GROQ"] = key 164 | self.save_config() 165 | 166 | def set_netlify_api_key(self, key): 167 | self.config["API_KEYS"]["NETLIFY"] = key 168 | self.save_config() 169 | 170 | def set_logging_rest_api(self, value): 171 | self.config["LOGGING"]["LOG_REST_API"] = "true" if value else "false" 172 | self.save_config() 173 | 174 | def set_logging_prompts(self, value): 175 | self.config["LOGGING"]["LOG_PROMPTS"] = "true" if value else "false" 176 | self.save_config() 177 | 178 | def set_timeout_inference(self, value): 179 | self.config["TIMEOUT"]["INFERENCE"] = value 180 | self.save_config() 181 | 182 | def save_config(self): 183 | with open("config.toml", "w") as f: 184 | toml.dump(self.config, f) 185 | 186 | def update_config(self, data): 187 | for key, value in data.items(): 188 | if key in self.config: 189 | with open("config.toml", "r+") as f: 190 | config = toml.load(f) 191 | for sub_key, sub_value in value.items(): 192 | self.config[key][sub_key] = sub_value 193 | config[key][sub_key] = sub_value 194 | f.seek(0) 195 | toml.dump(config, f) 196 | -------------------------------------------------------------------------------- /src/documenter/graphwiz.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/src/documenter/graphwiz.py -------------------------------------------------------------------------------- /src/documenter/pdf.py: -------------------------------------------------------------------------------- 1 | import os 2 | from io import BytesIO 3 | from markdown import markdown 4 | from xhtml2pdf import pisa 5 | 6 | from src.config import Config 7 | 8 | class PDF: 9 | def __init__(self): 10 | config = Config() 11 | self.pdf_path = config.get_pdfs_dir() 12 | 13 | def markdown_to_pdf(self, markdown_string, project_name): 14 | html_string = markdown(markdown_string) 15 | 16 | out_file_path = os.path.join(self.pdf_path, f"{project_name}.pdf") 17 | with open(out_file_path, "wb") as out_file: 18 | pisa_status = pisa.CreatePDF(html_string, dest=out_file) 19 | 20 | if pisa_status.err: 21 | raise Exception("Error generating PDF") 22 | 23 | return out_file_path -------------------------------------------------------------------------------- /src/documenter/uml.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/src/documenter/uml.py -------------------------------------------------------------------------------- /src/experts/__UNIMPLEMENTED__: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/src/experts/__UNIMPLEMENTED__ -------------------------------------------------------------------------------- /src/experts/chemistry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Function calls and Parser for: 3 | - SMILE Notation 4 | - Molecule Parser 5 | 6 | Visualization for: 7 | - Molecule Structure 8 | - Molecule Properties 9 | 10 | Use RDKit bindings 11 | """ -------------------------------------------------------------------------------- /src/experts/game-dev.py: -------------------------------------------------------------------------------- 1 | """ 2 | RAG for Unity/Godot/Unreal Engine code blocks 3 | """ -------------------------------------------------------------------------------- /src/experts/math.py: -------------------------------------------------------------------------------- 1 | """ 2 | Evaluator Function Calling 3 | Wolphram Alpha Plugin 4 | """ -------------------------------------------------------------------------------- /src/experts/medical.py: -------------------------------------------------------------------------------- 1 | """ 2 | PubMed archive RAG 3 | """ -------------------------------------------------------------------------------- /src/experts/physics.py: -------------------------------------------------------------------------------- 1 | """ 2 | Physics Function Calls 3 | """ -------------------------------------------------------------------------------- /src/experts/stackoverflow.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stack overflow query searcher and retrieval 3 | """ -------------------------------------------------------------------------------- /src/experts/web-design.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tailwind UI Components code snippets RAG 3 | """ -------------------------------------------------------------------------------- /src/filesystem/__init__.py: -------------------------------------------------------------------------------- 1 | from .read_code import ReadCode -------------------------------------------------------------------------------- /src/filesystem/read_code.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from src.config import Config 4 | 5 | """ 6 | TODO: Replace this with `code2prompt` - https://github.com/mufeedvh/code2prompt 7 | """ 8 | 9 | class ReadCode: 10 | def __init__(self, project_name: str): 11 | config = Config() 12 | project_path = config.get_projects_dir() 13 | self.directory_path = os.path.join(project_path, project_name.lower().replace(" ", "-")) 14 | 15 | def read_directory(self): 16 | files_list = [] 17 | for root, _dirs, files in os.walk(self.directory_path): 18 | for file in files: 19 | try: 20 | file_path = os.path.join(root, file) 21 | with open(file_path, 'r') as file_content: 22 | files_list.append({"filename": file_path, "code": file_content.read()}) 23 | except: 24 | pass 25 | 26 | return files_list 27 | 28 | def code_set_to_markdown(self): 29 | code_set = self.read_directory() 30 | markdown = "" 31 | for code in code_set: 32 | markdown += f"### {code['filename']}:\n\n" 33 | markdown += f"```\n{code['code']}\n```\n\n" 34 | markdown += "---\n\n" 35 | return markdown 36 | -------------------------------------------------------------------------------- /src/init.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.config import Config 3 | from src.logger import Logger 4 | 5 | 6 | def init_devika(): 7 | logger = Logger() 8 | 9 | logger.info("Initializing Devika...") 10 | logger.info("checking configurations...") 11 | 12 | config = Config() 13 | 14 | sqlite_db = config.get_sqlite_db() 15 | screenshots_dir = config.get_screenshots_dir() 16 | pdfs_dir = config.get_pdfs_dir() 17 | projects_dir = config.get_projects_dir() 18 | logs_dir = config.get_logs_dir() 19 | 20 | logger.info("Initializing Prerequisites Jobs...") 21 | os.makedirs(os.path.dirname(sqlite_db), exist_ok=True) 22 | os.makedirs(screenshots_dir, exist_ok=True) 23 | os.makedirs(pdfs_dir, exist_ok=True) 24 | os.makedirs(projects_dir, exist_ok=True) 25 | os.makedirs(logs_dir, exist_ok=True) 26 | 27 | from src.bert.sentence import SentenceBert 28 | 29 | logger.info("Loading sentence-transformer BERT models...") 30 | prompt = "Light-weight keyword extraction exercise for BERT model loading.".strip() 31 | SentenceBert(prompt).extract_keywords() 32 | logger.info("BERT model loaded successfully.") 33 | -------------------------------------------------------------------------------- /src/llm/__init__.py: -------------------------------------------------------------------------------- 1 | from .llm import LLM -------------------------------------------------------------------------------- /src/llm/claude_client.py: -------------------------------------------------------------------------------- 1 | from anthropic import Anthropic 2 | 3 | from src.config import Config 4 | 5 | class Claude: 6 | def __init__(self): 7 | config = Config() 8 | api_key = config.get_claude_api_key() 9 | self.client = Anthropic( 10 | api_key=api_key, 11 | ) 12 | 13 | def inference(self, model_id: str, prompt: str) -> str: 14 | message = self.client.messages.create( 15 | max_tokens=4096, 16 | messages=[ 17 | { 18 | "role": "user", 19 | "content": prompt.strip(), 20 | } 21 | ], 22 | model=model_id, 23 | temperature=0 24 | ) 25 | 26 | return message.content[0].text 27 | -------------------------------------------------------------------------------- /src/llm/gemini_client.py: -------------------------------------------------------------------------------- 1 | import google.generativeai as genai 2 | from google.generativeai.types import HarmCategory, HarmBlockThreshold 3 | 4 | from src.config import Config 5 | 6 | class Gemini: 7 | def __init__(self): 8 | config = Config() 9 | api_key = config.get_gemini_api_key() 10 | genai.configure(api_key=api_key) 11 | 12 | def inference(self, model_id: str, prompt: str) -> str: 13 | config = genai.GenerationConfig(temperature=0) 14 | model = genai.GenerativeModel(model_id, generation_config=config) 15 | # Set safety settings for the request 16 | safety_settings = { 17 | HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE, 18 | HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE, 19 | # You can adjust other categories as needed 20 | } 21 | response = model.generate_content(prompt, safety_settings=safety_settings) 22 | try: 23 | # Check if the response contains text 24 | return response.text 25 | except ValueError: 26 | # If the response doesn't contain text, check if the prompt was blocked 27 | print("Prompt feedback:", response.prompt_feedback) 28 | # Also check the finish reason to see if the response was blocked 29 | print("Finish reason:", response.candidates[0].finish_reason) 30 | # If the finish reason was SAFETY, the safety ratings have more details 31 | print("Safety ratings:", response.candidates[0].safety_ratings) 32 | # Handle the error or return an appropriate message 33 | return "Error: Unable to generate content Gemini API" 34 | -------------------------------------------------------------------------------- /src/llm/groq_client.py: -------------------------------------------------------------------------------- 1 | from groq import Groq as _Groq 2 | 3 | from src.config import Config 4 | 5 | 6 | class Groq: 7 | def __init__(self): 8 | config = Config() 9 | api_key = config.get_groq_api_key() 10 | self.client = _Groq(api_key=api_key) 11 | 12 | def inference(self, model_id: str, prompt: str) -> str: 13 | chat_completion = self.client.chat.completions.create( 14 | messages=[ 15 | { 16 | "role": "user", 17 | "content": prompt.strip(), 18 | } 19 | ], 20 | model=model_id, 21 | temperature=0 22 | ) 23 | 24 | return chat_completion.choices[0].message.content 25 | -------------------------------------------------------------------------------- /src/llm/llm.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import tiktoken 4 | from typing import List, Tuple 5 | 6 | from src.socket_instance import emit_agent 7 | from .ollama_client import Ollama 8 | from .claude_client import Claude 9 | from .openai_client import OpenAi 10 | from .gemini_client import Gemini 11 | from .mistral_client import MistralAi 12 | from .groq_client import Groq 13 | from .lm_studio_client import LMStudio 14 | 15 | from src.state import AgentState 16 | 17 | from src.config import Config 18 | from src.logger import Logger 19 | 20 | TIKTOKEN_ENC = tiktoken.get_encoding("cl100k_base") 21 | 22 | ollama = Ollama() 23 | logger = Logger() 24 | agentState = AgentState() 25 | config = Config() 26 | 27 | 28 | class LLM: 29 | def __init__(self, model_id: str = None): 30 | self.model_id = model_id 31 | self.log_prompts = config.get_logging_prompts() 32 | self.timeout_inference = config.get_timeout_inference() 33 | self.models = { 34 | "CLAUDE": [ 35 | ("Claude 3 Opus", "claude-3-opus-20240229"), 36 | ("Claude 3 Sonnet", "claude-3-sonnet-20240229"), 37 | ("Claude 3 Haiku", "claude-3-haiku-20240307"), 38 | ], 39 | "OPENAI": [ 40 | ("GPT-4o-mini", "gpt-4o-mini"), 41 | ("GPT-4o", "gpt-4o"), 42 | ("GPT-4 Turbo", "gpt-4-turbo"), 43 | ("GPT-3.5 Turbo", "gpt-3.5-turbo-0125"), 44 | ], 45 | "GOOGLE": [ 46 | ("Gemini 1.0 Pro", "gemini-pro"), 47 | ("Gemini 1.5 Flash", "gemini-1.5-flash"), 48 | ("Gemini 1.5 Pro", "gemini-1.5-pro"), 49 | ], 50 | "MISTRAL": [ 51 | ("Mistral 7b", "open-mistral-7b"), 52 | ("Mistral 8x7b", "open-mixtral-8x7b"), 53 | ("Mistral Medium", "mistral-medium-latest"), 54 | ("Mistral Small", "mistral-small-latest"), 55 | ("Mistral Large", "mistral-large-latest"), 56 | ], 57 | "GROQ": [ 58 | ("LLAMA3 8B", "llama3-8b-8192"), 59 | ("LLAMA3 70B", "llama3-70b-8192"), 60 | ("LLAMA2 70B", "llama2-70b-4096"), 61 | ("Mixtral", "mixtral-8x7b-32768"), 62 | ("GEMMA 7B", "gemma-7b-it"), 63 | ], 64 | "OLLAMA": [], 65 | "LM_STUDIO": [ 66 | ("LM Studio", "local-model"), 67 | ], 68 | 69 | } 70 | if ollama.client: 71 | self.models["OLLAMA"] = [(model["name"], model["name"]) for model in ollama.models] 72 | 73 | def list_models(self) -> dict: 74 | return self.models 75 | 76 | def model_enum(self, model_name: str) -> Tuple[str, str]: 77 | model_dict = { 78 | model[0]: (model_enum, model[1]) 79 | for model_enum, models in self.models.items() 80 | for model in models 81 | } 82 | return model_dict.get(model_name, (None, None)) 83 | 84 | @staticmethod 85 | def update_global_token_usage(string: str, project_name: str): 86 | token_usage = len(TIKTOKEN_ENC.encode(string)) 87 | agentState.update_token_usage(project_name, token_usage) 88 | 89 | total = agentState.get_latest_token_usage(project_name) + token_usage 90 | emit_agent("tokens", {"token_usage": total}) 91 | 92 | def inference(self, prompt: str, project_name: str) -> str: 93 | self.update_global_token_usage(prompt, project_name) 94 | 95 | model_enum, model_name = self.model_enum(self.model_id) 96 | 97 | print(f"Model: {self.model_id}, Enum: {model_enum}") 98 | if model_enum is None: 99 | raise ValueError(f"Model {self.model_id} not supported") 100 | 101 | model_mapping = { 102 | "OLLAMA": ollama, 103 | "CLAUDE": Claude(), 104 | "OPENAI": OpenAi(), 105 | "GOOGLE": Gemini(), 106 | "MISTRAL": MistralAi(), 107 | "GROQ": Groq(), 108 | "LM_STUDIO": LMStudio() 109 | } 110 | 111 | try: 112 | import concurrent.futures 113 | import time 114 | 115 | start_time = time.time() 116 | model = model_mapping[model_enum] 117 | 118 | with concurrent.futures.ThreadPoolExecutor() as executor: 119 | future = executor.submit(model.inference, model_name, prompt) 120 | try: 121 | while True: 122 | elapsed_time = time.time() - start_time 123 | elapsed_seconds = format(elapsed_time, ".2f") 124 | emit_agent("inference", {"type": "time", "elapsed_time": elapsed_seconds}) 125 | if int(elapsed_time) == 5: 126 | emit_agent("inference", {"type": "warning", "message": "Inference is taking longer than expected"}) 127 | if elapsed_time > self.timeout_inference: 128 | raise concurrent.futures.TimeoutError 129 | if future.done(): 130 | break 131 | time.sleep(0.5) 132 | 133 | response = future.result(timeout=self.timeout_inference).strip() 134 | 135 | except concurrent.futures.TimeoutError: 136 | logger.error(f"Inference failed. took too long. Model: {model_enum}, Model ID: {self.model_id}") 137 | emit_agent("inference", {"type": "error", "message": "Inference took too long. Please try again."}) 138 | response = False 139 | sys.exit() 140 | 141 | except Exception as e: 142 | logger.error(str(e)) 143 | response = False 144 | emit_agent("inference", {"type": "error", "message": str(e)}) 145 | sys.exit() 146 | 147 | 148 | except KeyError: 149 | raise ValueError(f"Model {model_enum} not supported") 150 | 151 | if self.log_prompts: 152 | logger.debug(f"Response ({model}): --> {response}") 153 | 154 | self.update_global_token_usage(response, project_name) 155 | 156 | return response 157 | -------------------------------------------------------------------------------- /src/llm/lm_studio_client.py: -------------------------------------------------------------------------------- 1 | from src.logger import Logger 2 | from src.config import Config 3 | from openai import OpenAI 4 | 5 | 6 | log = Logger() 7 | 8 | class LMStudio: 9 | def __init__(self): 10 | try: 11 | self.api_endpoint = Config().get_lmstudio_api_endpoint() 12 | self.client = OpenAI(base_url=self.api_endpoint, api_key="not-needed") 13 | log.info("LM Studio available") 14 | except: 15 | self.api_endpoint = None 16 | self.client = None 17 | log.warning("LM Studio not available") 18 | log.warning("Make sure to set the LM Studio API endpoint in the config") 19 | 20 | def inference(self, model_id: str, prompt: str) -> str: 21 | chat_completion = self.client.chat.completions.create( 22 | messages=[ 23 | { 24 | "role": "user", 25 | "content": prompt.strip(), 26 | } 27 | ], 28 | model=model_id, # unused 29 | ) 30 | return chat_completion.choices[0].message.content 31 | -------------------------------------------------------------------------------- /src/llm/mistral_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from mistralai import Mistral, UserMessage # Updated import from mistralai 3 | 4 | from src.config import Config 5 | 6 | 7 | class MistralAi: 8 | def __init__(self): 9 | config = Config() 10 | api_key = config.get_mistral_api_key() # Retrieve API key using the existing Config class 11 | self.client = Mistral(api_key=api_key) # Initialize Mistral client with the new class 12 | 13 | def inference(self, model_id: str, prompt: str) -> str: 14 | print("prompt", prompt.strip()) 15 | # Use the new method for chat completion 16 | chat_response = self.client.chat.complete( 17 | model=model_id, # Model ID remains the same 18 | messages=[ # Update to use dictionary format for messages 19 | { 20 | "role": "user", 21 | "content": prompt.strip() 22 | } 23 | ], 24 | ) 25 | # Access the response using the new structure 26 | return chat_response.choices[0].message.content # Extract content from the response 27 | -------------------------------------------------------------------------------- /src/llm/ollama_client.py: -------------------------------------------------------------------------------- 1 | import ollama 2 | from src.logger import Logger 3 | from src.config import Config 4 | 5 | log = Logger() 6 | 7 | 8 | class Ollama: 9 | def __init__(self): 10 | try: 11 | self.client = ollama.Client(Config().get_ollama_api_endpoint()) 12 | self.models = self.client.list()["models"] 13 | log.info("Ollama available") 14 | except: 15 | self.client = None 16 | log.warning("Ollama not available") 17 | log.warning("run ollama server to use ollama models otherwise use API models") 18 | 19 | def inference(self, model_id: str, prompt: str) -> str: 20 | response = self.client.generate( 21 | model=model_id, 22 | prompt=prompt.strip(), 23 | options={"temperature": 0} 24 | ) 25 | return response['response'] 26 | -------------------------------------------------------------------------------- /src/llm/openai_client.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | 3 | from src.config import Config 4 | 5 | 6 | class OpenAi: 7 | def __init__(self): 8 | config = Config() 9 | api_key = config.get_openai_api_key() 10 | base_url = config.get_openai_api_base_url() 11 | self.client = OpenAI(api_key=api_key, base_url=base_url) 12 | 13 | def inference(self, model_id: str, prompt: str) -> str: 14 | chat_completion = self.client.chat.completions.create( 15 | messages=[ 16 | { 17 | "role": "user", 18 | "content": prompt.strip(), 19 | } 20 | ], 21 | model=model_id, 22 | temperature=0 23 | ) 24 | return chat_completion.choices[0].message.content 25 | -------------------------------------------------------------------------------- /src/logger.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from fastlogging import LogInit 4 | from flask import request 5 | 6 | from src.config import Config 7 | 8 | 9 | class Logger: 10 | def __init__(self, filename="devika_agent.log"): 11 | config = Config() 12 | logs_dir = config.get_logs_dir() 13 | self.logger = LogInit(pathName=logs_dir + "/" + filename, console=True, colors=True, encoding="utf-8") 14 | 15 | def read_log_file(self) -> str: 16 | with open(self.logger.pathName, "r") as file: 17 | return file.read() 18 | 19 | def info(self, message: str): 20 | self.logger.info(message) 21 | self.logger.flush() 22 | 23 | def error(self, message: str): 24 | self.logger.error(message) 25 | self.logger.flush() 26 | 27 | def warning(self, message: str): 28 | self.logger.warning(message) 29 | self.logger.flush() 30 | 31 | def debug(self, message: str): 32 | self.logger.debug(message) 33 | self.logger.flush() 34 | 35 | def exception(self, message: str): 36 | self.logger.exception(message) 37 | self.logger.flush() 38 | 39 | 40 | def route_logger(logger: Logger): 41 | """ 42 | Decorator factory that creates a decorator to log route entry and exit points. 43 | The decorator uses the provided logger to log the information. 44 | 45 | :param logger: The logger instance to use for logging. 46 | """ 47 | 48 | log_enabled = Config().get_logging_rest_api() 49 | 50 | def decorator(func): 51 | 52 | @wraps(func) 53 | def wrapper(*args, **kwargs): 54 | # Log entry point 55 | if log_enabled: 56 | logger.info(f"{request.path} {request.method}") 57 | 58 | # Call the actual route function 59 | response = func(*args, **kwargs) 60 | 61 | from werkzeug.wrappers import Response 62 | 63 | # Log exit point, including response summary if possible 64 | try: 65 | if log_enabled: 66 | if isinstance(response, Response) and response.direct_passthrough: 67 | logger.debug(f"{request.path} {request.method} - Response: File response") 68 | else: 69 | response_summary = response.get_data(as_text=True) 70 | if 'settings' in request.path: 71 | response_summary = "*** Settings are not logged ***" 72 | logger.debug(f"{request.path} {request.method} - Response: {response_summary}") 73 | except Exception as e: 74 | logger.exception(f"{request.path} {request.method} - {e})") 75 | 76 | return response 77 | return wrapper 78 | return decorator 79 | -------------------------------------------------------------------------------- /src/memory/__init__.py: -------------------------------------------------------------------------------- 1 | from .knowledge_base import KnowledgeBase -------------------------------------------------------------------------------- /src/memory/knowledge_base.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from sqlmodel import Field, Session, SQLModel, create_engine 3 | 4 | from src.config import Config 5 | 6 | """ 7 | TODO: The tag check should be a BM25 search, it's just a simple equality check now. 8 | """ 9 | 10 | class Knowledge(SQLModel, table=True): 11 | id: Optional[int] = Field(default=None, primary_key=True) 12 | tag: str 13 | contents: str 14 | 15 | class KnowledgeBase: 16 | def __init__(self): 17 | config = Config() 18 | sqlite_path = config.get_sqlite_db() 19 | self.engine = create_engine(f"sqlite:///{sqlite_path}") 20 | SQLModel.metadata.create_all(self.engine) 21 | 22 | def add_knowledge(self, tag: str, contents: str): 23 | knowledge = Knowledge(tag=tag, contents=contents) 24 | with Session(self.engine) as session: 25 | session.add(knowledge) 26 | session.commit() 27 | 28 | def get_knowledge(self, tag: str) -> str: 29 | with Session(self.engine) as session: 30 | knowledge = session.query(Knowledge).filter(Knowledge.tag == tag).first() 31 | if knowledge: 32 | return knowledge.contents 33 | return None -------------------------------------------------------------------------------- /src/memory/rag.py: -------------------------------------------------------------------------------- 1 | """ 2 | Vector Search for Code Docs + Docs Loading 3 | """ -------------------------------------------------------------------------------- /src/sandbox/code_runner.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/src/sandbox/code_runner.py -------------------------------------------------------------------------------- /src/sandbox/firejail.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/src/sandbox/firejail.py -------------------------------------------------------------------------------- /src/services/__init__.py: -------------------------------------------------------------------------------- 1 | from .git import Git 2 | from .github import GitHub 3 | from .netlify import Netlify -------------------------------------------------------------------------------- /src/services/git.py: -------------------------------------------------------------------------------- 1 | import git as GitPython 2 | 3 | class Git: 4 | def __init__(self, path): 5 | self.repo = GitPython.Repo(path) 6 | 7 | def clone(self, url, path): 8 | return GitPython.Repo.clone_from(url, path) 9 | 10 | def get_branches(self): 11 | return self.repo.branches 12 | 13 | def get_commits(self, branch): 14 | return self.repo.iter_commits(branch) 15 | 16 | def get_commit(self, commit): 17 | return self.repo.commit(commit) 18 | 19 | def get_file(self, commit, file): 20 | return self.repo.git.show(f'{commit}:{file}') -------------------------------------------------------------------------------- /src/services/github.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from typing import List 4 | 5 | class GitHub: 6 | def __init__(self, token: str) -> None: 7 | self.token = token 8 | 9 | def get_repositories(self) -> List[str]: 10 | headers = {"Authorization": f"token {self.token}"} 11 | response = requests.get( 12 | "https://api.github.com/user/repos", headers=headers 13 | ) 14 | response.raise_for_status() 15 | return [repo["full_name"] for repo in response.json()] -------------------------------------------------------------------------------- /src/services/netlify.py: -------------------------------------------------------------------------------- 1 | from netlify_py import NetlifyPy 2 | 3 | from src.config import Config 4 | from src.project import ProjectManager 5 | 6 | class Netlify: 7 | def __init__(self): 8 | config = Config() 9 | api_key = config.get_netlify_api_key() 10 | self.netlify = NetlifyPy(access_token=api_key) 11 | 12 | def deploy(self, project_name: str): 13 | project_path = ProjectManager().get_project_path(project_name) 14 | 15 | site = self.netlify.site.create_site() 16 | 17 | print("===" * 10) 18 | print(site) 19 | 20 | site_id = site["id"] 21 | 22 | deploy = self.netlify.deploys.deploy_site(site_id, project_path) 23 | 24 | print("===" * 10) 25 | print(deploy) 26 | 27 | return deploy 28 | 29 | -------------------------------------------------------------------------------- /src/services/utils.py: -------------------------------------------------------------------------------- 1 | # create wrapper function that will has retry logic of 5 times 2 | import sys 3 | import time 4 | from functools import wraps 5 | import json 6 | 7 | from src.socket_instance import emit_agent 8 | 9 | def retry_wrapper(func): 10 | def wrapper(*args, **kwargs): 11 | max_tries = 5 12 | tries = 0 13 | while tries < max_tries: 14 | result = func(*args, **kwargs) 15 | if result: 16 | return result 17 | print("Invalid response from the model, I'm trying again...") 18 | emit_agent("info", {"type": "warning", "message": "Invalid response from the model, trying again..."}) 19 | tries += 1 20 | time.sleep(2) 21 | print("Maximum 5 attempts reached. try other models") 22 | emit_agent("info", {"type": "error", "message": "Maximum attempts reached. model keeps failing."}) 23 | sys.exit(1) 24 | 25 | return False 26 | return wrapper 27 | 28 | 29 | class InvalidResponseError(Exception): 30 | pass 31 | 32 | def validate_responses(func): 33 | @wraps(func) 34 | def wrapper(*args, **kwargs): 35 | args = list(args) 36 | response = args[1] 37 | response = response.strip() 38 | 39 | try: 40 | response = json.loads(response) 41 | print("first", type(response)) 42 | args[1] = response 43 | return func(*args, **kwargs) 44 | 45 | except json.JSONDecodeError: 46 | pass 47 | 48 | try: 49 | response = response.split("```")[1] 50 | if response: 51 | response = json.loads(response.strip()) 52 | print("second", type(response)) 53 | args[1] = response 54 | return func(*args, **kwargs) 55 | 56 | except (IndexError, json.JSONDecodeError): 57 | pass 58 | 59 | try: 60 | start_index = response.find('{') 61 | end_index = response.rfind('}') 62 | if start_index != -1 and end_index != -1: 63 | json_str = response[start_index:end_index+1] 64 | try: 65 | response = json.loads(json_str) 66 | print("third", type(response)) 67 | args[1] = response 68 | return func(*args, **kwargs) 69 | 70 | except json.JSONDecodeError: 71 | pass 72 | except json.JSONDecodeError: 73 | pass 74 | 75 | for line in response.splitlines(): 76 | try: 77 | response = json.loads(line) 78 | print("fourth", type(response)) 79 | args[1] = response 80 | return func(*args, **kwargs) 81 | 82 | except json.JSONDecodeError: 83 | pass 84 | 85 | # If all else fails, raise an exception 86 | emit_agent("info", {"type": "error", "message": "Failed to parse response as JSON"}) 87 | # raise InvalidResponseError("Failed to parse response as JSON") 88 | return False 89 | 90 | return wrapper -------------------------------------------------------------------------------- /src/socket_instance.py: -------------------------------------------------------------------------------- 1 | # socketio_instance.py 2 | from flask_socketio import SocketIO 3 | from src.logger import Logger 4 | socketio = SocketIO(cors_allowed_origins="*", async_mode="gevent") 5 | 6 | logger = Logger() 7 | 8 | 9 | def emit_agent(channel, content, log=True): 10 | try: 11 | socketio.emit(channel, content) 12 | if log: 13 | logger.info(f"SOCKET {channel} MESSAGE: {content}") 14 | return True 15 | except Exception as e: 16 | logger.error(f"SOCKET {channel} ERROR: {str(e)}") 17 | return False 18 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | pnpm-lock.yaml 12 | .lockb -------------------------------------------------------------------------------- /ui/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /ui/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/ui/bun.lockb -------------------------------------------------------------------------------- /ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.js", 6 | "css": "src/app.pcss", 7 | "baseColor": "zinc" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils" 12 | }, 13 | "typescript": false 14 | } -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "start": "vite build && vite preview", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@monaco-editor/loader": "^1.4.0", 13 | "@sveltejs/adapter-auto": "^3.0.0", 14 | "@sveltejs/kit": "^2.0.0", 15 | "@sveltejs/vite-plugin-svelte": "^3.0.2", 16 | "autoprefixer": "^10.4.16", 17 | "monaco-editor": "^0.48.0", 18 | "postcss": "^8.4.32", 19 | "postcss-load-config": "^5.0.2", 20 | "svelte": "^4.2.7", 21 | "tailwindcss": "^3.4.3", 22 | "vite": "^5.2.8", 23 | "vite-plugin-wasm": "^3.3.0" 24 | }, 25 | "type": "module", 26 | "dependencies": { 27 | "@xterm/addon-fit": "^0.10.0", 28 | "@xterm/xterm": "^5.5.0", 29 | "bits-ui": "^0.21.2", 30 | "clsx": "^2.1.0", 31 | "dompurify": "^3.1.5", 32 | "mode-watcher": "^0.3.0", 33 | "paneforge": "^0.0.3", 34 | "socket.io-client": "^4.7.5", 35 | "svelte-sonner": "^0.3.21", 36 | "tailwind-merge": "^2.2.2", 37 | "tailwind-variants": "^0.2.1", 38 | "tiktoken": "^1.0.13" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ui/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require("tailwindcss"); 2 | const autoprefixer = require("autoprefixer"); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer, 10 | ], 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /ui/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %sveltekit.head% 9 | Devika AI 10 | 11 | 12 | 13 |
%sveltekit.body%
14 | 15 | 16 | -------------------------------------------------------------------------------- /ui/src/app.pcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --primary: #2F3337; 8 | --background: #ffffff; 9 | --secondary: #f1f2f5; 10 | --tertiary: #919AA3; 11 | 12 | --foreground: #303438; 13 | --foreground-invert: #ffffff; 14 | --foreground-light: #9CA3AB; 15 | --foreground-secondary: #9CA3AB; 16 | 17 | --btn: #2F3337; 18 | --btn-active: #000000; 19 | 20 | --border: #E4E3E8; 21 | --seperator: #E4E3E8; 22 | 23 | --window-outline: #E4E3E8; 24 | 25 | --browser-window-dots: #E4E3E8; 26 | --browser-window-search: #F7F8FA; 27 | --browser-window-ribbon: #ffffff; 28 | --browser-window-foreground: #303438; 29 | --browser-window-background: #F7F8FA; 30 | 31 | --terminal-window-dots: #E4E3E8; 32 | --terminal-window-ribbon: #ffffff; 33 | --terminal-window-background: #F7F8FA; 34 | --terminal-window-foreground: #313336; 35 | 36 | --slider-empty: #9CA3AB; 37 | --slider-filled: #2F3337; 38 | --slider-thumb: #303438; 39 | 40 | --monologue-background: #F1F3F5; 41 | --monologue-outline: #B2BAC2; 42 | } 43 | .dark{ 44 | --primary: #ECECEC; 45 | --background: #1D1F21; 46 | --secondary: #2F3337; 47 | --tertiary: #81878C; 48 | 49 | --foreground: #dcdcdc; 50 | --foreground-invert: #1D1F21; 51 | --foreground-light: #E6E9EB; 52 | --foreground-secondary: #9CA3AB; 53 | 54 | --btn: #ECECEC; 55 | --btn-active: #ffffff; 56 | 57 | --border: #2B2F34; 58 | --seperator: #495058; 59 | 60 | --window-outline: #4E555D; 61 | 62 | --browser-window-dots: #191C1E; 63 | --browser-window-search: #1D2124; 64 | --browser-window-ribbon: #292E32; 65 | --browser-window-foreground: #DDDFE1; 66 | --browser-window-background: #111315; 67 | 68 | --terminal-window-dots: #191C1E; 69 | --terminal-window-ribbon: #292E32; 70 | --terminal-window-background: #111315; 71 | --terminal-window-foreground: #9CA3AB; 72 | 73 | --slider-empty: #2F3337; 74 | --slider-filled: #81878C; 75 | --slider-thumb: #ffffff; 76 | 77 | --monologue-background: #242729; 78 | --monologue-outline: #464C51; 79 | } 80 | } 81 | 82 | @layer base { 83 | * { 84 | @apply border-border; 85 | } 86 | body { 87 | @apply bg-background text-foreground; 88 | } 89 | 90 | /* Styling for scrollbar */ 91 | 92 | /* WebKit (Chrome, Safari) */ 93 | *::-webkit-scrollbar { 94 | width: 5px; 95 | height: 2px 96 | } 97 | *::-webkit-scrollbar-thumb { 98 | background: #999797; 99 | border-radius: 0.5rem; 100 | } 101 | *::-webkit-scrollbar-thumb:hover { 102 | background: #6b7280; 103 | } 104 | 105 | /* firefox */ 106 | @-moz-document url-prefix() { 107 | :global(*) { 108 | scrollbar-width: thin; 109 | scrollbar-color: #999797 #FFFFFF; 110 | } 111 | 112 | :global(*::-moz-scrollbar) { 113 | width: 5px; 114 | } 115 | 116 | :global(*::-moz-scrollbar-thumb) { 117 | background: #999797; 118 | border-radius: 0.5rem; 119 | } 120 | 121 | :global(*::-moz-scrollbar-thumb:hover) { 122 | background: #6b7280; 123 | } 124 | } 125 | 126 | /* Internet Explorer/Edge */ 127 | :global(*::-ms-scrollbar) { 128 | width: 5px; 129 | } 130 | :global(*::-ms-scrollbar-thumb) { 131 | background: #999797; 132 | border-radius: 0.5rem; 133 | } 134 | :global(*::-ms-scrollbar-thumb:hover) { 135 | background: #6b7280; 136 | } 137 | } -------------------------------------------------------------------------------- /ui/src/lib/api.js: -------------------------------------------------------------------------------- 1 | import { 2 | agentState, 3 | internet, 4 | modelList, 5 | projectList, 6 | messages, 7 | projectFiles, 8 | searchEngineList, 9 | } from "./store"; 10 | import { io } from "socket.io-client"; 11 | 12 | 13 | const getApiBaseUrl = () => { 14 | if (typeof window !== 'undefined') { 15 | const host = window.location.hostname; 16 | if (host === 'localhost' || host === '127.0.0.1') { 17 | return 'http://127.0.0.1:1337'; 18 | } else { 19 | return `http://${host}:1337`; 20 | } 21 | } else { 22 | return 'http://127.0.0.1:1337'; 23 | } 24 | }; 25 | 26 | export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || getApiBaseUrl(); 27 | export const socket = io(API_BASE_URL, { autoConnect: false }); 28 | 29 | export async function checkServerStatus() { 30 | try{await fetch(`${API_BASE_URL}/api/status`) ; return true;} 31 | catch (error) { 32 | return false; 33 | } 34 | 35 | } 36 | 37 | export async function fetchInitialData() { 38 | const response = await fetch(`${API_BASE_URL}/api/data`); 39 | const data = await response.json(); 40 | projectList.set(data.projects); 41 | modelList.set(data.models); 42 | searchEngineList.set(data.search_engines); 43 | localStorage.setItem("defaultData", JSON.stringify(data)); 44 | } 45 | 46 | export async function createProject(projectName) { 47 | await fetch(`${API_BASE_URL}/api/create-project`, { 48 | method: "POST", 49 | headers: { 50 | "Content-Type": "application/json", 51 | }, 52 | body: JSON.stringify({ project_name: projectName }), 53 | }); 54 | projectList.update((projects) => [...projects, projectName]); 55 | } 56 | 57 | export async function deleteProject(projectName) { 58 | await fetch(`${API_BASE_URL}/api/delete-project`, { 59 | method: "POST", 60 | headers: { 61 | "Content-Type": "application/json", 62 | }, 63 | body: JSON.stringify({ project_name: projectName }), 64 | }); 65 | } 66 | 67 | export async function fetchMessages() { 68 | const projectName = localStorage.getItem("selectedProject"); 69 | const response = await fetch(`${API_BASE_URL}/api/messages`, { 70 | method: "POST", 71 | headers: { 72 | "Content-Type": "application/json", 73 | }, 74 | body: JSON.stringify({ project_name: projectName }), 75 | }); 76 | const data = await response.json(); 77 | messages.set(data.messages); 78 | } 79 | 80 | export async function fetchAgentState() { 81 | const projectName = localStorage.getItem("selectedProject"); 82 | const response = await fetch(`${API_BASE_URL}/api/get-agent-state`, { 83 | method: "POST", 84 | headers: { 85 | "Content-Type": "application/json", 86 | }, 87 | body: JSON.stringify({ project_name: projectName }), 88 | }); 89 | const data = await response.json(); 90 | agentState.set(data.state); 91 | } 92 | 93 | export async function executeAgent(prompt) { 94 | const projectName = localStorage.getItem("selectedProject"); 95 | const modelId = localStorage.getItem("selectedModel"); 96 | 97 | if (!modelId) { 98 | alert("Please select the LLM model first."); 99 | return; 100 | } 101 | 102 | await fetch(`${API_BASE_URL}/api/execute-agent`, { 103 | method: "POST", 104 | headers: { 105 | "Content-Type": "application/json", 106 | }, 107 | body: JSON.stringify({ 108 | prompt: prompt, 109 | base_model: modelId, 110 | project_name: projectName, 111 | }), 112 | }); 113 | 114 | await fetchMessages(); 115 | } 116 | 117 | export async function getBrowserSnapshot(snapshotPath) { 118 | const response = await fetch(`${API_BASE_URL}/api/browser-snapshot`, { 119 | method: "POST", 120 | headers: { 121 | "Content-Type": "application/json", 122 | }, 123 | body: JSON.stringify({ snapshot_path: snapshotPath }), 124 | }); 125 | const data = await response.json(); 126 | return data.snapshot; 127 | } 128 | 129 | export async function fetchProjectFiles() { 130 | const projectName = localStorage.getItem("selectedProject"); 131 | const response = await fetch(`${API_BASE_URL}/api/get-project-files?project_name=${projectName}`) 132 | const data = await response.json(); 133 | projectFiles.set(data.files); 134 | return data.files; 135 | } 136 | 137 | export async function checkInternetStatus() { 138 | if (navigator.onLine) { 139 | internet.set(true); 140 | } else { 141 | internet.set(false); 142 | } 143 | } 144 | 145 | export async function fetchSettings() { 146 | const response = await fetch(`${API_BASE_URL}/api/settings`); 147 | const data = await response.json(); 148 | return data.settings; 149 | } 150 | 151 | export async function updateSettings(settings) { 152 | await fetch(`${API_BASE_URL}/api/settings`, { 153 | method: "POST", 154 | headers: { 155 | "Content-Type": "application/json", 156 | }, 157 | body: JSON.stringify(settings), 158 | }); 159 | } 160 | 161 | export async function fetchLogs() { 162 | const response = await fetch(`${API_BASE_URL}/api/logs`); 163 | const data = await response.json(); 164 | return data.logs; 165 | } 166 | -------------------------------------------------------------------------------- /ui/src/lib/components/BrowserWidget.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 27 |
28 |
29 | {#if $agentState?.browser_session.screenshot} 30 | 35 | {:else} 36 |
💡 TIP: You can include a Git URL in your prompt to clone a repo!
37 | {/if} 38 |
39 |
40 | 41 | -------------------------------------------------------------------------------- /ui/src/lib/components/EditorWidget.svelte: -------------------------------------------------------------------------------- 1 | 79 | 80 | 81 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | {#if Object.keys(models).length == 0} 92 |
Code viewer
93 | {/if} 94 |
95 |
96 |
97 |
98 |
99 |
-------------------------------------------------------------------------------- /ui/src/lib/components/MessageContainer.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
82 | 83 | 105 | -------------------------------------------------------------------------------- /ui/src/lib/components/MessageInput.svelte: -------------------------------------------------------------------------------- 1 | 79 | 80 |
81 |
82 |
83 | Agent status: 84 | {#if $agentState !== null} 85 | {#if $agentState.agent_is_active} 86 | Active 87 | {:else} 88 | Inactive 89 | {/if} 90 | {:else} 91 | Deactive 92 | {/if} 93 |
94 | 95 |
96 | Model Inference: {inference_time} sec 97 |
98 | 99 |
100 | 101 |
102 | 118 | 126 |

127 | 0 128 |

129 |
130 |
131 | 132 | 139 | -------------------------------------------------------------------------------- /ui/src/lib/components/MonacoEditor.js: -------------------------------------------------------------------------------- 1 | import loader from "@monaco-editor/loader"; 2 | import { Icons } from "../icons"; 3 | 4 | function getFileLanguage(fileType) { 5 | const fileTypeToLanguage = { 6 | js: "javascript", 7 | jsx: "javascript", 8 | ts: "typescript", 9 | tsx: "typescript", 10 | html: "html", 11 | css: "css", 12 | py: "python", 13 | java: "java", 14 | rb: "ruby", 15 | php: "php", 16 | cpp: "c++", 17 | c: "c", 18 | swift: "swift", 19 | kt: "kotlin", 20 | json: "json", 21 | xml: "xml", 22 | sql: "sql", 23 | sh: "shell", 24 | }; 25 | const language = fileTypeToLanguage[fileType.toLowerCase()]; 26 | return language; 27 | } 28 | 29 | const getTheme = () => { 30 | const theme = localStorage.getItem("mode-watcher-mode"); 31 | return theme === "light" ? "vs-light" : "vs-dark"; 32 | }; 33 | 34 | export async function initializeMonaco() { 35 | const monacoEditor = await import("monaco-editor"); 36 | loader.config({ monaco: monacoEditor.default }); 37 | return loader.init(); 38 | } 39 | 40 | export async function initializeEditorRef(monaco, container) { 41 | const editor = monaco.editor.create(container, { 42 | theme: getTheme(), 43 | readOnly: false, 44 | automaticLayout: true, 45 | }); 46 | return editor; 47 | } 48 | 49 | export function createModel(monaco, file) { 50 | const model = monaco.editor.createModel( 51 | file.code, 52 | getFileLanguage(file.file.split(".").pop()) 53 | ); 54 | return model; 55 | } 56 | 57 | export function disposeEditor(editor) { 58 | if(editor) editor.dispose(); 59 | } 60 | 61 | export function enableTabSwitching(editor, models, tabContainer) { 62 | tabContainer.innerHTML = ""; 63 | Object.keys(models).forEach((filename, index) => { 64 | const tabElement = document.createElement("div"); 65 | tabElement.textContent = filename.split("/").pop(); 66 | tabElement.className = "tab p-2 me-2 rounded-lg text-sm cursor-pointer hover:bg-secondary text-primary whitespace-nowrap"; 67 | tabElement.setAttribute("data-filename", filename); 68 | tabElement.addEventListener("click", () => 69 | switchTab(editor, models, filename, tabElement) 70 | ); 71 | if (index === Object.keys(models).length - 1) { 72 | tabElement.classList.add("bg-secondary"); 73 | } 74 | tabContainer.appendChild(tabElement); 75 | }); 76 | } 77 | 78 | function switchTab(editor, models, filename, tabElement) { 79 | Object.entries(models).forEach(([file, model]) => { 80 | if (file === filename) { 81 | editor.setModel(model); 82 | } 83 | }); 84 | 85 | const allTabElements = tabElement?.parentElement?.children; 86 | for (let i = 0; i < allTabElements?.length; i++) { 87 | allTabElements[i].classList.remove("bg-secondary"); 88 | } 89 | 90 | tabElement.classList.add("bg-secondary"); 91 | } 92 | 93 | export function sidebar(editor, models, sidebarContainer) { 94 | sidebarContainer.innerHTML = ""; 95 | const createSidebarElement = (filename, isFolder) => { 96 | const sidebarElement = document.createElement("div"); 97 | sidebarElement.classList.add("mx-3", "p-1", "px-2", "cursor-pointer"); 98 | if (isFolder) { 99 | sidebarElement.innerHTML = `

${Icons.Folder}${" "}${filename}

`; 100 | // TODO implement folder collapse/expand to the element sidebarElement 101 | } else { 102 | sidebarElement.innerHTML = `

${Icons.File}${" "}${filename}

`; 103 | } 104 | return sidebarElement; 105 | }; 106 | 107 | const changeTabColor = (index) => { 108 | const allTabElements = document.querySelectorAll("#tabContainer")[0].children; 109 | for (let i = 0; i < allTabElements?.length; i++) { 110 | allTabElements[i].classList.remove("bg-secondary"); 111 | } 112 | allTabElements[index].classList.add("bg-secondary"); 113 | } 114 | 115 | const folders = {}; 116 | 117 | Object.entries(models).forEach(([filename, model], modelIndex) => { 118 | const parts = filename.split('/'); 119 | let currentFolder = sidebarContainer; 120 | 121 | parts.forEach((part, index) => { 122 | if (index === parts.length - 1) { 123 | const fileElement = createSidebarElement(part, false); 124 | fileElement.addEventListener("click", () => { 125 | editor.setModel(model); 126 | changeTabColor(modelIndex); 127 | }); 128 | currentFolder.appendChild(fileElement); 129 | } else { 130 | const folderName = part; 131 | if (!folders[folderName]) { 132 | const folderElement = createSidebarElement(part, true); 133 | currentFolder.appendChild(folderElement); 134 | folders[folderName] = folderElement; 135 | currentFolder = folderElement; 136 | } else { 137 | currentFolder = folders[folderName]; 138 | } 139 | } 140 | }); 141 | }); 142 | } -------------------------------------------------------------------------------- /ui/src/lib/components/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
28 | {#each navItems as { icon, tooltip, route }, i} 29 | 35 | {/each} 36 |
37 | -------------------------------------------------------------------------------- /ui/src/lib/components/TerminalWidget.svelte: -------------------------------------------------------------------------------- 1 | 69 | 70 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Terminal 80 |
81 |
85 |
86 | 87 | 103 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/Seperator.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
-------------------------------------------------------------------------------- /ui/src/lib/components/ui/SidebarButton.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 19 | 20 | 21 | 44 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/resizable/index.js: -------------------------------------------------------------------------------- 1 | import { Pane } from "paneforge"; 2 | import Handle from "./resizable-handle.svelte"; 3 | import PaneGroup from "./resizable-pane-group.svelte"; 4 | export { 5 | PaneGroup, 6 | Pane, 7 | Handle, 8 | // 9 | PaneGroup as ResizablePaneGroup, 10 | Pane as ResizablePane, 11 | Handle as ResizableHandle, 12 | }; 13 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/resizable/resizable-handle.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | div]:rotate-90", 14 | className 15 | )} 16 | > 17 | {#if withHandle} 18 |
19 | 20 |
21 | {/if} 22 |
23 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/resizable/resizable-pane-group.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/select/index.js: -------------------------------------------------------------------------------- 1 | import { Select as SelectPrimitive } from "bits-ui"; 2 | import Label from "./select-label.svelte"; 3 | import Item from "./select-item.svelte"; 4 | import Content from "./select-content.svelte"; 5 | import Trigger from "./select-trigger.svelte"; 6 | import Separator from "./select-separator.svelte"; 7 | const Root = SelectPrimitive.Root; 8 | const Group = SelectPrimitive.Group; 9 | const Input = SelectPrimitive.Input; 10 | const Value = SelectPrimitive.Value; 11 | export { 12 | Root, 13 | Group, 14 | Input, 15 | Label, 16 | Item, 17 | Value, 18 | Content, 19 | Trigger, 20 | Separator, 21 | // 22 | Root as Select, 23 | Group as SelectGroup, 24 | Input as SelectInput, 25 | Label as SelectLabel, 26 | Item as SelectItem, 27 | Value as SelectValue, 28 | Content as SelectContent, 29 | Trigger as SelectTrigger, 30 | Separator as SelectSeparator, 31 | }; 32 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/select/select-content.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 |
32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/select/select-item.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | 30 | {@html Icons.Check} 31 | 32 | 33 | 34 | {label || value} 35 | 36 | 37 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/select/select-label.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/select/select-separator.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/select/select-trigger.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | span]:line-clamp-1", 12 | className 13 | )} 14 | {...$$restProps} 15 | let:builder 16 | on:click 17 | on:keydown 18 | > 19 | 20 |
21 | {@html Icons.ChevronDown} 22 |
23 |
24 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/sonner/index.js: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from "./sonner.svelte"; 2 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/sonner/sonner.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/tabs/index.js: -------------------------------------------------------------------------------- 1 | import { Tabs as TabsPrimitive } from "bits-ui"; 2 | import Content from "./tabs-content.svelte"; 3 | import List from "./tabs-list.svelte"; 4 | import Trigger from "./tabs-trigger.svelte"; 5 | 6 | const Root = TabsPrimitive.Root; 7 | 8 | export { 9 | Root, 10 | Content, 11 | List, 12 | Trigger, 13 | // 14 | Root as Tabs, 15 | Content as TabsContent, 16 | List as TabsList, 17 | Trigger as TabsTrigger, 18 | }; 19 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/tabs/tabs-content.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/tabs/tabs-list.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ui/src/lib/components/ui/tabs/tabs-trigger.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ui/src/lib/icons.js: -------------------------------------------------------------------------------- 1 | export const Icons = { 2 | HOME: ' ', 3 | SETTINGS: ' ', 4 | LOGS: ' ', 5 | X: '', 6 | ChevronDown : '', 7 | Check : '', 8 | CornerDownLeft : '', 9 | Folder: '', 10 | File: '', 11 | }; -------------------------------------------------------------------------------- /ui/src/lib/sockets.js: -------------------------------------------------------------------------------- 1 | import { socket } from "./api"; 2 | import { messages, agentState, isSending, tokenUsage } from "./store"; 3 | import { toast } from "svelte-sonner"; 4 | import { get } from "svelte/store"; 5 | 6 | let prevMonologue = null; 7 | 8 | export function initializeSockets() { 9 | 10 | socket.connect(); 11 | 12 | let state = get(agentState); 13 | prevMonologue = state?.internal_monologue; 14 | 15 | socket.emit("socket_connect", { data: "frontend connected!" }); 16 | socket.on("socket_response", function (msg) { 17 | console.log(msg); 18 | }); 19 | 20 | socket.on("server-message", function (data) { 21 | console.log(data) 22 | messages.update((msgs) => [...msgs, data["messages"]]); 23 | }); 24 | 25 | socket.on("agent-state", function (state) { 26 | const lastState = state[state.length - 1]; 27 | agentState.set(lastState); 28 | if (lastState.completed) { 29 | isSending.set(false); 30 | } 31 | }); 32 | 33 | socket.on("tokens", function (tokens) { 34 | tokenUsage.set(tokens["token_usage"]); 35 | }); 36 | 37 | socket.on("inference", function (error) { 38 | if (error["type"] == "error") { 39 | toast.error(error["message"]); 40 | isSending.set(false); 41 | } else if (error["type"] == "warning") { 42 | toast.warning(error["message"]); 43 | } 44 | }); 45 | 46 | socket.on("info", function (info) { 47 | if (info["type"] == "error") { 48 | toast.error(info["message"]); 49 | isSending.set(false); 50 | } else if (info["type"] == "warning") { 51 | toast.warning(info["message"]); 52 | } else if (info["type"] == "info") { 53 | toast.info(info["message"]); 54 | } 55 | }); 56 | 57 | 58 | agentState.subscribe((state) => { 59 | function handleMonologueChange(newValue) { 60 | if (newValue) { 61 | toast(newValue); 62 | } 63 | } 64 | if ( 65 | state && 66 | state.internal_monologue && 67 | state.internal_monologue !== prevMonologue 68 | ) { 69 | handleMonologueChange(state.internal_monologue); 70 | prevMonologue = state.internal_monologue; 71 | } 72 | }); 73 | } 74 | 75 | export function destroySockets() { 76 | if (socket.connected) { 77 | socket.off("socket_response"); 78 | socket.off("server-message"); 79 | socket.off("agent-state"); 80 | socket.off("tokens"); 81 | socket.off("inference"); 82 | socket.off("info"); 83 | } 84 | } 85 | 86 | export function emitMessage(channel, message) { 87 | socket.emit(channel, message); 88 | } 89 | 90 | export function socketListener(channel, callback) { 91 | socket.on(channel, callback); 92 | } 93 | -------------------------------------------------------------------------------- /ui/src/lib/store.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | // Helper function to get item from localStorage 4 | function getItemFromLocalStorage(key, defaultValue) { 5 | const storedValue = localStorage.getItem(key); 6 | if (storedValue) { 7 | return storedValue; 8 | } 9 | localStorage.setItem(key, defaultValue); 10 | return defaultValue; 11 | } 12 | 13 | // Helper function to handle subscription and local storage setting 14 | function subscribeAndStore(store, key, defaultValue) { 15 | store.set(getItemFromLocalStorage(key, defaultValue)); 16 | store.subscribe(value => { 17 | localStorage.setItem(key, value); 18 | }); 19 | } 20 | 21 | // Server related stores 22 | export const serverStatus = writable(false); 23 | export const internet = writable(true); 24 | 25 | // Message related stores 26 | export const messages = writable([]); 27 | export const projectFiles = writable(null); 28 | 29 | // Selection related stores 30 | export const selectedProject = writable(''); 31 | export const selectedModel = writable(''); 32 | export const selectedSearchEngine = writable(''); 33 | 34 | subscribeAndStore(selectedProject, 'selectedProject', 'select project'); 35 | subscribeAndStore(selectedModel, 'selectedModel', 'select model'); 36 | subscribeAndStore(selectedSearchEngine, 'selectedSearchEngine', 'select search engine'); 37 | 38 | // List related stores 39 | export const projectList = writable([]); 40 | export const modelList = writable({}); 41 | export const searchEngineList = writable([]); 42 | 43 | // Agent related stores 44 | export const agentState = writable(null); 45 | export const isSending = writable(false); 46 | 47 | // Token usage store 48 | export const tokenUsage = writable(0); 49 | -------------------------------------------------------------------------------- /ui/src/lib/token.js: -------------------------------------------------------------------------------- 1 | import { Tiktoken } from "tiktoken/lite"; 2 | import cl100k_base from "tiktoken/encoders/cl100k_base.json"; 3 | 4 | const encoding = new Tiktoken( 5 | cl100k_base.bpe_ranks, 6 | cl100k_base.special_tokens, 7 | cl100k_base.pat_str 8 | ); 9 | 10 | export function calculateTokens(text) { 11 | const tokens = encoding.encode(text); 12 | return tokens.length; 13 | } 14 | -------------------------------------------------------------------------------- /ui/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { cubicOut } from "svelte/easing"; 4 | 5 | export function cn(...inputs) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | 9 | export const flyAndScale = ( 10 | node, 11 | params = { y: -8, x: 0, start: 0.95, duration: 150 } 12 | ) => { 13 | const style = getComputedStyle(node); 14 | const transform = style.transform === "none" ? "" : style.transform; 15 | 16 | const scaleConversion = (valueA, scaleA, scaleB) => { 17 | const [minA, maxA] = scaleA; 18 | const [minB, maxB] = scaleB; 19 | 20 | const percentage = (valueA - minA) / (maxA - minA); 21 | const valueB = percentage * (maxB - minB) + minB; 22 | 23 | return valueB; 24 | }; 25 | 26 | const styleToString = (style) => { 27 | return Object.keys(style).reduce((str, key) => { 28 | if (style[key] === undefined) return str; 29 | return str + `${key}:${style[key]};`; 30 | }, ""); 31 | }; 32 | 33 | return { 34 | duration: params.duration ?? 200, 35 | delay: 0, 36 | css: (t) => { 37 | const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); 38 | const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); 39 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 40 | 41 | return styleToString({ 42 | transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, 43 | opacity: t 44 | }); 45 | }, 46 | easing: cubicOut 47 | }; 48 | }; -------------------------------------------------------------------------------- /ui/src/routes/+layout.js: -------------------------------------------------------------------------------- 1 | export const ssr = false 2 | -------------------------------------------------------------------------------- /ui/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /ui/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 | 41 | 42 |
43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 |
52 |
53 |
54 | 55 |
56 |
57 |
-------------------------------------------------------------------------------- /ui/src/routes/logs/+page.svelte: -------------------------------------------------------------------------------- 1 | 39 | 40 |
41 |

Logs

42 |
43 |
44 |

Request logs

45 |
46 | {#each logs as log} 47 |

48 | {@html log} 49 |

50 | {/each} 51 |
52 |
53 |
54 |

Socket logs

55 |
56 | {#each socket_logs as log} 57 |

58 | {@html log} 59 |

60 | {/each} 61 |
62 |
63 |
64 |
-------------------------------------------------------------------------------- /ui/static/assets/bootup.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/ui/static/assets/bootup.mp3 -------------------------------------------------------------------------------- /ui/static/assets/devika-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/ui/static/assets/devika-avatar.png -------------------------------------------------------------------------------- /ui/static/assets/devika-avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ui/static/assets/user-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/ui/static/assets/user-avatar.png -------------------------------------------------------------------------------- /ui/static/assets/user-avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stitionai/devika/3b98ed31e22cd2893563d4b375c2538c72a2e224/ui/static/favicon.png -------------------------------------------------------------------------------- /ui/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | import adapter from "@sveltejs/adapter-auto"; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | kit: { 7 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 8 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 9 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 10 | adapter: adapter(), 11 | }, 12 | 13 | preprocess: [vitePreprocess({})], 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const config = { 3 | darkMode: ["class"], 4 | content: ["./src/**/*.{html,js,svelte,ts}"], 5 | safelist: ["dark"], 6 | theme: { 7 | container: { 8 | center: true, 9 | padding: "2rem", 10 | screens: { 11 | "2xl": "1400px" 12 | } 13 | }, 14 | extend: { 15 | colors: { 16 | 'primary': 'var(--primary)', 17 | 'background': 'var(--background)', 18 | 'secondary': 'var(--secondary)', 19 | 'tertiary': 'var(--tertiary)', 20 | 'foreground': 'var(--foreground)', 21 | 'foreground-invert': 'var(--foreground-invert)', 22 | 'foreground-light': 'var(--foreground-light)', 23 | 'foreground-secondary': 'var(--foreground-secondary)', 24 | 'border': 'var(--border)', 25 | 'btn-active': 'var(--btn-active)', 26 | 'seperator': 'var(--seperator)', 27 | 'window-outline': 'var(--window-outline)', 28 | 'browser-window-dots': 'var(--browser-window-dots)', 29 | 'browser-window-search': 'var(--browser-window-search)', 30 | 'browser-window-ribbon': 'var(--browser-window-ribbon)', 31 | 'browser-window-foreground': 'var(--browser-window-foreground)', 32 | 'browser-window-background': 'var(--browser-window-background)', 33 | 'terminal-window-dots': 'var(--terminal-window-dots)', 34 | 'terminal-window-ribbon': 'var(--terminal-window-ribbon)', 35 | 'terminal-window-background': 'var(--terminal-window-background)', 36 | 'terminal-window-foreground': 'var(--terminal-window-foreground)', 37 | 'slider-empty': 'var(--slider-empty)', 38 | 'slider-filled': 'var(--slider-filled)', 39 | 'slider-thumb': 'var(--slider-thumb)', 40 | 'monologue-background': 'var(--monologue-background)', 41 | 'monologue-outline': 'var(--monologue-outline)', 42 | }, 43 | fontFamily: { 44 | sans: ["Helvetica"] 45 | } 46 | } 47 | }, 48 | }; 49 | 50 | export default config; -------------------------------------------------------------------------------- /ui/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | import wasm from "vite-plugin-wasm"; 4 | 5 | export default defineConfig({ 6 | plugins: [sveltekit(), wasm()], 7 | server: { 8 | port: 3000, 9 | }, 10 | preview: { 11 | port: 3001, 12 | }, 13 | build: { 14 | target: "esnext", 15 | }, 16 | }); 17 | --------------------------------------------------------------------------------