stratifyai 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
api/static/models.html ADDED
@@ -0,0 +1,567 @@
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>StratifyAI - Model Catalog</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1400px;
23
+ margin: 0 auto;
24
+ }
25
+
26
+ .header {
27
+ background: white;
28
+ padding: 20px 30px;
29
+ border-radius: 10px;
30
+ margin-bottom: 20px;
31
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
32
+ display: flex;
33
+ justify-content: space-between;
34
+ align-items: center;
35
+ }
36
+
37
+ .header h1 {
38
+ color: #667eea;
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 15px;
42
+ }
43
+
44
+ .header h1 img {
45
+ height: 40px;
46
+ width: auto;
47
+ }
48
+
49
+ .back-btn {
50
+ background: #667eea;
51
+ color: white;
52
+ border: none;
53
+ padding: 10px 20px;
54
+ border-radius: 5px;
55
+ cursor: pointer;
56
+ font-weight: 500;
57
+ text-decoration: none;
58
+ transition: background 0.3s;
59
+ }
60
+
61
+ .back-btn:hover {
62
+ background: #5568d3;
63
+ }
64
+
65
+ .summary-bar {
66
+ background: white;
67
+ padding: 15px 25px;
68
+ border-radius: 10px;
69
+ margin-bottom: 20px;
70
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
71
+ display: flex;
72
+ gap: 30px;
73
+ flex-wrap: wrap;
74
+ }
75
+
76
+ .summary-item {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 8px;
80
+ }
81
+
82
+ .summary-item .label {
83
+ color: #666;
84
+ font-size: 14px;
85
+ }
86
+
87
+ .summary-item .value {
88
+ font-weight: 600;
89
+ color: #333;
90
+ font-size: 18px;
91
+ }
92
+
93
+ .filters {
94
+ background: white;
95
+ padding: 15px 25px;
96
+ border-radius: 10px;
97
+ margin-bottom: 20px;
98
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
99
+ display: flex;
100
+ gap: 20px;
101
+ align-items: center;
102
+ flex-wrap: wrap;
103
+ }
104
+
105
+ .filter-group {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 8px;
109
+ }
110
+
111
+ .filter-group label {
112
+ color: #666;
113
+ font-size: 14px;
114
+ }
115
+
116
+ .filter-group select,
117
+ .filter-group input {
118
+ padding: 8px 12px;
119
+ border: 1px solid #ddd;
120
+ border-radius: 5px;
121
+ font-size: 14px;
122
+ }
123
+
124
+ .filter-group input[type="checkbox"] {
125
+ width: 18px;
126
+ height: 18px;
127
+ cursor: pointer;
128
+ }
129
+
130
+ .main-content {
131
+ background: white;
132
+ padding: 20px;
133
+ border-radius: 10px;
134
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
135
+ overflow-x: auto;
136
+ }
137
+
138
+ .provider-section {
139
+ margin-bottom: 30px;
140
+ }
141
+
142
+ .provider-header {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 15px;
146
+ margin-bottom: 15px;
147
+ padding-bottom: 10px;
148
+ border-bottom: 2px solid #eee;
149
+ }
150
+
151
+ .provider-name {
152
+ font-size: 20px;
153
+ font-weight: 600;
154
+ color: #333;
155
+ }
156
+
157
+ .provider-status {
158
+ padding: 4px 12px;
159
+ border-radius: 20px;
160
+ font-size: 12px;
161
+ font-weight: 500;
162
+ }
163
+
164
+ .provider-status.active {
165
+ background: #dcfce7;
166
+ color: #166534;
167
+ }
168
+
169
+ .provider-status.inactive {
170
+ background: #fef3c7;
171
+ color: #92400e;
172
+ }
173
+
174
+ .provider-meta {
175
+ font-size: 13px;
176
+ color: #666;
177
+ }
178
+
179
+ table {
180
+ width: 100%;
181
+ border-collapse: collapse;
182
+ font-size: 14px;
183
+ }
184
+
185
+ th, td {
186
+ padding: 12px 15px;
187
+ text-align: left;
188
+ border-bottom: 1px solid #eee;
189
+ }
190
+
191
+ th {
192
+ background: #f8f9fa;
193
+ font-weight: 600;
194
+ color: #333;
195
+ position: sticky;
196
+ top: 0;
197
+ }
198
+
199
+ tr:hover {
200
+ background: #f8f9fa;
201
+ }
202
+
203
+ .model-name {
204
+ font-weight: 500;
205
+ color: #333;
206
+ }
207
+
208
+ .cost {
209
+ font-family: 'SFMono-Regular', Consolas, monospace;
210
+ color: #666;
211
+ }
212
+
213
+ .context {
214
+ font-family: 'SFMono-Regular', Consolas, monospace;
215
+ }
216
+
217
+ .capability {
218
+ display: inline-block;
219
+ padding: 3px 8px;
220
+ border-radius: 4px;
221
+ font-size: 11px;
222
+ font-weight: 500;
223
+ margin: 2px;
224
+ }
225
+
226
+ .capability.vision {
227
+ background: #dbeafe;
228
+ color: #1e40af;
229
+ }
230
+
231
+ .capability.tools {
232
+ background: #fce7f3;
233
+ color: #be185d;
234
+ }
235
+
236
+ .capability.caching {
237
+ background: #d1fae5;
238
+ color: #047857;
239
+ }
240
+
241
+ .capability.reasoning {
242
+ background: #fef3c7;
243
+ color: #92400e;
244
+ }
245
+
246
+ .validated-badge {
247
+ display: inline-block;
248
+ padding: 3px 8px;
249
+ border-radius: 4px;
250
+ font-size: 11px;
251
+ font-weight: 500;
252
+ }
253
+
254
+ .validated-badge.yes {
255
+ background: #dcfce7;
256
+ color: #166534;
257
+ }
258
+
259
+ .validated-badge.no {
260
+ background: #fee2e2;
261
+ color: #991b1b;
262
+ }
263
+
264
+ .loading {
265
+ text-align: center;
266
+ padding: 50px;
267
+ color: #666;
268
+ }
269
+
270
+ .loading-spinner {
271
+ display: inline-block;
272
+ width: 40px;
273
+ height: 40px;
274
+ border: 4px solid #f3f3f3;
275
+ border-top: 4px solid #667eea;
276
+ border-radius: 50%;
277
+ animation: spin 1s linear infinite;
278
+ margin-bottom: 15px;
279
+ }
280
+
281
+ @keyframes spin {
282
+ 0% { transform: rotate(0deg); }
283
+ 100% { transform: rotate(360deg); }
284
+ }
285
+
286
+ .error-message {
287
+ background: #fee2e2;
288
+ color: #991b1b;
289
+ padding: 15px 20px;
290
+ border-radius: 8px;
291
+ margin-bottom: 20px;
292
+ }
293
+
294
+ .no-models {
295
+ color: #666;
296
+ font-style: italic;
297
+ padding: 20px;
298
+ text-align: center;
299
+ }
300
+ </style>
301
+ </head>
302
+ <body>
303
+ <div class="container">
304
+ <div class="header">
305
+ <h1>
306
+ <img src="/static/stratifyai_wide_logo.png" alt="StratifyAI Logo">
307
+ Model Catalog
308
+ </h1>
309
+ <a href="/" class="back-btn">← Back to Chat</a>
310
+ </div>
311
+
312
+ <div class="summary-bar" id="summary-bar">
313
+ <div class="summary-item">
314
+ <span class="label">Total Models:</span>
315
+ <span class="value" id="total-models">-</span>
316
+ </div>
317
+ <div class="summary-item">
318
+ <span class="label">Providers:</span>
319
+ <span class="value" id="total-providers">-</span>
320
+ </div>
321
+ <div class="summary-item">
322
+ <span class="label">Active Providers:</span>
323
+ <span class="value" id="active-providers">-</span>
324
+ </div>
325
+ </div>
326
+
327
+ <div class="filters">
328
+ <div class="filter-group">
329
+ <label for="provider-filter">Provider:</label>
330
+ <select id="provider-filter">
331
+ <option value="">All Providers</option>
332
+ </select>
333
+ </div>
334
+ <div class="filter-group">
335
+ <label for="capability-filter">Capability:</label>
336
+ <select id="capability-filter">
337
+ <option value="">All Capabilities</option>
338
+ <option value="vision">Vision</option>
339
+ <option value="tools">Tools</option>
340
+ <option value="caching">Caching</option>
341
+ <option value="reasoning">Reasoning</option>
342
+ </select>
343
+ </div>
344
+ <div class="filter-group">
345
+ <input type="checkbox" id="active-only">
346
+ <label for="active-only">Active providers only</label>
347
+ </div>
348
+ <div class="filter-group">
349
+ <input type="checkbox" id="validated-only">
350
+ <label for="validated-only">Validated models only</label>
351
+ </div>
352
+ <div class="filter-group">
353
+ <button id="reset-filters" style="background: #6b7280; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; font-weight: 500;">🔄 Reset Filters</button>
354
+ </div>
355
+ </div>
356
+
357
+ <div class="main-content" id="main-content">
358
+ <div class="loading">
359
+ <div class="loading-spinner"></div>
360
+ <p>Loading model catalog...</p>
361
+ </div>
362
+ </div>
363
+ </div>
364
+
365
+ <script>
366
+ const API_BASE = 'http://localhost:8080';
367
+ let allData = null;
368
+
369
+ // Format cost for display
370
+ function formatCost(cost) {
371
+ if (cost === 0 || cost === null || cost === undefined) return '-';
372
+ if (cost < 0.01) return `$${cost.toFixed(4)}`;
373
+ return `$${cost.toFixed(2)}`;
374
+ }
375
+
376
+ // Format context window for display
377
+ function formatContext(context) {
378
+ if (!context) return '-';
379
+ if (context >= 1000000) return `${(context / 1000000).toFixed(1)}M`;
380
+ if (context >= 1000) return `${(context / 1000).toFixed(0)}K`;
381
+ return context.toString();
382
+ }
383
+
384
+ // Capitalize provider name
385
+ function capitalizeProvider(provider) {
386
+ const special = {
387
+ 'openai': 'OpenAI',
388
+ 'anthropic': 'Anthropic',
389
+ 'google': 'Google',
390
+ 'deepseek': 'DeepSeek',
391
+ 'groq': 'Groq',
392
+ 'grok': 'Grok (X.AI)',
393
+ 'ollama': 'Ollama',
394
+ 'openrouter': 'OpenRouter',
395
+ 'bedrock': 'AWS Bedrock'
396
+ };
397
+ return special[provider] || provider.charAt(0).toUpperCase() + provider.slice(1);
398
+ }
399
+
400
+ // Build capabilities badges
401
+ function buildCapabilities(model) {
402
+ let badges = [];
403
+ if (model.supports_vision) badges.push('<span class="capability vision">👁️ Vision</span>');
404
+ if (model.supports_tools) badges.push('<span class="capability tools">🔧 Tools</span>');
405
+ if (model.supports_caching) badges.push('<span class="capability caching">💾 Caching</span>');
406
+ if (model.reasoning_model) badges.push('<span class="capability reasoning">🧠 Reasoning</span>');
407
+ return badges.length > 0 ? badges.join('') : '<span style="color: #999;">-</span>';
408
+ }
409
+
410
+ // Render models table
411
+ function renderModels() {
412
+ if (!allData) return;
413
+
414
+ const providerFilter = document.getElementById('provider-filter').value;
415
+ const capabilityFilter = document.getElementById('capability-filter').value;
416
+ const activeOnly = document.getElementById('active-only').checked;
417
+ const validatedOnly = document.getElementById('validated-only').checked;
418
+
419
+ const container = document.getElementById('main-content');
420
+ let html = '';
421
+
422
+ // Get providers in order
423
+ const providerOrder = ['openai', 'anthropic', 'google', 'deepseek', 'groq', 'grok', 'ollama', 'openrouter', 'bedrock'];
424
+ const providers = providerFilter ? [providerFilter] : providerOrder;
425
+
426
+ for (const provider of providers) {
427
+ const providerData = allData.providers[provider];
428
+ if (!providerData) continue;
429
+
430
+ // Filter by active status
431
+ if (activeOnly && !providerData.active) continue;
432
+
433
+ // Filter models
434
+ let models = providerData.models || [];
435
+
436
+ if (capabilityFilter) {
437
+ models = models.filter(m => {
438
+ switch (capabilityFilter) {
439
+ case 'vision': return m.supports_vision;
440
+ case 'tools': return m.supports_tools;
441
+ case 'caching': return m.supports_caching;
442
+ case 'reasoning': return m.reasoning_model;
443
+ default: return true;
444
+ }
445
+ });
446
+ }
447
+
448
+ if (validatedOnly) {
449
+ models = models.filter(m => m.validated);
450
+ }
451
+
452
+ if (models.length === 0) continue;
453
+
454
+ // Build provider section
455
+ html += `
456
+ <div class="provider-section">
457
+ <div class="provider-header">
458
+ <span class="provider-name">${capitalizeProvider(provider)}</span>
459
+ <span class="provider-status ${providerData.active ? 'active' : 'inactive'}">
460
+ ${providerData.active ? '✓ Active' : '⚠ No API Key'}
461
+ </span>
462
+ <span class="provider-meta">${models.length} model${models.length !== 1 ? 's' : ''}</span>
463
+ ${providerData.validation_error ? `<span class="provider-meta" style="color: #dc2626;">⚠ ${providerData.validation_error}</span>` : ''}
464
+ </div>
465
+ <table>
466
+ <thead>
467
+ <tr>
468
+ <th>Model</th>
469
+ <th>Context</th>
470
+ <th>Cost (Input)</th>
471
+ <th>Cost (Output)</th>
472
+ <th>Capabilities</th>
473
+ <th>Validated</th>
474
+ </tr>
475
+ </thead>
476
+ <tbody>
477
+ `;
478
+
479
+ for (const model of models) {
480
+ html += `
481
+ <tr>
482
+ <td class="model-name">${model.id}</td>
483
+ <td class="context">${formatContext(model.context_window)}</td>
484
+ <td class="cost">${formatCost(model.cost_input)}/1M</td>
485
+ <td class="cost">${formatCost(model.cost_output)}/1M</td>
486
+ <td>${buildCapabilities(model)}</td>
487
+ <td>
488
+ <span class="validated-badge ${model.validated ? 'yes' : 'no'}">
489
+ ${model.validated ? '✓ Yes' : '✗ No'}
490
+ </span>
491
+ </td>
492
+ </tr>
493
+ `;
494
+ }
495
+
496
+ html += `
497
+ </tbody>
498
+ </table>
499
+ </div>
500
+ `;
501
+ }
502
+
503
+ if (!html) {
504
+ html = '<div class="no-models">No models match the current filters.</div>';
505
+ }
506
+
507
+ container.innerHTML = html;
508
+ }
509
+
510
+ // Load data
511
+ async function loadData() {
512
+ try {
513
+ const response = await fetch(`${API_BASE}/api/all-models`);
514
+ if (!response.ok) throw new Error('Failed to load models');
515
+
516
+ allData = await response.json();
517
+
518
+ // Update summary
519
+ document.getElementById('total-models').textContent = allData.summary.total_models;
520
+ document.getElementById('total-providers').textContent = allData.summary.total_providers;
521
+ document.getElementById('active-providers').textContent = allData.summary.active_providers;
522
+
523
+ // Populate provider filter
524
+ const providerFilter = document.getElementById('provider-filter');
525
+ const providerOrder = ['openai', 'anthropic', 'google', 'deepseek', 'groq', 'grok', 'ollama', 'openrouter', 'bedrock'];
526
+ for (const provider of providerOrder) {
527
+ if (allData.providers[provider]) {
528
+ const option = document.createElement('option');
529
+ option.value = provider;
530
+ option.textContent = capitalizeProvider(provider);
531
+ providerFilter.appendChild(option);
532
+ }
533
+ }
534
+
535
+ // Render models
536
+ renderModels();
537
+
538
+ } catch (error) {
539
+ document.getElementById('main-content').innerHTML = `
540
+ <div class="error-message">
541
+ ❌ Error loading model catalog: ${error.message}
542
+ </div>
543
+ `;
544
+ }
545
+ }
546
+
547
+ // Reset filters
548
+ function resetFilters() {
549
+ document.getElementById('provider-filter').value = '';
550
+ document.getElementById('capability-filter').value = '';
551
+ document.getElementById('active-only').checked = false;
552
+ document.getElementById('validated-only').checked = false;
553
+ renderModels();
554
+ }
555
+
556
+ // Event listeners for filters
557
+ document.getElementById('provider-filter').addEventListener('change', renderModels);
558
+ document.getElementById('capability-filter').addEventListener('change', renderModels);
559
+ document.getElementById('active-only').addEventListener('change', renderModels);
560
+ document.getElementById('validated-only').addEventListener('change', renderModels);
561
+ document.getElementById('reset-filters').addEventListener('click', resetFilters);
562
+
563
+ // Initialize
564
+ loadData();
565
+ </script>
566
+ </body>
567
+ </html>
Binary file
Binary file
Binary file