"""Office Status API - Star Office style visualization for Jarvis agents.""" from datetime import datetime, timedelta from typing import Literal from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter(prefix="/api/office", tags=["office"]) # ============================================================================ # State Definitions (mapped to spaceship areas) # ============================================================================ # idle → Rest Bay (breakroom) # writing/researching/executing → Command Console (writing) # syncing → Server Room (syncing) # error → Repair Bay (error) SHIP_AREAS = { "breakroom": {"x": 200, "y": 300}, # Rest Bay - bottom left "writing": {"x": 640, "y": 200}, # Command Console - center top "server": {"x": 640, "y": 400}, # Server Room - center bottom "error": {"x": 1000, "y": 300}, # Repair Bay - right side } STATES = { "idle": {"name": "待命", "area": "breakroom"}, "writing": {"name": "执行中", "area": "writing"}, "researching": {"name": "研究中", "area": "writing"}, "executing": {"name": "执行中", "area": "writing"}, "syncing": {"name": "同步中", "area": "server"}, "error": {"name": "故障中", "area": "error"}, } # ============================================================================ # Data Models # ============================================================================ class AgentState(BaseModel): agent_id: str name: str state: Literal["idle", "writing", "researching", "executing", "syncing", "error"] detail: str | None = None area: str | None = None is_main: bool = False auth_status: str = "approved" # approved, pending, rejected, offline class SetStateRequest(BaseModel): state: str detail: str | None = None class OfficeStatus(BaseModel): state: str detail: str | None = None agent_name: str timestamp: str class OfficeMemo(BaseModel): success: bool date: str memo: str # ============================================================================ # In-Memory State (in production, this would come from Jarvis's agent state) # ============================================================================ _current_state: dict = { "agent_id": "jarvis-main", "name": "JARVIS", "state": "idle", "detail": "战舰启动中...", "area": "breakroom", "is_main": True, "auth_status": "approved", } def normalize_state(state: str | None) -> str: """Normalize various state names to our canonical states.""" if not state: return "idle" state = state.lower().strip() if state in ("working", "run", "running"): return "writing" if state in ("sync", "syncing"): return "syncing" if state in ("research", "researching"): return "researching" if state in ("execute", "executing"): return "executing" if state == "error": return "error" return "idle" def get_state_info(state: str) -> dict: """Get state info including area mapping.""" return STATES.get(state, STATES["idle"]) # ============================================================================ # API Endpoints # ============================================================================ @router.get("/status", response_model=OfficeStatus) async def get_status(): """Get current agent status.""" state_info = get_state_info(_current_state["state"]) return OfficeStatus( state=_current_state["state"], detail=_current_state.get("detail"), agent_name=_current_state["name"], timestamp=datetime.now().isoformat(), ) @router.get("/yesterday-memo", response_model=OfficeMemo) async def get_yesterday_memo(): """Return a lightweight public memo for the Star Office viewer.""" target_date = (datetime.now() - timedelta(days=1)).date().isoformat() detail = (_current_state.get("detail") or "No detailed log was recorded.").strip() memo = ( "Yesterday summary\n" f"- Last known state: {_current_state['state']}\n" f"- Detail: {detail}\n" "- Next step: open the command surface and continue from the current work thread." ) return OfficeMemo(success=True, date=target_date, memo=memo) @router.post("/set_state") async def set_state(req: SetStateRequest): """Set the current agent state.""" normalized = normalize_state(req.state) state_info = get_state_info(normalized) _current_state["state"] = normalized _current_state["detail"] = req.detail or "" _current_state["area"] = state_info["area"] return { "success": True, "state": normalized, "area": state_info["area"], "detail": _current_state["detail"], } @router.get("/agents") async def get_agents(): """Get all agents in the office (for multi-agent support).""" # For now, return just the main agent # In full implementation, this would query Jarvis's agent registry state_info = get_state_info(_current_state["state"]) return [ { "agentId": _current_state["agent_id"], "name": _current_state["name"], "state": _current_state["state"], "detail": _current_state.get("detail", ""), "area": state_info["area"], "isMain": _current_state.get("is_main", True), "authStatus": _current_state.get("auth_status", "approved"), "updated_at": datetime.now().isoformat(), } ] @router.get("/areas") async def get_areas(): """Get all spaceship areas with coordinates.""" return SHIP_AREAS @router.get("/health") async def health(): """Health check.""" return {"status": "ok", "service": "office"}