diff --git a/messages/en.json b/messages/en.json index 6de8349..f3cd603 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1059,6 +1059,10 @@ "title": "AI Image", "description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly" }, + "AIImagesPage": { + "title": "AI Images Generator", + "description": "Is a AI Image Generator" + }, "AIChatPage": { "title": "AI Chat", "description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly" diff --git a/messages/zh.json b/messages/zh.json index b222833..2b156f9 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -1059,6 +1059,10 @@ "title": "AI 图片", "description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS,简单且毫不费力" }, + "AIImagesPage": { + "title": "AI 图片生成器", + "description": "但模型图片生成测试" + }, "AIChatPage": { "title": "AI 聊天", "description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS,简单且毫不费力" diff --git a/src/ai/image/components/ImageGeneratorByProvider.tsx b/src/ai/image/components/ImageGeneratorByProvider.tsx new file mode 100644 index 0000000..0dd184d --- /dev/null +++ b/src/ai/image/components/ImageGeneratorByProvider.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { useState } from 'react'; +import { useImageGeneration } from '../hooks/use-image-generation'; +import { + MODEL_CONFIGS, + type ModelMode, + PROVIDERS, + type ProviderKey, +} from '../lib/provider-config'; +import type { Suggestion } from '../lib/suggestions'; +import { ImageGeneratorHeader } from './ImageGeneratorHeader'; +import { ModelCardCarousel } from './ModelCardCarousel'; +import { PromptInput } from './PromptInput'; + +export function ImageImageGeneratorByProvider({ + suggestions, + providerKey, +}: { + suggestions: Suggestion[]; + providerKey: ProviderKey; +}) { + const { + images, + timings, + failedProviders, + isLoading, + startGeneration, + activePrompt, + } = useImageGeneration(); + + const [showProviders, setShowProviders] = useState(true); + const [selectedModels, setSelectedModels] = useState< + Record + >(MODEL_CONFIGS.performance); + const [mode, setMode] = useState('performance'); + const toggleView = () => { + setShowProviders((prev) => !prev); + }; + + const handleModeChange = (newMode: ModelMode) => { + setMode(newMode); + setSelectedModels(MODEL_CONFIGS[newMode]); + setShowProviders(true); + }; + + const handleModelChange = (providerKey: ProviderKey, model: string) => { + setSelectedModels((prev) => ({ ...prev, [providerKey]: model })); + }; + + // Only map the active provider to its selected model + const providerToModel = { + [providerKey]: selectedModels[providerKey], + } as Record; + + const handlePromptSubmit = (newPrompt: string) => { + const activeProviders: ProviderKey[] = [providerKey]; + startGeneration(newPrompt, activeProviders, providerToModel); + setShowProviders(false); + }; + + return ( +
+
+ {/* header */} + + + {/* input prompt */} + + + {/* models carousel */} + {(() => { + const key = providerKey; + const provider = PROVIDERS[key]; + if (!provider) return null; + + const imageItem = images.find((img) => img.provider === key); + const imageData = imageItem?.image; + const modelId = imageItem?.modelId ?? selectedModels[key] ?? ''; + const timing = timings[key]; + + const modelProps = { + label: provider.displayName, + models: provider.models, + value: selectedModels[key], + providerKey: key, + onChange: (model: string, pKey: ProviderKey) => + handleModelChange(pKey, model), + iconPath: provider.iconPath, + color: provider.color, + enabled: true, + image: imageData, + modelId, + timing, + failed: failedProviders.includes(key), + } as const; + + return ( + <> +
+ +
+ {activePrompt && activePrompt.length > 0 && ( +
+ {activePrompt} +
+ )} + + ); + })()} +
+
+ ); +} diff --git a/src/ai/image/components/ImagePlayground.tsx b/src/ai/image/components/ImagePlayground.tsx index 105e3db..47d3516 100644 --- a/src/ai/image/components/ImagePlayground.tsx +++ b/src/ai/image/components/ImagePlayground.tsx @@ -78,7 +78,7 @@ export function ImagePlayground({
{/* header */} - {/* */} + {/* input prompt */} = { // https://ai-sdk.dev/providers/ai-sdk-providers/replicate#image-models - replicate: { - displayName: 'Replicate', - iconPath: '/provider-icons/replicate.svg', - color: 'from-purple-500 to-blue-500', - models: [ - 'black-forest-labs/flux-1.1-pro', - 'black-forest-labs/flux-1.1-pro-ultra', - 'black-forest-labs/flux-dev', - 'black-forest-labs/flux-pro', - 'black-forest-labs/flux-schnell', - 'ideogram-ai/ideogram-v2', - 'ideogram-ai/ideogram-v2-turbo', - 'luma/photon', - 'luma/photon-flash', - 'recraft-ai/recraft-v3', - // 'recraft-ai/recraft-v3-svg', // added by Fox - // 'stability-ai/stable-diffusion-3.5-medium', // added by Fox - 'stability-ai/stable-diffusion-3.5-large', - 'stability-ai/stable-diffusion-3.5-large-turbo', - ], - }, + // replicate: { + // displayName: 'Replicate', + // iconPath: '/provider-icons/replicate.svg', + // color: 'from-purple-500 to-blue-500', + // models: [ + // 'black-forest-labs/flux-1.1-pro', + // 'black-forest-labs/flux-1.1-pro-ultra', + // 'black-forest-labs/flux-dev', + // 'black-forest-labs/flux-pro', + // 'black-forest-labs/flux-schnell', + // 'ideogram-ai/ideogram-v2', + // 'ideogram-ai/ideogram-v2-turbo', + // 'luma/photon', + // 'luma/photon-flash', + // 'recraft-ai/recraft-v3', + // // 'recraft-ai/recraft-v3-svg', // added by Fox + // // 'stability-ai/stable-diffusion-3.5-medium', // added by Fox + // 'stability-ai/stable-diffusion-3.5-large', + // 'stability-ai/stable-diffusion-3.5-large-turbo', + // ], + // }, // https://ai-sdk.dev/providers/ai-sdk-providers/openai#image-models - openai: { - displayName: 'OpenAI', - iconPath: '/provider-icons/openai.svg', - color: 'from-blue-500 to-cyan-500', - models: [ - // 'gpt-image-1', // added by Fox - 'dall-e-2', - 'dall-e-3', - ], - }, + // openai: { + // displayName: 'OpenAI', + // iconPath: '/provider-icons/openai.svg', + // color: 'from-blue-500 to-cyan-500', + // models: [ + // // 'gpt-image-1', // added by Fox + // 'dall-e-2', + // 'dall-e-3', + // ], + // }, // https://ai-sdk.dev/providers/ai-sdk-providers/fireworks#image-models - fireworks: { - displayName: 'Fireworks', - iconPath: '/provider-icons/fireworks.svg', - color: 'from-orange-500 to-red-500', - models: [ - 'accounts/fireworks/models/flux-1-dev-fp8', - 'accounts/fireworks/models/flux-1-schnell-fp8', - 'accounts/fireworks/models/playground-v2-5-1024px-aesthetic', - 'accounts/fireworks/models/japanese-stable-diffusion-xl', - 'accounts/fireworks/models/playground-v2-1024px-aesthetic', - 'accounts/fireworks/models/SSD-1B', - 'accounts/fireworks/models/stable-diffusion-xl-1024-v1-0', - ], - }, + // fireworks: { + // displayName: 'Fireworks', + // iconPath: '/provider-icons/fireworks.svg', + // color: 'from-orange-500 to-red-500', + // models: [ + // 'accounts/fireworks/models/flux-1-dev-fp8', + // 'accounts/fireworks/models/flux-1-schnell-fp8', + // 'accounts/fireworks/models/playground-v2-5-1024px-aesthetic', + // 'accounts/fireworks/models/japanese-stable-diffusion-xl', + // 'accounts/fireworks/models/playground-v2-1024px-aesthetic', + // 'accounts/fireworks/models/SSD-1B', + // 'accounts/fireworks/models/stable-diffusion-xl-1024-v1-0', + // ], + // }, // https://ai-sdk.dev/providers/ai-sdk-providers/fal#image-models fal: { displayName: 'Fal', diff --git a/src/app/[locale]/(marketing)/ai/images/page.tsx b/src/app/[locale]/(marketing)/ai/images/page.tsx new file mode 100644 index 0000000..e52d091 --- /dev/null +++ b/src/app/[locale]/(marketing)/ai/images/page.tsx @@ -0,0 +1,49 @@ +import { ImageGenerator } from '@/ai/image/components/ImageGenerator'; +import { ImageImageGeneratorByProvider } from '@/ai/image/components/ImageGeneratorByProvider'; +import { ImagePlayground } from '@/ai/image/components/ImagePlayground'; +import { getRandomSuggestions } from '@/ai/image/lib/suggestions'; +import { constructMetadata } from '@/lib/metadata'; +import { getUrlWithLocale } from '@/lib/urls/urls'; +import { ImageIcon } from 'lucide-react'; +import type { Metadata } from 'next'; +import type { Locale } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: Locale }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'Metadata' }); + const pt = await getTranslations({ locale, namespace: 'AIImagePage' }); + + return constructMetadata({ + title: pt('title') + ' | ' + t('title'), + description: pt('description'), + canonicalUrl: getUrlWithLocale('/ai/image', locale), + }); +} + +export default async function AIImagePage() { + const t = await getTranslations('AIImagesPage'); + + return ( +
+
+ {/* Header Section */} +
+
+ + {t('title')} +
+
+ + {/* Image Playground Component */} +
+ +
+
+
+ ); +}