| |
| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <title>AgenticCore Chatbot Frontend</title> |
| <style> |
| :root { |
| --bg: #0b0d12; |
| --panel: #0f172a; |
| --panel-2: #111827; |
| --text: #e5e7eb; |
| --muted: #9ca3af; |
| --accent: #60a5fa; |
| --border: #1f2940; |
| --danger: #ef4444; |
| --success: #22c55e; |
| } |
| * { box-sizing: border-box; } |
| body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: var(--bg); color: var(--text); } |
| .wrap { max-width: 920px; margin: 32px auto; padding: 0 16px; } |
| header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 16px; } |
| header h1 { font-size: 18px; margin: 0; letter-spacing: .3px; } |
| header .badge { font-size: 12px; opacity: .85; padding: 4px 8px; border:1px solid var(--border); border-radius: 999px; background: rgba(255,255,255,0.03); } |
| .card { background: var(--panel); border: 1px solid var(--border); border-radius: 16px; padding: 16px; } |
| .row { display: flex; gap: 10px; align-items: center; } |
| .stack { display: grid; gap: 12px; } |
| label { font-size: 12px; color: var(--muted); } |
| input[type=text] { flex: 1; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--panel-2); color: var(--text); outline: none; } |
| input[type=text]::placeholder { color: #6b7280; } |
| button { padding: 10px 14px; border-radius: 12px; border: 1px solid var(--border); background: #1f2937; color: var(--text); cursor: pointer; transition: transform .02s ease, background .2s; } |
| button:hover { background: #273449; } |
| button:active { transform: translateY(1px); } |
| .btn-primary { background: #1f2937; border-color: #31405a; } |
| .btn-ghost { background: transparent; border-color: var(--border); } |
| .grid { display: grid; gap: 12px; } |
| .grid-2 { grid-template-columns: 1fr 1fr; } |
| .log { margin-top: 16px; display: grid; gap: 10px; } |
| .bubble { max-width: 80%; padding: 12px 14px; border-radius: 14px; line-height: 1.35; } |
| .user { background: #1e293b; border:1px solid #2b3b55; margin-left: auto; border-bottom-right-radius: 4px; } |
| .bot { background: #0d1b2a; border:1px solid #223049; margin-right: auto; border-bottom-left-radius: 4px; } |
| .meta { font-size: 12px; color: var(--muted); margin-top: 4px; } |
| pre { margin: 0; white-space: pre-wrap; word-break: break-word; } |
| .status { display:flex; align-items:center; gap:8px; font-size: 12px; color: var(--muted); } |
| .dot { width:8px; height:8px; border-radius:999px; background: #64748b; display:inline-block; } |
| .dot.ok { background: var(--success); } |
| .dot.bad { background: var(--danger); } |
| footer { margin: 24px 0; text-align:center; color: var(--muted); font-size: 12px; } |
| .small { font-size: 12px; } |
| @media (max-width: 700px) { .grid-2 { grid-template-columns: 1fr; } } |
| </style> |
| </head> |
| <body> |
| <div class="wrap"> |
| <header> |
| <h1>AgenticCore Chatbot Frontend</h1> |
| <div class="badge">Frontend → FastAPI → providers_unified</div> |
| </header> |
|
|
| <section class="card stack"> |
| <div class="grid grid-2"> |
| <div class="stack"> |
| <label for="backend">Backend URL</label> |
| <div class="row"> |
| <input id="backend" type="text" placeholder="http://127.0.0.1:8000" /> |
| <button id="save" class="btn-ghost">Save</button> |
| </div> |
| <div class="status" id="status"><span class="dot"></span><span>Not checked</span></div> |
| </div> |
| <div class="stack"> |
| <label for="message">Message</label> |
| <div class="row"> |
| <input id="message" type="text" placeholder="Type a message…" /> |
| <button id="send" class="btn-primary">Send</button> |
| </div> |
| <div class="row"> |
| <button id="cap" class="btn-ghost small">Capabilities</button> |
| <button id="health" class="btn-ghost small">Health</button> |
| <button id="clear" class="btn-ghost small">Clear</button> |
| </div> |
| </div> |
| </div> |
| <div class="log" id="log"></div> |
| </section> |
|
|
| <footer> |
| Use with your FastAPI backend at <code>/chatbot/message</code>. Configure CORS if you serve this file from a different origin. |
| </footer> |
| </div> |
|
|
| <script> |
| const $ = (sel) => document.querySelector(sel); |
| const backendInput = $('#backend'); |
| const sendBtn = $('#send'); |
| const saveBtn = $('#save'); |
| const msgInput = $('#message'); |
| const capBtn = $('#cap'); |
| const healthBtn = $('#health'); |
| const clearBtn = $('#clear'); |
| const log = $('#log'); |
| const status = $('#status'); |
| const dot = status.querySelector('.dot'); |
| const statusText = status.querySelector('span:last-child'); |
| |
| function getBackendUrl() { |
| return localStorage.getItem('BACKEND_URL') || 'http://127.0.0.1:8000'; |
| } |
| function setBackendUrl(v) { |
| localStorage.setItem('BACKEND_URL', v); |
| } |
| function cardUser(text) { |
| const div = document.createElement('div'); |
| div.className = 'bubble user'; |
| div.textContent = text; |
| log.appendChild(div); |
| log.scrollTop = log.scrollHeight; |
| } |
| function cardBot(obj) { |
| const wrap = document.createElement('div'); |
| wrap.className = 'bubble bot'; |
| const pre = document.createElement('pre'); |
| pre.textContent = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2); |
| wrap.appendChild(pre); |
| log.appendChild(wrap); |
| log.scrollTop = log.scrollHeight; |
| } |
| function setStatus(ok, text) { |
| dot.classList.toggle('ok', !!ok); |
| dot.classList.toggle('bad', ok === false); |
| statusText.textContent = text || (ok ? 'OK' : 'Error'); |
| } |
| async function api(path, init) { |
| const base = backendInput.value.trim().replace(/\/$/, ''); |
| const url = base + path; |
| const resp = await fetch(url, init); |
| if (!resp.ok) { |
| let t = await resp.text().catch(() => ''); |
| throw new Error(`HTTP ${resp.status} ${resp.statusText} — ${t}`); |
| } |
| const contentType = resp.headers.get('content-type') || ''; |
| if (contentType.includes('application/json')) return resp.json(); |
| return resp.text(); |
| } |
| |
| async function checkHealth() { |
| try { |
| const h = await api('/health', { method: 'GET' }); |
| setStatus(true, 'Healthy'); |
| cardBot({ health: h }); |
| } catch (e) { |
| setStatus(false, String(e.message || e)); |
| cardBot({ error: String(e.message || e) }); |
| } |
| } |
| |
| async function sendMessage() { |
| const text = msgInput.value.trim(); |
| if (!text) return; |
| cardUser(text); |
| msgInput.value = ''; |
| try { |
| const data = await api('/chatbot/message', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ message: text }) |
| }); |
| cardBot(data); |
| } catch (e) { |
| cardBot({ error: String(e.message || e) }); |
| } |
| } |
| |
| async function showCapabilities() { |
| try { |
| |
| const data = await api('/chatbot/message', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ message: 'help' }) |
| }); |
| cardBot(data); |
| } catch (e) { |
| cardBot({ capabilities: ['text-input','sentiment-analysis','help'], note: 'API help failed, showing defaults', error: String(e.message || e) }); |
| } |
| } |
| |
| |
| backendInput.value = getBackendUrl(); |
| saveBtn.onclick = () => { setBackendUrl(backendInput.value.trim()); setStatus(null, 'Saved'); }; |
| sendBtn.onclick = sendMessage; |
| msgInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') sendMessage(); }); |
| capBtn.onclick = showCapabilities; |
| healthBtn.onclick = checkHealth; |
| clearBtn.onclick = () => { log.innerHTML = ''; setStatus(null, 'Idle'); }; |
| |
| |
| checkHealth(); |
| </script> |
| </body> |
| </html> |
|
|