pool-anything 0.1.0 → 0.1.1
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.
- package/README.md +13 -4
- package/data/providers.json +1 -1
- package/dist/admin/branding.js +1 -0
- package/dist/admin/pages.js +632 -46
- package/dist/admin/seo.js +58 -0
- package/dist/admin/shell.js +45 -2
- package/dist/cli/commands.js +276 -0
- package/dist/cli/nl.js +10 -0
- package/dist/cli/repl.js +95 -331
- package/dist/cli/tui.js +254 -0
- package/dist/cli/ui.js +2 -0
- package/dist/cli.js +11 -3
- package/dist/data/providers.json +1 -1
- package/dist/playground/adapters.js +304 -0
- package/dist/pool/index.js +39 -0
- package/dist/proxy/forward.js +44 -2
- package/dist/router/api.js +2 -2
- package/dist/server.js +43 -4
- package/dist/upstream/index.js +10 -0
- package/dist/version.js +160 -0
- package/package.json +8 -3
package/dist/admin/pages.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { LOGO_V, DOCS_URL, ICONS } from "./branding.js";
|
|
2
|
+
import { HOME_HEAD_EXTRA, SEO_TITLE, subPageHead } from "./seo.js";
|
|
3
|
+
import { ADAPTERS } from "../playground/adapters.js";
|
|
2
4
|
import { shellCss, shellNav, shellJs, sharedHeader, sharedLayoutCss } from "./shell.js";
|
|
3
5
|
import { tokensCss } from "./tokens.js";
|
|
4
6
|
import { docsComponentsCss, ddSelect } from "./components.js";
|
|
@@ -124,7 +126,8 @@ export const page = `<!doctype html>
|
|
|
124
126
|
<head>
|
|
125
127
|
<meta charset="utf-8" />
|
|
126
128
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
127
|
-
<title
|
|
129
|
+
<title>${SEO_TITLE}</title>
|
|
130
|
+
${HOME_HEAD_EXTRA}
|
|
128
131
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
129
132
|
<style>
|
|
130
133
|
${tokensCss}
|
|
@@ -134,9 +137,13 @@ export const page = `<!doctype html>
|
|
|
134
137
|
* { box-sizing:border-box; }
|
|
135
138
|
body { margin:0; font-family:var(--font-sans); background:var(--bg); color:var(--ink); }
|
|
136
139
|
header { height:56px; background:var(--card); border-bottom:1px solid var(--line); display:flex; align-items:center; padding:0 16px; gap:8px; position:sticky; top:0; }
|
|
137
|
-
header .spacer { margin-left:auto; display:flex; gap:4px; }
|
|
140
|
+
header .spacer { margin-left:auto; display:flex; gap:4px; align-items:center; }
|
|
138
141
|
header button, header a { font-size:14px; padding:6px 12px; border-radius:8px; border:0; background:inherit; color:var(--ink); text-decoration:none; cursor:pointer; }
|
|
139
142
|
header button:hover, header a:hover { background:var(--paper-2); }
|
|
143
|
+
.ver-pill { font-size:12px; font-weight:600; color:var(--ink-soft); background:var(--paper-2); border:1px solid var(--line); border-radius:999px; padding:4px 10px; white-space:nowrap; }
|
|
144
|
+
.ver-update { font-size:12px !important; font-weight:600; border:1px solid var(--ink) !important; background:var(--ink) !important; color:var(--card) !important; border-radius:999px !important; padding:4px 12px !important; cursor:pointer; white-space:nowrap; }
|
|
145
|
+
.ver-update[hidden] { display:none; }
|
|
146
|
+
.ver-update:disabled { opacity:.6; cursor:default; }
|
|
140
147
|
.shell { display:grid; grid-template-columns:var(--sbw,260px) 1fr; min-height:100vh; transition:grid-template-columns 250ms cubic-bezier(0.77,0,0.175,1); }
|
|
141
148
|
.shell.collapsed { --sbw:57px; }
|
|
142
149
|
.sidebar { background:var(--card); border-right:1px solid var(--line); display:flex; flex-direction:column; min-height:100vh; height:100vh; position:sticky; top:0; overflow:hidden; white-space:nowrap; }
|
|
@@ -230,6 +237,12 @@ export const page = `<!doctype html>
|
|
|
230
237
|
.hero-row, .search-card { max-width:400px; }
|
|
231
238
|
.hero-row h1, .hero-row { gap:2px; }
|
|
232
239
|
h1 { font-family:var(--font-serif); font-size:clamp(20px, 4vw, 26px); font-weight:500; }
|
|
240
|
+
.hero-sub { width:100%; max-width:640px; margin:8px auto 0; text-align:center; font-size:14px; line-height:1.55; color:var(--ink-soft); }
|
|
241
|
+
.seo-foot { width:100%; max-width:640px; margin:28px auto 0; background:var(--card); border:1px solid var(--line); border-radius:12px; padding:18px 20px; font-size:13.5px; line-height:1.6; color:var(--body-copy); }
|
|
242
|
+
.seo-foot h2 { font-size:14px; margin:14px 0 6px; color:var(--ink); }
|
|
243
|
+
.seo-foot h2:first-child { margin-top:0; }
|
|
244
|
+
.seo-foot ul { margin:6px 0; padding-left:18px; display:flex; flex-direction:column; gap:4px; }
|
|
245
|
+
.seo-foot a { color:var(--ink); }
|
|
233
246
|
.sec-label { font-size:11px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin:0 0 8px; }
|
|
234
247
|
.conn-grid { width:100%; display:flex; flex-direction:column; gap:8px; }
|
|
235
248
|
.prov-lg { display:flex; align-items:center; gap:10px; width:100%; background:var(--card); border:1px solid var(--line); border-radius:12px; padding:10px 12px; cursor:pointer; font-size:13px; font-weight:600; text-align:left; animation:fadeSlide .28s cubic-bezier(.2,.7,.3,1) both; }
|
|
@@ -349,6 +362,7 @@ ${docsComponentsCss}
|
|
|
349
362
|
<a class="mi" href="/keys"><span class="ic">${ICONS.keys}</span><span class="lbl">API key manager</span></a>
|
|
350
363
|
<a class="mi" href="/playground"><span class="ic">${ICONS.playground}</span><span class="lbl">Playground</span></a>
|
|
351
364
|
<a class="mi" href="/analytics"><span class="ic">${ICONS.analytics}</span><span class="lbl">Analytics</span></a>
|
|
365
|
+
<a class="mi" href="/history"><span class="ic">${ICONS.history}</span><span class="lbl">History</span></a>
|
|
352
366
|
<a class="mi" href="${DOCS_URL}" target="_blank" rel="noopener" title="Opens external docs in a new tab"><span class="ic">${ICONS.docs}</span><span class="lbl">Docs</span><span class="ext">${ICONS.external}</span></a>
|
|
353
367
|
</nav>
|
|
354
368
|
<div class="sb-footer"><button class="collapse-btn" id="collapseBtn" type="button" data-sidebar="trigger" aria-expanded="true" aria-label="Collapse sidebar"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M21.25 6.72v10.56a2.97 2.97 0 0 1-2.97 2.97H5.72a2.97 2.97 0 0 1-2.97-2.97V6.72a2.97 2.97 0 0 1 2.97-2.97h12.56a2.97 2.97 0 0 1 2.97 2.97"></path><path d="M6.25 7.25v9.5"></path></svg></button></div>
|
|
@@ -371,12 +385,15 @@ ${docsComponentsCss}
|
|
|
371
385
|
<header>
|
|
372
386
|
<div></div>
|
|
373
387
|
<div class="spacer">
|
|
388
|
+
<span class="ver-pill" id="verPill" title="pool-anything version">v…</span>
|
|
389
|
+
<button class="ver-update" id="verUpdate" type="button" hidden>Update</button>
|
|
374
390
|
<a href="${DOCS_URL}" target="_blank" rel="noopener">Docs</a>
|
|
375
391
|
</div>
|
|
376
392
|
</header>
|
|
377
393
|
<main>
|
|
378
394
|
<div class="wrap">
|
|
379
395
|
<div class="hero-row"><img class="hero-logo" src="/logo.png" alt="pool-anything logo" width="40" height="40" /><h1>What do you want to pool</h1></div>
|
|
396
|
+
<p class="hero-sub">Pool free-tier API keys and rotate through them — one endpoint, round-robin key rotation, usage & quota tracking for Groq, OpenRouter, Gemini and 30+ providers.</p>
|
|
380
397
|
<div class="search-card">
|
|
381
398
|
<div class="search-box">
|
|
382
399
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"></path></svg>
|
|
@@ -425,6 +442,18 @@ ${docsComponentsCss}
|
|
|
425
442
|
</div>
|
|
426
443
|
</div>
|
|
427
444
|
</div>
|
|
445
|
+
<section class="seo-foot" aria-label="About pool-anything">
|
|
446
|
+
<h2>Pool free-tier API keys into one endpoint</h2>
|
|
447
|
+
<p>Most AI and API providers hand you a small free tier — a rate limit, a daily quota, a handful of trial tokens. <strong>pool-anything</strong> gathers N keys for the same provider into a pool, then serves them through a single drop-in HTTP proxy that round-robins across keys and tracks usage per key in SQLite.</p>
|
|
448
|
+
<h2>Why pool keys?</h2>
|
|
449
|
+
<ul>
|
|
450
|
+
<li>Round-robin rotation evens out rate limits across all keys.</li>
|
|
451
|
+
<li>Per-key usage, quota windows, cooldown and failover keep pools healthy.</li>
|
|
452
|
+
<li>36 preconfigured providers: Groq, OpenRouter, Gemini, OpenAI, Anthropic, ElevenLabs, Tavily, Resend and more — plus any custom provider.</li>
|
|
453
|
+
<li>Self-hosted TypeScript + SQLite core with zero-dependency server, web UI, analytics dashboard and playground.</li>
|
|
454
|
+
</ul>
|
|
455
|
+
<p>Start in <a href="/pools">Pools</a>, gather keys in the <a href="/keys">API key manager</a>, try a pooled key in the <a href="/playground">Playground</a>, or read the <a href="https://pool-anything.pages.dev/docs">docs</a>.</p>
|
|
456
|
+
</section>
|
|
428
457
|
</div>
|
|
429
458
|
</main>
|
|
430
459
|
</div>
|
|
@@ -698,6 +727,40 @@ ${docsComponentsCss}
|
|
|
698
727
|
setTimeout(() => document.getElementById('browseAll').scrollIntoView({ block: 'start' }), 100);
|
|
699
728
|
}
|
|
700
729
|
});
|
|
730
|
+
(function () {
|
|
731
|
+
var pill = document.getElementById('verPill');
|
|
732
|
+
var btn = document.getElementById('verUpdate');
|
|
733
|
+
if (!pill || !btn) return;
|
|
734
|
+
function txt(el, s) { if (el) el.textContent = s; }
|
|
735
|
+
fetch('/api/version').then(function (r) { return r.json(); }).then(function (v) {
|
|
736
|
+
if (!v || !v.current) return;
|
|
737
|
+
txt(pill, 'v' + v.current);
|
|
738
|
+
pill.title = v.latest ? ('current v' + v.current + ', latest v' + v.latest) : ('pool-anything v' + v.current);
|
|
739
|
+
if (v.updateAvailable) {
|
|
740
|
+
btn.hidden = false;
|
|
741
|
+
txt(btn, 'Update to v' + v.latest);
|
|
742
|
+
}
|
|
743
|
+
}).catch(function () {});
|
|
744
|
+
btn.addEventListener('click', function () {
|
|
745
|
+
if (btn.disabled) return;
|
|
746
|
+
btn.disabled = true;
|
|
747
|
+
txt(btn, 'Updating…');
|
|
748
|
+
fetch('/api/update', { method: 'POST' }).then(function (r) { return r.json(); }).then(function (j) {
|
|
749
|
+
if (j && j.ok) {
|
|
750
|
+
txt(pill, 'v' + (j.current || j.latest || ''));
|
|
751
|
+
txt(btn, 'Updated — restart server');
|
|
752
|
+
pill.title = (j.text || 'updated');
|
|
753
|
+
} else {
|
|
754
|
+
txt(btn, 'Update failed — retry');
|
|
755
|
+
btn.disabled = false;
|
|
756
|
+
pill.title = ((j && (j.text || j.error)) || 'update failed');
|
|
757
|
+
}
|
|
758
|
+
}).catch(function () {
|
|
759
|
+
txt(btn, 'Update failed — retry');
|
|
760
|
+
btn.disabled = false;
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
})();
|
|
701
764
|
loadProviders();
|
|
702
765
|
loadAnalytics();
|
|
703
766
|
</script>
|
|
@@ -708,6 +771,7 @@ export function keysPage() {
|
|
|
708
771
|
<html lang="en"><head><meta charset="utf-8"/>
|
|
709
772
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
710
773
|
<title>API key manager — pool-anything</title>
|
|
774
|
+
${subPageHead("/keys/", "Add, view, edit and rotate pooled API keys per provider with masked storage and per-key usage.")}
|
|
711
775
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
712
776
|
<style>
|
|
713
777
|
*{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);background:var(--paper-2);color:var(--ink)}
|
|
@@ -769,7 +833,7 @@ async function refresh(){
|
|
|
769
833
|
const li=document.createElement('li');
|
|
770
834
|
li.innerHTML='<span></span><small></small>';
|
|
771
835
|
li.querySelector('span').textContent=k.label+' · '+k.masked;
|
|
772
|
-
li.querySelector('small').textContent='used '+((us.perKey||[]).find(x=>x.id===k.id)?.used||0);
|
|
836
|
+
li.querySelector('small').textContent='used '+((us.perKey||[]).find(x=>x.id===k.id)?.used||0)+(k.cooling?' · cooling':'');
|
|
773
837
|
const v=document.createElement('button');v.textContent='View';v.className='ghost sm';
|
|
774
838
|
v.onclick=async()=>{
|
|
775
839
|
if(v.dataset.open){
|
|
@@ -843,6 +907,7 @@ export function analyticsPage() {
|
|
|
843
907
|
<html lang="en"><head><meta charset="utf-8"/>
|
|
844
908
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
845
909
|
<title>Analytics — pool-anything</title>
|
|
910
|
+
${subPageHead("/analytics/", "Analytics dashboard: total requests, tokens tracked, pools, keys pooled and 14-day usage charts.")}
|
|
846
911
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
847
912
|
<style>
|
|
848
913
|
*{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);background:var(--paper-2);color:var(--ink)}
|
|
@@ -990,6 +1055,7 @@ export function poolsPage() {
|
|
|
990
1055
|
<html lang="en"><head><meta charset="utf-8"/>
|
|
991
1056
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
992
1057
|
<title>Pools — pool-anything</title>
|
|
1058
|
+
${subPageHead("/pools/", "Pools holding API keys with per-pool usage, key counts and quota status.")}
|
|
993
1059
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
994
1060
|
<style>
|
|
995
1061
|
*{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);background:var(--paper-2);color:var(--ink)}
|
|
@@ -1029,14 +1095,14 @@ async function init(){
|
|
|
1029
1095
|
card.style.animationDelay=Math.min(i*40,320)+'ms';
|
|
1030
1096
|
card.innerHTML='<div class="chead"><img alt=""/><b></b><span class="pill"></span></div><ul></ul><div class="row" style="margin-top:10px"><a class="manage" href="/keys">Manage keys →</a></div>';
|
|
1031
1097
|
logoSrc(card.querySelector('img'),prov.logoFile);
|
|
1032
|
-
card.querySelector('b').textContent=prov.name+' · '+p.name;
|
|
1098
|
+
card.querySelector('b').textContent=prov.name+' · '+p.name+' · #'+p.id;
|
|
1033
1099
|
card.querySelector('.pill').textContent=ks.length+' key(s) · used '+(us.used||0);
|
|
1034
1100
|
const ul=card.querySelector('ul');
|
|
1035
1101
|
ks.forEach(k=>{
|
|
1036
1102
|
const li=document.createElement('li');
|
|
1037
1103
|
li.innerHTML='<span></span><small></small>';
|
|
1038
1104
|
li.querySelector('span').textContent=k.label+' · '+k.masked;
|
|
1039
|
-
li.querySelector('small').textContent='used '+((us.perKey||[]).find(x=>x.id===k.id)?.used||0);
|
|
1105
|
+
li.querySelector('small').textContent='used '+((us.perKey||[]).find(x=>x.id===k.id)?.used||0)+(k.cooling?' · cooling':'');
|
|
1040
1106
|
ul.appendChild(li);
|
|
1041
1107
|
});
|
|
1042
1108
|
box.appendChild(card);
|
|
@@ -1046,11 +1112,145 @@ async function init(){
|
|
|
1046
1112
|
init();
|
|
1047
1113
|
</script></div></div>${shellJs}</body></html>`;
|
|
1048
1114
|
}
|
|
1115
|
+
export function historyPage() {
|
|
1116
|
+
return `<!doctype html>
|
|
1117
|
+
<html lang="en"><head><meta charset="utf-8"/>
|
|
1118
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
1119
|
+
<title>History — pool-anything</title>
|
|
1120
|
+
${subPageHead("/history/", "History of proxied API calls per pool and key: status, tokens, errors and retained responses.")}
|
|
1121
|
+
<link rel="icon" type="image/png" href="/logo.png" />
|
|
1122
|
+
<style>
|
|
1123
|
+
*{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);background:var(--paper-2);color:var(--ink)}
|
|
1124
|
+
${shellCss}
|
|
1125
|
+
${sharedLayoutCss}
|
|
1126
|
+
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:18px}
|
|
1127
|
+
.row{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center}
|
|
1128
|
+
select{border:1px solid var(--line);border-radius:8px;min-height:34px;padding:0 10px;font-size:14px}
|
|
1129
|
+
button{border:1px solid var(--ink);background:var(--ink);color:var(--card);border-radius:8px;min-height:34px;padding:0 12px;font-size:14px;cursor:pointer}
|
|
1130
|
+
label.auto{display:flex;gap:6px;align-items:center;font-size:13px;color:var(--ink-soft)}
|
|
1131
|
+
table.hist{width:100%;border-collapse:collapse;margin-top:12px;font-size:13px}
|
|
1132
|
+
table.hist th{text-align:left;font-size:12px;color:var(--ink-soft);font-weight:600;padding:8px 10px;border-bottom:1px solid var(--line);white-space:nowrap}
|
|
1133
|
+
table.hist td{padding:8px 10px;border-bottom:1px solid var(--line-soft);vertical-align:top}
|
|
1134
|
+
table.hist td.mono{font-family:var(--font-mono);font-size:12px}
|
|
1135
|
+
.st{display:inline-block;min-width:44px;text-align:center;font-family:var(--font-mono);font-size:12px;font-weight:600;border-radius:6px;padding:2px 8px}
|
|
1136
|
+
.st2{background:#e6f4ea;color:#1F7A4D}.st4{background:#fef3e2;color:#B26A00}.st5{background:#fdecea;color:#B3261E}.st0{background:var(--paper-2);color:var(--ink-soft)}
|
|
1137
|
+
details.body{max-width:420px}details.body summary{cursor:pointer;color:var(--ink-soft);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px}
|
|
1138
|
+
details.body pre{white-space:pre-wrap;word-break:break-word;font-size:12px;background:var(--paper-2);border-radius:8px;padding:8px;max-height:240px;overflow:auto}
|
|
1139
|
+
.empty{color:var(--ink-soft);font-size:14px;text-align:center;padding:24px 0}
|
|
1140
|
+
.err{color:var(--danger-ink);font-size:13px;min-height:18px}
|
|
1141
|
+
${docsComponentsCss}
|
|
1142
|
+
</style></head><body><div class="shell" id="shell">${shellNav("/history")}<div class="content">${sharedHeader}<main>
|
|
1143
|
+
<div class="wrap">
|
|
1144
|
+
<div class="page-head"><a href="/"><img src="/logo.png" alt="pool-anything" width="28" height="28"/></a><h1>History</h1></div>
|
|
1145
|
+
<a class="back" href="/">← pool search</a>
|
|
1146
|
+
<div class="err" id="err"></div>
|
|
1147
|
+
<div class="card"><div class="row"><select id="hpool" aria-label="Pool filter"></select><select id="hlimit" aria-label="Row limit"><option value="25">25 rows</option><option value="50" selected>50 rows</option><option value="100">100 rows</option><option value="200">200 rows</option></select><button id="hrefresh">Refresh</button><label class="auto"><input type="checkbox" id="hauto"/> auto-refresh 5s</label></div>
|
|
1148
|
+
<table class="hist"><thead><tr><th>Time (UTC)</th><th>Pool</th><th>Key</th><th>Request</th><th>Status</th><th>Tokens</th><th>Error</th><th>Response</th></tr></thead><tbody id="hrows"></tbody></table>
|
|
1149
|
+
<div class="empty" id="hempty" hidden>No proxy calls recorded yet — send traffic through <span style="font-family:var(--font-mono)">/api/pools/:id/proxy</span>.</div>
|
|
1150
|
+
</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
</main><script>
|
|
1153
|
+
${clientCoreJs}
|
|
1154
|
+
var poolsById={};
|
|
1155
|
+
function stClass(s){if(s===null||s===undefined)return 'st0';if(s>=200&&s<300)return 'st2';if(s>=400&&s<500)return 'st4';if(s>=500)return 'st5';return 'st0';}
|
|
1156
|
+
function stLabel(s){return (s===null||s===undefined)?'—':String(s);}
|
|
1157
|
+
function mediaFor(c){
|
|
1158
|
+
if(c.encoding==='base64'&&c.body&&!c.truncated){
|
|
1159
|
+
var ct=String(c.content_type||'');
|
|
1160
|
+
var src='data:'+(ct||'application/octet-stream')+';base64,'+c.body;
|
|
1161
|
+
if(/audio/i.test(ct))return {kind:'audio',src:src};
|
|
1162
|
+
if(/video/i.test(ct))return {kind:'video',src:src};
|
|
1163
|
+
if(/image/i.test(ct))return {kind:'image',src:src};
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
if(c.encoding!=='base64'&&c.body&&!c.truncated){
|
|
1167
|
+
try{
|
|
1168
|
+
var d=JSON.parse(c.body);
|
|
1169
|
+
var first=d&&d.data&&d.data[0];
|
|
1170
|
+
var u=(first&&first.url)||d.url||'';
|
|
1171
|
+
if(typeof u==='string'&&/^https?:\\/\\//.test(u))return {kind:'url',src:u};
|
|
1172
|
+
}catch(e){}
|
|
1173
|
+
}
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
async function loadPools(){
|
|
1177
|
+
const pools=await j(await fetch('/api/pools'));
|
|
1178
|
+
poolsById={};
|
|
1179
|
+
const sel=document.getElementById('hpool');sel.innerHTML='';
|
|
1180
|
+
const all=document.createElement('option');all.value='';all.textContent='All pools';sel.appendChild(all);
|
|
1181
|
+
pools.forEach(function(p){poolsById[p.id]=p;var o=document.createElement('option');o.value=String(p.id);o.textContent='#'+p.id+' '+p.name+' ('+p.provider+')';sel.appendChild(o);});
|
|
1182
|
+
}
|
|
1183
|
+
async function refresh(){
|
|
1184
|
+
err('');
|
|
1185
|
+
const pid=document.getElementById('hpool').value;
|
|
1186
|
+
const limit=document.getElementById('hlimit').value;
|
|
1187
|
+
var rows=[];
|
|
1188
|
+
if(pid){
|
|
1189
|
+
rows=await j(await fetch('/api/pools/'+pid+'/calls?limit='+encodeURIComponent(limit)));
|
|
1190
|
+
if(rows&&rows.error){err(rows.error);return;}
|
|
1191
|
+
}else{
|
|
1192
|
+
const pools=await j(await fetch('/api/pools'));
|
|
1193
|
+
const lists=await Promise.all(pools.map(async function(p){return j(await fetch('/api/pools/'+p.id+'/calls?limit='+encodeURIComponent(limit)));}));
|
|
1194
|
+
lists.forEach(function(l){if(Array.isArray(l))rows=rows.concat(l);});
|
|
1195
|
+
rows.sort(function(a,b){return b.id-a.id;});
|
|
1196
|
+
rows=rows.slice(0,Number(limit));
|
|
1197
|
+
}
|
|
1198
|
+
const tb=document.getElementById('hrows');tb.innerHTML='';
|
|
1199
|
+
document.getElementById('hempty').hidden=rows.length>0;
|
|
1200
|
+
rows.forEach(function(c){
|
|
1201
|
+
var p=poolsById[c.pool_id]||{name:'#'+c.pool_id,provider:''};
|
|
1202
|
+
var tr=document.createElement('tr');
|
|
1203
|
+
var preview=c.body?(c.body.length>90?c.body.slice(0,90)+'…':c.body):'—';
|
|
1204
|
+
tr.innerHTML='<td class="mono"></td><td></td><td class="mono"></td><td class="mono"></td><td><span></span></td><td class="mono"></td><td class="mono"></td><td></td>';
|
|
1205
|
+
var tds=tr.querySelectorAll('td');
|
|
1206
|
+
tds[0].textContent=c.created_at||'';
|
|
1207
|
+
tds[1].textContent=p.name+(p.provider?' ('+p.provider+')':'');
|
|
1208
|
+
tds[2].textContent='#'+c.key_id;
|
|
1209
|
+
tds[3].textContent=c.method+' '+c.path;
|
|
1210
|
+
var badge=tds[4].querySelector('span');badge.className='st '+stClass(c.status);badge.textContent=stLabel(c.status);
|
|
1211
|
+
tds[5].textContent=c.tokens||'';
|
|
1212
|
+
tds[6].textContent=c.error||'';
|
|
1213
|
+
var cell=tds[7];
|
|
1214
|
+
var media=mediaFor(c);
|
|
1215
|
+
if(media&&(media.kind==='audio'||media.kind==='video'||media.kind==='image')){
|
|
1216
|
+
var mel;
|
|
1217
|
+
if(media.kind==='audio'){mel=document.createElement('audio');mel.controls=true;}
|
|
1218
|
+
else if(media.kind==='video'){mel=document.createElement('video');mel.controls=true;mel.style.maxWidth='320px';}
|
|
1219
|
+
else{mel=document.createElement('img');mel.alt='generated image';mel.style.maxWidth='240px';mel.style.borderRadius='8px';}
|
|
1220
|
+
mel.src=media.src;mel.preload='metadata';cell.appendChild(mel);
|
|
1221
|
+
}else if(media&&media.kind==='url'){
|
|
1222
|
+
var link=document.createElement('a');link.href=media.src;link.target='_blank';link.rel='noopener';
|
|
1223
|
+
if(/\.(png|jpe?g|webp|gif)(\\?|#|$)/i.test(media.src)){var th=document.createElement('img');th.src=media.src;th.alt='generated image';th.style.maxWidth='240px';th.style.borderRadius='8px';link.appendChild(th);}
|
|
1224
|
+
else link.textContent=media.src.slice(0,90);
|
|
1225
|
+
cell.appendChild(link);
|
|
1226
|
+
}else if(c.body){
|
|
1227
|
+
var d=document.createElement('details');d.className='body';
|
|
1228
|
+
var s=document.createElement('summary');s.textContent=preview;d.appendChild(s);
|
|
1229
|
+
var pre=document.createElement('pre');var cap=c.encoding==='base64'?'~2MB media cap':'4000 chars';pre.textContent=c.body+((c.truncated)?'\\n…(truncated at '+cap+', tail not retained)':'');d.appendChild(pre);
|
|
1230
|
+
cell.appendChild(d);
|
|
1231
|
+
}else cell.textContent='—';
|
|
1232
|
+
tb.appendChild(tr);
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
var autoTimer=null;
|
|
1236
|
+
function setAuto(on){
|
|
1237
|
+
if(autoTimer){clearInterval(autoTimer);autoTimer=null;}
|
|
1238
|
+
if(on)autoTimer=setInterval(refresh,5000);
|
|
1239
|
+
}
|
|
1240
|
+
document.getElementById('hrefresh').onclick=refresh;
|
|
1241
|
+
document.getElementById('hpool').onchange=refresh;
|
|
1242
|
+
document.getElementById('hlimit').onchange=refresh;
|
|
1243
|
+
document.getElementById('hauto').onchange=function(e){setAuto(e.target.checked);};
|
|
1244
|
+
async function init(){await loadPools();refresh();}
|
|
1245
|
+
init();
|
|
1246
|
+
</script></div></div>${shellJs}</body></html>`;
|
|
1247
|
+
}
|
|
1049
1248
|
export function playgroundPage() {
|
|
1050
1249
|
return `<!doctype html>
|
|
1051
1250
|
<html lang="en"><head><meta charset="utf-8"/>
|
|
1052
1251
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
1053
1252
|
<title>Playground — pool-anything</title>
|
|
1253
|
+
${subPageHead("/playground/", "Chat, image, audio and video playground to try a pooled API key across providers.")}
|
|
1054
1254
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
1055
1255
|
<style>
|
|
1056
1256
|
*{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);background:var(--paper-2);color:var(--ink)}
|
|
@@ -1063,6 +1263,11 @@ ${sharedLayoutCss}
|
|
|
1063
1263
|
.seg button.on{background:var(--card);box-shadow:var(--shadow-sm);color:var(--ink);font-weight:600}
|
|
1064
1264
|
.ctl{border:1px solid var(--line);border-radius:8px;min-height:34px;padding:0 10px;font-size:13px;background:var(--card);color:var(--ink);font-family:inherit}
|
|
1065
1265
|
select.ctl{max-width:220px}input.ctl.model{min-width:220px}
|
|
1266
|
+
.dd-combo{position:relative;display:inline-flex;align-items:stretch;min-width:0}
|
|
1267
|
+
.dd-combo input.ctl.model{border-radius:8px 0 0 8px;min-width:0}
|
|
1268
|
+
.model-chev{border:1px solid var(--line);border-left:0;background:var(--card);color:var(--ink-faint);border-radius:0 8px 8px 0;min-width:34px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-family:inherit}
|
|
1269
|
+
.model-chev:hover{background:var(--paper-2);color:var(--ink)}
|
|
1270
|
+
.dd-combo .dd-menu{left:auto;right:0;min-width:220px;max-width:340px}
|
|
1066
1271
|
.btn{border:1px solid var(--line);background:var(--card);border-radius:8px;min-height:34px;padding:0 12px;font-size:13px;cursor:pointer;color:var(--ink);font-family:inherit;display:inline-flex;align-items:center;gap:6px}
|
|
1067
1272
|
.btn:hover{background:var(--paper-2)}
|
|
1068
1273
|
.pg-grid{display:grid;grid-template-columns:minmax(0,1fr) 400px;gap:16px;align-items:start;min-height:560px}
|
|
@@ -1120,6 +1325,20 @@ details.params summary{cursor:pointer;color:var(--ink-soft);font-size:12px}
|
|
|
1120
1325
|
.modes button.off{opacity:.4;cursor:not-allowed}
|
|
1121
1326
|
.media-out{margin-top:12px;display:flex;flex-direction:column;gap:10px}
|
|
1122
1327
|
.media-out audio,.media-out video{width:100%;border-radius:10px;background:var(--code-dark)}
|
|
1328
|
+
.pa{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:10px 12px;box-shadow:var(--shadow-sm)}
|
|
1329
|
+
.pa-row{display:flex;align-items:center;gap:10px}
|
|
1330
|
+
.pa-play{width:40px;height:40px;flex-shrink:0;border:0;border-radius:50%;background:var(--green-bg);color:var(--green-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-family:inherit}
|
|
1331
|
+
.pa-play:hover{filter:brightness(.96)}
|
|
1332
|
+
.pa-play svg{width:16px;height:16px;display:block}
|
|
1333
|
+
.pa-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13.5px;font-weight:600;color:var(--ink)}
|
|
1334
|
+
.pa-x{flex-shrink:0;border:0;background:none;color:var(--ink-faint);cursor:pointer;font-size:18px;line-height:1;border-radius:6px;padding:2px 6px;font-family:inherit}
|
|
1335
|
+
.pa-x:hover{color:var(--ink);background:var(--paper-2)}
|
|
1336
|
+
.pa-wave{display:flex;align-items:flex-end;gap:1px;height:56px;margin-top:10px;cursor:pointer;border-radius:6px}
|
|
1337
|
+
.pa-wave:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
1338
|
+
.pa-bar{flex:1;min-width:1px;border-radius:2px;background:linear-gradient(to top,var(--line),var(--paper-2));min-height:4px;opacity:.85;transition:height .25s ease,background .2s ease,opacity .2s ease}
|
|
1339
|
+
.pa-bar.on{background:linear-gradient(to top,var(--green-ink),var(--green-chip));opacity:1}
|
|
1340
|
+
.pa-times{display:flex;justify-content:space-between;margin-top:6px;font-size:11px;color:var(--ink-soft);font-variant-numeric:tabular-nums}
|
|
1341
|
+
.pa audio{display:none}
|
|
1123
1342
|
.media-out .dl{font-size:13px;color:var(--ink)}
|
|
1124
1343
|
${docsComponentsCss}
|
|
1125
1344
|
</style></head><body><div class="shell" id="shell">${shellNav("/playground")}<div class="content">${sharedHeader}<main>
|
|
@@ -1130,7 +1349,7 @@ ${docsComponentsCss}
|
|
|
1130
1349
|
<div class="seg"><button id="tabChat" class="on" type="button">Chat</button><button id="tabStudio" type="button">Studio</button></div>
|
|
1131
1350
|
<div class="spacer"></div>
|
|
1132
1351
|
${ddSelect("pool", "Pool")}
|
|
1133
|
-
<input class="ctl model" id="model" value="" placeholder="model id" aria-label="Model" spellcheck="false" autocomplete="off"
|
|
1352
|
+
<span class="dd-combo" id="dd-model"><input class="ctl model" id="model" value="" placeholder="model id" aria-label="Model" spellcheck="false" autocomplete="off"/><button type="button" class="model-chev" id="modelChev" aria-haspopup="listbox" aria-expanded="false" aria-label="Choose model"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button><div class="dd-menu" id="modelMenu" role="listbox" hidden></div></span>
|
|
1134
1353
|
<button class="btn" id="copyTop" type="button" title="Copy code">⧉</button>
|
|
1135
1354
|
<button class="btn" id="hideCode" type="button"></> Hide code</button>
|
|
1136
1355
|
</div>
|
|
@@ -1144,6 +1363,20 @@ ${ddSelect("pool", "Pool")}
|
|
|
1144
1363
|
<label>Temperature<input class="ctl" id="temp" value="1" inputmode="decimal"/></label>
|
|
1145
1364
|
<label>Max tokens<input class="ctl" id="maxtokens" value="2048" inputmode="numeric"/></label>
|
|
1146
1365
|
<label>Top P<input class="ctl" id="topp" value="1" inputmode="decimal"/></label>
|
|
1366
|
+
</div>
|
|
1367
|
+
<div class="params-grid" id="probeVars" hidden style="margin-top:8px">
|
|
1368
|
+
<label id="probeOrgWrap">Org<input class="ctl" id="probeOrg" value="" placeholder="turso org slug" spellcheck="false" autocomplete="off"/></label>
|
|
1369
|
+
<label id="probeSidWrap">Account SID<input class="ctl" id="probeSid" value="" placeholder="twilio AC… SID" spellcheck="false" autocomplete="off"/></label>
|
|
1370
|
+
<label id="probeDomainWrap">Domain<input class="ctl" id="probeDomain" value="" placeholder="mailgun domain" spellcheck="false" autocomplete="off"/></label>
|
|
1371
|
+
<label id="probeDepthWrap">Depth<select class="ctl" id="probeDepth"><option value="basic">basic (1 credit)</option><option value="advanced">advanced (2 credits)</option><option value="fast">fast (1 credit)</option><option value="ultra-fast">ultra-fast (1 credit)</option></select></label>
|
|
1372
|
+
<label id="probeTopicWrap">Topic<select class="ctl" id="probeTopic"><option value="general">general</option><option value="news">news</option><option value="finance">finance</option></select></label>
|
|
1373
|
+
<label id="probeTimeWrap">Time range<select class="ctl" id="probeTime"><option value="">any time</option><option value="day">day</option><option value="week">week</option><option value="month">month</option><option value="year">year</option></select></label>
|
|
1374
|
+
<label id="probeMaxWrap">Max results<input class="ctl" id="probeMax" value="5" inputmode="numeric"/></label>
|
|
1375
|
+
<label id="probeAnswerWrap">Answer<select class="ctl" id="probeAnswer"><option value="basic">answer: basic</option><option value="advanced">answer: advanced</option><option value="false">answer: off</option></select></label>
|
|
1376
|
+
<label id="probeChunksWrap">Chunks/source<select class="ctl" id="probeChunks"><option value="3">3</option><option value="1">1</option><option value="2">2</option></select></label>
|
|
1377
|
+
<label id="probeAutoWrap">Auto params<select class="ctl" id="probeAuto"><option value="false">off</option><option value="true">on (2 credits)</option></select></label>
|
|
1378
|
+
<label id="probeInclWrap">Include domains<input class="ctl" id="probeIncl" value="" placeholder="comma-separated" spellcheck="false" autocomplete="off"/></label>
|
|
1379
|
+
<label id="probeExclWrap">Exclude domains<input class="ctl" id="probeExcl" value="" placeholder="comma-separated" spellcheck="false" autocomplete="off"/></label>
|
|
1147
1380
|
</div></details>
|
|
1148
1381
|
<div class="chat-foot">
|
|
1149
1382
|
<button class="iconbtn" id="addMsg" type="button" title="Add message">+</button>
|
|
@@ -1179,7 +1412,8 @@ ${ddSelect("codeLang", "Code language", '<option value="python">Python</option><
|
|
|
1179
1412
|
<div id="studioImage">
|
|
1180
1413
|
<div class="fld" style="margin-top:12px"><label>PROMPT</label><textarea class="ghost" id="imgPrompt" rows="2" placeholder="Describe the image you want to generate..."></textarea></div>
|
|
1181
1414
|
<div class="params-grid" style="margin-top:12px">
|
|
1182
|
-
<label>Model<input class="ctl" id="imgModel" value="" placeholder="image model id" spellcheck="false" autocomplete="off"/></label>
|
|
1415
|
+
<label>Model<input class="ctl" id="imgModel" value="" placeholder="image model id" spellcheck="false" autocomplete="off" list="imgModelList"/></label>
|
|
1416
|
+
<datalist id="imgModelList"></datalist>
|
|
1183
1417
|
<label>Size${ddSelect("imgSize", "Size", "<option>1024x1024</option><option>1792x1024</option><option>1024x1792</option><option>512x512</option>", "dd-block")}</label>
|
|
1184
1418
|
</div>
|
|
1185
1419
|
<div class="chat-foot" style="justify-content:center">
|
|
@@ -1190,8 +1424,10 @@ ${ddSelect("codeLang", "Code language", '<option value="python">Python</option><
|
|
|
1190
1424
|
<div id="studioAudio" hidden>
|
|
1191
1425
|
<div class="fld" style="margin-top:12px"><label>TEXT TO SPEAK</label><textarea class="ghost" id="audText" rows="2" placeholder="Text for TTS / audio generation..."></textarea></div>
|
|
1192
1426
|
<div class="params-grid" style="margin-top:12px">
|
|
1193
|
-
<label>Model<input class="ctl" id="audModel" value="" placeholder="audio model id" spellcheck="false" autocomplete="off"/></label>
|
|
1194
|
-
<
|
|
1427
|
+
<label>Model<input class="ctl" id="audModel" value="" placeholder="audio model id" spellcheck="false" autocomplete="off" list="audModelList"/></label>
|
|
1428
|
+
<datalist id="audModelList"></datalist>
|
|
1429
|
+
<label>Voice<input class="ctl" id="audVoice" value="alloy" spellcheck="false" autocomplete="off" list="audVoiceList"/></label>
|
|
1430
|
+
<datalist id="audVoiceList"></datalist>
|
|
1195
1431
|
<label>Format${ddSelect("audFormat", "Format", "<option>mp3</option><option>wav</option><option>opus</option>", "dd-block")}</label>
|
|
1196
1432
|
</div>
|
|
1197
1433
|
<div class="chat-foot" style="justify-content:center">
|
|
@@ -1202,7 +1438,8 @@ ${ddSelect("codeLang", "Code language", '<option value="python">Python</option><
|
|
|
1202
1438
|
<div id="studioVideo" hidden>
|
|
1203
1439
|
<div class="fld" style="margin-top:12px"><label>PROMPT</label><textarea class="ghost" id="vidPrompt" rows="2" placeholder="Describe the video you want to generate..."></textarea></div>
|
|
1204
1440
|
<div class="params-grid" style="margin-top:12px">
|
|
1205
|
-
<label>Model<input class="ctl" id="vidModel" value="" placeholder="video model id" spellcheck="false" autocomplete="off"/></label>
|
|
1441
|
+
<label>Model<input class="ctl" id="vidModel" value="" placeholder="video model id" spellcheck="false" autocomplete="off" list="vidModelList"/></label>
|
|
1442
|
+
<datalist id="vidModelList"></datalist>
|
|
1206
1443
|
<label>Duration${ddSelect("vidDur", "Duration", "<option>4s</option><option>8s</option><option>12s</option>", "dd-block")}</label>
|
|
1207
1444
|
<label>Resolution${ddSelect("vidRes", "Resolution", "<option>720p</option><option>1080p</option><option>480p</option>", "dd-block")}</label>
|
|
1208
1445
|
</div>
|
|
@@ -1212,15 +1449,35 @@ ${ddSelect("codeLang", "Code language", '<option value="python">Python</option><
|
|
|
1212
1449
|
<div class="media-out" id="videoOut"></div>
|
|
1213
1450
|
</div>
|
|
1214
1451
|
</div>
|
|
1452
|
+
<details class="params" style="margin-top:12px"><summary>Debug log</summary><div class="chat-foot" style="margin-top:8px">
|
|
1453
|
+
<button class="btn" id="copyDebug" type="button">⧉ Copy</button>
|
|
1454
|
+
<button class="btn" id="clearDebug" type="button">Clear</button>
|
|
1455
|
+
</div><pre id="debugLog" style="max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:12px">No requests yet.</pre></details>
|
|
1215
1456
|
</div>
|
|
1216
1457
|
</main><script>
|
|
1217
1458
|
${clientCoreJs}
|
|
1218
1459
|
${clientDropdownJs}
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
const VID_MODELS={
|
|
1223
|
-
|
|
1460
|
+
const ADAPTERS=${JSON.stringify(ADAPTERS)};
|
|
1461
|
+
// Derived defaults/caps from the adapter table (single source of truth —
|
|
1462
|
+
// edit src/playground/adapters.ts, not these maps).
|
|
1463
|
+
const MODELS={};const IMG_MODELS={};const AUD_MODELS={};const VID_MODELS={};const VOICE_DEFAULTS={};const CAPS={};
|
|
1464
|
+
for(var __pid in ADAPTERS){var __a=ADAPTERS[__pid];
|
|
1465
|
+
if(__a.defaults.chat)MODELS[__pid]=__a.defaults.chat;
|
|
1466
|
+
if(__a.defaults.image)IMG_MODELS[__pid]=__a.defaults.image;
|
|
1467
|
+
if(__a.defaults.audio)AUD_MODELS[__pid]=__a.defaults.audio;
|
|
1468
|
+
VOICE_DEFAULTS[__pid]=__a.voiceDefault||'alloy';
|
|
1469
|
+
CAPS[__pid]={vision:__a.vision?1:0,audioIn:__a.audioIn?1:0,videoIn:__a.videoIn?1:0,imageOut:__a.image?1:0,audioOut:__a.audio?1:0,videoOut:__a.video?1:0};
|
|
1470
|
+
}
|
|
1471
|
+
function curAdapter(){var p=curProv();return (p&&ADAPTERS[p])||null;}
|
|
1472
|
+
function getPath(o,path){try{var parts=String(path).split('.');var cur=o;for(var i=0;i<parts.length;i++){if(cur==null)return undefined;var k=parts[i];if(k==='$')continue;cur=/^[0-9]+$/.test(k)?cur[Number(k)]:cur[k];}return cur;}catch(e){return undefined;}}
|
|
1473
|
+
function fillTpl(t,vars){if(typeof t==='string'){if(Object.prototype.hasOwnProperty.call(vars,t))return vars[t];var out=t;for(var k in vars){var v=vars[k];if(v===null||v===undefined||typeof v==='object')continue;out=out.split(k).join(v);}return out;}if(Array.isArray(t))return t.map(function(x){return fillTpl(x,vars);});if(t&&typeof t==='object'){var o={};for(var k in t){var fv=fillTpl(t[k],vars);if(fv!==null&&fv!==undefined)o[k]=fv;}return o;}return t;}
|
|
1474
|
+
function csvList(s){return String(s||'').split(',').map(function(x){return x.trim();}).filter(Boolean);}
|
|
1475
|
+
function probeVars(text){return {'<<QUERY>>':text,'<<TEXT>>':text,'<<MODEL>>':document.getElementById('model').value.trim(),'<<ORG>>':document.getElementById('probeOrg').value.trim(),'<<SID>>':document.getElementById('probeSid').value.trim(),'<<DOMAIN>>':document.getElementById('probeDomain').value.trim(),'<<DEPTH>>':document.getElementById('probeDepth').value,'<<TOPIC>>':document.getElementById('probeTopic').value,'<<TIME_RANGE>>':document.getElementById('probeTime').value||null,'<<MAX_RESULTS>>':Math.max(1,Math.min(20,parseInt(document.getElementById('probeMax').value,10)||5)),'<<ANSWER>>':document.getElementById('probeAnswer').value==='false'?false:document.getElementById('probeAnswer').value,'<<CHUNKS>>':Math.max(1,Math.min(3,parseInt(document.getElementById('probeChunks').value,10)||3)),'<<AUTO>>':document.getElementById('probeAuto').value==='true','<<INCL_DOMAINS>>':(function(){var l=csvList(document.getElementById('probeIncl').value);return l.length?l:null;})(),'<<EXCL_DOMAINS>>':(function(){var l=csvList(document.getElementById('probeExcl').value);return l.length?l:null;})(),'<<VOICE>>':document.getElementById('audVoice').value.trim(),'<<SIZE>>':(document.getElementById('imgSize')||{value:''}).value};}
|
|
1476
|
+
function extractText(d,paths){var list=(paths||[]).concat(['choices.0.message.content','content.0.text','candidates.0.content.parts.0.text']);for(var i=0;i<list.length;i++){if(list[i]==='$')continue;var v=getPath(d,list[i]);if(typeof v==='string'&&v)return v;if(typeof v==='number')return String(v);if(v&&typeof v==='object'){var s=JSON.stringify(v);if(s&&s!=='{}'&&s!=='[]')return s;}}return null;}
|
|
1477
|
+
function anthropicBody(p,msgs){var sys=[];var rest=[];msgs.forEach(function(m){if(m.role==='system'&&typeof m.content==='string')sys.push(m.content);else rest.push(m);});var b={model:p.model,max_tokens:p.maxtokens,messages:rest.length?rest:[{role:'user',content:'hi'}]};if(sys.length)b.system=sys.join('\\n\\n');return b;}
|
|
1478
|
+
function geminiBody(p,msgs){var sys=[];var contents=[];msgs.forEach(function(m){var parts=Array.isArray(m.content)?m.content:[{text:typeof m.content==='string'?m.content:JSON.stringify(m.content)}];if(m.role==='system')parts.forEach(function(x){if(x.text)sys.push(x.text);});else if(m.role==='assistant')contents.push({role:'model',parts:parts});else contents.push({role:'user',parts:parts});});if(!contents.length)contents.push({role:'user',parts:[{text:'hi'}]});var b={contents:contents};if(sys.length)b.system_instruction={parts:[{text:sys.join('\\n\\n')}]};return b;}
|
|
1479
|
+
function chatBodyFor(prov,p,msgs){var a=curAdapter();if(a&&a.chatStyle==='anthropic')return anthropicBody(p,msgs);if(a&&a.chatStyle==='gemini')return geminiBody(p,msgs);return {model:p.model,messages:msgs,temperature:p.temp,max_tokens:p.maxtokens,top_p:p.topp};}
|
|
1480
|
+
function chatPathFor(prov,model,fallback){var a=curAdapter();var t=(a&&a.chatPath)||fallback||'/chat/completions';return t.split('<<MODEL>>').join(model||'');}
|
|
1224
1481
|
const NO_VISION_RE=/tts|whisper|transcri|embed|image|flux|diffusion|imagen|dall|sonic|nova|stt|audit/i;
|
|
1225
1482
|
function capsFor(prov,model){
|
|
1226
1483
|
const b=CAPS[prov]||{vision:0,audioIn:0,videoIn:0,imageOut:0,audioOut:0,videoOut:0};
|
|
@@ -1244,6 +1501,17 @@ const IMG_MIMES=['image/png','image/jpeg','image/webp'];
|
|
|
1244
1501
|
const AUD_MIMES=['audio/mpeg','audio/wav','audio/webm','audio/ogg','audio/mp4'];
|
|
1245
1502
|
const VID_MIMES=['video/mp4','video/webm'];
|
|
1246
1503
|
const MAX_MEDIA_BYTES=8000000;
|
|
1504
|
+
// Debug log: last 20 playground proxy calls (request + upstream response).
|
|
1505
|
+
// Rendered into #debugLog; never includes API keys (auth is server-side).
|
|
1506
|
+
let pgLog=[];
|
|
1507
|
+
function pgLogAdd(e){
|
|
1508
|
+
e.t=new Date().toISOString().slice(11,19);
|
|
1509
|
+
pgLog.unshift(e);if(pgLog.length>20)pgLog.length=20;
|
|
1510
|
+
var el=document.getElementById('debugLog');
|
|
1511
|
+
if(el)el.textContent=pgLog.map(function(x){
|
|
1512
|
+
return '['+x.t+'] '+x.tab+' '+x.method+' '+x.path+' prov='+x.prov+' model='+(x.model||'-')+' → up='+x.upstream+' key='+(x.key||'-')+' '+x.ms+'ms'+(x.err?' ERR='+x.err:'')+'\\n req: '+x.req+'\\n res: '+x.res;
|
|
1513
|
+
}).join('\\n')||'No requests yet.';
|
|
1514
|
+
}
|
|
1247
1515
|
let pendingMedia=[];
|
|
1248
1516
|
function fmtKB(n){return Math.round(n/1024)+'KB';}
|
|
1249
1517
|
function renderMediaPrev(){
|
|
@@ -1316,6 +1584,12 @@ function chatContentFor(prov,text,media){
|
|
|
1316
1584
|
if(nonImg.length)return null;
|
|
1317
1585
|
return anthropicBlocks(text,media);
|
|
1318
1586
|
}
|
|
1587
|
+
var a=curAdapter();
|
|
1588
|
+
if(a&&a.chatStyle==='gemini'){
|
|
1589
|
+
const parts=[{text:text}];
|
|
1590
|
+
media.forEach(function(x){if(x.kind==='image')parts.push({inline_data:{mime_type:x.mime,data:x.b64}});});
|
|
1591
|
+
return parts;
|
|
1592
|
+
}
|
|
1319
1593
|
return openAiParts(text,media);
|
|
1320
1594
|
}
|
|
1321
1595
|
let lastDefault='';
|
|
@@ -1418,6 +1692,9 @@ function updateCode(){
|
|
|
1418
1692
|
const url=location.origin+'/api/pools/'+pid+'/proxy';
|
|
1419
1693
|
const lang=document.getElementById('codeLang').value;
|
|
1420
1694
|
const prov=curProv(),model=p.model;
|
|
1695
|
+
const pa=curAdapter();
|
|
1696
|
+
if(pa&&!pa.chatPath&&pa.probe){updateProbeCode();return;}
|
|
1697
|
+
const effPath=p.path||chatPathFor(prov,p.model,'/chat/completions');
|
|
1421
1698
|
const c=capsFor(prov,model);
|
|
1422
1699
|
const bad=pendingMedia.filter(function(a){return (a.kind==='image'&&!c.vision)||(a.kind==='audio'&&!c.audioIn)||(a.kind==='video'&&!c.videoIn);});
|
|
1423
1700
|
if(pendingMedia.length&&bad.length){
|
|
@@ -1426,10 +1703,11 @@ function updateCode(){
|
|
|
1426
1703
|
document.getElementById('code').innerHTML='<span class="c">'+esc(rawSnippet)+'</span>';
|
|
1427
1704
|
return;
|
|
1428
1705
|
}
|
|
1429
|
-
const body=
|
|
1706
|
+
const body=chatBodyFor(prov,p,msgs);
|
|
1707
|
+
const style=(pa&&pa.chatStyle)||'openai';
|
|
1430
1708
|
const shortMsgs=JSON.stringify(msgs,function(k,v){return k==='data'&&typeof v==='string'&&v.length>64?v.slice(0,64)+'…<base64 '+v.length+' chars>':k==='url'&&typeof v==='string'&&v.indexOf('base64,')>=0?v.slice(0,80)+'…<base64>':v;});
|
|
1431
1709
|
if(lang==='curl'){
|
|
1432
|
-
rawSnippet='curl -X POST '+url+' \\\\\\n -H "Content-Type: application/json" \\\\\\n -d '+pyStr(JSON.stringify({path:
|
|
1710
|
+
rawSnippet='curl -X POST '+url+' \\\\\\n -H "Content-Type: application/json" \\\\\\n -d '+pyStr(JSON.stringify({path:effPath,method:'POST',body}))+';\\n';
|
|
1433
1711
|
document.getElementById('code').innerHTML='<span class="c"># runs through your pooled keys — auth injected server-side</span>\\n'+esc(rawSnippet);
|
|
1434
1712
|
return;
|
|
1435
1713
|
}
|
|
@@ -1438,8 +1716,9 @@ function updateCode(){
|
|
|
1438
1716
|
lines.push('resp = requests.<span class="fn">post</span>(');
|
|
1439
1717
|
lines.push(' '+pyStr(url)+',');
|
|
1440
1718
|
lines.push(' json={');
|
|
1441
|
-
lines.push(' '+pyStr('path')+': '+pyStr(
|
|
1719
|
+
lines.push(' '+pyStr('path')+': '+pyStr(effPath)+',');
|
|
1442
1720
|
lines.push(' '+pyStr('method')+': '+pyStr('POST')+',');
|
|
1721
|
+
if(style==='openai'){
|
|
1443
1722
|
lines.push(' '+pyStr('body')+': {');
|
|
1444
1723
|
lines.push(' '+pyStr('model')+': '+pyStr(p.model)+',');
|
|
1445
1724
|
lines.push(' '+pyStr('messages')+': '+esc(shortMsgs)+',');
|
|
@@ -1447,6 +1726,9 @@ function updateCode(){
|
|
|
1447
1726
|
lines.push(' '+pyStr('max_tokens')+': <span class="n">'+p.maxtokens+'</span>,');
|
|
1448
1727
|
lines.push(' '+pyStr('top_p')+': <span class="n">'+p.topp+'</span>,');
|
|
1449
1728
|
lines.push(' },');
|
|
1729
|
+
}else{
|
|
1730
|
+
lines.push(' '+pyStr('body')+': '+esc(JSON.stringify(body))+',');
|
|
1731
|
+
}
|
|
1450
1732
|
lines.push(' },');
|
|
1451
1733
|
lines.push(' timeout=<span class="n">120</span>,');
|
|
1452
1734
|
lines.push(')');
|
|
@@ -1454,7 +1736,7 @@ function updateCode(){
|
|
|
1454
1736
|
lines.push('<span class="fn">print</span>(data[<span class="s">'+pyStr('status')+'</span>])');
|
|
1455
1737
|
lines.push('<span class="fn">print</span>(data[<span class="s">'+pyStr('body')+'</span>])');
|
|
1456
1738
|
document.getElementById('code').innerHTML=lines.join('\\n');
|
|
1457
|
-
rawSnippet='import requests\\n\\nresp = requests.post(\\n '+JSON.stringify(url)+',\\n json='+JSON.stringify({path:
|
|
1739
|
+
rawSnippet='import requests\\n\\nresp = requests.post(\\n '+JSON.stringify(url)+',\\n json='+JSON.stringify({path:effPath,method:'POST',body},null,4)+',\\n timeout=120,\\n)\\ndata = resp.json()\\nprint(data["status"])\\nprint(data["body"])\\n';
|
|
1458
1740
|
}
|
|
1459
1741
|
function studioMode(){if(document.getElementById('modeAudio').classList.contains('on'))return 'audio';if(document.getElementById('modeVideo').classList.contains('on'))return 'video';return 'image';}
|
|
1460
1742
|
function setStudioMode(m){
|
|
@@ -1465,10 +1747,11 @@ function setStudioMode(m){
|
|
|
1465
1747
|
document.getElementById('studioAudio').hidden=m!=='audio';
|
|
1466
1748
|
document.getElementById('studioVideo').hidden=m!=='video';
|
|
1467
1749
|
updateStudioModes();updateCode();
|
|
1750
|
+
pgSyncHash();
|
|
1468
1751
|
}
|
|
1469
1752
|
function updateStudioModes(){
|
|
1470
1753
|
const prov=curProv();
|
|
1471
|
-
const im=document.getElementById('imgModel').value.trim()||
|
|
1754
|
+
const im=document.getElementById('imgModel').value.trim()||IMG_MODELS[prov]||'';
|
|
1472
1755
|
const c=capsFor(prov,im);
|
|
1473
1756
|
const mi=document.getElementById('modeImage'),ma=document.getElementById('modeAudio'),mv=document.getElementById('modeVideo');
|
|
1474
1757
|
const note=document.getElementById('studioNote');
|
|
@@ -1494,9 +1777,22 @@ function studioParams(){
|
|
|
1494
1777
|
};
|
|
1495
1778
|
}
|
|
1496
1779
|
function studioPayload(s){
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1780
|
+
var a=curAdapter();
|
|
1781
|
+
var spec=s.mode==='audio'?(a&&a.audio):s.mode==='video'?(a&&a.video):(a&&a.image);
|
|
1782
|
+
if(!spec){
|
|
1783
|
+
if(s.mode==='audio')return {path:'/audio/speech',body:{model:s.model,input:s.prompt,voice:s.voice,response_format:s.format}};
|
|
1784
|
+
if(s.mode==='video')return {path:'/videos/generations',body:{model:s.model,prompt:s.prompt,duration:s.duration,resolution:s.resolution,n:1}};
|
|
1785
|
+
return {path:'/images/generations',body:{model:s.model,prompt:s.prompt,size:s.size,n:1}};
|
|
1786
|
+
}
|
|
1787
|
+
var vars={'<<MODEL>>':s.model,'<<TEXT>>':s.prompt,'<<VOICE>>':s.voice||VOICE_DEFAULTS[curProv()]||'alloy','<<SIZE>>':s.size||'1024x1024'};
|
|
1788
|
+
var usePath=spec.path;
|
|
1789
|
+
if(spec.altPath&&spec.altMatch){try{if(new RegExp(spec.altMatch,'i').test(s.model))usePath=spec.altPath;}catch(e){}}
|
|
1790
|
+
var out={path:fillTpl(usePath,vars),method:'POST'};
|
|
1791
|
+
if(spec.query)out.query=fillTpl(spec.query,vars);
|
|
1792
|
+
var b=fillTpl(spec.body,vars);
|
|
1793
|
+
if(s.mode==='audio'&&s.format&&b&&typeof b==='object'&&curProv()==='openai')b.response_format=s.format;
|
|
1794
|
+
if(a&&a.formOnly)out.form=b;else out.body=b;
|
|
1795
|
+
return out;
|
|
1500
1796
|
}
|
|
1501
1797
|
function updateStudioCode(){
|
|
1502
1798
|
const s=studioParams();
|
|
@@ -1504,9 +1800,13 @@ function updateStudioCode(){
|
|
|
1504
1800
|
const url=location.origin+'/api/pools/'+pid+'/proxy';
|
|
1505
1801
|
const lang=document.getElementById('codeLang').value;
|
|
1506
1802
|
const pl=studioPayload(s);
|
|
1507
|
-
const
|
|
1803
|
+
const shown={path:pl.path,method:'POST'};
|
|
1804
|
+
if(pl.query)shown.query=pl.query;
|
|
1805
|
+
if(pl.form)shown.form=pl.form;else shown.body=pl.body;
|
|
1806
|
+
const bodyKey=pl.form?'form':'body';
|
|
1807
|
+
const body=pl.form||pl.body;
|
|
1508
1808
|
if(lang==='curl'){
|
|
1509
|
-
rawSnippet='curl -X POST '+url+' \\\\\\n -H "Content-Type: application/json" \\\\\\n -d '+pyStr(JSON.stringify(
|
|
1809
|
+
rawSnippet='curl -X POST '+url+' \\\\\\n -H "Content-Type: application/json" \\\\\\n -d '+pyStr(JSON.stringify(shown))+';\\n';
|
|
1510
1810
|
document.getElementById('code').innerHTML='<span class="c"># runs through your pooled keys — auth injected server-side</span>\\n'+esc(rawSnippet);
|
|
1511
1811
|
return;
|
|
1512
1812
|
}
|
|
@@ -1515,9 +1815,9 @@ function updateStudioCode(){
|
|
|
1515
1815
|
lines.push('resp = requests.<span class="fn">post</span>(');
|
|
1516
1816
|
lines.push(' '+pyStr(url)+',');
|
|
1517
1817
|
lines.push(' json={');
|
|
1518
|
-
lines.push(' '+pyStr('path')+': '+pyStr(
|
|
1818
|
+
lines.push(' '+pyStr('path')+': '+pyStr(shown.path)+',');
|
|
1519
1819
|
lines.push(' '+pyStr('method')+': '+pyStr('POST')+',');
|
|
1520
|
-
lines.push(' '+pyStr(
|
|
1820
|
+
lines.push(' '+pyStr(bodyKey)+': '+esc(JSON.stringify(body,null,4))+',');
|
|
1521
1821
|
lines.push(' },');
|
|
1522
1822
|
lines.push(' timeout=<span class="n">120</span>,');
|
|
1523
1823
|
lines.push(')');
|
|
@@ -1525,16 +1825,61 @@ function updateStudioCode(){
|
|
|
1525
1825
|
lines.push('<span class="fn">print</span>(data[<span class="s">'+pyStr('status')+'</span>])');
|
|
1526
1826
|
lines.push('<span class="fn">print</span>(data[<span class="s">'+pyStr('body')+'</span>])');
|
|
1527
1827
|
document.getElementById('code').innerHTML=lines.join('\\n');
|
|
1528
|
-
rawSnippet='import requests\\n\\nresp = requests.post(\\n '+JSON.stringify(url)+',\\n json='+JSON.stringify(
|
|
1828
|
+
rawSnippet='import requests\\n\\nresp = requests.post(\\n '+JSON.stringify(url)+',\\n json='+JSON.stringify(shown,null,4)+',\\n timeout=120,\\n)\\ndata = resp.json()\\nprint(data["status"])\\nprint(data["body"])\\n';
|
|
1529
1829
|
}
|
|
1530
1830
|
function mediaResultBox(id){return document.getElementById(id);}
|
|
1831
|
+
const PA_PLAY='<svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>';
|
|
1832
|
+
const PA_PAUSE='<svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="5" width="3.4" height="14" rx="1" fill="currentColor"/><rect x="13.6" y="5" width="3.4" height="14" rx="1" fill="currentColor"/></svg>';
|
|
1833
|
+
var __paCtx=null;
|
|
1834
|
+
function paContext(){try{if(!__paCtx)__paCtx=new (window.AudioContext||window.webkitAudioContext)();if(__paCtx.state==='suspended')__paCtx.resume();return __paCtx;}catch(e){return null;}}
|
|
1835
|
+
function fmtTime(s){if(!isFinite(s)||s<0)s=0;var m=Math.floor(s/60);var ss=Math.floor(s%60);return m+':'+(ss<10?'0':'')+ss;}
|
|
1836
|
+
function audioPlayer(src,title){
|
|
1837
|
+
var wrap=document.createElement('div');wrap.className='pa';
|
|
1838
|
+
var row=document.createElement('div');row.className='pa-row';
|
|
1839
|
+
var pb=document.createElement('button');pb.type='button';pb.className='pa-play';pb.setAttribute('aria-label','Play audio');pb.innerHTML=PA_PLAY;
|
|
1840
|
+
var tt=document.createElement('div');tt.className='pa-title';tt.textContent=title||'audio';
|
|
1841
|
+
var x=document.createElement('button');x.type='button';x.className='pa-x';x.setAttribute('aria-label','Remove player');x.textContent='×';
|
|
1842
|
+
row.appendChild(pb);row.appendChild(tt);row.appendChild(x);wrap.appendChild(row);
|
|
1843
|
+
var wave=document.createElement('div');wave.className='pa-wave';wave.setAttribute('role','slider');wave.setAttribute('aria-label','Seek in audio');wave.tabIndex=0;
|
|
1844
|
+
wrap.appendChild(wave);
|
|
1845
|
+
var times=document.createElement('div');times.className='pa-times';
|
|
1846
|
+
var cur=document.createElement('span');cur.textContent='0:00';
|
|
1847
|
+
var dur=document.createElement('span');dur.textContent='0:00';
|
|
1848
|
+
times.appendChild(cur);times.appendChild(dur);wrap.appendChild(times);
|
|
1849
|
+
var au=document.createElement('audio');au.src=src;au.preload='metadata';wrap.appendChild(au);
|
|
1850
|
+
var bars=[];
|
|
1851
|
+
function paint(f){for(var i=0;i<bars.length;i++)bars[i].classList.toggle('on',bars[i]._f<=f);}
|
|
1852
|
+
function buildBars(vals){wave.innerHTML='';bars=[];for(var i=0;i<vals.length;i++){var b=document.createElement('div');b.className='pa-bar';b._f=(i+1)/vals.length;b.style.height=Math.max(8,Math.round(vals[i]*100))+'%';bars.push(b);wave.appendChild(b);}}
|
|
1853
|
+
var init=[];for(var k=0;k<150;k++)init.push(0.12);
|
|
1854
|
+
buildBars(init);
|
|
1855
|
+
function decode(){
|
|
1856
|
+
var ctx=paContext();if(!ctx)return;
|
|
1857
|
+
fetch(src).then(function(r){return r.arrayBuffer();}).then(function(buf){return ctx.decodeAudioData(buf);}).then(function(ab){
|
|
1858
|
+
var ch=ab.getChannelData(0);var n=150;var step=Math.max(1,Math.floor(ch.length/n));
|
|
1859
|
+
var vals=[];var mx=0.0001;
|
|
1860
|
+
for(var i=0;i<n;i++){var m=0;for(var j=i*step;j<Math.min(ch.length,(i+1)*step);j+=7){var v=Math.abs(ch[j]);if(v>m)m=v;}vals.push(m);if(m>mx)mx=m;}
|
|
1861
|
+
buildBars(vals.map(function(v){return v/mx;}));
|
|
1862
|
+
}).catch(function(){});
|
|
1863
|
+
}
|
|
1864
|
+
pb.onclick=function(){if(au.paused){try{var pr=au.play();if(pr&&pr.catch)pr.catch(function(){});}catch(e){}}else au.pause();};
|
|
1865
|
+
var raf=0;var lastSec=-1;
|
|
1866
|
+
function tick(){if(au.paused)return;var s=Math.floor(au.currentTime);if(s!==lastSec){lastSec=s;cur.textContent=fmtTime(au.currentTime);}paint(au.duration?au.currentTime/au.duration:0);raf=requestAnimationFrame(tick);}
|
|
1867
|
+
au.onplay=function(){pb.innerHTML=PA_PAUSE;pb.setAttribute('aria-label','Pause audio');cancelAnimationFrame(raf);lastSec=-1;tick();};
|
|
1868
|
+
au.onpause=function(){pb.innerHTML=PA_PLAY;pb.setAttribute('aria-label','Play audio');cancelAnimationFrame(raf);};
|
|
1869
|
+
au.onloadedmetadata=function(){dur.textContent=fmtTime(au.duration);};
|
|
1870
|
+
au.ontimeupdate=function(){cur.textContent=fmtTime(au.currentTime);};
|
|
1871
|
+
au.onended=function(){cancelAnimationFrame(raf);paint(0);cur.textContent=fmtTime(0);};
|
|
1872
|
+
x.onclick=function(){try{au.pause();}catch(e){}wrap.remove();};
|
|
1873
|
+
wave.onclick=function(e){var r=wave.getBoundingClientRect();var f=(e.clientX-r.left)/r.width;if(au.duration)au.currentTime=Math.max(0,Math.min(1,f))*au.duration;};
|
|
1874
|
+
wave.onkeydown=function(e){if(!au.duration)return;if(e.key==='ArrowRight'){au.currentTime=Math.min(au.duration,au.currentTime+5);e.preventDefault();}else if(e.key==='ArrowLeft'){au.currentTime=Math.max(0,au.currentTime-5);e.preventDefault();}else if(e.key==='Home'){au.currentTime=0;e.preventDefault();}else if(e.key==='End'){au.currentTime=au.duration;e.preventDefault();}};
|
|
1875
|
+
decode();
|
|
1876
|
+
return wrap;
|
|
1877
|
+
}
|
|
1531
1878
|
function appendMediaOut(boxId,kind,src,mime,caption,downloadName){
|
|
1532
1879
|
const box=mediaResultBox(boxId);
|
|
1533
1880
|
const wrap=document.createElement('div');
|
|
1534
|
-
|
|
1535
|
-
el.controls=true;el.src=src;
|
|
1536
|
-
if(kind==='video'){el.preload='metadata';}
|
|
1537
|
-
wrap.appendChild(el);
|
|
1881
|
+
if(kind==='audio')wrap.appendChild(audioPlayer(src,caption||downloadName||'audio'));
|
|
1882
|
+
else{const el=document.createElement('video');el.controls=true;el.src=src;el.preload='metadata';wrap.appendChild(el);}
|
|
1538
1883
|
const cap=document.createElement('div');cap.style.cssText='font-size:12px;color:var(--ink-soft)';cap.textContent=caption;
|
|
1539
1884
|
wrap.appendChild(cap);
|
|
1540
1885
|
const a=document.createElement('a');a.className='dl';a.href=src;a.download=downloadName;a.textContent='Download '+kind;
|
|
@@ -1561,6 +1906,8 @@ function detectMediaInBody(r){
|
|
|
1561
1906
|
}
|
|
1562
1907
|
if(first.url)return {kind:studioMode(),src:first.url,mime:'',json:true,caption:first.revised_prompt||''};
|
|
1563
1908
|
}
|
|
1909
|
+
if(Array.isArray(d.output)&&d.output.length&&typeof d.output[0]==='string'&&d.output[0].indexOf('http')===0)
|
|
1910
|
+
return {kind:'image',src:d.output[0],mime:'',json:true};
|
|
1564
1911
|
if(d.audio||d.audio_base64||d.data_base64){
|
|
1565
1912
|
const b=d.audio||d.audio_base64||d.data_base64;
|
|
1566
1913
|
return {kind:'audio',src:'data:audio/mpeg;base64,'+b,mime:'audio/mpeg',json:true};
|
|
@@ -1568,6 +1915,32 @@ function detectMediaInBody(r){
|
|
|
1568
1915
|
}catch{}
|
|
1569
1916
|
return null;
|
|
1570
1917
|
}
|
|
1918
|
+
async function pollReplicate(r){
|
|
1919
|
+
if(curProv()!=='replicate')return null;
|
|
1920
|
+
let id=null;
|
|
1921
|
+
try{const d=JSON.parse(r.body||'{}');if(d.status==='succeeded')return r;id=d.id||null;}catch(e){return null;}
|
|
1922
|
+
if(!id)return null;
|
|
1923
|
+
const pid=Number(document.getElementById('pool').value);
|
|
1924
|
+
for(let i=0;i<45;i++){
|
|
1925
|
+
await new Promise(function(res){setTimeout(res,2000);});
|
|
1926
|
+
const p=await j(await fetch('/api/pools/'+pid+'/proxy',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({path:'/predictions/'+id,method:'GET'})}));
|
|
1927
|
+
if(p.error)continue;
|
|
1928
|
+
try{const pd=JSON.parse(p.body||'{}');if(pd.status==='succeeded'||pd.status==='failed'||pd.status==='canceled')return p;}catch(e){return p;}
|
|
1929
|
+
}
|
|
1930
|
+
err('Replicate prediction timed out; check its dashboard for the result.');
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
function triedNote(r){
|
|
1934
|
+
var t=r.tried;
|
|
1935
|
+
if(!t||!t.length)return '';
|
|
1936
|
+
var bits=t.map(function(x){
|
|
1937
|
+
var what=x.status?('HTTP '+x.status):(x.detail||'skipped');
|
|
1938
|
+
return '#'+x.key_id+' ('+(x.label||'?')+'): '+what;
|
|
1939
|
+
});
|
|
1940
|
+
var s=' — tried '+bits.join(', ');
|
|
1941
|
+
if(r.error==='all keys failed or cooling down')s+='. Keys recover on their own (30s backoff, doubling); re-saving a key in the key manager clears its cooldown immediately.';
|
|
1942
|
+
return s;
|
|
1943
|
+
}
|
|
1571
1944
|
async function sendStudio(){
|
|
1572
1945
|
err('');
|
|
1573
1946
|
const s=studioParams();
|
|
@@ -1575,6 +1948,7 @@ async function sendStudio(){
|
|
|
1575
1948
|
if(s.mode==='image'&&!s.prompt.trim()){err('Describe the image first.');return;}
|
|
1576
1949
|
if(s.mode!=='image'&&!s.prompt.trim()){err(s.mode==='audio'?'Type text to synthesize first.':'Describe the video first.');return;}
|
|
1577
1950
|
if(!s.model){err('Enter a model id.');return;}
|
|
1951
|
+
if(/[{}]/.test(s.model)){err('Model "'+s.model+'" looks like a template — replace {...} with a real id (e.g. aura-2-thalia-en for Deepgram).');return;}
|
|
1578
1952
|
const prov=curProv();
|
|
1579
1953
|
const need=s.mode==='image'?'imageOut':s.mode==='audio'?'audioOut':'videoOut';
|
|
1580
1954
|
if(!capsFor(prov,s.model)[need]){err(gateNote(need,prov,s.model));return;}
|
|
@@ -1583,10 +1957,17 @@ async function sendStudio(){
|
|
|
1583
1957
|
const label=btn.innerHTML;
|
|
1584
1958
|
btn.disabled=true;btn.textContent=s.mode==='image'?'Generating…':s.mode==='audio'?'Synthesizing…':'Rendering…';
|
|
1585
1959
|
try{
|
|
1586
|
-
const
|
|
1587
|
-
if(
|
|
1588
|
-
|
|
1589
|
-
|
|
1960
|
+
const preq={path:pl.path,method:'POST'};
|
|
1961
|
+
if(pl.query)preq.query=pl.query;
|
|
1962
|
+
if(pl.form)preq.form=pl.form;else preq.body=pl.body;
|
|
1963
|
+
const t0=Date.now();
|
|
1964
|
+
const r=await j(await fetch('/api/pools/'+pid+'/proxy',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(preq)}));
|
|
1965
|
+
pgLogAdd({tab:'studio-'+s.mode,method:'POST',path:preq.path,prov:prov,model:s.model,upstream:r.error?'ERR':r.status,key:r.key_id?('#'+r.key_id):'',ms:Date.now()-t0,err:r.error||'',req:JSON.stringify(preq).slice(0,1500),res:String(r.body||'').slice(0,800)});
|
|
1966
|
+
if(r.error){err(r.error+(r.body?' — '+String(r.body).slice(0,300):'')+triedNote(r));return;}
|
|
1967
|
+
const rep=await pollReplicate(r);
|
|
1968
|
+
if(rep&&rep!==r)pgLogAdd({tab:'studio-poll',method:'GET',path:'replicate prediction',prov:prov,model:s.model,upstream:rep.error?'ERR':rep.status,key:'',ms:0,err:rep.error||'',req:'',res:String(rep.body||'').slice(0,800)});
|
|
1969
|
+
const hit=detectMediaInBody(rep||r);
|
|
1970
|
+
if(!hit){var said=String((rep||r).body||'');err('No media in upstream response (status '+r.status+', '+pl.path+'). Upstream said: '+(said?said.slice(0,300):'(empty — often a wrong path or model id for this provider)'));return;}
|
|
1590
1971
|
if(s.mode==='image'){
|
|
1591
1972
|
let src=hit.src;
|
|
1592
1973
|
if(hit.kind!=='image'&&!hit.json){err('Unexpected media kind in response.');return;}
|
|
@@ -1595,12 +1976,82 @@ async function sendStudio(){
|
|
|
1595
1976
|
const img=document.createElement('img');img.src=src;img.alt=s.prompt;img.loading='lazy';
|
|
1596
1977
|
const cap=document.createElement('figcaption');cap.textContent=s.model+' · '+(hit.caption||s.prompt);
|
|
1597
1978
|
fig.appendChild(img);fig.appendChild(cap);
|
|
1979
|
+
histPush('imgModel',s.model);histFill('imgModel','imgModelList');
|
|
1598
1980
|
gal.prepend(fig);
|
|
1599
1981
|
return;
|
|
1600
1982
|
}
|
|
1983
|
+
if(s.mode==='audio'){histPush('audModel',s.model);histPush('audVoice',s.voice);histFill('audModel','audModelList');histFill('audVoice','audVoiceList');}
|
|
1984
|
+
else{histPush('vidModel',s.model);histFill('vidModel','vidModelList');}
|
|
1601
1985
|
appendMediaOut(s.mode==='audio'?'audioOut':'videoOut',s.mode,hit.src,hit.mime,s.model+' · '+s.prompt.slice(0,60),s.mode+'-output');
|
|
1602
1986
|
}finally{btn.disabled=false;btn.innerHTML=label;updateCode();}
|
|
1603
1987
|
}
|
|
1988
|
+
function probeQueryText(){
|
|
1989
|
+
var out=[];
|
|
1990
|
+
document.getElementById('turns').childNodes.forEach(function(n){
|
|
1991
|
+
if(n.className&&n.className.indexOf('uturn')>=0){
|
|
1992
|
+
var t=n.dataset.locked?(n.dataset.content||''):n.querySelector('textarea').value;
|
|
1993
|
+
if(t.trim())out.push(t.trim());
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
return out.join('\\n');
|
|
1997
|
+
}
|
|
1998
|
+
function probeRequest(){
|
|
1999
|
+
var a=curAdapter();var pr=a.probe;var q=probeQueryText()||'<<QUERY>>';
|
|
2000
|
+
var vars=probeVars(q);
|
|
2001
|
+
var path=fillTpl(pr.path,vars);
|
|
2002
|
+
var req={path:path,method:pr.method||'POST'};
|
|
2003
|
+
if(pr.query)req.query=fillTpl(pr.query,vars);
|
|
2004
|
+
if(pr.body!==undefined&&pr.body!==null){
|
|
2005
|
+
var b=fillTpl(pr.body,vars);
|
|
2006
|
+
if(a.formOnly)req.form=b;else req.body=b;
|
|
2007
|
+
}
|
|
2008
|
+
var s=JSON.stringify(req);
|
|
2009
|
+
var missing=['<<ORG>>','<<SID>>','<<DOMAIN>>'].filter(function(k){return s.indexOf(k)>=0;});
|
|
2010
|
+
return {req:req,missing:missing};
|
|
2011
|
+
}
|
|
2012
|
+
async function sendProbe(){
|
|
2013
|
+
err('');
|
|
2014
|
+
var btn=document.getElementById('submit');
|
|
2015
|
+
var a=curAdapter();
|
|
2016
|
+
if(!probeQueryText()){err(a&&a.kind==='search'?'Type the search query first.':'Type the request text first.');return;}
|
|
2017
|
+
var pr=probeRequest();
|
|
2018
|
+
if(pr.missing.length){err('Fill '+pr.missing.join(', ')+' under Parameters first.');return;}
|
|
2019
|
+
var pid=Number(document.getElementById('pool').value);
|
|
2020
|
+
btn.disabled=true;lockComposers();
|
|
2021
|
+
try{
|
|
2022
|
+
var t0=Date.now();
|
|
2023
|
+
var r=await j(await fetch('/api/pools/'+pid+'/proxy',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(pr.req)}));
|
|
2024
|
+
pgLogAdd({tab:'probe',method:pr.req.method||'POST',path:pr.req.path,prov:curProv(),model:document.getElementById('model').value.trim(),upstream:r.error?'ERR':r.status,key:r.key_id?('#'+r.key_id):'',ms:Date.now()-t0,err:r.error||'',req:JSON.stringify(pr.req).slice(0,1500),res:String(r.body||'').slice(0,800)});
|
|
2025
|
+
if(r.error){addAssistantTurn(r.error,true);addUserTurn('').focus();return;}
|
|
2026
|
+
var text=r.body||'';
|
|
2027
|
+
try{
|
|
2028
|
+
var d=JSON.parse(text);
|
|
2029
|
+
var hit=extractText(d,a.textPaths);
|
|
2030
|
+
text=hit!==null?hit:text;
|
|
2031
|
+
if(typeof text!=='string')text=JSON.stringify(text,null,2);
|
|
2032
|
+
}catch(e){}
|
|
2033
|
+
addAssistantTurn(text,r.status<200||r.status>=300);
|
|
2034
|
+
addUserTurn('').focus();
|
|
2035
|
+
}finally{btn.disabled=false;updateCode();}
|
|
2036
|
+
}
|
|
2037
|
+
function updateProbeCode(){
|
|
2038
|
+
var pid=document.getElementById('pool').value||'1';
|
|
2039
|
+
var url=location.origin+'/api/pools/'+pid+'/proxy';
|
|
2040
|
+
var lang=document.getElementById('codeLang').value;
|
|
2041
|
+
var pr=probeRequest();
|
|
2042
|
+
if(pr.missing.length){
|
|
2043
|
+
rawSnippet='# Fill '+pr.missing.join(', ')+' under Parameters first.\\n';
|
|
2044
|
+
document.getElementById('code').innerHTML='<span class="c">'+esc(rawSnippet)+'</span>';
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if(lang==='curl'){
|
|
2048
|
+
rawSnippet='curl -X POST '+url+' -H "Content-Type: application/json" -d '+pyStr(JSON.stringify(pr.req))+';\\n';
|
|
2049
|
+
document.getElementById('code').innerHTML='<span class="c"># runs through your pooled keys — auth injected server-side</span>\\n'+esc(rawSnippet);
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
rawSnippet='import requests\\n\\nresp = requests.post(\\n '+JSON.stringify(url)+',\\n json='+JSON.stringify(pr.req,null,4)+',\\n timeout=120,\\n)\\ndata = resp.json()\\nprint(data["status"])\\nprint(data["body"])\\n';
|
|
2053
|
+
document.getElementById('code').innerHTML='<span class="c"># probe via pooled keys — auth injected server-side</span>\\n'+esc(rawSnippet);
|
|
2054
|
+
}
|
|
1604
2055
|
async function send(){
|
|
1605
2056
|
err('');
|
|
1606
2057
|
const btn=document.getElementById('submit');
|
|
@@ -1608,6 +2059,8 @@ async function send(){
|
|
|
1608
2059
|
if(!msgs.some(m=>m.role==='user')){err('Type a user message first.');return;}
|
|
1609
2060
|
if(!p.model){err('Enter a model id.');return;}
|
|
1610
2061
|
const prov=curProv();
|
|
2062
|
+
const pa0=curAdapter();
|
|
2063
|
+
if(pa0&&!pa0.chatPath&&pa0.probe){return sendProbe();}
|
|
1611
2064
|
const c=capsFor(prov,p.model);
|
|
1612
2065
|
const bad=pendingMedia.filter(function(a){return (a.kind==='image'&&!c.vision)||(a.kind==='audio'&&!c.audioIn)||(a.kind==='video'&&!c.videoIn);});
|
|
1613
2066
|
if(bad.length){err(gateNote(bad[0].kind==='image'?'vision':bad[0].kind==='audio'?'audioIn':'videoIn',prov,p.model));return;}
|
|
@@ -1615,19 +2068,131 @@ async function send(){
|
|
|
1615
2068
|
btn.disabled=true;
|
|
1616
2069
|
lockComposers();
|
|
1617
2070
|
try{
|
|
1618
|
-
const
|
|
2071
|
+
const t0=Date.now();
|
|
2072
|
+
const preq={path:chatPathFor(prov,p.model,p.path),method:'POST',body:chatBodyFor(prov,p,msgs)};
|
|
2073
|
+
const r=await j(await fetch('/api/pools/'+pid+'/proxy',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(preq)}));
|
|
2074
|
+
pgLogAdd({tab:'chat',method:'POST',path:preq.path,prov:prov,model:p.model,upstream:r.error?'ERR':r.status,key:r.key_id?('#'+r.key_id):'',ms:Date.now()-t0,err:r.error||'',req:JSON.stringify(preq).slice(0,1500),res:String(r.body||'').slice(0,800)});
|
|
1619
2075
|
pendingMedia=[];renderMediaPrev();updateMediaUI();
|
|
1620
2076
|
if(r.error){addAssistantTurn(r.error,true);addUserTurn('').focus();return;}
|
|
1621
2077
|
let text=r.body||'';
|
|
1622
2078
|
try{
|
|
1623
2079
|
const d=JSON.parse(text);
|
|
1624
|
-
|
|
2080
|
+
const hit=extractText(d,(pa0&&pa0.textPaths)||[]);
|
|
2081
|
+
text=hit!==null?hit:text;
|
|
1625
2082
|
if(typeof text!=='string')text=JSON.stringify(text,null,2);
|
|
1626
2083
|
}catch{}
|
|
2084
|
+
if(r.status>=200&&r.status<300){histPush('model',p.model);modelMenuSync();}
|
|
1627
2085
|
addAssistantTurn(text,r.status<200||r.status>=300);
|
|
1628
2086
|
addUserTurn('').focus();
|
|
1629
2087
|
}finally{btn.disabled=false;updateCode();}
|
|
1630
2088
|
}
|
|
2089
|
+
// Pasted-model history: every id the user types is remembered per
|
|
2090
|
+
// provider+field (localStorage, max 10 each) and offered back as
|
|
2091
|
+
// datalist suggestions, ahead of live-listed models.
|
|
2092
|
+
function histKey(field){return curProv()+'::'+field;}
|
|
2093
|
+
function histGet(field){
|
|
2094
|
+
try{var h=JSON.parse(localStorage.getItem('pa-model-history')||'{}');var l=h[histKey(field)];return Array.isArray(l)?l.filter(function(x){return typeof x==='string'&&x;}):[];}catch(e){return [];}
|
|
2095
|
+
}
|
|
2096
|
+
function histPush(field,val){
|
|
2097
|
+
val=String(val||'').trim();if(!val)return;
|
|
2098
|
+
try{
|
|
2099
|
+
var h=JSON.parse(localStorage.getItem('pa-model-history')||'{}');
|
|
2100
|
+
var k=histKey(field);var l=h[k]||[];
|
|
2101
|
+
l=[val].concat(l.filter(function(x){return x!==val;}));
|
|
2102
|
+
h[k]=l.slice(0,10);
|
|
2103
|
+
localStorage.setItem('pa-model-history',JSON.stringify(h));
|
|
2104
|
+
}catch(e){}
|
|
2105
|
+
}
|
|
2106
|
+
function histFill(field,listId,live){
|
|
2107
|
+
var dl=document.getElementById(listId);if(!dl)return;dl.innerHTML='';
|
|
2108
|
+
var seen={};
|
|
2109
|
+
histGet(field).concat(live||[]).slice(0,200).forEach(function(id){
|
|
2110
|
+
if(!id||seen[id])return;seen[id]=1;
|
|
2111
|
+
var o=document.createElement('option');o.value=id;dl.appendChild(o);
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
var histTimers={};
|
|
2115
|
+
function histSoon(field){
|
|
2116
|
+
if(histTimers[field])clearTimeout(histTimers[field]);
|
|
2117
|
+
histTimers[field]=setTimeout(function(){
|
|
2118
|
+
var el=document.getElementById(field);if(!el||!el.value.trim())return;
|
|
2119
|
+
histPush(field,el.value);
|
|
2120
|
+
if(field==='model')modelMenuSync();
|
|
2121
|
+
else histFill(field,field+'List',undefined);
|
|
2122
|
+
},800);
|
|
2123
|
+
}
|
|
2124
|
+
function modelMenuSync(){
|
|
2125
|
+
var m=document.getElementById('modelMenu');if(!m)return;m.innerHTML='';
|
|
2126
|
+
var pid=String(document.getElementById('pool').value);
|
|
2127
|
+
var live=(window._pgModels&&window._pgModels[pid])||[];
|
|
2128
|
+
var a=curAdapter();var def=a&&a.defaults&&a.defaults.chat;
|
|
2129
|
+
var seen={};var cur=document.getElementById('model').value;
|
|
2130
|
+
var seeds=histGet('model').concat(live);
|
|
2131
|
+
if(def)seeds=seeds.concat([def]);
|
|
2132
|
+
if(a&&a.defaults){if(a.defaults.image)seeds=seeds.concat([a.defaults.image]);if(a.defaults.audio)seeds=seeds.concat([a.defaults.audio]);}
|
|
2133
|
+
seeds.slice(0,200).forEach(function(id){
|
|
2134
|
+
if(!id||seen[id])return;seen[id]=1;
|
|
2135
|
+
var it=document.createElement('button');it.type='button';it.className='dd-item';it.setAttribute('role','option');
|
|
2136
|
+
var t=document.createElement('span');t.className='dd-txt';t.textContent=id;it.appendChild(t);
|
|
2137
|
+
if(id===cur)it.setAttribute('aria-selected','true');
|
|
2138
|
+
it.onclick=(function(v){return function(){
|
|
2139
|
+
var box=document.getElementById('model');box.value=v;
|
|
2140
|
+
box.dispatchEvent(new Event('input',{bubbles:true}));box.dispatchEvent(new Event('change',{bubbles:true}));
|
|
2141
|
+
modelMenuClose();box.focus();
|
|
2142
|
+
};})(id);
|
|
2143
|
+
m.appendChild(it);
|
|
2144
|
+
});
|
|
2145
|
+
if(!m.childNodes.length){var d=document.createElement('div');d.className='dd-item';d.style.cursor='default';d.textContent='No saved models yet — paste an id into the field.';m.appendChild(d);}
|
|
2146
|
+
}
|
|
2147
|
+
function modelMenuClose(){var w=document.getElementById('dd-model');if(w)w.classList.remove('open');var m=document.getElementById('modelMenu');if(m)m.hidden=true;var c=document.getElementById('modelChev');if(c)c.setAttribute('aria-expanded','false');}
|
|
2148
|
+
function modelMenuToggle(){var w=document.getElementById('dd-model');var m=document.getElementById('modelMenu');var c=document.getElementById('modelChev');if(!w||!m)return;modelMenuSync();if(w.classList.contains('open'))modelMenuClose();else{w.classList.add('open');m.hidden=false;if(c)c.setAttribute('aria-expanded','true');}}
|
|
2149
|
+
async function refreshModels(){
|
|
2150
|
+
const list=document.getElementById('modelList');
|
|
2151
|
+
const box=document.getElementById('model');
|
|
2152
|
+
if(list)list.innerHTML='';
|
|
2153
|
+
const a=curAdapter();
|
|
2154
|
+
if(!a||!a.modelsPath)return;
|
|
2155
|
+
try{
|
|
2156
|
+
const pid=document.getElementById('pool').value;
|
|
2157
|
+
const r=await j(await fetch('/api/pools/'+pid+'/proxy',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({path:a.modelsPath,method:'GET'})}));
|
|
2158
|
+
if(r.error||!r.body)return;
|
|
2159
|
+
const d=JSON.parse(r.body);
|
|
2160
|
+
let ids=[];
|
|
2161
|
+
if(a.modelsStyle==='gemini'&&Array.isArray(d.models))
|
|
2162
|
+
ids=d.models.map(function(m){return String(m.name||'').replace(/^models\\//,'');}).filter(Boolean);
|
|
2163
|
+
else if(Array.isArray(d.data))
|
|
2164
|
+
ids=d.data.map(function(m){return m.id||m.name||'';}).filter(Boolean);
|
|
2165
|
+
window._pgModels=window._pgModels||{};
|
|
2166
|
+
window._pgModels[String(pid)]=ids;
|
|
2167
|
+
modelMenuSync();
|
|
2168
|
+
if(box&&!box.value&&ids.length){
|
|
2169
|
+
const pref=(a.defaults&&a.defaults.chat)||'';
|
|
2170
|
+
box.value=ids.indexOf(pref)>=0?pref:ids[0];
|
|
2171
|
+
}
|
|
2172
|
+
}catch(e){}
|
|
2173
|
+
}
|
|
2174
|
+
function applyPoolDefaults(){
|
|
2175
|
+
const prov=curProv();const a=curAdapter();
|
|
2176
|
+
const pathBox=document.getElementById('path');
|
|
2177
|
+
const modelBox=document.getElementById('model');
|
|
2178
|
+
if(a&&a.chatPath)pathBox.value=chatPathFor(prov,modelBox.value||((a.defaults||{}).chat||''),'/chat/completions');
|
|
2179
|
+
else if(a&&a.probe)pathBox.value=fillTpl(a.probe.path,probeVars(''));
|
|
2180
|
+
const pv=document.getElementById('probeVars');
|
|
2181
|
+
const pt=a&&a.probe?JSON.stringify(a.probe):'';
|
|
2182
|
+
if(pv)pv.hidden=!(a&&a.probe&&/<<(ORG|SID|DOMAIN|DEPTH|TOPIC|TIME_RANGE|MAX_RESULTS|ANSWER|CHUNKS|AUTO|INCL_DOMAINS|EXCL_DOMAINS)>>/.test(pt));
|
|
2183
|
+
var showPv=function(id,ph){var w=document.getElementById(id);if(w)w.hidden=pt.indexOf(ph)<0;};
|
|
2184
|
+
showPv('probeOrgWrap','<<ORG>>');showPv('probeSidWrap','<<SID>>');showPv('probeDomainWrap','<<DOMAIN>>');showPv('probeDepthWrap','<<DEPTH>>');showPv('probeTopicWrap','<<TOPIC>>');showPv('probeTimeWrap','<<TIME_RANGE>>');showPv('probeMaxWrap','<<MAX_RESULTS>>');showPv('probeAnswerWrap','<<ANSWER>>');showPv('probeChunksWrap','<<CHUNKS>>');showPv('probeAutoWrap','<<AUTO>>');showPv('probeInclWrap','<<INCL_DOMAINS>>');showPv('probeExclWrap','<<EXCL_DOMAINS>>');
|
|
2185
|
+
const mw=document.getElementById('dd-model');
|
|
2186
|
+
if(mw)mw.hidden=!!(a&&!a.chatPath);
|
|
2187
|
+
const av=document.getElementById('audVoice');
|
|
2188
|
+
const vd=VOICE_DEFAULTS[prov]||'alloy';
|
|
2189
|
+
if(av&&(!av.value||Object.keys(VOICE_DEFAULTS).map(function(k){return VOICE_DEFAULTS[k];}).indexOf(av.value)>=0))av.value=vd;
|
|
2190
|
+
histFill('imgModel','imgModelList');
|
|
2191
|
+
histFill('audModel','audModelList');
|
|
2192
|
+
histFill('vidModel','vidModelList');
|
|
2193
|
+
histFill('audVoice','audVoiceList');
|
|
2194
|
+
refreshModels();
|
|
2195
|
+
}
|
|
1631
2196
|
async function init(){
|
|
1632
2197
|
initDropdowns();
|
|
1633
2198
|
const provs=await j(await fetch('/api/providers'));
|
|
@@ -1638,11 +2203,10 @@ async function init(){
|
|
|
1638
2203
|
const pmap=window._pgPmap;
|
|
1639
2204
|
for(const p of pools){
|
|
1640
2205
|
const ks=await j(await fetch('/api/pools/'+p.id+'/keys'));
|
|
1641
|
-
const prov=provs.find(x=>x.id===p.provider);
|
|
1642
2206
|
pmap[p.id]=p.provider;
|
|
1643
2207
|
const o=document.createElement('option');
|
|
1644
2208
|
o.value=p.id;
|
|
1645
|
-
o.textContent=
|
|
2209
|
+
o.textContent=p.name;
|
|
1646
2210
|
o.disabled=ks.length===0;
|
|
1647
2211
|
sel.appendChild(o);
|
|
1648
2212
|
}
|
|
@@ -1675,12 +2239,16 @@ async function init(){
|
|
|
1675
2239
|
lastImgDefault=document.getElementById('imgModel').value;
|
|
1676
2240
|
lastAudDefault=document.getElementById('audModel').value;
|
|
1677
2241
|
lastVidDefault=document.getElementById('vidModel').value;
|
|
1678
|
-
sel.onchange=()=>{applyDefault();applyImgDefault();applyAudDefault();applyVidDefault();lastDefault=document.getElementById('model').value;lastImgDefault=document.getElementById('imgModel').value;lastAudDefault=document.getElementById('audModel').value;lastVidDefault=document.getElementById('vidModel').value;updateMediaUI();updateStudioModes();updateCode();};
|
|
2242
|
+
sel.onchange=()=>{applyDefault();applyImgDefault();applyAudDefault();applyVidDefault();lastDefault=document.getElementById('model').value;lastImgDefault=document.getElementById('imgModel').value;lastAudDefault=document.getElementById('audModel').value;lastVidDefault=document.getElementById('vidModel').value;applyPoolDefaults();updateMediaUI();updateStudioModes();updateCode();};
|
|
2243
|
+
applyPoolDefaults();
|
|
1679
2244
|
addUserTurn('');
|
|
1680
|
-
['system','model','path','temp','maxtokens','topp'].forEach(id=>document.getElementById(id).addEventListener('input',()=>{if(id==='model')lastDefault='⌁';
|
|
1681
|
-
['
|
|
1682
|
-
['
|
|
1683
|
-
['
|
|
2245
|
+
['system','model','path','temp','maxtokens','topp'].forEach(id=>document.getElementById(id).addEventListener('input',()=>{if(id==='model'){lastDefault='⌁';histSoon('model');updateMediaUI();updateStudioModes();}updateCode();}));
|
|
2246
|
+
['probeOrg','probeSid','probeDomain','probeDepth','probeTopic','probeTime','probeMax','probeAnswer','probeChunks','probeAuto','probeIncl','probeExcl'].forEach(id=>document.getElementById(id).addEventListener('input',updateCode));
|
|
2247
|
+
['probeOrg','probeSid','probeDomain','probeDepth','probeTopic','probeTime','probeMax','probeAnswer','probeChunks','probeAuto','probeIncl','probeExcl'].forEach(id=>document.getElementById(id).addEventListener('change',updateCode));
|
|
2248
|
+
['imgPrompt','imgModel','imgSize'].forEach(id=>document.getElementById(id).addEventListener('input',()=>{if(id==='imgModel'){lastImgDefault='⌁';histSoon('imgModel');}updateStudioModes();updateCode();}));
|
|
2249
|
+
['audText','audModel','audVoice','audFormat'].forEach(id=>document.getElementById(id).addEventListener('input',()=>{if(id==='audModel')lastAudDefault='⌁';if(id==='audModel')histSoon('audModel');if(id==='audVoice')histSoon('audVoice');updateStudioModes();updateCode();}));
|
|
2250
|
+
['vidPrompt','vidModel','vidDur','vidRes'].forEach(id=>document.getElementById(id).addEventListener('input',()=>{if(id==='vidModel'){lastVidDefault='⌁';histSoon('vidModel');}updateStudioModes();updateCode();}));
|
|
2251
|
+
[['model','model'],['imgModel','imgModel'],['audModel','audModel'],['vidModel','vidModel'],['audVoice','audVoice']].forEach(function(pair){document.getElementById(pair[0]).addEventListener('change',function(){histPush(pair[1],this.value);if(pair[1]==='model')modelMenuSync();else histFill(pair[1],pair[1]+'List',window._pgModels&&window._pgModels[String(document.getElementById('pool').value)]);});});
|
|
1684
2252
|
document.getElementById('generate').onclick=()=>sendStudio();
|
|
1685
2253
|
document.getElementById('genAudio').onclick=()=>sendStudio();
|
|
1686
2254
|
document.getElementById('genVideo').onclick=()=>sendStudio();
|
|
@@ -1700,12 +2268,28 @@ async function init(){
|
|
|
1700
2268
|
document.getElementById('addMsg').onclick=()=>{addUserTurn('').focus();};
|
|
1701
2269
|
const copy=()=>{navigator.clipboard.writeText(rawSnippet).catch(()=>{});};
|
|
1702
2270
|
document.getElementById('copyCode').onclick=copy;
|
|
2271
|
+
document.getElementById('copyDebug').onclick=()=>{navigator.clipboard.writeText(JSON.stringify(pgLog,null,2)).catch(()=>{});};
|
|
2272
|
+
document.getElementById('clearDebug').onclick=()=>{pgLog=[];document.getElementById('debugLog').textContent='No requests yet.';};
|
|
1703
2273
|
document.getElementById('copyTop').onclick=copy;
|
|
1704
2274
|
document.getElementById('hideCode').onclick=e=>{const g=document.getElementById('grid');g.classList.toggle('nocode');e.target.textContent=g.classList.contains('nocode')?'</> Show code':'</> Hide code';};
|
|
1705
2275
|
document.getElementById('tabChat').onclick=()=>setTab(true);
|
|
1706
2276
|
document.getElementById('tabStudio').onclick=()=>setTab(false);
|
|
2277
|
+
document.getElementById('modelChev').onclick=function(e){e.stopPropagation();modelMenuToggle();};
|
|
2278
|
+
document.getElementById('modelMenu').addEventListener('keydown',function(e){if(e.key==='Escape'){modelMenuClose();document.getElementById('model').focus();}});
|
|
2279
|
+
document.addEventListener('pointerdown',function(e){var w=document.getElementById('dd-model');if(w&&w.classList.contains('open')&&!w.contains(e.target))modelMenuClose();});
|
|
1707
2280
|
document.addEventListener('keydown',e=>{if((e.metaKey||e.ctrlKey)&&e.key==='Enter'){e.preventDefault();if(document.getElementById('tabStudio').classList.contains('on'))sendStudio();else send();}});
|
|
2281
|
+
window.addEventListener('hashchange',()=>{pgFromHash();});
|
|
1708
2282
|
updateMediaUI();updateStudioModes();updateCode();
|
|
2283
|
+
pgFromHash();
|
|
2284
|
+
}
|
|
2285
|
+
function pgAnchor(){return isStudio()?('studio-'+studioMode()):'chat';}
|
|
2286
|
+
function pgSyncHash(){try{history.replaceState(null,'','#'+pgAnchor());}catch(e){}}
|
|
2287
|
+
function pgFromHash(){
|
|
2288
|
+
var h=String(location.hash||'').replace('#','');
|
|
2289
|
+
if(h==='chat'){setTab(true);return true;}
|
|
2290
|
+
if(h==='studio'){setStudioMode('image');setTab(false);return true;}
|
|
2291
|
+
if(h.indexOf('studio-')===0){var m=h.slice(7);if(m==='image'||m==='audio'||m==='video'){setStudioMode(m);setTab(false);return true;}}
|
|
2292
|
+
return false;
|
|
1709
2293
|
}
|
|
1710
2294
|
function setTab(chat){
|
|
1711
2295
|
document.getElementById('tabChat').classList.toggle('on',chat);
|
|
@@ -1713,6 +2297,7 @@ function setTab(chat){
|
|
|
1713
2297
|
document.getElementById('grid').hidden=!chat;
|
|
1714
2298
|
document.getElementById('studioPane').hidden=chat;
|
|
1715
2299
|
updateCode();
|
|
2300
|
+
pgSyncHash();
|
|
1716
2301
|
}
|
|
1717
2302
|
init();
|
|
1718
2303
|
</script></div></div>${shellJs}</body></html>`;
|
|
@@ -1722,6 +2307,7 @@ export function providerPage() {
|
|
|
1722
2307
|
<html lang="en"><head><meta charset="utf-8"/>
|
|
1723
2308
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
1724
2309
|
<title>Provider — pool-anything</title>
|
|
2310
|
+
${subPageHead("/provider/", "Gather API keys for one provider, see per-key usage and copy a pooled proxy snippet.")}
|
|
1725
2311
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
1726
2312
|
<style>
|
|
1727
2313
|
*{box-sizing:border-box}body{margin:0;font-family:var(--font-sans);background:var(--paper-2);color:var(--ink)}
|
|
@@ -1805,7 +2391,7 @@ async function refresh(){
|
|
|
1805
2391
|
const row=document.createElement('div');
|
|
1806
2392
|
row.className='krow';row.style.animationDelay=Math.min(i*40,300)+'ms';
|
|
1807
2393
|
const m=document.createElement('span');m.className='meta';m.textContent=k.label+' · '+k.masked;row.appendChild(m);
|
|
1808
|
-
const u=document.createElement('span');u.className='use';u.textContent=(usageMap[k.id]||0)+' tokens';row.appendChild(u);
|
|
2394
|
+
const u=document.createElement('span');u.className='use';u.textContent=(usageMap[k.id]||0)+' tokens'+(k.cooling?' · cooling':'');row.appendChild(u);
|
|
1809
2395
|
const v=document.createElement('button');v.textContent='View';v.type='button';
|
|
1810
2396
|
v.onclick=async()=>{
|
|
1811
2397
|
if(v.dataset.open){delete v.dataset.open;v.textContent='View';m.textContent=k.label+' · '+k.masked;return;}
|