Commit 5f852d45 authored by jan.koester's avatar jan.koester
Browse files

test

parent 34f8dd71
Loading
Loading
Loading
Loading
+165 −0
Original line number Diff line number Diff line
@@ -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;
+25 −27
Original line number Diff line number Diff line
@@ -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>&#x1F916; <span data-i18n="I18N_AI">KI-Assistent</span></span>
            <div class="ai-titlebar-actions">
                <button id="btn-ai-clear" title="Chat leeren">&#x1F5D1;</button>
                <button id="btn-ai-close" title="Schlie&szlig;en">&#x2715;</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&auml;ngen">&#x1F4CE;<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">&#x27A4;</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>

+178 −43
Original line number Diff line number Diff line
@@ -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() {
@@ -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 = '&#x1F4C4; ' + pendingFiles[i].name +
                    ' <span class="ai-chip-remove" data-idx="' + i + '">&#x2715;</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) {
@@ -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();
@@ -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 ---