@umituz/react-native-ai-generation-content 1.17.144 → 1.17.146
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/photo-restoration/presentation/components/PhotoRestoreResultView.tsx +1 -1
- package/src/features/replace-background/index.ts +5 -50
- package/src/features/replace-background/presentation/components/index.ts +0 -12
- package/src/features/replace-background/presentation/hooks/index.ts +0 -3
- package/src/infrastructure/utils/index.ts +1 -0
- package/src/infrastructure/utils/result-validator.util.ts +0 -214
- package/src/infrastructure/utils/url-extractor.util.ts +209 -0
- package/src/presentation/hooks/flow-state.utils.ts +101 -0
- package/src/presentation/hooks/useGenerationFlow.ts +47 -178
- package/src/presentation/types/flow-config.types.ts +5 -99
- package/src/presentation/types/flow-default-configs.ts +106 -0
- package/src/features/replace-background/domain/entities/background.types.ts +0 -77
- package/src/features/replace-background/domain/entities/component.types.ts +0 -87
- package/src/features/replace-background/domain/entities/config.types.ts +0 -41
- package/src/features/replace-background/domain/entities/index.ts +0 -30
- package/src/features/replace-background/infrastructure/constants/index.ts +0 -5
- package/src/features/replace-background/infrastructure/constants/prompts.constants.ts +0 -15
- package/src/features/replace-background/infrastructure/index.ts +0 -5
- package/src/features/replace-background/presentation/components/BackgroundFeature.tsx +0 -143
- package/src/features/replace-background/presentation/components/ComparisonSlider.tsx +0 -187
- package/src/features/replace-background/presentation/components/ErrorDisplay.tsx +0 -60
- package/src/features/replace-background/presentation/components/FeatureHeader.tsx +0 -80
- package/src/features/replace-background/presentation/components/GenerateButton.tsx +0 -85
- package/src/features/replace-background/presentation/components/ImagePicker.tsx +0 -136
- package/src/features/replace-background/presentation/components/ModeSelector.tsx +0 -78
- package/src/features/replace-background/presentation/components/PromptInput.tsx +0 -142
- package/src/features/replace-background/presentation/components/ResultDisplay.tsx +0 -122
- package/src/features/replace-background/presentation/hooks/useBackgroundFeature.ts +0 -119
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Picker Component
|
|
3
|
-
* @description Image selection component with placeholder state
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { memo } from "react";
|
|
7
|
-
import { View, StyleSheet, TouchableOpacity, Image } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
AtomicIcon,
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
} from "@umituz/react-native-design-system";
|
|
13
|
-
import type { ImagePickerProps } from "../../domain/entities";
|
|
14
|
-
|
|
15
|
-
export const ImagePicker: React.FC<ImagePickerProps> = memo(
|
|
16
|
-
function ImagePicker({
|
|
17
|
-
imageUri,
|
|
18
|
-
isProcessing,
|
|
19
|
-
onSelectImage,
|
|
20
|
-
placeholderText,
|
|
21
|
-
}) {
|
|
22
|
-
const tokens = useAppDesignTokens();
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<View style={styles.container}>
|
|
26
|
-
<TouchableOpacity
|
|
27
|
-
style={[
|
|
28
|
-
styles.imagePickerBox,
|
|
29
|
-
{ backgroundColor: tokens.colors.surface },
|
|
30
|
-
]}
|
|
31
|
-
onPress={onSelectImage}
|
|
32
|
-
disabled={isProcessing}
|
|
33
|
-
activeOpacity={0.8}
|
|
34
|
-
>
|
|
35
|
-
{imageUri ? (
|
|
36
|
-
<View style={styles.imageContainer}>
|
|
37
|
-
<Image source={{ uri: imageUri }} style={styles.image} />
|
|
38
|
-
<View style={styles.imageOverlay}>
|
|
39
|
-
<View
|
|
40
|
-
style={[
|
|
41
|
-
styles.editBadge,
|
|
42
|
-
{ backgroundColor: tokens.colors.primary },
|
|
43
|
-
]}
|
|
44
|
-
>
|
|
45
|
-
<AtomicIcon
|
|
46
|
-
name="image-plus"
|
|
47
|
-
size="md"
|
|
48
|
-
|
|
49
|
-
/>
|
|
50
|
-
</View>
|
|
51
|
-
</View>
|
|
52
|
-
</View>
|
|
53
|
-
) : (
|
|
54
|
-
<View
|
|
55
|
-
style={[
|
|
56
|
-
styles.placeholderContainer,
|
|
57
|
-
{ backgroundColor: tokens.colors.surface },
|
|
58
|
-
]}
|
|
59
|
-
>
|
|
60
|
-
<View
|
|
61
|
-
style={[
|
|
62
|
-
styles.uploadIconContainer,
|
|
63
|
-
{ backgroundColor: tokens.colors.surfaceSecondary },
|
|
64
|
-
]}
|
|
65
|
-
>
|
|
66
|
-
<AtomicIcon
|
|
67
|
-
name="upload"
|
|
68
|
-
size="lg"
|
|
69
|
-
|
|
70
|
-
/>
|
|
71
|
-
</View>
|
|
72
|
-
<AtomicText
|
|
73
|
-
|
|
74
|
-
style={[
|
|
75
|
-
styles.placeholderText,
|
|
76
|
-
{ color: tokens.colors.primary },
|
|
77
|
-
]}
|
|
78
|
-
>
|
|
79
|
-
{placeholderText}
|
|
80
|
-
</AtomicText>
|
|
81
|
-
</View>
|
|
82
|
-
)}
|
|
83
|
-
</TouchableOpacity>
|
|
84
|
-
</View>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const styles = StyleSheet.create({
|
|
90
|
-
container: {
|
|
91
|
-
marginVertical: 16,
|
|
92
|
-
alignItems: "center",
|
|
93
|
-
},
|
|
94
|
-
imagePickerBox: {
|
|
95
|
-
width: "100%",
|
|
96
|
-
aspectRatio: 1,
|
|
97
|
-
borderRadius: 20,
|
|
98
|
-
overflow: "hidden",
|
|
99
|
-
},
|
|
100
|
-
imageContainer: {
|
|
101
|
-
flex: 1,
|
|
102
|
-
position: "relative",
|
|
103
|
-
},
|
|
104
|
-
image: {
|
|
105
|
-
width: "100%",
|
|
106
|
-
height: "100%",
|
|
107
|
-
},
|
|
108
|
-
imageOverlay: {
|
|
109
|
-
position: "absolute",
|
|
110
|
-
bottom: 0,
|
|
111
|
-
left: 0,
|
|
112
|
-
right: 0,
|
|
113
|
-
height: "25%",
|
|
114
|
-
justifyContent: "flex-end",
|
|
115
|
-
alignItems: "flex-end",
|
|
116
|
-
padding: 16,
|
|
117
|
-
},
|
|
118
|
-
editBadge: {
|
|
119
|
-
borderRadius: 20,
|
|
120
|
-
padding: 10,
|
|
121
|
-
},
|
|
122
|
-
placeholderContainer: {
|
|
123
|
-
flex: 1,
|
|
124
|
-
justifyContent: "center",
|
|
125
|
-
alignItems: "center",
|
|
126
|
-
},
|
|
127
|
-
uploadIconContainer: {
|
|
128
|
-
borderRadius: 50,
|
|
129
|
-
padding: 24,
|
|
130
|
-
marginBottom: 16,
|
|
131
|
-
},
|
|
132
|
-
placeholderText: {
|
|
133
|
-
textAlign: "center",
|
|
134
|
-
fontWeight: "600",
|
|
135
|
-
},
|
|
136
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mode Selector Component
|
|
3
|
-
* @description Horizontal scrollable mode selection toolbar
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { memo } from "react";
|
|
8
|
-
import { View, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
AtomicIcon,
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
} from "@umituz/react-native-design-system";
|
|
13
|
-
import type { ModeSelectorProps } from "../../domain/entities";
|
|
14
|
-
|
|
15
|
-
export const ModeSelector: React.FC<ModeSelectorProps> = memo(
|
|
16
|
-
function ModeSelector({ activeMode, onModeChange, isProcessing, modes }) {
|
|
17
|
-
const tokens = useAppDesignTokens();
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<View
|
|
21
|
-
style={[
|
|
22
|
-
styles.container,
|
|
23
|
-
{ backgroundColor: tokens.colors.surface },
|
|
24
|
-
]}
|
|
25
|
-
>
|
|
26
|
-
<ScrollView
|
|
27
|
-
horizontal
|
|
28
|
-
showsHorizontalScrollIndicator={false}
|
|
29
|
-
contentContainerStyle={styles.scrollContent}
|
|
30
|
-
>
|
|
31
|
-
{modes.map((mode) => {
|
|
32
|
-
const isActive = activeMode === mode.id;
|
|
33
|
-
return (
|
|
34
|
-
<TouchableOpacity
|
|
35
|
-
key={mode.id}
|
|
36
|
-
style={[
|
|
37
|
-
styles.modeButton,
|
|
38
|
-
isActive && {
|
|
39
|
-
backgroundColor: tokens.colors.primary,
|
|
40
|
-
},
|
|
41
|
-
]}
|
|
42
|
-
onPress={() => !isProcessing && onModeChange(mode.id)}
|
|
43
|
-
disabled={isProcessing}
|
|
44
|
-
activeOpacity={0.7}
|
|
45
|
-
>
|
|
46
|
-
<AtomicIcon
|
|
47
|
-
name={mode.icon}
|
|
48
|
-
size="md"
|
|
49
|
-
color={isActive ? "onPrimary" : "onSurface"}
|
|
50
|
-
/>
|
|
51
|
-
</TouchableOpacity>
|
|
52
|
-
);
|
|
53
|
-
})}
|
|
54
|
-
</ScrollView>
|
|
55
|
-
</View>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const styles = StyleSheet.create({
|
|
61
|
-
container: {
|
|
62
|
-
borderRadius: 24,
|
|
63
|
-
paddingVertical: 8,
|
|
64
|
-
paddingHorizontal: 4,
|
|
65
|
-
},
|
|
66
|
-
scrollContent: {
|
|
67
|
-
flexDirection: "row",
|
|
68
|
-
gap: 8,
|
|
69
|
-
paddingHorizontal: 8,
|
|
70
|
-
},
|
|
71
|
-
modeButton: {
|
|
72
|
-
width: 48,
|
|
73
|
-
height: 48,
|
|
74
|
-
borderRadius: 16,
|
|
75
|
-
justifyContent: "center",
|
|
76
|
-
alignItems: "center",
|
|
77
|
-
},
|
|
78
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt Input Component
|
|
3
|
-
* @description Text input with sample prompt chips
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { memo } from "react";
|
|
7
|
-
import { View, TextInput, StyleSheet, TouchableOpacity } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
useAppDesignTokens,
|
|
11
|
-
} from "@umituz/react-native-design-system";
|
|
12
|
-
import type { PromptInputProps } from "../../domain/entities";
|
|
13
|
-
import { DEFAULT_SAMPLE_PROMPTS } from "../../infrastructure/constants";
|
|
14
|
-
|
|
15
|
-
export const PromptInput: React.FC<PromptInputProps> = memo(
|
|
16
|
-
function PromptInput({
|
|
17
|
-
value,
|
|
18
|
-
onChangeText,
|
|
19
|
-
isProcessing,
|
|
20
|
-
label,
|
|
21
|
-
placeholder,
|
|
22
|
-
samplePrompts = DEFAULT_SAMPLE_PROMPTS,
|
|
23
|
-
samplePromptsLabel,
|
|
24
|
-
}) {
|
|
25
|
-
const tokens = useAppDesignTokens();
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<View style={styles.container}>
|
|
29
|
-
{label && (
|
|
30
|
-
<AtomicText
|
|
31
|
-
|
|
32
|
-
style={[
|
|
33
|
-
styles.label,
|
|
34
|
-
{
|
|
35
|
-
color: tokens.colors.textPrimary,
|
|
36
|
-
marginBottom: tokens.spacing.sm,
|
|
37
|
-
},
|
|
38
|
-
]}
|
|
39
|
-
>
|
|
40
|
-
{label}
|
|
41
|
-
</AtomicText>
|
|
42
|
-
)}
|
|
43
|
-
|
|
44
|
-
<TextInput
|
|
45
|
-
value={value}
|
|
46
|
-
onChangeText={onChangeText}
|
|
47
|
-
placeholder={placeholder}
|
|
48
|
-
placeholderTextColor={tokens.colors.textTertiary}
|
|
49
|
-
multiline
|
|
50
|
-
|
|
51
|
-
editable={!isProcessing}
|
|
52
|
-
style={[
|
|
53
|
-
styles.input,
|
|
54
|
-
{
|
|
55
|
-
backgroundColor: tokens.colors.surface,
|
|
56
|
-
borderColor: tokens.colors.border,
|
|
57
|
-
color: tokens.colors.textPrimary,
|
|
58
|
-
},
|
|
59
|
-
]}
|
|
60
|
-
/>
|
|
61
|
-
|
|
62
|
-
{samplePrompts.length > 0 && (
|
|
63
|
-
<>
|
|
64
|
-
{samplePromptsLabel && (
|
|
65
|
-
<AtomicText
|
|
66
|
-
|
|
67
|
-
style={[
|
|
68
|
-
styles.sampleLabel,
|
|
69
|
-
{
|
|
70
|
-
color: tokens.colors.textSecondary,
|
|
71
|
-
marginTop: tokens.spacing.md,
|
|
72
|
-
marginBottom: tokens.spacing.sm,
|
|
73
|
-
},
|
|
74
|
-
]}
|
|
75
|
-
>
|
|
76
|
-
{samplePromptsLabel}
|
|
77
|
-
</AtomicText>
|
|
78
|
-
)}
|
|
79
|
-
|
|
80
|
-
<View style={styles.sampleContainer}>
|
|
81
|
-
{samplePrompts.map((prompt) => (
|
|
82
|
-
<TouchableOpacity
|
|
83
|
-
key={prompt.id}
|
|
84
|
-
style={[
|
|
85
|
-
styles.sampleChip,
|
|
86
|
-
{
|
|
87
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
88
|
-
borderColor: tokens.colors.border,
|
|
89
|
-
},
|
|
90
|
-
]}
|
|
91
|
-
onPress={() => onChangeText(prompt.text)}
|
|
92
|
-
disabled={isProcessing}
|
|
93
|
-
>
|
|
94
|
-
<AtomicText
|
|
95
|
-
|
|
96
|
-
style={{ color: tokens.colors.textSecondary }}
|
|
97
|
-
|
|
98
|
-
>
|
|
99
|
-
{prompt.text}
|
|
100
|
-
</AtomicText>
|
|
101
|
-
</TouchableOpacity>
|
|
102
|
-
))}
|
|
103
|
-
</View>
|
|
104
|
-
</>
|
|
105
|
-
)}
|
|
106
|
-
</View>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const styles = StyleSheet.create({
|
|
112
|
-
container: {
|
|
113
|
-
marginVertical: 16,
|
|
114
|
-
},
|
|
115
|
-
label: {
|
|
116
|
-
fontWeight: "600",
|
|
117
|
-
},
|
|
118
|
-
input: {
|
|
119
|
-
minHeight: 120,
|
|
120
|
-
borderWidth: 1,
|
|
121
|
-
borderRadius: 16,
|
|
122
|
-
paddingHorizontal: 16,
|
|
123
|
-
paddingVertical: 14,
|
|
124
|
-
fontSize: 16,
|
|
125
|
-
textAlignVertical: "top",
|
|
126
|
-
lineHeight: 24,
|
|
127
|
-
},
|
|
128
|
-
sampleLabel: {
|
|
129
|
-
fontWeight: "500",
|
|
130
|
-
},
|
|
131
|
-
sampleContainer: {
|
|
132
|
-
flexDirection: "row",
|
|
133
|
-
flexWrap: "wrap",
|
|
134
|
-
gap: 8,
|
|
135
|
-
},
|
|
136
|
-
sampleChip: {
|
|
137
|
-
paddingHorizontal: 12,
|
|
138
|
-
paddingVertical: 8,
|
|
139
|
-
borderRadius: 20,
|
|
140
|
-
borderWidth: 1,
|
|
141
|
-
},
|
|
142
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Result Display Component
|
|
3
|
-
* @description Displays processed image with save/reset actions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { memo } from "react";
|
|
7
|
-
import { View, StyleSheet, Image, TouchableOpacity } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
AtomicIcon,
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
} from "@umituz/react-native-design-system";
|
|
13
|
-
import type { ResultDisplayProps } from "../../domain/entities";
|
|
14
|
-
|
|
15
|
-
export const ResultDisplay: React.FC<ResultDisplayProps> = memo(
|
|
16
|
-
function ResultDisplay({
|
|
17
|
-
imageUrl,
|
|
18
|
-
isProcessing,
|
|
19
|
-
onSave,
|
|
20
|
-
onReset,
|
|
21
|
-
saveButtonText,
|
|
22
|
-
resetButtonText,
|
|
23
|
-
}) {
|
|
24
|
-
const tokens = useAppDesignTokens();
|
|
25
|
-
|
|
26
|
-
if (!imageUrl || isProcessing) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<View style={styles.container}>
|
|
32
|
-
<View style={[styles.resultContainer, { borderColor: tokens.colors.borderLight }]}>
|
|
33
|
-
<Image
|
|
34
|
-
source={{ uri: imageUrl }}
|
|
35
|
-
style={styles.resultImage}
|
|
36
|
-
resizeMode="cover"
|
|
37
|
-
/>
|
|
38
|
-
</View>
|
|
39
|
-
|
|
40
|
-
<View style={styles.actionsContainer}>
|
|
41
|
-
<TouchableOpacity
|
|
42
|
-
style={[
|
|
43
|
-
styles.actionButton,
|
|
44
|
-
{ backgroundColor: tokens.colors.backgroundSecondary },
|
|
45
|
-
]}
|
|
46
|
-
onPress={onReset}
|
|
47
|
-
>
|
|
48
|
-
<AtomicIcon
|
|
49
|
-
name="refresh-cw"
|
|
50
|
-
size="md"
|
|
51
|
-
|
|
52
|
-
/>
|
|
53
|
-
<AtomicText
|
|
54
|
-
|
|
55
|
-
style={[styles.actionText, { color: tokens.colors.textPrimary }]}
|
|
56
|
-
>
|
|
57
|
-
{resetButtonText}
|
|
58
|
-
</AtomicText>
|
|
59
|
-
</TouchableOpacity>
|
|
60
|
-
|
|
61
|
-
<TouchableOpacity
|
|
62
|
-
style={[
|
|
63
|
-
styles.actionButton,
|
|
64
|
-
{ backgroundColor: tokens.colors.success },
|
|
65
|
-
]}
|
|
66
|
-
onPress={onSave}
|
|
67
|
-
>
|
|
68
|
-
<AtomicIcon
|
|
69
|
-
name="download"
|
|
70
|
-
size="md"
|
|
71
|
-
|
|
72
|
-
/>
|
|
73
|
-
<AtomicText
|
|
74
|
-
|
|
75
|
-
style={[
|
|
76
|
-
styles.actionText,
|
|
77
|
-
{ color: tokens.colors.backgroundPrimary },
|
|
78
|
-
]}
|
|
79
|
-
>
|
|
80
|
-
{saveButtonText}
|
|
81
|
-
</AtomicText>
|
|
82
|
-
</TouchableOpacity>
|
|
83
|
-
</View>
|
|
84
|
-
</View>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const styles = StyleSheet.create({
|
|
90
|
-
container: {
|
|
91
|
-
marginTop: 24,
|
|
92
|
-
marginBottom: 16,
|
|
93
|
-
},
|
|
94
|
-
resultContainer: {
|
|
95
|
-
width: "100%",
|
|
96
|
-
aspectRatio: 1,
|
|
97
|
-
borderRadius: 24,
|
|
98
|
-
overflow: "hidden",
|
|
99
|
-
marginBottom: 16,
|
|
100
|
-
borderWidth: 1,
|
|
101
|
-
},
|
|
102
|
-
resultImage: {
|
|
103
|
-
width: "100%",
|
|
104
|
-
height: "100%",
|
|
105
|
-
},
|
|
106
|
-
actionsContainer: {
|
|
107
|
-
flexDirection: "row",
|
|
108
|
-
gap: 12,
|
|
109
|
-
},
|
|
110
|
-
actionButton: {
|
|
111
|
-
flex: 1,
|
|
112
|
-
flexDirection: "row",
|
|
113
|
-
alignItems: "center",
|
|
114
|
-
justifyContent: "center",
|
|
115
|
-
paddingVertical: 14,
|
|
116
|
-
borderRadius: 16,
|
|
117
|
-
gap: 8,
|
|
118
|
-
},
|
|
119
|
-
actionText: {
|
|
120
|
-
fontWeight: "600",
|
|
121
|
-
},
|
|
122
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useBackgroundFeature Hook
|
|
3
|
-
* @description Main hook for background feature state and actions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useCallback, useState } from "react";
|
|
7
|
-
import type {
|
|
8
|
-
BackgroundFeatureState,
|
|
9
|
-
BackgroundProcessResult,
|
|
10
|
-
UseBackgroundFeatureConfig,
|
|
11
|
-
StudioMode,
|
|
12
|
-
} from "../../domain/entities";
|
|
13
|
-
|
|
14
|
-
export interface UseBackgroundFeatureReturn extends BackgroundFeatureState {
|
|
15
|
-
readonly selectImage: () => Promise<void>;
|
|
16
|
-
readonly process: (prompt?: string) => Promise<void>;
|
|
17
|
-
readonly save: () => Promise<void>;
|
|
18
|
-
readonly reset: () => void;
|
|
19
|
-
readonly setPrompt: (prompt: string) => void;
|
|
20
|
-
readonly setMode: (mode: StudioMode) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function useBackgroundFeature(
|
|
24
|
-
config: UseBackgroundFeatureConfig
|
|
25
|
-
): UseBackgroundFeatureReturn {
|
|
26
|
-
const [imageUri, setImageUri] = useState<string | null>(null);
|
|
27
|
-
const [prompt, setPrompt] = useState<string>("");
|
|
28
|
-
const [processedUrl, setProcessedUrl] = useState<string | null>(null);
|
|
29
|
-
const [isProcessing, setIsProcessing] = useState(false);
|
|
30
|
-
const [progress, setProgress] = useState(0);
|
|
31
|
-
const [error, setError] = useState<string | null>(null);
|
|
32
|
-
const [mode, setMode] = useState<StudioMode>(
|
|
33
|
-
config.defaultMode ?? "transparent"
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const selectImage = useCallback(async (): Promise<void> => {
|
|
37
|
-
if (!config.onSelectImage) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const uri = await config.onSelectImage();
|
|
43
|
-
if (uri) {
|
|
44
|
-
setImageUri(uri);
|
|
45
|
-
setError(null);
|
|
46
|
-
setProcessedUrl(null);
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
setError(err instanceof Error ? err.message : "Failed to select image");
|
|
50
|
-
}
|
|
51
|
-
}, [config]);
|
|
52
|
-
|
|
53
|
-
const process = useCallback(
|
|
54
|
-
async (newPrompt?: string): Promise<void> => {
|
|
55
|
-
if (!imageUri) {
|
|
56
|
-
setError("Please select an image first");
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const currentPrompt = newPrompt ?? prompt;
|
|
61
|
-
setIsProcessing(true);
|
|
62
|
-
setProgress(0);
|
|
63
|
-
setError(null);
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const result: BackgroundProcessResult = await config.processRequest({
|
|
67
|
-
imageUri,
|
|
68
|
-
prompt: currentPrompt,
|
|
69
|
-
mode,
|
|
70
|
-
onProgress: setProgress,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
if (result.success && result.imageUrl) {
|
|
74
|
-
setProcessedUrl(result.imageUrl);
|
|
75
|
-
} else {
|
|
76
|
-
setError(result.error || "Processing failed");
|
|
77
|
-
}
|
|
78
|
-
} catch (err) {
|
|
79
|
-
setError(err instanceof Error ? err.message : "Processing failed");
|
|
80
|
-
} finally {
|
|
81
|
-
setIsProcessing(false);
|
|
82
|
-
setProgress(0);
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
[imageUri, prompt, mode, config]
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const save = useCallback((): Promise<void> => {
|
|
89
|
-
if (!processedUrl) {
|
|
90
|
-
return Promise.resolve();
|
|
91
|
-
}
|
|
92
|
-
return Promise.resolve();
|
|
93
|
-
}, [processedUrl]);
|
|
94
|
-
|
|
95
|
-
const reset = useCallback((): void => {
|
|
96
|
-
setImageUri(null);
|
|
97
|
-
setPrompt("");
|
|
98
|
-
setProcessedUrl(null);
|
|
99
|
-
setIsProcessing(false);
|
|
100
|
-
setProgress(0);
|
|
101
|
-
setError(null);
|
|
102
|
-
}, []);
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
imageUri,
|
|
106
|
-
prompt,
|
|
107
|
-
processedUrl,
|
|
108
|
-
isProcessing,
|
|
109
|
-
progress,
|
|
110
|
-
error,
|
|
111
|
-
mode,
|
|
112
|
-
selectImage,
|
|
113
|
-
process,
|
|
114
|
-
save,
|
|
115
|
-
reset,
|
|
116
|
-
setPrompt,
|
|
117
|
-
setMode,
|
|
118
|
-
};
|
|
119
|
-
}
|