fix: pass editorial brief to LLM prompt + improve missing API key error
- Add 'brief' field to GenerateContentRequest schema - Pass brief from router to generate_post_text service - Inject brief as mandatory instructions in LLM prompt with highest priority - Return structured error when LLM provider/API key not configured - Show dedicated warning banner with link to Settings when API key missing Fixes: content ignoring editorial brief, unhelpful API key error messages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,9 +78,21 @@ def generate_content(
|
|||||||
model = request.model or _get_setting(db, "llm_model", current_user.id)
|
model = request.model or _get_setting(db, "llm_model", current_user.id)
|
||||||
|
|
||||||
if not provider_name:
|
if not provider_name:
|
||||||
raise HTTPException(status_code=400, detail="LLM provider not configured. Set 'llm_provider' in settings.")
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={
|
||||||
|
"message": "Provider AI non configurato. Vai in Impostazioni → Provider AI per scegliere il provider e inserire la API key.",
|
||||||
|
"missing_settings": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
if not api_key:
|
if not api_key:
|
||||||
raise HTTPException(status_code=400, detail="LLM API key not configured. Set 'llm_api_key' in settings.")
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail={
|
||||||
|
"message": "API key non configurata. Vai in Impostazioni → Provider AI per inserire la tua API key.",
|
||||||
|
"missing_settings": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# Build character dict for content service
|
# Build character dict for content service
|
||||||
char_dict = {
|
char_dict = {
|
||||||
@@ -98,6 +110,7 @@ def generate_content(
|
|||||||
llm_provider=llm,
|
llm_provider=llm,
|
||||||
platform=request.effective_platform,
|
platform=request.effective_platform,
|
||||||
topic_hint=request.topic_hint,
|
topic_hint=request.topic_hint,
|
||||||
|
brief=request.brief,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate hashtags
|
# Generate hashtags
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class GenerateContentRequest(BaseModel):
|
|||||||
platforms: List[str] = [] # new: multi-platform (overrides platform if non-empty)
|
platforms: List[str] = [] # new: multi-platform (overrides platform if non-empty)
|
||||||
content_types: List[str] = [] # new: multi-type (overrides content_type if non-empty)
|
content_types: List[str] = [] # new: multi-type (overrides content_type if non-empty)
|
||||||
topic_hint: Optional[str] = None
|
topic_hint: Optional[str] = None
|
||||||
|
brief: Optional[str] = None # editorial brief: technique + instructions for the LLM
|
||||||
include_affiliates: bool = True
|
include_affiliates: bool = True
|
||||||
provider: Optional[str] = None
|
provider: Optional[str] = None
|
||||||
model: Optional[str] = None
|
model: Optional[str] = None
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ def generate_post_text(
|
|||||||
llm_provider: LLMProvider,
|
llm_provider: LLMProvider,
|
||||||
platform: str,
|
platform: str,
|
||||||
topic_hint: str | None = None,
|
topic_hint: str | None = None,
|
||||||
|
brief: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Generate social media post text based on a character profile.
|
"""Generate social media post text based on a character profile.
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ def generate_post_text(
|
|||||||
topic_hint: Optional topic suggestion to guide generation.
|
topic_hint: Optional topic suggestion to guide generation.
|
||||||
llm_provider: LLM provider instance for text generation.
|
llm_provider: LLM provider instance for text generation.
|
||||||
platform: Target platform (e.g. 'instagram', 'facebook', 'tiktok', 'youtube').
|
platform: Target platform (e.g. 'instagram', 'facebook', 'tiktok', 'youtube').
|
||||||
|
brief: Optional editorial brief with narrative technique and detailed instructions.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Generated post text as a string.
|
Generated post text as a string.
|
||||||
@@ -78,8 +80,17 @@ def generate_post_text(
|
|||||||
if topic_hint:
|
if topic_hint:
|
||||||
topic_instruction = f" The post should be about: {topic_hint}."
|
topic_instruction = f" The post should be about: {topic_hint}."
|
||||||
|
|
||||||
|
# Brief is the highest-priority instruction — it overrides defaults
|
||||||
|
brief_instruction = ""
|
||||||
|
if brief:
|
||||||
|
brief_instruction = (
|
||||||
|
f"\n\nISRUZIONI OBBLIGATORIE DAL BRIEF EDITORIALE:\n{brief}\n"
|
||||||
|
f"Rispetta TUTTE le istruzioni del brief. "
|
||||||
|
f"Il brief ha priorità su qualsiasi altra indicazione."
|
||||||
|
)
|
||||||
|
|
||||||
prompt = (
|
prompt = (
|
||||||
f"{guidance}{topic_instruction}\n\n"
|
f"{guidance}{topic_instruction}{brief_instruction}\n\n"
|
||||||
f"Write the post now. Output ONLY the post text, nothing else."
|
f"Write the post now. Output ONLY the post text, nothing else."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,13 @@ export default function ContentPage() {
|
|||||||
setGenerated(data)
|
setGenerated(data)
|
||||||
setEditText(data.text_content || '')
|
setEditText(data.text_content || '')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err.data?.missing_settings) {
|
||||||
|
setError('__MISSING_SETTINGS__')
|
||||||
|
} else if (err.data?.upgrade_required) {
|
||||||
|
setError(err.data.message || 'Limite piano raggiunto. Passa a Pro.')
|
||||||
|
} else {
|
||||||
setError(err.message || 'Errore nella generazione')
|
setError(err.message || 'Errore nella generazione')
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -140,11 +146,23 @@ export default function ContentPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (error === '__MISSING_SETTINGS__' ? (
|
||||||
|
<div style={{ padding: '1.25rem 1.5rem', backgroundColor: '#FFFBEB', border: '1px solid #FDE68A', borderLeft: '4px solid #F59E0B', marginBottom: '1rem' }}>
|
||||||
|
<div style={{ fontWeight: 600, fontSize: '0.9rem', color: '#92400E', marginBottom: '0.5rem' }}>
|
||||||
|
⚙ Provider AI non configurato
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: '0.85rem', color: '#78350F', margin: '0 0 0.75rem', lineHeight: 1.6 }}>
|
||||||
|
Per generare contenuti devi prima configurare un provider AI (Claude, OpenAI, Gemini...) e inserire la tua API key.
|
||||||
|
</p>
|
||||||
|
<Link to="/settings" style={{ ...btnPrimary, backgroundColor: '#F59E0B', textDecoration: 'none', fontSize: '0.82rem', padding: '0.5rem 1rem' }}>
|
||||||
|
Vai alle Impostazioni →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div style={{ padding: '0.75rem 1rem', backgroundColor: 'var(--error-light)', border: '1px solid #FED7D7', color: 'var(--error)', fontSize: '0.875rem', marginBottom: '1rem' }}>
|
<div style={{ padding: '0.75rem 1rem', backgroundColor: 'var(--error-light)', border: '1px solid #FED7D7', color: 'var(--error)', fontSize: '0.875rem', marginBottom: '1rem' }}>
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.25rem' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.25rem' }}>
|
||||||
{/* Generation form */}
|
{/* Generation form */}
|
||||||
|
|||||||
Reference in New Issue
Block a user