fix: hide framework labels in generated content + editable hashtags

- Add explicit instruction to LLM: never write framework labels (PROBLEMA,
  AGITAZIONE, SOLUZIONE, etc.) — use them as invisible narrative structure only
- Replace static hashtag chips with HashtagEditor component:
  - Click hashtag to edit inline
  - Click X to remove
  - Input field to add new hashtags
  - Save button to persist changes to DB

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michele
2026-04-03 18:20:06 +02:00
parent 497b95e673
commit a6270c2e3f
2 changed files with 89 additions and 11 deletions

View File

@@ -41,7 +41,12 @@ def generate_post_text(
f"Your expertise covers: {topics_str}. "
f"Your communication style is {tone}. "
f"You create authentic, engaging content that resonates with your audience. "
f"Never reveal you are an AI. Write as {name} would naturally write."
f"Never reveal you are an AI. Write as {name} would naturally write.\n\n"
f"REGOLA CRITICA: Se ti viene indicata una tecnica narrativa (PAS, AIDA, Storytelling, ecc.), "
f"usala SOLO come struttura invisibile del testo. "
f"NON scrivere MAI le etichette del framework nel post (es. non scrivere 'PROBLEMA:', "
f"'AGITAZIONE:', 'SOLUZIONE:', 'ATTENZIONE:', 'INTERESSE:', ecc.). "
f"Il lettore non deve percepire alcun framework — deve sembrare un post naturale e spontaneo."
)
# Platform-specific instructions

View File

@@ -346,16 +346,11 @@ export default function ContentPage() {
)}
{generated.hashtags?.length > 0 && (
<div style={{ marginBottom: '1rem' }}>
<span style={{ ...labelStyle, display: 'block', marginBottom: '0.4rem' }}>Hashtag</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.35rem' }}>
{generated.hashtags.map((tag, i) => (
<span key={i} style={{ fontSize: '0.78rem', padding: '0.15rem 0.5rem', backgroundColor: 'var(--accent-light)', color: 'var(--accent)' }}>
{tag}
</span>
))}
</div>
</div>
<HashtagEditor
hashtags={generated.hashtags}
onChange={newTags => setGenerated(prev => ({ ...prev, hashtags: newTags }))}
postId={generated.id}
/>
)}
<div style={{ display: 'flex', gap: '0.5rem', paddingTop: '1rem', borderTop: '1px solid var(--border)', flexWrap: 'wrap' }}>
@@ -382,6 +377,84 @@ export default function ContentPage() {
)
}
function HashtagEditor({ hashtags, onChange, postId }) {
const [newTag, setNewTag] = useState('')
const [editIdx, setEditIdx] = useState(null)
const [editValue, setEditValue] = useState('')
const [saving, setSaving] = useState(false)
const removeTag = (idx) => {
const updated = hashtags.filter((_, i) => i !== idx)
onChange(updated)
}
const addTag = () => {
let tag = newTag.trim()
if (!tag) return
if (!tag.startsWith('#')) tag = `#${tag}`
if (!hashtags.includes(tag)) {
onChange([...hashtags, tag])
}
setNewTag('')
}
const startEdit = (idx) => {
setEditIdx(idx)
setEditValue(hashtags[idx])
}
const confirmEdit = () => {
if (editIdx === null) return
let tag = editValue.trim()
if (!tag) { removeTag(editIdx); setEditIdx(null); return }
if (!tag.startsWith('#')) tag = `#${tag}`
const updated = [...hashtags]
updated[editIdx] = tag
onChange(updated)
setEditIdx(null)
}
const saveHashtags = async () => {
setSaving(true)
try {
await api.put(`/content/posts/${postId}`, { hashtags })
} catch (e) { /* silent */ }
setSaving(false)
}
return (
<div style={{ marginBottom: '1rem' }}>
<span style={{ ...labelStyle, display: 'block', marginBottom: '0.4rem' }}>Hashtag</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.35rem', marginBottom: '0.5rem' }}>
{hashtags.map((tag, i) => (
editIdx === i ? (
<input key={i} type="text" value={editValue} onChange={e => setEditValue(e.target.value)}
onBlur={confirmEdit} onKeyDown={e => { if (e.key === 'Enter') confirmEdit(); if (e.key === 'Escape') setEditIdx(null) }}
autoFocus style={{ fontSize: '0.78rem', padding: '0.15rem 0.5rem', border: '1px solid var(--accent)', outline: 'none', fontFamily: "'DM Sans', sans-serif", width: Math.max(60, editValue.length * 8) }} />
) : (
<span key={i} style={{ fontSize: '0.78rem', padding: '0.15rem 0.5rem', backgroundColor: 'var(--accent-light)', color: 'var(--accent)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}
onClick={() => startEdit(i)}>
{tag}
<span onClick={e => { e.stopPropagation(); removeTag(i) }} style={{ fontSize: '0.65rem', cursor: 'pointer', opacity: 0.7, lineHeight: 1 }} title="Rimuovi">✕</span>
</span>
)
))}
</div>
<div style={{ display: 'flex', gap: '0.35rem', alignItems: 'center' }}>
<input type="text" value={newTag} onChange={e => setNewTag(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addTag() } }}
placeholder="Aggiungi hashtag…" style={{ fontSize: '0.8rem', padding: '0.3rem 0.6rem', border: '1px solid var(--border)', outline: 'none', fontFamily: "'DM Sans', sans-serif", flex: 1 }}
onFocus={e => e.target.style.borderColor = 'var(--ink)'} onBlur={e => e.target.style.borderColor = 'var(--border)'} />
<button type="button" onClick={addTag} style={{ fontSize: '0.78rem', padding: '0.3rem 0.6rem', backgroundColor: 'var(--cream-dark)', border: 'none', cursor: 'pointer', color: 'var(--ink-light)' }}>+</button>
</div>
<button type="button" onClick={saveHashtags} disabled={saving}
style={{ fontSize: '0.72rem', marginTop: '0.35rem', padding: '0.2rem 0.5rem', backgroundColor: 'transparent', border: '1px solid var(--border)', cursor: 'pointer', color: 'var(--ink-muted)' }}>
{saving ? 'Salvataggio' : 'Salva hashtag'}
</button>
</div>
)
}
const labelStyle = {
fontSize: '0.72rem', fontWeight: 700, letterSpacing: '0.1em',
textTransform: 'uppercase', color: 'var(--ink)',