/* 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);