|
|
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); |