Loading editor/html/css/editor.css +165 −0 Original line number Diff line number Diff line Loading @@ -411,6 +411,171 @@ dialog::backdrop { background: rgba(0, 0, 0, 0.6); } /* AI Panel — non-modal, draggable */ dialog.ai-panel { position: fixed; padding: 0; min-width: 380px; max-width: 520px; width: 460px; height: 520px; display: flex; flex-direction: column; resize: both; overflow: hidden; z-index: 1000; margin: 0; inset: unset; right: 20px; bottom: 20px; } dialog.ai-panel::backdrop { background: none; pointer-events: none; } .ai-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: var(--bg-tertiary); border-bottom: 1px solid var(--border); cursor: grab; user-select: none; font-size: 13px; font-weight: 600; flex-shrink: 0; } .ai-titlebar:active { cursor: grabbing; } .ai-titlebar-actions { display: flex; gap: 4px; } .ai-titlebar-actions button { background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 14px; padding: 2px 6px; border-radius: 3px; } .ai-titlebar-actions button:hover { background: var(--bg-primary); color: var(--text-primary); } .ai-chat-history { flex: 1; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 10px; } .ai-msg { padding: 8px 10px; border-radius: 8px; font-size: 13px; line-height: 1.45; max-width: 92%; word-wrap: break-word; white-space: pre-wrap; } .ai-msg.user { background: var(--accent); color: var(--bg-primary); align-self: flex-end; border-bottom-right-radius: 2px; } .ai-msg.assistant { background: var(--bg-tertiary); color: var(--text-primary); align-self: flex-start; border-bottom-left-radius: 2px; } .ai-msg.assistant .ai-msg-actions { display: flex; gap: 6px; margin-top: 6px; } .ai-msg.assistant .ai-msg-actions button { background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); padding: 3px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; } .ai-msg.assistant .ai-msg-actions button:hover { color: var(--text-primary); background: var(--bg-primary); } .ai-msg.status { color: var(--text-secondary); font-size: 12px; font-style: italic; align-self: center; background: none; padding: 2px; } .ai-msg.error { color: #e55; font-size: 12px; align-self: center; background: none; padding: 2px; } .ai-input-area { border-top: 1px solid var(--border); padding: 8px 10px; flex-shrink: 0; background: var(--bg-secondary); } .ai-prompt-row { display: flex; gap: 6px; align-items: flex-end; } .ai-prompt-row textarea { flex: 1; resize: none; padding: 6px 8px; background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px; font-size: 13px; font-family: inherit; margin: 0; min-height: 2.2em; max-height: 6em; } .ai-attach-btn { cursor: pointer; font-size: 18px; padding: 4px; margin: 0; line-height: 1; display: flex; align-items: center; } .ai-send-btn { background: var(--accent); color: var(--bg-primary); border: none; border-radius: 6px; padding: 6px 12px; font-size: 16px; cursor: pointer; line-height: 1; } .ai-send-btn:hover { opacity: 0.85; } .ai-send-btn:disabled { opacity: 0.4; cursor: default; } .ai-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 0; } .ai-attachments:empty { display: none; } .ai-attachment-chip { display: inline-flex; align-items: center; gap: 4px; background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 12px; padding: 2px 8px; font-size: 11px; color: var(--text-secondary); } .ai-attachment-chip .ai-chip-remove { cursor: pointer; font-size: 13px; color: var(--text-secondary); margin-left: 2px; } .ai-attachment-chip .ai-chip-remove:hover { color: #e55; } dialog h3 { margin-bottom: 16px; font-size: 16px; Loading editor/html/index.html +25 −27 Original line number Diff line number Diff line Loading @@ -243,35 +243,33 @@ </div> </dialog> <dialog id="ai-dialog"> <h3 data-i18n="I18N_AI">KI-Assistent</h3> <div class="set-field"> <label data-i18n="I18N_AI_PROMPT">Prompt</label> <textarea id="ai-prompt" rows="5" data-i18n-placeholder="I18N_AI_PROMPT_PLACEHOLDER" placeholder="Beschreibe was die KI generieren soll..."></textarea> </div> <div class="set-field" style="display:flex;gap:1em;align-items:center;flex-wrap:wrap"> <label style="display:flex;align-items:center;gap:0.3em"> <dialog id="ai-dialog" class="ai-panel"> <div class="ai-titlebar" id="ai-titlebar"> <span>🤖 <span data-i18n="I18N_AI">KI-Assistent</span></span> <div class="ai-titlebar-actions"> <button id="btn-ai-clear" title="Chat leeren">🗑</button> <button id="btn-ai-close" title="Schließen">✕</button> </div> </div> <div id="ai-chat-history" class="ai-chat-history"></div> <div class="ai-input-area"> <div style="display:flex;gap:0.8em;align-items:center;flex-wrap:wrap;font-size:0.8rem;padding:0.3em 0"> <label style="display:flex;align-items:center;gap:0.2em;margin:0"> <input type="checkbox" id="ai-desktop" checked> <span data-i18n="I18N_AI_DESKTOP">Desktop</span> </label> <label style="display:flex;align-items:center;gap:0.3em"> <label style="display:flex;align-items:center;gap:0.2em;margin:0"> <input type="checkbox" id="ai-mobile" checked> <span data-i18n="I18N_AI_MOBILE">Mobile</span> </label> <label style="display:flex;align-items:center;gap:0.3em"> <input type="checkbox" id="ai-include-doc"> <span data-i18n="I18N_AI_INCLUDE_DOC">Dokument anhängen</span> <label style="display:flex;align-items:center;gap:0.2em;margin:0"> <input type="checkbox" id="ai-include-doc"> <span data-i18n="I18N_AI_INCLUDE_DOC">Dokument</span> </label> </div> <div id="ai-result-container" style="display:none"> <label data-i18n="I18N_AI_RESULT">Ergebnis</label> <textarea id="ai-result" rows="10"></textarea> <div class="set-field" style="display:flex;gap:0.5em;flex-wrap:wrap"> <button id="btn-ai-import" class="set-save-btn" data-i18n="I18N_AI_IMPORT_HTML">Als HTML importieren</button> <button id="btn-ai-copy" class="set-save-btn" data-i18n="I18N_COPY">Kopieren</button> </div> <div id="ai-attachments" class="ai-attachments"></div> <div class="ai-prompt-row"> <textarea id="ai-prompt" rows="2" data-i18n-placeholder="I18N_AI_PROMPT_PLACEHOLDER" placeholder="Beschreibe was die KI generieren soll..."></textarea> <label class="ai-attach-btn" title="Datei anhängen">📎<input type="file" id="ai-file-input" style="display:none" multiple accept=".txt,.html,.css,.js,.json,.xml,.md,.csv,.svg"></label> <button id="btn-ai-send" class="ai-send-btn" title="Senden">➤</button> </div> <div id="ai-status" class="set-status"></div> <div class="dialog-actions"> <button id="btn-ai-send" class="set-save-btn" data-i18n="I18N_AI_SEND">An KI senden</button> <button id="btn-ai-close" data-i18n="Close">Schließen</button> </div> </dialog> Loading editor/html/js/editor.js +178 −43 Original line number Diff line number Diff line Loading @@ -174,11 +174,12 @@ }); document.getElementById('btn-ai').addEventListener('click', function() { document.getElementById('ai-prompt').value = ''; document.getElementById('ai-result').value = ''; document.getElementById('ai-result-container').style.display = 'none'; document.getElementById('ai-status').textContent = ''; document.getElementById('ai-dialog').showModal(); var dlg = document.getElementById('ai-dialog'); if (dlg.open) { dlg.close(); } else { dlg.show(); // non-modal } }); document.getElementById('btn-import-html').addEventListener('click', function() { Loading Loading @@ -572,45 +573,192 @@ // --- AI dialog --- function bindAiDialog() { var chatHistory = document.getElementById('ai-chat-history'); var promptEl = document.getElementById('ai-prompt'); var sendBtn = document.getElementById('btn-ai-send'); var fileInput = document.getElementById('ai-file-input'); var attachmentsDiv = document.getElementById('ai-attachments'); var pendingFiles = []; // {name, content} // Close document.getElementById('btn-ai-close').addEventListener('click', function() { document.getElementById('ai-dialog').close(); }); document.getElementById('btn-ai-send').addEventListener('click', function() { var prompt = document.getElementById('ai-prompt').value.trim(); if (!prompt) return; // Clear chat document.getElementById('btn-ai-clear').addEventListener('click', function() { chatHistory.innerHTML = ''; pendingFiles = []; attachmentsDiv.innerHTML = ''; }); var statusEl = document.getElementById('ai-status'); statusEl.textContent = I18n.t('I18N_AI_PROCESSING', 'KI verarbeitet...'); statusEl.className = 'set-status'; document.getElementById('ai-result-container').style.display = 'none'; // Drag support (function() { var titlebar = document.getElementById('ai-titlebar'); var dlg = document.getElementById('ai-dialog'); var dragging = false, startX, startY, origLeft, origTop; titlebar.addEventListener('mousedown', function(e) { if (e.target.tagName === 'BUTTON') return; dragging = true; startX = e.clientX; startY = e.clientY; var rect = dlg.getBoundingClientRect(); origLeft = rect.left; origTop = rect.top; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!dragging) return; var dx = e.clientX - startX; var dy = e.clientY - startY; dlg.style.left = (origLeft + dx) + 'px'; dlg.style.top = (origTop + dy) + 'px'; dlg.style.right = 'unset'; dlg.style.bottom = 'unset'; }); document.addEventListener('mouseup', function() { dragging = false; }); })(); // File attachment fileInput.addEventListener('change', function() { var files = fileInput.files; for (var i = 0; i < files.length; i++) { (function(file) { var reader = new FileReader(); reader.onload = function(e) { pendingFiles.push({name: file.name, content: e.target.result}); renderAttachments(); }; reader.readAsText(file); })(files[i]); } fileInput.value = ''; }); function renderAttachments() { attachmentsDiv.innerHTML = ''; for (var i = 0; i < pendingFiles.length; i++) { var chip = document.createElement('span'); chip.className = 'ai-attachment-chip'; chip.innerHTML = '📄 ' + pendingFiles[i].name + ' <span class="ai-chip-remove" data-idx="' + i + '">✕</span>'; attachmentsDiv.appendChild(chip); } } attachmentsDiv.addEventListener('click', function(e) { var remove = e.target.closest('.ai-chip-remove'); if (remove) { var idx = parseInt(remove.getAttribute('data-idx'), 10); pendingFiles.splice(idx, 1); renderAttachments(); } }); // Send with Enter (Shift+Enter for newline) promptEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendBtn.click(); } }); // Send prompt sendBtn.addEventListener('click', function() { var prompt = promptEl.value.trim(); if (!prompt && pendingFiles.length === 0) return; var desktop = document.getElementById('ai-desktop').checked; var mobile = document.getElementById('ai-mobile').checked; var includeDoc = document.getElementById('ai-include-doc').checked; EditorApi.aiPrompt(prompt, desktop, mobile, includeDoc).then(function(resp) { document.getElementById('ai-result').value = resp.result || ''; document.getElementById('ai-result-container').style.display = 'block'; statusEl.textContent = ''; // Build full prompt with file attachments var fullPrompt = prompt; for (var i = 0; i < pendingFiles.length; i++) { fullPrompt += '\n\n--- Datei: ' + pendingFiles[i].name + ' ---\n' + pendingFiles[i].content; } // Show user message var userLabel = prompt; if (pendingFiles.length > 0) { userLabel += '\n📄 ' + pendingFiles.map(function(f) { return f.name; }).join(', '); } addMessage('user', userLabel); // Show status var statusEl = addMessage('status', I18n.t('I18N_AI_PROCESSING', 'KI verarbeitet...')); promptEl.value = ''; pendingFiles = []; attachmentsDiv.innerHTML = ''; sendBtn.disabled = true; EditorApi.aiPrompt(fullPrompt, desktop, mobile, includeDoc).then(function(resp) { statusEl.remove(); var result = resp.result || ''; addAssistantMessage(result); sendBtn.disabled = false; }).catch(function(err) { statusEl.textContent = I18n.t('I18N_AI_FAILED', 'KI-Anfrage fehlgeschlagen') + ': ' + (err.error || ''); statusEl.className = 'set-status error'; statusEl.remove(); addMessage('error', I18n.t('I18N_AI_FAILED', 'KI-Anfrage fehlgeschlagen') + ': ' + (err.error || '')); sendBtn.disabled = false; }); }); document.getElementById('btn-ai-copy').addEventListener('click', function() { var textarea = document.getElementById('ai-result'); textarea.select(); document.execCommand('copy'); function addMessage(type, text) { var div = document.createElement('div'); div.className = 'ai-msg ' + type; div.textContent = text; chatHistory.appendChild(div); chatHistory.scrollTop = chatHistory.scrollHeight; return div; } function addAssistantMessage(text) { var div = document.createElement('div'); div.className = 'ai-msg assistant'; var content = document.createElement('div'); content.textContent = text; div.appendChild(content); var actions = document.createElement('div'); actions.className = 'ai-msg-actions'; var copyBtn = document.createElement('button'); copyBtn.textContent = I18n.t('I18N_COPY', 'Kopieren'); copyBtn.addEventListener('click', function() { navigator.clipboard.writeText(text).then(function() { copyBtn.textContent = '\u2713'; setTimeout(function() { copyBtn.textContent = I18n.t('I18N_COPY', 'Kopieren'); }, 1500); }); }); actions.appendChild(copyBtn); // Import AI result as HTML into widget tree document.getElementById('btn-ai-import').addEventListener('click', function() { var output = document.getElementById('ai-result').value; if (!output) return; var importBtn = document.createElement('button'); importBtn.textContent = I18n.t('I18N_AI_IMPORT_HTML', 'Als HTML importieren'); importBtn.addEventListener('click', function() { var htmlContent = extractHtml(text); var statusEl = addMessage('status', I18n.t('I18N_AI_PROCESSING', 'Importiere...')); EditorApi.importHtml(htmlContent).then(function() { statusEl.remove(); addMessage('status', '\u2713 ' + I18n.t('I18N_IMPORT_HTML', 'Importiert')); DocumentTree.clearSelection(); PropertiesPanel.clear(); refreshDocument(); }).catch(function(err) { statusEl.remove(); addMessage('error', I18n.t('I18N_IMPORT_FAILED', 'Import fehlgeschlagen') + ': ' + (err.error || '')); }); }); actions.appendChild(importBtn); div.appendChild(actions); chatHistory.appendChild(div); chatHistory.scrollTop = chatHistory.scrollHeight; return div; } // Extract HTML from markdown code blocks (```html ... ```) function extractHtml(output) { var htmlContent = output; var startIndex = output.indexOf('```html'); if (startIndex !== -1) { Loading @@ -621,7 +769,6 @@ htmlContent = output.substring(startIndex + 7).trim(); } } else { // Find first < tag var tagIndex = output.indexOf('<'); if (tagIndex !== -1) { htmlContent = output.substring(tagIndex).trim(); Loading @@ -631,20 +778,8 @@ } } } var statusEl = document.getElementById('ai-status'); statusEl.textContent = I18n.t('I18N_AI_PROCESSING', 'Importiere...'); EditorApi.importHtml(htmlContent).then(function() { document.getElementById('ai-dialog').close(); DocumentTree.clearSelection(); PropertiesPanel.clear(); refreshDocument(); }).catch(function(err) { statusEl.textContent = I18n.t('I18N_IMPORT_FAILED', 'Import fehlgeschlagen') + ': ' + (err.error || ''); statusEl.className = 'set-status error'; }); }); return htmlContent; } } // --- Connection Manager --- Loading Loading
editor/html/css/editor.css +165 −0 Original line number Diff line number Diff line Loading @@ -411,6 +411,171 @@ dialog::backdrop { background: rgba(0, 0, 0, 0.6); } /* AI Panel — non-modal, draggable */ dialog.ai-panel { position: fixed; padding: 0; min-width: 380px; max-width: 520px; width: 460px; height: 520px; display: flex; flex-direction: column; resize: both; overflow: hidden; z-index: 1000; margin: 0; inset: unset; right: 20px; bottom: 20px; } dialog.ai-panel::backdrop { background: none; pointer-events: none; } .ai-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: var(--bg-tertiary); border-bottom: 1px solid var(--border); cursor: grab; user-select: none; font-size: 13px; font-weight: 600; flex-shrink: 0; } .ai-titlebar:active { cursor: grabbing; } .ai-titlebar-actions { display: flex; gap: 4px; } .ai-titlebar-actions button { background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 14px; padding: 2px 6px; border-radius: 3px; } .ai-titlebar-actions button:hover { background: var(--bg-primary); color: var(--text-primary); } .ai-chat-history { flex: 1; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 10px; } .ai-msg { padding: 8px 10px; border-radius: 8px; font-size: 13px; line-height: 1.45; max-width: 92%; word-wrap: break-word; white-space: pre-wrap; } .ai-msg.user { background: var(--accent); color: var(--bg-primary); align-self: flex-end; border-bottom-right-radius: 2px; } .ai-msg.assistant { background: var(--bg-tertiary); color: var(--text-primary); align-self: flex-start; border-bottom-left-radius: 2px; } .ai-msg.assistant .ai-msg-actions { display: flex; gap: 6px; margin-top: 6px; } .ai-msg.assistant .ai-msg-actions button { background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); padding: 3px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; } .ai-msg.assistant .ai-msg-actions button:hover { color: var(--text-primary); background: var(--bg-primary); } .ai-msg.status { color: var(--text-secondary); font-size: 12px; font-style: italic; align-self: center; background: none; padding: 2px; } .ai-msg.error { color: #e55; font-size: 12px; align-self: center; background: none; padding: 2px; } .ai-input-area { border-top: 1px solid var(--border); padding: 8px 10px; flex-shrink: 0; background: var(--bg-secondary); } .ai-prompt-row { display: flex; gap: 6px; align-items: flex-end; } .ai-prompt-row textarea { flex: 1; resize: none; padding: 6px 8px; background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px; font-size: 13px; font-family: inherit; margin: 0; min-height: 2.2em; max-height: 6em; } .ai-attach-btn { cursor: pointer; font-size: 18px; padding: 4px; margin: 0; line-height: 1; display: flex; align-items: center; } .ai-send-btn { background: var(--accent); color: var(--bg-primary); border: none; border-radius: 6px; padding: 6px 12px; font-size: 16px; cursor: pointer; line-height: 1; } .ai-send-btn:hover { opacity: 0.85; } .ai-send-btn:disabled { opacity: 0.4; cursor: default; } .ai-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 0; } .ai-attachments:empty { display: none; } .ai-attachment-chip { display: inline-flex; align-items: center; gap: 4px; background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 12px; padding: 2px 8px; font-size: 11px; color: var(--text-secondary); } .ai-attachment-chip .ai-chip-remove { cursor: pointer; font-size: 13px; color: var(--text-secondary); margin-left: 2px; } .ai-attachment-chip .ai-chip-remove:hover { color: #e55; } dialog h3 { margin-bottom: 16px; font-size: 16px; Loading
editor/html/index.html +25 −27 Original line number Diff line number Diff line Loading @@ -243,35 +243,33 @@ </div> </dialog> <dialog id="ai-dialog"> <h3 data-i18n="I18N_AI">KI-Assistent</h3> <div class="set-field"> <label data-i18n="I18N_AI_PROMPT">Prompt</label> <textarea id="ai-prompt" rows="5" data-i18n-placeholder="I18N_AI_PROMPT_PLACEHOLDER" placeholder="Beschreibe was die KI generieren soll..."></textarea> </div> <div class="set-field" style="display:flex;gap:1em;align-items:center;flex-wrap:wrap"> <label style="display:flex;align-items:center;gap:0.3em"> <dialog id="ai-dialog" class="ai-panel"> <div class="ai-titlebar" id="ai-titlebar"> <span>🤖 <span data-i18n="I18N_AI">KI-Assistent</span></span> <div class="ai-titlebar-actions"> <button id="btn-ai-clear" title="Chat leeren">🗑</button> <button id="btn-ai-close" title="Schließen">✕</button> </div> </div> <div id="ai-chat-history" class="ai-chat-history"></div> <div class="ai-input-area"> <div style="display:flex;gap:0.8em;align-items:center;flex-wrap:wrap;font-size:0.8rem;padding:0.3em 0"> <label style="display:flex;align-items:center;gap:0.2em;margin:0"> <input type="checkbox" id="ai-desktop" checked> <span data-i18n="I18N_AI_DESKTOP">Desktop</span> </label> <label style="display:flex;align-items:center;gap:0.3em"> <label style="display:flex;align-items:center;gap:0.2em;margin:0"> <input type="checkbox" id="ai-mobile" checked> <span data-i18n="I18N_AI_MOBILE">Mobile</span> </label> <label style="display:flex;align-items:center;gap:0.3em"> <input type="checkbox" id="ai-include-doc"> <span data-i18n="I18N_AI_INCLUDE_DOC">Dokument anhängen</span> <label style="display:flex;align-items:center;gap:0.2em;margin:0"> <input type="checkbox" id="ai-include-doc"> <span data-i18n="I18N_AI_INCLUDE_DOC">Dokument</span> </label> </div> <div id="ai-result-container" style="display:none"> <label data-i18n="I18N_AI_RESULT">Ergebnis</label> <textarea id="ai-result" rows="10"></textarea> <div class="set-field" style="display:flex;gap:0.5em;flex-wrap:wrap"> <button id="btn-ai-import" class="set-save-btn" data-i18n="I18N_AI_IMPORT_HTML">Als HTML importieren</button> <button id="btn-ai-copy" class="set-save-btn" data-i18n="I18N_COPY">Kopieren</button> </div> <div id="ai-attachments" class="ai-attachments"></div> <div class="ai-prompt-row"> <textarea id="ai-prompt" rows="2" data-i18n-placeholder="I18N_AI_PROMPT_PLACEHOLDER" placeholder="Beschreibe was die KI generieren soll..."></textarea> <label class="ai-attach-btn" title="Datei anhängen">📎<input type="file" id="ai-file-input" style="display:none" multiple accept=".txt,.html,.css,.js,.json,.xml,.md,.csv,.svg"></label> <button id="btn-ai-send" class="ai-send-btn" title="Senden">➤</button> </div> <div id="ai-status" class="set-status"></div> <div class="dialog-actions"> <button id="btn-ai-send" class="set-save-btn" data-i18n="I18N_AI_SEND">An KI senden</button> <button id="btn-ai-close" data-i18n="Close">Schließen</button> </div> </dialog> Loading
editor/html/js/editor.js +178 −43 Original line number Diff line number Diff line Loading @@ -174,11 +174,12 @@ }); document.getElementById('btn-ai').addEventListener('click', function() { document.getElementById('ai-prompt').value = ''; document.getElementById('ai-result').value = ''; document.getElementById('ai-result-container').style.display = 'none'; document.getElementById('ai-status').textContent = ''; document.getElementById('ai-dialog').showModal(); var dlg = document.getElementById('ai-dialog'); if (dlg.open) { dlg.close(); } else { dlg.show(); // non-modal } }); document.getElementById('btn-import-html').addEventListener('click', function() { Loading Loading @@ -572,45 +573,192 @@ // --- AI dialog --- function bindAiDialog() { var chatHistory = document.getElementById('ai-chat-history'); var promptEl = document.getElementById('ai-prompt'); var sendBtn = document.getElementById('btn-ai-send'); var fileInput = document.getElementById('ai-file-input'); var attachmentsDiv = document.getElementById('ai-attachments'); var pendingFiles = []; // {name, content} // Close document.getElementById('btn-ai-close').addEventListener('click', function() { document.getElementById('ai-dialog').close(); }); document.getElementById('btn-ai-send').addEventListener('click', function() { var prompt = document.getElementById('ai-prompt').value.trim(); if (!prompt) return; // Clear chat document.getElementById('btn-ai-clear').addEventListener('click', function() { chatHistory.innerHTML = ''; pendingFiles = []; attachmentsDiv.innerHTML = ''; }); var statusEl = document.getElementById('ai-status'); statusEl.textContent = I18n.t('I18N_AI_PROCESSING', 'KI verarbeitet...'); statusEl.className = 'set-status'; document.getElementById('ai-result-container').style.display = 'none'; // Drag support (function() { var titlebar = document.getElementById('ai-titlebar'); var dlg = document.getElementById('ai-dialog'); var dragging = false, startX, startY, origLeft, origTop; titlebar.addEventListener('mousedown', function(e) { if (e.target.tagName === 'BUTTON') return; dragging = true; startX = e.clientX; startY = e.clientY; var rect = dlg.getBoundingClientRect(); origLeft = rect.left; origTop = rect.top; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!dragging) return; var dx = e.clientX - startX; var dy = e.clientY - startY; dlg.style.left = (origLeft + dx) + 'px'; dlg.style.top = (origTop + dy) + 'px'; dlg.style.right = 'unset'; dlg.style.bottom = 'unset'; }); document.addEventListener('mouseup', function() { dragging = false; }); })(); // File attachment fileInput.addEventListener('change', function() { var files = fileInput.files; for (var i = 0; i < files.length; i++) { (function(file) { var reader = new FileReader(); reader.onload = function(e) { pendingFiles.push({name: file.name, content: e.target.result}); renderAttachments(); }; reader.readAsText(file); })(files[i]); } fileInput.value = ''; }); function renderAttachments() { attachmentsDiv.innerHTML = ''; for (var i = 0; i < pendingFiles.length; i++) { var chip = document.createElement('span'); chip.className = 'ai-attachment-chip'; chip.innerHTML = '📄 ' + pendingFiles[i].name + ' <span class="ai-chip-remove" data-idx="' + i + '">✕</span>'; attachmentsDiv.appendChild(chip); } } attachmentsDiv.addEventListener('click', function(e) { var remove = e.target.closest('.ai-chip-remove'); if (remove) { var idx = parseInt(remove.getAttribute('data-idx'), 10); pendingFiles.splice(idx, 1); renderAttachments(); } }); // Send with Enter (Shift+Enter for newline) promptEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendBtn.click(); } }); // Send prompt sendBtn.addEventListener('click', function() { var prompt = promptEl.value.trim(); if (!prompt && pendingFiles.length === 0) return; var desktop = document.getElementById('ai-desktop').checked; var mobile = document.getElementById('ai-mobile').checked; var includeDoc = document.getElementById('ai-include-doc').checked; EditorApi.aiPrompt(prompt, desktop, mobile, includeDoc).then(function(resp) { document.getElementById('ai-result').value = resp.result || ''; document.getElementById('ai-result-container').style.display = 'block'; statusEl.textContent = ''; // Build full prompt with file attachments var fullPrompt = prompt; for (var i = 0; i < pendingFiles.length; i++) { fullPrompt += '\n\n--- Datei: ' + pendingFiles[i].name + ' ---\n' + pendingFiles[i].content; } // Show user message var userLabel = prompt; if (pendingFiles.length > 0) { userLabel += '\n📄 ' + pendingFiles.map(function(f) { return f.name; }).join(', '); } addMessage('user', userLabel); // Show status var statusEl = addMessage('status', I18n.t('I18N_AI_PROCESSING', 'KI verarbeitet...')); promptEl.value = ''; pendingFiles = []; attachmentsDiv.innerHTML = ''; sendBtn.disabled = true; EditorApi.aiPrompt(fullPrompt, desktop, mobile, includeDoc).then(function(resp) { statusEl.remove(); var result = resp.result || ''; addAssistantMessage(result); sendBtn.disabled = false; }).catch(function(err) { statusEl.textContent = I18n.t('I18N_AI_FAILED', 'KI-Anfrage fehlgeschlagen') + ': ' + (err.error || ''); statusEl.className = 'set-status error'; statusEl.remove(); addMessage('error', I18n.t('I18N_AI_FAILED', 'KI-Anfrage fehlgeschlagen') + ': ' + (err.error || '')); sendBtn.disabled = false; }); }); document.getElementById('btn-ai-copy').addEventListener('click', function() { var textarea = document.getElementById('ai-result'); textarea.select(); document.execCommand('copy'); function addMessage(type, text) { var div = document.createElement('div'); div.className = 'ai-msg ' + type; div.textContent = text; chatHistory.appendChild(div); chatHistory.scrollTop = chatHistory.scrollHeight; return div; } function addAssistantMessage(text) { var div = document.createElement('div'); div.className = 'ai-msg assistant'; var content = document.createElement('div'); content.textContent = text; div.appendChild(content); var actions = document.createElement('div'); actions.className = 'ai-msg-actions'; var copyBtn = document.createElement('button'); copyBtn.textContent = I18n.t('I18N_COPY', 'Kopieren'); copyBtn.addEventListener('click', function() { navigator.clipboard.writeText(text).then(function() { copyBtn.textContent = '\u2713'; setTimeout(function() { copyBtn.textContent = I18n.t('I18N_COPY', 'Kopieren'); }, 1500); }); }); actions.appendChild(copyBtn); // Import AI result as HTML into widget tree document.getElementById('btn-ai-import').addEventListener('click', function() { var output = document.getElementById('ai-result').value; if (!output) return; var importBtn = document.createElement('button'); importBtn.textContent = I18n.t('I18N_AI_IMPORT_HTML', 'Als HTML importieren'); importBtn.addEventListener('click', function() { var htmlContent = extractHtml(text); var statusEl = addMessage('status', I18n.t('I18N_AI_PROCESSING', 'Importiere...')); EditorApi.importHtml(htmlContent).then(function() { statusEl.remove(); addMessage('status', '\u2713 ' + I18n.t('I18N_IMPORT_HTML', 'Importiert')); DocumentTree.clearSelection(); PropertiesPanel.clear(); refreshDocument(); }).catch(function(err) { statusEl.remove(); addMessage('error', I18n.t('I18N_IMPORT_FAILED', 'Import fehlgeschlagen') + ': ' + (err.error || '')); }); }); actions.appendChild(importBtn); div.appendChild(actions); chatHistory.appendChild(div); chatHistory.scrollTop = chatHistory.scrollHeight; return div; } // Extract HTML from markdown code blocks (```html ... ```) function extractHtml(output) { var htmlContent = output; var startIndex = output.indexOf('```html'); if (startIndex !== -1) { Loading @@ -621,7 +769,6 @@ htmlContent = output.substring(startIndex + 7).trim(); } } else { // Find first < tag var tagIndex = output.indexOf('<'); if (tagIndex !== -1) { htmlContent = output.substring(tagIndex).trim(); Loading @@ -631,20 +778,8 @@ } } } var statusEl = document.getElementById('ai-status'); statusEl.textContent = I18n.t('I18N_AI_PROCESSING', 'Importiere...'); EditorApi.importHtml(htmlContent).then(function() { document.getElementById('ai-dialog').close(); DocumentTree.clearSelection(); PropertiesPanel.clear(); refreshDocument(); }).catch(function(err) { statusEl.textContent = I18n.t('I18N_IMPORT_FAILED', 'Import fehlgeschlagen') + ': ' + (err.error || ''); statusEl.className = 'set-status error'; }); }); return htmlContent; } } // --- Connection Manager --- Loading