Back to Supermemory

Customer Support Bot

apps/docs/cookbook/customer-support.mdx

latest37.5 KB
Original Source

Create a customer support system that remembers every interaction, tracks issues across conversations, and provides personalized support based on customer history and preferences.

What You'll Build

A customer support bot that:

  • Remembers customer history across all conversations and channels
  • Tracks ongoing issues and follows up automatically
  • Provides personalized responses based on customer tier and preferences
  • Escalates complex issues to human agents with full context
  • Learns from resolutions to improve future responses

Prerequisites

  • Node.js 18+ or Python 3.8+
  • Supermemory API key
  • OpenAI API key
  • Customer database or CRM integration
  • Basic understanding of customer support workflows

Implementation

Step 1: Customer Context Management

<Tabs> <Tab title="Next.js"> ```typescript lib/customer-context.ts import { Supermemory } from 'supermemory'
const client = new Supermemory({
  apiKey: process.env.SUPERMEMORY_API_KEY!
})

interface Customer {
  id: string
  email: string
  name: string
  tier: 'free' | 'pro' | 'enterprise'
  joinDate: string
  preferences?: Record<string, any>
}

interface SupportTicket {
  id: string
  customerId: string
  subject: string
  status: 'open' | 'pending' | 'resolved' | 'closed'
  priority: 'low' | 'medium' | 'high' | 'urgent'
  category: string
  createdAt: string
  updatedAt: string
  assignedAgent?: string
}

export class CustomerContextManager {
  private getContainerTag(customerId: string): string {
    return `customer_${customerId}`
  }

  async addInteraction(customerId: string, interaction: {
    type: 'chat' | 'email' | 'phone' | 'ticket'
    content: string
    channel: string
    outcome?: 'resolved' | 'escalated' | 'pending'
    agentId?: string
    metadata?: Record<string, any>
  }) {
    try {
      const result = await client.add({
        content: `${interaction.type.toUpperCase()}: ${interaction.content}`,
        containerTag: this.getContainerTag(customerId),
        metadata: {
          type: 'customer_interaction',
          interactionType: interaction.type,
          channel: interaction.channel,
          outcome: interaction.outcome,
          agentId: interaction.agentId,
          timestamp: new Date().toISOString(),
          ...interaction.metadata
        }
      })

      return result
    } catch (error) {
      console.error('Failed to add customer interaction:', error)
      throw error
    }
  }

  async getCustomerHistory(customerId: string, limit: number = 10) {
    try {
      const memories = await client.documents.list({
        containerTags: [this.getContainerTag(customerId)],
        limit,
        sort: 'updatedAt',
        order: 'desc'
      })

      return memories.memories.map(memory => ({
        id: memory.id,
        content: memory.content,
        type: memory.metadata?.interactionType || 'unknown',
        channel: memory.metadata?.channel,
        outcome: memory.metadata?.outcome,
        timestamp: memory.metadata?.timestamp || memory.createdAt,
        agentId: memory.metadata?.agentId
      }))
    } catch (error) {
      console.error('Failed to get customer history:', error)
      throw error
    }
  }

  async searchCustomerContext(customerId: string, query: string) {
    try {
      const results = await client.search.memories({
        q: query,
        containerTag: this.getContainerTag(customerId),
        threshold: 0.6,
        limit: 5,
        rerank: true
      })

      return results.results.map(result => ({
        content: result.memory,
        similarity: result.similarity,
        metadata: result.metadata
      }))
    } catch (error) {
      console.error('Failed to search customer context:', error)
      throw error
    }
  }

  async trackIssue(customerId: string, issue: {
    subject: string
    description: string
    category: string
    priority: 'low' | 'medium' | 'high' | 'urgent'
    status: 'open' | 'pending' | 'resolved'
  }) {
    try {
      const issueContent = `ISSUE: ${issue.subject}\n\nDescription: ${issue.description}\nCategory: ${issue.category}\nPriority: ${issue.priority}\nStatus: ${issue.status}`

      const result = await client.add({
        content: issueContent,
        containerTag: this.getContainerTag(customerId),
        metadata: {
          type: 'support_issue',
          subject: issue.subject,
          category: issue.category,
          priority: issue.priority,
          status: issue.status,
          createdAt: new Date().toISOString()
        }
      })

      return result
    } catch (error) {
      console.error('Failed to track issue:', error)
      throw error
    }
  }

  async updateIssueStatus(issueId: string, status: 'open' | 'pending' | 'resolved' | 'closed', resolution?: string) {
    try {
      // Note: In a real implementation, you'd update the memory
      // For now, we'll add a status update
      const memory = await client.documents.get(issueId)
      const customerId = memory.containerTags?.[0]?.replace('customer_', '') || ''

      const updateContent = `ISSUE UPDATE: ${memory.metadata?.subject}\nStatus changed to: ${status}${resolution ? `\nResolution: ${resolution}` : ''}`

      return await this.addInteraction(customerId, {
        type: 'ticket',
        content: updateContent,
        channel: 'internal',
        outcome: status === 'resolved' ? 'resolved' : 'pending',
        metadata: {
          originalIssueId: issueId,
          statusUpdate: true
        }
      })
    } catch (error) {
      console.error('Failed to update issue status:', error)
      throw error
    }
  }
}
```
</Tab> <Tab title="Python"> ```python customer_context.py from supermemory import Supermemory import os from typing import Dict, List, Any, Optional from datetime import datetime from enum import Enum
class InteractionType(Enum):
    CHAT = "chat"
    EMAIL = "email"
    PHONE = "phone"
    TICKET = "ticket"

class Priority(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    URGENT = "urgent"

class Status(Enum):
    OPEN = "open"
    PENDING = "pending"
    RESOLVED = "resolved"
    CLOSED = "closed"

class CustomerContextManager:
    def __init__(self):
        self.client = Supermemory(api_key=os.getenv("SUPERMEMORY_API_KEY"))

    def _get_container_tag(self, customer_id: str) -> str:
        return f"customer_{customer_id}"

    def add_interaction(self, customer_id: str, interaction: Dict[str, Any]) -> Dict:
        """Add a customer interaction to memory"""
        try:
            content = f"{interaction['type'].upper()}: {interaction['content']}"

            result = self.client.add(
                content=content,
                container_tag=self._get_container_tag(customer_id),
                metadata={
                    'type': 'customer_interaction',
                    'interactionType': interaction['type'],
                    'channel': interaction['channel'],
                    'outcome': interaction.get('outcome'),
                    'agentId': interaction.get('agentId'),
                    'timestamp': datetime.now().isoformat(),
                    **interaction.get('metadata', {})
                }
            )
            return result
        except Exception as e:
            print(f"Failed to add customer interaction: {e}")
            raise

    def get_customer_history(self, customer_id: str, limit: int = 10) -> List[Dict]:
        """Get customer interaction history"""
        try:
            memories = self.client.documents.list(
                container_tags=[self._get_container_tag(customer_id)],
                limit=limit,
                sort='updatedAt',
                order='desc'
            )

            return [
                {
                    'id': memory.id,
                    'content': memory.content,
                    'type': memory.metadata.get('interactionType', 'unknown') if memory.metadata else 'unknown',
                    'channel': memory.metadata.get('channel') if memory.metadata else None,
                    'outcome': memory.metadata.get('outcome') if memory.metadata else None,
                    'timestamp': memory.metadata.get('timestamp', memory.created_at) if memory.metadata else memory.created_at,
                    'agentId': memory.metadata.get('agentId') if memory.metadata else None
                }
                for memory in memories.memories
            ]
        except Exception as e:
            print(f"Failed to get customer history: {e}")
            raise

    def search_customer_context(self, customer_id: str, query: str) -> List[Dict]:
        """Search customer's interaction history"""
        try:
            results = self.client.search.memories(
                q=query,
                container_tag=self._get_container_tag(customer_id),
                threshold=0.6,
                limit=5,
                rerank=True
            )

            return [
                {
                    'content': result.memory,
                    'similarity': result.similarity,
                    'metadata': result.metadata
                }
                for result in results.results
            ]
        except Exception as e:
            print(f"Failed to search customer context: {e}")
            raise

    def track_issue(self, customer_id: str, issue: Dict[str, str]) -> Dict:
        """Track a customer support issue"""
        try:
            issue_content = f"""ISSUE: {issue['subject']}

Description: {issue['description']} Category: {issue['category']} Priority: {issue['priority']} Status: {issue['status']}"""

            result = self.client.add(
                content=issue_content,
                container_tag=self._get_container_tag(customer_id),
                metadata={
                    'type': 'support_issue',
                    'subject': issue['subject'],
                    'category': issue['category'],
                    'priority': issue['priority'],
                    'status': issue['status'],
                    'createdAt': datetime.now().isoformat()
                }
            )
            return result
        except Exception as e:
            print(f"Failed to track issue: {e}")
            raise

    def update_issue_status(self, issue_id: str, status: str, resolution: Optional[str] = None) -> Dict:
        """Update the status of a support issue"""
        try:
            # Get original issue
            memory = self.client.documents.get(issue_id)
            customer_id = (memory.container_tags[0] if memory.container_tags else '').replace('customer_', '')

            update_content = f"ISSUE UPDATE: {memory.metadata.get('subject', 'Unknown')}\nStatus changed to: {status}"
            if resolution:
                update_content += f"\nResolution: {resolution}"

            return self.add_interaction(customer_id, {
                'type': 'ticket',
                'content': update_content,
                'channel': 'internal',
                'outcome': 'resolved' if status == 'resolved' else 'pending',
                'metadata': {
                    'originalIssueId': issue_id,
                    'statusUpdate': True
                }
            })
        except Exception as e:
            print(f"Failed to update issue status: {e}")
            raise
```
</Tab> </Tabs>

Step 2: Support API with Context

<Tabs> <Tab title="Next.js API Route"> ```typescript app/api/support/chat/route.ts import { streamText } from 'ai' import { createOpenAI } from '@ai-sdk/openai' import { CustomerContextManager } from '@/lib/customer-context'
const openai = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY!
})

const contextManager = new CustomerContextManager()

interface Customer {
  id: string
  name: string
  email: string
  tier: 'free' | 'pro' | 'enterprise'
  joinDate: string
}

export async function POST(request: Request) {
  const {
    message,
    customerId,
    customer,
    conversationHistory = [],
    agentId
  } = await request.json()

  try {
    // Get customer history and context
    const [history, contextResults] = await Promise.all([
      contextManager.getCustomerHistory(customerId, 5),
      contextManager.searchCustomerContext(customerId, message)
    ])

    // Build customer context
    const customerContext = `

CUSTOMER PROFILE:

  • Name: ${customer.name}
  • Email: ${customer.email}
  • Tier: ${customer.tier.toUpperCase()}
  • Member since: ${customer.joinDate}

RECENT INTERACTIONS (Last 5): ${history.map(h => - ${h.timestamp}: ${h.type.toUpperCase()} - ${h.content.substring(0, 100)}...).join('\n')}

RELEVANT CONTEXT: ${contextResults.map(c => - ${c.content.substring(0, 150)}... (${(c.similarity * 100).toFixed(1)}% relevant)).join('\n')} `.trim()

    // Determine if escalation is needed
    const escalationKeywords = ['angry', 'frustrated', 'cancel', 'refund', 'legal', 'complaint', 'manager', 'supervisor']
    const needsEscalation = escalationKeywords.some(keyword =>
      message.toLowerCase().includes(keyword)
    ) || customer.tier === 'enterprise'

    const systemPrompt = `You are a helpful customer support agent with access to complete customer history and context.

CUSTOMER CONTEXT: ${customerContext}

SUPPORT GUIDELINES:

  1. Personalization: Address the customer by name and reference their tier/history when relevant

  2. Context Awareness: Use previous interactions to inform your response

  3. Tier-Specific Service:

    • Free: Standard support, guide to self-service resources
    • Pro: Priority support, detailed explanations, proactive suggestions
    • Enterprise: White-glove service, immediate escalation path, dedicated attention
  4. Issue Tracking: If this is a new issue, categorize it (billing, technical, account, product)

  5. Escalation: ${needsEscalation ? 'This interaction may need human agent escalation - provide helpful response but prepare escalation summary' : 'Handle directly unless customer specifically requests human agent'}

RESPONSE STYLE:

  • Professional but friendly
  • Reference specific details from customer history when relevant
  • Provide actionable next steps
  • Include relevant links or resources for their tier level

If you cannot resolve the issue completely, prepare a clear summary for escalation to human agents.`

    const messages = [
      { role: 'system' as const, content: systemPrompt },
      ...conversationHistory,
      { role: 'user' as const, content: message }
    ]

    const result = await streamText({
      model: openai('gpt-5'),
      messages,
      temperature: 0.3,
      maxTokens: 800,
      onFinish: async (completion) => {
        // Store this interaction
        await contextManager.addInteraction(customerId, {
          type: 'chat',
          content: `Customer: ${message}\nAgent: ${completion.text}`,
          channel: 'web_chat',
          outcome: needsEscalation ? 'escalated' : 'resolved',
          agentId,
          metadata: {
            customerTier: customer.tier,
            needsEscalation,
            responseLength: completion.text.length
          }
        })

        // If this looks like a new issue, track it
        if (message.length > 50 && !contextResults.some(c => c.similarity > 0.8)) {
          const issueCategory = categorizeIssue(message)
          const priority = determinePriority(customer.tier, message)

          await contextManager.trackIssue(customerId, {
            subject: message.substring(0, 100),
            description: message,
            category: issueCategory,
            priority,
            status: needsEscalation ? 'pending' : 'open'
          })
        }
      }
    })

    return result.toAIStreamResponse({
      data: {
        needsEscalation,
        customerTier: customer.tier,
        contextCount: contextResults.length
      }
    })

  } catch (error) {
    console.error('Support chat error:', error)
    return Response.json(
      { error: 'Failed to process support request', details: error.message },
      { status: 500 }
    )
  }
}

function categorizeIssue(message: string): string {
  const categories = {
    billing: ['bill', 'charge', 'payment', 'refund', 'price', 'cost'],
    technical: ['error', 'bug', 'broken', 'not working', 'crash', 'slow'],
    account: ['login', 'password', 'access', 'settings', 'profile'],
    product: ['feature', 'how to', 'tutorial', 'help', 'guide']
  }

  const messageLower = message.toLowerCase()

  for (const [category, keywords] of Object.entries(categories)) {
    if (keywords.some(keyword => messageLower.includes(keyword))) {
      return category
    }
  }

  return 'general'
}

function determinePriority(tier: string, message: string): 'low' | 'medium' | 'high' | 'urgent' {
  const urgentKeywords = ['urgent', 'critical', 'emergency', 'down', 'broken']
  const highKeywords = ['important', 'asap', 'soon', 'problem']

  const messageLower = message.toLowerCase()

  if (urgentKeywords.some(keyword => messageLower.includes(keyword))) {
    return 'urgent'
  }

  if (tier === 'enterprise') {
    return highKeywords.some(keyword => messageLower.includes(keyword)) ? 'urgent' : 'high'
  }

  if (tier === 'pro') {
    return highKeywords.some(keyword => messageLower.includes(keyword)) ? 'high' : 'medium'
  }

  return 'low'
}
```
</Tab> <Tab title="Python FastAPI"> ```python support_api.py from fastapi import FastAPI, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel from typing import List, Dict, Any, Optional import openai from customer_context import CustomerContextManager import json import os import re
app = FastAPI()

openai_client = openai.AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
context_manager = CustomerContextManager()

class Customer(BaseModel):
    id: str
    name: str
    email: str
    tier: str
    joinDate: str

class SupportRequest(BaseModel):
    message: str
    customerId: str
    customer: Customer
    conversationHistory: List[Dict[str, str]] = []
    agentId: Optional[str] = None

def categorize_issue(message: str) -> str:
    """Categorize support issue based on message content"""
    categories = {
        'billing': ['bill', 'charge', 'payment', 'refund', 'price', 'cost'],
        'technical': ['error', 'bug', 'broken', 'not working', 'crash', 'slow'],
        'account': ['login', 'password', 'access', 'settings', 'profile'],
        'product': ['feature', 'how to', 'tutorial', 'help', 'guide']
    }

    message_lower = message.lower()

    for category, keywords in categories.items():
        if any(keyword in message_lower for keyword in keywords):
            return category

    return 'general'

def determine_priority(tier: str, message: str) -> str:
    """Determine issue priority based on tier and message content"""
    urgent_keywords = ['urgent', 'critical', 'emergency', 'down', 'broken']
    high_keywords = ['important', 'asap', 'soon', 'problem']

    message_lower = message.lower()

    if any(keyword in message_lower for keyword in urgent_keywords):
        return 'urgent'

    if tier == 'enterprise':
        return 'urgent' if any(keyword in message_lower for keyword in high_keywords) else 'high'

    if tier == 'pro':
        return 'high' if any(keyword in message_lower for keyword in high_keywords) else 'medium'

    return 'low'

@app.post("/support/chat")
async def support_chat(request: SupportRequest):
    try:
        # Get customer history and context
        history = context_manager.get_customer_history(request.customerId, 5)
        context_results = context_manager.search_customer_context(request.customerId, request.message)

        # Build customer context
        customer_context = f"""

CUSTOMER PROFILE:

  • Name: {request.customer.name}
  • Email: {request.customer.email}
  • Tier: {request.customer.tier.upper()}
  • Member since: {request.customer.joinDate}

RECENT INTERACTIONS (Last 5): {chr(10).join([f"- {h['timestamp']}: {h['type'].upper()} - {h['content'][:100]}..." for h in history])}

RELEVANT CONTEXT: {chr(10).join([f"- {c['content'][:150]}... ({c['similarity']*100:.1f}% relevant)" for c in context_results])} """.strip()

        # Determine if escalation is needed
        escalation_keywords = ['angry', 'frustrated', 'cancel', 'refund', 'legal', 'complaint', 'manager', 'supervisor']
        needs_escalation = any(keyword in request.message.lower() for keyword in escalation_keywords) or request.customer.tier == 'enterprise'

        system_prompt = f"""You are a helpful customer support agent with access to complete customer history and context.

CUSTOMER CONTEXT: {customer_context}

SUPPORT GUIDELINES:

  1. Personalization: Address the customer by name and reference their tier/history when relevant

  2. Context Awareness: Use previous interactions to inform your response

  3. Tier-Specific Service:

    • Free: Standard support, guide to self-service resources
    • Pro: Priority support, detailed explanations, proactive suggestions
    • Enterprise: White-glove service, immediate escalation path, dedicated attention
  4. Issue Tracking: If this is a new issue, categorize it (billing, technical, account, product)

  5. Escalation: {'This interaction may need human agent escalation - provide helpful response but prepare escalation summary' if needs_escalation else 'Handle directly unless customer specifically requests human agent'}

RESPONSE STYLE:

  • Professional but friendly
  • Reference specific details from customer history when relevant
  • Provide actionable next steps
  • Include relevant links or resources for their tier level

If you cannot resolve the issue completely, prepare a clear summary for escalation to human agents."""

        messages = [
            {"role": "system", "content": system_prompt},
            *request.conversationHistory,
            {"role": "user", "content": request.message}
        ]

        response = await openai_client.chat.completions.create(
            model="gpt-5",
            messages=messages,
            temperature=0.3,
            max_tokens=800,
            stream=True
        )

        async def generate():
            full_response = ""
            async for chunk in response:
                if chunk.choices[0].delta.content:
                    content = chunk.choices[0].delta.content
                    full_response += content
                    yield f"data: {json.dumps({'content': content})}\n\n"

            # Store interaction after completion
            context_manager.add_interaction(request.customerId, {
                'type': 'chat',
                'content': f"Customer: {request.message}\nAgent: {full_response}",
                'channel': 'web_chat',
                'outcome': 'escalated' if needs_escalation else 'resolved',
                'agentId': request.agentId,
                'metadata': {
                    'customerTier': request.customer.tier,
                    'needsEscalation': needs_escalation,
                    'responseLength': len(full_response)
                }
            })

            # Track new issues
            if len(request.message) > 50 and not any(c['similarity'] > 0.8 for c in context_results):
                issue_category = categorize_issue(request.message)
                priority = determine_priority(request.customer.tier, request.message)

                context_manager.track_issue(request.customerId, {
                    'subject': request.message[:100],
                    'description': request.message,
                    'category': issue_category,
                    'priority': priority,
                    'status': 'pending' if needs_escalation else 'open'
                })

            yield f"data: {json.dumps({'done': True, 'needsEscalation': needs_escalation})}\n\n"

        return StreamingResponse(generate(), media_type="text/plain")

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Support chat error: {str(e)}")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
```
</Tab> </Tabs>

Step 3: Support Dashboard Interface

tsx
'use client'

import { useState, useEffect } from 'react'
import { useChat } from 'ai/react'
import { CustomerContextManager } from '@/lib/customer-context'

interface Customer {
  id: string
  name: string
  email: string
  tier: 'free' | 'pro' | 'enterprise'
  joinDate: string
}

interface SupportTicket {
  id: string
  subject: string
  status: 'open' | 'pending' | 'resolved' | 'closed'
  priority: 'low' | 'medium' | 'high' | 'urgent'
  category: string
  createdAt: string
}

export default function SupportDashboard() {
  const [selectedCustomer, setSelectedCustomer] = useState<Customer | null>(null)
  const [customerHistory, setCustomerHistory] = useState<any[]>([])
  const [tickets, setTickets] = useState<SupportTicket[]>([])
  const [showEscalation, setShowEscalation] = useState(false)
  const [agentId] = useState('agent_001') // In real app, get from auth

  const contextManager = new CustomerContextManager()

  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/support/chat',
    body: {
      customerId: selectedCustomer?.id,
      customer: selectedCustomer,
      agentId
    },
    onFinish: (message, { data }) => {
      if (data?.needsEscalation) {
        setShowEscalation(true)
      }
      // Refresh customer history
      if (selectedCustomer) {
        loadCustomerHistory(selectedCustomer.id)
      }
    }
  })

  // Mock customers - in real app, fetch from your customer database
  const mockCustomers: Customer[] = [
    {
      id: 'cust_001',
      name: 'Sarah Johnson',
      email: '[email protected]',
      tier: 'pro',
      joinDate: '2023-06-15'
    },
    {
      id: 'cust_002',
      name: 'TechCorp Inc',
      email: '[email protected]',
      tier: 'enterprise',
      joinDate: '2022-03-20'
    },
    {
      id: 'cust_003',
      name: 'Mike Chen',
      email: '[email protected]',
      tier: 'free',
      joinDate: '2024-01-10'
    }
  ]

  const loadCustomerHistory = async (customerId: string) => {
    try {
      const history = await contextManager.getCustomerHistory(customerId, 10)
      setCustomerHistory(history)
    } catch (error) {
      console.error('Failed to load customer history:', error)
    }
  }

  const handleCustomerSelect = async (customer: Customer) => {
    setSelectedCustomer(customer)
    await loadCustomerHistory(customer.id)
    setShowEscalation(false)
  }

  const getTierColor = (tier: string) => {
    switch (tier) {
      case 'enterprise': return 'bg-purple-100 text-purple-800'
      case 'pro': return 'bg-blue-100 text-blue-800'
      case 'free': return 'bg-gray-100 text-gray-800'
      default: return 'bg-gray-100 text-gray-800'
    }
  }

  const getPriorityColor = (priority: string) => {
    switch (priority) {
      case 'urgent': return 'bg-red-100 text-red-800'
      case 'high': return 'bg-orange-100 text-orange-800'
      case 'medium': return 'bg-yellow-100 text-yellow-800'
      case 'low': return 'bg-green-100 text-green-800'
      default: return 'bg-gray-100 text-gray-800'
    }
  }

  return (
    <div className="h-screen flex">
      <div className="w-80 bg-white border-r border-gray-200 overflow-y-auto">
        <div className="p-4 border-b">
          <h2 className="text-lg font-semibold">Customers</h2>
        </div>
        <div className="divide-y divide-gray-200">
          {mockCustomers.map((customer) => (
            <div
              key={customer.id}
              onClick={() => handleCustomerSelect(customer)}
              className={`p-4 cursor-pointer hover:bg-gray-50 ${
                selectedCustomer?.id === customer.id ? 'bg-blue-50 border-r-2 border-blue-500' : ''
              }`}
            >
              <div className="flex items-center justify-between mb-2">
                <div className="font-medium text-gray-900">{customer.name}</div>
                <span className={`px-2 py-1 text-xs rounded-full ${getTierColor(customer.tier)}`}>
                  {customer.tier}
                </span>
              </div>
              <div className="text-sm text-gray-600">{customer.email}</div>
              <div className="text-xs text-gray-500 mt-1">
                Member since {customer.joinDate}
              </div>
            </div>
          ))}
        </div>
      </div>
      <div className="flex-1 flex flex-col">
        {selectedCustomer ? (
          <>
            <div className="bg-white border-b border-gray-200 p-4">
              <div className="flex items-center justify-between">
                <div>
                  <h1 className="text-xl font-semibold">{selectedCustomer.name}</h1>
                  <p className="text-gray-600">{selectedCustomer.email}</p>
                </div>
                <div className="flex items-center space-x-4">
                  <span className={`px-3 py-1 text-sm rounded-full ${getTierColor(selectedCustomer.tier)}`}>
                    {selectedCustomer.tier.toUpperCase()} Customer
                  </span>
                  {showEscalation && (
                    <div className="bg-red-100 text-red-800 px-3 py-1 text-sm rounded-full">
                      Needs Escalation
                    </div>
                  )}
                </div>
              </div>
            </div>

            <div className="flex-1 flex">
              <div className="flex-1 flex flex-col">
                <div className="flex-1 overflow-y-auto p-4 space-y-4">
                  {messages.length === 0 && (
                    <div className="text-center py-8 text-gray-500">
                      <div className="text-lg font-medium">Welcome to Support Chat</div>
                      <p className="mt-2">
                        Start a conversation with {selectedCustomer.name}
                      </p>
                      <div className="mt-4 text-sm">
                        <p><strong>Customer Tier:</strong> {selectedCustomer.tier}</p>
                        <p><strong>Join Date:</strong> {selectedCustomer.joinDate}</p>
                      </div>
                    </div>
                  )}

                  {messages.map((message) => (
                    <div
                      key={message.id}
                      className={`flex ${
                        message.role === 'user' ? 'justify-end' : 'justify-start'
                      }`}
                    >
                      <div
                        className={`max-w-2xl p-4 rounded-lg ${
                          message.role === 'user'
                            ? 'bg-blue-500 text-white'
                            : 'bg-gray-100 text-gray-900'
                        }`}
                      >
                        <div className="flex items-center space-x-2 mb-2">
                          <span className="text-sm font-medium">
                            {message.role === 'user' ? selectedCustomer.name : 'Support Agent'}
                          </span>
                          <span className="text-xs opacity-75">
                            {new Date().toLocaleTimeString()}
                          </span>
                        </div>
                        <div className="whitespace-pre-wrap">{message.content}</div>
                      </div>
                    </div>
                  ))}

                  {isLoading && (
                    <div className="flex justify-start">
                      <div className="max-w-2xl p-4 bg-gray-100 rounded-lg">
                        <div className="flex items-center space-x-2">
                          <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
                          <span className="text-sm">Agent is typing...</span>
                        </div>
                      </div>
                    </div>
                  )}
                </div>
                <div className="border-t border-gray-200 p-4">
                  <form onSubmit={handleSubmit} className="flex space-x-2">
                    <input
                      value={input}
                      onChange={handleInputChange}
                      placeholder={`Respond to ${selectedCustomer.name}...`}
                      className="flex-1 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                      disabled={isLoading}
                    />
                    <button
                      type="submit"
                      disabled={isLoading || !input.trim()}
                      className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
                    >
                      Send
                    </button>
                  </form>
                </div>
              </div>
              <div className="w-80 bg-gray-50 border-l border-gray-200 overflow-y-auto">
                <div className="p-4 border-b bg-white">
                  <h3 className="font-medium">Customer History</h3>
                </div>
                <div className="p-4 space-y-3">
                  {customerHistory.map((interaction, index) => (
                    <div key={index} className="bg-white p-3 rounded-lg border text-sm">
                      <div className="flex items-center justify-between mb-2">
                        <span className="font-medium capitalize">{interaction.type}</span>
                        <span className="text-xs text-gray-500">
                          {new Date(interaction.timestamp).toLocaleDateString()}
                        </span>
                      </div>
                      <p className="text-gray-700 line-clamp-3">
                        {interaction.content.length > 100
                          ? `${interaction.content.substring(0, 100)}...`
                          : interaction.content
                        }
                      </p>
                      {interaction.outcome && (
                        <div className="mt-2">
                          <span className={`text-xs px-2 py-1 rounded ${
                            interaction.outcome === 'resolved'
                              ? 'bg-green-100 text-green-800'
                              : interaction.outcome === 'escalated'
                              ? 'bg-red-100 text-red-800'
                              : 'bg-yellow-100 text-yellow-800'
                          }`}>
                            {interaction.outcome}
                          </span>
                        </div>
                      )}
                    </div>
                  ))}

                  {customerHistory.length === 0 && (
                    <div className="text-center py-8 text-gray-500">
                      <p>No previous interactions</p>
                    </div>
                  )}
                </div>
              </div>
            </div>
          </>
        ) : (
          <div className="flex-1 flex items-center justify-center text-gray-500">
            <div className="text-center">
              <div className="text-lg font-medium">Customer Support</div>
              <p className="mt-2">Select a customer to start a support conversation</p>
            </div>
          </div>
        )}
      </div>
    </div>
  )
}

Testing Your Support System

Step 4: Test Support Scenarios

  1. Test Customer Tiers:

    • Free tier: Basic responses, self-service guidance
    • Pro tier: Detailed help, proactive suggestions
    • Enterprise: White-glove service, escalation readiness
  2. Test Memory & Context:

    • Ask about a previous issue
    • Reference customer preferences
    • Follow up on unresolved tickets
  3. Test Escalation Triggers:

    • Use keywords like "angry", "manager", "refund"
    • Test enterprise customer automatic escalation

This comprehensive customer support recipe provides the foundation for building intelligent, context-aware support systems that improve customer satisfaction through personalized service.


Customize this recipe based on your specific support workflows and customer needs.