Files
JARVIS/backend/app/routers/office.py

180 lines
5.7 KiB
Python

"""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"}