Spaces:
Runtime error
Runtime error
Update src files with improvements and fixes
Browse files- src/ai/flows/auto-select-image-model.ts +76 -106
- src/ai/flows/enhance-user-prompt.ts +74 -30
- src/ai/flows/generate-image-google.ts +75 -25
- src/ai/flows/generate-image.ts +4 -3
- src/app/page.tsx +5 -2
- src/lib/api-config.ts +1 -1
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.
|
| 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 {
|
| 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
|
| 35 |
preferredProvider: ProviderEnum.optional().describe('User-preferred provider when smart routing is disabled.'),
|
| 36 |
-
clientApiKeys: ClientApiKeysSchema.describe('
|
| 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
|
| 84 |
*/
|
| 85 |
-
function
|
| 86 |
const clientKey = clientKeys?.[provider];
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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) ||
|
| 107 |
);
|
| 108 |
|
| 109 |
-
//
|
| 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
|
| 115 |
};
|
| 116 |
}
|
| 117 |
|
| 118 |
// Cast to the enum type for Zod compatibility
|
| 119 |
const typedAvailableProviders = availableProviders as ('openai' | 'google' | 'qwen')[];
|
| 120 |
|
| 121 |
-
//
|
| 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 |
-
//
|
| 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: `${
|
| 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.
|
| 170 |
-
const
|
| 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:
|
| 206 |
availableProviders: typedAvailableProviders,
|
| 207 |
-
reason:
|
| 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
|
| 5 |
-
*
|
| 6 |
-
*
|
| 7 |
-
*
|
| 8 |
-
*
|
| 9 |
-
*
|
| 10 |
-
* - EnhanceUserPromptOutput: The output type for the enhanceUserPrompt function.
|
| 11 |
*/
|
| 12 |
|
| 13 |
-
import {
|
| 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
|
| 23 |
});
|
| 24 |
export type EnhanceUserPromptOutput = z.infer<typeof EnhanceUserPromptOutputSchema>;
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
export async function enhanceUserPrompt(input: EnhanceUserPromptInput): Promise<EnhanceUserPromptOutput> {
|
| 27 |
-
|
| 28 |
-
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 6 |
*/
|
| 7 |
|
| 8 |
-
import {
|
| 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 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
|
|
|
| 44 |
const endTime = Date.now();
|
| 45 |
console.log(`[Google] Image generation took ${endTime - startTime}ms`);
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
return {
|
| 52 |
-
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 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
| 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({
|
|
|
|
|
|
|
|
|
|
| 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: '
|
| 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 |
},
|