Spaces:
Sleeping
Sleeping
| <template> | |
| <div | |
| class="flex flex-col gap-8 w-full animate-fade-in-up max-h-[78vh] overflow-y-auto pr-1 custom-scrollbar" | |
| > | |
| <!-- Header Section --> | |
| <div | |
| class="premium-glass rounded-3xl p-6 border border-white/20 shadow-xl bg-white/70 backdrop-blur-xl sticky top-0 z-20" | |
| > | |
| <div class="flex items-center justify-between gap-4 flex-wrap"> | |
| <div class="flex items-center gap-4"> | |
| <div | |
| class="w-12 h-12 rounded-2xl bg-indigo-500/10 flex items-center justify-center border border-indigo-500/20 shadow-inner" | |
| > | |
| <UIcon | |
| name="i-lucide-scroll-text" | |
| class="w-6 h-6 text-indigo-600" | |
| /> | |
| </div> | |
| <div> | |
| <h2 class="text-xl font-black text-slate-900 leading-tight"> | |
| Review Video Plan | |
| </h2> | |
| <p class="text-sm text-slate-500 font-medium mt-0.5"> | |
| Iterate on scene details before starting production | |
| </p> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-3"> | |
| <UButton | |
| @click="handleRevise" | |
| :loading="store.isRevising" | |
| :disabled="!hasAnyFeedback || store.isGenerating" | |
| variant="soft" | |
| color="primary" | |
| icon="i-lucide-refresh-cw" | |
| class="rounded-xl px-5 py-2.5 font-bold transition-all hover:scale-105 active:scale-95" | |
| > | |
| Revise Plans | |
| </UButton> | |
| <UButton | |
| @click="handleApprove" | |
| :loading="store.isGenerating && !store.isRevising" | |
| :disabled="store.isRevising" | |
| class="btn-premium rounded-xl px-6 py-2.5 font-black text-white shadow-lg shadow-green-500/20 transition-all hover:scale-105 active:scale-95 flex items-center gap-2" | |
| > | |
| <UIcon name="i-lucide-check-circle" class="w-5 h-5" /> | |
| Approve & Generate | |
| </UButton> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Scene Plans List --> | |
| <div class="flex flex-col gap-6"> | |
| <div | |
| v-for="(scene, idx) in project.scenes" | |
| :key="idx" | |
| class="premium-glass rounded-3xl overflow-hidden border border-white/20 shadow-lg group hover:shadow-xl transition-all duration-500" | |
| :class="{ 'opacity-60 grayscale-[0.2]': store.isRevising }" | |
| > | |
| <!-- Scene Header --> | |
| <div | |
| class="bg-white/60 p-5 border-b border-slate-100 flex items-center justify-between gap-4" | |
| > | |
| <div class="flex items-center gap-4"> | |
| <div | |
| class="w-9 h-9 rounded-xl bg-slate-900 text-white font-mono text-sm font-black flex items-center justify-center shadow-md" | |
| > | |
| {{ Number(idx) + 1 }} | |
| </div> | |
| <div> | |
| <h3 class="font-black text-slate-900 text-lg"> | |
| {{ scene.title || `Scene ${Number(idx) + 1}` }} | |
| </h3> | |
| <p | |
| class="text-xs text-indigo-500 font-bold uppercase tracking-wider" | |
| > | |
| Strategy & Review | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-6 grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <!-- Left: Current Plan Details (Parsed) --> | |
| <div class="space-y-6"> | |
| <div class="space-y-4"> | |
| <div | |
| v-if="scene.purpose" | |
| class="bg-blue-50/50 p-3 rounded-lg border border-blue-100" | |
| > | |
| <h5 | |
| class="text-[10px] font-bold uppercase tracking-wider text-blue-500 mb-1 flex items-center gap-1" | |
| > | |
| <UIcon name="i-lucide-target" class="w-3 h-3" /> | |
| Scene Purpose | |
| </h5> | |
| <p class="text-xs text-slate-700 italic leading-snug"> | |
| {{ scene.purpose }} | |
| </p> | |
| </div> | |
| <div v-if="scene.description"> | |
| <h5 | |
| class="text-[10px] font-bold uppercase tracking-wider text-slate-400 mb-1" | |
| > | |
| Visual Concept | |
| </h5> | |
| <p class="text-xs text-slate-800 leading-relaxed"> | |
| {{ scene.description }} | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right: Feedback Section --> | |
| <div class="flex flex-col h-full"> | |
| <h4 | |
| class="text-[11px] font-black uppercase tracking-widest text-slate-400 mb-2 flex items-center gap-1.5" | |
| > | |
| <UIcon name="i-lucide-message-square" class="w-3.5 h-3.5" /> | |
| Your Feedback / Changes | |
| </h4> | |
| <div class="flex-1 relative group/input"> | |
| <textarea | |
| v-model="feedbacks[Number(idx) + 1]" | |
| placeholder="What should change in this scene?" | |
| class="w-full h-full min-h-[120px] p-4 rounded-2xl bg-white border-2 border-slate-100 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-500/10 outline-none transition-all text-sm font-medium placeholder:text-slate-300 resize-none shadow-inner" | |
| ></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref, computed } from "vue"; | |
| import { useProjectStore } from "~/stores/projects"; | |
| const props = defineProps<{ | |
| project: any; | |
| }>(); | |
| const store = useProjectStore(); | |
| const feedbacks = ref<Record<number, string>>({}); | |
| const hasAnyFeedback = computed(() => { | |
| return Object.values(feedbacks.value).some((f) => f.trim().length > 0); | |
| }); | |
| function extractContent(plan: string, tag: string) { | |
| if (!plan) return "No plan details available."; | |
| const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, "i"); | |
| const match = plan.match(regex); | |
| return match && match[1] ? match[1].trim() : plan.trim(); | |
| } | |
| async function handleRevise() { | |
| const activeFeedbacks: Record<number, string> = {}; | |
| for (const [id, msg] of Object.entries(feedbacks.value)) { | |
| if (msg.trim()) activeFeedbacks[Number(id)] = msg.trim(); | |
| } | |
| if (Object.keys(activeFeedbacks).length === 0) return; | |
| await store.revisePlan(props.project.id, activeFeedbacks); | |
| // Clear feedbacks after revision | |
| feedbacks.value = {}; | |
| } | |
| async function handleApprove() { | |
| await store.approvePlan(props.project.id); | |
| } | |
| </script> | |
| <style scoped> | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background: rgba(0, 0, 0, 0.05); | |
| border-radius: 10px; | |
| } | |
| </style> | |