Spaces:
Running
Running
Update index.html
Browse files- index.html +36 -22
index.html
CHANGED
|
@@ -258,9 +258,9 @@ tailwind.config = {
|
|
| 258 |
<div class="relative">
|
| 259 |
<select id="model-select" class="w-full bg-ash border border-mist rounded-md px-3 py-2 pr-7 text-xs font-mono text-cream focus:border-amber transition-colors cursor-pointer" onchange="saveSettings()">
|
| 260 |
<optgroup label='Openrouter'>
|
|
|
|
| 261 |
<option value='openrouter/hunter-alpha'>Hunter Alpha </option>
|
| 262 |
<option value='openrouter/healer-alpha'>Healer Alpha </option>
|
| 263 |
-
<option value='openrouter/free'>Free Models Router </option>
|
| 264 |
</optgroup>
|
| 265 |
<optgroup label='Nvidia'>
|
| 266 |
<option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
|
|
@@ -577,26 +577,25 @@ let chats = {}; // { id: { id, title, messages: [], createdAt } }
|
|
| 577 |
|
| 578 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 579 |
function init() {
|
| 580 |
-
if (localStorage.getItem('
|
| 581 |
|
| 582 |
-
if (localStorage.getItem('
|
| 583 |
|
| 584 |
loadSettings();
|
| 585 |
loadChats();
|
| 586 |
updateModelBadge();
|
| 587 |
-
newChat();
|
| 588 |
}
|
| 589 |
|
| 590 |
function dismissPrivacyNotice() {
|
| 591 |
document.getElementById('privacy-modal').remove();
|
| 592 |
-
localStorage.setItem('
|
| 593 |
}
|
| 594 |
|
| 595 |
function toggleTheme() {
|
| 596 |
const isLight = document.body.classList.toggle('light');
|
| 597 |
document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
|
| 598 |
document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
|
| 599 |
-
localStorage.setItem('
|
| 600 |
document.getElementById('hljs-theme').href = isLight
|
| 601 |
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
|
| 602 |
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
|
|
@@ -610,13 +609,13 @@ function saveSettings() {
|
|
| 610 |
temperature: document.getElementById('temperature').value,
|
| 611 |
maxTokens: document.getElementById('max-tokens').value,
|
| 612 |
};
|
| 613 |
-
localStorage.setItem('
|
| 614 |
updateModelBadge();
|
| 615 |
}
|
| 616 |
|
| 617 |
function loadSettings() {
|
| 618 |
try {
|
| 619 |
-
const s = JSON.parse(localStorage.getItem('
|
| 620 |
if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
|
| 621 |
if (s.model) document.getElementById('model-select').value = s.model;
|
| 622 |
if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
|
|
@@ -643,12 +642,12 @@ function toggleKeyVisibility() {
|
|
| 643 |
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
|
| 644 |
|
| 645 |
function saveChats() {
|
| 646 |
-
localStorage.setItem('
|
| 647 |
}
|
| 648 |
|
| 649 |
function loadChats() {
|
| 650 |
try {
|
| 651 |
-
chats = JSON.parse(localStorage.getItem('
|
| 652 |
} catch(e) { chats = {}; }
|
| 653 |
renderConvList();
|
| 654 |
}
|
|
@@ -671,7 +670,9 @@ function switchChat(id) {
|
|
| 671 |
renderMessages();
|
| 672 |
renderConvList();
|
| 673 |
const chat = chats[id];
|
| 674 |
-
|
|
|
|
|
|
|
| 675 |
closeSidebar();
|
| 676 |
}
|
| 677 |
|
|
@@ -684,9 +685,11 @@ function deleteChat(id, e) {
|
|
| 684 |
}
|
| 685 |
|
| 686 |
function clearCurrentChat() {
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
|
|
|
|
|
|
| 690 |
document.getElementById('chat-title').textContent = 'New conversation';
|
| 691 |
saveChats();
|
| 692 |
renderConvList();
|
|
@@ -695,6 +698,8 @@ function clearCurrentChat() {
|
|
| 695 |
|
| 696 |
function renderConvList() {
|
| 697 |
const list = document.getElementById('conv-list');
|
|
|
|
|
|
|
| 698 |
const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
|
| 699 |
if (sorted.length === 0) {
|
| 700 |
list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
|
|
@@ -717,9 +722,10 @@ const msgContentMap = {};
|
|
| 717 |
|
| 718 |
function renderMessages() {
|
| 719 |
const container = document.getElementById('messages');
|
| 720 |
-
const
|
|
|
|
| 721 |
|
| 722 |
-
if (msgs.length === 0) {
|
| 723 |
container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
|
| 724 |
<div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
|
| 725 |
<div class="text-fog text-sm font-mono max-w-xs">Enter your OpenRouter API key in the sidebar, choose a model, and start a conversation.</div>
|
|
@@ -762,7 +768,7 @@ function renderMessage(msg, idx) {
|
|
| 762 |
} else {
|
| 763 |
return `<div class="flex gap-3 items-start">
|
| 764 |
<div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
|
| 765 |
-
<span class="text-ink text-xs font-display font-bold italic">
|
| 766 |
</div>
|
| 767 |
<div class="flex-1 min-w-0 group">
|
| 768 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
|
|
@@ -810,7 +816,10 @@ async function sendMessage() {
|
|
| 810 |
const input = document.getElementById('user-input');
|
| 811 |
const text = input.value.trim();
|
| 812 |
if (!text || isStreaming) return;
|
| 813 |
-
|
|
|
|
|
|
|
|
|
|
| 814 |
const apiKey = document.getElementById('api-key').value.trim();
|
| 815 |
if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
|
| 816 |
|
|
@@ -855,7 +864,7 @@ async function sendMessage() {
|
|
| 855 |
placeholder.className = 'flex gap-3 items-start';
|
| 856 |
placeholder.innerHTML = `
|
| 857 |
<div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
|
| 858 |
-
<span class="text-ink text-xs font-display font-bold italic">
|
| 859 |
</div>
|
| 860 |
<div class="flex-1 min-w-0">
|
| 861 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
|
|
@@ -1041,13 +1050,18 @@ function openSidebar() {
|
|
| 1041 |
function closeSidebar() {
|
| 1042 |
const s = document.getElementById('sidebar');
|
| 1043 |
const o = document.getElementById('overlay');
|
|
|
|
|
|
|
| 1044 |
s.classList.add('-translate-x-full');
|
| 1045 |
-
|
| 1046 |
-
|
|
|
|
|
|
|
|
|
|
| 1047 |
}
|
| 1048 |
|
| 1049 |
// ββ Boot βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1050 |
init();
|
| 1051 |
</script>
|
| 1052 |
</body>
|
| 1053 |
-
</html>
|
|
|
|
| 258 |
<div class="relative">
|
| 259 |
<select id="model-select" class="w-full bg-ash border border-mist rounded-md px-3 py-2 pr-7 text-xs font-mono text-cream focus:border-amber transition-colors cursor-pointer" onchange="saveSettings()">
|
| 260 |
<optgroup label='Openrouter'>
|
| 261 |
+
<option value='openrouter/free'>Free Models Router </option>
|
| 262 |
<option value='openrouter/hunter-alpha'>Hunter Alpha </option>
|
| 263 |
<option value='openrouter/healer-alpha'>Healer Alpha </option>
|
|
|
|
| 264 |
</optgroup>
|
| 265 |
<optgroup label='Nvidia'>
|
| 266 |
<option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
|
|
|
|
| 577 |
|
| 578 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 579 |
function init() {
|
| 580 |
+
if (localStorage.getItem('flexchat_privacy_seen')) dismissPrivacyNotice();
|
| 581 |
|
| 582 |
+
if (localStorage.getItem('flexchat_theme') === 'light') toggleTheme(); // β add this
|
| 583 |
|
| 584 |
loadSettings();
|
| 585 |
loadChats();
|
| 586 |
updateModelBadge();
|
|
|
|
| 587 |
}
|
| 588 |
|
| 589 |
function dismissPrivacyNotice() {
|
| 590 |
document.getElementById('privacy-modal').remove();
|
| 591 |
+
localStorage.setItem('flexchat_privacy_seen', '1');
|
| 592 |
}
|
| 593 |
|
| 594 |
function toggleTheme() {
|
| 595 |
const isLight = document.body.classList.toggle('light');
|
| 596 |
document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
|
| 597 |
document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
|
| 598 |
+
localStorage.setItem('flexchat_theme', isLight ? 'light' : 'dark');
|
| 599 |
document.getElementById('hljs-theme').href = isLight
|
| 600 |
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
|
| 601 |
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
|
|
|
|
| 609 |
temperature: document.getElementById('temperature').value,
|
| 610 |
maxTokens: document.getElementById('max-tokens').value,
|
| 611 |
};
|
| 612 |
+
localStorage.setItem('flexchat_settings', JSON.stringify(settings));
|
| 613 |
updateModelBadge();
|
| 614 |
}
|
| 615 |
|
| 616 |
function loadSettings() {
|
| 617 |
try {
|
| 618 |
+
const s = JSON.parse(localStorage.getItem('flexchat_settings') || '{}');
|
| 619 |
if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
|
| 620 |
if (s.model) document.getElementById('model-select').value = s.model;
|
| 621 |
if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
|
|
|
|
| 642 |
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
|
| 643 |
|
| 644 |
function saveChats() {
|
| 645 |
+
localStorage.setItem('flexchat_chats', JSON.stringify(chats));
|
| 646 |
}
|
| 647 |
|
| 648 |
function loadChats() {
|
| 649 |
try {
|
| 650 |
+
chats = JSON.parse(localStorage.getItem('flexchat_chats') || '{}');
|
| 651 |
} catch(e) { chats = {}; }
|
| 652 |
renderConvList();
|
| 653 |
}
|
|
|
|
| 670 |
renderMessages();
|
| 671 |
renderConvList();
|
| 672 |
const chat = chats[id];
|
| 673 |
+
if (chat) {
|
| 674 |
+
document.getElementById('chat-title').textContent = chat.title;
|
| 675 |
+
}
|
| 676 |
closeSidebar();
|
| 677 |
}
|
| 678 |
|
|
|
|
| 685 |
}
|
| 686 |
|
| 687 |
function clearCurrentChat() {
|
| 688 |
+
if (!currentChatId) return;
|
| 689 |
+
const chat = chats[currentChatId];
|
| 690 |
+
if (!chat) return;
|
| 691 |
+
chat.messages = [];
|
| 692 |
+
chat.title = 'New conversation';
|
| 693 |
document.getElementById('chat-title').textContent = 'New conversation';
|
| 694 |
saveChats();
|
| 695 |
renderConvList();
|
|
|
|
| 698 |
|
| 699 |
function renderConvList() {
|
| 700 |
const list = document.getElementById('conv-list');
|
| 701 |
+
if (!list) return;
|
| 702 |
+
|
| 703 |
const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
|
| 704 |
if (sorted.length === 0) {
|
| 705 |
list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
|
|
|
|
| 722 |
|
| 723 |
function renderMessages() {
|
| 724 |
const container = document.getElementById('messages');
|
| 725 |
+
const chat = chats[currentChatId];
|
| 726 |
+
const msgs = chat ? chat.messages : [];
|
| 727 |
|
| 728 |
+
if (!chat || msgs.length === 0) {
|
| 729 |
container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
|
| 730 |
<div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
|
| 731 |
<div class="text-fog text-sm font-mono max-w-xs">Enter your OpenRouter API key in the sidebar, choose a model, and start a conversation.</div>
|
|
|
|
| 768 |
} else {
|
| 769 |
return `<div class="flex gap-3 items-start">
|
| 770 |
<div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
|
| 771 |
+
<span class="text-ink text-xs font-display font-bold italic">Bot</span>
|
| 772 |
</div>
|
| 773 |
<div class="flex-1 min-w-0 group">
|
| 774 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
|
|
|
|
| 816 |
const input = document.getElementById('user-input');
|
| 817 |
const text = input.value.trim();
|
| 818 |
if (!text || isStreaming) return;
|
| 819 |
+
|
| 820 |
+
if (!currentChatId || !chats[currentChatId]) {
|
| 821 |
+
newChat();
|
| 822 |
+
}
|
| 823 |
const apiKey = document.getElementById('api-key').value.trim();
|
| 824 |
if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
|
| 825 |
|
|
|
|
| 864 |
placeholder.className = 'flex gap-3 items-start';
|
| 865 |
placeholder.innerHTML = `
|
| 866 |
<div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
|
| 867 |
+
<span class="text-ink text-xs font-display font-bold italic">Bot</span>
|
| 868 |
</div>
|
| 869 |
<div class="flex-1 min-w-0">
|
| 870 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
|
|
|
|
| 1050 |
function closeSidebar() {
|
| 1051 |
const s = document.getElementById('sidebar');
|
| 1052 |
const o = document.getElementById('overlay');
|
| 1053 |
+
|
| 1054 |
+
s.removeAttribute('data-open');
|
| 1055 |
s.classList.add('-translate-x-full');
|
| 1056 |
+
|
| 1057 |
+
o.classList.add('hidden');
|
| 1058 |
+
setTimeout(() => {
|
| 1059 |
+
o.style.display = 'none';
|
| 1060 |
+
}, 300);
|
| 1061 |
}
|
| 1062 |
|
| 1063 |
// ββ Boot βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1064 |
init();
|
| 1065 |
</script>
|
| 1066 |
</body>
|
| 1067 |
+
</html>
|