goji-search 1.1.4 → 2.0.1
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/goji-search/assets/avatar1.jpeg +0 -0
- package/dist/goji-search/assets/avatar2.jpeg +0 -0
- package/dist/goji-search/assets/avatar3.jpeg +0 -0
- package/dist/goji-search/assets/chat-logo.png +0 -0
- package/dist/goji-search/components/elements/closing-card.d.ts +20 -0
- package/dist/goji-search/components/elements/inspiration-menu.d.ts +3 -1
- package/dist/goji-search/components/elements/language-selector.d.ts +10 -0
- package/dist/goji-search/components/elements/message-list.d.ts +3 -1
- package/dist/goji-search/components/elements/search-input.d.ts +3 -1
- package/dist/goji-search/components/goji-search-component.d.ts +35 -2
- package/dist/goji-search/hooks/useCompanyTheme.d.ts +21 -0
- package/dist/goji-search/lib/goji-client.d.ts +24 -4
- package/dist/index.js +11754 -4
- package/package.json +20 -17
- package/dist/goji-search/components/elements/action-buttons.js +0 -164
- package/dist/goji-search/components/elements/auto-expanding-textarea.js +0 -46
- package/dist/goji-search/components/elements/calendar-integration.js +0 -49
- package/dist/goji-search/components/elements/inspiration-menu.js +0 -87
- package/dist/goji-search/components/elements/message-list.js +0 -301
- package/dist/goji-search/components/elements/search-input.js +0 -136
- package/dist/goji-search/components/elements/suggested-questions.js +0 -57
- package/dist/goji-search/components/goji-search-component.js +0 -679
- package/dist/goji-search/components/goji-search.css +0 -1
- package/dist/goji-search/config/company.js +0 -100
- package/dist/goji-search/lib/calendar-config.js +0 -10
- package/dist/goji-search/lib/goji-client.js +0 -229
|
@@ -1,679 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import "./goji-search.css";
|
|
4
|
-
import { ArrowLeft, X, Maximize2, Minimize2 } from "lucide-react";
|
|
5
|
-
import { useState, useEffect, useRef, useId, useMemo } from "react";
|
|
6
|
-
import { MessageList } from "./elements/message-list";
|
|
7
|
-
import chatLogo from "../assets/chat-logo.png";
|
|
8
|
-
import { SearchInput } from "./elements/search-input";
|
|
9
|
-
import { ActionButtons } from "./elements/action-buttons";
|
|
10
|
-
import CalendarIntegration from "./elements/calendar-integration";
|
|
11
|
-
import { createGojiClient } from "../lib/goji-client";
|
|
12
|
-
import { companyConfig } from "../config/company";
|
|
13
|
-
export function GojiSearchComponent({ apiUrl } = {}) {
|
|
14
|
-
// Create client instance based on provided API URL
|
|
15
|
-
const client = useMemo(() => createGojiClient(apiUrl), [apiUrl]);
|
|
16
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
17
|
-
const [size, setSize] = useState("xs");
|
|
18
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
19
|
-
const [messages, setMessages] = useState([]);
|
|
20
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
21
|
-
const [showCalendar, setShowCalendar] = useState(false);
|
|
22
|
-
const [sessionId, setSessionId] = useState();
|
|
23
|
-
const inputRef = useRef(null);
|
|
24
|
-
const sparkleRef = useRef(null);
|
|
25
|
-
const streamingCancelRef = useRef(null);
|
|
26
|
-
const mediaRecorderRef = useRef(null);
|
|
27
|
-
const audioChunksRef = useRef([]);
|
|
28
|
-
const componentRef = useRef(null);
|
|
29
|
-
const [hoverTopExpand, setHoverTopExpand] = useState(false);
|
|
30
|
-
const [hoverTopClose, setHoverTopClose] = useState(false);
|
|
31
|
-
const [isRecording, setIsRecording] = useState(false);
|
|
32
|
-
const [backendSuggestedQuestions, setBackendSuggestedQuestions] = useState(null);
|
|
33
|
-
const mouseLeaveTimeoutRef = useRef(null);
|
|
34
|
-
const mouseEnterTimeoutRef = useRef(null);
|
|
35
|
-
// Language selection state - will be set from backend default
|
|
36
|
-
const [selectedLanguage, setSelectedLanguage] = useState("en");
|
|
37
|
-
const [showLanguageMenu, setShowLanguageMenu] = useState(false);
|
|
38
|
-
const currentLanguage = selectedLanguage;
|
|
39
|
-
const languageConfig = companyConfig.languages[currentLanguage];
|
|
40
|
-
// Use backend-provided questions only (no hardcoded prompts)
|
|
41
|
-
const inspirationQuestions = backendSuggestedQuestions || [];
|
|
42
|
-
const idBase = useId();
|
|
43
|
-
const xTooltipId = `${idBase}-x-tip`;
|
|
44
|
-
const expandTooltipId = `${idBase}-expand-tip`;
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const timer = setTimeout(() => {
|
|
47
|
-
setSize("s");
|
|
48
|
-
}, 4000);
|
|
49
|
-
return () => clearTimeout(timer);
|
|
50
|
-
}, []);
|
|
51
|
-
// Close language menu when clicking outside
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (!showLanguageMenu)
|
|
54
|
-
return;
|
|
55
|
-
const handleClickOutside = (e) => {
|
|
56
|
-
const target = e.target;
|
|
57
|
-
if (!target.closest("[data-language-menu]")) {
|
|
58
|
-
setShowLanguageMenu(false);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
62
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
63
|
-
}, [showLanguageMenu]);
|
|
64
|
-
// Language change handler with persistence
|
|
65
|
-
const handleLanguageChange = (lang) => {
|
|
66
|
-
setSelectedLanguage(lang);
|
|
67
|
-
setShowLanguageMenu(false);
|
|
68
|
-
// Persist to localStorage
|
|
69
|
-
if (typeof window !== "undefined") {
|
|
70
|
-
localStorage.setItem("goji-language", lang);
|
|
71
|
-
}
|
|
72
|
-
// Reload suggestions for new language
|
|
73
|
-
client
|
|
74
|
-
.getSuggestions({ language: lang })
|
|
75
|
-
.then((res) => {
|
|
76
|
-
if (Array.isArray(res?.suggestions) && res.suggestions.length > 0) {
|
|
77
|
-
setBackendSuggestedQuestions(res.suggestions);
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
.catch((e) => console.error("Failed to fetch suggestions", e));
|
|
81
|
-
};
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
// Load initial suggestions and default language from backend on mount (ONLY ONCE)
|
|
84
|
-
;
|
|
85
|
-
(async () => {
|
|
86
|
-
try {
|
|
87
|
-
// Fetch suggestions with initial language (en)
|
|
88
|
-
const res = await client.getSuggestions({ language: "en" });
|
|
89
|
-
// Check if backend provides a different default language
|
|
90
|
-
if (res?.default_language && typeof res.default_language === "string") {
|
|
91
|
-
const backendDefaultLang = res.default_language.toLowerCase().trim();
|
|
92
|
-
// Validate it's a supported language
|
|
93
|
-
if (companyConfig.languages[backendDefaultLang]) {
|
|
94
|
-
// Set the language FIRST
|
|
95
|
-
setSelectedLanguage(backendDefaultLang);
|
|
96
|
-
// Then fetch suggestions for the correct language
|
|
97
|
-
if (backendDefaultLang !== "en") {
|
|
98
|
-
const langRes = await client.getSuggestions({ language: backendDefaultLang });
|
|
99
|
-
if (Array.isArray(langRes?.suggestions) && langRes.suggestions.length > 0) {
|
|
100
|
-
setBackendSuggestedQuestions(langRes.suggestions);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// Use English suggestions
|
|
105
|
-
if (Array.isArray(res?.suggestions) && res.suggestions.length > 0) {
|
|
106
|
-
setBackendSuggestedQuestions(res.suggestions);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
// No default language override, use English suggestions
|
|
113
|
-
if (Array.isArray(res?.suggestions) && res.suggestions.length > 0) {
|
|
114
|
-
setBackendSuggestedQuestions(res.suggestions);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (e) {
|
|
119
|
-
// Silent fail; UI will simply show empty inspiration until chat flows populate
|
|
120
|
-
console.error("Failed to fetch suggestions", e);
|
|
121
|
-
}
|
|
122
|
-
})();
|
|
123
|
-
return () => {
|
|
124
|
-
if (streamingCancelRef.current) {
|
|
125
|
-
streamingCancelRef.current();
|
|
126
|
-
}
|
|
127
|
-
client.closeWebSocket();
|
|
128
|
-
};
|
|
129
|
-
}, [client]);
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (size === "xs")
|
|
132
|
-
return;
|
|
133
|
-
const handleClickOutside = (e) => {
|
|
134
|
-
const target = e.target;
|
|
135
|
-
if (componentRef.current && !componentRef.current.contains(target)) {
|
|
136
|
-
handleClose();
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
140
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
141
|
-
}, [size]);
|
|
142
|
-
const handleSearch = async (e) => {
|
|
143
|
-
e.preventDefault();
|
|
144
|
-
if (!searchQuery.trim() || isStreaming)
|
|
145
|
-
return;
|
|
146
|
-
const userMessage = {
|
|
147
|
-
role: "user",
|
|
148
|
-
content: searchQuery.trim(),
|
|
149
|
-
timestamp: Date.now(),
|
|
150
|
-
};
|
|
151
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
152
|
-
setSearchQuery("");
|
|
153
|
-
// don't force-shrink xl -> l; only set to l if not currently xl
|
|
154
|
-
setSize((prev) => (prev === "xl" ? "xl" : "l"));
|
|
155
|
-
setIsStreaming(true);
|
|
156
|
-
// Create placeholder for assistant message
|
|
157
|
-
const assistantMessageIndex = messages.length + 1;
|
|
158
|
-
setMessages((prev) => [...prev, { role: "assistant", content: "", timestamp: Date.now() }]);
|
|
159
|
-
let accumulatedContent = "";
|
|
160
|
-
try {
|
|
161
|
-
const cancelFn = await client.streamChat({
|
|
162
|
-
message: userMessage.content,
|
|
163
|
-
sessionId,
|
|
164
|
-
limit: 5,
|
|
165
|
-
language: currentLanguage,
|
|
166
|
-
onDelta: (delta) => {
|
|
167
|
-
accumulatedContent += delta;
|
|
168
|
-
setMessages((prev) => {
|
|
169
|
-
const updated = [...prev];
|
|
170
|
-
updated[assistantMessageIndex] = {
|
|
171
|
-
...updated[assistantMessageIndex],
|
|
172
|
-
content: accumulatedContent,
|
|
173
|
-
};
|
|
174
|
-
return updated;
|
|
175
|
-
});
|
|
176
|
-
},
|
|
177
|
-
onDone: (response) => {
|
|
178
|
-
setSessionId(response.session_id);
|
|
179
|
-
setMessages((prev) => {
|
|
180
|
-
const updated = [...prev];
|
|
181
|
-
updated[assistantMessageIndex] = {
|
|
182
|
-
...updated[assistantMessageIndex],
|
|
183
|
-
content: response.answer,
|
|
184
|
-
sources: response.sources,
|
|
185
|
-
};
|
|
186
|
-
return updated;
|
|
187
|
-
});
|
|
188
|
-
// Update suggested questions from backend
|
|
189
|
-
if (response.suggested_questions && response.suggested_questions.length > 0) {
|
|
190
|
-
setBackendSuggestedQuestions(response.suggested_questions);
|
|
191
|
-
}
|
|
192
|
-
setIsStreaming(false);
|
|
193
|
-
streamingCancelRef.current = null;
|
|
194
|
-
},
|
|
195
|
-
onError: (error) => {
|
|
196
|
-
console.error(" Streaming error:", error);
|
|
197
|
-
setMessages((prev) => {
|
|
198
|
-
const updated = [...prev];
|
|
199
|
-
updated[assistantMessageIndex] = {
|
|
200
|
-
...updated[assistantMessageIndex],
|
|
201
|
-
content: "I'm sorry, I encountered an error. Please try again.",
|
|
202
|
-
};
|
|
203
|
-
return updated;
|
|
204
|
-
});
|
|
205
|
-
setIsStreaming(false);
|
|
206
|
-
streamingCancelRef.current = null;
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
streamingCancelRef.current = cancelFn;
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
console.error(" Failed to start streaming:", error);
|
|
213
|
-
setIsStreaming(false);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
const handleSuggestionClick = async (question) => {
|
|
217
|
-
const userMessage = {
|
|
218
|
-
role: "user",
|
|
219
|
-
content: question,
|
|
220
|
-
timestamp: Date.now(),
|
|
221
|
-
};
|
|
222
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
223
|
-
// preserve xl if already expanded
|
|
224
|
-
setSize((prev) => (prev === "xl" ? "xl" : "l"));
|
|
225
|
-
setIsStreaming(true);
|
|
226
|
-
const assistantMessageIndex = messages.length + 1;
|
|
227
|
-
setMessages((prev) => [...prev, { role: "assistant", content: "", timestamp: Date.now() }]);
|
|
228
|
-
let accumulatedContent = "";
|
|
229
|
-
try {
|
|
230
|
-
const cancelFn = await client.streamChat({
|
|
231
|
-
message: question,
|
|
232
|
-
sessionId,
|
|
233
|
-
limit: 5,
|
|
234
|
-
language: currentLanguage,
|
|
235
|
-
onDelta: (delta) => {
|
|
236
|
-
accumulatedContent += delta;
|
|
237
|
-
setMessages((prev) => {
|
|
238
|
-
const updated = [...prev];
|
|
239
|
-
updated[assistantMessageIndex] = {
|
|
240
|
-
...updated[assistantMessageIndex],
|
|
241
|
-
content: accumulatedContent,
|
|
242
|
-
};
|
|
243
|
-
return updated;
|
|
244
|
-
});
|
|
245
|
-
},
|
|
246
|
-
onDone: (response) => {
|
|
247
|
-
setSessionId(response.session_id);
|
|
248
|
-
setMessages((prev) => {
|
|
249
|
-
const updated = [...prev];
|
|
250
|
-
updated[assistantMessageIndex] = {
|
|
251
|
-
...updated[assistantMessageIndex],
|
|
252
|
-
content: response.answer,
|
|
253
|
-
sources: response.sources,
|
|
254
|
-
};
|
|
255
|
-
return updated;
|
|
256
|
-
});
|
|
257
|
-
// Update suggested questions from backend
|
|
258
|
-
if (response.suggested_questions && response.suggested_questions.length > 0) {
|
|
259
|
-
setBackendSuggestedQuestions(response.suggested_questions);
|
|
260
|
-
}
|
|
261
|
-
setIsStreaming(false);
|
|
262
|
-
streamingCancelRef.current = null;
|
|
263
|
-
},
|
|
264
|
-
onError: (error) => {
|
|
265
|
-
console.error(" Streaming error:", error);
|
|
266
|
-
setMessages((prev) => {
|
|
267
|
-
const updated = [...prev];
|
|
268
|
-
updated[assistantMessageIndex] = {
|
|
269
|
-
...updated[assistantMessageIndex],
|
|
270
|
-
content: "I'm sorry, I encountered an error. Please try again.",
|
|
271
|
-
};
|
|
272
|
-
return updated;
|
|
273
|
-
});
|
|
274
|
-
setIsStreaming(false);
|
|
275
|
-
streamingCancelRef.current = null;
|
|
276
|
-
},
|
|
277
|
-
});
|
|
278
|
-
streamingCancelRef.current = cancelFn;
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
console.error(" Failed to start streaming:", error);
|
|
282
|
-
setIsStreaming(false);
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
const handleClose = () => {
|
|
286
|
-
setSearchQuery("");
|
|
287
|
-
setSize("xs");
|
|
288
|
-
setShowCalendar(false);
|
|
289
|
-
setHoverTopClose(false);
|
|
290
|
-
};
|
|
291
|
-
const handleVoiceSearch = async () => {
|
|
292
|
-
try {
|
|
293
|
-
if (typeof window === "undefined")
|
|
294
|
-
return;
|
|
295
|
-
if (isRecording) {
|
|
296
|
-
try {
|
|
297
|
-
mediaRecorderRef.current?.stop();
|
|
298
|
-
}
|
|
299
|
-
catch (_) { }
|
|
300
|
-
setIsRecording(false);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
304
|
-
const recorder = new MediaRecorder(stream);
|
|
305
|
-
mediaRecorderRef.current = recorder;
|
|
306
|
-
audioChunksRef.current = [];
|
|
307
|
-
recorder.ondataavailable = (e) => {
|
|
308
|
-
if (e.data && e.data.size > 0) {
|
|
309
|
-
audioChunksRef.current.push(e.data);
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
recorder.onstop = async () => {
|
|
313
|
-
try {
|
|
314
|
-
const blob = new Blob(audioChunksRef.current, { type: "audio/webm" });
|
|
315
|
-
// Send to backend for transcription
|
|
316
|
-
const language = companyConfig.defaultLanguage || "en";
|
|
317
|
-
const result = await client.transcribeAudio({
|
|
318
|
-
file: blob,
|
|
319
|
-
filename: "recording.webm",
|
|
320
|
-
model: "gpt-4o-transcribe",
|
|
321
|
-
language,
|
|
322
|
-
});
|
|
323
|
-
const text = (result?.text || "").trim();
|
|
324
|
-
if (!text)
|
|
325
|
-
return;
|
|
326
|
-
// Auto-submit the transcribed text
|
|
327
|
-
const userMessage = {
|
|
328
|
-
role: "user",
|
|
329
|
-
content: text,
|
|
330
|
-
timestamp: Date.now(),
|
|
331
|
-
};
|
|
332
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
333
|
-
setSearchQuery("");
|
|
334
|
-
setSize((prev) => (prev === "xl" ? "xl" : "l"));
|
|
335
|
-
setIsStreaming(true);
|
|
336
|
-
const assistantMessageIndex = messages.length + 1;
|
|
337
|
-
setMessages((prev) => [...prev, { role: "assistant", content: "", timestamp: Date.now() }]);
|
|
338
|
-
let accumulatedContent = "";
|
|
339
|
-
try {
|
|
340
|
-
const cancelFn = await client.streamChat({
|
|
341
|
-
message: userMessage.content,
|
|
342
|
-
sessionId,
|
|
343
|
-
limit: 5,
|
|
344
|
-
language: currentLanguage,
|
|
345
|
-
onDelta: (delta) => {
|
|
346
|
-
accumulatedContent += delta;
|
|
347
|
-
setMessages((prev) => {
|
|
348
|
-
const updated = [...prev];
|
|
349
|
-
updated[assistantMessageIndex] = {
|
|
350
|
-
...updated[assistantMessageIndex],
|
|
351
|
-
content: accumulatedContent,
|
|
352
|
-
};
|
|
353
|
-
return updated;
|
|
354
|
-
});
|
|
355
|
-
},
|
|
356
|
-
onDone: (response) => {
|
|
357
|
-
setSessionId(response.session_id);
|
|
358
|
-
setMessages((prev) => {
|
|
359
|
-
const updated = [...prev];
|
|
360
|
-
updated[assistantMessageIndex] = {
|
|
361
|
-
...updated[assistantMessageIndex],
|
|
362
|
-
content: response.answer,
|
|
363
|
-
sources: response.sources,
|
|
364
|
-
};
|
|
365
|
-
return updated;
|
|
366
|
-
});
|
|
367
|
-
// Update suggested questions from backend
|
|
368
|
-
if (response.suggested_questions && response.suggested_questions.length > 0) {
|
|
369
|
-
setBackendSuggestedQuestions(response.suggested_questions);
|
|
370
|
-
}
|
|
371
|
-
setIsStreaming(false);
|
|
372
|
-
streamingCancelRef.current = null;
|
|
373
|
-
},
|
|
374
|
-
onError: (error) => {
|
|
375
|
-
console.error(" Streaming error:", error);
|
|
376
|
-
setMessages((prev) => {
|
|
377
|
-
const updated = [...prev];
|
|
378
|
-
updated[assistantMessageIndex] = {
|
|
379
|
-
...updated[assistantMessageIndex],
|
|
380
|
-
content: "I'm sorry, I encountered an error. Please try again.",
|
|
381
|
-
};
|
|
382
|
-
return updated;
|
|
383
|
-
});
|
|
384
|
-
setIsStreaming(false);
|
|
385
|
-
streamingCancelRef.current = null;
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
streamingCancelRef.current = cancelFn;
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
console.error(" Failed to start streaming:", error);
|
|
392
|
-
setIsStreaming(false);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
catch (err) {
|
|
396
|
-
console.error("Transcription failed:", err);
|
|
397
|
-
}
|
|
398
|
-
finally {
|
|
399
|
-
// Cleanup audio tracks
|
|
400
|
-
try {
|
|
401
|
-
mediaRecorderRef.current?.stream?.getTracks?.().forEach((t) => t.stop());
|
|
402
|
-
}
|
|
403
|
-
catch (_) { }
|
|
404
|
-
mediaRecorderRef.current = null;
|
|
405
|
-
audioChunksRef.current = [];
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
recorder.start(250);
|
|
409
|
-
setIsRecording(true);
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
console.error("Voice search failed:", err);
|
|
413
|
-
setIsRecording(false);
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
const handleToggle = () => {
|
|
417
|
-
if (size === "xs") {
|
|
418
|
-
setSize(messages.length > 0 ? "l" : "s");
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
setSize("xs");
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
const handleExpandToggle = () => {
|
|
425
|
-
// toggle between large and extra-large
|
|
426
|
-
if (size === "xl") {
|
|
427
|
-
setSize("l");
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
setSize("xl");
|
|
431
|
-
}
|
|
432
|
-
setHoverTopExpand(false);
|
|
433
|
-
};
|
|
434
|
-
const handleInputFocus = () => {
|
|
435
|
-
if (messages.length > 0) {
|
|
436
|
-
// if currently xl, keep xl; otherwise go to l
|
|
437
|
-
setSize((prev) => (prev === "xl" ? "xl" : "l"));
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
setSize("m");
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
const handleInputChange = (e) => {
|
|
444
|
-
setSearchQuery(e.target.value);
|
|
445
|
-
if (e.target.value && size === "s") {
|
|
446
|
-
setSize("m");
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
const handleMouseEnter = () => {
|
|
450
|
-
setIsHovered(true);
|
|
451
|
-
if (mouseLeaveTimeoutRef.current) {
|
|
452
|
-
clearTimeout(mouseLeaveTimeoutRef.current);
|
|
453
|
-
mouseLeaveTimeoutRef.current = null;
|
|
454
|
-
}
|
|
455
|
-
if (mouseEnterTimeoutRef.current) {
|
|
456
|
-
clearTimeout(mouseEnterTimeoutRef.current);
|
|
457
|
-
mouseEnterTimeoutRef.current = null;
|
|
458
|
-
}
|
|
459
|
-
mouseEnterTimeoutRef.current = setTimeout(() => {
|
|
460
|
-
if (size === "s") {
|
|
461
|
-
setSize("m");
|
|
462
|
-
}
|
|
463
|
-
mouseEnterTimeoutRef.current = null;
|
|
464
|
-
}, 100);
|
|
465
|
-
};
|
|
466
|
-
const handleMouseLeave = () => {
|
|
467
|
-
setIsHovered(false);
|
|
468
|
-
if (mouseEnterTimeoutRef.current) {
|
|
469
|
-
clearTimeout(mouseEnterTimeoutRef.current);
|
|
470
|
-
mouseEnterTimeoutRef.current = null;
|
|
471
|
-
}
|
|
472
|
-
mouseLeaveTimeoutRef.current = setTimeout(() => {
|
|
473
|
-
if (size === "m" && !searchQuery && messages.length === 0) {
|
|
474
|
-
setSize("s");
|
|
475
|
-
}
|
|
476
|
-
mouseLeaveTimeoutRef.current = null;
|
|
477
|
-
}, 150);
|
|
478
|
-
};
|
|
479
|
-
const handleInputBlur = () => {
|
|
480
|
-
if (!searchQuery && messages.length === 0) {
|
|
481
|
-
setSize("s");
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
const handleCalendarClick = () => {
|
|
485
|
-
setShowCalendar(true);
|
|
486
|
-
setSize("xl");
|
|
487
|
-
};
|
|
488
|
-
const handleBackToChat = () => {
|
|
489
|
-
setShowCalendar(false);
|
|
490
|
-
};
|
|
491
|
-
const sizeConfig = {
|
|
492
|
-
xs: { maxWidth: "5.5rem", bottom: "0.5rem", padding: "0" },
|
|
493
|
-
s: { maxWidth: "25.5rem", bottom: "0.5rem", padding: "0" },
|
|
494
|
-
m: { maxWidth: "30rem", bottom: "0.75rem", padding: "0.25rem" },
|
|
495
|
-
l: { maxWidth: "45vw", bottom: "1.25rem", padding: "0.5rem" },
|
|
496
|
-
xl: { maxWidth: "70vw", bottom: "1.5rem", padding: "0.75rem" },
|
|
497
|
-
};
|
|
498
|
-
const currentConfig = sizeConfig[size];
|
|
499
|
-
return (_jsx("div", { style: {
|
|
500
|
-
position: "fixed",
|
|
501
|
-
bottom: currentConfig.bottom,
|
|
502
|
-
left: 0,
|
|
503
|
-
right: 0,
|
|
504
|
-
zIndex: 50,
|
|
505
|
-
padding: "0.7rem",
|
|
506
|
-
transition: "bottom 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
507
|
-
}, children: _jsx("div", { style: {
|
|
508
|
-
maxWidth: currentConfig.maxWidth,
|
|
509
|
-
margin: "0 auto",
|
|
510
|
-
transition: "max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
511
|
-
}, children: _jsxs("div", { ref: componentRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: {
|
|
512
|
-
borderRadius: size === "xs" ? "0.75rem" : "1rem",
|
|
513
|
-
border: size === "xs" || size === "s" ? "none" : "1px solid rgba(85, 85, 85, 0.18)",
|
|
514
|
-
backgroundColor: size === "xs" || size === "s" ? "transparent" : "rgba(245, 245, 250, 0.12)",
|
|
515
|
-
padding: size === "s" ? "0" : currentConfig.padding,
|
|
516
|
-
// remove top padding so inner message list / calendar sit flush with the component border
|
|
517
|
-
paddingTop: 0,
|
|
518
|
-
height: size === "l" ? "55vh" : size === "xl" ? "70vh" : "auto",
|
|
519
|
-
display: "flex",
|
|
520
|
-
flexDirection: "column",
|
|
521
|
-
position: "relative",
|
|
522
|
-
boxShadow: size === "xs" || size === "s"
|
|
523
|
-
? "none"
|
|
524
|
-
: size === "l"
|
|
525
|
-
? "0 8px 32px 0 rgba(0, 0, 0, 0.12), inset 0 1px 0 0 rgba(255, 255, 255, 0.4)"
|
|
526
|
-
: "0 4px 24px 0 rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.3)",
|
|
527
|
-
backdropFilter: size === "xs" || size === "s" ? "none" : "blur(40px) saturate(180%) brightness(110%)",
|
|
528
|
-
WebkitBackdropFilter: size === "xs" || size === "s" ? "none" : "blur(40px) saturate(180%) brightness(110%)",
|
|
529
|
-
transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
530
|
-
}, children: [["m", "l", "xl"].includes(size) && (_jsx("div", { style: {
|
|
531
|
-
position: "absolute",
|
|
532
|
-
top: 0,
|
|
533
|
-
right: "0.5rem",
|
|
534
|
-
zIndex: 50,
|
|
535
|
-
paddingBottom: "0.5rem",
|
|
536
|
-
}, children: _jsx("div", { style: {
|
|
537
|
-
position: "relative",
|
|
538
|
-
display: ((size === "l" || size === "xl") && messages.length > 0) || showCalendar ? "flex" : "none",
|
|
539
|
-
gap: "0.5rem",
|
|
540
|
-
alignItems: "center",
|
|
541
|
-
justifyContent: "flex-end",
|
|
542
|
-
zIndex: 20,
|
|
543
|
-
paddingRight: "0.3rem",
|
|
544
|
-
backgroundColor: "transparent",
|
|
545
|
-
borderRadius: "0.75rem",
|
|
546
|
-
}, children: _jsxs("div", { style: {
|
|
547
|
-
display: "flex",
|
|
548
|
-
gap: "0.375rem",
|
|
549
|
-
alignItems: "center",
|
|
550
|
-
padding: "0.3rem 0 0.5rem 0",
|
|
551
|
-
}, children: [_jsxs("div", { style: { position: "relative" }, children: [_jsx("button", { type: "button", onClick: handleExpandToggle, onMouseEnter: () => setHoverTopExpand(true), onMouseLeave: () => setHoverTopExpand(false), "aria-label": size === "xl" ? "Restore" : "Expand", "aria-describedby": hoverTopExpand ? expandTooltipId : undefined, style: {
|
|
552
|
-
padding: "0.375rem",
|
|
553
|
-
display: "flex",
|
|
554
|
-
alignItems: "center",
|
|
555
|
-
justifyContent: "center",
|
|
556
|
-
color: "rgba(0, 0, 0, 0.75)",
|
|
557
|
-
transition: "all 0.15s",
|
|
558
|
-
cursor: "pointer",
|
|
559
|
-
outline: "none",
|
|
560
|
-
}, children: size === "xl" ? (_jsx(Minimize2, { style: { width: "0.9rem", height: "0.9rem" } })) : (_jsx(Maximize2, { style: { width: "0.9rem", height: "0.9rem" } })) }), _jsx("span", { id: expandTooltipId, role: "tooltip", style: {
|
|
561
|
-
position: "absolute",
|
|
562
|
-
top: "calc(100% + 0.4rem)",
|
|
563
|
-
left: "50%",
|
|
564
|
-
transform: "translateX(-50%)",
|
|
565
|
-
whiteSpace: "nowrap",
|
|
566
|
-
background: "rgba(0,0,0,0.9)",
|
|
567
|
-
color: "#fff",
|
|
568
|
-
padding: "4px 6px",
|
|
569
|
-
borderRadius: "1.3rem",
|
|
570
|
-
fontSize: "0.55rem",
|
|
571
|
-
boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
|
|
572
|
-
opacity: hoverTopExpand ? 1 : 0,
|
|
573
|
-
pointerEvents: "none",
|
|
574
|
-
transition: "opacity 120ms ease-in-out",
|
|
575
|
-
zIndex: 30,
|
|
576
|
-
}, children: size === "xl" ? "Restore" : "Expand" })] }), _jsxs("div", { style: { position: "relative" }, children: [_jsx("button", { type: "button", onClick: handleClose, onMouseEnter: () => setHoverTopClose(true), onMouseLeave: () => setHoverTopClose(false), "aria-label": "Close", "aria-describedby": hoverTopClose ? xTooltipId : undefined, style: {
|
|
577
|
-
padding: "0.375rem",
|
|
578
|
-
display: "flex",
|
|
579
|
-
alignItems: "center",
|
|
580
|
-
justifyContent: "center",
|
|
581
|
-
color: "rgba(0, 0, 0, 0.75)",
|
|
582
|
-
transition: "all 0.15s",
|
|
583
|
-
cursor: "pointer",
|
|
584
|
-
outline: "none",
|
|
585
|
-
}, children: _jsx(X, { style: { width: "0.9rem", height: "0.9rem" } }) }), _jsx("span", { id: xTooltipId, role: "tooltip", style: {
|
|
586
|
-
position: "absolute",
|
|
587
|
-
top: "calc(100% + 0.4rem)",
|
|
588
|
-
left: "50%",
|
|
589
|
-
transform: "translateX(-50%)",
|
|
590
|
-
whiteSpace: "nowrap",
|
|
591
|
-
background: "rgba(0,0,0,0.9)",
|
|
592
|
-
color: "#fff",
|
|
593
|
-
padding: "4px 6px",
|
|
594
|
-
borderRadius: "1.3rem",
|
|
595
|
-
fontSize: "0.55rem",
|
|
596
|
-
boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
|
|
597
|
-
opacity: hoverTopClose ? 1 : 0,
|
|
598
|
-
pointerEvents: "none",
|
|
599
|
-
transition: "opacity 120ms ease-in-out",
|
|
600
|
-
zIndex: 30,
|
|
601
|
-
}, children: "Close" })] })] }) }) })), size === "xs" ? (_jsx("button", { type: "button", onClick: handleToggle, style: {
|
|
602
|
-
background: "transparent",
|
|
603
|
-
border: "none",
|
|
604
|
-
padding: 0,
|
|
605
|
-
display: "flex",
|
|
606
|
-
alignItems: "center",
|
|
607
|
-
justifyContent: "center",
|
|
608
|
-
cursor: "pointer",
|
|
609
|
-
outline: "none",
|
|
610
|
-
width: "100%",
|
|
611
|
-
}, children: _jsxs("div", { className: "ai", children: [_jsxs("div", { className: "container-ai", children: [_jsx("div", { className: "c c4" }), _jsx("div", { className: "c c1" }), _jsx("div", { className: "c c2" }), _jsx("div", { className: "c c3" }), _jsx("div", { className: "rings-ai", children: _jsx("div", { className: "rings-ai" }) })] }), _jsx("div", { className: "glass-ai" })] }) })) : (_jsx("div", { style: {
|
|
612
|
-
display: "flex",
|
|
613
|
-
flexDirection: "column",
|
|
614
|
-
gap: "0.75rem",
|
|
615
|
-
height: "100%",
|
|
616
|
-
}, children: showCalendar ? (_jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: "0.75rem", minHeight: 0 }, children: [_jsx("button", { type: "button", onClick: handleBackToChat, style: {
|
|
617
|
-
display: "flex",
|
|
618
|
-
alignItems: "center",
|
|
619
|
-
justifyContent: "center",
|
|
620
|
-
padding: "0.3rem 0 0.5rem 0.3rem",
|
|
621
|
-
color: "rgba(0, 0, 0, 0.75)",
|
|
622
|
-
cursor: "pointer",
|
|
623
|
-
transition: "all 0.2s",
|
|
624
|
-
alignSelf: "flex-start",
|
|
625
|
-
outline: "none",
|
|
626
|
-
}, children: _jsx(ArrowLeft, { style: { width: "0.875rem", height: "0.875rem" } }) }), _jsx("div", { style: {
|
|
627
|
-
flex: 1,
|
|
628
|
-
borderRadius: "0.75rem",
|
|
629
|
-
overflow: "hidden",
|
|
630
|
-
backgroundColor: "transparent",
|
|
631
|
-
minHeight: 0,
|
|
632
|
-
display: "flex",
|
|
633
|
-
flexDirection: "column",
|
|
634
|
-
}, children: _jsx(CalendarIntegration, { onBooked: handleBackToChat }) })] })) : (_jsxs(_Fragment, { children: [(size === "l" || size === "xl") && messages.length > 0 && (_jsx(MessageList, { messages: messages, isStreaming: isStreaming, aiAvatarSrc: chatLogo, size: size })), size === "s" ? (_jsxs("div", { style: {
|
|
635
|
-
display: "flex",
|
|
636
|
-
alignItems: "center",
|
|
637
|
-
gap: "0.35rem",
|
|
638
|
-
transition: "max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
639
|
-
}, children: [_jsx("button", { type: "button", onClick: handleToggle, style: {
|
|
640
|
-
background: "transparent",
|
|
641
|
-
border: "none",
|
|
642
|
-
padding: 0,
|
|
643
|
-
display: "flex",
|
|
644
|
-
alignItems: "center",
|
|
645
|
-
justifyContent: "center",
|
|
646
|
-
cursor: "pointer",
|
|
647
|
-
outline: "none",
|
|
648
|
-
flexShrink: 0,
|
|
649
|
-
}, children: _jsxs("div", { className: "ai", children: [_jsxs("div", { className: "container-ai", children: [_jsx("div", { className: "c c4" }), _jsx("div", { className: "c c1" }), _jsx("div", { className: "c c2" }), _jsx("div", { className: "c c3" }), _jsx("div", { className: "rings-ai", children: _jsx("div", { className: "rings-ai" }) })] }), _jsx("div", { className: "glass-ai" })] }) }), _jsx("div", { style: {
|
|
650
|
-
display: "flex",
|
|
651
|
-
flexDirection: "column",
|
|
652
|
-
gap: "0.35rem",
|
|
653
|
-
flex: 1,
|
|
654
|
-
backgroundColor: "transparent",
|
|
655
|
-
borderRadius: "1.5rem",
|
|
656
|
-
border: "1px solid rgba(85, 85, 85, 0.18)",
|
|
657
|
-
boxShadow: "0 4px 24px 0 rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.3)",
|
|
658
|
-
backdropFilter: "blur(40px) saturate(180%) brightness(110%)",
|
|
659
|
-
WebkitBackdropFilter: "blur(40px) saturate(180%) brightness(110%)",
|
|
660
|
-
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
661
|
-
}, children: _jsxs("form", { onSubmit: handleSearch, style: {
|
|
662
|
-
display: "flex",
|
|
663
|
-
alignItems: "center",
|
|
664
|
-
gap: "0.375rem",
|
|
665
|
-
transition: "gap 0.3s ease",
|
|
666
|
-
flexShrink: 0,
|
|
667
|
-
}, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(SearchInput, { value: searchQuery, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: "Search...", size: size, setSize: setSize, inputRef: inputRef, inspirationQuestions: inspirationQuestions, onInspirationClick: handleSuggestionClick, sparkleRef: sparkleRef, suggestedLabel: languageConfig.suggested }) }), _jsx(ActionButtons, { size: size, isHovered: isHovered, isStreaming: isStreaming, searchQuery: searchQuery, isRecording: isRecording, onSubmit: () => { }, onVoiceSearch: handleVoiceSearch, onClose: handleClose, onCalendarClick: handleCalendarClick })] }) })] })) : (_jsx("div", { style: {
|
|
668
|
-
display: "flex",
|
|
669
|
-
flexDirection: "column",
|
|
670
|
-
gap: "0.5rem",
|
|
671
|
-
paddingTop: 0,
|
|
672
|
-
}, children: _jsxs("form", { onSubmit: handleSearch, style: {
|
|
673
|
-
display: "flex",
|
|
674
|
-
alignItems: "center",
|
|
675
|
-
gap: "0.375rem",
|
|
676
|
-
transition: "gap 0.3s ease",
|
|
677
|
-
flexShrink: 0,
|
|
678
|
-
}, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(SearchInput, { value: searchQuery, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: "Ask me anything", size: size, setSize: setSize, inputRef: inputRef, inspirationQuestions: inspirationQuestions, onInspirationClick: handleSuggestionClick, sparkleRef: sparkleRef, suggestedLabel: languageConfig.suggested }) }), _jsx(ActionButtons, { size: size, isHovered: isHovered, isStreaming: isStreaming, searchQuery: searchQuery, isRecording: isRecording, onSubmit: () => { }, onVoiceSearch: handleVoiceSearch, onClose: handleClose, onCalendarClick: handleCalendarClick })] }) }))] })) }))] }) }) }));
|
|
679
|
-
}
|