Redesign virtual office with desk workstations and spatial layout

Each agent sits at a workstation with a monitor, status-colored glow for
active agents, and a rich tooltip showing name, role, and current task.
Departments render as labeled rooms with subtle borders. The monitor
lights up when the agent has a running task run, giving a real sense of
who is working and who is idle.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-30 21:42:50 -04:00
1 parent 453e47274a
commit 3a3c5b7ca3
2 files changed
+133 -119

No files matched your search

@@ -180,6 +180,7 @@ export function Shell({
<VirtualOffice <VirtualOffice
agents={snapshot.agents} agents={snapshot.agents}
tasks={snapshot.tasks} tasks={snapshot.tasks}
taskRuns={snapshot.taskRuns}
presence={snapshot.chatPresence} presence={snapshot.chatPresence}
orgId={organization.id} orgId={organization.id}
/> />
@@ -1,11 +1,12 @@
import { useState } from "react"; import { useState, type CSSProperties } from "react";
import type { AgentRecord, TaskRecord } from "../../../shared/domain"; import type { AgentRecord, TaskRecord, TaskRunRecord } from "../../../shared/domain";
import type { ChatPresenceRecord } from "../../../shared/chat-domain"; import type { ChatPresenceRecord } from "../../../shared/chat-domain";
import { Avatar } from "./ui"; import { Avatar } from "./ui";
interface VirtualOfficeProps { interface VirtualOfficeProps {
agents: AgentRecord[]; agents: AgentRecord[];
tasks: TaskRecord[]; tasks: TaskRecord[];
taskRuns: TaskRunRecord[];
presence: ChatPresenceRecord[]; presence: ChatPresenceRecord[];
orgId: string; orgId: string;
} }
@@ -15,29 +16,22 @@ type PresenceStatus = "active" | "thinking" | "busy" | "idle";
interface AgentDesk { interface AgentDesk {
agent: AgentRecord; agent: AgentRecord;
status: PresenceStatus; status: PresenceStatus;
statusText: string;
currentTask: string | null; currentTask: string | null;
runningNow: boolean;
} }
function resolveDesks(props: VirtualOfficeProps): AgentDesk[] { function resolveDesks(props: VirtualOfficeProps): AgentDesk[] {
const { agents, tasks, presence, orgId } = props; const { agents, tasks, taskRuns, presence, orgId } = props;
const orgAgents = agents.filter((a) => a.orgId === orgId); return agents.filter((a) => a.orgId === orgId).map((agent) => {
return orgAgents.map((agent) => {
const p = presence.find((r) => r.principalType === "agent" && r.principalId === agent.id); const p = presence.find((r) => r.principalType === "agent" && r.principalId === agent.id);
const activeTask = tasks.find( const activeTask = tasks.find((t) => t.orgId === orgId && t.assigneeAgentId === agent.id && t.status === "in_progress");
(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 };
const status = mapStatus(p?.status);
return {
agent,
status,
statusText: p?.statusText ?? "",
currentTask: activeTask?.title ?? null,
};
}); });
} }
function mapStatus(raw: string | undefined): PresenceStatus { function mapStatus(raw: string | undefined, running: boolean): PresenceStatus {
if (running) return "active";
if (!raw) return "idle"; if (!raw) return "idle";
if (raw === "thinking") return "thinking"; if (raw === "thinking") return "thinking";
if (raw === "waiting_approval" || raw === "busy") return "busy"; if (raw === "waiting_approval" || raw === "busy") return "busy";
@@ -45,25 +39,39 @@ function mapStatus(raw: string | undefined): PresenceStatus {
return "idle"; return "idle";
} }
const STATUS_RING: Record<PresenceStatus, string> = { const STATUS_COLOR: Record<PresenceStatus, string> = {
active: "var(--green)", active: "#34d399",
thinking: "var(--accent)", thinking: "#818cf8",
busy: "var(--amber)", busy: "#fbbf24",
idle: "var(--text-4)", idle: "rgba(120,120,120,0.35)",
}; };
const DEPT_FLOOR: Record<string, { label: string; color: string }> = { const DESK_BG: CSSProperties = {
engineering: { label: "Engineering", color: "var(--accent-soft)" }, background: "rgba(255,255,255,0.03)",
product: { label: "Product", color: "rgba(168, 130, 255, 0.08)" }, border: "1px solid rgba(255,255,255,0.06)",
design: { label: "Design", color: "rgba(255, 170, 130, 0.08)" }, borderRadius: 10,
marketing: { label: "Marketing", color: "rgba(130, 200, 255, 0.08)" }, padding: "10px 6px 8px",
operations: { label: "Operations", color: "rgba(180, 180, 180, 0.06)" }, 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 MONITOR: CSSProperties = {
const key = dept.toLowerCase(); width: 28,
return DEPT_FLOOR[key] ?? { label: dept, color: "rgba(180, 180, 180, 0.06)" }; 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) { export function VirtualOffice(props: VirtualOfficeProps) {
const desks = resolveDesks(props); const desks = resolveDesks(props);
@@ -76,127 +84,132 @@ export function VirtualOffice(props: VirtualOfficeProps) {
departments.get(dept)!.push(desk); departments.get(dept)!.push(desk);
} }
if (desks.length === 0) { if (desks.length === 0) return null;
return <div style={{ padding: 24, color: "var(--text-3)", fontSize: 13 }}>No agents yet</div>;
}
return ( return (
<div style={{ padding: "12px 8px", overflowY: "auto", height: "100%" }}> <div style={{ padding: "12px 10px", overflowY: "auto", height: "100%" }}>
{[...departments.entries()].map(([dept, deptDesks]) => { {[...departments.entries()].map(([dept, deptDesks]) => (
const floor = floorForDept(dept); <Room key={dept} label={dept} desks={deptDesks} hoveredId={hoveredId} onHover={setHoveredId} />
return ( ))}
<div key={dept} style={{ marginBottom: 16 }}>
<div style={{
fontSize: 10,
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.05em",
color: "var(--text-3)",
padding: "0 8px 6px",
}}>
{floor.label}
</div>
<div style={{
background: floor.color,
borderRadius: 10,
padding: 8,
display: "grid",
gridTemplateColumns: "repeat(auto-fill, 64px)",
gap: 4,
justifyContent: "start",
}}>
{deptDesks.map((desk) => (
<DeskCell
key={desk.agent.id}
desk={desk}
hovered={hoveredId === desk.agent.id}
onHover={setHoveredId}
/>
))}
</div>
</div>
);
})}
</div> </div>
); );
} }
function DeskCell({ function Room({ label, desks, hoveredId, onHover }: {
desk, label: string;
hovered, desks: AgentDesk[];
onHover, hoveredId: string | null;
}: {
desk: AgentDesk;
hovered: boolean;
onHover: (id: string | null) => void; onHover: (id: string | null) => void;
}) { }) {
const ring = STATUS_RING[desk.status]; return (
const isWorking = desk.status === "active" || desk.status === "thinking"; <div style={{ marginBottom: 14 }}>
<div style={{
fontSize: 10,
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.06em",
color: "var(--text-4)",
padding: "0 4px 5px",
}}>
{label}
</div>
<div style={{
border: "1px solid rgba(255,255,255,0.05)",
borderRadius: 12,
padding: 6,
background: "rgba(255,255,255,0.015)",
display: "grid",
gridTemplateColumns: "repeat(auto-fill, 72px)",
gap: 4,
justifyContent: "start",
}}>
{desks.map((desk) => (
<Workstation key={desk.agent.id} desk={desk} hovered={hoveredId === desk.agent.id} onHover={onHover} />
))}
</div>
</div>
);
}
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 ( return (
<div <div
style={{ style={{
display: "flex", ...DESK_BG,
flexDirection: "column", borderColor: hovered ? "rgba(255,255,255,0.14)" : undefined,
alignItems: "center", boxShadow: glowing ? `0 0 12px -4px ${color}` : undefined,
padding: "6px 2px",
borderRadius: 8,
cursor: "default",
position: "relative",
transition: "background 0.15s",
background: hovered ? "rgba(255,255,255,0.06)" : "transparent",
}} }}
onMouseEnter={() => onHover(desk.agent.id)} onMouseEnter={() => onHover(desk.agent.id)}
onMouseLeave={() => onHover(null)} onMouseLeave={() => onHover(null)}
> >
<div style={{ ...MONITOR, borderColor: glowing ? color : undefined }}>
{glowing && <div style={{ width: "100%", height: "100%", background: `${color}22` }} />}
</div>
<div style={{ width: 6, height: 3, background: "rgba(255,255,255,0.08)", borderRadius: "0 0 2px 2px", marginBottom: 4 }} />
<div style={{ position: "relative" }}> <div style={{ position: "relative" }}>
<Avatar name={desk.agent.name} size="lg" bot /> <Avatar name={desk.agent.name} size="sm" bot />
<div style={{ <div style={{
position: "absolute", position: "absolute",
bottom: -1, bottom: -1,
right: -1, right: -2,
width: 10, width: 7,
height: 10, height: 7,
borderRadius: "50%", borderRadius: "50%",
background: ring, background: color,
border: "2px solid var(--panel)", border: "1.5px solid var(--bg)",
boxShadow: isWorking ? `0 0 4px ${ring}` : undefined, boxShadow: glowing ? `0 0 5px ${color}` : undefined,
}} /> }} />
</div> </div>
<div style={{ <div style={{
fontSize: 10, fontSize: 9,
color: "var(--text-2)", color: "var(--text-3)",
marginTop: 4, marginTop: 3,
textAlign: "center", textAlign: "center",
lineHeight: 1.2, lineHeight: 1.15,
maxWidth: 60, maxWidth: 64,
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
whiteSpace: "nowrap", whiteSpace: "nowrap",
}}> }}>
{desk.agent.name.split(" ")[0]} {desk.agent.name.split(" ")[0]}
</div> </div>
{hovered && desk.currentTask && ( {hovered && (
<Tooltip name={desk.agent.name} role={desk.agent.role} task={desk.currentTask} status={desk.status} />
)}
</div>
);
}
function Tooltip({ name, role, task, status }: { name: string; role: string; task: string | null; status: PresenceStatus }) {
return (
<div style={{
position: "absolute",
bottom: "calc(100% + 6px)",
left: "50%",
transform: "translateX(-50%)",
background: "var(--panel)",
border: "1px solid var(--border)",
borderRadius: 8,
padding: "6px 10px",
zIndex: 30,
boxShadow: "0 4px 16px rgba(0,0,0,0.18)",
pointerEvents: "none",
minWidth: 120,
maxWidth: 200,
}}>
<div style={{ fontSize: 12, fontWeight: 560, color: "var(--text-1)", marginBottom: 1 }}>{name}</div>
<div style={{ fontSize: 10, color: "var(--text-3)", marginBottom: task ? 4 : 0 }}>{role}</div>
{task && (
<div style={{ <div style={{
position: "absolute", fontSize: 10,
bottom: "100%", color: STATUS_COLOR[status],
left: "50%",
transform: "translateX(-50%)",
background: "var(--panel)",
border: "1px solid var(--border)",
borderRadius: 6,
padding: "4px 8px",
fontSize: 11,
color: "var(--text-1)",
whiteSpace: "nowrap",
maxWidth: 200,
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
zIndex: 20, whiteSpace: "nowrap",
boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
marginBottom: 4,
pointerEvents: "none",
}}> }}>
{desk.currentTask} {task}
</div> </div>
)} )}
</div> </div>