@synergenius/flow-weaver-pack-weaver 0.9.17 → 0.9.18
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-config.js +67 -0
- package/dist/ui/bot-dashboard.js +183 -0
- package/dist/ui/bot-status.js +94 -0
- package/flowweaver.manifest.json +7 -17
- package/package.json +1 -1
- package/src/ui/bot-config.tsx +90 -0
- package/src/ui/bot-dashboard.tsx +257 -0
- package/src/ui/bot-status.tsx +120 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/ui/bot-config.tsx
|
|
4
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
var React = require("react");
|
|
6
|
+
var { useState, useEffect, useCallback } = React;
|
|
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
|
+
var { CollapsibleSection } = require("@fw/plugin-ui-kit");
|
|
11
|
+
var { LoadingSpinner } = require("@fw/plugin-ui-kit");
|
|
12
|
+
var { Banner } = require("@fw/plugin-ui-kit");
|
|
13
|
+
var { Badge } = require("@fw/plugin-ui-kit");
|
|
14
|
+
var { IconButton } = require("@fw/plugin-ui-kit");
|
|
15
|
+
var TOOL_URL = "/api/pack-tool/@synergenius/flow-weaver-pack-weaver/fw_weaver_insights";
|
|
16
|
+
function BotConfig({ packName, botId }) {
|
|
17
|
+
const [data, setData] = useState(null);
|
|
18
|
+
const [error, setError] = useState(null);
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const fetchData = useCallback(async () => {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(TOOL_URL, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
body: JSON.stringify({}),
|
|
26
|
+
credentials: "include"
|
|
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));
|
|
40
|
+
setError(null);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
setError(e.message ?? "Failed to load");
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
fetchData();
|
|
49
|
+
}, [fetchData]);
|
|
50
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSpinner, { size: "sm" });
|
|
51
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "8px 12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Banner, { variant: "error", size: "compact", children: error }) });
|
|
52
|
+
const bot = data?.bots?.find((b) => b.name === botId) ?? data?.bots?.[0];
|
|
53
|
+
const provider = bot?.provider ?? "Not configured";
|
|
54
|
+
const approval = bot?.approvalMode ?? "auto";
|
|
55
|
+
const hasBots = (data?.bots?.length ?? 0) > 0;
|
|
56
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleSection, { title: "Configuration", defaultOpen: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "4px 12px 12px" }, children: [
|
|
57
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeyValueRow, { label: "Provider", value: provider }),
|
|
58
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeyValueRow, { label: "Approval", value: approval }),
|
|
59
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeyValueRow, { label: "Trust Phase", value: `P${data?.trust?.phase ?? "?"} (${data?.trust?.score ?? 0}/100)` }),
|
|
60
|
+
!hasBots && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 12 }, children: [
|
|
61
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Banner, { variant: "info", size: "compact", children: "No .weaver.json detected. Create one to configure providers and approval modes." }),
|
|
62
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-subtle", style: { marginTop: 6, fontFamily: "var(--typography-family-mono)" }, children: "npx flow-weaver weaver init" })
|
|
63
|
+
] }),
|
|
64
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 10, display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "refresh", size: "xs", variant: "clear", onClick: fetchData, title: "Refresh" }) })
|
|
65
|
+
] }) });
|
|
66
|
+
}
|
|
67
|
+
module.exports = BotConfig;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/ui/bot-dashboard.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 { Badge } = require("@fw/plugin-ui-kit");
|
|
10
|
+
var { CollapsibleSection } = require("@fw/plugin-ui-kit");
|
|
11
|
+
var { LoadingSpinner } = require("@fw/plugin-ui-kit");
|
|
12
|
+
var { Banner } = require("@fw/plugin-ui-kit");
|
|
13
|
+
var { IconButton } = require("@fw/plugin-ui-kit");
|
|
14
|
+
var { styled } = require("@fw/plugin-theme");
|
|
15
|
+
var BASE = "/api/pack-tool/@synergenius/flow-weaver-pack-weaver";
|
|
16
|
+
function callTool(tool, body = {}) {
|
|
17
|
+
return fetch(`${BASE}/${tool}`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify(body),
|
|
21
|
+
credentials: "include"
|
|
22
|
+
}).then(async (res) => {
|
|
23
|
+
if (!res.ok) throw new Error("Request failed");
|
|
24
|
+
const json = await res.json();
|
|
25
|
+
if (json.isError) throw new Error(json.result);
|
|
26
|
+
return JSON.parse(json.result);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
var KpiRow = styled.div({
|
|
30
|
+
display: "flex",
|
|
31
|
+
gap: "8px",
|
|
32
|
+
marginBottom: "12px"
|
|
33
|
+
});
|
|
34
|
+
var KpiCard = styled.div({
|
|
35
|
+
flex: 1,
|
|
36
|
+
padding: "8px 10px",
|
|
37
|
+
borderRadius: "6px",
|
|
38
|
+
border: "1px solid $color-border-default",
|
|
39
|
+
backgroundColor: "$color-surface-elevated"
|
|
40
|
+
});
|
|
41
|
+
var KpiValue = styled.div({
|
|
42
|
+
fontSize: "20px",
|
|
43
|
+
fontWeight: 700,
|
|
44
|
+
lineHeight: 1.2
|
|
45
|
+
});
|
|
46
|
+
var KpiLabel = styled.div({
|
|
47
|
+
fontSize: "10px",
|
|
48
|
+
opacity: 0.5,
|
|
49
|
+
textTransform: "uppercase",
|
|
50
|
+
letterSpacing: "0.04em",
|
|
51
|
+
marginTop: "2px"
|
|
52
|
+
});
|
|
53
|
+
var InsightRow = styled.div({
|
|
54
|
+
display: "flex",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
gap: "8px",
|
|
57
|
+
padding: "5px 0",
|
|
58
|
+
borderBottom: "1px solid $color-border-default"
|
|
59
|
+
});
|
|
60
|
+
var RunRow = styled.div({
|
|
61
|
+
display: "flex",
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
gap: "8px",
|
|
64
|
+
padding: "5px 0",
|
|
65
|
+
borderBottom: "1px solid $color-border-default",
|
|
66
|
+
fontSize: "12px"
|
|
67
|
+
});
|
|
68
|
+
function healthColor(score) {
|
|
69
|
+
if (score >= 80) return "var(--color-status-positive)";
|
|
70
|
+
if (score >= 50) return "var(--color-status-caution)";
|
|
71
|
+
return "var(--color-status-negative)";
|
|
72
|
+
}
|
|
73
|
+
var severityToVariant = {
|
|
74
|
+
critical: "negative",
|
|
75
|
+
warning: "caution",
|
|
76
|
+
info: "neutral"
|
|
77
|
+
};
|
|
78
|
+
var outcomeToIcon = {
|
|
79
|
+
completed: { name: "taskAlt", color: "color-status-positive" },
|
|
80
|
+
success: { name: "taskAlt", color: "color-status-positive" },
|
|
81
|
+
failed: { name: "error", color: "color-status-negative" },
|
|
82
|
+
error: { name: "error", color: "color-status-negative" },
|
|
83
|
+
skipped: { name: "minus", color: "color-text-subtle" }
|
|
84
|
+
};
|
|
85
|
+
function formatTime(iso) {
|
|
86
|
+
try {
|
|
87
|
+
const diffMs = Date.now() - new Date(iso).getTime();
|
|
88
|
+
const diffMin = Math.floor(diffMs / 6e4);
|
|
89
|
+
if (diffMin < 1) return "now";
|
|
90
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
91
|
+
const diffH = Math.floor(diffMin / 60);
|
|
92
|
+
if (diffH < 24) return `${diffH}h ago`;
|
|
93
|
+
return `${Math.floor(diffH / 24)}d ago`;
|
|
94
|
+
} catch {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function BotDashboard({ packName, botId }) {
|
|
99
|
+
const [insights, setInsights] = useState(null);
|
|
100
|
+
const [history, setHistory] = useState([]);
|
|
101
|
+
const [error, setError] = useState(null);
|
|
102
|
+
const [loading, setLoading] = useState(true);
|
|
103
|
+
const timerRef = useRef();
|
|
104
|
+
const fetchData = useCallback(async () => {
|
|
105
|
+
try {
|
|
106
|
+
const [ins, hist] = await Promise.allSettled([
|
|
107
|
+
callTool("fw_weaver_insights"),
|
|
108
|
+
callTool("fw_weaver_history", { limit: 5 })
|
|
109
|
+
]);
|
|
110
|
+
if (ins.status === "fulfilled") {
|
|
111
|
+
setInsights(ins.value);
|
|
112
|
+
setError(null);
|
|
113
|
+
} else setError(ins.reason?.message ?? "Failed to load insights");
|
|
114
|
+
if (hist.status === "fulfilled") {
|
|
115
|
+
setHistory(Array.isArray(hist.value) ? hist.value : []);
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
setError(e.message ?? "Failed to load");
|
|
119
|
+
} finally {
|
|
120
|
+
setLoading(false);
|
|
121
|
+
}
|
|
122
|
+
}, []);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
fetchData();
|
|
125
|
+
timerRef.current = setInterval(fetchData, 3e4);
|
|
126
|
+
return () => clearInterval(timerRef.current);
|
|
127
|
+
}, [fetchData]);
|
|
128
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 16, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSpinner, { size: "sm" }) });
|
|
129
|
+
if (error && !insights) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Banner, { variant: "error", size: "compact", children: error }) });
|
|
130
|
+
if (!insights) return null;
|
|
131
|
+
const healthScore = insights.health?.overall ?? 0;
|
|
132
|
+
const trust = insights.trust ?? { phase: 1, score: 0 };
|
|
133
|
+
const cost = insights.cost ?? { last7Days: 0, trend: "stable" };
|
|
134
|
+
const topInsights = (insights.insights ?? []).slice(0, 3);
|
|
135
|
+
const recentRuns = history.slice(0, 5);
|
|
136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleSection, { title: "Dashboard", defaultOpen: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "4px 12px 12px" }, children: [
|
|
137
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiRow, { children: [
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiCard, { children: [
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KpiValue, { style: { color: healthColor(healthScore) }, children: healthScore }),
|
|
140
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KpiLabel, { children: "Health" })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiCard, { children: [
|
|
143
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiValue, { children: [
|
|
144
|
+
"P",
|
|
145
|
+
trust.phase
|
|
146
|
+
] }),
|
|
147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiLabel, { children: [
|
|
148
|
+
"Trust ",
|
|
149
|
+
trust.score,
|
|
150
|
+
"/100"
|
|
151
|
+
] })
|
|
152
|
+
] }),
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiCard, { children: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(KpiValue, { children: [
|
|
155
|
+
"$",
|
|
156
|
+
cost.last7Days.toFixed(2)
|
|
157
|
+
] }),
|
|
158
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KpiLabel, { children: "7-day cost" })
|
|
159
|
+
] })
|
|
160
|
+
] }),
|
|
161
|
+
topInsights.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", style: { marginBottom: 6, textTransform: "uppercase", letterSpacing: "0.04em" }, children: "Insights" }),
|
|
163
|
+
topInsights.map((ins, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InsightRow, { children: [
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: severityToVariant[ins.severity] ?? "neutral", size: "sm", children: ins.severity }),
|
|
165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-medium", style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: ins.title })
|
|
166
|
+
] }, i))
|
|
167
|
+
] }),
|
|
168
|
+
recentRuns.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 8 }, children: [
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-bold", color: "color-text-subtle", style: { marginBottom: 6, textTransform: "uppercase", letterSpacing: "0.04em" }, children: "Recent Runs" }),
|
|
170
|
+
recentRuns.map((run, i) => {
|
|
171
|
+
const oc = outcomeToIcon[run.outcome] ?? { name: "help", color: "color-text-subtle" };
|
|
172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RunRow, { children: [
|
|
173
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: oc.name, size: 14, color: oc.color }),
|
|
174
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-medium", style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: run.summary ?? run.workflowFile ?? run.outcome }),
|
|
175
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", style: { fontFamily: "var(--typography-family-mono)" }, children: formatTime(run.startedAt) })
|
|
176
|
+
] }, run.id ?? i);
|
|
177
|
+
})
|
|
178
|
+
] }),
|
|
179
|
+
topInsights.length === 0 && recentRuns.length === 0 && healthScore === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-subtle", style: { textAlign: "center", padding: 20 }, children: "No data yet. Run a bot to see dashboard metrics." }),
|
|
180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 4 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, { icon: "refresh", size: "xs", variant: "clear", onClick: fetchData, title: "Refresh" }) })
|
|
181
|
+
] }) });
|
|
182
|
+
}
|
|
183
|
+
module.exports = BotDashboard;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/ui/bot-status.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 { StatusDot } = require("@fw/plugin-ui-kit");
|
|
9
|
+
var { Icon } = require("@fw/plugin-ui-kit");
|
|
10
|
+
var TOOL_URL = "/api/pack-tool/@synergenius/flow-weaver-pack-weaver/fw_weaver_status";
|
|
11
|
+
var stateToDot = {
|
|
12
|
+
idle: "neutral",
|
|
13
|
+
planning: "caution",
|
|
14
|
+
executing: "info",
|
|
15
|
+
reviewing: "caution",
|
|
16
|
+
completed: "positive",
|
|
17
|
+
failed: "negative",
|
|
18
|
+
paused: "neutral"
|
|
19
|
+
};
|
|
20
|
+
var stateToIcon = {
|
|
21
|
+
idle: "pending",
|
|
22
|
+
planning: "psychology",
|
|
23
|
+
executing: "running",
|
|
24
|
+
reviewing: "rateReview",
|
|
25
|
+
completed: "taskAlt",
|
|
26
|
+
failed: "error",
|
|
27
|
+
paused: "pause"
|
|
28
|
+
};
|
|
29
|
+
function BotStatus({ packName, botId }) {
|
|
30
|
+
const [data, setData] = useState(null);
|
|
31
|
+
const [error, setError] = useState(null);
|
|
32
|
+
const timerRef = useRef();
|
|
33
|
+
const fetchData = useCallback(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(TOOL_URL, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({}),
|
|
39
|
+
credentials: "include"
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
setError("Failed to fetch");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const json = await res.json();
|
|
46
|
+
if (json.isError) {
|
|
47
|
+
setError(json.result);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
setData(JSON.parse(json.result));
|
|
51
|
+
setError(null);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
setError(e.message ?? "Failed to load");
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
fetchData();
|
|
58
|
+
timerRef.current = setInterval(fetchData, 5e3);
|
|
59
|
+
return () => clearInterval(timerRef.current);
|
|
60
|
+
}, [fetchData]);
|
|
61
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "6px 12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-status-negative", children: error }) });
|
|
62
|
+
if (!data) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "6px 12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-regular", color: "color-text-subtle", children: "Loading status..." }) });
|
|
63
|
+
const state = data.state ?? "idle";
|
|
64
|
+
const dotStatus = stateToDot[state] ?? "neutral";
|
|
65
|
+
const iconName = stateToIcon[state] ?? "pending";
|
|
66
|
+
const completed = data.completedTasks ?? 0;
|
|
67
|
+
const total = data.totalTasks ?? 0;
|
|
68
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
|
|
69
|
+
padding: "8px 12px",
|
|
70
|
+
display: "flex",
|
|
71
|
+
alignItems: "center",
|
|
72
|
+
gap: 8,
|
|
73
|
+
borderBottom: "1px solid var(--color-border-default)",
|
|
74
|
+
background: "var(--color-surface-elevated)"
|
|
75
|
+
}, children: [
|
|
76
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: iconName, size: 14, color: `color-status-${dotStatus === "neutral" ? "info" : dotStatus}` }),
|
|
77
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Typography, { variant: "caption-bold", style: { textTransform: "capitalize" }, children: state }),
|
|
78
|
+
data.currentTask && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
79
|
+
Typography,
|
|
80
|
+
{
|
|
81
|
+
variant: "caption-regular",
|
|
82
|
+
color: "color-text-medium",
|
|
83
|
+
style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
84
|
+
children: data.currentTask
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
total > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Typography, { variant: "smallCaption-regular", color: "color-text-subtle", children: [
|
|
88
|
+
completed,
|
|
89
|
+
"/",
|
|
90
|
+
total
|
|
91
|
+
] })
|
|
92
|
+
] });
|
|
93
|
+
}
|
|
94
|
+
module.exports = BotStatus;
|
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.18",
|
|
5
5
|
"description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
|
|
6
6
|
"engineVersion": ">=0.22.10",
|
|
7
7
|
"categories": [
|
|
@@ -1072,22 +1072,12 @@
|
|
|
1072
1072
|
}
|
|
1073
1073
|
}
|
|
1074
1074
|
],
|
|
1075
|
-
"uiContributions": [
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
"area": "dashboard",
|
|
1082
|
-
"size": "large"
|
|
1083
|
-
},
|
|
1084
|
-
{
|
|
1085
|
-
"type": "panel",
|
|
1086
|
-
"id": "weaver-evolution",
|
|
1087
|
-
"name": "Evolution History",
|
|
1088
|
-
"component": "dist/ui/evolution-panel.js"
|
|
1089
|
-
}
|
|
1090
|
-
],
|
|
1075
|
+
"uiContributions": [],
|
|
1076
|
+
"botUI": {
|
|
1077
|
+
"config": "dist/ui/bot-config.js",
|
|
1078
|
+
"status": "dist/ui/bot-status.js",
|
|
1079
|
+
"dashboard": "dist/ui/bot-dashboard.js"
|
|
1080
|
+
},
|
|
1091
1081
|
"sandboxCapabilities": [
|
|
1092
1082
|
"fetch:hooks.slack.com",
|
|
1093
1083
|
"fetch:discord.com",
|
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaver Bot Config Panel — read-only configuration summary.
|
|
3
|
+
* Runs in the platform bot-panel sandbox.
|
|
4
|
+
*/
|
|
5
|
+
const React = require('react');
|
|
6
|
+
const { useState, useEffect, useCallback } = React;
|
|
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
|
+
const { CollapsibleSection } = require('@fw/plugin-ui-kit');
|
|
11
|
+
const { LoadingSpinner } = require('@fw/plugin-ui-kit');
|
|
12
|
+
const { Banner } = require('@fw/plugin-ui-kit');
|
|
13
|
+
const { Badge } = require('@fw/plugin-ui-kit');
|
|
14
|
+
const { IconButton } = require('@fw/plugin-ui-kit');
|
|
15
|
+
|
|
16
|
+
const TOOL_URL = '/api/pack-tool/@synergenius/flow-weaver-pack-weaver/fw_weaver_insights';
|
|
17
|
+
|
|
18
|
+
interface InsightsResult {
|
|
19
|
+
health: { overall: number };
|
|
20
|
+
bots: Array<{ name: string; provider?: string; approvalMode?: string }>;
|
|
21
|
+
trust: { phase: number; score: number };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function BotConfig({ packName, botId }: { packName: string; botId: string }) {
|
|
25
|
+
const [data, setData] = useState<InsightsResult | null>(null);
|
|
26
|
+
const [error, setError] = useState<string | null>(null);
|
|
27
|
+
const [loading, setLoading] = useState(true);
|
|
28
|
+
|
|
29
|
+
const fetchData = useCallback(async () => {
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(TOOL_URL, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
body: JSON.stringify({}),
|
|
35
|
+
credentials: 'include',
|
|
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));
|
|
41
|
+
setError(null);
|
|
42
|
+
} catch (e: any) {
|
|
43
|
+
setError(e.message ?? 'Failed to load');
|
|
44
|
+
} finally {
|
|
45
|
+
setLoading(false);
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
useEffect(() => { fetchData(); }, [fetchData]);
|
|
50
|
+
|
|
51
|
+
if (loading) return <LoadingSpinner size="sm" />;
|
|
52
|
+
|
|
53
|
+
if (error) return (
|
|
54
|
+
<div style={{ padding: '8px 12px' }}>
|
|
55
|
+
<Banner variant="error" size="compact">{error}</Banner>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const bot = data?.bots?.find(b => b.name === botId) ?? data?.bots?.[0];
|
|
60
|
+
const provider = bot?.provider ?? 'Not configured';
|
|
61
|
+
const approval = bot?.approvalMode ?? 'auto';
|
|
62
|
+
const hasBots = (data?.bots?.length ?? 0) > 0;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<CollapsibleSection title="Configuration" defaultOpen>
|
|
66
|
+
<div style={{ padding: '4px 12px 12px' }}>
|
|
67
|
+
<KeyValueRow label="Provider" value={provider} />
|
|
68
|
+
<KeyValueRow label="Approval" value={approval} />
|
|
69
|
+
<KeyValueRow label="Trust Phase" value={`P${data?.trust?.phase ?? '?'} (${data?.trust?.score ?? 0}/100)`} />
|
|
70
|
+
|
|
71
|
+
{!hasBots && (
|
|
72
|
+
<div style={{ marginTop: 12 }}>
|
|
73
|
+
<Banner variant="info" size="compact">
|
|
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
|
|
78
|
+
</Typography>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<div style={{ marginTop: 10, display: 'flex', justifyContent: 'flex-end' }}>
|
|
83
|
+
<IconButton icon="refresh" size="xs" variant="clear" onClick={fetchData} title="Refresh" />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</CollapsibleSection>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = BotConfig;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaver Bot Dashboard — persistent overview with health, trust, costs, insights, and run history.
|
|
3
|
+
* Runs in the platform bot-panel sandbox. Refreshes every 30 seconds.
|
|
4
|
+
*/
|
|
5
|
+
const React = require('react');
|
|
6
|
+
const { useState, useEffect, useCallback, useRef } = React;
|
|
7
|
+
const { Typography } = require('@fw/plugin-ui-kit');
|
|
8
|
+
const { Icon } = require('@fw/plugin-ui-kit');
|
|
9
|
+
const { Badge } = require('@fw/plugin-ui-kit');
|
|
10
|
+
const { CollapsibleSection } = require('@fw/plugin-ui-kit');
|
|
11
|
+
const { LoadingSpinner } = require('@fw/plugin-ui-kit');
|
|
12
|
+
const { Banner } = require('@fw/plugin-ui-kit');
|
|
13
|
+
const { IconButton } = require('@fw/plugin-ui-kit');
|
|
14
|
+
const { styled } = require('@fw/plugin-theme');
|
|
15
|
+
|
|
16
|
+
const BASE = '/api/pack-tool/@synergenius/flow-weaver-pack-weaver';
|
|
17
|
+
|
|
18
|
+
interface InsightsData {
|
|
19
|
+
health: { overall: number; workflows?: Array<{ file: string; score: number }> };
|
|
20
|
+
bots: Array<{ name: string; successRate: number; totalTasksRun: number }>;
|
|
21
|
+
insights: Array<{ severity: string; title: string; description: string; confidence: number }>;
|
|
22
|
+
cost: { last7Days: number; trend: string };
|
|
23
|
+
trust: { phase: number; score: number };
|
|
24
|
+
evolution?: { totalCycles: number; successRate: number };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RunEntry {
|
|
28
|
+
id?: string;
|
|
29
|
+
outcome: string;
|
|
30
|
+
workflowFile?: string;
|
|
31
|
+
startedAt: string;
|
|
32
|
+
duration?: number;
|
|
33
|
+
summary?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function callTool(tool: string, body: Record<string, unknown> = {}) {
|
|
37
|
+
return fetch(`${BASE}/${tool}`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { 'Content-Type': 'application/json' },
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
credentials: 'include',
|
|
42
|
+
}).then(async res => {
|
|
43
|
+
if (!res.ok) throw new Error('Request failed');
|
|
44
|
+
const json = await res.json();
|
|
45
|
+
if (json.isError) throw new Error(json.result);
|
|
46
|
+
return JSON.parse(json.result);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Styled components ---
|
|
51
|
+
|
|
52
|
+
const KpiRow = styled.div({
|
|
53
|
+
display: 'flex',
|
|
54
|
+
gap: '8px',
|
|
55
|
+
marginBottom: '12px',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const KpiCard = styled.div({
|
|
59
|
+
flex: 1,
|
|
60
|
+
padding: '8px 10px',
|
|
61
|
+
borderRadius: '6px',
|
|
62
|
+
border: '1px solid $color-border-default',
|
|
63
|
+
backgroundColor: '$color-surface-elevated',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const KpiValue = styled.div({
|
|
67
|
+
fontSize: '20px',
|
|
68
|
+
fontWeight: 700,
|
|
69
|
+
lineHeight: 1.2,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const KpiLabel = styled.div({
|
|
73
|
+
fontSize: '10px',
|
|
74
|
+
opacity: 0.5,
|
|
75
|
+
textTransform: 'uppercase',
|
|
76
|
+
letterSpacing: '0.04em',
|
|
77
|
+
marginTop: '2px',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const InsightRow = styled.div({
|
|
81
|
+
display: 'flex',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
gap: '8px',
|
|
84
|
+
padding: '5px 0',
|
|
85
|
+
borderBottom: '1px solid $color-border-default',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const RunRow = styled.div({
|
|
89
|
+
display: 'flex',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
gap: '8px',
|
|
92
|
+
padding: '5px 0',
|
|
93
|
+
borderBottom: '1px solid $color-border-default',
|
|
94
|
+
fontSize: '12px',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// --- Helpers ---
|
|
98
|
+
|
|
99
|
+
type TIconNameType = string;
|
|
100
|
+
|
|
101
|
+
function healthColor(score: number): string {
|
|
102
|
+
if (score >= 80) return 'var(--color-status-positive)';
|
|
103
|
+
if (score >= 50) return 'var(--color-status-caution)';
|
|
104
|
+
return 'var(--color-status-negative)';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const severityToVariant: Record<string, string> = {
|
|
108
|
+
critical: 'negative',
|
|
109
|
+
warning: 'caution',
|
|
110
|
+
info: 'neutral',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const outcomeToIcon: Record<string, { name: TIconNameType; color: string }> = {
|
|
114
|
+
completed: { name: 'taskAlt', color: 'color-status-positive' },
|
|
115
|
+
success: { name: 'taskAlt', color: 'color-status-positive' },
|
|
116
|
+
failed: { name: 'error', color: 'color-status-negative' },
|
|
117
|
+
error: { name: 'error', color: 'color-status-negative' },
|
|
118
|
+
skipped: { name: 'minus', color: 'color-text-subtle' },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
function formatTime(iso: string): string {
|
|
122
|
+
try {
|
|
123
|
+
const diffMs = Date.now() - new Date(iso).getTime();
|
|
124
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
125
|
+
if (diffMin < 1) return 'now';
|
|
126
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
127
|
+
const diffH = Math.floor(diffMin / 60);
|
|
128
|
+
if (diffH < 24) return `${diffH}h ago`;
|
|
129
|
+
return `${Math.floor(diffH / 24)}d ago`;
|
|
130
|
+
} catch { return ''; }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- Component ---
|
|
134
|
+
|
|
135
|
+
function BotDashboard({ packName, botId }: { packName: string; botId: string }) {
|
|
136
|
+
const [insights, setInsights] = useState<InsightsData | null>(null);
|
|
137
|
+
const [history, setHistory] = useState<RunEntry[]>([]);
|
|
138
|
+
const [error, setError] = useState<string | null>(null);
|
|
139
|
+
const [loading, setLoading] = useState(true);
|
|
140
|
+
const timerRef = useRef<ReturnType<typeof setInterval>>();
|
|
141
|
+
|
|
142
|
+
const fetchData = useCallback(async () => {
|
|
143
|
+
try {
|
|
144
|
+
const [ins, hist] = await Promise.allSettled([
|
|
145
|
+
callTool('fw_weaver_insights'),
|
|
146
|
+
callTool('fw_weaver_history', { limit: 5 }),
|
|
147
|
+
]);
|
|
148
|
+
if (ins.status === 'fulfilled') { setInsights(ins.value); setError(null); }
|
|
149
|
+
else setError(ins.reason?.message ?? 'Failed to load insights');
|
|
150
|
+
if (hist.status === 'fulfilled') {
|
|
151
|
+
setHistory(Array.isArray(hist.value) ? hist.value : []);
|
|
152
|
+
}
|
|
153
|
+
} catch (e: any) {
|
|
154
|
+
setError(e.message ?? 'Failed to load');
|
|
155
|
+
} finally {
|
|
156
|
+
setLoading(false);
|
|
157
|
+
}
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
fetchData();
|
|
162
|
+
timerRef.current = setInterval(fetchData, 30_000);
|
|
163
|
+
return () => clearInterval(timerRef.current);
|
|
164
|
+
}, [fetchData]);
|
|
165
|
+
|
|
166
|
+
if (loading) return <div style={{ padding: 16, textAlign: 'center' }}><LoadingSpinner size="sm" /></div>;
|
|
167
|
+
|
|
168
|
+
if (error && !insights) return (
|
|
169
|
+
<div style={{ padding: 12 }}>
|
|
170
|
+
<Banner variant="error" size="compact">{error}</Banner>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!insights) return null;
|
|
175
|
+
|
|
176
|
+
const healthScore = insights.health?.overall ?? 0;
|
|
177
|
+
const trust = insights.trust ?? { phase: 1, score: 0 };
|
|
178
|
+
const cost = insights.cost ?? { last7Days: 0, trend: 'stable' };
|
|
179
|
+
const topInsights = (insights.insights ?? []).slice(0, 3);
|
|
180
|
+
const recentRuns = history.slice(0, 5);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<CollapsibleSection title="Dashboard" defaultOpen>
|
|
184
|
+
<div style={{ padding: '4px 12px 12px' }}>
|
|
185
|
+
{/* KPI cards */}
|
|
186
|
+
<KpiRow>
|
|
187
|
+
<KpiCard>
|
|
188
|
+
<KpiValue style={{ color: healthColor(healthScore) }}>{healthScore}</KpiValue>
|
|
189
|
+
<KpiLabel>Health</KpiLabel>
|
|
190
|
+
</KpiCard>
|
|
191
|
+
<KpiCard>
|
|
192
|
+
<KpiValue>P{trust.phase}</KpiValue>
|
|
193
|
+
<KpiLabel>Trust {trust.score}/100</KpiLabel>
|
|
194
|
+
</KpiCard>
|
|
195
|
+
<KpiCard>
|
|
196
|
+
<KpiValue>${cost.last7Days.toFixed(2)}</KpiValue>
|
|
197
|
+
<KpiLabel>7-day cost</KpiLabel>
|
|
198
|
+
</KpiCard>
|
|
199
|
+
</KpiRow>
|
|
200
|
+
|
|
201
|
+
{/* Insights */}
|
|
202
|
+
{topInsights.length > 0 && (
|
|
203
|
+
<div style={{ marginBottom: 12 }}>
|
|
204
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle" style={{ marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.04em' }}>
|
|
205
|
+
Insights
|
|
206
|
+
</Typography>
|
|
207
|
+
{topInsights.map((ins, i) => (
|
|
208
|
+
<InsightRow key={i}>
|
|
209
|
+
<Badge variant={(severityToVariant[ins.severity] ?? 'neutral') as any} size="sm">
|
|
210
|
+
{ins.severity}
|
|
211
|
+
</Badge>
|
|
212
|
+
<Typography variant="caption-regular" color="color-text-medium" style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
213
|
+
{ins.title}
|
|
214
|
+
</Typography>
|
|
215
|
+
</InsightRow>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{/* Run history */}
|
|
221
|
+
{recentRuns.length > 0 && (
|
|
222
|
+
<div style={{ marginBottom: 8 }}>
|
|
223
|
+
<Typography variant="smallCaption-bold" color="color-text-subtle" style={{ marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.04em' }}>
|
|
224
|
+
Recent Runs
|
|
225
|
+
</Typography>
|
|
226
|
+
{recentRuns.map((run, i) => {
|
|
227
|
+
const oc = outcomeToIcon[run.outcome] ?? { name: 'help', color: 'color-text-subtle' };
|
|
228
|
+
return (
|
|
229
|
+
<RunRow key={run.id ?? i}>
|
|
230
|
+
<Icon name={oc.name as any} size={14} color={oc.color} />
|
|
231
|
+
<Typography variant="caption-regular" color="color-text-medium" style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
232
|
+
{run.summary ?? run.workflowFile ?? run.outcome}
|
|
233
|
+
</Typography>
|
|
234
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ fontFamily: 'var(--typography-family-mono)' }}>
|
|
235
|
+
{formatTime(run.startedAt)}
|
|
236
|
+
</Typography>
|
|
237
|
+
</RunRow>
|
|
238
|
+
);
|
|
239
|
+
})}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{topInsights.length === 0 && recentRuns.length === 0 && healthScore === 0 && (
|
|
244
|
+
<Typography variant="caption-regular" color="color-text-subtle" style={{ textAlign: 'center', padding: 20 }}>
|
|
245
|
+
No data yet. Run a bot to see dashboard metrics.
|
|
246
|
+
</Typography>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 4 }}>
|
|
250
|
+
<IconButton icon="refresh" size="xs" variant="clear" onClick={fetchData} title="Refresh" />
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</CollapsibleSection>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = BotDashboard;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaver Bot Status Widget — live session status during execution.
|
|
3
|
+
* Runs in the platform bot-panel sandbox. Refreshes every 5 seconds.
|
|
4
|
+
*/
|
|
5
|
+
const React = require('react');
|
|
6
|
+
const { useState, useEffect, useCallback, useRef } = React;
|
|
7
|
+
const { Typography } = require('@fw/plugin-ui-kit');
|
|
8
|
+
const { StatusDot } = require('@fw/plugin-ui-kit');
|
|
9
|
+
const { Icon } = require('@fw/plugin-ui-kit');
|
|
10
|
+
|
|
11
|
+
const TOOL_URL = '/api/pack-tool/@synergenius/flow-weaver-pack-weaver/fw_weaver_status';
|
|
12
|
+
|
|
13
|
+
interface StatusData {
|
|
14
|
+
state: string;
|
|
15
|
+
currentTask?: string;
|
|
16
|
+
completedTasks?: number;
|
|
17
|
+
totalTasks?: number;
|
|
18
|
+
botName?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type DotStatus = 'positive' | 'negative' | 'caution' | 'neutral' | 'info';
|
|
22
|
+
|
|
23
|
+
const stateToDot: Record<string, DotStatus> = {
|
|
24
|
+
idle: 'neutral',
|
|
25
|
+
planning: 'caution',
|
|
26
|
+
executing: 'info',
|
|
27
|
+
reviewing: 'caution',
|
|
28
|
+
completed: 'positive',
|
|
29
|
+
failed: 'negative',
|
|
30
|
+
paused: 'neutral',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const stateToIcon: Record<string, string> = {
|
|
34
|
+
idle: 'pending',
|
|
35
|
+
planning: 'psychology',
|
|
36
|
+
executing: 'running',
|
|
37
|
+
reviewing: 'rateReview',
|
|
38
|
+
completed: 'taskAlt',
|
|
39
|
+
failed: 'error',
|
|
40
|
+
paused: 'pause',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function BotStatus({ packName, botId }: { packName: string; botId: string }) {
|
|
44
|
+
const [data, setData] = useState<StatusData | null>(null);
|
|
45
|
+
const [error, setError] = useState<string | null>(null);
|
|
46
|
+
const timerRef = useRef<ReturnType<typeof setInterval>>();
|
|
47
|
+
|
|
48
|
+
const fetchData = useCallback(async () => {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(TOOL_URL, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({}),
|
|
54
|
+
credentials: 'include',
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) { setError('Failed to fetch'); return; }
|
|
57
|
+
const json = await res.json();
|
|
58
|
+
if (json.isError) { setError(json.result); return; }
|
|
59
|
+
setData(JSON.parse(json.result));
|
|
60
|
+
setError(null);
|
|
61
|
+
} catch (e: any) {
|
|
62
|
+
setError(e.message ?? 'Failed to load');
|
|
63
|
+
}
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
fetchData();
|
|
68
|
+
timerRef.current = setInterval(fetchData, 5_000);
|
|
69
|
+
return () => clearInterval(timerRef.current);
|
|
70
|
+
}, [fetchData]);
|
|
71
|
+
|
|
72
|
+
if (error) return (
|
|
73
|
+
<div style={{ padding: '6px 12px' }}>
|
|
74
|
+
<Typography variant="caption-regular" color="color-status-negative">{error}</Typography>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
if (!data) return (
|
|
78
|
+
<div style={{ padding: '6px 12px' }}>
|
|
79
|
+
<Typography variant="caption-regular" color="color-text-subtle">Loading status...</Typography>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const state = data.state ?? 'idle';
|
|
84
|
+
const dotStatus = stateToDot[state] ?? 'neutral';
|
|
85
|
+
const iconName = stateToIcon[state] ?? 'pending';
|
|
86
|
+
const completed = data.completedTasks ?? 0;
|
|
87
|
+
const total = data.totalTasks ?? 0;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div style={{
|
|
91
|
+
padding: '8px 12px',
|
|
92
|
+
display: 'flex',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
gap: 8,
|
|
95
|
+
borderBottom: '1px solid var(--color-border-default)',
|
|
96
|
+
background: 'var(--color-surface-elevated)',
|
|
97
|
+
}}>
|
|
98
|
+
<Icon name={iconName as any} size={14} color={`color-status-${dotStatus === 'neutral' ? 'info' : dotStatus}`} />
|
|
99
|
+
<Typography variant="caption-bold" style={{ textTransform: 'capitalize' }}>
|
|
100
|
+
{state}
|
|
101
|
+
</Typography>
|
|
102
|
+
{data.currentTask && (
|
|
103
|
+
<Typography
|
|
104
|
+
variant="caption-regular"
|
|
105
|
+
color="color-text-medium"
|
|
106
|
+
style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
|
107
|
+
>
|
|
108
|
+
{data.currentTask}
|
|
109
|
+
</Typography>
|
|
110
|
+
)}
|
|
111
|
+
{total > 0 && (
|
|
112
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
113
|
+
{completed}/{total}
|
|
114
|
+
</Typography>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = BotStatus;
|