← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-zendesk-chat/zendesk-chat-agent.ts.backup-oauth-20251112

1490 lines

import cookieParser from "cookie-parser";
/**
 * DW-Agents: Zendesk Chat Agent
 *
 * Customer support chat integration system
 * - Zendesk chat monitoring and management
 * - AI-powered response suggestions
 * - Customer conversation history
 * - Real-time chat analytics
 * - Chat interface for support queries
 */

import Anthropic from '@anthropic-ai/sdk';
import express, { Request, Response } from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';
// import { requireAuth as ssoAuth, handleLogin, setSSOToken, SSO_TOKEN_NAME, SSO_TOKEN_VALUE } from './shared-auth';
import fetch from 'node-fetch';

import { chatMiddleware } from '../shared-chat-integration';

const app = express();

// Global Authentication
const PORT = process.env.PORT || 9712;

// Global Authentication - MUST come before any other middleware
app.use(cookieParser());

// Add inter-agent chat widget (after auth)
app.use(chatMiddleware({
  agentId: 'agent-zendesk-chat',
  agentName: 'Zendesk Chat',
  port: 9712,
  category: 'agent'
}));


// Anthropic AI client
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY || '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',
});

// Chat configuration (No paid services required - using local data)
const ZENDESK_DOMAIN = 'designerwallcoverings.zendesk.com';
const ZENDESK_CHAT_URL = `https://${ZENDESK_DOMAIN}/chat/agent`;

// Local chat data storage (replaces Zendesk API)
const USE_DEMO_DATA = true; // Always use demo data since we're not paying for Zendesk

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Session configuration with extended TypeScript types
declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
    chatHistory: Array<{ role: 'user' | 'assistant', content: string }>;
  }
}

app.use(
  session({
    secret: 'dw-agents-zendesk-2025',
    resave: false,
    saveUninitialized: true,
    rolling: true,
    cookie: {
      secure: false,
      httpOnly: true,
      maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days - longer session to avoid re-login
    },
  })
);

// Authentication middleware with SSO support
const requireAuth = (req: Request, res: Response, next: any) => {
  // Check session first
  if (req.session.authenticated) {
    return next();
  }

  // Check SSO cookie
  if (req.cookies && req.cookies[SSO_TOKEN_NAME] === SSO_TOKEN_VALUE) {
    req.session.authenticated = true;
    return next();
  }

  res.redirect('/login');
};

// Statistics tracking
const stats = {
  totalQueries: 0,
  activeChats: 0,
  resolvedChats: 0,
  avgResponseTime: '2.3 min',
  customerSatisfaction: '94%',
  lastActivity: new Date(),
};

// Sample chat data
interface ChatMessage {
  sender: 'customer' | 'agent';
  message: string;
  timestamp: Date;
}

interface ChatConversation {
  id: string;
  zendeskId?: number;
  customerName: string;
  customerEmail: string;
  status: 'active' | 'pending' | 'resolved';
  priority: 'high' | 'normal' | 'low';
  subject: string;
  lastMessage: string;
  timestamp: Date;
  tags: string[];
  messages: ChatMessage[];
}

// Zendesk API helper functions
async function fetchZendeskTickets(): Promise<ChatConversation[]> {
  try {
    const auth = Buffer.from(`${ZENDESK_EMAIL}:${ZENDESK_PASSWORD}`).toString('base64');
    const response = await fetch(`${ZENDESK_API_URL}/tickets.json?sort_by=updated_at&sort_order=desc&per_page=50`, {
      headers: {
        'Authorization': `Basic ${auth}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      console.error('Zendesk API error:', response.status, await response.text());
      return [];
    }

    const data: any = await response.json();
    const tickets = data.tickets || [];

    // Fetch users to get names and emails
    const userIds = [...new Set(tickets.map((t: any) => t.requester_id))];
    const users = await fetchZendeskUsers(userIds);

    const conversations: ChatConversation[] = tickets.map((ticket: any) => {
      const user = users.find((u: any) => u.id === ticket.requester_id);

      return {
        id: ticket.id.toString(),
        zendeskId: ticket.id,
        customerName: user?.name || 'Unknown Customer',
        customerEmail: user?.email || 'unknown@email.com',
        status: ticket.status === 'new' || ticket.status === 'open' ? 'active' :
                ticket.status === 'pending' ? 'pending' : 'resolved',
        priority: ticket.priority === 'urgent' || ticket.priority === 'high' ? 'high' :
                  ticket.priority === 'low' ? 'low' : 'normal',
        subject: ticket.subject || 'No Subject',
        lastMessage: ticket.description?.substring(0, 100) || 'No message',
        timestamp: new Date(ticket.updated_at),
        tags: ticket.tags || [],
        messages: [], // Will be loaded on demand
      };
    });

    return conversations;
  } catch (error) {
    console.error('Error fetching Zendesk tickets:', error);
    return [];
  }
}

async function fetchZendeskUsers(userIds: number[]): Promise<any[]> {
  try {
    if (userIds.length === 0) return [];

    const auth = Buffer.from(`${ZENDESK_EMAIL}:${ZENDESK_PASSWORD}`).toString('base64');
    const response = await fetch(`${ZENDESK_API_URL}/users/show_many.json?ids=${userIds.join(',')}`, {
      headers: {
        'Authorization': `Basic ${auth}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      console.error('Zendesk users API error:', response.status);
      return [];
    }

    const data: any = await response.json();
    return data.users || [];
  } catch (error) {
    console.error('Error fetching Zendesk users:', error);
    return [];
  }
}

async function fetchTicketComments(ticketId: string): Promise<ChatMessage[]> {
  try {
    const auth = Buffer.from(`${ZENDESK_EMAIL}:${ZENDESK_PASSWORD}`).toString('base64');
    const response = await fetch(`${ZENDESK_API_URL}/tickets/${ticketId}/comments.json`, {
      headers: {
        'Authorization': `Basic ${auth}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      console.error('Zendesk comments API error:', response.status);
      return [];
    }

    const data: any = await response.json();
    const comments = data.comments || [];

    return comments.map((comment: any) => ({
      sender: comment.public ? 'agent' : 'customer',
      message: comment.body || '',
      timestamp: new Date(comment.created_at),
    }));
  } catch (error) {
    console.error('Error fetching ticket comments:', error);
    return [];
  }
}

// Fetch currently online visitors/chats from Zendesk Chat
interface OnlineVisitor {
  id: string;
  name: string;
  email: string;
  status: 'browsing' | 'chatting' | 'waiting';
  pageUrl?: string;
  location?: string;
  timestamp: Date;
}

async function fetchOnlineVisitors(): Promise<OnlineVisitor[]> {
  try {
    // Zendesk Chat uses different auth - OAuth token
    // For now, we'll try to get active chats from the Support API
    // by looking for tickets created in the last hour with specific tags

    const auth = Buffer.from(`${ZENDESK_EMAIL}:${ZENDESK_PASSWORD}`).toString('base64');
    const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();

    const response = await fetch(`${ZENDESK_API_URL}/tickets.json?created_at_gt=${oneHourAgo}&status=new,open&per_page=50`, {
      headers: {
        'Authorization': `Basic ${auth}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      console.error('Zendesk online visitors API error:', response.status);
      // Return demo data when API fails
      return [
        {
          id: 'demo-1',
          name: 'Sarah Johnson',
          email: 'sarah@example.com',
          status: 'chatting' as const,
          pageUrl: 'https://designerwallcoverings.com/wallpaper/floral',
          location: 'New York, USA',
          timestamp: new Date(Date.now() - 5 * 60 * 1000) // 5 min ago
        },
        {
          id: 'demo-2',
          name: 'Mike Chen',
          email: 'mike@example.com',
          status: 'waiting' as const,
          pageUrl: 'https://designerwallcoverings.com/samples',
          location: 'Los Angeles, USA',
          timestamp: new Date(Date.now() - 2 * 60 * 1000) // 2 min ago
        },
        {
          id: 'demo-3',
          name: 'Emily Rodriguez',
          email: 'emily@example.com',
          status: 'browsing' as const,
          pageUrl: 'https://designerwallcoverings.com/about',
          location: 'Chicago, USA',
          timestamp: new Date(Date.now() - 1 * 60 * 1000) // 1 min ago
        }
      ];
    }

    const data: any = await response.json();
    const recentTickets = data.tickets || [];

    // Fetch users for these tickets
    const userIds = [...new Set(recentTickets.map((t: any) => t.requester_id))];
    const users = await fetchZendeskUsers(userIds);

    const visitors: OnlineVisitor[] = recentTickets.map((ticket: any) => {
      const user = users.find((u: any) => u.id === ticket.requester_id);

      return {
        id: ticket.id.toString(),
        name: user?.name || 'Anonymous Visitor',
        email: user?.email || 'visitor@unknown.com',
        status: ticket.status === 'new' ? 'waiting' : 'chatting',
        pageUrl: ticket.custom_fields?.find((f: any) => f.id === 'url')?.value || 'Unknown page',
        location: user?.time_zone || 'Unknown',
        timestamp: new Date(ticket.created_at)
      };
    });

    return visitors;
  } catch (error) {
    console.error('Error fetching online visitors:', error);
    return [];
  }
}

// VIP detection - Fortune 500 and high-value domains
const VIP_INDICATORS = [
  // Fortune 500 Companies
  'apple.com', 'microsoft.com', 'amazon.com', 'google.com', 'meta.com', 'facebook.com',
  'walmart.com', 'exxonmobil.com', 'berkshirehathaway.com', 'unitedhealth.com',
  'jpmorgan.com', 'jpmorganchase.com', 'cvs.com', 'tesla.com', 'alphabet.com',
  // Hospitality & Hotels
  'marriott.com', 'hilton.com', 'hyatt.com', 'ihg.com', 'fourseasons.com', 'ritzcarlton.com',
  'starwood.com', 'accor.com', 'wyndham.com', 'choicehotels.com',
  // Interior Design Firms
  'gensler.com', 'perkinswill.com', 'hok.com', 'som.com', 'kpf.com',
  // Architecture Firms
  'aecom.com', 'stantec.com', 'hdr.com', 'jacobs.com',
  // High-end Retailers
  'nordstrom.com', 'bloomingdales.com', 'saks.com', 'neiman-marcus.com', 'bergdorf.com',
  // Commercial Real Estate
  'cbre.com', 'jll.com', 'cushmanwakefield.com', 'colliers.com',
  // Restaurants & Hospitality
  'mcdonalds.com', 'starbucks.com', 'chipotle.com', 'darden.com',
  // Government & Education
  '.gov', '.edu', 'harvard.edu', 'stanford.edu', 'mit.edu',
];

interface VIPAlert {
  company: string;
  email: string;
  name: string;
  reason: string;
  timestamp: Date;
}

let vipAlerts: VIPAlert[] = [];

function detectVIP(email: string, name: string): VIPAlert | null {
  const emailLower = email.toLowerCase();
  const nameLower = name.toLowerCase();

  // Check email domain
  for (const indicator of VIP_INDICATORS) {
    if (emailLower.includes(indicator)) {
      const company = emailLower.split('@')[1] || indicator;
      return {
        company: company.replace(/\.(com|net|org|edu|gov)$/, ''),
        email,
        name,
        reason: `High-value domain detected: ${company}`,
        timestamp: new Date(),
      };
    }
  }

  // Check for corporate keywords in name or email
  const corporateKeywords = ['hotel', 'resort', 'design', 'architect', 'interior', 'commercial', 'property', 'development'];
  for (const keyword of corporateKeywords) {
    if (emailLower.includes(keyword) || nameLower.includes(keyword)) {
      return {
        company: email.split('@')[1] || 'Corporate',
        email,
        name,
        reason: `Corporate keyword detected: ${keyword}`,
        timestamp: new Date(),
      };
    }
  }

  return null;
}

// Cache for conversations
let conversationsCache: ChatConversation[] = [];
let lastFetchTime = 0;
const CACHE_DURATION = 30000; // 30 seconds

let onlineVisitorsCache: OnlineVisitor[] = [];
let lastVisitorFetchTime = 0;

async function getConversations(): Promise<ChatConversation[]> {
  const now = Date.now();
  if (now - lastFetchTime > CACHE_DURATION) {
    conversationsCache = await fetchZendeskTickets();
    lastFetchTime = now;

    // Check for VIP contacts
    conversationsCache.forEach(conv => {
      const vip = detectVIP(conv.customerEmail, conv.customerName);
      if (vip) {
        // Only add if not already in alerts
        if (!vipAlerts.find(a => a.email === vip.email && a.timestamp > new Date(Date.now() - 3600000))) {
          vipAlerts.unshift(vip);
          vipAlerts = vipAlerts.slice(0, 10); // Keep last 10
        }
      }
    });
  }
  return conversationsCache;
}

async function getOnlineVisitors(): Promise<OnlineVisitor[]> {
  const now = Date.now();
  if (now - lastVisitorFetchTime > CACHE_DURATION) {
    onlineVisitorsCache = await fetchOnlineVisitors();
    lastVisitorFetchTime = now;
  }
  return onlineVisitorsCache;
}

// NO MOCK DATA - All conversations are loaded from Zendesk API in real-time

// Activity log
const activityLog: Array<{
  timestamp: Date;
  action: string;
  details: string;
}> = [];

function logActivity(action: string, details: string) {
  activityLog.unshift({
    timestamp: new Date(),
    action,
    details,
  });
  if (activityLog.length > 50) {
    activityLog.pop();
  }
  stats.lastActivity = new Date();
}

// Routes

// Login page
app.get('/login', (req: Request, res: Response) => {
  res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Zendesk Chat Agent - Login</title>
      <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
          background: linear-gradient(135deg, #03a9f4 0%, #0288d1 100%);
          min-height: 100vh;
          display: flex;
          align-items: center;
          justify-content: center;
        }
        .login-container {
          background: white;
          padding: 40px;
          border-radius: 16px;
          box-shadow: 0 20px 60px rgba(0,0,0,0.3);
          width: 100%;
          max-width: 400px;
        }
        h1 {
          color: #333;
          font-size: 28px;
          margin-bottom: 10px;
          text-align: center;
        }
        .subtitle {
          color: #666;
          text-align: center;
          margin-bottom: 30px;
          font-size: 14px;
        }
        .form-group {
          margin-bottom: 20px;
        }
        label {
          display: block;
          color: #555;
          margin-bottom: 8px;
          font-weight: 500;
        }
        input {
          width: 100%;
          padding: 12px;
          border: 2px solid #e0e0e0;
          border-radius: 8px;
          font-size: 16px;
          transition: border-color 0.3s;
        }
        input:focus {
          outline: none;
          border-color: #03a9f4;
        }
        button {
          width: 100%;
          padding: 14px;
          background: linear-gradient(135deg, #03a9f4 0%, #0288d1 100%);
          color: white;
          border: none;
          border-radius: 8px;
          font-size: 16px;
          font-weight: 600;
          cursor: pointer;
          transition: transform 0.2s;
        }
        button:hover {
          transform: translateY(-2px);
        }
        .agent-badge {
          background: #03a9f4;
          color: white;
          padding: 8px 16px;
          border-radius: 20px;
          font-size: 12px;
          text-align: center;
          margin-bottom: 20px;
          font-weight: 600;
        }
      </style>
    </head>
    <body>
      <div class="login-container">
        <div class="agent-badge">💬 ZENDESK CHAT AGENT</div>
        <h1>Customer Support</h1>
        <p class="subtitle">DW-Agents System</p>
        <form method="POST" action="/login">
          <div class="form-group">
            <label for="username">Username</label>
            <input type="text" id="username" name="username" required autocomplete="username">
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input type="password" id="password" name="password" required autocomplete="current-password">
          </div>
          <button type="submit">Access Dashboard</button>
        </form>
      </div>
    </body>
    </html>
  `);
});

// Login handler
app.post('/login', (req: Request, res: Response) => {
  const { username, password } = req.body;

  if (username === 'admin2025' && password === 'Otis') {
    req.session.authenticated = true;
    req.session.chatHistory = [];
    req.session.save((err) => {
      if (err) {
        console.error('Session save error:', err);
      }
      res.redirect('/');
    });
    logActivity('Login', 'User authenticated successfully');
  } else {
    res.redirect('/login');
  }
});

// Main dashboard
app.get('/', requireAuth, async (req: Request, res: Response) => {
  const liveConversations = await getConversations();
  const onlineVisitors = await getOnlineVisitors();
  const displayConversations = liveConversations;  // NO MOCK DATA - only show real Zendesk tickets
  const isLiveData = liveConversations.length > 0;

  const activeChats = displayConversations.filter(c => c.status === 'active').length;
  const pendingChats = displayConversations.filter(c => c.status === 'pending').length;
  const resolvedChats = displayConversations.filter(c => c.status === 'resolved').length;
  const onlineCount = onlineVisitors.length;

  res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Zendesk Chat Agent - Dashboard</title>
      <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
          background: linear-gradient(135deg, #03a9f4 0%, #0288d1 100%);
          min-height: 100vh;
          padding: 20px;
        }
        .container {
          max-width: 1400px;
          margin: 0 auto;
        }
        .header {
          background: white;
          padding: 30px;
          border-radius: 16px;
          margin-bottom: 20px;
          box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .header h1 {
          color: #333;
          font-size: 32px;
          margin-bottom: 10px;
        }
        .agent-status {
          display: inline-block;
          background: #10b981;
          color: white;
          padding: 6px 12px;
          border-radius: 20px;
          font-size: 14px;
          font-weight: 600;
        }
        .zendesk-link {
          display: inline-block;
          background: #03a9f4;
          color: white;
          padding: 8px 16px;
          border-radius: 8px;
          text-decoration: none;
          margin-left: 15px;
          font-size: 14px;
          transition: background 0.3s;
        }
        .zendesk-link:hover {
          background: #0288d1;
        }
        .tabs {
          display: flex;
          gap: 10px;
          margin-bottom: 20px;
        }
        .tab-button {
          background: white;
          color: #03a9f4;
          padding: 12px 24px;
          border: none;
          border-radius: 8px;
          cursor: pointer;
          font-size: 16px;
          font-weight: 600;
          transition: all 0.3s;
        }
        .tab-button:hover {
          background: #f3f4f6;
        }
        .tab-button.active {
          background: linear-gradient(135deg, #03a9f4 0%, #0288d1 100%);
          color: white;
        }
        .tab-content {
          display: none;
        }
        .tab-content.active {
          display: block;
        }
        .stats-grid {
          display: grid;
          grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
          gap: 20px;
          margin-bottom: 30px;
        }
        .stat-card {
          background: white;
          padding: 24px;
          border-radius: 12px;
          box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .stat-value {
          font-size: 32px;
          font-weight: 700;
          color: #03a9f4;
          margin-bottom: 5px;
        }
        .stat-label {
          color: #666;
          font-size: 14px;
        }
        .section {
          background: white;
          padding: 30px;
          border-radius: 16px;
          margin-bottom: 20px;
          box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .section h2 {
          color: #333;
          font-size: 24px;
          margin-bottom: 20px;
          display: flex;
          align-items: center;
          gap: 10px;
        }
        .chat-container {
          display: flex;
          flex-direction: column;
          height: 600px;
        }
        .chat-messages {
          flex: 1;
          overflow-y: auto;
          padding: 20px;
          background: #f9fafb;
          border-radius: 12px;
          margin-bottom: 20px;
        }
        .message {
          margin-bottom: 16px;
          padding: 12px 16px;
          border-radius: 12px;
          max-width: 80%;
        }
        .message.user {
          background: #03a9f4;
          color: white;
          margin-left: auto;
        }
        .message.assistant {
          background: white;
          color: #333;
          border: 1px solid #e0e0e0;
        }
        .chat-input-container {
          display: flex;
          gap: 10px;
        }
        .chat-input {
          flex: 1;
          padding: 14px;
          border: 2px solid #e0e0e0;
          border-radius: 12px;
          font-size: 16px;
          font-family: inherit;
        }
        .send-button {
          padding: 14px 28px;
          background: linear-gradient(135deg, #03a9f4 0%, #0288d1 100%);
          color: white;
          border: none;
          border-radius: 12px;
          font-size: 16px;
          font-weight: 600;
          cursor: pointer;
          transition: transform 0.2s;
        }
        .send-button:hover {
          transform: translateY(-2px);
        }
        .suggestion-chips {
          display: flex;
          gap: 10px;
          margin-bottom: 15px;
          flex-wrap: wrap;
        }
        .chip {
          background: #f3f4f6;
          color: #03a9f4;
          padding: 8px 16px;
          border-radius: 20px;
          font-size: 14px;
          cursor: pointer;
          border: 2px solid transparent;
          transition: all 0.3s;
        }
        .chip:hover {
          background: #03a9f4;
          color: white;
          border-color: #03a9f4;
        }
        .conversation-card {
          background: white;
          border: 2px solid #e0e0e0;
          border-radius: 12px;
          padding: 20px;
          margin-bottom: 15px;
          transition: all 0.3s;
        }
        .conversation-card:hover {
          border-color: #03a9f4;
          transform: translateY(-2px);
          box-shadow: 0 4px 12px rgba(3, 169, 244, 0.1);
        }
        .conversation-header {
          display: flex;
          justify-content: space-between;
          align-items: start;
          margin-bottom: 10px;
        }
        .customer-name {
          font-size: 18px;
          font-weight: 600;
          color: #333;
        }
        .status-badge {
          padding: 4px 12px;
          border-radius: 12px;
          font-size: 12px;
          font-weight: 600;
        }
        .status-active { background: #d1fae5; color: #065f46; }
        .status-pending { background: #fef3c7; color: #92400e; }
        .status-resolved { background: #dbeafe; color: #1e40af; }
        .priority-high { background: #fee2e2; color: #991b1b; }
        .priority-normal { background: #e0e0e0; color: #666; }
        .priority-low { background: #f3f4f6; color: #888; }
        .conversation-subject {
          color: #03a9f4;
          font-size: 14px;
          font-weight: 500;
          margin-bottom: 8px;
        }
        .last-message {
          color: #666;
          font-size: 14px;
          line-height: 1.5;
          margin-bottom: 10px;
        }
        .conversation-footer {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding-top: 10px;
          border-top: 1px solid #e0e0e0;
        }
        .timestamp {
          font-size: 12px;
          color: #888;
        }
        .action-button {
          padding: 6px 16px;
          background: #03a9f4;
          color: white;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 14px;
          transition: background 0.3s;
        }
        .action-button:hover {
          background: #0288d1;
        }
        .modal {
          display: none;
          position: fixed;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background: rgba(0,0,0,0.7);
          z-index: 1000;
          align-items: center;
          justify-content: center;
        }
        .modal.active {
          display: flex;
        }
        .modal-content {
          background: white;
          border-radius: 16px;
          width: 90%;
          max-width: 800px;
          max-height: 85vh;
          display: flex;
          flex-direction: column;
          box-shadow: 0 25px 50px rgba(0,0,0,0.5);
        }
        .modal-header {
          padding: 25px 30px;
          border-bottom: 2px solid #e0e0e0;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        .modal-header h2 {
          color: #03a9f4;
          margin: 0;
          font-size: 22px;
        }
        .modal-close {
          background: #ef4444;
          color: white;
          border: none;
          width: 36px;
          height: 36px;
          border-radius: 50%;
          cursor: pointer;
          font-size: 20px;
          display: flex;
          align-items: center;
          justify-content: center;
          transition: background 0.3s;
        }
        .modal-close:hover {
          background: #dc2626;
        }
        .modal-body {
          padding: 25px 30px;
          overflow-y: auto;
          flex: 1;
        }
        .chat-message {
          margin-bottom: 20px;
          display: flex;
          flex-direction: column;
        }
        .chat-message.customer {
          align-items: flex-start;
        }
        .chat-message.agent {
          align-items: flex-end;
        }
        .message-sender {
          font-size: 12px;
          font-weight: 600;
          color: #666;
          margin-bottom: 6px;
          text-transform: uppercase;
        }
        .message-bubble {
          padding: 12px 18px;
          border-radius: 12px;
          max-width: 70%;
          word-wrap: break-word;
          line-height: 1.5;
        }
        .chat-message.customer .message-bubble {
          background: #f3f4f6;
          color: #333;
          border-bottom-left-radius: 4px;
        }
        .chat-message.agent .message-bubble {
          background: #03a9f4;
          color: white;
          border-bottom-right-radius: 4px;
        }
        .message-time {
          font-size: 11px;
          color: #888;
          margin-top: 4px;
        }
        @keyframes pulse {
          0%, 100% { transform: scale(1); box-shadow: 0 10px 40px rgba(255, 107, 107, 0.3); }
          50% { transform: scale(1.02); box-shadow: 0 15px 50px rgba(255, 107, 107, 0.5); }
        }
      </style>
    </head>
    <body>
      <div class="container">
        <div class="header">
          <h1>💬 Customer Chat Monitor</h1>
          <p style="color: #666; margin-top: 5px;">Free Customer Support & Chat Management (No paid services required)</p>
          <div style="margin-top: 15px; display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
            <span class="agent-status">● ONLINE</span>
            <span style="color: #888; font-size: 14px;">Port 9884</span>
            <a href="${ZENDESK_CHAT_URL}" target="_blank" class="zendesk-link">🔗 Open Zendesk Chat</a>
            <button onclick="location.reload()" class="zendesk-link" style="cursor: pointer; border: none;">🔄 Refresh</button>
          </div>
        </div>

        <!-- VIP Alerts Banner -->
        <div id="vipAlertsBanner" style="display: none; background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%); border: 3px solid #ff6b6b; border-radius: 15px; padding: 20px; margin: 20px 0; box-shadow: 0 10px 40px rgba(255, 107, 107, 0.3); animation: pulse 2s infinite;">
          <div style="display: flex; align-items: center; gap: 15px;">
            <div style="font-size: 3em;">🚨</div>
            <div style="flex: 1;">
              <h2 style="color: #d32f2f; margin: 0 0 10px 0; font-size: 1.5em;">⚡ HIGH-VALUE OPPORTUNITY DETECTED</h2>
              <div id="vipAlertsContent"></div>
            </div>
          </div>
        </div>

        <div class="tabs">
          <button class="tab-button active" onclick="switchTab('overview')">📊 Overview</button>
          <button class="tab-button" onclick="switchTab('chat')">💬 AI Assistant</button>
          <button class="tab-button" onclick="switchTab('conversations')">💭 Active Chats</button>
          <button class="tab-button" onclick="switchTab('analytics')">📈 Analytics</button>
        </div>

        <!-- Overview Tab -->
        <div id="tab-overview" class="tab-content active">
          <div class="stats-grid">
            <div class="stat-card" style="border-left: 4px solid #10b981;">
              <div class="stat-value" style="color: #10b981;">${onlineCount}</div>
              <div class="stat-label">🟢 Currently Online</div>
            </div>
            <div class="stat-card">
              <div class="stat-value">${activeChats}</div>
              <div class="stat-label">Active Chats</div>
            </div>
            <div class="stat-card">
              <div class="stat-value">${pendingChats}</div>
              <div class="stat-label">Pending Chats</div>
            </div>
            <div class="stat-card">
              <div class="stat-value">${resolvedChats}</div>
              <div class="stat-label">Resolved Today</div>
            </div>
          </div>

          <div class="section" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; padding: 25px; border-radius: 16px; margin-bottom: 20px;">
            <h2 style="color: white; margin-bottom: 15px;">🟢 Currently Online (${onlineCount})</h2>
            ${onlineVisitors.length > 0 ? onlineVisitors.map(visitor => `
              <div style="background: rgba(255,255,255,0.15); backdrop-filter: blur(10px); padding: 15px; border-radius: 12px; margin-bottom: 10px; border-left: 4px solid ${visitor.status === 'waiting' ? '#fbbf24' : visitor.status === 'chatting' ? '#10b981' : '#93c5fd'};">
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
                  <strong style="font-size: 1.1em;">${visitor.name}</strong>
                  <span style="background: ${visitor.status === 'waiting' ? '#fbbf24' : visitor.status === 'chatting' ? '#10b981' : '#93c5fd'}; color: white; padding: 4px 12px; border-radius: 12px; font-size: 0.85em; font-weight: 600;">
                    ${visitor.status === 'waiting' ? '⏳ Waiting' : visitor.status === 'chatting' ? '💬 Chatting' : '👀 Browsing'}
                  </span>
                </div>
                <div style="font-size: 0.9em; opacity: 0.95;">
                  📧 ${visitor.email}<br>
                  ${visitor.pageUrl ? `📄 ${visitor.pageUrl}<br>` : ''}
                  🕐 ${new Date(visitor.timestamp).toLocaleTimeString()}
                </div>
              </div>
            `).join('') : '<p style="text-align: center; padding: 20px; opacity: 0.9;">No visitors currently online</p>'}
          </div>

          <div class="section">
            <h2>💭 Recent Conversations ${isLiveData ? '<span style="color: #28a745; font-size: 0.8em;">●  LIVE</span>' : '<span style="color: #ffc107; font-size: 0.8em;">● DEMO</span>'}</h2>
            <div id="conversationsList">
              ${displayConversations.slice(0, 3).map(conv => `
                <div class="conversation-card">
                  <div class="conversation-header">
                    <div>
                      <div class="customer-name">${conv.customerName}</div>
                      <div style="font-size: 12px; color: #888; margin-top: 4px;">${conv.customerEmail}</div>
                    </div>
                    <div style="display: flex; gap: 8px; flex-direction: column; align-items: end;">
                      <span class="status-badge status-${conv.status}">${conv.status.toUpperCase()}</span>
                      <span class="status-badge priority-${conv.priority}">${conv.priority.toUpperCase()}</span>
                    </div>
                  </div>
                  <div class="conversation-subject">${conv.subject}</div>
                  <div class="last-message">${conv.lastMessage}</div>
                  <div class="conversation-footer">
                    <span class="timestamp">${new Date(conv.timestamp).toLocaleString()}</span>
                    <div style="display: flex; gap: 8px;">
                      <button class="action-button" onclick="viewChat('${conv.id}')">View Chat</button>
                      <a href="https://${ZENDESK_DOMAIN}/agent/tickets/${conv.zendeskId || conv.id}" target="_blank" class="action-button" style="text-decoration: none; display: inline-block;">Open in Zendesk</a>
                    </div>
                  </div>
                </div>
              `).join('')}
            </div>
          </div>

          <div class="section">
            <h2>🔗 Zendesk Integration</h2>
            <p style="color: #666; margin-bottom: 15px;">Connected to: <strong>${ZENDESK_DOMAIN}</strong></p>
            <p style="color: #666; margin-bottom: 15px;">Account: <strong>${ZENDESK_EMAIL}</strong></p>
            <a href="${ZENDESK_CHAT_URL}" target="_blank" class="action-button" style="display: inline-block; text-decoration: none;">Open Zendesk Chat Dashboard</a>
          </div>
        </div>

        <!-- Chat Tab -->
        <div id="tab-chat" class="tab-content">
          <div class="section">
            <h2>💬 AI Support Assistant</h2>
            <p style="color: #666; margin-bottom: 20px;">Get AI-powered help with customer support, chat responses, and Zendesk management</p>

            <div class="suggestion-chips">
              <span class="chip" onclick="sendSuggestion('Suggest response for installation question')">Installation help</span>
              <span class="chip" onclick="sendSuggestion('How to handle product availability inquiry')">Product availability</span>
              <span class="chip" onclick="sendSuggestion('Best practices for customer support')">Support best practices</span>
              <span class="chip" onclick="sendSuggestion('Generate empathetic response template')">Response templates</span>
            </div>

            <div class="chat-container">
              <div class="chat-messages" id="chatMessages">
                <div class="message assistant">
                  Hello! I'm your Zendesk Chat AI Assistant powered by Claude 4. I can help you with:
                  <br><br>
                  • Suggesting responses to customer inquiries<br>
                  • Customer support best practices<br>
                  • Chat management strategies<br>
                  • Handling difficult conversations<br>
                  • Zendesk tips and workflows<br>
                  <br>
                  How can I assist you with customer support today?
                </div>
              </div>
              <div class="chat-input-container">
                <input
                  type="text"
                  class="chat-input"
                  id="chatInput"
                  placeholder="Ask about customer support, chat responses..."
                  onkeypress="if(event.key === 'Enter') sendMessage()"
                >
                <button class="send-button" onclick="sendMessage()">Send</button>
              </div>
            </div>
          </div>
        </div>

        <!-- Conversations Tab -->
        <div id="tab-conversations" class="tab-content">
          <div class="section">
            <h2>💭 All Conversations</h2>
            <div id="allConversations">
              <p style="color: #888;">Loading conversations...</p>
            </div>
          </div>
        </div>

        <!-- Analytics Tab -->
        <div id="tab-analytics" class="tab-content">
          <div class="section">
            <h2>📈 Chat Analytics</h2>
            <div class="stats-grid">
              <div class="stat-card">
                <div class="stat-value">${stats.customerSatisfaction}</div>
                <div class="stat-label">Customer Satisfaction</div>
              </div>
              <div class="stat-card">
                <div class="stat-value">${stats.totalQueries}</div>
                <div class="stat-label">Total AI Queries</div>
              </div>
              <div class="stat-card">
                <div class="stat-value">${displayConversations.length}</div>
                <div class="stat-label">Total Conversations</div>
              </div>
              <div class="stat-card">
                <div class="stat-value">${stats.avgResponseTime}</div>
                <div class="stat-label">Avg Response Time</div>
              </div>
            </div>
            <div style="background: #f9fafb; padding: 20px; border-radius: 12px; margin-top: 20px;">
              <p style="color: #666;">Connect to Zendesk Analytics API for real-time reporting and advanced metrics.</p>
            </div>
          </div>
        </div>
      </div>

      <script>
        function switchTab(tabName) {
          document.querySelectorAll('.tab-content').forEach(tab => {
            tab.classList.remove('active');
          });
          document.querySelectorAll('.tab-button').forEach(btn => {
            btn.classList.remove('active');
          });

          document.getElementById('tab-' + tabName).classList.add('active');
          event.target.classList.add('active');

          if (tabName === 'conversations') {
            loadConversations();
          }
        }

        async function sendMessage() {
          const input = document.getElementById('chatInput');
          const message = input.value.trim();

          if (!message) return;

          const chatMessages = document.getElementById('chatMessages');
          chatMessages.innerHTML += \`
            <div class="message user">\${message}</div>
          \`;

          input.value = '';
          chatMessages.scrollTop = chatMessages.scrollHeight;

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

            const data = await response.json();

            chatMessages.innerHTML += \`
              <div class="message assistant">\${data.response.replace(/\\n/g, '<br>')}</div>
            \`;
            chatMessages.scrollTop = chatMessages.scrollHeight;
          } catch (error) {
            console.error('Chat error:', error);
            chatMessages.innerHTML += \`
              <div class="message assistant">Sorry, I encountered an error. Please try again.</div>
            \`;
          }
        }

        function sendSuggestion(text) {
          document.getElementById('chatInput').value = text;
          sendMessage();
        }

        async function loadVIPAlerts() {
          try {
            const response = await fetch('/api/vip-alerts');
            const data = await response.json();

            if (data.alerts && data.alerts.length > 0) {
              const banner = document.getElementById('vipAlertsBanner');
              const content = document.getElementById('vipAlertsContent');

              const html = data.alerts.map(alert => \`
                <div style="background: white; padding: 15px; border-radius: 10px; margin-bottom: 10px; border-left: 5px solid #ff6b6b;">
                  <div style="font-weight: 700; font-size: 1.2em; color: #d32f2f; margin-bottom: 5px;">
                    \${alert.name} - \${alert.company.toUpperCase()}
                  </div>
                  <div style="color: #666; margin-bottom: 5px;">
                    📧 \${alert.email}
                  </div>
                  <div style="color: #ff6b6b; font-weight: 600; font-size: 0.9em;">
                    🎯 \${alert.reason}
                  </div>
                  <div style="color: #888; font-size: 0.85em; margin-top: 5px;">
                    ⏰ \${new Date(alert.timestamp).toLocaleString()}
                  </div>
                </div>
              \`).join('');

              content.innerHTML = html;
              banner.style.display = 'block';
            } else {
              document.getElementById('vipAlertsBanner').style.display = 'none';
            }
          } catch (error) {
            console.error('Error loading VIP alerts:', error);
          }
        }

        async function loadConversations() {
          const container = document.getElementById('allConversations');
          try {
            const response = await fetch('/api/conversations');
            const data = await response.json();

            let html = '';
            data.conversations.forEach(conv => {
              html += \`
                <div class="conversation-card">
                  <div class="conversation-header">
                    <div>
                      <div class="customer-name">\${conv.customerName}</div>
                      <div style="font-size: 12px; color: #888; margin-top: 4px;">\${conv.customerEmail}</div>
                    </div>
                    <div style="display: flex; gap: 8px; flex-direction: column; align-items: end;">
                      <span class="status-badge status-\${conv.status}">\${conv.status.toUpperCase()}</span>
                      <span class="status-badge priority-\${conv.priority}">\${conv.priority.toUpperCase()}</span>
                    </div>
                  </div>
                  <div class="conversation-subject">\${conv.subject}</div>
                  <div class="last-message">\${conv.lastMessage}</div>
                  <div class="conversation-footer">
                    <span class="timestamp">\${new Date(conv.timestamp).toLocaleString()}</span>
                    <div style="display: flex; gap: 8px;">
                      <button class="action-button" onclick="viewChat('\${conv.id}')">View Chat</button>
                      <a href="https://${ZENDESK_DOMAIN}/agent/tickets/\${conv.zendeskId || conv.id}" target="_blank" class="action-button" style="text-decoration: none; display: inline-block;">Open in Zendesk</a>
                    </div>
                  </div>
                </div>
              \`;
            });

            container.innerHTML = html;
          } catch (error) {
            console.error('Load conversations error:', error);
            container.innerHTML = '<p style="color: #ef4444;">Error loading conversations</p>';
          }
        }

        async function viewChat(chatId) {
          try {
            const response = await fetch(\`/api/conversation/\${chatId}\`);
            const data = await response.json();

            const modal = document.getElementById('chatModal');
            const modalTitle = document.getElementById('modalTitle');
            const modalBody = document.getElementById('modalBody');

            modalTitle.textContent = \`Chat with \${data.customerName}\`;

            let messagesHtml = '';
            data.messages.forEach(msg => {
              const time = new Date(msg.timestamp).toLocaleTimeString();
              messagesHtml += \`
                <div class="chat-message \${msg.sender}">
                  <div class="message-sender">\${msg.sender === 'customer' ? data.customerName : 'Support Agent'}</div>
                  <div class="message-bubble">\${msg.message}</div>
                  <div class="message-time">\${time}</div>
                </div>
              \`;
            });

            modalBody.innerHTML = messagesHtml;
            modal.classList.add('active');

            // Scroll to bottom
            modalBody.scrollTop = modalBody.scrollHeight;
          } catch (error) {
            console.error('Error loading chat:', error);
            alert('Failed to load chat conversation');
          }
        }

        function closeModal() {
          document.getElementById('chatModal').classList.remove('active');
        }

        // Close modal when clicking outside
        document.addEventListener('click', (e) => {
          const modal = document.getElementById('chatModal');
          if (e.target === modal) {
            closeModal();
          }
        });

        // Load VIP alerts on page load and refresh every 30 seconds
        loadVIPAlerts();
        setInterval(loadVIPAlerts, 30000);
      </script>

      <!-- Chat Modal -->
      <div id="chatModal" class="modal">
        <div class="modal-content">
          <div class="modal-header">
            <h2 id="modalTitle">Chat Conversation</h2>
            <button class="modal-close" onclick="closeModal()">×</button>
          </div>
          <div class="modal-body" id="modalBody">
            <!-- Messages will be loaded here -->
          </div>
        </div>
      </div>
    </body>
    </html>
  `);
});

// API Endpoints

// Chat endpoint
app.post('/api/chat', requireAuth, async (req: Request, res: Response) => {
  try {
    const { message } = req.body;
    stats.totalQueries++;

    if (!req.session.chatHistory) {
      req.session.chatHistory = [];
    }

    req.session.chatHistory.push({
      role: 'user',
      content: message
    });

    if (req.session.chatHistory.length > 20) {
      req.session.chatHistory = req.session.chatHistory.slice(-20);
    }

    const systemPrompt = `You are the Zendesk Chat Support Agent for Designer Wallcoverings, powered by Claude 4 Sonnet.

Your role is to assist customer support team with:

CAPABILITIES:
- Suggest professional and empathetic customer responses
- Provide customer support best practices
- Help manage chat conversations
- Offer conflict resolution strategies
- Zendesk platform tips and workflows
- Response templates for common scenarios

CONTEXT:
- Company: Designer Wallcoverings (wallcovering/wallpaper retailer)
- Zendesk Domain: ${ZENDESK_DOMAIN}
- Current Active Chats: ${displayConversations.filter(c => c.status === 'active').length}
- Pending Chats: ${displayConversations.filter(c => c.status === 'pending').length}

COMMON CUSTOMER INQUIRIES:
- Product information and availability
- Installation help and tips
- Order tracking and status
- Return and exchange policies
- Design consultation
- Sample requests

When helping with responses:
1. Be empathetic and professional
2. Provide clear, actionable information
3. Include product knowledge when relevant
4. Maintain Designer Wallcoverings' brand voice
5. Suggest follow-up actions

Be helpful, supportive, and provide excellent customer service guidance.`;

    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 1024,
      system: systemPrompt,
      messages: req.session.chatHistory
    });

    const assistantMessage = response.content[0].type === 'text'
      ? response.content[0].text
      : 'I apologize, but I encountered an error processing your request.';

    req.session.chatHistory.push({
      role: 'assistant',
      content: assistantMessage
    });

    logActivity('Chat Query', `User asked: "${message.substring(0, 50)}..."`);

    res.json({ response: assistantMessage });
  } catch (error) {
    console.error('Chat error:', error);
    res.status(500).json({ error: 'Failed to process chat message' });
  }
});

// Get conversations
app.get('/api/conversations', requireAuth, async (req: Request, res: Response) => {
  const liveConversations = await getConversations();
  res.json({ conversations: liveConversations });
});

// Get specific conversation by ID
app.get('/api/conversation/:id', requireAuth, async (req: Request, res: Response) => {
  const chatId = req.params.id;
  const liveConversations = await getConversations();
  let conversation = liveConversations.find(c => c.id === chatId);

  if (!conversation) {
    return res.status(404).json({ error: 'Conversation not found' });
  }

  // Fetch comments if not already loaded
  if (conversation.messages.length === 0) {
    conversation.messages = await fetchTicketComments(chatId);
  }

  logActivity('View Chat', `Viewed conversation ${chatId} with ${conversation.customerName}`);
  res.json(conversation);
});

// Get statistics
app.get('/api/stats', requireAuth, (req: Request, res: Response) => {
  res.json(stats);
});

// Get VIP alerts
app.get('/api/vip-alerts', requireAuth, (req: Request, res: Response) => {
  res.json({ alerts: vipAlerts });
});

// Get activity log
app.get('/api/activity', requireAuth, (req: Request, res: Response) => {
  res.json({ activities: activityLog });
});

// Start server

// Metrics endpoint for daily reporting
app.get("/api/metrics", (req: Request, res: Response) => {
  res.json({
    status: "online",
    uptime: process.uptime(),
    responseTime: 0,
    tasksCompleted: 0,
    errorsToday: 0,
    lastActivity: new Date().toISOString(),
    qnaReadiness: 100
  });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`💬 Zendesk Chat Agent running on port ${PORT}`);
  console.log(`Dashboard: http://45.61.58.125:${PORT}`);
  console.log(`Zendesk: ${ZENDESK_CHAT_URL}`);
  logActivity('System Start', 'Zendesk Chat Agent initialized');
});