mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
Implement a slide-in chat panel that allows users to ask questions about their codebase using Claude Opus 4.5 with read-only access to project files. Backend changes: - Add SQLAlchemy models for conversation persistence (assistant_database.py) - Create AssistantChatSession with read-only Claude SDK client - Add WebSocket endpoint for real-time chat streaming - Include read-only MCP tools: feature_get_stats, feature_get_next, etc. Frontend changes: - Add floating action button (bottom-right) to toggle panel - Create slide-in panel component (400px width) - Implement WebSocket hook with reconnection logic - Add keyboard shortcut 'A' to toggle assistant Key features: - Read-only access: Only Read, Glob, Grep, WebFetch, WebSearch tools - Persistent history: Conversations saved to SQLite per project - Real-time streaming: Text chunks streamed as Claude generates response - Tool visibility: Shows when assistant is using tools to explore code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
177 lines
5.5 KiB
Python
177 lines
5.5 KiB
Python
"""
|
|
FastAPI Main Application
|
|
========================
|
|
|
|
Main entry point for the Autonomous Coding UI server.
|
|
Provides REST API, WebSocket, and static file serving.
|
|
"""
|
|
|
|
import shutil
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, Request, WebSocket, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
|
|
from .routers import projects_router, features_router, agent_router, spec_creation_router, filesystem_router, assistant_chat_router
|
|
from .websocket import project_websocket
|
|
from .services.process_manager import cleanup_all_managers
|
|
from .services.assistant_chat_session import cleanup_all_sessions as cleanup_assistant_sessions
|
|
from .schemas import SetupStatus
|
|
|
|
|
|
# Paths
|
|
ROOT_DIR = Path(__file__).parent.parent
|
|
UI_DIST_DIR = ROOT_DIR / "ui" / "dist"
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Lifespan context manager for startup and shutdown."""
|
|
# Startup
|
|
yield
|
|
# Shutdown - cleanup all running agents and assistant sessions
|
|
await cleanup_all_managers()
|
|
await cleanup_assistant_sessions()
|
|
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(
|
|
title="Autonomous Coding UI",
|
|
description="Web UI for the Autonomous Coding Agent",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# CORS - allow only localhost origins for security
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[
|
|
"http://localhost:5173", # Vite dev server
|
|
"http://127.0.0.1:5173",
|
|
"http://localhost:8888", # Production
|
|
"http://127.0.0.1:8888",
|
|
],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Security Middleware
|
|
# ============================================================================
|
|
|
|
@app.middleware("http")
|
|
async def require_localhost(request: Request, call_next):
|
|
"""Only allow requests from localhost."""
|
|
client_host = request.client.host if request.client else None
|
|
|
|
# Allow localhost connections
|
|
if client_host not in ("127.0.0.1", "::1", "localhost", None):
|
|
raise HTTPException(status_code=403, detail="Localhost access only")
|
|
|
|
return await call_next(request)
|
|
|
|
|
|
# ============================================================================
|
|
# Include Routers
|
|
# ============================================================================
|
|
|
|
app.include_router(projects_router)
|
|
app.include_router(features_router)
|
|
app.include_router(agent_router)
|
|
app.include_router(spec_creation_router)
|
|
app.include_router(filesystem_router)
|
|
app.include_router(assistant_chat_router)
|
|
|
|
|
|
# ============================================================================
|
|
# WebSocket Endpoint
|
|
# ============================================================================
|
|
|
|
@app.websocket("/ws/projects/{project_name}")
|
|
async def websocket_endpoint(websocket: WebSocket, project_name: str):
|
|
"""WebSocket endpoint for real-time project updates."""
|
|
await project_websocket(websocket, project_name)
|
|
|
|
|
|
# ============================================================================
|
|
# Setup & Health Endpoints
|
|
# ============================================================================
|
|
|
|
@app.get("/api/health")
|
|
async def health_check():
|
|
"""Health check endpoint."""
|
|
return {"status": "healthy"}
|
|
|
|
|
|
@app.get("/api/setup/status", response_model=SetupStatus)
|
|
async def setup_status():
|
|
"""Check system setup status."""
|
|
# Check for Claude CLI
|
|
claude_cli = shutil.which("claude") is not None
|
|
|
|
# Check for credentials file
|
|
credentials_path = Path.home() / ".claude" / ".credentials.json"
|
|
credentials = credentials_path.exists()
|
|
|
|
# Check for Node.js and npm
|
|
node = shutil.which("node") is not None
|
|
npm = shutil.which("npm") is not None
|
|
|
|
return SetupStatus(
|
|
claude_cli=claude_cli,
|
|
credentials=credentials,
|
|
node=node,
|
|
npm=npm,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Static File Serving (Production)
|
|
# ============================================================================
|
|
|
|
# Serve React build files if they exist
|
|
if UI_DIST_DIR.exists():
|
|
# Mount static assets
|
|
app.mount("/assets", StaticFiles(directory=UI_DIST_DIR / "assets"), name="assets")
|
|
|
|
@app.get("/")
|
|
async def serve_index():
|
|
"""Serve the React app index.html."""
|
|
return FileResponse(UI_DIST_DIR / "index.html")
|
|
|
|
@app.get("/{path:path}")
|
|
async def serve_spa(path: str):
|
|
"""
|
|
Serve static files or fall back to index.html for SPA routing.
|
|
"""
|
|
# Check if the path is an API route (shouldn't hit this due to router ordering)
|
|
if path.startswith("api/") or path.startswith("ws/"):
|
|
raise HTTPException(status_code=404)
|
|
|
|
# Try to serve the file directly
|
|
file_path = UI_DIST_DIR / path
|
|
if file_path.exists() and file_path.is_file():
|
|
return FileResponse(file_path)
|
|
|
|
# Fall back to index.html for SPA routing
|
|
return FileResponse(UI_DIST_DIR / "index.html")
|
|
|
|
|
|
# ============================================================================
|
|
# Main Entry Point
|
|
# ============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(
|
|
"server.main:app",
|
|
host="127.0.0.1", # Localhost only for security
|
|
port=8888,
|
|
reload=True,
|
|
)
|