← back to Designer Wallcoverings

DW-Agents/dw-agents/in-parallel-agent.ts.backup-pre-theme

671 lines

import express from 'express';
import path from 'path';
import fs from 'fs';
import session from 'express-session';
import { AgentMemory } from './shared-memory-system';

const app = express();
const PORT = 9891;
// Initialize agent memory system
const agentMemory = new AgentMemory('in-parallel');
console.log('📚 Memory system initialized for in-parallel');


// Session configuration
declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
  }
}

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

// Serve static files
app.use(express.static('public'));
app.use(express.json());

// Authentication middleware - but mark all sessions as authenticated for this public monitoring tool
app.use((req, res, next) => {
  req.session.authenticated = true;
  next();
});

interface ParallelTask {
  id: string;
  title: string;
  description: string;
  status: 'running' | 'completed' | 'paused' | 'error';
  progress: {
    current: number;
    total: number;
    percentage: number;
  };
  stats: {
    startTime: string;
    estimatedCompletion?: string;
    rate?: string;
    elapsed?: string;
  };
  details: string[];
  monitorUrl?: string;
  category: 'shopify' | 'infrastructure' | 'data' | 'other';
  priority: 'high' | 'medium' | 'low';
}

// Load tasks from JSON file
let parallelTasks: ParallelTask[] = [];

function loadTasks() {
  try {
    const dataPath = path.join(__dirname, 'parallel-tasks.json');
    if (fs.existsSync(dataPath)) {
      parallelTasks = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
    } else {
      // Initialize with sample DWKK task
      parallelTasks = [
        {
          id: 'dwkk-sample-price-update',
          title: '🚨 DWKK Sample Price Update',
          description: 'Updating all DWKK- SKU sample variants to $4.25',
          status: 'running',
          progress: {
            current: 0,
            total: 27289,
            percentage: 0
          },
          stats: {
            startTime: new Date().toISOString(),
            rate: '~2 variants per second',
            estimatedCompletion: new Date(Date.now() + 3.8 * 60 * 60 * 1000).toISOString()
          },
          details: [
            '✅ Only DWKK- SKUs',
            '✅ Only variants with Size = "Sample"',
            '✅ Only ACTIVE variants',
            '✅ Setting price to $4.25'
          ],
          monitorUrl: 'http://45.61.58.125:6766/',
          category: 'shopify',
          priority: 'high'
        }
      ];
      saveTasks();
    }
  } catch (error) {
    console.error('Error loading tasks:', error);
    parallelTasks = [];
  }
}

function saveTasks() {
  try {
    const dataPath = path.join(__dirname, 'parallel-tasks.json');
    fs.writeFileSync(dataPath, JSON.stringify(parallelTasks, null, 2));
  } catch (error) {
    console.error('Error saving tasks:', error);
  }
}

// Initialize tasks on startup
loadTasks();

// Login route - auto-authenticate and redirect to home
app.get('/login', (req, res) => {
  req.session.authenticated = true;
  res.redirect('/');
});

app.get('/', (req, res) => {
  const html = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>In Parallel - Background Tasks Monitor</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: linear-gradient(135deg, #36454f 0%, #708090 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
        }

        .header {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 30px;
            margin-bottom: 30px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
        }

        .header h1 {
            font-size: 2.5rem;
            color: #36454f;
            margin-bottom: 10px;
        }

        .header p {
            color: #708090;
            font-size: 1.1rem;
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }

        .stat-card {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
        }

        .stat-card h3 {
            color: #708090;
            font-size: 0.9rem;
            font-weight: 500;
            margin-bottom: 10px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .stat-card .value {
            font-size: 2rem;
            font-weight: 700;
            color: #36454f;
        }

        .stat-card.running .value {
            color: #48bb78;
        }

        .stat-card.completed .value {
            color: #4299e1;
        }

        .tasks-grid {
            display: grid;
            gap: 25px;
        }

        .task-card {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 30px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
            transition: transform 0.2s, box-shadow 0.2s;
        }

        .task-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15);
        }

        .task-header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: 20px;
        }

        .task-title {
            flex: 1;
        }

        .task-title h2 {
            font-size: 1.5rem;
            color: #36454f;
            margin-bottom: 8px;
        }

        .task-title p {
            color: #708090;
            font-size: 1rem;
        }

        .status-badge {
            padding: 8px 16px;
            border-radius: 20px;
            font-size: 0.85rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .status-badge.running {
            background: #c6f6d5;
            color: #22543d;
        }

        .status-badge.completed {
            background: #bee3f8;
            color: #2c5282;
        }

        .status-badge.paused {
            background: #fed7aa;
            color: #7c2d12;
        }

        .status-badge.error {
            background: #fed7d7;
            color: #742a2a;
        }

        .progress-section {
            margin: 25px 0;
        }

        .progress-bar-container {
            background: #e2e8f0;
            border-radius: 10px;
            height: 30px;
            overflow: hidden;
            margin-bottom: 15px;
        }

        .progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #48bb78 0%, #38a169 100%);
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: 600;
            transition: width 0.5s ease;
        }

        .progress-details {
            display: flex;
            justify-content: space-between;
            color: #708090;
            font-size: 0.95rem;
        }

        .stats-row {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 20px 0;
        }

        .stat-item {
            background: #f5f5f5;
            padding: 15px;
            border-radius: 10px;
        }

        .stat-item h4 {
            color: #708090;
            font-size: 0.85rem;
            margin-bottom: 5px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .stat-item p {
            color: #36454f;
            font-size: 1.1rem;
            font-weight: 600;
        }

        .details-list {
            list-style: none;
            margin: 20px 0;
        }

        .details-list li {
            padding: 10px 0;
            color: #708090;
            border-bottom: 1px solid #e2e8f0;
        }

        .details-list li:last-child {
            border-bottom: none;
        }

        .monitor-button {
            display: inline-block;
            background: linear-gradient(135deg, #36454f 0%, #708090 100%);
            color: white;
            padding: 12px 24px;
            border-radius: 10px;
            text-decoration: none;
            font-weight: 600;
            margin-top: 15px;
            transition: transform 0.2s, box-shadow 0.2s;
        }

        .monitor-button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
        }

        .empty-state {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 60px 30px;
            text-align: center;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
        }

        .empty-state h2 {
            color: #36454f;
            font-size: 1.8rem;
            margin-bottom: 15px;
        }

        .empty-state p {
            color: #708090;
            font-size: 1.1rem;
        }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .pulsing {
            animation: pulse 2s ease-in-out infinite;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>⚡ In Parallel</h1>
            <p>Background Tasks & Long-Running Operations Monitor</p>
        </div>

        <div class="stats-grid" id="statsGrid">
            <!-- Stats will be inserted here -->
        </div>

        <div class="tasks-grid" id="tasksGrid">
            <!-- Tasks will be inserted here -->
        </div>

        <div class="empty-state" id="emptyState" style="display: none;">
            <h2>🎉 No Active Tasks</h2>
            <p>All background operations are complete!</p>
        </div>
    </div>

    <script>
        async function fetchTasks() {
            try {
                const response = await fetch('/api/tasks');
                const tasks = await response.json();
                renderTasks(tasks);
            } catch (error) {
                console.error('Error fetching tasks:', error);
            }
        }

        function formatTime(isoString) {
            if (!isoString) return 'N/A';
            const date = new Date(isoString);
            return date.toLocaleString();
        }

        function formatElapsed(startTime) {
            const start = new Date(startTime);
            const now = new Date();
            const diff = now - start;
            const hours = Math.floor(diff / 3600000);
            const minutes = Math.floor((diff % 3600000) / 60000);
            return hours > 0 ? \`\${hours}h \${minutes}m\` : \`\${minutes}m\`;
        }

        function renderTasks(tasks) {
            const statsGrid = document.getElementById('statsGrid');
            const tasksGrid = document.getElementById('tasksGrid');
            const emptyState = document.getElementById('emptyState');

            if (tasks.length === 0) {
                statsGrid.style.display = 'none';
                tasksGrid.style.display = 'none';
                emptyState.style.display = 'block';
                return;
            }

            statsGrid.style.display = 'grid';
            tasksGrid.style.display = 'grid';
            emptyState.style.display = 'none';

            // Calculate stats
            const runningCount = tasks.filter(t => t.status === 'running').length;
            const completedCount = tasks.filter(t => t.status === 'completed').length;
            const totalProgress = tasks.reduce((sum, t) => sum + t.progress.current, 0);
            const totalItems = tasks.reduce((sum, t) => sum + t.progress.total, 0);

            // Render stats
            statsGrid.innerHTML = \`
                <div class="stat-card running">
                    <h3>Running Tasks</h3>
                    <div class="value">\${runningCount}</div>
                </div>
                <div class="stat-card completed">
                    <h3>Completed</h3>
                    <div class="value">\${completedCount}</div>
                </div>
                <div class="stat-card">
                    <h3>Total Progress</h3>
                    <div class="value">\${totalProgress.toLocaleString()}</div>
                </div>
                <div class="stat-card">
                    <h3>Total Items</h3>
                    <div class="value">\${totalItems.toLocaleString()}</div>
                </div>
            \`;

            // Render tasks
            tasksGrid.innerHTML = tasks.map(task => \`
                <div class="task-card">
                    <div class="task-header">
                        <div class="task-title">
                            <h2>\${task.title}</h2>
                            <p>\${task.description}</p>
                        </div>
                        <span class="status-badge \${task.status} \${task.status === 'running' ? 'pulsing' : ''}">\${task.status}</span>
                    </div>

                    <div class="progress-section">
                        <div class="progress-bar-container">
                            <div class="progress-bar" style="width: \${task.progress.percentage}%">
                                \${task.progress.percentage.toFixed(1)}%
                            </div>
                        </div>
                        <div class="progress-details">
                            <span>\${task.progress.current.toLocaleString()} / \${task.progress.total.toLocaleString()}</span>
                            <span>\${task.progress.percentage.toFixed(2)}% Complete</span>
                        </div>
                    </div>

                    <div class="stats-row">
                        <div class="stat-item">
                            <h4>Started</h4>
                            <p>\${formatTime(task.stats.startTime)}</p>
                        </div>
                        <div class="stat-item">
                            <h4>Elapsed</h4>
                            <p>\${formatElapsed(task.stats.startTime)}</p>
                        </div>
                        \${task.stats.rate ? \`
                        <div class="stat-item">
                            <h4>Rate</h4>
                            <p>\${task.stats.rate}</p>
                        </div>
                        \` : ''}
                        \${task.stats.estimatedCompletion ? \`
                        <div class="stat-item">
                            <h4>Est. Completion</h4>
                            <p>\${formatTime(task.stats.estimatedCompletion)}</p>
                        </div>
                        \` : ''}
                    </div>

                    \${task.details.length > 0 ? \`
                    <ul class="details-list">
                        \${task.details.map(detail => \`<li>\${detail}</li>\`).join('')}
                    </ul>
                    \` : ''}

                    \${task.monitorUrl ? \`
                        <a href="\${task.monitorUrl}" target="_blank" class="monitor-button">
                            📊 Open Detailed Monitor
                        </a>
                    \` : ''}
                </div>
            \`).join('');
        }

        // Initial load
        fetchTasks();

        // Auto-refresh every 5 seconds
        setInterval(fetchTasks, 5000);
    </script>
</body>
</html>
  `;

  res.send(html);
});

app.get('/api/tasks', (req, res) => {
  res.json(parallelTasks);
});

app.post('/api/tasks', (req, res) => {
  const newTask: ParallelTask = req.body;
  parallelTasks.push(newTask);
  saveTasks();
  res.json({ success: true, task: newTask });
});

app.put('/api/tasks/:id', (req, res) => {
  const { id } = req.params;
  const updates = req.body;

  const taskIndex = parallelTasks.findIndex(t => t.id === id);
  if (taskIndex !== -1) {
    parallelTasks[taskIndex] = { ...parallelTasks[taskIndex], ...updates };
    saveTasks();
    res.json({ success: true, task: parallelTasks[taskIndex] });
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});

app.delete('/api/tasks/:id', (req, res) => {
  const { id } = req.params;
  const taskIndex = parallelTasks.findIndex(t => t.id === id);

  if (taskIndex !== -1) {
    const deletedTask = parallelTasks.splice(taskIndex, 1)[0];
    saveTasks();
    res.json({ success: true, task: deletedTask });
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});


// API: Chat endpoint for universal header
app.post('/api/chat', async (req, res) => {

  // Memory system commands
  if (message.toLowerCase().match(/^(remember|note|preference|learning):/i)) {
    const parts = message.split(':');
    const type = parts[0].toLowerCase();
    const content = parts.slice(1).join(':').trim();
    const memType = type === 'remember' ? 'note' : type as any;
    agentMemory.add(content, memType);
    return res.json({
      response: `✅ Memory saved: "${content}"`,
      success: true,
      memoryAdded: true
    });
  }

  if (message.toLowerCase().includes('show') && message.toLowerCase().includes('memor')) {
    const summary = agentMemory.getContextSummary();
    return res.json({ response: summary, success: true });
  }

  if (message.toLowerCase().includes('search memor')) {
    const query = message.split(/for|about/i)[1]?.trim() || '';
    const results = agentMemory.search(query);
    return res.json({
      response: results.length > 0
        ? 'Found memories:\n' + results.map(m => `- ${m.content}`).join('\n')
        : 'No matching memories found.',
      success: true
    });
  }

  if (message.toLowerCase().includes('forget') || message.toLowerCase().includes('delete memor')) {
    const query = message.split(/forget|delete/i)[1]?.trim() || '';
    const results = agentMemory.search(query);
    if (results.length > 0) {
      agentMemory.delete(results[0].id);
      return res.json({ response: `✅ Forgot: "${results[0].content}"`, success: true });
    }
    return res.json({ response: 'Nothing found to forget.', success: true });
  }

  try {
    const { message } = req.body;
    const systemPrompt = `You are the In Parallel AI assistant for Designer Wallcoverings. Execute multiple tasks concurrently`;

    const Anthropic = require('@anthropic-ai/sdk').default;
    const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY || '' });

    const response = await anthropic.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 1024,
      system: systemPrompt,
      messages: [{ role: 'user', content: message }],
    });

    const assistantMessage = response.content[0].type === 'text' ? response.content[0].text : '';
    res.json({ response: assistantMessage, success: true });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Start server
app.listen(PORT, () => {
  console.log(`✅ In Parallel Agent running on port ${PORT}`);
  console.log(`🌐 Access at: http://45.61.58.125:${PORT}/`);
});