Feat: Add TTS to analysis tabs and improve Research Queries UX

- Add TextToSpeechButton to Outline, Takeaways, and Guest tabs in analysis phase
- Add help icon with tooltip to Research Queries explaining the workflow
- Change Run Research button to show 'Next: Select Query' when disabled
- Add hint text 'Select a query to continue' when no queries selected
This commit is contained in:
ajaysi
2026-04-06 17:59:13 +05:30
parent 12960a22ea
commit 3f1d5cbb09
4 changed files with 50 additions and 5 deletions

View File

@@ -3,12 +3,15 @@ import { Stack, Box, Typography, Chip, Paper } from "@mui/material";
import { Quiz as TalkIcon } from "@mui/icons-material"; import { Quiz as TalkIcon } from "@mui/icons-material";
import { PodcastAnalysis } from "../../types"; import { PodcastAnalysis } from "../../types";
import { AnalysisTabContent } from "../AnalysisTabNav"; import { AnalysisTabContent } from "../AnalysisTabNav";
import { TextToSpeechButton } from "../../../shared/TextToSpeechButton";
interface GuestTabProps { interface GuestTabProps {
analysis: PodcastAnalysis; analysis: PodcastAnalysis;
} }
export const GuestTab: React.FC<GuestTabProps> = ({ analysis }) => { export const GuestTab: React.FC<GuestTabProps> = ({ analysis }) => {
const talkingPointsText = analysis.guest_talking_points?.map((p, idx) => `Question ${idx + 1}: ${p}`).join(" ") || "";
if (!analysis.guest_talking_points || analysis.guest_talking_points.length === 0) { if (!analysis.guest_talking_points || analysis.guest_talking_points.length === 0) {
return ( return (
<AnalysisTabContent title="Guest Talking Points" icon={<TalkIcon />}> <AnalysisTabContent title="Guest Talking Points" icon={<TalkIcon />}>
@@ -22,6 +25,9 @@ export const GuestTab: React.FC<GuestTabProps> = ({ analysis }) => {
return ( return (
<AnalysisTabContent title="Guest Talking Points" icon={<TalkIcon />}> <AnalysisTabContent title="Guest Talking Points" icon={<TalkIcon />}>
<Stack spacing={2}> <Stack spacing={2}>
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 1 }}>
<TextToSpeechButton text={talkingPointsText} size="small" showSettings />
</Box>
{analysis.guest_talking_points.map((point: string, idx: number) => ( {analysis.guest_talking_points.map((point: string, idx: number) => (
<Paper key={idx} elevation={0} sx={{ p: 2, bgcolor: "#faf5ff", border: "1px solid rgba(168,85,247,0.2)", borderRadius: 2, display: "flex", alignItems: "flex-start", gap: 1.5 }}> <Paper key={idx} elevation={0} sx={{ p: 2, bgcolor: "#faf5ff", border: "1px solid rgba(168,85,247,0.2)", borderRadius: 2, display: "flex", alignItems: "flex-start", gap: 1.5 }}>
<Chip label="Q" size="small" sx={{ minWidth: 24, bgcolor: "#a855f7", color: "#fff" }} /> <Chip label="Q" size="small" sx={{ minWidth: 24, bgcolor: "#a855f7", color: "#fff" }} />

View File

@@ -1,8 +1,9 @@
import React from "react"; import React from "react";
import { Stack, Box, Typography, Chip, TextField, IconButton } from "@mui/material"; import { Stack, Box, Typography, Chip } from "@mui/material";
import { ListAlt as ListAltIcon, Add as AddIcon } from "@mui/icons-material"; import { ListAlt as ListAltIcon } from "@mui/icons-material";
import { PodcastAnalysis } from "../../types"; import { PodcastAnalysis } from "../../types";
import { AnalysisTabContent } from "../AnalysisTabNav"; import { AnalysisTabContent } from "../AnalysisTabNav";
import { TextToSpeechButton } from "../../../shared/TextToSpeechButton";
interface OutlineTabProps { interface OutlineTabProps {
analysis: PodcastAnalysis; analysis: PodcastAnalysis;
@@ -11,6 +12,11 @@ interface OutlineTabProps {
} }
export const OutlineTab: React.FC<OutlineTabProps> = ({ analysis, isEditing, onUpdateOutline }) => { export const OutlineTab: React.FC<OutlineTabProps> = ({ analysis, isEditing, onUpdateOutline }) => {
const outlineText = analysis.suggestedOutlines?.map((outline, idx) => {
const segments = outline.segments?.map((s, sIdx) => `${sIdx + 1}. ${s}`).join(" ");
return `Option ${idx + 1}: ${outline.title}. ${segments}`;
}).join(" ") || "";
return ( return (
<AnalysisTabContent title="Episode Outline" icon={<ListAltIcon />}> <AnalysisTabContent title="Episode Outline" icon={<ListAltIcon />}>
<Stack spacing={3}> <Stack spacing={3}>
@@ -20,6 +26,10 @@ export const OutlineTab: React.FC<OutlineTabProps> = ({ analysis, isEditing, onU
<Typography variant="subtitle2" sx={{ color: "#0f172a", fontWeight: 700 }}> <Typography variant="subtitle2" sx={{ color: "#0f172a", fontWeight: 700 }}>
Option {idx + 1}: {outline.title} Option {idx + 1}: {outline.title}
</Typography> </Typography>
<TextToSpeechButton
text={`Option ${idx + 1}: ${outline.title}. ${outline.segments?.map((s, sIdx) => `Step ${sIdx + 1}: ${s}`).join(" ")}`}
size="small"
/>
</Stack> </Stack>
<Stack spacing={1}> <Stack spacing={1}>
{outline.segments?.map((segment: string, sIdx: number) => ( {outline.segments?.map((segment: string, sIdx: number) => (

View File

@@ -3,12 +3,15 @@ import { Stack, Box, Typography, Chip, Paper } from "@mui/material";
import { Lightbulb as TipsIcon } from "@mui/icons-material"; import { Lightbulb as TipsIcon } from "@mui/icons-material";
import { PodcastAnalysis } from "../../types"; import { PodcastAnalysis } from "../../types";
import { AnalysisTabContent } from "../AnalysisTabNav"; import { AnalysisTabContent } from "../AnalysisTabNav";
import { TextToSpeechButton } from "../../../shared/TextToSpeechButton";
interface TakeawaysTabProps { interface TakeawaysTabProps {
analysis: PodcastAnalysis; analysis: PodcastAnalysis;
} }
export const TakeawaysTab: React.FC<TakeawaysTabProps> = ({ analysis }) => { export const TakeawaysTab: React.FC<TakeawaysTabProps> = ({ analysis }) => {
const takeawaysText = analysis.key_takeaways?.map((t, idx) => `Takeaway ${idx + 1}: ${t}`).join(" ") || "";
if (!analysis.key_takeaways || analysis.key_takeaways.length === 0) { if (!analysis.key_takeaways || analysis.key_takeaways.length === 0) {
return ( return (
<AnalysisTabContent title="Key Takeaways" icon={<TipsIcon />}> <AnalysisTabContent title="Key Takeaways" icon={<TipsIcon />}>
@@ -22,6 +25,9 @@ export const TakeawaysTab: React.FC<TakeawaysTabProps> = ({ analysis }) => {
return ( return (
<AnalysisTabContent title="Key Takeaways" icon={<TipsIcon />}> <AnalysisTabContent title="Key Takeaways" icon={<TipsIcon />}>
<Stack spacing={2}> <Stack spacing={2}>
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 1 }}>
<TextToSpeechButton text={takeawaysText} size="small" showSettings />
</Box>
{analysis.key_takeaways.map((takeaway: string, idx: number) => ( {analysis.key_takeaways.map((takeaway: string, idx: number) => (
<Paper key={idx} elevation={0} sx={{ p: 2, bgcolor: "#f0fdf4", border: "1px solid rgba(34,197,94,0.2)", borderRadius: 2, display: "flex", alignItems: "flex-start", gap: 1.5 }}> <Paper key={idx} elevation={0} sx={{ p: 2, bgcolor: "#f0fdf4", border: "1px solid rgba(34,197,94,0.2)", borderRadius: 2, display: "flex", alignItems: "flex-start", gap: 1.5 }}>
<Chip label={idx + 1} size="small" sx={{ minWidth: 24, bgcolor: "#22c55e", color: "#fff" }} /> <Chip label={idx + 1} size="small" sx={{ minWidth: 24, bgcolor: "#22c55e", color: "#fff" }} />

View File

@@ -23,7 +23,7 @@ import {
TextField, TextField,
IconButton, IconButton,
} from "@mui/material"; } from "@mui/material";
import { Search as SearchIcon, AutoAwesome as AutoAwesomeIcon, Refresh as RefreshIcon, Edit as EditIcon, Delete as DeleteIcon, CheckCircle as CheckCircleIcon } from "@mui/icons-material"; import { Search as SearchIcon, AutoAwesome as AutoAwesomeIcon, Refresh as RefreshIcon, Edit as EditIcon, Delete as DeleteIcon, CheckCircle as CheckCircleIcon, Help as HelpIcon } from "@mui/icons-material";
import { ResearchProvider } from "../../../services/blogWriterApi"; import { ResearchProvider } from "../../../services/blogWriterApi";
import { Query } from "../types"; import { Query } from "../types";
import { GlassyCard, glassyCardSx, PrimaryButton, SecondaryButton } from "../ui"; import { GlassyCard, glassyCardSx, PrimaryButton, SecondaryButton } from "../ui";
@@ -113,6 +113,24 @@ export const QuerySelection: React.FC<QuerySelectionProps> = ({
<SearchIcon /> <SearchIcon />
Research Queries Research Queries
</Typography> </Typography>
<Tooltip
title={
<Box sx={{ maxWidth: 320 }}>
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
How it works:
</Typography>
<Typography variant="body2" component="div" sx={{ fontSize: "0.8125rem", lineHeight: 1.5 }}>
1. Select one or more research queries to focus your research.<br/><br/>
2. Click "Run Research" to gather web and semantic insights.<br/><br/>
3. The research results will be used to generate a factual, relevant podcast script.
</Typography>
</Box>
}
arrow
placement="top"
>
<HelpIcon fontSize="small" sx={{ color: "#94a3b8", cursor: "help", ml: 0.5 }} />
</Tooltip>
<Tooltip title="Regenerate research queries with custom feedback"> <Tooltip title="Regenerate research queries with custom feedback">
<PrimaryButton <PrimaryButton
size="small" size="small"
@@ -256,7 +274,12 @@ export const QuerySelection: React.FC<QuerySelectionProps> = ({
))} ))}
</List> </List>
<Box sx={{ display: "flex", justifyContent: "flex-end" }}> <Box sx={{ display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 2, flexWrap: "wrap" }}>
{selectedCount === 0 && (
<Typography variant="caption" sx={{ color: "#64748b", fontStyle: "italic" }}>
Select a query to continue
</Typography>
)}
<PrimaryButton <PrimaryButton
onClick={onRunResearch} onClick={onRunResearch}
disabled={selectedCount === 0 || isResearching} disabled={selectedCount === 0 || isResearching}
@@ -268,7 +291,7 @@ export const QuerySelection: React.FC<QuerySelectionProps> = ({
: `Run research with ${selectedCount} selected ${selectedCount === 1 ? "query" : "queries"}` : `Run research with ${selectedCount} selected ${selectedCount === 1 ? "query" : "queries"}`
} }
> >
{isResearching ? "Running Research..." : "Run Research"} {isResearching ? "Running Research..." : selectedCount === 0 ? "Next: Select Query" : "Run Research"}
</PrimaryButton> </PrimaryButton>
</Box> </Box>
</Stack> </Stack>