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