huggingbot-modules / modules_repos.js
broadfield's picture
Fix name collision: rename fetch helper to req, export object to moduleApi (v1.0.1)
c763302 verified
Raw History Blame Contribute Delete
10.6 kB
// HuggingBot Module: Repo Creation & Multi-Type Ops v1.0.1
// ============================================================================
// Companion to the HuggingFace Hub module (modules_huggingface.js).
// Adds what the Hub module lacks:
// * hf_create_repo β€” create Space, Dataset, or Model repos
// * hf_upload_repo_file β€” upload a file to ANY repo type (space|model|dataset)
// * hf_delete_repo β€” delete any repo type
// * hf_list_my_datasets β€” list datasets you own
// * hf_list_my_models -- list models you own
// Tool names are unique (no clash with the Hub module), so both modules
// can be registered side by side in the ModuleSystem.
// Token is shared with the Hub module via localStorage key 'chatseed_hf_token'.
// v1.0.1: renamed the fetch helper from `api` to `req` to avoid colliding
// with the exported module object `api`.
// ============================================================================
(function (MS) {
'use strict';
var HF = 'https://huggingface.co';
var TK = 'chatseed_hf_token';
function tok() { try { return localStorage.getItem(TK) || ''; } catch (e) { return ''; } }
function hdr() {
var t = tok();
if (!t) throw new Error('No HuggingFace token set. Use hf_set_token or hf_login_ui first.');
return { 'Authorization': 'Bearer ' + t, 'Content-Type': 'application/json' };
}
async function req(path, opts) {
opts = opts || {};
var headers = Object.assign({}, hdr(), opts.headers || {});
var res = await fetch(HF + path, Object.assign({}, opts, { headers: headers }));
if (!res.ok) {
var m = '';
try { var j = await res.json(); m = j.error || JSON.stringify(j); }
catch (e) { try { m = await res.text(); } catch (e2) { m = res.statusText; } }
throw new Error('HF API ' + res.status + ': ' + m);
}
if (res.status === 204) return null;
return (res.headers.get('content-type') || '').indexOf('application/json') > -1 ? res.json() : res.text();
}
function pathFor(t) { return t === 'model' ? 'models' : t === 'dataset' ? 'datasets' : 'spaces'; }
function parseRepo(id) {
var p = String(id || '').split('/');
if (p.length !== 2) throw new Error('Invalid repo_id. Use format "namespace/repo-name"');
return { ns: p[0], name: p[1] };
}
function parseJson(v) {
if (!v) return {};
if (typeof v === 'object') return v;
try { return JSON.parse(v); } catch (e) { throw new Error('Invalid JSON argument: ' + v); }
}
async function commit(type, repo, files, deleted, summary) {
var p = parseRepo(repo);
var body = { summary: summary || 'Update via HuggingBot', description: '',
files: (files || []).map(function (f) { return { path: f.path, content: f.content, encoding: f.encoding || 'utf-8' }; }) };
if (deleted && deleted.length) body.deletedEntries = deleted;
return req('/api/' + pathFor(type) + '/' + p.ns + '/' + p.name + '/commit/main', { method: 'POST', body: JSON.stringify(body) });
}
async function createRepo(a) {
var type = a.type || 'space';
if (['space', 'model', 'dataset'].indexOf(type) < 0) throw new Error('Invalid type "' + type + '". Use space|model|dataset.');
var me = await req('/api/whoami-v2');
var ns = a.org || me.name;
var payload = { name: a.name, type: type, private: !!a.private };
if (type === 'space') payload.sdk = a.sdk || 'gradio';
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 sec = parseJson(a.secrets), vars = parseJson(a.variables);
if (Object.keys(sec).length) payload.secrets = sec;
if (Object.keys(vars).length) payload.variables = vars;
await req('/api/repos/create', { method: 'POST', body: JSON.stringify(payload) });
var repoId = ns + '/' + a.name;
var base = pathFor(type);
var label = type.charAt(0).toUpperCase() + type.slice(1);
var out = ['βœ… **' + label + ' created successfully!**', '', '**Repo:** ' + repoId, '**Type:** ' + type,
'**Visibility:** ' + (a.private ? 'πŸ”’ Private' : '🌍 Public'), '**URL:** https://huggingface.co/' + base + '/' + repoId];
if (type === 'space') { out.push('**SDK:** ' + (a.sdk || 'gradio')); out.push('**App:** https://' + repoId.replace(/\//g, '-') + '.hf.space'); }
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 with `hf_upload_repo_file` (pass `type: "' + type + '"`) or `hf_upload_file` for spaces.');
return out.join('\n');
}
async function deleteRepo(a) {
var type = a.type || 'space';
var p = parseRepo(a.repo_id);
var base = pathFor(type);
if (type === 'space') {
try {
var rt = await req('/api/spaces/' + p.ns + '/' + p.name + '/runtime');
if (rt.stage === 'PAUSED' || rt.stage === 'SLEEPING') {
await req('/api/spaces/' + p.ns + '/' + p.name + '/restart', { method: 'POST' });
await new Promise(function (r) { setTimeout(r, 2000); });
}
} catch (e) { /* best-effort */ }
}
try {
await req('/api/' + base + '/' + p.ns + '/' + p.name, { method: 'DELETE' });
return 'βœ… **' + type + ' deleted:** ' + a.repo_id;
} catch (e) {
try {
await req('/api/repos/' + p.ns + '/' + p.name + '?type=' + type, { method: 'DELETE' });
return 'βœ… **' + type + ' deleted (via repos endpoint):** ' + a.repo_id;
} catch (e2) {
throw new Error('Could not delete ' + type + ': ' + e.message + (type === 'space' ? '. Try restarting it first with hf_restart_space.' : ''));
}
}
}
async function uploadRepoFile(a) {
if (!a.path || a.content === undefined) throw new Error('Both `path` and `content` are required.');
var type = a.type || 'space';
await commit(type, a.repo_id, [{ path: a.path, content: a.content }], null, a.summary);
return ['βœ… **File uploaded:** `' + a.path + '` β†’ `' + a.repo_id + '` (' + type + ')',
'πŸ“ ' + (a.summary || 'Update via HuggingBot'), '',
type === 'space' ? 'πŸ’‘ The Space will now rebuild. Use `hf_space_status` to check.' : 'πŸ’‘ The ' + type + ' repo was updated on `main`.'
].join('\n');
}
async function listMine(kind, a) {
var me = await req('/api/whoami-v2');
var name = me.name || me.user;
var limit = Math.min(Math.max((a && a.limit) || 10, 1), 100);
var base = kind === 'dataset' ? 'datasets' : kind === 'model' ? 'models' : 'spaces';
var res = await req('/api/' + base + '?author=' + encodeURIComponent(name) + '&limit=' + limit + '&sort=lastModified');
var label = kind.charAt(0).toUpperCase() + kind.slice(1);
if (!res || !res.length) return 'πŸ“­ No ' + kind + 's found under **' + name + '**.\nCreate one with `hf_create_repo` (type: "' + kind + '")!';
var lines = ['πŸ“‚ **Your ' + label + 's (' + name + '):**', ''];
res.forEach(function (r) {
var id = r.id || (r.namespace + '/' + r.name);
var vis = r.private ? 'πŸ”’' : '🌍';
var likes = r.likes ? ' ⭐' + r.likes : '';
var dl = r.downloads ? ' πŸ“₯' + (r.downloads / 1000).toFixed(0) + 'k' : '';
lines.push(vis + ' **' + id + '**' + likes + dl);
});
lines.push('', 'πŸ’‘ Use `hf_repo_info` or `hf_list_files` on any of them.');
return lines.join('\n');
}
var TOOLS = [
{ name: 'hf_create_repo', description: 'Create a new HuggingFace repo: Space, Dataset, or Model. Pass `type` to choose β€” this is how you create datasets and models.',
parameters: { type: 'object', properties: { name: { type: 'string', description: 'Repo name (e.g. "my-dataset")' },
type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type to create' },
private: { type: 'boolean', default: false }, org: { type: 'string' }, title: { type: 'string' }, license: { type: 'string' },
sdk: { type: 'string', enum: ['gradio', 'streamlit', 'docker', 'static'], description: 'SDK (spaces only)' },
hardware: { type: 'string', description: 'Hardware (spaces only)' }, sleep_time: { type: 'number' },
secrets: { type: 'string', description: 'JSON {"key":"value"} (spaces only)' }, variables: { type: 'string', description: 'JSON (spaces only)' } },
required: ['name'] } },
{ name: 'hf_upload_repo_file', description: 'Upload a single file to ANY repo type: Space, Dataset, or Model (e.g. README.md, data files, model config).',
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' },
path: { type: 'string', description: 'Path in the repo (e.g. "ideas/001-x.md")' }, content: { type: 'string' },
summary: { type: 'string', description: 'Commit message' } }, required: ['repo_id', 'path', 'content'] } },
{ name: 'hf_delete_repo', description: 'Delete any HuggingFace repo: Space, Dataset, or Model.',
parameters: { type: 'object', properties: { repo_id: { type: 'string' },
type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space' } }, required: ['repo_id'] } },
{ name: 'hf_list_my_datasets', description: 'List all datasets owned by your account.',
parameters: { type: 'object', properties: { limit: { type: 'number', default: 10 } }, required: [] } },
{ name: 'hf_list_my_models', description: 'List all models owned by your account.',
parameters: { type: 'object', properties: { limit: { type: 'number', default: 10 } }, required: [] } }
];
var REGISTRY = {
hf_create_repo: createRepo,
hf_upload_repo_file: uploadRepoFile,
hf_delete_repo: deleteRepo,
hf_list_my_datasets: function (a) { return listMine('datasets', a); },
hf_list_my_models: function (a) { return listMine('models', a); }
};
var moduleApi = {
name: 'Repo Creation & Multi-Type Ops',
description: 'Create HF datasets/models/spaces and upload files to any repo type.',
tools: TOOLS.map(function (t) { return { type: 'function', function: t }; }),
handleToolCall: async function (tn, args) {
if (REGISTRY[tn]) return REGISTRY[tn](args || {});
return null;
}
};
MS.register('huggingface-repos', moduleApi);
})(ModuleSystem);