@sleep2agi/agent-network-dashboard 0.5.3-preview.99 → 0.5.4
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +35 -35
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +29 -0
- package/.next/routes-manifest.json +12 -0
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +2 -2
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +13 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- 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 +2 -2
- 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/api/hub/upload/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/upload/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/upload/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/upload/route.js +7 -0
- package/.next/server/app/api/hub/upload/route.js.map +5 -0
- package/.next/server/app/api/hub/upload/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/upload/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +2 -2
- 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 +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- 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 +2 -2
- 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/manifest.webmanifest/route/app-paths-manifest.json +3 -0
- package/.next/server/app/manifest.webmanifest/route/build-manifest.json +9 -0
- package/.next/server/app/manifest.webmanifest/route/server-reference-manifest.json +4 -0
- package/.next/server/app/manifest.webmanifest/route.js +8 -0
- package/.next/server/app/manifest.webmanifest/route.js.map +5 -0
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -0
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +3 -0
- package/.next/server/app/manifest.webmanifest.body +1 -0
- package/.next/server/app/manifest.webmanifest.meta +1 -0
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- 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 +2 -2
- 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/app-paths-manifest.json +2 -0
- package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js +18 -0
- package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js.map +1 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_upload_route_actions_03_eqnf.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_manifest_webmanifest_route_actions_0m16rb8.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_manifest_webmanifest_route_actions_0m16rb8.js.map +1 -0
- package/.next/server/chunks/{[externals]__11vad82._.js → [externals]__036g1.i._.js} +2 -2
- package/.next/server/chunks/[externals]__036g1.i._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0-8_f0o._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-8_f0o._.js.map +1 -0
- package/.next/server/chunks/{[root-of-the-server]__08uyvdq._.js → [root-of-the-server]__0jndzts._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__08uyvdq._.js.map → [root-of-the-server]__0jndzts._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +4 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/middleware.js +2 -2
- package/.next/server/middleware.js.nft.json +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{07~8sx3_5uiox.js → 04wjx7vbxusw5.js} +4 -1
- package/.next/static/chunks/089t1exs6apb8.js +4 -0
- package/.next/static/chunks/{084e1mr6acfta.js → 09e8kxo30n5cf.js} +1 -1
- package/.next/static/chunks/{0v4-5tng.uh.7.js → 0k-c1chkvf78s.js} +2 -2
- package/.next/static/chunks/0v2~nlpk-cx6v.css +2 -0
- package/.next/static/chunks/0xsye-9kffdi0.js +1 -0
- package/.next/static/chunks/0~rx_~akeylmq.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/app/api/hub/send/route.ts +11 -2
- package/app/api/hub/servers/route.ts +3 -29
- package/app/api/hub/upload/route.ts +83 -0
- package/app/components/AppShell.tsx +3 -1
- package/app/components/MobileNav.tsx +45 -0
- package/app/components/TaskChatPanel.tsx +130 -6
- package/app/components/TopoGraph.tsx +4591 -216
- package/app/globals.css +839 -0
- package/app/layout.tsx +6 -0
- package/app/lib/serverDedupe.ts +122 -0
- package/app/lib/vendorIdentity.ts +74 -56
- package/app/manifest.ts +23 -0
- package/package.json +4 -4
- package/public/vendors/claude.svg +7 -8
- package/public/vendors/minimax.svg +8 -9
- package/public/vendors/openai.svg +8 -10
- package/scripts/p157-rc2-dedup-test.mjs +110 -0
- package/scripts/topo-a11y-titles-catalog-test.mjs +132 -0
- package/scripts/topo-active-links-chip-halo-layers-test.mjs +81 -0
- package/scripts/topo-alias-chat-brightness-test.mjs +79 -0
- package/scripts/topo-alias-text-halo-layers-test.mjs +98 -0
- package/scripts/topo-animation-temporal-modes-catalog-test.mjs +144 -0
- package/scripts/topo-avatar-chat-gate-test.mjs +77 -0
- package/scripts/topo-avatar-drop-shadow-test.mjs +86 -0
- package/scripts/topo-avatar-fallback-rotate-test.mjs +92 -0
- package/scripts/topo-avatar-rotate-test.mjs +85 -0
- package/scripts/topo-avatar-scale-test.mjs +89 -0
- package/scripts/topo-badge-chat-gate-test.mjs +74 -0
- package/scripts/topo-brand-drop-shadow-test.mjs +71 -0
- package/scripts/topo-brand-logo-halo-layers-test.mjs +85 -0
- package/scripts/topo-canvas-desc-a11y-test.mjs +87 -0
- package/scripts/topo-canvas-entrance-animation-test.mjs +117 -0
- package/scripts/topo-canvas-scan-beam-diagonal-test.mjs +148 -0
- package/scripts/topo-canvas-scan-beam-test.mjs +109 -0
- package/scripts/topo-canvas-scan-beam-vertical-test.mjs +123 -0
- package/scripts/topo-card-chat-brightness-test.mjs +86 -0
- package/scripts/topo-chat-ring-breath-curve-test.mjs +114 -0
- package/scripts/topo-chat-ring-brightness-test.mjs +80 -0
- package/scripts/topo-chat-ring-halo-layers-test.mjs +100 -0
- package/scripts/topo-chat-ring-r-breath-test.mjs +121 -0
- package/scripts/topo-chat-ring-status-halo-test.mjs +106 -0
- package/scripts/topo-chat-ring-sw-breath-test.mjs +123 -0
- package/scripts/topo-chip-pin-halo-test.mjs +85 -0
- package/scripts/topo-chrome-control-halo-layers-test.mjs +22 -0
- package/scripts/topo-chrome-fullscreen-breath-test.mjs +121 -0
- package/scripts/topo-chrome-layout-trailer-breath-test.mjs +86 -0
- package/scripts/topo-chrome-nodesize-trailer-breath-test.mjs +86 -0
- package/scripts/topo-chrome-reset-breath-test.mjs +115 -0
- package/scripts/topo-chrome-strip-entrance-animation-test.mjs +115 -0
- package/scripts/topo-chrome-wrapper-halo-test.mjs +83 -0
- package/scripts/topo-chrome-zoom-wrapper-breath-test.mjs +85 -0
- package/scripts/topo-click-ripple-glow-test.mjs +86 -0
- package/scripts/topo-click-ripple-halo-layers-test.mjs +79 -0
- package/scripts/topo-click-ripple-sw-test.mjs +110 -0
- package/scripts/topo-crescent-envelope-breath-test.mjs +103 -0
- package/scripts/topo-dense-alias-chat-halo-test.mjs +73 -0
- package/scripts/topo-dense-alias-halo-layers-test.mjs +80 -0
- package/scripts/topo-dual-axis-surfaces-catalog-test.mjs +94 -0
- package/scripts/topo-edge-badge-circle-brightness-test.mjs +82 -0
- package/scripts/topo-edge-badge-circle-hot-pulse-test.mjs +100 -0
- package/scripts/topo-edge-badge-digit-halo-layers-test.mjs +107 -0
- package/scripts/topo-edge-badge-endpoint-gate-test.mjs +94 -0
- package/scripts/topo-edge-badge-halo-layers-test.mjs +85 -0
- package/scripts/topo-edge-badge-hot-pulse-test.mjs +92 -0
- package/scripts/topo-edge-chat-gate-test.mjs +71 -0
- package/scripts/topo-edge-flow-rail-halo-layers-test.mjs +89 -0
- package/scripts/topo-edge-particle-halo-layers-test.mjs +91 -0
- package/scripts/topo-edge-pin-halo-test.mjs +99 -0
- package/scripts/topo-edge-visible-halo-layers-test.mjs +87 -0
- package/scripts/topo-endpoint-ring-flow-halo-test.mjs +107 -0
- package/scripts/topo-endpoint-ring-halo-layers-test.mjs +100 -0
- package/scripts/topo-filter-pill-halo-layers-test.mjs +27 -0
- package/scripts/topo-flow-arrow-brightness-test.mjs +82 -0
- package/scripts/topo-fullscreen-brightness-test.mjs +84 -0
- package/scripts/topo-group-box-brightness-test.mjs +84 -0
- package/scripts/topo-group-box-halo-layers-test.mjs +91 -0
- package/scripts/topo-group-chat-gate-test.mjs +77 -0
- package/scripts/topo-group-label-halo-layers-test.mjs +78 -0
- package/scripts/topo-group-tint-brightness-test.mjs +82 -0
- package/scripts/topo-h2-dual-axis-breath-test.mjs +92 -0
- package/scripts/topo-h2-triple-axis-breath-test.mjs +142 -0
- package/scripts/topo-halo-chat-gate-test.mjs +72 -0
- package/scripts/topo-hover-detail-halo-test.mjs +76 -0
- package/scripts/topo-hub-a11y-title-test.mjs +95 -0
- package/scripts/topo-hub-core-brightness-test.mjs +82 -0
- package/scripts/topo-hub-core-halo-layers-test.mjs +81 -0
- package/scripts/topo-hub-digit-halo-layers-test.mjs +76 -0
- package/scripts/topo-hub-halo-halo-layers-test.mjs +76 -0
- package/scripts/topo-hub-highlight-halo-layers-test.mjs +78 -0
- package/scripts/topo-hub-hover-ring-halo-layers-test.mjs +71 -0
- package/scripts/topo-hub-spoke-halo-layers-test.mjs +97 -0
- package/scripts/topo-hub-spoke-self-filter-test.mjs +119 -0
- package/scripts/topo-kicker-breath-test.mjs +100 -0
- package/scripts/topo-kicker-dual-axis-breath-test.mjs +81 -0
- package/scripts/topo-kicker-halo-layers-test.mjs +82 -0
- package/scripts/topo-kicker-triple-axis-breath-test.mjs +124 -0
- package/scripts/topo-label-card-brightness-test.mjs +81 -0
- package/scripts/topo-layout-toggle-brightness-test.mjs +94 -0
- package/scripts/topo-layout-toggle-halo-layers-test.mjs +95 -0
- package/scripts/topo-legend-count-brightness-test.mjs +80 -0
- package/scripts/topo-legend-count-halo-layers-test.mjs +79 -0
- package/scripts/topo-legend-panel-title-breath-test.mjs +86 -0
- package/scripts/topo-legend-pin-ring-brightness-test.mjs +82 -0
- package/scripts/topo-legend-pin-ring-halo-layers-test.mjs +71 -0
- package/scripts/topo-legend-row-count-brightness-test.mjs +85 -0
- package/scripts/topo-legend-tint-brightness-test.mjs +83 -0
- package/scripts/topo-legend-trio-halo-layers-test.mjs +22 -0
- package/scripts/topo-live-lanes.mjs +48 -0
- package/scripts/topo-minimap-container-halo-test.mjs +82 -0
- package/scripts/topo-minimap-dot-chat-gate-test.mjs +81 -0
- package/scripts/topo-minimap-viewport-brightness-test.mjs +84 -0
- package/scripts/topo-minimap-viewport-halo-layers-test.mjs +24 -0
- package/scripts/topo-more-footer-brightness-test.mjs +94 -0
- package/scripts/topo-node-avatar-halo-layers-test.mjs +25 -0
- package/scripts/topo-node-hover-ring-halo-layers-test.mjs +70 -0
- package/scripts/topo-node-label-card-halo-test.mjs +76 -0
- package/scripts/topo-nodesize-brightness-test.mjs +82 -0
- package/scripts/topo-nodesize-halo-layers-test.mjs +89 -0
- package/scripts/topo-overlap-test.mjs +22 -8
- package/scripts/topo-panel-count-halo-layers-test.mjs +91 -0
- package/scripts/topo-panel-rect-halo-test.mjs +90 -0
- package/scripts/topo-panel-titles-dual-axis-breath-test.mjs +94 -0
- package/scripts/topo-panel-titles-halo-layers-test.mjs +23 -0
- package/scripts/topo-panel-titles-triple-axis-breath-test.mjs +161 -0
- package/scripts/topo-pip-brightness-test.mjs +85 -0
- package/scripts/topo-pressure-bar-halo-layers-test.mjs +19 -0
- package/scripts/topo-r717-scan-beam-pair-pattern-test.mjs +100 -0
- package/scripts/topo-r717-triple-axis-pair-pattern-test.mjs +113 -0
- package/scripts/topo-r717-triple-axis-tier-pattern-test.mjs +117 -0
- package/scripts/topo-recent-count-brightness-test.mjs +84 -0
- package/scripts/topo-recent-more-halo-layers-test.mjs +90 -0
- package/scripts/topo-recent-panel-hot-pulse-test.mjs +105 -0
- package/scripts/topo-recent-panel-title-breath-test.mjs +91 -0
- package/scripts/topo-recent-pip-halo-layers-test.mjs +82 -0
- package/scripts/topo-recent-row-chat-gate-test.mjs +75 -0
- package/scripts/topo-recent-row-content-lift-test.mjs +140 -0
- package/scripts/topo-recent-row-text-halo-layers-test.mjs +67 -0
- package/scripts/topo-recent-tint-brightness-test.mjs +80 -0
- package/scripts/topo-recent-ts-brightness-test.mjs +86 -0
- package/scripts/topo-reset-brightness-test.mjs +83 -0
- package/scripts/topo-respiratory-axis-count-stats-test.mjs +154 -0
- package/scripts/topo-respiratory-patterns-catalog-test.mjs +112 -0
- package/scripts/topo-respiratory-rolodex-test.mjs +83 -0
- package/scripts/topo-respiratory-tiers-catalog-test.mjs +119 -0
- package/scripts/topo-respiratory-triple-axis-surfaces-catalog-test.mjs +127 -0
- package/scripts/topo-runtime-badge-brightness-test.mjs +78 -0
- package/scripts/topo-runtime-badge-halo-layers-test.mjs +87 -0
- package/scripts/topo-runtime-badge-rotate-test.mjs +85 -0
- package/scripts/topo-section-title-breath-test.mjs +83 -0
- package/scripts/topo-section-title-halo-layers-test.mjs +88 -0
- package/scripts/topo-spoke-chat-gate-test.mjs +72 -0
- package/scripts/topo-status-pin-pill-halo-layers-test.mjs +17 -0
- package/scripts/topo-status-ring-chat-gate-test.mjs +72 -0
- package/scripts/topo-status-ring-halo-layers-test.mjs +105 -0
- package/scripts/topo-status-ring-status-halo-test.mjs +110 -0
- package/scripts/topo-sub-text-chat-brightness-test.mjs +81 -0
- package/scripts/topo-svg-title-a11y-r731-test.mjs +93 -0
- package/scripts/topo-svg-title-a11y-test.mjs +88 -0
- package/scripts/topo-title-block-entrance-animation-test.mjs +127 -0
- package/scripts/topo-title-block-envelope-breath-test.mjs +87 -0
- package/scripts/topo-tree-diag.mjs +95 -0
- package/scripts/topo-vendor-chip-halo-layers-test.mjs +18 -0
- package/scripts/topo-vendor-chip-pin-halo-test.mjs +88 -0
- package/scripts/topo-vendor-count-suffix-halo-layers-test.mjs +79 -0
- package/scripts/topo-vendor-distribution-wrapper-halo-test.mjs +93 -0
- package/scripts/topo-vendor-letter-halo-layers-test.mjs +93 -0
- package/scripts/topo-watermark-dual-axis-breath-test.mjs +88 -0
- package/scripts/topo-watermark-envelope-breath-test.mjs +88 -0
- package/scripts/topo-watermark-triple-axis-breath-test.mjs +129 -0
- package/scripts/topo-working-online-chip-halo-layers-test.mjs +94 -0
- package/scripts/topo-zoom-buttons-brightness-test.mjs +94 -0
- package/scripts/topo-zoom-in-out-halo-layers-test.mjs +97 -0
- package/scripts/topo-zoom-level-breath-test.mjs +87 -0
- package/scripts/topo-zoom-level-brightness-test.mjs +83 -0
- package/scripts/topo-zoom-level-dual-axis-breath-test.mjs +83 -0
- package/scripts/topo-zoom-level-halo-layers-test.mjs +78 -0
- package/scripts/topo-zoom-level-triple-axis-breath-test.mjs +148 -0
- package/.next/static/chunks/09oxd2mm27b_n.js +0 -1
- package/.next/static/chunks/0ots8-uksxz9p.js +0 -4
- package/.next/static/chunks/0voxs92keb6tg.js +0 -1
- package/.next/static/chunks/13~aih56vx-cf.css +0 -2
- /package/.next/server/chunks/{[externals]__11vad82._.js.map → 0ykm__next-internal_server_app_api_hub_upload_route_actions_03_eqnf.js.map} +0 -0
- /package/.next/static/{Ha31LjrNJgJIEydZivRlp → xq88BMF7fMUHWh10yaKTn}/_buildManifest.js +0 -0
- /package/.next/static/{Ha31LjrNJgJIEydZivRlp → xq88BMF7fMUHWh10yaKTn}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{Ha31LjrNJgJIEydZivRlp → xq88BMF7fMUHWh10yaKTn}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requireDashboardAuth, getV3UserToken } from '@/app/lib/dashboard-auth';
|
|
2
|
+
import { dedupeByHostname, parseHubTime, type HubServerRow } from '@/app/lib/serverDedupe';
|
|
2
3
|
|
|
3
4
|
const HUB_URL = process.env.COMMHUB_URL || 'http://127.0.0.1:9200';
|
|
4
5
|
|
|
@@ -32,35 +33,8 @@ async function hubHeaders(): Promise<Record<string, string>> {
|
|
|
32
33
|
* field in 0.8.1-preview.5.
|
|
33
34
|
*/
|
|
34
35
|
|
|
35
|
-
interface HubServerRow {
|
|
36
|
-
hostname: string;
|
|
37
|
-
ip?: string | null;
|
|
38
|
-
agent_count?: number;
|
|
39
|
-
cpu_load_1min?: number | null;
|
|
40
|
-
cpu_cores?: number | null;
|
|
41
|
-
mem_used_gb?: number | null;
|
|
42
|
-
mem_total_gb?: number | null;
|
|
43
|
-
mem_avail_gb?: number | null;
|
|
44
|
-
disk_used_gb?: number | null;
|
|
45
|
-
disk_total_gb?: number | null;
|
|
46
|
-
cpu_history?: number[];
|
|
47
|
-
mem_history?: number[];
|
|
48
|
-
agents?: unknown[];
|
|
49
|
-
last_seen?: string | null;
|
|
50
|
-
status?: 'online' | 'offline';
|
|
51
|
-
note?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
function freshnessStatus(lastSeen: string | null | undefined): 'online' | 'offline' {
|
|
55
|
-
|
|
56
|
-
// commhub-server@0.8.1-preview.5 reports last_seen in "YYYY-MM-DD
|
|
57
|
-
// HH:MM:SS" (space-separated, no timezone). Treat as UTC so all
|
|
58
|
-
// clients agree regardless of their local zone. If a later hub
|
|
59
|
-
// upgrade switches to ISO 8601 with explicit timezone, Date.parse
|
|
60
|
-
// will accept either format and the +'Z' suffix is harmless.
|
|
61
|
-
const iso = lastSeen.includes('T') ? lastSeen : lastSeen.replace(' ', 'T');
|
|
62
|
-
const withTz = /[zZ]|[+-]\d{2}:?\d{2}$/.test(iso) ? iso : iso + 'Z';
|
|
63
|
-
const t = Date.parse(withTz);
|
|
37
|
+
const t = parseHubTime(lastSeen);
|
|
64
38
|
if (Number.isNaN(t)) return 'offline';
|
|
65
39
|
// 90s freshness window — wider than the agent-node 30s report
|
|
66
40
|
// cadence to absorb network latency + clock drift between client
|
|
@@ -117,7 +91,7 @@ export async function GET() {
|
|
|
117
91
|
? raw.servers
|
|
118
92
|
: [];
|
|
119
93
|
return Response.json({
|
|
120
|
-
servers: rows.map(normalizeServer),
|
|
94
|
+
servers: dedupeByHostname(rows).map(normalizeServer),
|
|
121
95
|
unavailable: raw?.unavailable === true,
|
|
122
96
|
});
|
|
123
97
|
} catch (e: unknown) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { requireDashboardAuth } from '@/app/lib/dashboard-auth';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const UPLOAD_DIR = '/tmp/agent-network-dashboard-uploads';
|
|
7
|
+
const MAX_BYTES = 12 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
function safeName(name: string) {
|
|
10
|
+
const ext = path.extname(name).toLowerCase().replace(/[^a-z0-9.]/g, '');
|
|
11
|
+
return `${Date.now()}-${randomUUID()}${ext || '.bin'}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isImage(type: string, filename: string) {
|
|
15
|
+
return type.startsWith('image/') || /\.(png|jpe?g|gif|webp)$/i.test(filename);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function POST(req: Request) {
|
|
19
|
+
const authFailure = await requireDashboardAuth();
|
|
20
|
+
if (authFailure) return authFailure;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const form = await req.formData();
|
|
24
|
+
const file = form.get('file');
|
|
25
|
+
if (!(file instanceof File)) {
|
|
26
|
+
return Response.json({ error: 'missing file' }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
if (!isImage(file.type, file.name)) {
|
|
29
|
+
return Response.json({ error: 'only image uploads are supported' }, { status: 400 });
|
|
30
|
+
}
|
|
31
|
+
if (file.size > MAX_BYTES) {
|
|
32
|
+
return Response.json({ error: 'image is too large', max_bytes: MAX_BYTES }, { status: 413 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await mkdir(UPLOAD_DIR, { recursive: true });
|
|
36
|
+
const filename = safeName(file.name);
|
|
37
|
+
const fullPath = path.join(UPLOAD_DIR, filename);
|
|
38
|
+
const bytes = Buffer.from(await file.arrayBuffer());
|
|
39
|
+
await writeFile(fullPath, bytes);
|
|
40
|
+
|
|
41
|
+
return Response.json({
|
|
42
|
+
ok: true,
|
|
43
|
+
filename,
|
|
44
|
+
path: fullPath,
|
|
45
|
+
url: `/api/hub/upload?file=${encodeURIComponent(filename)}`,
|
|
46
|
+
mime: file.type || 'application/octet-stream',
|
|
47
|
+
size: file.size,
|
|
48
|
+
});
|
|
49
|
+
} catch (e: unknown) {
|
|
50
|
+
return Response.json({ error: 'upload failed', detail: e instanceof Error ? e.message : String(e) }, { status: 500 });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function GET(req: Request) {
|
|
55
|
+
const authFailure = await requireDashboardAuth();
|
|
56
|
+
if (authFailure) return authFailure;
|
|
57
|
+
|
|
58
|
+
const url = new URL(req.url);
|
|
59
|
+
const filename = url.searchParams.get('file') || '';
|
|
60
|
+
if (!/^[0-9]+-[a-f0-9-]+\.[a-z0-9]+$/i.test(filename)) {
|
|
61
|
+
return Response.json({ error: 'invalid file' }, { status: 400 });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const fullPath = path.join(UPLOAD_DIR, filename);
|
|
66
|
+
const data = await readFile(fullPath);
|
|
67
|
+
const ext = path.extname(filename).toLowerCase();
|
|
68
|
+
const mime =
|
|
69
|
+
ext === '.png' ? 'image/png' :
|
|
70
|
+
ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :
|
|
71
|
+
ext === '.gif' ? 'image/gif' :
|
|
72
|
+
ext === '.webp' ? 'image/webp' :
|
|
73
|
+
'application/octet-stream';
|
|
74
|
+
return new Response(data, {
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': mime,
|
|
77
|
+
'Cache-Control': 'private, max-age=86400',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
return Response.json({ error: 'not found' }, { status: 404 });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -6,6 +6,7 @@ import { HealthBanner } from './HealthBanner';
|
|
|
6
6
|
import { CommandPalette } from './CommandPalette';
|
|
7
7
|
import { HelpOverlay } from './HelpOverlay';
|
|
8
8
|
import { ServersDrawer } from './ServersDrawer';
|
|
9
|
+
import { MobileNav } from './MobileNav';
|
|
9
10
|
|
|
10
11
|
const NO_SHELL_PATHS = ['/login'];
|
|
11
12
|
|
|
@@ -21,8 +22,9 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
|
|
21
22
|
<Sidebar />
|
|
22
23
|
<main className="flex-1 min-w-0 flex flex-col">
|
|
23
24
|
<HealthBanner />
|
|
24
|
-
<div className="flex-1 min-w-0">{children}</div>
|
|
25
|
+
<div className="flex-1 min-w-0 pb-20 lg:pb-0">{children}</div>
|
|
25
26
|
</main>
|
|
27
|
+
<MobileNav />
|
|
26
28
|
<CommandPalette />
|
|
27
29
|
<HelpOverlay />
|
|
28
30
|
<ServersDrawer />
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
|
|
6
|
+
const MOBILE_NAV_ITEMS = [
|
|
7
|
+
{ href: '/', label: 'Home', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
|
|
8
|
+
{ href: '/tasks', label: 'Tasks', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4' },
|
|
9
|
+
{ href: '/nodes', label: 'Agents', icon: 'M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01' },
|
|
10
|
+
{ href: '/messages', label: 'Chats', icon: 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z' },
|
|
11
|
+
{ href: '/logs', label: 'Status', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function MobileNav() {
|
|
15
|
+
const pathname = usePathname();
|
|
16
|
+
const isActive = (href: string) => href === '/' ? pathname === '/' : pathname.startsWith(href);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<nav className="fixed inset-x-0 bottom-0 z-40 border-t border-[#2a2a4a] bg-[#0d0d1a]/95 px-1 pb-[calc(env(safe-area-inset-bottom)+0.25rem)] pt-1.5 backdrop-blur lg:hidden">
|
|
20
|
+
<div className="mx-auto grid max-w-md grid-cols-5 gap-1">
|
|
21
|
+
{MOBILE_NAV_ITEMS.map(item => {
|
|
22
|
+
const active = isActive(item.href);
|
|
23
|
+
return (
|
|
24
|
+
<Link
|
|
25
|
+
key={item.href}
|
|
26
|
+
href={item.href}
|
|
27
|
+
prefetch={false}
|
|
28
|
+
aria-current={active ? 'page' : undefined}
|
|
29
|
+
className={`flex min-h-12 flex-col items-center justify-center gap-0.5 rounded-xl px-1 text-[10px] transition-colors ${
|
|
30
|
+
active
|
|
31
|
+
? 'bg-cyan-500/12 text-cyan-300'
|
|
32
|
+
: 'text-gray-500 active:bg-[#1a1a3a] active:text-gray-200'
|
|
33
|
+
}`}
|
|
34
|
+
>
|
|
35
|
+
<svg className="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6}>
|
|
36
|
+
<path strokeLinecap="round" strokeLinejoin="round" d={item.icon} />
|
|
37
|
+
</svg>
|
|
38
|
+
<span className="max-w-full truncate">{item.label}</span>
|
|
39
|
+
</Link>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
</div>
|
|
43
|
+
</nav>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -15,6 +15,57 @@ interface ChatTask {
|
|
|
15
15
|
created_at: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function extractAttachmentPreviews(text: string): string[] {
|
|
19
|
+
const urls: string[] = [];
|
|
20
|
+
for (const line of (text || '').split('\n')) {
|
|
21
|
+
const match = line.match(/^\s*-\s*预览:\s*(\S+)\s*$/);
|
|
22
|
+
if (match?.[1]) urls.push(match[1]);
|
|
23
|
+
}
|
|
24
|
+
return Array.from(new Set(urls));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AttachmentPreviews({ text }: { text: string }) {
|
|
28
|
+
const urls = extractAttachmentPreviews(text);
|
|
29
|
+
if (urls.length === 0) return null;
|
|
30
|
+
return (
|
|
31
|
+
<div className="mt-2 grid grid-cols-2 gap-2">
|
|
32
|
+
{urls.map((url) => (
|
|
33
|
+
<a key={url} href={url} target="_blank" rel="noreferrer" className="group block overflow-hidden rounded-lg border border-cyan-500/20 bg-black/20">
|
|
34
|
+
<img
|
|
35
|
+
src={url}
|
|
36
|
+
alt="Dashboard attachment preview"
|
|
37
|
+
className="h-28 w-full object-cover transition-transform duration-200 group-hover:scale-[1.02]"
|
|
38
|
+
loading="lazy"
|
|
39
|
+
/>
|
|
40
|
+
</a>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function LocalImagePreview({ file, onRemove }: { file: File; onRemove: () => void }) {
|
|
47
|
+
const [url, setUrl] = useState('');
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const objectUrl = URL.createObjectURL(file);
|
|
50
|
+
setUrl(objectUrl);
|
|
51
|
+
return () => URL.revokeObjectURL(objectUrl);
|
|
52
|
+
}, [file]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="relative h-16 w-16 overflow-hidden rounded-lg border border-cyan-500/25 bg-black/20">
|
|
56
|
+
{url && <img src={url} alt={file.name} className="h-full w-full object-cover" />}
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={onRemove}
|
|
60
|
+
className="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/70 text-xs text-white hover:bg-red-500"
|
|
61
|
+
aria-label={`Remove ${file.name}`}
|
|
62
|
+
>
|
|
63
|
+
×
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
18
69
|
const STATUS_STEPS = ['created', 'delivered', 'running', 'replied'];
|
|
19
70
|
const STATUS_COLORS: Record<string, string> = {
|
|
20
71
|
created: 'bg-gray-400', delivered: 'bg-blue-400', running: 'bg-green-400',
|
|
@@ -123,6 +174,7 @@ interface TaskChatPanelProps {
|
|
|
123
174
|
export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskChatPanelProps) {
|
|
124
175
|
const [messages, setMessages] = useState<ChatTask[]>([]);
|
|
125
176
|
const [input, setInput] = useState('');
|
|
177
|
+
const [attachedFiles, setAttachedFiles] = useState<File[]>([]);
|
|
126
178
|
const [priority, setPriority] = useState('normal');
|
|
127
179
|
const [sending, setSending] = useState(false);
|
|
128
180
|
const [pollingIds, setPollingIds] = useState<Set<string>>(new Set());
|
|
@@ -228,8 +280,21 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
228
280
|
return () => clearInterval(interval);
|
|
229
281
|
}, [pollingIds]);
|
|
230
282
|
|
|
283
|
+
const uploadAttachments = async () => {
|
|
284
|
+
const uploaded = [];
|
|
285
|
+
for (const file of attachedFiles) {
|
|
286
|
+
const form = new FormData();
|
|
287
|
+
form.append('file', file);
|
|
288
|
+
const res = await fetch('/api/hub/upload', { method: 'POST', body: form });
|
|
289
|
+
const data = await res.json().catch(() => ({ ok: false, error: `HTTP ${res.status}` }));
|
|
290
|
+
if (!res.ok || !data.ok) throw new Error(data.error || data.detail || `upload failed: ${file.name}`);
|
|
291
|
+
uploaded.push({ type: 'image', name: file.name, path: data.path, url: data.url, mime: data.mime, size: data.size });
|
|
292
|
+
}
|
|
293
|
+
return uploaded;
|
|
294
|
+
};
|
|
295
|
+
|
|
231
296
|
const send = async () => {
|
|
232
|
-
if (!input.trim() || sending) return;
|
|
297
|
+
if ((!input.trim() && attachedFiles.length === 0) || sending) return;
|
|
233
298
|
let taskContent = input.trim();
|
|
234
299
|
let sendTo = targetAlias;
|
|
235
300
|
|
|
@@ -245,9 +310,14 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
245
310
|
|
|
246
311
|
setSending(true);
|
|
247
312
|
setInput('');
|
|
313
|
+
setAttachedFiles([]);
|
|
248
314
|
setShowMentions(false);
|
|
249
315
|
if (textareaRef.current) textareaRef.current.style.height = 'auto';
|
|
250
316
|
|
|
317
|
+
const pendingAttachmentText = attachedFiles.length > 0
|
|
318
|
+
? `\n\n[Dashboard 附件待上传]\n${attachedFiles.map(f => `- 图片: ${f.name}`).join('\n')}`
|
|
319
|
+
: '';
|
|
320
|
+
|
|
251
321
|
// Optimistic echo first so the user always sees their message in history
|
|
252
322
|
// even if the network call fails or the server returns ok:false. Earlier
|
|
253
323
|
// version only added on data.ok=true → silent failures looked like
|
|
@@ -259,17 +329,31 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
259
329
|
to_name: sendTo,
|
|
260
330
|
status: 'created',
|
|
261
331
|
priority,
|
|
262
|
-
content: taskContent,
|
|
332
|
+
content: `${taskContent}${pendingAttachmentText}`.trim(),
|
|
263
333
|
result: '',
|
|
264
334
|
created_at: new Date().toISOString(),
|
|
265
335
|
};
|
|
266
336
|
setMessages(prev => [...prev, optimistic]);
|
|
267
337
|
|
|
268
338
|
try {
|
|
339
|
+
const uploaded = await uploadAttachments();
|
|
340
|
+
if (uploaded.length > 0) {
|
|
341
|
+
const attachmentText = [
|
|
342
|
+
'',
|
|
343
|
+
'',
|
|
344
|
+
'[Dashboard 附件]',
|
|
345
|
+
...uploaded.map(f => `- 图片: ${f.path}`),
|
|
346
|
+
...uploaded.map(f => `- 预览: ${f.url}`),
|
|
347
|
+
].join('\n');
|
|
348
|
+
taskContent = `${taskContent}${attachmentText}`.trim();
|
|
349
|
+
setMessages(prev => prev.map(m =>
|
|
350
|
+
m.task_id === localTaskId ? { ...m, content: taskContent } : m,
|
|
351
|
+
));
|
|
352
|
+
}
|
|
269
353
|
const res = await fetch('/api/hub/send', {
|
|
270
354
|
method: 'POST',
|
|
271
355
|
headers: { 'Content-Type': 'application/json' },
|
|
272
|
-
body: JSON.stringify({ alias: sendTo, task: taskContent, priority }),
|
|
356
|
+
body: JSON.stringify({ alias: sendTo, task: taskContent, priority, attachments: uploaded }),
|
|
273
357
|
});
|
|
274
358
|
const data = await res.json().catch(() => ({ ok: false, error: `HTTP ${res.status}` }));
|
|
275
359
|
if (data.ok && data.message_id) {
|
|
@@ -318,6 +402,20 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
318
402
|
setShowMentions(false);
|
|
319
403
|
};
|
|
320
404
|
|
|
405
|
+
const addImageFiles = (files: FileList | File[]) => {
|
|
406
|
+
const images = Array.from(files).filter(file => file.type.startsWith('image/'));
|
|
407
|
+
if (images.length === 0) return;
|
|
408
|
+
setAttachedFiles(prev => [...prev, ...images].slice(0, 6));
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
|
412
|
+
const files = Array.from(e.clipboardData.files || []);
|
|
413
|
+
if (files.some(file => file.type.startsWith('image/'))) {
|
|
414
|
+
addImageFiles(files);
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
321
419
|
const insertMention = (nodeName: string) => {
|
|
322
420
|
const lastAt = input.lastIndexOf('@');
|
|
323
421
|
const before = input.slice(0, lastAt);
|
|
@@ -372,6 +470,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
372
470
|
</div>
|
|
373
471
|
<div className="text-[13px] text-[var(--fg)]">
|
|
374
472
|
<MarkdownContent text={m.content} />
|
|
473
|
+
<AttachmentPreviews text={m.content} />
|
|
375
474
|
</div>
|
|
376
475
|
<div className="flex items-center justify-between mt-1.5 gap-3">
|
|
377
476
|
<StatusBar status={m.status} />
|
|
@@ -393,6 +492,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
393
492
|
</div>
|
|
394
493
|
<div className="text-[13px] text-[var(--fg)]">
|
|
395
494
|
<MarkdownContent text={m.result} />
|
|
495
|
+
<AttachmentPreviews text={m.result} />
|
|
396
496
|
</div>
|
|
397
497
|
</div>
|
|
398
498
|
</div>
|
|
@@ -437,11 +537,24 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
437
537
|
value={input}
|
|
438
538
|
onChange={e => { handleInputChange(e.target.value); autoResize(e.target); }}
|
|
439
539
|
onKeyDown={handleKeyDown}
|
|
540
|
+
onPaste={handlePaste}
|
|
440
541
|
placeholder={`Message ${alias}...`}
|
|
441
542
|
rows={1}
|
|
442
|
-
className="w-full bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl px-4 py-2.5 pr-
|
|
543
|
+
className="w-full bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl px-4 py-2.5 pr-24 text-sm text-[var(--fg)] placeholder-[var(--fg-dim)] focus:border-cyan-500/40 focus:outline-none resize-none transition-colors"
|
|
443
544
|
/>
|
|
444
545
|
<div className="absolute right-2 bottom-1.5 flex items-center gap-1">
|
|
546
|
+
<label className="p-1 text-[var(--fg-dim)] hover:text-cyan-300 cursor-pointer rounded hover:bg-cyan-500/10" title="Attach image">
|
|
547
|
+
<input
|
|
548
|
+
type="file"
|
|
549
|
+
accept="image/*"
|
|
550
|
+
multiple
|
|
551
|
+
className="hidden"
|
|
552
|
+
onChange={e => { if (e.target.files) addImageFiles(e.target.files); e.currentTarget.value = ''; }}
|
|
553
|
+
/>
|
|
554
|
+
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
555
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M16.5 6.5l-7.78 7.78a3 3 0 104.24 4.24l8.49-8.49a5 5 0 00-7.07-7.07L5.89 11.45a7 7 0 109.9 9.9l7.07-7.07" />
|
|
556
|
+
</svg>
|
|
557
|
+
</label>
|
|
445
558
|
<select value={priority} onChange={e => setPriority(e.target.value)}
|
|
446
559
|
className="bg-transparent text-[9px] text-[var(--fg-dim)] focus:outline-none cursor-pointer">
|
|
447
560
|
<option value="normal">N</option>
|
|
@@ -450,7 +563,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
450
563
|
</select>
|
|
451
564
|
</div>
|
|
452
565
|
</div>
|
|
453
|
-
<button onClick={send} disabled={sending || !input.trim()}
|
|
566
|
+
<button onClick={send} disabled={sending || (!input.trim() && attachedFiles.length === 0)}
|
|
454
567
|
className="p-2.5 bg-cyan-600 hover:bg-cyan-500 disabled:bg-[var(--border)] disabled:text-[var(--fg-dim)] text-[var(--fg)] rounded-xl transition-all shrink-0 active:scale-95">
|
|
455
568
|
{sending ? (
|
|
456
569
|
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
@@ -461,9 +574,20 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
461
574
|
)}
|
|
462
575
|
</button>
|
|
463
576
|
</div>
|
|
577
|
+
{attachedFiles.length > 0 && (
|
|
578
|
+
<div className="mt-2 flex flex-wrap gap-2">
|
|
579
|
+
{attachedFiles.map((file, index) => (
|
|
580
|
+
<LocalImagePreview
|
|
581
|
+
key={`${file.name}-${file.size}-${index}`}
|
|
582
|
+
file={file}
|
|
583
|
+
onRemove={() => setAttachedFiles(prev => prev.filter((_, i) => i !== index))}
|
|
584
|
+
/>
|
|
585
|
+
))}
|
|
586
|
+
</div>
|
|
587
|
+
)}
|
|
464
588
|
<div className="flex justify-between text-[9px] text-[var(--fg-dim)] mt-1.5">
|
|
465
589
|
<span>{input.includes('@') ? `Sending to: ${targetAlias}` : `Type @ to mention another node`}</span>
|
|
466
|
-
<span>Enter to send ·
|
|
590
|
+
<span>Enter to send · paste image</span>
|
|
467
591
|
</div>
|
|
468
592
|
</div>
|
|
469
593
|
</>
|