@@ -7,19 +7,52 @@ import { ChevronDown, RefreshCw, Settings, Zap } from "lucide-react";
77import { useTranslation } from "react-i18next" ;
88import { AIService } from "../../services/ai-service" ;
99import { OPENAI_PROVIDER_NAME } from "../../services/providers/openai-service" ;
10+ import { ImageGenerationManager , ImageGenerationStatus , ImageGenerationHandler } from "../../services/image-generation-handler" ;
11+ import { DatabaseIntegrationService } from "../../services/database-integration" ;
12+ import { ImageGenerationResult } from "../../types/image" ;
13+ import ImageGenerateHistoryItem from "../image/ImageGenerateHistoryItem" ;
1014
1115export const ImageGenerationPage = ( ) => {
1216 const { t } = useTranslation ( ) ;
1317 const [ prompt , setPrompt ] = useState ( "" ) ;
14- const [ isGenerating , setIsGenerating ] = useState ( false ) ;
15- const [ imageResult , setImageResult ] = useState < string | null > ( null ) ;
1618 const [ error , setError ] = useState < Error | null > ( null ) ;
1719 const [ isApiKeyMissing , setIsApiKeyMissing ] = useState ( true ) ;
1820 const [ aspectRatio , setAspectRatio ] = useState ( "1:1" ) ;
1921 const [ imageCount , setImageCount ] = useState ( 1 ) ;
2022 const [ randomSeed , setRandomSeed ] = useState (
2123 Math . floor ( Math . random ( ) * 1000000 ) . toString ( )
2224 ) ;
25+ const [ generationResults , setGenerationResults ] = useState < Map < string , ImageGenerationHandler > > (
26+ new Map ( )
27+ ) ;
28+ const [ historyResults , setHistoryResults ] = useState < ImageGenerationResult [ ] > ( [ ] ) ;
29+ const [ isLoadingHistory , setIsLoadingHistory ] = useState ( true ) ;
30+
31+ // Initialize image generation manager
32+ useEffect ( ( ) => {
33+ const imageManager = ImageGenerationManager . getInstance ( ) ;
34+
35+ // Register for updates on generation status changes
36+ imageManager . setUpdateCallback ( ( handlers ) => {
37+ setGenerationResults ( new Map ( handlers ) ) ;
38+ } ) ;
39+
40+ // Load history from database
41+ const loadHistoryResults = async ( ) => {
42+ try {
43+ setIsLoadingHistory ( true ) ;
44+ const dbService = DatabaseIntegrationService . getInstance ( ) ;
45+ await dbService . initialize ( ) ;
46+ // TODO: Implement loading image history from database when available
47+ setIsLoadingHistory ( false ) ;
48+ } catch ( error ) {
49+ console . error ( 'Error loading image history:' , error ) ;
50+ setIsLoadingHistory ( false ) ;
51+ }
52+ } ;
53+
54+ loadHistoryResults ( ) ;
55+ } , [ ] ) ;
2356
2457 // Check if API key is available
2558 useEffect ( ( ) => {
@@ -40,7 +73,6 @@ export const ImageGenerationPage = () => {
4073 const handleGenerateImage = async ( ) => {
4174 if ( ! prompt . trim ( ) ) return ;
4275
43- setIsGenerating ( true ) ;
4476 setError ( null ) ;
4577
4678 try {
@@ -51,6 +83,20 @@ export const ImageGenerationPage = () => {
5183 throw new Error ( "OpenAI service not available" ) ;
5284 }
5385
86+ // Create a new generation handler
87+ const imageManager = ImageGenerationManager . getInstance ( ) ;
88+ const handler = imageManager . createHandler ( {
89+ prompt : prompt ,
90+ seed : randomSeed ,
91+ number : imageCount ,
92+ aspectRatio : aspectRatio ,
93+ provider : OPENAI_PROVIDER_NAME ,
94+ model : "dall-e-3" ,
95+ } ) ;
96+
97+ // Set status to generating
98+ handler . setGenerating ( ) ;
99+
54100 // Map aspect ratio to size dimensions
55101 const sizeMap : Record < string , `${number } x${number } `> = {
56102 "1:1" : "1024x1024" ,
@@ -68,23 +114,28 @@ export const ImageGenerationPage = () => {
68114 style : "vivid"
69115 } ) ;
70116
71- // Set the result image
117+ // Process the result
72118 if ( images && images . length > 0 ) {
73- // Check if the image is already a full data URL
74- const base64Data = images [ 0 ] as string ;
75- if ( base64Data . startsWith ( 'data:image' ) ) {
76- setImageResult ( base64Data ) ;
77- } else {
78- // If it's just a base64 string without the data URI prefix, add it
79- setImageResult ( `data:image/png;base64,${ base64Data } ` ) ;
80- }
119+ // Convert image data to full URLs if needed
120+ const processedImages = images . map ( img => {
121+ const base64Data = img as string ;
122+ if ( base64Data . startsWith ( 'data:image' ) ) {
123+ return base64Data ;
124+ } else {
125+ return `data:image/png;base64,${ base64Data } ` ;
126+ }
127+ } ) ;
128+
129+ // Update the handler with successful results
130+ handler . setSuccess ( processedImages ) ;
131+
132+ // Generate new seed for next generation
133+ generateNewSeed ( ) ;
81134 } else {
82135 throw new Error ( "No images generated" ) ;
83136 }
84137 } catch ( err ) {
85138 setError ( err as Error ) ;
86- } finally {
87- setIsGenerating ( false ) ;
88139 }
89140 } ;
90141
@@ -93,6 +144,25 @@ export const ImageGenerationPage = () => {
93144 setRandomSeed ( Math . floor ( Math . random ( ) * 1000000 ) . toString ( ) ) ;
94145 } ;
95146
147+ // Get all results to display in order (active generations first, then history)
148+ const getAllResults = ( ) => {
149+ const handlerResults = Array . from ( generationResults . values ( ) )
150+ . map ( handler => handler . getResult ( ) )
151+ . sort ( ( a , b ) => {
152+ // Prioritize active generations first
153+ if ( a . status === ImageGenerationStatus . GENERATING && b . status !== ImageGenerationStatus . GENERATING ) {
154+ return - 1 ;
155+ }
156+ if ( b . status === ImageGenerationStatus . GENERATING && a . status !== ImageGenerationStatus . GENERATING ) {
157+ return 1 ;
158+ }
159+ // Then sort by most recent first (assuming imageResultId is a UUID with timestamp components)
160+ return b . imageResultId . localeCompare ( a . imageResultId ) ;
161+ } ) ;
162+
163+ return [ ...handlerResults , ...historyResults ] ;
164+ } ;
165+
96166 return (
97167 < div className = "flex flex-col w-full h-full bg-white" >
98168 { isApiKeyMissing && (
@@ -121,19 +191,19 @@ export const ImageGenerationPage = () => {
121191 < div className = "flex flex-row gap-2 p-2 border border-gray-300 rounded-lg shadow-sm" >
122192 < button
123193 onClick = { handleGenerateImage }
124- disabled = { isGenerating || ! prompt . trim ( ) || isApiKeyMissing }
194+ disabled = { ! prompt . trim ( ) || isApiKeyMissing }
125195 className = "px-4 py-2.5 text-nowrap flex flex-row gap-1 text-white text-center confirm-btn"
126196 >
127197 < Settings > </ Settings >
128198 { t ( "imageGeneration.settingsButton" ) }
129199 </ button >
130200 < button
131201 onClick = { handleGenerateImage }
132- disabled = { isGenerating || ! prompt . trim ( ) || isApiKeyMissing }
202+ disabled = { ! prompt . trim ( ) || isApiKeyMissing }
133203 className = "px-4 py-2.5 text-nowrap flex flex-row gap-1 text-white text-center confirm-btn"
134204 >
135205 < Zap > </ Zap >
136- { isGenerating
206+ { Array . from ( generationResults . values ( ) ) . some ( h => h . getStatus ( ) === ImageGenerationStatus . GENERATING )
137207 ? t ( "imageGeneration.generating" )
138208 : t ( "imageGeneration.generateButton" ) }
139209 </ button >
@@ -154,41 +224,34 @@ export const ImageGenerationPage = () => {
154224 </ div >
155225 ) }
156226
157- { /* Loading indicator */ }
158- { isGenerating && (
159- < div className = "flex items-center justify-center h-64 rounded-lg bg-gray-50" >
160- < div className = "flex flex-col items-center" >
161- < div className = "w-10 h-10 border-4 rounded-full border-primary-300 border-t-primary-600 animate-spin" > </ div >
162- < p className = "mt-4 text-gray-600" >
163- { t ( "imageGeneration.generating" ) }
164- </ p >
165- </ div >
166- </ div >
167- ) }
168-
169227 { /* Results display */ }
170- { imageResult && ! isGenerating && (
171- < div className = "grid grid-cols-1 gap-4" >
172- < div className = "overflow-hidden border rounded-lg image-result-area" >
173- < img
174- src = { imageResult }
175- alt = "Generated from AI"
176- className = "object-contain w-full"
177- />
228+ < div className = "grid grid-cols-1 gap-4" >
229+ { /* Active generations and history */ }
230+ { getAllResults ( ) . map ( result => (
231+ < ImageGenerateHistoryItem
232+ key = { result . imageResultId }
233+ imageResult = { result }
234+ />
235+ ) ) }
236+
237+ { /* Loading indicator for history */ }
238+ { isLoadingHistory && (
239+ < div className = "flex items-center justify-center h-24 rounded-lg bg-gray-50" >
240+ < div className = "w-8 h-8 border-4 rounded-full border-primary-300 border-t-primary-600 animate-spin" > </ div >
178241 </ div >
179- </ div >
180- ) }
181-
182- { /* Placeholder when no results */ }
183- { ! imageResult && ! isGenerating && (
184- < div className = "flex items -center justify-center h-64 rounded-lg image-generation-result-placeholder " >
185- < div className = "text-center " >
186- < p className = "text-gray-500" >
187- { t ( "imageGeneration.placeholderText" ) }
188- </ p >
242+ ) }
243+
244+ { /* Placeholder when no results */ }
245+ { getAllResults ( ) . length === 0 && ! isLoadingHistory && (
246+ < div className = "flex items-center justify-center h-64 rounded-lg image-generation-result-placeholder" >
247+ < div className = "text -center" >
248+ < p className = "text-gray-500 " >
249+ { t ( "imageGeneration.placeholderText" ) }
250+ </ p >
251+ </ div >
189252 </ div >
190- </ div >
191- ) }
253+ ) }
254+ </ div >
192255 </ div >
193256
194257 { /* Right side - Results */ }
0 commit comments