fmlemos commited on
Commit
cd24728
·
verified ·
1 Parent(s): 7ad9850

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +790 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Zeroshot Chatbot Openrouter
3
- emoji: 👀
4
- colorFrom: pink
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: zeroshot-chatbot-openrouter
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,790 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenRouter Chat Interface</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ @keyframes pulse {
11
+ 0%, 100% { opacity: 1; }
12
+ 50% { opacity: 0.5; }
13
+ }
14
+ .typing-indicator {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ }
18
+ .typing-dot {
19
+ width: 8px;
20
+ height: 8px;
21
+ margin: 0 2px;
22
+ background-color: #9CA3AF;
23
+ border-radius: 50%;
24
+ animation: pulse 1.5s infinite ease-in-out;
25
+ }
26
+ .typing-dot:nth-child(1) { animation-delay: 0s; }
27
+ .typing-dot:nth-child(2) { animation-delay: 0.3s; }
28
+ .typing-dot:nth-child(3) { animation-delay: 0.6s; }
29
+ .chat-container {
30
+ height: calc(100vh - 180px);
31
+ }
32
+ .message-transition {
33
+ transition: all 0.3s ease;
34
+ }
35
+ .model-selector:hover .model-dropdown {
36
+ display: block;
37
+ }
38
+ .scrollbar-thin::-webkit-scrollbar {
39
+ width: 4px;
40
+ }
41
+ .scrollbar-thin::-webkit-scrollbar-track {
42
+ background: #f1f1f1;
43
+ }
44
+ .scrollbar-thin::-webkit-scrollbar-thumb {
45
+ background: #888;
46
+ border-radius: 2px;
47
+ }
48
+ .scrollbar-thin::-webkit-scrollbar-thumb:hover {
49
+ background: #555;
50
+ }
51
+ /* Markdown styling for responses */
52
+ .markdown-response strong {
53
+ font-weight: bold;
54
+ }
55
+ .markdown-response em {
56
+ font-style: italic;
57
+ }
58
+ .markdown-response code {
59
+ background-color: #f3f4f6;
60
+ padding: 0.2em 0.4em;
61
+ border-radius: 3px;
62
+ font-family: monospace;
63
+ }
64
+ .markdown-response pre {
65
+ background-color: #f3f4f6;
66
+ padding: 1em;
67
+ border-radius: 4px;
68
+ overflow-x: auto;
69
+ margin: 0.5em 0;
70
+ }
71
+ .markdown-response ul,
72
+ .markdown-response ol {
73
+ padding-left: 1.5em;
74
+ margin: 0.5em 0;
75
+ }
76
+ .markdown-response ul {
77
+ list-style-type: disc;
78
+ }
79
+ .markdown-response ol {
80
+ list-style-type: decimal;
81
+ }
82
+
83
+ .model-category {
84
+ padding: 8px 12px;
85
+ font-size: 0.75rem;
86
+ font-weight: 600;
87
+ color: #6b7280;
88
+ background-color: #f9fafb;
89
+ border-bottom: 1px solid #e5e7eb;
90
+ text-transform: uppercase;
91
+ letter-spacing: 0.05em;
92
+ }
93
+
94
+ .model-popular {
95
+ position: relative;
96
+ }
97
+
98
+ .model-popular::after {
99
+ content: "★ Popular";
100
+ position: absolute;
101
+ right: 10px;
102
+ top: 10px;
103
+ font-size: 0.65rem;
104
+ background-color: #fef3c7;
105
+ color: #92400e;
106
+ padding: 2px 4px;
107
+ border-radius: 4px;
108
+ }
109
+
110
+ .model-new::after {
111
+ content: "🆕 New";
112
+ position: absolute;
113
+ right: 10px;
114
+ top: 10px;
115
+ font-size: 0.65rem;
116
+ background-color: #dbeafe;
117
+ color: #1e40af;
118
+ padding: 2px 4px;
119
+ border-radius: 4px;
120
+ }
121
+ </style>
122
+ </head>
123
+ <body class="bg-gray-100 font-sans">
124
+ <div class="container mx-auto max-w-4xl p-4">
125
+ <!-- Header -->
126
+ <header class="bg-white rounded-lg shadow-md p-4 mb-4 flex justify-between items-center">
127
+ <div class="flex items-center">
128
+ <i class="fas fa-robot text-2xl text-indigo-600 mr-3"></i>
129
+ <h1 class="text-xl font-bold text-gray-800">OpenRouter Chat</h1>
130
+ </div>
131
+ <div class="relative model-selector">
132
+ <button id="modelButton" class="flex items-center bg-indigo-100 hover:bg-indigo-200 text-indigo-800 font-medium py-2 px-4 rounded-lg transition">
133
+ <span id="selectedModel">GPT-4</span>
134
+ <i class="fas fa-chevron-down ml-2 text-sm"></i>
135
+ </button>
136
+ <div class="model-dropdown hidden absolute right-0 mt-2 w-96 bg-white rounded-md shadow-lg z-10 border border-gray-200">
137
+ <div class="p-2 border-b border-gray-200">
138
+ <input type="text" id="modelSearch", placeholder="Search 100+ models...", class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
139
+ </div>
140
+ <div class="overflow-y-auto max-h-96 scrollbar-thin" id="modelList">
141
+ <!-- Models will be dynamically loaded here -->
142
+ </div>
143
+ <div class="p-2 bg-gray-50 text-xs text-gray-500 border-t border-gray-200 flex justify-between">
144
+ <span id="modelCount">Loading models...</span>
145
+ <span>OpenRouter.ai</span>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </header>
150
+
151
+ <!-- Chat Container -->
152
+ <div class="chat-container bg-white rounded-lg shadow-md p-4 mb-4 overflow-y-auto scrollbar-thin">
153
+ <div id="chatMessages" class="space-y-4">
154
+ <!-- Welcome message -->
155
+ <div class="message-transition flex justify-start">
156
+ <div class="flex-shrink-0 h-10 w-10 rounded-full bg-indigo-100 flex items-center justify center">
157
+ <i class="fas fa-robot text-indigo-600"></i>
158
+ </div>
159
+ <div class="ml-3">
160
+ <div class="bg-gray-100 rounded-lg py-2 px-4 inline-block">
161
+ <p class="text-gray-800">Hello! I'm your AI assistant powered by <strong>OpenRouter</strong>. Here's what you can do:</p>
162
+ <ul class="list-disc pl-5 mt-2 space-y-1">
163
+ <li>Select different AI models from the dropdown above</li>
164
+ <li>Search through 100+ available models</li>
165
+ <li>Your messages will be sent to the selected model</li>
166
+ <li>All interaction happens through the OpenRouter API</li>
167
+ </ul>
168
+ <p class="mt-2 text-gray-800">Please enter your <strong>OpenRouter API Key</strong> in the settings to get started.</p>
169
+ </div>
170
+ <p class="text-xs text-gray-500 mt-1">Today at <span id="currentTime"></span></p>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Input Area -->
177
+ <div class="bg-white rounded-lg shadow-md p-4">
178
+ <div class="flex items-center">
179
+ <textarea id="userInput" rows="1" class="flex-grow border border-gray-300 rounded-l-lg py-2 px-4 focus:outline-none focus:ring-2 focus:ring-indigo-500 resize-none" placeholder="Type your message here..."></textarea>
180
+ <button id="sendButton" class="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-r-lg transition h-10 flex items-center justify-center">
181
+ <i class="fas fa-paper-plane"></i>
182
+ </button>
183
+ </div>
184
+ <div class="flex justify-between items-center mt-2">
185
+ <div class="text-xs text-gray-500">
186
+ <span id="charCount">0</span>/1000
187
+ </div>
188
+ <div class="flex space-x-2">
189
+ <button id="settingsButton" class="text-gray-500 hover:text-indigo-600 transition">
190
+ <i class="fas fa-cog"></i>
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+
197
+ <!-- Settings Modal -->
198
+ <div id="settingsModal" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50 hidden">
199
+ <div class="bg-white rounded-lg p-6 w-96">
200
+ <div class="flex justify-between items-center mb-4">
201
+ <h3 class="text-lg font-semibold">Settings</h3>
202
+ <button id="closeSettings" class="text-gray-500 hover:text-gray-700">
203
+ <i class="fas fa-times"></i>
204
+ </button>
205
+ </div>
206
+ <div class="space-y-4">
207
+ <div>
208
+ <label for="apiKey" class="block text-sm font-medium text-gray-700 mb-1">OpenRouter API Key</label>
209
+ <input type="password" id="apiKey" placeholder="sk-or-XXXXXXXXXXXXXXXXXXXXXXXX", class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
210
+ </div>
211
+ <div>
212
+ <label class="inline-flex items-center">
213
+ <input type="checkbox" id="saveKey" checked class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
214
+ <span class="ml-2 text-sm text-gray-600">Save API Key (uses localStorage)</span>
215
+ </label>
216
+ </div>
217
+ <div>
218
+ <label for="temperature" class="block text-sm font-medium text-gray-700 mb-1">Temperature: <span id="tempValue">0.7</span></label>
219
+ <input type="range" id="temperature", min="0", max="2", step="0.1", value="0.7", class="w-full">
220
+ </div>
221
+ </div>
222
+ <div class="mt-6 flex justify-end space-x-3">
223
+ <button id="saveSettings" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 transition">
224
+ Save
225
+ </button>
226
+ <button id="cancelSettings" class="bg-gray-200 text-gray-800 px-4 py-2 rounded-md hover:bg-gray-300 transition">
227
+ Cancel
228
+ </button>
229
+ </div>
230
+ </div>
231
+ </div>
232
+
233
+ <script>
234
+ // Comprehensive list of OpenRouter models with categories
235
+ const openRouterModels = [
236
+ // OpenAI Models
237
+ { id: 'openai/gpt-4', name: 'GPT-4', provider: 'OpenAI', category: 'popular' },
238
+ { id: 'openai/gpt-4-turbo', name: 'GPT-4 Turbo', provider: 'OpenAI', category: 'openai' },
239
+ { id: 'openai/gpt-4-32k', name: 'GPT-4 32k', provider: 'OpenAI', category: 'openai' },
240
+ { id: 'openai/gpt-3.5-turbo', name: 'GPT-3.5 Turbo', provider: 'OpenAI', category: 'openai' },
241
+ { id: 'openai/gpt-3.5-turbo-16k', name: 'GPT-3.5 Turbo 16k', provider: 'OpenAI', category: 'openai' },
242
+
243
+ // Anthropic Models
244
+ { id: 'anthropic/claude-2', name: 'Claude 2', provider: 'Anthropic', category: 'popular' },
245
+ { id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus', provider: 'Anthropic', category: 'anthropic' },
246
+ { id: 'anthropic/claude-3-sonnet', name: 'Claude 3 Sonnet', provider: 'Anthropic', category: 'anthropic', tags: ['new'] },
247
+ { id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', provider: 'Anthropic', category: 'anthropic', tags: ['new'] },
248
+ { id: 'anthropic/claude-2.1', name: 'Claude 2.1', provider: 'Anthropic', category: 'anthropic' },
249
+ { id: 'anthropic/claude-instant-v1', name: 'Claude Instant', provider: 'Anthropic', category: 'anthropic' },
250
+
251
+ // Google Models - including all Gemma 3 models
252
+ { id: 'google/gemini-pro', name: 'Gemini Pro', provider: 'Google', category: 'google' },
253
+ { id: 'google/palm-2-chat-bison', name: 'PaLM 2 Chat', provider: 'Google', category: 'google' },
254
+ { id: 'google/palm-2-codechat-bison', name: 'PaLM 2 Code Chat', provider: 'Google', category: 'google' },
255
+ { id: 'google/gemma-7b-it', name: 'Gemma 7B Instruct', provider: 'Google', category: 'google', tags: ['new'] },
256
+ { id: 'google/gemma-2b-it', name: 'Gemma 2B Instruct', provider: 'Google', category: 'google', tags: ['new'] },
257
+ { id: 'google/gemma-3-2b-instruct', name: 'Gemma 3 2B Instruct', provider: 'Google', category: 'google', tags: ['new'] },
258
+ { id: 'google/gemma-3-7b-instruct', name: 'Gemma 3 7B Instruct', provider: 'Google', category: 'google', tags: ['new'] },
259
+ { id: 'google/gemma-3-22b-instruct', name: 'Gemma 3 22B Instruct', provider: 'Google', category: 'google', tags: ['new'] },
260
+
261
+ // Meta Models
262
+ { id: 'meta-llama/llama-2-70b-chat', name: 'Llama 2 70B', provider: 'Meta', category: 'popular' },
263
+ { id: 'meta-llama/llama-2-13b-chat', name: 'Llama 2 13B', provider: 'Meta', category: 'meta' },
264
+ { id: 'meta-llama/llama-2-7b-chat', name: 'Llama 2 7B', provider: 'Meta', category: 'meta' },
265
+ { id: 'meta-llama/codellama-34b-instruct', name: 'CodeLlama 34B', provider: 'Meta', category: 'meta' },
266
+
267
+ // Mistral Models
268
+ { id: 'mistralai/mistral-7b-instruct', name: 'Mistral 7B', provider: 'Mistral', category: 'popular' },
269
+ { id: 'mistralai/mixtral-8x7b-instruct', name: 'Mixtral 8x7B', provider: 'Mistral', category: 'mistral' },
270
+ { id: 'mistralai/mistral-medium', name: 'Mistral Medium', provider: 'Mistral', category: 'mistral' },
271
+
272
+ // Other Models
273
+ { id: 'nousresearch/nous-hermes-2-mixtral-8x7b-dpo', name: 'Hermes 2 Mixtral', provider: 'Nous', category: 'other' },
274
+ { id: 'openchat/openchat-7b', name: 'OpenChat 7B', provider: 'OpenChat', category: 'other' },
275
+ { id: 'phind/phind-codellama-34b', name: 'Phind CodeLlama', provider: 'Phind', category: 'other' },
276
+ { id: 'intel/neural-chat-7b', name: 'Neural Chat 7B', provider: 'Intel', category: 'other' },
277
+ { id: 'upstage/solar-10.7b-instruct-v1.0', name: 'Solar 10.7B', provider: 'Upstage', category: 'other' },
278
+ { id: 'jondurbin/airoboros-l2-70b-2.2.1', name: 'Airoboros L2 70B', provider: 'Nous', category: 'other' },
279
+ { id: 'gryphe/mythomax-l2-13b', name: 'MythoMax L2 13B', provider: 'Gryphe', category: 'other' },
280
+ { id: 'undi95/remm-slerp-l2-13b', name: 'ReMM L2 13B', provider: 'Undi', category: 'other' },
281
+ { id: 'migtissera/synthia-70b-v1.2b', name: 'Synthia 70B', provider: 'Migtissera', category: 'other' },
282
+ { id: 'pygmalionai/mythalion-13b', name: 'Mythalion 13B', provider: 'Pygmalion', category: 'other' },
283
+
284
+ // Enterprise Models
285
+ { id: 'anthropic/claude-v2:enterprise', name: 'Claude Enterprise', provider: 'Anthropic', category: 'enterprise' },
286
+ { id: 'cohere/command-nightly', name: 'Command', provider: 'Cohere', category: 'enterprise' },
287
+
288
+ // Coding Models
289
+ { id: 'deepseek-ai/deepseek-coder-33b-instruct', name: 'DeepSeek Coder 33B', provider: 'DeepSeek', category: 'coding' },
290
+ { id: 'bigcode/starcoder', name: 'StarCoder', provider: 'BigCode', category: 'coding' },
291
+ { id: 'codellama/codellama-34b-instruct', name: 'CodeLlama 34B (Fireworks)', provider: 'Meta', category: 'coding' },
292
+
293
+ // Creative Writing Models
294
+ { id: 'lizpreciatior/lzlv-70b-fp16-hf', name: 'LZLV 70B', provider: 'Preciatior', category: 'creative' },
295
+ { id: 'togethercomputer/alpaca-7b', name: 'Alpaca 7B', provider: 'Together', category: 'creative' },
296
+ ];
297
+
298
+ // Category names for display
299
+ const categoryNames = {
300
+ 'popular': '⭐ Popular Models',
301
+ 'openai': 'OpenAI',
302
+ 'anthropic': 'Anthropic',
303
+ 'google': 'Google',
304
+ 'meta': 'Meta',
305
+ 'mistral': 'Mistral',
306
+ 'other': 'Other Models',
307
+ 'enterprise': 'Enterprise Models',
308
+ 'coding': 'Coding Models',
309
+ 'creative': 'Creative Writing'
310
+ };
311
+
312
+ // Main application object
313
+ const app = {
314
+ currentModel: 'openai/gpt-4',
315
+ apiKey: null,
316
+ temperature: 0.7,
317
+ conversationHistory: [],
318
+
319
+ init: function() {
320
+ // Set current time
321
+ const now = new Date();
322
+ const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
323
+ document.getElementById('currentTime').textContent = timeString;
324
+
325
+ // Load saved settings
326
+ this.loadSettings();
327
+
328
+ // Initialize UI components
329
+ this.initModelSelection();
330
+ this.initChat();
331
+ this.initSettings();
332
+
333
+ // Auto-focus input
334
+ document.getElementById('userInput').focus();
335
+ },
336
+
337
+ loadSettings: function() {
338
+ // Load API key from localStorage if available
339
+ if (localStorage.getItem('openRouterApiKey')) {
340
+ this.apiKey = localStorage.getItem('openRouterApiKey');
341
+ document.getElementById('apiKey').value = this.apiKey;
342
+ }
343
+
344
+ // Load other settings
345
+ if (localStorage.getItem('openRouterTemperature')) {
346
+ this.temperature = parseFloat(localStorage.getItem('openRouterTemperature'));
347
+ document.getElementById('temperature').value = this.temperature;
348
+ document.getElementById('tempValue').textContent = this.temperature.toFixed(1);
349
+ }
350
+
351
+ // Check if saveKey was enabled
352
+ if (localStorage.getItem('openRouterSaveKey') === 'true') {
353
+ document.getElementById('saveKey').checked = true;
354
+ }
355
+ },
356
+
357
+ initModelSelection: function() {
358
+ const modelButton = document.getElementById('modelButton');
359
+ const modelDropdown = document.querySelector('.model-dropdown');
360
+ const selectedModel = document.getElementById('selectedModel');
361
+ const modelSearch = document.getElementById('modelSearch');
362
+ const modelList = document.getElementById('modelList');
363
+ const modelCount = document.getElementById('modelCount');
364
+
365
+ // Group models by category
366
+ const groupedModels = {};
367
+ openRouterModels.forEach(model => {
368
+ if (!groupedModels[model.category]) {
369
+ groupedModels[model.category] = [];
370
+ }
371
+ groupedModels[model.category].push(model);
372
+ });
373
+
374
+ // Sort categories in specific order
375
+ const categoryOrder = ['popular', 'openai', 'anthropic', 'google', 'meta', 'mistral', 'coding', 'creative', 'enterprise', 'other'];
376
+ const sortedCategories = categoryOrder.filter(cat => groupedModels[cat]);
377
+
378
+ // Render model list
379
+ let modelListHTML = '';
380
+ sortedCategories.forEach(category => {
381
+ modelListHTML += `<div class="model-category">${categoryNames[category]}</div>`;
382
+ groupedModels[category].forEach(model => {
383
+ const providerColor = {
384
+ 'OpenAI': 'indigo',
385
+ 'Anthropic': 'purple',
386
+ 'Google': 'blue',
387
+ 'Meta': 'orange',
388
+ 'Mistral': 'green',
389
+ 'Nous': 'yellow',
390
+ 'OpenChat': 'cyan',
391
+ 'Phind': 'pink',
392
+ 'Intel': 'blue',
393
+ 'Upstage': 'gray',
394
+ 'DeepSeek': 'teal',
395
+ 'BigCode': 'indigo',
396
+ 'Pygmalion': 'fuchsia',
397
+ 'Undi': 'violet',
398
+ 'Gryphe': 'amber',
399
+ 'Preciatior': 'rose',
400
+ 'Migtissera': 'sky',
401
+ 'Together': 'emerald'
402
+ }[model.provider] || 'gray';
403
+
404
+ const tagsHTML = model.tags?.includes('new') ?
405
+ '<span class="absolute right-2 top-2 text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">New</span>' : '';
406
+
407
+ const isPopular = model.category === 'popular' ?
408
+ '<span class="absolute right-2 top-2 text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Popular</span>' : '';
409
+
410
+ modelListHTML += `
411
+ <div class="model-option relative py-2 px-4 hover:bg-indigo-50 cursor-pointer" data-model="${model.id}">
412
+ ${tagsHTML}
413
+ ${isPopular}
414
+ <div class="flex justify-between items-center">
415
+ <span>${model.name}</span>
416
+ <span class="text-xs bg-${providerColor}-100 text-${providerColor}-800 px-2 py-1 rounded">${model.provider}</span>
417
+ </div>
418
+ </div>
419
+ `;
420
+ });
421
+ });
422
+
423
+ modelList.innerHTML = modelListHTML;
424
+ modelCount.textContent = `${openRouterModels.length} models available`;
425
+
426
+ // Set up model selection
427
+ const modelOptions = document.querySelectorAll('.model-option');
428
+
429
+ modelButton.addEventListener('click', function(e) {
430
+ e.stopPropagation();
431
+ modelDropdown.classList.toggle('hidden');
432
+ if (!modelDropdown.classList.contains('hidden')) {
433
+ modelSearch.focus();
434
+ }
435
+ });
436
+
437
+ modelOptions.forEach(option => {
438
+ option.addEventListener('click', function() {
439
+ const modelName = this.getAttribute('data-model');
440
+ const selectedModelObj = openRouterModels.find(m => m.id === modelName);
441
+ app.currentModel = modelName;
442
+ selectedModel.textContent = selectedModelObj.name;
443
+ modelDropdown.classList.add('hidden');
444
+
445
+ // Notify user of model change
446
+ app.addSystemMessage(`Changed model to: ${selectedModelObj.name} (${selectedModelObj.provider})`);
447
+ });
448
+ });
449
+
450
+ // Model search functionality
451
+ modelSearch.addEventListener('input', function() {
452
+ const searchTerm = this.value.toLowerCase();
453
+ const options = modelList.querySelectorAll('.model-option, .model-category');
454
+
455
+ if (searchTerm.trim() === '') {
456
+ // Show all models with their original categories
457
+ const categories = modelList.querySelectorAll('.model-category');
458
+ categories.forEach(cat => cat.style.display = 'block');
459
+
460
+ modelList.querySelectorAll('.model-option').forEach(opt => {
461
+ opt.style.display = 'block';
462
+ });
463
+ return;
464
+ }
465
+
466
+ let visibleCount = 0;
467
+ let lastVisibleCategory = null;
468
+
469
+ options.forEach(option => {
470
+ if (option.classList.contains('model-category')) {
471
+ return; // Skip category headings initially
472
+ }
473
+
474
+ const modelName = option.textContent.toLowerCase();
475
+ const modelId = option.getAttribute('data-model');
476
+ const modelObj = openRouterModels.find(m => m.id === modelId);
477
+
478
+ if (modelName.includes(searchTerm) ||
479
+ modelObj.provider.toLowerCase().includes(searchTerm) ||
480
+ modelObj.id.toLowerCase().includes(searchTerm)) {
481
+
482
+ option.style.display = 'block';
483
+ visibleCount++;
484
+
485
+ // Show the category heading for this model if not already shown
486
+ const category = modelObj.category;
487
+ const categoryHeading = Array.from(modelList.querySelectorAll('.model-category'))
488
+ .find(cat => cat.textContent === categoryNames[category]);
489
+
490
+ if (categoryHeading && categoryHeading.style.display !== 'block') {
491
+ categoryHeading.style.display = 'block';
492
+ lastVisibleCategory = categoryHeading;
493
+ }
494
+ } else {
495
+ option.style.display = 'none';
496
+ }
497
+ });
498
+
499
+ // Hide all category headings except those with visible models
500
+ modelList.querySelectorAll('.model-category').forEach(cat => {
501
+ let hasVisible = false;
502
+ let nextElement = cat.nextElementSibling;
503
+
504
+ while (nextElement && !nextElement.classList.contains('model-category')) {
505
+ if (nextElement.style.display === 'block') {
506
+ hasVisible = true;
507
+ break;
508
+ }
509
+ nextElement = nextElement.nextElementSibling;
510
+ }
511
+
512
+ cat.style.display = hasVisible ? 'block' : 'none';
513
+ });
514
+
515
+ // Update model count in footer
516
+ modelCount.textContent = `${visibleCount} of ${openRouterModels.length} models`;
517
+
518
+ // Scroll to first result if search isn't empty
519
+ if (searchTerm.trim() !== '') {
520
+ const firstVisible = modelList.querySelector('.model-option[style="display: block;"]');
521
+ if (firstVisible) {
522
+ firstVisible.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
523
+ }
524
+ }
525
+ });
526
+
527
+ // Close dropdown when clicking outside
528
+ document.addEventListener('click', function(event) {
529
+ if (!modelButton.contains(event.target) && !modelDropdown.contains(event.target)) {
530
+ modelDropdown.classList.add('hidden');
531
+ }
532
+ });
533
+ },
534
+
535
+ initChat: function() {
536
+ const userInput = document.getElementById('userInput');
537
+ const sendButton = document.getElementById('sendButton');
538
+ const chatMessages = document.getElementById('chatMessages');
539
+ const charCount = document.getElementById('charCount');
540
+
541
+ userInput.addEventListener('input', function() {
542
+ charCount.textContent = this.value.length;
543
+ });
544
+
545
+ sendButton.addEventListener('click', function() {
546
+ const message = userInput.value.trim();
547
+ if (message) {
548
+ if (!app.apiKey) {
549
+ app.addSystemMessage('Please enter your OpenRouter API Key in settings first.');
550
+ document.getElementById('settingsButton').click();
551
+ return;
552
+ }
553
+
554
+ app.addMessage(message, true);
555
+ userInput.value = '';
556
+ charCount.textContent = '0';
557
+
558
+ const typingIndicator = app.showTypingIndicator();
559
+
560
+ // Call OpenRouter API
561
+ app.callOpenRouter(message, typingIndicator);
562
+ }
563
+ });
564
+
565
+ userInput.addEventListener('keypress', function(e) {
566
+ if (e.key === 'Enter' && !e.shiftKey) {
567
+ e.preventDefault();
568
+ sendButton.click();
569
+ }
570
+ });
571
+
572
+ // Auto-resize textarea
573
+ userInput.addEventListener('input', function() {
574
+ this.style.height = 'auto';
575
+ this.style.height = (this.scrollHeight) + 'px';
576
+ });
577
+ },
578
+
579
+ initSettings: function() {
580
+ const settingsModal = document.getElementById('settingsModal');
581
+ const settingsButton = document.getElementById('settingsButton');
582
+ const closeSettings = document.getElementById('closeSettings');
583
+ const saveSettings = document.getElementById('saveSettings');
584
+ const cancelSettings = document.getElementById('cancelSettings');
585
+ const temperatureSlider = document.getElementById('temperature');
586
+ const tempValue = document.getElementById('tempValue');
587
+
588
+ // Show settings modal
589
+ settingsButton.addEventListener('click', function() {
590
+ settingsModal.classList.remove('hidden');
591
+ });
592
+
593
+ // Close settings modal
594
+ closeSettings.addEventListener('click', function() {
595
+ settingsModal.classList.add('hidden');
596
+ });
597
+
598
+ cancelSettings.addEventListener('click', function() {
599
+ settingsModal.classList.add('hidden');
600
+ });
601
+
602
+ // Save settings
603
+ saveSettings.addEventListener('click', function() {
604
+ const apiKey = document.getElementById('apiKey').value.trim();
605
+ const saveKey = document.getElementById('saveKey').checked;
606
+
607
+ if (!apiKey) {
608
+ alert('Please enter your OpenRouter API Key');
609
+ return;
610
+ }
611
+
612
+ app.apiKey = apiKey;
613
+ app.temperature = parseFloat(temperatureSlider.value);
614
+
615
+ if (saveKey) {
616
+ localStorage.setItem('openRouterApiKey', apiKey);
617
+ localStorage.setItem('openRouterSaveKey', 'true');
618
+ } else {
619
+ localStorage.removeItem('openRouterApiKey');
620
+ localStorage.removeItem('openRouterSaveKey');
621
+ }
622
+
623
+ localStorage.setItem('openRouterTemperature', app.temperature);
624
+
625
+ settingsModal.classList.add('hidden');
626
+ app.addSystemMessage('Settings saved successfully.');
627
+ });
628
+
629
+ // Update temperature value display
630
+ temperatureSlider.addEventListener('input', function() {
631
+ tempValue.textContent = this.value;
632
+ });
633
+ },
634
+
635
+ addMessage: function(content, isUser) {
636
+ const chatMessages = document.getElementById('chatMessages');
637
+ const messageDiv = document.createElement('div');
638
+ messageDiv.className = `message-transition flex ${isUser ? 'justify-end' : 'justify-start'}`;
639
+
640
+ if (isUser) {
641
+ messageDiv.innerHTML = `
642
+ <div class="mr-3">
643
+ <div class="bg-indigo-600 text-white rounded-lg py-2 px-4 inline-block max-w-[90%]">
644
+ <p>${content}</p>
645
+ </div>
646
+ <p class="text-xs text-gray-500 mt-1 text-right">Just now</p>
647
+ </div>
648
+ <div class="flex-shrink-0 h-10 w-10 rounded-full bg-indigo-600 flex items-center justify-center">
649
+ <i class="fas fa-user text-white"></i>
650
+ </div>
651
+ `;
652
+ } else {
653
+ // Process potential markdown in responses
654
+ const mdContent = this.simpleMarkdown(content);
655
+
656
+ messageDiv.innerHTML = `
657
+ <div class="flex-shrink-0 h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center">
658
+ <i class="fas fa-robot text-indigo-600"></i>
659
+ </div>
660
+ <div class="ml-3">
661
+ <div class="bg-gray-100 rounded-lg py-2 px-4 inline-block max-w-[90%] markdown-response">
662
+ ${mdContent}
663
+ </div>
664
+ <p class="text-xs text-gray-500 mt-1">Just now</p>
665
+ </div>
666
+ `;
667
+ }
668
+
669
+ chatMessages.appendChild(messageDiv);
670
+ chatMessages.scrollTop = chatMessages.scrollHeight;
671
+
672
+ // Add to conversation history
673
+ this.conversationHistory.push({
674
+ role: isUser ? 'user' : 'assistant',
675
+ content: content
676
+ });
677
+ },
678
+
679
+ addSystemMessage: function(content) {
680
+ const chatMessages = document.getElementById('chatMessages');
681
+ const messageDiv = document.createElement('div');
682
+ messageDiv.className = 'message-transition flex justify-start';
683
+
684
+ messageDiv.innerHTML = `
685
+ <div class="flex-shrink-0 h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
686
+ <i class="fas fa-info-circle text-gray-600"></i>
687
+ </div>
688
+ <div class="ml-3">
689
+ <div class="bg-gray-200 rounded-lg py-2 px-4 inline-block">
690
+ <p class="text-gray-800">${content}</p>
691
+ </div>
692
+ <p class="text-xs text-gray-500 mt-1">System</p>
693
+ </div>
694
+ `;
695
+
696
+ chatMessages.appendChild(messageDiv);
697
+ chatMessages.scrollTop = chatMessages.scrollHeight;
698
+ },
699
+
700
+ showTypingIndicator: function() {
701
+ const chatMessages = document.getElementById('chatMessages');
702
+ const typingDiv = document.createElement('div');
703
+ typingDiv.className = 'message-transition flex justify-start';
704
+ typingDiv.innerHTML = `
705
+ <div class="flex-shrink-0 h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center">
706
+ <i class="fas fa-robot text-indigo-600"></i>
707
+ </div>
708
+ <div class="ml-3">
709
+ <div class="bg-gray-100 rounded-lg py-2 px-4 inline-block">
710
+ <div class="typing-indicator">
711
+ <div class="typing-dot"></div>
712
+ <div class="typing-dot"></div>
713
+ <div class="typing-dot"></div>
714
+ </div>
715
+ </div>
716
+ </div>
717
+ `;
718
+ chatMessages.appendChild(typingDiv);
719
+ chatMessages.scrollTop = chatMessages.scrollHeight;
720
+ return typingDiv;
721
+ },
722
+
723
+ removeTypingIndicator: function(typingDiv) {
724
+ typingDiv.remove();
725
+ },
726
+
727
+ simpleMarkdown: function(text) {
728
+ // Very basic markdown to HTML conversion
729
+ text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
730
+ text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
731
+ text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
732
+ text = text.replace(/\n/g, '<br>');
733
+
734
+ // Simple list detection
735
+ text = text.replace(/^\s*\*\s(.*)$/gm, '<li>$1</li>');
736
+ text = text.replace(/<li>.*<\/li>/g, function(match) {
737
+ return '<ul>' + match + '</ul>';
738
+ });
739
+
740
+ return text;
741
+ },
742
+
743
+ callOpenRouter: async function(message, typingIndicator) {
744
+ try {
745
+ const messages = [...this.conversationHistory];
746
+ messages.push({
747
+ role: 'user',
748
+ content: message
749
+ });
750
+
751
+ const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
752
+ method: 'POST',
753
+ headers: {
754
+ 'Authorization': `Bearer ${this.apiKey}`,
755
+ 'Content-Type': 'application/json',
756
+ 'HTTP-Referer': window.location.href,
757
+ 'X-Title': 'OpenRouter Chat Interface'
758
+ },
759
+ body: JSON.stringify({
760
+ model: this.currentModel,
761
+ messages: messages,
762
+ temperature: this.temperature
763
+ })
764
+ });
765
+
766
+ if (!response.ok) {
767
+ throw new Error(`API request failed with status ${response.status}: ${await response.text()}`);
768
+ }
769
+
770
+ const data = await response.json();
771
+ const aiResponse = data.choices[0].message.content;
772
+
773
+ this.removeTypingIndicator(typingIndicator);
774
+ this.addMessage(aiResponse, false);
775
+
776
+ } catch (error) {
777
+ console.error('Error calling OpenRouter:', error);
778
+ this.removeTypingIndicator(typingIndicator);
779
+ this.addSystemMessage(`Error: ${error.message}`);
780
+ }
781
+ }
782
+ };
783
+
784
+ // Initialize the app
785
+ document.addEventListener('DOMContentLoaded', function() {
786
+ app.init();
787
+ });
788
+ </script>
789
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=fmlemos/zeroshot-chatbot-openrouter" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
790
+ </html>