feat: Google Gemini image generation for project covers
Schema:
- SystemSettings: geminiApiKey, geminiModel, imageProvider fields
- imageProvider: "dalle" (default) or "gemini"
Gemini Client (packages/api/src/gemini-client.ts):
- Direct HTTP call to Gemini REST API with responseModalities: [TEXT, IMAGE]
- Returns base64 data URL
- Error parsing with user-friendly messages
Router (project.ts):
- generateCover: routes to DALL-E or Gemini based on imageProvider setting
- New isImageGenConfigured query returning { configured, provider }
Admin UI (SystemSettingsClient.tsx):
- "Image Generation" section with provider radio buttons (DALL-E / Gemini)
- Conditional fields: DALL-E config or Gemini API key + model
- Separate save button for image settings
Security:
- geminiApiKey sanitized in audit logs (SENSITIVE_FIELDS)
- API key stored server-side only, never sent to client
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -12,6 +12,7 @@ const SENSITIVE_FIELDS = new Set([
|
||||
"smtpPassword",
|
||||
"azureDalleApiKey",
|
||||
"anonymizationSeed",
|
||||
"geminiApiKey",
|
||||
]);
|
||||
|
||||
export const settingsRouter = createTRPCRouter({
|
||||
@@ -55,6 +56,11 @@ export const settingsRouter = createTRPCRouter({
|
||||
azureDalleDeployment: settings?.azureDalleDeployment ?? null,
|
||||
azureDalleEndpoint: settings?.azureDalleEndpoint ?? null,
|
||||
hasDalleApiKey: !!settings?.azureDalleApiKey,
|
||||
// Gemini
|
||||
geminiModel: settings?.geminiModel ?? "gemini-2.0-flash-preview-image-generation",
|
||||
hasGeminiApiKey: !!settings?.geminiApiKey,
|
||||
// Image provider
|
||||
imageProvider: settings?.imageProvider ?? "dalle",
|
||||
// Vacation defaults
|
||||
vacationDefaultDays: settings?.vacationDefaultDays ?? 28,
|
||||
// Timeline
|
||||
@@ -103,6 +109,11 @@ export const settingsRouter = createTRPCRouter({
|
||||
azureDalleDeployment: z.string().optional(),
|
||||
azureDalleEndpoint: z.string().url().optional().or(z.literal("")),
|
||||
azureDalleApiKey: z.string().optional(),
|
||||
// Gemini image generation
|
||||
geminiApiKey: z.string().optional(),
|
||||
geminiModel: z.string().optional(),
|
||||
// Image provider selection
|
||||
imageProvider: z.enum(["dalle", "gemini"]).optional(),
|
||||
// Vacation
|
||||
vacationDefaultDays: z.number().int().min(0).max(365).optional(),
|
||||
// Timeline
|
||||
@@ -155,6 +166,14 @@ export const settingsRouter = createTRPCRouter({
|
||||
data.azureDalleEndpoint = input.azureDalleEndpoint || null;
|
||||
if (input.azureDalleApiKey !== undefined)
|
||||
data.azureDalleApiKey = input.azureDalleApiKey || null;
|
||||
// Gemini
|
||||
if (input.geminiApiKey !== undefined)
|
||||
data.geminiApiKey = input.geminiApiKey || null;
|
||||
if (input.geminiModel !== undefined)
|
||||
data.geminiModel = input.geminiModel || null;
|
||||
// Image provider
|
||||
if (input.imageProvider !== undefined)
|
||||
data.imageProvider = input.imageProvider;
|
||||
// Vacation
|
||||
if (input.vacationDefaultDays !== undefined) data.vacationDefaultDays = input.vacationDefaultDays;
|
||||
// Timeline
|
||||
|
||||
Reference in New Issue
Block a user