(function () { 'use strict'; let promptData = { personal: {}, default: {}, can_edit_defaults: false }; let currentKind = null; let currentPrompt = null; let currentVersions = []; let versionIndex = 0; let uiText = { prompt_not_found: 'Prompt not found', no_previous_versions: 'No previous versions stored.', no_lines_for_view: 'No lines for this view.', restore_version_confirm: 'Restore version', delete_version_confirm: 'Delete version', default_prompt_prefix: 'Default prompt: ', prompt_prefix: 'Prompt: ', created: 'created', updated: 'updated', default_prompt: 'default prompt', version: 'version', showing_version: 'showing version', of: 'of', old: 'old', new: 'new' }; function byId(id) { return document.getElementById(id); } function readPromptData() { const el = byId('promptDataJson'); const textEl = byId('promptTextJson'); if (!el) { return; } try { promptData = JSON.parse(el.textContent || '{}'); } catch (e) { console.error('Could not parse prompt data JSON', e); promptData = { personal: {}, default: {} }; } if (!textEl) { return; } try { uiText = Object.assign(uiText, JSON.parse(textEl.textContent || '{}')); } catch (e) { console.error('Could not parse prompt text JSON', e); } } function esc(s) { return String(s) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function fmtTs(ts) { if (!ts) { return '-'; } const d = new Date(ts * 1000); return d.toISOString().replace('T', ' ').slice(0, 19); } function promptActionUrl(kind) { const url = new URL(window.location.href); url.pathname = '/prompts'; url.searchParams.set('mode', kind === 'default' ? 'defaults' : 'personal'); return url.pathname + url.search; } function setEditing(editing) { const shell = byId('promptModalForm'); const mayEdit = currentKind !== 'default' || promptData.can_edit_defaults; editing = editing && mayEdit; shell.classList.toggle('edit-mode', editing); shell.classList.toggle('can-edit', mayEdit); shell.classList.toggle('read-only-default', currentKind === 'default' && !mayEdit); byId('modalName').readOnly = !editing; byId('modalContent').readOnly = !editing; byId('modalDefaultKey').readOnly = !editing; byId('modalLanguage').disabled = !editing; } function openEditModal() { if (!currentPrompt) { return; } if (currentKind === 'default' && !promptData.can_edit_defaults) { return; } setEditing(true); byId('promptModalBackdrop').classList.add('open'); renderVersionPane(); } function closeEditModal() { byId('promptModalBackdrop').classList.remove('open'); if (currentPrompt) { resetEditorFields(); renderVersionPane(); } } function resetEditorFields() { byId('modalName').value = currentPrompt.name; byId('modalLanguage').value = currentPrompt.language; byId('modalContent').value = currentPrompt.content; byId('modalDefaultKey').value = currentPrompt.default_key || ''; setEditing(false); } function openPromptEditor(kind, id) { currentKind = kind; currentPrompt = promptData[kind] ? promptData[kind][id] : null; if (!currentPrompt) { alert(uiText.prompt_not_found); return; } currentVersions = currentPrompt.versions || []; currentVersions.sort(function (a, b) { return b.version_no - a.version_no; }); /* * The newest stored version is often the current snapshot. If possible, * start by showing the one before that. */ versionIndex = currentVersions.length > 1 ? 1 : 0; byId('modalTitle').textContent = (kind === 'default' ? uiText.default_prompt_prefix : uiText.prompt_prefix) + currentPrompt.name; byId('viewerTitle').textContent = (kind === 'default' ? uiText.default_prompt_prefix : uiText.prompt_prefix) + currentPrompt.name; byId('modalMeta').textContent = uiText.created + ' ' + fmtTs(currentPrompt.created_at) + ' · ' + uiText.updated + ' ' + fmtTs(currentPrompt.updated_at) + (kind === 'default' && !promptData.can_edit_defaults ? ' · ' + uiText.default_prompt : ''); byId('viewerMeta').textContent = byId('modalMeta').textContent; byId('viewerContent').textContent = currentPrompt.content; byId('defaultKeyRow').style.display = kind === 'default' ? 'block' : 'none'; if (kind === 'default') { byId('modalAction').value = 'update_default'; byId('modalDefaultId').value = currentPrompt.id; byId('modalPromptId').value = ''; } else { byId('modalAction').value = 'update_prompt'; byId('modalPromptId').value = currentPrompt.id; byId('modalDefaultId').value = ''; } byId('promptModalForm').action = promptActionUrl(kind); byId('modalAuxForm').action = promptActionUrl(kind); byId('promptViewer').classList.remove('is-empty'); byId('promptViewer').classList.toggle('is-default-prompt', kind === 'default'); byId('promptModalBackdrop').classList.toggle('is-default-prompt', kind === 'default'); byId('editPromptButton').style.display = kind === 'default' && !promptData.can_edit_defaults ? 'none' : ''; document.querySelectorAll('.prompt-select').forEach(function (button) { button.classList.toggle( 'selected', button.dataset.kind === kind && button.dataset.id === String(id) ); }); resetEditorFields(); renderVersionPane(); } function currentSelectedVersion() { if (currentVersions.length === 0) { return null; } if (versionIndex < 0) { versionIndex = 0; } if (versionIndex >= currentVersions.length) { versionIndex = currentVersions.length - 1; } return currentVersions[versionIndex]; } function olderVersion() { if (currentVersions.length === 0) { return; } if (versionIndex < currentVersions.length - 1) { versionIndex++; renderVersionPane(); } } function newerVersion() { if (currentVersions.length === 0) { return; } const minIndex = currentVersions.length > 1 ? 1 : 0; if (versionIndex > minIndex) { versionIndex--; renderVersionPane(); } } function renderVersionPane() { const version = currentSelectedVersion(); const contentEl = byId('versionContent'); const metaEl = byId('selectedVersionMeta'); const indicatorEl = byId('versionIndicator'); if (!version) { contentEl.innerHTML = '' + esc(uiText.no_previous_versions) + ''; metaEl.textContent = ''; indicatorEl.textContent = ''; return; } metaEl.textContent = uiText.version + ' ' + version.version_no + ' · ' + fmtTs(version.created_at) + (version.note ? ' · ' + version.note : ''); indicatorEl.textContent = uiText.showing_version + ' ' + version.version_no + ' (' + (versionIndex + 1) + ' ' + uiText.of + ' ' + currentVersions.length + ')'; const currentText = byId('modalContent').value; const oldText = version.content; const mode = byId('diffMode').value; if (mode === 'plain') { contentEl.innerHTML = esc(oldText); return; } contentEl.innerHTML = renderLineDiff(oldText, currentText, mode); } function linesOf(s) { return String(s).split(/\r?\n/); } function lcsTable(a, b) { const m = a.length; const n = b.length; const dp = Array.from({ length: m + 1 }, function () { return Array(n + 1).fill(0); }); for (let i = m - 1; i >= 0; i--) { for (let j = n - 1; j >= 0; j--) { if (a[i] === b[j]) { dp[i][j] = dp[i + 1][j + 1] + 1; } else { dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]); } } } return dp; } function diffOps(oldLines, newLines) { const dp = lcsTable(oldLines, newLines); const ops = []; let i = 0; let j = 0; while (i < oldLines.length && j < newLines.length) { if (oldLines[i] === newLines[j]) { ops.push({ type: 'same', oldLine: oldLines[i], newLine: newLines[j] }); i++; j++; } else if (dp[i + 1][j] >= dp[i][j + 1]) { ops.push({ type: 'deleted', oldLine: oldLines[i] }); i++; } else { ops.push({ type: 'added', newLine: newLines[j] }); j++; } } while (i < oldLines.length) { ops.push({ type: 'deleted', oldLine: oldLines[i] }); i++; } while (j < newLines.length) { ops.push({ type: 'added', newLine: newLines[j] }); j++; } return pairChangedLines(ops); } function pairChangedLines(ops) { const paired = []; for (let k = 0; k < ops.length; k++) { const a = ops[k]; const b = ops[k + 1]; if (a && b && a.type === 'deleted' && b.type === 'added') { paired.push({ type: 'changed', oldLine: a.oldLine, newLine: b.newLine }); k++; } else if (a && b && a.type === 'added' && b.type === 'deleted') { paired.push({ type: 'changed', oldLine: b.oldLine, newLine: a.newLine }); k++; } else { paired.push(a); } } return paired; } function renderLineDiff(oldText, newText, mode) { const ops = diffOps(linesOf(oldText), linesOf(newText)); const out = []; for (const op of ops) { if (mode !== 'all' && mode !== op.type) { continue; } if (op.type === 'same') { out.push('' + esc(op.oldLine) + ''); } else if (op.type === 'added') { out.push('+ ' + esc(op.newLine) + ''); } else if (op.type === 'deleted') { out.push('- ' + esc(op.oldLine) + ''); } else if (op.type === 'changed') { out.push( '~ ' + esc(uiText.old) + ': ' + esc(op.oldLine) + '' ); out.push( '~ ' + esc(uiText.new) + ': ' + esc(op.newLine) + '' ); } } if (out.length === 0) { return '' + esc(uiText.no_lines_for_view) + ''; } return out.join(''); } function configureAux(action, versionNo) { const aux = byId('modalAuxForm'); byId('auxAction').value = action; byId('auxVersionNo').value = versionNo || ''; byId('auxVersionNote').value = 'modal snapshot'; if (currentKind === 'default') { byId('auxDefaultId').value = currentPrompt.id; byId('auxPromptId').value = ''; } else { byId('auxPromptId').value = currentPrompt.id; byId('auxDefaultId').value = ''; } return aux; } function storeSnapshot() { if (!currentPrompt) { return; } const action = currentKind === 'default' ? 'create_default_version' : 'create_version'; configureAux(action, '').submit(); } function restoreSelectedVersion() { const version = currentSelectedVersion(); if (!currentPrompt || !version) { return; } if (!confirm(uiText.restore_version_confirm + ' ' + version.version_no + '?')) { return; } const action = currentKind === 'default' ? 'restore_default_version' : 'restore_version'; configureAux(action, version.version_no).submit(); } function deleteSelectedVersion() { const version = currentSelectedVersion(); if (!currentPrompt || !version) { return; } if (!confirm(uiText.delete_version_confirm + ' ' + version.version_no + '?')) { return; } const action = currentKind === 'default' ? 'delete_default_version' : 'delete_version'; configureAux(action, version.version_no).submit(); } function bindEvents() { document.querySelectorAll('.prompt-tab').forEach(function (tab) { tab.addEventListener('click', function () { const tabName = tab.dataset.tab; document.querySelectorAll('.prompt-tab').forEach(function (other) { const active = other === tab; other.classList.toggle('active', active); other.setAttribute('aria-selected', active ? 'true' : 'false'); }); document.querySelectorAll('.prompt-tab-panel').forEach(function (panel) { panel.classList.toggle('active', panel.id === 'tab-' + tabName); }); }); }); document.querySelectorAll('.js-open-prompt').forEach(function (button) { button.addEventListener('click', function () { openPromptEditor(button.dataset.kind, button.dataset.id); }); }); byId('editPromptButton').addEventListener('click', function () { openEditModal(); }); byId('cancelEditButton').addEventListener('click', function () { closeEditModal(); }); byId('versionNewerButton').addEventListener('click', newerVersion); byId('versionOlderButton').addEventListener('click', olderVersion); byId('diffMode').addEventListener('change', renderVersionPane); byId('modalContent').addEventListener('input', renderVersionPane); byId('snapshotButton').addEventListener('click', storeSnapshot); byId('restoreVersionButton').addEventListener('click', restoreSelectedVersion); byId('deleteVersionButton').addEventListener('click', deleteSelectedVersion); byId('promptModalBackdrop').addEventListener('click', function (ev) { if (ev.target === byId('promptModalBackdrop')) { closeEditModal(); } }); document.addEventListener('keydown', function (ev) { if (ev.key === 'Escape') { closeEditModal(); } }); setEditing(false); } document.addEventListener('DOMContentLoaded', function () { readPromptData(); bindEvents(); }); }());