ai-site-pilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,611 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ var framerMotion = require('framer-motion');
7
+ var lucideReact = require('lucide-react');
8
+ var ReactMarkdown = require('react-markdown');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
14
+
15
+ // src/components/SitePilot.tsx
16
+ function useChat(options) {
17
+ const { apiEndpoint, initialMessages = [], onToolCall, onStreamStart, onStreamEnd } = options;
18
+ const [messages, setMessages] = react.useState(initialMessages);
19
+ const [input, setInput] = react.useState("");
20
+ const [isLoading, setIsLoading] = react.useState(false);
21
+ const [streamingMessageId, setStreamingMessageId] = react.useState(null);
22
+ const abortControllerRef = react.useRef(null);
23
+ const addMessage = react.useCallback((message) => {
24
+ const newMessage = {
25
+ ...message,
26
+ id: Date.now().toString(),
27
+ timestamp: /* @__PURE__ */ new Date()
28
+ };
29
+ setMessages((prev) => [...prev, newMessage]);
30
+ return newMessage;
31
+ }, []);
32
+ const clearMessages = react.useCallback(() => {
33
+ setMessages(initialMessages);
34
+ }, [initialMessages]);
35
+ const sendMessage = react.useCallback(
36
+ async (content) => {
37
+ const messageContent = content || input;
38
+ if (!messageContent.trim() || isLoading) return;
39
+ if (abortControllerRef.current) {
40
+ abortControllerRef.current.abort();
41
+ }
42
+ abortControllerRef.current = new AbortController();
43
+ const userMessage = {
44
+ id: Date.now().toString(),
45
+ role: "user",
46
+ content: messageContent,
47
+ timestamp: /* @__PURE__ */ new Date()
48
+ };
49
+ setMessages((prev) => [...prev, userMessage]);
50
+ setInput("");
51
+ setIsLoading(true);
52
+ onStreamStart?.();
53
+ const assistantMessageId = (Date.now() + 1).toString();
54
+ const assistantMessage = {
55
+ id: assistantMessageId,
56
+ role: "assistant",
57
+ content: "",
58
+ timestamp: /* @__PURE__ */ new Date(),
59
+ toolCalls: []
60
+ };
61
+ setMessages((prev) => [...prev, assistantMessage]);
62
+ setStreamingMessageId(assistantMessageId);
63
+ let fullText = "";
64
+ const toolCalls = [];
65
+ try {
66
+ const apiMessages = messages.concat(userMessage).map((m) => ({
67
+ role: m.role,
68
+ content: m.content
69
+ }));
70
+ const response = await fetch(apiEndpoint, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ body: JSON.stringify({ messages: apiMessages }),
74
+ signal: abortControllerRef.current.signal
75
+ });
76
+ if (!response.ok) {
77
+ throw new Error("Failed to get response");
78
+ }
79
+ const reader = response.body?.getReader();
80
+ if (!reader) throw new Error("No reader available");
81
+ const decoder = new TextDecoder();
82
+ while (true) {
83
+ const { done, value } = await reader.read();
84
+ if (done) break;
85
+ const text = decoder.decode(value);
86
+ const lines = text.split("\n");
87
+ for (const line of lines) {
88
+ if (line.startsWith("data: ")) {
89
+ try {
90
+ const data = JSON.parse(line.slice(6));
91
+ if (data.type === "text") {
92
+ fullText += data.content;
93
+ setMessages(
94
+ (prev) => prev.map(
95
+ (m) => m.id === assistantMessageId ? { ...m, content: fullText } : m
96
+ )
97
+ );
98
+ } else if (data.type === "tool") {
99
+ const toolCall = { name: data.name, args: data.args };
100
+ toolCalls.push(toolCall);
101
+ if (onToolCall) {
102
+ try {
103
+ await onToolCall(data.name, data.args);
104
+ } catch (e) {
105
+ console.error("Tool execution error:", e);
106
+ }
107
+ }
108
+ } else if (data.type === "done") {
109
+ if (toolCalls.length > 0) {
110
+ setMessages(
111
+ (prev) => prev.map(
112
+ (m) => m.id === assistantMessageId ? { ...m, toolCalls } : m
113
+ )
114
+ );
115
+ }
116
+ if (!fullText) {
117
+ setMessages(
118
+ (prev) => prev.map(
119
+ (m) => m.id === assistantMessageId ? { ...m, content: "I've made some changes. Take a look!" } : m
120
+ )
121
+ );
122
+ }
123
+ } else if (data.type === "error") {
124
+ throw new Error(data.message);
125
+ }
126
+ } catch {
127
+ }
128
+ }
129
+ }
130
+ }
131
+ } catch (error) {
132
+ if (error instanceof Error && error.name === "AbortError") {
133
+ return;
134
+ }
135
+ console.error("Chat error:", error);
136
+ setMessages(
137
+ (prev) => prev.map(
138
+ (m) => m.id === assistantMessageId ? { ...m, content: "Sorry, I encountered an error. Please try again." } : m
139
+ )
140
+ );
141
+ } finally {
142
+ setIsLoading(false);
143
+ setStreamingMessageId(null);
144
+ onStreamEnd?.();
145
+ }
146
+ },
147
+ [apiEndpoint, input, isLoading, messages, onToolCall, onStreamStart, onStreamEnd]
148
+ );
149
+ return {
150
+ messages,
151
+ input,
152
+ setInput,
153
+ isLoading,
154
+ streamingMessageId,
155
+ sendMessage,
156
+ clearMessages,
157
+ addMessage
158
+ };
159
+ }
160
+ function useSpeech(options = {}) {
161
+ const { lang = "en-US", onResult } = options;
162
+ const [isSupported, setIsSupported] = react.useState(false);
163
+ const [isListening, setIsListening] = react.useState(false);
164
+ const [ttsEnabled, setTtsEnabled] = react.useState(false);
165
+ const recognitionRef = react.useRef(null);
166
+ react.useEffect(() => {
167
+ if (typeof window === "undefined") return;
168
+ const windowWithSpeech = window;
169
+ const SpeechRecognitionAPI = windowWithSpeech.SpeechRecognition || windowWithSpeech.webkitSpeechRecognition;
170
+ if (SpeechRecognitionAPI) {
171
+ setIsSupported(true);
172
+ recognitionRef.current = new SpeechRecognitionAPI();
173
+ recognitionRef.current.continuous = false;
174
+ recognitionRef.current.interimResults = true;
175
+ recognitionRef.current.lang = lang;
176
+ recognitionRef.current.onresult = (event) => {
177
+ const result = event.results[0];
178
+ const transcript = result[0].transcript;
179
+ onResult?.(transcript, result.isFinal);
180
+ if (result.isFinal) {
181
+ setIsListening(false);
182
+ }
183
+ };
184
+ recognitionRef.current.onend = () => setIsListening(false);
185
+ recognitionRef.current.onerror = () => setIsListening(false);
186
+ }
187
+ }, [lang, onResult]);
188
+ const startListening = react.useCallback(() => {
189
+ if (!recognitionRef.current) return;
190
+ try {
191
+ recognitionRef.current.start();
192
+ setIsListening(true);
193
+ } catch (e) {
194
+ console.error("Speech recognition error:", e);
195
+ }
196
+ }, []);
197
+ const stopListening = react.useCallback(() => {
198
+ if (!recognitionRef.current) return;
199
+ recognitionRef.current.stop();
200
+ setIsListening(false);
201
+ }, []);
202
+ const toggleListening = react.useCallback(() => {
203
+ if (isListening) {
204
+ stopListening();
205
+ } else {
206
+ startListening();
207
+ }
208
+ }, [isListening, startListening, stopListening]);
209
+ const speak = react.useCallback(
210
+ (text) => {
211
+ if (!ttsEnabled || typeof window === "undefined" || !("speechSynthesis" in window)) {
212
+ return;
213
+ }
214
+ speechSynthesis.cancel();
215
+ const utterance = new SpeechSynthesisUtterance(text);
216
+ utterance.rate = 1;
217
+ utterance.pitch = 1;
218
+ const voices = speechSynthesis.getVoices();
219
+ const preferredVoice = voices.find(
220
+ (v) => v.name.includes("Google") || v.name.includes("Samantha") || v.name.includes("Alex")
221
+ );
222
+ if (preferredVoice) {
223
+ utterance.voice = preferredVoice;
224
+ }
225
+ speechSynthesis.speak(utterance);
226
+ },
227
+ [ttsEnabled]
228
+ );
229
+ const cancelSpeech = react.useCallback(() => {
230
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
231
+ speechSynthesis.cancel();
232
+ }
233
+ }, []);
234
+ return {
235
+ isSupported,
236
+ isListening,
237
+ toggleListening,
238
+ startListening,
239
+ stopListening,
240
+ speak,
241
+ ttsEnabled,
242
+ setTtsEnabled,
243
+ cancelSpeech
244
+ };
245
+ }
246
+ function ChatMessage({ message, isStreaming, isFullscreen }) {
247
+ const isUser = message.role === "user";
248
+ return /* @__PURE__ */ jsxRuntime.jsx(
249
+ framerMotion.motion.div,
250
+ {
251
+ initial: { opacity: 0, y: 10 },
252
+ animate: { opacity: 1, y: 0 },
253
+ className: `flex ${isUser ? "justify-end" : "justify-start"}`,
254
+ children: /* @__PURE__ */ jsxRuntime.jsx(
255
+ "div",
256
+ {
257
+ className: `px-4 py-3 rounded-2xl text-sm leading-relaxed ${isFullscreen ? "max-w-[70%]" : "max-w-[85%]"} ${isUser ? "pilot-message-user rounded-br-md" : "pilot-message-assistant rounded-bl-md"}`,
258
+ children: isUser ? message.content : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pilot-prose", children: [
259
+ /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown__default.default, { children: message.content }),
260
+ isStreaming && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-2 h-4 pilot-cursor ml-0.5 animate-pulse" })
261
+ ] })
262
+ }
263
+ )
264
+ }
265
+ );
266
+ }
267
+ function ChatInput({
268
+ value,
269
+ onChange,
270
+ onSubmit,
271
+ disabled = false,
272
+ placeholder = "Type a message...",
273
+ isListening = false,
274
+ onToggleListening,
275
+ showMic = false
276
+ }) {
277
+ const handleSubmit = (e) => {
278
+ e.preventDefault();
279
+ if (value.trim() && !disabled) {
280
+ onSubmit();
281
+ }
282
+ };
283
+ return /* @__PURE__ */ jsxRuntime.jsx("form", { onSubmit: handleSubmit, className: "p-4 pilot-border-top", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 pilot-input-container px-4 py-3 transition-colors", children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx(
285
+ "input",
286
+ {
287
+ type: "text",
288
+ value,
289
+ onChange: (e) => onChange(e.target.value),
290
+ className: "bg-transparent flex-1 outline-none text-sm pilot-input",
291
+ placeholder: isListening ? "Listening..." : placeholder,
292
+ disabled
293
+ }
294
+ ),
295
+ showMic && onToggleListening && /* @__PURE__ */ jsxRuntime.jsx(
296
+ "button",
297
+ {
298
+ type: "button",
299
+ onClick: onToggleListening,
300
+ disabled,
301
+ className: `p-2 rounded-xl transition-all ${isListening ? "pilot-mic-active" : "pilot-mic-inactive"}`,
302
+ children: isListening ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "w-4 h-4" })
303
+ }
304
+ ),
305
+ /* @__PURE__ */ jsxRuntime.jsx(
306
+ "button",
307
+ {
308
+ type: "submit",
309
+ disabled: !value.trim() || disabled,
310
+ className: `p-2 rounded-xl transition-all ${value.trim() && !disabled ? "pilot-send-active" : "pilot-send-inactive"}`,
311
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-4 h-4" })
312
+ }
313
+ )
314
+ ] }) });
315
+ }
316
+ function Suggestions({
317
+ suggestions,
318
+ onSelect,
319
+ isVisible = true,
320
+ isFullscreen = false,
321
+ suggestionsToShow = 3,
322
+ rotationInterval = 5e3
323
+ }) {
324
+ const [currentIndex, setCurrentIndex] = react.useState(0);
325
+ react.useEffect(() => {
326
+ if (!isVisible || suggestions.length <= suggestionsToShow) return;
327
+ const interval = setInterval(() => {
328
+ setCurrentIndex((prev) => (prev + 1) % Math.ceil(suggestions.length / suggestionsToShow));
329
+ }, rotationInterval);
330
+ return () => clearInterval(interval);
331
+ }, [isVisible, suggestions.length, suggestionsToShow, rotationInterval]);
332
+ const currentSuggestions = suggestions.slice(
333
+ currentIndex * suggestionsToShow,
334
+ currentIndex * suggestionsToShow + suggestionsToShow
335
+ );
336
+ const totalPages = Math.ceil(suggestions.length / suggestionsToShow);
337
+ if (!isVisible || suggestions.length === 0) return null;
338
+ return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: /* @__PURE__ */ jsxRuntime.jsxs(
339
+ framerMotion.motion.div,
340
+ {
341
+ initial: { opacity: 0, y: 10 },
342
+ animate: { opacity: 1, y: 0 },
343
+ exit: { opacity: 0, y: -10 },
344
+ transition: { duration: 0.3 },
345
+ className: "pt-2",
346
+ children: [
347
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 pilot-text-muted", children: [
349
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, { className: "w-3 h-3" }),
350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] uppercase tracking-wider font-medium", children: "Try asking" })
351
+ ] }),
352
+ totalPages > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1", children: Array.from({ length: totalPages }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
353
+ "button",
354
+ {
355
+ onClick: () => setCurrentIndex(i),
356
+ className: `h-1.5 rounded-full transition-all ${i === currentIndex ? "pilot-dot-active w-3" : "pilot-dot-inactive w-1.5 hover:opacity-75"}`
357
+ },
358
+ i
359
+ )) })
360
+ ] }),
361
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
362
+ framerMotion.motion.div,
363
+ {
364
+ initial: { opacity: 0, x: 20 },
365
+ animate: { opacity: 1, x: 0 },
366
+ exit: { opacity: 0, x: -20 },
367
+ transition: { duration: 0.3 },
368
+ className: `flex gap-2 ${isFullscreen ? "flex-row flex-wrap" : "flex-col"}`,
369
+ children: currentSuggestions.map((suggestion, index) => /* @__PURE__ */ jsxRuntime.jsxs(
370
+ framerMotion.motion.button,
371
+ {
372
+ initial: { opacity: 0, y: 10 },
373
+ animate: { opacity: 1, y: 0 },
374
+ transition: { delay: index * 0.1 },
375
+ onClick: () => onSelect(suggestion.text),
376
+ className: "pilot-suggestion group flex items-center gap-2 px-3 py-2 rounded-xl text-xs text-left transition-all duration-200",
377
+ children: [
378
+ suggestion.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base", children: suggestion.icon }),
379
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: suggestion.text })
380
+ ]
381
+ },
382
+ suggestion.text
383
+ ))
384
+ },
385
+ currentIndex
386
+ ) })
387
+ ]
388
+ }
389
+ ) });
390
+ }
391
+ function SitePilot({
392
+ apiEndpoint,
393
+ theme = {},
394
+ suggestions = [],
395
+ features = {},
396
+ onToolCall,
397
+ defaultOpen = false,
398
+ placeholder = "Type a message...",
399
+ welcomeMessage = "Hi! I'm here to help you navigate and explore. What would you like to know?",
400
+ className = ""
401
+ }) {
402
+ const {
403
+ accent = "amber",
404
+ position = "bottom-right",
405
+ borderRadius = 24
406
+ } = theme;
407
+ const {
408
+ speech = true,
409
+ tts = true,
410
+ fullscreen = true,
411
+ suggestions: showSuggestionsFeature = true
412
+ } = features;
413
+ const [isOpen, setIsOpen] = react.useState(defaultOpen);
414
+ const [isFullscreen, setIsFullscreen] = react.useState(false);
415
+ const [showSuggestions, setShowSuggestions] = react.useState(true);
416
+ const messagesEndRef = react.useRef(null);
417
+ const initialMessages = welcomeMessage ? [
418
+ {
419
+ id: "init",
420
+ role: "assistant",
421
+ content: welcomeMessage,
422
+ timestamp: /* @__PURE__ */ new Date()
423
+ }
424
+ ] : [];
425
+ const {
426
+ messages,
427
+ input,
428
+ setInput,
429
+ isLoading,
430
+ streamingMessageId,
431
+ sendMessage
432
+ } = useChat({
433
+ apiEndpoint,
434
+ initialMessages,
435
+ onToolCall,
436
+ onStreamStart: () => setShowSuggestions(false)
437
+ });
438
+ const {
439
+ isSupported: speechSupported,
440
+ isListening,
441
+ toggleListening,
442
+ speak,
443
+ ttsEnabled,
444
+ setTtsEnabled
445
+ } = useSpeech({
446
+ onResult: (transcript, isFinal) => {
447
+ setInput(transcript);
448
+ }
449
+ });
450
+ react.useEffect(() => {
451
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
452
+ }, [messages, isOpen]);
453
+ react.useEffect(() => {
454
+ if (!ttsEnabled) return;
455
+ const lastMessage = messages[messages.length - 1];
456
+ if (lastMessage?.role === "assistant" && lastMessage.id !== streamingMessageId && lastMessage.content) {
457
+ speak(lastMessage.content);
458
+ }
459
+ }, [messages, streamingMessageId, ttsEnabled, speak]);
460
+ const handleSuggestionSelect = react.useCallback(
461
+ (text) => {
462
+ sendMessage(text);
463
+ },
464
+ [sendMessage]
465
+ );
466
+ const handleSubmit = react.useCallback(() => {
467
+ sendMessage();
468
+ }, [sendMessage]);
469
+ const positionClasses = {
470
+ "bottom-right": "bottom-24 right-6 md:right-8",
471
+ "bottom-left": "bottom-24 left-6 md:left-8",
472
+ "top-right": "top-24 right-6 md:right-8",
473
+ "top-left": "top-24 left-6 md:left-8"
474
+ };
475
+ const buttonPositionClasses = {
476
+ "bottom-right": "bottom-6 right-6 md:right-8",
477
+ "bottom-left": "bottom-6 left-6 md:left-8",
478
+ "top-right": "top-6 right-6 md:right-8",
479
+ "top-left": "top-6 left-6 md:left-8"
480
+ };
481
+ const cssVars = {
482
+ "--pilot-accent": accent,
483
+ "--pilot-radius": `${borderRadius}px`
484
+ };
485
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `pilot-container ${className}`, style: cssVars, children: [
486
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxRuntime.jsxs(
487
+ framerMotion.motion.div,
488
+ {
489
+ initial: { opacity: 0, y: 20, scale: 0.95 },
490
+ animate: { opacity: 1, y: 0, scale: 1 },
491
+ exit: { opacity: 0, y: 20, scale: 0.95 },
492
+ transition: { type: "spring", stiffness: 400, damping: 30 },
493
+ layout: true,
494
+ className: `fixed pilot-panel flex flex-col shadow-2xl z-[200] transition-all duration-300 ${isFullscreen ? "inset-4 md:inset-8" : `${positionClasses[position]} w-[calc(100%-48px)] md:w-[400px] h-[520px]`}`,
495
+ style: { borderRadius: `${borderRadius}px` },
496
+ children: [
497
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 py-4 pilot-border-bottom flex justify-between items-center", children: [
498
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
499
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-10 h-10 pilot-avatar rounded-xl flex items-center justify-center shadow-lg", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-5 h-5 text-white" }) }),
500
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
501
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-semibold pilot-text text-sm", children: "AI Assistant" }),
502
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
503
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-1.5 h-1.5 pilot-status-dot rounded-full animate-pulse" }),
504
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] pilot-text-muted", children: "Ready to help" })
505
+ ] })
506
+ ] })
507
+ ] }),
508
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
509
+ tts && /* @__PURE__ */ jsxRuntime.jsx(
510
+ "button",
511
+ {
512
+ onClick: () => setTtsEnabled(!ttsEnabled),
513
+ className: `p-2 rounded-lg transition-colors ${ttsEnabled ? "pilot-button-active" : "pilot-button-inactive"}`,
514
+ title: ttsEnabled ? "Disable voice" : "Enable voice",
515
+ children: ttsEnabled ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VolumeX, { className: "w-4 h-4" })
516
+ }
517
+ ),
518
+ fullscreen && /* @__PURE__ */ jsxRuntime.jsx(
519
+ "button",
520
+ {
521
+ onClick: () => setIsFullscreen(!isFullscreen),
522
+ className: "p-2 rounded-lg transition-colors pilot-button-inactive",
523
+ title: isFullscreen ? "Exit fullscreen" : "Fullscreen",
524
+ children: isFullscreen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "w-4 h-4" })
525
+ }
526
+ ),
527
+ /* @__PURE__ */ jsxRuntime.jsx(
528
+ "button",
529
+ {
530
+ onClick: () => setIsOpen(false),
531
+ className: "p-2 hover:bg-white/5 rounded-lg transition-colors",
532
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4 pilot-text-muted" })
533
+ }
534
+ )
535
+ ] })
536
+ ] }),
537
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent", children: [
538
+ messages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx(
539
+ ChatMessage,
540
+ {
541
+ message: msg,
542
+ isStreaming: streamingMessageId === msg.id,
543
+ isFullscreen
544
+ },
545
+ msg.id
546
+ )),
547
+ isLoading && !streamingMessageId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-start", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pilot-loading px-4 py-3 rounded-2xl rounded-bl-md", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1.5", children: [0, 0.15, 0.3].map((delay, i) => /* @__PURE__ */ jsxRuntime.jsx(
548
+ framerMotion.motion.span,
549
+ {
550
+ className: "w-2 h-2 pilot-loading-dot rounded-full",
551
+ animate: { y: [0, -4, 0] },
552
+ transition: { duration: 0.6, repeat: Infinity, delay }
553
+ },
554
+ i
555
+ )) }) }) }),
556
+ showSuggestionsFeature && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
557
+ Suggestions,
558
+ {
559
+ suggestions,
560
+ onSelect: handleSuggestionSelect,
561
+ isVisible: showSuggestions && !isLoading,
562
+ isFullscreen
563
+ }
564
+ ),
565
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
566
+ ] }),
567
+ /* @__PURE__ */ jsxRuntime.jsx(
568
+ ChatInput,
569
+ {
570
+ value: input,
571
+ onChange: setInput,
572
+ onSubmit: handleSubmit,
573
+ disabled: isLoading,
574
+ placeholder,
575
+ isListening,
576
+ onToggleListening: toggleListening,
577
+ showMic: speech && speechSupported
578
+ }
579
+ )
580
+ ]
581
+ }
582
+ ) }),
583
+ !isOpen && /* @__PURE__ */ jsxRuntime.jsxs(
584
+ framerMotion.motion.button,
585
+ {
586
+ onClick: () => setIsOpen(true),
587
+ initial: { opacity: 0, scale: 0.8 },
588
+ animate: { opacity: 1, scale: 1 },
589
+ whileHover: { scale: 1.05 },
590
+ whileTap: { scale: 0.95 },
591
+ className: `fixed ${buttonPositionClasses[position]} flex items-center gap-2 pilot-toggle-button px-5 py-3.5 rounded-2xl font-medium shadow-xl transition-shadow z-[200]`,
592
+ children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircle, { className: "w-5 h-5" }),
594
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm hidden sm:inline", children: "Ask AI" }),
595
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2 h-2 bg-white/80 rounded-full animate-pulse" })
596
+ ]
597
+ }
598
+ )
599
+ ] });
600
+ }
601
+ var SitePilot_default = SitePilot;
602
+
603
+ exports.ChatInput = ChatInput;
604
+ exports.ChatMessage = ChatMessage;
605
+ exports.SitePilot = SitePilot;
606
+ exports.Suggestions = Suggestions;
607
+ exports.default = SitePilot_default;
608
+ exports.useChat = useChat;
609
+ exports.useSpeech = useSpeech;
610
+ //# sourceMappingURL=index.js.map
611
+ //# sourceMappingURL=index.js.map