Spaces:
Running
Running
Download modules_huggingface.js from broadfield/huggingbot-modules: direct link, hf CLI and curl.
- Browser
- Download file 52.7 kB
-
https://huggingface.co/spaces/broadfield/huggingbot-modules/resolve/main/modules_huggingface.js
- Command line
-
hf download hf://spaces/broadfield/huggingbot-modules/modules_huggingface.js
-
curl -L -o modules_huggingface.js https://huggingface.co/spaces/broadfield/huggingbot-modules/resolve/main/modules_huggingface.js
52.7 kB
| // 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 = '<div style="display:flex;align-items:center;gap:8px;"><span style="font-size:1.4rem;">π€</span><span style="font-size:1.05rem;font-weight:600;color:#f3f4f6;">HuggingFace Login</span></div>' + | |
| '<button id="huggingbot-hf-close" style="background:none;border:none;color:#9ca3af;font-size:1.2rem;cursor:pointer;">β</button>'; | |
| var body = document.createElement('div'); | |
| body.innerHTML = | |
| '<p style="color:#d1d5db;font-size:.82rem;line-height:1.5;margin:0 0 14px;">Paste your HuggingFace access token.</p>' + | |
| '<input id="huggingbot-hf-token" type="password" placeholder="hf_..." style="width:100%;box-sizing:border-box;padding:10px 12px;background:#030712;border:1px solid #4b5563;border-radius:8px;color:#f3f4f6;font-size:.9rem;outline:none;"/>' + | |
| '<div style="margin-top:6px;display:flex;align-items:center;gap:6px;"><input id="huggingbot-hf-show" type="checkbox" style="accent-color:#059669;"><label for="huggingbot-hf-show" style="color:#6b7280;font-size:.75rem;cursor:pointer;">Show token</label></div>' + | |
| '<div id="huggingbot-hf-status" style="display:none;font-size:.8rem;padding:8px 12px;border-radius:8px;margin-top:10px;"></div>' + | |
| '<div style="display:flex;gap:10px;margin-top:14px;">' + | |
| '<button id="huggingbot-hf-submit" style="flex:1;padding:10px;background:#059669;color:#fff;border:none;border-radius:10px;font-weight:500;cursor:pointer;">π Set Token</button>' + | |
| '<button id="huggingbot-hf-cancel" style="padding:10px 16px;background:#374151;color:#d1d5db;border:none;border-radius:10px;cursor:pointer;">Cancel</button></div>' + | |
| '<p style="color:#6b7280;font-size:.7rem;margin:12px 0 0;text-align:center;">Get a token at <a href="https://huggingface.co/settings/tokens" target="_blank" style="color:#60a5fa;">huggingface.co/settings/tokens</a></p>'; | |
| 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 <code>hf_</code>.', '#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 <strong>' + (info.name || info.user || 'unknown') + '</strong> (' + 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 <id>` 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'; | |
| 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); |