ClaCe commited on
Commit
f941c33
·
verified ·
1 Parent(s): 9a17482

Upload index.html

Browse files
Files changed (1) hide show
  1. static/index.html +500 -0
static/index.html CHANGED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>ML/AI Use Cases Assistant</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ primary: '#3b82f6',
14
+ secondary: '#8b5cf6',
15
+ }
16
+ }
17
+ }
18
+ }
19
+ </script>
20
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
21
+ </head>
22
+ <body class="bg-gradient-to-br from-blue-50 via-white to-purple-50 min-h-screen">
23
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
24
+ <!-- Header -->
25
+ <div class="text-center mb-12">
26
+ <div class="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full mb-6">
27
+ <i class="fas fa-brain text-white text-2xl"></i>
28
+ </div>
29
+ <h1 class="text-4xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent mb-4">
30
+ ML/AI Use Cases Assistant
31
+ </h1>
32
+ <p class="text-gray-600 text-lg max-w-2xl mx-auto">
33
+ Get AI-powered advice for your business problems based on real implementations from 310+ companies
34
+ </p>
35
+ </div>
36
+
37
+ <!-- API Key Section -->
38
+ <div class="bg-white rounded-2xl shadow-xl p-8 mb-8 border border-gray-100">
39
+ <div class="flex items-center mb-6">
40
+ <div class="w-10 h-10 bg-gradient-to-r from-red-400 to-orange-500 rounded-lg flex items-center justify-center mr-4">
41
+ <i class="fas fa-key text-white"></i>
42
+ </div>
43
+ <h2 class="text-2xl font-bold text-gray-800">API Key Required</h2>
44
+ </div>
45
+
46
+ <div class="space-y-4">
47
+ <div>
48
+ <label for="apiKey" class="block text-sm font-semibold text-gray-700 mb-3">
49
+ <i class="fas fa-shield-alt text-blue-500 mr-2"></i>
50
+ Enter your HuggingFace API Key
51
+ </label>
52
+ <div class="relative">
53
+ <input
54
+ type="password"
55
+ id="apiKey"
56
+ placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
57
+ class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-700"
58
+ />
59
+ <div id="keyStatus" class="absolute right-3 top-3 hidden">
60
+ <i class="fas fa-check-circle text-green-500" id="keyValid"></i>
61
+ <i class="fas fa-times-circle text-red-500" id="keyInvalid"></i>
62
+ <i class="fas fa-spinner fa-spin text-blue-500" id="keyValidating"></i>
63
+ </div>
64
+ </div>
65
+ <div id="keyError" class="mt-2 text-sm text-red-600 hidden"></div>
66
+ </div>
67
+
68
+ <div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
69
+ <p class="text-sm text-blue-800 mb-2">
70
+ <i class="fas fa-info-circle mr-2"></i>
71
+ <strong>Don't have an API key?</strong>
72
+ </p>
73
+ <ol class="text-sm text-blue-700 space-y-1 ml-4">
74
+ <li>1. Go to <a href="https://huggingface.co/settings/tokens" target="_blank" class="underline">HuggingFace Settings</a></li>
75
+ <li>2. Click "Create new token"</li>
76
+ <li>3. Select "Read" access and create</li>
77
+ <li>4. Copy and paste the token above</li>
78
+ </ol>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Main App (Initially Disabled) -->
84
+ <div id="mainApp" class="opacity-50 pointer-events-none">
85
+
86
+ <!-- Search Form -->
87
+ <div class="bg-white rounded-2xl shadow-xl p-8 mb-8 border border-gray-100">
88
+ <form id="chatForm" class="space-y-6">
89
+ <div class="relative">
90
+ <label for="query" class="block text-sm font-semibold text-gray-700 mb-3">
91
+ <i class="fas fa-question-circle text-blue-500 mr-2"></i>
92
+ What business problem would you like to solve?
93
+ </label>
94
+ <textarea
95
+ id="query"
96
+ name="query"
97
+ rows="4"
98
+ class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none text-gray-700 placeholder-gray-400"
99
+ placeholder="e.g., I want to reduce customer churn in my SaaS business..."
100
+ required
101
+ ></textarea>
102
+ </div>
103
+
104
+ <button
105
+ type="submit"
106
+ id="submitBtn"
107
+ class="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-semibold py-4 px-6 rounded-lg transition-all duration-200 transform hover:scale-[1.02] shadow-lg"
108
+ >
109
+ <i class="fas fa-magic mr-2"></i>
110
+ Get AI-Powered Solution
111
+ </button>
112
+ </form>
113
+ </div>
114
+
115
+ <!-- Loading State -->
116
+ <div id="loading" class="hidden text-center py-12">
117
+ <div class="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
118
+ <p class="text-gray-600 text-lg">Analyzing your problem and searching through 309+ use cases...</p>
119
+ <p class="text-sm text-gray-500 mt-2">This may take a moment while the AI processes your request</p>
120
+ </div>
121
+
122
+ <!-- Processing Logs -->
123
+ <div id="processingLogs" class="hidden max-w-4xl mx-auto mb-8">
124
+ <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-100">
125
+ <div class="flex items-center mb-4">
126
+ <div class="w-8 h-8 bg-gradient-to-r from-green-400 to-blue-500 rounded-lg flex items-center justify-center mr-3">
127
+ <i class="fas fa-terminal text-white text-sm"></i>
128
+ </div>
129
+ <h3 class="text-lg font-semibold text-gray-800">Processing Details</h3>
130
+ </div>
131
+ <div id="logMessages" class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm max-h-64 overflow-y-auto">
132
+ <!-- Logs will be inserted here -->
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Results Container -->
138
+ <div id="results" class="hidden space-y-8">
139
+ <!-- Solution Approach -->
140
+ <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
141
+ <div class="flex items-center mb-6">
142
+ <div class="w-10 h-10 bg-gradient-to-r from-green-400 to-blue-500 rounded-lg flex items-center justify-center mr-4">
143
+ <i class="fas fa-lightbulb text-white"></i>
144
+ </div>
145
+ <h2 class="text-2xl font-bold text-gray-800">AI-Powered Solution Approach</h2>
146
+ </div>
147
+ <div id="solutionContent" class="prose prose-gray max-w-none">
148
+ <!-- Solution content will be inserted here -->
149
+ </div>
150
+ </div>
151
+
152
+ <!-- Company Examples -->
153
+ <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
154
+ <div class="flex items-center mb-6">
155
+ <div class="w-10 h-10 bg-gradient-to-r from-purple-400 to-pink-500 rounded-lg flex items-center justify-center mr-4">
156
+ <i class="fas fa-building text-white"></i>
157
+ </div>
158
+ <h2 class="text-2xl font-bold text-gray-800">Real Company Examples</h2>
159
+ </div>
160
+ <div id="examplesContent" class="space-y-4">
161
+ <!-- Examples will be inserted here -->
162
+ </div>
163
+ </div>
164
+
165
+ <!-- Recommended Models -->
166
+ <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
167
+ <div class="flex items-center mb-6">
168
+ <div class="w-10 h-10 bg-gradient-to-r from-orange-400 to-red-500 rounded-lg flex items-center justify-center mr-4">
169
+ <i class="fas fa-cogs text-white"></i>
170
+ </div>
171
+ <h2 class="text-2xl font-bold text-gray-800">Recommended ML Models</h2>
172
+ </div>
173
+
174
+ <!-- Fine-tuned Models Section -->
175
+ <div id="fineTunedSection" class="mb-8">
176
+ <h3 class="text-lg font-semibold text-gray-700 mb-4 flex items-center">
177
+ <i class="fas fa-bullseye text-purple-500 mr-2"></i>
178
+ Fine-tuned & Specialized Models
179
+ </h3>
180
+ <p class="text-sm text-gray-600 mb-4">Ready-to-use models specifically trained for your type of problem</p>
181
+ <div id="fineTunedModels" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
182
+ <!-- Fine-tuned models will be inserted here -->
183
+ </div>
184
+ </div>
185
+
186
+ <!-- General Models Section -->
187
+ <div id="generalSection">
188
+ <h3 class="text-lg font-semibold text-gray-700 mb-4 flex items-center">
189
+ <i class="fas fa-tools text-blue-500 mr-2"></i>
190
+ General Foundation Models
191
+ </h3>
192
+ <p class="text-sm text-gray-600 mb-4">Base models you can fine-tune for your specific use case</p>
193
+ <div id="generalModels" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
194
+ <!-- General models will be inserted here -->
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <!-- Error Message -->
201
+ <div id="error" class="hidden bg-red-50 border border-red-200 rounded-lg p-4 mb-8">
202
+ <div class="flex items-center">
203
+ <i class="fas fa-exclamation-triangle text-red-500 mr-2"></i>
204
+ <p class="text-red-700" id="errorMessage"></p>
205
+ </div>
206
+ </div>
207
+
208
+ </div> <!-- Close mainApp -->
209
+ </div>
210
+
211
+ <script>
212
+ // Global variables
213
+ let userApiKey = localStorage.getItem('hf_api_key') || '';
214
+ let isApiKeyValid = false;
215
+
216
+ // DOM elements
217
+ const chatForm = document.getElementById('chatForm');
218
+ const loading = document.getElementById('loading');
219
+ const processingLogs = document.getElementById('processingLogs');
220
+ const logMessages = document.getElementById('logMessages');
221
+ const results = document.getElementById('results');
222
+ const error = document.getElementById('error');
223
+ const submitBtn = document.getElementById('submitBtn');
224
+ const apiKeyInput = document.getElementById('apiKey');
225
+ const keyStatus = document.getElementById('keyStatus');
226
+ const keyValid = document.getElementById('keyValid');
227
+ const keyInvalid = document.getElementById('keyInvalid');
228
+ const keyValidating = document.getElementById('keyValidating');
229
+ const keyError = document.getElementById('keyError');
230
+ const mainApp = document.getElementById('mainApp');
231
+
232
+ // Initialize on page load
233
+ document.addEventListener('DOMContentLoaded', () => {
234
+ if (userApiKey) {
235
+ apiKeyInput.value = userApiKey;
236
+ validateApiKey(userApiKey);
237
+ }
238
+ });
239
+
240
+ // API Key validation
241
+ apiKeyInput.addEventListener('input', debounce(async (e) => {
242
+ const apiKey = e.target.value.trim();
243
+ if (apiKey.length < 10) {
244
+ resetKeyStatus();
245
+ return;
246
+ }
247
+ await validateApiKey(apiKey);
248
+ }, 500));
249
+
250
+ async function validateApiKey(apiKey) {
251
+ if (!apiKey || apiKey.length < 10) {
252
+ resetKeyStatus();
253
+ return false;
254
+ }
255
+
256
+ showKeyStatus('validating');
257
+
258
+ try {
259
+ const response = await fetch('/validate-key', {
260
+ method: 'POST',
261
+ headers: {
262
+ 'Content-Type': 'application/json',
263
+ },
264
+ body: JSON.stringify({ api_key: apiKey }),
265
+ });
266
+
267
+ const data = await response.json();
268
+
269
+ if (response.ok && data.valid) {
270
+ showKeyStatus('valid');
271
+ userApiKey = apiKey;
272
+ localStorage.setItem('hf_api_key', apiKey);
273
+ isApiKeyValid = true;
274
+ enableMainApp();
275
+ return true;
276
+ } else {
277
+ showKeyStatus('invalid', data.error || 'Invalid API key');
278
+ isApiKeyValid = false;
279
+ disableMainApp();
280
+ return false;
281
+ }
282
+ } catch (err) {
283
+ console.error('Key validation error:', err);
284
+ showKeyStatus('invalid', 'Failed to validate API key. Please check your internet connection.');
285
+ isApiKeyValid = false;
286
+ disableMainApp();
287
+ return false;
288
+ }
289
+ }
290
+
291
+ function showKeyStatus(status, errorMessage = '') {
292
+ keyStatus.classList.remove('hidden');
293
+ keyValid.classList.add('hidden');
294
+ keyInvalid.classList.add('hidden');
295
+ keyValidating.classList.add('hidden');
296
+ keyError.classList.add('hidden');
297
+
298
+ if (status === 'valid') {
299
+ keyValid.classList.remove('hidden');
300
+ keyError.classList.add('hidden');
301
+ } else if (status === 'invalid') {
302
+ keyInvalid.classList.remove('hidden');
303
+ if (errorMessage) {
304
+ keyError.textContent = errorMessage;
305
+ keyError.classList.remove('hidden');
306
+ }
307
+ } else if (status === 'validating') {
308
+ keyValidating.classList.remove('hidden');
309
+ }
310
+ }
311
+
312
+ function resetKeyStatus() {
313
+ keyStatus.classList.add('hidden');
314
+ keyError.classList.add('hidden');
315
+ isApiKeyValid = false;
316
+ disableMainApp();
317
+ }
318
+
319
+ function enableMainApp() {
320
+ mainApp.classList.remove('opacity-50', 'pointer-events-none');
321
+ }
322
+
323
+ function disableMainApp() {
324
+ mainApp.classList.add('opacity-50', 'pointer-events-none');
325
+ }
326
+
327
+ function debounce(func, wait) {
328
+ let timeout;
329
+ return function executedFunction(...args) {
330
+ const later = () => {
331
+ clearTimeout(timeout);
332
+ func(...args);
333
+ };
334
+ clearTimeout(timeout);
335
+ timeout = setTimeout(later, wait);
336
+ };
337
+ }
338
+
339
+ chatForm.addEventListener('submit', async (e) => {
340
+ e.preventDefault();
341
+
342
+ if (!isApiKeyValid) {
343
+ showError('Please enter a valid HuggingFace API key first.');
344
+ return;
345
+ }
346
+
347
+ const query = document.getElementById('query').value.trim();
348
+ if (!query) return;
349
+
350
+ // Show loading state
351
+ loading.classList.remove('hidden');
352
+ results.classList.add('hidden');
353
+ error.classList.add('hidden');
354
+ submitBtn.disabled = true;
355
+ submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Processing...';
356
+
357
+ try {
358
+ const response = await fetch('/chat', {
359
+ method: 'POST',
360
+ headers: {
361
+ 'Content-Type': 'application/json',
362
+ 'X-HF-API-Key': userApiKey,
363
+ },
364
+ body: JSON.stringify({ query: query }),
365
+ });
366
+
367
+ if (!response.ok) {
368
+ const errorData = await response.json();
369
+ throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
370
+ }
371
+
372
+ const data = await response.json();
373
+ displayResults(data);
374
+
375
+ } catch (err) {
376
+ console.error('Error:', err);
377
+ showError(err.message || 'Failed to get response. Please check your API key and try again.');
378
+ } finally {
379
+ // Hide loading state
380
+ loading.classList.add('hidden');
381
+ submitBtn.disabled = false;
382
+ submitBtn.innerHTML = '<i class="fas fa-magic mr-2"></i>Get AI-Powered Solution';
383
+ }
384
+ });
385
+
386
+ function displayResults(data) {
387
+ // Display processing logs first
388
+ if (data.logs && data.logs.length > 0) {
389
+ logMessages.innerHTML = '';
390
+ data.logs.forEach(log => {
391
+ const logDiv = document.createElement('div');
392
+ logDiv.className = 'text-green-400 mb-1';
393
+ logDiv.textContent = log;
394
+ logMessages.appendChild(logDiv);
395
+ });
396
+ processingLogs.classList.remove('hidden');
397
+ }
398
+
399
+ // Display solution approach
400
+ const solutionContent = document.getElementById('solutionContent');
401
+ solutionContent.innerHTML = formatText(data.solution_approach);
402
+
403
+ // Display company examples
404
+ const examplesContent = document.getElementById('examplesContent');
405
+ examplesContent.innerHTML = '';
406
+
407
+ data.company_examples.forEach((example, index) => {
408
+ const exampleCard = document.createElement('div');
409
+ exampleCard.className = 'bg-gradient-to-r from-gray-50 to-blue-50 rounded-lg p-6 border border-gray-200';
410
+ exampleCard.innerHTML = `
411
+ <div class="flex items-start justify-between mb-3">
412
+ <h3 class="font-bold text-lg text-gray-800">${example.company}</h3>
413
+ <span class="bg-blue-100 text-blue-800 text-sm font-medium px-2.5 py-0.5 rounded">${example.year}</span>
414
+ </div>
415
+ <p class="text-sm text-purple-600 font-medium mb-2">${example.industry}</p>
416
+ <p class="text-gray-700 mb-3">${example.description}</p>
417
+ <div class="text-sm text-gray-600">
418
+ <p class="leading-relaxed">${example.summary}</p>
419
+ </div>
420
+ <div class="mt-3 flex items-center justify-between">
421
+ <span class="text-xs text-gray-500">Similarity: ${(example.similarity_score * 100).toFixed(1)}%</span>
422
+ ${example.url ? `<a href="${example.url}" target="_blank" class="inline-flex items-center text-xs text-blue-600 hover:text-blue-800 font-medium transition-colors">
423
+ <i class="fas fa-external-link-alt mr-1"></i>
424
+ Read Article
425
+ </a>` : ''}
426
+ </div>
427
+ `;
428
+ examplesContent.appendChild(exampleCard);
429
+ });
430
+
431
+ // Display recommended models
432
+ const fineTunedModels = document.getElementById('fineTunedModels');
433
+ const generalModels = document.getElementById('generalModels');
434
+
435
+ fineTunedModels.innerHTML = '';
436
+ generalModels.innerHTML = '';
437
+
438
+ if (data.recommended_models) {
439
+ // Display fine-tuned models
440
+ if (data.recommended_models.fine_tuned && data.recommended_models.fine_tuned.length > 0) {
441
+ data.recommended_models.fine_tuned.forEach(model => {
442
+ const modelCard = createModelCard(model, 'purple');
443
+ fineTunedModels.appendChild(modelCard);
444
+ });
445
+ } else {
446
+ fineTunedModels.innerHTML = '<p class="text-gray-500 col-span-full text-center py-4">No specialized models found</p>';
447
+ }
448
+
449
+ // Display general models
450
+ if (data.recommended_models.general && data.recommended_models.general.length > 0) {
451
+ data.recommended_models.general.forEach(model => {
452
+ const modelCard = createModelCard(model, 'blue');
453
+ generalModels.appendChild(modelCard);
454
+ });
455
+ } else {
456
+ generalModels.innerHTML = '<p class="text-gray-500 col-span-full text-center py-4">No general models found</p>';
457
+ }
458
+ }
459
+
460
+ results.classList.remove('hidden');
461
+ }
462
+
463
+ function createModelCard(model, colorTheme) {
464
+ const modelCard = document.createElement('div');
465
+ const colorClasses = {
466
+ 'purple': 'from-purple-50 to-indigo-50 border-purple-200 text-purple-600',
467
+ 'blue': 'from-blue-50 to-cyan-50 border-blue-200 text-blue-600'
468
+ };
469
+
470
+ modelCard.className = `bg-gradient-to-br ${colorClasses[colorTheme]} rounded-lg p-4 border hover:shadow-md transition-shadow`;
471
+ modelCard.innerHTML = `
472
+ <div class="flex items-start justify-between mb-2">
473
+ <h4 class="font-semibold text-gray-800 text-sm">${model.name.split('/').pop()}</h4>
474
+ <span class="text-xs bg-white px-2 py-1 rounded ${colorTheme === 'purple' ? 'text-purple-600' : 'text-blue-600'}">${model.type}</span>
475
+ </div>
476
+ <p class="text-xs text-gray-600 mb-2">${model.description}</p>
477
+ <p class="text-xs ${colorTheme === 'purple' ? 'text-purple-600' : 'text-blue-600'} mb-2 capitalize">${model.task.replace('-', ' ')}</p>
478
+ <p class="text-xs text-gray-500 mb-3">Downloads: ${model.downloads.toLocaleString()}</p>
479
+ <a href="${model.url}" target="_blank"
480
+ class="inline-flex items-center text-xs ${colorTheme === 'purple' ? 'text-purple-600 hover:text-purple-800' : 'text-blue-600 hover:text-blue-800'} font-medium">
481
+ View Model <i class="fas fa-external-link-alt ml-1"></i>
482
+ </a>
483
+ `;
484
+ return modelCard;
485
+ }
486
+
487
+ function formatText(text) {
488
+ // Simple text formatting - convert newlines to paragraphs
489
+ return text.split('\n\n').map(paragraph =>
490
+ `<p class="mb-4 text-gray-700 leading-relaxed">${paragraph.trim()}</p>`
491
+ ).join('');
492
+ }
493
+
494
+ function showError(message) {
495
+ document.getElementById('errorMessage').textContent = message;
496
+ error.classList.remove('hidden');
497
+ }
498
+ </script>
499
+ </body>
500
+ </html>