diff --git a/static/script.js b/static/script.js index dfccdde..157c895 100644 --- a/static/script.js +++ b/static/script.js @@ -74,6 +74,8 @@ document.addEventListener('DOMContentLoaded', () => { const generateBtn = document.getElementById('generate-btn'); const promptInput = document.getElementById('prompt'); const promptNoteInput = document.getElementById('prompt-note'); + const promptHighlight = document.getElementById('prompt-highlight'); + const promptPlaceholderText = promptInput?.getAttribute('placeholder') || ''; const aspectRatioInput = document.getElementById('aspect-ratio'); const resolutionInput = document.getElementById('resolution'); const apiKeyInput = document.getElementById('api-key'); @@ -197,6 +199,59 @@ document.addEventListener('DOMContentLoaded', () => { return formData; } + const promptHighlightColors = [ + '#34d399', // green + '#f97316', // orange + '#facc15', // yellow + '#38bdf8', // blue + '#fb7185', // pink + '#a855f7', // purple + ]; + + function escapeHtml(value) { + return value.replace(/[&<>"']/g, (char) => { + switch (char) { + case '&': return '&'; + case '<': return '<'; + case '>': return '>'; + case '"': return '"'; + case "'": return '''; + default: return char; + } + }); + } + + function buildPromptHighlightHtml(value) { + if (!promptHighlight) return ''; + if (!value) { + return `${escapeHtml(promptPlaceholderText)}`; + } + + const placeholderRegex = /(\{[^{}]*\}|\[[^\[\]]*\])/g; + let lastIndex = 0; + let match; + let colorIndex = 0; + let html = ''; + + while ((match = placeholderRegex.exec(value)) !== null) { + html += escapeHtml(value.slice(lastIndex, match.index)); + const color = promptHighlightColors[colorIndex % promptHighlightColors.length]; + html += `${escapeHtml(match[0])}`; + lastIndex = match.index + match[0].length; + colorIndex++; + } + + html += escapeHtml(value.slice(lastIndex)); + return html || `${escapeHtml(promptPlaceholderText)}`; + } + + function refreshPromptHighlight() { + if (!promptHighlight || !promptInput) return; + promptHighlight.innerHTML = buildPromptHighlightHtml(promptInput.value); + promptHighlight.scrollTop = promptInput.scrollTop; + promptHighlight.scrollLeft = promptInput.scrollLeft; + } + // --- End Helper Functions --- let zoomLevel = 1; @@ -216,6 +271,7 @@ document.addEventListener('DOMContentLoaded', () => { if (template.prompt) { promptInput.value = i18n.getText(template.prompt); persistSettings(); + refreshPromptHighlight(); } // Stay in template gallery view - don't auto-switch // User will switch view by selecting image from history or generating @@ -271,6 +327,7 @@ document.addEventListener('DOMContentLoaded', () => { const savedSettings = loadSettings(); slotManager.initialize(savedSettings.referenceImages || []); + refreshPromptHighlight(); apiKeyInput.addEventListener('input', persistSettings); let isApiKeyVisible = false; @@ -293,7 +350,15 @@ document.addEventListener('DOMContentLoaded', () => { }); } refreshApiKeyVisibility(); - promptInput.addEventListener('input', persistSettings); + promptInput.addEventListener('input', () => { + refreshPromptHighlight(); + persistSettings(); + }); + promptInput.addEventListener('scroll', () => { + if (!promptHighlight) return; + promptHighlight.scrollTop = promptInput.scrollTop; + promptHighlight.scrollLeft = promptInput.scrollLeft; + }); promptNoteInput.addEventListener('input', persistSettings); aspectRatioInput.addEventListener('change', persistSettings); resolutionInput.addEventListener('change', persistSettings); @@ -665,6 +730,7 @@ document.addEventListener('DOMContentLoaded', () => { if (data.refined_prompt) { promptInput.value = data.refined_prompt; + refreshPromptHighlight(); persistSettings(); // Save the new prompt refineModal.classList.add('hidden'); } @@ -1540,7 +1606,10 @@ document.addEventListener('DOMContentLoaded', () => { function applyMetadata(metadata) { if (!metadata) return; - if (metadata.prompt) promptInput.value = metadata.prompt; + if (metadata.prompt) { + promptInput.value = metadata.prompt; + refreshPromptHighlight(); + } // If metadata doesn't have 'note' field, set to empty string instead of keeping current value if (metadata.hasOwnProperty('note')) { diff --git a/static/style.css b/static/style.css index a7b7199..13f92be 100644 --- a/static/style.css +++ b/static/style.css @@ -411,6 +411,66 @@ textarea { width: 100%; } +.prompt-wrapper.prompt-highlighting { + position: relative; + border: 1px solid var(--border-color); + /* border-radius: 0.5rem; */ + /* background-color: var(--input-bg); */ + backdrop-filter: blur(6px); + overflow: hidden; +} + +.prompt-wrapper.prompt-highlighting:focus-within { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); +} + +.prompt-highlight { + position: absolute; + inset: 0; + padding: 0.75rem; + overflow: auto; + pointer-events: none; + white-space: pre-wrap; + word-break: break-word; + z-index: 1; + color: var(--text-primary); + font-family: inherit; + font-size: 0.875rem; + line-height: 1.4; + scrollbar-width: none; +} + +.prompt-highlight::-webkit-scrollbar { + width: 0; + height: 0; +} + +.prompt-highlight-segment { + font-weight: 700; +} + +.prompt-highlight .prompt-placeholder { + color: var(--text-secondary); +} + +.prompt-wrapper.prompt-highlighting textarea { + position: relative; + z-index: 2; + border: none; + outline: none; + background: transparent; + color: transparent; + caret-color: var(--accent-color); + padding: 0.75rem; + font-family: inherit; + font-size: 0.875rem; + line-height: 1.4; + resize: vertical; + min-height: 100px; + opacity:0.3 +} + .prompt-actions { display: flex; justify-content: flex-end; diff --git a/templates/index.html b/templates/index.html index 0b1a238..0ee4943 100644 --- a/templates/index.html +++ b/templates/index.html @@ -40,7 +40,8 @@
-
+
+
@@ -459,4 +460,4 @@ - \ No newline at end of file +