Spaces:
Sleeping
Sleeping
| <!-- created by ChiragGOAT --> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>MindBot | Psychology Companion</title> | |
| <style> | |
| @import url("https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=Manrope:wght@400;500;600;700&family=Playfair+Display:wght@700;800&display=swap"); | |
| :root { | |
| --bg-ivory: #f5f0e4; | |
| --bg-mint: #d6e7d6; | |
| --bg-sand: #f1d6b8; | |
| --ink-main: #1d2a22; | |
| --ink-soft: #4e6257; | |
| --card: #fffdf7; | |
| --line: #d8d4c8; | |
| --accent: #2d7865; | |
| --accent-deep: #1f5c4f; | |
| --accent-warm: #c86c34; | |
| --bubble-user: #2d7865; | |
| --bubble-bot: #edf4ef; | |
| --bubble-system: #efe8da; | |
| --shadow: 0 16px 40px rgba(41, 51, 45, 0.13); | |
| --radius-xl: 24px; | |
| --radius-lg: 16px; | |
| --radius-md: 12px; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| min-height: 100vh; | |
| background: | |
| radial-gradient(circle at 12% 20%, rgba(200, 108, 52, 0.16), transparent 42%), | |
| radial-gradient(circle at 86% 80%, rgba(45, 120, 101, 0.17), transparent 42%), | |
| linear-gradient(140deg, var(--bg-ivory), var(--bg-mint) 58%, var(--bg-sand)); | |
| color: var(--ink-main); | |
| font-family: "Manrope", "Segoe UI", sans-serif; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 28px; | |
| overflow-x: hidden; | |
| overflow-y: auto; | |
| } | |
| .blob { | |
| position: fixed; | |
| border-radius: 999px; | |
| filter: blur(8px); | |
| pointer-events: none; | |
| opacity: 0.5; | |
| animation: drift 13s ease-in-out infinite; | |
| z-index: 0; | |
| } | |
| .blob.one { | |
| width: 260px; | |
| height: 260px; | |
| top: -70px; | |
| right: -40px; | |
| background: rgba(200, 108, 52, 0.33); | |
| } | |
| .blob.two { | |
| width: 280px; | |
| height: 280px; | |
| left: -80px; | |
| bottom: -90px; | |
| background: rgba(45, 120, 101, 0.3); | |
| animation-delay: -4s; | |
| } | |
| .shell { | |
| width: min(1080px, 100%); | |
| margin: auto; | |
| display: grid; | |
| grid-template-columns: 0.95fr 1.05fr; | |
| gap: 20px; | |
| position: relative; | |
| z-index: 1; | |
| animation: rise 700ms ease; | |
| } | |
| .card { | |
| border: 1px solid rgba(255, 255, 255, 0.72); | |
| border-radius: var(--radius-xl); | |
| background: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(255, 253, 247, 0.95)); | |
| box-shadow: var(--shadow); | |
| backdrop-filter: blur(8px); | |
| } | |
| .intro { | |
| padding: 28px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 18px; | |
| justify-content: space-between; | |
| } | |
| .kicker { | |
| width: fit-content; | |
| font-size: 12px; | |
| letter-spacing: 0.18em; | |
| text-transform: uppercase; | |
| font-weight: 700; | |
| color: var(--accent-deep); | |
| background: rgba(45, 120, 101, 0.14); | |
| border-radius: 999px; | |
| padding: 6px 12px; | |
| } | |
| h1 { | |
| font-family: "Fraunces", Georgia, serif; | |
| font-size: clamp(30px, 3.5vw, 44px); | |
| line-height: 1.08; | |
| font-weight: 600; | |
| margin-top: 6px; | |
| } | |
| .lead { | |
| color: var(--ink-soft); | |
| font-size: 15px; | |
| line-height: 1.7; | |
| max-width: 44ch; | |
| } | |
| .chips { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| margin-top: 2px; | |
| } | |
| .chip { | |
| border: 1px solid var(--line); | |
| background: rgba(255, 255, 255, 0.8); | |
| color: var(--ink-main); | |
| border-radius: 999px; | |
| padding: 10px 14px; | |
| font-size: 13px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: 180ms ease; | |
| } | |
| .chip:hover:not(:disabled) { | |
| border-color: var(--accent); | |
| transform: translateY(-1px); | |
| } | |
| .chip:disabled { | |
| opacity: 0.55; | |
| cursor: not-allowed; | |
| } | |
| .disclaimer { | |
| margin-top: 4px; | |
| padding: 12px 14px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid #efd3bf; | |
| background: #fff6ef; | |
| color: #7e4f29; | |
| font-size: 12px; | |
| line-height: 1.5; | |
| } | |
| .chat { | |
| padding: 18px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| min-height: 650px; | |
| } | |
| .chat-box { | |
| flex: 1; | |
| border-radius: 18px; | |
| border: 1px solid var(--line); | |
| background: rgba(255, 255, 255, 0.74); | |
| padding: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| overflow-y: auto; | |
| scroll-behavior: smooth; | |
| } | |
| .msg { | |
| max-width: 90%; | |
| padding: 11px 14px; | |
| border-radius: 14px; | |
| line-height: 1.6; | |
| font-size: 14px; | |
| white-space: pre-wrap; | |
| animation: pop 220ms ease; | |
| } | |
| .msg.user { | |
| align-self: flex-end; | |
| background: var(--bubble-user); | |
| color: #ffffff; | |
| border-bottom-right-radius: 6px; | |
| } | |
| .msg.bot { | |
| align-self: flex-start; | |
| background: var(--bubble-bot); | |
| color: var(--ink-main); | |
| border: 1px solid #cfe0d2; | |
| border-bottom-left-radius: 6px; | |
| } | |
| .msg.system { | |
| align-self: center; | |
| background: var(--bubble-system); | |
| color: var(--ink-soft); | |
| font-size: 12px; | |
| border: 1px solid #e3d9c9; | |
| text-align: center; | |
| } | |
| .sources { | |
| align-self: flex-start; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| margin-top: -2px; | |
| max-width: 100%; | |
| } | |
| .source-tag { | |
| font-size: 11px; | |
| font-weight: 700; | |
| letter-spacing: 0.01em; | |
| color: var(--accent-deep); | |
| border: 1px dashed #8ab5a7; | |
| border-radius: 999px; | |
| background: #eef8f4; | |
| padding: 5px 10px; | |
| } | |
| .composer { | |
| border: 1px solid var(--line); | |
| border-radius: var(--radius-lg); | |
| background: rgba(255, 255, 255, 0.82); | |
| padding: 12px; | |
| display: grid; | |
| gap: 10px; | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 48px; | |
| max-height: 140px; | |
| border: none; | |
| outline: none; | |
| resize: none; | |
| background: transparent; | |
| color: var(--ink-main); | |
| font-family: "Manrope", "Segoe UI", sans-serif; | |
| font-size: 15px; | |
| line-height: 1.5; | |
| } | |
| textarea::placeholder { | |
| color: #7d8e84; | |
| } | |
| .actions { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 10px; | |
| align-items: center; | |
| } | |
| .btn { | |
| border: none; | |
| border-radius: 999px; | |
| padding: 10px 16px; | |
| font-size: 13px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: transform 160ms ease, opacity 160ms ease; | |
| } | |
| .btn:hover:not(:disabled) { | |
| transform: translateY(-1px); | |
| } | |
| .btn.primary { | |
| background: var(--accent); | |
| color: #ffffff; | |
| } | |
| .btn.secondary { | |
| background: #f3ede2; | |
| color: #66452f; | |
| border: 1px solid #e0c8b0; | |
| } | |
| .btn:disabled { | |
| opacity: 0.55; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| #status { | |
| min-height: 20px; | |
| font-size: 12px; | |
| color: var(--ink-soft); | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding-left: 4px; | |
| } | |
| #status.thinking::before { | |
| content: ""; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 999px; | |
| background: var(--accent-warm); | |
| animation: pulse 900ms infinite; | |
| } | |
| @keyframes pop { | |
| from { | |
| opacity: 0; | |
| transform: translateY(6px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes rise { | |
| from { | |
| opacity: 0; | |
| transform: translateY(12px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(0.8); opacity: 0.5; } | |
| 70% { transform: scale(1.1); opacity: 1; } | |
| 100% { transform: scale(0.8); opacity: 0.5; } | |
| } | |
| @keyframes drift { | |
| 0%, 100% { transform: translateY(0px); } | |
| 50% { transform: translateY(18px); } | |
| } | |
| @media (max-width: 940px) { | |
| body { | |
| padding: 24px; | |
| } | |
| .shell { | |
| grid-template-columns: 1fr; | |
| gap: 24px; | |
| } | |
| .chat { | |
| min-height: 540px; | |
| } | |
| } | |
| @media (max-width: 520px) { | |
| body { | |
| padding: 14px; | |
| } | |
| .shell { | |
| gap: 16px; | |
| } | |
| .intro, | |
| .chat { | |
| padding: 18px; | |
| } | |
| h1 { | |
| font-size: 30px; | |
| } | |
| .msg { | |
| max-width: 100%; | |
| } | |
| .actions { | |
| flex-wrap: wrap; | |
| } | |
| .btn { | |
| flex: 1; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="blob one"></div> | |
| <div class="blob two"></div> | |
| <main class="shell"> | |
| <section class="intro card"> | |
| <div> | |
| <p class="kicker">Mind Psychology Bot</p> | |
| <h1>Understand your mind with calm, grounded conversations.</h1> | |
| <p class="lead"> | |
| Ask about thoughts, habits, behavior, emotions, and relationships. | |
| MindBot answers using psychology book knowledge from your local RAG pipeline. | |
| </p> | |
| </div> | |
| <div class="chips"> | |
| <button class="chip" type="button" data-prompt="Why do I overthink small decisions?">Overthinking</button> | |
| <button class="chip" type="button" data-prompt="How can I build better daily habits?">Habit building</button> | |
| <button class="chip" type="button" data-prompt="Why do I procrastinate even when I care?">Procrastination</button> | |
| <button class="chip" type="button" data-prompt="How can I regulate stress in difficult weeks?">Stress regulation</button> | |
| </div> | |
| <p class="disclaimer"> | |
| MindBot provides educational guidance from books, not medical diagnosis or emergency care. | |
| </p> | |
| </section> | |
| <section class="chat card"> | |
| <div class="chat-box" id="chatBox" aria-live="polite"> | |
| <div class="msg system">Ask your first question to begin.</div> | |
| </div> | |
| <div class="composer"> | |
| <textarea id="questionInput" placeholder="What pattern in my thinking should I work on first?"></textarea> | |
| <div class="actions"> | |
| <button class="btn secondary" id="resetBtn" type="button">New Session</button> | |
| <button class="btn primary" id="askBtn" type="button">Send</button> | |
| </div> | |
| </div> | |
| <div id="status">Ready</div> | |
| </section> | |
| </main> | |
| <script> | |
| const chatBox = document.getElementById("chatBox"); | |
| const input = document.getElementById("questionInput"); | |
| const askBtn = document.getElementById("askBtn"); | |
| const resetBtn = document.getElementById("resetBtn"); | |
| const statusEl = document.getElementById("status"); | |
| const chips = document.querySelectorAll(".chip"); | |
| let busy = false; | |
| function setStatus(message, thinking) { | |
| statusEl.textContent = message; | |
| statusEl.classList.toggle("thinking", Boolean(thinking)); | |
| } | |
| function setBusy(isBusy) { | |
| busy = isBusy; | |
| askBtn.disabled = isBusy; | |
| resetBtn.disabled = isBusy; | |
| input.disabled = isBusy; | |
| chips.forEach((chip) => { | |
| chip.disabled = isBusy; | |
| }); | |
| } | |
| function addMessage(text, type) { | |
| const bubble = document.createElement("div"); | |
| bubble.className = "msg " + type; | |
| bubble.textContent = text; | |
| chatBox.appendChild(bubble); | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| } | |
| function addSources(sources) { | |
| if (!Array.isArray(sources) || sources.length === 0) { | |
| return; | |
| } | |
| const container = document.createElement("div"); | |
| container.className = "sources"; | |
| sources.forEach((source) => { | |
| const tag = document.createElement("span"); | |
| tag.className = "source-tag"; | |
| tag.textContent = source; | |
| container.appendChild(tag); | |
| }); | |
| chatBox.appendChild(container); | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| } | |
| function resizeInput() { | |
| input.style.height = "auto"; | |
| input.style.height = Math.min(input.scrollHeight, 140) + "px"; | |
| } | |
| async function askBackend(question) { | |
| const response = await fetch("/ask", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify({ question: question }) | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(data.detail || "Request failed"); | |
| } | |
| return data; | |
| } | |
| async function sendMessage(prefilledText) { | |
| const text = (prefilledText || input.value).trim(); | |
| if (!text || busy) { | |
| return; | |
| } | |
| addMessage(text, "user"); | |
| input.value = ""; | |
| resizeInput(); | |
| setBusy(true); | |
| setStatus("MindBot is reflecting...", true); | |
| try { | |
| const data = await askBackend(text); | |
| addMessage(data.answer || "I could not generate a response.", "bot"); | |
| addSources(data.sources || []); | |
| setStatus("Ready", false); | |
| } catch (error) { | |
| addMessage("I hit an error: " + error.message, "system"); | |
| setStatus("Could not reach backend", false); | |
| } | |
| setBusy(false); | |
| input.focus(); | |
| } | |
| async function resetConversation() { | |
| if (busy) { | |
| return; | |
| } | |
| setBusy(true); | |
| setStatus("Resetting session...", true); | |
| try { | |
| await askBackend("/reset"); | |
| chatBox.innerHTML = ""; | |
| addMessage("New session started. Ask your first question.", "system"); | |
| setStatus("Session reset complete", false); | |
| } catch (error) { | |
| addMessage("Could not reset session: " + error.message, "system"); | |
| setStatus("Reset failed", false); | |
| } | |
| setBusy(false); | |
| input.focus(); | |
| } | |
| askBtn.addEventListener("click", function () { | |
| sendMessage(); | |
| }); | |
| resetBtn.addEventListener("click", function () { | |
| resetConversation(); | |
| }); | |
| chips.forEach((chip) => { | |
| chip.addEventListener("click", function () { | |
| sendMessage(chip.dataset.prompt || ""); | |
| }); | |
| }); | |
| input.addEventListener("input", resizeInput); | |
| input.addEventListener("keydown", function (event) { | |
| if (event.key === "Enter" && !event.shiftKey) { | |
| event.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| resizeInput(); | |
| </script> | |
| </body> | |
| </html> |