"""Workspace API — query history and feedback, stored in Redis.""" from __future__ import annotations import json from typing import Literal import redis.asyncio as aioredis from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel from src.config import settings from src.utils.logger import get_logger logger = get_logger(__name__) router = APIRouter(tags=["workspace"]) HISTORY_KEY = "gs:queries" # shared with analytics FEEDBACK_KEY = "gs:feedback:{id}" # one key per query_id async def _redis() -> aioredis.Redis: return aioredis.from_url(settings.redis_url, decode_responses=True) # --------------------------------------------------------------------------- # GET /api/workspace/history # --------------------------------------------------------------------------- @router.get("/api/workspace/history") async def get_history( page: int = Query(default=1, ge=1), limit: int = Query(default=20, ge=1, le=100), ) -> dict: r = await _redis() try: total = await r.llen(HISTORY_KEY) start = (page - 1) * limit raw_list = await r.lrange(HISTORY_KEY, start, start + limit - 1) except Exception as exc: logger.warning("workspace_history_redis_failed", extra={"error": str(exc)}) return {"items": [], "total": 0} finally: await r.aclose() items = [] for raw in raw_list: try: ev = json.loads(raw) items.append({ "id": ev.get("id", ""), "query": ev.get("query", ""), "answer_brief": ev.get("answer_brief", ""), "created_at": ev.get("created_at", ""), "success": ev.get("success", True), "duration_ms": ev.get("duration_ms", 0), }) except Exception: continue return {"items": items, "total": total} # --------------------------------------------------------------------------- # POST /api/query/{query_id}/feedback # --------------------------------------------------------------------------- Sentiment = Literal["helpful", "not_helpful", "hallucinated"] class FeedbackBody(BaseModel): sentiment: Sentiment text: str | None = None @router.post("/api/query/{query_id}/feedback") async def post_feedback(query_id: str, body: FeedbackBody) -> dict: r = await _redis() try: key = FEEDBACK_KEY.format(id=query_id) payload = {"sentiment": body.sentiment, "text": body.text or ""} await r.set(key, json.dumps(payload), ex=86400 * 30) except Exception as exc: logger.warning("workspace_feedback_redis_failed", extra={"query_id": query_id, "error": str(exc)}) finally: await r.aclose() logger.info("feedback_received", extra={"query_id": query_id, "sentiment": body.sentiment}) return {"ok": True}