Skip to main content

chat-widget

Build a real-time support chat system with a floating widget for users and an admin dashboard for support staff. Use when the user wants live chat, customer support chat, real-time messaging, or in-app support.

Zur Installation springen

Quellinformationen

Repository
muhammedadnank/Antigravity-Skills
Letzte Quellaktivität
6. Juni 2026 um 18:30
Erkannte Sprache von SKILL.md
Englisch
Sterne
13
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
chat-widget
description
Build a real-time support chat system with a floating widget for users and an admin dashboard for support staff. Use when the user wants live chat, customer support chat, real-time messaging, or in-app support.
risk
unknown
source
community
# Live Support Chat Widget Build a real-time support chat system with a floating widget for users and an admin dashboard for support staff. ## When to Use This Skill Use when the user wants to: - Add a live chat widget to their app - Build customer support chat functionality - Create real-time messaging between users and admins - Add an in-app support channel ## Architecture Overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ FRONTEND │ ├─────────────────────────────┬───────────────────────────────────┤ │ User Widget │ Admin Dashboard │ │ - Floating chat button │ - Chat list (active/archived) │ │ - Message panel │ - Conversation view │ │ - Unread badge │ - Archive/restore controls │ │ - Connection indicator │ - User info display │ └─────────────┬───────────────┴───────────────┬───────────────────┘ │ │ │ WebSocket + REST API │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ BACKEND │ ├─────────────────────────────────────────────────────────────────┤ │ Channels │ Controllers │ │ - ChatChannel (per chat) │ - User: get/create chat │ │ - AdminChannel (global) │ - Admin: list, view, archive │ ├─────────────────────────────┼───────────────────────────────────┤ │ Models │ Jobs │ │ - Chat (1 per user) │ - Email notification (delayed) │ │ - Message (many per chat) │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Implementation Guide ### Step 1: Data Models Create two tables: `support_chats` and `support_messages`. **support_chats** ``` id - primary key (UUID recommended) user_id - foreign key to users (UNIQUE - one chat per user) last_message_at - timestamp (for sorting chats by recency) admin_viewed_at - timestamp (tracks when admin last viewed) archived_at - timestamp (null = active, set = archived) created_at updated_at ``` **support_messages** ``` id - primary key (UUID recommended) chat_id - foreign key to support_chats content - text (required) sender_type - enum: 'user' | 'admin' read_at - timestamp (null = unread) created_at updated_at ``` **Key indexes:** - `support_chats.user_id` (unique) - `support_chats.last_message_at` (for sorting) - `support_chats.archived_at` (for filtering) - `support_messages.chat_id` - `support_messages.(chat_id, created_at)` (composite, for ordering) **Model relationships:** ``` User has_one SupportChat SupportChat belongs_to User SupportChat has_many SupportMessages SupportMessage belongs_to SupportChat ``` **Model methods to implement:** Chat model: ```pseudo function touch_last_message() update last_message_at = now() function unread_for_admin?() return exists message where sender_type = 'user' and created_at > admin_viewed_at function mark_viewed_by_admin() update admin_viewed_at = now() function archive() update archived_at = now() function unarchive() update archived_at = null function archived?() return archived_at != null ``` Message model: ```pseudo after_create: chat.touch_last_message() if sender_type == 'user' and chat.archived?: chat.unarchive() // Auto-reactivate on new user message after_create_commit: broadcast_to_chat_channel(message_data) if sender_type == 'user': broadcast_to_admin_notification_channel(message_data, chat_info) if sender_type == 'admin': schedule_email_notification(delay: 5.minutes) ``` ### Step 2: API Endpoints **User-facing:** ``` GET /support_chat - Get or create user's chat with messages PATCH /support_chat/mark_read - Mark admin messages as read ``` **Admin-facing:** ``` GET /admin/chats - List chats (query: archived=true/false) GET /admin/chats/:id - Get chat with messages POST /admin/chats/:id/archive - Archive chat POST /admin/chats/:id/unarchive - Restore chat ``` **Controller logic:** User GET /support_chat: ```pseudo function show() chat = current_user.support_chat || create_chat(user: current_user) return { id: chat.id, messages: chat.messages.map(m => serialize_message(m)) } ``` Admin GET /admin/chats: ```pseudo function index() chats = SupportChat .where(archived_at: params.archived ? not_null : null) .includes(:user, :messages) .order(last_message_at: desc) return chats.map(c => { id: c.id, user_email: c.user.email, last_message_preview: c.messages.last?.content.truncate(100), last_message_sender: c.messages.last?.sender_type, message_count: c.messages.count, unread: c.unread_for_admin?, archived: c.archived? }) ``` ### Step 3: WebSocket Channels Create two channels for real-time communication. **ChatChannel** (specific to each chat): ```pseudo class ChatChannel on_subscribe(chat_id): chat = find_chat(chat_id) if not authorized(chat): reject() return stream_from "support_chat:#{chat_id}" function authorized(chat): return chat.user_id == current_user.id OR current_user.is_admin action send_message(content): if content.blank: return sender_type = current_user.is_admin ? 'admin' : 'user' chat.messages.create(content: content, sender_type: sender_type) ``` **AdminNotificationChannel** (global for all admins): ```pseudo class AdminNotificationChannel on_subscribe: if not current_user.is_admin: reject() return stream_from "admin_support_notifications" ``` **Broadcasting (from Message model):** ```pseudo function broadcast_message(): message_data = { id: id, content: content, sender_type: sender_type, read_at: read_at, created_at: created_at } // Broadcast to chat subscribers (user + any viewing admins) broadcast("support_chat:#{chat.id}", { type: "new_message", message: message_data }) // Notify all admins when user sends message if sender_type == 'user': broadcast("admin_support_notifications", { type: "new_user_message", chat_id: chat.id, user_email: chat.user.email, message: message_data }) ``` ### Step 4: Frontend - User Widget Create a floating chat widget with these components: **Component structure:** ``` ChatWidget (root container) ├── ChatButton (fixed position, bottom-right) │ ├── Icon (message bubble when closed, X when open) │ └── UnreadBadge (shows count, caps at "9+") └── ChatPanel (slides up when open) ├── Header (title + connection status dot) ├── MessageList (scrollable) │ └── MessageBubble (styled by sender_type) └── InputArea ├── Textarea (auto-expanding) └── SendButton ``` **State management hook:** ```pseudo function useSupportChat(): state: chat: Chat | null connected: boolean loading: boolean refs: consumer: WebSocketConsumer subscription: ChannelSubscription seenMessageIds: Set<string> // For deduplication on_mount: fetch('/support_chat') .then(data => { chat = data seenMessageIds.addAll(data.messages.map(m => m.id)) }) when chat.id changes: subscription = consumer.subscribe('ChatChannel', { chat_id: chat.id }) subscription.on_received(data => { if data.type == 'new_message': if seenMessageIds.has(data.message.id): return // Dedupe seenMessageIds.add(data.message.id) chat.messages.push(data.message) if data.message.sender_type == 'admin': play_notification_sound() }) subscription.on_connected(() => connected = true) subscription.on_disconnected(() => connected = false) on_unmount: subscription.unsubscribe() function sendMessage(content): subscription.perform('send_message', { content: content.trim() }) function markAsRead(): fetch('/support_chat/mark_read', { method: 'PATCH' }) // Update local state to mark admin messages as read return { chat, connected, loading, sendMessage, markAsRead } ``` **Widget behavior:** - Show floating button at bottom-right corner (fixed position) - Display unread count badge (count messages where sender_type='admin' and read_at=null) - Toggle panel open/closed on button click - Auto-call markAsRead() when panel opens - Auto-scroll to bottom when new messages arrive - Show connection status indicator (green dot = connected) - Keyboard: Enter to send, Shift+Enter for newline **Message styling:** - User messages: right-aligned, primary color background - Admin messages: left-aligned, secondary/muted background - Show timestamp on each message ### Step 5: Frontend - Admin Dashboard Create two pages: chat list and chat detail. **Chat List Page:** ``` Header: "Support Chats" Tabs: [Active] [Archived] Chat cards (sorted by last_message_at desc): ┌─────────────────────────────────────────┐ │ [Unread indicator] user@example.com │ │ Last message preview text... │ │ 5 messages · 2 minutes ago │ └─────────────────────────────────────────┘ ``` Features: - Tab filtering (active vs archived) - Unread indicator (highlight border or badge) - Click to navigate to detail - Show "You: " prefix if last message was from admin **Chat Detail Page:** ``` Header: user@example.com [Archive/Restore button] Back link Messages (grouped by date): ──── Monday, January 29 ──── [User bubble] Message content 10:30 AM [Admin bubble] Reply content 10:35 AM Input area (same as widget) ``` Features: - Group messages by date with dividers - User messages left, admin messages right (opposite of user widget) - Show sender label ("You" for admin, user email/name for user) - Archive/restore toggle button - Same WebSocket subscription as user widget for real-time updates - Call mark_viewed_by_admin() when page loads (server-side) ### Step 6: Email Notifications Send email to user when admin replies and user hasn't seen it. **Job/worker:** ```pseudo class SupportReplyNotificationJob perform(message): if message.sender_type != 'admin': return if message.read_at != null: return // Already read, skip send_email( to: message.chat.user.email, subject: "New reply from Support", body: "You have a new message from our support team..." ) ``` **Scheduling:** - Schedule job with 5-minute delay when admin sends message - This gives user time to see message in-app before email - Job checks if still unread before sending ### Step 7: TypeScript Types ```typescript interface SupportMessage { id: string content: string sender_type: 'user' | 'admin' read_at: string | null // ISO8601 created_at: string // ISO8601 } interface SupportChat { id: string messages: SupportMessage[] } interface SupportChatListItem { id: string user_id: string user_email: string last_message_at: string | null last_message_preview: string | null last_message_sender: 'user' | 'admin' | null message_count: number unread: boolean archived: boolean } interface AdminSupportChat { id: string user_id: string user_email: string archived: boolean messages: SupportMessage[] } // WebSocket message types interface ChatChannelMessage { type: 'new_message' message: SupportMessage } interface AdminNotificationMessage { type: 'new_user_message' chat_id: string user_email: string message: SupportMessage } ``` ## Key Design Decisions 1. **One chat per user** - Simplifies UX, user always has same conversation history 2. **Soft-delete via archiving** - Preserves history, allows restore 3. **Auto-unarchive** - When user sends message to archived chat, reactivate it 4. **Delayed email notifications** - 5 min delay prevents spam for rapid replies 5. **Message deduplication** - Track seen IDs to prevent duplicates from send + broadcast echo 6. **Separate admin channel** - Allows future features like global unread count, desktop notifications ## Testing Checklist After implementation: - [ ] User can open widget and send message - [ ] Admin sees message in real-time on dashboard - [ ] Admin can reply and user sees it instantly - [ ] Unread badge shows correct count - [ ] Badge clears when widget opens - [ ] Connection indicator reflects actual status - [ ] Archive/restore works correctly - [ ] Auto-unarchive triggers on user message - [ ] Email sends after 5 min if message unread - [ ] Email does NOT send if user already read message - [ ] Messages appear in chronological order - [ ] No duplicate messages appear ## Common Pitfalls 1. **Forgetting deduplication** - Messages sent by current user echo back via broadcast 2. **Race conditions on read status** - Use database transactions 3. **WebSocket auth** - Verify user can access the specific chat 4. **Stale connection status** - Handle reconnection gracefully 5. **Missing indexes** - Add composite index on (chat_id, created_at) 6. **Email timing** - Use background job, not synchronous send --- ## Framework-Specific Guidance ### Ruby on Rails **Models:** ```ruby # app/models/support_chat.rb class SupportChat < ApplicationRecord belongs_to :user has_many :support_messages, dependent: :destroy scope :active, -> { where(archived_at: nil) } scope :archived, -> { where.not(archived_at: nil) } scope :recent_first, -> { order(last_message_at: :desc) } def touch_last_message update_column(:last_message_at, Time.current) end def unread_for_admin? support_messages.where(sender_type: :user) .where("created_at > ?", admin_viewed_at || Time.at(0)).exists? end def archive! update_column(:archived_at, Time.current) end def unarchive! update_column(:archived_at, nil) end end
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen