Include token requirement in the example api calls

parent 49005032
......@@ -188,20 +188,17 @@ td code{font-family:var(--mono);font-size:11.5px;background:var(--raised);paddin
.modal-close{background:none;border:none;color:var(--text-3);cursor:pointer;font-size:1.125rem;line-height:1;padding:.125rem;border-radius:3px;transition:color .1s}
.modal-close:hover{color:var(--text)}
.modal-body{padding:1.125rem}
.donate-modal-box{max-width:520px}
.nav-donate-btn{cursor:pointer;font-family:inherit;font-size:inherit;border:none}
.donate-modal-box{max-width:560px}
.nav-donate-btn{background:transparent;border:none;cursor:pointer;color:var(--text-3)}
.nav-donate-btn:hover{color:var(--text)}
.donate-tagline{font-size:13px;color:var(--text-2);margin:0 0 1.25rem;line-height:1.55}
.donate-coins{display:flex;gap:1rem}
.donate-coin{flex:1;display:flex;flex-direction:column;align-items:center;gap:.625rem;padding:.875rem;background:var(--raised);border:1px solid var(--border);border-radius:8px}
.donate-coin-header{display:flex;align-items:center;gap:.375rem}
.donate-coin-icon{font-size:1rem;font-weight:700}
.donate-coin-btc{color:#f7931a}
.donate-coin-eth{color:#627eea}
.donate-coin-name{font-size:13px;font-weight:600}
.donate-qr{border-radius:6px;border:1px solid var(--border);background:#fff;padding:4px}
.donate-addr-wrap{display:flex;align-items:center;gap:.375rem;width:100%}
.donate-addr{flex:1;font-family:var(--mono);font-size:9.5px;color:var(--accent);word-break:break-all;line-height:1.4}
.donate-copy{flex-shrink:0;background:none;border:1px solid var(--border);border-radius:4px;cursor:pointer;padding:.2rem .35rem;font-size:.8rem;color:var(--text-2);transition:color .1s,border-color .1s}
.donate-coins{display:flex;gap:.875rem}
.donate-coin{flex:1;display:flex;flex-direction:column;align-items:center;gap:.75rem;padding:1rem;background:var(--raised);border:1px solid var(--border);border-radius:8px}
.donate-coin-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--text-3)}
.donate-qr{display:block;border-radius:5px}
.donate-addr-row{display:flex;align-items:center;gap:.5rem;width:100%;background:var(--card);border:1px solid var(--border);border-radius:5px;padding:.375rem .5rem}
.donate-addr{flex:1;font-family:var(--mono);font-size:9px;color:var(--text-2);word-break:break-all;line-height:1.5;user-select:all}
.donate-copy{flex-shrink:0;background:none;border:1px solid var(--border);border-radius:4px;cursor:pointer;padding:.2rem .5rem;font-size:11px;font-weight:600;color:var(--text-3);font-family:var(--font);transition:color .1s,border-color .1s}
.donate-copy:hover{color:var(--text);border-color:var(--border-2)}
@media(max-width:480px){.donate-coins{flex-direction:column}}
......
......@@ -29,7 +29,7 @@
<a href="/admin/users" class="nav-link {% if '/users' in request.url.path %}active{% endif %}">Users</a>
<a href="/admin/settings" class="nav-link {% if '/settings' in request.url.path %}active{% endif %}">Settings</a>
{% endif %}
<button class="nav-link nav-donate-btn" onclick="document.getElementById('donateModal').classList.add('show')">Donate</button>
<button class="nav-link nav-donate-btn" onclick="document.getElementById('donateModal').classList.add('show')">Donate &#9829;</button>
</div>
</div>
<div class="topnav-right">
......@@ -47,28 +47,22 @@
<button class="modal-close" onclick="document.getElementById('donateModal').classList.remove('show')">&times;</button>
</div>
<div class="modal-body">
<p class="donate-tagline">CoderAI is free and open source. If you find it useful, consider supporting its development with a crypto donation — every bit helps!</p>
<p class="donate-tagline">CoderAI is free and open source. If you find it useful, consider supporting its development — every contribution helps.</p>
<div class="donate-coins">
<div class="donate-coin">
<div class="donate-coin-header">
<span class="donate-coin-icon donate-coin-btc">&#₿;</span>
<span class="donate-coin-name">Bitcoin (BTC)</span>
</div>
<img class="donate-qr" src="https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=bitcoin:bc1qcpt2uutqkz4456j5r78rjm3gwq03h5fpwmcc5u" alt="BTC QR code" width="160" height="160">
<div class="donate-addr-wrap">
<span class="donate-coin-label">Bitcoin &mdash; BTC</span>
<img class="donate-qr" src="https://api.qrserver.com/v1/create-qr-code/?size=160x160&color=DDE1F0&bgcolor=161820&qzone=2&data=bitcoin:bc1qcpt2uutqkz4456j5r78rjm3gwq03h5fpwmcc5u" alt="BTC QR code" width="160" height="160">
<div class="donate-addr-row">
<code class="donate-addr" id="btcAddr">bc1qcpt2uutqkz4456j5r78rjm3gwq03h5fpwmcc5u</code>
<button class="donate-copy" onclick="donateCopy('btcAddr',this)" title="Copy">&#128203;</button>
<button class="donate-copy" onclick="donateCopy('btcAddr',this)">Copy</button>
</div>
</div>
<div class="donate-coin">
<div class="donate-coin-header">
<span class="donate-coin-icon donate-coin-eth">&#9830;</span>
<span class="donate-coin-name">Ethereum (ETH)</span>
</div>
<img class="donate-qr" src="https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=ethereum:0xdA6dAb526515b5cb556d20269207D43fcc760E51" alt="ETH QR code" width="160" height="160">
<div class="donate-addr-wrap">
<span class="donate-coin-label">Ethereum &mdash; ETH</span>
<img class="donate-qr" src="https://api.qrserver.com/v1/create-qr-code/?size=160x160&color=DDE1F0&bgcolor=161820&qzone=2&data=ethereum:0xdA6dAb526515b5cb556d20269207D43fcc760E51" alt="ETH QR code" width="160" height="160">
<div class="donate-addr-row">
<code class="donate-addr" id="ethAddr">0xdA6dAb526515b5cb556d20269207D43fcc760E51</code>
<button class="donate-copy" onclick="donateCopy('ethAddr',this)" title="Copy">&#128203;</button>
<button class="donate-copy" onclick="donateCopy('ethAddr',this)">Copy</button>
</div>
</div>
</div>
......
......@@ -245,6 +245,10 @@ a.dl { display:inline-block; margin-top:.4rem; }
.pipe-summary { font-size:12px; color:var(--text-2); line-height:1.45; }
.pipe-tags { display:flex; flex-wrap:wrap; gap:.35rem; }
.pipe-tag { font-size:10px; padding:.16rem .45rem; border-radius:999px; background:var(--surface-2); color:var(--text-2); border:1px solid var(--border); }
.pipe-badge { font-size:10px; padding:.15rem .45rem; border-radius:999px; font-weight:600; align-self:flex-start; }
.pipe-badge.ready { background:#0f2a0f; color:#4caf50; border:1px solid #2a5a2a; }
.pipe-badge.partial { background:#2a1f0a; color:#f0a020; border:1px solid #5a3a10; }
.pipe-badge.unavailable { background:var(--surface-2); color:var(--text-3); border:1px solid var(--border); }
.pipe-steps { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; font-size:11px; color:var(--text-2); margin-bottom:.1rem; }
.pipe-step { background:var(--surface-2); border-radius:5px; padding:.2rem .5rem; color:var(--text-2); }
.pipe-arrow { color:var(--text-3); }
......@@ -1094,7 +1098,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
<div class="hist-list" id="artifact-history-list"></div>
</div>
<details class="pipe-card">
<details class="pipe-card" data-requires='["image_generation","image_to_video"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Image → Video Pipeline</div>
......@@ -1161,7 +1165,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
</div>
</details>
<details class="pipe-card">
<details class="pipe-card" data-requires='["speech_to_text","text_to_speech"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Video → Dub + Subtitle</div>
......@@ -1190,7 +1194,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
</div>
</details>
<details class="pipe-card">
<details class="pipe-card" data-requires='["text_generation","image_generation","video_generation"]' data-optional='["text_to_speech"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Full Story</div>
......@@ -1225,7 +1229,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
</div>
</details>
<details class="pipe-card">
<details class="pipe-card" data-requires='["speech_to_text","text_to_speech"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Audio/Video Dub with Voice Clone</div>
......@@ -1268,7 +1272,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
</div>
</details>
<details class="pipe-card">
<details class="pipe-card" data-requires='["image_to_image"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Face Swap</div>
......@@ -1307,7 +1311,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
</div>
</details>
<details class="pipe-card">
<details class="pipe-card" data-requires='["text_to_speech"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Voice Clone (TTS)</div>
......@@ -1344,7 +1348,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
</div>
</details>
<details class="pipe-card">
<details class="pipe-card" data-requires='["audio_to_audio"]' data-optional='["text_to_speech"]'>
<summary>
<div class="pipe-head">
<div class="pipe-title">Voice Convert (SVC)</div>
......@@ -1449,6 +1453,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
// State
// ─────────────────────────────────────────────────────────────────
let models = [], activeModel = null, chatHistory = [], chatBusy = false, attachedImage = null;
let apiToken = null;
let charSlots = {}; // prefix → [{name:'', images:[b64...]}]
let _charProfiles = []; // cached list from /v1/characters
let artifactHistory = [];
......@@ -1499,7 +1504,10 @@ const TAB_STATE = {
available: { label:'Ready', className:'state-ready' },
partial: { label:'Partial', className:'state-partial' },
unavailable: { label:'Unavailable', className:'state-unavailable' },
none: {},
};
// Subs whose features are served by any available dedicated model, not only activeModel.
const CROSS_MODEL_SUBS = new Set(['aud-stt', 'aud-tts', 'aud-gen', 'embed']);
const STUDIO_CAPABILITIES = {
'vid-dub': {
category:'video',
......@@ -1648,6 +1656,41 @@ function capabilitySetForModel(m) {
return new Set((m?.capabilities || []).filter(Boolean));
}
function crossModelCaps() {
const s = new Set();
models.forEach(m => (m.capabilities || []).forEach(c => s.add(c)));
return s;
}
function bestModelForCap(cap) {
return models.find(m => (m.capabilities || []).includes(cap)) || null;
}
function updatePipelineBadges() {
const allCaps = crossModelCaps();
document.querySelectorAll('.pipe-card[data-requires]').forEach(card => {
let reqs, opts;
try { reqs = JSON.parse(card.dataset.requires || '[]'); } catch(e) { reqs = []; }
try { opts = JSON.parse(card.dataset.optional || '[]'); } catch(e) { opts = []; }
const hasAll = reqs.every(c => allCaps.has(c));
const hasSome = reqs.some(c => allCaps.has(c));
const state = hasAll ? 'ready' : (hasSome || (!reqs.length && opts.some(c => allCaps.has(c)))) ? 'partial' : 'unavailable';
const label = state === 'ready' ? 'Ready' : state === 'partial' ? 'Partial' : 'Unavailable';
const head = card.querySelector('.pipe-head');
if (!head) return;
let badge = head.querySelector('.pipe-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'pipe-badge';
const title = head.querySelector('.pipe-title');
if (title) title.insertAdjacentElement('afterend', badge);
else head.insertBefore(badge, head.firstChild);
}
badge.className = `pipe-badge ${state}`;
badge.textContent = label;
});
}
function hasTypeFallback(rule, type) {
return Array.isArray(rule.fallbackTypes) && rule.fallbackTypes.includes(type);
}
......@@ -1672,7 +1715,7 @@ function evaluateCategoryState(cat, subStates, caps, type) {
? 'available'
: 'partial';
}
if (cat === 'pipe') return 'available';
if (cat === 'pipe') return 'none';
if (cat === 'embed') return caps.has('embeddings') || type === 'embedding' ? 'available' : 'unavailable';
const states = (CATEGORY_SUBS[cat] || []).map(sub => subStates[sub]).filter(Boolean);
if (states.includes('available')) return 'available';
......@@ -2054,8 +2097,9 @@ function buildTTSPreviewData() {
}
function buildSTTPreviewData() {
const sttModel = bestModelForCap('speech_to_text') || activeModel;
return previewExportBody('/v1/audio/transcriptions', {
model: activeModel?.id || '',
model: sttModel?.id || '',
file: fileOrNull('as-file') ? '<multipart audio/video file>' : undefined,
language: val('as-lang') || undefined,
prompt: val('as-prompt') || undefined,
......@@ -2143,15 +2187,17 @@ function buildCodeSnippet(kind, preview) {
const origin = window.location.hostname === '0.0.0.0'
? window.location.origin.replace('0.0.0.0', '127.0.0.1')
: window.location.origin;
const token = apiToken || 'YOUR_API_KEY';
if (kind === 'curl') {
return `curl -X POST ${origin}${preview.endpoint} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer ${token}" \\
-d '${preview.json.replace(/'/g, "'\\''")}'`;
}
if (kind === 'python') {
return `import requests\n\npayload = ${preview.json}\nresponse = requests.post(\n "${origin}${preview.endpoint}",\n json=payload,\n timeout=300,\n)\nprint(response.json())`;
return `import requests\n\npayload = ${preview.json}\nresponse = requests.post(\n "${origin}${preview.endpoint}",\n headers={"Authorization": "Bearer ${token}"},\n json=payload,\n timeout=300,\n)\nprint(response.json())`;
}
return `const payload = ${preview.json};\nconst response = await fetch("${origin}${preview.endpoint}", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify(payload),\n});\nconst data = await response.json();\nconsole.log(data);`;
return `const payload = ${preview.json};\nconst response = await fetch("${origin}${preview.endpoint}", {\n method: "POST",\n headers: { "Content-Type": "application/json", "Authorization": "Bearer ${token}" },\n body: JSON.stringify(payload),\n});\nconst data = await response.json();\nconsole.log(data);`;
}
function renderRequestPreview(panel, config) {
......@@ -2426,6 +2472,7 @@ function selectModel(m) {
function updateTabs(m) {
const caps = capabilitySetForModel(m);
const allCaps = crossModelCaps();
const type = m.type || 'text';
refreshAudioBackendHealth();
const subStates = {};
......@@ -2433,8 +2480,10 @@ function updateTabs(m) {
if (VIDEO_EXTRA_SUBS.includes(sub) && type === 'video' && !rule.fallbackTypes) {
rule = Object.assign({}, rule, { fallbackTypes:['video'] });
}
subStates[sub] = evaluateSubCapability(rule, caps, type);
const evalCaps = CROSS_MODEL_SUBS.has(sub) ? allCaps : caps;
subStates[sub] = evaluateSubCapability(rule, evalCaps, type);
});
updatePipelineBadges();
const categoryStates = {};
CATEGORY_TABS.forEach(cat => {
categoryStates[cat] = evaluateCategoryState(cat, subStates, caps, type);
......@@ -3545,12 +3594,13 @@ async function genTTS() {
// STT
// ─────────────────────────────────────────────────────────────────
async function genSTT() {
if (!activeModel) return;
const sttModel = bestModelForCap('speech_to_text') || activeModel;
if (!sttModel) return;
const f = fileOrNull('as-file');
if (!f) { $('as-prog').textContent='Select an audio file.'; return; }
$('as-prog').textContent='Transcribing…';
const fd = new FormData();
fd.append('file', f); fd.append('model', activeModel.id);
fd.append('file', f); fd.append('model', sttModel.id);
if (val('as-lang')) fd.append('language', val('as-lang'));
if (val('as-prompt')) fd.append('prompt', val('as-prompt'));
try {
......@@ -3562,7 +3612,7 @@ async function genSTT() {
pushArtifactHistory({
task:'Transcription',
family:'speech to text',
model:activeModel.id,
model:sttModel.id,
summary:buildSTTHistorySummary(d.text || JSON.stringify(d), f.name),
links:[],
});
......@@ -3834,6 +3884,9 @@ loadModels();
loadVoiceProfiles();
initRequestPreviews();
initPipelineBuilder();
fetch('/admin/api/tokens').then(r => r.json()).then(tokens => {
if (Array.isArray(tokens) && tokens.length) apiToken = tokens[0].token;
}).catch(() => {});
// ── Pipeline accordion: opening one card closes all others ────────
document.querySelector('.pipe-panel').addEventListener('click', e => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment