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:
@@ -41,7 +41,12 @@ def generate_post_text(
|
|||||||
f"Your expertise covers: {topics_str}. "
|
f"Your expertise covers: {topics_str}. "
|
||||||
f"Your communication style is {tone}. "
|
f"Your communication style is {tone}. "
|
||||||
f"You create authentic, engaging content that resonates with your audience. "
|
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
|
# Platform-specific instructions
|
||||||
|
|||||||
@@ -346,16 +346,11 @@ export default function ContentPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{generated.hashtags?.length > 0 && (
|
{generated.hashtags?.length > 0 && (
|
||||||
<div style={{ marginBottom: '1rem' }}>
|
<HashtagEditor
|
||||||
<span style={{ ...labelStyle, display: 'block', marginBottom: '0.4rem' }}>Hashtag</span>
|
hashtags={generated.hashtags}
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.35rem' }}>
|
onChange={newTags => setGenerated(prev => ({ ...prev, hashtags: newTags }))}
|
||||||
{generated.hashtags.map((tag, i) => (
|
postId={generated.id}
|
||||||
<span key={i} style={{ fontSize: '0.78rem', padding: '0.15rem 0.5rem', backgroundColor: 'var(--accent-light)', color: 'var(--accent)' }}>
|
/>
|
||||||
{tag}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', paddingTop: '1rem', borderTop: '1px solid var(--border)', flexWrap: 'wrap' }}>
|
<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 = {
|
const labelStyle = {
|
||||||
fontSize: '0.72rem', fontWeight: 700, letterSpacing: '0.1em',
|
fontSize: '0.72rem', fontWeight: 700, letterSpacing: '0.1em',
|
||||||
textTransform: 'uppercase', color: 'var(--ink)',
|
textTransform: 'uppercase', color: 'var(--ink)',
|
||||||
|
|||||||
Reference in New Issue
Block a user