Merge main into cursor/implement-usage-based-subscription-and-monitoring-0179

- Resolved merge conflicts in gemini_grounded_provider.py
- Removed conflicting Python cache file
- Integrated latest LinkedIn Writer features from main branch
This commit is contained in:
ajaysi
2025-09-12 16:58:26 +05:30
24 changed files with 4196 additions and 1881 deletions

View File

@@ -21,6 +21,7 @@ from services.linkedin.content_generator_prompts import (
CarouselGenerator,
VideoScriptGenerator
)
from services.persona_analysis_service import PersonaAnalysisService
class ContentGenerator:
@@ -340,8 +341,27 @@ class ContentGenerator:
logger.error("Gemini Grounded Provider not available - cannot generate content without AI provider")
raise Exception("Gemini Grounded Provider not available - cannot generate content without AI provider")
# Build the prompt for grounded generation using the new prompt builder
prompt = PostPromptBuilder.build_post_prompt(request)
# Build the prompt for grounded generation using persona if available (DB vs session override)
persona_service = PersonaAnalysisService()
persona_data = persona_service.get_persona_for_platform(user_id=getattr(request, 'user_id', 1), platform='linkedin') if hasattr(request, 'user_id') else None
if getattr(request, 'persona_override', None):
try:
# Merge shallowly: override core and platform adaptation parts
override = request.persona_override
if persona_data:
core = persona_data.get('core_persona', {})
platform_adapt = persona_data.get('platform_adaptation', {})
if 'core_persona' in override:
core.update(override['core_persona'])
if 'platform_adaptation' in override:
platform_adapt.update(override['platform_adaptation'])
persona_data['core_persona'] = core
persona_data['platform_adaptation'] = platform_adapt
else:
persona_data = override
except Exception:
pass
prompt = PostPromptBuilder.build_post_prompt(request, persona=persona_data)
# Generate grounded content using native Google Search grounding
result = await self.gemini_grounded.generate_grounded_content(
@@ -395,8 +415,26 @@ class ContentGenerator:
logger.error("Gemini Grounded Provider not available - cannot generate content without AI provider")
raise Exception("Gemini Grounded Provider not available - cannot generate content without AI provider")
# Build the prompt for grounded generation using the new prompt builder
prompt = ArticlePromptBuilder.build_article_prompt(request)
# Build the prompt for grounded generation using persona if available (DB vs session override)
persona_service = PersonaAnalysisService()
persona_data = persona_service.get_persona_for_platform(user_id=getattr(request, 'user_id', 1), platform='linkedin') if hasattr(request, 'user_id') else None
if getattr(request, 'persona_override', None):
try:
override = request.persona_override
if persona_data:
core = persona_data.get('core_persona', {})
platform_adapt = persona_data.get('platform_adaptation', {})
if 'core_persona' in override:
core.update(override['core_persona'])
if 'platform_adaptation' in override:
platform_adapt.update(override['platform_adaptation'])
persona_data['core_persona'] = core
persona_data['platform_adaptation'] = platform_adapt
else:
persona_data = override
except Exception:
pass
prompt = ArticlePromptBuilder.build_article_prompt(request, persona=persona_data)
# Generate grounded content using native Google Search grounding
result = await self.gemini_grounded.generate_grounded_content(

View File

@@ -4,14 +4,14 @@ LinkedIn Article Generation Prompts
This module contains prompt templates and builders for generating LinkedIn articles.
"""
from typing import Any
from typing import Any, Optional, Dict
class ArticlePromptBuilder:
"""Builder class for LinkedIn article generation prompts."""
@staticmethod
def build_article_prompt(request: Any) -> str:
def build_article_prompt(request: Any, persona: Optional[Dict[str, Any]] = None) -> str:
"""
Build prompt for article generation.
@@ -21,6 +21,27 @@ class ArticlePromptBuilder:
Returns:
Formatted prompt string for article generation
"""
persona_block = ""
if persona:
try:
core = persona.get('core_persona', persona)
platform_adaptation = persona.get('platform_adaptation', persona.get('platform_persona', {}))
linguistic = core.get('linguistic_fingerprint', {})
sentence_metrics = linguistic.get('sentence_metrics', {})
lexical_features = linguistic.get('lexical_features', {})
tonal_range = core.get('tonal_range', {})
persona_block = f"""
PERSONA CONTEXT:
- Persona Name: {core.get('persona_name', 'N/A')}
- Archetype: {core.get('archetype', 'N/A')}
- Core Belief: {core.get('core_belief', 'N/A')}
- Default Tone: {tonal_range.get('default_tone', request.tone)}
- Avg Sentence Length: {sentence_metrics.get('average_sentence_length_words', 18)} words
- Go-to Words: {', '.join(lexical_features.get('go_to_words', [])[:5])}
""".rstrip()
except Exception:
persona_block = ""
prompt = f"""
You are a senior content strategist and industry expert specializing in {request.industry}. Create a comprehensive, thought-provoking LinkedIn article that establishes authority, drives engagement, and provides genuine value to professionals in this field.
@@ -30,6 +51,8 @@ class ArticlePromptBuilder:
TARGET AUDIENCE: {request.target_audience or 'Industry professionals, executives, and thought leaders'}
WORD COUNT: {request.word_count} words
{persona_block}
CONTENT STRUCTURE:
- Compelling headline that promises specific value
- Engaging introduction with a hook and clear value proposition

View File

@@ -4,14 +4,14 @@ LinkedIn Post Generation Prompts
This module contains prompt templates and builders for generating LinkedIn posts.
"""
from typing import Any
from typing import Any, Optional, Dict
class PostPromptBuilder:
"""Builder class for LinkedIn post generation prompts."""
@staticmethod
def build_post_prompt(request: Any) -> str:
def build_post_prompt(request: Any, persona: Optional[Dict[str, Any]] = None) -> str:
"""
Build prompt for post generation.
@@ -21,6 +21,33 @@ class PostPromptBuilder:
Returns:
Formatted prompt string for post generation
"""
persona_block = ""
if persona:
try:
# Expecting structure similar to persona_service.get_persona_for_platform output
core = persona.get('core_persona', persona)
platform_adaptation = persona.get('platform_adaptation', persona.get('platform_persona', {}))
linguistic = core.get('linguistic_fingerprint', {})
sentence_metrics = linguistic.get('sentence_metrics', {})
lexical_features = linguistic.get('lexical_features', {})
rhetorical_devices = linguistic.get('rhetorical_devices', {})
tonal_range = core.get('tonal_range', {})
persona_block = f"""
PERSONA CONTEXT:
- Persona Name: {core.get('persona_name', 'N/A')}
- Archetype: {core.get('archetype', 'N/A')}
- Core Belief: {core.get('core_belief', 'N/A')}
- Tone: {tonal_range.get('default_tone', request.tone)}
- Sentence Length (avg): {sentence_metrics.get('average_sentence_length_words', 15)} words
- Preferred Sentence Type: {sentence_metrics.get('preferred_sentence_type', 'simple and compound')}
- Go-to Words: {', '.join(lexical_features.get('go_to_words', [])[:5])}
- Avoid Words: {', '.join(lexical_features.get('avoid_words', [])[:5])}
- Rhetorical Style: {rhetorical_devices.get('summary','balanced rhetorical questions and examples')}
""".rstrip()
except Exception:
persona_block = ""
prompt = f"""
You are an expert LinkedIn content strategist with 10+ years of experience in the {request.industry} industry. Create a highly engaging, professional LinkedIn post that drives meaningful engagement and establishes thought leadership.
@@ -30,6 +57,8 @@ class PostPromptBuilder:
TARGET AUDIENCE: {request.target_audience or 'Industry professionals, decision-makers, and thought leaders'}
MAX LENGTH: {request.max_length} characters
{persona_block}
CONTENT REQUIREMENTS:
- Start with a compelling hook that addresses a pain point or opportunity
- Include 2-3 specific, actionable insights or data points

View File

@@ -396,9 +396,7 @@ class GeminiGroundedProvider:
logger.error(f"First candidate structure: {dir(candidates[0]) if candidates else 'No candidates'}")
raise ValueError("No grounding metadata found - grounding is not working properly")
else:
logger.error("❌ CRITICAL: No candidates found in response")
logger.error(f"Response structure: {dir(response)}")
raise ValueError("No candidates found in response - grounding is not working properly")
logger.warning("⚠️ No candidates found in response. Returning content without sources.")
# Add content-specific processing
if content_type == "linkedin_post":