fix: separate hashtags from content in AI generation
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

- Remove hashtag instructions from AI prompts (content-only generation)
- Add _split_hashtags() to extract any hashtags AI still includes
- generate_content() now returns (content, hashtags, model) tuple
- Prevents duplicate hashtags when publishing (content + hashtags field)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-19 09:43:18 +01:00
parent c1a8cb6183
commit b73fcb59d1
2 changed files with 58 additions and 15 deletions

View File

@ -368,8 +368,8 @@ def social_publisher_generate():
for key, default in defaults.items():
context.setdefault(key, default)
content, model = social_publisher.generate_content(post_type, context, tone=tone)
return jsonify({'success': True, 'content': content, 'model': model})
content, hashtags, model = social_publisher.generate_content(post_type, context, tone=tone)
return jsonify({'success': True, 'content': content, 'hashtags': hashtags, 'model': model})
except Exception as e:
logger.error(f"AI generation failed: {e}")
return jsonify({'success': False, 'error': 'Nie udalo sie wygenerowac tresci. Sprobuj ponownie lub wpisz tresc recznie.'}), 500

View File

@ -8,6 +8,7 @@ Supports per-company Facebook configuration via OAuth tokens.
import logging
import os
import re
from datetime import datetime
from typing import Optional, Dict, List, Tuple
@ -39,12 +40,12 @@ Post powinien:
- Opisać czym zajmuje się firma (2-3 zdania)
- Podkreślić wartość tej firmy dla społeczności biznesowej
- Zachęcić do odwiedzenia strony firmy
- Zawierać 3-5 hashtagów (#NordaBiznes #IzbaGospodarcza #Wejherowo + branżowe)
- Być napisany ciepło, z dumą i wspierająco
- Mieć 100-200 słów
- Być po polsku
- NIE dodawaj hashtagów zostaną wygenerowane osobno
Odpowiedz WYŁĄCZNIE tekstem postu (z hashtagami na końcu).""",
Odpowiedz WYŁĄCZNIE tekstem postu, BEZ hashtagów.""",
'event_invitation': """Napisz post na Facebook zapraszający na wydarzenie Izby Gospodarczej NORDA Biznes:
@ -58,11 +59,11 @@ Post powinien:
- Zawierać najważniejsze informacje (co, kiedy, gdzie)
- Wspomnieć korzyści z udziału
- Zawierać CTA (zachęta do rejestracji/kontaktu)
- Zawierać 3-5 hashtagów (#NordaBiznes #Networking #Wejherowo + tematyczne)
- Mieć 100-150 słów
- Być po polsku
- NIE dodawaj hashtagów zostaną wygenerowane osobno
Odpowiedz WYŁĄCZNIE tekstem postu.""",
Odpowiedz WYŁĄCZNIE tekstem postu, BEZ hashtagów.""",
'event_recap': """Napisz post na Facebook będący relacją z wydarzenia Izby Gospodarczej NORDA Biznes:
@ -77,11 +78,11 @@ Post powinien:
- Podsumować najważniejsze tematy/wnioski
- Wspomnieć atmosferę i wartość spotkania
- Zachęcić do udziału w kolejnych wydarzeniach
- Zawierać 3-5 hashtagów
- Mieć 100-200 słów
- Być po polsku, relacyjny i dziękujący
- NIE dodawaj hashtagów zostaną wygenerowane osobno
Odpowiedz WYŁĄCZNIE tekstem postu.""",
Odpowiedz WYŁĄCZNIE tekstem postu, BEZ hashtagów.""",
'regional_news': """Napisz post na Facebook dla Izby Gospodarczej NORDA Biznes komentujący aktualność regionalną:
@ -93,11 +94,11 @@ Post powinien:
- Informować o temacie w kontekście biznesowym regionu
- Być informacyjny i ekspercki
- Dodać perspektywę Izby (jak to wpływa na lokalny biznes)
- Zawierać 3-5 hashtagów (#NordaBiznes #Pomorze #Wejherowo + tematyczne)
- Mieć 100-200 słów
- Być po polsku
- NIE dodawaj hashtagów zostaną wygenerowane osobno
Odpowiedz WYŁĄCZNIE tekstem postu.""",
Odpowiedz WYŁĄCZNIE tekstem postu, BEZ hashtagów.""",
'chamber_news': """Napisz post na Facebook z aktualnościami Izby Gospodarczej NORDA Biznes:
@ -108,11 +109,11 @@ Post powinien:
- Być oficjalny ale przystępny
- Przekazać najważniejsze informacje
- Zachęcić do interakcji (pytania, komentarze)
- Zawierać 3-5 hashtagów (#NordaBiznes #IzbaGospodarcza + tematyczne)
- Mieć 80-150 słów
- Być po polsku
- NIE dodawaj hashtagów zostaną wygenerowane osobno
Odpowiedz WYŁĄCZNIE tekstem postu.""",
Odpowiedz WYŁĄCZNIE tekstem postu, BEZ hashtagów.""",
}
@ -411,7 +412,47 @@ class SocialPublisherService:
# ---- AI Content Generation ----
def generate_content(self, post_type: str, context: dict, tone: str = None) -> Tuple[str, str]:
@staticmethod
def _split_hashtags(text: str) -> Tuple[str, str]:
"""Split content and hashtags. Returns (clean_content, hashtags)."""
lines = text.strip().split('\n')
content_lines = []
hashtag_words = []
for line in lines:
stripped = line.strip()
# Line is purely hashtags (all words start with #)
if stripped and all(w.startswith('#') for w in stripped.split()):
hashtag_words.extend(stripped.split())
else:
content_lines.append(line)
# Also extract inline hashtags from the last content line
if content_lines:
last = content_lines[-1].strip()
inline_tags = re.findall(r'#\w+', last)
if inline_tags and len(inline_tags) >= 2:
# Remove inline hashtags from last line
cleaned_last = re.sub(r'\s*#\w+', '', last).strip()
if cleaned_last:
content_lines[-1] = cleaned_last
else:
content_lines.pop()
hashtag_words.extend(inline_tags)
content = '\n'.join(content_lines).strip()
# Deduplicate hashtags preserving order
seen = set()
unique_tags = []
for tag in hashtag_words:
low = tag.lower()
if low not in seen:
seen.add(low)
unique_tags.append(tag)
return content, ' '.join(unique_tags)
def generate_content(self, post_type: str, context: dict, tone: str = None) -> Tuple[str, str, str]:
"""Generate post content using AI.
Args:
@ -420,7 +461,7 @@ class SocialPublisherService:
tone: One of POST_TONES keys (default: DEFAULT_TONE)
Returns:
(content: str, ai_model: str)
(content: str, hashtags: str, ai_model: str)
"""
template = AI_PROMPTS.get(post_type)
if not template:
@ -444,7 +485,9 @@ class SocialPublisherService:
if not result:
raise RuntimeError("AI nie wygenerował treści. Spróbuj ponownie.")
return result.strip(), 'gemini-3-flash'
# Split out any hashtags AI may have included despite instructions
content, hashtags = self._split_hashtags(result)
return content, hashtags, 'gemini-3-flash'
def generate_hashtags(self, content: str, post_type: str = '') -> Tuple[str, str]:
"""Generate hashtags for given post content using AI.