@contentgrowth/llm-service 0.8.2 → 0.8.4

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.
@@ -0,0 +1,1344 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/ui/react/components/ChatBubble.tsx
9
+ import { jsx } from "react/jsx-runtime";
10
+ function ChatBubble({ onClick }) {
11
+ return /* @__PURE__ */ jsx(
12
+ "button",
13
+ {
14
+ onClick,
15
+ className: "bg-blue-500 hover:bg-blue-600 text-white rounded-full w-12 h-12 flex items-center justify-center shadow-lg animate-pulse",
16
+ "aria-label": "Open chat assistant",
17
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" }) })
18
+ }
19
+ );
20
+ }
21
+
22
+ // src/ui/react/components/MessageBubble.tsx
23
+ import ReactMarkdown from "react-markdown";
24
+ import remarkGfm from "remark-gfm";
25
+ import { SparklesIcon } from "@heroicons/react/24/solid";
26
+
27
+ // src/ui/react/context/ChatConfigContext.tsx
28
+ import { createContext, useContext } from "react";
29
+ import { jsx as jsx2 } from "react/jsx-runtime";
30
+ var ChatConfigContext = createContext({});
31
+ var ChatConfigProvider = ({ config, children }) => {
32
+ return /* @__PURE__ */ jsx2(ChatConfigContext.Provider, { value: config, children });
33
+ };
34
+ var useChatConfig = () => useContext(ChatConfigContext);
35
+
36
+ // src/ui/react/components/MessageBubble.tsx
37
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
38
+ var MessageBubble = ({
39
+ message,
40
+ isUser,
41
+ userAvatarUrl,
42
+ onViewChanges
43
+ }) => {
44
+ const { user } = useChatConfig();
45
+ const finalAvatarUrl = userAvatarUrl || (user == null ? void 0 : user.avatarUrl);
46
+ const isSystem = message.role === "system";
47
+ if (isSystem) {
48
+ return /* @__PURE__ */ jsx3("div", { className: "text-center my-4", children: /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500 italic bg-gray-100 px-3 py-1 rounded-full", children: [
49
+ message.content,
50
+ message.diff && onViewChanges && /* @__PURE__ */ jsx3(
51
+ "button",
52
+ {
53
+ onClick: () => onViewChanges(message.diff),
54
+ className: "ml-2 font-semibold text-blue-600 hover:underline focus:outline-none",
55
+ children: "View Changes"
56
+ }
57
+ )
58
+ ] }) });
59
+ }
60
+ return /* @__PURE__ */ jsxs("div", { className: `flex items-start gap-3 my-4 ${isUser ? "justify-end" : "justify-start"}`, children: [
61
+ !isUser && /* @__PURE__ */ jsx3("div", { className: "flex-shrink-0 h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center text-white", children: /* @__PURE__ */ jsx3(SparklesIcon, { className: "h-5 w-5" }) }),
62
+ /* @__PURE__ */ jsxs(
63
+ "div",
64
+ {
65
+ className: `max-w-[85%] px-4 py-3 rounded-2xl shadow-sm overflow-hidden ${isUser ? "bg-blue-500 text-white rounded-br-none" : "bg-gray-100 text-gray-800 rounded-bl-none"}`,
66
+ children: [
67
+ /* @__PURE__ */ jsx3("div", { className: `text-sm ${!isUser ? "prose prose-sm max-w-none" : ""}`, children: isUser ? /* @__PURE__ */ jsx3("p", { className: "whitespace-pre-wrap", children: message.content }) : /* @__PURE__ */ jsx3(
68
+ ReactMarkdown,
69
+ {
70
+ remarkPlugins: [remarkGfm],
71
+ components: {
72
+ p: ({ node, ...props }) => /* @__PURE__ */ jsx3("p", { className: "mb-2 last:mb-0", ...props }),
73
+ ul: ({ node, ...props }) => /* @__PURE__ */ jsx3("ul", { className: "list-disc ml-4 mb-2", ...props }),
74
+ ol: ({ node, ...props }) => /* @__PURE__ */ jsx3("ol", { className: "list-decimal ml-4 mb-2", ...props }),
75
+ li: ({ node, ...props }) => /* @__PURE__ */ jsx3("li", { className: "mb-0.5", ...props }),
76
+ a: ({ node, ...props }) => /* @__PURE__ */ jsx3("a", { className: "text-blue-600 hover:underline", ...props }),
77
+ code: ({ node, ...props }) => /* @__PURE__ */ jsx3("code", { className: "bg-gray-200 rounded px-1 py-0.5 text-xs font-mono", ...props })
78
+ },
79
+ children: message.content
80
+ }
81
+ ) }),
82
+ message.role === "assistant" && message.diff && onViewChanges && /* @__PURE__ */ jsx3(
83
+ "button",
84
+ {
85
+ onClick: () => onViewChanges(message.diff),
86
+ className: "mt-2 text-sm text-blue-500 hover:underline block",
87
+ children: "View Changes"
88
+ }
89
+ ),
90
+ message.actionResult && message.actionResult.type === "link" && /* @__PURE__ */ jsx3("div", { className: "mt-2 pt-2 border-t border-gray-300", children: /* @__PURE__ */ jsxs(
91
+ "a",
92
+ {
93
+ href: message.actionResult.url,
94
+ target: message.actionResult.target || "_blank",
95
+ rel: "noopener noreferrer",
96
+ onClick: (e) => {
97
+ var _a;
98
+ if (((_a = message.actionResult) == null ? void 0 : _a.target) && message.actionResult.target !== "_blank") {
99
+ e.preventDefault();
100
+ window.open(message.actionResult.url, message.actionResult.target);
101
+ }
102
+ },
103
+ className: `inline-flex items-center gap-1 text-sm font-medium hover:underline ${isUser ? "text-blue-100 hover:text-white" : "text-blue-600 hover:text-blue-800"}`,
104
+ children: [
105
+ message.actionResult.label,
106
+ /* @__PURE__ */ jsx3("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) })
107
+ ]
108
+ }
109
+ ) })
110
+ ]
111
+ }
112
+ ),
113
+ isUser && /* @__PURE__ */ jsx3("div", { className: "flex-shrink-0 h-8 w-8 rounded-full bg-gray-200 overflow-hidden flex items-center justify-center text-gray-500", children: finalAvatarUrl ? /* @__PURE__ */ jsx3(
114
+ "img",
115
+ {
116
+ className: "h-full w-full object-cover",
117
+ src: finalAvatarUrl,
118
+ alt: "User",
119
+ onError: (e) => {
120
+ var _a;
121
+ e.target.style.display = "none";
122
+ (_a = e.target.parentElement) == null ? void 0 : _a.classList.remove("bg-gray-200");
123
+ }
124
+ }
125
+ ) : /* @__PURE__ */ jsx3("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5", children: /* @__PURE__ */ jsx3("path", { fillRule: "evenodd", d: "M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z", clipRule: "evenodd" }) }) })
126
+ ] });
127
+ };
128
+
129
+ // src/ui/react/components/ChatHeader.tsx
130
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
131
+ function ChatHeader({
132
+ contextTitle,
133
+ currentTask,
134
+ onClose
135
+ }) {
136
+ return /* @__PURE__ */ jsxs2("div", { className: "bg-blue-500 text-white px-4 py-4 flex justify-between items-center", children: [
137
+ /* @__PURE__ */ jsx4("h3", { className: "font-medium text-lg", children: currentTask ? `DoveText Virtual Assistant (${currentTask.currentStep}/${currentTask.steps})` : `DoveText Virtual Assistant (${contextTitle})` }),
138
+ /* @__PURE__ */ jsx4(
139
+ "button",
140
+ {
141
+ onClick: onClose,
142
+ className: "bg-blue-600 hover:bg-blue-700 text-white rounded-full p-1.5 flex items-center justify-center transition-colors cursor-pointer",
143
+ "aria-label": "Close chat",
144
+ children: /* @__PURE__ */ jsx4("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx4("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }) })
145
+ }
146
+ )
147
+ ] });
148
+ }
149
+
150
+ // src/ui/react/components/ChatInputArea.tsx
151
+ import { useState as useState3, useRef as useRef3, useImperativeHandle, forwardRef } from "react";
152
+ import { StopIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
153
+
154
+ // src/ui/react/hooks/useSpeechRecognition.ts
155
+ import { useState, useEffect, useCallback, useRef } from "react";
156
+ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
157
+ const [isListening, setIsListening] = useState(false);
158
+ const [transcript, setTranscript] = useState("");
159
+ const [error, setError] = useState(null);
160
+ const [isSupported, setIsSupported] = useState(false);
161
+ const recognitionRef = useRef(null);
162
+ useEffect(() => {
163
+ if (typeof window !== "undefined") {
164
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
165
+ if (SpeechRecognition) {
166
+ setIsSupported(true);
167
+ const recognition = new SpeechRecognition();
168
+ recognition.continuous = true;
169
+ recognition.interimResults = true;
170
+ recognition.lang = language;
171
+ recognition.onstart = () => {
172
+ setIsListening(true);
173
+ setError(null);
174
+ };
175
+ recognition.onend = () => {
176
+ setIsListening(false);
177
+ if (onEnd) onEnd();
178
+ };
179
+ recognition.onresult = (event) => {
180
+ let interimTranscript = "";
181
+ let finalTranscript = "";
182
+ for (let i = event.results.length - 1; i < event.results.length; ++i) {
183
+ const result = event.results[i];
184
+ if (result.isFinal) {
185
+ finalTranscript += result[0].transcript;
186
+ if (onResult) onResult(finalTranscript, true);
187
+ } else {
188
+ interimTranscript += result[0].transcript;
189
+ if (onResult) onResult(interimTranscript, false);
190
+ }
191
+ }
192
+ setTranscript((prev) => prev + finalTranscript);
193
+ };
194
+ recognition.onerror = (event) => {
195
+ if (event.error === "not-allowed" && process.env.NODE_ENV === "development") {
196
+ console.warn("Speech recognition blocked. Simulating input for development...");
197
+ setError(null);
198
+ setIsListening(true);
199
+ setTimeout(() => {
200
+ const mockText = "This is a simulated voice input for testing.";
201
+ setTranscript((prev) => prev + (prev ? " " : "") + mockText);
202
+ if (onResult) onResult(mockText, true);
203
+ setIsListening(false);
204
+ if (onEnd) onEnd();
205
+ }, 1e3);
206
+ return;
207
+ }
208
+ console.error("Speech recognition error", event.error);
209
+ setError(event.error);
210
+ setIsListening(false);
211
+ };
212
+ recognitionRef.current = recognition;
213
+ }
214
+ }
215
+ return () => {
216
+ if (recognitionRef.current) {
217
+ recognitionRef.current.stop();
218
+ }
219
+ };
220
+ }, [onResult, onEnd]);
221
+ const start = useCallback(() => {
222
+ if (recognitionRef.current && !isListening) {
223
+ try {
224
+ setTranscript("");
225
+ recognitionRef.current.start();
226
+ } catch (e) {
227
+ console.error("Failed to start speech recognition:", e);
228
+ }
229
+ }
230
+ }, [isListening]);
231
+ const stop = useCallback(() => {
232
+ if (recognitionRef.current && isListening) {
233
+ recognitionRef.current.stop();
234
+ }
235
+ }, [isListening]);
236
+ const resetTranscript = useCallback(() => {
237
+ setTranscript("");
238
+ }, []);
239
+ return {
240
+ isListening,
241
+ transcript,
242
+ start,
243
+ stop,
244
+ resetTranscript,
245
+ isSupported,
246
+ error
247
+ };
248
+ };
249
+
250
+ // src/ui/react/hooks/useAudioRecorder.ts
251
+ import { useState as useState2, useRef as useRef2, useCallback as useCallback2 } from "react";
252
+ var useAudioRecorder = (onStop) => {
253
+ const [isRecording, setIsRecording] = useState2(false);
254
+ const [blob, setBlob] = useState2(null);
255
+ const [error, setError] = useState2(null);
256
+ const mediaRecorderRef = useRef2(null);
257
+ const chunksRef = useRef2([]);
258
+ const start = useCallback2(async () => {
259
+ try {
260
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
261
+ const mediaRecorder = new MediaRecorder(stream);
262
+ mediaRecorderRef.current = mediaRecorder;
263
+ chunksRef.current = [];
264
+ mediaRecorder.ondataavailable = (e) => {
265
+ if (e.data.size > 0) {
266
+ chunksRef.current.push(e.data);
267
+ }
268
+ };
269
+ mediaRecorder.onstop = () => {
270
+ const audioBlob = new Blob(chunksRef.current, { type: "audio/webm" });
271
+ setBlob(audioBlob);
272
+ setIsRecording(false);
273
+ if (onStop) onStop(audioBlob);
274
+ stream.getTracks().forEach((track) => track.stop());
275
+ };
276
+ mediaRecorder.start();
277
+ setIsRecording(true);
278
+ setError(null);
279
+ } catch (e) {
280
+ console.error("Failed to start audio recording:", e);
281
+ setError(e.message || "Microphone access denied");
282
+ }
283
+ }, [onStop]);
284
+ const stop = useCallback2(() => {
285
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
286
+ mediaRecorderRef.current.stop();
287
+ }
288
+ }, []);
289
+ return {
290
+ isRecording,
291
+ start,
292
+ stop,
293
+ blob,
294
+ error
295
+ };
296
+ };
297
+
298
+ // src/ui/react/hooks/useProactiveResize.ts
299
+ import { useEffect as useEffect2 } from "react";
300
+ function useProactiveResize(textareaRef, measurementRef, value, disabled) {
301
+ useEffect2(() => {
302
+ if (!textareaRef.current || !measurementRef.current) return;
303
+ const styles = window.getComputedStyle(textareaRef.current);
304
+ measurementRef.current.style.width = styles.width;
305
+ measurementRef.current.style.font = styles.font;
306
+ measurementRef.current.style.padding = styles.padding;
307
+ measurementRef.current.style.border = styles.border;
308
+ measurementRef.current.style.boxSizing = styles.boxSizing;
309
+ measurementRef.current.style.whiteSpace = "pre-wrap";
310
+ measurementRef.current.style.visibility = "hidden";
311
+ measurementRef.current.style.position = "absolute";
312
+ measurementRef.current.style.pointerEvents = "none";
313
+ measurementRef.current.textContent = value + "\u200B";
314
+ const scrollHeight = measurementRef.current.offsetHeight;
315
+ textareaRef.current.style.height = `${scrollHeight}px`;
316
+ }, [value, disabled, textareaRef, measurementRef]);
317
+ }
318
+
319
+ // src/ui/react/components/ChatInputArea.tsx
320
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
321
+ var ChatInputArea = forwardRef(({
322
+ onSubmit,
323
+ isSending,
324
+ showInputForm,
325
+ currentTask,
326
+ lastInteractiveMessage,
327
+ voiceConfig: propVoiceConfig,
328
+ // Rename to distinguish from context
329
+ onStop,
330
+ hintText,
331
+ placeholder,
332
+ value,
333
+ onChange
334
+ }, ref) => {
335
+ var _a, _b, _c, _d;
336
+ const [internalMessage, setInternalMessage] = useState3("");
337
+ const [isVoiceActive, setIsVoiceActive] = useState3(false);
338
+ const [inputMode, setInputMode] = useState3("text");
339
+ const textareaRef = useRef3(null);
340
+ const measurementRef = useRef3(null);
341
+ const isControlled = value !== void 0;
342
+ const message = isControlled ? value : internalMessage;
343
+ const { voice: globalVoice } = useChatConfig();
344
+ const isVoiceEnabled = (_a = globalVoice == null ? void 0 : globalVoice.enabled) != null ? _a : !!propVoiceConfig;
345
+ const voiceConfig = isVoiceEnabled ? propVoiceConfig || (globalVoice == null ? void 0 : globalVoice.config) : void 0;
346
+ const triggerChange = (newValue) => {
347
+ if (isControlled && onChange && textareaRef.current) {
348
+ const syntheticEvent = {
349
+ target: { value: newValue },
350
+ currentTarget: { value: newValue }
351
+ };
352
+ onChange(syntheticEvent);
353
+ } else {
354
+ setInternalMessage(newValue);
355
+ }
356
+ };
357
+ const isInputDisabled = (currentTask == null ? void 0 : currentTask.complete) || (lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactive) && (((_b = lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) == null ? void 0 : _b.function) === "form" && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted) || ((_c = lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) == null ? void 0 : _c.function) === "confirm" && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted));
358
+ useProactiveResize(textareaRef, measurementRef, message, isInputDisabled || isVoiceActive || inputMode === "voice");
359
+ const nativeSpeech = useSpeechRecognition((text) => {
360
+ triggerChange(message + (message ? " " : "") + text);
361
+ }, () => {
362
+ var _a2;
363
+ setIsVoiceActive(false);
364
+ (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceEnd) == null ? void 0 : _a2.call(voiceConfig);
365
+ }, voiceConfig == null ? void 0 : voiceConfig.language);
366
+ const customRecorder = useAudioRecorder(async (blob) => {
367
+ var _a2;
368
+ setIsVoiceActive(false);
369
+ (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceEnd) == null ? void 0 : _a2.call(voiceConfig);
370
+ if (voiceConfig == null ? void 0 : voiceConfig.onAudioCapture) {
371
+ try {
372
+ const text = await voiceConfig.onAudioCapture(blob);
373
+ if (text) triggerChange(message + (message ? " " : "") + text);
374
+ } catch (e) {
375
+ console.error("Audio capture failed", e);
376
+ }
377
+ }
378
+ });
379
+ useImperativeHandle(ref, () => ({
380
+ focus: () => {
381
+ var _a2;
382
+ (_a2 = textareaRef.current) == null ? void 0 : _a2.focus();
383
+ },
384
+ setValue: (newValue) => {
385
+ triggerChange(newValue);
386
+ }
387
+ }));
388
+ const handleSubmit = (e) => {
389
+ if (e) e.preventDefault();
390
+ if (!message.trim()) {
391
+ setTimeout(() => {
392
+ var _a2;
393
+ return (_a2 = textareaRef.current) == null ? void 0 : _a2.focus();
394
+ }, 0);
395
+ return;
396
+ }
397
+ const currentMessage = message;
398
+ triggerChange("");
399
+ setTimeout(() => {
400
+ var _a2;
401
+ return (_a2 = textareaRef.current) == null ? void 0 : _a2.focus();
402
+ }, 0);
403
+ onSubmit(currentMessage);
404
+ };
405
+ const handleKeyDown = (e) => {
406
+ if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.altKey) {
407
+ e.preventDefault();
408
+ handleSubmit();
409
+ }
410
+ };
411
+ const startRecording = async () => {
412
+ var _a2;
413
+ if (isVoiceActive) return;
414
+ setIsVoiceActive(true);
415
+ (_a2 = voiceConfig == null ? void 0 : voiceConfig.onVoiceStart) == null ? void 0 : _a2.call(voiceConfig);
416
+ if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
417
+ if (!nativeSpeech.isSupported) {
418
+ alert("Speech recognition is not supported in this browser.");
419
+ setIsVoiceActive(false);
420
+ return;
421
+ }
422
+ nativeSpeech.start();
423
+ } else {
424
+ await customRecorder.start();
425
+ }
426
+ };
427
+ const stopRecording = () => {
428
+ if (!isVoiceActive) return;
429
+ if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
430
+ nativeSpeech.stop();
431
+ } else {
432
+ customRecorder.stop();
433
+ }
434
+ };
435
+ const getPlaceholder = () => {
436
+ if (placeholder) return placeholder;
437
+ if (isVoiceActive) return "Listening...";
438
+ if (currentTask == null ? void 0 : currentTask.complete) {
439
+ return "Task completed!";
440
+ }
441
+ if ((lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactive) && (lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.isResponseSubmitted)) {
442
+ const interactiveType = lastInteractiveMessage.interactiveData.function;
443
+ switch (interactiveType) {
444
+ case "chat":
445
+ return "Type your response...";
446
+ case "confirm":
447
+ return "Select Yes or No, or type your response...";
448
+ case "select":
449
+ return "Choose an option or type your response...";
450
+ case "form":
451
+ return "Fill out the form...";
452
+ default:
453
+ return "Type your response...";
454
+ }
455
+ }
456
+ return currentTask ? "Continue your conversation..." : "Ask me anything...";
457
+ };
458
+ const inputHint = !isInputDisabled && !(lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactive) ? null : ((_d = lastInteractiveMessage == null ? void 0 : lastInteractiveMessage.interactiveData) == null ? void 0 : _d.function) === "form" ? "Please provide information by completing above form" : null;
459
+ if (!showInputForm) {
460
+ return null;
461
+ }
462
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col w-full", children: [
463
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
464
+ voiceConfig && /* @__PURE__ */ jsx5(
465
+ "button",
466
+ {
467
+ type: "button",
468
+ onClick: () => {
469
+ if (inputMode === "voice" && isVoiceActive) {
470
+ stopRecording();
471
+ }
472
+ setInputMode((prev) => prev === "text" ? "voice" : "text");
473
+ },
474
+ className: "mb-1 p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0 border border-gray-300 bg-white",
475
+ title: inputMode === "text" ? "Switch to Voice" : "Switch to Keyboard",
476
+ children: inputMode === "text" ? (
477
+ // Voice Icon (Waveform)
478
+ /* @__PURE__ */ jsx5("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 text-gray-600", children: /* @__PURE__ */ jsx5("path", { d: "M11.25 4.532A.75.75 0 0 1 12 5.25v13.5a.75.75 0 0 1-1.5 0V5.25a.75.75 0 0 1 .75-.718ZM7.5 8.25a.75.75 0 0 1 .75.75v5.25a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm9 0a.75.75 0 0 1 .75.75v5.25a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75ZM3.75 10.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Zm16.5 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Z" }) })
479
+ ) : (
480
+ // Keyboard Icon (Filled)
481
+ /* @__PURE__ */ jsx5("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 text-gray-600", children: /* @__PURE__ */ jsx5("path", { fillRule: "evenodd", d: "M3 6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm4.5 3a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75V9Zm6 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75V9Zm6 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75V9Zm-12 4.5a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1-.75-.75v-1.5Zm6 0a.75.75 0 0 1 .75-.75h6.75a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-.75.75h-6.75a.75.75 0 0 1-.75-.75v-1.5Z", clipRule: "evenodd" }) })
482
+ )
483
+ }
484
+ ),
485
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1 flex items-center border border-gray-300 rounded-lg overflow-hidden focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500 bg-white min-h-[42px] mb-1", children: [
486
+ inputMode === "text" && /* @__PURE__ */ jsxs3(Fragment, { children: [
487
+ /* @__PURE__ */ jsx5(
488
+ "span",
489
+ {
490
+ ref: measurementRef,
491
+ className: "absolute invisible whitespace-pre-wrap p-0 m-0 text-gray-700 leading-6",
492
+ style: { fontSize: "1rem" }
493
+ }
494
+ ),
495
+ /* @__PURE__ */ jsx5(
496
+ "textarea",
497
+ {
498
+ ref: textareaRef,
499
+ value: message,
500
+ onChange: (e) => {
501
+ if (isControlled && onChange) {
502
+ onChange(e);
503
+ } else {
504
+ setInternalMessage(e.target.value);
505
+ }
506
+ },
507
+ onKeyDown: handleKeyDown,
508
+ placeholder: getPlaceholder(),
509
+ disabled: isInputDisabled || isVoiceActive,
510
+ rows: 1,
511
+ className: `flex-grow px-4 py-2 outline-none text-gray-700 placeholder-gray-500 disabled:bg-gray-100 resize-none leading-6 w-full ${isInputDisabled ? "cursor-not-allowed" : ""}`
512
+ }
513
+ )
514
+ ] }),
515
+ inputMode === "voice" && /* @__PURE__ */ jsx5("div", { className: "flex-grow flex justify-center items-center p-1", children: /* @__PURE__ */ jsx5(
516
+ "button",
517
+ {
518
+ type: "button",
519
+ onMouseDown: startRecording,
520
+ onMouseUp: stopRecording,
521
+ onTouchStart: startRecording,
522
+ onTouchEnd: stopRecording,
523
+ disabled: isInputDisabled,
524
+ className: `flex-grow py-2 text-center font-medium rounded-md transition-colors select-none ${isVoiceActive ? "bg-blue-100 text-blue-700" : "bg-gray-50 text-gray-700 hover:bg-gray-100"}`,
525
+ children: isVoiceActive ? "Release to Send" : "Hold to Talk"
526
+ }
527
+ ) }),
528
+ (inputMode === "text" || isSending) && /* @__PURE__ */ jsxs3("div", { className: "relative mx-2 flex-shrink-0", children: [
529
+ isSending && /* @__PURE__ */ jsx5("div", { className: "absolute -inset-1", children: /* @__PURE__ */ jsxs3(
530
+ "svg",
531
+ {
532
+ className: "animate-spin h-full w-full text-blue-500 opacity-75",
533
+ xmlns: "http://www.w3.org/2000/svg",
534
+ fill: "none",
535
+ viewBox: "0 0 24 24",
536
+ children: [
537
+ /* @__PURE__ */ jsx5(
538
+ "circle",
539
+ {
540
+ className: "opacity-25",
541
+ cx: "12",
542
+ cy: "12",
543
+ r: "10",
544
+ stroke: "currentColor",
545
+ strokeWidth: "4"
546
+ }
547
+ ),
548
+ /* @__PURE__ */ jsx5(
549
+ "path",
550
+ {
551
+ className: "opacity-75",
552
+ fill: "currentColor",
553
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
554
+ }
555
+ )
556
+ ]
557
+ }
558
+ ) }),
559
+ /* @__PURE__ */ jsx5(
560
+ "button",
561
+ {
562
+ type: "button",
563
+ onClick: (e) => {
564
+ if (isSending && onStop) {
565
+ e.preventDefault();
566
+ onStop();
567
+ } else {
568
+ handleSubmit();
569
+ }
570
+ },
571
+ disabled: (currentTask == null ? void 0 : currentTask.complete) || isSending && !onStop || isInputDisabled || isVoiceActive,
572
+ className: `relative z-10 text-white rounded-full p-2 transition-colors duration-200 disabled:bg-gray-400 disabled:cursor-not-allowed ${isSending && onStop ? "bg-red-500 hover:bg-red-600" : "bg-blue-600 hover:bg-blue-700"}`,
573
+ title: isSending && onStop ? "Stop generating" : "Send message",
574
+ children: isSending ? onStop ? /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" }) : (
575
+ // AND we show the overlay spinner outside?
576
+ // Actually `ChatInput.tsx` lines 117-140 are `isLoading && (...)`. It is always shown when loading.
577
+ // So we have a spinner ring AROUND the button (absolute -inset-1).
578
+ // AND potentially a spinner INSIDE the button if no onStop?
579
+ // In my case, I will stick to:
580
+ // If onStop: Show StopIcon. Button is Red.
581
+ // If !onStop: Show Spinner inside? Or just let the outer ring do the work?
582
+ // Legacy `Spinner` component usage inside button suggests double spinner if we are not careful.
583
+ // But usually `onStop` is provided for streaming.
584
+ // If I look at the screenshot, it shows a RED button (with stop icon) and a BLUE ring around it.
585
+ // That matches: Red button (bg-red-500) + Blue Spinner Ring (text-blue-500).
586
+ // So I will replicate that structure.
587
+ /* @__PURE__ */ jsx5(StopIcon, { className: "h-5 w-5" })
588
+ ) : /* @__PURE__ */ jsx5(PaperAirplaneIcon, { className: "h-5 w-5" })
589
+ }
590
+ )
591
+ ] })
592
+ ] })
593
+ ] }),
594
+ inputHint && /* @__PURE__ */ jsx5("div", { className: "text-sm text-red-500 bg-red-50 py-1 px-4 rounded-lg mt-1", children: inputHint }),
595
+ hintText && inputMode === "text" && /* @__PURE__ */ jsx5("p", { className: "text-xs text-gray-500 ml-12 mb-2 mt-1", children: hintText })
596
+ ] });
597
+ });
598
+ ChatInputArea.displayName = "ChatInputArea";
599
+
600
+ // src/ui/react/components/ChatMessageList.tsx
601
+ import { useEffect as useEffect6, useRef as useRef5 } from "react";
602
+
603
+ // src/ui/react/components/interactive/ConfirmInteraction.tsx
604
+ import { useState as useState4 } from "react";
605
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
606
+ var ConfirmInteraction = ({
607
+ parameters,
608
+ onResponse,
609
+ isResponseSubmitted
610
+ }) => {
611
+ const [selectedOption, setSelectedOption] = useState4(null);
612
+ const params = parameters;
613
+ const { yesPrompt, noPrompt } = params;
614
+ console.log("[ConfirmInteraction] Parameters:", params);
615
+ const handleOptionClick = (value, buttonText) => {
616
+ if (isResponseSubmitted) return;
617
+ setSelectedOption(buttonText);
618
+ onResponse(value);
619
+ };
620
+ return /* @__PURE__ */ jsx6("div", { className: "mt-2 mb-4", children: /* @__PURE__ */ jsxs4("div", { className: "flex space-x-2", children: [
621
+ /* @__PURE__ */ jsx6(
622
+ "button",
623
+ {
624
+ onClick: () => handleOptionClick(true, yesPrompt),
625
+ disabled: isResponseSubmitted,
626
+ className: `px-4 py-2 rounded-md text-sm transition-colors ${isResponseSubmitted ? selectedOption === yesPrompt ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-500 cursor-not-allowed" : "bg-blue-100 text-blue-700 hover:bg-blue-200"}`,
627
+ children: yesPrompt
628
+ }
629
+ ),
630
+ /* @__PURE__ */ jsx6(
631
+ "button",
632
+ {
633
+ onClick: () => handleOptionClick(false, noPrompt),
634
+ disabled: isResponseSubmitted,
635
+ className: `px-4 py-2 rounded-md text-sm transition-colors ${isResponseSubmitted ? selectedOption === noPrompt ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-500 cursor-not-allowed" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}`,
636
+ children: noPrompt
637
+ }
638
+ )
639
+ ] }) });
640
+ };
641
+ var ConfirmInteraction_default = ConfirmInteraction;
642
+
643
+ // src/ui/react/components/interactive/SelectInteraction.tsx
644
+ import { useState as useState5, useEffect as useEffect4 } from "react";
645
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
646
+ var SelectInteraction = ({
647
+ parameters,
648
+ onResponse,
649
+ isResponseSubmitted,
650
+ message
651
+ }) => {
652
+ const [selectedOption, setSelectedOption] = useState5(null);
653
+ const [customOption, setCustomOption] = useState5(null);
654
+ const params = parameters;
655
+ const { question, options, placeholder } = params;
656
+ useEffect4(() => {
657
+ if (isResponseSubmitted && (message == null ? void 0 : message.responseValue)) {
658
+ const responseValueStr = String(message.responseValue);
659
+ setSelectedOption(responseValueStr);
660
+ if (!options.includes(responseValueStr)) {
661
+ setCustomOption(responseValueStr);
662
+ }
663
+ }
664
+ }, [isResponseSubmitted, message, options]);
665
+ const handleOptionClick = (option) => {
666
+ if (isResponseSubmitted) return;
667
+ setSelectedOption(option);
668
+ setCustomOption(null);
669
+ onResponse(option);
670
+ };
671
+ return /* @__PURE__ */ jsxs5("div", { className: "mt-2 mb-4", children: [
672
+ /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2", children: options.map((option, index) => /* @__PURE__ */ jsx7(
673
+ "button",
674
+ {
675
+ onClick: () => handleOptionClick(option),
676
+ disabled: isResponseSubmitted,
677
+ className: `px-4 py-2 rounded-md text-sm transition-colors ${isResponseSubmitted ? selectedOption === option ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-500 cursor-not-allowed" : "bg-blue-100 text-blue-700 hover:bg-blue-200"}`,
678
+ children: option
679
+ },
680
+ index
681
+ )) }),
682
+ customOption && isResponseSubmitted && /* @__PURE__ */ jsxs5("div", { className: "mt-2 text-sm text-amber-600 bg-amber-50 p-2 rounded", children: [
683
+ "User provided custom option: ",
684
+ /* @__PURE__ */ jsxs5("span", { className: "font-semibold", children: [
685
+ '"',
686
+ customOption,
687
+ '"'
688
+ ] })
689
+ ] })
690
+ ] });
691
+ };
692
+ var SelectInteraction_default = SelectInteraction;
693
+
694
+ // src/ui/react/components/interactive/FormInteraction.tsx
695
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4 } from "react";
696
+ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
697
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
698
+ var FormInteraction = ({
699
+ parameters,
700
+ onResponse,
701
+ isResponseSubmitted,
702
+ submittedValues
703
+ }) => {
704
+ const [isModalOpen, setIsModalOpen] = useState6(false);
705
+ const [formValues, setFormValues] = useState6({});
706
+ const [isExpanded, setIsExpanded] = useState6(false);
707
+ const [parsedFields, setParsedFields] = useState6([]);
708
+ const formButtonsRef = useRef4(null);
709
+ const parseParameters = () => {
710
+ const { prompt, description, submitText = "Submit", cancelText = "Cancel" } = parameters;
711
+ let fieldsArray = [];
712
+ if (parameters.fields) {
713
+ if (Array.isArray(parameters.fields)) {
714
+ fieldsArray = parameters.fields.map((field) => {
715
+ if (typeof field === "string") {
716
+ try {
717
+ const fieldData = JSON.parse(field);
718
+ const name = fieldData.name;
719
+ return {
720
+ name,
721
+ label: fieldData.label || name,
722
+ type: fieldData.type || "string",
723
+ required: fieldData.required || false,
724
+ options: fieldData.options || [],
725
+ placeholder: fieldData.description || fieldData.label || name,
726
+ defaultValue: fieldData.defaultValue
727
+ };
728
+ } catch (error) {
729
+ console.error(`Error parsing field:`, error);
730
+ return null;
731
+ }
732
+ }
733
+ return field;
734
+ }).filter((field) => field !== null);
735
+ } else if (typeof parameters.fields === "object") {
736
+ fieldsArray = Object.entries(parameters.fields).map(([name, fieldDataStr]) => {
737
+ let fieldData = {};
738
+ try {
739
+ if (typeof fieldDataStr === "string") {
740
+ fieldData = JSON.parse(fieldDataStr);
741
+ } else {
742
+ fieldData = fieldDataStr;
743
+ }
744
+ } catch (error) {
745
+ console.error(`Error parsing field data for ${name}:`, error);
746
+ }
747
+ return {
748
+ name,
749
+ label: fieldData.label || name,
750
+ type: fieldData.type || "string",
751
+ required: fieldData.required || false,
752
+ options: fieldData.options || [],
753
+ placeholder: fieldData.description || fieldData.label || name,
754
+ defaultValue: fieldData.defaultValue
755
+ };
756
+ });
757
+ console.log("Field arrays: ", fieldsArray);
758
+ }
759
+ }
760
+ return {
761
+ title: prompt || "Please fill out this form",
762
+ description,
763
+ fields: fieldsArray,
764
+ submitText,
765
+ cancelText
766
+ };
767
+ };
768
+ const params = parseParameters();
769
+ useEffect5(() => {
770
+ const processedParams = parseParameters();
771
+ setParsedFields(processedParams.fields);
772
+ const initialValues = {};
773
+ processedParams.fields.forEach((field) => {
774
+ if (field.defaultValue !== void 0) {
775
+ initialValues[field.name] = field.defaultValue;
776
+ } else if (field.type === "checkbox") {
777
+ initialValues[field.name] = false;
778
+ } else {
779
+ initialValues[field.name] = "";
780
+ }
781
+ });
782
+ setFormValues(initialValues);
783
+ if (!isResponseSubmitted) {
784
+ setIsModalOpen(true);
785
+ }
786
+ }, [parameters, isResponseSubmitted]);
787
+ useEffect5(() => {
788
+ if (isModalOpen && formButtonsRef.current) {
789
+ setTimeout(() => {
790
+ var _a;
791
+ (_a = formButtonsRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
792
+ }, 100);
793
+ }
794
+ }, [isModalOpen]);
795
+ const handleInputChange = (field, value) => {
796
+ setFormValues((prev) => ({
797
+ ...prev,
798
+ [field.name]: value
799
+ }));
800
+ };
801
+ const handleSubmit = (e) => {
802
+ e.preventDefault();
803
+ onResponse(formValues);
804
+ setIsModalOpen(false);
805
+ };
806
+ const handleCancel = () => {
807
+ onResponse({});
808
+ setIsModalOpen(false);
809
+ };
810
+ const renderField = (field) => {
811
+ const { name, label, type, required, options, placeholder } = field;
812
+ switch (type) {
813
+ case "string":
814
+ case "email":
815
+ case "number":
816
+ case "password":
817
+ return /* @__PURE__ */ jsxs6("div", { className: "mb-4 flex items-center space-x-4", children: [
818
+ /* @__PURE__ */ jsxs6("label", { htmlFor: name, className: "text-sm font-medium text-gray-700 min-w-[120px]", children: [
819
+ label,
820
+ required && /* @__PURE__ */ jsx8("span", { className: "text-red-500", children: "*" })
821
+ ] }),
822
+ /* @__PURE__ */ jsx8(
823
+ "input",
824
+ {
825
+ type: type === "string" ? "text" : type,
826
+ id: name,
827
+ name,
828
+ value: formValues[name] || "",
829
+ onChange: (e) => handleInputChange(field, e.target.value),
830
+ placeholder,
831
+ required,
832
+ disabled: isResponseSubmitted,
833
+ className: "flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
834
+ }
835
+ )
836
+ ] }, name);
837
+ case "textarea":
838
+ return /* @__PURE__ */ jsxs6("div", { className: "mb-4 flex items-start space-x-4", children: [
839
+ /* @__PURE__ */ jsxs6("label", { htmlFor: name, className: "text-sm font-medium text-gray-700 min-w-[120px] pt-2", children: [
840
+ label,
841
+ required && /* @__PURE__ */ jsx8("span", { className: "text-red-500", children: "*" })
842
+ ] }),
843
+ /* @__PURE__ */ jsx8(
844
+ "textarea",
845
+ {
846
+ id: name,
847
+ name,
848
+ value: formValues[name] || "",
849
+ onChange: (e) => handleInputChange(field, e.target.value),
850
+ placeholder,
851
+ required,
852
+ disabled: isResponseSubmitted,
853
+ rows: 4,
854
+ className: "flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
855
+ }
856
+ )
857
+ ] }, name);
858
+ case "select":
859
+ return /* @__PURE__ */ jsxs6("div", { className: "mb-4 flex items-center space-x-4", children: [
860
+ /* @__PURE__ */ jsxs6("label", { htmlFor: name, className: "text-sm font-medium text-gray-700 min-w-[120px]", children: [
861
+ label,
862
+ required && /* @__PURE__ */ jsx8("span", { className: "text-red-500", children: "*" })
863
+ ] }),
864
+ /* @__PURE__ */ jsxs6(
865
+ "select",
866
+ {
867
+ id: name,
868
+ name,
869
+ value: formValues[name] || "",
870
+ onChange: (e) => handleInputChange(field, e.target.value),
871
+ required,
872
+ disabled: isResponseSubmitted,
873
+ className: "flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500",
874
+ children: [
875
+ /* @__PURE__ */ jsx8("option", { value: "", disabled: true, children: placeholder || "Select an option" }),
876
+ options == null ? void 0 : options.map((option, index) => /* @__PURE__ */ jsx8("option", { value: option, children: option }, index))
877
+ ]
878
+ }
879
+ )
880
+ ] }, name);
881
+ case "checkbox":
882
+ return /* @__PURE__ */ jsxs6("div", { className: "mb-4 flex items-center space-x-4", children: [
883
+ /* @__PURE__ */ jsx8("div", { className: "min-w-[120px]" }),
884
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center", children: [
885
+ /* @__PURE__ */ jsx8(
886
+ "input",
887
+ {
888
+ type: "checkbox",
889
+ id: name,
890
+ name,
891
+ checked: !!formValues[name],
892
+ onChange: (e) => handleInputChange(field, e.target.checked),
893
+ required,
894
+ disabled: isResponseSubmitted,
895
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsxs6("label", { htmlFor: name, className: "ml-2 text-sm text-gray-700", children: [
899
+ label,
900
+ required && /* @__PURE__ */ jsx8("span", { className: "text-red-500", children: "*" })
901
+ ] })
902
+ ] })
903
+ ] }, name);
904
+ case "radio":
905
+ return /* @__PURE__ */ jsxs6("div", { className: "mb-4 flex space-x-4", children: [
906
+ /* @__PURE__ */ jsxs6("div", { className: "text-sm font-medium text-gray-700 min-w-[120px] pt-2", children: [
907
+ label,
908
+ required && /* @__PURE__ */ jsx8("span", { className: "text-red-500", children: "*" })
909
+ ] }),
910
+ /* @__PURE__ */ jsx8("div", { className: "flex-1 space-y-2", children: options == null ? void 0 : options.map((option, index) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center", children: [
911
+ /* @__PURE__ */ jsx8(
912
+ "input",
913
+ {
914
+ id: `${name}-${index}`,
915
+ name,
916
+ type: "radio",
917
+ value: option,
918
+ checked: formValues[name] === option,
919
+ onChange: () => handleInputChange(field, option),
920
+ required,
921
+ disabled: isResponseSubmitted,
922
+ className: "h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500"
923
+ }
924
+ ),
925
+ /* @__PURE__ */ jsx8("label", { htmlFor: `${name}-${index}`, className: "ml-2 text-sm text-gray-700", children: option })
926
+ ] }, index)) })
927
+ ] }, name);
928
+ default:
929
+ return null;
930
+ }
931
+ };
932
+ if (isResponseSubmitted) {
933
+ return /* @__PURE__ */ jsxs6("div", { className: "mt-2 bg-gray-50 p-3 rounded-md border border-gray-200", children: [
934
+ /* @__PURE__ */ jsxs6("div", { className: "flex justify-between items-center cursor-pointer", onClick: () => setIsExpanded(!isExpanded), children: [
935
+ /* @__PURE__ */ jsxs6("span", { className: "font-medium text-gray-700", children: [
936
+ isExpanded ? "Hide" : "Show",
937
+ " Form Responses"
938
+ ] }),
939
+ isExpanded ? /* @__PURE__ */ jsx8(ChevronUpIcon, { className: "h-5 w-5 text-gray-500" }) : /* @__PURE__ */ jsx8(ChevronDownIcon, { className: "h-5 w-5 text-gray-500" })
940
+ ] }),
941
+ isExpanded && /* @__PURE__ */ jsx8("div", { className: "mt-2 space-y-2", children: parsedFields.map((field) => /* @__PURE__ */ jsxs6("div", { className: "flex", children: [
942
+ /* @__PURE__ */ jsxs6("span", { className: "text-sm font-medium text-gray-600 mr-2", children: [
943
+ field.label,
944
+ ":"
945
+ ] }),
946
+ /* @__PURE__ */ jsx8("span", { className: "text-sm text-gray-800", children: typeof (submittedValues == null ? void 0 : submittedValues[field.name]) === "boolean" ? submittedValues[field.name] ? "Yes" : "No" : (submittedValues == null ? void 0 : submittedValues[field.name]) || "Not provided" })
947
+ ] }, field.name)) })
948
+ ] });
949
+ }
950
+ return /* @__PURE__ */ jsx8("div", { className: "mt-2", children: isModalOpen && /* @__PURE__ */ jsx8("div", { className: "p-4", children: /* @__PURE__ */ jsxs6("form", { onSubmit: handleSubmit, className: "mt-4", children: [
951
+ parsedFields.map((field) => renderField(field)),
952
+ /* @__PURE__ */ jsxs6("div", { ref: formButtonsRef, className: "flex justify-end mt-4 space-x-2", children: [
953
+ /* @__PURE__ */ jsx8(
954
+ "button",
955
+ {
956
+ type: "button",
957
+ onClick: handleCancel,
958
+ disabled: isResponseSubmitted,
959
+ className: "px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 border border-gray-300 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500",
960
+ children: params.cancelText
961
+ }
962
+ ),
963
+ /* @__PURE__ */ jsx8(
964
+ "button",
965
+ {
966
+ type: "submit",
967
+ disabled: isResponseSubmitted,
968
+ className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500",
969
+ children: params.submitText
970
+ }
971
+ )
972
+ ] })
973
+ ] }) }) });
974
+ };
975
+ var FormInteraction_default = FormInteraction;
976
+
977
+ // src/ui/react/components/interactive/PresentInteraction.tsx
978
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
979
+ var ReactMarkdown2;
980
+ try {
981
+ ReactMarkdown2 = __require("react-markdown");
982
+ } catch (error) {
983
+ ReactMarkdown2 = ({ children }) => /* @__PURE__ */ jsx9("div", { className: "whitespace-pre-wrap", children });
984
+ }
985
+ var PresentInteraction = ({
986
+ parameters
987
+ }) => {
988
+ const params = parameters;
989
+ const { title, content, format = "text", level = "info" } = params;
990
+ const getLevelStyles = () => {
991
+ switch (level) {
992
+ case "error":
993
+ return {
994
+ container: "bg-red-50 border-red-100",
995
+ title: "text-red-800",
996
+ content: "text-red-700"
997
+ };
998
+ case "warn":
999
+ case "warning":
1000
+ return {
1001
+ container: "bg-amber-50 border-amber-100",
1002
+ title: "text-amber-800",
1003
+ content: "text-amber-700"
1004
+ };
1005
+ case "success":
1006
+ return {
1007
+ container: "bg-green-50 border-green-100",
1008
+ title: "text-green-800",
1009
+ content: "text-green-700"
1010
+ };
1011
+ case "info":
1012
+ default:
1013
+ return {
1014
+ container: "bg-blue-50 border-blue-100",
1015
+ title: "text-blue-800",
1016
+ content: "text-blue-700"
1017
+ };
1018
+ }
1019
+ };
1020
+ const styles = getLevelStyles();
1021
+ return /* @__PURE__ */ jsxs7("div", { className: `mt-2 mb-4 p-4 ${styles.container} border rounded-md`, children: [
1022
+ title && /* @__PURE__ */ jsx9("div", { className: `font-medium ${styles.title} mb-2`, children: title }),
1023
+ /* @__PURE__ */ jsx9("div", { className: `text-sm ${styles.content}`, children: format === "markdown" ? /* @__PURE__ */ jsx9(ReactMarkdown2, { className: "prose prose-sm max-w-none", children: content }) : format === "html" ? /* @__PURE__ */ jsx9("div", { dangerouslySetInnerHTML: { __html: content } }) : /* @__PURE__ */ jsx9("div", { className: "whitespace-pre-wrap", children: content }) })
1024
+ ] });
1025
+ };
1026
+ var PresentInteraction_default = PresentInteraction;
1027
+
1028
+ // src/ui/react/components/interactive/InteractiveMessageHandler.tsx
1029
+ import { jsx as jsx10 } from "react/jsx-runtime";
1030
+ var InteractiveMessageHandler = ({
1031
+ message,
1032
+ onResponse,
1033
+ isResponseSubmitted,
1034
+ parentMessage
1035
+ }) => {
1036
+ const { function: functionType, parameters } = message;
1037
+ const submittedResponse = parentMessage == null ? void 0 : parentMessage.responseValue;
1038
+ switch (functionType) {
1039
+ case "confirm":
1040
+ return /* @__PURE__ */ jsx10(
1041
+ ConfirmInteraction_default,
1042
+ {
1043
+ parameters,
1044
+ onResponse,
1045
+ isResponseSubmitted
1046
+ }
1047
+ );
1048
+ case "select":
1049
+ return /* @__PURE__ */ jsx10(
1050
+ SelectInteraction_default,
1051
+ {
1052
+ parameters,
1053
+ onResponse,
1054
+ isResponseSubmitted,
1055
+ message: parentMessage
1056
+ }
1057
+ );
1058
+ case "form":
1059
+ return /* @__PURE__ */ jsx10(
1060
+ FormInteraction_default,
1061
+ {
1062
+ parameters,
1063
+ onResponse,
1064
+ isResponseSubmitted,
1065
+ submittedValues: submittedResponse
1066
+ }
1067
+ );
1068
+ case "present":
1069
+ return /* @__PURE__ */ jsx10(
1070
+ PresentInteraction_default,
1071
+ {
1072
+ parameters
1073
+ }
1074
+ );
1075
+ case "chat":
1076
+ return null;
1077
+ default:
1078
+ console.warn(`Unknown interactive function type: ${functionType}`);
1079
+ return null;
1080
+ }
1081
+ };
1082
+ var InteractiveMessageHandler_default = InteractiveMessageHandler;
1083
+
1084
+ // src/ui/react/components/ChatMessageList.tsx
1085
+ import { InformationCircleIcon } from "@heroicons/react/24/outline";
1086
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1087
+ var ChatMessageList = ({
1088
+ chatHistory,
1089
+ isProcessing,
1090
+ processingHint,
1091
+ currentTask,
1092
+ getContextExample,
1093
+ onInteractiveResponse
1094
+ }) => {
1095
+ const chatContainerRef = useRef5(null);
1096
+ const lastMessageRef = useRef5(null);
1097
+ const processingIndicatorRef = useRef5(null);
1098
+ useEffect6(() => {
1099
+ if (isProcessing && processingIndicatorRef.current) {
1100
+ processingIndicatorRef.current.scrollIntoView({ behavior: "smooth" });
1101
+ return;
1102
+ }
1103
+ if (lastMessageRef.current) {
1104
+ lastMessageRef.current.scrollIntoView({ behavior: "smooth" });
1105
+ } else if (chatContainerRef.current) {
1106
+ chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
1107
+ }
1108
+ }, [chatHistory, isProcessing]);
1109
+ const handleInteractiveResponse = (messageId, response) => {
1110
+ if (onInteractiveResponse) {
1111
+ onInteractiveResponse(messageId, response);
1112
+ }
1113
+ };
1114
+ return /* @__PURE__ */ jsxs8(
1115
+ "div",
1116
+ {
1117
+ ref: chatContainerRef,
1118
+ className: "flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50",
1119
+ children: [
1120
+ chatHistory.length === 0 && !isProcessing && /* @__PURE__ */ jsxs8("div", { className: "text-center py-8", children: [
1121
+ /* @__PURE__ */ jsx11("h3", { className: "text-lg font-medium text-gray-700 mb-2", children: "How can I help you today?" }),
1122
+ /* @__PURE__ */ jsxs8("p", { className: "text-sm text-gray-500 mb-4", children: [
1123
+ "Try asking me something like ",
1124
+ getContextExample()
1125
+ ] })
1126
+ ] }),
1127
+ chatHistory.map((message, index) => {
1128
+ const isLastMessage = index === chatHistory.length - 1;
1129
+ const isUser = message.role === "user";
1130
+ const isError = message.role === "error";
1131
+ return /* @__PURE__ */ jsxs8(
1132
+ "div",
1133
+ {
1134
+ ref: isLastMessage ? lastMessageRef : void 0,
1135
+ className: `flex flex-col w-full ${isUser ? "items-end" : "items-start"}`,
1136
+ children: [
1137
+ /* @__PURE__ */ jsx11("div", { className: "text-xs text-gray-400 mb-1 px-1", children: message.timestamp ? `${isUser ? "Sent" : "Received"} at ${new Date(message.timestamp).toLocaleString()}` : "" }),
1138
+ /* @__PURE__ */ jsx11("div", { className: "w-full", children: message.interactive && message.interactiveData ? /* @__PURE__ */ jsx11("div", { className: `max-w-[85%] ${isUser ? "ml-auto" : "mr-auto"}`, children: /* @__PURE__ */ jsx11(
1139
+ InteractiveMessageHandler_default,
1140
+ {
1141
+ message: message.interactiveData,
1142
+ onResponse: (response) => {
1143
+ const messageId = message.id || `interactive-${index}-${Date.now()}`;
1144
+ message.responseValue = response;
1145
+ handleInteractiveResponse(messageId, response);
1146
+ },
1147
+ isResponseSubmitted: !!message.isResponseSubmitted,
1148
+ parentMessage: message
1149
+ }
1150
+ ) }) : /* @__PURE__ */ jsx11(
1151
+ MessageBubble,
1152
+ {
1153
+ message,
1154
+ isUser
1155
+ }
1156
+ ) }),
1157
+ isUser && message.interactive && /* @__PURE__ */ jsxs8("div", { className: "flex items-center mt-1 space-x-1 text-xs text-gray-500 italic", children: [
1158
+ /* @__PURE__ */ jsx11(InformationCircleIcon, { className: "h-3 w-3 text-blue-400" }),
1159
+ /* @__PURE__ */ jsxs8("span", { children: [
1160
+ message.request === "form" && "Response to form submission",
1161
+ message.request === "select" && "Response to selection prompt",
1162
+ message.request === "confirm" && "Response to confirmation prompt"
1163
+ ] })
1164
+ ] })
1165
+ ]
1166
+ },
1167
+ message.id || `message-${index}`
1168
+ );
1169
+ }),
1170
+ isProcessing && /* @__PURE__ */ jsx11(
1171
+ "div",
1172
+ {
1173
+ ref: processingIndicatorRef,
1174
+ className: "flex justify-start",
1175
+ children: /* @__PURE__ */ jsx11("div", { className: "bg-white text-gray-800 border border-gray-200 rounded-lg px-4 py-2 max-w-[85%]", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center", children: [
1176
+ /* @__PURE__ */ jsx11("span", { className: "text-sm", children: processingHint }),
1177
+ /* @__PURE__ */ jsxs8("span", { className: "ml-2 flex space-x-1", children: [
1178
+ /* @__PURE__ */ jsx11("span", { className: "h-2 w-2 bg-gray-400 rounded-full animate-bounce", style: { animationDelay: "0ms" } }),
1179
+ /* @__PURE__ */ jsx11("span", { className: "h-2 w-2 bg-gray-400 rounded-full animate-bounce", style: { animationDelay: "150ms" } }),
1180
+ /* @__PURE__ */ jsx11("span", { className: "h-2 w-2 bg-gray-400 rounded-full animate-bounce", style: { animationDelay: "300ms" } })
1181
+ ] })
1182
+ ] }) })
1183
+ }
1184
+ ),
1185
+ currentTask && !currentTask.complete && /* @__PURE__ */ jsxs8("div", { className: "mt-4 bg-blue-50 border border-blue-100 rounded-lg p-3", children: [
1186
+ /* @__PURE__ */ jsxs8("div", { className: "text-sm text-blue-800 mb-1", children: [
1187
+ "Task in progress: Step ",
1188
+ currentTask.currentStep,
1189
+ " of ",
1190
+ currentTask.steps
1191
+ ] }),
1192
+ /* @__PURE__ */ jsx11("div", { className: "w-full bg-blue-200 rounded-full h-2", children: /* @__PURE__ */ jsx11(
1193
+ "div",
1194
+ {
1195
+ className: "bg-blue-500 h-2 rounded-full",
1196
+ style: { width: `${currentTask.currentStep / currentTask.steps * 100}%` }
1197
+ }
1198
+ ) })
1199
+ ] })
1200
+ ]
1201
+ }
1202
+ );
1203
+ };
1204
+
1205
+ // src/ui/react/components/ConnectionStatus.tsx
1206
+ import { Wifi, WifiOff, RefreshCw } from "lucide-react";
1207
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1208
+ var ConnectionStatus = ({
1209
+ connectionStatus,
1210
+ onReconnect
1211
+ }) => {
1212
+ return /* @__PURE__ */ jsxs9("div", { className: "bg-amber-100 connection-status flex items-center justify-between px-4 py-1 text-xs", children: [
1213
+ /* @__PURE__ */ jsx12("div", { className: "text-gray-700 font-medium", children: "You are talking with your personal Content Strategist" }),
1214
+ connectionStatus === "connected" && /* @__PURE__ */ jsxs9("div", { className: "flex items-center text-green-600", children: [
1215
+ /* @__PURE__ */ jsx12(Wifi, { className: "w-3 h-3 mr-1" }),
1216
+ /* @__PURE__ */ jsx12("span", { children: "Connected" })
1217
+ ] }),
1218
+ connectionStatus === "reconnecting" && /* @__PURE__ */ jsxs9("div", { className: "flex items-center text-amber-600", children: [
1219
+ /* @__PURE__ */ jsx12(RefreshCw, { className: "w-3 h-3 mr-1 animate-spin" }),
1220
+ /* @__PURE__ */ jsx12("span", { children: "Reconnecting..." })
1221
+ ] }),
1222
+ connectionStatus === "disconnected" && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
1223
+ /* @__PURE__ */ jsxs9("div", { className: "text-red-600 flex items-center", children: [
1224
+ /* @__PURE__ */ jsx12(WifiOff, { className: "w-3 h-3 mr-1" }),
1225
+ /* @__PURE__ */ jsx12("span", { children: "Disconnected" })
1226
+ ] }),
1227
+ /* @__PURE__ */ jsxs9(
1228
+ "button",
1229
+ {
1230
+ onClick: onReconnect,
1231
+ className: "text-blue-600 hover:text-blue-800 flex items-center",
1232
+ children: [
1233
+ /* @__PURE__ */ jsx12(RefreshCw, { className: "w-3 h-3 mr-1" }),
1234
+ /* @__PURE__ */ jsx12("span", { children: "Reconnect" })
1235
+ ]
1236
+ }
1237
+ )
1238
+ ] })
1239
+ ] });
1240
+ };
1241
+
1242
+ // src/ui/react/components/Spinner.tsx
1243
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1244
+ var Spinner = ({
1245
+ size = "md",
1246
+ className = "",
1247
+ color = "text-blue-600"
1248
+ }) => {
1249
+ const sizeClasses = {
1250
+ sm: "h-4 w-4",
1251
+ md: "h-6 w-6",
1252
+ lg: "h-8 w-8",
1253
+ xl: "h-12 w-12"
1254
+ };
1255
+ return /* @__PURE__ */ jsxs10(
1256
+ "svg",
1257
+ {
1258
+ className: `animate-spin ${sizeClasses[size]} ${color} ${className}`,
1259
+ xmlns: "http://www.w3.org/2000/svg",
1260
+ fill: "none",
1261
+ viewBox: "0 0 24 24",
1262
+ children: [
1263
+ /* @__PURE__ */ jsx13(
1264
+ "circle",
1265
+ {
1266
+ className: "opacity-25",
1267
+ cx: "12",
1268
+ cy: "12",
1269
+ r: "10",
1270
+ stroke: "currentColor",
1271
+ strokeWidth: "4"
1272
+ }
1273
+ ),
1274
+ /* @__PURE__ */ jsx13(
1275
+ "path",
1276
+ {
1277
+ className: "opacity-75",
1278
+ fill: "currentColor",
1279
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
1280
+ }
1281
+ )
1282
+ ]
1283
+ }
1284
+ );
1285
+ };
1286
+
1287
+ // src/ui/react/services/whisper-client.ts
1288
+ async function transcribeAudio(audioBlob, config) {
1289
+ const formData = new FormData();
1290
+ formData.append("audio", audioBlob, "recording.webm");
1291
+ if (config.language) {
1292
+ formData.append("language", config.language);
1293
+ }
1294
+ try {
1295
+ const response = await fetch(config.endpoint, {
1296
+ method: "POST",
1297
+ body: formData,
1298
+ headers: config.headers || {},
1299
+ credentials: "same-origin"
1300
+ // Ensure cookies are sent for same-domain requests
1301
+ });
1302
+ if (!response.ok) {
1303
+ const errorText = await response.text();
1304
+ throw new Error(`Transcription failed (${response.status}): ${errorText}`);
1305
+ }
1306
+ const data = await response.json();
1307
+ return data.text;
1308
+ } catch (error) {
1309
+ console.error("Transcription client error:", error);
1310
+ throw error;
1311
+ }
1312
+ }
1313
+
1314
+ // src/ui/react/utils/voice-utils.ts
1315
+ function createWhisperVoiceConfig(config, callbacks) {
1316
+ return {
1317
+ mode: "custom",
1318
+ onVoiceStart: callbacks == null ? void 0 : callbacks.onVoiceStart,
1319
+ onVoiceEnd: callbacks == null ? void 0 : callbacks.onVoiceEnd,
1320
+ /**
1321
+ * The core handler: takes the recorded blob and sends it to the server.
1322
+ */
1323
+ onAudioCapture: async (blob) => {
1324
+ return await transcribeAudio(blob, config);
1325
+ }
1326
+ };
1327
+ }
1328
+ export {
1329
+ ChatBubble,
1330
+ ChatConfigProvider,
1331
+ ChatHeader,
1332
+ ChatInputArea,
1333
+ ChatMessageList,
1334
+ ConnectionStatus,
1335
+ MessageBubble,
1336
+ Spinner,
1337
+ createWhisperVoiceConfig,
1338
+ transcribeAudio,
1339
+ useAudioRecorder,
1340
+ useChatConfig,
1341
+ useProactiveResize,
1342
+ useSpeechRecognition
1343
+ };
1344
+ //# sourceMappingURL=index.js.map