agent-state-machine 2.4.0 → 2.5.0
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/bin/cli.js +7 -7
- package/lib/remote/client.js +19 -6
- package/lib/runtime/agent.js +29 -2
- package/lib/runtime/runtime.js +34 -2
- package/package.json +1 -1
- package/vercel-server/api/config/[token].js +76 -0
- package/vercel-server/api/history/[token].js +1 -0
- package/vercel-server/api/ws/cli.js +39 -20
- package/vercel-server/local-server.js +98 -11
- package/vercel-server/public/remote/assets/index-BHvHkNOe.css +1 -0
- package/vercel-server/public/remote/assets/index-BSL55rdk.js +188 -0
- package/vercel-server/public/remote/index.html +2 -2
- package/vercel-server/ui/src/App.jsx +35 -0
- package/vercel-server/ui/src/components/ContentCard.jsx +180 -2
- package/vercel-server/ui/src/components/Header.jsx +59 -11
- package/vercel-server/ui/src/components/SettingsModal.jsx +130 -0
- package/vercel-server/public/remote/assets/index-Bnvi3AUu.js +0 -173
- package/vercel-server/public/remote/assets/index-DH2uv4Ll.css +0 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
6
6
|
<title>{{WORKFLOW_NAME}}</title>
|
|
7
|
-
<script type="module" crossorigin src="/remote/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/remote/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/remote/assets/index-BSL55rdk.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/remote/assets/index-BHvHkNOe.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -6,6 +6,7 @@ import Footer from "./components/Footer.jsx";
|
|
|
6
6
|
import Header from "./components/Header.jsx";
|
|
7
7
|
import InteractionForm from "./components/InteractionForm.jsx";
|
|
8
8
|
import SendingCard from "./components/SendingCard.jsx";
|
|
9
|
+
import SettingsModal from "./components/SettingsModal.jsx";
|
|
9
10
|
|
|
10
11
|
export default function App() {
|
|
11
12
|
const [history, setHistory] = useState([]);
|
|
@@ -17,6 +18,8 @@ export default function App() {
|
|
|
17
18
|
const [pendingInteraction, setPendingInteraction] = useState(null);
|
|
18
19
|
const [hasNew, setHasNew] = useState(false);
|
|
19
20
|
const [sendingState, setSendingState] = useState(null);
|
|
21
|
+
const [config, setConfig] = useState({ fullAuto: false, autoSelectDelay: 20 });
|
|
22
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
20
23
|
|
|
21
24
|
useEffect(() => {
|
|
22
25
|
const savedTheme = localStorage.getItem("rf_theme") || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
@@ -38,6 +41,7 @@ export default function App() {
|
|
|
38
41
|
const historyUrl = token ? `/api/history/${token}` : "/api/history";
|
|
39
42
|
const eventsUrl = token ? `/api/events/${token}` : "/api/events";
|
|
40
43
|
const submitUrl = token ? `/api/submit/${token}` : "/api/submit";
|
|
44
|
+
const configUrl = token ? `/api/config/${token}` : "/api/config";
|
|
41
45
|
|
|
42
46
|
const fetchData = async () => {
|
|
43
47
|
try {
|
|
@@ -59,12 +63,28 @@ export default function App() {
|
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
if (data.workflowName) setWorkflowName(data.workflowName);
|
|
66
|
+
if (data.config) setConfig(data.config);
|
|
62
67
|
setStatus("connected");
|
|
63
68
|
} catch (error) {
|
|
64
69
|
setStatus("disconnected");
|
|
65
70
|
}
|
|
66
71
|
};
|
|
67
72
|
|
|
73
|
+
const updateConfig = async (updates) => {
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(configUrl, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/json" },
|
|
78
|
+
body: JSON.stringify(updates),
|
|
79
|
+
});
|
|
80
|
+
if (res.ok) {
|
|
81
|
+
setConfig((prev) => ({ ...prev, ...updates }));
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error("Failed to update config:", err);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
68
88
|
const prevLen = useRef(0);
|
|
69
89
|
useEffect(() => {
|
|
70
90
|
if (history.length > prevLen.current) {
|
|
@@ -152,6 +172,10 @@ export default function App() {
|
|
|
152
172
|
viewMode={viewMode}
|
|
153
173
|
setViewMode={setViewMode}
|
|
154
174
|
history={history}
|
|
175
|
+
fullAuto={config.fullAuto}
|
|
176
|
+
onToggleFullAuto={() => updateConfig({ fullAuto: !config.fullAuto })}
|
|
177
|
+
onOpenSettings={() => setSettingsOpen(true)}
|
|
178
|
+
configDisabled={status !== "connected"}
|
|
155
179
|
/>
|
|
156
180
|
|
|
157
181
|
<main className="main-stage overflow-hidden">
|
|
@@ -223,6 +247,17 @@ export default function App() {
|
|
|
223
247
|
onJumpToLatest={() => setPageIndex(history.length - 1)}
|
|
224
248
|
className={viewMode === "log" ? "opacity-0 pointer-events-none" : "opacity-100"}
|
|
225
249
|
/>
|
|
250
|
+
|
|
251
|
+
<SettingsModal
|
|
252
|
+
isOpen={settingsOpen}
|
|
253
|
+
onClose={() => setSettingsOpen(false)}
|
|
254
|
+
fullAuto={config.fullAuto}
|
|
255
|
+
onToggleFullAuto={() => updateConfig({ fullAuto: !config.fullAuto })}
|
|
256
|
+
autoSelectDelay={config.autoSelectDelay}
|
|
257
|
+
onDelayChange={(delay) => updateConfig({ autoSelectDelay: delay })}
|
|
258
|
+
onStop={() => updateConfig({ stop: true })}
|
|
259
|
+
disabled={status !== "connected"}
|
|
260
|
+
/>
|
|
226
261
|
</div>
|
|
227
262
|
);
|
|
228
263
|
}
|
|
@@ -4,6 +4,27 @@ import { Bot, Brain, ChevronDown, ChevronRight, Search, X } from "lucide-react";
|
|
|
4
4
|
|
|
5
5
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
6
|
|
|
7
|
+
// Format model string for display - extracts model ID from various formats
|
|
8
|
+
const formatModelName = (model) => {
|
|
9
|
+
if (!model) return null;
|
|
10
|
+
// Handle api:provider:model format (e.g., "api:google:gemini-2.5-pro" -> "gemini-2.5-pro")
|
|
11
|
+
if (model.startsWith("api:")) {
|
|
12
|
+
const parts = model.split(":");
|
|
13
|
+
return parts.length >= 3 ? parts.slice(2).join(":") : model;
|
|
14
|
+
}
|
|
15
|
+
// Handle CLI commands with -m/--model flag (e.g., "gemini -m gemini-2.5-flash-lite" -> "gemini-2.5-flash-lite")
|
|
16
|
+
if (model.includes(" ")) {
|
|
17
|
+
const parts = model.split(/\s+/);
|
|
18
|
+
const mIndex = parts.findIndex(p => p === "-m" || p === "--model");
|
|
19
|
+
if (mIndex !== -1 && parts[mIndex + 1]) {
|
|
20
|
+
return parts[mIndex + 1];
|
|
21
|
+
}
|
|
22
|
+
// No -m flag, just return the CLI name
|
|
23
|
+
return parts[0];
|
|
24
|
+
}
|
|
25
|
+
return model;
|
|
26
|
+
};
|
|
27
|
+
|
|
7
28
|
const highlightText = (text, query) => {
|
|
8
29
|
const source = String(text ?? "");
|
|
9
30
|
const q = (query || "").trim();
|
|
@@ -204,29 +225,45 @@ export default function ContentCard({ item, pageIndex = 0, history = [] }) {
|
|
|
204
225
|
const [showRaw, setShowRaw] = useState(false);
|
|
205
226
|
const [showPromptSearch, setShowPromptSearch] = useState(false);
|
|
206
227
|
const [showTokens, setShowTokens] = useState(false);
|
|
228
|
+
const [showTokenDetails, setShowTokenDetails] = useState(false);
|
|
207
229
|
|
|
208
230
|
// Calculate token stats: point-in-time (up to current page) and total (full session)
|
|
209
231
|
const tokenStats = useMemo(() => {
|
|
210
232
|
let pointInTime = { inputTokens: 0, outputTokens: 0 };
|
|
211
|
-
let total = { inputTokens: 0, outputTokens: 0 };
|
|
233
|
+
let total = { inputTokens: 0, outputTokens: 0, cost: 0 };
|
|
234
|
+
const agentBreakdown = [];
|
|
212
235
|
|
|
213
236
|
for (let i = 0; i < history.length; i++) {
|
|
214
237
|
const event = history[i];
|
|
215
238
|
if (event?.event === "AGENT_COMPLETED" && event.usage) {
|
|
216
239
|
const input = event.usage.inputTokens || 0;
|
|
217
240
|
const output = event.usage.outputTokens || 0;
|
|
241
|
+
const cached = event.usage.cachedTokens || 0;
|
|
242
|
+
const cost = event.usage.cost || 0;
|
|
218
243
|
total.inputTokens += input;
|
|
219
244
|
total.outputTokens += output;
|
|
245
|
+
total.cost += cost;
|
|
220
246
|
if (i <= pageIndex) {
|
|
221
247
|
pointInTime.inputTokens += input;
|
|
222
248
|
pointInTime.outputTokens += output;
|
|
223
249
|
}
|
|
250
|
+
agentBreakdown.push({
|
|
251
|
+
agent: event.agent || "Unknown",
|
|
252
|
+
model: event.model || null,
|
|
253
|
+
inputTokens: input,
|
|
254
|
+
outputTokens: output,
|
|
255
|
+
cachedTokens: cached,
|
|
256
|
+
cost: cost,
|
|
257
|
+
timestamp: event.timestamp,
|
|
258
|
+
index: i,
|
|
259
|
+
});
|
|
224
260
|
}
|
|
225
261
|
}
|
|
226
262
|
|
|
227
263
|
return {
|
|
228
264
|
pointInTime,
|
|
229
265
|
total,
|
|
266
|
+
agentBreakdown,
|
|
230
267
|
hasTokens: total.inputTokens > 0 || total.outputTokens > 0
|
|
231
268
|
};
|
|
232
269
|
}, [history, pageIndex]);
|
|
@@ -588,6 +625,17 @@ export default function ContentCard({ item, pageIndex = 0, history = [] }) {
|
|
|
588
625
|
Agent started
|
|
589
626
|
</div>
|
|
590
627
|
<h2 className="text-3xl sm:text-4xl font-black tracking-tight">{item.agent}</h2>
|
|
628
|
+
{item.model && (
|
|
629
|
+
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-black/5 dark:bg-white/10 text-xs font-mono text-black/60 dark:text-white/60">
|
|
630
|
+
{item.modelAlias && (
|
|
631
|
+
<>
|
|
632
|
+
<span className="font-semibold">{item.modelAlias}</span>
|
|
633
|
+
<span className="text-black/30 dark:text-white/30">•</span>
|
|
634
|
+
</>
|
|
635
|
+
)}
|
|
636
|
+
{formatModelName(item.model)}
|
|
637
|
+
</div>
|
|
638
|
+
)}
|
|
591
639
|
</div>
|
|
592
640
|
</div>
|
|
593
641
|
|
|
@@ -974,7 +1022,7 @@ export default function ContentCard({ item, pageIndex = 0, history = [] }) {
|
|
|
974
1022
|
{/* Model badge */}
|
|
975
1023
|
{item.model && (
|
|
976
1024
|
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-black/5 dark:bg-white/10 text-xs font-mono text-black/60 dark:text-white/60">
|
|
977
|
-
{item.model}
|
|
1025
|
+
{formatModelName(item.model)}
|
|
978
1026
|
</div>
|
|
979
1027
|
)}
|
|
980
1028
|
|
|
@@ -1060,6 +1108,16 @@ export default function ContentCard({ item, pageIndex = 0, history = [] }) {
|
|
|
1060
1108
|
<span className="font-semibold">{tokenStats.total.outputTokens.toLocaleString()}</span>
|
|
1061
1109
|
</div>
|
|
1062
1110
|
</div>
|
|
1111
|
+
<button
|
|
1112
|
+
type="button"
|
|
1113
|
+
onClick={() => {
|
|
1114
|
+
setShowTokens(false);
|
|
1115
|
+
setShowTokenDetails(true);
|
|
1116
|
+
}}
|
|
1117
|
+
className="w-full mt-2 pt-3 border-t border-black/10 dark:border-white/10 text-[10px] font-semibold tracking-[0.16em] uppercase text-black/50 dark:text-white/50 hover:text-black dark:hover:text-white transition-colors text-center"
|
|
1118
|
+
>
|
|
1119
|
+
View details
|
|
1120
|
+
</button>
|
|
1063
1121
|
</div>
|
|
1064
1122
|
)}
|
|
1065
1123
|
</div>
|
|
@@ -1079,6 +1137,126 @@ export default function ContentCard({ item, pageIndex = 0, history = [] }) {
|
|
|
1079
1137
|
content
|
|
1080
1138
|
)}
|
|
1081
1139
|
</div>
|
|
1140
|
+
|
|
1141
|
+
{/* Token Details Modal */}
|
|
1142
|
+
{showTokenDetails && (
|
|
1143
|
+
<div className="fixed inset-0 z-50 bg-white text-black dark:bg-black dark:text-white">
|
|
1144
|
+
<div className="h-full w-full overflow-y-auto custom-scroll px-6 sm:px-10 py-10">
|
|
1145
|
+
<div className="max-w-4xl mx-auto space-y-8">
|
|
1146
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
1147
|
+
<div className="space-y-1">
|
|
1148
|
+
<div className="text-[11px] font-semibold tracking-[0.32em] uppercase text-black/50 dark:text-white/50">
|
|
1149
|
+
Token Usage Details
|
|
1150
|
+
</div>
|
|
1151
|
+
<div className="text-2xl font-bold">
|
|
1152
|
+
{tokenStats.agentBreakdown.length} agent {tokenStats.agentBreakdown.length === 1 ? "call" : "calls"}
|
|
1153
|
+
</div>
|
|
1154
|
+
</div>
|
|
1155
|
+
<button
|
|
1156
|
+
type="button"
|
|
1157
|
+
onClick={() => setShowTokenDetails(false)}
|
|
1158
|
+
className="text-[10px] font-bold tracking-[0.2em] uppercase text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white"
|
|
1159
|
+
>
|
|
1160
|
+
Close
|
|
1161
|
+
</button>
|
|
1162
|
+
</div>
|
|
1163
|
+
|
|
1164
|
+
{/* Totals Summary */}
|
|
1165
|
+
<div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6">
|
|
1166
|
+
<div className="text-[10px] font-semibold tracking-[0.2em] uppercase text-black/40 dark:text-white/40 mb-4">
|
|
1167
|
+
Session Totals
|
|
1168
|
+
</div>
|
|
1169
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-6">
|
|
1170
|
+
<div className="space-y-1">
|
|
1171
|
+
<div className="text-xs text-black/50 dark:text-white/50">Input Tokens</div>
|
|
1172
|
+
<div className="text-xl font-mono font-bold">{tokenStats.total.inputTokens.toLocaleString()}</div>
|
|
1173
|
+
</div>
|
|
1174
|
+
<div className="space-y-1">
|
|
1175
|
+
<div className="text-xs text-black/50 dark:text-white/50">Output Tokens</div>
|
|
1176
|
+
<div className="text-xl font-mono font-bold">{tokenStats.total.outputTokens.toLocaleString()}</div>
|
|
1177
|
+
</div>
|
|
1178
|
+
<div className="space-y-1">
|
|
1179
|
+
<div className="text-xs text-black/50 dark:text-white/50">Total Tokens</div>
|
|
1180
|
+
<div className="text-xl font-mono font-bold">{(tokenStats.total.inputTokens + tokenStats.total.outputTokens).toLocaleString()}</div>
|
|
1181
|
+
</div>
|
|
1182
|
+
{tokenStats.total.cost > 0 && (
|
|
1183
|
+
<div className="space-y-1">
|
|
1184
|
+
<div className="text-xs text-black/50 dark:text-white/50">Total Cost</div>
|
|
1185
|
+
<div className="text-xl font-mono font-bold">${tokenStats.total.cost.toFixed(4)}</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
)}
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
|
|
1191
|
+
{/* Agent Breakdown */}
|
|
1192
|
+
<div className="space-y-4">
|
|
1193
|
+
<div className="text-[10px] font-semibold tracking-[0.2em] uppercase text-black/40 dark:text-white/40">
|
|
1194
|
+
Per-Agent Breakdown
|
|
1195
|
+
</div>
|
|
1196
|
+
<div className="space-y-3">
|
|
1197
|
+
{tokenStats.agentBreakdown.map((agent, idx) => (
|
|
1198
|
+
<div
|
|
1199
|
+
key={`${agent.agent}-${idx}`}
|
|
1200
|
+
className="rounded-[20px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-5"
|
|
1201
|
+
>
|
|
1202
|
+
<div className="flex flex-wrap items-start justify-between gap-4 mb-4">
|
|
1203
|
+
<div className="space-y-1">
|
|
1204
|
+
<div className="text-lg font-bold">{agent.agent}</div>
|
|
1205
|
+
<div className="flex items-center gap-2">
|
|
1206
|
+
{agent.model && (
|
|
1207
|
+
<span className="inline-flex px-2 py-0.5 rounded-full bg-black/5 dark:bg-white/10 text-xs font-mono text-black/60 dark:text-white/60">
|
|
1208
|
+
{formatModelName(agent.model)}
|
|
1209
|
+
</span>
|
|
1210
|
+
)}
|
|
1211
|
+
<span className="text-xs text-black/40 dark:text-white/40">
|
|
1212
|
+
{new Date(agent.timestamp).toLocaleTimeString([], {
|
|
1213
|
+
hour: "2-digit",
|
|
1214
|
+
minute: "2-digit",
|
|
1215
|
+
second: "2-digit",
|
|
1216
|
+
})}
|
|
1217
|
+
</span>
|
|
1218
|
+
</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
{agent.cost > 0 && (
|
|
1221
|
+
<div className="text-right">
|
|
1222
|
+
<div className="text-lg font-mono font-bold">${agent.cost.toFixed(4)}</div>
|
|
1223
|
+
</div>
|
|
1224
|
+
)}
|
|
1225
|
+
</div>
|
|
1226
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-sm">
|
|
1227
|
+
<div className="space-y-0.5">
|
|
1228
|
+
<div className="text-xs text-black/40 dark:text-white/40">Input</div>
|
|
1229
|
+
<div className="font-mono font-semibold">{agent.inputTokens.toLocaleString()}</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
<div className="space-y-0.5">
|
|
1232
|
+
<div className="text-xs text-black/40 dark:text-white/40">Output</div>
|
|
1233
|
+
<div className="font-mono font-semibold">{agent.outputTokens.toLocaleString()}</div>
|
|
1234
|
+
</div>
|
|
1235
|
+
{agent.cachedTokens > 0 && (
|
|
1236
|
+
<div className="space-y-0.5">
|
|
1237
|
+
<div className="text-xs text-black/40 dark:text-white/40">Cached</div>
|
|
1238
|
+
<div className="font-mono font-semibold text-black/60 dark:text-white/60">{agent.cachedTokens.toLocaleString()}</div>
|
|
1239
|
+
</div>
|
|
1240
|
+
)}
|
|
1241
|
+
<div className="space-y-0.5">
|
|
1242
|
+
<div className="text-xs text-black/40 dark:text-white/40">Total</div>
|
|
1243
|
+
<div className="font-mono font-semibold">{(agent.inputTokens + agent.outputTokens).toLocaleString()}</div>
|
|
1244
|
+
</div>
|
|
1245
|
+
</div>
|
|
1246
|
+
</div>
|
|
1247
|
+
))}
|
|
1248
|
+
</div>
|
|
1249
|
+
</div>
|
|
1250
|
+
|
|
1251
|
+
{tokenStats.agentBreakdown.length === 0 && (
|
|
1252
|
+
<div className="rounded-[20px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 text-center text-black/50 dark:text-white/50">
|
|
1253
|
+
No token usage data available yet.
|
|
1254
|
+
</div>
|
|
1255
|
+
)}
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
)}
|
|
1082
1260
|
</div>
|
|
1083
1261
|
);
|
|
1084
1262
|
}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import { Moon, Sun, LayoutList, Presentation } from "lucide-react";
|
|
1
|
+
import { Moon, Sun, LayoutList, Presentation, Play, Pause, Settings } from "lucide-react";
|
|
2
2
|
import CopyButton from "./CopyButton.jsx";
|
|
3
3
|
|
|
4
|
-
export default function Header({
|
|
4
|
+
export default function Header({
|
|
5
|
+
workflowName,
|
|
6
|
+
status,
|
|
7
|
+
theme,
|
|
8
|
+
toggleTheme,
|
|
9
|
+
viewMode,
|
|
10
|
+
setViewMode,
|
|
11
|
+
history,
|
|
12
|
+
fullAuto,
|
|
13
|
+
onToggleFullAuto,
|
|
14
|
+
onOpenSettings,
|
|
15
|
+
configDisabled,
|
|
16
|
+
}) {
|
|
17
|
+
const isConnected = status === "connected";
|
|
18
|
+
|
|
5
19
|
return (
|
|
6
20
|
<header className="fixed top-0 inset-x-0 h-20 px-6 sm:px-10 lg:px-12 flex items-center justify-between z-50 bg-bg/80 backdrop-blur-3xl">
|
|
7
21
|
<div className="flex items-center gap-4">
|
|
8
22
|
<div
|
|
9
23
|
className={`w-2.5 h-2.5 rounded-full border ${
|
|
10
|
-
|
|
24
|
+
isConnected
|
|
11
25
|
? "bg-black border-black dark:bg-white dark:border-white"
|
|
12
26
|
: "bg-transparent border-black/30 dark:border-white/30"
|
|
13
27
|
}`}
|
|
@@ -19,7 +33,37 @@ export default function Header({ workflowName, status, theme, toggleTheme, viewM
|
|
|
19
33
|
</div>
|
|
20
34
|
|
|
21
35
|
<div className="flex items-center gap-2">
|
|
36
|
+
{/* Play/Pause button - quick toggle for full-auto */}
|
|
37
|
+
<button
|
|
38
|
+
onClick={onToggleFullAuto}
|
|
39
|
+
disabled={configDisabled}
|
|
40
|
+
className={`tooltip w-10 h-10 flex items-center justify-center rounded-full transition-colors ${
|
|
41
|
+
configDisabled
|
|
42
|
+
? "opacity-30 cursor-not-allowed"
|
|
43
|
+
: "hover:bg-black/5 dark:hover:bg-white/10"
|
|
44
|
+
}`}
|
|
45
|
+
data-tooltip={fullAuto ? "Pause auto" : "Enable auto"}
|
|
46
|
+
aria-label={fullAuto ? "Disable full-auto mode" : "Enable full-auto mode"}
|
|
47
|
+
>
|
|
48
|
+
{fullAuto ? (
|
|
49
|
+
<Pause className="w-5 h-5 opacity-60" />
|
|
50
|
+
) : (
|
|
51
|
+
<Play className="w-5 h-5 opacity-40 hover:opacity-100" />
|
|
52
|
+
)}
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
{/* Settings button */}
|
|
56
|
+
<button
|
|
57
|
+
onClick={onOpenSettings}
|
|
58
|
+
className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
|
59
|
+
data-tooltip="Settings"
|
|
60
|
+
aria-label="Open settings"
|
|
61
|
+
>
|
|
62
|
+
<Settings className="w-5 h-5 opacity-40 hover:opacity-100" />
|
|
63
|
+
</button>
|
|
64
|
+
|
|
22
65
|
<CopyButton text={history || []} label="Copy full history" disabled={!history || history.length === 0} />
|
|
66
|
+
|
|
23
67
|
<button
|
|
24
68
|
onClick={() => setViewMode(viewMode === "present" ? "log" : "present")}
|
|
25
69
|
className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
|
@@ -33,14 +77,18 @@ export default function Header({ workflowName, status, theme, toggleTheme, viewM
|
|
|
33
77
|
)}
|
|
34
78
|
</button>
|
|
35
79
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
80
|
+
<button
|
|
81
|
+
onClick={toggleTheme}
|
|
82
|
+
className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
|
83
|
+
data-tooltip={theme === "dark" ? "Light theme" : "Dark theme"}
|
|
84
|
+
aria-label={theme === "dark" ? "Switch to Light theme" : "Switch to Dark theme"}
|
|
85
|
+
>
|
|
86
|
+
{theme === "dark" ? (
|
|
87
|
+
<Sun className="w-5 h-5 opacity-40 hover:opacity-100" />
|
|
88
|
+
) : (
|
|
89
|
+
<Moon className="w-5 h-5 opacity-40 hover:opacity-100" />
|
|
90
|
+
)}
|
|
91
|
+
</button>
|
|
44
92
|
</div>
|
|
45
93
|
</header>
|
|
46
94
|
);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { X, Square } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
export default function SettingsModal({
|
|
5
|
+
isOpen,
|
|
6
|
+
onClose,
|
|
7
|
+
fullAuto,
|
|
8
|
+
onToggleFullAuto,
|
|
9
|
+
autoSelectDelay,
|
|
10
|
+
onDelayChange,
|
|
11
|
+
onStop,
|
|
12
|
+
disabled,
|
|
13
|
+
}) {
|
|
14
|
+
const [showStopConfirm, setShowStopConfirm] = useState(false);
|
|
15
|
+
|
|
16
|
+
if (!isOpen) return null;
|
|
17
|
+
|
|
18
|
+
const handleStop = () => {
|
|
19
|
+
if (showStopConfirm) {
|
|
20
|
+
onStop();
|
|
21
|
+
setShowStopConfirm(false);
|
|
22
|
+
onClose();
|
|
23
|
+
} else {
|
|
24
|
+
setShowStopConfirm(true);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const handleBackdropClick = (e) => {
|
|
29
|
+
if (e.target === e.currentTarget) {
|
|
30
|
+
setShowStopConfirm(false);
|
|
31
|
+
onClose();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
38
|
+
onClick={handleBackdropClick}
|
|
39
|
+
>
|
|
40
|
+
<div className="bg-white dark:bg-black border border-black/10 dark:border-white/10 rounded-3xl p-8 w-[90vw] max-w-md shadow-2xl">
|
|
41
|
+
{/* Header */}
|
|
42
|
+
<div className="flex justify-between items-center mb-6">
|
|
43
|
+
<h2 className="text-xl font-bold">Settings</h2>
|
|
44
|
+
<button
|
|
45
|
+
onClick={() => {
|
|
46
|
+
setShowStopConfirm(false);
|
|
47
|
+
onClose();
|
|
48
|
+
}}
|
|
49
|
+
className="p-2 hover:bg-black/5 dark:hover:bg-white/10 rounded-full transition-colors"
|
|
50
|
+
aria-label="Close settings"
|
|
51
|
+
>
|
|
52
|
+
<X className="w-5 h-5" />
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Full-auto toggle */}
|
|
57
|
+
<div className="flex items-center justify-between py-4 border-b border-black/10 dark:border-white/10">
|
|
58
|
+
<div>
|
|
59
|
+
<div className="font-semibold">Full-Auto Mode</div>
|
|
60
|
+
<div className="text-sm text-black/50 dark:text-white/50">
|
|
61
|
+
Auto-select recommended options
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<button
|
|
65
|
+
onClick={onToggleFullAuto}
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
className={`w-12 h-7 rounded-full transition-colors relative ${
|
|
68
|
+
fullAuto
|
|
69
|
+
? "bg-black dark:bg-white"
|
|
70
|
+
: "bg-black/20 dark:bg-white/20"
|
|
71
|
+
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
72
|
+
aria-pressed={fullAuto}
|
|
73
|
+
aria-label="Toggle full-auto mode"
|
|
74
|
+
>
|
|
75
|
+
<div
|
|
76
|
+
className={`absolute top-1 w-5 h-5 rounded-full bg-white dark:bg-black transition-transform ${
|
|
77
|
+
fullAuto ? "left-6" : "left-1"
|
|
78
|
+
}`}
|
|
79
|
+
/>
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
{/* Countdown delay */}
|
|
84
|
+
<div className="flex items-center justify-between py-4 border-b border-black/10 dark:border-white/10">
|
|
85
|
+
<div>
|
|
86
|
+
<div className="font-semibold">Countdown Delay</div>
|
|
87
|
+
<div className="text-sm text-black/50 dark:text-white/50">
|
|
88
|
+
Seconds before auto-select
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<input
|
|
92
|
+
type="number"
|
|
93
|
+
value={autoSelectDelay}
|
|
94
|
+
onChange={(e) => {
|
|
95
|
+
const val = parseInt(e.target.value, 10);
|
|
96
|
+
if (!isNaN(val) && val >= 1 && val <= 120) {
|
|
97
|
+
onDelayChange(val);
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
min={1}
|
|
101
|
+
max={120}
|
|
102
|
+
disabled={disabled}
|
|
103
|
+
className="w-20 p-2 text-center rounded-xl border border-black/20 dark:border-white/20 bg-transparent disabled:opacity-50"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Stop button */}
|
|
108
|
+
<div className="mt-6">
|
|
109
|
+
<button
|
|
110
|
+
onClick={handleStop}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
className={`w-full py-4 rounded-2xl font-bold flex items-center justify-center gap-2 transition-colors ${
|
|
113
|
+
showStopConfirm
|
|
114
|
+
? "bg-red-600 text-white hover:bg-red-700"
|
|
115
|
+
: "bg-red-500/10 text-red-500 hover:bg-red-500/20"
|
|
116
|
+
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
117
|
+
>
|
|
118
|
+
<Square className="w-5 h-5" />
|
|
119
|
+
{showStopConfirm ? "Click again to confirm" : "Stop Workflow"}
|
|
120
|
+
</button>
|
|
121
|
+
{showStopConfirm && (
|
|
122
|
+
<div className="text-center text-sm text-black/50 dark:text-white/50 mt-2">
|
|
123
|
+
This will terminate the CLI process
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|