@synergenius/flow-weaver-pack-weaver 0.9.19 → 0.9.21
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/ui/bot-activity.js +249 -0
- package/dist/ui/bot-config.js +79 -35
- package/flowweaver.manifest.json +2 -1
- package/package.json +1 -1
- package/src/ui/bot-activity.tsx +343 -0
- package/src/ui/bot-config.tsx +94 -34
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/ui/bot-activity.tsx
|
|
4
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
var React = require("react");
|
|
6
|
+
var { useState, useEffect, useCallback, useRef } = React;
|
|
7
|
+
var { Typography } = require("@fw/plugin-ui-kit");
|
|
8
|
+
var { Icon } = require("@fw/plugin-ui-kit");
|
|
9
|
+
var { IconButton } = require("@fw/plugin-ui-kit");
|
|
10
|
+
var { Badge } = require("@fw/plugin-ui-kit");
|
|
11
|
+
var { ScrollArea } = require("@fw/plugin-ui-kit");
|
|
12
|
+
var { LoadingSpinner } = require("@fw/plugin-ui-kit");
|
|
13
|
+
var { styled } = require("@fw/plugin-theme");
|
|
14
|
+
var REFRESH_INTERVAL = 3e4;
|
|
15
|
+
function packToolUrl(toolName) {
|
|
16
|
+
return `/api/pack-tool/@synergenius/flow-weaver-pack-weaver/${toolName}`;
|
|
17
|
+
}
|
|
18
|
+
async function callTool(toolName, body = {}) {
|
|
19
|
+
const res = await fetch(packToolUrl(toolName), {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify(body),
|
|
23
|
+
credentials: "include"
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) throw new Error("Request failed");
|
|
26
|
+
const json = await res.json();
|
|
27
|
+
if (json.isError) throw new Error(json.result);
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(json.result);
|
|
30
|
+
} catch {
|
|
31
|
+
return json.result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var Container = styled.div({
|
|
35
|
+
display: "flex",
|
|
36
|
+
flexDirection: "column",
|
|
37
|
+
height: "100%",
|
|
38
|
+
overflow: "hidden"
|
|
39
|
+
});
|
|
40
|
+
var SectionHeader = styled.div({
|
|
41
|
+
display: "flex",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
gap: "6px",
|
|
44
|
+
padding: "10px 12px 4px"
|
|
45
|
+
});
|
|
46
|
+
var RunningCard = styled.div({
|
|
47
|
+
margin: "4px 12px 8px",
|
|
48
|
+
padding: "10px 12px",
|
|
49
|
+
borderRadius: "$border-radius-compact",
|
|
50
|
+
backgroundColor: "$color-surface-elevated",
|
|
51
|
+
borderLeft: "3px solid $color-brand-main",
|
|
52
|
+
cursor: "pointer",
|
|
53
|
+
"&:hover": { opacity: 0.85 }
|
|
54
|
+
});
|
|
55
|
+
var RunningHeader = styled.div({
|
|
56
|
+
display: "flex",
|
|
57
|
+
alignItems: "center",
|
|
58
|
+
gap: "8px",
|
|
59
|
+
marginBottom: "6px"
|
|
60
|
+
});
|
|
61
|
+
var RunningControls = styled.div({
|
|
62
|
+
display: "flex",
|
|
63
|
+
gap: "4px",
|
|
64
|
+
marginLeft: "auto"
|
|
65
|
+
});
|
|
66
|
+
var QueueItem = styled.div({
|
|
67
|
+
display: "flex",
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
gap: "8px",
|
|
70
|
+
padding: "6px 12px",
|
|
71
|
+
borderBottom: "1px solid $color-border-default"
|
|
72
|
+
});
|
|
73
|
+
var RunRow = styled.div({
|
|
74
|
+
display: "flex",
|
|
75
|
+
alignItems: "center",
|
|
76
|
+
gap: "8px",
|
|
77
|
+
padding: "8px 12px",
|
|
78
|
+
cursor: "pointer",
|
|
79
|
+
borderBottom: "1px solid $color-border-default",
|
|
80
|
+
"&:hover": { backgroundColor: "$color-surface-elevated" }
|
|
81
|
+
});
|
|
82
|
+
var RunText = styled.div({
|
|
83
|
+
flex: 1,
|
|
84
|
+
overflow: "hidden",
|
|
85
|
+
textOverflow: "ellipsis",
|
|
86
|
+
whiteSpace: "nowrap"
|
|
87
|
+
});
|
|
88
|
+
var KpiBar = styled.div({
|
|
89
|
+
display: "flex",
|
|
90
|
+
justifyContent: "center",
|
|
91
|
+
gap: "12px",
|
|
92
|
+
padding: "8px 12px",
|
|
93
|
+
borderTop: "1px solid $color-border-default",
|
|
94
|
+
flexShrink: 0
|
|
95
|
+
});
|
|
96
|
+
var EmptyState = styled.div({
|
|
97
|
+
display: "flex",
|
|
98
|
+
flexDirection: "column",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
justifyContent: "center",
|
|
101
|
+
gap: "8px",
|
|
102
|
+
padding: "24px 16px",
|
|
103
|
+
flex: 1
|
|
104
|
+
});
|
|
105
|
+
var outcomeConfig = {
|
|
106
|
+
completed: { icon: "taskAlt", color: "color-status-positive" },
|
|
107
|
+
success: { icon: "taskAlt", color: "color-status-positive" },
|
|
108
|
+
failed: { icon: "error", color: "color-status-negative" },
|
|
109
|
+
error: { icon: "error", color: "color-status-negative" },
|
|
110
|
+
skipped: { icon: "minus", color: "color-text-subtle" }
|
|
111
|
+
};
|
|
112
|
+
function formatTime(iso) {
|
|
113
|
+
try {
|
|
114
|
+
const diffMs = Date.now() - new Date(iso).getTime();
|
|
115
|
+
if (diffMs < 0) return "just now";
|
|
116
|
+
const diffMin = Math.floor(diffMs / 6e4);
|
|
117
|
+
if (diffMin < 1) return "now";
|
|
118
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
119
|
+
const diffH = Math.floor(diffMin / 60);
|
|
120
|
+
if (diffH < 24) return `${diffH}h ago`;
|
|
121
|
+
return `${Math.floor(diffH / 24)}d ago`;
|
|
122
|
+
} catch {
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function healthColor(score) {
|
|
127
|
+
if (score >= 80) return "var(--color-status-positive)";
|
|
128
|
+
if (score >= 50) return "var(--color-status-caution)";
|
|
129
|
+
return "var(--color-status-negative)";
|
|
130
|
+
}
|
|
131
|
+
function BotActivity({ packName, botId }) {
|
|
132
|
+
const [history, setHistory] = useState([]);
|
|
133
|
+
const [queue, setQueue] = useState([]);
|
|
134
|
+
const [insights, setInsights] = useState(null);
|
|
135
|
+
const [status, setStatus] = useState(null);
|
|
136
|
+
const [loading, setLoading] = useState(true);
|
|
137
|
+
const timerRef = useRef();
|
|
138
|
+
const fetchAll = useCallback(async () => {
|
|
139
|
+
const [hist, q, ins, st] = await Promise.allSettled([
|
|
140
|
+
callTool("fw_weaver_history", { limit: 15 }),
|
|
141
|
+
callTool("fw_weaver_queue", { action: "list" }),
|
|
142
|
+
callTool("fw_weaver_insights"),
|
|
143
|
+
callTool("fw_weaver_status")
|
|
144
|
+
]);
|
|
145
|
+
if (hist.status === "fulfilled") setHistory(Array.isArray(hist.value) ? hist.value : []);
|
|
146
|
+
if (q.status === "fulfilled") setQueue(Array.isArray(q.value) ? q.value : []);
|
|
147
|
+
if (ins.status === "fulfilled") setInsights(ins.value);
|
|
148
|
+
if (st.status === "fulfilled") setStatus(st.value);
|
|
149
|
+
setLoading(false);
|
|
150
|
+
}, []);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
fetchAll();
|
|
153
|
+
timerRef.current = setInterval(fetchAll, REFRESH_INTERVAL);
|
|
154
|
+
return () => clearInterval(timerRef.current);
|
|
155
|
+
}, [fetchAll]);
|
|
156
|
+
const openWorkspace = useCallback((runId) => {
|
|
157
|
+
window.dispatchEvent(new CustomEvent("fw:open-bot-workspace", {
|
|
158
|
+
detail: { runId, botId }
|
|
159
|
+
}));
|
|
160
|
+
}, [botId]);
|
|
161
|
+
const handleRemoveQueueItem = useCallback(async (id) => {
|
|
162
|
+
await callTool("fw_weaver_queue", { action: "remove", id });
|
|
163
|
+
setQueue((prev) => prev.filter((q) => q.id !== id));
|
|
164
|
+
}, []);
|
|
165
|
+
const handlePause = useCallback(async (e) => {
|
|
166
|
+
e.stopPropagation();
|
|
167
|
+
await callTool("fw_weaver_steer", { command: "pause" });
|
|
168
|
+
}, []);
|
|
169
|
+
const handleStop = useCallback(async (e) => {
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
await callTool("fw_weaver_steer", { command: "cancel" });
|
|
172
|
+
}, []);
|
|
173
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyState, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSpinner, { size: "small" }) });
|
|
174
|
+
const isRunning = status?.state && status.state !== "idle" && status.state !== "no active session";
|
|
175
|
+
const recentRuns = history.slice(0, 15);
|
|
176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Container, { children: [
|
|
177
|
+
isRunning && status && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SectionHeader, { children: [
|
|
179
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: "playArrow", size: 12, color: "color-brand-main" }),
|
|
180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", style: { textTransform: "uppercase", letterSpacing: "0.04em" }, children: "Running" })
|
|
181
|
+
] }),
|
|
182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RunningCard, { onClick: () => openWorkspace(), children: [
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RunningHeader, { children: [
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "info", children: status.state }),
|
|
185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RunningControls, { children: [
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "pause", size: "xs", variant: "clear", onClick: handlePause, title: "Pause" }),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "stop", size: "xs", variant: "clear", onClick: handleStop, title: "Stop" })
|
|
188
|
+
] })
|
|
189
|
+
] }),
|
|
190
|
+
status.currentTask && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-medium", style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: status.currentTask })
|
|
191
|
+
] })
|
|
192
|
+
] }),
|
|
193
|
+
queue.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
194
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SectionHeader, { children: [
|
|
195
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: "pendingActions", size: 12, color: "color-text-subtle" }),
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", style: { textTransform: "uppercase", letterSpacing: "0.04em" }, children: [
|
|
197
|
+
"Queued (",
|
|
198
|
+
queue.length,
|
|
199
|
+
")"
|
|
200
|
+
] })
|
|
201
|
+
] }),
|
|
202
|
+
queue.map((q) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(QueueItem, { children: [
|
|
203
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: "pending", size: 14, color: "color-text-subtle" }),
|
|
204
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-medium", style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: q.instruction }),
|
|
205
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "close", size: "xs", variant: "clear", onClick: () => handleRemoveQueueItem(q.id), title: "Remove" })
|
|
206
|
+
] }, q.id))
|
|
207
|
+
] }),
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SectionHeader, { children: [
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: "scheduled", size: 12, color: "color-text-subtle" }),
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", style: { textTransform: "uppercase", letterSpacing: "0.04em" }, children: "Recent" }),
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1 } }),
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "refresh", size: "xs", variant: "clear", onClick: fetchAll, title: "Refresh" })
|
|
213
|
+
] }),
|
|
214
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollArea, { style: { flex: 1 }, children: recentRuns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyState, { children: [
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: "info", size: 20, color: "color-text-subtle" }),
|
|
216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-subtle", children: "No recent runs. Ask Weaver to run a bot." })
|
|
217
|
+
] }) : recentRuns.map((run, i) => {
|
|
218
|
+
const oc = outcomeConfig[run.outcome] ?? { icon: "help", color: "color-text-subtle" };
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RunRow, { onClick: () => openWorkspace(run.id), children: [
|
|
220
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: oc.icon, size: 14, color: oc.color }),
|
|
221
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-medium", children: run.summary ?? run.workflowFile ?? run.outcome }) }),
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", style: { fontFamily: "var(--typography-family-mono)", flexShrink: 0 }, children: formatTime(run.startedAt) })
|
|
223
|
+
] }, run.id ?? i);
|
|
224
|
+
}) }),
|
|
225
|
+
insights && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiBar, { children: [
|
|
226
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", children: [
|
|
227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: healthColor(insights.health?.overall ?? 0), fontWeight: 600 }, children: insights.health?.overall ?? 0 }),
|
|
228
|
+
" ",
|
|
229
|
+
"health"
|
|
230
|
+
] }),
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", children: [
|
|
232
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontWeight: 600 }, children: [
|
|
233
|
+
"P",
|
|
234
|
+
insights.trust?.phase ?? "?"
|
|
235
|
+
] }),
|
|
236
|
+
" ",
|
|
237
|
+
"trust"
|
|
238
|
+
] }),
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", children: [
|
|
240
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontWeight: 600 }, children: [
|
|
241
|
+
"$",
|
|
242
|
+
(insights.cost?.last7Days ?? 0).toFixed(2)
|
|
243
|
+
] }),
|
|
244
|
+
"/7d"
|
|
245
|
+
] })
|
|
246
|
+
] })
|
|
247
|
+
] });
|
|
248
|
+
}
|
|
249
|
+
module.exports = BotActivity;
|
package/dist/ui/bot-config.js
CHANGED
|
@@ -5,38 +5,59 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
5
5
|
var React = require("react");
|
|
6
6
|
var { useState, useEffect, useCallback } = React;
|
|
7
7
|
var { Typography } = require("@fw/plugin-ui-kit");
|
|
8
|
-
var { Icon } = require("@fw/plugin-ui-kit");
|
|
9
|
-
var { KeyValueRow } = require("@fw/plugin-ui-kit");
|
|
10
8
|
var { CollapsibleSection } = require("@fw/plugin-ui-kit");
|
|
11
9
|
var { LoadingSpinner } = require("@fw/plugin-ui-kit");
|
|
12
10
|
var { Banner } = require("@fw/plugin-ui-kit");
|
|
13
|
-
var { Badge } = require("@fw/plugin-ui-kit");
|
|
14
11
|
var { IconButton } = require("@fw/plugin-ui-kit");
|
|
15
|
-
var
|
|
12
|
+
var { Badge } = require("@fw/plugin-ui-kit");
|
|
13
|
+
var { styled } = require("@fw/plugin-theme");
|
|
14
|
+
var BASE = "/api/pack-tool/@synergenius/flow-weaver-pack-weaver";
|
|
15
|
+
var ConfigRow = styled.div({
|
|
16
|
+
display: "flex",
|
|
17
|
+
justifyContent: "space-between",
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
padding: "6px 0",
|
|
20
|
+
borderBottom: "1px solid $color-border-default",
|
|
21
|
+
"&:last-of-type": { borderBottom: "none" }
|
|
22
|
+
});
|
|
23
|
+
var Footer = styled.div({
|
|
24
|
+
display: "flex",
|
|
25
|
+
justifyContent: "space-between",
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
marginTop: "10px",
|
|
28
|
+
paddingTop: "6px",
|
|
29
|
+
borderTop: "1px solid $color-border-default"
|
|
30
|
+
});
|
|
31
|
+
function callTool(tool, body = {}) {
|
|
32
|
+
return fetch(`${BASE}/${tool}`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
credentials: "include"
|
|
37
|
+
}).then(async (res) => {
|
|
38
|
+
if (!res.ok) throw new Error("Request failed");
|
|
39
|
+
const json = await res.json();
|
|
40
|
+
if (json.isError) throw new Error(json.result);
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(json.result);
|
|
43
|
+
} catch {
|
|
44
|
+
return json.result;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
16
48
|
function BotConfig({ packName, botId }) {
|
|
17
|
-
const [
|
|
49
|
+
const [insights, setInsights] = useState(null);
|
|
50
|
+
const [providers, setProviders] = useState([]);
|
|
18
51
|
const [error, setError] = useState(null);
|
|
19
52
|
const [loading, setLoading] = useState(true);
|
|
20
53
|
const fetchData = useCallback(async () => {
|
|
21
54
|
try {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!res.ok) {
|
|
29
|
-
setError("Failed to fetch");
|
|
30
|
-
setLoading(false);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const json = await res.json();
|
|
34
|
-
if (json.isError) {
|
|
35
|
-
setError(json.result);
|
|
36
|
-
setLoading(false);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
setData(JSON.parse(json.result));
|
|
55
|
+
const [ins, provs] = await Promise.allSettled([
|
|
56
|
+
callTool("fw_weaver_insights"),
|
|
57
|
+
callTool("fw_weaver_providers")
|
|
58
|
+
]);
|
|
59
|
+
if (ins.status === "fulfilled") setInsights(ins.value);
|
|
60
|
+
if (provs.status === "fulfilled" && Array.isArray(provs.value)) setProviders(provs.value);
|
|
40
61
|
setError(null);
|
|
41
62
|
} catch (e) {
|
|
42
63
|
setError(e.message ?? "Failed to load");
|
|
@@ -47,21 +68,44 @@ function BotConfig({ packName, botId }) {
|
|
|
47
68
|
useEffect(() => {
|
|
48
69
|
fetchData();
|
|
49
70
|
}, [fetchData]);
|
|
50
|
-
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSpinner, { size: "small" });
|
|
51
|
-
if (error) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "8px 12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Banner, { status: "danger", size: "small", children: error }) });
|
|
52
|
-
const bot =
|
|
53
|
-
const
|
|
71
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 16, display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSpinner, { size: "small" }) });
|
|
72
|
+
if (error && !insights) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "8px 12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Banner, { status: "danger", size: "small", children: error }) });
|
|
73
|
+
const bot = insights?.bots?.find((b) => b.name === botId) ?? insights?.bots?.[0];
|
|
74
|
+
const configuredProvider = bot?.provider;
|
|
75
|
+
const availableProvider = providers.find((p) => p.envVarsSet);
|
|
76
|
+
const activeProvider = configuredProvider || availableProvider?.name;
|
|
77
|
+
const isAutoDetected = !configuredProvider && !!availableProvider;
|
|
54
78
|
const approval = bot?.approvalMode ?? "auto";
|
|
55
|
-
const
|
|
79
|
+
const trustPhase = insights?.trust?.phase ?? 1;
|
|
80
|
+
const trustScore = insights?.trust?.score ?? 0;
|
|
81
|
+
const noProvider = !activeProvider;
|
|
56
82
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleSection, { title: "Configuration", defaultExpanded: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "4px 12px 12px" }, children: [
|
|
57
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ConfigRow, { children: [
|
|
84
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", children: "Provider" }),
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
86
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-high", children: activeProvider ?? "None detected" }),
|
|
87
|
+
isAutoDetected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "default", children: "auto" })
|
|
88
|
+
] })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ConfigRow, { children: [
|
|
91
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", children: "Approval" }),
|
|
92
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-high", children: approval })
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ConfigRow, { children: [
|
|
95
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", children: "Trust" }),
|
|
96
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Typography, { variant: "caption-regular", color: "color-text-high", children: [
|
|
97
|
+
"P",
|
|
98
|
+
trustPhase,
|
|
99
|
+
" (",
|
|
100
|
+
trustScore,
|
|
101
|
+
"/100)"
|
|
102
|
+
] })
|
|
63
103
|
] }),
|
|
64
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 10
|
|
104
|
+
noProvider && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Banner, { status: "warning", size: "small", children: "No AI provider detected. Set ANTHROPIC_API_KEY or install Claude CLI." }) }),
|
|
105
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Footer, { children: [
|
|
106
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", children: "Customize via .weaver.json" }),
|
|
107
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "refresh", size: "xs", variant: "clear", onClick: fetchData, title: "Refresh" })
|
|
108
|
+
] })
|
|
65
109
|
] }) });
|
|
66
110
|
}
|
|
67
111
|
module.exports = BotConfig;
|
package/flowweaver.manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 2,
|
|
3
3
|
"name": "@synergenius/flow-weaver-pack-weaver",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.21",
|
|
5
5
|
"description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
|
|
6
6
|
"engineVersion": ">=0.22.10",
|
|
7
7
|
"categories": [
|
|
@@ -1074,6 +1074,7 @@
|
|
|
1074
1074
|
],
|
|
1075
1075
|
"uiContributions": [],
|
|
1076
1076
|
"botUI": {
|
|
1077
|
+
"activity": "dist/ui/bot-activity.js",
|
|
1077
1078
|
"config": "dist/ui/bot-config.js",
|
|
1078
1079
|
"status": "dist/ui/bot-status.js",
|
|
1079
1080
|
"dashboard": "dist/ui/bot-dashboard.js"
|
package/package.json
CHANGED
|
@@ -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,19 +1,42 @@
|
|
|
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
|
-
const { KeyValueRow } = require('@fw/plugin-ui-kit');
|
|
10
8
|
const { CollapsibleSection } = require('@fw/plugin-ui-kit');
|
|
11
9
|
const { LoadingSpinner } = require('@fw/plugin-ui-kit');
|
|
12
10
|
const { Banner } = require('@fw/plugin-ui-kit');
|
|
13
|
-
const { Badge } = require('@fw/plugin-ui-kit');
|
|
14
11
|
const { IconButton } = require('@fw/plugin-ui-kit');
|
|
12
|
+
const { Badge } = require('@fw/plugin-ui-kit');
|
|
13
|
+
const { styled } = require('@fw/plugin-theme');
|
|
14
|
+
|
|
15
|
+
const BASE = '/api/pack-tool/@synergenius/flow-weaver-pack-weaver';
|
|
16
|
+
|
|
17
|
+
const ConfigRow = styled.div({
|
|
18
|
+
display: 'flex',
|
|
19
|
+
justifyContent: 'space-between',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
padding: '6px 0',
|
|
22
|
+
borderBottom: '1px solid $color-border-default',
|
|
23
|
+
'&:last-of-type': { borderBottom: 'none' },
|
|
24
|
+
});
|
|
15
25
|
|
|
16
|
-
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
|
+
}
|
|
17
40
|
|
|
18
41
|
interface InsightsResult {
|
|
19
42
|
health: { overall: number };
|
|
@@ -21,23 +44,34 @@ interface InsightsResult {
|
|
|
21
44
|
trust: { phase: number; score: number };
|
|
22
45
|
}
|
|
23
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
|
+
|
|
24
61
|
function BotConfig({ packName, botId }: { packName: string; botId: string }) {
|
|
25
|
-
const [
|
|
62
|
+
const [insights, setInsights] = useState<InsightsResult | null>(null);
|
|
63
|
+
const [providers, setProviders] = useState<ProviderInfo[]>([]);
|
|
26
64
|
const [error, setError] = useState<string | null>(null);
|
|
27
65
|
const [loading, setLoading] = useState(true);
|
|
28
66
|
|
|
29
67
|
const fetchData = useCallback(async () => {
|
|
30
68
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!res.ok) { setError('Failed to fetch'); setLoading(false); return; }
|
|
38
|
-
const json = await res.json();
|
|
39
|
-
if (json.isError) { setError(json.result); setLoading(false); return; }
|
|
40
|
-
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);
|
|
41
75
|
setError(null);
|
|
42
76
|
} catch (e: any) {
|
|
43
77
|
setError(e.message ?? 'Failed to load');
|
|
@@ -48,40 +82,66 @@ function BotConfig({ packName, botId }: { packName: string; botId: string }) {
|
|
|
48
82
|
|
|
49
83
|
useEffect(() => { fetchData(); }, [fetchData]);
|
|
50
84
|
|
|
51
|
-
if (loading) return
|
|
85
|
+
if (loading) return (
|
|
86
|
+
<div style={{ padding: 16, display: 'flex', justifyContent: 'center' }}>
|
|
87
|
+
<LoadingSpinner size="small" />
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
52
90
|
|
|
53
|
-
if (error) return (
|
|
91
|
+
if (error && !insights) return (
|
|
54
92
|
<div style={{ padding: '8px 12px' }}>
|
|
55
93
|
<Banner status="danger" size="small">{error}</Banner>
|
|
56
94
|
</div>
|
|
57
95
|
);
|
|
58
96
|
|
|
59
|
-
|
|
60
|
-
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;
|
|
61
103
|
const approval = bot?.approvalMode ?? 'auto';
|
|
62
|
-
const
|
|
104
|
+
const trustPhase = insights?.trust?.phase ?? 1;
|
|
105
|
+
const trustScore = insights?.trust?.score ?? 0;
|
|
106
|
+
const noProvider = !activeProvider;
|
|
63
107
|
|
|
64
108
|
return (
|
|
65
109
|
<CollapsibleSection title="Configuration" defaultExpanded>
|
|
66
110
|
<div style={{ padding: '4px 12px 12px' }}>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<div style={{ marginTop: 12 }}>
|
|
73
|
-
<Banner status="info" size="small">
|
|
74
|
-
No .weaver.json detected. Create one to configure providers and approval modes.
|
|
75
|
-
</Banner>
|
|
76
|
-
<Typography variant="caption-regular" color="color-text-subtle" style={{ marginTop: 6, fontFamily: 'var(--typography-family-mono)' }}>
|
|
77
|
-
npx flow-weaver weaver init
|
|
111
|
+
<ConfigRow>
|
|
112
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle">Provider</Typography>
|
|
113
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
114
|
+
<Typography variant="caption-regular" color="color-text-high">
|
|
115
|
+
{activeProvider ?? 'None detected'}
|
|
78
116
|
</Typography>
|
|
117
|
+
{isAutoDetected && <Badge variant="default">auto</Badge>}
|
|
118
|
+
</span>
|
|
119
|
+
</ConfigRow>
|
|
120
|
+
<ConfigRow>
|
|
121
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle">Approval</Typography>
|
|
122
|
+
<Typography variant="caption-regular" color="color-text-high">{approval}</Typography>
|
|
123
|
+
</ConfigRow>
|
|
124
|
+
<ConfigRow>
|
|
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>
|
|
129
|
+
</ConfigRow>
|
|
130
|
+
|
|
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.
|
|
135
|
+
</Banner>
|
|
79
136
|
</div>
|
|
80
137
|
)}
|
|
81
138
|
|
|
82
|
-
<
|
|
139
|
+
<Footer>
|
|
140
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
141
|
+
Customize via .weaver.json
|
|
142
|
+
</Typography>
|
|
83
143
|
<IconButton icon="refresh" size="xs" variant="clear" onClick={fetchData} title="Refresh" />
|
|
84
|
-
</
|
|
144
|
+
</Footer>
|
|
85
145
|
</div>
|
|
86
146
|
</CollapsibleSection>
|
|
87
147
|
);
|