@umituz/react-native-ai-generation-content 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/features/audio-generation/index.ts +0 -1
- package/src/features/colorization/index.ts +0 -1
- package/src/features/face-swap/index.ts +0 -1
- package/src/features/future-prediction/index.ts +0 -1
- package/src/features/image-captioning/index.ts +0 -1
- package/src/features/inpainting/index.ts +0 -1
- package/src/features/photo-restoration/index.ts +0 -1
- package/src/features/sketch-to-image/index.ts +0 -1
- package/src/features/style-transfer/index.ts +0 -1
- package/src/features/text-to-image/index.ts +0 -1
- package/src/features/text-to-video/index.ts +0 -1
- package/src/features/upscaling/index.ts +0 -1
- package/src/index.ts +24 -0
- package/src/presentation/components/index.ts +1 -0
- package/src/presentation/components/photo-step/PhotoStep.tsx +96 -0
- package/src/presentation/components/photo-step/index.ts +2 -0
- package/src/presentation/hooks/base/index.ts +9 -0
- package/src/presentation/hooks/base/types.ts +47 -0
- package/src/presentation/hooks/base/use-dual-image-feature.ts +178 -0
- package/src/presentation/hooks/base/use-image-with-prompt-feature.ts +170 -0
- package/src/presentation/hooks/base/use-single-image-feature.ts +154 -0
- package/src/presentation/hooks/index.ts +9 -0
- package/src/presentation/hooks/useGenerationFlow.ts +315 -0
- package/src/presentation/types/flow-config.types.ts +246 -0
- package/src/features/audio-generation/presentation/hooks.ts +0 -39
- package/src/features/colorization/presentation/hooks.ts +0 -39
- package/src/features/face-swap/presentation/hooks.ts +0 -41
- package/src/features/future-prediction/presentation/hooks.ts +0 -39
- package/src/features/image-captioning/presentation/hooks.ts +0 -39
- package/src/features/inpainting/presentation/hooks.ts +0 -39
- package/src/features/photo-restoration/presentation/hooks.ts +0 -39
- package/src/features/sketch-to-image/presentation/hooks.ts +0 -39
- package/src/features/style-transfer/presentation/hooks.ts +0 -39
- package/src/features/text-to-image/presentation/hooks.ts +0 -39
- package/src/features/text-to-video/presentation/hooks.ts +0 -39
- package/src/features/upscaling/presentation/hooks.ts +0 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
68
68
|
"@typescript-eslint/parser": "^7.0.0",
|
|
69
69
|
"@umituz/react-native-animation": "*",
|
|
70
|
-
"@umituz/react-native-design-system": "^2.
|
|
70
|
+
"@umituz/react-native-design-system": "^2.4.1",
|
|
71
71
|
"@umituz/react-native-firebase": "^1.13.20",
|
|
72
72
|
"@umituz/react-native-haptics": "^1.0.2",
|
|
73
73
|
"@umituz/react-native-image": "*",
|
package/src/index.ts
CHANGED
|
@@ -186,6 +186,7 @@ export {
|
|
|
186
186
|
usePendingJobs,
|
|
187
187
|
useBackgroundGeneration,
|
|
188
188
|
usePhotoGeneration,
|
|
189
|
+
useGenerationFlow,
|
|
189
190
|
} from "./presentation/hooks";
|
|
190
191
|
|
|
191
192
|
export type {
|
|
@@ -203,6 +204,8 @@ export type {
|
|
|
203
204
|
PhotoGenerationConfig,
|
|
204
205
|
PhotoGenerationState,
|
|
205
206
|
PhotoGenerationStatus,
|
|
207
|
+
UseGenerationFlowOptions,
|
|
208
|
+
UseGenerationFlowReturn,
|
|
206
209
|
} from "./presentation/hooks";
|
|
207
210
|
|
|
208
211
|
// =============================================================================
|
|
@@ -222,6 +225,7 @@ export {
|
|
|
222
225
|
ResultStoryCard,
|
|
223
226
|
ResultActions,
|
|
224
227
|
DEFAULT_RESULT_CONFIG,
|
|
228
|
+
PhotoStep,
|
|
225
229
|
} from "./presentation/components";
|
|
226
230
|
|
|
227
231
|
export type {
|
|
@@ -246,8 +250,28 @@ export type {
|
|
|
246
250
|
ResultActionsConfig,
|
|
247
251
|
ResultLayoutConfig,
|
|
248
252
|
ResultActionButton,
|
|
253
|
+
PhotoStepProps,
|
|
249
254
|
} from "./presentation/components";
|
|
250
255
|
|
|
256
|
+
// =============================================================================
|
|
257
|
+
// PRESENTATION LAYER - Flow Configuration
|
|
258
|
+
// =============================================================================
|
|
259
|
+
|
|
260
|
+
export {
|
|
261
|
+
DEFAULT_SINGLE_PHOTO_FLOW,
|
|
262
|
+
DEFAULT_DUAL_PHOTO_FLOW,
|
|
263
|
+
} from "./presentation/types/flow-config.types";
|
|
264
|
+
|
|
265
|
+
export type {
|
|
266
|
+
PhotoStepConfig,
|
|
267
|
+
TextInputStepConfig,
|
|
268
|
+
PreviewStepConfig,
|
|
269
|
+
GenerationFlowConfig,
|
|
270
|
+
PhotoStepData,
|
|
271
|
+
TextInputStepData,
|
|
272
|
+
GenerationFlowState,
|
|
273
|
+
} from "./presentation/types/flow-config.types";
|
|
274
|
+
|
|
251
275
|
// =============================================================================
|
|
252
276
|
// DOMAINS - AI Prompts
|
|
253
277
|
// =============================================================================
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhotoStep Component
|
|
3
|
+
* Configurable photo upload step using design system components
|
|
4
|
+
*
|
|
5
|
+
* @package @umituz/react-native-ai-generation-content
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useMemo } from "react";
|
|
9
|
+
import { View, StyleSheet, type ViewStyle, type StyleProp } from "react-native";
|
|
10
|
+
import {
|
|
11
|
+
StepHeader,
|
|
12
|
+
PhotoUploadCard,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
import type { PhotoStepConfig } from "../../types/flow-config.types";
|
|
15
|
+
|
|
16
|
+
export interface PhotoStepProps {
|
|
17
|
+
/** Step configuration */
|
|
18
|
+
config: PhotoStepConfig;
|
|
19
|
+
/** Current photo URI */
|
|
20
|
+
imageUri: string | null;
|
|
21
|
+
/** Photo preview URL */
|
|
22
|
+
previewUrl?: string;
|
|
23
|
+
/** Whether photo is being validated */
|
|
24
|
+
isValidating?: boolean;
|
|
25
|
+
/** Whether photo is valid */
|
|
26
|
+
isValid?: boolean | null;
|
|
27
|
+
/** Handler for photo selection */
|
|
28
|
+
onPhotoSelect: () => void;
|
|
29
|
+
/** Whether photo selection is disabled */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** Step title */
|
|
32
|
+
title: string;
|
|
33
|
+
/** Step subtitle */
|
|
34
|
+
subtitle?: string;
|
|
35
|
+
/** Translation strings for photo upload card */
|
|
36
|
+
translations: {
|
|
37
|
+
tapToUpload: string;
|
|
38
|
+
selectPhoto: string;
|
|
39
|
+
change: string;
|
|
40
|
+
analyzing?: string;
|
|
41
|
+
};
|
|
42
|
+
/** Additional content to render below photo card */
|
|
43
|
+
children?: React.ReactNode;
|
|
44
|
+
/** Container style */
|
|
45
|
+
style?: StyleProp<ViewStyle>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const PhotoStep: React.FC<PhotoStepProps> = ({
|
|
49
|
+
config,
|
|
50
|
+
imageUri,
|
|
51
|
+
previewUrl,
|
|
52
|
+
isValidating = false,
|
|
53
|
+
isValid = null,
|
|
54
|
+
onPhotoSelect,
|
|
55
|
+
disabled = false,
|
|
56
|
+
title,
|
|
57
|
+
subtitle,
|
|
58
|
+
translations,
|
|
59
|
+
children,
|
|
60
|
+
style,
|
|
61
|
+
}) => {
|
|
62
|
+
const styles = useMemo(
|
|
63
|
+
() =>
|
|
64
|
+
StyleSheet.create({
|
|
65
|
+
container: {
|
|
66
|
+
flex: 1,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
[],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<View style={[styles.container, style]}>
|
|
74
|
+
{/* Step Header */}
|
|
75
|
+
<StepHeader
|
|
76
|
+
title={title}
|
|
77
|
+
subtitle={subtitle}
|
|
78
|
+
config={config.header}
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
{/* Photo Upload Card */}
|
|
82
|
+
<PhotoUploadCard
|
|
83
|
+
imageUri={previewUrl || imageUri}
|
|
84
|
+
onPress={onPhotoSelect}
|
|
85
|
+
isValidating={isValidating}
|
|
86
|
+
isValid={config.enableValidation ? isValid : null}
|
|
87
|
+
disabled={disabled}
|
|
88
|
+
config={config.photoCard}
|
|
89
|
+
translations={translations}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
{/* Additional content (e.g., name input, tips, etc.) */}
|
|
93
|
+
{children}
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Feature Hook Types
|
|
3
|
+
* Provider-agnostic types for feature hooks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Result from AI processing
|
|
8
|
+
*/
|
|
9
|
+
export interface FeatureProcessResult {
|
|
10
|
+
readonly success: boolean;
|
|
11
|
+
readonly outputUrl?: string;
|
|
12
|
+
readonly error?: string;
|
|
13
|
+
readonly metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base state shared by all feature hooks
|
|
18
|
+
*/
|
|
19
|
+
export interface BaseFeatureState {
|
|
20
|
+
readonly isProcessing: boolean;
|
|
21
|
+
readonly progress: number;
|
|
22
|
+
readonly error: string | null;
|
|
23
|
+
readonly processedUrl: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base actions shared by all feature hooks
|
|
28
|
+
*/
|
|
29
|
+
export interface BaseFeatureActions {
|
|
30
|
+
readonly reset: () => void;
|
|
31
|
+
readonly clearError: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Progress callback type
|
|
36
|
+
*/
|
|
37
|
+
export type OnProgressCallback = (progress: number) => void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Image selection callback - provided by app
|
|
41
|
+
*/
|
|
42
|
+
export type OnSelectImageCallback = () => Promise<string | null>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Save callback - provided by app
|
|
46
|
+
*/
|
|
47
|
+
export type OnSaveCallback = (url: string) => Promise<void>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDualImageFeature Hook
|
|
3
|
+
* Provider-agnostic hook for dual image processing features
|
|
4
|
+
* Examples: AI Hug, AI Kiss, Face Swap
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useCallback, useState } from "react";
|
|
8
|
+
import type {
|
|
9
|
+
BaseFeatureState,
|
|
10
|
+
BaseFeatureActions,
|
|
11
|
+
FeatureProcessResult,
|
|
12
|
+
OnProgressCallback,
|
|
13
|
+
OnSelectImageCallback,
|
|
14
|
+
OnSaveCallback,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Request passed to processRequest callback
|
|
19
|
+
*/
|
|
20
|
+
export interface DualImageProcessRequest {
|
|
21
|
+
readonly firstImageUri: string;
|
|
22
|
+
readonly secondImageUri: string;
|
|
23
|
+
readonly onProgress: OnProgressCallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for dual image feature
|
|
28
|
+
*/
|
|
29
|
+
export interface UseDualImageFeatureConfig {
|
|
30
|
+
readonly onSelectFirstImage: OnSelectImageCallback;
|
|
31
|
+
readonly onSelectSecondImage: OnSelectImageCallback;
|
|
32
|
+
readonly processRequest: (
|
|
33
|
+
request: DualImageProcessRequest
|
|
34
|
+
) => Promise<FeatureProcessResult>;
|
|
35
|
+
readonly onSave?: OnSaveCallback;
|
|
36
|
+
readonly onError?: (error: string) => void;
|
|
37
|
+
readonly onSuccess?: (url: string) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* State for dual image feature
|
|
42
|
+
*/
|
|
43
|
+
export interface DualImageFeatureState extends BaseFeatureState {
|
|
44
|
+
readonly firstImageUri: string | null;
|
|
45
|
+
readonly secondImageUri: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Return type for dual image feature hook
|
|
50
|
+
*/
|
|
51
|
+
export interface UseDualImageFeatureReturn
|
|
52
|
+
extends DualImageFeatureState,
|
|
53
|
+
BaseFeatureActions {
|
|
54
|
+
readonly selectFirstImage: () => Promise<void>;
|
|
55
|
+
readonly selectSecondImage: () => Promise<void>;
|
|
56
|
+
readonly process: () => Promise<void>;
|
|
57
|
+
readonly save: () => Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useDualImageFeature(
|
|
61
|
+
config: UseDualImageFeatureConfig
|
|
62
|
+
): UseDualImageFeatureReturn {
|
|
63
|
+
const [firstImageUri, setFirstImageUri] = useState<string | null>(null);
|
|
64
|
+
const [secondImageUri, setSecondImageUri] = useState<string | null>(null);
|
|
65
|
+
const [processedUrl, setProcessedUrl] = useState<string | null>(null);
|
|
66
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
67
|
+
const [progress, setProgress] = useState(0);
|
|
68
|
+
const [error, setError] = useState<string | null>(null);
|
|
69
|
+
|
|
70
|
+
const selectFirstImage = useCallback(async (): Promise<void> => {
|
|
71
|
+
try {
|
|
72
|
+
const uri = await config.onSelectFirstImage();
|
|
73
|
+
if (uri) {
|
|
74
|
+
setFirstImageUri(uri);
|
|
75
|
+
setError(null);
|
|
76
|
+
setProcessedUrl(null);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
80
|
+
setError(message);
|
|
81
|
+
config.onError?.(message);
|
|
82
|
+
}
|
|
83
|
+
}, [config]);
|
|
84
|
+
|
|
85
|
+
const selectSecondImage = useCallback(async (): Promise<void> => {
|
|
86
|
+
try {
|
|
87
|
+
const uri = await config.onSelectSecondImage();
|
|
88
|
+
if (uri) {
|
|
89
|
+
setSecondImageUri(uri);
|
|
90
|
+
setError(null);
|
|
91
|
+
setProcessedUrl(null);
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
95
|
+
setError(message);
|
|
96
|
+
config.onError?.(message);
|
|
97
|
+
}
|
|
98
|
+
}, [config]);
|
|
99
|
+
|
|
100
|
+
const process = useCallback(async (): Promise<void> => {
|
|
101
|
+
if (!firstImageUri || !secondImageUri) {
|
|
102
|
+
const message = "error.noImages";
|
|
103
|
+
setError(message);
|
|
104
|
+
config.onError?.(message);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setIsProcessing(true);
|
|
109
|
+
setProgress(0);
|
|
110
|
+
setError(null);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await config.processRequest({
|
|
114
|
+
firstImageUri,
|
|
115
|
+
secondImageUri,
|
|
116
|
+
onProgress: setProgress,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (result.success && result.outputUrl) {
|
|
120
|
+
setProcessedUrl(result.outputUrl);
|
|
121
|
+
config.onSuccess?.(result.outputUrl);
|
|
122
|
+
} else {
|
|
123
|
+
const message = result.error || "error.processing";
|
|
124
|
+
setError(message);
|
|
125
|
+
config.onError?.(message);
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
const message = err instanceof Error ? err.message : "error.processing";
|
|
129
|
+
setError(message);
|
|
130
|
+
config.onError?.(message);
|
|
131
|
+
} finally {
|
|
132
|
+
setIsProcessing(false);
|
|
133
|
+
setProgress(0);
|
|
134
|
+
}
|
|
135
|
+
}, [firstImageUri, secondImageUri, config]);
|
|
136
|
+
|
|
137
|
+
const save = useCallback(async (): Promise<void> => {
|
|
138
|
+
if (!processedUrl || !config.onSave) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await config.onSave(processedUrl);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const message = err instanceof Error ? err.message : "error.save";
|
|
146
|
+
setError(message);
|
|
147
|
+
config.onError?.(message);
|
|
148
|
+
}
|
|
149
|
+
}, [processedUrl, config]);
|
|
150
|
+
|
|
151
|
+
const reset = useCallback((): void => {
|
|
152
|
+
setFirstImageUri(null);
|
|
153
|
+
setSecondImageUri(null);
|
|
154
|
+
setProcessedUrl(null);
|
|
155
|
+
setIsProcessing(false);
|
|
156
|
+
setProgress(0);
|
|
157
|
+
setError(null);
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
const clearError = useCallback((): void => {
|
|
161
|
+
setError(null);
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
firstImageUri,
|
|
166
|
+
secondImageUri,
|
|
167
|
+
processedUrl,
|
|
168
|
+
isProcessing,
|
|
169
|
+
progress,
|
|
170
|
+
error,
|
|
171
|
+
selectFirstImage,
|
|
172
|
+
selectSecondImage,
|
|
173
|
+
process,
|
|
174
|
+
save,
|
|
175
|
+
reset,
|
|
176
|
+
clearError,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useImageWithPromptFeature Hook
|
|
3
|
+
* Provider-agnostic hook for image + prompt processing features
|
|
4
|
+
* Examples: Inpainting, Style Transfer, Background Replacement
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useCallback, useState } from "react";
|
|
8
|
+
import type {
|
|
9
|
+
BaseFeatureState,
|
|
10
|
+
BaseFeatureActions,
|
|
11
|
+
FeatureProcessResult,
|
|
12
|
+
OnProgressCallback,
|
|
13
|
+
OnSelectImageCallback,
|
|
14
|
+
OnSaveCallback,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Request passed to processRequest callback
|
|
19
|
+
*/
|
|
20
|
+
export interface ImageWithPromptProcessRequest {
|
|
21
|
+
readonly imageUri: string;
|
|
22
|
+
readonly prompt: string;
|
|
23
|
+
readonly onProgress: OnProgressCallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for image with prompt feature
|
|
28
|
+
*/
|
|
29
|
+
export interface UseImageWithPromptFeatureConfig {
|
|
30
|
+
readonly onSelectImage: OnSelectImageCallback;
|
|
31
|
+
readonly processRequest: (
|
|
32
|
+
request: ImageWithPromptProcessRequest
|
|
33
|
+
) => Promise<FeatureProcessResult>;
|
|
34
|
+
readonly onSave?: OnSaveCallback;
|
|
35
|
+
readonly onError?: (error: string) => void;
|
|
36
|
+
readonly onSuccess?: (url: string) => void;
|
|
37
|
+
readonly requirePrompt?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* State for image with prompt feature
|
|
42
|
+
*/
|
|
43
|
+
export interface ImageWithPromptFeatureState extends BaseFeatureState {
|
|
44
|
+
readonly imageUri: string | null;
|
|
45
|
+
readonly prompt: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Return type for image with prompt feature hook
|
|
50
|
+
*/
|
|
51
|
+
export interface UseImageWithPromptFeatureReturn
|
|
52
|
+
extends ImageWithPromptFeatureState,
|
|
53
|
+
BaseFeatureActions {
|
|
54
|
+
readonly selectImage: () => Promise<void>;
|
|
55
|
+
readonly setPrompt: (prompt: string) => void;
|
|
56
|
+
readonly process: () => Promise<void>;
|
|
57
|
+
readonly save: () => Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useImageWithPromptFeature(
|
|
61
|
+
config: UseImageWithPromptFeatureConfig
|
|
62
|
+
): UseImageWithPromptFeatureReturn {
|
|
63
|
+
const [imageUri, setImageUri] = useState<string | null>(null);
|
|
64
|
+
const [prompt, setPrompt] = useState<string>("");
|
|
65
|
+
const [processedUrl, setProcessedUrl] = useState<string | null>(null);
|
|
66
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
67
|
+
const [progress, setProgress] = useState(0);
|
|
68
|
+
const [error, setError] = useState<string | null>(null);
|
|
69
|
+
|
|
70
|
+
const selectImage = useCallback(async (): Promise<void> => {
|
|
71
|
+
try {
|
|
72
|
+
const uri = await config.onSelectImage();
|
|
73
|
+
if (uri) {
|
|
74
|
+
setImageUri(uri);
|
|
75
|
+
setError(null);
|
|
76
|
+
setProcessedUrl(null);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
80
|
+
setError(message);
|
|
81
|
+
config.onError?.(message);
|
|
82
|
+
}
|
|
83
|
+
}, [config]);
|
|
84
|
+
|
|
85
|
+
const process = useCallback(async (): Promise<void> => {
|
|
86
|
+
if (!imageUri) {
|
|
87
|
+
const message = "error.noImage";
|
|
88
|
+
setError(message);
|
|
89
|
+
config.onError?.(message);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (config.requirePrompt && !prompt.trim()) {
|
|
94
|
+
const message = "error.noPrompt";
|
|
95
|
+
setError(message);
|
|
96
|
+
config.onError?.(message);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setIsProcessing(true);
|
|
101
|
+
setProgress(0);
|
|
102
|
+
setError(null);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const result = await config.processRequest({
|
|
106
|
+
imageUri,
|
|
107
|
+
prompt: prompt.trim(),
|
|
108
|
+
onProgress: setProgress,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (result.success && result.outputUrl) {
|
|
112
|
+
setProcessedUrl(result.outputUrl);
|
|
113
|
+
config.onSuccess?.(result.outputUrl);
|
|
114
|
+
} else {
|
|
115
|
+
const message = result.error || "error.processing";
|
|
116
|
+
setError(message);
|
|
117
|
+
config.onError?.(message);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
const message = err instanceof Error ? err.message : "error.processing";
|
|
121
|
+
setError(message);
|
|
122
|
+
config.onError?.(message);
|
|
123
|
+
} finally {
|
|
124
|
+
setIsProcessing(false);
|
|
125
|
+
setProgress(0);
|
|
126
|
+
}
|
|
127
|
+
}, [imageUri, prompt, config]);
|
|
128
|
+
|
|
129
|
+
const save = useCallback(async (): Promise<void> => {
|
|
130
|
+
if (!processedUrl || !config.onSave) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
await config.onSave(processedUrl);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const message = err instanceof Error ? err.message : "error.save";
|
|
138
|
+
setError(message);
|
|
139
|
+
config.onError?.(message);
|
|
140
|
+
}
|
|
141
|
+
}, [processedUrl, config]);
|
|
142
|
+
|
|
143
|
+
const reset = useCallback((): void => {
|
|
144
|
+
setImageUri(null);
|
|
145
|
+
setPrompt("");
|
|
146
|
+
setProcessedUrl(null);
|
|
147
|
+
setIsProcessing(false);
|
|
148
|
+
setProgress(0);
|
|
149
|
+
setError(null);
|
|
150
|
+
}, []);
|
|
151
|
+
|
|
152
|
+
const clearError = useCallback((): void => {
|
|
153
|
+
setError(null);
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
imageUri,
|
|
158
|
+
prompt,
|
|
159
|
+
processedUrl,
|
|
160
|
+
isProcessing,
|
|
161
|
+
progress,
|
|
162
|
+
error,
|
|
163
|
+
selectImage,
|
|
164
|
+
setPrompt,
|
|
165
|
+
process,
|
|
166
|
+
save,
|
|
167
|
+
reset,
|
|
168
|
+
clearError,
|
|
169
|
+
};
|
|
170
|
+
}
|