Spaces:
Sleeping
Sleeping
<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> | |
/* your existing styles here */ | |
: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> | |
<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"> | |
This version allows the agent to ask for your Patient ID (PID) during the conversation. | |
</p> | |
</div> | |
<script> | |
const NODE_CHAT_ENDPOINT = "/chat"; | |
const chatEl = document.getElementById("chat"); | |
const msgEl = document.getElementById("message"); | |
const sendBtn = document.getElementById("send"); | |
const stateView = document.getElementById("stateView"); | |
let chatHistory = JSON.parse(localStorage.getItem("chatHistory")) || []; | |
let patientState = JSON.parse(localStorage.getItem("patientState")) || {}; | |
sendBtn.addEventListener("click", onSend); | |
msgEl.addEventListener("keydown", (e) => { | |
if (e.key === "Enter") onSend(); | |
}); | |
function saveState() { | |
localStorage.setItem("chatHistory", JSON.stringify(chatHistory)); | |
localStorage.setItem("patientState", JSON.stringify(patientState)); | |
} | |
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 message = (msgEl.value || "").trim(); | |
if (!message) return; | |
// Clear the input and disable the button while we wait for the response | |
msgEl.value = ""; | |
sendBtn.disabled = true; | |
// Add the user's message to the chat history | |
chatHistory.push({ role: "user", content: message }); | |
renderChat(); | |
saveState(); | |
try { | |
// Send the chat history and patient state to the backend | |
const res = await fetch(NODE_CHAT_ENDPOINT, { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify({ 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; | |
// Add the assistant's reply to the chat history | |
chatHistory.push({ role: "assistant", content: reply }); | |
renderChat(); | |
saveState(); | |
// Display the raw agent state for debugging | |
stateView.textContent = JSON.stringify(patientState, null, 2); | |
} catch (err) { | |
// Handle any errors and display them in the chat | |
chatHistory.push({ role: "assistant", content: `Error: ${err.message}` }); | |
renderChat(); | |
saveState(); | |
} finally { | |
sendBtn.disabled = false; | |
} | |
} | |
// Initial greeting | |
if (chatHistory.length === 0) { | |
chatHistory.push({ role: "assistant", content: "Hello! I am your patient assistant. Please tell me your Patient ID to get started." }); | |
saveState(); | |
} | |
renderChat(); | |
</script> | |
</body> | |
</html> | |