@umituz/react-native-ai-generation-content 1.17.309 → 1.18.0
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 +1 -1
- package/src/features/anime-selfie/domain/types/anime-selfie.types.ts +1 -0
- package/src/features/anime-selfie/presentation/hooks/useAnimeSelfieFeature.ts +31 -126
- package/src/features/hd-touch-up/domain/types/hd-touch-up.types.ts +1 -0
- package/src/features/hd-touch-up/presentation/hooks/useHDTouchUpFeature.ts +25 -105
- package/src/features/image-to-image/presentation/hooks/useDualImageFeature.ts +111 -75
- package/src/features/image-to-image/presentation/hooks/useImageWithPromptFeature.ts +115 -84
- package/src/features/image-to-image/presentation/hooks/useSingleImageFeature.ts +93 -69
- package/src/features/photo-restoration/domain/types/photo-restore.types.ts +1 -0
- package/src/features/photo-restoration/presentation/hooks/usePhotoRestoreFeature.ts +25 -121
- package/src/features/remove-object/presentation/hooks/useRemoveObjectFeature.ts +125 -79
- package/src/features/shared/dual-image-video/presentation/hooks/useDualImageVideoFeature.ts +106 -64
- package/src/index.ts +6 -4
- package/src/presentation/hooks/generation/index.ts +19 -0
- package/src/presentation/hooks/generation/useImageGeneration.ts +157 -0
- package/src/presentation/hooks/generation/useVideoGeneration.ts +107 -0
- package/src/presentation/hooks/index.ts +8 -12
- package/src/presentation/hooks/base/index.ts +0 -9
- package/src/presentation/hooks/base/types.ts +0 -47
- package/src/presentation/hooks/base/use-dual-image-feature.ts +0 -170
- package/src/presentation/hooks/base/use-image-with-prompt-feature.ts +0 -167
- package/src/presentation/hooks/base/use-single-image-feature.ts +0 -154
- package/src/presentation/hooks/base/utils/feature-state.factory.ts +0 -133
- package/src/presentation/hooks/generation-callbacks.types.ts +0 -42
- package/src/presentation/hooks/useGenerationCallbacksBuilder.ts +0 -126
|
@@ -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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
+
setImageError(message);
|
|
81
141
|
}
|
|
82
142
|
}, [onSelectImage, config]);
|
|
83
143
|
|
|
84
144
|
const setPrompt = useCallback(
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
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 (!
|
|
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 && !
|
|
161
|
+
if (options?.promptRequired && !prompt.trim()) {
|
|
105
162
|
const error = "Prompt is required";
|
|
106
|
-
|
|
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
|
-
|
|
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(
|
|
125
|
-
|
|
126
|
-
const input = options?.buildInput
|
|
127
|
-
?
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
188
|
+
}, [imageUri, prompt, config, options, onBeforeProcess, orchestrator]);
|
|
165
189
|
|
|
166
190
|
const save = useCallback(async () => {
|
|
167
|
-
if (!
|
|
191
|
+
if (!orchestrator.result) return;
|
|
168
192
|
|
|
169
193
|
try {
|
|
170
|
-
await onSaveImage(
|
|
194
|
+
await onSaveImage(orchestrator.result);
|
|
171
195
|
} catch (error) {
|
|
172
196
|
const message = error instanceof Error ? error.message : String(error);
|
|
173
|
-
|
|
197
|
+
setImageError(message);
|
|
174
198
|
}
|
|
175
|
-
}, [
|
|
199
|
+
}, [orchestrator.result, onSaveImage]);
|
|
176
200
|
|
|
177
201
|
const reset = useCallback(() => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
134
|
+
}, [imageUri, config, options, onBeforeProcess, orchestrator]);
|
|
119
135
|
|
|
120
136
|
const save = useCallback(async () => {
|
|
121
|
-
if (!
|
|
137
|
+
if (!orchestrator.result) return;
|
|
122
138
|
|
|
123
139
|
try {
|
|
124
|
-
await onSaveImage(
|
|
140
|
+
await onSaveImage(orchestrator.result);
|
|
125
141
|
} catch (error) {
|
|
126
142
|
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
-
|
|
143
|
+
setImageError(message);
|
|
128
144
|
}
|
|
129
|
-
}, [
|
|
145
|
+
}, [orchestrator.result, onSaveImage]);
|
|
130
146
|
|
|
131
147
|
const reset = useCallback(() => {
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
3
|
+
* Uses base single image hook for photo restoration
|
|
4
|
+
* Uses centralized orchestrator for credit/error handling
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
}
|