goji-search 2.0.8 → 3.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.
Files changed (30) hide show
  1. package/README.md +81 -5
  2. package/dist/goji-search/assets/chat_logo.jpeg +0 -0
  3. package/dist/goji-search/components/elements/action-buttons.d.ts +2 -1
  4. package/dist/goji-search/components/elements/calendar-integration.d.ts +1 -0
  5. package/dist/goji-search/components/elements/choice-bubbles.d.ts +15 -0
  6. package/dist/goji-search/components/elements/deep-analysis-card.d.ts +16 -0
  7. package/dist/goji-search/components/elements/email-input-box.d.ts +23 -0
  8. package/dist/goji-search/components/elements/expandable-section.d.ts +7 -0
  9. package/dist/goji-search/components/elements/inspiration-menu.d.ts +6 -3
  10. package/dist/goji-search/components/elements/message-list.d.ts +9 -2
  11. package/dist/goji-search/components/elements/search-input.d.ts +9 -5
  12. package/dist/goji-search/components/elements/slide-panel.d.ts +15 -0
  13. package/dist/goji-search/components/error-boundary.d.ts +16 -0
  14. package/dist/goji-search/components/goji-search-component.d.ts +7 -5
  15. package/dist/goji-search/hooks/useChatStream.d.ts +48 -0
  16. package/dist/goji-search/hooks/useCompanyTheme.d.ts +27 -0
  17. package/dist/goji-search/hooks/useIdleReengagement.d.ts +90 -0
  18. package/dist/goji-search/hooks/useReturningVisitor.d.ts +61 -0
  19. package/dist/goji-search/lib/debug.d.ts +9 -0
  20. package/dist/goji-search/lib/goji-client.d.ts +204 -5
  21. package/dist/goji-search/lib/utm-capture.d.ts +40 -0
  22. package/dist/goji-search/lib/visitor-id.d.ts +62 -0
  23. package/dist/index.d.ts +7 -1
  24. package/dist/index.js +8991 -6117
  25. package/dist/setupTests.d.ts +27 -0
  26. package/dist/web-component.d.ts +59 -0
  27. package/dist/widget.js +572 -0
  28. package/package.json +20 -5
  29. package/dist/goji-search/components/elements/language-selector.d.ts +0 -10
  30. package/dist/goji-search/components/elements/suggested-questions.d.ts +0 -6
package/README.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Goji Search
2
2
 
3
+ AI-powered chat widget for websites. Embeddable as a React component, Web Component, or vanilla JS widget.
4
+
5
+ ## Features
6
+
7
+ - Real-time streaming chat via WebSocket
8
+ - Multi-tenant support with company-specific branding
9
+ - Returning visitor detection and personalized welcome messages
10
+ - Idle re-engagement prompts
11
+ - Deep analysis cards (ROI, comparisons, cost breakdowns)
12
+ - CTA cards with calendar booking and email capture
13
+ - Multi-language support (EN, FR, IT, ES, DE)
14
+ - Voice transcription
15
+ - Shadow DOM style isolation for embedding
16
+ - Gated debug logging
17
+
3
18
  ## Installation
4
19
 
5
20
  ```bash
@@ -8,16 +23,77 @@ npm install goji-search
8
23
  yarn add goji-search
9
24
  ```
10
25
 
11
- ## Usage
26
+ ## Quick Start
27
+
28
+ ### React
12
29
 
13
30
  ```tsx
14
31
  import { GojiSearchComponent } from 'goji-search';
15
32
 
16
33
  function App() {
17
34
  return (
18
- <div>
19
- <GojiSearchComponent /* props here */ />
20
- </div>
35
+ <GojiSearchComponent
36
+ companyId="your-company-id"
37
+ apiKey="goji_yourcompany_prod_xxx"
38
+ apiUrl="https://api.getgoji.ai"
39
+ />
21
40
  );
22
41
  }
23
- ```
42
+ ```
43
+
44
+ ### HTML / Vanilla JS
45
+
46
+ ```html
47
+ <script src="https://cdn.getgoji.ai/widget.js"></script>
48
+ <script>
49
+ GojiWidget.init({
50
+ companyId: 'your-company-id',
51
+ apiKey: 'goji_yourcompany_prod_xxx',
52
+ apiUrl: 'https://api.getgoji.ai'
53
+ });
54
+ </script>
55
+ ```
56
+
57
+ ## Props
58
+
59
+ | Prop | Type | Required | Description |
60
+ |------|------|----------|-------------|
61
+ | `companyId` | `string` | Yes | Your company identifier |
62
+ | `apiKey` | `string` | Yes | Your Goji API key |
63
+ | `apiUrl` | `string` | No | Backend API URL (default: `http://localhost:8000`) |
64
+ | `aiColor` | `string` | No | AI avatar color in HEX (falls back to brand color from company theme) |
65
+ | `userMessageBackgroundColor` | `string` | No | User message bubble background color |
66
+
67
+ ## Exports
68
+
69
+ ```tsx
70
+ // React component and error boundary
71
+ import { GojiSearchComponent, GojiSearchErrorBoundary } from 'goji-search';
72
+
73
+ // API client
74
+ import { GojiSearchClient, createGojiClient } from 'goji-search';
75
+
76
+ // Hooks
77
+ import { useReturningVisitor, useVisitorId } from 'goji-search';
78
+
79
+ // UTM and visitor utilities
80
+ import { captureVisitorContext, getVisitorContext, getOrCreateVisitorId } from 'goji-search';
81
+ ```
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ npm install # Install dependencies
87
+ npm run dev # Development server
88
+ npm run build # Build React library + widget bundle
89
+ npm run lint # Lint
90
+ npm run test # Run tests (watch mode)
91
+ npm run test:run # Run tests once
92
+ npm run test:coverage # Run tests with coverage
93
+ ```
94
+
95
+ ## Documentation
96
+
97
+ See [USAGE.md](./USAGE.md) for detailed documentation, advanced usage, and API reference.
98
+
99
+ See [CHANGELOG.md](./CHANGELOG.md) for version history.
@@ -4,10 +4,11 @@ interface ActionButtonsProps {
4
4
  isStreaming: boolean;
5
5
  searchQuery: string;
6
6
  isRecording?: boolean;
7
+ hideCalendar?: boolean;
7
8
  onSubmit: () => void;
8
9
  onVoiceSearch: () => void;
9
10
  onClose: () => void;
10
11
  onCalendarClick: () => void;
11
12
  }
12
- export declare function ActionButtons({ size, isHovered, isStreaming, searchQuery, isRecording, onSubmit, onVoiceSearch, onClose, onCalendarClick, }: ActionButtonsProps): import("react/jsx-runtime").JSX.Element | null;
13
+ export declare function ActionButtons({ size, isHovered, isStreaming, searchQuery, isRecording, hideCalendar, onSubmit, onVoiceSearch, onClose, onCalendarClick, }: ActionButtonsProps): import("react/jsx-runtime").JSX.Element | null;
13
14
  export {};
@@ -1,4 +1,5 @@
1
1
  interface CalendarIntegrationProps {
2
+ calLink?: string;
2
3
  onBooked?: () => void;
3
4
  }
4
5
  export default function CalendarIntegration(props?: CalendarIntegrationProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * ChoiceBubbles Component
3
+ *
4
+ * Renders interactive choice buttons for guided conversations.
5
+ * When clicked, the selected choice is sent as the user's next message.
6
+ */
7
+ interface ChoiceBubblesProps {
8
+ choices: string[];
9
+ onChoiceClick: (choice: string) => void;
10
+ disabled?: boolean;
11
+ variant?: "default" | "faded" | "dark";
12
+ aiColor?: string;
13
+ }
14
+ export declare function ChoiceBubbles({ choices, onChoiceClick, disabled, variant, aiColor, }: ChoiceBubblesProps): import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
@@ -0,0 +1,16 @@
1
+ export type AnalysisType = "roi" | "comparison" | "cost_breakdown" | "key_metrics";
2
+ export type ConfidenceLevel = "high" | "medium" | "low";
3
+ export interface DeepAnalysisData {
4
+ analysis_type: AnalysisType;
5
+ data: Record<string, any>;
6
+ confidence: ConfidenceLevel;
7
+ data_source_note: string;
8
+ generated_at?: number;
9
+ }
10
+ export interface DeepAnalysisCardProps {
11
+ analysisType: AnalysisType;
12
+ data?: DeepAnalysisData;
13
+ isLoading?: boolean;
14
+ onCollapse?: () => void;
15
+ }
16
+ export declare function DeepAnalysisCard({ analysisType, data, isLoading, onCollapse, }: DeepAnalysisCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Email Input Box Component
3
+ *
4
+ * Displays a dedicated email input box when the backend requests email collection.
5
+ * - Appears below follow-up questions (after ClosingCard if present)
6
+ * - Includes email validation
7
+ * - Animated entrance/exit
8
+ * - Respects localized prompt text from backend
9
+ */
10
+ interface EmailInputBoxProps {
11
+ /** Localized prompt text from backend */
12
+ promptText: string;
13
+ /** Session ID for submission */
14
+ sessionId?: string;
15
+ /** Callback when email is submitted */
16
+ onSubmit: (email: string) => Promise<void>;
17
+ /** Callback when user dismisses the box */
18
+ onDismiss?: () => void;
19
+ /** Additional CSS class */
20
+ className?: string;
21
+ }
22
+ export declare function EmailInputBox({ promptText, sessionId, onSubmit, onDismiss, className, }: EmailInputBoxProps): import("react/jsx-runtime").JSX.Element | null;
23
+ export {};
@@ -0,0 +1,7 @@
1
+ interface ExpandableSectionProps {
2
+ title: string;
3
+ children: React.ReactNode;
4
+ defaultExpanded?: boolean;
5
+ }
6
+ export declare function ExpandableSection({ title, children, defaultExpanded }: ExpandableSectionProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -1,11 +1,14 @@
1
- import type { ReactNode } from "react";
2
1
  interface InspirationMenuProps {
3
2
  questions: string[];
4
3
  onQuestionClick: (question: string) => void;
5
4
  onClose: () => void;
6
5
  suggestedLabel?: string;
7
- languageSelector?: ReactNode;
6
+ showLanguageSelector?: boolean;
7
+ supportedLanguages?: string[];
8
+ currentLanguage?: string;
9
+ onLanguageChange?: (lang: string) => void;
10
+ languageLabels?: Record<string, string>;
8
11
  color?: string;
9
12
  }
10
- export declare function InspirationMenu({ questions, onQuestionClick, onClose, suggestedLabel, languageSelector, color }: InspirationMenuProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function InspirationMenu({ questions, onQuestionClick, onClose, suggestedLabel, showLanguageSelector, supportedLanguages, currentLanguage, onLanguageChange, languageLabels, color }: InspirationMenuProps): import("react/jsx-runtime").JSX.Element;
11
14
  export {};
@@ -11,6 +11,11 @@ interface Message {
11
11
  content: string;
12
12
  timestamp: number;
13
13
  sources?: ChatSource[];
14
+ isReengagement?: boolean;
15
+ choices?: string[];
16
+ choicesSelected?: boolean;
17
+ choicesVariant?: "default" | "faded" | "dark";
18
+ choiceMode?: "send" | "action";
14
19
  }
15
20
  interface MessageListProps {
16
21
  messages: Message[];
@@ -18,7 +23,9 @@ interface MessageListProps {
18
23
  aiAvatarSrc?: string;
19
24
  size?: "m" | "l" | "xl" | "s" | "xs";
20
25
  brandName?: string;
21
- userBackgroundColor?: string;
26
+ onChoiceClick?: (messageIndex: number, choice: string) => void;
27
+ userMessageBackgroundColor?: string;
28
+ aiColor?: string;
22
29
  }
23
- export declare function MessageList({ messages, isStreaming, aiAvatarSrc, size, brandName, userBackgroundColor, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
30
+ export declare function MessageList({ messages, isStreaming, aiAvatarSrc, size, brandName, onChoiceClick, userMessageBackgroundColor, aiColor }: MessageListProps): import("react/jsx-runtime").JSX.Element;
24
31
  export {};
@@ -1,5 +1,4 @@
1
1
  import type React from "react";
2
- import type { ReactNode } from "react";
3
2
  interface SearchInputProps {
4
3
  value: string;
5
4
  onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
@@ -8,13 +7,18 @@ interface SearchInputProps {
8
7
  placeholder: string;
9
8
  size: "xs" | "s" | "m" | "l" | "xl";
10
9
  setSize: (size: "xs" | "s" | "m" | "l" | "xl") => void;
11
- inputRef: React.RefObject<HTMLTextAreaElement>;
10
+ inputRef: React.RefObject<HTMLTextAreaElement | null>;
12
11
  inspirationQuestions: string[];
13
12
  onInspirationClick: (question: string) => void;
14
- sparkleRef: React.RefObject<HTMLDivElement>;
13
+ sparkleRef: React.RefObject<HTMLDivElement | null>;
15
14
  suggestedLabel?: string;
16
- languageSelector?: ReactNode;
15
+ showLanguageSelector?: boolean;
16
+ supportedLanguages?: string[];
17
+ currentLanguage?: string;
18
+ onLanguageChange?: (lang: string) => void;
19
+ languageLabels?: Record<string, string>;
20
+ forceMenuOpenToken?: number;
17
21
  color?: string;
18
22
  }
19
- export declare function SearchInput({ value, onChange, onFocus, onBlur, placeholder, size, setSize, inputRef, inspirationQuestions, onInspirationClick, sparkleRef, suggestedLabel, languageSelector, color }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function SearchInput({ value, onChange, onFocus, onBlur, placeholder, size, setSize, inputRef, inspirationQuestions, onInspirationClick, sparkleRef, suggestedLabel, showLanguageSelector, supportedLanguages, currentLanguage, onLanguageChange, languageLabels, forceMenuOpenToken, color }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
20
24
  export {};
@@ -0,0 +1,15 @@
1
+ import type { VisualSlide } from "../../lib/goji-client";
2
+ interface SlidePanelProps {
3
+ /** Visual slides with id and description */
4
+ visualSlides: VisualSlide[];
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ /** Base URL for slide images (defaults to /slides/) */
8
+ slidesBasePath?: string;
9
+ /** Total number of slides (for display like "3/9") - optional */
10
+ totalSlides?: number;
11
+ /** Show description below media (default: false). For videos, this could be used for subtitles. */
12
+ showDescription?: boolean;
13
+ }
14
+ export declare function SlidePanel({ visualSlides, isOpen, onClose, slidesBasePath, totalSlides, showDescription, }: SlidePanelProps): import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ interface ErrorBoundaryProps {
3
+ children: React.ReactNode;
4
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
5
+ }
6
+ interface ErrorBoundaryState {
7
+ hasError: boolean;
8
+ }
9
+ export declare class GojiSearchErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
10
+ constructor(props: ErrorBoundaryProps);
11
+ static getDerivedStateFromError(_error: Error): ErrorBoundaryState;
12
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
13
+ private handleRetry;
14
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
15
+ }
16
+ export {};
@@ -16,14 +16,16 @@ export interface GojiSearchComponentProps {
16
16
  */
17
17
  apiKey: string;
18
18
  /**
19
- * Initial AI color theme in HEX format
20
- * @default "#4654F7"
19
+ * Custom color for AI avatar animation (CSS color value)
20
+ * Used by web-component for theming
21
+ * @example "#4654F7"
21
22
  */
22
23
  aiColor?: string;
23
24
  /**
24
- * Background color for user messages in HEX format
25
- * @default "#4654F7"
26
- */
25
+ * Custom background color for user message bubbles (CSS color value)
26
+ * Used by web-component for theming
27
+ * @example "#E8E8FF"
28
+ */
27
29
  userMessageBackgroundColor?: string;
28
30
  /**
29
31
  * Widget position on the horizontal axis.
@@ -0,0 +1,48 @@
1
+ import type { GojiSearchClient, ChatSource, CTACard, EmailRequest, DeepAnalysis, PendingAnalysis, VisualSlide } from "../lib/goji-client";
2
+ import type { VisitorContext } from "../lib/utm-capture";
3
+ export interface Message {
4
+ role: "user" | "assistant";
5
+ content: string;
6
+ timestamp: number;
7
+ sources?: ChatSource[];
8
+ slideIds?: number[];
9
+ isReengagement?: boolean;
10
+ choices?: string[];
11
+ choicesSelected?: boolean;
12
+ choicesVariant?: "default" | "faded" | "dark";
13
+ choiceMode?: "send" | "action";
14
+ emailToken?: string;
15
+ }
16
+ export interface UseChatStreamOptions {
17
+ client: GojiSearchClient;
18
+ visitorId: string | undefined;
19
+ companyId: string;
20
+ supportedLanguages: string[];
21
+ onLanguageDetected?: (lang: string) => void;
22
+ onResponseComplete?: () => void;
23
+ resetIdleTimerRef: React.MutableRefObject<() => void>;
24
+ }
25
+ export interface UseChatStreamReturn {
26
+ messages: Message[];
27
+ setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
28
+ isStreaming: boolean;
29
+ sessionId: string | undefined;
30
+ setSessionId: React.Dispatch<React.SetStateAction<string | undefined>>;
31
+ currentCTACard: CTACard | null;
32
+ setCurrentCTACard: React.Dispatch<React.SetStateAction<CTACard | null>>;
33
+ currentEmailRequest: EmailRequest | null;
34
+ setCurrentEmailRequest: React.Dispatch<React.SetStateAction<EmailRequest | null>>;
35
+ pendingAnalysis: PendingAnalysis | null;
36
+ currentAnalysis: DeepAnalysis | null;
37
+ setCurrentAnalysis: React.Dispatch<React.SetStateAction<DeepAnalysis | null>>;
38
+ currentVisualSlides: VisualSlide[];
39
+ isSlidePanelOpen: boolean;
40
+ setIsSlidePanelOpen: React.Dispatch<React.SetStateAction<boolean>>;
41
+ backendSuggestedQuestions: string[] | null;
42
+ setBackendSuggestedQuestions: React.Dispatch<React.SetStateAction<string[] | null>>;
43
+ streamingCancelRef: React.MutableRefObject<(() => void) | null>;
44
+ sendMessage: (message: string, language: string, visitorContext: VisitorContext | undefined, options?: {
45
+ preAction?: () => void;
46
+ }) => Promise<void>;
47
+ }
48
+ export declare function useChatStream({ client, visitorId, companyId, supportedLanguages, onLanguageDetected, onResponseComplete, resetIdleTimerRef, }: UseChatStreamOptions): UseChatStreamReturn;
@@ -12,10 +12,37 @@ interface CompanyTheme {
12
12
  welcome_message: string;
13
13
  placeholder_text: string;
14
14
  initial_suggestions: string[];
15
+ languages?: {
16
+ supported: string[];
17
+ default: string;
18
+ };
19
+ calendar_link?: string;
20
+ }
21
+ export interface ThemeCSSVars {
22
+ '--brand-color': string;
23
+ '--brand-color-dark': string;
24
+ '--brand-color-light': string;
25
+ '--brand-color-lighter': string;
26
+ '--brand-color-rgb': string;
27
+ '--brand-color-dark-rgb': string;
15
28
  }
16
29
  export declare function useCompanyTheme(apiUrl: string, companyId: string, apiKey: string): {
17
30
  theme: CompanyTheme | null;
31
+ cssVars: ThemeCSSVars | null;
18
32
  loading: boolean;
19
33
  error: string | null;
34
+ applyToElement: (element: HTMLElement | null) => void;
35
+ };
36
+ export declare function parseColor(color: string): {
37
+ r: number;
38
+ g: number;
39
+ b: number;
40
+ } | null;
41
+ export declare function hexToRgb(hex: string): {
42
+ r: number;
43
+ g: number;
44
+ b: number;
20
45
  };
46
+ export declare function rgbToHex(r: number, g: number, b: number): string;
47
+ export declare function darken(color: string, amount: number): string;
21
48
  export {};
@@ -0,0 +1,90 @@
1
+ /**
2
+ * useIdleReengagement Hook
3
+ *
4
+ * Manages idle detection and re-engagement prompts for chat visitors.
5
+ * Detects when a visitor has been idle for a specified time and triggers
6
+ * a re-engagement request to the backend.
7
+ */
8
+ export interface IdleReengagementConfig {
9
+ /** Time in ms before first re-engagement (default: 60000 = 60s) */
10
+ softTimeoutMs?: number;
11
+ /** Time in ms before second re-engagement (default: 120000 = 120s) */
12
+ hardTimeoutMs?: number;
13
+ /** Minimum time in ms between re-engagement attempts (default: 60000 = 60s) */
14
+ minBetweenMs?: number;
15
+ /** Maximum number of re-engagement attempts (default: 2) */
16
+ maxAttempts?: number;
17
+ }
18
+ export interface IdleReengagementState {
19
+ /** Number of re-engagement attempts made this session */
20
+ attemptCount: number;
21
+ /** Whether re-engagement is currently blocked */
22
+ isBlocked: boolean;
23
+ /** Reason for blocking (if any) */
24
+ blockedReason: string | null;
25
+ /** Time until next re-engagement check (ms) */
26
+ timeUntilNextCheck: number | null;
27
+ /** Whether the idle timer is active */
28
+ isTimerActive: boolean;
29
+ }
30
+ export interface UseIdleReengagementOptions {
31
+ /** Configuration for timeouts and limits */
32
+ config?: IdleReengagementConfig;
33
+ /** Whether the widget is minimized (blocks re-engagement) */
34
+ isMinimized?: boolean;
35
+ /** Whether the assistant is currently streaming a response */
36
+ isStreaming?: boolean;
37
+ /** Whether a CTA card is currently displayed */
38
+ hasCTACard?: boolean;
39
+ /** Whether an email request is currently displayed */
40
+ hasEmailRequest?: boolean;
41
+ /** Whether the calendar is open */
42
+ isCalendarOpen?: boolean;
43
+ /** Whether there are any messages in the conversation */
44
+ hasMessages?: boolean;
45
+ /** Current session ID (required for re-engagement) */
46
+ sessionId?: string;
47
+ /** Callback when re-engagement should be triggered */
48
+ onReengagementTrigger?: (attempt: number) => void;
49
+ /** Whether idle detection should be enabled */
50
+ enabled?: boolean;
51
+ }
52
+ export interface UseIdleReengagementResult {
53
+ /** Current state of the re-engagement system */
54
+ state: IdleReengagementState;
55
+ /** Reset the idle timer (call on user activity) */
56
+ resetTimer: () => void;
57
+ /** Stop the idle timer completely */
58
+ stopTimer: () => void;
59
+ /** Start or restart the idle timer */
60
+ startTimer: () => void;
61
+ /** Increment attempt count (call after re-engagement sent) */
62
+ incrementAttempt: () => void;
63
+ /** Reset the entire re-engagement state for a new session */
64
+ resetState: () => void;
65
+ }
66
+ /**
67
+ * Hook for managing idle detection and re-engagement prompts.
68
+ *
69
+ * Tracks visitor idle time and triggers re-engagement when thresholds are met.
70
+ * Respects blocking conditions (CTA displayed, streaming, minimized, etc.)
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * const { state, resetTimer, incrementAttempt } = useIdleReengagement({
75
+ * isMinimized: size === "xs",
76
+ * isStreaming,
77
+ * hasCTACard: !!currentCTACard,
78
+ * hasMessages: messages.length > 0,
79
+ * sessionId,
80
+ * onReengagementTrigger: (attempt) => {
81
+ * // Request re-engagement from backend
82
+ * client.requestReengagement(sessionId, attempt)
83
+ * },
84
+ * })
85
+ *
86
+ * // Reset timer on user activity
87
+ * const handleUserActivity = () => resetTimer()
88
+ * ```
89
+ */
90
+ export declare function useIdleReengagement(options: UseIdleReengagementOptions): UseIdleReengagementResult;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * useReturningVisitor Hook
3
+ *
4
+ * Manages returning visitor detection and personalized welcome messages.
5
+ * Checks if the visitor has been here before and loads their context.
6
+ */
7
+ import type { GojiSearchClient, ReturningVisitorContext } from "../lib/goji-client";
8
+ export interface UseReturningVisitorResult {
9
+ /** Whether the visitor has been here before */
10
+ isReturning: boolean;
11
+ /** Whether we're still loading the visitor context */
12
+ isLoading: boolean;
13
+ /** The visitor's unique ID */
14
+ visitorId: string;
15
+ /** Full visitor context (if returning) */
16
+ visitorContext: ReturningVisitorContext | null;
17
+ /** Personalized or default welcome message */
18
+ welcomeMessage: string | null;
19
+ /** Visitor's preferred language (if returning) */
20
+ preferredLanguage: string | null;
21
+ /** Error if context loading failed */
22
+ error: Error | null;
23
+ /** Manually refresh the visitor context */
24
+ refresh: () => Promise<void>;
25
+ }
26
+ export interface UseReturningVisitorOptions {
27
+ /** The GojiSearch client instance */
28
+ client: GojiSearchClient;
29
+ /** Default language for welcome message */
30
+ language?: string;
31
+ /** Whether to automatically fetch context on mount */
32
+ autoFetch?: boolean;
33
+ /** Callback when returning visitor is detected */
34
+ onReturningVisitor?: (context: ReturningVisitorContext) => void;
35
+ }
36
+ /**
37
+ * Hook for managing returning visitor detection and personalization.
38
+ *
39
+ * On mount:
40
+ * 1. Gets or creates a visitor ID (stored in localStorage)
41
+ * 2. Checks if visitor is returning via backend API
42
+ * 3. Returns their context and personalized welcome message
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * const { isReturning, welcomeMessage, visitorId } = useReturningVisitor({
47
+ * client,
48
+ * language: "en",
49
+ * })
50
+ *
51
+ * // Use welcomeMessage instead of default welcome for returning visitors
52
+ * const displayMessage = isReturning ? welcomeMessage : defaultWelcome
53
+ * ```
54
+ */
55
+ export declare function useReturningVisitor(options: UseReturningVisitorOptions): UseReturningVisitorResult;
56
+ /**
57
+ * Lightweight hook that just provides the visitor ID without fetching context.
58
+ *
59
+ * Use this when you only need the visitor ID for tracking purposes.
60
+ */
61
+ export declare function useVisitorId(): string;
@@ -0,0 +1,9 @@
1
+ declare global {
2
+ interface Window {
3
+ __GOJI_DEBUG__?: boolean;
4
+ }
5
+ }
6
+ export declare function isGojiDebugEnabled(): boolean;
7
+ export declare function gojiDebugLog(...args: unknown[]): void;
8
+ export declare function gojiDebugWarn(...args: unknown[]): void;
9
+ export declare function gojiDebugError(...args: unknown[]): void;