KiWA001 commited on
Commit
75ba686
·
1 Parent(s): b349581

fix: make root index full dashboard, add stripped public docs

Browse files
Files changed (3) hide show
  1. main.py +6 -2
  2. static/docs.html +232 -1
  3. static/public_docs.html +664 -0
main.py CHANGED
@@ -276,9 +276,13 @@ async def deep_research_endpoint(request: Request):
276
 
277
  @app.get("/qazmlpdocs", include_in_schema=False)
278
  async def qazmlp_docs():
279
- """Serve the Secured Dashboard (with Stats)."""
280
- return FileResponse("static/qazmlpdocs.html")
281
 
 
 
 
 
282
 
283
  @app.get("/", tags=["Dashboard"])
284
  async def root():
 
276
 
277
  @app.get("/qazmlpdocs", include_in_schema=False)
278
  async def qazmlp_docs():
279
+ """Serve the Secured Dashboard (Alias to Root)."""
280
+ return FileResponse("static/docs.html")
281
 
282
+ @app.get("/docs/public", include_in_schema=False)
283
+ async def public_docs_page():
284
+ """Serve the Stripped Public Documentation."""
285
+ return FileResponse("static/public_docs.html")
286
 
287
  @app.get("/", tags=["Dashboard"])
288
  async def root():
static/docs.html CHANGED
@@ -451,6 +451,15 @@
451
 
452
  <button class="demo-btn" onclick="runDemo('chat-basic')">Run Request ▶</button>
453
  <div id="chat-basic-status" class="demo-status"></div>
 
 
 
 
 
 
 
 
 
454
  <div id="chat-basic-res" class="demo-response"></div>
455
  </div>
456
  </div>
@@ -522,7 +531,60 @@
522
  </div>
523
  </div>
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
 
527
  </div>
528
 
@@ -558,11 +620,13 @@
558
  if (type === 'chat-basic') {
559
  // Simple Chat
560
  const inputVal = document.getElementById('chat-basic-input').value;
 
 
561
  if (!inputVal) { alert("Please enter a message"); return; }
562
 
563
  url = '/v1/chat/completions';
564
  body = {
565
- model: "gemini-3-flash",
566
  messages: [{ role: "user", content: inputVal }]
567
  };
568
  }
@@ -647,6 +711,173 @@
647
  }
648
  }
649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
  </script>
651
 
652
  </body>
 
451
 
452
  <button class="demo-btn" onclick="runDemo('chat-basic')">Run Request ▶</button>
453
  <div id="chat-basic-status" class="demo-status"></div>
454
+
455
+ <select id="chat-basic-model" class="demo-select" style="margin-top:10px;">
456
+ <option value="gemini-3-flash">gemini-3-flash (Default)</option>
457
+ <option value="gpt-4o">gpt-4o</option>
458
+ <option value="gpt-4o-mini">gpt-4o-mini</option>
459
+ <option value="glm-4">glm-4</option>
460
+ <option value="mistral-large">mistral-large</option>
461
+ </select>
462
+
463
  <div id="chat-basic-res" class="demo-response"></div>
464
  </div>
465
  </div>
 
531
  </div>
532
  </div>
533
 
534
+ <!-- Live Ranking Container -->
535
+ <div class="endpoint-block">
536
+ <div class="endpoint-header">
537
+ <div class="endpoint-title">
538
+ <h3>🏆 Live Model Ranking (Time-Weighted)</h3>
539
+ <p>Real-time performance tracked by the engine (Speed & Reliability).</p>
540
+ </div>
541
+ <span class="method-badge LIVE">LIVE UPDATES</span>
542
+ </div>
543
+ <div class="endpoint-body full-width">
544
+ <div style="padding: 24px; width: 100%; overflow-x: auto;">
545
+ <table class="param-table" id="rankings-table">
546
+ <thead>
547
+ <tr>
548
+ <th width="50">#</th>
549
+ <th>Model ID</th>
550
+ <th>Score</th>
551
+ <th>Avg Time</th>
552
+ <th>Success</th>
553
+ <th>Fail</th>
554
+ </tr>
555
+ </thead>
556
+ <tbody>
557
+ <tr>
558
+ <td colspan="7" style="text-align:center; padding: 20px;">Loading live stats...</td>
559
+ </tr>
560
+ </tbody>
561
+ </table>
562
+ </div>
563
+ </div>
564
+ </div>
565
 
566
+ <!-- Network Analytics (Graphs) -->
567
+ <div class="endpoint-block">
568
+ <div class="endpoint-header">
569
+ <div class="endpoint-title">
570
+ <h3>Network Analytics</h3>
571
+ <p>Live Latency vs. Reliability & Provider Distribution.</p>
572
+ </div>
573
+ </div>
574
+ <div class="endpoint-body" id="analytics-body"
575
+ style="display: flex; flex-direction: column; gap: 20px; padding: 20px;">
576
+ <div style="background: var(--bg-secondary); padding: 15px; border-radius: 8px; flex: 1;">
577
+ <h4 style="margin-bottom:10px; color:var(--text-secondary); font-size:12px; font-weight:700;">SPEED
578
+ vs RELIABILITY</h4>
579
+ <canvas id="scatterChart"></canvas>
580
+ </div>
581
+ <div style="background: var(--bg-secondary); padding: 15px; border-radius: 8px; flex: 1;">
582
+ <h4 style="margin-bottom:10px; color:var(--text-secondary); font-size:12px; font-weight:700;">
583
+ PROVIDER DISTRIBUTION</h4>
584
+ <canvas id="pieChart"></canvas>
585
+ </div>
586
+ </div>
587
+ </div>
588
 
589
  </div>
590
 
 
620
  if (type === 'chat-basic') {
621
  // Simple Chat
622
  const inputVal = document.getElementById('chat-basic-input').value;
623
+ const modelVal = document.getElementById('chat-basic-model').value || "gemini-3-flash";
624
+
625
  if (!inputVal) { alert("Please enter a message"); return; }
626
 
627
  url = '/v1/chat/completions';
628
  body = {
629
+ model: modelVal,
630
  messages: [{ role: "user", content: inputVal }]
631
  };
632
  }
 
711
  }
712
  }
713
 
714
+ // --- Live Ranking Logic ---
715
+
716
+ let availableModelsSet = new Set();
717
+ let scatterChart, pieChart;
718
+
719
+ async function fetchRankingStats() {
720
+ try {
721
+ const t = new Date().getTime();
722
+ const [statsRes, modelsRes] = await Promise.all([
723
+ fetch(`/admin/stats?t=${t}`),
724
+ fetch(`/models?t=${t}`)
725
+ ]);
726
+
727
+ if (!statsRes.ok) {
728
+ throw new Error(`Stats HTTP ${statsRes.status}`);
729
+ }
730
+ const stats = await statsRes.json();
731
+
732
+ availableModelsSet.clear();
733
+ if (modelsRes.ok) {
734
+ const data = await modelsRes.json();
735
+ // Robust handling: Support both array and object wrapper
736
+ const modelsList = Array.isArray(data) ? data : (data.models || []);
737
+
738
+ if (Array.isArray(modelsList)) {
739
+ modelsList.forEach(m => availableModelsSet.add(`${m.provider}/${m.model}`));
740
+ } else {
741
+ console.error("Expected array but got:", data);
742
+ }
743
+ }
744
+
745
+ renderDashboard(stats);
746
+ } catch (e) {
747
+ console.error("Failed to fetch ranking", e);
748
+ const tbody = document.querySelector('#rankings-table tbody');
749
+ // Only replace if we haven't rendered data yet (or if it's the loading state)
750
+ if (tbody.innerHTML.includes('Loading')) {
751
+ tbody.innerHTML = `<tr><td colspan="7" style="color:#ef4444; text-align:center; padding: 20px;">
752
+ Failed to load stats: ${e.message}<br>
753
+ <small style="opacity:0.7">If on Hugging Face, check "Logs" tab for backend errors.</small>
754
+ </td></tr>`;
755
+ }
756
+ }
757
+ }
758
+
759
+ function calculateScore(s, f, timeMs, cf) {
760
+ let base = s - (f * 2);
761
+ let penalty = (timeMs || 0) / 1000.0;
762
+ let score = base - penalty;
763
+ if (cf >= 3) return score - 100000;
764
+ return score;
765
+ }
766
+
767
+ function renderDashboard(data) {
768
+ let rows = [];
769
+ let providerCounts = {};
770
+ let scatterData = [];
771
+
772
+ for (let [key, val] of Object.entries(data)) {
773
+ let score = calculateScore(val.success, val.failure, val.avg_time_ms, val.consecutive_failures);
774
+ rows.push({ id: key, ...val, score: score });
775
+
776
+ // Provider stats for Pie
777
+ let prov = key.split('/')[0];
778
+ providerCounts[prov] = (providerCounts[prov] || 0) + val.success;
779
+
780
+ // Scatter Data
781
+ scatterData.push({
782
+ x: val.avg_time_ms || 0,
783
+ y: score,
784
+ id: key
785
+ });
786
+ }
787
+ rows.sort((a, b) => b.score - a.score);
788
+
789
+ // Render Table
790
+ const tbody = document.querySelector('#rankings-table tbody');
791
+ tbody.innerHTML = '';
792
+
793
+ if (rows.length === 0) {
794
+ tbody.innerHTML = '<tr><td colspan="7" style="text-align:center; padding: 20px;">No stats available yet. Make a request!</td></tr>';
795
+ } else {
796
+ rows.forEach((row, index) => {
797
+ const tr = document.createElement('tr');
798
+ let scoreClass = row.score > 0 ? 'score-good' : 'score-bad';
799
+ let timeStr = row.avg_time_ms ? Math.round(row.avg_time_ms) + 'ms' : '-';
800
+
801
+ tr.innerHTML = `
802
+ <td><span class="rank-badge">${index + 1}</span></td>
803
+ <td><b>${row.id}</b></td>
804
+ <td class="${scoreClass}">${row.score.toFixed(2)}</td>
805
+ <td>${timeStr}</td>
806
+ <td>${row.success}</td>
807
+ <td>${row.failure}</td>
808
+ `;
809
+ tbody.appendChild(tr);
810
+ });
811
+ }
812
+
813
+ // Render Charts
814
+ updateScatterChart(scatterData);
815
+ updatePieChart(providerCounts);
816
+ }
817
+
818
+ function updateScatterChart(data) {
819
+ const ctx = document.getElementById('scatterChart').getContext('2d');
820
+ if (scatterChart) {
821
+ scatterChart.data.datasets[0].data = data;
822
+ scatterChart.update('none');
823
+ return;
824
+ }
825
+ const validData = data.filter(d => d.x > 0);
826
+ scatterChart = new Chart(ctx, {
827
+ type: 'scatter',
828
+ data: {
829
+ datasets: [{
830
+ label: 'Models',
831
+ data: validData,
832
+ backgroundColor: '#8b5cf6',
833
+ borderColor: '#8b5cf6',
834
+ }]
835
+ },
836
+ options: {
837
+ responsive: true,
838
+ maintainAspectRatio: false,
839
+ animation: { duration: 1000 },
840
+ scales: {
841
+ x: { type: 'linear', position: 'bottom', title: { display: true, text: 'Time (ms)', color: '#6b6b80' }, grid: { color: '#2a2a3a' } },
842
+ y: { title: { display: true, text: 'Score', color: '#6b6b80' }, grid: { color: '#2a2a3a' } }
843
+ },
844
+ plugins: {
845
+ legend: { display: false },
846
+ tooltip: { callbacks: { label: (ctx) => ctx.raw.id + ': ' + ctx.raw.y.toFixed(2) } }
847
+ }
848
+ }
849
+ });
850
+ }
851
+
852
+ function updatePieChart(counts) {
853
+ if (window.pieChartRendered) return;
854
+ const ctx = document.getElementById('pieChart').getContext('2d');
855
+ window.pieChartRendered = true;
856
+ pieChart = new Chart(ctx, {
857
+ type: 'doughnut',
858
+ data: {
859
+ labels: Object.keys(counts),
860
+ datasets: [{
861
+ data: Object.values(counts),
862
+ backgroundColor: ['#8b5cf6', '#22c55e', '#f59e0b', '#ef4444', '#ec4899', '#3b82f6'],
863
+ borderWidth: 0
864
+ }]
865
+ },
866
+ options: {
867
+ responsive: true,
868
+ maintainAspectRatio: false,
869
+ animation: { duration: 1000 },
870
+ plugins: {
871
+ legend: { position: 'right', labels: { color: '#9898aa', font: { size: 10 } } }
872
+ }
873
+ }
874
+ });
875
+ }
876
+
877
+ // Init Live Ranking
878
+ fetchRankingStats();
879
+ setInterval(fetchRankingStats, 5000);
880
+
881
  </script>
882
 
883
  </body>
static/public_docs.html ADDED
@@ -0,0 +1,664 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>K-AI API — Feel free to AI</title>
8
+ <meta name="description" content="K-AI API — Free AI proxy API. No signup, no API keys. Feel free to AI.">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap"
12
+ rel="stylesheet">
13
+ <!-- Chart.js -->
14
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
15
+ <style>
16
+ :root {
17
+ --bg-primary: #0a0a0f;
18
+ --bg-secondary: #12121a;
19
+ --bg-card: #16161f;
20
+ --bg-card-hover: #1c1c28;
21
+ --border: #2a2a3a;
22
+ --text-primary: #f0f0f5;
23
+ --text-secondary: #9898aa;
24
+ --text-muted: #6b6b80;
25
+ --accent: #6366f1;
26
+ --accent-hover: #818cf8;
27
+ --accent-glow: rgba(99, 102, 241, 0.15);
28
+ --gradient-hero: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
29
+ --radius-lg: 16px;
30
+ --radius-sm: 8px;
31
+
32
+ /* Ranking Colors */
33
+ --success: #22c55e;
34
+ --error: #ef4444;
35
+ }
36
+
37
+ * {
38
+ margin: 0;
39
+ padding: 0;
40
+ box-sizing: border-box;
41
+ }
42
+
43
+ body {
44
+ font-family: 'Inter', -apple-system, sans-serif;
45
+ background: var(--bg-primary);
46
+ color: var(--text-primary);
47
+ line-height: 1.6;
48
+ }
49
+
50
+ .container {
51
+ max-width: 1000px;
52
+ margin: 0 auto;
53
+ padding: 0 24px;
54
+ }
55
+
56
+ /* ─── Hero ─── */
57
+ .hero {
58
+ text-align: center;
59
+ padding: 100px 0 80px;
60
+ background: radial-gradient(circle at top center, rgba(99, 102, 241, 0.08) 0%, transparent 70%);
61
+ }
62
+
63
+ .hero h1 {
64
+ font-size: 72px;
65
+ font-weight: 900;
66
+ letter-spacing: -3px;
67
+ background: var(--gradient-hero);
68
+ -webkit-background-clip: text;
69
+ background-clip: text;
70
+ -webkit-text-fill-color: transparent;
71
+ margin-bottom: 8px;
72
+ }
73
+
74
+ .hero .motto {
75
+ font-size: 24px;
76
+ font-weight: 300;
77
+ letter-spacing: 2px;
78
+ color: var(--text-secondary);
79
+ text-transform: uppercase;
80
+ }
81
+
82
+ .hero .desc {
83
+ margin-top: 24px;
84
+ font-size: 18px;
85
+ color: var(--text-muted);
86
+ max-width: 600px;
87
+ margin-left: auto;
88
+ margin-right: auto;
89
+ }
90
+
91
+ /* ─── Section ─── */
92
+ section {
93
+ padding: 60px 0;
94
+ border-top: 1px solid var(--bg-secondary);
95
+ }
96
+
97
+ .section-title {
98
+ font-size: 28px;
99
+ font-weight: 700;
100
+ margin-bottom: 40px;
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 12px;
104
+ }
105
+
106
+ .section-title .icon {
107
+ width: 32px;
108
+ height: 32px;
109
+ background: var(--bg-card);
110
+ border-radius: 8px;
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+ font-size: 16px;
115
+ }
116
+
117
+ /* ─── Endpoint Block ─── */
118
+ .endpoint-block {
119
+ background: var(--bg-card);
120
+ border: 1px solid var(--border);
121
+ border-radius: var(--radius-lg);
122
+ overflow: hidden;
123
+ margin-bottom: 40px;
124
+ }
125
+
126
+ .endpoint-header {
127
+ padding: 24px;
128
+ border-bottom: 1px solid var(--border);
129
+ display: flex;
130
+ justify-content: space-between;
131
+ align-items: flex-start;
132
+ }
133
+
134
+ .endpoint-title h3 {
135
+ font-size: 20px;
136
+ font-weight: 700;
137
+ margin-bottom: 4px;
138
+ }
139
+
140
+ .endpoint-title p {
141
+ color: var(--text-secondary);
142
+ font-size: 14px;
143
+ }
144
+
145
+ .method-badge {
146
+ font-family: 'JetBrains Mono', monospace;
147
+ font-size: 13px;
148
+ font-weight: 700;
149
+ padding: 4px 10px;
150
+ border-radius: 6px;
151
+ background: rgba(99, 102, 241, 0.2);
152
+ color: #818cf8;
153
+ }
154
+
155
+ .method-badge.GET {
156
+ background: rgba(34, 197, 94, 0.2);
157
+ color: #4ade80;
158
+ }
159
+
160
+ .method-badge.LIVE {
161
+ background: rgba(245, 158, 11, 0.2);
162
+ color: #fbbf24;
163
+ }
164
+
165
+ .endpoint-body {
166
+ display: grid;
167
+ grid-template-columns: 1.2fr 0.8fr;
168
+ }
169
+
170
+ .endpoint-body.full-width {
171
+ grid-template-columns: 1fr;
172
+ }
173
+
174
+ @media (max-width: 800px) {
175
+
176
+ .endpoint-body,
177
+ #analytics-body {
178
+ flex-direction: column !important;
179
+ display: flex !important;
180
+ }
181
+ }
182
+
183
+ .endpoint-docs {
184
+ padding: 24px;
185
+ border-right: 1px solid var(--border);
186
+ }
187
+
188
+ .endpoint-demo {
189
+ padding: 24px;
190
+ background: var(--bg-secondary);
191
+ }
192
+
193
+ .param-table {
194
+ width: 100%;
195
+ border-collapse: collapse;
196
+ font-size: 14px;
197
+ margin-top: 16px;
198
+ }
199
+
200
+ .param-table th {
201
+ text-align: left;
202
+ color: var(--text-muted);
203
+ font-weight: 600;
204
+ padding-bottom: 8px;
205
+ border-bottom: 1px solid var(--border);
206
+ }
207
+
208
+ .param-table td {
209
+ padding: 12px 0;
210
+ /* More padding for ranking */
211
+ border-bottom: 1px solid var(--border);
212
+ color: var(--text-secondary);
213
+ font-family: 'JetBrains Mono', monospace;
214
+ font-size: 13px;
215
+ }
216
+
217
+ .param-table tr:last-child td {
218
+ border-bottom: none;
219
+ }
220
+
221
+ .param-name {
222
+ font-family: 'JetBrains Mono', monospace;
223
+ color: var(--accent-hover);
224
+ }
225
+
226
+ .param-desc {
227
+ color: var(--text-secondary);
228
+ }
229
+
230
+ .param-req {
231
+ color: var(--text-muted);
232
+ font-size: 12px;
233
+ margin-left: 6px;
234
+ }
235
+
236
+ /* ─── Interactive Demo ─── */
237
+ .demo-label {
238
+ font-size: 11px;
239
+ font-weight: 700;
240
+ text-transform: uppercase;
241
+ letter-spacing: 1px;
242
+ color: var(--text-muted);
243
+ margin-bottom: 12px;
244
+ display: block;
245
+ }
246
+
247
+ .demo-input {
248
+ width: 100%;
249
+ background: var(--bg-input);
250
+ border: 1px solid var(--border);
251
+ border-radius: var(--radius-sm);
252
+ padding: 10px;
253
+ color: var(--text-primary);
254
+ font-family: 'JetBrains Mono', monospace;
255
+ font-size: 13px;
256
+ margin-bottom: 12px;
257
+ }
258
+
259
+ .demo-input:focus {
260
+ outline: none;
261
+ border-color: var(--accent);
262
+ }
263
+
264
+ .demo-select {
265
+ width: 100%;
266
+ background: var(--bg-card);
267
+ color: var(--text-primary);
268
+ padding: 10px;
269
+ margin-bottom: 12px;
270
+ border-radius: var(--radius-sm);
271
+ border: 1px solid var(--border);
272
+ }
273
+
274
+ .demo-btn {
275
+ width: 100%;
276
+ padding: 8px 12px;
277
+ /* Reduced padding */
278
+ background: var(--accent);
279
+ color: white;
280
+ border: none;
281
+ border-radius: var(--radius-sm);
282
+ font-weight: 600;
283
+ cursor: pointer;
284
+ transition: all 0.2s;
285
+ }
286
+
287
+ .demo-btn:hover {
288
+ background: var(--accent-hover);
289
+ }
290
+
291
+ .demo-btn:disabled {
292
+ opacity: 0.5;
293
+ cursor: wait;
294
+ }
295
+
296
+ .demo-response {
297
+ margin-top: 16px;
298
+ background: #0d0d14;
299
+ border: 1px solid var(--border);
300
+ border-radius: var(--radius-sm);
301
+ padding: 12px;
302
+ font-family: 'JetBrains Mono', monospace;
303
+ font-size: 11px;
304
+ /* Reduced font size */
305
+ color: var(--text-secondary);
306
+ white-space: pre-wrap;
307
+ display: none;
308
+ max-height: 500px;
309
+ /* Scrollable */
310
+ /* Scrollable */
311
+ overflow-y: auto;
312
+ }
313
+
314
+ .demo-response pre {
315
+ white-space: pre-wrap;
316
+ word-wrap: break-word;
317
+ margin: 0;
318
+ font-family: inherit;
319
+ }
320
+
321
+ .demo-status {
322
+ margin-top: 12px;
323
+ font-weight: 600;
324
+ font-size: 13px;
325
+ display: none;
326
+ }
327
+
328
+ .demo-response.visible {
329
+ display: block;
330
+ }
331
+
332
+ .demo-response.success {
333
+ border-color: rgba(34, 197, 94, 0.3);
334
+ color: #4ade80;
335
+ }
336
+
337
+ .demo-response.error {
338
+ border-color: rgba(239, 68, 68, 0.3);
339
+ color: #f87171;
340
+ }
341
+
342
+ /* ─── Ranking Badges & Styles ─── */
343
+ .rank-badge {
344
+ display: inline-block;
345
+ width: 24px;
346
+ height: 24px;
347
+ line-height: 24px;
348
+ text-align: center;
349
+ border-radius: 50%;
350
+ background: var(--border);
351
+ color: var(--text-muted);
352
+ font-weight: bold;
353
+ font-size: 11px;
354
+ }
355
+
356
+ tr:nth-child(1) .rank-badge {
357
+ background: #Eab308;
358
+ color: #000;
359
+ }
360
+
361
+ /* Gold */
362
+ tr:nth-child(2) .rank-badge {
363
+ background: #94a3b8;
364
+ color: #000;
365
+ }
366
+
367
+ /* Silver */
368
+ tr:nth-child(3) .rank-badge {
369
+ background: #b45309;
370
+ color: #fff;
371
+ }
372
+
373
+ /* Bronze */
374
+
375
+ .score-good {
376
+ color: var(--success);
377
+ }
378
+
379
+ .score-bad {
380
+ color: var(--error);
381
+ }
382
+
383
+ /* Charts */
384
+ canvas {
385
+ max-height: 250px;
386
+ width: 100%;
387
+ }
388
+
389
+ /* ─── Footer ─── */
390
+ footer {
391
+ text-align: center;
392
+ padding: 40px;
393
+ color: var(--text-muted);
394
+ font-size: 14px;
395
+ }
396
+ </style>
397
+ </head>
398
+
399
+ <body>
400
+
401
+ <div class="hero">
402
+ <div class="container">
403
+ <h1>K-AI API</h1>
404
+ <p class="motto">Feel free to AI</p>
405
+ <p class="desc">The completely free AI proxy. No signup. No keys. Just code.</p>
406
+ </div>
407
+ </div>
408
+
409
+ <div class="container">
410
+
411
+ <!-- POST /chat (Basic) -->
412
+ <div class="endpoint-block">
413
+ <div class="endpoint-header">
414
+ <div class="endpoint-title">
415
+ <h3>Chat Completion</h3>
416
+ <p>Send a message and get a response from the best available AI.</p>
417
+ </div>
418
+ <span class="method-badge">POST /v1/chat/completions</span>
419
+ </div>
420
+ <div class="endpoint-body">
421
+ <div class="endpoint-docs">
422
+ <span class="demo-label">Parameters</span>
423
+ <table class="param-table">
424
+ <tr>
425
+ <td><span class="param-name">message</span></td>
426
+ <td><span class="param-desc">Your prompt</span><span class="param-req">(required)</span>
427
+ </td>
428
+ </tr>
429
+ </table>
430
+ <br>
431
+ <span class="demo-label">Example Request</span>
432
+ <div class="demo-response visible" style="color: #a78bfa;">
433
+ curl https://kiwa001-kai-api-gateway.hf.space/v1/chat/completions \
434
+ -H "Content-Type: application/json" \
435
+ -H "Authorization: Bearer sk-kai-demo-public" \
436
+ -d '{"model": "gemini-3-flash", "messages": [{"role": "user", "content": "Hello!"}]}'
437
+ </div>
438
+ </div>
439
+ <div class="endpoint-demo">
440
+ <span class="demo-label">Try It Live</span>
441
+ <textarea id="chat-basic-input" class="demo-input" rows="3"
442
+ placeholder='{"message": "What is AI?"}'>What is the capital of France?</textarea>
443
+
444
+ <select id="chat-basic-model" class="demo-select" style="margin-top:10px;">
445
+ <option value="gemini-3-flash">gemini-3-flash (Default)</option>
446
+ <option value="gpt-4o">gpt-4o</option>
447
+ <option value="gpt-4o-mini">gpt-4o-mini</option>
448
+ <option value="glm-4">glm-4</option>
449
+ <option value="mistral-large">mistral-large</option>
450
+ </select>
451
+
452
+ <button class="demo-btn" onclick="runDemo('chat-basic')">Run Request ▶</button>
453
+ <div id="chat-basic-status" class="demo-status"></div>
454
+
455
+ <select id="chat-basic-model" class="demo-select" style="margin-top:10px;">
456
+ <option value="gemini-3-flash">gemini-3-flash (Default)</option>
457
+ <option value="gpt-4o">gpt-4o</option>
458
+ <option value="gpt-4o-mini">gpt-4o-mini</option>
459
+ <option value="glm-4">glm-4</option>
460
+ <option value="mistral-large">mistral-large</option>
461
+ </select>
462
+
463
+ <div id="chat-basic-res" class="demo-response"></div>
464
+ </div>
465
+ </div>
466
+ </div>
467
+
468
+
469
+
470
+
471
+
472
+
473
+ <!-- GET /models -->
474
+ <div class="endpoint-block">
475
+ <div class="endpoint-header">
476
+ <div class="endpoint-title">
477
+ <h3>List Models</h3>
478
+ <p>Get all currently available AI models.</p>
479
+ </div>
480
+ <span class="method-badge GET">GET /models</span>
481
+ </div>
482
+ <div class="endpoint-body">
483
+ <div class="endpoint-docs">
484
+ <p style="color:var(--text-secondary); font-size:14px;">Returns a JSON list of all models supported
485
+ by the API, ranked by quality.</p>
486
+ </div>
487
+ <div class="endpoint-demo">
488
+ <span class="demo-label">Try It Live</span>
489
+ <button class="demo-btn" onclick="runDemo('models')">Fetch Models ▶</button>
490
+ <div id="models-res" class="demo-response"></div>
491
+ </div>
492
+ </div>
493
+ </div>
494
+
495
+
496
+ <!-- POST /search & /deep_research -->
497
+ <div class="endpoint-block">
498
+ <div class="endpoint-header">
499
+ <div class="endpoint-title">
500
+ <h3>Web Search & Research</h3>
501
+ <p>Reverse-engineered web search and deep content gathering. No API keys required.</p>
502
+ </div>
503
+ <span class="method-badge">POST /search</span>
504
+ </div>
505
+ <div class="endpoint-body">
506
+ <div class="endpoint-docs">
507
+ <span class="demo-label">Endpoints</span>
508
+ <ul style="color:var(--text-secondary); font-size:13px; margin-left:18px; margin-bottom:10px;">
509
+ <li><code>/search</code>: Standard web search (Links)</li>
510
+ <li><code>/deep_research</code>: Deep content gathering (Scraper)</li>
511
+ </ul>
512
+ <span class="demo-label">Parameters</span>
513
+ <table class="param-table">
514
+ <tr>
515
+ <td><span class="param-name">query</span></td>
516
+ <td><span class="param-desc">Search topic</span><span class="param-req">(required)</span>
517
+ </td>
518
+ </tr>
519
+ </table>
520
+ </div>
521
+ <div class="endpoint-demo">
522
+ <span class="demo-label">Try It Live</span>
523
+ <input id="search-query" class="demo-input" placeholder="Query" value="When was Python released?">
524
+ <select id="search-mode" class="demo-select">
525
+ <option value="search">Simple Search (Links)</option>
526
+ <option value="deep">Deep Research (Content Gathering)</option>
527
+ </select>
528
+ <button class="demo-btn" onclick="runSearch()">Execute Search ▶</button>
529
+ <div id="search-res" class="demo-response"></div>
530
+ </div>
531
+ </div>
532
+ </div>
533
+
534
+
535
+
536
+ </div>
537
+
538
+ <footer>K-AI API — Feel free to AI</footer>
539
+
540
+ <script>
541
+ // Global flag for Pie Chart
542
+ window.pieChartRendered = false;
543
+
544
+ async function runDemo(type) {
545
+ const resBox = document.getElementById(type + '-res');
546
+ const statusBox = document.getElementById(type + '-status');
547
+
548
+ resBox.className = 'demo-response visible';
549
+ resBox.style.display = 'none'; // Hide content while loading
550
+ resBox.innerHTML = '';
551
+
552
+ if (statusBox) {
553
+ statusBox.style.display = 'block';
554
+ statusBox.innerHTML = 'Sending Request... <span class="loading-spin"></span>';
555
+ statusBox.style.color = 'var(--text-muted)';
556
+ }
557
+
558
+ const startTime = Date.now();
559
+
560
+ try {
561
+ let url, body, method = 'POST';
562
+ let headers = {
563
+ 'Content-Type': 'application/json',
564
+ 'Authorization': 'Bearer sk-kai-demo-public' // Use Demo Key for Dashboard
565
+ };
566
+
567
+ if (type === 'chat-basic') {
568
+ // Simple Chat
569
+ const inputVal = document.getElementById('chat-basic-input').value;
570
+ const modelVal = document.getElementById('chat-basic-model').value || "gemini-3-flash";
571
+
572
+ if (!inputVal) { alert("Please enter a message"); return; }
573
+
574
+ url = '/v1/chat/completions';
575
+ body = {
576
+ model: modelVal,
577
+ messages: [{ role: "user", content: inputVal }]
578
+ };
579
+ }
580
+ else if (type === 'chat-adv') {
581
+ // Advanced Chat
582
+ const model = document.getElementById('chat-adv-model').value || "gemini-3-flash";
583
+ const userMsg = document.getElementById('chat-adv-msg').value;
584
+
585
+ if (!userMsg) { alert("Please enter a message"); return; }
586
+
587
+ url = '/v1/chat/completions';
588
+ body = {
589
+ model: model,
590
+ messages: [
591
+ { role: "user", content: userMsg }
592
+ ]
593
+ };
594
+ }
595
+ else if (type === 'models') {
596
+ // List Models
597
+ url = '/models'; // This is GET, no body
598
+ method = 'GET';
599
+ body = undefined;
600
+ // Keep models as public? Or require auth?
601
+ // Usually /models is authenticated in OpenAI but let's keep it open for now or add auth.
602
+ }
603
+
604
+ const response = await fetch(url, {
605
+ method: method,
606
+ headers: headers,
607
+ body: body ? JSON.stringify(body) : undefined
608
+ });
609
+
610
+ const data = await response.json();
611
+ const duration = Date.now() - startTime;
612
+
613
+ if (!response.ok) throw new Error(data.detail || 'Request failed');
614
+
615
+ if (!response.ok) throw new Error(data.detail || 'Request failed');
616
+
617
+ if (statusBox) {
618
+ statusBox.innerHTML = `Success (${duration}ms)`;
619
+ statusBox.style.color = 'var(--success)';
620
+ }
621
+
622
+ resBox.style.display = 'block';
623
+ resBox.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
624
+
625
+ } catch (err) {
626
+ resBox.innerHTML = `
627
+ <div style="margin-bottom:5px; font-weight:bold; color:var(--error);">
628
+ Error
629
+ </div>
630
+ <pre>${err.message}</pre>
631
+ `;
632
+ }
633
+ }
634
+
635
+ async function runSearch() {
636
+ const query = document.getElementById('search-query').value;
637
+ const mode = document.getElementById('search-mode').value;
638
+ const resBox = document.getElementById('search-res');
639
+
640
+ if (!query) { alert("Please enter a query"); return; }
641
+
642
+ resBox.className = 'demo-response visible';
643
+ resBox.textContent = '⏳ Searching... (Deep Research may take 10s+)';
644
+
645
+ const endpoint = mode === 'deep' ? '/deep_research' : '/search';
646
+
647
+ try {
648
+ const res = await fetch(endpoint, {
649
+ method: 'POST',
650
+ headers: { 'Content-Type': 'application/json' },
651
+ body: JSON.stringify({ query: query })
652
+ });
653
+ const data = await res.json();
654
+ resBox.className = 'demo-response visible ' + (res.ok ? 'success' : 'error');
655
+ resBox.innerText = JSON.stringify(data, null, 2);
656
+ } catch (e) {
657
+ resBox.innerText = 'Error: ' + e.message;
658
+ }
659
+ }
660
+
661
+ </script>
662
+ </body>
663
+
664
+ </html>