File size: 3,994 Bytes
c84aa63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React, { useState, useEffect, useRef } from 'react'
import axios from 'axios'
import { ImagePlus, Sparkles } from 'lucide-react'

export default function GeneratorForm({ setJobId, setStatus, setSplatUrl }) {
  const [prompt, setPrompt] = useState('')
  const [file, setFile] = useState(null)
  const [isSubmitting, setIsSubmitting] = useState(false)
  
  const intervalRef = useRef(null)

  useEffect(() => {
    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current)
    }
  }, [])

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!prompt) return

    setIsSubmitting(true)
    setStatus('PENDING')
    setSplatUrl(null)

    const formData = new FormData()
    formData.append('prompt', prompt)
    if (file) {
      formData.append('image', file)
    }

    try {
      const response = await axios.post('/api/generate', formData, {
        headers: { 'Content-Type': 'multipart/form-data' }
      })
      const newJobId = response.data.job_id
      setJobId(newJobId)
      pollStatus(newJobId)
    } catch (err) {
      console.error(err)
      setStatus('FAILED')
      setIsSubmitting(false)
    }
  }

  const pollStatus = async (id) => {
    if (intervalRef.current) clearInterval(intervalRef.current)
    
    intervalRef.current = setInterval(async () => {
      try {
        const res = await axios.get(`/api/status/${id}`)
        const state = res.data.status
        setStatus(state)
        
        if (state === 'READY') {
          clearInterval(intervalRef.current)
          setSplatUrl(res.data.ply_url)
          setIsSubmitting(false)
        } else if (state === 'FAILED' || state === 'NOT_FOUND') {
          clearInterval(intervalRef.current)
          setIsSubmitting(false)
          setStatus('FAILED')
        }
      } catch (err) {
        console.error(err)
        clearInterval(intervalRef.current)
        setIsSubmitting(false)
        setStatus('FAILED')
      }
    }, 2000)
  }

  return (
    <form onSubmit={handleSubmit} className="glass p-6 rounded-3xl flex flex-col gap-6 h-full border border-white/10 shadow-xl">
      <div className="flex flex-col gap-2">
        <label className="text-sm font-semibold text-slate-300 ml-1">Describe the Scene</label>
        <textarea 
          className="w-full bg-slate-900/50 border border-white/10 rounded-xl p-4 text-slate-100 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 resize-none h-32 transition-all"
          placeholder="A majestic marble statue of a lion in a roman courtyard, cinematic lighting, 8k resolution..."
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
        />
      </div>

      <div className="flex flex-col gap-2">
        <label className="text-sm font-semibold text-slate-300 ml-1">Reference Image (Optional)</label>
        <label className="w-full bg-slate-900/50 border border-white/10 border-dashed rounded-xl p-6 flex flex-col items-center justify-center cursor-pointer hover:bg-slate-800/50 transition-all text-slate-400 group">
          <input 
            type="file" 
            className="hidden" 
            accept="image/*"
            onChange={(e) => setFile(e.target.files[0])}
          />
          <ImagePlus className="w-8 h-8 mb-2 group-hover:scale-110 transition-transform" />
          <span className="text-sm text-center">{file ? file.name : 'Click to upload image'}</span>
        </label>
      </div>

      <div className="flex-1" />

      <button 
        disabled={isSubmitting || !prompt}
        className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-semibold py-4 rounded-xl flex items-center justify-center gap-2 transition-all disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-blue-500/25"
      >
        <Sparkles className="w-5 h-5" />
        {isSubmitting ? 'Generating...' : 'Generate 3D World'}
      </button>
    </form>
  )
}