ClaCe commited on
Commit
21d5a20
·
verified ·
1 Parent(s): 02ffe60

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +12 -1
  2. index.html +504 -0
app.py CHANGED
@@ -152,26 +152,37 @@ async def validate_api_key(request: ApiKeyRequest):
152
  """Validate user's HuggingFace API key"""
153
  api_key = request.api_key.strip()
154
 
 
 
155
  if not api_key or not api_key.startswith('hf_'):
 
156
  return {"valid": False, "error": "Invalid API key format. Must start with 'hf_'"}
157
 
158
  try:
159
  # Test the API key by making a simple request to HuggingFace API
 
160
  test_response = requests.get(
161
  "https://huggingface.co/api/whoami",
162
  headers={"Authorization": f"Bearer {api_key}"},
163
  timeout=10
164
  )
165
 
 
 
 
166
  if test_response.status_code == 200:
167
  user_info = test_response.json()
 
168
  return {"valid": True, "user": user_info.get("name", "Unknown")}
169
  else:
170
- return {"valid": False, "error": "Invalid API key or insufficient permissions"}
 
171
 
172
  except requests.RequestException as e:
 
173
  return {"valid": False, "error": "Failed to validate API key. Please check your connection."}
174
  except Exception as e:
 
175
  return {"valid": False, "error": "Validation failed. Please try again."}
176
 
177
  @app.get("/logs")
 
152
  """Validate user's HuggingFace API key"""
153
  api_key = request.api_key.strip()
154
 
155
+ print(f"🔑 Validating API key: {api_key[:10]}...") # Debug log
156
+
157
  if not api_key or not api_key.startswith('hf_'):
158
+ print(f"❌ Invalid format: {api_key[:10] if api_key else 'empty'}")
159
  return {"valid": False, "error": "Invalid API key format. Must start with 'hf_'"}
160
 
161
  try:
162
  # Test the API key by making a simple request to HuggingFace API
163
+ print("🔍 Testing API key with HuggingFace whoami endpoint...")
164
  test_response = requests.get(
165
  "https://huggingface.co/api/whoami",
166
  headers={"Authorization": f"Bearer {api_key}"},
167
  timeout=10
168
  )
169
 
170
+ print(f"📊 Response status: {test_response.status_code}")
171
+ print(f"📊 Response text: {test_response.text[:200]}...")
172
+
173
  if test_response.status_code == 200:
174
  user_info = test_response.json()
175
+ print(f"✅ Valid API key for user: {user_info.get('name', 'Unknown')}")
176
  return {"valid": True, "user": user_info.get("name", "Unknown")}
177
  else:
178
+ print(f" Invalid response: {test_response.status_code} - {test_response.text}")
179
+ return {"valid": False, "error": f"Invalid API key (status {test_response.status_code})"}
180
 
181
  except requests.RequestException as e:
182
+ print(f"❌ Request exception: {e}")
183
  return {"valid": False, "error": "Failed to validate API key. Please check your connection."}
184
  except Exception as e:
185
+ print(f"❌ General exception: {e}")
186
  return {"valid": False, "error": "Validation failed. Please try again."}
187
 
188
  @app.get("/logs")
index.html ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ console.log('Validation response:', { status: response.status, ok: response.ok, data });
270
+
271
+ if (response.ok && data.valid) {
272
+ console.log('✅ API key validated successfully');
273
+ showKeyStatus('valid');
274
+ userApiKey = apiKey;
275
+ localStorage.setItem('hf_api_key', apiKey);
276
+ isApiKeyValid = true;
277
+ enableMainApp();
278
+ return true;
279
+ } else {
280
+ console.log('❌ API key validation failed:', data);
281
+ showKeyStatus('invalid', data.error || 'Invalid API key');
282
+ isApiKeyValid = false;
283
+ disableMainApp();
284
+ return false;
285
+ }
286
+ } catch (err) {
287
+ console.error('Key validation error:', err);
288
+ showKeyStatus('invalid', 'Failed to validate API key. Please check your internet connection.');
289
+ isApiKeyValid = false;
290
+ disableMainApp();
291
+ return false;
292
+ }
293
+ }
294
+
295
+ function showKeyStatus(status, errorMessage = '') {
296
+ keyStatus.classList.remove('hidden');
297
+ keyValid.classList.add('hidden');
298
+ keyInvalid.classList.add('hidden');
299
+ keyValidating.classList.add('hidden');
300
+ keyError.classList.add('hidden');
301
+
302
+ if (status === 'valid') {
303
+ keyValid.classList.remove('hidden');
304
+ keyError.classList.add('hidden');
305
+ } else if (status === 'invalid') {
306
+ keyInvalid.classList.remove('hidden');
307
+ if (errorMessage) {
308
+ keyError.textContent = errorMessage;
309
+ keyError.classList.remove('hidden');
310
+ }
311
+ } else if (status === 'validating') {
312
+ keyValidating.classList.remove('hidden');
313
+ }
314
+ }
315
+
316
+ function resetKeyStatus() {
317
+ keyStatus.classList.add('hidden');
318
+ keyError.classList.add('hidden');
319
+ isApiKeyValid = false;
320
+ disableMainApp();
321
+ }
322
+
323
+ function enableMainApp() {
324
+ mainApp.classList.remove('opacity-50', 'pointer-events-none');
325
+ }
326
+
327
+ function disableMainApp() {
328
+ mainApp.classList.add('opacity-50', 'pointer-events-none');
329
+ }
330
+
331
+ function debounce(func, wait) {
332
+ let timeout;
333
+ return function executedFunction(...args) {
334
+ const later = () => {
335
+ clearTimeout(timeout);
336
+ func(...args);
337
+ };
338
+ clearTimeout(timeout);
339
+ timeout = setTimeout(later, wait);
340
+ };
341
+ }
342
+
343
+ chatForm.addEventListener('submit', async (e) => {
344
+ e.preventDefault();
345
+
346
+ if (!isApiKeyValid) {
347
+ showError('Please enter a valid HuggingFace API key first.');
348
+ return;
349
+ }
350
+
351
+ const query = document.getElementById('query').value.trim();
352
+ if (!query) return;
353
+
354
+ // Show loading state
355
+ loading.classList.remove('hidden');
356
+ results.classList.add('hidden');
357
+ error.classList.add('hidden');
358
+ submitBtn.disabled = true;
359
+ submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Processing...';
360
+
361
+ try {
362
+ const response = await fetch('/chat', {
363
+ method: 'POST',
364
+ headers: {
365
+ 'Content-Type': 'application/json',
366
+ 'X-HF-API-Key': userApiKey,
367
+ },
368
+ body: JSON.stringify({ query: query }),
369
+ });
370
+
371
+ if (!response.ok) {
372
+ const errorData = await response.json();
373
+ throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
374
+ }
375
+
376
+ const data = await response.json();
377
+ displayResults(data);
378
+
379
+ } catch (err) {
380
+ console.error('Error:', err);
381
+ showError(err.message || 'Failed to get response. Please check your API key and try again.');
382
+ } finally {
383
+ // Hide loading state
384
+ loading.classList.add('hidden');
385
+ submitBtn.disabled = false;
386
+ submitBtn.innerHTML = '<i class="fas fa-magic mr-2"></i>Get AI-Powered Solution';
387
+ }
388
+ });
389
+
390
+ function displayResults(data) {
391
+ // Display processing logs first
392
+ if (data.logs && data.logs.length > 0) {
393
+ logMessages.innerHTML = '';
394
+ data.logs.forEach(log => {
395
+ const logDiv = document.createElement('div');
396
+ logDiv.className = 'text-green-400 mb-1';
397
+ logDiv.textContent = log;
398
+ logMessages.appendChild(logDiv);
399
+ });
400
+ processingLogs.classList.remove('hidden');
401
+ }
402
+
403
+ // Display solution approach
404
+ const solutionContent = document.getElementById('solutionContent');
405
+ solutionContent.innerHTML = formatText(data.solution_approach);
406
+
407
+ // Display company examples
408
+ const examplesContent = document.getElementById('examplesContent');
409
+ examplesContent.innerHTML = '';
410
+
411
+ data.company_examples.forEach((example, index) => {
412
+ const exampleCard = document.createElement('div');
413
+ exampleCard.className = 'bg-gradient-to-r from-gray-50 to-blue-50 rounded-lg p-6 border border-gray-200';
414
+ exampleCard.innerHTML = `
415
+ <div class="flex items-start justify-between mb-3">
416
+ <h3 class="font-bold text-lg text-gray-800">${example.company}</h3>
417
+ <span class="bg-blue-100 text-blue-800 text-sm font-medium px-2.5 py-0.5 rounded">${example.year}</span>
418
+ </div>
419
+ <p class="text-sm text-purple-600 font-medium mb-2">${example.industry}</p>
420
+ <p class="text-gray-700 mb-3">${example.description}</p>
421
+ <div class="text-sm text-gray-600">
422
+ <p class="leading-relaxed">${example.summary}</p>
423
+ </div>
424
+ <div class="mt-3 flex items-center justify-between">
425
+ <span class="text-xs text-gray-500">Similarity: ${(example.similarity_score * 100).toFixed(1)}%</span>
426
+ ${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">
427
+ <i class="fas fa-external-link-alt mr-1"></i>
428
+ Read Article
429
+ </a>` : ''}
430
+ </div>
431
+ `;
432
+ examplesContent.appendChild(exampleCard);
433
+ });
434
+
435
+ // Display recommended models
436
+ const fineTunedModels = document.getElementById('fineTunedModels');
437
+ const generalModels = document.getElementById('generalModels');
438
+
439
+ fineTunedModels.innerHTML = '';
440
+ generalModels.innerHTML = '';
441
+
442
+ if (data.recommended_models) {
443
+ // Display fine-tuned models
444
+ if (data.recommended_models.fine_tuned && data.recommended_models.fine_tuned.length > 0) {
445
+ data.recommended_models.fine_tuned.forEach(model => {
446
+ const modelCard = createModelCard(model, 'purple');
447
+ fineTunedModels.appendChild(modelCard);
448
+ });
449
+ } else {
450
+ fineTunedModels.innerHTML = '<p class="text-gray-500 col-span-full text-center py-4">No specialized models found</p>';
451
+ }
452
+
453
+ // Display general models
454
+ if (data.recommended_models.general && data.recommended_models.general.length > 0) {
455
+ data.recommended_models.general.forEach(model => {
456
+ const modelCard = createModelCard(model, 'blue');
457
+ generalModels.appendChild(modelCard);
458
+ });
459
+ } else {
460
+ generalModels.innerHTML = '<p class="text-gray-500 col-span-full text-center py-4">No general models found</p>';
461
+ }
462
+ }
463
+
464
+ results.classList.remove('hidden');
465
+ }
466
+
467
+ function createModelCard(model, colorTheme) {
468
+ const modelCard = document.createElement('div');
469
+ const colorClasses = {
470
+ 'purple': 'from-purple-50 to-indigo-50 border-purple-200 text-purple-600',
471
+ 'blue': 'from-blue-50 to-cyan-50 border-blue-200 text-blue-600'
472
+ };
473
+
474
+ modelCard.className = `bg-gradient-to-br ${colorClasses[colorTheme]} rounded-lg p-4 border hover:shadow-md transition-shadow`;
475
+ modelCard.innerHTML = `
476
+ <div class="flex items-start justify-between mb-2">
477
+ <h4 class="font-semibold text-gray-800 text-sm">${model.name.split('/').pop()}</h4>
478
+ <span class="text-xs bg-white px-2 py-1 rounded ${colorTheme === 'purple' ? 'text-purple-600' : 'text-blue-600'}">${model.type}</span>
479
+ </div>
480
+ <p class="text-xs text-gray-600 mb-2">${model.description}</p>
481
+ <p class="text-xs ${colorTheme === 'purple' ? 'text-purple-600' : 'text-blue-600'} mb-2 capitalize">${model.task.replace('-', ' ')}</p>
482
+ <p class="text-xs text-gray-500 mb-3">Downloads: ${model.downloads.toLocaleString()}</p>
483
+ <a href="${model.url}" target="_blank"
484
+ 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">
485
+ View Model <i class="fas fa-external-link-alt ml-1"></i>
486
+ </a>
487
+ `;
488
+ return modelCard;
489
+ }
490
+
491
+ function formatText(text) {
492
+ // Simple text formatting - convert newlines to paragraphs
493
+ return text.split('\n\n').map(paragraph =>
494
+ `<p class="mb-4 text-gray-700 leading-relaxed">${paragraph.trim()}</p>`
495
+ ).join('');
496
+ }
497
+
498
+ function showError(message) {
499
+ document.getElementById('errorMessage').textContent = message;
500
+ error.classList.remove('hidden');
501
+ }
502
+ </script>
503
+ </body>
504
+ </html>