suisuyy commited on
Commit
0817dc1
·
1 Parent(s): 7df968b

Rename project from xpaintai to paintai; update canvas dimensions and AI API endpoint; enhance AI features with dimensions mode

Browse files
App.tsx CHANGED
@@ -10,7 +10,7 @@ import { DEFAULT_AI_API_ENDPOINT } from './constants';
10
  // Custom Hooks
11
  import { useToasts } from './hooks/useToasts';
12
  import { useCanvasHistory } from './hooks/useCanvasHistory';
13
- import { useAiFeatures, AiImageQuality } from './hooks/useAiFeatures';
14
  import { useDrawingTools } from './hooks/useDrawingTools';
15
  import { useFullscreen } from './hooks/useFullscreen';
16
  import { useConfirmModal } from './hooks/useConfirmModal';
@@ -23,8 +23,10 @@ const App: React.FC = () => {
23
 
24
  // Settings specific states
25
  const [showZoomSlider, setShowZoomSlider] = useState<boolean>(true);
26
- const [aiImageQuality, setAiImageQuality] = useState<AiImageQuality>('low');
27
  const [aiApiEndpoint, setAiApiEndpoint] = useState<string>(DEFAULT_AI_API_ENDPOINT);
 
 
28
 
29
  // Custom Hooks Instantiation
30
  const { toasts, showToast } = useToasts();
@@ -84,6 +86,7 @@ const App: React.FC = () => {
84
  setZoomLevel,
85
  aiImageQuality,
86
  aiApiEndpoint,
 
87
  currentCanvasWidth: canvasWidth, // Pass current canvas dimensions
88
  currentCanvasHeight: canvasHeight, // Pass current canvas dimensions
89
  });
@@ -192,6 +195,8 @@ const App: React.FC = () => {
192
  onAiImageQualityChange={setAiImageQuality}
193
  aiApiEndpoint={aiApiEndpoint}
194
  onAiApiEndpointChange={setAiApiEndpoint}
 
 
195
  isFullscreenActive={isFullscreenActive}
196
  onToggleFullscreen={toggleFullscreen}
197
  currentCanvasWidth={canvasWidth}
@@ -233,4 +238,4 @@ const App: React.FC = () => {
233
  );
234
  };
235
 
236
- export default App;
 
10
  // Custom Hooks
11
  import { useToasts } from './hooks/useToasts';
12
  import { useCanvasHistory } from './hooks/useCanvasHistory';
13
+ import { useAiFeatures, AiImageQuality, AiDimensionsMode } from './hooks/useAiFeatures';
14
  import { useDrawingTools } from './hooks/useDrawingTools';
15
  import { useFullscreen } from './hooks/useFullscreen';
16
  import { useConfirmModal } from './hooks/useConfirmModal';
 
23
 
24
  // Settings specific states
25
  const [showZoomSlider, setShowZoomSlider] = useState<boolean>(true);
26
+ const [aiImageQuality, setAiImageQuality] = useState<AiImageQuality>('medium');
27
  const [aiApiEndpoint, setAiApiEndpoint] = useState<string>(DEFAULT_AI_API_ENDPOINT);
28
+ const [aiDimensionsMode, setAiDimensionsMode] = useState<AiDimensionsMode>('match_canvas');
29
+
30
 
31
  // Custom Hooks Instantiation
32
  const { toasts, showToast } = useToasts();
 
86
  setZoomLevel,
87
  aiImageQuality,
88
  aiApiEndpoint,
89
+ aiDimensionsMode,
90
  currentCanvasWidth: canvasWidth, // Pass current canvas dimensions
91
  currentCanvasHeight: canvasHeight, // Pass current canvas dimensions
92
  });
 
195
  onAiImageQualityChange={setAiImageQuality}
196
  aiApiEndpoint={aiApiEndpoint}
197
  onAiApiEndpointChange={setAiApiEndpoint}
198
+ aiDimensionsMode={aiDimensionsMode}
199
+ onAiDimensionsModeChange={setAiDimensionsMode}
200
  isFullscreenActive={isFullscreenActive}
201
  onToggleFullscreen={toggleFullscreen}
202
  currentCanvasWidth={canvasWidth}
 
238
  );
239
  };
240
 
241
+ export default App;
components/AiEditModal.tsx CHANGED
@@ -49,7 +49,7 @@ const AiEditModal: React.FC<AiEditModalProps> = ({
49
  <img
50
  src={imageUrl}
51
  alt="Uploaded image preview"
52
- className="w-auto h-40 rounded border border-slate-300 object-contain mx-auto"
53
  />
54
  </div>
55
 
@@ -63,7 +63,7 @@ const AiEditModal: React.FC<AiEditModalProps> = ({
63
  onChange={(e) => onPromptChange(e.target.value)}
64
  placeholder="e.g., 'make the cat wear a party hat', 'change background to a beach'"
65
  rows={3}
66
- className="w-full p-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-xl"
67
  disabled={isLoading}
68
  aria-describedby={error ? "ai-edit-error" : undefined}
69
  />
 
49
  <img
50
  src={imageUrl}
51
  alt="Uploaded image preview"
52
+ className="max-w-full h-auto max-h-48 rounded border border-slate-300 object-contain mx-auto"
53
  />
54
  </div>
55
 
 
63
  onChange={(e) => onPromptChange(e.target.value)}
64
  placeholder="e.g., 'make the cat wear a party hat', 'change background to a beach'"
65
  rows={3}
66
+ className="w-full p-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
67
  disabled={isLoading}
68
  aria-describedby={error ? "ai-edit-error" : undefined}
69
  />
components/SettingsPanel.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import React from 'react';
2
  import { CloseIcon, FullscreenEnterIcon, FullscreenExitIcon } from './icons'; // Import fullscreen icons
3
- import { AiImageQuality } from '../hooks/useAiFeatures';
4
 
5
  interface SettingsPanelProps {
6
  isOpen: boolean;
@@ -11,6 +11,8 @@ interface SettingsPanelProps {
11
  onAiImageQualityChange: (quality: AiImageQuality) => void;
12
  aiApiEndpoint: string;
13
  onAiApiEndpointChange: (endpoint: string) => void;
 
 
14
  isFullscreenActive: boolean;
15
  onToggleFullscreen: () => void;
16
  currentCanvasWidth: number;
@@ -19,13 +21,26 @@ interface SettingsPanelProps {
19
  }
20
 
21
  const CANVAS_SIZE_OPTIONS = [
22
- { label: '1000 x 1000 px', width: 1000, height: 1000 },
 
23
  { label: '1200 x 1200 px', width: 1200, height: 1200 },
24
  { label: '1600 x 1600 px', width: 1600, height: 1600 },
25
  { label: '2000 x 2000 px', width: 2000, height: 2000 },
26
  { label: '4000 x 4000 px', width: 4000, height: 4000 },
27
  ];
28
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  const SettingsPanel: React.FC<SettingsPanelProps> = ({
30
  isOpen,
31
  onClose,
@@ -35,6 +50,8 @@ const SettingsPanel: React.FC<SettingsPanelProps> = ({
35
  onAiImageQualityChange,
36
  aiApiEndpoint,
37
  onAiApiEndpointChange,
 
 
38
  isFullscreenActive,
39
  onToggleFullscreen,
40
  currentCanvasWidth,
@@ -157,26 +174,50 @@ const SettingsPanel: React.FC<SettingsPanelProps> = ({
157
  <h3 className="text-md font-medium text-slate-700 mb-2">AI Image Generation</h3>
158
 
159
  <div className="bg-slate-50 p-3 rounded-md border border-slate-200 mb-4">
160
- <label htmlFor="aiApiEndpointInput" className="block text-sm text-slate-600 mb-1">
161
- API Endpoint URL
162
  </label>
163
- <input
164
- type="text"
165
- id="aiApiEndpointInput"
166
- value={aiApiEndpoint}
167
- onChange={(e) => onAiApiEndpointChange(e.target.value)}
168
- className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
169
- placeholder="Enter AI API URL"
170
- aria-describedby="ai-api-description"
171
- />
 
 
 
 
172
  <p id="ai-api-description" className="text-xs text-slate-500 mt-1">
173
- Use <code className="bg-slate-200 px-1 rounded">{'{prompt}'}</code> for text prompt and <code className="bg-slate-200 px-1 rounded">{'{imgurl.url}'}</code> for image URL.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  </p>
175
  </div>
176
 
177
  <div className="bg-slate-50 p-3 rounded-md border border-slate-200">
178
  <label htmlFor="aiImageQualitySelect" className="block text-sm text-slate-600 mb-1">
179
- Image Quality (Pollinations API)
180
  </label>
181
  <select
182
  id="aiImageQualitySelect"
@@ -192,7 +233,7 @@ const SettingsPanel: React.FC<SettingsPanelProps> = ({
192
  ))}
193
  </select>
194
  <p id="ai-quality-description" className="text-xs text-slate-500 mt-1">
195
- This quality setting primarily applies to Pollinations-like APIs. Custom endpoints must manage their own quality parameters if needed.
196
  </p>
197
  </div>
198
  </div>
 
1
  import React from 'react';
2
  import { CloseIcon, FullscreenEnterIcon, FullscreenExitIcon } from './icons'; // Import fullscreen icons
3
+ import { AiImageQuality, AiDimensionsMode } from '../hooks/useAiFeatures';
4
 
5
  interface SettingsPanelProps {
6
  isOpen: boolean;
 
11
  onAiImageQualityChange: (quality: AiImageQuality) => void;
12
  aiApiEndpoint: string;
13
  onAiApiEndpointChange: (endpoint: string) => void;
14
+ aiDimensionsMode: AiDimensionsMode;
15
+ onAiDimensionsModeChange: (mode: AiDimensionsMode) => void;
16
  isFullscreenActive: boolean;
17
  onToggleFullscreen: () => void;
18
  currentCanvasWidth: number;
 
21
  }
22
 
23
  const CANVAS_SIZE_OPTIONS = [
24
+ { label: '1024 x 1536 px', width: 1024, height: 1536 },
25
+ { label: '1024 x 1024 px', width: 1024, height: 1024 },
26
  { label: '1200 x 1200 px', width: 1200, height: 1200 },
27
  { label: '1600 x 1600 px', width: 1600, height: 1600 },
28
  { label: '2000 x 2000 px', width: 2000, height: 2000 },
29
  { label: '4000 x 4000 px', width: 4000, height: 4000 },
30
  ];
31
 
32
+ const AI_API_OPTIONS = [
33
+ {
34
+ label: 'Pollinations AI',
35
+ value: 'https://rpfor.deno.dev/proxy?url=https://image.pollinations.ai/prompt/{prompt}?nologo=true&model=gptimage&image={imgurl.url}&quality={quality}{size_params}'
36
+ },
37
+ {
38
+ label: 'Gemini Simple Get',
39
+ value: 'https://geminisimpleget.deno.dev/prompt/{prompt}?image={imgurl.url}'
40
+ }
41
+ ];
42
+
43
+
44
  const SettingsPanel: React.FC<SettingsPanelProps> = ({
45
  isOpen,
46
  onClose,
 
50
  onAiImageQualityChange,
51
  aiApiEndpoint,
52
  onAiApiEndpointChange,
53
+ aiDimensionsMode,
54
+ onAiDimensionsModeChange,
55
  isFullscreenActive,
56
  onToggleFullscreen,
57
  currentCanvasWidth,
 
174
  <h3 className="text-md font-medium text-slate-700 mb-2">AI Image Generation</h3>
175
 
176
  <div className="bg-slate-50 p-3 rounded-md border border-slate-200 mb-4">
177
+ <label htmlFor="aiApiEndpointSelect" className="block text-sm text-slate-600 mb-1">
178
+ API Endpoint
179
  </label>
180
+ <select
181
+ id="aiApiEndpointSelect"
182
+ value={aiApiEndpoint}
183
+ onChange={(e) => onAiApiEndpointChange(e.target.value)}
184
+ className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
185
+ aria-describedby="ai-api-description"
186
+ >
187
+ {AI_API_OPTIONS.map((option) => (
188
+ <option key={option.value} value={option.value}>
189
+ {option.label}
190
+ </option>
191
+ ))}
192
+ </select>
193
  <p id="ai-api-description" className="text-xs text-slate-500 mt-1">
194
+ Select an API for image generation. Placeholders are filled automatically.
195
+ </p>
196
+ </div>
197
+
198
+ <div className="bg-slate-50 p-3 rounded-md border border-slate-200 mb-4">
199
+ <label htmlFor="aiDimensionsModeSelect" className="block text-sm text-slate-600 mb-1">
200
+ AI Image Dimensions
201
+ </label>
202
+ <select
203
+ id="aiDimensionsModeSelect"
204
+ value={aiDimensionsMode}
205
+ onChange={(e) => onAiDimensionsModeChange(e.target.value as AiDimensionsMode)}
206
+ className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
207
+ aria-describedby="ai-dimensions-description"
208
+ >
209
+ <option value="api_default">API Default (no size)</option>
210
+ <option value="match_canvas">Match Canvas Size</option>
211
+ <option value="fixed_1024">Fixed 1024x1024</option>
212
+ </select>
213
+ <p id="ai-dimensions-description" className="text-xs text-slate-500 mt-1">
214
+ Controls `width` & `height` params for APIs that support them (e.g., Pollinations AI).
215
  </p>
216
  </div>
217
 
218
  <div className="bg-slate-50 p-3 rounded-md border border-slate-200">
219
  <label htmlFor="aiImageQualitySelect" className="block text-sm text-slate-600 mb-1">
220
+ Image Quality
221
  </label>
222
  <select
223
  id="aiImageQualitySelect"
 
233
  ))}
234
  </select>
235
  <p id="ai-quality-description" className="text-xs text-slate-500 mt-1">
236
+ Used for APIs that support a 'quality' parameter (e.g., Pollinations AI).
237
  </p>
238
  </div>
239
  </div>
constants.ts CHANGED
@@ -4,6 +4,6 @@ export const CANVAS_AUTOSAVE_KEY = 'autosavedCanvas';
4
  export const MAX_HISTORY_STEPS = 100;
5
  export const DEFAULT_PEN_COLOR = '#000000';
6
  export const DEFAULT_PEN_SIZE = 5;
7
- export const DEFAULT_CANVAS_WIDTH = 1600;
8
- export const DEFAULT_CANVAS_HEIGHT = 1600;
9
- export const DEFAULT_AI_API_ENDPOINT = 'https://geminisimpleget.deno.dev/prompt/{prompt}?image={imgurl.url}';
 
4
  export const MAX_HISTORY_STEPS = 100;
5
  export const DEFAULT_PEN_COLOR = '#000000';
6
  export const DEFAULT_PEN_SIZE = 5;
7
+ export const DEFAULT_CANVAS_WIDTH = 1024;
8
+ export const DEFAULT_CANVAS_HEIGHT = 1536;
9
+ export const DEFAULT_AI_API_ENDPOINT = 'https://rpfor.deno.dev/proxy?url=https://image.pollinations.ai/prompt/{prompt}?nologo=true&model=gptimage&image={imgurl.url}&quality={quality}{size_params}';
hooks/useAiFeatures.ts CHANGED
@@ -7,6 +7,7 @@ const SHARE_API_URL = 'https://sharefile.suisuy.eu.org';
7
  const MAX_UPLOAD_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
8
 
9
  export type AiImageQuality = 'low' | 'medium' | 'hd';
 
10
 
11
  interface AiFeaturesHook {
12
  isMagicUploading: boolean;
@@ -28,6 +29,7 @@ interface UseAiFeaturesProps {
28
  setZoomLevel: (zoom: number) => void;
29
  aiImageQuality: AiImageQuality;
30
  aiApiEndpoint: string;
 
31
  currentCanvasWidth: number;
32
  currentCanvasHeight: number;
33
  }
@@ -39,6 +41,7 @@ export const useAiFeatures = ({
39
  setZoomLevel,
40
  aiImageQuality,
41
  aiApiEndpoint,
 
42
  currentCanvasWidth,
43
  currentCanvasHeight,
44
  }: UseAiFeaturesProps): AiFeaturesHook => {
@@ -175,26 +178,19 @@ export const useAiFeatures = ({
175
  let finalApiUrl = aiApiEndpoint
176
  .replace('{prompt}', encodedPrompt)
177
  .replace('{imgurl.url}', encodedImageUrl);
178
-
179
- if (aiApiEndpoint.includes('pollinations.ai')) {
180
- const pollinationsParams = new URLSearchParams({
181
- model: 'gptimage',
182
- private: 'true',
183
- quality: aiImageQuality,
184
- safe: 'false',
185
- transparent: 'false',
186
- width: '1024',
187
- height: '1024',
188
- seed: Date.now().toString(),
189
- });
190
 
191
- if (finalApiUrl.endsWith('/')) {
192
- finalApiUrl = `${finalApiUrl}${encodedPrompt}?image=${encodedImageUrl}&${pollinationsParams.toString()}`;
193
- } else if (finalApiUrl.includes('?')) {
194
- finalApiUrl = `${finalApiUrl}&image=${encodedImageUrl}&${pollinationsParams.toString()}`;
195
- } else {
196
- finalApiUrl = `${finalApiUrl}?image=${encodedImageUrl}&${pollinationsParams.toString()}`;
 
 
 
 
197
  }
 
198
  }
199
 
200
  try {
@@ -235,7 +231,7 @@ export const useAiFeatures = ({
235
  setAiEditError(error.message || 'An unknown error occurred during AI image generation.');
236
  setIsGeneratingAiImage(false);
237
  }
238
- }, [aiPrompt, sharedImageUrlForAi, showToast, loadAiImageOntoCanvas, aiImageQuality, aiApiEndpoint]);
239
 
240
 
241
  const handleCancelAiEdit = () => {
 
7
  const MAX_UPLOAD_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
8
 
9
  export type AiImageQuality = 'low' | 'medium' | 'hd';
10
+ export type AiDimensionsMode = 'api_default' | 'match_canvas' | 'fixed_1024';
11
 
12
  interface AiFeaturesHook {
13
  isMagicUploading: boolean;
 
29
  setZoomLevel: (zoom: number) => void;
30
  aiImageQuality: AiImageQuality;
31
  aiApiEndpoint: string;
32
+ aiDimensionsMode: AiDimensionsMode;
33
  currentCanvasWidth: number;
34
  currentCanvasHeight: number;
35
  }
 
41
  setZoomLevel,
42
  aiImageQuality,
43
  aiApiEndpoint,
44
+ aiDimensionsMode,
45
  currentCanvasWidth,
46
  currentCanvasHeight,
47
  }: UseAiFeaturesProps): AiFeaturesHook => {
 
178
  let finalApiUrl = aiApiEndpoint
179
  .replace('{prompt}', encodedPrompt)
180
  .replace('{imgurl.url}', encodedImageUrl);
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ if (finalApiUrl.includes('{quality}')) {
183
+ finalApiUrl = finalApiUrl.replace('{quality}', aiImageQuality);
184
+ }
185
+
186
+ if (finalApiUrl.includes('{size_params}')) {
187
+ let sizeParamsString = '';
188
+ if (aiDimensionsMode === 'match_canvas') {
189
+ sizeParamsString = `&width=${currentCanvasWidth}&height=${currentCanvasHeight}`;
190
+ } else if (aiDimensionsMode === 'fixed_1024') {
191
+ sizeParamsString = `&width=1024&height=1024`;
192
  }
193
+ finalApiUrl = finalApiUrl.replace('{size_params}', sizeParamsString);
194
  }
195
 
196
  try {
 
231
  setAiEditError(error.message || 'An unknown error occurred during AI image generation.');
232
  setIsGeneratingAiImage(false);
233
  }
234
+ }, [aiPrompt, sharedImageUrlForAi, showToast, loadAiImageOntoCanvas, aiImageQuality, aiApiEndpoint, aiDimensionsMode, currentCanvasWidth, currentCanvasHeight]);
235
 
236
 
237
  const handleCancelAiEdit = () => {
metadata.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "name": "xpaintai",
3
  "description": "A simple web-based paint application with features like pen customization, undo/redo, image loading/exporting, and autosave to IndexedDB.",
4
  "requestFramePermissions": [],
5
  "prompt": ""
 
1
  {
2
+ "name": "paintai",
3
  "description": "A simple web-based paint application with features like pen customization, undo/redo, image loading/exporting, and autosave to IndexedDB.",
4
  "requestFramePermissions": [],
5
  "prompt": ""
package.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "name": "xpaintai",
3
  "private": true,
4
  "version": "0.0.0",
5
  "type": "module",
 
1
  {
2
+ "name": "paintai",
3
  "private": true,
4
  "version": "0.0.0",
5
  "type": "module",