diff --git a/.gitignore b/.gitignore index 1e4df31..c8acf44 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ __pycache__ infrastructure/ nginx/ prd/ -.vercel \ No newline at end of file +.vercel + +venv/ +.venv/ \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..6ac2314 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v24.11.1 \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..7acdc73 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13.7 \ No newline at end of file diff --git a/api/.env.example b/api/.env.example index c9451a4..39d9040 100644 --- a/api/.env.example +++ b/api/.env.example @@ -5,14 +5,14 @@ LOG_LEVEL="DEBUG" # Change these values if you deploy the backend and frontend # on any hosting provider with some DNS. Please ensure to # provide the URL with scheme like http or https -# BACKEND_API_ENDPOINT: "http://localhost:8000" -# UI_APP_URL: "http://localhost:3010" +BACKEND_API_ENDPOINT="http://localhost:8000" +UI_APP_URL="http://localhost:3000" # Database Configuration DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/postgres" REDIS_URL="redis://:redissecret@localhost:6379" -# AWS S3 Configuration (required for SaaS mode) +# AWS S3 Configuration ENABLE_AWS_S3="false" # AWS_ACCESS_KEY_ID="" # AWS_SECRET_ACCESS_KEY="" diff --git a/api/routes/workflow.py b/api/routes/workflow.py index 0a05ee2..e6ebde9 100644 --- a/api/routes/workflow.py +++ b/api/routes/workflow.py @@ -600,7 +600,7 @@ async def get_workflow_run( "call_duration_seconds": int( round(run.cost_info.get("call_duration_seconds")) ) - if run.cost_info + if run.cost_info and run.cost_info.get("call_duration_seconds") is not None else None, } if run.cost_info diff --git a/docker-compose-local.yaml b/docker-compose-local.yaml index 1145451..45a83ec 100644 --- a/docker-compose-local.yaml +++ b/docker-compose-local.yaml @@ -38,9 +38,31 @@ services: networks: - app-network + minio: + image: minio/minio + container_name: minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + MINIO_API_CORS_ALLOW_ORIGIN: "*" + ports: + - "127.0.0.1:9000:9000" # Bind to localhost explicitly + - "127.0.0.1:9001:9001" + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - app-network + volumes: postgres_data: redis_data: + minio_data: networks: app-network: diff --git a/docs/contribution/introduction.mdx b/docs/contribution/introduction.mdx new file mode 100644 index 0000000..0c8b45b --- /dev/null +++ b/docs/contribution/introduction.mdx @@ -0,0 +1,4 @@ +--- +title: Contribution +description: If you would like to set up the development environment and use a coding agent like Claude to make changes to the codebase, you can follow this document to help setup the right development environment for yourself. +--- \ No newline at end of file diff --git a/docs/contribution/setup.mdx b/docs/contribution/setup.mdx new file mode 100644 index 0000000..c6c7ae8 --- /dev/null +++ b/docs/contribution/setup.mdx @@ -0,0 +1,56 @@ +--- +title: Setting Up +description: You can use this document to setup the dev environment for yourself. +--- + +If the below steps do not work out for you, it would be great if you can open an issue on [Github](https://github.com/dograh-hq/dograh/issues). + + +### System Requirements +- git to clone the forked repository +- Node.js 24 to run the UI (we recommend using [NVM](https://github.com/nvm-sh/nvm) to manage your node versions locally) +- Python 3.13 to run the backend +- Docker to run the database and redis cache locally + +### Steps +1. Fork the Dograh repository by going to https://github.com/dograh-hq/dograh +2. Clone the forked repository on your machine +``` +git clone https://github.com//dograh +cd dograh +``` +3. Create a python virtual environment +``` +python3 -m venv venv +source venv/bin/activate +``` +4. Install the requirements +``` +pip install -r api/requirements.txt +``` +5. Ensure you are on right version of Node.js using `node --version` +``` +nvm use 24 +``` +6. Install UI dependencies +``` +cd ui && npm install +``` +7. Start local docker services after making sure you dont have any other instance of postgres or redis running by checking `docker ps` +``` +cd .. && docker compose -f docker-compose-local.yaml up +``` +8. Setup environment variables +`` +cp api/.env.example api/.env && cp ui/.env.example ui/.env +`` +9. Setup pipecat +``` +bash scripts/setup_pipecat.sh +``` +10. Start backend services +If you wish to start the services in debug mode, we ship a launch.json file which you can use in VSCode. +``` +bash scripts/start_services.sh +``` +11. diff --git a/docs/docs.json b/docs/docs.json index b9ff93a..e005bcd 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -54,6 +54,13 @@ } ] }, + { + "group": "Contribution", + "pages": [ + "contribution/introduction", + "contribution/setup" + ] + }, { "group": "Deployment", "pages": [ diff --git a/scripts/start_services.sh b/scripts/start_services.sh index 98d7239..670109f 100755 --- a/scripts/start_services.sh +++ b/scripts/start_services.sh @@ -1,6 +1,42 @@ #!/usr/bin/env bash set -e # Exit on error +############################################################################### +### ARGUMENT PARSING +############################################################################### + +DEV_MODE=false + +show_help() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --dev Enable development mode with auto-reload for API changes" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Start in production mode" + echo " $0 --dev # Start in development mode with auto-reload" +} + +while [[ $# -gt 0 ]]; do + case $1 in + --dev) + DEV_MODE=true + shift + ;; + --help|-h) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac +done + ############################################################################### ### CONFIGURATION ############################################################################### @@ -21,7 +57,12 @@ ARQ_WORKERS=${ARQ_WORKERS:-1} # Log startup cd "$BASE_DIR" -echo "Starting Dograh Services at $(date) in BASE_DIR: ${BASE_DIR}" +if $DEV_MODE; then + echo "Starting Dograh Services (DEV MODE) at $(date) in BASE_DIR: ${BASE_DIR}" + echo "Auto-reload enabled for api/ directory changes" +else + echo "Starting Dograh Services at $(date) in BASE_DIR: ${BASE_DIR}" +fi ############################################################################### ### 1) Load environment variables @@ -47,10 +88,19 @@ SERVICE_NAMES=( "uvicorn" ) +# Build uvicorn command based on mode +if $DEV_MODE; then + # Dev mode: single worker with auto-reload (--reload is incompatible with --workers > 1) + UVICORN_CMD="uvicorn api.app:app --host 0.0.0.0 --port $FASTAPI_PORT --reload --reload-dir api" +else + # Production mode: multiple workers, no reload + UVICORN_CMD="uvicorn api.app:app --host 0.0.0.0 --port $FASTAPI_PORT --workers $FASTAPI_WORKERS" +fi + SERVICE_COMMANDS=( "python -m api.services.telephony.ari_manager" "python -m api.services.campaign.campaign_orchestrator" - "uvicorn api.app:app --host 0.0.0.0 --port $FASTAPI_PORT --workers $FASTAPI_WORKERS" + "$UVICORN_CMD" ) # Add ARQ workers dynamically @@ -208,14 +258,21 @@ done echo echo "──────────────────────────────────────────────────" +if $DEV_MODE; then + echo "Mode: DEVELOPMENT (auto-reload enabled)" +else + echo "Mode: PRODUCTION" +fi +echo "" for name in "${SERVICE_NAMES[@]}"; do pid=$(<"$RUN_DIR/$name.pid") echo "✓ $name (PID $pid) → $LOG_DIR/$name.log" done +echo "" echo " Rotation: ${LOG_ROTATION_SIZE:-100 MB}" echo " Retention: ${LOG_RETENTION:-7 days}" echo " Compression: ${LOG_COMPRESSION:-gz}" echo "Logs: tail -f $LOG_DIR/*.log" echo "Rotated logs: ls $LOG_DIR/*.log.*" -echo "To stop: run this script again or kill -TERM - for process groups" +echo "To stop: ./scripts/stop_services.sh" echo "──────────────────────────────────────────────────" diff --git a/scripts/stop_services.sh b/scripts/stop_services.sh new file mode 100755 index 0000000..887e1aa --- /dev/null +++ b/scripts/stop_services.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +set -e # Exit on error + +############################################################################### +### CONFIGURATION +############################################################################### + +# Determine BASE_DIR as parent of the scripts directory +BASE_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" + +RUN_DIR="$BASE_DIR/run" # Where we keep *.pid + +cd "$BASE_DIR" +echo "Stopping Dograh Services at $(date) in BASE_DIR: ${BASE_DIR}" + +############################################################################### +### HELPER FUNCTIONS +############################################################################### + +# Function to get all descendant PIDs of a process (children, grandchildren, etc.) +get_descendants() { + local parent_pid=$1 + local descendants="" + local children + + # Get direct children + children=$(pgrep -P "$parent_pid" 2>/dev/null || true) + + for child in $children; do + # Recursively get descendants of each child + descendants="$descendants $child $(get_descendants "$child")" + done + + echo "$descendants" +} + +# Function to kill a process and all its descendants +kill_process_tree() { + local pid=$1 + local signal=$2 + local descendants + + descendants=$(get_descendants "$pid") + + # Kill children first (bottom-up), then parent + for desc_pid in $descendants; do + if kill -0 "$desc_pid" 2>/dev/null; then + kill "$signal" "$desc_pid" 2>/dev/null || true + fi + done + + # Kill the parent + if kill -0 "$pid" 2>/dev/null; then + kill "$signal" "$pid" 2>/dev/null || true + fi +} + +############################################################################### +### STOP SERVICES +############################################################################### + +if [[ ! -d "$RUN_DIR" ]]; then + echo "No run directory found at $RUN_DIR" + echo "No services appear to be running." + exit 0 +fi + +# Find all PID files in the run directory +pid_files=("$RUN_DIR"/*.pid) + +# Check if any PID files exist +if [[ ! -e "${pid_files[0]}" ]]; then + echo "No PID files found in $RUN_DIR" + echo "No services appear to be running." + exit 0 +fi + +stopped_count=0 +failed_count=0 + +for pidfile in "${pid_files[@]}"; do + # Extract service name from pidfile path + name=$(basename "$pidfile" .pid) + + if [[ -f "$pidfile" ]]; then + oldpid=$(<"$pidfile") + + if kill -0 "$oldpid" 2>/dev/null; then + echo "Stopping $name (PID $oldpid and all descendants)..." + + # Kill the entire process tree (parent + all descendants) + kill_process_tree "$oldpid" "-TERM" + sleep 4 + + # Check if parent or any descendants are still alive + still_alive=false + if kill -0 "$oldpid" 2>/dev/null; then + still_alive=true + else + for desc_pid in $(get_descendants "$oldpid"); do + if kill -0 "$desc_pid" 2>/dev/null; then + still_alive=true + break + fi + done + fi + + if $still_alive; then + echo " Warning: $name did not exit cleanly, forcing stop..." + kill_process_tree "$oldpid" "-KILL" + sleep 1 + + # Final check + if kill -0 "$oldpid" 2>/dev/null; then + echo " Error: Failed to stop $name (PID $oldpid)" + ((failed_count++)) + else + echo " Stopped $name (forced)" + ((stopped_count++)) + fi + else + echo " Stopped $name" + ((stopped_count++)) + fi + else + echo "Service $name (PID $oldpid) is not running" + fi + + rm -f "$pidfile" + fi +done + +# Clean up any port tracking files for uvicorn +rm -f "$RUN_DIR/uvicorn.port" "$RUN_DIR/uvicorn_new.port" "$RUN_DIR/uvicorn_old.pid" + +############################################################################### +### SUMMARY +############################################################################### + +echo +echo "──────────────────────────────────────────────────" +echo "Stopped $stopped_count service(s)" +if [[ $failed_count -gt 0 ]]; then + echo "Failed to stop $failed_count service(s)" +fi +echo "──────────────────────────────────────────────────" diff --git a/ui/.env.example b/ui/.env.example new file mode 100644 index 0000000..284108b --- /dev/null +++ b/ui/.env.example @@ -0,0 +1,6 @@ +NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 +BACKEND_URL=http://localhost:8000 + +NEXT_PUBLIC_NODE_ENV=development +NEXT_PUBLIC_DEPLOYMENT_MODE: "oss" +NEXT_PUBLIC_AUTH_PROVIDER="local"