Skip to main content
Glama
index.html32.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TechForge Manufacturing - Smart Quoting System</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { /* TechForge Manufacturing Brand Colors */ --primary-blue: #003d82; --primary-orange: #ff6b35; --dark-blue: #002347; --light-blue: #0066cc; --accent-yellow: #ffc107; --gray-dark: #2c3e50; --gray-medium: #34495e; --gray-light: #ecf0f1; --white: #ffffff; --success: #27ae60; --warning: #f39c12; --danger: #e74c3c; } body { font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, var(--dark-blue) 0%, var(--primary-blue) 100%); min-height: 100vh; padding: 0; } /* Top Brand Bar */ .brand-bar { background: var(--dark-blue); padding: 15px 30px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); display: flex; justify-content: space-between; align-items: center; } .brand-logo { display: flex; align-items: center; gap: 15px; } .logo-icon { width: 50px; height: 50px; background: var(--primary-orange); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 24px; color: white; box-shadow: 0 4px 8px rgba(255, 107, 53, 0.3); } .brand-text h1 { color: var(--white); font-size: 28px; font-weight: 700; margin: 0; letter-spacing: -0.5px; } .brand-text p { color: var(--primary-orange); font-size: 13px; margin: 0; font-weight: 500; letter-spacing: 1px; text-transform: uppercase; } .brand-tagline { color: var(--gray-light); font-size: 14px; font-style: italic; } .container { max-width: 1400px; margin: 0 auto; padding: 20px; } header { background: var(--white); border-radius: 12px; padding: 30px; margin-bottom: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-left: 5px solid var(--primary-orange); } header h2 { color: var(--primary-blue); font-size: 28px; margin-bottom: 10px; } .subtitle { color: var(--gray-medium); font-size: 16px; } .nav-tabs { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; } .nav-tab { background: var(--white); border: 2px solid var(--primary-blue); padding: 15px 30px; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1); color: var(--primary-blue); } .nav-tab:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,61,130,0.2); background: var(--light-blue); color: white; border-color: var(--light-blue); } .nav-tab.active { background: var(--primary-orange); color: white; border-color: var(--primary-orange); box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3); } .content-panel { display: none; } .content-panel.active { display: block; } .card { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); margin-bottom: 20px; border-left: 5px solid var(--primary-blue); } .card h2 { color: var(--primary-blue); margin-bottom: 20px; font-size: 24px; display: flex; align-items: center; gap: 10px; } .card h2::before { content: "⚙"; color: var(--primary-orange); font-size: 28px; } .form-group { margin-bottom: 20px; } label { display: block; margin-bottom: 8px; color: var(--gray-dark); font-weight: 600; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; } input[type="text"], input[type="email"], input[type="number"], textarea { width: 100%; padding: 12px 15px; border: 2px solid var(--gray-light); border-radius: 8px; font-size: 14px; transition: all 0.3s; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: var(--primary-orange); box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.1); } textarea { min-height: 120px; resize: vertical; } .btn { background: var(--primary-orange); color: white; border: none; padding: 14px 35px; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s; text-transform: uppercase; letter-spacing: 0.5px; box-shadow: 0 4px 8px rgba(255, 107, 53, 0.3); } .btn:hover { background: #ff5722; transform: translateY(-2px); box-shadow: 0 6px 12px rgba(255, 107, 53, 0.4); } .btn:disabled { background: var(--gray-light); color: var(--gray-medium); cursor: not-allowed; transform: none; box-shadow: none; } .btn-secondary { background: var(--primary-blue); box-shadow: 0 4px 8px rgba(0, 61, 130, 0.3); } .btn-secondary:hover { background: var(--light-blue); box-shadow: 0 6px 12px rgba(0, 61, 130, 0.4); } .results { margin-top: 30px; } .alert { padding: 20px; border-radius: 8px; margin-bottom: 20px; border-left: 5px solid; font-weight: 500; } .alert-success { background: #d4edda; color: #155724; border-left-color: var(--success); } .alert-warning { background: #fff3cd; color: #856404; border-left-color: var(--warning); } .alert-danger { background: #f8d7da; color: #721c24; border-left-color: var(--danger); } .alert-info { background: #d1ecf1; color: #0c5460; border-left-color: var(--light-blue); } .match-card { background: var(--gray-light); border: 2px solid #ddd; border-left: 5px solid var(--primary-orange); border-radius: 8px; padding: 20px; margin-bottom: 15px; transition: all 0.3s; } .match-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); transform: translateX(5px); } .match-score { display: inline-block; padding: 6px 16px; border-radius: 20px; font-weight: bold; font-size: 14px; } .score-high { background: var(--success); color: white; } .score-medium { background: var(--warning); color: white; } .score-low { background: var(--danger); color: white; } .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; } .stat-card { background: var(--white); border: 3px solid var(--primary-blue); color: var(--primary-blue); padding: 30px; border-radius: 12px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.1); transition: all 0.3s; } .stat-card:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0,61,130,0.2); border-color: var(--primary-orange); } .stat-value { font-size: 48px; font-weight: bold; margin-bottom: 10px; color: var(--primary-orange); } .stat-label { font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: var(--primary-blue); } .loading { display: inline-block; width: 20px; height: 20px; border: 3px solid var(--gray-light); border-top: 3px solid var(--primary-orange); border-radius: 50%; animation: spin 1s linear infinite; margin-left: 10px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .quote-table { width: 100%; border-collapse: collapse; margin-top: 20px; } .quote-table th, .quote-table td { padding: 15px; text-align: left; border-bottom: 2px solid var(--gray-light); } .quote-table th { background: var(--primary-blue); font-weight: 600; color: white; text-transform: uppercase; font-size: 13px; letter-spacing: 0.5px; } .quote-table tr:hover { background: rgba(0, 61, 130, 0.05); } .badge { display: inline-block; padding: 5px 12px; border-radius: 12px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; } .badge-success { background: var(--success); color: white; } .badge-draft { background: var(--warning); color: white; } .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .cost-breakdown { background: var(--gray-light); padding: 20px; border-radius: 8px; margin-top: 15px; border-left: 4px solid var(--primary-orange); } .cost-item { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #ddd; font-size: 15px; } .cost-item:last-child { border-bottom: none; font-weight: bold; font-size: 20px; margin-top: 10px; padding-top: 15px; border-top: 3px solid var(--primary-blue); color: var(--primary-blue); } .empty-state { text-align: center; padding: 60px 20px; color: var(--gray-medium); } .footer { background: var(--dark-blue); color: var(--gray-light); padding: 20px; text-align: center; margin-top: 40px; border-radius: 12px; } .footer p { margin: 5px 0; font-size: 14px; } /* Industrial accent elements */ h3 { color: var(--primary-blue); border-bottom: 3px solid var(--primary-orange); padding-bottom: 10px; margin-bottom: 20px; margin-top: 30px; } @media (max-width: 768px) { .two-col { grid-template-columns: 1fr; } .nav-tabs { flex-direction: column; } .brand-bar { flex-direction: column; gap: 15px; text-align: center; } .stat-value { font-size: 36px; } } </style> </head> <body> <!-- Brand Header --> <div class="brand-bar"> <div class="brand-logo"> <div class="logo-icon">TF</div> <div class="brand-text"> <h1>TechForge Manufacturing</h1> <p>Precision Engineering Solutions</p> </div> </div> <div class="brand-tagline"> "Forging Innovation Since 1985" </div> </div> <div class="container"> <header> <h2>Smart Quoting System</h2> <p class="subtitle">AI-powered quote generation with historical data intelligence</p> </header> <div class="nav-tabs"> <button class="nav-tab active" onclick="switchTab('new-quote')">New Quote</button> <button class="nav-tab" onclick="switchTab('history')">Quote History</button> <button class="nav-tab" onclick="switchTab('dashboard')">Dashboard</button> </div> <!-- New Quote Panel --> <div id="new-quote" class="content-panel active"> <div class="card"> <h2>Create New Quote</h2> <form id="rfpForm" onsubmit="submitRFP(event)"> <div class="form-group"> <label for="rfpText">RFP Description *</label> <textarea id="rfpText" name="rfpText" required placeholder="Example: We need 200 pieces of 6061-T6 aluminum brackets, CNC machined with anodized finish. Tolerance +/-0.005. Delivery needed by March 2025."></textarea> <small style="color: #666;">Include material, quantity, processes, tolerances, and any special requirements</small> </div> <div class="two-col"> <div class="form-group"> <label for="quantity">Quantity *</label> <input type="number" id="quantity" name="quantity" required min="1" placeholder="200"> </div> <div class="form-group"> <label for="customerName">Customer Name</label> <input type="text" id="customerName" name="customerName" placeholder="Acme Manufacturing"> </div> </div> <div class="two-col"> <div class="form-group"> <label for="contactEmail">Contact Email</label> <input type="email" id="contactEmail" name="contactEmail" placeholder="buyer@example.com"> </div> <div class="form-group"> <label for="dueDate">Due Date</label> <input type="text" id="dueDate" name="dueDate" placeholder="2025-03-15"> </div> </div> <button type="submit" class="btn" id="submitBtn"> Generate Quote </button> <button type="button" class="btn btn-secondary" onclick="clearForm()" style="margin-left: 10px;"> Clear Form </button> </form> <div id="results" class="results" style="display: none;"></div> </div> </div> <!-- Historical Quotes Panel --> <div id="history" class="content-panel"> <div class="card"> <h2>Historical Quote Database</h2> <button class="btn" onclick="loadHistoricalQuotes()" style="margin-bottom: 20px;"> Refresh Data </button> <div id="historicalQuotesTable"></div> </div> </div> <!-- Dashboard Panel --> <div id="dashboard" class="content-panel"> <div class="card"> <h2>System Overview</h2> <div class="grid" id="dashboardStats"> <div class="stat-card"> <div class="stat-value" id="totalQuotes">--</div> <div class="stat-label">Total Quotes</div> </div> <div class="stat-card"> <div class="stat-value" id="avgLeadTime">--</div> <div class="stat-label">Avg Lead Time (Days)</div> </div> <div class="stat-card"> <div class="stat-value" id="avgCost">--</div> <div class="stat-label">Avg Cost Per Unit</div> </div> </div> </div> <div class="card"> <h2>Quick Actions</h2> <button class="btn" onclick="loadSampleRFQ()" style="margin-right: 10px;">Load Sample RFQ</button> <button class="btn btn-secondary" onclick="window.location.reload()">Refresh Page</button> </div> <div class="card"> <h2>System Status</h2> <div id="systemStatus">Checking...</div> </div> </div> <!-- Footer --> <div class="footer"> <p><strong>TechForge Manufacturing</strong> | 1985 Industrial Way, Manufacturing City, USA</p> <p>Phone: (555) 123-4567 | Email: quotes@techforge.example.com</p> <p>© 2024 TechForge Manufacturing. All rights reserved.</p> </div> </div> <script> const API_BASE = window.location.origin; // Tab switching function switchTab(tabName) { document.querySelectorAll('.content-panel').forEach(panel => { panel.classList.remove('active'); }); document.querySelectorAll('.nav-tab').forEach(tab => { tab.classList.remove('active'); }); document.getElementById(tabName).classList.add('active'); event.target.classList.add('active'); if (tabName === 'history') { loadHistoricalQuotes(); } else if (tabName === 'dashboard') { loadDashboard(); } } // Submit RFP Form async function submitRFP(event) { event.preventDefault(); const submitBtn = document.getElementById('submitBtn'); const resultsDiv = document.getElementById('results'); submitBtn.disabled = true; submitBtn.innerHTML = 'Processing...<span class="loading"></span>'; resultsDiv.style.display = 'none'; const formData = { rfp: { rawText: document.getElementById('rfpText').value, qty: parseInt(document.getElementById('quantity').value), customerName: document.getElementById('customerName').value || undefined, contactEmail: document.getElementById('contactEmail').value || undefined, dueDate: document.getElementById('dueDate').value || undefined } }; try { const response = await fetch(`${API_BASE}/mcp/invoke/evaluateRfpAndDraftQuote`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await response.json(); if (data.ok) { displayResults(data.result); } else { showError(data.error || 'Failed to process RFP'); } } catch (error) { showError('Failed to connect to server. Is it running?'); } finally { submitBtn.disabled = false; submitBtn.innerHTML = 'Generate Quote'; } } // Display results function displayResults(result) { const { parsedRfp, matches, estimate, doc } = result; const resultsDiv = document.getElementById('results'); const confidenceClass = estimate.confidence === 'high' ? 'success' : estimate.confidence === 'medium' ? 'warning' : 'danger'; let html = ` <div class="alert alert-${confidenceClass}"> <strong>Confidence: ${estimate.confidence.toUpperCase()}</strong><br> ${getConfidenceMessage(estimate.confidence)} </div> <h3>Parsed RFP</h3> <div style="background: #ecf0f1; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #ff6b35;"> <p><strong>Material:</strong> ${parsedRfp.material || 'Not specified'}</p> <p><strong>Processes:</strong> ${parsedRfp.processes?.join(', ') || 'Not specified'}</p> <p><strong>Quantity:</strong> ${parsedRfp.qty}</p> <p><strong>Tolerances:</strong> ${parsedRfp.tolerances || 'Not specified'}</p> </div> <h3>Similar Historical Quotes (${matches.length} found)</h3> `; if (matches.length > 0) { matches.slice(0, 3).forEach(match => { const scorePercent = (match.score * 100).toFixed(1); const scoreClass = match.score >= 0.85 ? 'high' : match.score >= 0.70 ? 'medium' : 'low'; html += ` <div class="match-card"> <div style="display: flex; justify-content: space-between; margin-bottom: 10px;"> <strong style="color: var(--primary-blue);">${match.quote.id}</strong> <span class="match-score score-${scoreClass}">${scorePercent}% Match</span> </div> <p><strong>Customer:</strong> ${match.quote.customerName || 'N/A'}</p> <p><strong>Cost:</strong> $${match.quote.costPerUnit.toFixed(2)}/unit | Lead: ${match.quote.leadDays} days</p> <p><strong>Matching:</strong> ${match.matchingFields.join(', ')}</p> <p style="color: #666; font-size: 14px; margin-top: 5px;">${match.notes}</p> </div> `; }); } else { html += `<p style="color: #666;">No similar historical quotes found. This appears to be a new type of request.</p>`; } html += ` <h3>Cost Estimate</h3> <div class="cost-breakdown"> <div class="cost-item"> <span>Material Cost</span> <span>$${estimate.breakdown.materialCost.toFixed(2)}</span> </div> <div class="cost-item"> <span>Processing Cost</span> <span>$${estimate.breakdown.processCost.toFixed(2)}</span> </div> <div class="cost-item"> <span>Tooling (per unit)</span> <span>$${estimate.breakdown.toolingPerUnit.toFixed(2)}</span> </div> <div class="cost-item"> <span>Overhead</span> <span>$${estimate.breakdown.overhead.toFixed(2)}</span> </div> <div class="cost-item"> <span>Price Per Unit</span> <span>$${estimate.pricePerUnit.toFixed(2)}</span> </div> <div class="cost-item"> <span>Total Price (${parsedRfp.qty} units)</span> <span>$${estimate.totalPrice.toFixed(2)}</span> </div> </div> <h3 style="margin-top: 30px;">Quote Details</h3> <div style="background: #ecf0f1; padding: 15px; border-radius: 8px; border-left: 4px solid #003d82;"> <p><strong>Quote ID:</strong> ${doc.quoteId}</p> <p><strong>Lead Time:</strong> ${estimate.leadDays} days</p> <p><strong>Status:</strong> <span class="badge badge-draft">DRAFT</span></p> <p><strong>Payment Terms:</strong> ${doc.terms}</p> </div> `; resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function getConfidenceMessage(confidence) { if (confidence === 'high') { return 'This estimate is based on very similar past work. The pricing should be reliable.'; } else if (confidence === 'medium') { return 'This estimate is based on similar work, but some adjustments may be needed. Review before sending.'; } else { return 'This is a new type of work with limited historical data. Engineering review is strongly recommended.'; } } // Load historical quotes async function loadHistoricalQuotes() { const tableDiv = document.getElementById('historicalQuotesTable'); tableDiv.innerHTML = '<p>Loading...</p>'; try { const response = await fetch(`${API_BASE}/mcp/utility/historicalQuotes`); const data = await response.json(); if (data.ok && data.quotes.length > 0) { let html = ` <table class="quote-table"> <thead> <tr> <th>Quote ID</th> <th>Customer</th> <th>Material</th> <th>Processes</th> <th>Qty Range</th> <th>Cost/Unit</th> <th>Lead Days</th> <th>Status</th> </tr> </thead> <tbody> `; data.quotes.forEach(quote => { html += ` <tr> <td><strong>${quote.id}</strong></td> <td>${quote.customerName || 'N/A'}</td> <td>${quote.normalized.material}</td> <td>${quote.normalized.processes.join(', ')}</td> <td>${quote.normalized.qtyRange[0]}-${quote.normalized.qtyRange[1]}</td> <td>$${quote.costPerUnit.toFixed(2)}</td> <td>${quote.leadDays}${quote.actualLeadDays ? ` (actual: ${quote.actualLeadDays})` : ''}</td> <td><span class="badge ${quote.approved ? 'badge-success' : 'badge-draft'}">${quote.approved ? 'Approved' : 'Draft'}</span></td> </tr> `; }); html += ` </tbody> </table> `; tableDiv.innerHTML = html; } else { tableDiv.innerHTML = '<div class="empty-state"><p>No historical quotes found.</p></div>'; } } catch (error) { tableDiv.innerHTML = '<div class="alert alert-danger">Failed to load historical quotes</div>'; } } // Load dashboard async function loadDashboard() { try { const response = await fetch(`${API_BASE}/mcp/utility/historicalQuotes`); const data = await response.json(); if (data.ok && data.quotes.length > 0) { const quotes = data.quotes; document.getElementById('totalQuotes').textContent = quotes.length; const avgLead = quotes.reduce((sum, q) => sum + q.leadDays, 0) / quotes.length; document.getElementById('avgLeadTime').textContent = avgLead.toFixed(1); const avgCost = quotes.reduce((sum, q) => sum + q.costPerUnit, 0) / quotes.length; document.getElementById('avgCost').textContent = '$' + avgCost.toFixed(2); } // Check system status const healthResponse = await fetch(`${API_BASE}/health`); const healthData = await healthResponse.json(); document.getElementById('systemStatus').innerHTML = ` <div class="alert alert-success"> <strong>✓ System Online</strong><br> Server is running and ready to process quotes. </div> `; } catch (error) { document.getElementById('systemStatus').innerHTML = ` <div class="alert alert-danger"> <strong>✗ System Offline</strong><br> Cannot connect to server. Please start the server first. </div> `; } } // Helper functions function showError(message) { const resultsDiv = document.getElementById('results'); resultsDiv.innerHTML = `<div class="alert alert-danger"><strong>Error:</strong> ${message}</div>`; resultsDiv.style.display = 'block'; } function clearForm() { document.getElementById('rfpForm').reset(); document.getElementById('results').style.display = 'none'; } function loadSampleRFQ() { document.getElementById('rfpText').value = 'We need 200 pieces of 6061-T6 aluminum brackets, CNC machined with anodized finish. Tolerance +/-0.005. Delivery needed by March 2025.'; document.getElementById('quantity').value = '200'; document.getElementById('customerName').value = 'Sample Customer'; document.getElementById('contactEmail').value = 'buyer@example.com'; document.getElementById('dueDate').value = '2025-03-15'; switchTab('new-quote'); document.querySelector('.nav-tab').click(); } // Initialize dashboard on page load window.addEventListener('load', () => { loadDashboard(); }); </script> </body> </html>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/r-long/mcp-quoting-system'

If you have feedback or need assistance with the MCP directory API, please join our Discord server