This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/sw/surfaces/settings/personas.js
gobha 74f3cb84e9 Changeset 0.37.5 (#217)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-21 13:41:25 +00:00

155 lines
5.8 KiB
JavaScript

/**
* PersonasSection — user persona list + create/edit
*/
const { html } = window;
const { useState, useEffect, useCallback } = hooks;
function esc(s) { return s == null ? '' : String(s); }
export function PersonasSection() {
const [personas, setPersonas] = useState(null);
const [showForm, setShowForm] = useState(false);
const [editId, setEditId] = useState(null);
const [form, setForm] = useState({ name: '', description: '', system_prompt: '' });
const [saving, setSaving] = useState(false);
const load = useCallback(async () => {
try {
const resp = await sw.api.personas.list();
setPersonas(resp?.data || resp || []);
} catch (e) {
setPersonas([]);
}
}, []);
useEffect(() => { load(); }, [load]);
const openAdd = useCallback(() => {
setEditId(null);
setForm({ name: '', description: '', system_prompt: '' });
setShowForm(true);
}, []);
const openEdit = useCallback((p) => {
setEditId(p.id);
setForm({ name: p.name || '', description: p.description || '', system_prompt: p.system_prompt || '' });
setShowForm(true);
}, []);
const cancel = useCallback(() => {
setShowForm(false);
setEditId(null);
}, []);
const save = useCallback(async () => {
if (!form.name.trim()) {
sw.emit('toast', { message: 'Name is required', variant: 'error' });
return;
}
setSaving(true);
try {
if (editId) {
await sw.api.personas.update(editId, form);
sw.emit('toast', { message: 'Persona updated', variant: 'success' });
} else {
await sw.api.personas.create(form);
sw.emit('toast', { message: 'Persona created', variant: 'success' });
}
setShowForm(false);
setEditId(null);
load();
} catch (e) {
sw.emit('toast', { message: e.message, variant: 'error' });
} finally {
setSaving(false);
}
}, [form, editId, load]);
const del = useCallback(async (p) => {
const ok = await sw.confirm(`Delete persona "${p.name}"?`);
if (!ok) return;
try {
await sw.api.personas.del(p.id);
sw.emit('toast', { message: 'Persona deleted', variant: 'success' });
load();
} catch (e) {
sw.emit('toast', { message: e.message, variant: 'error' });
}
}, [load]);
const setField = useCallback((key, val) => {
setForm(f => ({ ...f, [key]: val }));
}, []);
if (personas === null) {
return html`<div class="settings-placeholder">Loading personas\u2026</div>`;
}
return html`
<div style="margin-bottom:12px;">
<button class="btn-md btn-primary" onClick=${openAdd}>+ New Persona</button>
</div>
${showForm && html`
<div class="settings-section" style="margin-bottom:16px;">
<h3>${editId ? 'Edit Persona' : 'New Persona'}</h3>
<div class="form-group">
<label>Name</label>
<input type="text" value=${form.name}
onInput=${e => setField('name', e.target.value)} />
</div>
<div class="form-group">
<label>Description</label>
<input type="text" value=${form.description}
onInput=${e => setField('description', e.target.value)} />
</div>
<div class="form-group">
<label>System Prompt</label>
<textarea rows="4" value=${form.system_prompt}
onInput=${e => setField('system_prompt', e.target.value)}
style="max-width:100%;" />
</div>
<div style="display:flex;gap:8px;margin-top:12px;">
<button class="btn-md btn-primary" disabled=${saving}
onClick=${save}>
${saving ? 'Saving\u2026' : editId ? 'Update' : 'Create'}
</button>
<button class="btn-md btn-secondary" onClick=${cancel}>Cancel</button>
</div>
</div>
`}
${!personas.length && !showForm && html`
<div class="empty-hint">No personas configured.</div>
`}
${personas.length > 0 && html`
<div>
${personas.map(p => html`
<div class="settings-section" style="margin-bottom:12px;">
<div style="display:flex;align-items:center;justify-content:space-between;">
<div>
<strong style="font-size:14px;">${esc(p.name)}</strong>
${p.description && html`
<div style="font-size:12px;color:var(--text-2);margin-top:2px;">
${esc(p.description)}
</div>
`}
</div>
<div style="display:flex;gap:4px;">
<button class="icon-btn" title="Edit" onClick=${() => openEdit(p)}>
\u{270F}\u{FE0F}
</button>
<button class="icon-btn icon-btn-danger" title="Delete"
onClick=${() => del(p)}>
\u{1F5D1}
</button>
</div>
</div>
</div>
`)}
</div>
`}
`;
}