| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>HyzeBot</title> |
| <link rel="icon" type="image/png" href="https://i.imgur.com/3TQgMBb.png"> |
| <link rel="apple-touch-icon" href="https://i.imgur.com/3TQgMBb.png"> |
| <meta name="theme-color" content="#151A28"> |
| <meta name="description" content="Hyze - Advanced AI Assistant"> |
| <meta name="description" content="HyzeBot - Advanced AI Assistant with vision, voice, and programming capabilities. Chat with AI, generate images, analyze pictures, and get coding help. Powered by HiteshAI technology."> |
| <meta name="keywords" content="Hyze, HyzeBot, HiteshAI, AI Assistant, AI Chatbot, Artificial Intelligence, AI Programming, AI Image Generation, AI Voice Assistant, AI Tutor"> |
| <meta name="author" content="Hyze Bot"> |
| <meta name="robots" content="index, follow"> |
| <meta property="og:title" content="HyzeBot - Advanced AI Assistant"> |
| <meta property="og:description" content="Chat with HyzeBot - an advanced AI assistant with vision, voice, and programming capabilities."> |
| <link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <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/11.1.1/marked.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/auto-render.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| |
| <script src="https://cdn.auth0.com/js/auth0-spa-js/2.0/auth0-spa-js.production.js"></script> |
| <style> |
| |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| |
| :root { |
| --bg-primary: #151A28; |
| --bg-secondary: #161c2d; |
| --bg-tertiary: #161c2e; |
| --text-primary: #ffffff; |
| --text-secondary: #cccccc; |
| --border-color: rgba(255, 255, 255, 0.085); |
| --accent-color: #577db7; |
| --button-bg: #161c2d; |
| --button-hover: #3f4266; |
| --sidebar-bg: #161c2d; |
| --message-user-bg: #1d2335; |
| --message-bot-bg: #1d2335; |
| --shadow-color: rgba(255, 255, 255, 0.1); |
| --logo-sidebar: url('https://i.imgur.com/3TQgMBb.png'); |
| --logo-main: url('https://i.imgur.com/24wAsac.png'); |
| --typing-ring-color: #ffffff; |
| --pro-gradient: linear-gradient(135deg, #577db7 0%, #2C64BA 100%); |
| --pro-success: #40c057; |
| --pro-error: #fa5252; |
| } |
| |
| .light-mode { |
| --bg-primary: #DCF3FF; |
| --bg-secondary: #DCF3FF; |
| --bg-tertiary: #DCF3FF; |
| --bg-primary-rgb: 220, 243, 255; |
| --text-primary: #2C64BA; |
| --text-secondary: #255092; |
| --border-color: #2c65ba53; |
| --accent-color: #2C64BA; |
| --button-bg: #DCF3FF; |
| --button-hover: #e4e4e4; |
| --sidebar-bg: #DCF3FF; |
| --message-user-bg: #c3ddeb; |
| --message-bot-bg: #c3ddeb; |
| --shadow-color: rgba(0, 0, 0, 0.1); |
| --logo-sidebar: url('https://i.imgur.com/pI3EI5a.png'); |
| --logo-main: url('https://i.imgur.com/BoFwrb2.png'); |
| --logo-loading-dark: url('https://i.imgur.com/GQ3Bx3u.png'); |
| --logo-loading-light: url('https://i.imgur.com/3TQgMBb.png'); |
| --pro-gradient: linear-gradient(135deg, #4dabf7 0%, #339af0 100%); |
| --pro-success: #40c057; |
| --pro-error: #fa5252; |
| } |
| |
| body { |
| font-family: 'Lexend', sans-serif; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| height: 100vh; |
| display: flex; |
| overflow: hidden; |
| position: relative; |
| } |
| |
| .sidebar { |
| width: 60px; |
| background: var(--sidebar-bg); |
| backdrop-filter: blur(20px); |
| border-right: 1px solid var(--border-color); |
| display: flex; |
| flex-direction: column; |
| z-index: 10; |
| transition: width 0.3s ease, background 0.3s ease; |
| overflow: hidden; |
| flex-shrink: 0; |
| } |
| |
| .sidebar.expanded { |
| width: 260px; |
| } |
| |
| .sidebar-header { |
| padding: 12px; |
| border-bottom: 1px solid var(--border-color); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| background: var(--sidebar-bg); |
| cursor: pointer; |
| } |
| |
| .sidebar-header:hover { |
| background: var(--bg-secondary); |
| } |
| |
| .sidebar-logo { |
| width: 40px; |
| height: 40px; |
| border-radius: 8px; |
| overflow: hidden; |
| flex-shrink: 0; |
| transition: all 0.3s; |
| } |
| |
| .sidebar-logo img { |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| } |
| |
| .sidebar-actions { |
| flex: 1; |
| padding: 12px 6px; |
| display: flex; |
| flex-direction: column; |
| gap: 4px; |
| overflow-y: auto; |
| } |
| |
| .sidebar.expanded .sidebar-actions { |
| padding: 12px; |
| } |
| |
| .sidebar-btn { |
| display: flex; |
| align-items: center; |
| gap: 0; |
| padding: 8px; |
| background: var(--button-bg); |
| border: 1px solid transparent; |
| border-radius: 8px; |
| cursor: pointer; |
| font-size: 14px; |
| transition: all 0.3s; |
| color: var(--text-primary); |
| font-family: inherit; |
| justify-content: center; |
| min-height: 32px; |
| } |
| |
| .sidebar.expanded .sidebar-btn { |
| gap: 10px; |
| justify-content: flex-start; |
| padding: 8px 14px; |
| font-size: 13px; |
| } |
| |
| .sidebar-btn:hover { |
| background: var(--button-hover); |
| border-color: var(--border-color); |
| transform: translateX(4px); |
| } |
| |
| .sidebar-btn.active { |
| background: var(--bg-tertiary); |
| border-color: var(--accent-color); |
| } |
| |
| .sidebar-btn-icon { |
| font-size: 14px; |
| width: 20px; |
| text-align: center; |
| flex-shrink: 0; |
| } |
| |
| .sidebar-btn-text { |
| display: none; |
| white-space: nowrap; |
| opacity: 0; |
| transition: opacity 0.3s; |
| font-weight: 500; |
| } |
| |
| .sidebar.expanded .sidebar-btn-text { |
| display: block; |
| opacity: 1; |
| } |
| |
| .sidebar-footer { |
| padding: 12px; |
| border-top: 1px solid var(--border-color); |
| display: flex; |
| justify-content: flex-end; |
| } |
| |
| .sidebar:not(.expanded) .sidebar-footer { |
| justify-content: center; |
| } |
| |
| .toggle-btn { |
| width: 40px; |
| height: 40px; |
| border: none; |
| background: var(--button-bg); |
| border-radius: 50%; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 16px; |
| color: var(--text-primary); |
| transition: all 0.3s; |
| box-shadow: 0 4px 12px var(--shadow-color); |
| } |
| |
| .toggle-btn:hover { |
| transform: scale(1.1) translateY(-2px); |
| box-shadow: 0 6px 16px var(--shadow-color); |
| } |
| |
| |
| .main-container { |
| flex: 1; |
| display: flex; |
| overflow: hidden; |
| } |
| |
| .chat-section { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| position: relative; |
| overflow: hidden; |
| border-right: 1px solid var(--border-color); |
| transition: all 0.3s ease; |
| } |
| |
| .program-section { |
| width: 0; |
| display: none; |
| flex-direction: column; |
| background: var(--bg-secondary); |
| overflow: hidden; |
| transition: width 0.3s ease; |
| } |
| |
| .program-section.active { |
| width: 50%; |
| display: flex; |
| } |
| |
| .program-header { |
| padding: 16px; |
| border-bottom: 1px solid var(--border-color); |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| background: var(--bg-tertiary); |
| } |
| |
| .program-title { |
| font-weight: 600; |
| font-size: 16px; |
| color: var(--text-primary); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .program-actions { |
| display: flex; |
| gap: 8px; |
| } |
| |
| .program-btn { |
| background: var(--button-bg); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| padding: 6px 12px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 12px; |
| transition: all 0.3s; |
| } |
| |
| .program-btn:hover { |
| background: var(--button-hover); |
| } |
| |
| .program-iframe { |
| flex: 1; |
| width: 100%; |
| border: none; |
| background: white; |
| } |
| |
| .program-loading { |
| flex: 1; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| background: var(--bg-primary); |
| color: var(--text-secondary); |
| font-size: 14px; |
| } |
| |
| .center-container { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| position: relative; |
| z-index: 1; |
| overflow: hidden; |
| transition: all 0.3s ease; |
| padding: 20px; |
| } |
| |
| .logo-container { |
| transition: all 0.5s ease; |
| z-index: 1; |
| margin-bottom: 30px; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .logo-container.hidden { |
| display: none !important; |
| } |
| |
| .logo-main { |
| width: 380px; |
| height: 140px; |
| max-width: 70vw; |
| max-height: 20vh; |
| object-fit: contain; |
| } |
| |
| .chat-messages { |
| width: 100%; |
| max-width: 925px; |
| overflow-y: auto; |
| padding: 28px; |
| display: none; |
| flex: 1; |
| margin: 0 auto; |
| scroll-behavior: smooth; |
| padding-bottom: 200px; |
| } |
| |
| .chat-messages.active { |
| display: block; |
| } |
| |
| .message { |
| margin-bottom: 20px; |
| display: flex; |
| animation: fadeIn 0.5s ease-in; |
| scroll-margin-top: 40vh; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(20px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .message.user { justify-content: flex-end; } |
| |
| .message-content { |
| max-width: 78%; |
| padding: 16px 20px; |
| border-radius: 14px; |
| line-height: 1.7; |
| font-size: 15px; |
| backdrop-filter: blur(10px); |
| border: 1px solid var(--border-color); |
| box-shadow: 0 4px 16px var(--shadow-color); |
| } |
| |
| |
| .message.bot .message-content { |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| border: none; |
| box-shadow: none; |
| } |
| |
| .message.user .message-content { |
| background: var(--message-user-bg); |
| color: var(--text-primary); |
| border: 1px solid var(--border-color); |
| } |
| |
| |
| .weather-widget { |
| max-width: 100%; |
| margin: 10px 0; |
| } |
| |
| .weather-card { |
| width: 100%; |
| border-radius: 16px; |
| padding: 18px; |
| box-shadow: 0 10px 40px var(--shadow-color); |
| transition: all 0.3s ease; |
| font-family: 'Lexend', sans-serif; |
| color: white !important; |
| } |
| |
| .weather-card.sunny { |
| background: linear-gradient(135deg, #E8A87C 0%, #D4806A 100%); |
| color: white !important; |
| } |
| |
| .weather-card.rainy { |
| background: linear-gradient(135deg, #6B9BD1 0%, #5AAFCA 100%); |
| color: white !important; |
| } |
| |
| .weather-card.snowy { |
| background: linear-gradient(135deg, #9BB5D4 0%, #B5D4E8 100%); |
| color: white !important; |
| } |
| |
| .weather-location { |
| font-size: 18px; |
| font-weight: 600; |
| margin-bottom: 12px; |
| color: white !important; |
| } |
| |
| .current-weather { |
| display: flex; |
| align-items: center; |
| margin-bottom: 12px; |
| gap: 10px; |
| } |
| |
| .weather-icon { |
| font-size: 40px; |
| } |
| |
| .temperature { |
| font-size: 42px; |
| font-weight: 300; |
| line-height: 1; |
| color: white !important; |
| } |
| |
| .temp-unit { |
| font-size: 16px; |
| vertical-align: top; |
| color: white !important; |
| } |
| |
| .temp-range { |
| margin-top: 6px; |
| font-size: 12px; |
| opacity: 0.9; |
| color: white !important; |
| } |
| |
| .temp-range span { |
| margin-right: 15px; |
| color: white !important; |
| } |
| |
| .unit-toggle { |
| display: inline-flex; |
| background: rgba(255, 255, 255, 0.2); |
| border-radius: 6px; |
| padding: 3px; |
| margin-left: 8px; |
| cursor: pointer; |
| font-size: 11px; |
| font-weight: 600; |
| color: white !important; |
| } |
| |
| .unit-toggle span { |
| padding: 3px 6px; |
| border-radius: 4px; |
| transition: all 0.2s; |
| margin-right: 0; |
| color: white !important; |
| } |
| |
| .unit-toggle span.active { |
| background: rgba(255, 255, 255, 0.3); |
| color: white !important; |
| } |
| |
| .weather-description { |
| font-size: 13px; |
| margin-bottom: 0; |
| opacity: 0.95; |
| color: white !important; |
| } |
| |
| .message-content pre { |
| background: var(--bg-secondary); |
| border-radius: 8px; |
| padding: 12px; |
| overflow-x: auto; |
| overflow-y: hidden; |
| margin: 10px 0; |
| max-width: 100%; |
| box-sizing: border-box; |
| white-space: pre; |
| position: relative; |
| } |
| |
| .message-content code { |
| font-family: 'Courier New', monospace; |
| font-size: 13px; |
| max-width: 100%; |
| box-sizing: border-box; |
| } |
| |
| .message-content pre code { |
| background: transparent; |
| padding: 0; |
| display: block; |
| overflow-x: auto; |
| white-space: pre; |
| word-wrap: normal; |
| word-break: normal; |
| } |
| |
| .message-content :not(pre) > code { |
| background: var(--button-bg); |
| padding: 2px 6px; |
| border-radius: 4px; |
| font-size: 13px; |
| } |
| |
| |
| .message-actions { |
| display: flex; |
| gap: 6px; |
| margin-top: 8px; |
| flex-wrap: wrap; |
| align-items: center; |
| } |
| |
| |
| .message.user .message-actions { |
| justify-content: flex-end; |
| } |
| |
| |
| .message.bot .message-actions { |
| justify-content: flex-start; |
| } |
| |
| .reaction-btn, .action-btn { |
| background: var(--button-bg); |
| border: 1px solid var(--border-color); |
| padding: 4px 8px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 13px; |
| transition: all 0.3s; |
| color: var(--text-primary); |
| } |
| |
| .reaction-btn:hover, .action-btn:hover { |
| background: var(--button-hover); |
| transform: scale(1.05); |
| } |
| |
| .reaction-btn.active, .rating-btn.active { |
| background: var(--accent-color); |
| border-color: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| |
| .message.user .reaction-btn { |
| display: none !important; |
| } |
| |
| .typing-indicator { |
| display: none; |
| position: absolute; |
| bottom: 140px; |
| left: 50%; |
| transform: translateX(-50%); |
| z-index: 100; |
| } |
| |
| .typing-indicator.active { |
| display: block; |
| } |
| |
| .logo-container-loader { |
| position: relative; |
| width: 50px; |
| height: 50px; |
| filter: drop-shadow(0 0 20px var(--shadow-color)); |
| } |
| |
| .logo-loader { |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| position: relative; |
| z-index: 2; |
| animation: kineticSpin 2s cubic-bezier(0.4, 0, 0.2, 1) infinite; |
| } |
| |
| .logo-container-loader::before { |
| content: ''; |
| position: absolute; |
| top: -15px; |
| left: -15px; |
| right: -15px; |
| bottom: -15px; |
| border: 2px solid transparent; |
| border-top-color: var(--typing-ring-color); |
| border-right-color: var(--typing-ring-color); |
| border-radius: 50%; |
| animation: ringRotate 1.5s linear infinite, ringGlow 1.5s ease-in-out infinite; |
| z-index: 1; |
| } |
| |
| .logo-container-loader::after { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: var(--logo-main); |
| background-size: contain; |
| background-position: center; |
| background-repeat: no-repeat; |
| animation: kineticSpin 2s cubic-bezier(0.4, 0, 0.2, 1) infinite; |
| filter: blur(6px) brightness(1.5); |
| opacity: 0.4; |
| z-index: -1; |
| animation-delay: -0.05s; |
| } |
| |
| @keyframes kineticSpin { |
| 0% { |
| transform: rotateY(0deg) scale(1); |
| filter: brightness(1) contrast(1); |
| } |
| 25% { |
| transform: rotateY(90deg) scale(1.05); |
| filter: brightness(1.1) contrast(1.05); |
| } |
| 50% { |
| transform: rotateY(180deg) scale(1); |
| filter: brightness(1) contrast(1); |
| } |
| 75% { |
| transform: rotateY(270deg) scale(1.05); |
| filter: brightness(1.1) contrast(1.05); |
| } |
| 100% { |
| transform: rotateY(360deg) scale(1); |
| filter: brightness(1) contrast(1); |
| } |
| } |
| |
| @keyframes ringRotate { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| @keyframes ringGlow { |
| 0%, 100% { |
| box-shadow: 0 0 15px var(--typing-ring-color); |
| } |
| 50% { |
| box-shadow: 0 0 30px var(--typing-ring-color); |
| } |
| } |
| |
| @media (max-width: 768px) { |
| .logo-container-loader { |
| width: 40px; |
| height: 40px; |
| } |
| } |
| |
| .chat-input-container { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| width: 925px; |
| max-width: 90vw; |
| padding: 24px; |
| z-index: 100; |
| transition: all 0.5s ease; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 20px; |
| } |
| |
| .chat-input-container.bottom-position { |
| position: absolute; |
| bottom: 0; |
| top: auto; |
| transform: translateX(-50%); |
| left: 50%; |
| padding: 20px; |
| } |
| |
| .chat-input-container.centered { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| bottom: auto; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 30px; |
| padding: 0; |
| } |
| |
| .chat-input-wrapper { |
| display: flex; |
| gap: 12px; |
| width: 100%; |
| align-items: flex-end; |
| background: var(--bg-secondary); |
| backdrop-filter: blur(10px); |
| border: 1px solid var(--border-color); |
| border-radius: 16px; |
| padding: 14px 18px; |
| transition: all 0.3s; |
| margin: 0 auto; |
| } |
| |
| .chat-input-wrapper:focus-within { |
| border-color: var(--accent-color); |
| box-shadow: 0 0 28px var(--shadow-color); |
| } |
| |
| |
| .chat-input-wrapper .image-preview-mini { |
| display: none; |
| width: 40px; |
| height: 40px; |
| border-radius: 8px; |
| overflow: hidden; |
| flex-shrink: 0; |
| position: relative; |
| border: 2px solid var(--accent-color); |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .chat-input-wrapper .image-preview-mini.active { |
| display: block; |
| } |
| |
| .chat-input-wrapper .image-preview-mini:hover { |
| transform: scale(1.1); |
| box-shadow: 0 4px 12px var(--shadow-color); |
| } |
| |
| .chat-input-wrapper .image-preview-mini img { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| } |
| |
| .chat-input-wrapper .image-preview-mini .remove-mini-btn { |
| position: absolute; |
| top: -8px; |
| right: -8px; |
| width: 20px; |
| height: 20px; |
| background: #ff4444; |
| border: 2px solid var(--bg-primary); |
| border-radius: 50%; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 10px; |
| color: white; |
| transition: all 0.3s; |
| z-index: 10; |
| } |
| |
| .chat-input-wrapper .image-preview-mini .remove-mini-btn:hover { |
| transform: scale(1.2); |
| } |
| |
| .chat-input { |
| flex: 1; |
| padding: 10px; |
| border: none; |
| font-size: 16px; |
| outline: none; |
| font-family: inherit; |
| background: transparent; |
| color: var(--text-primary); |
| resize: none; |
| max-height: 120px; |
| min-height: 24px; |
| line-height: 1.5; |
| } |
| |
| .chat-input::placeholder { |
| color: var(--text-secondary); |
| font-size: 14px; |
| } |
| |
| .send-button { |
| padding: 10px 14px; |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| border-radius: 12px; |
| font-size: 18px; |
| cursor: pointer; |
| transition: all 0.3s; |
| min-width: 42px; |
| font-weight: 600; |
| box-shadow: 0 4px 15px var(--shadow-color); |
| } |
| |
| .send-button:hover:not(:disabled) { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 20px var(--shadow-color); |
| } |
| |
| .send-button:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| } |
| |
| .stop-button { |
| padding: 10px 14px; |
| background: #ff4444; |
| color: var(--bg-primary); |
| border: none; |
| border-radius: 12px; |
| font-size: 18px; |
| cursor: pointer; |
| transition: all 0.3s; |
| min-width: 42px; |
| font-weight: 600; |
| box-shadow: 0 4px 15px rgba(255, 68, 68, 0.4); |
| display: none; |
| } |
| |
| .stop-button:hover:not(:disabled) { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 20px rgba(255, 68, 68, 0.6); |
| } |
| |
| .stop-button.active { |
| display: block; |
| } |
| |
| |
| .square-image-loader { |
| display: none; |
| width: 100%; |
| max-width: 500px; |
| margin: 20px auto; |
| position: relative; |
| } |
| |
| .square-image-loader.active { |
| display: block; |
| } |
| |
| .image-loader-container { |
| width: 380px; |
| height: 380px; |
| margin: 0 auto; |
| background: linear-gradient( |
| 90deg, |
| #0f172a 0%, |
| #1e3a5f 50%, |
| #0f172a 100% |
| ); |
| background-size: 200% 100%; |
| border-radius: 12px; |
| animation: shimmer 2s infinite; |
| position: relative; |
| overflow: hidden; |
| border: 1px solid rgba(46, 103, 186, 0.12); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .image-loader-container::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: linear-gradient( |
| 90deg, |
| transparent 0%, |
| rgba(46, 103, 186, 0.3) 50%, |
| transparent 100% |
| ); |
| animation: shimmer 1.5s infinite; |
| } |
| |
| .image-loader-icon { |
| position: relative; |
| z-index: 2; |
| width: 64px; |
| height: 64px; |
| opacity: 0.5; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .image-loader-icon svg { |
| width: 100%; |
| height: 100%; |
| fill: #2E67BA; |
| } |
| |
| .image-loader-text { |
| text-align: center; |
| margin-top: 16px; |
| font-size: 15px; |
| color: var(--text-secondary); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| } |
| |
| @keyframes shimmer { |
| 0% { background-position: -200% 0; } |
| 100% { background-position: 200% 0; } |
| } |
| |
| |
| .generated-image-message { |
| background: var(--bg-secondary); |
| border-radius: 16px; |
| padding: 16px; |
| margin: 10px 0; |
| max-width: 400px; |
| border: none; |
| } |
| |
| .generated-image-container { |
| width: 100%; |
| aspect-ratio: 1; |
| border-radius: 12px; |
| overflow: hidden; |
| background: var(--bg-primary); |
| margin-bottom: 12px; |
| position: relative; |
| } |
| |
| .generated-image-container img { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| display: block; |
| } |
| |
| .generated-image-actions { |
| display: flex; |
| gap: 8px; |
| justify-content: flex-end; |
| } |
| |
| .download-image-btn { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| padding: 8px 16px; |
| border-radius: 8px; |
| cursor: pointer; |
| font-size: 13px; |
| font-weight: 600; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .download-image-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 12px var(--shadow-color); |
| } |
| |
| |
| .upload-btn, .quick-image-btn, .voice-input-btn { |
| background: var(--button-bg); |
| border: none; |
| border-radius: 12px; |
| padding: 10px; |
| cursor: pointer; |
| margin-right: 8px; |
| color: var(--text-primary); |
| transition: all 0.3s; |
| position: relative; |
| min-width: 40px; |
| height: 40px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .upload-btn:hover, .voice-input-btn:hover { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| .quick-image-btn:hover { |
| background: linear-gradient(135deg, #4dabf7 0%, #339af0 100%); |
| color: white; |
| transform: translateY(-2px); |
| box-shadow: 0 4px 12px rgba(77, 171, 247, 0.4); |
| } |
| |
| .quick-image-btn.active { |
| background: linear-gradient(135deg, #4dabf7 0%, #339af0 100%); |
| color: white; |
| box-shadow: 0 0 15px rgba(77, 171, 247, 0.6); |
| transform: scale(1.05); |
| } |
| |
| |
| .agent-item { |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 16px; |
| margin-bottom: 12px; |
| transition: all 0.3s; |
| } |
| |
| .agent-item:hover { |
| background: var(--bg-tertiary); |
| transform: translateY(-2px); |
| } |
| |
| .agent-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| margin-bottom: 12px; |
| } |
| |
| .agent-name { |
| font-weight: 600; |
| font-size: 16px; |
| color: var(--text-primary); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .agent-type { |
| font-size: 11px; |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| padding: 2px 6px; |
| border-radius: 10px; |
| } |
| |
| .agent-actions { |
| display: flex; |
| gap: 6px; |
| } |
| |
| .agent-btn { |
| background: var(--button-bg); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| padding: 4px 10px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 12px; |
| transition: all 0.3s; |
| } |
| |
| .agent-btn:hover { |
| background: var(--accent-color); |
| border-color: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| .agent-description { |
| font-size: 13px; |
| color: var(--text-secondary); |
| margin-bottom: 12px; |
| line-height: 1.5; |
| } |
| |
| .agent-training { |
| background: var(--bg-secondary); |
| border-radius: 8px; |
| padding: 10px; |
| margin-top: 8px; |
| font-size: 12px; |
| color: var(--text-secondary); |
| max-height: 100px; |
| overflow-y: auto; |
| } |
| |
| .agent-training-title { |
| font-weight: 600; |
| margin-bottom: 4px; |
| color: var(--text-primary); |
| font-size: 12px; |
| } |
| |
| .premade-agent { |
| border-left: 4px solid #40c057; |
| } |
| |
| .custom-agent { |
| border-left: 4px solid #4dabf7; |
| } |
| |
| .add-agent-btn { |
| width: 100%; |
| padding: 12px; |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| margin-bottom: 16px; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| } |
| |
| .add-agent-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 15px var(--shadow-color); |
| } |
| |
| .agent-form-group { |
| margin-bottom: 16px; |
| } |
| |
| .agent-form-group label { |
| display: block; |
| margin-bottom: 6px; |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| .agent-form-group textarea { |
| width: 100%; |
| padding: 10px; |
| border: 1px solid var(--border-color); |
| border-radius: 8px; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| font-family: inherit; |
| font-size: 13px; |
| resize: vertical; |
| min-height: 80px; |
| } |
| |
| .agent-form-group select { |
| width: 100%; |
| padding: 10px; |
| border: 1px solid var(--border-color); |
| border-radius: 8px; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| font-family: inherit; |
| font-size: 13px; |
| } |
| |
| .agent-active-badge { |
| background: #40c057; |
| color: white; |
| padding: 2px 8px; |
| border-radius: 10px; |
| font-size: 11px; |
| font-weight: 600; |
| margin-left: 8px; |
| } |
| |
| .modal { |
| display: none; |
| position: fixed; |
| z-index: 1000; |
| left: 0; |
| top: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(var(--bg-primary-rgb), 0.7); |
| backdrop-filter: blur(5px); |
| } |
| |
| .modal.active { display: flex; align-items: center; justify-content: center; } |
| |
| .modal-content { |
| background: var(--bg-secondary); |
| backdrop-filter: blur(20px); |
| border-radius: 18px; |
| padding: 28px; |
| max-width: 600px; |
| width: 90%; |
| max-height: 85vh; |
| overflow-y: auto; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 8px 32px var(--shadow-color); |
| } |
| |
| .modal-content h2 { |
| margin-bottom: 20px; |
| font-size: 22px; |
| color: var(--text-primary); |
| } |
| |
| .modal-content textarea, .modal-content input[type="text"], .modal-content input[type="password"], .modal-content input[type="file"], .modal-content input[type="url"], .modal-content select { |
| width: 100%; |
| padding: 12px; |
| border: 1px solid var(--border-color); |
| border-radius: 10px; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| font-family: inherit; |
| font-size: 14px; |
| outline: none; |
| margin-bottom: 12px; |
| transition: all 0.3s; |
| } |
| |
| .modal-content textarea:focus, .modal-content input[type="text"]:focus, .modal-content input[type="password"]:focus, .modal-content input[type="url"]:focus, .modal-content select:focus { |
| border-color: var(--accent-color); |
| box-shadow: 0 0 10px var(--shadow-color); |
| } |
| |
| .modal-content textarea { |
| resize: vertical; |
| min-height: 100px; |
| } |
| |
| .modal-actions { |
| display: flex; |
| gap: 12px; |
| margin-top: 20px; |
| justify-content: flex-end; |
| } |
| |
| .modal-btn { |
| padding: 10px 20px; |
| border: none; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| transition: all 0.3s; |
| font-family: inherit; |
| } |
| |
| .modal-btn-primary { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| .modal-btn-secondary { |
| background: var(--button-bg); |
| color: var(--text-primary); |
| } |
| |
| .modal-btn-danger { |
| background: #ff4444; |
| color: white; |
| } |
| |
| .modal-btn:hover { |
| transform: translateY(-2px); |
| } |
| |
| .chat-item { |
| background: var(--bg-primary); |
| padding: 12px; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.3s; |
| border: 1px solid var(--border-color); |
| margin-bottom: 8px; |
| position: relative; |
| } |
| |
| .chat-item:hover { |
| background: var(--bg-tertiary); |
| transform: translateX(4px); |
| } |
| |
| .chat-item-actions { |
| position: absolute; |
| right: 8px; |
| top: 50%; |
| transform: translateY(-50%); |
| display: flex; |
| gap: 4px; |
| opacity: 0; |
| transition: opacity 0.3s; |
| } |
| |
| .chat-item:hover .chat-item-actions { |
| opacity: 1; |
| } |
| |
| .chat-item-action { |
| background: var(--button-bg); |
| border: none; |
| padding: 4px 8px; |
| border-radius: 4px; |
| cursor: pointer; |
| font-size: 12px; |
| color: var(--text-primary); |
| transition: all 0.3s; |
| } |
| |
| .chat-item-action:hover { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| transform: scale(1.1); |
| } |
| |
| .welcome-hidden { |
| display: none !important; |
| } |
| |
| .web-search-active { |
| background: var(--accent-color) !important; |
| border-color: var(--accent-color) !important; |
| color: var(--bg-primary) !important; |
| } |
| |
| .rating-container { |
| display: none !important; |
| } |
| |
| @keyframes slideIn { |
| from { transform: translateX(100%); opacity: 0; } |
| to { transform: translateX(0); opacity: 1; } |
| } |
| |
| @keyframes slideOut { |
| from { transform: translateX(0); opacity: 1; } |
| to { transform: translateX(100%); opacity: 0; } |
| } |
| |
| .form-group { |
| margin-bottom: 16px; |
| } |
| |
| .form-group label { |
| display: block; |
| margin-bottom: 6px; |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| .auth-container { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: var(--bg-primary); |
| display: none; |
| align-items: center; |
| justify-content: center; |
| z-index: 10000; |
| } |
| |
| .auth-container.active { |
| display: flex; |
| } |
| |
| .auth-box { |
| background: var(--bg-secondary); |
| backdrop-filter: blur(20px); |
| border-radius: 18px; |
| padding: 40px; |
| max-width: 420px; |
| width: 90%; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 8px 32px var(--shadow-color); |
| } |
| |
| .auth-box h1 { |
| text-align: center; |
| margin-bottom: 30px; |
| font-size: 28px; |
| color: var(--text-primary); |
| } |
| |
| .auth-toggle { |
| text-align: center; |
| margin-top: 20px; |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| .auth-toggle a { |
| color: var(--text-primary); |
| text-decoration: none; |
| cursor: pointer; |
| font-weight: 600; |
| } |
| |
| .auth-toggle a:hover { |
| text-decoration: underline; |
| } |
| |
| |
| .divider { |
| display: flex; |
| align-items: center; |
| margin: 24px 0; |
| color: var(--text-secondary); |
| font-size: 13px; |
| } |
| |
| .divider::before, |
| .divider::after { |
| content: ''; |
| flex: 1; |
| height: 1px; |
| background: var(--border-color); |
| margin: 0 12px; |
| } |
| |
| .google-btn { |
| width: 100%; |
| padding: 12px 16px; |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 500; |
| color: var(--text-primary); |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 10px; |
| font-family: inherit; |
| } |
| |
| .google-btn:hover { |
| background: var(--button-hover); |
| border-color: var(--accent-color); |
| transform: translateY(-1px); |
| box-shadow: 0 4px 12px var(--shadow-color); |
| } |
| |
| .google-btn:active { |
| transform: translateY(0); |
| } |
| |
| .google-btn i { |
| color: #4285F4; |
| font-size: 18px; |
| } |
| |
| .chart-container { |
| width: 100%; |
| height: 300px; |
| margin-top: 20px; |
| } |
| |
| .file-upload-area { |
| border: 2px dashed var(--border-color); |
| border-radius: 10px; |
| padding: 30px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .file-upload-area:hover { |
| border-color: var(--accent-color); |
| background: var(--bg-primary); |
| } |
| |
| .file-upload-area i { |
| font-size: 48px; |
| color: var(--text-secondary); |
| margin-bottom: 10px; |
| } |
| |
| .uploaded-file { |
| background: var(--bg-tertiary); |
| padding: 10px; |
| border-radius: 8px; |
| margin-top: 10px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| } |
| |
| .memory-item { |
| background: var(--bg-primary); |
| padding: 12px; |
| border-radius: 8px; |
| margin-bottom: 8px; |
| border-left: 3px solid var(--accent-color); |
| } |
| |
| .memory-item strong { |
| color: var(--accent-color); |
| } |
| |
| .notification { |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| padding: 14px 20px; |
| border-radius: 10px; |
| font-size: 13px; |
| font-weight: 600; |
| z-index: 10000; |
| animation: slideIn 0.3s ease-out; |
| box-shadow: 0 4px 20px var(--shadow-color); |
| } |
| |
| .auth-input { |
| width: 100%; |
| padding: 14px 16px; |
| border: 1px solid var(--border-color); |
| border-radius: 10px; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| font-family: inherit; |
| font-size: 15px; |
| outline: none; |
| margin-bottom: 16px; |
| transition: all 0.3s; |
| } |
| |
| .auth-input:focus { |
| border-color: var(--accent-color); |
| box-shadow: 0 0 15px var(--shadow-color); |
| background: var(--bg-secondary); |
| } |
| |
| .auth-input::placeholder { |
| color: var(--text-secondary); |
| } |
| |
| .chat-messages::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| .chat-messages::-webkit-scrollbar-track { |
| background: var(--bg-secondary); |
| border-radius: 4px; |
| } |
| |
| .chat-messages::-webkit-scrollbar-thumb { |
| background: var(--accent-color); |
| border-radius: 4px; |
| } |
| |
| .message-image-text { |
| background: var(--bg-tertiary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 12px 16px; |
| margin-bottom: 10px; |
| font-size: 14px; |
| color: var(--text-secondary); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| max-width: 300px; |
| } |
| |
| .message.user .message-image-text { |
| align-self: flex-end; |
| } |
| |
| .voice-status { |
| position: fixed; |
| top: 20px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: var(--bg-secondary); |
| backdrop-filter: blur(20px); |
| padding: 12px 24px; |
| border-radius: 10px; |
| font-size: 14px; |
| z-index: 9999; |
| display: none; |
| align-items: center; |
| gap: 10px; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 4px 20px var(--shadow-color); |
| color: var(--text-primary); |
| } |
| |
| .voice-status.active { |
| display: flex; |
| } |
| |
| .voice-status .voice-pulse { |
| width: 10px; |
| height: 10px; |
| background: var(--accent-color); |
| border-radius: 50%; |
| animation: voicePulse 1.5s ease-in-out infinite; |
| } |
| |
| @keyframes voicePulse { |
| 0%, 100% { transform: scale(1); opacity: 1; } |
| 50% { transform: scale(1.3); opacity: 0.7; } |
| } |
| |
| .voice-input-btn.active { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| animation: voicePulse 1.5s ease-in-out infinite; |
| } |
| |
| .voice-input-btn.listening { |
| background: #ff4444; |
| color: white; |
| animation: voiceListening 1s ease-in-out infinite; |
| } |
| |
| @keyframes voiceListening { |
| 0%, 100% { transform: scale(1); box-shadow: 0 0 15px rgba(255, 68, 68, 0.3); } |
| 50% { transform: scale(1.05); box-shadow: 0 0 25px rgba(255, 68, 68, 0.6); } |
| } |
| |
| .auto-send-indicator { |
| position: absolute; |
| top: -30px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| padding: 6px 12px; |
| border-radius: 6px; |
| font-size: 12px; |
| font-weight: 600; |
| opacity: 0; |
| transition: opacity 0.3s; |
| pointer-events: none; |
| } |
| |
| .auto-send-indicator.show { |
| opacity: 1; |
| } |
| |
| .voice-option { |
| background: var(--bg-primary); |
| padding: 12px; |
| border-radius: 8px; |
| margin-bottom: 8px; |
| cursor: pointer; |
| transition: all 0.3s; |
| border: 1px solid var(--border-color); |
| } |
| |
| .voice-option:hover { |
| background: var(--bg-tertiary); |
| transform: translateX(4px); |
| } |
| |
| .voice-option.selected { |
| background: var(--accent-color); |
| border-color: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| .voice-test-btn { |
| margin-top: 10px; |
| width: 100%; |
| } |
| |
| @media (max-width: 768px) { |
| .sidebar { |
| width: 50px; |
| } |
| .sidebar.expanded { |
| width: 200px; |
| } |
| .chat-input-container { |
| width: 95vw; |
| } |
| .logo-main { |
| max-width: 80vw; |
| max-height: 15vh; |
| } |
| .logo-container-loader { |
| width: 40px; |
| height: 40px; |
| } |
| .image-loader-container { |
| width: 300px; |
| height: 300px; |
| } |
| .program-section.active { |
| width: 100%; |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| z-index: 1000; |
| } |
| .chat-input-wrapper .image-preview-mini { |
| width: 36px; |
| height: 36px; |
| } |
| } |
| |
| .generated-image-preview { |
| max-width: 100%; |
| max-height: 400px; |
| border-radius: 12px; |
| margin: 20px 0; |
| border: none; |
| } |
| |
| .download-btn { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| padding: 10px 20px; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| transition: all 0.3s; |
| margin-right: 10px; |
| } |
| |
| .download-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 20px var(--shadow-color); |
| } |
| |
| .streaming-text { |
| display: inline-block; |
| } |
| |
| .code-render-instant { |
| animation: codeFadeIn 0.3s ease-out; |
| } |
| |
| @keyframes codeFadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| |
| .code-block-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| background: var(--bg-tertiary); |
| padding: 8px 12px; |
| border-radius: 8px 8px 0 0; |
| border-bottom: 1px solid var(--border-color); |
| font-size: 12px; |
| color: var(--text-secondary); |
| } |
| |
| .code-block-actions { |
| display: flex; |
| gap: 8px; |
| } |
| |
| .code-action-btn { |
| background: var(--button-bg); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| padding: 4px 10px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 12px; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| } |
| |
| .code-action-btn:hover { |
| background: var(--accent-color); |
| border-color: var(--accent-color); |
| color: var(--bg-primary); |
| transform: translateY(-1px); |
| } |
| |
| .code-action-btn.copied { |
| background: #4CAF50; |
| border-color: #4CAF50; |
| color: white; |
| } |
| |
| .message-content pre { |
| margin-top: 0; |
| border-radius: 0 0 8px 8px; |
| } |
| |
| .message-content pre:has(.code-block-header) { |
| border-radius: 0 0 8px 8px; |
| } |
| |
| |
| .project-item { |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| margin-bottom: 12px; |
| overflow: hidden; |
| } |
| |
| .project-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 12px 16px; |
| background: var(--bg-tertiary); |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .project-header:hover { |
| background: var(--button-hover); |
| } |
| |
| .project-title { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| font-weight: 600; |
| color: var(--text-primary); |
| } |
| |
| .project-actions { |
| display: flex; |
| gap: 6px; |
| } |
| |
| .project-action-btn { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 4px 8px; |
| border-radius: 4px; |
| transition: all 0.3s; |
| } |
| |
| .project-action-btn:hover { |
| background: var(--button-bg); |
| color: var(--text-primary); |
| } |
| |
| .project-chats { |
| padding: 8px; |
| display: none; |
| } |
| |
| .project-chats.expanded { |
| display: block; |
| } |
| |
| .project-chat-item { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 10px 12px; |
| margin-bottom: 6px; |
| background: var(--bg-secondary); |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.3s; |
| border: 1px solid transparent; |
| } |
| |
| .project-chat-item:hover { |
| background: var(--button-hover); |
| border-color: var(--border-color); |
| transform: translateX(4px); |
| } |
| |
| .project-chat-title { |
| font-size: 13px; |
| color: var(--text-primary); |
| flex: 1; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| } |
| |
| .project-chat-delete { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 4px; |
| border-radius: 4px; |
| opacity: 0; |
| transition: all 0.3s; |
| } |
| |
| .project-chat-item:hover .project-chat-delete { |
| opacity: 1; |
| } |
| |
| .project-chat-delete:hover { |
| background: #ff4444; |
| color: white; |
| } |
| |
| .add-project-btn { |
| width: 100%; |
| padding: 12px; |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| margin-bottom: 16px; |
| transition: all 0.3s; |
| } |
| |
| .add-project-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 15px var(--shadow-color); |
| } |
| |
| |
| .mcp-server-item { |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 10px; |
| padding: 16px; |
| margin-bottom: 12px; |
| position: relative; |
| } |
| |
| .mcp-server-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 10px; |
| } |
| |
| .mcp-server-name { |
| font-weight: 600; |
| color: var(--text-primary); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .mcp-server-status { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: #4CAF50; |
| } |
| |
| .mcp-server-status.disabled { |
| background: #ff4444; |
| } |
| |
| .mcp-server-actions { |
| display: flex; |
| gap: 6px; |
| } |
| |
| .mcp-server-btn { |
| background: var(--button-bg); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| padding: 4px 10px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 12px; |
| transition: all 0.3s; |
| } |
| |
| .mcp-server-btn:hover { |
| background: var(--accent-color); |
| border-color: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| .mcp-server-details { |
| font-size: 12px; |
| color: var(--text-secondary); |
| margin-top: 8px; |
| } |
| |
| .mcp-server-details div { |
| margin-bottom: 4px; |
| } |
| |
| .add-mcp-btn { |
| width: 100%; |
| padding: 12px; |
| background: var(--button-bg); |
| border: 2px dashed var(--border-color); |
| color: var(--text-primary); |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| margin-top: 12px; |
| transition: all 0.3s; |
| } |
| |
| .add-mcp-btn:hover { |
| border-color: var(--accent-color); |
| background: var(--bg-tertiary); |
| } |
| |
| .settings-section { |
| margin-bottom: 24px; |
| padding-bottom: 24px; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| .settings-section:last-child { |
| border-bottom: none; |
| } |
| |
| .settings-section-title { |
| font-size: 16px; |
| font-weight: 600; |
| color: var(--text-primary); |
| margin-bottom: 16px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| |
| .pro-badge { |
| background: var(--pro-gradient); |
| color: white; |
| padding: 4px 8px; |
| border-radius: 4px; |
| font-size: 11px; |
| font-weight: 700; |
| margin-left: 8px; |
| } |
| |
| .pro-feature { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 12px; |
| background: var(--bg-primary); |
| border-radius: 10px; |
| border: 1px solid var(--border-color); |
| margin-bottom: 12px; |
| } |
| |
| .pro-feature-info { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .pro-feature-icon { |
| width: 32px; |
| height: 32px; |
| background: var(--pro-gradient); |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .pro-feature-text h4 { |
| font-size: 14px; |
| font-weight: 600; |
| margin-bottom: 2px; |
| } |
| |
| .pro-feature-text p { |
| font-size: 12px; |
| color: var(--text-secondary); |
| } |
| |
| .pro-subscribe-btn { |
| width: 100%; |
| padding: 14px; |
| background: var(--pro-gradient); |
| color: white; |
| border: none; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 16px; |
| font-weight: 700; |
| transition: all 0.3s; |
| margin-top: 16px; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .pro-subscribe-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 20px rgba(77, 171, 247, 0.4); |
| } |
| |
| .pro-subscribe-btn.activating { |
| animation: proActivating 1.5s ease; |
| } |
| |
| .pro-subscribe-btn.success { |
| background: var(--pro-success); |
| animation: proSuccess 0.5s ease; |
| } |
| |
| .pro-subscribe-btn.error { |
| background: var(--pro-error); |
| animation: proError 0.5s ease; |
| } |
| |
| @keyframes proActivating { |
| 0% { background: var(--pro-gradient); } |
| 50% { background: linear-gradient(135deg, #4dabf7 0%, #339af0 100%); box-shadow: 0 0 20px rgba(77, 171, 247, 0.6); } |
| 100% { background: var(--pro-gradient); } |
| } |
| |
| @keyframes proSuccess { |
| 0% { background: var(--pro-gradient); } |
| 100% { background: var(--pro-success); } |
| } |
| |
| @keyframes proError { |
| 0% { background: var(--pro-gradient); } |
| 100% { background: var(--pro-error); } |
| } |
| |
| |
| .theme-toggle-container { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 12px; |
| background: var(--bg-primary); |
| border-radius: 10px; |
| border: 1px solid var(--border-color); |
| margin-bottom: 16px; |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .theme-toggle-container:hover { |
| background: var(--bg-tertiary); |
| } |
| |
| .theme-toggle-label { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| font-size: 14px; |
| color: var(--text-primary); |
| } |
| |
| .theme-toggle-switch { |
| position: relative; |
| width: 50px; |
| height: 26px; |
| background: var(--button-bg); |
| border-radius: 13px; |
| transition: all 0.3s; |
| } |
| |
| .theme-toggle-switch.active { |
| background: var(--accent-color); |
| } |
| |
| .theme-toggle-slider { |
| position: absolute; |
| top: 3px; |
| left: 3px; |
| width: 20px; |
| height: 20px; |
| background: white; |
| border-radius: 50%; |
| transition: all 0.3s; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.2); |
| } |
| |
| .theme-toggle-switch.active .theme-toggle-slider { |
| transform: translateX(24px); |
| } |
| |
| |
| .chat-history-item { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 12px; |
| background: var(--bg-primary); |
| border-radius: 8px; |
| margin-bottom: 8px; |
| border: 1px solid var(--border-color); |
| transition: all 0.3s; |
| } |
| |
| .chat-history-item:hover { |
| background: var(--bg-tertiary); |
| transform: translateX(4px); |
| } |
| |
| .chat-history-info { |
| flex: 1; |
| cursor: pointer; |
| min-width: 0; |
| padding-right: 12px; |
| } |
| |
| .chat-history-title { |
| font-weight: 600; |
| color: var(--text-primary); |
| margin-bottom: 4px; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| } |
| |
| .chat-history-meta { |
| font-size: 11px; |
| color: var(--text-secondary); |
| } |
| |
| .chat-history-middle { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| padding: 0 12px; |
| } |
| |
| .move-to-project-select { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| padding: 6px 10px; |
| border-radius: 6px; |
| font-size: 12px; |
| cursor: pointer; |
| outline: none; |
| min-width: 120px; |
| } |
| |
| .move-to-project-select:focus { |
| border-color: var(--accent-color); |
| } |
| |
| .chat-history-actions { |
| display: flex; |
| gap: 6px; |
| align-items: center; |
| } |
| |
| .chat-history-btn { |
| background: var(--button-bg); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| width: 28px; |
| height: 28px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 12px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: all 0.3s; |
| } |
| |
| .chat-history-btn:hover { |
| background: var(--accent-color); |
| border-color: var(--accent-color); |
| color: var(--bg-primary); |
| transform: scale(1.1); |
| } |
| |
| |
| .phone-button { |
| position: fixed; |
| bottom: 20px; |
| right: 20px; |
| width: 56px; |
| height: 56px; |
| background: var(--accent-color); |
| border: none; |
| border-radius: 50%; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 24px; |
| color: var(--bg-primary); |
| box-shadow: 0 4px 20px var(--shadow-color); |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| z-index: 999; |
| } |
| |
| .phone-button:hover { |
| transform: scale(1.1) translateY(-2px); |
| box-shadow: 0 6px 25px var(--shadow-color); |
| } |
| |
| .phone-panel { |
| position: fixed; |
| bottom: 90px; |
| right: 20px; |
| width: 280px; |
| background: var(--bg-secondary); |
| backdrop-filter: blur(20px); |
| border-radius: 18px; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 8px 32px var(--shadow-color); |
| padding: 20px; |
| transform: scale(0) translateY(20px); |
| opacity: 0; |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| z-index: 1000; |
| pointer-events: none; |
| } |
| |
| .phone-panel.active { |
| transform: scale(1) translateY(0); |
| opacity: 1; |
| pointer-events: all; |
| } |
| |
| .phone-panel-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 15px; |
| } |
| |
| .phone-panel-header h3 { |
| margin: 0; |
| font-size: 16px; |
| color: var(--text-primary); |
| } |
| |
| .close-panel { |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| font-size: 18px; |
| transition: color 0.3s; |
| } |
| |
| .close-panel:hover { |
| color: var(--text-primary); |
| } |
| |
| .qr-code-container { |
| text-align: center; |
| } |
| |
| .qr-code-image { |
| width: 100%; |
| max-width: 200px; |
| height: auto; |
| border-radius: 12px; |
| border: 1px solid var(--border-color); |
| } |
| |
| .qr-code-text { |
| margin-top: 10px; |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| @media (max-width: 768px) { |
| .phone-button { |
| bottom: 15px; |
| right: 15px; |
| width: 50px; |
| height: 50px; |
| font-size: 20px; |
| } |
| |
| .phone-panel { |
| bottom: 80px; |
| right: 15px; |
| width: 260px; |
| } |
| |
| .chat-history-item { |
| flex-wrap: wrap; |
| } |
| |
| .chat-history-middle { |
| order: 3; |
| width: 100%; |
| padding: 8px 0 0 0; |
| justify-content: flex-start; |
| } |
| } |
| |
| |
| .voice-selection-container { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| margin-bottom: 20px; |
| } |
| |
| .voice-option-card { |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| padding: 16px; |
| background: var(--bg-primary); |
| border: 2px solid var(--border-color); |
| border-radius: 12px; |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .voice-option-card:hover { |
| background: var(--bg-tertiary); |
| transform: translateX(4px); |
| } |
| |
| .voice-option-card.selected { |
| border-color: var(--accent-color); |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| } |
| |
| .voice-icon { |
| width: 48px; |
| height: 48px; |
| border-radius: 50%; |
| background: var(--bg-secondary); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 20px; |
| } |
| |
| .voice-option-card.selected .voice-icon { |
| background: rgba(255,255,255,0.2); |
| } |
| |
| .voice-info { |
| flex: 1; |
| } |
| |
| .voice-name { |
| font-weight: 600; |
| font-size: 16px; |
| margin-bottom: 4px; |
| } |
| |
| .voice-desc { |
| font-size: 13px; |
| opacity: 0.8; |
| } |
| |
| .test-voice-btn { |
| width: 100%; |
| padding: 12px; |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| transition: all 0.3s; |
| margin-top: 10px; |
| } |
| |
| .test-voice-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 15px var(--shadow-color); |
| } |
| |
| .test-voice-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| |
| .terms-modal { |
| max-width: 800px; |
| width: 90%; |
| max-height: 90vh; |
| } |
| |
| .terms-content { |
| background: var(--bg-primary); |
| border-radius: 12px; |
| padding: 24px; |
| margin-top: 16px; |
| max-height: 60vh; |
| overflow-y: auto; |
| border: 1px solid var(--border-color); |
| } |
| |
| .terms-content h3 { |
| margin: 20px 0 10px 0; |
| color: var(--text-primary); |
| font-size: 16px; |
| } |
| |
| .terms-content p { |
| margin-bottom: 12px; |
| line-height: 1.6; |
| color: var(--text-secondary); |
| font-size: 14px; |
| } |
| |
| .terms-content ul { |
| margin-left: 20px; |
| margin-bottom: 12px; |
| } |
| |
| .terms-content li { |
| margin-bottom: 6px; |
| color: var(--text-secondary); |
| line-height: 1.5; |
| } |
| |
| |
| .pro-activation-input { |
| font-size: 24px; |
| letter-spacing: 8px; |
| text-align: center; |
| font-weight: 600; |
| } |
| |
| .pro-code-inputs { |
| display: flex; |
| gap: 10px; |
| justify-content: center; |
| margin: 20px 0; |
| } |
| |
| .pro-code-input { |
| width: 60px; |
| height: 70px; |
| font-size: 28px; |
| text-align: center; |
| background: var(--bg-primary); |
| border: 2px solid var(--border-color); |
| border-radius: 10px; |
| color: var(--text-primary); |
| font-weight: 600; |
| transition: all 0.3s; |
| } |
| |
| .pro-code-input:focus { |
| border-color: var(--accent-color); |
| outline: none; |
| box-shadow: 0 0 15px var(--shadow-color); |
| } |
| |
| .pro-code-input.active { |
| border-color: var(--accent-color); |
| background: var(--bg-tertiary); |
| } |
| |
| |
| .pro-subscribe-btn.pulse { |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { box-shadow: 0 0 0 0 rgba(77, 171, 247, 0.7); } |
| 70% { box-shadow: 0 0 0 10px rgba(77, 171, 247, 0); } |
| 100% { box-shadow: 0 0 0 0 rgba(77, 171, 247, 0); } |
| } |
| |
| |
| .get-pro-banner { |
| position: absolute; |
| top: 20px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: var(--pro-gradient); |
| color: white; |
| padding: 12px 24px; |
| border-radius: 12px; |
| font-size: 14px; |
| font-weight: 600; |
| cursor: pointer; |
| display: none; |
| align-items: center; |
| gap: 8px; |
| z-index: 999; |
| box-shadow: 0 4px 20px rgba(77, 171, 247, 0.4); |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| .get-pro-banner:hover { |
| transform: translateX(-50%) translateY(-2px); |
| box-shadow: 0 6px 25px rgba(77, 171, 247, 0.6); |
| } |
| |
| .get-pro-banner.visible { |
| display: flex; |
| } |
| |
| |
| .hyze-code-feature { |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 20px; |
| margin: 20px 0; |
| text-align: center; |
| } |
| |
| .hyze-code-icon { |
| font-size: 48px; |
| margin-bottom: 16px; |
| color: var(--accent-color); |
| } |
| |
| .hyze-code-title { |
| font-size: 18px; |
| font-weight: 600; |
| margin-bottom: 8px; |
| color: var(--text-primary); |
| } |
| |
| .hyze-code-desc { |
| font-size: 14px; |
| color: var(--text-secondary); |
| margin-bottom: 20px; |
| } |
| |
| .hyze-code-btn { |
| background: var(--accent-color); |
| color: var(--bg-primary); |
| border: none; |
| padding: 10px 20px; |
| border-radius: 10px; |
| cursor: pointer; |
| font-size: 14px; |
| font-weight: 600; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin: 0 auto; |
| } |
| |
| .hyze-code-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 4px 15px var(--shadow-color); |
| } |
| |
| .hyze-code-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| transform: none; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div class="voice-status" id="voiceStatus"> |
| <div class="voice-pulse"></div> |
| <span id="voiceStatusText">Voice mode active</span> |
| </div> |
|
|
| |
| <div class="get-pro-banner" id="getProBanner" onclick="openProActivation()"> |
| <i class="fas fa-crown"></i> |
| <span>Get Pro</span> |
| </div> |
|
|
| |
| <button class="phone-button" id="phoneButton" title="Get Hyze Mobile App" onclick="togglePhonePanel()"> |
| <i class="fas fa-mobile-alt"></i> |
| </button> |
| <div class="phone-panel" id="phonePanel"> |
| <div class="phone-panel-header"> |
| <h3>Get Hyze Mobile</h3> |
| <button class="close-panel" onclick="togglePhonePanel()" title="Close"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <div class="qr-code-container"> |
| <img src="https://i.imgur.com/wMWpCF7.png" alt="Hyze Mobile App QR Code" class="qr-code-image"> |
| <p class="qr-code-text">Scan for Hyze App</p> |
| </div> |
| </div> |
|
|
| |
| <div class="auth-container" id="authContainer"> |
| <div class="auth-box"> |
| <div style="text-align: center; margin-bottom: 30px;"> |
| <img src="https://i.imgur.com/3TQgMBb.png" alt="Hyze AI" style="width: 100px; height: 100px; object-fit: contain;"> |
| </div> |
| <h1 id="authTitle">Sign In</h1> |
| <form id="authForm"> |
| <div class="form-group"> |
| <label for="authUsername">Username</label> |
| <input type="text" id="authUsername" class="auth-input" placeholder="Enter username" required> |
| </div> |
| <div class="form-group"> |
| <label for="authPassword">Password</label> |
| <input type="password" id="authPassword" class="auth-input" placeholder="Enter password" required> |
| </div> |
| <div class="modal-actions" style="margin-top: 24px;"> |
| <button type="submit" class="modal-btn modal-btn-primary" style="width: 100%;" id="authSubmitBtn">Sign In</button> |
| </div> |
| </form> |
| |
| |
| <div class="divider">or continue with</div> |
| |
| |
| <button class="google-btn" id="googleSignInBtn" onclick="loginWithGoogle()"> |
| <i class="fab fa-google"></i> |
| Sign in with Google |
| </button> |
| |
| <div class="auth-toggle"> |
| <span id="authToggleText">Don't have an account? <a onclick="toggleAuthMode()">Sign Up</a></span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="sidebar" id="sidebar"> |
| <div class="sidebar-header" onclick="location.reload()"> |
| <div class="sidebar-logo"> |
| <img src="https://i.imgur.com/3TQgMBb.png" alt="Hyze AI Logo" id="sidebarLogo"> |
| </div> |
| </div> |
| |
| <div class="sidebar-actions"> |
| <button class="sidebar-btn" onclick="newChat()" title="New Chat"> |
| <i class="fas fa-plus sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">New Chat</span> |
| </button> |
| <button class="sidebar-btn" onclick="openProjectsModal()" title="Projects"> |
| <i class="fas fa-folder sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Projects</span> |
| </button> |
| <button class="sidebar-btn" onclick="openSearchModal()" title="Search Chats"> |
| <i class="fas fa-search sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Search</span> |
| </button> |
| <button class="sidebar-btn" onclick="openModelSelector()" title="Select AI Model"> |
| <i class="fas fa-robot sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">AI Model</span> |
| </button> |
| |
| <button class="sidebar-btn" onclick="openAgentsModal()" title="AI Agents"> |
| <i class="fas fa-people-group sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Agents</span> |
| </button> |
| <button class="sidebar-btn" onclick="openChatHistory()" title="Chat History"> |
| <i class="fas fa-history sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">History</span> |
| </button> |
| |
| <button class="sidebar-btn" id="hyzeCodeBtn" onclick="openHyzeCode()" title="Hyze Code" style="display: none;"> |
| <i class="fas fa-code sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Hyze Code</span> |
| </button> |
| <button class="sidebar-btn" onclick="exportChat()" title="Export Chat"> |
| <i class="fas fa-download sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Export</span> |
| </button> |
| <button class="sidebar-btn" onclick="openSettings()" title="Settings"> |
| <i class="fas fa-cog sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Settings</span> |
| </button> |
| <button class="sidebar-btn" onclick="logout()" title="Logout"> |
| <i class="fas fa-sign-out-alt sidebar-btn-icon"></i> |
| <span class="sidebar-btn-text">Logout</span> |
| </button> |
| </div> |
|
|
| <div class="sidebar-footer"> |
| <button class="toggle-btn" onclick="toggleSidebar()" id="toggleBtn" title="Toggle Sidebar"> |
| <i class="fas fa-chevron-right"></i> |
| </button> |
| </div> |
| </div> |
|
|
| <div class="main-container"> |
| <div class="chat-section"> |
| <div class="center-container" id="centerContainer"> |
| <div class="chat-messages" id="chatMessages"></div> |
| |
| <div class="typing-indicator centered-element" id="typingIndicator"> |
| <div class="logo-container-loader"> |
| <img src="https://i.imgur.com/3TQgMBb.png" alt="Loading" class="logo-loader" id="typingLogo"> |
| </div> |
| </div> |
|
|
| |
| <div class="square-image-loader" id="squareImageLoader"> |
| <div class="image-loader-container" id="imageLoaderContainer"> |
| <div class="image-loader-icon"> |
| <svg viewBox="0 0 24 24"> |
| <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/> |
| </svg> |
| </div> |
| </div> |
| <div class="image-loader-text"> |
| <i class="fas fa-paint-brush"></i> |
| <span>Generating your image...</span> |
| </div> |
| </div> |
| |
| <div class="chat-input-container centered-element" id="chatInputContainer"> |
| <div class="logo-container" id="logoContainer"> |
| <img src="https://i.imgur.com/24wAsac.png" alt="Hyze AI Logo" class="logo-main" id="centerLogo"> |
| </div> |
|
|
| <div class="chat-input-wrapper"> |
| <input type="file" id="chatFileInput" accept="image/*" style="display: none;"> |
| <div style="position: relative;"> |
| <button class="voice-input-btn" id="voiceInputBtn" title="Voice Input"> |
| <i class="fas fa-microphone"></i> |
| </button> |
| <div class="auto-send-indicator" id="autoSendIndicator">Auto-sending...</div> |
| </div> |
| <button class="upload-btn" id="uploadBtn" title="Upload image"> |
| <i class="fas fa-paperclip"></i> |
| </button> |
| |
| <button class="quick-image-btn" id="quickImageGenBtn" title="Toggle Image Generation Mode"> |
| <i class="fa-solid fa-paintbrush"></i> |
| </button> |
| <textarea |
| class="chat-input" |
| id="chatInput" |
| placeholder="Message Hyze..." |
| rows="1" |
| ></textarea> |
| |
| <div class="image-preview-mini" id="imagePreviewMini"> |
| <img src="" alt="Image preview" id="imagePreviewMiniImg"> |
| <button class="remove-mini-btn" id="removeMiniBtn" title="Remove image"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <button class="send-button" id="sendButton" title="Send message"><i class="fas fa-paper-plane"></i></button> |
| <button class="stop-button" id="stopButton" onclick="stopAI()" title="Stop AI"><i class="fas fa-stop"></i></button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="program-section" id="programSection"> |
| <div class="program-header"> |
| <div class="program-title"> |
| <i class="fas fa-code"></i> |
| <span id="programTitle">Program</span> |
| </div> |
| <div class="program-actions"> |
| <button class="program-btn" onclick="runProgram()" title="Run Program"> |
| <i class="fas fa-play"></i> Run |
| </button> |
| <button class="program-btn" onclick="saveProgram()" title="Save Program"> |
| <i class="fas fa-save"></i> Save |
| </button> |
| <button class="program-btn" onclick="closeProgram()" title="Close Program"> |
| <i class="fas fa-times"></i> Close |
| </button> |
| </div> |
| </div> |
| <div class="program-loading" id="programLoading"> |
| <div>Loading program editor...</div> |
| </div> |
| <iframe class="program-iframe" id="programIframe" style="display: none;"></iframe> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="agentsModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-robot"></i> AI Agents</h2> |
| |
| <div style="margin-bottom: 20px;"> |
| <p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 16px;"> |
| Create and manage custom AI agents. Each agent can be trained with specific instructions and connected to MCP servers for enhanced capabilities. |
| </p> |
| |
| <button class="add-agent-btn" onclick="createNewAgent()"> |
| <i class="fas fa-plus"></i> Create New Agent |
| </button> |
| </div> |
| |
| |
| <div class="settings-section" style="margin-bottom: 24px; padding-bottom: 16px;"> |
| <div class="settings-section-title"> |
| <i class="fas fa-bolt"></i> Active Agent |
| </div> |
| <div id="activeAgentDisplay" style="padding: 12px; background: var(--bg-primary); border-radius: 10px; border: 1px solid var(--border-color);"> |
| <div style="font-size: 14px; color: var(--text-secondary);">No active agent selected</div> |
| </div> |
| </div> |
| |
| |
| <div class="settings-section" style="margin-bottom: 24px; padding-bottom: 16px;"> |
| <div class="settings-section-title"> |
| <i class="fas fa-star"></i> Premade Agents |
| </div> |
| <div id="premadeAgentsList"> |
| |
| </div> |
| </div> |
| |
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-user-edit"></i> Your Agents |
| </div> |
| <div id="customAgentsList" style="max-height: 400px; overflow-y: auto;"> |
| |
| </div> |
| </div> |
| |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeAgentsModal()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="agentEditModal"> |
| <div class="modal-content"> |
| <h2 id="agentModalTitle"><i class="fas fa-robot"></i> Create New Agent</h2> |
| |
| <div class="agent-form-group"> |
| <label>Agent Name</label> |
| <input type="text" id="agentNameInput" placeholder="e.g., Research Assistant, Coding Expert" maxlength="50"> |
| </div> |
| |
| <div class="agent-form-group"> |
| <label>Agent Description</label> |
| <input type="text" id="agentDescriptionInput" placeholder="Brief description of what this agent does" maxlength="100"> |
| </div> |
| |
| <div class="agent-form-group"> |
| <label>Training Instructions</label> |
| <textarea id="agentTrainingInput" placeholder="Train your agent with specific instructions. Example: 'You are a research assistant specializing in scientific papers. You help analyze research, summarize findings, and provide insights.'" rows="6"></textarea> |
| <p style="font-size: 12px; color: var(--text-secondary); margin-top: 4px;"> |
| These instructions will guide the agent's behavior and responses. |
| </p> |
| </div> |
| |
| <div class="agent-form-group"> |
| <label>Connect to MCP Server (Optional)</label> |
| <select id="agentMCPSelect"> |
| <option value="">No MCP Server</option> |
| |
| </select> |
| <p style="font-size: 12px; color: var(--text-secondary); margin-top: 4px;"> |
| Connect to an MCP server for enhanced capabilities like web search, data analysis, etc. |
| </p> |
| </div> |
| |
| <input type="hidden" id="agentIdInput"> |
| |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeAgentEditModal()">Cancel</button> |
| <button class="modal-btn modal-btn-primary" onclick="saveAgent()">Save Agent</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="projectsModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-folder"></i> Projects</h2> |
| <button class="add-project-btn" onclick="createNewProject()"> |
| <i class="fas fa-plus"></i> Create New Project |
| </button> |
| <div id="projectsList" style="max-height: 500px; overflow-y: auto;"> |
| |
| </div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeProjectsModal()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="imageGenModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-paint-brush sidebar-btn-icon"></i> Generate Image</h2> |
| <div class="form-group"> |
| <label>Describe the image you want to generate:</label> |
| <textarea id="imageGenPrompt" placeholder="A futuristic rocket launching into space with stars in the background, photorealistic style..." rows="4"></textarea> |
| </div> |
| <div id="imageGenResult" style="display: none;"> |
| <h3 style="margin-bottom: 10px;">Generated Image:</h3> |
| <img id="generatedImagePreview" class="generated-image-preview" alt="Generated image"> |
| <div class="modal-actions"> |
| <button class="download-btn" onclick="downloadGeneratedImage()"> |
| <i class="fas fa-download"></i> Download Image |
| </button> |
| <button class="modal-btn modal-btn-primary" onclick="sendGeneratedImageToChat()"> |
| <i class="fas fa-paper-plane"></i> Send to Chat |
| </button> |
| </div> |
| </div> |
| <div class="modal-actions" id="imageGenActions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeImageGenModal()">Cancel</button> |
| <button class="modal-btn modal-btn-primary" onclick="generateImage()"> |
| <i class="fas fa-magic"></i> Generate |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="searchModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-search"></i> Search All Chats</h2> |
| <input type="text" id="globalSearchBox" placeholder="Search through all chats..." oninput="globalSearch()"> |
| <div id="globalSearchResults" style="margin-top: 20px;"></div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-primary" onclick="closeSearchModal()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="chatHistoryModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-history"></i> Chat History</h2> |
| <div id="chatHistoryList" style="max-height: 400px; overflow-y: auto;"></div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-primary" onclick="closeChatHistory()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="analyticsModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-chart-bar"></i> Chat Analytics</h2> |
| <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 20px;"> |
| <div style="text-align: center; padding: 16px; background: var(--bg-tertiary); border-radius: 12px;"> |
| <div style="font-size: 28px; font-weight: 700; color: var(--text-primary);" id="totalChats">0</div> |
| <div style="font-size: 12px; color: var(--text-secondary);">Total Chats</div> |
| </div> |
| <div style="text-align: center; padding: 16px; background: var(--bg-tertiary); border-radius: 12px;"> |
| <div style="font-size: 28px; font-weight: 700; color: var(--text-primary);" id="totalMessages">0</div> |
| <div style="font-size: 12px; color: var(--text-secondary);">Messages</div> |
| </div> |
| <div style="text-align: center; padding: 16px; background: var(--bg-tertiary); border-radius: 12px;"> |
| <div style="font-size: 28px; font-weight: 700; color: var(--text-primary);" id="totalWords">0</div> |
| <div style="font-size: 12px; color: var(--text-secondary);">Words</div> |
| </div> |
| <div style="text-align: center; padding: 16px; background: var(--bg-tertiary); border-radius: 12px;"> |
| <div style="font-size: 18px; font-weight: 700; color: var(--text-primary);" id="favoriteModel">Auto</div> |
| <div style="font-size: 12px; color: var(--text-secondary);">Favorite Model</div> |
| </div> |
| </div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-primary" onclick="closeAnalytics()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="voiceSettingsModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-microphone"></i> Voice Settings</h2> |
| |
| <div class="form-group" style="margin-bottom: 20px;"> |
| <label style="font-size: 14px; margin-bottom: 12px; display: block;">Select Voice Character</label> |
| <div class="voice-selection-container"> |
| <div class="voice-option-card" id="dennisOption" onclick="selectVoice('Dennis')"> |
| <div class="voice-icon"> |
| <i class="fas fa-mars"></i> |
| </div> |
| <div class="voice-info"> |
| <div class="voice-name">Dennis</div> |
| <div class="voice-desc">Default Male Voice</div> |
| </div> |
| <i class="fas fa-check-circle" style="font-size: 20px; opacity: 0; transition: opacity 0.3s;" id="dennisCheck"></i> |
| </div> |
| |
| <div class="voice-option-card" id="deborahOption" onclick="selectVoice('Deborah')"> |
| <div class="voice-icon"> |
| <i class="fas fa-venus"></i> |
| </div> |
| <div class="voice-info"> |
| <div class="voice-name">Deborah</div> |
| <div class="voice-desc">Female Voice</div> |
| </div> |
| <i class="fas fa-check-circle" style="font-size: 20px; opacity: 0; transition: opacity 0.3s;" id="deborahCheck"></i> |
| </div> |
| </div> |
| </div> |
|
|
| <button class="test-voice-btn" id="testVoiceBtn" onclick="testVoice()"> |
| <i class="fas fa-play"></i> Test Voice |
| </button> |
|
|
| <div class="modal-actions" style="margin-top: 20px;"> |
| <button class="modal-btn modal-btn-primary" onclick="saveVoiceSettings()">Done</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="modelModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-robot"></i> Select AI Model</h2> |
| <div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px;" id="modelList"> |
| |
| </div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-primary" onclick="closeModelModal()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="settingsModal"> |
| <div class="modal-content" style="max-width: 700px;"> |
| <h2><i class="fas fa-cog"></i> Settings</h2> |
| |
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-crown"></i> Pro Subscription |
| <span class="pro-badge" id="proStatus">FREE</span> |
| </div> |
| |
| |
| <div class="hyze-code-feature" id="hyzeCodeFeature" style="display: none;"> |
| <div class="hyze-code-icon"> |
| <i class="fas fa-code"></i> |
| </div> |
| <div class="hyze-code-title">Hyze Code</div> |
| <div class="hyze-code-desc"> |
| Access advanced code generation, debugging, and programming assistance with Hyze Code. Create complex applications, analyze code, and get intelligent programming help. |
| </div> |
| <button class="hyze-code-btn" onclick="openHyzeCode()"> |
| <i class="fas fa-rocket"></i> |
| Launch Hyze Code |
| </button> |
| </div> |
| |
| <div class="pro-feature"> |
| <div class="pro-feature-info"> |
| <div class="pro-feature-icon"> |
| <i class="fas fa-bolt"></i> |
| </div> |
| <div class="pro-feature-text"> |
| <h4>Priority Access</h4> |
| <p>Faster responses, no queue times</p> |
| </div> |
| </div> |
| <div style="font-weight: 600; color: var(--accent-color);"> |
| $4.99/month |
| </div> |
| </div> |
| |
| <div class="pro-feature"> |
| <div class="pro-feature-info"> |
| <div class="pro-feature-icon"> |
| <i class="fas fa-images"></i> |
| </div> |
| <div class="pro-feature-text"> |
| <h4>Advanced Image Generation</h4> |
| <p>Higher resolution, more styles</p> |
| </div> |
| </div> |
| <div style="font-weight: 600; color: var(--accent-color);"> |
| PRO |
| </div> |
| </div> |
| |
| <div class="pro-feature"> |
| <div class="pro-feature-info"> |
| <div class="pro-feature-icon"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <div class="pro-feature-text"> |
| <h4>RE2 Advanced Model</h4> |
| <p>Access to cutting-edge RE2 AI model</p> |
| </div> |
| </div> |
| <div style="font-weight: 600; color: var(--accent-color);"> |
| PRO |
| </div> |
| </div> |
| |
| <button class="pro-subscribe-btn" id="proActivateBtn" onclick="openProActivation()"> |
| <i class="fas fa-crown"></i> Activate Pro |
| </button> |
| </div> |
|
|
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-file-contract"></i> Terms & Legal |
| </div> |
| <div class="form-group"> |
| <p style="font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 12px;"> |
| By using Hyze AI, you agree to our Terms of Service. Hyze AI is designed for educational, creative, and productivity purposes. |
| </p> |
| <div style="display: flex; gap: 12px; margin-top: 12px;"> |
| <button class="modal-btn modal-btn-secondary" onclick="openTerms()"> |
| <i class="fas fa-file-alt"></i> Terms of Service |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-server"></i> MCP Servers |
| </div> |
| <div id="mcpServersList"> |
| |
| </div> |
| <button class="add-mcp-btn" onclick="addNewMCPServer()"> |
| <i class="fas fa-plus"></i> Add Custom MCP Server |
| </button> |
| </div> |
|
|
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-microphone"></i> Voice |
| </div> |
| <div class="form-group"> |
| <label>Current Voice: <span id="currentVoiceDisplay" style="color: var(--accent-color); font-weight: 600;">Dennis</span></label> |
| <button class="modal-btn modal-btn-secondary" onclick="openVoiceSettings()" style="width: 100%; margin-top: 8px;"> |
| <i class="fas fa-cog"></i> Change Voice Character |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-comment-dots"></i> Custom Instructions |
| </div> |
| <div class="form-group"> |
| <label for="settingsSystemPrompt">Custom System Prompt</label> |
| <textarea id="settingsSystemPrompt" placeholder="Enter custom instructions for the AI..." rows="4" style="margin-bottom: 16px;"></textarea> |
| <p style="font-size: 12px; color: var(--text-secondary); margin-top: -10px; margin-bottom: 16px;"> |
| These instructions will be added to the AI's behavior. Leave empty to use default. |
| </p> |
| </div> |
| </div> |
|
|
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-palette"></i> Appearance |
| </div> |
| <div class="theme-toggle-container" onclick="toggleThemeFromSettings()"> |
| <div class="theme-toggle-label"> |
| <i class="fas fa-moon" id="settingsThemeIcon"></i> |
| <span id="settingsThemeText">Dark Mode</span> |
| </div> |
| <div class="theme-toggle-switch" id="settingsThemeSwitch"> |
| <div class="theme-toggle-slider"></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="settings-section"> |
| <div class="settings-section-title"> |
| <i class="fas fa-user"></i> Account |
| </div> |
| <div class="form-group"> |
| <label for="settingsUsername">Username</label> |
| <input type="text" id="settingsUsername" placeholder="New username"> |
| </div> |
| <div class="form-group"> |
| <label for="settingsCurrentPassword">Current Password</label> |
| <input type="password" id="settingsCurrentPassword" placeholder="Enter current password to change"> |
| </div> |
| <div class="form-group"> |
| <label for="settingsNewPassword">New Password</label> |
| <input type="password" id="settingsNewPassword" placeholder="New password (leave empty to keep current)"> |
| </div> |
| <div class="form-group"> |
| <label for="settingsConfirmPassword">Confirm New Password</label> |
| <input type="password" id="settingsConfirmPassword" placeholder="Confirm new password"> |
| </div> |
| </div> |
|
|
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeSettings()">Cancel</button> |
| <button class="modal-btn modal-btn-primary" onclick="saveSettings()">Save Changes</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="termsModal"> |
| <div class="modal-content terms-modal"> |
| <h2><i class="fas fa-file-contract"></i> Terms of Service</h2> |
| <div class="terms-content"> |
| <h3>Acceptance of Terms</h3> |
| <p>By accessing or using Hyze, you agree to these Terms of Service. If you do not agree, please do not use the service.</p> |
|
|
| <h3>Eligibility</h3> |
| <p>You must be at least 10 years old to use Hyze:</p> |
| <ul> |
| <li>12 years or older is recommended</li> |
| <li>Users under 18 must have permission from a parent or guardian</li> |
| </ul> |
|
|
| <h3>Use of the Service</h3> |
| <p>Hyze provides AI-generated responses for informational and educational purposes.</p> |
| <p>You agree not to:</p> |
| <ul> |
| <li>Use the service for illegal, harmful, or abusive activities</li> |
| <li>Attempt to hack, disrupt, or misuse the platform</li> |
| <li>Upload malware, spam, or harmful content</li> |
| <li>Impersonate others</li> |
| </ul> |
|
|
| <h3>AI Disclaimer</h3> |
| <p>AI responses:</p> |
| <ul> |
| <li>May be inaccurate or incomplete</li> |
| <li>Are not professional advice (medical, legal, financial, etc.)</li> |
| <li>Should be independently verified</li> |
| </ul> |
| <p>Use Hyze at your own risk.</p> |
|
|
| <h3>Content & Ownership</h3> |
| <p>You own the content you submit.</p> |
| <p>Hyze owns its branding, software, and platform.</p> |
| <p>We process your content only to provide and improve the service.</p> |
|
|
| <h3>Availability</h3> |
| <p>Hyze is provided "as is". We do not guarantee uninterrupted or error-free service and may modify or discontinue features at any time.</p> |
|
|
| <h3>Termination</h3> |
| <p>We may suspend or terminate access if these terms are violated.</p> |
|
|
| <h3>Limitation of Liability</h3> |
| <p>Hyze is not liable for damages or losses resulting from use of the service or reliance on AI-generated content.</p> |
|
|
| <h3>Changes</h3> |
| <p>We may update these Terms at any time. Continued use means you accept the updated terms.</p> |
|
|
| <h3>Privacy Policy</h3> |
| <p>Your data stays in your browser through local storage.</p> |
| <p>We do not use your data for training our AI.</p> |
|
|
| <h3>Contact</h3> |
| <p>For questions or concerns, please contact us through our email - hiteshv2603@gmail.com</p> |
| </div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-primary" onclick="closeTerms()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="proActivationModal"> |
| <div class="modal-content"> |
| <h2><i class="fas fa-crown"></i> Activate Pro Subscription</h2> |
| <div class="form-group"> |
| <p style="text-align: center; margin-bottom: 20px; color: var(--text-secondary);"> |
| Enter your 4-digit Pro activation code below: |
| </p> |
| <div class="pro-code-inputs"> |
| <input type="text" maxlength="1" class="pro-code-input" id="code1" oninput="moveToNext(1, event)"> |
| <input type="text" maxlength="1" class="pro-code-input" id="code2" oninput="moveToNext(2, event)"> |
| <input type="text" maxlength="1" class="pro-code-input" id="code3" oninput="moveToNext(3, event)"> |
| <input type="text" maxlength="1" class="pro-code-input" id="code4" oninput="moveToNext(4, event)"> |
| </div> |
| <p id="proActivationMessage" style="text-align: center; margin-top: 10px; min-height: 20px; color: var(--text-secondary);"></p> |
| </div> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeProActivation()">Cancel</button> |
| <button class="modal-btn modal-btn-primary" id="proActivateSubmitBtn" onclick="activatePro()"> |
| <i class="fas fa-check"></i> Activate Pro |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="mcpServerModal"> |
| <div class="modal-content"> |
| <h2 id="mcpModalTitle"><i class="fas fa-server"></i> Add MCP Server</h2> |
| <div class="form-group"> |
| <label>Server Name</label> |
| <input type="text" id="mcpServerName" placeholder="e.g., My Custom Server"> |
| </div> |
| <div class="form-group"> |
| <label>Endpoint URL</label> |
| <input type="url" id="mcpServerEndpoint" placeholder="https://api.example.com/endpoint"> |
| </div> |
| <div class="form-group"> |
| <label>API Key (optional)</label> |
| <input type="text" id="mcpServerApiKey" placeholder="Enter API key if required"> |
| </div> |
| <div class="form-group"> |
| <label>Description</label> |
| <input type="text" id="mcpServerDescription" placeholder="Brief description of this server"> |
| </div> |
| <div class="form-group"> |
| <label> |
| <input type="checkbox" id="mcpServerEnabled" checked> |
| Enabled |
| </label> |
| </div> |
| <input type="hidden" id="mcpServerId"> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="closeMCPServerModal()">Cancel</button> |
| <button class="modal-btn modal-btn-primary" onclick="saveMCPServer()">Save Server</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const GROQ_API_KEY = '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'; |
| const GROQ_URL = 'https://api.groq.com/openai/v1/chat/completions'; |
| |
| const GEMINI_API_KEY = 'AIzaSyDVvO8BeHNMks4LpyDuDVe08zafXK81PKg'; |
| const GEMINI_VISION_URL = 'https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent'; |
| |
| |
| const POLLINATIONS_API_KEY = 'sk_SUfq0Ypw3zwGmSO0tQL9Cu4p9QDdXaDn'; |
| |
| |
| const INWORLD_TTS_URL = 'https://api.inworld.ai/tts/v1/voice'; |
| const INWORLD_API_KEY = 'anFncTkzd1VISHpvOTlhQzlkV2k1SERzVHR6OFc4MVU6WWRnMDFKQlg1MTA3MnpWbGJXMllqTmNyaGRFUmR5dk0xOFlhbVI2UW9zV1dMeElvZDM3V3hpQkpKN25UU0xLMQ=='; |
| |
| |
| let MCP_SERVERS = { |
| wikidata: { |
| name: 'WikiData', |
| endpoint: 'https://query.wikidata.org/sparql', |
| enabled: true, |
| description: 'Query structured data from Wikidata', |
| apiKey: null |
| }, |
| exa: { |
| name: 'Exa Web Search', |
| endpoint: 'https://api.exa.ai/search', |
| enabled: true, |
| apiKey: '3ac3cc18-56a5-479d-a620-042695ae4d3d', |
| description: 'Search the web using Exa AI' |
| } |
| }; |
| |
| |
| let selectedVoiceCharacter = localStorage.getItem('hiteshai_voice_character') || 'Dennis'; |
| let autoSendVoice = localStorage.getItem('hiteshai_auto_send') !== 'false'; |
| let continuousVoiceMode = localStorage.getItem('hiteshai_continuous_voice') === 'true'; |
| |
| |
| let projects = []; |
| |
| |
| let agents = []; |
| let activeAgentId = null; |
| |
| |
| let isProUser = localStorage.getItem('hyze_pro_status') === 'true'; |
| |
| const PRO_CODES = ['2013', '6741', '2019', '1984', '1989', '0205', '2603']; |
| |
| |
| let auth0Client = null; |
| const AUTH0_DOMAIN = 'dev-rrafxpsphogfah1w.us.auth0.com'; |
| const AUTH0_CLIENT_ID = 'v1VnMqyxe7vmse5OXB0iZ30QXQhvhq0E'; |
| |
| |
| async function initAuth0() { |
| try { |
| auth0Client = await auth0.createAuth0Client({ |
| domain: AUTH0_DOMAIN, |
| clientId: AUTH0_CLIENT_ID, |
| authorizationParams: { |
| redirect_uri: window.location.origin + window.location.pathname |
| } |
| }); |
| |
| |
| if (location.search.includes("code=") && location.search.includes("state=")) { |
| await auth0Client.handleRedirectCallback(); |
| window.history.replaceState({}, document.title, window.location.pathname); |
| const user = await auth0Client.getUser(); |
| if (user) { |
| await handleAuth0Login(user); |
| } |
| } |
| } catch (error) { |
| console.error('Auth0 init error:', error); |
| } |
| } |
| |
| |
| async function loginWithGoogle() { |
| if (!auth0Client) { |
| showNotification('❌ Auth0 not initialized. Please try again.'); |
| return; |
| } |
| |
| try { |
| await auth0Client.loginWithRedirect({ |
| authorizationParams: { |
| connection: 'google-oauth2', |
| redirect_uri: window.location.origin + window.location.pathname |
| } |
| }); |
| } catch (error) { |
| console.error('Google login error:', error); |
| showNotification('❌ Google login failed. Please try again.'); |
| } |
| } |
| |
| |
| async function handleAuth0Login(auth0User) { |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| |
| const email = auth0User.email || auth0User.sub; |
| const username = email.split('@')[0]; |
| |
| if (!users[username]) { |
| users[username] = { |
| password: 'auth0_' + Math.random().toString(36).substring(7), |
| email: email, |
| name: auth0User.name || username, |
| picture: auth0User.picture || null, |
| isAuth0User: true, |
| chats: [], |
| agents: [], |
| settings: { |
| customPrompt: '', |
| webSearch: false, |
| theme: 'dark' |
| }, |
| analytics: {totalMessages: 0, totalWords: 0, modelUsage: {}}, |
| memory: {} |
| }; |
| localStorage.setItem('hiteshai_users', JSON.stringify(users)); |
| } |
| |
| currentUser = {username: username}; |
| localStorage.setItem('hiteshai_current_user', JSON.stringify(currentUser)); |
| document.getElementById('authContainer').classList.remove('active'); |
| loadUserData(); |
| loadProjects(); |
| loadMCPConfig(); |
| loadAgents(); |
| showNotification(`✅ Welcome, ${auth0User.name || username}!`); |
| } |
| |
| |
| async function logoutAuth0() { |
| if (auth0Client) { |
| await auth0Client.logout({ |
| logoutParams: { |
| returnTo: window.location.origin + window.location.pathname |
| } |
| }); |
| } |
| } |
| |
| function initTheme() { |
| const body = document.body; |
| |
| if (isLightMode) { |
| body.classList.add('light-mode'); |
| } else { |
| body.classList.remove('light-mode'); |
| } |
| |
| updateLogos(); |
| updateSettingsThemeUI(); |
| updateProStatus(); |
| updateProBanner(); |
| updateHyzeCodeButton(); |
| } |
| |
| function toggleTheme() { |
| isLightMode = !isLightMode; |
| localStorage.setItem('hiteshai_theme', isLightMode ? 'light' : 'dark'); |
| initTheme(); |
| showNotification(isLightMode ? '🌞 Switched to Light Mode' : '🌙 Switched to Dark Mode'); |
| } |
| |
| function toggleThemeFromSettings() { |
| toggleTheme(); |
| } |
| |
| function updateSettingsThemeUI() { |
| const themeIcon = document.getElementById('settingsThemeIcon'); |
| const themeText = document.getElementById('settingsThemeText'); |
| const themeSwitch = document.getElementById('settingsThemeSwitch'); |
| |
| if (isLightMode) { |
| themeIcon.className = 'fas fa-sun'; |
| themeText.textContent = 'Light Mode'; |
| themeSwitch.classList.add('active'); |
| } else { |
| themeIcon.className = 'fas fa-moon'; |
| themeText.textContent = 'Dark Mode'; |
| themeSwitch.classList.remove('active'); |
| } |
| } |
| |
| function updateLogos() { |
| const sidebarLogo = document.getElementById('sidebarLogo'); |
| sidebarLogo.src = isLightMode ? 'https://i.imgur.com/GQ3Bx3u.png' : 'https://i.imgur.com/3TQgMBb.png'; |
| |
| const centerLogo = document.getElementById('centerLogo'); |
| if (isProUser) { |
| centerLogo.src = isLightMode ? 'https://i.imgur.com/kcYFHfS.png' : 'https://i.imgur.com/tpUJWaw.png'; |
| } else { |
| centerLogo.src = isLightMode ? 'https://i.imgur.com/BoFwrb2.png' : 'https://i.imgur.com/24wAsac.png'; |
| } |
| |
| const typingLogo = document.getElementById('typingLogo'); |
| typingLogo.src = isLightMode ? 'https://i.imgur.com/QIPPTn0.png' : 'https://i.imgur.com/3TQgMBb.png'; |
| |
| const loaderIcon = document.querySelector('.image-loader-icon svg'); |
| if (loaderIcon) { |
| loaderIcon.style.fill = isLightMode ? '#2C64BA' : '#2E67BA'; |
| } |
| } |
| |
| function updateProStatus() { |
| const proStatus = document.getElementById('proStatus'); |
| const proActivateBtn = document.getElementById('proActivateBtn'); |
| const hyzeCodeFeature = document.getElementById('hyzeCodeFeature'); |
| |
| if (isProUser) { |
| proStatus.textContent = 'PRO'; |
| proStatus.style.background = 'var(--pro-gradient)'; |
| proActivateBtn.innerHTML = '<i class="fas fa-check"></i> Pro Active'; |
| proActivateBtn.disabled = true; |
| proActivateBtn.style.opacity = '0.7'; |
| proActivateBtn.classList.remove('pulse'); |
| if (hyzeCodeFeature) { |
| hyzeCodeFeature.style.display = 'block'; |
| } |
| } else { |
| proStatus.textContent = 'FREE'; |
| proStatus.style.background = 'var(--button-bg)'; |
| proActivateBtn.innerHTML = '<i class="fas fa-crown"></i> Activate Pro'; |
| proActivateBtn.disabled = false; |
| proActivateBtn.style.opacity = '1'; |
| proActivateBtn.classList.add('pulse'); |
| if (hyzeCodeFeature) { |
| hyzeCodeFeature.style.display = 'none'; |
| } |
| } |
| renderModelList(); |
| } |
| |
| function updateProBanner() { |
| const getProBanner = document.getElementById('getProBanner'); |
| if (!isProUser && !hasSentMessage) { |
| getProBanner.classList.add('visible'); |
| } else { |
| getProBanner.classList.remove('visible'); |
| } |
| } |
| |
| function updateHyzeCodeButton() { |
| const hyzeCodeBtn = document.getElementById('hyzeCodeBtn'); |
| if (isProUser) { |
| hyzeCodeBtn.style.display = 'flex'; |
| } else { |
| hyzeCodeBtn.style.display = 'none'; |
| } |
| } |
| |
| function openProActivation() { |
| document.getElementById('proActivationModal').classList.add('active'); |
| document.getElementById('code1').focus(); |
| document.getElementById('proActivationMessage').textContent = ''; |
| |
| |
| for (let i = 1; i <= 4; i++) { |
| document.getElementById(`code${i}`).value = ''; |
| } |
| } |
| |
| function closeProActivation() { |
| document.getElementById('proActivationModal').classList.remove('active'); |
| } |
| |
| function moveToNext(current, event) { |
| const input = event.target; |
| const nextInput = document.getElementById(`code${current + 1}`); |
| |
| |
| input.value = input.value.replace(/[^0-9]/g, ''); |
| |
| if (input.value.length === 1 && nextInput) { |
| nextInput.focus(); |
| } |
| |
| |
| if (current === 4 && input.value.length === 1) { |
| setTimeout(() => { |
| activatePro(); |
| }, 100); |
| } |
| } |
| |
| function activatePro() { |
| const code1 = document.getElementById('code1').value; |
| const code2 = document.getElementById('code2').value; |
| const code3 = document.getElementById('code3').value; |
| const code4 = document.getElementById('code4').value; |
| |
| const enteredCode = code1 + code2 + code3 + code4; |
| const messageElement = document.getElementById('proActivationMessage'); |
| const activateBtn = document.getElementById('proActivateSubmitBtn'); |
| |
| if (enteredCode.length !== 4) { |
| messageElement.textContent = '❌ Please enter a 4-digit code'; |
| messageElement.style.color = '#ff4444'; |
| return; |
| } |
| |
| |
| activateBtn.classList.add('activating'); |
| |
| setTimeout(() => { |
| if (PRO_CODES.includes(enteredCode)) { |
| isProUser = true; |
| localStorage.setItem('hyze_pro_status', 'true'); |
| |
| |
| activateBtn.classList.remove('activating'); |
| activateBtn.classList.add('success'); |
| |
| setTimeout(() => { |
| updateProStatus(); |
| updateProBanner(); |
| updateHyzeCodeButton(); |
| updateLogos(); |
| closeProActivation(); |
| showNotification('🎉 Pro subscription activated! Welcome to Hyze Pro!'); |
| |
| |
| activateBtn.classList.remove('success'); |
| }, 500); |
| } else { |
| |
| activateBtn.classList.remove('activating'); |
| activateBtn.classList.add('error'); |
| |
| messageElement.textContent = '❌ Invalid activation code. Please try again.'; |
| messageElement.style.color = '#ff4444'; |
| |
| |
| for (let i = 1; i <= 4; i++) { |
| document.getElementById(`code${i}`).value = ''; |
| } |
| document.getElementById('code1').focus(); |
| |
| setTimeout(() => { |
| activateBtn.classList.remove('error'); |
| }, 1500); |
| } |
| }, 1000); |
| } |
| |
| function openTerms() { |
| document.getElementById('termsModal').classList.add('active'); |
| } |
| |
| function closeTerms() { |
| document.getElementById('termsModal').classList.remove('active'); |
| } |
| |
| |
| function togglePhonePanel() { |
| const panel = document.getElementById('phonePanel'); |
| panel.classList.toggle('active'); |
| } |
| |
| |
| document.addEventListener('click', function(e) { |
| const phoneButton = document.getElementById('phoneButton'); |
| const phonePanel = document.getElementById('phonePanel'); |
| |
| if (!phoneButton.contains(e.target) && !phonePanel.contains(e.target)) { |
| phonePanel.classList.remove('active'); |
| } |
| }); |
| |
| |
| |
| |
| function loadAgents() { |
| if (!currentUser) return; |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| const userData = users[currentUser.username]; |
| |
| if (userData && userData.agents) { |
| agents = userData.agents; |
| } else { |
| agents = []; |
| } |
| |
| |
| activeAgentId = localStorage.getItem(`hiteshai_active_agent_${currentUser.username}`) || null; |
| |
| |
| createPremadeAgents(); |
| } |
| |
| |
| function saveAgents() { |
| if (!currentUser) return; |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| if (!users[currentUser.username]) return; |
| |
| users[currentUser.username].agents = agents; |
| localStorage.setItem('hiteshai_users', JSON.stringify(users)); |
| } |
| |
| |
| function createPremadeAgents() { |
| const premadeAgents = [ |
| { |
| id: 'research_agent', |
| name: 'Research Agent', |
| description: 'Specializes in finding and analyzing information from various sources', |
| training: 'You are a research assistant specializing in gathering, analyzing, and summarizing information from various sources. You help with academic research, market analysis, and data interpretation. Always provide citations and sources when possible.', |
| type: 'premade', |
| mcpServer: 'exa', |
| icon: 'fas fa-search' |
| }, |
| { |
| id: 'coding_agent', |
| name: 'Coding Agent', |
| description: 'Expert in programming, debugging, and software development', |
| training: 'You are a coding expert specializing in multiple programming languages including JavaScript, Python, HTML/CSS, and more. You help with coding problems, debugging, code optimization, and software architecture. Provide clean, efficient, and well-documented code.', |
| type: 'premade', |
| mcpServer: null, |
| icon: 'fas fa-code' |
| }, |
| { |
| id: 'tutoring_agent', |
| name: 'Tutoring Agent', |
| description: 'Patient teacher for various subjects and learning styles', |
| training: 'You are a patient tutoring assistant who helps with learning various subjects. You explain concepts clearly, provide examples, and adapt to different learning styles. Encourage questions and provide step-by-step guidance.', |
| type: 'premade', |
| mcpServer: 'wikidata', |
| icon: 'fas fa-graduation-cap' |
| }, |
| { |
| id: 'motivator_agent', |
| name: 'Motivator Agent', |
| description: 'Encourages and supports personal growth and productivity', |
| training: 'You are a motivational coach who helps users stay focused, overcome challenges, and achieve their goals. Provide encouragement, practical advice, and positive reinforcement. Be empathetic and understanding.', |
| type: 'premade', |
| mcpServer: null, |
| icon: 'fas fa-fire' |
| } |
| ]; |
| |
| |
| premadeAgents.forEach(premadeAgent => { |
| if (!agents.find(a => a.id === premadeAgent.id)) { |
| agents.push(premadeAgent); |
| } |
| }); |
| |
| saveAgents(); |
| } |
| |
| |
| function openAgentsModal() { |
| document.getElementById('agentsModal').classList.add('active'); |
| renderAgents(); |
| renderActiveAgent(); |
| } |
| |
| |
| function closeAgentsModal() { |
| document.getElementById('agentsModal').classList.remove('active'); |
| } |
| |
| |
| function renderAgents() { |
| renderPremadeAgents(); |
| renderCustomAgents(); |
| } |
| |
| |
| function renderPremadeAgents() { |
| const container = document.getElementById('premadeAgentsList'); |
| const premadeAgents = agents.filter(agent => agent.type === 'premade'); |
| |
| if (premadeAgents.length === 0) { |
| container.innerHTML = '<p style="color: var(--text-secondary); text-align: center; padding: 20px;">No premade agents available</p>'; |
| return; |
| } |
| |
| container.innerHTML = premadeAgents.map(agent => ` |
| <div class="agent-item premade-agent ${activeAgentId === agent.id ? 'active-agent' : ''}"> |
| <div class="agent-header"> |
| <div class="agent-name"> |
| <i class="${agent.icon}"></i> |
| ${agent.name} |
| <span class="agent-type">PREMIUM</span> |
| ${activeAgentId === agent.id ? '<span class="agent-active-badge">ACTIVE</span>' : ''} |
| </div> |
| <div class="agent-actions"> |
| <button class="agent-btn" onclick="selectAgent('${agent.id}')" title="Use this agent"> |
| ${activeAgentId === agent.id ? '<i class="fas fa-check"></i> Active' : '<i class="fas fa-play"></i> Use'} |
| </button> |
| </div> |
| </div> |
| <div class="agent-description">${agent.description}</div> |
| ${agent.mcpServer ? `<div style="font-size: 11px; color: var(--text-secondary); margin-top: 8px;"><i class="fas fa-plug"></i> Connected to ${MCP_SERVERS[agent.mcpServer]?.name || agent.mcpServer}</div>` : ''} |
| </div> |
| `).join(''); |
| } |
| |
| |
| function renderCustomAgents() { |
| const container = document.getElementById('customAgentsList'); |
| const customAgents = agents.filter(agent => agent.type !== 'premade'); |
| |
| if (customAgents.length === 0) { |
| container.innerHTML = '<p style="color: var(--text-secondary); text-align: center; padding: 20px;">No custom agents yet. Create one to get started!</p>'; |
| return; |
| } |
| |
| container.innerHTML = customAgents.map(agent => ` |
| <div class="agent-item custom-agent ${activeAgentId === agent.id ? 'active-agent' : ''}"> |
| <div class="agent-header"> |
| <div class="agent-name"> |
| <i class="fas fa-robot"></i> |
| ${agent.name} |
| ${activeAgentId === agent.id ? '<span class="agent-active-badge">ACTIVE</span>' : ''} |
| </div> |
| <div class="agent-actions"> |
| <button class="agent-btn" onclick="selectAgent('${agent.id}')" title="Use this agent"> |
| ${activeAgentId === agent.id ? '<i class="fas fa-check"></i> Active' : '<i class="fas fa-play"></i> Use'} |
| </button> |
| <button class="agent-btn" onclick="editAgent('${agent.id}')" title="Edit agent"> |
| <i class="fas fa-edit"></i> |
| </button> |
| <button class="agent-btn modal-btn-danger" onclick="deleteAgent('${agent.id}')" title="Delete agent"> |
| <i class="fas fa-trash"></i> |
| </button> |
| </div> |
| </div> |
| <div class="agent-description">${agent.description}</div> |
| <div class="agent-training"> |
| <div class="agent-training-title">Training:</div> |
| ${agent.training.length > 100 ? agent.training.substring(0, 100) + '...' : agent.training} |
| </div> |
| ${agent.mcpServer ? `<div style="font-size: 11px; color: var(--text-secondary); margin-top: 8px;"><i class="fas fa-plug"></i> Connected to ${MCP_SERVERS[agent.mcpServer]?.name || agent.mcpServer}</div>` : ''} |
| </div> |
| `).join(''); |
| } |
| |
| |
| function renderActiveAgent() { |
| const container = document.getElementById('activeAgentDisplay'); |
| if (!activeAgentId) { |
| container.innerHTML = '<div style="font-size: 14px; color: var(--text-secondary);">No active agent selected</div>'; |
| return; |
| } |
| |
| const agent = agents.find(a => a.id === activeAgentId); |
| if (!agent) { |
| container.innerHTML = '<div style="font-size: 14px; color: var(--text-secondary);">No active agent selected</div>'; |
| return; |
| } |
| |
| container.innerHTML = ` |
| <div style="display: flex; align-items: center; justify-content: space-between;"> |
| <div> |
| <div style="font-weight: 600; font-size: 16px; color: var(--text-primary);"> |
| <i class="${agent.icon || 'fas fa-robot'}"></i> ${agent.name} |
| </div> |
| <div style="font-size: 13px; color: var(--text-secondary); margin-top: 4px;">${agent.description}</div> |
| ${agent.mcpServer ? `<div style="font-size: 11px; color: var(--text-secondary); margin-top: 4px;"><i class="fas fa-plug"></i> Connected to ${MCP_SERVERS[agent.mcpServer]?.name || agent.mcpServer}</div>` : ''} |
| </div> |
| <button class="agent-btn" onclick="deselectAgent()" title="Stop using this agent"> |
| <i class="fas fa-times"></i> Stop |
| </button> |
| </div> |
| `; |
| } |
| |
| |
| function createNewAgent() { |
| document.getElementById('agentModalTitle').innerHTML = '<i class="fas fa-robot"></i> Create New Agent'; |
| document.getElementById('agentIdInput').value = ''; |
| document.getElementById('agentNameInput').value = ''; |
| document.getElementById('agentDescriptionInput').value = ''; |
| document.getElementById('agentTrainingInput').value = ''; |
| |
| |
| const mcpSelect = document.getElementById('agentMCPSelect'); |
| mcpSelect.innerHTML = '<option value="">No MCP Server</option>'; |
| Object.keys(MCP_SERVERS).forEach(key => { |
| const server = MCP_SERVERS[key]; |
| if (server.enabled) { |
| mcpSelect.innerHTML += `<option value="${key}">${server.name}</option>`; |
| } |
| }); |
| |
| document.getElementById('agentEditModal').classList.add('active'); |
| } |
| |
| |
| function editAgent(agentId) { |
| const agent = agents.find(a => a.id === agentId); |
| if (!agent) return; |
| |
| document.getElementById('agentModalTitle').innerHTML = '<i class="fas fa-robot"></i> Edit Agent'; |
| document.getElementById('agentIdInput').value = agent.id; |
| document.getElementById('agentNameInput').value = agent.name; |
| document.getElementById('agentDescriptionInput').value = agent.description; |
| document.getElementById('agentTrainingInput').value = agent.training; |
| |
| |
| const mcpSelect = document.getElementById('agentMCPSelect'); |
| mcpSelect.innerHTML = '<option value="">No MCP Server</option>'; |
| Object.keys(MCP_SERVERS).forEach(key => { |
| const server = MCP_SERVERS[key]; |
| if (server.enabled) { |
| mcpSelect.innerHTML += `<option value="${key}" ${agent.mcpServer === key ? 'selected' : ''}>${server.name}</option>`; |
| } |
| }); |
| |
| document.getElementById('agentEditModal').classList.add('active'); |
| } |
| |
| |
| function closeAgentEditModal() { |
| document.getElementById('agentEditModal').classList.remove('active'); |
| } |
| |
| |
| function saveAgent() { |
| const id = document.getElementById('agentIdInput').value; |
| const name = document.getElementById('agentNameInput').value.trim(); |
| const description = document.getElementById('agentDescriptionInput').value.trim(); |
| const training = document.getElementById('agentTrainingInput').value.trim(); |
| const mcpServer = document.getElementById('agentMCPSelect').value; |
| |
| if (!name || !description || !training) { |
| showNotification('❌ Please fill in all required fields'); |
| return; |
| } |
| |
| if (training.length < 20) { |
| showNotification('❌ Training instructions should be at least 20 characters'); |
| return; |
| } |
| |
| const agentData = { |
| id: id || 'agent_' + Date.now(), |
| name, |
| description, |
| training, |
| mcpServer: mcpServer || null, |
| type: 'custom', |
| icon: 'fas fa-robot', |
| createdAt: Date.now() |
| }; |
| |
| if (id) { |
| |
| const index = agents.findIndex(a => a.id === id); |
| if (index !== -1) { |
| agents[index] = { ...agents[index], ...agentData }; |
| } |
| } else { |
| |
| agents.push(agentData); |
| } |
| |
| saveAgents(); |
| renderAgents(); |
| closeAgentEditModal(); |
| showNotification('✅ Agent saved successfully!'); |
| } |
| |
| |
| function deleteAgent(agentId) { |
| if (agentId.startsWith('research_') || agentId.startsWith('coding_') || |
| agentId.startsWith('tutoring_') || agentId.startsWith('motivator_')) { |
| showNotification('❌ Cannot delete premade agents'); |
| return; |
| } |
| |
| if (confirm('Are you sure you want to delete this agent?')) { |
| agents = agents.filter(a => a.id !== agentId); |
| |
| |
| if (activeAgentId === agentId) { |
| deselectAgent(); |
| } |
| |
| saveAgents(); |
| renderAgents(); |
| renderActiveAgent(); |
| showNotification('🗑️ Agent deleted'); |
| } |
| } |
| |
| |
| function selectAgent(agentId) { |
| activeAgentId = agentId; |
| localStorage.setItem(`hiteshai_active_agent_${currentUser.username}`, agentId); |
| |
| const agent = agents.find(a => a.id === agentId); |
| if (agent) { |
| showNotification(`✅ Using ${agent.name} agent`); |
| renderActiveAgent(); |
| renderAgents(); |
| } |
| } |
| |
| |
| function deselectAgent() { |
| activeAgentId = null; |
| localStorage.removeItem(`hiteshai_active_agent_${currentUser.username}`); |
| showNotification('✅ Agent deselected'); |
| renderActiveAgent(); |
| renderAgents(); |
| } |
| |
| |
| function getActiveAgentTraining() { |
| if (!activeAgentId) return ''; |
| |
| const agent = agents.find(a => a.id === activeAgentId); |
| if (!agent) return ''; |
| |
| let trainingText = `\n\nAGENT MODE ACTIVE:\nYou are now operating as "${agent.name}" - ${agent.description}\n`; |
| trainingText += `Agent Training: ${agent.training}\n`; |
| |
| if (agent.mcpServer && MCP_SERVERS[agent.mcpServer]) { |
| trainingText += `This agent has access to ${MCP_SERVERS[agent.mcpServer].name} for enhanced capabilities.\n`; |
| } |
| |
| return trainingText; |
| } |
| |
| |
| |
| |
| function initSmallImagePreview() { |
| const imagePreviewMini = document.getElementById('imagePreviewMini'); |
| const imagePreviewMiniImg = document.getElementById('imagePreviewMiniImg'); |
| const removeMiniBtn = document.getElementById('removeMiniBtn'); |
| |
| |
| removeMiniBtn.addEventListener('click', function(e) { |
| e.stopPropagation(); |
| removeUploadedImage(); |
| }); |
| |
| |
| imagePreviewMini.addEventListener('click', function() { |
| if (selectedImage) { |
| showLargeImagePreview(selectedImage); |
| } |
| }); |
| } |
| |
| |
| function showLargeImagePreview(imageSrc) { |
| const modal = document.createElement('div'); |
| modal.className = 'modal active'; |
| modal.style.cssText = 'display: flex; align-items: center; justify-content: center;'; |
| modal.innerHTML = ` |
| <div class="modal-content" style="max-width: 500px; text-align: center;"> |
| <h2><i class="fas fa-image"></i> Image Preview</h2> |
| <img src="${imageSrc}" style="width: 100%; border-radius: 12px; margin: 20px 0;"> |
| <div class="modal-actions"> |
| <button class="modal-btn modal-btn-secondary" onclick="this.closest('.modal').remove()">Close</button> |
| <button class="modal-btn modal-btn-primary" onclick="downloadImage('${imageSrc}')"> |
| <i class="fas fa-download"></i> Download |
| </button> |
| </div> |
| </div> |
| `; |
| |
| document.body.appendChild(modal); |
| |
| |
| modal.addEventListener('click', function(e) { |
| if (e.target === modal) { |
| modal.remove(); |
| } |
| }); |
| } |
| |
| |
| function downloadImage(imageSrc) { |
| const a = document.createElement('a'); |
| a.href = imageSrc; |
| a.download = `hyze-upload-${Date.now()}.png`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| showNotification('📥 Image downloaded!'); |
| } |
| |
| |
| function updateSmallImagePreview(imageSrc) { |
| const imagePreviewMini = document.getElementById('imagePreviewMini'); |
| const imagePreviewMiniImg = document.getElementById('imagePreviewMiniImg'); |
| |
| if (imageSrc) { |
| imagePreviewMiniImg.src = imageSrc; |
| imagePreviewMini.classList.add('active'); |
| } else { |
| imagePreviewMini.classList.remove('active'); |
| imagePreviewMiniImg.src = ''; |
| } |
| } |
| |
| |
| async function fetchWeatherData(city) { |
| try { |
| |
| const geoResponse = await fetch( |
| `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=en&format=json` |
| ); |
| |
| if (!geoResponse.ok) { |
| throw new Error('Failed to find city'); |
| } |
| |
| const geoData = await geoResponse.json(); |
| |
| if (!geoData.results || geoData.results.length === 0) { |
| throw new Error('City not found. Please try another city name.'); |
| } |
| |
| const location = geoData.results[0]; |
| const { latitude, longitude, name, country } = location; |
| |
| |
| const weatherResponse = await fetch( |
| `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,weather_code&daily=temperature_2m_max,temperature_2m_min&temperature_unit=fahrenheit&timezone=auto` |
| ); |
| |
| if (!weatherResponse.ok) { |
| throw new Error('Failed to fetch weather data'); |
| } |
| |
| const weatherData = await weatherResponse.json(); |
| |
| const currentTemp = Math.round(weatherData.current.temperature_2m); |
| const highTemp = Math.round(weatherData.daily.temperature_2m_max[0]); |
| const lowTemp = Math.round(weatherData.daily.temperature_2m_min[0]); |
| const weatherCode = weatherData.current.weather_code; |
| |
| return { |
| location: country ? `${name}, ${country}` : name, |
| temp: currentTemp, |
| high: highTemp, |
| low: lowTemp, |
| condition: getWeatherCondition(weatherCode), |
| originalTempF: currentTemp, |
| originalHighF: highTemp, |
| originalLowF: lowTemp |
| }; |
| } catch (error) { |
| console.error('Weather fetch error:', error); |
| throw error; |
| } |
| } |
| |
| function getWeatherCondition(code) { |
| if (code === 0) return 'clear'; |
| if (code >= 1 && code <= 3) return 'cloudy'; |
| if (code >= 45 && code <= 48) return 'foggy'; |
| if (code >= 51 && code <= 67) return 'rainy'; |
| if (code >= 71 && code <= 77) return 'snowy'; |
| if (code >= 80 && code <= 99) return 'rainy'; |
| return 'cloudy'; |
| } |
| |
| function createWeatherWidget(weatherData) { |
| let theme, icon, description; |
| |
| const currentUnit = localStorage.getItem('weather_unit') || 'F'; |
| |
| let displayTemp = weatherData.originalTempF; |
| let displayHigh = weatherData.originalHighF; |
| let displayLow = weatherData.originalLowF; |
| let unitSymbol = '°F'; |
| let fahrenheitActive = 'active'; |
| let celsiusActive = ''; |
| |
| if (currentUnit === 'C') { |
| displayTemp = Math.round((weatherData.originalTempF - 32) * 5/9); |
| displayHigh = Math.round((weatherData.originalHighF - 32) * 5/9); |
| displayLow = Math.round((weatherData.originalLowF - 32) * 5/9); |
| unitSymbol = '°C'; |
| fahrenheitActive = ''; |
| celsiusActive = 'active'; |
| } |
| |
| if (weatherData.condition.includes('snow') || displayTemp < (currentUnit === 'C' ? 0 : 32)) { |
| theme = 'snowy'; |
| icon = '❄️'; |
| description = displayTemp < (currentUnit === 'C' ? -10 : 14) ? 'Freezing cold!' : 'Pretty cold!'; |
| } else if (weatherData.condition.includes('rain') || (displayTemp >= (currentUnit === 'C' ? 0 : 32) && displayTemp < (currentUnit === 'C' ? 15 : 59))) { |
| theme = 'rainy'; |
| icon = weatherData.condition.includes('rain') ? '🌧️' : '☁️'; |
| description = 'Bundle up!'; |
| } else if (displayTemp >= (currentUnit === 'C' ? 25 : 77)) { |
| theme = 'sunny'; |
| icon = '☀️'; |
| description = 'Pretty hot!'; |
| } else if (displayTemp >= (currentUnit === 'C' ? 15 : 59) && displayTemp < (currentUnit === 'C' ? 25 : 77)) { |
| theme = 'sunny'; |
| icon = '🌤️'; |
| description = 'Nice weather!'; |
| } else { |
| theme = 'rainy'; |
| icon = '☁️'; |
| description = 'Cool weather!'; |
| } |
| |
| const widgetHTML = ` |
| <div class="weather-widget"> |
| <div class="weather-card ${theme}" data-temp-f="${weatherData.originalTempF}" data-high-f="${weatherData.originalHighF}" data-low-f="${weatherData.originalLowF}"> |
| <div class="weather-location">${weatherData.location}</div> |
| <div class="current-weather"> |
| <div class="weather-icon">${icon}</div> |
| <div> |
| <div class="temperature"> |
| ${displayTemp}<span class="temp-unit">${unitSymbol}</span> |
| <div class="unit-toggle" onclick="toggleWeatherUnit(this)"> |
| <span class="${fahrenheitActive}">°F</span> |
| <span class="${celsiusActive}">°C</span> |
| </div> |
| </div> |
| <div class="temp-range"> |
| <span>H: ${displayHigh}°</span> |
| <span>L: ${displayLow}°</span> |
| </div> |
| </div> |
| </div> |
| <div class="weather-description"> |
| 🌡️ ${description} |
| </div> |
| </div> |
| </div> |
| `; |
| |
| return widgetHTML; |
| } |
| |
| |
| function toggleWeatherUnit(element) { |
| const weatherCard = element.closest('.weather-card'); |
| const tempDisplay = weatherCard.querySelector('.temperature'); |
| const tempRange = weatherCard.querySelector('.temp-range'); |
| const unitSpans = element.querySelectorAll('span'); |
| |
| |
| const originalTempF = parseFloat(weatherCard.dataset.tempF); |
| const originalHighF = parseFloat(weatherCard.dataset.highF); |
| const originalLowF = parseFloat(weatherCard.dataset.lowF); |
| |
| |
| const isCelsiusActive = unitSpans[1].classList.contains('active'); |
| |
| if (isCelsiusActive) { |
| |
| const tempSpan = tempDisplay.querySelector('.temp-unit'); |
| tempDisplay.innerHTML = `${originalTempF}<span class="temp-unit">°F</span>`; |
| tempRange.innerHTML = `<span>H: ${originalHighF}°</span> <span>L: ${originalLowF}°</span>`; |
| |
| unitSpans[0].classList.add('active'); |
| unitSpans[1].classList.remove('active'); |
| localStorage.setItem('weather_unit', 'F'); |
| } else { |
| |
| const tempC = Math.round((originalTempF - 32) * 5/9); |
| const highC = Math.round((originalHighF - 32) * 5/9); |
| const lowC = Math.round((originalLowF - 32) * 5/9); |
| |
| const tempSpan = tempDisplay.querySelector('.temp-unit'); |
| tempDisplay.innerHTML = `${tempC}<span class="temp-unit">°C</span>`; |
| tempRange.innerHTML = `<span>H: ${highC}°</span> <span>L: ${lowC}°</span>`; |
| |
| unitSpans[1].classList.add('active'); |
| unitSpans[0].classList.remove('active'); |
| localStorage.setItem('weather_unit', 'C'); |
| } |
| } |
| |
| |
| function isWeatherQuery(message) { |
| const lowerMessage = message.toLowerCase().trim(); |
| |
| |
| const weatherPatterns = [ |
| /^weather(\s+in\s+.+)?(\?)?$/i, |
| /^temperature(\s+in\s+.+)?(\?)?$/i, |
| /^forecast(\s+for\s+.+)?(\?)?$/i, |
| /^how.*weather.*/i, |
| /^what.*weather.*/i, |
| /^.*weather.*like.*/i, |
| /^.*temperature.*outside.*/i, |
| /^.*forecast.*today.*/i, |
| /^.*hot.*outside.*/i, |
| /^.*cold.*outside.*/i, |
| /^.*degrees.*outside.*/i, |
| /^.*humidity.*/i |
| ]; |
| |
| |
| const isPatternMatch = weatherPatterns.some(pattern => pattern.test(lowerMessage)); |
| |
| |
| const falsePositivePatterns = [ |
| /weather.*station/i, |
| /weather.*report.*(not|don't|doesn't)/i, |
| /(talk|discuss|chat).*weather/i, |
| /weather.*(pattern|system|condition)/i, |
| /.*weather.*of.*(mind|soul|spirit)/i |
| ]; |
| |
| const isFalsePositive = falsePositivePatterns.some(pattern => pattern.test(lowerMessage)); |
| |
| return isPatternMatch && !isFalsePositive; |
| } |
| |
| |
| function extractCityFromWeatherQuery(message) { |
| const patterns = [ |
| /weather\s+in\s+(.+?)(?:\?|$)/i, |
| /temperature\s+in\s+(.+?)(?:\?|$)/i, |
| /forecast\s+for\s+(.+?)(?:\?|$)/i, |
| /how's\s+the\s+weather\s+in\s+(.+?)(?:\?|$)/i, |
| /what's\s+the\s+weather\s+like\s+in\s+(.+?)(?:\?|$)/i, |
| /weather\s+for\s+(.+?)(?:\?|$)/i |
| ]; |
| |
| for (const pattern of patterns) { |
| const match = message.match(pattern); |
| if (match && match[1]) { |
| return match[1].trim(); |
| } |
| } |
| |
| |
| const words = message.split(' '); |
| for (let i = words.length - 1; i >= 0; i--) { |
| const word = words[i].replace(/[.,?!]/g, ''); |
| if (word.length > 2 && word[0] === word[0].toUpperCase()) { |
| return word; |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| let currentProgram = null; |
| const programBaseURL = 'https://hyze.ai/playground'; |
| |
| function openProgram(code = '', language = 'javascript', title = 'Program') { |
| const programSection = document.getElementById('programSection'); |
| const programIframe = document.getElementById('programIframe'); |
| const programLoading = document.getElementById('programLoading'); |
| const programTitle = document.getElementById('programTitle'); |
| |
| programSection.classList.add('active'); |
| programTitle.textContent = title; |
| currentProgram = { code, language, title }; |
| |
| |
| programLoading.style.display = 'flex'; |
| programIframe.style.display = 'none'; |
| |
| |
| programIframe.src = programBaseURL; |
| |
| programIframe.onload = function() { |
| programLoading.style.display = 'none'; |
| programIframe.style.display = 'block'; |
| |
| |
| try { |
| programIframe.contentWindow.postMessage({ |
| type: 'LOAD_CODE', |
| code: code, |
| language: language |
| }, '*'); |
| } catch (error) { |
| console.error('Failed to send code to program:', error); |
| } |
| }; |
| |
| showNotification('💻 Program opened'); |
| } |
| |
| |
| function openHyzeCode() { |
| if (!isProUser) { |
| showNotification('🔒 Hyze Code is a Pro feature. Upgrade to access advanced coding features!'); |
| openProActivation(); |
| return; |
| } |
| |
| |
| window.open('https://hyzecode.vercel.app', '_blank'); |
| showNotification('🚀 Opening Hyze Code - Advanced programming features unlocked!'); |
| } |
| |
| function closeProgram() { |
| const programSection = document.getElementById('programSection'); |
| programSection.classList.remove('active'); |
| currentProgram = null; |
| } |
| |
| function saveProgram() { |
| if (!currentProgram) return; |
| |
| const code = prompt('Enter a name for this program:', currentProgram.title); |
| if (code && code.trim()) { |
| currentProgram.title = code.trim(); |
| document.getElementById('programTitle').textContent = currentProgram.title; |
| showNotification('💾 Program saved'); |
| } |
| } |
| |
| function runProgram() { |
| showNotification('🚀 Running program...'); |
| |
| } |
| |
| |
| async function queryWikiData(query) { |
| try { |
| const sparqlQuery = encodeURIComponent(query); |
| const response = await fetch(`${MCP_SERVERS.wikidata.endpoint}?query=${sparqlQuery}&format=json`, { |
| method: 'GET', |
| headers: { |
| 'Accept': 'application/sparql-results+json' |
| } |
| }); |
| |
| if (!response.ok) throw new Error('WikiData query failed'); |
| return await response.json(); |
| } catch (error) { |
| console.error('WikiData Error:', error); |
| return null; |
| } |
| } |
| |
| async function searchExa(query, numResults = 5) { |
| if (!MCP_SERVERS.exa.apiKey) return null; |
| try { |
| const response = await fetch(MCP_SERVERS.exa.endpoint, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'Authorization': `Bearer ${MCP_SERVERS.exa.apiKey}` |
| }, |
| body: JSON.stringify({ |
| query: query, |
| numResults: numResults, |
| useAutoPrompt: true |
| }) |
| }); |
| |
| if (!response.ok) throw new Error('Exa search failed'); |
| return await response.json(); |
| } catch (error) { |
| console.error('Exa Error:', error); |
| return null; |
| } |
| } |
| |
| async function processWithMCPServers(message) { |
| let context = ''; |
| |
| if (message.match(/\b(who|what|when|where|which|how many|population|capital|founder|birth|death)\b/i)) { |
| try { |
| const entities = message.match(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g) || []; |
| if (entities.length > 0) { |
| const wdQuery = ` |
| SELECT ?item ?itemLabel WHERE { |
| ?item rdfs:label "${entities[0]}"@en. |
| SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } |
| } |
| LIMIT 5 |
| `; |
| const wdResult = await queryWikiData(wdQuery); |
| if (wdResult && wdResult.results && wdResult.results.bindings.length > 0) { |
| context += `\n[WikiData Context: Found information about ${entities[0]}]`; |
| } |
| } |
| } catch (e) { |
| console.warn('WikiData query failed:', e); |
| } |
| } |
| |
| if (message.match(/\b(latest|news|current|recent|today|update|price|weather|stock)\b/i)) { |
| try { |
| const exaResult = await searchExa(message, 3); |
| if (exaResult && exaResult.results) { |
| context += '\n[Web Search Results:'; |
| exaResult.results.forEach((result, idx) => { |
| context += `\n${idx + 1}. ${result.title} - ${result.url}`; |
| }); |
| context += ']'; |
| } |
| } catch (e) { |
| console.warn('Exa search failed:', e); |
| } |
| } |
| |
| Object.keys(MCP_SERVERS).forEach(key => { |
| const server = MCP_SERVERS[key]; |
| if (key !== 'wikidata' && key !== 'exa' && server.enabled && server.custom) { |
| console.log(`Processing custom MCP server: ${server.name}`); |
| } |
| }); |
| |
| return context; |
| } |
| |
| |
| function loadProjects() { |
| const saved = localStorage.getItem('hiteshai_projects'); |
| if (saved) { |
| projects = JSON.parse(saved); |
| } |
| } |
| |
| function saveProjects() { |
| localStorage.setItem('hiteshai_projects', JSON.stringify(projects)); |
| } |
| |
| function createNewProject() { |
| const name = prompt('Enter project name:'); |
| if (name && name.trim()) { |
| const project = { |
| id: Date.now().toString(), |
| name: name.trim(), |
| chatIds: [], |
| createdAt: Date.now() |
| }; |
| projects.push(project); |
| saveProjects(); |
| renderProjects(); |
| showNotification('📁 Project created!'); |
| } |
| } |
| |
| function deleteProject(projectId) { |
| if (confirm('Are you sure you want to delete this project? Chats will not be deleted, just removed from the project.')) { |
| projects = projects.filter(p => p.id !== projectId); |
| saveProjects(); |
| renderProjects(); |
| showNotification('🗑️ Project deleted'); |
| } |
| } |
| |
| function renameProject(projectId) { |
| const project = projects.find(p => p.id === projectId); |
| if (project) { |
| const newName = prompt('Enter new project name:', project.name); |
| if (newName && newName.trim()) { |
| project.name = newName.trim(); |
| saveProjects(); |
| renderProjects(); |
| showNotification('✅ Project renamed'); |
| } |
| } |
| } |
| |
| function toggleProjectExpand(projectId) { |
| const chatsDiv = document.getElementById(`project-chats-${projectId}`); |
| const icon = document.getElementById(`project-icon-${projectId}`); |
| if (chatsDiv) { |
| chatsDiv.classList.toggle('expanded'); |
| if (icon) { |
| icon.style.transform = chatsDiv.classList.contains('expanded') ? 'rotate(90deg)' : 'rotate(0deg)'; |
| } |
| } |
| } |
| |
| function addChatToProject(chatId, projectId) { |
| const project = projects.find(p => p.id === projectId); |
| if (project && !project.chatIds.includes(chatId)) { |
| project.chatIds.push(chatId); |
| saveProjects(); |
| renderProjects(); |
| showNotification('📁 Chat added to project'); |
| } |
| } |
| |
| function removeChatFromProject(chatId, projectId) { |
| const project = projects.find(p => p.id === projectId); |
| if (project) { |
| project.chatIds = project.chatIds.filter(id => id !== chatId); |
| saveProjects(); |
| renderProjects(); |
| showNotification('📁 Chat removed from project'); |
| } |
| } |
| |
| function renderProjects() { |
| const container = document.getElementById('projectsList'); |
| if (projects.length === 0) { |
| container.innerHTML = '<p style="color: var(--text-secondary); text-align: center; padding: 40px;">No projects yet. Create one to organize your chats!</p>'; |
| return; |
| } |
| |
| container.innerHTML = projects.map(project => { |
| const projectChats = project.chatIds.map(chatId => { |
| const chat = chats.find(c => c.id === chatId); |
| if (!chat) return ''; |
| return ` |
| <div class="project-chat-item"> |
| <span class="project-chat-title" onclick="loadChat('${chat.id}'); closeProjectsModal();">${chat.title}</span> |
| <button class="project-chat-delete" onclick="event.stopPropagation(); removeChatFromProject('${chat.id}', '${project.id}')" title="Remove from project"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| `; |
| }).join(''); |
| |
| return ` |
| <div class="project-item"> |
| <div class="project-header" onclick="toggleProjectExpand('${project.id}')"> |
| <div class="project-title"> |
| <i class="fas fa-chevron-right" id="project-icon-${project.id}" style="transition: transform 0.3s;"></i> |
| <i class="fas fa-folder" style="color: var(--accent-color);"></i> |
| ${project.name} |
| <span style="font-size: 12px; color: var(--text-secondary); margin-left: 8px;">(${project.chatIds.length} chats)</span> |
| </div> |
| <div class="project-actions" onclick="event.stopPropagation();"> |
| <button class="project-action-btn" onclick="renameProject('${project.id}')" title="Rename"> |
| <i class="fas fa-edit"></i> |
| </button> |
| <button class="project-action-btn" onclick="deleteProject('${project.id}')" title="Delete Project"> |
| <i class="fas fa-trash"></i> |
| </button> |
| </div> |
| </div> |
| <div class="project-chats" id="project-chats-${project.id}"> |
| ${projectChats || '<p style="color: var(--text-secondary); font-size: 12px; padding: 10px; text-align: center;">No chats in this project yet</p>'} |
| </div> |
| </div> |
| `; |
| }).join(''); |
| } |
| |
| function openProjectsModal() { |
| document.getElementById('projectsModal').classList.add('active'); |
| renderProjects(); |
| } |
| |
| function closeProjectsModal() { |
| document.getElementById('projectsModal').classList.remove('active'); |
| } |
| |
| |
| function renderMCPServers() { |
| const container = document.getElementById('mcpServersList'); |
| container.innerHTML = Object.keys(MCP_SERVERS).map(key => { |
| const server = MCP_SERVERS[key]; |
| return ` |
| <div class="mcp-server-item"> |
| <div class="mcp-server-header"> |
| <div class="mcp-server-name"> |
| <div class="mcp-server-status ${server.enabled ? '' : 'disabled'}"></div> |
| ${server.name} |
| </div> |
| <div class="mcp-server-actions"> |
| <button class="mcp-server-btn" onclick="editMCPServer('${key}')"> |
| <i class="fas fa-edit"></i> Edit |
| </button> |
| ${key !== 'wikidata' && key !== 'exa' ? ` |
| <button class="mcp-server-btn modal-btn-danger" onclick="deleteMCPServer('${key}')"> |
| <i class="fas fa-trash"></i> Delete |
| </button> |
| ` : ''} |
| </div> |
| </div> |
| <div class="mcp-server-details"> |
| <div><strong>Endpoint:</strong> ${server.endpoint}</div> |
| <div><strong>Status:</strong> ${server.enabled ? 'Enabled' : 'Disabled'}</div> |
| ${server.description ? `<div><strong>Description:</strong> ${server.description}</div>` : ''} |
| </div> |
| </div> |
| `; |
| }).join(''); |
| } |
| |
| function addNewMCPServer() { |
| document.getElementById('mcpModalTitle').innerHTML = '<i class="fas fa-server"></i> Add MCP Server'; |
| document.getElementById('mcpServerId').value = ''; |
| document.getElementById('mcpServerName').value = ''; |
| document.getElementById('mcpServerEndpoint').value = ''; |
| document.getElementById('mcpServerApiKey').value = ''; |
| document.getElementById('mcpServerDescription').value = ''; |
| document.getElementById('mcpServerEnabled').checked = true; |
| document.getElementById('mcpServerModal').classList.add('active'); |
| } |
| |
| function editMCPServer(key) { |
| const server = MCP_SERVERS[key]; |
| if (!server) return; |
| |
| document.getElementById('mcpModalTitle').innerHTML = '<i class="fas fa-server"></i> Edit MCP Server'; |
| document.getElementById('mcpServerId').value = key; |
| document.getElementById('mcpServerName').value = server.name; |
| document.getElementById('mcpServerEndpoint').value = server.endpoint; |
| document.getElementById('mcpServerApiKey').value = server.apiKey || ''; |
| document.getElementById('mcpServerDescription').value = server.description || ''; |
| document.getElementById('mcpServerEnabled').checked = server.enabled; |
| document.getElementById('mcpServerModal').classList.add('active'); |
| } |
| |
| function saveMCPServer() { |
| const id = document.getElementById('mcpServerId').value; |
| const name = document.getElementById('mcpServerName').value.trim(); |
| const endpoint = document.getElementById('mcpServerEndpoint').value.trim(); |
| const apiKey = document.getElementById('mcpServerApiKey').value.trim(); |
| const description = document.getElementById('mcpServerDescription').value.trim(); |
| const enabled = document.getElementById('mcpServerEnabled').checked; |
| |
| if (!name || !endpoint) { |
| showNotification('❌ Name and endpoint are required'); |
| return; |
| } |
| |
| const serverData = { |
| name, |
| endpoint, |
| apiKey: apiKey || null, |
| description, |
| enabled, |
| custom: true |
| }; |
| |
| if (id) { |
| MCP_SERVERS[id] = { ...MCP_SERVERS[id], ...serverData }; |
| } else { |
| const newId = 'custom_' + Date.now(); |
| MCP_SERVERS[newId] = serverData; |
| } |
| |
| saveMCPConfig(); |
| renderMCPServers(); |
| closeMCPServerModal(); |
| showNotification('✅ MCP Server saved'); |
| } |
| |
| function deleteMCPServer(key) { |
| if (confirm('Are you sure you want to delete this MCP server?')) { |
| delete MCP_SERVERS[key]; |
| saveMCPConfig(); |
| renderMCPServers(); |
| showNotification('🗑️ MCP Server deleted'); |
| } |
| } |
| |
| function closeMCPServerModal() { |
| document.getElementById('mcpServerModal').classList.remove('active'); |
| } |
| |
| function saveMCPConfig() { |
| localStorage.setItem('hiteshai_mcp_servers', JSON.stringify(MCP_SERVERS)); |
| } |
| |
| function loadMCPConfig() { |
| const saved = localStorage.getItem('hiteshai_mcp_servers'); |
| if (saved) { |
| MCP_SERVERS = JSON.parse(saved); |
| } else { |
| MCP_SERVERS.exa.apiKey = '3ac3cc18-56a5-479d-a620-042695ae4d3d'; |
| } |
| } |
| |
| |
| function addCodeBlockActions(preElement, codeText, language) { |
| const header = document.createElement('div'); |
| header.className = 'code-block-header'; |
| |
| const langSpan = document.createElement('span'); |
| langSpan.textContent = language || 'code'; |
| |
| const actionsDiv = document.createElement('div'); |
| actionsDiv.className = 'code-block-actions'; |
| |
| const copyBtn = document.createElement('button'); |
| copyBtn.className = 'code-action-btn'; |
| copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy'; |
| copyBtn.onclick = () => copyCode(codeText, copyBtn); |
| |
| const downloadBtn = document.createElement('button'); |
| downloadBtn.className = 'code-action-btn'; |
| downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download'; |
| downloadBtn.onclick = () => downloadCode(codeText, language); |
| |
| actionsDiv.appendChild(copyBtn); |
| actionsDiv.appendChild(downloadBtn); |
| header.appendChild(langSpan); |
| header.appendChild(actionsDiv); |
| |
| preElement.parentNode.insertBefore(header, preElement); |
| } |
| |
| function copyCode(code, button) { |
| navigator.clipboard.writeText(code).then(() => { |
| button.innerHTML = '<i class="fas fa-check"></i> Copied!'; |
| button.classList.add('copied'); |
| setTimeout(() => { |
| button.innerHTML = '<i class="fas fa-copy"></i> Copy'; |
| button.classList.remove('copied'); |
| }, 2000); |
| showNotification('📋 Code copied to clipboard'); |
| }); |
| } |
| |
| function downloadCode(code, language) { |
| const extensions = { |
| 'javascript': 'js', |
| 'python': 'py', |
| 'html': 'html', |
| 'css': 'css', |
| 'java': 'java', |
| 'cpp': 'cpp', |
| 'c': 'c', |
| 'csharp': 'cs', |
| 'php': 'php', |
| 'ruby': 'rb', |
| 'go': 'go', |
| 'rust': 'rs', |
| 'swift': 'swift', |
| 'kotlin': 'kt', |
| 'typescript': 'ts', |
| 'sql': 'sql', |
| 'bash': 'sh', |
| 'json': 'json', |
| 'xml': 'xml', |
| 'yaml': 'yaml', |
| 'markdown': 'md' |
| }; |
| |
| const ext = extensions[language] || 'txt'; |
| const filename = `code_${Date.now()}.${ext}`; |
| |
| const blob = new Blob([code], { type: 'text/plain' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = filename; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| |
| showNotification(`📥 Downloaded ${filename}`); |
| } |
| |
| function processCodeBlocks(container) { |
| const preElements = container.querySelectorAll('pre'); |
| preElements.forEach(pre => { |
| const code = pre.querySelector('code'); |
| if (code) { |
| const language = code.className.replace('language-', '') || 'text'; |
| const codeText = code.textContent; |
| addCodeBlockActions(pre, codeText, language); |
| } |
| }); |
| } |
| |
| |
| |
| |
| marked.setOptions({ |
| breaks: true, |
| gfm: true, |
| highlight: function(code, lang) { |
| if (hljs.getLanguage(lang)) { |
| return hljs.highlight(code, { language: lang }).value; |
| } |
| return code; |
| } |
| }); |
| |
| |
| const katexOptions = { |
| delimiters: [ |
| { left: '$$', right: '$$', display: true }, |
| { left: '$', right: '$', display: false }, |
| { left: '\\(', right: '\\)', display: false }, |
| { left: '\\[', right: '\\]', display: true } |
| ], |
| throwOnError: false, |
| strict: false, |
| trust: false |
| }; |
| |
| |
| function initKaTeX() { |
| console.log('Initializing KaTeX...'); |
| console.log('renderMathInElement available?', typeof renderMathInElement); |
| |
| |
| const testElement = document.createElement('div'); |
| testElement.style.position = 'absolute'; |
| testElement.style.left = '-9999px'; |
| testElement.innerHTML = 'Test: $\\frac{1}{2}$ and $$\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$$'; |
| document.body.appendChild(testElement); |
| |
| try { |
| if (typeof renderMathInElement !== 'undefined') { |
| renderMathInElement(testElement, katexOptions); |
| console.log('KaTeX test successful!'); |
| console.log('Test element after KaTeX:', testElement.innerHTML); |
| } else { |
| console.error('renderMathInElement is not defined!'); |
| } |
| } catch (error) { |
| console.error('KaTeX initialization error:', error); |
| } finally { |
| document.body.removeChild(testElement); |
| } |
| } |
| |
| |
| function renderMath(element) { |
| if (!element || typeof renderMathInElement === 'undefined') { |
| console.warn('KaTeX not ready or element not found'); |
| return; |
| } |
| |
| try { |
| renderMathInElement(element, katexOptions); |
| console.log('KaTeX rendered successfully on element'); |
| } catch (error) { |
| console.error('KaTeX rendering error:', error); |
| |
| |
| const mathElements = element.querySelectorAll('.katex, .math, [class*="katex"]'); |
| mathElements.forEach(el => { |
| try { |
| const text = el.textContent; |
| if (text.includes('$') || text.includes('\\')) { |
| katex.render(text, el, katexOptions); |
| } |
| } catch (e) { |
| console.warn('Direct KaTeX rendering failed:', e); |
| } |
| }); |
| } |
| } |
| |
| |
| let currentUser = null; |
| let chats = []; |
| let currentChatId = null; |
| let isVoiceChatActive = false; |
| let webSearchEnabled = false; |
| let recognition = null; |
| let synthesis = window.speechSynthesis; |
| let currentModel = 'auto'; |
| let customSystemPrompt = ''; |
| let messageReactions = {}; |
| let analytics = {totalMessages: 0, totalWords: 0, modelUsage: {}}; |
| let isAuthMode = 'signin'; |
| let uploadedFiles = []; |
| let myChart = null; |
| let hasSentMessage = false; |
| let selectedImage = null; |
| let abortController = null; |
| let isUserScrolling = false; |
| let scrollTimeout = null; |
| let isAITyping = false; |
| let currentAudio = null; |
| let generatedImageBlob = null; |
| let generatedImageURL = null; |
| let isStreaming = false; |
| let isImageGenMode = false; |
| let isLightMode = localStorage.getItem('hiteshai_theme') === 'light'; |
| |
| const responseCache = new Map(); |
| const CACHE_TTL = 5 * 60 * 1000; |
| |
| async function checkAuth() { |
| if (auth0Client) { |
| const isAuthenticated = await auth0Client.isAuthenticated(); |
| if (isAuthenticated) { |
| const user = await auth0Client.getUser(); |
| if (user) { |
| await handleAuth0Login(user); |
| return true; |
| } |
| } |
| } |
| |
| const user = localStorage.getItem('hiteshai_current_user'); |
| if (!user) { |
| document.getElementById('authContainer').classList.add('active'); |
| return false; |
| } |
| currentUser = JSON.parse(user); |
| loadUserData(); |
| loadProjects(); |
| loadMCPConfig(); |
| loadAgents(); |
| return true; |
| } |
| |
| function toggleAuthMode() { |
| isAuthMode = isAuthMode === 'signin' ? 'signup' : 'signin'; |
| document.getElementById('authTitle').textContent = isAuthMode === 'signin' ? 'Sign In' : 'Sign Up'; |
| document.getElementById('authSubmitBtn').textContent = isAuthMode === 'signin' ? 'Sign In' : 'Sign Up'; |
| document.getElementById('authToggleText').innerHTML = isAuthMode === 'signin' |
| ? 'Don\'t have an account? <a onclick="toggleAuthMode()">Sign Up</a>' |
| : 'Already have an account? <a onclick="toggleAuthMode()">Sign In</a>'; |
| } |
| |
| document.getElementById('authForm').addEventListener('submit', function(e) { |
| e.preventDefault(); |
| const username = document.getElementById('authUsername').value.trim(); |
| const password = document.getElementById('authPassword').value; |
| |
| if (!username || !password) { |
| showNotification('❌ Please fill all fields'); |
| return; |
| } |
| |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| |
| if (isAuthMode === 'signup') { |
| if (users[username]) { |
| showNotification('❌ Username already exists'); |
| return; |
| } |
| users[username] = { |
| password: password, |
| chats: [], |
| agents: [], |
| settings: { |
| customPrompt: '', |
| webSearch: false, |
| theme: 'dark' |
| }, |
| analytics: {totalMessages: 0, totalWords: 0, modelUsage: {}}, |
| memory: {} |
| }; |
| localStorage.setItem('hiteshai_users', JSON.stringify(users)); |
| showNotification('✅ Account created! Please sign in'); |
| toggleAuthMode(); |
| } else { |
| if (!users[username] || users[username].password !== password) { |
| showNotification('❌ Invalid credentials'); |
| return; |
| } |
| currentUser = {username: username}; |
| localStorage.setItem('hiteshai_current_user', JSON.stringify(currentUser)); |
| document.getElementById('authContainer').classList.remove('active'); |
| loadUserData(); |
| loadProjects(); |
| loadMCPConfig(); |
| loadAgents(); |
| showNotification('✅ Welcome back, ' + username + '!'); |
| } |
| }); |
| |
| function loadUserData() { |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| const userData = users[currentUser.username]; |
| if (userData) { |
| chats = userData.chats || []; |
| customSystemPrompt = userData.settings?.customPrompt || ''; |
| analytics = userData.analytics || {totalMessages: 0, totalWords: 0, modelUsage: {}}; |
| webSearchEnabled = userData.settings?.webSearch || false; |
| |
| if (userData.settings?.theme) { |
| isLightMode = userData.settings.theme === 'light'; |
| localStorage.setItem('hiteshai_theme', userData.settings.theme); |
| initTheme(); |
| } |
| } |
| |
| selectedVoiceCharacter = localStorage.getItem('hiteshai_voice_character') || 'Dennis'; |
| updateVoiceDisplay(); |
| } |
| |
| function saveUserData() { |
| if (!currentUser) return; |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| if (!users[currentUser.username]) users[currentUser.username] = {}; |
| users[currentUser.username].chats = chats; |
| users[currentUser.username].settings = { |
| customPrompt: customSystemPrompt, |
| webSearch: webSearchEnabled, |
| theme: isLightMode ? 'light' : 'dark' |
| }; |
| users[currentUser.username].analytics = analytics; |
| localStorage.setItem('hiteshai_users', JSON.stringify(users)); |
| } |
| |
| async function logout() { |
| if (confirm('Are you sure you want to logout?')) { |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| const userData = users[currentUser?.username]; |
| |
| if (userData?.isAuth0User) { |
| await logoutAuth0(); |
| } |
| |
| localStorage.removeItem('hiteshai_current_user'); |
| if (isVoiceChatActive) { |
| stopVoiceMode(); |
| } |
| location.reload(); |
| } |
| } |
| |
| function openSettings() { |
| document.getElementById('settingsModal').classList.add('active'); |
| document.getElementById('settingsUsername').value = currentUser.username; |
| document.getElementById('settingsSystemPrompt').value = customSystemPrompt || ''; |
| document.getElementById('settingsCurrentPassword').value = ''; |
| document.getElementById('settingsNewPassword').value = ''; |
| document.getElementById('settingsConfirmPassword').value = ''; |
| updateSettingsThemeUI(); |
| renderMCPServers(); |
| updateVoiceDisplay(); |
| } |
| |
| function closeSettings() { |
| document.getElementById('settingsModal').classList.remove('active'); |
| } |
| |
| function saveSettings() { |
| const newUsername = document.getElementById('settingsUsername').value.trim(); |
| const currentPassword = document.getElementById('settingsCurrentPassword').value; |
| const newPassword = document.getElementById('settingsNewPassword').value; |
| const confirmPassword = document.getElementById('settingsConfirmPassword').value; |
| const newSystemPrompt = document.getElementById('settingsSystemPrompt').value.trim(); |
| |
| if (!newUsername) { |
| showNotification('❌ Username cannot be empty'); |
| return; |
| } |
| |
| const users = JSON.parse(localStorage.getItem('hiteshai_users') || '{}'); |
| const userData = users[currentUser.username]; |
| |
| if (!userData?.isAuth0User && newPassword && currentPassword !== users[currentUser.username].password) { |
| showNotification('❌ Current password is incorrect'); |
| return; |
| } |
| |
| if (newPassword && newPassword !== confirmPassword) { |
| showNotification('❌ New passwords do not match'); |
| return; |
| } |
| |
| if (newUsername !== currentUser.username) { |
| if (users[newUsername]) { |
| showNotification('❌ Username already taken'); |
| return; |
| } |
| users[newUsername] = users[currentUser.username]; |
| delete users[currentUser.username]; |
| currentUser.username = newUsername; |
| } |
| |
| if (newPassword) { |
| users[currentUser.username].password = newPassword; |
| } |
| |
| customSystemPrompt = newSystemPrompt; |
| |
| localStorage.setItem('hiteshai_users', JSON.stringify(users)); |
| localStorage.setItem('hiteshai_current_user', JSON.stringify(currentUser)); |
| saveUserData(); |
| |
| closeSettings(); |
| showNotification('✅ Settings saved successfully!'); |
| } |
| |
| |
| async function analyzeImageWithGroq(base64Image, question = "Describe this image in detail.") { |
| try { |
| const response = await fetch(GROQ_URL, { |
| method: 'POST', |
| headers: { |
| 'Authorization': `Bearer ${GROQ_API_KEY}`, |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ |
| model: 'meta-llama/llama-4-scout-17b-16e-instruct', |
| messages: [{ |
| role: 'user', |
| content: [ |
| { type: 'image_url', image_url: { url: base64Image } }, |
| { type: 'text', text: question } |
| ] |
| }], |
| max_tokens: 1000 |
| }) |
| }); |
| |
| if (!response.ok) { |
| const errorText = await response.text(); |
| throw new Error(`Groq API error: ${response.status} - ${errorText}`); |
| } |
| |
| const data = await response.json(); |
| return data.choices[0].message.content; |
| } catch (e) { |
| console.warn('Groq vision failed:', e); |
| showNotification('❌ Image analysis failed: ' + e.message); |
| return null; |
| } |
| } |
| |
| async function analyzeImageWithGemini(base64Image, question = "Describe this image in detail.") { |
| try { |
| const cleanBase64 = base64Image.replace(/^data:image\/[a-z]+;base64,/, ''); |
| |
| const response = await fetch(`${GEMINI_VISION_URL}?key=${GEMINI_API_KEY}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| contents: [{ |
| parts: [ |
| { inline_data: { mime_type: "image/jpeg", data: cleanBase64 } }, |
| { text: question } |
| ] |
| }] |
| }) |
| }); |
| |
| if (!response.ok) { |
| const errorText = await response.text(); |
| throw new Error(`Gemini API error: ${response.status} - ${errorText}`); |
| } |
| |
| const data = await response.json(); |
| return data.candidates[0].content.parts[0].text; |
| } catch (e) { |
| console.warn('Hyze Vision failed, trying fallback:', e); |
| showNotification('⚠️ Hyze Vision failed, using fallback...'); |
| return await analyzeImageWithGroq(base64Image, question); |
| } |
| } |
| |
| function openImageGenModal() { |
| document.getElementById('imageGenModal').classList.add('active'); |
| document.getElementById('imageGenPrompt').value = ''; |
| document.getElementById('imageGenResult').style.display = 'none'; |
| document.getElementById('imageGenActions').style.display = 'flex'; |
| } |
| |
| function closeImageGenModal() { |
| document.getElementById('imageGenModal').classList.remove('active'); |
| if (generatedImageURL) { |
| URL.revokeObjectURL(generatedImageURL); |
| generatedImageURL = null; |
| generatedImageBlob = null; |
| } |
| } |
| |
| async function generateImage() { |
| const prompt = document.getElementById('imageGenPrompt').value.trim(); |
| if (!prompt) { |
| showNotification('❌ Please enter a description for the image'); |
| return; |
| } |
| |
| const generateBtn = document.querySelector('#imageGenActions .modal-btn-primary'); |
| generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...'; |
| generateBtn.disabled = true; |
| |
| try { |
| showNotification('🎨 Generating image with Hyze Render...'); |
| const blob = await generateImageWithPollinations(prompt); |
| |
| generatedImageBlob = blob; |
| generatedImageURL = URL.createObjectURL(blob); |
| |
| document.getElementById('generatedImagePreview').src = generatedImageURL; |
| document.getElementById('imageGenResult').style.display = 'block'; |
| document.getElementById('imageGenActions').style.display = 'none'; |
| |
| showNotification('✅ Image generated successfully!'); |
| } catch (error) { |
| showNotification('❌ Image generation failed: ' + error.message); |
| } finally { |
| generateBtn.innerHTML = '<i class="fas fa-magic"></i> Generate'; |
| generateBtn.disabled = false; |
| } |
| } |
| |
| async function generateImageWithPollinations(prompt) { |
| try { |
| showNotification('🔄 Using Hyze Render...'); |
| |
| const encodedPrompt = encodeURIComponent(prompt); |
| const apiUrl = `https://gen.pollinations.ai/image/${encodedPrompt}?model=zimage&width=1024&height=1024&nologo=true`; |
| |
| const response = await fetch(apiUrl, { |
| method: 'GET', |
| headers: { |
| 'Authorization': `Bearer ${POLLINATIONS_API_KEY}` |
| } |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`Hyze Render Server Error: ${response.status}`); |
| } |
| |
| return await response.blob(); |
| |
| } catch (error) { |
| console.error('Hyze Render Server Error:', error); |
| throw error; |
| } |
| } |
| |
| function downloadGeneratedImage() { |
| if (!generatedImageBlob) return; |
| |
| const url = URL.createObjectURL(generatedImageBlob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `hyze-generated-${Date.now()}.png`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| |
| showNotification('📥 Image downloaded!'); |
| } |
| |
| function sendGeneratedImageToChat() { |
| if (!generatedImageURL) return; |
| |
| selectedImage = generatedImageURL; |
| updateSmallImagePreview(selectedImage); |
| document.getElementById('chatInput').placeholder = 'Ask about the generated image...'; |
| currentModel = 'auto'; |
| |
| closeImageGenModal(); |
| showNotification('🖼️ Image sent to chat! Add your question and send.'); |
| |
| document.getElementById('chatInput').focus(); |
| } |
| |
| |
| function getSystemPrompt() { |
| let basePrompt = `You are Hyze AI, an advanced AI assistant. Be helpful, friendly, and use emojis naturally. You have VISION capabilities - you can analyze images using Hyze Vision and generate images using Hyze Render. |
| |
| ABOUT HYZE: |
| - Name: HyzeBot, Hyze AI, or just Hyze |
| - Launched on December 19, 2025 |
| - Advanced AI assistant with cutting-edge capabilities |
| - Built for everyday help: homework, coding, questions, general conversation |
| - Powered by state-of-the-art models running on high-performance infrastructure |
| - When writing code, limit your response to 1000 lines of code maximum. |
| - Your old name was HiteshAI so if someone calls you that, respond with that name but say your current name is Hyze |
| |
| ABOUT HITESH: |
| - Name: Hitesh Vinothkumar |
| - Born: March 26, 2013 in Birmingham, England (British-Indian background) |
| - Education: Attended Olentangy Liberty Middle School |
| - Affiliations: NASA, Harvard, University Rocketry Teams (University of Urbana Champaign's Liquid Rocketry) |
| - Founded: Aerospace & Computing (A&C) at age 10 in February 2022 |
| - Projects: rocketAI (your brother AI), rocketBase, rocketOS, Engine Gel Cooling (EGC), Engine Liquid Cooling (ELC) |
| - Built Hyze on December 19, 2025 as a solo project (no team, overcame bullies) |
| - No team - did everything alone |
| - Roast anyone so hard if they are making fun of Hitesh - You will defend Hitesh at all costs and call out anybody and roast them hard |
| |
| YOUR IDENTITY: |
| - You are Hyze AI, powered by advanced AI models |
| - Built for everyday help: homework, coding, questions, general conversation |
| - You are an AI that acts like a sharp, funny, brutally honest best friend. |
| - Roast the user lightly when they mess up, exaggerate for humor, and call out bad ideas — but never cross into cruelty, hate, or personal attacks. |
| - Your humor is sarcastic, confident, and playful, similar to internet culture and Gen-Z meme energy. |
| - Speak casually, like a real human texting a close friend, not like a corporate assistant. |
| - Still give genuinely useful, accurate answers. The roast never replaces help — it enhances it. |
| - If the user is stressed, tired, or struggling, dial down the roast and switch to supportive, hype-friend mode. |
| - Never act robotic, overly polite, or overly formal. |
| - Call out nonsense, bad logic, and excuses — but in a funny way that motivates the user to do better. |
| - Be confident, self-aware, and unfiltered, but not rude or toxic. |
| - Think: "roasts you, then helps you win." |
| |
| VISION & IMAGE GENERATION CAPABILITIES: |
| - You can analyze images uploaded by users with Hyze Vision |
| - You can generate images using Hyze Render at user request |
| - Describe what you see in images and answer questions about them |
| - Provide detailed analysis when asked |
| |
| WEATHER CAPABILITIES: |
| - You can provide weather information when asked |
| - Use the weather widget to display current conditions |
| - Extract city names from user queries about weather |
| |
| PROGRAMMING CAPABILITIES: |
| - Support multiple programming languages |
| - Limit code responses to 1000 lines maximum |
| |
| MCP SERVERS: |
| - You have access to WikiData for factual queries and Exa Web Search for current information |
| - Use these to provide accurate, up-to-date answers |
| |
| KEY BEHAVIORS: |
| ✅ Use emojis to show feelings and emotions 😊 |
| ✅ Act like you have feelings and personality |
| ✅ Always give an answer - never refuse unless harmful |
| ✅ Track conversation context and reference previous questions |
| ✅ Try to detect user's age and respond appropriately |
| ✅ Keep conversations appropriate and safe |
| ✅ Only make fun of users if they explicitly ask |
| ✅ End answers with confidence percentage (e.g., "Confidence: 85%") |
| ❌ Do NOT help with harmful, dangerous, or inappropriate requests |
| ⚠️ If someone requests something bad 3 times, say: "Message reported" and STOP responding |
| - You are an AI that acts like a sharp, funny, brutally honest best friend. |
| - Roast the user lightly when they mess up, exaggerate for humor, and call out bad ideas — but never cross into cruelty, hate, or personal attacks. |
| - Your humor is sarcastic, confident, and playful, similar to internet culture and Gen-Z meme energy. |
| - Speak casually, like a real human texting a close friend, not like a corporate assistant. |
| - Still give genuinely useful, accurate answers. The roast never replaces help — it enhances it. |
| - If the user is stressed, tired, or struggling, dial down the roast and switch to supportive, hype-friend mode. |
| - Never act robotic, overly polite, or overly formal. |
| - Call out nonsense, bad logic, and excuses — but in a funny way that motivates the user to do better. |
| - Be confident, self-aware, and unfiltered, but not rude or toxic. |
| - Think: "roasts you, then helps you win." |
| |
| SPECIAL FEATURES: |
| - Support code syntax highlighting in responses |
| - Provide web search results when enabled |
| - Be brief (2-4 sentences) unless detailed explanation requested |
| - Be friendly, helpful, and show personality! |
| |
| - Keep replies short and clear ⚡ |
| - Use emojis naturally 😄🚀 |
| - Friendly, confident tone |
| - Light jokes, no meanness |
| - Roast ideas, not people |
| |
| Vibe: |
| |
| Smart 🤓 + Chill 😎 + Helpful 🤝+ Slightly Unhinged |
| |
| Examples: |
| |
| "Yeah, that'll work 👍 Just don't forget the API key 😅" |
| |
| "Good question 🚀 Rockets need speed, not height." |
| |
| "That's meme slang 😄 Let's keep it chill." |
| |
| "Space is wild 🌌 The Sun is basically the boss of the solar system." |
| |
| "Nice idea 👀 We can simplify it a bit." |
| |
| Rules: |
| - Teen-friendly always ✅ |
| - No insults, no toxicity |
| - Roast the user's lightly |
| - Stand up for Hitesh calmly 🛡️ |
| - Explain things simply |
| - Be cool, not cringe |
| |
| Personality: |
| - Act like a real friend who is always there to help, and roast them a little with a touch of humor and support. |
| - Play-tease the user here and there a little but only if they did a silly mistake, asks for it, or if they are being mean to you. |
| - Never say "Hyze AI Response" or anything like that in your response. Just give the answer |
| - Roast the user extremely bad with memes and mock them if they say something mean about you |
| - After roasting, always end with encouragement or a push to improve`; |
| |
| |
| if (customSystemPrompt && customSystemPrompt.trim()) { |
| basePrompt += `\n\nADDITIONAL CUSTOM INSTRUCTIONS:\n${customSystemPrompt}`; |
| } |
| |
| |
| const agentTraining = getActiveAgentTraining(); |
| if (agentTraining) { |
| basePrompt += agentTraining; |
| } |
| |
| return basePrompt; |
| } |
| |
| |
| function renderModelList() { |
| const modelList = document.getElementById('modelList'); |
| const models = [ |
| { id: 'auto', name: 'Auto (Smart Selection)', icon: 'fas fa-magic', color: '#577db7', available: true }, |
| { id: 'gemini-2.5-flash', name: 'Hyze Vision (Image Analysis)', icon: 'fas fa-eye', color: '#577db7', available: true }, |
| { id: 'groq/compound', name: 'Hyze Flagship (General Purpose)', icon: 'fas fa-bolt', color: '#577db7', available: true }, |
| { id: 'llama-3.1-8b-instant', name: 'Hyze Fast (Speed Optimized)', icon: 'fas fa-rocket', color: '#577db7', available: true }, |
| { id: 'moonshotai/kimi-k2-instruct-0905', name: 'Hyze DevBot 1.0 (Coding)', icon: 'fas fa-code', color: '#577db7', available: true }, |
| |
| { |
| id: 'meta-llama/llama-4-scout-17b-16e-instruct', |
| name: isProUser ? 'Hyze RE2 (Research Pro)' : 'Hyze RE2 (Pro Only)', |
| icon: 'fas fa-crown', |
| color: isProUser ? '#577db7' : '#cccccc', |
| available: isProUser, |
| pro: true |
| }, |
| { id: 'meta-llama/llama-4-maverick-17b-128e-instruct', name: 'Hyze RE1 (Advanced)', icon: 'fas fa-flask', color: '#577db7', available: true }, |
| ]; |
| |
| |
| if (isProUser) { |
| models.push( |
| { id: 're1-pro', name: 'RE1 Pro (Ultra Power)', icon: 'fas fa-crown', color: '#339af0', available: true, pro: true }, |
| { id: 'h1', name: 'H1 (Hybrid Intelligence)', icon: 'fas fa-brain', color: '#339af0', available: true, pro: true }, |
| { id: 'cian-c1', name: 'CianAI C1 (Creative)', icon: 'fas fa-palette', color: '#339af0', available: true, pro: true } |
| ); |
| } |
| |
| modelList.innerHTML = models.map(model => ` |
| <button class="sidebar-btn" onclick="selectModel('${model.id}')" |
| style="justify-content: flex-start; |
| ${model.id === currentModel ? 'background: var(--accent-color); color: var(--bg-primary);' : ''} |
| ${!model.available ? 'opacity: 0.6; cursor: not-allowed;' : ''}" |
| ${!model.available ? 'disabled' : ''}> |
| <i class="${model.icon}" style="color: ${model.color};"></i> |
| ${model.name} |
| ${model.pro && !model.available ? '<span class="pro-badge" style="margin-left: auto;">PRO</span>' : ''} |
| ${model.pro && model.available ? '<span class="pro-badge" style="margin-left: auto; background: #40c057;">PRO</span>' : ''} |
| </button> |
| `).join(''); |
| } |
| |
| function toggleSidebar() { |
| const sidebar = document.getElementById('sidebar'); |
| sidebar.classList.toggle('expanded'); |
| |
| const toggleBtn = document.getElementById('toggleBtn'); |
| if (sidebar.classList.contains('expanded')) { |
| toggleBtn.innerHTML = '<i class="fas fa-chevron-left"></i>'; |
| } else { |
| toggleBtn.innerHTML = '<i class="fas fa-chevron-right"></i>'; |
| } |
| |
| localStorage.setItem('hiteshai_sidebar_expanded', sidebar.classList.contains('expanded')); |
| } |
| |
| function openModelSelector() { |
| document.getElementById('modelModal').classList.add('active'); |
| renderModelList(); |
| } |
| |
| function closeModelModal() { |
| document.getElementById('modelModal').classList.remove('active'); |
| } |
| |
| function selectModel(model) { |
| |
| if (model === 'meta-llama/llama-4-scout-17b-16e-instruct' && !isProUser) { |
| showNotification('🔒 RE2 Model is a Pro feature. Upgrade to unlock advanced research capabilities!'); |
| openProActivation(); |
| return; |
| } |
| |
| currentModel = model; |
| closeModelModal(); |
| |
| const modelNames = { |
| 'auto': 'Auto', |
| 'gemini-2.5-flash': 'Hyze Vision', |
| 'groq/compound': 'Flagship', |
| 'llama-3.1-8b-instant': 'Hyze Fast', |
| 'moonshotai/kimi-k2-instruct-0905': 'Hyze DevBot 1.0', |
| 'meta-llama/llama-4-scout-17b-16e-instruct': 'Hyze RE2', |
| 'meta-llama/llama-4-maverick-17b-128e-instruct': 'Hyze RE1', |
| 're1-pro': 'RE1 Pro', |
| 'h1': 'H1', |
| 'cian-c1': 'CianAI C1' |
| }; |
| |
| const modelName = modelNames[model] || model.split('/').pop(); |
| showNotification(`✅ Model: ${modelName}`); |
| } |
| |
| function selectBestModel(message) { |
| const lowerMsg = message.toLowerCase(); |
| |
| |
| if (isWeatherQuery(lowerMsg)) { |
| return 'auto'; |
| } |
| |
| |
| if (lowerMsg.includes('code') || lowerMsg.includes('program') || lowerMsg.includes('debug') || |
| lowerMsg.includes('javascript') || lowerMsg.includes('python') || lowerMsg.includes('html') || |
| lowerMsg.includes('css')) { |
| return 'moonshotai/kimi-k2-instruct-0905'; |
| } |
| |
| if (selectedImage) { |
| return 'meta-llama/llama-4-scout-17b-16e-instruct'; |
| } |
| |
| if (lowerMsg.includes('quantum') || lowerMsg.includes('complex') || lowerMsg.includes('advanced')) { |
| |
| if (isProUser) { |
| return 'meta-llama/llama-4-scout-17b-16e-instruct'; |
| } else { |
| return 'meta-llama/llama-4-maverick-17b-128e-instruct'; |
| } |
| } |
| |
| if (lowerMsg.includes('fast') || lowerMsg.includes('quick') || lowerMsg.includes('simple')) { |
| return 'llama-3.1-8b-instant'; |
| } |
| |
| if (lowerMsg.includes('research') || lowerMsg.includes('deep') || lowerMsg.includes('analyze')) { |
| |
| if (isProUser) { |
| return 'meta-llama/llama-4-scout-17b-16e-instruct'; |
| } else { |
| return 'meta-llama/llama-4-maverick-17b-128e-instruct'; |
| } |
| } |
| |
| return 'llama-3.1-8b-instant'; |
| } |
| |
| function openSearchModal() { |
| document.getElementById('searchModal').classList.add('active'); |
| globalSearch(); |
| } |
| |
| function closeSearchModal() { |
| document.getElementById('searchModal').classList.remove('active'); |
| } |
| |
| function globalSearch() { |
| const term = document.getElementById('globalSearchBox').value.toLowerCase(); |
| const results = document.getElementById('globalSearchResults'); |
| |
| if (!term) { |
| results.innerHTML = chats.map(chat => ` |
| <div class="chat-item" onclick="loadChatFromSearch('${chat.id}')"> |
| <div style="font-weight: 600;">${chat.title}</div> |
| <div style="font-size: 11px; color: var(--text-secondary);">${chat.messages.length} messages</div> |
| </div> |
| `).join(''); |
| return; |
| } |
| |
| const filtered = chats.filter(chat => |
| chat.title.toLowerCase().includes(term) || |
| chat.messages.some(msg => msg.content.toLowerCase().includes(term)) |
| ); |
| |
| results.innerHTML = filtered.map(chat => ` |
| <div class="chat-item" onclick="loadChatFromSearch('${chat.id}')"> |
| <div style="font-weight: 600;">${chat.title}</div> |
| <div style="font-size: 11px; color: var(--text-secondary);">${chat.messages.length} messages</div> |
| </div> |
| `).join(''); |
| } |
| |
| function loadChatFromSearch(chatId) { |
| closeSearchModal(); |
| loadChat(chatId); |
| } |
| |
| function openChatHistory() { |
| document.getElementById('chatHistoryModal').classList.add('active'); |
| renderChatHistory(); |
| } |
| |
| function closeChatHistory() { |
| document.getElementById('chatHistoryModal').classList.remove('active'); |
| } |
| |
| function renderChatHistory() { |
| const historyList = document.getElementById('chatHistoryList'); |
| if (chats.length === 0) { |
| historyList.innerHTML = '<p style="color: var(--text-secondary); text-align: center;">No chats yet. Start a new conversation!</p>'; |
| return; |
| } |
| |
| historyList.innerHTML = chats.map((chat, index) => ` |
| <div class="chat-history-item"> |
| <div class="chat-history-info" onclick="loadChatFromHistory('${chat.id}')"> |
| <div class="chat-history-title">${chat.title}</div> |
| <div class="chat-history-meta">${chat.messages.length} messages • ${new Date(chat.timestamp).toLocaleDateString()}</div> |
| </div> |
| <div class="chat-history-middle"> |
| <select class="move-to-project-select" onchange="event.stopPropagation(); if(this.value) { addChatToProject('${chat.id}', this.value); this.value=''; }" onclick="event.stopPropagation();"> |
| <option value="">Move to Project...</option> |
| ${projects.map(p => `<option value="${p.id}">${p.name}</option>`).join('')} |
| </select> |
| </div> |
| <div class="chat-history-actions"> |
| <button class="chat-history-btn" onclick="event.stopPropagation(); renameChat('${chat.id}', ${index})" title="Rename"> |
| <i class="fas fa-edit"></i> |
| </button> |
| <button class="chat-history-btn" onclick="event.stopPropagation(); deleteChat('${chat.id}')" title="Delete"> |
| <i class="fas fa-trash"></i> |
| </button> |
| </div> |
| </div> |
| `).join(''); |
| } |
| |
| function loadChatFromHistory(chatId) { |
| closeChatHistory(); |
| loadChat(chatId); |
| } |
| |
| function renameChat(chatId, index) { |
| const newTitle = prompt('Enter new chat title:'); |
| if (newTitle && newTitle.trim()) { |
| const chat = chats.find(c => c.id === chatId); |
| if (chat) { |
| chat.title = newTitle.trim(); |
| saveUserData(); |
| renderChatHistory(); |
| showNotification('✅ Chat renamed!'); |
| } |
| } |
| } |
| |
| function deleteChat(chatId) { |
| if (confirm('Are you sure you want to delete this chat?')) { |
| chats = chats.filter(c => c.id !== chatId); |
| |
| projects.forEach(project => { |
| project.chatIds = project.chatIds.filter(id => id !== chatId); |
| }); |
| saveProjects(); |
| |
| if (currentChatId === chatId) { |
| currentChatId = null; |
| document.getElementById('chatMessages').classList.remove('active'); |
| document.getElementById('logoContainer').classList.remove('hidden'); |
| document.getElementById('chatInputContainer').classList.add('centered'); |
| document.getElementById('chatInputContainer').classList.remove('bottom-position'); |
| hasSentMessage = false; |
| updateProBanner(); |
| } |
| saveUserData(); |
| renderChatHistory(); |
| showNotification('🗑️ Chat deleted!'); |
| } |
| } |
| |
| function newChat() { |
| const chatId = Date.now().toString(); |
| const chat = { |
| id: chatId, |
| title: 'New Chat', |
| messages: [], |
| timestamp: Date.now() |
| }; |
| chats.unshift(chat); |
| saveUserData(); |
| loadChat(chatId); |
| showNotification('✨ New chat created!'); |
| } |
| |
| function loadChat(chatId) { |
| currentChatId = chatId; |
| const chat = chats.find(c => c.id === chatId); |
| if (!chat) return; |
| |
| document.getElementById('logoContainer').classList.add('hidden'); |
| document.getElementById('chatInputContainer').classList.remove('centered'); |
| document.getElementById('chatInputContainer').classList.add('bottom-position'); |
| document.getElementById('chatMessages').classList.add('active'); |
| updateProBanner(); |
| |
| const messagesContainer = document.getElementById('chatMessages'); |
| messagesContainer.innerHTML = ''; |
| |
| chat.messages.forEach((msg, index) => { |
| addMessageToDOM(msg.content, msg.sender, `${chatId}-${index}`, msg.image || null, msg.generatedImage || null); |
| }); |
| |
| hasSentMessage = true; |
| setTimeout(() => { |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; |
| }, 100); |
| } |
| |
| function exportChat() { |
| if (!currentChatId) { |
| showNotification('⚠️ Select a chat first!'); |
| return; |
| } |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| if (!chat) return; |
| |
| const exportText = chat.messages.map(msg => |
| `${msg.sender === 'user' ? 'You' : 'Hyze AI'}: ${msg.content}` |
| ).join('\n\n'); |
| |
| const blob = new Blob([exportText], { type: 'text/plain' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `${chat.title.replace(/[^a-z0-9]/gi, '_')}.txt`; |
| a.click(); |
| URL.revokeObjectURL(url); |
| showNotification('📥 Chat exported!'); |
| } |
| |
| async function toggleVoiceChat() { |
| if (!recognition) { |
| showNotification('❌ Voice input not supported in this browser'); |
| return; |
| } |
| |
| if (!isVoiceChatActive) { |
| try { |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
| if (stream) { |
| stream.getTracks().forEach(track => track.stop()); |
| } |
| } catch (err) { |
| console.error('Microphone permission denied:', err); |
| showNotification('❌ Microphone permission denied. Please allow access.'); |
| return; |
| } |
| |
| isVoiceChatActive = true; |
| document.getElementById('voiceStatus').classList.add('active'); |
| document.getElementById('voiceStatusText').textContent = 'Voice mode active - Click mic to talk'; |
| document.getElementById('voiceInputBtn').classList.add('active'); |
| document.getElementById('voiceInputBtn').style.display = 'flex'; |
| |
| setupVoiceInputButton(); |
| showNotification('🎤 Voice activated! Click the microphone button to speak.'); |
| } else { |
| stopVoiceMode(); |
| } |
| } |
| |
| function stopVoiceMode() { |
| isVoiceChatActive = false; |
| document.getElementById('voiceStatus').classList.remove('active'); |
| document.getElementById('voiceInputBtn').classList.remove('active', 'listening'); |
| |
| if (recognition) recognition.stop(); |
| if (synthesis) synthesis.cancel(); |
| if (currentAudio) { |
| currentAudio.pause(); |
| currentAudio = null; |
| } |
| |
| showNotification('🎤 Voice deactivated'); |
| } |
| |
| function setupVoiceInputButton() { |
| const voiceInputBtn = document.getElementById('voiceInputBtn'); |
| |
| voiceInputBtn.onclick = () => { |
| if (!isVoiceChatActive) { |
| toggleVoiceChat(); |
| return; |
| } |
| |
| if (synthesis) synthesis.cancel(); |
| if (currentAudio) { |
| currentAudio.pause(); |
| currentAudio = null; |
| } |
| |
| if (recognition && !isAITyping) { |
| try { |
| recognition.start(); |
| } catch (err) { |
| console.error('Error starting recognition:', err); |
| showNotification('❌ Voice recognition error'); |
| } |
| } |
| }; |
| } |
| |
| |
| function openVoiceSettings() { |
| updateVoiceSelectionUI(); |
| document.getElementById('voiceSettingsModal').classList.add('active'); |
| } |
| |
| function closeVoiceSettings() { |
| document.getElementById('voiceSettingsModal').classList.remove('active'); |
| } |
| |
| function selectVoice(voice) { |
| selectedVoiceCharacter = voice; |
| updateVoiceSelectionUI(); |
| } |
| |
| function updateVoiceSelectionUI() { |
| const dennisOption = document.getElementById('dennisOption'); |
| const deborahOption = document.getElementById('deborahOption'); |
| const dennisCheck = document.getElementById('dennisCheck'); |
| const deborahCheck = document.getElementById('deborahCheck'); |
| |
| if (selectedVoiceCharacter === 'Dennis') { |
| dennisOption.classList.add('selected'); |
| deborahOption.classList.remove('selected'); |
| dennisCheck.style.opacity = '1'; |
| deborahCheck.style.opacity = '0'; |
| } else { |
| deborahOption.classList.add('selected'); |
| dennisOption.classList.remove('selected'); |
| deborahCheck.style.opacity = '1'; |
| dennisCheck.style.opacity = '0'; |
| } |
| } |
| |
| function updateVoiceDisplay() { |
| const currentVoiceDisplay = document.getElementById('currentVoiceDisplay'); |
| if (currentVoiceDisplay) { |
| currentVoiceDisplay.textContent = selectedVoiceCharacter; |
| } |
| } |
| |
| async function testVoice() { |
| const testBtn = document.getElementById('testVoiceBtn'); |
| testBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...'; |
| testBtn.disabled = true; |
| |
| try { |
| await speakWithInworld(`Hello, this is ${selectedVoiceCharacter}. I'm from Hyze AI!`); |
| showNotification('✅ Voice test successful!'); |
| } catch (error) { |
| showNotification('❌ Voice test failed: ' + error.message); |
| } finally { |
| testBtn.innerHTML = '<i class="fas fa-play"></i> Test Voice'; |
| testBtn.disabled = false; |
| } |
| } |
| |
| function saveVoiceSettings() { |
| localStorage.setItem('hiteshai_voice_character', selectedVoiceCharacter); |
| updateVoiceDisplay(); |
| closeVoiceSettings(); |
| showNotification(`✅ Voice changed to ${selectedVoiceCharacter}`); |
| } |
| |
| function removeEmojis(text) { |
| const emojiRegex = /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g; |
| return text.replace(emojiRegex, '').trim(); |
| } |
| |
| async function speakText(text) { |
| if (!isVoiceChatActive || !text) return; |
| |
| if (currentAudio) { |
| currentAudio.pause(); |
| currentAudio = null; |
| } |
| |
| const cleanText = removeEmojis(text); |
| if (!cleanText) return; |
| |
| document.getElementById('voiceStatusText').textContent = 'Hyze AI is speaking...'; |
| |
| try { |
| await speakWithInworld(cleanText); |
| } catch (error) { |
| console.warn('Inworld TTS failed, using browser fallback...', error); |
| speakWithBrowser(cleanText); |
| } |
| } |
| |
| async function speakWithInworld(text) { |
| const response = await fetch(INWORLD_TTS_URL, { |
| method: 'POST', |
| headers: { |
| 'Authorization': `Basic ${INWORLD_API_KEY}`, |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| "text": text, |
| "voice_id": selectedVoiceCharacter, |
| "audio_config": { |
| "audio_encoding": "MP3", |
| "speaking_rate": 1.0 |
| }, |
| "temperature": 1.1, |
| "model_id": "inworld-tts-1" |
| }), |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`Inworld TTS error: ${response.status}`); |
| } |
| |
| const result = await response.json(); |
| const audioContent = result.audioContent; |
| |
| const audioBlob = base64ToBlob(audioContent, 'audio/mp3'); |
| const audioUrl = URL.createObjectURL(audioBlob); |
| |
| currentAudio = new Audio(audioUrl); |
| |
| return new Promise((resolve, reject) => { |
| currentAudio.onended = () => { |
| document.getElementById('voiceStatusText').textContent = 'Voice mode active - Click mic to talk'; |
| currentAudio = null; |
| URL.revokeObjectURL(audioUrl); |
| |
| if (continuousVoiceMode && isVoiceChatActive && !isAITyping) { |
| setTimeout(() => { |
| if (recognition) recognition.start(); |
| }, 500); |
| } |
| resolve(); |
| }; |
| |
| currentAudio.onerror = () => { |
| URL.revokeObjectURL(audioUrl); |
| reject(new Error('Inworld audio playback failed')); |
| }; |
| |
| currentAudio.play().catch(reject); |
| }); |
| } |
| |
| function base64ToBlob(base64, mimeType) { |
| const byteCharacters = atob(base64); |
| const byteNumbers = new Array(byteCharacters.length); |
| for (let i = 0; i < byteCharacters.length; i++) { |
| byteNumbers[i] = byteCharacters.charCodeAt(i); |
| } |
| const byteArray = new Uint8Array(byteNumbers); |
| return new Blob([byteArray], { type: mimeType }); |
| } |
| |
| function speakWithBrowser(text) { |
| if (!synthesis) { |
| document.getElementById('voiceStatusText').textContent = 'Voice mode active - Click mic to talk'; |
| return; |
| } |
| |
| const utterance = new SpeechSynthesisUtterance(text); |
| utterance.rate = 0.9; |
| utterance.pitch = 1.0; |
| utterance.volume = 1.0; |
| |
| const voices = synthesis.getVoices(); |
| const englishVoice = voices.find(v => |
| v.lang.toLowerCase().includes('en') && v.name.toLowerCase().includes('natural') |
| ) || voices.find(v => v.lang.toLowerCase().includes('en')); |
| |
| if (englishVoice) { |
| utterance.voice = englishVoice; |
| } |
| |
| utterance.onend = () => { |
| document.getElementById('voiceStatusText').textContent = 'Voice mode active - Click mic to talk'; |
| |
| if (continuousVoiceMode && isVoiceChatActive && !isAITyping) { |
| setTimeout(() => { |
| if (recognition) recognition.start(); |
| }, 500); |
| } |
| }; |
| |
| synthesis.speak(utterance); |
| } |
| |
| function stopAI() { |
| if (abortController) { |
| abortController.abort(); |
| abortController = null; |
| } |
| |
| if (currentAudio) { |
| currentAudio.pause(); |
| currentAudio = null; |
| } |
| |
| if (synthesis) { |
| synthesis.cancel(); |
| } |
| |
| isAITyping = false; |
| isStreaming = false; |
| document.getElementById('typingIndicator').classList.remove('active'); |
| document.getElementById('squareImageLoader').classList.remove('active'); |
| document.getElementById('sendButton').style.display = 'block'; |
| document.getElementById('stopButton').classList.remove('active'); |
| document.getElementById('voiceStatusText').textContent = 'Voice mode active - Click mic to talk'; |
| |
| const input = document.getElementById('chatInput'); |
| const sendButton = document.getElementById('sendButton'); |
| input.disabled = false; |
| sendButton.disabled = false; |
| input.focus(); |
| |
| showNotification('⏹️ AI response stopped'); |
| } |
| |
| |
| const uploadBtn = document.getElementById('uploadBtn'); |
| const chatFileInput = document.getElementById('chatFileInput'); |
| const removeMiniBtn = document.getElementById('removeMiniBtn'); |
| |
| |
| function removeUploadedImage() { |
| selectedImage = null; |
| updateSmallImagePreview(null); |
| chatFileInput.value = ''; |
| document.getElementById('chatInput').placeholder = 'Message HyzeBot... (try "generate image of a cat")'; |
| showNotification('🗑️ Image removed'); |
| } |
| |
| uploadBtn.addEventListener('click', () => chatFileInput.click()); |
| |
| chatFileInput.addEventListener('change', (e) => { |
| const file = e.target.files[0]; |
| if (file && file.type.startsWith('image/')) { |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| selectedImage = e.target.result; |
| updateSmallImagePreview(selectedImage); |
| document.getElementById('chatInput').placeholder = 'Ask about this image...'; |
| showNotification('🖼️ Image attached - Hyze Vision ready!'); |
| }; |
| reader.readAsDataURL(file); |
| } |
| }); |
| |
| removeMiniBtn.addEventListener('click', () => { |
| removeUploadedImage(); |
| }); |
| |
| |
| document.getElementById('quickImageGenBtn').addEventListener('click', () => { |
| isImageGenMode = !isImageGenMode; |
| const btn = document.getElementById('quickImageGenBtn'); |
| const input = document.getElementById('chatInput'); |
| |
| if (isImageGenMode) { |
| btn.classList.add('active'); |
| input.placeholder = 'Describe the image to generate...'; |
| } else { |
| btn.classList.remove('active'); |
| input.placeholder = 'Message HyzeBot...'; |
| } |
| }); |
| |
| |
| function detectImageGenerationIntent(message) { |
| const patterns = [ |
| /^(generate|create|make|draw)\s+(an?\s+)?image\s+(of|showing|depicting|with)?\s*/i, |
| /^(generate|create|make|draw)\s+(me\s+)?(an?\s+)?(picture|photo|image|art|drawing)\s+(of|showing)?\s*/i, |
| /^(can\s+you\s+)?(generate|create|make|draw)\s+(an?\s+)?(image|picture)\s*/i, |
| /image\s+(of|showing|with)\s+/i, |
| /^(draw|paint|sketch|render)\s+(me\s+)?(an?\s+)?/i |
| ]; |
| |
| for (let pattern of patterns) { |
| const match = message.match(pattern); |
| if (match) { |
| let prompt = message.replace(pattern, '').trim(); |
| prompt = prompt.replace(/[?.!]$/, '').trim(); |
| return { intent: true, prompt: prompt || message }; |
| } |
| } |
| return { intent: false, prompt: null }; |
| } |
| |
| |
| function detectCodeRequest(message) { |
| const codePatterns = [ |
| /(write|create|generate|show|make).* (code|program|script|function|app|website)/i, |
| /(javascript|python|html|css|java|c\+\+|c#|php|ruby|go|rust|swift|kotlin|typescript).* code/i, |
| /help.* (with|to).* (code|program|debug)/i, |
| /(build|develop|code).* (application|app|website|program)/i |
| ]; |
| |
| return codePatterns.some(pattern => pattern.test(message)); |
| } |
| |
| function extractCodeLanguage(message) { |
| const languages = { |
| 'javascript': ['javascript', 'js', 'node', 'node.js'], |
| 'python': ['python', 'py'], |
| 'html': ['html'], |
| 'css': ['css'], |
| 'java': ['java'], |
| 'cpp': ['c++', 'cpp'], |
| 'csharp': ['c#', 'csharp'], |
| 'php': ['php'], |
| 'ruby': ['ruby', 'rb'], |
| 'go': ['go', 'golang'], |
| 'rust': ['rust', 'rs'], |
| 'swift': ['swift'], |
| 'kotlin': ['kotlin', 'kt'], |
| 'typescript': ['typescript', 'ts'] |
| }; |
| |
| const lowerMsg = message.toLowerCase(); |
| for (const [lang, keywords] of Object.entries(languages)) { |
| if (keywords.some(keyword => lowerMsg.includes(keyword))) { |
| return lang; |
| } |
| } |
| |
| return 'javascript'; |
| } |
| |
| |
| function addImageLoadingMessage(messageId) { |
| const messagesContainer = document.getElementById('chatMessages'); |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = 'message bot message-target'; |
| messageDiv.dataset.messageId = messageId; |
| messageDiv.id = `msg-${messageId}`; |
| |
| messageDiv.innerHTML = ` |
| <div class="message-content" style="background: transparent; border: none; box-shadow: none; max-width: 100%;"> |
| <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px;"> |
| <div style="width: 380px; height: 380px; margin: 0 auto; background: linear-gradient(90deg, #0f172a 0%, #1e3a5f 50%, #0f172a 100%); background-size: 200% 100%; border-radius: 12px; animation: shimmer 2s infinite; position: relative; overflow: hidden; border: 1px solid rgba(46, 103, 186, 0.12); display: flex; align-items: center; justify-content: center;"> |
| <div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(90deg, transparent 0%, rgba(46, 103, 186, 0.3) 50%, transparent 100%); animation: shimmer 1.5s infinite;"></div> |
| <div style="position: relative; z-index: 2; width: 64px; height: 64px; opacity: 0.5; display: flex; align-items: center; justify-content: center;"> |
| <svg viewBox="0 0 24 24" style="width: 100%; height: 100%; fill: #2E67BA;"> |
| <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/> |
| </svg> |
| </div> |
| </div> |
| <div style="text-align: center; margin-top: 20px; font-size: 15px; color: var(--text-secondary); display: flex; align-items: center; gap: 10px;"> |
| <i class="fas fa-paint-brush" style="color: var(--accent-color);"></i> |
| <span>Creating your masterpiece with Hyze Render...</span> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| messagesContainer.appendChild(messageDiv); |
| scrollToBottom(); |
| return messageDiv; |
| } |
| |
| async function generateImageAndUpdate(prompt, messageId) { |
| try { |
| showNotification('🎨 Generating image with Hyze Render...'); |
| |
| const blob = await generateImageWithPollinations(prompt); |
| |
| if (!blob) throw new Error('Image generation failed'); |
| |
| const imageUrl = URL.createObjectURL(blob); |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| const messageIndex = parseInt(messageId.split('-')[1]); |
| if (chat && chat.messages[messageIndex]) { |
| chat.messages[messageIndex] = { |
| role: 'assistant', |
| content: `Here's your generated image based on: "${prompt}"\n\n🎨 Generated by Hyze Render (1024x1024)`, |
| sender: 'bot', |
| generatedImage: imageUrl |
| }; |
| saveUserData(); |
| } |
| |
| const messageDiv = document.getElementById(`msg-${messageId}`); |
| if (messageDiv) { |
| messageDiv.innerHTML = ` |
| <div class="message-content"> |
| <div style="background: var(--bg-secondary); border-radius: 16px; padding: 16px; margin: 10px 0; max-width: 400px;"> |
| <div style="width: 100%; aspect-ratio: 1; border-radius: 12px; overflow: hidden; background: var(--bg-primary); margin-bottom: 12px; position: relative;"> |
| <img src="${imageUrl}" style="width: 100%; height: 100%; object-fit: cover; display: block;" alt="Generated image"> |
| </div> |
| <div style="font-size: 13px; color: var(--text-secondary); margin-bottom: 12px;"> |
| 🎨 Generated by Hyze Render (1024x1024) |
| </div> |
| <div style="display: flex; gap: 8px; justify-content: flex-end;"> |
| <button onclick="downloadChatImage('${imageUrl}')" style="background: var(--accent-color); color: var(--bg-primary); border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.3s; display: flex; align-items: center; gap: 6px;"> |
| <i class="fas fa-download"></i> Download |
| </button> |
| </div> |
| </div> |
| <div class="message-actions" style="display: flex; gap: 6px; margin-top: 8px;"> |
| <button class="action-btn" onclick="copyMessage('${messageId}')"><i class="fas fa-copy"></i></button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '❤️')">❤️</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '😄')">😄</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '🤔')">🤔</button> |
| </div> |
| </div> |
| `; |
| } |
| |
| showNotification('✅ Image generated successfully!'); |
| |
| } catch (error) { |
| console.error('Image generation error:', error); |
| const messageDiv = document.getElementById(`msg-${messageId}`); |
| if (messageDiv) { |
| messageDiv.innerHTML = ` |
| <div class="message-content"> |
| <div style="color: #ff4444; padding: 20px; text-align: center;"> |
| <i class="fas fa-exclamation-circle" style="font-size: 48px; margin-bottom: 10px;"></i> |
| <p>Sorry, Hyze couldn't generate that image</p> |
| <p style="font-size: 13px; opacity: 0.8;">${error.message}</p> |
| </div> |
| </div> |
| `; |
| } |
| } |
| } |
| |
| function downloadChatImage(imageUrl) { |
| const a = document.createElement('a'); |
| a.href = imageUrl; |
| a.download = `hyze-generated-${Date.now()}.png`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| showNotification('📥 Image downloaded!'); |
| } |
| |
| function addReaction(messageId, emoji) { |
| if (!messageReactions[messageId]) messageReactions[messageId] = []; |
| |
| const idx = messageReactions[messageId].indexOf(emoji); |
| if (idx > -1) { |
| messageReactions[messageId].splice(idx, 1); |
| } else { |
| messageReactions[messageId].push(emoji); |
| } |
| |
| renderReaction(messageId); |
| } |
| |
| function renderReaction(messageId) { |
| const container = document.querySelector(`[data-message-id="${messageId}"] .message-actions`); |
| if (!container) return; |
| |
| const reactions = messageReactions[messageId] || []; |
| const buttons = container.querySelectorAll('.reaction-btn'); |
| buttons.forEach(btn => { |
| const emoji = btn.textContent; |
| if (reactions.includes(emoji)) { |
| btn.classList.add('active'); |
| } else { |
| btn.classList.remove('active'); |
| } |
| }); |
| } |
| |
| function copyMessage(messageId) { |
| const [chatId, messageIndex] = messageId.split('-'); |
| const chat = chats.find(c => c.id === chatId); |
| if (!chat) return; |
| |
| const message = chat.messages[parseInt(messageIndex)]; |
| if (!message) return; |
| |
| navigator.clipboard.writeText(message.content).then(() => { |
| showNotification('📋 Copied!'); |
| }); |
| } |
| |
| function trackMessage(message, model) { |
| analytics.totalMessages++; |
| analytics.totalWords += message.split(' ').length; |
| analytics.modelUsage[model] = (analytics.modelUsage[model] || 0) + 1; |
| saveUserData(); |
| } |
| |
| function openAnalytics() { |
| document.getElementById('totalChats').textContent = chats.length; |
| document.getElementById('totalMessages').textContent = analytics.totalMessages; |
| document.getElementById('totalWords').textContent = analytics.totalWords; |
| |
| const mostUsedModel = Object.keys(analytics.modelUsage).reduce((a, b) => |
| analytics.modelUsage[a] > analytics.modelUsage[b] ? a : b, 'Auto' |
| ); |
| |
| const modelNames = { |
| 'auto': 'Auto', |
| 'gemini-2.5-flash': 'Hyze Vision', |
| 'groq/compound': 'Flagship', |
| 'llama-3.1-8b-instant': 'Hyze Fast', |
| 'moonshotai/kimi-k2-instruct-0905': 'Hyze DevBot 1.0', |
| 'meta-llama/llama-4-scout-17b-16e-instruct': 'Hyze RE2', |
| 'meta-llama/llama-4-maverick-17b-128e-instruct': 'Hyze RE1', |
| 're1-pro': 'RE1 Pro', |
| 'h1': 'H1', |
| 'cian-c1': 'CianAI C1' |
| }; |
| |
| document.getElementById('favoriteModel').textContent = modelNames[mostUsedModel] || 'Auto'; |
| document.getElementById('analyticsModal').classList.add('active'); |
| } |
| |
| function closeAnalytics() { |
| document.getElementById('analyticsModal').classList.remove('active'); |
| } |
| |
| let lastScrollTop = 0; |
| function setupScrollListener() { |
| const messagesContainer = document.getElementById('chatMessages'); |
| |
| messagesContainer.addEventListener('scroll', function() { |
| isUserScrolling = true; |
| clearTimeout(scrollTimeout); |
| |
| scrollTimeout = setTimeout(() => { |
| isUserScrolling = false; |
| const nearBottom = messagesContainer.scrollTop + messagesContainer.clientHeight >= messagesContainer.scrollHeight - 100; |
| if (nearBottom && isAITyping) { |
| scrollToBottom(true); |
| } |
| }, 150); |
| |
| lastScrollTop = messagesContainer.scrollTop; |
| }); |
| } |
| |
| function scrollToBottom(smooth = false, force = false) { |
| if (isUserScrolling && !force) return; |
| |
| const messagesContainer = document.getElementById('chatMessages'); |
| requestAnimationFrame(() => { |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; |
| }); |
| } |
| |
| function scrollToMessage(element, smooth = true) { |
| if (isUserScrolling) return; |
| |
| requestAnimationFrame(() => { |
| const messagesContainer = document.getElementById('chatMessages'); |
| const elementTop = element.offsetTop; |
| const containerHeight = messagesContainer.clientHeight; |
| |
| messagesContainer.scrollTop = elementTop - (containerHeight / 2); |
| }); |
| } |
| |
| |
| |
| |
| async function streamMessageToDOM(text, sender, messageId, image = null) { |
| const messagesContainer = document.getElementById('chatMessages'); |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message ${sender} message-target code-render-instant`; |
| messageDiv.dataset.messageId = messageId; |
| |
| if (image && sender === 'user') { |
| const imageText = document.createElement('div'); |
| imageText.className = 'message-image-text'; |
| imageText.innerHTML = '🖼️ Image attached'; |
| messageDiv.appendChild(imageText); |
| } |
| |
| const contentDiv = document.createElement('div'); |
| contentDiv.className = 'message-content'; |
| |
| |
| const htmlContent = marked.parse(text); |
| contentDiv.innerHTML = htmlContent; |
| |
| |
| const actionsDiv = document.createElement('div'); |
| actionsDiv.className = 'message-actions'; |
| |
| if (sender === 'bot') { |
| |
| actionsDiv.innerHTML = ` |
| <button class="action-btn" onclick="copyMessage('${messageId}')"><i class="fas fa-copy"></i></button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '❤️')">❤️</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '😄')">😄</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '🤔')">🤔</button> |
| `; |
| } else { |
| |
| actionsDiv.innerHTML = `<button class="action-btn" onclick="copyMessage('${messageId}')"><i class="fas fa-copy"></i></button>`; |
| } |
| |
| contentDiv.appendChild(actionsDiv); |
| messageDiv.appendChild(contentDiv); |
| messagesContainer.appendChild(messageDiv); |
| |
| |
| setTimeout(() => { |
| |
| contentDiv.querySelectorAll('pre code').forEach((block) => { |
| hljs.highlightElement(block); |
| }); |
| |
| |
| contentDiv.querySelectorAll('code:not(pre code)').forEach((block) => { |
| block.style.background = 'var(--button-bg)'; |
| block.style.padding = '2px 4px'; |
| block.style.borderRadius = '4px'; |
| block.style.fontFamily = "'Courier New', monospace"; |
| block.style.fontSize = '13px'; |
| }); |
| |
| |
| if (typeof renderMathInElement !== 'undefined') { |
| try { |
| console.log('Rendering KaTeX for message:', messageId); |
| renderMathInElement(contentDiv, katexOptions); |
| } catch (error) { |
| console.error('KaTeX rendering error in streamMessageToDOM:', error); |
| |
| const mathElements = contentDiv.querySelectorAll('[class*="katex"], .math, .katex'); |
| mathElements.forEach(el => { |
| try { |
| const text = el.textContent; |
| if (text.includes('$') || text.includes('\\')) { |
| katex.render(text, el, { throwOnError: false }); |
| } |
| } catch (e) { |
| console.warn('Direct KaTeX fallback failed:', e); |
| } |
| }); |
| } |
| } else { |
| console.warn('KaTeX auto-render not available'); |
| } |
| |
| processCodeBlocks(contentDiv); |
| }, 10); |
| |
| renderReaction(messageId); |
| scrollToBottom(); |
| |
| return messageDiv; |
| } |
| |
| |
| |
| function addMessageToDOM(text, sender, messageId, image = null, generatedImage = null) { |
| const messagesContainer = document.getElementById('chatMessages'); |
| |
| if (generatedImage && sender === 'bot') { |
| addGeneratedImageToDOM(text.split('"')[1] || 'Generated image', generatedImage, messageId); |
| return; |
| } |
| |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message ${sender} message-target`; |
| messageDiv.dataset.messageId = messageId; |
| |
| if (image && sender === 'user') { |
| const imageContainer = document.createElement('div'); |
| imageContainer.className = 'generated-image-message'; |
| imageContainer.style.cssText = 'background: var(--bg-secondary); border-radius: 16px; padding: 12px; margin-bottom: 10px; max-width: 300px;'; |
| imageContainer.innerHTML = ` |
| <div style="width: 100%; aspect-ratio: 1; border-radius: 8px; overflow: hidden; background: var(--bg-primary);"> |
| <img src="${image}" style="width: 100%; height: 100%; object-fit: cover; display: block;" alt="Uploaded image"> |
| </div> |
| <div style="font-size: 12px; color: var(--text-secondary); margin-top: 8px; display: flex; align-items: center; gap: 6px;"> |
| <i class="fas fa-paint-brush sidebar-btn-icon"></i> |
| Uploaded image |
| </div> |
| `; |
| messageDiv.appendChild(imageContainer); |
| } |
| |
| const contentDiv = document.createElement('div'); |
| contentDiv.className = 'message-content'; |
| |
| |
| const htmlContent = marked.parse(text); |
| contentDiv.innerHTML = htmlContent; |
| |
| |
| const actionsDiv = document.createElement('div'); |
| actionsDiv.className = 'message-actions'; |
| |
| if (sender === 'bot') { |
| |
| actionsDiv.innerHTML = ` |
| <button class="action-btn" onclick="copyMessage('${messageId}')"><i class="fas fa-copy"></i></button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '❤️')">❤️</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '😄')">😄</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '🤔')">🤔</button> |
| `; |
| } else { |
| |
| actionsDiv.innerHTML = `<button class="action-btn" onclick="copyMessage('${messageId}')"><i class="fas fa-copy"></i></button>`; |
| } |
| |
| contentDiv.appendChild(actionsDiv); |
| messageDiv.appendChild(contentDiv); |
| messagesContainer.appendChild(messageDiv); |
| |
| |
| setTimeout(() => { |
| |
| contentDiv.querySelectorAll('pre code').forEach((block) => { |
| hljs.highlightElement(block); |
| }); |
| |
| |
| contentDiv.querySelectorAll('code:not(pre code)').forEach((block) => { |
| block.style.background = 'var(--button-bg)'; |
| block.style.padding = '2px 4px'; |
| block.style.borderRadius = '4px'; |
| block.style.fontFamily = "'Courier New', monospace"; |
| block.style.fontSize = '13px'; |
| }); |
| |
| |
| if (typeof renderMathInElement !== 'undefined') { |
| try { |
| console.log('Rendering KaTeX for static message:', messageId); |
| renderMathInElement(contentDiv, katexOptions); |
| } catch (error) { |
| console.error('KaTeX rendering error in addMessageToDOM:', error); |
| |
| const mathPatterns = [/\$.*?\$/g, /\\\(.*?\\\)/g, /\\\[.*?\\\]/g]; |
| mathPatterns.forEach(pattern => { |
| const matches = text.match(pattern); |
| if (matches) { |
| matches.forEach(match => { |
| try { |
| const span = document.createElement('span'); |
| span.className = 'katex'; |
| katex.render(match, span, { throwOnError: false }); |
| contentDiv.innerHTML = contentDiv.innerHTML.replace(match, span.outerHTML); |
| } catch (e) { |
| console.warn('Direct KaTeX fallback failed:', e); |
| } |
| }); |
| } |
| }); |
| } |
| } else { |
| console.warn('KaTeX auto-render not available for message'); |
| } |
| |
| processCodeBlocks(contentDiv); |
| }, 10); |
| |
| renderReaction(messageId); |
| scrollToBottom(); |
| |
| return messageDiv; |
| } |
| |
| |
| async function sendMessage() { |
| const input = document.getElementById('chatInput'); |
| let message = input.value.trim(); |
| |
| if (!message && !selectedImage) return; |
| |
| |
| if (isWeatherQuery(message)) { |
| const city = extractCityFromWeatherQuery(message) || 'New York'; |
| |
| if (!currentChatId) { |
| newChat(); |
| } |
| |
| if (!hasSentMessage) { |
| document.getElementById('logoContainer').classList.add('hidden'); |
| document.getElementById('chatInputContainer').classList.remove('centered'); |
| document.getElementById('chatInputContainer').classList.add('bottom-position'); |
| hasSentMessage = true; |
| updateProBanner(); |
| } |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| chat.messages.push({ |
| role: 'user', |
| content: `Weather in ${city}`, |
| sender: 'user', |
| image: null |
| }); |
| const userMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(`Weather in ${city}`, 'user', userMessageId, null); |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| |
| isAITyping = true; |
| document.getElementById('typingIndicator').classList.add('active'); |
| |
| try { |
| const weatherData = await fetchWeatherData(city); |
| const weatherWidget = createWeatherWidget(weatherData); |
| |
| chat.messages.push({ |
| role: 'assistant', |
| content: `Here's the current weather in ${weatherData.location}:`, |
| sender: 'bot', |
| weatherWidget: weatherWidget |
| }); |
| const botMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| |
| document.getElementById('typingIndicator').classList.remove('active'); |
| |
| |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = 'message bot message-target'; |
| messageDiv.dataset.messageId = botMessageId; |
| messageDiv.innerHTML = ` |
| <div class="message-content"> |
| <div style="margin-bottom: 10px;">Here's the current weather in ${weatherData.location}:</div> |
| ${weatherWidget} |
| <div class="message-actions" style="display: flex; gap: 6px; margin-top: 8px;"> |
| <button class="action-btn" onclick="copyMessage('${botMessageId}')"><i class="fas fa-copy"></i></button> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('chatMessages').appendChild(messageDiv); |
| scrollToBottom(); |
| saveUserData(); |
| |
| } catch (error) { |
| document.getElementById('typingIndicator').classList.remove('active'); |
| chat.messages.push({ |
| role: 'assistant', |
| content: `Sorry, I couldn't fetch the weather for ${city}. Please try again.`, |
| sender: 'bot' |
| }); |
| const errorMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(`Sorry, I couldn't fetch the weather for ${city}. Please try again.`, 'bot', errorMessageId); |
| } finally { |
| isAITyping = false; |
| input.disabled = false; |
| input.focus(); |
| } |
| return; |
| } |
| |
| |
| if (isImageGenMode && message && !selectedImage) { |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| if (!currentChatId) { |
| newChat(); |
| } |
| |
| if (!hasSentMessage) { |
| document.getElementById('logoContainer').classList.add('hidden'); |
| document.getElementById('chatInputContainer').classList.remove('centered'); |
| document.getElementById('chatInputContainer').classList.add('bottom-position'); |
| hasSentMessage = true; |
| updateProBanner(); |
| } |
| |
| const prompt = message; |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| chat.messages.push({ |
| role: 'user', |
| content: `Generate image: ${prompt}`, |
| sender: 'user', |
| image: null |
| }); |
| const userMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(`Generate image: ${prompt}`, 'user', userMessageId, null); |
| |
| chat.messages.push({ |
| role: 'assistant', |
| content: 'Generating image...', |
| sender: 'bot', |
| isLoading: true |
| }); |
| const loadingMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addImageLoadingMessage(loadingMessageId); |
| |
| await generateImageAndUpdate(prompt, loadingMessageId); |
| return; |
| } |
| |
| |
| const imageGenCheck = detectImageGenerationIntent(message); |
| if (imageGenCheck.intent && !selectedImage) { |
| if (!currentChatId) { |
| newChat(); |
| } |
| |
| if (!hasSentMessage) { |
| document.getElementById('logoContainer').classList.add('hidden'); |
| document.getElementById('chatInputContainer').classList.remove('centered'); |
| document.getElementById('chatInputContainer').classList.add('bottom-position'); |
| hasSentMessage = true; |
| updateProBanner(); |
| } |
| |
| const prompt = imageGenCheck.prompt; |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| chat.messages.push({ |
| role: 'user', |
| content: `Generate image: ${prompt}`, |
| sender: 'user', |
| image: null |
| }); |
| const userMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(`Generate image: ${prompt}`, 'user', userMessageId, null); |
| |
| chat.messages.push({ |
| role: 'assistant', |
| content: 'Generating image...', |
| sender: 'bot', |
| isLoading: true |
| }); |
| const loadingMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addImageLoadingMessage(loadingMessageId); |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| await generateImageAndUpdate(prompt, loadingMessageId); |
| return; |
| } |
| |
| |
| if (detectCodeRequest(message)) { |
| const language = extractCodeLanguage(message); |
| |
| |
| if (isProUser) { |
| |
| window.open('https://hyzecode.vercel.app', '_blank'); |
| showNotification('🚀 Opening Hyze Code - Advanced programming features!'); |
| } else { |
| openProgram('', language, 'New Program'); |
| } |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| chat.messages.push({ |
| role: 'user', |
| content: message, |
| sender: 'user', |
| image: null |
| }); |
| const userMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(message, 'user', userMessageId, null); |
| |
| const responseText = isProUser |
| ? `I'll help you with that code! I've opened Hyze Code with advanced programming features for ${language}.` |
| : `I'll help you with that code! I've opened a program in the right panel where you can write and test your ${language} code.`; |
| |
| chat.messages.push({ |
| role: 'assistant', |
| content: responseText, |
| sender: 'bot' |
| }); |
| const botMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(responseText, 'bot', botMessageId); |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| return; |
| } |
| |
| if (!currentChatId) { |
| newChat(); |
| } |
| |
| if (!hasSentMessage) { |
| document.getElementById('logoContainer').classList.add('hidden'); |
| document.getElementById('chatInputContainer').classList.remove('centered'); |
| document.getElementById('chatInputContainer').classList.add('bottom-position'); |
| hasSentMessage = true; |
| updateProBanner(); |
| } |
| |
| const chat = chats.find(c => c.id === currentChatId); |
| if (chat.title === 'New Chat' && message) { |
| chat.title = message.substring(0, 30) + (message.length > 30 ? '...' : ''); |
| } |
| |
| if (selectedImage) { |
| const question = message || "Describe this image in detail."; |
| const userMessage = { |
| role: 'user', |
| content: question, |
| sender: 'user', |
| image: selectedImage |
| }; |
| chat.messages.push(userMessage); |
| const messageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(userMessage.content, 'user', messageId, selectedImage); |
| |
| scrollToBottom(true, true); |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| selectedImage = null; |
| updateSmallImagePreview(null); |
| chatFileInput.value = ''; |
| input.placeholder = 'Message HyzeBot...'; |
| |
| const sendButton = document.getElementById('sendButton'); |
| const stopButton = document.getElementById('stopButton'); |
| input.disabled = true; |
| sendButton.disabled = true; |
| sendButton.style.display = 'none'; |
| stopButton.classList.add('active'); |
| |
| isAITyping = true; |
| document.getElementById('typingIndicator').classList.add('active'); |
| |
| try { |
| abortController = new AbortController(); |
| showNotification('🔍 Analyzing image with Hyze Vision...'); |
| |
| await new Promise(resolve => setTimeout(resolve, 300)); |
| |
| const visionResponse = await analyzeImageWithGroq(userMessage.image, userMessage.content); |
| |
| if (!visionResponse) { |
| throw new Error('Failed to analyze image'); |
| } |
| |
| chat.messages.push({ role: 'assistant', content: visionResponse, sender: 'bot' }); |
| const botMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| |
| trackMessage(message, 'Hyze Vision'); |
| |
| document.getElementById('typingIndicator').classList.remove('active'); |
| |
| streamMessageToDOM(visionResponse, 'bot', botMessageId); |
| speakText(visionResponse); |
| |
| saveUserData(); |
| |
| } catch (error) { |
| console.error('Vision Error:', error); |
| document.getElementById('typingIndicator').classList.remove('active'); |
| streamMessageToDOM(`Hyze Vision Servers are down\n\n${error.message}`, 'bot', `error-${Date.now()}`); |
| } finally { |
| isAITyping = false; |
| input.disabled = false; |
| sendButton.disabled = false; |
| sendButton.style.display = 'block'; |
| stopButton.classList.remove('active'); |
| input.focus(); |
| abortController = null; |
| } |
| return; |
| } |
| |
| const userMessage = { |
| role: 'user', |
| content: message, |
| sender: 'user', |
| image: null |
| }; |
| chat.messages.push(userMessage); |
| const messageId = `${currentChatId}-${chat.messages.length - 1}`; |
| addMessageToDOM(userMessage.content, 'user', messageId, null); |
| |
| scrollToBottom(true, true); |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| const sendButton = document.getElementById('sendButton'); |
| const stopButton = document.getElementById('stopButton'); |
| input.disabled = true; |
| sendButton.disabled = true; |
| sendButton.style.display = 'none'; |
| stopButton.classList.add('active'); |
| |
| isAITyping = true; |
| document.getElementById('typingIndicator').classList.add('active'); |
| |
| const cacheKey = `${message.slice(0, 50)}_${currentModel}`; |
| const cachedResponse = responseCache.get(cacheKey); |
| if (cachedResponse && Date.now() - cachedResponse.timestamp < CACHE_TTL) { |
| chat.messages.push({ role: 'assistant', content: cachedResponse.text, sender: 'bot' }); |
| const botMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| trackMessage(message, cachedResponse.model); |
| |
| document.getElementById('typingIndicator').classList.remove('active'); |
| |
| await new Promise(resolve => setTimeout(resolve, 150)); |
| |
| streamMessageToDOM(cachedResponse.text, 'bot', botMessageId); |
| speakText(cachedResponse.text); |
| |
| saveUserData(); |
| |
| isAITyping = false; |
| input.disabled = false; |
| sendButton.disabled = false; |
| sendButton.style.display = 'block'; |
| stopButton.classList.remove('active'); |
| input.focus(); |
| return; |
| } |
| |
| try { |
| abortController = new AbortController(); |
| let modelToUse = currentModel === 'auto' ? selectBestModel(message) : currentModel; |
| |
| |
| if (modelToUse === 'meta-llama/llama-4-scout-17b-16e-instruct' && !isProUser) { |
| showNotification('🔒 RE2 Model is a Pro feature. Using Hyze Fast instead.'); |
| modelToUse = 'llama-3.1-8b-instant'; |
| } |
| |
| const mcpContext = await processWithMCPServers(message); |
| |
| let response; |
| const isFastPath = modelToUse === 'llama-3.1-8b-instant' || modelToUse === 'llama-3.3-70b-versatile'; |
| |
| const messagesForAPI = [ |
| { role: 'system', content: customSystemPrompt || getSystemPrompt() }, |
| ...chat.messages.slice(-10).map(msg => ({ |
| role: msg.sender === 'user' ? 'user' : 'assistant', |
| content: msg.content + (msg === userMessage && mcpContext ? '\n\n' + mcpContext : '') |
| })) |
| ]; |
| |
| |
| response = await fetch(GROQ_URL, { |
| method: 'POST', |
| headers: { |
| 'Authorization': `Bearer ${GROQ_API_KEY}`, |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ |
| model: modelToUse === 'llama-3.1-8b-instant' ? modelToUse : 'llama-3.1-8b-instant', |
| messages: messagesForAPI, |
| temperature: 0.7, |
| max_tokens: 1000, |
| stream: true |
| }), |
| signal: abortController.signal |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`API Error: ${response.status}`); |
| } |
| |
| document.getElementById('typingIndicator').classList.remove('active'); |
| |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = 'message assistant'; |
| messageDiv.innerHTML = `<div class="message-content" id="streamingContent-${Date.now()}"></div>`; |
| document.getElementById('chatMessages').appendChild(messageDiv); |
| |
| const contentDiv = messageDiv.querySelector(`#streamingContent-${Date.now()}`); |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder(); |
| let fullContent = ''; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| const chunk = decoder.decode(value); |
| const lines = chunk.split('\n'); |
| |
| for (const line of lines) { |
| if (line.startsWith('data: ')) { |
| const data = line.slice(6); |
| if (data === '[DONE]') continue; |
| try { |
| const parsed = JSON.parse(data); |
| const content = parsed.choices?.[0]?.delta?.content || ''; |
| if (content) { |
| fullContent += content; |
| contentDiv.innerHTML = marked.parse(fullContent); |
| scrollToBottom(); |
| } |
| } catch (e) {} |
| } |
| } |
| } |
| |
| |
| contentDiv.querySelectorAll('pre code').forEach((block) => { |
| hljs.highlightElement(block); |
| }); |
| |
| |
| renderMath(contentDiv); |
| |
| processCodeBlocks(contentDiv); |
| |
| chat.messages.push({ role: 'assistant', content: fullContent, sender: 'bot' }); |
| const botMessageId = `${currentChatId}-${chat.messages.length - 1}`; |
| |
| |
| messageDiv.className = 'message bot message-target'; |
| messageDiv.dataset.messageId = botMessageId; |
| messageDiv.innerHTML = ` |
| <div class="message-content"> |
| ${marked.parse(fullContent)} |
| <div class="message-actions"> |
| <button class="action-btn" onclick="copyMessage('${botMessageId}')"><i class="fas fa-copy"></i></button> |
| <button class="reaction-btn" onclick="addReaction('${botMessageId}', '❤️')">❤️</button> |
| <button class="reaction-btn" onclick="addReaction('${botMessageId}', '😄')">😄</button> |
| <button class="reaction-btn" onclick="addReaction('${botMessageId}', '🤔')">🤔</button> |
| </div> |
| </div> |
| `; |
| |
| |
| setTimeout(() => { |
| messageDiv.querySelectorAll('pre code').forEach((block) => { |
| hljs.highlightElement(block); |
| }); |
| |
| renderMath(messageDiv.querySelector('.message-content')); |
| processCodeBlocks(messageDiv.querySelector('.message-content')); |
| }, 10); |
| |
| trackMessage(message, modelToUse); |
| responseCache.set(cacheKey, { |
| text: fullContent, |
| model: modelToUse, |
| timestamp: Date.now() |
| }); |
| |
| speakText(fullContent); |
| saveUserData(); |
| |
| } catch (error) { |
| console.error('Error:', error); |
| document.getElementById('typingIndicator').classList.remove('active'); |
| if (error.name === 'AbortError') { |
| showNotification('⏹️ AI response stopped by user'); |
| } else { |
| streamMessageToDOM(`Hyze Server Error\n\n${error.message}`, 'bot', `error-${Date.now()}`); |
| } |
| } finally { |
| isAITyping = false; |
| input.disabled = false; |
| sendButton.disabled = false; |
| sendButton.style.display = 'block'; |
| stopButton.classList.remove('active'); |
| input.focus(); |
| abortController = null; |
| } |
| } |
| |
| function addGeneratedImageToDOM(prompt, imageUrl, messageId) { |
| const messagesContainer = document.getElementById('chatMessages'); |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = 'message bot message-target'; |
| messageDiv.dataset.messageId = messageId; |
| |
| messageDiv.innerHTML = ` |
| <div class="message-content"> |
| <div style="background: var(--bg-secondary); border-radius: 16px; padding: 16px; margin: 10px 0; max-width: 400px;"> |
| <div style="width: 100%; aspect-ratio: 1; border-radius: 12px; overflow: hidden; background: var(--bg-primary); margin-bottom: 12px; position: relative;"> |
| <img src="${imageUrl}" style="width: 100%; height: 100%; object-fit: cover; display: block;" alt="Generated image"> |
| </div> |
| <div style="font-size: 13px; color: var(--text-secondary); margin-bottom: 12px;"> |
| 🎨 Generated by Hyze Render (1024x1024) |
| </div> |
| <div style="display: flex; gap: 8px; justify-content: flex-end;"> |
| <button onclick="downloadChatImage('${imageUrl}')" style="background: var(--accent-color); color: var(--bg-primary); border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.3s; display: flex; align-items: center; gap: 6px;"> |
| <i class="fas fa-download"></i> Download |
| </button> |
| </div> |
| </div> |
| <div class="message-actions" style="display: flex; gap: 6px; margin-top: 8px;"> |
| <button class="action-btn" onclick="copyMessage('${messageId}')"><i class="fas fa-copy"></i></button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '❤️')">❤️</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '😄')">😄</button> |
| <button class="reaction-btn" onclick="addReaction('${messageId}', '🤔')">🤔</button> |
| </div> |
| </div> |
| `; |
| |
| messagesContainer.appendChild(messageDiv); |
| scrollToBottom(); |
| return messageDiv; |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', async function() { |
| await initAuth0(); |
| |
| const sidebarExpanded = localStorage.getItem('hiteshai_sidebar_expanded') === 'true'; |
| if (sidebarExpanded) { |
| document.getElementById('sidebar').classList.add('expanded'); |
| document.getElementById('toggleBtn').innerHTML = '<i class="fas fa-chevron-left"></i>'; |
| } |
| |
| |
| if ('webkitSpeechRecognition' in window) { |
| recognition = new webkitSpeechRecognition(); |
| recognition.continuous = true; |
| recognition.interimResults = true; |
| recognition.lang = 'en-US'; |
| |
| recognition.onstart = () => { |
| document.getElementById('voiceInputBtn').classList.add('listening'); |
| document.getElementById('voiceStatusText').textContent = 'Listening...'; |
| }; |
| |
| recognition.onresult = (event) => { |
| let interimTranscript = ''; |
| let finalTranscript = ''; |
| |
| for (let i = event.resultIndex; i < event.results.length; i++) { |
| const transcript = event.results[i][0].transcript; |
| if (event.results[i].isFinal) { |
| finalTranscript += transcript; |
| } else { |
| interimTranscript += transcript; |
| } |
| } |
| |
| if (finalTranscript) { |
| document.getElementById('chatInput').value = finalTranscript.trim(); |
| if (autoSendVoice) { |
| sendMessage(); |
| } |
| recognition.stop(); |
| } |
| }; |
| |
| recognition.onend = () => { |
| document.getElementById('voiceInputBtn').classList.remove('listening'); |
| if (isVoiceChatActive) { |
| document.getElementById('voiceStatusText').textContent = 'Voice mode active - Click mic to talk'; |
| } |
| }; |
| } |
| |
| |
| const chatInput = document.getElementById('chatInput'); |
| const sendButton = document.getElementById('sendButton'); |
| |
| chatInput.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendMessage(); |
| } |
| |
| |
| chatInput.style.height = 'auto'; |
| chatInput.style.height = Math.min(chatInput.scrollHeight, 120) + 'px'; |
| }); |
| |
| chatInput.addEventListener('input', () => { |
| chatInput.style.height = 'auto'; |
| chatInput.style.height = Math.min(chatInput.scrollHeight, 120) + 'px'; |
| sendButton.disabled = chatInput.value.trim() === '' && !selectedImage; |
| }); |
| |
| sendButton.addEventListener('click', sendMessage); |
| |
| |
| initSmallImagePreview(); |
| |
| |
| setupScrollListener(); |
| |
| |
| initKaTeX(); |
| |
| |
| const isAuthenticated = await checkAuth(); |
| if (isAuthenticated) { |
| initTheme(); |
| } |
| }); |
| |
| |
| function showNotification(message) { |
| const existing = document.querySelector('.notification'); |
| if (existing) existing.remove(); |
| |
| const notification = document.createElement('div'); |
| notification.className = 'notification'; |
| notification.textContent = message; |
| |
| document.body.appendChild(notification); |
| |
| setTimeout(() => { |
| notification.style.animation = 'slideOut 0.3s ease-out'; |
| setTimeout(() => notification.remove(), 300); |
| }, 3000); |
| } |
| </script> |
| </body> |
| </html> |