mrbeniwal commited on
Commit
c737c0b
·
1 Parent(s): 9a4e7bf

Update src files with improvements and fixes

Browse files
src/ai/flows/auto-select-image-model.ts CHANGED
@@ -4,14 +4,16 @@
4
  * @fileOverview Smart routing flow for automatically selecting the best image generation provider.
5
  *
6
  * This flow:
7
- * 1. Checks which API keys are valid/present
8
- * 2. If smart routing is enabled and multiple providers are available, uses AI to select the best one
9
  * 3. Falls back to available providers when some are unavailable
10
  * 4. Returns the selected provider along with available options
 
 
 
11
  */
12
 
13
- import { ai } from '@/ai/genkit';
14
- import { z } from 'genkit';
15
  import {
16
  type ImageProvider,
17
  PROVIDER_CONFIGS
@@ -31,9 +33,9 @@ const ClientApiKeysSchema = z.object({
31
  // Input schema for the smart router
32
  const SmartRouterInputSchema = z.object({
33
  prompt: z.string().describe('The user prompt for image generation.'),
34
- smartRoutingEnabled: z.boolean().describe('Whether AI-based smart routing is enabled.'),
35
  preferredProvider: ProviderEnum.optional().describe('User-preferred provider when smart routing is disabled.'),
36
- clientApiKeys: ClientApiKeysSchema.describe('Optional API keys provided by user from client-side.'),
37
  });
38
  export type SmartRouterInput = z.infer<typeof SmartRouterInputSchema>;
39
 
@@ -45,80 +47,96 @@ const SmartRouterOutputSchema = z.object({
45
  });
46
  export type SmartRouterOutput = z.infer<typeof SmartRouterOutputSchema>;
47
 
48
- // AI prompt for selecting the best provider based on the user's prompt
49
- const selectProviderPrompt = ai.definePrompt({
50
- name: 'selectProviderPrompt',
51
- input: {
52
- schema: z.object({
53
- prompt: z.string(),
54
- availableProviders: z.array(z.string()),
55
- }),
56
- },
57
- output: {
58
- schema: z.object({
59
- selectedProvider: ProviderEnum,
60
- reason: z.string(),
61
- }),
62
- },
63
- prompt: `You are an AI assistant that selects the best image generation provider based on a user's prompt.
64
-
65
- Available providers: {{{availableProviders}}}
66
-
67
- Provider strengths:
68
- - openai (DALL-E 3): Best for photorealistic images, product photography, professional headshots, realistic scenes, and detailed realistic artwork.
69
- - google (Gemini): Best for general purpose, quick generation, simple images, icons, and versatile everyday needs.
70
- - qwen (Qwen Image Plus): Best for artistic, creative, stylized, anime, illustration, digital art, fantasy, and visually striking artistic images.
71
-
72
- Analyze the prompt and select the BEST provider from the AVAILABLE providers list.
73
- If a provider is not in the available list, DO NOT select it.
74
-
75
- User's prompt: {{{prompt}}}
76
-
77
- Respond with:
78
- 1. selectedProvider: The name of the best provider (must be one from the available list)
79
- 2. reason: A brief explanation of why this provider is best for this prompt`,
80
- });
81
-
82
  /**
83
- * Check if a provider has a valid key either from client or env
84
  */
85
- function hasValidKey(provider: ImageProvider, clientKeys?: { openai?: string; google?: string; qwen?: string }): boolean {
86
  const clientKey = clientKeys?.[provider];
87
- if (clientKey && clientKey.trim().length > 0) {
88
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
- return false; // Server-side env check is done separately
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
  /**
94
  * Smart router for selecting the best image generation provider.
 
95
  */
96
  export async function smartSelectProvider(input: SmartRouterInput): Promise<SmartRouterOutput> {
97
  // 1. Get available providers from environment
98
  const envAvailableProviders = await getAvailableProviders();
99
 
100
- // 2. Also check client-provided keys
101
  const clientKeys = input.clientApiKeys;
102
  const allProviders: ImageProvider[] = ['openai', 'google', 'qwen'];
103
 
104
  // Combine: provider is available if it has env key OR valid client key
105
  const availableProviders = allProviders.filter(provider =>
106
- envAvailableProviders.includes(provider) || hasValidKey(provider, clientKeys)
107
  );
108
 
109
- // 2. Handle case where no providers are available
110
  if (availableProviders.length === 0) {
111
  return {
112
  selectedProvider: 'google', // Default, but won't work
113
  availableProviders: [],
114
- reason: 'No API keys are configured. Please add at least one API key to your environment.',
115
  };
116
  }
117
 
118
  // Cast to the enum type for Zod compatibility
119
  const typedAvailableProviders = availableProviders as ('openai' | 'google' | 'qwen')[];
120
 
121
- // 3. If only one provider is available, use it
122
  if (typedAvailableProviders.length === 1) {
123
  const provider = typedAvailableProviders[0];
124
  return {
@@ -128,7 +146,7 @@ export async function smartSelectProvider(input: SmartRouterInput): Promise<Smar
128
  };
129
  }
130
 
131
- // 4. If smart routing is disabled and user has a preference
132
  if (!input.smartRoutingEnabled && input.preferredProvider) {
133
  // Check if preferred provider is available
134
  if (typedAvailableProviders.includes(input.preferredProvider)) {
@@ -140,70 +158,22 @@ export async function smartSelectProvider(input: SmartRouterInput): Promise<Smar
140
  } else {
141
  // Preferred provider not available, fall back to first available
142
  const fallback = typedAvailableProviders[0];
 
 
143
  return {
144
  selectedProvider: fallback,
145
  availableProviders: typedAvailableProviders,
146
- reason: `${PROVIDER_CONFIGS[input.preferredProvider].displayName} is not available. Falling back to ${PROVIDER_CONFIGS[fallback].displayName}.`,
147
- };
148
- }
149
- }
150
-
151
- // 5. Smart routing enabled - use AI to select the best provider
152
- try {
153
- const { output } = await selectProviderPrompt({
154
- prompt: input.prompt,
155
- availableProviders: typedAvailableProviders,
156
- });
157
-
158
- if (output && typedAvailableProviders.includes(output.selectedProvider)) {
159
- return {
160
- selectedProvider: output.selectedProvider,
161
- availableProviders: typedAvailableProviders,
162
- reason: output.reason,
163
  };
164
  }
165
- } catch (error) {
166
- console.error('[SmartRouter] AI selection failed, using fallback:', error);
167
  }
168
 
169
- // 6. Fallback: Use keyword matching if AI fails
170
- const prompt = input.prompt.toLowerCase();
171
 
172
- // Check for photorealistic keywords -> OpenAI
173
- const photorealisticKeywords = ['photo', 'realistic', 'professional', 'headshot', 'product', 'portrait'];
174
- if (typedAvailableProviders.includes('openai') &&
175
- photorealisticKeywords.some(kw => prompt.includes(kw))) {
176
- return {
177
- selectedProvider: 'openai',
178
- availableProviders: typedAvailableProviders,
179
- reason: 'Detected photorealistic content in prompt, using OpenAI DALL-E.',
180
- };
181
- }
182
-
183
- // Check for artistic keywords -> Qwen
184
- const artisticKeywords = ['artistic', 'fantasy', 'abstract', 'stylized', 'watercolor', 'painting', 'illustration', 'anime', 'digital art'];
185
- if (typedAvailableProviders.includes('qwen') &&
186
- artisticKeywords.some(kw => prompt.includes(kw))) {
187
- return {
188
- selectedProvider: 'qwen',
189
- availableProviders: typedAvailableProviders,
190
- reason: 'Detected artistic content in prompt, using Qwen Image Plus.',
191
- };
192
- }
193
-
194
- // Default to Google for general purpose
195
- if (typedAvailableProviders.includes('google')) {
196
- return {
197
- selectedProvider: 'google',
198
- availableProviders: typedAvailableProviders,
199
- reason: 'Using Google Gemini for general purpose image generation.',
200
- };
201
- }
202
-
203
- // Final fallback - use first available
204
  return {
205
- selectedProvider: typedAvailableProviders[0],
206
  availableProviders: typedAvailableProviders,
207
- reason: `Using ${PROVIDER_CONFIGS[typedAvailableProviders[0]].displayName} as default.`,
208
  };
209
  }
 
4
  * @fileOverview Smart routing flow for automatically selecting the best image generation provider.
5
  *
6
  * This flow:
7
+ * 1. Checks which API keys are valid/present (from client or env)
8
+ * 2. Uses keyword matching to select the best provider for the prompt
9
  * 3. Falls back to available providers when some are unavailable
10
  * 4. Returns the selected provider along with available options
11
+ *
12
+ * NOTE: This version uses keyword matching only (no AI/Genkit) to support
13
+ * client-only API key mode without requiring server-side env configuration.
14
  */
15
 
16
+ import { z } from 'zod';
 
17
  import {
18
  type ImageProvider,
19
  PROVIDER_CONFIGS
 
33
  // Input schema for the smart router
34
  const SmartRouterInputSchema = z.object({
35
  prompt: z.string().describe('The user prompt for image generation.'),
36
+ smartRoutingEnabled: z.boolean().describe('Whether smart routing is enabled.'),
37
  preferredProvider: ProviderEnum.optional().describe('User-preferred provider when smart routing is disabled.'),
38
+ clientApiKeys: ClientApiKeysSchema.describe('API keys provided by user from client-side.'),
39
  });
40
  export type SmartRouterInput = z.infer<typeof SmartRouterInputSchema>;
41
 
 
47
  });
48
  export type SmartRouterOutput = z.infer<typeof SmartRouterOutputSchema>;
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  /**
51
+ * Check if a provider has a valid key from client
52
  */
53
+ function hasValidClientKey(provider: ImageProvider, clientKeys?: { openai?: string; google?: string; qwen?: string }): boolean {
54
  const clientKey = clientKeys?.[provider];
55
+ return !!(clientKey && clientKey.trim().length > 0);
56
+ }
57
+
58
+ /**
59
+ * Keyword-based provider selection
60
+ */
61
+ function selectProviderByKeywords(
62
+ prompt: string,
63
+ availableProviders: ('openai' | 'google' | 'qwen')[]
64
+ ): { provider: 'openai' | 'google' | 'qwen'; reason: string } {
65
+ const lowerPrompt = prompt.toLowerCase();
66
+
67
+ // Check for photorealistic keywords -> OpenAI
68
+ const photorealisticKeywords = [
69
+ 'photo', 'photograph', 'realistic', 'professional', 'headshot',
70
+ 'product', 'portrait', 'camera', 'hdr', 'stock photo', 'documentary'
71
+ ];
72
+ if (availableProviders.includes('openai') &&
73
+ photorealisticKeywords.some(kw => lowerPrompt.includes(kw))) {
74
+ return {
75
+ provider: 'openai',
76
+ reason: 'Detected photorealistic content - using OpenAI DALL-E for best results.',
77
+ };
78
  }
79
+
80
+ // Check for artistic keywords -> Qwen
81
+ const artisticKeywords = [
82
+ 'artistic', 'fantasy', 'abstract', 'stylized', 'watercolor',
83
+ 'painting', 'illustration', 'anime', 'digital art', 'concept art',
84
+ 'cartoon', 'manga', 'sketch', 'oil painting', 'impressionist',
85
+ 'surreal', 'dreamy', 'magical', 'ethereal'
86
+ ];
87
+ if (availableProviders.includes('qwen') &&
88
+ artisticKeywords.some(kw => lowerPrompt.includes(kw))) {
89
+ return {
90
+ provider: 'qwen',
91
+ reason: 'Detected artistic/creative content - using Qwen for stylized results.',
92
+ };
93
+ }
94
+
95
+ // Default to Google for general purpose
96
+ if (availableProviders.includes('google')) {
97
+ return {
98
+ provider: 'google',
99
+ reason: 'Using Google Gemini for general purpose image generation.',
100
+ };
101
+ }
102
+
103
+ // Final fallback - use first available
104
+ return {
105
+ provider: availableProviders[0],
106
+ reason: `Using ${PROVIDER_CONFIGS[availableProviders[0]].displayName} as default.`,
107
+ };
108
  }
109
 
110
  /**
111
  * Smart router for selecting the best image generation provider.
112
+ * Uses keyword matching for selection (no external AI calls).
113
  */
114
  export async function smartSelectProvider(input: SmartRouterInput): Promise<SmartRouterOutput> {
115
  // 1. Get available providers from environment
116
  const envAvailableProviders = await getAvailableProviders();
117
 
118
+ // 2. Check client-provided keys
119
  const clientKeys = input.clientApiKeys;
120
  const allProviders: ImageProvider[] = ['openai', 'google', 'qwen'];
121
 
122
  // Combine: provider is available if it has env key OR valid client key
123
  const availableProviders = allProviders.filter(provider =>
124
+ envAvailableProviders.includes(provider) || hasValidClientKey(provider, clientKeys)
125
  );
126
 
127
+ // 3. Handle case where no providers are available
128
  if (availableProviders.length === 0) {
129
  return {
130
  selectedProvider: 'google', // Default, but won't work
131
  availableProviders: [],
132
+ reason: 'No API keys are configured. Please add at least one API key in Settings.',
133
  };
134
  }
135
 
136
  // Cast to the enum type for Zod compatibility
137
  const typedAvailableProviders = availableProviders as ('openai' | 'google' | 'qwen')[];
138
 
139
+ // 4. If only one provider is available, use it
140
  if (typedAvailableProviders.length === 1) {
141
  const provider = typedAvailableProviders[0];
142
  return {
 
146
  };
147
  }
148
 
149
+ // 5. If smart routing is disabled and user has a preference
150
  if (!input.smartRoutingEnabled && input.preferredProvider) {
151
  // Check if preferred provider is available
152
  if (typedAvailableProviders.includes(input.preferredProvider)) {
 
158
  } else {
159
  // Preferred provider not available, fall back to first available
160
  const fallback = typedAvailableProviders[0];
161
+ const preferredConfig = input.preferredProvider ? PROVIDER_CONFIGS[input.preferredProvider] : null;
162
+ const preferredDisplayName = preferredConfig?.displayName || input.preferredProvider || 'Selected provider';
163
  return {
164
  selectedProvider: fallback,
165
  availableProviders: typedAvailableProviders,
166
+ reason: `${preferredDisplayName} is not available. Falling back to ${PROVIDER_CONFIGS[fallback].displayName}.`,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  };
168
  }
 
 
169
  }
170
 
171
+ // 6. Smart routing enabled - use keyword matching to select appropriate provider
172
+ const selection = selectProviderByKeywords(input.prompt, typedAvailableProviders);
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  return {
175
+ selectedProvider: selection.provider,
176
  availableProviders: typedAvailableProviders,
177
+ reason: selection.reason,
178
  };
179
  }
src/ai/flows/enhance-user-prompt.ts CHANGED
@@ -1,50 +1,94 @@
1
  'use server';
2
 
3
  /**
4
- * @fileOverview This file defines a Genkit flow for enhancing user-provided prompts using the Gemini AI model.
5
- *
6
- * The flow takes a user prompt as input and returns an enhanced version of the prompt.
7
- * It exports:
8
- * - enhanceUserPrompt: The main function to call to enhance a prompt.
9
- * - EnhanceUserPromptInput: The input type for the enhanceUserPrompt function.
10
- * - EnhanceUserPromptOutput: The output type for the enhanceUserPrompt function.
11
  */
12
 
13
- import {ai} from '@/ai/genkit';
14
- import {z} from 'genkit';
15
 
16
  const EnhanceUserPromptInputSchema = z.object({
17
  prompt: z.string().describe('The user-provided prompt to enhance.'),
 
18
  });
19
  export type EnhanceUserPromptInput = z.infer<typeof EnhanceUserPromptInputSchema>;
20
 
21
  const EnhanceUserPromptOutputSchema = z.object({
22
- enhancedPrompt: z.string().describe('The AI-enhanced version of the prompt.'),
23
  });
24
  export type EnhanceUserPromptOutput = z.infer<typeof EnhanceUserPromptOutputSchema>;
25
 
 
 
 
 
26
  export async function enhanceUserPrompt(input: EnhanceUserPromptInput): Promise<EnhanceUserPromptOutput> {
27
- return enhanceUserPromptFlow(input);
28
- }
29
 
30
- const enhanceUserPromptPrompt = ai.definePrompt({
31
- name: 'enhanceUserPromptPrompt',
32
- input: {schema: EnhanceUserPromptInputSchema},
33
- output: {schema: EnhanceUserPromptOutputSchema},
34
- prompt: `You are an AI prompt enhancer. You will take the user's prompt and enhance it to be more descriptive and specific, in order to get better results from image generation models.
35
 
36
- User Prompt: {{{prompt}}}`,
37
- model: 'googleai/gemini-2.5-pro',
38
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- const enhanceUserPromptFlow = ai.defineFlow(
41
- {
42
- name: 'enhanceUserPromptFlow',
43
- inputSchema: EnhanceUserPromptInputSchema,
44
- outputSchema: EnhanceUserPromptOutputSchema,
45
- },
46
- async input => {
47
- const {output} = await enhanceUserPromptPrompt(input);
48
- return output!;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
- );
 
1
  'use server';
2
 
3
  /**
4
+ * @fileOverview Prompt enhancement flow that optionally uses Google AI to enhance prompts.
5
+ *
6
+ * If a Google API key is available (from client or env), it will use AI to enhance the prompt.
7
+ * Otherwise, it returns the original prompt unchanged.
8
+ *
9
+ * This supports client-only API key mode without requiring server-side env configuration.
 
10
  */
11
 
12
+ import { z } from 'zod';
 
13
 
14
  const EnhanceUserPromptInputSchema = z.object({
15
  prompt: z.string().describe('The user-provided prompt to enhance.'),
16
+ googleApiKey: z.string().optional().describe('Optional Google API key from client.'),
17
  });
18
  export type EnhanceUserPromptInput = z.infer<typeof EnhanceUserPromptInputSchema>;
19
 
20
  const EnhanceUserPromptOutputSchema = z.object({
21
+ enhancedPrompt: z.string().describe('The enhanced version of the prompt.'),
22
  });
23
  export type EnhanceUserPromptOutput = z.infer<typeof EnhanceUserPromptOutputSchema>;
24
 
25
+ /**
26
+ * Enhance a user prompt using Google AI if available.
27
+ * Falls back to returning the original prompt if no API key is available.
28
+ */
29
  export async function enhanceUserPrompt(input: EnhanceUserPromptInput): Promise<EnhanceUserPromptOutput> {
30
+ // Use provided API key or fall back to environment variable
31
+ const apiKey = input.googleApiKey || process.env.GOOGLE_GENAI_API_KEY;
32
 
33
+ // If no API key available, return original prompt
34
+ if (!apiKey) {
35
+ console.log('[EnhancePrompt] No Google API key available, skipping enhancement');
36
+ return { enhancedPrompt: input.prompt };
37
+ }
38
 
39
+ try {
40
+ // Use Google's REST API directly to enhance the prompt
41
+ const response = await fetch(
42
+ `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
43
+ {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ body: JSON.stringify({
49
+ contents: [
50
+ {
51
+ parts: [
52
+ {
53
+ text: `You are an AI prompt enhancer for image generation. Take the user's prompt and enhance it to be more descriptive and specific, to get better results from image generation models.
54
+
55
+ IMPORTANT:
56
+ - Only return the enhanced prompt, nothing else.
57
+ - Keep the core subject and intent the same.
58
+ - Add details about lighting, style, composition, and mood.
59
+ - Keep it concise (under 200 words).
60
 
61
+ User's prompt: ${input.prompt}
62
+
63
+ Enhanced prompt:`,
64
+ },
65
+ ],
66
+ },
67
+ ],
68
+ generationConfig: {
69
+ temperature: 0.7,
70
+ maxOutputTokens: 300,
71
+ },
72
+ }),
73
+ }
74
+ );
75
+
76
+ if (!response.ok) {
77
+ console.warn('[EnhancePrompt] API call failed, using original prompt');
78
+ return { enhancedPrompt: input.prompt };
79
+ }
80
+
81
+ const data = await response.json();
82
+ const enhancedText = data.candidates?.[0]?.content?.parts?.[0]?.text;
83
+
84
+ if (enhancedText && enhancedText.trim().length > 0) {
85
+ console.log('[EnhancePrompt] Successfully enhanced prompt');
86
+ return { enhancedPrompt: enhancedText.trim() };
87
+ }
88
+
89
+ return { enhancedPrompt: input.prompt };
90
+ } catch (error) {
91
+ console.warn('[EnhancePrompt] Failed to enhance prompt, using original:', error);
92
+ return { enhancedPrompt: input.prompt };
93
  }
94
+ }
src/ai/flows/generate-image-google.ts CHANGED
@@ -2,14 +2,14 @@
2
 
3
  /**
4
  * @fileOverview Google Gemini image generation flow.
5
- * Uses the Genkit AI framework with Google's Gemini model for image generation.
6
  */
7
 
8
- import { ai } from '@/ai/genkit';
9
- import { z } from 'genkit';
10
 
11
  const GenerateImageGoogleInputSchema = z.object({
12
  prompt: z.string().describe('The text prompt to generate an image from.'),
 
13
  });
14
  export type GenerateImageGoogleInput = z.infer<typeof GenerateImageGoogleInputSchema>;
15
 
@@ -21,37 +21,87 @@ const GenerateImageGoogleOutputSchema = z.object({
21
  export type GenerateImageGoogleOutput = z.infer<typeof GenerateImageGoogleOutputSchema>;
22
 
23
  export async function generateImageGoogle(input: GenerateImageGoogleInput): Promise<GenerateImageGoogleOutput> {
24
- return generateImageGoogleFlow(input);
25
- }
26
 
27
- const generateImageGoogleFlow = ai.defineFlow(
28
- {
29
- name: 'generateImageGoogleFlow',
30
- inputSchema: GenerateImageGoogleInputSchema,
31
- outputSchema: GenerateImageGoogleOutputSchema,
32
- },
33
- async (input) => {
34
- const startTime = Date.now();
35
-
36
- const { media } = await ai.generate({
37
- model: 'googleai/gemini-2.0-flash-exp',
38
- prompt: input.prompt,
39
- config: {
40
- responseModalities: ['IMAGE', 'TEXT'],
41
- },
42
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
 
44
  const endTime = Date.now();
45
  console.log(`[Google] Image generation took ${endTime - startTime}ms`);
46
 
47
- if (!media?.url) {
48
- throw new Error('Google image generation failed - no image returned');
 
 
 
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  return {
52
- url: media.url,
53
  prompt: input.prompt,
54
  provider: 'google' as const,
55
  };
 
 
 
56
  }
57
- );
 
2
 
3
  /**
4
  * @fileOverview Google Gemini image generation flow.
5
+ * Uses Google's Generative AI API directly to support client-provided API keys.
6
  */
7
 
8
+ import { z } from 'zod';
 
9
 
10
  const GenerateImageGoogleInputSchema = z.object({
11
  prompt: z.string().describe('The text prompt to generate an image from.'),
12
+ apiKey: z.string().optional().describe('Optional API key provided by user.'),
13
  });
14
  export type GenerateImageGoogleInput = z.infer<typeof GenerateImageGoogleInputSchema>;
15
 
 
21
  export type GenerateImageGoogleOutput = z.infer<typeof GenerateImageGoogleOutputSchema>;
22
 
23
  export async function generateImageGoogle(input: GenerateImageGoogleInput): Promise<GenerateImageGoogleOutput> {
24
+ // Use provided API key or fall back to environment variable
25
+ const apiKey = input.apiKey || process.env.GOOGLE_GENAI_API_KEY;
26
 
27
+ if (!apiKey) {
28
+ throw new Error('Google API key is not configured. Please add your API key in Settings.');
29
+ }
30
+
31
+ const startTime = Date.now();
32
+
33
+ try {
34
+ // Use Google's REST API directly to support client-provided keys
35
+ const response = await fetch(
36
+ `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`,
37
+ {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ },
42
+ body: JSON.stringify({
43
+ contents: [
44
+ {
45
+ parts: [
46
+ {
47
+ text: input.prompt,
48
+ },
49
+ ],
50
+ },
51
+ ],
52
+ generationConfig: {
53
+ responseModalities: ['IMAGE', 'TEXT'],
54
+ },
55
+ }),
56
+ }
57
+ );
58
+
59
+ if (!response.ok) {
60
+ const error = await response.json().catch(() => ({}));
61
+ const errorMessage = error.error?.message || response.statusText;
62
+ throw new Error(`Google API error: ${errorMessage}`);
63
+ }
64
 
65
+ const data = await response.json();
66
  const endTime = Date.now();
67
  console.log(`[Google] Image generation took ${endTime - startTime}ms`);
68
 
69
+ // Extract image from response
70
+ // The response structure: { candidates: [{ content: { parts: [{ inlineData: { mimeType, data } }] } }] }
71
+ const candidates = data.candidates;
72
+ if (!candidates || candidates.length === 0) {
73
+ throw new Error('Google image generation failed - no candidates returned');
74
  }
75
 
76
+ const parts = candidates[0]?.content?.parts;
77
+ if (!parts || parts.length === 0) {
78
+ throw new Error('Google image generation failed - no parts in response');
79
+ }
80
+
81
+ // Find the image part
82
+ const imagePart = parts.find((part: any) => part.inlineData?.mimeType?.startsWith('image/'));
83
+
84
+ if (!imagePart?.inlineData?.data) {
85
+ // Check if we got text instead of image
86
+ const textPart = parts.find((part: any) => part.text);
87
+ if (textPart) {
88
+ console.error('[Google] Received text instead of image:', textPart.text);
89
+ }
90
+ throw new Error('Google image generation failed - no image data in response');
91
+ }
92
+
93
+ // Convert to data URI
94
+ const mimeType = imagePart.inlineData.mimeType;
95
+ const base64Data = imagePart.inlineData.data;
96
+ const dataUri = `data:${mimeType};base64,${base64Data}`;
97
+
98
  return {
99
+ url: dataUri,
100
  prompt: input.prompt,
101
  provider: 'google' as const,
102
  };
103
+ } catch (error) {
104
+ console.error('[Google] Generation error:', error);
105
+ throw error;
106
  }
107
+ }
src/ai/flows/generate-image.ts CHANGED
@@ -54,8 +54,9 @@ export async function generateImage(input: GenerateImageInput): Promise<Generate
54
 
55
  case 'google':
56
  default:
57
- // Google uses Genkit which reads from process.env directly
58
- // Client-provided keys for Google would require a different approach
59
- return generateImageGoogle({ prompt: input.prompt });
 
60
  }
61
  }
 
54
 
55
  case 'google':
56
  default:
57
+ return generateImageGoogle({
58
+ prompt: input.prompt,
59
+ apiKey: input.apiKeys?.google,
60
+ });
61
  }
62
  }
src/app/page.tsx CHANGED
@@ -73,10 +73,13 @@ export default function DashboardPage() {
73
  description: routerResult.reason,
74
  });
75
 
76
- // 4. Enhance prompt
77
  let generationPrompt = values.prompt;
78
  try {
79
- const enhanced = await enhanceUserPrompt({ prompt: values.prompt });
 
 
 
80
  generationPrompt = enhanced.enhancedPrompt;
81
  } catch (enhanceError) {
82
  console.warn('[Page] Prompt enhancement failed, using original prompt:', enhanceError);
 
73
  description: routerResult.reason,
74
  });
75
 
76
+ // 4. Enhance prompt (uses Google API if available)
77
  let generationPrompt = values.prompt;
78
  try {
79
+ const enhanced = await enhanceUserPrompt({
80
+ prompt: values.prompt,
81
+ googleApiKey: apiKeys.google || undefined,
82
+ });
83
  generationPrompt = enhanced.enhancedPrompt;
84
  } catch (enhanceError) {
85
  console.warn('[Page] Prompt enhancement failed, using original prompt:', enhanceError);
src/lib/api-config.ts CHANGED
@@ -24,7 +24,7 @@ export const PROVIDER_CONFIGS: Record<ImageProvider, ProviderConfig> = {
24
  google: {
25
  name: 'google',
26
  displayName: 'Google Gemini',
27
- envKey: 'GEMINI_API_KEY',
28
  bestFor: ['general purpose', 'quick', 'versatile', 'simple', 'icons'],
29
  description: 'Best for general purpose and quick generation',
30
  },
 
24
  google: {
25
  name: 'google',
26
  displayName: 'Google Gemini',
27
+ envKey: 'GOOGLE_GENAI_API_KEY',
28
  bestFor: ['general purpose', 'quick', 'versatile', 'simple', 'icons'],
29
  description: 'Best for general purpose and quick generation',
30
  },