| class CustomPromptInput extends HTMLElement { |
| constructor() { |
| super(); |
| this.mode = 'image'; |
| } |
|
|
| connectedCallback() { |
| this.attachShadow({ mode: 'open' }); |
| this.render(); |
| this.attachEvents(); |
| } |
|
|
| render() { |
| this.shadowRoot.innerHTML = ` |
| <style> |
| :host { |
| display: block; |
| width: 100%; |
| } |
| |
| .prompt-container { |
| background: rgba(30, 41, 59, 0.6); |
| backdrop-filter: blur(20px); |
| border: 1px solid rgba(148, 163, 184, 0.2); |
| border-radius: 24px; |
| padding: 1.5rem; |
| transition: all 0.3s ease; |
| } |
| |
| .prompt-container:focus-within { |
| border-color: #0ea5e9; |
| box-shadow: 0 0 60px rgba(14, 165, 233, 0.2); |
| } |
| |
| .mode-selector { |
| display: flex; |
| gap: 0.5rem; |
| margin-bottom: 1rem; |
| flex-wrap: wrap; |
| } |
| |
| .mode-btn { |
| padding: 0.5rem 1rem; |
| border-radius: 9999px; |
| border: 1px solid transparent; |
| background: rgba(15, 23, 42, 0.5); |
| color: #94a3b8; |
| font-size: 0.875rem; |
| font-weight: 500; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| } |
| |
| .mode-btn:hover { |
| background: rgba(30, 41, 59, 0.8); |
| color: #e2e8f0; |
| } |
| |
| .mode-btn.active { |
| background: linear-gradient(135deg, #0ea5e9, #d946ef); |
| color: white; |
| border-color: transparent; |
| } |
| |
| .input-wrapper { |
| position: relative; |
| } |
| |
| textarea { |
| width: 100%; |
| min-height: 120px; |
| background: transparent; |
| border: none; |
| color: #e2e8f0; |
| font-size: 1.125rem; |
| line-height: 1.6; |
| resize: none; |
| outline: none; |
| font-family: inherit; |
| } |
| |
| textarea::placeholder { |
| color: #64748b; |
| } |
| |
| .input-footer { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| margin-top: 1rem; |
| padding-top: 1rem; |
| border-top: 1px solid rgba(148, 163, 184, 0.1); |
| } |
| |
| .input-actions { |
| display: flex; |
| gap: 0.75rem; |
| } |
| |
| .action-btn { |
| width: 40px; |
| height: 40px; |
| border-radius: 12px; |
| border: 1px solid rgba(148, 163, 184, 0.2); |
| background: rgba(15, 23, 42, 0.5); |
| color: #94a3b8; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .action-btn:hover { |
| background: rgba(30, 41, 59, 0.8); |
| color: #0ea5e9; |
| border-color: #0ea5e9; |
| } |
| |
| .generate-btn { |
| padding: 0.875rem 2rem; |
| background: linear-gradient(135deg, #0ea5e9, #d946ef); |
| border: none; |
| border-radius: 16px; |
| color: white; |
| font-size: 1rem; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| display: flex; |
| align-items: center; |
| gap: 0.75rem; |
| } |
| |
| .generate-btn:hover:not(:disabled) { |
| transform: scale(1.02); |
| box-shadow: 0 20px 60px rgba(14, 165, 233, 0.4); |
| } |
| |
| .generate-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| } |
| |
| .generate-btn.loading { |
| background: linear-gradient(90deg, #0ea5e9, #d946ef, #0ea5e9); |
| background-size: 200% 100%; |
| animation: shimmer 1.5s infinite; |
| } |
| |
| @keyframes shimmer { |
| 0% { background-position: -200% 0; } |
| 100% { background-position: 200% 0; } |
| } |
| |
| .char-count { |
| font-size: 0.75rem; |
| color: #64748b; |
| } |
| .quality-presets { |
| display: flex; |
| gap: 0.5rem; |
| margin-top: 1rem; |
| flex-wrap: wrap; |
| } |
| |
| .quality-preset { |
| padding: 0.5rem 1rem; |
| background: rgba(30, 41, 59, 0.6); |
| border: 1px solid rgba(148, 163, 184, 0.2); |
| border-radius: 12px; |
| color: #94a3b8; |
| font-size: 0.75rem; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 0.25rem; |
| } |
| |
| .quality-preset:hover { |
| background: rgba(30, 41, 59, 0.8); |
| border-color: #0ea5e9; |
| color: #e2e8f0; |
| } |
| |
| .quality-preset.active { |
| background: linear-gradient(135deg, #0ea5e9, #d946ef); |
| border-color: transparent; |
| color: white; |
| } |
| |
| .quality-preset .res { |
| font-size: 0.875rem; |
| font-weight: 700; |
| } |
| |
| .quality-preset .label { |
| font-size: 0.65rem; |
| opacity: 0.8; |
| text-transform: uppercase; |
| } |
| |
| .suggestions { |
| display: flex; |
| gap: 0.5rem; |
| margin-top: 1rem; |
| flex-wrap: wrap; |
| } |
| |
| .suggestion-chip { |
| padding: 0.375rem 0.875rem; |
| background: rgba(14, 165, 233, 0.1); |
| border: 1px solid rgba(14, 165, 233, 0.2); |
| border-radius: 9999px; |
| color: #7dd3fc; |
| font-size: 0.75rem; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .suggestion-chip:hover { |
| background: rgba(14, 165, 233, 0.2); |
| border-color: #0ea5e9; |
| } |
| </style> |
| |
| <div class="prompt-container"> |
| <div class="mode-selector"> |
| <button class="mode-btn ${this.mode === 'image' ? 'active' : ''}" data-mode="image"> |
| <i data-feather="image" style="width: 14px; height: 14px;"></i> |
| Image |
| </button> |
| <button class="mode-btn ${this.mode === 'video' ? 'active' : ''}" data-mode="video"> |
| <i data-feather="video" style="width: 14px; height: 14px;"></i> |
| Video |
| </button> |
| <button class="mode-btn ${this.mode === 'gif' ? 'active' : ''}" data-mode="gif"> |
| <i data-feather="film" style="width: 14px; height: 14px;"></i> |
| GIF |
| </button> |
| <button class="mode-btn ${this.mode === '3d' ? 'active' : ''}" data-mode="3d"> |
| <i data-feather="box" style="width: 14px; height: 14px;"></i> |
| 3D Model |
| </button> |
| </div> |
| |
| <div class="input-wrapper"> |
| <textarea |
| id="prompt-input" |
| placeholder="Describe what you want to create... (e.g., 'A futuristic cityscape with flying cars and neon lights at sunset')" |
| maxlength="500" |
| ></textarea> |
| </div> |
| |
| <div class="input-footer"> |
| <div class="input-actions"> |
| <button class="action-btn" title="Upload reference image"> |
| <i data-feather="upload" style="width: 18px; height: 18px;"></i> |
| </button> |
| <button class="action-btn" title="Random prompt"> |
| <i data-feather="shuffle" style="width: 18px; height: 18px;"></i> |
| </button> |
| <button class="action-btn" title="Voice input"> |
| <i data-feather="mic" style="width: 18px; height: 18px;"></i> |
| </button> |
| </div> |
| |
| <div style="display: flex; align-items: center; gap: 1rem;"> |
| <span class="char-count"><span id="char-count">0</span>/500</span> |
| <button class="generate-btn" id="generate-btn"> |
| <i data-feather="sparkles" style="width: 20px; height: 20px;"></i> |
| Generate |
| </button> |
| </div> |
| </div> |
| <div class="quality-presets"> |
| <button class="quality-preset" data-w="1920" data-h="1080"> |
| <span class="res">FHD</span> |
| <span class="label">Fast</span> |
| </button> |
| <button class="quality-preset active" data-w="3840" data-h="2160"> |
| <span class="res">4K</span> |
| <span class="label">Balanced</span> |
| </button> |
| <button class="quality-preset" data-w="7680" data-h="4320"> |
| <span class="res">8K</span> |
| <span class="label">Quality</span> |
| </button> |
| <button class="quality-preset" data-w="15360" data-h="8640"> |
| <span class="res">16K</span> |
| <span class="label">Ultra</span> |
| </button> |
| </div> |
| |
| <div class="suggestions"> |
| <span class="suggestion-chip">8K cinematic</span> |
| <span class="suggestion-chip">HDR landscape</span> |
| <span class="suggestion-chip">Ultra-detailed portrait</span> |
| <span class="suggestion-chip">Photorealistic 16K</span> |
| <span class="suggestion-chip">IMAX quality</span> |
| </div> |
| </div> |
| `; |
| } |
|
|
| attachEvents() { |
| const textarea = this.shadowRoot.getElementById('prompt-input'); |
| const charCount = this.shadowRoot.getElementById('char-count'); |
| const generateBtn = this.shadowRoot.getElementById('generate-btn'); |
| const modeBtns = this.shadowRoot.querySelectorAll('.mode-btn'); |
| const suggestions = this.shadowRoot.querySelectorAll('.suggestion-chip'); |
|
|
| |
| textarea.addEventListener('input', () => { |
| charCount.textContent = textarea.value.length; |
| }); |
|
|
| |
| modeBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| modeBtns.forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| this.mode = btn.dataset.mode; |
| }); |
| }); |
|
|
| |
| generateBtn.addEventListener('click', () => { |
| const prompt = textarea.value.trim(); |
| if (!prompt) { |
| textarea.focus(); |
| return; |
| } |
|
|
| generateBtn.disabled = true; |
| generateBtn.classList.add('loading'); |
| generateBtn.innerHTML = ` |
| <i data-feather="loader" style="width: 20px; height: 20px; animation: spin 1s linear infinite;"></i> |
| Generating... |
| `; |
| document.dispatchEvent(new CustomEvent('submitPrompt', { |
| detail: { |
| prompt, |
| mode: this.mode, |
| settings: { |
| ...window.VortexAI?.state?.settings, |
| width: this.selectedResolution?.width || 3840, |
| height: this.selectedResolution?.height || 2160 |
| } |
| } |
| })); |
| }); |
| |
| const qualityPresets = this.shadowRoot.querySelectorAll('.quality-preset'); |
| qualityPresets.forEach(preset => { |
| preset.addEventListener('click', () => { |
| qualityPresets.forEach(p => p.classList.remove('active')); |
| preset.classList.add('active'); |
| |
| this.selectedResolution = { |
| width: parseInt(preset.dataset.w), |
| height: parseInt(preset.dataset.h) |
| }; |
| }); |
| }); |
| |
| |
| this.selectedResolution = { width: 3840, height: 2160 }; |
|
|
| |
| suggestions.forEach(chip => { |
| chip.addEventListener('click', () => { |
| textarea.value = chip.textContent; |
| charCount.textContent = textarea.value.length; |
| textarea.focus(); |
| }); |
| }); |
| |
| const randomBtn = this.shadowRoot.querySelector('[title="Random prompt"]'); |
| const randomPrompts = [ |
| 'A cyberpunk samurai standing on a rooftop in Tokyo, neon lights reflecting on wet streets', |
| 'An underwater city with bioluminescent architecture and manta rays swimming through', |
| 'A steampunk airship battle above Victorian London at sunset', |
| 'Crystalline forest with light refractions creating rainbow pathways', |
| 'Mars colony interior with hydroponic gardens and Earth view through windows' |
| ]; |
| |
| randomBtn.addEventListener('click', () => { |
| const random = randomPrompts[Math.floor(Math.random() * randomPrompts.length)]; |
| textarea.value = random; |
| charCount.textContent = random.length; |
| }); |
| } |
| } |
|
|
| customElements.define('custom-prompt-input', CustomPromptInput); |