Spaces:
Sleeping
Sleeping
Update public/index.html
Browse files- public/index.html +80 -26
public/index.html
CHANGED
|
@@ -16,13 +16,11 @@
|
|
| 16 |
}
|
| 17 |
</script>
|
| 18 |
<style>
|
| 19 |
-
/* Custom Scrollbar for better look */
|
| 20 |
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 21 |
::-webkit-scrollbar-track { background: transparent; }
|
| 22 |
::-webkit-scrollbar-thumb { background: #374151; border-radius: 4px; }
|
| 23 |
::-webkit-scrollbar-thumb:hover { background: #4b5563; }
|
| 24 |
|
| 25 |
-
/* Markdown Styles & Overrides */
|
| 26 |
.markdown-body { font-size: 0.95rem; line-height: 1.6; }
|
| 27 |
.markdown-body p { margin-bottom: 1rem; }
|
| 28 |
.markdown-body p:last-child { margin-bottom: 0; }
|
|
@@ -30,11 +28,9 @@
|
|
| 30 |
.markdown-body pre { background: #1e1e1e; padding: 2.5rem 1rem 1rem 1rem; border-radius: 0.5rem; position: relative; overflow-x: auto; margin-bottom: 1rem; }
|
| 31 |
.markdown-body pre code { font-size: 0.85rem; }
|
| 32 |
|
| 33 |
-
/* Copy Button inside Code Blocks */
|
| 34 |
.copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: #4b5563; color: #e5e7eb; border: none; padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
|
| 35 |
.copy-btn:hover { background: #6b7280; }
|
| 36 |
|
| 37 |
-
/* Reasoning Block */
|
| 38 |
.reasoning-block { border-left: 3px solid #6366f1; padding-left: 1rem; margin-bottom: 1rem; color: #9ca3af; font-size: 0.9em; background: rgba(99, 102, 241, 0.05); padding: 0.75rem; border-radius: 0 0.5rem 0.5rem 0; }
|
| 39 |
</style>
|
| 40 |
</head>
|
|
@@ -56,22 +52,40 @@
|
|
| 56 |
</button>
|
| 57 |
</div>
|
| 58 |
</div>
|
| 59 |
-
<div id="chat-list" class="flex-1 overflow-y-auto p-3 space-y-2">
|
| 60 |
-
<!-- Chats loaded here -->
|
| 61 |
-
</div>
|
| 62 |
</aside>
|
| 63 |
|
| 64 |
<!-- Main Chat Area -->
|
| 65 |
<main class="flex-1 flex flex-col h-full w-full bg-gray-950 min-w-0">
|
| 66 |
<!-- Header -->
|
| 67 |
<header class="h-14 min-h-[3.5rem] border-b border-gray-800 flex items-center px-4 lg:px-6 justify-between bg-gray-900/80 backdrop-blur-sm z-10">
|
| 68 |
-
|
| 69 |
-
|
|
|
|
| 70 |
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
| 71 |
</button>
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
</div>
|
| 74 |
-
|
|
|
|
| 75 |
<span class="hidden sm:inline-block text-xs text-blue-300 bg-blue-900/30 border border-blue-800/50 px-2 py-1 rounded-md font-mono">
|
| 76 |
Tokens: <span id="token-counter">0</span>
|
| 77 |
</span>
|
|
@@ -87,9 +101,7 @@
|
|
| 87 |
</header>
|
| 88 |
|
| 89 |
<!-- Messages -->
|
| 90 |
-
<div id="chat-window" class="flex-1 overflow-y-auto p-4 lg:p-6 space-y-6 scroll-smooth">
|
| 91 |
-
<!-- Messages populated here -->
|
| 92 |
-
</div>
|
| 93 |
|
| 94 |
<!-- Input Area -->
|
| 95 |
<div class="p-3 lg:p-4 bg-gray-900 border-t border-gray-800 shrink-0">
|
|
@@ -111,7 +123,6 @@
|
|
| 111 |
</main>
|
| 112 |
|
| 113 |
<script>
|
| 114 |
-
// Configure Marked with highlight.js for styling block code
|
| 115 |
marked.setOptions({
|
| 116 |
highlight: function(code, lang) {
|
| 117 |
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
@@ -123,7 +134,50 @@
|
|
| 123 |
let attachedImages =[];
|
| 124 |
let pollingInterval = null;
|
| 125 |
|
| 126 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
function toggleSidebar() {
|
| 128 |
const sidebar = document.getElementById('sidebar');
|
| 129 |
const overlay = document.getElementById('mobile-overlay');
|
|
@@ -131,7 +185,6 @@
|
|
| 131 |
overlay.classList.toggle('hidden');
|
| 132 |
}
|
| 133 |
|
| 134 |
-
// Auto-resize Textarea
|
| 135 |
const textarea = document.getElementById('message-input');
|
| 136 |
textarea.addEventListener('input', function() {
|
| 137 |
this.style.height = 'auto';
|
|
@@ -155,7 +208,7 @@
|
|
| 155 |
const chat = await res.json();
|
| 156 |
selectChat(chat.id);
|
| 157 |
loadSidebar();
|
| 158 |
-
if(window.innerWidth < 1024) toggleSidebar();
|
| 159 |
}
|
| 160 |
|
| 161 |
async function deleteCurrentChat() {
|
|
@@ -165,6 +218,7 @@
|
|
| 165 |
currentChatId = null;
|
| 166 |
document.getElementById('chat-window').innerHTML = '';
|
| 167 |
document.getElementById('current-chat-title').innerText = 'Select or create a chat';
|
|
|
|
| 168 |
document.getElementById('token-counter').innerText = '0';
|
| 169 |
loadSidebar();
|
| 170 |
}
|
|
@@ -177,15 +231,16 @@
|
|
| 177 |
const chat = await res.json();
|
| 178 |
|
| 179 |
document.getElementById('current-chat-title').innerText = chat.title;
|
|
|
|
| 180 |
document.getElementById('token-counter').innerText = chat.totalTokens.toLocaleString();
|
| 181 |
|
|
|
|
| 182 |
renderMessages(chat.messages);
|
| 183 |
loadSidebar();
|
| 184 |
|
| 185 |
if(window.innerWidth < 1024 && !document.getElementById('sidebar').classList.contains('-translate-x-full')) {
|
| 186 |
-
toggleSidebar();
|
| 187 |
}
|
| 188 |
-
|
| 189 |
if (chat.isGenerating) pollGeneratingChat(id);
|
| 190 |
}
|
| 191 |
|
|
@@ -209,14 +264,12 @@
|
|
| 209 |
`;
|
| 210 |
}).join('');
|
| 211 |
|
| 212 |
-
// Add copy buttons to code blocks dynamically
|
| 213 |
document.querySelectorAll('.markdown-body pre').forEach(pre => {
|
| 214 |
if (!pre.querySelector('.copy-btn')) {
|
| 215 |
const btn = document.createElement('button');
|
| 216 |
btn.className = 'copy-btn';
|
| 217 |
btn.innerText = 'Copy';
|
| 218 |
btn.onclick = () => {
|
| 219 |
-
// Extract text excluding the button itself
|
| 220 |
const codeText = pre.querySelector('code')?.innerText || pre.innerText.replace('Copy', '');
|
| 221 |
navigator.clipboard.writeText(codeText.trim());
|
| 222 |
btn.innerText = 'Copied!';
|
|
@@ -234,6 +287,10 @@
|
|
| 234 |
const chat = await res.json();
|
| 235 |
renderMessages(chat.messages);
|
| 236 |
document.getElementById('token-counter').innerText = chat.totalTokens.toLocaleString();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
if (!chat.isGenerating) clearInterval(pollingInterval);
|
| 238 |
}, 1500);
|
| 239 |
}
|
|
@@ -283,9 +340,8 @@
|
|
| 283 |
`;
|
| 284 |
container.scrollTop = container.scrollHeight;
|
| 285 |
|
| 286 |
-
// Reset Input Area
|
| 287 |
input.value = '';
|
| 288 |
-
input.style.height = 'auto';
|
| 289 |
attachedImages =[];
|
| 290 |
document.getElementById('image-preview-container').innerHTML = '';
|
| 291 |
document.getElementById('image-preview-container').classList.add('hidden');
|
|
@@ -346,8 +402,6 @@
|
|
| 346 |
|
| 347 |
document.getElementById('message-input').addEventListener('keydown', (e) => {
|
| 348 |
if (e.key === 'Enter' && !e.shiftKey) {
|
| 349 |
-
// Don't send if on mobile to allow enter for newlines natively, unless specifically required.
|
| 350 |
-
// For a dev tool, shift+enter is usually fine.
|
| 351 |
e.preventDefault();
|
| 352 |
sendMessage();
|
| 353 |
}
|
|
|
|
| 16 |
}
|
| 17 |
</script>
|
| 18 |
<style>
|
|
|
|
| 19 |
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 20 |
::-webkit-scrollbar-track { background: transparent; }
|
| 21 |
::-webkit-scrollbar-thumb { background: #374151; border-radius: 4px; }
|
| 22 |
::-webkit-scrollbar-thumb:hover { background: #4b5563; }
|
| 23 |
|
|
|
|
| 24 |
.markdown-body { font-size: 0.95rem; line-height: 1.6; }
|
| 25 |
.markdown-body p { margin-bottom: 1rem; }
|
| 26 |
.markdown-body p:last-child { margin-bottom: 0; }
|
|
|
|
| 28 |
.markdown-body pre { background: #1e1e1e; padding: 2.5rem 1rem 1rem 1rem; border-radius: 0.5rem; position: relative; overflow-x: auto; margin-bottom: 1rem; }
|
| 29 |
.markdown-body pre code { font-size: 0.85rem; }
|
| 30 |
|
|
|
|
| 31 |
.copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: #4b5563; color: #e5e7eb; border: none; padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
|
| 32 |
.copy-btn:hover { background: #6b7280; }
|
| 33 |
|
|
|
|
| 34 |
.reasoning-block { border-left: 3px solid #6366f1; padding-left: 1rem; margin-bottom: 1rem; color: #9ca3af; font-size: 0.9em; background: rgba(99, 102, 241, 0.05); padding: 0.75rem; border-radius: 0 0.5rem 0.5rem 0; }
|
| 35 |
</style>
|
| 36 |
</head>
|
|
|
|
| 52 |
</button>
|
| 53 |
</div>
|
| 54 |
</div>
|
| 55 |
+
<div id="chat-list" class="flex-1 overflow-y-auto p-3 space-y-2"></div>
|
|
|
|
|
|
|
| 56 |
</aside>
|
| 57 |
|
| 58 |
<!-- Main Chat Area -->
|
| 59 |
<main class="flex-1 flex flex-col h-full w-full bg-gray-950 min-w-0">
|
| 60 |
<!-- Header -->
|
| 61 |
<header class="h-14 min-h-[3.5rem] border-b border-gray-800 flex items-center px-4 lg:px-6 justify-between bg-gray-900/80 backdrop-blur-sm z-10">
|
| 62 |
+
|
| 63 |
+
<div class="flex items-center gap-3 overflow-hidden group w-full max-w-[50%]">
|
| 64 |
+
<button onclick="toggleSidebar()" class="lg:hidden p-1 -ml-1 text-gray-400 hover:text-white shrink-0">
|
| 65 |
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
| 66 |
</button>
|
| 67 |
+
|
| 68 |
+
<!-- Title Display Mode -->
|
| 69 |
+
<div id="title-display" class="flex items-center gap-2 overflow-hidden w-full">
|
| 70 |
+
<h2 id="current-chat-title" class="font-semibold text-gray-100 truncate text-sm lg:text-base">Select or create a chat</h2>
|
| 71 |
+
<button onclick="enableTitleEdit()" id="edit-title-btn" class="hidden text-gray-500 hover:text-blue-400 p-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" title="Rename Chat">
|
| 72 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg>
|
| 73 |
+
</button>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Title Edit Mode -->
|
| 77 |
+
<div id="title-edit" class="hidden items-center gap-2 w-full">
|
| 78 |
+
<input type="text" id="title-input" class="bg-gray-800 text-sm lg:text-base text-gray-100 border border-blue-500 rounded px-2 py-1 outline-none w-full" />
|
| 79 |
+
<button onclick="saveTitle()" class="text-blue-400 hover:text-blue-300 p-1 shrink-0 bg-gray-800 rounded">
|
| 80 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
| 81 |
+
</button>
|
| 82 |
+
<button onclick="cancelTitleEdit()" class="text-gray-400 hover:text-red-400 p-1 shrink-0 bg-gray-800 rounded">
|
| 83 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
| 84 |
+
</button>
|
| 85 |
+
</div>
|
| 86 |
</div>
|
| 87 |
+
|
| 88 |
+
<div class="flex items-center gap-2 lg:gap-4 shrink-0 pl-2">
|
| 89 |
<span class="hidden sm:inline-block text-xs text-blue-300 bg-blue-900/30 border border-blue-800/50 px-2 py-1 rounded-md font-mono">
|
| 90 |
Tokens: <span id="token-counter">0</span>
|
| 91 |
</span>
|
|
|
|
| 101 |
</header>
|
| 102 |
|
| 103 |
<!-- Messages -->
|
| 104 |
+
<div id="chat-window" class="flex-1 overflow-y-auto p-4 lg:p-6 space-y-6 scroll-smooth"></div>
|
|
|
|
|
|
|
| 105 |
|
| 106 |
<!-- Input Area -->
|
| 107 |
<div class="p-3 lg:p-4 bg-gray-900 border-t border-gray-800 shrink-0">
|
|
|
|
| 123 |
</main>
|
| 124 |
|
| 125 |
<script>
|
|
|
|
| 126 |
marked.setOptions({
|
| 127 |
highlight: function(code, lang) {
|
| 128 |
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
|
|
| 134 |
let attachedImages =[];
|
| 135 |
let pollingInterval = null;
|
| 136 |
|
| 137 |
+
// --- TITLE EDITING LOGIC ---
|
| 138 |
+
function enableTitleEdit() {
|
| 139 |
+
if (!currentChatId) return;
|
| 140 |
+
const currentTitle = document.getElementById('current-chat-title').innerText;
|
| 141 |
+
document.getElementById('title-display').classList.add('hidden');
|
| 142 |
+
document.getElementById('title-edit').classList.remove('hidden');
|
| 143 |
+
document.getElementById('title-edit').classList.add('flex');
|
| 144 |
+
|
| 145 |
+
const input = document.getElementById('title-input');
|
| 146 |
+
input.value = currentTitle;
|
| 147 |
+
input.focus();
|
| 148 |
+
input.select();
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
function cancelTitleEdit() {
|
| 152 |
+
document.getElementById('title-edit').classList.add('hidden');
|
| 153 |
+
document.getElementById('title-edit').classList.remove('flex');
|
| 154 |
+
document.getElementById('title-display').classList.remove('hidden');
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
async function saveTitle() {
|
| 158 |
+
if (!currentChatId) return cancelTitleEdit();
|
| 159 |
+
const newTitle = document.getElementById('title-input').value.trim();
|
| 160 |
+
if (!newTitle) return cancelTitleEdit();
|
| 161 |
+
|
| 162 |
+
document.getElementById('current-chat-title').innerText = newTitle;
|
| 163 |
+
cancelTitleEdit();
|
| 164 |
+
|
| 165 |
+
await fetch(`/api/chats/${currentChatId}/title`, {
|
| 166 |
+
method: 'PUT',
|
| 167 |
+
headers: { 'Content-Type': 'application/json' },
|
| 168 |
+
body: JSON.stringify({ title: newTitle })
|
| 169 |
+
});
|
| 170 |
+
loadSidebar(); // Refresh sidebar to show new name
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
// Handle Enter and Escape keys for title input
|
| 174 |
+
document.getElementById('title-input').addEventListener('keydown', (e) => {
|
| 175 |
+
if (e.key === 'Enter') saveTitle();
|
| 176 |
+
if (e.key === 'Escape') cancelTitleEdit();
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
// --- UI CORE LOGIC ---
|
| 181 |
function toggleSidebar() {
|
| 182 |
const sidebar = document.getElementById('sidebar');
|
| 183 |
const overlay = document.getElementById('mobile-overlay');
|
|
|
|
| 185 |
overlay.classList.toggle('hidden');
|
| 186 |
}
|
| 187 |
|
|
|
|
| 188 |
const textarea = document.getElementById('message-input');
|
| 189 |
textarea.addEventListener('input', function() {
|
| 190 |
this.style.height = 'auto';
|
|
|
|
| 208 |
const chat = await res.json();
|
| 209 |
selectChat(chat.id);
|
| 210 |
loadSidebar();
|
| 211 |
+
if(window.innerWidth < 1024) toggleSidebar();
|
| 212 |
}
|
| 213 |
|
| 214 |
async function deleteCurrentChat() {
|
|
|
|
| 218 |
currentChatId = null;
|
| 219 |
document.getElementById('chat-window').innerHTML = '';
|
| 220 |
document.getElementById('current-chat-title').innerText = 'Select or create a chat';
|
| 221 |
+
document.getElementById('edit-title-btn').classList.add('hidden');
|
| 222 |
document.getElementById('token-counter').innerText = '0';
|
| 223 |
loadSidebar();
|
| 224 |
}
|
|
|
|
| 231 |
const chat = await res.json();
|
| 232 |
|
| 233 |
document.getElementById('current-chat-title').innerText = chat.title;
|
| 234 |
+
document.getElementById('edit-title-btn').classList.remove('hidden'); // Show edit icon for active chat
|
| 235 |
document.getElementById('token-counter').innerText = chat.totalTokens.toLocaleString();
|
| 236 |
|
| 237 |
+
cancelTitleEdit(); // Ensure we reset title UI state if switching
|
| 238 |
renderMessages(chat.messages);
|
| 239 |
loadSidebar();
|
| 240 |
|
| 241 |
if(window.innerWidth < 1024 && !document.getElementById('sidebar').classList.contains('-translate-x-full')) {
|
| 242 |
+
toggleSidebar();
|
| 243 |
}
|
|
|
|
| 244 |
if (chat.isGenerating) pollGeneratingChat(id);
|
| 245 |
}
|
| 246 |
|
|
|
|
| 264 |
`;
|
| 265 |
}).join('');
|
| 266 |
|
|
|
|
| 267 |
document.querySelectorAll('.markdown-body pre').forEach(pre => {
|
| 268 |
if (!pre.querySelector('.copy-btn')) {
|
| 269 |
const btn = document.createElement('button');
|
| 270 |
btn.className = 'copy-btn';
|
| 271 |
btn.innerText = 'Copy';
|
| 272 |
btn.onclick = () => {
|
|
|
|
| 273 |
const codeText = pre.querySelector('code')?.innerText || pre.innerText.replace('Copy', '');
|
| 274 |
navigator.clipboard.writeText(codeText.trim());
|
| 275 |
btn.innerText = 'Copied!';
|
|
|
|
| 287 |
const chat = await res.json();
|
| 288 |
renderMessages(chat.messages);
|
| 289 |
document.getElementById('token-counter').innerText = chat.totalTokens.toLocaleString();
|
| 290 |
+
if (chat.title !== document.getElementById('current-chat-title').innerText && document.getElementById('title-display').classList.contains('hidden') === false) {
|
| 291 |
+
document.getElementById('current-chat-title').innerText = chat.title;
|
| 292 |
+
loadSidebar();
|
| 293 |
+
}
|
| 294 |
if (!chat.isGenerating) clearInterval(pollingInterval);
|
| 295 |
}, 1500);
|
| 296 |
}
|
|
|
|
| 340 |
`;
|
| 341 |
container.scrollTop = container.scrollHeight;
|
| 342 |
|
|
|
|
| 343 |
input.value = '';
|
| 344 |
+
input.style.height = 'auto';
|
| 345 |
attachedImages =[];
|
| 346 |
document.getElementById('image-preview-container').innerHTML = '';
|
| 347 |
document.getElementById('image-preview-container').classList.add('hidden');
|
|
|
|
| 402 |
|
| 403 |
document.getElementById('message-input').addEventListener('keydown', (e) => {
|
| 404 |
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
|
|
|
|
| 405 |
e.preventDefault();
|
| 406 |
sendMessage();
|
| 407 |
}
|