Menu
Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f
AkurAI-Framework / site/frontend/ai/components/input/preferences_panel.js
Latest change 63ae119f1bdd5ddffd3f9a2b3b3216aa9d21449b - feat(ai): Input component batch — option_list, parameter_slider, preferences_panel, question_flow by Ólafur Búi Ólafsson
/**
* Preferences Panel — tool-UI input component.
* Payload: { groups: [{ label, fields: [{ key, type, label?, value?, options? }] }] }
* field.type: 'toggle' | 'select' | 'text'
* select fields carry an options array: [{ id, label }] or string[]
* Receipt: { component: 'preferences_panel', values: { [key]: value } }
*/
function emit(el, detail) {
el.dispatchEvent(new CustomEvent('toolui:input', { bubbles: true, detail }));
}
function buildToggle(field, state) {
const lbl = document.createElement('label');
lbl.className = 'switch';
lbl.style.cssText =
'justify-content:space-between;padding:10px 12px;' +
'border:1px solid var(--border);border-radius:10px;';
const text = document.createElement('span');
text.className = 'switch-text';
text.textContent = field.label || field.key;
lbl.appendChild(text);
const inp = document.createElement('input');
inp.type = 'checkbox';
inp.className = 'switch-input';
inp.checked = Boolean(field.value);
inp.addEventListener('change', () => { state[field.key] = inp.checked; });
lbl.appendChild(inp);
return lbl;
}
function buildSelect(field, state) {
const wrapper = document.createElement('div');
wrapper.className = 'field';
const lbl = document.createElement('label');
lbl.style.cssText = 'color:var(--muted);font-size:13px;';
lbl.textContent = field.label || field.key;
wrapper.appendChild(lbl);
const sel = document.createElement('select');
const opts = field.options || [];
opts.forEach(opt => {
const o = document.createElement('option');
const id = (opt && typeof opt === 'object') ? String(opt.id ?? opt) : String(opt);
const text = (opt && typeof opt === 'object') ? String(opt.label ?? opt.id ?? opt) : String(opt);
o.value = id;
o.textContent = text;
if (id === String(field.value)) o.selected = true;
sel.appendChild(o);
});
sel.addEventListener('change', () => { state[field.key] = sel.value; });
wrapper.appendChild(sel);
return wrapper;
}
function buildText(field, state) {
const wrapper = document.createElement('div');
wrapper.className = 'field';
const lbl = document.createElement('label');
lbl.style.cssText = 'color:var(--muted);font-size:13px;';
lbl.textContent = field.label || field.key;
wrapper.appendChild(lbl);
const inp = document.createElement('input');
inp.type = 'text';
inp.value = field.value != null ? String(field.value) : '';
inp.placeholder = field.placeholder || '';
inp.addEventListener('input', () => { state[field.key] = inp.value; });
wrapper.appendChild(inp);
return wrapper;
}
export const def = {
name: 'preferences_panel',
category: 'Input',
title: 'Preferences Panel',
match(p) {
return p != null && Array.isArray(p.groups) && p.groups.length > 0;
},
render(p, ctx) {
const { groups = [] } = p;
const state = {};
// Seed state from initial field values.
groups.forEach(g => {
(g.fields || []).forEach(f => {
state[f.key] = f.value != null ? f.value : (f.type === 'toggle' ? false : '');
});
});
const root = document.createElement('div');
root.className = 'card';
root.style.cssText = 'display:grid;gap:20px;max-width:480px;';
groups.forEach(group => {
const section = document.createElement('div');
section.style.cssText = 'display:grid;gap:10px;';
if (group.label) {
const heading = document.createElement('div');
heading.style.cssText =
'font-size:12px;font-weight:700;letter-spacing:.08em;' +
'text-transform:uppercase;color:var(--accent);' +
'padding-bottom:8px;border-bottom:1px solid var(--border);';
heading.textContent = group.label;
section.appendChild(heading);
}
(group.fields || []).forEach(field => {
let el;
if (field.type === 'toggle') {
el = buildToggle(field, state);
} else if (field.type === 'select') {
el = buildSelect(field, state);
} else {
el = buildText(field, state);
}
section.appendChild(el);
});
root.appendChild(section);
});
const btn = document.createElement('button');
btn.className = 'btn btn-primary btn-sm';
btn.textContent = 'Save preferences';
btn.addEventListener('click', () => {
const receipt = { component: 'preferences_panel', values: Object.assign({}, state) };
if (ctx && typeof ctx.sendReceipt === 'function') ctx.sendReceipt(receipt);
emit(root, receipt);
btn.disabled = true;
btn.textContent = 'Saved ✓';
});
root.appendChild(btn);
return root;
},
example: {
groups: [
{
label: 'Appearance',
fields: [
{ key: 'dark_mode', type: 'toggle', label: 'Dark mode', value: true },
{ key: 'compact', type: 'toggle', label: 'Compact layout', value: false }
]
},
{
label: 'Locale',
fields: [
{
key: 'language', type: 'select', label: 'Language', value: 'en',
options: [{ id: 'en', label: 'English' }, { id: 'is', label: 'Íslenska' }]
},
{ key: 'display_name', type: 'text', label: 'Display name', value: '' }
]
}
]
}
};