Research Wizard and CopilotKit mitigation review
This commit is contained in:
@@ -270,6 +270,7 @@ export const BlogWriter: React.FC = () => {
|
||||
handleOutlineAction,
|
||||
handleContentAction,
|
||||
handleSEOAction,
|
||||
handleApplySEORecommendations,
|
||||
handlePublishAction,
|
||||
} = usePhaseActionHandlers({
|
||||
research,
|
||||
@@ -284,6 +285,7 @@ export const BlogWriter: React.FC = () => {
|
||||
outlineGenRef,
|
||||
setOutline,
|
||||
setContentConfirmed,
|
||||
setIsSEOAnalysisModalOpen,
|
||||
setIsSEOMetadataModalOpen,
|
||||
runSEOAnalysisDirect,
|
||||
onOutlineComplete: handleCachedOutlineComplete,
|
||||
@@ -391,6 +393,7 @@ export const BlogWriter: React.FC = () => {
|
||||
onOutlineAction: handleOutlineAction,
|
||||
onContentAction: handleContentAction,
|
||||
onSEOAction: handleSEOAction,
|
||||
onApplySEORecommendations: handleApplySEORecommendations,
|
||||
onPublishAction: handlePublishAction,
|
||||
}}
|
||||
hasResearch={!!research}
|
||||
@@ -399,6 +402,7 @@ export const BlogWriter: React.FC = () => {
|
||||
hasContent={Object.keys(sections).length > 0}
|
||||
contentConfirmed={contentConfirmed}
|
||||
hasSEOAnalysis={!!seoAnalysis}
|
||||
seoRecommendationsApplied={seoRecommendationsApplied}
|
||||
hasSEOMetadata={!!seoMetadata}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ interface HeaderBarProps {
|
||||
hasContent?: boolean;
|
||||
contentConfirmed?: boolean;
|
||||
hasSEOAnalysis?: boolean;
|
||||
seoRecommendationsApplied?: boolean;
|
||||
hasSEOMetadata?: boolean;
|
||||
}
|
||||
|
||||
@@ -28,6 +29,7 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
hasContent = false,
|
||||
contentConfirmed = false,
|
||||
hasSEOAnalysis = false,
|
||||
seoRecommendationsApplied = false,
|
||||
hasSEOMetadata = false,
|
||||
}) => {
|
||||
return (
|
||||
@@ -61,6 +63,7 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
hasContent={hasContent}
|
||||
contentConfirmed={contentConfirmed}
|
||||
hasSEOAnalysis={hasSEOAnalysis}
|
||||
seoRecommendationsApplied={seoRecommendationsApplied}
|
||||
hasSEOMetadata={hasSEOMetadata}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -25,28 +25,28 @@ export const WriterCopilotSidebar: React.FC<WriterCopilotSidebarProps> = ({
|
||||
}}
|
||||
suggestions={suggestions}
|
||||
makeSystemMessage={(context: string, additional?: string) => {
|
||||
const hasResearch = research !== null;
|
||||
const hasOutline = outline.length > 0;
|
||||
const hasResearch = research !== null && research !== undefined;
|
||||
const hasOutline = outline && outline.length > 0;
|
||||
const isOutlineConfirmed = outlineConfirmed;
|
||||
const researchInfo = hasResearch
|
||||
const researchInfo = hasResearch && research
|
||||
? {
|
||||
sources: research.sources?.length || 0,
|
||||
queries: research.search_queries?.length || 0,
|
||||
angles: research.suggested_angles?.length || 0,
|
||||
primaryKeywords: research.keyword_analysis?.primary || [],
|
||||
searchIntent: research.keyword_analysis?.search_intent || 'informational',
|
||||
sources: research?.sources?.length || 0,
|
||||
queries: research?.search_queries?.length || 0,
|
||||
angles: research?.suggested_angles?.length || 0,
|
||||
primaryKeywords: research?.keyword_analysis?.primary || [],
|
||||
searchIntent: research?.keyword_analysis?.search_intent || 'informational',
|
||||
}
|
||||
: null;
|
||||
|
||||
const outlineContext = hasOutline
|
||||
const outlineContext = hasOutline && outline
|
||||
? `
|
||||
OUTLINE DETAILS:
|
||||
- Total sections: ${outline.length}
|
||||
- Section headings: ${outline.map((s: any) => s.heading).join(', ')}
|
||||
- Total target words: ${outline.reduce((sum: number, s: any) => sum + (s.target_words || 0), 0)}
|
||||
- Section breakdown: ${outline
|
||||
- Section headings: ${(outline || []).map((s: any) => s?.heading || 'Untitled').join(', ')}
|
||||
- Total target words: ${(outline || []).reduce((sum: number, s: any) => sum + (s?.target_words || 0), 0)}
|
||||
- Section breakdown: ${(outline || [])
|
||||
.map(
|
||||
(s: any) => `${s.heading} (${s.target_words || 0} words, ${s.subheadings?.length || 0} subheadings, ${s.key_points?.length || 0} key points)`
|
||||
(s: any) => `${s?.heading || 'Untitled'} (${s?.target_words || 0} words, ${s?.subheadings?.length || 0} subheadings, ${s?.key_points?.length || 0} key points)`
|
||||
)
|
||||
.join('; ')}
|
||||
`
|
||||
@@ -65,7 +65,7 @@ ${hasResearch && researchInfo ? `
|
||||
- Search intent: ${researchInfo.searchIntent}
|
||||
` : '❌ No research completed yet'}
|
||||
|
||||
${hasOutline ? `✅ OUTLINE GENERATED: ${outline.length} sections created${isOutlineConfirmed ? ' (CONFIRMED)' : ' (PENDING CONFIRMATION)'}` : '❌ No outline generated yet'}
|
||||
${hasOutline && outline ? `✅ OUTLINE GENERATED: ${outline.length} sections created${isOutlineConfirmed ? ' (CONFIRMED)' : ' (PENDING CONFIRMATION)'}` : '❌ No outline generated yet'}
|
||||
${outlineContext}
|
||||
|
||||
Available tools:
|
||||
|
||||
@@ -17,6 +17,7 @@ interface UsePhaseActionHandlersProps {
|
||||
outlineGenRef: React.RefObject<any>;
|
||||
setOutline: (outline: any[]) => void;
|
||||
setContentConfirmed: (confirmed: boolean) => void;
|
||||
setIsSEOAnalysisModalOpen: (open: boolean) => void;
|
||||
setIsSEOMetadataModalOpen: (open: boolean) => void;
|
||||
runSEOAnalysisDirect: () => string;
|
||||
onOutlineComplete?: (outline: any) => void;
|
||||
@@ -36,6 +37,7 @@ export const usePhaseActionHandlers = ({
|
||||
outlineGenRef,
|
||||
setOutline,
|
||||
setContentConfirmed,
|
||||
setIsSEOAnalysisModalOpen,
|
||||
setIsSEOMetadataModalOpen,
|
||||
runSEOAnalysisDirect,
|
||||
onOutlineComplete,
|
||||
@@ -162,13 +164,20 @@ export const usePhaseActionHandlers = ({
|
||||
}
|
||||
navigateToPhase('seo');
|
||||
runSEOAnalysisDirect();
|
||||
debug.log('[BlogWriter] SEO action triggered');
|
||||
debug.log('[BlogWriter] SEO action triggered - running SEO analysis');
|
||||
}, [contentConfirmed, setContentConfirmed, navigateToPhase, runSEOAnalysisDirect]);
|
||||
|
||||
const handleApplySEORecommendations = useCallback(() => {
|
||||
navigateToPhase('seo');
|
||||
setIsSEOAnalysisModalOpen(true);
|
||||
debug.log('[BlogWriter] Apply SEO Recommendations action triggered - opening SEO analysis modal');
|
||||
}, [navigateToPhase, setIsSEOAnalysisModalOpen]);
|
||||
|
||||
const handlePublishAction = useCallback(() => {
|
||||
navigateToPhase('publish');
|
||||
// Can be called from SEO phase (after recommendations applied) or publish phase
|
||||
navigateToPhase('seo'); // Stay in SEO phase if called from there
|
||||
setIsSEOMetadataModalOpen(true);
|
||||
debug.log('[BlogWriter] Publish action triggered - opening SEO metadata modal');
|
||||
debug.log('[BlogWriter] Generate SEO Metadata action triggered - opening SEO metadata modal');
|
||||
}, [navigateToPhase, setIsSEOMetadataModalOpen]);
|
||||
|
||||
return {
|
||||
@@ -176,6 +185,7 @@ export const usePhaseActionHandlers = ({
|
||||
handleOutlineAction,
|
||||
handleContentAction,
|
||||
handleSEOAction,
|
||||
handleApplySEORecommendations,
|
||||
handlePublishAction,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface PhaseActionHandlers {
|
||||
onOutlineAction?: () => void; // Generate outline
|
||||
onContentAction?: () => void; // Confirm outline + generate content
|
||||
onSEOAction?: () => void; // Run SEO analysis
|
||||
onApplySEORecommendations?: () => void; // Apply SEO recommendations
|
||||
onPublishAction?: () => void; // Generate SEO metadata or publish
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ interface PhaseNavigationProps {
|
||||
hasContent?: boolean;
|
||||
contentConfirmed?: boolean;
|
||||
hasSEOAnalysis?: boolean;
|
||||
seoRecommendationsApplied?: boolean;
|
||||
hasSEOMetadata?: boolean;
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
hasContent = false,
|
||||
contentConfirmed = false,
|
||||
hasSEOAnalysis = false,
|
||||
seoRecommendationsApplied = false,
|
||||
hasSEOMetadata = false,
|
||||
}) => {
|
||||
// Determine which action to show for each phase when CopilotKit is unavailable
|
||||
@@ -61,7 +64,10 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
}
|
||||
break;
|
||||
case 'outline':
|
||||
if (hasResearch && !hasOutline) {
|
||||
// Show "Create Outline" if research exists and outline is not yet confirmed
|
||||
// This ensures users can create/regenerate outline after research, even if cached one exists
|
||||
// Once outline is confirmed, we hide the button to avoid confusion during content generation
|
||||
if (hasResearch && !outlineConfirmed) {
|
||||
return { label: 'Create Outline', handler: actionHandlers.onOutlineAction || null };
|
||||
}
|
||||
break;
|
||||
@@ -71,13 +77,26 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
}
|
||||
break;
|
||||
case 'seo':
|
||||
if (hasContent && contentConfirmed && !hasSEOAnalysis) {
|
||||
// Priority order matching CopilotKit suggestions:
|
||||
// 1. No SEO analysis yet - Run SEO Analysis
|
||||
// Note: We check hasContent (sections exist) - contentConfirmed is checked but not strictly required
|
||||
// This allows users to run SEO analysis even if contentConfirmed hasn't been explicitly set
|
||||
if (hasContent && !hasSEOAnalysis) {
|
||||
return { label: 'Run SEO Analysis', handler: actionHandlers.onSEOAction || null };
|
||||
}
|
||||
// 2. SEO analysis exists but recommendations not applied - Apply SEO Recommendations
|
||||
if (hasSEOAnalysis && !seoRecommendationsApplied) {
|
||||
return { label: 'Apply SEO Recommendations', handler: actionHandlers.onApplySEORecommendations || null };
|
||||
}
|
||||
// 3. SEO analysis exists and recommendations applied but no metadata - Generate SEO Metadata
|
||||
if (hasSEOAnalysis && seoRecommendationsApplied && !hasSEOMetadata) {
|
||||
return { label: 'Generate SEO Metadata', handler: actionHandlers.onPublishAction || null };
|
||||
}
|
||||
break;
|
||||
case 'publish':
|
||||
if (hasSEOAnalysis && !hasSEOMetadata) {
|
||||
return { label: 'Generate SEO Metadata', handler: actionHandlers.onPublishAction || null };
|
||||
// Only show if SEO metadata exists (ready to publish)
|
||||
if (hasSEOAnalysis && seoRecommendationsApplied && hasSEOMetadata) {
|
||||
return { label: 'Ready to Publish', handler: null }; // Publish handled separately
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -97,17 +116,59 @@ export const PhaseNavigation: React.FC<PhaseNavigationProps> = ({
|
||||
const isCompleted = phase.completed;
|
||||
const isDisabled = phase.disabled;
|
||||
const action = getActionForPhase(phase.id);
|
||||
|
||||
// Show action button when:
|
||||
// 1. CopilotKit is unavailable
|
||||
// 2. Action handler exists
|
||||
// 3. Phase is not disabled
|
||||
// 4. Show for current phase OR next actionable phase (not completed)
|
||||
// For research phase specifically, always show if no research exists
|
||||
// 4. Show for current phase OR next actionable phase (not completed) OR phases with available actions
|
||||
// For research phase: always show if no research exists
|
||||
// For outline phase: always show if research exists but no outline (like research phase)
|
||||
// For SEO phase: always show if action handler exists (prerequisites are met)
|
||||
const isResearchPhase = phase.id === 'research' && !hasResearch;
|
||||
const showAction = !copilotKitAvailable && action.handler && !isDisabled && (
|
||||
// Outline phase: show action whenever research exists and action handler is available
|
||||
// This allows users to create/regenerate outline after research, even if cached one exists
|
||||
const isOutlinePhase = phase.id === 'outline' && hasResearch && action.handler;
|
||||
// SEO phase: show action whenever prerequisites are met (action handler exists)
|
||||
// Similar to research/outline, show SEO actions whenever handler exists and phase is enabled
|
||||
const isSEOPhase = phase.id === 'seo' && action.handler;
|
||||
|
||||
// Debug logging for SEO phase (temporary - for troubleshooting)
|
||||
if (phase.id === 'seo' && !copilotKitAvailable && process.env.NODE_ENV === 'development') {
|
||||
console.log('[PhaseNavigation] SEO phase debug:', {
|
||||
phaseId: phase.id,
|
||||
isCurrent,
|
||||
isCompleted,
|
||||
isDisabled,
|
||||
hasContent,
|
||||
contentConfirmed,
|
||||
hasSEOAnalysis,
|
||||
seoRecommendationsApplied,
|
||||
hasSEOMetadata,
|
||||
actionLabel: action.label,
|
||||
actionHandler: !!action.handler,
|
||||
copilotKitAvailable,
|
||||
isSEOPhase,
|
||||
showActionWillBe: !copilotKitAvailable && action.handler && !isDisabled && (
|
||||
isCurrent ||
|
||||
(!isCompleted && !isDisabled) ||
|
||||
isResearchPhase ||
|
||||
isOutlinePhase ||
|
||||
isSEOPhase
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// Show action if: current phase, or phase is not completed and not disabled, or it's research/outline/SEO with available action
|
||||
// For SEO: show whenever action handler exists (prerequisites are met), even if phase is marked as disabled/completed
|
||||
// This is critical because SEO prerequisites (hasContent && contentConfirmed) are validated in getActionForPhase,
|
||||
// so if action.handler exists, we should show it regardless of phase navigation's disabled state
|
||||
const showAction = !copilotKitAvailable && action.handler && (
|
||||
isCurrent ||
|
||||
(!isCompleted && !isDisabled) ||
|
||||
isResearchPhase
|
||||
isResearchPhase ||
|
||||
isOutlinePhase ||
|
||||
isSEOPhase // Show SEO actions when handler exists - handler existence means prerequisites are met, so ignore isDisabled
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import React from 'react';
|
||||
import { BlogResearchResponse } from '../../../services/blogWriterApi';
|
||||
|
||||
interface GoogleSearchModalProps {
|
||||
research: BlogResearchResponse;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const GoogleSearchModal: React.FC<GoogleSearchModalProps> = ({ research, onClose }) => {
|
||||
if (!research.search_widget && !research.search_queries?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSearchClick = (query: string) => {
|
||||
// Open Google Search in new tab per Google requirements
|
||||
const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
|
||||
window.open(searchUrl, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1000,
|
||||
backdropFilter: 'blur(4px)'
|
||||
}}
|
||||
onClick={onClose}>
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '32px',
|
||||
maxWidth: '800px',
|
||||
width: '90%',
|
||||
maxHeight: '90vh',
|
||||
overflow: 'auto',
|
||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
border: '1px solid #e5e7eb',
|
||||
position: 'relative'
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
{/* Header */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '24px',
|
||||
borderBottom: '2px solid #f3f4f6',
|
||||
paddingBottom: '16px'
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
<div style={{
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
backgroundColor: '#f8fafc',
|
||||
borderRadius: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px solid #e2e8f0'
|
||||
}}>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="#4285F4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={{
|
||||
margin: 0,
|
||||
color: '#1f2937',
|
||||
fontSize: '24px',
|
||||
fontWeight: '700'
|
||||
}}>
|
||||
Google Search Suggestions
|
||||
</h3>
|
||||
<p style={{
|
||||
margin: '4px 0 0 0',
|
||||
color: '#6b7280',
|
||||
fontSize: '14px'
|
||||
}}>
|
||||
Explore related searches and sources
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
fontSize: '28px',
|
||||
cursor: 'pointer',
|
||||
color: '#6b7280',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'all 0.2s ease',
|
||||
width: '40px',
|
||||
height: '40px'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
||||
e.currentTarget.style.color = '#374151';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
e.currentTarget.style.color = '#6b7280';
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Google Search Widget - Display exactly as provided per Google requirements */}
|
||||
{research.search_widget && (
|
||||
<div style={{
|
||||
marginBottom: '32px',
|
||||
width: '100%',
|
||||
padding: '20px',
|
||||
backgroundColor: '#f8fafc',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid #e2e8f0'
|
||||
}}>
|
||||
<div style={{
|
||||
marginBottom: '12px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '600',
|
||||
color: '#475569',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px'
|
||||
}}>
|
||||
<span>🔍</span>
|
||||
<span>Search Suggestions (Click to open in Google)</span>
|
||||
</div>
|
||||
{/* Render Google's HTML exactly as provided - no modifications */}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: research.search_widget }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search Queries List */}
|
||||
{research.search_queries && research.search_queries.length > 0 && (
|
||||
<div style={{ marginTop: '32px' }}>
|
||||
<h4 style={{
|
||||
margin: '0 0 16px 0',
|
||||
color: '#1f2937',
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<span>📋</span>
|
||||
Additional Search Queries
|
||||
</h4>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{research.search_queries.map((query, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleSearchClick(query)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '12px 16px',
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
fontSize: '14px',
|
||||
color: '#374151',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f9fafb';
|
||||
e.currentTarget.style.borderColor = '#4285F4';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'white';
|
||||
e.currentTarget.style.borderColor = '#d1d5db';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
<span style={{ flex: 1 }}>{query}</span>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0 }}>
|
||||
<path d="M7 17L17 7M17 7H7M17 7V17" stroke="#4285F4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Footer */}
|
||||
<div style={{
|
||||
marginTop: '24px',
|
||||
padding: '16px',
|
||||
backgroundColor: '#eff6ff',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #bfdbfe'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: '8px',
|
||||
fontSize: '13px',
|
||||
color: '#1e40af'
|
||||
}}>
|
||||
<span style={{ fontSize: '16px', lineHeight: '1.5' }}>ℹ️</span>
|
||||
<div>
|
||||
<div style={{ fontWeight: '600', marginBottom: '4px' }}>
|
||||
About These Suggestions
|
||||
</div>
|
||||
<div style={{ lineHeight: '1.6' }}>
|
||||
These search suggestions are generated by Google's AI to help you explore related topics.
|
||||
Clicking any suggestion will open Google Search in a new tab to find the latest and most relevant information.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoogleSearchModal;
|
||||
|
||||
@@ -436,20 +436,7 @@ export const ResearchSources: React.FC<ResearchSourcesProps> = ({ research }) =>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Google Search Suggestions - Per Google Display Requirements */}
|
||||
{research.search_widget && (
|
||||
<div style={{
|
||||
marginBottom: '24px',
|
||||
width: '100%',
|
||||
position: 'relative'
|
||||
}}>
|
||||
{/* Google Search Widget - Display exactly as provided without modifications */}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: research.search_widget }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Note: Google Search Widget is shown in GoogleSearchModal instead */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gap: '12px',
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { ResearchSources } from './ResearchSources';
|
||||
export { ResearchGrounding } from './ResearchGrounding';
|
||||
export { GoogleSearchModal } from './GoogleSearchModal';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { BlogResearchResponse } from '../../services/blogWriterApi';
|
||||
import { ResearchSources, ResearchGrounding } from './ResearchComponents';
|
||||
import { ResearchSources, ResearchGrounding, GoogleSearchModal } from './ResearchComponents';
|
||||
|
||||
interface ResearchResultsProps {
|
||||
research: BlogResearchResponse;
|
||||
@@ -10,6 +10,7 @@ export const ResearchResults: React.FC<ResearchResultsProps> = ({ research }) =>
|
||||
const [showAnglesModal, setShowAnglesModal] = useState(false);
|
||||
const [showCompetitorModal, setShowCompetitorModal] = useState(false);
|
||||
const [showGroundingModal, setShowGroundingModal] = useState(false);
|
||||
const [showSearchModal, setShowSearchModal] = useState(false);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
|
||||
// Show toast message on component mount
|
||||
@@ -501,6 +502,38 @@ export const ResearchResults: React.FC<ResearchResultsProps> = ({ research }) =>
|
||||
>
|
||||
📝 Use Research Blog Topics
|
||||
</div>
|
||||
|
||||
{/* Google Search Suggestions Chip - Only show when we have search data */}
|
||||
{(research.search_widget || (research.search_queries && research.search_queries.length > 0)) && (
|
||||
<div
|
||||
onClick={() => setShowSearchModal(true)}
|
||||
style={{
|
||||
backgroundColor: '#fff8e1',
|
||||
color: '#f57c00',
|
||||
border: '1px solid #ffb74d',
|
||||
borderRadius: '20px',
|
||||
padding: '6px 16px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#ffe082';
|
||||
e.currentTarget.style.transform = 'scale(1.05)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#fff8e1';
|
||||
e.currentTarget.style.transform = 'scale(1)';
|
||||
}}
|
||||
>
|
||||
🔍 Google Search
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -539,6 +572,14 @@ export const ResearchResults: React.FC<ResearchResultsProps> = ({ research }) =>
|
||||
{renderAnglesModal()}
|
||||
{renderCompetitorModal()}
|
||||
{renderGroundingModal()}
|
||||
|
||||
{/* Google Search Modal */}
|
||||
{showSearchModal && (
|
||||
<GoogleSearchModal
|
||||
research={research}
|
||||
onClose={() => setShowSearchModal(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user