Chatbot / index.html
HyzeAI's picture
Upload index.html
a286c1f verified
Raw
History Blame Contribute Delete
293 kB
<!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>
<!-- Auth0 SDK -->
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.0/auth0-spa-js.production.js"></script>
<style>
/* All your existing CSS styles remain exactly the same */
* { 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 layout with chat and program */
.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);
}
/* Changed: AI response matches background, no borders */
.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 Styles - Updated: All text white */
.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; /* Force white text */
}
.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;
}
/* Updated: Message actions and reactions combined in one row for AI */
.message-actions {
display: flex;
gap: 6px;
margin-top: 8px;
flex-wrap: wrap;
align-items: center;
}
/* User messages: Only copy button */
.message.user .message-actions {
justify-content: flex-end;
}
/* Bot messages: Copy + reactions in same row */
.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);
}
/* User messages don't show reactions */
.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);
}
/* NEW: Small image preview next to send button */
.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 */
.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 */
.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);
}
/* Updated: Buttons without borders */
.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);
}
/* NEW: Agents Modal Styles */
.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; /* Hide rating container */
}
@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;
}
/* Google Sign In */
.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 Actions */
.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;
}
/* Projects Styles */
.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 Styles */
.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 Subscription Styles */
.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 in settings */
.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 Alignment Fixes */
.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 Styles */
.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 Styles */
.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 of Service Modal */
.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 Modal */
.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 Activation Button Animation */
.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 Button - Top Middle */
.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 */
.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>
<!-- Voice Status Indicator -->
<div class="voice-status" id="voiceStatus">
<div class="voice-pulse"></div>
<span id="voiceStatusText">Voice mode active</span>
</div>
<!-- Get Pro Banner - Top Middle -->
<div class="get-pro-banner" id="getProBanner" onclick="openProActivation()">
<i class="fas fa-crown"></i>
<span>Get Pro</span>
</div>
<!-- Mobile App QR Code Button -->
<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>
<!-- Authentication Screen -->
<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>
<!-- Divider -->
<div class="divider">or continue with</div>
<!-- Google Sign In Button -->
<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>
<!-- NEW: Agents Button for all users -->
<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>
<!-- Hyze Code Button - Only for Pro -->
<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>
<!-- Square Image Loader -->
<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>
<!-- Quick Image Generation 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>
<!-- NEW: Small image preview next to send button -->
<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>
<!-- Program Section -->
<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>
<!-- NEW: Agents Modal -->
<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>
<!-- Active Agent Section -->
<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>
<!-- Premade Agents -->
<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">
<!-- Premade agents will be rendered here -->
</div>
</div>
<!-- Custom Agents -->
<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;">
<!-- Custom agents will be rendered here -->
</div>
</div>
<div class="modal-actions">
<button class="modal-btn modal-btn-secondary" onclick="closeAgentsModal()">Close</button>
</div>
</div>
</div>
<!-- NEW: Create/Edit Agent Modal -->
<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>
<!-- MCP servers will be populated here -->
</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>
<!-- Projects Modal -->
<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;">
<!-- Projects will be rendered here -->
</div>
<div class="modal-actions">
<button class="modal-btn modal-btn-secondary" onclick="closeProjectsModal()">Close</button>
</div>
</div>
</div>
<!-- Image Gen Modal -->
<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>
<!-- Search Modal -->
<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>
<!-- Chat History Modal -->
<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>
<!-- Analytics Modal -->
<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>
<!-- Voice Settings Modal -->
<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>
<!-- Model Modal -->
<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">
<!-- Models will be populated here -->
</div>
<div class="modal-actions">
<button class="modal-btn modal-btn-primary" onclick="closeModelModal()">Close</button>
</div>
</div>
</div>
<!-- Settings Modal -->
<div class="modal" id="settingsModal">
<div class="modal-content" style="max-width: 700px;">
<h2><i class="fas fa-cog"></i> Settings</h2>
<!-- Pro Subscription Section -->
<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>
<!-- Hyze Code Feature for Pro Users -->
<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>
<!-- Terms of Service Section -->
<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>
<!-- MCP Servers Section -->
<div class="settings-section">
<div class="settings-section-title">
<i class="fas fa-server"></i> MCP Servers
</div>
<div id="mcpServersList">
<!-- MCP servers will be rendered here -->
</div>
<button class="add-mcp-btn" onclick="addNewMCPServer()">
<i class="fas fa-plus"></i> Add Custom MCP Server
</button>
</div>
<!-- Voice Settings -->
<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>
<!-- Custom System Prompt -->
<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>
<!-- Appearance -->
<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>
<!-- User Account Settings -->
<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>
<!-- Terms of Service Modal -->
<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>
<!-- Pro Activation Modal -->
<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>
<!-- Add/Edit MCP Server Modal -->
<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>
/* ========== OPTIMIZED API CONFIGURATION ========== */
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';
// Pollinations API Configuration
const POLLINATIONS_API_KEY = 'sk_SUfq0Ypw3zwGmSO0tQL9Cu4p9QDdXaDn';
// Inworld TTS Configuration
const INWORLD_TTS_URL = 'https://api.inworld.ai/tts/v1/voice';
const INWORLD_API_KEY = 'anFncTkzd1VISHpvOTlhQzlkV2k1SERzVHR6OFc4MVU6WWRnMDFKQlg1MTA3MnpWbGJXMllqTmNyaGRFUmR5dk0xOFlhbVI2UW9zV1dMeElvZDM3V3hpQkpKN25UU0xLMQ==';
// Default MCP Servers
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'
}
};
// Voice configuration
let selectedVoiceCharacter = localStorage.getItem('hiteshai_voice_character') || 'Dennis';
let autoSendVoice = localStorage.getItem('hiteshai_auto_send') !== 'false';
let continuousVoiceMode = localStorage.getItem('hiteshai_continuous_voice') === 'true';
// Projects data structure
let projects = [];
// NEW: Agents data structure
let agents = [];
let activeAgentId = null;
// Pro subscription status
let isProUser = localStorage.getItem('hyze_pro_status') === 'true';
// Valid Pro activation codes
const PRO_CODES = ['2013', '6741', '2019', '1984', '1989', '0205', '2603'];
// Auth0 Configuration
let auth0Client = null;
const AUTH0_DOMAIN = 'dev-rrafxpsphogfah1w.us.auth0.com';
const AUTH0_CLIENT_ID = 'v1VnMqyxe7vmse5OXB0iZ30QXQhvhq0E';
// Initialize Auth0
async function initAuth0() {
try {
auth0Client = await auth0.createAuth0Client({
domain: AUTH0_DOMAIN,
clientId: AUTH0_CLIENT_ID,
authorizationParams: {
redirect_uri: window.location.origin + window.location.pathname
}
});
// Check if we're returning from Auth0 redirect
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);
}
}
// Login with Google via Auth0
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.');
}
}
// Handle Auth0 user login
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: [], // NEW: Initialize empty agents array
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(); // NEW: Load agents
showNotification(`✅ Welcome, ${auth0User.name || username}!`);
}
// Logout Auth0
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 = '';
// Clear inputs
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}`);
// Only allow numbers
input.value = input.value.replace(/[^0-9]/g, '');
if (input.value.length === 1 && nextInput) {
nextInput.focus();
}
// Auto-submit if all 4 digits are entered
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;
}
// Add activating animation
activateBtn.classList.add('activating');
setTimeout(() => {
if (PRO_CODES.includes(enteredCode)) {
isProUser = true;
localStorage.setItem('hyze_pro_status', 'true');
// Success animation
activateBtn.classList.remove('activating');
activateBtn.classList.add('success');
setTimeout(() => {
updateProStatus();
updateProBanner();
updateHyzeCodeButton();
updateLogos();
closeProActivation();
showNotification('🎉 Pro subscription activated! Welcome to Hyze Pro!');
// Reset button
activateBtn.classList.remove('success');
}, 500);
} else {
// Error animation
activateBtn.classList.remove('activating');
activateBtn.classList.add('error');
messageElement.textContent = '❌ Invalid activation code. Please try again.';
messageElement.style.color = '#ff4444';
// Clear inputs
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');
}
// PHONE PANEL FUNCTIONS
function togglePhonePanel() {
const panel = document.getElementById('phonePanel');
panel.classList.toggle('active');
}
// Close panel when clicking outside
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');
}
});
/* ========== NEW: AGENTS FUNCTIONS ========== */
// Load agents from localStorage
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 = [];
}
// Load active agent ID
activeAgentId = localStorage.getItem(`hiteshai_active_agent_${currentUser.username}`) || null;
// Create premade agents if they don't exist
createPremadeAgents();
}
// Save agents to localStorage
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));
}
// Create premade agents
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'
}
];
// Add premade agents if they don't exist
premadeAgents.forEach(premadeAgent => {
if (!agents.find(a => a.id === premadeAgent.id)) {
agents.push(premadeAgent);
}
});
saveAgents();
}
// Open agents modal
function openAgentsModal() {
document.getElementById('agentsModal').classList.add('active');
renderAgents();
renderActiveAgent();
}
// Close agents modal
function closeAgentsModal() {
document.getElementById('agentsModal').classList.remove('active');
}
// Render agents in the modal
function renderAgents() {
renderPremadeAgents();
renderCustomAgents();
}
// Render premade agents
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('');
}
// Render custom agents
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('');
}
// Render active agent display
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>
`;
}
// Create new agent
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 = '';
// Populate MCP server dropdown
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');
}
// Edit existing agent
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;
// Populate MCP server dropdown
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');
}
// Close agent edit modal
function closeAgentEditModal() {
document.getElementById('agentEditModal').classList.remove('active');
}
// Save agent (create or update)
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) {
// Update existing agent
const index = agents.findIndex(a => a.id === id);
if (index !== -1) {
agents[index] = { ...agents[index], ...agentData };
}
} else {
// Create new agent
agents.push(agentData);
}
saveAgents();
renderAgents();
closeAgentEditModal();
showNotification('✅ Agent saved successfully!');
}
// Delete agent
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 deleting active agent, deselect it
if (activeAgentId === agentId) {
deselectAgent();
}
saveAgents();
renderAgents();
renderActiveAgent();
showNotification('🗑️ Agent deleted');
}
}
// Select an agent to use
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();
}
}
// Deselect active agent
function deselectAgent() {
activeAgentId = null;
localStorage.removeItem(`hiteshai_active_agent_${currentUser.username}`);
showNotification('✅ Agent deselected');
renderActiveAgent();
renderAgents();
}
// Get active agent training for system prompt
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;
}
/* ========== NEW: SMALL IMAGE PREVIEW FUNCTIONS ========== */
// Initialize small image preview
function initSmallImagePreview() {
const imagePreviewMini = document.getElementById('imagePreviewMini');
const imagePreviewMiniImg = document.getElementById('imagePreviewMiniImg');
const removeMiniBtn = document.getElementById('removeMiniBtn');
// Handle remove button click
removeMiniBtn.addEventListener('click', function(e) {
e.stopPropagation();
removeUploadedImage();
});
// Handle click on preview to show larger view
imagePreviewMini.addEventListener('click', function() {
if (selectedImage) {
showLargeImagePreview(selectedImage);
}
});
}
// Show large image preview in modal
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);
// Close modal when clicking outside
modal.addEventListener('click', function(e) {
if (e.target === modal) {
modal.remove();
}
});
}
// Download image
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!');
}
// Update small image preview
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 = '';
}
}
// WEATHER WIDGET FUNCTIONS - FIXED TOGGLE
async function fetchWeatherData(city) {
try {
// Geocode the city name to get coordinates
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;
// Get weather data using coordinates
const weatherResponse = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=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;
}
// FIXED: Weather unit toggle that works multiple times
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');
// Get stored Fahrenheit values from data attributes
const originalTempF = parseFloat(weatherCard.dataset.tempF);
const originalHighF = parseFloat(weatherCard.dataset.highF);
const originalLowF = parseFloat(weatherCard.dataset.lowF);
// Check current active unit
const isCelsiusActive = unitSpans[1].classList.contains('active');
if (isCelsiusActive) {
// Switch to Fahrenheit
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 {
// Switch to Celsius
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');
}
}
// FIXED: Improved weather query detection to reduce false positives
function isWeatherQuery(message) {
const lowerMessage = message.toLowerCase().trim();
// More specific weather patterns to reduce false positives
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
];
// Check if message matches any weather pattern
const isPatternMatch = weatherPatterns.some(pattern => pattern.test(lowerMessage));
// Additional checks to avoid false positives
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;
}
// Extract city from weather query
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();
}
}
// If no pattern matches, try to extract the last word that looks like a city
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;
}
// PROGRAM FUNCTIONS
let currentProgram = null;
const programBaseURL = 'https://hyze.ai/playground'; // Base URL for program 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 };
// Show loading
programLoading.style.display = 'flex';
programIframe.style.display = 'none';
// Load the playground in iframe
programIframe.src = programBaseURL;
programIframe.onload = function() {
programLoading.style.display = 'none';
programIframe.style.display = 'block';
// Send code to iframe (if playground supports postMessage)
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');
}
// FIXED: Hyze Code now redirects to external website
function openHyzeCode() {
if (!isProUser) {
showNotification('🔒 Hyze Code is a Pro feature. Upgrade to access advanced coding features!');
openProActivation();
return;
}
// Redirect to external Hyze Code website
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...');
// In a real implementation, this would execute the code in the playground
}
// MCP SERVER FUNCTIONS
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;
}
// PROJECTS FUNCTIONS
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');
}
// MCP SETTINGS FUNCTIONS
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';
}
}
// CODE BLOCK FUNCTIONS
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);
}
});
}
/* ========== KATEX MATH RENDERING FIXES ========== */
// Configure marked to preserve LaTeX
marked.setOptions({
breaks: true,
gfm: true,
highlight: function(code, lang) {
if (hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return code;
}
});
// KaTeX configuration
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
};
// Initialize KaTeX after DOM is ready
function initKaTeX() {
console.log('Initializing KaTeX...');
console.log('renderMathInElement available?', typeof renderMathInElement);
// Test KaTeX with a simple expression
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 to render math in an element
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);
// Fallback: Try direct KaTeX rendering for any missed elements
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);
}
});
}
}
// Optimized state management
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(); // NEW: Load agents
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: [], // NEW: Initialize empty agents array
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(); // NEW: Load agents
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!');
}
// Updated: Image analysis with llama-4-scout model
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();
}
// UPDATED: Get system prompt with agent support
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`;
// Add custom instructions
if (customSystemPrompt && customSystemPrompt.trim()) {
basePrompt += `\n\nADDITIONAL CUSTOM INSTRUCTIONS:\n${customSystemPrompt}`;
}
// Add active agent training if available
const agentTraining = getActiveAgentTraining();
if (agentTraining) {
basePrompt += agentTraining;
}
return basePrompt;
}
// UPDATED: Render model list with RE2 for Pro users only
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 },
// RE2 is now a Pro-only model
{
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 },
];
// Add additional Pro models if user is Pro
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) {
// Check if RE2 is selected and user is not Pro
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();
// Check for weather query
if (isWeatherQuery(lowerMsg)) {
return 'auto'; // Weather queries don't need specific model
}
// Check for coding
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')) {
// Check if user is Pro before selecting RE2
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')) {
// Check if user is Pro before selecting RE2
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');
}
}
};
}
// Voice Settings Functions
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');
}
/* ========== UPDATED IMAGE UPLOAD HANDLING ========== */
const uploadBtn = document.getElementById('uploadBtn');
const chatFileInput = document.getElementById('chatFileInput');
const removeMiniBtn = document.getElementById('removeMiniBtn');
// Remove uploaded image
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();
});
// ===== IMAGE GENERATION TOGGLE BUTTON =====
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...';
}
});
// ===== IMAGE GENERATION DETECTION =====
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 };
}
// ===== CODE DETECTION FOR PROGRAM =====
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'; // Default language
}
// ===== NEW IMAGE LOADING MESSAGE =====
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);
});
}
/* ========== UPDATED MESSAGE RENDERING WITH KATEX FIX ========== */
// Updated streaming function with proper KaTeX rendering
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';
// Parse markdown
const htmlContent = marked.parse(text);
contentDiv.innerHTML = htmlContent;
// Create actions container
const actionsDiv = document.createElement('div');
actionsDiv.className = 'message-actions';
if (sender === 'bot') {
// Bot message: Copy + reactions in one row (no rating buttons)
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 {
// User message: Only copy button
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);
// Apply syntax highlighting and KaTeX
setTimeout(() => {
// Syntax highlighting
contentDiv.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
// Inline code styling
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';
});
// KaTeX math rendering - IMPORTANT: Wait for content to be in DOM
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);
// Fallback to direct KaTeX for math elements
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); // Small delay to ensure DOM is ready
renderReaction(messageId);
scrollToBottom();
return messageDiv;
}
/* ========== UPDATED addMessageToDOM WITH KATEX FIX ========== */
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';
// Parse markdown with LaTeX preservation
const htmlContent = marked.parse(text);
contentDiv.innerHTML = htmlContent;
// Create actions container
const actionsDiv = document.createElement('div');
actionsDiv.className = 'message-actions';
if (sender === 'bot') {
// Bot message: Copy + reactions in one row (no rating buttons)
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 {
// User message: Only copy button
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);
// Apply syntax highlighting and KaTeX
setTimeout(() => {
// Syntax highlighting
contentDiv.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
// Inline code styling
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';
});
// KaTeX math rendering - Wait for DOM to be ready
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);
// Fallback for math expressions
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); // Small delay to ensure DOM is ready
renderReaction(messageId);
scrollToBottom();
return messageDiv;
}
// Optimized sendMessage with streaming like code.html
async function sendMessage() {
const input = document.getElementById('chatInput');
let message = input.value.trim();
if (!message && !selectedImage) return;
// FIXED: Only trigger weather flow if it's actually a weather query
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';
// Show loading for weather
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');
// Add weather widget to chat
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 image generation mode is active, treat as image generation
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;
}
// Check for image generation intent
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;
}
// Check for code request to open program
if (detectCodeRequest(message)) {
const language = extractCodeLanguage(message);
// Check if user is Pro for Hyze Code
if (isProUser) {
// Redirect to external Hyze Code website for Pro users
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;
// Check if trying to use RE2 without Pro
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 : '')
}))
];
// Use streaming for faster response like code.html
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', // Fast model for streaming
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) {}
}
}
}
// Post-processing
contentDiv.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
// Use KaTeX for math
renderMath(contentDiv);
processCodeBlocks(contentDiv);
chat.messages.push({ role: 'assistant', content: fullContent, sender: 'bot' });
const botMessageId = `${currentChatId}-${chat.messages.length - 1}`;
// Update with proper message structure
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>
`;
// Re-apply highlighting and KaTeX
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;
}
// Initialize everything
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>';
}
// Initialize voice recognition
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';
}
};
}
// Initialize chat input
const chatInput = document.getElementById('chatInput');
const sendButton = document.getElementById('sendButton');
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
// Auto-resize textarea
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);
// Initialize small image preview
initSmallImagePreview();
// Initialize scroll listener
setupScrollListener();
// Initialize KaTeX
initKaTeX();
// Check auth
const isAuthenticated = await checkAuth();
if (isAuthenticated) {
initTheme();
}
});
// Utility function for notifications
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>