Add structured podcast research cost_est across backend/frontend
This commit is contained in:
@@ -130,10 +130,10 @@ export const ResearchSummary: React.FC<ResearchSummaryProps> = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{research.cost !== undefined && (
|
||||
{research.costEst?.total !== undefined && (
|
||||
<Chip
|
||||
icon={<AttachMoneyIcon sx={{ fontSize: "0.875rem !important" }} />}
|
||||
label={`$${research.cost.toFixed(3)}`}
|
||||
label={`$${research.costEst.total.toFixed(3)}`}
|
||||
size="small"
|
||||
sx={{
|
||||
background: alpha("#f59e0b", 0.1),
|
||||
@@ -356,4 +356,3 @@ export const ResearchSummary: React.FC<ResearchSummaryProps> = ({
|
||||
</GlassyCard>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,16 @@ export type ResearchInsight = {
|
||||
source_indices: number[];
|
||||
};
|
||||
|
||||
export type PodcastCostEst = {
|
||||
total: number;
|
||||
breakdown: {
|
||||
phase: "Analyze" | "Gather" | "Write" | "Produce";
|
||||
cost: number;
|
||||
}[];
|
||||
currency: "USD";
|
||||
last_updated: string;
|
||||
};
|
||||
|
||||
export type Research = {
|
||||
summary: string;
|
||||
keyInsights: ResearchInsight[];
|
||||
@@ -45,7 +55,7 @@ export type Research = {
|
||||
searchQueries?: string[];
|
||||
searchType?: string;
|
||||
provider?: string;
|
||||
cost?: number;
|
||||
costEst?: PodcastCostEst;
|
||||
sourceCount?: number;
|
||||
expertQuotes?: { quote: string; source_index: number }[];
|
||||
listenerCta?: string[];
|
||||
@@ -222,4 +232,3 @@ export type TaskStatus = {
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -95,6 +95,30 @@ const DEFAULT_STATE: PodcastProjectState = {
|
||||
|
||||
const STORAGE_KEY = 'podcast_project_state';
|
||||
|
||||
const normalizeResearchCostEst = (research: any): Research | null => {
|
||||
if (!research) return research;
|
||||
|
||||
const fromSnakeCase = research.cost_est;
|
||||
const fromCamelCase = research.costEst;
|
||||
const legacyCost = typeof research.cost === "number" ? research.cost : undefined;
|
||||
const normalizedCostEst = fromCamelCase || (fromSnakeCase ? {
|
||||
total: Number(fromSnakeCase.total || 0),
|
||||
breakdown: Array.isArray(fromSnakeCase.breakdown) ? fromSnakeCase.breakdown : [],
|
||||
currency: fromSnakeCase.currency || "USD",
|
||||
last_updated: fromSnakeCase.last_updated || new Date().toISOString(),
|
||||
} : undefined);
|
||||
|
||||
return {
|
||||
...research,
|
||||
costEst: normalizedCostEst || (legacyCost !== undefined ? {
|
||||
total: legacyCost,
|
||||
breakdown: [],
|
||||
currency: "USD",
|
||||
last_updated: new Date().toISOString(),
|
||||
} : undefined),
|
||||
};
|
||||
};
|
||||
|
||||
export const usePodcastProjectState = () => {
|
||||
const [state, setState] = useState<PodcastProjectState>(() => {
|
||||
// Initialize from localStorage if available
|
||||
@@ -107,6 +131,7 @@ export const usePodcastProjectState = () => {
|
||||
const restoredState: PodcastProjectState = {
|
||||
...DEFAULT_STATE,
|
||||
...parsed,
|
||||
research: normalizeResearchCostEst(parsed.research),
|
||||
selectedQueries: parsed.selectedQueries ? new Set(parsed.selectedQueries) : new Set(),
|
||||
renderJobs: parsed.renderJobs || [],
|
||||
};
|
||||
@@ -401,7 +426,7 @@ export const usePodcastProjectState = () => {
|
||||
analysis: dbProject.analysis,
|
||||
queries: dbProject.queries || [],
|
||||
selectedQueries: new Set(dbProject.selected_queries || []),
|
||||
research: dbProject.research,
|
||||
research: normalizeResearchCostEst(dbProject.research),
|
||||
rawResearch: dbProject.raw_research,
|
||||
estimate: dbProject.estimate,
|
||||
scriptData: dbProject.script_data,
|
||||
@@ -454,4 +479,3 @@ export const usePodcastProjectState = () => {
|
||||
loadProjectFromDb,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -173,7 +173,12 @@ const mapSourcesToFacts = (sources: ExaSource[]): Fact[] => {
|
||||
type ExaResearchResult = {
|
||||
sources: ExaSource[];
|
||||
search_queries?: string[];
|
||||
cost?: { total?: number };
|
||||
cost_est?: {
|
||||
total?: number;
|
||||
breakdown?: { phase: "Analyze" | "Gather" | "Write" | "Produce"; cost: number }[];
|
||||
currency?: "USD";
|
||||
last_updated?: string;
|
||||
};
|
||||
search_type?: string;
|
||||
provider?: string;
|
||||
content?: string;
|
||||
@@ -212,7 +217,14 @@ const mapExaResearchResponse = (response: any): Research => {
|
||||
searchQueries: response.search_queries,
|
||||
searchType: response.search_type,
|
||||
provider: response.provider || "exa",
|
||||
cost: response.cost?.total,
|
||||
costEst: response.cost_est
|
||||
? {
|
||||
total: Number(response.cost_est.total || 0),
|
||||
breakdown: Array.isArray(response.cost_est.breakdown) ? response.cost_est.breakdown : [],
|
||||
currency: response.cost_est.currency || "USD",
|
||||
last_updated: response.cost_est.last_updated || new Date().toISOString(),
|
||||
}
|
||||
: undefined,
|
||||
sourceCount: response.sources?.length || 0,
|
||||
};
|
||||
};
|
||||
@@ -953,4 +965,3 @@ export const podcastApi = {
|
||||
};
|
||||
|
||||
export type PodcastApi = typeof podcastApi;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user