Skip to content

Frontend Integration

Rumen Damyanov edited this page Jul 31, 2025 · 1 revision

Frontend Integration

Guide to integrating chat UI components with modern JavaScript frameworks.

Table of Contents

Overview

PHP Chatbot provides flexible frontend integration options for modern web applications. Choose from pre-built components for popular frameworks or create your own custom implementation.

Frontend Options

Framework Component Type TypeScript Styling Best For
React Functional + Hooks CSS Modules, Styled Components Modern React apps
Vue Composition API Scoped CSS, CSS-in-JS Vue 3 applications
Angular Standalone Components Angular Material, SCSS Enterprise applications
Vanilla JS ES6 Classes CSS, SCSS Any web application

Quick Start

  1. Copy component files to your project
  2. Install dependencies (if any)
  3. Configure API endpoint
  4. Customize styling as needed
  5. Initialize component in your app

React Components

Basic React Chatbot Component

Create components/Chatbot.jsx:

import React, { useState, useRef, useEffect } from 'react';
import './Chatbot.css';

const Chatbot = ({ 
  apiUrl = '/api/chatbot/message',
  title = 'Chat Support',
  greeting = 'Hello! How can I help you?',
  placeholder = 'Type your message...',
  position = 'bottom-right'
}) => {
  const [isOpen, setIsOpen] = useState(false);
  const [messages, setMessages] = useState([
    { id: 1, text: greeting, sender: 'bot', timestamp: Date.now() }
  ]);
  const [inputValue, setInputValue] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [conversationId] = useState(() => 
    `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  );
  
  const messagesEndRef = useRef(null);
  const inputRef = useRef(null);
  
  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };
  
  useEffect(() => {
    scrollToBottom();
  }, [messages]);
  
  useEffect(() => {
    if (isOpen && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isOpen]);
  
  const sendMessage = async () => {
    if (!inputValue.trim() || isLoading) return;
    
    const userMessage = {
      id: Date.now(),
      text: inputValue,
      sender: 'user',
      timestamp: Date.now()
    };
    
    setMessages(prev => [...prev, userMessage]);
    setInputValue('');
    setIsLoading(true);
    
    try {
      const response = await fetch(apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content,
        },
        body: JSON.stringify({
          message: userMessage.text,
          conversation_id: conversationId,
        }),
      });
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const data = await response.json();
      
      const botMessage = {
        id: Date.now() + 1,
        text: data.reply || 'Sorry, I encountered an error.',
        sender: 'bot',
        timestamp: Date.now()
      };
      
      setMessages(prev => [...prev, botMessage]);
      
    } catch (error) {
      console.error('Chatbot error:', error);
      
      const errorMessage = {
        id: Date.now() + 1,
        text: 'Sorry, I\'m having trouble connecting. Please try again.',
        sender: 'bot',
        timestamp: Date.now()
      };
      
      setMessages(prev => [...prev, errorMessage]);
    } finally {
      setIsLoading(false);
    }
  };
  
  const handleKeyPress = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  };
  
  const toggleChat = () => {
    setIsOpen(!isOpen);
  };
  
  return (
    <div className={`chatbot-container chatbot-${position}`}>
      {isOpen && (
        <div className="chatbot-popup">
          <div className="chatbot-header">
            <h4>{title}</h4>
            <button 
              className="chatbot-close"
              onClick={toggleChat}
              aria-label="Close chat"
            >
              ×
            </button>
          </div>
          
          <div className="chatbot-messages">
            {messages.map((message) => (
              <div
                key={message.id}
                className={`chatbot-message ${message.sender}-message`}
              >
                <div className="message-content">
                  {message.text}
                </div>
                <div className="message-time">
                  {new Date(message.timestamp).toLocaleTimeString([], {
                    hour: '2-digit',
                    minute: '2-digit'
                  })}
                </div>
              </div>
            ))}
            
            {isLoading && (
              <div className="chatbot-message bot-message">
                <div className="message-content">
                  <div className="typing-indicator">
                    <span></span>
                    <span></span>
                    <span></span>
                  </div>
                </div>
              </div>
            )}
            
            <div ref={messagesEndRef} />
          </div>
          
          <div className="chatbot-input">
            <input
              ref={inputRef}
              type="text"
              value={inputValue}
              onChange={(e) => setInputValue(e.target.value)}
              onKeyPress={handleKeyPress}
              placeholder={placeholder}
              disabled={isLoading}
              maxLength={2000}
            />
            <button
              onClick={sendMessage}
              disabled={!inputValue.trim() || isLoading}
              className="send-button"
              aria-label="Send message"
            >
              <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
                <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
              </svg>
            </button>
          </div>
        </div>
      )}
      
      <button
        className="chatbot-toggle"
        onClick={toggleChat}
        aria-label={isOpen ? "Close chat" : "Open chat"}
      >
        {isOpen ? '×' : '💬'}
      </button>
    </div>
  );
};

export default Chatbot;

React with TypeScript

Create components/Chatbot.tsx:

import React, { useState, useRef, useEffect, useCallback } from 'react';
import './Chatbot.css';

interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

interface ChatbotProps {
  apiUrl?: string;
  title?: string;
  greeting?: string;
  placeholder?: string;
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  theme?: 'light' | 'dark';
  maxMessages?: number;
  onMessageSent?: (message: string) => void;
  onMessageReceived?: (message: string) => void;
}

interface ApiResponse {
  reply: string;
  conversation_id?: string;
  timestamp?: string;
  error?: string;
}

const Chatbot: React.FC<ChatbotProps> = ({
  apiUrl = '/api/chatbot/message',
  title = 'Chat Support',
  greeting = 'Hello! How can I help you?',
  placeholder = 'Type your message...',
  position = 'bottom-right',
  theme = 'light',
  maxMessages = 100,
  onMessageSent,
  onMessageReceived
}) => {
  const [isOpen, setIsOpen] = useState<boolean>(false);
  const [messages, setMessages] = useState<Message[]>([
    { 
      id: 1, 
      text: greeting, 
      sender: 'bot', 
      timestamp: Date.now() 
    }
  ]);
  const [inputValue, setInputValue] = useState<string>('');
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  
  const conversationId = useRef<string>(
    `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  );
  
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  
  const scrollToBottom = useCallback(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, []);
  
  useEffect(() => {
    scrollToBottom();
  }, [messages, scrollToBottom]);
  
  useEffect(() => {
    if (isOpen && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isOpen]);
  
  const addMessage = useCallback((text: string, sender: 'user' | 'bot') => {
    const newMessage: Message = {
      id: Date.now() + Math.random(),
      text,
      sender,
      timestamp: Date.now()
    };
    
    setMessages(prev => {
      const updated = [...prev, newMessage];
      // Limit message history
      return updated.slice(-maxMessages);
    });
    
    if (sender === 'user' && onMessageSent) {
      onMessageSent(text);
    } else if (sender === 'bot' && onMessageReceived) {
      onMessageReceived(text);
    }
  }, [maxMessages, onMessageSent, onMessageReceived]);
  
  const sendMessage = useCallback(async () => {
    if (!inputValue.trim() || isLoading) return;
    
    const messageText = inputValue.trim();
    setInputValue('');
    setError(null);
    
    addMessage(messageText, 'user');
    setIsLoading(true);
    
    try {
      const csrfToken = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
      
      const response = await fetch(apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(csrfToken && { 'X-CSRF-TOKEN': csrfToken }),
        },
        body: JSON.stringify({
          message: messageText,
          conversation_id: conversationId.current,
        }),
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const data: ApiResponse = await response.json();
      
      if (data.error) {
        throw new Error(data.error);
      }
      
      addMessage(data.reply || 'I received your message but couldn\'t generate a response.', 'bot');
      
    } catch (error) {
      console.error('Chatbot error:', error);
      
      const errorMessage = error instanceof Error 
        ? `Error: ${error.message}`
        : 'Sorry, I\'m having trouble connecting. Please try again.';
      
      addMessage(errorMessage, 'bot');
      setError(errorMessage);
    } finally {
      setIsLoading(false);
    }
  }, [inputValue, isLoading, apiUrl, addMessage]);
  
  const handleKeyPress = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  }, [sendMessage]);
  
  const toggleChat = useCallback(() => {
    setIsOpen(prev => !prev);
    setError(null);
  }, []);
  
  const clearMessages = useCallback(() => {
    setMessages([{ 
      id: 1, 
      text: greeting, 
      sender: 'bot', 
      timestamp: Date.now() 
    }]);
    setError(null);
  }, [greeting]);
  
  return (
    <div className={`chatbot-container chatbot-${position} chatbot-theme-${theme}`}>
      {isOpen && (
        <div className="chatbot-popup">
          <div className="chatbot-header">
            <h4>{title}</h4>
            <div className="chatbot-header-actions">
              <button
                className="chatbot-clear"
                onClick={clearMessages}
                aria-label="Clear conversation"
                title="Clear conversation"
              >
                🗑️
              </button>
              <button 
                className="chatbot-close"
                onClick={toggleChat}
                aria-label="Close chat"
              >
                ×
              </button>
            </div>
          </div>
          
          {error && (
            <div className="chatbot-error">
              <span>⚠️ {error}</span>
              <button onClick={() => setError(null)}>×</button>
            </div>
          )}
          
          <div className="chatbot-messages">
            {messages.map((message) => (
              <div
                key={message.id}
                className={`chatbot-message ${message.sender}-message`}
              >
                <div className="message-content">
                  {message.text}
                </div>
                <div className="message-time">
                  {new Date(message.timestamp).toLocaleTimeString([], {
                    hour: '2-digit',
                    minute: '2-digit'
                  })}
                </div>
              </div>
            ))}
            
            {isLoading && (
              <div className="chatbot-message bot-message">
                <div className="message-content">
                  <div className="typing-indicator">
                    <span></span>
                    <span></span>
                    <span></span>
                  </div>
                </div>
              </div>
            )}
            
            <div ref={messagesEndRef} />
          </div>
          
          <div className="chatbot-input">
            <input
              ref={inputRef}
              type="text"
              value={inputValue}
              onChange={(e) => setInputValue(e.target.value)}
              onKeyPress={handleKeyPress}
              placeholder={placeholder}
              disabled={isLoading}
              maxLength={2000}
              aria-label="Type your message"
            />
            <button
              onClick={sendMessage}
              disabled={!inputValue.trim() || isLoading}
              className="send-button"
              aria-label="Send message"
            >
              <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
                <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
              </svg>
            </button>
          </div>
        </div>
      )}
      
      <button
        className="chatbot-toggle"
        onClick={toggleChat}
        aria-label={isOpen ? "Close chat" : "Open chat"}
      >
        {isOpen ? '×' : '💬'}
      </button>
    </div>
  );
};

export default Chatbot;

React Hooks for Chatbot Logic

Create hooks/useChatbot.ts:

import { useState, useRef, useCallback, useEffect } from 'react';

interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

interface UseChatbotOptions {
  apiUrl: string;
  greeting?: string;
  maxMessages?: number;
  onError?: (error: Error) => void;
}

export const useChatbot = ({
  apiUrl,
  greeting = 'Hello! How can I help you?',
  maxMessages = 100,
  onError
}: UseChatbotOptions) => {
  const [messages, setMessages] = useState<Message[]>([
    { id: 1, text: greeting, sender: 'bot', timestamp: Date.now() }
  ]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  
  const conversationId = useRef(
    `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  );
  
  const addMessage = useCallback((text: string, sender: 'user' | 'bot') => {
    const newMessage: Message = {
      id: Date.now() + Math.random(),
      text,
      sender,
      timestamp: Date.now()
    };
    
    setMessages(prev => {
      const updated = [...prev, newMessage];
      return updated.slice(-maxMessages);
    });
  }, [maxMessages]);
  
  const sendMessage = useCallback(async (text: string) => {
    if (!text.trim() || isLoading) return;
    
    addMessage(text, 'user');
    setIsLoading(true);
    setError(null);
    
    try {
      const response = await fetch(apiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: text,
          conversation_id: conversationId.current,
        }),
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      const data = await response.json();
      
      if (data.error) {
        throw new Error(data.error);
      }
      
      addMessage(data.reply || 'No response received.', 'bot');
      
    } catch (err) {
      const error = err instanceof Error ? err : new Error('Unknown error');
      setError(error.message);
      addMessage('Sorry, I encountered an error.', 'bot');
      
      if (onError) {
        onError(error);
      }
    } finally {
      setIsLoading(false);
    }
  }, [apiUrl, isLoading, addMessage, onError]);
  
  const clearMessages = useCallback(() => {
    setMessages([{ id: 1, text: greeting, sender: 'bot', timestamp: Date.now() }]);
    setError(null);
  }, [greeting]);
  
  return {
    messages,
    isLoading,
    error,
    sendMessage,
    clearMessages,
    conversationId: conversationId.current
  };
};

Usage Examples

Basic Usage

import React from 'react';
import Chatbot from './components/Chatbot';

function App() {
  return (
    <div className="App">
      <h1>My Website</h1>
      <p>Welcome to my website!</p>
      
      <Chatbot 
        apiUrl="/api/chatbot/message"
        title="Customer Support"
        greeting="Hi! How can I help you today?"
      />
    </div>
  );
}

export default App;

Advanced Usage with Custom Handlers

import React from 'react';
import Chatbot from './components/Chatbot';

function App() {
  const handleMessageSent = (message: string) => {
    console.log('User sent:', message);
    // Analytics tracking
    gtag('event', 'chatbot_message_sent', {
      message_length: message.length
    });
  };
  
  const handleMessageReceived = (message: string) => {
    console.log('Bot replied:', message);
    // Analytics tracking
    gtag('event', 'chatbot_message_received');
  };
  
  return (
    <div className="App">
      <Chatbot 
        apiUrl="/api/chatbot/message"
        title="AI Assistant"
        greeting="Hello! I'm your AI assistant. What can I help you with?"
        placeholder="Ask me anything..."
        position="bottom-right"
        theme="dark"
        maxMessages={50}
        onMessageSent={handleMessageSent}
        onMessageReceived={handleMessageReceived}
      />
    </div>
  );
}

Next.js Integration

// pages/_app.tsx
import type { AppProps } from 'next/app';
import dynamic from 'next/dynamic';

const Chatbot = dynamic(() => import('../components/Chatbot'), {
  ssr: false // Disable server-side rendering for chatbot
});

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <Chatbot 
        apiUrl="/api/chatbot"
        title="Help Center"
      />
    </>
  );
}

// pages/api/chatbot.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }
  
  try {
    // Your chatbot logic here
    const { message } = req.body;
    
    // Call your PHP backend or implement logic directly
    const response = await fetch('http://localhost:8000/api/chatbot', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message })
    });
    
    const data = await response.json();
    res.status(200).json(data);
    
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
}

Vue Components

Basic Vue 3 Composition API Component

Create components/Chatbot.vue:

<template>
  <div :class="containerClasses">
    <!-- Chat Popup -->
    <Transition name="slide-up">
      <div v-if="isOpen" class="chatbot-popup">
        <!-- Header -->
        <div class="chatbot-header">
          <h4>{{ title }}</h4>
          <div class="chatbot-header-actions">
            <button
              @click="clearMessages"
              class="chatbot-clear"
              aria-label="Clear conversation"
              title="Clear conversation"
            >
              🗑️
            </button>
            <button 
              @click="toggleChat"
              class="chatbot-close"
              aria-label="Close chat"
            >
              ×
            </button>
          </div>
        </div>
        
        <!-- Error Display -->
        <div v-if="error" class="chatbot-error">
          <span>⚠️ {{ error }}</span>
          <button @click="clearError">×</button>
        </div>
        
        <!-- Messages -->
        <div ref="messagesContainer" class="chatbot-messages">
          <TransitionGroup name="message" tag="div">
            <div
              v-for="message in messages"
              :key="message.id"
              :class="getMessageClasses(message)"
            >
              <div class="message-content">
                {{ message.text }}
              </div>
              <div class="message-time">
                {{ formatTime(message.timestamp) }}
              </div>
            </div>
          </TransitionGroup>
          
          <!-- Typing Indicator -->
          <div v-if="isLoading" class="chatbot-message bot-message">
            <div class="message-content">
              <div class="typing-indicator">
                <span></span>
                <span></span>
                <span></span>
              </div>
            </div>
          </div>
        </div>
        
        <!-- Input -->
        <div class="chatbot-input">
          <input
            ref="messageInput"
            v-model="inputValue"
            @keydown.enter.prevent="sendMessage"
            :placeholder="placeholder"
            :disabled="isLoading"
            maxlength="2000"
            type="text"
            aria-label="Type your message"
          />
          <button
            @click="sendMessage"
            :disabled="!canSendMessage"
            class="send-button"
            aria-label="Send message"
          >
            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
              <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
            </svg>
          </button>
        </div>
      </div>
    </Transition>
    
    <!-- Toggle Button -->
    <button
      @click="toggleChat"
      class="chatbot-toggle"
      :aria-label="isOpen ? 'Close chat' : 'Open chat'"
    >
      {{ isOpen ? '×' : '💬' }}
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, nextTick, watch, onMounted } from 'vue';

// Props
interface Props {
  apiUrl?: string;
  title?: string;
  greeting?: string;
  placeholder?: string;
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  theme?: 'light' | 'dark';
  maxMessages?: number;
}

const props = withDefaults(defineProps<Props>(), {
  apiUrl: '/api/chatbot/message',
  title: 'Chat Support',
  greeting: 'Hello! How can I help you?',
  placeholder: 'Type your message...',
  position: 'bottom-right',
  theme: 'light',
  maxMessages: 100
});

// Emits
interface Emits {
  messageSent: [message: string];
  messageReceived: [message: string];
  error: [error: Error];
}

const emit = defineEmits<Emits>();

// Types
interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

// Reactive state
const isOpen = ref(false);
const inputValue = ref('');
const isLoading = ref(false);
const error = ref<string | null>(null);
const messagesContainer = ref<HTMLElement>();
const messageInput = ref<HTMLInputElement>();

const conversationId = ref(
  `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
);

const messages = ref<Message[]>([
  {
    id: 1,
    text: props.greeting,
    sender: 'bot',
    timestamp: Date.now()
  }
]);

// Computed
const containerClasses = computed(() => [
  'chatbot-container',
  `chatbot-${props.position}`,
  `chatbot-theme-${props.theme}`
]);

const canSendMessage = computed(() => 
  inputValue.value.trim() && !isLoading.value
);

// Methods
const addMessage = (text: string, sender: 'user' | 'bot') => {
  const newMessage: Message = {
    id: Date.now() + Math.random(),
    text,
    sender,
    timestamp: Date.now()
  };
  
  messages.value.push(newMessage);
  
  // Limit message history
  if (messages.value.length > props.maxMessages) {
    messages.value = messages.value.slice(-props.maxMessages);
  }
  
  // Emit events
  if (sender === 'user') {
    emit('messageSent', text);
  } else {
    emit('messageReceived', text);
  }
};

const sendMessage = async () => {
  if (!canSendMessage.value) return;
  
  const messageText = inputValue.value.trim();
  inputValue.value = '';
  error.value = null;
  
  addMessage(messageText, 'user');
  isLoading.value = true;
  
  try {
    const csrfToken = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
    
    const response = await fetch(props.apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(csrfToken && { 'X-CSRF-TOKEN': csrfToken }),
      },
      body: JSON.stringify({
        message: messageText,
        conversation_id: conversationId.value,
      }),
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    const data = await response.json();
    
    if (data.error) {
      throw new Error(data.error);
    }
    
    addMessage(data.reply || 'No response received.', 'bot');
    
  } catch (err) {
    const errorMessage = err instanceof Error ? err.message : 'Unknown error';
    error.value = errorMessage;
    addMessage('Sorry, I encountered an error.', 'bot');
    emit('error', err instanceof Error ? err : new Error(errorMessage));
    
  } finally {
    isLoading.value = false;
  }
};

const toggleChat = () => {
  isOpen.value = !isOpen.value;
  error.value = null;
};

const clearMessages = () => {
  messages.value = [
    {
      id: 1,
      text: props.greeting,
      sender: 'bot',
      timestamp: Date.now()
    }
  ];
  error.value = null;
};

const clearError = () => {
  error.value = null;
};

const getMessageClasses = (message: Message) => [
  'chatbot-message',
  `${message.sender}-message`
];

const formatTime = (timestamp: number) => {
  return new Date(timestamp).toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit'
  });
};

const scrollToBottom = () => {
  nextTick(() => {
    if (messagesContainer.value) {
      messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
    }
  });
};

// Watchers
watch(messages, scrollToBottom, { deep: true });
watch(isOpen, (newValue) => {
  if (newValue && messageInput.value) {
    nextTick(() => {
      messageInput.value?.focus();
    });
  }
});

// Lifecycle
onMounted(() => {
  scrollToBottom();
});
</script>

<style scoped>
/* Transitions */
.slide-up-enter-active,
.slide-up-leave-active {
  transition: all 0.3s ease;
}

.slide-up-enter-from {
  opacity: 0;
  transform: translateY(20px) scale(0.95);
}

.slide-up-leave-to {
  opacity: 0;
  transform: translateY(20px) scale(0.95);
}

.message-enter-active {
  transition: all 0.3s ease;
}

.message-enter-from {
  opacity: 0;
  transform: translateY(10px);
}

/* Component styles - same as React version */
.chatbot-container {
  position: fixed;
  z-index: 1000;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}

.chatbot-bottom-right {
  bottom: 20px;
  right: 20px;
}

.chatbot-bottom-left {
  bottom: 20px;
  left: 20px;
}

.chatbot-top-right {
  top: 20px;
  right: 20px;
}

.chatbot-top-left {
  top: 20px;
  left: 20px;
}

.chatbot-popup {
  width: 350px;
  height: 500px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  margin-bottom: 10px;
  border: 1px solid #e1e5e9;
}

.chatbot-theme-dark .chatbot-popup {
  background: #2c2c2c;
  border-color: #404040;
  color: #ffffff;
}

.chatbot-header {
  padding: 16px 20px;
  background: #4f46e5;
  color: white;
  border-radius: 12px 12px 0 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.chatbot-header h4 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.chatbot-header-actions {
  display: flex;
  gap: 8px;
}

.chatbot-clear,
.chatbot-close {
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: white;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  transition: background-color 0.2s;
}

.chatbot-clear:hover,
.chatbot-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

.chatbot-error {
  background: #fee2e2;
  color: #991b1b;
  padding: 12px;
  margin: 12px;
  border-radius: 6px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 14px;
}

.chatbot-error button {
  background: none;
  border: none;
  color: #991b1b;
  cursor: pointer;
  font-size: 16px;
}

.chatbot-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.chatbot-message {
  display: flex;
  flex-direction: column;
  max-width: 80%;
}

.user-message {
  align-items: flex-end;
  align-self: flex-end;
}

.bot-message {
  align-items: flex-start;
  align-self: flex-start;
}

.message-content {
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.4;
  word-wrap: break-word;
}

.user-message .message-content {
  background: #4f46e5;
  color: white;
  border-bottom-right-radius: 4px;
}

.bot-message .message-content {
  background: #f3f4f6;
  color: #374151;
  border-bottom-left-radius: 4px;
}

.chatbot-theme-dark .bot-message .message-content {
  background: #404040;
  color: #e5e5e5;
}

.message-time {
  font-size: 11px;
  color: #9ca3af;
  margin-top: 4px;
}

.typing-indicator {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 4px 0;
}

.typing-indicator span {
  width: 6px;
  height: 6px;
  background: #9ca3af;
  border-radius: 50%;
  animation: typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) {
  animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes typing {
  0%, 60%, 100% {
    transform: translateY(0);
  }
  30% {
    transform: translateY(-10px);
  }
}

.chatbot-input {
  padding: 16px;
  border-top: 1px solid #e5e7eb;
  display: flex;
  gap: 8px;
  align-items: center;
}

.chatbot-theme-dark .chatbot-input {
  border-top-color: #404040;
}

.chatbot-input input {
  flex: 1;
  padding: 10px 12px;
  border: 1px solid #d1d5db;
  border-radius: 20px;
  font-size: 14px;
  outline: none;
  transition: border-color 0.2s;
}

.chatbot-input input:focus {
  border-color: #4f46e5;
}

.chatbot-theme-dark .chatbot-input input {
  background: #404040;
  border-color: #555;
  color: #ffffff;
}

.chatbot-input input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.send-button {
  width: 40px;
  height: 40px;
  background: #4f46e5;
  border: none;
  border-radius: 50%;
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.2s;
}

.send-button:hover:not(:disabled) {
  background: #4338ca;
}

.send-button:disabled {
  background: #9ca3af;
  cursor: not-allowed;
}

.chatbot-toggle {
  width: 60px;
  height: 60px;
  background: #4f46e5;
  border: none;
  border-radius: 50%;
  color: white;
  font-size: 24px;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(79, 70, 229, 0.3);
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chatbot-toggle:hover {
  background: #4338ca;
  transform: scale(1.05);
}

/* Mobile responsive */
@media (max-width: 480px) {
  .chatbot-popup {
    width: calc(100vw - 40px);
    height: calc(100vh - 100px);
    position: fixed;
    bottom: 80px;
    left: 20px;
    right: 20px;
  }
}
</style>

Vue 3 Composable for Chatbot Logic

Create composables/useChatbot.ts:

import { ref, computed, type Ref } from 'vue';

interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

interface UseChatbotOptions {
  apiUrl: string;
  greeting?: string;
  maxMessages?: number;
  onError?: (error: Error) => void;
}

export function useChatbot(options: UseChatbotOptions) {
  const {
    apiUrl,
    greeting = 'Hello! How can I help you?',
    maxMessages = 100,
    onError
  } = options;

  // State
  const messages: Ref<Message[]> = ref([
    { id: 1, text: greeting, sender: 'bot', timestamp: Date.now() }
  ]);
  const isLoading = ref(false);
  const error = ref<string | null>(null);
  const conversationId = ref(
    `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  );

  // Computed
  const lastMessage = computed(() => 
    messages.value[messages.value.length - 1] || null
  );

  const messageCount = computed(() => messages.value.length);

  // Methods
  const addMessage = (text: string, sender: 'user' | 'bot') => {
    const newMessage: Message = {
      id: Date.now() + Math.random(),
      text,
      sender,
      timestamp: Date.now()
    };

    messages.value.push(newMessage);

    // Limit message history
    if (messages.value.length > maxMessages) {
      messages.value = messages.value.slice(-maxMessages);
    }
  };

  const sendMessage = async (text: string): Promise<void> => {
    if (!text.trim() || isLoading.value) return;

    addMessage(text, 'user');
    isLoading.value = true;
    error.value = null;

    try {
      const response = await fetch(apiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: text,
          conversation_id: conversationId.value,
        }),
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const data = await response.json();

      if (data.error) {
        throw new Error(data.error);
      }

      addMessage(data.reply || 'No response received.', 'bot');

    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Unknown error';
      error.value = errorMessage;
      addMessage('Sorry, I encountered an error.', 'bot');

      if (onError && err instanceof Error) {
        onError(err);
      }
    } finally {
      isLoading.value = false;
    }
  };

  const clearMessages = () => {
    messages.value = [
      { id: 1, text: greeting, sender: 'bot', timestamp: Date.now() }
    ];
    error.value = null;
  };

  const clearError = () => {
    error.value = null;
  };

  return {
    // State
    messages: readonly(messages),
    isLoading: readonly(isLoading),
    error: readonly(error),
    conversationId: readonly(conversationId),

    // Computed
    lastMessage,
    messageCount,

    // Methods
    sendMessage,
    addMessage,
    clearMessages,
    clearError
  };
}

Vue Usage Examples

Basic Usage

<template>
  <div id="app">
    <h1>My Vue App</h1>
    <p>Welcome to my application!</p>
    
    <Chatbot
      api-url="/api/chatbot/message"
      title="Support Assistant"
      greeting="Hello! How can I assist you today?"
      position="bottom-right"
      theme="light"
      @message-sent="handleMessageSent"
      @message-received="handleMessageReceived"
      @error="handleError"
    />
  </div>
</template>

<script setup lang="ts">
import Chatbot from './components/Chatbot.vue';

const handleMessageSent = (message: string) => {
  console.log('User sent:', message);
};

const handleMessageReceived = (message: string) => {
  console.log('Bot replied:', message);
};

const handleError = (error: Error) => {
  console.error('Chatbot error:', error);
};
</script>

Advanced Usage with Composable

<template>
  <div class="chat-page">
    <div class="chat-header">
      <h2>Customer Support</h2>
      <p>Messages: {{ messageCount }}</p>
    </div>
    
    <div class="chat-messages">
      <div
        v-for="message in messages"
        :key="message.id"
        :class="['message', message.sender]"
      >
        {{ message.text }}
      </div>
    </div>
    
    <div class="chat-input">
      <input
        v-model="inputText"
        @keydown.enter="handleSend"
        placeholder="Type a message..."
        :disabled="isLoading"
      />
      <button @click="handleSend" :disabled="!inputText.trim() || isLoading">
        Send
      </button>
    </div>
    
    <div v-if="error" class="error">
      {{ error }}
      <button @click="clearError">×</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useChatbot } from '@/composables/useChatbot';

const inputText = ref('');

const {
  messages,
  isLoading,
  error,
  messageCount,
  sendMessage,
  clearError
} = useChatbot({
  apiUrl: '/api/chatbot/message',
  greeting: 'Welcome! How can I help you?',
  onError: (err) => {
    console.error('Chat error:', err);
  }
});

const handleSend = async () => {
  if (!inputText.value.trim()) return;
  
  await sendMessage(inputText.value);
  inputText.value = '';
};
</script>

Nuxt 3 Integration

<!-- components/Chatbot.client.vue -->
<template>
  <ClientOnly>
    <Chatbot
      :api-url="runtimeConfig.public.chatbotApiUrl"
      title="Help Center"
      :greeting="$t('chatbot.greeting')"
      :placeholder="$t('chatbot.placeholder')"
      @message-sent="trackMessageSent"
      @message-received="trackMessageReceived"
    />
  </ClientOnly>
</template>

<script setup lang="ts">
import Chatbot from './Chatbot.vue';

const runtimeConfig = useRuntimeConfig();
const { $gtag } = useNuxtApp();

const trackMessageSent = (message: string) => {
  $gtag('event', 'chatbot_message_sent', {
    message_length: message.length
  });
};

const trackMessageReceived = (message: string) => {
  $gtag('event', 'chatbot_message_received');
};
</script>

<!-- server/api/chatbot.post.ts -->
<script>
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  
  try {
    // Your chatbot logic here
    const response = await $fetch('http://localhost:8000/api/chatbot', {
      method: 'POST',
      body: {
        message: body.message,
        conversation_id: body.conversation_id
      }
    });
    
    return response;
  } catch (error) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Chatbot service unavailable'
    });
  }
});
</script>

<!-- nuxt.config.ts -->
<script>
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      chatbotApiUrl: process.env.CHATBOT_API_URL || '/api/chatbot'
    }
  }
});
</script>

Pinia Store Integration

// stores/chatbot.ts
import { defineStore } from 'pinia';
import type { Message } from '@/types/chatbot';

export const useChatbotStore = defineStore('chatbot', () => {
  const messages = ref<Message[]>([]);
  const isLoading = ref(false);
  const error = ref<string | null>(null);
  const isOpen = ref(false);
  
  const conversationHistory = computed(() => 
    messages.value.filter(m => m.sender === 'user')
  );
  
  const addMessage = (message: Message) => {
    messages.value.push(message);
  };
  
  const clearMessages = () => {
    messages.value = [];
  };
  
  const toggleChat = () => {
    isOpen.value = !isOpen.value;
  };
  
  return {
    messages: readonly(messages),
    isLoading: readonly(isLoading),
    error: readonly(error),
    isOpen: readonly(isOpen),
    conversationHistory,
    addMessage,
    clearMessages,
    toggleChat
  };
});

Angular Components

Angular Standalone Component

Create components/chatbot.component.ts:

import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewChecked } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { BehaviorSubject, Subject, catchError, of, takeUntil } from 'rxjs';

interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

interface ChatbotConfig {
  apiUrl: string;
  title: string;
  greeting: string;
  placeholder: string;
  position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  theme: 'light' | 'dark';
  maxMessages: number;
}

@Component({
  selector: 'app-chatbot',
  standalone: true,
  imports: [CommonModule, FormsModule, HttpClientModule],
  template: `
    <div [class]="containerClasses">
      <!-- Chat Popup -->
      <div *ngIf="isOpen" class="chatbot-popup" [@slideUp]>
        <!-- Header -->
        <div class="chatbot-header">
          <h4>{{ config.title }}</h4>
          <div class="chatbot-header-actions">
            <button
              (click)="clearMessages()"
              class="chatbot-clear"
              aria-label="Clear conversation"
              title="Clear conversation"
            >
              🗑️
            </button>
            <button 
              (click)="toggleChat()"
              class="chatbot-close"
              aria-label="Close chat"
            >
              ×
            </button>
          </div>
        </div>
        
        <!-- Error Display -->
        <div *ngIf="error$ | async as error" class="chatbot-error">
          <span>⚠️ {{ error }}</span>
          <button (click)="clearError()">×</button>
        </div>
        
        <!-- Messages -->
        <div #messagesContainer class="chatbot-messages">
          <div
            *ngFor="let message of messages$ | async; trackBy: trackMessage"
            [class]="getMessageClasses(message)"
            [@messageSlide]
          >
            <div class="message-content">
              {{ message.text }}
            </div>
            <div class="message-time">
              {{ formatTime(message.timestamp) }}
            </div>
          </div>
          
          <!-- Typing Indicator -->
          <div *ngIf="isLoading$ | async" class="chatbot-message bot-message">
            <div class="message-content">
              <div class="typing-indicator">
                <span></span>
                <span></span>
                <span></span>
              </div>
            </div>
          </div>
        </div>
        
        <!-- Input -->
        <div class="chatbot-input">
          <input
            #messageInput
            [(ngModel)]="inputValue"
            (keydown.enter)="sendMessage()"
            [placeholder]="config.placeholder"
            [disabled]="isLoading$ | async"
            maxlength="2000"
            type="text"
            aria-label="Type your message"
          />
          <button
            (click)="sendMessage()"
            [disabled]="!canSendMessage()"
            class="send-button"
            aria-label="Send message"
          >
            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
              <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
            </svg>
          </button>
        </div>
      </div>
      
      <!-- Toggle Button -->
      <button
        (click)="toggleChat()"
        class="chatbot-toggle"
        [attr.aria-label]="isOpen ? 'Close chat' : 'Open chat'"
      >
        {{ isOpen ? '×' : '💬' }}
      </button>
    </div>
  `,
  styleUrls: ['./chatbot.component.scss'],
  animations: [
    // Add your animations here
  ]
})
export class ChatbotComponent implements OnInit, OnDestroy, AfterViewChecked {
  @Input() config: Partial<ChatbotConfig> = {};
  
  @Output() messageSent = new EventEmitter<string>();
  @Output() messageReceived = new EventEmitter<string>();
  @Output() errorOccurred = new EventEmitter<Error>();
  
  @ViewChild('messagesContainer') messagesContainer!: ElementRef;
  @ViewChild('messageInput') messageInput!: ElementRef;

  // Default configuration
  private defaultConfig: ChatbotConfig = {
    apiUrl: '/api/chatbot/message',
    title: 'Chat Support',
    greeting: 'Hello! How can I help you?',
    placeholder: 'Type your message...',
    position: 'bottom-right',
    theme: 'light',
    maxMessages: 100
  };

  // Component state
  isOpen = false;
  inputValue = '';
  conversationId = `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

  // Reactive state
  private messages$ = new BehaviorSubject<Message[]>([]);
  private isLoading$ = new BehaviorSubject<boolean>(false);
  private error$ = new BehaviorSubject<string | null>(null);
  private destroy$ = new Subject<void>();
  private shouldScrollToBottom = false;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    // Initialize with greeting message
    const initialMessage: Message = {
      id: 1,
      text: this.config.greeting || this.defaultConfig.greeting,
      sender: 'bot',
      timestamp: Date.now()
    };
    
    this.messages$.next([initialMessage]);
  }

  ngAfterViewChecked() {
    if (this.shouldScrollToBottom) {
      this.scrollToBottom();
      this.shouldScrollToBottom = false;
    }
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  // Getters for reactive state
  get messages$() { return this.messages$.asObservable(); }
  get isLoading$() { return this.isLoading$.asObservable(); }
  get error$() { return this.error$.asObservable(); }

  // Computed properties
  get containerClasses(): string {
    const position = this.config.position || this.defaultConfig.position;
    const theme = this.config.theme || this.defaultConfig.theme;
    return `chatbot-container chatbot-${position} chatbot-theme-${theme}`;
  }

  get mergedConfig(): ChatbotConfig {
    return { ...this.defaultConfig, ...this.config };
  }

  // Methods
  canSendMessage(): boolean {
    return this.inputValue.trim().length > 0 && !this.isLoading$.value;
  }

  async sendMessage(): Promise<void> {
    if (!this.canSendMessage()) return;

    const messageText = this.inputValue.trim();
    this.inputValue = '';
    this.error$.next(null);

    this.addMessage(messageText, 'user');
    this.isLoading$.next(true);

    const payload = {
      message: messageText,
      conversation_id: this.conversationId
    };

    this.http.post<any>(this.mergedConfig.apiUrl, payload)
      .pipe(
        catchError(error => {
          console.error('Chatbot API error:', error);
          return of({ 
            error: error.status === 0 
              ? 'Network error. Please check your connection.' 
              : `HTTP ${error.status}: ${error.statusText}` 
          });
        }),
        takeUntil(this.destroy$)
      )
      .subscribe({
        next: (response) => {
          if (response.error) {
            this.handleError(new Error(response.error));
          } else {
            const reply = response.reply || 'No response received.';
            this.addMessage(reply, 'bot');
            this.messageReceived.emit(reply);
          }
        },
        error: (error) => {
          this.handleError(error);
        },
        complete: () => {
          this.isLoading$.next(false);
        }
      });
  }

  private addMessage(text: string, sender: 'user' | 'bot'): void {
    const newMessage: Message = {
      id: Date.now() + Math.random(),
      text,
      sender,
      timestamp: Date.now()
    };

    const currentMessages = this.messages$.value;
    const updatedMessages = [...currentMessages, newMessage];

    // Limit message history
    const maxMessages = this.config.maxMessages || this.defaultConfig.maxMessages;
    const limitedMessages = updatedMessages.slice(-maxMessages);

    this.messages$.next(limitedMessages);
    this.shouldScrollToBottom = true;

    if (sender === 'user') {
      this.messageSent.emit(text);
    }
  }

  private handleError(error: Error): void {
    this.error$.next(error.message);
    this.addMessage('Sorry, I encountered an error. Please try again.', 'bot');
    this.errorOccurred.emit(error);
  }

  toggleChat(): void {
    this.isOpen = !this.isOpen;
    this.error$.next(null);

    if (this.isOpen) {
      // Focus input after view update
      setTimeout(() => {
        if (this.messageInput?.nativeElement) {
          this.messageInput.nativeElement.focus();
        }
      }, 100);
    }
  }

  clearMessages(): void {
    const greetingMessage: Message = {
      id: 1,
      text: this.config.greeting || this.defaultConfig.greeting,
      sender: 'bot',
      timestamp: Date.now()
    };
    
    this.messages$.next([greetingMessage]);
    this.error$.next(null);
    this.shouldScrollToBottom = true;
  }

  clearError(): void {
    this.error$.next(null);
  }

  getMessageClasses(message: Message): string {
    return `chatbot-message ${message.sender}-message`;
  }

  formatTime(timestamp: number): string {
    return new Date(timestamp).toLocaleTimeString([], {
      hour: '2-digit',
      minute: '2-digit'
    });
  }

  trackMessage(index: number, message: Message): number {
    return message.id;
  }

  private scrollToBottom(): void {
    if (this.messagesContainer?.nativeElement) {
      const element = this.messagesContainer.nativeElement;
      element.scrollTop = element.scrollHeight;
    }
  }
}

Angular Service for Chatbot Logic

Create services/chatbot.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';

export interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

export interface ChatbotResponse {
  reply: string;
  conversation_id?: string;
  timestamp?: string;
  error?: string;
}

export interface ChatbotConfig {
  apiUrl: string;
  greeting: string;
  maxMessages: number;
  retryAttempts: number;
}

@Injectable({
  providedIn: 'root'
})
export class ChatbotService {
  private messages$ = new BehaviorSubject<Message[]>([]);
  private isLoading$ = new BehaviorSubject<boolean>(false);
  private error$ = new BehaviorSubject<string | null>(null);
  private config: ChatbotConfig;
  private conversationId: string;

  constructor(private http: HttpClient) {
    this.config = {
      apiUrl: '/api/chatbot/message',
      greeting: 'Hello! How can I help you?',
      maxMessages: 100,
      retryAttempts: 3
    };
    
    this.conversationId = this.generateConversationId();
    this.initializeWithGreeting();
  }

  // Observables
  getMessages(): Observable<Message[]> {
    return this.messages$.asObservable();
  }

  getLoadingState(): Observable<boolean> {
    return this.isLoading$.asObservable();
  }

  getErrorState(): Observable<string | null> {
    return this.error$.asObservable();
  }

  // Configuration
  updateConfig(newConfig: Partial<ChatbotConfig>): void {
    this.config = { ...this.config, ...newConfig };
  }

  getConfig(): ChatbotConfig {
    return { ...this.config };
  }

  // Message management
  sendMessage(text: string): Observable<Message> {
    if (!text.trim() || this.isLoading$.value) {
      return throwError(() => new Error('Invalid message or service busy'));
    }

    const userMessage = this.addMessage(text, 'user');
    this.isLoading$.next(true);
    this.error$.next(null);

    const payload = {
      message: text,
      conversation_id: this.conversationId
    };

    return this.http.post<ChatbotResponse>(this.config.apiUrl, payload)
      .pipe(
        map(response => {
          if (response.error) {
            throw new Error(response.error);
          }
          return this.addMessage(response.reply || 'No response received.', 'bot');
        }),
        tap(() => this.isLoading$.next(false)),
        catchError(error => {
          this.isLoading$.next(false);
          return this.handleError(error);
        })
      );
  }

  addMessage(text: string, sender: 'user' | 'bot'): Message {
    const newMessage: Message = {
      id: Date.now() + Math.random(),
      text,
      sender,
      timestamp: Date.now()
    };

    const currentMessages = this.messages$.value;
    const updatedMessages = [...currentMessages, newMessage];

    // Limit message history
    const limitedMessages = updatedMessages.slice(-this.config.maxMessages);
    this.messages$.next(limitedMessages);

    return newMessage;
  }

  clearMessages(): void {
    this.initializeWithGreeting();
    this.error$.next(null);
  }

  clearError(): void {
    this.error$.next(null);
  }

  // Conversation management
  getConversationId(): string {
    return this.conversationId;
  }

  resetConversation(): void {
    this.conversationId = this.generateConversationId();
    this.clearMessages();
  }

  // Utility methods
  getLastMessage(): Message | null {
    const messages = this.messages$.value;
    return messages.length > 0 ? messages[messages.length - 1] : null;
  }

  getMessageCount(): number {
    return this.messages$.value.length;
  }

  getUserMessages(): Message[] {
    return this.messages$.value.filter(m => m.sender === 'user');
  }

  getBotMessages(): Message[] {
    return this.messages$.value.filter(m => m.sender === 'bot');
  }

  // Private methods
  private generateConversationId(): string {
    return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private initializeWithGreeting(): void {
    const greetingMessage: Message = {
      id: 1,
      text: this.config.greeting,
      sender: 'bot',
      timestamp: Date.now()
    };
    
    this.messages$.next([greetingMessage]);
  }

  private handleError(error: HttpErrorResponse): Observable<Message> {
    let errorMessage: string;

    if (error.status === 0) {
      errorMessage = 'Network error. Please check your connection.';
    } else if (error.status >= 500) {
      errorMessage = 'Server error. Please try again later.';
    } else if (error.status === 429) {
      errorMessage = 'Too many requests. Please wait a moment.';
    } else {
      errorMessage = error.error?.message || `Error ${error.status}: ${error.statusText}`;
    }

    this.error$.next(errorMessage);
    const errorResponseMessage = this.addMessage(
      'Sorry, I encountered an error. Please try again.',
      'bot'
    );

    return throwError(() => new Error(errorMessage));
  }
}

Angular Usage Examples

Basic Angular App Integration

// app.component.ts
import { Component } from '@angular/core';
import { ChatbotComponent } from './components/chatbot.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ChatbotComponent],
  template: `
    <div class="app">
      <header>
        <h1>My Angular App</h1>
      </header>
      
      <main>
        <p>Welcome to my application!</p>
      </main>
      
      <app-chatbot
        [config]="chatbotConfig"
        (messageSent)="onMessageSent($event)"
        (messageReceived)="onMessageReceived($event)"
        (errorOccurred)="onError($event)"
      ></app-chatbot>
    </div>
  `
})
export class AppComponent {
  chatbotConfig = {
    apiUrl: '/api/chatbot/message',
    title: 'Support Assistant',
    greeting: 'Hi! How can I help you today?',
    position: 'bottom-right' as const,
    theme: 'light' as const,
    maxMessages: 50
  };

  onMessageSent(message: string) {
    console.log('User sent:', message);
    // Analytics tracking
  }

  onMessageReceived(message: string) {
    console.log('Bot replied:', message);
    // Analytics tracking
  }

  onError(error: Error) {
    console.error('Chatbot error:', error);
    // Error reporting
  }
}

Advanced Angular Integration with Service

// chat.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ChatbotService, Message } from '../services/chatbot.service';

@Component({
  selector: 'app-chat',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div class="chat-container">
      <div class="chat-header">
        <h2>Customer Support</h2>
        <div class="chat-stats">
          <span>Messages: {{ messageCount$ | async }}</span>
          <button (click)="clearConversation()">Clear</button>
        </div>
      </div>
      
      <div class="chat-messages">
        <div
          *ngFor="let message of messages$ | async; trackBy: trackMessage"
          [class]="'message ' + message.sender"
        >
          <div class="message-content">{{ message.text }}</div>
          <div class="message-time">{{ formatTime(message.timestamp) }}</div>
        </div>
        
        <div *ngIf="isLoading$ | async" class="loading">
          AI is typing...
        </div>
      </div>
      
      <div class="chat-input">
        <input
          [(ngModel)]="inputText"
          (keydown.enter)="sendMessage()"
          placeholder="Type your message..."
          [disabled]="isLoading$ | async"
        />
        <button 
          (click)="sendMessage()" 
          [disabled]="!inputText.trim() || (isLoading$ | async)"
        >
          Send
        </button>
      </div>
      
      <div *ngIf="error$ | async as error" class="error">
        {{ error }}
        <button (click)="clearError()">×</button>
      </div>
    </div>
  `,
  styleUrls: ['./chat.component.scss']
})
export class ChatComponent implements OnInit, OnDestroy {
  messages$: Observable<Message[]>;
  isLoading$: Observable<boolean>;
  error$: Observable<string | null>;
  messageCount$: Observable<number>;
  
  inputText = '';
  private destroy$ = new Subject<void>();

  constructor(private chatbotService: ChatbotService) {
    this.messages$ = this.chatbotService.getMessages();
    this.isLoading$ = this.chatbotService.getLoadingState();
    this.error$ = this.chatbotService.getErrorState();
    this.messageCount$ = this.messages$.pipe(
      map(messages => messages.length)
    );
  }

  ngOnInit() {
    // Configure the chatbot service
    this.chatbotService.updateConfig({
      apiUrl: '/api/chatbot/message',
      greeting: 'Welcome to our support chat!',
      maxMessages: 100
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  sendMessage() {
    if (!this.inputText.trim()) return;

    this.chatbotService.sendMessage(this.inputText)
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (message) => {
          console.log('Message sent successfully:', message);
        },
        error: (error) => {
          console.error('Failed to send message:', error);
        }
      });

    this.inputText = '';
  }

  clearConversation() {
    this.chatbotService.resetConversation();
  }

  clearError() {
    this.chatbotService.clearError();
  }

  trackMessage(index: number, message: Message): number {
    return message.id;
  }

  formatTime(timestamp: number): string {
    return new Date(timestamp).toLocaleTimeString([], {
      hour: '2-digit',
      minute: '2-digit'
    });
  }
}

Vanilla JavaScript

Basic Vanilla JS Implementation

Create chatbot.js:

class Chatbot {
  constructor(options = {}) {
    this.config = {
      apiUrl: '/api/chatbot/message',
      title: 'Chat Support',
      greeting: 'Hello! How can I help you?',
      placeholder: 'Type your message...',
      position: 'bottom-right',
      theme: 'light',
      maxMessages: 100,
      ...options
    };

    this.isOpen = false;
    this.isLoading = false;
    this.messages = [];
    this.conversationId = `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    this.init();
  }

  init() {
    this.createElements();
    this.attachEventListeners();
    this.addMessage(this.config.greeting, 'bot');
  }

  createElements() {
    // Create container
    this.container = document.createElement('div');
    this.container.className = `chatbot-container chatbot-${this.config.position} chatbot-theme-${this.config.theme}`;
    
    // Create popup
    this.popup = document.createElement('div');
    this.popup.className = 'chatbot-popup';
    this.popup.style.display = 'none';
    
    // Create header
    this.header = document.createElement('div');
    this.header.className = 'chatbot-header';
    this.header.innerHTML = `
      <h4>${this.config.title}</h4>
      <div class="chatbot-header-actions">
        <button class="chatbot-clear" title="Clear conversation">🗑️</button>
        <button class="chatbot-close">×</button>
      </div>
    `;
    
    // Create messages container
    this.messagesContainer = document.createElement('div');
    this.messagesContainer.className = 'chatbot-messages';
    
    // Create input container
    this.inputContainer = document.createElement('div');
    this.inputContainer.className = 'chatbot-input';
    this.inputContainer.innerHTML = `
      <input type="text" placeholder="${this.config.placeholder}" maxlength="2000">
      <button class="send-button">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
          <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
        </svg>
      </button>
    `;
    
    // Create toggle button
    this.toggleButton = document.createElement('button');
    this.toggleButton.className = 'chatbot-toggle';
    this.toggleButton.textContent = '💬';
    
    // Assemble components
    this.popup.appendChild(this.header);
    this.popup.appendChild(this.messagesContainer);
    this.popup.appendChild(this.inputContainer);
    this.container.appendChild(this.popup);
    this.container.appendChild(this.toggleButton);
    
    // Add to document
    document.body.appendChild(this.container);
    
    // Get references to interactive elements
    this.input = this.inputContainer.querySelector('input');
    this.sendButton = this.inputContainer.querySelector('.send-button');
    this.clearButton = this.header.querySelector('.chatbot-clear');
    this.closeButton = this.header.querySelector('.chatbot-close');
  }

  attachEventListeners() {
    this.toggleButton.addEventListener('click', () => this.toggleChat());
    this.closeButton.addEventListener('click', () => this.toggleChat());
    this.clearButton.addEventListener('click', () => this.clearMessages());
    this.sendButton.addEventListener('click', () => this.sendMessage());
    
    this.input.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        this.sendMessage();
      }
    });
  }

  toggleChat() {
    this.isOpen = !this.isOpen;
    
    if (this.isOpen) {
      this.popup.style.display = 'flex';
      this.toggleButton.textContent = '×';
      this.input.focus();
      this.scrollToBottom();
    } else {
      this.popup.style.display = 'none';
      this.toggleButton.textContent = '💬';
    }
  }

  async sendMessage() {
    const text = this.input.value.trim();
    if (!text || this.isLoading) return;

    this.input.value = '';
    this.addMessage(text, 'user');
    this.setLoading(true);

    try {
      const response = await fetch(this.config.apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
        },
        body: JSON.stringify({
          message: text,
          conversation_id: this.conversationId
        })
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const data = await response.json();
      
      if (data.error) {
        throw new Error(data.error);
      }

      this.addMessage(data.reply || 'No response received.', 'bot');
      
      // Emit custom event
      this.container.dispatchEvent(new CustomEvent('messageReceived', {
        detail: { message: data.reply }
      }));

    } catch (error) {
      console.error('Chatbot error:', error);
      this.addMessage('Sorry, I encountered an error. Please try again.', 'bot');
      
      // Emit error event
      this.container.dispatchEvent(new CustomEvent('error', {
        detail: { error }
      }));
    } finally {
      this.setLoading(false);
    }
  }

  addMessage(text, sender) {
    const message = {
      id: Date.now() + Math.random(),
      text,
      sender,
      timestamp: Date.now()
    };

    this.messages.push(message);

    // Limit message history
    if (this.messages.length > this.config.maxMessages) {
      this.messages = this.messages.slice(-this.config.maxMessages);
      this.renderMessages();
    } else {
      this.renderMessage(message);
    }

    this.scrollToBottom();

    // Emit message event
    this.container.dispatchEvent(new CustomEvent('messageSent', {
      detail: { message: text, sender }
    }));
  }

  renderMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = `chatbot-message ${message.sender}-message`;
    messageElement.innerHTML = `
      <div class="message-content">${this.escapeHtml(message.text)}</div>
      <div class="message-time">${this.formatTime(message.timestamp)}</div>
    `;
    
    this.messagesContainer.appendChild(messageElement);
  }

  renderMessages() {
    this.messagesContainer.innerHTML = '';
    this.messages.forEach(message => this.renderMessage(message));
  }

  setLoading(loading) {
    this.isLoading = loading;
    this.input.disabled = loading;
    this.sendButton.disabled = loading;

    // Remove existing typing indicator
    const existing = this.messagesContainer.querySelector('.typing-indicator-message');
    if (existing) existing.remove();

    if (loading) {
      const typingElement = document.createElement('div');
      typingElement.className = 'chatbot-message bot-message typing-indicator-message';
      typingElement.innerHTML = `
        <div class="message-content">
          <div class="typing-indicator">
            <span></span>
            <span></span>
            <span></span>
          </div>
        </div>
      `;
      this.messagesContainer.appendChild(typingElement);
      this.scrollToBottom();
    }
  }

  clearMessages() {
    this.messages = [];
    this.renderMessages();
    this.addMessage(this.config.greeting, 'bot');
  }

  scrollToBottom() {
    setTimeout(() => {
      this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
    }, 100);
  }

  formatTime(timestamp) {
    return new Date(timestamp).toLocaleTimeString([], {
      hour: '2-digit',
      minute: '2-digit'
    });
  }

  escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

  // Public API methods
  open() {
    if (!this.isOpen) this.toggleChat();
  }

  close() {
    if (this.isOpen) this.toggleChat();
  }

  destroy() {
    this.container.remove();
  }

  updateConfig(newConfig) {
    this.config = { ...this.config, ...newConfig };
  }
}

// Usage example
document.addEventListener('DOMContentLoaded', () => {
  const chatbot = new Chatbot({
    apiUrl: '/api/chatbot/message',
    title: 'Customer Support',
    greeting: 'Hi! How can I help you today?',
    position: 'bottom-right',
    theme: 'light'
  });

  // Listen to events
  chatbot.container.addEventListener('messageSent', (e) => {
    console.log('Message sent:', e.detail);
  });

  chatbot.container.addEventListener('messageReceived', (e) => {
    console.log('Message received:', e.detail);
  });

  chatbot.container.addEventListener('error', (e) => {
    console.error('Chatbot error:', e.detail);
  });
});

TypeScript Support

TypeScript Definitions

Create types/chatbot.d.ts:

// Core interfaces
export interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

export interface ChatbotConfig {
  apiUrl: string;
  title: string;
  greeting: string;
  placeholder: string;
  position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  theme: 'light' | 'dark';
  maxMessages: number;
}

export interface ApiResponse {
  reply: string;
  conversation_id?: string;
  timestamp?: string;
  error?: string;
}

// Event interfaces
export interface MessageEvent {
  message: string;
  sender: 'user' | 'bot';
  timestamp: number;
}

export interface ErrorEvent {
  error: Error;
  context?: string;
}

// Component prop interfaces
export interface ChatbotProps extends Partial<ChatbotConfig> {
  onMessageSent?: (message: string) => void;
  onMessageReceived?: (message: string) => void;
  onError?: (error: Error) => void;
  className?: string;
  style?: React.CSSProperties;
}

// Hook interfaces
export interface UseChatbotOptions {
  apiUrl: string;
  greeting?: string;
  maxMessages?: number;
  onError?: (error: Error) => void;
}

export interface UseChatbotReturn {
  messages: Message[];
  isLoading: boolean;
  error: string | null;
  sendMessage: (text: string) => Promise<void>;
  clearMessages: () => void;
  conversationId: string;
}

// Service interfaces
export interface ChatbotService {
  sendMessage(text: string): Promise<Message>;
  getMessages(): Message[];
  clearMessages(): void;
  getConversationId(): string;
  updateConfig(config: Partial<ChatbotConfig>): void;
}

// Global declarations for vanilla JS
declare global {
  interface Window {
    Chatbot: typeof Chatbot;
  }
}

export class Chatbot {
  constructor(options?: Partial<ChatbotConfig>);
  open(): void;
  close(): void;
  destroy(): void;
  updateConfig(config: Partial<ChatbotConfig>): void;
  sendMessage(text: string): Promise<void>;
  clearMessages(): void;
  on(event: 'messageSent' | 'messageReceived' | 'error', handler: Function): void;
}

Styling and Themes

SCSS Variables and Themes

Create styles/chatbot-themes.scss:

// Base variables
:root {
  --chatbot-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
  --chatbot-border-radius: 12px;
  --chatbot-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  --chatbot-transition: all 0.2s ease;
  --chatbot-z-index: 1000;
}

// Light theme (default)
.chatbot-theme-light {
  --chatbot-primary-color: #4f46e5;
  --chatbot-primary-hover: #4338ca;
  --chatbot-background: #ffffff;
  --chatbot-surface: #f9fafb;
  --chatbot-border: #e5e7eb;
  --chatbot-text-primary: #111827;
  --chatbot-text-secondary: #6b7280;
  --chatbot-text-muted: #9ca3af;
  --chatbot-error: #ef4444;
  --chatbot-error-bg: #fef2f2;
  --chatbot-success: #10b981;
  --chatbot-user-message-bg: var(--chatbot-primary-color);
  --chatbot-user-message-text: #ffffff;
  --chatbot-bot-message-bg: #f3f4f6;
  --chatbot-bot-message-text: #374151;
  --chatbot-input-bg: #ffffff;
  --chatbot-input-border: #d1d5db;
  --chatbot-input-focus: var(--chatbot-primary-color);
}

// Dark theme
.chatbot-theme-dark {
  --chatbot-primary-color: #6366f1;
  --chatbot-primary-hover: #5b21b6;
  --chatbot-background: #1f2937;
  --chatbot-surface: #374151;
  --chatbot-border: #4b5563;
  --chatbot-text-primary: #f9fafb;
  --chatbot-text-secondary: #d1d5db;
  --chatbot-text-muted: #9ca3af;
  --chatbot-error: #f87171;
  --chatbot-error-bg: #7f1d1d;
  --chatbot-success: #34d399;
  --chatbot-user-message-bg: var(--chatbot-primary-color);
  --chatbot-user-message-text: #ffffff;
  --chatbot-bot-message-bg: #4b5563;
  --chatbot-bot-message-text: #e5e7eb;
  --chatbot-input-bg: #374151;
  --chatbot-input-border: #4b5563;
  --chatbot-input-focus: var(--chatbot-primary-color);
}

// High contrast theme
.chatbot-theme-high-contrast {
  --chatbot-primary-color: #000000;
  --chatbot-primary-hover: #333333;
  --chatbot-background: #ffffff;
  --chatbot-surface: #f5f5f5;
  --chatbot-border: #000000;
  --chatbot-text-primary: #000000;
  --chatbot-text-secondary: #333333;
  --chatbot-text-muted: #666666;
  --chatbot-error: #cc0000;
  --chatbot-error-bg: #ffe6e6;
  --chatbot-success: #006600;
  --chatbot-user-message-bg: #000000;
  --chatbot-user-message-text: #ffffff;
  --chatbot-bot-message-bg: #f0f0f0;
  --chatbot-bot-message-text: #000000;
  --chatbot-input-bg: #ffffff;
  --chatbot-input-border: #000000;
  --chatbot-input-focus: #000000;
}

Complete CSS Styles

Create styles/chatbot.css:

/* Import themes */
@import 'chatbot-themes.scss';

/* Base styles */
.chatbot-container {
  position: fixed;
  z-index: var(--chatbot-z-index);
  font-family: var(--chatbot-font-family);
  font-size: 14px;
  line-height: 1.5;
}

/* Positioning */
.chatbot-bottom-right {
  bottom: 20px;
  right: 20px;
}

.chatbot-bottom-left {
  bottom: 20px;
  left: 20px;
}

.chatbot-top-right {
  top: 20px;
  right: 20px;
}

.chatbot-top-left {
  top: 20px;
  left: 20px;
}

/* Popup container */
.chatbot-popup {
  width: 350px;
  height: 500px;
  background: var(--chatbot-background);
  border-radius: var(--chatbot-border-radius);
  box-shadow: var(--chatbot-shadow);
  display: flex;
  flex-direction: column;
  margin-bottom: 10px;
  border: 1px solid var(--chatbot-border);
  overflow: hidden;
  transform-origin: bottom right;
  animation: chatbot-slide-up 0.3s ease;
}

@keyframes chatbot-slide-up {
  from {
    opacity: 0;
    transform: translateY(20px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* Header */
.chatbot-header {
  padding: 16px 20px;
  background: var(--chatbot-primary-color);
  color: white;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-shrink: 0;
}

.chatbot-header h4 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.chatbot-header-actions {
  display: flex;
  gap: 8px;
}

.chatbot-clear,
.chatbot-close {
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: white;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  transition: var(--chatbot-transition);
}

.chatbot-clear:hover,
.chatbot-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Error display */
.chatbot-error {
  background: var(--chatbot-error-bg);
  color: var(--chatbot-error);
  padding: 12px;
  margin: 12px;
  border-radius: 6px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 14px;
  border: 1px solid var(--chatbot-error);
}

.chatbot-error button {
  background: none;
  border: none;
  color: var(--chatbot-error);
  cursor: pointer;
  font-size: 16px;
  padding: 0;
  margin-left: 8px;
}

/* Messages container */
.chatbot-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  scroll-behavior: smooth;
}

.chatbot-messages::-webkit-scrollbar {
  width: 6px;
}

.chatbot-messages::-webkit-scrollbar-track {
  background: transparent;
}

.chatbot-messages::-webkit-scrollbar-thumb {
  background: var(--chatbot-text-muted);
  border-radius: 3px;
}

.chatbot-messages::-webkit-scrollbar-thumb:hover {
  background: var(--chatbot-text-secondary);
}

/* Individual messages */
.chatbot-message {
  display: flex;
  flex-direction: column;
  max-width: 80%;
  animation: chatbot-message-slide 0.3s ease;
}

@keyframes chatbot-message-slide {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.user-message {
  align-items: flex-end;
  align-self: flex-end;
}

.bot-message {
  align-items: flex-start;
  align-self: flex-start;
}

.message-content {
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.4;
  word-wrap: break-word;
  white-space: pre-wrap;
}

.user-message .message-content {
  background: var(--chatbot-user-message-bg);
  color: var(--chatbot-user-message-text);
  border-bottom-right-radius: 4px;
}

.bot-message .message-content {
  background: var(--chatbot-bot-message-bg);
  color: var(--chatbot-bot-message-text);
  border-bottom-left-radius: 4px;
}

.message-time {
  font-size: 11px;
  color: var(--chatbot-text-muted);
  margin-top: 4px;
  opacity: 0.8;
}

/* Typing indicator */
.typing-indicator {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 4px 0;
}

.typing-indicator span {
  width: 6px;
  height: 6px;
  background: var(--chatbot-text-muted);
  border-radius: 50%;
  animation: chatbot-typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) {
  animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes chatbot-typing {
  0%, 60%, 100% {
    transform: translateY(0);
  }
  30% {
    transform: translateY(-8px);
  }
}

/* Input area */
.chatbot-input {
  padding: 16px;
  border-top: 1px solid var(--chatbot-border);
  display: flex;
  gap: 8px;
  align-items: center;
  flex-shrink: 0;
}

.chatbot-input input {
  flex: 1;
  padding: 10px 12px;
  border: 1px solid var(--chatbot-input-border);
  border-radius: 20px;
  font-size: 14px;
  outline: none;
  transition: var(--chatbot-transition);
  background: var(--chatbot-input-bg);
  color: var(--chatbot-text-primary);
}

.chatbot-input input:focus {
  border-color: var(--chatbot-input-focus);
  box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}

.chatbot-input input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.chatbot-input input::placeholder {
  color: var(--chatbot-text-muted);
}

/* Send button */
.send-button {
  width: 40px;
  height: 40px;
  background: var(--chatbot-primary-color);
  border: none;
  border-radius: 50%;
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--chatbot-transition);
  flex-shrink: 0;
}

.send-button:hover:not(:disabled) {
  background: var(--chatbot-primary-hover);
  transform: scale(1.05);
}

.send-button:disabled {
  background: var(--chatbot-text-muted);
  cursor: not-allowed;
  transform: none;
}

/* Toggle button */
.chatbot-toggle {
  width: 60px;
  height: 60px;
  background: var(--chatbot-primary-color);
  border: none;
  border-radius: 50%;
  color: white;
  font-size: 24px;
  cursor: pointer;
  box-shadow: var(--chatbot-shadow);
  transition: var(--chatbot-transition);
  display: flex;
  align-items: center;
  justify-content: center;
}

.chatbot-toggle:hover {
  background: var(--chatbot-primary-hover);
  transform: scale(1.05);
}

.chatbot-toggle:active {
  transform: scale(0.95);
}

/* Mobile responsive */
@media (max-width: 480px) {
  .chatbot-popup {
    width: calc(100vw - 40px);
    height: calc(100vh - 100px);
    max-height: 600px;
  }
  
  .chatbot-container {
    left: 20px !important;
    right: 20px !important;
    bottom: 20px !important;
  }
  
  .chatbot-message {
    max-width: 90%;
  }
}

/* High contrast mode support */
@media (prefers-contrast: high) {
  .chatbot-container {
    --chatbot-border: #000000;
    --chatbot-text-primary: #000000;
    --chatbot-background: #ffffff;
  }
}

/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
  .chatbot-popup,
  .chatbot-message,
  .chatbot-toggle {
    animation: none;
    transition: none;
  }
  
  .typing-indicator span {
    animation: none;
  }
}

/* Focus styles for accessibility */
.chatbot-toggle:focus,
.chatbot-clear:focus,
.chatbot-close:focus,
.send-button:focus {
  outline: 2px solid var(--chatbot-primary-color);
  outline-offset: 2px;
}

.chatbot-input input:focus {
  outline: 2px solid var(--chatbot-primary-color);
  outline-offset: 2px;
}

Custom Components

Creating Custom Themes

// Custom theme example
const customTheme = {
  primary: '#ff6b6b',
  primaryHover: '#ff5252',
  background: '#f8f9fa',
  surface: '#ffffff',
  border: '#dee2e6',
  textPrimary: '#212529',
  textSecondary: '#6c757d',
  userMessageBg: '#ff6b6b',
  userMessageText: '#ffffff',
  botMessageBg: '#e9ecef',
  botMessageText: '#495057'
};

// Apply custom theme
function applyCustomTheme(chatbotContainer, theme) {
  const root = chatbotContainer;
  Object.entries(theme).forEach(([key, value]) => {
    const cssVar = `--chatbot-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;
    root.style.setProperty(cssVar, value);
  });
}

Advanced Customization

interface ChatbotCustomization {
  avatar?: {
    user?: string;
    bot?: string;
  };
  sounds?: {
    send?: string;
    receive?: string;
    error?: string;
  };
  animations?: {
    messageEntry?: string;
    typing?: string;
  };
  features?: {
    fileUpload?: boolean;
    voiceInput?: boolean;
    suggestions?: string[];
    typing?: boolean;
  };
}

class AdvancedChatbot extends Chatbot {
  constructor(options: ChatbotOptions & { customization?: ChatbotCustomization }) {
    super(options);
    this.customization = options.customization || {};
    this.setupCustomFeatures();
  }

  private setupCustomFeatures() {
    if (this.customization.features?.fileUpload) {
      this.addFileUploadFeature();
    }
    
    if (this.customization.features?.voiceInput) {
      this.addVoiceInputFeature();
    }
    
    if (this.customization.features?.suggestions) {
      this.addSuggestionsFeature();
    }
  }

  private addFileUploadFeature() {
    // Implementation for file upload
  }

  private addVoiceInputFeature() {
    // Implementation for voice input
  }

  private addSuggestionsFeature() {
    // Implementation for message suggestions
  }
}

Summary

This comprehensive frontend integration guide provides:

Complete React components with TypeScript support and hooks
Full Vue 3 components with Composition API and composables
Angular standalone components with services and RxJS
Vanilla JavaScript implementation for any framework
TypeScript definitions for type safety
Complete styling system with themes and customization
Accessibility features and responsive design
Advanced customization options for enterprise use

Each implementation includes real-world examples, best practices, and framework-specific patterns to help developers integrate the chatbot seamlessly into their applications.

Next recommended reading: Examples for real-world implementation scenarios.

Clone this wiki locally