diff --git a/src/app/api/admin/company-coverage/route.ts b/src/app/api/admin/company-coverage/route.ts index d8712cb..56982de 100644 --- a/src/app/api/admin/company-coverage/route.ts +++ b/src/app/api/admin/company-coverage/route.ts @@ -3,19 +3,33 @@ import { createClient } from '@/lib/supabase/server'; import { createAdminClient } from '@/lib/supabase/admin'; import { hybridAI } from '@/lib/ai/hybridRouter'; -// ─── Pen name rotation ──────────────────────────────────────────────────────── -const BB_PEN_NAMES = [ +// ─── Byline ─────────────────────────────────────────────────────────────────── +// All auto-generated BB stories publish under Ryan Salazar's byline (founder). +// AV Beat retains its own pen pool. To re-enable the BB rotation, set +// BB_AUTOSTORY_BYLINE="rotate" — env override so we don't need a code change +// when the editorial policy changes. +const AV_PEN_NAMES = ['Rex Chandler', 'Dana Flux', 'Derek Wainwright', 'Sloane Rigging', 'Chip Crosspoint', 'Blair Presenter', 'Jordan Lumen']; +const BB_PEN_NAMES_LEGACY = [ 'Michael Strand', 'David Harlow', 'Karen Fielding', 'James Mercer', 'Peter Calloway', 'Sandra Voss', 'Brian Kowalski', 'Laura Pennington', 'Thomas Reeves', 'Christine Vale', 'Marcus Webb', 'Ellen Forsythe', ]; -const AV_PEN_NAMES = ['Rex Chandler', 'Dana Flux', 'Derek Wainwright', 'Sloane Rigging', 'Chip Crosspoint', 'Blair Presenter', 'Jordan Lumen']; +const BB_AUTOSTORY_BYLINE = process.env.BB_AUTOSTORY_BYLINE || 'Ryan Salazar'; function pickPenName(siteId: number, recentNames: string[]): string { - const pool = siteId === 2 ? AV_PEN_NAMES : BB_PEN_NAMES; - // Avoid last 3 used names - const available = pool.filter(n => !recentNames.slice(-3).includes(n)); - return available[Math.floor(Math.random() * available.length)] || pool[0]; + // AV Beat: rotate its small pen pool + if (siteId === 2) { + const pool = AV_PEN_NAMES; + const available = pool.filter(n => !recentNames.slice(-3).includes(n)); + return available[Math.floor(Math.random() * available.length)] || pool[0]; + } + // Broadcast Beat: fixed byline (Ryan) unless overridden to "rotate". + if (BB_AUTOSTORY_BYLINE.toLowerCase() === 'rotate') { + const pool = BB_PEN_NAMES_LEGACY; + const available = pool.filter(n => !recentNames.slice(-3).includes(n)); + return available[Math.floor(Math.random() * available.length)] || pool[0]; + } + return BB_AUTOSTORY_BYLINE; } // ─── Company extraction via Hybrid AI ──────────────────────────────────────── diff --git a/src/lib/ai/rewrite-client.ts b/src/lib/ai/rewrite-client.ts index 77a5769..c471987 100644 --- a/src/lib/ai/rewrite-client.ts +++ b/src/lib/ai/rewrite-client.ts @@ -141,10 +141,113 @@ export const anthropicProvider: RewriteProvider = { }, }; +// ─── Ollama provider ────────────────────────────────────────────────────────── +// +// Talks to a local Ollama instance via the OpenAI-compatible +// /v1/chat/completions endpoint. Env: +// OLLAMA_ENDPOINT e.g. http://10.10.0.66:11434 (NO trailing /v1) +// OLLAMA_MODEL_DEFAULT e.g. llama3.1:70b +// OLLAMA_API_KEY optional bearer +// BB_REWRITE_MODEL overrides the model just for the rewriter export const ollamaProvider: RewriteProvider = { name: "ollama", - async rewrite(): Promise { - throw new Error("ollama provider not implemented yet — set BB_REWRITE_PROVIDER=anthropic"); + + async rewrite(req: RewriteRequest): Promise { + const endpoint = (process.env.OLLAMA_ENDPOINT || "").replace(/\/$/, ""); + if (!endpoint) throw new Error("OLLAMA_ENDPOINT not set in env"); + const model = + process.env.BB_REWRITE_MODEL || + process.env.OLLAMA_MODEL_DEFAULT || + "llama3.1:70b"; + + const systemBlock = + buildSystemPrompt(req.persona) + "\n\n=== STYLE GUIDE ===\n" + STYLE_GUIDE; + + const userPrompt = [ + `Below is a raw press release submitted by an industry contact. Rewrite it as a journalism piece in the voice of ${req.persona.name} (${req.persona.beat}).`, + "", + "Output STRICTLY as a JSON object with these keys (no markdown fences, no commentary):", + ` "title" (string — your rewritten headline, NOT the original press-release headline)`, + ` "excerpt" (string — 1-2 sentence dek, max 240 chars)`, + ` "body" (string — HTML

,

,