ai-cut/app/components/feature/SceneEditor.vue

215 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="handleClose">
<BaseCard variant="elevated" class="w-full max-w-2xl max-h-[90vh] overflow-y-auto m-4">
<template #header>
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold text-gray-900">編輯場景:{{ scene?.name }}</h2>
<button @click="handleClose" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</template>
<div v-if="scene" class="space-y-6">
<!-- 基本資訊 -->
<div>
<h3 class="font-semibold text-gray-900 mb-4">基本資訊</h3>
<div class="space-y-4">
<BaseInput
v-model="editedScene.name"
label="場景名稱"
:required="true"
/>
<BaseTextarea
v-model="editedScene.description"
label="場景描述"
:rows="3"
/>
<BaseInput
v-model="editedScene.environment"
label="環境設定"
placeholder="例如:室內、室外、城市、森林"
/>
</div>
</div>
<!-- 場景設定 -->
<div>
<h3 class="font-semibold text-gray-900 mb-4">場景設定</h3>
<div class="space-y-4">
<BaseInput
v-model="editedScene.lighting"
label="光線設定"
placeholder="例如:自然光、人工光、昏暗"
/>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700">道具</label>
<div class="flex flex-wrap gap-2 mb-2">
<span
v-for="(prop, index) in editedScene.props"
:key="index"
class="inline-flex items-center gap-1 px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm"
>
{{ prop }}
<button
@click="removeProp(index)"
class="text-green-600 hover:text-green-800"
>
×
</button>
</span>
</div>
<div class="flex gap-2">
<BaseInput
v-model="newProp"
placeholder="輸入道具名稱"
class="flex-1"
@keyup.enter="addProp"
/>
<BaseButton variant="outline" @click="addProp">新增</BaseButton>
</div>
</div>
</div>
</div>
<!-- 顏色設定 -->
<div>
<h3 class="font-semibold text-gray-900 mb-4">顏色設定</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block mb-2 text-sm font-medium text-gray-700">主色</label>
<input
v-model="editedScene.colors.primary"
type="color"
class="w-full h-10 rounded-lg border border-gray-200"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700">次色</label>
<input
v-model="editedScene.colors.secondary"
type="color"
class="w-full h-10 rounded-lg border border-gray-200"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700">強調色</label>
<input
v-model="editedScene.colors.accent"
type="color"
class="w-full h-10 rounded-lg border border-gray-200"
/>
</div>
</div>
</div>
<!-- 場景圖片預覽 -->
<div>
<h3 class="font-semibold text-gray-900 mb-4">場景圖片</h3>
<div class="aspect-video bg-white border-2 border-gray-200 rounded-lg flex items-center justify-center mb-2">
<span v-if="!editedScene.image" class="text-gray-400">場景圖片</span>
<img v-else :src="editedScene.image" alt="場景" class="w-full h-full object-contain rounded-lg" />
</div>
<BaseButton
variant="outline"
@click="generateImage"
:disabled="isGenerating"
class="w-full"
>
{{ isGenerating ? '生成中...' : '生成場景圖片' }}
</BaseButton>
</div>
<!-- 操作按鈕 -->
<div class="flex justify-end gap-4 pt-4 border-t border-gray-200">
<BaseButton variant="outline" @click="handleClose">取消</BaseButton>
<BaseButton variant="primary" @click="handleSave">儲存</BaseButton>
</div>
</div>
</BaseCard>
</div>
</template>
<script setup lang="ts">
import type { SceneAsset } from '~/types/storyboard'
import { useAIImageGeneration } from '~/composables/useAIImageGeneration'
interface Props {
isOpen: boolean
scene: SceneAsset | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
close: []
save: [scene: SceneAsset]
}>()
const { generateSceneImage: generateSceneImageUrl, isLoading: isGenerating } = useAIImageGeneration()
const editedScene = ref<SceneAsset | null>(null)
const newProp = ref('')
watch(() => props.scene, (newScene) => {
if (newScene) {
editedScene.value = {
...newScene,
props: [...(newScene.props || [])],
colors: {
primary: newScene.colors?.primary || '#FFFFFF',
secondary: newScene.colors?.secondary || '#CCCCCC',
accent: newScene.colors?.accent || '#000000'
}
}
}
}, { immediate: true })
function handleClose() {
emit('close')
}
function handleSave() {
if (editedScene.value) {
emit('save', editedScene.value)
handleClose()
}
}
function addProp() {
if (newProp.value.trim() && editedScene.value) {
if (!editedScene.value.props) {
editedScene.value.props = []
}
editedScene.value.props.push(newProp.value.trim())
newProp.value = ''
}
}
function removeProp(index: number) {
if (editedScene.value?.props) {
editedScene.value.props.splice(index, 1)
}
}
async function generateImage() {
if (!editedScene.value) return
try {
const style = '寫實風格' // 可以從 workflow 中取得
const imageUrl = await generateSceneImageUrl(
editedScene.value.name,
editedScene.value.description,
style
)
editedScene.value.image = imageUrl
} catch (err) {
console.error('生成場景圖片失敗:', err)
}
}
</script>