multi_image / src /ai /flows /auto-select-image-model.ts
mrbeniwal's picture
Update src files with improvements and fixes
c737c0b
'use server';
/**
* @fileOverview Smart routing flow for automatically selecting the best image generation provider.
*
* This flow:
* 1. Checks which API keys are valid/present (from client or env)
* 2. Uses keyword matching to select the best provider for the prompt
* 3. Falls back to available providers when some are unavailable
* 4. Returns the selected provider along with available options
*
* NOTE: This version uses keyword matching only (no AI/Genkit) to support
* client-only API key mode without requiring server-side env configuration.
*/
import { z } from 'zod';
import {
type ImageProvider,
PROVIDER_CONFIGS
} from '@/lib/api-config';
import { getAvailableProviders } from '@/lib/api-validation';
// Shared provider enum for consistent typing
const ProviderEnum = z.enum(['openai', 'google', 'qwen']);
// Client API keys schema
const ClientApiKeysSchema = z.object({
openai: z.string().optional(),
google: z.string().optional(),
qwen: z.string().optional(),
}).optional();
// Input schema for the smart router
const SmartRouterInputSchema = z.object({
prompt: z.string().describe('The user prompt for image generation.'),
smartRoutingEnabled: z.boolean().describe('Whether smart routing is enabled.'),
preferredProvider: ProviderEnum.optional().describe('User-preferred provider when smart routing is disabled.'),
clientApiKeys: ClientApiKeysSchema.describe('API keys provided by user from client-side.'),
});
export type SmartRouterInput = z.infer<typeof SmartRouterInputSchema>;
// Output schema for the smart router
const SmartRouterOutputSchema = z.object({
selectedProvider: ProviderEnum.describe('The selected image generation provider.'),
availableProviders: z.array(ProviderEnum).describe('List of available providers.'),
reason: z.string().describe('Reason for the selection.'),
});
export type SmartRouterOutput = z.infer<typeof SmartRouterOutputSchema>;
/**
* Check if a provider has a valid key from client
*/
function hasValidClientKey(provider: ImageProvider, clientKeys?: { openai?: string; google?: string; qwen?: string }): boolean {
const clientKey = clientKeys?.[provider];
return !!(clientKey && clientKey.trim().length > 0);
}
/**
* Keyword-based provider selection
*/
function selectProviderByKeywords(
prompt: string,
availableProviders: ('openai' | 'google' | 'qwen')[]
): { provider: 'openai' | 'google' | 'qwen'; reason: string } {
const lowerPrompt = prompt.toLowerCase();
// Check for photorealistic keywords -> OpenAI
const photorealisticKeywords = [
'photo', 'photograph', 'realistic', 'professional', 'headshot',
'product', 'portrait', 'camera', 'hdr', 'stock photo', 'documentary'
];
if (availableProviders.includes('openai') &&
photorealisticKeywords.some(kw => lowerPrompt.includes(kw))) {
return {
provider: 'openai',
reason: 'Detected photorealistic content - using OpenAI DALL-E for best results.',
};
}
// Check for artistic keywords -> Qwen
const artisticKeywords = [
'artistic', 'fantasy', 'abstract', 'stylized', 'watercolor',
'painting', 'illustration', 'anime', 'digital art', 'concept art',
'cartoon', 'manga', 'sketch', 'oil painting', 'impressionist',
'surreal', 'dreamy', 'magical', 'ethereal'
];
if (availableProviders.includes('qwen') &&
artisticKeywords.some(kw => lowerPrompt.includes(kw))) {
return {
provider: 'qwen',
reason: 'Detected artistic/creative content - using Qwen for stylized results.',
};
}
// Default to Google for general purpose
if (availableProviders.includes('google')) {
return {
provider: 'google',
reason: 'Using Google Gemini for general purpose image generation.',
};
}
// Final fallback - use first available
return {
provider: availableProviders[0],
reason: `Using ${PROVIDER_CONFIGS[availableProviders[0]].displayName} as default.`,
};
}
/**
* Smart router for selecting the best image generation provider.
* Uses keyword matching for selection (no external AI calls).
*/
export async function smartSelectProvider(input: SmartRouterInput): Promise<SmartRouterOutput> {
// 1. Get available providers from environment
const envAvailableProviders = await getAvailableProviders();
// 2. Check client-provided keys
const clientKeys = input.clientApiKeys;
const allProviders: ImageProvider[] = ['openai', 'google', 'qwen'];
// Combine: provider is available if it has env key OR valid client key
const availableProviders = allProviders.filter(provider =>
envAvailableProviders.includes(provider) || hasValidClientKey(provider, clientKeys)
);
// 3. Handle case where no providers are available
if (availableProviders.length === 0) {
return {
selectedProvider: 'google', // Default, but won't work
availableProviders: [],
reason: 'No API keys are configured. Please add at least one API key in Settings.',
};
}
// Cast to the enum type for Zod compatibility
const typedAvailableProviders = availableProviders as ('openai' | 'google' | 'qwen')[];
// 4. If only one provider is available, use it
if (typedAvailableProviders.length === 1) {
const provider = typedAvailableProviders[0];
return {
selectedProvider: provider,
availableProviders: typedAvailableProviders,
reason: `Only ${PROVIDER_CONFIGS[provider].displayName} is available.`,
};
}
// 5. If smart routing is disabled and user has a preference
if (!input.smartRoutingEnabled && input.preferredProvider) {
// Check if preferred provider is available
if (typedAvailableProviders.includes(input.preferredProvider)) {
return {
selectedProvider: input.preferredProvider,
availableProviders: typedAvailableProviders,
reason: `User selected ${PROVIDER_CONFIGS[input.preferredProvider].displayName}.`,
};
} else {
// Preferred provider not available, fall back to first available
const fallback = typedAvailableProviders[0];
const preferredConfig = input.preferredProvider ? PROVIDER_CONFIGS[input.preferredProvider] : null;
const preferredDisplayName = preferredConfig?.displayName || input.preferredProvider || 'Selected provider';
return {
selectedProvider: fallback,
availableProviders: typedAvailableProviders,
reason: `${preferredDisplayName} is not available. Falling back to ${PROVIDER_CONFIGS[fallback].displayName}.`,
};
}
}
// 6. Smart routing enabled - use keyword matching to select appropriate provider
const selection = selectProviderByKeywords(input.prompt, typedAvailableProviders);
return {
selectedProvider: selection.provider,
availableProviders: typedAvailableProviders,
reason: selection.reason,
};
}