CodeVed / index.html
Hanuman2's picture
Update index.html
7d871ba verified
Raw
History Blame
59.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>CODE VED | Engineered by Divy Patel</title>
<meta name="description" content="CODE VED - 202 Billion Parameter Enterprise AI System">
<!-- Premium Fonts -->
<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=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<!-- Syntax Highlighting & Parsers -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<style>
:root {
/* Clean Minimalist Light Theme */
--bg-main: #ffffff;
--bg-sidebar: #f9f9f9;
--bg-input: #f4f4f4;
--bg-hover: #ebebeb;
--text-primary: #0f0f0f;
--text-secondary: #606060;
--text-tertiary: #8e8e8e;
--border-light: #e5e5e5;
--border-focus: #d0d0d0;
--brand-color: #1a1a1a;
--brand-accent: #0066cc; /* Tool Calling Color */
--font-ui: 'Inter', sans-serif;
--font-code: 'JetBrains Mono', monospace;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 24px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
/* 📱 MOBILE BUG FIX: Using 100dvh to prevent input dock from hiding behind browser URL bar */
html, body {
height: 100%; height: 100dvh; width: 100vw;
font-family: var(--font-ui); background-color: var(--bg-main); color: var(--text-primary);
display: flex; overflow: hidden; -webkit-font-smoothing: antialiased;
}
button { background: none; border: none; cursor: pointer; color: inherit; font-family: inherit; }
input, textarea { font-family: inherit; outline: none; border: none; background: transparent; }
/* Custom Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #a0a0a0; }
/* --- 1. SIDEBAR (Restored History & Power Button) --- */
.sidebar {
width: 260px; background-color: var(--bg-sidebar); border-right: 1px solid var(--border-light);
display: flex; flex-direction: column; transition: transform 0.3s ease, width 0.3s ease; z-index: 100;
height: 100%; flex-shrink: 0;
}
.sidebar.collapsed { margin-left: -260px; }
.sidebar-header { padding: 16px; display: flex; align-items: center; justify-content: space-between; }
.btn-new-chat {
display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 10px 12px;
border-radius: var(--radius-sm); background: #fff; border: 1px solid var(--border-light);
font-size: 14px; font-weight: 500; transition: 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.02);
}
.btn-new-chat:hover { background: var(--bg-hover); color: #000; border-color: #ccc; }
.sidebar-history { flex: 1; overflow-y: auto; padding: 10px; }
.history-item { padding: 10px; border-radius: var(--radius-sm); font-size: 13px; color: var(--text-secondary); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; transition: 0.2s;}
.history-item:hover { background: var(--border-light); color: var(--text-primary); }
.sidebar-footer { padding: 16px; border-top: 1px solid var(--border-light); display: flex; align-items: center; gap: 12px; transition: background 0.2s; }
.user-avatar { width: 34px; height: 34px; border-radius: 50%; background: var(--brand-color); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 600; flex-shrink: 0; }
.user-info-box { flex: 1; overflow: hidden; }
.user-name { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-primary); }
.user-sub { font-size: 11px; font-weight: 500; color: var(--text-tertiary); margin-top: 2px; }
.btn-power {
width: 32px; height: 32px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;
color: var(--text-secondary); transition: 0.2s; cursor: pointer; border: 1px solid transparent; flex-shrink: 0;
}
.btn-power:hover { background: #ffe6e6; color: #cc0000; border-color: #ffcccc; }
/* --- 2. MAIN WORKSPACE AREA --- */
.main-area { flex: 1; display: flex; flex-direction: column; position: relative; background: var(--bg-main); transition: 0.3s; height: 100%; overflow: hidden; }
.top-bar { padding: 12px 20px; display: flex; align-items: center; justify-content: space-between; background: var(--bg-main); z-index: 10; flex-shrink: 0; border-bottom: 1px solid transparent; transition: border 0.3s; }
.top-bar.scrolled { border-bottom: 1px solid var(--border-light); }
.btn-icon { width: 32px; height: 32px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: 0.2s; }
.btn-icon:hover { background: var(--bg-hover); color: var(--text-primary); }
.btn-icon svg { width: 18px; height: 18px; }
.model-selector { font-size: 14px; font-weight: 600; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
/* --- 3. CHAT CONTAINER --- */
.chat-container { flex: 1; overflow-y: auto; padding: 20px 20px 40px; display: flex; flex-direction: column; align-items: center; scroll-behavior: smooth; }
/* Restored Welcome Screen */
.welcome-center { margin: auto; text-align: center; display: flex; flex-direction: column; align-items: center; animation: fadeIn 0.5s ease; }
.welcome-center img { width: 70px; height: 70px; border-radius: 18px; margin-bottom: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.welcome-center h1 { font-size: 26px; font-weight: 500; color: var(--text-primary); margin-bottom: 8px; letter-spacing: -0.5px; }
.welcome-center p { font-size: 14px; color: var(--text-secondary); }
.message-wrapper { width: 100%; max-width: 760px; margin-bottom: 32px; display: flex; flex-direction: column; }
.user-message { align-self: flex-end; max-width: 85%; background: var(--bg-input); padding: 12px 18px; border-radius: 20px 20px 4px 20px; font-size: 15px; line-height: 1.5; color: var(--text-primary); border: 1px solid var(--border-light); }
.bot-message { align-self: flex-start; max-width: 100%; display: flex; gap: 16px; width: 100%; }
.bot-avatar { width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0; overflow: hidden; border: 1px solid var(--border-light); }
.bot-avatar img { width: 100%; height: 100%; object-fit: cover; }
.bot-content { flex: 1; font-size: 15.5px; line-height: 1.6; color: var(--text-primary); min-width: 0; }
/* Markdown Styling */
.bot-content p { margin-bottom: 16px; }
.bot-content p:last-child { margin-bottom: 0; }
.bot-content strong { font-weight: 600; color: #000; }
.bot-content code { font-family: var(--font-code); background: var(--bg-input); padding: 2px 6px; border-radius: 4px; font-size: 0.9em; color: #d63384; }
.bot-content pre { background: #f6f8fa; padding: 16px; border-radius: var(--radius-sm); border: 1px solid var(--border-light); overflow-x: auto; margin: 16px 0; }
.bot-content pre code { background: none; padding: 0; color: inherit; font-size: 14px; }
.chat-attachment { display: inline-flex; align-items: center; gap: 8px; background: #fff; border: 1px solid var(--border-light); padding: 6px 12px; border-radius: var(--radius-md); font-size: 13px; font-weight: 500; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.chat-img-preview { max-width: 250px; border-radius: var(--radius-md); border: 1px solid var(--border-light); margin-bottom: 10px; }
/* --- 4. GOOGLE SEARCH TOOL UI --- */
.tool-indicator { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; color: var(--brand-accent); background: rgba(0, 102, 204, 0.05); padding: 8px 14px; border-radius: var(--radius-sm); margin-bottom: 16px; border: 1px solid rgba(0, 102, 204, 0.2); transition: 0.3s; }
.tool-indicator.active { animation: pulseGlow 2s infinite alternate; }
.tool-indicator svg { width: 16px; height: 16px; }
.tool-indicator.active svg { animation: spin 2s linear infinite; }
@keyframes pulseGlow { 0% { box-shadow: 0 0 0 rgba(0, 102, 204, 0); border-color: rgba(0, 102, 204, 0.2); } 100% { box-shadow: 0 0 12px rgba(0, 102, 204, 0.3); border-color: rgba(0, 102, 204, 0.5); } }
@keyframes spin { 100% { transform: rotate(360deg); } }
/* --- 5. FIXED INPUT DOCK (MOBILE SAFE) --- */
.input-dock { flex-shrink: 0; padding: 15px 20px 25px; background: var(--bg-main); border-top: 1px solid transparent; display: flex; justify-content: center; z-index: 20; width: 100%; transition: border 0.3s; }
.input-dock.scrolled { border-top: 1px solid var(--border-light); }
.input-container { width: 100%; max-width: 760px; background: var(--bg-input); border: 1px solid var(--border-light); border-radius: var(--radius-lg); padding: 8px 12px; display: flex; flex-direction: column; transition: 0.2s; position: relative; }
.input-container:focus-within { border-color: var(--border-focus); background: #fff; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
.file-preview-bar { display: none; padding: 8px 8px 12px; align-items: center; gap: 12px; border-bottom: 1px solid var(--border-light); margin-bottom: 8px; }
.file-preview-bar.active { display: flex; }
.file-preview-icon { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; background: #fff; border: 1px solid var(--border-light); display: flex; align-items: center; justify-content: center; font-size: 18px; }
.file-preview-name { flex: 1; font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-primary); }
.btn-remove-file { width: 24px; height: 24px; border-radius: 50%; background: #ffe6e6; color: #cc0000; display: flex; align-items: center; justify-content: center; }
.input-row { display: flex; align-items: flex-end; gap: 8px; width: 100%; }
.tools-left { display: flex; gap: 4px; }
.tool-btn { width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: 0.2s; position: relative; flex-shrink: 0; }
.tool-btn:hover { background: #e5e5e5; color: var(--text-primary); }
.tool-btn svg { width: 18px; height: 18px; }
.tool-btn.recording { color: #e60000; animation: pulse 1.5s infinite; }
.chat-input { flex: 1; padding: 7px 4px; font-size: 15px; resize: none; max-height: 120px; min-height: 24px; color: var(--text-primary); line-height: 1.5; }
.chat-input::placeholder { color: var(--text-tertiary); }
.btn-send { width: 34px; height: 34px; border-radius: 50%; background: var(--brand-color); color: #fff; display: flex; align-items: center; justify-content: center; transition: 0.2s; flex-shrink: 0; }
.btn-send svg { width: 16px; height: 16px; transform: translateX(-1px); }
.btn-send:hover { background: #000; transform: translateY(-1px); }
.btn-send:disabled { background: #ccc; cursor: not-allowed; transform: none; }
.hidden-input { display: none; }
.dropdown-menu { position: absolute; bottom: 100%; left: 0; margin-bottom: 10px; background: #fff; border: 1px solid var(--border-light); border-radius: var(--radius-md); padding: 6px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: none; flex-direction: column; gap: 2px; min-width: 180px; z-index: 100; }
.dropdown-menu.active { display: flex; animation: fadeIn 0.2s; }
.dropdown-item { padding: 10px 12px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 10px; cursor: pointer; transition: 0.2s; }
.dropdown-item:hover { background: var(--bg-hover); }
/* --- 6. SECURE AUTHENTICATION MODAL --- */
.auth-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); backdrop-filter: blur(5px); display: none; align-items: center; justify-content: center; z-index: 2000; }
.auth-modal { background: #fff; width: 90%; max-width: 400px; border-radius: var(--radius-md); padding: 32px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); position: relative; }
.auth-close { position: absolute; top: 16px; right: 16px; color: var(--text-secondary); width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: 0.2s; }
.auth-close:hover { background: var(--bg-hover); color: var(--text-primary); }
.auth-title { font-size: 22px; font-weight: 600; margin-bottom: 24px; text-align: center; letter-spacing: -0.5px; }
.auth-tabs { display: flex; background: var(--bg-input); border-radius: var(--radius-sm); padding: 4px; margin-bottom: 24px; }
.auth-tab { flex: 1; text-align: center; padding: 8px; font-size: 13px; font-weight: 500; cursor: pointer; border-radius: 6px; color: var(--text-secondary); transition: 0.2s; }
.auth-tab.active { background: #fff; color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.auth-input-group { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; }
.auth-input { width: 100%; padding: 12px 14px; border: 1px solid var(--border-light); border-radius: var(--radius-sm); font-size: 14px; transition: 0.2s; }
.auth-input:focus { border-color: var(--brand-color); outline: none; }
.auth-btn { width: 100%; padding: 12px; background: var(--brand-color); color: #fff; font-size: 14px; font-weight: 500; border-radius: var(--radius-sm); transition: 0.2s; }
.auth-btn:hover { background: #000; }
.auth-btn:disabled { background: #ccc; cursor: not-allowed; }
.auth-phase { display: none; }
.auth-phase.active { display: flex; flex-direction: column; }
.auth-message { font-size: 12px; color: #cc0000; text-align: center; margin-top: 10px; min-height: 18px; }
/* --- 7. WORKSPACE RIGHT PANEL --- */
.workspace-panel { width: 0; background: #fff; border-left: 1px solid var(--border-light); display: flex; flex-direction: column; transition: width 0.3s ease; overflow: hidden; box-shadow: -5px 0 20px rgba(0,0,0,0.02); z-index: 90; }
.workspace-panel.active { width: 50%; }
.ws-header { padding: 12px 20px; border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; background: #fafafa; }
.ws-tabs { display: flex; gap: 4px; background: #ebebeb; padding: 4px; border-radius: var(--radius-sm); }
.ws-tab { padding: 6px 16px; font-size: 13px; font-weight: 500; border-radius: 6px; cursor: pointer; color: var(--text-secondary); transition: 0.2s; }
.ws-tab.active { background: #fff; color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.ws-actions { display: flex; gap: 8px; }
.ws-btn { padding: 6px; border-radius: var(--radius-sm); color: var(--text-secondary); transition: 0.2s; }
.ws-btn:hover { background: #ebebeb; color: var(--text-primary); }
.ws-content { flex: 1; overflow: hidden; position: relative; }
.ws-code { height: 100%; overflow: auto; padding: 20px; background: #fbfbfb; }
.ws-code pre { margin: 0; font-family: var(--font-code); font-size: 14px; }
.ws-preview { display: none; height: 100%; width: 100%; flex-direction: column; background: #fff; }
.ws-console { background: #1a1a1a; color: #fff; font-family: var(--font-code); padding: 20px; font-size: 13px; flex: 1; overflow: auto; white-space: pre-wrap; }
.btn-launch-ws { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: var(--bg-main); border: 1px solid var(--border-light); border-radius: var(--radius-sm); font-size: 12px; font-weight: 500; cursor: pointer; margin-top: 10px; transition: 0.2s; }
.btn-launch-ws:hover { background: #f0f0f0; }
/* Utilities */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.typing-indicator { display: flex; gap: 4px; padding: 10px 0; }
.typing-dot { width: 6px; height: 6px; background: var(--text-tertiary); border-radius: 50%; animation: typeBounce 1.4s infinite ease-in-out both; }
.typing-dot:nth-child(1) { animation-delay: -0.32s; } .typing-dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes typeBounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
svg { width: 100%; height: 100%; }
@media (max-width: 900px) {
.sidebar { position: absolute; left: 0; height: 100%; box-shadow: 5px 0 20px rgba(0,0,0,0.05); }
.sidebar.collapsed { left: -260px; margin-left: 0; }
.workspace-panel.active { width: 100%; position: absolute; top:0; left:0; height:100%; z-index: 1000; }
.input-dock { padding: 10px 15px 15px; }
}
</style>
</head>
<body>
<!-- SIDEBAR -->
<aside class="sidebar collapsed" id="sidebar">
<div class="sidebar-header">
<button class="btn-icon" onclick="UI.toggleSidebar()" title="Close Sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
</button>
<span style="font-weight: 600; font-size: 14px; letter-spacing: 0.5px;">CODE VED</span>
</div>
<div style="padding: 0 16px;">
<button class="btn-new-chat" onclick="HistoryManager.clearHistory()">
<svg style="width:16px; height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
New Chat
</button>
</div>
<!-- RESTORED: Chat History Section -->
<div class="sidebar-history" id="chatHistory">
<!-- Dynamic History populated here -->
</div>
<!-- User Profile Area -->
<div class="sidebar-footer" id="profileArea">
<div class="user-avatar" id="uAv">G</div>
<div class="user-info-box">
<div class="user-name" id="uName">Guest Session</div>
<div class="user-sub" id="uSub" onclick="Auth.openModal()" style="cursor:pointer; color:var(--brand-accent);">Login Required</div>
</div>
<!-- RESTORED: Power Button (Logout) -->
<button class="btn-power" id="btnLogout" onclick="Auth.handleLogout()" title="Logout" style="display:none;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg>
</button>
</div>
</aside>
<!-- MAIN AREA -->
<main class="main-area">
<header class="top-bar" id="topBar">
<button class="btn-icon" onclick="UI.toggleSidebar()" title="Open Menu" id="btnMenuOpen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
</button>
<div class="model-selector">CODE VED 202B</div>
<div style="width: 32px;"></div> <!-- Spacer -->
</header>
<div class="chat-container" id="chatContainer">
<div class="welcome-center" id="welcomeScreen">
<img src="https://i.ibb.co/MyYStcGP/TIRANGA-20260613-131924-0000.png" alt="CODE VED Logo">
<h1>What's your focus today?</h1>
<p id="welcomeGreeting">Hi Divy Patel, I am ready to assist you.</p>
</div>
</div>
<div class="input-dock" id="inputDock">
<div class="input-container">
<div class="file-preview-bar" id="filePreviewBar">
<img id="imgPreview" class="file-preview-icon" src="" style="display:none;">
<div id="docPreview" class="file-preview-icon" style="display:none;">
<svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
</div>
<div class="file-preview-name" id="fileName">document.pdf</div>
<button class="btn-remove-file" onclick="FileSys.discard()"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
</div>
<div class="input-row">
<div class="tools-left">
<div style="position: relative;">
<button class="tool-btn" onclick="UI.toggleAttachMenu()" title="Attach File">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
</button>
<div class="dropdown-menu" id="attachMenu">
<div class="dropdown-item" onclick="document.getElementById('imgUpload').click()">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg> Upload Image
</div>
<div class="dropdown-item" onclick="document.getElementById('docUpload').click()">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg> Upload Document
</div>
</div>
<input type="file" id="imgUpload" class="hidden-input" accept="image/*" onchange="FileSys.process(this, 'image')">
<input type="file" id="docUpload" class="hidden-input" accept=".pdf,.txt,.docx,.html,.js,.py,.css,.cpp,.c,.json,.md" onchange="FileSys.process(this, 'document')">
</div>
<button class="tool-btn" id="btnStt" onclick="Speech.toggle()" title="Voice Typing">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
</button>
</div>
<textarea class="chat-input" id="mainInput" placeholder="Message CODE VED..." rows="1"></textarea>
<button class="btn-send" id="btnSend" onclick="Chat.handleSend()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
</button>
</div>
</div>
</div>
</main>
<!-- WORKSPACE PANEL -->
<aside class="workspace-panel" id="workspacePanel">
<div class="ws-header">
<div class="ws-tabs">
<div class="ws-tab active" id="tabCode" onclick="Workspace.switchTab('code')">Code</div>
<div class="ws-tab" id="tabPreview" onclick="Workspace.switchTab('preview')">Preview</div>
</div>
<div class="ws-actions">
<button class="ws-btn" onclick="Workspace.copy()" title="Copy Code">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
<button class="ws-btn" onclick="Workspace.close()" title="Close Panel">
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
<div class="ws-content">
<div class="ws-code" id="wsCodeView"><pre><code id="wsCodeBlock" class="hljs"></code></pre></div>
<div class="ws-preview" id="wsPreviewView"></div>
</div>
</aside>
<!-- SECURE AUTHENTICATION MODAL -->
<div class="auth-overlay" id="authModal">
<div class="auth-modal">
<button class="auth-close" onclick="Auth.closeModal()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
<div class="auth-title">Welcome to CODE VED</div>
<div class="auth-tabs">
<div class="auth-tab active" id="tabLogin" onclick="Auth.switchTab('login')">Login</div>
<div class="auth-tab" id="tabRegister" onclick="Auth.switchTab('register')">Register</div>
</div>
<!-- Login Flow -->
<div id="flowLogin">
<div class="auth-phase active" id="loginPhase1">
<div class="auth-input-group">
<input type="email" class="auth-input" id="logEmail" placeholder="Email Address">
</div>
<button class="auth-btn" id="btnLogOtp" onclick="Auth.process('login_send_otp')">Send Secure OTP</button>
</div>
<div class="auth-phase" id="loginPhase2">
<div class="auth-input-group">
<input type="number" class="auth-input" id="logOtp" placeholder="Enter OTP">
</div>
<button class="auth-btn" id="btnLogVerify" onclick="Auth.process('login_verify')">Verify & Login</button>
<button style="margin-top:10px; font-size:12px; color:var(--text-secondary); width:100%; text-align:center;" onclick="Auth.switchPhase('loginPhase2', 'loginPhase1')">Back to Email</button>
</div>
</div>
<!-- Register Flow -->
<div id="flowRegister" style="display:none;">
<div class="auth-phase active" id="regPhase1">
<div class="auth-input-group">
<input type="text" class="auth-input" id="regName" placeholder="Full Name">
<input type="email" class="auth-input" id="regEmail" placeholder="Email Address">
</div>
<button class="auth-btn" id="btnRegOtp" onclick="Auth.process('register_send_otp')">Create Account</button>
</div>
<div class="auth-phase" id="regPhase2">
<div class="auth-input-group">
<input type="number" class="auth-input" id="regOtp" placeholder="Enter OTP">
</div>
<button class="auth-btn" id="btnRegVerify" onclick="Auth.process('register_verify')">Verify & Register</button>
<button style="margin-top:10px; font-size:12px; color:var(--text-secondary); width:100%; text-align:center;" onclick="Auth.switchPhase('regPhase2', 'regPhase1')">Back to Details</button>
</div>
</div>
<div class="auth-message" id="authMsg"></div>
</div>
</div>
<script>
// --- 1. CONFIG & STATE ---
const Config = {
GAS_URL: "https://script.google.com/macros/s/AKfycbz5vrlB4GrfkfBVYdQ52IQweIXC1cNDwKuTUorxdAiOdSSIrH55mzJlaz9kq1Y94ot5/exec",
API_ENDPOINT: "/api/chat", // Pointing to your powerful Python backend
LOGO: "https://i.ibb.co/MyYStcGP/TIRANGA-20260613-131924-0000.png"
};
const State = {
user: localStorage.getItem('codeved_user') || null,
name: localStorage.getItem('codeved_name') || null,
guestCount: parseInt(localStorage.getItem('codeved_guest') || '0', 10),
attachment: null,
isProcessing: false,
history: [],
workspaces: [],
currentWs: null
};
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
// --- 2. UI UTILITIES ---
const UI = {
toggleSidebar: () => {
const sb = document.getElementById('sidebar');
const btnOpen = document.getElementById('btnMenuOpen');
sb.classList.toggle('collapsed');
btnOpen.style.display = sb.classList.contains('collapsed') ? 'flex' : 'none';
},
toggleAttachMenu: () => document.getElementById('attachMenu').classList.toggle('active'),
autoGrow: (el) => { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 120) + 'px'; },
scrollToBottom: () => { const c = document.getElementById('chatContainer'); c.scrollTop = c.scrollHeight; },
escape: (s) => s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'),
showAuthMsg: (msg, isError=true) => {
const el = document.getElementById('authMsg');
el.innerText = msg; el.style.color = isError ? '#cc0000' : '#009900';
setTimeout(() => el.innerText='', 4000);
},
handleScrollEvents: () => {
const c = document.getElementById('chatContainer');
const topBar = document.getElementById('topBar');
const dock = document.getElementById('inputDock');
if (c.scrollTop > 10) topBar.classList.add('scrolled'); else topBar.classList.remove('scrolled');
if (c.scrollHeight - c.scrollTop - c.clientHeight > 10) dock.classList.add('scrolled'); else dock.classList.remove('scrolled');
}
};
document.getElementById('chatContainer').addEventListener('scroll', UI.handleScrollEvents);
document.addEventListener('click', (e) => {
if(!e.target.closest('.tools-left')) document.getElementById('attachMenu').classList.remove('active');
});
const inp = document.getElementById('mainInput');
inp.addEventListener('input', function() { UI.autoGrow(this); });
inp.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); Chat.handleSend(); }
});
// --- 3. AUTHENTICATION & HISTORY DB (RESTORED COMPLETELY) ---
const Auth = {
init: () => {
const uAv = document.getElementById('uAv');
const uName = document.getElementById('uName');
const uSub = document.getElementById('uSub');
const btnLogout = document.getElementById('btnLogout');
const greeting = document.getElementById('welcomeGreeting');
if (State.user) {
const dispName = State.name || State.user.split('@')[0];
uAv.innerText = dispName.charAt(0).toUpperCase();
uName.innerText = dispName;
uSub.innerText = "Authenticated";
uSub.style.color = "#009900";
uSub.onclick = null;
uSub.style.cursor = 'default';
btnLogout.style.display = 'flex';
greeting.innerText = `Hi ${dispName}, I am ready to assist you.`;
HistoryManager.sync(); // Load History on Boot
} else {
uAv.innerText = "G";
uName.innerText = "Guest Session";
uSub.innerText = `Queries: ${State.guestCount}/10`;
uSub.style.color = "var(--text-tertiary)";
btnLogout.style.display = 'none';
greeting.innerText = `Hi Divy Patel, I am ready to assist you.`;
}
},
handleLogout: () => {
if(confirm("Are you sure you want to log out?")) {
localStorage.removeItem('codeved_user');
localStorage.removeItem('codeved_name');
location.reload();
}
},
openModal: () => document.getElementById('authModal').style.display = 'flex',
closeModal: () => document.getElementById('authModal').style.display = 'none',
switchTab: (tab) => {
document.getElementById('tabLogin').classList.remove('active');
document.getElementById('tabRegister').classList.remove('active');
document.getElementById('flowLogin').style.display = 'none';
document.getElementById('flowRegister').style.display = 'none';
if(tab === 'login') { document.getElementById('tabLogin').classList.add('active'); document.getElementById('flowLogin').style.display = 'block'; }
else { document.getElementById('tabRegister').classList.add('active'); document.getElementById('flowRegister').style.display = 'block'; }
},
switchPhase: (from, to) => { document.getElementById(from).classList.remove('active'); document.getElementById(to).classList.add('active'); },
process: async (action) => {
let payload = { action: action };
let btnId = '';
if (action === 'register_send_otp') {
payload.name = document.getElementById('regName').value.trim();
payload.email = document.getElementById('regEmail').value.trim();
payload.phone = "0000000000"; payload.organization = "CODE VED System";
if (!payload.name || !payload.email) return UI.showAuthMsg("Please fill all details.");
btnId = 'btnRegOtp';
} else if (action === 'login_send_otp') {
payload.email = document.getElementById('logEmail').value.trim();
if (!payload.email) return UI.showAuthMsg("Email is required.");
btnId = 'btnLogOtp';
} else if (action === 'register_verify' || action === 'login_verify') {
payload.email = document.getElementById(action === 'register_verify' ? 'regEmail' : 'logEmail').value.trim();
payload.otp = document.getElementById(action === 'register_verify' ? 'regOtp' : 'logOtp').value.trim();
if (!payload.otp) return UI.showAuthMsg("OTP is required.");
btnId = action === 'register_verify' ? 'btnRegVerify' : 'btnLogVerify';
}
const btn = document.getElementById(btnId);
const originalText = btn.innerText;
btn.disabled = true; btn.innerText = "Processing...";
try {
const res = await fetch(Config.GAS_URL, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify(payload) });
const data = await res.json();
if (data.status === 'success') {
UI.showAuthMsg(data.message, false);
if (action === 'register_send_otp') Auth.switchPhase('regPhase1', 'regPhase2');
else if (action === 'login_send_otp') Auth.switchPhase('loginPhase1', 'loginPhase2');
else {
localStorage.setItem('codeved_user', payload.email);
if(payload.name) localStorage.setItem('codeved_name', payload.name);
location.reload();
}
} else { UI.showAuthMsg(data.message); }
} catch (e) { UI.showAuthMsg("Database connection error. Please try again."); }
btn.disabled = false; btn.innerText = originalText;
}
};
const HistoryManager = {
sync: async () => {
if(!State.user) return;
try {
const res = await fetch(Config.GAS_URL, { method: 'POST', body: JSON.stringify({action: "get_history", email: State.user})});
const data = await res.json();
if(data.status === 'success' && data.historyJSON !== "[]") {
State.history = JSON.parse(data.historyJSON);
document.getElementById('welcomeScreen').style.display = 'none';
State.history.forEach((msg, idx) => {
if(msg.role === 'user') {
Chat.renderUser(msg.content, '');
// Add to sidebar history list
const shortText = msg.content.substring(0, 30) + (msg.content.length > 30 ? '...' : '');
document.getElementById('chatHistory').innerHTML += `<div class="history-item" onclick="alert('History navigation active')">${UI.escape(shortText)}</div>`;
}
else {
const botNode = Chat.renderBot();
botNode.innerHTML = marked.parse(msg.content.replace(/<think>[\s\S]*?<\/think>/gi, ''));
}
});
setTimeout(UI.scrollToBottom, 200);
}
} catch(e) { console.warn("History sync failed."); }
},
save: () => {
if(!State.user) return;
fetch(Config.GAS_URL, {
method: 'POST', body: JSON.stringify({action: "save_history", email: State.user, historyJSON: JSON.stringify(State.history)})
}).catch(e=>{});
},
clearHistory: async () => {
if(State.user) {
try { await fetch(Config.GAS_URL, { method: 'POST', body: JSON.stringify({action: "delete_history", email: State.user}) }); } catch(e){}
}
location.reload();
}
};
// --- 4. FILE HANDLING (Secure) ---
const FileSys = {
process: async (input, type) => {
document.getElementById('attachMenu').classList.remove('active');
const file = input.files[0];
if (!file) return;
document.getElementById('filePreviewBar').classList.add('active');
document.getElementById('fileName').innerText = file.name;
const r = new FileReader();
if (type === 'image') {
document.getElementById('imgPreview').style.display = 'flex';
document.getElementById('docPreview').style.display = 'none';
r.onload = (e) => {
document.getElementById('imgPreview').src = e.target.result;
State.attachment = { type: 'image', data: e.target.result.split(',')[1], name: file.name };
};
r.readAsDataURL(file);
} else {
document.getElementById('imgPreview').style.display = 'none';
document.getElementById('docPreview').style.display = 'flex';
if (file.name.endsWith('.pdf')) {
r.onload = async function() {
const pdf = await pdfjsLib.getDocument(new Uint8Array(this.result)).promise;
let txt = "";
for(let i=1; i<=pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
txt += content.items.map(s => s.str).join(' ') + "\n";
}
State.attachment = { type: 'text', data: txt.trim() || "[No text found]", name: file.name };
};
r.readAsArrayBuffer(file);
} else if (file.name.endsWith('.docx')) {
r.onload = async function() {
const res = await mammoth.extractRawText({ arrayBuffer: this.result });
State.attachment = { type: 'text', data: res.value || "[No text found]", name: file.name };
};
r.readAsArrayBuffer(file);
} else {
r.onload = (e) => {
let data = e.target.result;
if(file.name.endsWith('.html')) {
data = data.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "\n[SCRIPT SANITIZED]\n");
}
State.attachment = { type: 'text', data: data, name: file.name };
};
r.readAsText(file);
}
}
input.value = '';
},
discard: () => {
State.attachment = null;
document.getElementById('filePreviewBar').classList.remove('active');
}
};
// --- 5. SPEECH TO TEXT ---
const Speech = {
rec: null, isRec: false,
init: () => {
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) return alert("Speech Recognition not supported in this browser.");
Speech.rec = new SR();
Speech.rec.continuous = false; Speech.rec.interimResults = true; Speech.rec.lang = 'en-US';
Speech.rec.onstart = () => { Speech.isRec = true; document.getElementById('btnStt').classList.add('recording'); };
Speech.rec.onresult = (e) => {
let trans = '';
for (let i = e.resultIndex; i < e.results.length; ++i) {
if (e.results[i].isFinal) trans += e.results[i][0].transcript;
}
if (trans) {
const input = document.getElementById('mainInput');
input.value += (input.value ? ' ' : '') + trans;
UI.autoGrow(input);
}
};
Speech.rec.onerror = () => Speech.stop();
Speech.rec.onend = () => Speech.stop();
},
toggle: () => {
if (!Speech.rec) Speech.init();
if (Speech.isRec) Speech.stop();
else { try { Speech.rec.start(); } catch(e) {} }
},
stop: () => {
if(Speech.rec) Speech.rec.stop();
Speech.isRec = false;
document.getElementById('btnStt').classList.remove('recording');
}
};
// --- 6. WORKSPACE PRO MAX ---
const Workspace = {
open: (id) => {
State.currentWs = id;
const d = State.workspaces[id];
const b = document.getElementById('wsCodeBlock');
b.className = `hljs language-${d.lang || 'plaintext'}`;
b.textContent = d.code;
hljs.highlightElement(b);
document.getElementById('workspacePanel').classList.add('active');
if(window.innerWidth <= 900) { document.getElementById('sidebar').classList.add('collapsed'); }
Workspace.switchTab('code');
},
close: () => document.getElementById('workspacePanel').classList.remove('active'),
switchTab: (tab) => {
document.getElementById('tabCode').classList.remove('active');
document.getElementById('tabPreview').classList.remove('active');
if(tab === 'code') {
document.getElementById('tabCode').classList.add('active');
document.getElementById('wsCodeView').style.display = 'block';
document.getElementById('wsPreviewView').style.display = 'none';
} else {
document.getElementById('tabPreview').classList.add('active');
document.getElementById('wsCodeView').style.display = 'none';
document.getElementById('wsPreviewView').style.display = 'flex';
Workspace.run();
}
},
copy: () => {
if(State.currentWs !== null) {
navigator.clipboard.writeText(State.workspaces[State.currentWs].code).then(() => alert('Code Copied!'));
}
},
run: async () => {
if (State.currentWs === null) return;
const d = State.workspaces[State.currentWs];
const lang = (d.lang || '').toLowerCase();
const view = document.getElementById('wsPreviewView');
if (['html', 'xml', 'javascript', 'js', 'css'].includes(lang)) {
let html = d.code;
if(lang === 'javascript' || lang === 'js') html = `<script>${d.code}<\/script>`;
if(lang === 'css') html = `<style>${d.code}</style><div style="padding:20px;font-family:sans-serif;">CSS Applied. Add HTML to test.</div>`;
const iframe = document.createElement('iframe');
iframe.style.width = '100%'; iframe.style.height = '100%'; iframe.style.border = 'none';
view.innerHTML = ''; view.appendChild(iframe);
iframe.contentWindow.document.open(); iframe.contentWindow.document.write(html); iframe.contentWindow.document.close();
return;
}
view.innerHTML = '<div class="ws-console" style="color:var(--brand-accent);">Compiling code...</div>';
const pMap = {'python':'python', 'py':'python', 'c':'c', 'cpp':'c++', 'java':'java'};
const rLang = pMap[lang];
if(!rLang) { view.innerHTML = `<div class="ws-console" style="color:#cc0000;">Live Preview not supported for ${lang}.</div>`; return; }
try {
const res = await fetch('https://emkc.org/api/v2/piston/execute', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ language: rLang, version: "*", files: [{ content: d.code }] })
});
const data = await res.json();
let out = (data.compile?.output || "") + "\n" + (data.run?.output || "") + "\n" + (data.run?.stderr || "");
view.innerHTML = `<div class="ws-console">${UI.escape(out.trim() || "[Execution Finished]")}</div>`;
} catch(e) { view.innerHTML = `<div class="ws-console" style="color:#cc0000;">Engine Error.</div>`; }
}
};
const MDRenderer = new marked.Renderer();
MDRenderer.code = function(code, lang) {
const id = State.workspaces.length;
State.workspaces.push({code: code, lang: lang});
const highlighted = hljs.highlightAuto(code).value;
return `
<div style="margin: 16px 0;">
<div style="background:#f6f8fa; border:1px solid var(--border-light); border-radius:8px 8px 0 0; padding:8px 12px; display:flex; justify-content:space-between; align-items:center;">
<span style="font-size:12px; font-family:var(--font-code); color:var(--text-secondary); font-weight:600;">${lang || 'code'}</span>
<button class="btn-launch-ws" style="margin:0; padding:4px 10px; font-size:12px;" onclick="Workspace.open(${id})">
<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> Open
</button>
</div>
<pre style="margin:0; border-top:none; border-radius:0 0 8px 8px;"><code class="hljs">${highlighted}</code></pre>
</div>`;
};
marked.use({ renderer: MDRenderer });
// --- 7. CHAT ENGINE (WITH SMART AUTO-SEARCH UI) ---
const Chat = {
renderUser: (txt, attachUI) => {
const c = document.getElementById('chatContainer');
const w = document.createElement('div'); w.className = 'message-wrapper user-message';
w.innerHTML = `${attachUI} ${UI.escape(txt)}`;
c.appendChild(w); UI.scrollToBottom();
},
renderBot: () => {
const c = document.getElementById('chatContainer');
const w = document.createElement('div'); w.className = 'message-wrapper bot-message';
w.innerHTML = `
<div class="bot-avatar"><img src="${Config.LOGO}"></div>
<div class="bot-content"><div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div></div></div>`;
c.appendChild(w); UI.scrollToBottom();
return w.querySelector('.bot-content');
},
getToolHTML: (isActive) => {
return `
<div class="tool-indicator ${isActive ? 'active' : ''}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
${isActive ? 'Searching on Google Search...' : 'Searched on Google Search'}
</div><br>`;
},
handleSend: async () => {
if(State.isProcessing) return;
const input = document.getElementById('mainInput');
const text = input.value.trim();
if(!text && !State.attachment) return;
// Guest Limit Validation
if(!State.user) {
if(State.guestCount >= 10) { Auth.openModal(); UI.showAuthMsg("Guest limit reached. Secure Login required.", true); return; }
State.guestCount++; localStorage.setItem('codeved_guest', State.guestCount);
document.getElementById('uSub').innerText = `Queries: ${State.guestCount}/10`;
}
State.isProcessing = true; document.getElementById('btnSend').disabled = true;
input.value = ''; input.style.height = 'auto'; document.getElementById('welcomeScreen').style.display = 'none';
let payloadStr = text;
let attachUI = '';
let mediaArray = [];
// 💡 SMART AUTO-SEARCH DETECTION
const queryLower = text.toLowerCase();
const searchTriggers = ["search", "latest", "news", "update", "current", "2026", "today", "price", "who won", "weather", "aaj"];
let isSearching = searchTriggers.some(kw => queryLower.includes(kw));
if(State.attachment) {
if(State.attachment.type === 'text') {
payloadStr = `[File attached: ${State.attachment.name}]\n\n---DATA---\n${State.attachment.data}\n---END DATA---\n\nUser: ${text}`;
attachUI = `<div class="chat-attachment"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path></svg> ${State.attachment.name}</div><br>`;
} else {
mediaArray.push(State.attachment);
attachUI = `<img src="data:image/jpeg;base64,${State.attachment.data}" class="chat-img-preview"><br>`;
}
}
Chat.renderUser(text || '[Attached Media]', attachUI);
// Update History UI
const shortText = (text || 'Media Attached').substring(0, 30) + "...";
document.getElementById('chatHistory').innerHTML += `<div class="history-item">${UI.escape(shortText)}</div>`;
State.history.push({ role: 'user', content: payloadStr });
FileSys.discard();
const botContent = Chat.renderBot();
// Show glowing tool UI immediately if it's a search
if (isSearching) {
botContent.innerHTML = Chat.getToolHTML(true) + `<div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div></div>`;
}
try {
// Call the Powerful Backend
const res = await fetch(Config.API_ENDPOINT, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: payloadStr,
attachments: mediaArray,
is_search: isSearching, // SIGNAL BACKEND TO SEARCH
history: State.history
})
});
if(!res.ok) throw new Error("API Offline");
const reader = res.body.getReader();
const decoder = new TextDecoder();
let fullText = "";
while(true) {
const {done, value} = await reader.read();
if(done) break;
const chunk = decoder.decode(value, {stream: true});
const lines = chunk.split('\n');
for(let line of lines) {
if(line.startsWith('data: ')) {
const dataStr = line.substring(6).trim();
if(dataStr === '[DONE]') continue;
try {
const json = JSON.parse(dataStr);
if(json.choices && json.choices[0].delta.content) fullText += json.choices[0].delta.content;
else if (json.response) fullText += json.response;
// Parse Markdown, Stop Tool animation if it was active
let finalHtml = marked.parse(fullText.replace(/<think>[\s\S]*?<\/think>/gi, ''));
if(isSearching) {
botContent.innerHTML = Chat.getToolHTML(false) + finalHtml;
} else {
botContent.innerHTML = finalHtml;
}
UI.scrollToBottom();
} catch(e){}
}
}
}
State.history.push({ role: 'assistant', content: fullText });
HistoryManager.save();
} catch(e) {
botContent.innerHTML = `<span style="color:#cc0000; font-weight:500;">Connection Offline. Backend is unreachable.</span>`;
} finally {
State.isProcessing = false;
document.getElementById('btnSend').disabled = false;
UI.scrollToBottom();
}
}
};
window.onload = () => {
Auth.init();
if(window.innerWidth > 900) { document.getElementById('btnMenuOpen').style.display = 'none'; document.getElementById('sidebar').classList.remove('collapsed'); }
};
</script>
</body>
</html>