diff --git a/app/pixel-office/page.tsx b/app/pixel-office/page.tsx index e9bf868..b49c032 100644 --- a/app/pixel-office/page.tsx +++ b/app/pixel-office/page.tsx @@ -239,6 +239,11 @@ export default function PixelOfficePage() { const [versionLoading, setVersionLoading] = useState(false) const [versionLoadFailed, setVersionLoadFailed] = useState(false) const [showIdleRank, setShowIdleRank] = useState(false) + const selectedAgentOpenedAtRef = useRef(0) + const tokenRankOpenedAtRef = useRef(0) + const modelPanelOpenedAtRef = useRef(0) + const fullscreenPhotoOpenedAtRef = useRef(0) + const [subagentCreatorInfo, setSubagentCreatorInfo] = useState<{ parentAgentId: string; x: number; y: number } | null>(null) const [serverTooltip, setServerTooltip] = useState<{ open: boolean; x: number; y: number }>({ open: false, x: 0, y: 0 }) const idleRankRef = useRef | null>(null) const floatingCommentsRef = useRef>([]) @@ -977,6 +982,7 @@ export default function PixelOfficePage() { if (!editor.isEditMode) { // Non-edit mode: check camera click or character click if (e.button === 0) { + setSubagentCreatorInfo(null) const rect = canvasRef.current.getBoundingClientRect() const clickX = e.clientX - rect.left const clickY = e.clientY - rect.top @@ -1026,6 +1032,7 @@ export default function PixelOfficePage() { tileY >= f.row && tileY < f.row + entry.footprintH })) { // Click on right bookshelf — show model panel + modelPanelOpenedAtRef.current = performance.now() setShowModelPanel(true) } else if (office.layout.furniture.some(f => { if (f.uid !== 'whiteboard-r') return false @@ -1035,6 +1042,7 @@ export default function PixelOfficePage() { tileY >= f.row && tileY < f.row + entry.footprintH })) { // Click on right whiteboard — show token ranking + tokenRankOpenedAtRef.current = performance.now() setShowTokenRank(true) } else if (office.layout.furniture.some(f => { if (f.uid !== 'clock-r') return false @@ -1076,6 +1084,7 @@ export default function PixelOfficePage() { void refreshGatewayHealthSnapshot() } else if (photographRef.current && tileX >= 10 && tileX < 17 && tileY >= -0.5 && tileY < 1) { // Click on wall photograph — fullscreen view + fullscreenPhotoOpenedAtRef.current = performance.now() setFullscreenPhoto(true) } else if (contributionsRef.current && contributionsRef.current.username !== 'mock' && tileX >= 1 && tileX < 10 && tileY >= -0.5 && tileY < 1) { // Click on GitHub contribution heatmap — open profile @@ -1091,12 +1100,35 @@ export default function PixelOfficePage() { let found = false for (const [aid, cid] of map.entries()) { if (cid === charId) { + selectedAgentOpenedAtRef.current = performance.now() setSelectedAgentId(aid) found = true break } } - if (!found) setSelectedAgentId(null) + if (!found) { + const clickedCh = office.characters.get(charId) + if (clickedCh?.systemRoleType === 'gateway_sre' && isMobileViewport) { + setServerTooltip({ open: true, x: clickX, y: clickY }) + void refreshGatewayHealthSnapshot() + } else if (clickedCh?.isSubagent && clickedCh.parentAgentId != null) { + let parentAgentId: string | null = null + for (const [aid, cid] of map.entries()) { + if (cid === clickedCh.parentAgentId) { + parentAgentId = aid + break + } + } + if (parentAgentId && isMobileViewport) { + setSubagentCreatorInfo({ + parentAgentId, + x: clickX, + y: clickY, + }) + } + } + setSelectedAgentId(null) + } } else { setSelectedAgentId(null) } @@ -1319,13 +1351,17 @@ export default function PixelOfficePage() { setShowModelPanel(false) return } + if (subagentCreatorInfo) { + setSubagentCreatorInfo(null) + return + } if (selectedAgentId) { setSelectedAgentId(null) } } window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) - }, [fullscreenPhoto, isEditMode, selectedAgentId, showActivityHeatmap, showIdleRank, showModelPanel, showPhonePanel, showTokenRank]) + }, [fullscreenPhoto, isEditMode, selectedAgentId, showActivityHeatmap, showIdleRank, showModelPanel, showPhonePanel, showTokenRank, subagentCreatorInfo]) // ── Editor toolbar callbacks ────────────────────────────────── const handleToolChange = useCallback((tool: EditTool) => { @@ -1686,7 +1722,7 @@ export default function PixelOfficePage() { )} {/* Server click tooltip */} - {serverTooltip.open && !isEditMode && !selectedAgentId && !isMobileViewport && (() => { + {serverTooltip.open && !isEditMode && !selectedAgentId && (() => { const snapshot = gatewaySreRef.current const status = snapshot.status === 'healthy' || snapshot.status === 'degraded' || snapshot.status === 'down' ? snapshot.status @@ -1696,10 +1732,12 @@ export default function PixelOfficePage() { : status === 'degraded' ? 'text-yellow-400' : status === 'down' ? 'text-red-400' : 'text-[var(--text-muted)]' + const tooltipLeft = Math.max(8, Math.min(serverTooltip.x + 12, (containerRef.current?.clientWidth || 300) - (isMobileViewport ? 220 : 200))) + const tooltipTop = Math.max(8, serverTooltip.y + 12) return (
e.stopPropagation()} onClick={(e) => e.stopPropagation()} > @@ -1743,7 +1781,13 @@ export default function PixelOfficePage() { : stats.todayAvgResponseMs > 30000 ? 'text-yellow-400' : 'text-green-400' : 'text-[var(--text-muted)]' return ( -
setSelectedAgentId(null)}> +
{ + if (isMobileViewport && performance.now() - selectedAgentOpenedAtRef.current < 280) return + setSelectedAgentId(null) + }} + >
e.stopPropagation()}>
@@ -1770,9 +1814,46 @@ export default function PixelOfficePage() { ) })()} + {/* Mobile subagent creator tooltip */} + {subagentCreatorInfo && !isEditMode && isMobileViewport && (() => { + const tooltipLeft = Math.max(8, Math.min(subagentCreatorInfo.x + 12, (containerRef.current?.clientWidth || 300) - 220)) + const tooltipTop = Math.max(8, subagentCreatorInfo.y + 12) + return ( +
e.stopPropagation()} + onClick={(e) => e.stopPropagation()} + > + +
+ 🧑‍🔧 + 临时工来源 +
+
+
{subagentCreatorInfo.parentAgentId} agent创建的subagent
+
+
+ ) + })()} + {/* Model panel (bookshelf click) */} {showModelPanel && !isEditMode && ( -
setShowModelPanel(false)}> +
{ + if (isMobileViewport && performance.now() - modelPanelOpenedAtRef.current < 280) return + setShowModelPanel(false) + }} + >
e.stopPropagation()}>
📚 {t('models.title')} @@ -1816,8 +1897,13 @@ export default function PixelOfficePage() { .map(a => ({ ...a, tokens: agentStatsRef.current.get(a.agentId)?.totalTokens || 0 })) .sort((a, b) => b.tokens - a.tokens) const maxTokens = ranked[0]?.tokens || 1 + const handleCloseTokenRankOverlay = () => { + // Prevent the opening tap from immediately closing the modal on mobile. + if (isMobileViewport && performance.now() - tokenRankOpenedAtRef.current < 280) return + setShowTokenRank(false) + } return ( -
setShowTokenRank(false)}> +
e.stopPropagation()}>
📊 Token {t('agent.tokenUsage')} @@ -1863,7 +1949,7 @@ export default function PixelOfficePage() { const svgH = topPad + 7 * (cellSize + gap) return (
setShowActivityHeatmap(false)}> -
e.stopPropagation()}> +
e.stopPropagation()}>
🕐 {t('pixelOffice.heatmap.title')} @@ -2000,7 +2086,13 @@ export default function PixelOfficePage() { {/* Fullscreen photograph viewer */} {fullscreenPhoto && photographRef.current && ( -
setFullscreenPhoto(false)}> +
{ + if (isMobileViewport && performance.now() - fullscreenPhotoOpenedAtRef.current < 280) return + setFullscreenPhoto(false) + }} + > photograph