// "Ausflugsziel übersetzen (KI)"-Modal: lässt Nutzer ein bestehendes // Ausflugsziel und eine Zielsprache (Englisch/Spanisch/Französisch) // auswählen und erzeugt daraus per EventMap.ai.buildTranslationPrompt() // (js/ai/translatePromptBuilder.js) einen fertigen, kopierbaren // Übersetzungs-Prompt für eine externe KI. // // Aufbau/Muster bewusst analog zu js/ui/promptGenerator.js (gleiche // Modal-Chrome-Klassen aus css/promptGenerator.css, gleicher Copy/Download- // Ablauf). Rendering ausschließlich über textContent/DOM-APIs (el()). // // WICHTIG: Diese Datei erzeugt nur den Prompt. Die JSON-Antwort der KI (mit // dem übersetzten "translations"-Feld) muss aktuell noch manuell in die // Ortsdaten übernommen werden - ein automatischer Import/Merge ist (noch) // nicht Teil dieser Datei. (function () { const { el, clearChildren } = EventMap.utils; const TARGET_LANGUAGE_OPTIONS = [ { value: 'en', label: 'Englisch' }, { value: 'es', label: 'Spanisch' }, { value: 'fr', label: 'Französisch' }, ]; let container = null; let refs = null; function field(id, labelText, inputNode) { inputNode.id = id; return el('div', { className: 'form-field' }, [ el('label', { className: 'form-field__label', attrs: { for: id } }, [labelText]), inputNode, ]); } function buildPlaceOptions() { const { places } = EventMap.store.getState(); const sorted = [...places].sort((a, b) => (a.name || '').localeCompare(b.name || '', 'de')); if (sorted.length === 0) { return [el('option', { attrs: { value: '' }, text: '(Keine Ausflugsziele vorhanden)' })]; } return sorted.map((place) => el('option', { attrs: { value: place.id }, text: place.city ? `${place.name} (${place.city})` : place.name, }) ); } function buildSkeleton(rootContainer) { refs = {}; refs.backdrop = el('div', { className: 'prompt-generator-modal__backdrop', on: { click: () => closeTranslatePromptGenerator() }, }); refs.closeBtn = el( 'button', { className: 'prompt-generator-modal__close', attrs: { type: 'button', 'aria-label': 'Übersetzungs-Prompt-Generator schließen' }, on: { click: () => closeTranslatePromptGenerator() }, }, ['✕'] ); refs.title = el('h2', { className: 'prompt-generator__title', attrs: { id: 'translate-prompt-generator-title' }, text: '🌐 Ausflugsziel übersetzen (KI)', }); refs.intro = el('p', { className: 'prompt-generator__intro', text: 'Wähle ein bestehendes Ausflugsziel und eine Zielsprache aus, um einen fertigen Prompt für eine ' + 'externe KI (z.B. ChatGPT, Claude, Gemini) zu erzeugen. Der Prompt bittet die KI, Name, Kurz- und ' + 'ausführliche Beschreibung in die gewählte Sprache zu übersetzen.', }); refs.placeSelect = el('select', { on: { change: updateOutput } }, buildPlaceOptions()); refs.languageSelect = el( 'select', { on: { change: updateOutput } }, TARGET_LANGUAGE_OPTIONS.map((opt) => el('option', { attrs: { value: opt.value }, text: opt.label })) ); const form = el('div', { className: 'place-form prompt-generator__form' }, [ el('fieldset', { className: 'place-form__section' }, [ el('legend', { className: 'place-form__section-label', text: 'Auswahl' }), field('tpg-place', 'Ausflugsziel', refs.placeSelect), field('tpg-language', 'Zielsprache', refs.languageSelect), ]), ]); refs.output = el('textarea', { className: 'prompt-generator__output-textarea', attrs: { readonly: '', id: 'translate-prompt-generator-output', rows: '16' }, }); refs.copyBtn = el( 'button', { className: 'btn btn--primary', attrs: { type: 'button' }, on: { click: handleCopyClick } }, ['In Zwischenablage kopieren'] ); refs.downloadBtn = el( 'button', { className: 'btn btn--secondary', attrs: { type: 'button' }, on: { click: handleDownloadClick } }, ['Als Textdatei speichern'] ); refs.copyStatus = el('p', { className: 'prompt-generator__status', attrs: { role: 'status' } }); const outputSection = el('div', { className: 'prompt-generator__output' }, [ el('h3', { className: 'place-form__section-label', text: 'Generierter Prompt' }), refs.output, el('div', { className: 'prompt-generator__output-actions' }, [refs.copyBtn, refs.downloadBtn, refs.copyStatus]), el('div', { className: 'prompt-generator__instructions' }, [ el('h3', { className: 'place-form__section-label', text: 'Anleitung' }), el('ol', { className: 'prompt-generator__instructions-list' }, [ el('li', { text: 'Ausflugsziel und Zielsprache auswählen.' }), el('li', { text: 'Prompt kopieren oder als Textdatei speichern.' }), el('li', { text: 'In eine KI (z.B. ChatGPT, Claude, Gemini) einfügen und die Antwort abwarten.', }), el('li', { text: 'Die JSON-Antwort enthält das Feld "translations" - dieses aktuell noch manuell in die ' + 'gespeicherten Ortsdaten übernehmen (automatischer Import folgt ggf. später).', }), ]), ]), ]); const layout = el('div', { className: 'prompt-generator__layout' }, [form, outputSection]); refs.panel = el( 'div', { className: 'prompt-generator-modal__panel', attrs: { role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'translate-prompt-generator-title' }, }, [refs.closeBtn, refs.title, refs.intro, layout] ); rootContainer.appendChild(refs.backdrop); rootContainer.appendChild(refs.panel); } /** Berechnet den Prompt live neu. Ohne auswählbares Ausflugsziel wird stattdessen ein Hinweis angezeigt. */ function updateOutput() { const { places } = EventMap.store.getState(); const place = places.find((p) => p.id === refs.placeSelect.value); if (!place) { refs.output.value = '(Bitte zuerst ein Ausflugsziel anlegen, um einen Übersetzungs-Prompt zu erzeugen.)'; refs.copyBtn.disabled = true; refs.downloadBtn.disabled = true; return; } refs.output.value = EventMap.ai.buildTranslationPrompt(place, refs.languageSelect.value); refs.copyBtn.disabled = false; refs.downloadBtn.disabled = false; } function resetCopyStatus() { refs.copyStatus.textContent = ''; } async function handleCopyClick() { const success = await EventMap.utils.copyToClipboard(refs.output.value); const defaultLabel = 'In Zwischenablage kopieren'; if (success) { refs.copyBtn.textContent = 'Kopiert!'; refs.copyStatus.textContent = ''; } else { refs.copyBtn.textContent = defaultLabel; refs.copyStatus.textContent = 'Kopieren war nicht möglich. Bitte den Text im Feld oben manuell markieren und kopieren.'; } setTimeout(() => { refs.copyBtn.textContent = defaultLabel; }, 2000); } function handleDownloadClick() { EventMap.utils.downloadTextFile('eventmap-uebersetzung-prompt.txt', refs.output.value); } /** Baut die Ortsauswahl neu auf (z.B. falls seit dem letzten Öffnen Orte hinzugekommen sind), behält die Auswahl falls noch gültig. */ function refreshPlaceOptions() { const currentValue = refs.placeSelect.value; clearChildren(refs.placeSelect); buildPlaceOptions().forEach((option) => refs.placeSelect.appendChild(option)); if ([...refs.placeSelect.options].some((opt) => opt.value === currentValue)) { refs.placeSelect.value = currentValue; } } /** Öffnet den Übersetzungs-Prompt-Generator. */ function openTranslatePromptGenerator() { if (!container || !refs) { console.error( 'EventMap: Übersetzungs-Prompt-Generator wurde noch nicht initialisiert (initTranslatePromptGenerator()).' ); return; } refreshPlaceOptions(); resetCopyStatus(); updateOutput(); container.hidden = false; document.body.classList.add('prompt-generator-modal-open'); refs.placeSelect.focus(); } function closeTranslatePromptGenerator() { if (!container || container.hidden) return; container.hidden = true; document.body.classList.remove('prompt-generator-modal-open'); } /** Initialisiert das Übersetzungs-Prompt-Generator-Modal im Element mit der übergebenen id. */ function initTranslatePromptGenerator(containerId) { const containerEl = document.getElementById(containerId); if (!containerEl) { console.error(`EventMap: Übersetzungs-Prompt-Generator-Container #${containerId} nicht gefunden.`); return; } container = containerEl; clearChildren(container); buildSkeleton(container); document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && !container.hidden) { closeTranslatePromptGenerator(); } }); } EventMap.ui.initTranslatePromptGenerator = initTranslatePromptGenerator; EventMap.ui.openTranslatePromptGenerator = openTranslatePromptGenerator; })();