Files
claude-office/src/lib/positions.ts
T
JianMiauandClaude Fable 5 f935a2ef2d 建立 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>
2026-07-20 12:31:18 +08:00

63 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
};
});
}