API / index.html
Rox-Turbo's picture
Create index.html
6800ca4 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="description" content="Rox AI - Next-generation conversational AI interface">
<meta name="theme-color" content="#667eea">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Rox AI | Intelligent Conversations</title>
<!-- Preconnect for performance -->
<link rel="preconnect" href="https://Rox-Turbo-API.hf.space">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
/* Design System - Colors */
--primary-50: #eef2ff;
--primary-100: #e0e7ff;
--primary-200: #c7d2fe;
--primary-300: #a5b4fc;
--primary-400: #818cf8;
--primary-500: #667eea;
--primary-600: #5b67d6;
--primary-700: #4f46e5;
--primary-800: #3730a3;
--primary-900: #312e81;
--accent-purple: #764ba2;
--accent-pink: #f093fb;
--accent-blue: #4facfe;
/* Neutral Scale */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* Semantic Colors */
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--info: #3b82f6;
/* Spacing System */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-10: 2.5rem;
--space-12: 3rem;
--space-16: 4rem;
/* Typography */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--shadow-glow: 0 0 20px rgba(102, 126, 234, 0.5);
/* Transitions */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-spring: 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
/* Border Radius */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
--radius-full: 9999px;
/* Z-Index Scale */
--z-dropdown: 100;
--z-sticky: 200;
--z-modal: 300;
--z-tooltip: 400;
--z-toast: 500;
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: var(--gray-900);
--bg-secondary: var(--gray-800);
--bg-tertiary: var(--gray-700);
--text-primary: var(--gray-50);
--text-secondary: var(--gray-300);
--text-tertiary: var(--gray-400);
--border-color: var(--gray-700);
}
}
@media (prefers-color-scheme: light) {
:root {
--bg-primary: #ffffff;
--bg-secondary: var(--gray-50);
--bg-tertiary: var(--gray-100);
--text-primary: var(--gray-900);
--text-secondary: var(--gray-600);
--text-tertiary: var(--gray-400);
--border-color: var(--gray-200);
}
}
/* Reset & Base */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
scroll-behavior: smooth;
}
body {
font-family: var(--font-sans);
background: linear-gradient(135deg, var(--primary-500) 0%, var(--accent-purple) 50%, var(--primary-700) 100%);
background-attachment: fixed;
min-height: 100vh;
color: var(--text-primary);
line-height: 1.6;
overflow-x: hidden;
}
/* Animated Background */
.ambient-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
overflow: hidden;
}
.ambient-bg::before,
.ambient-bg::after {
content: '';
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.4;
animation: float 20s infinite ease-in-out;
}
.ambient-bg::before {
width: 600px;
height: 600px;
background: var(--accent-pink);
top: -200px;
right: -100px;
animation-delay: 0s;
}
.ambient-bg::after {
width: 500px;
height: 500px;
background: var(--accent-blue);
bottom: -150px;
left: -100px;
animation-delay: -10s;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(30px, -30px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
}
/* App Container */
.app-container {
display: grid;
grid-template-columns: 280px 1fr;
grid-template-rows: 1fr;
height: 100vh;
max-width: 1600px;
margin: 0 auto;
background: var(--bg-primary);
box-shadow: var(--shadow-2xl);
overflow: hidden;
}
@media (max-width: 1024px) {
.app-container {
grid-template-columns: 1fr;
}
}
/* Sidebar */
.sidebar {
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: var(--space-6);
gap: var(--space-6);
overflow-y: auto;
}
@media (max-width: 1024px) {
.sidebar {
position: fixed;
left: -100%;
top: 0;
height: 100vh;
width: 280px;
z-index: var(--z-modal);
transition: left var(--transition-slow);
}
.sidebar.open {
left: 0;
}
}
.sidebar-header {
display: flex;
align-items: center;
gap: var(--space-3);
padding-bottom: var(--space-6);
border-bottom: 1px solid var(--border-color);
}
.logo {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary-500), var(--accent-purple));
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: white;
box-shadow: var(--shadow-md);
}
.brand-text h1 {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.025em;
}
.brand-text p {
font-size: 0.75rem;
color: var(--text-tertiary);
font-weight: 500;
}
/* New Chat Button */
.btn-new-chat {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
border: none;
border-radius: var(--radius-lg);
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-base);
box-shadow: var(--shadow-md);
}
.btn-new-chat:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg), var(--shadow-glow);
}
.btn-new-chat:active {
transform: translateY(0);
}
/* Conversation History */
.history-section {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.section-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-tertiary);
padding: 0 var(--space-2);
}
.conversation-list {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.conversation-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--transition-fast);
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 500;
}
.conversation-item:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.conversation-item.active {
background: var(--primary-50);
color: var(--primary-700);
}
.conversation-item svg {
width: 18px;
height: 18px;
flex-shrink: 0;
}
/* Settings Panel */
.settings-panel {
border-top: 1px solid var(--border-color);
padding-top: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.setting-group {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.setting-label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.model-selector {
position: relative;
}
.model-select {
width: 100%;
padding: var(--space-3) var(--space-4);
padding-right: var(--space-10);
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
font-size: 0.875rem;
color: var(--text-primary);
cursor: pointer;
appearance: none;
transition: all var(--transition-fast);
}
.model-select:hover {
border-color: var(--primary-300);
}
.model-select:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px var(--primary-100);
}
.model-selector::after {
content: '▼';
position: absolute;
right: var(--space-4);
top: 50%;
transform: translateY(-50%);
font-size: 0.625rem;
color: var(--text-tertiary);
pointer-events: none;
}
/* Toggle Switch */
.toggle-group {
display: flex;
align-items: center;
justify-content: space-between;
}
.toggle-label {
font-size: 0.875rem;
color: var(--text-secondary);
font-weight: 500;
}
.toggle-switch {
position: relative;
width: 44px;
height: 24px;
background: var(--gray-300);
border-radius: var(--radius-full);
cursor: pointer;
transition: background var(--transition-fast);
}
.toggle-switch.active {
background: var(--primary-500);
}
.toggle-switch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform var(--transition-spring);
box-shadow: var(--shadow-sm);
}
.toggle-switch.active::after {
transform: translateX(20px);
}
/* Main Chat Area */
.main-content {
display: flex;
flex-direction: column;
height: 100vh;
position: relative;
}
/* Mobile Header */
.mobile-header {
display: none;
align-items: center;
justify-content: space-between;
padding: var(--space-4);
background: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
}
@media (max-width: 1024px) {
.mobile-header {
display: flex;
}
}
.menu-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-secondary);
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
color: var(--text-secondary);
transition: all var(--transition-fast);
}
.menu-btn:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
/* Chat Header */
.chat-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-6);
background: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
}
.chat-info {
display: flex;
align-items: center;
gap: var(--space-3);
}
.model-badge {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-1) var(--space-3);
background: var(--primary-50);
color: var(--primary-700);
font-size: 0.75rem;
font-weight: 600;
border-radius: var(--radius-full);
border: 1px solid var(--primary-200);
}
.status-indicator {
width: 8px;
height: 8px;
background: var(--success);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.header-actions {
display: flex;
gap: var(--space-2);
}
.icon-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
cursor: pointer;
color: var(--text-secondary);
transition: all var(--transition-fast);
}
.icon-btn:hover {
background: var(--bg-secondary);
color: var(--text-primary);
border-color: var(--primary-300);
}
/* Messages Container */
.messages-container {
flex: 1;
overflow-y: auto;
padding: var(--space-6);
scroll-behavior: smooth;
}
.messages-container::-webkit-scrollbar {
width: 8px;
}
.messages-container::-webkit-scrollbar-track {
background: transparent;
}
.messages-container::-webkit-scrollbar-thumb {
background: var(--gray-300);
border-radius: var(--radius-full);
}
.messages-container::-webkit-scrollbar-thumb:hover {
background: var(--gray-400);
}
.welcome-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
gap: var(--space-6);
padding: var(--space-8);
}
.welcome-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-500), var(--accent-purple));
border-radius: var(--radius-2xl);
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
box-shadow: var(--shadow-xl);
animation: bounce-in var(--transition-spring);
}
@keyframes bounce-in {
0% { transform: scale(0); opacity: 0; }
50% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
.welcome-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.025em;
}
.welcome-subtitle {
font-size: 1rem;
color: var(--text-secondary);
max-width: 400px;
}
.suggestion-chips {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
justify-content: center;
margin-top: var(--space-4);
}
.suggestion-chip {
padding: var(--space-3) var(--space-5);
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-full);
font-size: 0.875rem;
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.suggestion-chip:hover {
background: var(--primary-50);
border-color: var(--primary-300);
color: var(--primary-700);
transform: translateY(-2px);
}
/* Message Styles */
.message {
display: flex;
gap: var(--space-4);
margin-bottom: var(--space-6);
animation: message-in var(--transition-base);
max-width: 100%;
}
@keyframes message-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
flex-direction: row-reverse;
}
.message-avatar {
width: 36px;
height: 36px;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 1rem;
}
.message.user .message-avatar {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
}
.message.assistant .message-avatar {
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.message-content-wrapper {
display: flex;
flex-direction: column;
gap: var(--space-2);
max-width: calc(100% - 60px);
}
.message.user .message-content-wrapper {
align-items: flex-end;
}
.message-header {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: 0.75rem;
color: var(--text-tertiary);
}
.message-content {
padding: var(--space-4) var(--space-5);
border-radius: var(--radius-xl);
font-size: 0.9375rem;
line-height: 1.7;
word-wrap: break-word;
position: relative;
}
.message.user .message-content {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
border-bottom-right-radius: var(--radius-sm);
box-shadow: var(--shadow-md);
}
.message.assistant .message-content {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-bottom-left-radius: var(--radius-sm);
}
/* Code Blocks */
.code-block {
background: var(--gray-900);
border-radius: var(--radius-lg);
margin: var(--space-4) 0;
overflow: hidden;
}
.code-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3) var(--space-4);
background: var(--gray-800);
border-bottom: 1px solid var(--gray-700);
}
.code-language {
font-size: 0.75rem;
font-weight: 600;
color: var(--gray-400);
text-transform: uppercase;
}
.code-actions {
display: flex;
gap: var(--space-2);
}
.code-btn {
padding: var(--space-1) var(--space-3);
background: var(--gray-700);
border: none;
border-radius: var(--radius-md);
font-size: 0.75rem;
color: var(--gray-300);
cursor: pointer;
transition: all var(--transition-fast);
font-family: var(--font-sans);
}
.code-btn:hover {
background: var(--gray-600);
color: white;
}
.code-content {
padding: var(--space-4);
overflow-x: auto;
font-family: var(--font-mono);
font-size: 0.875rem;
line-height: 1.6;
color: #e5e7eb;
}
.code-content pre {
margin: 0;
}
/* Inline Code */
code {
font-family: var(--font-mono);
font-size: 0.875em;
background: var(--bg-tertiary);
padding: 0.2em 0.4em;
border-radius: var(--radius-md);
color: var(--primary-600);
}
.message.user code {
background: rgba(255, 255, 255, 0.2);
color: white;
}
/* Typing Indicator */
.typing-indicator {
display: flex;
gap: var(--space-4);
margin-bottom: var(--space-6);
opacity: 0;
transition: opacity var(--transition-fast);
}
.typing-indicator.visible {
opacity: 1;
}
.typing-bubbles {
display: flex;
align-items: center;
gap: 4px;
padding: var(--space-4) var(--space-5);
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-xl);
border-bottom-left-radius: var(--radius-sm);
}
.typing-bubble {
width: 8px;
height: 8px;
background: var(--primary-400);
border-radius: 50%;
animation: typing-bounce 1.4s infinite ease-in-out both;
}
.typing-bubble:nth-child(1) { animation-delay: -0.32s; }
.typing-bubble:nth-child(2) { animation-delay: -0.16s; }
@keyframes typing-bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}
/* Input Area */
.input-container {
padding: var(--space-4) var(--space-6);
background: var(--bg-primary);
border-top: 1px solid var(--border-color);
}
.input-wrapper {
display: flex;
gap: var(--space-3);
align-items: flex-end;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-xl);
padding: var(--space-3);
transition: all var(--transition-fast);
}
.input-wrapper:focus-within {
border-color: var(--primary-300);
box-shadow: 0 0 0 3px var(--primary-100);
background: var(--bg-primary);
}
.input-actions {
display: flex;
gap: var(--space-2);
padding-bottom: var(--space-1);
}
.input-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
color: var(--text-tertiary);
transition: all var(--transition-fast);
flex-shrink: 0;
}
.input-btn:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.message-input {
flex: 1;
background: transparent;
border: none;
resize: none;
font-family: var(--font-sans);
font-size: 0.9375rem;
line-height: 1.6;
color: var(--text-primary);
max-height: 200px;
min-height: 24px;
padding: var(--space-1) 0;
}
.message-input:focus {
outline: none;
}
.message-input::placeholder {
color: var(--text-tertiary);
}
.send-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
color: white;
transition: all var(--transition-fast);
flex-shrink: 0;
box-shadow: var(--shadow-md);
}
.send-btn:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: var(--shadow-lg);
}
.send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.input-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: var(--space-2);
padding: 0 var(--space-2);
font-size: 0.75rem;
color: var(--text-tertiary);
}
.shortcut-hint {
display: flex;
gap: var(--space-3);
}
kbd {
font-family: var(--font-mono);
padding: 0.125rem 0.375rem;
background: var(--bg-tertiary);
border-radius: var(--radius-md);
font-size: 0.75rem;
border: 1px solid var(--border-color);
}
/* Toast Notifications */
.toast-container {
position: fixed;
bottom: var(--space-6);
right: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-3);
z-index: var(--z-toast);
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-4) var(--space-5);
background: var(--gray-800);
color: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-xl);
font-size: 0.875rem;
font-weight: 500;
animation: toast-in var(--transition-spring);
pointer-events: auto;
}
@keyframes toast-in {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.toast.success { border-left: 4px solid var(--success); }
.toast.error { border-left: 4px solid var(--error); }
.toast.info { border-left: 4px solid var(--info); }
/* Overlay for mobile sidebar */
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: calc(var(--z-modal) - 1);
backdrop-filter: blur(4px);
}
@media (max-width: 1024px) {
.sidebar-overlay.visible {
display: block;
}
}
/* Empty State */
.empty-state {
text-align: center;
padding: var(--space-12);
color: var(--text-tertiary);
}
/* Loading State */
.skeleton {
background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-secondary) 50%, var(--bg-tertiary) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: var(--radius-md);
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Responsive Adjustments */
@media (max-width: 640px) {
.messages-container {
padding: var(--space-4);
}
.message-content {
padding: var(--space-3) var(--space-4);
font-size: 0.875rem;
}
.welcome-title {
font-size: 1.5rem;
}
.input-container {
padding: var(--space-3) var(--space-4);
}
}
/* Print Styles */
@media print {
.sidebar, .input-container, .chat-header {
display: none;
}
.messages-container {
overflow: visible;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Focus Visible */
:focus-visible {
outline: 2px solid var(--primary-500);
outline-offset: 2px;
}
/* Selection */
::selection {
background: var(--primary-200);
color: var(--primary-900);
}
</style>
</head>
<body>
<div class="ambient-bg"></div>
<div class="app-container">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">🚀</div>
<div class="brand-text">
<h1>Rox AI</h1>
<p>Next-Gen Intelligence</p>
</div>
</div>
<button class="btn-new-chat" onclick="chatApp.newConversation()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
New Conversation
</button>
<div class="history-section">
<span class="section-title">Recent Conversations</span>
<div class="conversation-list" id="conversationList">
<!-- Populated by JS -->
</div>
</div>
<div class="settings-panel">
<div class="setting-group">
<span class="setting-label">Model</span>
<div class="model-selector">
<select class="model-select" id="modelSelect">
<option value="chat">Rox Core (Balanced)</option>
<option value="turbo">Rox Turbo (Fast)</option>
<option value="coder">Rox Coder (Code)</option>
<option value="turbo45">Rox 4.5 Turbo (Advanced)</option>
<option value="ultra">Rox Ultra (Powerful)</option>
<option value="dyno">Rox Dyno (Dynamic)</option>
<option value="coder7">Rox 7 Coder (Enterprise)</option>
<option value="vision">Rox Vision (Multimodal)</option>
</select>
</div>
</div>
<div class="setting-group">
<div class="toggle-group">
<span class="toggle-label">Stream Responses</span>
<div class="toggle-switch active" id="streamToggle" onclick="chatApp.toggleStreaming()"></div>
</div>
</div>
<div class="setting-group">
<div class="toggle-group">
<span class="toggle-label">Auto-scroll</span>
<div class="toggle-switch active" id="autoScrollToggle" onclick="chatApp.toggleAutoScroll()"></div>
</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Mobile Header -->
<div class="mobile-header">
<button class="menu-btn" onclick="chatApp.toggleSidebar()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div class="chat-info">
<span class="model-badge" id="mobileModelBadge">
<span class="status-indicator"></span>
Rox Core
</span>
</div>
<button class="icon-btn" onclick="chatApp.newConversation()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
<!-- Chat Header -->
<div class="chat-header">
<div class="chat-info">
<span class="model-badge" id="modelBadge">
<span class="status-indicator"></span>
<span id="currentModelName">Rox Core</span>
</span>
</div>
<div class="header-actions">
<button class="icon-btn" onclick="chatApp.exportConversation()" title="Export">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
<button class="icon-btn" onclick="chatApp.clearConversation()" title="Clear">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
</div>
</div>
<!-- Messages -->
<div class="messages-container" id="messagesContainer">
<div class="welcome-screen" id="welcomeScreen">
<div class="welcome-icon">🚀</div>
<h2 class="welcome-title">Welcome to Rox AI</h2>
<p class="welcome-subtitle">Experience next-generation AI conversations. Select a model and start chatting.</p>
<div class="suggestion-chips">
<button class="suggestion-chip" onclick="chatApp.sendSuggestion('Explain quantum computing in simple terms')">
Explain quantum computing
</button>
<button class="suggestion-chip" onclick="chatApp.sendSuggestion('Write a Python function to calculate fibonacci')">
Write Python code
</button>
<button class="suggestion-chip" onclick="chatApp.sendSuggestion('Help me brainstorm ideas for a sci-fi novel')">
Brainstorm ideas
</button>
<button class="suggestion-chip" onclick="chatApp.sendSuggestion('Analyze the implications of AI in healthcare')">
AI analysis
</button>
</div>
</div>
<div id="messagesList" style="display: none;"></div>
</div>
<!-- Input -->
<div class="input-container">
<div class="input-wrapper">
<div class="input-actions">
<button class="input-btn" title="Attach file">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
</svg>
</button>
</div>
<textarea
class="message-input"
id="messageInput"
placeholder="Message Rox AI..."
rows="1"
oninput="chatApp.autoResize(this)"
onkeydown="chatApp.handleKeydown(event)"
></textarea>
<button class="send-btn" id="sendBtn" onclick="chatApp.sendMessage()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</button>
</div>
<div class="input-footer">
<span>Rox AI can make mistakes. Consider checking important information.</span>
<div class="shortcut-hint">
<span><kbd>Shift</kbd> + <kbd>Enter</kbd> for new line</span>
<span><kbd>Enter</kbd> to send</span>
</div>
</div>
</div>
</main>
</div>
<!-- Mobile Sidebar Overlay -->
<div class="sidebar-overlay" id="sidebarOverlay" onclick="chatApp.toggleSidebar()"></div>
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
/**
* Rox AI - Production Grade Chat Application
* Architecture: Modular ES6+ Class-based design with state management
*/
class ChatApplication {
constructor() {
// Configuration
this.config = {
apiBaseUrl: 'https://Rox-Turbo-API.hf.space',
maxRetries: 3,
retryDelay: 1000,
maxHistory: 50,
debounceMs: 150
};
// State Management
this.state = {
currentConversationId: null,
conversations: new Map(),
isStreaming: true,
autoScroll: true,
isProcessing: false,
currentModel: 'chat',
sidebarOpen: false
};
// DOM Elements Cache
this.elements = {
messagesContainer: document.getElementById('messagesContainer'),
messagesList: document.getElementById('messagesList'),
welcomeScreen: document.getElementById('welcomeScreen'),
messageInput: document.getElementById('messageInput'),
sendBtn: document.getElementById('sendBtn'),
modelSelect: document.getElementById('modelSelect'),
streamToggle: document.getElementById('streamToggle'),
autoScrollToggle: document.getElementById('autoScrollToggle'),
conversationList: document.getElementById('conversationList'),
sidebar: document.getElementById('sidebar'),
sidebarOverlay: document.getElementById('sidebarOverlay'),
modelBadge: document.getElementById('modelBadge'),
currentModelName: document.getElementById('currentModelName'),
mobileModelBadge: document.getElementById('mobileModelBadge'),
toastContainer: document.getElementById('toastContainer')
};
// Initialize
this.init();
}
init() {
this.loadConversations();
this.setupEventListeners();
this.renderConversationList();
// Focus input on load
setTimeout(() => this.elements.messageInput.focus(), 100);
}
setupEventListeners() {
// Model selection
this.elements.modelSelect.addEventListener('change', (e) => {
this.state.currentModel = e.target.value;
this.updateModelDisplay();
});
// Window resize
window.addEventListener('resize', this.debounce(() => {
this.scrollToBottom();
}, this.config.debounceMs));
// Before unload - save state
window.addEventListener('beforeunload', () => {
this.saveConversations();
});
}
// ==================== Core Messaging ====================
async sendMessage() {
if (this.state.isProcessing) return;
const input = this.elements.messageInput;
const message = input.value.trim();
if (!message) return;
// Initialize conversation if needed
if (!this.state.currentConversationId) {
this.createNewConversation();
}
// Add user message
this.addMessage('user', message);
// Clear input
input.value = '';
input.style.height = 'auto';
this.updateSendButton();
// Update conversation history
const conversation = this.state.conversations.get(this.state.currentConversationId);
conversation.messages.push({ role: 'user', content: message, timestamp: Date.now() });
conversation.title = this.generateTitle(conversation.messages);
// Show typing indicator
this.showTypingIndicator();
// Process based on streaming preference
try {
if (this.state.isStreaming) {
await this.handleStreamingResponse(conversation);
} else {
await this.handleStandardResponse(conversation);
}
this.saveConversations();
this.renderConversationList();
} catch (error) {
this.handleError(error);
} finally {
this.hideTypingIndicator();
this.state.isProcessing = false;
this.updateSendButton();
}
}
async handleStreamingResponse(conversation) {
const apiUrl = `${this.config.apiBaseUrl}/${this.state.currentModel}`;
let retryCount = 0;
while (retryCount < this.config.maxRetries) {
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream'
},
body: JSON.stringify({
messages: conversation.messages.slice(-this.config.maxHistory),
temperature: 0.7,
top_p: 0.95,
max_tokens: 8192,
stream: true
})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
let messageElement = null;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
if (parsed.content) {
if (!messageElement) {
messageElement = this.addMessage('assistant', '');
}
fullContent += parsed.content;
this.updateMessageContent(messageElement, fullContent);
if (this.state.autoScroll) {
this.scrollToBottom();
}
}
} catch (e) {
// Ignore parse errors in stream
}
}
}
}
if (fullContent) {
conversation.messages.push({
role: 'assistant',
content: fullContent,
timestamp: Date.now()
});
}
return;
} catch (error) {
retryCount++;
if (retryCount >= this.config.maxRetries) throw error;
await this.delay(this.config.retryDelay * retryCount);
}
}
}
async handleStandardResponse(conversation) {
const apiUrl = `${this.config.apiBaseUrl}/${this.state.currentModel}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: conversation.messages.slice(-this.config.maxHistory),
temperature: 0.7,
top_p: 0.95,
max_tokens: 8192,
stream: false
})
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.detail || `HTTP ${response.status}`);
}
const data = await response.json();
if (data.content) {
this.addMessage('assistant', data.content);
conversation.messages.push({
role: 'assistant',
content: data.content,
timestamp: Date.now()
});
}
}
// ==================== UI Management ====================
addMessage(role, content) {
// Hide welcome screen, show messages
this.elements.welcomeScreen.style.display = 'none';
this.elements.messagesList.style.display = 'block';
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = role === 'user' ? '👤' : '🤖';
const wrapper = document.createElement('div');
wrapper.className = 'message-content-wrapper';
const header = document.createElement('div');
header.className = 'message-header';
header.textContent = role === 'user' ? 'You' : 'Rox AI';
const time = document.createElement('span');
time.textContent = '• ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
header.appendChild(time);
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
if (role === 'assistant') {
contentDiv.innerHTML = this.formatContent(content);
} else {
contentDiv.textContent = content;
}
wrapper.appendChild(header);
wrapper.appendChild(contentDiv);
messageDiv.appendChild(avatar);
messageDiv.appendChild(wrapper);
this.elements.messagesList.appendChild(messageDiv);
if (this.state.autoScroll) {
this.scrollToBottom();
}
return contentDiv;
}
updateMessageContent(element, content) {
element.innerHTML = this.formatContent(content);
}
formatContent(content) {
// Simple markdown-like formatting
let formatted = content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// Code blocks
formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
const language = lang || 'text';
return `
<div class="code-block">
<div class="code-header">
<span class="code-language">${language}</span>
<div class="code-actions">
<button class="code-btn" onclick="chatApp.copyCode(this)">Copy</button>
</div>
</div>
<div class="code-content"><pre>${code.trim()}</pre></div>
</div>
`;
});
// Inline code
formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');
// Bold
formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Italic
formatted = formatted.replace(/\*(.*?)\*/g, '<em>$1</em>');
// Line breaks
formatted = formatted.replace(/\n/g, '<br>');
return formatted;
}
showTypingIndicator() {
const indicator = document.createElement('div');
indicator.className = 'typing-indicator visible';
indicator.id = 'typingIndicator';
indicator.innerHTML = `
<div class="message-avatar">🤖</div>
<div class="typing-bubbles">
<div class="typing-bubble"></div>
<div class="typing-bubble"></div>
<div class="typing-bubble"></div>
</div>
`;
this.elements.messagesList.appendChild(indicator);
this.scrollToBottom();
}
hideTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) indicator.remove();
}
// ==================== Conversation Management ====================
createNewConversation() {
const id = 'conv_' + Date.now();
const conversation = {
id,
title: 'New Conversation',
messages: [],
model: this.state.currentModel,
createdAt: Date.now(),
updatedAt: Date.now()
};
this.state.conversations.set(id, conversation);
this.state.currentConversationId = id;
this.renderConversationList();
return conversation;
}
newConversation() {
this.state.currentConversationId = null;
this.elements.messagesList.innerHTML = '';
this.elements.messagesList.style.display = 'none';
this.elements.welcomeScreen.style.display = 'flex';
this.elements.messageInput.value = '';
this.elements.messageInput.style.height = 'auto';
this.elements.messageInput.focus();
if (window.innerWidth <= 1024) {
this.toggleSidebar();
}
}
loadConversation(id) {
const conversation = this.state.conversations.get(id);
if (!conversation) return;
this.state.currentConversationId = id;
this.state.currentModel = conversation.model || 'chat';
this.elements.modelSelect.value = this.state.currentModel;
this.updateModelDisplay();
// Render messages
this.elements.messagesList.innerHTML = '';
this.elements.welcomeScreen.style.display = 'none';
this.elements.messagesList.style.display = 'block';
conversation.messages.forEach(msg => {
if (msg.role === 'user') {
this.addMessage('user', msg.content);
} else {
const element = this.addMessage('assistant', '');
this.updateMessageContent(element, msg.content);
}
});
this.scrollToBottom();
if (window.innerWidth <= 1024) {
this.toggleSidebar();
}
}
clearConversation() {
if (!this.state.currentConversationId) return;
if (confirm('Clear all messages in this conversation?')) {
const conversation = this.state.conversations.get(this.state.currentConversationId);
conversation.messages = [];
this.elements.messagesList.innerHTML = '';
this.saveConversations();
}
}
exportConversation() {
if (!this.state.currentConversationId) {
this.showToast('No conversation to export', 'error');
return;
}
const conversation = this.state.conversations.get(this.state.currentConversationId);
const exportData = {
title: conversation.title,
model: conversation.model,
exportedAt: new Date().toISOString(),
messages: conversation.messages
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `rox-ai-conversation-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
this.showToast('Conversation exported', 'success');
}
// ==================== UI Helpers ====================
toggleSidebar() {
this.state.sidebarOpen = !this.state.sidebarOpen;
this.elements.sidebar.classList.toggle('open', this.state.sidebarOpen);
this.elements.sidebarOverlay.classList.toggle('visible', this.state.sidebarOpen);
}
toggleStreaming() {
this.state.isStreaming = !this.state.isStreaming;
this.elements.streamToggle.classList.toggle('active', this.state.isStreaming);
this.showToast(this.state.isStreaming ? 'Streaming enabled' : 'Streaming disabled', 'info');
}
toggleAutoScroll() {
this.state.autoScroll = !this.state.autoScroll;
this.elements.autoScrollToggle.classList.toggle('active', this.state.autoScroll);
}
updateModelDisplay() {
const modelNames = {
'chat': 'Rox Core',
'turbo': 'Rox Turbo',
'coder': 'Rox Coder',
'turbo45': 'Rox 4.5 Turbo',
'ultra': 'Rox Ultra',
'dyno': 'Rox Dyno',
'coder7': 'Rox 7 Coder',
'vision': 'Rox Vision'
};
const name = modelNames[this.state.currentModel] || 'Rox Core';
this.elements.currentModelName.textContent = name;
this.elements.mobileModelBadge.innerHTML = `<span class="status-indicator"></span>${name}`;
}
updateSendButton() {
this.elements.sendBtn.disabled = this.state.isProcessing || !this.elements.messageInput.value.trim();
}
autoResize(textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
this.updateSendButton();
}
handleKeydown(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.sendMessage();
}
}
sendSuggestion(text) {
this.elements.messageInput.value = text;
this.autoResize(this.elements.messageInput);
this.sendMessage();
}
scrollToBottom() {
this.elements.messagesContainer.scrollTop = this.elements.messagesContainer.scrollHeight;
}
// ==================== Data Persistence ====================
saveConversations() {
const data = Array.from(this.state.conversations.entries());
localStorage.setItem('rox_ai_conversations', JSON.stringify(data));
localStorage.setItem('rox_ai_current', this.state.currentConversationId || '');
}
loadConversations() {
try {
const saved = localStorage.getItem('rox_ai_conversations');
const current = localStorage.getItem('rox_ai_current');
if (saved) {
const parsed = JSON.parse(saved);
this.state.conversations = new Map(parsed);
}
if (current && this.state.conversations.has(current)) {
this.loadConversation(current);
}
} catch (e) {
console.error('Failed to load conversations:', e);
}
}
renderConversationList() {
const list = this.elements.conversationList;
list.innerHTML = '';
const sorted = Array.from(this.state.conversations.values())
.sort((a, b) => b.updatedAt - a.updatedAt)
.slice(0, 10); // Show last 10
sorted.forEach(conv => {
const item = document.createElement('div');
item.className = `conversation-item ${conv.id === this.state.currentConversationId ? 'active' : ''}`;
item.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${conv.title}</span>
`;
item.onclick = () => this.loadConversation(conv.id);
list.appendChild(item);
});
}
generateTitle(messages) {
if (messages.length === 0) return 'New Conversation';
const firstUser = messages.find(m => m.role === 'user');
if (!firstUser) return 'New Conversation';
return firstUser.content.slice(0, 30) + (firstUser.content.length > 30 ? '...' : '');
}
// ==================== Utilities ====================
copyCode(button) {
const code = button.closest('.code-block').querySelector('pre').textContent;
navigator.clipboard.writeText(code).then(() => {
button.textContent = 'Copied!';
setTimeout(() => button.textContent = 'Copy', 2000);
});
}
showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = {
success: '✓',
error: '✕',
info: 'ℹ'
};
toast.innerHTML = `<span>${icons[type]}</span> ${message}`;
this.elements.toastContainer.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'toast-in 0.3s reverse forwards';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
handleError(error) {
console.error('Chat error:', error);
this.showToast(error.message || 'Failed to send message', 'error');
this.addMessage('system', `Error: ${error.message}. Please try again.`);
}
debounce(fn, ms) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), ms);
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Initialize Application
const chatApp = new ChatApplication();
</script>
</body>
</html>