(() => { 'use strict'; const MODEL_WORKFLOW_ENDPOINT = window.MODEL_WORKFLOW_ENDPOINT || 'https://ayambabu23--workflow-execute-workflow.modal.run/'; const MODEL_API_ENDPOINT = window.MODEL_API_ENDPOINT || 'https://vici-bio--api-execute-workflow.modal.run/'; const MODEL_STATUS_ENDPOINT = window.MODEL_STATUS_ENDPOINT || 'https://vici-bio--api-check-status.modal.run/'; const root = document.getElementById('boltzgen-ui'); if (!root) return; const tabs = Array.from(root.querySelectorAll('.model-tab[data-tab]')); const apiCodeEl = document.getElementById('api-code-block'); const apiLangTabs = Array.from(root.querySelectorAll('.api-lang-tab[data-lang]')); const apiActionBtns = Array.from(root.querySelectorAll('.api-action-btn')); const executeBtnMembers = root.querySelector('.model-actions [data-ms-content="members"]'); const executeBtnGuest = root.querySelector('.model-actions [data-ms-content="!members"]'); const modelSlug = String(root.dataset.model || 'boltzgen').trim() || 'boltzgen'; const modelKey = 'boltzgen'; const API_LANGS = ['python', 'curl', 'javascript']; let currentTab = inferInitialTab(); let currentApiLang = 'python'; let currentApiSnippet = { text: '', html: '' }; // For BoltzGen: Sync means "build from form" and include placeholders for uploads. // If you later decide you want to actually inline base64 uploads into the API snippet, // flip this to true and we will attempt to read files (can be large). let apiInlineUploads = false; let isRenderingApiSnippet = false; let renderSeq = 0; const API_DEF_CONTENT = window.VICI_API_DEF_CONTENT || { 'token-id': { title: 'Token-ID', html: ` Your Vici Token ID. Send it as the Token-ID header. Generate it in your Account . ` }, 'token-secret': { title: 'Token-Secret', html: ` Your Vici Token Secret. Send it as the Token-Secret header. You only see this once when you generate it. Generate it in your Account . ` }, 'workflow-name': { title: `workflow_name / ${modelKey}.name`, html: `A friendly run name shown in your Dashboard. The outer workflow_name and inner ${modelKey}.name should match.` } }; // --- Hash tab routing --- function tabFromHash(hash = window.location.hash) { const h = String(hash || '').trim().toLowerCase(); if (h === '#basic') return 'basic'; if (h === '#advanced') return 'advanced'; if (h === '#api') return 'api'; return null; } function syncHashToTab(tab, { replace = true } = {}) { const nextHash = `#${tab}`; if (String(window.location.hash || '').toLowerCase() === nextHash) return; try { const url = new URL(window.location.href); url.hash = nextHash; if (replace && window.history?.replaceState) window.history.replaceState(null, '', url.toString()); else if (!replace && window.history?.pushState) window.history.pushState(null, '', url.toString()); else window.location.hash = nextHash; } catch { window.location.hash = nextHash; } } function bindHashRouting() { window.addEventListener('hashchange', () => { const next = tabFromHash(); if (!next || next === currentTab) return; setTab(next, { silent: true, syncHash: false }); }); } function inferInitialTab() { const fromHash = tabFromHash(); if (fromHash) return fromHash; if (root.classList.contains('is-tab-api')) return 'api'; if (root.classList.contains('is-tab-advanced')) return 'advanced'; return 'basic'; } // --- Helpers --- function escapeHtml(str) { return String(str ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function isPlainObject(v) { return Object.prototype.toString.call(v) === '[object Object]'; } function deepClone(v) { return JSON.parse(JSON.stringify(v)); } function stripExecutionContextForApi(value) { const blocked = new Set(['member_id', 'msid', 'user_id', 'team_id']); if (Array.isArray(value)) return value.map(stripExecutionContextForApi); if (!isPlainObject(value)) return value; const out = {}; Object.entries(value).forEach(([k, v]) => { if (blocked.has(k)) return; out[k] = stripExecutionContextForApi(v); }); return out; } function pulseBtn(btn, cls) { if (!btn) return; btn.classList.remove(cls); void btn.offsetWidth; btn.classList.add(cls); const onEnd = () => { btn.classList.remove(cls); btn.removeEventListener('animationend', onEnd); }; btn.addEventListener('animationend', onEnd); } function copyTextRobust(text) { if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text); return new Promise((resolve, reject) => { try { const ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.left = '-9999px'; ta.style.top = '-9999px'; document.body.appendChild(ta); ta.select(); ta.setSelectionRange(0, ta.value.length); const ok = document.execCommand('copy'); ta.remove(); ok ? resolve() : reject(new Error('copy failed')); } catch (err) { reject(err); } }); } // --- Tabs --- function setTab(tab, { silent = false, syncHash = true, replaceHash = false } = {}) { if (!['basic', 'advanced', 'api'].includes(tab)) return; currentTab = tab; root.classList.remove('is-tab-basic', 'is-tab-advanced', 'is-tab-api'); root.classList.add(`is-tab-${tab}`); tabs.forEach(btn => { const active = btn.dataset.tab === tab; btn.classList.toggle('is-active', active); btn.setAttribute('aria-selected', active ? 'true' : 'false'); btn.setAttribute('tabindex', active ? '0' : '-1'); }); if (syncHash) syncHashToTab(tab, { replace: replaceHash || silent }); if (tab === 'api') renderApiSnippet({ forceDefault: false, toast: false }); } function initTabs() { tabs.forEach(btn => { btn.addEventListener('click', () => setTab(btn.dataset.tab, { replaceHash: false })); btn.addEventListener('keydown', (e) => { if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return; e.preventDefault(); const i = tabs.indexOf(btn); let next = i; if (e.key === 'ArrowRight') next = (i + 1) % tabs.length; if (e.key === 'ArrowLeft') next = (i - 1 + tabs.length) % tabs.length; if (e.key === 'Home') next = 0; if (e.key === 'End') next = tabs.length - 1; tabs[next]?.focus(); setTab(tabs[next]?.dataset.tab || 'basic', { replaceHash: false }); }); }); setTab(currentTab || 'basic', { silent: true, syncHash: true, replaceHash: true }); } // --- Build BoltzGen payload for API preview --- function safeBoltzgenName(raw) { if (typeof window.canonicalizeBoltzgenName === 'function') { return window.canonicalizeBoltzgenName(raw); } // fallback let s = String(raw || '').trim().replace(/\s+/g, '_'); try { s = s.normalize('NFKD'); } catch {} s = s.replace(/[^\w-]+/g, '').replace(/_+/g, '_').toLowerCase(); s = s.replace(/^[^a-z0-9]+/, '').replace(/[^a-z0-9]+$/, ''); return s.slice(0, 64); } function defaultApiPayload() { return { workflow_name: `my_${modelKey}_run`, [modelKey]: { name: `my_${modelKey}_run`, protocol: 'protein-anything', num_designs: 10, budget: 2, stages: { design: { recycling_steps: 3, sampling_steps: 500, diffusion_samples: 1 }, affinity: { recycling_steps: 3, sampling_steps: 200, diffusion_samples: 5 }, fold: { recycling_steps: 3, sampling_steps: 200, diffusion_samples: 5 }, inverse_fold: { recycling_steps: 3, sampling_steps: 200, diffusion_samples: 1, noise: 0.2 } }, filtering: { alpha: 0.001, filter_biased: true, additional_filters: [] }, entities: [ { file: { path: 'target.cif' } }, { protein: { id: 'B', sequence: '80..120' } } ], constraints: [], uploads: { 'target.cif': '' } } }; } function readNumber(id, fallback) { const el = document.getElementById(id); if (!el) return fallback; const val = Number(el.value); return Number.isFinite(val) ? val : fallback; } function readBoolSelect(id, fallback = true) { const el = document.getElementById(id); if (!el) return fallback; const v = String(el.value || '').toLowerCase(); if (v === 'true') return true; if (v === 'false') return false; return fallback; } function getEntityBlocks() { return Array.from(document.querySelectorAll('#boltzgen-entity-container .entity-block:not([data-removing])')); } // Light gather: build entities + placeholder uploads without reading base64. // Uses your existing global helpers if present. function gatherEntitiesLight() { const blocks = getEntityBlocks(); const entities = []; const uploads = {}; if (!blocks.length) return { entities: [], uploads: {} }; for (const block of blocks) { const chain = (block.dataset.chain || '').trim(); const kind = block.dataset.kind || ''; const entityId = chain || 'A'; if (kind === 'structure') { const fileInput = block.querySelector('input[type="file"]'); const file = fileInput?.files?.[0]; const path = file?.name || 'target.cif'; // placeholder upload uploads[path] = ''; // Reuse the same optional section collectors if they exist globally. // If they do not exist, we still submit a minimal file block. const fileObj = { path }; try { if (typeof window.boltzgenCollectRows === 'function') { const state = window.BOLTZGEN_STRUCTURE_STATE?.get?.(block); const canonical = window.boltzgenCanonicalResidues || ((v) => String(v || '').trim()); const a2l = window.boltzgenAuthRangesToLabels; const translate = (cid, raw) => { const txt = canonical(raw || '').trim(); if (!txt) return ''; if (typeof a2l === 'function' && state) return a2l(state, cid, txt); return txt; }; const include = window.boltzgenCollectRows(block, 'include', row => { const cid = row.querySelector('.include-chain')?.value.trim() || chain; const res = canonical(row.querySelector('.include-range')?.value || '').trim(); if (!cid || !res) return null; const labelRes = translate(cid, res); if (!labelRes) return null; return { chain: { id: cid, res_index: labelRes } }; }); const includeProx = window.boltzgenCollectRows(block, 'include_proximity', row => { const cid = row.querySelector('.include-chain')?.value.trim() || chain; const res = canonical(row.querySelector('.include-range')?.value || '').trim(); const radiusRaw = row.querySelector('.include-radius')?.value; if (!cid || !res) return null; const labelRes = translate(cid, res); if (!labelRes) return null; const out = { chain: { id: cid, res_index: labelRes } }; if (radiusRaw !== undefined && radiusRaw !== null && String(radiusRaw).trim() !== '') out.radius = Number(radiusRaw); return out; }); const bindingTypes = window.boltzgenCollectRows(block, 'binding_types', row => { const cid = row.querySelector('.binding-chain')?.value.trim() || chain; const mode = row.querySelector('.binding-mode')?.value || 'binding'; const val = canonical(row.querySelector('.binding-value')?.value || '').trim(); if (!cid || !val) return null; const labelVal = translate(cid, val); if (!labelVal) return null; return { chain: { id: cid, [mode]: labelVal } }; }); const groups = window.boltzgenCollectRows(block, 'structure_groups', row => { const gid = row.querySelector('.group-id')?.value.trim(); const visRaw = row.querySelector('.group-visibility')?.value; const res = canonical(row.querySelector('.group-range')?.value || '').trim(); if (!gid && !res && !visRaw) return null; const group = {}; if (gid) group.id = gid; if (visRaw !== undefined && visRaw !== null && String(visRaw).trim() !== '') group.visibility = Number(visRaw); const labelRes = translate(chain, res); if (labelRes) group.res_index = labelRes; return { group }; }); const design = window.boltzgenCollectRows(block, 'design', row => { const cid = row.querySelector('.design-chain')?.value.trim() || chain; const res = canonical(row.querySelector('.design-range')?.value || '').trim(); if (!cid || !res) return null; const labelRes = translate(cid, res); if (!labelRes) return null; return { chain: { id: cid, res_index: labelRes } }; }); const secondary = window.boltzgenCollectRows(block, 'secondary_structure', row => { const cid = row.querySelector('.sec-chain')?.value.trim() || chain; const loop = canonical(row.querySelector('.sec-loop')?.value || '').trim(); const helix = canonical(row.querySelector('.sec-helix')?.value || '').trim(); const sheet = canonical(row.querySelector('.sec-sheet')?.value || '').trim(); if (!cid || (!loop && !helix && !sheet)) return null; const chainObj = { id: cid }; const loopL = translate(cid, loop); const helixL = translate(cid, helix); const sheetL = translate(cid, sheet); if (loopL) chainObj.loop = loopL; if (helixL) chainObj.helix = helixL; if (sheetL) chainObj.sheet = sheetL; return { chain: chainObj }; }); const insertions = window.boltzgenCollectRows(block, 'design_insertions', row => { const cid = row.querySelector('.ins-id')?.value.trim() || chain; const resIndex = canonical(row.querySelector('.ins-res')?.value || '').trim(); const numRes = (row.querySelector('.ins-num')?.value || '').trim(); const ss = row.querySelector('.ins-ss')?.value || ''; if (!cid || !resIndex || !numRes) return null; const labelResIndex = translate(cid, resIndex); if (!labelResIndex) return null; const insertion = { id: cid, res_index: labelResIndex, num_residues: numRes }; if (ss) insertion.secondary_structure = ss; return { insertion }; }); if (include?.length) fileObj.include = include; if (includeProx?.length) fileObj.include_proximity = includeProx; if (bindingTypes?.length) fileObj.binding_types = bindingTypes; if (groups?.length) fileObj.structure_groups = groups; if (design?.length) fileObj.design = design; if (secondary?.length) fileObj.secondary_structure = secondary; if (insertions?.length) fileObj.design_insertions = insertions; } } catch (err) { console.warn('[boltzgen api] entity light gather failed', err); } entities.push({ file: fileObj }); continue; } if (kind === 'protein') { const mode = block.querySelector('.protein-mode')?.value || 'sequence'; let sequenceVal = ''; if (mode === 'sequence') { const seq = (block.querySelector('.protein-sequence')?.value || '').trim(); sequenceVal = seq || 'ACDEFGHIKLMNPQRSTVWY'; } else { const min = parseInt(block.querySelector('.protein-min')?.value || '80', 10); const max = parseInt(block.querySelector('.protein-max')?.value || '120', 10); sequenceVal = `${min}..${max}`; } const proteinObj = { id: entityId, sequence: sequenceVal }; try { if (typeof window.boltzgenReadBinding === 'function') { const bindingTypes = window.boltzgenReadBinding(block.querySelector('.protein-binding-section')); if (bindingTypes) proteinObj.binding_types = bindingTypes; } } catch {} const sec = (block.querySelector('.protein-secondary')?.value || '').trim(); if (sec) proteinObj.secondary_structure = sec; const cyclic = !!block.querySelector('.protein-cyclic')?.checked; if (cyclic) proteinObj.cyclic = true; entities.push({ protein: proteinObj }); continue; } if (kind === 'ligand') { const raw = (block.querySelector('.ligand-code')?.value || '').trim(); const ligandObj = { id: entityId }; if (raw) { const normalized = raw.replace(/\s+/g, ''); const isCcd = /^[A-Za-z0-9]{2,6}$/.test(normalized) && !/[a-z]/.test(normalized); if (isCcd) ligandObj.ccd = normalized.toUpperCase(); else ligandObj.smiles = raw; } else { ligandObj.ccd = 'ATP'; } try { if (typeof window.boltzgenReadBinding === 'function') { const bindingTypes = window.boltzgenReadBinding(block.querySelector('.ligand-binding-section')); if (bindingTypes) ligandObj.binding_types = bindingTypes; } } catch {} entities.push({ ligand: ligandObj }); continue; } } return { entities, uploads }; } function gatherConstraintsSafe() { try { if (typeof window.boltzgenGatherConstraints === 'function') { return window.boltzgenGatherConstraints(); } } catch (err) { console.warn('[boltzgen api] constraints gather failed', err); } return []; } async function buildBoltzgenPayloadFromForm({ forApi = false } = {}) { const nameEl = document.getElementById('boltzgen-name'); const rawName = String(nameEl?.value || '').trim(); const jobName = safeBoltzgenName(rawName || `my_${modelKey}_run`); const protocol = String(document.getElementById('boltzgen-protocol')?.value || 'protein-anything').trim(); const numDesigns = parseInt(String(document.getElementById('boltzgen-num-designs')?.value || '0'), 10) || 0; const budget = parseInt(String(document.getElementById('boltzgen-budget')?.value || '0'), 10) || 0; const stages = { design: { recycling_steps: readNumber('design-recycles', 3), sampling_steps: readNumber('design-sampling', 500), diffusion_samples: readNumber('design-diffusion', 1) }, affinity: { recycling_steps: readNumber('affinity-recycles', 3), sampling_steps: readNumber('affinity-sampling', 200), diffusion_samples: readNumber('affinity-diffusion', 5) }, fold: { recycling_steps: readNumber('fold-recycles', 3), sampling_steps: readNumber('fold-sampling', 200), diffusion_samples: readNumber('fold-diffusion', 5) }, inverse_fold: { recycling_steps: readNumber('inverse-recycles', 3), sampling_steps: readNumber('inverse-sampling', 200), diffusion_samples: readNumber('inverse-diffusion', 1), noise: readNumber('inverse-noise', 0.2) } }; const alphaRaw = parseFloat(String(document.getElementById('filter-alpha')?.value || '0.001')); const filtering = { alpha: Number.isFinite(alphaRaw) ? alphaRaw : 0.001, filter_biased: readBoolSelect('filter-biased', true), additional_filters: Array.from(document.querySelectorAll('#boltzgen-filter-list .filter-row')) .map(row => { const metric = (row.querySelector('.filter-metric')?.value || '').trim(); const comp = row.querySelector('.filter-comparator')?.value || '>'; const valRaw = row.querySelector('.filter-value')?.value; const val = valRaw !== undefined && valRaw !== null ? String(valRaw).trim() : ''; if (!metric || !val) return null; return `${metric}${comp}${val}`; }) .filter(Boolean) }; const rmsdRaw = parseFloat(String(document.getElementById('filter-rmsd')?.value || '')); if (Number.isFinite(rmsdRaw)) filtering.refolding_rmsd_threshold = rmsdRaw; // Entities + uploads const { entities, uploads } = gatherEntitiesLight(); // Constraints const constraints = gatherConstraintsSafe(); const job = { name: jobName, protocol, num_designs: numDesigns > 0 ? numDesigns : 10, budget: budget > 0 ? budget : 2, stages, filtering, entities: entities.length ? entities : deepClone(defaultApiPayload()[modelKey].entities), constraints, uploads: uploads && Object.keys(uploads).length ? uploads : { 'target.cif': '' } }; const payload = { workflow_name: job.name, [modelKey]: job }; return stripExecutionContextForApi(payload); } // --- API snippet marker rendering (kept from template, shortened) --- function toDefRefSafe(path) { return String(path).replace(/[^a-zA-Z0-9._:-]+/g, '_').slice(0, 180); } function valueTypeLabel(v) { if (Array.isArray(v)) return 'array'; if (v === null) return 'null'; return typeof v; } function buildGenericPayloadDef(path, value) { const pathLabel = String(path || 'payload'); const type = valueTypeLabel(value); let typeHint = `Expected type: ${escapeHtml(type)}.`; if (type === 'string') typeHint = 'Expected type: string. Replace with the value for your run.'; if (type === 'number') typeHint = 'Expected type: number. Use an integer or decimal.'; if (type === 'boolean') typeHint = 'Expected type: boolean (true or false).'; let extra = 'Replace this example value with a valid value for your model.'; const pathLower = pathLabel.toLowerCase(); if (pathLower.endsWith('.name')) extra = 'Run/job name for this model block. Keep aligned with workflow_name.'; if (pathLower.includes('uploads')) extra = 'For UI submissions this is uploaded automatically. For API usage, replace with base64 file data.'; if (pathLower.includes('entities')) extra = 'Entity specification. Structure files use file.path plus optional directives.'; if (pathLower.includes('constraints')) extra = 'Optional constraints such as bonds or length constraints.'; return { title: pathLabel, html: `
${escapeHtml(pathLabel)}
${typeHint}
${escapeHtml(extra)}
` }; } let apiDynamicDefContent = {}; function stringifyPayloadWithMarkers(payloadObj) { const markers = []; const dynamicDefs = {}; const mark = (value, kind = 'string', defRef = '') => { const token = `__MARK_${markers.length}__`; markers.push({ token, value, kind, defRef }); return token; }; const payload = deepClone(payloadObj); function walk(node, pathParts = []) { if (Array.isArray(node)) { for (let i = 0; i < node.length; i++) { const v = node[i]; const childPath = [...pathParts, `[${i}]`]; if (v && typeof v === 'object') { walk(v, childPath); continue; } const pathStr = childPath.join('.'); const defRef = toDefRefSafe(`payload:${pathStr}`); dynamicDefs[defRef] = buildGenericPayloadDef(pathStr, v); let kind = 'string'; if (typeof v === 'number') kind = 'number'; else if (typeof v === 'boolean') kind = 'boolean'; else if (v === null) kind = 'null'; node[i] = mark(v, kind, defRef); } return; } if (!node || typeof node !== 'object') return; Object.keys(node).forEach((key) => { const v = node[key]; const childPath = [...pathParts, key]; if (v && typeof v === 'object') { walk(v, childPath); return; } const pathStr = childPath.join('.'); const isWorkflowName = pathStr === 'workflow_name'; const isInnerModelName = pathStr === `${modelKey}.name`; let defRef = 'workflow-name'; if (!isWorkflowName && !isInnerModelName) { defRef = toDefRefSafe(`payload:${pathStr}`); dynamicDefs[defRef] = buildGenericPayloadDef(pathStr, v); } let kind = 'string'; if (typeof v === 'number') kind = 'number'; else if (typeof v === 'boolean') kind = 'boolean'; else if (v === null) kind = 'null'; node[key] = mark(v, kind, defRef); }); } walk(payload, []); const jsonText = JSON.stringify(payload, null, 2); let text = jsonText; let html = escapeHtml(jsonText); markers.forEach((m) => { const quotedToken = `"${m.token}"`; const quotedTokenHtml = `"${m.token}"`; const jsonEscaped = JSON.stringify(String(m.value)); let textVal = jsonEscaped; let htmlVal = `${escapeHtml(jsonEscaped)}`; if (m.kind === 'number') { textVal = String(m.value); htmlVal = `${escapeHtml(String(m.value))}`; } else if (m.kind === 'boolean') { textVal = m.value ? 'true' : 'false'; htmlVal = `${m.value ? 'true' : 'false'}`; } else if (m.kind === 'null') { textVal = 'null'; htmlVal = `null`; } text = text.split(quotedToken).join(textVal); html = html.split(quotedTokenHtml).join(htmlVal); }); return { text, html, defs: dynamicDefs }; } function getApiTemplate(lang, payloadText, payloadHtml) { const HEREDOC_TAG = '__VICI_PAYLOAD_JSON__'; if (lang === 'python') { return { text: [ '# POST a BoltzGen job (Python)', '# Set TOKEN_ID and TOKEN_SECRET to your values.', 'import json', 'import requests', '', `API_URL = "${MODEL_API_ENDPOINT}"`, 'TOKEN_ID = ""', 'TOKEN_SECRET = ""', '', 'payload = json.loads(r"""', payloadText, '""")', '', 'resp = requests.post(', ' API_URL,', ' headers={', ' "Content-Type": "application/json",', ' "Token-ID": TOKEN_ID,', ' "Token-Secret": TOKEN_SECRET,', ' },', ' json=payload', ')', '', 'resp.raise_for_status()', 'print(resp.json())' ].join('\n'), html: [ '# POST a BoltzGen job (Python)', '# Set TOKEN_ID and TOKEN_SECRET to your values.', 'import json', 'import requests', '', `API_URL = "${escapeHtml(MODEL_API_ENDPOINT)}"`, `TOKEN_ID = "<TOKEN_ID>"`, `TOKEN_SECRET = "<TOKEN_SECRET>"`, '', 'payload = json.loads(r"""', payloadHtml, '""")', '', 'resp = requests.post(', ' API_URL,', ' headers={', ' "Content-Type": "application/json",', ' "Token-ID": TOKEN_ID,', ' "Token-Secret": TOKEN_SECRET,', ' },', ' json=payload', ')', '', 'resp.raise_for_status()', 'print(resp.json())' ].join('\n') }; } if (lang === 'curl') { return { text: [ '# POST a BoltzGen job (cURL)', '# Set TOKEN_ID and TOKEN_SECRET to your values.', '', `curl -X POST "${MODEL_API_ENDPOINT}" \\`, ' -H "Content-Type: application/json" \\', ' -H "Token-ID: " \\', ' -H "Token-Secret: " \\', ` --data-binary @- <<'${HEREDOC_TAG}'`, payloadText, HEREDOC_TAG ].join('\n'), html: [ '# POST a BoltzGen job (cURL)', '# Set TOKEN_ID and TOKEN_SECRET to your values.', '', `curl -X POST "${escapeHtml(MODEL_API_ENDPOINT)}" \\`, ' -H "Content-Type: application/json" \\', ' -H "Token-ID: <TOKEN_ID>" \\', ' -H "Token-Secret: <TOKEN_SECRET>" \\', ` --data-binary @- <<'${escapeHtml(HEREDOC_TAG)}'`, payloadHtml, `${escapeHtml(HEREDOC_TAG)}` ].join('\n') }; } return { text: [ '// POST a BoltzGen job (JavaScript)', '// Set TOKEN_ID and TOKEN_SECRET to your values.', '', '(async () => {', ` const API_URL = "${MODEL_API_ENDPOINT}";`, ' const TOKEN_ID = "";', ' const TOKEN_SECRET = "";', '', ` const payload = ${payloadText};`, '', ' const resp = await fetch(API_URL, {', ' method: "POST",', ' headers: {', ' "Content-Type": "application/json",', ' "Token-ID": TOKEN_ID,', ' "Token-Secret": TOKEN_SECRET,', ' },', ' body: JSON.stringify(payload),', ' });', '', ' if (!resp.ok) throw new Error(`Request failed: ${resp.status}`);', '', ' console.log(await resp.json());', '})().catch((err) => {', ' console.error(err);', '});' ].join('\n'), html: [ '// POST a BoltzGen job (JavaScript)', '// Set TOKEN_ID and TOKEN_SECRET to your values.', '', '(async () => {', ` const API_URL = "${escapeHtml(MODEL_API_ENDPOINT)}";`, ` const TOKEN_ID = "<TOKEN_ID>";`, ` const TOKEN_SECRET = "<TOKEN_SECRET>";`, '', ` const payload = ${payloadHtml};`, '', ' const resp = await fetch(API_URL, {', ' method: "POST",', ' headers: {', ' "Content-Type": "application/json",', ' "Token-ID": TOKEN_ID,', ' "Token-Secret": TOKEN_SECRET,', ' },', ' body: JSON.stringify(payload),', ' });', '', ' if (!resp.ok) throw new Error(`Request failed: ${resp.status}`);', '', ' console.log(await resp.json());', '})().catch((err) => {', ' console.error(err);', '});' ].join('\n') }; } // --- API definitions popout --- let apiDefPopoutEl = null; let apiDefAnchorEl = null; let apiDefHideTimer = null; function ensureApiDefPopout() { if (apiDefPopoutEl) return apiDefPopoutEl; const el = document.createElement('div'); el.className = 'api-def-popout'; el.setAttribute('role', 'dialog'); el.setAttribute('aria-hidden', 'true'); el.innerHTML = `
`; el.addEventListener('mouseenter', () => { if (apiDefHideTimer) { clearTimeout(apiDefHideTimer); apiDefHideTimer = null; } }); el.addEventListener('mouseleave', () => scheduleHideApiDefPopout()); document.body.appendChild(el); apiDefPopoutEl = el; return el; } function getApiDefinition(defRef) { return apiDynamicDefContent?.[defRef] || API_DEF_CONTENT?.[defRef] || null; } function positionApiDefPopout(anchorEl) { const pop = ensureApiDefPopout(); if (!anchorEl) return; const a = anchorEl.getBoundingClientRect(); const p = pop.getBoundingClientRect(); const gap = 10; const margin = 12; let left = a.left; let top = a.bottom + gap; if (left + p.width > window.innerWidth - margin) left = window.innerWidth - p.width - margin; if (left < margin) left = margin; if (top + p.height > window.innerHeight - margin) top = a.top - p.height - gap; if (top < margin) top = margin; pop.style.left = `${Math.round(left)}px`; pop.style.top = `${Math.round(top)}px`; } function showApiDefPopoutFor(targetEl) { if (!targetEl) return; const defRef = targetEl.getAttribute('data-def-ref'); if (!defRef) return; const def = getApiDefinition(defRef); if (!def) return; if (apiDefHideTimer) { clearTimeout(apiDefHideTimer); apiDefHideTimer = null; } const pop = ensureApiDefPopout(); pop.querySelector('.api-def-popout__title').textContent = def.title || defRef; pop.querySelector('.api-def-popout__body').innerHTML = def.html || ''; apiDefAnchorEl = targetEl; pop.classList.add('is-visible'); pop.setAttribute('aria-hidden', 'false'); positionApiDefPopout(targetEl); } function hideApiDefPopout() { const pop = ensureApiDefPopout(); pop.classList.remove('is-visible'); pop.setAttribute('aria-hidden', 'true'); apiDefAnchorEl = null; } function scheduleHideApiDefPopout(delay = 120) { if (apiDefHideTimer) clearTimeout(apiDefHideTimer); apiDefHideTimer = setTimeout(() => { apiDefHideTimer = null; hideApiDefPopout(); }, delay); } function bindApiDefinitionPopout() { if (!apiCodeEl) return; ensureApiDefPopout(); apiCodeEl.addEventListener('mouseover', (e) => { const target = e.target.closest('.tok-editable[data-def-ref]'); if (!target || !apiCodeEl.contains(target)) return; showApiDefPopoutFor(target); }); apiCodeEl.addEventListener('mouseout', (e) => { const from = e.target.closest('.tok-editable[data-def-ref]'); if (!from || !apiCodeEl.contains(from)) return; const to = e.relatedTarget; if (to && (from.contains(to) || ensureApiDefPopout().contains(to))) return; scheduleHideApiDefPopout(); }); apiCodeEl.addEventListener('mousemove', (e) => { const target = e.target.closest('.tok-editable[data-def-ref]'); if (!target || !apiCodeEl.contains(target)) return; if (apiDefAnchorEl === target) positionApiDefPopout(target); }); window.addEventListener('scroll', () => { if (apiDefAnchorEl && apiDefPopoutEl?.classList.contains('is-visible')) positionApiDefPopout(apiDefAnchorEl); }, true); window.addEventListener('resize', () => { if (apiDefAnchorEl && apiDefPopoutEl?.classList.contains('is-visible')) positionApiDefPopout(apiDefAnchorEl); }); } // --- API render --- async function renderApiSnippet({ forceDefault = false, toast = false } = {}) { if (!apiCodeEl) return; if (isRenderingApiSnippet) return; const seq = ++renderSeq; isRenderingApiSnippet = true; try { apiCodeEl.textContent = '# Building API snippet...'; let payloadSource = null; apiDynamicDefContent = {}; if (!forceDefault) { try { payloadSource = await buildBoltzgenPayloadFromForm({ forApi: true }); } catch (err) { payloadSource = null; } } if (!payloadSource) { payloadSource = deepClone(defaultApiPayload()); } payloadSource = stripExecutionContextForApi(payloadSource); const payloadBlock = stringifyPayloadWithMarkers(payloadSource); apiDynamicDefContent = payloadBlock.defs || {}; const snippet = getApiTemplate(currentApiLang, payloadBlock.text, payloadBlock.html); currentApiSnippet = snippet; if (seq !== renderSeq) return; apiCodeEl.innerHTML = snippet.html; if (toast) { showToast?.('success', forceDefault ? 'Reset API snippet to defaults.' : 'Synced API snippet from form.'); } } finally { isRenderingApiSnippet = false; } } function setApiLang(lang) { if (!API_LANGS.includes(lang)) return; currentApiLang = lang; apiLangTabs.forEach(btn => { const active = btn.dataset.lang === lang; btn.classList.toggle('is-active', active); btn.setAttribute('aria-selected', active ? 'true' : 'false'); }); renderApiSnippet({ forceDefault: false, toast: false }); } function findApiActionButtons() { let syncBtn = null, copyBtn = null, resetApiBtn = null; apiActionBtns.forEach((btn, i) => { const label = `${btn.getAttribute('aria-label') || ''} ${btn.title || ''} ${btn.textContent || ''}`.toLowerCase(); if (!syncBtn && label.includes('sync')) syncBtn = btn; else if (!copyBtn && label.includes('copy')) copyBtn = btn; else if (!resetApiBtn && label.includes('reset')) resetApiBtn = btn; if (i === 0 && !syncBtn) syncBtn = btn; if (i === 1 && !copyBtn) copyBtn = btn; if (i === 2 && !resetApiBtn) resetApiBtn = btn; }); return { syncBtn, copyBtn, resetApiBtn }; } function bindApiControls() { apiLangTabs.forEach(btn => { btn.addEventListener('click', () => setApiLang(btn.dataset.lang || 'python')); btn.addEventListener('keydown', (e) => { if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return; e.preventDefault(); const i = apiLangTabs.indexOf(btn); let next = i; if (e.key === 'ArrowRight') next = (i + 1) % apiLangTabs.length; if (e.key === 'ArrowLeft') next = (i - 1 + apiLangTabs.length) % apiLangTabs.length; if (e.key === 'Home') next = 0; if (e.key === 'End') next = apiLangTabs.length - 1; apiLangTabs[next]?.focus(); setApiLang(apiLangTabs[next]?.dataset.lang || 'python'); }); }); const { syncBtn, copyBtn, resetApiBtn } = findApiActionButtons(); syncBtn?.addEventListener('click', async () => { pulseBtn(syncBtn, 'pulse-blue'); // Keep uploads as placeholders by default apiInlineUploads = false; await renderApiSnippet({ forceDefault: false, toast: true }); }); copyBtn?.addEventListener('click', () => { const text = currentApiSnippet?.text?.trim(); if (!text) { showToast?.('error', 'Nothing to copy yet.'); return; } pulseBtn(copyBtn, 'pulse-green'); copyTextRobust(text) .then(() => showToast?.('success', 'Copied API snippet.')) .catch(() => showToast?.('error', 'Copy failed. Select code and copy manually.')); }); resetApiBtn?.addEventListener('click', async () => { pulseBtn(resetApiBtn, 'pulse-red'); apiInlineUploads = false; await renderApiSnippet({ forceDefault: true, toast: true }); }); } // --- Execute --- function bindExecute() { if (executeBtnMembers && executeBtnMembers.tagName.toLowerCase() === 'button') { executeBtnMembers.type = 'button'; executeBtnMembers.addEventListener('click', (e) => { e.preventDefault(); if (typeof window.submitBoltzgenJob === 'function') { window.submitBoltzgenJob(); } else { showToast?.('error', 'submitBoltzgenJob is not available on this page.'); } }); } if (executeBtnGuest && executeBtnGuest.tagName.toLowerCase() === 'a') { // guest link stays as-is } } // Keep API preview updated when user edits while on API tab (cheap, light gather) function bindApiAutoRefresh() { root.addEventListener('input', () => { if (currentTab === 'api') renderApiSnippet({ forceDefault: false, toast: false }); }); root.addEventListener('change', () => { if (currentTab === 'api') renderApiSnippet({ forceDefault: false, toast: false }); }); } function init() { bindHashRouting(); initTabs(); bindApiControls(); bindApiDefinitionPopout(); bindExecute(); bindApiAutoRefresh(); setApiLang('python'); renderApiSnippet({ forceDefault: false, toast: false }); window.ModelPage = { root, modelSlug, modelKey, setTab, getCurrentTab: () => currentTab, renderApiSnippet, setApiLang, getApiSnippet: () => ({ ...currentApiSnippet }), endpoints: { workflow: MODEL_WORKFLOW_ENDPOINT, api: MODEL_API_ENDPOINT, status: MODEL_STATUS_ENDPOINT } }; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();