Update index.html
Browse files- index.html +168 -64
index.html
CHANGED
|
@@ -104,57 +104,47 @@ tailwind.config = {
|
|
| 104 |
--pre-bg: #e4ddd0;
|
| 105 |
}
|
| 106 |
|
| 107 |
-
body
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
.
|
| 127 |
-
|
| 128 |
-
.
|
| 129 |
-
.border-amber\/25 { border-color: color-mix(in srgb, var(--accent) 25%, transparent) !important; }
|
| 130 |
-
.from-amber { --tw-gradient-from: var(--accent) !important; }
|
| 131 |
-
.to-ember { --tw-gradient-to: var(--accent2) !important; }
|
| 132 |
-
.bg-rose { background: var(--danger) !important; }
|
| 133 |
-
.hover\:text-rose:hover { color: var(--danger) !important; }
|
| 134 |
-
.hover\:text-sage:hover { color: var(--safe) !important; }
|
| 135 |
-
.hover\:text-pearl:hover { color: var(--pearl) !important; }
|
| 136 |
-
textarea, input, select { background: var(--mist); color: var(--cream); }
|
| 137 |
-
|
| 138 |
-
.prose-msg { line-height: 1.65; color: var(--prose); }
|
| 139 |
.prose-msg p { margin: 0 0 0.6em; } .prose-msg p:last-child { margin-bottom: 0; }
|
| 140 |
-
.prose-msg code { font-family: '
|
| 141 |
-
.prose-msg pre { background: var(--pre-bg); border: 1px solid var(--border); border-radius: 6px; padding: 12px 14px; overflow-x: auto; margin: 0.6em 0; }
|
| 142 |
-
.prose-msg pre code { background: transparent; padding: 0; color: var(--prose); font-size: 0.82em; }
|
| 143 |
-
.prose-msg h1,.prose-msg h2,.prose-msg h3 { font-family: '
|
| 144 |
.prose-msg h1 { font-size: 1.25em; } .prose-msg h2 { font-size: 1.1em; } .prose-msg h3 { font-size: 1em; }
|
| 145 |
.prose-msg ul, .prose-msg ol { padding-left: 1.25em; margin: 0.4em 0 0.6em; }
|
|
|
|
| 146 |
.prose-msg li { margin: 0.2em 0; }
|
| 147 |
.prose-msg blockquote { border-left: 2px solid var(--accent); padding-left: 0.75em; color: var(--ghost); margin: 0.5em 0; font-style: italic; }
|
| 148 |
.prose-msg a { color: var(--accent); text-decoration: underline; }
|
| 149 |
.prose-msg strong { color: var(--cream); font-weight: 500; }
|
| 150 |
.prose-msg table { border-collapse: collapse; width: 100%; margin: 0.5em 0; font-size: 0.85em; }
|
| 151 |
-
.prose-msg th { background: var(--mist); color: var(--cream); padding: 5px 8px; text-align: left; }
|
| 152 |
.prose-msg td { border-top: 1px solid var(--border); padding: 5px 8px; }
|
| 153 |
|
| 154 |
.prose-user { line-height: 1.65; color: var(--user-text); }
|
| 155 |
.prose-user p { margin: 0 0 0.5em; } .prose-user p:last-child { margin-bottom: 0; }
|
| 156 |
-
.prose-user code { background: rgba(
|
| 157 |
-
.prose-user pre { background: rgba(0,0,0,0.15); border-radius: 5px; padding: 10px 12px; overflow-x: auto; margin: 0.5em 0; }
|
| 158 |
.prose-user strong { font-weight: 500; }
|
| 159 |
|
| 160 |
textarea { resize: none; overflow: hidden; }
|
|
@@ -164,9 +154,109 @@ tailwind.config = {
|
|
| 164 |
.dot-2 { animation: blink 1.2s 0.2s infinite; }
|
| 165 |
.dot-3 { animation: blink 1.2s 0.4s infinite; }
|
| 166 |
select { -webkit-appearance: none; appearance: none; }
|
| 167 |
-
textarea:focus, input:focus, select:focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent); }
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
</style>
|
| 171 |
</head>
|
| 172 |
<body class="h-screen flex overflow-hidden">
|
|
@@ -250,9 +340,9 @@ tailwind.config = {
|
|
| 250 |
<div class="relative">
|
| 251 |
<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()">
|
| 252 |
<optgroup label='Openrouter'>
|
|
|
|
| 253 |
<option value='openrouter/hunter-alpha'>Hunter Alpha </option>
|
| 254 |
<option value='openrouter/healer-alpha'>Healer Alpha </option>
|
| 255 |
-
<option value='openrouter/free'>Free Models Router </option>
|
| 256 |
</optgroup>
|
| 257 |
<optgroup label='Nvidia'>
|
| 258 |
<option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
|
|
@@ -569,26 +659,25 @@ let chats = {}; // { id: { id, title, messages: [], createdAt } }
|
|
| 569 |
|
| 570 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 571 |
function init() {
|
| 572 |
-
if (localStorage.getItem('
|
| 573 |
|
| 574 |
-
if (localStorage.getItem('
|
| 575 |
|
| 576 |
loadSettings();
|
| 577 |
loadChats();
|
| 578 |
updateModelBadge();
|
| 579 |
-
newChat();
|
| 580 |
}
|
| 581 |
|
| 582 |
function dismissPrivacyNotice() {
|
| 583 |
document.getElementById('privacy-modal').remove();
|
| 584 |
-
localStorage.setItem('
|
| 585 |
}
|
| 586 |
|
| 587 |
function toggleTheme() {
|
| 588 |
const isLight = document.body.classList.toggle('light');
|
| 589 |
document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
|
| 590 |
document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
|
| 591 |
-
localStorage.setItem('
|
| 592 |
document.getElementById('hljs-theme').href = isLight
|
| 593 |
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
|
| 594 |
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
|
|
@@ -602,13 +691,13 @@ function saveSettings() {
|
|
| 602 |
temperature: document.getElementById('temperature').value,
|
| 603 |
maxTokens: document.getElementById('max-tokens').value,
|
| 604 |
};
|
| 605 |
-
localStorage.setItem('
|
| 606 |
updateModelBadge();
|
| 607 |
}
|
| 608 |
|
| 609 |
function loadSettings() {
|
| 610 |
try {
|
| 611 |
-
const s = JSON.parse(localStorage.getItem('
|
| 612 |
if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
|
| 613 |
if (s.model) document.getElementById('model-select').value = s.model;
|
| 614 |
if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
|
|
@@ -635,12 +724,12 @@ function toggleKeyVisibility() {
|
|
| 635 |
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
|
| 636 |
|
| 637 |
function saveChats() {
|
| 638 |
-
localStorage.setItem('
|
| 639 |
}
|
| 640 |
|
| 641 |
function loadChats() {
|
| 642 |
try {
|
| 643 |
-
chats = JSON.parse(localStorage.getItem('
|
| 644 |
} catch(e) { chats = {}; }
|
| 645 |
renderConvList();
|
| 646 |
}
|
|
@@ -663,7 +752,9 @@ function switchChat(id) {
|
|
| 663 |
renderMessages();
|
| 664 |
renderConvList();
|
| 665 |
const chat = chats[id];
|
| 666 |
-
|
|
|
|
|
|
|
| 667 |
closeSidebar();
|
| 668 |
}
|
| 669 |
|
|
@@ -676,9 +767,11 @@ function deleteChat(id, e) {
|
|
| 676 |
}
|
| 677 |
|
| 678 |
function clearCurrentChat() {
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
|
|
|
|
|
|
| 682 |
document.getElementById('chat-title').textContent = 'New conversation';
|
| 683 |
saveChats();
|
| 684 |
renderConvList();
|
|
@@ -687,6 +780,8 @@ function clearCurrentChat() {
|
|
| 687 |
|
| 688 |
function renderConvList() {
|
| 689 |
const list = document.getElementById('conv-list');
|
|
|
|
|
|
|
| 690 |
const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
|
| 691 |
if (sorted.length === 0) {
|
| 692 |
list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
|
|
@@ -709,9 +804,10 @@ const msgContentMap = {};
|
|
| 709 |
|
| 710 |
function renderMessages() {
|
| 711 |
const container = document.getElementById('messages');
|
| 712 |
-
const
|
|
|
|
| 713 |
|
| 714 |
-
if (msgs.length === 0) {
|
| 715 |
container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
|
| 716 |
<div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
|
| 717 |
<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>
|
|
@@ -754,7 +850,7 @@ function renderMessage(msg, idx) {
|
|
| 754 |
} else {
|
| 755 |
return `<div class="flex gap-3 items-start">
|
| 756 |
<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">
|
| 757 |
-
<span class="text-ink text-xs font-display font-bold italic">
|
| 758 |
</div>
|
| 759 |
<div class="flex-1 min-w-0 group">
|
| 760 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
|
|
@@ -802,7 +898,10 @@ async function sendMessage() {
|
|
| 802 |
const input = document.getElementById('user-input');
|
| 803 |
const text = input.value.trim();
|
| 804 |
if (!text || isStreaming) return;
|
| 805 |
-
|
|
|
|
|
|
|
|
|
|
| 806 |
const apiKey = document.getElementById('api-key').value.trim();
|
| 807 |
if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
|
| 808 |
|
|
@@ -847,7 +946,7 @@ async function sendMessage() {
|
|
| 847 |
placeholder.className = 'flex gap-3 items-start';
|
| 848 |
placeholder.innerHTML = `
|
| 849 |
<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">
|
| 850 |
-
<span class="text-ink text-xs font-display font-bold italic">
|
| 851 |
</div>
|
| 852 |
<div class="flex-1 min-w-0">
|
| 853 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
|
|
@@ -1033,13 +1132,18 @@ function openSidebar() {
|
|
| 1033 |
function closeSidebar() {
|
| 1034 |
const s = document.getElementById('sidebar');
|
| 1035 |
const o = document.getElementById('overlay');
|
|
|
|
|
|
|
| 1036 |
s.classList.add('-translate-x-full');
|
| 1037 |
-
|
| 1038 |
-
|
|
|
|
|
|
|
|
|
|
| 1039 |
}
|
| 1040 |
|
| 1041 |
// ββ Boot βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1042 |
init();
|
| 1043 |
</script>
|
| 1044 |
</body>
|
| 1045 |
-
</html>
|
|
|
|
| 104 |
--pre-bg: #e4ddd0;
|
| 105 |
}
|
| 106 |
|
| 107 |
+
body:not(.light) {
|
| 108 |
+
background-image:
|
| 109 |
+
radial-gradient(1px 1px at 20% 30%, rgba(160,200,255,0.4) 0%, transparent 100%),
|
| 110 |
+
radial-gradient(1px 1px at 75% 15%, rgba(160,200,255,0.3) 0%, transparent 100%),
|
| 111 |
+
radial-gradient(1px 1px at 55% 70%, rgba(160,200,255,0.25) 0%, transparent 100%),
|
| 112 |
+
radial-gradient(1px 1px at 90% 55%, rgba(160,200,255,0.2) 0%, transparent 100%),
|
| 113 |
+
radial-gradient(1px 1px at 10% 85%, rgba(160,200,255,0.2) 0%, transparent 100%);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
body:not(.light) #sidebar::before {
|
| 117 |
+
content: '';
|
| 118 |
+
position: absolute;
|
| 119 |
+
top: 0; left: 0; right: 0;
|
| 120 |
+
height: 1px;
|
| 121 |
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
| 122 |
+
opacity: 0.5;
|
| 123 |
+
}
|
| 124 |
+
/*#sidebar { position: relative; }*/
|
| 125 |
+
|
| 126 |
+
.font-display { font-family: 'Syne', sans-serif !important; }
|
| 127 |
+
|
| 128 |
+
.prose-msg { line-height: 1.7; color: var(--prose); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
.prose-msg p { margin: 0 0 0.6em; } .prose-msg p:last-child { margin-bottom: 0; }
|
| 130 |
+
.prose-msg code { font-family: 'Intel One Mono', 'Fira Code', monospace; background: var(--code-bg); padding: 0.1em 0.35em; border-radius: 4px; font-size: 0.875em; color: var(--code-col); border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent); }
|
| 131 |
+
.prose-msg pre { background: var(--pre-bg); border: 1px solid var(--border); border-left: 2px solid var(--accent); border-radius: 6px; padding: 12px 14px; overflow-x: auto; margin: 0.6em 0; }
|
| 132 |
+
.prose-msg pre code { background: transparent; border: none; padding: 0; color: var(--prose); font-size: 0.82em; }
|
| 133 |
+
.prose-msg h1,.prose-msg h2,.prose-msg h3 { font-family: 'Syne', sans-serif; color: var(--cream); margin: 0.8em 0 0.3em; font-weight: 700; }
|
| 134 |
.prose-msg h1 { font-size: 1.25em; } .prose-msg h2 { font-size: 1.1em; } .prose-msg h3 { font-size: 1em; }
|
| 135 |
.prose-msg ul, .prose-msg ol { padding-left: 1.25em; margin: 0.4em 0 0.6em; }
|
| 136 |
+
.prose-msg li::marker { color: var(--accent); }
|
| 137 |
.prose-msg li { margin: 0.2em 0; }
|
| 138 |
.prose-msg blockquote { border-left: 2px solid var(--accent); padding-left: 0.75em; color: var(--ghost); margin: 0.5em 0; font-style: italic; }
|
| 139 |
.prose-msg a { color: var(--accent); text-decoration: underline; }
|
| 140 |
.prose-msg strong { color: var(--cream); font-weight: 500; }
|
| 141 |
.prose-msg table { border-collapse: collapse; width: 100%; margin: 0.5em 0; font-size: 0.85em; }
|
| 142 |
+
.prose-msg th { background: var(--mist); color: var(--cream); padding: 5px 8px; text-align: left; border-bottom: 1px solid var(--accent); }
|
| 143 |
.prose-msg td { border-top: 1px solid var(--border); padding: 5px 8px; }
|
| 144 |
|
| 145 |
.prose-user { line-height: 1.65; color: var(--user-text); }
|
| 146 |
.prose-user p { margin: 0 0 0.5em; } .prose-user p:last-child { margin-bottom: 0; }
|
| 147 |
+
.prose-user code { background: rgba(255,255,255,0.1); padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.875em; }
|
|
|
|
| 148 |
.prose-user strong { font-weight: 500; }
|
| 149 |
|
| 150 |
textarea { resize: none; overflow: hidden; }
|
|
|
|
| 154 |
.dot-2 { animation: blink 1.2s 0.2s infinite; }
|
| 155 |
.dot-3 { animation: blink 1.2s 0.4s infinite; }
|
| 156 |
select { -webkit-appearance: none; appearance: none; }
|
| 157 |
+
textarea:focus, input:focus, select:focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent), 0 0 12px color-mix(in srgb, var(--accent) 20%, transparent); }
|
| 158 |
+
|
| 159 |
+
/* Tool panel styles */
|
| 160 |
+
#tool-panel, #file-explorer-panel { transition: all 0.3s ease; }
|
| 161 |
+
.tool-item { transition: all 0.2s ease; }
|
| 162 |
+
.tool-item:hover { transform: translateX(4px); }
|
| 163 |
+
.tool-item.active { background: var(--accent); color: var(--ink); }
|
| 164 |
+
|
| 165 |
+
/* File Explorer Styles */
|
| 166 |
+
.file-tree-item { transition: all 0.15s ease; }
|
| 167 |
+
.file-tree-item:hover { background: var(--mist); }
|
| 168 |
+
.file-tree-item.active { background: var(--accent) !important; color: var(--ink); }
|
| 169 |
+
.file-tree-children { border-left: 1px solid var(--border); margin-left: 11px; padding-left: 8px; }
|
| 170 |
+
.file-explorer-editor { animation: slideIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
|
| 171 |
+
|
| 172 |
+
/* CRITICAL MOBILE FIX - Main container */
|
| 173 |
+
@media (max-width: 768px) {
|
| 174 |
+
/* Override flex behavior on mobile */
|
| 175 |
+
main {
|
| 176 |
+
flex: none !important;
|
| 177 |
+
width: 100% !important;
|
| 178 |
+
max-width: 100vw !important;
|
| 179 |
+
margin-left: 0 !important;
|
| 180 |
+
position: relative !important;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
/* Overlay */
|
| 185 |
+
#overlay {
|
| 186 |
+
position: fixed !important;
|
| 187 |
+
inset: 0 !important;
|
| 188 |
+
background: rgba(0,0,0,0.6) !important;
|
| 189 |
+
display: none !important;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
#overlay:not(.hidden) {
|
| 193 |
+
display: block !important;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
/* Fix message container */
|
| 197 |
+
#messages {
|
| 198 |
+
padding-left: 16px !important;
|
| 199 |
+
padding-right: 16px !important;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
/* Fix input footer */
|
| 203 |
+
#input-footer {
|
| 204 |
+
padding-left: 16px !important;
|
| 205 |
+
padding-right: 16px !important;
|
| 206 |
+
padding-bottom: 5vh;
|
| 207 |
+
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
/* Remove any negative margins */
|
| 211 |
+
.flex-1 {
|
| 212 |
+
min-width: 0 !important;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
/* File explorer panel on mobile */
|
| 216 |
+
#file-explorer-panel {
|
| 217 |
+
width: 100% !important;
|
| 218 |
+
max-width: 100vw !important;
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/* Extra small screens */
|
| 223 |
+
@media (max-width: 480px) {
|
| 224 |
+
main {
|
| 225 |
+
width: 100vw !important;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
#messages, #input-footer {
|
| 229 |
+
padding-left: 12px !important;
|
| 230 |
+
padding-right: 12px !important;
|
| 231 |
+
}
|
| 232 |
+
#input-footer {
|
| 233 |
+
padding-bottom: 5vh;
|
| 234 |
+
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
/* iOS Safari safe area */
|
| 239 |
+
@supports (-webkit-touch-callout: none) {
|
| 240 |
+
body {
|
| 241 |
+
padding-bottom: env(safe-area-inset-bottom);
|
| 242 |
+
}
|
| 243 |
+
#input-footer {
|
| 244 |
+
padding-bottom: 5vh;
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
/* Smooth scrolling on mobile */
|
| 249 |
+
#messages {
|
| 250 |
+
-webkit-overflow-scrolling: touch;
|
| 251 |
+
overscroll-behavior: contain;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
#tool-result-banner, #error-banner {
|
| 255 |
+
margin-left: 12px;
|
| 256 |
+
margin-right: 12px;
|
| 257 |
+
font-size: 11px;
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
</style>
|
| 261 |
</head>
|
| 262 |
<body class="h-screen flex overflow-hidden">
|
|
|
|
| 340 |
<div class="relative">
|
| 341 |
<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()">
|
| 342 |
<optgroup label='Openrouter'>
|
| 343 |
+
<option value='openrouter/free'>Free Models Router </option>
|
| 344 |
<option value='openrouter/hunter-alpha'>Hunter Alpha </option>
|
| 345 |
<option value='openrouter/healer-alpha'>Healer Alpha </option>
|
|
|
|
| 346 |
</optgroup>
|
| 347 |
<optgroup label='Nvidia'>
|
| 348 |
<option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
|
|
|
|
| 659 |
|
| 660 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 661 |
function init() {
|
| 662 |
+
if (localStorage.getItem('flexchat_privacy_seen')) dismissPrivacyNotice();
|
| 663 |
|
| 664 |
+
if (localStorage.getItem('flexchat_theme') === 'light') toggleTheme(); // β add this
|
| 665 |
|
| 666 |
loadSettings();
|
| 667 |
loadChats();
|
| 668 |
updateModelBadge();
|
|
|
|
| 669 |
}
|
| 670 |
|
| 671 |
function dismissPrivacyNotice() {
|
| 672 |
document.getElementById('privacy-modal').remove();
|
| 673 |
+
localStorage.setItem('flexchat_privacy_seen', '1');
|
| 674 |
}
|
| 675 |
|
| 676 |
function toggleTheme() {
|
| 677 |
const isLight = document.body.classList.toggle('light');
|
| 678 |
document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
|
| 679 |
document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
|
| 680 |
+
localStorage.setItem('flexchat_theme', isLight ? 'light' : 'dark');
|
| 681 |
document.getElementById('hljs-theme').href = isLight
|
| 682 |
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
|
| 683 |
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
|
|
|
|
| 691 |
temperature: document.getElementById('temperature').value,
|
| 692 |
maxTokens: document.getElementById('max-tokens').value,
|
| 693 |
};
|
| 694 |
+
localStorage.setItem('flexchat_settings', JSON.stringify(settings));
|
| 695 |
updateModelBadge();
|
| 696 |
}
|
| 697 |
|
| 698 |
function loadSettings() {
|
| 699 |
try {
|
| 700 |
+
const s = JSON.parse(localStorage.getItem('flexchat_settings') || '{}');
|
| 701 |
if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
|
| 702 |
if (s.model) document.getElementById('model-select').value = s.model;
|
| 703 |
if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
|
|
|
|
| 724 |
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
|
| 725 |
|
| 726 |
function saveChats() {
|
| 727 |
+
localStorage.setItem('flexchat_chats', JSON.stringify(chats));
|
| 728 |
}
|
| 729 |
|
| 730 |
function loadChats() {
|
| 731 |
try {
|
| 732 |
+
chats = JSON.parse(localStorage.getItem('flexchat_chats') || '{}');
|
| 733 |
} catch(e) { chats = {}; }
|
| 734 |
renderConvList();
|
| 735 |
}
|
|
|
|
| 752 |
renderMessages();
|
| 753 |
renderConvList();
|
| 754 |
const chat = chats[id];
|
| 755 |
+
if (chat) {
|
| 756 |
+
document.getElementById('chat-title').textContent = chat.title;
|
| 757 |
+
}
|
| 758 |
closeSidebar();
|
| 759 |
}
|
| 760 |
|
|
|
|
| 767 |
}
|
| 768 |
|
| 769 |
function clearCurrentChat() {
|
| 770 |
+
if (!currentChatId) return;
|
| 771 |
+
const chat = chats[currentChatId];
|
| 772 |
+
if (!chat) return;
|
| 773 |
+
chat.messages = [];
|
| 774 |
+
chat.title = 'New conversation';
|
| 775 |
document.getElementById('chat-title').textContent = 'New conversation';
|
| 776 |
saveChats();
|
| 777 |
renderConvList();
|
|
|
|
| 780 |
|
| 781 |
function renderConvList() {
|
| 782 |
const list = document.getElementById('conv-list');
|
| 783 |
+
if (!list) return;
|
| 784 |
+
|
| 785 |
const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
|
| 786 |
if (sorted.length === 0) {
|
| 787 |
list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
|
|
|
|
| 804 |
|
| 805 |
function renderMessages() {
|
| 806 |
const container = document.getElementById('messages');
|
| 807 |
+
const chat = chats[currentChatId];
|
| 808 |
+
const msgs = chat ? chat.messages : [];
|
| 809 |
|
| 810 |
+
if (!chat || msgs.length === 0) {
|
| 811 |
container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
|
| 812 |
<div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
|
| 813 |
<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>
|
|
|
|
| 850 |
} else {
|
| 851 |
return `<div class="flex gap-3 items-start">
|
| 852 |
<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">
|
| 853 |
+
<span class="text-ink text-xs font-display font-bold italic">Bot</span>
|
| 854 |
</div>
|
| 855 |
<div class="flex-1 min-w-0 group">
|
| 856 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
|
|
|
|
| 898 |
const input = document.getElementById('user-input');
|
| 899 |
const text = input.value.trim();
|
| 900 |
if (!text || isStreaming) return;
|
| 901 |
+
|
| 902 |
+
if (!currentChatId || !chats[currentChatId]) {
|
| 903 |
+
newChat();
|
| 904 |
+
}
|
| 905 |
const apiKey = document.getElementById('api-key').value.trim();
|
| 906 |
if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
|
| 907 |
|
|
|
|
| 946 |
placeholder.className = 'flex gap-3 items-start';
|
| 947 |
placeholder.innerHTML = `
|
| 948 |
<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">
|
| 949 |
+
<span class="text-ink text-xs font-display font-bold italic">Bot</span>
|
| 950 |
</div>
|
| 951 |
<div class="flex-1 min-w-0">
|
| 952 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
|
|
|
|
| 1132 |
function closeSidebar() {
|
| 1133 |
const s = document.getElementById('sidebar');
|
| 1134 |
const o = document.getElementById('overlay');
|
| 1135 |
+
|
| 1136 |
+
s.removeAttribute('data-open');
|
| 1137 |
s.classList.add('-translate-x-full');
|
| 1138 |
+
|
| 1139 |
+
o.classList.add('hidden');
|
| 1140 |
+
setTimeout(() => {
|
| 1141 |
+
o.style.display = 'none';
|
| 1142 |
+
}, 300);
|
| 1143 |
}
|
| 1144 |
|
| 1145 |
// ββ Boot βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1146 |
init();
|
| 1147 |
</script>
|
| 1148 |
</body>
|
| 1149 |
+
</html>
|