/** * 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 => `
  • ${s}
  • `).join(''); const picks = Object.entries(ai.recommended_entrypoints || {}).map( ([k, v]) => `${k}${v}` ).join(''); const intentRows = (help.intent_map || []).slice(0, 12).map(intent => ` ${intent.id} ${intent.user_wants} ${intent.method} ${intent.path} ${(intent.keywords || []).slice(0, 4).join(', ')} `).join(''); panel.innerHTML = `

    ${help.summary || ''}

    ${ai.read_this_first || ''}

    Query algorithm

      ${steps}

    Recommended entrypoints

    ${picks}
    KeyEndpoint

    Intent map (first 12 — full list in /api/max/help)

    ${intentRows}
    IDUser wantsEndpointKeywords

    Search intents: GET /api/max/help/intents?q=futures  |  Download: guide.json

    `; } catch (err) { panel.innerHTML = `

    Could not load AI guide: ${err.message}. Use /api/max/help directly.

    `; } } 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 = `
    ${(summary.comprehensive_resources || 0) + (summary.unified_registry_entries || 0)}Resource entries
    ${providers.healthy_count ?? 0}/${providers.probed_count ?? 0}Provider probes
    ${pages.functional_count ?? 0}/${pages.total ?? 0}Functional pages
    ${(pressure.providers || []).length}Pressure-tracked providers
    ${health.cache?.entries ?? 0}Cache entries
    `; } catch (err) { el.innerHTML = `

    Live /api/max stats unavailable (${err.message}). Check /api/max/health after the Space starts.

    `; } } 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 = 'Max Provider API'; toc.insertBefore(li, toc.children[2] || null); } const endpoints = help.catalog?.endpoints || []; const rows = endpoints.map(ep => ` ${ep.method || 'GET'} ${ep.path} ${ep.group || 'API'} ${ep.purpose || ''} `).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 => `
  • ${n}
  • `).join(''); const section = document.createElement('section'); section.className = 'help-section'; section.id = 'max-provider-api'; section.innerHTML = `

    Max Provider API — Endpoint Catalog

    Live from /api/max/help (schema v${help.schema_version || '1'}). ${help.intent_count || 0} intents mapped for AI agents.

    ${rows || ''}
    MethodPathGroupPurpose
    Endpoint catalog unavailable.
    ${examples || `curl ${API_BASE}/api/max/health`}

    Runtime guarantees

    `; main.insertBefore(section, main.firstElementChild?.nextSibling || main.firstElementChild || null); this.setupCopyButtons(section); this.setupToc(); } } export default HelpPage;