@umituz/react-native-ai-generation-content 1.17.225 → 1.17.226
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/ai-hug/presentation/components/AIHugFeature.tsx +2 -2
- package/src/features/ai-kiss/presentation/components/AIKissFeature.tsx +5 -2
- package/src/features/anime-selfie/presentation/components/AnimeSelfieFeature.tsx +7 -3
- package/src/features/face-swap/presentation/components/FaceSwapFeature.tsx +7 -3
- package/src/features/hd-touch-up/presentation/components/HDTouchUpFeature.tsx +7 -3
- package/src/features/image-to-image/domain/types/base.types.ts +41 -226
- package/src/features/image-to-image/domain/types/partials/config.types.ts +37 -0
- package/src/features/image-to-image/domain/types/partials/hook.types.ts +55 -0
- package/src/features/image-to-image/domain/types/partials/metadata.types.ts +32 -0
- package/src/features/image-to-image/domain/types/partials/result.types.ts +44 -0
- package/src/features/image-to-image/domain/types/partials/state.types.ts +34 -0
- package/src/features/image-to-image/domain/types/partials/translation.types.ts +57 -0
- package/src/features/photo-restoration/presentation/components/PhotoRestoreFeature.tsx +7 -3
- package/src/features/remove-background/presentation/components/RemoveBackgroundFeature.tsx +3 -3
- package/src/features/remove-object/presentation/components/RemoveObjectFeature.tsx +3 -3
- package/src/features/replace-background/presentation/components/ReplaceBackgroundFeature.tsx +7 -3
- package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +7 -3
- package/src/infrastructure/utils/error-classifier.util.ts +8 -40
- package/src/infrastructure/utils/error-patterns.constants.ts +41 -0
- package/src/presentation/hooks/base/use-dual-image-feature.ts +68 -76
- package/src/presentation/hooks/base/use-image-with-prompt-feature.ts +70 -73
- package/src/presentation/hooks/base/utils/feature-state.factory.ts +133 -0
- package/src/presentation/layouts/index.ts +14 -11
- package/src/presentation/layouts/types/feature-states.ts +38 -0
- package/src/presentation/layouts/types/index.ts +35 -0
- package/src/presentation/layouts/types/input-props.ts +34 -0
- package/src/presentation/layouts/{types.ts → types/layout-props.ts} +22 -125
- package/src/presentation/layouts/types/result-props.ts +33 -0
- package/src/presentation/layouts/types/translations.ts +35 -0
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
import React, { useMemo } from "react";
|
|
8
8
|
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
9
9
|
import { SingleImageFeatureLayout } from "../../../../presentation/layouts";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ProcessingModalRenderProps,
|
|
12
|
+
SingleImageInputRenderProps,
|
|
13
|
+
CustomResultRenderProps,
|
|
14
|
+
} from "../../../../presentation/layouts";
|
|
11
15
|
import { PhotoRestoreResultView } from "./PhotoRestoreResultView";
|
|
12
16
|
import { usePhotoRestoreFeature } from "../hooks";
|
|
13
17
|
import type {
|
|
@@ -60,7 +64,7 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
60
64
|
translations={translations}
|
|
61
65
|
modalTranslations={modalTranslations}
|
|
62
66
|
renderProcessingModal={renderProcessingModal}
|
|
63
|
-
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
|
|
67
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }: SingleImageInputRenderProps) => (
|
|
64
68
|
<PhotoUploadCard
|
|
65
69
|
imageUri={imageUri}
|
|
66
70
|
onPress={onSelect}
|
|
@@ -80,7 +84,7 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
80
84
|
}}
|
|
81
85
|
/>
|
|
82
86
|
)}
|
|
83
|
-
renderCustomResult={({ processedUrl, originalImageUri, onSave, onReset }) => (
|
|
87
|
+
renderCustomResult={({ processedUrl, originalImageUri, onSave, onReset }: CustomResultRenderProps) => (
|
|
84
88
|
<PhotoRestoreResultView
|
|
85
89
|
originalUri={originalImageUri}
|
|
86
90
|
processedUri={processedUrl}
|
|
@@ -8,7 +8,7 @@ import React, { useMemo } from "react";
|
|
|
8
8
|
import { Image, StyleSheet } from "react-native";
|
|
9
9
|
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
10
10
|
import { SingleImageFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
-
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps, SingleImageInputRenderProps, ResultRenderProps } from "../../../../presentation/layouts";
|
|
12
12
|
import { useRemoveBackgroundFeature } from "../hooks";
|
|
13
13
|
import type {
|
|
14
14
|
RemoveBackgroundTranslations,
|
|
@@ -60,7 +60,7 @@ export const RemoveBackgroundFeature: React.FC<RemoveBackgroundFeatureProps> = (
|
|
|
60
60
|
translations={translations}
|
|
61
61
|
modalTranslations={modalTranslations}
|
|
62
62
|
renderProcessingModal={renderProcessingModal}
|
|
63
|
-
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
|
|
63
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }: SingleImageInputRenderProps) => (
|
|
64
64
|
<PhotoUploadCard
|
|
65
65
|
imageUri={imageUri}
|
|
66
66
|
onPress={onSelect}
|
|
@@ -80,7 +80,7 @@ export const RemoveBackgroundFeature: React.FC<RemoveBackgroundFeatureProps> = (
|
|
|
80
80
|
}}
|
|
81
81
|
/>
|
|
82
82
|
)}
|
|
83
|
-
renderResult={({ imageUrl, imageSize }) => (
|
|
83
|
+
renderResult={({ imageUrl, imageSize }: ResultRenderProps) => (
|
|
84
84
|
<Image
|
|
85
85
|
source={{ uri: imageUrl }}
|
|
86
86
|
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
13
|
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
14
14
|
import { SingleImageWithPromptFeatureLayout } from "../../../../presentation/layouts";
|
|
15
|
-
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
15
|
+
import type { ProcessingModalRenderProps, SingleImageWithPromptInputRenderProps, ResultRenderProps } from "../../../../presentation/layouts";
|
|
16
16
|
import { useRemoveObjectFeature } from "../hooks";
|
|
17
17
|
import type {
|
|
18
18
|
RemoveObjectTranslations,
|
|
@@ -66,7 +66,7 @@ export const RemoveObjectFeature: React.FC<RemoveObjectFeatureProps> = ({
|
|
|
66
66
|
translations={translations}
|
|
67
67
|
modalTranslations={modalTranslations}
|
|
68
68
|
renderProcessingModal={renderProcessingModal}
|
|
69
|
-
renderInput={({ imageUri, onSelect, isDisabled, isProcessing, prompt, onPromptChange }) => (
|
|
69
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing, prompt, onPromptChange }: SingleImageWithPromptInputRenderProps) => (
|
|
70
70
|
<>
|
|
71
71
|
<PhotoUploadCard
|
|
72
72
|
imageUri={imageUri}
|
|
@@ -120,7 +120,7 @@ export const RemoveObjectFeature: React.FC<RemoveObjectFeatureProps> = ({
|
|
|
120
120
|
</View>
|
|
121
121
|
</>
|
|
122
122
|
)}
|
|
123
|
-
renderResult={({ imageUrl, imageSize }) => (
|
|
123
|
+
renderResult={({ imageUrl, imageSize }: ResultRenderProps) => (
|
|
124
124
|
<Image
|
|
125
125
|
source={{ uri: imageUrl }}
|
|
126
126
|
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
package/src/features/replace-background/presentation/components/ReplaceBackgroundFeature.tsx
CHANGED
|
@@ -9,7 +9,11 @@ import { View, Image, StyleSheet } from "react-native";
|
|
|
9
9
|
import { AtomicInput } from "@umituz/react-native-design-system";
|
|
10
10
|
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
11
11
|
import { SingleImageWithPromptFeatureLayout } from "../../../../presentation/layouts";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ProcessingModalRenderProps,
|
|
14
|
+
SingleImageWithPromptInputRenderProps,
|
|
15
|
+
ResultRenderProps,
|
|
16
|
+
} from "../../../../presentation/layouts";
|
|
13
17
|
import { useReplaceBackgroundFeature } from "../hooks";
|
|
14
18
|
import type {
|
|
15
19
|
ReplaceBackgroundTranslations,
|
|
@@ -61,7 +65,7 @@ export const ReplaceBackgroundFeature: React.FC<ReplaceBackgroundFeatureProps> =
|
|
|
61
65
|
translations={translations}
|
|
62
66
|
modalTranslations={modalTranslations}
|
|
63
67
|
renderProcessingModal={renderProcessingModal}
|
|
64
|
-
renderInput={({ imageUri, onSelect, isDisabled, isProcessing, prompt, onPromptChange }) => (
|
|
68
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing, prompt, onPromptChange }: SingleImageWithPromptInputRenderProps) => (
|
|
65
69
|
<>
|
|
66
70
|
<PhotoUploadCard
|
|
67
71
|
imageUri={imageUri}
|
|
@@ -94,7 +98,7 @@ export const ReplaceBackgroundFeature: React.FC<ReplaceBackgroundFeatureProps> =
|
|
|
94
98
|
</View>
|
|
95
99
|
</>
|
|
96
100
|
)}
|
|
97
|
-
renderResult={({ imageUrl, imageSize }) => (
|
|
101
|
+
renderResult={({ imageUrl, imageSize }: ResultRenderProps) => (
|
|
98
102
|
<Image
|
|
99
103
|
source={{ uri: imageUrl }}
|
|
100
104
|
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
import React, { useMemo } from "react";
|
|
8
8
|
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
9
9
|
import { SingleImageFeatureLayout } from "../../../../presentation/layouts";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ProcessingModalRenderProps,
|
|
12
|
+
SingleImageInputRenderProps,
|
|
13
|
+
CustomResultRenderProps,
|
|
14
|
+
} from "../../../../presentation/layouts";
|
|
11
15
|
import { UpscaleResultView } from "./UpscaleResultView";
|
|
12
16
|
import { useUpscaleFeature } from "../hooks";
|
|
13
17
|
import type {
|
|
@@ -61,7 +65,7 @@ export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
|
61
65
|
modalTranslations={modalTranslations}
|
|
62
66
|
description={translations.description}
|
|
63
67
|
renderProcessingModal={renderProcessingModal}
|
|
64
|
-
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
|
|
68
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }: SingleImageInputRenderProps) => (
|
|
65
69
|
<PhotoUploadCard
|
|
66
70
|
imageUri={imageUri}
|
|
67
71
|
onPress={onSelect}
|
|
@@ -81,7 +85,7 @@ export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
|
81
85
|
}}
|
|
82
86
|
/>
|
|
83
87
|
)}
|
|
84
|
-
renderCustomResult={({ processedUrl, originalImageUri, onSave, onReset }) => (
|
|
88
|
+
renderCustomResult={({ processedUrl, originalImageUri, onSave, onReset }: CustomResultRenderProps) => (
|
|
85
89
|
<UpscaleResultView
|
|
86
90
|
originalUri={originalImageUri}
|
|
87
91
|
processedUri={processedUrl}
|
|
@@ -4,47 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { AIErrorType, type AIErrorInfo } from "../../domain/entities";
|
|
7
|
+
import {
|
|
8
|
+
NETWORK_ERROR_PATTERNS,
|
|
9
|
+
RATE_LIMIT_PATTERNS,
|
|
10
|
+
AUTH_ERROR_PATTERNS,
|
|
11
|
+
CONTENT_POLICY_PATTERNS,
|
|
12
|
+
SERVER_ERROR_PATTERNS,
|
|
13
|
+
} from "./error-patterns.constants";
|
|
7
14
|
|
|
8
15
|
declare const __DEV__: boolean;
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
"network",
|
|
12
|
-
"timeout",
|
|
13
|
-
"socket",
|
|
14
|
-
"econnrefused",
|
|
15
|
-
"enotfound",
|
|
16
|
-
"fetch failed",
|
|
17
|
-
"connection",
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const RATE_LIMIT_PATTERNS = ["rate limit", "too many requests", "429"];
|
|
21
|
-
|
|
22
|
-
const AUTH_ERROR_PATTERNS = [
|
|
23
|
-
"unauthorized",
|
|
24
|
-
"authentication",
|
|
25
|
-
"invalid api key",
|
|
26
|
-
"401",
|
|
27
|
-
"403",
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const CONTENT_POLICY_PATTERNS = [
|
|
31
|
-
"content policy",
|
|
32
|
-
"safety",
|
|
33
|
-
"moderation",
|
|
34
|
-
"inappropriate",
|
|
35
|
-
"blocked",
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const SERVER_ERROR_PATTERNS = [
|
|
39
|
-
"internal server error",
|
|
40
|
-
"500",
|
|
41
|
-
"502",
|
|
42
|
-
"503",
|
|
43
|
-
"504",
|
|
44
|
-
"service unavailable",
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
function matchesPatterns(message: string, patterns: string[]): boolean {
|
|
17
|
+
function matchesPatterns(message: string, patterns: readonly string[]): boolean {
|
|
48
18
|
const lowerMessage = message.toLowerCase();
|
|
49
19
|
return patterns.some((pattern) => lowerMessage.includes(pattern));
|
|
50
20
|
}
|
|
@@ -64,7 +34,6 @@ function getStatusCode(error: unknown): number | undefined {
|
|
|
64
34
|
|
|
65
35
|
function logClassification(info: AIErrorInfo): AIErrorInfo {
|
|
66
36
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
67
|
-
|
|
68
37
|
console.log("[ErrorClassifier] Classified as:", {
|
|
69
38
|
type: info.type,
|
|
70
39
|
messageKey: info.messageKey,
|
|
@@ -79,7 +48,6 @@ export function classifyError(error: unknown): AIErrorInfo {
|
|
|
79
48
|
const statusCode = getStatusCode(error);
|
|
80
49
|
|
|
81
50
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
82
|
-
|
|
83
51
|
console.log("[ErrorClassifier] Classifying error:", {
|
|
84
52
|
message: message.slice(0, 100),
|
|
85
53
|
statusCode,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Pattern Constants
|
|
3
|
+
* Error classification patterns for error classifier utility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const NETWORK_ERROR_PATTERNS = [
|
|
7
|
+
"network",
|
|
8
|
+
"timeout",
|
|
9
|
+
"socket",
|
|
10
|
+
"econnrefused",
|
|
11
|
+
"enotfound",
|
|
12
|
+
"fetch failed",
|
|
13
|
+
"connection",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export const RATE_LIMIT_PATTERNS = ["rate limit", "too many requests", "429"] as const;
|
|
17
|
+
|
|
18
|
+
export const AUTH_ERROR_PATTERNS = [
|
|
19
|
+
"unauthorized",
|
|
20
|
+
"authentication",
|
|
21
|
+
"invalid api key",
|
|
22
|
+
"401",
|
|
23
|
+
"403",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export const CONTENT_POLICY_PATTERNS = [
|
|
27
|
+
"content policy",
|
|
28
|
+
"safety",
|
|
29
|
+
"moderation",
|
|
30
|
+
"inappropriate",
|
|
31
|
+
"blocked",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export const SERVER_ERROR_PATTERNS = [
|
|
35
|
+
"internal server error",
|
|
36
|
+
"500",
|
|
37
|
+
"502",
|
|
38
|
+
"503",
|
|
39
|
+
"504",
|
|
40
|
+
"service unavailable",
|
|
41
|
+
] as const;
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
OnSelectImageCallback,
|
|
14
14
|
OnSaveCallback,
|
|
15
15
|
} from "./types";
|
|
16
|
+
import { createFeatureStateHandlers, executeProcess, executeSave } from "./utils/feature-state.factory";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Request passed to processRequest callback
|
|
@@ -30,7 +31,7 @@ export interface UseDualImageFeatureConfig {
|
|
|
30
31
|
readonly onSelectFirstImage: OnSelectImageCallback;
|
|
31
32
|
readonly onSelectSecondImage: OnSelectImageCallback;
|
|
32
33
|
readonly processRequest: (
|
|
33
|
-
request: DualImageProcessRequest
|
|
34
|
+
request: DualImageProcessRequest,
|
|
34
35
|
) => Promise<FeatureProcessResult>;
|
|
35
36
|
readonly onSave?: OnSaveCallback;
|
|
36
37
|
readonly onError?: (error: string) => void;
|
|
@@ -57,27 +58,39 @@ export interface UseDualImageFeatureReturn
|
|
|
57
58
|
readonly save: () => Promise<void>;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
const initialState: DualImageFeatureState = {
|
|
62
|
+
firstImageUri: null,
|
|
63
|
+
secondImageUri: null,
|
|
64
|
+
processedUrl: null,
|
|
65
|
+
isProcessing: false,
|
|
66
|
+
progress: 0,
|
|
67
|
+
error: null,
|
|
68
|
+
};
|
|
69
|
+
|
|
60
70
|
export function useDualImageFeature(
|
|
61
|
-
config: UseDualImageFeatureConfig
|
|
71
|
+
config: UseDualImageFeatureConfig,
|
|
62
72
|
): UseDualImageFeatureReturn {
|
|
63
|
-
const [
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
const [state, setState] = useState<DualImageFeatureState>(initialState);
|
|
74
|
+
|
|
75
|
+
const { reset, clearError } = createFeatureStateHandlers({
|
|
76
|
+
setState,
|
|
77
|
+
initialState,
|
|
78
|
+
});
|
|
69
79
|
|
|
70
80
|
const selectFirstImage = useCallback(async (): Promise<void> => {
|
|
71
81
|
try {
|
|
72
82
|
const uri = await config.onSelectFirstImage();
|
|
73
83
|
if (uri) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
setState((prev) => ({
|
|
85
|
+
...prev,
|
|
86
|
+
firstImageUri: uri,
|
|
87
|
+
error: null,
|
|
88
|
+
processedUrl: null,
|
|
89
|
+
}));
|
|
77
90
|
}
|
|
78
91
|
} catch (err) {
|
|
79
92
|
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
80
|
-
|
|
93
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
81
94
|
config.onError?.(message);
|
|
82
95
|
}
|
|
83
96
|
}, [config]);
|
|
@@ -86,88 +99,67 @@ export function useDualImageFeature(
|
|
|
86
99
|
try {
|
|
87
100
|
const uri = await config.onSelectSecondImage();
|
|
88
101
|
if (uri) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
setState((prev) => ({
|
|
103
|
+
...prev,
|
|
104
|
+
secondImageUri: uri,
|
|
105
|
+
error: null,
|
|
106
|
+
processedUrl: null,
|
|
107
|
+
}));
|
|
92
108
|
}
|
|
93
109
|
} catch (err) {
|
|
94
110
|
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
95
|
-
|
|
111
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
96
112
|
config.onError?.(message);
|
|
97
113
|
}
|
|
98
114
|
}, [config]);
|
|
99
115
|
|
|
100
116
|
const process = useCallback(async (): Promise<void> => {
|
|
101
|
-
if (!firstImageUri || !secondImageUri) {
|
|
117
|
+
if (!state.firstImageUri || !state.secondImageUri) {
|
|
102
118
|
const message = "error.noImages";
|
|
103
|
-
|
|
119
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
104
120
|
config.onError?.(message);
|
|
105
121
|
return;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
setProgress(0);
|
|
124
|
+
const result = await executeProcess({
|
|
125
|
+
canProcess: () => !!state.firstImageUri && !!state.secondImageUri,
|
|
126
|
+
setError: (error) => setState((prev) => ({ ...prev, error })),
|
|
127
|
+
setProcessing: (isProcessing) => setState((prev) => ({ ...prev, isProcessing })),
|
|
128
|
+
onError: config.onError,
|
|
129
|
+
processFn: () =>
|
|
130
|
+
config.processRequest({
|
|
131
|
+
firstImageUri: state.firstImageUri!,
|
|
132
|
+
secondImageUri: state.secondImageUri!,
|
|
133
|
+
onProgress: (progress) => setState((prev) => ({ ...prev, progress })),
|
|
134
|
+
}),
|
|
135
|
+
onSuccess: (result) => {
|
|
136
|
+
if (result.outputUrl) {
|
|
137
|
+
setState((prev) => ({ ...prev, processedUrl: result.outputUrl ?? null }));
|
|
138
|
+
config.onSuccess?.(result.outputUrl);
|
|
139
|
+
} else {
|
|
140
|
+
const message = result.error || "error.processing";
|
|
141
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
142
|
+
config.onError?.(message);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!result) {
|
|
148
|
+
setState((prev) => ({ ...prev, progress: 0 }));
|
|
134
149
|
}
|
|
135
|
-
}, [firstImageUri, secondImageUri, config]);
|
|
150
|
+
}, [state.firstImageUri, state.secondImageUri, config]);
|
|
136
151
|
|
|
137
152
|
const save = useCallback(async (): Promise<void> => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
}, []);
|
|
153
|
+
await executeSave({
|
|
154
|
+
processedUrl: state.processedUrl,
|
|
155
|
+
onSave: config.onSave,
|
|
156
|
+
setError: (error) => setState((prev) => ({ ...prev, error })),
|
|
157
|
+
onError: config.onError,
|
|
158
|
+
});
|
|
159
|
+
}, [state.processedUrl, config]);
|
|
163
160
|
|
|
164
161
|
return {
|
|
165
|
-
|
|
166
|
-
secondImageUri,
|
|
167
|
-
processedUrl,
|
|
168
|
-
isProcessing,
|
|
169
|
-
progress,
|
|
170
|
-
error,
|
|
162
|
+
...state,
|
|
171
163
|
selectFirstImage,
|
|
172
164
|
selectSecondImage,
|
|
173
165
|
process,
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
OnSelectImageCallback,
|
|
14
14
|
OnSaveCallback,
|
|
15
15
|
} from "./types";
|
|
16
|
+
import { createFeatureStateHandlers, executeProcess, executeSave } from "./utils/feature-state.factory";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Request passed to processRequest callback
|
|
@@ -57,109 +58,105 @@ export interface UseImageWithPromptFeatureReturn
|
|
|
57
58
|
readonly save: () => Promise<void>;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
const initialState: ImageWithPromptFeatureState = {
|
|
62
|
+
imageUri: null,
|
|
63
|
+
prompt: "",
|
|
64
|
+
processedUrl: null,
|
|
65
|
+
isProcessing: false,
|
|
66
|
+
progress: 0,
|
|
67
|
+
error: null,
|
|
68
|
+
};
|
|
69
|
+
|
|
60
70
|
export function useImageWithPromptFeature(
|
|
61
|
-
config: UseImageWithPromptFeatureConfig
|
|
71
|
+
config: UseImageWithPromptFeatureConfig,
|
|
62
72
|
): UseImageWithPromptFeatureReturn {
|
|
63
|
-
const [
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
const [state, setState] = useState<ImageWithPromptFeatureState>(initialState);
|
|
74
|
+
|
|
75
|
+
const { reset, clearError } = createFeatureStateHandlers({
|
|
76
|
+
setState,
|
|
77
|
+
initialState,
|
|
78
|
+
});
|
|
69
79
|
|
|
70
80
|
const selectImage = useCallback(async (): Promise<void> => {
|
|
71
81
|
try {
|
|
72
82
|
const uri = await config.onSelectImage();
|
|
73
83
|
if (uri) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
setState((prev) => ({
|
|
85
|
+
...prev,
|
|
86
|
+
imageUri: uri,
|
|
87
|
+
error: null,
|
|
88
|
+
processedUrl: null,
|
|
89
|
+
}));
|
|
77
90
|
}
|
|
78
91
|
} catch (err) {
|
|
79
92
|
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
80
|
-
|
|
93
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
81
94
|
config.onError?.(message);
|
|
82
95
|
}
|
|
83
96
|
}, [config]);
|
|
84
97
|
|
|
98
|
+
const setPrompt = useCallback((prompt: string) => {
|
|
99
|
+
setState((prev) => ({ ...prev, prompt }));
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
85
102
|
const process = useCallback(async (): Promise<void> => {
|
|
86
|
-
if (!imageUri) {
|
|
103
|
+
if (!state.imageUri) {
|
|
87
104
|
const message = "error.noImage";
|
|
88
|
-
|
|
105
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
89
106
|
config.onError?.(message);
|
|
90
107
|
return;
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
if (config.requirePrompt && !prompt.trim()) {
|
|
110
|
+
if (config.requirePrompt && !state.prompt.trim()) {
|
|
94
111
|
const message = "error.noPrompt";
|
|
95
|
-
|
|
112
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
96
113
|
config.onError?.(message);
|
|
97
114
|
return;
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
const result = await executeProcess({
|
|
118
|
+
canProcess: () => {
|
|
119
|
+
if (!state.imageUri) return false;
|
|
120
|
+
if (config.requirePrompt) return !!state.prompt.trim();
|
|
121
|
+
return true;
|
|
122
|
+
},
|
|
123
|
+
setError: (error) => setState((prev) => ({ ...prev, error })),
|
|
124
|
+
setProcessing: (isProcessing) => setState((prev) => ({ ...prev, isProcessing })),
|
|
125
|
+
onError: config.onError,
|
|
126
|
+
processFn: () =>
|
|
127
|
+
config.processRequest({
|
|
128
|
+
imageUri: state.imageUri!,
|
|
129
|
+
prompt: state.prompt.trim(),
|
|
130
|
+
onProgress: (progress) => setState((prev) => ({ ...prev, progress })),
|
|
131
|
+
}),
|
|
132
|
+
onSuccess: (result) => {
|
|
133
|
+
if (result.outputUrl) {
|
|
134
|
+
setState((prev) => ({ ...prev, processedUrl: result.outputUrl ?? null }));
|
|
135
|
+
config.onSuccess?.(result.outputUrl);
|
|
136
|
+
} else {
|
|
137
|
+
const message = result.error || "error.processing";
|
|
138
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
139
|
+
config.onError?.(message);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!result) {
|
|
145
|
+
setState((prev) => ({ ...prev, progress: 0 }));
|
|
126
146
|
}
|
|
127
|
-
}, [imageUri, prompt, config]);
|
|
147
|
+
}, [state.imageUri, state.prompt, config]);
|
|
128
148
|
|
|
129
149
|
const save = useCallback(async (): Promise<void> => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
}, []);
|
|
150
|
+
await executeSave({
|
|
151
|
+
processedUrl: state.processedUrl,
|
|
152
|
+
onSave: config.onSave,
|
|
153
|
+
setError: (error) => setState((prev) => ({ ...prev, error })),
|
|
154
|
+
onError: config.onError,
|
|
155
|
+
});
|
|
156
|
+
}, [state.processedUrl, config]);
|
|
155
157
|
|
|
156
158
|
return {
|
|
157
|
-
|
|
158
|
-
prompt,
|
|
159
|
-
processedUrl,
|
|
160
|
-
isProcessing,
|
|
161
|
-
progress,
|
|
162
|
-
error,
|
|
159
|
+
...state,
|
|
163
160
|
selectImage,
|
|
164
161
|
setPrompt,
|
|
165
162
|
process,
|