Spaces:
Paused
Paused
File size: 5,470 Bytes
3d2098f | 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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | import { useState } from 'react'
import { api } from '../api'
import { ErrorBox } from '../hooks'
const csv = (s) => s.split(',').map((x) => x.trim()).filter(Boolean)
// mode="create": standalone form on the home screen.
// mode="edit": step 1 of the wizard, edits the existing project.
export default function SetupStep({ mode, data, refreshProject, setStep, onCreated, onCancel }) {
const refresh = refreshProject
const project = data?.project
const [form, setForm] = useState(() => ({
name: project?.name || '',
topic: project?.niche.topic || '',
keywords: project?.niche.keywords.join(', ') || '',
subreddits: project?.niche.subreddits.join(', ') || '',
audience: project?.niche.audience || '',
tone: project?.niche.tone || '',
format: project?.format || 'youtube_short',
}))
const [busy, setBusy] = useState(false)
const [filling, setFilling] = useState(false)
const [error, setError] = useState('')
const set = (k) => (e) => setForm({ ...form, [k]: e.target.value })
// Ask the LLM to derive the remaining fields from the niche topic.
// Only fills columns the user left blank, so it never overwrites their input.
const autofill = async () => {
if (!form.topic.trim()) { setError('Enter a niche topic first.'); return }
setFilling(true)
setError('')
try {
const s = await api.post('/api/niche/suggest', { topic: form.topic.trim() })
setForm((f) => ({
...f,
name: f.name.trim() || s.name || '',
keywords: f.keywords.trim() || (s.keywords || []).join(', '),
subreddits: f.subreddits.trim() || (s.subreddits || []).join(', '),
audience: f.audience.trim() || s.audience || '',
tone: f.tone.trim() || s.tone || '',
}))
} catch (e) {
setError(e.message)
} finally {
setFilling(false)
}
}
const body = () => ({
name: form.name || form.topic,
niche: {
topic: form.topic,
keywords: csv(form.keywords),
subreddits: csv(form.subreddits),
audience: form.audience,
tone: form.tone,
},
format: form.format,
references: project ? project.references : [],
})
const submit = async () => {
if (!form.topic.trim()) { setError('A niche topic is required.'); return }
setBusy(true)
setError('')
try {
if (mode === 'create') {
const created = await api.post('/api/projects', body())
onCreated(created)
} else {
await api.patch(`/api/projects/${project.id}`, body())
await refresh()
setStep(1)
}
} catch (e) {
setError(e.message)
} finally {
setBusy(false)
}
}
return (
<div>
<h2>{mode === 'create' ? 'New project' : 'Project setup'}</h2>
<p className="subtitle">Define the niche. Nothing here is hardcoded — every later stage builds on these fields.</p>
<ErrorBox error={error} />
<div className="card">
<label className="field"><span className="lbl">Project name</span>
<input type="text" value={form.name} onChange={set('name')} placeholder="e.g. Deep Sea Shorts" />
</label>
<label className="field"><span className="lbl">Niche topic *</span>
<div className="row">
<input className="grow" type="text" value={form.topic} onChange={set('topic')}
placeholder="e.g. deep sea creatures"
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); autofill() } }} />
<button className="btn secondary" onClick={autofill} disabled={filling || !form.topic.trim()}
title="Suggest keywords, subreddits, audience and tone from this niche (only fills blank fields)">
{filling ? 'Filling…' : '✨ Autofill'}
</button>
</div>
</label>
<div className="row">
<label className="field grow"><span className="lbl">Keywords (comma separated)</span>
<input type="text" value={form.keywords} onChange={set('keywords')} placeholder="ocean, marine biology" />
</label>
<label className="field grow"><span className="lbl">Subreddits (comma separated)</span>
<input type="text" value={form.subreddits} onChange={set('subreddits')} placeholder="thalassophobia, ocean" />
</label>
</div>
<div className="row">
<label className="field grow"><span className="lbl">Audience</span>
<input type="text" value={form.audience} onChange={set('audience')} placeholder="curious adults" />
</label>
<label className="field grow"><span className="lbl">Tone</span>
<input type="text" value={form.tone} onChange={set('tone')} placeholder="playful, awe-inspiring" />
</label>
</div>
<label className="field"><span className="lbl">Output format</span>
<select value={form.format} onChange={set('format')}>
<option value="youtube_short">YouTube Short (9:16)</option>
<option value="instagram_reel">Instagram Reel (9:16)</option>
</select>
</label>
<div className="row">
<button className="btn" onClick={submit} disabled={busy}>
{busy ? 'Saving…' : mode === 'create' ? 'Create project' : 'Save & continue'}
</button>
{onCancel && <button className="btn secondary" onClick={onCancel}>Cancel</button>}
</div>
</div>
</div>
)
}
|