| name | chatbot-widget-creator |
| description | Creates a battle-tested ChatGPT-style chatbot widget that solves real-world production issues. Features infinite re-render protection, text selection "Ask AI", RAG backend integration, streaming SSE, and comprehensive performance monitoring. |
| category | frontend |
| version | 3.2.0 |
| date_updated | Sun Dec 07 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
Chatbot Widget Creator Skill
Purpose
Creates a production-ready ChatGPT-style chatbot widget with advanced features:
- Custom Portrait Layout: Compact 380px x 700px styling matching ChatGPT aesthetics
- Infinite re-render protection using useReducer and split context pattern
- Text selection "Ask AI" functionality with smart tooltips
- Streaming responses with Server-Sent Events (SSE)
- RAG backend integration ready for FastAPI/Qdrant setup
- Performance monitoring and debugging utilities
- Modern glassmorphic UI with ChatGPT-style interface
Key Improvements Based on Real Implementation
1. Refined UI/UX
- Compact Dimensions: 380px width x 700px height for optimal sidebar integration
- Clean Message UI: Removed timestamps, read receipts, and file uploads for a cleaner reading experience
- Themed Integration: Dynamically uses
var(--ifm-color-primary)to match host site branding - Bottom Alignment: Message bubbles and avatars are bottom-aligned for a modern chat look
- Minimalist Indicators: Simplified 3-dot pulsing animation for thinking state
2. State Management Architecture
- useReducer pattern instead of multiple useState hooks
- Split context (StateContext + ActionsContext) to prevent unnecessary re-renders
- Stable callbacks with proper dependencies to avoid circular references
- AbortController for proper cleanup of streaming connections
3. Performance Optimizations
- React.memo wrapping for expensive components like
MessageBubble - useMemo for computed values and complex operations
- useCallback with stable dependencies
- Render counter utilities for debugging
- Virtualization support for long conversations (50+ messages)
4. Text Selection Feature
- useTextSelection hook for detecting text selections
- Smart positioning tooltip with edge detection
- Context-aware prompts when asking about selected text
- Length validation with truncation warnings
Prerequisites
Backend Requirements:
- FastAPI or similar with SSE support
- Endpoint:
POST /api/chatreturning Server-Sent Events - Request format:
{ "question": string, "stream": boolean } - Response format: SSE with
{ "type": "chunk", "content": string }
Frontend Dependencies:
npm install react framer-motion react-markdown react-syntax-highlighter remark-gfm lucide-react clsx tailwind-merge # Note: No ChatKit dependency - this is a custom implementation
Quick Start
1. Create the Widget Structure
# Create main directory structure
mkdir -p src/components/ChatWidget/{components,hooks,contexts,utils,styles}
# Copy all template files
cp -r .claude/skills/chatbot-widget-creator/templates/* src/components/ChatWidget/
2. Backend API Requirements
Your backend must implement:
@app.post("/api/chat")
async def chat_endpoint(request: ChatRequest):
"""Chat endpoint with Server-Sent Events streaming."""
# Request format
{
"question": "What is physical AI?",
"stream": true,
"context": { # Optional
"selectedText": "...",
"source": "User selection"
}
}
# Response format (SSE)
data: {"type": "start", "session_id": "..."}
data: {"type": "chunk", "content": "Physical AI refers to..."}
data: {"type": "chunk", "content": "embodied AI systems..."}
data: {"type": "done", "session_id": "..."}
3. Integration
Add to your site root (e.g., src/theme/Root.tsx):
import React from 'react';
import AnimatedChatWidget from '../components/ChatWidget/components/AnimatedChatWidget';
export default function Root({ children }) {
const getChatEndpoint = () => {
const hostname = window.location.hostname;
if (hostname === 'localhost') {
return 'http://localhost:7860/api/chat';
}
return 'https://your-domain.com/api/chat';
};
return (
<>
{children}
<AnimatedChatWidget
apiUrl={getChatEndpoint()}
maxTextSelectionLength={2000}
fallbackTextLength={5000}
/>
</>
);
}
Architecture Details
State Management (Critical for Performance)
// Consolidated state to prevent fragmentation
interface ChatState {
messages: ChatMessage[];
isOpen: boolean;
isThinking: boolean;
currentStreamingId?: string;
error: Error | null;
renderCount: number;
}
// Split context pattern
const ChatStateContext = createContext<{ state: ChatState }>();
const ChatActionsContext = createContext<{ actions: ChatActions }>();
// Components only subscribe to what they need
const messages = useChatSelector(s => s.messages); // Re-renders on messages change
const actions = useChatActions(); // Never re-renders
Key Anti-Patterns Avoided
❌ Multiple useState hooks:
// Bad - causes context fragmentation const [messages, setMessages] = useState([]); const [isOpen, setIsOpen] = useState(false);✅ Consolidated useReducer:
// Good - single state source const [state, dispatch] = useReducer(chatReducer, initialState);❌ Circular dependencies:
// Bad - callback depends on state that changes const handleChunk = useCallback((chunk) => { if (session.currentStreamingId) { // Dependency on state updateMessage(session.currentStreamingId, chunk); } }, [session.currentStreamingId]); // Infinite re-render!✅ Stable references:
// Good - no circular dependencies const streamingIdRef = useRef<string>(); const handleChunk = useCallback((chunk) => { if (streamingIdRef.current) { dispatch(updateStreamingAction(streamingIdRef.current, chunk)); } }, [dispatch]); // Stable dependency array
Streaming Response Handling
// Proper SSE parsing
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data !== '[DONE]') {
const parsed = JSON.parse(data);
if (parsed.type === 'chunk' && parsed.content) {
handleChunk(parsed.content);
} else if (parsed.type === 'done') {
handleComplete();
}
}
}
}
Customization Guide
Theming
Edit src/components/ChatWidget/styles/ChatWidget.module.css:
/* Primary colors */
.widget {
background: #171717; /* Dark mode background */
width: 380px;
height: 700px;
}
/* Message bubbles */
.userMessageBubble {
background: var(--ifm-color-primary); /* Theme integration */
color: white;
}
.aiMessageBubble {
background: #21262d;
border: 1px solid #30363d;
}
Component Reference
Core Components
- AnimatedChatWidget: Main entry point with animations
- ChatInterface: ChatGPT-style UI container
- MessageBubble: Individual message display (React.memo optimized)
- InputArea: Message input (No file upload)
- SelectionTooltip: Text selection "Ask AI" tooltip
- ThinkingIndicator: Minimalist 3-dot pulsing animation
Hooks
- useChatSession: Access chat state and actions
- useStreamingResponse: Handle SSE connections
- useTextSelection: Detect and handle text selection
- useErrorHandler: Centralized error management
Utilities
- chatReducer: State transitions
- api.ts: API request/response formatting
- animations.ts: Framer Motion configs
Troubleshooting
Common Issues and Solutions
Infinite Re-renders
- Check for circular dependencies in useCallback
- Ensure split context pattern is properly implemented
- Use React DevTools Profiler to identify causes
Memory Leaks
- Ensure AbortController cleanup on unmount
- Check for unclosed SSE connections
- Monitor with
window.performance.memory
SSE Not Working
- Verify CORS headers include
text/event-stream - Check that responses use correct
data: {}format - Ensure
Cache-Control: no-cacheis set
- Verify CORS headers include
Text Selection Issues
- Verify
useTextSelectionis enabled - Check for CSS
user-select: noneconflicts - Ensure z-index is high enough for tooltip
- Verify
Production Checklist
- Remove console.log statements in production
- Add proper error tracking (Sentry, etc.)
- Implement rate limiting on backend
- Add CORS configuration
- Test on slow networks
- Verify memory leak prevention
- Test with long conversations (100+ messages)
Result
A production-ready chat widget that:
- Matches the ChatGPT visual aesthetic
- Never crashes from infinite re-renders
- Provides smooth text selection interactions
- Streams responses efficiently
- Maintains performance over time
- Handles all edge cases gracefully