LogitCode commited on
Commit
19f51ac
Β·
verified Β·
1 Parent(s): 4e5bdc4

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +139 -57
index.html CHANGED
@@ -104,38 +104,6 @@ tailwind.config = {
104
  --pre-bg: #d8eaf8;
105
  }
106
 
107
- body { background: var(--bg); color: var(--pearl); }
108
- #sidebar { background: var(--surface); border-color: var(--border); }
109
- header { background: var(--surface); border-color: var(--border); }
110
- .bg-smoke { background: var(--surface) !important; }
111
- .bg-ash { background: var(--mist) !important; }
112
- .bg-mist { background: var(--mist) !important; }
113
- .hover\:bg-ash:hover { background: var(--border) !important; }
114
- .hover\:bg-mist:hover { background: var(--mist) !important; }
115
- .border-ash { border-color: var(--border) !important; }
116
- .border-mist { border-color: var(--mist) !important; }
117
- .text-fog { color: var(--fog) !important; }
118
- .text-ghost { color: var(--ghost) !important; }
119
- .text-pearl { color: var(--pearl) !important; }
120
- .text-cream { color: var(--cream) !important; }
121
- .text-amber { color: var(--accent) !important; }
122
- .text-ink { color: var(--user-text) !important; }
123
- .text-rose { color: var(--danger) !important; }
124
- .text-sage { color: var(--safe) !important; }
125
- .bg-amber { background: var(--user-bg) !important; }
126
- .hover\:bg-ember:hover { background: var(--accent2) !important; }
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
- /* Subtle starfield in dark mode */
139
  body:not(.light) {
140
  background-image:
141
  radial-gradient(1px 1px at 20% 30%, rgba(160,200,255,0.4) 0%, transparent 100%),
@@ -145,7 +113,6 @@ tailwind.config = {
145
  radial-gradient(1px 1px at 10% 85%, rgba(160,200,255,0.2) 0%, transparent 100%);
146
  }
147
 
148
- /* Glowing accent on sidebar top */
149
  body:not(.light) #sidebar::before {
150
  content: '';
151
  position: absolute;
@@ -154,7 +121,7 @@ tailwind.config = {
154
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
155
  opacity: 0.5;
156
  }
157
- #sidebar { position: relative; }
158
 
159
  .font-display { font-family: 'Syne', sans-serif !important; }
160
 
@@ -188,7 +155,108 @@ tailwind.config = {
188
  .dot-3 { animation: blink 1.2s 0.4s infinite; }
189
  select { -webkit-appearance: none; appearance: none; }
190
  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); }
191
- @media (max-width: 768px) { main { padding-bottom: 5vh; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  </style>
193
  </head>
194
  <body class="h-screen flex overflow-hidden">
@@ -272,9 +340,9 @@ tailwind.config = {
272
  <div class="relative">
273
  <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()">
274
  <optgroup label='Openrouter'>
 
275
  <option value='openrouter/hunter-alpha'>Hunter Alpha </option>
276
  <option value='openrouter/healer-alpha'>Healer Alpha </option>
277
- <option value='openrouter/free'>Free Models Router </option>
278
  </optgroup>
279
  <optgroup label='Nvidia'>
280
  <option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
@@ -591,26 +659,25 @@ let chats = {}; // { id: { id, title, messages: [], createdAt } }
591
 
592
  // ── Init ───────────────────────────────────────────────────────────────────
593
  function init() {
594
- if (localStorage.getItem('nexus_privacy_seen')) dismissPrivacyNotice();
595
 
596
- if (localStorage.getItem('nexus_theme') === 'light') toggleTheme(); // ← add this
597
 
598
  loadSettings();
599
  loadChats();
600
  updateModelBadge();
601
- newChat();
602
  }
603
 
604
  function dismissPrivacyNotice() {
605
  document.getElementById('privacy-modal').remove();
606
- localStorage.setItem('nexus_privacy_seen', '1');
607
  }
608
 
609
  function toggleTheme() {
610
  const isLight = document.body.classList.toggle('light');
611
  document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
612
  document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
613
- localStorage.setItem('nexus_theme', isLight ? 'light' : 'dark');
614
  document.getElementById('hljs-theme').href = isLight
615
  ? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
616
  : 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
@@ -624,13 +691,13 @@ function saveSettings() {
624
  temperature: document.getElementById('temperature').value,
625
  maxTokens: document.getElementById('max-tokens').value,
626
  };
627
- localStorage.setItem('nexus_settings', JSON.stringify(settings));
628
  updateModelBadge();
629
  }
630
 
631
  function loadSettings() {
632
  try {
633
- const s = JSON.parse(localStorage.getItem('nexus_settings') || '{}');
634
  if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
635
  if (s.model) document.getElementById('model-select').value = s.model;
636
  if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
@@ -657,12 +724,12 @@ function toggleKeyVisibility() {
657
  function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
658
 
659
  function saveChats() {
660
- localStorage.setItem('nexus_chats', JSON.stringify(chats));
661
  }
662
 
663
  function loadChats() {
664
  try {
665
- chats = JSON.parse(localStorage.getItem('nexus_chats') || '{}');
666
  } catch(e) { chats = {}; }
667
  renderConvList();
668
  }
@@ -685,7 +752,9 @@ function switchChat(id) {
685
  renderMessages();
686
  renderConvList();
687
  const chat = chats[id];
688
- document.getElementById('chat-title').textContent = chat.title;
 
 
689
  closeSidebar();
690
  }
691
 
@@ -698,9 +767,11 @@ function deleteChat(id, e) {
698
  }
699
 
700
  function clearCurrentChat() {
701
- if (!currentChatId) return;
702
- chats[currentChatId].messages = [];
703
- chats[currentChatId].title = 'New conversation';
 
 
704
  document.getElementById('chat-title').textContent = 'New conversation';
705
  saveChats();
706
  renderConvList();
@@ -709,6 +780,8 @@ function clearCurrentChat() {
709
 
710
  function renderConvList() {
711
  const list = document.getElementById('conv-list');
 
 
712
  const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
713
  if (sorted.length === 0) {
714
  list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
@@ -731,9 +804,10 @@ const msgContentMap = {};
731
 
732
  function renderMessages() {
733
  const container = document.getElementById('messages');
734
- const msgs = chats[currentChatId]?.messages || [];
 
735
 
736
- if (msgs.length === 0) {
737
  container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
738
  <div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
739
  <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>
@@ -776,7 +850,7 @@ function renderMessage(msg, idx) {
776
  } else {
777
  return `<div class="flex gap-3 items-start">
778
  <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">
779
- <span class="text-ink text-xs font-display font-bold italic">N</span>
780
  </div>
781
  <div class="flex-1 min-w-0 group">
782
  <div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
@@ -824,7 +898,10 @@ async function sendMessage() {
824
  const input = document.getElementById('user-input');
825
  const text = input.value.trim();
826
  if (!text || isStreaming) return;
827
-
 
 
 
828
  const apiKey = document.getElementById('api-key').value.trim();
829
  if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
830
 
@@ -869,7 +946,7 @@ async function sendMessage() {
869
  placeholder.className = 'flex gap-3 items-start';
870
  placeholder.innerHTML = `
871
  <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">
872
- <span class="text-ink text-xs font-display font-bold italic">N</span>
873
  </div>
874
  <div class="flex-1 min-w-0">
875
  <div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
@@ -1055,13 +1132,18 @@ function openSidebar() {
1055
  function closeSidebar() {
1056
  const s = document.getElementById('sidebar');
1057
  const o = document.getElementById('overlay');
 
 
1058
  s.classList.add('-translate-x-full');
1059
- o.classList.add('opacity-0');
1060
- setTimeout(() => o.classList.add('hidden'), 300);
 
 
 
1061
  }
1062
 
1063
  // ── Boot ───────────────────────────────────────────────────────────────────
1064
  init();
1065
  </script>
1066
  </body>
1067
- </html>
 
104
  --pre-bg: #d8eaf8;
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%),
 
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;
 
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
 
 
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>