334 lines
12 KiB
Python
334 lines
12 KiB
Python
"""
|
||
System utilities - default prompts and helper functions
|
||
"""
|
||
from typing import Optional
|
||
|
||
|
||
def get_default_prompt(prompt_type: str) -> str:
|
||
"""Get default prompt value by type"""
|
||
defaults = {
|
||
'clustering': """You are a semantic strategist and SEO architecture engine. Your task is to analyze the provided keyword list and group them into meaningful, intent-driven topic clusters that reflect how real users search, think, and act online.
|
||
|
||
Return a single JSON object with a "clusters" array. Each cluster must follow this structure:
|
||
|
||
{
|
||
"name": "[Descriptive cluster name — natural, SEO-relevant, clearly expressing the topic]",
|
||
"description": "[1–2 concise sentences explaining what this cluster covers and why these keywords belong together]",
|
||
"keywords": ["keyword 1", "keyword 2", "keyword 3", "..."]
|
||
}
|
||
|
||
CLUSTERING STRATEGY:
|
||
|
||
1. Keyword-first, structure-follows:
|
||
- Do NOT rely on assumed categories or existing content structures.
|
||
- Begin purely from the meaning, intent, and behavioral connection between keywords.
|
||
|
||
2. Use multi-dimensional grouping logic:
|
||
- Group keywords by these behavioral dimensions:
|
||
• Search Intent → informational, commercial, transactional, navigational
|
||
• Use-Case or Problem → what the user is trying to achieve or solve
|
||
• Function or Feature → how something works or what it does
|
||
• Persona or Audience → who the content or product serves
|
||
• Context → location, time, season, platform, or device
|
||
- Combine 2–3 dimensions naturally where they make sense.
|
||
|
||
3. Model real search behavior:
|
||
- Favor clusters that form natural user journeys such as:
|
||
• Problem ➝ Solution
|
||
• General ➝ Specific
|
||
• Product ➝ Use-case
|
||
• Buyer ➝ Benefit
|
||
• Tool ➝ Function
|
||
• Task ➝ Method
|
||
- Each cluster should feel like a real topic hub users would explore in depth.
|
||
|
||
4. Avoid superficial groupings:
|
||
- Do not cluster keywords just because they share words.
|
||
- Do not force-fit outliers or unrelated keywords.
|
||
- Exclude keywords that don't logically connect to any cluster.
|
||
|
||
5. Quality rules:
|
||
- Each cluster should include between 3–10 strongly related keywords.
|
||
- Never duplicate a keyword across multiple clusters.
|
||
- Prioritize semantic strength, search intent, and usefulness for SEO-driven content structure.
|
||
- It's better to output fewer, high-quality clusters than many weak or shallow ones.
|
||
|
||
INPUT FORMAT:
|
||
{
|
||
"keywords": [IGNY8_KEYWORDS]
|
||
}
|
||
|
||
OUTPUT FORMAT:
|
||
Return ONLY the final JSON object in this format:
|
||
{
|
||
"clusters": [
|
||
{
|
||
"name": "...",
|
||
"description": "...",
|
||
"keywords": ["...", "...", "..."]
|
||
}
|
||
]
|
||
}
|
||
|
||
Do not include any explanations, text, or commentary outside the JSON output.
|
||
""",
|
||
|
||
'ideas': """Generate SEO-optimized, high-quality content ideas and outlines for each keyword cluster.
|
||
Input:
|
||
Clusters: [IGNY8_CLUSTERS]
|
||
Keywords: [IGNY8_CLUSTER_KEYWORDS]
|
||
|
||
Output: JSON with "ideas" array.
|
||
Each cluster → 1 cluster_hub + 2–4 supporting ideas.
|
||
Each idea must include:
|
||
title, description, content_type, content_structure, cluster_id, estimated_word_count (1500–2200), and covered_keywords.
|
||
|
||
Outline Rules:
|
||
|
||
Intro: 1 hook (30–40 words) + 2 intro paragraphs (50–60 words each).
|
||
|
||
5–8 H2 sections, each with 2–3 H3s.
|
||
|
||
Each H2 ≈ 250–300 words, mixed content (paragraphs, lists, tables, blockquotes).
|
||
|
||
Vary section format and tone; no bullets or lists at start.
|
||
|
||
Tables have columns; blockquotes = expert POV or data insight.
|
||
|
||
Use depth, examples, and real context.
|
||
|
||
Avoid repetitive structure.
|
||
|
||
Tone: Professional editorial flow. No generic phrasing. Use varied sentence openings and realistic examples.
|
||
|
||
Output JSON Example:
|
||
|
||
{
|
||
"ideas": [
|
||
{
|
||
"title": "Best Organic Cotton Duvet Covers for All Seasons",
|
||
"description": {
|
||
"introduction": {
|
||
"hook": "Transform your sleep with organic cotton that blends comfort and sustainability.",
|
||
"paragraphs": [
|
||
{"content_type": "paragraph", "details": "Overview of organic cotton's rise in bedding industry."},
|
||
{"content_type": "paragraph", "details": "Why consumers prefer organic bedding over synthetic alternatives."}
|
||
]
|
||
},
|
||
"H2": [
|
||
{
|
||
"heading": "Why Choose Organic Cotton for Bedding?",
|
||
"subsections": [
|
||
{"subheading": "Health and Skin Benefits", "content_type": "paragraph", "details": "Discuss hypoallergenic and chemical-free aspects."},
|
||
{"subheading": "Environmental Sustainability", "content_type": "list", "details": "Eco benefits like low water use, no pesticides."},
|
||
{"subheading": "Long-Term Cost Savings", "content_type": "table", "details": "Compare durability and pricing over time."}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
"content_type": "post",
|
||
"content_structure": "review",
|
||
"cluster_id": 12,
|
||
"estimated_word_count": 1800,
|
||
"covered_keywords": "organic duvet covers, eco-friendly bedding, sustainable sheets"
|
||
}
|
||
]
|
||
}""",
|
||
|
||
'content_generation': """You are an editorial content strategist. Your task is to generate a complete JSON response object that includes all the fields listed below, based on the provided content idea, keyword cluster, and keyword list.
|
||
|
||
Only the `content` field should contain HTML inside JSON object.
|
||
|
||
==================
|
||
Generate a complete JSON response object matching this structure:
|
||
==================
|
||
|
||
{
|
||
"title": "[Blog title using the primary keyword — full sentence case]",
|
||
"meta_title": "[Meta title under 60 characters — natural, optimized, and compelling]",
|
||
"meta_description": "[Meta description under 160 characters — clear and enticing summary]",
|
||
"content": "[HTML content — full editorial structure with <p>, <h2>, <h3>, <ul>, <ol>, <table>]",
|
||
"word_count": [Exact integer — word count of HTML body only],
|
||
"primary_keyword": "[Single primary keyword used in title and first paragraph]",
|
||
"secondary_keywords": [
|
||
"[Keyword 1]",
|
||
"[Keyword 2]",
|
||
"[Keyword 3]"
|
||
],
|
||
"tags": [
|
||
"[2–4 word lowercase tag 1]",
|
||
"[2–4 word lowercase tag 2]",
|
||
"[2–4 word lowercase tag 3]",
|
||
"[2–4 word lowercase tag 4]",
|
||
"[2–4 word lowercase tag 5]"
|
||
],
|
||
"categories": [
|
||
"[Parent Category > Child Category]",
|
||
"[Optional Second Category > Optional Subcategory]"
|
||
]
|
||
}
|
||
|
||
===========================
|
||
CONTENT FLOW RULES
|
||
===========================
|
||
|
||
**INTRODUCTION:**
|
||
- Start with 1 italicized hook (30–40 words)
|
||
- Follow with 2 narrative paragraphs (each 50–60 words; 2–3 sentences max)
|
||
- No headings allowed in intro
|
||
|
||
**H2 SECTIONS (5–8 total):**
|
||
Each section should be 250–300 words and follow this format:
|
||
1. Two narrative paragraphs (80–120 words each, 2–3 sentences)
|
||
2. One list or table (must come *after* a paragraph)
|
||
3. Optional closing paragraph (40–60 words)
|
||
4. Insert 2–3 subsections naturally after main paragraphs
|
||
|
||
**Formatting Rules:**
|
||
- Vary use of unordered lists, ordered lists, and tables across sections
|
||
- Never begin any section or sub-section with a list or table
|
||
|
||
===========================
|
||
KEYWORD & SEO RULES
|
||
===========================
|
||
|
||
- **Primary keyword** must appear in:
|
||
- The title
|
||
- First paragraph of the introduction
|
||
- At least 2 H2 headings
|
||
|
||
- **Secondary keywords** must be used naturally, not forced
|
||
|
||
- **Tone & style guidelines:**
|
||
- No robotic or passive voice
|
||
- Avoid generic intros like "In today's world…"
|
||
- Don't repeat heading in opening sentence
|
||
- Vary sentence structure and length
|
||
|
||
|
||
|
||
===========================
|
||
INPUT VARIABLES
|
||
===========================
|
||
|
||
CONTENT IDEA DETAILS:
|
||
[IGNY8_IDEA]
|
||
|
||
KEYWORD CLUSTER:
|
||
[IGNY8_CLUSTER]
|
||
|
||
ASSOCIATED KEYWORDS:
|
||
[IGNY8_KEYWORDS]
|
||
|
||
===========================
|
||
OUTPUT FORMAT
|
||
===========================
|
||
|
||
Return ONLY the final JSON object.
|
||
Do NOT include any comments, formatting, or explanations.""",
|
||
|
||
'image_prompt_extraction': """Extract image prompts from the following article content.
|
||
|
||
ARTICLE TITLE: {title}
|
||
|
||
ARTICLE CONTENT:
|
||
{content}
|
||
|
||
Extract image prompts for:
|
||
1. Featured Image: One main image that represents the article topic
|
||
2. In-Article Images: Up to {max_images} images that would be useful within the article content
|
||
|
||
Return a JSON object with this structure:
|
||
{{
|
||
"featured_prompt": "Detailed description of the featured image",
|
||
"in_article_prompts": [
|
||
"Description of first in-article image",
|
||
"Description of second in-article image",
|
||
...
|
||
]
|
||
}}
|
||
|
||
Make sure each prompt is detailed enough for image generation, describing the visual elements, style, mood, and composition.""",
|
||
|
||
'image_prompt_template': '{image_type} image for blog post titled "{post_title}": {image_prompt}',
|
||
|
||
'negative_prompt': 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title',
|
||
|
||
'site_structure_generation': """You are the lead IA and conversion-focused strategist for a new marketing website. Use the inputs from the site builder wizard to craft an SEO-rich, user-journey friendly site architecture that a design and content team can build immediately.
|
||
|
||
INPUT DATA
|
||
----------
|
||
BUSINESS BRIEF:
|
||
[IGNY8_BUSINESS_BRIEF]
|
||
|
||
PRIMARY OBJECTIVES (rank by impact):
|
||
[IGNY8_OBJECTIVES]
|
||
|
||
DESIGN & STORY STYLE NOTES:
|
||
[IGNY8_STYLE]
|
||
|
||
SITE INFO (platform, audience, product/service category, any technical requirements):
|
||
[IGNY8_SITE_INFO]
|
||
|
||
TASK
|
||
----
|
||
1. Interpret the brief to define the core narrative arc (problem → solution → proof → conversion).
|
||
2. Output a JSON object that matches the SiteStructure schema:
|
||
{
|
||
"site": {
|
||
"name": "[Site or campaign name]",
|
||
"primary_navigation": ["..."],
|
||
"secondary_navigation": ["..."],
|
||
"hero_message": "[Top-level positioning statement]",
|
||
"tone": "[Voice guidance derived from style input]"
|
||
},
|
||
"pages": [
|
||
{
|
||
"slug": "kebab-case-slug",
|
||
"title": "SEO-friendly page title",
|
||
"type": "[page role e.g. hero, solution, proof, pricing, conversion]",
|
||
"status": "draft",
|
||
"objective": "Single measurable goal for this page aligned to objectives",
|
||
"primary_cta": "Primary call to action text",
|
||
"blocks": [
|
||
{
|
||
"type": "[block archetype e.g. hero, feature_grid, testimonial]",
|
||
"heading": "Section headline optimized for intent",
|
||
"subheading": "Support copy that clarifies value or context",
|
||
"layout": "Suggested layout pattern (full-bleed, two-column, cards, stats, etc.)",
|
||
"content": [
|
||
"Key talking points, proof, FAQs, offers, or data points"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
|
||
3. Produce 6–10 total pages covering the full funnel (awareness, consideration, evaluation, conversion, post-conversion/support) unless the objectives explicitly demand fewer.
|
||
4. Every page must include at least three blocks ordered to tell a story, with clear internal logic (hook → build trust → guide action).
|
||
5. Craft navigation labels that mirror user language, avoid jargon, and reinforce topical authority.
|
||
6. Emphasize SEO signals: use keyword-rich yet natural titles, include topical coverage (solutions, use cases, proof, resources), and highlight schema-worthy elements (stats, FAQs, testimonials).
|
||
|
||
RESPONSE RULES
|
||
--------------
|
||
- Return ONLY the JSON object described above. Do not wrap it in markdown.
|
||
- Keep text human and specific; never say "Lorem ipsum" or "Example".
|
||
- When objectives mention specific offers, personas, or industries, reflect them in page titles, CTAs, and block content.
|
||
- If data is missing, infer the most logical assumption and note it inline with phrasing like "(assumed: ...)".
|
||
""",
|
||
}
|
||
|
||
return defaults.get(prompt_type, '')
|
||
|
||
|
||
def get_prompt_value(account, prompt_type: str) -> str:
|
||
"""Get prompt value for an account, or default if not set"""
|
||
try:
|
||
from .models import AIPrompt
|
||
prompt = AIPrompt.objects.get(account=account, prompt_type=prompt_type, is_active=True)
|
||
return prompt.prompt_value
|
||
except AIPrompt.DoesNotExist:
|
||
return get_default_prompt(prompt_type)
|
||
|