feat(content): implement Content Engine MVP (v1.0) with GTM integration
This commit is contained in:
181
content-engine/content_orchestrator.py
Normal file
181
content-engine/content_orchestrator.py
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
import content_db_manager as db_manager
|
||||
|
||||
# Ensure helper path is correct
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from helpers import call_gemini_flash, scrape_website_details
|
||||
from config import Config
|
||||
|
||||
LOG_DIR = "Log_from_docker"
|
||||
if not os.path.exists(LOG_DIR):
|
||||
os.makedirs(LOG_DIR)
|
||||
|
||||
run_timestamp = datetime.now().strftime("%y-%m-%d_%H-%M-%S")
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
Config.load_api_keys()
|
||||
|
||||
def get_copywriter_instruction(lang='de'):
|
||||
return r"""
|
||||
Du bist ein Senior Copywriter und SEO-Experte. Deine Spezialität ist der 'Challenger Sale'.
|
||||
Du schreibst Texte, die fachlich tief fundiert, professionell und leicht aggressiv/fordernd sind.
|
||||
|
||||
DEIN STIL:
|
||||
- Keine Buzzwords ohne Substanz.
|
||||
- Fokus auf den 'Cost of Inaction' (Was kostet es den Kunden, wenn er NICHT handelt?).
|
||||
- Übersetzung von Technik in geschäftlichen Nutzen.
|
||||
- SEO-Integration: Baue Keywords natürlich aber präsent ein.
|
||||
"""
|
||||
|
||||
# --- MODES ---
|
||||
|
||||
def list_gtm_projects(payload):
|
||||
projects = db_manager.get_all_gtm_projects()
|
||||
return {"projects": projects}
|
||||
|
||||
def import_project(payload):
|
||||
gtm_id = payload.get('gtmProjectId')
|
||||
if not gtm_id:
|
||||
return {"error": "Missing gtmProjectId"}
|
||||
|
||||
result = db_manager.import_gtm_project(gtm_id)
|
||||
if not result:
|
||||
return {"error": "GTM Project not found or import failed"}
|
||||
|
||||
return result
|
||||
|
||||
def list_content_projects(payload):
|
||||
projects = db_manager.get_all_content_projects()
|
||||
return {"projects": projects}
|
||||
|
||||
def load_project_details(payload):
|
||||
project_id = payload.get('projectId')
|
||||
project = db_manager.get_content_project(project_id)
|
||||
if not project:
|
||||
return {"error": "Project not found"}
|
||||
|
||||
assets = db_manager.get_project_assets(project_id)
|
||||
project['assets'] = assets
|
||||
return project
|
||||
|
||||
def seo_brainstorming(payload):
|
||||
project_id = payload.get('projectId')
|
||||
lang = payload.get('lang', 'de')
|
||||
|
||||
project = db_manager.get_content_project(project_id)
|
||||
if not project:
|
||||
return {"error": "Project context not found"}
|
||||
|
||||
gtm_data = project.get('gtm_data_snapshot', {})
|
||||
|
||||
# GOLDEN RULE: Use Raw Quotes and .format()
|
||||
prompt = r"""
|
||||
Basierend auf folgendem GTM-Kontext (Strategie für ein technisches Produkt):
|
||||
{gtm_context}
|
||||
|
||||
AUFGABE:
|
||||
Generiere eine strategische Liste von 15 SEO-Keywords.
|
||||
1. 5 Short-Tail Fokus-Keywords (z.B. Produktkategorie + 'kaufen/mieten').
|
||||
2. 10 Long-Tail Keywords, die spezifische Pain Points oder Usecases adressieren (z.B. 'Kostenreduktion bei Sicherheitsrundgängen').
|
||||
|
||||
Die Keywords müssen für Entscheider relevant sein (CFO, Head of Security, Operations Manager).
|
||||
|
||||
Output NUR als JSON Liste von Strings.
|
||||
""".format(gtm_context=json.dumps(gtm_data))
|
||||
|
||||
response = call_gemini_flash(prompt, system_instruction=get_copywriter_instruction(lang), json_mode=True)
|
||||
keywords = json.loads(response)
|
||||
|
||||
db_manager.save_seo_strategy(project_id, {"seed_keywords": keywords})
|
||||
return {"keywords": keywords}
|
||||
|
||||
def generate_section(payload):
|
||||
project_id = payload.get('projectId')
|
||||
section_key = payload.get('sectionKey') # e.g., 'hero', 'problem', 'features'
|
||||
manual_content = payload.get('manualContent')
|
||||
lang = payload.get('lang', 'de')
|
||||
keywords = payload.get('keywords', [])
|
||||
|
||||
if manual_content:
|
||||
# User is saving their manual edits
|
||||
db_manager.save_content_asset(project_id, 'website_section', section_key, f"Section: {section_key}", manual_content, keywords)
|
||||
return {"status": "saved", "sectionKey": section_key}
|
||||
|
||||
project = db_manager.get_content_project(project_id)
|
||||
if not project:
|
||||
return {"error": "Project context not found"}
|
||||
|
||||
gtm_data = project.get('gtm_data_snapshot', {})
|
||||
|
||||
# Context extraction
|
||||
category = project.get('category')
|
||||
|
||||
prompt = r"""
|
||||
Erstelle den Website-Inhalt für die Sektion '{section}' eines Produkts in der Kategorie '{cat}'.
|
||||
|
||||
STRATEGIE-KONTEXT:
|
||||
{gtm_context}
|
||||
|
||||
SEO-KEYWORDS ZU NUTZEN:
|
||||
{kws}
|
||||
|
||||
ANFORDERUNG:
|
||||
- Schreibe im Stil eines Senior Copywriters (fachlich fundiert, Challenger Sale).
|
||||
- Format: Markdown.
|
||||
- Die Sektion muss den Nutzer zur nächsten Aktion (CTA) führen.
|
||||
""".format(
|
||||
section=section_key,
|
||||
cat=category,
|
||||
gtm_context=json.dumps(gtm_data),
|
||||
kws=json.dumps(keywords)
|
||||
)
|
||||
|
||||
content = call_gemini_flash(prompt, system_instruction=get_copywriter_instruction(lang), json_mode=False)
|
||||
|
||||
# Save as asset
|
||||
db_manager.save_content_asset(project_id, 'website_section', section_key, f"Section: {section_key}", content, keywords)
|
||||
|
||||
return {"content": content, "sectionKey": section_key}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Content Engine Orchestrator")
|
||||
parser.add_argument("--mode", required=True)
|
||||
parser.add_argument("--payload_file", help="Path to JSON payload")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = {}
|
||||
if args.payload_file:
|
||||
with open(args.payload_file, 'r') as f:
|
||||
payload = json.load(f)
|
||||
|
||||
modes = {
|
||||
"list_gtm_projects": list_gtm_projects,
|
||||
"import_project": import_project,
|
||||
"list_content_projects": list_content_projects,
|
||||
"load_project": load_project_details,
|
||||
"seo_brainstorming": seo_brainstorming,
|
||||
"generate_section": generate_section,
|
||||
}
|
||||
|
||||
if args.mode in modes:
|
||||
try:
|
||||
result = modes[args.mode](payload)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
except Exception as e:
|
||||
logging.error(f"Error in mode {args.mode}: {str(e)}")
|
||||
print(json.dumps({"error": str(e)}))
|
||||
else:
|
||||
print(json.dumps({"error": f"Unknown mode: {args.mode}"}))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user