// persona-kb.js — Persona–Knowledge Base binding UI (v0.17.0) // // Provides renderPersonaKBPicker(container, opts) which renders a multi-select // KB picker with auto_search toggles for use in persona create/edit forms. // Pattern: (container, options) → control object with getValues/setValues/clear. 'use strict'; // ── Persona-KB Picker ─────────────────────────── /** * Renders a KB picker into a container element. * * Options: * scope - 'admin' | 'team' | 'personal' — controls which KBs are shown * teamId - required for team scope * prefix - DOM id prefix (default: 'pkb') * * Returns: { getValues(), setValues(kbs), clear(), refresh() } */ function renderPersonaKBPicker(containerEl, options = {}) { const pfx = options.prefix || 'pkb'; let _allKBs = []; // available KBs from server let _selected = {}; // kb_id → { selected: bool, auto_search: bool } containerEl.innerHTML = `
Bind KBs to this persona — users will search these KBs automatically when using this persona.
Loading knowledge bases…
`; // ── Load available KBs ── async function loadKBs() { try { let resp; if (options.scope === 'team' && options.teamId) { resp = await API._get(`/api/v1/teams/${options.teamId}/knowledge-bases`); } else if (options.scope === 'admin') { resp = await API._get('/api/v1/knowledge-bases'); } else { // User scope — show discoverable KBs resp = await API._get('/api/v1/knowledge-bases-discoverable'); } _allKBs = (resp.data || []).filter(kb => kb.chunk_count > 0); } catch (e) { _allKBs = []; } render(); } // ── Render KB list ── function render() { const list = document.getElementById(`${pfx}_list`); if (!list) return; if (_allKBs.length === 0) { list.innerHTML = '
No knowledge bases with content available.
'; return; } list.innerHTML = _allKBs.map(kb => { const sel = _selected[kb.id] || { selected: false, auto_search: false }; return ` `; }).join(''); // Wire checkboxes list.querySelectorAll('input[data-kb-id]').forEach(cb => { cb.addEventListener('change', function() { const kbId = this.dataset.kbId; if (!_selected[kbId]) _selected[kbId] = { selected: false, auto_search: false }; _selected[kbId].selected = this.checked; render(); }); }); list.querySelectorAll('input[data-kb-auto]').forEach(cb => { cb.addEventListener('change', function() { const kbId = this.dataset.kbAuto; if (_selected[kbId]) { _selected[kbId].auto_search = this.checked; } }); }); } // ── Control object ── const control = { getValues() { const kbIds = []; const autoSearch = {}; for (const [kbId, state] of Object.entries(_selected)) { if (state.selected) { kbIds.push(kbId); if (state.auto_search) { autoSearch[kbId] = true; } } } return { kb_ids: kbIds, auto_search: autoSearch }; }, setValues(personaKBs) { _selected = {}; if (Array.isArray(personaKBs)) { for (const pkb of personaKBs) { _selected[pkb.kb_id] = { selected: true, auto_search: !!pkb.auto_search, }; } } render(); }, clear() { _selected = {}; render(); }, async refresh() { await loadKBs(); } }; // Auto-load on create loadKBs(); return control; } // ── Persona-KB Save/Load Helpers ──────────────── /** * Load persona KB bindings and populate the picker. * @param {string} personaId * @param {object} kbPicker - control object from renderPersonaKBPicker * @param {string} apiPrefix - e.g. '/api/v1/admin' or '/api/v1/teams/:teamId' */ async function loadPersonaKBs(personaId, kbPicker, apiPrefix) { if (!personaId || !kbPicker) return; try { const resp = await API._get(`${apiPrefix}/personas/${personaId}/knowledge-bases`); kbPicker.setValues(resp.data || []); } catch (e) { console.warn('Failed to load persona KBs:', e.message); } } /** * Save persona KB bindings from the picker. * @param {string} personaId * @param {object} kbPicker - control object from renderPersonaKBPicker * @param {string} apiPrefix - e.g. '/api/v1/admin' or '/api/v1/teams/:teamId' */ async function savePersonaKBs(personaId, kbPicker, apiPrefix) { if (!personaId || !kbPicker) return; const values = kbPicker.getValues(); try { await API._put(`${apiPrefix}/personas/${personaId}/knowledge-bases`, values); } catch (e) { console.warn('Failed to save persona KBs:', e.message); } }