← back to Designer Wallcoverings
DW-Agents/dw-agents/agent-trend-research/trend-research-agent.ts.backup-oauth-20251112
1324 lines
/**
* DW-Agents: Trend Research & Vendor Relations Agent
*
* Market trend analysis and vendor management system
* - Design trend research and forecasting
* - Competitor analysis
* - Vendor relationship management
* - Market intelligence gathering
* - Chat interface for research queries
*/
import Anthropic from '@anthropic-ai/sdk';
import express, { Request, Response } from 'express';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import { chatMiddleware } from '../shared-chat-integration';
const app = express();
const PORT = process.env.PORT || 9883;
// Global Authentication - MUST come before any other middleware
app.use(cookieParser());
// Anthropic AI client
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
// Middleware
app.use(express.json());
// Add inter-agent chat widget
app.use(chatMiddleware({
agentId: 'agent-trend',
agentName: 'Trend Research',
port: 9883,
category: 'agent'
}));
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-trend-research-2025',
resave: false,
saveUninitialized: true,
rolling: true,
cookie: {
secure: false,
httpOnly: true,
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
},
})
);
// Authentication middleware
// Statistics tracking
const stats = {
totalQueries: 0,
trendsResearched: 0,
vendorsTracked: 0,
reportsGenerated: 0,
competitorAnalyses: 0,
lastActivity: new Date(),
};
// Sample data structures
interface Trend {
id: string;
name: string;
category: string;
description: string;
popularity: 'rising' | 'stable' | 'declining';
confidence: number;
dateIdentified: Date;
sources: string[];
keywords: string[];
}
interface Vendor {
id: string;
name: string;
category: string;
contactPerson: string;
email: string;
phone: string;
website: string;
rating: number;
relationship: 'excellent' | 'good' | 'fair' | 'needs-attention';
lastContact: Date;
notes: string;
}
const currentTrends: Trend[] = [
{
id: 'trend-001',
name: 'Maximalism & Bold Patterns',
category: 'Design Style',
description: 'Return of vibrant colors, busy patterns, and eclectic mixing of styles',
popularity: 'rising',
confidence: 92,
dateIdentified: new Date('2025-10-15'),
sources: ['Design Milk', 'Elle Decor', 'Trade Shows'],
keywords: ['maximalism', 'bold', 'vibrant', 'eclectic', 'patterns']
},
{
id: 'trend-002',
name: 'Sustainable & Natural Materials',
category: 'Materials',
description: 'Growing demand for eco-friendly wallcoverings with natural textures',
popularity: 'rising',
confidence: 95,
dateIdentified: new Date('2025-09-20'),
sources: ['Architectural Digest', 'Industry Reports', 'Customer Surveys'],
keywords: ['sustainable', 'eco-friendly', 'natural', 'organic', 'grasscloth']
},
{
id: 'trend-003',
name: 'Metallic Accents',
category: 'Finishes',
description: 'Subtle metallic elements adding glamour to modern spaces',
popularity: 'stable',
confidence: 88,
dateIdentified: new Date('2025-08-10'),
sources: ['House Beautiful', 'Pinterest Trends', 'Instagram Analytics'],
keywords: ['metallic', 'gold', 'silver', 'glamour', 'shimmer']
},
{
id: 'trend-004',
name: 'Geometric & Abstract Shapes',
category: 'Patterns',
description: 'Modern geometric designs with abstract interpretations',
popularity: 'stable',
confidence: 85,
dateIdentified: new Date('2025-07-25'),
sources: ['Design Within Reach', 'Trade Shows', 'Social Media'],
keywords: ['geometric', 'abstract', 'modern', 'shapes', 'linear']
},
{
id: 'trend-005',
name: 'Vintage Victorian',
category: 'Design Style',
description: 'Traditional Victorian patterns making a comeback',
popularity: 'declining',
confidence: 70,
dateIdentified: new Date('2025-06-01'),
sources: ['Historical Analysis', 'Sales Data'],
keywords: ['victorian', 'vintage', 'traditional', 'ornate', 'damask']
},
];
const vendors: Vendor[] = [
{
id: 'vendor-001',
name: 'Artisan Papers Co.',
category: 'Premium Wallcoverings',
contactPerson: 'Sarah Mitchell',
email: 'sarah@artisanpapers.com',
phone: '(555) 123-4567',
website: 'www.artisanpapers.com',
rating: 5,
relationship: 'excellent',
lastContact: new Date('2025-11-01'),
notes: 'Excellent quality, reliable delivery. Main supplier for luxury line.'
},
{
id: 'vendor-002',
name: 'EcoWall Solutions',
category: 'Sustainable Materials',
contactPerson: 'David Chen',
email: 'david@ecowall.com',
phone: '(555) 234-5678',
website: 'www.ecowall.com',
rating: 4,
relationship: 'good',
lastContact: new Date('2025-10-28'),
notes: 'Great sustainable options. Expanding product line in Q1 2026.'
},
{
id: 'vendor-003',
name: 'Global Textiles Ltd',
category: 'Grasscloth & Natural',
contactPerson: 'Maria Rodriguez',
email: 'maria@globaltextiles.com',
phone: '(555) 345-6789',
website: 'www.globaltextiles.com',
rating: 4,
relationship: 'good',
lastContact: new Date('2025-10-15'),
notes: 'Competitive pricing on grasscloth. International shipping can be slow.'
},
{
id: 'vendor-004',
name: 'Budget Walls Inc',
category: 'Value Line',
contactPerson: 'Tom Wilson',
email: 'tom@budgetwalls.com',
phone: '(555) 456-7890',
website: 'www.budgetwalls.com',
rating: 3,
relationship: 'needs-attention',
lastContact: new Date('2025-09-20'),
notes: 'Recent quality issues. Need to discuss improvement plan.'
},
];
// 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>Trend Research 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, #f093fb 0%, #f5576c 100%);
min-height: 100vh;
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;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-button {
background: white;
color: #f5576c;
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, #f093fb 0%, #f5576c 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: #f5576c;
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: #f5576c;
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, #f093fb 0%, #f5576c 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: #f5576c;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s;
}
.chip:hover {
background: #f5576c;
color: white;
border-color: #f5576c;
}
.trend-card {
background: white;
border: 2px solid #e0e0e0;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
transition: all 0.3s;
}
.trend-card:hover {
border-color: #f5576c;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.1);
}
.trend-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 10px;
}
.trend-name {
font-size: 18px;
font-weight: 600;
color: #333;
}
.trend-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.badge-rising { background: #d1fae5; color: #065f46; }
.badge-stable { background: #dbeafe; color: #1e40af; }
.badge-declining { background: #fee2e2; color: #991b1b; }
.trend-category {
color: #f5576c;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
.trend-description {
color: #666;
font-size: 14px;
line-height: 1.5;
margin-bottom: 10px;
}
.trend-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid #e0e0e0;
}
.confidence-bar {
flex: 1;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
overflow: hidden;
margin-right: 10px;
}
.confidence-fill {
height: 100%;
background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);
}
.vendor-card {
background: white;
border: 2px solid #e0e0e0;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
}
.vendor-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.vendor-name {
font-size: 18px;
font-weight: 600;
color: #333;
}
.rating-stars {
color: #f59e0b;
font-size: 16px;
}
.vendor-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-bottom: 15px;
}
.info-item {
font-size: 14px;
color: #666;
}
.info-label {
font-weight: 600;
color: #333;
}
.relationship-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
display: inline-block;
}
.rel-excellent { background: #d1fae5; color: #065f46; }
.rel-good { background: #dbeafe; color: #1e40af; }
.rel-fair { background: #fef3c7; color: #92400e; }
.rel-needs-attention { background: #fee2e2; color: #991b1b; }
.action-button {
padding: 8px 16px;
background: #f5576c;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.action-button:hover {
background: #e04858;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📊 Trend Research & Vendor Relations Agent</h1>
<p style="color: #666; margin-top: 5px;">Market Intelligence & Vendor Management</p>
<div style="margin-top: 15px;">
<span class="agent-status">● ONLINE</span>
<span style="color: #888; margin-left: 15px; font-size: 14px;">Port 9883</span>
</div>
</div>
<div class="tabs">
<button class="tab-button active" onclick="switchTab('overview')">📊 Overview</button>
<button class="tab-button" onclick="switchTab('chat')">💬 Chat Assistant</button>
<button class="tab-button" onclick="switchTab('trends')">🔥 Current Trends</button>
<button class="tab-button" onclick="switchTab('vendors')">🤝 Vendor Relations</button>
<button class="tab-button" onclick="switchTab('reports')">📈 Reports</button>
</div>
<!-- Overview Tab -->
<div id="tab-overview" class="tab-content active">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">${stats.totalQueries}</div>
<div class="stat-label">Research Queries</div>
</div>
<div class="stat-card">
<div class="stat-value">${currentTrends.length}</div>
<div class="stat-label">Active Trends</div>
</div>
<div class="stat-card">
<div class="stat-value">${vendors.length}</div>
<div class="stat-label">Vendors Tracked</div>
</div>
<div class="stat-card">
<div class="stat-value">${stats.reportsGenerated}</div>
<div class="stat-label">Reports Generated</div>
</div>
</div>
<div class="section">
<h2>🔥 Top Rising Trends</h2>
<div style="display: grid; gap: 15px;">
${currentTrends
.filter(t => t.popularity === 'rising')
.slice(0, 3)
.map(trend => `
<div class="trend-card">
<div class="trend-header">
<div class="trend-name">${trend.name}</div>
<span class="trend-badge badge-rising">RISING</span>
</div>
<div class="trend-category">${trend.category}</div>
<div class="trend-description">${trend.description}</div>
<div class="trend-footer">
<div class="confidence-bar">
<div class="confidence-fill" style="width: ${trend.confidence}%"></div>
</div>
<span style="font-size: 12px; color: #666; min-width: 50px;">${trend.confidence}% confidence</span>
</div>
</div>
`).join('')}
</div>
</div>
<div class="section">
<h2>🤝 Vendor Relationships Needing Attention</h2>
<div id="vendorAlerts">
${vendors
.filter(v => v.relationship === 'needs-attention')
.map(vendor => `
<div class="vendor-card" style="border-color: #ef4444;">
<div class="vendor-header">
<div class="vendor-name">${vendor.name}</div>
<span class="relationship-badge rel-needs-attention">NEEDS ATTENTION</span>
</div>
<div class="info-item">${vendor.notes}</div>
<div style="margin-top: 10px;">
<button class="action-button">Contact Vendor</button>
</div>
</div>
`).join('')}
${vendors.filter(v => v.relationship === 'needs-attention').length === 0 ? '<p style="color: #888;">All vendor relationships in good standing!</p>' : ''}
</div>
</div>
</div>
<!-- Chat Tab -->
<div id="tab-chat" class="tab-content">
<div class="section">
<h2>💬 Research Chat Assistant</h2>
<p style="color: #666; margin-bottom: 20px;">Ask about market trends, competitors, or vendor relationships</p>
<div class="suggestion-chips">
<span class="chip" onclick="sendSuggestion('What are the top rising trends?')">Top rising trends</span>
<span class="chip" onclick="sendSuggestion('Analyze sustainable wallcovering demand')">Sustainable demand</span>
<span class="chip" onclick="sendSuggestion('Show vendor performance')">Vendor performance</span>
<span class="chip" onclick="sendSuggestion('Competitor analysis')">Competitor analysis</span>
</div>
<div class="chat-container">
<div class="chat-messages" id="chatMessages">
<div class="message assistant">
Hello! I'm your Trend Research & Vendor Relations Agent powered by Claude 4. I can help you with:
<br><br>
• Market trend analysis and forecasting<br>
• Design trend research<br>
• Competitor intelligence<br>
• Vendor relationship management<br>
• Industry insights and recommendations<br>
<br>
What would you like to research today?
</div>
</div>
<div class="chat-input-container">
<input
type="text"
class="chat-input"
id="chatInput"
placeholder="Ask about trends, competitors, or vendors..."
onkeypress="if(event.key === 'Enter') sendMessage()"
>
<button class="send-button" onclick="sendMessage()">Send</button>
</div>
</div>
</div>
</div>
<!-- Trends Tab -->
<div id="tab-trends" class="tab-content">
<div class="section">
<h2>🔥 Current Market Trends</h2>
<div id="trendsContainer">
<p style="color: #888;">Loading trends...</p>
</div>
</div>
</div>
<!-- Vendors Tab -->
<div id="tab-vendors" class="tab-content">
<div class="section">
<h2>🤝 Vendor Relationships</h2>
<button class="action-button" style="margin-bottom: 20px;">+ Add New Vendor</button>
<div id="vendorsContainer">
<p style="color: #888;">Loading vendors...</p>
</div>
</div>
</div>
<!-- Reports Tab -->
<div id="tab-reports" class="tab-content">
<div class="section">
<h2>📈 Market Intelligence Reports</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px;">
<div style="border: 2px solid #e0e0e0; padding: 20px; border-radius: 12px;">
<h3 style="color: #f5576c; margin-bottom: 10px;">Trend Forecast Report</h3>
<p style="color: #666; font-size: 14px; margin-bottom: 15px;">Comprehensive analysis of emerging trends and predictions</p>
<div style="display: flex; gap: 10px;">
<button class="action-button" onclick="generateReport('trend-forecast', 'Trend Forecast', true)">Generate Report</button>
<button class="action-button" style="background: #6b7280;" onclick="generateReport('trend-forecast', 'Trend Forecast', false)">See Last Report</button>
</div>
</div>
<div style="border: 2px solid #e0e0e0; padding: 20px; border-radius: 12px;">
<h3 style="color: #f5576c; margin-bottom: 10px;">Competitor Analysis</h3>
<p style="color: #666; font-size: 14px; margin-bottom: 15px;">Detailed analysis of competitor offerings and positioning</p>
<div style="display: flex; gap: 10px;">
<button class="action-button" onclick="generateReport('competitor-analysis', 'Competitor Analysis', true)">Generate Report</button>
<button class="action-button" style="background: #6b7280;" onclick="generateReport('competitor-analysis', 'Competitor Analysis', false)">See Last Report</button>
</div>
</div>
<div style="border: 2px solid #e0e0e0; padding: 20px; border-radius: 12px;">
<h3 style="color: #f5576c; margin-bottom: 10px;">Vendor Performance</h3>
<p style="color: #666; font-size: 14px; margin-bottom: 15px;">Review vendor ratings and relationship health</p>
<div style="display: flex; gap: 10px;">
<button class="action-button" onclick="generateReport('vendor-performance', 'Vendor Performance', true)">Generate Report</button>
<button class="action-button" style="background: #6b7280;" onclick="generateReport('vendor-performance', 'Vendor Performance', false)">See Last Report</button>
</div>
</div>
<div style="border: 2px solid #e0e0e0; padding: 20px; border-radius: 12px;">
<h3 style="color: #f5576c; margin-bottom: 10px;">Market Opportunities</h3>
<p style="color: #666; font-size: 14px; margin-bottom: 15px;">Identify untapped market segments and opportunities</p>
<div style="display: flex; gap: 10px;">
<button class="action-button" onclick="generateReport('market-opportunities', 'Market Opportunities', true)">Generate Report</button>
<button class="action-button" style="background: #6b7280;" onclick="generateReport('market-opportunities', 'Market Opportunities', false)">See Last Report</button>
</div>
</div>
</div>
<div id="reportOutput" style="display: none; margin-top: 30px; background: white; padding: 30px; border-radius: 12px; border: 2px solid #f5576c;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 id="reportTitle" style="color: #f5576c; margin: 0;">Report</h3>
<button onclick="closeReport()" style="background: #dc143c; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer;">✕ Close</button>
</div>
<div id="reportContent" style="color: #333; line-height: 1.8; white-space: pre-wrap; font-size: 14px;"></div>
</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 === 'trends') {
loadTrends();
} else if (tabName === 'vendors') {
loadVendors();
}
}
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 loadTrends() {
const container = document.getElementById('trendsContainer');
try {
const response = await fetch('/api/trends');
const data = await response.json();
let html = '';
data.trends.forEach(trend => {
const badgeClass = 'badge-' + trend.popularity;
html += \`
<div class="trend-card">
<div class="trend-header">
<div class="trend-name">\${trend.name}</div>
<span class="trend-badge \${badgeClass}">\${trend.popularity.toUpperCase()}</span>
</div>
<div class="trend-category">\${trend.category}</div>
<div class="trend-description">\${trend.description}</div>
<div style="margin-top: 10px; font-size: 12px; color: #888;">
Sources: \${trend.sources.join(', ')}
</div>
<div class="trend-footer">
<div class="confidence-bar">
<div class="confidence-fill" style="width: \${trend.confidence}%"></div>
</div>
<span style="font-size: 12px; color: #666; min-width: 50px;">\${trend.confidence}% confidence</span>
</div>
</div>
\`;
});
container.innerHTML = html;
} catch (error) {
console.error('Load trends error:', error);
container.innerHTML = '<p style="color: #ef4444;">Error loading trends</p>';
}
}
async function loadVendors() {
const container = document.getElementById('vendorsContainer');
try {
const response = await fetch('/api/vendors');
const data = await response.json();
let html = '';
data.vendors.forEach(vendor => {
const relClass = 'rel-' + vendor.relationship.toLowerCase().replace(' ', '-');
const stars = '★'.repeat(vendor.rating) + '☆'.repeat(5 - vendor.rating);
html += \`
<div class="vendor-card">
<div class="vendor-header">
<div>
<div class="vendor-name">\${vendor.name}</div>
<div style="font-size: 14px; color: #666; margin-top: 5px;">\${vendor.category}</div>
</div>
<div>
<div class="rating-stars">\${stars}</div>
<span class="relationship-badge \${relClass}">\${vendor.relationship.toUpperCase().replace('-', ' ')}</span>
</div>
</div>
<div class="vendor-info">
<div class="info-item"><span class="info-label">Contact:</span> \${vendor.contactPerson}</div>
<div class="info-item"><span class="info-label">Email:</span> \${vendor.email}</div>
<div class="info-item"><span class="info-label">Phone:</span> \${vendor.phone}</div>
<div class="info-item"><span class="info-label">Website:</span> \${vendor.website}</div>
</div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e0e0e0;">
<div style="font-size: 14px; color: #666; margin-bottom: 10px;"><strong>Notes:</strong> \${vendor.notes}</div>
<div style="font-size: 12px; color: #888;">Last contact: \${new Date(vendor.lastContact).toLocaleDateString()}</div>
</div>
</div>
\`;
});
container.innerHTML = html;
} catch (error) {
console.error('Load vendors error:', error);
container.innerHTML = '<p style="color: #ef4444;">Error loading vendors</p>';
}
}
async function generateReport(reportType, reportTitle, forceRegenerate = true) {
const button = event.target;
const originalText = button.textContent;
button.textContent = forceRegenerate ? '⏳ Generating...' : '📋 Loading...';
button.disabled = true;
const reportOutput = document.getElementById('reportOutput');
const reportTitleEl = document.getElementById('reportTitle');
const reportContent = document.getElementById('reportContent');
try {
const response = await fetch(\`/api/reports/\${reportType}\`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ forceRegenerate })
});
const data = await response.json();
// Check if no cached report was found
if (data.error && data.error.includes('No cached report')) {
alert('No cached report found. Please generate a new report first.');
button.textContent = originalText;
button.disabled = false;
return;
}
const generatedDate = new Date(data.timestamp).toLocaleString('en-US', {
month: 'short', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: '2-digit', hour12: true
});
reportTitleEl.textContent = \`\${reportTitle} Report (Generated: \${generatedDate})\`;
reportContent.textContent = data.report;
reportOutput.style.display = 'block';
reportOutput.scrollIntoView({ behavior: 'smooth' });
} catch (error) {
console.error('Report generation error:', error);
alert('Failed to generate report. Please try again.');
} finally {
button.textContent = originalText;
button.disabled = false;
}
}
function closeReport() {
document.getElementById('reportOutput').style.display = 'none';
}
</script>
</body>
</html>
`);
});
// API Endpoints
// Chat endpoint
app.post('/api/chat', 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 Trend Research & Vendor Relations Agent for Designer Wallcoverings, powered by Claude 4 Sonnet.
Your role is to provide market intelligence and vendor management insights:
CAPABILITIES:
- Market trend analysis and forecasting
- Design trend research
- Competitor intelligence gathering
- Vendor relationship management
- Industry insights and recommendations
- Opportunity identification
CURRENT TRENDS DATA:
- Total active trends: ${currentTrends.length}
- Rising trends: ${currentTrends.filter(t => t.popularity === 'rising').length}
- Current trend categories: ${Array.from(new Set(currentTrends.map(t => t.category))).join(', ')}
KEY TRENDS TO HIGHLIGHT:
${currentTrends.slice(0, 3).map(t => `- ${t.name} (${t.popularity}): ${t.description}`).join('\n')}
VENDOR DATA:
- Total vendors: ${vendors.length}
- Excellent relationships: ${vendors.filter(v => v.relationship === 'excellent').length}
- Need attention: ${vendors.filter(v => v.relationship === 'needs-attention').length}
VENDOR CATEGORIES:
${Array.from(new Set(vendors.map(v => v.category))).join(', ')}
When users ask about trends, provide detailed analysis with actionable insights. When they ask about vendors, give specific recommendations for relationship management.
Be professional, insightful, and provide strategic recommendations based on market data.`;
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 trends
app.get('/api/trends', (req: Request, res: Response) => {
res.json({ trends: currentTrends });
});
// Get vendors
app.get('/api/vendors', (req: Request, res: Response) => {
res.json({ vendors });
});
// Get statistics
app.get('/api/stats', (req: Request, res: Response) => {
res.json(stats);
});
// Get activity log
app.get('/api/activity', (req: Request, res: Response) => {
res.json({ activities: activityLog });
});
// Report cache storage
const reportCache: { [key: string]: { report: string; timestamp: Date } } = {};
// Generate Trend Forecast Report
app.post('/api/reports/trend-forecast', async (req: Request, res: Response) => {
try {
const { forceRegenerate } = req.body;
// Check cache first
if (!forceRegenerate) {
if (reportCache['trend-forecast']) {
console.log('📋 Serving cached Trend Forecast Report');
return res.json(reportCache['trend-forecast']);
} else {
return res.status(404).json({ error: 'No cached report found. Please generate a new report first.' });
}
}
stats.reportsGenerated++;
logActivity('Report Generation', 'Trend Forecast Report requested');
const systemPrompt = `You are the Trend Research Agent generating a comprehensive Trend Forecast Report for Designer Wallcoverings.
CURRENT TRENDS DATA:
${currentTrends.map(t => `
- ${t.name} (${t.popularity})
Category: ${t.category}
Confidence: ${t.confidence}%
Description: ${t.description}
Keywords: ${t.keywords.join(', ')}
Sources: ${t.sources.join(', ')}
`).join('\n')}
Generate a detailed Trend Forecast Report with:
1. Executive Summary (key findings)
2. Rising Trends Analysis (what's gaining momentum)
3. Market Predictions (6-12 month forecast)
4. Strategic Recommendations (actionable steps)
5. Risk Assessment (potential challenges)
Format the report professionally with clear sections and actionable insights.`;
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2048,
system: systemPrompt,
messages: [{ role: 'user', content: 'Generate a comprehensive Trend Forecast Report based on current market data.' }]
});
const report = response.content[0].type === 'text'
? response.content[0].text
: 'Error generating report';
// Cache the report
const result = { report, timestamp: new Date() };
reportCache['trend-forecast'] = result;
res.json(result);
} catch (error) {
console.error('Trend forecast error:', error);
res.status(500).json({ error: 'Failed to generate report' });
}
});
// Generate Competitor Analysis Report
app.post('/api/reports/competitor-analysis', async (req: Request, res: Response) => {
try {
const { forceRegenerate } = req.body;
// Check cache first
if (!forceRegenerate) {
if (reportCache['competitor-analysis']) {
console.log('📋 Serving cached Competitor Analysis Report');
return res.json(reportCache['competitor-analysis']);
} else {
return res.status(404).json({ error: 'No cached report found. Please generate a new report first.' });
}
}
stats.reportsGenerated++;
stats.competitorAnalyses++;
logActivity('Report Generation', 'Competitor Analysis Report requested');
const systemPrompt = `You are the Trend Research Agent generating a Competitor Analysis Report for Designer Wallcoverings.
As a wallcovering and fabric distributor, analyze the competitive landscape:
KEY COMPETITORS:
- Direct: Other luxury wallcovering distributors
- Indirect: Home improvement retailers, online marketplaces
- Emerging: Direct-to-consumer brands
ANALYSIS AREAS:
1. Market Position & Differentiation
2. Pricing Strategies
3. Product Range Comparison
4. Marketing & Customer Engagement
5. Distribution Channels
6. Competitive Advantages & Weaknesses
7. Market Share Trends
8. Strategic Recommendations
CURRENT MARKET CONTEXT:
${currentTrends.slice(0, 3).map(t => `- ${t.name}: ${t.description}`).join('\n')}
Generate a detailed competitor analysis with strategic insights and actionable recommendations.`;
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2048,
system: systemPrompt,
messages: [{ role: 'user', content: 'Generate a comprehensive Competitor Analysis Report.' }]
});
const report = response.content[0].type === 'text'
? response.content[0].text
: 'Error generating report';
// Cache the report
const result = { report, timestamp: new Date() };
reportCache['competitor-analysis'] = result;
res.json(result);
} catch (error) {
console.error('Competitor analysis error:', error);
res.status(500).json({ error: 'Failed to generate report' });
}
});
// Generate Vendor Performance Report
app.post('/api/reports/vendor-performance', async (req: Request, res: Response) => {
try {
const { forceRegenerate } = req.body;
// Check cache first
if (!forceRegenerate) {
if (reportCache['vendor-performance']) {
console.log('📋 Serving cached Vendor Performance Report');
return res.json(reportCache['vendor-performance']);
} else {
return res.status(404).json({ error: 'No cached report found. Please generate a new report first.' });
}
}
stats.reportsGenerated++;
logActivity('Report Generation', 'Vendor Performance Report requested');
const systemPrompt = `You are the Trend Research Agent generating a Vendor Performance Report for Designer Wallcoverings.
VENDOR DATA:
${vendors.map(v => `
- ${v.name}
Category: ${v.category}
Rating: ${v.rating}/5 stars
Relationship: ${v.relationship}
Contact: ${v.contactPerson} (${v.email})
Last Contact: ${new Date(v.lastContact).toLocaleDateString()}
Notes: ${v.notes}
`).join('\n')}
SUMMARY STATISTICS:
- Total Vendors: ${vendors.length}
- Excellent Relationships: ${vendors.filter(v => v.relationship === 'excellent').length}
- Good Relationships: ${vendors.filter(v => v.relationship === 'good').length}
- Needs Attention: ${vendors.filter(v => v.relationship === 'needs-attention').length}
- Average Rating: ${(vendors.reduce((sum, v) => sum + v.rating, 0) / vendors.length).toFixed(1)}/5
Generate a comprehensive Vendor Performance Report with:
1. Overall Performance Summary
2. Top Performing Vendors (strengths & best practices)
3. Vendors Needing Attention (issues & action plans)
4. Relationship Health Analysis
5. Strategic Recommendations for improvement
6. Risk Assessment (dependency on key vendors)
Format professionally with actionable insights.`;
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2048,
system: systemPrompt,
messages: [{ role: 'user', content: 'Generate a comprehensive Vendor Performance Report.' }]
});
const report = response.content[0].type === 'text'
? response.content[0].text
: 'Error generating report';
// Cache the report
const result = { report, timestamp: new Date() };
reportCache['vendor-performance'] = result;
res.json(result);
} catch (error) {
console.error('Vendor performance error:', error);
res.status(500).json({ error: 'Failed to generate report' });
}
});
// Generate Market Opportunities Report
app.post('/api/reports/market-opportunities', async (req: Request, res: Response) => {
try {
const { forceRegenerate } = req.body;
// Check cache first
if (!forceRegenerate) {
if (reportCache['market-opportunities']) {
console.log('📋 Serving cached Market Opportunities Report');
return res.json(reportCache['market-opportunities']);
} else {
return res.status(404).json({ error: 'No cached report found. Please generate a new report first.' });
}
}
stats.reportsGenerated++;
logActivity('Report Generation', 'Market Opportunities Report requested');
const systemPrompt = `You are the Trend Research Agent generating a Market Opportunities Report for Designer Wallcoverings.
BUSINESS CONTEXT:
- Premium wallcoverings and fabric distributor
- Focus on luxury residential and commercial projects
- Currently tracking ${currentTrends.length} active trends
RISING TRENDS:
${currentTrends.filter(t => t.popularity === 'rising').map(t => `
- ${t.name} (${t.confidence}% confidence)
${t.description}
Keywords: ${t.keywords.join(', ')}
`).join('\n')}
VENDOR CAPABILITIES:
${Array.from(new Set(vendors.map(v => v.category))).join(', ')}
Generate a strategic Market Opportunities Report with:
1. Executive Summary (key opportunities)
2. Untapped Market Segments
- Geographic expansion opportunities
- Customer segments (residential vs commercial, luxury vs mid-market)
- Product category gaps
3. Emerging Trend Opportunities (capitalize on rising trends)
4. Partnership & Collaboration Opportunities
5. Digital & E-commerce Opportunities
6. Strategic Recommendations (prioritized action plan)
7. ROI Projections & Timeline
Focus on actionable, profitable opportunities aligned with current capabilities.`;
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2048,
system: systemPrompt,
messages: [{ role: 'user', content: 'Generate a comprehensive Market Opportunities Report.' }]
});
const report = response.content[0].type === 'text'
? response.content[0].text
: 'Error generating report';
// Cache the report
const result = { report, timestamp: new Date() };
reportCache['market-opportunities'] = result;
res.json(result);
} catch (error) {
console.error('Market opportunities error:', error);
res.status(500).json({ error: 'Failed to generate report' });
}
});
// 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, () => {
console.log(`📊 Trend Research Agent running on port ${PORT}`);
console.log(`Dashboard: http://45.61.58.125:${PORT}`);
logActivity('System Start', 'Trend Research Agent initialized');
});