KiWA001 commited on
Commit
45ac16b
·
1 Parent(s): 0f810f2

refactor: remove legacy /chat endpoint and update dashboard

Browse files
Files changed (2) hide show
  1. main.py +0 -62
  2. static/docs.html +230 -198
main.py CHANGED
@@ -255,68 +255,6 @@ async def root():
255
  return FileResponse("static/docs.html")
256
 
257
 
258
- @app.post(
259
- "/chat",
260
- response_model=ChatResponse,
261
- responses={500: {"model": ErrorResponse}},
262
- tags=["Chat"],
263
- )
264
- async def chat(request: ChatRequest):
265
- """
266
- Send a message and get an AI response.
267
-
268
- - **message**: Your prompt/question (required)
269
- - **model**: Optional model name (defaults to best available)
270
- - **provider**: "auto" (tries all), "g4f", or "pollinations"
271
- - **system_prompt**: Optional system instructions for the AI
272
-
273
- Each request is fully stateless — no conversation history has to be retained.
274
- If a specific provider is chosen, tries all models on that provider
275
- ranked by quality before falling back to other providers.
276
- All users have unlimited access — no rate limiting.
277
- """
278
- logger.info(
279
- f"Chat request: model={request.model}, provider={request.provider}, "
280
- f"message_length={len(request.message)}"
281
- )
282
-
283
- try:
284
- result = await engine.chat(
285
- prompt=request.message,
286
- model=request.model,
287
- provider=request.provider or "auto",
288
- system_prompt=request.system_prompt,
289
- )
290
-
291
- return ChatResponse(
292
- response=result["response"],
293
- model=result["model"],
294
- provider=result["provider"],
295
- attempts=result.get("attempts", 1),
296
- response_time_ms=result.get("response_time_ms"),
297
- timestamp=datetime.utcnow().isoformat() + "Z",
298
- )
299
-
300
- except ValueError as e:
301
- raise HTTPException(status_code=400, detail=str(e))
302
- except RuntimeError as e:
303
- logger.error(f"All models exhausted: {e}")
304
- raise HTTPException(
305
- status_code=503,
306
- detail=(
307
- "All AI models are currently unavailable. "
308
- "Please contact the developer to fix this issue. "
309
- f"Details: {str(e)}"
310
- ),
311
- )
312
- except Exception as e:
313
- logger.error(f"Unexpected error: {e}")
314
- raise HTTPException(
315
- status_code=500,
316
- detail=f"Internal server error: {str(e)}",
317
- )
318
-
319
-
320
  @app.get(
321
  "/models",
322
  response_model=ModelsResponse,
 
255
  return FileResponse("static/docs.html")
256
 
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  @app.get(
259
  "/models",
260
  response_model=ModelsResponse,
static/docs.html CHANGED
@@ -517,7 +517,7 @@
517
  <h3>List Models</h3>
518
  <p>Get all currently available AI models.</p>
519
  </div>
520
- <span class="method-badge GET">GET /models</span>
521
  </div>
522
  <div class="endpoint-body">
523
  <div class="endpoint-docs">
@@ -598,165 +598,197 @@
598
  async function runDemo(type) {
599
  const resBox = document.getElementById(type + '-res');
600
  resBox.className = 'demo-response visible';
601
- resBox.textContent = '⏳ Sending request...';
 
602
 
603
- try {
604
- let url = '/chat';
605
- let method = 'POST';
606
- let body = {};
607
 
608
- if (type === 'chat-basic') {
609
- body = { message: document.getElementById('chat-basic-input').value };
610
- } else if (type === 'chat-adv') {
 
 
 
 
 
 
 
611
  body = {
612
- message: document.getElementById('chat-adv-msg').value,
613
- system_prompt: document.getElementById('chat-adv-sys').value,
614
- model: document.getElementById('chat-adv-model').value
615
  };
616
- } else if (type === 'models') {
617
- url = '/models';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  method = 'GET';
619
- body = null;
 
 
620
  }
621
 
622
- const res = await fetch(url, {
623
  method: method,
624
- headers: { 'Content-Type': 'application/json' },
625
- body: body ? JSON.stringify(body) : null
626
  });
627
 
628
- const data = await res.json();
629
- resBox.className = 'demo-response visible ' + (res.ok ? 'success' : 'error');
630
- resBox.textContent = JSON.stringify(data, null, 2);
631
- } catch (e) {
632
- resBox.textContent = 'Error: ' + e.message;
633
- }
634
- }
635
-
636
- async function runSearch() {
637
- const query = document.getElementById('search-query').value;
638
- const mode = document.getElementById('search-mode').value;
639
- const resBox = document.getElementById('search-res');
640
-
641
- if (!query) { alert("Please enter a query"); return; }
642
 
643
- resBox.className = 'demo-response visible';
644
- resBox.textContent = '⏳ Searching... (Deep Research may take 10s+)';
645
 
646
- const endpoint = mode === 'deep' ? '/deep_research' : '/search';
 
 
 
 
 
647
 
648
- try {
649
- const res = await fetch(endpoint, {
650
- method: 'POST',
651
- headers: { 'Content-Type': 'application/json' },
652
- body: JSON.stringify({ query: query })
653
- });
654
- const data = await res.json();
655
- resBox.className = 'demo-response visible ' + (res.ok ? 'success' : 'error');
656
- resBox.innerText = JSON.stringify(data, null, 2);
657
- } catch (e) {
658
- resBox.innerText = 'Error: ' + e.message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  }
660
- }
661
 
662
- // --- Live Ranking Logic ---
663
 
664
- let availableModelsSet = new Set();
665
- let scatterChart, pieChart;
666
 
667
- async function fetchRankingStats() {
668
- try {
669
- const t = new Date().getTime();
670
- const [statsRes, modelsRes] = await Promise.all([
671
- fetch(`/admin/stats?t=${t}`),
672
- fetch(`/models?t=${t}`)
673
- ]);
674
-
675
- if (!statsRes.ok) {
676
- throw new Error(`Stats HTTP ${statsRes.status}`);
677
- }
678
- const stats = await statsRes.json();
679
-
680
- availableModelsSet.clear();
681
- if (modelsRes.ok) {
682
- const data = await modelsRes.json();
683
- // Robust handling: Support both array and object wrapper
684
- const modelsList = Array.isArray(data) ? data : (data.models || []);
685
-
686
- if (Array.isArray(modelsList)) {
687
- modelsList.forEach(m => availableModelsSet.add(`${m.provider}/${m.model}`));
688
- } else {
689
- console.error("Expected array but got:", data);
 
690
  }
691
- }
692
 
693
- renderDashboard(stats);
694
- } catch (e) {
695
- console.error("Failed to fetch ranking", e);
696
- const tbody = document.querySelector('#rankings-table tbody');
697
- // Only replace if we haven't rendered data yet (or if it's the loading state)
698
- if (tbody.innerHTML.includes('Loading')) {
699
- tbody.innerHTML = `<tr><td colspan="7" style="color:#ef4444; text-align:center; padding: 20px;">
700
  Failed to load stats: ${e.message}<br>
701
  <small style="opacity:0.7">If on Hugging Face, check "Logs" tab for backend errors.</small>
702
  </td></tr>`;
 
703
  }
704
  }
705
- }
706
 
707
- function calculateScore(s, f, timeMs, cf) {
708
- let base = s - (f * 2);
709
- let penalty = (timeMs || 0) / 1000.0;
710
- let score = base - penalty;
711
- if (cf >= 3) return score - 100000;
712
- return score;
713
- }
714
-
715
- function renderDashboard(data) {
716
- let rows = [];
717
- let providerCounts = {};
718
- let scatterData = [];
719
-
720
- for (let [key, val] of Object.entries(data)) {
721
- let score = calculateScore(val.success, val.failure, val.avg_time_ms, val.consecutive_failures);
722
- rows.push({ id: key, ...val, score: score });
723
-
724
- // Provider stats for Pie
725
- let prov = key.split('/')[0];
726
- providerCounts[prov] = (providerCounts[prov] || 0) + val.success;
727
-
728
- // Scatter Data
729
- scatterData.push({
730
- x: val.avg_time_ms || 0,
731
- y: score,
732
- id: key
733
- });
734
  }
735
- rows.sort((a, b) => b.score - a.score);
736
-
737
- // Render Table
738
- const tbody = document.querySelector('#rankings-table tbody');
739
- tbody.innerHTML = '';
740
-
741
- if (rows.length === 0) {
742
- tbody.innerHTML = '<tr><td colspan="7" style="text-align:center; padding: 20px;">No stats available yet. Make a request!</td></tr>';
743
- } else {
744
- rows.forEach((row, index) => {
745
- const tr = document.createElement('tr');
746
- let scoreClass = row.score > 0 ? 'score-good' : 'score-bad';
747
- let timeStr = row.avg_time_ms ? Math.round(row.avg_time_ms) + 'ms' : '-';
748
-
749
- let status = '';
750
- if (!availableModelsSet.has(row.id)) {
751
- status = '<span style="color:#9ca3af; font-size:11px; border:1px solid #374151; padding:2px 6px; border-radius:4px;">OFFLINE (Local Only)</span>';
752
- tr.style.opacity = '0.6';
753
- } else if (row.consecutive_failures >= 3) {
754
- status = '<span style="color:#ef4444">⚠️ CRITICAL</span>';
755
- } else {
756
- status = '<span style="color:#22c55e">● Active</span>';
757
- }
758
 
759
- tr.innerHTML = `
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  <td><span class="rank-badge">${index + 1}</span></td>
761
  <td><b>${row.id}</b></td>
762
  <td class="${scoreClass}">${row.score.toFixed(2)}</td>
@@ -765,77 +797,77 @@
765
  <td>${row.failure}</td>
766
  <td>${status}</td>
767
  `;
768
- tbody.appendChild(tr);
769
- });
770
- }
771
-
772
- // Render Charts
773
- updateScatterChart(scatterData);
774
- updatePieChart(providerCounts);
775
- }
776
 
777
- function updateScatterChart(data) {
778
- const ctx = document.getElementById('scatterChart').getContext('2d');
779
- if (scatterChart) {
780
- scatterChart.data.datasets[0].data = data;
781
- scatterChart.update('none');
782
- return;
783
  }
784
- const validData = data.filter(d => d.x > 0);
785
- scatterChart = new Chart(ctx, {
786
- type: 'scatter',
787
- data: {
788
- datasets: [{
789
- label: 'Models',
790
- data: validData,
791
- backgroundColor: '#8b5cf6',
792
- borderColor: '#8b5cf6',
793
- }]
794
- },
795
- options: {
796
- responsive: true,
797
- maintainAspectRatio: false,
798
- animation: { duration: 1000 },
799
- scales: {
800
- x: { type: 'linear', position: 'bottom', title: { display: true, text: 'Time (ms)', color: '#6b6b80' }, grid: { color: '#2a2a3a' } },
801
- y: { title: { display: true, text: 'Score', color: '#6b6b80' }, grid: { color: '#2a2a3a' } }
802
  },
803
- plugins: {
804
- legend: { display: false },
805
- tooltip: { callbacks: { label: (ctx) => ctx.raw.id + ': ' + ctx.raw.y.toFixed(2) } }
 
 
 
 
 
 
 
 
 
806
  }
807
- }
808
- });
809
- }
810
-
811
- function updatePieChart(counts) {
812
- if (window.pieChartRendered) return;
813
- const ctx = document.getElementById('pieChart').getContext('2d');
814
- window.pieChartRendered = true;
815
- pieChart = new Chart(ctx, {
816
- type: 'doughnut',
817
- data: {
818
- labels: Object.keys(counts),
819
- datasets: [{
820
- data: Object.values(counts),
821
- backgroundColor: ['#8b5cf6', '#22c55e', '#f59e0b', '#ef4444', '#ec4899', '#3b82f6'],
822
- borderWidth: 0
823
- }]
824
- },
825
- options: {
826
- responsive: true,
827
- maintainAspectRatio: false,
828
- animation: { duration: 1000 },
829
- plugins: {
830
- legend: { position: 'right', labels: { color: '#9898aa', font: { size: 10 } } }
831
  }
832
- }
833
- });
834
- }
835
 
836
- // Init Live Ranking
837
- fetchRankingStats();
838
- setInterval(fetchRankingStats, 5000);
839
 
840
  </script>
841
 
 
517
  <h3>List Models</h3>
518
  <p>Get all currently available AI models.</p>
519
  </div>
520
+ <span class="method-badge GET">POST /v1/chat/completions</span>
521
  </div>
522
  <div class="endpoint-body">
523
  <div class="endpoint-docs">
 
598
  async function runDemo(type) {
599
  const resBox = document.getElementById(type + '-res');
600
  resBox.className = 'demo-response visible';
601
+ resBox.style.display = 'block';
602
+ resBox.innerHTML = 'Sending Request... <span class="loading-spin"></span>';
603
 
604
+ const startTime = Date.now();
 
 
 
605
 
606
+ try {
607
+ let url, body, method = 'POST';
608
+ let headers = {
609
+ 'Content-Type': 'application/json',
610
+ 'Authorization': 'Bearer sk-kai-demo-public' // Use Demo Key for Dashboard
611
+ };
612
+
613
+ if (type === 'chat') {
614
+ // Simple Chat
615
+ url = '/v1/chat/completions';
616
  body = {
617
+ model: "gemini-3-flash",
618
+ messages: [{ role: "user", content: "Hello! Who are you?" }]
 
619
  };
620
+ }
621
+ else if (type === 'chat-adv') {
622
+ // Advanced Chat
623
+ const model = document.getElementById('chat-adv-model').value || "gemini-3-flash";
624
+ url = '/v1/chat/completions';
625
+ body = {
626
+ model: model,
627
+ messages: [
628
+ { role: "system", content: "You are a helpful assistant." },
629
+ { role: "user", content: "Tell me a fun fact." }
630
+ ]
631
+ };
632
+ }
633
+ else if (type === 'models') {
634
+ // List Models
635
+ url = '/models'; // This is GET, no body
636
  method = 'GET';
637
+ body = undefined;
638
+ // Keep models as public? Or require auth?
639
+ // Usually /models is authenticated in OpenAI but let's keep it open for now or add auth.
640
  }
641
 
642
+ const response = await fetch(url, {
643
  method: method,
644
+ headers: headers,
645
+ body: body ? JSON.stringify(body) : undefined
646
  });
647
 
648
+ const data = await response.json();
649
+ const duration = Date.now() - startTime;
 
 
 
 
 
 
 
 
 
 
 
 
650
 
651
+ if (!response.ok) throw new Error(data.detail || 'Request failed');
 
652
 
653
+ resBox.innerHTML = `
654
+ <div style="margin-bottom:5px; font-weight:bold; color:var(--success);">
655
+ Success (${duration}ms)
656
+ </div>
657
+ <pre>${JSON.stringify(data, null, 2)}</pre>
658
+ `;
659
 
660
+ } catch (err) {
661
+ resBox.innerHTML = `
662
+ <div style="margin-bottom:5px; font-weight:bold; color:var(--error);">
663
+ Error
664
+ </div>
665
+ <pre>${err.message}</pre>
666
+ `;
667
+ }
668
+ async function runSearch() {
669
+ const query = document.getElementById('search-query').value;
670
+ const mode = document.getElementById('search-mode').value;
671
+ const resBox = document.getElementById('search-res');
672
+
673
+ if (!query) { alert("Please enter a query"); return; }
674
+
675
+ resBox.className = 'demo-response visible';
676
+ resBox.textContent = '⏳ Searching... (Deep Research may take 10s+)';
677
+
678
+ const endpoint = mode === 'deep' ? '/deep_research' : '/search';
679
+
680
+ try {
681
+ const res = await fetch(endpoint, {
682
+ method: 'POST',
683
+ headers: { 'Content-Type': 'application/json' },
684
+ body: JSON.stringify({ query: query })
685
+ });
686
+ const data = await res.json();
687
+ resBox.className = 'demo-response visible ' + (res.ok ? 'success' : 'error');
688
+ resBox.innerText = JSON.stringify(data, null, 2);
689
+ } catch (e) {
690
+ resBox.innerText = 'Error: ' + e.message;
691
+ }
692
  }
 
693
 
694
+ // --- Live Ranking Logic ---
695
 
696
+ let availableModelsSet = new Set();
697
+ let scatterChart, pieChart;
698
 
699
+ async function fetchRankingStats() {
700
+ try {
701
+ const t = new Date().getTime();
702
+ const [statsRes, modelsRes] = await Promise.all([
703
+ fetch(`/admin/stats?t=${t}`),
704
+ fetch(`/models?t=${t}`)
705
+ ]);
706
+
707
+ if (!statsRes.ok) {
708
+ throw new Error(`Stats HTTP ${statsRes.status}`);
709
+ }
710
+ const stats = await statsRes.json();
711
+
712
+ availableModelsSet.clear();
713
+ if (modelsRes.ok) {
714
+ const data = await modelsRes.json();
715
+ // Robust handling: Support both array and object wrapper
716
+ const modelsList = Array.isArray(data) ? data : (data.models || []);
717
+
718
+ if (Array.isArray(modelsList)) {
719
+ modelsList.forEach(m => availableModelsSet.add(`${m.provider}/${m.model}`));
720
+ } else {
721
+ console.error("Expected array but got:", data);
722
+ }
723
  }
 
724
 
725
+ renderDashboard(stats);
726
+ } catch (e) {
727
+ console.error("Failed to fetch ranking", e);
728
+ const tbody = document.querySelector('#rankings-table tbody');
729
+ // Only replace if we haven't rendered data yet (or if it's the loading state)
730
+ if (tbody.innerHTML.includes('Loading')) {
731
+ tbody.innerHTML = `<tr><td colspan="7" style="color:#ef4444; text-align:center; padding: 20px;">
732
  Failed to load stats: ${e.message}<br>
733
  <small style="opacity:0.7">If on Hugging Face, check "Logs" tab for backend errors.</small>
734
  </td></tr>`;
735
+ }
736
  }
737
  }
 
738
 
739
+ function calculateScore(s, f, timeMs, cf) {
740
+ let base = s - (f * 2);
741
+ let penalty = (timeMs || 0) / 1000.0;
742
+ let score = base - penalty;
743
+ if (cf >= 3) return score - 100000;
744
+ return score;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
746
 
747
+ function renderDashboard(data) {
748
+ let rows = [];
749
+ let providerCounts = {};
750
+ let scatterData = [];
751
+
752
+ for (let [key, val] of Object.entries(data)) {
753
+ let score = calculateScore(val.success, val.failure, val.avg_time_ms, val.consecutive_failures);
754
+ rows.push({ id: key, ...val, score: score });
755
+
756
+ // Provider stats for Pie
757
+ let prov = key.split('/')[0];
758
+ providerCounts[prov] = (providerCounts[prov] || 0) + val.success;
759
+
760
+ // Scatter Data
761
+ scatterData.push({
762
+ x: val.avg_time_ms || 0,
763
+ y: score,
764
+ id: key
765
+ });
766
+ }
767
+ rows.sort((a, b) => b.score - a.score);
768
+
769
+ // Render Table
770
+ const tbody = document.querySelector('#rankings-table tbody');
771
+ tbody.innerHTML = '';
772
+
773
+ if (rows.length === 0) {
774
+ tbody.innerHTML = '<tr><td colspan="7" style="text-align:center; padding: 20px;">No stats available yet. Make a request!</td></tr>';
775
+ } else {
776
+ rows.forEach((row, index) => {
777
+ const tr = document.createElement('tr');
778
+ let scoreClass = row.score > 0 ? 'score-good' : 'score-bad';
779
+ let timeStr = row.avg_time_ms ? Math.round(row.avg_time_ms) + 'ms' : '-';
780
+
781
+ let status = '';
782
+ if (!availableModelsSet.has(row.id)) {
783
+ status = '<span style="color:#9ca3af; font-size:11px; border:1px solid #374151; padding:2px 6px; border-radius:4px;">OFFLINE (Local Only)</span>';
784
+ tr.style.opacity = '0.6';
785
+ } else if (row.consecutive_failures >= 3) {
786
+ status = '<span style="color:#ef4444">⚠️ CRITICAL</span>';
787
+ } else {
788
+ status = '<span style="color:#22c55e">● Active</span>';
789
+ }
790
+
791
+ tr.innerHTML = `
792
  <td><span class="rank-badge">${index + 1}</span></td>
793
  <td><b>${row.id}</b></td>
794
  <td class="${scoreClass}">${row.score.toFixed(2)}</td>
 
797
  <td>${row.failure}</td>
798
  <td>${status}</td>
799
  `;
800
+ tbody.appendChild(tr);
801
+ });
802
+ }
 
 
 
 
 
803
 
804
+ // Render Charts
805
+ updateScatterChart(scatterData);
806
+ updatePieChart(providerCounts);
 
 
 
807
  }
808
+
809
+ function updateScatterChart(data) {
810
+ const ctx = document.getElementById('scatterChart').getContext('2d');
811
+ if (scatterChart) {
812
+ scatterChart.data.datasets[0].data = data;
813
+ scatterChart.update('none');
814
+ return;
815
+ }
816
+ const validData = data.filter(d => d.x > 0);
817
+ scatterChart = new Chart(ctx, {
818
+ type: 'scatter',
819
+ data: {
820
+ datasets: [{
821
+ label: 'Models',
822
+ data: validData,
823
+ backgroundColor: '#8b5cf6',
824
+ borderColor: '#8b5cf6',
825
+ }]
826
  },
827
+ options: {
828
+ responsive: true,
829
+ maintainAspectRatio: false,
830
+ animation: { duration: 1000 },
831
+ scales: {
832
+ x: { type: 'linear', position: 'bottom', title: { display: true, text: 'Time (ms)', color: '#6b6b80' }, grid: { color: '#2a2a3a' } },
833
+ y: { title: { display: true, text: 'Score', color: '#6b6b80' }, grid: { color: '#2a2a3a' } }
834
+ },
835
+ plugins: {
836
+ legend: { display: false },
837
+ tooltip: { callbacks: { label: (ctx) => ctx.raw.id + ': ' + ctx.raw.y.toFixed(2) } }
838
+ }
839
  }
840
+ });
841
+ }
842
+
843
+ function updatePieChart(counts) {
844
+ if (window.pieChartRendered) return;
845
+ const ctx = document.getElementById('pieChart').getContext('2d');
846
+ window.pieChartRendered = true;
847
+ pieChart = new Chart(ctx, {
848
+ type: 'doughnut',
849
+ data: {
850
+ labels: Object.keys(counts),
851
+ datasets: [{
852
+ data: Object.values(counts),
853
+ backgroundColor: ['#8b5cf6', '#22c55e', '#f59e0b', '#ef4444', '#ec4899', '#3b82f6'],
854
+ borderWidth: 0
855
+ }]
856
+ },
857
+ options: {
858
+ responsive: true,
859
+ maintainAspectRatio: false,
860
+ animation: { duration: 1000 },
861
+ plugins: {
862
+ legend: { position: 'right', labels: { color: '#9898aa', font: { size: 10 } } }
863
+ }
864
  }
865
+ });
866
+ }
 
867
 
868
+ // Init Live Ranking
869
+ fetchRankingStats();
870
+ setInterval(fetchRankingStats, 5000);
871
 
872
  </script>
873