feat: implement Agent Chat Widget for entity pages

TASK_PHASE_AGENT_CHAT_WIDGET_MVP.md completed:

Backend:
- Add /api/v1/agents/{agent_id}/chat-room endpoint
- Add /api/v1/nodes/{node_id}/chat-room endpoint
- Add /api/v1/microdaos/{slug}/chat-room endpoint

Frontend:
- Create AgentChatWidget.tsx floating chat component
- Integrate into /agents/:agentId page
- Integrate into /nodes/:nodeId page
- Integrate into /microdao/:slug page

Ontology rule implemented:
'No page without agents' = ability to directly talk to agents on that page
This commit is contained in:
Apple
2025-11-30 09:10:45 -08:00
parent fb4a33f016
commit 6297adc0dc
6 changed files with 692 additions and 0 deletions

View File

@@ -1255,6 +1255,158 @@ async def chat_bootstrap(
}
# =============================================================================
# Chat Room API (TASK_PHASE_AGENT_CHAT_WIDGET_MVP)
# =============================================================================
@api_router.get("/agents/{agent_id}/chat-room")
async def get_agent_chat_room(agent_id: str):
"""
Отримати інформацію про кімнату чату для агента.
Повертає room_id, agent info для ініціалізації чату.
"""
try:
agent = await repo_city.get_agent_by_id(agent_id)
if not agent:
raise HTTPException(status_code=404, detail=f"Agent not found: {agent_id}")
# Get agent's primary room or create room slug
room_slug = f"agent-console-{agent.get('public_slug') or agent_id}"
room = await repo_city.get_room_by_slug(room_slug)
# If room doesn't exist, try to get agent's primary room from dashboard
if not room:
rooms = await repo_city.get_agent_rooms(agent_id)
if rooms and len(rooms) > 0:
room = rooms[0]
room_slug = room.get("slug", room_slug)
return {
"agent_id": agent_id,
"agent_display_name": agent.get("display_name"),
"agent_avatar_url": agent.get("avatar_url"),
"agent_status": agent.get("status", "offline"),
"agent_kind": agent.get("kind"),
"room_slug": room_slug,
"room_id": room.get("id") if room else None,
"matrix_room_id": room.get("matrix_room_id") if room else None,
"chat_available": room is not None and room.get("matrix_room_id") is not None
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get agent chat room for {agent_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to get agent chat room")
@api_router.get("/nodes/{node_id}/chat-room")
async def get_node_chat_room(node_id: str):
"""
Отримати інформацію про кімнату чату для ноди.
Повертає room_id, guardian/steward agents info.
"""
try:
node = await repo_city.get_node_by_id(node_id)
if not node:
raise HTTPException(status_code=404, detail=f"Node not found: {node_id}")
# Get node support room
node_slug = node_id.replace("node-", "").replace("-", "_")
room_slug = f"node-support-{node_slug}"
room = await repo_city.get_room_by_slug(room_slug)
# Get guardian and steward agents
guardian_agent = None
steward_agent = None
if node.get("guardian_agent"):
guardian_agent = {
"id": node["guardian_agent"].get("id"),
"display_name": node["guardian_agent"].get("name"),
"kind": node["guardian_agent"].get("kind"),
"role": "guardian"
}
if node.get("steward_agent"):
steward_agent = {
"id": node["steward_agent"].get("id"),
"display_name": node["steward_agent"].get("name"),
"kind": node["steward_agent"].get("kind"),
"role": "steward"
}
return {
"node_id": node_id,
"node_name": node.get("name"),
"node_status": node.get("status", "offline"),
"room_slug": room_slug,
"room_id": room.get("id") if room else None,
"matrix_room_id": room.get("matrix_room_id") if room else None,
"chat_available": room is not None and room.get("matrix_room_id") is not None,
"agents": [a for a in [guardian_agent, steward_agent] if a is not None]
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get node chat room for {node_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to get node chat room")
@api_router.get("/microdaos/{slug}/chat-room")
async def get_microdao_chat_room(slug: str):
"""
Отримати інформацію про кімнату чату для MicroDAO.
Повертає room_id, orchestrator agent info.
"""
try:
dao = await repo_city.get_microdao_by_slug(slug)
if not dao:
raise HTTPException(status_code=404, detail=f"MicroDAO not found: {slug}")
# Get MicroDAO lobby room
room_slug = f"microdao-lobby-{slug}"
room = await repo_city.get_room_by_slug(room_slug)
# If no lobby room, try to get primary room
if not room:
rooms = await repo_city.get_microdao_rooms(dao["id"])
if rooms and len(rooms) > 0:
# Find primary room or first room
primary = next((r for r in rooms if r.get("room_role") == "primary"), rooms[0])
room = primary
room_slug = room.get("slug", room_slug)
# Get orchestrator agent
orchestrator = None
orchestrator_id = dao.get("orchestrator_agent_id")
if orchestrator_id:
orch_agent = await repo_city.get_agent_by_id(orchestrator_id)
if orch_agent:
orchestrator = {
"id": orchestrator_id,
"display_name": orch_agent.get("display_name"),
"avatar_url": orch_agent.get("avatar_url"),
"kind": orch_agent.get("kind"),
"role": "orchestrator"
}
return {
"microdao_id": dao["id"],
"microdao_slug": slug,
"microdao_name": dao.get("name"),
"room_slug": room_slug,
"room_id": room.get("id") if room else None,
"matrix_room_id": room.get("matrix_room_id") if room else None,
"chat_available": room is not None and room.get("matrix_room_id") is not None,
"orchestrator": orchestrator
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get microdao chat room for {slug}: {e}")
raise HTTPException(status_code=500, detail="Failed to get microdao chat room")
# =============================================================================
# City Feed API
# =============================================================================