|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8" /> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
|
<title>Patient Assistant Chat</title> |
|
|
<style> |
|
|
|
|
|
:root { --bg:#0f172a; --panel:#111827; --soft:#1f2937; --text:#e5e7eb; --muted:#9ca3af; --accent:#22c55e; } |
|
|
* { box-sizing: border-box; } |
|
|
body { margin:0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); } |
|
|
.app { max-width: 900px; margin: 0 auto; padding: 20px; } |
|
|
.card { background: var(--panel); border: 1px solid #242b3a; border-radius: 16px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,.25); } |
|
|
.header { padding: 16px 20px; border-bottom: 1px solid #242b3a; display: flex; align-items: center; gap: 12px; } |
|
|
.badge { background: #0b3b26; color: #8bf2c0; padding: 6px 10px; border-radius: 999px; font-weight: 600; font-size: 12px; letter-spacing: .3px; } |
|
|
.row { display:flex; gap:12px; align-items:center; flex-wrap: wrap; } |
|
|
label { font-size: 13px; color: var(--muted); } |
|
|
input[type="text"] { background: var(--soft); color: var(--text); border: 1px solid #2b3346; padding: 10px 12px; border-radius: 10px; min-width: 200px; outline: none; } |
|
|
input[type="text"]:focus { border-color: #374462; box-shadow: 0 0 0 3px rgba(59,130,246,.15); } |
|
|
button { background: var(--accent); color: #052e19; border: none; padding: 10px 14px; border-radius: 10px; font-weight: 700; cursor: pointer; } |
|
|
button.secondary { background: #334155; color: #dbeafe; } |
|
|
button:disabled { opacity: .6; cursor: not-allowed; } |
|
|
|
|
|
.chat { height: 420px; overflow-y: auto; padding: 18px; display: flex; flex-direction: column; gap: 10px; background: #0b1220; } |
|
|
.msg { max-width: 80%; padding: 10px 12px; border-radius: 12px; border: 1px solid #1f2937; white-space: pre-wrap; word-break: break-word; } |
|
|
.user { align-self: flex-end; background: #0b3b26; border-color: #14532d; } |
|
|
.assist { align-self: flex-start; background: #111827; border-color: #1f2937; } |
|
|
|
|
|
.composer { display: grid; grid-template-columns: 1fr auto; gap: 10px; padding: 14px; border-top: 1px solid #242b3a; background: #0b1220; } |
|
|
.composer input { width: 100%; } |
|
|
|
|
|
.state { padding: 12px 16px; border-top: 1px dashed #243044; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; color: #cbd5e1; background: #0b1220; } |
|
|
details > summary { cursor: pointer; } |
|
|
.small { font-size: 12px; color: var(--muted); } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="app"> |
|
|
<div class="card"> |
|
|
<div class="header"> |
|
|
<span class="badge">Patient Assistant</span> |
|
|
<div class="row" style="gap:8px;"> |
|
|
<label for="pid">Patient ID</label> |
|
|
<input id="pid" type="text" placeholder="e.g. 1234" /> |
|
|
<button id="savePid">Save</button> |
|
|
<span id="status" class="small">Disconnected</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="chat" class="chat"></div> |
|
|
|
|
|
<div class="composer"> |
|
|
<input id="message" type="text" placeholder="Type your message…" /> |
|
|
<button id="send">Send</button> |
|
|
</div> |
|
|
|
|
|
<div class="state"> |
|
|
<details> |
|
|
<summary>Agent State (from backend)</summary> |
|
|
<pre id="stateView">{}</pre> |
|
|
</details> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<p class="small" style="margin-top:10px; opacity:.8"> |
|
|
Configure your Node API URL in the script below (defaults to <code>/chat</code> on the same origin). |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const NODE_CHAT_ENDPOINT = "/chat"; |
|
|
|
|
|
const chatEl = document.getElementById("chat"); |
|
|
const pidEl = document.getElementById("pid"); |
|
|
const savePidBtn = document.getElementById("savePid"); |
|
|
const msgEl = document.getElementById("message"); |
|
|
const sendBtn = document.getElementById("send"); |
|
|
const statusEl = document.getElementById("status"); |
|
|
const stateView = document.getElementById("stateView"); |
|
|
|
|
|
let chatHistory = []; |
|
|
let patientState = {}; |
|
|
|
|
|
|
|
|
pidEl.value = localStorage.getItem("patient_id") || ""; |
|
|
updateStatus(); |
|
|
|
|
|
savePidBtn.addEventListener("click", () => { |
|
|
localStorage.setItem("patient_id", pidEl.value.trim()); |
|
|
updateStatus(); |
|
|
msgEl.focus(); |
|
|
}); |
|
|
|
|
|
sendBtn.addEventListener("click", onSend); |
|
|
msgEl.addEventListener("keydown", (e) => { if (e.key === "Enter") onSend(); }); |
|
|
|
|
|
function updateStatus() { |
|
|
const pid = (pidEl.value || "").trim(); |
|
|
statusEl.textContent = pid ? `Patient ${pid}` : "No patient selected"; |
|
|
} |
|
|
|
|
|
function renderChat() { |
|
|
chatEl.innerHTML = ""; |
|
|
for (const m of chatHistory) { |
|
|
const div = document.createElement("div"); |
|
|
div.className = `msg ${m.role === "user" ? "user" : "assist"}`; |
|
|
div.textContent = `${m.role === "user" ? "You" : "Assistant"}: ${m.content}`; |
|
|
chatEl.appendChild(div); |
|
|
} |
|
|
chatEl.scrollTop = chatEl.scrollHeight; |
|
|
} |
|
|
|
|
|
async function onSend() { |
|
|
const patient_id = (pidEl.value || "").trim(); |
|
|
const message = (msgEl.value || "").trim(); |
|
|
if (!patient_id) { alert("Please enter Patient ID"); return; } |
|
|
if (!message) return; |
|
|
|
|
|
chatHistory.push({ role: "user", content: message }); |
|
|
renderChat(); |
|
|
msgEl.value = ""; |
|
|
sendBtn.disabled = true; |
|
|
|
|
|
try { |
|
|
const res = await fetch(NODE_CHAT_ENDPOINT, { |
|
|
method: "POST", |
|
|
headers: { "Content-Type": "application/json" }, |
|
|
body: JSON.stringify({ patient_id, chat_history: chatHistory, patient_state: patientState }) |
|
|
}); |
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`); |
|
|
const data = await res.json(); |
|
|
|
|
|
const reply = data.assistant_reply || "(no reply)"; |
|
|
patientState = data.updated_state || patientState; |
|
|
|
|
|
chatHistory.push({ role: "assistant", content: reply }); |
|
|
renderChat(); |
|
|
|
|
|
stateView.textContent = JSON.stringify(patientState, null, 2); |
|
|
} catch (err) { |
|
|
chatHistory.push({ role: "assistant", content: `Error: ${err.message}` }); |
|
|
renderChat(); |
|
|
} finally { |
|
|
sendBtn.disabled = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
chatHistory.push({ role: "assistant", content: "Hello! I am your patient assistant. Set your Patient ID and ask your question." }); |
|
|
renderChat(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|