Chat assistant
This commit is contained in:
@@ -905,21 +905,21 @@ Strategy Weight: {strategy_weight:.1f} / 1.0
|
|||||||
improvements_text += f"- {imp}\n"
|
improvements_text += f"- {imp}\n"
|
||||||
|
|
||||||
# Revision mode with structured feedback
|
# 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:**
|
**VORHERIGE VERSION:**
|
||||||
{previous_version}
|
{previous_version}
|
||||||
|
|
||||||
**AKTUELLER SCORE:** {critic_result.get('overall_score', 'N/A')}/100
|
{score_text}**FEEDBACK:**
|
||||||
|
|
||||||
**FEEDBACK:**
|
|
||||||
{feedback}
|
{feedback}
|
||||||
{specific_changes_text}
|
{specific_changes_text}
|
||||||
{improvements_text}
|
{improvements_text}
|
||||||
**DEINE AUFGABE:**
|
**DEINE AUFGABE:**
|
||||||
1. Führe die konkreten Änderungen EXAKT durch
|
1. Führe die Änderungen durch wie im Feedback beschrieben
|
||||||
2. Behalte alles bei was GUT bewertet wurde
|
2. Behalte alles bei was gut funktioniert
|
||||||
3. Der überarbeitete Post soll mindestens 85 Punkte erreichen
|
3. Der überarbeitete Post soll die Anforderungen erfüllen
|
||||||
|
|
||||||
Gib NUR den überarbeiteten Post zurück - keine Kommentare."""
|
Gib NUR den überarbeiteten Post zurück - keine Kommentare."""
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,13 @@
|
|||||||
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
<span class="sidebar-text">Post erstellen</span>
|
<span class="sidebar-text">Post erstellen</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/chat-create" class="nav-link flex items-center justify-between px-4 py-3 rounded-lg text-gray-300 hover:bg-brand-bg-light transition-colors {% if page == 'chat-create' %}active{% endif %}">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
||||||
|
<span class="sidebar-text">Chat Assistent</span>
|
||||||
|
</div>
|
||||||
|
<span class="sidebar-text px-1.5 py-0.5 bg-brand-highlight text-brand-bg-dark rounded text-xs font-bold">NEU</span>
|
||||||
|
</a>
|
||||||
<a href="/posts" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-gray-300 hover:bg-brand-bg-light transition-colors {% if page == 'posts' %}active{% endif %}">
|
<a href="/posts" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-gray-300 hover:bg-brand-bg-light transition-colors {% if page == 'posts' %}active{% endif %}">
|
||||||
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
||||||
<span class="sidebar-text">Meine Posts</span>
|
<span class="sidebar-text">Meine Posts</span>
|
||||||
|
|||||||
414
src/web/templates/user/chat_create.html
Normal file
414
src/web/templates/user/chat_create.html
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Chat Assistent{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
/* Make chat page full height and remove padding */
|
||||||
|
#mainContent > div {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fixed elements adjust to sidebar with smooth transition */
|
||||||
|
.chat-fixed-header,
|
||||||
|
.chat-fixed-input {
|
||||||
|
left: 256px;
|
||||||
|
right: 0;
|
||||||
|
transition: left 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When sidebar is collapsed, adjust fixed elements */
|
||||||
|
aside.collapsed ~ main .chat-fixed-header,
|
||||||
|
aside.collapsed ~ main .chat-fixed-input {
|
||||||
|
left: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouncing dots animation */
|
||||||
|
.dot {
|
||||||
|
animation: bounce 1.4s infinite ease-in-out;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.dot:nth-child(1) { animation-delay: -0.32s; }
|
||||||
|
.dot:nth-child(2) { animation-delay: -0.16s; }
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 80%, 100% { transform: translateY(0); }
|
||||||
|
40% { transform: translateY(-8px); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Header (Fixed at top) -->
|
||||||
|
<div class="chat-fixed-header fixed top-0 bg-brand-bg z-20">
|
||||||
|
<div class="px-8 py-4">
|
||||||
|
<h1 class="text-xl font-bold text-white">💬 Chat Assistent</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Messages Area (Full page scroll with padding for fixed header and input) -->
|
||||||
|
<div id="chat-messages" class="px-8 space-y-4 pb-64 max-w-[80%] mx-auto" style="padding-top: 80px;">
|
||||||
|
<!-- Welcome Message -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-brand-highlight/20 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-lg">🤖</span>
|
||||||
|
</div>
|
||||||
|
<div class="max-w-[70%]">
|
||||||
|
<div class="bg-brand-bg-dark rounded-2xl p-4 border border-gray-600">
|
||||||
|
<p class="text-gray-300">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 text-sm mt-2">
|
||||||
|
Du kannst mich danach bitten, den Post anzupassen, umzuschreiben oder zu verbessern.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Input Area (Fixed at bottom, floating over messages) -->
|
||||||
|
<div class="chat-fixed-input fixed bottom-0 bg-brand-bg z-10">
|
||||||
|
<div class="pt-4 pb-6 px-8">
|
||||||
|
<div class="space-y-3 mx-auto" style="max-width: 768px;">
|
||||||
|
<!-- Post Type Selector (Chips) - Above Input -->
|
||||||
|
<div class="flex flex-wrap gap-1.5" id="post-type-chips">
|
||||||
|
{% for pt in post_types %}
|
||||||
|
<button
|
||||||
|
onclick="selectPostType('{{ pt.id }}', this)"
|
||||||
|
data-post-type-id="{{ pt.id }}"
|
||||||
|
class="post-type-chip px-3 py-1 rounded-full text-xs font-medium transition-all
|
||||||
|
{% if loop.first %}bg-brand-highlight text-black{% else %}bg-brand-bg-dark text-gray-400 hover:bg-brand-bg-light hover:text-gray-300{% endif %}"
|
||||||
|
{% if loop.first %}data-selected="true"{% endif %}>
|
||||||
|
{{ pt.name }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Input Bar -->
|
||||||
|
<div class="flex gap-3 items-center">
|
||||||
|
<div class="flex-1 relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="chat-input"
|
||||||
|
placeholder="Beschreibe deinen Post..."
|
||||||
|
class="w-full input-bg border border-gray-600 rounded-full px-6 py-3 text-white focus:outline-none focus:border-brand-highlight transition-colors"
|
||||||
|
onkeydown="handleChatKeydown(event)">
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onclick="sendMessage()"
|
||||||
|
id="send-btn"
|
||||||
|
class="w-12 h-12 bg-brand-highlight hover:bg-yellow-500 text-black rounded-full transition-all flex items-center justify-center flex-shrink-0 hover:scale-110"
|
||||||
|
title="Senden (Enter)">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick="savePost()"
|
||||||
|
id="save-btn"
|
||||||
|
class="hidden w-12 h-12 bg-green-600 hover:bg-green-500 text-white rounded-full transition-all items-center justify-center flex-shrink-0 hover:scale-110"
|
||||||
|
title="Post speichern">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let chatHistory = [];
|
||||||
|
let currentPost = null;
|
||||||
|
let conversationId = null;
|
||||||
|
let selectedPostTypeId = null;
|
||||||
|
|
||||||
|
// User profile data
|
||||||
|
const userProfilePicture = "{{ profile_picture or '' }}";
|
||||||
|
const userInitial = "{{ (session.display_name or session.linkedin_name or 'U')[0] | upper }}";
|
||||||
|
|
||||||
|
// Initialize: Select first post type
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const firstChip = document.querySelector('.post-type-chip[data-selected="true"]');
|
||||||
|
if (firstChip) {
|
||||||
|
selectedPostTypeId = firstChip.dataset.postTypeId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectPostType(postTypeId, element) {
|
||||||
|
// Update selected state
|
||||||
|
selectedPostTypeId = postTypeId;
|
||||||
|
|
||||||
|
// Update chip styles
|
||||||
|
document.querySelectorAll('.post-type-chip').forEach(chip => {
|
||||||
|
chip.classList.remove('bg-brand-highlight', 'text-black');
|
||||||
|
chip.classList.add('bg-brand-bg-dark', 'text-gray-400');
|
||||||
|
chip.removeAttribute('data-selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
element.classList.remove('bg-brand-bg-dark', 'text-gray-400');
|
||||||
|
element.classList.add('bg-brand-highlight', 'text-black');
|
||||||
|
element.setAttribute('data-selected', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChatKeydown(event) {
|
||||||
|
// Send on Enter (not Shift+Enter)
|
||||||
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage() {
|
||||||
|
const input = document.getElementById('chat-input');
|
||||||
|
const message = input.value.trim();
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
showToast('Bitte gib eine Nachricht ein', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedPostTypeId) {
|
||||||
|
showToast('Bitte wähle einen Post-Typ aus', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user message to chat
|
||||||
|
addMessageToChat('user', message);
|
||||||
|
input.value = '';
|
||||||
|
|
||||||
|
// Add temporary "generating" message
|
||||||
|
const tempMessageId = 'temp-generating-msg';
|
||||||
|
const container = document.getElementById('chat-messages');
|
||||||
|
const tempMsg = document.createElement('div');
|
||||||
|
tempMsg.id = tempMessageId;
|
||||||
|
tempMsg.className = 'flex items-start gap-3';
|
||||||
|
tempMsg.innerHTML = `
|
||||||
|
<div class="w-8 h-8 bg-brand-highlight/20 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-lg">🤖</span>
|
||||||
|
</div>
|
||||||
|
<div class="max-w-[70%]">
|
||||||
|
<div class="bg-brand-bg-dark rounded-2xl p-4 border border-gray-600">
|
||||||
|
<p class="text-gray-300">
|
||||||
|
wird generiert<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
container.appendChild(tempMsg);
|
||||||
|
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
|
||||||
|
|
||||||
|
// Disable input while processing - show black spinner in button
|
||||||
|
const sendBtn = document.getElementById('send-btn');
|
||||||
|
const originalHTML = sendBtn.innerHTML;
|
||||||
|
sendBtn.disabled = true;
|
||||||
|
sendBtn.innerHTML = '<svg class="w-5 h-5 animate-spin" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" opacity="0.3"/><path d="M12 2v4c3.31 0 6 2.69 6 6h4c0-5.52-4.48-10-10-10z"/></svg>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const endpoint = currentPost ? '/api/employee/chat/refine' : '/api/employee/chat/generate';
|
||||||
|
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
post_type_id: selectedPostTypeId,
|
||||||
|
conversation_id: conversationId,
|
||||||
|
current_post: currentPost,
|
||||||
|
chat_history: chatHistory
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Remove temporary "generating" message
|
||||||
|
const tempMsg = document.getElementById(tempMessageId);
|
||||||
|
if (tempMsg) tempMsg.remove();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
currentPost = result.post;
|
||||||
|
conversationId = result.conversation_id;
|
||||||
|
|
||||||
|
// Add AI response to chat
|
||||||
|
addMessageToChat('ai', result.explanation || 'Hier ist dein Post:', result.post);
|
||||||
|
|
||||||
|
// Show save button
|
||||||
|
const saveBtn = document.getElementById('save-btn');
|
||||||
|
saveBtn.classList.remove('hidden');
|
||||||
|
saveBtn.classList.add('flex');
|
||||||
|
|
||||||
|
// Store in history
|
||||||
|
chatHistory.push({
|
||||||
|
user: message,
|
||||||
|
ai: result.post,
|
||||||
|
explanation: result.explanation
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showToast('Fehler: ' + (result.error || 'Unbekannter Fehler'), 'error');
|
||||||
|
addMessageToChat('ai', '❌ Entschuldigung, es gab einen Fehler. Bitte versuche es erneut.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showToast('Netzwerkfehler beim Generieren', 'error');
|
||||||
|
|
||||||
|
// Remove temporary message on error
|
||||||
|
const tempMsg = document.getElementById(tempMessageId);
|
||||||
|
if (tempMsg) tempMsg.remove();
|
||||||
|
|
||||||
|
addMessageToChat('ai', '❌ Netzwerkfehler. Bitte überprüfe deine Verbindung.');
|
||||||
|
} finally {
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
sendBtn.innerHTML = originalHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessageToChat(type, text, post = null) {
|
||||||
|
const container = document.getElementById('chat-messages');
|
||||||
|
const messageDiv = document.createElement('div');
|
||||||
|
|
||||||
|
if (type === 'user') {
|
||||||
|
// User messages: right-aligned, avatar on right, max-width 70%
|
||||||
|
messageDiv.className = 'flex items-start gap-3 justify-end';
|
||||||
|
|
||||||
|
const userAvatarHtml = userProfilePicture
|
||||||
|
? `<img src="${userProfilePicture}" alt="User" class="w-full h-full object-cover" referrerpolicy="no-referrer">`
|
||||||
|
: `<span class="text-brand-bg-dark font-bold text-sm">${userInitial}</span>`;
|
||||||
|
|
||||||
|
messageDiv.innerHTML = `
|
||||||
|
<div class="max-w-[70%]">
|
||||||
|
<div class="bg-blue-900/30 rounded-2xl p-4 border border-blue-700/50">
|
||||||
|
<p class="text-white">${escapeHtml(text)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-8 h-8 bg-brand-highlight rounded-full overflow-hidden flex items-center justify-center flex-shrink-0">
|
||||||
|
${userAvatarHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// Bot messages: left-aligned, avatar on left, max-width 70%
|
||||||
|
messageDiv.className = 'flex items-start gap-3';
|
||||||
|
const postHtml = post ? `
|
||||||
|
<div class="mt-3 p-4 bg-brand-bg rounded-2xl border border-brand-highlight/30">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<svg class="w-4 h-4 text-brand-highlight" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="text-brand-highlight font-medium text-sm">Post-Entwurf</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-200 whitespace-pre-wrap text-sm leading-relaxed">${escapeHtml(post)}</div>
|
||||||
|
</div>
|
||||||
|
` : '';
|
||||||
|
|
||||||
|
messageDiv.innerHTML = `
|
||||||
|
<div class="w-8 h-8 bg-brand-highlight/20 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-lg">🤖</span>
|
||||||
|
</div>
|
||||||
|
<div class="max-w-[70%]">
|
||||||
|
<div class="bg-brand-bg-dark rounded-2xl p-4 border border-gray-600">
|
||||||
|
<p class="text-gray-300">${escapeHtml(text)}</p>
|
||||||
|
${postHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(messageDiv);
|
||||||
|
|
||||||
|
// Scroll to bottom smoothly
|
||||||
|
setTimeout(() => {
|
||||||
|
container.scrollTo({
|
||||||
|
top: container.scrollHeight,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePost() {
|
||||||
|
if (!currentPost) {
|
||||||
|
showToast('Kein Post zum Speichern vorhanden', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedPostTypeId) {
|
||||||
|
showToast('Bitte wähle einen Post-Typ aus', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveBtn = document.getElementById('save-btn');
|
||||||
|
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
const originalHTML = saveBtn.innerHTML;
|
||||||
|
saveBtn.innerHTML = '<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/employee/chat/save', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
post_content: currentPost,
|
||||||
|
post_type_id: selectedPostTypeId,
|
||||||
|
conversation_id: conversationId,
|
||||||
|
chat_history: chatHistory
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Post erfolgreich gespeichert!', 'success');
|
||||||
|
|
||||||
|
// Redirect to post details after short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `/posts/${result.post_id}`;
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
showToast('Fehler beim Speichern: ' + (result.error || 'Unbekannter Fehler'), 'error');
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = originalHTML;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showToast('Netzwerkfehler beim Speichern', 'error');
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = originalHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(message, type = 'info') {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
const colors = {
|
||||||
|
success: 'bg-green-600 border-green-500',
|
||||||
|
error: 'bg-red-600 border-red-500',
|
||||||
|
info: 'bg-blue-600 border-blue-500',
|
||||||
|
warning: 'bg-yellow-600 border-yellow-500'
|
||||||
|
};
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
success: '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
|
||||||
|
error: '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>',
|
||||||
|
info: '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
|
||||||
|
warning: '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>'
|
||||||
|
};
|
||||||
|
|
||||||
|
toast.className = `fixed bottom-8 right-8 ${colors[type]} text-white px-6 py-4 rounded-xl shadow-2xl border-2 z-50 flex items-center gap-3 transform transition-all duration-300`;
|
||||||
|
toast.innerHTML = `
|
||||||
|
${icons[type]}
|
||||||
|
<span class="font-medium">${message}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.style.opacity = '0';
|
||||||
|
toast.style.transform = 'translateY(1rem)';
|
||||||
|
setTimeout(() => toast.remove(), 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
onclick="window.location.href='/company/manage/post/{{ post.id }}?employee_id={{ employee_id }}'">
|
onclick="window.location.href='/company/manage/post/{{ post.id }}?employee_id={{ employee_id }}'">
|
||||||
<div class="flex items-start justify-between gap-2 mb-2">
|
<div class="flex items-start justify-between gap-2 mb-2">
|
||||||
<h4 class="post-card-title">{{ post.topic_title or 'Untitled' }}</h4>
|
<h4 class="post-card-title">{{ post.topic_title or 'Untitled' }}</h4>
|
||||||
{% if post.critic_feedback and post.critic_feedback | length > 0 %}
|
{% 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].overall_score %}
|
{% set score = post.critic_feedback[-1].get('overall_score', 0) %}
|
||||||
<span class="score-badge flex-shrink-0 {{ 'score-high' if score >= 85 else 'score-medium' if score >= 70 else 'score-low' }}">
|
<span class="score-badge flex-shrink-0 {{ 'score-high' if score >= 85 else 'score-medium' if score >= 70 else 'score-low' }}">
|
||||||
{{ score }}
|
{{ score }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -510,9 +510,9 @@
|
|||||||
<span class="px-3 py-1.5 rounded-lg text-sm font-medium {{ 'bg-green-600/30 text-green-300 border border-green-600/50' if post.status == 'approved' else 'bg-yellow-600/30 text-yellow-300 border border-yellow-600/50' }}">
|
<span class="px-3 py-1.5 rounded-lg text-sm font-medium {{ 'bg-green-600/30 text-green-300 border border-green-600/50' if post.status == 'approved' else 'bg-yellow-600/30 text-yellow-300 border border-yellow-600/50' }}">
|
||||||
{{ post.status | capitalize }}
|
{{ post.status | capitalize }}
|
||||||
</span>
|
</span>
|
||||||
{% if final_feedback %}
|
{% if final_feedback and final_feedback.get('overall_score') is not none %}
|
||||||
<span class="px-3 py-1.5 rounded-lg text-sm font-bold {{ 'bg-green-600/30 text-green-300' if final_feedback.overall_score >= 85 else 'bg-yellow-600/30 text-yellow-300' if final_feedback.overall_score >= 70 else 'bg-red-600/30 text-red-300' }}">
|
<span class="px-3 py-1.5 rounded-lg text-sm font-bold {{ 'bg-green-600/30 text-green-300' if final_feedback.get('overall_score', 0) >= 85 else 'bg-yellow-600/30 text-yellow-300' if final_feedback.get('overall_score', 0) >= 70 else 'bg-red-600/30 text-red-300' }}">
|
||||||
Score: {{ final_feedback.overall_score }}/100
|
Score: {{ final_feedback.get('overall_score', 0) }}/100
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -833,11 +833,11 @@
|
|||||||
<!-- Bottom Section: Score, Feedback, Actions -->
|
<!-- Bottom Section: Score, Feedback, Actions -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||||
<!-- Score Breakdown -->
|
<!-- Score Breakdown -->
|
||||||
{% if final_feedback and final_feedback.scores %}
|
{% if final_feedback and final_feedback.get('scores') and final_feedback.get('overall_score') is not none %}
|
||||||
<div class="section-card rounded-xl p-6">
|
<div class="section-card rounded-xl p-6">
|
||||||
<h3 class="font-semibold text-white mb-4 flex items-center gap-2">
|
<h3 class="font-semibold text-white mb-4 flex items-center gap-2">
|
||||||
<svg class="w-5 h-5 text-brand-highlight" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
<svg class="w-5 h-5 text-brand-highlight" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
||||||
Score: {{ final_feedback.overall_score }}/100
|
Score: {{ final_feedback.get('overall_score', 0) }}/100
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -943,10 +943,12 @@
|
|||||||
<span class="px-3 py-1 bg-brand-highlight/20 text-brand-highlight rounded-lg text-sm font-medium">Version {{ i + 1 }}</span>
|
<span class="px-3 py-1 bg-brand-highlight/20 text-brand-highlight rounded-lg text-sm font-medium">Version {{ i + 1 }}</span>
|
||||||
{% if post.critic_feedback and i < post.critic_feedback | length %}
|
{% if post.critic_feedback and i < post.critic_feedback | length %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="px-2 py-1 rounded text-xs font-medium {{ 'bg-green-600/30 text-green-300' if post.critic_feedback[i].overall_score >= 85 else 'bg-yellow-600/30 text-yellow-300' }}">
|
{% if post.critic_feedback[i].get('overall_score') is not none %}
|
||||||
Score: {{ post.critic_feedback[i].overall_score }}/100
|
<span class="px-2 py-1 rounded text-xs font-medium {{ 'bg-green-600/30 text-green-300' if post.critic_feedback[i].get('overall_score', 0) >= 85 else 'bg-yellow-600/30 text-yellow-300' }}">
|
||||||
|
Score: {{ post.critic_feedback[i].get('overall_score', 0) }}/100
|
||||||
</span>
|
</span>
|
||||||
{% if post.critic_feedback[i].approved %}
|
{% endif %}
|
||||||
|
{% if post.critic_feedback[i].get('approved') %}
|
||||||
<span class="px-2 py-1 bg-green-600/30 text-green-300 rounded text-xs">Approved</span>
|
<span class="px-2 py-1 bg-green-600/30 text-green-300 rounded text-xs">Approved</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
onclick="window.location.href='/posts/{{ post.id }}'">
|
onclick="window.location.href='/posts/{{ post.id }}'">
|
||||||
<div class="flex items-start justify-between gap-2 mb-2">
|
<div class="flex items-start justify-between gap-2 mb-2">
|
||||||
<h4 class="post-card-title">{{ post.topic_title or 'Untitled' }}</h4>
|
<h4 class="post-card-title">{{ post.topic_title or 'Untitled' }}</h4>
|
||||||
{% if post.critic_feedback and post.critic_feedback | length > 0 %}
|
{% 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].overall_score %}
|
{% set score = post.critic_feedback[-1].get('overall_score', 0) %}
|
||||||
<span class="score-badge flex-shrink-0 {{ 'score-high' if score >= 85 else 'score-medium' if score >= 70 else 'score-low' }}">
|
<span class="score-badge flex-shrink-0 {{ 'score-high' if score >= 85 else 'score-medium' if score >= 70 else 'score-low' }}">
|
||||||
{{ score }}
|
{{ score }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1354,7 +1354,9 @@ async def posts_page(request: Request):
|
|||||||
"profile_picture": profile_picture
|
"profile_picture": profile_picture
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
logger.error(f"Error loading posts: {e}")
|
logger.error(f"Error loading posts: {e}")
|
||||||
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
return templates.TemplateResponse("posts.html", {
|
return templates.TemplateResponse("posts.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"page": "posts",
|
"page": "posts",
|
||||||
@@ -1628,7 +1630,9 @@ async def post_detail_page(request: Request, post_id: str):
|
|||||||
"media_items_dict": media_items_dict
|
"media_items_dict": media_items_dict
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
logger.error(f"Error loading post detail: {e}")
|
logger.error(f"Error loading post detail: {e}")
|
||||||
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
return RedirectResponse(url="/posts", status_code=302)
|
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)
|
@user_router.get("/status", response_class=HTMLResponse)
|
||||||
async def status_page(request: Request):
|
async def status_page(request: Request):
|
||||||
"""User's status page."""
|
"""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)
|
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")
|
@user_router.post("/api/company/invite")
|
||||||
async def send_company_invitation(request: Request):
|
async def send_company_invitation(request: Request):
|
||||||
"""Send invitation to a new employee."""
|
"""Send invitation to a new employee."""
|
||||||
|
|||||||
Reference in New Issue
Block a user