ALwrity onboarding fixes
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
"title": "AI LLM Providers",
|
"title": "AI LLM Providers",
|
||||||
"description": "Configure AI language model providers",
|
"description": "Configure AI language model providers",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"completed_at": "2025-09-30T11:54:21.688932",
|
"completed_at": "2025-10-03T17:29:12.878656",
|
||||||
"data": {
|
"data": {
|
||||||
"api_keys": {
|
"api_keys": {
|
||||||
"gemini": "AIzaSyB6QrCiOBAzh8xLdmSumec2ysdHeyqyxgw",
|
"gemini": "AIzaSyB6QrCiOBAzh8xLdmSumec2ysdHeyqyxgw",
|
||||||
@@ -19,9 +19,175 @@
|
|||||||
"step_number": 2,
|
"step_number": 2,
|
||||||
"title": "Website Analysis",
|
"title": "Website Analysis",
|
||||||
"description": "Set up website analysis and crawling",
|
"description": "Set up website analysis and crawling",
|
||||||
"status": "pending",
|
"status": "completed",
|
||||||
"completed_at": null,
|
"completed_at": "2025-10-03T17:42:17.953305",
|
||||||
"data": null,
|
"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": []
|
"validation_errors": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -61,9 +227,9 @@
|
|||||||
"validation_errors": []
|
"validation_errors": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"current_step": 2,
|
"current_step": 3,
|
||||||
"started_at": "2025-09-29T17:22:14.375002",
|
"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,
|
"is_completed": false,
|
||||||
"completed_at": null
|
"completed_at": null
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ from sqlalchemy.orm import Session
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from models.component_logic import (
|
from models.component_logic import (
|
||||||
UserInfoRequest, UserInfoResponse,
|
UserInfoRequest, UserInfoResponse,
|
||||||
@@ -45,6 +46,23 @@ research_utilities = ResearchUtilities()
|
|||||||
# Create router
|
# Create router
|
||||||
router = APIRouter(prefix="/api/onboarding", tags=["component_logic"])
|
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
|
# AI Research Endpoints
|
||||||
|
|
||||||
@router.post("/ai-research/validate-user", response_model=UserInfoResponse)
|
@router.post("/ai-research/validate-user", response_model=UserInfoResponse)
|
||||||
@@ -99,11 +117,8 @@ async def configure_research_preferences(
|
|||||||
preferences_service = ResearchPreferencesService(db)
|
preferences_service = ResearchPreferencesService(db)
|
||||||
|
|
||||||
# Use authenticated Clerk user ID for proper user isolation
|
# Use authenticated Clerk user ID for proper user isolation
|
||||||
# Convert user_id to int if service expects it, or update service to accept string
|
# Use consistent SHA256-based conversion
|
||||||
try:
|
user_id_int = clerk_user_id_to_int(user_id)
|
||||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
|
||||||
except:
|
|
||||||
user_id_int = hash(user_id) % 2147483647
|
|
||||||
|
|
||||||
# Save preferences with user ID (not session_id)
|
# Save preferences with user ID (not session_id)
|
||||||
preferences_id = preferences_service.save_preferences_with_style_data(user_id_int, preferences)
|
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)
|
analysis_service = WebsiteAnalysisService(db_session)
|
||||||
|
|
||||||
# Use authenticated Clerk user ID for proper user isolation
|
# Use authenticated Clerk user ID for proper user isolation
|
||||||
try:
|
# Use consistent SHA256-based conversion
|
||||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
user_id_int = clerk_user_id_to_int(user_id)
|
||||||
except:
|
|
||||||
user_id_int = hash(user_id) % 2147483647
|
|
||||||
|
|
||||||
# Check for existing analysis if URL is provided
|
# Check for existing analysis if URL is provided
|
||||||
existing_analysis = None
|
existing_analysis = None
|
||||||
@@ -536,11 +549,44 @@ async def complete_style_detection(
|
|||||||
timestamp=datetime.now().isoformat()
|
timestamp=datetime.now().isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 2: Analyze style
|
# Step 2-4: Parallelize AI API calls for performance (3 calls → 1 parallel batch)
|
||||||
style_analysis = style_logic.analyze_content_style(crawl_result['content'])
|
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'):
|
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'
|
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:
|
if 'API key' in error_msg or 'configure' in error_msg:
|
||||||
return StyleDetectionResponse(
|
return StyleDetectionResponse(
|
||||||
@@ -549,7 +595,6 @@ async def complete_style_detection(
|
|||||||
timestamp=datetime.now().isoformat()
|
timestamp=datetime.now().isoformat()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Save error analysis
|
|
||||||
analysis_service.save_error_analysis(user_id_int, request.url or "text_sample", error_msg)
|
analysis_service.save_error_analysis(user_id_int, request.url or "text_sample", error_msg)
|
||||||
return StyleDetectionResponse(
|
return StyleDetectionResponse(
|
||||||
success=False,
|
success=False,
|
||||||
@@ -557,17 +602,20 @@ async def complete_style_detection(
|
|||||||
timestamp=datetime.now().isoformat()
|
timestamp=datetime.now().isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 3: Analyze patterns (optional)
|
# Process patterns result
|
||||||
style_patterns = None
|
style_patterns = None
|
||||||
if request.include_patterns:
|
if request.include_patterns and patterns_result and not isinstance(patterns_result, Exception):
|
||||||
patterns_result = style_logic.analyze_style_patterns(crawl_result['content'])
|
if patterns_result.get('success'):
|
||||||
if patterns_result and patterns_result.get('success'):
|
|
||||||
style_patterns = patterns_result.get('patterns')
|
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
|
style_guidelines = None
|
||||||
if request.include_guidelines:
|
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'):
|
if guidelines_result and guidelines_result.get('success'):
|
||||||
style_guidelines = guidelines_result.get('guidelines')
|
style_guidelines = guidelines_result.get('guidelines')
|
||||||
|
|
||||||
@@ -628,10 +676,8 @@ async def check_existing_analysis(
|
|||||||
analysis_service = WebsiteAnalysisService(db_session)
|
analysis_service = WebsiteAnalysisService(db_session)
|
||||||
|
|
||||||
# Use authenticated Clerk user ID for proper user isolation
|
# Use authenticated Clerk user ID for proper user isolation
|
||||||
try:
|
# Use consistent SHA256-based conversion
|
||||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
user_id_int = clerk_user_id_to_int(user_id)
|
||||||
except:
|
|
||||||
user_id_int = hash(user_id) % 2147483647
|
|
||||||
|
|
||||||
# Check for existing analysis for THIS USER ONLY
|
# Check for existing analysis for THIS USER ONLY
|
||||||
existing_analysis = analysis_service.check_existing_analysis(user_id_int, website_url)
|
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)
|
analysis_service = WebsiteAnalysisService(db_session)
|
||||||
|
|
||||||
# Use authenticated Clerk user ID for proper user isolation
|
# Use authenticated Clerk user ID for proper user isolation
|
||||||
try:
|
# Use consistent SHA256-based conversion
|
||||||
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
|
user_id_int = clerk_user_id_to_int(user_id)
|
||||||
except:
|
|
||||||
user_id_int = hash(user_id) % 2147483647
|
|
||||||
|
|
||||||
# Get analyses for THIS USER ONLY (not all users!)
|
# Get analyses for THIS USER ONLY (not all users!)
|
||||||
analyses = analysis_service.get_session_analyses(user_id_int)
|
analyses = analysis_service.get_session_analyses(user_id_int)
|
||||||
|
|||||||
@@ -117,26 +117,24 @@ class ClerkAuthMiddleware:
|
|||||||
# Use cached PyJWKClient to avoid repeated JWKS fetches
|
# Use cached PyJWKClient to avoid repeated JWKS fetches
|
||||||
if jwks_url not in self._jwks_client_cache:
|
if jwks_url not in self._jwks_client_cache:
|
||||||
logger.info(f"Creating new PyJWKClient for {jwks_url} with caching enabled")
|
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(
|
self._jwks_client_cache[jwks_url] = PyJWKClient(
|
||||||
jwks_url,
|
jwks_url,
|
||||||
cache_keys=True,
|
cache_keys=True,
|
||||||
max_cached_keys=16,
|
max_cached_keys=16
|
||||||
cache_jwk_set_timeout=3600, # Cache JWKS for 1 hour
|
|
||||||
timeout=10 # 10 second timeout for JWKS fetch
|
|
||||||
)
|
)
|
||||||
|
|
||||||
jwks_client = self._jwks_client_cache[jwks_url]
|
jwks_client = self._jwks_client_cache[jwks_url]
|
||||||
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
||||||
|
|
||||||
# Verify and decode the token with clock skew tolerance
|
# 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(
|
decoded_token = jwt.decode(
|
||||||
token,
|
token,
|
||||||
signing_key.key,
|
signing_key.key,
|
||||||
algorithms=["RS256"],
|
algorithms=["RS256"],
|
||||||
options={"verify_signature": True, "verify_exp": True},
|
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
|
# Extract user information
|
||||||
@@ -171,7 +169,7 @@ class ClerkAuthMiddleware:
|
|||||||
decoded_token = jwt.decode(
|
decoded_token = jwt.decode(
|
||||||
token,
|
token,
|
||||||
options={"verify_signature": False},
|
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
|
# Extract user information from the token
|
||||||
|
|||||||
@@ -41,11 +41,17 @@ class WebsiteAnalysisService:
|
|||||||
|
|
||||||
if existing_analysis:
|
if existing_analysis:
|
||||||
# Update existing analysis
|
# Update existing analysis
|
||||||
existing_analysis.writing_style = analysis_data.get('style_analysis', {}).get('writing_style')
|
style_analysis = analysis_data.get('style_analysis', {})
|
||||||
existing_analysis.content_characteristics = analysis_data.get('style_analysis', {}).get('content_characteristics')
|
existing_analysis.writing_style = style_analysis.get('writing_style')
|
||||||
existing_analysis.target_audience = analysis_data.get('style_analysis', {}).get('target_audience')
|
existing_analysis.content_characteristics = style_analysis.get('content_characteristics')
|
||||||
existing_analysis.content_type = analysis_data.get('style_analysis', {}).get('content_type')
|
existing_analysis.target_audience = style_analysis.get('target_audience')
|
||||||
existing_analysis.recommended_settings = analysis_data.get('style_analysis', {}).get('recommended_settings')
|
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.crawl_result = analysis_data.get('crawl_result')
|
||||||
existing_analysis.style_patterns = analysis_data.get('style_patterns')
|
existing_analysis.style_patterns = analysis_data.get('style_patterns')
|
||||||
existing_analysis.style_guidelines = analysis_data.get('style_guidelines')
|
existing_analysis.style_guidelines = analysis_data.get('style_guidelines')
|
||||||
@@ -59,20 +65,28 @@ class WebsiteAnalysisService:
|
|||||||
return existing_analysis.id
|
return existing_analysis.id
|
||||||
else:
|
else:
|
||||||
# Create new analysis
|
# Create new analysis
|
||||||
analysis = WebsiteAnalysis(
|
style_analysis = analysis_data.get('style_analysis', {})
|
||||||
session_id=session_id,
|
analysis_args = {
|
||||||
website_url=website_url,
|
'session_id': session_id,
|
||||||
writing_style=analysis_data.get('style_analysis', {}).get('writing_style'),
|
'website_url': website_url,
|
||||||
content_characteristics=analysis_data.get('style_analysis', {}).get('content_characteristics'),
|
'writing_style': style_analysis.get('writing_style'),
|
||||||
target_audience=analysis_data.get('style_analysis', {}).get('target_audience'),
|
'content_characteristics': style_analysis.get('content_characteristics'),
|
||||||
content_type=analysis_data.get('style_analysis', {}).get('content_type'),
|
'target_audience': style_analysis.get('target_audience'),
|
||||||
recommended_settings=analysis_data.get('style_analysis', {}).get('recommended_settings'),
|
'content_type': style_analysis.get('content_type'),
|
||||||
crawl_result=analysis_data.get('crawl_result'),
|
'recommended_settings': style_analysis.get('recommended_settings'),
|
||||||
style_patterns=analysis_data.get('style_patterns'),
|
'crawl_result': analysis_data.get('crawl_result'),
|
||||||
style_guidelines=analysis_data.get('style_guidelines'),
|
'style_patterns': analysis_data.get('style_patterns'),
|
||||||
status='completed',
|
'style_guidelines': analysis_data.get('style_guidelines'),
|
||||||
warning_message=analysis_data.get('warning')
|
'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.add(analysis)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|||||||
@@ -82,13 +82,64 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
const [showHighlightsModal, setShowHighlightsModal] = useState(false);
|
const [showHighlightsModal, setShowHighlightsModal] = useState(false);
|
||||||
const [selectedCompetitorHighlights, setSelectedCompetitorHighlights] = useState<string[]>([]);
|
const [selectedCompetitorHighlights, setSelectedCompetitorHighlights] = useState<string[]>([]);
|
||||||
const [selectedCompetitorTitle, setSelectedCompetitorTitle] = 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);
|
setIsAnalyzing(true);
|
||||||
setShowProgressModal(true);
|
setShowProgressModal(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setAnalysisProgress(0);
|
setAnalysisProgress(0);
|
||||||
setAnalysisStep('Initializing competitor discovery...');
|
setAnalysisStep('Initializing competitor discovery...');
|
||||||
|
setUsingCachedData(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setAnalysisStep('Validating session...');
|
setAnalysisStep('Validating session...');
|
||||||
@@ -139,10 +190,28 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
setAnalysisProgress(100);
|
setAnalysisProgress(100);
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
setCompetitors(result.competitors || []);
|
const analysisData = {
|
||||||
setSocialMediaAccounts(result.social_media_accounts || {});
|
competitors: result.competitors || [],
|
||||||
setSocialMediaCitations(result.social_media_citations || []);
|
social_media_accounts: result.social_media_accounts || {},
|
||||||
setResearchSummary(result.research_summary || null);
|
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);
|
setShowProgressModal(false);
|
||||||
setIsAnalyzing(false);
|
setIsAnalyzing(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -154,11 +223,23 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
setIsAnalyzing(false);
|
setIsAnalyzing(false);
|
||||||
setShowProgressModal(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(() => {
|
useEffect(() => {
|
||||||
startCompetitorDiscovery();
|
const initialize = async () => {
|
||||||
}, [startCompetitorDiscovery]);
|
// 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 handleContinue = () => {
|
||||||
const researchData = {
|
const researchData = {
|
||||||
@@ -180,20 +261,58 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box sx={classes.container}>
|
<Box sx={classes.container}>
|
||||||
<Box sx={classes.header}>
|
<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
|
Research Your Competition
|
||||||
</Typography>
|
</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
|
Discover your competitors and analyze their strategies to gain competitive advantage
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</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 && (
|
{error && (
|
||||||
<Alert severity="error" sx={{ mb: 3 }}>
|
<Alert severity="error" sx={{ mb: 3 }}>
|
||||||
{error}
|
{error}
|
||||||
<Button
|
<Button
|
||||||
startIcon={<RefreshIcon />}
|
startIcon={<RefreshIcon />}
|
||||||
onClick={startCompetitorDiscovery}
|
onClick={() => startCompetitorDiscovery(true)}
|
||||||
sx={{ ml: 2 }}
|
sx={{ ml: 2 }}
|
||||||
>
|
>
|
||||||
Retry
|
Retry
|
||||||
@@ -204,23 +323,36 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
{!isAnalyzing && !error && (competitors.length > 0 || researchSummary) && (
|
{!isAnalyzing && !error && (competitors.length > 0 || researchSummary) && (
|
||||||
<Box>
|
<Box>
|
||||||
{researchSummary && (
|
{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">
|
<Typography variant="h6" gutterBottom fontWeight={600} color="primary">
|
||||||
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||||
Research Summary
|
Research Summary
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Grid container spacing={3} mt={1}>
|
<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}>
|
<Typography variant="h4" color="primary" fontWeight={700}>
|
||||||
{researchSummary.total_competitors}
|
{researchSummary.total_competitors}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ color: '#4a5568 !important' }} // Force dark text for readability
|
||||||
|
>
|
||||||
Competitors Found
|
Competitors Found
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={9}>
|
<Grid item xs={12} sm={6} md={9}>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: '#2d3748 !important' }} // Force dark text for readability
|
||||||
|
>
|
||||||
{researchSummary.market_insights}
|
{researchSummary.market_insights}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -231,8 +363,14 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
{/* Social Media Accounts Section */}
|
{/* Social Media Accounts Section */}
|
||||||
{Object.keys(socialMediaAccounts).length > 0 && (
|
{Object.keys(socialMediaAccounts).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Typography variant="h6" gutterBottom fontWeight={600} mb={3}>
|
<Typography
|
||||||
<ShareIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
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
|
Social Media Presence
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -250,8 +388,18 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={12} sm={6} md={4} key={platform}>
|
<Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={platform}>
|
||||||
<Card sx={{ height: '100%' }}>
|
<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>
|
<CardContent>
|
||||||
<Box display="flex" alignItems="center" gap={2}>
|
<Box display="flex" alignItems="center" gap={2}>
|
||||||
<Avatar sx={{ width: 40, height: 40, bgcolor: 'primary.main' }}>
|
<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}>
|
<Typography
|
||||||
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
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})
|
Discovered Competitors ({competitors.length})
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
{competitors.map((competitor, index) => (
|
{competitors.map((competitor, index) => (
|
||||||
<Grid item xs={12} md={6} lg={4} key={index}>
|
<Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={index}>
|
||||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<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 }}>
|
<CardContent sx={{ flexGrow: 1 }}>
|
||||||
<Box display="flex" alignItems="flex-start" gap={2} mb={2}>
|
<Box display="flex" alignItems="flex-start" gap={2} mb={2}>
|
||||||
<Avatar sx={{ width: 40, height: 40 }}>
|
<Avatar sx={{ width: 40, height: 40 }}>
|
||||||
<BusinessIcon />
|
<BusinessIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Box flex={1}>
|
<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}
|
{competitor.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ color: '#4a5568 !important' }} // Force dark text for readability
|
||||||
|
>
|
||||||
{competitor.domain}
|
{competitor.domain}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
@@ -311,7 +486,11 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</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.length > 150
|
||||||
? `${competitor.summary.substring(0, 150)}...`
|
? `${competitor.summary.substring(0, 150)}...`
|
||||||
: competitor.summary
|
: competitor.summary
|
||||||
|
|||||||
@@ -8,27 +8,17 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
|
||||||
Grid,
|
Grid,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContentText,
|
DialogContentText
|
||||||
Fade,
|
|
||||||
LinearProgress,
|
|
||||||
Stepper,
|
|
||||||
Step,
|
|
||||||
StepLabel,
|
|
||||||
Checkbox,
|
|
||||||
FormControlLabel
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
Analytics as AnalyticsIcon,
|
Analytics as AnalyticsIcon,
|
||||||
History as HistoryIcon,
|
History as HistoryIcon,
|
||||||
Business as BusinessIcon,
|
Business as BusinessIcon
|
||||||
Star as StarIcon,
|
|
||||||
Verified as VerifiedIcon
|
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
// Extracted components
|
// Extracted components
|
||||||
@@ -37,7 +27,6 @@ import { AnalysisResultsDisplay, AnalysisProgressDisplay } from './WebsiteStep/c
|
|||||||
// Extracted utilities
|
// Extracted utilities
|
||||||
import {
|
import {
|
||||||
fixUrlFormat,
|
fixUrlFormat,
|
||||||
extractDomainName,
|
|
||||||
checkExistingAnalysis,
|
checkExistingAnalysis,
|
||||||
loadExistingAnalysis,
|
loadExistingAnalysis,
|
||||||
performAnalysis,
|
performAnalysis,
|
||||||
@@ -336,18 +325,17 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stepData = {
|
// Set the loaded analysis data for display
|
||||||
website: fixedUrl,
|
setDomainName(result.domainName || domainName);
|
||||||
domainName: result.domainName || domainName,
|
setAnalysis(result.analysis);
|
||||||
analysis: result.analysis,
|
setSuccess('Previous analysis loaded successfully!');
|
||||||
useAnalysisForGenAI,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store in localStorage for Step 3 (Competitor Analysis)
|
// Store in localStorage for Step 3 (Competitor Analysis)
|
||||||
localStorage.setItem('website_url', fixedUrl);
|
localStorage.setItem('website_url', fixedUrl);
|
||||||
localStorage.setItem('website_analysis_data', JSON.stringify(result.analysis));
|
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 () => {
|
const handleNewAnalysis = async () => {
|
||||||
|
|||||||
@@ -14,22 +14,15 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Alert,
|
Alert,
|
||||||
Paper,
|
Paper
|
||||||
Tooltip
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
AutoAwesome as AutoAwesomeIcon,
|
AutoAwesome as AutoAwesomeIcon,
|
||||||
Verified as VerifiedIcon,
|
Verified as VerifiedIcon,
|
||||||
Psychology as PsychologyIcon,
|
|
||||||
Analytics as AnalyticsIcon,
|
Analytics as AnalyticsIcon,
|
||||||
TrendingUp as TrendingUpIcon,
|
TrendingUp as TrendingUpIcon,
|
||||||
Language as LanguageIcon,
|
|
||||||
Palette as PaletteIcon,
|
|
||||||
Speed as SpeedIcon,
|
|
||||||
Group as GroupIcon,
|
|
||||||
Business as BusinessIcon,
|
Business as BusinessIcon,
|
||||||
Lightbulb as LightbulbIcon,
|
Lightbulb as LightbulbIcon
|
||||||
Warning as WarningIcon
|
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
// Import rendering utilities
|
// Import rendering utilities
|
||||||
@@ -45,7 +38,12 @@ import {
|
|||||||
} from '../utils/renderUtils';
|
} from '../utils/renderUtils';
|
||||||
|
|
||||||
// Import extracted components
|
// Import extracted components
|
||||||
import { EnhancedGuidelinesSection, KeyInsightsGrid } from './index';
|
import {
|
||||||
|
EnhancedGuidelinesSection,
|
||||||
|
KeyInsightsGrid,
|
||||||
|
ContentCharacteristicsSection,
|
||||||
|
TargetAudienceAnalysisSection
|
||||||
|
} from './index';
|
||||||
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
|
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
|
||||||
|
|
||||||
interface StyleAnalysis {
|
interface StyleAnalysis {
|
||||||
@@ -169,10 +167,24 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
<Box sx={styles.analysisHeader}>
|
<Box sx={styles.analysisHeader}>
|
||||||
<VerifiedIcon sx={styles.analysisHeaderIcon} />
|
<VerifiedIcon sx={styles.analysisHeaderIcon} />
|
||||||
<Box>
|
<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
|
{domainName} Style Analysis
|
||||||
</Typography>
|
</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
|
Comprehensive content analysis and personalized recommendations
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -186,253 +198,32 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Content Characteristics Section */}
|
{/* Content Characteristics Section */}
|
||||||
{analysis.content_characteristics && (
|
<ContentCharacteristicsSection
|
||||||
<Box sx={styles.analysisSection}>
|
contentCharacteristics={analysis.content_characteristics as any}
|
||||||
<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 && (
|
{/* Target Audience Analysis Section */}
|
||||||
<Grid item xs={12} md={6}>
|
<TargetAudienceAnalysisSection
|
||||||
<Tooltip title="How easy it is for readers to understand the content. Higher scores mean the content is easier to read and comprehend." arrow>
|
targetAudience={analysis.target_audience as any}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Content Type Details Section */}
|
{/* Content Type Details Section */}
|
||||||
{analysis.content_type && (
|
{analysis.content_type && (
|
||||||
<Box sx={styles.analysisSection}>
|
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
|
||||||
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
|
<Typography
|
||||||
<BusinessIcon color="primary" />
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
...styles.analysisSectionHeader,
|
||||||
|
color: '#1a202c !important', // Force dark text
|
||||||
|
fontWeight: '700 !important'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BusinessIcon sx={{ color: '#667eea !important' }} />
|
||||||
Content Type Analysis
|
Content Type Analysis
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{analysis.content_type.purpose && (
|
{analysis.content_type.purpose && (
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
'Content Purpose',
|
'Content Purpose',
|
||||||
analysis.content_type.purpose,
|
analysis.content_type.purpose,
|
||||||
@@ -443,7 +234,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.content_type.call_to_action && (
|
{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(
|
{renderKeyInsight(
|
||||||
'Call to Action Style',
|
'Call to Action Style',
|
||||||
analysis.content_type.call_to_action,
|
analysis.content_type.call_to_action,
|
||||||
@@ -454,7 +245,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.content_type.conversion_focus && (
|
{analysis.content_type.conversion_focus && (
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
'Conversion Focus',
|
'Conversion Focus',
|
||||||
analysis.content_type.conversion_focus,
|
analysis.content_type.conversion_focus,
|
||||||
@@ -465,7 +256,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.content_type.educational_value && (
|
{analysis.content_type.educational_value && (
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
'Educational Value',
|
'Educational Value',
|
||||||
analysis.content_type.educational_value,
|
analysis.content_type.educational_value,
|
||||||
@@ -507,7 +298,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Content Strategy */}
|
{/* Content Strategy */}
|
||||||
{analysis.content_strategy && (
|
{analysis.content_strategy && (
|
||||||
<Box sx={styles.analysisSection}>
|
<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" />
|
<AutoAwesomeIcon color="primary" />
|
||||||
Content Strategy
|
Content Strategy
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -522,14 +321,22 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Recommended Settings for AI Generation */}
|
{/* Recommended Settings for AI Generation */}
|
||||||
{analysis.recommended_settings && (
|
{analysis.recommended_settings && (
|
||||||
<Box sx={styles.analysisSection}>
|
<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" />
|
<AutoAwesomeIcon color="primary" />
|
||||||
Recommended AI Generation Settings
|
Recommended AI Generation Settings
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paper elevation={3} sx={styles.analysisGradientPaperPrimary}>
|
<Paper elevation={3} sx={styles.analysisGradientPaperPrimary}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{analysis.recommended_settings.writing_tone && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Writing Tone:
|
Writing Tone:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -540,7 +347,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.recommended_settings.target_audience && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Target Audience:
|
Target Audience:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -551,7 +358,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.recommended_settings.content_type && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Content Type:
|
Content Type:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -562,7 +369,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.recommended_settings.creativity_level && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Creativity Level:
|
Creativity Level:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -573,7 +380,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.recommended_settings.industry_context && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Industry Context:
|
Industry Context:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -584,7 +391,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.recommended_settings.geographic_location && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Geographic Focus:
|
Geographic Focus:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -595,7 +402,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.recommended_settings.brand_alignment && (
|
{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}>
|
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
|
||||||
Brand Alignment:
|
Brand Alignment:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -628,7 +435,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Style Consistency Section */}
|
{/* Style Consistency Section */}
|
||||||
{analysis.style_consistency && (
|
{analysis.style_consistency && (
|
||||||
<Box sx={styles.analysisSection}>
|
<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" />
|
<AnalyticsIcon color="info" />
|
||||||
Style Consistency
|
Style Consistency
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -643,7 +458,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Unique Elements Section */}
|
{/* Unique Elements Section */}
|
||||||
{analysis.unique_elements && analysis.unique_elements.length > 0 && (
|
{analysis.unique_elements && analysis.unique_elements.length > 0 && (
|
||||||
<Box sx={styles.analysisSection}>
|
<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" />
|
<AutoAwesomeIcon color="primary" />
|
||||||
Unique Style Elements
|
Unique Style Elements
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -670,13 +493,13 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Best Practices & Avoid Elements */}
|
{/* Best Practices & Avoid Elements */}
|
||||||
<Grid container spacing={2} sx={styles.analysisSection}>
|
<Grid container spacing={2} sx={styles.analysisSection}>
|
||||||
{analysis.best_practices && (
|
{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)}
|
{renderBestPracticesSection(analysis.best_practices)}
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{analysis.avoid_elements && (
|
{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)}
|
{renderAvoidElementsSection(analysis.avoid_elements)}
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
@@ -685,7 +508,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Competitive Advantages */}
|
{/* Competitive Advantages */}
|
||||||
{analysis.competitive_advantages && analysis.competitive_advantages.length > 0 && (
|
{analysis.competitive_advantages && analysis.competitive_advantages.length > 0 && (
|
||||||
<Box sx={styles.analysisSection}>
|
<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" />
|
<TrendingUpIcon color="success" />
|
||||||
Competitive Advantages
|
Competitive Advantages
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -704,7 +535,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
{/* Content Calendar Suggestions */}
|
{/* Content Calendar Suggestions */}
|
||||||
{analysis.content_calendar_suggestions && analysis.content_calendar_suggestions.length > 0 && (
|
{analysis.content_calendar_suggestions && analysis.content_calendar_suggestions.length > 0 && (
|
||||||
<Box sx={styles.analysisSection}>
|
<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" />
|
<AnalyticsIcon color="primary" />
|
||||||
Content Calendar Suggestions
|
Content Calendar Suggestions
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -720,6 +559,56 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
|
|||||||
</Box>
|
</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 */}
|
{/* GenAI Integration Checkbox */}
|
||||||
<Box sx={styles.analysisCheckboxContainer}>
|
<Box sx={styles.analysisCheckboxContainer}>
|
||||||
<FormControlLabel
|
<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 (
|
return (
|
||||||
<Grid container spacing={2} sx={{ mb: 2.5 }}>
|
<Grid container spacing={2} sx={{ mb: 2.5 }}>
|
||||||
{writing_style?.tone && (
|
{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>
|
<Tooltip title="The emotional quality and attitude of the writing - how it makes readers feel and the mood it creates." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -71,7 +71,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writing_style?.complexity && (
|
{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>
|
<Tooltip title="How sophisticated or simple the content is. Moderate complexity balances depth with accessibility." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -86,7 +86,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writing_style?.voice && (
|
{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>
|
<Tooltip title="The unique personality and style of the writing - what makes it distinctive and recognizable." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -101,7 +101,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writing_style?.engagement_level && (
|
{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>
|
<Tooltip title="How well the content captures and maintains reader attention throughout the piece." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -116,7 +116,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writing_style?.brand_personality && (
|
{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>
|
<Tooltip title="The human characteristics and traits associated with the brand, like friendly, professional, or innovative." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -131,7 +131,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writing_style?.formality_level && (
|
{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>
|
<Tooltip title="How formal or casual the writing style is. Semi-formal strikes a balance between professional and approachable." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -146,7 +146,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writing_style?.emotional_appeal && (
|
{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>
|
<Tooltip title="How the content connects with readers' emotions - what feelings it aims to evoke." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -161,7 +161,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{target_audience?.expertise_level && (
|
{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>
|
<Tooltip title="The skill level and experience of the intended readers - from beginners to experts in the subject matter." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -176,7 +176,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{target_audience?.geographic_focus && target_audience.geographic_focus.trim() !== '' && (
|
{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>
|
<Tooltip title="The geographical regions or areas the content is primarily intended for - local, national, or global reach." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{renderKeyInsight(
|
||||||
@@ -191,7 +191,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{content_type?.primary_type && (
|
{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>
|
<Tooltip title="The main category or format of content being created - blog posts, tutorials, product descriptions, etc." arrow>
|
||||||
<Box>
|
<Box>
|
||||||
{renderKeyInsight(
|
{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 AnalysisProgressDisplay } from './AnalysisProgressDisplay';
|
||||||
export { default as EnhancedGuidelinesSection } from './EnhancedGuidelinesSection';
|
export { default as EnhancedGuidelinesSection } from './EnhancedGuidelinesSection';
|
||||||
export { default as KeyInsightsGrid } from './KeyInsightsGrid';
|
export { default as KeyInsightsGrid } from './KeyInsightsGrid';
|
||||||
|
export { default as ContentCharacteristicsSection } from './ContentCharacteristicsSection';
|
||||||
|
export { default as TargetAudienceAnalysisSection } from './TargetAudienceAnalysisSection';
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
Zoom,
|
Zoom,
|
||||||
Divider
|
Divider
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { useTheme, alpha } from '@mui/material/styles';
|
||||||
import {
|
import {
|
||||||
ExpandMore as ExpandMoreIcon,
|
ExpandMore as ExpandMoreIcon,
|
||||||
CheckCircle as CheckIcon,
|
CheckCircle as CheckIcon,
|
||||||
@@ -39,49 +40,120 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} 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 = (
|
export const renderKeyInsight = (
|
||||||
title: string,
|
title: string,
|
||||||
value: string | string[],
|
value: string | string[],
|
||||||
icon: React.ReactNode,
|
icon: React.ReactNode,
|
||||||
color: string = 'primary'
|
color: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info' = 'primary'
|
||||||
) => (
|
) => <KeyInsightCard title={title} value={value} icon={icon} color={color} />;
|
||||||
<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>
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a guidelines card with title, items, and icon
|
* 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
|
// Extract domain name for personalization
|
||||||
const extractedDomain = extractDomainName(website);
|
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 = {
|
const comprehensiveAnalysis = {
|
||||||
...result.analysis.style_analysis,
|
// Top-level style analysis fields from database
|
||||||
guidelines: result.analysis.style_guidelines,
|
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,
|
best_practices: result.analysis.style_guidelines?.best_practices,
|
||||||
avoid_elements: result.analysis.style_guidelines?.avoid_elements,
|
avoid_elements: result.analysis.style_guidelines?.avoid_elements,
|
||||||
content_strategy: result.analysis.style_guidelines?.content_strategy,
|
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_patterns: result.analysis.style_patterns,
|
||||||
style_consistency: result.analysis.style_patterns?.style_consistency,
|
style_consistency: result.analysis.style_patterns?.style_consistency,
|
||||||
unique_elements: result.analysis.style_patterns?.unique_elements
|
unique_elements: result.analysis.style_patterns?.unique_elements
|
||||||
@@ -193,10 +208,13 @@ export const performAnalysis = async (
|
|||||||
// Combine all analysis data into a comprehensive object
|
// Combine all analysis data into a comprehensive object
|
||||||
const comprehensiveAnalysis = {
|
const comprehensiveAnalysis = {
|
||||||
...result.style_analysis,
|
...result.style_analysis,
|
||||||
guidelines: result.style_guidelines,
|
guidelines: result.style_guidelines?.guidelines,
|
||||||
best_practices: result.style_guidelines?.best_practices,
|
best_practices: result.style_guidelines?.best_practices,
|
||||||
avoid_elements: result.style_guidelines?.avoid_elements,
|
avoid_elements: result.style_guidelines?.avoid_elements,
|
||||||
content_strategy: result.style_guidelines?.content_strategy,
|
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_patterns: result.style_patterns,
|
||||||
style_consistency: result.style_patterns?.style_consistency,
|
style_consistency: result.style_patterns?.style_consistency,
|
||||||
unique_elements: result.style_patterns?.unique_elements
|
unique_elements: result.style_patterns?.unique_elements
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ export const useOnboardingStyles = () => {
|
|||||||
const styles = {
|
const styles = {
|
||||||
// Layout styles
|
// Layout styles
|
||||||
container: {
|
container: {
|
||||||
maxWidth: 800,
|
maxWidth: '100%', // Use full width for maximum data display
|
||||||
mx: 'auto',
|
mx: 0, // Remove auto margins to use full width
|
||||||
|
px: { xs: 1, md: 2 }, // Minimal padding to maximize content area
|
||||||
},
|
},
|
||||||
|
|
||||||
// Header styles
|
// Header styles
|
||||||
@@ -241,17 +242,25 @@ export const useOnboardingStyles = () => {
|
|||||||
analysisContainer: {
|
analysisContainer: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 2,
|
gap: 3,
|
||||||
width: '100%',
|
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: {
|
analysisHeaderCard: {
|
||||||
mb: 2,
|
mb: 3,
|
||||||
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.08) 100%)',
|
background: theme.palette.mode === 'dark'
|
||||||
borderRadius: 2,
|
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.08) 100%)'
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
|
||||||
border: `1px solid rgba(255, 255, 255, 0.1)`,
|
borderRadius: 3,
|
||||||
backdropFilter: 'blur(20px)',
|
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',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -274,12 +283,12 @@ export const useOnboardingStyles = () => {
|
|||||||
analysisHeaderTitle: {
|
analysisHeaderTitle: {
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
letterSpacing: '-0.025em',
|
letterSpacing: '-0.025em',
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.mode === 'dark' ? '#ffffff' : '#1a202c', // High contrast dark text
|
||||||
fontSize: '1.5rem',
|
fontSize: '1.5rem',
|
||||||
},
|
},
|
||||||
|
|
||||||
analysisHeaderSubtitle: {
|
analysisHeaderSubtitle: {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.mode === 'dark' ? '#e2e8f0' : '#4a5568', // High contrast secondary text
|
||||||
fontSize: '0.95rem',
|
fontSize: '0.95rem',
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
mt: 0.5,
|
mt: 0.5,
|
||||||
@@ -292,11 +301,19 @@ export const useOnboardingStyles = () => {
|
|||||||
analysisSectionHeader: {
|
analysisSectionHeader: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1,
|
gap: 1.5,
|
||||||
fontWeight: 600,
|
fontWeight: 700,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.mode === 'dark' ? '#ffffff' : '#1a202c', // High contrast dark text
|
||||||
fontSize: '1.1rem',
|
fontSize: '1.25rem',
|
||||||
mb: 1.5,
|
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: {
|
analysisSubheader: {
|
||||||
|
|||||||
Reference in New Issue
Block a user