@synergenius/flow-weaver-pack-weaver 0.9.20 → 0.9.22
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/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +76 -31
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/types.d.ts +16 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/ui/bot-activity.js +249 -0
- package/dist/ui/bot-config.js +57 -35
- package/flowweaver.manifest.json +2 -1
- package/package.json +1 -1
- package/src/bot/runner.ts +83 -36
- package/src/bot/types.ts +8 -0
- package/src/ui/bot-activity.tsx +343 -0
- package/src/ui/bot-config.tsx +74 -31
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaver Bot Activity Panel — sidebar activity feed.
|
|
3
|
+
* Shows running bot, queued tasks, recent runs, and KPI bar.
|
|
4
|
+
* Runs in the platform bot-panel sandbox.
|
|
5
|
+
*/
|
|
6
|
+
const React = require('react');
|
|
7
|
+
const { useState, useEffect, useCallback, useRef } = React;
|
|
8
|
+
const { Typography } = require('@fw/plugin-ui-kit');
|
|
9
|
+
const { Icon } = require('@fw/plugin-ui-kit');
|
|
10
|
+
const { IconButton } = require('@fw/plugin-ui-kit');
|
|
11
|
+
const { Badge } = require('@fw/plugin-ui-kit');
|
|
12
|
+
const { ScrollArea } = require('@fw/plugin-ui-kit');
|
|
13
|
+
const { LoadingSpinner } = require('@fw/plugin-ui-kit');
|
|
14
|
+
const { styled } = require('@fw/plugin-theme');
|
|
15
|
+
|
|
16
|
+
const REFRESH_INTERVAL = 30_000;
|
|
17
|
+
|
|
18
|
+
function packToolUrl(toolName: string) {
|
|
19
|
+
// packName is injected via props — but for now the component knows its own pack
|
|
20
|
+
return `/api/pack-tool/@synergenius/flow-weaver-pack-weaver/${toolName}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function callTool(toolName: string, body: Record<string, unknown> = {}) {
|
|
24
|
+
const res = await fetch(packToolUrl(toolName), {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
body: JSON.stringify(body),
|
|
28
|
+
credentials: 'include',
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) throw new Error('Request failed');
|
|
31
|
+
const json = await res.json();
|
|
32
|
+
if (json.isError) throw new Error(json.result);
|
|
33
|
+
try { return JSON.parse(json.result); } catch { return json.result; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- Types ---
|
|
37
|
+
|
|
38
|
+
interface HistoryEntry {
|
|
39
|
+
id?: string;
|
|
40
|
+
outcome: string;
|
|
41
|
+
summary?: string;
|
|
42
|
+
workflowFile?: string;
|
|
43
|
+
startedAt: string;
|
|
44
|
+
duration?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface InsightsData {
|
|
48
|
+
health: { overall: number };
|
|
49
|
+
trust: { phase: number; score: number };
|
|
50
|
+
cost: { last7Days: number };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface QueueEntry {
|
|
54
|
+
id: string;
|
|
55
|
+
instruction: string;
|
|
56
|
+
priority: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Styled Components ---
|
|
60
|
+
|
|
61
|
+
const Container = styled.div({
|
|
62
|
+
display: 'flex',
|
|
63
|
+
flexDirection: 'column',
|
|
64
|
+
height: '100%',
|
|
65
|
+
overflow: 'hidden',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const SectionHeader = styled.div({
|
|
69
|
+
display: 'flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
gap: '6px',
|
|
72
|
+
padding: '10px 12px 4px',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const RunningCard = styled.div({
|
|
76
|
+
margin: '4px 12px 8px',
|
|
77
|
+
padding: '10px 12px',
|
|
78
|
+
borderRadius: '$border-radius-compact',
|
|
79
|
+
backgroundColor: '$color-surface-elevated',
|
|
80
|
+
borderLeft: '3px solid $color-brand-main',
|
|
81
|
+
cursor: 'pointer',
|
|
82
|
+
'&:hover': { opacity: 0.85 },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const RunningHeader = styled.div({
|
|
86
|
+
display: 'flex',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
gap: '8px',
|
|
89
|
+
marginBottom: '6px',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const RunningControls = styled.div({
|
|
93
|
+
display: 'flex',
|
|
94
|
+
gap: '4px',
|
|
95
|
+
marginLeft: 'auto',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const QueueItem = styled.div({
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
gap: '8px',
|
|
102
|
+
padding: '6px 12px',
|
|
103
|
+
borderBottom: '1px solid $color-border-default',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const RunRow = styled.div({
|
|
107
|
+
display: 'flex',
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
gap: '8px',
|
|
110
|
+
padding: '8px 12px',
|
|
111
|
+
cursor: 'pointer',
|
|
112
|
+
borderBottom: '1px solid $color-border-default',
|
|
113
|
+
'&:hover': { backgroundColor: '$color-surface-elevated' },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const RunText = styled.div({
|
|
117
|
+
flex: 1,
|
|
118
|
+
overflow: 'hidden',
|
|
119
|
+
textOverflow: 'ellipsis',
|
|
120
|
+
whiteSpace: 'nowrap',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const KpiBar = styled.div({
|
|
124
|
+
display: 'flex',
|
|
125
|
+
justifyContent: 'center',
|
|
126
|
+
gap: '12px',
|
|
127
|
+
padding: '8px 12px',
|
|
128
|
+
borderTop: '1px solid $color-border-default',
|
|
129
|
+
flexShrink: 0,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const EmptyState = styled.div({
|
|
133
|
+
display: 'flex',
|
|
134
|
+
flexDirection: 'column',
|
|
135
|
+
alignItems: 'center',
|
|
136
|
+
justifyContent: 'center',
|
|
137
|
+
gap: '8px',
|
|
138
|
+
padding: '24px 16px',
|
|
139
|
+
flex: 1,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// --- Helpers ---
|
|
143
|
+
|
|
144
|
+
const outcomeConfig: Record<string, { icon: string; color: string }> = {
|
|
145
|
+
completed: { icon: 'taskAlt', color: 'color-status-positive' },
|
|
146
|
+
success: { icon: 'taskAlt', color: 'color-status-positive' },
|
|
147
|
+
failed: { icon: 'error', color: 'color-status-negative' },
|
|
148
|
+
error: { icon: 'error', color: 'color-status-negative' },
|
|
149
|
+
skipped: { icon: 'minus', color: 'color-text-subtle' },
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
function formatTime(iso: string): string {
|
|
153
|
+
try {
|
|
154
|
+
const diffMs = Date.now() - new Date(iso).getTime();
|
|
155
|
+
if (diffMs < 0) return 'just now';
|
|
156
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
157
|
+
if (diffMin < 1) return 'now';
|
|
158
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
159
|
+
const diffH = Math.floor(diffMin / 60);
|
|
160
|
+
if (diffH < 24) return `${diffH}h ago`;
|
|
161
|
+
return `${Math.floor(diffH / 24)}d ago`;
|
|
162
|
+
} catch { return ''; }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function healthColor(score: number): string {
|
|
166
|
+
if (score >= 80) return 'var(--color-status-positive)';
|
|
167
|
+
if (score >= 50) return 'var(--color-status-caution)';
|
|
168
|
+
return 'var(--color-status-negative)';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// --- Component ---
|
|
172
|
+
|
|
173
|
+
function BotActivity({ packName, botId }: { packName: string; botId: string }) {
|
|
174
|
+
const [history, setHistory] = useState<HistoryEntry[]>([]);
|
|
175
|
+
const [queue, setQueue] = useState<QueueEntry[]>([]);
|
|
176
|
+
const [insights, setInsights] = useState<InsightsData | null>(null);
|
|
177
|
+
const [status, setStatus] = useState<{ state: string; currentTask?: string } | null>(null);
|
|
178
|
+
const [loading, setLoading] = useState(true);
|
|
179
|
+
const timerRef = useRef<ReturnType<typeof setInterval>>();
|
|
180
|
+
|
|
181
|
+
const fetchAll = useCallback(async () => {
|
|
182
|
+
const [hist, q, ins, st] = await Promise.allSettled([
|
|
183
|
+
callTool('fw_weaver_history', { limit: 15 }),
|
|
184
|
+
callTool('fw_weaver_queue', { action: 'list' }),
|
|
185
|
+
callTool('fw_weaver_insights'),
|
|
186
|
+
callTool('fw_weaver_status'),
|
|
187
|
+
]);
|
|
188
|
+
if (hist.status === 'fulfilled') setHistory(Array.isArray(hist.value) ? hist.value : []);
|
|
189
|
+
if (q.status === 'fulfilled') setQueue(Array.isArray(q.value) ? q.value : []);
|
|
190
|
+
if (ins.status === 'fulfilled') setInsights(ins.value);
|
|
191
|
+
if (st.status === 'fulfilled') setStatus(st.value);
|
|
192
|
+
setLoading(false);
|
|
193
|
+
}, []);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
fetchAll();
|
|
197
|
+
timerRef.current = setInterval(fetchAll, REFRESH_INTERVAL);
|
|
198
|
+
return () => clearInterval(timerRef.current);
|
|
199
|
+
}, [fetchAll]);
|
|
200
|
+
|
|
201
|
+
// Open center workspace via platform's window system
|
|
202
|
+
const openWorkspace = useCallback((runId?: string) => {
|
|
203
|
+
// Dispatch custom event that the platform's BotPanel listens for
|
|
204
|
+
window.dispatchEvent(new CustomEvent('fw:open-bot-workspace', {
|
|
205
|
+
detail: { runId, botId },
|
|
206
|
+
}));
|
|
207
|
+
}, [botId]);
|
|
208
|
+
|
|
209
|
+
const handleRemoveQueueItem = useCallback(async (id: string) => {
|
|
210
|
+
await callTool('fw_weaver_queue', { action: 'remove', id });
|
|
211
|
+
setQueue((prev) => prev.filter((q) => q.id !== id));
|
|
212
|
+
}, []);
|
|
213
|
+
|
|
214
|
+
const handlePause = useCallback(async (e: React.MouseEvent) => {
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
await callTool('fw_weaver_steer', { command: 'pause' });
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
const handleStop = useCallback(async (e: React.MouseEvent) => {
|
|
220
|
+
e.stopPropagation();
|
|
221
|
+
await callTool('fw_weaver_steer', { command: 'cancel' });
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
if (loading) return (
|
|
225
|
+
<EmptyState>
|
|
226
|
+
<LoadingSpinner size="small" />
|
|
227
|
+
</EmptyState>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const isRunning = status?.state && status.state !== 'idle' && status.state !== 'no active session';
|
|
231
|
+
const recentRuns = history.slice(0, 15);
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<Container>
|
|
235
|
+
{/* RUNNING */}
|
|
236
|
+
{isRunning && status && (
|
|
237
|
+
<>
|
|
238
|
+
<SectionHeader>
|
|
239
|
+
<Icon name="playArrow" size={12} color="color-brand-main" />
|
|
240
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle" style={{ textTransform: 'uppercase', letterSpacing: '0.04em' }}>
|
|
241
|
+
Running
|
|
242
|
+
</Typography>
|
|
243
|
+
</SectionHeader>
|
|
244
|
+
<RunningCard onClick={() => openWorkspace()}>
|
|
245
|
+
<RunningHeader>
|
|
246
|
+
<Badge variant="info">{status.state}</Badge>
|
|
247
|
+
<RunningControls>
|
|
248
|
+
<IconButton icon="pause" size="xs" variant="clear" onClick={handlePause} title="Pause" />
|
|
249
|
+
<IconButton icon="stop" size="xs" variant="clear" onClick={handleStop} title="Stop" />
|
|
250
|
+
</RunningControls>
|
|
251
|
+
</RunningHeader>
|
|
252
|
+
{status.currentTask && (
|
|
253
|
+
<Typography variant="caption-regular" color="color-text-medium" style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
254
|
+
{status.currentTask}
|
|
255
|
+
</Typography>
|
|
256
|
+
)}
|
|
257
|
+
</RunningCard>
|
|
258
|
+
</>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{/* QUEUED */}
|
|
262
|
+
{queue.length > 0 && (
|
|
263
|
+
<>
|
|
264
|
+
<SectionHeader>
|
|
265
|
+
<Icon name="pendingActions" size={12} color="color-text-subtle" />
|
|
266
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle" style={{ textTransform: 'uppercase', letterSpacing: '0.04em' }}>
|
|
267
|
+
Queued ({queue.length})
|
|
268
|
+
</Typography>
|
|
269
|
+
</SectionHeader>
|
|
270
|
+
{queue.map((q: QueueEntry) => (
|
|
271
|
+
<QueueItem key={q.id}>
|
|
272
|
+
<Icon name="pending" size={14} color="color-text-subtle" />
|
|
273
|
+
<Typography variant="caption-regular" color="color-text-medium" style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
274
|
+
{q.instruction}
|
|
275
|
+
</Typography>
|
|
276
|
+
<IconButton icon="close" size="xs" variant="clear" onClick={() => handleRemoveQueueItem(q.id)} title="Remove" />
|
|
277
|
+
</QueueItem>
|
|
278
|
+
))}
|
|
279
|
+
</>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
{/* RECENT */}
|
|
283
|
+
<SectionHeader>
|
|
284
|
+
<Icon name="scheduled" size={12} color="color-text-subtle" />
|
|
285
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle" style={{ textTransform: 'uppercase', letterSpacing: '0.04em' }}>
|
|
286
|
+
Recent
|
|
287
|
+
</Typography>
|
|
288
|
+
<div style={{ flex: 1 }} />
|
|
289
|
+
<IconButton icon="refresh" size="xs" variant="clear" onClick={fetchAll} title="Refresh" />
|
|
290
|
+
</SectionHeader>
|
|
291
|
+
|
|
292
|
+
<ScrollArea style={{ flex: 1 }}>
|
|
293
|
+
{recentRuns.length === 0 ? (
|
|
294
|
+
<EmptyState>
|
|
295
|
+
<Icon name="info" size={20} color="color-text-subtle" />
|
|
296
|
+
<Typography variant="caption-regular" color="color-text-subtle">
|
|
297
|
+
No recent runs. Ask Weaver to run a bot.
|
|
298
|
+
</Typography>
|
|
299
|
+
</EmptyState>
|
|
300
|
+
) : (
|
|
301
|
+
recentRuns.map((run: HistoryEntry, i: number) => {
|
|
302
|
+
const oc = outcomeConfig[run.outcome] ?? { icon: 'help', color: 'color-text-subtle' };
|
|
303
|
+
return (
|
|
304
|
+
<RunRow key={run.id ?? i} onClick={() => openWorkspace(run.id)}>
|
|
305
|
+
<Icon name={oc.icon as any} size={14} color={oc.color} />
|
|
306
|
+
<RunText>
|
|
307
|
+
<Typography variant="caption-regular" color="color-text-medium">
|
|
308
|
+
{run.summary ?? run.workflowFile ?? run.outcome}
|
|
309
|
+
</Typography>
|
|
310
|
+
</RunText>
|
|
311
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ fontFamily: 'var(--typography-family-mono)', flexShrink: 0 }}>
|
|
312
|
+
{formatTime(run.startedAt)}
|
|
313
|
+
</Typography>
|
|
314
|
+
</RunRow>
|
|
315
|
+
);
|
|
316
|
+
})
|
|
317
|
+
)}
|
|
318
|
+
</ScrollArea>
|
|
319
|
+
|
|
320
|
+
{/* KPI BAR */}
|
|
321
|
+
{insights && (
|
|
322
|
+
<KpiBar>
|
|
323
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
324
|
+
<span style={{ color: healthColor(insights.health?.overall ?? 0), fontWeight: 600 }}>
|
|
325
|
+
{insights.health?.overall ?? 0}
|
|
326
|
+
</span>
|
|
327
|
+
{' '}health
|
|
328
|
+
</Typography>
|
|
329
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
330
|
+
<span style={{ fontWeight: 600 }}>P{insights.trust?.phase ?? '?'}</span>
|
|
331
|
+
{' '}trust
|
|
332
|
+
</Typography>
|
|
333
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
334
|
+
<span style={{ fontWeight: 600 }}>${(insights.cost?.last7Days ?? 0).toFixed(2)}</span>
|
|
335
|
+
/7d
|
|
336
|
+
</Typography>
|
|
337
|
+
</KpiBar>
|
|
338
|
+
)}
|
|
339
|
+
</Container>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = BotActivity;
|
package/src/ui/bot-config.tsx
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Weaver Bot Config Panel —
|
|
2
|
+
* Weaver Bot Config Panel — shows active configuration (detected + configured).
|
|
3
3
|
* Runs in the platform bot-panel sandbox.
|
|
4
4
|
*/
|
|
5
5
|
const React = require('react');
|
|
6
6
|
const { useState, useEffect, useCallback } = React;
|
|
7
7
|
const { Typography } = require('@fw/plugin-ui-kit');
|
|
8
|
-
const { Icon } = require('@fw/plugin-ui-kit');
|
|
9
8
|
const { CollapsibleSection } = require('@fw/plugin-ui-kit');
|
|
10
9
|
const { LoadingSpinner } = require('@fw/plugin-ui-kit');
|
|
11
10
|
const { Banner } = require('@fw/plugin-ui-kit');
|
|
12
11
|
const { IconButton } = require('@fw/plugin-ui-kit');
|
|
12
|
+
const { Badge } = require('@fw/plugin-ui-kit');
|
|
13
13
|
const { styled } = require('@fw/plugin-theme');
|
|
14
14
|
|
|
15
|
+
const BASE = '/api/pack-tool/@synergenius/flow-weaver-pack-weaver';
|
|
16
|
+
|
|
15
17
|
const ConfigRow = styled.div({
|
|
16
18
|
display: 'flex',
|
|
17
19
|
justifyContent: 'space-between',
|
|
@@ -21,7 +23,20 @@ const ConfigRow = styled.div({
|
|
|
21
23
|
'&:last-of-type': { borderBottom: 'none' },
|
|
22
24
|
});
|
|
23
25
|
|
|
24
|
-
const
|
|
26
|
+
const Footer = styled.div({
|
|
27
|
+
display: 'flex',
|
|
28
|
+
justifyContent: 'space-between',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
marginTop: '10px',
|
|
31
|
+
paddingTop: '6px',
|
|
32
|
+
borderTop: '1px solid $color-border-default',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
interface ProviderInfo {
|
|
36
|
+
name: string;
|
|
37
|
+
source: string;
|
|
38
|
+
envVarsSet: boolean;
|
|
39
|
+
}
|
|
25
40
|
|
|
26
41
|
interface InsightsResult {
|
|
27
42
|
health: { overall: number };
|
|
@@ -29,23 +44,34 @@ interface InsightsResult {
|
|
|
29
44
|
trust: { phase: number; score: number };
|
|
30
45
|
}
|
|
31
46
|
|
|
47
|
+
function callTool(tool: string, body: Record<string, unknown> = {}) {
|
|
48
|
+
return fetch(`${BASE}/${tool}`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body: JSON.stringify(body),
|
|
52
|
+
credentials: 'include',
|
|
53
|
+
}).then(async res => {
|
|
54
|
+
if (!res.ok) throw new Error('Request failed');
|
|
55
|
+
const json = await res.json();
|
|
56
|
+
if (json.isError) throw new Error(json.result);
|
|
57
|
+
try { return JSON.parse(json.result); } catch { return json.result; }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
32
61
|
function BotConfig({ packName, botId }: { packName: string; botId: string }) {
|
|
33
|
-
const [
|
|
62
|
+
const [insights, setInsights] = useState<InsightsResult | null>(null);
|
|
63
|
+
const [providers, setProviders] = useState<ProviderInfo[]>([]);
|
|
34
64
|
const [error, setError] = useState<string | null>(null);
|
|
35
65
|
const [loading, setLoading] = useState(true);
|
|
36
66
|
|
|
37
67
|
const fetchData = useCallback(async () => {
|
|
38
68
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!res.ok) { setError('Failed to fetch'); setLoading(false); return; }
|
|
46
|
-
const json = await res.json();
|
|
47
|
-
if (json.isError) { setError(json.result); setLoading(false); return; }
|
|
48
|
-
setData(JSON.parse(json.result));
|
|
69
|
+
const [ins, provs] = await Promise.allSettled([
|
|
70
|
+
callTool('fw_weaver_insights'),
|
|
71
|
+
callTool('fw_weaver_providers'),
|
|
72
|
+
]);
|
|
73
|
+
if (ins.status === 'fulfilled') setInsights(ins.value);
|
|
74
|
+
if (provs.status === 'fulfilled' && Array.isArray(provs.value)) setProviders(provs.value);
|
|
49
75
|
setError(null);
|
|
50
76
|
} catch (e: any) {
|
|
51
77
|
setError(e.message ?? 'Failed to load');
|
|
@@ -56,49 +82,66 @@ function BotConfig({ packName, botId }: { packName: string; botId: string }) {
|
|
|
56
82
|
|
|
57
83
|
useEffect(() => { fetchData(); }, [fetchData]);
|
|
58
84
|
|
|
59
|
-
if (loading) return
|
|
85
|
+
if (loading) return (
|
|
86
|
+
<div style={{ padding: 16, display: 'flex', justifyContent: 'center' }}>
|
|
87
|
+
<LoadingSpinner size="small" />
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
60
90
|
|
|
61
|
-
if (error) return (
|
|
91
|
+
if (error && !insights) return (
|
|
62
92
|
<div style={{ padding: '8px 12px' }}>
|
|
63
93
|
<Banner status="danger" size="small">{error}</Banner>
|
|
64
94
|
</div>
|
|
65
95
|
);
|
|
66
96
|
|
|
67
|
-
|
|
68
|
-
const
|
|
97
|
+
// Determine active provider — prefer configured, fall back to first available
|
|
98
|
+
const bot = insights?.bots?.find((b: any) => b.name === botId) ?? insights?.bots?.[0];
|
|
99
|
+
const configuredProvider = bot?.provider;
|
|
100
|
+
const availableProvider = providers.find((p: ProviderInfo) => p.envVarsSet);
|
|
101
|
+
const activeProvider = configuredProvider || availableProvider?.name;
|
|
102
|
+
const isAutoDetected = !configuredProvider && !!availableProvider;
|
|
69
103
|
const approval = bot?.approvalMode ?? 'auto';
|
|
70
|
-
const
|
|
104
|
+
const trustPhase = insights?.trust?.phase ?? 1;
|
|
105
|
+
const trustScore = insights?.trust?.score ?? 0;
|
|
106
|
+
const noProvider = !activeProvider;
|
|
71
107
|
|
|
72
108
|
return (
|
|
73
109
|
<CollapsibleSection title="Configuration" defaultExpanded>
|
|
74
110
|
<div style={{ padding: '4px 12px 12px' }}>
|
|
75
111
|
<ConfigRow>
|
|
76
112
|
<Typography variant="smallCaption-bold" color="color-text-subtle">Provider</Typography>
|
|
77
|
-
<
|
|
113
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
114
|
+
<Typography variant="caption-regular" color="color-text-high">
|
|
115
|
+
{activeProvider ?? 'None detected'}
|
|
116
|
+
</Typography>
|
|
117
|
+
{isAutoDetected && <Badge variant="default">auto</Badge>}
|
|
118
|
+
</span>
|
|
78
119
|
</ConfigRow>
|
|
79
120
|
<ConfigRow>
|
|
80
121
|
<Typography variant="smallCaption-bold" color="color-text-subtle">Approval</Typography>
|
|
81
122
|
<Typography variant="caption-regular" color="color-text-high">{approval}</Typography>
|
|
82
123
|
</ConfigRow>
|
|
83
124
|
<ConfigRow>
|
|
84
|
-
<Typography variant="smallCaption-bold" color="color-text-subtle">Trust
|
|
85
|
-
<Typography variant="caption-regular" color="color-text-high">
|
|
125
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle">Trust</Typography>
|
|
126
|
+
<Typography variant="caption-regular" color="color-text-high">
|
|
127
|
+
P{trustPhase} ({trustScore}/100)
|
|
128
|
+
</Typography>
|
|
86
129
|
</ConfigRow>
|
|
87
130
|
|
|
88
|
-
{
|
|
89
|
-
<div style={{ marginTop:
|
|
90
|
-
<Banner status="
|
|
91
|
-
No
|
|
131
|
+
{noProvider && (
|
|
132
|
+
<div style={{ marginTop: 10 }}>
|
|
133
|
+
<Banner status="warning" size="small">
|
|
134
|
+
No AI provider detected. Set ANTHROPIC_API_KEY or install Claude CLI.
|
|
92
135
|
</Banner>
|
|
93
|
-
<Typography variant="caption-regular" color="color-text-subtle" style={{ marginTop: 6, fontFamily: 'var(--typography-family-mono)' }}>
|
|
94
|
-
npx flow-weaver weaver init
|
|
95
|
-
</Typography>
|
|
96
136
|
</div>
|
|
97
137
|
)}
|
|
98
138
|
|
|
99
|
-
<
|
|
139
|
+
<Footer>
|
|
140
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
141
|
+
Customize via .weaver.json
|
|
142
|
+
</Typography>
|
|
100
143
|
<IconButton icon="refresh" size="xs" variant="clear" onClick={fetchData} title="Refresh" />
|
|
101
|
-
</
|
|
144
|
+
</Footer>
|
|
102
145
|
</div>
|
|
103
146
|
</CollapsibleSection>
|
|
104
147
|
);
|