├── .env.example ├── .gitattributes ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── app └── components │ └── Todo.tsx ├── backend ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── camel_agent.py │ │ ├── deps.py │ │ ├── main.py │ │ └── routes │ │ │ ├── __init__.py │ │ │ ├── agent.py │ │ │ ├── approval.py │ │ │ ├── camel.py │ │ │ ├── human.py │ │ │ ├── mock_data.py │ │ │ ├── rag.py │ │ │ ├── rolePlaying.py │ │ │ ├── settings.py │ │ │ └── workflow.py │ ├── core │ │ ├── __init__.py │ │ └── config.py │ ├── main.py │ ├── models │ │ ├── __init__.py │ │ ├── approval.py │ │ ├── chat.py │ │ ├── human.py │ │ ├── rag.py │ │ ├── settings.py │ │ └── workflow.py │ └── tests │ │ ├── __init__.py │ │ └── conftest.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format.sh │ ├── lint.sh │ └── test.sh └── tests-start.sh ├── docker-compose.override.yml ├── docker-compose.traefik.yml ├── docker-compose.yml ├── draft_prd.markdown ├── frontend ├── .dockerignore ├── .env ├── .gitignore ├── .nvmrc ├── Dockerfile ├── README.md ├── biome.json ├── components.json ├── eslint.config.js ├── index.html ├── modify-openapi-operationids.js ├── nginx-backend-not-found.conf ├── nginx.conf ├── package-lock.json ├── package.json ├── playwright.config.ts ├── playwright │ └── .auth │ │ └── user.json ├── public │ └── assets │ │ └── images │ │ ├── camel-logo-purple.svg │ │ ├── camel-logo.svg │ │ └── favicon.png ├── src │ ├── AIPlayground.css │ ├── AIPlayground.tsx │ ├── App.css │ ├── App.tsx │ ├── Canvas.tsx │ ├── assets │ │ ├── fonts │ │ │ ├── OpenSans.ttf │ │ │ └── Palatino.ttf │ │ └── react.svg │ ├── canvas.css │ ├── components │ │ ├── app-sidebar.tsx │ │ ├── nav-main.tsx │ │ ├── nav-projects.tsx │ │ ├── nav-user.tsx │ │ ├── team-switcher.tsx │ │ └── ui │ │ │ ├── avatar.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── chat-input.tsx │ │ │ ├── chat │ │ │ ├── chat-bubble.tsx │ │ │ ├── chat-input.tsx │ │ │ ├── chat-message-list.tsx │ │ │ ├── expandable-chat.tsx │ │ │ ├── hooks │ │ │ │ └── useAutoScroll.tsx │ │ │ └── message-loading.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── code-block.tsx │ │ │ ├── code-editor.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── file-upload.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── resizable.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── switch.tsx │ │ │ ├── tabs.tsx │ │ │ └── tooltip.tsx │ ├── hooks │ │ └── use-mobile.ts │ ├── index.css │ ├── library │ │ └── utils.ts │ ├── main.tsx │ ├── routeTree.gen.ts │ ├── routes │ │ ├── __root.tsx │ │ ├── _layout.tsx │ │ ├── _layout │ │ │ ├── admin.tsx │ │ │ ├── index.tsx │ │ │ ├── settings.tsx │ │ │ └── tasks.tsx │ │ ├── login.tsx │ │ └── signup.tsx │ └── vite-env.d.ts ├── tests │ ├── auth.setup.ts │ ├── camel.spec.ts │ ├── config.ts │ ├── home.spec.ts │ └── utils │ │ ├── random.ts │ │ └── user.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── hooks └── post_gen_project.py └── scripts ├── build-push.sh ├── build.sh ├── deploy.sh ├── test-local.sh └── test.sh /.env.example: -------------------------------------------------------------------------------- 1 | # Domain 2 | # This would be set to the production domain with an env var on deployment 3 | DOMAIN=localhost 4 | # To test the local Traefik config 5 | # DOMAIN=localhost.app.camel-ai.org 6 | 7 | # Used by the backend to generate links in emails to the frontend 8 | FRONTEND_HOST=http://localhost:5173 9 | # In staging and production, set this env var to the frontend host, e.g. 10 | # FRONTEND_HOST=http://localhost.app.camel-ai.org 11 | 12 | # INSTALL_DEV: boolean, set to true to install dev dependencies 13 | INSTALL_DEV=false 14 | # Environment: local, staging, production 15 | ENVIRONMENT=local 16 | 17 | PROJECT_NAME=CamelWebApp 18 | STACK_NAME=camelwebapp 19 | 20 | # Celery 21 | CELERY_BROKER_URL=redis://redis:6379/0 22 | CELERY_RESULT_BACKEND=redis://redis:6379/0 23 | 24 | # Backend 25 | BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.app.camel-ai.org,http://localhost.app.camel-ai.org" 26 | SECRET_KEY=changethis 27 | FIRST_SUPERUSER=admin@example.com 28 | FIRST_SUPERUSER_PASSWORD=changethis 29 | 30 | # Postgres 31 | POSTGRES_SERVER=localhost 32 | POSTGRES_PORT=5432 33 | POSTGRES_DB=app 34 | POSTGRES_USER=postgres 35 | POSTGRES_PASSWORD=changethis 36 | 37 | SENTRY_DSN= 38 | 39 | # Configure these with your own Docker registry images 40 | DOCKER_IMAGE_BACKEND=camelwebapp-backend 41 | DOCKER_IMAGE_FRONTEND=camelwebapp-frontend 42 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | .idea/ 163 | 164 | .env.local 165 | 166 | node_modules/ 167 | /test-results/ 168 | /playwright-report/ 169 | /blob-report/ 170 | /playwright/.cache/ 171 | 172 | ### NextJS template 173 | # dependencies 174 | /node_modules 175 | /.pnp 176 | .pnp.js 177 | 178 | # testing 179 | /coverage 180 | 181 | # next.js 182 | /.next/ 183 | /out/ 184 | 185 | # production 186 | /build 187 | 188 | # misc 189 | .DS_Store 190 | *.pem 191 | 192 | # debug 193 | npm-debug.log* 194 | yarn-debug.log* 195 | yarn-error.log* 196 | .pnpm-debug.log* 197 | 198 | # local env files 199 | .env*.local 200 | 201 | # vercel 202 | .vercel 203 | 204 | # typescript 205 | *.tsbuildinfo 206 | next-env.d.ts 207 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.4.0 6 | hooks: 7 | - id: check-added-large-files 8 | - id: check-toml 9 | - id: check-yaml 10 | args: 11 | - --unsafe 12 | - id: end-of-file-fixer 13 | exclude: ^frontend/src/client/.* 14 | - id: trailing-whitespace 15 | exclude: ^frontend/src/client/.* 16 | - repo: https://github.com/charliermarsh/ruff-pre-commit 17 | rev: v0.2.2 18 | hooks: 19 | - id: ruff 20 | args: 21 | - --fix 22 | - id: ruff-format 23 | 24 | ci: 25 | autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks 26 | autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CAMEL Web App 2 | 3 | ## Overview 4 | CAMEL Web App is a React-based web interface designed to demonstrate CAMEL's various modules. This interactive platform enables users to explore and interact with CAMEL's capabilities through a user-friendly interface. 5 | 6 | ## Contributing 7 | We welcome contributions! Please feel free to submit a Pull Request. 8 | 9 | ## Contributors 10 | - Front-end Lead: [xinyuguan3](https://github.com/xinyuguan3) 11 | - Back-end Lead: [koch3092](https://github.com/koch3092) 12 | - Back-end: [xzjjj](https://github.com/xzjjj) 13 | - Full Stack: [User235514](https://github.com/User235514) 14 | 15 | ## Features 16 | - **Multi-Model Support** 17 | - DeepSeek 18 | - Llama 19 | - Qwen 20 | - Support for future model integrations 21 | 22 | - **Advanced Model Management** 23 | - Flexible model switching 24 | - Customizable system messages 25 | - Configurable model parameters 26 | - Tool integration support 27 | 28 | - **Tool Integration** 29 | - Access to CAMEL toolkits 30 | - Tool-specific configuration options 31 | - Parameter input interface 32 | - Result visualization 33 | 34 | - **Role Playing Sessions** 35 | - Multi-agent interactions 36 | - Customizable agent roles 37 | - Task-specific configurations 38 | - Session management 39 | 40 | - **Workforce Module** 41 | - Coordinate multiple specialized agents 42 | - Mix of single agents and role-playing pairs 43 | - Customizable workforce configuration 44 | - Collaborative task solving 45 | - Flexible agent role assignment 46 | 47 | ## Target Users 48 | - Developers integrating CAMEL into their applications 49 | - Researchers exploring Agent capabilities 50 | - Open-source contributors 51 | 52 | ## Technology Stack and Features 53 | 54 | - ⚡ [**FastAPI**](https://fastapi.tiangolo.com) for the Python backend API. 55 | - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) for the Python SQL database interactions (ORM). 56 | - 🔍 [Pydantic](https://docs.pydantic.dev), used by FastAPI, for the data validation and settings management. 57 | - 💾 [PostgreSQL](https://www.postgresql.org) as the SQL database. 58 | - 🚀 [React](https://react.dev) for the frontend. 59 | - 💃 Using TypeScript, hooks, Vite, and other parts of a modern frontend stack. 60 | - 🎨 [Chakra UI](https://chakra-ui.com) for the frontend components. 61 | - 🤖 An automatically generated frontend client. 62 | - 🧪 [Playwright](https://playwright.dev) for End-to-End testing. 63 | - 🦇 Dark mode support. 64 | - 🐋 [Docker Compose](https://www.docker.com) for development and production. 65 | - 🔑 JWT (JSON Web Token) authentication. 66 | - ✅ Tests with [Pytest](https://pytest.org). 67 | - 📞 [Traefik](https://traefik.io) as a local reverse proxy / load balancer. 68 | - 🚢 Deployment instructions using Docker Compose, including how to set up a frontend Traefik proxy to handle automatic HTTPS certificates. 69 | - 🏭 CI (continuous integration) and CD (continuous deployment) based on GitHub Actions. 70 | 71 | ## Getting Started 72 | 73 | ### Clone Repository 74 | You can clone this repository with: 75 | 76 | ```bash 77 | git clone https://github.com/camel-ai/camel_web_app.git 78 | ``` 79 | 80 | ### Configure 81 | 82 | Copy the `.env.example` files to `.env`: 83 | 84 | ```bash 85 | cp .env.example .env 86 | ``` 87 | 88 | Then you can then update configs in the `.env` files to customize your configurations. 89 | 90 | Before deploying it, make sure you change at least the values for: 91 | 92 | - `SECRET_KEY` 93 | - `POSTGRES_PASSWORD` 94 | 95 | You can (and should) pass these as environment variables from secrets. 96 | 97 | Also, you can set the following environment variables: 98 | 99 | - `SENTRY_DSN`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env. 100 | 101 | ### Generate Secret Keys 102 | 103 | Some environment variables in the `.env` file have a default value of `changethis`. 104 | 105 | You have to change them with a secret key, to generate secret keys you can run the following command: 106 | 107 | ```bash 108 | python -c "import secrets; print(secrets.token_urlsafe(32))" 109 | ``` 110 | 111 | Copy the content and use that as password / secret key. And run that again to generate another secure key. 112 | 113 | ### Run all services using Docker Compose 114 | You can run all the services using Docker Compose with: 115 | ```bash 116 | docker compose up -d 117 | ``` 118 | 119 | You will see something like: 120 | ```bash 121 | [+] Running 15/15oard] exporting to image 0.0s 122 | ✔ Service frontend Built 41.3s 123 | ✔ Service celery-worker Built 92.7s 124 | ✔ Service backend Built 1.2s 125 | ✔ Service celery-dashboard Built 0.6s 126 | ✔ Network camel_web_app_default Created 0.0s 127 | ✔ Network camel_web_app_traefik-public Created 0.0s 128 | ✔ Volume "camel_web_app_app-db-data" Created 0.0s 129 | ✔ Container camel_web_app-proxy-1 Started 0.3s 130 | ✔ Container camel_web_app-frontend-1 Started 0.3s 131 | ✔ Container camel_web_app-db-1 Started 0.3s 132 | ✔ Container camel_web_app-redis-1 Healthy 5.8s 133 | ✔ Container camel_web_app-celery-worker-1 Started 5.9s 134 | ✔ Container camel_web_app-adminer-1 Started 0.4s 135 | ✔ Container camel_web_app-backend-1 Started 6.0s 136 | ✔ Container camel_web_app-celery-dashboard-1 Started 6.1s 137 | ``` 138 | ## URLs 139 | 140 | ### Development URLs 141 | 142 | Development URLs, for local development. 143 | 144 | Frontend: http://localhost:5173 145 | 146 | Backend: http://localhost:8000 147 | 148 | Automatic Interactive Docs (Swagger UI): http://localhost:8000/docs 149 | 150 | Automatic Alternative Docs (ReDoc): http://localhost:8000/redoc 151 | 152 | Adminer: http://localhost:8080 153 | 154 | Traefik UI: http://localhost:8090 155 | 156 | ### Development URLs with `localhost.app.camel-ai.org` Configured 157 | 158 | Development URLs, for local development. 159 | 160 | Frontend: http://localhost.app.camel-ai.org 161 | 162 | Backend: http://localhost.app.camel-ai.org/api 163 | 164 | Automatic Interactive Docs (Swagger UI): http://localhost.app.camel-ai.org/docs 165 | 166 | Automatic Alternative Docs (ReDoc): http://localhost.app.camel-ai.org/redoc 167 | 168 | Adminer: http://localhost.app.camel-ai.org:8080 169 | 170 | Traefik UI: http://localhost.app.camel-ai.org:8090 171 | 172 | ## License 173 | (To be added: License information) 174 | -------------------------------------------------------------------------------- /app/components/Todo.tsx: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__ 3 | app.egg-info 4 | *.pyc 5 | .mypy_cache 6 | .coverage 7 | htmlcov 8 | .venv 9 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | app.egg-info 3 | *.pyc 4 | .mypy_cache 5 | .coverage 6 | htmlcov 7 | .cache 8 | .venv 9 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.10 2 | 3 | WORKDIR /app/ 4 | 5 | # Install Poetry 6 | RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ 7 | cd /usr/local/bin && \ 8 | ln -s /opt/poetry/bin/poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | # Copy poetry.lock* in case it doesn't exist in the repo 12 | COPY ./pyproject.toml ./poetry.lock* /app/ 13 | 14 | # Allow installing dev dependencies to run tests 15 | ARG INSTALL_DEV=false 16 | RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --only main ; fi" 17 | 18 | ENV PYTHONPATH=/app 19 | 20 | COPY ./scripts/ /app/ 21 | 22 | COPY ./tests-start.sh /app/ 23 | 24 | COPY ./app /app/app 25 | 26 | CMD ["fastapi", "run", "/app/app/main.py"] 27 | -------------------------------------------------------------------------------- /backend/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/backend/app/__init__.py -------------------------------------------------------------------------------- /backend/app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/backend/app/api/__init__.py -------------------------------------------------------------------------------- /backend/app/api/camel_agent.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from pydantic import BaseModel 3 | from camel.agents import ChatAgent 4 | from camel.configs import ChatGPTConfig 5 | from camel.models import ModelFactory 6 | from camel.types import ModelPlatformType, ModelType 7 | import os 8 | from dotenv import load_dotenv 9 | 10 | load_dotenv() 11 | 12 | router = APIRouter() 13 | 14 | class AgentRequest(BaseModel): 15 | system_message: str 16 | user_message: str 17 | platform_type: str = "OPENAI" # 默认使用 OpenAI 18 | model_type: str = "GPT_4" # 默认使用 GPT-4 19 | api_key: str | None = None 20 | base_url: str | None = None 21 | 22 | class AgentResponse(BaseModel): 23 | content: str 24 | 25 | @router.post("/create-agent", response_model=AgentResponse) 26 | async def create_agent(request: AgentRequest): 27 | try: 28 | # 使用环境变量中的 API key,如果请求中没有提供的话 29 | api_key = request.api_key or os.getenv("OPENAI_API_KEY") 30 | if not api_key: 31 | raise HTTPException(status_code=400, detail="API key is required") 32 | 33 | # 创建模型 34 | model = ModelFactory.create( 35 | model_platform=ModelPlatformType[request.platform_type], 36 | model_type=ModelType[request.model_type], 37 | api_key=api_key, 38 | url=request.base_url, 39 | model_config_dict=ChatGPTConfig(temperature=0.0).as_dict(), 40 | ) 41 | 42 | # 创建 agent 43 | agent = ChatAgent( 44 | system_message=request.system_message, 45 | model=model, 46 | message_window_size=10 47 | ) 48 | 49 | # 获取响应 50 | response = agent.step(request.user_message) 51 | 52 | return AgentResponse(content=response.msgs[0].content) 53 | 54 | except Exception as e: 55 | raise HTTPException(status_code=500, detail=str(e)) -------------------------------------------------------------------------------- /backend/app/api/deps.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/backend/app/api/deps.py -------------------------------------------------------------------------------- /backend/app/api/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from app.api.routes import ( 4 | settings, 5 | rag, 6 | workflow, 7 | approval, 8 | mock_data, 9 | human, 10 | agent, 11 | rolePlaying, 12 | camel, 13 | ) 14 | 15 | api_router = APIRouter() 16 | api_router.include_router(settings.router, prefix="/settings", tags=["settings"]) 17 | api_router.include_router(rag.router, prefix="/rag", tags=["rag"]) 18 | api_router.include_router(workflow.router, prefix="/workflow", tags=["workflow"]) 19 | api_router.include_router(approval.router, prefix="/approval", tags=["approval"]) 20 | api_router.include_router(mock_data.router, prefix="/mock_data", tags=["mock_data"]) 21 | api_router.include_router(human.router, prefix="/human", tags=["human"]) 22 | api_router.include_router(agent.router, prefix="/agent", tags=["agent"]) 23 | api_router.include_router(rolePlaying.router, prefix="/rolePlaying", tags=["rolePlaying"]) 24 | api_router.include_router(camel.router, prefix="/camel", tags=["camel"]) 25 | -------------------------------------------------------------------------------- /backend/app/api/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/backend/app/api/routes/__init__.py -------------------------------------------------------------------------------- /backend/app/api/routes/agent.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from app.models.chat import ChatRequest 3 | from app.models.settings import Settings 4 | from typing import List, Dict, Any 5 | from camel.agents import ChatAgent 6 | from camel.configs import ChatGPTConfig 7 | from camel.models import ModelFactory 8 | from camel.types import ModelPlatformType, ModelType 9 | 10 | router = APIRouter() 11 | 12 | @router.post("/generate_code") 13 | async def generate_code(request_data: Settings): 14 | return generate_code_fun(request_data) 15 | 16 | def generate_code_fun(code: str) -> str: 17 | code = "" 18 | return code 19 | 20 | @router.post("/chat", response_model=Dict[str, Any]) 21 | async def chat(request: ChatRequest): 22 | try: 23 | # 创建模型配置 24 | model_config = ChatGPTConfig( 25 | temperature=request.temperature, 26 | max_tokens=request.max_tokens 27 | ) 28 | 29 | # 创建模型 30 | model = ModelFactory.create( 31 | model_platform=ModelPlatformType[request.platform_type], 32 | model_type=ModelType[request.model_type], 33 | api_key=request.api_key, 34 | url=request.base_url, 35 | model_config_dict=model_config.as_dict(), 36 | ) 37 | 38 | # 创建 agent 39 | agent = ChatAgent( 40 | system_message=request.system_message, 41 | model=model, 42 | message_window_size=10 43 | ) 44 | 45 | # 获取响应 46 | response = agent.step(request.user_message) 47 | 48 | return {"content": response.msgs[0].content, "role": "assistant"} 49 | 50 | except Exception as e: 51 | raise HTTPException(status_code=500, detail=str(e)) 52 | -------------------------------------------------------------------------------- /backend/app/api/routes/approval.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from app.models.approval import ApprovalRequest, ApprovalResponse 3 | 4 | router = APIRouter() 5 | 6 | @router.post( 7 | "/approval/approve", 8 | response_model=ApprovalResponse, 9 | summary="Process approval request", 10 | description="Process the approval request based on the request ID and action type (approve or reject)." 11 | ) 12 | async def approve_request(request: ApprovalRequest): 13 | """Process approval request""" 14 | # Simulate approval logic 15 | print("Approval request received:", request) 16 | return {"message": "Request approved successfully!"} 17 | -------------------------------------------------------------------------------- /backend/app/api/routes/camel.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from pydantic import BaseModel 3 | from camel.agents import ChatAgent 4 | from camel.configs import ChatGPTConfig 5 | from camel.models import ModelFactory 6 | from camel.types import ModelPlatformType, ModelType 7 | from typing import Optional 8 | 9 | router = APIRouter() 10 | 11 | class AgentRequest(BaseModel): 12 | system_message: str 13 | user_message: str 14 | platform_type: str 15 | model_type: str 16 | base_url: Optional[str] = None 17 | api_key: str 18 | temperature: float = 0.7 19 | max_tokens: int = 2000 20 | 21 | class AgentResponse(BaseModel): 22 | content: str 23 | role: str = "assistant" 24 | 25 | @router.post("/chat", response_model=AgentResponse) 26 | async def chat_with_agent(request: AgentRequest) -> AgentResponse: 27 | try: 28 | # 创建模型配置 29 | model_config = ChatGPTConfig( 30 | temperature=request.temperature, 31 | max_tokens=request.max_tokens 32 | ) 33 | 34 | # 创建模型 35 | model = ModelFactory.create( 36 | model_platform=ModelPlatformType[request.platform_type], 37 | model_type=ModelType[request.model_type], 38 | api_key=request.api_key, 39 | url=request.base_url, 40 | model_config_dict=model_config.as_dict(), 41 | ) 42 | 43 | # 创建 agent 44 | agent = ChatAgent( 45 | system_message=request.system_message, 46 | model=model, 47 | message_window_size=10 48 | ) 49 | 50 | # 获取响应 51 | response = agent.step(request.user_message) 52 | 53 | return AgentResponse(content=response.msgs[0].content) 54 | 55 | except Exception as e: 56 | raise HTTPException(status_code=500, detail=str(e)) -------------------------------------------------------------------------------- /backend/app/api/routes/human.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from app.models.human import Human 3 | 4 | router = APIRouter() 5 | 6 | @router.post( 7 | "/human/process", 8 | response_model=Human, 9 | summary="Process Human request", 10 | description="Process the Human workflow based on the parameters provided by the frontend." 11 | ) 12 | async def process_human(request: Human): 13 | """Process Human request""" 14 | # Simulate Human processing logic 15 | print("Processing human:", request) 16 | return {"message": "Human process completed successfully!"} 17 | -------------------------------------------------------------------------------- /backend/app/api/routes/mock_data.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException, Request, APIRouter 2 | from pydantic import BaseModel 3 | from typing import List, Dict, Any 4 | from fastapi.responses import JSONResponse 5 | from fastapi.middleware.cors import CORSMiddleware 6 | 7 | from app.models.settings import Settings 8 | from app.models.chat import ChatRequest 9 | 10 | router = APIRouter() 11 | 12 | # 模拟的数据库数据 13 | MOCK_DATA = { 14 | "platformOptions": [ 15 | {"value": "OPENAI", "label": "OpenAI"}, 16 | {"value": "MISTRALAI", "label": "MistralAI"}, 17 | {"value": "ANTHROPIC", "label": "Anthropic"}, 18 | {"value": "QWEN", "label": "Qwen"}, 19 | {"value": "DEEPSEEK", "label": "DeepSeek"}, 20 | ], 21 | "modelOptions": { 22 | "OPENAI": [ 23 | {"value": "GPT_4o", "label": "GPT-4o"}, 24 | {"value": "GPT_4o_mini", "label": "GPT-4o-mini"}, 25 | {"value": "o1", "label": "o1"}, 26 | {"value": "o1_preview", "label": "o1-preview"}, 27 | {"value": "o1_mini", "label": "o1-mini"}, 28 | {"value": "GPT_4_TURBO", "label": "GPT-4-turbo"}, 29 | {"value": "GPT_4", "label": "GPT-4"}, 30 | {"value": "GPT_3_5_TURBO", "label": "GPT-3.5-Turbo"}, 31 | ], 32 | "MISTRALAI": [ 33 | {"value": "MISTRAL_LARGE_2", "label": "Mistral-large-2"}, 34 | {"value": "MISTRAL_12B_2409", "label": "Mistral-12b-2409"}, 35 | {"value": "MISTRAL_8B_LATEST", "label": "Mistral-8b-latest"}, 36 | {"value": "MISTRAL_3B_LATEST", "label": "Mistral-3b-latest"}, 37 | {"value": "OPEN_MISTRAL_7B", "label": "Open-mistral-7b"}, 38 | {"value": "OPEN_MISTRAL_NEMO", "label": "Open-mistral-nemo"}, 39 | {"value": "CODESTRAL", "label": "Codestral"}, 40 | {"value": "OPEN_MIXTRAL_8X7B", "label": "Open-mixtral-8x7b"}, 41 | {"value": "OPEN_MIXTRAL_8X22B", "label": "Open-mixtral-8x22b"}, 42 | {"value": "OPEN_CODESTRAL_MAMBA", "label": "Open-codestral-mamba"}, 43 | ], 44 | "ANTHROPIC": [ 45 | {"value": "CLAUDE_3_5_SONNET_LATEST", "label": "Claude-3-5-Sonnet-latest"}, 46 | {"value": "CLAUDE_3_5_HAIKU_LATEST", "label": "Claude-3-5-haiku-latest"}, 47 | {"value": "CLAUDE_3_HAIKU_20240307", "label": "Claude-3-haiku-20240307"}, 48 | {"value": "CLAUDE_3_SONNET_20240229", "label": "Claude-3-sonnet-20240229"}, 49 | {"value": "CLAUDE_3_OPUS_LATEST", "label": "Claude-3-opus-latest"}, 50 | {"value": "CLAUDE_2_0", "label": "Claude-2.0"}, 51 | ], 52 | "QWEN": [ 53 | {"value": "QWEN_32b_preview", "label": "Qwen-32b-preview"}, 54 | {"value": "QWEN_MAX", "label": "Qwen-max"}, 55 | {"value": "QWEN_PLUS", "label": "Qwen-plus"}, 56 | {"value": "QWEN_TURBO", "label": "Qwen-turbo"}, 57 | {"value": "QWEN_LONG", "label": "Qwen-long"}, 58 | {"value": "QWEN_VL_MAX", "label": "Qwen-vl-max"}, 59 | {"value": "QWEN_MATH_PLUS", "label": "Qwen-math-plus"}, 60 | {"value": "QWEN_MATH_TURBO", "label": "Qwen-math-turbo"}, 61 | {"value": "QWEN_CODER_TURBO", "label": "Qwen-coder-turbo"}, 62 | {"value": "QWEN2_5_CODER_32B_INSTRUCT", "label": "Qwen2.5-coder-32b-instruct"}, 63 | {"value": "QWEN2_5_72B_INSTRUCT", "label": "Qwen2.5-72b-instruct"}, 64 | {"value": "QWEN2_5_32B_INSTRUCT", "label": "Qwen2.5-32b-instruct"}, 65 | {"value": "QWEN2_5_14B_INSTRUCT", "label": "Qwen2.5-14b-instruct"}, 66 | ], 67 | "DEEPSEEK": [ 68 | {"value": "DEEPSEEK_CHAT", "label": "DeepSeek-chat"}, 69 | {"value": "DEEPSEEK_REASONER", "label": "DeepSeek-reasoner"}, 70 | ], 71 | }, 72 | "languageOptions": [ 73 | {"value": "English", "label": "English"}, 74 | {"value": "Chinese", "label": "中文"}, 75 | {"value": "Japanese", "label": "日本語"}, 76 | {"value": "Korean", "label": "한국어"}, 77 | ], 78 | "approvalHistory": [ 79 | { 80 | "id": 1, 81 | "tool": "File System Access", 82 | "timestamp": "2024-03-20 10:30:45", 83 | "status": "approved", 84 | "risk": "low", 85 | }, 86 | { 87 | "id": 2, 88 | "tool": "Database Write", 89 | "timestamp": "2024-03-20 11:15:22", 90 | "status": "rejected", 91 | "risk": "high", 92 | }, 93 | ], 94 | "pendingApprovals": [ 95 | { 96 | "id": 1, 97 | "tool": "File System Access", 98 | "risk": "high", 99 | "description": "Request to access sensitive file: /data/users.db", 100 | "requestedBy": "AI Agent", 101 | "timestamp": "2024-03-21 15:30:00", 102 | "context": { 103 | "purpose": "Data analysis", 104 | "impact": "High - Involves sensitive user data", 105 | }, 106 | }, 107 | { 108 | "id": 2, 109 | "tool": "External API Call", 110 | "risk": "medium", 111 | "description": "Request to call external API: api.example.com", 112 | "requestedBy": "AI Agent", 113 | "timestamp": "2024-03-21 15:28:00", 114 | "context": { 115 | "purpose": "Data verification", 116 | "impact": "Medium - External service interaction", 117 | }, 118 | }, 119 | ], 120 | "recentActivity": [ 121 | { 122 | "id": 1, 123 | "type": "approval", 124 | "timestamp": "2024-03-21 15:35:00", 125 | "description": "Approved database query request", 126 | }, 127 | { 128 | "id": 2, 129 | "type": "rejection", 130 | "timestamp": "2024-03-21 15:20:00", 131 | "description": "Rejected unauthorized file access attempt", 132 | }, 133 | ], 134 | } 135 | 136 | 137 | # 获取所有选项和数据 138 | @router.get("/api/getOptions", response_model=Dict[str, Any]) 139 | async def get_options(): 140 | return MOCK_DATA 141 | 142 | # 处理聊天请求 143 | @router.post("/api/chat", response_model=Dict[str, Any]) 144 | async def chat(request:ChatRequest,settings: Settings): 145 | print(request) 146 | return {"message": "Hello, this is a mock response."} 147 | -------------------------------------------------------------------------------- /backend/app/api/routes/rag.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, UploadFile, File, HTTPException 2 | from app.models.rag import RAGRequest, RAGResponse 3 | from app.models.chat import ChatRequest 4 | from typing import List, Dict, Any 5 | 6 | router = APIRouter() 7 | 8 | @router.post( 9 | "/rag/process", 10 | response_model=RAGResponse, 11 | summary="Process RAG request", 12 | description="Process the RAG workflow based on parameters provided by the frontend (such as document sources, embedding models, vector storage, etc.)." 13 | ) 14 | async def process_rag(request: RAGRequest): 15 | """Process RAG request""" 16 | # Simulate RAG processing logic 17 | print("Processing RAG:", request) 18 | return {"message": "RAG process completed successfully!"} 19 | 20 | @router.post( 21 | "/rag/upload", 22 | response_model=RAGResponse, 23 | summary="Upload document", 24 | description="Upload documents to support the RAG workflow. Supported file formats include PDF, TXT, DOC, DOCX." 25 | ) 26 | async def upload_documents(file: UploadFile = File(...)): 27 | """Upload document""" 28 | # Simulate document upload logic 29 | print("File uploaded:", file.filename) 30 | return {"message": "Document uploaded successfully!"} 31 | 32 | @router.post("/rag/chat", response_model=Dict[str, Any]) 33 | async def chat(request: ChatRequest, settings: RAGRequest): 34 | print(request) 35 | return {"message": "Hello, this is a mock response."} 36 | -------------------------------------------------------------------------------- /backend/app/api/routes/rolePlaying.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from app.models.chat import ChatRequest 3 | from app.models.settings import Settings 4 | from typing import List, Dict, Any 5 | router = APIRouter() 6 | 7 | 8 | @router.post("/rolePlaying/generate_code") 9 | async def generate_code(request_data: Settings): 10 | return generate_code_fun(request_data) 11 | 12 | def generate_code_fun(code: str) -> str: 13 | code = "" 14 | return code 15 | 16 | 17 | 18 | @router.post("/rolePlaying/chat", response_model=Dict[str, Any]) 19 | async def chat(request:ChatRequest,assistantAgent: Settings,userAgent: Settings): 20 | print(request) 21 | return {"message": "Hello, this is a mock response."} 22 | -------------------------------------------------------------------------------- /backend/app/api/routes/settings.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | 3 | from fastapi import APIRouter, HTTPException 4 | from app.models.settings import Settings, SettingsResponse,CodeGenerateRequest 5 | from pydantic import BaseModel 6 | import re 7 | 8 | router = APIRouter() 9 | 10 | @router.post("/settings/generate_code") 11 | async def generate_code(request_data: CodeGenerateRequest): 12 | module_type = request_data.moduleType 13 | settings = request_data.settings 14 | 15 | def get_module_code(module_type: str, settings: Settings): 16 | if module_type == "Module1": 17 | return generate_module1_code(settings) 18 | elif module_type == "Module2": 19 | return generate_module2_code(settings) 20 | elif module_type == "Module5": 21 | return generate_module5_code(settings) 22 | else: 23 | return "// Module not implemented" 24 | 25 | def generate_module1_code(settings: Settings): 26 | return f""" 27 | from camel.agents import ChatAgent 28 | from camel.configs import ChatGPTConfig 29 | from camel.models import ModelFactory 30 | from camel.types import ModelPlatformType, ModelType 31 | 32 | model = ModelFactory.create( 33 | model_platform=ModelPlatformType.{settings.platformType}, 34 | model_type=ModelType.{settings.modelType}, 35 | model_config_dict=ChatGPTConfig(temperature=0.0).as_dict(), 36 | ) 37 | 38 | agent = ChatAgent(model=model, system_message="{settings.systemMessage}") 39 | response = agent.step("{settings.systemMessage}") 40 | print(response.msgs[0].content) 41 | """ 42 | 43 | def generate_module2_code(settings: Settings): 44 | return f""" 45 | from camel.agents import RolePlaying 46 | from camel.configs import ChatGPTConfig 47 | from camel.models import ModelFactory 48 | 49 | role_playing = RolePlaying( 50 | assistant_role_name="{settings.assistantRole}", 51 | user_role_name="{settings.userRole}", 52 | task_prompt="{settings.taskPrompt}", 53 | output_language="{settings.outputLanguage}", 54 | model_config=ChatGPTConfig(temperature=0.7) 55 | ) 56 | 57 | response = role_playing.start() 58 | print(response.assistant_message) 59 | """ 60 | 61 | def generate_module5_code(settings: Settings): 62 | return f""" 63 | from camel.embeddings import OpenAIEmbedding 64 | from camel.retrievers import AutoRetriever 65 | from camel.storages import QdrantStorage 66 | from camel.models import ModelFactory 67 | 68 | embedding_model = OpenAIEmbedding(model_name="{settings.embeddingModel}") 69 | vector_store = QdrantStorage.create(embedding_model=embedding_model) 70 | 71 | retriever = AutoRetriever( 72 | vector_store=vector_store, 73 | top_k={settings.retrievalParams.get('topK', 3)}, 74 | similarity_threshold={settings.retrievalParams.get('threshold', 0.7)} 75 | ) 76 | 77 | response = retriever.retrieve(query="") 78 | print(response) 79 | """ 80 | 81 | code = get_module_code(module_type, settings) 82 | # 安全检查:避免代码注入 83 | code = sanitize_code(code) 84 | return {"codeExample": code} 85 | 86 | def sanitize_code(code: str) -> str: 87 | # 移除潜在的恶意代码 88 | code = re.sub(r'[\w\s]*import[\s\w]*', '', code, flags=re.IGNORECASE) 89 | code = re.sub(r'eval\([\w\s,\'"]*\)', '', code, flags=re.IGNORECASE) 90 | code = re.sub(r'exec\([\w\s,\'"]*\)', '', code, flags=re.IGNORECASE) 91 | code = re.sub(r'os\.([\w\.]*)', '', code, flags=re.IGNORECASE) 92 | return code 93 | -------------------------------------------------------------------------------- /backend/app/api/routes/workflow.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from app.models.workflow import WorkflowRequest, WorkflowResponse 3 | 4 | router = APIRouter() 5 | 6 | @router.post( 7 | "/workflow/start", 8 | response_model=WorkflowResponse, 9 | summary="Start Workflow", 10 | description="Start the workflow based on the user-configured agents and task definitions." 11 | ) 12 | async def start_workflow(request: WorkflowRequest): 13 | """Start Workflow""" 14 | # Simulate workflow processing logic 15 | print("Starting workflow:", request) 16 | return {"message": "Workflow started successfully!"} 17 | -------------------------------------------------------------------------------- /backend/app/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/backend/app/core/__init__.py -------------------------------------------------------------------------------- /backend/app/core/config.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | import warnings 3 | from typing import Annotated, Any, Literal, Union, Optional 4 | 5 | from pydantic import ( 6 | AnyUrl, 7 | BeforeValidator, 8 | HttpUrl, 9 | computed_field, 10 | model_validator, 11 | ) 12 | from pydantic_settings import BaseSettings, SettingsConfigDict 13 | from typing_extensions import Self 14 | 15 | 16 | def parse_cors(v: Any) -> Union[list[str], str]: 17 | if isinstance(v, str) and not v.startswith("["): 18 | return [i.strip() for i in v.split(",")] 19 | elif isinstance(v, (list, str)): 20 | return v 21 | raise ValueError(v) 22 | 23 | 24 | class Settings(BaseSettings): 25 | model_config = SettingsConfigDict( 26 | env_file=".env", env_ignore_empty=True, extra="ignore" 27 | ) 28 | API_V1_STR: str = "/api/v1" 29 | SECRET_KEY: str = secrets.token_urlsafe(32) 30 | # 60 minutes * 24 hours * 8 days = 8 days 31 | ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 32 | DOMAIN: str = "localhost" 33 | ENVIRONMENT: Literal["local", "staging", "production"] = "local" 34 | 35 | @computed_field # type: ignore[prop-decorator] 36 | @property 37 | def server_host(self) -> str: 38 | # Use HTTPS for anything other than local development 39 | if self.ENVIRONMENT == "local": 40 | return f"http://{self.DOMAIN}" 41 | return f"https://{self.DOMAIN}" 42 | 43 | BACKEND_CORS_ORIGINS: Annotated[ 44 | Union[list[AnyUrl], str], BeforeValidator(parse_cors) 45 | ] = [] 46 | 47 | PROJECT_NAME: str = "CAMEL Web App" 48 | SENTRY_DSN: HttpUrl | None = None 49 | 50 | def _check_default_secret(self, var_name: str, value: Optional[str]) -> None: 51 | if value == "changethis": 52 | message = ( 53 | f'The value of {var_name} is "changethis", ' 54 | "for security, please change it, at least for deployments." 55 | ) 56 | if self.ENVIRONMENT == "local": 57 | warnings.warn(message, stacklevel=1) 58 | else: 59 | raise ValueError(message) 60 | 61 | @model_validator(mode="after") 62 | def _enforce_non_default_secrets(self) -> Self: 63 | self._check_default_secret("SECRET_KEY", self.SECRET_KEY) 64 | 65 | return self 66 | 67 | 68 | settings = Settings() # type: ignore 69 | -------------------------------------------------------------------------------- /backend/app/main.py: -------------------------------------------------------------------------------- 1 | import sentry_sdk 2 | from fastapi import FastAPI 3 | from fastapi.routing import APIRoute 4 | from starlette.middleware.cors import CORSMiddleware 5 | 6 | from app.api.main import api_router 7 | from app.core.config import settings 8 | 9 | 10 | def custom_generate_unique_id(route: APIRoute) -> str: 11 | return f"{route.tags[0]}-{route.name}" 12 | 13 | 14 | if settings.SENTRY_DSN and settings.ENVIRONMENT != "local": 15 | sentry_sdk.init(dsn=str(settings.SENTRY_DSN), enable_tracing=True) 16 | 17 | app = FastAPI( 18 | title=settings.PROJECT_NAME, 19 | openapi_url=f"{settings.API_V1_STR}/openapi.json", 20 | generate_unique_id_function=custom_generate_unique_id, 21 | ) 22 | 23 | # Set all CORS enabled origins 24 | if settings.BACKEND_CORS_ORIGINS: 25 | app.add_middleware( 26 | CORSMiddleware, 27 | allow_origins=[ 28 | str(origin).strip("/") for origin in settings.BACKEND_CORS_ORIGINS 29 | ], 30 | allow_credentials=True, 31 | allow_methods=["*"], 32 | allow_headers=["*"], 33 | ) 34 | 35 | app.include_router(api_router, prefix=settings.API_V1_STR) 36 | -------------------------------------------------------------------------------- /backend/app/models/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [] 2 | -------------------------------------------------------------------------------- /backend/app/models/approval.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | class ApprovalRequest(BaseModel): 4 | """ 5 | 审批请求模型,包含审批请求的ID和操作类型。 6 | """ 7 | id: int = Field(..., description="The ID of the approval request.") 8 | action: str = Field(..., description="The action to be taken on the request (e.g., 'approve', 'reject').") 9 | 10 | class ApprovalResponse(BaseModel): 11 | """ 12 | 审批响应模型,返回审批结果。 13 | """ 14 | message: str = Field(..., description="The result message of the approval process.") 15 | -------------------------------------------------------------------------------- /backend/app/models/chat.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import Optional 3 | 4 | 5 | class ChatRequest(BaseModel): 6 | user_message: str 7 | system_message: str 8 | platform_type: str = "OPENAI" 9 | model_type: str = "GPT_4" 10 | base_url: Optional[str] = None 11 | api_key: str 12 | temperature: float = 0.7 13 | max_tokens: int = 2000 14 | -------------------------------------------------------------------------------- /backend/app/models/human.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | class Human(BaseModel): 4 | apiKey: str 5 | timeout: str 6 | level: str 7 | email: bool 8 | browser: bool 9 | slack: bool 10 | fileSystemAccess: str 11 | externalAPICall: str 12 | -------------------------------------------------------------------------------- /backend/app/models/rag.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | class RAGRequest(BaseModel): 4 | """ 5 | RAG请求模型,包含处理RAG流程所需的所有参数。 6 | """ 7 | RAGType: str = Field(..., description="The type of RAG process to be performed (e.g., 'embedding', 'graph').") 8 | fileUrl: str = Field(..., description="The URL of the documents to be processed.") 9 | documentSource: str = Field(..., description="The source of the documents to be processed (e.g., file path or URL).") 10 | embeddingModel: str = Field(..., description="The embedding model used for document processing (e.g., text-embedding-3-small).") 11 | vectorStore: str = Field(..., description="The vector store used for storing document embeddings (e.g., FAISS, Qdrant).") 12 | retrievalParams: dict = Field(..., description="Parameters for document retrieval (e.g., topK, threshold).") 13 | graphDbConfig: dict = Field(..., description="Configuration for the graph database (e.g., Neo4j URI, username, password).") 14 | topNumber: int = Field(..., description="The number of documents to retrieve.") 15 | class RAGResponse(BaseModel): 16 | """ 17 | RAG响应模型,返回处理结果。 18 | """ 19 | message: str = Field(..., description="The result message of the RAG process.") 20 | -------------------------------------------------------------------------------- /backend/app/models/settings.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | class Settings(BaseModel): 6 | """ 7 | 用户设置模型,包含前端界面中可配置的各项参数。 8 | """ 9 | availableToolkits: list = Field(default=[], description="List of available toolkits for the user to choose from.") 10 | platformType: str = Field(..., description="The platform type selected by the user (e.g., OPENAI, MISTRALAI).") 11 | modelType: str = Field(..., description="The model type selected by the user (e.g., GPT_4, MISTRAL_LARGE_2).") 12 | apiKey: str = Field(..., description="The API key for authentication with the selected platform.") 13 | baseURL: str = Field(..., description="The base URL for the selected platform.") 14 | yourApiKey: str = Field(default="", description="The API key for authentication with the selected platform.") 15 | yourBaseURL: str = Field(default="", description="The base URL for the selected platform.") 16 | yourPlatformType: str = Field(default="", description="The platform type selected by the user (e.g., OPENAI, MISTRALAI).") 17 | yourModelType: str = Field(default="", description="The model type selected by the user (e.g., GPT_4, MISTRAL_LARGE_2).") 18 | systemMessage: str = Field(..., description="The system message that defines the behavior of the AI agent.") 19 | outputLanguage: str = Field(..., description="The output language for the AI agent's responses.") 20 | temperature: float = Field(..., description="The temperature parameter for the AI agent.") 21 | agents: list = Field(default=[], description="List of agents configured by the user.") 22 | max_tokens: int = Field(..., description="The maximum number of tokens for the AI agent's responses.") 23 | pendingApprovals: list = Field(default=[], description="List of pending approval requests.") 24 | recentActivity: list = Field(default=[], description="List of recent activity logs.") 25 | retrievalParams: Dict 26 | 27 | class SettingsResponse(BaseModel): 28 | """ 29 | 设置响应模型,返回保存的设置数据。 30 | """ 31 | data: Settings 32 | 33 | class CodeGenerateRequest(BaseModel): 34 | moduleType: str 35 | settings: Settings 36 | 37 | -------------------------------------------------------------------------------- /backend/app/models/workflow.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | class WorkflowRequest(BaseModel): 4 | """ 5 | 工作流请求模型,包含启动工作流所需的所有参数。 6 | """ 7 | agents: list = Field(..., description="List of agents configured for the workflow.") 8 | taskDefinition: str = Field(..., description="The definition of the task to be executed by the workflow.") 9 | 10 | class WorkflowResponse(BaseModel): 11 | """ 12 | 工作流响应模型,返回启动结果。 13 | """ 14 | message: str = Field(..., description="The result message of the workflow process.") 15 | -------------------------------------------------------------------------------- /backend/app/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/backend/app/tests/__init__.py -------------------------------------------------------------------------------- /backend/app/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Generator 2 | 3 | import pytest 4 | from fastapi.testclient import TestClient 5 | from app.main import app 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def client() -> Generator[TestClient, None, None]: 10 | with TestClient(app) as c: 11 | yield c 12 | -------------------------------------------------------------------------------- /backend/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "camel-web-app" 3 | version = "0.1.0" 4 | description = "A web application for CAMEL" 5 | readme = "README.md" 6 | package-mode = false 7 | 8 | [tool.poetry.dependencies] 9 | python = ">=3.10, <3.13" 10 | uvicorn = {extras = ["standard"], version = "^0.24.0.post1"} 11 | fastapi = {extras = ["standard"], version = "^0.115.11"} 12 | python-multipart = "<1.0.0,>=0.0.7" 13 | tenacity = "^8.2.3" 14 | pydantic = ">2.0" 15 | 16 | gunicorn = "^22.0.0" 17 | # Pin bcrypt until passlib supports the latest 18 | pydantic-settings = "^2.2.1" 19 | sentry-sdk = {extras = ["fastapi"], version = "^1.40.6"} 20 | camel-ai = {extras = ["all"], version = "^0.2.38"} 21 | 22 | [tool.poetry.group.dev.dependencies] 23 | pytest = "^7.4.3" 24 | mypy = "^1.8.0" 25 | ruff = "^0.2.2" 26 | pre-commit = "^3.6.2" 27 | types-passlib = "^1.7.7.20240106" 28 | coverage = "^7.4.3" 29 | 30 | [build-system] 31 | requires = ["poetry>=0.12"] 32 | build-backend = "poetry.masonry.api" 33 | 34 | [tool.mypy] 35 | strict = true 36 | exclude = ["venv", ".venv", "alembic"] 37 | 38 | [tool.ruff] 39 | target-version = "py310" 40 | exclude = ["alembic"] 41 | 42 | [tool.ruff.lint] 43 | select = [ 44 | "E", # pycodestyle errors 45 | "W", # pycodestyle warnings 46 | "F", # pyflakes 47 | "I", # isort 48 | "B", # flake8-bugbear 49 | "C4", # flake8-comprehensions 50 | "UP", # pyupgrade 51 | "ARG001", # unused arguments in functions 52 | ] 53 | ignore = [ 54 | "E501", # line too long, handled by black 55 | "B008", # do not perform function calls in argument defaults 56 | "W191", # indentation contains tabs 57 | "B904", # Allow raising exceptions without from e, for HTTPException 58 | ] 59 | 60 | [tool.ruff.lint.pyupgrade] 61 | # Preserve types, even if a file imports `from __future__ import annotations`. 62 | keep-runtime-typing = true 63 | -------------------------------------------------------------------------------- /backend/scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | set -x 3 | 4 | ruff check app scripts --fix 5 | ruff format app scripts 6 | -------------------------------------------------------------------------------- /backend/scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | 6 | mypy app 7 | ruff check app 8 | ruff format app --check 9 | -------------------------------------------------------------------------------- /backend/scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | 6 | coverage run --source=app -m pytest 7 | coverage report --show-missing 8 | coverage html --title "${@-coverage}" 9 | -------------------------------------------------------------------------------- /backend/tests-start.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -e 3 | set -x 4 | 5 | bash ./scripts/test.sh "$@" 6 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | proxy: 4 | image: traefik:3.0 5 | volumes: 6 | - /var/run/docker.sock:/var/run/docker.sock 7 | ports: 8 | - "80:80" 9 | - "8090:8080" 10 | # Duplicate the command from docker-compose.yml to add --api.insecure=true 11 | command: 12 | # Enable Docker in Traefik, so that it reads labels from Docker services 13 | - --providers.docker 14 | # Add a constraint to only use services with the label for this stack 15 | - --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`) 16 | # Do not expose all Docker services, only the ones explicitly exposed 17 | - --providers.docker.exposedbydefault=false 18 | # Create an entrypoint "http" listening on port 80 19 | - --entrypoints.http.address=:80 20 | # Create an entrypoint "https" listening on port 443 21 | - --entrypoints.https.address=:443 22 | # Enable the access log, with HTTP requests 23 | - --accesslog 24 | # Enable the Traefik log, for configurations and errors 25 | - --log 26 | # Enable debug logging for local development 27 | - --log.level=DEBUG 28 | # Enable the Dashboard and API 29 | - --api 30 | # Enable the Dashboard and API in insecure mode for local development 31 | - --api.insecure=true 32 | labels: 33 | # Enable Traefik for this service, to make it available in the public network 34 | - traefik.enable=true 35 | - traefik.constraint-label=traefik-public 36 | # Dummy https-redirect middleware that doesn't really redirect, only to 37 | # allow running it locally 38 | - traefik.http.middlewares.https-redirect.contenttype.autodetect=false 39 | networks: 40 | - traefik-public 41 | - default 42 | 43 | db: 44 | restart: "no" 45 | ports: 46 | - "5432:5432" 47 | 48 | adminer: 49 | restart: "no" 50 | ports: 51 | - "8080:8080" 52 | 53 | celery-worker: 54 | restart: "no" 55 | volumes: 56 | - ./backend/:/app 57 | 58 | celery-dashboard: 59 | restart: "no" 60 | ports: 61 | - "5556:5555" 62 | 63 | backend: 64 | restart: "no" 65 | ports: 66 | - "8000:80" 67 | volumes: 68 | - ./backend/:/app 69 | build: 70 | context: ./backend 71 | args: 72 | INSTALL_DEV: ${INSTALL_DEV-true} 73 | # command: sleep infinity # Infinite loop to keep container alive doing nothing 74 | command: /start-reload.sh 75 | 76 | frontend: 77 | restart: "no" 78 | ports: 79 | - "5173:80" 80 | build: 81 | context: ./frontend 82 | args: 83 | - VITE_API_URL=http://localhost:8000 84 | - NODE_ENV=development 85 | 86 | networks: 87 | traefik-public: 88 | # For local dev, don't expect an external Traefik network 89 | external: false 90 | -------------------------------------------------------------------------------- /docker-compose.traefik.yml: -------------------------------------------------------------------------------- 1 | services: 2 | traefik: 3 | image: traefik:3.0 4 | ports: 5 | # Listen on port 80, default for HTTP, necessary to redirect to HTTPS 6 | - 80:80 7 | # Listen on port 443, default for HTTPS 8 | - 443:443 9 | restart: always 10 | labels: 11 | # Enable Traefik for this service, to make it available in the public network 12 | - traefik.enable=true 13 | # Use the traefik-public network (declared below) 14 | - traefik.docker.network=traefik-public 15 | # Define the port inside of the Docker service to use 16 | - traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080 17 | # Make Traefik use this domain (from an environment variable) in HTTP 18 | - traefik.http.routers.traefik-dashboard-http.entrypoints=http 19 | - traefik.http.routers.traefik-dashboard-http.rule=Host(`traefik.${DOMAIN?Variable not set}`) 20 | # traefik-https the actual router using HTTPS 21 | - traefik.http.routers.traefik-dashboard-https.entrypoints=https 22 | - traefik.http.routers.traefik-dashboard-https.rule=Host(`traefik.${DOMAIN?Variable not set}`) 23 | - traefik.http.routers.traefik-dashboard-https.tls=true 24 | # Use the "le" (Let's Encrypt) resolver created below 25 | - traefik.http.routers.traefik-dashboard-https.tls.certresolver=le 26 | # Use the special Traefik service api@internal with the web UI/Dashboard 27 | - traefik.http.routers.traefik-dashboard-https.service=api@internal 28 | # https-redirect middleware to redirect HTTP to HTTPS 29 | - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https 30 | - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true 31 | # traefik-http set up only to use the middleware to redirect to https 32 | - traefik.http.routers.traefik-dashboard-http.middlewares=https-redirect 33 | # admin-auth middleware with HTTP Basic auth 34 | # Using the environment variables USERNAME and HASHED_PASSWORD 35 | - traefik.http.middlewares.admin-auth.basicauth.users=${USERNAME?Variable not set}:${HASHED_PASSWORD?Variable not set} 36 | # Enable HTTP Basic auth, using the middleware created above 37 | - traefik.http.routers.traefik-dashboard-https.middlewares=admin-auth 38 | volumes: 39 | # Add Docker as a mounted volume, so that Traefik can read the labels of other services 40 | - /var/run/docker.sock:/var/run/docker.sock:ro 41 | # Mount the volume to store the certificates 42 | - traefik-public-certificates:/certificates 43 | command: 44 | # Enable Docker in Traefik, so that it reads labels from Docker services 45 | - --providers.docker 46 | # Do not expose all Docker services, only the ones explicitly exposed 47 | - --providers.docker.exposedbydefault=false 48 | # Create an entrypoint "http" listening on port 80 49 | - --entrypoints.http.address=:80 50 | # Create an entrypoint "https" listening on port 443 51 | - --entrypoints.https.address=:443 52 | # Create the certificate resolver "le" for Let's Encrypt, uses the environment variable EMAIL 53 | - --certificatesresolvers.le.acme.email=${EMAIL?Variable not set} 54 | # Store the Let's Encrypt certificates in the mounted volume 55 | - --certificatesresolvers.le.acme.storage=/certificates/acme.json 56 | # Use the TLS Challenge for Let's Encrypt 57 | - --certificatesresolvers.le.acme.tlschallenge=true 58 | # Enable the access log, with HTTP requests 59 | - --accesslog 60 | # Enable the Traefik log, for configurations and errors 61 | - --log 62 | # Enable the Dashboard and API 63 | - --api 64 | networks: 65 | # Use the public network created to be shared between Traefik and 66 | # any other service that needs to be publicly available with HTTPS 67 | - traefik-public 68 | 69 | volumes: 70 | # Create a volume to store the certificates, even if the container is recreated 71 | traefik-public-certificates: 72 | 73 | networks: 74 | # Use the previously created public network "traefik-public", shared with other 75 | # services that need to be publicly available via this Traefik 76 | traefik-public: 77 | external: true 78 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_API_URL=https://api.app.camel-ai.org -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | openapi.json 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | /test-results/ 27 | /playwright-report/ 28 | /blob-report/ 29 | /playwright/.cache/ 30 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 0, "build-stage", based on Node.js, to build and compile the frontend 2 | FROM node:20 AS build-stage 3 | 4 | WORKDIR /app 5 | 6 | COPY package*.json /app/ 7 | 8 | RUN npm install 9 | 10 | COPY ./ /app/ 11 | 12 | ARG VITE_API_URL=${VITE_API_URL} 13 | 14 | RUN npm run build 15 | 16 | 17 | # Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx 18 | FROM nginx:1 19 | 20 | COPY --from=build-stage /app/dist/ /usr/share/nginx/html 21 | 22 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf 23 | COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # FastAPI Project - Frontend 2 | 3 | The frontend is built with [Vite](https://vitejs.dev/), [React](https://reactjs.org/), [TypeScript](https://www.typescriptlang.org/), [TanStack Query](https://tanstack.com/query), [TanStack Router](https://tanstack.com/router) and [Chakra UI](https://chakra-ui.com/). 4 | 5 | ## Frontend development 6 | 7 | Before you begin, ensure that you have either the Node Version Manager (nvm) or Fast Node Manager (fnm) installed on your system. 8 | 9 | * To install fnm follow the [official fnm guide](https://github.com/Schniz/fnm#installation). If you prefer nvm, you can install it using the [official nvm guide](https://github.com/nvm-sh/nvm#installing-and-updating). 10 | 11 | * After installing either nvm or fnm, proceed to the `frontend` directory: 12 | 13 | ```bash 14 | cd frontend 15 | ``` 16 | * If the Node.js version specified in the `.nvmrc` file isn't installed on your system, you can install it using the appropriate command: 17 | 18 | ```bash 19 | # If using fnm 20 | fnm install 21 | 22 | # If using nvm 23 | nvm install 24 | ``` 25 | 26 | * Once the installation is complete, switch to the installed version: 27 | 28 | ```bash 29 | # If using fnm 30 | fnm use 31 | 32 | # If using nvm 33 | nvm use 34 | ``` 35 | 36 | * Within the `frontend` directory, install the necessary NPM packages: 37 | 38 | ```bash 39 | npm install 40 | ``` 41 | 42 | * And start the live server with the following `npm` script: 43 | 44 | ```bash 45 | npm run dev 46 | ``` 47 | 48 | * Then open your browser at http://localhost:5173/. 49 | 50 | Notice that this live server is not running inside Docker, it's for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But building the image at every change will not be as productive as running the local development server with live reload. 51 | 52 | Check the file `package.json` to see other available options. 53 | 54 | ### Removing the frontend 55 | 56 | If you are developing an API-only app and want to remove the frontend, you can do it easily: 57 | 58 | * Remove the `./frontend` directory. 59 | 60 | * In the `docker-compose.yml` file, remove the whole service / section `frontend`. 61 | 62 | * In the `docker-compose.override.yml` file, remove the whole service / section `frontend`. 63 | 64 | Done, you have a frontend-less (api-only) app. 🤓 65 | 66 | --- 67 | 68 | If you want, you can also remove the `FRONTEND` environment variables from: 69 | 70 | * `.env` 71 | * `./scripts/*.sh` 72 | 73 | But it would be only to clean them up, leaving them won't really have any effect either way. 74 | 75 | ## Generate Client 76 | 77 | * Start the Docker Compose stack. 78 | 79 | * Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` at the root of the `frontend` directory. 80 | 81 | * To simplify the names in the generated frontend client code, modify the `openapi.json` file by running the following script: 82 | 83 | ```bash 84 | node modify-openapi-operationids.js 85 | ``` 86 | 87 | * To generate the frontend client, run: 88 | 89 | ```bash 90 | npm run generate-client 91 | ``` 92 | 93 | * Commit the changes. 94 | 95 | Notice that everytime the backend changes (changing the OpenAPI schema), you should follow these steps again to update the frontend client. 96 | 97 | ## Using a Remote API 98 | 99 | If you want to use a remote API, you can set the environment variable `VITE_API_URL` to the URL of the remote API. For example, you can set it in the `frontend/.env` file: 100 | 101 | ```env 102 | VITE_API_URL=https://my-remote-api.example.com 103 | ``` 104 | 105 | Then, when you run the frontend, it will use that URL as the base URL for the API. 106 | 107 | ## Code Structure 108 | 109 | The frontend code is structured as follows: 110 | 111 | * `frontend/src` - The main frontend code. 112 | * `frontend/src/assets` - Static assets. 113 | * `frontend/src/client` - The generated OpenAPI client. 114 | * `frontend/src/components` - The different components of the frontend. 115 | * `frontend/src/hooks` - Custom hooks. 116 | * `frontend/src/routes` - The different routes of the frontend which include the pages. 117 | * `theme.tsx` - The Chakra UI custom theme. 118 | 119 | ## End-to-End Testing with Playwright 120 | 121 | The frontend includes initial end-to-end tests using Playwright. To run the tests, you need to have the Docker Compose stack running. Start the stack with the following command: 122 | 123 | ```bash 124 | docker compose up -d 125 | ``` 126 | 127 | Then, you can run the tests with the following command: 128 | 129 | ```bash 130 | npx playwright test 131 | ``` 132 | 133 | You can also run your tests in UI mode to see the browser and interact with it running: 134 | 135 | ```bash 136 | npx playwright test --ui 137 | ``` 138 | 139 | To stop and remove the Docker Compose stack and clean the data created in tests, use the following command: 140 | 141 | ```bash 142 | docker compose down -v 143 | ``` 144 | 145 | To update the tests, navigate to the tests directory and modify the existing test files or add new ones as needed. 146 | 147 | For more information on writing and running Playwright tests, refer to the official [Playwright documentation](https://playwright.dev/docs/intro). -------------------------------------------------------------------------------- /frontend/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.6.1/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "files": { 7 | "ignore": [ 8 | "node_modules", 9 | "src/client/", 10 | "src/routeTree.gen.ts", 11 | "playwright.config.ts", 12 | "playwright-report" 13 | ] 14 | }, 15 | "linter": { 16 | "enabled": true, 17 | "rules": { 18 | "recommended": true, 19 | "suspicious": { 20 | "noExplicitAny": "off", 21 | "noArrayIndexKey": "off" 22 | }, 23 | "style": { 24 | "noNonNullAssertion": "off" 25 | } 26 | } 27 | }, 28 | "formatter": { 29 | "indentStyle": "space" 30 | }, 31 | "javascript": { 32 | "formatter": { 33 | "quoteStyle": "double", 34 | "semicolons": "asNeeded" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Camel Webapp 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/modify-openapi-operationids.js: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs" 2 | 3 | async function modifyOpenAPIFile(filePath) { 4 | try { 5 | const data = await fs.promises.readFile(filePath) 6 | const openapiContent = JSON.parse(data) 7 | 8 | const paths = openapiContent.paths 9 | for (const pathKey of Object.keys(paths)) { 10 | const pathData = paths[pathKey] 11 | for (const method of Object.keys(pathData)) { 12 | const operation = pathData[method] 13 | if (operation.tags && operation.tags.length > 0) { 14 | const tag = operation.tags[0] 15 | const operationId = operation.operationId 16 | const toRemove = `${tag}-` 17 | if (operationId.startsWith(toRemove)) { 18 | const newOperationId = operationId.substring(toRemove.length) 19 | operation.operationId = newOperationId 20 | } 21 | } 22 | } 23 | } 24 | 25 | await fs.promises.writeFile( 26 | filePath, 27 | JSON.stringify(openapiContent, null, 2), 28 | ) 29 | console.log("File successfully modified") 30 | } catch (err) { 31 | console.error("Error:", err) 32 | } 33 | } 34 | 35 | const filePath = "./openapi.json" 36 | modifyOpenAPIFile(filePath) 37 | -------------------------------------------------------------------------------- /frontend/nginx-backend-not-found.conf: -------------------------------------------------------------------------------- 1 | location /api { 2 | return 404; 3 | } 4 | location /docs { 5 | return 404; 6 | } 7 | location /redoc { 8 | return 404; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location / { 5 | root /usr/share/nginx/html; 6 | index index.html index.htm; 7 | try_files $uri /index.html =404; 8 | } 9 | 10 | include /etc/nginx/extra-conf.d/*.conf; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", 10 | "preview": "vite preview", 11 | "generate-client": "openapi-ts --input http://localhost.data.eigent.ai/api/v1/openapi.json --output ./src/client --client axios --exportSchemas true && biome format --write ./src/client" 12 | }, 13 | "dependencies": { 14 | "@radix-ui/react-avatar": "^1.1.3", 15 | "@radix-ui/react-checkbox": "^1.1.4", 16 | "@radix-ui/react-collapsible": "^1.1.3", 17 | "@radix-ui/react-dialog": "^1.1.6", 18 | "@radix-ui/react-dropdown-menu": "^2.1.6", 19 | "@radix-ui/react-label": "^2.1.2", 20 | "@radix-ui/react-select": "^2.1.6", 21 | "@radix-ui/react-separator": "^1.1.2", 22 | "@radix-ui/react-slider": "^1.2.3", 23 | "@radix-ui/react-slot": "^1.1.2", 24 | "@radix-ui/react-switch": "^1.1.3", 25 | "@radix-ui/react-tabs": "^1.1.3", 26 | "@radix-ui/react-tooltip": "^1.1.8", 27 | "@tabler/icons-react": "^3.31.0", 28 | "@tailwindcss/vite": "^4.0.17", 29 | "@tanstack/react-query": "^5.28.14", 30 | "@tanstack/react-query-devtools": "^5.28.14", 31 | "@tanstack/react-router": "^1.19.1", 32 | "@types/react-syntax-highlighter": "^15.5.13", 33 | "axios": "1.6.2", 34 | "class-variance-authority": "^0.7.1", 35 | "clsx": "^2.1.1", 36 | "cmdk": "^1.1.1", 37 | "form-data": "4.0.0", 38 | "framer-motion": "^12.6.2", 39 | "lucide-react": "^0.484.0", 40 | "motion": "^12.6.2", 41 | "react": "^18.2.0", 42 | "react-dom": "^18.2.0", 43 | "react-dropzone": "^14.3.8", 44 | "react-error-boundary": "^4.0.13", 45 | "react-hook-form": "7.49.3", 46 | "react-icons": "5.0.1", 47 | "react-resizable-panels": "^2.1.7", 48 | "react-syntax-highlighter": "^15.6.1", 49 | "tailwind-merge": "^3.0.2", 50 | "tailwindcss-animate": "^1.0.7", 51 | "tw-animate-css": "^1.2.5" 52 | }, 53 | "devDependencies": { 54 | "@biomejs/biome": "1.6.1", 55 | "@hey-api/openapi-ts": "^0.34.1", 56 | "@playwright/test": "^1.45.2", 57 | "@shadcn/ui": "^0.0.4", 58 | "@tailwindcss/forms": "^0.5.10", 59 | "@tanstack/router-devtools": "^1.19.1", 60 | "@tanstack/router-vite-plugin": "^1.19.0", 61 | "@types/node": "^20.17.28", 62 | "@types/react": "^18.2.37", 63 | "@types/react-dom": "^18.2.15", 64 | "@vitejs/plugin-react-swc": "^3.5.0", 65 | "autoprefixer": "^10.4.21", 66 | "dotenv": "^16.4.5", 67 | "postcss": "^8.5.3", 68 | "postcss-import": "^16.1.0", 69 | "postcss-preset-env": "^10.1.5", 70 | "tailwindcss": "^4.0.17", 71 | "typescript": "^5.2.2", 72 | "vite": "^5.0.13" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /frontend/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | 10 | /** 11 | * See https://playwright.dev/docs/test-configuration. 12 | */ 13 | export default defineConfig({ 14 | testDir: './tests', 15 | /* Run tests in files in parallel */ 16 | fullyParallel: true, 17 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 18 | forbidOnly: !!process.env.CI, 19 | /* Retry on CI only */ 20 | retries: process.env.CI ? 2 : 0, 21 | /* Opt out of parallel tests on CI. */ 22 | workers: process.env.CI ? 1 : undefined, 23 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 24 | reporter: 'html', 25 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 26 | use: { 27 | /* Base URL to use in actions like `await page.goto('/')`. */ 28 | baseURL: 'http://localhost:5173', 29 | 30 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 31 | trace: 'on-first-retry', 32 | }, 33 | 34 | /* Configure projects for major browsers */ 35 | projects: [ 36 | { name: 'setup', testMatch: /.*\.setup\.ts/ }, 37 | 38 | { 39 | name: 'chromium', 40 | use: { 41 | ...devices['Desktop Chrome'], 42 | storageState: 'playwright/.auth/user.json', 43 | }, 44 | dependencies: ['setup'], 45 | }, 46 | 47 | // { 48 | // name: 'firefox', 49 | // use: { 50 | // ...devices['Desktop Firefox'], 51 | // storageState: 'playwright/.auth/user.json', 52 | // }, 53 | // dependencies: ['setup'], 54 | // }, 55 | 56 | // { 57 | // name: 'webkit', 58 | // use: { 59 | // ...devices['Desktop Safari'], 60 | // storageState: 'playwright/.auth/user.json', 61 | // }, 62 | // dependencies: ['setup'], 63 | // }, 64 | 65 | /* Test against mobile viewports. */ 66 | // { 67 | // name: 'Mobile Chrome', 68 | // use: { ...devices['Pixel 5'] }, 69 | // }, 70 | // { 71 | // name: 'Mobile Safari', 72 | // use: { ...devices['iPhone 12'] }, 73 | // }, 74 | 75 | /* Test against branded browsers. */ 76 | // { 77 | // name: 'Microsoft Edge', 78 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 79 | // }, 80 | // { 81 | // name: 'Google Chrome', 82 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 83 | // }, 84 | ], 85 | 86 | /* Run your local dev server before starting the tests */ 87 | webServer: { 88 | command: 'npm run dev', 89 | url: 'http://localhost:5173', 90 | reuseExistingServer: !process.env.CI, 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /frontend/playwright/.auth/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "cookies": [], 3 | "origins": [] 4 | } -------------------------------------------------------------------------------- /frontend/public/assets/images/camel-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 21 | 25 | 32 | 36 | 43 | 51 | 58 | 63 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /frontend/public/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/frontend/public/assets/images/favicon.png -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | /* max-width: 1280px; */ 3 | /* margin: 0 auto; */ 4 | /* padding: 2rem; */ 5 | /* text-align: center; */ 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import AIPlayground from './AIPlayground' 3 | import './AIPlayground.css' 4 | import './App.css' 5 | 6 | function App() { 7 | const [count, setCount] = useState(0) 8 | return ( 9 | 10 |
11 | 12 |
13 | ); 14 | } 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /frontend/src/Canvas.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface CanvasProps { 4 | children: React.ReactNode; 5 | } 6 | 7 | const Canvas: React.FC = ({ children }) => { 8 | return ( 9 |
10 | {/* 网格背景 */} 11 |
12 | {/* 内容区域 */} 13 |
14 | {children} 15 |
16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Canvas; -------------------------------------------------------------------------------- /frontend/src/assets/fonts/OpenSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/frontend/src/assets/fonts/OpenSans.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Palatino.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camel-ai/camel_web_app/896153609faf66b6e3f8040ca109b4993ac0783c/frontend/src/assets/fonts/Palatino.ttf -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/canvas.css: -------------------------------------------------------------------------------- 1 | .canvas-container { 2 | width: 100%; 3 | height: 100vh; 4 | background: #f5f5f5; 5 | position: relative; 6 | overflow: hidden; 7 | } 8 | 9 | .canvas-grid { 10 | width: 100%; 11 | height: 100%; 12 | background-image: radial-gradient(circle, #e0e0e0 1px, transparent 1px); 13 | background-size: 20px 20px; 14 | } 15 | 16 | .canvas-content { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | } -------------------------------------------------------------------------------- /frontend/src/components/app-sidebar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { 3 | AudioWaveform, 4 | BookOpen, 5 | Bot, 6 | Command, 7 | Frame, 8 | GalleryVerticalEnd, 9 | Map, 10 | PieChart, 11 | Settings2, 12 | SquareTerminal, 13 | Github, 14 | BookText, 15 | } from "lucide-react" 16 | 17 | import { NavMain } from "@/components/nav-main" 18 | import { NavProjects } from "@/components/nav-projects" 19 | import { NavUser } from "@/components/nav-user" 20 | import { TeamSwitcher } from "@/components/team-switcher" 21 | import { 22 | Sidebar, 23 | SidebarContent, 24 | SidebarFooter, 25 | SidebarHeader, 26 | SidebarRail, 27 | } from "@/components/ui/sidebar" 28 | 29 | // This is sample data. 30 | const data = { 31 | user: { 32 | name: "camel", 33 | email: "m@example.com", 34 | avatar: "/avatars/shadcn.jpg", 35 | }, 36 | teams: [ 37 | { 38 | name: "Acme Inc", 39 | logo: GalleryVerticalEnd, 40 | plan: "Enterprise", 41 | }, 42 | { 43 | name: "Acme Corp.", 44 | logo: AudioWaveform, 45 | plan: "Startup", 46 | }, 47 | { 48 | name: "Evil Corp.", 49 | logo: Command, 50 | plan: "Free", 51 | }, 52 | ], 53 | navMain: [ 54 | { 55 | title: "Playground", 56 | url: "#", 57 | icon: SquareTerminal, 58 | isActive: true, 59 | items: [ 60 | { 61 | title: "Create Your First Agent", 62 | url: "#", 63 | }, 64 | { 65 | title: "Role Playing Session", 66 | url: "#", 67 | }, 68 | { 69 | title: "Workforce Session", 70 | url: "#", 71 | }, 72 | { 73 | title: "Synthetic Data", 74 | url: "#", 75 | }, 76 | { 77 | title: "RAG&Graph RAG", 78 | url: "#", 79 | }, 80 | { 81 | title: "Human-in-the-loop", 82 | url: "#", 83 | }, 84 | ], 85 | }, 86 | // { 87 | // title: "Models", 88 | // url: "#", 89 | // icon: Bot, 90 | // items: [ 91 | // { 92 | // title: "Genesis", 93 | // url: "#", 94 | // }, 95 | // { 96 | // title: "Explorer", 97 | // url: "#", 98 | // }, 99 | // { 100 | // title: "Quantum", 101 | // url: "#", 102 | // }, 103 | // ], 104 | // }, 105 | // { 106 | // title: "Documentation", 107 | // url: "#", 108 | // icon: BookOpen, 109 | // items: [ 110 | // { 111 | // title: "Introduction", 112 | // url: "#", 113 | // }, 114 | // { 115 | // title: "Get Started", 116 | // url: "#", 117 | // }, 118 | // { 119 | // title: "Tutorials", 120 | // url: "#", 121 | // }, 122 | // { 123 | // title: "Changelog", 124 | // url: "#", 125 | // }, 126 | // ], 127 | // }, 128 | // { 129 | // title: "Settings", 130 | // url: "#", 131 | // icon: Settings2, 132 | // items: [ 133 | // { 134 | // title: "General", 135 | // url: "#", 136 | // }, 137 | // { 138 | // title: "Team", 139 | // url: "#", 140 | // }, 141 | // { 142 | // title: "Billing", 143 | // url: "#", 144 | // }, 145 | // { 146 | // title: "Limits", 147 | // url: "#", 148 | // }, 149 | // ], 150 | // }, 151 | ], 152 | projects: [ 153 | // { 154 | // name: "Design Engineering", 155 | // url: "#", 156 | // icon: Frame, 157 | // }, 158 | // { 159 | // name: "Sales & Marketing", 160 | // url: "#", 161 | // icon: PieChart, 162 | // }, 163 | // { 164 | // name: "Travel", 165 | // url: "#", 166 | // icon: Map, 167 | // }, 168 | ], 169 | } 170 | 171 | export interface AppSidebarProps extends React.ComponentProps { 172 | onModuleChange?: (moduleId: string) => void; 173 | } 174 | 175 | const moduleIdMap = { 176 | "Create Your First Agent": "Module1", 177 | "Role Playing Session": "Module2", 178 | "Workforce Session": "Module3", 179 | "Synthetic Data": "Module4", 180 | "RAG&Graph RAG": "Module5", 181 | "Human-in-the-loop": "Module6" 182 | }; 183 | 184 | export function AppSidebar({ onModuleChange, ...props }: AppSidebarProps) { 185 | const [starCount, setStarCount] = React.useState(0); 186 | 187 | React.useEffect(() => { 188 | fetch('https://api.github.com/repos/camel-ai/camel') 189 | .then(response => response.json()) 190 | .then(data => setStarCount(data.stargazers_count)) 191 | .catch(error => console.error('Error fetching star count:', error)); 192 | }, []); 193 | 194 | const handleItemClick = (title: string) => { 195 | const moduleId = moduleIdMap[title]; 196 | if (moduleId && onModuleChange) { 197 | onModuleChange(moduleId); 198 | } 199 | }; 200 | 201 | const navMainWithHandlers = data.navMain.map(section => ({ 202 | ...section, 203 | items: section.items?.map(item => ({ 204 | ...item, 205 | onClick: () => handleItemClick(item.title) 206 | })) 207 | })); 208 | 209 | return ( 210 | 211 | 212 |
213 | Camel Logo 214 |
215 | {/* */} 216 |
217 | 218 | 219 | 220 | 221 | 222 | 242 | {/* */} 243 | 244 | 245 |
246 | ) 247 | } 248 | -------------------------------------------------------------------------------- /frontend/src/components/nav-main.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ChevronRight, type LucideIcon } from "lucide-react" 4 | 5 | import { 6 | Collapsible, 7 | CollapsibleContent, 8 | CollapsibleTrigger, 9 | } from "@/components/ui/collapsible" 10 | import { 11 | SidebarGroup, 12 | SidebarGroupLabel, 13 | SidebarMenu, 14 | SidebarMenuButton, 15 | SidebarMenuItem, 16 | SidebarMenuSub, 17 | SidebarMenuSubButton, 18 | SidebarMenuSubItem, 19 | } from "@/components/ui/sidebar" 20 | 21 | export function NavMain({ 22 | items, 23 | }: { 24 | items: { 25 | title: string 26 | url: string 27 | icon?: LucideIcon 28 | isActive?: boolean 29 | items?: { 30 | title: string 31 | url: string 32 | onClick?: () => void 33 | }[] 34 | }[] 35 | }) { 36 | return ( 37 | 38 | CAMEL 39 | 40 | {items.map((item) => ( 41 | 47 | 48 | 49 | 50 | {item.icon && } 51 | {item.title} 52 | 53 | 54 | 55 | 56 | 57 | {item.items?.map((subItem) => ( 58 | 59 | 63 | {subItem.title} 64 | 65 | 66 | ))} 67 | 68 | 69 | 70 | 71 | ))} 72 | 73 | 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/components/nav-projects.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Folder, 3 | Forward, 4 | MoreHorizontal, 5 | Trash2, 6 | type LucideIcon, 7 | } from "lucide-react" 8 | 9 | import { 10 | DropdownMenu, 11 | DropdownMenuContent, 12 | DropdownMenuItem, 13 | DropdownMenuSeparator, 14 | DropdownMenuTrigger, 15 | } from "@/components/ui/dropdown-menu" 16 | import { 17 | SidebarGroup, 18 | SidebarGroupLabel, 19 | SidebarMenu, 20 | SidebarMenuAction, 21 | SidebarMenuButton, 22 | SidebarMenuItem, 23 | useSidebar, 24 | } from "@/components/ui/sidebar" 25 | 26 | export function NavProjects({ 27 | projects, 28 | }: { 29 | projects: { 30 | name: string 31 | url: string 32 | icon: LucideIcon 33 | }[] 34 | }) { 35 | const { isMobile } = useSidebar() 36 | 37 | return ( 38 | 39 | {/* Projects */} 40 | 41 | {projects.map((item) => ( 42 | 43 | 44 | 45 | 46 | {item.name} 47 | 48 | 49 | 50 | 51 | 52 | 53 | More 54 | 55 | 56 | 61 | 62 | 63 | View Project 64 | 65 | 66 | 67 | Share Project 68 | 69 | 70 | 71 | 72 | Delete Project 73 | 74 | 75 | 76 | 77 | ))} 78 | {/* 79 | 80 | 81 | More 82 | 83 | */} 84 | 85 | 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /frontend/src/components/nav-user.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | BadgeCheck, 5 | Bell, 6 | ChevronsUpDown, 7 | CreditCard, 8 | LogOut, 9 | Sparkles, 10 | } from "lucide-react" 11 | 12 | import { 13 | Avatar, 14 | AvatarFallback, 15 | AvatarImage, 16 | } from "@/components/ui/avatar" 17 | import { 18 | DropdownMenu, 19 | DropdownMenuContent, 20 | DropdownMenuGroup, 21 | DropdownMenuItem, 22 | DropdownMenuLabel, 23 | DropdownMenuSeparator, 24 | DropdownMenuTrigger, 25 | } from "@/components/ui/dropdown-menu" 26 | import { 27 | SidebarMenu, 28 | SidebarMenuButton, 29 | SidebarMenuItem, 30 | useSidebar, 31 | } from "@/components/ui/sidebar" 32 | 33 | export function NavUser({ 34 | user, 35 | }: { 36 | user: { 37 | name: string 38 | email: string 39 | avatar: string 40 | } 41 | }) { 42 | const { isMobile } = useSidebar() 43 | 44 | return ( 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | CN 56 | 57 |
58 | {user.name} 59 | {user.email} 60 |
61 | 62 |
63 |
64 | 70 | 71 |
72 | 73 | 74 | CN 75 | 76 |
77 | {user.name} 78 | {user.email} 79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 | Upgrade to Pro 87 | 88 | 89 | 90 | 91 | 92 | 93 | Account 94 | 95 | 96 | 97 | Billing 98 | 99 | 100 | 101 | Notifications 102 | 103 | 104 | 105 | 106 | 107 | Log out 108 | 109 |
110 |
111 |
112 |
113 | ) 114 | } 115 | -------------------------------------------------------------------------------- /frontend/src/components/team-switcher.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { ChevronsUpDown, Plus } from "lucide-react" 3 | 4 | import { 5 | DropdownMenu, 6 | DropdownMenuContent, 7 | DropdownMenuItem, 8 | DropdownMenuLabel, 9 | DropdownMenuSeparator, 10 | DropdownMenuShortcut, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu" 13 | import { 14 | SidebarMenu, 15 | SidebarMenuButton, 16 | SidebarMenuItem, 17 | useSidebar, 18 | } from "@/components/ui/sidebar" 19 | 20 | export function TeamSwitcher({ 21 | teams, 22 | }: { 23 | teams: { 24 | name: string 25 | logo: React.ElementType 26 | plan: string 27 | }[] 28 | }) { 29 | const { isMobile } = useSidebar() 30 | const [activeTeam, setActiveTeam] = React.useState(teams[0]) 31 | 32 | if (!activeTeam) { 33 | return null 34 | } 35 | 36 | return ( 37 | 38 | 39 | 40 | 41 | 45 |
46 | 47 |
48 |
49 | {activeTeam.name} 50 | {activeTeam.plan} 51 |
52 | 53 |
54 |
55 | 61 | 62 | Teams 63 | 64 | {teams.map((team, index) => ( 65 | setActiveTeam(team)} 68 | className="gap-2 p-2" 69 | > 70 |
71 | 72 |
73 | {team.name} 74 | ⌘{index + 1} 75 |
76 | ))} 77 | 78 | 79 |
80 | 81 |
82 |
Add team
83 |
84 |
85 |
86 |
87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /frontend/src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/library/utils" 7 | 8 | function Avatar({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | function AvatarImage({ 25 | className, 26 | ...props 27 | }: React.ComponentProps) { 28 | return ( 29 | 34 | ) 35 | } 36 | 37 | function AvatarFallback({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | export { Avatar, AvatarImage, AvatarFallback } 54 | -------------------------------------------------------------------------------- /frontend/src/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { ChevronRight, MoreHorizontal } from "lucide-react" 4 | 5 | import { cn } from "@/library/utils" 6 | 7 | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { 8 | return