@sleep2agi/agent-network-dashboard 0.5.4 → 0.5.5-preview.1
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 +1 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +66 -65
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +24 -0
- package/.next/routes-manifest.json +6 -0
- 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.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +3 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +3 -3
- 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/index.html +3 -3
- 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.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 +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.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +3 -3
- 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/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 +3 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +3 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +3 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page.js.nft.json +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +3 -3
- 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/servers/page/app-paths-manifest.json +3 -0
- package/.next/server/app/servers/page/build-manifest.json +17 -0
- package/.next/server/app/servers/page/next-font-manifest.json +10 -0
- package/.next/server/app/servers/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/servers/page/server-reference-manifest.json +4 -0
- package/.next/server/app/servers/page.js +14 -0
- package/.next/server/app/servers/page.js.map +5 -0
- package/.next/server/app/servers/page.js.nft.json +1 -0
- package/.next/server/app/servers/page_client-reference-manifest.js +3 -0
- package/.next/server/app/servers.html +12 -0
- package/.next/server/app/servers.meta +15 -0
- package/.next/server/app/servers.rsc +24 -0
- package/.next/server/app/servers.segments/_full.segment.rsc +24 -0
- package/.next/server/app/servers.segments/_head.segment.rsc +6 -0
- package/.next/server/app/servers.segments/_index.segment.rsc +8 -0
- package/.next/server/app/servers.segments/_tree.segment.rsc +3 -0
- package/.next/server/app/servers.segments/servers/__PAGE__.segment.rsc +9 -0
- package/.next/server/app/servers.segments/servers.segment.rsc +5 -0
- 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 +3 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +3 -3
- 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 +3 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +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 +3 -3
- 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 +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_10sgwq_.js +4 -0
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_10sgwq_.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0qylf1f._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0qylf1f._.js.map +1 -0
- 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/[root-of-the-server]__11fu-5m._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__11fu-5m._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_servers_page_actions_0mc5jn..js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_servers_page_actions_0mc5jn..js.map +1 -0
- 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 +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_servers_page_tsx_0jib5qm._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_servers_page_tsx_0jib5qm._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.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 +3 -0
- package/.next/server/pages/404.html +3 -3
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{04wjx7vbxusw5.js → 0csnc6nlttr5s.js} +2 -2
- package/.next/static/chunks/{0k-c1chkvf78s.js → 0iwava1u7a3b4.js} +2 -2
- package/.next/static/chunks/0l4_5zb9iglew.css +2 -0
- package/.next/static/chunks/0lxkdd7i1ck0g.js +1 -0
- package/.next/static/chunks/{09e8kxo30n5cf.js → 0q_0ejb7xirob.js} +1 -1
- package/.next/static/chunks/0qwqp6ulyj3o6.js +1 -0
- package/.next/static/chunks/0wyjrc0bekhiz.js +1 -0
- package/.next/static/chunks/0~ykmap37nw9d.js +1 -0
- package/.next/static/chunks/13yktdzuatx3d.js +1 -0
- package/.next/static/chunks/176am8j.kv9.u.js +1 -0
- package/.next/static/chunks/{089t1exs6apb8.js → 17r9h6cx1w6q-.js} +1 -1
- 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/components/AppShell.tsx +2 -2
- package/app/components/CommandCenter.tsx +1 -1
- package/app/components/DispatchPanel.tsx +1 -1
- package/app/components/MobileNav.tsx +16 -3
- package/app/components/ServersDrawer.tsx +170 -158
- package/app/components/Sidebar.tsx +1 -0
- package/app/components/TaskChatPanel.tsx +81 -26
- package/app/components/TaskDrawer.tsx +1 -1
- package/app/layout.tsx +2 -2
- package/app/nodes/page.tsx +21 -151
- package/app/servers/page.tsx +30 -0
- package/package.json +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0p-95r_._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0p-95r_._.js.map +0 -1
- package/.next/static/chunks/01lmbsd37fybu.js +0 -1
- package/.next/static/chunks/03o.h6kvmw4l_.js +0 -1
- package/.next/static/chunks/0ku0fjqlm9mca.js +0 -1
- package/.next/static/chunks/0tvn2l1pc.h65.js +0 -1
- package/.next/static/chunks/0v2~nlpk-cx6v.css +0 -2
- package/.next/static/chunks/0xsye-9kffdi0.js +0 -1
- package/.next/static/chunks/0~rx_~akeylmq.js +0 -1
- /package/.next/static/{xq88BMF7fMUHWh10yaKTn → nB1Z7KvLgOywguHTiJ_My}/_buildManifest.js +0 -0
- /package/.next/static/{xq88BMF7fMUHWh10yaKTn → nB1Z7KvLgOywguHTiJ_My}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{xq88BMF7fMUHWh10yaKTn → nB1Z7KvLgOywguHTiJ_My}/_ssgManifest.js +0 -0
|
@@ -195,27 +195,27 @@ function AgentList({ agents }: { agents?: ServerAgent[] }) {
|
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
function useServers(enabled = true) {
|
|
199
|
+
const { data, error } = useSWR<ServersResponse>(
|
|
200
|
+
enabled ? '/api/hub/servers' : null,
|
|
201
|
+
fetcher,
|
|
202
|
+
{ refreshInterval: 5000, dedupingInterval: 3000 },
|
|
203
|
+
);
|
|
204
|
+
const servers = data?.servers ?? [];
|
|
205
|
+
const unavailable = data?.unavailable === true;
|
|
206
|
+
const onlineCount = servers.filter(s => s.status === 'online').length;
|
|
207
|
+
const loading = enabled && !data && !error;
|
|
208
|
+
return { data, error, servers, unavailable, onlineCount, loading };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function ServersPanel({ enabled = true, className = '' }: { enabled?: boolean; className?: string }) {
|
|
204
212
|
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
205
|
-
// Drawer state is per-user-machine — persist like the other dashboard
|
|
206
|
-
// sticky toggles (`anet-topo-layout`, `anet-topo-view`, etc.).
|
|
207
213
|
useEffect(() => {
|
|
208
|
-
try { if (localStorage.getItem('anet-servers-drawer') === '1') setOpen(true); } catch {}
|
|
209
214
|
try {
|
|
210
215
|
const raw = localStorage.getItem('anet-servers-drawer-expanded');
|
|
211
216
|
if (raw) setExpanded(new Set(JSON.parse(raw)));
|
|
212
217
|
} catch {}
|
|
213
218
|
}, []);
|
|
214
|
-
const toggle = () => setOpen(prev => {
|
|
215
|
-
const next = !prev;
|
|
216
|
-
try { localStorage.setItem('anet-servers-drawer', next ? '1' : '0'); } catch {}
|
|
217
|
-
return next;
|
|
218
|
-
});
|
|
219
219
|
const toggleExpanded = (hostname: string) => setExpanded(prev => {
|
|
220
220
|
const next = new Set(prev);
|
|
221
221
|
if (next.has(hostname)) next.delete(hostname); else next.add(hostname);
|
|
@@ -223,19 +223,165 @@ export function ServersDrawer() {
|
|
|
223
223
|
return next;
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
+
const { data, error, servers, unavailable, loading } = useServers(enabled);
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<div className={`min-h-0 overflow-y-auto px-2 py-2 space-y-2 ${className}`} data-servers-body>
|
|
230
|
+
{loading && (
|
|
231
|
+
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-4">
|
|
232
|
+
loading servers…
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
{error && !data && (
|
|
236
|
+
<div className="rounded-md border px-2.5 py-2 text-[10px] font-mono"
|
|
237
|
+
style={{ background: 'rgb(239 68 68 / 0.06)', borderColor: 'rgb(239 68 68 / 0.25)', color: '#ef4444' }}>
|
|
238
|
+
hub unreachable · retrying every 5s
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
{unavailable && (
|
|
242
|
+
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-3 leading-relaxed">
|
|
243
|
+
host telemetry not available<br/>
|
|
244
|
+
<span className="text-[var(--fg-dim)]">upgrade commhub-server ≥ 0.8.1-preview.2</span>
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
{!loading && !error && !unavailable && servers.length === 0 && (
|
|
248
|
+
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-4">
|
|
249
|
+
no servers reporting yet
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
{servers.map(s => {
|
|
253
|
+
const offline = s.status === 'offline';
|
|
254
|
+
const cpuPct = s.cpu_load_1min != null && s.cpu_cores > 0 ? (s.cpu_load_1min / s.cpu_cores) * 100 : null;
|
|
255
|
+
const memPct = s.mem_used_gb != null && s.mem_total_gb != null && s.mem_total_gb > 0 ? (s.mem_used_gb / s.mem_total_gb) * 100 : null;
|
|
256
|
+
const diskPct = s.disk_used_gb != null && s.disk_total_gb != null && s.disk_total_gb > 0 ? (s.disk_used_gb / s.disk_total_gb) * 100 : null;
|
|
257
|
+
const isExpanded = expanded.has(s.hostname);
|
|
258
|
+
return (
|
|
259
|
+
<div
|
|
260
|
+
key={s.hostname}
|
|
261
|
+
className="rounded-lg border px-2.5 py-2 space-y-1.5"
|
|
262
|
+
style={{
|
|
263
|
+
background: 'var(--bg)',
|
|
264
|
+
borderColor: 'var(--border)',
|
|
265
|
+
opacity: offline ? 0.55 : 1,
|
|
266
|
+
}}
|
|
267
|
+
data-server-card={s.hostname}
|
|
268
|
+
data-server-expanded={isExpanded ? 'true' : 'false'}
|
|
269
|
+
title={s.ip ? `${s.hostname} · ${s.ip}` : s.hostname}
|
|
270
|
+
>
|
|
271
|
+
{/* v0.10.0 Hero 1+2: header row — click toggles expanded
|
|
272
|
+
detail view (sparklines + disk + agent rollup). */}
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
onClick={() => toggleExpanded(s.hostname)}
|
|
276
|
+
aria-expanded={isExpanded}
|
|
277
|
+
className="w-full flex items-center justify-between gap-2 text-left"
|
|
278
|
+
data-server-card-toggle={s.hostname}
|
|
279
|
+
>
|
|
280
|
+
<div className="min-w-0 flex items-center gap-1.5">
|
|
281
|
+
{/* Hero 1 health badge — worst-of CPU/Mem/Disk */}
|
|
282
|
+
{!offline && <HealthBadge cpu={cpuPct} mem={memPct} disk={diskPct} />}
|
|
283
|
+
<span className="font-mono text-[12px] font-semibold truncate" style={{ color: 'var(--fg)' }}>{s.hostname}</span>
|
|
284
|
+
{s.note && <span className="text-[9px] text-[var(--fg-dim)]">({s.note})</span>}
|
|
285
|
+
</div>
|
|
286
|
+
<span className="flex items-center gap-1 shrink-0">
|
|
287
|
+
<span className="text-[10px] text-[var(--fg-muted)] font-mono tabular-nums">
|
|
288
|
+
{s.agent_count} agent{s.agent_count === 1 ? '' : 's'}
|
|
289
|
+
</span>
|
|
290
|
+
{/* Chevron — rotates 90° on expanded */}
|
|
291
|
+
<svg
|
|
292
|
+
width="10" height="10" viewBox="0 0 10 10"
|
|
293
|
+
style={{ transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 150ms ease-out' }}
|
|
294
|
+
aria-hidden
|
|
295
|
+
>
|
|
296
|
+
<path d="M3 1.5L7 5L3 8.5" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
297
|
+
</svg>
|
|
298
|
+
</span>
|
|
299
|
+
</button>
|
|
300
|
+
{!offline && cpuPct != null && (
|
|
301
|
+
<Bar pct={cpuPct} label={`CPU ${s.cpu_load_1min!.toFixed(2)}/${s.cpu_cores}`} />
|
|
302
|
+
)}
|
|
303
|
+
{!offline && memPct != null && (
|
|
304
|
+
<Bar pct={memPct} label={`RAM ${s.mem_used_gb!.toFixed(1)}/${s.mem_total_gb!.toFixed(1)}G`} />
|
|
305
|
+
)}
|
|
306
|
+
{offline && (
|
|
307
|
+
<div className="text-[10px] text-[var(--fg-dim)] font-mono italic">CPU n/a · RAM n/a · offline</div>
|
|
308
|
+
)}
|
|
309
|
+
{/* v0.10.0 Hero 1+2: expanded detail — disk bar +
|
|
310
|
+
5-min sparklines + agent rollup. Visible only when
|
|
311
|
+
the user clicks the card. Renders gracefully when
|
|
312
|
+
upstream hub hasn't shipped optional fields. */}
|
|
313
|
+
{isExpanded && !offline && (
|
|
314
|
+
<div className="pt-1 mt-1 border-t space-y-1.5" style={{ borderColor: 'var(--border)' }}>
|
|
315
|
+
{diskPct != null ? (
|
|
316
|
+
<Bar pct={diskPct} label={`DISK ${s.disk_used_gb!.toFixed(1)}/${s.disk_total_gb!.toFixed(1)}G`} />
|
|
317
|
+
) : (
|
|
318
|
+
/* #157 sibling fix — same misleading version-pin copy
|
|
319
|
+
dropped at the disk-metric placeholder. Same
|
|
320
|
+
rationale as the agent-rollup copy above. */
|
|
321
|
+
<div className="text-[9px] text-[var(--fg-dim)] font-mono italic" data-server-disk-missing="true">disk metric not reported by hub</div>
|
|
322
|
+
)}
|
|
323
|
+
{s.cpu_history && s.cpu_history.length >= 2 && (
|
|
324
|
+
<div className="space-y-0.5">
|
|
325
|
+
<div className="text-[9px] text-[var(--fg-muted)] font-mono">CPU · 5-min</div>
|
|
326
|
+
<Sparkline values={s.cpu_history} tint="#10b981" label="CPU" />
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
{s.mem_history && s.mem_history.length >= 2 && (
|
|
330
|
+
<div className="space-y-0.5">
|
|
331
|
+
<div className="text-[9px] text-[var(--fg-muted)] font-mono">RAM · 5-min</div>
|
|
332
|
+
<Sparkline values={s.mem_history} tint="#06b6d4" label="MEM" />
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
{/* v0.10.2 RFC-014 §7 close gate #3 — disk usage
|
|
336
|
+
5-min curve. Amber tint matches the disk bar
|
|
337
|
+
tier convention (DISK > CPU/Mem in alert-
|
|
338
|
+
priority hierarchy since disk-full is a hard
|
|
339
|
+
failure mode). Render only when agent-node
|
|
340
|
+
2.4.1-preview.0+ has shipped disk_history;
|
|
341
|
+
backward-compat handles older agents silently
|
|
342
|
+
(no sparkline, no broken state). */}
|
|
343
|
+
{s.disk_history && s.disk_history.length >= 2 && (
|
|
344
|
+
<div className="space-y-0.5">
|
|
345
|
+
<div className="text-[9px] text-[var(--fg-muted)] font-mono">DISK · 5-min</div>
|
|
346
|
+
<Sparkline values={s.disk_history} tint="#f59e0b" label="DISK" />
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
|
+
<div className="pt-1">
|
|
350
|
+
<div className="text-[9px] text-[var(--fg-muted)] font-mono mb-0.5">agents</div>
|
|
351
|
+
<AgentList agents={s.agents} />
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
})}
|
|
358
|
+
{servers.length > 0 && (
|
|
359
|
+
<div className="pt-1 text-[9px] text-[var(--fg-dim)] font-mono text-center">
|
|
360
|
+
live · refreshing every 5s
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function ServersDrawer() {
|
|
368
|
+
const [open, setOpen] = useState(false);
|
|
369
|
+
// Drawer state is per-user-machine — persist like the other dashboard
|
|
370
|
+
// sticky toggles (`anet-topo-layout`, `anet-topo-view`, etc.).
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
try { if (localStorage.getItem('anet-servers-drawer') === '1') setOpen(true); } catch {}
|
|
373
|
+
}, []);
|
|
374
|
+
const toggle = () => setOpen(prev => {
|
|
375
|
+
const next = !prev;
|
|
376
|
+
try { localStorage.setItem('anet-servers-drawer', next ? '1' : '0'); } catch {}
|
|
377
|
+
return next;
|
|
378
|
+
});
|
|
379
|
+
|
|
226
380
|
// Round 20 / #119 step 3 final delivery — real SWR fetch. 5s refresh
|
|
227
381
|
// matches the other live drawers (HealthBanner, Sidebar) so an operator
|
|
228
382
|
// sees host telemetry update in roughly the same beat as session state.
|
|
229
383
|
// Only poll while expanded — collapsed icon strip doesn't need fresh data.
|
|
230
|
-
const {
|
|
231
|
-
open ? '/api/hub/servers' : null,
|
|
232
|
-
fetcher,
|
|
233
|
-
{ refreshInterval: 5000, dedupingInterval: 3000 },
|
|
234
|
-
);
|
|
235
|
-
const servers = data?.servers ?? [];
|
|
236
|
-
const unavailable = data?.unavailable === true;
|
|
237
|
-
const onlineCount = servers.filter(s => s.status === 'online').length;
|
|
238
|
-
const loading = open && !data && !error;
|
|
384
|
+
const { servers, onlineCount } = useServers(open);
|
|
239
385
|
|
|
240
386
|
return (
|
|
241
387
|
<aside
|
|
@@ -271,141 +417,7 @@ export function ServersDrawer() {
|
|
|
271
417
|
</button>
|
|
272
418
|
|
|
273
419
|
{open && (
|
|
274
|
-
<
|
|
275
|
-
{loading && (
|
|
276
|
-
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-4">
|
|
277
|
-
loading servers…
|
|
278
|
-
</div>
|
|
279
|
-
)}
|
|
280
|
-
{error && !data && (
|
|
281
|
-
<div className="rounded-md border px-2.5 py-2 text-[10px] font-mono"
|
|
282
|
-
style={{ background: 'rgb(239 68 68 / 0.06)', borderColor: 'rgb(239 68 68 / 0.25)', color: '#ef4444' }}>
|
|
283
|
-
hub unreachable · retrying every 5s
|
|
284
|
-
</div>
|
|
285
|
-
)}
|
|
286
|
-
{unavailable && (
|
|
287
|
-
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-3 leading-relaxed">
|
|
288
|
-
host telemetry not available<br/>
|
|
289
|
-
<span className="text-[var(--fg-dim)]">upgrade commhub-server ≥ 0.8.1-preview.2</span>
|
|
290
|
-
</div>
|
|
291
|
-
)}
|
|
292
|
-
{!loading && !error && !unavailable && servers.length === 0 && (
|
|
293
|
-
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-4">
|
|
294
|
-
no servers reporting yet
|
|
295
|
-
</div>
|
|
296
|
-
)}
|
|
297
|
-
{servers.map(s => {
|
|
298
|
-
const offline = s.status === 'offline';
|
|
299
|
-
const cpuPct = s.cpu_load_1min != null && s.cpu_cores > 0 ? (s.cpu_load_1min / s.cpu_cores) * 100 : null;
|
|
300
|
-
const memPct = s.mem_used_gb != null && s.mem_total_gb != null && s.mem_total_gb > 0 ? (s.mem_used_gb / s.mem_total_gb) * 100 : null;
|
|
301
|
-
const diskPct = s.disk_used_gb != null && s.disk_total_gb != null && s.disk_total_gb > 0 ? (s.disk_used_gb / s.disk_total_gb) * 100 : null;
|
|
302
|
-
const isExpanded = expanded.has(s.hostname);
|
|
303
|
-
return (
|
|
304
|
-
<div
|
|
305
|
-
key={s.hostname}
|
|
306
|
-
className="rounded-lg border px-2.5 py-2 space-y-1.5"
|
|
307
|
-
style={{
|
|
308
|
-
background: 'var(--bg)',
|
|
309
|
-
borderColor: 'var(--border)',
|
|
310
|
-
opacity: offline ? 0.55 : 1,
|
|
311
|
-
}}
|
|
312
|
-
data-server-card={s.hostname}
|
|
313
|
-
data-server-expanded={isExpanded ? 'true' : 'false'}
|
|
314
|
-
title={s.ip ? `${s.hostname} · ${s.ip}` : s.hostname}
|
|
315
|
-
>
|
|
316
|
-
{/* v0.10.0 Hero 1+2: header row — click toggles expanded
|
|
317
|
-
detail view (sparklines + disk + agent rollup). */}
|
|
318
|
-
<button
|
|
319
|
-
type="button"
|
|
320
|
-
onClick={() => toggleExpanded(s.hostname)}
|
|
321
|
-
aria-expanded={isExpanded}
|
|
322
|
-
className="w-full flex items-center justify-between gap-2 text-left"
|
|
323
|
-
data-server-card-toggle={s.hostname}
|
|
324
|
-
>
|
|
325
|
-
<div className="min-w-0 flex items-center gap-1.5">
|
|
326
|
-
{/* Hero 1 health badge — worst-of CPU/Mem/Disk */}
|
|
327
|
-
{!offline && <HealthBadge cpu={cpuPct} mem={memPct} disk={diskPct} />}
|
|
328
|
-
<span className="font-mono text-[12px] font-semibold truncate" style={{ color: 'var(--fg)' }}>{s.hostname}</span>
|
|
329
|
-
{s.note && <span className="text-[9px] text-[var(--fg-dim)]">({s.note})</span>}
|
|
330
|
-
</div>
|
|
331
|
-
<span className="flex items-center gap-1 shrink-0">
|
|
332
|
-
<span className="text-[10px] text-[var(--fg-muted)] font-mono tabular-nums">
|
|
333
|
-
{s.agent_count} agent{s.agent_count === 1 ? '' : 's'}
|
|
334
|
-
</span>
|
|
335
|
-
{/* Chevron — rotates 90° on expanded */}
|
|
336
|
-
<svg
|
|
337
|
-
width="10" height="10" viewBox="0 0 10 10"
|
|
338
|
-
style={{ transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 150ms ease-out' }}
|
|
339
|
-
aria-hidden
|
|
340
|
-
>
|
|
341
|
-
<path d="M3 1.5L7 5L3 8.5" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
342
|
-
</svg>
|
|
343
|
-
</span>
|
|
344
|
-
</button>
|
|
345
|
-
{!offline && cpuPct != null && (
|
|
346
|
-
<Bar pct={cpuPct} label={`CPU ${s.cpu_load_1min!.toFixed(2)}/${s.cpu_cores}`} />
|
|
347
|
-
)}
|
|
348
|
-
{!offline && memPct != null && (
|
|
349
|
-
<Bar pct={memPct} label={`RAM ${s.mem_used_gb!.toFixed(1)}/${s.mem_total_gb!.toFixed(1)}G`} />
|
|
350
|
-
)}
|
|
351
|
-
{offline && (
|
|
352
|
-
<div className="text-[10px] text-[var(--fg-dim)] font-mono italic">CPU n/a · RAM n/a · offline</div>
|
|
353
|
-
)}
|
|
354
|
-
{/* v0.10.0 Hero 1+2: expanded detail — disk bar +
|
|
355
|
-
5-min sparklines + agent rollup. Visible only when
|
|
356
|
-
the user clicks the card. Renders gracefully when
|
|
357
|
-
upstream hub hasn't shipped optional fields. */}
|
|
358
|
-
{isExpanded && !offline && (
|
|
359
|
-
<div className="pt-1 mt-1 border-t space-y-1.5" style={{ borderColor: 'var(--border)' }}>
|
|
360
|
-
{diskPct != null ? (
|
|
361
|
-
<Bar pct={diskPct} label={`DISK ${s.disk_used_gb!.toFixed(1)}/${s.disk_total_gb!.toFixed(1)}G`} />
|
|
362
|
-
) : (
|
|
363
|
-
/* #157 sibling fix — same misleading version-pin copy
|
|
364
|
-
dropped at the disk-metric placeholder. Same
|
|
365
|
-
rationale as the agent-rollup copy above. */
|
|
366
|
-
<div className="text-[9px] text-[var(--fg-dim)] font-mono italic" data-server-disk-missing="true">disk metric not reported by hub</div>
|
|
367
|
-
)}
|
|
368
|
-
{s.cpu_history && s.cpu_history.length >= 2 && (
|
|
369
|
-
<div className="space-y-0.5">
|
|
370
|
-
<div className="text-[9px] text-[var(--fg-muted)] font-mono">CPU · 5-min</div>
|
|
371
|
-
<Sparkline values={s.cpu_history} tint="#10b981" label="CPU" />
|
|
372
|
-
</div>
|
|
373
|
-
)}
|
|
374
|
-
{s.mem_history && s.mem_history.length >= 2 && (
|
|
375
|
-
<div className="space-y-0.5">
|
|
376
|
-
<div className="text-[9px] text-[var(--fg-muted)] font-mono">RAM · 5-min</div>
|
|
377
|
-
<Sparkline values={s.mem_history} tint="#06b6d4" label="MEM" />
|
|
378
|
-
</div>
|
|
379
|
-
)}
|
|
380
|
-
{/* v0.10.2 RFC-014 §7 close gate #3 — disk usage
|
|
381
|
-
5-min curve. Amber tint matches the disk bar
|
|
382
|
-
tier convention (DISK > CPU/Mem in alert-
|
|
383
|
-
priority hierarchy since disk-full is a hard
|
|
384
|
-
failure mode). Render only when agent-node
|
|
385
|
-
2.4.1-preview.0+ has shipped disk_history;
|
|
386
|
-
backward-compat handles older agents silently
|
|
387
|
-
(no sparkline, no broken state). */}
|
|
388
|
-
{s.disk_history && s.disk_history.length >= 2 && (
|
|
389
|
-
<div className="space-y-0.5">
|
|
390
|
-
<div className="text-[9px] text-[var(--fg-muted)] font-mono">DISK · 5-min</div>
|
|
391
|
-
<Sparkline values={s.disk_history} tint="#f59e0b" label="DISK" />
|
|
392
|
-
</div>
|
|
393
|
-
)}
|
|
394
|
-
<div className="pt-1">
|
|
395
|
-
<div className="text-[9px] text-[var(--fg-muted)] font-mono mb-0.5">agents</div>
|
|
396
|
-
<AgentList agents={s.agents} />
|
|
397
|
-
</div>
|
|
398
|
-
</div>
|
|
399
|
-
)}
|
|
400
|
-
</div>
|
|
401
|
-
);
|
|
402
|
-
})}
|
|
403
|
-
{servers.length > 0 && (
|
|
404
|
-
<div className="pt-1 text-[9px] text-[var(--fg-dim)] font-mono text-center">
|
|
405
|
-
live · refreshing every 5s
|
|
406
|
-
</div>
|
|
407
|
-
)}
|
|
408
|
-
</div>
|
|
420
|
+
<ServersPanel enabled={open} className="flex-1" />
|
|
409
421
|
)}
|
|
410
422
|
</aside>
|
|
411
423
|
);
|
|
@@ -13,6 +13,7 @@ const NAV_ITEMS = [
|
|
|
13
13
|
{ href: '/', label: 'Overview', 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' },
|
|
14
14
|
{ 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' },
|
|
15
15
|
{ href: '/nodes', label: 'Nodes', 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' },
|
|
16
|
+
{ href: '/servers', label: 'Servers', icon: 'M4 6.5A2.5 2.5 0 016.5 4h11A2.5 2.5 0 0120 6.5v1A2.5 2.5 0 0117.5 10h-11A2.5 2.5 0 014 7.5v-1zM4 16.5A2.5 2.5 0 016.5 14h11a2.5 2.5 0 012.5 2.5v1a2.5 2.5 0 01-2.5 2.5h-11A2.5 2.5 0 014 17.5v-1zM7 7h.01M7 17h.01' },
|
|
16
17
|
{ href: '/messages', label: 'Messages', 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' },
|
|
17
18
|
{ href: '/settings/networks', label: 'Networks', icon: 'M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9' },
|
|
18
19
|
{ href: '/logs', label: 'Audit Log', 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' },
|
|
@@ -13,6 +13,31 @@ interface ChatTask {
|
|
|
13
13
|
content: string;
|
|
14
14
|
result: string;
|
|
15
15
|
created_at: string;
|
|
16
|
+
completed_at?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ChatEvent =
|
|
20
|
+
| { kind: 'task'; task: ChatTask; at: string }
|
|
21
|
+
| { kind: 'reply'; task: ChatTask; at: string };
|
|
22
|
+
|
|
23
|
+
function eventTime(value?: string) {
|
|
24
|
+
if (!value) return 0;
|
|
25
|
+
return new Date(value.replace(' ', 'T') + (value.includes('T') ? '' : 'Z')).getTime() || 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatTimestamp(value?: string) {
|
|
29
|
+
if (!value) return '--';
|
|
30
|
+
const d = new Date(value.replace(' ', 'T') + (value.includes('T') ? '' : 'Z'));
|
|
31
|
+
if (Number.isNaN(d.getTime())) return value;
|
|
32
|
+
return d.toLocaleString(undefined, { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function quotePreview(text: string, max = 140) {
|
|
36
|
+
const compact = (text || '')
|
|
37
|
+
.replace(/\[Dashboard 附件[\s\S]*$/m, '[附件]')
|
|
38
|
+
.replace(/\s+/g, ' ')
|
|
39
|
+
.trim();
|
|
40
|
+
return compact.length > max ? `${compact.slice(0, max)}…` : compact;
|
|
16
41
|
}
|
|
17
42
|
|
|
18
43
|
function extractAttachmentPreviews(text: string): string[] {
|
|
@@ -433,11 +458,24 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
433
458
|
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
|
|
434
459
|
};
|
|
435
460
|
|
|
461
|
+
const chatEvents: ChatEvent[] = messages
|
|
462
|
+
.flatMap((task): ChatEvent[] => {
|
|
463
|
+
const events: ChatEvent[] = [{ kind: 'task', task, at: task.created_at }];
|
|
464
|
+
if (task.result) events.push({ kind: 'reply', task, at: task.completed_at || task.created_at });
|
|
465
|
+
return events;
|
|
466
|
+
})
|
|
467
|
+
.sort((a, b) => {
|
|
468
|
+
const delta = eventTime(a.at) - eventTime(b.at);
|
|
469
|
+
if (delta !== 0) return delta;
|
|
470
|
+
if (a.task.task_id !== b.task.task_id) return a.task.task_id.localeCompare(b.task.task_id);
|
|
471
|
+
return a.kind === 'task' ? -1 : 1;
|
|
472
|
+
});
|
|
473
|
+
|
|
436
474
|
// Inline mode: just the chat content, no panel chrome
|
|
437
475
|
const chatContent = (
|
|
438
476
|
<>
|
|
439
477
|
{/* Messages area */}
|
|
440
|
-
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
|
|
478
|
+
<div className="flex-1 overflow-y-auto px-3 py-3 sm:px-4 sm:py-4 space-y-3 sm:space-y-4">
|
|
441
479
|
{!historyLoaded && (
|
|
442
480
|
<div className="flex justify-center py-8">
|
|
443
481
|
<div className="w-5 h-5 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin" />
|
|
@@ -451,7 +489,8 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
451
489
|
</div>
|
|
452
490
|
)}
|
|
453
491
|
|
|
454
|
-
{
|
|
492
|
+
{chatEvents.map((event) => {
|
|
493
|
+
const m = event.task;
|
|
455
494
|
// Distinguish task source so users can tell when a peer agent
|
|
456
495
|
// forwarded a task vs when they themselves sent it from Dashboard.
|
|
457
496
|
const fromUser = !m.from_name || m.from_name === 'Dashboard' || m.from_name === 'api' || m.from_name === 'hub';
|
|
@@ -459,11 +498,41 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
459
498
|
const senderBadge = fromUser
|
|
460
499
|
? null
|
|
461
500
|
: <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-purple-500/15 border border-purple-500/30 text-[9px] text-purple-300 font-medium">↳ {m.from_name}</span>;
|
|
501
|
+
if (event.kind === 'reply') {
|
|
502
|
+
return (
|
|
503
|
+
<div key={`${m.task_id}:reply`} className="flex justify-start">
|
|
504
|
+
<div className="max-w-[92%] sm:max-w-[85%] bg-green-500/8 border border-green-500/15 rounded-2xl rounded-bl-md px-3 py-2.5 sm:px-4 shadow-sm">
|
|
505
|
+
<div className="flex items-center justify-between gap-2 mb-2">
|
|
506
|
+
<div className="flex items-center gap-1.5 min-w-0">
|
|
507
|
+
{m.to_name && <AliasAvatar alias={m.to_name} size={14} />}
|
|
508
|
+
<span className="text-[10px] text-[var(--fg)] font-medium truncate">{m.to_name}</span>
|
|
509
|
+
<span className="text-[9px] text-green-300/70">replied</span>
|
|
510
|
+
</div>
|
|
511
|
+
<span className="shrink-0 rounded-md bg-black/20 px-1.5 py-0.5 text-[9px] text-[var(--fg-dim)]" title={event.at}>
|
|
512
|
+
{timeAgo(event.at)} · {formatTimestamp(event.at)}
|
|
513
|
+
</span>
|
|
514
|
+
</div>
|
|
515
|
+
<div className="mb-2 rounded-lg border-l-2 border-cyan-400/40 bg-black/20 px-2.5 py-1.5">
|
|
516
|
+
<div className="text-[9px] text-cyan-300/80">
|
|
517
|
+
引用 {senderLabel} 的任务 · {formatTimestamp(m.created_at)}
|
|
518
|
+
</div>
|
|
519
|
+
<div className="mt-0.5 max-h-10 overflow-hidden text-[11px] leading-5 text-[var(--fg-muted)]">
|
|
520
|
+
{quotePreview(m.content) || 'No content'}
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
<div className="text-[13px] text-[var(--fg)]">
|
|
524
|
+
<MarkdownContent text={m.result} />
|
|
525
|
+
<AttachmentPreviews text={m.result} />
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
462
531
|
return (
|
|
463
|
-
<div key={m.task_id} className="space-y-2">
|
|
532
|
+
<div key={`${m.task_id}:task`} className="space-y-2">
|
|
464
533
|
{/* Outgoing task — labeled with origin so peer-forwarded tasks are obvious */}
|
|
465
534
|
<div className="flex justify-end">
|
|
466
|
-
<div className="max-w-[85%] bg-cyan-500/8 border border-cyan-500/15 rounded-2xl rounded-br-md px-
|
|
535
|
+
<div className="max-w-[92%] sm:max-w-[85%] bg-cyan-500/8 border border-cyan-500/15 rounded-2xl rounded-br-md px-3 py-2.5 sm:px-4 shadow-sm">
|
|
467
536
|
<div className="flex items-center gap-2 mb-1">
|
|
468
537
|
<span className={`text-[10px] font-medium ${fromUser ? 'text-cyan-400' : 'text-purple-300'}`}>{senderLabel}</span>
|
|
469
538
|
{!fromUser && <span className="text-[9px] text-[var(--fg-dim)]">forwarded to {m.to_name}</span>}
|
|
@@ -472,32 +541,18 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
472
541
|
<MarkdownContent text={m.content} />
|
|
473
542
|
<AttachmentPreviews text={m.content} />
|
|
474
543
|
</div>
|
|
475
|
-
<div className="flex items-center justify-between mt-1.5 gap-3">
|
|
544
|
+
<div className="flex flex-wrap items-center justify-between mt-1.5 gap-2 sm:gap-3">
|
|
476
545
|
<StatusBar status={m.status} />
|
|
477
546
|
<div className="flex items-center gap-2 shrink-0">
|
|
478
547
|
{senderBadge}
|
|
479
|
-
<span className="text-[9px] text-[var(--fg-dim)]"
|
|
548
|
+
<span className="rounded-md bg-black/15 px-1.5 py-0.5 text-[9px] text-[var(--fg-dim)]" title={m.created_at}>
|
|
549
|
+
{timeAgo(m.created_at)} · {formatTimestamp(m.created_at)}
|
|
550
|
+
</span>
|
|
480
551
|
</div>
|
|
481
552
|
</div>
|
|
482
553
|
</div>
|
|
483
554
|
</div>
|
|
484
555
|
|
|
485
|
-
{/* Incoming reply */}
|
|
486
|
-
{m.result && (
|
|
487
|
-
<div className="flex justify-start">
|
|
488
|
-
<div className="max-w-[85%] bg-green-500/8 border border-green-500/15 rounded-2xl rounded-bl-md px-4 py-2.5 shadow-sm">
|
|
489
|
-
<div className="flex items-center gap-1.5 mb-1.5">
|
|
490
|
-
{m.to_name && <AliasAvatar alias={m.to_name} size={14} />}
|
|
491
|
-
<span className="text-[10px] text-[var(--fg)] font-medium">{m.to_name}</span>
|
|
492
|
-
</div>
|
|
493
|
-
<div className="text-[13px] text-[var(--fg)]">
|
|
494
|
-
<MarkdownContent text={m.result} />
|
|
495
|
-
<AttachmentPreviews text={m.result} />
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
)}
|
|
500
|
-
|
|
501
556
|
{/* Typing indicator when running */}
|
|
502
557
|
{m.status === 'running' && !m.result && (
|
|
503
558
|
<div className="flex justify-start">
|
|
@@ -517,7 +572,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
517
572
|
</div>
|
|
518
573
|
|
|
519
574
|
{/* Input area */}
|
|
520
|
-
<div className="border-t border-[var(--border)] bg-[var(--bg-secondary)] px-
|
|
575
|
+
<div className="border-t border-[var(--border)] bg-[var(--bg-secondary)] px-3 py-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] sm:px-4">
|
|
521
576
|
<div className="flex items-end gap-2">
|
|
522
577
|
<div className="flex-1 relative">
|
|
523
578
|
{/* @ mention dropdown */}
|
|
@@ -540,7 +595,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
540
595
|
onPaste={handlePaste}
|
|
541
596
|
placeholder={`Message ${alias}...`}
|
|
542
597
|
rows={1}
|
|
543
|
-
className="w-full bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl px-
|
|
598
|
+
className="w-full bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl px-3 py-2.5 pr-24 text-base sm:px-4 sm:text-sm text-[var(--fg)] placeholder-[var(--fg-dim)] focus:border-cyan-500/40 focus:outline-none resize-none transition-colors"
|
|
544
599
|
/>
|
|
545
600
|
<div className="absolute right-2 bottom-1.5 flex items-center gap-1">
|
|
546
601
|
<label className="p-1 text-[var(--fg-dim)] hover:text-cyan-300 cursor-pointer rounded hover:bg-cyan-500/10" title="Attach image">
|
|
@@ -585,7 +640,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
585
640
|
))}
|
|
586
641
|
</div>
|
|
587
642
|
)}
|
|
588
|
-
<div className="flex justify-between text-[9px] text-[var(--fg-dim)] mt-1.5">
|
|
643
|
+
<div className="hidden sm:flex justify-between text-[9px] text-[var(--fg-dim)] mt-1.5">
|
|
589
644
|
<span>{input.includes('@') ? `Sending to: ${targetAlias}` : `Type @ to mention another node`}</span>
|
|
590
645
|
<span>Enter to send · paste image</span>
|
|
591
646
|
</div>
|
|
@@ -600,7 +655,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
|
|
|
600
655
|
return (
|
|
601
656
|
<>
|
|
602
657
|
<div className="fixed inset-0 bg-black/30 z-40 lg:hidden anet-fade-in" onClick={onClose} />
|
|
603
|
-
<div className="fixed top-0 right-0 h-
|
|
658
|
+
<div className="fixed top-0 right-0 h-[100dvh] w-full lg:w-[500px] bg-[var(--bg)] border-l border-[var(--border)] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in">
|
|
604
659
|
{/* Header */}
|
|
605
660
|
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--border)] bg-[var(--bg-secondary)]">
|
|
606
661
|
<div className="flex items-center gap-3">
|
|
@@ -81,7 +81,7 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
|
|
|
81
81
|
return (
|
|
82
82
|
<>
|
|
83
83
|
<div className="fixed inset-0 bg-black/30 z-40 anet-fade-in" onClick={onClose} />
|
|
84
|
-
<div className="fixed top-0 right-0 h-
|
|
84
|
+
<div className="fixed top-0 right-0 h-[100dvh] w-full lg:w-[500px] bg-[#0a0a1a] border-l border-[#2a2a4a] z-50 flex flex-col shadow-2xl shadow-black/60 overflow-y-auto animate-slide-in">
|
|
85
85
|
{/* Header */}
|
|
86
86
|
<div className="flex items-center justify-between px-5 py-4 border-b border-[#2a2a4a] bg-[#0d0d1a] sticky top-0">
|
|
87
87
|
<div>
|
package/app/layout.tsx
CHANGED