| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>Sacred Texts β Divine Knowledge</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| <link |
| href="https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=IM+Fell+English:ital@0;1&display=swap" |
| rel="stylesheet" /> |
|
|
| <style> |
| |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| |
| :root { |
| --bg: #0d0b07; |
| --surface: #16130d; |
| --surface-2: #1e1a11; |
| --border: #3a2e1a; |
| --gold: #c9993a; |
| --gold-light: #e8c170; |
| --gold-pale: #f5e4b0; |
| --cream: #f0e6cc; |
| --muted: #7a6a4a; |
| --gita: #e07b3b; |
| --quran: #3bba85; |
| --bible: #5b8ce0; |
| --granth: #b07ce0; |
| --danger: #e06060; |
| } |
| |
| html, body { |
| height: 100%; |
| background: var(--bg); |
| color: var(--cream); |
| font-family: 'Cormorant Garamond', Georgia, serif; |
| font-size: 18px; |
| line-height: 1.7; |
| overflow: hidden; |
| } |
| |
| body::before { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| background: |
| radial-gradient(ellipse 80% 60% at 20% 10%, rgba(201,153,58,.07) 0%, transparent 60%), |
| radial-gradient(ellipse 60% 80% at 80% 90%, rgba(91,140,224,.05) 0%, transparent 60%), |
| radial-gradient(ellipse 50% 50% at 50% 50%, rgba(176,124,224,.04) 0%, transparent 60%), |
| url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='400' height='400' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"); |
| pointer-events: none; |
| z-index: 0; |
| } |
| |
| |
| .app { |
| position: relative; |
| z-index: 1; |
| display: grid; |
| grid-template-rows: auto 1fr auto; |
| height: 100vh; |
| max-width: 860px; |
| margin: 0 auto; |
| padding: 0 16px; |
| } |
| |
| |
| header { |
| padding: 20px 0 14px; |
| text-align: center; |
| border-bottom: 1px solid var(--border); |
| position: relative; |
| } |
| |
| .mandala { |
| font-size: 1.8rem; |
| letter-spacing: .5rem; |
| color: var(--gold); |
| opacity: .6; |
| margin-bottom: 6px; |
| animation: spin 60s linear infinite; |
| display: inline-block; |
| } |
| @keyframes spin { to { transform: rotate(360deg); } } |
| |
| h1 { |
| font-family: 'Cinzel Decorative', serif; |
| font-size: clamp(1.1rem, 3vw, 1.7rem); |
| font-weight: 400; |
| color: var(--gold-pale); |
| letter-spacing: .12em; |
| text-shadow: 0 0 40px rgba(201,153,58,.3); |
| } |
| |
| .subtitle { |
| font-family: 'IM Fell English', serif; |
| font-style: italic; |
| font-size: .9rem; |
| color: var(--muted); |
| margin-top: 3px; |
| } |
| |
| .badges { |
| display: flex; |
| justify-content: center; |
| gap: 10px; |
| margin-top: 10px; |
| flex-wrap: wrap; |
| } |
| |
| .badge { |
| font-size: .7rem; |
| letter-spacing: .1em; |
| text-transform: uppercase; |
| padding: 2px 9px; |
| border-radius: 20px; |
| border: 1px solid; |
| font-family: 'Cormorant Garamond', serif; |
| font-weight: 600; |
| } |
| .badge-gita { color: var(--gita); border-color: var(--gita); background: rgba(224,123,59,.1); } |
| .badge-quran { color: var(--quran); border-color: var(--quran); background: rgba(59,186,133,.1); } |
| .badge-bible { color: var(--bible); border-color: var(--bible); background: rgba(91,140,224,.1); } |
| .badge-granth { color: var(--granth); border-color: var(--granth); background: rgba(176,124,224,.1); } |
| |
| |
| .session-bar { |
| display: none; |
| align-items: center; |
| justify-content: space-between; |
| gap: 8px; |
| margin-top: 10px; |
| padding: 5px 10px; |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| background: var(--surface); |
| font-size: .75rem; |
| color: var(--muted); |
| } |
| |
| .session-bar.visible { display: flex; } |
| |
| .session-turn-count { |
| font-family: 'Cormorant Garamond', serif; |
| font-style: italic; |
| } |
| |
| .session-turn-count span { |
| color: var(--gold-light); |
| font-weight: 600; |
| } |
| |
| .new-convo-btn { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| background: none; |
| border: 1px solid var(--border); |
| color: var(--muted); |
| padding: 3px 10px; |
| border-radius: 6px; |
| font-family: 'Cormorant Garamond', serif; |
| font-size: .75rem; |
| cursor: pointer; |
| transition: all .2s; |
| } |
| .new-convo-btn:hover { |
| border-color: var(--danger); |
| color: var(--danger); |
| } |
| |
| |
| .chat-window { |
| overflow-y: auto; |
| padding: 24px 0; |
| display: flex; |
| flex-direction: column; |
| gap: 24px; |
| scrollbar-width: thin; |
| scrollbar-color: var(--border) transparent; |
| } |
| .chat-window::-webkit-scrollbar { width: 4px; } |
| .chat-window::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } |
| |
| |
| .welcome { |
| text-align: center; |
| margin: auto; |
| padding: 20px; |
| max-width: 500px; |
| } |
| .welcome-icon { font-size: 3.2rem; margin-bottom: 14px; filter: drop-shadow(0 0 20px rgba(201,153,58,.4)); } |
| .welcome h2 { |
| font-family: 'IM Fell English', serif; |
| font-style: italic; |
| font-size: 1.4rem; |
| color: var(--gold-light); |
| margin-bottom: 8px; |
| } |
| .welcome p { font-size: .92rem; color: var(--muted); line-height: 1.8; } |
| |
| .suggested-queries { margin-top: 20px; display: flex; flex-direction: column; gap: 7px; } |
| .suggested-queries button { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| color: var(--cream); |
| padding: 9px 14px; |
| border-radius: 8px; |
| font-family: 'Cormorant Garamond', serif; |
| font-size: .92rem; |
| font-style: italic; |
| cursor: pointer; |
| transition: all .2s; |
| text-align: left; |
| } |
| .suggested-queries button:hover { border-color: var(--gold); color: var(--gold-pale); background: var(--surface-2); } |
| |
| |
| .message { |
| display: flex; |
| flex-direction: column; |
| gap: 6px; |
| animation: fadeUp .4s ease both; |
| } |
| @keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } |
| |
| .message-user { align-items: flex-end; } |
| .message-assistant { align-items: flex-start; } |
| |
| .msg-label { |
| font-size: .68rem; |
| letter-spacing: .15em; |
| text-transform: uppercase; |
| color: var(--muted); |
| font-weight: 600; |
| padding: 0 4px; |
| } |
| |
| .msg-bubble { |
| max-width: 92%; |
| padding: 14px 18px; |
| border-radius: 12px; |
| line-height: 1.75; |
| } |
| |
| .message-user .msg-bubble { |
| background: var(--surface-2); |
| border: 1px solid var(--border); |
| color: var(--cream); |
| font-style: italic; |
| font-size: .97rem; |
| border-bottom-right-radius: 4px; |
| } |
| |
| .message-assistant .msg-bubble { |
| background: linear-gradient(135deg, var(--surface) 0%, rgba(30,26,17,.95) 100%); |
| border: 1px solid rgba(201,153,58,.2); |
| color: var(--cream); |
| font-size: .97rem; |
| border-bottom-left-radius: 4px; |
| box-shadow: 0 4px 24px rgba(0,0,0,.4), inset 0 1px 0 rgba(201,153,58,.1); |
| } |
| |
| .msg-bubble p { margin-bottom: 1em; } |
| .msg-bubble p:last-child { margin-bottom: 0; } |
| .msg-bubble strong { color: var(--gold-light); font-weight: 600; } |
| |
| |
| .followup-pill { |
| font-size: .68rem; |
| padding: 2px 8px; |
| border-radius: 10px; |
| background: rgba(201,153,58,.08); |
| border: 1px solid rgba(201,153,58,.2); |
| color: var(--muted); |
| margin-left: 6px; |
| font-style: italic; |
| vertical-align: middle; |
| } |
| |
| |
| .sources { max-width: 92%; margin-top: 4px; } |
| .sources-label { |
| font-size: .7rem; |
| letter-spacing: .12em; |
| text-transform: uppercase; |
| color: var(--muted); |
| margin-bottom: 6px; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| .sources-label::before, .sources-label::after { content: ''; flex: 1; height: 1px; background: var(--border); } |
| .sources-label::before { max-width: 20px; } |
| |
| .source-tags { display: flex; flex-wrap: wrap; gap: 6px; } |
| .source-tag { |
| font-size: .76rem; |
| padding: 4px 10px; |
| border-radius: 6px; |
| border: 1px solid; |
| font-family: 'Cormorant Garamond', serif; |
| cursor: default; |
| transition: all .2s; |
| } |
| .source-tag:hover { transform: translateY(-1px); filter: brightness(1.2); } |
| |
| .source-gita { color: var(--gita); border-color: rgba(224,123,59,.4); background: rgba(224,123,59,.08); } |
| .source-quran { color: var(--quran); border-color: rgba(59,186,133,.4); background: rgba(59,186,133,.08); } |
| .source-bible { color: var(--bible); border-color: rgba(91,140,224,.4); background: rgba(91,140,224,.08); } |
| .source-granth { color: var(--granth); border-color: rgba(176,124,224,.4); background: rgba(176,124,224,.08); } |
| .source-other { color: var(--gold-light); border-color: rgba(201,153,58,.4); background: rgba(201,153,58,.08); } |
| |
| |
| .loading { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 12px 16px; |
| border: 1px solid rgba(201,153,58,.15); |
| border-radius: 12px; |
| background: var(--surface); |
| width: fit-content; |
| max-width: 280px; |
| } |
| .loading-dots { display: flex; gap: 5px; } |
| .loading-dots span { |
| width: 6px; height: 6px; |
| border-radius: 50%; |
| background: var(--gold); |
| animation: dot-pulse 1.4s ease-in-out infinite; |
| } |
| .loading-dots span:nth-child(2) { animation-delay: .2s; } |
| .loading-dots span:nth-child(3) { animation-delay: .4s; } |
| @keyframes dot-pulse { |
| 0%,80%,100% { opacity: .2; transform: scale(.8); } |
| 40% { opacity: 1; transform: scale(1.1); } |
| } |
| .loading-text { font-size: .82rem; font-style: italic; color: var(--muted); } |
| |
| |
| .thinking-dots { display: inline-flex; gap: 4px; margin-left: 4px; } |
| .thinking-dots span { |
| width: 4px; height: 4px; |
| background: var(--gold); |
| border-radius: 50%; |
| animation: bounce 1.4s infinite ease-in-out; |
| } |
| @keyframes bounce { 0%,80%,100% { transform: scale(0); } 40% { transform: scale(1); } } |
| |
| |
| .error-bubble { |
| background: rgba(180,60,60,.1); |
| border: 1px solid rgba(180,60,60,.3); |
| color: #e08080; |
| padding: 12px 16px; |
| border-radius: 10px; |
| font-size: .9rem; |
| max-width: 92%; |
| } |
| |
| |
| .input-area { padding: 14px 0 22px; border-top: 1px solid var(--border); } |
| .input-row { display: flex; gap: 10px; align-items: flex-end; } |
| |
| textarea { |
| flex: 1; |
| background: var(--surface); |
| border: 1px solid var(--border); |
| color: var(--cream); |
| padding: 13px 15px; |
| border-radius: 12px; |
| font-family: 'Cormorant Garamond', serif; |
| font-size: .97rem; |
| line-height: 1.6; |
| resize: none; |
| min-height: 50px; |
| max-height: 130px; |
| outline: none; |
| transition: border-color .2s, box-shadow .2s; |
| } |
| textarea::placeholder { color: var(--muted); font-style: italic; } |
| textarea:focus { |
| border-color: rgba(201,153,58,.5); |
| box-shadow: 0 0 0 3px rgba(201,153,58,.08); |
| } |
| |
| .send-btn { |
| width: 50px; height: 50px; |
| border-radius: 12px; |
| border: 1px solid rgba(201,153,58,.4); |
| background: linear-gradient(135deg, rgba(201,153,58,.2), rgba(201,153,58,.05)); |
| color: var(--gold); |
| font-size: 1.25rem; |
| cursor: pointer; |
| transition: all .2s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| } |
| .send-btn:hover:not(:disabled) { |
| background: linear-gradient(135deg, rgba(201,153,58,.35), rgba(201,153,58,.15)); |
| border-color: var(--gold); |
| transform: translateY(-1px); |
| box-shadow: 0 4px 16px rgba(201,153,58,.2); |
| } |
| .send-btn:disabled { opacity: .3; cursor: not-allowed; transform: none; } |
| |
| .input-hint { font-size: .7rem; color: var(--muted); margin-top: 7px; text-align: center; font-style: italic; } |
| </style> |
| </head> |
|
|
| <body> |
| <div class="app"> |
|
|
| |
| <header> |
| <div class="mandala">β¦</div> |
| <h1>Life Guide</h1> |
| <p class="subtitle">Wisdom from the Bhagavad Gita, Quran, Bible & Guru Granth Sahib</p> |
| <div class="badges"> |
| <span class="badge badge-gita">Bhagavad Gita</span> |
| <span class="badge badge-quran">Quran</span> |
| <span class="badge badge-bible">Bible</span> |
| <span class="badge badge-granth">Guru Granth Sahib</span> |
| </div> |
|
|
| |
| <div class="session-bar" id="sessionBar"> |
| <span class="session-turn-count" id="turnCountLabel"> |
| Turn <span id="turnCount">0</span> |
| </span> |
| <button class="new-convo-btn" onclick="startNewConversation()" title="Clear history and start fresh"> |
| βΊ New Conversation |
| </button> |
| </div> |
| </header> |
|
|
| |
| <div class="chat-window" id="chatWindow"> |
| <div class="welcome" id="welcomePane"> |
| <div class="welcome-icon">ποΈ</div> |
| <h2>"Seek, and it shall be given unto you"</h2> |
| <p>Ask any spiritual or philosophical question. Answers are drawn exclusively from the |
| Bhagavad Gita, Quran, Bible, and Guru Granth Sahib.<br><br> |
| <em style="color:var(--gold-light); font-size:.9rem;"> |
| You can now ask follow-up questions β the guide remembers the conversation. |
| </em> |
| </p> |
| <div class="suggested-queries"> |
| <button onclick="askSuggested(this)">What do the scriptures say about forgiveness?</button> |
| <button onclick="askSuggested(this)">How should one face fear and death?</button> |
| <button onclick="askSuggested(this)">What is the purpose of prayer and worship?</button> |
| <button onclick="askSuggested(this)">What is the nature of the soul according to each religion?</button> |
| <button onclick="askSuggested(this)">What do the scriptures teach about humility and selfless service?</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="input-area"> |
| <div class="input-row"> |
| <textarea id="questionInput" |
| placeholder="Ask a question, or follow up on the previous answerβ¦" |
| rows="1" |
| onkeydown="handleKey(event)" |
| oninput="autoResize(this)"></textarea> |
| <button class="send-btn" id="sendBtn" onclick="sendQuestion()" title="Ask (Enter)">β¦</button> |
| </div> |
| <p class="input-hint">Enter to ask Β· Shift+Enter for new line Β· Follow-ups like "elaborate on point 2" work!</p> |
| </div> |
|
|
| </div> |
|
|
| <script> |
| const API_BASE = window.location.origin; |
| let isLoading = false; |
| let sessionId = null; |
| let turnCount = 0; |
| |
| |
| function loadSession() { |
| sessionId = localStorage.getItem("rag_session_id") || null; |
| } |
| |
| function saveSession(id) { |
| sessionId = id; |
| localStorage.setItem("rag_session_id", id); |
| } |
| |
| function updateSessionBar() { |
| const bar = document.getElementById("sessionBar"); |
| const count = document.getElementById("turnCount"); |
| if (turnCount > 0) { |
| bar.classList.add("visible"); |
| count.textContent = turnCount; |
| } else { |
| bar.classList.remove("visible"); |
| } |
| } |
| |
| async function startNewConversation() { |
| if (!sessionId) return; |
| if (turnCount > 0 && !confirm("Start a new conversation? This will clear all history.")) return; |
| |
| try { |
| await fetch(`${API_BASE}/clear`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ session_id: sessionId }), |
| }); |
| } catch (_) {} |
| |
| |
| sessionId = null; |
| turnCount = 0; |
| localStorage.removeItem("rag_session_id"); |
| updateSessionBar(); |
| |
| const chatWindow = document.getElementById("chatWindow"); |
| chatWindow.innerHTML = ` |
| <div class="welcome" id="welcomePane"> |
| <div class="welcome-icon">ποΈ</div> |
| <h2>"Seek, and it shall be given unto you"</h2> |
| <p>Ask any spiritual or philosophical question. Answers are drawn exclusively from the |
| Bhagavad Gita, Quran, Bible, and Guru Granth Sahib.<br><br> |
| <em style="color:var(--gold-light); font-size:.9rem;"> |
| You can now ask follow-up questions β the guide remembers the conversation. |
| </em> |
| </p> |
| <div class="suggested-queries"> |
| <button onclick="askSuggested(this)">What do the scriptures say about forgiveness?</button> |
| <button onclick="askSuggested(this)">How should one face fear and death?</button> |
| <button onclick="askSuggested(this)">What is the purpose of prayer and worship?</button> |
| <button onclick="askSuggested(this)">What is the nature of the soul according to each religion?</button> |
| <button onclick="askSuggested(this)">What do the scriptures teach about humility and selfless service?</button> |
| </div> |
| </div>`; |
| } |
| |
| |
| function getSourceClass(book) { |
| const b = book.toLowerCase(); |
| if (b.includes("gita")) return "source-gita"; |
| if (b.includes("quran") || b.includes("koran")) return "source-quran"; |
| if (b.includes("bible") || b.includes("testament")) return "source-bible"; |
| if (b.includes("granth") || b.includes("guru")) return "source-granth"; |
| return "source-other"; |
| } |
| |
| function hideWelcome() { |
| const w = document.getElementById("welcomePane"); |
| if (w) w.remove(); |
| } |
| |
| function scrollToBottom() { |
| const w = document.getElementById("chatWindow"); |
| w.scrollTop = w.scrollHeight; |
| } |
| |
| function autoResize(el) { |
| el.style.height = "auto"; |
| el.style.height = Math.min(el.scrollHeight, 130) + "px"; |
| } |
| |
| function formatAnswer(text) { |
| text = text.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>"); |
| return text.split(/\n\n+/).filter(p => p.trim()).map(p => `<p>${p.trim()}</p>`).join(""); |
| } |
| |
| function escapeHtml(str) { |
| return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
| } |
| |
| |
| function appendUserMessage(question, isFollowup) { |
| const w = document.getElementById("chatWindow"); |
| const div = document.createElement("div"); |
| div.className = "message message-user"; |
| const pill = isFollowup |
| ? `<span class="followup-pill">follow-up</span>` |
| : ""; |
| div.innerHTML = ` |
| <span class="msg-label">You${pill}</span> |
| <div class="msg-bubble">${escapeHtml(question)}</div> |
| `; |
| w.appendChild(div); |
| scrollToBottom(); |
| } |
| |
| function appendLoading() { |
| const w = document.getElementById("chatWindow"); |
| const div = document.createElement("div"); |
| div.className = "message message-assistant"; |
| div.id = "loadingMsg"; |
| div.innerHTML = ` |
| <span class="msg-label">Sacred Texts</span> |
| <div class="loading"> |
| <div class="loading-dots"><span></span><span></span><span></span></div> |
| <span class="loading-text">Consulting the scripturesβ¦</span> |
| </div> |
| `; |
| w.appendChild(div); |
| scrollToBottom(); |
| return div; |
| } |
| |
| function renderSourcesInPlace(container, sources) { |
| const sourceTags = (sources || []).map(s => { |
| const cls = getSourceClass(s.book); |
| return `<span class="source-tag ${cls}" title="${escapeHtml(s.snippet || '')}">π ${escapeHtml(s.book)}</span>`; |
| }).join(""); |
| if (sourceTags) { |
| container.innerHTML = ` |
| <div class="sources"> |
| <div class="sources-label">Citations</div> |
| <div class="source-tags">${sourceTags}</div> |
| </div>`; |
| } |
| } |
| |
| |
| async function sendQuestion() { |
| if (isLoading) return; |
| const input = document.getElementById("questionInput"); |
| const question = input.value.trim(); |
| if (!question) return; |
| |
| hideWelcome(); |
| const isFollowup = turnCount > 0; |
| |
| isLoading = true; |
| document.getElementById("sendBtn").disabled = true; |
| input.value = ""; |
| input.style.height = "auto"; |
| |
| appendUserMessage(question, isFollowup); |
| const loadingEl = appendLoading(); |
| |
| try { |
| const payload = { question }; |
| if (sessionId) payload.session_id = sessionId; |
| |
| const res = await fetch(`${API_BASE}/ask`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(payload), |
| }); |
| |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({ detail: res.statusText })); |
| throw new Error(err.detail || "Server error"); |
| } |
| |
| |
| const returnedSession = res.headers.get("X-Session-Id"); |
| if (returnedSession) saveSession(returnedSession); |
| |
| |
| loadingEl.innerHTML = ` |
| <span class="msg-label">Sacred Texts</span> |
| <div class="msg-bubble" id="currentStreamingMsg"> |
| <div class="loading-text">The scriptures are being revealed |
| <span class="thinking-dots"><span></span><span></span><span></span></span> |
| </div> |
| </div> |
| <div id="currentStreamingSources"></div>`; |
| |
| const bubble = document.getElementById("currentStreamingMsg"); |
| const sourcesContainer = document.getElementById("currentStreamingSources"); |
| let fullAnswer = ""; |
| let buffer = ""; |
| let firstToken = false; |
| |
| const reader = res.body.getReader(); |
| const decoder = new TextDecoder(); |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split("\n"); |
| buffer = lines.pop(); |
| |
| for (const line of lines) { |
| if (!line.trim()) continue; |
| try { |
| const parsed = JSON.parse(line); |
| |
| if (parsed.type === "token") { |
| if (!firstToken) { bubble.innerHTML = ""; firstToken = true; } |
| fullAnswer += parsed.data; |
| bubble.innerHTML = formatAnswer(fullAnswer); |
| scrollToBottom(); |
| } |
| else if (parsed.type === "sources") { |
| renderSourcesInPlace(sourcesContainer, parsed.data); |
| } |
| else if (parsed.type === "cache") { |
| bubble.innerHTML = formatAnswer(parsed.data.answer); |
| renderSourcesInPlace(sourcesContainer, parsed.data.sources); |
| scrollToBottom(); |
| } |
| } catch (e) { |
| console.warn("Stream parse error:", e); |
| } |
| } |
| } |
| |
| |
| turnCount++; |
| updateSessionBar(); |
| |
| |
| bubble.removeAttribute("id"); |
| sourcesContainer.removeAttribute("id"); |
| |
| } catch (err) { |
| loadingEl.innerHTML = ` |
| <span class="msg-label">Error</span> |
| <div class="error-bubble">β οΈ ${escapeHtml(err.message)}</div>`; |
| scrollToBottom(); |
| } finally { |
| isLoading = false; |
| document.getElementById("sendBtn").disabled = false; |
| input.focus(); |
| } |
| } |
| |
| function askSuggested(btn) { |
| const input = document.getElementById("questionInput"); |
| input.value = btn.textContent.trim(); |
| autoResize(input); |
| sendQuestion(); |
| } |
| |
| function handleKey(e) { |
| if (e.key === "Enter" && !e.shiftKey) { |
| e.preventDefault(); |
| sendQuestion(); |
| } |
| } |
| |
| |
| loadSession(); |
| </script> |
| </body> |
| </html> |