import { useEffect, useState } from 'react' import { api, runJob } from '../api' import { useJob, ProgressBox, ErrorBox } from '../hooks' import PromptPanel from '../components/PromptPanel' const IG_TOOLTIP = 'Instagram pages are style-only: we never fetch or scrape them (against ' + "Instagram's terms, and there is no public API). Your notes feed the " + 'style prompt as text instead.' export default function ReferencesStep({ data, refreshProject, setStep, projectId }) { const project = data.project const [draft, setDraft] = useState({ kind: 'youtube', name: '', url: '', notes: '' }) const [busy, setBusy] = useState(false) const [suggestPrompt, setSuggestPrompt] = useState(null) const [stylePrompt, setStylePrompt] = useState(null) const suggestJob = useJob() const styleJob = useJob() // Editable style-card output const [card, setCard] = useState(data.style?.style_card || '') const [cardDirty, setCardDirty] = useState(false) const [cardSaving, setCardSaving] = useState(false) useEffect(() => { setCard(data.style?.style_card || ''); setCardDirty(false) }, [data.style]) const saveRefs = async (references) => { setBusy(true) try { await api.patch(`/api/projects/${projectId}`, { name: project.name, niche: project.niche, format: project.format, references, }) await refreshProject() } finally { setBusy(false) } } const addDraft = async () => { if (!draft.name.trim() && !draft.url.trim()) return await saveRefs([...project.references, { ...draft, channel_id: '' }]) setDraft({ kind: 'youtube', name: '', url: '', notes: '' }) } const remove = (i) => saveRefs(project.references.filter((_, j) => j !== i)) // Inline editing of an existing reference entry. const [editIdx, setEditIdx] = useState(null) const [editRef, setEditRef] = useState(null) const startEdit = (i) => { setEditIdx(i); setEditRef({ ...project.references[i] }) } const saveEdit = async () => { // Editing the name/url means the channel must be re-resolved, so clear it. const next = project.references.map((r, j) => (j === editIdx ? { ...editRef, channel_id: "" } : r)) setEditIdx(null) await saveRefs(next) } const suggest = async () => { try { const suggested = await suggestJob.start(`/api/projects/${projectId}/references/suggest`, { n: 5, prompt: suggestPrompt }) const existing = new Set(project.references.map((r) => r.name.toLowerCase())) const fresh = suggested.filter((r) => !existing.has(r.name.toLowerCase())) if (fresh.length) await saveRefs([...project.references, ...fresh]) } catch { /* shown */ } } const buildStyle = async () => { try { await styleJob.start(`/api/projects/${projectId}/style/refresh`, { prompt: stylePrompt }) await refreshProject() } catch { /* shown */ } } const saveCard = async () => { setCardSaving(true) try { await api.put(`/api/projects/${projectId}/style`, { ...data.style, style_card: card }) await refreshProject() setCardDirty(false) } catch (e) { styleJob.setError(e.message) } finally { setCardSaving(false) } } return (
YouTube references are data sources (top Shorts + transcripts drive topics and style). Instagram references are style-only inspiration.
No references yet — add some or use Suggest.
} {project.references.map((r, i) => ( editIdx === i ? (AI proposes channels for your niche and verifies each on YouTube.
Distilled from your references' top Shorts. Edit the prompt before building, and edit the resulting card.