add ai image generator page

This commit is contained in:
songtianlun 2025-09-11 00:01:48 +08:00
parent 3bd171683d
commit c994529d01
6 changed files with 225 additions and 46 deletions

View File

@ -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"

View File

@ -1059,6 +1059,10 @@
"title": "AI 图片",
"description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS简单且毫不费力"
},
"AIImagesPage": {
"title": "AI 图片生成器",
"description": "但模型图片生成测试"
},
"AIChatPage": {
"title": "AI 聊天",
"description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS简单且毫不费力"

View File

@ -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<ProviderKey, string>
>(MODEL_CONFIGS.performance);
const [mode, setMode] = useState<ModelMode>('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<ProviderKey, string>;
const handlePromptSubmit = (newPrompt: string) => {
const activeProviders: ProviderKey[] = [providerKey];
startGeneration(newPrompt, activeProviders, providerToModel);
setShowProviders(false);
};
return (
<div className="rounded-lg bg-background py-8 px-4 sm:px-6 lg:px-8">
<div className="mx-auto">
{/* header */}
<ImageGeneratorHeader />
{/* input prompt */}
<PromptInput
onSubmit={handlePromptSubmit}
isLoading={isLoading}
showProviders={showProviders}
onToggleProviders={toggleView}
mode={mode}
onModeChange={handleModeChange}
suggestions={suggestions}
/>
{/* 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 (
<>
<div>
<ModelCardCarousel models={[modelProps]} />
</div>
{activePrompt && activePrompt.length > 0 && (
<div className="text-center mt-8 text-muted-foreground">
{activePrompt}
</div>
)}
</>
);
})()}
</div>
</div>
);
}

View File

@ -78,7 +78,7 @@ export function ImagePlayground({
<div className="rounded-lg bg-background py-8 px-4 sm:px-6 lg:px-8">
<div className="mx-auto">
{/* header */}
{/* <ImageGeneratorHeader /> */}
<ImageGeneratorHeader />
{/* input prompt */}
<PromptInput

View File

@ -11,53 +11,53 @@ export const PROVIDERS: Record<
}
> = {
// 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',

View File

@ -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<Metadata | undefined> {
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 (
<div className="min-h-screen bg-muted/50 rounded-lg">
<div className="container mx-auto px-4 py-8 md:py-16">
{/* Header Section */}
<div className="text-center space-y-6 mb-12">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 text-primary text-sm font-medium">
<ImageIcon className="size-4" />
{t('title')}
</div>
</div>
{/* Image Playground Component */}
<div className="max-w-6xl mx-auto">
<ImageImageGeneratorByProvider suggestions={getRandomSuggestions(5)} providerKey='fal' />
</div>
</div>
</div>
);
}