diff --git a/AgentCompany-New/src/renderer/src/shared/Shell.tsx b/AgentCompany-New/src/renderer/src/shared/Shell.tsx index b3c510d2..a54cce40 100644 --- a/AgentCompany-New/src/renderer/src/shared/Shell.tsx +++ b/AgentCompany-New/src/renderer/src/shared/Shell.tsx @@ -180,6 +180,7 @@ export function Shell({ diff --git a/AgentCompany-New/src/renderer/src/shared/VirtualOffice.tsx b/AgentCompany-New/src/renderer/src/shared/VirtualOffice.tsx index 427d27db..789dedbe 100644 --- a/AgentCompany-New/src/renderer/src/shared/VirtualOffice.tsx +++ b/AgentCompany-New/src/renderer/src/shared/VirtualOffice.tsx @@ -1,11 +1,12 @@ -import { useState } from "react"; -import type { AgentRecord, TaskRecord } from "../../../shared/domain"; +import { useState, type CSSProperties } from "react"; +import type { AgentRecord, TaskRecord, TaskRunRecord } from "../../../shared/domain"; import type { ChatPresenceRecord } from "../../../shared/chat-domain"; import { Avatar } from "./ui"; interface VirtualOfficeProps { agents: AgentRecord[]; tasks: TaskRecord[]; + taskRuns: TaskRunRecord[]; presence: ChatPresenceRecord[]; orgId: string; } @@ -15,29 +16,22 @@ type PresenceStatus = "active" | "thinking" | "busy" | "idle"; interface AgentDesk { agent: AgentRecord; status: PresenceStatus; - statusText: string; currentTask: string | null; + runningNow: boolean; } function resolveDesks(props: VirtualOfficeProps): AgentDesk[] { - const { agents, tasks, presence, orgId } = props; - const orgAgents = agents.filter((a) => a.orgId === orgId); - return orgAgents.map((agent) => { + const { agents, tasks, taskRuns, presence, orgId } = props; + return agents.filter((a) => a.orgId === orgId).map((agent) => { const p = presence.find((r) => r.principalType === "agent" && r.principalId === agent.id); - const activeTask = tasks.find( - (t) => t.orgId === orgId && t.assigneeAgentId === agent.id && t.status === "in_progress", - ); - const status = mapStatus(p?.status); - return { - agent, - status, - statusText: p?.statusText ?? "", - currentTask: activeTask?.title ?? null, - }; + const activeTask = tasks.find((t) => t.orgId === orgId && t.assigneeAgentId === agent.id && t.status === "in_progress"); + const runningNow = taskRuns.some((r) => r.agentId === agent.id && r.state === "running"); + return { agent, status: mapStatus(p?.status, runningNow), currentTask: activeTask?.title ?? null, runningNow }; }); } -function mapStatus(raw: string | undefined): PresenceStatus { +function mapStatus(raw: string | undefined, running: boolean): PresenceStatus { + if (running) return "active"; if (!raw) return "idle"; if (raw === "thinking") return "thinking"; if (raw === "waiting_approval" || raw === "busy") return "busy"; @@ -45,25 +39,39 @@ function mapStatus(raw: string | undefined): PresenceStatus { return "idle"; } -const STATUS_RING: Record = { - active: "var(--green)", - thinking: "var(--accent)", - busy: "var(--amber)", - idle: "var(--text-4)", +const STATUS_COLOR: Record = { + active: "#34d399", + thinking: "#818cf8", + busy: "#fbbf24", + idle: "rgba(120,120,120,0.35)", }; -const DEPT_FLOOR: Record = { - engineering: { label: "Engineering", color: "var(--accent-soft)" }, - product: { label: "Product", color: "rgba(168, 130, 255, 0.08)" }, - design: { label: "Design", color: "rgba(255, 170, 130, 0.08)" }, - marketing: { label: "Marketing", color: "rgba(130, 200, 255, 0.08)" }, - operations: { label: "Operations", color: "rgba(180, 180, 180, 0.06)" }, +const DESK_BG: CSSProperties = { + background: "rgba(255,255,255,0.03)", + border: "1px solid rgba(255,255,255,0.06)", + borderRadius: 10, + padding: "10px 6px 8px", + display: "flex", + flexDirection: "column", + alignItems: "center", + position: "relative", + cursor: "default", + transition: "border-color 0.15s, box-shadow 0.15s", }; -function floorForDept(dept: string): { label: string; color: string } { - const key = dept.toLowerCase(); - return DEPT_FLOOR[key] ?? { label: dept, color: "rgba(180, 180, 180, 0.06)" }; -} +const MONITOR: CSSProperties = { + width: 28, + height: 18, + borderRadius: 3, + border: "1.5px solid rgba(255,255,255,0.12)", + marginBottom: 2, + display: "flex", + alignItems: "center", + justifyContent: "center", + fontSize: 8, + position: "relative", + overflow: "hidden", +}; export function VirtualOffice(props: VirtualOfficeProps) { const desks = resolveDesks(props); @@ -76,127 +84,132 @@ export function VirtualOffice(props: VirtualOfficeProps) { departments.get(dept)!.push(desk); } - if (desks.length === 0) { - return
No agents yet
; - } + if (desks.length === 0) return null; return ( -
- {[...departments.entries()].map(([dept, deptDesks]) => { - const floor = floorForDept(dept); - return ( -
-
- {floor.label} -
-
- {deptDesks.map((desk) => ( - - ))} -
-
- ); - })} +
+ {[...departments.entries()].map(([dept, deptDesks]) => ( + + ))}
); } -function DeskCell({ - desk, - hovered, - onHover, -}: { - desk: AgentDesk; - hovered: boolean; +function Room({ label, desks, hoveredId, onHover }: { + label: string; + desks: AgentDesk[]; + hoveredId: string | null; onHover: (id: string | null) => void; }) { - const ring = STATUS_RING[desk.status]; - const isWorking = desk.status === "active" || desk.status === "thinking"; + return ( +
+
+ {label} +
+
+ {desks.map((desk) => ( + + ))} +
+
+ ); +} + +function Workstation({ desk, hovered, onHover }: { desk: AgentDesk; hovered: boolean; onHover: (id: string | null) => void }) { + const color = STATUS_COLOR[desk.status]; + const glowing = desk.status === "active" || desk.status === "thinking"; return (
onHover(desk.agent.id)} onMouseLeave={() => onHover(null)} > +
+ {glowing &&
} +
+
- +
{desk.agent.name.split(" ")[0]}
- {hovered && desk.currentTask && ( + {hovered && ( + + )} +
+ ); +} + +function Tooltip({ name, role, task, status }: { name: string; role: string; task: string | null; status: PresenceStatus }) { + return ( +
+
{name}
+
{role}
+ {task && (
- {desk.currentTask} + {task}
)}