feat: apply enchancements
This commit is contained in:
parent
680d2d2bad
commit
36e5b910e9
|
|
@ -4,6 +4,7 @@ import { useState, useRef, KeyboardEvent } from 'react';
|
|||
import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey';
|
||||
import { GenerationTimer } from '@/components/demo/GenerationTimer';
|
||||
import { ResultCard } from '@/components/demo/ResultCard';
|
||||
import { EnhancementOptions, EnhancementOptionsData } from '@/components/demo/EnhancementOptions';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ interface GenerationResult {
|
|||
response: object;
|
||||
geminiParams: object;
|
||||
};
|
||||
enhancementOptions?: EnhancementOptionsData;
|
||||
}
|
||||
|
||||
interface ApiKeyInfo {
|
||||
|
|
@ -57,6 +59,9 @@ export default function DemoTTIPage() {
|
|||
const [generationStartTime, setGenerationStartTime] = useState<number | undefined>();
|
||||
const [generationError, setGenerationError] = useState('');
|
||||
|
||||
// Enhancement Options State
|
||||
const [enhancementOptions, setEnhancementOptions] = useState<EnhancementOptionsData>({});
|
||||
|
||||
// Results State
|
||||
const [results, setResults] = useState<GenerationResult[]>([]);
|
||||
|
||||
|
|
@ -135,7 +140,35 @@ export default function DemoTTIPage() {
|
|||
const timestamp = new Date();
|
||||
|
||||
try {
|
||||
// Call API twice in parallel (both identical for now)
|
||||
// Build enhancement options for right image (only non-empty values)
|
||||
const rightEnhancementOptions: any = {};
|
||||
if (enhancementOptions.autoEnhance) {
|
||||
rightEnhancementOptions.autoEnhance = true;
|
||||
}
|
||||
if (enhancementOptions.imageStyle) {
|
||||
rightEnhancementOptions.imageStyle = enhancementOptions.imageStyle;
|
||||
}
|
||||
if (enhancementOptions.aspectRatio) {
|
||||
rightEnhancementOptions.aspectRatio = enhancementOptions.aspectRatio;
|
||||
}
|
||||
if (enhancementOptions.mood) {
|
||||
rightEnhancementOptions.mood = enhancementOptions.mood;
|
||||
}
|
||||
if (enhancementOptions.lighting) {
|
||||
rightEnhancementOptions.lighting = enhancementOptions.lighting;
|
||||
}
|
||||
if (enhancementOptions.cameraAngle) {
|
||||
rightEnhancementOptions.cameraAngle = enhancementOptions.cameraAngle;
|
||||
}
|
||||
if (enhancementOptions.negativePrompts && enhancementOptions.negativePrompts.length > 0) {
|
||||
rightEnhancementOptions.negativePrompts = enhancementOptions.negativePrompts;
|
||||
}
|
||||
|
||||
const hasEnhancementOptions = Object.keys(rightEnhancementOptions).length > 0;
|
||||
|
||||
// Call API twice in parallel
|
||||
// Left: original prompt with no enhancement options
|
||||
// Right: original prompt WITH enhancement options
|
||||
const [leftResult, rightResult] = await Promise.all([
|
||||
fetch(`${API_BASE_URL}/api/text-to-image`, {
|
||||
method: 'POST',
|
||||
|
|
@ -157,6 +190,10 @@ export default function DemoTTIPage() {
|
|||
body: JSON.stringify({
|
||||
prompt: prompt.trim(),
|
||||
filename: `demo_${resultId}_right`,
|
||||
autoEnhance: true,
|
||||
...(hasEnhancementOptions && {
|
||||
enhancementOptions: rightEnhancementOptions
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
|
@ -201,10 +238,16 @@ export default function DemoTTIPage() {
|
|||
request: {
|
||||
prompt: prompt.trim(),
|
||||
filename: `demo_${resultId}_right`,
|
||||
autoEnhance: true,
|
||||
...(hasEnhancementOptions && {
|
||||
enhancementOptions: rightEnhancementOptions
|
||||
}),
|
||||
},
|
||||
response: rightData,
|
||||
geminiParams: rightData.data?.geminiParams || {},
|
||||
},
|
||||
// Store enhancement options for display in inspect mode
|
||||
enhancementOptions: hasEnhancementOptions ? enhancementOptions : undefined,
|
||||
};
|
||||
|
||||
if (!leftData.success) {
|
||||
|
|
@ -345,7 +388,7 @@ export default function DemoTTIPage() {
|
|||
)}
|
||||
|
||||
{/* Prompt Input Section */}
|
||||
<section className="mb-8 p-5 bg-slate-900/80 backdrop-blur-sm border border-slate-700 rounded-2xl" aria-label="Prompt Input">
|
||||
<section className="mb-6 p-5 bg-slate-900/80 backdrop-blur-sm border border-slate-700 rounded-2xl" aria-label="Prompt Input">
|
||||
<h2 className="text-lg font-semibold text-white mb-3">Your Prompt</h2>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
|
|
@ -358,7 +401,20 @@ export default function DemoTTIPage() {
|
|||
className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed resize-none"
|
||||
aria-label="Image generation prompt"
|
||||
/>
|
||||
<div className="mt-3 flex items-center justify-between gap-4 flex-wrap">
|
||||
</section>
|
||||
|
||||
{/* Enhancement Options Section */}
|
||||
<section className="mb-6" aria-label="Enhancement Options">
|
||||
<EnhancementOptions
|
||||
value={enhancementOptions}
|
||||
onChange={setEnhancementOptions}
|
||||
disabled={!apiKeyValidated || generating}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Generate Button Section */}
|
||||
<section className="mb-8 p-5 bg-slate-900/80 backdrop-blur-sm border border-slate-700 rounded-2xl" aria-label="Generation Controls">
|
||||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||
<div className="text-sm text-gray-500">
|
||||
{generating ? (
|
||||
<GenerationTimer isGenerating={generating} startTime={generationStartTime} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,264 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface EnhancementOptionsData {
|
||||
imageStyle?: string;
|
||||
aspectRatio?: string;
|
||||
mood?: string;
|
||||
lighting?: string;
|
||||
cameraAngle?: string;
|
||||
negativePrompts?: string[];
|
||||
}
|
||||
|
||||
interface EnhancementOptionsProps {
|
||||
value: EnhancementOptionsData;
|
||||
onChange: (options: EnhancementOptionsData) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const IMAGE_STYLES = [
|
||||
{ value: '', label: 'Select style...' },
|
||||
{ value: 'photorealistic', label: 'Photorealistic' },
|
||||
{ value: 'illustration', label: 'Illustration' },
|
||||
{ value: 'minimalist', label: 'Minimalist' },
|
||||
{ value: 'sticker', label: 'Sticker' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'comic', label: 'Comic' },
|
||||
];
|
||||
|
||||
const ASPECT_RATIOS = [
|
||||
{ value: '', label: 'Select ratio...' },
|
||||
{ value: 'square', label: 'Square (1:1)' },
|
||||
{ value: 'portrait', label: 'Portrait (3:4)' },
|
||||
{ value: 'landscape', label: 'Landscape (4:3)' },
|
||||
{ value: 'wide', label: 'Wide (16:9)' },
|
||||
{ value: 'ultrawide', label: 'Ultrawide (21:9)' },
|
||||
];
|
||||
|
||||
export function EnhancementOptions({ value, onChange, disabled = false }: EnhancementOptionsProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const updateValue = (key: keyof EnhancementOptionsData, newValue: any) => {
|
||||
onChange({ ...value, [key]: newValue });
|
||||
};
|
||||
|
||||
const handleNegativePromptsChange = (text: string) => {
|
||||
const prompts = text
|
||||
.split(',')
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p.length > 0);
|
||||
updateValue('negativePrompts', prompts.length > 0 ? prompts : undefined);
|
||||
};
|
||||
|
||||
const clearAll = () => {
|
||||
onChange({
|
||||
imageStyle: undefined,
|
||||
aspectRatio: undefined,
|
||||
mood: undefined,
|
||||
lighting: undefined,
|
||||
cameraAngle: undefined,
|
||||
negativePrompts: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const hasAnyOptions =
|
||||
value.imageStyle ||
|
||||
value.aspectRatio ||
|
||||
value.mood ||
|
||||
value.lighting ||
|
||||
value.cameraAngle ||
|
||||
(value.negativePrompts && value.negativePrompts.length > 0);
|
||||
|
||||
return (
|
||||
<div className="p-5 bg-slate-900/80 backdrop-blur-sm border border-slate-700 rounded-2xl">
|
||||
{/* Header */}
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
disabled={disabled}
|
||||
className="w-full flex items-center justify-between group disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-amber-500 rounded-lg"
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls="enhancement-options-content"
|
||||
aria-label={isExpanded ? 'Collapse enhancement options' : 'Expand enhancement options'}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-lg font-semibold text-white">
|
||||
Enhancement Options
|
||||
<span className="text-gray-500 text-sm font-normal ml-2">
|
||||
(Always enhances second image)
|
||||
</span>
|
||||
</h2>
|
||||
{hasAnyOptions && !isExpanded && (
|
||||
<span className="px-2 py-0.5 text-xs bg-amber-600/20 text-amber-400 rounded-full">
|
||||
{Object.keys(value).filter(k => value[k as keyof EnhancementOptionsData]).length} active
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-400 transition-transform ${
|
||||
isExpanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Expandable Content */}
|
||||
{isExpanded && (
|
||||
<div
|
||||
id="enhancement-options-content"
|
||||
className="mt-5 space-y-4 animate-fade-in"
|
||||
role="region"
|
||||
aria-label="Enhancement options form"
|
||||
>
|
||||
|
||||
{/* Dropdowns Row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Image Style */}
|
||||
<div>
|
||||
<label htmlFor="image-style" className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Image Style
|
||||
</label>
|
||||
<select
|
||||
id="image-style"
|
||||
value={value.imageStyle || ''}
|
||||
onChange={(e) => updateValue('imageStyle', e.target.value || undefined)}
|
||||
disabled={disabled}
|
||||
className="w-full px-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{IMAGE_STYLES.map((style) => (
|
||||
<option key={style.value} value={style.value}>
|
||||
{style.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Aspect Ratio */}
|
||||
<div>
|
||||
<label htmlFor="aspect-ratio" className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Aspect Ratio
|
||||
</label>
|
||||
<select
|
||||
id="aspect-ratio"
|
||||
value={value.aspectRatio || ''}
|
||||
onChange={(e) => updateValue('aspectRatio', e.target.value || undefined)}
|
||||
disabled={disabled}
|
||||
className="w-full px-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{ASPECT_RATIOS.map((ratio) => (
|
||||
<option key={ratio.value} value={ratio.value}>
|
||||
{ratio.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text Input Fields */}
|
||||
<div className="space-y-3">
|
||||
{/* Mood */}
|
||||
<div>
|
||||
<label htmlFor="mood" className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Mood
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="mood"
|
||||
value={value.mood || ''}
|
||||
onChange={(e) => updateValue('mood', e.target.value || undefined)}
|
||||
placeholder="e.g., peaceful, energetic, mysterious..."
|
||||
maxLength={100}
|
||||
disabled={disabled}
|
||||
className="w-full px-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-describedby="mood-hint"
|
||||
/>
|
||||
<p id="mood-hint" className="text-xs text-gray-500 mt-1">
|
||||
Max 100 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Lighting */}
|
||||
<div>
|
||||
<label htmlFor="lighting" className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Lighting
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lighting"
|
||||
value={value.lighting || ''}
|
||||
onChange={(e) => updateValue('lighting', e.target.value || undefined)}
|
||||
placeholder="e.g., golden hour, dramatic shadows, soft natural light..."
|
||||
maxLength={100}
|
||||
disabled={disabled}
|
||||
className="w-full px-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-describedby="lighting-hint"
|
||||
/>
|
||||
<p id="lighting-hint" className="text-xs text-gray-500 mt-1">
|
||||
Max 100 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Camera Angle */}
|
||||
<div>
|
||||
<label htmlFor="camera-angle" className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Camera Angle
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="camera-angle"
|
||||
value={value.cameraAngle || ''}
|
||||
onChange={(e) => updateValue('cameraAngle', e.target.value || undefined)}
|
||||
placeholder="e.g., wide shot, close-up, bird's eye view..."
|
||||
maxLength={100}
|
||||
disabled={disabled}
|
||||
className="w-full px-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-describedby="camera-angle-hint"
|
||||
/>
|
||||
<p id="camera-angle-hint" className="text-xs text-gray-500 mt-1">
|
||||
Max 100 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Negative Prompts */}
|
||||
<div>
|
||||
<label htmlFor="negative-prompts" className="block text-sm font-medium text-gray-400 mb-2">
|
||||
Negative Prompts
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="negative-prompts"
|
||||
value={value.negativePrompts?.join(', ') || ''}
|
||||
onChange={(e) => handleNegativePromptsChange(e.target.value)}
|
||||
placeholder="e.g., blurry, low quality, distorted..."
|
||||
disabled={disabled}
|
||||
className="w-full px-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-describedby="negative-prompts-hint"
|
||||
/>
|
||||
<p id="negative-prompts-hint" className="text-xs text-gray-500 mt-1">
|
||||
Comma-separated list (max 10 items, 100 chars each)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Clear All Button */}
|
||||
{hasAnyOptions && (
|
||||
<div className="pt-2 border-t border-slate-700">
|
||||
<button
|
||||
onClick={clearAll}
|
||||
disabled={disabled}
|
||||
className="w-full md:w-auto px-4 py-2 text-sm bg-slate-800 hover:bg-slate-700 text-gray-300 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
Clear All Options
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,6 +7,16 @@ import { CompletedTimerBadge } from './GenerationTimer';
|
|||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
||||
|
||||
interface EnhancementOptionsData {
|
||||
autoEnhance: boolean;
|
||||
imageStyle?: string;
|
||||
aspectRatio?: string;
|
||||
mood?: string;
|
||||
lighting?: string;
|
||||
cameraAngle?: string;
|
||||
negativePrompts?: string[];
|
||||
}
|
||||
|
||||
interface GenerationResult {
|
||||
id: string;
|
||||
timestamp: Date;
|
||||
|
|
@ -35,6 +45,7 @@ interface GenerationResult {
|
|||
response: object;
|
||||
geminiParams: object;
|
||||
};
|
||||
enhancementOptions?: EnhancementOptionsData;
|
||||
}
|
||||
|
||||
interface ResultCardProps {
|
||||
|
|
@ -60,7 +71,37 @@ export function ResultCard({
|
|||
const [viewMode, setViewMode] = useState<ViewMode>('preview');
|
||||
const [activeTab, setActiveTab] = useState<CodeTab>('curl');
|
||||
|
||||
const curlCode = `curl -X POST ${API_BASE_URL}/api/text-to-image \\
|
||||
// Build enhancement options JSON for code examples
|
||||
const buildEnhancementOptionsJson = () => {
|
||||
if (!result.enhancementOptions) return '';
|
||||
|
||||
const opts: any = {};
|
||||
if (result.enhancementOptions.autoEnhance) opts.autoEnhance = true;
|
||||
if (result.enhancementOptions.imageStyle) opts.imageStyle = result.enhancementOptions.imageStyle;
|
||||
if (result.enhancementOptions.aspectRatio) opts.aspectRatio = result.enhancementOptions.aspectRatio;
|
||||
if (result.enhancementOptions.mood) opts.mood = result.enhancementOptions.mood;
|
||||
if (result.enhancementOptions.lighting) opts.lighting = result.enhancementOptions.lighting;
|
||||
if (result.enhancementOptions.cameraAngle) opts.cameraAngle = result.enhancementOptions.cameraAngle;
|
||||
if (result.enhancementOptions.negativePrompts) opts.negativePrompts = result.enhancementOptions.negativePrompts;
|
||||
|
||||
if (Object.keys(opts).length === 0) return '';
|
||||
return JSON.stringify(opts, null, 2).split('\n').map((line, i) => i === 0 ? line : ` ${line}`).join('\n');
|
||||
};
|
||||
|
||||
const enhancementOptionsJson = buildEnhancementOptionsJson();
|
||||
const hasEnhancementOptions = !!result.enhancementOptions;
|
||||
|
||||
const curlCode = hasEnhancementOptions
|
||||
? `# Right image (with enhancement options)
|
||||
curl -X POST ${API_BASE_URL}/api/text-to-image \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "X-API-Key: ${apiKey}" \\
|
||||
-d '{
|
||||
"prompt": "${result.originalPrompt.replace(/"/g, '\\"')}",
|
||||
"filename": "generated_image",
|
||||
"enhancementOptions": ${enhancementOptionsJson.replace(/\n/g, '\n ')}
|
||||
}'`
|
||||
: `curl -X POST ${API_BASE_URL}/api/text-to-image \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "X-API-Key: ${apiKey}" \\
|
||||
-d '{
|
||||
|
|
@ -68,7 +109,23 @@ export function ResultCard({
|
|||
"filename": "generated_image"
|
||||
}'`;
|
||||
|
||||
const fetchCode = `fetch('${API_BASE_URL}/api/text-to-image', {
|
||||
const fetchCode = hasEnhancementOptions
|
||||
? `// Right image (with enhancement options)
|
||||
fetch('${API_BASE_URL}/api/text-to-image', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': '${apiKey}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: '${result.originalPrompt.replace(/'/g, "\\'")}',
|
||||
filename: 'generated_image',
|
||||
enhancementOptions: ${enhancementOptionsJson}
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => console.log(data));`
|
||||
: `fetch('${API_BASE_URL}/api/text-to-image', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
@ -82,7 +139,18 @@ export function ResultCard({
|
|||
.then(res => res.json())
|
||||
.then(data => console.log(data));`;
|
||||
|
||||
const restCode = `### Generate Image - Text to Image
|
||||
const restCode = hasEnhancementOptions
|
||||
? `### Right Image - With Enhancement Options
|
||||
POST ${API_BASE_URL}/api/text-to-image
|
||||
Content-Type: application/json
|
||||
X-API-Key: ${apiKey}
|
||||
|
||||
{
|
||||
"prompt": "${result.originalPrompt.replace(/"/g, '\\"')}",
|
||||
"filename": "generated_image",
|
||||
"enhancementOptions": ${enhancementOptionsJson}
|
||||
}`
|
||||
: `### Generate Image - Text to Image
|
||||
POST ${API_BASE_URL}/api/text-to-image
|
||||
Content-Type: application/json
|
||||
X-API-Key: ${apiKey}
|
||||
|
|
@ -167,6 +235,7 @@ X-API-Key: ${apiKey}
|
|||
onDownload={onDownload}
|
||||
onReusePrompt={onReusePrompt}
|
||||
filename="enhanced.png"
|
||||
hasEnhancementOptions={!!result.enhancementOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -211,6 +280,7 @@ function ImagePreview({
|
|||
onDownload,
|
||||
onReusePrompt,
|
||||
filename,
|
||||
hasEnhancementOptions = false,
|
||||
}: {
|
||||
image: GenerationResult['leftImage'];
|
||||
label: string;
|
||||
|
|
@ -219,11 +289,19 @@ function ImagePreview({
|
|||
onDownload: (url: string, filename: string) => void;
|
||||
onReusePrompt: (prompt: string) => void;
|
||||
filename: string;
|
||||
hasEnhancementOptions?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex-shrink-0">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<span className="text-sm font-medium text-gray-400">{label}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-400">{label}</span>
|
||||
{hasEnhancementOptions && (
|
||||
<span className="px-2 py-0.5 text-xs bg-amber-600/20 text-amber-400 rounded-full border border-amber-600/30">
|
||||
+ Options
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{image?.error ? (
|
||||
|
|
|
|||
|
|
@ -106,3 +106,16 @@ Content-Type: image/jpeg
|
|||
|
||||
< ./reference.jpg
|
||||
------WebKitFormBoundary--
|
||||
|
||||
|
||||
### Generate Image - Text to Image
|
||||
POST http://localhost:3000/api/text-to-image
|
||||
Content-Type: application/json
|
||||
X-API-Key: bnt_61ba018f01474491cbaacec4509220d7154fffcd011f005eece4dba7889fba99
|
||||
|
||||
{
|
||||
"prompt": "фотография детской кроватки в стиле piratespunk",
|
||||
"filename": "generated_image",
|
||||
"autoEnhance": true
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue