@vezlo/assistant-chat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { Send, X, MessageCircle, Bot, ThumbsUp, ThumbsDown } from 'lucide-react';
5
+ import { generateId, formatTimestamp } from '../utils/index.js';
6
+ import { VezloFooter } from './ui/VezloFooter.js';
7
+ import { createConversation, createUserMessage, generateAIResponse } from '../api/index.js';
8
+ export function Widget({ config, isPlayground = false, onOpen, onClose, onMessage, onError, useShadowRoot = false, }) {
9
+ // Use defaultOpen from config, fallback to isPlayground for backward compatibility
10
+ const [isOpen, setIsOpen] = useState(config.defaultOpen ?? isPlayground);
11
+ // Update isOpen when config.defaultOpen changes
12
+ useEffect(() => {
13
+ if (config.defaultOpen !== undefined) {
14
+ setIsOpen(config.defaultOpen);
15
+ }
16
+ }, [config.defaultOpen]);
17
+ const [messages, setMessages] = useState([]);
18
+ const [input, setInput] = useState('');
19
+ const [isLoading, setIsLoading] = useState(false);
20
+ const [messageFeedback, setMessageFeedback] = useState({});
21
+ const [streamingMessage, setStreamingMessage] = useState('');
22
+ const [conversationUuid, setConversationUuid] = useState(null);
23
+ const [isCreatingConversation, setIsCreatingConversation] = useState(false);
24
+ const messagesEndRef = useRef(null);
25
+ const hostRef = useRef(null);
26
+ const shadowRef = useRef(null);
27
+ const shadowMountRef = useRef(null);
28
+ const [shadowReady, setShadowReady] = useState(false);
29
+ useEffect(() => {
30
+ // No global scroll locking for embedded component usage
31
+ return () => { };
32
+ }, [isPlayground]);
33
+ // Initialize Shadow DOM if enabled
34
+ useEffect(() => {
35
+ if (!useShadowRoot)
36
+ return;
37
+ if (!hostRef.current)
38
+ return;
39
+ if (!shadowRef.current) {
40
+ shadowRef.current = hostRef.current.attachShadow({ mode: 'open' });
41
+ // Create a dedicated mount point inside the shadow for React
42
+ const mount = document.createElement('div');
43
+ mount.setAttribute('id', 'vezlo-shadow-mount');
44
+ shadowRef.current.appendChild(mount);
45
+ shadowMountRef.current = mount;
46
+ // Clone existing styles into shadow to ensure Tailwind/host CSS is available
47
+ try {
48
+ const headNodes = Array.from(document.head.querySelectorAll('style, link[rel="stylesheet"]'));
49
+ headNodes.forEach((node) => {
50
+ shadowRef.current?.appendChild(node.cloneNode(true));
51
+ });
52
+ }
53
+ catch { }
54
+ // Mark shadow as ready to trigger a re-render for the portal
55
+ setShadowReady(true);
56
+ }
57
+ }, [useShadowRoot]);
58
+ // Create conversation when widget opens for the first time
59
+ useEffect(() => {
60
+ const initializeConversation = async () => {
61
+ if (isOpen && !conversationUuid && !isCreatingConversation) {
62
+ setIsCreatingConversation(true);
63
+ try {
64
+ // Create a new conversation
65
+ const userUuid = import.meta.env.VITE_DEFAULT_USER_UUID || 'user-' + generateId().substring(0, 8);
66
+ const companyUuid = import.meta.env.VITE_DEFAULT_COMPANY_UUID || 'company-' + generateId().substring(0, 8);
67
+ const conversation = await createConversation({
68
+ title: 'New Chat',
69
+ user_uuid: userUuid,
70
+ company_uuid: companyUuid,
71
+ });
72
+ setConversationUuid(conversation.uuid);
73
+ console.log('[Widget] Conversation created:', conversation.uuid);
74
+ // Add welcome message after conversation is created
75
+ const welcomeMsg = {
76
+ id: generateId(),
77
+ content: config.welcomeMessage || 'Hello! I\'m your AI assistant. How can I help you today?',
78
+ role: 'assistant',
79
+ timestamp: new Date(),
80
+ };
81
+ setMessages([welcomeMsg]);
82
+ onMessage?.(welcomeMsg);
83
+ }
84
+ catch (error) {
85
+ console.error('[Widget] Failed to create conversation:', error);
86
+ onError?.('Failed to initialize conversation');
87
+ // Still show welcome message even if conversation creation fails
88
+ const welcomeMsg = {
89
+ id: generateId(),
90
+ content: config.welcomeMessage || 'Hello! I\'m your AI assistant. How can I help you today?',
91
+ role: 'assistant',
92
+ timestamp: new Date(),
93
+ };
94
+ setMessages([welcomeMsg]);
95
+ onMessage?.(welcomeMsg);
96
+ }
97
+ finally {
98
+ setIsCreatingConversation(false);
99
+ }
100
+ }
101
+ };
102
+ initializeConversation();
103
+ }, [isOpen, conversationUuid, isCreatingConversation, config.welcomeMessage, onMessage, onError]);
104
+ useEffect(() => {
105
+ // Scroll to bottom when messages change or when streaming
106
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
107
+ }, [messages, streamingMessage]);
108
+ const handleSendMessage = async () => {
109
+ if (!input.trim() || isLoading || !conversationUuid)
110
+ return;
111
+ const userMessageContent = input;
112
+ const userMessage = {
113
+ id: generateId(),
114
+ content: userMessageContent,
115
+ role: 'user',
116
+ timestamp: new Date(),
117
+ };
118
+ setMessages((prev) => [...prev, userMessage]);
119
+ onMessage?.(userMessage);
120
+ setInput('');
121
+ setIsLoading(true);
122
+ try {
123
+ // Step 1: Create user message via API
124
+ const userMessageResponse = await createUserMessage(conversationUuid, {
125
+ content: userMessageContent,
126
+ });
127
+ console.log('[Widget] User message created:', userMessageResponse.uuid);
128
+ // Update the user message with the actual UUID from server
129
+ setMessages((prev) => prev.map((msg) => msg.id === userMessage.id ? { ...msg, id: userMessageResponse.uuid } : msg));
130
+ // Step 2: Generate AI response
131
+ // Keep loading indicator visible until AI response is received
132
+ const aiResponse = await generateAIResponse(userMessageResponse.uuid);
133
+ console.log('[Widget] AI response received:', aiResponse.uuid);
134
+ // Hide loading indicator now that we have the response
135
+ setIsLoading(false);
136
+ // Stream the AI response character by character
137
+ const responseContent = aiResponse.content;
138
+ setStreamingMessage('');
139
+ let currentText = '';
140
+ const streamInterval = setInterval(() => {
141
+ if (currentText.length < responseContent.length) {
142
+ currentText += responseContent[currentText.length];
143
+ setStreamingMessage(currentText);
144
+ }
145
+ else {
146
+ clearInterval(streamInterval);
147
+ // Add the complete message to messages array
148
+ const assistantMessage = {
149
+ id: aiResponse.uuid,
150
+ content: responseContent,
151
+ role: 'assistant',
152
+ timestamp: new Date(aiResponse.created_at),
153
+ };
154
+ setMessages((prev) => [...prev, assistantMessage]);
155
+ onMessage?.(assistantMessage);
156
+ setStreamingMessage('');
157
+ }
158
+ }, 15); // 15ms delay between characters for smooth streaming
159
+ }
160
+ catch (error) {
161
+ console.error('[Widget] Error sending message:', error);
162
+ setIsLoading(false);
163
+ onError?.('Failed to send message');
164
+ // Show error message to user
165
+ const errorMessage = {
166
+ id: generateId(),
167
+ content: 'Sorry, I encountered an error processing your message. Please try again.',
168
+ role: 'assistant',
169
+ timestamp: new Date(),
170
+ };
171
+ setMessages((prev) => [...prev, errorMessage]);
172
+ onMessage?.(errorMessage);
173
+ }
174
+ };
175
+ const handleKeyPress = (e) => {
176
+ if (e.key === 'Enter' && !e.shiftKey) {
177
+ e.preventDefault();
178
+ handleSendMessage();
179
+ }
180
+ };
181
+ const handleFeedback = (messageId, type) => {
182
+ setMessageFeedback(prev => ({
183
+ ...prev,
184
+ [messageId]: prev[messageId] === type ? null : type
185
+ }));
186
+ };
187
+ const handleOpenWidget = () => {
188
+ setIsOpen(true);
189
+ onOpen?.();
190
+ // Notify parent window (for embed mode)
191
+ if (window.parent && !isPlayground) {
192
+ window.parent.postMessage({ type: 'vezlo-widget-opened' }, '*');
193
+ }
194
+ };
195
+ const handleCloseWidget = () => {
196
+ setIsOpen(false);
197
+ onClose?.();
198
+ // Notify parent window (for embed mode)
199
+ if (window.parent && !isPlayground) {
200
+ window.parent.postMessage({ type: 'vezlo-widget-closed' }, '*');
201
+ }
202
+ };
203
+ // Fixed positioning styles for embedded usage (non-playground)
204
+ const isBottomRight = config.position !== 'bottom-left';
205
+ const isInIframe = window.parent !== window;
206
+ const containerStyle = isPlayground
207
+ ? {
208
+ width: '100%',
209
+ height: '100%',
210
+ display: 'flex',
211
+ alignItems: 'center',
212
+ justifyContent: 'center',
213
+ pointerEvents: 'none',
214
+ }
215
+ : isInIframe
216
+ ? {
217
+ // When in iframe, don't add positioning - let iframe handle it
218
+ position: 'relative',
219
+ zIndex: 2147483647,
220
+ pointerEvents: 'none', // container lets clicks pass unless on inner elements
221
+ }
222
+ : {
223
+ position: 'fixed',
224
+ zIndex: 2147483647,
225
+ bottom: 20,
226
+ right: isBottomRight ? 20 : undefined,
227
+ left: !isBottomRight ? 20 : undefined,
228
+ pointerEvents: 'none', // container lets clicks pass unless on inner elements
229
+ };
230
+ const content = (_jsxs("div", { id: "vezlo-widget-root", className: ``, style: containerStyle, children: [_jsx("style", {
231
+ // Using unique, prefixed names to avoid collisions with host styles
232
+ dangerouslySetInnerHTML: {
233
+ __html: `
234
+ @keyframes vezloDotPulse { 0%, 80%, 100% { opacity: .2; transform: scale(0.8);} 40% { opacity: 1; transform: scale(1);} }
235
+ @keyframes vezloCaretBlink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }
236
+ `
237
+ } }), !isOpen && (_jsxs("div", { className: "relative animate-fadeIn", style: { pointerEvents: 'auto', width: 64, height: 64 }, children: [_jsx("div", { style: {
238
+ position: 'absolute',
239
+ inset: 0,
240
+ backgroundColor: config.themeColor || '#059669',
241
+ borderRadius: 9999,
242
+ opacity: 0.2,
243
+ filter: 'blur(0px)'
244
+ } }), _jsx("button", { onClick: handleOpenWidget, style: {
245
+ position: 'relative',
246
+ width: 64,
247
+ height: 64,
248
+ borderRadius: 9999,
249
+ color: '#fff',
250
+ background: `linear-gradient(135deg, ${config.themeColor}, ${config.themeColor}dd)`,
251
+ boxShadow: '0 10px 25px rgba(0,0,0,0.20)',
252
+ display: 'flex',
253
+ alignItems: 'center',
254
+ justifyContent: 'center',
255
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease'
256
+ }, onMouseEnter: (e) => {
257
+ e.currentTarget.style.transform = 'scale(1.05)';
258
+ e.currentTarget.style.boxShadow = '0 14px 30px rgba(0,0,0,0.22)';
259
+ }, onMouseLeave: (e) => {
260
+ e.currentTarget.style.transform = 'scale(1)';
261
+ e.currentTarget.style.boxShadow = '0 10px 25px rgba(0,0,0,0.20)';
262
+ }, children: _jsx(MessageCircle, { className: "w-7 h-7" }) })] })), isOpen && (_jsxs("div", { className: "bg-white rounded-2xl shadow-2xl flex flex-col border border-gray-100 overflow-hidden animate-fadeIn", style: {
263
+ pointerEvents: 'auto',
264
+ width: (config.size && config.size.width) ? config.size.width : 420,
265
+ height: (config.size && config.size.height) ? config.size.height : 600
266
+ }, children: [_jsxs("div", { className: "text-white p-4 flex justify-between items-center relative overflow-hidden", style: { background: `linear-gradient(to right, ${config.themeColor}, ${config.themeColor}dd, ${config.themeColor}bb)`, color: '#fff' }, children: [_jsxs("div", { className: "absolute inset-0 opacity-10", children: [_jsx("div", { className: "absolute top-0 left-0 w-full h-full bg-gradient-to-br from-white/20 to-transparent" }), _jsx("div", { className: "absolute bottom-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-8 translate-x-8" })] }), _jsxs("div", { className: "flex items-center gap-3 relative z-10", children: [_jsx("div", { className: "w-10 h-10 bg-white/20 rounded-full flex items-center justify-center backdrop-blur-sm", children: _jsx(Bot, { className: "w-5 h-5 text-white" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-lg", children: config.title }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-2 h-2 bg-green-400 rounded-full animate-pulse" }), _jsxs("p", { className: "text-xs text-emerald-100", children: ["Online \u2022 ", config.subtitle] })] })] })] }), _jsx("button", { onClick: handleCloseWidget, className: "hover:bg-white/20 rounded-lg p-2 transition-all duration-200 hover:scale-110 relative z-10", children: _jsx(X, { className: "w-5 h-5" }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 bg-gradient-to-b from-gray-50 to-gray-100", children: [messages.map((message, index) => (_jsxs("div", { className: `flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} animate-fadeIn`, style: { animationDelay: `${index * 0.1}s` }, children: [message.role === 'assistant' && (_jsx("div", { className: "w-8 h-8 bg-emerald-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1 mr-2", children: _jsx(Bot, { className: "w-4 h-4 text-emerald-600" }) })), _jsxs("div", { className: "flex flex-col max-w-[75%]", children: [_jsx("div", { className: `rounded-2xl px-4 py-3 shadow-sm transition-all duration-200 hover:shadow-md ${message.role === 'user'
267
+ ? 'text-white'
268
+ : 'bg-white text-gray-900 border border-gray-200'}`, style: {
269
+ backgroundColor: message.role === 'user' ? config.themeColor : undefined,
270
+ boxShadow: message.role === 'user'
271
+ ? `0 4px 12px ${config.themeColor}4D` // 4D is ~30% opacity
272
+ : '0 2px 8px rgba(0, 0, 0, 0.1)'
273
+ }, children: _jsx("p", { className: "text-sm whitespace-pre-wrap break-words leading-relaxed", children: message.content }) }), _jsxs("div", { className: "flex items-center justify-between mt-1", children: [_jsx("p", { className: `text-xs ${message.role === 'user' ? 'text-emerald-100' : 'text-gray-500'}`, children: formatTimestamp(message.timestamp) }), message.role === 'assistant' && (_jsxs("div", { className: "flex items-center gap-1 ml-2", children: [_jsx("button", { onClick: () => handleFeedback(message.id, 'like'), className: `p-1 rounded transition-all duration-200 hover:scale-110 cursor-pointer ${messageFeedback[message.id] === 'like'
274
+ ? 'text-green-600'
275
+ : 'text-gray-400 hover:text-green-600'}`, children: _jsx(ThumbsUp, { className: `w-4 h-4 ${messageFeedback[message.id] === 'like' ? 'fill-current' : ''}` }) }), _jsx("button", { onClick: () => handleFeedback(message.id, 'dislike'), className: `p-1 rounded transition-all duration-200 hover:scale-110 cursor-pointer ${messageFeedback[message.id] === 'dislike'
276
+ ? 'text-red-600'
277
+ : 'text-gray-400 hover:text-red-600'}`, children: _jsx(ThumbsDown, { className: `w-4 h-4 ${messageFeedback[message.id] === 'dislike' ? 'fill-current' : ''}` }) })] }))] })] })] }, message.id))), streamingMessage && (_jsxs("div", { className: "flex justify-start animate-fadeIn", children: [_jsx("div", { className: "w-8 h-8 bg-emerald-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1 mr-2", children: _jsx(Bot, { className: "w-4 h-4 text-emerald-600" }) }), _jsx("div", { className: "flex flex-col max-w-[75%]", children: _jsx("div", { className: "bg-white text-gray-900 border border-gray-200 rounded-2xl px-4 py-3 shadow-sm", children: _jsxs("p", { className: "text-sm whitespace-pre-wrap break-words leading-relaxed", style: { color: '#111827' }, children: [streamingMessage, _jsx("span", { style: { display: 'inline-block', animation: 'vezloCaretBlink 1s steps(1, end) infinite' }, children: "|" })] }) }) })] })), isLoading && (_jsx("div", { className: "flex justify-start animate-fadeIn", children: _jsx("div", { className: "bg-white border border-gray-200 rounded-2xl px-4 py-3 flex items-center gap-3 shadow-sm", children: _jsxs("div", { className: "flex gap-1", style: { display: 'flex', gap: '4px' }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: 9999, backgroundColor: config.themeColor || '#10b981', display: 'inline-block', animation: 'vezloDotPulse 1s infinite ease-in-out', animationDelay: '0s' } }), _jsx("span", { style: { width: 8, height: 8, borderRadius: 9999, backgroundColor: config.themeColor || '#10b981', display: 'inline-block', animation: 'vezloDotPulse 1s infinite ease-in-out', animationDelay: '0.15s' } }), _jsx("span", { style: { width: 8, height: 8, borderRadius: 9999, backgroundColor: config.themeColor || '#10b981', display: 'inline-block', animation: 'vezloDotPulse 1s infinite ease-in-out', animationDelay: '0.3s' } })] }) }) })), _jsx("div", { ref: messagesEndRef })] }), _jsx("div", { className: "border-t border-gray-200 p-4 bg-white", children: _jsxs("div", { className: "flex gap-3", children: [_jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), onKeyPress: handleKeyPress, placeholder: config.placeholder, disabled: isLoading, className: "flex-1 px-4 py-3 border border-gray-300 rounded-2xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed text-sm transition-all duration-200" }), _jsx("button", { onClick: handleSendMessage, disabled: !input.trim() || isLoading, className: "text-white px-4 py-3 rounded-2xl hover:from-blue-700 hover:to-blue-800 transition-all duration-200 disabled:bg-gray-300 disabled:cursor-not-allowed flex items-center justify-center shadow-lg hover:shadow-xl disabled:shadow-none transform hover:scale-105 disabled:scale-100", style: { background: `linear-gradient(to right, ${config.themeColor}, ${config.themeColor}dd)` }, children: _jsx(Send, { className: "w-4 h-4" }) })] }) }), _jsx("div", { className: "border-t border-gray-200 px-4 py-3 bg-gradient-to-r from-gray-50 to-gray-100", children: _jsx(VezloFooter, { size: "sm" }) })] }))] }));
278
+ if (useShadowRoot) {
279
+ // Ensure host exists in DOM
280
+ return (_jsx("div", { ref: hostRef, style: { all: 'initial' }, children: shadowReady && shadowMountRef.current ? createPortal(content, shadowMountRef.current) : null }));
281
+ }
282
+ return content;
283
+ }
@@ -0,0 +1,5 @@
1
+ interface VezloFooterProps {
2
+ size?: 'sm' | 'md';
3
+ }
4
+ export declare function VezloFooter({ size }: VezloFooterProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { THEME } from '../../config/theme.js';
3
+ export function VezloFooter({ size = 'md' }) {
4
+ const iconSize = size === 'sm' ? 'w-3 h-3' : 'w-4 h-4';
5
+ const textSize = size === 'sm' ? 'text-xs' : 'text-sm';
6
+ return (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx("div", { className: `${iconSize} rounded-sm flex items-center justify-center`, style: { backgroundColor: THEME.primary.hex }, children: _jsx("span", { className: "text-white text-xs font-bold", children: "V" }) }), _jsxs("p", { className: `${textSize} text-gray-600`, children: ["Powered by ", _jsx("span", { className: "font-semibold", style: { color: THEME.primary.hex }, children: "Vezlo" })] })] }));
7
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Centralized theme configuration
3
+ * Change these values to update colors across the entire application
4
+ */
5
+ export declare const THEME: {
6
+ readonly primary: {
7
+ readonly hex: "#059669";
8
+ readonly tailwind: "emerald";
9
+ };
10
+ readonly colors: {
11
+ readonly bg: "bg-emerald-600";
12
+ readonly bgHover: "bg-emerald-700";
13
+ readonly bgLight: "bg-emerald-100";
14
+ readonly bgLighter: "bg-emerald-50";
15
+ readonly text: "text-emerald-600";
16
+ readonly textHover: "text-emerald-700";
17
+ readonly textLight: "text-emerald-100";
18
+ readonly border: "border-emerald-600";
19
+ readonly borderLight: "border-emerald-200";
20
+ };
21
+ };
22
+ export declare const getButtonGradient: (color?: string) => string;
23
+ export declare const getHeaderGradient: (color?: string) => string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Centralized theme configuration
3
+ * Change these values to update colors across the entire application
4
+ */
5
+ export const THEME = {
6
+ // Primary brand color (emerald)
7
+ primary: {
8
+ hex: '#059669',
9
+ tailwind: 'emerald',
10
+ },
11
+ // Tailwind color variants
12
+ colors: {
13
+ bg: 'bg-emerald-600',
14
+ bgHover: 'bg-emerald-700',
15
+ bgLight: 'bg-emerald-100',
16
+ bgLighter: 'bg-emerald-50',
17
+ text: 'text-emerald-600',
18
+ textHover: 'text-emerald-700',
19
+ textLight: 'text-emerald-100',
20
+ border: 'border-emerald-600',
21
+ borderLight: 'border-emerald-200',
22
+ },
23
+ };
24
+ // Helper function to get gradient for buttons
25
+ export const getButtonGradient = (color = THEME.primary.hex) => {
26
+ return `linear-gradient(to right, ${color}, ${color}dd)`;
27
+ };
28
+ // Helper function to get header gradient
29
+ export const getHeaderGradient = (color = THEME.primary.hex) => {
30
+ return `linear-gradient(to right, ${color}, ${color}dd, ${color}bb)`;
31
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { Widget } from './components/Widget.js';
2
+ export type { WidgetProps } from './components/Widget.js';
3
+ export type { WidgetConfig, ChatMessage, ChatSource, ChatState, AssistantServerResponse } from './types/index.js';
4
+ export { generateId, formatTimestamp, validateWidgetConfig, parseSize, getEnvConfig, debugLog } from './utils/index.js';
5
+ export { THEME } from './config/theme.js';
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // Export the main Widget component for library usage
2
+ export { Widget } from './components/Widget.js';
3
+ // Export utilities
4
+ export { generateId, formatTimestamp, validateWidgetConfig, parseSize, getEnvConfig, debugLog } from './utils/index.js';
5
+ // Export theme
6
+ export { THEME } from './config/theme.js';
@@ -0,0 +1,54 @@
1
+ export interface WidgetConfig {
2
+ uuid: string;
3
+ theme: 'light' | 'dark';
4
+ position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
5
+ size: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ title: string;
10
+ subtitle?: string;
11
+ placeholder?: string;
12
+ welcomeMessage?: string;
13
+ apiUrl: string;
14
+ apiKey: string;
15
+ themeColor?: string;
16
+ defaultOpen?: boolean;
17
+ }
18
+ export interface ChatMessage {
19
+ id: string;
20
+ content: string;
21
+ role: 'user' | 'assistant';
22
+ timestamp: Date;
23
+ sources?: ChatSource[];
24
+ }
25
+ export interface ChatSource {
26
+ title: string;
27
+ url?: string;
28
+ content?: string;
29
+ }
30
+ export interface ChatState {
31
+ messages: ChatMessage[];
32
+ isLoading: boolean;
33
+ error?: string;
34
+ isOpen: boolean;
35
+ }
36
+ export interface AssistantServerResponse {
37
+ message: string;
38
+ sources?: ChatSource[];
39
+ conversationId?: string;
40
+ }
41
+ export interface WidgetProps {
42
+ config: WidgetConfig;
43
+ onMessage?: (message: ChatMessage) => void;
44
+ onError?: (error: string) => void;
45
+ }
46
+ export interface EnvConfig {
47
+ VITE_ASSISTANT_SERVER_URL: string;
48
+ VITE_ASSISTANT_SERVER_API_KEY: string;
49
+ VITE_WIDGET_DEFAULT_THEME: 'light' | 'dark';
50
+ VITE_WIDGET_DEFAULT_POSITION: string;
51
+ VITE_WIDGET_DEFAULT_SIZE: string;
52
+ VITE_DEV_MODE: boolean;
53
+ VITE_DEBUG_MODE: boolean;
54
+ }
@@ -0,0 +1,2 @@
1
+ // Core types for the assistant chat widget
2
+ export {};
@@ -0,0 +1,19 @@
1
+ import { type ClassValue } from 'clsx';
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ export declare function generateId(): string;
4
+ export declare function formatTimestamp(date: Date): string;
5
+ export declare function validateWidgetConfig(config: any): boolean;
6
+ export declare function parseSize(sizeString: string): {
7
+ width: number;
8
+ height: number;
9
+ };
10
+ export declare function getEnvConfig(): {
11
+ VITE_ASSISTANT_SERVER_URL: any;
12
+ VITE_ASSISTANT_SERVER_API_KEY: any;
13
+ VITE_WIDGET_DEFAULT_THEME: "light" | "dark";
14
+ VITE_WIDGET_DEFAULT_POSITION: any;
15
+ VITE_WIDGET_DEFAULT_SIZE: any;
16
+ VITE_DEV_MODE: boolean;
17
+ VITE_DEBUG_MODE: boolean;
18
+ };
19
+ export declare function debugLog(message: string, data?: any): void;
@@ -0,0 +1,47 @@
1
+ import { clsx } from 'clsx';
2
+ // Utility function for conditional CSS classes
3
+ export function cn(...inputs) {
4
+ return clsx(inputs);
5
+ }
6
+ // Generate unique IDs
7
+ export function generateId() {
8
+ return Math.random().toString(36).substr(2, 9);
9
+ }
10
+ // Format timestamp
11
+ export function formatTimestamp(date) {
12
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
13
+ }
14
+ // Validate widget configuration
15
+ export function validateWidgetConfig(config) {
16
+ return (config &&
17
+ typeof config.uuid === 'string' &&
18
+ typeof config.apiUrl === 'string' &&
19
+ typeof config.apiKey === 'string' &&
20
+ typeof config.title === 'string');
21
+ }
22
+ // Parse size string (e.g., "350x500") to object
23
+ export function parseSize(sizeString) {
24
+ const [width, height] = sizeString.split('x').map(Number);
25
+ return {
26
+ width: width || 350,
27
+ height: height || 500
28
+ };
29
+ }
30
+ // Get environment configuration
31
+ export function getEnvConfig() {
32
+ return {
33
+ VITE_ASSISTANT_SERVER_URL: import.meta.env.VITE_ASSISTANT_SERVER_URL || 'http://localhost:3000',
34
+ VITE_ASSISTANT_SERVER_API_KEY: import.meta.env.VITE_ASSISTANT_SERVER_API_KEY || '',
35
+ VITE_WIDGET_DEFAULT_THEME: import.meta.env.VITE_WIDGET_DEFAULT_THEME || 'light',
36
+ VITE_WIDGET_DEFAULT_POSITION: import.meta.env.VITE_WIDGET_DEFAULT_POSITION || 'bottom-right',
37
+ VITE_WIDGET_DEFAULT_SIZE: import.meta.env.VITE_WIDGET_DEFAULT_SIZE || '350x500',
38
+ VITE_DEV_MODE: import.meta.env.VITE_DEV_MODE === 'true',
39
+ VITE_DEBUG_MODE: import.meta.env.VITE_DEBUG_MODE === 'true'
40
+ };
41
+ }
42
+ // Debug logging
43
+ export function debugLog(message, data) {
44
+ if (getEnvConfig().VITE_DEBUG_MODE) {
45
+ console.log(`[VezloChat] ${message}`, data);
46
+ }
47
+ }
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@vezlo/assistant-chat",
3
+ "version": "1.0.0",
4
+ "description": "React component library for AI-powered chat widgets with RAG knowledge base integration and real-time streaming",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "module": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./lib/index.js",
12
+ "types": "./lib/index.d.ts"
13
+ },
14
+ "./widget.js": "./public/widget.js"
15
+ },
16
+ "files": [
17
+ "lib",
18
+ "public/widget.js",
19
+ "PACKAGE_README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "dev": "vite",
24
+ "build": "tsc -b && npm run build:types && vite build",
25
+ "build:types": "tsc -p tsconfig.lib.json",
26
+ "lint": "eslint .",
27
+ "preview": "vite preview --port 5173 --host 0.0.0.0",
28
+ "clean": "rm -rf dist",
29
+ "start": "npm run preview",
30
+ "vercel-build": "npm run build",
31
+ "prepack": "npm run build"
32
+ },
33
+ "dependencies": {
34
+ "clsx": "^2.1.1",
35
+ "lucide-react": "^0.544.0",
36
+ "react-router-dom": "^7.9.3",
37
+ "tailwindcss": "^4.1.14"
38
+ },
39
+ "devDependencies": {
40
+ "@eslint/js": "^9.36.0",
41
+ "@tailwindcss/postcss": "^4.1.14",
42
+ "@tailwindcss/typography": "^0.5.19",
43
+ "@types/node": "^24.6.2",
44
+ "@types/react": "^19.1.16",
45
+ "@types/react-dom": "^19.1.9",
46
+ "@types/react-router-dom": "^5.3.3",
47
+ "@vitejs/plugin-react": "^5.0.4",
48
+ "autoprefixer": "^10.4.21",
49
+ "eslint": "^9.36.0",
50
+ "eslint-plugin-react-hooks": "^5.2.0",
51
+ "eslint-plugin-react-refresh": "^0.4.22",
52
+ "globals": "^16.4.0",
53
+ "postcss": "^8.5.6",
54
+ "typescript": "~5.9.3",
55
+ "typescript-eslint": "^8.45.0",
56
+ "vite": "^7.1.7"
57
+ },
58
+ "keywords": [
59
+ "ai-chatbot",
60
+ "chat-widget",
61
+ "chatbot",
62
+ "knowledge-base",
63
+ "rag",
64
+ "vector-search",
65
+ "embeddings",
66
+ "conversational-ai",
67
+ "llm",
68
+ "ai-assistant",
69
+ "saas-bot",
70
+ "customer-support",
71
+ "react-widget",
72
+ "embeddable-chat",
73
+ "semantic-search",
74
+ "ai-sdk",
75
+ "openai",
76
+ "supabase",
77
+ "pgvector",
78
+ "typescript"
79
+ ],
80
+ "author": "Vezlo",
81
+ "license": "AGPL-3.0",
82
+ "peerDependencies": {
83
+ "react": ">=18",
84
+ "react-dom": ">=18"
85
+ },
86
+ "publishConfig": {
87
+ "access": "public"
88
+ },
89
+ "repository": {
90
+ "type": "git",
91
+ "url": "git+https://github.com/vezlo/assistant-chat.git"
92
+ },
93
+ "homepage": "https://github.com/vezlo/assistant-chat#readme",
94
+ "bugs": {
95
+ "url": "https://github.com/vezlo/assistant-chat/issues"
96
+ },
97
+ "engines": {
98
+ "node": ">=20.0.0",
99
+ "npm": ">=9.0.0"
100
+ },
101
+ "readme": "PACKAGE_README.md"
102
+ }