// ── Modal Helper ── function _createModal(titleText, extraClasses) { const overlay = document.createElement('div'); overlay.className = 'sp-overlay' + (extraClasses && extraClasses.overlay ? ' ' + extraClasses.overlay : ''); const modal = document.createElement('div'); modal.className = 'sp-modal' + (extraClasses && extraClasses.modal ? ' ' + extraClasses.modal : ''); const header = document.createElement('div'); header.className = 'sp-header'; const title = document.createElement('span'); title.textContent = titleText; const closeBtn = document.createElement('button'); closeBtn.innerHTML = ''; closeBtn.className = 'sp-close icon-btn'; header.appendChild(title); header.appendChild(closeBtn); const body = document.createElement('div'); body.className = 'sp-body' + (extraClasses && extraClasses.body ? ' ' + extraClasses.body : ''); modal.appendChild(header); modal.appendChild(body); overlay.appendChild(modal); document.body.appendChild(overlay); const close = () => overlay.remove(); closeBtn.addEventListener('click', close); overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); }); const onKey = (e) => { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } }; document.addEventListener('keydown', onKey); return { overlay, modal, body, close }; } // ── System-Prompt Modal ── function _renderSystemPromptBlocks(container, blocks) { for (var i = 0; i < blocks.length; i++) { var block = blocks[i]; if (!block.content) continue; var wrapper = document.createElement('div'); wrapper.className = 'sp-block'; var label = document.createElement('div'); label.className = 'sp-block-label'; label.innerHTML = ' ' + esc(block.label || 'Block'); wrapper.appendChild(label); var content = document.createElement('div'); content.className = 'sp-block-content markdown'; content.innerHTML = renderMarkdown(block.content); parseFluentEmojis(content); wrapper.appendChild(content); container.appendChild(wrapper); } } async function showSystemPromptModal(sessionId) { const { body } = _createModal('System-Prompt', { body: 'markdown' }); body.innerHTML = '
Laden...
'; try { const blocks = await apiGetSystemPromptBlocks(sessionId, typeof _planModeActive !== 'undefined' && _planModeActive); body.innerHTML = ''; _renderSystemPromptBlocks(body, blocks); } catch (e) { body.innerHTML = '
Fehler beim Laden des System-Prompts
'; } } // ── Payload Preview Modal ── async function showPayloadPreviewModal(sessionId) { const { body } = _createModal('Payload Preview', { modal: 'payload-preview-modal' }); body.innerHTML = '
Laden...
'; try { var planMode = typeof _planModeActive !== 'undefined' && _planModeActive; var payload = await apiGetPayloadPreview(sessionId, planMode); body.innerHTML = ''; var pre = document.createElement('pre'); pre.className = 'payload-json'; pre.textContent = JSON.stringify(payload, null, 2); body.appendChild(pre); } catch (e) { body.innerHTML = '
Fehler beim Laden des Payloads
'; } } // ── Workflow-Status Modal (Ultracode) ── function _wfNum(n) { return Number(n || 0).toLocaleString('de-DE'); } function _wfTime(t) { try { return new Date(t).toLocaleTimeString('de-DE'); } catch (e) { return ''; } } function _wfDur(ms) { ms = Number(ms || 0); if (ms <= 0) return '–'; var s = Math.round(ms / 1000); if (s < 60) return s + 's'; var m = Math.floor(s / 60); return m + 'm ' + (s % 60) + 's'; } function _renderWorkflowStatus(body, data) { var status = (data && data.status) || 'none'; var wf = data && data.workflow; var meta = { running: { label: 'Workflow läuft', cls: 'wf-running', icon: 'fa-spinner fa-spin' }, completed: { label: 'Workflow abgeschlossen', cls: 'wf-done', icon: 'fa-check-circle' }, none: { label: 'Kein Workflow für diese Session', cls: 'wf-none', icon: 'fa-ban' } }; var sm = meta[status] || meta.none; body.innerHTML = ''; var head = document.createElement('div'); head.className = 'wf-status-head ' + sm.cls; head.innerHTML = ' ' + esc(sm.label); body.appendChild(head); if (!wf) { var empty = document.createElement('div'); empty.className = 'wf-empty'; empty.textContent = 'Für diese Session wurde noch kein dynamischer Workflow (Ultracode) ausgeführt.'; body.appendChild(empty); return; } // Name / Zusammenfassung des Workflows (nur nach Abschluss vorhanden). if (wf.workflowName || wf.summary) { var title = document.createElement('div'); title.className = 'wf-title'; title.innerHTML = (wf.workflowName ? '' + esc(wf.workflowName) + '' : '') + (wf.summary ? '' + esc(wf.summary) + '' : ''); body.appendChild(title); } // runId + Resume-Hinweis: Bei einem abgebrochenen Lauf kann der Agent den Workflow mit dieser // runId (resumeFromRunId) fortsetzen – fertige Subagenten werden aus dem Journal wiederverwendet. if (wf.runId) { var rid = document.createElement('div'); rid.className = 'wf-meta'; rid.innerHTML = 'runId: ' + esc(wf.runId) + '' + (status !== 'completed' ? ' · fortsetzbar via resumeFromRunId' : ''); body.appendChild(rid); } // Kennzahlen. var stats = [ { label: 'Subagenten', value: _wfNum(wf.subagentCount) }, { label: 'Fertig', value: _wfNum(wf.completedCount) + ' / ' + _wfNum(wf.subagentCount) }, { label: 'Tokens', value: _wfNum(wf.totalTokens) }, { label: 'Tool-Aufrufe', value: _wfNum(wf.totalToolCalls) }, { label: status === 'running' ? 'Läuft seit' : 'Dauer', value: _wfDur(wf.durationMs) } ]; var grid = document.createElement('div'); grid.className = 'wf-stat-grid'; stats.forEach(function (s) { var cell = document.createElement('div'); cell.className = 'wf-stat'; cell.innerHTML = '
' + esc(String(s.value)) + '
' + esc(s.label) + '
'; grid.appendChild(cell); }); body.appendChild(grid); // Phasen (nur nach Abschluss). var phases = wf.phases || []; if (phases.length) { var psec = document.createElement('div'); psec.className = 'wf-section'; psec.innerHTML = '
Phasen
'; var plist = document.createElement('div'); plist.className = 'wf-phase-list'; phases.forEach(function (p) { var row = document.createElement('div'); row.className = 'wf-phase-row'; row.innerHTML = '' + esc(p.title || '') + '' + (p.detail ? '' + esc(p.detail) + '' : ''); plist.appendChild(row); }); psec.appendChild(plist); body.appendChild(psec); } // Subagenten-Details. Nach Abschluss: Labels + Tokens/Tool-Calls/Dauer (aus .json). // Live/bei Abbruch: agentId als Label + State + geretteter Output (aus journal.jsonl) – so ist // die Arbeit fertiger Subagenten auch dann sichtbar, wenn der Lauf nie sauber endete. var agents = wf.agents || []; if (agents.length) { var asec = document.createElement('div'); asec.className = 'wf-section'; asec.innerHTML = '
Subagenten
'; var alist = document.createElement('div'); alist.className = 'wf-agent-list'; agents.forEach(function (a) { var st = a.state || ''; var stCls = st === 'done' ? 'wf-ag-done' : (st === 'running' ? 'wf-ag-running' : 'wf-ag-other'); var hasMetrics = (a.tokens || a.toolCalls || a.durationMs); var row = document.createElement('div'); row.className = 'wf-agent-row'; row.innerHTML = '
' + '' + '' + esc(a.label || ('Agent ' + (a.index || ''))) + '' + (a.phaseTitle ? '' + esc(a.phaseTitle) + '' : '') + '
' + (hasMetrics ? '
' + ' ' + _wfNum(a.tokens) + '' + ' ' + _wfNum(a.toolCalls) + '' + ' ' + _wfDur(a.durationMs) + '' + '
' : '') + (a.resultPreview ? '
' + esc(a.resultPreview) + '
' : ''); alist.appendChild(row); }); asec.appendChild(alist); body.appendChild(asec); } if (wf.startedAt) { var ts = document.createElement('div'); ts.className = 'wf-meta'; ts.textContent = 'Start: ' + _wfTime(wf.startedAt); body.appendChild(ts); } } async function showWorkflowStatusModal(sessionId) { const { body, overlay } = _createModal('Workflow-Status', { modal: 'workflow-status-modal' }); body.innerHTML = '
Laden...
'; var poll = async function () { try { var data = await apiGetWorkflowStatus(sessionId); _renderWorkflowStatus(body, data); return data && data.status; } catch (e) { body.innerHTML = '
Fehler beim Laden des Workflow-Status
'; return null; } }; var status = await poll(); // Live-Refresh, solange ein Workflow läuft – endet automatisch beim Schließen des Modals. if (status === 'running') { var timer = setInterval(async function () { if (!document.body.contains(overlay)) { clearInterval(timer); return; } var s = await poll(); if (s !== 'running') clearInterval(timer); }, 2000); } } // ── Command Output Modal ── function showCommandOutputModal(name, command, result) { const { body } = _createModal(name, { modal: 'cmd-output-modal' }); const exitCode = result.exitCode != null ? result.exitCode : '?'; const isError = exitCode !== 0; const statusText = result.status === 'timeout' ? 'Timeout' : `Exit ${exitCode}`; const meta = document.createElement('div'); meta.className = 'cmd-output-meta'; meta.innerHTML = `${esc(command)} ${esc(statusText)}`; body.appendChild(meta); const output = document.createElement('pre'); output.className = 'cmd-output-content'; output.textContent = result.output || '(kein Output)'; body.appendChild(output); } // ── Krea Image Preview Modal ── function showKreaImageModal(titleText, fullUrl) { const { body } = _createModal(titleText, { overlay: 'krea-overlay', modal: 'krea-modal', body: 'krea-modal-body' }); var img = document.createElement('img'); img.src = fullUrl; img.className = 'krea-modal-image'; img.alt = titleText; img.addEventListener('error', function() { body.innerHTML = '
Bild konnte nicht geladen werden
'; }); body.appendChild(img); } // ── Picker Modal Helper ── function _openPickerModal(title, items, currentId, onSelect, options) { options = options || {}; const noneLabel = options.noneLabel || 'Keine Auswahl'; const noneIcon = options.noneIcon || 'fa-ban'; const overlay = document.createElement('div'); overlay.className = 'agent-picker-overlay'; const modal = document.createElement('div'); modal.className = 'agent-picker-modal'; const header = document.createElement('div'); header.className = 'agent-picker-header'; header.innerHTML = `${esc(title)}`; modal.appendChild(header); const body = document.createElement('div'); body.className = 'agent-picker-body'; // "Keine" Option const noneCard = document.createElement('div'); noneCard.className = 'card agent-picker-item' + (!currentId ? ' selected' : ''); noneCard.innerHTML = `
${esc(noneLabel)}
`; noneCard.addEventListener('click', () => { onSelect(null); overlay.remove(); }); body.appendChild(noneCard); // Items for (const item of items) { const cardEl = options.buildCard(item); cardEl.classList.add('agent-picker-item'); if (item._pickerId === currentId) cardEl.classList.add('selected'); cardEl.addEventListener('click', () => { onSelect(item._pickerId); overlay.remove(); }); body.appendChild(cardEl); } modal.appendChild(body); overlay.appendChild(modal); document.body.appendChild(overlay); header.querySelector('.agent-picker-close').addEventListener('click', () => overlay.remove()); overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); }); } // ── Multi-Select Picker Modal ── // Wie _openPickerModal, aber Mehrfachauswahl: Karten togglen ihren selected-Zustand, // ein "Übernehmen"-Button bestätigt. onConfirm bekommt das Array der gewählten _pickerId. function _openMultiPickerModal(title, items, selectedIds, onConfirm, options) { options = options || {}; var selected = new Set(selectedIds || []); var overlay = document.createElement('div'); overlay.className = 'agent-picker-overlay'; var modal = document.createElement('div'); modal.className = 'agent-picker-modal'; var header = document.createElement('div'); header.className = 'agent-picker-header'; header.innerHTML = '' + esc(title) + ''; modal.appendChild(header); var body = document.createElement('div'); body.className = 'agent-picker-body'; for (var i = 0; i < items.length; i++) { (function(item) { var cardEl = options.buildCard(item); cardEl.classList.add('agent-picker-item'); if (selected.has(item._pickerId)) cardEl.classList.add('selected'); cardEl.addEventListener('click', function() { if (selected.has(item._pickerId)) { selected.delete(item._pickerId); cardEl.classList.remove('selected'); } else { selected.add(item._pickerId); cardEl.classList.add('selected'); } }); body.appendChild(cardEl); })(items[i]); } modal.appendChild(body); var footer = document.createElement('div'); footer.style.cssText = 'display:flex;justify-content:flex-end;gap:8px;padding:12px;border-top:1px solid var(--f2-border)'; var confirmBtn = document.createElement('button'); confirmBtn.className = 'add-cmd-btn'; confirmBtn.textContent = 'Übernehmen'; confirmBtn.addEventListener('click', function() { onConfirm(Array.from(selected)); overlay.remove(); }); footer.appendChild(confirmBtn); modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay); header.querySelector('.agent-picker-close').addEventListener('click', function() { overlay.remove(); }); overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); }); } // ── Telegram Bot Picker Modal ── function openTelegramBotPickerModal(bots, currentBotId, onSelect) { var items = bots.map(function(b) { b._pickerId = b.botId; return b; }); _openPickerModal('Telegram Bot auswählen', items, currentBotId, onSelect, { noneLabel: 'Kein Bot', noneIcon: 'fa-paper-plane', buildCard: function(b) { return buildTelegramBotCard(b); } }); } // ── OpenClaw Import Modal ── async function showOpenClawImportModal(sessionId) { const { body, close } = _createModal('OpenClaw Import', { modal: 'openclaw-import-modal' }); body.innerHTML = '
Sessions werden geladen...
'; try { const sessions = await apiGetOpenclawSessions(); if (!sessions || sessions.length === 0) { body.innerHTML = '
Keine OpenClaw-Sessions gefunden
'; return; } body.innerHTML = ''; var hint = document.createElement('div'); hint.className = 'openclaw-hint'; hint.textContent = 'Wähle eine OpenClaw-Session zum Importieren. Vorherige Imports werden aktualisiert.'; body.appendChild(hint); var list = document.createElement('div'); list.className = 'openclaw-session-list'; for (var i = 0; i < sessions.length; i++) { var s = sessions[i]; var card = document.createElement('div'); card.className = 'openclaw-session-card'; // Header var header = document.createElement('div'); header.className = 'openclaw-card-header'; var idSpan = document.createElement('span'); idSpan.className = 'openclaw-session-id'; idSpan.textContent = s.sessionId.substring(0, 8) + '…'; idSpan.title = s.sessionId; header.appendChild(idSpan); var meta = document.createElement('span'); meta.className = 'openclaw-card-meta'; meta.innerHTML = ' ' + esc(formatDate(s.createdAt)) + '' + ' ' + s.totalMessages + ' Nachrichten' + (s.model && s.model !== 'unknown' ? ' ' + esc(s.model) + '' : ''); header.appendChild(meta); card.appendChild(header); // Summary if (s.summary) { var summary = document.createElement('div'); summary.className = 'openclaw-summary'; // Take first line of summary that's meaningful (skip empty lines and headers) var summaryLines = s.summary.split('\n').filter(function(l) { return l.trim() && !l.startsWith('#'); }); var summaryText = summaryLines.slice(0, 2).join(' ').trim(); if (summaryText.length > 150) summaryText = summaryText.substring(0, 150) + '…'; summary.textContent = summaryText || s.summary.substring(0, 150); card.appendChild(summary); } // Preview messages if (s.previewMessages && s.previewMessages.length > 0) { var preview = document.createElement('div'); preview.className = 'openclaw-preview'; for (var j = 0; j < s.previewMessages.length; j++) { var pm = s.previewMessages[j]; var line = document.createElement('div'); line.className = 'openclaw-preview-msg openclaw-preview-' + pm.role; var icon = pm.role === 'user' ? 'fa-user' : 'fa-cog'; line.innerHTML = ' ' + esc(pm.text) + ''; preview.appendChild(line); } card.appendChild(preview); } // Click handler - capture session in closure (function(openclawSession) { card.addEventListener('click', async function() { // Confirm var confirmed = confirm('OpenClaw-Session importieren?\n\nSession: ' + openclawSession.sessionId.substring(0, 8) + '…\n' + openclawSession.totalMessages + ' Nachrichten\n\nVorhandene Imports werden ersetzt.'); if (!confirmed) return; card.classList.add('loading-state'); try { var result = await apiImportOpenclawSession(openclawSession.sessionId, sessionId); close(); showToast(result.messageCount + ' Nachrichten in ' + result.chainCount + ' Chains importiert', 'success'); } catch (e) { card.classList.remove('loading-state'); showToast('Import fehlgeschlagen: ' + e.message, 'error'); } }); })(s); list.appendChild(card); } body.appendChild(list); } catch (e) { body.innerHTML = '
Fehler: ' + esc(e.message) + '
'; } } // ── Remote-Verbindungen Modal ── async function showRemoteModal(sessionId) { const { body, close } = _createModal('Remote-Verbindungen'); body.innerHTML = '
Laden...
'; async function renderConnections() { try { const connections = await apiGetRemoteConnections(); body.innerHTML = ''; var empty = document.createElement('div'); empty.className = 'sp-loading'; empty.innerHTML = '
Windows (PowerShell)
' + 'irm ' + esc(location.origin) + '/remote.ps1 | iex' + '
Linux (Bash)
' + 'curl -s ' + esc(location.origin) + '/remote.sh | bash'; body.appendChild(empty); if (!connections || connections.length === 0) { return; } for (var i = 0; i < connections.length; i++) { var conn = connections[i]; var card = document.createElement('div'); card.className = 'sp-block'; card.classList.add('clickable'); var isAssigned = conn.assignedSessionId === sessionId; var isAssignedOther = conn.assignedSessionId && conn.assignedSessionId !== sessionId; var label = document.createElement('div'); label.className = 'sp-block-label'; var statusIcon = isAssigned ? 'fa-check-circle' : (isAssignedOther ? 'fa-link' : 'fa-desktop'); var statusClass = isAssigned ? ' text-primary' : ''; label.innerHTML = ' ' + esc(conn.username || '?') + '@' + esc(conn.hostname || '?'); card.appendChild(label); var content = document.createElement('div'); content.className = 'sp-block-content'; content.className = 'remote-card-content'; var info = document.createElement('span'); info.className = 'remote-card-info'; var connDate = new Date(conn.connectedAt); info.textContent = conn.os + ' • Verbunden seit ' + connDate.toLocaleTimeString('de'); content.appendChild(info); var btn = document.createElement('button'); if (isAssigned) { btn.textContent = 'Trennen'; btn.classList.add('text-danger'); } else if (isAssignedOther) { btn.textContent = 'Andere Session'; btn.disabled = true; } else { btn.textContent = 'Verbinden'; btn.classList.add('text-primary'); } content.appendChild(btn); card.appendChild(content); body.appendChild(card); // Event-Handler (function(c, assigned) { btn.addEventListener('click', async function(e) { e.stopPropagation(); btn.disabled = true; try { if (assigned) { await apiUnassignRemote(c.id); } else { await apiAssignRemote(c.id, sessionId); } await renderConnections(); } catch(err) { showToast('Fehler: ' + err.message, 'error'); btn.disabled = false; } }); })(conn, isAssigned); } } catch (e) { body.innerHTML = '
Fehler beim Laden: ' + esc(e.message) + '
'; } } await renderConnections(); } // Event-Delegation für Krea-Thumbnails im Chat document.addEventListener('click', function(e) { var ref = e.target.closest('.krea-ref'); if (ref) { var fullUrl = ref.dataset.fullurl; var filename = ref.dataset.filename; if (fullUrl) showKreaImageModal(filename || 'Bild', fullUrl); } });