建立 Claude Office 虛擬辦公室(模擬 + 實況雙模式)
摘要: 參考 WW-AI-Lab/openclaw-office(MIT)的 office-2d 邏輯,重新實作 Claude 風格的 multi-agent 虛擬辦公室:四分區平面圖、走廊尋路、chibi 小人、狀態表情氣泡、 subagent 熱桌、會議聚集,以及可視化真實 Claude Code 活動的實況模式。 根本原因: 需要一個能一眼看出各專案 Claude Code 工作狀態(是否派發任務、調用工具、 派生 subagent)的監控介面,現有工具沒有對應的視覺化。 影響: 新專案,不影響既有系統。實況模式的監看伺服器僅綁 localhost:5179, 會讀取本機 ~/.claude/projects/ 的 session transcripts。 修法: - Vite 6 + React 19 + Zustand,SVG 平面圖 + CSS 動畫,實測 60fps - src/sim/director.ts:劇本模擬引擎(FSM + 會議排程) - server/watch.mjs:零依賴 Node watcher,tail JSONL 轉譯為 SSE 事件 - src/gateway/live.ts:事件對映 store action,支援模式即時切換 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { MEETING_CENTER, ZONES } from "./constants";
|
||||
import type { Point } from "./types";
|
||||
|
||||
export interface DeskSlot {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
function gridSlots(
|
||||
zone: { x: number; y: number; width: number; height: number },
|
||||
cols: number,
|
||||
rows: number,
|
||||
padX: number,
|
||||
padTop: number,
|
||||
padBottom: number,
|
||||
): DeskSlot[] {
|
||||
const availW = zone.width - padX * 2;
|
||||
const availH = zone.height - padTop - padBottom;
|
||||
const cellW = availW / cols;
|
||||
const cellH = availH / rows;
|
||||
const slots: DeskSlot[] = [];
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
slots.push({
|
||||
x: Math.round(zone.x + padX + cellW * (col + 0.5)),
|
||||
y: Math.round(zone.y + padTop + cellH * (row + 0.5)),
|
||||
});
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
/** 6 fixed desks for main agents (3 × 2). */
|
||||
export const DESK_SLOTS = gridSlots(ZONES.desk, 3, 2, 60, 66, 40);
|
||||
|
||||
/** 8 hot desks for subagents (4 × 2). */
|
||||
export const HOT_DESK_SLOTS = gridSlots(ZONES.hotDesk, 4, 2, 50, 70, 36);
|
||||
|
||||
/** Standing spots in the lounge, between the sofas and the reception desk. */
|
||||
export const LOUNGE_ANCHORS: Point[] = (() => {
|
||||
const lz = ZONES.lounge;
|
||||
return [
|
||||
{ x: lz.x + 200, y: lz.y + 88 },
|
||||
{ x: lz.x + 265, y: lz.y + 140 },
|
||||
{ x: lz.x + 360, y: lz.y + 88 },
|
||||
{ x: lz.x + 60, y: lz.y + 150 },
|
||||
{ x: lz.x + 145, y: lz.y + 140 },
|
||||
{ x: lz.x + 430, y: lz.y + 150 },
|
||||
];
|
||||
})();
|
||||
|
||||
/** Circular seats around the meeting table. */
|
||||
export function meetingSeats(count: number, center: Point = MEETING_CENTER): Point[] {
|
||||
const radius = Math.min(74 + count * 6, 108);
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
const angle = (2 * Math.PI * i) / count - Math.PI / 2;
|
||||
return {
|
||||
x: Math.round(center.x + Math.cos(angle) * radius),
|
||||
y: Math.round(center.y + Math.sin(angle) * radius),
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user