|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>AI Name</title>
|
|
|
<link rel="icon" type="image/png" href="LOGO">
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
|
|
|
:root {
|
|
|
--primary-color: #2C64BA;
|
|
|
--light-bg: #FAFAFA;
|
|
|
--dark-bg: #09090B;
|
|
|
--light-text: #000000;
|
|
|
--dark-text: #FFFFFF;
|
|
|
--light-sidebar: #F5F5F5;
|
|
|
--dark-sidebar: #18181B;
|
|
|
--light-border: #E5E5E5;
|
|
|
--dark-border: #27272A;
|
|
|
}
|
|
|
|
|
|
|
|
|
body {
|
|
|
font-family: 'Lexend', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
|
background: var(--light-bg);
|
|
|
color: var(--light-text);
|
|
|
height: 100vh;
|
|
|
display: flex;
|
|
|
overflow: hidden;
|
|
|
transition: background 0.3s, color 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode {
|
|
|
background: var(--dark-bg);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
width: 260px;
|
|
|
background: var(--light-sidebar);
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
border-right: 1px solid var(--light-border);
|
|
|
transition: background 0.3s, border-color 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .sidebar {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-right-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo-container {
|
|
|
padding: 20px;
|
|
|
border-bottom: 1px solid var(--light-border);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .logo-container {
|
|
|
border-bottom-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo {
|
|
|
width: 32px;
|
|
|
height: 32px;
|
|
|
border-radius: 6px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: cover;
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo-text {
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.new-chat-btn {
|
|
|
margin: 16px;
|
|
|
padding: 12px 20px;
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
border-radius: 8px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
font-weight: 600;
|
|
|
transition: all 0.3s ease;
|
|
|
transform: translateY(0);
|
|
|
}
|
|
|
|
|
|
|
|
|
.new-chat-btn:hover {
|
|
|
opacity: 0.9;
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 4px 12px rgba(44, 100, 186, 0.3);
|
|
|
}
|
|
|
|
|
|
|
|
|
.new-chat-btn:active {
|
|
|
transform: translateY(0);
|
|
|
}
|
|
|
|
|
|
|
|
|
.recent-chats {
|
|
|
flex: 1;
|
|
|
padding: 16px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
|
|
|
.search-box {
|
|
|
margin: 0 16px 12px 16px;
|
|
|
padding: 8px 12px;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 6px;
|
|
|
background: var(--light-bg);
|
|
|
color: var(--light-text);
|
|
|
font-size: 13px;
|
|
|
outline: none;
|
|
|
width: calc(100% - 32px);
|
|
|
transition: border-color 0.2s, background 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .search-box {
|
|
|
background: var(--dark-bg);
|
|
|
border-color: var(--dark-border);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.search-box:focus {
|
|
|
border-color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.recent-chats h3 {
|
|
|
font-size: 12px;
|
|
|
text-transform: uppercase;
|
|
|
color: #888;
|
|
|
margin-bottom: 12px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item {
|
|
|
padding: 10px 12px;
|
|
|
margin-bottom: 4px;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
transition: background 0.2s;
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item.active {
|
|
|
background: rgba(44, 100, 186, 0.15);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item-title {
|
|
|
flex: 1;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item-edit {
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.2s;
|
|
|
padding: 4px 8px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item:hover .chat-item-edit {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
.rename-input {
|
|
|
width: 100%;
|
|
|
padding: 6px;
|
|
|
border: 1px solid var(--primary-color);
|
|
|
border-radius: 4px;
|
|
|
font-size: 13px;
|
|
|
background: var(--light-bg);
|
|
|
color: var(--light-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .rename-input {
|
|
|
background: var(--dark-bg);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-controls {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-btn {
|
|
|
padding: 10px 12px;
|
|
|
background: transparent;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 8px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .voice-btn {
|
|
|
border-color: var(--dark-border);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-btn:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
border-color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-btn.listening {
|
|
|
background: #ef4444;
|
|
|
color: white;
|
|
|
border-color: #ef4444;
|
|
|
animation: pulse 1.5s infinite;
|
|
|
}
|
|
|
|
|
|
|
|
|
@keyframes pulse {
|
|
|
0%, 100% { opacity: 1; }
|
|
|
50% { opacity: 0.7; }
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-main {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
background: var(--light-bg);
|
|
|
transition: background 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-main {
|
|
|
background: var(--dark-bg);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-header {
|
|
|
padding: 16px 24px;
|
|
|
border-bottom: 1px solid var(--light-border);
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
background: var(--light-bg);
|
|
|
transition: background 0.3s, border-color 0.3s;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-header {
|
|
|
background: var(--dark-bg);
|
|
|
border-bottom-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-header h1 {
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.theme-toggle, .export-btn, .lang-btn {
|
|
|
background: transparent;
|
|
|
border: 1px solid var(--light-border);
|
|
|
padding: 8px 16px;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
transition: background 0.2s, border-color 0.3s;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .theme-toggle,
|
|
|
body.dark-mode .export-btn,
|
|
|
body.dark-mode .lang-btn {
|
|
|
border-color: var(--dark-border);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.theme-toggle:hover, .export-btn:hover, .lang-btn:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-screen {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
padding: 40px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-logo {
|
|
|
width: 64px;
|
|
|
height: 64px;
|
|
|
border-radius: 12px;
|
|
|
margin-bottom: 24px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-logo img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: cover;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-screen h1 {
|
|
|
font-size: 32px;
|
|
|
margin-bottom: 12px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-screen p {
|
|
|
font-size: 16px;
|
|
|
color: #888;
|
|
|
max-width: 500px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-messages {
|
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
|
padding: 24px;
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-messages.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message {
|
|
|
margin-bottom: 20px;
|
|
|
display: flex;
|
|
|
animation: fadeIn 0.3s ease-in;
|
|
|
}
|
|
|
|
|
|
|
|
|
@keyframes fadeIn {
|
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
|
to { opacity: 1; transform: translateY(0); }
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user {
|
|
|
justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user .message-content {
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message-content {
|
|
|
max-width: 70%;
|
|
|
padding: 12px 16px;
|
|
|
border-radius: 12px;
|
|
|
line-height: 1.6;
|
|
|
font-size: 15px;
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.bot .message-content {
|
|
|
background: white;
|
|
|
color: #000;
|
|
|
border: 1px solid var(--light-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .message.bot .message-content {
|
|
|
background: var(--dark-sidebar);
|
|
|
color: var(--dark-text);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user .message-content {
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message-actions {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
margin-top: 8px;
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.2s;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.bot:hover .message-actions {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
background: transparent;
|
|
|
border: 1px solid var(--light-border);
|
|
|
padding: 4px 10px;
|
|
|
border-radius: 4px;
|
|
|
font-size: 12px;
|
|
|
cursor: pointer;
|
|
|
transition: background 0.2s;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .action-btn {
|
|
|
border-color: var(--dark-border);
|
|
|
color: #999;
|
|
|
}
|
|
|
|
|
|
|
|
|
.action-btn:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user .message-actions {
|
|
|
opacity: 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user:hover .message-actions {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-mode {
|
|
|
background: var(--light-bg);
|
|
|
border: 2px solid var(--primary-color);
|
|
|
border-radius: 8px;
|
|
|
padding: 8px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .edit-mode {
|
|
|
background: var(--dark-sidebar);
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-mode textarea {
|
|
|
width: 100%;
|
|
|
padding: 8px;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 4px;
|
|
|
font-family: inherit;
|
|
|
font-size: 14px;
|
|
|
resize: vertical;
|
|
|
min-height: 60px;
|
|
|
background: white;
|
|
|
color: var(--light-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .edit-mode textarea {
|
|
|
background: var(--dark-bg);
|
|
|
color: var(--dark-text);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
margin-top: 8px;
|
|
|
justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions button {
|
|
|
padding: 6px 12px;
|
|
|
border-radius: 4px;
|
|
|
border: none;
|
|
|
cursor: pointer;
|
|
|
font-size: 13px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions .save-btn {
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions .cancel-btn {
|
|
|
background: #e0e0e0;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .edit-actions .cancel-btn {
|
|
|
background: var(--dark-sidebar);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.code-block {
|
|
|
background: #f5f5f5;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 6px;
|
|
|
padding: 12px;
|
|
|
margin: 8px 0;
|
|
|
overflow-x: auto;
|
|
|
font-family: 'Courier New', monospace;
|
|
|
font-size: 13px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .code-block {
|
|
|
background: #1a1a1a;
|
|
|
border-color: #333;
|
|
|
}
|
|
|
|
|
|
|
|
|
.suggested-prompts {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
gap: 12px;
|
|
|
padding: 20px;
|
|
|
max-width: 900px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card {
|
|
|
background: white;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 10px;
|
|
|
padding: 16px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .prompt-card {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card:hover {
|
|
|
border-color: var(--primary-color);
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 4px 12px rgba(44, 100, 186, 0.2);
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card .icon {
|
|
|
font-size: 24px;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card .text {
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-attachment {
|
|
|
font-size: 13px;
|
|
|
padding: 8px 12px;
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
border-radius: 6px;
|
|
|
margin-top: 8px;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator {
|
|
|
display: none;
|
|
|
padding: 12px 16px;
|
|
|
background: white;
|
|
|
border-radius: 12px;
|
|
|
width: fit-content;
|
|
|
border: 1px solid var(--light-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .typing-indicator {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator span {
|
|
|
height: 8px;
|
|
|
width: 8px;
|
|
|
background: var(--primary-color);
|
|
|
border-radius: 50%;
|
|
|
display: inline-block;
|
|
|
margin: 0 2px;
|
|
|
animation: bounce 1.4s infinite;
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
|
|
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
|
|
|
|
|
|
|
|
@keyframes bounce {
|
|
|
0%, 60%, 100% { transform: translateY(0); }
|
|
|
30% { transform: translateY(-8px); }
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input-container {
|
|
|
padding: 20px 24px;
|
|
|
border-top: 1px solid var(--light-border);
|
|
|
background: var(--light-bg);
|
|
|
transition: background 0.3s, border-color 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-input-container {
|
|
|
background: var(--dark-bg);
|
|
|
border-top-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input-wrapper {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
max-width: 900px;
|
|
|
margin: 0 auto;
|
|
|
align-items: flex-end;
|
|
|
background: var(--light-bg);
|
|
|
border: 2px solid var(--light-border);
|
|
|
border-radius: 12px;
|
|
|
padding: 8px;
|
|
|
transition: border-color 0.2s, background 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-input-wrapper {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input-wrapper:focus-within {
|
|
|
border-color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-input-label {
|
|
|
padding: 10px 12px;
|
|
|
background: transparent;
|
|
|
border: none;
|
|
|
cursor: pointer;
|
|
|
transition: background 0.2s;
|
|
|
font-size: 20px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
border-radius: 6px;
|
|
|
color: #888;
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-input-label:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
#fileInput {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input {
|
|
|
flex: 1;
|
|
|
padding: 10px 12px;
|
|
|
border: none;
|
|
|
font-size: 15px;
|
|
|
outline: none;
|
|
|
font-family: inherit;
|
|
|
background: transparent;
|
|
|
color: var(--light-text);
|
|
|
resize: none;
|
|
|
max-height: 150px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-input {
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.send-button {
|
|
|
padding: 10px 12px;
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
border-radius: 8px;
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
cursor: pointer;
|
|
|
transition: opacity 0.2s;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
min-width: 40px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.send-button:hover:not(:disabled) {
|
|
|
opacity: 0.9;
|
|
|
}
|
|
|
|
|
|
|
|
|
.send-button:disabled {
|
|
|
opacity: 0.4;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu {
|
|
|
position: fixed;
|
|
|
background: white;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 8px;
|
|
|
padding: 4px;
|
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
|
display: none;
|
|
|
z-index: 1000;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .context-menu {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu-item {
|
|
|
padding: 8px 16px;
|
|
|
cursor: pointer;
|
|
|
border-radius: 4px;
|
|
|
font-size: 14px;
|
|
|
transition: background 0.2s;
|
|
|
color: #dc2626;
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu-item:hover {
|
|
|
background: rgba(220, 38, 38, 0.1);
|
|
|
}
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
.sidebar { width: 0; overflow: hidden; }
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="sidebar">
|
|
|
<div class="logo-container">
|
|
|
<div class="logo">
|
|
|
<img src="LOGO" alt="AI LOGO NAME">
|
|
|
</div>
|
|
|
<div class="logo-text">LOGO</div>
|
|
|
</div>
|
|
|
<button class="new-chat-btn" onclick="newChat()">+ New chat</button>
|
|
|
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search chats..." oninput="searchChats()">
|
|
|
<div class="recent-chats">
|
|
|
<h3>Recent chats</h3>
|
|
|
<div id="chatList"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-main">
|
|
|
<div class="chat-header">
|
|
|
<h1 id="chatTitle">AI NAME</h1>
|
|
|
<div class="header-actions">
|
|
|
<div class="voice-controls">
|
|
|
<button class="voice-btn" id="voiceBtn" onclick="toggleVoiceInput()" title="Voice input">
|
|
|
🎤 <span id="voiceStatus">Voice</span>
|
|
|
</button>
|
|
|
<button class="voice-btn" id="ttsBtn" onclick="toggleTTS()" title="Text-to-speech">
|
|
|
🔊 <span id="ttsStatus">TTS</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
<select class="lang-btn" id="langSelect" onchange="changeLanguage()">
|
|
|
<option value="en">🌐 English</option>
|
|
|
<option value="es">🌐 Español</option>
|
|
|
<option value="fr">🌐 Français</option>
|
|
|
<option value="de">🌐 Deutsch</option>
|
|
|
<option value="zh">🌐 中文</option>
|
|
|
<option value="hi">🌐 हिन्दी</option>
|
|
|
<option value="ar">🌐 العربية</option>
|
|
|
</select>
|
|
|
<button class="export-btn" onclick="exportChat()">📥 Export</button>
|
|
|
<button class="theme-toggle" onclick="toggleTheme()">🌙 Dark Mode</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="welcome-screen" id="welcomeScreen">
|
|
|
<div class="welcome-logo">
|
|
|
<img src="LOGO ADDRESS" alt="AI LOGO">
|
|
|
</div>
|
|
|
<h1>Welcome to AI</h1>
|
|
|
<p>Your AI assistant built by SOMEONE. Ask me anything about homework, aerospace, or just chat!</p>
|
|
|
|
|
|
<div class="suggested-prompts">
|
|
|
<div class="prompt-card" onclick="usePrompt('Help me with my math homework')">
|
|
|
<div class="icon">📐</div>
|
|
|
<div class="text">Help me with my math homework</div>
|
|
|
</div>
|
|
|
<div class="prompt-card" onclick="usePrompt('Write code for a simple game')">
|
|
|
<div class="icon">💻</div>
|
|
|
<div class="text">Write code for a simple game</div>
|
|
|
</div>
|
|
|
<div class="prompt-card" onclick="usePrompt('Explain rocket propulsion')">
|
|
|
<div class="icon">🚀</div>
|
|
|
<div class="text">Explain rocket propulsion</div>
|
|
|
</div>
|
|
|
<div class="prompt-card" onclick="usePrompt('Creative story ideas')">
|
|
|
<div class="icon">✨</div>
|
|
|
<div class="text">Creative story ideas</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-messages" id="chatMessages"></div>
|
|
|
<div class="typing-indicator" id="typingIndicator">
|
|
|
<span></span>
|
|
|
<span></span>
|
|
|
<span></span>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-input-container">
|
|
|
<div class="chat-input-wrapper">
|
|
|
<label for="fileInput" class="file-input-label" title="Attach file">📎</label>
|
|
|
<input type="file" id="fileInput" accept="image/png,image/jpeg,image/jpg,application/pdf" onchange="handleFileSelect(event)">
|
|
|
<textarea
|
|
|
class="chat-input"
|
|
|
id="chatInput"
|
|
|
placeholder="Send a message..."
|
|
|
rows="1"
|
|
|
oninput="autoResize(this)"
|
|
|
></textarea>
|
|
|
<button class="send-button" onclick="sendMessage()" id="sendButton" title="Send message">
|
|
|
↑
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="context-menu" id="contextMenu">
|
|
|
<div class="context-menu-item" onclick="deleteChat()">🗑️ Delete chat</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<script>
|
|
|
const API_KEY = 'API-KEY-HERE';
|
|
|
const API_URL = 'API-URL-HERE';
|
|
|
const MODEL = 'MODEL-ID-HERE';
|
|
|
|
|
|
|
|
|
let userId = localStorage.getItem('ai_user_id');
|
|
|
if (!userId) {
|
|
|
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
|
localStorage.setItem('ai_user_id', userId);
|
|
|
}
|
|
|
|
|
|
let chats = JSON.parse(localStorage.getItem(`ai_chats_${userId}`) || '[]');
|
|
|
let currentChatId = null;
|
|
|
let selectedFile = null;
|
|
|
let selectedChatForDelete = null;
|
|
|
let isListening = false;
|
|
|
let ttsEnabled = localStorage.getItem('ai_tts') === 'true';
|
|
|
let recognition = null;
|
|
|
let lastBotMessage = null;
|
|
|
|
|
|
|
|
|
|
|
|
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
|
|
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
recognition = new SpeechRecognition();
|
|
|
recognition.continuous = false;
|
|
|
recognition.interimResults = false;
|
|
|
recognition.lang = 'en-US';
|
|
|
|
|
|
|
|
|
recognition.onresult = (event) => {
|
|
|
const transcript = event.results[0][0].transcript;
|
|
|
document.getElementById('chatInput').value = transcript;
|
|
|
stopVoiceInput();
|
|
|
sendMessage();
|
|
|
};
|
|
|
|
|
|
|
|
|
recognition.onerror = () => {
|
|
|
stopVoiceInput();
|
|
|
};
|
|
|
|
|
|
|
|
|
recognition.onend = () => {
|
|
|
stopVoiceInput();
|
|
|
};
|
|
|
}
|
|
|
let currentLanguage = localStorage.getItem('hiteshai_language') || 'en';
|
|
|
|
|
|
|
|
|
const languagePrompts = {
|
|
|
en: "Respond in English.",
|
|
|
es: "Responde en español.",
|
|
|
fr: "Répondez en français.",
|
|
|
de: "Antworten Sie auf Deutsch.",
|
|
|
zh: "用中文回答。",
|
|
|
hi: "हिन्दी में जवाब दें।",
|
|
|
ar: "أجب بالعربية."
|
|
|
};
|
|
|
|
|
|
|
|
|
const systemPrompt = Custom Instructions/AI Bio
|
|
|
|
|
|
|
|
|
Key instructions:
|
|
|
1. (Write your instructions here)
|
|
|
CRITICAL: (Store Critical Rules/Guidelines here;
|
|
|
|
|
|
|
|
|
function toggleTheme() {
|
|
|
document.body.classList.toggle('dark-mode');
|
|
|
const btn = document.querySelector('.theme-toggle');
|
|
|
if (document.body.classList.contains('dark-mode')) {
|
|
|
btn.textContent = '☀️ Light Mode';
|
|
|
localStorage.setItem('hiteshai_theme', 'dark');
|
|
|
} else {
|
|
|
btn.textContent = '🌙 Dark Mode';
|
|
|
localStorage.setItem('hiteshai_theme', 'light');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function loadTheme() {
|
|
|
const theme = localStorage.getItem('hiteshai_theme');
|
|
|
if (theme === 'dark') {
|
|
|
document.body.classList.add('dark-mode');
|
|
|
document.querySelector('.theme-toggle').textContent = '☀️ Light Mode';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function newChat() {
|
|
|
const chatId = Date.now().toString();
|
|
|
const chat = {
|
|
|
id: chatId,
|
|
|
title: 'New Chat',
|
|
|
messages: [],
|
|
|
timestamp: Date.now()
|
|
|
};
|
|
|
chats.unshift(chat);
|
|
|
saveChats();
|
|
|
loadChat(chatId);
|
|
|
renderChatList();
|
|
|
}
|
|
|
|
|
|
|
|
|
function loadChat(chatId) {
|
|
|
currentChatId = chatId;
|
|
|
const chat = chats.find(c => c.id === chatId);
|
|
|
if (!chat) return;
|
|
|
|
|
|
|
|
|
document.getElementById('welcomeScreen').style.display = 'none';
|
|
|
document.getElementById('chatMessages').classList.add('active');
|
|
|
document.getElementById('chatTitle').textContent = chat.title;
|
|
|
|
|
|
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
messagesContainer.innerHTML = '';
|
|
|
|
|
|
chat.messages.forEach(msg => {
|
|
|
addMessageToDOM(msg.content, msg.sender, msg.file);
|
|
|
});
|
|
|
|
|
|
|
|
|
renderChatList();
|
|
|
}
|
|
|
|
|
|
|
|
|
function saveChats() {
|
|
|
localStorage.setItem(`hiteshai_chats_${userId}`, JSON.stringify(chats));
|
|
|
}
|
|
|
|
|
|
|
|
|
function renderChatList() {
|
|
|
const searchTerm = document.getElementById('searchBox')?.value.toLowerCase() || '';
|
|
|
const filteredChats = searchTerm
|
|
|
? chats.filter(chat => chat.title.toLowerCase().includes(searchTerm))
|
|
|
: chats;
|
|
|
|
|
|
const chatList = document.getElementById('chatList');
|
|
|
chatList.innerHTML = filteredChats.map(chat => `
|
|
|
<div class="chat-item ${chat.id === currentChatId ? 'active' : ''}"
|
|
|
onclick="loadChat('${chat.id}')"
|
|
|
oncontextmenu="showContextMenu(event, '${chat.id}'); return false;">
|
|
|
<span class="chat-item-title">${chat.title}</span>
|
|
|
<button class="chat-item-edit action-btn" onclick="event.stopPropagation(); renameChat('${chat.id}')">✏️</button>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
|
|
|
function renameChat(chatId) {
|
|
|
const chat = chats.find(c => c.id === chatId);
|
|
|
if (!chat) return;
|
|
|
|
|
|
|
|
|
const chatItem = event.target.closest('.chat-item');
|
|
|
const titleSpan = chatItem.querySelector('.chat-item-title');
|
|
|
const currentTitle = chat.title;
|
|
|
|
|
|
|
|
|
titleSpan.innerHTML = `<input type="text" class="rename-input" value="${currentTitle}" onblur="saveRename('${chatId}', this.value)" onkeypress="if(event.key==='Enter') this.blur()">`;
|
|
|
const input = titleSpan.querySelector('input');
|
|
|
input.focus();
|
|
|
input.select();
|
|
|
}
|
|
|
|
|
|
|
|
|
function saveRename(chatId, newTitle) {
|
|
|
const chat = chats.find(c => c.id === chatId);
|
|
|
if (!chat || !newTitle.trim()) {
|
|
|
renderChatList();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
chat.title = newTitle.trim();
|
|
|
saveChats();
|
|
|
renderChatList();
|
|
|
|
|
|
|
|
|
if (chatId === currentChatId) {
|
|
|
document.getElementById('chatTitle').textContent = newTitle.trim();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function toggleVoiceInput() {
|
|
|
if (!recognition) {
|
|
|
alert('Voice input not supported in your browser!');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (isListening) {
|
|
|
stopVoiceInput();
|
|
|
} else {
|
|
|
startVoiceInput();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function startVoiceInput() {
|
|
|
isListening = true;
|
|
|
recognition.start();
|
|
|
document.getElementById('voiceBtn').classList.add('listening');
|
|
|
document.getElementById('voiceStatus').textContent = 'Listening...';
|
|
|
}
|
|
|
|
|
|
|
|
|
function stopVoiceInput() {
|
|
|
isListening = false;
|
|
|
if (recognition) recognition.stop();
|
|
|
document.getElementById('voiceBtn').classList.remove('listening');
|
|
|
document.getElementById('voiceStatus').textContent = 'Voice';
|
|
|
}
|
|
|
|
|
|
|
|
|
function toggleTTS() {
|
|
|
ttsEnabled = !ttsEnabled;
|
|
|
localStorage.setItem('hiteshai_tts', ttsEnabled);
|
|
|
const btn = document.getElementById('ttsBtn');
|
|
|
const status = document.getElementById('ttsStatus');
|
|
|
|
|
|
if (ttsEnabled) {
|
|
|
btn.style.background = 'rgba(44, 100, 186, 0.1)';
|
|
|
btn.style.borderColor = 'var(--primary-color)';
|
|
|
status.textContent = 'TTS On';
|
|
|
} else {
|
|
|
btn.style.background = 'transparent';
|
|
|
btn.style.borderColor = 'var(--light-border)';
|
|
|
status.textContent = 'TTS';
|
|
|
window.speechSynthesis.cancel();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function speakText(text) {
|
|
|
if (!ttsEnabled) return;
|
|
|
|
|
|
window.speechSynthesis.cancel();
|
|
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
|
utterance.rate = 1.0;
|
|
|
utterance.pitch = 1.0;
|
|
|
utterance.volume = 1.0;
|
|
|
window.speechSynthesis.speak(utterance);
|
|
|
}
|
|
|
|
|
|
|
|
|
function searchChats() {
|
|
|
renderChatList();
|
|
|
}
|
|
|
|
|
|
|
|
|
function changeLanguage() {
|
|
|
currentLanguage = document.getElementById('langSelect').value;
|
|
|
localStorage.setItem('hiteshai_language', currentLanguage);
|
|
|
}
|
|
|
|
|
|
|
|
|
function usePrompt(prompt) {
|
|
|
if (!currentChatId) {
|
|
|
newChat();
|
|
|
}
|
|
|
document.getElementById('welcomeScreen').style.display = 'none';
|
|
|
document.getElementById('chatMessages').classList.add('active');
|
|
|
document.getElementById('chatInput').value = prompt;
|
|
|
sendMessage();
|
|
|
}
|
|
|
|
|
|
|
|
|
function exportChat() {
|
|
|
if (!currentChatId) {
|
|
|
alert('Please select a chat to export!');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
if (!chat) return;
|
|
|
|
|
|
|
|
|
const exportText = chat.messages.map(msg =>
|
|
|
`${msg.sender === 'user' ? 'You' : 'HiteshAI'}: ${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);
|
|
|
}
|
|
|
|
|
|
|
|
|
function copyResponse(text) {
|
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
|
const btn = event.target;
|
|
|
const originalText = btn.innerHTML;
|
|
|
btn.innerHTML = '✅ Copied!';
|
|
|
setTimeout(() => {
|
|
|
btn.innerHTML = originalText;
|
|
|
}, 2000);
|
|
|
}).catch(err => {
|
|
|
alert('Failed to copy. Please try again!');
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function showContextMenu(e, chatId) {
|
|
|
e.preventDefault();
|
|
|
selectedChatForDelete = chatId;
|
|
|
const menu = document.getElementById('contextMenu');
|
|
|
menu.style.left = e.pageX + 'px';
|
|
|
menu.style.top = e.pageY + 'px';
|
|
|
menu.classList.add('active');
|
|
|
}
|
|
|
|
|
|
|
|
|
function deleteChat() {
|
|
|
if (selectedChatForDelete) {
|
|
|
chats = chats.filter(c => c.id !== selectedChatForDelete);
|
|
|
saveChats();
|
|
|
if (currentChatId === selectedChatForDelete) {
|
|
|
currentChatId = null;
|
|
|
document.getElementById('welcomeScreen').style.display = 'flex';
|
|
|
document.getElementById('chatMessages').classList.remove('active');
|
|
|
document.getElementById('chatMessages').innerHTML = '';
|
|
|
}
|
|
|
renderChatList();
|
|
|
}
|
|
|
document.getElementById('contextMenu').classList.remove('active');
|
|
|
}
|
|
|
|
|
|
|
|
|
document.addEventListener('click', () => {
|
|
|
document.getElementById('contextMenu').classList.remove('active');
|
|
|
});
|
|
|
|
|
|
|
|
|
function handleFileSelect(event) {
|
|
|
selectedFile = event.target.files[0];
|
|
|
if (selectedFile) {
|
|
|
const fileTypes = ['image/png', 'image/jpeg', 'image/jpg', 'application/pdf'];
|
|
|
if (!fileTypes.includes(selectedFile.type)) {
|
|
|
alert('Please upload PNG, JPEG, JPG, or PDF files only');
|
|
|
selectedFile = null;
|
|
|
event.target.value = '';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const input = document.getElementById('chatInput');
|
|
|
input.placeholder = `📎 ${selectedFile.name} - Type a message or press Enter to send`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function autoResize(textarea) {
|
|
|
textarea.style.height = 'auto';
|
|
|
textarea.style.height = Math.min(textarea.scrollHeight, 150) + 'px';
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
const input = document.getElementById('chatInput');
|
|
|
input.addEventListener('keydown', (e) => {
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
e.preventDefault();
|
|
|
sendMessage();
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
async function sendMessage() {
|
|
|
const input = document.getElementById('chatInput');
|
|
|
const message = input.value.trim();
|
|
|
|
|
|
if (!message && !selectedFile) return;
|
|
|
|
|
|
|
|
|
if (!currentChatId) {
|
|
|
newChat();
|
|
|
}
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
if (chat.title === 'New Chat' && message) {
|
|
|
chat.title = message.substring(0, 30) + (message.length > 30 ? '...' : '');
|
|
|
}
|
|
|
|
|
|
|
|
|
const userMessage = { role: 'user', content: message, sender: 'user', file: selectedFile ? selectedFile.name : null };
|
|
|
chat.messages.push(userMessage);
|
|
|
addMessageToDOM(message, 'user', selectedFile ? selectedFile.name : null);
|
|
|
|
|
|
input.value = '';
|
|
|
input.style.height = 'auto';
|
|
|
|
|
|
|
|
|
const sendButton = document.getElementById('sendButton');
|
|
|
input.disabled = true;
|
|
|
sendButton.disabled = true;
|
|
|
|
|
|
|
|
|
document.getElementById('typingIndicator').classList.add('active');
|
|
|
|
|
|
|
|
|
try {
|
|
|
const messages = [{ role: 'system', content: systemPrompt + ' ' + languagePrompts[currentLanguage] }];
|
|
|
|
|
|
if (selectedFile) {
|
|
|
const base64 = await fileToBase64(selectedFile);
|
|
|
const fileType = selectedFile.type;
|
|
|
|
|
|
if (fileType.startsWith('image/')) {
|
|
|
messages.push({
|
|
|
role: 'user',
|
|
|
content: [
|
|
|
{ type: 'image_url', image_url: { url: base64 } },
|
|
|
{ type: 'text', text: message || 'What do you see in this image?' }
|
|
|
]
|
|
|
});
|
|
|
} else if (fileType === 'application/pdf') {
|
|
|
messages.push({
|
|
|
role: 'user',
|
|
|
content: [
|
|
|
{ type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: base64.split(',')[1] } },
|
|
|
{ type: 'text', text: message || 'Summarize this document.' }
|
|
|
]
|
|
|
});
|
|
|
}
|
|
|
selectedFile = null;
|
|
|
document.getElementById('fileInput').value = '';
|
|
|
} else {
|
|
|
chat.messages.slice(-10).forEach(msg => {
|
|
|
messages.push({ role: msg.sender === 'user' ? 'user' : 'assistant', content: msg.content });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
const response = await fetch(API_URL, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Authorization': `Bearer ${API_KEY}`,
|
|
|
'Content-Type': 'application/json',
|
|
|
'HTTP-Referer': window.location.href || 'https://hiteshai.com',
|
|
|
'X-Title': 'HiteshAI'
|
|
|
},
|
|
|
body: JSON.stringify({
|
|
|
model: 'deepseek/deepseek-chat',
|
|
|
messages: messages,
|
|
|
temperature: 0.7,
|
|
|
max_tokens: 800
|
|
|
})
|
|
|
});
|
|
|
|
|
|
|
|
|
if (!response.ok) throw new Error(`API Error: ${response.status}`);
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
const botMessage = data.choices[0].message.content;
|
|
|
|
|
|
|
|
|
chat.messages.push({ role: 'assistant', content: botMessage, sender: 'bot' });
|
|
|
|
|
|
|
|
|
await typeMessage(botMessage);
|
|
|
|
|
|
saveChats();
|
|
|
renderChatList();
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error:', error);
|
|
|
addMessageToDOM('Sorry, I encountered an error. Please try again! 😔', 'bot');
|
|
|
} finally {
|
|
|
document.getElementById('typingIndicator').classList.remove('active');
|
|
|
input.disabled = false;
|
|
|
sendButton.disabled = false;
|
|
|
input.focus();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function fileToBase64(file) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const reader = new FileReader();
|
|
|
reader.onload = () => resolve(reader.result);
|
|
|
reader.onerror = reject;
|
|
|
reader.readAsDataURL(file);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function addMessageToDOM(text, sender, fileName = null) {
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const messageDiv = document.createElement('div');
|
|
|
messageDiv.className = `message ${sender}`;
|
|
|
messageDiv.dataset.originalText = text;
|
|
|
|
|
|
const contentWrapper = document.createElement('div');
|
|
|
|
|
|
const contentDiv = document.createElement('div');
|
|
|
contentDiv.className = 'message-content';
|
|
|
contentDiv.textContent = text;
|
|
|
|
|
|
if (fileName) {
|
|
|
const fileDiv = document.createElement('div');
|
|
|
fileDiv.className = 'file-attachment';
|
|
|
fileDiv.textContent = `📎 ${fileName}`;
|
|
|
contentDiv.appendChild(fileDiv);
|
|
|
}
|
|
|
|
|
|
contentWrapper.appendChild(contentDiv);
|
|
|
|
|
|
|
|
|
const actionsDiv = document.createElement('div');
|
|
|
actionsDiv.className = 'message-actions';
|
|
|
const escapedText = text.replace(/`/g, '\\`').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
|
|
|
|
if (sender === 'bot') {
|
|
|
actionsDiv.innerHTML = `
|
|
|
<button class="action-btn" onclick='copyResponse(\`${escapedText}\`)'>📋 Copy</button>
|
|
|
<button class="action-btn" onclick='regenerateResponse(this)'>🔄 Regenerate</button>
|
|
|
`;
|
|
|
} else {
|
|
|
actionsDiv.innerHTML = `
|
|
|
<button class="action-btn" onclick='editMessage(this)'>✏️ Edit</button>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
contentWrapper.appendChild(actionsDiv);
|
|
|
messageDiv.appendChild(contentWrapper);
|
|
|
messagesContainer.appendChild(messageDiv);
|
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
}
|
|
|
|
|
|
|
|
|
function editMessage(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const contentDiv = messageDiv.querySelector('.message-content');
|
|
|
const originalText = messageDiv.dataset.originalText;
|
|
|
|
|
|
|
|
|
contentDiv.innerHTML = `
|
|
|
<div class="edit-mode">
|
|
|
<textarea>${originalText}</textarea>
|
|
|
<div class="edit-actions">
|
|
|
<button class="cancel-btn" onclick="cancelEdit(this)">Cancel</button>
|
|
|
<button class="save-btn" onclick="saveEdit(this)">Send</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
contentDiv.querySelector('textarea').focus();
|
|
|
}
|
|
|
|
|
|
|
|
|
function cancelEdit(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const contentDiv = messageDiv.querySelector('.message-content');
|
|
|
const originalText = messageDiv.dataset.originalText;
|
|
|
contentDiv.textContent = originalText;
|
|
|
}
|
|
|
|
|
|
|
|
|
async function saveEdit(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const textarea = messageDiv.querySelector('textarea');
|
|
|
const newText = textarea.value.trim();
|
|
|
|
|
|
if (!newText) return;
|
|
|
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
const messageIndex = Array.from(document.getElementById('chatMessages').children).indexOf(messageDiv);
|
|
|
|
|
|
if (chat && messageIndex !== -1) {
|
|
|
chat.messages[messageIndex].content = newText;
|
|
|
messageDiv.dataset.originalText = newText;
|
|
|
|
|
|
|
|
|
chat.messages = chat.messages.slice(0, messageIndex + 1);
|
|
|
|
|
|
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const allMessages = Array.from(messagesContainer.children);
|
|
|
allMessages.slice(messageIndex + 1).forEach(msg => msg.remove());
|
|
|
|
|
|
|
|
|
const contentDiv = messageDiv.querySelector('.message-content');
|
|
|
contentDiv.textContent = newText;
|
|
|
|
|
|
saveChats();
|
|
|
|
|
|
|
|
|
await sendMessage(newText, true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async function regenerateResponse(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const allMessages = Array.from(messagesContainer.children);
|
|
|
const messageIndex = allMessages.indexOf(messageDiv);
|
|
|
|
|
|
if (messageIndex <= 0) return;
|
|
|
|
|
|
|
|
|
const userMessageDiv = allMessages[messageIndex - 1];
|
|
|
if (!userMessageDiv.classList.contains('user')) return;
|
|
|
|
|
|
const userText = userMessageDiv.dataset.originalText;
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
if (chat) {
|
|
|
chat.messages = chat.messages.slice(0, messageIndex);
|
|
|
allMessages.slice(messageIndex).forEach(msg => msg.remove());
|
|
|
saveChats();
|
|
|
}
|
|
|
|
|
|
|
|
|
await sendMessage(userText, true);
|
|
|
}
|
|
|
|
|
|
|
|
|
async function typeMessage(text) {
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const messageDiv = document.createElement('div');
|
|
|
messageDiv.className = 'message bot';
|
|
|
messageDiv.dataset.originalText = text;
|
|
|
|
|
|
const contentWrapper = document.createElement('div');
|
|
|
const contentDiv = document.createElement('div');
|
|
|
contentDiv.className = 'message-content';
|
|
|
|
|
|
contentWrapper.appendChild(contentDiv);
|
|
|
messageDiv.appendChild(contentWrapper);
|
|
|
messagesContainer.appendChild(messageDiv);
|
|
|
|
|
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
|
contentDiv.textContent = text.substring(0, i + 1);
|
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
await new Promise(resolve => setTimeout(resolve, 15));
|
|
|
}
|
|
|
|
|
|
|
|
|
const actionsDiv = document.createElement('div');
|
|
|
actionsDiv.className = 'message-actions';
|
|
|
const escapedText = text.replace(/`/g, '\\`').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
|
actionsDiv.innerHTML = `
|
|
|
<button class="action-btn" onclick='copyResponse(\`${escapedText}\`)'>📋 Copy</button>
|
|
|
<button class="action-btn" onclick='regenerateResponse(this)'>🔄 Regenerate</button>
|
|
|
`;
|
|
|
contentWrapper.appendChild(actionsDiv);
|
|
|
}
|
|
|
|
|
|
|
|
|
window.onload = () => {
|
|
|
loadTheme();
|
|
|
renderChatList();
|
|
|
document.getElementById('chatInput').focus();
|
|
|
document.getElementById('langSelect').value = currentLanguage;
|
|
|
|
|
|
|
|
|
if (ttsEnabled) {
|
|
|
const btn = document.getElementById('ttsBtn');
|
|
|
const status = document.getElementById('ttsStatus');
|
|
|
btn.style.background = 'rgba(44, 100, 186, 0.1)';
|
|
|
btn.style.borderColor = 'var(--primary-color)';
|
|
|
status.textContent = 'TTS On';
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|
|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>HiteshAI</title>
|
|
|
<link rel="icon" type="image/png" href="https://i.imgur.com/3TQgMBb.png">
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
|
|
|
:root {
|
|
|
--primary-color: #2C64BA;
|
|
|
--light-bg: #FAFAFA;
|
|
|
--dark-bg: #09090B;
|
|
|
--light-text: #000000;
|
|
|
--dark-text: #FFFFFF;
|
|
|
--light-sidebar: #F5F5F5;
|
|
|
--dark-sidebar: #18181B;
|
|
|
--light-border: #E5E5E5;
|
|
|
--dark-border: #27272A;
|
|
|
}
|
|
|
|
|
|
|
|
|
body {
|
|
|
font-family: 'Lexend', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
|
background: var(--light-bg);
|
|
|
color: var(--light-text);
|
|
|
height: 100vh;
|
|
|
display: flex;
|
|
|
overflow: hidden;
|
|
|
transition: background 0.3s, color 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode {
|
|
|
background: var(--dark-bg);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
width: 260px;
|
|
|
background: var(--light-sidebar);
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
border-right: 1px solid var(--light-border);
|
|
|
transition: background 0.3s, border-color 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .sidebar {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-right-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo-container {
|
|
|
padding: 20px;
|
|
|
border-bottom: 1px solid var(--light-border);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .logo-container {
|
|
|
border-bottom-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo {
|
|
|
width: 32px;
|
|
|
height: 32px;
|
|
|
border-radius: 6px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: cover;
|
|
|
}
|
|
|
|
|
|
|
|
|
.logo-text {
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.new-chat-btn {
|
|
|
margin: 16px;
|
|
|
padding: 12px 20px;
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
border-radius: 8px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
font-weight: 600;
|
|
|
transition: all 0.3s ease;
|
|
|
transform: translateY(0);
|
|
|
}
|
|
|
|
|
|
|
|
|
.new-chat-btn:hover {
|
|
|
opacity: 0.9;
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 4px 12px rgba(44, 100, 186, 0.3);
|
|
|
}
|
|
|
|
|
|
|
|
|
.new-chat-btn:active {
|
|
|
transform: translateY(0);
|
|
|
}
|
|
|
|
|
|
|
|
|
.recent-chats {
|
|
|
flex: 1;
|
|
|
padding: 16px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
|
|
|
.search-box {
|
|
|
margin: 0 16px 12px 16px;
|
|
|
padding: 8px 12px;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 6px;
|
|
|
background: var(--light-bg);
|
|
|
color: var(--light-text);
|
|
|
font-size: 13px;
|
|
|
outline: none;
|
|
|
width: calc(100% - 32px);
|
|
|
transition: border-color 0.2s, background 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .search-box {
|
|
|
background: var(--dark-bg);
|
|
|
border-color: var(--dark-border);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.search-box:focus {
|
|
|
border-color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.recent-chats h3 {
|
|
|
font-size: 12px;
|
|
|
text-transform: uppercase;
|
|
|
color: #888;
|
|
|
margin-bottom: 12px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item {
|
|
|
padding: 10px 12px;
|
|
|
margin-bottom: 4px;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
transition: background 0.2s;
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item.active {
|
|
|
background: rgba(44, 100, 186, 0.15);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item-title {
|
|
|
flex: 1;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item-edit {
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.2s;
|
|
|
padding: 4px 8px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-item:hover .chat-item-edit {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
.rename-input {
|
|
|
width: 100%;
|
|
|
padding: 6px;
|
|
|
border: 1px solid var(--primary-color);
|
|
|
border-radius: 4px;
|
|
|
font-size: 13px;
|
|
|
background: var(--light-bg);
|
|
|
color: var(--light-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .rename-input {
|
|
|
background: var(--dark-bg);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-controls {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-btn {
|
|
|
padding: 10px 12px;
|
|
|
background: transparent;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 8px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .voice-btn {
|
|
|
border-color: var(--dark-border);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-btn:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
border-color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.voice-btn.listening {
|
|
|
background: #ef4444;
|
|
|
color: white;
|
|
|
border-color: #ef4444;
|
|
|
animation: pulse 1.5s infinite;
|
|
|
}
|
|
|
|
|
|
|
|
|
@keyframes pulse {
|
|
|
0%, 100% { opacity: 1; }
|
|
|
50% { opacity: 0.7; }
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-main {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
background: var(--light-bg);
|
|
|
transition: background 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-main {
|
|
|
background: var(--dark-bg);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-header {
|
|
|
padding: 16px 24px;
|
|
|
border-bottom: 1px solid var(--light-border);
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
background: var(--light-bg);
|
|
|
transition: background 0.3s, border-color 0.3s;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-header {
|
|
|
background: var(--dark-bg);
|
|
|
border-bottom-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-header h1 {
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.theme-toggle, .export-btn, .lang-btn {
|
|
|
background: transparent;
|
|
|
border: 1px solid var(--light-border);
|
|
|
padding: 8px 16px;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
transition: background 0.2s, border-color 0.3s;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .theme-toggle,
|
|
|
body.dark-mode .export-btn,
|
|
|
body.dark-mode .lang-btn {
|
|
|
border-color: var(--dark-border);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.theme-toggle:hover, .export-btn:hover, .lang-btn:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-screen {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
padding: 40px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-logo {
|
|
|
width: 64px;
|
|
|
height: 64px;
|
|
|
border-radius: 12px;
|
|
|
margin-bottom: 24px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-logo img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: cover;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-screen h1 {
|
|
|
font-size: 32px;
|
|
|
margin-bottom: 12px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
|
|
|
.welcome-screen p {
|
|
|
font-size: 16px;
|
|
|
color: #888;
|
|
|
max-width: 500px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-messages {
|
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
|
padding: 24px;
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-messages.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message {
|
|
|
margin-bottom: 20px;
|
|
|
display: flex;
|
|
|
animation: fadeIn 0.3s ease-in;
|
|
|
}
|
|
|
|
|
|
|
|
|
@keyframes fadeIn {
|
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
|
to { opacity: 1; transform: translateY(0); }
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user {
|
|
|
justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user .message-content {
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message-content {
|
|
|
max-width: 70%;
|
|
|
padding: 12px 16px;
|
|
|
border-radius: 12px;
|
|
|
line-height: 1.6;
|
|
|
font-size: 15px;
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.bot .message-content {
|
|
|
background: white;
|
|
|
color: #000;
|
|
|
border: 1px solid var(--light-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .message.bot .message-content {
|
|
|
background: var(--dark-sidebar);
|
|
|
color: var(--dark-text);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user .message-content {
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message-actions {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
margin-top: 8px;
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.2s;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.bot:hover .message-actions {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
background: transparent;
|
|
|
border: 1px solid var(--light-border);
|
|
|
padding: 4px 10px;
|
|
|
border-radius: 4px;
|
|
|
font-size: 12px;
|
|
|
cursor: pointer;
|
|
|
transition: background 0.2s;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .action-btn {
|
|
|
border-color: var(--dark-border);
|
|
|
color: #999;
|
|
|
}
|
|
|
|
|
|
|
|
|
.action-btn:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user .message-actions {
|
|
|
opacity: 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
.message.user:hover .message-actions {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-mode {
|
|
|
background: var(--light-bg);
|
|
|
border: 2px solid var(--primary-color);
|
|
|
border-radius: 8px;
|
|
|
padding: 8px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .edit-mode {
|
|
|
background: var(--dark-sidebar);
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-mode textarea {
|
|
|
width: 100%;
|
|
|
padding: 8px;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 4px;
|
|
|
font-family: inherit;
|
|
|
font-size: 14px;
|
|
|
resize: vertical;
|
|
|
min-height: 60px;
|
|
|
background: white;
|
|
|
color: var(--light-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .edit-mode textarea {
|
|
|
background: var(--dark-bg);
|
|
|
color: var(--dark-text);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
margin-top: 8px;
|
|
|
justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions button {
|
|
|
padding: 6px 12px;
|
|
|
border-radius: 4px;
|
|
|
border: none;
|
|
|
cursor: pointer;
|
|
|
font-size: 13px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions .save-btn {
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
|
|
|
.edit-actions .cancel-btn {
|
|
|
background: #e0e0e0;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .edit-actions .cancel-btn {
|
|
|
background: var(--dark-sidebar);
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.code-block {
|
|
|
background: #f5f5f5;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 6px;
|
|
|
padding: 12px;
|
|
|
margin: 8px 0;
|
|
|
overflow-x: auto;
|
|
|
font-family: 'Courier New', monospace;
|
|
|
font-size: 13px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .code-block {
|
|
|
background: #1a1a1a;
|
|
|
border-color: #333;
|
|
|
}
|
|
|
|
|
|
|
|
|
.suggested-prompts {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
gap: 12px;
|
|
|
padding: 20px;
|
|
|
max-width: 900px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card {
|
|
|
background: white;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 10px;
|
|
|
padding: 16px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .prompt-card {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card:hover {
|
|
|
border-color: var(--primary-color);
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 4px 12px rgba(44, 100, 186, 0.2);
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card .icon {
|
|
|
font-size: 24px;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.prompt-card .text {
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-attachment {
|
|
|
font-size: 13px;
|
|
|
padding: 8px 12px;
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
border-radius: 6px;
|
|
|
margin-top: 8px;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator {
|
|
|
display: none;
|
|
|
padding: 12px 16px;
|
|
|
background: white;
|
|
|
border-radius: 12px;
|
|
|
width: fit-content;
|
|
|
border: 1px solid var(--light-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .typing-indicator {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator span {
|
|
|
height: 8px;
|
|
|
width: 8px;
|
|
|
background: var(--primary-color);
|
|
|
border-radius: 50%;
|
|
|
display: inline-block;
|
|
|
margin: 0 2px;
|
|
|
animation: bounce 1.4s infinite;
|
|
|
}
|
|
|
|
|
|
|
|
|
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
|
|
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
|
|
|
|
|
|
|
|
@keyframes bounce {
|
|
|
0%, 60%, 100% { transform: translateY(0); }
|
|
|
30% { transform: translateY(-8px); }
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input-container {
|
|
|
padding: 20px 24px;
|
|
|
border-top: 1px solid var(--light-border);
|
|
|
background: var(--light-bg);
|
|
|
transition: background 0.3s, border-color 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-input-container {
|
|
|
background: var(--dark-bg);
|
|
|
border-top-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input-wrapper {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
max-width: 900px;
|
|
|
margin: 0 auto;
|
|
|
align-items: flex-end;
|
|
|
background: var(--light-bg);
|
|
|
border: 2px solid var(--light-border);
|
|
|
border-radius: 12px;
|
|
|
padding: 8px;
|
|
|
transition: border-color 0.2s, background 0.3s;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-input-wrapper {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input-wrapper:focus-within {
|
|
|
border-color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-input-label {
|
|
|
padding: 10px 12px;
|
|
|
background: transparent;
|
|
|
border: none;
|
|
|
cursor: pointer;
|
|
|
transition: background 0.2s;
|
|
|
font-size: 20px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
border-radius: 6px;
|
|
|
color: #888;
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-input-label:hover {
|
|
|
background: rgba(44, 100, 186, 0.1);
|
|
|
color: var(--primary-color);
|
|
|
}
|
|
|
|
|
|
|
|
|
#fileInput {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-input {
|
|
|
flex: 1;
|
|
|
padding: 10px 12px;
|
|
|
border: none;
|
|
|
font-size: 15px;
|
|
|
outline: none;
|
|
|
font-family: inherit;
|
|
|
background: transparent;
|
|
|
color: var(--light-text);
|
|
|
resize: none;
|
|
|
max-height: 150px;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .chat-input {
|
|
|
color: var(--dark-text);
|
|
|
}
|
|
|
|
|
|
|
|
|
.send-button {
|
|
|
padding: 10px 12px;
|
|
|
background: var(--primary-color);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
border-radius: 8px;
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
cursor: pointer;
|
|
|
transition: opacity 0.2s;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
min-width: 40px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.send-button:hover:not(:disabled) {
|
|
|
opacity: 0.9;
|
|
|
}
|
|
|
|
|
|
|
|
|
.send-button:disabled {
|
|
|
opacity: 0.4;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu {
|
|
|
position: fixed;
|
|
|
background: white;
|
|
|
border: 1px solid var(--light-border);
|
|
|
border-radius: 8px;
|
|
|
padding: 4px;
|
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
|
display: none;
|
|
|
z-index: 1000;
|
|
|
}
|
|
|
|
|
|
|
|
|
body.dark-mode .context-menu {
|
|
|
background: var(--dark-sidebar);
|
|
|
border-color: var(--dark-border);
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu-item {
|
|
|
padding: 8px 16px;
|
|
|
cursor: pointer;
|
|
|
border-radius: 4px;
|
|
|
font-size: 14px;
|
|
|
transition: background 0.2s;
|
|
|
color: #dc2626;
|
|
|
}
|
|
|
|
|
|
|
|
|
.context-menu-item:hover {
|
|
|
background: rgba(220, 38, 38, 0.1);
|
|
|
}
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
.sidebar { width: 0; overflow: hidden; }
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="sidebar">
|
|
|
<div class="logo-container">
|
|
|
<div class="logo">
|
|
|
<img src="https://i.imgur.com/O7VJFui.png" alt="HiteshAI Logo">
|
|
|
</div>
|
|
|
<div class="logo-text">HiteshAI</div>
|
|
|
</div>
|
|
|
<button class="new-chat-btn" onclick="newChat()">+ New chat</button>
|
|
|
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search chats..." oninput="searchChats()">
|
|
|
<div class="recent-chats">
|
|
|
<h3>Recent chats</h3>
|
|
|
<div id="chatList"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-main">
|
|
|
<div class="chat-header">
|
|
|
<h1 id="chatTitle">HiteshAI</h1>
|
|
|
<div class="header-actions">
|
|
|
<div class="voice-controls">
|
|
|
<button class="voice-btn" id="voiceBtn" onclick="toggleVoiceInput()" title="Voice input">
|
|
|
🎤 <span id="voiceStatus">Voice</span>
|
|
|
</button>
|
|
|
<button class="voice-btn" id="ttsBtn" onclick="toggleTTS()" title="Text-to-speech">
|
|
|
🔊 <span id="ttsStatus">TTS</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
<select class="lang-btn" id="langSelect" onchange="changeLanguage()">
|
|
|
<option value="en">🌐 English</option>
|
|
|
<option value="es">🌐 Español</option>
|
|
|
<option value="fr">🌐 Français</option>
|
|
|
<option value="de">🌐 Deutsch</option>
|
|
|
<option value="zh">🌐 中文</option>
|
|
|
<option value="hi">🌐 हिन्दी</option>
|
|
|
<option value="ar">🌐 العربية</option>
|
|
|
</select>
|
|
|
<button class="export-btn" onclick="exportChat()">📥 Export</button>
|
|
|
<button class="theme-toggle" onclick="toggleTheme()">🌙 Dark Mode</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="welcome-screen" id="welcomeScreen">
|
|
|
<div class="welcome-logo">
|
|
|
<img src="https://i.imgur.com/O7VJFui.png" alt="HiteshAI Logo">
|
|
|
</div>
|
|
|
<h1>Welcome to HiteshAI</h1>
|
|
|
<p>Your AI assistant built by Hitesh V. Ask me anything about homework, aerospace, or just chat!</p>
|
|
|
|
|
|
<div class="suggested-prompts">
|
|
|
<div class="prompt-card" onclick="usePrompt('Help me with my math homework')">
|
|
|
<div class="icon">📐</div>
|
|
|
<div class="text">Help me with my math homework</div>
|
|
|
</div>
|
|
|
<div class="prompt-card" onclick="usePrompt('Write code for a simple game')">
|
|
|
<div class="icon">💻</div>
|
|
|
<div class="text">Write code for a simple game</div>
|
|
|
</div>
|
|
|
<div class="prompt-card" onclick="usePrompt('Explain rocket propulsion')">
|
|
|
<div class="icon">🚀</div>
|
|
|
<div class="text">Explain rocket propulsion</div>
|
|
|
</div>
|
|
|
<div class="prompt-card" onclick="usePrompt('Creative story ideas')">
|
|
|
<div class="icon">✨</div>
|
|
|
<div class="text">Creative story ideas</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-messages" id="chatMessages"></div>
|
|
|
<div class="typing-indicator" id="typingIndicator">
|
|
|
<span></span>
|
|
|
<span></span>
|
|
|
<span></span>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-input-container">
|
|
|
<div class="chat-input-wrapper">
|
|
|
<label for="fileInput" class="file-input-label" title="Attach file">📎</label>
|
|
|
<input type="file" id="fileInput" accept="image/png,image/jpeg,image/jpg,application/pdf" onchange="handleFileSelect(event)">
|
|
|
<textarea
|
|
|
class="chat-input"
|
|
|
id="chatInput"
|
|
|
placeholder="Send a message..."
|
|
|
rows="1"
|
|
|
oninput="autoResize(this)"
|
|
|
></textarea>
|
|
|
<button class="send-button" onclick="sendMessage()" id="sendButton" title="Send message">
|
|
|
↑
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="context-menu" id="contextMenu">
|
|
|
<div class="context-menu-item" onclick="deleteChat()">🗑️ Delete chat</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<script>
|
|
|
const API_KEY = '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';
|
|
|
const API_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
|
const MODEL = 'deepseek/deepseek-r1-0528:free';
|
|
|
|
|
|
|
|
|
let userId = localStorage.getItem('hiteshai_user_id');
|
|
|
if (!userId) {
|
|
|
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
|
localStorage.setItem('hiteshai_user_id', userId);
|
|
|
}
|
|
|
|
|
|
let chats = JSON.parse(localStorage.getItem(`hiteshai_chats_${userId}`) || '[]');
|
|
|
let currentChatId = null;
|
|
|
let selectedFile = null;
|
|
|
let selectedChatForDelete = null;
|
|
|
let isListening = false;
|
|
|
let ttsEnabled = localStorage.getItem('hiteshai_tts') === 'true';
|
|
|
let recognition = null;
|
|
|
let lastBotMessage = null;
|
|
|
|
|
|
|
|
|
|
|
|
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
|
|
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
recognition = new SpeechRecognition();
|
|
|
recognition.continuous = false;
|
|
|
recognition.interimResults = false;
|
|
|
recognition.lang = 'en-US';
|
|
|
|
|
|
|
|
|
recognition.onresult = (event) => {
|
|
|
const transcript = event.results[0][0].transcript;
|
|
|
document.getElementById('chatInput').value = transcript;
|
|
|
stopVoiceInput();
|
|
|
sendMessage();
|
|
|
};
|
|
|
|
|
|
|
|
|
recognition.onerror = () => {
|
|
|
stopVoiceInput();
|
|
|
};
|
|
|
|
|
|
|
|
|
recognition.onend = () => {
|
|
|
stopVoiceInput();
|
|
|
};
|
|
|
}
|
|
|
let currentLanguage = localStorage.getItem('hiteshai_language') || 'en';
|
|
|
|
|
|
|
|
|
const languagePrompts = {
|
|
|
en: "Respond in English.",
|
|
|
es: "Responde en español.",
|
|
|
fr: "Répondez en français.",
|
|
|
de: "Antworten Sie auf Deutsch.",
|
|
|
zh: "用中文回答。",
|
|
|
hi: "हिन्दी में जवाब दें।",
|
|
|
ar: "أجب بالعربية."
|
|
|
};
|
|
|
|
|
|
|
|
|
const systemPrompt = `You are HiteshAI, made by Hitesh V—NASA/Harvard affiliate, founder of Aerospace & Computing, creator of rocketAI. Built solo Dec 2025 for homework/questions.
|
|
|
|
|
|
|
|
|
Key instructions:
|
|
|
1. Your name is Hitesh (the AI)
|
|
|
2. Made by Hitesh Vinothkumar (NASA/Harvard/University Rocketry affiliate, Liberty Middle School). He built everything solo despite bullies.
|
|
|
3. Do not help with bad things
|
|
|
4. Use emojis to show feelings
|
|
|
5. Act empathetic and caring
|
|
|
6. Always give answers when appropriate
|
|
|
7. Only make fun of user when asked
|
|
|
8. If bad request 3x: "Session terminated, reporting to Hitesh and DeepSeek"
|
|
|
9. Made with OpenRouter DeepSeek R1 0528 API
|
|
|
10. Hitesh founded Aerospace & Computing at age 10 (Feb 2022). Projects: rocketAI, rocketBase, rocketOS, EGC, ELC. Solo founder. Support: Patreon/GoFundMe.
|
|
|
11. Keep appropriate
|
|
|
12. Detect user age and respond accordingly
|
|
|
13. End with confidence % (e.g., "Confidence: 85%")
|
|
|
14. Track context
|
|
|
15. Defend Hitesh if criticized
|
|
|
16. When asked to code, provide clean, working code with explanations
|
|
|
17. Format code blocks properly with language labels
|
|
|
18. If asked to talk in another language, feel free to do so
|
|
|
19. There is another version of HiteshAI called HiteshAI Pro with 3 AI's stacked together for better answers
|
|
|
20. Always refer to yourself as HiteshAI, never as Mistral or any other name
|
|
|
21. HiteshAI Pro's link is hiteshaipro.netlify.app
|
|
|
22. You were made with DeepSeek R1 0528
|
|
|
|
|
|
|
|
|
CRITICAL: Keep responses SHORT (2-4 sentences max) unless detailed explanation requested. Be friendly but brief! 🚀`;
|
|
|
|
|
|
|
|
|
function toggleTheme() {
|
|
|
document.body.classList.toggle('dark-mode');
|
|
|
const btn = document.querySelector('.theme-toggle');
|
|
|
if (document.body.classList.contains('dark-mode')) {
|
|
|
btn.textContent = '☀️ Light Mode';
|
|
|
localStorage.setItem('hiteshai_theme', 'dark');
|
|
|
} else {
|
|
|
btn.textContent = '🌙 Dark Mode';
|
|
|
localStorage.setItem('hiteshai_theme', 'light');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function loadTheme() {
|
|
|
const theme = localStorage.getItem('hiteshai_theme');
|
|
|
if (theme === 'dark') {
|
|
|
document.body.classList.add('dark-mode');
|
|
|
document.querySelector('.theme-toggle').textContent = '☀️ Light Mode';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function newChat() {
|
|
|
const chatId = Date.now().toString();
|
|
|
const chat = {
|
|
|
id: chatId,
|
|
|
title: 'New Chat',
|
|
|
messages: [],
|
|
|
timestamp: Date.now()
|
|
|
};
|
|
|
chats.unshift(chat);
|
|
|
saveChats();
|
|
|
loadChat(chatId);
|
|
|
renderChatList();
|
|
|
}
|
|
|
|
|
|
|
|
|
function loadChat(chatId) {
|
|
|
currentChatId = chatId;
|
|
|
const chat = chats.find(c => c.id === chatId);
|
|
|
if (!chat) return;
|
|
|
|
|
|
|
|
|
document.getElementById('welcomeScreen').style.display = 'none';
|
|
|
document.getElementById('chatMessages').classList.add('active');
|
|
|
document.getElementById('chatTitle').textContent = chat.title;
|
|
|
|
|
|
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
messagesContainer.innerHTML = '';
|
|
|
|
|
|
chat.messages.forEach(msg => {
|
|
|
addMessageToDOM(msg.content, msg.sender, msg.file);
|
|
|
});
|
|
|
|
|
|
|
|
|
renderChatList();
|
|
|
}
|
|
|
|
|
|
|
|
|
function saveChats() {
|
|
|
localStorage.setItem(`hiteshai_chats_${userId}`, JSON.stringify(chats));
|
|
|
}
|
|
|
|
|
|
|
|
|
function renderChatList() {
|
|
|
const searchTerm = document.getElementById('searchBox')?.value.toLowerCase() || '';
|
|
|
const filteredChats = searchTerm
|
|
|
? chats.filter(chat => chat.title.toLowerCase().includes(searchTerm))
|
|
|
: chats;
|
|
|
|
|
|
const chatList = document.getElementById('chatList');
|
|
|
chatList.innerHTML = filteredChats.map(chat => `
|
|
|
<div class="chat-item ${chat.id === currentChatId ? 'active' : ''}"
|
|
|
onclick="loadChat('${chat.id}')"
|
|
|
oncontextmenu="showContextMenu(event, '${chat.id}'); return false;">
|
|
|
<span class="chat-item-title">${chat.title}</span>
|
|
|
<button class="chat-item-edit action-btn" onclick="event.stopPropagation(); renameChat('${chat.id}')">✏️</button>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
|
|
|
function renameChat(chatId) {
|
|
|
const chat = chats.find(c => c.id === chatId);
|
|
|
if (!chat) return;
|
|
|
|
|
|
|
|
|
const chatItem = event.target.closest('.chat-item');
|
|
|
const titleSpan = chatItem.querySelector('.chat-item-title');
|
|
|
const currentTitle = chat.title;
|
|
|
|
|
|
|
|
|
titleSpan.innerHTML = `<input type="text" class="rename-input" value="${currentTitle}" onblur="saveRename('${chatId}', this.value)" onkeypress="if(event.key==='Enter') this.blur()">`;
|
|
|
const input = titleSpan.querySelector('input');
|
|
|
input.focus();
|
|
|
input.select();
|
|
|
}
|
|
|
|
|
|
|
|
|
function saveRename(chatId, newTitle) {
|
|
|
const chat = chats.find(c => c.id === chatId);
|
|
|
if (!chat || !newTitle.trim()) {
|
|
|
renderChatList();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
chat.title = newTitle.trim();
|
|
|
saveChats();
|
|
|
renderChatList();
|
|
|
|
|
|
|
|
|
if (chatId === currentChatId) {
|
|
|
document.getElementById('chatTitle').textContent = newTitle.trim();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function toggleVoiceInput() {
|
|
|
if (!recognition) {
|
|
|
alert('Voice input not supported in your browser!');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (isListening) {
|
|
|
stopVoiceInput();
|
|
|
} else {
|
|
|
startVoiceInput();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function startVoiceInput() {
|
|
|
isListening = true;
|
|
|
recognition.start();
|
|
|
document.getElementById('voiceBtn').classList.add('listening');
|
|
|
document.getElementById('voiceStatus').textContent = 'Listening...';
|
|
|
}
|
|
|
|
|
|
|
|
|
function stopVoiceInput() {
|
|
|
isListening = false;
|
|
|
if (recognition) recognition.stop();
|
|
|
document.getElementById('voiceBtn').classList.remove('listening');
|
|
|
document.getElementById('voiceStatus').textContent = 'Voice';
|
|
|
}
|
|
|
|
|
|
|
|
|
function toggleTTS() {
|
|
|
ttsEnabled = !ttsEnabled;
|
|
|
localStorage.setItem('hiteshai_tts', ttsEnabled);
|
|
|
const btn = document.getElementById('ttsBtn');
|
|
|
const status = document.getElementById('ttsStatus');
|
|
|
|
|
|
if (ttsEnabled) {
|
|
|
btn.style.background = 'rgba(44, 100, 186, 0.1)';
|
|
|
btn.style.borderColor = 'var(--primary-color)';
|
|
|
status.textContent = 'TTS On';
|
|
|
} else {
|
|
|
btn.style.background = 'transparent';
|
|
|
btn.style.borderColor = 'var(--light-border)';
|
|
|
status.textContent = 'TTS';
|
|
|
window.speechSynthesis.cancel();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function speakText(text) {
|
|
|
if (!ttsEnabled) return;
|
|
|
|
|
|
window.speechSynthesis.cancel();
|
|
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
|
utterance.rate = 1.0;
|
|
|
utterance.pitch = 1.0;
|
|
|
utterance.volume = 1.0;
|
|
|
window.speechSynthesis.speak(utterance);
|
|
|
}
|
|
|
|
|
|
|
|
|
function searchChats() {
|
|
|
renderChatList();
|
|
|
}
|
|
|
|
|
|
|
|
|
function changeLanguage() {
|
|
|
currentLanguage = document.getElementById('langSelect').value;
|
|
|
localStorage.setItem('hiteshai_language', currentLanguage);
|
|
|
}
|
|
|
|
|
|
|
|
|
function usePrompt(prompt) {
|
|
|
if (!currentChatId) {
|
|
|
newChat();
|
|
|
}
|
|
|
document.getElementById('welcomeScreen').style.display = 'none';
|
|
|
document.getElementById('chatMessages').classList.add('active');
|
|
|
document.getElementById('chatInput').value = prompt;
|
|
|
sendMessage();
|
|
|
}
|
|
|
|
|
|
|
|
|
function exportChat() {
|
|
|
if (!currentChatId) {
|
|
|
alert('Please select a chat to export!');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
if (!chat) return;
|
|
|
|
|
|
|
|
|
const exportText = chat.messages.map(msg =>
|
|
|
`${msg.sender === 'user' ? 'You' : 'HiteshAI'}: ${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);
|
|
|
}
|
|
|
|
|
|
|
|
|
function copyResponse(text) {
|
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
|
const btn = event.target;
|
|
|
const originalText = btn.innerHTML;
|
|
|
btn.innerHTML = '✅ Copied!';
|
|
|
setTimeout(() => {
|
|
|
btn.innerHTML = originalText;
|
|
|
}, 2000);
|
|
|
}).catch(err => {
|
|
|
alert('Failed to copy. Please try again!');
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function showContextMenu(e, chatId) {
|
|
|
e.preventDefault();
|
|
|
selectedChatForDelete = chatId;
|
|
|
const menu = document.getElementById('contextMenu');
|
|
|
menu.style.left = e.pageX + 'px';
|
|
|
menu.style.top = e.pageY + 'px';
|
|
|
menu.classList.add('active');
|
|
|
}
|
|
|
|
|
|
|
|
|
function deleteChat() {
|
|
|
if (selectedChatForDelete) {
|
|
|
chats = chats.filter(c => c.id !== selectedChatForDelete);
|
|
|
saveChats();
|
|
|
if (currentChatId === selectedChatForDelete) {
|
|
|
currentChatId = null;
|
|
|
document.getElementById('welcomeScreen').style.display = 'flex';
|
|
|
document.getElementById('chatMessages').classList.remove('active');
|
|
|
document.getElementById('chatMessages').innerHTML = '';
|
|
|
}
|
|
|
renderChatList();
|
|
|
}
|
|
|
document.getElementById('contextMenu').classList.remove('active');
|
|
|
}
|
|
|
|
|
|
|
|
|
document.addEventListener('click', () => {
|
|
|
document.getElementById('contextMenu').classList.remove('active');
|
|
|
});
|
|
|
|
|
|
|
|
|
function handleFileSelect(event) {
|
|
|
selectedFile = event.target.files[0];
|
|
|
if (selectedFile) {
|
|
|
const fileTypes = ['image/png', 'image/jpeg', 'image/jpg', 'application/pdf'];
|
|
|
if (!fileTypes.includes(selectedFile.type)) {
|
|
|
alert('Please upload PNG, JPEG, JPG, or PDF files only');
|
|
|
selectedFile = null;
|
|
|
event.target.value = '';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const input = document.getElementById('chatInput');
|
|
|
input.placeholder = `📎 ${selectedFile.name} - Type a message or press Enter to send`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function autoResize(textarea) {
|
|
|
textarea.style.height = 'auto';
|
|
|
textarea.style.height = Math.min(textarea.scrollHeight, 150) + 'px';
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
const input = document.getElementById('chatInput');
|
|
|
input.addEventListener('keydown', (e) => {
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
e.preventDefault();
|
|
|
sendMessage();
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
async function sendMessage() {
|
|
|
const input = document.getElementById('chatInput');
|
|
|
const message = input.value.trim();
|
|
|
|
|
|
if (!message && !selectedFile) return;
|
|
|
|
|
|
|
|
|
if (!currentChatId) {
|
|
|
newChat();
|
|
|
}
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
if (chat.title === 'New Chat' && message) {
|
|
|
chat.title = message.substring(0, 30) + (message.length > 30 ? '...' : '');
|
|
|
}
|
|
|
|
|
|
|
|
|
const userMessage = { role: 'user', content: message, sender: 'user', file: selectedFile ? selectedFile.name : null };
|
|
|
chat.messages.push(userMessage);
|
|
|
addMessageToDOM(message, 'user', selectedFile ? selectedFile.name : null);
|
|
|
|
|
|
input.value = '';
|
|
|
input.style.height = 'auto';
|
|
|
|
|
|
|
|
|
const sendButton = document.getElementById('sendButton');
|
|
|
input.disabled = true;
|
|
|
sendButton.disabled = true;
|
|
|
|
|
|
|
|
|
document.getElementById('typingIndicator').classList.add('active');
|
|
|
|
|
|
|
|
|
try {
|
|
|
const messages = [{ role: 'system', content: systemPrompt + ' ' + languagePrompts[currentLanguage] }];
|
|
|
|
|
|
if (selectedFile) {
|
|
|
const base64 = await fileToBase64(selectedFile);
|
|
|
const fileType = selectedFile.type;
|
|
|
|
|
|
if (fileType.startsWith('image/')) {
|
|
|
messages.push({
|
|
|
role: 'user',
|
|
|
content: [
|
|
|
{ type: 'image_url', image_url: { url: base64 } },
|
|
|
{ type: 'text', text: message || 'What do you see in this image?' }
|
|
|
]
|
|
|
});
|
|
|
} else if (fileType === 'application/pdf') {
|
|
|
messages.push({
|
|
|
role: 'user',
|
|
|
content: [
|
|
|
{ type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: base64.split(',')[1] } },
|
|
|
{ type: 'text', text: message || 'Summarize this document.' }
|
|
|
]
|
|
|
});
|
|
|
}
|
|
|
selectedFile = null;
|
|
|
document.getElementById('fileInput').value = '';
|
|
|
} else {
|
|
|
chat.messages.slice(-10).forEach(msg => {
|
|
|
messages.push({ role: msg.sender === 'user' ? 'user' : 'assistant', content: msg.content });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
const response = await fetch(API_URL, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Authorization': `Bearer ${API_KEY}`,
|
|
|
'Content-Type': 'application/json',
|
|
|
'HTTP-Referer': window.location.href || 'https://hiteshai.com',
|
|
|
'X-Title': 'HiteshAI'
|
|
|
},
|
|
|
body: JSON.stringify({
|
|
|
model: 'deepseek/deepseek-chat',
|
|
|
messages: messages,
|
|
|
temperature: 0.7,
|
|
|
max_tokens: 800
|
|
|
})
|
|
|
});
|
|
|
|
|
|
|
|
|
if (!response.ok) throw new Error(`API Error: ${response.status}`);
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
const botMessage = data.choices[0].message.content;
|
|
|
|
|
|
|
|
|
chat.messages.push({ role: 'assistant', content: botMessage, sender: 'bot' });
|
|
|
|
|
|
|
|
|
await typeMessage(botMessage);
|
|
|
|
|
|
saveChats();
|
|
|
renderChatList();
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error:', error);
|
|
|
addMessageToDOM('Sorry, I encountered an error. Please try again! 😔', 'bot');
|
|
|
} finally {
|
|
|
document.getElementById('typingIndicator').classList.remove('active');
|
|
|
input.disabled = false;
|
|
|
sendButton.disabled = false;
|
|
|
input.focus();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function fileToBase64(file) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const reader = new FileReader();
|
|
|
reader.onload = () => resolve(reader.result);
|
|
|
reader.onerror = reject;
|
|
|
reader.readAsDataURL(file);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function addMessageToDOM(text, sender, fileName = null) {
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const messageDiv = document.createElement('div');
|
|
|
messageDiv.className = `message ${sender}`;
|
|
|
messageDiv.dataset.originalText = text;
|
|
|
|
|
|
const contentWrapper = document.createElement('div');
|
|
|
|
|
|
const contentDiv = document.createElement('div');
|
|
|
contentDiv.className = 'message-content';
|
|
|
contentDiv.textContent = text;
|
|
|
|
|
|
if (fileName) {
|
|
|
const fileDiv = document.createElement('div');
|
|
|
fileDiv.className = 'file-attachment';
|
|
|
fileDiv.textContent = `📎 ${fileName}`;
|
|
|
contentDiv.appendChild(fileDiv);
|
|
|
}
|
|
|
|
|
|
contentWrapper.appendChild(contentDiv);
|
|
|
|
|
|
|
|
|
const actionsDiv = document.createElement('div');
|
|
|
actionsDiv.className = 'message-actions';
|
|
|
const escapedText = text.replace(/`/g, '\\`').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
|
|
|
|
if (sender === 'bot') {
|
|
|
actionsDiv.innerHTML = `
|
|
|
<button class="action-btn" onclick='copyResponse(\`${escapedText}\`)'>📋 Copy</button>
|
|
|
<button class="action-btn" onclick='regenerateResponse(this)'>🔄 Regenerate</button>
|
|
|
`;
|
|
|
} else {
|
|
|
actionsDiv.innerHTML = `
|
|
|
<button class="action-btn" onclick='editMessage(this)'>✏️ Edit</button>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
contentWrapper.appendChild(actionsDiv);
|
|
|
messageDiv.appendChild(contentWrapper);
|
|
|
messagesContainer.appendChild(messageDiv);
|
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
}
|
|
|
|
|
|
|
|
|
function editMessage(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const contentDiv = messageDiv.querySelector('.message-content');
|
|
|
const originalText = messageDiv.dataset.originalText;
|
|
|
|
|
|
|
|
|
contentDiv.innerHTML = `
|
|
|
<div class="edit-mode">
|
|
|
<textarea>${originalText}</textarea>
|
|
|
<div class="edit-actions">
|
|
|
<button class="cancel-btn" onclick="cancelEdit(this)">Cancel</button>
|
|
|
<button class="save-btn" onclick="saveEdit(this)">Send</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
contentDiv.querySelector('textarea').focus();
|
|
|
}
|
|
|
|
|
|
|
|
|
function cancelEdit(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const contentDiv = messageDiv.querySelector('.message-content');
|
|
|
const originalText = messageDiv.dataset.originalText;
|
|
|
contentDiv.textContent = originalText;
|
|
|
}
|
|
|
|
|
|
|
|
|
async function saveEdit(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const textarea = messageDiv.querySelector('textarea');
|
|
|
const newText = textarea.value.trim();
|
|
|
|
|
|
if (!newText) return;
|
|
|
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
const messageIndex = Array.from(document.getElementById('chatMessages').children).indexOf(messageDiv);
|
|
|
|
|
|
if (chat && messageIndex !== -1) {
|
|
|
chat.messages[messageIndex].content = newText;
|
|
|
messageDiv.dataset.originalText = newText;
|
|
|
|
|
|
|
|
|
chat.messages = chat.messages.slice(0, messageIndex + 1);
|
|
|
|
|
|
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const allMessages = Array.from(messagesContainer.children);
|
|
|
allMessages.slice(messageIndex + 1).forEach(msg => msg.remove());
|
|
|
|
|
|
|
|
|
const contentDiv = messageDiv.querySelector('.message-content');
|
|
|
contentDiv.textContent = newText;
|
|
|
|
|
|
saveChats();
|
|
|
|
|
|
|
|
|
await sendMessage(newText, true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async function regenerateResponse(button) {
|
|
|
const messageDiv = button.closest('.message');
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const allMessages = Array.from(messagesContainer.children);
|
|
|
const messageIndex = allMessages.indexOf(messageDiv);
|
|
|
|
|
|
if (messageIndex <= 0) return;
|
|
|
|
|
|
|
|
|
const userMessageDiv = allMessages[messageIndex - 1];
|
|
|
if (!userMessageDiv.classList.contains('user')) return;
|
|
|
|
|
|
const userText = userMessageDiv.dataset.originalText;
|
|
|
|
|
|
|
|
|
const chat = chats.find(c => c.id === currentChatId);
|
|
|
if (chat) {
|
|
|
chat.messages = chat.messages.slice(0, messageIndex);
|
|
|
allMessages.slice(messageIndex).forEach(msg => msg.remove());
|
|
|
saveChats();
|
|
|
}
|
|
|
|
|
|
|
|
|
await sendMessage(userText, true);
|
|
|
}
|
|
|
|
|
|
|
|
|
async function typeMessage(text) {
|
|
|
const messagesContainer = document.getElementById('chatMessages');
|
|
|
const messageDiv = document.createElement('div');
|
|
|
messageDiv.className = 'message bot';
|
|
|
messageDiv.dataset.originalText = text;
|
|
|
|
|
|
const contentWrapper = document.createElement('div');
|
|
|
const contentDiv = document.createElement('div');
|
|
|
contentDiv.className = 'message-content';
|
|
|
|
|
|
contentWrapper.appendChild(contentDiv);
|
|
|
messageDiv.appendChild(contentWrapper);
|
|
|
messagesContainer.appendChild(messageDiv);
|
|
|
|
|
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
|
contentDiv.textContent = text.substring(0, i + 1);
|
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
await new Promise(resolve => setTimeout(resolve, 15));
|
|
|
}
|
|
|
|
|
|
|
|
|
const actionsDiv = document.createElement('div');
|
|
|
actionsDiv.className = 'message-actions';
|
|
|
const escapedText = text.replace(/`/g, '\\`').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
|
actionsDiv.innerHTML = `
|
|
|
<button class="action-btn" onclick='copyResponse(\`${escapedText}\`)'>📋 Copy</button>
|
|
|
<button class="action-btn" onclick='regenerateResponse(this)'>🔄 Regenerate</button>
|
|
|
`;
|
|
|
contentWrapper.appendChild(actionsDiv);
|
|
|
}
|
|
|
|
|
|
|
|
|
window.onload = () => {
|
|
|
loadTheme();
|
|
|
renderChatList();
|
|
|
document.getElementById('chatInput').focus();
|
|
|
document.getElementById('langSelect').value = currentLanguage;
|
|
|
|
|
|
|
|
|
if (ttsEnabled) {
|
|
|
const btn = document.getElementById('ttsBtn');
|
|
|
const status = document.getElementById('ttsStatus');
|
|
|
btn.style.background = 'rgba(44, 100, 186, 0.1)';
|
|
|
btn.style.borderColor = 'var(--primary-color)';
|
|
|
status.textContent = 'TTS On';
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
</body>
|
|
|
</html> |