Spaces:
Running
Running
Update index.html
Browse files- index.html +169 -82
index.html
CHANGED
|
@@ -104,67 +104,39 @@ tailwind.config = {
|
|
| 104 |
--pre-bg: #ede4cc;
|
| 105 |
}
|
| 106 |
|
| 107 |
-
body
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
.
|
| 118 |
-
|
| 119 |
-
.
|
| 120 |
-
.
|
| 121 |
-
.
|
| 122 |
-
.
|
| 123 |
-
.
|
| 124 |
-
.
|
| 125 |
-
.
|
| 126 |
-
.
|
| 127 |
-
.bg-amber\/10 { background: color-mix(in srgb, var(--accent) 10%, transparent) !important; }
|
| 128 |
-
.bg-amber\/20 { background: color-mix(in srgb, var(--accent) 20%, transparent) !important; }
|
| 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 |
-
/* Serif body text for assistant prose */
|
| 139 |
-
.prose-msg { line-height: 1.75; color: var(--prose); font-family: 'Crimson Pro', serif; font-size: 1.05em; }
|
| 140 |
-
.prose-msg p { margin: 0 0 0.7em; } .prose-msg p:last-child { margin-bottom: 0; }
|
| 141 |
-
.prose-msg code { font-family: 'Courier Prime', monospace; background: var(--code-bg); padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.82em; color: var(--code-col); }
|
| 142 |
-
.prose-msg pre { background: var(--pre-bg); border: 1px solid var(--border); border-radius: 4px; padding: 12px 14px; overflow-x: auto; margin: 0.7em 0; font-family: 'Courier Prime', monospace; }
|
| 143 |
-
.prose-msg pre code { background: transparent; padding: 0; color: var(--prose); font-size: 0.85em; }
|
| 144 |
-
.prose-msg h1,.prose-msg h2,.prose-msg h3 { font-family: 'Crimson Pro', serif; color: var(--cream); margin: 0.9em 0 0.3em; font-weight: 600; font-style: italic; }
|
| 145 |
-
.prose-msg h1 { font-size: 1.4em; } .prose-msg h2 { font-size: 1.2em; } .prose-msg h3 { font-size: 1.05em; }
|
| 146 |
-
.prose-msg ul, .prose-msg ol { padding-left: 1.4em; margin: 0.4em 0 0.7em; }
|
| 147 |
.prose-msg li::marker { color: var(--accent); }
|
| 148 |
-
.prose-msg li { margin: 0.
|
| 149 |
-
.prose-msg blockquote { border-left:
|
| 150 |
.prose-msg a { color: var(--accent); text-decoration: underline; }
|
| 151 |
-
.prose-msg strong { color: var(--cream); font-weight:
|
| 152 |
-
.prose-msg
|
| 153 |
-
.prose-msg
|
| 154 |
-
.prose-msg
|
| 155 |
-
.prose-msg td { border-top: 1px solid var(--border); padding: 6px 10px; }
|
| 156 |
-
|
| 157 |
-
/* User bubbles stay monospace */
|
| 158 |
-
.prose-user { line-height: 1.65; color: var(--user-text); font-family: 'Courier Prime', monospace; }
|
| 159 |
-
.prose-user p { margin: 0 0 0.5em; } .prose-user p:last-child { margin-bottom: 0; }
|
| 160 |
-
.prose-user code { background: rgba(0,0,0,0.12); padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.875em; }
|
| 161 |
-
.prose-user strong { font-weight: 700; }
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
.font-display { font-family: 'Crimson Pro', serif !important; }
|
| 168 |
|
| 169 |
textarea { resize: none; overflow: hidden; }
|
| 170 |
#sidebar { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
@@ -173,8 +145,109 @@ tailwind.config = {
|
|
| 173 |
.dot-2 { animation: blink 1.2s 0.2s infinite; }
|
| 174 |
.dot-3 { animation: blink 1.2s 0.4s infinite; }
|
| 175 |
select { -webkit-appearance: none; appearance: none; }
|
| 176 |
-
textarea:focus, input:focus, select:focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent); }
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
</style>
|
| 179 |
</head>
|
| 180 |
<body class="h-screen flex overflow-hidden">
|
|
@@ -258,9 +331,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 +650,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 +682,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 +715,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 +743,9 @@ function switchChat(id) {
|
|
| 671 |
renderMessages();
|
| 672 |
renderConvList();
|
| 673 |
const chat = chats[id];
|
| 674 |
-
|
|
|
|
|
|
|
| 675 |
closeSidebar();
|
| 676 |
}
|
| 677 |
|
|
@@ -684,9 +758,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 +771,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 +795,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 +841,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 +889,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 +937,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 +1123,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>
|
|
|
|
| 104 |
--pre-bg: #ede4cc;
|
| 105 |
}
|
| 106 |
|
| 107 |
+
body:not(.light) #sidebar::before {
|
| 108 |
+
content: '';
|
| 109 |
+
position: absolute;
|
| 110 |
+
top: 0; left: 0; right: 0;
|
| 111 |
+
height: 1px;
|
| 112 |
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
| 113 |
+
opacity: 0.5;
|
| 114 |
+
}
|
| 115 |
+
/*#sidebar { position: relative; }*/
|
| 116 |
+
|
| 117 |
+
.font-display { font-family: 'Syne', sans-serif !important; }
|
| 118 |
+
|
| 119 |
+
.prose-msg { line-height: 1.7; color: var(--prose); }
|
| 120 |
+
.prose-msg p { margin: 0 0 0.6em; } .prose-msg p:last-child { margin-bottom: 0; }
|
| 121 |
+
.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); }
|
| 122 |
+
.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; }
|
| 123 |
+
.prose-msg pre code { background: transparent; border: none; padding: 0; color: var(--prose); font-size: 0.82em; }
|
| 124 |
+
.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; }
|
| 125 |
+
.prose-msg h1 { font-size: 1.25em; } .prose-msg h2 { font-size: 1.1em; } .prose-msg h3 { font-size: 1em; }
|
| 126 |
+
.prose-msg ul, .prose-msg ol { padding-left: 1.25em; margin: 0.4em 0 0.6em; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
.prose-msg li::marker { color: var(--accent); }
|
| 128 |
+
.prose-msg li { margin: 0.2em 0; }
|
| 129 |
+
.prose-msg blockquote { border-left: 2px solid var(--accent); padding-left: 0.75em; color: var(--ghost); margin: 0.5em 0; font-style: italic; }
|
| 130 |
.prose-msg a { color: var(--accent); text-decoration: underline; }
|
| 131 |
+
.prose-msg strong { color: var(--cream); font-weight: 500; }
|
| 132 |
+
.prose-msg table { border-collapse: collapse; width: 100%; margin: 0.5em 0; font-size: 0.85em; }
|
| 133 |
+
.prose-msg th { background: var(--mist); color: var(--cream); padding: 5px 8px; text-align: left; border-bottom: 1px solid var(--accent); }
|
| 134 |
+
.prose-msg td { border-top: 1px solid var(--border); padding: 5px 8px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
+
.prose-user { line-height: 1.65; color: var(--user-text); }
|
| 137 |
+
.prose-user p { margin: 0 0 0.5em; } .prose-user p:last-child { margin-bottom: 0; }
|
| 138 |
+
.prose-user code { background: rgba(255,255,255,0.1); padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.875em; }
|
| 139 |
+
.prose-user strong { font-weight: 500; }
|
|
|
|
| 140 |
|
| 141 |
textarea { resize: none; overflow: hidden; }
|
| 142 |
#sidebar { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
|
|
| 145 |
.dot-2 { animation: blink 1.2s 0.2s infinite; }
|
| 146 |
.dot-3 { animation: blink 1.2s 0.4s infinite; }
|
| 147 |
select { -webkit-appearance: none; appearance: none; }
|
| 148 |
+
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); }
|
| 149 |
+
|
| 150 |
+
/* Tool panel styles */
|
| 151 |
+
#tool-panel, #file-explorer-panel { transition: all 0.3s ease; }
|
| 152 |
+
.tool-item { transition: all 0.2s ease; }
|
| 153 |
+
.tool-item:hover { transform: translateX(4px); }
|
| 154 |
+
.tool-item.active { background: var(--accent); color: var(--ink); }
|
| 155 |
+
|
| 156 |
+
/* File Explorer Styles */
|
| 157 |
+
.file-tree-item { transition: all 0.15s ease; }
|
| 158 |
+
.file-tree-item:hover { background: var(--mist); }
|
| 159 |
+
.file-tree-item.active { background: var(--accent) !important; color: var(--ink); }
|
| 160 |
+
.file-tree-children { border-left: 1px solid var(--border); margin-left: 11px; padding-left: 8px; }
|
| 161 |
+
.file-explorer-editor { animation: slideIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
|
| 162 |
+
|
| 163 |
+
/* CRITICAL MOBILE FIX - Main container */
|
| 164 |
+
@media (max-width: 768px) {
|
| 165 |
+
/* Override flex behavior on mobile */
|
| 166 |
+
main {
|
| 167 |
+
flex: none !important;
|
| 168 |
+
width: 100% !important;
|
| 169 |
+
max-width: 100vw !important;
|
| 170 |
+
margin-left: 0 !important;
|
| 171 |
+
position: relative !important;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
/* Overlay */
|
| 176 |
+
#overlay {
|
| 177 |
+
position: fixed !important;
|
| 178 |
+
inset: 0 !important;
|
| 179 |
+
background: rgba(0,0,0,0.6) !important;
|
| 180 |
+
display: none !important;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
#overlay:not(.hidden) {
|
| 184 |
+
display: block !important;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
/* Fix message container */
|
| 188 |
+
#messages {
|
| 189 |
+
padding-left: 16px !important;
|
| 190 |
+
padding-right: 16px !important;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
/* Fix input footer */
|
| 194 |
+
#input-footer {
|
| 195 |
+
padding-left: 16px !important;
|
| 196 |
+
padding-right: 16px !important;
|
| 197 |
+
padding-bottom: 5vh;
|
| 198 |
+
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/* Remove any negative margins */
|
| 202 |
+
.flex-1 {
|
| 203 |
+
min-width: 0 !important;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
/* File explorer panel on mobile */
|
| 207 |
+
#file-explorer-panel {
|
| 208 |
+
width: 100% !important;
|
| 209 |
+
max-width: 100vw !important;
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
/* Extra small screens */
|
| 214 |
+
@media (max-width: 480px) {
|
| 215 |
+
main {
|
| 216 |
+
width: 100vw !important;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
#messages, #input-footer {
|
| 220 |
+
padding-left: 12px !important;
|
| 221 |
+
padding-right: 12px !important;
|
| 222 |
+
}
|
| 223 |
+
#input-footer {
|
| 224 |
+
padding-bottom: 5vh;
|
| 225 |
+
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
/* iOS Safari safe area */
|
| 230 |
+
@supports (-webkit-touch-callout: none) {
|
| 231 |
+
body {
|
| 232 |
+
padding-bottom: env(safe-area-inset-bottom);
|
| 233 |
+
}
|
| 234 |
+
#input-footer {
|
| 235 |
+
padding-bottom: 5vh;
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
/* Smooth scrolling on mobile */
|
| 240 |
+
#messages {
|
| 241 |
+
-webkit-overflow-scrolling: touch;
|
| 242 |
+
overscroll-behavior: contain;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
#tool-result-banner, #error-banner {
|
| 246 |
+
margin-left: 12px;
|
| 247 |
+
margin-right: 12px;
|
| 248 |
+
font-size: 11px;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
</style>
|
| 252 |
</head>
|
| 253 |
<body class="h-screen flex overflow-hidden">
|
|
|
|
| 331 |
<div class="relative">
|
| 332 |
<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()">
|
| 333 |
<optgroup label='Openrouter'>
|
| 334 |
+
<option value='openrouter/free'>Free Models Router </option>
|
| 335 |
<option value='openrouter/hunter-alpha'>Hunter Alpha </option>
|
| 336 |
<option value='openrouter/healer-alpha'>Healer Alpha </option>
|
|
|
|
| 337 |
</optgroup>
|
| 338 |
<optgroup label='Nvidia'>
|
| 339 |
<option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
|
|
|
|
| 650 |
|
| 651 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 652 |
function init() {
|
| 653 |
+
if (localStorage.getItem('flexchat_privacy_seen')) dismissPrivacyNotice();
|
| 654 |
|
| 655 |
+
if (localStorage.getItem('flexchat_theme') === 'light') toggleTheme(); // β add this
|
| 656 |
|
| 657 |
loadSettings();
|
| 658 |
loadChats();
|
| 659 |
updateModelBadge();
|
|
|
|
| 660 |
}
|
| 661 |
|
| 662 |
function dismissPrivacyNotice() {
|
| 663 |
document.getElementById('privacy-modal').remove();
|
| 664 |
+
localStorage.setItem('flexchat_privacy_seen', '1');
|
| 665 |
}
|
| 666 |
|
| 667 |
function toggleTheme() {
|
| 668 |
const isLight = document.body.classList.toggle('light');
|
| 669 |
document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
|
| 670 |
document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
|
| 671 |
+
localStorage.setItem('flexchat_theme', isLight ? 'light' : 'dark');
|
| 672 |
document.getElementById('hljs-theme').href = isLight
|
| 673 |
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
|
| 674 |
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
|
|
|
|
| 682 |
temperature: document.getElementById('temperature').value,
|
| 683 |
maxTokens: document.getElementById('max-tokens').value,
|
| 684 |
};
|
| 685 |
+
localStorage.setItem('flexchat_settings', JSON.stringify(settings));
|
| 686 |
updateModelBadge();
|
| 687 |
}
|
| 688 |
|
| 689 |
function loadSettings() {
|
| 690 |
try {
|
| 691 |
+
const s = JSON.parse(localStorage.getItem('flexchat_settings') || '{}');
|
| 692 |
if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
|
| 693 |
if (s.model) document.getElementById('model-select').value = s.model;
|
| 694 |
if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
|
|
|
|
| 715 |
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
|
| 716 |
|
| 717 |
function saveChats() {
|
| 718 |
+
localStorage.setItem('flexchat_chats', JSON.stringify(chats));
|
| 719 |
}
|
| 720 |
|
| 721 |
function loadChats() {
|
| 722 |
try {
|
| 723 |
+
chats = JSON.parse(localStorage.getItem('flexchat_chats') || '{}');
|
| 724 |
} catch(e) { chats = {}; }
|
| 725 |
renderConvList();
|
| 726 |
}
|
|
|
|
| 743 |
renderMessages();
|
| 744 |
renderConvList();
|
| 745 |
const chat = chats[id];
|
| 746 |
+
if (chat) {
|
| 747 |
+
document.getElementById('chat-title').textContent = chat.title;
|
| 748 |
+
}
|
| 749 |
closeSidebar();
|
| 750 |
}
|
| 751 |
|
|
|
|
| 758 |
}
|
| 759 |
|
| 760 |
function clearCurrentChat() {
|
| 761 |
+
if (!currentChatId) return;
|
| 762 |
+
const chat = chats[currentChatId];
|
| 763 |
+
if (!chat) return;
|
| 764 |
+
chat.messages = [];
|
| 765 |
+
chat.title = 'New conversation';
|
| 766 |
document.getElementById('chat-title').textContent = 'New conversation';
|
| 767 |
saveChats();
|
| 768 |
renderConvList();
|
|
|
|
| 771 |
|
| 772 |
function renderConvList() {
|
| 773 |
const list = document.getElementById('conv-list');
|
| 774 |
+
if (!list) return;
|
| 775 |
+
|
| 776 |
const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
|
| 777 |
if (sorted.length === 0) {
|
| 778 |
list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
|
|
|
|
| 795 |
|
| 796 |
function renderMessages() {
|
| 797 |
const container = document.getElementById('messages');
|
| 798 |
+
const chat = chats[currentChatId];
|
| 799 |
+
const msgs = chat ? chat.messages : [];
|
| 800 |
|
| 801 |
+
if (!chat || msgs.length === 0) {
|
| 802 |
container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
|
| 803 |
<div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
|
| 804 |
<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>
|
|
|
|
| 841 |
} else {
|
| 842 |
return `<div class="flex gap-3 items-start">
|
| 843 |
<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">
|
| 844 |
+
<span class="text-ink text-xs font-display font-bold italic">Bot</span>
|
| 845 |
</div>
|
| 846 |
<div class="flex-1 min-w-0 group">
|
| 847 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
|
|
|
|
| 889 |
const input = document.getElementById('user-input');
|
| 890 |
const text = input.value.trim();
|
| 891 |
if (!text || isStreaming) return;
|
| 892 |
+
|
| 893 |
+
if (!currentChatId || !chats[currentChatId]) {
|
| 894 |
+
newChat();
|
| 895 |
+
}
|
| 896 |
const apiKey = document.getElementById('api-key').value.trim();
|
| 897 |
if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
|
| 898 |
|
|
|
|
| 937 |
placeholder.className = 'flex gap-3 items-start';
|
| 938 |
placeholder.innerHTML = `
|
| 939 |
<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">
|
| 940 |
+
<span class="text-ink text-xs font-display font-bold italic">Bot</span>
|
| 941 |
</div>
|
| 942 |
<div class="flex-1 min-w-0">
|
| 943 |
<div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
|
|
|
|
| 1123 |
function closeSidebar() {
|
| 1124 |
const s = document.getElementById('sidebar');
|
| 1125 |
const o = document.getElementById('overlay');
|
| 1126 |
+
|
| 1127 |
+
s.removeAttribute('data-open');
|
| 1128 |
s.classList.add('-translate-x-full');
|
| 1129 |
+
|
| 1130 |
+
o.classList.add('hidden');
|
| 1131 |
+
setTimeout(() => {
|
| 1132 |
+
o.style.display = 'none';
|
| 1133 |
+
}, 300);
|
| 1134 |
}
|
| 1135 |
|
| 1136 |
// ββ Boot βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1137 |
init();
|
| 1138 |
</script>
|
| 1139 |
</body>
|
| 1140 |
+
</html>
|