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/README.md +295 -0
- package/dist/api/index.d.mts +78 -0
- package/dist/api/index.d.ts +78 -0
- package/dist/api/index.js +142 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +137 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +69 -0
- package/dist/hooks/index.d.ts +69 -0
- package/dist/hooks/index.js +240 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +237 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +611 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +597 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +238 -0
- package/dist/tools/index.d.mts +71 -0
- package/dist/tools/index.d.ts +71 -0
- package/dist/tools/index.js +124 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/index.mjs +119 -0
- package/dist/tools/index.mjs.map +1 -0
- package/dist/types--7jDyUM6.d.mts +32 -0
- package/dist/types--7jDyUM6.d.ts +32 -0
- package/dist/types-DAvVRuXd.d.mts +71 -0
- package/dist/types-DAvVRuXd.d.ts +71 -0
- package/package.json +81 -0
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
|