Merge remote-tracking branch 'origin/codex/locate-and-render-brollinfopanel-component'

This commit is contained in:
ajaysi
2026-04-20 08:32:06 +05:30
2 changed files with 146 additions and 15 deletions

View File

@@ -8,6 +8,7 @@ import { GlassyCard, PrimaryButton, SecondaryButton } from "../ui";
import { SceneEditor } from "./SceneEditor"; import { SceneEditor } from "./SceneEditor";
import { InlineAudioPlayer } from "../InlineAudioPlayer"; import { InlineAudioPlayer } from "../InlineAudioPlayer";
import { aiApiClient } from "../../../api/client"; import { aiApiClient } from "../../../api/client";
import { BrollInfoPanel } from "./parts/BrollInfoPanel";
interface ScriptEditorProps { interface ScriptEditorProps {
projectId: string; projectId: string;
@@ -50,6 +51,7 @@ export const ScriptEditor: React.FC<ScriptEditorProps> = ({
const [approvingSceneId, setApprovingSceneId] = useState<string | null>(null); const [approvingSceneId, setApprovingSceneId] = useState<string | null>(null);
const [generatingAudioId, setGeneratingAudioId] = useState<string | null>(null); const [generatingAudioId, setGeneratingAudioId] = useState<string | null>(null);
const [showScriptFormatInfo, setShowScriptFormatInfo] = useState(false); const [showScriptFormatInfo, setShowScriptFormatInfo] = useState(false);
const [generatingChartId, setGeneratingChartId] = useState<string | null>(null);
const [combiningAudio, setCombiningAudio] = useState(false); const [combiningAudio, setCombiningAudio] = useState(false);
const [combinedAudioResult, setCombinedAudioResult] = useState<{ const [combinedAudioResult, setCombinedAudioResult] = useState<{
url: string; url: string;
@@ -277,6 +279,102 @@ export const ScriptEditor: React.FC<ScriptEditorProps> = ({
} }
}, [script, projectId, onError]); }, [script, projectId, onError]);
const generateChartPreviews = useCallback(async () => {
if (!script) return;
const scenesWithData = script.scenes.filter(
(scene) => scene.chart_data && Object.keys(scene.chart_data).length > 0
);
if (scenesWithData.length === 0) {
onError("No scenes have chart data to generate previews.");
return;
}
try {
setGeneratingChartId("all");
const updatedScenes = await Promise.all(
script.scenes.map(async (scene) => {
if (!scene.chart_data || Object.keys(scene.chart_data).length === 0) {
return scene;
}
try {
const result = await podcastApi.generateChartPreview({
chart_data: scene.chart_data,
chart_type: scene.chart_data.type || "bar_comparison",
title: scene.title,
});
return {
...scene,
broll_preview_url: result.preview_url,
chart_id: result.chart_id,
};
} catch (error) {
console.error(`Failed to generate chart preview for scene ${scene.id}:`, error);
return scene;
}
})
);
const updatedScript = { ...script, scenes: updatedScenes };
setScript(updatedScript);
emitScriptChange(updatedScript);
} catch (error: any) {
console.error("Chart preview generation failed:", error);
onError(`Failed to generate chart previews: ${error.message || error}`);
} finally {
setGeneratingChartId(null);
}
}, [script, emitScriptChange, onError]);
const regenerateChart = useCallback(async (sceneId: string) => {
if (!script) return;
const scene = script.scenes.find((s) => s.id === sceneId);
if (!scene?.chart_data) return;
try {
setGeneratingChartId(sceneId);
const result = await podcastApi.generateChartPreview({
chart_data: scene.chart_data,
chart_type: scene.chart_data.type || "bar_comparison",
title: scene.title,
});
const updatedScript = {
...script,
scenes: script.scenes.map((s) =>
s.id === sceneId
? { ...s, broll_preview_url: result.preview_url, chart_id: result.chart_id }
: s
),
};
setScript(updatedScript);
emitScriptChange(updatedScript);
} catch (error: any) {
console.error("Chart regeneration failed:", error);
onError(`Failed to regenerate chart: ${error.message || error}`);
} finally {
setGeneratingChartId(null);
}
}, [script, emitScriptChange, onError]);
const removeChart = useCallback((sceneId: string) => {
if (!script) return;
const updatedScript = {
...script,
scenes: script.scenes.map((scene) =>
scene.id === sceneId
? { ...scene, chart_data: undefined, broll_preview_url: undefined, broll_video_url: undefined }
: scene
),
};
setScript(updatedScript);
emitScriptChange(updatedScript);
}, [script, emitScriptChange]);
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 4 }}> <Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 4 }}>
@@ -607,6 +705,15 @@ export const ScriptEditor: React.FC<ScriptEditorProps> = ({
</Typography> </Typography>
</Alert> </Alert>
<BrollInfoPanel
activeScript={script}
generatingChartId={generatingChartId}
generateChartPreviews={generateChartPreviews}
regenerateChart={regenerateChart}
removeChart={removeChart}
scenesWithCharts={script.scenes.filter((s) => s.chart_data && Object.keys(s.chart_data).length > 0).length}
/>
<Stack spacing={2}> <Stack spacing={2}>
{script.scenes.map((scene, idx) => ( {script.scenes.map((scene, idx) => (
<GlassyCard <GlassyCard
@@ -837,4 +944,3 @@ export const ScriptEditor: React.FC<ScriptEditorProps> = ({
</Box> </Box>
); );
}; };

View File

@@ -2,24 +2,47 @@ import React from "react";
import { Stack, Box, Typography, Alert, Paper, Button, CircularProgress, Chip } from "@mui/material"; import { Stack, Box, Typography, Alert, Paper, Button, CircularProgress, Chip } from "@mui/material";
import { BarChart as BarChartIcon, AutoAwesome as AutoAwesomeIcon, Refresh as RefreshIcon, DeleteOutline as DeleteIcon } from "@mui/icons-material"; import { BarChart as BarChartIcon, AutoAwesome as AutoAwesomeIcon, Refresh as RefreshIcon, DeleteOutline as DeleteIcon } from "@mui/icons-material";
import { useScriptEditor } from "../ScriptEditorContext"; import { useScriptEditor } from "../ScriptEditorContext";
import { Script } from "../../types";
interface BrollInfoPanelProps {
activeScript?: Script | null;
generatingChartId?: string | null;
generateChartPreviews?: () => Promise<void>;
regenerateChart?: (sceneId: string) => Promise<void>;
removeChart?: (sceneId: string) => void;
scenesWithCharts?: number;
}
export const BrollInfoPanel: React.FC<BrollInfoPanelProps> = (props) => {
let contextValue: ReturnType<typeof useScriptEditor> | null = null;
try {
contextValue = useScriptEditor();
} catch {
contextValue = null;
}
export const BrollInfoPanel: React.FC = () => {
const { const {
activeScript, activeScript,
generatingChartId, generatingChartId,
setGeneratingChartId,
generateChartPreviews, generateChartPreviews,
regenerateChart, regenerateChart,
removeChart, removeChart,
scenesWithCharts scenesWithCharts
} = useScriptEditor(); } = contextValue ?? {};
if (!activeScript || activeScript.scenes.length === 0) { const resolvedActiveScript = props.activeScript ?? activeScript;
const resolvedGeneratingChartId = props.generatingChartId ?? generatingChartId;
const resolvedGenerateChartPreviews = props.generateChartPreviews ?? generateChartPreviews;
const resolvedRegenerateChart = props.regenerateChart ?? regenerateChart;
const resolvedRemoveChart = props.removeChart ?? removeChart;
if (!resolvedActiveScript || resolvedActiveScript.scenes.length === 0) {
return null; return null;
} }
const scenesWithData = activeScript.scenes.filter(s => s.chart_data && Object.keys(s.chart_data).length > 0); const scenesWithData = resolvedActiveScript.scenes.filter(s => s.chart_data && Object.keys(s.chart_data).length > 0);
const hasChartData = scenesWithData.length > 0; const hasChartData = scenesWithData.length > 0;
const resolvedScenesWithCharts = props.scenesWithCharts ?? scenesWithCharts ?? scenesWithData.length;
return ( return (
<Paper <Paper
@@ -45,7 +68,7 @@ export const BrollInfoPanel: React.FC = () => {
{hasChartData && ( {hasChartData && (
<Chip <Chip
label={`${scenesWithData.length} scene${scenesWithData.length > 1 ? 's' : ''} with charts`} label={`${resolvedScenesWithCharts} scene${resolvedScenesWithCharts > 1 ? 's' : ''} with charts`}
size="small" size="small"
sx={{ background: "rgba(34, 197, 94, 0.1)", color: "#16a34a", fontWeight: 600 }} sx={{ background: "rgba(34, 197, 94, 0.1)", color: "#16a34a", fontWeight: 600 }}
/> />
@@ -68,9 +91,9 @@ export const BrollInfoPanel: React.FC = () => {
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Button <Button
variant="contained" variant="contained"
startIcon={generatingChartId ? <CircularProgress size={16} color="inherit" /> : <AutoAwesomeIcon />} startIcon={resolvedGeneratingChartId ? <CircularProgress size={16} color="inherit" /> : <AutoAwesomeIcon />}
onClick={generateChartPreviews} onClick={resolvedGenerateChartPreviews}
disabled={!!generatingChartId} disabled={!!resolvedGeneratingChartId || !resolvedGenerateChartPreviews}
sx={{ sx={{
background: "linear-gradient(135deg, #22c55e 0%, #10b981 100%)", background: "linear-gradient(135deg, #22c55e 0%, #10b981 100%)",
"&:hover": { background: "linear-gradient(135deg, #16a34a 0%, #059669 100%)" }, "&:hover": { background: "linear-gradient(135deg, #16a34a 0%, #059669 100%)" },
@@ -78,7 +101,7 @@ export const BrollInfoPanel: React.FC = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
{generatingChartId ? "Generating..." : "Generate Chart Previews"} {resolvedGeneratingChartId ? "Generating..." : "Generate Chart Previews"}
</Button> </Button>
</Stack> </Stack>
@@ -104,7 +127,7 @@ export const BrollInfoPanel: React.FC = () => {
</Box> </Box>
<Stack direction="row" spacing={1}> <Stack direction="row" spacing={1}>
{generatingChartId === scene.id ? ( {resolvedGeneratingChartId === scene.id ? (
<CircularProgress size={20} /> <CircularProgress size={20} />
) : scene.broll_preview_url ? ( ) : scene.broll_preview_url ? (
<> <>
@@ -116,14 +139,16 @@ export const BrollInfoPanel: React.FC = () => {
<Button <Button
size="small" size="small"
startIcon={<RefreshIcon />} startIcon={<RefreshIcon />}
onClick={() => regenerateChart(scene.id)} onClick={() => resolvedRegenerateChart?.(scene.id)}
disabled={!resolvedRegenerateChart}
> >
Regenerate Regenerate
</Button> </Button>
<Button <Button
size="small" size="small"
startIcon={<DeleteIcon />} startIcon={<DeleteIcon />}
onClick={() => removeChart(scene.id)} onClick={() => resolvedRemoveChart?.(scene.id)}
disabled={!resolvedRemoveChart}
sx={{ color: "#ef4444" }} sx={{ color: "#ef4444" }}
> >
Remove Remove
@@ -137,4 +162,4 @@ export const BrollInfoPanel: React.FC = () => {
)} )}
</Paper> </Paper>
); );
}; };