diff --git a/README.md b/README.md index 3389fef..e19c5ea 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,8 @@ ## 🚀 Быстрый старт -### Запуск одной командой: - **Windows:** -```bash +```cmd start.bat ``` @@ -19,13 +17,15 @@ chmod +x start.sh ./start.sh ``` -Это автоматически: -- ✅ Проверит зависимости -- ✅ Установит пакеты -- ✅ Соберет frontend -- ✅ Запустит сервер +Скрипт: +1. Соберет фронтенд в `backend/public` +2. Запустит backend на http://localhost:8000 +3. Фронтенд отдается с бэкенда -**Готово!** Откройте http://localhost:8000 +**Один процесс, один порт, как в production.** + +Перезапуск после изменений: +- Просто запусти скрипт заново (Ctrl+C → start.bat) --- diff --git a/RUN.bat b/RUN.bat new file mode 100644 index 0000000..00d6894 --- /dev/null +++ b/RUN.bat @@ -0,0 +1,55 @@ +@echo off +REM =============================== +REM AI Review - Simple Launcher +REM =============================== + +title AI Review + +echo. +echo ================================ +echo AI Review - Starting +echo ================================ +echo. + +REM Переходим в корень проекта +cd /d "%~dp0" + +REM Собираем фронтенд +echo [1/3] Building frontend... +cd frontend +if not exist "node_modules\" npm install +call npm run build +if %ERRORLEVEL% NEQ 0 ( + echo. + echo [ERROR] Build failed! + pause + exit /b 1 +) +cd .. + +REM Переходим в backend +echo. +echo [2/3] Setup backend... +cd backend + +REM Создаем venv если нет +if not exist "venv\" ( + python -m venv venv +) + +REM Активируем и устанавливаем зависимости +call venv\Scripts\activate.bat +pip install -q -r requirements.txt + +REM Запускаем сервер +echo. +echo [3/3] Starting server... +echo ================================ +echo. +echo http://localhost:8000 +echo. +echo ================================ +echo. + +uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + diff --git a/backend/app/agents/reviewer.py b/backend/app/agents/reviewer.py index 51a1c96..c11dd9e 100644 --- a/backend/app/agents/reviewer.py +++ b/backend/app/agents/reviewer.py @@ -526,7 +526,11 @@ class ReviewerAgent: on_event: callable = None ) -> Dict[str, Any]: """Run the review workflow with streaming events""" + print(f"\n{'='*80}") print(f"🎬 Starting review stream for PR #{pr_number}") + print(f" Review ID: {review_id}") + print(f" Callback: {on_event is not None}") + print(f"{'='*80}\n") # Store callback in instance for access in nodes self._stream_callback = on_event @@ -545,9 +549,10 @@ class ReviewerAgent: final_state = None event_count = 0 + callback_count = 0 # Stream through the graph - print(f"📊 Starting graph stream with mode=['updates']") + print(f"📊 Starting graph.astream() with mode=['updates']\n") try: async for event in self.graph.astream( @@ -555,33 +560,59 @@ class ReviewerAgent: stream_mode=["updates"] ): event_count += 1 - print(f"📨 Event #{event_count} received from graph") - print(f" Type: {type(event)}") - print(f" Event content: {event}") + print(f"\n{'─'*80}") + print(f"📨 STREAM Event #{event_count}") + print(f" Type: {type(event).__name__}") + print(f" Is tuple: {isinstance(event, tuple)}") + print(f" Content: {event}") + print(f"{'─'*80}") - # LangGraph returns events as dict: {node_name: node_output} - if isinstance(event, dict): - for node_name, node_data in event.items(): - print(f" 🔔 Node update: {node_name}") - print(f" 🔔 Node data type: {type(node_data)}") - - if on_event: - print(f" 📤 Sending event to callback for node: {node_name}") - await on_event({ - "type": "agent_step", - "step": node_name, - "message": f"Шаг: {node_name}", - "data": { - "status": node_data.get("status") if isinstance(node_data, dict) else None - } - }) - - # Store final state - if isinstance(node_data, dict): - final_state = node_data + # LangGraph returns events as tuple: ('updates', {node_name: node_output}) + if isinstance(event, tuple) and len(event) == 2: + event_type, event_data = event[0], event[1] + print(f"✓ Tuple detected:") + print(f" [0] event_type: '{event_type}'") + print(f" [1] event_data type: {type(event_data).__name__}") + + # Handle 'updates' events + if event_type == 'updates' and isinstance(event_data, dict): + print(f"✓ Updates event with dict data") + for node_name, node_state in event_data.items(): + print(f"\n 🔔 Node: '{node_name}'") + print(f" State type: {type(node_state).__name__}") + + if on_event: + callback_count += 1 + print(f" 📤 Calling callback #{callback_count}...") + try: + await on_event({ + "type": "agent_step", + "step": node_name, + "message": f"Шаг: {node_name}", + "data": { + "status": node_state.get("status") if isinstance(node_state, dict) else None + } + }) + print(f" ✓ Callback executed successfully") + except Exception as e: + print(f" ❌ Callback error: {e}") + import traceback + traceback.print_exc() + else: + print(f" ⚠️ No callback set!") + + # Store final state + if isinstance(node_state, dict): + final_state = node_state + else: + print(f" ⚠️ Not an 'updates' event or data is not dict") + print(f" event_type={event_type}, isinstance(event_data, dict)={isinstance(event_data, dict)}") else: - print(f" ⚠️ Unexpected event format (not dict): {type(event)}") + print(f" ❌ NOT a tuple or wrong length!") + print(f" isinstance(event, tuple)={isinstance(event, tuple)}") + if isinstance(event, tuple): + print(f" len(event)={len(event)}") except Exception as e: print(f"❌ Error in graph streaming: {e}") diff --git a/backend/app/api/reviews.py b/backend/app/api/reviews.py index 96eca64..64d9f5e 100644 --- a/backend/app/api/reviews.py +++ b/backend/app/api/reviews.py @@ -131,9 +131,41 @@ async def get_review( async def run_review_task(review_id: int, pr_number: int, repository_id: int, db: AsyncSession): - """Background task to run review""" + """Background task to run review with streaming""" + from app.main import manager + from datetime import datetime as dt + + # Create event handler for streaming + async def on_review_event(event: dict): + """Handle review events and broadcast to clients""" + try: + event_data = { + "type": event.get("type", "agent_update"), + "review_id": review_id, + "pr_number": pr_number, + "timestamp": dt.utcnow().isoformat(), + "data": event + } + + # Save to DB + from app.models.review_event import ReviewEvent + db_event = ReviewEvent( + review_id=review_id, + event_type=event.get("type", "agent_update"), + step=event.get("step"), + message=event.get("message"), + data=event + ) + db.add(db_event) + await db.commit() + + # Broadcast + await manager.broadcast(event_data) + except Exception as e: + print(f"Error in review event handler: {e}") + agent = ReviewerAgent(db) - await agent.run_review(review_id, pr_number, repository_id) + await agent.run_review_stream(review_id, pr_number, repository_id, on_event=on_review_event) @router.post("/{review_id}/retry") diff --git a/backend/app/api/webhooks.py b/backend/app/api/webhooks.py index 64633a0..c37bc80 100644 --- a/backend/app/api/webhooks.py +++ b/backend/app/api/webhooks.py @@ -13,11 +13,43 @@ router = APIRouter() async def start_review_task(review_id: int, pr_number: int, repository_id: int): - """Background task to start review""" + """Background task to start review with streaming""" from app.database import async_session_maker + from app.main import manager + from datetime import datetime as dt + async with async_session_maker() as db: + # Create event handler for streaming + async def on_review_event(event: dict): + """Handle review events and broadcast to clients""" + try: + event_data = { + "type": event.get("type", "agent_update"), + "review_id": review_id, + "pr_number": pr_number, + "timestamp": dt.utcnow().isoformat(), + "data": event + } + + # Save to DB + from app.models.review_event import ReviewEvent + db_event = ReviewEvent( + review_id=review_id, + event_type=event.get("type", "agent_update"), + step=event.get("step"), + message=event.get("message"), + data=event + ) + db.add(db_event) + await db.commit() + + # Broadcast + await manager.broadcast(event_data) + except Exception as e: + print(f"Error in webhook review event handler: {e}") + agent = ReviewerAgent(db) - await agent.run_review(review_id, pr_number, repository_id) + await agent.run_review_stream(review_id, pr_number, repository_id, on_event=on_review_event) @router.post("/gitea/{repository_id}") diff --git a/backend/app/main.py b/backend/app/main.py index 7ce0fab..87b1b2e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -29,11 +29,23 @@ class ConnectionManager: async def broadcast(self, message: dict): """Broadcast message to all connected clients""" - for connection in self.active_connections: + print(f"\n[BROADCAST] Sending to {len(self.active_connections)} clients") + print(f"[BROADCAST] Message type: {message.get('type')}") + print(f"[BROADCAST] Message: {str(message)[:200]}...") + + sent_count = 0 + error_count = 0 + + for i, connection in enumerate(self.active_connections): try: await connection.send_json(message) - except Exception: - pass + sent_count += 1 + print(f"[BROADCAST] ✓ Sent to client #{i+1}") + except Exception as e: + error_count += 1 + print(f"[BROADCAST] ✗ Failed to send to client #{i+1}: {e}") + + print(f"[BROADCAST] Result: {sent_count} sent, {error_count} failed") # Create connection manager diff --git a/backend/app/workers/task_worker.py b/backend/app/workers/task_worker.py index 1eacdd7..12c75cb 100644 --- a/backend/app/workers/task_worker.py +++ b/backend/app/workers/task_worker.py @@ -206,6 +206,14 @@ class ReviewTaskWorker: # Create event handler async def on_review_event(event: dict): """Handle review events and broadcast to clients""" + print(f"\n{'*'*80}") + print(f"CALLBACK INVOKED!") + print(f" Event type: {event.get('type')}") + print(f" Event step: {event.get('step')}") + print(f" Event message: {event.get('message')}") + print(f" Active WS connections: {len(manager.active_connections)}") + print(f"{'*'*80}") + try: # Prepare event data event_data = { @@ -216,6 +224,7 @@ class ReviewTaskWorker: "data": event } + print(f" Prepared event_data: {event_data}") logger.info(f" 🔔 Broadcasting event: type={event.get('type')}, connections={len(manager.active_connections)}") # Save event to database @@ -229,10 +238,13 @@ class ReviewTaskWorker: ) db.add(db_event) await db.commit() + print(f" ✓ Event saved to DB: {db_event.id}") logger.debug(f" 💾 Event saved to DB: {db_event.id}") # Broadcast to all connected clients + print(f" Broadcasting to {len(manager.active_connections)} connections...") await manager.broadcast(event_data) + print(f" ✓ Broadcast completed") # Log the event if event.get("type") == "agent_step": @@ -242,6 +254,7 @@ class ReviewTaskWorker: message = event.get("message", "")[:100] logger.info(f" 💬 LLM: {message}...") except Exception as e: + print(f" ❌ ERROR in callback: {e}") logger.error(f" ❌ Ошибка broadcast события: {e}") import traceback traceback.print_exc() diff --git a/backend/test_graph_format.py b/backend/test_graph_format.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/backend/test_graph_format.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/start.bat b/start.bat index 35ec4f3..f2d44ff 100644 --- a/start.bat +++ b/start.bat @@ -1,100 +1,78 @@ @echo off -REM Единый скрипт запуска AI Code Review Platform +REM AI Review - Build & Start -echo. -echo ======================================== -echo AI Code Review Platform - Запуск -echo ======================================== +echo ================================ +echo AI Review - Starting +echo ================================ echo. -REM 1. Проверка Node.js -echo [STEP 1/7] Проверка Node.js... -where node >nul 2>nul -if %ERRORLEVEL% NEQ 0 ( - echo [ERROR] Node.js не установлен! Установите Node.js 18+ и попробуйте снова. - pause - exit /b 1 -) -node --version -echo [OK] Node.js установлен -echo. - -REM 2. Проверка Python -echo [STEP 2/7] Проверка Python... -where python >nul 2>nul -if %ERRORLEVEL% NEQ 0 ( - echo [ERROR] Python не установлен! Установите Python 3.10+ и попробуйте снова. - pause - exit /b 1 -) -python --version -echo [OK] Python установлен -echo. - -REM 3. Установка зависимостей frontend -echo [STEP 3/7] Установка зависимостей frontend... +REM 1. Build Frontend +echo [1/3] Building frontend... cd frontend + if not exist "node_modules\" ( - echo Установка npm пакетов... + echo Installing npm packages... call npm install -) else ( - echo node_modules уже существует, пропускаем... -) -echo [OK] Зависимости frontend установлены -echo. - -REM 4. Сборка frontend -echo [STEP 4/7] Сборка frontend... - -REM Создаем .env.production для production -echo VITE_API_URL=/api > .env.production -echo VITE_WS_URL= >> .env.production - -call npm run build -echo [OK] Frontend собран в backend/public -echo. - -REM 5. Установка зависимостей backend -cd ..\backend -echo [STEP 5/7] Установка зависимостей backend... -if not exist "venv\" ( - echo Создание виртуального окружения... - python -m venv venv -) - -REM Активация venv -call venv\Scripts\activate.bat - -REM Установка зависимостей -pip install -r requirements.txt -echo [OK] Зависимости backend установлены -echo. - -REM 6. Проверка .env -echo [STEP 6/7] Проверка конфигурации... -if not exist ".env" ( - echo [WARNING] Файл .env не найден! - if exist ".env.example" ( - echo Создаем .env из примера... - copy .env.example .env - echo [OK] Создан .env файл - echo [WARNING] ВАЖНО: Отредактируйте .env и добавьте необходимые токены! - ) else ( - echo [ERROR] .env.example не найден! + if %ERRORLEVEL% NEQ 0 ( + echo [ERROR] npm install failed + cd .. + pause + exit /b 1 ) ) -echo. -REM 7. Запуск backend -echo [STEP 7/7] Запуск сервера... -echo ======================================== +echo Building... +call npm run build + +if %ERRORLEVEL% NEQ 0 ( + echo [ERROR] Frontend build failed + cd .. + pause + exit /b 1 +) + +echo [OK] Frontend built to backend\public +cd .. + +REM 2. Setup Backend echo. -echo Backend: http://localhost:8000 -echo Frontend: http://localhost:8000 +echo [2/3] Setting up backend... +cd backend + +if not exist "venv\" ( + echo Creating venv... + python -m venv venv + if %ERRORLEVEL% NEQ 0 ( + echo [ERROR] Failed to create venv + cd .. + pause + exit /b 1 + ) +) + +echo Activating venv... +call venv\Scripts\activate.bat + +echo Installing dependencies... +pip install -q -r requirements.txt + +if %ERRORLEVEL% NEQ 0 ( + echo [ERROR] Failed to install dependencies + cd .. + pause + exit /b 1 +) + +REM 3. Start Backend +echo. +echo [3/3] Starting server... +echo ================================ +echo. +echo URL: http://localhost:8000 echo API Docs: http://localhost:8000/docs echo. -echo Для остановки нажмите Ctrl+C +echo Press Ctrl+C to stop +echo ================================ echo. uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload - diff --git a/start.sh b/start.sh index deb47be..48e0f8f 100644 --- a/start.sh +++ b/start.sh @@ -1,104 +1,54 @@ #!/bin/bash -# Единый скрипт запуска AI Code Review Platform +# AI Review - Build & Start set -e -echo "🚀 AI Code Review Platform - Запуск" -echo "====================================" +echo "================================" +echo "AI Review - Starting" +echo "================================" echo "" -# Цвета для вывода -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# 1. Проверка Node.js -echo -e "${YELLOW}📦 Проверка Node.js...${NC}" -if ! command -v node &> /dev/null; then - echo "❌ Node.js не установлен! Установите Node.js 18+ и попробуйте снова." - exit 1 -fi -echo -e "${GREEN}✅ Node.js $(node --version)${NC}" -echo "" - -# 2. Проверка Python -echo -e "${YELLOW}🐍 Проверка Python...${NC}" -if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then - echo "❌ Python не установлен! Установите Python 3.10+ и попробуйте снова." - exit 1 -fi - -PYTHON_CMD="python3" -if ! command -v python3 &> /dev/null; then - PYTHON_CMD="python" -fi -echo -e "${GREEN}✅ Python $($PYTHON_CMD --version)${NC}" -echo "" - -# 3. Установка зависимостей frontend -echo -e "${YELLOW}📦 Установка зависимостей frontend...${NC}" +# 1. Build Frontend +echo "[1/3] Building frontend..." cd frontend + if [ ! -d "node_modules" ]; then + echo "Installing npm packages..." npm install -else - echo "node_modules уже существует, пропускаем..." fi -echo -e "${GREEN}✅ Зависимости frontend установлены${NC}" -echo "" - -# 4. Сборка frontend -echo -e "${YELLOW}🔨 Сборка frontend...${NC}" - -# Создаем .env.production для production -cat > .env.production << 'EOF' -VITE_API_URL=/api -VITE_WS_URL= -EOF +echo "Building..." npm run build -echo -e "${GREEN}✅ Frontend собран в backend/public${NC}" -echo "" -# 5. Установка зависимостей backend -cd ../backend -echo -e "${YELLOW}📦 Установка зависимостей backend...${NC}" +echo "[OK] Frontend built to backend/public" +cd .. + +# 2. Setup Backend +echo "" +echo "[2/3] Setting up backend..." +cd backend + if [ ! -d "venv" ]; then - echo "Создание виртуального окружения..." - $PYTHON_CMD -m venv venv + echo "Creating venv..." + python3 -m venv venv fi -# Активация venv +echo "Activating venv..." source venv/bin/activate -# Установка зависимостей -pip install -r requirements.txt -echo -e "${GREEN}✅ Зависимости backend установлены${NC}" -echo "" +echo "Installing dependencies..." +pip install -q -r requirements.txt -# 6. Проверка .env -if [ ! -f ".env" ]; then - echo -e "${YELLOW}⚠️ Файл .env не найден!${NC}" - echo "Создаем .env из примера..." - if [ -f ".env.example" ]; then - cp .env.example .env - echo -e "${GREEN}✅ Создан .env файл${NC}" - echo -e "${YELLOW}⚠️ ВАЖНО: Отредактируйте .env и добавьте необходимые токены!${NC}" - else - echo "❌ .env.example не найден!" - fi - echo "" -fi - -# 7. Запуск backend -echo -e "${GREEN}🎉 Запуск сервера...${NC}" -echo "====================================" +# 3. Start Backend echo "" -echo "📍 Backend: http://localhost:8000" -echo "📍 Frontend: http://localhost:8000" -echo "📍 API Docs: http://localhost:8000/docs" +echo "[3/3] Starting server..." +echo "================================" echo "" -echo "Для остановки нажмите Ctrl+C" +echo "URL: http://localhost:8000" +echo "API Docs: http://localhost:8000/docs" +echo "" +echo "Press Ctrl+C to stop" +echo "================================" echo "" uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload -