feat(agent-office): notification badges + CEO desk document panel + telegram test
- Add notification state management with badge counts in useAgentManager - Render exclamation badge on agent sprites (separate from status icons) - Add CEO desk document icon with click-to-open activity panel - Create DocumentPanel with unified activity feed + per-agent detail tabs - Add telegram test button to stock agent ChatPanel - Remove TaskHistory + bottom toolbar (replaced by DocumentPanel) - Add getActivityFeed API helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { drawTileMap } from './TileMap';
|
||||
import { AgentSprite } from './AgentSprite';
|
||||
import { getCharLabel } from './SpriteSheet';
|
||||
import { getCharLabel, drawNotificationBadge } from './SpriteSheet';
|
||||
|
||||
const STATUS_ICONS = {
|
||||
idle: null,
|
||||
@@ -19,6 +19,8 @@ export class OfficeRenderer {
|
||||
this.agents = {};
|
||||
this._animId = null;
|
||||
this._onClick = null;
|
||||
this._onCeoClick = null;
|
||||
this._ceoDocBadge = 0;
|
||||
|
||||
const agentIds = ['stock', 'music'];
|
||||
for (const id of agentIds) {
|
||||
@@ -56,6 +58,21 @@ export class OfficeRenderer {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
// CEO desk click detection
|
||||
const ceo = this.mapData.waypoints.ceo_desk;
|
||||
if (ceo) {
|
||||
const { scale, offsetX, offsetY, tileSize } = this.renderInfo;
|
||||
const cx = offsetX + ceo.x * tileSize * scale;
|
||||
const cy = offsetY + ceo.y * tileSize * scale;
|
||||
const hitW = 5 * tileSize * scale;
|
||||
const hitH = 2 * tileSize * scale;
|
||||
if (canvasX >= cx - tileSize * scale && canvasY >= cy - tileSize * scale &&
|
||||
canvasX <= cx + hitW && canvasY <= cy + hitH) {
|
||||
if (this._onCeoClick) this._onCeoClick();
|
||||
return 'ceo_desk';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -76,6 +93,19 @@ export class OfficeRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
setOnCeoClick(handler) {
|
||||
this._onCeoClick = handler;
|
||||
}
|
||||
|
||||
setCeoDocBadge(count) {
|
||||
this._ceoDocBadge = count;
|
||||
}
|
||||
|
||||
setAgentNotification(agentId, count) {
|
||||
const sprite = this.agents[agentId];
|
||||
if (sprite) sprite.setNotification(count);
|
||||
}
|
||||
|
||||
_loop(timestamp) {
|
||||
const { ctx, canvas, mapData } = this;
|
||||
|
||||
@@ -95,6 +125,9 @@ export class OfficeRenderer {
|
||||
this._drawOverlay(ctx, sprite, id);
|
||||
}
|
||||
|
||||
// CEO desk document icon
|
||||
this._drawCeoDoc(ctx);
|
||||
|
||||
this._animId = requestAnimationFrame(this._loop);
|
||||
}
|
||||
|
||||
@@ -111,6 +144,11 @@ export class OfficeRenderer {
|
||||
ctx.fillText(icon, cx, cy - 15 * scale);
|
||||
}
|
||||
|
||||
// Notification badge (separate from status icon)
|
||||
if (sprite.notificationCount > 0) {
|
||||
drawNotificationBadge(ctx, cx, cy - 15 * scale, sprite.notificationCount, scale * 1.5);
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
||||
ctx.font = `${8 * scale}px monospace`;
|
||||
ctx.textAlign = 'center';
|
||||
@@ -126,4 +164,48 @@ export class OfficeRenderer {
|
||||
ctx.fillText(sprite.detail, cx, bubbleY);
|
||||
}
|
||||
}
|
||||
|
||||
_drawCeoDoc(ctx) {
|
||||
if (!this.renderInfo) return;
|
||||
const ceo = this.mapData.waypoints.ceo_desk;
|
||||
if (!ceo) return;
|
||||
|
||||
const { scale, offsetX, offsetY, tileSize } = this.renderInfo;
|
||||
const dx = offsetX + (ceo.x - 1) * tileSize * scale;
|
||||
const dy = offsetY + (ceo.y - 1) * tileSize * scale;
|
||||
const docW = 12 * scale;
|
||||
const docH = 16 * scale;
|
||||
|
||||
// Paper
|
||||
ctx.fillStyle = '#e8e0d0';
|
||||
ctx.fillRect(dx, dy, docW, docH);
|
||||
// Lines on paper
|
||||
ctx.fillStyle = '#bbb';
|
||||
for (let i = 0; i < 4; i++) {
|
||||
ctx.fillRect(dx + 2 * scale, dy + (3 + i * 3) * scale, 8 * scale, 1);
|
||||
}
|
||||
// Folded corner
|
||||
ctx.fillStyle = '#d0c8b8';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(dx + docW - 3 * scale, dy);
|
||||
ctx.lineTo(dx + docW, dy + 3 * scale);
|
||||
ctx.lineTo(dx + docW - 3 * scale, dy + 3 * scale);
|
||||
ctx.fill();
|
||||
|
||||
// Badge on document
|
||||
if (this._ceoDocBadge > 0) {
|
||||
const bx = dx + docW;
|
||||
const by = dy;
|
||||
const r = 4 * scale;
|
||||
ctx.beginPath();
|
||||
ctx.arc(bx, by, r, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#f43f5e';
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = `bold ${5 * scale}px monospace`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(this._ceoDocBadge > 9 ? '9+' : String(this._ceoDocBadge), bx, by);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user