// HuggingBot Module: HuggingFace Hub v2.0.0 (FULL + EXPANDED) // ============================================================================ // Full remote control of the HuggingFace Hub from chat: // auth, Spaces CRUD, file ops, secrets/hardware/sleep config, search, // model inference (Inference API), Gradio Space API, repo browsing. // Rebuilt for the HuggingBot ModuleSystem from ChatSeed v12 (all API fixes // applied: flavor/gcTimeout keys, PUT /settings, paused-space delete pre-check, // deep hardware name extraction) and expanded with: // * hf_search_datasets // * modern Gradio API routing (/api/{name} with /api/predict fallback) // * pipeline-aware Inference API calls (task routing) // * binary (image) inference response handling -> inline markdown // * multi-repo-type support for file/repo operations (space|model|dataset) // ============================================================================ (function(MS) { 'use strict'; var HF_API_BASE = 'https://huggingface.co'; var INFER_API_BASE = 'https://api-inference.huggingface.co'; var TOKEN_KEY = 'chatseed_hf_token'; // ─── Token helpers ──────────────────────────────────────────────────── function getToken() { try { return localStorage.getItem(TOKEN_KEY) || ''; } catch (e) { return ''; } } function setToken(t) { try { localStorage.setItem(TOKEN_KEY, t); } catch (e) {} } function clearToken() { try { localStorage.removeItem(TOKEN_KEY); } catch (e) {} } function authHeader() { var t = getToken(); if (!t) throw new Error('No HuggingFace token set. Use hf_set_token or hf_login_ui to set your HF access token first.'); return { 'Authorization': 'Bearer ' + t }; } // ─── Fetch helpers ──────────────────────────────────────────────────── async function hfFetch(path, options) { options = options || {}; var headers = Object.assign({ 'Content-Type': 'application/json' }, authHeader(), options.headers || {}); var res = await fetch(HF_API_BASE + path, Object.assign({}, options, { headers: headers })); if (!res.ok) { var msg = ''; try { var err = await res.json(); msg = err.error || JSON.stringify(err); } catch (e) { try { msg = await res.text(); } catch (e2) { msg = res.statusText; } } throw new Error('HF API ' + res.status + ': ' + msg); } if (res.status === 204) return null; return (res.headers.get('content-type') || '').indexOf('application/json') > -1 ? res.json() : res.text(); } async function hfFetchRaw(path) { var token = getToken(); var headers = {}; if (token) headers['Authorization'] = 'Bearer ' + token; var res = await fetch(HF_API_BASE + path, { headers: headers }); if (!res.ok) { var msg = ''; try { var err = await res.json(); msg = err.error || JSON.stringify(err); } catch (e) { msg = res.statusText; } throw new Error('HF API ' + res.status + ': ' + msg); } return res.text(); } async function hfFetchHead(path) { var token = getToken(); var headers = {}; if (token) headers['Authorization'] = 'Bearer ' + token; var res = await fetch(HF_API_BASE + path, { method: 'HEAD', headers: headers }); return { ok: res.ok, status: res.status }; } // ─── Repo helpers ───────────────────────────────────────────────────── function repoTypePath(type) { switch (type) { case 'model': return 'models'; case 'dataset': return 'datasets'; case 'space': return 'spaces'; default: return 'spaces'; } } function parseRepoId(repoId) { var parts = String(repoId || '').split('/'); if (parts.length !== 2) throw new Error('Invalid repo_id. Use format "namespace/repo-name"'); return { namespace: parts[0], name: parts[1] }; } async function commitFiles(repoType, namespace, repo, files, deletedEntries, summary, description) { var body = { summary: summary || 'Update via HuggingBot', description: description || '', files: (files || []).map(function (f) { return { path: f.path, content: f.content, encoding: f.encoding || 'utf-8' }; }) }; if (deletedEntries && deletedEntries.length) body.deletedEntries = deletedEntries; return hfFetch('/api/' + repoTypePath(repoType) + '/' + namespace + '/' + repo + '/commit/main', { method: 'POST', body: JSON.stringify(body) }); } // Centralized settings helper — routes ALL Space settings through // PUT /api/spaces/{ns}/{name}/settings with the keys HF actually accepts. async function updateSpaceSettings(namespace, repo, settings) { return hfFetch('/api/spaces/' + namespace + '/' + repo + '/settings', { method: 'PUT', body: JSON.stringify(settings) }); } // Deep-extract a human-readable hardware name from varied API response shapes. function extractHardwareName(hw) { if (!hw) return 'cpu-basic'; if (typeof hw === 'string') return hw; if (typeof hw === 'object') { return hw.currentPrettyName || hw.requestedPrettyName || (hw.current ? (typeof hw.current === 'object' ? (hw.current.id || hw.current.name) : hw.current) : null) || (hw.requested ? (typeof hw.requested === 'object' ? (hw.requested.id || hw.requested.name) : hw.requested) : null) || hw.id || hw.name || hw.displayName || JSON.stringify(hw) || 'cpu-basic'; } return String(hw); } function repoAppUrl(repoId) { return 'https://' + repoId.replace(/\//g, '-') + '.hf.space'; } function maskToken(t) { return t.substring(0, 6) + '…' + t.substring(t.length - 4); } function parseJsonArg(v) { if (!v) return {}; if (typeof v === 'object') return v; try { return JSON.parse(v); } catch (e) { throw new Error('Invalid JSON argument: ' + v); } } // ─── Secure Login Popup (token never passes through the model) ──────── function openLoginPopup() { var existing = document.getElementById('huggingbot-hf-login-overlay'); if (existing) existing.remove(); var overlay = document.createElement('div'); overlay.id = 'huggingbot-hf-login-overlay'; overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.7);z-index:10000;display:flex;align-items:center;justify-content:center;padding:16px;'; var box = document.createElement('div'); box.style.cssText = 'background:#111827;border:1px solid #374151;border-radius:16px;padding:24px;width:420px;max-width:100%;box-shadow:0 25px 50px rgba(0,0,0,.5);font-family:system-ui,sans-serif;'; var header = document.createElement('div'); header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;'; header.innerHTML = '
🤗HuggingFace Login
' + ''; var body = document.createElement('div'); body.innerHTML = '

Paste your HuggingFace access token.

' + '' + '
' + '' + '
' + '' + '
' + '

Get a token at huggingface.co/settings/tokens

'; box.appendChild(header); box.appendChild(body); overlay.appendChild(box); document.body.appendChild(overlay); var input = document.getElementById('huggingbot-hf-token'); var statusEl = document.getElementById('huggingbot-hf-status'); var submitBtn = document.getElementById('huggingbot-hf-submit'); function close() { overlay.remove(); } function showStatus(msg, color) { statusEl.style.display = 'block'; statusEl.style.background = color + '18'; statusEl.style.border = '1px solid ' + color + '44'; statusEl.style.color = color; statusEl.innerHTML = msg; } document.getElementById('huggingbot-hf-show').addEventListener('change', function () { input.type = this.checked ? 'text' : 'password'; }); document.getElementById('huggingbot-hf-close').addEventListener('click', close); document.getElementById('huggingbot-hf-cancel').addEventListener('click', close); submitBtn.addEventListener('click', async function () { var token = input.value.trim(); if (!token) return showStatus('⚠️ Please enter a token.', '#f59e0b'); if (token.indexOf('hf_') !== 0) return showStatus('⚠️ Tokens should start with hf_.', '#f59e0b'); setToken(token); submitBtn.disabled = true; submitBtn.textContent = '⏳ Verifying...'; try { var res = await fetch('https://huggingface.co/api/whoami-v2', { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); if (res.ok) { var info = await res.json(); showStatus('✅ Authenticated as ' + (info.name || info.user || 'unknown') + ' (' + maskToken(token) + ')', '#059669'); setTimeout(close, 1200); } else { showStatus('⚠️ Token stored, but verification failed (' + res.status + ').', '#f59e0b'); submitBtn.disabled = false; submitBtn.textContent = '🔐 Set Token'; } } catch (e) { showStatus('⚠️ Token stored locally, but could not verify: ' + e.message, '#f59e0b'); submitBtn.disabled = false; submitBtn.textContent = '🔐 Set Token'; } }); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') submitBtn.click(); if (e.key === 'Escape') close(); }); setTimeout(function () { input.focus(); }, 100); } // ─── Tool schemas (exactly as registered in the runtime) ────────────── var TOOLS = [ { name: 'hf_set_token', description: 'Store your HuggingFace access token for this session. Get it from https://huggingface.co/settings/tokens', parameters: { type: 'object', properties: { token: { type: 'string', description: 'Your HF access token (hf_...). Required for all write operations.' } }, required: ['token'] } }, { name: 'hf_login_ui', description: '🔐 Open a secure popup to enter your HF token. The token is stored directly in your browser — it never passes through the AI model.', parameters: { type: 'object', properties: {} } }, { name: 'hf_logout', description: 'Clear your stored HuggingFace token from localStorage.', parameters: { type: 'object', properties: {} } }, { name: 'hf_token_status', description: 'Check if a HF token is stored and who it belongs to (without revealing the token).', parameters: { type: 'object', properties: {} } }, { name: 'hf_whoami', description: 'Check who you are authenticated as on HuggingFace Hub.', parameters: { type: 'object', properties: {} } }, { name: 'hf_create_space', description: 'Create a new HuggingFace Space. Supports Gradio, Streamlit, Docker, or static SDKs.', parameters: { type: 'object', properties: { name: { type: 'string', description: 'Space name (e.g. "my-cool-demo"). Must be unique under your namespace.' }, sdk: { type: 'string', enum: ['gradio', 'streamlit', 'docker', 'static'], default: 'gradio', description: 'SDK/framework to use' }, type: { type: 'string', enum: ['space'], default: 'space' }, title: { type: 'string', description: 'Optional display title' }, license: { type: 'string', description: 'Optional license (e.g. "mit", "apache-2.0")' }, hardware: { type: 'string', description: 'Hardware upgrade (e.g. "t4-medium", "cpu-upgrade"). See HF docs for options.' }, secrets: { type: 'string', description: 'JSON object of secrets {"key": "value", ...}' }, variables: { type: 'string', description: 'JSON object of env variables {"KEY": "VALUE", ...}' }, sleep_time: { type: 'number', description: 'Sleep timeout in seconds (for GPU spaces)' }, private: { type: 'boolean', default: false, description: 'Create as private space?' }, org: { type: 'string', description: 'Organization namespace (if creating under an org)' } }, required: ['name'] } }, { name: 'hf_duplicate_space', description: 'Duplicate an existing Space (useful for forking with custom config).', parameters: { type: 'object', properties: { from: { type: 'string', description: 'Source Space ID (e.g. "username/space-name")' }, name: { type: 'string', description: 'New Space name' }, org: { type: 'string', description: 'Optional org to duplicate into' }, hardware: { type: 'string', description: 'Hardware for the new Space' }, secrets: { type: 'string', description: 'JSON of secrets' }, variables: { type: 'string', description: 'JSON of env variables' }, sleep_time: { type: 'number', description: 'Sleep timeout in seconds' }, private: { type: 'boolean', default: false } }, required: ['from', 'name'] } }, { name: 'hf_delete_space', description: 'Delete a Space repository from HuggingFace Hub.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID to delete (e.g. "username/my-space")' } }, required: ['repo_id'] } }, { name: 'hf_upload_file', description: 'Upload a single file to a Space (app.py, requirements.txt, README.md, etc.). Triggers a rebuild.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'Path in the repo (e.g. "app.py" or "src/utils.py")' }, content: { type: 'string', description: 'File content as text' }, summary: { type: 'string', description: 'Commit message (default: "Update via HuggingBot")' }, description: { type: 'string', description: 'Optional commit description' } }, required: ['repo_id', 'path', 'content'] } }, { name: 'hf_upload_app', description: 'Upload a complete Gradio/Streamlit app with requirements.txt in one go.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, app_code: { type: 'string', description: 'The app.py (or app.js for static) source code' }, requirements: { type: 'string', description: 'Optional requirements.txt content (one package per line)' }, readme: { type: 'string', description: 'Optional README.md content' }, extra_files: { type: 'string', description: 'Optional JSON of extra files {"path": "content", ...}' }, summary: { type: 'string', description: 'Commit message' } }, required: ['repo_id', 'app_code'] } }, { name: 'hf_delete_file', description: 'Delete a file from a Space repository.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'File path in the repo to delete' }, summary: { type: 'string', description: 'Commit message' } }, required: ['repo_id', 'path'] } }, { name: 'hf_set_secret', description: 'Add or update a secret (environment variable) for a Space. Triggers a restart.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, key: { type: 'string', description: 'Secret name (e.g. "HF_TOKEN", "API_KEY")' }, value: { type: 'string', description: 'Secret value' } }, required: ['repo_id', 'key', 'value'] } }, { name: 'hf_delete_secret', description: 'Delete a secret from a Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, key: { type: 'string', description: 'Secret name to delete' } }, required: ['repo_id', 'key'] } }, { name: 'hf_set_hardware', description: 'Request a hardware upgrade/downgrade for a Space (e.g. CPU → GPU). Triggers a restart.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, hardware: { type: 'string', description: 'Hardware tier: cpu-basic (free), cpu-upgrade ($0.03/hr), t4-small ($0.40), t4-medium ($0.60), l4-1x ($0.80), a10g-small ($1.00), a100-large ($2.50)', enum: ['cpu-basic', 'cpu-upgrade', 't4-small', 't4-medium', 'l4-1x', 'a10g-small', 'a10g-large', 'a100-large'] }, sleep_time: { type: 'number', description: 'Auto-sleep timeout in seconds (for GPU) - e.g. 7200 = 2h' } }, required: ['repo_id', 'hardware'] } }, { name: 'hf_set_visibility', description: 'Change the visibility of a Space (public / private).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, visibility: { type: 'string', enum: ['public', 'private'], description: 'New visibility level' } }, required: ['repo_id', 'visibility'] } }, { name: 'hf_set_sleep', description: 'Set auto-sleep timeout for a Space (useful for GPU spaces to save cost).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, sleep_time: { type: 'number', description: 'Sleep timeout in seconds (0 to disable, 300 = 5 min, 3600 = 1h, 7200 = 2h)' } }, required: ['repo_id', 'sleep_time'] } }, { name: 'hf_pause_space', description: 'Pause a Space to stop billing. It can be restarted later with hf_restart_space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID to pause' } }, required: ['repo_id'] } }, { name: 'hf_restart_space', description: 'Restart a paused Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID to restart' } }, required: ['repo_id'] } }, { name: 'hf_space_status', description: 'Get the runtime status, hardware, and stage of a Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' } }, required: ['repo_id'] } }, { name: 'hf_space_logs', description: 'Fetch the latest build/runtime logs from a Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' } }, required: ['repo_id'] } }, { name: 'hf_search_spaces', description: 'Search for Spaces on the HuggingFace Hub by query.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 5, description: 'Max results (1-25)' } }, required: ['query'] } }, { name: 'hf_list_my_spaces', description: 'List all Spaces owned by your account.', parameters: { type: 'object', properties: { limit: { type: 'number', default: 10, description: 'Max results' } }, required: [] } }, { name: 'hf_search_models', description: 'Search for models on the HuggingFace Hub.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 5, description: 'Max results' } }, required: ['query'] } }, { name: 'hf_call_space', description: 'Call a Gradio Space API endpoint (make predictions/inference via the Space API).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Gradio Space ID (e.g. "abidlabs/en2fr")' }, api_name: { type: 'string', default: '/predict', description: 'API endpoint name (e.g. "/predict", "/generate")' }, data: { type: 'string', description: 'JSON array of input data (e.g. ["Hello world"])' } }, required: ['repo_id', 'data'] } }, { name: 'hf_inference', description: 'Run inference on a HuggingFace model using the free Inference API.', parameters: { type: 'object', properties: { model: { type: 'string', description: 'Model ID (e.g. "gpt2", "meta-llama/Llama-3.2-3B-Instruct")' }, inputs: { type: 'string', description: 'Input text or JSON string of inputs' }, parameters: { type: 'string', description: 'Optional JSON string of generation parameters (max_length, temperature, etc.)' }, task: { type: 'string', description: 'Optional task type (e.g. "text-generation", "image-classification", "automatic-speech-recognition")' } }, required: ['model', 'inputs'] } }, { name: 'hf_repo_info', description: 'Get detailed information about any HF repo (Space, Model, or Dataset).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/repo-name")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id'] } }, { name: 'hf_list_files', description: 'List files in a repository at a given path.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' }, path: { type: 'string', default: '', description: 'Subdirectory path (optional)' }, revision: { type: 'string', default: 'main', description: 'Branch/revision' } }, required: ['repo_id'] } }, { name: 'hf_read_file', description: 'Read the content of a file from ANY HuggingFace repo (Space, Model, or Dataset), including PRIVATE repos.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'File path in the repo (e.g. "app.py", "config.json")' }, revision: { type: 'string', default: 'main', description: 'Branch/commit revision' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id', 'path'] } }, { name: 'hf_file_exists', description: 'Check if a file exists in a HuggingFace repo (including private repos).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'File path to check (e.g. "app.py", "config.json")' }, revision: { type: 'string', default: 'main', description: 'Branch/revision' } }, required: ['repo_id', 'path'] } }, { name: 'hf_repo_exists', description: 'Check if a HuggingFace repository exists (Space, Model, or Dataset). Works for private repos too.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id'] } }, { name: 'hf_list_commits', description: 'View the commit history of a HuggingFace repository.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' }, revision: { type: 'string', default: 'main', description: 'Branch/revision to view commits for' }, limit: { type: 'number', default: 10, description: 'Max commits to show (1-50)' } }, required: ['repo_id'] } }, { name: 'hf_move_file', description: 'Rename or move a file in a HuggingFace repository.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, source_path: { type: 'string', description: 'Current file path (e.g. "old_name.py")' }, dest_path: { type: 'string', description: 'New file path (e.g. "new_name.py" or "subfolder/new_name.py")' }, summary: { type: 'string', description: 'Commit message (default: "Move/Rename file")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id', 'source_path', 'dest_path'] } }, // ── Expanded (new in v2.0.0) ──────────────────────────────────────── { name: 'hf_search_datasets', description: 'Search for datasets on the HuggingFace Hub.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 5, description: 'Max results (1-25)' } }, required: ['query'] } } ]; // ─── Tool implementations ───────────────────────────────────────────── var REGISTRY = { hf_set_token: async function (a) { var t = String(a.token || '').trim(); if (!t || t.indexOf('hf_') !== 0) return '⚠️ Invalid token format. Tokens start with `hf_`. Get one at https://huggingface.co/settings/tokens'; setToken(t); try { var w = await hfFetch('/api/whoami-v2'); return '✅ **HuggingFace token stored** (' + maskToken(t) + ')\n👤 Authenticated as: **' + (w.name || w.user || 'unknown') + '**\nYou can now use all HF tools.'; } catch (e) { return '⚠️ Token stored (' + maskToken(t) + ') but verification failed: ' + e.message + '. Check that the token is valid.'; } }, hf_login_ui: async function () { openLoginPopup(); return '🔐 **Secure Login Popup Opened**\n\nA popup has been opened where you can paste your HuggingFace token.\n\n> 🛡️ Your token is stored directly in your browser\'s localStorage — it **never passes through this AI model**.\n\n💡 Tip: you can also paste your token directly in chat with `hf_set_token`, but the popup is more secure.\n\n📋 Get a token at: https://huggingface.co/settings/tokens'; }, hf_logout: async function () { var had = !!getToken(); clearToken(); return had ? '🚪 **Logged out.** HuggingFace token removed from localStorage.' : '👋 No token was stored. Nothing to clear.'; }, hf_token_status: async function () { var t = getToken(); if (!t) return '🔑 **No token stored.** Use `hf_login_ui` (secure popup) or `hf_set_token` to log in.'; try { var info = await hfFetch('/api/whoami-v2'); return ['🔑 **Token Status:** Active (' + maskToken(t) + ')', '👤 **Authenticated as:** ' + (info.name || info.user || 'unknown'), '📧 **Email:** ' + (info.email || 'N/A'), '💳 **Can Pay:** ' + (info.canPay ? 'Yes' : 'No'), '', '💡 Use `hf_logout` to clear your token.'].join('\n'); } catch (e) { return '⚠️ **Token stored** (' + maskToken(t) + ') but verification failed: ' + e.message; } }, hf_whoami: async function () { try { var info = await hfFetch('/api/whoami-v2'); var lines = ['**👤 HuggingFace User Info**', '**ID:** ' + (info.id || info.name || 'N/A'), '**Name:** ' + (info.name || info.user || 'N/A'), '**Email:** ' + (info.email || 'N/A'), '**Type:** ' + (info.type || info.org || 'user'), '**Can Pay:** ' + (info.canPay ? '✅ Yes' : '❌ No'), '**Plan:** ' + (info.plan ? (info.plan.name || info.plan) : 'Free/Unknown')]; if (info.orgs && info.orgs.length) lines.push('**Organizations:** ' + info.orgs.map(function (o) { return o.name; }).join(', ')); if (info.usage && info.usage.inference) lines.push('**Inference Usage:** ' + JSON.stringify(info.usage.inference)); return lines.join('\n'); } catch (e) { return '❌ Failed to get user info: ' + e.message; } }, hf_create_space: async function (a) { var me = await hfFetch('/api/whoami-v2'); var ns = a.org || me.name; var payload = { name: a.name, type: 'space', sdk: a.sdk || 'gradio', private: !!a.private }; if (a.title) payload.title = a.title; if (a.license) payload.license = a.license; if (a.hardware) payload.hardware = a.hardware; if (a.sleep_time) payload.sleep_time = a.sleep_time; var secrets = parseJsonArg(a.secrets); var variables = parseJsonArg(a.variables); if (Object.keys(secrets).length) payload.secrets = secrets; if (Object.keys(variables).length) payload.variables = variables; await hfFetch('/api/repos/create', { method: 'POST', body: JSON.stringify(payload) }); var repoId = ns + '/' + a.name; var out = ['✅ **Space created successfully!**', '', '**Repo:** ' + repoId, '**SDK:** ' + (a.sdk || 'gradio'), '**Visibility:** ' + (a.private ? '🔒 Private' : '🌍 Public'), '**URL:** https://huggingface.co/spaces/' + repoId, '**App:** ' + repoAppUrl(repoId)]; if (a.hardware) out.push('⚡ Hardware requested: ' + a.hardware); if (a.sleep_time) out.push('💤 Sleep timeout: ' + a.sleep_time + 's'); out.push('', '💡 **Next steps:** Upload files using `hf_upload_file` or `hf_upload_app`'); return out.join('\n'); }, hf_duplicate_space: async function (a) { var me = await hfFetch('/api/whoami-v2'); var ns = a.org || me.name; var body = { from: a.from, name: a.name, namespace: ns, private: !!a.private }; if (a.hardware) body.hardware = a.hardware; if (a.sleep_time) body.sleep_time = a.sleep_time; var secrets = parseJsonArg(a.secrets); var variables = parseJsonArg(a.variables); if (Object.keys(secrets).length) body.secrets = secrets; if (Object.keys(variables).length) body.variables = variables; await hfFetch('/api/spaces/duplicate', { method: 'POST', body: JSON.stringify(body) }); var repoId = ns + '/' + a.name; var out = ['✅ **Space duplicated!**', '', '**From:** ' + a.from, '**New Repo:** ' + repoId, '**URL:** https://huggingface.co/spaces/' + repoId, '**App:** ' + repoAppUrl(repoId)]; if (a.hardware) out.push('**Hardware:** ' + a.hardware); if (a.sleep_time) out.push('**Sleep:** ' + a.sleep_time + 's'); return out.join('\n'); }, hf_delete_space: async function (a) { var p = parseRepoId(a.repo_id); try { var rt = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/runtime'); if (rt.stage === 'PAUSED' || rt.stage === 'SLEEPING') { await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/restart', { method: 'POST' }); await new Promise(function (r) { setTimeout(r, 2000); }); } } catch (e) { /* best-effort */ } try { await hfFetch('/api/spaces/' + p.namespace + '/' + p.name, { method: 'DELETE' }); return '✅ **Space deleted:** ' + a.repo_id; } catch (e) { try { await hfFetch('/api/repos/' + p.namespace + '/' + p.name, { method: 'DELETE' }); return '✅ **Space deleted (via repos endpoint):** ' + a.repo_id; } catch (e2) { throw new Error('Could not delete space: ' + e.message + '. Try restarting it first with hf_restart_space.'); } } }, hf_upload_file: async function (a) { var p = parseRepoId(a.repo_id); await commitFiles('space', p.namespace, p.name, [{ path: a.path, content: a.content }], null, a.summary || 'Update via HuggingBot', a.description || ''); return ['✅ **File uploaded:** `' + a.path + '` → `' + a.repo_id + '`', '📝 ' + (a.summary || 'Update via HuggingBot'), '', '💡 The Space will now rebuild automatically. Check status with `hf_space_status`.'].join('\n'); }, hf_upload_app: async function (a) { var p = parseRepoId(a.repo_id); var files = [{ path: 'app.py', content: a.app_code }]; if (a.requirements) files.push({ path: 'requirements.txt', content: a.requirements }); if (a.readme) files.push({ path: 'README.md', content: a.readme }); if (a.extra_files) { var extras = parseJsonArg(a.extra_files); for (var k in extras) if (Object.prototype.hasOwnProperty.call(extras, k)) files.push({ path: k, content: String(extras[k]) }); } await commitFiles('space', p.namespace, p.name, files, null, a.summary || 'Deploy app via HuggingBot', 'Uploaded app.py' + (a.requirements ? ' + requirements.txt' : '') + (a.readme ? ' + README.md' : '')); return ['✅ **App deployed to:** `' + a.repo_id + '`', '📦 **Files uploaded:** ' + files.map(function (f) { return '`' + f.path + '`'; }).join(', '), '', '🌐 **Space:** https://huggingface.co/spaces/' + a.repo_id, '🚀 **App URL:** ' + repoAppUrl(a.repo_id), '', '💡 The Space is now building. Use `hf_space_status ' + a.repo_id + '` to check progress.'].join('\n'); }, hf_delete_file: async function (a) { var p = parseRepoId(a.repo_id); await commitFiles('space', p.namespace, p.name, null, [{ path: a.path }], a.summary || ('Delete ' + a.path), ''); return '✅ **File deleted:** `' + a.path + '` from `' + a.repo_id + '`'; }, hf_set_secret: async function (a) { var p = parseRepoId(a.repo_id); try { await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/secrets', { method: 'PUT', body: JSON.stringify({ key: a.key, value: a.value }) }); } catch (e) { if (String(e.message).indexOf('404') > -1 || String(e.message).indexOf('405') > -1) { await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/secrets', { method: 'POST', body: JSON.stringify({ key: a.key, value: a.value }) }); } else { throw e; } } return '🔐 **Secret set:** `' + a.key + '` in `' + a.repo_id + '`\n⚠️ The Space will restart.'; }, hf_delete_secret: async function (a) { var p = parseRepoId(a.repo_id); await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/secrets', { method: 'DELETE', body: JSON.stringify({ key: a.key }) }); return '🗑️ **Secret deleted:** `' + a.key + '` from `' + a.repo_id + '`'; }, hf_set_hardware: async function (a) { var p = parseRepoId(a.repo_id); var settings = { flavor: a.hardware }; if (a.sleep_time) settings.gcTimeout = a.sleep_time; await updateSpaceSettings(p.namespace, p.name, settings); var out = ['⚡ **Hardware change requested for:** `' + a.repo_id + '`', '**New hardware:** ' + a.hardware]; if (a.sleep_time) out.push('**Sleep timeout:** ' + a.sleep_time + 's'); out.push('', '⏳ The Space is restarting. Check status with `hf_space_status`.'); return out.join('\n'); }, hf_set_visibility: async function (a) { var p = parseRepoId(a.repo_id); await updateSpaceSettings(p.namespace, p.name, { private: a.visibility === 'private' }); return (a.visibility === 'private' ? '🔒' : '🌍') + ' **Visibility changed to ' + a.visibility + ':** `' + a.repo_id + '`'; }, hf_set_sleep: async function (a) { var p = parseRepoId(a.repo_id); await updateSpaceSettings(p.namespace, p.name, { gcTimeout: a.sleep_time }); var status = a.sleep_time === 0 ? '❌ Disabled (never sleeps)' : '💤 ' + a.sleep_time + 's (' + Math.round(a.sleep_time / 60) + ' min)'; return '✅ **Sleep configured:** `' + a.repo_id + '` → ' + status; }, hf_pause_space: async function (a) { var p = parseRepoId(a.repo_id); await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/pause', { method: 'POST' }); return '⏸️ **Space paused:** `' + a.repo_id + '`\n💡 Restart with `hf_restart_space ' + a.repo_id + '`'; }, hf_restart_space: async function (a) { var p = parseRepoId(a.repo_id); await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/restart', { method: 'POST' }); return '▶️ **Space restarting:** `' + a.repo_id + '`'; }, hf_space_status: async function (a) { var p = parseRepoId(a.repo_id); var runtime = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/runtime'); var repoInfo = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name); var emoji = { 'NO_APP_FILE': '📄', 'BUILDING': '🔨', 'RUNNING': '✅', 'RUNNING_BUILDING': '🔄', 'PAUSED': '⏸️', 'SLEEPING': '💤', 'STOPPED': '⛔', 'ERROR': '❌' }; var stage = runtime.stage || 'UNKNOWN'; var out = ['**📊 Space Status:** `' + a.repo_id + '`', '', '**Stage:** ' + (emoji[stage] || '❓') + ' ' + stage, '**SDK:** ' + (repoInfo.sdk || 'N/A'), '**Hardware:** ' + extractHardwareName(runtime.hardware), '**Requested HW:** ' + extractHardwareName(runtime.requestedHardware), '**Visibility:** ' + (repoInfo.private ? '🔒 Private' : '🌍 Public'), '**Likes:** ⭐ ' + (repoInfo.likes || 0), '**URL:** https://huggingface.co/spaces/' + a.repo_id, '**App:** ' + repoAppUrl(a.repo_id)]; return out.join('\n'); }, hf_space_logs: async function (a) { var p = parseRepoId(a.repo_id); var endpoints = [ '/api/spaces/' + p.namespace + '/' + p.name + '/runtime/logs', '/api/spaces/' + p.namespace + '/' + p.name + '/logs', '/api/spaces/' + p.namespace + '/' + p.name + '/runtime' ]; var lastError = null; for (var i = 0; i < endpoints.length; i++) { try { var result = await hfFetch(endpoints[i]); if (result && typeof result === 'object') { var logsStr = result.logs || result.stdout || result.stderr || result.build || JSON.stringify(result, null, 2); var truncated = logsStr.length > 5000 ? logsStr.slice(-5000) + '\n... (truncated, full: ' + logsStr.length + ' chars)' : logsStr; return ['📋 **Build Logs for** `' + a.repo_id + '`', '```', truncated, '```'].join('\n'); } } catch (e) { lastError = e; } } try { var rt = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/runtime'); if (rt && rt.errorMessage) return '⚠️ **Space has an error:**\n```\n' + rt.errorMessage + '\n```'; return 'ℹ️ **Space is** ' + (rt.stage || 'unknown') + '. No logs available via REST API.\n\n💡 Logs can be viewed in the HF UI: https://huggingface.co/spaces/' + a.repo_id; } catch (e2) { throw lastError || e2; } }, hf_search_spaces: async function (a) { var limit = Math.min(Math.max(a.limit || 5, 1), 25); var results = await hfFetch('/api/spaces?search=' + encodeURIComponent(a.query) + '&limit=' + limit + '&sort=likes'); if (!results || !results.length) return '🔍 No Spaces found for "' + a.query + '"'; var lines = ['🔍 **Spaces matching "' + a.query + '":**', '']; results.forEach(function (s, i) { var id = s.id || ((s.namespace || '?') + '/' + (s.name || '?')); var sdk = s.sdk ? ' [' + s.sdk + ']' : ''; var likes = s.likes ? ' ⭐' + s.likes : ''; var desc = s.description ? '\n > ' + s.description.substring(0, 150) : ''; lines.push((i + 1) + '. **' + id + '**' + sdk + likes); if (desc) lines.push(desc); lines.push(' https://huggingface.co/spaces/' + id); lines.push(''); }); return lines.join('\n'); }, hf_list_my_spaces: async function () { var userInfo = await hfFetch('/api/whoami-v2'); var name = userInfo.name || userInfo.user; var results = await hfFetch('/api/spaces?author=' + encodeURIComponent(name) + '&limit=25&sort=lastModified'); if (!results || !results.length) return '📭 No Spaces found under **' + name + '**.\nCreate one with `hf_create_space`!'; var lines = ['📂 **Your Spaces (' + name + '):**', '']; results.forEach(function (s) { var id = s.id || (s.namespace + '/' + s.name); var sdk = s.sdk ? ' [' + s.sdk + ']' : ''; var vis = s.private ? '🔒' : '🌍'; var likes = s.likes ? ' ⭐' + s.likes : ''; var stage = (s.runtime && s.runtime.stage) || ''; lines.push(vis + ' **' + id + '**' + sdk + likes + (stage ? ' — ' + stage : '')); }); lines.push('', '💡 Use `hf_space_status ` for details on any Space.'); return lines.join('\n'); }, hf_search_models: async function (a) { var limit = Math.min(Math.max(a.limit || 5, 1), 25); var results = await hfFetch('/api/models?search=' + encodeURIComponent(a.query) + '&limit=' + limit + '&sort=likes'); if (!results || !results.length) return '🔍 No models found for "' + a.query + '"'; var lines = ['🔍 **Models matching "' + a.query + '":**', '']; results.forEach(function (m, i) { var id = m.id || ((m.namespace || '?') + '/' + (m.name || '?')); var pipeline = m.pipeline_tag ? ' [' + m.pipeline_tag + ']' : ''; var likes = m.likes ? ' ⭐' + m.likes : ''; var downloads = m.downloads ? ' 📥' + (m.downloads / 1000).toFixed(0) + 'k' : ''; lines.push((i + 1) + '. **' + id + '**' + pipeline + likes + downloads); lines.push(' https://huggingface.co/' + id); }); return lines.join('\n'); }, hf_search_datasets: async function (a) { var limit = Math.min(Math.max(a.limit || 5, 1), 25); var results = await hfFetch('/api/datasets?search=' + encodeURIComponent(a.query) + '&limit=' + limit + '&sort=likes'); if (!results || !results.length) return '🔍 No datasets found for "' + a.query + '"'; var lines = ['🔍 **Datasets matching "' + a.query + '":**', '']; results.forEach(function (d, i) { var id = d.id || ((d.namespace || '?') + '/' + (d.name || '?')); var likes = d.likes ? ' ⭐' + d.likes : ''; var downloads = d.downloads ? ' 📥' + (d.downloads / 1000).toFixed(0) + 'k' : ''; lines.push((i + 1) + '. **' + id + '**' + likes + downloads); lines.push(' https://huggingface.co/datasets/' + id); }); return lines.join('\n'); }, hf_call_space: async function (a) { var p = parseRepoId(a.repo_id); var inputs = parseJsonArg(a.data); if (!Array.isArray(inputs)) inputs = [inputs]; var apiName = String(a.api_name || '/predict').replace(/^\//, ''); var host = repoAppUrl(a.repo_id); // Modern Gradio: POST /api/{name} with {data:[...]}; older: /api/predict var tryEndpoints = ['/api/' + apiName, '/api/predict']; var lastError = null; for (var i = 0; i < tryEndpoints.length; i++) { try { var res = await fetch(host + tryEndpoints[i], { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: inputs }) }); if (res.ok) { var out = await res.json(); if (out && out.data !== undefined) return '✅ **Result from `' + a.repo_id + '`' + (tryEndpoints[i] === '/api/predict' ? ' (legacy /predict)' : '') + ':**\n\n' + JSON.stringify(out.data, null, 2); return '✅ **Raw response from `' + a.repo_id + '`:**\n\n' + JSON.stringify(out, null, 2); } lastError = new Error('HTTP ' + res.status + ' ' + res.statusText); } catch (e) { lastError = e; } } throw new Error('Could not call Gradio API on ' + host + ': ' + (lastError ? lastError.message : 'unknown error') + '. The Space may be paused/sleeping or api_name may differ.'); }, hf_inference: async function (a) { var token = getToken(); if (!token) throw new Error('No HuggingFace token set. hf_inference uses your token for higher rate limits.'); var url = INFER_API_BASE + (a.task ? '/pipeline/' + encodeURIComponent(a.task) + '/' : '/models/') + a.model; var payload = { inputs: a.inputs }; if (a.parameters) payload.parameters = parseJsonArg(a.parameters); var res = await fetch(url, { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) { var em = ''; try { var ej = await res.json(); em = ej.error || JSON.stringify(ej); } catch (e) { em = res.statusText; } throw new Error('Inference API ' + res.status + ': ' + em); } var ct = res.headers.get('content-type') || ''; if (ct.indexOf('image/') > -1 || ct.indexOf('audio/') > -1) { var buf = await res.arrayBuffer(); var bytes = new Uint8Array(buf); var bin = ''; for (var i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); var b64 = btoa(bin); var mime = ct.split(';')[0]; if (mime.indexOf('image') > -1) return '✅ **Generated ' + mime + '** (' + (bytes.length / 1024).toFixed(1) + ' KB)\n\n![inference-result](data:' + mime + ';base64,' + b64 + ')'; return '✅ **Generated ' + mime + '** (' + (bytes.length / 1024).toFixed(1) + ' KB)\n\n' + (mime.indexOf('audio') > -1 ? '[🔊 Audio result](data:' + mime + ';base64,' + b64 + ')' : 'Binary result in base64: `' + b64.substring(0, 120) + '…`'); } var data = await res.json(); if (Array.isArray(data)) { var parts = ['✅ **Inference result from `' + a.model + '`:**', '']; data.forEach(function (item) { if (item && typeof item === 'object') { var g = item.generated_text || item.label || item.answer || item.summary_text || item.translation_text || JSON.stringify(item); parts.push(g); } else { parts.push(String(item)); } }); return parts.join('\n'); } return '✅ **Inference result from `' + a.model + '`:**\n\n' + (typeof data === 'string' ? data : JSON.stringify(data, null, 2)); }, hf_repo_info: async function (a) { var type = a.type || 'space'; var info = await hfFetch('/api/' + repoTypePath(type) + '/' + a.repo_id); var lines = ['**📦 Repo Info:** `' + a.repo_id + '` (' + type + ')', '']; var fields = { id: 'ID', name: 'Name', author: 'Author', pipeline_tag: 'Pipeline', library_name: 'Library', sdk: 'SDK', sdk_version: 'SDK Version', likes: '⭐ Likes', downloads: '📥 Downloads', license: 'License', private: 'Private', lastModified: 'Last Modified', createdAt: 'Created', tags: 'Tags', description: 'Description', cardData: 'Card Data', sha: 'Commit SHA' }; for (var k in fields) { if (info[k] !== undefined && info[k] !== null && info[k] !== '') { var v = info[k]; if (typeof v === 'object') v = JSON.stringify(v); if (k === 'private') v = v ? '🔒 Yes' : '🌍 No'; if (String(v).length > 200) v = String(v).substring(0, 200) + '…'; lines.push('**' + fields[k] + ':** ' + v); } } lines.push('', '🔗 https://huggingface.co/' + type + 's/' + a.repo_id); return lines.join('\n'); }, hf_list_files: async function (a) { var type = a.type || 'space'; var path = a.path || ''; var rev = a.revision || 'main'; var url = '/api/' + repoTypePath(type) + '/' + a.repo_id + '/tree/' + encodeURIComponent(rev) + (path ? '/' + path.split('/').map(encodeURIComponent).join('/') : '') + '?recursive=false&expand=false'; var files = await hfFetch(url); if (!files || !files.length) return '📁 No files found at `' + (path || '/') + '` in `' + a.repo_id + '`'; var lines = ['📂 **Files in `' + a.repo_id + '`' + (path ? '/' + path : '') + ' (' + rev + '):**', '']; files.forEach(function (f) { var icon = f.type === 'directory' ? '📁' : '📄'; var size = f.size !== undefined && f.size !== null ? ' — ' + (f.size / 1024).toFixed(1) + ' KB' : ''; lines.push(icon + ' `' + (f.path || f.name) + '`' + (f.type === 'directory' ? '/' : '') + size); }); return lines.join('\n'); }, hf_read_file: async function (a) { var type = a.type || 'space'; var rev = a.revision || 'main'; var rawPath = a.path.split('/').map(encodeURIComponent).join('/'); var text = await hfFetchRaw('/' + repoTypePath(type) + '/' + a.repo_id + '/raw/' + encodeURIComponent(rev) + '/' + rawPath); var truncated = text.length > 6000 ? text.substring(0, 6000) + '\n\n… (truncated, full: ' + text.length + ' chars)' : text; return '📄 **`' + a.path + '`** from `' + a.repo_id + '` (' + rev + '):\n\n```\n' + truncated + '\n```'; }, hf_file_exists: async function (a) { var type = a.type || 'space'; var rev = a.revision || 'main'; var rawPath = a.path.split('/').map(encodeURIComponent).join('/'); var head = await hfFetchHead('/' + repoTypePath(type) + '/' + a.repo_id + '/raw/' + encodeURIComponent(rev) + '/' + rawPath); return head.ok ? '✅ **File exists:** `' + a.path + '` in `' + a.repo_id + '` (HTTP ' + head.status + ')' : '❌ **File not found:** `' + a.path + '` in `' + a.repo_id + '` (HTTP ' + head.status + ')'; }, hf_repo_exists: async function (a) { var type = a.type || 'space'; try { var info = await hfFetch('/api/' + repoTypePath(type) + '/' + a.repo_id); return '✅ **Repo exists:** `' + a.repo_id + '` (' + type + (info.private ? ', 🔒 private' : ', 🌍 public') + ')'; } catch (e) { return '❌ **Repo not found or inaccessible:** `' + a.repo_id + '` (' + e.message + ')'; } }, hf_list_commits: async function (a) { var type = a.type || 'space'; var rev = a.revision || 'main'; var limit = Math.min(Math.max(a.limit || 10, 1), 50); var commits = await hfFetch('/api/' + repoTypePath(type) + '/' + a.repo_id + '/commits/' + encodeURIComponent(rev) + '?limit=' + limit); if (!commits || !commits.length) return '📭 No commits found for `' + a.repo_id + '`'; var lines = ['🗂️ **Commit history for `' + a.repo_id + '` (' + rev + '):**', '']; commits.forEach(function (c, i) { var date = c.createdAt ? new Date(c.createdAt).toLocaleString() : '?'; var short = (c.id || '').substring(0, 7); lines.push((i + 1) + '. **' + short + '** — ' + c.title + ''); lines.push(' 📅 ' + date + (c.author ? ' | 👤 ' + c.author.name : '')); }); return lines.join('\n'); }, hf_move_file: async function (a) { var p = parseRepoId(a.repo_id); var type = a.type || 'space'; var rev = 'main'; // Move = read raw source, write to dest, delete source (works for all repo types via commit API) var src = a.source_path.split('/').map(encodeURIComponent).join('/'); var content = await hfFetchRaw('/' + repoTypePath(type) + '/' + a.repo_id + '/raw/' + rev + '/' + src); await commitFiles(type, p.namespace, p.name, [{ path: a.dest_path, content: content }], [{ path: a.source_path }], a.summary || 'Move/Rename file', 'Moved ' + a.source_path + ' → ' + a.dest_path); return '📦 **File moved:** `' + a.source_path + '` → `' + a.dest_path + '` in `' + a.repo_id + '`'; } }; // ─── Register module ─────────────────────────────────────────────────── MS.register('huggingface', { name: 'HuggingFace Hub', description: 'Full HuggingFace Hub control: auth, Spaces CRUD, file ops, secrets/hardware/sleep config, hub search, model inference, Gradio API calls, and repo browsing (expanded).', tools: TOOLS.map(function (t) { return { type: 'function', function: t }; }), handleToolCall: async function (tn, args) { var fn = REGISTRY[tn]; if (!fn) return null; try { var out = await fn(args || {}); return typeof out === 'string' ? out : JSON.stringify(out, null, 2); } catch (e) { return '❌ ' + (e && e.message ? e.message : String(e)); } } }); })(ModuleSystem);