suisuyy commited on
Commit ·
90f5e5b
1
Parent(s): dbeee1d
Add generated image URL handling in AiEditModal and update useAiFeatures hook; improve prompt change logic
Browse files- App.tsx +6 -4
- components/AiEditModal.tsx +18 -2
- components/CanvasComponent.tsx +1 -1
- hooks/useAiFeatures.ts +16 -10
- translations.ts +3 -1
App.tsx
CHANGED
|
@@ -82,12 +82,13 @@ const App: React.FC = () => {
|
|
| 82 |
aiEditError,
|
| 83 |
isAskingAi,
|
| 84 |
askUrl,
|
|
|
|
| 85 |
handleMagicUpload,
|
| 86 |
handleGenerateAiImage,
|
| 87 |
handleAskAi,
|
| 88 |
handleCancelAiEdit,
|
| 89 |
setAiPrompt,
|
| 90 |
-
|
| 91 |
} = useAiFeatures({
|
| 92 |
currentDataURL,
|
| 93 |
showToast,
|
|
@@ -104,8 +105,8 @@ const App: React.FC = () => {
|
|
| 104 |
const handlePromptChange = (newPrompt: string) => {
|
| 105 |
setAiPrompt(newPrompt);
|
| 106 |
// Clear the previous "Ask" response when the user types a new prompt
|
| 107 |
-
if (askUrl !== null) {
|
| 108 |
-
|
| 109 |
}
|
| 110 |
};
|
| 111 |
|
|
@@ -198,6 +199,7 @@ const App: React.FC = () => {
|
|
| 198 |
isAsking={isAskingAi}
|
| 199 |
error={aiEditError}
|
| 200 |
askUrl={askUrl}
|
|
|
|
| 201 |
t={t}
|
| 202 |
/>
|
| 203 |
)}
|
|
@@ -264,4 +266,4 @@ const App: React.FC = () => {
|
|
| 264 |
);
|
| 265 |
};
|
| 266 |
|
| 267 |
-
export default App;
|
|
|
|
| 82 |
aiEditError,
|
| 83 |
isAskingAi,
|
| 84 |
askUrl,
|
| 85 |
+
generatedImageUrl,
|
| 86 |
handleMagicUpload,
|
| 87 |
handleGenerateAiImage,
|
| 88 |
handleAskAi,
|
| 89 |
handleCancelAiEdit,
|
| 90 |
setAiPrompt,
|
| 91 |
+
clearAiOutputs,
|
| 92 |
} = useAiFeatures({
|
| 93 |
currentDataURL,
|
| 94 |
showToast,
|
|
|
|
| 105 |
const handlePromptChange = (newPrompt: string) => {
|
| 106 |
setAiPrompt(newPrompt);
|
| 107 |
// Clear the previous "Ask" response when the user types a new prompt
|
| 108 |
+
if (askUrl !== null || generatedImageUrl !== null) {
|
| 109 |
+
clearAiOutputs();
|
| 110 |
}
|
| 111 |
};
|
| 112 |
|
|
|
|
| 199 |
isAsking={isAskingAi}
|
| 200 |
error={aiEditError}
|
| 201 |
askUrl={askUrl}
|
| 202 |
+
generatedImageUrl={generatedImageUrl}
|
| 203 |
t={t}
|
| 204 |
/>
|
| 205 |
)}
|
|
|
|
| 266 |
);
|
| 267 |
};
|
| 268 |
|
| 269 |
+
export default App;
|
components/AiEditModal.tsx
CHANGED
|
@@ -14,6 +14,7 @@ interface AiEditModalProps {
|
|
| 14 |
isAsking: boolean;
|
| 15 |
error: string | null;
|
| 16 |
askUrl: string | null;
|
|
|
|
| 17 |
t: TFunction;
|
| 18 |
}
|
| 19 |
|
|
@@ -29,6 +30,7 @@ const AiEditModal: React.FC<AiEditModalProps> = ({
|
|
| 29 |
isAsking,
|
| 30 |
error,
|
| 31 |
askUrl,
|
|
|
|
| 32 |
t,
|
| 33 |
}) => {
|
| 34 |
const iframeContainerRef = useRef<HTMLDivElement>(null);
|
|
@@ -87,7 +89,7 @@ const AiEditModal: React.FC<AiEditModalProps> = ({
|
|
| 87 |
</div>
|
| 88 |
|
| 89 |
{error && (
|
| 90 |
-
<div id="ai-edit-error" className="mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-md text-sm" role="alert">
|
| 91 |
<p className="font-semibold">{t('errorPrefix')}</p>
|
| 92 |
<p>{error}</p>
|
| 93 |
</div>
|
|
@@ -136,6 +138,20 @@ const AiEditModal: React.FC<AiEditModalProps> = ({
|
|
| 136 |
</button>
|
| 137 |
</div>
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
{askUrl && (
|
| 140 |
<div ref={iframeContainerRef} className="mt-4 pt-2">
|
| 141 |
<iframe
|
|
@@ -150,4 +166,4 @@ const AiEditModal: React.FC<AiEditModalProps> = ({
|
|
| 150 |
);
|
| 151 |
};
|
| 152 |
|
| 153 |
-
export default AiEditModal;
|
|
|
|
| 14 |
isAsking: boolean;
|
| 15 |
error: string | null;
|
| 16 |
askUrl: string | null;
|
| 17 |
+
generatedImageUrl: string | null;
|
| 18 |
t: TFunction;
|
| 19 |
}
|
| 20 |
|
|
|
|
| 30 |
isAsking,
|
| 31 |
error,
|
| 32 |
askUrl,
|
| 33 |
+
generatedImageUrl,
|
| 34 |
t,
|
| 35 |
}) => {
|
| 36 |
const iframeContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
| 89 |
</div>
|
| 90 |
|
| 91 |
{error && (
|
| 92 |
+
<div id="ai-edit-error" className="mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-md text-sm overflow-y-auto max-h-32" role="alert">
|
| 93 |
<p className="font-semibold">{t('errorPrefix')}</p>
|
| 94 |
<p>{error}</p>
|
| 95 |
</div>
|
|
|
|
| 138 |
</button>
|
| 139 |
</div>
|
| 140 |
|
| 141 |
+
{generatedImageUrl && (
|
| 142 |
+
<div className="mt-4 p-3 bg-slate-50 border border-slate-200 rounded-md text-sm">
|
| 143 |
+
<p className="text-slate-600 font-medium">{t('aiGeneratedImageURLMessage')}</p>
|
| 144 |
+
<a
|
| 145 |
+
href={generatedImageUrl}
|
| 146 |
+
target="_blank"
|
| 147 |
+
rel="noopener noreferrer"
|
| 148 |
+
className="text-blue-600 hover:text-blue-800 break-all underline"
|
| 149 |
+
>
|
| 150 |
+
{generatedImageUrl}
|
| 151 |
+
</a>
|
| 152 |
+
</div>
|
| 153 |
+
)}
|
| 154 |
+
|
| 155 |
{askUrl && (
|
| 156 |
<div ref={iframeContainerRef} className="mt-4 pt-2">
|
| 157 |
<iframe
|
|
|
|
| 166 |
);
|
| 167 |
};
|
| 168 |
|
| 169 |
+
export default AiEditModal;
|
components/CanvasComponent.tsx
CHANGED
|
@@ -202,4 +202,4 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({
|
|
| 202 |
);
|
| 203 |
};
|
| 204 |
|
| 205 |
-
export default CanvasComponent;
|
|
|
|
| 202 |
);
|
| 203 |
};
|
| 204 |
|
| 205 |
+
export default CanvasComponent;
|
hooks/useAiFeatures.ts
CHANGED
|
@@ -20,12 +20,13 @@ interface AiFeaturesHook {
|
|
| 20 |
aiEditError: string | null;
|
| 21 |
isAskingAi: boolean;
|
| 22 |
askUrl: string | null;
|
|
|
|
| 23 |
handleMagicUpload: () => Promise<void>;
|
| 24 |
handleGenerateAiImage: () => Promise<void>;
|
| 25 |
handleAskAi: () => Promise<void>;
|
| 26 |
handleCancelAiEdit: () => void;
|
| 27 |
setAiPrompt: React.Dispatch<React.SetStateAction<string>>;
|
| 28 |
-
|
| 29 |
}
|
| 30 |
|
| 31 |
interface UseAiFeaturesProps {
|
|
@@ -61,9 +62,11 @@ export const useAiFeatures = ({
|
|
| 61 |
const [aiEditError, setAiEditError] = useState<string | null>(null);
|
| 62 |
const [isAskingAi, setIsAskingAi] = useState<boolean>(false);
|
| 63 |
const [askUrl, setAskUrl] = useState<string | null>(null);
|
|
|
|
| 64 |
|
| 65 |
-
const
|
| 66 |
setAskUrl(null);
|
|
|
|
| 67 |
}, []);
|
| 68 |
|
| 69 |
const loadAiImageOntoCanvas = useCallback((aiImageDataUrl: string) => {
|
|
@@ -168,7 +171,7 @@ export const useAiFeatures = ({
|
|
| 168 |
setShowAiEditModal(true);
|
| 169 |
setAiPrompt('');
|
| 170 |
setAiEditError(null);
|
| 171 |
-
|
| 172 |
|
| 173 |
} catch (error: any) {
|
| 174 |
console.error('Magic upload error:', error);
|
|
@@ -176,7 +179,7 @@ export const useAiFeatures = ({
|
|
| 176 |
} finally {
|
| 177 |
setIsMagicUploading(false);
|
| 178 |
}
|
| 179 |
-
}, [isMagicUploading, currentDataURL, showToast,
|
| 180 |
|
| 181 |
const handleGenerateAiImage = useCallback(async () => {
|
| 182 |
if (!aiPrompt.trim() || !sharedImageUrlForAi) {
|
|
@@ -185,7 +188,7 @@ export const useAiFeatures = ({
|
|
| 185 |
}
|
| 186 |
setIsGeneratingAiImage(true);
|
| 187 |
setAiEditError(null);
|
| 188 |
-
|
| 189 |
showToast(t('generatingAiImage'), 'info');
|
| 190 |
|
| 191 |
const encodedPrompt = encodeURIComponent(aiPrompt);
|
|
@@ -208,6 +211,8 @@ export const useAiFeatures = ({
|
|
| 208 |
}
|
| 209 |
finalApiUrl = finalApiUrl.replace('{size_params}', sizeParamsString);
|
| 210 |
}
|
|
|
|
|
|
|
| 211 |
|
| 212 |
try {
|
| 213 |
const response = await fetch(finalApiUrl);
|
|
@@ -247,7 +252,7 @@ export const useAiFeatures = ({
|
|
| 247 |
setAiEditError(error.message || t('unknownAiError'));
|
| 248 |
setIsGeneratingAiImage(false);
|
| 249 |
}
|
| 250 |
-
}, [aiPrompt, sharedImageUrlForAi, showToast, loadAiImageOntoCanvas, aiImageQuality, aiApiEndpoint, aiDimensionsMode, currentCanvasWidth, currentCanvasHeight,
|
| 251 |
|
| 252 |
const handleAskAi = useCallback(async () => {
|
| 253 |
if (!aiPrompt.trim() || !sharedImageUrlForAi) {
|
|
@@ -256,7 +261,7 @@ export const useAiFeatures = ({
|
|
| 256 |
}
|
| 257 |
setIsAskingAi(true);
|
| 258 |
setAiEditError(null);
|
| 259 |
-
|
| 260 |
|
| 261 |
try {
|
| 262 |
const encodedPrompt = encodeURIComponent(aiPrompt);
|
|
@@ -270,7 +275,7 @@ export const useAiFeatures = ({
|
|
| 270 |
} finally {
|
| 271 |
setIsAskingAi(false);
|
| 272 |
}
|
| 273 |
-
}, [aiPrompt, sharedImageUrlForAi, t]);
|
| 274 |
|
| 275 |
|
| 276 |
const handleCancelAiEdit = () => {
|
|
@@ -285,7 +290,7 @@ export const useAiFeatures = ({
|
|
| 285 |
setAiPrompt('');
|
| 286 |
setAiEditError(null);
|
| 287 |
setSharedImageUrlForAi(null);
|
| 288 |
-
|
| 289 |
};
|
| 290 |
|
| 291 |
return {
|
|
@@ -297,11 +302,12 @@ export const useAiFeatures = ({
|
|
| 297 |
aiEditError,
|
| 298 |
isAskingAi,
|
| 299 |
askUrl,
|
|
|
|
| 300 |
handleMagicUpload,
|
| 301 |
handleGenerateAiImage,
|
| 302 |
handleAskAi,
|
| 303 |
handleCancelAiEdit,
|
| 304 |
setAiPrompt,
|
| 305 |
-
|
| 306 |
};
|
| 307 |
};
|
|
|
|
| 20 |
aiEditError: string | null;
|
| 21 |
isAskingAi: boolean;
|
| 22 |
askUrl: string | null;
|
| 23 |
+
generatedImageUrl: string | null;
|
| 24 |
handleMagicUpload: () => Promise<void>;
|
| 25 |
handleGenerateAiImage: () => Promise<void>;
|
| 26 |
handleAskAi: () => Promise<void>;
|
| 27 |
handleCancelAiEdit: () => void;
|
| 28 |
setAiPrompt: React.Dispatch<React.SetStateAction<string>>;
|
| 29 |
+
clearAiOutputs: () => void;
|
| 30 |
}
|
| 31 |
|
| 32 |
interface UseAiFeaturesProps {
|
|
|
|
| 62 |
const [aiEditError, setAiEditError] = useState<string | null>(null);
|
| 63 |
const [isAskingAi, setIsAskingAi] = useState<boolean>(false);
|
| 64 |
const [askUrl, setAskUrl] = useState<string | null>(null);
|
| 65 |
+
const [generatedImageUrl, setGeneratedImageUrl] = useState<string | null>(null);
|
| 66 |
|
| 67 |
+
const clearAiOutputs = useCallback(() => {
|
| 68 |
setAskUrl(null);
|
| 69 |
+
setGeneratedImageUrl(null);
|
| 70 |
}, []);
|
| 71 |
|
| 72 |
const loadAiImageOntoCanvas = useCallback((aiImageDataUrl: string) => {
|
|
|
|
| 171 |
setShowAiEditModal(true);
|
| 172 |
setAiPrompt('');
|
| 173 |
setAiEditError(null);
|
| 174 |
+
clearAiOutputs();
|
| 175 |
|
| 176 |
} catch (error: any) {
|
| 177 |
console.error('Magic upload error:', error);
|
|
|
|
| 179 |
} finally {
|
| 180 |
setIsMagicUploading(false);
|
| 181 |
}
|
| 182 |
+
}, [isMagicUploading, currentDataURL, showToast, clearAiOutputs, t]);
|
| 183 |
|
| 184 |
const handleGenerateAiImage = useCallback(async () => {
|
| 185 |
if (!aiPrompt.trim() || !sharedImageUrlForAi) {
|
|
|
|
| 188 |
}
|
| 189 |
setIsGeneratingAiImage(true);
|
| 190 |
setAiEditError(null);
|
| 191 |
+
clearAiOutputs();
|
| 192 |
showToast(t('generatingAiImage'), 'info');
|
| 193 |
|
| 194 |
const encodedPrompt = encodeURIComponent(aiPrompt);
|
|
|
|
| 211 |
}
|
| 212 |
finalApiUrl = finalApiUrl.replace('{size_params}', sizeParamsString);
|
| 213 |
}
|
| 214 |
+
|
| 215 |
+
setGeneratedImageUrl(finalApiUrl);
|
| 216 |
|
| 217 |
try {
|
| 218 |
const response = await fetch(finalApiUrl);
|
|
|
|
| 252 |
setAiEditError(error.message || t('unknownAiError'));
|
| 253 |
setIsGeneratingAiImage(false);
|
| 254 |
}
|
| 255 |
+
}, [aiPrompt, sharedImageUrlForAi, showToast, loadAiImageOntoCanvas, aiImageQuality, aiApiEndpoint, aiDimensionsMode, currentCanvasWidth, currentCanvasHeight, clearAiOutputs, t]);
|
| 256 |
|
| 257 |
const handleAskAi = useCallback(async () => {
|
| 258 |
if (!aiPrompt.trim() || !sharedImageUrlForAi) {
|
|
|
|
| 261 |
}
|
| 262 |
setIsAskingAi(true);
|
| 263 |
setAiEditError(null);
|
| 264 |
+
clearAiOutputs();
|
| 265 |
|
| 266 |
try {
|
| 267 |
const encodedPrompt = encodeURIComponent(aiPrompt);
|
|
|
|
| 275 |
} finally {
|
| 276 |
setIsAskingAi(false);
|
| 277 |
}
|
| 278 |
+
}, [aiPrompt, sharedImageUrlForAi, t, clearAiOutputs]);
|
| 279 |
|
| 280 |
|
| 281 |
const handleCancelAiEdit = () => {
|
|
|
|
| 290 |
setAiPrompt('');
|
| 291 |
setAiEditError(null);
|
| 292 |
setSharedImageUrlForAi(null);
|
| 293 |
+
clearAiOutputs();
|
| 294 |
};
|
| 295 |
|
| 296 |
return {
|
|
|
|
| 302 |
aiEditError,
|
| 303 |
isAskingAi,
|
| 304 |
askUrl,
|
| 305 |
+
generatedImageUrl,
|
| 306 |
handleMagicUpload,
|
| 307 |
handleGenerateAiImage,
|
| 308 |
handleAskAi,
|
| 309 |
handleCancelAiEdit,
|
| 310 |
setAiPrompt,
|
| 311 |
+
clearAiOutputs,
|
| 312 |
};
|
| 313 |
};
|
translations.ts
CHANGED
|
@@ -34,6 +34,7 @@ const translations = {
|
|
| 34 |
aiAsking: 'Asking...',
|
| 35 |
aiGenerateImage: 'Generate Image',
|
| 36 |
aiGenerating: 'Generating...',
|
|
|
|
| 37 |
|
| 38 |
// Settings Panel
|
| 39 |
settingsTitle: 'Application Settings',
|
|
@@ -148,6 +149,7 @@ const translations = {
|
|
| 148 |
aiAsking: '提问中...',
|
| 149 |
aiGenerateImage: '生成图片',
|
| 150 |
aiGenerating: '生成中...',
|
|
|
|
| 151 |
|
| 152 |
// Settings Panel
|
| 153 |
settingsTitle: '应用设置',
|
|
@@ -257,4 +259,4 @@ export const getTranslator = () => {
|
|
| 257 |
}
|
| 258 |
return text;
|
| 259 |
};
|
| 260 |
-
};
|
|
|
|
| 34 |
aiAsking: 'Asking...',
|
| 35 |
aiGenerateImage: 'Generate Image',
|
| 36 |
aiGenerating: 'Generating...',
|
| 37 |
+
aiGeneratedImageURLMessage: 'You can get the image via link:',
|
| 38 |
|
| 39 |
// Settings Panel
|
| 40 |
settingsTitle: 'Application Settings',
|
|
|
|
| 149 |
aiAsking: '提问中...',
|
| 150 |
aiGenerateImage: '生成图片',
|
| 151 |
aiGenerating: '生成中...',
|
| 152 |
+
aiGeneratedImageURLMessage: '您可以通过以下链接获取图片:',
|
| 153 |
|
| 154 |
// Settings Panel
|
| 155 |
settingsTitle: '应用设置',
|
|
|
|
| 259 |
}
|
| 260 |
return text;
|
| 261 |
};
|
| 262 |
+
};
|