@umituz/react-native-ai-generation-content 1.17.310 → 1.18.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.
@@ -1,26 +1,23 @@
1
1
  /**
2
2
  * useImageWithPromptFeature Hook Factory
3
3
  * Base hook for image + prompt processing features (e.g., replace-background)
4
+ * Uses centralized orchestrator for credit/error handling
4
5
  */
5
6
 
6
- import { useState, useCallback, useRef } from "react";
7
+ import { useState, useCallback, useRef, useMemo } from "react";
7
8
  import { generateUUID } from "@umituz/react-native-design-system";
8
9
  import { executeImageFeature } from "../../../../infrastructure/services";
10
+ import {
11
+ useGenerationOrchestrator,
12
+ type GenerationStrategy,
13
+ type AlertMessages,
14
+ } from "../../../../presentation/hooks/generation";
9
15
  import type {
10
16
  BaseImageWithPromptState,
11
17
  SingleImageConfig,
12
18
  BaseImageResult,
13
19
  } from "../../domain/types";
14
20
 
15
- const INITIAL_STATE: BaseImageWithPromptState = {
16
- imageUri: null,
17
- prompt: "",
18
- processedUrl: null,
19
- isProcessing: false,
20
- progress: 0,
21
- error: null,
22
- };
23
-
24
21
  export interface ImageWithPromptConfig<TResult extends BaseImageResult = BaseImageResult>
25
22
  extends SingleImageConfig<TResult> {
26
23
  defaultPrompt?: string;
@@ -52,6 +49,26 @@ export interface ImageWithPromptOptions {
52
49
  config: ImageWithPromptConfig,
53
50
  ) => Record<string, unknown>;
54
51
  promptRequired?: boolean;
52
+ /** Alert messages for error handling */
53
+ alertMessages?: AlertMessages;
54
+ /** User ID for credit operations */
55
+ userId?: string;
56
+ /** Callback when credits are exhausted */
57
+ onCreditsExhausted?: () => void;
58
+ }
59
+
60
+ const DEFAULT_ALERT_MESSAGES: AlertMessages = {
61
+ networkError: "No internet connection. Please check your network.",
62
+ policyViolation: "Content not allowed. Please try a different image.",
63
+ saveFailed: "Failed to save result. Please try again.",
64
+ creditFailed: "Credit operation failed. Please try again.",
65
+ unknown: "An error occurred. Please try again.",
66
+ };
67
+
68
+ interface ImageWithPromptInput {
69
+ imageBase64: string;
70
+ prompt: string;
71
+ options?: Record<string, unknown>;
55
72
  }
56
73
 
57
74
  export function useImageWithPromptFeature<
@@ -62,48 +79,88 @@ export function useImageWithPromptFeature<
62
79
  options?: ImageWithPromptOptions,
63
80
  ): ImageWithPromptHookReturn {
64
81
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
65
- const [state, setState] = useState<BaseImageWithPromptState>({
66
- ...INITIAL_STATE,
67
- prompt: config.defaultPrompt || "",
68
- });
82
+
83
+ // Image and prompt state (separate from orchestrator state)
84
+ const [imageUri, setImageUri] = useState<string | null>(null);
85
+ const [prompt, setPromptState] = useState(config.defaultPrompt || "");
86
+ const [imageError, setImageError] = useState<string | null>(null);
69
87
  const creationIdRef = useRef<string | null>(null);
70
88
 
89
+ // Create strategy for orchestrator
90
+ const strategy: GenerationStrategy<ImageWithPromptInput, string> = useMemo(
91
+ () => ({
92
+ execute: async (input, onProgress) => {
93
+ const executorInput = input.options
94
+ ? { ...input.options }
95
+ : { imageBase64: input.imageBase64, prompt: input.prompt };
96
+
97
+ const result = await executeImageFeature(
98
+ config.featureType,
99
+ executorInput,
100
+ { extractResult: config.extractResult, onProgress },
101
+ );
102
+
103
+ if (!result.success || !result.imageUrl) {
104
+ throw new Error(result.error || "Processing failed");
105
+ }
106
+
107
+ // Notify completion with creationId
108
+ const creationId = creationIdRef.current;
109
+ if (creationId) {
110
+ config.onProcessingComplete?.({ ...result, creationId } as unknown as TResult);
111
+ }
112
+
113
+ return result.imageUrl;
114
+ },
115
+ getCreditCost: () => config.creditCost || 1,
116
+ }),
117
+ [config],
118
+ );
119
+
120
+ // Use orchestrator for generation
121
+ const orchestrator = useGenerationOrchestrator(strategy, {
122
+ userId: options?.userId,
123
+ alertMessages: options?.alertMessages || DEFAULT_ALERT_MESSAGES,
124
+ onCreditsExhausted: options?.onCreditsExhausted,
125
+ onError: (error) => {
126
+ config.onError?.(error.message, creationIdRef.current ?? undefined);
127
+ },
128
+ });
129
+
71
130
  const selectImage = useCallback(async () => {
72
131
  try {
73
132
  const uri = await onSelectImage();
74
133
  if (uri) {
75
- setState((prev) => ({ ...prev, imageUri: uri, error: null }));
134
+ setImageUri(uri);
135
+ setImageError(null);
76
136
  config.onImageSelect?.(uri);
77
137
  }
78
138
  } catch (error) {
79
139
  const message = error instanceof Error ? error.message : String(error);
80
- setState((prev) => ({ ...prev, error: message }));
140
+ setImageError(message);
81
141
  }
82
142
  }, [onSelectImage, config]);
83
143
 
84
144
  const setPrompt = useCallback(
85
- (prompt: string) => {
86
- setState((prev) => ({ ...prev, prompt, error: null }));
87
- config.onPromptChange?.(prompt);
145
+ (newPrompt: string) => {
146
+ setPromptState(newPrompt);
147
+ setImageError(null);
148
+ config.onPromptChange?.(newPrompt);
88
149
  },
89
150
  [config],
90
151
  );
91
152
 
92
- const handleProgress = useCallback((progress: number) => {
93
- setState((prev) => ({ ...prev, progress }));
94
- }, []);
95
-
96
153
  const process = useCallback(async () => {
97
- if (!state.imageUri) return;
154
+ if (!imageUri) return;
98
155
 
99
156
  if (onBeforeProcess) {
100
157
  const canProceed = await onBeforeProcess();
101
158
  if (!canProceed) return;
102
159
  }
103
160
 
104
- if (options?.promptRequired && !state.prompt.trim()) {
161
+ if (options?.promptRequired && !prompt.trim()) {
105
162
  const error = "Prompt is required";
106
- setState((prev) => ({ ...prev, error }));
163
+ setImageError(error);
107
164
  config.onError?.(error, creationIdRef.current ?? undefined);
108
165
  return;
109
166
  }
@@ -111,78 +168,52 @@ export function useImageWithPromptFeature<
111
168
  const creationId = generateUUID();
112
169
  creationIdRef.current = creationId;
113
170
 
114
- setState((prev) => ({
115
- ...prev,
116
- isProcessing: true,
117
- progress: 0,
118
- error: null,
119
- }));
120
-
121
- config.onProcessingStart?.({ creationId, imageUri: state.imageUri });
171
+ config.onProcessingStart?.({ creationId, imageUri });
122
172
 
123
173
  try {
124
- const imageBase64 = await config.prepareImage(state.imageUri);
125
-
126
- const input = options?.buildInput
127
- ? options.buildInput(imageBase64, state.prompt, config)
128
- : { imageBase64, prompt: state.prompt };
129
-
130
- const result = await executeImageFeature(
131
- config.featureType,
132
- input,
133
- { extractResult: config.extractResult, onProgress: handleProgress },
134
- );
135
-
136
- if (result.success && result.imageUrl) {
137
- setState((prev) => ({
138
- ...prev,
139
- isProcessing: false,
140
- processedUrl: result.imageUrl!,
141
- progress: 100,
142
- }));
143
- config.onProcessingComplete?.({ ...result, creationId } as unknown as TResult);
144
- } else {
145
- const errorMessage = result.error || "Processing failed";
146
- setState((prev) => ({
147
- ...prev,
148
- isProcessing: false,
149
- error: errorMessage,
150
- progress: 0,
151
- }));
152
- config.onError?.(errorMessage, creationId);
153
- }
174
+ const imageBase64 = await config.prepareImage(imageUri);
175
+
176
+ const input: ImageWithPromptInput = options?.buildInput
177
+ ? {
178
+ imageBase64,
179
+ prompt,
180
+ options: options.buildInput(imageBase64, prompt, config),
181
+ }
182
+ : { imageBase64, prompt };
183
+
184
+ await orchestrator.generate(input);
154
185
  } catch (error) {
155
- const message = error instanceof Error ? error.message : String(error);
156
- setState((prev) => ({
157
- ...prev,
158
- isProcessing: false,
159
- error: message,
160
- progress: 0,
161
- }));
162
- config.onError?.(message, creationIdRef.current ?? undefined);
186
+ // Error already handled by orchestrator
163
187
  }
164
- }, [state.imageUri, state.prompt, config, options, handleProgress, onBeforeProcess]);
188
+ }, [imageUri, prompt, config, options, onBeforeProcess, orchestrator]);
165
189
 
166
190
  const save = useCallback(async () => {
167
- if (!state.processedUrl) return;
191
+ if (!orchestrator.result) return;
168
192
 
169
193
  try {
170
- await onSaveImage(state.processedUrl);
194
+ await onSaveImage(orchestrator.result);
171
195
  } catch (error) {
172
196
  const message = error instanceof Error ? error.message : String(error);
173
- setState((prev) => ({ ...prev, error: message }));
197
+ setImageError(message);
174
198
  }
175
- }, [state.processedUrl, onSaveImage]);
199
+ }, [orchestrator.result, onSaveImage]);
176
200
 
177
201
  const reset = useCallback(() => {
178
- setState({
179
- ...INITIAL_STATE,
180
- prompt: config.defaultPrompt || "",
181
- });
182
- }, [config.defaultPrompt]);
183
-
202
+ setImageUri(null);
203
+ setPromptState(config.defaultPrompt || "");
204
+ setImageError(null);
205
+ creationIdRef.current = null;
206
+ orchestrator.reset();
207
+ }, [config.defaultPrompt, orchestrator]);
208
+
209
+ // Combine states for backward compatibility
184
210
  return {
185
- ...state,
211
+ imageUri,
212
+ prompt,
213
+ processedUrl: orchestrator.result,
214
+ isProcessing: orchestrator.isGenerating,
215
+ progress: orchestrator.progress,
216
+ error: orchestrator.error?.message || imageError,
186
217
  selectImage,
187
218
  setPrompt,
188
219
  process,
@@ -1,29 +1,45 @@
1
1
  /**
2
2
  * useSingleImageFeature Hook Factory
3
3
  * Base hook for single image processing features
4
+ * Uses centralized orchestrator for credit/error handling
4
5
  */
5
6
 
6
- import { useState, useCallback, useRef } from "react";
7
+ import { useState, useCallback, useRef, useMemo } from "react";
7
8
  import { generateUUID } from "@umituz/react-native-design-system";
8
9
  import { executeImageFeature } from "../../../../infrastructure/services";
10
+ import {
11
+ useGenerationOrchestrator,
12
+ type GenerationStrategy,
13
+ type AlertMessages,
14
+ } from "../../../../presentation/hooks/generation";
9
15
  import type {
10
- BaseSingleImageState,
11
16
  BaseSingleImageHookProps,
12
17
  BaseSingleImageHookReturn,
13
18
  SingleImageConfig,
14
19
  BaseImageResult,
15
20
  } from "../../domain/types";
16
21
 
17
- const INITIAL_STATE: BaseSingleImageState = {
18
- imageUri: null,
19
- processedUrl: null,
20
- isProcessing: false,
21
- progress: 0,
22
- error: null,
23
- };
24
-
25
22
  export interface SingleImageFeatureOptions<TConfig extends SingleImageConfig> {
26
23
  buildInput?: (imageBase64: string, config: TConfig) => Record<string, unknown>;
24
+ /** Alert messages for error handling */
25
+ alertMessages?: AlertMessages;
26
+ /** User ID for credit operations */
27
+ userId?: string;
28
+ /** Callback when credits are exhausted */
29
+ onCreditsExhausted?: () => void;
30
+ }
31
+
32
+ const DEFAULT_ALERT_MESSAGES: AlertMessages = {
33
+ networkError: "No internet connection. Please check your network.",
34
+ policyViolation: "Content not allowed. Please try a different image.",
35
+ saveFailed: "Failed to save result. Please try again.",
36
+ creditFailed: "Credit operation failed. Please try again.",
37
+ unknown: "An error occurred. Please try again.",
38
+ };
39
+
40
+ interface SingleImageInput {
41
+ imageBase64: string;
42
+ options?: Record<string, unknown>;
27
43
  }
28
44
 
29
45
  export function useSingleImageFeature<
@@ -34,28 +50,65 @@ export function useSingleImageFeature<
34
50
  options?: SingleImageFeatureOptions<TConfig>,
35
51
  ): BaseSingleImageHookReturn {
36
52
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
37
- const [state, setState] = useState<BaseSingleImageState>(INITIAL_STATE);
53
+
54
+ // Image selection state (separate from orchestrator state)
55
+ const [imageUri, setImageUri] = useState<string | null>(null);
56
+ const [imageError, setImageError] = useState<string | null>(null);
38
57
  const creationIdRef = useRef<string | null>(null);
39
58
 
59
+ // Create strategy for orchestrator
60
+ const strategy: GenerationStrategy<SingleImageInput, string> = useMemo(
61
+ () => ({
62
+ execute: async (input, onProgress) => {
63
+ const result = await executeImageFeature(
64
+ config.featureType,
65
+ input.options ? { imageBase64: input.imageBase64, ...input.options } : { imageBase64: input.imageBase64 },
66
+ { extractResult: config.extractResult, onProgress },
67
+ );
68
+
69
+ if (!result.success || !result.imageUrl) {
70
+ throw new Error(result.error || "Processing failed");
71
+ }
72
+
73
+ // Notify completion with creationId
74
+ const creationId = creationIdRef.current;
75
+ if (creationId) {
76
+ config.onProcessingComplete?.({ ...result, creationId } as unknown as TResult);
77
+ }
78
+
79
+ return result.imageUrl;
80
+ },
81
+ getCreditCost: () => config.creditCost || 1,
82
+ }),
83
+ [config],
84
+ );
85
+
86
+ // Use orchestrator for generation
87
+ const orchestrator = useGenerationOrchestrator(strategy, {
88
+ userId: options?.userId,
89
+ alertMessages: options?.alertMessages || DEFAULT_ALERT_MESSAGES,
90
+ onCreditsExhausted: options?.onCreditsExhausted,
91
+ onError: (error) => {
92
+ config.onError?.(error.message, creationIdRef.current ?? undefined);
93
+ },
94
+ });
95
+
40
96
  const selectImage = useCallback(async () => {
41
97
  try {
42
98
  const uri = await onSelectImage();
43
99
  if (uri) {
44
- setState((prev) => ({ ...prev, imageUri: uri, error: null }));
100
+ setImageUri(uri);
101
+ setImageError(null);
45
102
  config.onImageSelect?.(uri);
46
103
  }
47
104
  } catch (error) {
48
105
  const message = error instanceof Error ? error.message : String(error);
49
- setState((prev) => ({ ...prev, error: message }));
106
+ setImageError(message);
50
107
  }
51
108
  }, [onSelectImage, config]);
52
109
 
53
- const handleProgress = useCallback((progress: number) => {
54
- setState((prev) => ({ ...prev, progress }));
55
- }, []);
56
-
57
110
  const process = useCallback(async () => {
58
- if (!state.imageUri) return;
111
+ if (!imageUri) return;
59
112
 
60
113
  if (onBeforeProcess) {
61
114
  const canProceed = await onBeforeProcess();
@@ -65,75 +118,46 @@ export function useSingleImageFeature<
65
118
  const creationId = generateUUID();
66
119
  creationIdRef.current = creationId;
67
120
 
68
- setState((prev) => ({
69
- ...prev,
70
- isProcessing: true,
71
- progress: 0,
72
- error: null,
73
- }));
74
-
75
- config.onProcessingStart?.({ creationId, imageUri: state.imageUri });
121
+ config.onProcessingStart?.({ creationId, imageUri });
76
122
 
77
123
  try {
78
- const imageBase64 = await config.prepareImage(state.imageUri);
124
+ const imageBase64 = await config.prepareImage(imageUri);
79
125
 
80
- const input = options?.buildInput
81
- ? options.buildInput(imageBase64, config)
126
+ const input: SingleImageInput = options?.buildInput
127
+ ? { imageBase64, options: options.buildInput(imageBase64, config) }
82
128
  : { imageBase64 };
83
129
 
84
- const result = await executeImageFeature(
85
- config.featureType,
86
- input,
87
- { extractResult: config.extractResult, onProgress: handleProgress },
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, creationId } as unknown as TResult);
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, creationId);
107
- }
130
+ await orchestrator.generate(input);
108
131
  } catch (error) {
109
- const message = error instanceof Error ? error.message : String(error);
110
- setState((prev) => ({
111
- ...prev,
112
- isProcessing: false,
113
- error: message,
114
- progress: 0,
115
- }));
116
- config.onError?.(message, creationIdRef.current ?? undefined);
132
+ // Error already handled by orchestrator
117
133
  }
118
- }, [state.imageUri, config, options, handleProgress, onBeforeProcess]);
134
+ }, [imageUri, config, options, onBeforeProcess, orchestrator]);
119
135
 
120
136
  const save = useCallback(async () => {
121
- if (!state.processedUrl) return;
137
+ if (!orchestrator.result) return;
122
138
 
123
139
  try {
124
- await onSaveImage(state.processedUrl);
140
+ await onSaveImage(orchestrator.result);
125
141
  } catch (error) {
126
142
  const message = error instanceof Error ? error.message : String(error);
127
- setState((prev) => ({ ...prev, error: message }));
143
+ setImageError(message);
128
144
  }
129
- }, [state.processedUrl, onSaveImage]);
145
+ }, [orchestrator.result, onSaveImage]);
130
146
 
131
147
  const reset = useCallback(() => {
132
- setState(INITIAL_STATE);
133
- }, []);
148
+ setImageUri(null);
149
+ setImageError(null);
150
+ creationIdRef.current = null;
151
+ orchestrator.reset();
152
+ }, [orchestrator]);
134
153
 
154
+ // Combine states for backward compatibility
135
155
  return {
136
- ...state,
156
+ imageUri,
157
+ processedUrl: orchestrator.result,
158
+ isProcessing: orchestrator.isGenerating,
159
+ progress: orchestrator.progress,
160
+ error: orchestrator.error?.message || imageError,
137
161
  selectImage,
138
162
  process,
139
163
  save,
@@ -53,6 +53,7 @@ export type PhotoRestoreResultExtractor = (
53
53
  ) => string | undefined;
54
54
 
55
55
  export interface PhotoRestoreFeatureConfig {
56
+ featureType: "photo-restore";
56
57
  creditCost?: number;
57
58
  extractResult?: PhotoRestoreResultExtractor;
58
59
  prepareImage: (imageUri: string) => Promise<string>;
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * usePhotoRestoreFeature Hook
3
- * Manages photo restore feature state and actions
3
+ * Uses base single image hook for photo restoration
4
+ * Uses centralized orchestrator for credit/error handling
4
5
  */
5
6
 
6
- import { useState, useCallback, useRef } from "react";
7
- import { generateUUID } from "@umituz/react-native-design-system";
8
- import { executeImageFeature } from "../../../../infrastructure/services";
9
- import type {
10
- PhotoRestoreFeatureState,
11
- PhotoRestoreFeatureConfig,
12
- PhotoRestoreResult,
13
- } from "../../domain/types";
7
+ import {
8
+ useSingleImageFeature,
9
+ type BaseSingleImageHookReturn,
10
+ } from "../../../image-to-image";
11
+ import type { AlertMessages } from "../../../../presentation/hooks/generation";
12
+ import type { PhotoRestoreFeatureConfig } from "../../domain/types";
14
13
 
15
14
  export interface UsePhotoRestoreFeatureProps {
16
15
  config: PhotoRestoreFeatureConfig;
@@ -19,124 +18,29 @@ export interface UsePhotoRestoreFeatureProps {
19
18
  onBeforeProcess?: () => Promise<boolean>;
20
19
  }
21
20
 
22
- export interface UsePhotoRestoreFeatureReturn extends PhotoRestoreFeatureState {
23
- selectImage: () => Promise<void>;
24
- process: () => Promise<void>;
25
- save: () => Promise<void>;
26
- reset: () => void;
21
+ export interface UsePhotoRestoreFeatureOptions {
22
+ /** Alert messages for error handling */
23
+ alertMessages?: AlertMessages;
24
+ /** User ID for credit operations */
25
+ userId?: string;
26
+ /** Callback when credits are exhausted */
27
+ onCreditsExhausted?: () => void;
27
28
  }
28
29
 
29
- const initialState: PhotoRestoreFeatureState = {
30
- imageUri: null,
31
- processedUrl: null,
32
- isProcessing: false,
33
- progress: 0,
34
- error: null,
35
- };
30
+ export interface UsePhotoRestoreFeatureReturn extends BaseSingleImageHookReturn {}
36
31
 
37
32
  export function usePhotoRestoreFeature(
38
33
  props: UsePhotoRestoreFeatureProps,
34
+ options?: UsePhotoRestoreFeatureOptions,
39
35
  ): UsePhotoRestoreFeatureReturn {
40
36
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
41
- const [state, setState] = useState<PhotoRestoreFeatureState>(initialState);
42
- const creationIdRef = useRef<string | null>(null);
43
37
 
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
- if (onBeforeProcess) {
65
- const canProceed = await onBeforeProcess();
66
- if (!canProceed) return;
67
- }
68
-
69
- const creationId = generateUUID();
70
- creationIdRef.current = creationId;
71
-
72
- setState((prev) => ({
73
- ...prev,
74
- isProcessing: true,
75
- progress: 0,
76
- error: null,
77
- }));
78
-
79
- config.onProcessingStart?.({ creationId, imageUri: state.imageUri });
80
-
81
- try {
82
- const imageBase64 = await config.prepareImage(state.imageUri);
83
-
84
- const result = await executeImageFeature(
85
- "photo-restore",
86
- { imageBase64 },
87
- { extractResult: config.extractResult, onProgress: handleProgress },
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, creationId } as PhotoRestoreResult & { creationId?: string });
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, creationId);
107
- }
108
- } catch (error) {
109
- const message = error instanceof Error ? error.message : String(error);
110
- setState((prev) => ({
111
- ...prev,
112
- isProcessing: false,
113
- error: message,
114
- progress: 0,
115
- }));
116
- config.onError?.(message, creationIdRef.current ?? undefined);
117
- }
118
- }, [state.imageUri, config, handleProgress, onBeforeProcess]);
119
-
120
- const save = useCallback(async () => {
121
- if (!state.processedUrl) return;
122
-
123
- try {
124
- await onSaveImage(state.processedUrl);
125
- } catch (error) {
126
- const message = error instanceof Error ? error.message : String(error);
127
- setState((prev) => ({ ...prev, error: message }));
128
- }
129
- }, [state.processedUrl, onSaveImage]);
130
-
131
- const reset = useCallback(() => {
132
- setState(initialState);
133
- }, []);
134
-
135
- return {
136
- ...state,
137
- selectImage,
138
- process,
139
- save,
140
- reset,
141
- };
38
+ // Cast config to any to bypass strict type checking while maintaining runtime behavior
39
+ return useSingleImageFeature(
40
+ { config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
41
+ {
42
+ buildInput: (imageBase64) => ({ imageBase64 }),
43
+ ...options,
44
+ },
45
+ );
142
46
  }