2026-03-14 15:44:48 +08:00
|
|
|
from typing import List, Optional
|
|
|
|
|
from fastapi import FastAPI, HTTPException, Body
|
2026-03-14 22:00:36 +08:00
|
|
|
from fastapi.responses import StreamingResponse
|
2026-03-14 15:44:48 +08:00
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
import asyncio
|
2026-03-14 22:00:36 +08:00
|
|
|
import json
|
2026-03-14 15:44:48 +08:00
|
|
|
|
2026-03-14 19:20:37 +08:00
|
|
|
from app.api import upload, llm, skills, users
|
2026-03-14 15:44:48 +08:00
|
|
|
from app.connectors.postgres import postgres_connector
|
|
|
|
|
from app.connectors.clickhouse import clickhouse_connector
|
|
|
|
|
from app.connectors.minio import minio_connector
|
|
|
|
|
from app.core.nanobot import nanobot_service
|
|
|
|
|
from app.agent.nl2sql import process_nl2sql, NL2SQLRequest, NL2SQLResponse
|
|
|
|
|
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
allow_origins=["http://localhost:5173", "http://localhost:5174", "*"],
|
|
|
|
|
allow_credentials=True,
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
allow_headers=["*"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
app.include_router(upload.router, prefix="/api/v1")
|
|
|
|
|
app.include_router(llm.router, prefix="/api/v1")
|
|
|
|
|
app.include_router(skills.router, prefix="/api/v1")
|
2026-03-14 19:20:37 +08:00
|
|
|
app.include_router(users.router, prefix="/api/v1")
|
2026-03-14 15:44:48 +08:00
|
|
|
|
|
|
|
|
@app.on_event("startup")
|
|
|
|
|
async def startup_event():
|
|
|
|
|
# Initialize nanobot in background
|
|
|
|
|
try:
|
|
|
|
|
await nanobot_service.start()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Nanobot startup failed: {e}")
|
|
|
|
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
|
|
|
async def shutdown_event():
|
|
|
|
|
await nanobot_service.stop()
|
|
|
|
|
|
|
|
|
|
@app.get("/")
|
|
|
|
|
def read_root():
|
|
|
|
|
return {"Hello": "DataClaw Backend"}
|
|
|
|
|
|
|
|
|
|
@app.get("/connect/postgres")
|
|
|
|
|
def test_postgres():
|
|
|
|
|
if postgres_connector.test_connection():
|
|
|
|
|
return {"status": "success", "message": "Connected to PostgreSQL"}
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to connect to PostgreSQL")
|
|
|
|
|
|
|
|
|
|
@app.get("/connect/clickhouse")
|
|
|
|
|
def test_clickhouse():
|
|
|
|
|
if clickhouse_connector.test_connection():
|
|
|
|
|
return {"status": "success", "message": "Connected to ClickHouse"}
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to connect to ClickHouse")
|
|
|
|
|
|
|
|
|
|
@app.get("/connect/minio")
|
|
|
|
|
def test_minio():
|
|
|
|
|
if minio_connector.test_connection():
|
|
|
|
|
return {"status": "success", "message": "Connected to MinIO"}
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to connect to MinIO")
|
|
|
|
|
|
|
|
|
|
@app.get("/nanobot/status")
|
|
|
|
|
def nanobot_status():
|
|
|
|
|
if nanobot_service.agent:
|
|
|
|
|
return {"status": "running", "model": nanobot_service.agent.model}
|
|
|
|
|
return {"status": "stopped"}
|
|
|
|
|
|
|
|
|
|
class ChatRequest(BaseModel):
|
|
|
|
|
message: str
|
2026-03-14 22:25:01 +08:00
|
|
|
session_id: str = "api:default"
|
2026-03-14 15:44:48 +08:00
|
|
|
skill_ids: Optional[List[str]] = None
|
2026-03-14 22:00:36 +08:00
|
|
|
model_id: Optional[str] = None
|
2026-03-14 15:44:48 +08:00
|
|
|
|
|
|
|
|
@app.post("/nanobot/chat")
|
|
|
|
|
async def nanobot_chat(request: ChatRequest):
|
|
|
|
|
try:
|
2026-03-14 22:00:36 +08:00
|
|
|
response = await nanobot_service.process_message(request.message, skill_ids=request.skill_ids, model_id=request.model_id)
|
2026-03-14 15:44:48 +08:00
|
|
|
return {"response": response}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
2026-03-14 22:00:36 +08:00
|
|
|
@app.post("/nanobot/chat/stream")
|
|
|
|
|
async def nanobot_chat_stream(request: ChatRequest):
|
|
|
|
|
async def event_generator():
|
|
|
|
|
try:
|
2026-03-14 22:07:40 +08:00
|
|
|
response = await nanobot_service.process_message(
|
|
|
|
|
request.message,
|
2026-03-14 22:25:01 +08:00
|
|
|
session_id=request.session_id,
|
2026-03-14 22:07:40 +08:00
|
|
|
skill_ids=request.skill_ids,
|
|
|
|
|
model_id=request.model_id,
|
|
|
|
|
)
|
|
|
|
|
text = response or ""
|
|
|
|
|
for ch in text:
|
|
|
|
|
yield f"data: {json.dumps({'type': 'delta', 'content': ch}, ensure_ascii=False)}\n\n"
|
|
|
|
|
await asyncio.sleep(0.008)
|
|
|
|
|
yield f"data: {json.dumps({'type': 'final', 'content': text}, ensure_ascii=False)}\n\n"
|
|
|
|
|
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n"
|
|
|
|
|
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
|
2026-03-14 22:00:36 +08:00
|
|
|
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
event_generator(),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers={
|
|
|
|
|
"Cache-Control": "no-cache",
|
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
|
"X-Accel-Buffering": "no",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-14 22:25:01 +08:00
|
|
|
@app.get("/nanobot/sessions")
|
|
|
|
|
def get_sessions():
|
|
|
|
|
if not nanobot_service.agent:
|
|
|
|
|
return []
|
|
|
|
|
# session_manager has list_sessions()
|
|
|
|
|
sessions = nanobot_service.agent.sessions.list_sessions()
|
|
|
|
|
return sessions
|
|
|
|
|
|
|
|
|
|
@app.get("/nanobot/sessions/{session_id}")
|
|
|
|
|
def get_session(session_id: str):
|
|
|
|
|
if not nanobot_service.agent:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Nanobot not running")
|
|
|
|
|
session = nanobot_service.agent.sessions.get_or_create(session_id)
|
|
|
|
|
return {
|
|
|
|
|
"key": session.key,
|
|
|
|
|
"created_at": session.created_at,
|
|
|
|
|
"updated_at": session.updated_at,
|
|
|
|
|
"metadata": session.metadata,
|
|
|
|
|
"messages": session.messages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@app.delete("/nanobot/sessions/{session_id}")
|
|
|
|
|
def delete_session(session_id: str):
|
|
|
|
|
if not nanobot_service.agent:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Nanobot not running")
|
|
|
|
|
|
|
|
|
|
# Try to remove from cache and delete file
|
|
|
|
|
session = nanobot_service.agent.sessions.get_or_create(session_id)
|
|
|
|
|
if session:
|
|
|
|
|
nanobot_service.agent.sessions.invalidate(session_id)
|
|
|
|
|
path = nanobot_service.agent.sessions._get_session_path(session_id)
|
|
|
|
|
if path.exists():
|
|
|
|
|
path.unlink()
|
|
|
|
|
return {"status": "success"}
|
|
|
|
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
|
|
|
|
|
|
|
|
@app.put("/nanobot/sessions/{session_id}")
|
|
|
|
|
def update_session(session_id: str, title: str = Body(..., embed=True)):
|
|
|
|
|
if not nanobot_service.agent:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Nanobot not running")
|
|
|
|
|
|
|
|
|
|
session = nanobot_service.agent.sessions.get_or_create(session_id)
|
|
|
|
|
session.metadata["title"] = title
|
|
|
|
|
nanobot_service.agent.sessions.save(session)
|
|
|
|
|
return {"status": "success", "title": title}
|
|
|
|
|
|
2026-03-14 15:44:48 +08:00
|
|
|
@app.post("/api/v1/agent/nl2sql", response_model=NL2SQLResponse)
|
|
|
|
|
async def run_nl2sql(request: NL2SQLRequest):
|
|
|
|
|
return await process_nl2sql(request)
|