@synergenius/flow-weaver-pack-weaver 0.9.59 → 0.9.77
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/dist/ai-chat-provider.d.ts +12 -0
- package/dist/ai-chat-provider.d.ts.map +1 -1
- package/dist/ai-chat-provider.js +351 -335
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/agent-loop.d.ts +20 -0
- package/dist/bot/agent-loop.d.ts.map +1 -0
- package/dist/bot/agent-loop.js +331 -0
- package/dist/bot/agent-loop.js.map +1 -0
- package/dist/bot/ai-router.d.ts +19 -0
- package/dist/bot/ai-router.d.ts.map +1 -0
- package/dist/bot/ai-router.js +104 -0
- package/dist/bot/ai-router.js.map +1 -0
- package/dist/bot/assistant-tools.d.ts.map +1 -1
- package/dist/bot/assistant-tools.js +49 -33
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/async-mutex.d.ts +13 -0
- package/dist/bot/async-mutex.d.ts.map +1 -0
- package/dist/bot/async-mutex.js +37 -0
- package/dist/bot/async-mutex.js.map +1 -0
- package/dist/bot/bot-manager.d.ts +2 -2
- package/dist/bot/bot-manager.d.ts.map +1 -1
- package/dist/bot/bot-manager.js +3 -3
- package/dist/bot/bot-manager.js.map +1 -1
- package/dist/bot/bot-registry.js +2 -2
- package/dist/bot/bot-registry.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +1 -0
- package/dist/bot/conversation-store.d.ts.map +1 -1
- package/dist/bot/conversation-store.js.map +1 -1
- package/dist/bot/dashboard.d.ts.map +1 -1
- package/dist/bot/dashboard.js +17 -8
- package/dist/bot/dashboard.js.map +1 -1
- package/dist/bot/improve-loop.js.map +1 -1
- package/dist/bot/index.d.ts +2 -4
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +1 -2
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/instance-manager.d.ts +31 -0
- package/dist/bot/instance-manager.d.ts.map +1 -0
- package/dist/bot/instance-manager.js +115 -0
- package/dist/bot/instance-manager.js.map +1 -0
- package/dist/bot/orchestrator.d.ts +36 -0
- package/dist/bot/orchestrator.d.ts.map +1 -0
- package/dist/bot/orchestrator.js +176 -0
- package/dist/bot/orchestrator.js.map +1 -0
- package/dist/bot/profile-store.d.ts +36 -0
- package/dist/bot/profile-store.d.ts.map +1 -0
- package/dist/bot/profile-store.js +208 -0
- package/dist/bot/profile-store.js.map +1 -0
- package/dist/bot/profile-types.d.ts +126 -0
- package/dist/bot/profile-types.d.ts.map +1 -0
- package/dist/bot/profile-types.js +7 -0
- package/dist/bot/profile-types.js.map +1 -0
- package/dist/bot/run-store.d.ts.map +1 -1
- package/dist/bot/run-store.js +8 -0
- package/dist/bot/run-store.js.map +1 -1
- package/dist/bot/runner.d.ts +4 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +5 -1
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +109 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -0
- package/dist/bot/swarm-controller.js +640 -0
- package/dist/bot/swarm-controller.js.map +1 -0
- package/dist/bot/swarm-event-log.d.ts +28 -0
- package/dist/bot/swarm-event-log.d.ts.map +1 -0
- package/dist/bot/swarm-event-log.js +54 -0
- package/dist/bot/swarm-event-log.js.map +1 -0
- package/dist/bot/task-prompt-builder.d.ts +22 -0
- package/dist/bot/task-prompt-builder.d.ts.map +1 -0
- package/dist/bot/task-prompt-builder.js +240 -0
- package/dist/bot/task-prompt-builder.js.map +1 -0
- package/dist/bot/task-store.d.ts +21 -0
- package/dist/bot/task-store.d.ts.map +1 -0
- package/dist/bot/task-store.js +364 -0
- package/dist/bot/task-store.js.map +1 -0
- package/dist/bot/task-types.d.ts +79 -0
- package/dist/bot/task-types.d.ts.map +1 -0
- package/dist/bot/task-types.js +6 -0
- package/dist/bot/task-types.js.map +1 -0
- package/dist/bot/types.d.ts +8 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +79 -54
- package/dist/cli-handlers.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/docs/docs/weaver-bot-usage.md +35 -18
- package/dist/docs/docs/weaver-config.md +20 -0
- package/dist/docs/docs/weaver-task-queue.md +31 -19
- package/dist/docs/weaver-config.md +15 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools.d.ts +17 -0
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +98 -279
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +6 -24
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
- package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
- package/dist/node-types/orchestrator-dispatch.js +63 -0
- package/dist/node-types/orchestrator-dispatch.js.map +1 -0
- package/dist/node-types/orchestrator-load-state.d.ts +16 -0
- package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
- package/dist/node-types/orchestrator-load-state.js +60 -0
- package/dist/node-types/orchestrator-load-state.js.map +1 -0
- package/dist/node-types/orchestrator-route.d.ts +16 -0
- package/dist/node-types/orchestrator-route.d.ts.map +1 -0
- package/dist/node-types/orchestrator-route.js +28 -0
- package/dist/node-types/orchestrator-route.js.map +1 -0
- package/dist/node-types/receive-task.d.ts +2 -3
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +3 -48
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +53 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/dist/ui/bot-activity.js +2 -2
- package/dist/ui/bot-constants.d.ts +14 -0
- package/dist/ui/bot-constants.d.ts.map +1 -0
- package/dist/ui/bot-constants.js +189 -0
- package/dist/ui/bot-constants.js.map +1 -0
- package/dist/ui/bot-panel.js +207 -245
- package/dist/ui/bot-slot-card.js +141 -0
- package/dist/ui/budget-bar.js +59 -0
- package/dist/ui/chat-task-result.js +178 -0
- package/dist/ui/decision-log.js +136 -0
- package/dist/ui/profile-card.js +158 -0
- package/dist/ui/profile-editor.js +597 -0
- package/dist/ui/swarm-controls.js +245 -0
- package/dist/ui/swarm-dashboard.js +3012 -0
- package/dist/ui/task-create-form.js +98 -0
- package/dist/ui/task-detail-view.js +1044 -0
- package/dist/ui/task-pool-list.js +156 -0
- package/dist/workflows/orchestrator.d.ts +21 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +281 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/weaver-bot-session.d.ts +65 -0
- package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
- package/dist/workflows/weaver-bot-session.js +68 -0
- package/dist/workflows/weaver-bot-session.js.map +1 -0
- package/dist/workflows/weaver.d.ts +24 -0
- package/dist/workflows/weaver.d.ts.map +1 -0
- package/dist/workflows/weaver.js +28 -0
- package/dist/workflows/weaver.js.map +1 -0
- package/flowweaver.manifest.json +547 -133
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +378 -371
- package/src/bot/ai-router.ts +132 -0
- package/src/bot/assistant-tools.ts +47 -29
- package/src/bot/async-mutex.ts +37 -0
- package/src/bot/bot-manager.ts +3 -3
- package/src/bot/bot-registry.ts +2 -2
- package/src/bot/conversation-store.ts +2 -1
- package/src/bot/dashboard.ts +17 -8
- package/src/bot/improve-loop.ts +6 -6
- package/src/bot/index.ts +2 -4
- package/src/bot/instance-manager.ts +128 -0
- package/src/bot/orchestrator.ts +244 -0
- package/src/bot/profile-store.ts +225 -0
- package/src/bot/profile-types.ts +141 -0
- package/src/bot/run-store.ts +8 -0
- package/src/bot/runner.ts +9 -1
- package/src/bot/swarm-controller.ts +780 -0
- package/src/bot/swarm-event-log.ts +57 -0
- package/src/bot/task-prompt-builder.ts +309 -0
- package/src/bot/task-store.ts +407 -0
- package/src/bot/task-types.ts +100 -0
- package/src/bot/types.ts +8 -0
- package/src/cli-handlers.ts +78 -53
- package/src/docs/weaver-bot-usage.md +35 -18
- package/src/docs/weaver-config.md +20 -0
- package/src/docs/weaver-task-queue.md +31 -19
- package/src/index.ts +5 -4
- package/src/mcp-tools.ts +129 -372
- package/src/node-types/bot-report.ts +6 -24
- package/src/node-types/orchestrator-dispatch.ts +71 -0
- package/src/node-types/orchestrator-load-state.ts +66 -0
- package/src/node-types/orchestrator-route.ts +33 -0
- package/src/node-types/receive-task.ts +3 -57
- package/src/ui/bot-activity.tsx +2 -2
- package/src/ui/bot-constants.ts +192 -0
- package/src/ui/bot-panel.tsx +213 -247
- package/src/ui/bot-slot-card.tsx +139 -0
- package/src/ui/budget-bar.tsx +30 -0
- package/src/ui/chat-task-result.tsx +236 -0
- package/src/ui/decision-log.tsx +148 -0
- package/src/ui/profile-card.tsx +157 -0
- package/src/ui/profile-editor.tsx +384 -0
- package/src/ui/swarm-controls.tsx +260 -0
- package/src/ui/swarm-dashboard.tsx +647 -0
- package/src/ui/task-create-form.tsx +87 -0
- package/src/ui/task-detail-view.tsx +841 -0
- package/src/ui/task-pool-list.tsx +187 -0
- package/src/workflows/orchestrator.ts +302 -0
- package/dist/docs/weaver-bot-usage.md +0 -34
- package/dist/docs/weaver-genesis.md +0 -32
- package/dist/docs/weaver-task-queue.md +0 -34
- package/dist/ui/bot-workspace.js +0 -1015
- package/dist/ui/chat-bot-result.js +0 -71
- package/dist/ui/queue-input.js +0 -82
- package/dist/ui/session-bar.js +0 -174
- package/src/bot/error-guide.ts +0 -4
- package/src/bot/retry-utils.ts +0 -4
- package/src/bot/session-state.ts +0 -116
- package/src/bot/task-queue.ts +0 -262
- package/src/ui/bot-workspace.tsx +0 -442
- package/src/ui/chat-bot-result.tsx +0 -81
- package/src/ui/queue-input.tsx +0 -56
- package/src/ui/session-bar.tsx +0 -157
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Dashboard — main workspace component for the swarm command center.
|
|
3
|
+
*
|
|
4
|
+
* Replaces bot-workspace.tsx as the primary center-dock workspace.
|
|
5
|
+
* Composes: SwarmControls, BudgetBar, BotSlotCard, TaskPoolList,
|
|
6
|
+
* TaskCreateForm, and TaskDetailView into a unified swarm dashboard.
|
|
7
|
+
*
|
|
8
|
+
* Two-panel navigation:
|
|
9
|
+
* - Default: full swarm dashboard (controls + budget + bots + task pool + create form)
|
|
10
|
+
* - On task click: TaskDetailView (with back button returning to dashboard)
|
|
11
|
+
*
|
|
12
|
+
* Data loading:
|
|
13
|
+
* - Polls fw_weaver_swarm_status every 3s for swarm state
|
|
14
|
+
* - Polls fw_weaver_task_list every 5s for task pool
|
|
15
|
+
* - Both called immediately on mount
|
|
16
|
+
*
|
|
17
|
+
* Pattern: CommonJS require for platform deps, ESM import for local components,
|
|
18
|
+
* React.createElement throughout, module.exports at end.
|
|
19
|
+
*/
|
|
20
|
+
const React = require('react');
|
|
21
|
+
const { useState, useEffect, useCallback, useRef } = React;
|
|
22
|
+
const {
|
|
23
|
+
Flex, ScrollArea, EmptyState, Typography, StatusIcon, Icon, Tag, Card, Tabs, SectionTitle, Button,
|
|
24
|
+
Input, IconButton, IconPicker, ColorPicker, toast, usePackWorkspace,
|
|
25
|
+
} = require('@fw/plugin-ui-kit');
|
|
26
|
+
|
|
27
|
+
// Local pack-specific components (bundled by esbuild)
|
|
28
|
+
import SwarmControls from './swarm-controls';
|
|
29
|
+
import BudgetBar from './budget-bar';
|
|
30
|
+
import BotSlotCard from './bot-slot-card';
|
|
31
|
+
import TaskPoolList from './task-pool-list';
|
|
32
|
+
import TaskCreateForm from './task-create-form';
|
|
33
|
+
import TaskDetailView from './task-detail-view';
|
|
34
|
+
import ProfileCard from './profile-card';
|
|
35
|
+
import ProfileEditor from './profile-editor';
|
|
36
|
+
import DecisionLog from './decision-log';
|
|
37
|
+
import { ICON_CATALOG, BOT_COLORS } from './bot-constants';
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Types
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
interface InstanceInfo {
|
|
44
|
+
instanceId: string;
|
|
45
|
+
profileId: string;
|
|
46
|
+
index: number;
|
|
47
|
+
status: 'idle' | 'executing' | 'paused' | 'stopped';
|
|
48
|
+
currentTaskId?: string;
|
|
49
|
+
currentRunId?: string;
|
|
50
|
+
startedAt?: string;
|
|
51
|
+
tokensUsed: number;
|
|
52
|
+
cost: number;
|
|
53
|
+
tasksCompleted: number;
|
|
54
|
+
tasksFailed: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface SwarmBudgetLayer {
|
|
58
|
+
limitTokens: number;
|
|
59
|
+
usedTokens: number;
|
|
60
|
+
limitCost: number;
|
|
61
|
+
usedCost: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface SwarmStatus {
|
|
65
|
+
status: 'idle' | 'running' | 'paused' | 'stopping';
|
|
66
|
+
instances: Record<string, InstanceInfo>;
|
|
67
|
+
maxConcurrent: number;
|
|
68
|
+
tasksCompleted: number;
|
|
69
|
+
tasksFailed: number;
|
|
70
|
+
totalTokensUsed: number;
|
|
71
|
+
totalCost: number;
|
|
72
|
+
budgets?: {
|
|
73
|
+
workspace: SwarmBudgetLayer;
|
|
74
|
+
session: SwarmBudgetLayer;
|
|
75
|
+
};
|
|
76
|
+
startedAt?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type TaskStatus = 'pending' | 'in-progress' | 'blocked' | 'done' | 'failed' | 'cancelled';
|
|
80
|
+
|
|
81
|
+
interface PoolTask {
|
|
82
|
+
id: string;
|
|
83
|
+
title: string;
|
|
84
|
+
status: TaskStatus;
|
|
85
|
+
priority: number;
|
|
86
|
+
isParent: boolean;
|
|
87
|
+
parentId?: string;
|
|
88
|
+
assignedProfile?: string;
|
|
89
|
+
createdAt: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Helpers
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
function parseToolResult(raw: unknown): unknown {
|
|
97
|
+
if (typeof raw === 'string') {
|
|
98
|
+
try { return JSON.parse(raw); } catch { return raw; }
|
|
99
|
+
}
|
|
100
|
+
return raw;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Resolve task title for a bot's currentTaskId from the task pool. */
|
|
104
|
+
function resolveTaskTitle(taskId: string | undefined, tasks: PoolTask[]): string | undefined {
|
|
105
|
+
if (!taskId) return undefined;
|
|
106
|
+
const task = tasks.find(t => t.id === taskId);
|
|
107
|
+
return task?.title;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Component
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
function SwarmDashboard() {
|
|
115
|
+
const ctx = usePackWorkspace();
|
|
116
|
+
const { callTool, onRefresh } = ctx;
|
|
117
|
+
|
|
118
|
+
// --- Navigation state ---
|
|
119
|
+
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
|
120
|
+
const [activeTab, setActiveTab] = useState('tasks');
|
|
121
|
+
const [editingBotId, setEditingBotId] = useState<string | null>(null);
|
|
122
|
+
const [profileEditorMode, setProfileEditorMode] = useState<'create' | 'edit' | null>(null);
|
|
123
|
+
const [editingProfileId, setEditingProfileId] = useState<string | null>(null);
|
|
124
|
+
|
|
125
|
+
// --- Data state ---
|
|
126
|
+
const [swarmStatus, setSwarmStatus] = useState<SwarmStatus | null>(null);
|
|
127
|
+
const [tasks, setTasks] = useState<PoolTask[]>([]);
|
|
128
|
+
const [registeredBots, setRegisteredBots] = useState<Array<{ id: string; name: string; description: string; icon?: string; color?: string }>>([]);
|
|
129
|
+
const [providers, setProviders] = useState<Array<{ name: string; source: string; envVarsSet: boolean }>>([]);
|
|
130
|
+
const [insights, setInsights] = useState<Record<string, unknown> | null>(null);
|
|
131
|
+
const [profiles, setProfiles] = useState<Array<Record<string, unknown>>>([]);
|
|
132
|
+
const [orchestratorDecisions, setOrchestratorDecisions] = useState<Array<Record<string, unknown>>>([]);
|
|
133
|
+
const [loading, setLoading] = useState(true);
|
|
134
|
+
|
|
135
|
+
// Refs to track mounted state for async cleanup
|
|
136
|
+
const mountedRef = useRef(true);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
mountedRef.current = true;
|
|
139
|
+
return () => { mountedRef.current = false; };
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
// --- Data fetching ---
|
|
143
|
+
|
|
144
|
+
const fetchSwarmStatus = useCallback(async () => {
|
|
145
|
+
try {
|
|
146
|
+
const raw = await callTool('fw_weaver_swarm_status', {});
|
|
147
|
+
if (!mountedRef.current) return;
|
|
148
|
+
const data = parseToolResult(raw) as SwarmStatus | null;
|
|
149
|
+
if (data) setSwarmStatus(data);
|
|
150
|
+
} catch {
|
|
151
|
+
// non-fatal — swarm might not be started
|
|
152
|
+
}
|
|
153
|
+
}, [callTool]);
|
|
154
|
+
|
|
155
|
+
const fetchTaskList = useCallback(async () => {
|
|
156
|
+
try {
|
|
157
|
+
const raw = await callTool('fw_weaver_task_list', {});
|
|
158
|
+
if (!mountedRef.current) return;
|
|
159
|
+
const data = parseToolResult(raw);
|
|
160
|
+
if (Array.isArray(data)) {
|
|
161
|
+
setTasks(data as PoolTask[]);
|
|
162
|
+
} else if (data && typeof data === 'object' && Array.isArray((data as Record<string, unknown>).tasks)) {
|
|
163
|
+
setTasks((data as Record<string, unknown>).tasks as PoolTask[]);
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// non-fatal
|
|
167
|
+
}
|
|
168
|
+
}, [callTool]);
|
|
169
|
+
|
|
170
|
+
const fetchBots = useCallback(async () => {
|
|
171
|
+
try {
|
|
172
|
+
const raw = await callTool('fw_weaver_list_bots', {});
|
|
173
|
+
if (!mountedRef.current) return;
|
|
174
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
175
|
+
if (Array.isArray(data)) setRegisteredBots(data);
|
|
176
|
+
} catch { /* non-fatal */ }
|
|
177
|
+
}, [callTool]);
|
|
178
|
+
|
|
179
|
+
const fetchConfig = useCallback(async () => {
|
|
180
|
+
try {
|
|
181
|
+
const [provRaw, insRaw] = await Promise.all([
|
|
182
|
+
callTool('fw_weaver_providers', {}),
|
|
183
|
+
callTool('fw_weaver_insights', {}),
|
|
184
|
+
]);
|
|
185
|
+
if (!mountedRef.current) return;
|
|
186
|
+
const provData = typeof provRaw === 'string' ? JSON.parse(provRaw) : provRaw;
|
|
187
|
+
const insData = typeof insRaw === 'string' ? JSON.parse(insRaw) : insRaw;
|
|
188
|
+
if (Array.isArray(provData)) setProviders(provData);
|
|
189
|
+
if (insData && typeof insData === 'object') setInsights(insData as Record<string, unknown>);
|
|
190
|
+
} catch { /* non-fatal */ }
|
|
191
|
+
}, [callTool]);
|
|
192
|
+
|
|
193
|
+
const fetchProfiles = useCallback(async () => {
|
|
194
|
+
try {
|
|
195
|
+
const raw = await callTool('fw_weaver_profile_list', {});
|
|
196
|
+
if (!mountedRef.current) return;
|
|
197
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
198
|
+
if (Array.isArray(data)) setProfiles(data);
|
|
199
|
+
} catch { /* non-fatal */ }
|
|
200
|
+
}, [callTool]);
|
|
201
|
+
|
|
202
|
+
const fetchOrchestratorStatus = useCallback(async () => {
|
|
203
|
+
try {
|
|
204
|
+
const raw = await callTool('fw_weaver_orchestrator_status', {});
|
|
205
|
+
if (!mountedRef.current) return;
|
|
206
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
207
|
+
if (data && typeof data === 'object') {
|
|
208
|
+
const decisions = (data as Record<string, unknown>).recentDecisions;
|
|
209
|
+
if (Array.isArray(decisions)) setOrchestratorDecisions(decisions);
|
|
210
|
+
}
|
|
211
|
+
} catch { /* non-fatal */ }
|
|
212
|
+
}, [callTool]);
|
|
213
|
+
|
|
214
|
+
const refreshAll = useCallback(async () => {
|
|
215
|
+
await Promise.all([fetchSwarmStatus(), fetchTaskList(), fetchBots(), fetchConfig(), fetchProfiles(), fetchOrchestratorStatus()]);
|
|
216
|
+
if (mountedRef.current) setLoading(false);
|
|
217
|
+
}, [fetchSwarmStatus, fetchTaskList, fetchBots, fetchConfig, fetchProfiles, fetchOrchestratorStatus]);
|
|
218
|
+
|
|
219
|
+
// Initial fetch on mount
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
refreshAll();
|
|
222
|
+
}, [refreshAll]);
|
|
223
|
+
|
|
224
|
+
// Poll swarm status every 3s
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
const interval = setInterval(fetchSwarmStatus, 3_000);
|
|
227
|
+
return () => clearInterval(interval);
|
|
228
|
+
}, [fetchSwarmStatus]);
|
|
229
|
+
|
|
230
|
+
// Poll task list every 5s
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
const interval = setInterval(fetchTaskList, 5_000);
|
|
233
|
+
return () => clearInterval(interval);
|
|
234
|
+
}, [fetchTaskList]);
|
|
235
|
+
|
|
236
|
+
// Poll profiles & orchestrator status every 10s
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
const interval = setInterval(() => {
|
|
239
|
+
fetchProfiles();
|
|
240
|
+
fetchOrchestratorStatus();
|
|
241
|
+
}, 10_000);
|
|
242
|
+
return () => clearInterval(interval);
|
|
243
|
+
}, [fetchProfiles, fetchOrchestratorStatus]);
|
|
244
|
+
|
|
245
|
+
// Listen for platform refresh events
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
return onRefresh(() => refreshAll());
|
|
248
|
+
}, [refreshAll, onRefresh]);
|
|
249
|
+
|
|
250
|
+
// --- Bot edit handlers ---
|
|
251
|
+
|
|
252
|
+
const handleUpdateBot = useCallback(async (botId: string, patch: Record<string, unknown>) => {
|
|
253
|
+
try {
|
|
254
|
+
await callTool('fw_weaver_register_bot', { id: botId, ...patch });
|
|
255
|
+
await fetchBots();
|
|
256
|
+
toast('Bot updated', { type: 'success' });
|
|
257
|
+
} catch (err: unknown) {
|
|
258
|
+
toast(err instanceof Error ? err.message : 'Failed to update bot', { type: 'error' });
|
|
259
|
+
}
|
|
260
|
+
}, [callTool, fetchBots]);
|
|
261
|
+
|
|
262
|
+
// --- Bot steering handlers ---
|
|
263
|
+
|
|
264
|
+
const handleSteerBot = useCallback(async (botId: string, command: string) => {
|
|
265
|
+
try {
|
|
266
|
+
await callTool('fw_weaver_steer', { botId, command });
|
|
267
|
+
toast(`${command} signal sent to ${botId}`, { type: 'info' });
|
|
268
|
+
fetchSwarmStatus();
|
|
269
|
+
} catch (err: unknown) {
|
|
270
|
+
toast(err instanceof Error ? err.message : `Failed to ${command}`, { type: 'error' });
|
|
271
|
+
}
|
|
272
|
+
}, [callTool, fetchSwarmStatus]);
|
|
273
|
+
|
|
274
|
+
// --- Navigation handlers ---
|
|
275
|
+
|
|
276
|
+
const handleTaskClick = useCallback((taskId: string) => {
|
|
277
|
+
setSelectedTaskId(taskId);
|
|
278
|
+
}, []);
|
|
279
|
+
|
|
280
|
+
const handleBack = useCallback(() => {
|
|
281
|
+
setSelectedTaskId(null);
|
|
282
|
+
// Refresh data when returning to dashboard
|
|
283
|
+
refreshAll();
|
|
284
|
+
}, [refreshAll]);
|
|
285
|
+
|
|
286
|
+
const handleTaskCreated = useCallback(() => {
|
|
287
|
+
fetchTaskList();
|
|
288
|
+
}, [fetchTaskList]);
|
|
289
|
+
|
|
290
|
+
// --- Render: TaskDetailView (when task is selected) ---
|
|
291
|
+
|
|
292
|
+
if (selectedTaskId) {
|
|
293
|
+
return React.createElement(TaskDetailView, {
|
|
294
|
+
taskId: selectedTaskId,
|
|
295
|
+
onBack: handleBack,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// --- Render: Swarm Dashboard (default view) ---
|
|
300
|
+
|
|
301
|
+
const swarmInstances = swarmStatus?.instances ?? {};
|
|
302
|
+
const swarmInstanceEntries = Object.values(swarmInstances) as InstanceInfo[];
|
|
303
|
+
const hasSwarmInstances = swarmInstanceEntries.length > 0;
|
|
304
|
+
const hasRegisteredBots = registeredBots.length > 0;
|
|
305
|
+
const sessionBudget = swarmStatus?.budgets?.session;
|
|
306
|
+
const hasBudget = !!(sessionBudget && (sessionBudget.limitTokens > 0 || sessionBudget.limitCost > 0));
|
|
307
|
+
const hasContent = hasSwarmInstances || hasRegisteredBots || tasks.length > 0 || swarmStatus !== null;
|
|
308
|
+
|
|
309
|
+
return React.createElement(Flex, {
|
|
310
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
311
|
+
style: { width: '100%', height: '100%', overflow: 'hidden' },
|
|
312
|
+
},
|
|
313
|
+
// ── SwarmControls (top bar) ──────────────────────────────────
|
|
314
|
+
React.createElement(SwarmControls, {
|
|
315
|
+
swarmStatus,
|
|
316
|
+
onRefresh: refreshAll,
|
|
317
|
+
}),
|
|
318
|
+
|
|
319
|
+
// ── BudgetBar (below controls) ──────────────────────────────
|
|
320
|
+
hasBudget && React.createElement(Flex, {
|
|
321
|
+
variant: 'column-stretch-start-nowrap-4',
|
|
322
|
+
style: { padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid var(--color-border-default)' },
|
|
323
|
+
},
|
|
324
|
+
React.createElement(BudgetBar, {
|
|
325
|
+
label: 'Tokens',
|
|
326
|
+
used: sessionBudget!.usedTokens,
|
|
327
|
+
limit: sessionBudget!.limitTokens,
|
|
328
|
+
unit: 'tokens',
|
|
329
|
+
}),
|
|
330
|
+
React.createElement(BudgetBar, {
|
|
331
|
+
label: 'Cost',
|
|
332
|
+
used: sessionBudget!.usedCost,
|
|
333
|
+
limit: sessionBudget!.limitCost,
|
|
334
|
+
unit: 'USD',
|
|
335
|
+
}),
|
|
336
|
+
),
|
|
337
|
+
|
|
338
|
+
// ── Bot slot cards (horizontal scrollable row) ──────────────
|
|
339
|
+
// ── Tabs ──────────────────────────────────────────────────────
|
|
340
|
+
React.createElement(Tabs, {
|
|
341
|
+
tabs: [
|
|
342
|
+
{ id: 'tasks', title: `Tasks (${tasks.length})` },
|
|
343
|
+
{ id: 'bots', title: hasSwarmInstances ? `Instances (${swarmInstanceEntries.length})` : `Bots (${registeredBots.length})` },
|
|
344
|
+
{ id: 'profiles', title: `Profiles (${profiles.length})` },
|
|
345
|
+
{ id: 'config', title: 'Config' },
|
|
346
|
+
],
|
|
347
|
+
activeTabId: activeTab,
|
|
348
|
+
onSelectTab: (id: string) => setActiveTab(id),
|
|
349
|
+
size: 'sm',
|
|
350
|
+
}),
|
|
351
|
+
|
|
352
|
+
// ── Tab content ──────────────────────────────────────────────
|
|
353
|
+
React.createElement(Flex, {
|
|
354
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
355
|
+
style: { flex: 1, minHeight: 0, overflow: 'auto' },
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
// Tasks tab
|
|
359
|
+
activeTab === 'tasks' && React.createElement(Flex, {
|
|
360
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
361
|
+
style: { flex: 1, minHeight: 0 },
|
|
362
|
+
},
|
|
363
|
+
!hasContent && !loading && React.createElement(EmptyState, {
|
|
364
|
+
icon: 'smartToy',
|
|
365
|
+
message: 'Swarm not started',
|
|
366
|
+
description: 'Start the swarm to begin processing tasks, or create tasks below.',
|
|
367
|
+
}),
|
|
368
|
+
|
|
369
|
+
hasContent && React.createElement(TaskPoolList, {
|
|
370
|
+
tasks,
|
|
371
|
+
onTaskClick: handleTaskClick,
|
|
372
|
+
}),
|
|
373
|
+
|
|
374
|
+
// Task create form (inside Tasks tab)
|
|
375
|
+
React.createElement(Flex, {
|
|
376
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
377
|
+
style: { padding: '8px 16px', flexShrink: 0, borderTop: '1px solid var(--color-border-default)' },
|
|
378
|
+
},
|
|
379
|
+
React.createElement(TaskCreateForm, {
|
|
380
|
+
onTaskCreated: handleTaskCreated,
|
|
381
|
+
}),
|
|
382
|
+
),
|
|
383
|
+
),
|
|
384
|
+
|
|
385
|
+
// Bots tab
|
|
386
|
+
activeTab === 'bots' && React.createElement(Flex, {
|
|
387
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
388
|
+
style: { flex: 1, minHeight: 0 },
|
|
389
|
+
},
|
|
390
|
+
// When swarm is running: show swarm instances
|
|
391
|
+
hasSwarmInstances && React.createElement(Flex, {
|
|
392
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
393
|
+
},
|
|
394
|
+
React.createElement(Flex, {
|
|
395
|
+
variant: 'row-center-start-nowrap-8',
|
|
396
|
+
style: { padding: '8px 16px', borderBottom: '1px solid var(--color-border-default)' },
|
|
397
|
+
},
|
|
398
|
+
React.createElement(Typography, {
|
|
399
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
400
|
+
style: { width: '120px', flexShrink: 0 },
|
|
401
|
+
}, 'Instance'),
|
|
402
|
+
React.createElement(Typography, {
|
|
403
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
404
|
+
style: { width: '110px', flexShrink: 0 },
|
|
405
|
+
}, 'Bot'),
|
|
406
|
+
React.createElement(Typography, {
|
|
407
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
408
|
+
style: { width: '70px', flexShrink: 0 },
|
|
409
|
+
}, 'Status'),
|
|
410
|
+
React.createElement(Typography, {
|
|
411
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
412
|
+
style: { flex: 1, minWidth: 0 },
|
|
413
|
+
}, 'Task'),
|
|
414
|
+
React.createElement(Typography, {
|
|
415
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
416
|
+
style: { width: '50px', flexShrink: 0, textAlign: 'right' },
|
|
417
|
+
}, 'Tokens'),
|
|
418
|
+
React.createElement(Typography, {
|
|
419
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
420
|
+
style: { width: '50px', flexShrink: 0, textAlign: 'right' },
|
|
421
|
+
}, 'Cost'),
|
|
422
|
+
React.createElement(Typography, {
|
|
423
|
+
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
424
|
+
style: { width: '50px', flexShrink: 0 },
|
|
425
|
+
}, ''),
|
|
426
|
+
),
|
|
427
|
+
// Instance rows
|
|
428
|
+
...swarmInstanceEntries.map((inst: InstanceInfo) => {
|
|
429
|
+
// Look up profile → bot for this instance
|
|
430
|
+
const profile = profiles.find((p: Record<string, unknown>) => p.id === inst.profileId);
|
|
431
|
+
const botId = profile?.botId as string | undefined;
|
|
432
|
+
const bot = registeredBots.find((b) => b.id === botId);
|
|
433
|
+
return React.createElement(BotSlotCard, {
|
|
434
|
+
key: inst.instanceId,
|
|
435
|
+
bot: {
|
|
436
|
+
botId: inst.instanceId,
|
|
437
|
+
botName: `${inst.profileId} #${inst.index}`,
|
|
438
|
+
status: inst.status,
|
|
439
|
+
currentTaskId: inst.currentTaskId,
|
|
440
|
+
currentRunId: inst.currentRunId,
|
|
441
|
+
startedAt: inst.startedAt,
|
|
442
|
+
tokensUsed: inst.tokensUsed,
|
|
443
|
+
cost: inst.cost,
|
|
444
|
+
},
|
|
445
|
+
profileName: (profile?.name as string) || inst.profileId,
|
|
446
|
+
botDisplayName: bot?.name,
|
|
447
|
+
botIcon: bot?.icon,
|
|
448
|
+
botColor: bot?.color,
|
|
449
|
+
currentTaskTitle: resolveTaskTitle(inst.currentTaskId, tasks),
|
|
450
|
+
onPause: (id: string) => handleSteerBot(id, 'pause'),
|
|
451
|
+
onResume: (id: string) => handleSteerBot(id, 'resume'),
|
|
452
|
+
onStop: (id: string) => handleSteerBot(id, 'cancel'),
|
|
453
|
+
});
|
|
454
|
+
}),
|
|
455
|
+
),
|
|
456
|
+
|
|
457
|
+
// When swarm is NOT running: show registered bots
|
|
458
|
+
!hasSwarmInstances && hasRegisteredBots && React.createElement(Flex, {
|
|
459
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
460
|
+
style: { padding: '8px 16px' },
|
|
461
|
+
},
|
|
462
|
+
...registeredBots.map((bot) => {
|
|
463
|
+
const isEditing = editingBotId === bot.id;
|
|
464
|
+
return React.createElement(Flex, {
|
|
465
|
+
key: bot.id,
|
|
466
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
467
|
+
style: { borderBottom: '1px solid var(--color-border-default)' },
|
|
468
|
+
},
|
|
469
|
+
// Bot row (clickable to toggle edit)
|
|
470
|
+
React.createElement(Flex, {
|
|
471
|
+
variant: 'row-center-start-nowrap-8',
|
|
472
|
+
style: { padding: '6px 0', cursor: 'pointer' },
|
|
473
|
+
onClick: () => setEditingBotId(isEditing ? null : bot.id),
|
|
474
|
+
},
|
|
475
|
+
React.createElement(Icon, {
|
|
476
|
+
name: bot.icon || 'smartToy', size: 16,
|
|
477
|
+
color: bot.color || 'color-text-medium',
|
|
478
|
+
}),
|
|
479
|
+
React.createElement(Flex, { variant: 'column-start-start-nowrap-1', style: { flex: 1, minWidth: 0 } },
|
|
480
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' }, bot.name),
|
|
481
|
+
bot.description && React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, bot.description),
|
|
482
|
+
),
|
|
483
|
+
React.createElement(Icon, { name: isEditing ? 'expandLess' : 'expandMore', size: 14, color: 'color-text-subtle' }),
|
|
484
|
+
),
|
|
485
|
+
|
|
486
|
+
// Edit section (icon + color pickers)
|
|
487
|
+
isEditing && React.createElement(Flex, {
|
|
488
|
+
variant: 'column-stretch-start-nowrap-12',
|
|
489
|
+
style: { padding: '8px 0 12px 24px' },
|
|
490
|
+
},
|
|
491
|
+
React.createElement(IconPicker, {
|
|
492
|
+
catalog: ICON_CATALOG,
|
|
493
|
+
value: bot.icon || 'smartToy',
|
|
494
|
+
onChange: (icon: string) => handleUpdateBot(bot.id, { icon }),
|
|
495
|
+
accentColor: bot.color || undefined,
|
|
496
|
+
defaultExpanded: true,
|
|
497
|
+
}),
|
|
498
|
+
React.createElement(ColorPicker, {
|
|
499
|
+
colors: BOT_COLORS,
|
|
500
|
+
value: bot.color || '',
|
|
501
|
+
onChange: (color: string) => handleUpdateBot(bot.id, { color }),
|
|
502
|
+
defaultExpanded: true,
|
|
503
|
+
}),
|
|
504
|
+
),
|
|
505
|
+
);
|
|
506
|
+
}),
|
|
507
|
+
),
|
|
508
|
+
|
|
509
|
+
// No bots at all
|
|
510
|
+
!hasSwarmInstances && !hasRegisteredBots && React.createElement(EmptyState, {
|
|
511
|
+
icon: 'smartToy',
|
|
512
|
+
message: 'No bots registered',
|
|
513
|
+
description: 'Register bots to start the swarm.',
|
|
514
|
+
}),
|
|
515
|
+
),
|
|
516
|
+
|
|
517
|
+
// Profiles tab — when ProfileEditor is open, render it INSTEAD of the list
|
|
518
|
+
activeTab === 'profiles' && profileEditorMode != null && React.createElement(ProfileEditor, {
|
|
519
|
+
mode: profileEditorMode,
|
|
520
|
+
profileId: editingProfileId ?? undefined,
|
|
521
|
+
bots: registeredBots.map((b: { id: string; name: string; icon?: string; color?: string }) => ({ id: b.id, name: b.name, icon: b.icon, color: b.color })),
|
|
522
|
+
onSave: () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); },
|
|
523
|
+
onCancel: () => { setProfileEditorMode(null); setEditingProfileId(null); },
|
|
524
|
+
onDelete: profileEditorMode === 'edit'
|
|
525
|
+
? () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); }
|
|
526
|
+
: undefined,
|
|
527
|
+
}),
|
|
528
|
+
|
|
529
|
+
// Profiles tab — default list view
|
|
530
|
+
activeTab === 'profiles' && profileEditorMode == null && React.createElement(Flex, {
|
|
531
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
532
|
+
style: { flex: 1, minHeight: 0 },
|
|
533
|
+
},
|
|
534
|
+
// ── Profile cards (scrollable) ──
|
|
535
|
+
React.createElement(Flex, {
|
|
536
|
+
variant: 'column-stretch-start-nowrap-8',
|
|
537
|
+
style: { flex: 1, minHeight: 0, overflow: 'auto', padding: '12px 16px' },
|
|
538
|
+
},
|
|
539
|
+
profiles.length > 0
|
|
540
|
+
? React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
|
|
541
|
+
...profiles.map((p: Record<string, unknown>) => {
|
|
542
|
+
const activeCount = swarmInstanceEntries.filter(
|
|
543
|
+
(inst: InstanceInfo) => inst.profileId === p.id && inst.status === 'executing',
|
|
544
|
+
).length;
|
|
545
|
+
return React.createElement(ProfileCard, {
|
|
546
|
+
key: p.id as string,
|
|
547
|
+
profile: p as unknown,
|
|
548
|
+
activeInstances: activeCount,
|
|
549
|
+
onEdit: (id: string) => { setEditingProfileId(id); setProfileEditorMode('edit'); },
|
|
550
|
+
onDelete: async (id: string) => {
|
|
551
|
+
const ok = await ctx.confirm('Are you sure you want to delete this profile?', {
|
|
552
|
+
title: 'Delete Profile',
|
|
553
|
+
confirmLabel: 'Delete',
|
|
554
|
+
state: 'danger',
|
|
555
|
+
});
|
|
556
|
+
if (!ok) return;
|
|
557
|
+
try {
|
|
558
|
+
await callTool('fw_weaver_profile_delete', { id });
|
|
559
|
+
await fetchProfiles();
|
|
560
|
+
toast('Profile deleted', { type: 'success' });
|
|
561
|
+
} catch (err: unknown) {
|
|
562
|
+
toast(err instanceof Error ? err.message : 'Failed to delete profile', { type: 'error' });
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
}),
|
|
567
|
+
)
|
|
568
|
+
: React.createElement(EmptyState, {
|
|
569
|
+
icon: 'person',
|
|
570
|
+
message: 'No profiles',
|
|
571
|
+
description: 'Profiles define how bots behave. Start the swarm to create defaults, or create one below.',
|
|
572
|
+
}),
|
|
573
|
+
|
|
574
|
+
// ── Routing / Decision Log (inside scrollable area) ──
|
|
575
|
+
orchestratorDecisions.length > 0 && React.createElement(DecisionLog, {
|
|
576
|
+
decisions: orchestratorDecisions as unknown[],
|
|
577
|
+
}),
|
|
578
|
+
),
|
|
579
|
+
|
|
580
|
+
// ── New Profile button (bottom bar) ──
|
|
581
|
+
React.createElement(Flex, {
|
|
582
|
+
variant: 'column-stretch-start-nowrap-0',
|
|
583
|
+
style: { flexShrink: 0, borderTop: '1px solid var(--color-border-default)', padding: '8px 16px' },
|
|
584
|
+
},
|
|
585
|
+
React.createElement(Button, {
|
|
586
|
+
size: 'xs', variant: 'clear', color: 'primary',
|
|
587
|
+
leftIcon: 'add',
|
|
588
|
+
onClick: () => setProfileEditorMode('create'),
|
|
589
|
+
}, 'New Profile'),
|
|
590
|
+
),
|
|
591
|
+
|
|
592
|
+
),
|
|
593
|
+
|
|
594
|
+
// Config tab
|
|
595
|
+
activeTab === 'config' && React.createElement(Flex, {
|
|
596
|
+
variant: 'column-stretch-start-nowrap-12',
|
|
597
|
+
style: { padding: '12px 16px' },
|
|
598
|
+
},
|
|
599
|
+
// ── Provider ──
|
|
600
|
+
React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
|
|
601
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Provider'),
|
|
602
|
+
React.createElement(Flex, { variant: 'row-center-start-nowrap-4' },
|
|
603
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
604
|
+
providers.find((p) => p.envVarsSet)?.name ?? 'None detected',
|
|
605
|
+
),
|
|
606
|
+
providers.find((p) => p.envVarsSet) && React.createElement(Tag, { size: 'small', color: 'info' }, 'active'),
|
|
607
|
+
),
|
|
608
|
+
),
|
|
609
|
+
|
|
610
|
+
// Trust
|
|
611
|
+
insights?.trust && React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
|
|
612
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Trust'),
|
|
613
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
614
|
+
`Phase ${(insights.trust as Record<string, unknown>).phase} · ${(insights.trust as Record<string, unknown>).score}/100`,
|
|
615
|
+
),
|
|
616
|
+
),
|
|
617
|
+
|
|
618
|
+
// Health
|
|
619
|
+
insights?.health && React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
|
|
620
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Health'),
|
|
621
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
622
|
+
`${(insights.health as Record<string, unknown>).overall}/100`,
|
|
623
|
+
),
|
|
624
|
+
),
|
|
625
|
+
|
|
626
|
+
// Available providers
|
|
627
|
+
providers.length > 0 && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-4' },
|
|
628
|
+
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Available Providers'),
|
|
629
|
+
React.createElement(Flex, { variant: 'row-center-start-wrap-4' },
|
|
630
|
+
...providers.map((p) =>
|
|
631
|
+
React.createElement(Tag, {
|
|
632
|
+
key: p.name,
|
|
633
|
+
size: 'small',
|
|
634
|
+
color: p.envVarsSet ? 'positive' : 'secondary',
|
|
635
|
+
}, p.name),
|
|
636
|
+
),
|
|
637
|
+
),
|
|
638
|
+
),
|
|
639
|
+
),
|
|
640
|
+
),
|
|
641
|
+
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export { SwarmDashboard };
|
|
646
|
+
export default SwarmDashboard;
|
|
647
|
+
module.exports = SwarmDashboard;
|