Really-amin's picture
Upload static/pages/help/help.js with huggingface_hub
7cc11f4 verified
Raw
History Blame Contribute Delete
8.22 kB
/**
* Help Page — live API catalog, search, copy buttons, smooth navigation
*/
const API_BASE = window.location.origin;
class HelpPage {
async init() {
this.setupSearch();
this.setupCopyButtons();
this.setupToc();
await this.loadLiveStats();
await this.renderAiAgentGuide();
await this.injectMaxProviderGuide();
console.log('[Help] Ready with /api/max catalog');
}
async renderAiAgentGuide() {
const panel = document.getElementById('ai-agent-panel');
if (!panel) return;
try {
const help = await this.fetchJson('/api/max/help');
const ai = help.for_ai_agents || {};
const steps = (ai.query_algorithm || []).map(s => `<li>${s}</li>`).join('');
const picks = Object.entries(ai.recommended_entrypoints || {}).map(
([k, v]) => `<tr><td><code>${k}</code></td><td><code>${v}</code></td></tr>`
).join('');
const intentRows = (help.intent_map || []).slice(0, 12).map(intent => `
<tr>
<td><code>${intent.id}</code></td>
<td>${intent.user_wants}</td>
<td><code>${intent.method} ${intent.path}</code></td>
<td>${(intent.keywords || []).slice(0, 4).join(', ')}</td>
</tr>
`).join('');
panel.innerHTML = `
<p><strong>${help.summary || ''}</strong></p>
<p>${ai.read_this_first || ''}</p>
<h3>Query algorithm</h3>
<ol>${steps}</ol>
<h3>Recommended entrypoints</h3>
<table class="api-table">
<thead><tr><th>Key</th><th>Endpoint</th></tr></thead>
<tbody>${picks}</tbody>
</table>
<h3>Intent map (first 12 — full list in <a href="/api/max/help">/api/max/help</a>)</h3>
<table class="api-table">
<thead><tr><th>ID</th><th>User wants</th><th>Endpoint</th><th>Keywords</th></tr></thead>
<tbody>${intentRows}</tbody>
</table>
<p>Search intents: <code>GET /api/max/help/intents?q=futures</code>
&nbsp;|&nbsp; Download: <a href="/api/max/help/guide.json">guide.json</a></p>
`;
} catch (err) {
panel.innerHTML = `<p class="help-note">Could not load AI guide: ${err.message}. Use <code>/api/max/help</code> directly.</p>`;
}
}
setupSearch() {
const input = document.getElementById('help-search');
if (!input) return;
input.addEventListener('input', (e) => {
const q = e.target.value.toLowerCase().trim();
document.querySelectorAll('.help-section').forEach((section) => {
const text = section.textContent.toLowerCase();
section.style.display = !q || text.includes(q) ? '' : 'none';
});
});
}
setupToc() {
document.querySelectorAll('.help-toc a').forEach((link) => {
link.addEventListener('click', (e) => {
const id = link.getAttribute('href')?.slice(1);
const el = id && document.getElementById(id);
if (el) {
e.preventDefault();
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
}
setupCopyButtons(root = document) {
root.querySelectorAll('[data-copy], .copy-btn').forEach((btn) => {
if (btn.dataset.boundCopy === 'true') return;
btn.dataset.boundCopy = 'true';
btn.addEventListener('click', async () => {
const text = btn.getAttribute('data-copy') || btn.closest('.code-wrap')?.querySelector('code')?.textContent;
if (!text) return;
try {
await navigator.clipboard.writeText(text.trim());
const label = btn.textContent;
btn.textContent = 'Copied!';
setTimeout(() => { btn.textContent = label; }, 1500);
} catch {
btn.textContent = 'Copy failed';
}
});
});
}
async fetchJson(path, timeoutMs = 15000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(`${API_BASE}${path}`, { signal: controller.signal, headers: { Accept: 'application/json' } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} finally {
clearTimeout(timer);
}
}
async loadLiveStats() {
const el = document.getElementById('live-stats');
if (!el) return;
try {
const [health, providers, pages, pressure] = await Promise.all([
this.fetchJson('/api/max/health'),
this.fetchJson('/api/max/providers/status?symbol=BTC'),
this.fetchJson('/api/max/pages/functionality'),
this.fetchJson('/api/max/pressure/status'),
]);
const summary = health.summary || health.resource_registry || {};
el.innerHTML = `
<div class="stat-pill"><strong>${(summary.comprehensive_resources || 0) + (summary.unified_registry_entries || 0)}</strong><span>Resource entries</span></div>
<div class="stat-pill"><strong>${providers.healthy_count ?? 0}/${providers.probed_count ?? 0}</strong><span>Provider probes</span></div>
<div class="stat-pill"><strong>${pages.functional_count ?? 0}/${pages.total ?? 0}</strong><span>Functional pages</span></div>
<div class="stat-pill"><strong>${(pressure.providers || []).length}</strong><span>Pressure-tracked providers</span></div>
<div class="stat-pill"><strong>${health.cache?.entries ?? 0}</strong><span>Cache entries</span></div>
`;
} catch (err) {
el.innerHTML = `<p class="help-note">Live /api/max stats unavailable (${err.message}). Check <code>/api/max/health</code> after the Space starts.</p>`;
}
}
async injectMaxProviderGuide() {
const main = document.querySelector('.help-main');
if (!main || document.getElementById('max-provider-api')) return;
let help;
try {
help = await this.fetchJson('/api/max/help');
} catch (error) {
help = {
catalog: { endpoints: [] },
examples: {
health: 'curl $BASE/api/max/health',
providers: "curl '$BASE/api/max/providers/status?symbol=BTC&include_slow=true'",
visual: "curl '$BASE/api/max/system/visual?symbol=BTC'",
},
important_notes: [`Could not load live catalog: ${error.message}`],
};
}
const toc = document.querySelector('.help-toc ul');
if (toc && !toc.querySelector('a[href="#max-provider-api"]')) {
const li = document.createElement('li');
li.innerHTML = '<a href="#max-provider-api">Max Provider API</a>';
toc.insertBefore(li, toc.children[2] || null);
}
const endpoints = help.catalog?.endpoints || [];
const rows = endpoints.map(ep => `
<tr>
<td class="method-${String(ep.method || 'GET').toLowerCase()}">${ep.method || 'GET'}</td>
<td><code>${ep.path}</code></td>
<td>${ep.group || 'API'}</td>
<td>${ep.purpose || ''}</td>
</tr>
`).join('');
const examples = Object.entries(help.quick_examples || help.examples || {}).map(
([name, cmd]) => `${name}: ${String(cmd).replaceAll('{BASE}', API_BASE).replaceAll('$BASE', API_BASE)}`
).join('\n');
const notes = (help.important_rules || help.important_notes || []).map(n => `<li>${n}</li>`).join('');
const section = document.createElement('section');
section.className = 'help-section';
section.id = 'max-provider-api';
section.innerHTML = `
<h2>Max Provider API — Endpoint Catalog</h2>
<p>Live from <code>/api/max/help</code> (schema v${help.schema_version || '1'}). ${help.intent_count || 0} intents mapped for AI agents.</p>
<table class="api-table">
<thead><tr><th>Method</th><th>Path</th><th>Group</th><th>Purpose</th></tr></thead>
<tbody>${rows || '<tr><td colspan="4">Endpoint catalog unavailable.</td></tr>'}</tbody>
</table>
<div class="code-wrap">
<button class="copy-btn" type="button">Copy</button>
<pre class="code-block"><code>${examples || `curl ${API_BASE}/api/max/health`}</code></pre>
</div>
<h3>Runtime guarantees</h3>
<ul>${notes}</ul>
`;
main.insertBefore(section, main.firstElementChild?.nextSibling || main.firstElementChild || null);
this.setupCopyButtons(section);
this.setupToc();
}
}
export default HelpPage;