This commit is contained in:
xds
2026-02-27 20:36:59 +03:00
parent 2ed2ee2937
commit a737c38c53
6 changed files with 201 additions and 235 deletions

View File

@@ -231,6 +231,18 @@ const onUseResultAsAsset = () => {
<div class="flex flex-col gap-3 bg-black/20 p-4 rounded-xl border border-white/5">
<!-- Grid for main params -->
<div class="grid grid-cols-2 gap-3">
<div class="flex flex-col gap-0.5">
<span class="text-[10px] text-slate-500 uppercase">Model</span>
<span class="text-xs text-violet-400 font-bold truncate" :title="previewImage.gen.model">{{ previewImage.gen.model || 'N/A' }}</span>
</div>
<div v-if="previewImage.gen.seed !== undefined && previewImage.gen.seed !== null" class="flex flex-col gap-0.5">
<span class="text-[10px] text-slate-500 uppercase">Seed</span>
<div class="flex items-center gap-1">
<span class="text-xs text-slate-200 font-mono">{{ previewImage.gen.seed }}</span>
<Button icon="pi pi-copy" @click="copyToClipboard(previewImage.gen.seed.toString())"
text class="!p-0 !w-3 !h-3 !text-slate-500 hover:!text-white" />
</div>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-[10px] text-slate-500 uppercase">Quality</span>
<span class="text-xs text-slate-200 font-semibold">{{ previewImage.gen.quality || 'N/A' }}</span>

View File

@@ -516,17 +516,7 @@ const prompt = ref('')
const isGenerating = ref(false)
const generationStatus = ref('')
const generationProgress = ref(0)
const sendToTelegram = ref(false)
const useProfileImage = ref(true)
const telegramId = ref(localStorage.getItem('telegram_id') || '')
const isTelegramIdSaved = ref(!!localStorage.getItem('telegram_id'))
const saveTelegramId = () => {
if (telegramId.value) {
localStorage.setItem('telegram_id', telegramId.value)
isTelegramIdSaved.value = true
}
}
const generationSuccess = ref(false)
const generationError = ref(null)
const generatedResult = ref(null)
@@ -539,6 +529,12 @@ const previousPrompt = ref('')
const isUploading = ref(false)
const fileInput = ref(null)
const model = ref({ key: 'gemini-3-pro-image-preview', value: 'Pro' })
const modelOptions = ref([
{ key: 'gemini-3.1-flash-image-preview', value: '2' },
{ key: 'gemini-3-pro-image-preview', value: 'Pro' }
])
const selectedAssets = ref([])
const toggleAssetSelection = (asset) => {
const index = selectedAssets.value.findIndex(a => a.id === asset.id)
@@ -558,9 +554,6 @@ const quality = ref({
value: '2K'
})
const qualityOptions = ref([{
key: 'ONEK',
value: '1K'
}, {
key: 'TWOK',
value: '2K'
}, {
@@ -729,6 +722,10 @@ const restoreGeneration = async (gen) => {
// 1. Set prompt
prompt.value = gen.prompt
// 1.1 Set Model
const foundModel = modelOptions.value.find(opt => opt.key === gen.model)
if (foundModel) model.value = foundModel
// 2. Set Quality
const foundQuality = qualityOptions.value.find(opt => opt.key === gen.quality)
if (foundQuality) quality.value = foundQuality
@@ -898,25 +895,14 @@ const handleGenerate = async () => {
generatedResult.value = null
try {
if (sendToTelegram.value && !telegramId.value) {
alert("Please enter your Telegram ID")
isGenerating.value = false
return
}
if (telegramId.value && telegramId.value !== localStorage.getItem('telegram_id')) {
localStorage.setItem('telegram_id', telegramId.value)
isTelegramIdSaved.value = true
}
const payload = {
model: model.value.key,
linked_character_id: character.value?.id,
environment_id: selectedEnvironment.value?.id || selectedEnvironment.value?._id || null,
aspect_ratio: aspectRatio.value.key,
quality: quality.value.key,
prompt: prompt.value,
assets_list: selectedAssets.value.map(a => a.id),
telegram_id: sendToTelegram.value ? telegramId.value : null,
use_profile_image: useProfileImage.value,
count: generationCount.value
}
@@ -1047,34 +1033,48 @@ const handleGenerate = async () => {
<h2 class="text-sm font-bold m-0">Settings</h2>
</div>
<div class="flex flex-col gap-1.5">
<label
class="text-slate-400 text-[9px] font-semibold uppercase tracking-wider">Quality</label>
<div
class="!flex !w-full !justify-between items-center justify-center !bg-slate-900/50 !p-1 gap-1 !rounded-lg !border !border-white/10">
<div v-for="option in qualityOptions" :key="option.key"
@click="quality = option"
class="w-full items-center justify-center justify-items-center !text-center hover:bg-white/5 hover:text-white p-1 hover:rounded-lg"
:class="quality.key === option.key ? 'bg-white/9 text-white rounded-lg' : ''">
<span class="text-white w-full text-center">{{ option.value }}</span>
<div class="grid grid-cols-3 gap-2">
<div class="flex flex-col gap-1.5">
<label
class="text-slate-400 text-[9px] font-semibold uppercase tracking-wider">Model</label>
<div
class="!flex !w-full !justify-between items-center justify-center !bg-slate-900/50 !p-1 gap-1 !rounded-lg !border !border-white/10">
<div v-for="option in modelOptions" :key="option.key"
@click="model = option"
class="w-full items-center justify-center justify-items-center !text-center hover:bg-white/5 hover:text-white p-1 hover:rounded-lg cursor-pointer"
:class="model.key === option.key ? 'bg-white/10 text-white rounded-lg shadow-sm' : 'text-slate-500'">
<span class="text-white w-full text-center text-[8px]">{{ option.value }}</span>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-1.5">
<label
class="text-slate-400 text-[9px] font-semibold uppercase tracking-wider">Aspect</label>
<div
class="!flex !w-full !justify-between items-center justify-center !bg-slate-900/50 !p-1 gap-1 !rounded-lg !border !border-white/10">
<div v-for="option in aspectRatioOptions" :key="option.key"
@click="aspectRatio = option"
class="w-full items-center justify-center justify-items-center !text-center hover:bg-white/5 hover:text-white p-1 hover:rounded-lg"
:class="aspectRatio.key === option.key ? 'bg-white/9 text-white rounded-lg' : ''">
<span class="text-white w-full text-center">{{ option.value }}</span>
<div class="flex flex-col gap-1.5">
<label
class="text-slate-400 text-[9px] font-semibold uppercase tracking-wider">Quality</label>
<div
class="!flex !w-full !justify-between items-center justify-center !bg-slate-900/50 !p-1 gap-1 !rounded-lg !border !border-white/10">
<div v-for="option in qualityOptions" :key="option.key"
@click="quality = option"
class="w-full items-center justify-center justify-items-center !text-center hover:bg-white/5 hover:text-white p-1 hover:rounded-lg cursor-pointer"
:class="quality.key === option.key ? 'bg-white/10 text-white rounded-lg' : ''">
<span class="text-white w-full text-center text-[8px]">{{ option.value }}</span>
</div>
</div>
</div>
<div class="flex flex-col gap-1.5">
<label
class="text-slate-400 text-[9px] font-semibold uppercase tracking-wider">Aspect</label>
<div
class="!flex !w-full !justify-between items-center justify-center !bg-slate-900/50 !p-1 gap-1 !rounded-lg !border !border-white/10">
<div v-for="option in aspectRatioOptions" :key="option.key"
@click="aspectRatio = option"
class="w-full items-center justify-center justify-items-center !text-center hover:bg-white/5 hover:text-white p-1 hover:rounded-lg cursor-pointer"
:class="aspectRatio.key === option.key ? 'bg-white/10 text-white rounded-lg' : ''">
<span class="text-white w-full text-center text-[8px]">{{ option.value }}</span>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-1.5">
@@ -1162,18 +1162,6 @@ const handleGenerate = async () => {
<div class="flex flex-col gap-1.5 mt-auto pt-1.5 border-t border-white/5">
<div class="flex flex-col gap-2 mb-2">
<div class="flex items-center gap-2">
<Checkbox v-model="sendToTelegram" :binary="true"
inputId="tg-check-char" />
<label for="tg-check-char"
class="text-[10px] text-slate-400 cursor-pointer select-none">Send
result to Telegram</label>
</div>
<div v-if="sendToTelegram && !isTelegramIdSaved"
class="animate-in fade-in slide-in-from-top-1 duration-200">
<InputText v-model="telegramId" placeholder="Enter Telegram ID"
class="w-full !text-[16px] !py-1" @blur="saveTelegramId" />
</div>
<div class="flex items-center gap-2 mt-1">
<Checkbox v-model="useProfileImage" :binary="true"
inputId="profile-img-check" />

View File

@@ -151,12 +151,14 @@ const assetPickerTab = ref('all') // 'all', 'uploaded', 'generated'
const modalAssets = ref([])
const isModalLoading = ref(false)
const tempSelectedAssets = ref([])
const model = ref({ key: 'gemini-3-pro-image-preview', value: 'Pro' })
const modelOptions = ref([
{ key: 'gemini-3.1-flash-image-preview', value: '2' },
{ key: 'gemini-3-pro-image-preview', value: 'Pro' }
])
const quality = ref({ key: 'TWOK', value: '2K' })
const aspectRatio = ref('NINESIXTEEN') // Default to Video (9:16)
const generationCount = ref(1)
const sendToTelegram = ref(false)
const telegramId = ref('')
const isTelegramIdSaved = ref(false)
const useProfileImage = ref(true)
const useEnvironment = ref(false)
const isImprovingPrompt = ref(false)
@@ -214,7 +216,6 @@ const onlyLiked = ref(false)
// Options
const qualityOptions = ref([
{ key: 'ONEK', value: '1K' },
{ key: 'TWOK', value: '2K' },
{ key: 'FOURK', value: '4K' }
])
@@ -225,24 +226,17 @@ const STORAGE_KEY = 'flexible_gen_settings'
const saveSettings = () => {
const settings = {
prompt: prompt.value,
model: model.value,
selectedCharacterId: selectedCharacter.value?.id || selectedCharacter.value?._id,
selectedEnvironmentId: selectedEnvironment.value?.id || selectedEnvironment.value?._id,
selectedAssetIds: selectedAssets.value.map(a => a.id),
quality: quality.value,
aspectRatio: aspectRatio.value,
sendToTelegram: sendToTelegram.value,
telegramId: telegramId.value,
useProfileImage: useProfileImage.value,
useEnvironment: useEnvironment.value,
generationCount: generationCount.value
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))
// Also save Telegram ID separately as it's used elsewhere
if (telegramId.value) {
localStorage.setItem('telegram_id', telegramId.value)
isTelegramIdSaved.value = true
}
}
const restoreSettings = () => {
@@ -254,6 +248,7 @@ const restoreSettings = () => {
// We need characters and assets loaded to fully restore objects
// For now, we'll store IDs and restore in loadData
if (settings.model) model.value = settings.model
if (settings.quality) quality.value = settings.quality
if (settings.aspectRatio) {
// Handle legacy object format if present
@@ -263,9 +258,6 @@ const restoreSettings = () => {
aspectRatio.value = settings.aspectRatio
}
}
sendToTelegram.value = settings.sendToTelegram || false
telegramId.value = settings.telegramId || localStorage.getItem('telegram_id') || ''
if (telegramId.value) isTelegramIdSaved.value = true
if (settings.useProfileImage !== undefined) useProfileImage.value = settings.useProfileImage
if (settings.useEnvironment !== undefined) useEnvironment.value = settings.useEnvironment
if (settings.generationCount) generationCount.value = Math.min(settings.generationCount, 4)
@@ -280,7 +272,7 @@ const restoreSettings = () => {
}
// Watchers for auto-save
watch([prompt, selectedCharacter, selectedEnvironment, selectedAssets, quality, aspectRatio, sendToTelegram, telegramId, useProfileImage, useEnvironment, generationCount], () => {
watch([prompt, selectedCharacter, selectedEnvironment, selectedAssets, quality, aspectRatio, useProfileImage, useEnvironment, generationCount], () => {
saveSettings()
}, { deep: true })
@@ -412,11 +404,6 @@ const refreshHistory = async () => {
const handleGenerate = async () => {
if (!prompt.value.trim()) return
if (sendToTelegram.value && !telegramId.value) {
alert("Please enter your Telegram ID")
return
}
isSubmitting.value = true
// Close settings to show gallery/progress (optional preference)
@@ -424,13 +411,13 @@ const handleGenerate = async () => {
try {
const payload = {
model: model.value.key,
aspect_ratio: aspectRatio.value, // Now a string
quality: quality.value.key,
prompt: prompt.value,
assets_list: selectedAssets.value.map(a => a.id),
linked_character_id: selectedCharacter.value?.id || selectedCharacter.value?._id || null,
environment_id: (selectedCharacter.value && useEnvironment.value) ? (selectedEnvironment.value?.id || selectedEnvironment.value?._id || null) : null,
telegram_id: sendToTelegram.value ? telegramId.value : null,
use_profile_image: selectedCharacter.value ? useProfileImage.value : false,
count: generationCount.value
}
@@ -1180,31 +1167,42 @@ const confirmAddToAlbum = async () => {
</div>
<div class="w-full lg:w-80 flex flex-col gap-4">
<div class="grid grid-cols-2 gap-4">
<div class="flex flex-col gap-2">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">Quality</label>
<Dropdown v-model="quality" :options="qualityOptions" optionLabel="value"
class="w-full !bg-slate-800 !border-white/10 !text-white !rounded-xl"
:pt="{ input: { class: '!text-white' }, trigger: { class: '!text-slate-400' }, panel: { class: '!bg-slate-800 !border-white/10' }, item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white' } }" />
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">Format</label>
<div class="flex items-center">
<div class="flex-1 flex bg-slate-800 rounded-xl border border-white/10 overflow-hidden">
<button @click="aspectRatio = 'THREEFOUR'"
class="flex-1 py-2 text-sm font-bold transition-all flex items-center justify-center gap-1"
:class="aspectRatio === 'THREEFOUR' ? 'bg-violet-600 text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'">
<i class="pi pi-image"></i> Photo
</button>
<div class="w-px bg-white/10"></div>
<button @click="aspectRatio = 'NINESIXTEEN'"
class="flex-1 py-2 text-sm font-bold transition-all flex items-center justify-center gap-1"
:class="aspectRatio === 'NINESIXTEEN' ? 'bg-violet-600 text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'">
<i class="pi pi-video"></i> Video
</button>
<div class="grid grid-cols-3 gap-2">
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Model</label>
<div class="flex bg-slate-800 p-1 rounded-xl border border-white/10 h-[34px]">
<div v-for="option in modelOptions" :key="option.key" @click="model = option"
class="flex-1 flex items-center justify-center cursor-pointer rounded-md text-[10px] font-bold transition-all"
:class="model.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Quality</label>
<div class="flex bg-slate-800 p-1 rounded-xl border border-white/10 h-[34px]">
<div v-for="option in qualityOptions" :key="option.key" @click="quality = option"
class="flex-1 flex items-center justify-center cursor-pointer rounded-md text-[10px] font-bold transition-all"
:class="quality.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Format</label>
<div class="flex bg-slate-800 rounded-xl border border-white/10 h-[34px] p-1">
<button @click="aspectRatio = 'THREEFOUR'"
class="flex-1 text-[10px] font-bold transition-all flex items-center justify-center rounded-md"
:class="aspectRatio === 'THREEFOUR' ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-white hover:bg-white/5'">
<i class="pi pi-image"></i>
</button>
<button @click="aspectRatio = 'NINESIXTEEN'"
class="flex-1 text-[10px] font-bold transition-all flex items-center justify-center rounded-md"
:class="aspectRatio === 'NINESIXTEEN' ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-white hover:bg-white/5'">
<i class="pi pi-video"></i>
</button>
</div>
</div>
</div>
<!-- Generation Count -->
@@ -1221,18 +1219,6 @@ const confirmAddToAlbum = async () => {
</div>
</div>
<div class="flex flex-col gap-2 bg-slate-800/50 p-3 rounded-xl border border-white/5">
<div class="flex items-center gap-2">
<Checkbox v-model="sendToTelegram" :binary="true" inputId="tg-check" />
<label for="tg-check" class="text-xs text-slate-300 cursor-pointer">Send to
Telegram</label>
</div>
<div v-if="sendToTelegram" class="animate-in fade-in slide-in-from-top-1">
<InputText v-model="telegramId" placeholder="Telegram ID"
class="w-full !text-[16px] !bg-slate-900 !border-white/10 !text-white !py-1.5" />
</div>
</div>
<div class="mt-auto">
<Button :label="isSubmitting ? 'Starting...' : 'Generate'"
:icon="isSubmitting ? 'pi pi-spin pi-spinner' : 'pi pi-sparkles'"

View File

@@ -76,7 +76,12 @@ const saveName = async () => {
const prompt = ref('')
const negativePrompt = ref('')
const selectedModel = ref('flux-schnell')
const model = ref({ key: 'gemini-3-pro-image-preview', value: 'Pro' })
const modelOptions = ref([
{ key: 'gemini-3.1-flash-image-preview', value: '2' },
{ key: 'gemini-3-pro-image-preview', value: 'Pro' }
])
const selectedModel = ref('flux-schnell') // Keep legacy if needed elsewhere, but we will use 'model' for generation
// Character & Assets (declared early for settings persistence)
const characters = ref([])
@@ -91,8 +96,6 @@ const SETTINGS_KEY = 'idea-gen-settings'
const quality = ref({ key: 'TWOK', value: '2K' })
const aspectRatio = ref('NINESIXTEEN') // Default to Video (9:16)
const imageCount = ref(1)
const sendToTelegram = ref(false)
const telegramId = ref('')
const useProfileImage = ref(true)
const useEnvironment = ref(false)
const isImprovingPrompt = ref(false)
@@ -135,12 +138,11 @@ watch(selectedCharacter, (newChar) => {
const saveSettings = () => {
const settings = {
prompt: prompt.value,
model: model.value,
quality: quality.value,
aspectRatio: aspectRatio.value,
imageCount: imageCount.value,
selectedModel: selectedModel.value,
sendToTelegram: sendToTelegram.value,
telegramId: telegramId.value,
useProfileImage: useProfileImage.value,
useEnvironment: useEnvironment.value,
selectedCharacterId: selectedCharacter.value?.id || selectedCharacter.value?._id || null,
@@ -148,9 +150,6 @@ const saveSettings = () => {
selectedAssetIds: selectedAssets.value.map(a => a.id),
}
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings))
if (telegramId.value) {
localStorage.setItem('telegram_id', telegramId.value)
}
}
const restoreSettings = () => {
@@ -158,6 +157,7 @@ const restoreSettings = () => {
if (!stored) return
try {
const s = JSON.parse(stored)
if (s.model) model.value = s.model
if (s.prompt) prompt.value = s.prompt
if (s.quality) quality.value = s.quality
if (s.aspectRatio) {
@@ -170,8 +170,6 @@ const restoreSettings = () => {
}
if (s.imageCount) imageCount.value = Math.min(s.imageCount, 4)
if (s.selectedModel) selectedModel.value = s.selectedModel
sendToTelegram.value = s.sendToTelegram || false
telegramId.value = s.telegramId || localStorage.getItem('telegram_id') || ''
if (s.useProfileImage !== undefined) useProfileImage.value = s.useProfileImage
if (s.useEnvironment !== undefined) useEnvironment.value = s.useEnvironment
_savedCharacterId = s.selectedCharacterId || null
@@ -189,7 +187,7 @@ const restoreSettings = () => {
}
restoreSettings()
watch([prompt, quality, aspectRatio, imageCount, selectedModel, sendToTelegram, telegramId, useProfileImage, useEnvironment, selectedCharacter, selectedEnvironment, selectedAssets], saveSettings, { deep: true })
watch([prompt, quality, aspectRatio, imageCount, model, useProfileImage, useEnvironment, selectedCharacter, selectedEnvironment, selectedAssets], saveSettings, { deep: true })
const viewMode = ref('feed') // 'feed' or 'gallery'
const onlyLiked = ref(false)
@@ -203,7 +201,6 @@ watch(isSettingsVisible, (val) => {
const API_URL = import.meta.env.VITE_API_URL
const qualityOptions = ref([
{ key: 'ONEK', value: '1K' },
{ key: 'TWOK', value: '2K' },
{ key: 'FOURK', value: '4K' }
])
@@ -306,13 +303,13 @@ const handleGenerate = async () => {
try {
// Construct Payload
const payload = {
model: model.value.key,
prompt: prompt.value,
aspect_ratio: aspectRatio.value, // Now a string
quality: quality.value.key,
assets_list: selectedAssets.value.map(a => a.id),
linked_character_id: selectedCharacter.value?.id || selectedCharacter.value?._id || null,
environment_id: (selectedCharacter.value && useEnvironment.value) ? (selectedEnvironment.value?.id || selectedEnvironment.value?._id || null) : null,
telegram_id: sendToTelegram.value ? telegramId.value : null,
use_profile_image: selectedCharacter.value ? useProfileImage.value : false,
count: imageCount.value,
idea_id: currentIdea.value.id
@@ -1426,31 +1423,39 @@ const markNsfw = async (gen) => {
</div> </div>
<div class="w-full lg:w-72 flex flex-col gap-2">
<div class="grid grid-cols-2 gap-2">
<div class="grid grid-cols-3 gap-2">
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Model</label>
<div class="flex bg-slate-800 p-1 rounded-xl border border-white/10">
<div v-for="option in modelOptions" :key="option.key" @click="model = option"
class="flex-1 text-center py-1 text-[10px] font-medium transition-all cursor-pointer rounded-md"
:class="model.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label
class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Quality</label>
<Dropdown v-model="quality" :options="qualityOptions" optionLabel="value"
class="w-full !bg-slate-800 !border-white/10 !text-white !rounded-xl"
:pt="{ input: { class: '!text-white' }, trigger: { class: '!text-slate-400' }, panel: { class: '!bg-slate-800 !border-white/10' }, item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white' } }" />
class="w-full !bg-slate-800 !border-white/10 !text-white !rounded-xl !h-[34px]"
:pt="{ input: { class: '!text-white !text-[10px] !py-1 !px-2' }, trigger: { class: '!text-slate-400 !w-6' }, panel: { class: '!bg-slate-800 !border-white/10' }, item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white !text-[10px] !py-1' } }" />
</div>
<div class="flex flex-col gap-1">
<label
class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Format</label>
<div class="flex items-center">
<div class="flex-1 flex bg-slate-800 rounded-lg border border-white/10 overflow-hidden">
<button @click="aspectRatio = 'THREEFOUR'"
class="flex-1 py-1 text-xs font-bold transition-all flex items-center justify-center gap-1"
:class="aspectRatio === 'THREEFOUR' ? 'bg-violet-600 text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'">
<i class="pi pi-image"></i> Photo
</button>
<div class="w-px bg-white/10"></div>
<button @click="aspectRatio = 'NINESIXTEEN'"
class="flex-1 py-1 text-xs font-bold transition-all flex items-center justify-center gap-1"
:class="aspectRatio === 'NINESIXTEEN' ? 'bg-violet-600 text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'">
<i class="pi pi-video"></i> Video
</button>
</div>
<div class="flex bg-slate-800 rounded-xl border border-white/10 overflow-hidden h-[34px]">
<button @click="aspectRatio = 'THREEFOUR'"
class="flex-1 text-[10px] font-bold transition-all flex items-center justify-center"
:class="aspectRatio === 'THREEFOUR' ? 'bg-violet-600 text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'">
<i class="pi pi-image"></i>
</button>
<div class="w-px bg-white/10"></div>
<button @click="aspectRatio = 'NINESIXTEEN'"
class="flex-1 text-[10px] font-bold transition-all flex items-center justify-center"
:class="aspectRatio === 'NINESIXTEEN' ? 'bg-violet-600 text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'">
<i class="pi pi-video"></i>
</button>
</div>
</div>
</div>
@@ -1469,18 +1474,6 @@ const markNsfw = async (gen) => {
</div>
</div>
<div class="flex flex-col gap-1 bg-slate-800/50 p-2 rounded-lg border border-white/5">
<div class="flex items-center gap-2">
<Checkbox v-model="sendToTelegram" :binary="true" inputId="idea-tg-check" />
<label for="idea-tg-check" class="text-xs text-slate-300 cursor-pointer">Send to
Telegram</label>
</div>
<div v-if="sendToTelegram" class="animate-in fade-in slide-in-from-top-1">
<InputText v-model="telegramId" placeholder="Telegram ID"
class="w-full !text-[16px] !bg-slate-900 !border-white/10 !text-white !py-1.5" />
</div>
</div>
<!-- NSFW Toggle -->
<div class="flex flex-col gap-1 bg-slate-800/50 p-2 rounded-lg border border-white/5">
<div class="flex items-center justify-between">

View File

@@ -187,12 +187,15 @@ const startIdeaFromInspiration = (inspiration) => {
// --- Generation Settings ---
const prompt = ref('')
const model = ref({ key: 'gemini-3-pro-image-preview', value: 'Pro' })
const modelOptions = ref([
{ key: 'gemini-3.1-flash-image-preview', value: '2' },
{ key: 'gemini-3-pro-image-preview', value: 'Pro' }
])
const selectedModel = ref('flux-schnell')
const quality = ref({ key: 'TWOK', value: '2K' })
const aspectRatio = ref({ key: 'NINESIXTEEN', value: '9:16' })
const imageCount = ref(1)
const sendToTelegram = ref(false)
const telegramId = ref('')
const useProfileImage = ref(true)
const useEnvironment = ref(false)
const isSubmittingGen = ref(false)
@@ -232,7 +235,6 @@ watch(selectedCharacter, (newChar) => {
})
const qualityOptions = ref([
{ key: 'ONEK', value: '1K' },
{ key: 'TWOK', value: '2K' },
{ key: 'FOURK', value: '4K' }
])
@@ -256,13 +258,12 @@ const restoreSettings = () => {
if (!stored) return
try {
const s = JSON.parse(stored)
if (s.model) model.value = s.model
if (s.prompt) prompt.value = s.prompt
if (s.quality) quality.value = s.quality
if (s.aspectRatio) aspectRatio.value = s.aspectRatio
if (s.imageCount) imageCount.value = Math.min(s.imageCount, 4)
if (s.selectedModel) selectedModel.value = s.selectedModel
sendToTelegram.value = s.sendToTelegram || false
telegramId.value = s.telegramId || localStorage.getItem('telegram_id') || ''
if (s.useProfileImage !== undefined) useProfileImage.value = s.useProfileImage
if (s.useEnvironment !== undefined) useEnvironment.value = s.useEnvironment
_savedCharacterId = s.selectedCharacterId || null
@@ -283,12 +284,11 @@ const restoreSettings = () => {
const saveSettings = () => {
const settings = {
prompt: prompt.value,
model: model.value,
quality: quality.value,
aspectRatio: aspectRatio.value,
imageCount: imageCount.value,
selectedModel: selectedModel.value,
sendToTelegram: sendToTelegram.value,
telegramId: telegramId.value,
useProfileImage: useProfileImage.value,
useEnvironment: useEnvironment.value,
selectedCharacterId: selectedCharacter.value?.id || null,
@@ -296,10 +296,9 @@ const saveSettings = () => {
selectedAssetIds: selectedAssets.value.map(a => a.id),
}
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings))
if (telegramId.value) localStorage.setItem('telegram_id', telegramId.value)
}
watch([prompt, quality, aspectRatio, imageCount, selectedModel, sendToTelegram, telegramId, useProfileImage, useEnvironment, selectedCharacter, selectedEnvironment, selectedAssets], saveSettings, { deep: true })
watch([prompt, quality, aspectRatio, imageCount, model, useProfileImage, useEnvironment, selectedCharacter, selectedEnvironment, selectedAssets], saveSettings, { deep: true })
onMounted(async () => {
restoreSettings()
@@ -834,18 +833,37 @@ const handleAssetPickerUpload = async (event) => {
<!-- RIGHT COLUMN: Settings & Button -->
<div class="w-full lg:w-72 flex flex-col gap-2">
<div class="grid grid-cols-2 gap-2">
<div class="grid grid-cols-3 gap-2">
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Model</label>
<div class="flex bg-slate-800 p-1 rounded-xl border border-white/10">
<div v-for="option in modelOptions" :key="option.key" @click="model = option"
class="flex-1 text-center py-1 text-[10px] font-medium transition-all cursor-pointer rounded-md"
:class="model.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Quality</label>
<Dropdown v-model="quality" :options="qualityOptions" optionLabel="value"
class="w-full !bg-slate-800 !border-white/10 !text-white !rounded-xl"
:pt="{ input: { class: '!text-white' }, trigger: { class: '!text-slate-400' }, panel: { class: '!bg-slate-800 !border-white/10' }, item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white' } }" />
<div class="flex bg-slate-800 p-1 rounded-xl border border-white/10 h-[34px]">
<div v-for="option in qualityOptions" :key="option.key" @click="quality = option"
class="flex-1 flex items-center justify-center cursor-pointer rounded-md text-[10px] font-bold transition-all"
:class="quality.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Ratio</label>
<Dropdown v-model="aspectRatio" :options="aspectRatioOptions" optionLabel="value"
class="w-full !bg-slate-800 !border-white/10 !text-white !rounded-xl"
:pt="{ input: { class: '!text-white' }, trigger: { class: '!text-slate-400' }, panel: { class: '!bg-slate-800 !border-white/10' }, item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white' } }" />
class="w-full !bg-slate-800 !border-white/10 !text-white !rounded-xl !h-[34px]"
:pt="{
input: { class: '!text-white !text-[10px] !py-1 !px-2 !font-bold' },
trigger: { class: '!text-slate-400 !w-6' },
panel: { class: '!bg-slate-800 !border-white/10' },
item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white !text-[10px] !py-1' }
}" />
</div>
</div>
@@ -863,19 +881,6 @@ const handleAssetPickerUpload = async (event) => {
</div>
</div>
<!-- Telegram (Copied from Detail View) -->
<div class="flex flex-col gap-1 bg-slate-800/50 p-2 rounded-lg border border-white/5">
<div class="flex items-center gap-2">
<Checkbox v-model="sendToTelegram" :binary="true" inputId="idea-tg-check" />
<label for="idea-tg-check" class="text-xs text-slate-300 cursor-pointer">Send to
Telegram</label>
</div>
<div v-if="sendToTelegram" class="animate-in fade-in slide-in-from-top-1">
<InputText v-model="telegramId" placeholder="Telegram ID"
class="w-full !text-[16px] !bg-slate-900 !border-white/10 !text-white !py-1.5" />
</div>
</div>
<div class="mt-auto">
<Button :label="isSubmittingGen ? 'Starting...' : 'Generate New Session'"

View File

@@ -12,6 +12,7 @@ import Dialog from 'primevue/dialog'
import Paginator from 'primevue/paginator'
import InputText from 'primevue/inputtext'
import Tag from 'primevue/tag'
import Dropdown from 'primevue/dropdown'
import GenerationPreviewModal from '../components/GenerationPreviewModal.vue'
const router = useRouter()
@@ -44,22 +45,17 @@ const assetsTotalRecords = ref(0)
const assetsRows = ref(12)
const assetsFirst = ref(0)
const activeAssetFilter = ref('all')
const sendToTelegram = ref(false)
const telegramId = ref(localStorage.getItem('telegram_id') || '')
const isTelegramIdSaved = ref(!!localStorage.getItem('telegram_id'))
const isUploading = ref(false)
const fileInput = ref(null)
const saveTelegramId = () => {
if (telegramId.value) {
localStorage.setItem('telegram_id', telegramId.value)
isTelegramIdSaved.value = true
}
}
const model = ref({ key: 'gemini-3-pro-image-preview', value: 'Pro' })
const modelOptions = ref([
{ key: 'gemini-3.1-flash-image-preview', value: '2' },
{ key: 'gemini-3-pro-image-preview', value: 'Pro' }
])
const quality = ref({ key: 'TWOK', value: '2K' })
const qualityOptions = ref([
{ key: 'ONEK', value: '1K' },
{ key: 'TWOK', value: '2K' },
{ key: 'FOURK', value: '4K' }
])
@@ -186,18 +182,6 @@ const onFileSelected = async (event) => {
const handleGenerate = async () => {
if (!prompt.value.trim()) return
// Validation for Telegram
if (sendToTelegram.value && !telegramId.value) {
alert("Please enter your Telegram ID")
return
}
// Save ID if provided
if (telegramId.value && telegramId.value !== localStorage.getItem('telegram_id')) {
localStorage.setItem('telegram_id', telegramId.value)
isTelegramIdSaved.value = true
}
isGenerating.value = true
generationSuccess.value = false
generationError.value = null
@@ -207,12 +191,12 @@ const handleGenerate = async () => {
try {
const payload = {
model: model.value.key,
aspect_ratio: aspectRatio.value.key,
quality: quality.value.key,
prompt: prompt.value,
assets_list: selectedAssets.value.map(a => a.id),
linked_character_id: null, // Explicitly null for global generation
telegram_id: sendToTelegram.value ? telegramId.value : null
}
const response = await aiService.runGeneration(payload)
@@ -294,6 +278,9 @@ const pollStatus = async (id) => {
const restoreGeneration = async (gen) => {
prompt.value = gen.prompt
const foundModel = modelOptions.value.find(opt => opt.key === gen.model)
if (foundModel) model.value = foundModel
const foundQuality = qualityOptions.value.find(opt => opt.key === gen.quality)
if (foundQuality) quality.value = foundQuality
@@ -461,13 +448,23 @@ onMounted(() => {
<!-- Settings Card -->
<div class="glass-panel p-6 rounded-2xl border border-white/5 bg-white/5 flex flex-col gap-6">
<!-- Quality & Aspect Ratio -->
<div class="grid grid-cols-2 gap-6">
<!-- Settings Row: Model, Quality & Aspect Ratio -->
<div class="grid grid-cols-3 gap-4">
<div class="flex flex-col gap-2">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">Model</label>
<div class="flex bg-slate-900/50 p-1 rounded-lg border border-white/10 h-[34px]">
<div v-for="option in modelOptions" :key="option.key" @click="model = option"
class="flex-1 flex items-center justify-center cursor-pointer rounded-md text-[10px] font-bold transition-all"
:class="model.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">Quality</label>
<div class="flex bg-slate-900/50 p-1 rounded-lg border border-white/10">
<div class="flex bg-slate-900/50 p-1 rounded-lg border border-white/10 h-[34px]">
<div v-for="option in qualityOptions" :key="option.key" @click="quality = option"
class="flex-1 text-center py-1.5 cursor-pointer rounded-md text-xs font-medium transition-all"
class="flex-1 flex items-center justify-center cursor-pointer rounded-md text-[10px] font-bold transition-all"
:class="quality.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
@@ -476,14 +473,14 @@ onMounted(() => {
<div class="flex flex-col gap-2">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">Aspect
Ratio</label>
<div class="flex bg-slate-900/50 p-1 rounded-lg border border-white/10">
<div v-for="option in aspectRatioOptions" :key="option.key"
@click="aspectRatio = option"
class="flex-1 text-center py-1.5 cursor-pointer rounded-md text-xs font-medium transition-all"
:class="aspectRatio.key === option.key ? 'bg-white/10 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'">
{{ option.value }}
</div>
</div>
<Dropdown v-model="aspectRatio" :options="aspectRatioOptions" optionLabel="value"
class="w-full !bg-slate-900/50 !border-white/10 !text-white !rounded-lg !h-[34px]"
:pt="{
input: { class: '!text-white !text-[10px] !py-1 !px-2 !font-bold' },
trigger: { class: '!text-slate-400 !w-6' },
panel: { class: '!bg-slate-900 !border-white/10' },
item: { class: '!text-slate-300 hover:!bg-white/10 hover:!text-white !text-[10px] !py-1' }
}" />
</div>
</div>
@@ -551,21 +548,6 @@ onMounted(() => {
<!-- Generate Button -->
<div class="mt-auto">
<div class="flex flex-col gap-2 mb-3">
<div class="flex items-center gap-2">
<Checkbox v-model="sendToTelegram" :binary="true" inputId="tg-check-gen" />
<label for="tg-check-gen"
class="text-xs text-slate-400 cursor-pointer select-none">Send result to
Telegram</label>
</div>
<div v-if="sendToTelegram && !isTelegramIdSaved"
class="animate-in fade-in slide-in-from-top-1 duration-200">
<InputText v-model="telegramId" placeholder="Enter Telegram ID"
class="w-full !text-[16px] !py-1.5" @blur="saveTelegramId" />
<small class="text-[10px] text-slate-500 block mt-0.5">ID will be saved for future
use</small>
</div>
</div>
<Button :label="isGenerating ? 'Generating...' : 'Generate Image'"
:icon="isGenerating ? 'pi pi-spin pi-spinner' : 'pi pi-magic'" :loading="isGenerating"
@click="handleGenerate"