ALwrity onboarding fixes
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"title": "AI LLM Providers",
|
||||
"description": "Configure AI language model providers",
|
||||
"status": "completed",
|
||||
"completed_at": "2025-09-30T11:54:21.688932",
|
||||
"completed_at": "2025-10-03T17:29:12.878656",
|
||||
"data": {
|
||||
"api_keys": {
|
||||
"gemini": "AIzaSyB6QrCiOBAzh8xLdmSumec2ysdHeyqyxgw",
|
||||
@@ -19,9 +19,175 @@
|
||||
"step_number": 2,
|
||||
"title": "Website Analysis",
|
||||
"description": "Set up website analysis and crawling",
|
||||
"status": "pending",
|
||||
"completed_at": null,
|
||||
"data": null,
|
||||
"status": "completed",
|
||||
"completed_at": "2025-10-03T17:42:17.953305",
|
||||
"data": {
|
||||
"website": "https://alwrity.com",
|
||||
"domainName": "Alwrity.com",
|
||||
"analysis": {
|
||||
"writing_style": {
|
||||
"tone": "Informative, enthusiastic",
|
||||
"voice": "Helpful, direct",
|
||||
"complexity": "Moderate",
|
||||
"engagement_level": "High",
|
||||
"brand_personality": "Innovative, friendly",
|
||||
"formality_level": "Semi-formal",
|
||||
"emotional_appeal": "Benefit-driven"
|
||||
},
|
||||
"content_characteristics": {
|
||||
"sentence_structure": "Varied",
|
||||
"vocabulary_level": "Accessible",
|
||||
"paragraph_organization": "Clear, concise",
|
||||
"content_flow": "Logical, user-centric",
|
||||
"readability_score": "Good",
|
||||
"content_density": "Moderate",
|
||||
"visual_elements_usage": "High"
|
||||
},
|
||||
"target_audience": {
|
||||
"demographics": [
|
||||
"Marketers",
|
||||
"Bloggers",
|
||||
"Content creators"
|
||||
],
|
||||
"expertise_level": "Beginner to intermediate",
|
||||
"industry_focus": "General, tech",
|
||||
"geographic_focus": "",
|
||||
"psychographic_profile": [
|
||||
"Tech-savvy",
|
||||
"Value-conscious",
|
||||
"Efficiency-seeking"
|
||||
],
|
||||
"pain_points": [
|
||||
"Time-consuming content creation",
|
||||
"Lack of SEO knowledge",
|
||||
"Writer's block"
|
||||
],
|
||||
"motivations": [
|
||||
"Efficiency",
|
||||
"Increased website traffic",
|
||||
"Content quality"
|
||||
]
|
||||
},
|
||||
"content_type": {
|
||||
"primary_type": "AI writing tool promotion",
|
||||
"secondary_types": [
|
||||
"Blog posts",
|
||||
"Marketing materials"
|
||||
],
|
||||
"purpose": "Promote AI tools, generate leads",
|
||||
"call_to_action": "Try Now!",
|
||||
"conversion_focus": "Tool usage",
|
||||
"educational_value": "Moderate"
|
||||
},
|
||||
"recommended_settings": {
|
||||
"writing_tone": "Enthusiastic, informative",
|
||||
"target_audience": "Content creators, marketers",
|
||||
"content_type": "AI tool promotion, blog",
|
||||
"creativity_level": "High",
|
||||
"geographic_location": "",
|
||||
"industry_context": "AI, Content Creation",
|
||||
"brand_alignment": "Strong"
|
||||
},
|
||||
"guidelines": {
|
||||
"tone_recommendations": [
|
||||
"Informative & Enthusiastic: Maintain a helpful and engaging tone. Example: 'Let's dive into how AI can revolutionize your content creation!'"
|
||||
],
|
||||
"structure_guidelines": [
|
||||
"Clear & Concise: Use headings, subheadings, and bullet points for easy readability. Example: Break down complex topics into digestible sections.",
|
||||
"Logical Flow: Organize content with a user-centric approach, starting with the problem and offering solutions. Example: Start with the pain points of content creation and then introduce your AI tools."
|
||||
],
|
||||
"vocabulary_suggestions": [
|
||||
"Accessible Language: Avoid jargon; use clear and concise language. Example: Instead of 'leverage AI,' use 'use AI.'"
|
||||
],
|
||||
"engagement_tips": [
|
||||
"Visuals: Incorporate images, screenshots, and videos to enhance understanding. Example: Include screenshots of your AI tools in action.",
|
||||
"Benefit-Driven: Focus on the benefits for the user. Example: 'Save time and create high-quality content with our AI.'"
|
||||
],
|
||||
"audience_considerations": [
|
||||
"Targeted Content: Address the needs of marketers, bloggers, and content creators. Example: Provide specific examples relevant to their workflows.",
|
||||
"Address Pain Points: Acknowledge and solve common content creation challenges. Example: Offer solutions to writer's block and SEO optimization."
|
||||
],
|
||||
"brand_alignment": [
|
||||
"Helpful & Innovative Voice: Maintain a helpful, innovative, and friendly brand voice. Example: Offer free resources and tutorials.",
|
||||
"Open Source Focus: Highlight the open-source nature of the tools. Example: Mention the benefits of open-source for users.",
|
||||
"Value Proposition: Clearly communicate the value of the tools. Example: 'Create fact-based, multilingual content efficiently.'"
|
||||
],
|
||||
"seo_optimization": [
|
||||
"Keyword Research: Identify relevant keywords and incorporate them naturally. Example: Use keywords like 'AI content creation,' 'SEO optimization,' and 'free AI tools.'",
|
||||
"Optimize Headings & Meta Descriptions: Use keywords in headings and create compelling meta descriptions. Example: Write a meta description that includes a clear call to action and keyword."
|
||||
],
|
||||
"conversion_optimization": [
|
||||
"Clear CTAs: Include clear calls to action. Example: 'Try our free AI tool today!'",
|
||||
"Focus on Benefits: Emphasize the value proposition. Example: 'Sign up to get instant access to AI-powered content creation.'"
|
||||
]
|
||||
},
|
||||
"best_practices": [
|
||||
"Provide In-Depth Tutorials: Offer detailed guides and tutorials to showcase the tools' capabilities.",
|
||||
"Showcase Diverse Use Cases: Demonstrate how the tools can be applied in various scenarios.",
|
||||
"Build Community: Encourage user interaction and feedback.",
|
||||
"Integrate with Platforms: Explore integrations with popular content platforms."
|
||||
],
|
||||
"avoid_elements": [
|
||||
"Overly Technical Jargon: Steer clear of overly complex technical terms that may alienate the audience.",
|
||||
"Misleading Claims: Avoid making exaggerated claims about AI capabilities.",
|
||||
"Negative Brand Association: Do not use language that portrays the brand as anything other than helpful and accessible."
|
||||
],
|
||||
"content_strategy": "Focus on creating informative, user-centric content that highlights the benefits of open-source AI tools for content creation, addressing the pain points of the target audience while providing practical solutions and SEO optimization.",
|
||||
"ai_generation_tips": [
|
||||
"Fact-Checking: Always verify the information generated by AI tools.",
|
||||
"Human Oversight: Review and refine AI-generated content for accuracy, clarity, and brand voice.",
|
||||
"Experimentation: Test different prompts and inputs to optimize output."
|
||||
],
|
||||
"competitive_advantages": [
|
||||
"Fact-Based Content: Emphasize the ability to generate fact-based content.",
|
||||
"Open Source: Highlight the benefits of open-source for users (e.g., transparency, community support, customization).",
|
||||
"Multilingual Support: Promote the multilingual capabilities of the tools."
|
||||
],
|
||||
"content_calendar_suggestions": [
|
||||
"Tutorials: Create step-by-step guides on using the AI tools for different content types.",
|
||||
"Use Case Studies: Showcase successful implementations of the tools.",
|
||||
"SEO Optimization Guides: Provide tips and best practices for improving search engine rankings.",
|
||||
"Industry News & Trends: Share insights on the latest developments in AI and content creation."
|
||||
],
|
||||
"style_patterns": {
|
||||
"patterns": {
|
||||
"sentence_length": "short to medium",
|
||||
"vocabulary_patterns": [
|
||||
"keywords related to AI and content generation",
|
||||
"action-oriented verbs"
|
||||
],
|
||||
"rhetorical_devices": [
|
||||
"repetition",
|
||||
"call to action"
|
||||
],
|
||||
"paragraph_structure": "varied, often short and focused",
|
||||
"transition_phrases": [
|
||||
"Click to",
|
||||
"and"
|
||||
]
|
||||
},
|
||||
"style_consistency": "consistent in tone and purpose",
|
||||
"unique_elements": [
|
||||
"focus on AI-powered content creation tools",
|
||||
"integration with platforms like WordPress"
|
||||
],
|
||||
"meta": {
|
||||
"schema_version": "1.1",
|
||||
"confidence": 0.8,
|
||||
"notes": "The content is promotional and tool-focused.",
|
||||
"uncertainty": {
|
||||
"fields": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"style_consistency": "consistent in tone and purpose",
|
||||
"unique_elements": [
|
||||
"focus on AI-powered content creation tools",
|
||||
"integration with platforms like WordPress"
|
||||
]
|
||||
},
|
||||
"useAnalysisForGenAI": true
|
||||
},
|
||||
"validation_errors": []
|
||||
},
|
||||
{
|
||||
@@ -61,9 +227,9 @@
|
||||
"validation_errors": []
|
||||
}
|
||||
],
|
||||
"current_step": 2,
|
||||
"current_step": 3,
|
||||
"started_at": "2025-09-29T17:22:14.375002",
|
||||
"last_updated": "2025-09-30T11:54:21.688938",
|
||||
"last_updated": "2025-10-03T17:42:17.953324",
|
||||
"is_completed": false,
|
||||
"completed_at": null
|
||||
}
|
||||
@@ -8,6 +8,7 @@ from sqlalchemy.orm import Session
|
||||
from loguru import logger
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
|
||||
from models.component_logic import (
|
||||
UserInfoRequest, UserInfoResponse,
|
||||
@@ -45,6 +46,23 @@ research_utilities = ResearchUtilities()
|
||||
# Create router
|
||||
router = APIRouter(prefix="/api/onboarding", tags=["component_logic"])
|
||||
|
||||
# Utility function for consistent user ID to integer conversion
|
||||
def clerk_user_id_to_int(user_id: str) -> int:
|
||||
"""
|
||||
Convert Clerk user ID to consistent integer for database session_id.
|
||||
Uses SHA256 hashing for deterministic, consistent results across all requests.
|
||||
|
||||
Args:
|
||||
user_id: Clerk user ID (e.g., 'user_2qA6V8bFFnhPRGp8JYxP4YTJtHl')
|
||||
|
||||
Returns:
|
||||
int: Deterministic integer derived from user ID
|
||||
"""
|
||||
# Use SHA256 for consistent hashing (unlike Python's hash() which varies per process)
|
||||
user_id_hash = hashlib.sha256(user_id.encode()).hexdigest()
|
||||
# Take first 8 characters of hex and convert to int, mod to fit in INT range
|
||||
return int(user_id_hash[:8], 16) % 2147483647
|
||||
|
||||
# AI Research Endpoints
|
||||
|
||||
@router.post("/ai-research/validate-user", response_model=UserInfoResponse)
|
||||
@@ -99,11 +117,8 @@ async def configure_research_preferences(
|
||||
preferences_service = ResearchPreferencesService(db)
|
||||
|
||||
# Use authenticated Clerk user ID for proper user isolation
|
||||
# Convert user_id to int if service expects it, or update service to accept string
|
||||
try:
|
||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
||||
except:
|
||||
user_id_int = hash(user_id) % 2147483647
|
||||
# Use consistent SHA256-based conversion
|
||||
user_id_int = clerk_user_id_to_int(user_id)
|
||||
|
||||
# Save preferences with user ID (not session_id)
|
||||
preferences_id = preferences_service.save_preferences_with_style_data(user_id_int, preferences)
|
||||
@@ -504,10 +519,8 @@ async def complete_style_detection(
|
||||
analysis_service = WebsiteAnalysisService(db_session)
|
||||
|
||||
# Use authenticated Clerk user ID for proper user isolation
|
||||
try:
|
||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
||||
except:
|
||||
user_id_int = hash(user_id) % 2147483647
|
||||
# Use consistent SHA256-based conversion
|
||||
user_id_int = clerk_user_id_to_int(user_id)
|
||||
|
||||
# Check for existing analysis if URL is provided
|
||||
existing_analysis = None
|
||||
@@ -536,11 +549,44 @@ async def complete_style_detection(
|
||||
timestamp=datetime.now().isoformat()
|
||||
)
|
||||
|
||||
# Step 2: Analyze style
|
||||
style_analysis = style_logic.analyze_content_style(crawl_result['content'])
|
||||
# Step 2-4: Parallelize AI API calls for performance (3 calls → 1 parallel batch)
|
||||
import asyncio
|
||||
from functools import partial
|
||||
|
||||
# Prepare parallel tasks
|
||||
logger.info("[complete_style_detection] Starting parallel AI analysis...")
|
||||
|
||||
async def run_style_analysis():
|
||||
"""Run style analysis in executor"""
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, partial(style_logic.analyze_content_style, crawl_result['content']))
|
||||
|
||||
async def run_patterns_analysis():
|
||||
"""Run patterns analysis in executor (if requested)"""
|
||||
if not request.include_patterns:
|
||||
return None
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, partial(style_logic.analyze_style_patterns, crawl_result['content']))
|
||||
|
||||
# Execute style and patterns analysis in parallel
|
||||
style_analysis, patterns_result = await asyncio.gather(
|
||||
run_style_analysis(),
|
||||
run_patterns_analysis(),
|
||||
return_exceptions=True
|
||||
)
|
||||
|
||||
# Check if style_analysis failed
|
||||
if isinstance(style_analysis, Exception):
|
||||
error_msg = str(style_analysis)
|
||||
logger.error(f"Style analysis failed with exception: {error_msg}")
|
||||
analysis_service.save_error_analysis(user_id_int, request.url or "text_sample", error_msg)
|
||||
return StyleDetectionResponse(
|
||||
success=False,
|
||||
error=f"Style analysis failed: {error_msg}",
|
||||
timestamp=datetime.now().isoformat()
|
||||
)
|
||||
|
||||
if not style_analysis or not style_analysis.get('success'):
|
||||
# Check if it's an API key issue
|
||||
error_msg = style_analysis.get('error', 'Unknown error') if style_analysis else 'Analysis failed'
|
||||
if 'API key' in error_msg or 'configure' in error_msg:
|
||||
return StyleDetectionResponse(
|
||||
@@ -549,7 +595,6 @@ async def complete_style_detection(
|
||||
timestamp=datetime.now().isoformat()
|
||||
)
|
||||
else:
|
||||
# Save error analysis
|
||||
analysis_service.save_error_analysis(user_id_int, request.url or "text_sample", error_msg)
|
||||
return StyleDetectionResponse(
|
||||
success=False,
|
||||
@@ -557,17 +602,20 @@ async def complete_style_detection(
|
||||
timestamp=datetime.now().isoformat()
|
||||
)
|
||||
|
||||
# Step 3: Analyze patterns (optional)
|
||||
# Process patterns result
|
||||
style_patterns = None
|
||||
if request.include_patterns:
|
||||
patterns_result = style_logic.analyze_style_patterns(crawl_result['content'])
|
||||
if patterns_result and patterns_result.get('success'):
|
||||
if request.include_patterns and patterns_result and not isinstance(patterns_result, Exception):
|
||||
if patterns_result.get('success'):
|
||||
style_patterns = patterns_result.get('patterns')
|
||||
|
||||
# Step 4: Generate guidelines (optional)
|
||||
# Step 4: Generate guidelines (depends on style_analysis, must run after)
|
||||
style_guidelines = None
|
||||
if request.include_guidelines:
|
||||
guidelines_result = style_logic.generate_style_guidelines(style_analysis.get('analysis', {}))
|
||||
loop = asyncio.get_event_loop()
|
||||
guidelines_result = await loop.run_in_executor(
|
||||
None,
|
||||
partial(style_logic.generate_style_guidelines, style_analysis.get('analysis', {}))
|
||||
)
|
||||
if guidelines_result and guidelines_result.get('success'):
|
||||
style_guidelines = guidelines_result.get('guidelines')
|
||||
|
||||
@@ -628,10 +676,8 @@ async def check_existing_analysis(
|
||||
analysis_service = WebsiteAnalysisService(db_session)
|
||||
|
||||
# Use authenticated Clerk user ID for proper user isolation
|
||||
try:
|
||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
||||
except:
|
||||
user_id_int = hash(user_id) % 2147483647
|
||||
# Use consistent SHA256-based conversion
|
||||
user_id_int = clerk_user_id_to_int(user_id)
|
||||
|
||||
# Check for existing analysis for THIS USER ONLY
|
||||
existing_analysis = analysis_service.check_existing_analysis(user_id_int, website_url)
|
||||
@@ -684,10 +730,8 @@ async def get_session_analyses(current_user: Dict[str, Any] = Depends(get_curren
|
||||
analysis_service = WebsiteAnalysisService(db_session)
|
||||
|
||||
# Use authenticated Clerk user ID for proper user isolation
|
||||
try:
|
||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
||||
except:
|
||||
user_id_int = hash(user_id) % 2147483647
|
||||
# Use consistent SHA256-based conversion
|
||||
user_id_int = clerk_user_id_to_int(user_id)
|
||||
|
||||
# Get analyses for THIS USER ONLY (not all users!)
|
||||
analyses = analysis_service.get_session_analyses(user_id_int)
|
||||
|
||||
@@ -117,26 +117,24 @@ class ClerkAuthMiddleware:
|
||||
# Use cached PyJWKClient to avoid repeated JWKS fetches
|
||||
if jwks_url not in self._jwks_client_cache:
|
||||
logger.info(f"Creating new PyJWKClient for {jwks_url} with caching enabled")
|
||||
# Create client with caching: cache_keys=True, max_cached_keys=16, cache_jwk_set_timeout=3600 (1 hour)
|
||||
# Create client with caching enabled (cache_keys=True keeps keys in memory)
|
||||
self._jwks_client_cache[jwks_url] = PyJWKClient(
|
||||
jwks_url,
|
||||
cache_keys=True,
|
||||
max_cached_keys=16,
|
||||
cache_jwk_set_timeout=3600, # Cache JWKS for 1 hour
|
||||
timeout=10 # 10 second timeout for JWKS fetch
|
||||
max_cached_keys=16
|
||||
)
|
||||
|
||||
jwks_client = self._jwks_client_cache[jwks_url]
|
||||
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
||||
|
||||
# Verify and decode the token with clock skew tolerance
|
||||
# Add 60 seconds leeway to handle clock skew between client/server
|
||||
# Add 300 seconds (5 minutes) leeway to handle clock skew and token refresh delays
|
||||
decoded_token = jwt.decode(
|
||||
token,
|
||||
signing_key.key,
|
||||
algorithms=["RS256"],
|
||||
options={"verify_signature": True, "verify_exp": True},
|
||||
leeway=60 # Allow 60 seconds clock skew
|
||||
leeway=300 # Allow 5 minutes leeway for token refresh during navigation
|
||||
)
|
||||
|
||||
# Extract user information
|
||||
@@ -171,7 +169,7 @@ class ClerkAuthMiddleware:
|
||||
decoded_token = jwt.decode(
|
||||
token,
|
||||
options={"verify_signature": False},
|
||||
leeway=60 # Allow 60 seconds clock skew
|
||||
leeway=300 # Allow 5 minutes leeway for token refresh
|
||||
)
|
||||
|
||||
# Extract user information from the token
|
||||
|
||||
@@ -41,11 +41,17 @@ class WebsiteAnalysisService:
|
||||
|
||||
if existing_analysis:
|
||||
# Update existing analysis
|
||||
existing_analysis.writing_style = analysis_data.get('style_analysis', {}).get('writing_style')
|
||||
existing_analysis.content_characteristics = analysis_data.get('style_analysis', {}).get('content_characteristics')
|
||||
existing_analysis.target_audience = analysis_data.get('style_analysis', {}).get('target_audience')
|
||||
existing_analysis.content_type = analysis_data.get('style_analysis', {}).get('content_type')
|
||||
existing_analysis.recommended_settings = analysis_data.get('style_analysis', {}).get('recommended_settings')
|
||||
style_analysis = analysis_data.get('style_analysis', {})
|
||||
existing_analysis.writing_style = style_analysis.get('writing_style')
|
||||
existing_analysis.content_characteristics = style_analysis.get('content_characteristics')
|
||||
existing_analysis.target_audience = style_analysis.get('target_audience')
|
||||
existing_analysis.content_type = style_analysis.get('content_type')
|
||||
existing_analysis.recommended_settings = style_analysis.get('recommended_settings')
|
||||
# Store brand_analysis and content_strategy_insights if model supports it
|
||||
if hasattr(existing_analysis, 'brand_analysis'):
|
||||
existing_analysis.brand_analysis = style_analysis.get('brand_analysis')
|
||||
if hasattr(existing_analysis, 'content_strategy_insights'):
|
||||
existing_analysis.content_strategy_insights = style_analysis.get('content_strategy_insights')
|
||||
existing_analysis.crawl_result = analysis_data.get('crawl_result')
|
||||
existing_analysis.style_patterns = analysis_data.get('style_patterns')
|
||||
existing_analysis.style_guidelines = analysis_data.get('style_guidelines')
|
||||
@@ -59,20 +65,28 @@ class WebsiteAnalysisService:
|
||||
return existing_analysis.id
|
||||
else:
|
||||
# Create new analysis
|
||||
analysis = WebsiteAnalysis(
|
||||
session_id=session_id,
|
||||
website_url=website_url,
|
||||
writing_style=analysis_data.get('style_analysis', {}).get('writing_style'),
|
||||
content_characteristics=analysis_data.get('style_analysis', {}).get('content_characteristics'),
|
||||
target_audience=analysis_data.get('style_analysis', {}).get('target_audience'),
|
||||
content_type=analysis_data.get('style_analysis', {}).get('content_type'),
|
||||
recommended_settings=analysis_data.get('style_analysis', {}).get('recommended_settings'),
|
||||
crawl_result=analysis_data.get('crawl_result'),
|
||||
style_patterns=analysis_data.get('style_patterns'),
|
||||
style_guidelines=analysis_data.get('style_guidelines'),
|
||||
status='completed',
|
||||
warning_message=analysis_data.get('warning')
|
||||
)
|
||||
style_analysis = analysis_data.get('style_analysis', {})
|
||||
analysis_args = {
|
||||
'session_id': session_id,
|
||||
'website_url': website_url,
|
||||
'writing_style': style_analysis.get('writing_style'),
|
||||
'content_characteristics': style_analysis.get('content_characteristics'),
|
||||
'target_audience': style_analysis.get('target_audience'),
|
||||
'content_type': style_analysis.get('content_type'),
|
||||
'recommended_settings': style_analysis.get('recommended_settings'),
|
||||
'crawl_result': analysis_data.get('crawl_result'),
|
||||
'style_patterns': analysis_data.get('style_patterns'),
|
||||
'style_guidelines': analysis_data.get('style_guidelines'),
|
||||
'status': 'completed',
|
||||
'warning_message': analysis_data.get('warning')
|
||||
}
|
||||
# Add brand_analysis and content_strategy_insights if model supports it
|
||||
if hasattr(WebsiteAnalysis, 'brand_analysis'):
|
||||
analysis_args['brand_analysis'] = style_analysis.get('brand_analysis')
|
||||
if hasattr(WebsiteAnalysis, 'content_strategy_insights'):
|
||||
analysis_args['content_strategy_insights'] = style_analysis.get('content_strategy_insights')
|
||||
|
||||
analysis = WebsiteAnalysis(**analysis_args)
|
||||
|
||||
self.db.add(analysis)
|
||||
self.db.commit()
|
||||
|
||||
@@ -82,13 +82,64 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
const [showHighlightsModal, setShowHighlightsModal] = useState(false);
|
||||
const [selectedCompetitorHighlights, setSelectedCompetitorHighlights] = useState<string[]>([]);
|
||||
const [selectedCompetitorTitle, setSelectedCompetitorTitle] = useState<string>('');
|
||||
const [usingCachedData, setUsingCachedData] = useState(false);
|
||||
|
||||
// Check for cached competitor analysis data
|
||||
const loadCachedAnalysis = useCallback(() => {
|
||||
try {
|
||||
const cachedData = localStorage.getItem('competitor_analysis_data');
|
||||
const cachedUrl = localStorage.getItem('competitor_analysis_url');
|
||||
const cacheTimestamp = localStorage.getItem('competitor_analysis_timestamp');
|
||||
|
||||
// Get current URL for comparison
|
||||
const finalUserUrl = userUrl || localStorage.getItem('website_url') || '';
|
||||
|
||||
if (cachedData && cachedUrl === finalUserUrl && cacheTimestamp) {
|
||||
const cacheAge = Date.now() - parseInt(cacheTimestamp);
|
||||
const cacheValidDuration = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
// Check if cache is still valid (less than 24 hours old)
|
||||
if (cacheAge < cacheValidDuration) {
|
||||
const parsedData = JSON.parse(cachedData);
|
||||
|
||||
console.log('CompetitorAnalysisStep: Loading cached competitor analysis:', {
|
||||
url: cachedUrl,
|
||||
cacheAge: Math.round(cacheAge / (60 * 1000)),
|
||||
competitors: parsedData.competitors?.length || 0
|
||||
});
|
||||
|
||||
setCompetitors(parsedData.competitors || []);
|
||||
setSocialMediaAccounts(parsedData.social_media_accounts || {});
|
||||
setSocialMediaCitations(parsedData.social_media_citations || []);
|
||||
setResearchSummary(parsedData.research_summary || null);
|
||||
setUsingCachedData(true);
|
||||
|
||||
return true; // Successfully loaded from cache
|
||||
} else {
|
||||
console.log('CompetitorAnalysisStep: Cache expired, will run fresh analysis');
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No valid cache found
|
||||
} catch (err) {
|
||||
console.error('Error loading cached analysis:', err);
|
||||
return false;
|
||||
}
|
||||
}, [userUrl]);
|
||||
|
||||
const startCompetitorDiscovery = useCallback(async (force = false) => {
|
||||
// Check cache first unless forced
|
||||
if (!force && loadCachedAnalysis()) {
|
||||
console.log('CompetitorAnalysisStep: Using cached competitor analysis');
|
||||
return;
|
||||
}
|
||||
|
||||
const startCompetitorDiscovery = useCallback(async () => {
|
||||
setIsAnalyzing(true);
|
||||
setShowProgressModal(true);
|
||||
setError(null);
|
||||
setAnalysisProgress(0);
|
||||
setAnalysisStep('Initializing competitor discovery...');
|
||||
setUsingCachedData(false);
|
||||
|
||||
try {
|
||||
setAnalysisStep('Validating session...');
|
||||
@@ -139,10 +190,28 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
setAnalysisProgress(100);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
setCompetitors(result.competitors || []);
|
||||
setSocialMediaAccounts(result.social_media_accounts || {});
|
||||
setSocialMediaCitations(result.social_media_citations || []);
|
||||
setResearchSummary(result.research_summary || null);
|
||||
const analysisData = {
|
||||
competitors: result.competitors || [],
|
||||
social_media_accounts: result.social_media_accounts || {},
|
||||
social_media_citations: result.social_media_citations || [],
|
||||
research_summary: result.research_summary || null
|
||||
};
|
||||
|
||||
setCompetitors(analysisData.competitors);
|
||||
setSocialMediaAccounts(analysisData.social_media_accounts);
|
||||
setSocialMediaCitations(analysisData.social_media_citations);
|
||||
setResearchSummary(analysisData.research_summary);
|
||||
|
||||
// Cache the analysis results
|
||||
try {
|
||||
localStorage.setItem('competitor_analysis_data', JSON.stringify(analysisData));
|
||||
localStorage.setItem('competitor_analysis_url', finalUserUrl);
|
||||
localStorage.setItem('competitor_analysis_timestamp', Date.now().toString());
|
||||
console.log('CompetitorAnalysisStep: Cached competitor analysis for future use');
|
||||
} catch (cacheErr) {
|
||||
console.warn('Failed to cache competitor analysis:', cacheErr);
|
||||
}
|
||||
|
||||
setShowProgressModal(false);
|
||||
setIsAnalyzing(false);
|
||||
} else {
|
||||
@@ -154,11 +223,23 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
setIsAnalyzing(false);
|
||||
setShowProgressModal(false);
|
||||
}
|
||||
}, [userUrl, industryContext]); // sessionId removed from dependencies
|
||||
}, [userUrl, industryContext, loadCachedAnalysis]); // sessionId removed from dependencies
|
||||
|
||||
// Initialize: Check cache first, then run analysis if needed
|
||||
useEffect(() => {
|
||||
startCompetitorDiscovery();
|
||||
}, [startCompetitorDiscovery]);
|
||||
const initialize = async () => {
|
||||
// Try to load from cache first
|
||||
const cacheLoaded = loadCachedAnalysis();
|
||||
|
||||
// If no cache found, run fresh analysis
|
||||
if (!cacheLoaded) {
|
||||
await startCompetitorDiscovery(false);
|
||||
}
|
||||
};
|
||||
|
||||
initialize();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); // Run only once on mount
|
||||
|
||||
const handleContinue = () => {
|
||||
const researchData = {
|
||||
@@ -180,20 +261,58 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
return (
|
||||
<Box sx={classes.container}>
|
||||
<Box sx={classes.header}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 600, mb: 2 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
mb: 2,
|
||||
color: '#1a202c !important' // Force dark text for readability
|
||||
}}
|
||||
>
|
||||
Research Your Competition
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ color: 'text.secondary', fontWeight: 400 }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
color: '#4a5568 !important', // Force dark secondary text
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Discover your competitors and analyze their strategies to gain competitive advantage
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{usingCachedData && !error && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{
|
||||
mb: 3,
|
||||
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)',
|
||||
border: '1px solid #81d4fa',
|
||||
color: '#01579b',
|
||||
'& .MuiAlert-icon': {
|
||||
color: '#0277bd'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Loaded previously analyzed competitor data.
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={() => startCompetitorDiscovery(true)}
|
||||
sx={{ ml: 2 }}
|
||||
size="small"
|
||||
>
|
||||
Run Fresh Analysis
|
||||
</Button>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
{error}
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={startCompetitorDiscovery}
|
||||
onClick={() => startCompetitorDiscovery(true)}
|
||||
sx={{ ml: 2 }}
|
||||
>
|
||||
Retry
|
||||
@@ -204,23 +323,36 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
{!isAnalyzing && !error && (competitors.length > 0 || researchSummary) && (
|
||||
<Box>
|
||||
{researchSummary && (
|
||||
<Paper sx={{ p: 3, mb: 4, backgroundColor: 'primary.50', border: '1px solid', borderColor: 'primary.200' }}>
|
||||
<Paper sx={{
|
||||
p: 3,
|
||||
mb: 4,
|
||||
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)', // Warm sky-blue gradient
|
||||
border: '1px solid #81d4fa',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 4px 12px rgba(3, 169, 244, 0.15)'
|
||||
}}>
|
||||
<Typography variant="h6" gutterBottom fontWeight={600} color="primary">
|
||||
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Research Summary
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3} mt={1}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
<Typography variant="h4" color="primary" fontWeight={700}>
|
||||
{researchSummary.total_competitors}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: '#4a5568 !important' }} // Force dark text for readability
|
||||
>
|
||||
Competitors Found
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={9}>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
<Grid item xs={12} sm={6} md={9}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ color: '#2d3748 !important' }} // Force dark text for readability
|
||||
>
|
||||
{researchSummary.market_insights}
|
||||
</Typography>
|
||||
</Grid>
|
||||
@@ -231,8 +363,14 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
{/* Social Media Accounts Section */}
|
||||
{Object.keys(socialMediaAccounts).length > 0 && (
|
||||
<>
|
||||
<Typography variant="h6" gutterBottom fontWeight={600} mb={3}>
|
||||
<ShareIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
<Typography
|
||||
variant="h6"
|
||||
gutterBottom
|
||||
fontWeight={600}
|
||||
mb={3}
|
||||
sx={{ color: '#1a202c !important' }} // Force dark text
|
||||
>
|
||||
<ShareIcon sx={{ mr: 1, verticalAlign: 'middle', color: '#667eea !important' }} />
|
||||
Social Media Presence
|
||||
</Typography>
|
||||
|
||||
@@ -250,8 +388,18 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} key={platform}>
|
||||
<Card sx={{ height: '100%' }}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={platform}>
|
||||
<Card sx={{
|
||||
height: '100%',
|
||||
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)',
|
||||
border: '1px solid #81d4fa',
|
||||
boxShadow: '0 4px 12px rgba(3, 169, 244, 0.15)',
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '0 8px 20px rgba(3, 169, 244, 0.25)'
|
||||
}
|
||||
}}>
|
||||
<CardContent>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Avatar sx={{ width: 40, height: 40, bgcolor: 'primary.main' }}>
|
||||
@@ -282,25 +430,52 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
<Typography variant="h6" gutterBottom fontWeight={600} mb={3}>
|
||||
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
<Typography
|
||||
variant="h6"
|
||||
gutterBottom
|
||||
fontWeight={600}
|
||||
mb={3}
|
||||
sx={{ color: '#1a202c !important' }} // Force dark text
|
||||
>
|
||||
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle', color: '#667eea !important' }} />
|
||||
Discovered Competitors ({competitors.length})
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{competitors.map((competitor, index) => (
|
||||
<Grid item xs={12} md={6} lg={4} key={index}>
|
||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={index}>
|
||||
<Card sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)',
|
||||
border: '1px solid #81d4fa',
|
||||
boxShadow: '0 4px 12px rgba(3, 169, 244, 0.15)',
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '0 8px 20px rgba(3, 169, 244, 0.25)'
|
||||
}
|
||||
}}>
|
||||
<CardContent sx={{ flexGrow: 1 }}>
|
||||
<Box display="flex" alignItems="flex-start" gap={2} mb={2}>
|
||||
<Avatar sx={{ width: 40, height: 40 }}>
|
||||
<BusinessIcon />
|
||||
</Avatar>
|
||||
<Box flex={1}>
|
||||
<Typography variant="h6" fontWeight={600} gutterBottom>
|
||||
<Typography
|
||||
variant="h6"
|
||||
fontWeight={600}
|
||||
gutterBottom
|
||||
sx={{ color: '#1a202c !important' }} // Force dark text for readability
|
||||
>
|
||||
{competitor.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
<Typography
|
||||
variant="body2"
|
||||
gutterBottom
|
||||
sx={{ color: '#4a5568 !important' }} // Force dark text for readability
|
||||
>
|
||||
{competitor.domain}
|
||||
</Typography>
|
||||
<Chip
|
||||
@@ -311,7 +486,11 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" mb={2}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
mb={2}
|
||||
sx={{ color: '#2d3748 !important' }} // Force dark text for readability
|
||||
>
|
||||
{competitor.summary.length > 150
|
||||
? `${competitor.summary.substring(0, 150)}...`
|
||||
: competitor.summary
|
||||
|
||||
@@ -8,27 +8,17 @@ import {
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Card,
|
||||
CardContent,
|
||||
Grid,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
DialogContentText,
|
||||
Fade,
|
||||
LinearProgress,
|
||||
Stepper,
|
||||
Step,
|
||||
StepLabel,
|
||||
Checkbox,
|
||||
FormControlLabel
|
||||
DialogContentText
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Analytics as AnalyticsIcon,
|
||||
History as HistoryIcon,
|
||||
Business as BusinessIcon,
|
||||
Star as StarIcon,
|
||||
Verified as VerifiedIcon
|
||||
Business as BusinessIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
// Extracted components
|
||||
@@ -37,7 +27,6 @@ import { AnalysisResultsDisplay, AnalysisProgressDisplay } from './WebsiteStep/c
|
||||
// Extracted utilities
|
||||
import {
|
||||
fixUrlFormat,
|
||||
extractDomainName,
|
||||
checkExistingAnalysis,
|
||||
loadExistingAnalysis,
|
||||
performAnalysis,
|
||||
@@ -336,18 +325,17 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
||||
return;
|
||||
}
|
||||
|
||||
const stepData = {
|
||||
website: fixedUrl,
|
||||
domainName: result.domainName || domainName,
|
||||
analysis: result.analysis,
|
||||
useAnalysisForGenAI,
|
||||
};
|
||||
// Set the loaded analysis data for display
|
||||
setDomainName(result.domainName || domainName);
|
||||
setAnalysis(result.analysis);
|
||||
setSuccess('Previous analysis loaded successfully!');
|
||||
|
||||
// Store in localStorage for Step 3 (Competitor Analysis)
|
||||
localStorage.setItem('website_url', fixedUrl);
|
||||
localStorage.setItem('website_analysis_data', JSON.stringify(result.analysis));
|
||||
|
||||
onContinue(stepData);
|
||||
// DO NOT call onContinue() here - let user review the analysis first
|
||||
// User will click "Continue" button when ready to proceed
|
||||
};
|
||||
|
||||
const handleNewAnalysis = async () => {
|
||||
|
||||
@@ -14,22 +14,15 @@ import {
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Alert,
|
||||
Paper,
|
||||
Tooltip
|
||||
Paper
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AutoAwesome as AutoAwesomeIcon,
|
||||
Verified as VerifiedIcon,
|
||||
Psychology as PsychologyIcon,
|
||||
Analytics as AnalyticsIcon,
|
||||
TrendingUp as TrendingUpIcon,
|
||||
Language as LanguageIcon,
|
||||
Palette as PaletteIcon,
|
||||
Speed as SpeedIcon,
|
||||
Group as GroupIcon,
|
||||
Business as BusinessIcon,
|
||||
Lightbulb as LightbulbIcon,
|
||||
Warning as WarningIcon
|
||||
Lightbulb as LightbulbIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
// Import rendering utilities
|
||||
@@ -45,7 +38,12 @@ import {
|
||||
} from '../utils/renderUtils';
|
||||
|
||||
// Import extracted components
|
||||
import { EnhancedGuidelinesSection, KeyInsightsGrid } from './index';
|
||||
import {
|
||||
EnhancedGuidelinesSection,
|
||||
KeyInsightsGrid,
|
||||
ContentCharacteristicsSection,
|
||||
TargetAudienceAnalysisSection
|
||||
} from './index';
|
||||
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
|
||||
|
||||
interface StyleAnalysis {
|
||||
@@ -169,10 +167,24 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
<Box sx={styles.analysisHeader}>
|
||||
<VerifiedIcon sx={styles.analysisHeaderIcon} />
|
||||
<Box>
|
||||
<Typography variant="h4" sx={styles.analysisHeaderTitle} gutterBottom>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
...styles.analysisHeaderTitle,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
{domainName} Style Analysis
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={styles.analysisHeaderSubtitle}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
...styles.analysisHeaderSubtitle,
|
||||
color: '#4a5568 !important' // Force dark secondary text
|
||||
}}
|
||||
>
|
||||
Comprehensive content analysis and personalized recommendations
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -186,253 +198,32 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
/>
|
||||
|
||||
{/* Content Characteristics Section */}
|
||||
{analysis.content_characteristics && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<AnalyticsIcon color="info" />
|
||||
Content Characteristics
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{analysis.content_characteristics.vocabulary_level && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="The complexity and sophistication of words used in the content. Higher levels use more advanced vocabulary while accessible levels use simpler, everyday words." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Vocabulary Level',
|
||||
analysis.content_characteristics.vocabulary_level,
|
||||
<LanguageIcon />,
|
||||
'info'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.content_characteristics.readability_score && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="How easy it is for readers to understand the content. Higher scores mean the content is easier to read and comprehend." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Readability Score',
|
||||
analysis.content_characteristics.readability_score,
|
||||
<SpeedIcon />,
|
||||
'success'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.content_characteristics.content_density && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="How much information is packed into each section. Moderate density balances information with readability." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Content Density',
|
||||
analysis.content_characteristics.content_density,
|
||||
<PaletteIcon />,
|
||||
'warning'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.content_characteristics.sentence_structure && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="The variety and complexity of sentence patterns used. Varied structures keep readers engaged." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Sentence Structure',
|
||||
analysis.content_characteristics.sentence_structure,
|
||||
<AnalyticsIcon />,
|
||||
'secondary'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.content_characteristics.paragraph_organization && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="How paragraphs are structured and organized. Clear organization helps readers follow the content easily." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Paragraph Organization',
|
||||
analysis.content_characteristics.paragraph_organization,
|
||||
<AnalyticsIcon />,
|
||||
'primary'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.content_characteristics.content_flow && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="How smoothly the content moves from one idea to the next. Good flow keeps readers engaged throughout." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Content Flow',
|
||||
analysis.content_characteristics.content_flow,
|
||||
<TrendingUpIcon />,
|
||||
'success'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.content_characteristics.visual_elements_usage && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Tooltip title="How often images, charts, and other visual elements are used to support the text content." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Visual Elements Usage',
|
||||
analysis.content_characteristics.visual_elements_usage,
|
||||
<PaletteIcon />,
|
||||
'warning'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
<ContentCharacteristicsSection
|
||||
contentCharacteristics={analysis.content_characteristics as any}
|
||||
/>
|
||||
|
||||
{/* Detailed Target Audience Section */}
|
||||
{analysis.target_audience && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<GroupIcon color="info" />
|
||||
Target Audience Analysis
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{analysis.target_audience.demographics && analysis.target_audience.demographics.length > 0 && (
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderKeyInsight(
|
||||
'Demographics',
|
||||
analysis.target_audience.demographics,
|
||||
<GroupIcon />,
|
||||
'info'
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.target_audience.industry_focus && (
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderKeyInsight(
|
||||
'Industry Focus',
|
||||
analysis.target_audience.industry_focus,
|
||||
<BusinessIcon />,
|
||||
'primary'
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.target_audience.geographic_focus && (
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderKeyInsight(
|
||||
'Geographic Focus',
|
||||
analysis.target_audience.geographic_focus,
|
||||
<AnalyticsIcon />,
|
||||
'secondary'
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.target_audience.psychographic_profile && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={styles.analysisAccentIconSuccess}>
|
||||
<PsychologyIcon />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
||||
Psychographic Profile
|
||||
</Typography>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{Array.isArray(analysis.target_audience.psychographic_profile)
|
||||
? analysis.target_audience.psychographic_profile.map((item: string, index: number) => (
|
||||
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
|
||||
{item}
|
||||
</Typography>
|
||||
))
|
||||
: (
|
||||
<Typography component="li" variant="body2" sx={styles.analysisListItem}>
|
||||
{analysis.target_audience.psychographic_profile}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.target_audience.pain_points && analysis.target_audience.pain_points.length > 0 && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={2} sx={styles.analysisAccentPaperError}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={styles.analysisAccentIconError}>
|
||||
<WarningIcon />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
||||
Pain Points
|
||||
</Typography>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{analysis.target_audience.pain_points.map((painPoint: string, index: number) => (
|
||||
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
|
||||
{painPoint}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.target_audience.motivations && analysis.target_audience.motivations.length > 0 && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={styles.analysisAccentIconSuccess}>
|
||||
<TrendingUpIcon />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
||||
Motivations
|
||||
</Typography>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{analysis.target_audience.motivations.map((motivation: string, index: number) => (
|
||||
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
|
||||
{motivation}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
{/* Target Audience Analysis Section */}
|
||||
<TargetAudienceAnalysisSection
|
||||
targetAudience={analysis.target_audience as any}
|
||||
/>
|
||||
|
||||
{/* Content Type Details Section */}
|
||||
{analysis.content_type && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<BusinessIcon color="primary" />
|
||||
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
>
|
||||
<BusinessIcon sx={{ color: '#667eea !important' }} />
|
||||
Content Type Analysis
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{analysis.content_type.purpose && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Content Purpose',
|
||||
analysis.content_type.purpose,
|
||||
@@ -443,7 +234,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.content_type.call_to_action && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Call to Action Style',
|
||||
analysis.content_type.call_to_action,
|
||||
@@ -454,7 +245,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.content_type.conversion_focus && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Conversion Focus',
|
||||
analysis.content_type.conversion_focus,
|
||||
@@ -465,7 +256,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.content_type.educational_value && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Educational Value',
|
||||
analysis.content_type.educational_value,
|
||||
@@ -507,7 +298,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Content Strategy */}
|
||||
{analysis.content_strategy && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<AutoAwesomeIcon color="primary" />
|
||||
Content Strategy
|
||||
</Typography>
|
||||
@@ -522,14 +321,22 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Recommended Settings for AI Generation */}
|
||||
{analysis.recommended_settings && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<AutoAwesomeIcon color="primary" />
|
||||
Recommended AI Generation Settings
|
||||
</Typography>
|
||||
<Paper elevation={3} sx={styles.analysisGradientPaperPrimary}>
|
||||
<Grid container spacing={2}>
|
||||
{analysis.recommended_settings.writing_tone && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Writing Tone:
|
||||
</Typography>
|
||||
@@ -540,7 +347,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.recommended_settings.target_audience && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Target Audience:
|
||||
</Typography>
|
||||
@@ -551,7 +358,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.recommended_settings.content_type && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Content Type:
|
||||
</Typography>
|
||||
@@ -562,7 +369,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.recommended_settings.creativity_level && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Creativity Level:
|
||||
</Typography>
|
||||
@@ -573,7 +380,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.recommended_settings.industry_context && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Industry Context:
|
||||
</Typography>
|
||||
@@ -584,7 +391,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.recommended_settings.geographic_location && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Geographic Focus:
|
||||
</Typography>
|
||||
@@ -595,7 +402,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
)}
|
||||
|
||||
{analysis.recommended_settings.brand_alignment && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||
Brand Alignment:
|
||||
</Typography>
|
||||
@@ -628,7 +435,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Style Consistency Section */}
|
||||
{analysis.style_consistency && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<AnalyticsIcon color="info" />
|
||||
Style Consistency
|
||||
</Typography>
|
||||
@@ -643,7 +458,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Unique Elements Section */}
|
||||
{analysis.unique_elements && analysis.unique_elements.length > 0 && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<AutoAwesomeIcon color="primary" />
|
||||
Unique Style Elements
|
||||
</Typography>
|
||||
@@ -670,13 +493,13 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Best Practices & Avoid Elements */}
|
||||
<Grid container spacing={2} sx={styles.analysisSection}>
|
||||
{analysis.best_practices && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderBestPracticesSection(analysis.best_practices)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{analysis.avoid_elements && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderAvoidElementsSection(analysis.avoid_elements)}
|
||||
</Grid>
|
||||
)}
|
||||
@@ -685,7 +508,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Competitive Advantages */}
|
||||
{analysis.competitive_advantages && analysis.competitive_advantages.length > 0 && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<TrendingUpIcon color="success" />
|
||||
Competitive Advantages
|
||||
</Typography>
|
||||
@@ -704,7 +535,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
{/* Content Calendar Suggestions */}
|
||||
{analysis.content_calendar_suggestions && analysis.content_calendar_suggestions.length > 0 && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<AnalyticsIcon color="primary" />
|
||||
Content Calendar Suggestions
|
||||
</Typography>
|
||||
@@ -720,6 +559,56 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Content Strategy */}
|
||||
{analysis.content_strategy && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<BusinessIcon color="primary" />
|
||||
Content Strategy Overview
|
||||
</Typography>
|
||||
<Paper elevation={3} sx={styles.analysisGradientPaperPrimary}>
|
||||
<Typography variant="body1" sx={styles.analysisParagraph}>
|
||||
{analysis.content_strategy}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* AI Generation Tips */}
|
||||
{analysis.ai_generation_tips && analysis.ai_generation_tips.length > 0 && (
|
||||
<Box sx={styles.analysisSection}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
<AutoAwesomeIcon color="secondary" />
|
||||
AI Content Generation Tips
|
||||
</Typography>
|
||||
<Paper elevation={3} sx={styles.analysisGradientPaperAccent}>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{analysis.ai_generation_tips.map((tip: string, index: number) => (
|
||||
<Typography component="li" variant="body1" key={index} sx={styles.analysisListItem}>
|
||||
{tip}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* GenAI Integration Checkbox */}
|
||||
<Box sx={styles.analysisCheckboxContainer}>
|
||||
<FormControlLabel
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Content Characteristics Section Component
|
||||
* Displays content characteristics analysis in a reusable format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Grid,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Analytics as AnalyticsIcon,
|
||||
Language as LanguageIcon,
|
||||
Speed as SpeedIcon,
|
||||
Palette as PaletteIcon,
|
||||
TrendingUp as TrendingUpIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import { renderKeyInsight } from '../utils/renderUtils';
|
||||
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
|
||||
|
||||
interface ContentCharacteristics {
|
||||
sentence_structure?: string;
|
||||
vocabulary_level?: string;
|
||||
paragraph_organization?: string;
|
||||
content_flow?: string;
|
||||
readability_score?: string;
|
||||
content_density?: string;
|
||||
visual_elements_usage?: string;
|
||||
}
|
||||
|
||||
interface ContentCharacteristicsSectionProps {
|
||||
contentCharacteristics?: ContentCharacteristics;
|
||||
}
|
||||
|
||||
const ContentCharacteristicsSection: React.FC<ContentCharacteristicsSectionProps> = ({
|
||||
contentCharacteristics
|
||||
}) => {
|
||||
const styles = useOnboardingStyles();
|
||||
|
||||
if (!contentCharacteristics) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
>
|
||||
<AnalyticsIcon sx={{ color: '#667eea !important' }} />
|
||||
Content Characteristics
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
{contentCharacteristics.vocabulary_level && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The complexity and sophistication of words used in the content. Higher levels use more advanced vocabulary while accessible levels use simpler, everyday words." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Vocabulary Level',
|
||||
contentCharacteristics.vocabulary_level,
|
||||
<LanguageIcon />,
|
||||
'info'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{contentCharacteristics.readability_score && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How easy it is for readers to understand the content. Higher scores mean the content is easier to read and comprehend." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Readability Score',
|
||||
contentCharacteristics.readability_score,
|
||||
<SpeedIcon />,
|
||||
'success'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{contentCharacteristics.content_density && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How much information is packed into each section. Moderate density balances information with readability." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Content Density',
|
||||
contentCharacteristics.content_density,
|
||||
<PaletteIcon />,
|
||||
'warning'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{contentCharacteristics.sentence_structure && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The variety and complexity of sentence patterns used. Varied structures keep readers engaged." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Sentence Structure',
|
||||
contentCharacteristics.sentence_structure,
|
||||
<AnalyticsIcon />,
|
||||
'secondary'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{contentCharacteristics.paragraph_organization && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How paragraphs are structured and organized. Clear organization helps readers follow the content easily." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Paragraph Organization',
|
||||
contentCharacteristics.paragraph_organization,
|
||||
<AnalyticsIcon />,
|
||||
'primary'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{contentCharacteristics.content_flow && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How smoothly the content moves from one idea to the next. Good flow keeps readers engaged throughout." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Content Flow',
|
||||
contentCharacteristics.content_flow,
|
||||
<TrendingUpIcon />,
|
||||
'success'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{contentCharacteristics.visual_elements_usage && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How often images, charts, and other visual elements are used to support the text content." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
'Visual Elements Usage',
|
||||
contentCharacteristics.visual_elements_usage,
|
||||
<PaletteIcon />,
|
||||
'warning'
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentCharacteristicsSection;
|
||||
@@ -56,7 +56,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ mb: 2.5 }}>
|
||||
{writing_style?.tone && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The emotional quality and attitude of the writing - how it makes readers feel and the mood it creates." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -71,7 +71,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{writing_style?.complexity && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How sophisticated or simple the content is. Moderate complexity balances depth with accessibility." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -86,7 +86,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{writing_style?.voice && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The unique personality and style of the writing - what makes it distinctive and recognizable." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -101,7 +101,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{writing_style?.engagement_level && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How well the content captures and maintains reader attention throughout the piece." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -116,7 +116,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{writing_style?.brand_personality && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The human characteristics and traits associated with the brand, like friendly, professional, or innovative." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -131,7 +131,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{writing_style?.formality_level && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How formal or casual the writing style is. Semi-formal strikes a balance between professional and approachable." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -146,7 +146,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{writing_style?.emotional_appeal && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="How the content connects with readers' emotions - what feelings it aims to evoke." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -161,7 +161,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{target_audience?.expertise_level && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The skill level and experience of the intended readers - from beginners to experts in the subject matter." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -176,7 +176,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{target_audience?.geographic_focus && target_audience.geographic_focus.trim() !== '' && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The geographical regions or areas the content is primarily intended for - local, national, or global reach." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
@@ -191,7 +191,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
||||
)}
|
||||
|
||||
{content_type?.primary_type && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Tooltip title="The main category or format of content being created - blog posts, tutorials, product descriptions, etc." arrow>
|
||||
<Box>
|
||||
{renderKeyInsight(
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Target Audience Analysis Section Component
|
||||
* Displays target audience analysis in a reusable format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Grid,
|
||||
Paper
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Group as GroupIcon,
|
||||
Business as BusinessIcon,
|
||||
Analytics as AnalyticsIcon,
|
||||
Psychology as PsychologyIcon,
|
||||
Warning as WarningIcon,
|
||||
TrendingUp as TrendingUpIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import { renderKeyInsight } from '../utils/renderUtils';
|
||||
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
|
||||
|
||||
interface TargetAudience {
|
||||
demographics?: string[];
|
||||
expertise_level?: string;
|
||||
industry_focus?: string;
|
||||
geographic_focus?: string;
|
||||
psychographic_profile?: string;
|
||||
pain_points?: string[];
|
||||
motivations?: string[];
|
||||
}
|
||||
|
||||
interface TargetAudienceAnalysisSectionProps {
|
||||
targetAudience?: TargetAudience;
|
||||
}
|
||||
|
||||
const TargetAudienceAnalysisSection: React.FC<TargetAudienceAnalysisSectionProps> = ({
|
||||
targetAudience
|
||||
}) => {
|
||||
const styles = useOnboardingStyles();
|
||||
|
||||
if (!targetAudience) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
...styles.analysisSectionHeader,
|
||||
color: '#1a202c !important', // Force dark text
|
||||
fontWeight: '700 !important'
|
||||
}}
|
||||
>
|
||||
<GroupIcon sx={{ color: '#667eea !important' }} />
|
||||
Target Audience Analysis
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
{targetAudience.demographics && targetAudience.demographics.length > 0 && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Demographics',
|
||||
targetAudience.demographics,
|
||||
<GroupIcon />,
|
||||
'info'
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{targetAudience.industry_focus && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Industry Focus',
|
||||
targetAudience.industry_focus,
|
||||
<BusinessIcon />,
|
||||
'primary'
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{targetAudience.geographic_focus && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
{renderKeyInsight(
|
||||
'Geographic Focus',
|
||||
targetAudience.geographic_focus,
|
||||
<AnalyticsIcon />,
|
||||
'secondary'
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{targetAudience.psychographic_profile && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={styles.analysisAccentIconSuccess}>
|
||||
<PsychologyIcon />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
||||
Psychographic Profile
|
||||
</Typography>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{Array.isArray(targetAudience.psychographic_profile)
|
||||
? targetAudience.psychographic_profile.map((item: string, index: number) => (
|
||||
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
|
||||
{item}
|
||||
</Typography>
|
||||
))
|
||||
: (
|
||||
<Typography component="li" variant="body2" sx={styles.analysisListItem}>
|
||||
{targetAudience.psychographic_profile}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{targetAudience.pain_points && targetAudience.pain_points.length > 0 && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Paper elevation={2} sx={styles.analysisAccentPaperError}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={styles.analysisAccentIconError}>
|
||||
<WarningIcon />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
||||
Pain Points
|
||||
</Typography>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{targetAudience.pain_points.map((painPoint: string, index: number) => (
|
||||
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
|
||||
{painPoint}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{targetAudience.motivations && targetAudience.motivations.length > 0 && (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={styles.analysisAccentIconSuccess}>
|
||||
<TrendingUpIcon />
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
|
||||
Motivations
|
||||
</Typography>
|
||||
<Box component="ul" sx={styles.analysisList}>
|
||||
{targetAudience.motivations.map((motivation: string, index: number) => (
|
||||
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
|
||||
{motivation}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TargetAudienceAnalysisSection;
|
||||
@@ -7,3 +7,5 @@ export { default as AnalysisResultsDisplay } from './AnalysisResultsDisplay';
|
||||
export { default as AnalysisProgressDisplay } from './AnalysisProgressDisplay';
|
||||
export { default as EnhancedGuidelinesSection } from './EnhancedGuidelinesSection';
|
||||
export { default as KeyInsightsGrid } from './KeyInsightsGrid';
|
||||
export { default as ContentCharacteristicsSection } from './ContentCharacteristicsSection';
|
||||
export { default as TargetAudienceAnalysisSection } from './TargetAudienceAnalysisSection';
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
Zoom,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import { useTheme, alpha } from '@mui/material/styles';
|
||||
import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
CheckCircle as CheckIcon,
|
||||
@@ -39,49 +40,120 @@ import {
|
||||
} from '@mui/icons-material';
|
||||
|
||||
/**
|
||||
* Renders a key insight card with icon and value
|
||||
* Key Insight Card Component
|
||||
*/
|
||||
interface KeyInsightProps {
|
||||
title: string;
|
||||
value: string | string[];
|
||||
icon: React.ReactNode;
|
||||
color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
|
||||
}
|
||||
|
||||
const KeyInsightCard: React.FC<KeyInsightProps> = ({
|
||||
title,
|
||||
value,
|
||||
icon,
|
||||
color = 'primary'
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
|
||||
// Helper function to safely get palette colors
|
||||
const getPaletteColor = (colorKey: string) => {
|
||||
const palette = theme.palette as any;
|
||||
return palette[colorKey] || palette.primary;
|
||||
};
|
||||
|
||||
const paletteColor = getPaletteColor(color);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 2.5,
|
||||
mb: 0,
|
||||
borderRadius: 2.5,
|
||||
background: isDark
|
||||
? `linear-gradient(135deg, ${alpha(paletteColor.main, 0.08)} 0%, ${alpha(paletteColor.main, 0.04)} 100%)`
|
||||
: `linear-gradient(135deg, ${alpha(paletteColor.main, 0.06)} 0%, ${alpha(paletteColor.light, 0.08)} 100%)`,
|
||||
border: `2px solid`,
|
||||
borderColor: isDark
|
||||
? alpha(paletteColor.main, 0.2)
|
||||
: alpha(paletteColor.main, 0.15),
|
||||
borderLeftWidth: '5px',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
'&:hover': {
|
||||
background: isDark
|
||||
? `linear-gradient(135deg, ${alpha(paletteColor.main, 0.12)} 0%, ${alpha(paletteColor.main, 0.08)} 100%)`
|
||||
: `linear-gradient(135deg, ${alpha(paletteColor.main, 0.10)} 0%, ${alpha(paletteColor.light, 0.12)} 100%)`,
|
||||
borderColor: alpha(paletteColor.main, 0.4),
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: isDark
|
||||
? `0 12px 40px ${alpha(paletteColor.main, 0.2)}`
|
||||
: `0 12px 40px ${alpha(paletteColor.main, 0.15)}`
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box
|
||||
sx={{
|
||||
color: `${color}.main`,
|
||||
fontSize: '1.75rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 2,
|
||||
background: isDark
|
||||
? alpha(paletteColor.main, 0.15)
|
||||
: alpha(paletteColor.main, 0.1),
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
fontSize: '0.75rem',
|
||||
letterSpacing: '0.5px',
|
||||
textTransform: 'uppercase',
|
||||
color: isDark ? '#ffffff !important' : '#000000 !important', // Maximum contrast
|
||||
mb: 0.5,
|
||||
display: 'block'
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '1.05rem',
|
||||
color: isDark ? '#ffffff !important' : '#000000 !important', // Maximum contrast
|
||||
lineHeight: 1.4
|
||||
}}
|
||||
>
|
||||
{Array.isArray(value) ? value.join(', ') : value}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Backward-compatible wrapper function for renderKeyInsight
|
||||
* @deprecated Use KeyInsightCard component directly instead
|
||||
*/
|
||||
export const renderKeyInsight = (
|
||||
title: string,
|
||||
value: string | string[],
|
||||
icon: React.ReactNode,
|
||||
color: string = 'primary'
|
||||
) => (
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
p: 2,
|
||||
mb: 1.5,
|
||||
borderRadius: 2,
|
||||
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.12) 100%)',
|
||||
border: `1px solid rgba(255, 255, 255, 0.15)`,
|
||||
borderLeft: `4px solid`,
|
||||
borderLeftColor: `${color}.main`,
|
||||
backdropFilter: 'blur(10px)',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.16) 100%)',
|
||||
border: `1px solid rgba(255, 255, 255, 0.25)`,
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: '0 8px 25px rgba(0, 0, 0, 0.3)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box sx={{ color: `${color}.main`, fontSize: '1.2rem' }}>
|
||||
{icon}
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom sx={{ fontWeight: 600, fontSize: '0.85rem' }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="body1" fontWeight={600} color="text.primary" sx={{ fontSize: '0.95rem' }}>
|
||||
{Array.isArray(value) ? value.join(', ') : value}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
title: string,
|
||||
value: string | string[],
|
||||
icon: React.ReactNode,
|
||||
color: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info' = 'primary'
|
||||
) => <KeyInsightCard title={title} value={value} icon={icon} color={color} />;
|
||||
|
||||
/**
|
||||
* Renders a guidelines card with title, items, and icon
|
||||
|
||||
@@ -117,13 +117,28 @@ export const loadExistingAnalysis = async (analysisId: number, website: string):
|
||||
// Extract domain name for personalization
|
||||
const extractedDomain = extractDomainName(website);
|
||||
|
||||
// Combine all analysis data into a comprehensive object
|
||||
// Database structure: flat fields at top level
|
||||
// Need to combine them into the format expected by UI
|
||||
const comprehensiveAnalysis = {
|
||||
...result.analysis.style_analysis,
|
||||
guidelines: result.analysis.style_guidelines,
|
||||
// Top-level style analysis fields from database
|
||||
writing_style: result.analysis.writing_style,
|
||||
content_characteristics: result.analysis.content_characteristics,
|
||||
target_audience: result.analysis.target_audience,
|
||||
content_type: result.analysis.content_type,
|
||||
brand_analysis: result.analysis.brand_analysis,
|
||||
content_strategy_insights: result.analysis.content_strategy_insights,
|
||||
recommended_settings: result.analysis.recommended_settings,
|
||||
|
||||
// Extract guidelines from style_guidelines object
|
||||
guidelines: result.analysis.style_guidelines?.guidelines,
|
||||
best_practices: result.analysis.style_guidelines?.best_practices,
|
||||
avoid_elements: result.analysis.style_guidelines?.avoid_elements,
|
||||
content_strategy: result.analysis.style_guidelines?.content_strategy,
|
||||
ai_generation_tips: result.analysis.style_guidelines?.ai_generation_tips,
|
||||
competitive_advantages: result.analysis.style_guidelines?.competitive_advantages,
|
||||
content_calendar_suggestions: result.analysis.style_guidelines?.content_calendar_suggestions,
|
||||
|
||||
// Style patterns
|
||||
style_patterns: result.analysis.style_patterns,
|
||||
style_consistency: result.analysis.style_patterns?.style_consistency,
|
||||
unique_elements: result.analysis.style_patterns?.unique_elements
|
||||
@@ -193,10 +208,13 @@ export const performAnalysis = async (
|
||||
// Combine all analysis data into a comprehensive object
|
||||
const comprehensiveAnalysis = {
|
||||
...result.style_analysis,
|
||||
guidelines: result.style_guidelines,
|
||||
guidelines: result.style_guidelines?.guidelines,
|
||||
best_practices: result.style_guidelines?.best_practices,
|
||||
avoid_elements: result.style_guidelines?.avoid_elements,
|
||||
content_strategy: result.style_guidelines?.content_strategy,
|
||||
ai_generation_tips: result.style_guidelines?.ai_generation_tips,
|
||||
competitive_advantages: result.style_guidelines?.competitive_advantages,
|
||||
content_calendar_suggestions: result.style_guidelines?.content_calendar_suggestions,
|
||||
style_patterns: result.style_patterns,
|
||||
style_consistency: result.style_patterns?.style_consistency,
|
||||
unique_elements: result.style_patterns?.unique_elements
|
||||
|
||||
@@ -6,8 +6,9 @@ export const useOnboardingStyles = () => {
|
||||
const styles = {
|
||||
// Layout styles
|
||||
container: {
|
||||
maxWidth: 800,
|
||||
mx: 'auto',
|
||||
maxWidth: '100%', // Use full width for maximum data display
|
||||
mx: 0, // Remove auto margins to use full width
|
||||
px: { xs: 1, md: 2 }, // Minimal padding to maximize content area
|
||||
},
|
||||
|
||||
// Header styles
|
||||
@@ -241,17 +242,25 @@ export const useOnboardingStyles = () => {
|
||||
analysisContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
gap: 3,
|
||||
width: '100%',
|
||||
maxWidth: '100%', // Use full width for maximum data display
|
||||
mx: 0, // Remove auto margins to use full width
|
||||
px: { xs: 1, md: 2 }, // Minimal padding to maximize content area
|
||||
},
|
||||
|
||||
analysisHeaderCard: {
|
||||
mb: 2,
|
||||
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.08) 100%)',
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
||||
border: `1px solid rgba(255, 255, 255, 0.1)`,
|
||||
backdropFilter: 'blur(20px)',
|
||||
mb: 3,
|
||||
background: theme.palette.mode === 'dark'
|
||||
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.08) 100%)'
|
||||
: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
|
||||
borderRadius: 3,
|
||||
boxShadow: theme.palette.mode === 'dark'
|
||||
? '0 8px 32px rgba(0, 0, 0, 0.4)'
|
||||
: '0 4px 20px rgba(0, 0, 0, 0.08)',
|
||||
border: theme.palette.mode === 'dark'
|
||||
? `1px solid rgba(255, 255, 255, 0.1)`
|
||||
: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
||||
@@ -274,12 +283,12 @@ export const useOnboardingStyles = () => {
|
||||
analysisHeaderTitle: {
|
||||
fontWeight: 700,
|
||||
letterSpacing: '-0.025em',
|
||||
color: theme.palette.text.primary,
|
||||
color: theme.palette.mode === 'dark' ? '#ffffff' : '#1a202c', // High contrast dark text
|
||||
fontSize: '1.5rem',
|
||||
},
|
||||
|
||||
analysisHeaderSubtitle: {
|
||||
color: theme.palette.text.secondary,
|
||||
color: theme.palette.mode === 'dark' ? '#e2e8f0' : '#4a5568', // High contrast secondary text
|
||||
fontSize: '0.95rem',
|
||||
lineHeight: 1.5,
|
||||
mt: 0.5,
|
||||
@@ -292,11 +301,19 @@ export const useOnboardingStyles = () => {
|
||||
analysisSectionHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '1.1rem',
|
||||
mb: 1.5,
|
||||
gap: 1.5,
|
||||
fontWeight: 700,
|
||||
color: theme.palette.mode === 'dark' ? '#ffffff' : '#1a202c', // High contrast dark text
|
||||
fontSize: '1.25rem',
|
||||
mb: 2,
|
||||
pb: 1.5,
|
||||
borderBottom: `3px solid ${theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.primary.main, 0.4)
|
||||
: alpha(theme.palette.primary.main, 0.25)}`, // More prominent border
|
||||
'& .MuiSvgIcon-root': {
|
||||
fontSize: '1.5rem',
|
||||
color: theme.palette.mode === 'dark' ? theme.palette.primary.light : theme.palette.primary.main,
|
||||
}
|
||||
},
|
||||
|
||||
analysisSubheader: {
|
||||
|
||||
Reference in New Issue
Block a user