diff --git a/src/agents/writer.py b/src/agents/writer.py index 742bc76..c52d0bd 100644 --- a/src/agents/writer.py +++ b/src/agents/writer.py @@ -905,21 +905,21 @@ Strategy Weight: {strategy_weight:.1f} / 1.0 improvements_text += f"- {imp}\n" # Revision mode with structured feedback - return f"""ÜBERARBEITE den Post basierend auf dem Kritiker-Feedback. + score_text = f"**AKTUELLER SCORE:** {critic_result.get('overall_score', 'N/A')}/100\n\n" if critic_result else "" + + return f"""ÜBERARBEITE den Post basierend auf dem Feedback. **VORHERIGE VERSION:** {previous_version} -**AKTUELLER SCORE:** {critic_result.get('overall_score', 'N/A')}/100 - -**FEEDBACK:** +{score_text}**FEEDBACK:** {feedback} {specific_changes_text} {improvements_text} **DEINE AUFGABE:** -1. Führe die konkreten Änderungen EXAKT durch -2. Behalte alles bei was GUT bewertet wurde -3. Der überarbeitete Post soll mindestens 85 Punkte erreichen +1. Führe die Änderungen durch wie im Feedback beschrieben +2. Behalte alles bei was gut funktioniert +3. Der überarbeitete Post soll die Anforderungen erfüllen Gib NUR den überarbeiteten Post zurück - keine Kommentare.""" diff --git a/src/web/templates/user/base.html b/src/web/templates/user/base.html index 12b78b6..f6b58da 100644 --- a/src/web/templates/user/base.html +++ b/src/web/templates/user/base.html @@ -130,6 +130,13 @@ Post erstellen + +
+ + Chat Assistent +
+ NEU +
Meine Posts diff --git a/src/web/templates/user/chat_create.html b/src/web/templates/user/chat_create.html new file mode 100644 index 0000000..8fd5f88 --- /dev/null +++ b/src/web/templates/user/chat_create.html @@ -0,0 +1,414 @@ +{% extends "base.html" %} + +{% block title %}Chat Assistent{% endblock %} + +{% block content %} + + + +
+
+

💬 Chat Assistent

+
+
+ + +
+ +
+
+ 🤖 +
+
+
+

+ Hallo! Ich helfe dir beim Erstellen deines LinkedIn-Posts. + Beschreibe mir einfach, worüber du schreiben möchtest, und ich erstelle einen ersten Entwurf für dich. +

+

+ Du kannst mich danach bitten, den Post anzupassen, umzuschreiben oder zu verbessern. +

+
+
+
+
+ + +
+
+
+ +
+ {% for pt in post_types %} + + {% endfor %} +
+ + +
+
+ +
+ + +
+
+
+
+ + + +{% endblock %} diff --git a/src/web/templates/user/company_manage_posts.html b/src/web/templates/user/company_manage_posts.html index 4c1d65c..397c3c0 100644 --- a/src/web/templates/user/company_manage_posts.html +++ b/src/web/templates/user/company_manage_posts.html @@ -10,8 +10,8 @@ onclick="window.location.href='/company/manage/post/{{ post.id }}?employee_id={{ employee_id }}'">

{{ post.topic_title or 'Untitled' }}

- {% if post.critic_feedback and post.critic_feedback | length > 0 %} - {% set score = post.critic_feedback[-1].overall_score %} + {% if post.critic_feedback and post.critic_feedback | length > 0 and post.critic_feedback[-1].get('overall_score') is not none %} + {% set score = post.critic_feedback[-1].get('overall_score', 0) %} {{ score }} diff --git a/src/web/templates/user/post_detail.html b/src/web/templates/user/post_detail.html index 79c1d14..e0a5a59 100644 --- a/src/web/templates/user/post_detail.html +++ b/src/web/templates/user/post_detail.html @@ -510,9 +510,9 @@ {{ post.status | capitalize }} - {% if final_feedback %} - - Score: {{ final_feedback.overall_score }}/100 + {% if final_feedback and final_feedback.get('overall_score') is not none %} + + Score: {{ final_feedback.get('overall_score', 0) }}/100 {% endif %}
@@ -833,11 +833,11 @@
- {% if final_feedback and final_feedback.scores %} + {% if final_feedback and final_feedback.get('scores') and final_feedback.get('overall_score') is not none %}

- Score: {{ final_feedback.overall_score }}/100 + Score: {{ final_feedback.get('overall_score', 0) }}/100

@@ -943,10 +943,12 @@ Version {{ i + 1 }} {% if post.critic_feedback and i < post.critic_feedback | length %}
- - Score: {{ post.critic_feedback[i].overall_score }}/100 + {% if post.critic_feedback[i].get('overall_score') is not none %} + + Score: {{ post.critic_feedback[i].get('overall_score', 0) }}/100 - {% if post.critic_feedback[i].approved %} + {% endif %} + {% if post.critic_feedback[i].get('approved') %} Approved {% endif %}
diff --git a/src/web/templates/user/posts.html b/src/web/templates/user/posts.html index 6690a6b..c4139b1 100644 --- a/src/web/templates/user/posts.html +++ b/src/web/templates/user/posts.html @@ -10,8 +10,8 @@ onclick="window.location.href='/posts/{{ post.id }}'">

{{ post.topic_title or 'Untitled' }}

- {% if post.critic_feedback and post.critic_feedback | length > 0 %} - {% set score = post.critic_feedback[-1].overall_score %} + {% if post.critic_feedback and post.critic_feedback | length > 0 and post.critic_feedback[-1].get('overall_score') is not none %} + {% set score = post.critic_feedback[-1].get('overall_score', 0) %} {{ score }} diff --git a/src/web/user/routes.py b/src/web/user/routes.py index 6271f8a..9058ecd 100644 --- a/src/web/user/routes.py +++ b/src/web/user/routes.py @@ -1354,7 +1354,9 @@ async def posts_page(request: Request): "profile_picture": profile_picture }) except Exception as e: + import traceback logger.error(f"Error loading posts: {e}") + logger.error(f"Traceback: {traceback.format_exc()}") return templates.TemplateResponse("posts.html", { "request": request, "page": "posts", @@ -1628,7 +1630,9 @@ async def post_detail_page(request: Request, post_id: str): "media_items_dict": media_items_dict }) except Exception as e: + import traceback logger.error(f"Error loading post detail: {e}") + logger.error(f"Traceback: {traceback.format_exc()}") return RedirectResponse(url="/posts", status_code=302) @@ -1690,6 +1694,35 @@ async def create_post_page(request: Request): }) +@user_router.get("/chat-create", response_class=HTMLResponse) +async def chat_create_page(request: Request): + """Chat-based post creation page.""" + session = require_user_session(request) + if not session: + return RedirectResponse(url="/login", status_code=302) + + user_id = UUID(session.user_id) + + # Get post types + post_types = await db.get_post_types(user_id) + if not post_types: + return templates.TemplateResponse("error.html", { + "request": request, + "session": session, + "error": "Keine Post-Typen gefunden. Bitte erstelle zuerst Post-Typen." + }) + + profile_picture = await get_user_avatar(session, user_id) + + return templates.TemplateResponse("chat_create.html", { + "request": request, + "page": "chat-create", + "session": session, + "post_types": post_types, + "profile_picture": profile_picture + }) + + @user_router.get("/status", response_class=HTMLResponse) async def status_page(request: Request): """User's status page.""" @@ -3252,6 +3285,271 @@ async def save_all_and_reanalyze(request: Request, background_tasks: BackgroundT return JSONResponse({"error": str(e)}, status_code=500) +@user_router.post("/api/employee/chat/generate") +async def chat_generate_post(request: Request): + """Generate initial post from chat message.""" + session = require_user_session(request) + if not session: + raise HTTPException(status_code=401, detail="Not authenticated") + + try: + data = await request.json() + message = data.get("message", "").strip() + post_type_id = data.get("post_type_id") + + if not message: + return JSONResponse({"success": False, "error": "Nachricht erforderlich"}) + + if not post_type_id: + return JSONResponse({"success": False, "error": "Post-Typ erforderlich"}) + + user_id = UUID(session.user_id) + + # Get post type info + post_type = await db.get_post_type(UUID(post_type_id)) + if not post_type: + return JSONResponse({"success": False, "error": "Post-Typ nicht gefunden"}) + + # Get profile analysis + profile_analysis = await db.get_profile_analysis(user_id) + if not profile_analysis: + return JSONResponse({"success": False, "error": "Profil-Analyse nicht gefunden"}) + + # Get company strategy if available + company_strategy = None + profile = await db.get_profile(user_id) + if profile and profile.company_id: + company = await db.get_company(profile.company_id) + if company and company.company_strategy: + company_strategy = company.company_strategy + + # Get example posts for style reference + linkedin_posts = await db.get_posts_by_type(user_id, UUID(post_type_id)) + if len(linkedin_posts) < 3: + linkedin_posts = await db.get_linkedin_posts(user_id) + + example_post_texts = [ + post.post_text for post in linkedin_posts + if post.post_text and len(post.post_text) > 100 + ][:10] + + # Generate post using writer agent with user's content as primary focus + from src.agents.writer import WriterAgent + writer = WriterAgent() + + # Create a topic structure from user's message + topic = { + "title": message[:100], + "fact": message, + "relevance": "User-specified content" + } + + # Generate post + post_content = await writer.process( + topic=topic, + profile_analysis=profile_analysis.full_analysis, + example_posts=example_post_texts, + post_type=post_type, + user_thoughts=message, # CRITICAL: User's input as primary content + company_strategy=company_strategy, + strategy_weight=post_type.strategy_weight + ) + + # Generate conversation ID + import uuid + conversation_id = str(uuid.uuid4()) + + return JSONResponse({ + "success": True, + "post": post_content, + "conversation_id": conversation_id, + "explanation": "Hier ist dein erster Entwurf basierend auf deiner Beschreibung:" + }) + + except Exception as e: + logger.error(f"Error generating chat post: {e}") + return JSONResponse({"success": False, "error": str(e)}, status_code=500) + + +@user_router.post("/api/employee/chat/refine") +async def chat_refine_post(request: Request): + """Refine existing post based on user feedback.""" + session = require_user_session(request) + if not session: + raise HTTPException(status_code=401, detail="Not authenticated") + + try: + data = await request.json() + message = data.get("message", "").strip() + current_post = data.get("current_post", "") + post_type_id = data.get("post_type_id") + chat_history = data.get("chat_history", []) + + if not message: + return JSONResponse({"success": False, "error": "Nachricht erforderlich"}) + + if not current_post: + return JSONResponse({"success": False, "error": "Kein Post zum Verfeinern vorhanden"}) + + if not post_type_id: + return JSONResponse({"success": False, "error": "Post-Typ erforderlich"}) + + user_id = UUID(session.user_id) + + # Get post type info + post_type = await db.get_post_type(UUID(post_type_id)) + if not post_type: + return JSONResponse({"success": False, "error": "Post-Typ nicht gefunden"}) + + # Get profile analysis + profile_analysis = await db.get_profile_analysis(user_id) + if not profile_analysis: + return JSONResponse({"success": False, "error": "Profil-Analyse nicht gefunden"}) + + # Ensure full_analysis is a dict + full_analysis = profile_analysis.full_analysis if profile_analysis.full_analysis else {} + if not isinstance(full_analysis, dict): + logger.warning(f"full_analysis is not a dict: {type(full_analysis)}") + full_analysis = {} + + # Get company strategy if available + company_strategy = None + profile = await db.get_profile(user_id) + if profile and profile.company_id: + company = await db.get_company(profile.company_id) + if company and company.company_strategy: + company_strategy = company.company_strategy + # Ensure it's a dict + if not isinstance(company_strategy, dict): + logger.warning(f"company_strategy is not a dict: {type(company_strategy)}") + company_strategy = None + + # Get example posts + linkedin_posts = await db.get_posts_by_type(user_id, UUID(post_type_id)) + if len(linkedin_posts) < 3: + linkedin_posts = await db.get_linkedin_posts(user_id) + + example_post_texts = [ + post.post_text for post in linkedin_posts + if post.post_text and len(post.post_text) > 100 + ][:10] + + # Refine post using writer with feedback + from src.agents.writer import WriterAgent + writer = WriterAgent() + + topic = { + "title": "Chat refinement", + "fact": message, + "relevance": "User refinement request" + } + + # Use writer's revision capability + refined_post = await writer.process( + topic=topic, + profile_analysis=full_analysis, + example_posts=example_post_texts, + feedback=message, # User's refinement instruction + previous_version=current_post, + post_type=post_type, + user_thoughts=message, + company_strategy=company_strategy, + strategy_weight=getattr(post_type, 'strategy_weight', 0.5) + ) + + return JSONResponse({ + "success": True, + "post": refined_post, + "conversation_id": data.get("conversation_id"), + "explanation": "Ich habe den Post angepasst:" + }) + + except Exception as e: + import traceback + logger.error(f"Error refining chat post: {e}") + logger.error(f"Traceback: {traceback.format_exc()}") + return JSONResponse({"success": False, "error": str(e)}, status_code=500) + + +@user_router.post("/api/employee/chat/save") +async def chat_save_post(request: Request): + """Save chat-generated post to database.""" + session = require_user_session(request) + if not session: + raise HTTPException(status_code=401, detail="Not authenticated") + + try: + data = await request.json() + post_content = data.get("post_content", "").strip() + post_type_id = data.get("post_type_id") + chat_history = data.get("chat_history", []) + + if not post_content: + return JSONResponse({"success": False, "error": "Post-Inhalt erforderlich"}) + + if not post_type_id: + return JSONResponse({"success": False, "error": "Post-Typ erforderlich"}) + + user_id = UUID(session.user_id) + + # Extract title from first sentence of post + first_sentence = post_content.split('\n')[0].strip() + if len(first_sentence) > 100: + title = first_sentence[:97] + "..." + else: + title = first_sentence if first_sentence else "Chat-generierter Post" + + # Create GeneratedPost with status draft + from src.database.models import GeneratedPost + import uuid as uuid_lib + + post_id = uuid_lib.uuid4() + + # Extract all AI-generated versions from chat history + writer_versions = [] + critic_feedback_list = [] + + for item in chat_history: + if 'ai' in item and item['ai']: + writer_versions.append(item['ai']) + # Store user feedback as "critic feedback" + if 'user' in item and item['user']: + critic_feedback_list.append({ + 'feedback': item['user'], + 'explanation': item.get('explanation', '') + }) + + # Add final version + writer_versions.append(post_content) + + num_iterations = len(writer_versions) + + generated_post = GeneratedPost( + id=post_id, + user_id=user_id, + post_content=post_content, + post_type_id=UUID(post_type_id), + status="draft", + iterations=num_iterations, + writer_versions=writer_versions, # All iterations saved here + critic_feedback=critic_feedback_list, # User feedback saved here + topic_title=title, + created_at=datetime.now(timezone.utc) + ) + + saved_post = await db.save_generated_post(generated_post) + + return JSONResponse({ + "success": True, + "post_id": str(saved_post.id), + "message": "Post erfolgreich gespeichert" + }) + + except Exception as e: + logger.error(f"Error saving chat post: {e}") + return JSONResponse({"success": False, "error": str(e)}, status_code=500) + + @user_router.post("/api/company/invite") async def send_company_invitation(request: Request): """Send invitation to a new employee."""