import { z } from 'zod'; import type { LlmClient } from '@/lib/ai/llm-client'; import { GeminiLlmClient } from '@/lib/ai/gemini-client'; import { clamp, nowIso, loadPhaseContainers, persistPhaseArtifacts } from '@/lib/server/projects'; import type { MarketingModel } from '@/lib/types/marketing'; const HomepageMessagingSchema = z.object({ headline: z.string().nullable(), subheadline: z.string().nullable(), bullets: z.array(z.string()).default([]), }); const MarketingModelSchema = z.object({ projectId: z.string(), icp: z.array(z.string()).default([]), positioning: z.string().nullable(), homepageMessaging: HomepageMessagingSchema, initialChannels: z.array(z.string()).default([]), launchAngles: z.array(z.string()).default([]), overallConfidence: z.number().min(0).max(1), }); export async function runMarketingPlanning( projectId: string, llmClient?: LlmClient, ): Promise { const { phaseData } = await loadPhaseContainers(projectId); const canonical = phaseData.canonicalProductModel; if (!canonical) { throw new Error('Canonical product model missing. Run buildCanonicalProductModel first.'); } const llm = llmClient ?? new GeminiLlmClient(); const systemPrompt = 'You are a SaaS marketing strategist. Given the canonical product model, produce ICP, positioning, homepage messaging, and launch ideas as strict JSON.'; const marketing = await llm.structuredCall({ model: 'gemini', systemPrompt, messages: [ { role: 'user', content: [ 'Canonical product model JSON:', '```json', JSON.stringify(canonical, null, 2), '```', 'Respond ONLY with valid JSON that matches the required schema.', ].join('\n'), }, ], schema: MarketingModelSchema, temperature: 0.2, }); await persistPhaseArtifacts(projectId, (phaseData, phaseScores, phaseHistory) => { phaseData.marketingPlan = marketing; phaseScores.marketing = { overallCompletion: clamp(marketing.homepageMessaging.bullets.length ? 0.7 : 0.5), overallConfidence: marketing.overallConfidence, updatedAt: nowIso(), }; phaseHistory.push({ phase: 'marketing', status: 'completed', timestamp: nowIso() }); return { phaseData, phaseScores, phaseHistory, nextPhase: 'marketing_ready' }; }); return marketing; }