RAI-BETA / index.html
Kawaquader's picture
Update index.html
383672e verified
<!DOCTYPE html>
<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>