feat: Agent Office — AI 에이전트 가상 오피스 (#2)
## Summary - Canvas 2D 픽셀아트 오피스 렌더링 (SpriteSheet + TileMap + AgentSprite) - WebSocket 실시간 에이전트 상태 동기화 (useAgentManager) - ChatPanel (명령/승인) + TaskHistory (작업 이력) UI - 다크 테마 + glassmorphism 패널 ## Changes (7 commits) - API helpers + route + Lab entry - Canvas engine: SpriteSheet, TileMap, AgentSprite, OfficeRenderer - React hooks: useAgentManager, useOfficeCanvas - Components: ChatPanel, TaskHistory - Main page + CSS - Code review fixes: claude agent 참조 제거, rejected 배지 추가 Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
90
src/pages/agent-office/canvas/TileMap.js
Normal file
90
src/pages/agent-office/canvas/TileMap.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const WALL_COLOR = '#2a2a3a';
|
||||
const DESK_COLOR = '#6b5b3a';
|
||||
const DESK_TOP = '#8b7b5a';
|
||||
const TABLE_COLOR = '#5a4a2a';
|
||||
const SOFA_COLOR = '#884444';
|
||||
const MONITOR_COLOR = '#224466';
|
||||
const MONITOR_SCREEN = '#44aacc';
|
||||
|
||||
export function drawTileMap(ctx, mapData, width, height) {
|
||||
const { tileSize, cols, rows, layers, furniture, colors } = mapData;
|
||||
const scaleX = width / (cols * tileSize);
|
||||
const scaleY = height / (rows * tileSize);
|
||||
const scale = Math.min(scaleX, scaleY);
|
||||
|
||||
const offsetX = (width - cols * tileSize * scale) / 2;
|
||||
const offsetY = (height - rows * tileSize * scale) / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(offsetX, offsetY);
|
||||
ctx.scale(scale, scale);
|
||||
|
||||
const floor = layers.floor;
|
||||
for (let r = 0; r < rows; r++) {
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const tile = floor[r][c];
|
||||
ctx.fillStyle = colors[String(tile)] || '#3a3a50';
|
||||
ctx.fillRect(c * tileSize, r * tileSize, tileSize, tileSize);
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.03)';
|
||||
ctx.strokeRect(c * tileSize, r * tileSize, tileSize, tileSize);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fillStyle = WALL_COLOR;
|
||||
ctx.fillRect(0, 0, cols * tileSize, 4);
|
||||
|
||||
for (const f of furniture) {
|
||||
const fx = f.x * tileSize;
|
||||
const fy = f.y * tileSize;
|
||||
const fw = (f.w || 2) * tileSize;
|
||||
const fh = (f.h || 2) * tileSize;
|
||||
|
||||
if (f.type === 'desk') {
|
||||
ctx.fillStyle = DESK_COLOR;
|
||||
ctx.fillRect(fx, fy, fw, fh);
|
||||
ctx.fillStyle = DESK_TOP;
|
||||
ctx.fillRect(fx + 2, fy + 2, fw - 4, 6);
|
||||
const mx = fx + fw / 2 - 8;
|
||||
ctx.fillStyle = MONITOR_COLOR;
|
||||
ctx.fillRect(mx, fy + 4, 16, 12);
|
||||
ctx.fillStyle = MONITOR_SCREEN;
|
||||
ctx.fillRect(mx + 2, fy + 6, 12, 8);
|
||||
if (f.label) {
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.6)';
|
||||
ctx.font = '8px monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(f.label, fx + fw / 2, fy + fh + 12);
|
||||
}
|
||||
} else if (f.type === 'table') {
|
||||
ctx.fillStyle = TABLE_COLOR;
|
||||
ctx.fillRect(fx, fy, fw, fh);
|
||||
ctx.fillStyle = '#7a6a4a';
|
||||
ctx.fillRect(fx + 4, fy + 4, fw - 8, fh - 8);
|
||||
} else if (f.type === 'sofa') {
|
||||
ctx.fillStyle = SOFA_COLOR;
|
||||
ctx.fillRect(fx, fy, 48, 32);
|
||||
ctx.fillStyle = '#aa5555';
|
||||
ctx.fillRect(fx + 4, fy + 4, 40, 24);
|
||||
} else if (f.type === 'coffee') {
|
||||
ctx.fillStyle = '#664422';
|
||||
ctx.fillRect(fx + 8, fy + 8, 16, 20);
|
||||
ctx.fillStyle = '#886644';
|
||||
ctx.fillRect(fx + 6, fy + 6, 20, 4);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
return { scale, offsetX, offsetY, tileSize };
|
||||
}
|
||||
|
||||
export function worldToTile(mapData, renderInfo, canvasX, canvasY) {
|
||||
const { scale, offsetX, offsetY, tileSize } = renderInfo;
|
||||
const wx = (canvasX - offsetX) / scale;
|
||||
const wy = (canvasY - offsetY) / scale;
|
||||
return { col: Math.floor(wx / tileSize), row: Math.floor(wy / tileSize), worldX: wx, worldY: wy };
|
||||
}
|
||||
|
||||
export function tileToCanvas(mapData, renderInfo, col, row) {
|
||||
const { scale, offsetX, offsetY, tileSize } = renderInfo;
|
||||
return { x: offsetX + col * tileSize * scale + (tileSize * scale) / 2, y: offsetY + row * tileSize * scale + (tileSize * scale) / 2 };
|
||||
}
|
||||
Reference in New Issue
Block a user