Skip to content

Commit 92c4a2d

Browse files
committed
Function; Add Image generation page
1 parent c6f94db commit 92c4a2d

File tree

7 files changed

+221
-26
lines changed

7 files changed

+221
-26
lines changed

src/App.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { useEffect } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { ChatPage } from './components/pages/ChatPage';
3+
import { ImageGenerationPage } from './components/pages/ImageGenerationPage';
34
import MainLayout from './components/layout/MainLayout';
45
import DatabaseInitializer from './components/core/DatabaseInitializer';
56

67
function App() {
8+
const [activePage, setActivePage] = useState('chat');
9+
const [showSettings, setShowSettings] = useState(false);
710

811
// Handle link clicks
912
useEffect(() => {
@@ -22,11 +25,15 @@ function App() {
2225
};
2326
}, []);
2427

28+
const handlePageChange = (page: string) => {
29+
setActivePage(page);
30+
};
2531

2632
return (
2733
<DatabaseInitializer>
28-
<MainLayout>
29-
<ChatPage/>
34+
<MainLayout activePage={activePage} onChangePage={handlePageChange} showSettings={showSettings} setShowSettings={setShowSettings}>
35+
{activePage === 'chat' && <ChatPage />}
36+
{activePage === 'image' && <ImageGenerationPage />}
3037
</MainLayout>
3138
</DatabaseInitializer>
3239
);

src/components/layout/MainLayout.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,47 @@
1-
import React, { ReactNode, useState } from 'react';
1+
import React, { ReactNode } from 'react';
22
import Sidebar from './Sidebar';
33
import SettingsPage from '../pages/SettingsPage';
44
import TopBar from './TopBar';
55
import { SettingsService } from '../../services/settings-service';
66

77
interface MainLayoutProps {
88
children: ReactNode;
9+
activePage: string;
10+
onChangePage: (page: string) => void;
11+
showSettings: boolean;
12+
setShowSettings: (showSettings: boolean) => void;
913
}
1014

11-
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
12-
const [activePage, setActivePage] = useState('chat');
13-
const [showSettings, setShowSettings] = useState(false);
14-
15-
// Handle page changes
16-
const handlePageChange = (page: string) => {
17-
if(activePage === 'settings' && page !== activePage){
15+
const MainLayout: React.FC<MainLayoutProps> = ({
16+
children,
17+
activePage,
18+
onChangePage,
19+
showSettings,
20+
setShowSettings
21+
}) => {
22+
23+
// Handle settings dialog
24+
const handleOpenSettingsDialog = () => {
25+
if(activePage === 'settings' && activePage !== 'settings'){
1826
// Save settings
27+
SettingsService.getInstance().saveSettings();
1928
}
2029

30+
setShowSettings(true);
31+
onChangePage('settings');
32+
};
33+
34+
// Handle page changes
35+
const handlePageChange = (page: string) => {
2136
if (page === 'settings') {
2237
setShowSettings(true);
23-
setActivePage('settings');
38+
onChangePage('settings');
2439
} else {
2540
setShowSettings(false);
26-
setActivePage(page);
41+
onChangePage(page);
2742
}
2843
};
2944

30-
// Handle page changes
31-
const handleOpenSettingsDialog = () => {
32-
handlePageChange('settings');
33-
};
34-
3545
// Handle selecting a model
3646
const handleSelectModel = (modelId: string, provider: string) => {
3747
SettingsService.getInstance().setSelectedModel(modelId);
@@ -50,6 +60,8 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
5060
<Sidebar
5161
activePage={activePage}
5262
onChangePage={handlePageChange}
63+
showSettings={showSettings}
64+
setShowSettings={setShowSettings}
5365
/>
5466

5567
<div className="flex flex-col flex-1 w-[calc(100%-68px)] bg-main-background-color">

src/components/layout/Sidebar.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
import React from 'react';
2-
import { MessageSquare, Settings } from 'lucide-react';
2+
import { MessageSquare, Settings, Image } from 'lucide-react';
33

44
interface SidebarProps {
55
activePage: string;
66
onChangePage: (page: string) => void;
7+
showSettings: boolean;
8+
setShowSettings: (showSettings: boolean) => void;
79
}
810

9-
export const Sidebar: React.FC<SidebarProps> = ({ activePage, onChangePage }) => {
11+
export const Sidebar: React.FC<SidebarProps> = ({
12+
activePage,
13+
onChangePage,
14+
showSettings,
15+
setShowSettings
16+
}) => {
17+
18+
const getActivePage = () => {
19+
if(showSettings){
20+
return 'settings';
21+
}
22+
else if(activePage === 'chat'){
23+
return 'chat';
24+
}
25+
else if(activePage === 'image'){
26+
return 'image';
27+
}
28+
29+
return '';
30+
}
31+
1032
return (
1133
<div className="w-[68px] h-full bg-main-background-color flex flex-col">
1234

1335
{/* Navigation buttons */}
1436
<div className="flex flex-col items-center flex-1 pt-2">
1537
<button
1638
className={`w-12 h-12 rounded-lg flex items-center justify-center transition-all duration-200 ${
17-
activePage === 'chat'
39+
getActivePage() === 'chat'
1840
? 'navigation-item-selected navigation-item-text'
1941
: 'navigation-item navigation-item-text'
2042
}`}
@@ -24,18 +46,30 @@ export const Sidebar: React.FC<SidebarProps> = ({ activePage, onChangePage }) =>
2446
<MessageSquare size={22} />
2547
</button>
2648

49+
<button
50+
className={`w-12 h-12 rounded-lg flex items-center justify-center transition-all duration-200 ${
51+
getActivePage() === 'image'
52+
? 'navigation-item-selected navigation-item-text'
53+
: 'navigation-item navigation-item-text'
54+
}`}
55+
onClick={() => onChangePage('image')}
56+
aria-label="Image Generation"
57+
>
58+
<Image size={22} />
59+
</button>
60+
2761
{/* Add more navigation buttons here as needed */}
2862
</div>
2963

3064
{/* Settings button at bottom */}
3165
<div className="flex justify-center mb-4">
3266
<button
3367
className={`flex items-center justify-center w-12 h-12 rounded-lg transition-all duration-200 ${
34-
activePage === 'settings'
68+
getActivePage() === 'settings'
3569
? 'navigation-item-selected navigation-item-text'
3670
: 'navigation-item navigation-item-text'
3771
}`}
38-
onClick={() => onChangePage('settings')}
72+
onClick={() => setShowSettings(true)}
3973
aria-label="Settings"
4074
>
4175
<Settings size={22} />
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { useState, useEffect } from 'react';
2+
import { SettingsService, SETTINGS_CHANGE_EVENT } from '../../services/settings-service';
3+
4+
export const ImageGenerationPage = () => {
5+
const [prompt, setPrompt] = useState('');
6+
const [isGenerating, setIsGenerating] = useState(false);
7+
const [imageResult, setImageResult] = useState<string | null>(null);
8+
const [error, setError] = useState<Error | null>(null);
9+
const [selectedModel, setSelectedModel] = useState('');
10+
const [selectedProvider, setSelectedProvider] = useState('');
11+
const [isApiKeyMissing, setIsApiKeyMissing] = useState(true);
12+
13+
// Check if API key is available
14+
useEffect(() => {
15+
setIsApiKeyMissing(!SettingsService.getInstance().getApiKey());
16+
setSelectedProvider(SettingsService.getInstance().getSelectedProvider());
17+
setSelectedModel(SettingsService.getInstance().getSelectedModel());
18+
19+
const handleSettingsChange = () => {
20+
setSelectedProvider(SettingsService.getInstance().getSelectedProvider());
21+
setSelectedModel(SettingsService.getInstance().getSelectedModel());
22+
setIsApiKeyMissing(!SettingsService.getInstance().getApiKey());
23+
};
24+
25+
window.addEventListener(SETTINGS_CHANGE_EVENT, handleSettingsChange);
26+
27+
return () => {
28+
window.removeEventListener(SETTINGS_CHANGE_EVENT, handleSettingsChange);
29+
};
30+
}, []);
31+
32+
// Handle generating an image (mock function)
33+
const handleGenerateImage = async () => {
34+
if (!prompt.trim()) return;
35+
36+
setIsGenerating(true);
37+
setError(null);
38+
39+
try {
40+
// In a real implementation, this would call an actual image generation service
41+
// For now, just simulate the process with a timeout
42+
await new Promise(resolve => setTimeout(resolve, 2000));
43+
44+
// For demo purposes, just show a placeholder result
45+
setImageResult(`https://placehold.co/512x512/eee/999?text=${encodeURIComponent(prompt)}`);
46+
} catch (err) {
47+
setError(err as Error);
48+
} finally {
49+
setIsGenerating(false);
50+
}
51+
};
52+
53+
return (
54+
<div className="flex flex-col w-full h-full p-6 bg-white">
55+
{isApiKeyMissing && (
56+
<div className="p-2 mb-4 text-sm text-center text-yellow-800 bg-yellow-100">
57+
Please set your API key for the selected provider in the settings.
58+
</div>
59+
)}
60+
61+
<h1 className="mb-6 text-2xl font-bold">AI Image Generation</h1>
62+
63+
<div className="flex flex-col gap-6">
64+
{/* Input section */}
65+
<div className="flex flex-col gap-3">
66+
<label htmlFor="prompt" className="font-medium">
67+
Prompt
68+
</label>
69+
<textarea
70+
id="prompt"
71+
className="w-full p-3 border rounded-lg resize-none"
72+
rows={5}
73+
placeholder="Describe the image you want to generate..."
74+
value={prompt}
75+
onChange={(e) => setPrompt(e.target.value)}
76+
/>
77+
78+
<div className="flex items-center justify-between">
79+
<div className="text-sm text-gray-500">
80+
Using model: {selectedModel || 'None selected'}
81+
</div>
82+
<button
83+
className="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50"
84+
onClick={handleGenerateImage}
85+
disabled={isGenerating || !prompt.trim() || isApiKeyMissing}
86+
>
87+
{isGenerating ? 'Generating...' : 'Generate Image'}
88+
</button>
89+
</div>
90+
</div>
91+
92+
{/* Error message */}
93+
{error && (
94+
<div className="p-3 text-red-700 bg-red-100 rounded-lg">
95+
Error: {error.message}
96+
</div>
97+
)}
98+
99+
{/* Results section */}
100+
{imageResult && (
101+
<div className="flex flex-col gap-3">
102+
<h2 className="text-xl font-medium">Generated Image</h2>
103+
<div className="flex justify-center p-2 border rounded-lg">
104+
<img
105+
src={imageResult}
106+
alt="Generated from AI"
107+
className="object-contain max-w-full max-h-96"
108+
/>
109+
</div>
110+
<div className="flex justify-end">
111+
<button className="px-3 py-1 border rounded-lg hover:bg-gray-100">
112+
Download
113+
</button>
114+
</div>
115+
</div>
116+
)}
117+
</div>
118+
</div>
119+
);
120+
};
121+
122+
export default ImageGenerationPage;

src/services/message-helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ export class MessageHelper {
346346
const fileContent: FilePart = {
347347
type: 'file',
348348
data: content.content,
349-
mimeType: 'application/pdf',
349+
mimeType: 'application/pdf', // SDK requires pdf mime type, but it supports all mime types
350350
filename: dataJson.name
351351
}
352352
return fileContent;

src/services/providers/openai-service.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,30 @@ export class OpenAIService implements AiServiceProvider {
157157
// eslint-disable-next-line @typescript-eslint/no-unused-vars
158158
options: {
159159
size?: `${number}x${number}`;
160+
aspectRatio?: `${number}:${number}`;
160161
style?: string;
161162
quality?: string;
162163
} = {}
164+
163165
): Promise<string[]> {
164-
throw new Error('Not implemented');
166+
167+
// const imageModel = openai.imageModel('dall-e-3');
168+
169+
// const result = await imageModel.doGenerate({
170+
// prompt: prompt,
171+
// n: 1,
172+
// size: options.size || '1024x1024',
173+
// aspectRatio: options.aspectRatio || '1:1',
174+
// seed: 42,
175+
// providerOptions: {
176+
// "openai": {
177+
// "style": options.style || 'vivid'
178+
// }
179+
// }
180+
// });
181+
182+
// return result.images;
183+
184+
return [];
165185
}
166186
}

src/services/settings-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export class SettingsService {
319319
/**
320320
* Save settings to database
321321
*/
322-
private async saveSettings(): Promise<void> {
322+
public async saveSettings(): Promise<void> {
323323
try {
324324
await this.dbService.saveSettings(this.settings);
325325

0 commit comments

Comments
 (0)