@umituz/react-native-ai-generation-content 1.15.0 → 1.15.2

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.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/src/features/audio-generation/index.ts +0 -1
  3. package/src/features/colorization/index.ts +0 -1
  4. package/src/features/face-swap/index.ts +0 -1
  5. package/src/features/future-prediction/index.ts +0 -1
  6. package/src/features/image-captioning/index.ts +0 -1
  7. package/src/features/inpainting/index.ts +0 -1
  8. package/src/features/photo-restoration/index.ts +0 -1
  9. package/src/features/sketch-to-image/index.ts +0 -1
  10. package/src/features/style-transfer/index.ts +0 -1
  11. package/src/features/text-to-image/index.ts +0 -1
  12. package/src/features/text-to-video/index.ts +0 -1
  13. package/src/features/upscaling/domain/index.ts +1 -0
  14. package/src/features/upscaling/domain/types/index.ts +2 -0
  15. package/src/features/upscaling/domain/types/provider.types.ts +23 -0
  16. package/src/features/upscaling/domain/types/upscale.types.ts +56 -0
  17. package/src/features/upscaling/index.ts +40 -2
  18. package/src/features/upscaling/infrastructure/index.ts +1 -0
  19. package/src/features/upscaling/infrastructure/services/index.ts +7 -0
  20. package/src/features/upscaling/infrastructure/services/upscale-executor.ts +64 -0
  21. package/src/features/upscaling/infrastructure/services/upscale-provider-registry.ts +77 -0
  22. package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +168 -0
  23. package/src/features/upscaling/presentation/components/UpscaleResultView.tsx +94 -0
  24. package/src/features/upscaling/presentation/components/index.ts +4 -0
  25. package/src/features/upscaling/presentation/hooks/index.ts +5 -0
  26. package/src/features/upscaling/presentation/hooks/useUpscaleFeature.ts +132 -0
  27. package/src/features/upscaling/presentation/index.ts +2 -0
  28. package/src/index.ts +6 -1
  29. package/src/presentation/hooks/base/index.ts +9 -0
  30. package/src/presentation/hooks/base/types.ts +47 -0
  31. package/src/presentation/hooks/base/use-dual-image-feature.ts +178 -0
  32. package/src/presentation/hooks/base/use-image-with-prompt-feature.ts +170 -0
  33. package/src/presentation/hooks/base/use-single-image-feature.ts +154 -0
  34. package/src/presentation/hooks/index.ts +3 -0
  35. package/src/features/audio-generation/presentation/hooks.ts +0 -39
  36. package/src/features/colorization/presentation/hooks.ts +0 -39
  37. package/src/features/face-swap/presentation/hooks.ts +0 -41
  38. package/src/features/future-prediction/presentation/hooks.ts +0 -39
  39. package/src/features/image-captioning/presentation/hooks.ts +0 -39
  40. package/src/features/inpainting/presentation/hooks.ts +0 -39
  41. package/src/features/photo-restoration/presentation/hooks.ts +0 -39
  42. package/src/features/sketch-to-image/presentation/hooks.ts +0 -39
  43. package/src/features/style-transfer/presentation/hooks.ts +0 -39
  44. package/src/features/text-to-image/presentation/hooks.ts +0 -39
  45. package/src/features/text-to-video/presentation/hooks.ts +0 -39
  46. package/src/features/upscaling/domain/entities.ts +0 -42
  47. package/src/features/upscaling/presentation/hooks.ts +0 -39
@@ -0,0 +1,132 @@
1
+ /**
2
+ * useUpscaleFeature Hook
3
+ * Manages upscale feature state and actions
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import { executeUpscale } from "../../infrastructure/services";
8
+ import type {
9
+ UpscaleFeatureState,
10
+ UpscaleFeatureConfig,
11
+ UpscaleResult,
12
+ } from "../../domain/types";
13
+
14
+ declare const __DEV__: boolean;
15
+
16
+ export interface UseUpscaleFeatureProps {
17
+ config?: UpscaleFeatureConfig;
18
+ userId: string;
19
+ onSelectImage: () => Promise<string | null>;
20
+ onSaveImage: (imageUrl: string) => Promise<void>;
21
+ }
22
+
23
+ export interface UseUpscaleFeatureReturn extends UpscaleFeatureState {
24
+ selectImage: () => Promise<void>;
25
+ process: () => Promise<void>;
26
+ save: () => Promise<void>;
27
+ reset: () => void;
28
+ }
29
+
30
+ const initialState: UpscaleFeatureState = {
31
+ imageUri: null,
32
+ processedUrl: null,
33
+ isProcessing: false,
34
+ progress: 0,
35
+ error: null,
36
+ };
37
+
38
+ export function useUpscaleFeature(
39
+ props: UseUpscaleFeatureProps,
40
+ ): UseUpscaleFeatureReturn {
41
+ const { config, userId, onSelectImage, onSaveImage } = props;
42
+ const [state, setState] = useState<UpscaleFeatureState>(initialState);
43
+
44
+ const selectImage = useCallback(async () => {
45
+ try {
46
+ const uri = await onSelectImage();
47
+ if (uri) {
48
+ setState((prev) => ({ ...prev, imageUri: uri, error: null }));
49
+ config?.onImageSelect?.(uri);
50
+ }
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ setState((prev) => ({ ...prev, error: message }));
54
+ }
55
+ }, [onSelectImage, config]);
56
+
57
+ const handleProgress = useCallback((progress: number) => {
58
+ setState((prev) => ({ ...prev, progress }));
59
+ }, []);
60
+
61
+ const process = useCallback(async () => {
62
+ if (!state.imageUri) return;
63
+
64
+ setState((prev) => ({
65
+ ...prev,
66
+ isProcessing: true,
67
+ progress: 0,
68
+ error: null,
69
+ }));
70
+
71
+ config?.onProcessingStart?.();
72
+
73
+ if (__DEV__) {
74
+ // eslint-disable-next-line no-console
75
+ console.log("[useUpscaleFeature] Starting upscale process");
76
+ }
77
+
78
+ const result: UpscaleResult = await executeUpscale(
79
+ {
80
+ imageUri: state.imageUri,
81
+ userId,
82
+ options: { scaleFactor: config?.defaultScaleFactor || 2 },
83
+ },
84
+ {
85
+ providerId: config?.providerId,
86
+ onProgress: handleProgress,
87
+ },
88
+ );
89
+
90
+ if (result.success && result.imageUrl) {
91
+ setState((prev) => ({
92
+ ...prev,
93
+ isProcessing: false,
94
+ processedUrl: result.imageUrl,
95
+ progress: 100,
96
+ }));
97
+ config?.onProcessingComplete?.(result);
98
+ } else {
99
+ const errorMessage = result.error || "Processing failed";
100
+ setState((prev) => ({
101
+ ...prev,
102
+ isProcessing: false,
103
+ error: errorMessage,
104
+ progress: 0,
105
+ }));
106
+ config?.onError?.(errorMessage);
107
+ }
108
+ }, [state.imageUri, userId, config, handleProgress]);
109
+
110
+ const save = useCallback(async () => {
111
+ if (!state.processedUrl) return;
112
+
113
+ try {
114
+ await onSaveImage(state.processedUrl);
115
+ } catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ setState((prev) => ({ ...prev, error: message }));
118
+ }
119
+ }, [state.processedUrl, onSaveImage]);
120
+
121
+ const reset = useCallback(() => {
122
+ setState(initialState);
123
+ }, []);
124
+
125
+ return {
126
+ ...state,
127
+ selectImage,
128
+ process,
129
+ save,
130
+ reset,
131
+ };
132
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./hooks";
2
+ export * from "./components";
package/src/index.ts CHANGED
@@ -297,9 +297,14 @@ export * from "./domains/creations";
297
297
  export * from "./domains/face-detection";
298
298
 
299
299
  // =============================================================================
300
- // DOMAINS - Feature Background
300
+ // FEATURES - Background
301
301
  // =============================================================================
302
302
 
303
303
  export * from "./features/background";
304
304
 
305
+ // =============================================================================
306
+ // FEATURES - Upscaling
307
+ // =============================================================================
308
+
309
+ export * from "./features/upscaling";
305
310
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Base Feature Hooks
3
+ * Provider-agnostic hooks for AI image processing features
4
+ */
5
+
6
+ export * from "./types";
7
+ export * from "./use-single-image-feature";
8
+ export * from "./use-dual-image-feature";
9
+ export * from "./use-image-with-prompt-feature";
@@ -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
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * useSingleImageFeature Hook
3
+ * Provider-agnostic hook for single image processing features
4
+ * App provides processRequest callback with their AI provider
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 SingleImageProcessRequest {
21
+ readonly imageUri: string;
22
+ readonly onProgress: OnProgressCallback;
23
+ }
24
+
25
+ /**
26
+ * Configuration for single image feature
27
+ */
28
+ export interface UseSingleImageFeatureConfig {
29
+ readonly onSelectImage: OnSelectImageCallback;
30
+ readonly processRequest: (
31
+ request: SingleImageProcessRequest
32
+ ) => Promise<FeatureProcessResult>;
33
+ readonly onSave?: OnSaveCallback;
34
+ readonly onError?: (error: string) => void;
35
+ readonly onSuccess?: (url: string) => void;
36
+ }
37
+
38
+ /**
39
+ * State for single image feature
40
+ */
41
+ export interface SingleImageFeatureState extends BaseFeatureState {
42
+ readonly imageUri: string | null;
43
+ }
44
+
45
+ /**
46
+ * Return type for single image feature hook
47
+ */
48
+ export interface UseSingleImageFeatureReturn
49
+ extends SingleImageFeatureState,
50
+ BaseFeatureActions {
51
+ readonly selectImage: () => Promise<void>;
52
+ readonly process: () => Promise<void>;
53
+ readonly save: () => Promise<void>;
54
+ }
55
+
56
+ export function useSingleImageFeature(
57
+ config: UseSingleImageFeatureConfig
58
+ ): UseSingleImageFeatureReturn {
59
+ const [imageUri, setImageUri] = useState<string | null>(null);
60
+ const [processedUrl, setProcessedUrl] = useState<string | null>(null);
61
+ const [isProcessing, setIsProcessing] = useState(false);
62
+ const [progress, setProgress] = useState(0);
63
+ const [error, setError] = useState<string | null>(null);
64
+
65
+ const selectImage = useCallback(async (): Promise<void> => {
66
+ try {
67
+ const uri = await config.onSelectImage();
68
+ if (uri) {
69
+ setImageUri(uri);
70
+ setError(null);
71
+ setProcessedUrl(null);
72
+ }
73
+ } catch (err) {
74
+ const message = err instanceof Error ? err.message : "error.selectImage";
75
+ setError(message);
76
+ config.onError?.(message);
77
+ }
78
+ }, [config]);
79
+
80
+ const process = useCallback(async (): Promise<void> => {
81
+ if (!imageUri) {
82
+ const message = "error.noImage";
83
+ setError(message);
84
+ config.onError?.(message);
85
+ return;
86
+ }
87
+
88
+ setIsProcessing(true);
89
+ setProgress(0);
90
+ setError(null);
91
+
92
+ try {
93
+ const result = await config.processRequest({
94
+ imageUri,
95
+ onProgress: setProgress,
96
+ });
97
+
98
+ if (result.success && result.outputUrl) {
99
+ setProcessedUrl(result.outputUrl);
100
+ config.onSuccess?.(result.outputUrl);
101
+ } else {
102
+ const message = result.error || "error.processing";
103
+ setError(message);
104
+ config.onError?.(message);
105
+ }
106
+ } catch (err) {
107
+ const message = err instanceof Error ? err.message : "error.processing";
108
+ setError(message);
109
+ config.onError?.(message);
110
+ } finally {
111
+ setIsProcessing(false);
112
+ setProgress(0);
113
+ }
114
+ }, [imageUri, config]);
115
+
116
+ const save = useCallback(async (): Promise<void> => {
117
+ if (!processedUrl || !config.onSave) {
118
+ return;
119
+ }
120
+
121
+ try {
122
+ await config.onSave(processedUrl);
123
+ } catch (err) {
124
+ const message = err instanceof Error ? err.message : "error.save";
125
+ setError(message);
126
+ config.onError?.(message);
127
+ }
128
+ }, [processedUrl, config]);
129
+
130
+ const reset = useCallback((): void => {
131
+ setImageUri(null);
132
+ setProcessedUrl(null);
133
+ setIsProcessing(false);
134
+ setProgress(0);
135
+ setError(null);
136
+ }, []);
137
+
138
+ const clearError = useCallback((): void => {
139
+ setError(null);
140
+ }, []);
141
+
142
+ return {
143
+ imageUri,
144
+ processedUrl,
145
+ isProcessing,
146
+ progress,
147
+ error,
148
+ selectImage,
149
+ process,
150
+ save,
151
+ reset,
152
+ clearError,
153
+ };
154
+ }