/* global window */ // AgentUI common UI utilities (DRY helpers shared by editor.html and pm-ui.js) (function (w) { 'use strict'; const AU = {}; // HTML escaping for safe text/attribute insertion AU.escapeHtml = function escapeHtml(s) { const str = String(s ?? ''); return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, "'"); }; // Attribute-safe escape (keeps quotes escaped; conservative) AU.escAttr = function escAttr(v) { const s = String(v ?? ''); return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, "'"); }; // Text-node escape (keeps quotes as-is for readability) AU.escText = function escText(v) { const s = String(v ?? ''); return s .replace(/&/g, '&') .replace(//g, '>'); }; // DRY helper: sync Drawflow node data + mirror into DOM.__data with deep copy AU.updateNodeDataAndDom = function updateNodeDataAndDom(editor, id, data) { try { editor && typeof editor.updateNodeDataFromId === 'function' && editor.updateNodeDataFromId(id, data); } catch (_) {} try { const el = document.querySelector('#node-' + id); if (el) el.__data = JSON.parse(JSON.stringify(data)); } catch (_) {} }; // Double rAF helper: waits for two animation frames; returns Promise or accepts callback AU.nextRaf2 = function nextRaf2(cb) { try { if (typeof requestAnimationFrame === 'function') { if (typeof cb === 'function') { requestAnimationFrame(() => { requestAnimationFrame(() => { try { cb(); } catch (_) {} }); }); return; } return new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))); } else { if (typeof cb === 'function') { setTimeout(() => { try { cb(); } catch (_) {} }, 32); return; } return new Promise((resolve) => setTimeout(resolve, 32)); } } catch (_) { if (typeof cb === 'function') { try { cb(); } catch (__ ) {} } return Promise.resolve(); } }; // Heuristic: looks like long base64 payload AU.isProbablyBase64 = function isProbablyBase64(s) { try { if (typeof s !== 'string') return false; if (s.length < 64) return false; return /^[A-Za-z0-9+/=\r\n]+$/.test(s); } catch { return false; } }; AU.trimBase64 = function trimBase64(s, maxLen = 180) { try { const str = String(s ?? ''); if (str.length > maxLen) { return str.slice(0, maxLen) + `... (trimmed ${str.length - maxLen})`; } return str; } catch { return String(s ?? ''); } }; // Flatten JSON-like object into [path, stringValue] pairs // Includes special handling for backend preview objects: { "__truncated__": true, "preview": "..." } AU.flattenObject = function flattenObject(obj, prefix = '') { const out = []; if (obj == null) return out; if (typeof obj !== 'object') { out.push([prefix, String(obj)]); return out; } try { const entries = Object.entries(obj); for (const [k, v] of entries) { const p = prefix ? `${prefix}.${k}` : k; if (v && typeof v === 'object' && !Array.isArray(v)) { // Special preview shape from backend if (Object.prototype.hasOwnProperty.call(v, '__truncated__') && Object.prototype.hasOwnProperty.call(v, 'preview')) { out.push([p, String(v.preview ?? '')]); continue; } out.push(...AU.flattenObject(v, p)); } else { try { const s = (typeof v === 'string') ? v : JSON.stringify(v, null, 0); out.push([p, s]); } catch { out.push([p, String(v)]); } } } } catch { // Fallback best-effort try { out.push([prefix, JSON.stringify(obj)]); } catch { out.push([prefix, String(obj)]); } } return out; }; // Format headers dictionary into text lines "Key: Value" AU.fmtHeaders = function fmtHeaders(h) { try { const keys = Object.keys(h || {}); return keys.map(k => `${k}: ${String(h[k])}`).join('\n'); } catch { return ''; } }; // Build HTTP request preview text AU.buildReqText = function buildReqText(x) { if (!x) return ''; const head = `${x.method || 'POST'} ${x.url || '/'} HTTP/1.1`; const host = (() => { try { const u = new URL(x.url); return `Host: ${u.host}`; } catch { return ''; } })(); const hs = AU.fmtHeaders(x.headers || {}); const body = String(x.body_text || '').trim(); return [head, host, hs, '', body].filter(Boolean).join('\n'); }; // Build HTTP response preview text AU.buildRespText = function buildRespText(x) { if (!x) return ''; const head = `HTTP/1.1 ${x.status || 0}`; const hs = AU.fmtHeaders(x.headers || {}); const body = String(x.body_text || '').trim(); return [head, hs, '', body].filter(Boolean).join('\n'); }; // Unified fetch helper with timeout and JSON handling AU.apiFetch = async function apiFetch(url, opts) { const t0 = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now(); const o = opts || {}; const method = String(o.method || 'GET').toUpperCase(); const expectJson = (o.expectJson !== false); // default true const headers = Object.assign({}, o.headers || {}); let body = o.body; const timeoutMs = Number.isFinite(o.timeoutMs) ? o.timeoutMs : 15000; const hasAbort = (typeof AbortController !== 'undefined'); const ctrl = hasAbort ? new AbortController() : null; let to = null; if (ctrl) { try { to = setTimeout(() => { try { ctrl.abort(); } catch(_){} }, timeoutMs); } catch(_) {} } try { if (expectJson) { if (!headers['Accept'] && !headers['accept']) headers['Accept'] = 'application/json'; } if (body != null) { const isForm = (typeof FormData !== 'undefined' && body instanceof FormData); const isBlob = (typeof Blob !== 'undefined' && body instanceof Blob); if (typeof body === 'object' && !isForm && !isBlob) { body = JSON.stringify(body); if (!headers['Content-Type'] && !headers['content-type']) headers['Content-Type'] = 'application/json'; } } const res = await fetch(url, { method, headers, body, signal: ctrl ? ctrl.signal : undefined }); const ct = String(res.headers && res.headers.get ? (res.headers.get('Content-Type') || '') : ''); const isJsonCt = /application\/json/i.test(ct); let data = null; if (expectJson || isJsonCt) { try { data = await res.json(); } catch (_) { data = null; } } else { try { data = await res.text(); } catch (_) { data = null; } } const t1 = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now(); try { console.debug('[AU.apiFetch]', { method, url, status: res.status, ms: Math.round(t1 - t0) }); } catch(_) {} if (!res.ok) { const msg = (data && typeof data === 'object' && data.error) ? String(data.error) : `HTTP ${res.status}`; const err = new Error(`apiFetch: ${msg}`); err.status = res.status; err.data = data; err.url = url; throw err; } return data; } finally { if (to) { try { clearTimeout(to); } catch(_) {} } } }; // Expose try { w.AU = AU; } catch (_) {} try { w.nextRaf2 = AU.nextRaf2; } catch (_) {} })(window);