diff --git a/.github/workflows/backend_check.yml b/.github/workflows/backend_check.yml index 13fa5abd..ce2b77ea 100644 --- a/.github/workflows/backend_check.yml +++ b/.github/workflows/backend_check.yml @@ -25,52 +25,63 @@ jobs: python -m pip install --upgrade pip poetry python -m poetry lock --no-update python -m poetry install --with lint --no-interaction - working-directory: backend/df_designer + working-directory: backend - name: run black codestyle run: | python -m poetry run black --line-length=120 --check . - working-directory: backend/df_designer + working-directory: backend - name: run flake8 codestyle run: | python -m poetry run flake8 --max-line-length 120 --ignore=E203 . - working-directory: backend/df_designer + working-directory: backend - name: run isort codestyle run: | python -m poetry run isort --line-length=120 --diff . - working-directory: backend/df_designer + working-directory: backend test_backend: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + os: [macOS-latest, ubuntu-latest] + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - name: set up python 3.10 + - name: set up python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: ${{ matrix.python-version }} - name: setup poetry and install dependencies run: | python -m pip install --upgrade pip poetry python -m poetry lock --no-update python -m poetry install --with lint --no-interaction - working-directory: backend/df_designer + working-directory: backend - name: build wheel run: | python -m poetry build - working-directory: backend/df_designer + working-directory: backend - name: Create new project run: | - python -m poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists - working-directory: backend/df_designer + python -m poetry run chatsky.ui init --destination ../ --no-input --overwrite-if-exists + working-directory: backend + + - name: Install chatsky-ui into new project poetry environment + run: | + ../bin/add_ui_to_toml.sh + working-directory: my_project - name: run tests run: | - python -m poetry install - python -m poetry run pytest ../backend/df_designer/app/tests/ --verbose - working-directory: df_designer_project + python -m poetry install --no-root + python -m poetry run pytest ../backend/chatsky_ui/tests/ --verbose + working-directory: my_project diff --git a/.github/workflows/build_and_upload_release.yml b/.github/workflows/build_and_upload_release.yml index 4bf6af25..27898a77 100644 --- a/.github/workflows/build_and_upload_release.yml +++ b/.github/workflows/build_and_upload_release.yml @@ -27,14 +27,14 @@ jobs: run: | bun install bun run build - cp -r ./dist/* ../backend/df_designer/app/static + cp -r ./dist/* ../backend/chatsky_ui/static working-directory: frontend - name: build wheels and test uploading to pypi if: startsWith(github.ref, 'refs/tags/v') != true run: | python -m poetry --build publish --dry-run - working-directory: backend/df_designer + working-directory: backend - name: build wheels and upload to pypi if: startsWith(github.ref, 'refs/tags/v') @@ -42,14 +42,14 @@ jobs: POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} run: | python -m poetry --build publish - working-directory: backend/df_designer + working-directory: backend - name: upload binaries into release if: startsWith(github.ref, 'refs/tags/v') uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: backend/df_designer/dist/* + file: backend/dist/* tag: ${{ github.ref }} overwrite: true file_glob: true diff --git a/.github/workflows/docker_check.yml b/.github/workflows/docker_check.yml index ac7c5795..4276fba6 100644 --- a/.github/workflows/docker_check.yml +++ b/.github/workflows/docker_check.yml @@ -20,14 +20,14 @@ jobs: python -m pip install --upgrade pip poetry python -m poetry lock --no-update python -m poetry install --with lint --no-ansi --no-interaction - working-directory: backend/df_designer + working-directory: backend - name: Create new project - run: python -m poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists - working-directory: backend/df_designer + run: python -m poetry run chatsky.ui init --destination ../ --no-input --overwrite-if-exists + working-directory: backend - name: Build Frontend - run: docker build -f Dockerfile --build-arg PROJECT_DIR=df_designer_project --target=frontend-builder . + run: docker build -f Dockerfile --build-arg PROJECT_DIR=my_project --target=frontend-builder . - name: Build backend & run app - run: docker build -f Dockerfile --build-arg PROJECT_DIR=df_designer_project --target=runtime . + run: docker build -f Dockerfile --build-arg PROJECT_DIR=my_project --target=runtime . diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml index 73c99a35..a0ae24bb 100644 --- a/.github/workflows/e2e_test.yml +++ b/.github/workflows/e2e_test.yml @@ -1,3 +1,4 @@ +#TODO: work with pip to install the package as the end-user would # name: test app # on: @@ -53,7 +54,7 @@ # - name: copy static files # run: | -# cp -r frontend/dist/. backend/df_designer/app/static/ +# cp -r frontend/dist/. backend/chatsky_ui/static/ # - name: set up python 3.10 # uses: actions/setup-python@v5 @@ -65,17 +66,17 @@ # python -m pip install --upgrade pip poetry # python -m poetry lock --no-update # python -m poetry install --with lint --no-interaction -# working-directory: backend/df_designer +# working-directory: backend # - name: build wheel # run: python -m poetry build -# working-directory: backend/df_designer +# working-directory: backend # - name: Archive backend dist # uses: actions/upload-artifact@v4 # with: # name: backend-dist -# path: backend/df_designer/dist +# path: backend/dist # run_app: @@ -89,39 +90,39 @@ # with: # python-version: '3.10' -# - name: setup dflowd poetry and install dependencies +# - name: setup chatsky-ui poetry and install dependencies # run: | # python -m pip install --upgrade pip poetry # python -m poetry lock --no-update # python -m poetry install --with lint --no-interaction -# working-directory: backend/df_designer +# working-directory: backend # - name: Create new project # run: | -# python -m poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists -# working-directory: backend/df_designer +# python -m poetry run chatsky.ui init --destination ../ --no-input --overwrite-if-exists +# working-directory: backend # - name: Create dist directory -# run: mkdir -p backend/df_designer/dist +# run: mkdir -p backend/dist # - name: Download backend dist # uses: actions/download-artifact@v4 # with: # name: backend-dist -# path: backend/df_designer/dist +# path: backend/dist # - name: setup project poetry and install dependencies # run: | # python -m pip install --upgrade pip poetry # python -m poetry lock --no-update # python -m poetry install --no-interaction -# working-directory: df_designer_project +# working-directory: my_project # - name: Run back & front # run: | -# python -m poetry run dflowd run_app & +# python -m poetry run chatsky.ui run_app & # sleep 10 -# working-directory: df_designer_project +# working-directory: my_project # - name: Install bun # run: npm install -g bun diff --git a/.gitignore b/.gitignore index 7752fb9b..4af2bc7a 100644 --- a/.gitignore +++ b/.gitignore @@ -245,9 +245,6 @@ cython_debug/ #.idea/ .vscode -/df_designer_front/node_modules -/df_designer_front/vscode -/df_designer_front/.vscode ./flows.json *.sqlite @@ -256,4 +253,7 @@ my_project /playwright-report/ /blob-report/ /playwright/.cache/ -df_designer_project + +temp_conf.yaml + +my_project diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df287470..ac05b8c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ We have almost finished the main functionality. Nevertheless, we will be glad to We will be glad if you contribute to Dialog Flow Designer. ## Rules for submitting a PR -All PRs are reviewed by DflowD developers team. In order to make the reviewer job easier and increase the chance that your PR will be accepted, please add a short description with information about why this PR is needed and what changes will be made. +All PRs are reviewed by Chatsky-UI developers team. In order to make the reviewer job easier and increase the chance that your PR will be accepted, please add a short description with information about why this PR is needed and what changes will be made. ## Development We use poetry as a handy dependency management and packaging tool, which reads pyproject.toml to get specification for commands. poetry is a tool for command running automatization. If your environment does not support poetry, it can be installed as a python package with `pipx install poetry`. However, It's recommended to install isolated from the global Python environment, which prevents potential conflicts with other packages ([Installation on the official site](https://python-poetry.org/docs/#installing-with-the-official-installer:~:text=its%20own%20environment.-,Install%20Poetry,-The%20installer%20script)). @@ -14,7 +14,7 @@ We use poetry as a handy dependency management and packaging tool, which reads p ```bash python3 -m venv poetry-venv \ # create virtual env and install poetry && poetry-venv/bin/pip install poetry==1.8.2 -cd backend/df_designer \ # using poetry, install DflowD package +cd backend \ # using poetry, install Chatsky-UI package && poetry install \ && poetry shell \ && cd ../../ diff --git a/Dockerfile b/Dockerfile index 40b97713..bc162f58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,14 +15,10 @@ RUN bun run build #--------------------------------------------------------- -# Use a slim variant to reduce image size where possible FROM python:3.10-slim as backend-builder WORKDIR /temp -ARG PROJECT_DIR -# ENV PROJECT_DIR ${PROJECT_DIR} - ENV POETRY_VERSION=1.8.2 \ POETRY_HOME=/poetry \ POETRY_VENV=/poetry-venv @@ -34,38 +30,29 @@ RUN python3 -m venv $POETRY_VENV \ ENV PATH="${PATH}:${POETRY_VENV}/bin" -COPY ./backend/df_designer /temp/backend/df_designer -COPY --from=frontend-builder /temp/frontend/dist /temp/backend/df_designer/app/static +COPY ./backend /temp/backend +COPY --from=frontend-builder /temp/frontend/dist /temp/backend/chatsky_ui/static -COPY ./${PROJECT_DIR} /temp/${PROJECT_DIR} # Build the wheel -WORKDIR /temp/backend/df_designer +WORKDIR /temp/backend RUN poetry build #--------------------------------------------------------- -#TODO: create something like src named e.g. runtime/ - FROM python:3.10-slim as runtime ARG PROJECT_DIR -COPY --from=backend-builder /poetry-venv /poetry-venv - -# Set environment variable to use the virtualenv -ENV PATH="/poetry-venv/bin:$PATH" +# Install pip and upgrade +RUN pip install --upgrade pip # Copy only the necessary files -COPY --from=backend-builder /temp/backend/df_designer /src2/backend/df_designer -COPY ./${PROJECT_DIR} /src2/project_dir +COPY --from=backend-builder /temp/backend/dist /src/dist +COPY ./${PROJECT_DIR} /src/project_dir # Install the wheel -WORKDIR /src2/project_dir -RUN poetry lock --no-update \ - && poetry install - -CMD ["poetry", "run", "dflowd", "run_app"] - +WORKDIR /src/project_dir +RUN pip install ../dist/*.whl -# #TODO: change scr to app (maybe) \ No newline at end of file +CMD ["chatsky.ui", "run_app"] diff --git a/Makefile b/Makefile index b983d78c..b474d072 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ SHELL = /bin/bash PYTHON = python3 -FRONTEND_DIR = ./frontend -BACKEND_DIR = ./backend/df_designer +FRONTEND_DIR = frontend +BACKEND_DIR = backend +PROJECT_NAME = my_project .PHONY: help @@ -46,7 +47,7 @@ install_backend_env: ## Installs backend dependencies using poetry .PHONY: clean_backend_env clean_backend_env: ## Removes backend dependencies using poetry - cd ${BACKEND_DIR} && poetry install_env remove --all + cd ${BACKEND_DIR} && poetry env remove --all .PHONY: build_backend build_backend: install_backend_env ## Builds the backend wheel @@ -61,47 +62,52 @@ check_project_arg: .PHONY: run_backend run_backend: check_project_arg ## Runs backend using the built dist. NEEDS arg: PROJECT_NAME - cd ${PROJECT_NAME} && poetry install && poetry run dflowd run_app --conf-reload="False" - + @set -a && . $(CURDIR)/.env && \ + cd ${PROJECT_NAME} && \ + poetry add $(CURDIR)/${BACKEND_DIR}/dist/*.whl && \ + poetry install && \ + . `poetry env info --path`/bin/activate && \ + chatsky.ui run_app + +# This environment activation method was used to avoid the issue of not being able to run the app in the same shell .PHONY: run_dev_backend run_dev_backend: check_project_arg install_backend_env ## Runs backend in dev mode. NEEDS arg: PROJECT_NAME - cd ${BACKEND_DIR} && poetry run dflowd run_app --project-dir ../../${PROJECT_NAME} + cd ${BACKEND_DIR} && \ + . `poetry env info --path`/bin/activate && \ + chatsky.ui run_app --project-dir ../${PROJECT_NAME} --conf-reload # backend tests .PHONY: unit_tests unit_tests: ## Runs all backend unit tests - if [ ! -d "./df_designer_project" ]; then \ - cd "${BACKEND_DIR}" && \ - poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists; \ - fi - - cd df_designer_project && \ - poetry install && \ - poetry run pytest ../${BACKEND_DIR}/app/tests/api ../${BACKEND_DIR}/app/tests/services + cd ${BACKEND_DIR} && \ + . `poetry env info --path`/bin/activate && \ + pytest ../${BACKEND_DIR}/chatsky_ui/tests/api ../${BACKEND_DIR}/chatsky_ui/tests/services .PHONY: integration_tests integration_tests: ## Runs all backend integration tests - if [ ! -d "./df_designer_project" ]; then \ + if [ ! -d "${PROJECT_NAME}" ]; then \ cd "${BACKEND_DIR}" && \ - poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists; \ + poetry run chatsky.ui init --destination ../ --no-input --overwrite-if-exists; \ fi - cd df_designer_project && \ - poetry install && \ - poetry run pytest ../${BACKEND_DIR}/app/tests/integration + cd ${BACKEND_DIR} && \ + . `poetry env info --path`/bin/activate && \ + cd ../${PROJECT_NAME} && \ + pytest ../${BACKEND_DIR}/chatsky_ui/tests/integration .PHONY: backend_e2e_test backend_e2e_test: ## Runs e2e backend test - if [ ! -d "./df_designer_project" ]; then \ + if [ ! -d "${PROJECT_NAME}" ]; then \ cd "${BACKEND_DIR}" && \ - poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists; \ + poetry run chatsky.ui init --destination ../ --no-input --overwrite-if-exists; \ fi - cd df_designer_project && \ - poetry install && \ - poetry run pytest ../${BACKEND_DIR}/app/tests/e2e + cd ${BACKEND_DIR} && \ + . `poetry env info --path`/bin/activate && \ + cd ../${PROJECT_NAME} && \ + pytest ../${BACKEND_DIR}/chatsky_ui/tests/e2e .PHONY: backend_tests @@ -128,8 +134,8 @@ build: install_env ## Builds both frontend & backend make build_backend .PHONY: run_app -run_app: check_project_arg install_env build_frontend ## Builds frontend and backend then runs the app. NEEDS arg: PROJECT_NAME - cp ${FRONTEND_DIR}/dist/* ${BACKEND_DIR}/app/static/ && \ +run_app: check_project_arg build_frontend ## Builds frontend and backend then runs the app. NEEDS arg: PROJECT_NAME + cp ${FRONTEND_DIR}/dist/* ${BACKEND_DIR}/chatsky_ui/static/ && \ make build_backend && \ make run_backend PROJECT_NAME=${PROJECT_NAME} @@ -141,10 +147,12 @@ run_dev: check_project_arg install_env ## Runs both backend and frontend in dev .PHONY: init_proj -init_proj: install_backend_env ## Initiates a new project using dflowd - cd ${BACKEND_DIR} && poetry run dflowd init --destination ../../ +init_proj: install_backend_env ## Initiates a new project using chatsky-ui + cd ${BACKEND_DIR} && poetry run chatsky.ui init --destination ../ .PHONY: build_docs build_docs: install_backend_env ## Builds the docs - cd docs && make html && cd ../ + cd ${BACKEND_DIR} && \ + . `poetry env info --path`/bin/activate && \ + cd ../docs && make html && cd ../ diff --git a/README.md b/README.md index cdbb514e..764f1309 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Quick Start ## System Requirements -Ensure you have Python version 3.10 or higher installed. +Ensure you have Python version 3.8 or higher installed. ## Installation To install the necessary package, run the following command: ```bash -pip install dflowd --pre +pip install chatsky-ui ``` -## Configuring the dflowd app +## Configuring the chatsky-ui app You may add a `.env` file in the root directory and configure any of following environment variables. The values shown below are the default ones. ```.env HOST=0.0.0.0 @@ -27,13 +27,12 @@ RUN_RUNNING_TIMEOUT=5 ## Project Initiation Initialize your project by running: ```bash -dflowd init -cd # enter the slug you choose for your project with the help of the previous command +chatsky.ui init ``` -The `dflowd init` command will start an interactive `cookiecutter` process to create a project based on a predefined template. The resulting project will be a simple example template that you can customize to suit your needs. +The `chatsky.ui init` command will start an interactive `cookiecutter` process to create a project based on a predefined template. The resulting project will be a simple example template that you can customize to suit your needs. ## Running Your Project To run your project, use the following command: ```bash -dflowd run_backend +chatsky.ui run_app --project-dir # enter the slug you choose for your project with the help of the previous command ``` diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 52816148..00000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -# Use a slim variant to reduce image size where possible -FROM python:3.10-slim as builder - -WORKDIR /src - -ARG PROJECT_DIR -# ENV PROJECT_DIR ${PROJECT_DIR} - -ENV POETRY_VERSION=1.8.2 \ - POETRY_HOME=/poetry \ - POETRY_VENV=/poetry-venv - -# Install Poetry in a virtual environment -RUN python3 -m venv $POETRY_VENV \ - && $POETRY_VENV/bin/pip install -U pip setuptools \ - && $POETRY_VENV/bin/pip install poetry==$POETRY_VERSION - -ENV PATH="${PATH}:${POETRY_VENV}/bin" - -COPY ./df_designer /src/df_designer -COPY ./${PROJECT_DIR} /src/${PROJECT_DIR} - -# Build the wheel -WORKDIR /src/df_designer -RUN poetry build - - -FROM python:3.10-slim as runtime - -ARG PROJECT_DIR - -COPY --from=builder /poetry-venv /poetry-venv - -# Set environment variable to use the virtualenv -ENV PATH="/poetry-venv/bin:$PATH" - -# Copy only the necessary files -COPY --from=builder /src/df_designer /df_designer -COPY --from=builder /src/${PROJECT_DIR} /${PROJECT_DIR} - -# Install the wheel -WORKDIR /${PROJECT_DIR} -RUN poetry lock --no-update \ - && poetry install - -CMD ["poetry", "run", "dflowd", "run_app"] diff --git a/backend/df_designer/README.md b/backend/README.md similarity index 100% rename from backend/df_designer/README.md rename to backend/README.md diff --git a/backend/chatsky_ui/__init__.py b/backend/chatsky_ui/__init__.py new file mode 100644 index 00000000..9dd81be4 --- /dev/null +++ b/backend/chatsky_ui/__init__.py @@ -0,0 +1,3 @@ +from importlib.metadata import version + +__version__ = version(__name__) diff --git a/backend/df_designer/app/api/__init_.py b/backend/chatsky_ui/api/__init_.py similarity index 100% rename from backend/df_designer/app/api/__init_.py rename to backend/chatsky_ui/api/__init_.py diff --git a/backend/df_designer/app/__init__.py b/backend/chatsky_ui/api/api_v1/__init__.py similarity index 100% rename from backend/df_designer/app/__init__.py rename to backend/chatsky_ui/api/api_v1/__init__.py diff --git a/backend/df_designer/app/api/api_v1/api.py b/backend/chatsky_ui/api/api_v1/api.py similarity index 80% rename from backend/df_designer/app/api/api_v1/api.py rename to backend/chatsky_ui/api/api_v1/api.py index 44ed909d..19eb4d16 100644 --- a/backend/df_designer/app/api/api_v1/api.py +++ b/backend/chatsky_ui/api/api_v1/api.py @@ -1,7 +1,7 @@ from fastapi import APIRouter -from app.api.api_v1.endpoints import bot, dff_services, flows, config -from app.core.config import settings +from chatsky_ui.api.api_v1.endpoints import bot, config, dff_services, flows +from chatsky_ui.core.config import settings api_router = APIRouter() diff --git a/backend/df_designer/app/api/api_v1/__init__.py b/backend/chatsky_ui/api/api_v1/endpoints/__init__.py similarity index 100% rename from backend/df_designer/app/api/api_v1/__init__.py rename to backend/chatsky_ui/api/api_v1/endpoints/__init__.py diff --git a/backend/df_designer/app/api/api_v1/endpoints/auth.py b/backend/chatsky_ui/api/api_v1/endpoints/auth.py similarity index 100% rename from backend/df_designer/app/api/api_v1/endpoints/auth.py rename to backend/chatsky_ui/api/api_v1/endpoints/auth.py diff --git a/backend/df_designer/app/api/api_v1/endpoints/bot.py b/backend/chatsky_ui/api/api_v1/endpoints/bot.py similarity index 85% rename from backend/df_designer/app/api/api_v1/endpoints/bot.py rename to backend/chatsky_ui/api/api_v1/endpoints/bot.py index a37eb760..283fd820 100644 --- a/backend/df_designer/app/api/api_v1/endpoints/bot.py +++ b/backend/chatsky_ui/api/api_v1/endpoints/bot.py @@ -1,22 +1,19 @@ import asyncio -from typing import Any, Optional +from typing import Any, Dict, List, Optional, Union from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, WebSocket, WebSocketException, status -from app.api import deps -from app.core.logger_config import get_logger -from app.schemas.pagination import Pagination -from app.schemas.preset import Preset -from app.services.index import Index -from app.services.process_manager import BuildManager, ProcessManager, RunManager -from app.services.websocket_manager import WebSocketManager +from chatsky_ui.api import deps +from chatsky_ui.schemas.pagination import Pagination +from chatsky_ui.schemas.preset import Preset +from chatsky_ui.services.index import Index +from chatsky_ui.services.process_manager import BuildManager, ProcessManager, RunManager +from chatsky_ui.services.websocket_manager import WebSocketManager router = APIRouter() -logger = get_logger(__name__) - -async def _stop_process(id_: int, process_manager: ProcessManager, process="run") -> dict[str, str]: +async def _stop_process(id_: int, process_manager: ProcessManager, process="run") -> Dict[str, str]: """Stops a `build` or `run` process with the given id.""" try: @@ -27,11 +24,11 @@ async def _stop_process(id_: int, process_manager: ProcessManager, process="run" detail="Process not found. It may have already exited or not started yet. Please check logs.", ) from e - logger.info("%s process '%s' has stopped", process.capitalize(), id_) + process_manager.logger.info("%s process '%s' has stopped", process.capitalize(), id_) return {"status": "ok"} -async def _check_process_status(id_: int, process_manager: ProcessManager) -> dict[str, str]: +async def _check_process_status(id_: int, process_manager: ProcessManager) -> Dict[str, str]: """Checks the status of a `build` or `run` process with the given id.""" if id_ not in process_manager.processes: raise HTTPException( @@ -48,7 +45,7 @@ async def start_build( background_tasks: BackgroundTasks, build_manager: BuildManager = Depends(deps.get_build_manager), index: Index = Depends(deps.get_index), -) -> dict[str, str | int]: +) -> Dict[str, Union[str, int]]: """Starts a `build` process with the given preset. @@ -64,12 +61,12 @@ async def start_build( await asyncio.sleep(preset.wait_time) build_id = await build_manager.start(preset) background_tasks.add_task(build_manager.check_status, build_id, index) - logger.info("Build process '%s' has started", build_id) + build_manager.logger.info("Build process '%s' has started", build_id) return {"status": "ok", "build_id": build_id} @router.get("/build/stop/{build_id}", status_code=200) -async def stop_build(*, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager)) -> dict[str, str]: +async def stop_build(*, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager)) -> Dict[str, str]: """Stops a `build` process with the given id. Args: @@ -88,7 +85,7 @@ async def stop_build(*, build_id: int, build_manager: BuildManager = Depends(dep @router.get("/build/status/{build_id}", status_code=200) async def check_build_status( *, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager) -) -> dict[str, str]: +) -> Dict[str, str]: """Checks the status of a `build` process with the given id. Args: @@ -107,13 +104,13 @@ async def check_build_status( return await _check_process_status(build_id, build_manager) -@router.get("/builds", response_model=Optional[list | dict], status_code=200) +@router.get("/builds", response_model=Optional[Union[list, dict]], status_code=200) async def check_build_processes( build_id: Optional[int] = None, build_manager: BuildManager = Depends(deps.get_build_manager), run_manager: RunManager = Depends(deps.get_run_manager), pagination: Pagination = Depends(), -) -> Optional[dict[str, Any]] | list[dict[str, Any]]: +) -> Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]: """Checks the status of all `build` processes and returns them along with their runs info. The offset and limit parameters can be used to paginate the results. @@ -132,7 +129,7 @@ async def check_build_processes( @router.get("/builds/logs/{build_id}", response_model=Optional[list], status_code=200) async def get_build_logs( build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager), pagination: Pagination = Depends() -) -> Optional[list[str]]: +) -> Optional[List[str]]: """Gets the logs of a specific `build` process. The offset and limit parameters can be used to paginate the results. @@ -148,7 +145,7 @@ async def start_run( preset: Preset, background_tasks: BackgroundTasks, run_manager: RunManager = Depends(deps.get_run_manager) -) -> dict[str, str | int]: +) -> Dict[str, Union[str, int]]: """Starts a `run` process with the given preset. This runs a background task to check the status of the process every 2 seconds. @@ -164,12 +161,12 @@ async def start_run( await asyncio.sleep(preset.wait_time) run_id = await run_manager.start(build_id, preset) background_tasks.add_task(run_manager.check_status, run_id) - logger.info("Run process '%s' has started", run_id) + run_manager.logger.info("Run process '%s' has started", run_id) return {"status": "ok", "run_id": run_id} @router.get("/run/stop/{run_id}", status_code=200) -async def stop_run(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)) -> dict[str, str]: +async def stop_run(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)) -> Dict[str, str]: """Stops a `run` process with the given id. Args: @@ -187,7 +184,7 @@ async def stop_run(*, run_id: int, run_manager: RunManager = Depends(deps.get_ru @router.get("/run/status/{run_id}", status_code=200) -async def check_run_status(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)) -> dict[str, Any]: +async def check_run_status(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)) -> Dict[str, Any]: """Checks the status of a `run` process with the given id. Args: @@ -206,12 +203,12 @@ async def check_run_status(*, run_id: int, run_manager: RunManager = Depends(dep return await _check_process_status(run_id, run_manager) -@router.get("/runs", response_model=Optional[list | dict], status_code=200) +@router.get("/runs", response_model=Optional[Union[list, dict]], status_code=200) async def check_run_processes( run_id: Optional[int] = None, run_manager: RunManager = Depends(deps.get_run_manager), pagination: Pagination = Depends(), -) -> Optional[dict[str, Any]] | list[dict[str, Any]]: +) -> Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]: """Checks the status of all `run` processes and returns them. The offset and limit parameters can be used to paginate the results. @@ -229,7 +226,7 @@ async def check_run_processes( @router.get("/runs/logs/{run_id}", response_model=Optional[list], status_code=200) async def get_run_logs( run_id: int, run_manager: RunManager = Depends(deps.get_run_manager), pagination: Pagination = Depends() -) -> Optional[list[str]]: +) -> Optional[List[str]]: """Gets the logs of a specific `run` process. The offset and limit parameters can be used to paginate the results. @@ -249,23 +246,23 @@ async def connect( The WebSocket URL should adhere to the format: /bot/run/connect?run_id=. """ - logger.debug("Connecting to websocket") + run_manager.logger.debug("Connecting to websocket") run_id = websocket.query_params.get("run_id") # Validate run_id if run_id is None: - logger.error("No run_id provided") + run_manager.logger.error("No run_id provided") raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) if not run_id.isdigit(): - logger.error("A non-digit run run_id provided") + run_manager.logger.error("A non-digit run run_id provided") raise WebSocketException(code=status.WS_1003_UNSUPPORTED_DATA) run_id = int(run_id) if run_id not in run_manager.processes: - logger.error("process with run_id '%s' exited or never existed", run_id) + run_manager.logger.error("process with run_id '%s' exited or never existed", run_id) raise WebSocketException(code=status.WS_1014_BAD_GATEWAY) await websocket_manager.connect(websocket) - logger.info("Websocket for run process '%s' has been opened", run_id) + run_manager.logger.info("Websocket for run process '%s' has been opened", run_id) await websocket.send_text("Start chatting") diff --git a/backend/chatsky_ui/api/api_v1/endpoints/config.py b/backend/chatsky_ui/api/api_v1/endpoints/config.py new file mode 100644 index 00000000..dde42f64 --- /dev/null +++ b/backend/chatsky_ui/api/api_v1/endpoints/config.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter + +from chatsky_ui import __version__ + +router = APIRouter() + + +@router.get("/version") +async def get_version(): + return __version__ diff --git a/backend/df_designer/app/api/api_v1/endpoints/dff_services.py b/backend/chatsky_ui/api/api_v1/endpoints/dff_services.py similarity index 75% rename from backend/df_designer/app/api/api_v1/endpoints/dff_services.py rename to backend/chatsky_ui/api/api_v1/endpoints/dff_services.py index 6b6376c3..eb67ded6 100644 --- a/backend/df_designer/app/api/api_v1/endpoints/dff_services.py +++ b/backend/chatsky_ui/api/api_v1/endpoints/dff_services.py @@ -1,27 +1,24 @@ import re from io import StringIO -from typing import Optional +from typing import Dict, Optional, Union import aiofiles from fastapi import APIRouter, Depends from pylint.lint import Run, pylinter from pylint.reporters.text import TextReporter -from app.api.deps import get_index -from app.clients.dff_client import get_dff_conditions -from app.core.config import settings -from app.core.logger_config import get_logger -from app.schemas.code_snippet import CodeSnippet -from app.services.index import Index -from app.utils.ast_utils import get_imports_from_file +from chatsky_ui.api.deps import get_index +from chatsky_ui.clients.dff_client import get_dff_conditions +from chatsky_ui.core.config import settings +from chatsky_ui.schemas.code_snippet import CodeSnippet +from chatsky_ui.services.index import Index +from chatsky_ui.utils.ast_utils import get_imports_from_file router = APIRouter() -logger = get_logger(__name__) - @router.get("/search/{service_name}", status_code=200) -async def search_service(service_name: str, index: Index = Depends(get_index)) -> dict[str, str | Optional[list]]: +async def search_service(service_name: str, index: Index = Depends(get_index)) -> Dict[str, Optional[Union[str, list]]]: """Searches for a custom service by name and returns its code. A service could be a condition, reponse, or pre/postservice. @@ -31,7 +28,7 @@ async def search_service(service_name: str, index: Index = Depends(get_index)) - @router.post("/lint_snippet", status_code=200) -async def lint_snippet(snippet: CodeSnippet) -> dict[str, str]: +async def lint_snippet(snippet: CodeSnippet) -> Dict[str, str]: """Lints a snippet with Pylint. This endpoint Joins the snippet with all imports existing in the conditions.py file and then runs Pylint on it. @@ -58,6 +55,6 @@ async def lint_snippet(snippet: CodeSnippet) -> dict[str, str]: @router.get("/get_conditions", status_code=200) -async def get_conditions() -> dict[str, str | list]: +async def get_conditions() -> Dict[str, Union[str, list]]: """Gets the dff's out-of-the-box conditions.""" return {"status": "ok", "data": get_dff_conditions()} diff --git a/backend/df_designer/app/api/api_v1/endpoints/flows.py b/backend/chatsky_ui/api/api_v1/endpoints/flows.py similarity index 65% rename from backend/df_designer/app/api/api_v1/endpoints/flows.py rename to backend/chatsky_ui/api/api_v1/endpoints/flows.py index ae6bf60b..c7168e84 100644 --- a/backend/df_designer/app/api/api_v1/endpoints/flows.py +++ b/backend/chatsky_ui/api/api_v1/endpoints/flows.py @@ -1,17 +1,16 @@ +from typing import Dict, Union + from fastapi import APIRouter from omegaconf import OmegaConf -from app.core.config import settings -from app.core.logger_config import get_logger -from app.db.base import read_conf, write_conf +from chatsky_ui.core.config import settings +from chatsky_ui.db.base import read_conf, write_conf router = APIRouter() -logger = get_logger(__name__) - @router.get("/") -async def flows_get() -> dict[str, str | dict[str, list]]: +async def flows_get() -> Dict[str, Union[str, Dict[str, list]]]: """Get the flows by reading the frontend_flows.yaml file.""" omega_flows = await read_conf(settings.frontend_flows_path) dict_flows = OmegaConf.to_container(omega_flows, resolve=True) @@ -19,7 +18,7 @@ async def flows_get() -> dict[str, str | dict[str, list]]: @router.post("/") -async def flows_post(flows: dict[str, list]) -> dict[str, str]: +async def flows_post(flows: Dict[str, list]) -> Dict[str, str]: """Write the flows to the frontend_flows.yaml file.""" await write_conf(flows, settings.frontend_flows_path) return {"status": "ok"} diff --git a/backend/chatsky_ui/api/deps.py b/backend/chatsky_ui/api/deps.py new file mode 100644 index 00000000..7317d638 --- /dev/null +++ b/backend/chatsky_ui/api/deps.py @@ -0,0 +1,36 @@ +from chatsky_ui.core.config import settings +from chatsky_ui.services.index import Index +from chatsky_ui.services.process_manager import BuildManager, RunManager +from chatsky_ui.services.websocket_manager import WebSocketManager + +build_manager = BuildManager() + + +def get_build_manager() -> BuildManager: + build_manager.set_logger() + return build_manager + + +run_manager = RunManager() + + +def get_run_manager() -> RunManager: + run_manager.set_logger() + return run_manager + + +websocket_manager = WebSocketManager() + + +def get_websocket_manager() -> WebSocketManager: + websocket_manager.set_logger() + return websocket_manager + + +index = Index() + + +def get_index() -> Index: + index.set_logger() + index.set_path(settings.index_path) + return index diff --git a/backend/chatsky_ui/cli.py b/backend/chatsky_ui/cli.py new file mode 100644 index 00000000..f322fea7 --- /dev/null +++ b/backend/chatsky_ui/cli.py @@ -0,0 +1,184 @@ +import asyncio +import json +import os +import string +import sys +from pathlib import Path + +import nest_asyncio +import typer +from cookiecutter.main import cookiecutter +from typing_extensions import Annotated + +# Patch nest_asyncio before importing DFF +nest_asyncio.apply = lambda: None + +from chatsky_ui.core.config import app_runner, settings # noqa: E402 +from chatsky_ui.core.logger_config import get_logger # noqa: E402 + +cli = typer.Typer( + help="🚀 Welcome to Chatsky-UI!\n\n" + "To get started, use the following commands:\n\n" + "1. `init` - Initializes a new Chatsky-UI project.\n\n" + "2. `run_app` - Runs the UI for your project.\n" +) + + +async def _execute_command(command_to_run): + logger = get_logger(__name__) + try: + process = await asyncio.create_subprocess_exec(*command_to_run.split()) + + # Check the return code to determine success + if process.returncode == 0: + logger.info("Command '%s' executed successfully.", command_to_run) + elif process.returncode is None: + logger.info("Process by command '%s' is running.", command_to_run) + await process.wait() + logger.info("Process ended with return code: %d.", process.returncode) + sys.exit(process.returncode) + else: + logger.error("Command '%s' failed with return code: %d", command_to_run, process.returncode) + sys.exit(process.returncode) + + except Exception as e: + logger.error("Error executing '%s': %s", command_to_run, str(e)) + sys.exit(1) + + +def _execute_command_file(build_id: int, project_dir: Path, command_file: str, preset: str): + logger = get_logger(__name__) + + presets_build_path = settings.presets / command_file + with open(presets_build_path, encoding="UTF-8") as file: + file_content = file.read() + + template = string.Template(file_content) + substituted_content = template.substitute(work_directory=project_dir, build_id=build_id) + + presets_build_file = json.loads(substituted_content) + if preset in presets_build_file: + command_to_run = presets_build_file[preset]["cmd"] + logger.debug("Executing command for preset '%s': %s", preset, command_to_run) + + asyncio.run(_execute_command(command_to_run)) + else: + raise ValueError(f"Invalid preset '{preset}'. Preset must be one of {list(presets_build_file.keys())}") + + +@cli.command("build_bot") +def build_bot( + build_id: Annotated[int, typer.Option(help="Id to save the build with")] = None, + project_dir: Path = None, + preset: Annotated[str, typer.Option(help="Could be one of: success, failure, loop")] = "success", +): + """Builds the bot with one of three various presets.""" + project_dir = project_dir or settings.work_directory + + if not project_dir.is_dir(): + raise NotADirectoryError(f"Directory {project_dir} doesn't exist") + settings.set_config(work_directory=project_dir) + + _execute_command_file(build_id, project_dir, "build.json", preset) + + +@cli.command("build_scenario") +def build_scenario( + build_id: Annotated[int, typer.Argument(help="Id to save the build with")], + project_dir: Annotated[Path, typer.Option(help="Your Chatsky-UI project directory")] = ".", + # TODO: add custom_dir - maybe the same way like project_dir +): + """Builds the bot with preset `success`""" + if not project_dir.is_dir(): + raise NotADirectoryError(f"Directory {project_dir} doesn't exist") + settings.set_config(work_directory=project_dir) + + from chatsky_ui.services.json_converter import converter # pylint: disable=C0415 + + asyncio.run(converter(build_id=build_id)) + + +@cli.command("run_bot") +def run_bot( + build_id: Annotated[int, typer.Option(help="Id of the build to run")] = None, + project_dir: Annotated[Path, typer.Option(help="Your Chatsky-UI project directory")] = None, + preset: Annotated[str, typer.Option(help="Could be one of: success, failure, loop")] = "success", +): + """Runs the bot with one of three various presets.""" + project_dir = project_dir or settings.work_directory + + if not project_dir.is_dir(): + raise NotADirectoryError(f"Directory {project_dir} doesn't exist") + settings.set_config(work_directory=project_dir) + + _execute_command_file(build_id, project_dir, "run.json", preset) + + +@cli.command("run_scenario") +def run_scenario( + build_id: Annotated[int, typer.Argument(help="Id of the build to run")], + project_dir: Annotated[Path, typer.Option(help="Your Chatsky-UI project directory")] = ".", +): + """Runs the bot with preset `success`""" + if not project_dir.is_dir(): + raise NotADirectoryError(f"Directory {project_dir} doesn't exist") + settings.set_config(work_directory=project_dir) + script_path = settings.scripts_dir / f"build_{build_id}.yaml" + + command_to_run = f"python {project_dir}/app.py --script-path {script_path}" + asyncio.run(_execute_command(command_to_run)) + + +@cli.command("run_app") +def run_app( + host: str = None, + port: int = None, + log_level: str = None, + conf_reload: Annotated[bool, typer.Option(help="True for dev-mode, False otherwise")] = None, + project_dir: Annotated[Path, typer.Option(help="Your Chatsky-UI project directory")] = Path("."), +) -> None: + """Runs the UI for your `project_dir` on `host:port`.""" + host = host or settings.host + port = port or settings.port + log_level = log_level or settings.log_level + conf_reload = conf_reload or settings.conf_reload + + if not project_dir.is_dir(): + raise NotADirectoryError(f"Directory {project_dir} doesn't exist") + + settings.set_config( + host=host, + port=port, + log_level=log_level, + conf_reload=str(conf_reload).lower() in ["true", "yes", "t", "y", "1"], + work_directory=project_dir, + ) + if conf_reload: + settings.save_config() # this is for the sake of maintaining the state of the settings + + app_runner.set_settings(settings) + app_runner.run() + + +@cli.command("init") +def init( + destination: Annotated[Path, typer.Option(help="Path where you want to create your project")] = None, + no_input: Annotated[bool, typer.Option(help="True for quick and easy initialization using default values")] = False, + overwrite_if_exists: Annotated[ + bool, + typer.Option(help="True for replacing any project named as `my_project`)"), + ] = True, +): + """Initializes a new Chatsky-UI project using an off-the-shelf template.""" + destination = destination or settings.work_directory + + original_dir = os.getcwd() + try: + os.chdir(destination) + cookiecutter( + "https://github.com/Ramimashkouk/df_d_template.git", + no_input=no_input, + overwrite_if_exists=overwrite_if_exists, + ) + finally: + os.chdir(original_dir) diff --git a/backend/df_designer/app/api/api_v1/endpoints/__init__.py b/backend/chatsky_ui/clients/__init__.py similarity index 100% rename from backend/df_designer/app/api/api_v1/endpoints/__init__.py rename to backend/chatsky_ui/clients/__init__.py diff --git a/backend/df_designer/app/clients/dff_client.py b/backend/chatsky_ui/clients/dff_client.py similarity index 95% rename from backend/df_designer/app/clients/dff_client.py rename to backend/chatsky_ui/clients/dff_client.py index 09317168..9163f619 100644 --- a/backend/df_designer/app/clients/dff_client.py +++ b/backend/chatsky_ui/clients/dff_client.py @@ -1,3 +1,5 @@ +from typing import List + import dff.script.conditions as cnd from dff.pipeline.pipeline import script_parsing @@ -9,7 +11,7 @@ } -def get_dff_conditions() -> list[dict]: +def get_dff_conditions() -> List[dict]: """Gets the DFF's out-of-the-box conditions. Returns: diff --git a/backend/df_designer/app/clients/__init__.py b/backend/chatsky_ui/core/__init__.py similarity index 100% rename from backend/df_designer/app/clients/__init__.py rename to backend/chatsky_ui/core/__init__.py diff --git a/backend/df_designer/app/core/auth.py b/backend/chatsky_ui/core/auth.py similarity index 100% rename from backend/df_designer/app/core/auth.py rename to backend/chatsky_ui/core/auth.py diff --git a/backend/chatsky_ui/core/config.py b/backend/chatsky_ui/core/config.py new file mode 100644 index 00000000..30cd10a9 --- /dev/null +++ b/backend/chatsky_ui/core/config.py @@ -0,0 +1,120 @@ +import os +from pathlib import Path + +import uvicorn +from dotenv import load_dotenv +from omegaconf import DictConfig, OmegaConf + +load_dotenv() + + +class Settings: + def __init__(self): + self.API_V1_STR = "/api/v1" + self.APP = "chatsky_ui.main:app" + + self.config_file_path = Path(__file__).absolute() + self.static_files = self.config_file_path.parent.with_name("static") + self.start_page = self.static_files / "index.html" + self.package_dir = self.config_file_path.parents[2] + self.temp_conf = self.config_file_path.with_name("temp_conf.yaml") + + self.set_config( + host=os.getenv("HOST", "0.0.0.0"), + port=os.getenv("PORT", "8000"), + log_level=os.getenv("LOG_LEVEL", "info"), + conf_reload=os.getenv("CONF_RELOAD", "false"), + work_directory=".", + ) + + def set_config(self, **kwargs): + for key, value in kwargs.items(): + if key == "work_directory": + value = Path(value) + elif key == "conf_reload": + value = str(value).lower() in ["true", "yes", "t", "y", "1"] + elif key == "port": + value = int(value) + setattr(self, key, value) + + if "work_directory" in kwargs: + self._set_user_proj_paths() + + def _set_user_proj_paths(self): + self.builds_path = self.work_directory / "chatsky_ui/app_data/builds.yaml" + self.runs_path = self.work_directory / "chatsky_ui/app_data/runs.yaml" + self.frontend_flows_path = self.work_directory / "chatsky_ui/app_data/frontend_flows.yaml" + self.dir_logs = self.work_directory / "chatsky_ui/logs" + self.presets = self.work_directory / "chatsky_ui/presets" + self.snippet2lint_path = self.work_directory / "chatsky_ui/.snippet2lint.py" + + self.custom_dir = self.work_directory / "bot/custom" + self.index_path = self.custom_dir / ".services_index.yaml" + self.conditions_path = self.custom_dir / "conditions.py" + self.responses_path = self.custom_dir / "responses.py" + self.scripts_dir = self.work_directory / "bot/scripts" + + def save_config(self): + if not self.temp_conf.exists(): + self.temp_conf.touch() + OmegaConf.save( + OmegaConf.create( + { + "work_directory": str(self.work_directory), + "host": self.host, + "port": self.port, + "log_level": self.log_level, + "conf_reload": self.conf_reload, + } + ), # type: ignore + self.temp_conf, + ) + + def _load_temp_config(self) -> DictConfig: + if not self.temp_conf.exists(): + raise FileNotFoundError(f"{self.temp_conf} not found.") + + return OmegaConf.load(self.temp_conf) # type: ignore + + def refresh_work_dir(self): + config = self._load_temp_config() + self.set_config(**config) + + +class AppRunner: + def __init__(self): + self._settings = None + + @property + def settings(self): + if self._settings is None: + raise ValueError("Settings has not been configured. Call set_logger() first.") + return self._settings + + def set_settings(self, app_settings: Settings): + self._settings = app_settings + + def run(self): + if reload := self.settings.conf_reload: + reload_conf = { + "reload": reload, + "reload_dirs": [str(self.settings.package_dir)], + "reload_excludes": [ + f"./{self.settings.work_directory}/*", + f"./{self.settings.work_directory}/*/*", + f"./{self.settings.work_directory}/*/*/*", + ], + } + else: + reload_conf = {"reload": reload} + uvicorn.run( + self.settings.APP, + host=self.settings.host, + port=self.settings.port, + log_level=self.settings.log_level, + **reload_conf, + ) + + +settings = Settings() +app_runner = AppRunner() diff --git a/backend/df_designer/app/core/logger_config.py b/backend/chatsky_ui/core/logger_config.py similarity index 65% rename from backend/df_designer/app/core/logger_config.py rename to backend/chatsky_ui/core/logger_config.py index 56d252a1..8fd0f345 100644 --- a/backend/df_designer/app/core/logger_config.py +++ b/backend/chatsky_ui/core/logger_config.py @@ -1,11 +1,11 @@ import logging from datetime import datetime from pathlib import Path -from typing import Literal, Optional +from typing import Dict, Literal, Optional -from app.core.config import settings +from chatsky_ui.core.config import settings -LOG_LEVELS: dict[str, int] = { +LOG_LEVELS: Dict[str, int] = { "critical": logging.CRITICAL, "error": logging.ERROR, "warning": logging.WARNING, @@ -43,17 +43,18 @@ def get_logger(name, file_handler_path: Optional[Path] = None): logger.propagate = False logger.setLevel(LOG_LEVELS[settings.log_level]) - c_handler = logging.StreamHandler() - f_handler = logging.FileHandler(file_handler_path) - c_handler.setLevel(LOG_LEVELS[settings.log_level]) - f_handler.setLevel(LOG_LEVELS[settings.log_level]) + if not logger.hasHandlers(): + c_handler = logging.StreamHandler() + f_handler = logging.FileHandler(file_handler_path) + c_handler.setLevel(LOG_LEVELS[settings.log_level]) + f_handler.setLevel(LOG_LEVELS[settings.log_level]) - c_format = logging.Formatter("%(name)s - %(levelname)s - %(message)s") - f_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") - c_handler.setFormatter(c_format) - f_handler.setFormatter(f_format) + c_format = logging.Formatter("%(name)s - %(levelname)s - %(message)s") + f_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + c_handler.setFormatter(c_format) + f_handler.setFormatter(f_format) - logger.addHandler(c_handler) - logger.addHandler(f_handler) + logger.addHandler(c_handler) + logger.addHandler(f_handler) return logger diff --git a/backend/df_designer/app/core/security.py b/backend/chatsky_ui/core/security.py similarity index 100% rename from backend/df_designer/app/core/security.py rename to backend/chatsky_ui/core/security.py diff --git a/backend/df_designer/app/core/__init__.py b/backend/chatsky_ui/db/__init__.py similarity index 100% rename from backend/df_designer/app/core/__init__.py rename to backend/chatsky_ui/db/__init__.py diff --git a/backend/df_designer/app/db/base.py b/backend/chatsky_ui/db/base.py similarity index 83% rename from backend/df_designer/app/db/base.py rename to backend/chatsky_ui/db/base.py index 3faa5825..6ea872a7 100644 --- a/backend/df_designer/app/db/base.py +++ b/backend/chatsky_ui/db/base.py @@ -1,6 +1,6 @@ from asyncio import Lock from pathlib import Path -from typing import List +from typing import List, Union import aiofiles from omegaconf import OmegaConf @@ -10,7 +10,7 @@ file_lock = Lock() -async def read_conf(path: Path) -> DictConfig | ListConfig: +async def read_conf(path: Path) -> Union[DictConfig, ListConfig]: async with file_lock: async with aiofiles.open(path, "r", encoding="UTF-8") as file: data = await file.read() @@ -18,7 +18,7 @@ async def read_conf(path: Path) -> DictConfig | ListConfig: return omega_data -async def write_conf(data: DictConfig | ListConfig | dict | list, path: Path) -> None: +async def write_conf(data: Union[DictConfig, ListConfig, dict, list], path: Path) -> None: yaml_conf = OmegaConf.to_yaml(data) async with file_lock: async with aiofiles.open(path, "w", encoding="UTF-8") as file: # TODO: change to "a" for append diff --git a/backend/df_designer/app/db/base_class.py b/backend/chatsky_ui/db/base_class.py similarity index 100% rename from backend/df_designer/app/db/base_class.py rename to backend/chatsky_ui/db/base_class.py diff --git a/backend/df_designer/app/db/__init__.py b/backend/chatsky_ui/db/crud/__init__.py similarity index 100% rename from backend/df_designer/app/db/__init__.py rename to backend/chatsky_ui/db/crud/__init__.py diff --git a/backend/df_designer/app/db/crud/base.py b/backend/chatsky_ui/db/crud/base.py similarity index 100% rename from backend/df_designer/app/db/crud/base.py rename to backend/chatsky_ui/db/crud/base.py diff --git a/backend/df_designer/app/db/crud/crud_bot.py b/backend/chatsky_ui/db/crud/crud_bot.py similarity index 100% rename from backend/df_designer/app/db/crud/crud_bot.py rename to backend/chatsky_ui/db/crud/crud_bot.py diff --git a/backend/df_designer/app/db/init_db.py b/backend/chatsky_ui/db/init_db.py similarity index 100% rename from backend/df_designer/app/db/init_db.py rename to backend/chatsky_ui/db/init_db.py diff --git a/backend/df_designer/app/db/crud/__init__.py b/backend/chatsky_ui/db/models/__init__.py similarity index 100% rename from backend/df_designer/app/db/crud/__init__.py rename to backend/chatsky_ui/db/models/__init__.py diff --git a/backend/df_designer/app/db/models/bot.py b/backend/chatsky_ui/db/models/bot.py similarity index 100% rename from backend/df_designer/app/db/models/bot.py rename to backend/chatsky_ui/db/models/bot.py diff --git a/backend/df_designer/app/db/session.py b/backend/chatsky_ui/db/session.py similarity index 100% rename from backend/df_designer/app/db/session.py rename to backend/chatsky_ui/db/session.py diff --git a/backend/df_designer/app/initial_data.py b/backend/chatsky_ui/initial_data.py similarity index 100% rename from backend/df_designer/app/initial_data.py rename to backend/chatsky_ui/initial_data.py diff --git a/backend/df_designer/app/main.py b/backend/chatsky_ui/main.py similarity index 82% rename from backend/df_designer/app/main.py rename to backend/chatsky_ui/main.py index f50dc5bf..05e890b2 100644 --- a/backend/df_designer/app/main.py +++ b/backend/chatsky_ui/main.py @@ -4,23 +4,28 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse -from app.api.api_v1.api import api_router -from app.api.deps import get_index -from app.core.config import settings +from chatsky_ui.api.api_v1.api import api_router +from chatsky_ui.api.deps import get_index +from chatsky_ui.core.config import settings index_dict = {} @asynccontextmanager async def lifespan(app: FastAPI): + if settings.temp_conf.exists(): + settings.refresh_work_dir() + index_dict["instance"] = get_index() await index_dict["instance"].load() yield - # Clean up and release the resources + + settings.temp_conf.unlink(missing_ok=True) app = FastAPI(title="DF Designer", lifespan=lifespan) + app.add_middleware( CORSMiddleware, allow_origins=["*"], diff --git a/backend/df_designer/app/db/models/__init__.py b/backend/chatsky_ui/schemas/__init__.py similarity index 100% rename from backend/df_designer/app/db/models/__init__.py rename to backend/chatsky_ui/schemas/__init__.py diff --git a/backend/df_designer/app/schemas/code_snippet.py b/backend/chatsky_ui/schemas/code_snippet.py similarity index 100% rename from backend/df_designer/app/schemas/code_snippet.py rename to backend/chatsky_ui/schemas/code_snippet.py diff --git a/backend/df_designer/app/schemas/pagination.py b/backend/chatsky_ui/schemas/pagination.py similarity index 100% rename from backend/df_designer/app/schemas/pagination.py rename to backend/chatsky_ui/schemas/pagination.py diff --git a/backend/df_designer/app/schemas/preset.py b/backend/chatsky_ui/schemas/preset.py similarity index 100% rename from backend/df_designer/app/schemas/preset.py rename to backend/chatsky_ui/schemas/preset.py diff --git a/backend/df_designer/app/schemas/process_status.py b/backend/chatsky_ui/schemas/process_status.py similarity index 100% rename from backend/df_designer/app/schemas/process_status.py rename to backend/chatsky_ui/schemas/process_status.py diff --git a/backend/df_designer/app/schemas/__init__.py b/backend/chatsky_ui/services/__init__.py similarity index 100% rename from backend/df_designer/app/schemas/__init__.py rename to backend/chatsky_ui/services/__init__.py diff --git a/backend/df_designer/app/services/index.py b/backend/chatsky_ui/services/index.py similarity index 90% rename from backend/df_designer/app/services/index.py rename to backend/chatsky_ui/services/index.py index 04f0749c..683ae9b3 100644 --- a/backend/df_designer/app/services/index.py +++ b/backend/chatsky_ui/services/index.py @@ -13,19 +13,36 @@ from omegaconf import OmegaConf from omegaconf.dictconfig import DictConfig -from app.core.config import settings -from app.core.logger_config import get_logger -from app.db.base import read_conf, read_logs, write_conf +from chatsky_ui.core.logger_config import get_logger +from chatsky_ui.db.base import read_conf, read_logs, write_conf class Index: def __init__(self): - self.path: Path = settings.index_path self.index: dict = {} self.conditions: List[str] = [] self.responses: List[str] = [] self.services: List[str] = [] - self.logger = get_logger(__name__) + self._logger = None + self._path = None + + @property + def logger(self): + if self._logger is None: + raise ValueError("Logger has not been configured. Call set_logger() first.") + return self._logger + + def set_logger(self): + self._logger = get_logger(__name__) + + @property + def path(self): + if self._path is None: + raise ValueError("Path has not been configured. Call set_path() first.") + return self._path + + def set_path(self, path: Path): + self._path = path async def _load_index(self) -> None: """Load indexed conditions, responses and services from disk.""" diff --git a/backend/df_designer/app/services/json_converter.py b/backend/chatsky_ui/services/json_converter.py similarity index 90% rename from backend/df_designer/app/services/json_converter.py rename to backend/chatsky_ui/services/json_converter.py index 8331beb1..d0d7a77a 100644 --- a/backend/df_designer/app/services/json_converter.py +++ b/backend/chatsky_ui/services/json_converter.py @@ -5,24 +5,25 @@ Converts a user project's frontend graph to a script understandable by DFF json-importer. """ from pathlib import Path -from typing import Tuple +from typing import List, Optional, Tuple -from app.api.deps import get_index -from app.core.logger_config import get_logger -from app.db.base import read_conf, write_conf -from app.services.index import Index from omegaconf.dictconfig import DictConfig +from chatsky_ui.api.deps import get_index +from chatsky_ui.core.logger_config import get_logger +from chatsky_ui.core.config import settings +from chatsky_ui.db.base import read_conf, write_conf +from chatsky_ui.services.index import Index + logger = get_logger(__name__) -def _get_db_paths(build_id: int, project_dir: Path, custom_dir: str) -> Tuple[Path, Path, Path, Path]: +def _get_db_paths(build_id: int) -> Tuple[Path, Path, Path, Path]: """Get paths to frontend graph, dff script, and dff custom conditions files.""" - - frontend_graph_path = project_dir / "df_designer" / "frontend_flows.yaml" - custom_conditions_file = project_dir / "bot" / custom_dir / "conditions.py" - custom_responses_file = project_dir / "bot" / custom_dir / "responses.py" - script_path = project_dir / "bot" / "scripts" / f"build_{build_id}.yaml" + frontend_graph_path = settings.frontend_flows_path + custom_conditions_file = settings.conditions_path + custom_responses_file = settings.responses_path + script_path = settings.scripts_dir / f"build_{build_id}.yaml" if not frontend_graph_path.exists(): raise FileNotFoundError(f"File {frontend_graph_path} doesn't exist") @@ -40,6 +41,7 @@ def _get_db_paths(build_id: int, project_dir: Path, custom_dir: str) -> Tuple[Pa def _organize_graph_according_to_nodes(flow_graph: DictConfig, script: dict) -> dict: nodes = {} for flow in flow_graph["flows"]: + node_names_in_one_flow = [] for node in flow.data.nodes: if "flags" in node.data: if "start" in node.data.flags: @@ -50,13 +52,17 @@ def _organize_graph_according_to_nodes(flow_graph: DictConfig, script: dict) -> if "fallback_label" in script["CONFIG"]: raise ValueError("There are more than one fallback node in the script") script["CONFIG"]["fallback_label"] = [flow.name, node.data.name] + + if node.data.name in node_names_in_one_flow: + raise ValueError(f"There is more than one node with the name '{node.data.name}' in the same flow.") + node_names_in_one_flow.append(node.data.name) nodes[node.id] = {"info": node} nodes[node.id]["flow"] = flow.name nodes[node.id]["TRANSITIONS"] = [] return nodes -def _get_condition(nodes: dict, edge: DictConfig) -> DictConfig | None: +def _get_condition(nodes: dict, edge: DictConfig) -> Optional[DictConfig]: """Get node's condition from `nodes` according to `edge` info.""" return next( (condition for condition in nodes[edge.source]["info"].data.conditions if condition["id"] == edge.sourceHandle), @@ -179,7 +185,7 @@ async def _replace(service: DictConfig, services_lines: list, cnd_strt_lineno: i return all_lines -async def update_responses_lines(nodes: dict, responses_lines: list, index: Index) -> tuple[dict, list[str]]: +async def update_responses_lines(nodes: dict, responses_lines: list, index: Index) -> Tuple[dict, List[str]]: """Organizes the responses in nodes in a format that json-importer accepts. If the response type is "python", its function will be added to responses_lines to be written @@ -220,18 +226,16 @@ async def update_responses_lines(nodes: dict, responses_lines: list, index: Inde return nodes, responses_lines -async def converter(build_id: int, project_dir: str, custom_dir: str = "custom") -> None: +async def converter(build_id: int) -> None: """Translate frontend flow script into dff script.""" index = get_index() await index.load() index.logger.debug("Loaded index '%s'", index.index) - frontend_graph_path, script_path, custom_conditions_file, custom_responses_file = _get_db_paths( - build_id, Path(project_dir), custom_dir - ) + frontend_graph_path, script_path, custom_conditions_file, custom_responses_file = _get_db_paths(build_id) script = { - "CONFIG": {"custom_dir": "/".join(["..", custom_dir])}, + "CONFIG": {"custom_dir": str("/" / settings.custom_dir)}, } flow_graph: DictConfig = await read_conf(frontend_graph_path) # type: ignore diff --git a/backend/df_designer/app/services/process.py b/backend/chatsky_ui/services/process.py similarity index 98% rename from backend/df_designer/app/services/process.py rename to backend/chatsky_ui/services/process.py index ffa033ce..9ad18522 100644 --- a/backend/df_designer/app/services/process.py +++ b/backend/chatsky_ui/services/process.py @@ -14,10 +14,10 @@ from dotenv import load_dotenv -from app.core.config import settings -from app.core.logger_config import get_logger, setup_logging -from app.db.base import read_conf, write_conf -from app.schemas.process_status import Status +from chatsky_ui.core.config import settings +from chatsky_ui.core.logger_config import get_logger, setup_logging +from chatsky_ui.db.base import read_conf, write_conf +from chatsky_ui.schemas.process_status import Status load_dotenv() diff --git a/backend/df_designer/app/services/process_manager.py b/backend/chatsky_ui/services/process_manager.py similarity index 83% rename from backend/df_designer/app/services/process_manager.py rename to backend/chatsky_ui/services/process_manager.py index eb8fbb9b..bb9e1d13 100644 --- a/backend/df_designer/app/services/process_manager.py +++ b/backend/chatsky_ui/services/process_manager.py @@ -7,26 +7,34 @@ are stored in the `processes` dictionary of process managers. """ from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from omegaconf import OmegaConf -from app.core.config import settings -from app.core.logger_config import get_logger -from app.db.base import read_conf, read_logs -from app.schemas.preset import Preset -from app.schemas.process_status import Status -from app.services.process import BuildProcess, RunProcess - -logger = get_logger(__name__) +from chatsky_ui.core.config import settings +from chatsky_ui.core.logger_config import get_logger +from chatsky_ui.db.base import read_conf, read_logs +from chatsky_ui.schemas.preset import Preset +from chatsky_ui.schemas.process_status import Status +from chatsky_ui.services.process import BuildProcess, RunProcess class ProcessManager: """Base for build and run process managers.""" def __init__(self): - self.processes: Dict[int, BuildProcess | RunProcess] = {} + self.processes: Dict[int, Union[BuildProcess, RunProcess]] = {} self.last_id: int + self._logger = None + + @property + def logger(self): + if self._logger is None: + raise ValueError("Logger has not been configured. Call set_logger() first.") + return self._logger + + def set_logger(self): + self._logger = get_logger(__name__) def get_last_id(self): """Gets the maximum id among processes of type BuildProcess or RunProcess.""" @@ -40,7 +48,7 @@ async def stop(self, id_: int) -> None: RuntimeError: If the process has not started yet. """ if id_ not in self.processes: - logger.error("Process with id '%s' not found in recent running processes", id_) + self.logger.error("Process with id '%s' not found in recent running processes", id_) raise ProcessLookupError try: await self.processes[id_].stop() @@ -76,7 +84,7 @@ async def fetch_process_logs(self, id_: int, offset: int, limit: int, path: Path """Returns the logs of one process according to its id. If the process is not found, returns None.""" process_info = await self.get_process_info(id_, path) if process_info is None: - logger.error("Id '%s' not found", id_) + self.logger.error("Id '%s' not found", id_) return None log_file = Path(process_info["log_path"]) @@ -84,14 +92,14 @@ async def fetch_process_logs(self, id_: int, offset: int, limit: int, path: Path logs = await read_logs(log_file) logs = [log for log in logs if log.strip()] except FileNotFoundError: - logger.error("Log file '%s' not found", log_file) + self.logger.error("Log file '%s' not found", log_file) return None if offset > len(logs): - logger.info("Offset '%s' is out of bounds ('%s' logs found)", offset, len(logs)) + self.logger.info("Offset '%s' is out of bounds ('%s' logs found)", offset, len(logs)) return None # TODO: raise error! - logger.info("Returning %s logs", len(logs)) + self.logger.info("Returning %s logs", len(logs)) return logs[offset : offset + limit] @@ -111,7 +119,11 @@ async def start(self, build_id: int, preset: Preset) -> int: Returns: int: the id of the new started process """ - cmd_to_run = f"dflowd run_bot {build_id} --preset {preset.end_status}" + cmd_to_run = ( + f"chatsky.ui run_bot --build-id {build_id} " + f"--preset {preset.end_status} " + f"--project-dir {settings.work_directory}" + ) self.last_id = max([run["id"] for run in await self.get_full_info(0, 10000)]) self.last_id += 1 id_ = self.last_id @@ -126,8 +138,9 @@ async def get_run_info(self, id_: int) -> Optional[Dict[str, Any]]: """Returns metadata of a specific run process identified by its unique ID.""" return await super().get_process_info(id_, settings.runs_path) - async def get_full_info(self, offset: int, limit: int, path: Path = settings.runs_path) -> List[Dict[str, Any]]: + async def get_full_info(self, offset: int, limit: int, path: Path = None) -> List[Dict[str, Any]]: """Returns metadata of ``limit`` number of run processes, starting from the ``offset``th process.""" + path = path or settings.runs_path return await super().get_full_info(offset, limit, path) async def fetch_run_logs(self, run_id: int, offset: int, limit: int) -> Optional[List[str]]: @@ -157,7 +170,11 @@ async def start(self, preset: Preset) -> int: self.last_id += 1 id_ = self.last_id process = BuildProcess(id_, preset.end_status) - cmd_to_run = f"dflowd build_bot {id_} --preset {preset.end_status}" + cmd_to_run = ( + f"chatsky.ui build_bot --build-id {id_} " + f"--preset {preset.end_status} " + f"--project-dir {settings.work_directory}" + ) await process.start(cmd_to_run) self.processes[id_] = process @@ -183,8 +200,9 @@ async def get_build_info(self, id_: int, run_manager: RunManager) -> Optional[Di builds_info = await self.get_full_info_with_runs_info(run_manager, offset=0, limit=10**5) return next((build for build in builds_info if build["id"] == id_), None) - async def get_full_info(self, offset: int, limit: int, path: Path = settings.builds_path) -> List[Dict[str, Any]]: + async def get_full_info(self, offset: int, limit: int, path: Path = None) -> List[Dict[str, Any]]: """Returns metadata of ``limit`` number of processes, starting from the ``offset`` process.""" + path = path or settings.builds_path return await super().get_full_info(offset, limit, path) async def get_full_info_with_runs_info( diff --git a/backend/df_designer/app/services/websocket_manager.py b/backend/chatsky_ui/services/websocket_manager.py similarity index 75% rename from backend/df_designer/app/services/websocket_manager.py rename to backend/chatsky_ui/services/websocket_manager.py index 4bedbaa2..e1229c6a 100644 --- a/backend/df_designer/app/services/websocket_manager.py +++ b/backend/chatsky_ui/services/websocket_manager.py @@ -3,14 +3,12 @@ """ import asyncio from asyncio.tasks import Task -from typing import Dict, Set +from typing import Dict, List, Set from fastapi import WebSocket, WebSocketDisconnect -from app.core.logger_config import get_logger -from app.services.process_manager import ProcessManager - -logger = get_logger(__name__) +from chatsky_ui.core.logger_config import get_logger +from chatsky_ui.services.process_manager import ProcessManager class WebSocketManager: @@ -18,7 +16,17 @@ class WebSocketManager: def __init__(self): self.pending_tasks: Dict[WebSocket, Set[Task]] = dict() - self.active_connections: list[WebSocket] = [] + self.active_connections: List[WebSocket] = [] + self._logger = None + + @property + def logger(self): + if self._logger is None: + raise ValueError("Logger has not been configured. Call set_logger() first.") + return self._logger + + def set_logger(self): + self._logger = get_logger(__name__) async def connect(self, websocket: WebSocket): """Accepts the websocket connection and marks it as active connection.""" @@ -29,7 +37,7 @@ def disconnect(self, websocket: WebSocket): """Cancels pending tasks of the open websocket process and removes it from active connections.""" # TODO: await websocket.close() if websocket in self.pending_tasks: - logger.info("Cancelling pending tasks") + self.logger.info("Cancelling pending tasks") for task in self.pending_tasks[websocket]: task.cancel() del self.pending_tasks[websocket] @@ -50,7 +58,7 @@ async def send_process_output_to_websocket( break await websocket.send_text(response.decode().strip()) except WebSocketDisconnect: - logger.info("Websocket connection is closed by client") + self.logger.info("Websocket connection is closed by client") except RuntimeError: raise @@ -65,8 +73,8 @@ async def forward_websocket_messages_to_process( break await process_manager.processes[run_id].write_stdin(user_message.encode() + b"\n") except asyncio.CancelledError: - logger.info("Websocket connection is closed") + self.logger.info("Websocket connection is closed") except WebSocketDisconnect: - logger.info("Websocket connection is closed by client") + self.logger.info("Websocket connection is closed by client") except RuntimeError: raise diff --git a/backend/df_designer/app/static/.gitkeep b/backend/chatsky_ui/static/.gitkeep similarity index 100% rename from backend/df_designer/app/static/.gitkeep rename to backend/chatsky_ui/static/.gitkeep diff --git a/backend/df_designer/app/services/__init__.py b/backend/chatsky_ui/tests/__init__.py similarity index 100% rename from backend/df_designer/app/services/__init__.py rename to backend/chatsky_ui/tests/__init__.py diff --git a/backend/df_designer/app/tests/__init__.py b/backend/chatsky_ui/tests/api/__init__.py similarity index 100% rename from backend/df_designer/app/tests/__init__.py rename to backend/chatsky_ui/tests/api/__init__.py diff --git a/backend/df_designer/app/tests/api/test_bot.py b/backend/chatsky_ui/tests/api/test_bot.py similarity index 83% rename from backend/df_designer/app/tests/api/test_bot.py rename to backend/chatsky_ui/tests/api/test_bot.py index 1235d172..164e27d9 100644 --- a/backend/df_designer/app/tests/api/test_bot.py +++ b/backend/chatsky_ui/tests/api/test_bot.py @@ -1,7 +1,7 @@ import pytest from fastapi import BackgroundTasks, HTTPException -from app.api.api_v1.endpoints.bot import ( +from chatsky_ui.api.api_v1.endpoints.bot import ( _check_process_status, _stop_process, check_build_processes, @@ -12,25 +12,24 @@ start_build, start_run, ) -from app.schemas.process_status import Status -from app.services.process_manager import BuildManager, RunManager +from chatsky_ui.schemas.process_status import Status +from chatsky_ui.services.process_manager import RunManager PROCESS_ID = 0 RUN_ID = 42 BUILD_ID = 43 -@pytest.mark.parametrize("process_type, process_manager", [("build", BuildManager), ("run", RunManager)]) @pytest.mark.asyncio -async def test_stop_process_success(mocker, process_type, process_manager): - mock_stop = mocker.AsyncMock() - mocker.patch.object(process_manager, "stop", mock_stop) +async def test_stop_process_success(mocker): + process_manager = mocker.MagicMock() + process_manager.stop = mocker.AsyncMock() # Call the function under test - await _stop_process(PROCESS_ID, process_manager(), process_type) + await _stop_process(PROCESS_ID, process_manager) # Assert the stop method was called once with the correct id - mock_stop.assert_awaited_once_with(PROCESS_ID) + process_manager.stop.assert_awaited_once_with(PROCESS_ID) # TODO: take into consideration the errors when process type is build @@ -79,8 +78,8 @@ async def test_start_build(mocker): @pytest.mark.asyncio async def test_check_build_processes_some_info(mocker, pagination): - build_manager = mocker.MagicMock(spec=BuildManager()) - run_manager = mocker.MagicMock(spec=RunManager()) + build_manager = mocker.AsyncMock() + run_manager = mocker.AsyncMock() await check_build_processes(BUILD_ID, build_manager, run_manager, pagination) @@ -90,8 +89,8 @@ async def test_check_build_processes_some_info(mocker, pagination): @pytest.mark.asyncio async def test_check_build_processes_all_info(mocker, pagination): build_id = None - build_manager = mocker.MagicMock(spec=BuildManager()) - run_manager = mocker.MagicMock(spec=RunManager()) + build_manager = mocker.AsyncMock() + run_manager = mocker.AsyncMock() await check_build_processes(build_id, build_manager, run_manager, pagination) @@ -102,7 +101,7 @@ async def test_check_build_processes_all_info(mocker, pagination): @pytest.mark.asyncio async def test_get_build_logs(mocker, pagination): - build_manager = mocker.MagicMock(spec=BuildManager()) + build_manager = mocker.AsyncMock() await get_build_logs(BUILD_ID, build_manager, pagination) @@ -127,7 +126,7 @@ async def test_start_run(mocker): @pytest.mark.asyncio async def test_check_run_processes_some_info(mocker, pagination): - run_manager = mocker.MagicMock(spec=RunManager()) + run_manager = mocker.AsyncMock() await check_run_processes(RUN_ID, run_manager, pagination) @@ -137,7 +136,7 @@ async def test_check_run_processes_some_info(mocker, pagination): @pytest.mark.asyncio async def test_check_run_processes_all_info(mocker, pagination): run_id = None - run_manager = mocker.MagicMock(spec=RunManager()) + run_manager = mocker.AsyncMock() await check_run_processes(run_id, run_manager, pagination) @@ -146,7 +145,7 @@ async def test_check_run_processes_all_info(mocker, pagination): @pytest.mark.asyncio async def test_get_run_logs(mocker, pagination): - run_manager = mocker.MagicMock(spec=RunManager()) + run_manager = mocker.AsyncMock() await get_run_logs(RUN_ID, run_manager, pagination) diff --git a/backend/df_designer/app/tests/api/test_flows.py b/backend/chatsky_ui/tests/api/test_flows.py similarity index 59% rename from backend/df_designer/app/tests/api/test_flows.py rename to backend/chatsky_ui/tests/api/test_flows.py index ba4ada64..30a631cf 100644 --- a/backend/df_designer/app/tests/api/test_flows.py +++ b/backend/chatsky_ui/tests/api/test_flows.py @@ -2,12 +2,12 @@ import pytest from omegaconf import OmegaConf -from app.api.api_v1.endpoints.flows import flows_get, flows_post +from chatsky_ui.api.api_v1.endpoints.flows import flows_get, flows_post @pytest.mark.asyncio async def test_flows_get(mocker): - mocker.patch("app.api.api_v1.endpoints.flows.read_conf", return_value=OmegaConf.create({"foo": "bar"})) + mocker.patch("chatsky_ui.api.api_v1.endpoints.flows.read_conf", return_value=OmegaConf.create({"foo": "bar"})) response = await flows_get() assert response["status"] == "ok" assert response["data"] == {"foo": "bar"} @@ -15,6 +15,6 @@ async def test_flows_get(mocker): @pytest.mark.asyncio async def test_flows_post(mocker): - mocker.patch("app.api.api_v1.endpoints.flows.write_conf", return_value={}) + mocker.patch("chatsky_ui.api.api_v1.endpoints.flows.write_conf", return_value={}) response = await flows_post({"foo": "bar"}) assert response["status"] == "ok" diff --git a/backend/df_designer/app/tests/conftest.py b/backend/chatsky_ui/tests/conftest.py similarity index 78% rename from backend/df_designer/app/tests/conftest.py rename to backend/chatsky_ui/tests/conftest.py index 0fee92e2..662ec4a2 100644 --- a/backend/df_designer/app/tests/conftest.py +++ b/backend/chatsky_ui/tests/conftest.py @@ -12,12 +12,12 @@ nest_asyncio.apply = lambda: None -from app.main import app -from app.schemas.pagination import Pagination -from app.schemas.preset import Preset -from app.services.process import RunProcess -from app.services.process_manager import BuildManager, RunManager -from app.services.websocket_manager import WebSocketManager +from chatsky_ui.main import app +from chatsky_ui.schemas.pagination import Pagination +from chatsky_ui.schemas.preset import Preset +from chatsky_ui.services.process import RunProcess +from chatsky_ui.services.process_manager import BuildManager, RunManager +from chatsky_ui.services.websocket_manager import WebSocketManager DUMMY_BUILD_ID = -1 @@ -74,7 +74,9 @@ async def _run_process(cmd_to_run): @pytest.fixture() def run_manager(): - return RunManager() + manager = RunManager() + manager.set_logger() + return manager @pytest.fixture() @@ -84,4 +86,6 @@ def build_manager(): @pytest.fixture def websocket_manager(): - return WebSocketManager() + manager = WebSocketManager() + manager.set_logger() + return manager diff --git a/backend/df_designer/app/tests/api/__init__.py b/backend/chatsky_ui/tests/e2e/__init__.py similarity index 100% rename from backend/df_designer/app/tests/api/__init__.py rename to backend/chatsky_ui/tests/e2e/__init__.py diff --git a/backend/df_designer/app/tests/e2e/test_e2e.py b/backend/chatsky_ui/tests/e2e/test_e2e.py similarity index 88% rename from backend/df_designer/app/tests/e2e/test_e2e.py rename to backend/chatsky_ui/tests/e2e/test_e2e.py index 88daf4ce..38ccd983 100644 --- a/backend/df_designer/app/tests/e2e/test_e2e.py +++ b/backend/chatsky_ui/tests/e2e/test_e2e.py @@ -5,13 +5,10 @@ from httpx_ws import aconnect_ws from httpx_ws.transport import ASGIWebSocketTransport -from app.api.deps import get_build_manager, get_run_manager -from app.core.logger_config import get_logger -from app.main import app -from app.schemas.process_status import Status -from app.tests.conftest import override_dependency, start_process - -logger = get_logger(__name__) +from chatsky_ui.api.deps import get_build_manager, get_run_manager +from chatsky_ui.main import app +from chatsky_ui.schemas.process_status import Status +from chatsky_ui.tests.conftest import override_dependency, start_process async def _assert_process_status(response, process_manager): diff --git a/backend/df_designer/app/tests/e2e/__init__.py b/backend/chatsky_ui/tests/integration/__init__.py similarity index 100% rename from backend/df_designer/app/tests/e2e/__init__.py rename to backend/chatsky_ui/tests/integration/__init__.py diff --git a/backend/df_designer/app/tests/integration/test_api_integration.py b/backend/chatsky_ui/tests/integration/test_api_integration.py similarity index 90% rename from backend/df_designer/app/tests/integration/test_api_integration.py rename to backend/chatsky_ui/tests/integration/test_api_integration.py index 53239526..81a1aa69 100644 --- a/backend/df_designer/app/tests/integration/test_api_integration.py +++ b/backend/chatsky_ui/tests/integration/test_api_integration.py @@ -9,11 +9,11 @@ from httpx_ws import aconnect_ws from httpx_ws.transport import ASGIWebSocketTransport -from app.api.deps import get_build_manager, get_run_manager -from app.core.logger_config import get_logger -from app.main import app -from app.schemas.process_status import Status -from app.tests.conftest import override_dependency, start_process +from chatsky_ui.api.deps import get_build_manager, get_run_manager +from chatsky_ui.core.logger_config import get_logger +from chatsky_ui.main import app +from chatsky_ui.schemas.process_status import Status +from chatsky_ui.tests.conftest import override_dependency, start_process load_dotenv() @@ -54,8 +54,15 @@ async def _test_start_process(mocker_obj, get_manager_func, endpoint, preset_end current_status = await _assert_process_status(response, process_manager, expected_end_status, timeout) if current_status == Status.RUNNING: - process_manager.processes[process_manager.last_id].process.terminate() - await process_manager.processes[process_manager.last_id].process.wait() + process = process_manager.processes[process_manager.last_id].process + process.terminate() + try: + await asyncio.wait_for(process.wait(), timeout=timeout) + logger.debug("The test process was gracefully terminated.") + except asyncio.TimeoutError: + process.kill() + await process.wait() + logger.debug("The test process was forcefully killed.") async def _test_stop_process(mocker, get_manager_func, start_endpoint, stop_endpoint): @@ -186,7 +193,7 @@ async def test_connect_to_ws(mocker): # Start a process start_response = await start_process( client, - endpoint=f"http://localhost:8000/api/v1/bot/run/start/{build_id}", + endpoint=f"http://localhost:8007/api/v1/bot/run/start/{build_id}", preset_end_status="success", ) assert start_response.status_code == 201 diff --git a/backend/df_designer/app/tests/integration/__init__.py b/backend/chatsky_ui/tests/services/__init__.py similarity index 100% rename from backend/df_designer/app/tests/integration/__init__.py rename to backend/chatsky_ui/tests/services/__init__.py diff --git a/backend/df_designer/app/tests/services/test_process.py b/backend/chatsky_ui/tests/services/test_process.py similarity index 65% rename from backend/df_designer/app/tests/services/test_process.py rename to backend/chatsky_ui/tests/services/test_process.py index e6bdd273..c0c958db 100644 --- a/backend/df_designer/app/tests/services/test_process.py +++ b/backend/chatsky_ui/tests/services/test_process.py @@ -2,24 +2,17 @@ import pytest -from app.core.logger_config import get_logger -from app.schemas.process_status import Status - -logger = get_logger(__name__) +from chatsky_ui.schemas.process_status import Status class TestRunProcess: - # def test_update_db_info(self, run_process): - # process = await run_process("echo 'Hello df_designer'") - # process.update_db_info() - @pytest.mark.asyncio @pytest.mark.parametrize( "cmd_to_run, status", [ ("sleep 10000", Status.RUNNING), ("false", Status.FAILED), - ("echo Hello df_designer", Status.COMPLETED), + ("echo Hello", Status.COMPLETED), ], ) async def test_check_status(self, run_process, cmd_to_run, status): @@ -39,16 +32,16 @@ async def test_stop(self, run_process): @pytest.mark.asyncio async def test_read_stdout(self, run_process): - process = await run_process("echo Hello df_designer") + process = await run_process("echo Hello") output = await process.read_stdout() - assert output.strip().decode() == "Hello df_designer" + assert output.strip().decode() == "Hello" @pytest.mark.asyncio async def test_write_stdout(self, run_process): process = await run_process("cat") - await process.write_stdin(b"DF_Designer team welcome you.\n") + await process.write_stdin(b"Chatsky-UI team welcome you.\n") output = await process.process.stdout.readline() - assert output.decode().strip() == "DF_Designer team welcome you." + assert output.decode().strip() == "Chatsky-UI team welcome you." # class TestBuildProcess: diff --git a/backend/df_designer/app/tests/services/test_process_manager.py b/backend/chatsky_ui/tests/services/test_process_manager.py similarity index 82% rename from backend/df_designer/app/tests/services/test_process_manager.py rename to backend/chatsky_ui/tests/services/test_process_manager.py index 86b2a414..11799169 100644 --- a/backend/df_designer/app/tests/services/test_process_manager.py +++ b/backend/chatsky_ui/tests/services/test_process_manager.py @@ -1,10 +1,7 @@ -import pytest from pathlib import Path -from omegaconf import OmegaConf - -from app.core.logger_config import get_logger -logger = get_logger(__name__) +import pytest +from omegaconf import OmegaConf RUN_ID = 42 BUILD_ID = 43 @@ -15,7 +12,7 @@ class TestRunManager: async def test_start(self, mocker, preset, run_manager): # noqa: F811 # Mock the RunProcess constructor whereever it's called in # the process_manager file within the scope of this test function - run_process = mocker.patch("app.services.process_manager.RunProcess") + run_process = mocker.patch("chatsky_ui.services.process_manager.RunProcess") run_process_instance = run_process.return_value run_process_instance.start = mocker.AsyncMock() run_manager.get_full_info = mocker.AsyncMock(return_value=[{"id": RUN_ID}]) @@ -23,7 +20,9 @@ async def test_start(self, mocker, preset, run_manager): # noqa: F811 await run_manager.start(build_id=BUILD_ID, preset=preset) run_process.assert_called_once_with(run_manager.last_id, BUILD_ID, preset.end_status) - run_process_instance.start.assert_awaited_once_with(f"dflowd run_bot {BUILD_ID} --preset {preset.end_status}") + run_process_instance.start.assert_awaited_once_with( + f"chatsky.ui run_bot --build-id {BUILD_ID} " f"--preset {preset.end_status} " f"--project-dir ." + ) assert run_manager.processes[run_manager.last_id] is run_process_instance @@ -56,7 +55,7 @@ async def test_get_process_info(self, mocker, run_manager): "status": "stopped", } - read_conf = mocker.patch("app.services.process_manager.read_conf") + read_conf = mocker.patch("chatsky_ui.services.process_manager.read_conf") read_conf.return_value = df_conf run_info = await run_manager.get_run_info(RUN_ID) @@ -77,7 +76,7 @@ async def test_get_full_info(self, mocker, run_manager): "status": "stopped", } - read_conf = mocker.patch("app.services.process_manager.read_conf") + read_conf = mocker.patch("chatsky_ui.services.process_manager.read_conf") read_conf.return_value = df_conf run_info = await run_manager.get_full_info(0, 1) @@ -85,10 +84,10 @@ async def test_get_full_info(self, mocker, run_manager): @pytest.mark.asyncio async def test_fetch_run_logs(self, mocker, run_manager): - LOG_PATH = Path("df_designer/logs/runs/20240425/42_211545.log") + LOG_PATH = Path("path/to/log") run_manager.get_process_info = mocker.AsyncMock(return_value={"id": RUN_ID, "log_path": LOG_PATH}) - read_logs = mocker.patch("app.services.process_manager.read_logs", return_value=["log1", "log2"]) + read_logs = mocker.patch("chatsky_ui.services.process_manager.read_logs", return_value=["log1", "log2"]) logs = await run_manager.fetch_run_logs(RUN_ID, 0, 1) diff --git a/backend/df_designer/app/tests/services/test_websocket_manager.py b/backend/chatsky_ui/tests/services/test_websocket_manager.py similarity index 100% rename from backend/df_designer/app/tests/services/test_websocket_manager.py rename to backend/chatsky_ui/tests/services/test_websocket_manager.py diff --git a/backend/df_designer/app/tests/services/__init__.py b/backend/chatsky_ui/utils/__init__.py similarity index 100% rename from backend/df_designer/app/tests/services/__init__.py rename to backend/chatsky_ui/utils/__init__.py diff --git a/backend/df_designer/app/utils/ast_utils.py b/backend/chatsky_ui/utils/ast_utils.py similarity index 100% rename from backend/df_designer/app/utils/ast_utils.py rename to backend/chatsky_ui/utils/ast_utils.py diff --git a/backend/df_designer/app/api/api_v1/endpoints/config.py b/backend/df_designer/app/api/api_v1/endpoints/config.py deleted file mode 100644 index 97ad716b..00000000 --- a/backend/df_designer/app/api/api_v1/endpoints/config.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import APIRouter - -import toml - -from app.core.config import settings - - -router = APIRouter() - - -@router.get("/version") -async def get_version(): - pyproject = toml.load(settings.pyproject_path) - return pyproject["tool"]["poetry"]["version"] diff --git a/backend/df_designer/app/api/deps.py b/backend/df_designer/app/api/deps.py deleted file mode 100644 index 934e5cd9..00000000 --- a/backend/df_designer/app/api/deps.py +++ /dev/null @@ -1,30 +0,0 @@ -from app.services.index import Index -from app.services.process_manager import BuildManager, RunManager -from app.services.websocket_manager import WebSocketManager - -build_manager = BuildManager() - - -def get_build_manager() -> BuildManager: - return build_manager - - -run_manager = RunManager() - - -def get_run_manager() -> RunManager: - return run_manager - - -websocket_manager = WebSocketManager() - - -def get_websocket_manager() -> WebSocketManager: - return websocket_manager - - -index = Index() - - -def get_index() -> Index: - return index diff --git a/backend/df_designer/app/cli.py b/backend/df_designer/app/cli.py deleted file mode 100644 index 699e4ba5..00000000 --- a/backend/df_designer/app/cli.py +++ /dev/null @@ -1,113 +0,0 @@ -import asyncio -import json -import os -import sys -from pathlib import Path - -import nest_asyncio -import typer -from cookiecutter.main import cookiecutter - -# Patch nest_asyncio before importing DFF -nest_asyncio.apply = lambda: None - -from app.core.config import app_runner, settings # noqa: E402 -from app.core.logger_config import get_logger # noqa: E402 - -cli = typer.Typer() - - -async def _execute_command(command_to_run): - logger = get_logger(__name__) - try: - process = await asyncio.create_subprocess_exec(*command_to_run.split()) - - # Check the return code to determine success - if process.returncode == 0: - logger.info("Command '%s' executed successfully.", command_to_run) - elif process.returncode is None: - logger.info("Process by command '%s' is running.", command_to_run) - await process.wait() - logger.info("Process ended with return code: %d.", process.returncode) - sys.exit(process.returncode) - else: - logger.error("Command '%s' failed with return code: %d", command_to_run, process.returncode) - sys.exit(process.returncode) - - except Exception as e: - logger.error("Error executing '%s': %s", command_to_run, str(e)) - sys.exit(1) - - -def _execute_command_file(build_id: int, project_dir: str, command_file: str, preset: str): - logger = get_logger(__name__) - presets_build_path = Path(project_dir) / "df_designer" / "presets" / command_file - with open(presets_build_path) as file: - presets_build_file = json.load(file) - - if preset in presets_build_file: - command_to_run = presets_build_file[preset]["cmd"] - if preset == "success": - command_to_run += f" {build_id}" - logger.debug("Executing command for preset '%s': %s", preset, command_to_run) - - asyncio.run(_execute_command(command_to_run)) - else: - raise ValueError(f"Invalid preset '{preset}'. Preset must be one of {list(presets_build_file.keys())}") - - -@cli.command("build_bot") -def build_bot(build_id: int, project_dir: str = settings.work_directory, preset: str = "success"): - _execute_command_file(build_id, project_dir, "build.json", preset) - - -@cli.command("build_scenario") -def build_scenario(build_id: int, project_dir: str = "."): - from app.services.json_converter import converter # pylint: disable=C0415 - - asyncio.run(converter(build_id=build_id, project_dir=project_dir)) - - -@cli.command("run_bot") -def run_bot(build_id: int, project_dir: str = settings.work_directory, preset: str = "success"): - _execute_command_file(build_id, project_dir, "run.json", preset) - - -@cli.command("run_scenario") -def run_scenario(build_id: int, project_dir: str = "."): - script_path = Path(project_dir) / "bot" / "scripts" / f"build_{build_id}.yaml" - if not script_path.exists(): - raise FileNotFoundError(f"File {script_path} doesn't exist") - command_to_run = f"poetry run python {project_dir}/app.py --script-path {script_path}" - asyncio.run(_execute_command(command_to_run)) - - -@cli.command("run_app") -def run_app( - ip_address: str = settings.host, - port: int = settings.port, - conf_reload: str = str(settings.conf_reload), - project_dir: str = settings.work_directory, -) -> None: - """Run the backend.""" - settings.host = ip_address - settings.port = port - settings.conf_reload = conf_reload.lower() in ["true", "yes", "t", "y", "1"] - settings.work_directory = project_dir - - app_runner.run() - - -@cli.command("init") -def init(destination: str = settings.work_directory, no_input: bool = False, overwrite_if_exists: bool = True): - original_dir = os.getcwd() - try: - os.chdir(destination) - cookiecutter( - "https://github.com/Ramimashkouk/df_d_template.git", - no_input=no_input, - overwrite_if_exists=overwrite_if_exists, - extra_context={"dflowd_version": "0.1.0b4"}, - ) - finally: - os.chdir(original_dir) diff --git a/backend/df_designer/app/core/config.py b/backend/df_designer/app/core/config.py deleted file mode 100644 index 254f9a1a..00000000 --- a/backend/df_designer/app/core/config.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -from pathlib import Path - -import uvicorn -from dotenv import load_dotenv -from pydantic_settings import BaseSettings - -load_dotenv() - - -class Settings(BaseSettings): - API_V1_STR: str = "/api/v1" - APP: str = "app.main:app" - - work_directory: str = "." - config_file_path: Path = Path(__file__).absolute() - static_files: Path = config_file_path.parent.with_name("static") - start_page: Path = static_files.joinpath("index.html") - package_dir: Path = config_file_path.parents[3] - pyproject_path: Path = package_dir / "df_designer" / "pyproject.toml" - - host: str = os.getenv("HOST", "0.0.0.0") - port: int = int(os.getenv("PORT", 8000)) - log_level: str = os.getenv("LOG_LEVEL", "info") - conf_reload: bool = os.getenv("CONF_RELOAD", "true").lower() in ["true", "1", "t", "y", "yes"] - - builds_path: Path = Path(f"{work_directory}/df_designer/builds.yaml") - runs_path: Path = Path(f"{work_directory}/df_designer/runs.yaml") - dir_logs: Path = Path(f"{work_directory}/df_designer/logs") - frontend_flows_path: Path = Path(f"{work_directory}/df_designer/frontend_flows.yaml") - index_path: Path = Path(f"{work_directory}/bot/custom/.services_index.yaml") - snippet2lint_path: Path = Path(f"{work_directory}/bot/custom/.snippet2lint.py") - - -class AppRunner: - def __init__(self, settings: Settings): - self.settings = settings - - def run(self): - if reload := self.settings.conf_reload: - reload_conf = { - "reload": reload, - "reload_dirs": [self.settings.work_directory, str(self.settings.package_dir)], - } - else: - reload_conf = {"reload": reload} - - uvicorn.run( - self.settings.APP, - host=self.settings.host, - port=self.settings.port, - log_level=self.settings.log_level, - **reload_conf, - ) - - -settings = Settings() -app_runner = AppRunner(settings) diff --git a/backend/df_designer/app/utils/__init__.py b/backend/df_designer/app/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/df_designer/poetry.lock b/backend/poetry.lock similarity index 99% rename from backend/df_designer/poetry.lock rename to backend/poetry.lock index 12c0be0f..d9412bf7 100644 --- a/backend/df_designer/poetry.lock +++ b/backend/poetry.lock @@ -33,6 +33,9 @@ files = [ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -153,6 +156,7 @@ mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -1012,6 +1016,7 @@ mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] @@ -1214,6 +1219,7 @@ files = [ [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -1426,6 +1432,7 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] @@ -1441,17 +1448,6 @@ files = [ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -1867,5 +1863,5 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "c00f7a8ff2d1dabc5a7cfa165e191ecbbc82198eead23946a4e7e76b78015728" +python-versions = "^3.8.1" +content-hash = "eacc9c09bcf6fc53ade1a2613baf756e347fb17a27296fce3ea76b498c7d3211" diff --git a/backend/df_designer/pyproject.toml b/backend/pyproject.toml similarity index 64% rename from backend/df_designer/pyproject.toml rename to backend/pyproject.toml index fca26ac6..acb2af8d 100644 --- a/backend/df_designer/pyproject.toml +++ b/backend/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] -name = "dflowd" -version = "0.1.0b4" -description = "Dialog Flow Designer" +name = "chatsky-ui" +version = "0.2.0" +description = "Chatsky-UI is GUI for Chatsky Framework, that is a free and open-source software stack for creating chatbots, released under the terms of Apache License 2.0." license = "Apache-2.0" authors = [ "Denis Kuznetsov ", @@ -9,10 +9,10 @@ authors = [ "Rami Mashkouk ", ] readme = "README.md" -packages = [{include = "app"}] +packages = [{include = "chatsky_ui"}] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.8.1" fastapi = "^0.110.0" uvicorn = {extras = ["standard"], version = "^0.28.0"} pydantic = "^2.6.3" @@ -28,12 +28,11 @@ pytest-mock = "^3.14.0" httpx = "^0.27.0" httpx-ws = "^0.6.0" pylint = "^3.2.3" -sphinx = "^7.3.7" -sphinx-rtd-theme = "^2.0.0" -toml = "^0.10.2" +sphinx = { version = "*", python = "^3.10"} +sphinx-rtd-theme = { version = "*", python = "^3.10"} [tool.poetry.scripts] -dflowd = "app.cli:cli" +"chatsky.ui" = "chatsky_ui.cli:cli" [tool.poetry.group.lint] optional = true diff --git a/backend/df_designer/run.sh b/backend/run.sh similarity index 71% rename from backend/df_designer/run.sh rename to backend/run.sh index a85b4608..272e1b74 100644 --- a/backend/df_designer/run.sh +++ b/backend/run.sh @@ -1,6 +1,6 @@ #!/bin/sh -export APP_MODULE=${APP_MODULE-app.main:app} +export APP_MODULE=${APP_MODULE-chatsky_ui.main:app} export HOST=${HOST:-0.0.0.0} export PORT=${PORT:-8001} diff --git a/bin/add_ui_to_toml.sh b/bin/add_ui_to_toml.sh new file mode 100755 index 00000000..a0be2353 --- /dev/null +++ b/bin/add_ui_to_toml.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Find the latest version of the wheel file +VERSION=$(basename $(ls ../backend/dist/chatsky_ui-*.whl) | sed -E 's/chatsky_ui-([^-]+)-.*/\1/' | head -n 1) + +# Add the specific version to my project +poetry add ../backend/dist/chatsky_ui-$VERSION-py3-none-any.whl diff --git a/compose.yaml b/compose.yaml index 9a4b6c76..a1295ea9 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,15 +1,17 @@ volumes: - project_data: + ui_data: + bot_data: services: backend: build: args: - PROJECT_DIR: df_designer_project + PROJECT_DIR: my_project context: ./ dockerfile: Dockerfile ports: - 8000:8000 volumes: - - project_data:/src2/df_designer_project + - ui_data:/src/project_dir/chatsky_ui/app_data + - bot_data:/src/project_dir/bot version: '3.8' diff --git a/docs/appref/app/api/api_v1/endpoints.rst b/docs/appref/app/api/api_v1/endpoints.rst deleted file mode 100644 index 09e45e10..00000000 --- a/docs/appref/app/api/api_v1/endpoints.rst +++ /dev/null @@ -1,26 +0,0 @@ -app.api.api\_v1.endpoints package -========================= - -app.api.api\_v1.endpoints.bot module ----------------------------- - -.. automodule:: app.api.api_v1.endpoints.bot - :members: - :undoc-members: - :show-inheritance: - -app.api.api\_v1.endpoints.dff\_services module --------------------------------------- - -.. automodule:: app.api.api_v1.endpoints.dff_services - :members: - :undoc-members: - :show-inheritance: - -app.api.api\_v1.endpoints.flows module ------------------------------- - -.. automodule:: app.api.api_v1.endpoints.flows - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/appref/app/services.rst b/docs/appref/app/services.rst deleted file mode 100644 index 6fef525f..00000000 --- a/docs/appref/app/services.rst +++ /dev/null @@ -1,42 +0,0 @@ -app.services package -==================== - -app.services.index module -------------------------- - -.. automodule:: app.services.index - :members: - :undoc-members: - :show-inheritance: - -app.services.json\_converter module ------------------------------------ - -.. automodule:: app.services.json_converter - :members: - :undoc-members: - :show-inheritance: - -app.services.process module ---------------------------- - -.. automodule:: app.services.process - :members: - :undoc-members: - :show-inheritance: - -app.services.process\_manager module ------------------------------------- - -.. automodule:: app.services.process_manager - :members: - :undoc-members: - :show-inheritance: - -app.services.websocket\_manager module --------------------------------------- - -.. automodule:: app.services.websocket_manager - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/appref/app/tests/api.rst b/docs/appref/app/tests/api.rst deleted file mode 100644 index fffb1a50..00000000 --- a/docs/appref/app/tests/api.rst +++ /dev/null @@ -1,18 +0,0 @@ -app.tests.api package -===================== - -app.tests.api.test\_bot module ------------------------------- - -.. automodule:: app.tests.api.test_bot - :members: - :undoc-members: - :show-inheritance: - -app.tests.api.test\_flows module --------------------------------- - -.. automodule:: app.tests.api.test_flows - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/appref/app/tests/e2e.rst b/docs/appref/app/tests/e2e.rst deleted file mode 100644 index 7f703ae7..00000000 --- a/docs/appref/app/tests/e2e.rst +++ /dev/null @@ -1,10 +0,0 @@ -app.tests.e2e package -===================== - -app.tests.e2e.test\_e2e module ------------------------------- - -.. automodule:: app.tests.e2e.test_e2e - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/appref/app/tests/integration.rst b/docs/appref/app/tests/integration.rst deleted file mode 100644 index 915763d1..00000000 --- a/docs/appref/app/tests/integration.rst +++ /dev/null @@ -1,10 +0,0 @@ -app.tests.integration package -============================= - -app.tests.integration.test\_api\_integration module ---------------------------------------------------- - -.. automodule:: app.tests.integration.test_api_integration - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/appref/app/tests/services.rst b/docs/appref/app/tests/services.rst deleted file mode 100644 index 047db33b..00000000 --- a/docs/appref/app/tests/services.rst +++ /dev/null @@ -1,26 +0,0 @@ -app.tests.services package -========================== - -app.tests.services.test\_process module ---------------------------------------- - -.. automodule:: app.tests.services.test_process - :members: - :undoc-members: - :show-inheritance: - -app.tests.services.test\_process\_manager module ------------------------------------------------- - -.. automodule:: app.tests.services.test_process_manager - :members: - :undoc-members: - :show-inheritance: - -app.tests.services.test\_websocket\_manager module --------------------------------------------------- - -.. automodule:: app.tests.services.test_websocket_manager - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/appref/app.rst b/docs/appref/chatsky_ui.rst similarity index 71% rename from docs/appref/app.rst rename to docs/appref/chatsky_ui.rst index 90b85e2a..95807256 100644 --- a/docs/appref/app.rst +++ b/docs/appref/chatsky_ui.rst @@ -1,4 +1,4 @@ -app package +chatsky_ui package =========== Subpackages @@ -8,13 +8,13 @@ Subpackages :glob: :maxdepth: 4 - app/* + chatsky_ui/* cli module -------------- -.. automodule:: app.cli +.. automodule:: chatsky_ui.cli :members: :undoc-members: :show-inheritance: @@ -22,7 +22,7 @@ cli module main module --------------- -.. automodule:: app.main +.. automodule:: chatsky_ui.main :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/app/api.rst b/docs/appref/chatsky_ui/api.rst similarity index 74% rename from docs/appref/app/api.rst rename to docs/appref/chatsky_ui/api.rst index 34d1dae1..51bc2fea 100644 --- a/docs/appref/app/api.rst +++ b/docs/appref/chatsky_ui/api.rst @@ -11,10 +11,10 @@ Subpackages api/* -app.api.deps module +chatsky_ui.api.deps module ------------------ -.. automodule:: app.api.deps +.. automodule:: chatsky_ui.api.deps :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/app/api/api_v1.rst b/docs/appref/chatsky_ui/api/api_v1.rst similarity index 63% rename from docs/appref/app/api/api_v1.rst rename to docs/appref/chatsky_ui/api/api_v1.rst index 72e21f51..19aac900 100644 --- a/docs/appref/app/api/api_v1.rst +++ b/docs/appref/chatsky_ui/api/api_v1.rst @@ -1,4 +1,4 @@ -app.api.api\_v1 package +chatsky_ui.api.api\_v1 package ========================= Subpackages @@ -11,10 +11,10 @@ Subpackages api_v1/* -app.api.api\_v1.api module +chatsky_ui.api.api\_v1.api module ---------------------------- -.. automodule:: app.api.api_v1.api +.. automodule:: chatsky_ui.api.api_v1.api :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/chatsky_ui/api/api_v1/endpoints.rst b/docs/appref/chatsky_ui/api/api_v1/endpoints.rst new file mode 100644 index 00000000..f450bd5e --- /dev/null +++ b/docs/appref/chatsky_ui/api/api_v1/endpoints.rst @@ -0,0 +1,26 @@ +chatsky_ui.api.api\_v1.endpoints package +========================= + +chatsky_ui.api.api\_v1.endpoints.bot module +---------------------------- + +.. automodule:: chatsky_ui.api.api_v1.endpoints.bot + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.api.api\_v1.endpoints.dff\_services module +-------------------------------------- + +.. automodule:: chatsky_ui.api.api_v1.endpoints.dff_services + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.api.api\_v1.endpoints.flows module +------------------------------ + +.. automodule:: chatsky_ui.api.api_v1.endpoints.flows + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/appref/app/clients.rst b/docs/appref/chatsky_ui/clients.rst similarity index 50% rename from docs/appref/app/clients.rst rename to docs/appref/chatsky_ui/clients.rst index bc25a2de..0a4b419d 100644 --- a/docs/appref/app/clients.rst +++ b/docs/appref/chatsky_ui/clients.rst @@ -1,10 +1,10 @@ -app.clients package +chatsky_ui.clients package =================== -app.clients.dff module +chatsky_ui.clients.dff module ---------------------- -.. automodule:: app.clients.dff +.. automodule:: chatsky_ui.clients.dff :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/app/core.rst b/docs/appref/chatsky_ui/core.rst similarity index 50% rename from docs/appref/app/core.rst rename to docs/appref/chatsky_ui/core.rst index 57ae4092..b3b64803 100644 --- a/docs/appref/app/core.rst +++ b/docs/appref/chatsky_ui/core.rst @@ -1,18 +1,18 @@ -app.core package +chatsky_ui.core package ================ -app.core.config module +chatsky_ui.core.config module ---------------------- -.. automodule:: app.core.config +.. automodule:: chatsky_ui.core.config :members: :undoc-members: :show-inheritance: -app.core.logger\_config module +chatsky_ui.core.logger\_config module ------------------------------ -.. automodule:: app.core.logger_config +.. automodule:: chatsky_ui.core.logger_config :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/app/db.rst b/docs/appref/chatsky_ui/db.rst similarity index 52% rename from docs/appref/app/db.rst rename to docs/appref/chatsky_ui/db.rst index 653573b8..e8bc4174 100644 --- a/docs/appref/app/db.rst +++ b/docs/appref/chatsky_ui/db.rst @@ -1,10 +1,10 @@ -app.db package +chatsky_ui.db package ============== -app.db.base module +chatsky_ui.db.base module ------------------ -.. automodule:: app.db.base +.. automodule:: chatsky_ui.db.base :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/app/schemas.rst b/docs/appref/chatsky_ui/schemas.rst similarity index 50% rename from docs/appref/app/schemas.rst rename to docs/appref/chatsky_ui/schemas.rst index 300c2a23..c20a0874 100644 --- a/docs/appref/app/schemas.rst +++ b/docs/appref/chatsky_ui/schemas.rst @@ -1,26 +1,26 @@ -app.schemas package +chatsky_ui.schemas package =================== -app.schemas.pagination module +chatsky_ui.schemas.pagination module ----------------------------- -.. automodule:: app.schemas.pagination +.. automodule:: chatsky_ui.schemas.pagination :members: :undoc-members: :show-inheritance: -app.schemas.preset module +chatsky_ui.schemas.preset module ------------------------- -.. automodule:: app.schemas.preset +.. automodule:: chatsky_ui.schemas.preset :members: :undoc-members: :show-inheritance: -app.schemas.process\_status module +chatsky_ui.schemas.process\_status module ---------------------------------- -.. automodule:: app.schemas.process_status +.. automodule:: chatsky_ui.schemas.process_status :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/chatsky_ui/services.rst b/docs/appref/chatsky_ui/services.rst new file mode 100644 index 00000000..3be7e826 --- /dev/null +++ b/docs/appref/chatsky_ui/services.rst @@ -0,0 +1,42 @@ +chatsky_ui.services package +==================== + +chatsky_ui.services.index module +------------------------- + +.. automodule:: chatsky_ui.services.index + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.services.json\_converter module +----------------------------------- + +.. automodule:: chatsky_ui.services.json_converter + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.services.process module +--------------------------- + +.. automodule:: chatsky_ui.services.process + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.services.process\_manager module +------------------------------------ + +.. automodule:: chatsky_ui.services.process_manager + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.services.websocket\_manager module +-------------------------------------- + +.. automodule:: chatsky_ui.services.websocket_manager + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/appref/app/tests.rst b/docs/appref/chatsky_ui/tests.rst similarity index 64% rename from docs/appref/app/tests.rst rename to docs/appref/chatsky_ui/tests.rst index a960dcbb..fe6b4a95 100644 --- a/docs/appref/app/tests.rst +++ b/docs/appref/chatsky_ui/tests.rst @@ -1,4 +1,4 @@ -app.tests package +chatsky_ui.tests package ================= Subpackages @@ -11,10 +11,10 @@ Subpackages tests/* -app.tests.conftest module +chatsky_ui.tests.conftest module ------------------------- -.. automodule:: app.tests.conftest +.. automodule:: chatsky_ui.tests.conftest :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/chatsky_ui/tests/api.rst b/docs/appref/chatsky_ui/tests/api.rst new file mode 100644 index 00000000..c123e1fe --- /dev/null +++ b/docs/appref/chatsky_ui/tests/api.rst @@ -0,0 +1,18 @@ +chatsky_ui.tests.api package +===================== + +chatsky_ui.tests.api.test\_bot module +------------------------------ + +.. automodule:: chatsky_ui.tests.api.test_bot + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.tests.api.test\_flows module +-------------------------------- + +.. automodule:: chatsky_ui.tests.api.test_flows + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/appref/chatsky_ui/tests/e2e.rst b/docs/appref/chatsky_ui/tests/e2e.rst new file mode 100644 index 00000000..946e388f --- /dev/null +++ b/docs/appref/chatsky_ui/tests/e2e.rst @@ -0,0 +1,10 @@ +chatsky_ui.tests.e2e package +===================== + +chatsky_ui.tests.e2e.test\_e2e module +------------------------------ + +.. automodule:: chatsky_ui.tests.e2e.test_e2e + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/appref/chatsky_ui/tests/integration.rst b/docs/appref/chatsky_ui/tests/integration.rst new file mode 100644 index 00000000..21146a99 --- /dev/null +++ b/docs/appref/chatsky_ui/tests/integration.rst @@ -0,0 +1,10 @@ +chatsky_ui.tests.integration package +============================= + +chatsky_ui.tests.integration.test\_api\_integration module +--------------------------------------------------- + +.. automodule:: chatsky_ui.tests.integration.test_api_integration + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/appref/chatsky_ui/tests/services.rst b/docs/appref/chatsky_ui/tests/services.rst new file mode 100644 index 00000000..1de9f52c --- /dev/null +++ b/docs/appref/chatsky_ui/tests/services.rst @@ -0,0 +1,26 @@ +chatsky_ui.tests.services package +========================== + +chatsky_ui.tests.services.test\_process module +--------------------------------------- + +.. automodule:: chatsky_ui.tests.services.test_process + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.tests.services.test\_process\_manager module +------------------------------------------------ + +.. automodule:: chatsky_ui.tests.services.test_process_manager + :members: + :undoc-members: + :show-inheritance: + +chatsky_ui.tests.services.test\_websocket\_manager module +-------------------------------------------------- + +.. automodule:: chatsky_ui.tests.services.test_websocket_manager + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index cc8a1c7c..7fc365ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,10 +10,10 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'DflowD' +project = 'Chatsky-UI' copyright = '2024, Denis Kuznetsov, Maks Rogatkin, Rami Mashkouk' author = 'Denis Kuznetsov, Maks Rogatkin, Rami Mashkouk' -release = '0.1.0-beta.1' +release = '0.2.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/maste r/usage/configuration.html#general-configuration diff --git a/docs/index.rst b/docs/index.rst index 98657bb0..61d1b181 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,9 @@ -.. DflowD documentation master file, created by +.. Chatsky-UI documentation master file, created by sphinx-quickstart on Tue Jun 18 10:41:20 2024. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to DflowD's documentation! +Welcome to Chatsky-UI's documentation! ================================== .. toctree:: diff --git a/frontend/dockerfile b/frontend/dockerfile deleted file mode 100644 index 7c13de21..00000000 --- a/frontend/dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM oven/bun:1 as base -WORKDIR /app - -COPY package*.json ./ -COPY bun.lockb ./ - -RUN bun install - -COPY ./ ./ \ No newline at end of file diff --git a/frontend/src/pages/Logs.tsx b/frontend/src/pages/Logs.tsx index d6e1c479..6b216920 100644 --- a/frontend/src/pages/Logs.tsx +++ b/frontend/src/pages/Logs.tsx @@ -197,7 +197,7 @@ const Logs = memo(() => { Logs file path: {currentItem.log_path} @@ -248,7 +248,7 @@ const Logs = memo(() => { Logs file path: {currentItem.log_path}