feat(frontend): BeOS icons as agent activity indicators, remove background scatter
This commit is contained in:
parent
63ce6ac1fa
commit
399c7958ff
1 changed files with 61 additions and 64 deletions
|
|
@ -46,71 +46,59 @@ function spawnSparkle(x, y) {
|
|||
if (sparkles.length > 60) sparkles.shift();
|
||||
}
|
||||
|
||||
// ── BeOS tile background system ──────────────────────────────────
|
||||
const beosTiles = [];
|
||||
// ── BeOS activity icons ──────────────────────────────────────────
|
||||
const activityIcons = {};
|
||||
const ACTIVITY_ICONS = [
|
||||
'dispatch', 'code', 'build', 'review', 'research', 'analyze',
|
||||
'write', 'security', 'monitor', 'memory', 'coach', 'heartbeat',
|
||||
'mail', 'collaborate', 'present', 'vault', 'think', 'idle',
|
||||
'blocked', 'document', 'terminal', 'package', 'network',
|
||||
'search', 'design', 'data',
|
||||
];
|
||||
|
||||
function loadBeosTiles() {
|
||||
const tileNames = [
|
||||
'bell', 'email', 'clock', 'folder-open', 'folder-drag-accept',
|
||||
'audio-volume-high', 'audio-volume-medium', 'audio-volume-muted',
|
||||
'bluetooth-active', 'firefox08icon', 'firebirdicon',
|
||||
'accessories-calculator', 'accessories-text-editor',
|
||||
'GameBoy_1_32x32x8', 'GameBoy 2_1_32x32x8',
|
||||
'alarm-clock-icon', 'config-users', 'Cinder-Block-icon',
|
||||
'appointment-missed', 'appointment-soon',
|
||||
];
|
||||
for (const name of tileNames) {
|
||||
function loadActivityIcons() {
|
||||
ACTIVITY_ICONS.forEach(name => {
|
||||
const img = new Image();
|
||||
img.src = `/office/beos-tiles/${name}.png`;
|
||||
beosTiles.push(img);
|
||||
}
|
||||
img.src = `/office/beos-icons/${name}.png`;
|
||||
activityIcons[name] = img;
|
||||
});
|
||||
}
|
||||
|
||||
// Track placed BeOS tile positions (computed once)
|
||||
let beosPlacements = null;
|
||||
let beosPlacementsComputed = false;
|
||||
// Map agent state → BeOS icon name
|
||||
function getActivityIcon(agent) {
|
||||
const activity = agent.activity || 'idle';
|
||||
|
||||
function computeBeosPlacements() {
|
||||
if (beosPlacementsComputed) return;
|
||||
beosPlacementsComputed = true;
|
||||
beosPlacements = [];
|
||||
// Sparse grid: every 4 tiles horizontally, every 3 vertically
|
||||
const gridSpacingX = 4;
|
||||
const gridSpacingY = 3;
|
||||
for (let row = 0; row < ROWS; row += gridSpacingY) {
|
||||
for (let col = 0; col < COLS; col += gridSpacingX) {
|
||||
const px = col * TILE_SIZE;
|
||||
const py = row * TILE_SIZE;
|
||||
// Skip dirt path rows
|
||||
const onHPath = (py >= 48 && py < 64) || (py >= 224 && py < 240) || (py >= 544 && py < 560);
|
||||
const onVPath = (col >= 6 && col < 7) || (col >= 13 && col < 14) || (col >= 22 && col < 23);
|
||||
if (onHPath || onVPath) continue;
|
||||
// Skip building areas
|
||||
let inBuilding = false;
|
||||
for (const b of BUILDINGS) {
|
||||
if (col >= b.col && col < b.col + b.w && row >= b.row && row < b.row + b.h) {
|
||||
inBuilding = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inBuilding) continue;
|
||||
const tileIdx = Math.floor(Math.random() * beosTiles.length);
|
||||
beosPlacements.push({ px, py, tileIdx });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activity === 'idle' || activity === 'offline') return 'idle';
|
||||
if (activity === 'blocked') return 'blocked';
|
||||
if (activity === 'thinking') return 'think';
|
||||
|
||||
function drawBeosTiles(ctx) {
|
||||
if (!beosPlacementsComputed) computeBeosPlacements();
|
||||
if (!beosPlacements) return;
|
||||
ctx.globalAlpha = 0.3;
|
||||
for (const p of beosPlacements) {
|
||||
const img = beosTiles[p.tileIdx];
|
||||
if (img && img.complete && img.naturalWidth > 0) {
|
||||
ctx.drawImage(img, p.px, p.py, TILE_SIZE, TILE_SIZE);
|
||||
}
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
// activity === 'working' → check currentTask keywords
|
||||
const task = (agent.currentTask || '').toLowerCase();
|
||||
|
||||
if (/\b(dispatch|rout)\b/.test(task)) return 'dispatch';
|
||||
if (/\b(code|implement|build|pr)\b/.test(task)) return /\bbuild\b/.test(task) ? 'build' : 'code';
|
||||
if (/\b(review|audit)\b/.test(task)) return 'review';
|
||||
if (/\bresearch\b/.test(task)) return 'research';
|
||||
if (/\bsearch\b/.test(task)) return 'search';
|
||||
if (/\banalyz\b/.test(task)) return 'analyze';
|
||||
if (/\b(writ|draft|content|blog)\b/.test(task)) return 'write';
|
||||
if (/\bsecurit|complianc\b/.test(task)) return 'security';
|
||||
if (/\bmonitor\b/.test(task)) return 'monitor';
|
||||
if (/\bhealth|check\b/.test(task)) return 'heartbeat';
|
||||
if (/\b(memory|index|vault|note)\b/.test(task)) return /\bvault\b/.test(task) ? 'vault' : 'memory';
|
||||
if (/\b(coach|learn|improv)\b/.test(task)) return 'coach';
|
||||
if (/\b(mail|message|communicat)\b/.test(task)) return 'mail';
|
||||
if (/\b(collaborat|pair|meeting)\b/.test(task)) return 'collaborate';
|
||||
if (/\b(present|slide|deck)\b/.test(task)) return 'present';
|
||||
if (/\b(document|spec|readme)\b/.test(task)) return 'document';
|
||||
if (/\b(terminal|cli|script)\b/.test(task)) return 'terminal';
|
||||
if (/\b(deploy|package|release)\b/.test(task)) return 'package';
|
||||
if (/\b(network|connect|api)\b/.test(task)) return 'network';
|
||||
if (/\b(design|layout|ui|css)\b/.test(task)) return 'design';
|
||||
if (/\b(data|stats)\b/.test(task)) return 'data';
|
||||
|
||||
// Default: code icon for working agents
|
||||
return 'code';
|
||||
}
|
||||
|
||||
// ── Draw helpers ──────────────────────────────────────────────────
|
||||
|
|
@ -401,6 +389,16 @@ function drawAgent(ctx, agent, time) {
|
|||
|
||||
ctx.restore();
|
||||
|
||||
// ── Activity icon above agent ──────────────────────────────
|
||||
const iconName = getActivityIcon(agent);
|
||||
const iconImg = activityIcons[iconName];
|
||||
if (iconImg && iconImg.complete && iconImg.naturalWidth > 0) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.drawImage(iconImg, px - 12, py + yOffset - 60, 24, 24);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Blocked: red tint overlay
|
||||
if (activity === 'blocked') {
|
||||
ctx.fillStyle = 'rgba(255,50,50,0.3)';
|
||||
|
|
@ -519,9 +517,9 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
|
|||
}
|
||||
}, [onAgentClick]);
|
||||
|
||||
// Load BeOS tiles on mount
|
||||
// Load activity icons on mount
|
||||
useEffect(() => {
|
||||
loadBeosTiles();
|
||||
loadActivityIcons();
|
||||
}, []);
|
||||
|
||||
// Animation loop
|
||||
|
|
@ -538,7 +536,6 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
|
|||
ctx.clearRect(0, 0, CANVAS_W, CANVAS_H);
|
||||
|
||||
drawGrass(ctx);
|
||||
drawBeosTiles(ctx);
|
||||
drawDirtPaths(ctx);
|
||||
for (const b of BUILDINGS) drawBuilding(ctx, b);
|
||||
drawCampfire(ctx, time);
|
||||
|
|
@ -643,8 +640,8 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
|
|||
display: 'block',
|
||||
imageRendering: 'pixelated',
|
||||
cursor: hoveredAgent ? 'pointer' : 'default',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue