goji-search 1.0.1 → 1.1.2

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.
@@ -1,34 +1,44 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import "./goji-search.css";
3
4
  import { ArrowLeft, X, Maximize2, Minimize2 } from "lucide-react";
4
- import { useState, useEffect, useRef, useId } from "react";
5
- import { SuggestedQuestions } from "./elements/suggested-questions";
5
+ import { useState, useEffect, useRef, useId, useMemo } from "react";
6
6
  import { MessageList } from "./elements/message-list";
7
+ import chatLogo from "../assets/chat-logo.png";
7
8
  import { SearchInput } from "./elements/search-input";
8
9
  import { ActionButtons } from "./elements/action-buttons";
9
10
  import CalendarIntegration from "./elements/calendar-integration";
10
- import { gojiClient } from "../lib/goji-client";
11
+ import { createGojiClient } from "../lib/goji-client";
11
12
  import { companyConfig } from "../config/company";
12
- export function GojiSearchComponent() {
13
+ export function GojiSearchComponent({ apiUrl } = {}) {
14
+ // Create client instance based on provided API URL
15
+ const client = useMemo(() => createGojiClient(apiUrl), [apiUrl]);
13
16
  const [searchQuery, setSearchQuery] = useState("");
14
17
  const [size, setSize] = useState("xs");
15
18
  const [isHovered, setIsHovered] = useState(false);
16
19
  const [messages, setMessages] = useState([]);
17
20
  const [isStreaming, setIsStreaming] = useState(false);
18
- const [showSuggestions, setShowSuggestions] = useState(false);
19
21
  const [showCalendar, setShowCalendar] = useState(false);
20
22
  const [sessionId, setSessionId] = useState();
21
- const [hasInteracted, setHasInteracted] = useState(false);
22
23
  const inputRef = useRef(null);
23
24
  const sparkleRef = useRef(null);
24
25
  const streamingCancelRef = useRef(null);
25
- const [isInputHovered, setIsInputHovered] = useState(false);
26
+ const mediaRecorderRef = useRef(null);
27
+ const audioChunksRef = useRef([]);
28
+ const componentRef = useRef(null);
26
29
  const [hoverTopExpand, setHoverTopExpand] = useState(false);
27
30
  const [hoverTopClose, setHoverTopClose] = useState(false);
28
- const currentLanguage = companyConfig.defaultLanguage;
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;
29
39
  const languageConfig = companyConfig.languages[currentLanguage];
30
- const suggestedQuestions = languageConfig.suggestedPrompts;
31
- const inspirationQuestions = languageConfig.suggestedPrompts;
40
+ // Use backend-provided questions only (no hardcoded prompts)
41
+ const inspirationQuestions = backendSuggestedQuestions || [];
32
42
  const idBase = useId();
33
43
  const xTooltipId = `${idBase}-x-tip`;
34
44
  const expandTooltipId = `${idBase}-expand-tip`;
@@ -38,22 +48,97 @@ export function GojiSearchComponent() {
38
48
  }, 4000);
39
49
  return () => clearTimeout(timer);
40
50
  }, []);
51
+ // Close language menu when clicking outside
41
52
  useEffect(() => {
42
- const timer = setTimeout(() => {
43
- if (!hasInteracted && size !== "xs") {
44
- setSize("xs");
53
+ if (!showLanguageMenu)
54
+ return;
55
+ const handleClickOutside = (e) => {
56
+ const target = e.target;
57
+ if (!target.closest("[data-language-menu]")) {
58
+ setShowLanguageMenu(false);
45
59
  }
46
- }, 12000);
47
- return () => clearTimeout(timer);
48
- }, [hasInteracted, size]);
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
+ };
49
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
+ })();
50
123
  return () => {
51
124
  if (streamingCancelRef.current) {
52
125
  streamingCancelRef.current();
53
126
  }
54
- gojiClient.closeWebSocket();
127
+ client.closeWebSocket();
55
128
  };
56
- }, []);
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]);
57
142
  const handleSearch = async (e) => {
58
143
  e.preventDefault();
59
144
  if (!searchQuery.trim() || isStreaming)
@@ -73,11 +158,11 @@ export function GojiSearchComponent() {
73
158
  setMessages((prev) => [...prev, { role: "assistant", content: "", timestamp: Date.now() }]);
74
159
  let accumulatedContent = "";
75
160
  try {
76
- const cancelFn = await gojiClient.streamChat({
161
+ const cancelFn = await client.streamChat({
77
162
  message: userMessage.content,
78
163
  sessionId,
79
164
  limit: 5,
80
- language: "en",
165
+ language: currentLanguage,
81
166
  onDelta: (delta) => {
82
167
  accumulatedContent += delta;
83
168
  setMessages((prev) => {
@@ -95,10 +180,15 @@ export function GojiSearchComponent() {
95
180
  const updated = [...prev];
96
181
  updated[assistantMessageIndex] = {
97
182
  ...updated[assistantMessageIndex],
183
+ content: response.answer,
98
184
  sources: response.sources,
99
185
  };
100
186
  return updated;
101
187
  });
188
+ // Update suggested questions from backend
189
+ if (response.suggested_questions && response.suggested_questions.length > 0) {
190
+ setBackendSuggestedQuestions(response.suggested_questions);
191
+ }
102
192
  setIsStreaming(false);
103
193
  streamingCancelRef.current = null;
104
194
  },
@@ -124,7 +214,6 @@ export function GojiSearchComponent() {
124
214
  }
125
215
  };
126
216
  const handleSuggestionClick = async (question) => {
127
- setShowSuggestions(false);
128
217
  const userMessage = {
129
218
  role: "user",
130
219
  content: question,
@@ -138,11 +227,11 @@ export function GojiSearchComponent() {
138
227
  setMessages((prev) => [...prev, { role: "assistant", content: "", timestamp: Date.now() }]);
139
228
  let accumulatedContent = "";
140
229
  try {
141
- const cancelFn = await gojiClient.streamChat({
230
+ const cancelFn = await client.streamChat({
142
231
  message: question,
143
232
  sessionId,
144
233
  limit: 5,
145
- language: "en",
234
+ language: currentLanguage,
146
235
  onDelta: (delta) => {
147
236
  accumulatedContent += delta;
148
237
  setMessages((prev) => {
@@ -160,10 +249,15 @@ export function GojiSearchComponent() {
160
249
  const updated = [...prev];
161
250
  updated[assistantMessageIndex] = {
162
251
  ...updated[assistantMessageIndex],
252
+ content: response.answer,
163
253
  sources: response.sources,
164
254
  };
165
255
  return updated;
166
256
  });
257
+ // Update suggested questions from backend
258
+ if (response.suggested_questions && response.suggested_questions.length > 0) {
259
+ setBackendSuggestedQuestions(response.suggested_questions);
260
+ }
167
261
  setIsStreaming(false);
168
262
  streamingCancelRef.current = null;
169
263
  },
@@ -192,12 +286,134 @@ export function GojiSearchComponent() {
192
286
  setSearchQuery("");
193
287
  setSize("xs");
194
288
  setShowCalendar(false);
289
+ setHoverTopClose(false);
195
290
  };
196
- const handleVoiceSearch = () => {
197
- console.log("Voice search activated");
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
+ }
198
415
  };
199
416
  const handleToggle = () => {
200
- setHasInteracted(true);
201
417
  if (size === "xs") {
202
418
  setSize(messages.length > 0 ? "l" : "s");
203
419
  }
@@ -213,53 +429,52 @@ export function GojiSearchComponent() {
213
429
  else {
214
430
  setSize("xl");
215
431
  }
432
+ setHoverTopExpand(false);
216
433
  };
217
434
  const handleInputFocus = () => {
218
- setHasInteracted(true);
219
435
  if (messages.length > 0) {
220
436
  // if currently xl, keep xl; otherwise go to l
221
437
  setSize((prev) => (prev === "xl" ? "xl" : "l"));
222
438
  }
223
439
  else {
224
440
  setSize("m");
225
- setShowSuggestions(true);
226
- }
227
- };
228
- const handleInputAreaMouseEnter = () => {
229
- setHasInteracted(true);
230
- setIsInputHovered(true);
231
- if (messages.length === 0 && !searchQuery) {
232
- setShowSuggestions(true);
233
441
  }
234
442
  };
235
- const handleInputAreaMouseLeave = () => {
236
- setIsInputHovered(false);
237
- setShowSuggestions(false);
238
- };
239
443
  const handleInputChange = (e) => {
240
444
  setSearchQuery(e.target.value);
241
445
  if (e.target.value && size === "s") {
242
446
  setSize("m");
243
447
  }
244
- if (e.target.value.length > 0 && messages.length === 0) {
245
- setShowSuggestions(true);
246
- }
247
- else {
248
- setShowSuggestions(false);
249
- }
250
448
  };
251
449
  const handleMouseEnter = () => {
252
- setHasInteracted(true);
253
450
  setIsHovered(true);
254
- if (size === "s") {
255
- setSize("m");
451
+ if (mouseLeaveTimeoutRef.current) {
452
+ clearTimeout(mouseLeaveTimeoutRef.current);
453
+ mouseLeaveTimeoutRef.current = null;
256
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);
257
465
  };
258
466
  const handleMouseLeave = () => {
259
467
  setIsHovered(false);
260
- if (size === "m" && !searchQuery && messages.length === 0) {
261
- setSize("s");
468
+ if (mouseEnterTimeoutRef.current) {
469
+ clearTimeout(mouseEnterTimeoutRef.current);
470
+ mouseEnterTimeoutRef.current = null;
262
471
  }
472
+ mouseLeaveTimeoutRef.current = setTimeout(() => {
473
+ if (size === "m" && !searchQuery && messages.length === 0) {
474
+ setSize("s");
475
+ }
476
+ mouseLeaveTimeoutRef.current = null;
477
+ }, 150);
263
478
  };
264
479
  const handleInputBlur = () => {
265
480
  if (!searchQuery && messages.length === 0) {
@@ -268,7 +483,7 @@ export function GojiSearchComponent() {
268
483
  };
269
484
  const handleCalendarClick = () => {
270
485
  setShowCalendar(true);
271
- setSize("l");
486
+ setSize("xl");
272
487
  };
273
488
  const handleBackToChat = () => {
274
489
  setShowCalendar(false);
@@ -277,8 +492,8 @@ export function GojiSearchComponent() {
277
492
  xs: { maxWidth: "5.5rem", bottom: "0.5rem", padding: "0" },
278
493
  s: { maxWidth: "25.5rem", bottom: "0.5rem", padding: "0" },
279
494
  m: { maxWidth: "30rem", bottom: "0.75rem", padding: "0.25rem" },
280
- l: { maxWidth: "45vw", bottom: "1.25rem", padding: "1rem" },
281
- xl: { maxWidth: "70vw", bottom: "1.5rem", padding: "1.25rem" },
495
+ l: { maxWidth: "45vw", bottom: "1.25rem", padding: "0.5rem" },
496
+ xl: { maxWidth: "70vw", bottom: "1.5rem", padding: "0.75rem" },
282
497
  };
283
498
  const currentConfig = sizeConfig[size];
284
499
  return (_jsx("div", { style: {
@@ -289,15 +504,17 @@ export function GojiSearchComponent() {
289
504
  zIndex: 50,
290
505
  padding: "0.7rem",
291
506
  transition: "bottom 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
292
- }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: _jsx("div", { style: {
507
+ }, children: _jsx("div", { style: {
293
508
  maxWidth: currentConfig.maxWidth,
294
509
  margin: "0 auto",
295
510
  transition: "max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
296
- }, children: _jsxs("div", { style: {
511
+ }, children: _jsxs("div", { ref: componentRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: {
297
512
  borderRadius: size === "xs" ? "0.75rem" : "1rem",
298
513
  border: size === "xs" || size === "s" ? "none" : "1px solid rgba(85, 85, 85, 0.18)",
299
- backgroundColor: size === "xs" || size === "s" ? "transparent" : "rgba(233, 210, 251, 0.15)",
514
+ backgroundColor: size === "xs" || size === "s" ? "transparent" : "rgba(245, 245, 250, 0.12)",
300
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,
301
518
  height: size === "l" ? "55vh" : size === "xl" ? "70vh" : "auto",
302
519
  display: "flex",
303
520
  flexDirection: "column",
@@ -310,77 +527,76 @@ export function GojiSearchComponent() {
310
527
  backdropFilter: size === "xs" || size === "s" ? "none" : "blur(40px) saturate(180%) brightness(110%)",
311
528
  WebkitBackdropFilter: size === "xs" || size === "s" ? "none" : "blur(40px) saturate(180%) brightness(110%)",
312
529
  transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
313
- }, children: [(size === "m" || size === "l" || size === "xl") && (_jsx("div", { style: {
530
+ }, children: [["m", "l", "xl"].includes(size) && (_jsx("div", { style: {
314
531
  position: "absolute",
315
- top: size === "m" ? "0.5rem" : "0.75rem",
316
- right: size === "m" ? "0.5rem" : "0.75rem",
532
+ top: 0,
533
+ right: "0.5rem",
317
534
  zIndex: 50,
535
+ paddingBottom: "0.5rem",
318
536
  }, children: _jsx("div", { style: {
319
537
  position: "relative",
320
- // show the top-right controls when we have no messages OR when in compact (m) mode OR when calendar is open
321
- // but hide them when only suggested questions are shown (messages.length === 0 && showSuggestions)
322
- display: (size === 'm' || messages.length === 0 || showCalendar) && !(messages.length === 0 && showSuggestions) ? 'inline-flex' : 'none',
323
- gap: '0.5rem',
324
- alignItems: 'center',
325
- }, children: _jsxs("div", { style: { display: 'inline-flex', gap: '0.375rem', alignItems: 'center' }, 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: {
326
- borderRadius: '1.3rem',
327
- border: '1px solid rgba(255, 255, 255, 0.3)',
328
- backgroundColor: 'rgba(255, 255, 255, 0.2)',
329
- padding: '0.375rem',
330
- display: 'flex',
331
- alignItems: 'center',
332
- justifyContent: 'center',
333
- color: 'rgba(0, 0, 0, 0.75)',
334
- transition: 'all 0.2s',
335
- cursor: 'pointer',
336
- outline: 'none',
337
- boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.3)',
338
- }, 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: {
339
- position: 'absolute',
340
- top: 'calc(100% + 0.4rem)',
341
- left: '50%',
342
- transform: 'translateX(-50%)',
343
- whiteSpace: 'nowrap',
344
- background: 'rgba(0,0,0,0.9)',
345
- color: '#fff',
346
- padding: '4px 6px',
347
- borderRadius: '1.3rem',
348
- fontSize: '0.55rem',
349
- boxShadow: '0 6px 18px rgba(0,0,0,0.35)',
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)",
350
572
  opacity: hoverTopExpand ? 1 : 0,
351
- pointerEvents: 'none',
352
- transition: 'opacity 120ms ease-in-out',
573
+ pointerEvents: "none",
574
+ transition: "opacity 120ms ease-in-out",
353
575
  zIndex: 30,
354
- }, 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: {
355
- borderRadius: "1.3rem",
356
- border: "1px solid rgba(255, 255, 255, 0.3)",
357
- backgroundColor: "rgba(255, 255, 255, 0.2)",
358
- padding: "0.4rem",
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",
359
578
  display: "flex",
360
579
  alignItems: "center",
361
580
  justifyContent: "center",
362
581
  color: "rgba(0, 0, 0, 0.75)",
363
- backdropFilter: "blur(20px)",
364
- WebkitBackdropFilter: "blur(20px)",
365
- transition: "all 0.2s",
582
+ transition: "all 0.15s",
366
583
  cursor: "pointer",
367
584
  outline: "none",
368
- boxShadow: "inset 0 1px 0 rgba(255, 255, 255, 0.3)",
369
- }, children: _jsx(X, { style: { width: "0.875rem", height: "0.875rem" } }) }), _jsx("span", { id: xTooltipId, role: "tooltip", style: {
370
- position: 'absolute',
371
- top: 'calc(100% + 0.4rem)',
372
- left: '50%',
373
- transform: 'translateX(-50%)',
374
- whiteSpace: 'nowrap',
375
- background: 'rgba(0,0,0,0.9)',
376
- color: '#fff',
377
- padding: '4px 6px',
378
- borderRadius: '1.3rem',
379
- fontSize: '0.55rem',
380
- boxShadow: '0 6px 18px rgba(0,0,0,0.35)',
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)",
381
597
  opacity: hoverTopClose ? 1 : 0,
382
- pointerEvents: 'none',
383
- transition: 'opacity 120ms ease-in-out',
598
+ pointerEvents: "none",
599
+ transition: "opacity 120ms ease-in-out",
384
600
  zIndex: 30,
385
601
  }, children: "Close" })] })] }) }) })), size === "xs" ? (_jsx("button", { type: "button", onClick: handleToggle, style: {
386
602
  background: "transparent",
@@ -396,34 +612,26 @@ export function GojiSearchComponent() {
396
612
  display: "flex",
397
613
  flexDirection: "column",
398
614
  gap: "0.75rem",
399
- height: "100%"
615
+ height: "100%",
400
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: {
401
617
  display: "flex",
402
618
  alignItems: "center",
403
619
  justifyContent: "center",
404
- padding: "0.5rem",
405
- borderRadius: "0.5rem",
406
- border: "1px solid rgba(255, 255, 255, 0.3)",
407
- backgroundColor: "rgba(255, 255, 255, 0.2)",
620
+ padding: "0.3rem 0 0.5rem 0.3rem",
408
621
  color: "rgba(0, 0, 0, 0.75)",
409
622
  cursor: "pointer",
410
623
  transition: "all 0.2s",
411
624
  alignSelf: "flex-start",
412
- width: "2.5rem",
413
- height: "2.5rem",
414
- }, onMouseEnter: (e) => {
415
- e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
416
- }, onMouseLeave: (e) => {
417
- e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
418
- }, children: _jsx(ArrowLeft, { style: { width: "1.125rem", height: "1.125rem" } }) }), _jsx("div", { style: {
625
+ outline: "none",
626
+ }, children: _jsx(ArrowLeft, { style: { width: "0.875rem", height: "0.875rem" } }) }), _jsx("div", { style: {
419
627
  flex: 1,
420
628
  borderRadius: "0.75rem",
421
629
  overflow: "hidden",
422
- backgroundColor: "rgba(255, 255, 255, 0.5)",
630
+ backgroundColor: "transparent",
423
631
  minHeight: 0,
424
632
  display: "flex",
425
633
  flexDirection: "column",
426
- }, children: _jsx(CalendarIntegration, { onBooked: handleBackToChat }) })] })) : (_jsxs(_Fragment, { children: [(size === "l" || size === "xl") && messages.length > 0 && (_jsx(MessageList, { messages: messages, isStreaming: isStreaming, aiAvatarSrc: undefined, onClose: handleClose, onExpand: handleExpandToggle, size: size })), size === "s" ? (_jsxs("div", { style: {
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: {
427
635
  display: "flex",
428
636
  alignItems: "center",
429
637
  gap: "0.35rem",
@@ -438,12 +646,12 @@ export function GojiSearchComponent() {
438
646
  cursor: "pointer",
439
647
  outline: "none",
440
648
  flexShrink: 0,
441
- }, 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", { onMouseEnter: handleInputAreaMouseEnter, onMouseLeave: handleInputAreaMouseLeave, style: {
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: {
442
650
  display: "flex",
443
651
  flexDirection: "column",
444
652
  gap: "0.35rem",
445
653
  flex: 1,
446
- backgroundColor: "rgba(233, 210, 251, 0.15)",
654
+ backgroundColor: "transparent",
447
655
  borderRadius: "1.5rem",
448
656
  border: "1px solid rgba(85, 85, 85, 0.18)",
449
657
  boxShadow: "0 4px 24px 0 rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.3)",
@@ -456,50 +664,16 @@ export function GojiSearchComponent() {
456
664
  gap: "0.375rem",
457
665
  transition: "gap 0.3s ease",
458
666
  flexShrink: 0,
459
- }, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(SearchInput, { value: searchQuery, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: "Search...", size: size, inputRef: inputRef, inspirationQuestions: inspirationQuestions, onInspirationClick: handleSuggestionClick, sparkleRef: sparkleRef }) }), _jsx(ActionButtons, { size: size, isHovered: isHovered, isStreaming: isStreaming, searchQuery: searchQuery, onSubmit: () => { }, onVoiceSearch: handleVoiceSearch, onClose: handleClose, onCalendarClick: handleCalendarClick })] }) })] })) : (_jsxs("div", { onMouseEnter: handleInputAreaMouseEnter, onMouseLeave: handleInputAreaMouseLeave, style: {
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: {
460
668
  display: "flex",
461
669
  flexDirection: "column",
462
670
  gap: "0.5rem",
463
- paddingTop: size === "m" ? "0.5rem" : "0.75rem",
464
- }, children: [_jsx("button", { type: "button", onClick: handleClose, style: {
465
- borderRadius: "1.3rem",
466
- border: "1px solid rgba(255, 255, 255, 0.3)",
467
- backgroundColor: "rgba(255, 255, 255, 0.2)",
468
- padding: "0.4rem",
469
- // rendered only when size !== 'xs' so show the button
470
- display: size === 'm' ? "flex" : "none",
471
- alignItems: "center",
472
- justifyContent: "center",
473
- alignSelf: "flex-end",
474
- width: "fit-content",
475
- color: "rgba(0, 0, 0, 0.75)",
476
- backdropFilter: "blur(20px)",
477
- WebkitBackdropFilter: "blur(20px)",
478
- transition: "all 0.2s",
479
- cursor: "pointer",
480
- outline: "none",
481
- boxShadow: "inset 0 1px 0 rgba(255, 255, 255, 0.3)",
482
- }, children: _jsx(X, { style: { width: "0.875rem", height: "0.875rem" } }) }), _jsx("span", { role: "tooltip", style: {
483
- position: "absolute",
484
- top: "calc(100% + 0.4rem)",
485
- left: "50%",
486
- transform: "translateX(-50%)",
487
- whiteSpace: "nowrap",
488
- background: "rgba(0,0,0,0.9)",
489
- color: "#fff",
490
- padding: "4px 6px",
491
- borderRadius: "1.3rem",
492
- fontSize: "0.55rem",
493
- boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
494
- opacity: 0,
495
- pointerEvents: "none",
496
- transition: "opacity 120ms ease-in-out",
497
- zIndex: 30,
498
- }, children: "Close" }), showSuggestions && messages.length === 0 && !searchQuery && (_jsx(SuggestedQuestions, { questions: suggestedQuestions, onQuestionClick: handleSuggestionClick })), _jsxs("form", { onSubmit: handleSearch, style: {
499
- display: "flex",
500
- alignItems: "center",
501
- gap: "0.375rem",
502
- transition: "gap 0.3s ease",
503
- flexShrink: 0,
504
- }, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(SearchInput, { value: searchQuery, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: "Ask me anything...", size: size, inputRef: inputRef, inspirationQuestions: inspirationQuestions, onInspirationClick: handleSuggestionClick, sparkleRef: sparkleRef }) }), _jsx(ActionButtons, { size: size, isHovered: isHovered, isStreaming: isStreaming, searchQuery: searchQuery, onSubmit: () => { }, onVoiceSearch: handleVoiceSearch, onClose: handleClose, onCalendarClick: handleCalendarClick })] })] }))] })) }))] }) }) }));
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 })] }) }))] })) }))] }) }) }));
505
679
  }