@sleep2agi/agent-network-dashboard 0.4.5-preview.9 → 0.4.5-preview.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mcp.json +11 -0
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +48 -48
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +4 -4
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page/next-font-manifest.json +1 -1
- package/.next/server/app/admin/page.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +4 -4
- package/.next/server/app/admin.rsc +15 -15
- package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +4 -4
- package/.next/server/app/index.rsc +15 -15
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/login/page/next-font-manifest.json +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +15 -15
- package/.next/server/app/login.segments/_full.segment.rsc +15 -15
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +4 -4
- package/.next/server/app/logs.rsc +15 -15
- package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/messages/page/next-font-manifest.json +1 -1
- package/.next/server/app/messages/page.js.nft.json +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +4 -4
- package/.next/server/app/messages.rsc +15 -15
- package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page/next-font-manifest.json +1 -1
- package/.next/server/app/node/page.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +4 -4
- package/.next/server/app/node.rsc +15 -15
- package/.next/server/app/node.segments/_full.segment.rsc +15 -15
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page/next-font-manifest.json +1 -1
- package/.next/server/app/nodes/page.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +4 -4
- package/.next/server/app/nodes.rsc +15 -15
- package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page/next-font-manifest.json +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +4 -4
- package/.next/server/app/server-logs.rsc +15 -15
- package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/networks/page.js.nft.json +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +4 -4
- package/.next/server/app/settings/networks.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +4 -4
- package/.next/server/app/settings/tokens.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +4 -4
- package/.next/server/app/settings.rsc +15 -15
- package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/page.js.nft.json +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +4 -4
- package/.next/server/app/tasks.rsc +15 -15
- package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +3 -3
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +1 -1
- package/.next/server/chunks/ssr/_0nne7mz._.js +3 -0
- package/.next/server/chunks/ssr/_0nne7mz._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0.zbtn5._.js +9 -0
- package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +1 -0
- package/.next/server/chunks/ssr/app_01y-~mp._.js +3 -0
- package/.next/server/chunks/ssr/app_01y-~mp._.js.map +1 -0
- package/.next/server/chunks/ssr/app_04n78pr._.js +3 -0
- package/.next/server/chunks/ssr/app_04n78pr._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0_agk0r._.js +3 -0
- package/.next/server/chunks/ssr/app_0_agk0r._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0aenxef._.js +3 -0
- package/.next/server/chunks/ssr/app_0aenxef._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.js +3 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0emvkn2._.js +3 -0
- package/.next/server/chunks/ssr/app_0emvkn2._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0esuljp._.js +3 -0
- package/.next/server/chunks/ssr/app_0esuljp._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0izi28j._.js +1 -1
- package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -1
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js +5 -5
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +1 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js +1 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +1 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +1 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +14 -14
- package/.next/server/pages/404.html +4 -4
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0-hlcuoemhe7w.js +7 -0
- package/.next/static/chunks/02_zpo7taex9o.js +7 -0
- package/.next/static/chunks/03b6jkic-1r9u.css +2 -0
- package/.next/static/chunks/06fliypy0.f0r.js +1 -0
- package/.next/static/chunks/06n3d~fbbtagk.js +1 -0
- package/.next/static/chunks/07eba1lz2u3p4.js +1 -0
- package/.next/static/chunks/0av2~0.96~d~g.js +1 -0
- package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
- package/.next/static/chunks/0g9aof0ob_fcj.js +1 -0
- package/.next/static/chunks/0gtbwcilfu6b7.js +1 -0
- package/.next/static/chunks/0s8jo6jypzkx5.js +1 -0
- package/.next/static/chunks/0ubqqhhpsaez4.js +1 -0
- package/.next/static/chunks/0vmfu_gz9tp4f.js +1 -0
- package/.next/static/chunks/0~qw9kxksr1v2.js +1 -0
- package/.next/static/chunks/12gprw3l0kvvf.js +1 -0
- package/.next/static/chunks/12mjtyh-j74~k.js +1 -0
- package/.next/static/chunks/12yuo353giomx.js +1 -0
- package/.next/static/media/4fa387ec64143e14-s.0.qu-9752pffj.woff2 +0 -0
- package/.next/static/media/5ce348bf30bf5439-s.0ee55_hj9qcer.woff2 +0 -0
- package/.next/static/media/6306c77e7c8268e4-s.0mao5jbfbduzp.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2 +0 -0
- package/.next/static/media/7d817b4c03b0c5f1-s.0uzt.a6d44yda.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.0mvwgmnhv29no.woff2 +0 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/admin/page.tsx +97 -27
- package/app/components/AgentCard.tsx +19 -5
- package/app/components/BroadcastBar.tsx +22 -4
- package/app/components/DispatchPanel.tsx +9 -5
- package/app/components/EmptyState.tsx +127 -12
- package/app/components/HealthBanner.tsx +11 -2
- package/app/components/HelpOverlay.tsx +50 -10
- package/app/components/InboxPanel.tsx +7 -4
- package/app/components/Sidebar.tsx +81 -44
- package/app/components/StatsBar.tsx +59 -36
- package/app/components/TaskChatPanel.tsx +8 -10
- package/app/components/TaskDrawer.tsx +72 -28
- package/app/components/TopoGraph.tsx +527 -41
- package/app/components/utils.ts +13 -0
- package/app/globals.css +90 -0
- package/app/lib/status.ts +85 -0
- package/app/lib/version.ts +7 -0
- package/app/login/page.tsx +40 -7
- package/app/logs/page.tsx +70 -21
- package/app/messages/page.tsx +34 -9
- package/app/node/page.tsx +52 -27
- package/app/nodes/page.tsx +45 -21
- package/app/page.tsx +103 -32
- package/app/server-logs/page.tsx +104 -28
- package/app/settings/networks/page.tsx +40 -16
- package/app/settings/page.tsx +39 -11
- package/app/tasks/[id]/page.tsx +2 -13
- package/app/tasks/page.tsx +66 -73
- package/package.json +1 -1
- package/public/intern_avatar.png +0 -0
- package/scripts/empty-state-test.mjs +41 -0
- package/scripts/fold-only.mjs +47 -0
- package/scripts/intern-alias-test.mjs +46 -0
- package/scripts/loop-review.mjs +270 -0
- package/scripts/network-derive-shot.mjs +16 -0
- package/scripts/network-derive-test.mjs +28 -0
- package/scripts/topo-brand-shot.mjs +61 -0
- package/scripts/topo-cluster-test.mjs +41 -0
- package/scripts/topo-controls-shot.mjs +24 -0
- package/scripts/topo-group-test.mjs +37 -0
- package/scripts/topo-inspect.mjs +56 -0
- package/scripts/topo-interaction-test.mjs +80 -0
- package/scripts/topo-mock-large.mjs +58 -0
- package/scripts/topo-r104-test.mjs +54 -0
- package/scripts/topo-shot.mjs +31 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__06w_u7t._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__06w_u7t._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0ehu0jb._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0ehu0jb._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js.map +0 -1
- package/.next/server/chunks/ssr/_13_v7-3._.js +0 -3
- package/.next/server/chunks/ssr/_13_v7-3._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0.0wpa~._.js +0 -3
- package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0ap_oiq._.js +0 -3
- package/.next/server/chunks/ssr/app_0ap_oiq._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0dk5wao._.js +0 -3
- package/.next/server/chunks/ssr/app_0dk5wao._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0et5yua._.js +0 -3
- package/.next/server/chunks/ssr/app_0et5yua._.js.map +0 -1
- package/.next/server/chunks/ssr/app_10b5blc._.js +0 -9
- package/.next/server/chunks/ssr/app_10b5blc._.js.map +0 -1
- package/.next/server/chunks/ssr/app_11vl967._.js +0 -3
- package/.next/server/chunks/ssr/app_11vl967._.js.map +0 -1
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +0 -3
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +0 -1
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +0 -3
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +0 -1
- package/.next/static/chunks/0-.hjh--kcdiy.js +0 -1
- package/.next/static/chunks/02afyxgpty3uf.js +0 -1
- package/.next/static/chunks/02flrewxlf_m_.js +0 -1
- package/.next/static/chunks/051gslfjka1kj.js +0 -1
- package/.next/static/chunks/07oobuohrpigy.js +0 -1
- package/.next/static/chunks/09al3qm76q7nd.js +0 -1
- package/.next/static/chunks/0e7c40p~wq665.js +0 -1
- package/.next/static/chunks/0g7m7zk.v7g6h.js +0 -1
- package/.next/static/chunks/0h.ajsk_uxigl.js +0 -1
- package/.next/static/chunks/0msa~bub38ytf.css +0 -2
- package/.next/static/chunks/0ogeals5ufhho.js +0 -1
- package/.next/static/chunks/10ir.g.n~hre0.js +0 -7
- package/.next/static/chunks/10njj7kn9ylkf.js +0 -1
- package/.next/static/chunks/12dcb86elsl-e.js +0 -7
- package/.next/static/chunks/13.dst7liziwo.js +0 -1
- package/.next/static/chunks/132e__998_q9r.js +0 -1
- package/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_buildManifest.js +0 -0
- /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_ssgManifest.js +0 -0
package/app/node/page.tsx
CHANGED
|
@@ -6,6 +6,8 @@ import Link from 'next/link';
|
|
|
6
6
|
import { Suspense } from 'react';
|
|
7
7
|
import { TaskChatPanel } from '../components/TaskChatPanel';
|
|
8
8
|
import { timeAgo } from '../components/utils';
|
|
9
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
10
|
+
import { SESSION_STATUS_TEXT_CLASS } from '../lib/status';
|
|
9
11
|
|
|
10
12
|
interface SessionDetail {
|
|
11
13
|
resume_id: string;
|
|
@@ -145,20 +147,29 @@ function NodeDetailContent() {
|
|
|
145
147
|
);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
/* Round 92: shared 5-state palette (was working / idle / other,
|
|
151
|
+
collapsed blocked + error into gray). */
|
|
152
|
+
const statusColor = SESSION_STATUS_TEXT_CLASS[session?.status || ''] || SESSION_STATUS_TEXT_CLASS.offline;
|
|
150
153
|
|
|
151
154
|
return (
|
|
152
155
|
<div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
|
|
153
|
-
{/* Header
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<
|
|
161
|
-
|
|
156
|
+
{/* Header — round 40: avatar joins the alias for cross-page hue
|
|
157
|
+
consistency; agent type lives in a subtitle so users know what
|
|
158
|
+
kind of node they're looking at. */}
|
|
159
|
+
<div className="flex items-center gap-3 mb-6 lg:ml-0 ml-10">
|
|
160
|
+
<Link href="/nodes" className="text-gray-500 hover:text-gray-300 text-sm shrink-0">← Nodes</Link>
|
|
161
|
+
<AliasAvatar alias={alias} size={32} />
|
|
162
|
+
<div className="min-w-0 flex-1">
|
|
163
|
+
<div className="flex items-center gap-2">
|
|
164
|
+
<h1 className="text-xl sm:text-2xl font-bold text-white truncate">{alias}</h1>
|
|
165
|
+
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${
|
|
166
|
+
sse > 0 ? (session?.status === 'working' ? 'bg-green-500 animate-pulse' : 'bg-emerald-400') : 'bg-gray-500'
|
|
167
|
+
}`} />
|
|
168
|
+
<span className={`text-[10px] uppercase tracking-wide font-semibold ${statusColor}`}>{session?.status || 'unknown'}</span>
|
|
169
|
+
</div>
|
|
170
|
+
{session?.agent && (
|
|
171
|
+
<div className="text-xs text-gray-500 truncate">{session.agent}{session.server ? <> <span className="text-gray-700 mx-1">·</span> {session.server}</> : null}</div>
|
|
172
|
+
)}
|
|
162
173
|
</div>
|
|
163
174
|
</div>
|
|
164
175
|
|
|
@@ -190,14 +201,22 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
|
|
|
190
201
|
|
|
191
202
|
return (
|
|
192
203
|
<div className="bg-[#0d0d1a] border border-[#2a2a4a] rounded-xl overflow-hidden h-[calc(100vh-140px)] flex flex-col">
|
|
193
|
-
{/* Tab bar
|
|
204
|
+
{/* Tab bar — round 40: emoji icons (💬 📋 📊) replaced with stroke
|
|
205
|
+
SVG to drop the "AI generated" tell. */}
|
|
194
206
|
<div className="flex border-b border-[#2a2a4a] bg-[#0a0a15] shrink-0">
|
|
195
|
-
{([
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
{([
|
|
208
|
+
{ id: 'chat', label: 'Chat', icon: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' },
|
|
209
|
+
{ id: 'events', label: 'Events', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },
|
|
210
|
+
{ id: 'info', label: 'Info', icon: 'M12 21a9 9 0 100-18 9 9 0 000 18z M12 8h.01 M11 12h1v4h1' },
|
|
211
|
+
] as const).map(t => (
|
|
212
|
+
<button key={t.id} onClick={() => setTab(t.id)}
|
|
213
|
+
className={`flex items-center gap-2 px-5 py-3 text-sm font-medium border-b-2 transition-colors ${
|
|
214
|
+
tab === t.id ? 'border-cyan-400 text-cyan-300' : 'border-transparent text-gray-500 hover:text-gray-300'
|
|
199
215
|
}`}>
|
|
200
|
-
|
|
216
|
+
<svg className="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
217
|
+
<path d={t.icon} />
|
|
218
|
+
</svg>
|
|
219
|
+
<span>{t.label}</span>
|
|
201
220
|
</button>
|
|
202
221
|
))}
|
|
203
222
|
</div>
|
|
@@ -299,13 +318,16 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
|
|
|
299
318
|
);
|
|
300
319
|
}
|
|
301
320
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
321
|
+
/** Round 59: emoji icons replaced with stroke SVG paths to drop the
|
|
322
|
+
* "AI generated" tell. Each path is sized to fit inside the 18px
|
|
323
|
+
* colored event dot. */
|
|
324
|
+
const EVENT_COLORS: Record<string, { dot: string; line: string; path: string }> = {
|
|
325
|
+
delivered: { dot: 'bg-blue-400', line: 'bg-blue-500/30', path: 'M12 5v14 M5 12l7 7 7-7' }, // down arrow
|
|
326
|
+
acked: { dot: 'bg-yellow-400', line: 'bg-yellow-500/30', path: 'M20 6 9 17l-5-5' }, // check
|
|
327
|
+
running: { dot: 'bg-green-400', line: 'bg-green-500/30', path: 'M13 2L4 14h6l-1 8 10-12h-6l1-8z' }, // bolt
|
|
328
|
+
replied: { dot: 'bg-purple-400', line: 'bg-purple-500/30', path: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' }, // chat bubble
|
|
329
|
+
failed: { dot: 'bg-red-400', line: 'bg-red-500/30', path: 'M18 6 6 18 M6 6l12 12' }, // X
|
|
330
|
+
created: { dot: 'bg-gray-400', line: 'bg-gray-500/30', path: 'M12 5v14 M5 12h14' }, // plus
|
|
309
331
|
};
|
|
310
332
|
|
|
311
333
|
function EventsTimeline({ events, loading }: { events: Array<{ id: number; event_type: string; from_status: string; to_status: string; detail: string; created_at: string; task_id?: string }>; loading: boolean }) {
|
|
@@ -334,9 +356,12 @@ function EventsTimeline({ events, loading }: { events: Array<{ id: number; event
|
|
|
334
356
|
const cfg = EVENT_COLORS[e.to_status] || EVENT_COLORS.created;
|
|
335
357
|
return (
|
|
336
358
|
<div key={e.id} className="relative pb-4 last:pb-0">
|
|
337
|
-
{/* Dot on line
|
|
338
|
-
|
|
339
|
-
|
|
359
|
+
{/* Dot on line — round 59: emoji icons replaced with
|
|
360
|
+
a 10px stroke SVG path matching event type. */}
|
|
361
|
+
<div className={`absolute -left-6 top-0.5 w-[18px] h-[18px] rounded-full ${cfg.dot} flex items-center justify-center z-10 border-2 border-[#111128]`}>
|
|
362
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#0a0a15" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
|
363
|
+
<path d={cfg.path} />
|
|
364
|
+
</svg>
|
|
340
365
|
</div>
|
|
341
366
|
{/* Content */}
|
|
342
367
|
<div className="ml-2">
|
package/app/nodes/page.tsx
CHANGED
|
@@ -5,17 +5,18 @@ import Link from 'next/link';
|
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
6
|
import { useSessions, useHealth } from '../lib/hooks';
|
|
7
7
|
import { TaskChatPanel } from '../components/TaskChatPanel';
|
|
8
|
-
import { EmptyState } from '../components/EmptyState';
|
|
8
|
+
import { EmptyState, NodesEmptyState } from '../components/EmptyState';
|
|
9
9
|
import { AliasAvatar } from '../components/AliasAvatar';
|
|
10
10
|
import type { Session } from '../components/types';
|
|
11
|
+
import { SESSION_STATUS_CHIP_CLASS as STATUS_COLORS } from '../lib/status';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
13
|
+
/** Round 81: shorten long server hostnames (Alibaba `iZ…oyZ` style)
|
|
14
|
+
* for table display. Returns the original string unchanged when ≤12
|
|
15
|
+
* chars. Full value should stay in `title=` for hover + screen-readers. */
|
|
16
|
+
function shortServer(server: string | null | undefined): string {
|
|
17
|
+
if (!server) return '—';
|
|
18
|
+
return server.length > 12 ? `${server.slice(0, 8)}…` : server;
|
|
19
|
+
}
|
|
19
20
|
|
|
20
21
|
type ViewMode = 'list' | 'grid';
|
|
21
22
|
type SessionRow = Session & { online: boolean };
|
|
@@ -121,6 +122,12 @@ export default function NodesPage() {
|
|
|
121
122
|
);
|
|
122
123
|
})()}
|
|
123
124
|
|
|
125
|
+
{/* Round 74: hide the filter+view chrome when there are no nodes
|
|
126
|
+
anywhere — these controls have nothing to act on, and they only
|
|
127
|
+
push the onboarding CTA further down. When at least one session
|
|
128
|
+
exists, the chrome is back even if the current filter happens
|
|
129
|
+
to hide everything (so users can clear filters). */}
|
|
130
|
+
{sessions.length > 0 && (
|
|
124
131
|
<div className="flex flex-wrap gap-3 mb-6">
|
|
125
132
|
<input
|
|
126
133
|
type="text"
|
|
@@ -142,7 +149,11 @@ export default function NodesPage() {
|
|
|
142
149
|
<option value="blocked">Blocked</option>
|
|
143
150
|
<option value="error">Error</option>
|
|
144
151
|
</select>
|
|
145
|
-
|
|
152
|
+
{/* Round 80: List/Grid toggle hidden on mobile — Grid uses
|
|
153
|
+
grid-cols-1 below `md` and List degrades to the same single
|
|
154
|
+
column, so the toggle has no visual effect under sm and only
|
|
155
|
+
steals a row of vertical space. */}
|
|
156
|
+
<div className="hidden sm:flex ml-auto rounded-lg border border-[#2a2a4a] bg-[#111128] p-1 text-sm">
|
|
146
157
|
{(['list', 'grid'] as const).map(mode => (
|
|
147
158
|
<button
|
|
148
159
|
key={mode}
|
|
@@ -159,17 +170,27 @@ export default function NodesPage() {
|
|
|
159
170
|
))}
|
|
160
171
|
</div>
|
|
161
172
|
</div>
|
|
173
|
+
)}
|
|
162
174
|
|
|
163
175
|
{loading ? (
|
|
164
176
|
<div className="animate-pulse space-y-2">
|
|
165
177
|
{[1,2,3,4].map(i => <div key={i} className="h-16 bg-gray-800/20 rounded-lg" />)}
|
|
166
178
|
</div>
|
|
167
179
|
) : filtered.length === 0 ? (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
sessions.length === 0 ? (
|
|
181
|
+
/* Round 73: first-run case — no agents anywhere. Use the same
|
|
182
|
+
onboarding empty state as the Overview (NodesEmptyState
|
|
183
|
+
includes the `npx … init` quickstart command). Previously this
|
|
184
|
+
was the "No nodes match your filters" copy, which was wrong
|
|
185
|
+
for new users who have no filters applied. */
|
|
186
|
+
<NodesEmptyState />
|
|
187
|
+
) : (
|
|
188
|
+
<EmptyState
|
|
189
|
+
variant="nodes"
|
|
190
|
+
title="No nodes match your filters"
|
|
191
|
+
sub="Try clearing search or status filters, or wait for an agent to register."
|
|
192
|
+
/>
|
|
193
|
+
)
|
|
173
194
|
) : viewMode === 'grid' ? (
|
|
174
195
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
|
175
196
|
{filtered.map(s => {
|
|
@@ -199,7 +220,7 @@ export default function NodesPage() {
|
|
|
199
220
|
</span>
|
|
200
221
|
</div>
|
|
201
222
|
<div className="mt-0.5 truncate text-xs text-gray-500">
|
|
202
|
-
{(s.agent || '—')}<span className="text-gray-700 mx-1.5">·</span
|
|
223
|
+
{(s.agent || '—')}<span className="text-gray-700 mx-1.5">·</span><span title={s.server || ''}>{shortServer(s.server)}</span>
|
|
203
224
|
</div>
|
|
204
225
|
</Link>
|
|
205
226
|
<div className="flex gap-1.5 shrink-0">
|
|
@@ -268,12 +289,14 @@ export default function NodesPage() {
|
|
|
268
289
|
</div>
|
|
269
290
|
) : (
|
|
270
291
|
<div className="space-y-2">
|
|
292
|
+
{/* Round 94: AGENT + SERVER merged into one `agent · server`
|
|
293
|
+
cell; the freed 2 columns expand CURRENT TASK so long task
|
|
294
|
+
content lines wrap less. Total still 12: 1+2+2+4+1+2. */}
|
|
271
295
|
<div className="hidden sm:grid sm:grid-cols-12 gap-2 px-4 py-2 text-xs text-gray-600 uppercase">
|
|
272
296
|
<div className="col-span-1">Status</div>
|
|
273
297
|
<div className="col-span-2">Alias</div>
|
|
274
|
-
<div className="col-span-2">Agent</div>
|
|
275
|
-
<div className="col-span-
|
|
276
|
-
<div className="col-span-2">Current Task</div>
|
|
298
|
+
<div className="col-span-2">Agent · Server</div>
|
|
299
|
+
<div className="col-span-4">Current Task</div>
|
|
277
300
|
<div className="col-span-1">Updated</div>
|
|
278
301
|
<div className="col-span-2 text-right">Actions</div>
|
|
279
302
|
</div>
|
|
@@ -298,9 +321,10 @@ export default function NodesPage() {
|
|
|
298
321
|
<span className="truncate text-sm font-medium text-white">{s.alias}</span>
|
|
299
322
|
</Link>
|
|
300
323
|
</div>
|
|
301
|
-
<div className="col-span-2 truncate text-xs text-gray-400"
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
<div className="col-span-2 truncate text-xs text-gray-400" title={s.server || ''}>
|
|
325
|
+
{s.agent || '--'}<span className="text-gray-700 mx-1.5">·</span>{shortServer(s.server)}
|
|
326
|
+
</div>
|
|
327
|
+
<div className="col-span-4 truncate text-xs text-gray-500" title={s.task || ''}>{s.task || '--'}</div>
|
|
304
328
|
<div className="col-span-1 text-xs text-gray-500">{timeAgo(s.last_seen_at || s.updated_at)}</div>
|
|
305
329
|
<div className="col-span-2 flex justify-end gap-1.5">
|
|
306
330
|
<button type="button" onClick={() => setChatAlias(s.alias)}
|
package/app/page.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
|
-
import { formatUptime } from './components/utils';
|
|
5
|
+
import { formatUptime, previewContent } from './components/utils';
|
|
6
6
|
import { StatsBar } from './components/StatsBar';
|
|
7
7
|
import { BroadcastBar } from './components/BroadcastBar';
|
|
8
8
|
import { TopoGraph } from './components/TopoGraph';
|
|
@@ -11,6 +11,7 @@ import { InboxPanel } from './components/InboxPanel';
|
|
|
11
11
|
import { LoadingSkeleton } from './components/LoadingSkeleton';
|
|
12
12
|
import { NodesEmptyState as EmptyState } from './components/EmptyState';
|
|
13
13
|
import { AliasAvatar } from './components/AliasAvatar';
|
|
14
|
+
import { STATUS_DOT_HEX, STATUS_CHIP_CLASS } from './lib/status';
|
|
14
15
|
import { UserBar } from './components/UserBar';
|
|
15
16
|
import { CommandCenter, useCommandCenter } from './components/CommandCenter';
|
|
16
17
|
import { DispatchPanel } from './components/DispatchPanel';
|
|
@@ -41,6 +42,7 @@ export default function Dashboard() {
|
|
|
41
42
|
const cmd = useCommandCenter();
|
|
42
43
|
const [showDispatch, setShowDispatch] = useState(false);
|
|
43
44
|
const [inbox, setInbox] = useState<InboxMessage[]>([]);
|
|
45
|
+
const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');
|
|
44
46
|
const { mutate } = useSWRConfig();
|
|
45
47
|
|
|
46
48
|
// SSE: instant revalidation on CommHub events
|
|
@@ -114,18 +116,27 @@ export default function Dashboard() {
|
|
|
114
116
|
return bWorking - aWorking;
|
|
115
117
|
});
|
|
116
118
|
|
|
119
|
+
/** Round 70: when the fleet is empty, the Overview reorders to lead with
|
|
120
|
+
* the "Spin up your first agent" CTA and hides the Quick Navigation /
|
|
121
|
+
* Nav rail / Broadcast bar that would otherwise occupy prime real estate
|
|
122
|
+
* with zeros and dead-end links. Computed once, used as a gate below. */
|
|
123
|
+
const fleetEmpty = sessions.length === 0 && !sessError;
|
|
124
|
+
|
|
117
125
|
return (
|
|
118
126
|
<div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
|
|
119
127
|
<div className="lg:ml-0 ml-10">
|
|
120
128
|
<StatsBar online={online} working={working} total={total} version={version} uptime={uptime} />
|
|
121
129
|
</div>
|
|
122
130
|
|
|
123
|
-
{/* Dispatch + User Bar
|
|
131
|
+
{/* Dispatch + User Bar — Dispatch hidden when fleet empty (nothing to
|
|
132
|
+
dispatch to); UserBar still useful (account/sign-out menu). */}
|
|
124
133
|
<div className="flex items-center gap-3 mb-4">
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
{!fleetEmpty && (
|
|
135
|
+
<button onClick={() => setShowDispatch(true)}
|
|
136
|
+
className="px-4 py-2 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white text-sm font-medium rounded-xl shadow-lg shadow-cyan-500/10 transition-all active:scale-95 shrink-0">
|
|
137
|
+
⚡ Dispatch
|
|
138
|
+
</button>
|
|
139
|
+
)}
|
|
129
140
|
<div className="flex-1"><UserBar /></div>
|
|
130
141
|
</div>
|
|
131
142
|
|
|
@@ -168,18 +179,15 @@ export default function Dashboard() {
|
|
|
168
179
|
<Link href="/tasks" prefetch={false} className="text-xs text-cyan-400 hover:text-cyan-300">View all →</Link>
|
|
169
180
|
</div>
|
|
170
181
|
<div className="flex flex-wrap gap-2">
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
].filter(({ key }) => taskStats[key])
|
|
181
|
-
.map(({ key, color }) => (
|
|
182
|
-
<Link key={key} href={`/tasks?status=${key}`} prefetch={false} className={`px-2.5 py-1 rounded-md border text-xs ${color} hover:opacity-80 transition-opacity`}>
|
|
182
|
+
{/* Round 69: order is "hot first" — active flow before terminal
|
|
183
|
+
states — intentionally different from the lifecycle order on
|
|
184
|
+
/tasks. Colors come from shared STATUS_CHIP_CLASS so a
|
|
185
|
+
palette tweak in app/lib/status.ts updates here too.
|
|
186
|
+
'created' added in r69 (was missing — enum-coverage bug). */}
|
|
187
|
+
{(['running', 'delivered', 'acked', 'replied', 'created', 'failed', 'cancelled', 'expired', 'closed'] as const)
|
|
188
|
+
.filter((key) => taskStats[key])
|
|
189
|
+
.map((key) => (
|
|
190
|
+
<Link key={key} href={`/tasks?status=${key}`} prefetch={false} className={`px-2.5 py-1 rounded-md border text-xs ${STATUS_CHIP_CLASS[key]} hover:opacity-80 transition-opacity`}>
|
|
183
191
|
{key}: {taskStats[key]}
|
|
184
192
|
</Link>
|
|
185
193
|
))}
|
|
@@ -191,7 +199,11 @@ export default function Dashboard() {
|
|
|
191
199
|
(1) Top: live stat cards (carry data, drill-in on click)
|
|
192
200
|
(2) Bottom: pure nav rail (no number, icon + label)
|
|
193
201
|
Round 24 — wrap both in a labelled block so the rhythm reads as
|
|
194
|
-
"here are the main jumps" instead of two disconnected strips.
|
|
202
|
+
"here are the main jumps" instead of two disconnected strips.
|
|
203
|
+
Round 70 — entire Quick Nav + Nav rail + Broadcast + Recent
|
|
204
|
+
Activity block is hidden when the fleet is empty so the
|
|
205
|
+
onboarding CTA gets the page above the fold. */}
|
|
206
|
+
{!fleetEmpty && <>
|
|
195
207
|
<div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 mb-2">Quick navigation</div>
|
|
196
208
|
<section className="mb-3 grid grid-cols-3 gap-2 sm:gap-3">
|
|
197
209
|
{(() => {
|
|
@@ -312,23 +324,25 @@ export default function Dashboard() {
|
|
|
312
324
|
<div className="space-y-2 max-h-40 overflow-y-auto">
|
|
313
325
|
{tasks.slice(0, 5).map((t: { task_id: string; from_name: string; to_name: string; status: string; content: string; created_at: string }) => (
|
|
314
326
|
<div key={t.task_id} className="flex items-center gap-2 text-xs">
|
|
315
|
-
<span
|
|
316
|
-
|
|
317
|
-
|
|
327
|
+
<span
|
|
328
|
+
className="w-1.5 h-1.5 rounded-full shrink-0"
|
|
329
|
+
style={{ backgroundColor: STATUS_DOT_HEX[t.status] || '#6b7280' }}
|
|
330
|
+
/>
|
|
318
331
|
{t.from_name && <AliasAvatar alias={t.from_name} size={14} />}
|
|
319
332
|
<span className="text-gray-300 shrink-0 max-w-[20%] truncate">{t.from_name || '?'}</span>
|
|
320
333
|
<span className="text-gray-600">→</span>
|
|
321
334
|
{t.to_name && <AliasAvatar alias={t.to_name} size={14} />}
|
|
322
335
|
<span className="text-gray-300 shrink-0 max-w-[20%] truncate">{t.to_name || '?'}</span>
|
|
323
|
-
<span className="text-gray-500 truncate flex-1">{t.content
|
|
336
|
+
<span className="text-gray-500 truncate flex-1" title={t.content || ''}>{previewContent(t.content).slice(0, 60)}</span>
|
|
324
337
|
<span className={`shrink-0 px-1.5 py-0.5 rounded text-[10px] border ${
|
|
325
|
-
t.status
|
|
338
|
+
STATUS_CHIP_CLASS[t.status] || 'text-gray-500 border-gray-700/30'
|
|
326
339
|
}`}>{t.status}</span>
|
|
327
340
|
</div>
|
|
328
341
|
))}
|
|
329
342
|
</div>
|
|
330
343
|
</section>
|
|
331
344
|
)}
|
|
345
|
+
</>}
|
|
332
346
|
|
|
333
347
|
{sessError && (
|
|
334
348
|
<div className="bg-red-900/20 border border-red-800/40 text-red-300 px-4 py-3 rounded-lg mb-6 text-sm flex items-center justify-between" role="alert">
|
|
@@ -358,14 +372,71 @@ export default function Dashboard() {
|
|
|
358
372
|
{showTopo && sessions.length > 0 && <TopoGraph sessions={sessions} sseSessions={sseSessions} />}
|
|
359
373
|
|
|
360
374
|
{sessions.length === 0 && !sessError ? (
|
|
361
|
-
<EmptyState
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
375
|
+
<EmptyState
|
|
376
|
+
hint={sessHint}
|
|
377
|
+
taskHistoryCount={Object.values(taskStats).reduce((a, b) => a + b, 0)}
|
|
378
|
+
/>
|
|
379
|
+
) : (() => {
|
|
380
|
+
const counts = {
|
|
381
|
+
all: sortedSessions.length,
|
|
382
|
+
working: sortedSessions.filter(s => isOnline(s) && s.status === 'working').length,
|
|
383
|
+
idle: sortedSessions.filter(s => isOnline(s) && s.status !== 'working').length,
|
|
384
|
+
offline: sortedSessions.filter(s => !isOnline(s)).length,
|
|
385
|
+
};
|
|
386
|
+
const filtered = sortedSessions.filter(s => {
|
|
387
|
+
if (agentFilter === 'all') return true;
|
|
388
|
+
if (agentFilter === 'offline') return !isOnline(s);
|
|
389
|
+
if (agentFilter === 'working') return isOnline(s) && s.status === 'working';
|
|
390
|
+
if (agentFilter === 'idle') return isOnline(s) && s.status !== 'working';
|
|
391
|
+
return true;
|
|
392
|
+
});
|
|
393
|
+
// Filter chip color keyed to status (round 34): working=green, idle=cyan,
|
|
394
|
+
// offline=gray, all=neutral. Inline hex dots avoid Tailwind v4 purge.
|
|
395
|
+
const chips: { key: typeof agentFilter; label: string; dot?: string }[] = [
|
|
396
|
+
{ key: 'all', label: 'All' },
|
|
397
|
+
{ key: 'working', label: 'Working', dot: '#4ade80' },
|
|
398
|
+
{ key: 'idle', label: 'Idle', dot: '#22d3ee' },
|
|
399
|
+
{ key: 'offline', label: 'Offline', dot: '#6b7280' },
|
|
400
|
+
];
|
|
401
|
+
return (
|
|
402
|
+
<>
|
|
403
|
+
<div className="mb-3 flex flex-wrap items-center gap-1">
|
|
404
|
+
{chips.map(c => (
|
|
405
|
+
<button
|
|
406
|
+
key={c.key}
|
|
407
|
+
type="button"
|
|
408
|
+
onClick={() => setAgentFilter(c.key)}
|
|
409
|
+
disabled={counts[c.key] === 0 && c.key !== 'all'}
|
|
410
|
+
className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed ${
|
|
411
|
+
agentFilter === c.key
|
|
412
|
+
? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'
|
|
413
|
+
: 'text-gray-500 hover:text-gray-200 hover:bg-[#1a1a2a] border border-transparent'
|
|
414
|
+
}`}
|
|
415
|
+
>
|
|
416
|
+
{c.dot && <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full" style={{ backgroundColor: c.dot }} />}
|
|
417
|
+
<span>{c.label}</span>
|
|
418
|
+
<span className={`text-[10px] tabular-nums ${agentFilter === c.key ? 'text-cyan-400' : 'text-gray-600'}`}>{counts[c.key]}</span>
|
|
419
|
+
</button>
|
|
420
|
+
))}
|
|
421
|
+
</div>
|
|
422
|
+
{/* Round 48: previous breakpoints had `lg:grid-cols-2 xl:grid-cols-3`
|
|
423
|
+
which kept lg (1024-1279px) at only 2 columns even though
|
|
424
|
+
each AgentCard is fine ≥260px wide. With the sidebar (208px),
|
|
425
|
+
main area at lg is ~816px so 3 cols at ~272px each fits.
|
|
426
|
+
xl breakpoint auto-inherits lg=3 cols; 2xl bumps to 4. */}
|
|
427
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4">
|
|
428
|
+
{filtered.map(s => (
|
|
429
|
+
<AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />
|
|
430
|
+
))}
|
|
431
|
+
</div>
|
|
432
|
+
{filtered.length === 0 && (
|
|
433
|
+
<div className="mt-4 text-center text-xs text-gray-600">
|
|
434
|
+
No agents match "{agentFilter}" — <button onClick={() => setAgentFilter('all')} className="underline hover:text-gray-400">Show all</button>
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
</>
|
|
438
|
+
);
|
|
439
|
+
})()}
|
|
369
440
|
|
|
370
441
|
<InboxPanel messages={inbox} />
|
|
371
442
|
|