import os import threading from pathlib import Path from typing import Optional from fastapi import FastAPI, Query, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse from pydantic import BaseModel from .agent import ShoppingAgent from .feedback import caminho_feedback, google_sheets_habilitado, salvar_feedback from .google_oauth import build_flow, get_authorization_url, load_credentials, save_credentials from .logger import salvar_log_busca from .memory import caminho_memoria_negativa oauth_flow_global = {} EMBEDDING_PROVIDER = os.getenv("EMBEDDING_PROVIDER", "transformers").strip().lower() HF_MODEL_REPO = os.getenv("HF_MODEL_REPO", "Ana2012/bertimbau-buscador").strip() def _env_flag(name, default="true"): return os.getenv(name, default).strip().lower() in {"1", "true", "yes", "on"} PRELOAD_AGENT = _env_flag("PRELOAD_AGENT", "true") LOGS_DIR = os.getenv("LOGS_DIR", "/data/logs") DATA_DIR = "/data" app = FastAPI(title="TCC2 Agent API") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) agent = None agent_lock = threading.Lock() def get_agent(): global agent if agent is None: with agent_lock: if agent is None: agent = ShoppingAgent() return agent @app.on_event("startup") def preload_agent(): if PRELOAD_AGENT: get_agent() class ChatRequest(BaseModel): query: Optional[str] = None message: Optional[str] = None top_k: int = 5 class FeedbackRequest(BaseModel): search_id: str query: str rank: int product_id: str product_name: str categoria_produto: Optional[str] = None categoria_inferida: Optional[str] = None rating: Optional[int] = None is_helpful: Optional[bool] = None note: Optional[str] = None feedback: Optional[str] = None motivo: Optional[str] = None score_final: Optional[float] = None score_semantico: Optional[float] = None bonus_lexical: Optional[float] = None penalidade_feedback: Optional[float] = None user_message: Optional[str] = None @app.get("/health") def health(): runtime = get_agent().runtime_info() if agent is not None else None return { "status": "ok", "agent_ready": agent is not None, "embedding_provider": EMBEDDING_PROVIDER, "model_repo": HF_MODEL_REPO, "preload_agent": PRELOAD_AGENT, "runtime": runtime, "feedback_storage": "google_sheets" if google_sheets_habilitado() else "csv", } @app.get("/", include_in_schema=False) def root(): return RedirectResponse(url="/docs") @app.get("/favicon.ico", include_in_schema=False) def favicon(): return Response(status_code=204) @app.get("/auth/google") def auth_google(): try: flow = build_flow() authorization_url, state = flow.authorization_url( access_type="offline", prompt="consent", include_granted_scopes="true", ) # 🔥 ESSENCIAL oauth_flow_global[state] = flow return RedirectResponse(url=authorization_url) except Exception as exc: return { "ok": False, "error": str(exc), } @app.get("/oauth2callback") def oauth2callback(code: str = Query(...), state: str = Query(...)): try: flow = oauth_flow_global.get(state) if not flow: return HTMLResponse( "

Erro: sessao OAuth expirada ou invalida.

", status_code=400, ) # 🔥 usa o MESMO flow (não recria!) flow.fetch_token(code=code) save_credentials(flow.credentials) # limpa memória oauth_flow_global.pop(state, None) return HTMLResponse( """

Autorizacao concluida com sucesso.

O backend ja pode salvar feedbacks no Google Sheets.

Voce ja pode fechar esta aba.

""" ) except Exception as exc: return HTMLResponse( f"""

Erro ao concluir autorizacao Google.

{str(exc)}

""", status_code=500, ) @app.get("/auth/status") def auth_status(): credentials = load_credentials() connected = credentials is not None and credentials.valid return { "google_sheets_connected": connected, "message": ( "Google Sheets autorizado e pronto para uso." if connected else "Google Sheets ainda nao autorizado. Acesse /auth/google para conectar." ), } @app.get("/debug/files") def debug_files(): data_path = Path(DATA_DIR) logs_path = Path(LOGS_DIR) feedback_path = Path(caminho_feedback()) memory_path = Path(caminho_memoria_negativa()) return { "data_exists": data_path.exists(), "logs_exists": logs_path.exists(), "feedback_exists": feedback_path.exists(), "negative_memory_exists": memory_path.exists(), "data_files": sorted(p.name for p in data_path.iterdir()) if data_path.exists() else [], "logs_files": sorted(p.name for p in logs_path.iterdir()) if logs_path.exists() else [], "feedback_file": str(feedback_path), "negative_memory_file": str(memory_path), "feedback_storage": "google_sheets" if google_sheets_habilitado() else "csv", } @app.get("/debug/feedback") def debug_feedback(): feedback_path = Path(caminho_feedback()) if not feedback_path.exists(): return {"error": "arquivo nao existe"} return {"conteudo": feedback_path.read_text(encoding="utf-8")} @app.get("/download/feedback") def download_feedback(): feedback_path = caminho_feedback() if not os.path.exists(feedback_path): return {"error": "arquivo nao existe"} return FileResponse(feedback_path, filename="feedback.csv") @app.get("/debug/memory") def debug_memory(): memory_path = Path(caminho_memoria_negativa()) if not memory_path.exists(): return {"status": "missing", "file": str(memory_path)} return { "status": "ok", "file": str(memory_path), "content": memory_path.read_text(encoding="utf-8"), } @app.post("/chat") def chat(request: ChatRequest): texto = request.query or request.message if not texto: return {"error": "query ou message deve ser informado"} resultado = get_agent().responder(texto, top_k=request.top_k) salvar_log_busca(resultado) return resultado @app.post("/feedback") def feedback(request: FeedbackRequest): feedback_file = caminho_feedback() print( "Salvando feedback:", { "query": request.query, "product_id": request.product_id, "feedback_file": feedback_file, "logs_dir_exists": os.path.exists(LOGS_DIR), "google_sheets_enabled": google_sheets_habilitado(), }, ) try: return salvar_feedback( search_id=request.search_id, query=request.query, rank=request.rank, product_id=request.product_id, product_name=request.product_name, categoria_produto=request.categoria_produto, rating=request.rating, is_helpful=request.is_helpful, note=request.note, categoria_inferida=request.categoria_inferida, feedback=request.feedback, motivo=request.motivo, score_final=request.score_final, score_semantico=request.score_semantico, bonus_lexical=request.bonus_lexical, penalidade_feedback=request.penalidade_feedback, user_message=request.user_message, ) except Exception as exc: return { "ok": False, "saved_local": False, "saved_google_sheets": False, "detail": str(exc), "feedback_file": feedback_file, "logs_dir_exists": os.path.exists(LOGS_DIR), "google_sheets_enabled": google_sheets_habilitado(), }