Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>RAI</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| :root { | |
| --sidebar-bg: #f7f7f5; | |
| --sidebar-hover: #efefec; | |
| --sidebar-active:#e8e8e4; | |
| --main-bg: #ffffff; | |
| --input-bg: #fff; | |
| --input-border: #e2e2de; | |
| --text-primary: #1a1a1a; | |
| --text-secondary:#6b6b6b; | |
| --text-tertiary: #b0b0aa; | |
| --accent: #1a7f5a; | |
| --accent-light: #eaf5f0; | |
| --user-bubble: #f0f0ec; | |
| --divider: #ebebeb; | |
| --font: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| --radius-sm: 8px; | |
| --radius-md: 14px; | |
| --radius-lg: 22px; | |
| } | |
| html, body { height: 100%; font-family: var(--font); background: #fff; color: var(--text-primary); font-size: 15px; line-height: 1.6; overflow: hidden; } | |
| .app { display: flex; height: 100vh; } | |
| /* ββ SIDEBAR ββ */ | |
| .sidebar { | |
| width: 248px; flex-shrink: 0; | |
| background: var(--sidebar-bg); | |
| border-right: 1px solid var(--divider); | |
| display: flex; flex-direction: column; | |
| padding: 10px; gap: 2px; | |
| } | |
| .sidebar-top { display: flex; align-items: center; justify-content: space-between; padding: 4px 6px 12px; } | |
| .logo { display: flex; align-items: center; gap: 9px; font-weight: 600; font-size: 0.95rem; color: var(--text-primary); letter-spacing: -0.01em; } | |
| .logo-mark { width: 26px; height: 26px; background: var(--accent); border-radius: 7px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.78rem; color: #fff; flex-shrink: 0; } | |
| .icon-btn { width: 32px; height: 32px; border-radius: var(--radius-sm); background: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: background .13s, color .13s; } | |
| .icon-btn:hover { background: var(--sidebar-hover); color: var(--text-primary); } | |
| .new-chat-btn { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: var(--radius-sm); border: none; background: transparent; color: var(--text-secondary); font-size: 0.875rem; font-family: var(--font); cursor: pointer; transition: background .13s, color .13s; width: 100%; text-align: left; } | |
| .new-chat-btn:hover { background: var(--sidebar-hover); color: var(--text-primary); } | |
| .section-label { font-size: 0.69rem; font-weight: 500; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: .07em; padding: 14px 10px 5px; } | |
| .chat-history { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; } | |
| .chat-history::-webkit-scrollbar { width: 3px; } | |
| .chat-history::-webkit-scrollbar-thumb { background: var(--input-border); border-radius: 4px; } | |
| .history-item { padding: 7px 10px; border-radius: var(--radius-sm); font-size: 0.84rem; color: var(--text-secondary); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: background .12s, color .12s; } | |
| .history-item:hover { background: var(--sidebar-hover); color: var(--text-primary); } | |
| .history-item.active { background: var(--sidebar-active); color: var(--text-primary); } | |
| .sidebar-footer { border-top: 1px solid var(--divider); padding-top: 10px; margin-top: 4px; } | |
| .user-row { display: flex; align-items: center; gap: 9px; padding: 7px 10px; border-radius: var(--radius-sm); cursor: pointer; transition: background .12s; } | |
| .user-row:hover { background: var(--sidebar-hover); } | |
| .user-av { width: 28px; height: 28px; border-radius: 50%; background: linear-gradient(135deg,#1a7f5a,#0d5c40); display: flex; align-items: center; justify-content: center; font-size: .72rem; font-weight: 600; color: #fff; flex-shrink: 0; } | |
| .user-name { font-size: .845rem; font-weight: 500; color: var(--text-primary); flex: 1; } | |
| /* ββ MAIN ββ */ | |
| .main { flex: 1; display: flex; flex-direction: column; min-width: 0; position: relative; } | |
| .topbar { height: 50px; display: flex; align-items: center; justify-content: space-between; padding: 0 22px; flex-shrink: 0; border-bottom: 1px solid var(--divider); } | |
| .model-pill { display: flex; align-items: center; gap: 5px; padding: 5px 11px; border-radius: 20px; border: 1px solid var(--divider); background: transparent; color: var(--text-primary); font-size: .845rem; font-weight: 500; font-family: var(--font); cursor: pointer; transition: background .12s; } | |
| .model-pill:hover { background: var(--sidebar-bg); } | |
| .top-btn { display: flex; align-items: center; gap: 5px; padding: 5px 12px; border-radius: 20px; border: 1px solid var(--divider); background: transparent; color: var(--text-secondary); font-size: .82rem; font-family: var(--font); cursor: pointer; transition: background .12s, color .12s; } | |
| .top-btn:hover { background: var(--sidebar-bg); color: var(--text-primary); } | |
| /* ββ CHAT AREA ββ */ | |
| #chatbox { flex: 1; overflow-y: auto; scroll-behavior: smooth; } | |
| #chatbox::-webkit-scrollbar { width: 4px; } | |
| #chatbox::-webkit-scrollbar-thumb { background: var(--input-border); border-radius: 4px; } | |
| /* ββ WELCOME ββ */ | |
| .welcome { | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; | |
| min-height: 100%; padding: 60px 24px 180px; text-align: center; gap: 0; | |
| } | |
| .welcome-icon { width: 50px; height: 50px; background: var(--accent); border-radius: 15px; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; font-weight: 700; color: #fff; margin-bottom: 22px; box-shadow: 0 6px 22px rgba(26,127,90,.2); } | |
| .welcome h1 { font-size: 1.85rem; font-weight: 600; color: var(--text-primary); letter-spacing: -.03em; margin-bottom: 10px; line-height: 1.25; } | |
| .welcome-sub { font-size: .93rem; color: var(--text-secondary); max-width: 340px; margin-bottom: 32px; } | |
| .chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; max-width: 520px; } | |
| .chip { padding: 9px 16px; border-radius: 20px; border: 1px solid var(--divider); background: var(--sidebar-bg); color: var(--text-secondary); font-size: .845rem; font-family: var(--font); cursor: pointer; transition: background .13s, color .13s, border-color .13s, transform .1s; white-space: nowrap; } | |
| .chip:hover { background: var(--accent-light); color: var(--accent); border-color: #b5ddd0; transform: translateY(-1px); } | |
| /* ββ MESSAGES ββ */ | |
| .msgs { max-width: 720px; margin: 0 auto; padding: 28px 22px 170px; display: flex; flex-direction: column; gap: 2px; } | |
| .msg-group { display: flex; flex-direction: column; padding: 5px 0; animation: fadeUp .17s ease-out; } | |
| @keyframes fadeUp { from{opacity:0;transform:translateY(5px)} to{opacity:1;transform:translateY(0)} } | |
| .user-bubble { align-self: flex-end; background: var(--user-bubble); color: var(--text-primary); padding: 11px 16px; border-radius: 18px 18px 4px 18px; max-width: 74%; font-size: .95rem; line-height: 1.6; word-break: break-word; } | |
| .bot-row { display: flex; gap: 13px; align-items: flex-start; padding: 4px 0; } | |
| .bot-av { width: 26px; height: 26px; border-radius: 50%; background: var(--accent); display: flex; align-items: center; justify-content: center; font-size: .7rem; font-weight: 700; color: #fff; flex-shrink: 0; margin-top: 3px; } | |
| .bot-text { flex: 1; min-width: 0; font-size: .95rem; line-height: 1.78; color: var(--text-primary); word-break: break-word; } | |
| .msg-actions { display: flex; gap: 2px; margin-top: 6px; margin-left: 39px; opacity: 0; transition: opacity .14s; } | |
| .msg-group:hover .msg-actions { opacity: 1; } | |
| .act-btn { width: 28px; height: 28px; border-radius: 6px; background: transparent; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-tertiary); transition: background .12s, color .12s; } | |
| .act-btn:hover { background: var(--sidebar-bg); color: var(--text-secondary); } | |
| /* Thinking dots */ | |
| .thinking { display: flex; align-items: center; gap: 4px; padding: 4px 0; } | |
| .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-tertiary); animation: pulse 1.3s ease-in-out infinite; } | |
| .dot:nth-child(2){animation-delay:.18s} .dot:nth-child(3){animation-delay:.36s} | |
| @keyframes pulse { 0%,60%,100%{transform:scale(.5);opacity:.35} 30%{transform:scale(1);opacity:1} } | |
| .cursor { display: inline-block; width: 2px; height: 15px; background: var(--text-primary); margin-left: 2px; vertical-align: middle; border-radius: 1px; animation: blink .85s step-end infinite; } | |
| @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} } | |
| /* ββ INPUT ββ */ | |
| .input-area { position: absolute; bottom: 0; left: 0; right: 0; padding: 10px 22px 18px; background: linear-gradient(to top,#fff 62%,rgba(255,255,255,0)); pointer-events: none; } | |
| .input-wrap { max-width: 720px; margin: 0 auto; pointer-events: all; } | |
| .input-box { background: var(--input-bg); border: 1px solid var(--input-border); border-radius: var(--radius-lg); padding: 13px 14px 13px 18px; display: flex; align-items: flex-end; gap: 10px; transition: border-color .18s, box-shadow .18s; box-shadow: 0 1px 4px rgba(0,0,0,.05); } | |
| .input-box:focus-within { border-color: #c8c8c3; box-shadow: 0 4px 18px rgba(0,0,0,.08); } | |
| #userInput { flex: 1; background: transparent; border: none; color: var(--text-primary); font-family: var(--font); font-size: .95rem; line-height: 1.6; outline: none; resize: none; max-height: 200px; min-height: 24px; overflow-y: auto; } | |
| #userInput::placeholder { color: var(--text-tertiary); } | |
| #sendBtn { width: 32px; height: 32px; border-radius: 50%; background: var(--text-primary); color: #fff; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: background .15s, transform .1s; } | |
| #sendBtn:hover:not(:disabled) { background: #333; transform: scale(1.06); } | |
| #sendBtn:disabled { background: var(--input-border); color: var(--text-tertiary); cursor: not-allowed; transform: none; } | |
| .disclaimer { text-align: center; font-size: .7rem; color: var(--text-tertiary); padding-top: 8px; } | |
| @media(max-width:640px){ .sidebar{display:none} .chips{flex-direction:column;align-items:center} } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <!-- SIDEBAR --> | |
| <aside class="sidebar"> | |
| <div class="sidebar-top"> | |
| <div class="logo"> | |
| <div class="logo-mark">R</div> | |
| RAI | |
| </div> | |
| <button class="icon-btn" title="New chat" onclick="startNewChat()"> | |
| <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg> | |
| </button> | |
| </div> | |
| <button class="new-chat-btn" onclick="startNewChat()"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg> | |
| New chat | |
| </button> | |
| <div class="section-label">Recent</div> | |
| <div class="chat-history" id="chatHistory"></div> | |
| <div class="sidebar-footer"> | |
| <div class="user-row"> | |
| <div class="user-av">U</div> | |
| <span class="user-name">User</span> | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-tertiary)"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- MAIN --> | |
| <main class="main"> | |
| <div class="topbar"> | |
| <button class="model-pill"> | |
| RAI Beta | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg> | |
| </button> | |
| <button class="top-btn" onclick="startNewChat()"> | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg> | |
| New chat | |
| </button> | |
| </div> | |
| <div id="chatbox"> | |
| <!-- Welcome screen --> | |
| <div class="welcome" id="welcomeScreen"> | |
| <div class="welcome-icon">R</div> | |
| <h1>What can I help with?</h1> | |
| <p class="welcome-sub">I just trained 10MB data, So i can not answer your all question. Developer testing his own AI, started from scratch.</p> | |
| <div class="chips"> | |
| <button class="chip" onclick="useSuggestion('Explain quantum computing in simple terms')">Explain quantum computing</button> | |
| <button class="chip" onclick="useSuggestion('Write a short creative story about a robot')">Write a creative story</button> | |
| <button class="chip" onclick="useSuggestion('Give me 5 productivity tips for developers')">Productivity tips</button> | |
| <button class="chip" onclick="useSuggestion('What are Python best practices?')">Python best practices</button> | |
| <button class="chip" onclick="useSuggestion('Summarize the concept of machine learning')">Explain machine learning</button> | |
| <button class="chip" onclick="useSuggestion('Help me brainstorm startup ideas in AI')">Startup ideas in AI</button> | |
| </div> | |
| </div> | |
| <!-- Messages --> | |
| <div class="msgs" id="msgs" style="display:none;"></div> | |
| </div> | |
| <!-- Input --> | |
| <div class="input-area"> | |
| <div class="input-wrap"> | |
| <div class="input-box"> | |
| <textarea id="userInput" placeholder="Message RAIβ¦" rows="1"></textarea> | |
| <button id="sendBtn" aria-label="Send"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg> | |
| </button> | |
| </div> | |
| <p class="disclaimer">RAI can make mistakes. Double-check important information.</p> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <script> | |
| const API_URL = "/chat"; | |
| const chatbox = document.getElementById('chatbox'); | |
| const welcomeScreen = document.getElementById('welcomeScreen'); | |
| const msgs = document.getElementById('msgs'); | |
| const userInput = document.getElementById('userInput'); | |
| const sendBtn = document.getElementById('sendBtn'); | |
| const statusBar = document.getElementById('statusBar'); | |
| const chatHistory = document.getElementById('chatHistory'); | |
| let currentSessionIndex = -1; | |
| function autoResize() { userInput.style.height = 'auto'; userInput.style.height = Math.min(userInput.scrollHeight, 200) + 'px'; } | |
| userInput.addEventListener('input', () => { autoResize(); sendBtn.disabled = userInput.value.trim() === ''; }); | |
| function useSuggestion(text) { userInput.value = text; autoResize(); sendBtn.disabled = false; sendMessage(); } | |
| function startNewChat() { | |
| welcomeScreen.style.display = 'flex'; | |
| msgs.style.display = 'none'; | |
| msgs.innerHTML = ''; | |
| currentSessionIndex = -1; | |
| userInput.value = ''; | |
| autoResize(); | |
| sendBtn.disabled = true; | |
| userInput.focus(); | |
| } | |
| function addToHistory(title) { | |
| chatHistory.querySelectorAll('.history-item').forEach(i => i.classList.remove('active')); | |
| const item = document.createElement('div'); | |
| item.className = 'history-item active'; | |
| item.textContent = title.length > 38 ? title.slice(0, 38) + 'β¦' : title; | |
| chatHistory.insertBefore(item, chatHistory.firstChild); | |
| } | |
| function appendUser(text) { | |
| welcomeScreen.style.display = 'none'; | |
| msgs.style.display = 'flex'; | |
| const g = document.createElement('div'); | |
| g.className = 'msg-group'; | |
| g.style.alignItems = 'flex-end'; | |
| const b = document.createElement('div'); | |
| b.className = 'user-bubble'; | |
| b.textContent = text; | |
| g.appendChild(b); | |
| msgs.appendChild(g); | |
| scrollDown(); | |
| } | |
| function appendBot() { | |
| const g = document.createElement('div'); | |
| g.className = 'msg-group'; | |
| const row = document.createElement('div'); | |
| row.className = 'bot-row'; | |
| const av = document.createElement('div'); | |
| av.className = 'bot-av'; | |
| av.textContent = 'R'; | |
| const textEl = document.createElement('div'); | |
| textEl.className = 'bot-text'; | |
| textEl.innerHTML = `<div class="thinking"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>`; | |
| const actions = document.createElement('div'); | |
| actions.className = 'msg-actions'; | |
| actions.innerHTML = ` | |
| <button class="act-btn" title="Copy" onclick="copyMsg(this)"> | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> | |
| </button> | |
| <button class="act-btn" title="Helpful"> | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3H14z"/><path d="M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg> | |
| </button> | |
| <button class="act-btn" title="Not helpful"> | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10z"/><path d="M17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/></svg> | |
| </button>`; | |
| row.appendChild(av); | |
| row.appendChild(textEl); | |
| g.appendChild(row); | |
| g.appendChild(actions); | |
| msgs.appendChild(g); | |
| scrollDown(); | |
| return textEl; | |
| } | |
| function copyMsg(btn) { | |
| const text = btn.closest('.msg-group').querySelector('.bot-text').innerText; | |
| navigator.clipboard.writeText(text).catch(() => {}); | |
| btn.style.color = 'var(--accent)'; | |
| setTimeout(() => btn.style.color = '', 1600); | |
| } | |
| async function sendMessage() { | |
| const text = userInput.value.trim(); | |
| if (!text) return; | |
| if (currentSessionIndex === -1) { addToHistory(text); currentSessionIndex = 0; } | |
| appendUser(text); | |
| userInput.value = ''; | |
| autoResize(); | |
| userInput.disabled = true; | |
| sendBtn.disabled = true; | |
| const botEl = appendBot(); | |
| try { | |
| const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text }) }); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const reader = res.body.getReader(); | |
| const decoder = new TextDecoder("utf-8"); | |
| let buffer = "", isFirst = true; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const lines = buffer.split("\n\n"); | |
| buffer = lines.pop(); | |
| for (const line of lines) { | |
| if (!line.startsWith("data: ")) continue; | |
| try { | |
| const data = JSON.parse(line.substring(6)); | |
| if (isFirst) { botEl.innerHTML = ''; isFirst = false; } | |
| if (data.error) { | |
| botEl.innerHTML = `<span style="color:#c0392b">${data.error}</span>`; | |
| } else if (data.text !== undefined) { | |
| botEl.innerHTML = data.text.replace(/\n/g, '<br>') + '<span class="cursor"></span>'; | |
| scrollDown(); | |
| } | |
| } catch(e) {} | |
| } | |
| } | |
| botEl.innerHTML = botEl.innerHTML.replace('<span class="cursor"></span>', ''); | |
| } catch (err) { | |
| botEl.innerHTML = `<span style="color:#c0392b">Connection error. Check that the RAI Beta Space is active.</span>`; | |
| } finally { | |
| userInput.disabled = false; | |
| sendBtn.disabled = false; | |
| setTimeout(scrollDown, 60); | |
| } | |
| } | |
| function scrollDown() { chatbox.scrollTop = chatbox.scrollHeight; } | |
| sendBtn.addEventListener('click', sendMessage); | |
| </script> | |
| </body> | |
| </html> | |