@sleep2agi/agent-network-dashboard 0.5.3-preview.265 → 0.5.3-preview.267

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.
Files changed (234) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +35 -35
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +29 -0
  7. package/.next/routes-manifest.json +12 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +13 -13
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +15 -15
  27. package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/upload/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/upload/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/upload/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/upload/route.js +7 -0
  37. package/.next/server/app/api/hub/upload/route.js.map +5 -0
  38. package/.next/server/app/api/hub/upload/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/upload/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +15 -15
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +15 -15
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +15 -15
  50. package/.next/server/app/login.segments/_full.segment.rsc +15 -15
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +15 -15
  59. package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/manifest.webmanifest/route/app-paths-manifest.json +3 -0
  66. package/.next/server/app/manifest.webmanifest/route/build-manifest.json +9 -0
  67. package/.next/server/app/manifest.webmanifest/route/server-reference-manifest.json +4 -0
  68. package/.next/server/app/manifest.webmanifest/route.js +8 -0
  69. package/.next/server/app/manifest.webmanifest/route.js.map +5 -0
  70. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -0
  71. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +3 -0
  72. package/.next/server/app/manifest.webmanifest.body +1 -0
  73. package/.next/server/app/manifest.webmanifest.meta +1 -0
  74. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/messages.html +2 -2
  76. package/.next/server/app/messages.rsc +15 -15
  77. package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
  78. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  83. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/node.html +2 -2
  85. package/.next/server/app/node.rsc +15 -15
  86. package/.next/server/app/node.segments/_full.segment.rsc +15 -15
  87. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  92. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/nodes.html +2 -2
  94. package/.next/server/app/nodes.rsc +15 -15
  95. package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
  96. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  97. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  98. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  99. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  100. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  101. package/.next/server/app/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/server-logs.html +2 -2
  104. package/.next/server/app/server-logs.rsc +15 -15
  105. package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
  106. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  112. package/.next/server/app/settings/networks.html +2 -2
  113. package/.next/server/app/settings/networks.rsc +15 -15
  114. package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
  115. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  116. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  117. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  118. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  119. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  120. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  121. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/settings/tokens.html +2 -2
  124. package/.next/server/app/settings/tokens.rsc +15 -15
  125. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
  126. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  131. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  132. package/.next/server/app/settings.html +2 -2
  133. package/.next/server/app/settings.rsc +15 -15
  134. package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
  135. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  136. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  137. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  138. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  139. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  140. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  141. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  142. package/.next/server/app/tasks.html +2 -2
  143. package/.next/server/app/tasks.rsc +15 -15
  144. package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
  145. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  146. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  147. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  148. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  149. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  150. package/.next/server/app-paths-manifest.json +2 -0
  151. package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js +18 -0
  152. package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js.map +1 -0
  153. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_upload_route_actions_03_eqnf.js +3 -0
  154. package/.next/server/chunks/0ykm__next-internal_server_app_manifest_webmanifest_route_actions_0m16rb8.js +3 -0
  155. package/.next/server/chunks/0ykm__next-internal_server_app_manifest_webmanifest_route_actions_0m16rb8.js.map +1 -0
  156. package/.next/server/chunks/{[externals]__11vad82._.js → [externals]__036g1.i._.js} +2 -2
  157. package/.next/server/chunks/[externals]__036g1.i._.js.map +1 -0
  158. package/.next/server/chunks/[root-of-the-server]__0-8_f0o._.js +3 -0
  159. package/.next/server/chunks/[root-of-the-server]__0-8_f0o._.js.map +1 -0
  160. package/.next/server/chunks/{[root-of-the-server]__08uyvdq._.js → [root-of-the-server]__0jndzts._.js} +2 -2
  161. package/.next/server/chunks/{[root-of-the-server]__08uyvdq._.js.map → [root-of-the-server]__0jndzts._.js.map} +1 -1
  162. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js +1 -1
  163. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js.map +1 -1
  164. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js +1 -1
  165. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js.map +1 -1
  166. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js +1 -1
  167. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js.map +1 -1
  168. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js +1 -1
  169. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js.map +1 -1
  170. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js +1 -1
  171. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js.map +1 -1
  172. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js +1 -1
  173. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js.map +1 -1
  174. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js +1 -1
  175. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js.map +1 -1
  176. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js +1 -1
  177. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js.map +1 -1
  178. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js +1 -1
  179. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js.map +1 -1
  180. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js +1 -1
  181. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js.map +1 -1
  182. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js +1 -1
  183. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js.map +1 -1
  184. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js +1 -1
  185. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js.map +1 -1
  186. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js +1 -1
  187. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js.map +1 -1
  188. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js +1 -1
  189. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js.map +1 -1
  190. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js +1 -1
  191. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js.map +1 -1
  192. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +4 -1
  193. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -1
  194. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
  195. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
  196. package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js +1 -1
  197. package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js.map +1 -1
  198. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  199. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  200. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  201. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  202. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  203. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  204. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  205. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  206. package/.next/server/middleware-build-manifest.js +3 -3
  207. package/.next/server/middleware.js +2 -2
  208. package/.next/server/middleware.js.nft.json +1 -1
  209. package/.next/server/pages/404.html +2 -2
  210. package/.next/server/pages/500.html +1 -1
  211. package/.next/static/chunks/{0hgizy171xukz.js → 07to0dy21pil-.js} +1 -1
  212. package/.next/static/chunks/{07o87nb~cwjcs.js → 089t1exs6apb8.js} +3 -3
  213. package/.next/static/chunks/{0tl4r8_~2_.r2.js → 09rzd1.kojbsa.js} +1 -1
  214. package/.next/static/chunks/0ae.az.vfm-il.css +2 -0
  215. package/.next/static/chunks/{07~8sx3_5uiox.js → 0ir6pphfe2yvm.js} +4 -1
  216. package/.next/static/chunks/{0v4-5tng.uh.7.js → 0k-c1chkvf78s.js} +2 -2
  217. package/.next/static/chunks/{0vx~f61uxwjb8.js → 0uqplti.5qwnv.js} +1 -1
  218. package/.next/trace +2 -2
  219. package/.next/trace-build +1 -1
  220. package/.next/types/routes.d.ts +2 -1
  221. package/.next/types/validator.ts +9 -0
  222. package/app/api/hub/upload/route.ts +83 -0
  223. package/app/components/AppShell.tsx +3 -1
  224. package/app/components/MobileNav.tsx +45 -0
  225. package/app/components/TaskChatPanel.tsx +82 -5
  226. package/app/components/TopoGraph.tsx +66 -24
  227. package/app/layout.tsx +6 -0
  228. package/app/manifest.ts +23 -0
  229. package/package.json +2 -2
  230. package/.next/static/chunks/16-7qr7qx9zrz.css +0 -2
  231. /package/.next/server/chunks/{[externals]__11vad82._.js.map → 0ykm__next-internal_server_app_api_hub_upload_route_actions_03_eqnf.js.map} +0 -0
  232. /package/.next/static/{xShHwZyMG8wh9mMzBFNLC → OCWKpyGElLOWutcoW5Ya9}/_buildManifest.js +0 -0
  233. /package/.next/static/{xShHwZyMG8wh9mMzBFNLC → OCWKpyGElLOWutcoW5Ya9}/_clientMiddlewareManifest.js +0 -0
  234. /package/.next/static/{xShHwZyMG8wh9mMzBFNLC → OCWKpyGElLOWutcoW5Ya9}/_ssgManifest.js +0 -0
@@ -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
+ }
@@ -123,6 +123,7 @@ interface TaskChatPanelProps {
123
123
  export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskChatPanelProps) {
124
124
  const [messages, setMessages] = useState<ChatTask[]>([]);
125
125
  const [input, setInput] = useState('');
126
+ const [attachedFiles, setAttachedFiles] = useState<File[]>([]);
126
127
  const [priority, setPriority] = useState('normal');
127
128
  const [sending, setSending] = useState(false);
128
129
  const [pollingIds, setPollingIds] = useState<Set<string>>(new Set());
@@ -228,8 +229,21 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
228
229
  return () => clearInterval(interval);
229
230
  }, [pollingIds]);
230
231
 
232
+ const uploadAttachments = async () => {
233
+ const uploaded = [];
234
+ for (const file of attachedFiles) {
235
+ const form = new FormData();
236
+ form.append('file', file);
237
+ const res = await fetch('/api/hub/upload', { method: 'POST', body: form });
238
+ const data = await res.json().catch(() => ({ ok: false, error: `HTTP ${res.status}` }));
239
+ if (!res.ok || !data.ok) throw new Error(data.error || data.detail || `upload failed: ${file.name}`);
240
+ uploaded.push({ name: file.name, path: data.path, url: data.url });
241
+ }
242
+ return uploaded;
243
+ };
244
+
231
245
  const send = async () => {
232
- if (!input.trim() || sending) return;
246
+ if ((!input.trim() && attachedFiles.length === 0) || sending) return;
233
247
  let taskContent = input.trim();
234
248
  let sendTo = targetAlias;
235
249
 
@@ -245,9 +259,14 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
245
259
 
246
260
  setSending(true);
247
261
  setInput('');
262
+ setAttachedFiles([]);
248
263
  setShowMentions(false);
249
264
  if (textareaRef.current) textareaRef.current.style.height = 'auto';
250
265
 
266
+ const pendingAttachmentText = attachedFiles.length > 0
267
+ ? `\n\n[Dashboard 附件待上传]\n${attachedFiles.map(f => `- 图片: ${f.name}`).join('\n')}`
268
+ : '';
269
+
251
270
  // Optimistic echo first so the user always sees their message in history
252
271
  // even if the network call fails or the server returns ok:false. Earlier
253
272
  // version only added on data.ok=true → silent failures looked like
@@ -259,13 +278,27 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
259
278
  to_name: sendTo,
260
279
  status: 'created',
261
280
  priority,
262
- content: taskContent,
281
+ content: `${taskContent}${pendingAttachmentText}`.trim(),
263
282
  result: '',
264
283
  created_at: new Date().toISOString(),
265
284
  };
266
285
  setMessages(prev => [...prev, optimistic]);
267
286
 
268
287
  try {
288
+ const uploaded = await uploadAttachments();
289
+ if (uploaded.length > 0) {
290
+ const attachmentText = [
291
+ '',
292
+ '',
293
+ '[Dashboard 附件]',
294
+ ...uploaded.map(f => `- 图片: ${f.path}`),
295
+ ...uploaded.map(f => `- 预览: ${f.url}`),
296
+ ].join('\n');
297
+ taskContent = `${taskContent}${attachmentText}`.trim();
298
+ setMessages(prev => prev.map(m =>
299
+ m.task_id === localTaskId ? { ...m, content: taskContent } : m,
300
+ ));
301
+ }
269
302
  const res = await fetch('/api/hub/send', {
270
303
  method: 'POST',
271
304
  headers: { 'Content-Type': 'application/json' },
@@ -318,6 +351,20 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
318
351
  setShowMentions(false);
319
352
  };
320
353
 
354
+ const addImageFiles = (files: FileList | File[]) => {
355
+ const images = Array.from(files).filter(file => file.type.startsWith('image/'));
356
+ if (images.length === 0) return;
357
+ setAttachedFiles(prev => [...prev, ...images].slice(0, 6));
358
+ };
359
+
360
+ const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
361
+ const files = Array.from(e.clipboardData.files || []);
362
+ if (files.some(file => file.type.startsWith('image/'))) {
363
+ addImageFiles(files);
364
+ e.preventDefault();
365
+ }
366
+ };
367
+
321
368
  const insertMention = (nodeName: string) => {
322
369
  const lastAt = input.lastIndexOf('@');
323
370
  const before = input.slice(0, lastAt);
@@ -437,11 +484,24 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
437
484
  value={input}
438
485
  onChange={e => { handleInputChange(e.target.value); autoResize(e.target); }}
439
486
  onKeyDown={handleKeyDown}
487
+ onPaste={handlePaste}
440
488
  placeholder={`Message ${alias}...`}
441
489
  rows={1}
442
- className="w-full bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl px-4 py-2.5 pr-16 text-sm text-[var(--fg)] placeholder-[var(--fg-dim)] focus:border-cyan-500/40 focus:outline-none resize-none transition-colors"
490
+ 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
491
  />
444
492
  <div className="absolute right-2 bottom-1.5 flex items-center gap-1">
493
+ <label className="p-1 text-[var(--fg-dim)] hover:text-cyan-300 cursor-pointer rounded hover:bg-cyan-500/10" title="Attach image">
494
+ <input
495
+ type="file"
496
+ accept="image/*"
497
+ multiple
498
+ className="hidden"
499
+ onChange={e => { if (e.target.files) addImageFiles(e.target.files); e.currentTarget.value = ''; }}
500
+ />
501
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
502
+ <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" />
503
+ </svg>
504
+ </label>
445
505
  <select value={priority} onChange={e => setPriority(e.target.value)}
446
506
  className="bg-transparent text-[9px] text-[var(--fg-dim)] focus:outline-none cursor-pointer">
447
507
  <option value="normal">N</option>
@@ -450,7 +510,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
450
510
  </select>
451
511
  </div>
452
512
  </div>
453
- <button onClick={send} disabled={sending || !input.trim()}
513
+ <button onClick={send} disabled={sending || (!input.trim() && attachedFiles.length === 0)}
454
514
  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
515
  {sending ? (
456
516
  <div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
@@ -461,9 +521,26 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
461
521
  )}
462
522
  </button>
463
523
  </div>
524
+ {attachedFiles.length > 0 && (
525
+ <div className="mt-2 flex flex-wrap gap-1.5">
526
+ {attachedFiles.map((file, index) => (
527
+ <span key={`${file.name}-${index}`} className="inline-flex items-center gap-1.5 max-w-[160px] px-2 py-1 rounded-lg border border-cyan-500/20 bg-cyan-500/8 text-[10px] text-cyan-200">
528
+ <span className="truncate">{file.name}</span>
529
+ <button
530
+ type="button"
531
+ onClick={() => setAttachedFiles(prev => prev.filter((_, i) => i !== index))}
532
+ className="text-cyan-300 hover:text-white"
533
+ aria-label={`Remove ${file.name}`}
534
+ >
535
+ ×
536
+ </button>
537
+ </span>
538
+ ))}
539
+ </div>
540
+ )}
464
541
  <div className="flex justify-between text-[9px] text-[var(--fg-dim)] mt-1.5">
465
542
  <span>{input.includes('@') ? `Sending to: ${targetAlias}` : `Type @ to mention another node`}</span>
466
- <span>Enter to send · Shift+Enter for newline</span>
543
+ <span>Enter to send · paste image</span>
467
544
  </div>
468
545
  </div>
469
546
  </>
@@ -72,6 +72,19 @@ interface MessageFlow {
72
72
  created_at: string;
73
73
  }
74
74
 
75
+ interface TaskFlow {
76
+ from_name?: string | null;
77
+ from_alias?: string | null;
78
+ to_name?: string | null;
79
+ to_alias?: string | null;
80
+ content?: string | null;
81
+ status?: string | null;
82
+ created_at?: string | null;
83
+ delivered_at?: string | null;
84
+ started_at?: string | null;
85
+ completed_at?: string | null;
86
+ }
87
+
75
88
  interface TopoGraphProps {
76
89
  sessions: Session[];
77
90
  sseSessions: Record<string, number>;
@@ -559,39 +572,63 @@ function computeGroups(sessions: { alias: string }[]): Record<string, string> {
559
572
  return keys;
560
573
  }
561
574
 
562
- function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point>) {
575
+ function buildFlowLinks(messages: MessageFlow[], tasks: TaskFlow[], positions: Record<string, Point>) {
563
576
  const links = new Map<string, FlowLink>();
564
577
 
565
- messages.forEach(message => {
566
- if (
567
- !positions[message.from_alias] ||
568
- !positions[message.to_alias] ||
569
- message.from_alias === message.to_alias
570
- ) {
578
+ const commandAlias = Object.keys(positions).find(a => a.includes('副指挥'))
579
+ ?? Object.keys(positions).find(a => a.includes('总指挥'))
580
+ ?? null;
581
+
582
+ const resolveAlias = (alias: string | null | undefined) => {
583
+ const clean = (alias || '').trim();
584
+ if (!clean) return null;
585
+ if (positions[clean]) return clean;
586
+ if ((clean === 'api' || clean === 'admin') && commandAlias) return commandAlias;
587
+ return clean;
588
+ };
589
+
590
+ const addLink = (fromRaw: string | null | undefined, toRaw: string | null | undefined, content: string, createdAt: string) => {
591
+ const from = resolveAlias(fromRaw);
592
+ const to = resolveAlias(toRaw);
593
+ if (!from || !to || !positions[from] || !positions[to] || from === to) {
571
594
  return;
572
595
  }
573
596
 
574
- const key = `${message.from_alias}->${message.to_alias}`;
597
+ const key = `${from}->${to}`;
575
598
  const current = links.get(key);
576
599
 
577
600
  // Keep the most-recent timestamp per pair so the render can fade
578
601
  // dormant edges (Round 10 freshness fade).
579
- const incoming = message.created_at || '';
602
+ const incoming = createdAt || '';
580
603
  const last_at = !current
581
604
  ? incoming
582
605
  : (incoming > current.last_at ? incoming : current.last_at);
583
606
 
584
607
  links.set(key, {
585
608
  key,
586
- from: message.from_alias,
587
- to: message.to_alias,
609
+ from,
610
+ to,
588
611
  count: (current?.count || 0) + 1,
589
- content: current?.content || message.content,
612
+ content: current?.content || content,
590
613
  last_at,
591
614
  });
615
+ };
616
+
617
+ messages.forEach(message => {
618
+ addLink(message.from_alias, message.to_alias, message.content, message.created_at);
619
+ });
620
+
621
+ tasks.forEach(task => {
622
+ const from = task.from_alias ?? task.from_name;
623
+ const to = task.to_alias ?? task.to_name;
624
+ const when = task.completed_at || task.started_at || task.delivered_at || task.created_at || '';
625
+ const label = task.content || (task.status ? `task:${task.status}` : 'task');
626
+ addLink(from, to, label, when);
592
627
  });
593
628
 
594
- return [...links.values()].slice(0, 18);
629
+ return [...links.values()]
630
+ .sort((a, b) => (b.last_at || '').localeCompare(a.last_at || ''))
631
+ .slice(0, 18);
595
632
  }
596
633
 
597
634
  export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProps) {
@@ -614,6 +651,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
614
651
  // wants to leave: "show me the rest of the flows" → /messages.
615
652
  const router = useRouter();
616
653
  const [messages, setMessages] = useState<MessageFlow[]>([]);
654
+ const [tasks, setTasks] = useState<TaskFlow[]>([]);
617
655
  // Issue #87: ring | grid layout toggle. Ring is the tiered-radial default;
618
656
  // grid arranges nodes in an N×M grid (better for 30+ nodes). Persisted to
619
657
  // localStorage like the zoom/pan view state. Declared above nodePositions
@@ -718,20 +756,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
718
756
  };
719
757
 
720
758
  useEffect(() => {
721
- const fetchMessages = async () => {
759
+ const fetchFlows = async () => {
722
760
  try {
723
- const res = await fetch('/api/hub/messages?limit=50');
724
- if (res.status === 401) {
761
+ const [messageRes, taskRes] = await Promise.all([
762
+ fetch('/api/hub/messages?limit=50'),
763
+ fetch('/api/hub/tasks?limit=100'),
764
+ ]);
765
+ if (messageRes.status === 401 || taskRes.status === 401) {
725
766
  window.location.assign('/login');
726
767
  return;
727
768
  }
728
769
 
729
- const data = await res.json();
730
- setMessages(data.messages || []);
770
+ const [messageData, taskData] = await Promise.all([messageRes.json(), taskRes.json()]);
771
+ setMessages(messageData.messages || []);
772
+ setTasks(taskData.tasks || []);
731
773
  } catch {}
732
774
  };
733
- fetchMessages();
734
- const interval = setInterval(fetchMessages, 5000);
775
+ fetchFlows();
776
+ const interval = setInterval(fetchFlows, 5000);
735
777
  return () => clearInterval(interval);
736
778
  }, []);
737
779
 
@@ -880,7 +922,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
880
922
  });
881
923
  }
882
924
 
883
- const links = buildFlowLinks(messages, positions);
925
+ const links = buildFlowLinks(messages, tasks, positions);
884
926
  const active = new Set<string>();
885
927
  links.forEach(link => { active.add(link.from); active.add(link.to); });
886
928
  // #111: one bounding box per multi-member group (Vincent 4722). Each
@@ -1194,7 +1236,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1194
1236
  // tree/swimlane. iter4 (a6689b6) suppressed them as "杂线"; Vincent
1195
1237
  // now explicitly wants them back — same buildFlowLinks the ring/grid
1196
1238
  // layouts use, drawn against the swimlane node positions.
1197
- const links = buildFlowLinks(messages, positions);
1239
+ const links = buildFlowLinks(messages, tasks, positions);
1198
1240
  const active = new Set<string>();
1199
1241
  links.forEach(link => { active.add(link.from); active.add(link.to); });
1200
1242
 
@@ -1271,7 +1313,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1271
1313
  positions[s.alias] = polarPoint(index, Math.max(offline.length, 1), offlineR, offlineRotation);
1272
1314
  });
1273
1315
 
1274
- const links = buildFlowLinks(messages, positions);
1316
+ const links = buildFlowLinks(messages, tasks, positions);
1275
1317
  const active = new Set<string>();
1276
1318
  links.forEach(link => {
1277
1319
  active.add(link.from);
@@ -1296,7 +1338,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1296
1338
  treeConnectors: { lines: [], synthLabels: [], synthRoot: false } as TreeLayout,
1297
1339
  treeContentBox: null as { x: number; y: number; w: number; h: number } | null,
1298
1340
  };
1299
- }, [messages, sessions, sseSessions, layout, nodeScale]);
1341
+ }, [messages, tasks, sessions, sseSessions, layout, nodeScale]);
1300
1342
 
1301
1343
  const workingCount = onlineNodes.filter(s => s.status === 'working').length;
1302
1344
  // Round 744 / Loop: legend header node-count. The legend panel's header
package/app/layout.tsx CHANGED
@@ -13,6 +13,12 @@ const geistMono = Geist_Mono({
13
13
  export const metadata: Metadata = {
14
14
  title: "Agent Network Dashboard",
15
15
  description: "Real-time monitoring dashboard for Agent Network nodes via CommHub",
16
+ manifest: "/manifest.webmanifest",
17
+ appleWebApp: {
18
+ capable: true,
19
+ statusBarStyle: "black-translucent",
20
+ title: "Agent Network",
21
+ },
16
22
  icons: { icon: '/favicon.svg' },
17
23
  openGraph: {
18
24
  title: "Agent Network Dashboard",
@@ -0,0 +1,23 @@
1
+ import type { MetadataRoute } from 'next';
2
+
3
+ export default function manifest(): MetadataRoute.Manifest {
4
+ return {
5
+ name: 'Agent Network Dashboard',
6
+ short_name: 'AgentNet',
7
+ description: 'Mobile command surface for Agent Network and CommHub',
8
+ start_url: '/',
9
+ scope: '/',
10
+ display: 'standalone',
11
+ background_color: '#0a0a1a',
12
+ theme_color: '#0a0a1a',
13
+ orientation: 'portrait',
14
+ icons: [
15
+ {
16
+ src: '/favicon.svg',
17
+ sizes: 'any',
18
+ type: 'image/svg+xml',
19
+ purpose: 'any',
20
+ },
21
+ ],
22
+ };
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.3-preview.265",
3
+ "version": "0.5.3-preview.267",
4
4
  "description": "Agent Network Dashboard \u2014 Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -44,4 +44,4 @@
44
44
  "tailwindcss": "^4",
45
45
  "typescript": "^5"
46
46
  }
47
- }
47
+ }