agent-state-machine 2.2.0 → 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 +78 -2
- package/lib/remote/client.js +37 -8
- 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/README.md +304 -56
- 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/sanity-runner.js +3 -1
- 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/README.md +291 -42
- package/templates/starter/config.js +1 -1
- package/vercel-server/api/submit/[token].js +2 -13
- package/vercel-server/api/ws/cli.js +40 -2
- package/vercel-server/local-server.js +32 -22
- 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 -62
- 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-BOKpYANC.js +0 -148
- package/vercel-server/public/remote/assets/index-DHL_iHQW.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-CmtT6ADh.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/remote/assets/index-BsJsLDKc.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -5,6 +5,8 @@ import EventsLog from "./components/EventsLog.jsx";
|
|
|
5
5
|
import Footer from "./components/Footer.jsx";
|
|
6
6
|
import Header from "./components/Header.jsx";
|
|
7
7
|
import InteractionForm from "./components/InteractionForm.jsx";
|
|
8
|
+
import SendingCard from "./components/SendingCard.jsx";
|
|
9
|
+
import { Search } from "lucide-react";
|
|
8
10
|
|
|
9
11
|
export default function App() {
|
|
10
12
|
const [history, setHistory] = useState([]);
|
|
@@ -15,6 +17,8 @@ export default function App() {
|
|
|
15
17
|
const [viewMode, setViewMode] = useState("present");
|
|
16
18
|
const [pendingInteraction, setPendingInteraction] = useState(null);
|
|
17
19
|
const [hasNew, setHasNew] = useState(false);
|
|
20
|
+
const [sendingState, setSendingState] = useState(null);
|
|
21
|
+
const [promptSearchRequestId, setPromptSearchRequestId] = useState(0);
|
|
18
22
|
|
|
19
23
|
useEffect(() => {
|
|
20
24
|
const savedTheme = localStorage.getItem("rf_theme") || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
@@ -76,6 +80,32 @@ export default function App() {
|
|
|
76
80
|
prevLen.current = history.length;
|
|
77
81
|
}, [history.length, pageIndex]);
|
|
78
82
|
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!sendingState || history.length === 0) return;
|
|
85
|
+
const latest = history[history.length - 1];
|
|
86
|
+
const hasNewEvent =
|
|
87
|
+
history.length !== sendingState.historyLength ||
|
|
88
|
+
(latest?.timestamp && latest.timestamp !== sendingState.lastEventTimestamp);
|
|
89
|
+
|
|
90
|
+
if (!hasNewEvent) return;
|
|
91
|
+
|
|
92
|
+
if (latest?.event === "INTERACTION_SUBMITTED") {
|
|
93
|
+
setSendingState((prev) =>
|
|
94
|
+
prev
|
|
95
|
+
? {
|
|
96
|
+
...prev,
|
|
97
|
+
historyLength: history.length,
|
|
98
|
+
lastEventTimestamp: latest?.timestamp || prev.lastEventTimestamp,
|
|
99
|
+
}
|
|
100
|
+
: prev
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setSendingState(null);
|
|
106
|
+
setPageIndex(history.length - 1);
|
|
107
|
+
}, [history, sendingState]);
|
|
108
|
+
|
|
79
109
|
useEffect(() => {
|
|
80
110
|
if (pageIndex === history.length - 1) {
|
|
81
111
|
setHasNew(false);
|
|
@@ -97,35 +127,6 @@ export default function App() {
|
|
|
97
127
|
|
|
98
128
|
const next = () => setPageIndex((prev) => Math.min(history.length - 1, prev + 1));
|
|
99
129
|
const prev = () => setPageIndex((prev) => Math.max(0, prev - 1));
|
|
100
|
-
const touchStart = useRef(null);
|
|
101
|
-
const ignoreTouch = useRef(false);
|
|
102
|
-
|
|
103
|
-
const handleTouchStart = (event) => {
|
|
104
|
-
const target = event.target;
|
|
105
|
-
const isInput = target.tagName === "TEXTAREA" || (target.tagName === "INPUT" && target.type === "number");
|
|
106
|
-
if (isInput && target.className !== "jumper-input") {
|
|
107
|
-
ignoreTouch.current = true;
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
ignoreTouch.current = false;
|
|
111
|
-
const touch = event.touches[0];
|
|
112
|
-
touchStart.current = { x: touch.clientX, y: touch.clientY };
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const handleTouchEnd = (event) => {
|
|
116
|
-
if (ignoreTouch.current || !touchStart.current) return;
|
|
117
|
-
const touch = event.changedTouches[0];
|
|
118
|
-
const dx = touch.clientX - touchStart.current.x;
|
|
119
|
-
const dy = touch.clientY - touchStart.current.y;
|
|
120
|
-
const absX = Math.abs(dx);
|
|
121
|
-
const absY = Math.abs(dy);
|
|
122
|
-
const swipeThreshold = 50;
|
|
123
|
-
if (absX > swipeThreshold && absX > absY + 10) {
|
|
124
|
-
if (dx > 0) prev();
|
|
125
|
-
if (dx < 0) next();
|
|
126
|
-
}
|
|
127
|
-
touchStart.current = null;
|
|
128
|
-
};
|
|
129
130
|
|
|
130
131
|
useEffect(() => {
|
|
131
132
|
const handler = (event) => {
|
|
@@ -139,8 +140,10 @@ export default function App() {
|
|
|
139
140
|
}, [history.length]);
|
|
140
141
|
|
|
141
142
|
const currentItem = history[pageIndex];
|
|
143
|
+
const isAgentStarted = currentItem?.event === "AGENT_STARTED";
|
|
142
144
|
const isRequestEvent = currentItem && (currentItem.event === "INTERACTION_REQUESTED" || currentItem.event === "PROMPT_REQUESTED");
|
|
143
145
|
const isRequest = pendingInteraction && isRequestEvent && currentItem.slug === pendingInteraction.slug;
|
|
146
|
+
const isSending = Boolean(sendingState);
|
|
144
147
|
|
|
145
148
|
return (
|
|
146
149
|
<div className="w-full h-[100dvh] flex flex-col relative overflow-hidden bg-bg">
|
|
@@ -167,54 +170,46 @@ export default function App() {
|
|
|
167
170
|
<AnimatePresence mode="wait">
|
|
168
171
|
<motion.div
|
|
169
172
|
key={pageIndex}
|
|
170
|
-
drag="x"
|
|
171
|
-
dragConstraints={{ left: 0, right: 0 }}
|
|
172
|
-
dragElastic={0.2}
|
|
173
|
-
style={{ touchAction: "pan-y" }}
|
|
174
|
-
onTouchStart={handleTouchStart}
|
|
175
|
-
onTouchEnd={handleTouchEnd}
|
|
176
|
-
onDragEnd={(e, { offset, velocity }) => {
|
|
177
|
-
const swipeThreshold = 50;
|
|
178
|
-
if (offset.x > swipeThreshold || velocity.x > 500) {
|
|
179
|
-
prev();
|
|
180
|
-
} else if (offset.x < -swipeThreshold || velocity.x < -500) {
|
|
181
|
-
next();
|
|
182
|
-
}
|
|
183
|
-
}}
|
|
184
173
|
initial={{ opacity: 0, scale: 0.99, filter: "blur(4px)" }}
|
|
185
174
|
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
|
|
186
175
|
exit={{ opacity: 0, scale: 1.01, filter: "blur(4px)" }}
|
|
187
176
|
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
|
188
177
|
className="w-full h-full"
|
|
189
178
|
>
|
|
190
|
-
{
|
|
179
|
+
{isSending ? (
|
|
180
|
+
<SendingCard submission={sendingState} />
|
|
181
|
+
) : isRequest ? (
|
|
191
182
|
<div className="content-width h-full">
|
|
192
183
|
<InteractionForm
|
|
193
184
|
interaction={pendingInteraction}
|
|
194
185
|
onSubmit={async (slug, targetKey, response) => {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
answer: responsePreview,
|
|
202
|
-
response
|
|
203
|
-
}
|
|
204
|
-
]);
|
|
205
|
-
setPageIndex((prev) => prev + 1);
|
|
206
|
-
await fetch(submitUrl, {
|
|
207
|
-
method: "POST",
|
|
208
|
-
headers: { "Content-Type": "application/json" },
|
|
209
|
-
body: JSON.stringify({ slug, targetKey, response })
|
|
186
|
+
const lastEvent = history[history.length - 1];
|
|
187
|
+
setSendingState({
|
|
188
|
+
slug,
|
|
189
|
+
targetKey,
|
|
190
|
+
historyLength: history.length,
|
|
191
|
+
lastEventTimestamp: lastEvent?.timestamp || null,
|
|
210
192
|
});
|
|
211
|
-
|
|
193
|
+
try {
|
|
194
|
+
const res = await fetch(submitUrl, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: { "Content-Type": "application/json" },
|
|
197
|
+
body: JSON.stringify({ slug, targetKey, response })
|
|
198
|
+
});
|
|
199
|
+
if (!res.ok) {
|
|
200
|
+
throw new Error(`Submit failed with ${res.status}`);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
setSendingState(null);
|
|
204
|
+
} finally {
|
|
205
|
+
setTimeout(fetchData, 1000);
|
|
206
|
+
}
|
|
212
207
|
}}
|
|
213
|
-
disabled={status === "disconnected"}
|
|
208
|
+
disabled={status === "disconnected" || isSending}
|
|
214
209
|
/>
|
|
215
210
|
</div>
|
|
216
211
|
) : (
|
|
217
|
-
<ContentCard item={currentItem} />
|
|
212
|
+
<ContentCard item={currentItem} promptSearchRequestId={promptSearchRequestId} />
|
|
218
213
|
)}
|
|
219
214
|
</motion.div>
|
|
220
215
|
</AnimatePresence>
|
|
@@ -230,6 +225,18 @@ export default function App() {
|
|
|
230
225
|
hasNew={hasNew}
|
|
231
226
|
onJumpToLatest={() => setPageIndex(history.length - 1)}
|
|
232
227
|
className={viewMode === "log" ? "opacity-0 pointer-events-none" : "opacity-100"}
|
|
228
|
+
leftSlot={
|
|
229
|
+
isAgentStarted ? (
|
|
230
|
+
<button
|
|
231
|
+
type="button"
|
|
232
|
+
onClick={() => setPromptSearchRequestId((prev) => prev + 1)}
|
|
233
|
+
className="w-12 h-12 rounded-full bg-white text-black dark:bg-black dark:text-white border border-black/10 dark:border-white/10 flex items-center justify-center shadow-2xl shadow-black/20 dark:shadow-white/10 hover:scale-[1.02] transition-transform"
|
|
234
|
+
aria-label="Search prompt sections"
|
|
235
|
+
>
|
|
236
|
+
<Search className="w-5 h-5" />
|
|
237
|
+
</button>
|
|
238
|
+
) : null
|
|
239
|
+
}
|
|
233
240
|
/>
|
|
234
241
|
</div>
|
|
235
242
|
);
|
|
@@ -1,15 +1,31 @@
|
|
|
1
|
-
import { useMemo, useState } from "react";
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { Bot, Check } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
export default function ChoiceInteraction({ interaction, onSubmit, disabled }) {
|
|
5
|
-
const { prompt, question, options = [], multiSelect, allowCustom } = interaction;
|
|
5
|
+
const { prompt, question, options = [], multiSelect, allowCustom, fullAuto, autoSelectDelay = 20, timestamp } = interaction;
|
|
6
6
|
const [selected, setSelected] = useState(multiSelect ? [] : null);
|
|
7
7
|
const [customText, setCustomText] = useState("");
|
|
8
8
|
const [showCustom, setShowCustom] = useState(false);
|
|
9
|
+
const [tick, setTick] = useState(0);
|
|
9
10
|
|
|
10
11
|
const list = useMemo(() => options || [], [options]);
|
|
11
12
|
const title = prompt || question || "Choose an option.";
|
|
12
13
|
|
|
14
|
+
// Calculate countdown based on event timestamp
|
|
15
|
+
const countdown = useMemo(() => {
|
|
16
|
+
if (!fullAuto || !timestamp) return null;
|
|
17
|
+
const eventTime = new Date(timestamp).getTime();
|
|
18
|
+
const elapsed = Math.floor((Date.now() - eventTime) / 1000);
|
|
19
|
+
return autoSelectDelay - elapsed;
|
|
20
|
+
}, [fullAuto, timestamp, autoSelectDelay, tick]);
|
|
21
|
+
|
|
22
|
+
// Tick every second to update countdown
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!fullAuto) return;
|
|
25
|
+
const timer = setInterval(() => setTick(t => t + 1), 1000);
|
|
26
|
+
return () => clearInterval(timer);
|
|
27
|
+
}, [fullAuto]);
|
|
28
|
+
|
|
13
29
|
const handleSelect = (key) => {
|
|
14
30
|
if (multiSelect) {
|
|
15
31
|
setSelected((prev) => (
|
|
@@ -44,19 +60,43 @@ export default function ChoiceInteraction({ interaction, onSubmit, disabled }) {
|
|
|
44
60
|
<div className="w-full h-full flex flex-col items-stretch overflow-hidden">
|
|
45
61
|
<div className="flex-1 overflow-y-auto custom-scroll px-6 py-12 space-y-8 flex flex-col items-center">
|
|
46
62
|
<div className="space-y-4 shrink-0">
|
|
47
|
-
<div className="w-16 h-16 rounded-3xl bg-
|
|
63
|
+
<div className="w-16 h-16 rounded-3xl bg-black text-white dark:bg-white dark:text-black flex items-center justify-center mx-auto shadow-2xl shadow-black/20 dark:shadow-white/10">
|
|
48
64
|
<Bot className="w-8 h-8" />
|
|
49
65
|
</div>
|
|
50
|
-
<h3 className="text-4xl font-extrabold tracking-tight text-fg pt-4 text-center">Choose an option.</h3>
|
|
66
|
+
<h3 className="text-3xl sm:text-4xl font-extrabold tracking-tight text-fg pt-4 text-center">Choose an option.</h3>
|
|
51
67
|
</div>
|
|
52
68
|
|
|
53
|
-
<div className="text-xl font-medium text-fg/70 text-center max-w-2xl whitespace-pre-wrap">
|
|
69
|
+
<div className="text-lg sm:text-xl font-medium text-fg/70 text-center max-w-2xl whitespace-pre-wrap break-words">
|
|
54
70
|
{title}
|
|
55
71
|
</div>
|
|
56
72
|
|
|
73
|
+
{fullAuto && countdown !== null && (
|
|
74
|
+
<div className="text-center">
|
|
75
|
+
<div className={`inline-flex items-center gap-2 px-4 py-2 rounded-full ${
|
|
76
|
+
countdown > 0
|
|
77
|
+
? "bg-yellow-500 text-black animate-pulse"
|
|
78
|
+
: "bg-black text-white dark:bg-white dark:text-black"
|
|
79
|
+
}`}>
|
|
80
|
+
<span className="text-xl">⚡</span>
|
|
81
|
+
<span className="text-sm font-bold">
|
|
82
|
+
{countdown > 0
|
|
83
|
+
? `Agent deciding in ${countdown}s...`
|
|
84
|
+
: "Auto-selecting recommended option..."}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
57
90
|
<div className="w-full max-w-2xl space-y-3">
|
|
58
|
-
{list.map((opt) => {
|
|
91
|
+
{list.map((opt, index) => {
|
|
59
92
|
const isSelected = multiSelect ? selected.includes(opt.key) : selected === opt.key;
|
|
93
|
+
const labelClass = isSelected
|
|
94
|
+
? "text-white dark:text-black"
|
|
95
|
+
: "text-black dark:text-white";
|
|
96
|
+
const descriptionClass = isSelected
|
|
97
|
+
? "text-white/70 dark:text-black/70"
|
|
98
|
+
: "text-black/50 dark:text-white/50";
|
|
99
|
+
|
|
60
100
|
return (
|
|
61
101
|
<button
|
|
62
102
|
key={opt.key}
|
|
@@ -65,21 +105,32 @@ export default function ChoiceInteraction({ interaction, onSubmit, disabled }) {
|
|
|
65
105
|
type="button"
|
|
66
106
|
className={`w-full p-6 rounded-2xl border-2 transition-all text-left ${
|
|
67
107
|
isSelected
|
|
68
|
-
? "border-
|
|
69
|
-
: "border-white/10 hover:border-white/
|
|
108
|
+
? "border-black bg-black text-white dark:border-white dark:bg-white dark:text-black"
|
|
109
|
+
: "border-black/10 dark:border-white/10 hover:border-black/30 dark:hover:border-white/30 bg-black/[0.02] dark:bg-white/[0.03]"
|
|
70
110
|
}`}
|
|
71
111
|
>
|
|
72
112
|
<div className="flex items-center gap-4">
|
|
73
113
|
<div className={`w-6 h-6 rounded-full border-2 flex items-center justify-center ${
|
|
74
114
|
isSelected
|
|
75
|
-
? "border-
|
|
76
|
-
: "border-white/
|
|
115
|
+
? "border-black bg-black text-white dark:border-white dark:bg-white dark:text-black"
|
|
116
|
+
: "border-black/30 dark:border-white/30"
|
|
77
117
|
}`}>
|
|
78
118
|
{isSelected && <Check className="w-4 h-4" />}
|
|
79
119
|
</div>
|
|
80
120
|
<div className="flex-1">
|
|
81
|
-
<div className=
|
|
82
|
-
|
|
121
|
+
<div className={`font-bold text-lg break-words flex flex-wrap items-center gap-2 ${labelClass}`}>
|
|
122
|
+
<span className="break-words">{opt.label || opt.key}</span>
|
|
123
|
+
{index === 0 && (
|
|
124
|
+
<span className={`ml-2 text-xs font-medium px-2 py-0.5 rounded-full ${
|
|
125
|
+
isSelected
|
|
126
|
+
? 'bg-white/20 dark:bg-black/20'
|
|
127
|
+
: 'bg-black/10 dark:bg-white/10'
|
|
128
|
+
}`}>
|
|
129
|
+
Recommended
|
|
130
|
+
</span>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
{opt.description && <div className={`text-sm mt-1 break-words ${descriptionClass}`}>{opt.description}</div>}
|
|
83
134
|
</div>
|
|
84
135
|
</div>
|
|
85
136
|
</button>
|
|
@@ -93,12 +144,12 @@ export default function ChoiceInteraction({ interaction, onSubmit, disabled }) {
|
|
|
93
144
|
type="button"
|
|
94
145
|
className={`w-full p-6 rounded-2xl border-2 transition-all text-left ${
|
|
95
146
|
showCustom
|
|
96
|
-
? "border-
|
|
97
|
-
: "border-white/10 hover:border-white/
|
|
147
|
+
? "border-black bg-black text-white dark:border-white dark:bg-white dark:text-black"
|
|
148
|
+
: "border-black/10 dark:border-white/10 hover:border-black/30 dark:hover:border-white/30 bg-black/[0.02] dark:bg-white/[0.03]"
|
|
98
149
|
}`}
|
|
99
150
|
>
|
|
100
|
-
<div className=
|
|
101
|
-
<div className=
|
|
151
|
+
<div className={`font-bold text-lg break-words ${showCustom ? "text-white dark:text-black" : "text-black dark:text-white"}`}>Other</div>
|
|
152
|
+
<div className={`text-sm mt-1 break-words ${showCustom ? "text-white/70 dark:text-black/70" : "text-black/50 dark:text-white/50"}`}>Provide a custom response</div>
|
|
102
153
|
</button>
|
|
103
154
|
)}
|
|
104
155
|
|
|
@@ -107,13 +158,13 @@ export default function ChoiceInteraction({ interaction, onSubmit, disabled }) {
|
|
|
107
158
|
value={customText}
|
|
108
159
|
onChange={(event) => setCustomText(event.target.value)}
|
|
109
160
|
placeholder="Type your response..."
|
|
110
|
-
className="w-full h-32 p-6 rounded-2xl bg-black/[0.
|
|
161
|
+
className="w-full h-32 p-6 rounded-2xl bg-black/[0.02] dark:bg-white/[0.03] border-2 border-black/20 dark:border-white/20 focus:border-black dark:focus:border-white focus:outline-none text-lg"
|
|
111
162
|
/>
|
|
112
163
|
)}
|
|
113
164
|
</div>
|
|
114
165
|
</div>
|
|
115
166
|
|
|
116
|
-
<div className="p-4 flex justify-center bg-gradient-to-t from-bg via-bg to-transparent shrink-0 border-t border-white/
|
|
167
|
+
<div className="p-4 flex justify-center bg-gradient-to-t from-bg via-bg to-transparent shrink-0 border-t border-black/10 dark:border-white/10">
|
|
117
168
|
<button
|
|
118
169
|
onClick={handleSubmit}
|
|
119
170
|
disabled={disabled || !isValid}
|
|
@@ -13,35 +13,35 @@ export default function ConfirmInteraction({ interaction, onSubmit, disabled })
|
|
|
13
13
|
<div className="w-full h-full flex flex-col items-stretch overflow-hidden">
|
|
14
14
|
<div className="flex-1 overflow-y-auto custom-scroll px-6 py-12 space-y-8 flex flex-col items-center justify-center">
|
|
15
15
|
<div className="space-y-4">
|
|
16
|
-
<div className="w-16 h-16 rounded-3xl bg-
|
|
16
|
+
<div className="w-16 h-16 rounded-3xl bg-black text-white dark:bg-white dark:text-black flex items-center justify-center mx-auto shadow-2xl shadow-black/20 dark:shadow-white/10">
|
|
17
17
|
<Bot className="w-8 h-8" />
|
|
18
18
|
</div>
|
|
19
|
-
<h3 className="text-4xl font-extrabold tracking-tight text-fg pt-4 text-center">Confirm action.</h3>
|
|
19
|
+
<h3 className="text-3xl sm:text-4xl font-extrabold tracking-tight text-fg pt-4 text-center">Confirm action.</h3>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
-
<div className="text-xl font-medium text-fg/70 text-center max-w-2xl whitespace-pre-wrap">
|
|
22
|
+
<div className="text-lg sm:text-xl font-medium text-fg/70 text-center max-w-2xl whitespace-pre-wrap break-words">
|
|
23
23
|
{prompt || question || "Please confirm."}
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
{context?.documentPath && (
|
|
27
27
|
<div className="text-sm text-fg/40 text-center">
|
|
28
|
-
Review: <code className="bg-white/10 px-2 py-1 rounded">{context.documentPath}</code>
|
|
28
|
+
Review: <code className="bg-black/10 dark:bg-white/10 px-2 py-1 rounded">{context.documentPath}</code>
|
|
29
29
|
</div>
|
|
30
30
|
)}
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
|
-
<div className="p-4 flex justify-center gap-4 bg-gradient-to-t from-bg via-bg to-transparent shrink-0 border-t border-white/
|
|
33
|
+
<div className="p-4 flex justify-center gap-4 bg-gradient-to-t from-bg via-bg to-transparent shrink-0 border-t border-black/10 dark:border-white/10">
|
|
34
34
|
<button
|
|
35
35
|
onClick={() => onSubmit({ confirmed: false, raw: cancelLabel })}
|
|
36
36
|
disabled={disabled}
|
|
37
|
-
className="px-12 py-6
|
|
37
|
+
className="px-12 py-6 border border-black/20 dark:border-white/20 text-fg rounded-full font-bold text-xl hover:bg-black/5 dark:hover:bg-white/10 transition-all disabled:opacity-30"
|
|
38
38
|
>
|
|
39
39
|
{cancelLabel}
|
|
40
40
|
</button>
|
|
41
41
|
<button
|
|
42
42
|
onClick={() => onSubmit({ confirmed: true, raw: confirmLabel })}
|
|
43
43
|
disabled={disabled}
|
|
44
|
-
className="px-12 py-6 bg-fg text-bg rounded-full font-bold text-xl hover:scale-105 active:scale-95 transition-all disabled:opacity-30 shadow-2xl"
|
|
44
|
+
className="px-12 py-6 bg-fg text-bg rounded-full font-bold text-xl hover:scale-105 active:scale-95 transition-all disabled:opacity-30 shadow-2xl shadow-black/20 dark:shadow-white/10"
|
|
45
45
|
>
|
|
46
46
|
{confirmLabel}
|
|
47
47
|
</button>
|