@vanira/sdk 0.0.33 → 0.0.35

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.
@@ -11,8 +11,9 @@ export declare class ChatService {
11
11
  static createChatProspect(prospectGroupId: string): Promise<string>;
12
12
  static fetchWelcomeMessage(agentId: string, prospectId: string, widgetId?: string, _pkKey?: string): Promise<ChatMessage & {
13
13
  chatId?: string;
14
+ conversationId?: string;
14
15
  }>;
15
- static sendChatMessage(agentId: string, prospectId: string, message: string, chatId: string | null, onChunk: (text: string) => void, onWidget: (widget: any) => void, onDone: (newChatId: string | null) => void, widgetId?: string, _pkKey?: string): Promise<void>;
16
+ static sendChatMessage(agentId: string, prospectId: string, message: string, chatId: string | null, onChunk: (text: string) => void, onWidget: (widget: any) => void, onDone: (newChatId: string | null, newConversationId: string | null) => void, widgetId?: string, _pkKey?: string): Promise<void>;
16
17
  static listenForAdminReplies(inboxId: string, sender: string, onMessage: (content: string) => void): {
17
18
  close: () => void;
18
19
  };
@@ -20,4 +21,5 @@ export declare class ChatService {
20
21
  callId: string;
21
22
  workerUrl: string;
22
23
  }>;
24
+ static resolveConversation(conversationId: string): Promise<void>;
23
25
  }
@@ -23,6 +23,7 @@ export interface SessionData {
23
23
  tabId: string;
24
24
  prospectId: string;
25
25
  chatId: string | null;
26
+ conversationId: string | null;
26
27
  messages: StoredMessage[];
27
28
  lastActive: number;
28
29
  }
@@ -53,7 +54,7 @@ export declare class SessionManager {
53
54
  */
54
55
  forceClaimSession(): void;
55
56
  /** Save / update the prospect and chat IDs for this session. */
56
- saveIds(prospectId: string, chatId: string | null): void;
57
+ saveIds(prospectId: string, chatId: string | null, conversationId?: string | null): void;
57
58
  /** Append a message to the persisted history. */
58
59
  pushMessage(role: 'user' | 'assistant', content: string): void;
59
60
  /** Update the content of the last assistant message (streaming). */
@@ -62,6 +63,8 @@ export declare class SessionManager {
62
63
  getSession(): SessionData | null;
63
64
  /** Clears ALL session data (prospect_id, chat_id, messages). */
64
65
  clearSession(): void;
66
+ /** Clears chat-specific session data (chatId, conversationId, messages) but keeps the prospectId. */
67
+ clearChatKeepProspect(): void;
65
68
  /** Stop the heartbeat and release resources. Call on widget destroy. */
66
69
  destroy(): void;
67
70
  on(event: SessionEvent, cb: Function): this;
@@ -20,6 +20,14 @@ export type VaniraAIStatus = 'idle' | 'connecting' | 'connected' | 'disconnected
20
20
  export interface VaniraAIConfig {
21
21
  /** Your Vanira agent ID from the dashboard */
22
22
  agentId: string;
23
+ /** Optional prospect ID. If supplied, the SDK resumes the prospect context. */
24
+ prospectId?: string;
25
+ /**
26
+ * Determines the lifecycle behavior when starting a connection:
27
+ * - 'continue': Reconnects/resumes the call using the provided `callId` or the stored sessionStorage call session.
28
+ * - 'new' (default): Ignores any previous call state and provisions a brand-new call.
29
+ */
30
+ sessionBehavior?: 'continue' | 'new';
23
31
  /** Bearer token for authentication. Required for ICE server fetching. */
24
32
  token?: string;
25
33
  /** Override the WebRTC server URL. Auto-detected if omitted. */
@@ -30,6 +38,10 @@ export interface VaniraAIConfig {
30
38
  iceServers?: RTCIceServer[];
31
39
  /** Optional call ID override. Auto-generated if omitted. */
32
40
  callId?: string;
41
+ /** Your sk_live_* or pk_live_* API key. Required for client-side call creation. */
42
+ apiKey?: string;
43
+ /** Override the default backend URL (e.g. http://localhost:8000). */
44
+ backendUrl?: string;
33
45
  }
34
46
  export interface ClientToolCall {
35
47
  /** The ref_code of the triggered action */
@@ -40,6 +52,8 @@ export interface ClientToolCall {
40
52
  tool_call_id: string;
41
53
  /** Whether the AI is paused waiting for result ('blocking') or keeps talking ('fire_and_forget') */
42
54
  execution_mode: 'blocking' | 'fire_and_forget';
55
+ /** Dynamic UI configuration parameters for zero-code presets */
56
+ client_fields?: Record<string, any>;
43
57
  }
44
58
  export interface TranscriptionEvent {
45
59
  text: string;
@@ -61,6 +75,12 @@ type EventMap = {
61
75
  track: MediaStreamTrack;
62
76
  stream: MediaStream;
63
77
  };
78
+ /** Fired when the prospect and call session are successfully generated/retrieved */
79
+ session_started: {
80
+ prospectId: string;
81
+ callId: string;
82
+ serverUrl: string;
83
+ };
64
84
  };
65
85
  type EventCallback<T> = T extends void ? () => void : (payload: T) => void;
66
86
  export declare class VaniraAI {
@@ -69,6 +89,12 @@ export declare class VaniraAI {
69
89
  private client;
70
90
  private listeners;
71
91
  constructor(config: VaniraAIConfig);
92
+ /** Returns the active WebRTC worker/server URL */
93
+ get serverUrl(): string | undefined;
94
+ /** Returns the active call ID */
95
+ get callId(): string | undefined;
96
+ /** Returns the active prospect ID associated with this session */
97
+ get prospectId(): string | undefined;
72
98
  /** Current connection status */
73
99
  get status(): VaniraAIStatus;
74
100
  /** Shorthand: true when status is 'connected' */
@@ -91,7 +117,11 @@ export declare class VaniraAI {
91
117
  * Connect to the Voice AI agent and start the call.
92
118
  * Requests microphone permission, establishes WebRTC, and opens the data channel.
93
119
  */
94
- start(): Promise<void>;
120
+ start(): Promise<{
121
+ callId: string;
122
+ prospectId: string;
123
+ serverUrl: string;
124
+ }>;
95
125
  /**
96
126
  * Disconnect the call and release all resources (microphone, WebRTC connection).
97
127
  */
@@ -127,6 +157,10 @@ export declare class VaniraAI {
127
157
  * Force the AI to stop speaking and immediately react to a UI event.
128
158
  * Use when the user performs a significant action (e.g., clicks a button, opens a page).
129
159
  *
160
+ * Do NOT call this after `uploadMedia()` — `uploadMedia()` already sends
161
+ * `client_media_update` which is the only notification the backend needs.
162
+ * Calling `triggerInterrupt` after an upload causes double-TTS.
163
+ *
130
164
  * @param actionName - A descriptive name for the action (e.g., 'user_clicked_cart')
131
165
  * @param data - Optional context about the action
132
166
  *
@@ -134,19 +168,40 @@ export declare class VaniraAI {
134
168
  * client.triggerInterrupt('user_opened_checkout', { cart_value: 299 });
135
169
  */
136
170
  triggerInterrupt(actionName: string, data?: Record<string, any>): void;
171
+ /**
172
+ * Cut the AI's current audio mid-sentence WITHOUT sending a client_action_trigger.
173
+ * Use this when you want to interrupt the agent but the next notification will be
174
+ * sent by the SDK itself (e.g. right before `uploadMedia()`).
175
+ */
176
+ interruptAudioOnly(): void;
137
177
  /**
138
178
  * Upload a file (photo, document, screenshot) to the AI agent during a live call.
139
179
  * Handles the HTTP upload and DataChannel notification automatically.
140
180
  *
141
- * @param file - File or Blob to upload (JPEG, PNG, GIF, WebP, PDF — max 50 MB)
142
- * @param reason - Routing key: 'general' | 'kyc_photo' | 'damage_photo' | 'selfie' | 'document' | 'screenshot'
143
- * @param message - Optional text the user wants to say about the file
144
- * @returns `{ media_id, url }` on success
181
+ * ⚠️ This method is SELF-CONTAINED:
182
+ * 1. POSTs the file to /media/upload
183
+ * 2. Sends `client_media_update` via the DataChannel
184
+ *
185
+ * The backend LLM receives `client_media_update` and calls `process_media` — NO
186
+ * additional `triggerInterrupt()` or `sendActionTrigger()` is needed.
187
+ * Calling those after `uploadMedia()` will cause a SECOND LLM response (double TTS).
188
+ *
189
+ * @param file - File or Blob to upload (JPEG, PNG, GIF, WebP, PDF — max 50 MB)
190
+ * @param reason - Routing key: 'general' | 'kyc_photo' | 'damage_photo' | 'selfie' | 'document' | 'screenshot'
191
+ * @param message - Optional text the user wants to say about the file
192
+ * @param interruptFirst - Pass `true` to cut the AI's current audio before uploading
193
+ * (sends `action_interrupt` only — NOT `client_action_trigger`)
194
+ * @returns `{ media_id, url }` on success
145
195
  *
146
196
  * @example
147
- * const { media_id, url } = await client.uploadMedia(file, 'damage_photo', 'Photo of the damage');
197
+ * // Correct one event, full context, LLM gets everything:
198
+ * const result = await client.uploadMedia(file, 'person_verification', 'Here is my ID', true);
199
+ *
200
+ * // ❌ Wrong — causes double TTS:
201
+ * const result = await client.uploadMedia(file, 'person_verification');
202
+ * client.triggerInterrupt('media_uploaded', { url: result.url }); // don't do this
148
203
  */
149
- uploadMedia(file: File | Blob, reason?: string, message?: string): Promise<{
204
+ uploadMedia(file: File | Blob, reason?: string, message?: string, interruptFirst?: boolean): Promise<{
150
205
  media_id: string;
151
206
  url: string;
152
207
  }>;
@@ -1,20 +1,26 @@
1
1
  import { WebRTCClientConfig } from '../types';
2
2
 
3
3
  export declare class WebRTCClient {
4
- private serverUrl;
5
- private agentId;
6
- private callId;
7
- private apiKey;
4
+ serverUrl: string;
5
+ agentId: string;
6
+ callId: string | undefined;
7
+ prospectId: string | undefined;
8
+ apiKey: string | undefined;
8
9
  private backendUrl;
10
+ token: string | undefined;
9
11
  private onConnected;
10
12
  private onDisconnected;
11
13
  private onError;
12
14
  private onTranscription;
15
+ private onLocalStream;
13
16
  private onRemoteTrack;
14
17
  private onClientToolCall;
18
+ private onSessionStarted;
19
+ private sessionStartedEmitted;
15
20
  private pc;
16
21
  private dataChannel;
17
22
  private audioElement;
23
+ private localStream;
18
24
  connected: boolean;
19
25
  constructor(config: WebRTCClientConfig);
20
26
  /**
package/dist/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export { ChatService } from './api/services/ChatService';
7
7
  export { SessionManager } from './core/SessionManager';
8
8
  export type { SessionData, StoredMessage, SessionEvent } from './core/SessionManager';
9
9
  export * from './types';
10
+ export * from './react';
@@ -0,0 +1,18 @@
1
+ import { default as React } from 'react';
2
+ import { VaniraPresetRegistry } from './types';
3
+
4
+ export interface BaseClient {
5
+ sendToolResult: (toolCallId: string, result: any) => void;
6
+ sendToolError: (toolCallId: string, error: string) => void;
7
+ sendContextUpdate?: (data: any) => void;
8
+ triggerActionInterrupt?: () => void;
9
+ sendActionTrigger?: (actionName: string, data: Record<string, any>) => void;
10
+ }
11
+ export interface PresetRendererProps {
12
+ client: BaseClient | null;
13
+ toolCall: any | null;
14
+ registry?: VaniraPresetRegistry;
15
+ /** Fired immediately if the toolCall is NOT a preset */
16
+ onCustomTool?: (toolCall: any) => void;
17
+ }
18
+ export declare const PresetRenderer: React.FC<PresetRendererProps>;
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './PresetRenderer';
3
+ export * from './registry';
4
+ export * from './presets/FormPreset';
5
+ export * from './presets/CalendarPreset';
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { VaniraPresetProps } from '../types';
3
+
4
+ export declare const CalendarPreset: React.FC<VaniraPresetProps>;
@@ -0,0 +1,16 @@
1
+ import { default as React } from 'react';
2
+ import { VaniraPresetProps } from '../types';
3
+
4
+ /**
5
+ * Built-in Camera Preset — vanira_camera
6
+ *
7
+ * Opens the device camera mid-call so the user can capture a photo and send it
8
+ * to the agent via client.uploadMedia() (same pipeline as file uploads).
9
+ *
10
+ * client_fields / arguments:
11
+ * - title: string (default: "Take a Photo")
12
+ * - description: string
13
+ * - reason: string (upload reason, default: "camera_capture")
14
+ * - facing_mode: "environment" | "user" (default: environment — rear camera on mobile)
15
+ */
16
+ export declare const CameraPreset: React.FC<VaniraPresetProps>;
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { VaniraPresetProps } from '../types';
3
+
4
+ export declare const FormPreset: React.FC<VaniraPresetProps>;
@@ -0,0 +1,16 @@
1
+ import { default as React } from 'react';
2
+ import { VaniraPresetProps } from '../types';
3
+
4
+ /**
5
+ * Built-in Navigate Preset — headless, zero-code navigation.
6
+ *
7
+ * Framework-agnostic: uses a custom 'vanira:navigate' event that the host
8
+ * router (React Router, Next.js, TanStack, etc.) can listen to without
9
+ * a global popstate which would tear down the React Router context.
10
+ *
11
+ * The PresetRenderer skips the overlay wrapper for this preset.
12
+ *
13
+ * Agent tool arguments:
14
+ * - target_url: string (e.g. "/docs", "/pricing", "https://external.com")
15
+ */
16
+ export declare const NavigatePreset: React.FC<VaniraPresetProps>;
@@ -0,0 +1,17 @@
1
+ import { default as React } from 'react';
2
+ import { VaniraPresetProps } from '../types';
3
+
4
+ /**
5
+ * Built-in Upload Preset — vanira_upload
6
+ *
7
+ * Renders a drag-and-drop file uploader. On submit, calls client.uploadMedia()
8
+ * which POSTs the file to /media/upload and sends client_media_update over the DataChannel.
9
+ * onComplete is called with { file_name, file_type, file_size, media_id, url }.
10
+ *
11
+ * Supported agent tool arguments / client_fields:
12
+ * - title: string (default: "Upload a File")
13
+ * - description: string (default: "Drop or select any file up to 3 MB")
14
+ * - reason: string (forwarded to uploadMedia, default: "upload")
15
+ * - accept: string (MIME types override)
16
+ */
17
+ export declare const UploadPreset: React.FC<VaniraPresetProps>;
@@ -0,0 +1,3 @@
1
+ import { VaniraPresetRegistry } from './types';
2
+
3
+ export declare const defaultRegistry: VaniraPresetRegistry;
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ export interface VaniraPresetProps {
3
+ /** The unique tool call execution ID from the AI */
4
+ toolCallId: string;
5
+ /** The dynamic configuration fields mapped from the Dashboard */
6
+ clientFields: Record<string, any>;
7
+ /** The parameters the AI extracted from the conversation */
8
+ arguments: Record<string, any>;
9
+ /** Called when the preset successfully gathers data to send back to the AI */
10
+ onComplete: (result: Record<string, any> | string) => void;
11
+ /** Called if the preset is cancelled or closed without data */
12
+ onCancel: (reason?: string) => void;
13
+ /** The live WebRTC client — available for presets that need to call SDK methods (e.g. uploadMedia) */
14
+ client?: any;
15
+ }
16
+ export type VaniraPresetComponent = React.ComponentType<VaniraPresetProps>;
17
+ export type VaniraPresetRegistry = Record<string, VaniraPresetComponent>;
18
+ export interface ActivePresetState {
19
+ presetId: string;
20
+ toolCall: any;
21
+ }
package/dist/types.d.ts CHANGED
@@ -34,10 +34,21 @@ export interface WebRTCClientConfig {
34
34
  serverUrl?: string;
35
35
  /** Call ID. Set automatically by createCall(); supply manually if managing the session yourself. */
36
36
  callId?: string;
37
+ /** Optional prospect ID. If supplied, the SDK resumes the prospect context. */
38
+ prospectId?: string;
39
+ /** Lifecycle behavior when starting a connection ('continue' or 'new') */
40
+ sessionBehavior?: 'continue' | 'new';
41
+ /** Callback when session starts or resumes with resolved IDs and server URL */
42
+ onSessionStarted?: (payload: {
43
+ prospectId: string;
44
+ callId: string;
45
+ serverUrl: string;
46
+ }) => void;
37
47
  onConnected?: () => void;
38
48
  onDisconnected?: () => void;
39
49
  onError?: (error: any) => void;
40
50
  onTranscription?: (text: string, isFinal: boolean) => void;
51
+ onLocalStream?: (stream: MediaStream) => void;
41
52
  onRemoteTrack?: (track: MediaStreamTrack, stream: MediaStream) => void;
42
53
  onClientToolCall?: (toolCall: any) => void;
43
54
  iceServers?: RTCIceServer[];
@@ -1,7 +1,7 @@
1
1
  import { IChatAdapter } from '../abstraction/interfaces';
2
2
 
3
3
  export declare class VaniraChatAdapter implements IChatAdapter {
4
- sendMessage(text: string, agentId: string, prospectId: string, chatId: string | null, onResponse: (response: any) => void, onStream?: (text: string) => void, onChatIdUpdate?: (newId: string) => void, widgetId?: string, pkKey?: string): Promise<void>;
4
+ sendMessage(text: string, agentId: string, prospectId: string, chatId: string | null, onResponse: (response: any) => void, onStream?: (text: string) => void, onChatIdUpdate?: (newId: string, newConversationId?: string) => void, widgetId?: string, pkKey?: string): Promise<void>;
5
5
  sendFile(file: File): Promise<void>;
6
6
  likeDislike(messageId: string, action: 'like' | 'dislike'): Promise<void>;
7
7
  }
@@ -10,6 +10,7 @@ export declare class ChatWindow {
10
10
  private input;
11
11
  private sendBtn;
12
12
  private typingIndicator;
13
+ private resolveBar;
13
14
  constructor(onSend: (message: string) => void);
14
15
  getElement(): HTMLDivElement;
15
16
  private formatTime;
@@ -28,5 +29,6 @@ export declare class ChatWindow {
28
29
  */
29
30
  addCallRecord(durationMs: number, timestamp?: number): void;
30
31
  clearMessages(): void;
32
+ setResolveCallback(onResolve: () => void): void;
31
33
  private scrollToBottom;
32
34
  }
@@ -1,6 +1,8 @@
1
1
  export declare class VoiceOrb {
2
2
  private onClick;
3
3
  private element;
4
+ private selectedBehavior;
4
5
  constructor(onClick: () => void);
6
+ getSessionBehavior(): 'continue' | 'new';
5
7
  getElement(): HTMLDivElement;
6
8
  }
@@ -35,7 +35,7 @@ export declare class VoiceOverlay {
35
35
  getElement(): HTMLDivElement;
36
36
  setMode(mode: 'voice' | 'avatar'): void;
37
37
  setVideoTrack(track: MediaStreamTrack): void;
38
- setStatus(status: 'connecting' | 'connected' | 'disconnected' | 'error'): void;
38
+ setStatus(status: 'connecting' | 'connected' | 'disconnected' | 'error', errorMsg?: string): void;
39
39
  setTranscription(text: string, isFinal: boolean): void;
40
40
  show(): void;
41
41
  hide(): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * WidgetPresetRenderer
3
+ * Vanilla-JS equivalent of the React PresetRenderer.
4
+ * Renders Calendar and Form presets as DOM overlays inside the widget shadow root.
5
+ */
6
+ type OnComplete = (result: Record<string, any>) => void;
7
+ type OnCancel = (reason?: string) => void;
8
+ export declare class WidgetPresetRenderer {
9
+ private overlay;
10
+ private styleInjected;
11
+ private calViewYear;
12
+ private calViewMonth;
13
+ private calSelectedTs;
14
+ private calSelectedTime;
15
+ constructor(_root: ShadowRoot);
16
+ /** Returns true if this tool call contains a known preset_id and was handled */
17
+ handle(toolCall: any, onComplete: OnComplete, onCancel: OnCancel): boolean;
18
+ /** Force-dismiss the active preset (call on disconnect) */
19
+ dismiss(): void;
20
+ private injectStyles;
21
+ private createOverlay;
22
+ private createModal;
23
+ private renderCalendar;
24
+ private loadSlots;
25
+ private renderSlots;
26
+ private renderForm;
27
+ }
28
+ export {};
@@ -0,0 +1,14 @@
1
+ export interface VaniraPresetProps {
2
+ /** The unique tool call execution ID from the AI */
3
+ toolCallId: string;
4
+ /** The dynamic configuration fields mapped from the Dashboard */
5
+ clientFields: Record<string, any>;
6
+ /** The parameters the AI extracted from the conversation */
7
+ arguments: Record<string, any>;
8
+ /** Called when the preset successfully gathers data to send back to the AI */
9
+ onComplete: (result: Record<string, any> | string) => void;
10
+ /** Called if the preset is cancelled or closed without data */
11
+ onCancel: (reason?: string) => void;
12
+ }
13
+ export type VaniraPresetComponent = React.ComponentType<VaniraPresetProps>;
14
+ export type VaniraPresetRegistry = Record<string, VaniraPresetComponent>;
@@ -3,6 +3,7 @@ import { AbstractWidgetProvider } from '../abstraction/AbstractWidgetProvider';
3
3
  export declare class VaniraInternalProvider extends AbstractWidgetProvider {
4
4
  private vaniraClient;
5
5
  private currentView;
6
+ private presetRenderer;
6
7
  private isPanelOpen;
7
8
  private callActive;
8
9
  private eventSource;
@@ -20,6 +21,7 @@ export declare class VaniraInternalProvider extends AbstractWidgetProvider {
20
21
  private chatServerUrl;
21
22
  private prospectId;
22
23
  private chatId;
24
+ private conversationId;
23
25
  private widgetId;
24
26
  private pkKey;
25
27
  private chatInitialized;
@@ -55,7 +57,7 @@ export declare class VaniraInternalProvider extends AbstractWidgetProvider {
55
57
  private initializeChatSession;
56
58
  initialize(config: any): Promise<void>;
57
59
  private processConfig;
58
- create_call(): Promise<void>;
60
+ create_call(behavior?: 'continue' | 'new'): Promise<void>;
59
61
  end_call(): void;
60
62
  private updateViewCallState;
61
63
  private updateViewStatus;
@@ -70,4 +72,5 @@ export declare class VaniraInternalProvider extends AbstractWidgetProvider {
70
72
  private closePanel;
71
73
  private updateWelcomeChipsVisibility;
72
74
  private handleFloatingChipClick;
75
+ private resolveActiveConversation;
73
76
  }
@@ -1,6 +1,7 @@
1
1
  export declare const icons: {
2
2
  phone: string;
3
3
  close: string;
4
+ refresh: string;
4
5
  mic: string;
5
6
  micOff: string;
6
7
  waves: string;
@@ -1 +1 @@
1
- export declare const widgetStyles = "\n :host(.vanira-portal) {\n position: fixed !important;\n bottom: 0 !important;\n right: 0 !important;\n top: auto !important;\n left: auto !important;\n width: 0 !important;\n height: 0 !important;\n pointer-events: none !important;\n z-index: 2147483647;\n overflow: visible !important;\n contain: none !important;\n }\n\n :host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n box-sizing: border-box;\n\n /* Default Theme Variables */\n --primary: #6366f1;\n --primary-hover: #4f46e5;\n --primary-rgb: 99, 102, 241;\n --secondary: #4f46e5;\n --secondary-rgb: 79, 70, 229;\n --bg: #ffffff;\n --text: #1f2937;\n --text-muted: #6b7280;\n --border: #e5e7eb;\n --shadow: 0 10px 40px rgba(0, 0, 0, 0.1);\n --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15);\n }\n\n :host * {\n box-sizing: border-box;\n }\n\n \n @keyframes fadeInScale {\n from { opacity: 0; transform: scale(0.8); }\n to { opacity: 1; transform: scale(1); }\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n @keyframes wave {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-4px); }\n }\n\n @keyframes wave-rotate-1 {\n 0% { transform: rotate(0deg) scale(1); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n 25% { transform: rotate(90deg) scale(1.02); border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; }\n 50% { transform: rotate(180deg) scale(1); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n 75% { transform: rotate(270deg) scale(0.98); border-radius: 40% 60% 70% 30% / 40% 40% 60% 50%; }\n 100% { transform: rotate(360deg) scale(1); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n }\n\n @keyframes wave-rotate-2 {\n 0% { transform: rotate(60deg) scale(1.02); border-radius: 40% 60% 60% 40% / 60% 40% 60% 40%; }\n 33% { transform: rotate(180deg) scale(0.98); border-radius: 60% 40% 40% 60% / 40% 60% 40% 60%; }\n 66% { transform: rotate(300deg) scale(1.02); border-radius: 50% 50% 40% 60% / 60% 40% 50% 50%; }\n 100% { transform: rotate(420deg) scale(1.02); border-radius: 40% 60% 60% 40% / 60% 40% 60% 40%; }\n }\n\n @keyframes wave-rotate-3 {\n 0% { transform: rotate(120deg) scale(1); border-radius: 50% 50% 50% 50%; }\n 50% { transform: rotate(300deg) scale(1.05); border-radius: 40% 60% 50% 50% / 50% 50% 60% 40%; }\n 100% { transform: rotate(480deg) scale(1); border-radius: 50% 50% 50% 50%; }\n }\n\n @keyframes clickBounce {\n 0% { transform: scale(1); }\n 25% { transform: scale(0.9) rotate(-5deg); }\n 50% { transform: scale(1.15) rotate(5deg); }\n 75% { transform: scale(0.95) rotate(-2deg); }\n 100% { transform: scale(1) rotate(0deg); }\n }\n\n @keyframes pulseGlow {\n 0%, 100% { box-shadow: 0 4px 16px rgba(239, 68, 68, 0.4); }\n 50% { box-shadow: 0 4px 24px rgba(239, 68, 68, 0.6), 0 0 32px rgba(239, 68, 68, 0.3); }\n }\n\n @keyframes typingBounce {\n 0%, 80%, 100% { transform: translateY(0); }\n 40% { transform: translateY(-6px); }\n }\n\n @keyframes floatOrb {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n\n @keyframes pulseGlowIdle {\n 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.4; }\n 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }\n }\n\n @keyframes spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n @keyframes voice-bars-orb {\n 0%, 100% { transform: scaleY(1); opacity: 0.7; }\n 50% { transform: scaleY(1.8); opacity: 1; }\n }\n\n\n .widget-fab {\n position: fixed;\n z-index: 2147483647;\n pointer-events: auto !important;\n width: 64px;\n height: 64px;\n border-radius: 50%;\n background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--shadow);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n animation: fadeInScale 0.4s ease-out;\n }\n\n .widget-fab:hover {\n transform: scale(1.1);\n box-shadow: var(--shadow-lg);\n }\n\n .widget-fab:active {\n transform: scale(0.95);\n }\n\n .widget-fab svg {\n width: 28px;\n height: 28px;\n color: white;\n }\n\n .widget-panel {\n position: fixed;\n z-index: 2147483647;\n pointer-events: auto;\n background: rgba(255, 255, 255, 0.98);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid #d1d5db;\n border-radius: 28px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n \n /* Default Dimensions */\n width: 400px;\n height: 600px;\n max-height: 90vh;\n max-width: 95vw;\n }\n\n .widget-panel.maximized {\n width: 90vw !important;\n height: 90vh !important;\n top: 5vh !important;\n left: 5vw !important;\n right: 5vw !important;\n bottom: 5vh !important;\n margin: auto;\n }\n\n .widget-body {\n padding: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n min-height: 0;\n }\n\n .widget-panel.hidden {\n opacity: 0;\n pointer-events: none !important;\n transform: translateY(20px) scale(0.95);\n }\n\n .widget-header {\n background: #1f2937;\n color: white;\n padding: 14px 20px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n\n .widget-title {\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n letter-spacing: 0.3px;\n }\n\n .close-btn {\n background: rgba(255, 255, 255, 0.15);\n border: none;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n backdrop-filter: blur(10px);\n }\n\n .close-btn:hover {\n background: rgba(255, 255, 255, 0.25);\n transform: scale(1.1);\n }\n\n .close-btn.maximize-btn {\n display: flex;\n }\n\n .close-btn svg {\n width: 18px;\n height: 18px;\n color: white;\n flex-shrink: 0;\n }\n\n .status-indicator {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px;\n background: #f9fafb;\n border-radius: 12px;\n margin-bottom: 20px;\n }\n\n .status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #10b981;\n animation: pulse 2s infinite;\n }\n\n .status-text {\n font-size: 14px;\n color: var(--text-muted);\n margin: 0;\n }\n\n .controls {\n display: flex;\n gap: 12px;\n justify-content: center;\n }\n\n .control-btn {\n flex: 1;\n padding: 14px 20px;\n border: none;\n border-radius: 12px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n }\n\n .control-btn svg {\n width: 18px;\n height: 18px;\n }\n\n .control-btn.primary {\n background: #1f2937;\n color: white;\n }\n\n .control-btn.primary:hover {\n background: #374151;\n }\n\n .control-btn.danger {\n background: #ef4444;\n color: white;\n }\n\n .control-btn.danger:hover {\n background: #dc2626;\n }\n\n .voice-mode-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding-bottom: 0;\n }\n\n .central-orb-container {\n position: relative;\n width: 120px;\n height: 120px;\n margin-bottom: 32px;\n cursor: pointer;\n }\n\n .central-orb {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n position: relative;\n z-index: 10;\n box-shadow: 0 10px 30px rgba(var(--primary-rgb), 0.3);\n transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .central-orb.active {\n animation: pulse 2s infinite;\n }\n \n .central-orb.idle {\n animation: floatOrb 4s ease-in-out infinite;\n cursor: pointer;\n }\n\n .central-orb.idle:hover {\n transform: scale(1.05);\n }\n\n .central-orb-glow {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 140%;\n height: 140%;\n background: radial-gradient(circle, rgba(var(--primary-rgb), 0.2) 0%, transparent 70%);\n animation: pulseGlowIdle 4s ease-in-out infinite;\n z-index: -1;\n pointer-events: none;\n }\n\n .central-orb svg {\n width: 24px;\n height: 24px;\n }\n\n .voice-status-text {\n font-size: 20px;\n font-weight: 600;\n color: var(--text);\n margin-bottom: 8px;\n text-align: center;\n }\n\n .voice-status-subtext {\n font-size: 14px;\n color: var(--text-muted);\n text-align: center;\n }\n\n /* Chat Mode Styles */\n .chat-messages {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n background: #f9fafb; \n \n scrollbar-width: thin;\n scrollbar-color: rgba(156, 163, 175, 0.5) transparent;\n }\n\n .chat-messages::-webkit-scrollbar {\n width: 6px;\n }\n\n .chat-messages::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .chat-messages::-webkit-scrollbar-thumb {\n background-color: rgba(156, 163, 175, 0.5);\n border-radius: 3px;\n }\n\n .chat-message {\n max-width: 85%;\n padding: 12px 16px;\n border-radius: 16px;\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n }\n\n .chat-message.user {\n align-self: flex-end;\n background: #000000;\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .chat-message.assistant {\n align-self: flex-start;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n color: var(--text);\n border-bottom-left-radius: 4px;\n box-shadow: 0 1px 2px rgba(0,0,0,0.05);\n }\n\n .chat-input-area {\n padding: 20px;\n border-top: 1px solid rgba(0, 0, 0, 0.05);\n display: flex;\n gap: 10px;\n align-items: center;\n background: white;\n z-index: 30;\n position: relative;\n border-bottom-left-radius: 28px;\n border-bottom-right-radius: 28px;\n }\n\n .chat-input {\n flex: 1;\n padding: 14px 20px;\n border: 1px solid #e5e7eb;\n border-radius: 28px;\n font-size: 15px;\n outline: none;\n transition: all 0.3s;\n background: #f3f4f6;\n color: #1f2937;\n }\n\n .chat-input:focus {\n border-color: var(--primary);\n background: white;\n box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);\n }\n\n .chat-send-btn {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: var(--primary);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);\n flex-shrink: 0;\n }\n\n .chat-send-btn:hover {\n transform: scale(1.05) rotate(-10deg);\n background: var(--primary-hover);\n box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);\n }\n\n .chat-send-btn:active {\n transform: scale(0.95);\n }\n\n .chat-send-btn svg {\n width: 22px;\n height: 22px;\n color: white;\n margin-left: 2px;\n }\n\n .avatar-container {\n background: transparent;\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n }\n \n .avatar-video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .voice-btn-simple {\n position: relative;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n border: 1.5px solid #e5e7eb;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s;\n overflow: visible;\n background: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n }\n\n .voice-btn-simple:hover {\n transform: scale(1.05);\n background: #f9fafb;\n border-color: #9ca3af;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);\n }\n\n .voice-btn-icon {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #4b5563;\n }\n\n .voice-btn-icon svg {\n width: 20px;\n height: 20px;\n stroke-width: 2px;\n }\n\n .absolute { position: absolute; }\n .relative { position: relative; }\n .rounded-full { border-radius: 9999px; }\n .flex { display: flex; }\n .items-center { align-items: center; }\n .justify-center { justify-content: center; }\n .w-full { width: 100%; }\n .h-full { height: 100%; }\n .z-50 { z-index: 50; }\n\n .branding-footer {\n display: flex;\n justify-content: center;\n padding: 4px 0 8px 0;\n background-color: #f9fafb;\n font-size: 10px;\n color: #9ca3af;\n font-weight: 500;\n letter-spacing: 0.025em;\n }\n\n .branding-footer b {\n color: #3b82f6;\n font-weight: 700;\n margin-left: 4px;\n }\n\n @media (max-width: 640px) {\n .widget-panel {\n width: 100vw !important;\n height: 100vh !important;\n max-width: 100vw !important;\n max-height: 100vh !important;\n top: 0 !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n border-radius: 0 !important;\n margin: 0 !important;\n }\n\n .widget-fab {\n width: 56px;\n height: 56px;\n bottom: 16px !important;\n right: 16px !important;\n }\n\n :host(.vanira-panel-open) .widget-fab {\n display: none !important;\n }\n\n .close-btn.maximize-btn {\n display: none !important;\n }\n\n .widget-header {\n padding: 12px 16px;\n }\n\n .widget-title {\n font-size: 16px;\n }\n\n .chat-input-area {\n padding: 12px;\n padding-bottom: env(safe-area-inset-bottom, 12px);\n }\n }\n";
1
+ export declare const widgetStyles = "\n :host(.vanira-portal) {\n position: fixed !important;\n bottom: 0 !important;\n right: 0 !important;\n top: auto !important;\n left: auto !important;\n width: 0 !important;\n height: 0 !important;\n pointer-events: none !important;\n z-index: 2147483647;\n overflow: visible !important;\n contain: none !important;\n }\n\n :host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n box-sizing: border-box;\n\n /* Default Theme Variables */\n --primary: #000000;\n --primary-hover: #111111;\n --primary-rgb: 0, 0, 0;\n --secondary: #111111;\n --secondary-rgb: 17, 17, 17;\n --bg: #ffffff;\n --text: #1f2937;\n --text-muted: #6b7280;\n --border: #e5e7eb;\n --shadow: 0 10px 40px rgba(0, 0, 0, 0.1);\n --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15);\n }\n\n :host * {\n box-sizing: border-box;\n }\n\n \n @keyframes fadeInScale {\n from { opacity: 0; transform: scale(0.8); }\n to { opacity: 1; transform: scale(1); }\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n @keyframes wave {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-4px); }\n }\n\n @keyframes wave-rotate-1 {\n 0% { transform: rotate(0deg) scale(1); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n 25% { transform: rotate(90deg) scale(1.02); border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; }\n 50% { transform: rotate(180deg) scale(1); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n 75% { transform: rotate(270deg) scale(0.98); border-radius: 40% 60% 70% 30% / 40% 40% 60% 50%; }\n 100% { transform: rotate(360deg) scale(1); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n }\n\n @keyframes wave-rotate-2 {\n 0% { transform: rotate(60deg) scale(1.02); border-radius: 40% 60% 60% 40% / 60% 40% 60% 40%; }\n 33% { transform: rotate(180deg) scale(0.98); border-radius: 60% 40% 40% 60% / 40% 60% 40% 60%; }\n 66% { transform: rotate(300deg) scale(1.02); border-radius: 50% 50% 40% 60% / 60% 40% 50% 50%; }\n 100% { transform: rotate(420deg) scale(1.02); border-radius: 40% 60% 60% 40% / 60% 40% 60% 40%; }\n }\n\n @keyframes wave-rotate-3 {\n 0% { transform: rotate(120deg) scale(1); border-radius: 50% 50% 50% 50%; }\n 50% { transform: rotate(300deg) scale(1.05); border-radius: 40% 60% 50% 50% / 50% 50% 60% 40%; }\n 100% { transform: rotate(480deg) scale(1); border-radius: 50% 50% 50% 50%; }\n }\n\n @keyframes clickBounce {\n 0% { transform: scale(1); }\n 25% { transform: scale(0.9) rotate(-5deg); }\n 50% { transform: scale(1.15) rotate(5deg); }\n 75% { transform: scale(0.95) rotate(-2deg); }\n 100% { transform: scale(1) rotate(0deg); }\n }\n\n @keyframes pulseGlow {\n 0%, 100% { box-shadow: 0 4px 16px rgba(239, 68, 68, 0.4); }\n 50% { box-shadow: 0 4px 24px rgba(239, 68, 68, 0.6), 0 0 32px rgba(239, 68, 68, 0.3); }\n }\n\n @keyframes typingBounce {\n 0%, 80%, 100% { transform: translateY(0); }\n 40% { transform: translateY(-6px); }\n }\n\n @keyframes floatOrb {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n\n @keyframes pulseGlowIdle {\n 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.4; }\n 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }\n }\n\n @keyframes spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n @keyframes voice-bars-orb {\n 0%, 100% { transform: scaleY(1); opacity: 0.7; }\n 50% { transform: scaleY(1.8); opacity: 1; }\n }\n\n\n .widget-fab {\n position: fixed;\n z-index: 2147483647;\n pointer-events: auto !important;\n width: 64px;\n height: 64px;\n border-radius: 50%;\n background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--shadow);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n animation: fadeInScale 0.4s ease-out;\n }\n\n .widget-fab:hover {\n transform: scale(1.1);\n box-shadow: var(--shadow-lg);\n }\n\n .widget-fab:active {\n transform: scale(0.95);\n }\n\n .widget-fab svg {\n width: 28px;\n height: 28px;\n color: white;\n }\n\n .widget-panel {\n position: fixed;\n z-index: 2147483647;\n pointer-events: auto;\n background: rgba(255, 255, 255, 0.98);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid #d1d5db;\n border-radius: 28px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n \n /* Default Dimensions */\n width: 400px;\n height: 600px;\n max-height: 90vh;\n max-width: 95vw;\n }\n\n .widget-panel.maximized {\n width: 90vw !important;\n height: 90vh !important;\n top: 5vh !important;\n left: 5vw !important;\n right: 5vw !important;\n bottom: 5vh !important;\n margin: auto;\n }\n\n .widget-body {\n padding: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n min-height: 0;\n }\n\n .widget-panel.hidden {\n opacity: 0;\n pointer-events: none !important;\n transform: translateY(20px) scale(0.95);\n }\n\n .widget-header {\n background: #1f2937;\n color: white;\n padding: 14px 20px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n\n .widget-title {\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n letter-spacing: 0.3px;\n }\n\n .close-btn {\n background: rgba(255, 255, 255, 0.15);\n border: none;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n backdrop-filter: blur(10px);\n }\n\n .close-btn:hover {\n background: rgba(255, 255, 255, 0.25);\n transform: scale(1.1);\n }\n\n .close-btn.maximize-btn {\n display: flex;\n }\n\n .close-btn svg {\n width: 18px;\n height: 18px;\n color: white;\n flex-shrink: 0;\n }\n\n .status-indicator {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px;\n background: #f9fafb;\n border-radius: 12px;\n margin-bottom: 20px;\n }\n\n .status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #10b981;\n animation: pulse 2s infinite;\n }\n\n .status-text {\n font-size: 14px;\n color: var(--text-muted);\n margin: 0;\n }\n\n .controls {\n display: flex;\n gap: 12px;\n justify-content: center;\n }\n\n .control-btn {\n flex: 1;\n padding: 14px 20px;\n border: none;\n border-radius: 12px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n }\n\n .control-btn svg {\n width: 18px;\n height: 18px;\n }\n\n .control-btn.primary {\n background: #1f2937;\n color: white;\n }\n\n .control-btn.primary:hover {\n background: #374151;\n }\n\n .control-btn.danger {\n background: #ef4444;\n color: white;\n }\n\n .control-btn.danger:hover {\n background: #dc2626;\n }\n\n .voice-mode-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding-bottom: 0;\n }\n\n .central-orb-container {\n position: relative;\n width: 120px;\n height: 120px;\n margin-bottom: 32px;\n cursor: pointer;\n }\n\n .central-orb {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n position: relative;\n z-index: 10;\n box-shadow: 0 10px 30px rgba(var(--primary-rgb), 0.3);\n transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .central-orb.active {\n animation: pulse 2s infinite;\n }\n \n .central-orb.idle {\n animation: floatOrb 4s ease-in-out infinite;\n cursor: pointer;\n }\n\n .central-orb.idle:hover {\n transform: scale(1.05);\n }\n\n .central-orb-glow {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 140%;\n height: 140%;\n background: radial-gradient(circle, rgba(var(--primary-rgb), 0.2) 0%, transparent 70%);\n animation: pulseGlowIdle 4s ease-in-out infinite;\n z-index: -1;\n pointer-events: none;\n }\n\n .central-orb svg {\n width: 24px;\n height: 24px;\n }\n\n .voice-status-text {\n font-size: 20px;\n font-weight: 600;\n color: var(--text);\n margin-bottom: 8px;\n text-align: center;\n }\n\n .voice-status-subtext {\n font-size: 14px;\n color: var(--text-muted);\n text-align: center;\n }\n\n /* Chat Mode Styles */\n .chat-messages {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n background: #f9fafb; \n \n scrollbar-width: thin;\n scrollbar-color: rgba(156, 163, 175, 0.5) transparent;\n }\n\n .chat-messages::-webkit-scrollbar {\n width: 6px;\n }\n\n .chat-messages::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .chat-messages::-webkit-scrollbar-thumb {\n background-color: rgba(156, 163, 175, 0.5);\n border-radius: 3px;\n }\n\n .chat-message {\n max-width: 85%;\n padding: 12px 16px;\n border-radius: 16px;\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n }\n\n .chat-message.user {\n align-self: flex-end;\n background: #000000;\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .chat-message.assistant {\n align-self: flex-start;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n color: var(--text);\n border-bottom-left-radius: 4px;\n box-shadow: 0 1px 2px rgba(0,0,0,0.05);\n }\n\n .chat-input-area {\n padding: 20px;\n border-top: 1px solid rgba(0, 0, 0, 0.05);\n display: flex;\n gap: 10px;\n align-items: center;\n background: white;\n z-index: 30;\n position: relative;\n border-bottom-left-radius: 28px;\n border-bottom-right-radius: 28px;\n }\n\n .chat-input {\n flex: 1;\n padding: 14px 20px;\n border: 1px solid #e5e7eb;\n border-radius: 28px;\n font-size: 15px;\n outline: none;\n transition: all 0.3s;\n background: #f3f4f6;\n color: #1f2937;\n }\n\n .chat-input:focus {\n border-color: var(--primary);\n background: white;\n box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);\n }\n\n .chat-send-btn {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: var(--primary);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);\n flex-shrink: 0;\n }\n\n .chat-send-btn:hover {\n transform: scale(1.05) rotate(-10deg);\n background: var(--primary-hover);\n box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);\n }\n\n .chat-send-btn:active {\n transform: scale(0.95);\n }\n\n .chat-send-btn svg {\n width: 22px;\n height: 22px;\n color: white;\n margin-left: 2px;\n }\n\n .avatar-container {\n background: transparent;\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n }\n \n .avatar-video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .voice-btn-simple {\n position: relative;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n border: 1.5px solid #e5e7eb;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s;\n overflow: visible;\n background: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n }\n\n .voice-btn-simple:hover {\n transform: scale(1.05);\n background: #f9fafb;\n border-color: #9ca3af;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);\n }\n\n .voice-btn-icon {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #4b5563;\n }\n\n .voice-btn-icon svg {\n width: 20px;\n height: 20px;\n stroke-width: 2px;\n }\n\n .absolute { position: absolute; }\n .relative { position: relative; }\n .rounded-full { border-radius: 9999px; }\n .flex { display: flex; }\n .items-center { align-items: center; }\n .justify-center { justify-content: center; }\n .w-full { width: 100%; }\n .h-full { height: 100%; }\n .z-50 { z-index: 50; }\n\n .branding-footer {\n display: flex;\n justify-content: center;\n padding: 4px 0 8px 0;\n background-color: #f9fafb;\n font-size: 10px;\n color: #9ca3af;\n font-weight: 500;\n letter-spacing: 0.025em;\n }\n\n .branding-footer b {\n color: #3b82f6;\n font-weight: 700;\n margin-left: 4px;\n }\n\n @media (max-width: 640px) {\n .widget-panel {\n width: 100vw !important;\n height: 100vh !important;\n max-width: 100vw !important;\n max-height: 100vh !important;\n top: 0 !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n border-radius: 0 !important;\n margin: 0 !important;\n }\n\n .widget-fab {\n width: 56px;\n height: 56px;\n bottom: 16px !important;\n right: 16px !important;\n }\n\n :host(.vanira-panel-open) .widget-fab {\n display: none !important;\n }\n\n .close-btn.maximize-btn {\n display: none !important;\n }\n\n .widget-header {\n padding: 12px 16px;\n }\n\n .widget-title {\n font-size: 16px;\n }\n\n .chat-input-area {\n padding: 12px;\n padding-bottom: env(safe-area-inset-bottom, 12px);\n }\n }\n\n /* Premium Session Behavior Selector */\n .session-selector-container {\n display: flex;\n gap: 8px;\n background: #f1f5f9;\n padding: 4px;\n border-radius: 12px;\n margin-top: 24px;\n width: 280px;\n box-sizing: border-box;\n border: 1px solid #e2e8f0;\n pointer-events: auto !important;\n }\n\n .session-option {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 8px 10px;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n border: 1.5px solid transparent;\n }\n\n .session-option:hover {\n background: rgba(255, 255, 255, 0.5);\n }\n\n .session-option.active {\n background: #ffffff;\n border-color: var(--primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);\n }\n\n .session-option .session-title {\n font-size: 11px;\n font-weight: 700;\n color: #1e293b;\n margin-bottom: 2px;\n letter-spacing: 0.2px;\n text-transform: uppercase;\n }\n\n .session-option .session-desc {\n font-size: 8.5px;\n font-weight: 500;\n color: #64748b;\n text-align: center;\n line-height: 1.2;\n }\n\n /* Selector styling for clean card launcher */\n .clean-card-launcher .session-selector-container {\n width: 100%;\n margin-top: 10px;\n margin-bottom: 6px;\n gap: 4px;\n padding: 3px;\n border-radius: 8px;\n }\n\n .clean-card-launcher .session-option {\n padding: 5px 6px;\n border-radius: 6px;\n }\n\n .clean-card-launcher .session-option .session-title {\n font-size: 9px;\n margin-bottom: 0;\n }\n\n .clean-card-launcher .session-option .session-desc {\n display: none;\n }\n\n /* Thin Resolve Bar & Link above text input */\n .chat-resolve-bar {\n padding: 8px 16px;\n border-top: 1px solid rgba(0, 0, 0, 0.05);\n border-bottom: 1px solid rgba(0, 0, 0, 0.03);\n background: #ffffff;\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 25;\n }\n\n .chat-resolve-link {\n background: none;\n border: none;\n font-size: 11px;\n font-weight: 500;\n color: var(--primary);\n cursor: pointer;\n transition: all 0.2s;\n text-decoration: underline;\n opacity: 0.8;\n }\n\n .chat-resolve-link:hover {\n opacity: 1;\n color: var(--primary-hover);\n }\n";
@@ -17,7 +17,7 @@ export declare abstract class AbstractChatView {
17
17
  getElement(): HTMLDivElement;
18
18
  getChatWindow(): ChatWindow;
19
19
  setCallActive(active: boolean): void;
20
- setStatus(status: 'connecting' | 'connected' | 'disconnected' | 'error'): void;
20
+ setStatus(status: 'connecting' | 'connected' | 'disconnected' | 'error', errorMsg?: string): void;
21
21
  setTranscription(text: string, isFinal: boolean): void;
22
22
  setVideoTrack(track: MediaStreamTrack): void;
23
23
  }
@@ -3,16 +3,16 @@ import { VoiceOverlay } from '../components';
3
3
  export declare abstract class AbstractVoiceView {
4
4
  protected element: HTMLDivElement;
5
5
  protected overlay: VoiceOverlay;
6
- protected onStartCall: () => void;
6
+ protected onStartCall: (behavior?: 'continue' | 'new') => void;
7
7
  protected onHangup: () => void;
8
8
  protected primaryColor: string;
9
9
  protected secondaryColor: string;
10
10
  protected onCallEnded?: (durationMs: number, startedAt: number) => void;
11
- constructor(onStartCall: () => void, onHangup: () => void, primaryColor?: string, secondaryColor?: string, onCallEnded?: (durationMs: number, startedAt: number) => void);
11
+ constructor(onStartCall: (behavior?: 'continue' | 'new') => void, onHangup: () => void, primaryColor?: string, secondaryColor?: string, onCallEnded?: (durationMs: number, startedAt: number) => void);
12
12
  protected initOverlay(): void;
13
13
  getElement(): HTMLDivElement;
14
14
  setCallActive(active: boolean): void;
15
- setStatus(status: 'connecting' | 'connected' | 'disconnected' | 'error'): void;
15
+ setStatus(status: 'connecting' | 'connected' | 'disconnected' | 'error', errorMsg?: string): void;
16
16
  setTranscription(text: string, isFinal: boolean): void;
17
17
  setVideoTrack(track: MediaStreamTrack): void;
18
18
  }
@@ -2,5 +2,5 @@ import { AbstractVoiceView } from './AbstractVoiceView';
2
2
 
3
3
  export declare class VoiceOnlyView extends AbstractVoiceView {
4
4
  private voiceOrb;
5
- constructor(onStartCall: () => void, onHangup: () => void, primaryColor?: string, secondaryColor?: string, onCallEnded?: (durationMs: number, startedAt: number) => void);
5
+ constructor(onStartCall: (behavior?: 'continue' | 'new') => void, onHangup: () => void, primaryColor?: string, secondaryColor?: string, onCallEnded?: (durationMs: number, startedAt: number) => void);
6
6
  }