add ai image generator page
This commit is contained in:
parent
3bd171683d
commit
c994529d01
@ -1059,6 +1059,10 @@
|
|||||||
"title": "AI Image",
|
"title": "AI Image",
|
||||||
"description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly"
|
"description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly"
|
||||||
},
|
},
|
||||||
|
"AIImagesPage": {
|
||||||
|
"title": "AI Images Generator",
|
||||||
|
"description": "Is a AI Image Generator"
|
||||||
|
},
|
||||||
"AIChatPage": {
|
"AIChatPage": {
|
||||||
"title": "AI Chat",
|
"title": "AI Chat",
|
||||||
"description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly"
|
"description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly"
|
||||||
|
@ -1059,6 +1059,10 @@
|
|||||||
"title": "AI 图片",
|
"title": "AI 图片",
|
||||||
"description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS,简单且毫不费力"
|
"description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS,简单且毫不费力"
|
||||||
},
|
},
|
||||||
|
"AIImagesPage": {
|
||||||
|
"title": "AI 图片生成器",
|
||||||
|
"description": "但模型图片生成测试"
|
||||||
|
},
|
||||||
"AIChatPage": {
|
"AIChatPage": {
|
||||||
"title": "AI 聊天",
|
"title": "AI 聊天",
|
||||||
"description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS,简单且毫不费力"
|
"description": "MkSaaS 让您在几天内轻松构建您的 AI SaaS,简单且毫不费力"
|
||||||
|
122
src/ai/image/components/ImageGeneratorByProvider.tsx
Normal file
122
src/ai/image/components/ImageGeneratorByProvider.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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="rounded-lg bg-background py-8 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
{/* header */}
|
{/* header */}
|
||||||
{/* <ImageGeneratorHeader /> */}
|
<ImageGeneratorHeader />
|
||||||
|
|
||||||
{/* input prompt */}
|
{/* input prompt */}
|
||||||
<PromptInput
|
<PromptInput
|
||||||
|
@ -11,53 +11,53 @@ export const PROVIDERS: Record<
|
|||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
// https://ai-sdk.dev/providers/ai-sdk-providers/replicate#image-models
|
// https://ai-sdk.dev/providers/ai-sdk-providers/replicate#image-models
|
||||||
replicate: {
|
// replicate: {
|
||||||
displayName: 'Replicate',
|
// displayName: 'Replicate',
|
||||||
iconPath: '/provider-icons/replicate.svg',
|
// iconPath: '/provider-icons/replicate.svg',
|
||||||
color: 'from-purple-500 to-blue-500',
|
// color: 'from-purple-500 to-blue-500',
|
||||||
models: [
|
// models: [
|
||||||
'black-forest-labs/flux-1.1-pro',
|
// 'black-forest-labs/flux-1.1-pro',
|
||||||
'black-forest-labs/flux-1.1-pro-ultra',
|
// 'black-forest-labs/flux-1.1-pro-ultra',
|
||||||
'black-forest-labs/flux-dev',
|
// 'black-forest-labs/flux-dev',
|
||||||
'black-forest-labs/flux-pro',
|
// 'black-forest-labs/flux-pro',
|
||||||
'black-forest-labs/flux-schnell',
|
// 'black-forest-labs/flux-schnell',
|
||||||
'ideogram-ai/ideogram-v2',
|
// 'ideogram-ai/ideogram-v2',
|
||||||
'ideogram-ai/ideogram-v2-turbo',
|
// 'ideogram-ai/ideogram-v2-turbo',
|
||||||
'luma/photon',
|
// 'luma/photon',
|
||||||
'luma/photon-flash',
|
// 'luma/photon-flash',
|
||||||
'recraft-ai/recraft-v3',
|
// 'recraft-ai/recraft-v3',
|
||||||
// 'recraft-ai/recraft-v3-svg', // added by Fox
|
// // 'recraft-ai/recraft-v3-svg', // added by Fox
|
||||||
// 'stability-ai/stable-diffusion-3.5-medium', // 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',
|
||||||
'stability-ai/stable-diffusion-3.5-large-turbo',
|
// 'stability-ai/stable-diffusion-3.5-large-turbo',
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
// https://ai-sdk.dev/providers/ai-sdk-providers/openai#image-models
|
// https://ai-sdk.dev/providers/ai-sdk-providers/openai#image-models
|
||||||
openai: {
|
// openai: {
|
||||||
displayName: 'OpenAI',
|
// displayName: 'OpenAI',
|
||||||
iconPath: '/provider-icons/openai.svg',
|
// iconPath: '/provider-icons/openai.svg',
|
||||||
color: 'from-blue-500 to-cyan-500',
|
// color: 'from-blue-500 to-cyan-500',
|
||||||
models: [
|
// models: [
|
||||||
// 'gpt-image-1', // added by Fox
|
// // 'gpt-image-1', // added by Fox
|
||||||
'dall-e-2',
|
// 'dall-e-2',
|
||||||
'dall-e-3',
|
// 'dall-e-3',
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
// https://ai-sdk.dev/providers/ai-sdk-providers/fireworks#image-models
|
// https://ai-sdk.dev/providers/ai-sdk-providers/fireworks#image-models
|
||||||
fireworks: {
|
// fireworks: {
|
||||||
displayName: 'Fireworks',
|
// displayName: 'Fireworks',
|
||||||
iconPath: '/provider-icons/fireworks.svg',
|
// iconPath: '/provider-icons/fireworks.svg',
|
||||||
color: 'from-orange-500 to-red-500',
|
// color: 'from-orange-500 to-red-500',
|
||||||
models: [
|
// models: [
|
||||||
'accounts/fireworks/models/flux-1-dev-fp8',
|
// 'accounts/fireworks/models/flux-1-dev-fp8',
|
||||||
'accounts/fireworks/models/flux-1-schnell-fp8',
|
// 'accounts/fireworks/models/flux-1-schnell-fp8',
|
||||||
'accounts/fireworks/models/playground-v2-5-1024px-aesthetic',
|
// 'accounts/fireworks/models/playground-v2-5-1024px-aesthetic',
|
||||||
'accounts/fireworks/models/japanese-stable-diffusion-xl',
|
// 'accounts/fireworks/models/japanese-stable-diffusion-xl',
|
||||||
'accounts/fireworks/models/playground-v2-1024px-aesthetic',
|
// 'accounts/fireworks/models/playground-v2-1024px-aesthetic',
|
||||||
'accounts/fireworks/models/SSD-1B',
|
// 'accounts/fireworks/models/SSD-1B',
|
||||||
'accounts/fireworks/models/stable-diffusion-xl-1024-v1-0',
|
// 'accounts/fireworks/models/stable-diffusion-xl-1024-v1-0',
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
// https://ai-sdk.dev/providers/ai-sdk-providers/fal#image-models
|
// https://ai-sdk.dev/providers/ai-sdk-providers/fal#image-models
|
||||||
fal: {
|
fal: {
|
||||||
displayName: 'Fal',
|
displayName: 'Fal',
|
||||||
|
49
src/app/[locale]/(marketing)/ai/images/page.tsx
Normal file
49
src/app/[locale]/(marketing)/ai/images/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user