feat: persist generation sources in post editor state and API

This commit is contained in:
Ender 2025-10-25 13:36:30 +02:00
parent 4d1f8298de
commit 4fd46f4d24
4 changed files with 22 additions and 6 deletions

View File

@ -31,6 +31,7 @@ export default function EditorShell({ onLogout: _onLogout, initialPostId, onBack
genImageKeys, genImageKeys,
generatedDraft, generatedDraft,
imagePlaceholders, imagePlaceholders,
generationSources,
// setters // setters
setDraft, setDraft,
setMeta, setMeta,
@ -40,6 +41,7 @@ export default function EditorShell({ onLogout: _onLogout, initialPostId, onBack
setActiveStep, setActiveStep,
setGeneratedDraft, setGeneratedDraft,
setImagePlaceholders, setImagePlaceholders,
setGenerationSources,
// actions // actions
savePost, savePost,
deletePost, deletePost,
@ -151,6 +153,7 @@ export default function EditorShell({ onLogout: _onLogout, initialPostId, onBack
onChangePrompt={setPromptText} onChangePrompt={setPromptText}
generatedDraft={generatedDraft} generatedDraft={generatedDraft}
imagePlaceholders={imagePlaceholders} imagePlaceholders={imagePlaceholders}
generationSources={generationSources}
onGeneratedDraft={(content) => { onGeneratedDraft={(content) => {
setGeneratedDraft(content); setGeneratedDraft(content);
void savePost({ generatedDraft: content }); void savePost({ generatedDraft: content });
@ -159,6 +162,10 @@ export default function EditorShell({ onLogout: _onLogout, initialPostId, onBack
setImagePlaceholders(placeholders); setImagePlaceholders(placeholders);
void savePost({ imagePlaceholders: placeholders }); void savePost({ imagePlaceholders: placeholders });
}} }}
onGenerationSources={(sources) => {
setGenerationSources(sources);
void savePost({ generationSources: sources });
}}
/> />
</StepContainer> </StepContainer>
)} )}

View File

@ -14,8 +14,10 @@ export default function StepGenerate({
onChangePrompt, onChangePrompt,
generatedDraft, generatedDraft,
imagePlaceholders, imagePlaceholders,
generationSources,
onGeneratedDraft, onGeneratedDraft,
onImagePlaceholders, onImagePlaceholders,
onGenerationSources,
}: { }: {
postClips: Clip[]; postClips: Clip[];
genImageKeys: string[]; genImageKeys: string[];
@ -24,13 +26,14 @@ export default function StepGenerate({
onChangePrompt: (v: string) => void; onChangePrompt: (v: string) => void;
generatedDraft: string; generatedDraft: string;
imagePlaceholders: string[]; imagePlaceholders: string[];
generationSources: Array<{ title: string; url: string }>;
onGeneratedDraft: (content: string) => void; onGeneratedDraft: (content: string) => void;
onImagePlaceholders: (placeholders: string[]) => void; onImagePlaceholders: (placeholders: string[]) => void;
onGenerationSources: (sources: Array<{ title: string; url: string }>) => void;
}) { }) {
const [generating, setGenerating] = useState(false); const [generating, setGenerating] = useState(false);
const [error, setError] = useState<string>(''); const [error, setError] = useState<string>('');
const [useWebSearch, setUseWebSearch] = useState(false); const [useWebSearch, setUseWebSearch] = useState(false);
const [sources, setSources] = useState<Array<{ title: string; url: string }>>([]);
return ( return (
<Box sx={{ display: 'grid', gap: 2 }}> <Box sx={{ display: 'grid', gap: 2 }}>
<StepHeader <StepHeader
@ -121,7 +124,7 @@ export default function StepGenerate({
onGeneratedDraft(result.content); onGeneratedDraft(result.content);
onImagePlaceholders(result.imagePlaceholders); onImagePlaceholders(result.imagePlaceholders);
setSources(result.sources || []); onGenerationSources(result.sources || []);
} catch (err: any) { } catch (err: any) {
setError(err?.message || 'Generation failed'); setError(err?.message || 'Generation failed');
} finally { } finally {
@ -149,11 +152,11 @@ export default function StepGenerate({
{generatedDraft && ( {generatedDraft && (
<CollapsibleSection title="Generated Draft"> <CollapsibleSection title="Generated Draft">
<Stack spacing={2}> <Stack spacing={2}>
{sources.length > 0 && ( {generationSources.length > 0 && (
<Alert severity="success"> <Alert severity="success">
<Typography variant="body2" sx={{ fontWeight: 'bold', mb: 0.5 }}>Sources ({sources.length}):</Typography> <Typography variant="body2" sx={{ fontWeight: 'bold', mb: 0.5 }}>Sources ({generationSources.length}):</Typography>
<Stack spacing={0.5}> <Stack spacing={0.5}>
{sources.map((source, idx) => ( {generationSources.map((source: { title: string; url: string }, idx: number) => (
<Typography key={idx} variant="caption" component="div"> <Typography key={idx} variant="caption" component="div">
{idx + 1}. <Link href={source.url} target="_blank" rel="noopener noreferrer">{source.title}</Link> {idx + 1}. <Link href={source.url} target="_blank" rel="noopener noreferrer">{source.title}</Link>
</Typography> </Typography>

View File

@ -18,6 +18,7 @@ export function usePostEditor(initialPostId?: string | null) {
const [genImageKeys, setGenImageKeys] = useState<string[]>([]); const [genImageKeys, setGenImageKeys] = useState<string[]>([]);
const [generatedDraft, setGeneratedDraft] = useState<string>(''); const [generatedDraft, setGeneratedDraft] = useState<string>('');
const [imagePlaceholders, setImagePlaceholders] = useState<string[]>([]); const [imagePlaceholders, setImagePlaceholders] = useState<string[]>([]);
const [generationSources, setGenerationSources] = useState<Array<{ title: string; url: string }>>([]);
const autoSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const autoSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => { useEffect(() => {
@ -45,6 +46,7 @@ export function usePostEditor(initialPostId?: string | null) {
if (Array.isArray(data.selectedImageKeys)) setGenImageKeys(data.selectedImageKeys); if (Array.isArray(data.selectedImageKeys)) setGenImageKeys(data.selectedImageKeys);
if (data.generatedDraft) setGeneratedDraft(data.generatedDraft); if (data.generatedDraft) setGeneratedDraft(data.generatedDraft);
if (Array.isArray(data.imagePlaceholders)) setImagePlaceholders(data.imagePlaceholders); if (Array.isArray(data.imagePlaceholders)) setImagePlaceholders(data.imagePlaceholders);
if (Array.isArray(data.generationSources)) setGenerationSources(data.generationSources);
} catch {} } catch {}
})(); })();
} }
@ -64,6 +66,7 @@ export function usePostEditor(initialPostId?: string | null) {
selectedImageKeys: genImageKeys.length > 0 ? genImageKeys : undefined, selectedImageKeys: genImageKeys.length > 0 ? genImageKeys : undefined,
generatedDraft: generatedDraft || undefined, generatedDraft: generatedDraft || undefined,
imagePlaceholders: imagePlaceholders.length > 0 ? imagePlaceholders : undefined, imagePlaceholders: imagePlaceholders.length > 0 ? imagePlaceholders : undefined,
generationSources: generationSources.length > 0 ? generationSources : undefined,
...(overrides || {}), ...(overrides || {}),
}; };
const data = await savePostApi(payload); const data = await savePostApi(payload);
@ -75,7 +78,7 @@ export function usePostEditor(initialPostId?: string | null) {
setToast({ open: true, message: 'Post saved', severity: 'success' }); setToast({ open: true, message: 'Post saved', severity: 'success' });
} }
return data; return data;
}, [postId, draft, meta, postStatus, promptText, genImageKeys, generatedDraft, imagePlaceholders]); }, [postId, draft, meta, postStatus, promptText, genImageKeys, generatedDraft, imagePlaceholders, generationSources]);
const deletePost = async () => { const deletePost = async () => {
if (!postId) return; if (!postId) return;
@ -176,6 +179,7 @@ export function usePostEditor(initialPostId?: string | null) {
genImageKeys, genImageKeys,
generatedDraft, generatedDraft,
imagePlaceholders, imagePlaceholders,
generationSources,
// setters // setters
setDraft, setDraft,
setMeta, setMeta,
@ -186,6 +190,7 @@ export function usePostEditor(initialPostId?: string | null) {
setActiveStep, setActiveStep,
setGeneratedDraft, setGeneratedDraft,
setImagePlaceholders, setImagePlaceholders,
setGenerationSources,
// actions // actions
savePost, savePost,
deletePost, deletePost,

View File

@ -10,6 +10,7 @@ export type SavePostPayload = {
selectedImageKeys?: string[]; selectedImageKeys?: string[];
generatedDraft?: string; generatedDraft?: string;
imagePlaceholders?: string[]; imagePlaceholders?: string[];
generationSources?: Array<{ title: string; url: string }>;
}; };
export async function getPost(id: string) { export async function getPost(id: string) {