| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>API Demo | Fair Dispatch System</title> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> |
| <style> |
| :root { |
| --bg-primary: #0a0f1a; |
| --bg-secondary: #0d1929; |
| --bg-card: #0f1e2e; |
| --accent-primary: #00d4aa; |
| --accent-secondary: #00b894; |
| --accent-glow: rgba(0, 212, 170, 0.4); |
| --text-primary: #ffffff; |
| --text-secondary: #94a3b8; |
| --text-muted: #64748b; |
| --border-primary: #1e3a5f; |
| --status-success: #00d4aa; |
| --status-error: #ef4444; |
| --status-warning: #fbbf24; |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| min-height: 100vh; |
| } |
| |
| .header { |
| height: 60px; |
| background: var(--bg-secondary); |
| border-bottom: 1px solid var(--border-primary); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 0 24px; |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| z-index: 100; |
| } |
| |
| .header-left { |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| } |
| |
| .logo { |
| width: 40px; |
| height: 40px; |
| background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .logo svg { |
| width: 24px; |
| height: 24px; |
| color: var(--bg-primary); |
| } |
| |
| .header-title h1 { |
| font-size: 18px; |
| font-weight: 600; |
| } |
| |
| .header-title .subtitle { |
| font-size: 12px; |
| color: var(--text-muted); |
| } |
| |
| .header-right { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| |
| .btn { |
| display: inline-flex; |
| align-items: center; |
| gap: 8px; |
| padding: 10px 20px; |
| border-radius: 8px; |
| font-size: 14px; |
| font-weight: 600; |
| cursor: pointer; |
| border: none; |
| transition: all 0.2s ease; |
| } |
| |
| .btn-primary { |
| background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); |
| color: var(--bg-primary); |
| box-shadow: 0 4px 16px var(--accent-glow); |
| } |
| |
| .btn-primary:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 24px var(--accent-glow); |
| } |
| |
| .btn-primary:disabled { |
| opacity: 0.6; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| .btn-secondary { |
| background: var(--bg-card); |
| color: var(--text-secondary); |
| border: 1px solid var(--border-primary); |
| } |
| |
| .btn-secondary:hover { |
| background: var(--bg-secondary); |
| color: var(--text-primary); |
| border-color: var(--accent-primary); |
| } |
| |
| .main-content { |
| margin-top: 60px; |
| padding: 24px; |
| display: flex; |
| flex-direction: column; |
| gap: 20px; |
| min-height: calc(100vh - 60px); |
| } |
| |
| .metrics-bar { |
| display: grid; |
| grid-template-columns: repeat(4, 1fr); |
| gap: 16px; |
| } |
| |
| .metric-card { |
| background: var(--bg-card); |
| border: 1px solid var(--border-primary); |
| border-radius: 12px; |
| padding: 16px 20px; |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| transition: all 0.3s ease; |
| } |
| |
| .metric-card:hover { |
| border-color: var(--accent-primary); |
| box-shadow: 0 4px 16px rgba(0, 212, 170, 0.1); |
| } |
| |
| .metric-label { |
| font-size: 12px; |
| color: var(--text-muted); |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .metric-value { |
| font-size: 28px; |
| font-weight: 700; |
| color: var(--accent-primary); |
| } |
| |
| .metric-value.pending { |
| color: var(--text-muted); |
| } |
| |
| .panels-container { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 20px; |
| flex: 1; |
| } |
| |
| .panel { |
| background: var(--bg-card); |
| border: 1px solid var(--border-primary); |
| border-radius: 12px; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| } |
| |
| .panel-header { |
| padding: 16px 20px; |
| border-bottom: 1px solid var(--border-primary); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| } |
| |
| .panel-title { |
| font-size: 14px; |
| font-weight: 600; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .panel-title svg { |
| width: 18px; |
| height: 18px; |
| color: var(--accent-primary); |
| } |
| |
| .panel-actions { |
| display: flex; |
| gap: 8px; |
| } |
| |
| .panel-btn { |
| padding: 6px 12px; |
| font-size: 12px; |
| border-radius: 6px; |
| background: var(--bg-secondary); |
| color: var(--text-secondary); |
| border: 1px solid var(--border-primary); |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .panel-btn:hover { |
| color: var(--text-primary); |
| border-color: var(--accent-primary); |
| } |
| |
| .panel-body { |
| flex: 1; |
| padding: 0; |
| position: relative; |
| } |
| |
| .json-textarea { |
| width: 100%; |
| height: 100%; |
| min-height: 400px; |
| background: transparent; |
| border: none; |
| color: var(--text-primary); |
| font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
| font-size: 13px; |
| line-height: 1.6; |
| padding: 16px 20px; |
| resize: none; |
| outline: none; |
| } |
| |
| .json-textarea:focus { |
| background: rgba(0, 212, 170, 0.02); |
| } |
| |
| .json-textarea.readonly { |
| color: var(--text-secondary); |
| } |
| |
| .json-textarea::placeholder { |
| color: var(--text-muted); |
| } |
| |
| .status-indicator { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| .status-dot { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: var(--status-success); |
| } |
| |
| .status-dot.loading { |
| animation: pulse 1s infinite; |
| background: var(--status-warning); |
| } |
| |
| .status-dot.error { |
| background: var(--status-error); |
| } |
| |
| @keyframes pulse { |
| |
| 0%, |
| 100% { |
| opacity: 1; |
| transform: scale(1); |
| } |
| |
| 50% { |
| opacity: 0.5; |
| transform: scale(1.2); |
| } |
| } |
| |
| .alert { |
| padding: 16px 20px; |
| border-radius: 8px; |
| font-size: 14px; |
| display: none; |
| } |
| |
| .alert.error { |
| display: block; |
| background: rgba(239, 68, 68, 0.1); |
| border: 1px solid var(--status-error); |
| color: var(--status-error); |
| } |
| |
| .alert.success { |
| display: block; |
| background: rgba(0, 212, 170, 0.1); |
| border: 1px solid var(--status-success); |
| color: var(--status-success); |
| } |
| |
| .curl-section { |
| background: var(--bg-card); |
| border: 1px solid var(--border-primary); |
| border-radius: 12px; |
| padding: 20px; |
| } |
| |
| .curl-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| margin-bottom: 16px; |
| } |
| |
| .curl-title { |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text-primary); |
| } |
| |
| .curl-code { |
| background: var(--bg-secondary); |
| border-radius: 8px; |
| padding: 16px; |
| overflow-x: auto; |
| } |
| |
| .curl-code pre { |
| font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
| font-size: 12px; |
| line-height: 1.6; |
| color: var(--accent-primary); |
| margin: 0; |
| white-space: pre-wrap; |
| } |
| |
| @media (max-width: 1024px) { |
| .panels-container { |
| grid-template-columns: 1fr; |
| } |
| |
| .metrics-bar { |
| grid-template-columns: repeat(2, 1fr); |
| } |
| } |
| |
| @media (max-width: 640px) { |
| .metrics-bar { |
| grid-template-columns: 1fr; |
| } |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <header class="header"> |
| <div class="header-left"> |
| <div class="logo"> |
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" /> |
| </svg> |
| </div> |
| <div class="header-title"> |
| <h1>API Demo</h1> |
| <span class="subtitle">Fair Dispatch System</span> |
| </div> |
| </div> |
| <div class="header-right"> |
| <div class="status-indicator" id="status-indicator"> |
| <span class="status-dot" id="status-dot"></span> |
| <span id="status-text">Ready</span> |
| </div> |
| <button class="btn btn-primary" id="run-btn" onclick="runAllocation()"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" |
| stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| Start Allocation |
| </button> |
| <a href="/" class="btn btn-secondary"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" |
| stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> |
| </svg> |
| Back to Dashboard |
| </a> |
| </div> |
| </header> |
|
|
| <main class="main-content"> |
| <div id="alert-container"></div> |
|
|
| <div class="metrics-bar"> |
| <div class="metric-card"> |
| <span class="metric-label">Gini Index</span> |
| <span class="metric-value pending" id="metric-gini">—</span> |
| </div> |
| <div class="metric-card"> |
| <span class="metric-label">Std Deviation</span> |
| <span class="metric-value pending" id="metric-stddev">—</span> |
| </div> |
| <div class="metric-card"> |
| <span class="metric-label">Avg Workload</span> |
| <span class="metric-value pending" id="metric-avg">—</span> |
| </div> |
| <div class="metric-card"> |
| <span class="metric-label">Assignments</span> |
| <span class="metric-value pending" id="metric-assignments">—</span> |
| </div> |
| </div> |
|
|
| <div class="panels-container"> |
| <div class="panel"> |
| <div class="panel-header"> |
| <span class="panel-title"> |
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /> |
| </svg> |
| Input JSON (AllocationRequest) |
| </span> |
| <div class="panel-actions"> |
| <button class="panel-btn" onclick="formatInput()">Format</button> |
| <button class="panel-btn" onclick="resetInput()">Reset</button> |
| </div> |
| </div> |
| <div class="panel-body"> |
| <textarea id="input-json" class="json-textarea" |
| placeholder="Paste your AllocationRequest JSON here..."></textarea> |
| </div> |
| </div> |
|
|
| <div class="panel"> |
| <div class="panel-header"> |
| <span class="panel-title"> |
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> |
| </svg> |
| Output JSON (AllocationResponse) |
| </span> |
| <div class="panel-actions"> |
| <button class="panel-btn" onclick="copyOutput()">Copy</button> |
| </div> |
| </div> |
| <div class="panel-body"> |
| <textarea id="output-json" class="json-textarea readonly" readonly |
| placeholder="Response will appear here..."></textarea> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="supervision-link-container" |
| style="display: none; margin: 20px; padding: 16px; background: linear-gradient(135deg, rgba(0, 212, 170, 0.15) 0%, rgba(0, 184, 148, 0.15) 100%); border: 1px solid #00d4aa; border-radius: 12px; text-align: center;"> |
| <div style="display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap;"> |
| <span style="color: #00d4aa; font-size: 14px;"> |
| <strong>✓ Allocation Complete</strong> — Run ID: <code id="run-id-display" |
| style="background: rgba(0,0,0,0.3); padding: 2px 8px; border-radius: 4px;">—</code> |
| </span> |
| <a id="supervision-link" href="#" target="_blank" |
| style="display: inline-flex; align-items: center; gap: 8px; padding: 10px 24px; background: linear-gradient(135deg, #00d4aa 0%, #00b894 100%); color: #0a0f1a; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.2s ease;"> |
| 🎯 Open Live Supervision Dashboard |
| </a> |
| </div> |
| </div> |
|
|
| <div class="curl-section"> |
| <div class="curl-header"> |
| <span class="curl-title">📋 cURL Example</span> |
| <button class="panel-btn" onclick="copyCurl()">Copy cURL</button> |
| </div> |
| <div class="curl-code"> |
| <pre id="curl-example">curl -X POST "http://localhost:8000/api/v1/allocate" \ |
| -H "Content-Type: application/json" \ |
| -d '{ |
| "allocation_date": "2026-02-05", |
| "warehouse": {"lat": 12.9716, "lng": 77.5946}, |
| "drivers": [ |
| {"id": "driver_001", "name": "Raju", "vehicle_capacity_kg": 150, "preferred_language": "en"}, |
| {"id": "driver_002", "name": "Priya", "vehicle_capacity_kg": 120, "preferred_language": "ta"} |
| ], |
| "packages": [ |
| {"id": "pkg_001", "weight_kg": 2.5, "fragility_level": 3, "address": "123 Main St", "latitude": 12.97, "longitude": 77.60, "priority": "NORMAL"}, |
| {"id": "pkg_002", "weight_kg": 8.0, "fragility_level": 2, "address": "456 Oak Ave", "latitude": 12.98, "longitude": 77.61, "priority": "HIGH"} |
| ] |
| }'</pre> |
| </div> |
| </div> |
| </main> |
|
|
| <script> |
| |
| const DEFAULT_REQUEST = { |
| allocation_date: new Date().toISOString().split('T')[0], |
| warehouse: { lat: 12.9716, lng: 77.5946 }, |
| drivers: [ |
| { id: "driver_001", name: "Raju", vehicle_capacity_kg: 150, preferred_language: "en" }, |
| { id: "driver_002", name: "Priya", vehicle_capacity_kg: 120, preferred_language: "ta" }, |
| { id: "driver_003", name: "Kumar", vehicle_capacity_kg: 180, preferred_language: "en" }, |
| { id: "driver_004", name: "Lakshmi", vehicle_capacity_kg: 100, preferred_language: "hi" }, |
| { id: "driver_005", name: "Arjun", vehicle_capacity_kg: 160, preferred_language: "te" } |
| ], |
| packages: [ |
| { id: "pkg_001", weight_kg: 2.5, fragility_level: 3, address: "4A, Ruby Apartment, 3rd Main Road, Koramangala", latitude: 12.9352, longitude: 77.6245, priority: "NORMAL" }, |
| { id: "pkg_002", weight_kg: 8.0, fragility_level: 2, address: "No. 12, 2nd Cross Street, Indiranagar", latitude: 12.9716, longitude: 77.6411, priority: "HIGH" }, |
| { id: "pkg_003", weight_kg: 1.5, fragility_level: 1, address: "45, Brigade Road, Ashok Nagar", latitude: 12.9719, longitude: 77.6074, priority: "NORMAL" }, |
| { id: "pkg_004", weight_kg: 5.0, fragility_level: 4, address: "78, MG Road, Near Trinity Metro", latitude: 12.9756, longitude: 77.6066, priority: "EXPRESS" }, |
| { id: "pkg_005", weight_kg: 3.2, fragility_level: 2, address: "23, Residency Road, Richmond Circle", latitude: 12.9682, longitude: 77.5973, priority: "NORMAL" }, |
| { id: "pkg_006", weight_kg: 6.8, fragility_level: 3, address: "101, Commercial Street, Shivaji Nagar", latitude: 12.9833, longitude: 77.6072, priority: "HIGH" }, |
| { id: "pkg_007", weight_kg: 2.1, fragility_level: 1, address: "55, Cunningham Road, Vasanth Nagar", latitude: 12.9927, longitude: 77.5855, priority: "NORMAL" }, |
| { id: "pkg_008", weight_kg: 4.5, fragility_level: 5, address: "88, Lavelle Road, Langford Town", latitude: 12.9644, longitude: 77.5957, priority: "EXPRESS" }, |
| { id: "pkg_009", weight_kg: 7.2, fragility_level: 2, address: "32, Vittal Mallya Road, Cubbon Park", latitude: 12.9738, longitude: 77.5956, priority: "NORMAL" }, |
| { id: "pkg_010", weight_kg: 1.8, fragility_level: 1, address: "67, Infantry Road, Shivaji Nagar", latitude: 12.9864, longitude: 77.5961, priority: "HIGH" } |
| ] |
| }; |
| |
| |
| document.getElementById('input-json').value = JSON.stringify(DEFAULT_REQUEST, null, 2); |
| |
| function setStatus(status, text) { |
| const dot = document.getElementById('status-dot'); |
| const textEl = document.getElementById('status-text'); |
| |
| dot.className = 'status-dot'; |
| if (status === 'loading') dot.classList.add('loading'); |
| if (status === 'error') dot.classList.add('error'); |
| |
| textEl.textContent = text; |
| } |
| |
| function showAlert(type, message) { |
| const container = document.getElementById('alert-container'); |
| container.innerHTML = `<div class="alert ${type}">${message}</div>`; |
| setTimeout(() => container.innerHTML = '', 5000); |
| } |
| |
| function updateMetrics(data) { |
| const gf = data.global_fairness || {}; |
| |
| document.getElementById('metric-gini').textContent = |
| gf.gini_index !== undefined ? gf.gini_index.toFixed(3) : '—'; |
| document.getElementById('metric-stddev').textContent = |
| gf.std_dev !== undefined ? gf.std_dev.toFixed(2) : '—'; |
| document.getElementById('metric-avg').textContent = |
| gf.avg_workload !== undefined ? gf.avg_workload.toFixed(2) : '—'; |
| document.getElementById('metric-assignments').textContent = |
| data.assignments ? data.assignments.length : '—'; |
| |
| |
| document.querySelectorAll('.metric-value').forEach(el => el.classList.remove('pending')); |
| } |
| |
| function resetMetrics() { |
| document.getElementById('metric-gini').textContent = '—'; |
| document.getElementById('metric-stddev').textContent = '—'; |
| document.getElementById('metric-avg').textContent = '—'; |
| document.getElementById('metric-assignments').textContent = '—'; |
| document.querySelectorAll('.metric-value').forEach(el => el.classList.add('pending')); |
| } |
| |
| async function runAllocation() { |
| const inputEl = document.getElementById('input-json'); |
| const outputEl = document.getElementById('output-json'); |
| const runBtn = document.getElementById('run-btn'); |
| |
| |
| let payload; |
| try { |
| payload = JSON.parse(inputEl.value); |
| } catch (e) { |
| showAlert('error', `Invalid JSON: ${e.message}`); |
| setStatus('error', 'JSON Parse Error'); |
| return; |
| } |
| |
| |
| runBtn.disabled = true; |
| setStatus('loading', 'Processing...'); |
| resetMetrics(); |
| outputEl.value = 'Sending request...'; |
| |
| try { |
| const startTime = performance.now(); |
| |
| |
| const response = await fetch('/api/v1/allocate/langgraph', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload) |
| }); |
| |
| const duration = ((performance.now() - startTime) / 1000).toFixed(2); |
| const data = await response.json(); |
| |
| outputEl.value = JSON.stringify(data, null, 2); |
| |
| if (response.ok) { |
| setStatus('success', `Completed in ${duration}s`); |
| updateMetrics(data); |
| |
| |
| const runId = data.allocation_run_id; |
| if (runId) { |
| const shortId = runId.substring(0, 8); |
| const supervisionUrl = `http://localhost:8090/?run_id=${encodeURIComponent(runId)}`; |
| |
| |
| const linkContainer = document.getElementById('supervision-link-container'); |
| const runIdDisplay = document.getElementById('run-id-display'); |
| const supervisionLink = document.getElementById('supervision-link'); |
| |
| runIdDisplay.textContent = shortId + '...'; |
| supervisionLink.href = supervisionUrl; |
| linkContainer.style.display = 'block'; |
| |
| |
| showAlert('success', `Allocation completed in ${duration}s`); |
| } else { |
| showAlert('success', `Allocation completed successfully in ${duration}s`); |
| } |
| } else { |
| setStatus('error', `Error ${response.status}`); |
| showAlert('error', `API Error: ${data.detail || data.message || 'Unknown error'}`); |
| } |
| } catch (e) { |
| setStatus('error', 'Connection Error'); |
| outputEl.value = JSON.stringify({ error: e.message }, null, 2); |
| showAlert('error', `Connection error: ${e.message}`); |
| } finally { |
| runBtn.disabled = false; |
| } |
| } |
| |
| function formatInput() { |
| const el = document.getElementById('input-json'); |
| try { |
| const parsed = JSON.parse(el.value); |
| el.value = JSON.stringify(parsed, null, 2); |
| } catch (e) { |
| showAlert('error', `Cannot format: ${e.message}`); |
| } |
| } |
| |
| function resetInput() { |
| document.getElementById('input-json').value = JSON.stringify(DEFAULT_REQUEST, null, 2); |
| document.getElementById('output-json').value = ''; |
| resetMetrics(); |
| setStatus('success', 'Ready'); |
| } |
| |
| function copyOutput() { |
| const el = document.getElementById('output-json'); |
| navigator.clipboard.writeText(el.value).then(() => { |
| showAlert('success', 'Output copied to clipboard'); |
| }); |
| } |
| |
| function copyCurl() { |
| const el = document.getElementById('curl-example'); |
| navigator.clipboard.writeText(el.textContent).then(() => { |
| showAlert('success', 'cURL command copied to clipboard'); |
| }); |
| } |
| </script> |
| </body> |
|
|
| </html> |