Menu
Public repository / d2de65b4934c81cfa0049cf6b956720e001fe1ec
AkurAI-Framework / site/frontend/ai/components-manifest.js
Latest change d2de65b4934c81cfa0049cf6b956720e001fe1ec - feat(ai): add AI components gallery page (stub manifest + gallery.js + ai-gallery.html) by Ólafur Búi Ólafsson
// STUB — orchestrator overwrites with real component imports from
// site/frontend/ai/components/<group>/<name>.js
//
// Shape: each entry is a `def` object:
// export const def = {
// name: string, // kebab-case identifier, unique
// category: string, // one of: Progress | Input | Display | Artifacts | Confirmation | Media
// title: string, // human label shown in the gallery
// match(payload): boolean, // returns true if this component handles payload
// render(payload, ctx): HTMLElement, // returns a mounted DOM element
// example: object, // preset payload; passed to render() in the gallery
// };
//
// The three inline defs below are real enough to test the gallery build.
const progressBar = {
name: 'progress-bar',
category: 'Progress',
title: 'Progress Bar',
match(payload) {
return payload && typeof payload.percent === 'number';
},
render(payload) {
const pct = Math.min(100, Math.max(0, payload.percent ?? 0));
const wrap = document.createElement('div');
wrap.style.cssText = 'width:100%;';
const label = document.createElement('div');
label.style.cssText = 'display:flex;justify-content:space-between;font-size:13px;color:var(--muted);margin-bottom:6px;';
label.innerHTML = `<span>${payload.label ?? 'Progress'}</span><span>${pct}%</span>`;
const track = document.createElement('div');
track.style.cssText = 'height:8px;border-radius:999px;background:var(--panel-2);overflow:hidden;';
const fill = document.createElement('div');
fill.style.cssText = `height:100%;border-radius:999px;background:linear-gradient(90deg,var(--accent),var(--accent-2));width:${pct}%;transition:width .4s ease;`;
track.appendChild(fill);
wrap.append(label, track);
return wrap;
},
example: { label: 'Indexing documents', percent: 68 },
};
const optionList = {
name: 'option-list',
category: 'Input',
title: 'Option List',
match(payload) {
return payload && Array.isArray(payload.options);
},
render(payload, ctx) {
const wrap = document.createElement('div');
wrap.style.cssText = 'display:grid;gap:8px;';
(payload.options ?? []).forEach(opt => {
const btn = document.createElement('button');
btn.className = 'btn btn-secondary';
btn.style.cssText = 'text-align:left;justify-content:flex-start;';
btn.textContent = opt.label ?? opt;
btn.addEventListener('click', () => {
ctx?.onAction?.('select', { option: opt });
btn.style.background = 'var(--accent)';
btn.style.color = '#fff';
});
wrap.appendChild(btn);
});
return wrap;
},
example: {
prompt: 'Which framework feature interests you most?',
options: [
{ label: 'Native ESM — no bundler', value: 'esm' },
{ label: 'From-scratch SQL engine', value: 'sql' },
{ label: 'Built-in semantic search', value: 'search' },
],
},
};
const statDisplay = {
name: 'stats-display',
category: 'Display',
title: 'Stats Display',
match(payload) {
return payload && Array.isArray(payload.stats);
},
render(payload) {
const grid = document.createElement('div');
grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:12px;';
(payload.stats ?? []).forEach(s => {
const cell = document.createElement('div');
cell.className = 'stat';
cell.innerHTML = `<div class="stat-value">${s.value}</div><div class="stat-label">${s.label}</div>`;
grid.appendChild(cell);
});
return grid;
},
example: {
stats: [
{ label: 'Binary size', value: '2.1 MB' },
{ label: 'Dependencies', value: '0' },
{ label: 'Startup', value: '< 1 ms' },
{ label: 'Queries / s', value: '120 k' },
],
},
};
// STUB — orchestrator overwrites this array with imports of all real component modules.
export const components = [progressBar, optionList, statDisplay];