Allow keeping selected calendar pieces/days and regenerating the rest.
Agency can mark good stories, keep only those days, or request a partial v2 for unmarked slots without replacing the whole month. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,8 @@ export type GeneratePreviewInput = {
|
||||
brandbook?: Brandbook | null;
|
||||
/** Publicaciones reales (pipeline / IG) además del histórico de calendarios. */
|
||||
publishedPosts?: PublishedNetworkPost[];
|
||||
/** Títulos / hooks a no repetir (piezas que el usuario conserva). */
|
||||
avoidTitles?: string[];
|
||||
};
|
||||
|
||||
export type GeneratePreviewResult = {
|
||||
@@ -88,6 +90,7 @@ async function anthropicStrategist(
|
||||
label: input.label,
|
||||
dna,
|
||||
trends,
|
||||
avoidTitles: input.avoidTitles || [],
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -216,4 +219,83 @@ export function mergePreviewIntoPlan(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Conserva piezas marcadas y sustituye el resto con un preview nuevo
|
||||
* (prioriza mismo día; si faltan, rellena con otras del preview).
|
||||
*/
|
||||
export function mergeKeepAndRegenerate(
|
||||
existing: RrssMonthPlan,
|
||||
preview: RrssMonthPlan,
|
||||
keepIds: Iterable<string>
|
||||
): RrssMonthPlan {
|
||||
const keep = new Set(keepIds);
|
||||
const kept = (existing.pieces || []).filter((p) => keep.has(p.id));
|
||||
const keptDates = new Set(kept.map((p) => p.date.slice(0, 10)));
|
||||
|
||||
const needByDate = new Map<string, number>();
|
||||
for (const p of existing.pieces || []) {
|
||||
if (keep.has(p.id)) continue;
|
||||
const d = p.date.slice(0, 10);
|
||||
needByDate.set(d, (needByDate.get(d) || 0) + 1);
|
||||
}
|
||||
|
||||
const usedPreview = new Set<string>();
|
||||
const replacements: RrssPiece[] = [];
|
||||
|
||||
const takeFromPreview = (preferDate: string | null, n: number): RrssPiece[] => {
|
||||
const pool = (preview.pieces || []).filter((p) => !usedPreview.has(p.id));
|
||||
const same = preferDate
|
||||
? pool.filter((p) => p.date.slice(0, 10) === preferDate)
|
||||
: [];
|
||||
const rest = pool.filter((p) => p.date.slice(0, 10) !== preferDate);
|
||||
const picked = [...same, ...rest].slice(0, n);
|
||||
for (const p of picked) usedPreview.add(p.id);
|
||||
return picked;
|
||||
};
|
||||
|
||||
for (const [date, n] of needByDate) {
|
||||
const picked = takeFromPreview(date, n);
|
||||
picked.forEach((p, i) => {
|
||||
replacements.push({
|
||||
...p,
|
||||
id: newPieceId(date, `${p.title}-${i}`),
|
||||
date,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...existing,
|
||||
pieces: [...kept, ...replacements].sort((a, b) =>
|
||||
a.date.localeCompare(b.date)
|
||||
),
|
||||
hero: {
|
||||
...existing.hero,
|
||||
...(preview.hero || {}),
|
||||
chips: [
|
||||
`${kept.length} conservadas`,
|
||||
`${replacements.length} regeneradas`,
|
||||
...(preview.hero?.chips || []).slice(0, 2),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Deja solo las piezas/días marcados. */
|
||||
export function keepOnlyPieces(
|
||||
existing: RrssMonthPlan,
|
||||
keepIds: Iterable<string>
|
||||
): RrssMonthPlan {
|
||||
const keep = new Set(keepIds);
|
||||
const pieces = (existing.pieces || []).filter((p) => keep.has(p.id));
|
||||
return {
|
||||
...existing,
|
||||
pieces,
|
||||
hero: {
|
||||
...existing.hero,
|
||||
chips: [`${pieces.length} piezas`, 'selección manual'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { monthLabel };
|
||||
|
||||
@@ -860,6 +860,7 @@ export function strategistSystemPrompt(input: {
|
||||
label: string;
|
||||
dna: ClientDna;
|
||||
trends: TrendBeat[];
|
||||
avoidTitles?: string[];
|
||||
}): string {
|
||||
const netBlock =
|
||||
input.dna.networkSamples.length > 0
|
||||
@@ -867,6 +868,13 @@ export function strategistSystemPrompt(input: {
|
||||
.map((s) => `- ${s}`)
|
||||
.join('\n')}\n`
|
||||
: '';
|
||||
const avoid = (input.avoidTitles || []).filter(Boolean).slice(0, 40);
|
||||
const avoidBlock =
|
||||
avoid.length > 0
|
||||
? `\nPIEZAS QUE EL CLIENTE YA CONSERVA (no repitas estos títulos ni el mismo ángulo):\n${avoid
|
||||
.map((t) => `- ${t}`)
|
||||
.join('\n')}\n`
|
||||
: '';
|
||||
return `Eres Head of Social de 4Ventures. Crea un calendario NUEVO para ${input.label} (${input.ym}).
|
||||
NO copies títulos viejos ni captiones publicadas. Usa como constraint creativo:
|
||||
1) Zona de marca / brandbook
|
||||
@@ -876,7 +884,7 @@ ADN series: ${input.dna.signatureSeries.join(', ')}.
|
||||
Voz: ${input.dna.brand.tone}. Vertical: ${input.dna.brand.vertical}.
|
||||
Volumen guía: ~${input.dna.avgPerWeek} piezas/semana · Stories ratio hist. ${(input.dna.storiesRatio * 100).toFixed(0)}%.
|
||||
${input.dna.brandbookPrompt ? `\nBRANDBOOK:\n${input.dna.brandbookPrompt}\n` : ''}
|
||||
${netBlock}
|
||||
${netBlock}${avoidBlock}
|
||||
Playbook 2026 ES: ${PLAYBOOK_2026.principle}
|
||||
Tendencias del mes:
|
||||
${input.trends.map((t) => `- ${t.label}: ${t.why}`).join('\n')}
|
||||
|
||||
Reference in New Issue
Block a user