/* global window, document */ // AgentUI Prompt Manager UI extracted from editor.html // Exposes window.PM.setupProviderCallPMUI(editor, id) // Depends on DOM elements rendered by editor.html inspector: // #pm-list, #pm-editor, #pm-name, #pm-role, #pm-prompt, #pm-save, #pm-cancel (function (w) { 'use strict'; function setupProviderCallPMUI(editor, id) { try { const n2 = editor.getNodeFromId(id); if (!n2) return; const d2 = n2.data || {}; if (!Array.isArray(d2.blocks)) d2.blocks = []; // Ensure node.data and DOM __data always reflect latest blocks function syncNodeDataBlocks() { try { const n = editor.getNodeFromId(id); if (!n) return; // Готовим новые данные с глубокой копией blocks const newData = { ...(n.data || {}), blocks: Array.isArray(d2.blocks) ? d2.blocks.map(b => ({ ...b })) : [] }; // 1) Обновляем внутреннее состояние Drawflow, чтобы export() возвращал актуальные данные try { editor.updateNodeDataFromId(id, newData); } catch (e) {} // 2) Обновляем DOM-отражение (источник правды для toPipelineJSON) const el2 = document.querySelector(`#node-${id}`); if (el2) el2.__data = JSON.parse(JSON.stringify(newData)); } catch (e) {} } // Initial sync to attach blocks into __data for toPipelineJSON syncNodeDataBlocks(); const listEl = document.getElementById('pm-list'); const addBtn = document.getElementById('pm-add'); const editorBox = document.getElementById('pm-editor'); const nameInp = document.getElementById('pm-name'); const roleSel = document.getElementById('pm-role'); const promptTxt = document.getElementById('pm-prompt'); const saveBtn = document.getElementById('pm-save'); const cancelBtn = document.getElementById('pm-cancel'); let editingId = null; // Изменения блока применяются только по кнопке «Сохранить» внутри редактора блока. // Drag&Drop через SortableJS (если доступен) if (w.Sortable && listEl && !listEl.__sortable) { listEl.__sortable = new w.Sortable(listEl, { animation: 150, handle: '.pm-handle', onEnd(evt) { const oldIndex = evt.oldIndex; const newIndex = evt.newIndex; if (oldIndex === newIndex) return; const moved = d2.blocks.splice(oldIndex, 1)[0]; d2.blocks.splice(newIndex, 0, moved); d2.blocks.forEach((b, i) => b.order = i); syncNodeDataBlocks(); } }); } function sortAndReindex() { d2.blocks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); d2.blocks.forEach((b, i) => b.order = i); } function renderList() { sortAndReindex(); if (!listEl) return; listEl.innerHTML = ''; d2.blocks.forEach((b, i) => { const domId = b.id || ('b' + i); const li = document.createElement('li'); li.draggable = true; li.dataset.id = domId; li.style.display = 'flex'; li.style.alignItems = 'center'; li.style.gap = '6px'; li.style.padding = '4px 0'; li.innerHTML = ` ${(b.name || ('Block ' + (i + 1))).replace(/ ${b.role || 'user'} `; // DnD via HTML5 fallback as well (kept for compatibility) li.addEventListener('dragstart', e => { e.dataTransfer.setData('text/plain', domId); }); li.addEventListener('dragover', e => { e.preventDefault(); }); li.addEventListener('drop', e => { e.preventDefault(); const srcId = e.dataTransfer.getData('text/plain'); const tgtId = domId; if (!srcId || srcId === tgtId) return; const srcIdx = d2.blocks.findIndex(x => (x.id || '') === srcId); const tgtIdx = d2.blocks.findIndex(x => (x.id || '') === tgtId); if (srcIdx < 0 || tgtIdx < 0) return; const [moved] = d2.blocks.splice(srcIdx, 1); d2.blocks.splice(tgtIdx, 0, moved); sortAndReindex(); renderList(); syncNodeDataBlocks(); }); // toggle li.querySelector('.pm-enabled').addEventListener('change', ev => { b.enabled = ev.target.checked; syncNodeDataBlocks(); }); // edit li.querySelector('.pm-edit').addEventListener('click', () => { openEditor(b); }); // delete li.querySelector('.pm-del').addEventListener('click', () => { const idx = d2.blocks.indexOf(b); if (idx >= 0) d2.blocks.splice(idx, 1); sortAndReindex(); renderList(); syncNodeDataBlocks(); if (editingId && editingId === (b.id || null)) { if (editorBox) editorBox.style.display = 'none'; editingId = null; } }); listEl.appendChild(li); }); } function openEditor(b) { // Гарантируем наличие id у редактируемого блока if (!b.id) { b.id = 'b' + Date.now().toString(36); syncNodeDataBlocks(); } editingId = b.id; if (editorBox) editorBox.style.display = ''; if (nameInp) nameInp.value = b.name || ''; if (roleSel) roleSel.value = (b.role || 'user'); if (promptTxt) promptTxt.value = b.prompt || ''; } if (addBtn) { addBtn.addEventListener('click', () => { const idv = 'b' + Date.now().toString(36); const nb = { id: idv, name: 'New Block', role: 'system', prompt: '', enabled: true, order: d2.blocks.length }; d2.blocks.push(nb); sortAndReindex(); renderList(); syncNodeDataBlocks(); openEditor(nb); }); } if (saveBtn) { saveBtn.addEventListener('click', () => { if (!editingId) { if (editorBox) editorBox.style.display = 'none'; return; } const b = d2.blocks.find(x => (x.id || null) === editingId); if (b) { b.name = nameInp ? nameInp.value : b.name; b.role = roleSel ? roleSel.value : b.role || 'user'; b.prompt = promptTxt ? promptTxt.value : b.prompt; // Пересоберём массив, чтобы избежать проблем с мутацией по ссылке d2.blocks = d2.blocks.map(x => (x.id === b.id ? ({ ...b }) : x)); } if (editorBox) editorBox.style.display = 'none'; editingId = null; renderList(); syncNodeDataBlocks(); // попытка автосохранения пайплайна, если доступна глобальная функция try { (typeof w.savePipeline === 'function') && w.savePipeline(); } catch (e) {} try { (typeof w.status === 'function') && w.status('Блок сохранён в pipeline.json'); } catch (e) {} }); } if (cancelBtn) { cancelBtn.addEventListener('click', () => { if (editorBox) editorBox.style.display = 'none'; editingId = null; }); } // Первичная отрисовка renderList(); } catch (e) {} } w.PM = { setupProviderCallPMUI }; })(window);