← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-purchasing-office/purchasing-office-agent.ts.backup-auth-1762615744

1471 lines

/**
 * DW-Agents: Purchasing (Office Supplies) Agent
 *
 * Manages office supply ordering with intelligent price comparison:
 * 1. Chat interface for supply requests and order tracking
 * 2. Amazon Personal & Business account integration
 * 3. Price comparison across vendors
 * 4. Order tracking and inventory management
 * 5. Automatic restock recommendations
 *
 * Port: 9880
 */

import Anthropic from '@anthropic-ai/sdk';
import express from 'express';
import session from 'express-session';
import fetch from 'node-fetch';
import * as fs from 'fs';
import * as cheerio from 'cheerio';
import { fetchRealAmazonOrders, fetchRealInventory, saveOrder, saveInventory } from './amazon-real-orders';
const { getUniversalHeader } = require('../shared-ui-components.js');

const app = express();
const PORT = 9880;

// Authentication
const AUTH_USERNAME = 'admin';
const AUTH_PASSWORD = '2025';

// Amazon configuration
const AMAZON_PERSONAL_EMAIL = 'nataliaabrams@gmail.com';
const AMAZON_PERSONAL_PASSWORD = 'Urbanon341';
const AMAZON_BUSINESS_EMAIL = 'info@designerwallcoverings.com';
const AMAZON_BUSINESS_PASSWORD = '*Amazonaccess911*';

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

// Stats tracking - LOAD REAL DATA
let stats = {
  totalQueries: 0,
  priceComparisons: 0,
  ordersTracked: 0,
  suppliesManaged: 0,
  lastQuery: null as { question: string; timestamp: Date } | null,
  recentOrders: [] as Array<{
    id: string;
    item: string;
    vendor: string;
    price: number;
    status: string;
    timestamp: Date;
  }>,
  inventory: [] as Array<{
    item: string;
    quantity: number;
    reorderLevel: number;
    lastOrdered: Date | null;
  }>
};

// Load real data on startup
async function loadRealData() {
  console.log('📦 Loading real Amazon data...');

  try {
    const orders = await fetchRealAmazonOrders();
    const inventory = await fetchRealInventory();

    stats.recentOrders = orders;
    stats.inventory = inventory;
    stats.ordersTracked = orders.length;
    stats.suppliesManaged = inventory.length;

    console.log(`✅ Loaded ${orders.length} orders and ${inventory.length} inventory items`);

    if (orders.length === 0) {
      console.log('\n⚠️  NO ORDERS FOUND - Add real data:');
      console.log('   📝 Edit: /root/DW-Agents/data/amazon-orders.json');
    }

    if (inventory.length === 0) {
      console.log('\n⚠️  NO INVENTORY - Add items:');
      console.log('   📝 Edit: /root/DW-Agents/data/office-inventory.json');
    }

  } catch (error) {
    console.error('❌ Error loading real data:', error);
  }
}

// Load data immediately
loadRealData();

// Chat history per session
const chatSessions: Map<string, Array<{ role: 'user' | 'assistant', content: string }>> = new Map();

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

app.use(
  session({
    secret: 'dw-purchasing-office-agent-2025',
    resave: false,
    saveUninitialized: true,
    rolling: true,
    cookie: {
      secure: false,
      httpOnly: true,
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    },
  })
);

declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
    chatHistory: Array<{ role: 'user' | 'assistant', content: string }>;
  }
}

// Load top deals from price tracking
function loadTopDeals(): string {
  try {
    const priceTrackingPath = '/root/DW-Agents/data/price-tracking.json';
    if (fs.existsSync(priceTrackingPath)) {
      const tracking = JSON.parse(fs.readFileSync(priceTrackingPath, 'utf-8'));
      const deals = tracking
        .filter((t: any) => t.priceChange < 0)
        .sort((a: any, b: any) => a.percentChange - b.percentChange)
        .slice(0, 4);

      if (deals.length === 0) {
        return '';
      }

      const lastChecked = deals[0]?.lastChecked ? new Date(deals[0].lastChecked) : new Date();
      const hoursAgo = Math.floor((Date.now() - lastChecked.getTime()) / (1000 * 60 * 60));
      const timeText = hoursAgo < 1 ? 'Just now' : hoursAgo === 1 ? '1 hour ago' : `${hoursAgo} hours ago`;
      const exactTime = lastChecked.toLocaleString('en-US', {
        month: 'short',
        day: 'numeric',
        hour: 'numeric',
        minute: '2-digit',
        hour12: true
      });

      const dealItems = deals.map((deal: any) => {
        const itemShort = deal.item.substring(0, 40) + (deal.item.length > 40 ? '...' : '');
        const savings = Math.abs(deal.priceChange).toFixed(2);
        const percent = Math.abs(deal.percentChange).toFixed(0);
        return `
          <a href="${deal.url}" target="_blank" class="deal-item" style="text-decoration: none; color: white;" title="Click to search on Amazon - Prices may vary">
            <strong>${itemShort}</strong>
            <span>Was $${deal.originalPrice.toFixed(2)} → $${deal.currentPrice.toFixed(2)}</span>
            <span class="deal-savings">SAVE $${savings} (${percent}% OFF)</span>
          </a>
        `;
      }).join('');

      return `
        <div class="deals-banner" id="dealsBanner">
          <button onclick="refreshDeals()" class="refresh-deals-btn" id="refreshBtn">
            🔄 REFRESH NOW
          </button>
          <div style="display: flex; align-items: center; gap: 15px; flex-shrink: 0; padding: 0 15px; border-right: 2px solid rgba(255,255,255,0.3);">
            <div class="deals-banner-title">🔥 DEALS ALERT!</div>
            <div style="font-size: 0.75em; opacity: 0.9; white-space: nowrap;">
              ⏰ <strong>${exactTime}</strong><br>
              <span style="font-size: 0.9em;">(${timeText})</span>
            </div>
          </div>
          ${dealItems}
        </div>
      `;
    }
  } catch (e) {
    // Ignore if file doesn't exist yet
  }
  return '';
}

const requireAuth = (req: express.Request, res: express.Response, next: express.NextFunction) => {
  // TEMPORARY: Authentication disabled for 4 hours (until 01:02:12 on 2025-11-08)
  return next();

  /* Original auth code (will be restored):
  if (req.session.authenticated) {
    next();
  } else {
    // For API requests, return JSON instead of redirecting
    if (req.path.startsWith('/api/')) {
      res.status(401).json({ error: 'Not authenticated. Please refresh the page and login.' });
    } else {
      res.redirect('/login');
    }
  }
  */
};

// Login page
app.get('/login', (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Login - Purchasing Agent</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, #667eea 0%, #764ba2 100%);
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
    }
    .login-container {
      background: white;
      padding: 40px;
      border-radius: 15px;
      box-shadow: 0 15px 35px rgba(0,0,0,0.2);
      max-width: 400px;
      width: 100%;
    }
    h1 { color: #667eea; text-align: center; margin-bottom: 30px; }
    input {
      width: 100%;
      padding: 12px;
      margin: 10px 0;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
    }
    button {
      width: 100%;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 15px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      margin-top: 10px;
    }
    .error { color: red; text-align: center; margin-bottom: 15px; }
  </style>
</head>
<body>
  <div class="login-container">
    <h1>🛒 Purchasing Agent</h1>
    <p style="text-align: center; color: #666; margin-bottom: 20px;">Office Supplies</p>
    ${req.query.error ? '<div class="error">Invalid credentials</div>' : ''}
    <form method="POST">
      <input type="text" name="username" placeholder="Username" required>
      <input type="password" name="password" placeholder="Password" required>
      <button type="submit">Login</button>
    </form>
  </div>
</body>
</html>
  `);
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username === AUTH_USERNAME && password === AUTH_PASSWORD) {
    req.session.authenticated = true;
    req.session.chatHistory = [];
    res.redirect('/');
  } else {
    res.redirect('/login?error=1');
  }
});

app.get('/logout', (req, res) => {
  req.session.destroy(() => res.redirect('/login'));
});

// Main dashboard with chat
app.get('/', requireAuth, (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Purchasing (Office Supplies) - DW-Agents</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Expires" content="0">
  <!-- v2.1 - Deals Banner with Refresh -->
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      padding: 20px;
      min-height: 100vh;
    }
    .container {
      max-width: 1800px;
      margin: 0 auto;
      display: grid;
      grid-template-columns: 280px 1fr 400px;
      gap: 20px;
    }
    .sidebar {
      display: flex;
      flex-direction: column;
      gap: 20px;
    }
    .header {
      background: white;
      padding: 30px;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .header h1 { color: #667eea; font-size: 2em; margin-bottom: 10px; }
    .header .subtitle { color: #666; font-size: 1.1em; }
    .stats-card {
      background: white;
      padding: 20px;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .stats-card h3 { color: #888; font-size: 0.85em; text-transform: uppercase; margin-bottom: 8px; }
    .stats-card .value { font-size: 2em; font-weight: bold; color: #667eea; }
    .chat-container {
      background: white;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
      display: flex;
      flex-direction: column;
      height: calc(100vh - 40px);
    }
    .chat-header {
      padding: 20px;
      border-bottom: 2px solid #f0f0f0;
    }
    .chat-header h2 { color: #667eea; margin-bottom: 5px; }
    .chat-header p { color: #888; font-size: 0.9em; }
    .chat-messages {
      flex: 1;
      padding: 20px;
      overflow-y: auto;
      background: #f8f9fa;
    }
    .message {
      margin-bottom: 15px;
      display: flex;
      gap: 10px;
    }
    .message.user {
      flex-direction: row-reverse;
    }
    .message-avatar {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.2em;
      flex-shrink: 0;
    }
    .message.user .message-avatar {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    }
    .message.assistant .message-avatar {
      background: #e0e0e0;
    }
    .message-content {
      max-width: 70%;
      padding: 12px 16px;
      border-radius: 12px;
      line-height: 1.5;
    }
    .message.user .message-content {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border-bottom-right-radius: 4px;
    }
    .message.assistant .message-content {
      background: white;
      color: #333;
      border-bottom-left-radius: 4px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    .chat-input-area {
      padding: 20px;
      border-top: 2px solid #f0f0f0;
      background: white;
    }
    .chat-input-container {
      display: flex;
      gap: 10px;
    }
    #chatInput {
      flex: 1;
      padding: 12px 16px;
      border: 2px solid #e0e0e0;
      border-radius: 25px;
      font-size: 1em;
      outline: none;
    }
    #chatInput:focus {
      border-color: #667eea;
    }
    .send-btn {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 12px 30px;
      border-radius: 25px;
      cursor: pointer;
      font-weight: 600;
      transition: transform 0.2s;
    }
    .send-btn:hover {
      transform: translateY(-2px);
    }
    .send-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    .suggestions {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
      margin-top: 10px;
    }
    .suggestion-chip {
      background: #f0f0f0;
      padding: 8px 15px;
      border-radius: 20px;
      font-size: 0.85em;
      cursor: pointer;
      transition: all 0.2s;
    }
    .suggestion-chip:hover {
      background: #667eea;
      color: white;
    }
    .typing-indicator {
      display: none;
      padding: 10px;
      color: #888;
      font-style: italic;
    }
    .typing-indicator.active {
      display: block;
    }
    .price-comparison {
      background: #f8f9fa;
      border: 1px solid #e0e0e0;
      border-radius: 8px;
      padding: 10px;
      margin-top: 10px;
    }
    .price-item {
      display: flex;
      justify-content: space-between;
      padding: 8px 0;
      border-bottom: 1px solid #e0e0e0;
    }
    .price-item:last-child {
      border-bottom: none;
    }
    .vendor-name {
      font-weight: 600;
      color: #667eea;
    }
    .price {
      font-weight: bold;
      color: #28a745;
    }
    .inventory-panel {
      background: white;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
      display: flex;
      flex-direction: column;
      height: calc(100vh - 40px);
      overflow: hidden;
    }
    .inventory-header {
      padding: 20px;
      border-bottom: 2px solid #f0f0f0;
    }
    .inventory-header h2 { color: #667eea; margin-bottom: 5px; }
    .inventory-header p { color: #888; font-size: 0.9em; }
    .inventory-list {
      flex: 1;
      padding: 15px;
      overflow-y: auto;
    }
    .inventory-item {
      background: #f8f9fa;
      border: 1px solid #e0e0e0;
      border-radius: 10px;
      padding: 15px;
      margin-bottom: 12px;
      transition: all 0.2s;
    }
    .inventory-item:hover {
      box-shadow: 0 3px 10px rgba(102, 126, 234, 0.2);
      transform: translateY(-2px);
    }
    .inventory-item-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .inventory-item-name {
      font-weight: 600;
      color: #333;
      font-size: 0.95em;
    }
    .inventory-item-quantity {
      background: #667eea;
      color: white;
      padding: 4px 10px;
      border-radius: 12px;
      font-size: 0.85em;
      font-weight: 600;
    }
    .inventory-item-quantity.low {
      background: #ff4757;
    }
    .inventory-item-details {
      color: #666;
      font-size: 0.85em;
      margin-bottom: 10px;
    }
    .find-price-btn {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 10px 16px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      font-size: 0.9em;
      width: 100%;
      transition: transform 0.2s;
    }
    .find-price-btn:hover {
      transform: translateY(-2px);
    }
    .find-price-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    .orders-bottom-panel {
      position: fixed;
      bottom: 0;
      left: 280px;
      right: 0;
      background: white;
      border-top: 2px solid #e0e0e0;
      box-shadow: 0 -3px 10px rgba(0,0,0,0.1);
      z-index: 1000;
      max-height: 160px;
      overflow-y: auto;
    }
    .orders-bottom-header {
      padding: 10px 20px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      font-weight: 600;
      font-size: 0.95em;
      position: sticky;
      top: 0;
      z-index: 10;
    }
    .orders-table {
      width: 100%;
      border-collapse: collapse;
      font-size: 0.85em;
    }
    .orders-table th {
      background: #f8f9fa;
      padding: 8px 12px;
      text-align: left;
      font-weight: 600;
      color: #667eea;
      border-bottom: 2px solid #e0e0e0;
      position: sticky;
      top: 35px;
      z-index: 5;
    }
    .orders-table td {
      padding: 8px 12px;
      border-bottom: 1px solid #f0f0f0;
      color: #333;
    }
    .orders-table tr:hover {
      background: #f8f9fa;
      cursor: pointer;
    }
    .order-status {
      padding: 3px 8px;
      border-radius: 10px;
      font-size: 0.8em;
      font-weight: 600;
    }
    .container {
      padding-bottom: 160px;
      padding-top: 80px;
    }
    .deals-banner {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      background: linear-gradient(135deg, #ff4757 0%, #d63031 100%);
      color: white;
      padding: 15px 30px;
      box-shadow: 0 3px 15px rgba(255,71,87,0.4);
      z-index: 2000;
      display: flex;
      align-items: center;
      gap: 20px;
      overflow-x: auto;
      white-space: nowrap;
    }
    .deals-banner-title {
      font-weight: 800;
      font-size: 1.1em;
      text-transform: uppercase;
      letter-spacing: 1px;
      flex-shrink: 0;
      animation: pulse 2s infinite;
    }
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.8; }
    }
    .deal-item {
      display: inline-flex;
      align-items: center;
      gap: 10px;
      background: rgba(255,255,255,0.2);
      padding: 8px 15px;
      border-radius: 20px;
      font-size: 0.9em;
      flex-shrink: 0;
      cursor: pointer;
      transition: all 0.3s ease;
      border: 2px solid transparent;
    }
    .deal-item:hover {
      background: rgba(255,255,255,0.35);
      transform: translateY(-2px);
      border: 2px solid white;
      box-shadow: 0 5px 15px rgba(0,0,0,0.3);
    }
    .deal-item strong {
      font-weight: 700;
    }
    .deal-savings {
      background: white;
      color: #d63031;
      padding: 3px 10px;
      border-radius: 12px;
      font-weight: 700;
      font-size: 0.85em;
    }
    .refresh-deals-btn {
      background: rgba(255,255,255,0.25);
      color: white;
      border: 2px solid white;
      padding: 10px 20px;
      border-radius: 25px;
      font-weight: 700;
      font-size: 0.85em;
      cursor: pointer;
      transition: all 0.3s ease;
      flex-shrink: 0;
      margin-right: 15px;
    }
    .refresh-deals-btn:hover {
      background: white;
      color: #d63031;
      transform: scale(1.05);
    }
    .refresh-deals-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  </style>
</head>
<body style="padding: 20px;">
  ${getUniversalHeader('Purchasing Office', 9880, 5)}

  ${loadTopDeals()}
  <div class="container" style="margin-top: 20px;">
    <div class="sidebar">
      <div class="header">
        <h1>🛒 Purchasing Agent</h1>
        <div class="subtitle">Office Supplies</div>
        <div style="margin-top: 15px;">
          <a href="/logout" style="color: #667eea; text-decoration: none;">Logout</a>
        </div>
      </div>

      <div class="stats-card">
        <h3>Total Queries</h3>
        <div class="value" id="totalQueries">0</div>
      </div>

      <div class="stats-card">
        <h3>Price Comparisons</h3>
        <div class="value" id="priceComparisons">0</div>
      </div>

      <div class="stats-card">
        <h3>Orders Tracked</h3>
        <div class="value" id="ordersTracked">0</div>
      </div>

      <div class="stats-card">
        <h3>Supplies Managed</h3>
        <div class="value" id="suppliesManaged">0</div>
      </div>
    </div>

    <div class="chat-container">
      <div class="chat-header">
        <h2>💬 Chat with Purchasing Agent</h2>
        <p>Ask about office supplies, pricing, orders, or inventory</p>
      </div>

      <div class="chat-messages" id="chatMessages">
        <div class="message assistant">
          <div class="message-avatar">🤖</div>
          <div class="message-content">
            Hi! I'm your Office Supplies Purchasing Agent. I can help you:
            <br><br>
            • Find and compare prices on office supplies<br>
            • Search Amazon Personal & Business accounts<br>
            • Track orders and shipments<br>
            • Manage inventory and reorder alerts<br>
            • Source the best deals<br>
            <br>
            <strong style="color: #28a745;">✅ REAL DATA MODE</strong> - No mock data!<br>
            Currently tracking <strong>${stats.ordersTracked}</strong> real orders and <strong>${stats.suppliesManaged}</strong> inventory items<br>
            <br>
            What can I help you with today?
          </div>
        </div>
      </div>

      <div class="typing-indicator" id="typingIndicator">
        Agent is typing...
      </div>

      <div class="chat-input-area">
        <div class="suggestions">
          <div class="suggestion-chip" onclick="sendSuggestion('Compare prices for printer paper')">🖨️ Printer paper prices</div>
          <div class="suggestion-chip" onclick="sendSuggestion('Check inventory status')">📦 Check inventory</div>
          <div class="suggestion-chip" onclick="sendSuggestion('Track recent orders')">🚚 Track orders</div>
          <div class="suggestion-chip" onclick="sendSuggestion('What office supplies are low in stock?')">⚠️ Low stock alerts</div>
          <div class="suggestion-chip" onclick="researchFurther()" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight: 600;">🔍 Research Further</div>
        </div>
        <div class="chat-input-container">
          <input
            type="text"
            id="chatInput"
            placeholder="Ask about office supplies, pricing, or orders..."
            onkeypress="if(event.key === 'Enter') sendMessage()"
          >
          <button class="send-btn" onclick="sendMessage()" id="sendBtn">Send</button>
        </div>
      </div>
    </div>

    <div class="inventory-panel">
      <div class="inventory-header">
        <h2>📦 Inventory Items</h2>
        <p>Click to find best prices</p>
      </div>
      <div class="inventory-list" id="inventoryList">
        ${stats.inventory.length > 0 ? stats.inventory.map((item, idx) => `
          <div class="inventory-item">
            <div class="inventory-item-header">
              <div class="inventory-item-name">${item.item}</div>
              <div class="inventory-item-quantity ${item.quantity < item.reorderLevel ? 'low' : ''}">${item.quantity} units</div>
            </div>
            <div class="inventory-item-details">
              Reorder Level: ${item.reorderLevel} units<br>
              Last Ordered: ${item.lastOrdered ? new Date(item.lastOrdered).toLocaleDateString() : 'Never'}
            </div>
            <button class="find-price-btn" onclick="findBestPrice('${item.item}', ${idx})">
              🔍 FIND BEST PRICE
            </button>
          </div>
        `).join('') : `
          <div style="padding: 40px; text-align: center; color: #888;">
            <div style="font-size: 3em; margin-bottom: 20px;">📦</div>
            <h3 style="color: #667eea; margin-bottom: 10px;">No Inventory Yet</h3>
            <p style="margin-bottom: 20px;">Add inventory items to track office supplies</p>
            <p style="font-size: 0.9em; background: #fff3cd; padding: 15px; border-radius: 8px; color: #856404;">
              <strong>📝 NO MOCK DATA</strong><br>
              Edit: /root/DW-Agents/data/office-inventory.json
            </p>
          </div>
        `}
      </div>
    </div>
  </div>

  <div class="orders-bottom-panel">
    <div class="orders-bottom-header">
      📦 Currently Tracking ${stats.ordersTracked} Real Orders
    </div>
    <table class="orders-table">
      <thead>
        <tr>
          <th>Date</th>
          <th>Product</th>
          <th>Price</th>
          <th>Vendor</th>
          <th>Status</th>
          <th>Order ID</th>
        </tr>
      </thead>
      <tbody>
        ${stats.recentOrders.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).map(order => `
          <tr onclick="sendSuggestion('Tell me about order ${order.id}')">
            <td>${new Date(order.timestamp).toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' })}</td>
            <td style="max-width: 300px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${order.item}</td>
            <td style="font-weight: 600; color: #28a745;">$${order.price.toFixed(2)}</td>
            <td>${order.vendor}</td>
            <td>
              <span class="order-status">${order.status === 'Delivered' ? '✅' : order.status === 'In Transit' ? '🚚' : '⏳'} ${order.status}</span>
            </td>
            <td style="font-family: monospace; font-size: 0.8em; color: #888;">${order.id}</td>
          </tr>
        `).join('')}
      </tbody>
    </table>
  </div>

  <script>
    function updateStats() {
      fetch('/api/stats')
        .then(r => r.json())
        .then(data => {
          document.getElementById('totalQueries').textContent = data.totalQueries;
          document.getElementById('priceComparisons').textContent = data.priceComparisons;
          document.getElementById('ordersTracked').textContent = data.ordersTracked;
          document.getElementById('suppliesManaged').textContent = data.suppliesManaged;
        });
    }

    async function refreshDeals() {
      const btn = document.getElementById('refreshBtn');
      const banner = document.getElementById('dealsBanner');

      btn.disabled = true;
      btn.innerHTML = '⏳ Refreshing prices...';

      try {
        const response = await fetch('/api/refresh-deals', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' }
        });

        const data = await response.json();

        if (data.success) {
          if (data.amazonBlocked) {
            // Amazon blocked scraping - show warning
            btn.innerHTML = '⚠️ Amazon Blocked';
            setTimeout(() => {
              btn.innerHTML = '🔄 REFRESH NOW';
              btn.disabled = false;
              // Add message to chat
              const messagesDiv = document.getElementById('chatMessages');
              if (messagesDiv) {
                addMessage('assistant', '⚠️ Amazon is currently blocking automated price checks. Showing last cached prices. The "Find Best Price" buttons will search Amazon directly.');
              }
            }, 3000);
          } else if (data.dealsFound) {
            // Found new deals - reload page
            btn.innerHTML = '✅ New deals found! Reloading...';
            setTimeout(() => {
              location.reload();
            }, 1000);
          } else {
            // No new deals but refresh succeeded
            btn.innerHTML = '✅ Up to date - No new deals';
            setTimeout(() => {
              btn.innerHTML = '🔄 REFRESH NOW';
              btn.disabled = false;
            }, 2000);
          }
        } else {
          btn.innerHTML = '❌ Failed - Try again';
          setTimeout(() => {
            btn.innerHTML = '🔄 REFRESH NOW';
            btn.disabled = false;
          }, 2000);
        }
      } catch (error) {
        btn.innerHTML = '❌ Error - Try again';
        setTimeout(() => {
          btn.innerHTML = '🔄 REFRESH NOW';
          btn.disabled = false;
        }, 2000);
      }
    }

    function addMessage(role, content) {
      const messagesDiv = document.getElementById('chatMessages');
      const messageDiv = document.createElement('div');
      messageDiv.className = \`message \${role}\`;

      const avatar = document.createElement('div');
      avatar.className = 'message-avatar';
      avatar.textContent = role === 'user' ? '👤' : '🤖';

      const contentDiv = document.createElement('div');
      contentDiv.className = 'message-content';
      contentDiv.innerHTML = content.replace(/\\n/g, '<br>');

      messageDiv.appendChild(avatar);
      messageDiv.appendChild(contentDiv);
      messagesDiv.appendChild(messageDiv);

      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    }

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

      if (!message) return;

      // Add user message
      addMessage('user', message);
      input.value = '';
      sendBtn.disabled = true;
      typingIndicator.classList.add('active');

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

        const data = await response.json();

        typingIndicator.classList.remove('active');
        addMessage('assistant', data.response);
        updateStats();

      } catch (error) {
        typingIndicator.classList.remove('active');
        addMessage('assistant', 'Sorry, I encountered an error. Please try again.');
      }

      sendBtn.disabled = false;
    }

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

    async function researchFurther() {
      const messagesDiv = document.getElementById('chatMessages');
      const typingIndicator = document.getElementById('typingIndicator');

      addMessage('user', '🔍 Research further on recent topics and provide deeper insights');
      typingIndicator.classList.add('active');

      try {
        const response = await fetch('/api/research', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' }
        });

        const data = await response.json();

        typingIndicator.classList.remove('active');
        addMessage('assistant', data.research);
        updateStats();
      } catch (error) {
        typingIndicator.classList.remove('active');
        addMessage('assistant', 'Sorry, I encountered an error while researching. Please try again.');
      }
    }

    async function findBestPrice(itemName, itemIndex) {
      const typingIndicator = document.getElementById('typingIndicator');
      const button = event.target;

      button.disabled = true;
      button.textContent = '🔍 Searching...';

      addMessage('user', \`Find best price for: \${itemName}\`);
      typingIndicator.classList.add('active');

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

        const data = await response.json();

        typingIndicator.classList.remove('active');

        // Check for authentication error
        if (response.status === 401) {
          addMessage('assistant', \`🔐 Your session has expired. Please refresh the page to login again.\`);
          return;
        }

        if (data.priceInfo) {
          addMessage('assistant', data.priceInfo);
        } else if (data.error) {
          addMessage('assistant', \`❌ Error: \${data.error}\`);
        } else {
          addMessage('assistant', 'Unable to fetch prices. Please try again.');
        }

        updateStats();
      } catch (error) {
        typingIndicator.classList.remove('active');
        addMessage('assistant', \`❌ Network error: \${error.message}. Please refresh the page and try again.\`);
        console.error('Find price error:', error);
      } finally {
        button.disabled = false;
        button.textContent = '🔍 FIND BEST PRICE';
      }
    }

    // Update stats every 10 seconds
    setInterval(updateStats, 10000);
    updateStats();
  </script>
</body>
</html>
  `);
});

// API endpoints
app.get('/api/stats', requireAuth, (req, res) => {
  res.json(stats);
});

app.post('/api/chat', requireAuth, async (req, res) => {
  try {
    const { message } = req.body;

    if (!message) {
      return res.status(400).json({ error: 'Message is required' });
    }

    stats.totalQueries++;
    stats.lastQuery = { question: message, timestamp: new Date() };

    // Initialize chat history for session
    if (!req.session.chatHistory) {
      req.session.chatHistory = [];
    }

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

    // Build system prompt with detailed data
    const systemPrompt = `You are the Purchasing (Office Supplies) Agent for Designer Wallcoverings.

Your responsibilities:
1. Help with office supply inquiries and price comparisons
2. Search and compare prices across Amazon Personal and Amazon Business accounts
3. Track orders and shipments with dates and tracking numbers
4. Manage inventory levels and alert on low stock
5. Source the best deals for office supplies

Available integrations:
- Amazon Personal Account (linked)
- Amazon Business Account (linked)
- Order tracking system
- Inventory management database

CURRENT INVENTORY (${stats.inventory.length} items):
${stats.inventory.length > 0 ? stats.inventory.map(item => {
  const lastOrderedDate = item.lastOrdered ? new Date(item.lastOrdered).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : 'Never';
  const status = item.quantity < item.reorderLevel ? '⚠️ LOW STOCK' : '✅ OK';
  return `- ${item.item}
  • Current: ${item.quantity} units ${status}
  • Reorder at: ${item.reorderLevel} units
  • Last ordered: ${lastOrderedDate}`;
}).join('\n') : '- No inventory tracked yet'}

ALL ORDERS (${stats.recentOrders.length} total) - Sorted by date:
${stats.recentOrders.length > 0 ? stats.recentOrders.slice().sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).map((order) => {
  const orderDate = new Date(order.timestamp).toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' });
  const status = order.status === 'Delivered' ? '✅' : order.status === 'In Transit' ? '🚚' : '⏳';
  return `${orderDate} | ${order.item} | $${order.price.toFixed(2)} | ${order.vendor} | ${status}`;
}).join('\n') : '- No orders'}

When user asks about:
- "last order" or "recent orders": Show the most recent orders with ALL details (date, price, status, tracking)
- "show dates": Display order dates and last ordered dates from inventory
- "last price": Show the most recent price paid for that item from order history
- "when did we order": Look through order history and show the date
- "what's low": List items below reorder level with last ordered date
- "track order": Show full order details including tracking number and status

IMPORTANT FORMATTING:
- Always show dates in format: "Oct 28, 2025"
- Always show prices with dollar sign: "$42.99"
- Always include order status with emoji
- When showing orders, include: Date, Item, Price, Status, Tracking
- When showing inventory, include: Quantity, Last Ordered Date, Reorder Level

Be detailed, clear, and always include dates and prices when relevant.`;

    // Call Claude API
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 2048, // Increased for detailed responses with dates/prices
      system: systemPrompt,
      messages: req.session.chatHistory.map(msg => ({
        role: msg.role,
        content: msg.content
      }))
    });

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

    // Add assistant response to history
    req.session.chatHistory.push({
      role: 'assistant',
      content: assistantMessage
    });

    // Keep only last 20 messages
    if (req.session.chatHistory.length > 20) {
      req.session.chatHistory = req.session.chatHistory.slice(-20);
    }

    // Update stats based on message content
    if (message.toLowerCase().includes('price') || message.toLowerCase().includes('compare')) {
      stats.priceComparisons++;
    }
    if (message.toLowerCase().includes('order') || message.toLowerCase().includes('track')) {
      stats.ordersTracked++;
    }

    res.json({
      response: assistantMessage,
      stats: {
        totalQueries: stats.totalQueries,
        priceComparisons: stats.priceComparisons
      }
    });

  } catch (error: any) {
    console.error('❌ Chat error:', error.message || error);
    console.error('Error details:', error);
    res.status(500).json({
      error: 'Failed to process message',
      response: `Error: ${error.message || 'Unknown error'}. Please try again or check logs.`
    });
  }
});

// Research endpoint
app.post('/api/research', requireAuth, async (req, res) => {
  try {
    stats.totalQueries++;

    // Build comprehensive research prompt
    const researchPrompt = `Provide comprehensive research and insights on office supply purchasing for Designer Wallcoverings.

Focus on:
1. Current market trends in office supplies
2. Best suppliers and vendors for 2025
3. Cost-saving strategies and bulk purchasing opportunities
4. Emerging products and technologies
5. Sustainable office supply options
6. Amazon Business vs Personal account optimization
7. Inventory management best practices

Recent context:
- Total queries: ${stats.totalQueries}
- Orders tracked: ${stats.ordersTracked}
- Recent orders: ${stats.recentOrders.slice(-3).map(o => o.item).join(', ') || 'None yet'}

Provide actionable insights and specific recommendations.`;

    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 2048,
      messages: [{
        role: 'user',
        content: researchPrompt
      }]
    });

    const research = response.content[0].type === 'text'
      ? response.content[0].text
      : 'Unable to generate research at this time.';

    res.json({ research });

  } catch (error) {
    console.error('Research error:', error);
    res.status(500).json({
      research: 'I apologize, but I encountered an error while researching. Please try again.'
    });
  }
});

// Find best price endpoint
app.post('/api/find-price', requireAuth, async (req, res) => {
  try {
    const { itemName, itemIndex } = req.body;

    if (!itemName) {
      return res.status(400).json({ error: 'Item name is required' });
    }

    stats.priceComparisons++;
    stats.totalQueries++;

    console.log(`🔍 Finding REAL prices for: ${itemName}`);

    // ONLY scrape real data - NO ESTIMATES!
    const vendors = [];

    // Scrape Amazon
    try {
      const amazonUrl = `https://www.amazon.com/s?k=${encodeURIComponent(itemName)}`;
      const response = await fetch(amazonUrl, {
        headers: {
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
          'Accept': 'text/html,application/xhtml+xml',
          'Accept-Language': 'en-US,en;q=0.5'
        }
      });

      if (response.ok) {
        const html = await response.text();
        const $ = cheerio.load(html);

        // Extract ALL products from first page
        $('div[data-component-type="s-search-result"]').each((idx, element) => {
          if (idx >= 3) return false; // Get top 3 results

          try {
            const $el = $(element);
            const title = $el.find('h2 a span').first().text().trim();
            const priceWhole = $el.find('.a-price-whole').first().text().replace(/[^0-9]/g, '');
            const priceFraction = $el.find('.a-price-fraction').first().text().replace(/[^0-9]/g, '');
            const productUrl = $el.find('h2 a').attr('href');

            if (priceWhole && title) {
              const price = parseFloat(`${priceWhole}.${priceFraction || '00'}`);
              const fullUrl = `https://www.amazon.com${productUrl}`;

              vendors.push({
                name: 'Amazon',
                product: title.substring(0, 80),
                price: price.toFixed(2),
                url: fullUrl + (fullUrl.includes('?') ? '&' : '?') + 'tag=designerwal0b-20'
              });
              console.log(`  ✅ Found: ${title.substring(0, 40)}... - $${price}`);
            }
          } catch (e) {
            // Skip this product
          }
        });
      }
    } catch (e) {
      console.log(`  ❌ Amazon scrape failed:`, e.message);
    }

    // Sort by price (lowest first)
    vendors.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));

    let priceInfo = '';

    if (vendors.length === 0) {
      priceInfo = `❌ Unable to fetch live prices from Amazon right now.\n\n`;
      priceInfo += `Please try these direct search links:\n\n`;
      priceInfo += `🛒 Amazon: https://www.amazon.com/s?k=${encodeURIComponent(itemName)}&tag=designerwal0b-20\n`;
      priceInfo += `📎 Staples: https://www.staples.com/s?searchKey=${encodeURIComponent(itemName)}\n`;
      priceInfo += `🏢 Office Depot: https://www.officedepot.com/catalog/search.do?Ntt=${encodeURIComponent(itemName)}\n`;
      priceInfo += `🏪 Costco: https://www.costco.com/s?dept=All&keyword=${encodeURIComponent(itemName)}\n`;

      res.json({ priceInfo });
      return;
    }

    // Build clean formatted response
    priceInfo = `🔍 LIVE PRICES FROM AMAZON\n`;
    priceInfo += `Search: ${itemName}\n\n`;

    vendors.forEach((vendor, idx) => {
      priceInfo += `${idx + 1}. $${vendor.price} - ${vendor.product}\n`;
      priceInfo += `   🛒 ${vendor.url}\n\n`;
    });

    priceInfo += `💡 Best Price: $${vendors[0].price}\n`;
    priceInfo += `\nClick any link above to purchase with your affiliate tag!`;

    console.log(`✅ Found ${vendors.length} real prices`);

    res.json({ priceInfo });

  } catch (error) {
    console.error('Price search error:', error);
    res.status(500).json({
      priceInfo: 'I apologize, but I encountered an error while searching for prices. Please try again.'
    });
  }
});

// Order management endpoints
app.post('/api/orders/add', requireAuth, (req, res) => {
  const { item, vendor, price } = req.body;

  const order = {
    id: `ORD-${Date.now()}`,
    item,
    vendor,
    price: parseFloat(price),
    status: 'Pending',
    timestamp: new Date()
  };

  stats.recentOrders.push(order);
  stats.ordersTracked++;

  res.json({ success: true, order });
});

app.get('/api/orders', requireAuth, (req, res) => {
  res.json(stats.recentOrders);
});

// Inventory management
app.post('/api/inventory/add', requireAuth, (req, res) => {
  const { item, quantity, reorderLevel } = req.body;

  const inventoryItem = {
    item,
    quantity: parseInt(quantity),
    reorderLevel: parseInt(reorderLevel),
    lastOrdered: null
  };

  stats.inventory.push(inventoryItem);
  stats.suppliesManaged++;

  res.json({ success: true, item: inventoryItem });
});

app.get('/api/inventory', requireAuth, (req, res) => {
  res.json(stats.inventory);
});

app.listen(PORT, '0.0.0.0', () => {
  console.log('');
  console.log('🛒 Purchasing (Office Supplies) Agent');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('🌍 External: http://45.61.58.125:' + PORT);
  console.log('🏠 Local: http://localhost:' + PORT);
  console.log('');
  console.log('💬 Chat Mode: Active');
  console.log('🛍️  Amazon Personal: Ready');
  console.log('🏢 Amazon Business: Ready');
  console.log('📦 Order Tracking: Active');
  console.log('📊 Inventory Management: Active');
  console.log('');
  console.log('✅ Purchasing Agent ready for queries...');
});

// Refresh deals endpoint - triggers daily-price-tracker.ts
app.post('/api/refresh-deals', requireAuth, async (req, res) => {
  try {
    console.log('🔄 Manual price refresh triggered...');

    const { spawn } = require('child_process');
    const trackerProcess = spawn('npx', ['tsx', '/root/DW-Agents/daily-price-tracker.ts'], {
      cwd: '/root/DW-Agents',
      stdio: 'pipe'
    });

    let output = '';
    let errorOutput = '';
    let responseSent = false;

    trackerProcess.stdout.on('data', (data: Buffer) => {
      output += data.toString();
      console.log(data.toString().trim());
    });

    trackerProcess.stderr.on('data', (data: Buffer) => {
      errorOutput += data.toString();
      console.error(data.toString().trim());
    });

    // Timeout after 2 minutes
    const timeout = setTimeout(() => {
      if (responseSent) return;
      responseSent = true;

      trackerProcess.kill();
      res.json({
        success: false,
        message: 'Refresh timed out - too many items'
      });
    }, 120000);

    trackerProcess.on('close', (code: number) => {
      clearTimeout(timeout); // Clear timeout FIRST to prevent double response
      if (responseSent) return;
      responseSent = true;

      if (code === 0) {
        console.log('✅ Price refresh completed successfully');

        // Check if Amazon blocked all requests
        const amazonBlocked = output.includes('HTTP 503') || output.includes('No prices tracked');
        const dealsFound = output.includes('PRICE DROPS');

        res.json({
          success: true,
          message: amazonBlocked ? 'Amazon blocked scraping - showing cached prices' : 'Prices refreshed successfully',
          dealsFound: dealsFound,
          amazonBlocked: amazonBlocked
        });
      } else {
        console.error('❌ Price refresh failed with code:', code);
        res.json({
          success: false,
          message: 'Failed to refresh prices',
          error: errorOutput
        });
      }
    });

  } catch (error: any) {
    console.error('❌ Refresh error:', error);
    res.status(500).json({
      success: false,
      message: error.message
    });
  }
});