← back to Marketing Command Center
Copy panel: Gemini path (preferred over dead Anthropic) → real AI copy via valid Gemini key
b546fbd7c35898020b7d8ce7178056d3d3e5bbf5 · 2026-06-10 08:58:51 -0700 · Steve
Files touched
Diff
commit b546fbd7c35898020b7d8ce7178056d3d3e5bbf5
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jun 10 08:58:51 2026 -0700
Copy panel: Gemini path (preferred over dead Anthropic) → real AI copy via valid Gemini key
---
modules/copy/index.js | 39 +++++++++++++++++++++++++++++++++------
1 file changed, 33 insertions(+), 6 deletions(-)
diff --git a/modules/copy/index.js b/modules/copy/index.js
index 6e00bec..26918a2 100644
--- a/modules/copy/index.js
+++ b/modules/copy/index.js
@@ -225,6 +225,29 @@ async function llmGenerate(ctx, n) {
return parseVariants(text, n);
}
+// Gemini path (Google Generative Language API). Preferred when GEMINI_API_KEY is
+// set — same brand system prompt, parsed by the shared parseVariants().
+async function geminiGenerate(ctx, n) {
+ const key = process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
+ const model = process.env.GEMINI_MODEL || 'gemini-2.0-flash';
+ const resp = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`, {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({
+ systemInstruction: { parts: [{ text: brandSystemPrompt() }] },
+ contents: [{ role: 'user', parts: [{ text: userPromptFor(ctx, n) }] }],
+ generationConfig: { maxOutputTokens: 1500, temperature: 0.9 },
+ }),
+ });
+ if (!resp.ok) {
+ const detail = await resp.text().catch(() => '');
+ throw new Error(`gemini ${resp.status}: ${detail.slice(0, 300)}`);
+ }
+ const data = await resp.json();
+ const text = (data.candidates?.[0]?.content?.parts || []).map(p => p.text || '').join('').trim();
+ return parseVariants(text, n);
+}
+
// Parse the model's response into clean variants. Tolerant of JSON, fenced
// JSON, or plain newline/numbered lists.
function parseVariants(raw, n) {
@@ -276,7 +299,7 @@ module.exports = {
voice: brand.voice,
tagline: brand.tagline,
compliance: brand.compliance,
- llm: !!process.env.ANTHROPIC_API_KEY,
+ llm: !!(process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.ANTHROPIC_API_KEY),
});
});
@@ -293,15 +316,19 @@ module.exports = {
};
const n = clampN(b.n);
- // LLM path when keyed; gracefully fall back to templates on any failure.
+ // LLM path when keyed — Gemini preferred, Anthropic next; gracefully fall
+ // back to templates on any failure so we never return nothing.
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
+ try {
+ const variants = await geminiGenerate(ctx, n);
+ if (variants.length) return res.json({ variants, kind, mock: false, source: 'gemini' });
+ } catch (e) { console.warn(`[copy] gemini failed, trying next: ${e.message}`); }
+ }
if (process.env.ANTHROPIC_API_KEY) {
try {
const variants = await llmGenerate(ctx, n);
if (variants.length) return res.json({ variants, kind, mock: false, source: 'anthropic' });
- // Empty parse → fall through to templates rather than return nothing.
- } catch (e) {
- console.warn(`[copy] LLM failed, using templates: ${e.message}`);
- }
+ } catch (e) { console.warn(`[copy] anthropic failed, using templates: ${e.message}`); }
}
const variants = localGenerate(ctx, n);
res.json({ variants, kind, mock: true, source: 'templates' });
← a85f5b2 Channels publishing engine: FB/IG/TikTok/YouTube adapters (r
·
back to Marketing Command Center
·
Channels OAuth connect machinery: per-platform connect/callb a57c5b8 →