agent-state-machine 2.2.1 → 2.2.2
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 +30 -2
- package/lib/runtime/agent.js +6 -2
- package/lib/runtime/interaction.js +2 -1
- package/lib/runtime/prompt.js +37 -1
- package/lib/runtime/runtime.js +67 -5
- package/package.json +1 -1
- package/templates/project-builder/agents/code-fixer.md +50 -0
- package/templates/project-builder/agents/code-writer.md +3 -0
- package/templates/project-builder/agents/sanity-checker.md +6 -0
- package/templates/project-builder/agents/test-planner.md +3 -1
- package/templates/project-builder/config.js +4 -4
- package/templates/project-builder/scripts/workflow-helpers.js +104 -2
- package/templates/project-builder/workflow.js +151 -14
- package/templates/starter/config.js +1 -1
- package/vercel-server/api/submit/[token].js +0 -11
- package/vercel-server/local-server.js +0 -19
- package/vercel-server/public/remote/assets/index-BsJsLDKc.css +1 -0
- package/vercel-server/public/remote/assets/index-CmtT6ADh.js +168 -0
- package/vercel-server/public/remote/index.html +2 -2
- package/vercel-server/ui/src/App.jsx +69 -19
- package/vercel-server/ui/src/components/ChoiceInteraction.jsx +69 -18
- package/vercel-server/ui/src/components/ConfirmInteraction.jsx +7 -7
- package/vercel-server/ui/src/components/ContentCard.jsx +600 -104
- package/vercel-server/ui/src/components/EventsLog.jsx +20 -13
- package/vercel-server/ui/src/components/Footer.jsx +9 -4
- package/vercel-server/ui/src/components/Header.jsx +12 -3
- package/vercel-server/ui/src/components/SendingCard.jsx +33 -0
- package/vercel-server/ui/src/components/TextInteraction.jsx +8 -8
- package/vercel-server/ui/src/index.css +82 -10
- package/vercel-server/public/remote/assets/index-CbgeVnKw.js +0 -148
- package/vercel-server/public/remote/assets/index-DHL_iHQW.css +0 -1
|
@@ -1,5 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
1
2
|
import CopyButton from "./CopyButton.jsx";
|
|
2
|
-
import { Bot, Brain, ChevronRight } from "lucide-react";
|
|
3
|
+
import { Bot, Brain, ChevronRight, Search, X } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
|
+
|
|
7
|
+
const highlightText = (text, query) => {
|
|
8
|
+
const source = String(text ?? "");
|
|
9
|
+
const q = (query || "").trim();
|
|
10
|
+
if (!q) return source;
|
|
11
|
+
const regex = new RegExp(escapeRegExp(q), "gi");
|
|
12
|
+
const matches = source.match(regex);
|
|
13
|
+
if (!matches) return source;
|
|
14
|
+
|
|
15
|
+
const parts = source.split(regex);
|
|
16
|
+
const nodes = [];
|
|
17
|
+
|
|
18
|
+
parts.forEach((part, index) => {
|
|
19
|
+
nodes.push(part);
|
|
20
|
+
if (index < matches.length) {
|
|
21
|
+
nodes.push(
|
|
22
|
+
<mark
|
|
23
|
+
key={`mark-${index}`}
|
|
24
|
+
className="rounded bg-black/10 dark:bg-white/20 px-1"
|
|
25
|
+
>
|
|
26
|
+
{matches[index]}
|
|
27
|
+
</mark>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return nodes;
|
|
33
|
+
};
|
|
3
34
|
|
|
4
35
|
function AgentStartedIcon({ className = "" }) {
|
|
5
36
|
return (
|
|
@@ -12,7 +43,107 @@ function AgentStartedIcon({ className = "" }) {
|
|
|
12
43
|
);
|
|
13
44
|
}
|
|
14
45
|
|
|
15
|
-
|
|
46
|
+
function MonoBlock({ children }) {
|
|
47
|
+
return (
|
|
48
|
+
<pre className="text-xs sm:text-sm font-mono leading-relaxed whitespace-pre-wrap break-words overflow-auto max-h-[60vh] p-6 rounded-[24px] border border-black bg-black text-white/90 dark:border-white dark:bg-white dark:text-black/90">
|
|
49
|
+
{children}
|
|
50
|
+
</pre>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TextBlock({ children }) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 text-base sm:text-lg leading-relaxed whitespace-pre-wrap break-words text-black/80 dark:text-white/80">
|
|
57
|
+
{children}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function PanelBody({ as: Component = "div", className = "", children }) {
|
|
63
|
+
return (
|
|
64
|
+
<Component
|
|
65
|
+
className={`border-t border-black/10 dark:border-white/10 p-6 ${className}`}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</Component>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function RawToggle({ open, onToggle }) {
|
|
73
|
+
return (
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={onToggle}
|
|
77
|
+
className="px-3 py-1 rounded-full text-[10px] font-bold tracking-[0.2em] uppercase border border-black/20 dark:border-white/20 text-black/60 dark:text-white/60 hover:text-black dark:hover:text-white hover:border-black/40 dark:hover:border-white/40 transition-colors"
|
|
78
|
+
aria-pressed={open}
|
|
79
|
+
aria-label={open ? "Hide raw event JSON" : "Show raw event JSON"}
|
|
80
|
+
>
|
|
81
|
+
Raw
|
|
82
|
+
</button>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const renderInlineValue = (value) => {
|
|
87
|
+
if (value === null || value === undefined) {
|
|
88
|
+
return <span className="italic">—</span>;
|
|
89
|
+
}
|
|
90
|
+
if (typeof value === "string") {
|
|
91
|
+
if (value.length > 140 || value.includes("\n")) {
|
|
92
|
+
return <TextBlock>{value}</TextBlock>;
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
<span className="text-lg sm:text-xl font-semibold whitespace-pre-wrap break-words">
|
|
96
|
+
{value}
|
|
97
|
+
</span>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (typeof value === "object") {
|
|
101
|
+
return <pre className="raw-json-block raw-json-block--full">{renderJsonWithHighlight(value)}</pre>;
|
|
102
|
+
}
|
|
103
|
+
return <span className="text-lg sm:text-xl font-semibold break-words">{String(value)}</span>;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function renderJsonWithHighlight(value) {
|
|
107
|
+
const raw = JSON.stringify(value, null, 2);
|
|
108
|
+
const regex =
|
|
109
|
+
/("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(?:\s*:)?|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
|
|
110
|
+
const nodes = [];
|
|
111
|
+
let lastIndex = 0;
|
|
112
|
+
let match;
|
|
113
|
+
|
|
114
|
+
while ((match = regex.exec(raw)) !== null) {
|
|
115
|
+
const { index } = match;
|
|
116
|
+
if (index > lastIndex) {
|
|
117
|
+
nodes.push(raw.slice(lastIndex, index));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const token = match[0];
|
|
121
|
+
let className = "json-token-number";
|
|
122
|
+
if (token.startsWith('"')) {
|
|
123
|
+
className = token.endsWith(":") ? "json-token-key" : "json-token-string";
|
|
124
|
+
} else if (token === "true" || token === "false") {
|
|
125
|
+
className = "json-token-boolean";
|
|
126
|
+
} else if (token === "null") {
|
|
127
|
+
className = "json-token-null";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
nodes.push(
|
|
131
|
+
<span key={`json-${index}`} className={className}>
|
|
132
|
+
{token}
|
|
133
|
+
</span>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
lastIndex = index + token.length;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (lastIndex < raw.length) {
|
|
140
|
+
nodes.push(raw.slice(lastIndex));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return nodes;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default function ContentCard({ item, promptSearchRequestId = 0 }) {
|
|
16
147
|
if (!item) return null;
|
|
17
148
|
|
|
18
149
|
const time = new Date(item.timestamp).toLocaleTimeString([], {
|
|
@@ -21,10 +152,42 @@ export default function ContentCard({ item }) {
|
|
|
21
152
|
second: "2-digit",
|
|
22
153
|
});
|
|
23
154
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
155
|
+
const [promptQuery, setPromptQuery] = useState("");
|
|
156
|
+
const [showRaw, setShowRaw] = useState(false);
|
|
157
|
+
const [showPromptSearch, setShowPromptSearch] = useState(false);
|
|
158
|
+
|
|
159
|
+
// Full-auto countdown logic (must be at top level for hooks rules)
|
|
160
|
+
const isInteractionEvent = item.event === "PROMPT_REQUESTED" || item.event === "INTERACTION_REQUESTED";
|
|
161
|
+
const isFullAuto = isInteractionEvent && !!item.fullAuto;
|
|
162
|
+
const interactionType = item.type || "text";
|
|
163
|
+
const interactionOptions = item.options || [];
|
|
164
|
+
const autoSelectDelay = item.autoSelectDelay ?? 20;
|
|
165
|
+
const shouldShowCountdown = isFullAuto && interactionType === "choice" && interactionOptions.length > 0;
|
|
166
|
+
|
|
167
|
+
// Calculate countdown directly - triggers re-render via tick state
|
|
168
|
+
const [tick, setTick] = useState(0);
|
|
169
|
+
|
|
170
|
+
const countdown = (() => {
|
|
171
|
+
if (!shouldShowCountdown) return null;
|
|
172
|
+
const eventTime = new Date(item.timestamp).getTime();
|
|
173
|
+
const elapsed = Math.floor((Date.now() - eventTime) / 1000);
|
|
174
|
+
return autoSelectDelay - elapsed;
|
|
175
|
+
})();
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (!shouldShowCountdown) return;
|
|
179
|
+
|
|
180
|
+
const timer = setInterval(() => {
|
|
181
|
+
setTick(t => t + 1); // Force re-render to recalculate countdown
|
|
182
|
+
}, 1000);
|
|
183
|
+
|
|
184
|
+
return () => clearInterval(timer);
|
|
185
|
+
}, [shouldShowCountdown, item.timestamp]);
|
|
186
|
+
|
|
187
|
+
const formatKey = (key) => {
|
|
188
|
+
const spaced = key.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
189
|
+
return spaced.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
190
|
+
};
|
|
28
191
|
|
|
29
192
|
const tryParseJson = (raw) => {
|
|
30
193
|
try {
|
|
@@ -109,11 +272,73 @@ export default function ContentCard({ item }) {
|
|
|
109
272
|
};
|
|
110
273
|
};
|
|
111
274
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
275
|
+
const splitPromptSections = (promptText) => {
|
|
276
|
+
if (typeof promptText !== "string" || !promptText.trim()) return [];
|
|
277
|
+
const lines = promptText.split(/\r?\n/);
|
|
278
|
+
const sections = [];
|
|
279
|
+
let current = null;
|
|
280
|
+
let inFence = false;
|
|
281
|
+
|
|
282
|
+
const pushSection = () => {
|
|
283
|
+
if (!current) return;
|
|
284
|
+
const content = current.content.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
285
|
+
if (!content) {
|
|
286
|
+
current = null;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
sections.push({ title: current.title || "Prompt", content });
|
|
290
|
+
current = null;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
lines.forEach((line) => {
|
|
294
|
+
const trimmed = line.trim();
|
|
295
|
+
if (/^```/.test(trimmed)) {
|
|
296
|
+
inFence = !inFence;
|
|
297
|
+
}
|
|
298
|
+
if (!trimmed) {
|
|
299
|
+
if (current) current.content.push("");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (!inFence && /^---+$/.test(trimmed)) {
|
|
303
|
+
pushSection();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const headingMatch = !inFence ? /^(#{1,6})\s+(.+)$/.exec(trimmed) : null;
|
|
307
|
+
if (headingMatch) {
|
|
308
|
+
pushSection();
|
|
309
|
+
current = { title: headingMatch[2].trim(), content: [] };
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (!current) current = { title: "Prompt", content: [] };
|
|
313
|
+
current.content.push(line);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
pushSection();
|
|
317
|
+
return sections;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const renderPromptSectionContent = (content, query) => {
|
|
321
|
+
if (!content) {
|
|
322
|
+
return (
|
|
323
|
+
<PanelBody className="italic text-black/40 dark:text-white/40">—</PanelBody>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (content.includes("```")) {
|
|
327
|
+
return (
|
|
328
|
+
<PanelBody
|
|
329
|
+
as="pre"
|
|
330
|
+
className="text-xs sm:text-sm font-mono leading-relaxed whitespace-pre-wrap break-words overflow-auto max-h-[60vh] text-black/90 dark:text-white/90"
|
|
331
|
+
>
|
|
332
|
+
{highlightText(content, query)}
|
|
333
|
+
</PanelBody>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return (
|
|
337
|
+
<PanelBody className="text-base sm:text-lg leading-relaxed whitespace-pre-wrap break-words text-black/80 dark:text-white/80">
|
|
338
|
+
{highlightText(content, query)}
|
|
339
|
+
</PanelBody>
|
|
340
|
+
);
|
|
341
|
+
};
|
|
117
342
|
|
|
118
343
|
const renderValue = (value) => {
|
|
119
344
|
if (value === null || value === undefined) {
|
|
@@ -122,7 +347,7 @@ export default function ContentCard({ item }) {
|
|
|
122
347
|
|
|
123
348
|
if (typeof value === "boolean") {
|
|
124
349
|
const base =
|
|
125
|
-
"inline-flex items-center rounded-full px-3 py-1 text-
|
|
350
|
+
"inline-flex items-center rounded-full px-3 py-1 text-[11px] font-semibold tracking-[0.16em] uppercase border";
|
|
126
351
|
return value ? (
|
|
127
352
|
<span className={`${base} bg-black text-white border-black dark:bg-white dark:text-black dark:border-white`}>
|
|
128
353
|
Yes
|
|
@@ -137,15 +362,15 @@ export default function ContentCard({ item }) {
|
|
|
137
362
|
if (typeof value === "string") {
|
|
138
363
|
const parsedJson = extractJsonFromString(value);
|
|
139
364
|
if (parsedJson !== null) {
|
|
140
|
-
return
|
|
365
|
+
return renderStructured(parsedJson);
|
|
141
366
|
}
|
|
142
367
|
|
|
143
368
|
if (value.length > 140 || value.includes("\n")) {
|
|
144
|
-
return <
|
|
369
|
+
return <TextBlock>{value}</TextBlock>;
|
|
145
370
|
}
|
|
146
371
|
|
|
147
372
|
return (
|
|
148
|
-
<span className="text-
|
|
373
|
+
<span className="text-lg sm:text-xl font-semibold whitespace-pre-wrap break-words">
|
|
149
374
|
{value}
|
|
150
375
|
</span>
|
|
151
376
|
);
|
|
@@ -155,7 +380,7 @@ export default function ContentCard({ item }) {
|
|
|
155
380
|
return <MonoBlock>{JSON.stringify(value, null, 2)}</MonoBlock>;
|
|
156
381
|
}
|
|
157
382
|
|
|
158
|
-
return <span className="text-
|
|
383
|
+
return <span className="text-lg sm:text-xl font-semibold break-words">{String(value)}</span>;
|
|
159
384
|
};
|
|
160
385
|
|
|
161
386
|
const MAX_STRUCT_DEPTH = 6;
|
|
@@ -181,9 +406,9 @@ export default function ContentCard({ item }) {
|
|
|
181
406
|
|
|
182
407
|
if (isSimple) {
|
|
183
408
|
return (
|
|
184
|
-
<div className="space-y-
|
|
409
|
+
<div className="space-y-3">
|
|
185
410
|
{items.map((entry, index) => (
|
|
186
|
-
<div key={`item-${index}`} className="text-base">
|
|
411
|
+
<div key={`item-${index}`} className="text-base sm:text-lg">
|
|
187
412
|
{renderValue(entry)}
|
|
188
413
|
</div>
|
|
189
414
|
))}
|
|
@@ -197,7 +422,7 @@ export default function ContentCard({ item }) {
|
|
|
197
422
|
{items.map((entry, index) => (
|
|
198
423
|
<div
|
|
199
424
|
key={`item-${index}`}
|
|
200
|
-
className="rounded-[20px] border border-black
|
|
425
|
+
className="rounded-[20px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-4"
|
|
201
426
|
>
|
|
202
427
|
{renderKeyValueGrid(Object.entries(entry), depth)}
|
|
203
428
|
</div>
|
|
@@ -211,9 +436,9 @@ export default function ContentCard({ item }) {
|
|
|
211
436
|
{items.map((entry, index) => (
|
|
212
437
|
<div
|
|
213
438
|
key={`item-${index}`}
|
|
214
|
-
className="space-y-4 rounded-[24px] border border-black
|
|
439
|
+
className="space-y-4 rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-5"
|
|
215
440
|
>
|
|
216
|
-
<div className="text-[
|
|
441
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
217
442
|
Item {index + 1}
|
|
218
443
|
</div>
|
|
219
444
|
{renderStructured(entry, depth + 1)}
|
|
@@ -245,13 +470,15 @@ export default function ContentCard({ item }) {
|
|
|
245
470
|
|
|
246
471
|
function renderKeyValueGrid(entries, depth = 0) {
|
|
247
472
|
return (
|
|
248
|
-
<div className="space-y-
|
|
473
|
+
<div className="space-y-6">
|
|
249
474
|
{entries.map(([key, value]) => (
|
|
250
|
-
<div key={key} className="
|
|
251
|
-
<div className="text-
|
|
475
|
+
<div key={key} className="grid gap-2 sm:grid-cols-[160px_1fr] sm:items-start min-w-0">
|
|
476
|
+
<div className="text-xs font-semibold text-black/50 dark:text-white/50 break-words">
|
|
252
477
|
{formatKey(key)}
|
|
253
478
|
</div>
|
|
254
|
-
<div className="text-base
|
|
479
|
+
<div className="text-base sm:text-lg leading-relaxed break-words min-w-0">
|
|
480
|
+
{renderStructured(value, depth + 1)}
|
|
481
|
+
</div>
|
|
255
482
|
</div>
|
|
256
483
|
))}
|
|
257
484
|
</div>
|
|
@@ -260,140 +487,400 @@ export default function ContentCard({ item }) {
|
|
|
260
487
|
|
|
261
488
|
let content = null;
|
|
262
489
|
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
if (promptSearchRequestId > 0) {
|
|
492
|
+
setShowPromptSearch(true);
|
|
493
|
+
}
|
|
494
|
+
}, [promptSearchRequestId]);
|
|
495
|
+
|
|
263
496
|
if (item.event === "AGENT_STARTED") {
|
|
264
497
|
const { cleanedPrompt, contextTitle, contextData, contextRaw } = extractContextFromPrompt(
|
|
265
498
|
item.prompt || ""
|
|
266
499
|
);
|
|
500
|
+
const promptSections = splitPromptSections(cleanedPrompt);
|
|
501
|
+
const trimmedPromptQuery = promptQuery.trim();
|
|
502
|
+
const normalizedPromptQuery = trimmedPromptQuery.toLowerCase();
|
|
503
|
+
const filteredPromptSections = trimmedPromptQuery
|
|
504
|
+
? promptSections.filter((section) =>
|
|
505
|
+
`${section.title}\n${section.content}`.toLowerCase().includes(normalizedPromptQuery)
|
|
506
|
+
)
|
|
507
|
+
: [];
|
|
508
|
+
const promptCountLabel = promptSections.length ? `${promptSections.length} sections` : null;
|
|
267
509
|
|
|
268
510
|
const contextTokenCount = estimateTokensFromText(contextRaw || "");
|
|
269
511
|
const contextMeta = contextRaw ? `~${formatTokenCount(contextTokenCount)}` : "";
|
|
270
512
|
|
|
271
513
|
content = (
|
|
272
|
-
<div className="space-y-
|
|
273
|
-
<div className="space-y-
|
|
514
|
+
<div className="space-y-10 py-6">
|
|
515
|
+
<div className="space-y-3 text-center">
|
|
274
516
|
<AgentStartedIcon />
|
|
275
|
-
<div className="space-y-
|
|
276
|
-
<
|
|
277
|
-
|
|
517
|
+
<div className="space-y-1">
|
|
518
|
+
<div className="text-[11px] font-semibold tracking-[0.28em] uppercase text-black/50 dark:text-white/50">
|
|
519
|
+
Agent started
|
|
520
|
+
</div>
|
|
521
|
+
<h2 className="text-3xl sm:text-4xl font-black tracking-tight">{item.agent}</h2>
|
|
278
522
|
</div>
|
|
279
523
|
</div>
|
|
280
524
|
|
|
281
525
|
{contextTitle && contextData !== null && (
|
|
282
|
-
<details className="group rounded-[24px] border border-black dark:border-white">
|
|
526
|
+
<details className="group rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04]">
|
|
283
527
|
<summary className="cursor-pointer select-none px-6 py-5 flex items-center justify-between gap-6 [&::-webkit-details-marker]:hidden">
|
|
284
528
|
<div className="flex items-center gap-3">
|
|
285
529
|
<ChevronRight className="w-4 h-4 transition-transform group-open:rotate-90" />
|
|
286
530
|
{contextTitle === "Current Context" ? (
|
|
287
531
|
<Brain className="w-4 h-4" aria-hidden="true" />
|
|
288
532
|
) : null}
|
|
289
|
-
<div className="text-
|
|
533
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/60 dark:text-white/60">
|
|
290
534
|
{contextTitle}
|
|
291
535
|
</div>
|
|
292
536
|
</div>
|
|
293
|
-
{contextMeta ? <div className="text-xs font-mono">{contextMeta}</div> : null}
|
|
537
|
+
{contextMeta ? <div className="text-xs font-mono text-black/50 dark:text-white/50">{contextMeta}</div> : null}
|
|
294
538
|
</summary>
|
|
295
539
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)}
|
|
302
|
-
|
|
540
|
+
{typeof contextData === "string" ? (
|
|
541
|
+
<PanelBody className="text-base sm:text-lg leading-relaxed whitespace-pre-wrap break-words text-black/80 dark:text-white/80">
|
|
542
|
+
{contextData}
|
|
543
|
+
</PanelBody>
|
|
544
|
+
) : (
|
|
545
|
+
<PanelBody>{renderStructured(contextData)}</PanelBody>
|
|
546
|
+
)}
|
|
303
547
|
</details>
|
|
304
548
|
)}
|
|
305
549
|
|
|
306
|
-
<div className="
|
|
307
|
-
|
|
550
|
+
<div className="space-y-4">
|
|
551
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
552
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
553
|
+
Prompt
|
|
554
|
+
</div>
|
|
555
|
+
{promptCountLabel ? (
|
|
556
|
+
<div className="text-xs font-mono text-black/40 dark:text-white/40">
|
|
557
|
+
{promptCountLabel}
|
|
558
|
+
</div>
|
|
559
|
+
) : null}
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<div className="space-y-4">
|
|
563
|
+
{promptSections.length > 0 ? (
|
|
564
|
+
promptSections.map((section, index) => {
|
|
565
|
+
const isInstructions = /(^| )instructions$/i.test(section.title.trim());
|
|
566
|
+
const shouldOpen = index === 0 || isInstructions;
|
|
567
|
+
const lineCount = section.content
|
|
568
|
+
? section.content.split(/\r?\n/).length
|
|
569
|
+
: 0;
|
|
570
|
+
return (
|
|
571
|
+
<details
|
|
572
|
+
key={`${section.title}-${index}`}
|
|
573
|
+
open={shouldOpen}
|
|
574
|
+
className="group rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04]"
|
|
575
|
+
>
|
|
576
|
+
<summary className="cursor-pointer select-none px-6 py-5 flex items-center justify-between gap-6 [&::-webkit-details-marker]:hidden">
|
|
577
|
+
<div className="flex items-center gap-3 min-w-0">
|
|
578
|
+
<ChevronRight className="w-4 h-4 transition-transform group-open:rotate-90" />
|
|
579
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/60 dark:text-white/60 break-words">
|
|
580
|
+
{section.title || "Prompt"}
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
{lineCount ? (
|
|
584
|
+
<div className="text-xs font-mono text-black/40 dark:text-white/40">
|
|
585
|
+
{lineCount} lines
|
|
586
|
+
</div>
|
|
587
|
+
) : null}
|
|
588
|
+
</summary>
|
|
589
|
+
{renderPromptSectionContent(section.content, "")}
|
|
590
|
+
</details>
|
|
591
|
+
);
|
|
592
|
+
})
|
|
593
|
+
) : (
|
|
594
|
+
<TextBlock>{cleanedPrompt}</TextBlock>
|
|
595
|
+
)}
|
|
596
|
+
</div>
|
|
308
597
|
</div>
|
|
598
|
+
|
|
599
|
+
{showPromptSearch ? (
|
|
600
|
+
<div className="fixed inset-0 z-40 bg-white text-black dark:bg-black dark:text-white">
|
|
601
|
+
<div className="h-full w-full overflow-y-auto custom-scroll px-6 sm:px-10 py-10">
|
|
602
|
+
<div className="max-w-3xl mx-auto space-y-8">
|
|
603
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
604
|
+
<div className="text-[11px] font-semibold tracking-[0.32em] uppercase text-black/50 dark:text-white/50">
|
|
605
|
+
Prompt search
|
|
606
|
+
</div>
|
|
607
|
+
<button
|
|
608
|
+
type="button"
|
|
609
|
+
onClick={() => setShowPromptSearch(false)}
|
|
610
|
+
className="text-[10px] font-bold tracking-[0.2em] uppercase text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white"
|
|
611
|
+
>
|
|
612
|
+
Close
|
|
613
|
+
</button>
|
|
614
|
+
</div>
|
|
615
|
+
|
|
616
|
+
<div className="relative">
|
|
617
|
+
<Search className="w-4 h-4 text-black/50 dark:text-white/60 absolute left-4 top-1/2 -translate-y-1/2" />
|
|
618
|
+
<input
|
|
619
|
+
value={promptQuery}
|
|
620
|
+
onChange={(event) => setPromptQuery(event.target.value)}
|
|
621
|
+
placeholder="Search the prompt..."
|
|
622
|
+
className="w-full rounded-full border border-black/20 dark:border-white/20 bg-transparent py-4 pl-11 pr-10 text-base text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-1 focus:ring-black/30 dark:focus:ring-white/40"
|
|
623
|
+
autoFocus
|
|
624
|
+
/>
|
|
625
|
+
{trimmedPromptQuery ? (
|
|
626
|
+
<button
|
|
627
|
+
type="button"
|
|
628
|
+
onClick={() => setPromptQuery("")}
|
|
629
|
+
aria-label="Clear prompt search"
|
|
630
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1 text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white"
|
|
631
|
+
>
|
|
632
|
+
<X className="w-3.5 h-3.5" />
|
|
633
|
+
</button>
|
|
634
|
+
) : null}
|
|
635
|
+
</div>
|
|
636
|
+
|
|
637
|
+
{trimmedPromptQuery ? (
|
|
638
|
+
filteredPromptSections.length > 0 ? (
|
|
639
|
+
<div className="space-y-4">
|
|
640
|
+
{filteredPromptSections.map((section, index) => {
|
|
641
|
+
const lineCount = section.content
|
|
642
|
+
? section.content.split(/\r?\n/).length
|
|
643
|
+
: 0;
|
|
644
|
+
return (
|
|
645
|
+
<div
|
|
646
|
+
key={`${section.title}-${index}`}
|
|
647
|
+
className="rounded-[22px] border border-black/10 dark:border-white/15 bg-black/[0.02] dark:bg-white/5 overflow-hidden"
|
|
648
|
+
>
|
|
649
|
+
<div className="px-5 py-4 flex items-center justify-between gap-4">
|
|
650
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/70 dark:text-white/70 break-words">
|
|
651
|
+
{highlightText(section.title || "Prompt", trimmedPromptQuery)}
|
|
652
|
+
</div>
|
|
653
|
+
{lineCount ? (
|
|
654
|
+
<div className="text-xs font-mono text-black/50 dark:text-white/50">
|
|
655
|
+
{lineCount} lines
|
|
656
|
+
</div>
|
|
657
|
+
) : null}
|
|
658
|
+
</div>
|
|
659
|
+
<div className="border-t border-black/10 dark:border-white/10 p-5">
|
|
660
|
+
<pre className="m-0 text-xs sm:text-sm font-mono leading-relaxed whitespace-pre-wrap break-words text-black/85 dark:text-white/85">
|
|
661
|
+
{highlightText(section.content || "", trimmedPromptQuery)}
|
|
662
|
+
</pre>
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
);
|
|
666
|
+
})}
|
|
667
|
+
</div>
|
|
668
|
+
) : (
|
|
669
|
+
<div className="rounded-[20px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/5 p-5 text-sm text-black/60 dark:text-white/60">
|
|
670
|
+
No prompt sections match "{trimmedPromptQuery}".
|
|
671
|
+
</div>
|
|
672
|
+
)
|
|
673
|
+
) : (
|
|
674
|
+
<div className="text-sm text-black/50 dark:text-white/50">
|
|
675
|
+
Start typing to filter prompt sections.
|
|
676
|
+
</div>
|
|
677
|
+
)}
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
) : null}
|
|
309
682
|
</div>
|
|
310
683
|
);
|
|
311
684
|
} else if (item.event && item.event.startsWith("WORKFLOW_")) {
|
|
312
685
|
const step = item.event.replace("WORKFLOW_", "");
|
|
313
686
|
content = (
|
|
314
|
-
<div className="min-h-[50vh] flex flex-col items-center justify-center text-center space-y-8 py-
|
|
315
|
-
<div className="text-[10px] font-
|
|
687
|
+
<div className="min-h-[50vh] flex flex-col items-center justify-center text-center space-y-8 py-16">
|
|
688
|
+
<div className="text-[10px] font-semibold tracking-[0.4em] uppercase text-black/50 dark:text-white/50">
|
|
316
689
|
Lifecycle status
|
|
317
690
|
</div>
|
|
318
691
|
|
|
319
|
-
<div className="w-full max-w-5xl rounded-[28px] border border-black
|
|
320
|
-
<h2 className="text-
|
|
692
|
+
<div className="w-full max-w-5xl rounded-[28px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-10">
|
|
693
|
+
<h2 className="text-4xl sm:text-6xl md:text-7xl font-black tracking-tighter leading-none uppercase">
|
|
321
694
|
{step}
|
|
322
695
|
</h2>
|
|
323
696
|
|
|
324
697
|
{item.error && (
|
|
325
|
-
<
|
|
326
|
-
{item.error}
|
|
327
|
-
</
|
|
698
|
+
<div className="mt-6">
|
|
699
|
+
<MonoBlock>{item.error}</MonoBlock>
|
|
700
|
+
</div>
|
|
328
701
|
)}
|
|
329
702
|
</div>
|
|
330
703
|
</div>
|
|
331
704
|
);
|
|
332
705
|
} else if (item.event === "PROMPT_REQUESTED" || item.event === "INTERACTION_REQUESTED") {
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
].filter((entry) => entry[1] !== undefined);
|
|
706
|
+
const isInteraction = item.event === "INTERACTION_REQUESTED";
|
|
707
|
+
const label = isInteraction ? "Interaction needed" : "Question asked";
|
|
708
|
+
const headline = item.question || item.prompt || "Prompt";
|
|
709
|
+
const agentName = item.agent || item.slug || "Agent";
|
|
338
710
|
|
|
339
711
|
content = (
|
|
340
|
-
<div className="
|
|
341
|
-
<div className="text-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
712
|
+
<div className="py-10 sm:py-14 space-y-8">
|
|
713
|
+
<div className="text-center space-y-4">
|
|
714
|
+
<AgentStartedIcon className="w-12 h-12" />
|
|
715
|
+
<div className="text-[11px] font-semibold tracking-[0.32em] uppercase text-black/50 dark:text-white/50">
|
|
716
|
+
{label}
|
|
717
|
+
</div>
|
|
718
|
+
<div className="text-lg sm:text-xl font-semibold text-black/70 dark:text-white/70">
|
|
719
|
+
{agentName} needs your input
|
|
720
|
+
</div>
|
|
721
|
+
</div>
|
|
722
|
+
<div className="text-3xl sm:text-4xl md:text-5xl font-black tracking-tight leading-tight text-center text-balance">
|
|
723
|
+
{headline}
|
|
345
724
|
</div>
|
|
346
725
|
|
|
347
|
-
{
|
|
348
|
-
<div className="text-
|
|
726
|
+
{shouldShowCountdown && countdown !== null && (
|
|
727
|
+
<div className="text-center">
|
|
728
|
+
<div className={`inline-flex items-center gap-2 px-4 py-2 rounded-full ${
|
|
729
|
+
countdown > 0
|
|
730
|
+
? "bg-black/5 dark:bg-white/10"
|
|
731
|
+
: "bg-black text-white dark:bg-white dark:text-black"
|
|
732
|
+
}`}>
|
|
733
|
+
<span className="text-xl">⚡</span>
|
|
734
|
+
<span className={`text-sm font-semibold ${
|
|
735
|
+
countdown > 0 ? "text-black/70 dark:text-white/70" : ""
|
|
736
|
+
}`}>
|
|
737
|
+
{countdown > 0
|
|
738
|
+
? `Agent deciding in ${countdown}s...`
|
|
739
|
+
: "Auto-selected"}
|
|
740
|
+
</span>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
349
743
|
)}
|
|
350
744
|
|
|
351
|
-
{
|
|
745
|
+
{interactionType === "choice" && interactionOptions.length > 0 && (
|
|
746
|
+
<div className="space-y-3 max-w-2xl mx-auto">
|
|
747
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50 text-center">
|
|
748
|
+
Options
|
|
749
|
+
</div>
|
|
750
|
+
<div className="space-y-2">
|
|
751
|
+
{interactionOptions.map((opt, index) => (
|
|
752
|
+
<div
|
|
753
|
+
key={opt.key || index}
|
|
754
|
+
className={`p-4 rounded-2xl border transition-all ${
|
|
755
|
+
index === 0
|
|
756
|
+
? "border-black bg-black text-white dark:border-white dark:bg-white dark:text-black"
|
|
757
|
+
: "border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.03]"
|
|
758
|
+
}`}
|
|
759
|
+
>
|
|
760
|
+
<div className="flex items-center gap-3">
|
|
761
|
+
<div className={`font-bold ${index === 0 ? "" : "text-black dark:text-white"}`}>
|
|
762
|
+
{opt.label || opt.key}
|
|
763
|
+
{index === 0 && (
|
|
764
|
+
<span className={`ml-2 text-xs font-medium px-2 py-0.5 rounded-full ${
|
|
765
|
+
"bg-white/20 dark:bg-black/20"
|
|
766
|
+
}`}>
|
|
767
|
+
Recommended
|
|
768
|
+
</span>
|
|
769
|
+
)}
|
|
770
|
+
</div>
|
|
771
|
+
</div>
|
|
772
|
+
{opt.description && (
|
|
773
|
+
<div className={`text-sm mt-1 ${
|
|
774
|
+
index === 0 ? "text-white/70 dark:text-black/70" : "text-black/50 dark:text-white/50"
|
|
775
|
+
}`}>
|
|
776
|
+
{opt.description}
|
|
777
|
+
</div>
|
|
778
|
+
)}
|
|
779
|
+
</div>
|
|
780
|
+
))}
|
|
781
|
+
</div>
|
|
782
|
+
{item.allowCustom && (
|
|
783
|
+
<div className="text-xs text-center text-black/40 dark:text-white/40 mt-2">
|
|
784
|
+
Custom response allowed
|
|
785
|
+
</div>
|
|
786
|
+
)}
|
|
787
|
+
</div>
|
|
788
|
+
)}
|
|
352
789
|
</div>
|
|
353
790
|
);
|
|
354
|
-
} else if (item.event === "
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
const details = [
|
|
358
|
-
["slug", item.slug],
|
|
359
|
-
["targetKey", item.targetKey],
|
|
360
|
-
].filter((entry) => entry[1] !== undefined);
|
|
791
|
+
} else if (item.event === "INTERACTION_AUTO_RESOLVED" || item.event === "PROMPT_AUTO_ANSWERED") {
|
|
792
|
+
const autoSelected = item.autoSelected || "Unknown";
|
|
793
|
+
const agentName = item.agent || item.slug || "Agent";
|
|
361
794
|
|
|
362
795
|
content = (
|
|
363
|
-
<div className="space-y-
|
|
364
|
-
<div className="
|
|
796
|
+
<div className="space-y-8 py-6">
|
|
797
|
+
<div className="space-y-4 text-center">
|
|
798
|
+
<div className="mx-auto w-14 h-14 rounded-full border-2 border-black dark:border-white bg-black dark:bg-white flex items-center justify-center">
|
|
799
|
+
<span className="text-2xl">⚡</span>
|
|
800
|
+
</div>
|
|
801
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
802
|
+
Auto-selected
|
|
803
|
+
</div>
|
|
804
|
+
<div className="text-lg sm:text-xl font-semibold text-black/70 dark:text-white/70">
|
|
805
|
+
{agentName} decided automatically
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
<div className="rounded-[24px] border-2 border-black bg-black text-white dark:border-white dark:bg-white dark:text-black p-6 text-center max-w-md mx-auto">
|
|
809
|
+
<div className="text-2xl sm:text-3xl font-black tracking-tight">
|
|
810
|
+
{autoSelected}
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
);
|
|
815
|
+
} else if (item.event === "INTERACTION_RESOLVED") {
|
|
816
|
+
const resolvedCopy =
|
|
817
|
+
item.source === "remote" ? "Remote response received." : "Response captured.";
|
|
818
|
+
const sourceCopy =
|
|
819
|
+
item.source === "remote" ? "Received from remote" : item.source ? `Received from ${item.source}` : "Received";
|
|
820
|
+
const agentName = item.agent || item.slug || "Agent";
|
|
365
821
|
|
|
366
|
-
|
|
367
|
-
|
|
822
|
+
content = (
|
|
823
|
+
<div className="space-y-8 py-6">
|
|
824
|
+
<div className="space-y-4 text-center">
|
|
825
|
+
<AgentStartedIcon className="w-12 h-12" />
|
|
826
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
827
|
+
Interaction resolved
|
|
828
|
+
</div>
|
|
829
|
+
<div className="text-lg sm:text-xl font-semibold text-black/70 dark:text-white/70">
|
|
830
|
+
{agentName} received your response
|
|
831
|
+
</div>
|
|
368
832
|
</div>
|
|
833
|
+
<div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 text-center">
|
|
834
|
+
<div className="text-xl sm:text-2xl font-semibold leading-relaxed text-balance">
|
|
835
|
+
{resolvedCopy}
|
|
836
|
+
</div>
|
|
837
|
+
<div className="mt-3 text-sm text-black/60 dark:text-white/60">
|
|
838
|
+
{sourceCopy}
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
);
|
|
843
|
+
} else if (item.event === "PROMPT_ANSWERED" || item.event === "INTERACTION_SUBMITTED") {
|
|
844
|
+
const responseValue = item.answer !== undefined ? item.answer : item.response;
|
|
369
845
|
|
|
370
|
-
|
|
846
|
+
content = (
|
|
847
|
+
<div className="space-y-8 py-6">
|
|
848
|
+
<div className="space-y-3">
|
|
849
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
850
|
+
Response sent
|
|
851
|
+
</div>
|
|
852
|
+
{renderInlineValue(responseValue)}
|
|
853
|
+
</div>
|
|
371
854
|
</div>
|
|
372
855
|
);
|
|
373
856
|
} else if (item.event === "AGENT_COMPLETED") {
|
|
374
|
-
const details = [
|
|
375
|
-
["agent", item.agent],
|
|
376
|
-
["attempts", item.attempts],
|
|
377
|
-
].filter((entry) => entry[1] !== undefined);
|
|
378
|
-
|
|
379
857
|
content = (
|
|
380
|
-
<div className="space-y-
|
|
381
|
-
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
858
|
+
<div className="space-y-10 py-6">
|
|
859
|
+
<div className="rounded-[28px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 sm:p-8 text-center space-y-4">
|
|
860
|
+
<AgentStartedIcon className="w-12 h-12" />
|
|
861
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
862
|
+
Agent completed
|
|
863
|
+
</div>
|
|
864
|
+
<div className="text-2xl sm:text-3xl font-black tracking-tight">
|
|
865
|
+
{item.agent || "Agent"}
|
|
866
|
+
</div>
|
|
867
|
+
{typeof item.attempts === "number" ? (
|
|
868
|
+
<div className="text-sm text-black/60 dark:text-white/60">
|
|
869
|
+
{item.attempts} {item.attempts === 1 ? "attempt" : "attempts"}
|
|
390
870
|
</div>
|
|
871
|
+
) : null}
|
|
872
|
+
</div>
|
|
391
873
|
|
|
392
|
-
|
|
874
|
+
{item.output !== undefined ? (
|
|
875
|
+
<div className="space-y-4">
|
|
876
|
+
<div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
|
|
877
|
+
Answer
|
|
878
|
+
</div>
|
|
879
|
+
<div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6">
|
|
880
|
+
{renderStructured(item.output)}
|
|
881
|
+
</div>
|
|
393
882
|
</div>
|
|
394
|
-
)}
|
|
395
|
-
|
|
396
|
-
{details.length > 0 && <div className="pt-4">{renderKeyValueGrid(details)}</div>}
|
|
883
|
+
) : null}
|
|
397
884
|
</div>
|
|
398
885
|
);
|
|
399
886
|
} else {
|
|
@@ -401,26 +888,35 @@ export default function ContentCard({ item }) {
|
|
|
401
888
|
([key]) => key !== "event" && key !== "timestamp"
|
|
402
889
|
);
|
|
403
890
|
|
|
404
|
-
const fallbackEntries = entries.length > 0 ? entries : [["event", item.event || "Event"]];
|
|
405
|
-
|
|
406
891
|
content = (
|
|
407
|
-
<div className="space-y-
|
|
408
|
-
<div className="
|
|
409
|
-
|
|
892
|
+
<div className="space-y-6 py-6">
|
|
893
|
+
<div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6">
|
|
894
|
+
<div className="text-lg sm:text-xl font-semibold leading-relaxed text-balance">
|
|
895
|
+
{item.event ? item.event.replace(/_/g, " ") : "Event"}
|
|
896
|
+
</div>
|
|
897
|
+
<div className="mt-3 text-sm text-black/60 dark:text-white/60">
|
|
898
|
+
{entries.length > 0 ? "Open Raw for full details." : "Open Raw for more info."}
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
410
901
|
</div>
|
|
411
902
|
);
|
|
412
903
|
}
|
|
413
904
|
|
|
414
905
|
return (
|
|
415
|
-
<div className="w-full h-full flex flex-col overflow-y-auto custom-scroll px-12 bg-white text-black dark:bg-black dark:text-white">
|
|
906
|
+
<div className="w-full h-full flex flex-col overflow-y-auto custom-scroll px-6 sm:px-10 lg:px-12 bg-white text-black dark:bg-black dark:text-white">
|
|
416
907
|
<div className="content-width flex-1">
|
|
417
|
-
<div className="flex items-center justify-between pt-10">
|
|
418
|
-
<div className="text-
|
|
419
|
-
|
|
908
|
+
<div className="flex flex-wrap items-center justify-between gap-4 pt-8 sm:pt-10 pb-6 border-b border-black/10 dark:border-white/10">
|
|
909
|
+
<div className="text-xs font-mono text-black/50 dark:text-white/50">{time}</div>
|
|
910
|
+
<div className="flex items-center gap-3">
|
|
911
|
+
<RawToggle open={showRaw} onToggle={() => setShowRaw((prev) => !prev)} />
|
|
912
|
+
<CopyButton text={item} />
|
|
420
913
|
</div>
|
|
421
|
-
<CopyButton text={item} />
|
|
422
914
|
</div>
|
|
423
|
-
{
|
|
915
|
+
{showRaw ? (
|
|
916
|
+
<pre className="raw-json-block">{renderJsonWithHighlight(item)}</pre>
|
|
917
|
+
) : (
|
|
918
|
+
content
|
|
919
|
+
)}
|
|
424
920
|
</div>
|
|
425
921
|
</div>
|
|
426
922
|
);
|