Spaces:
Running
Running
| <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,'&').replace(/</g,'<').replace(/>/g,'>'), | |
| 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> |