@umituz/react-native-ai-generation-content 1.15.1 → 1.15.3
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/upscaling/domain/index.ts +1 -0
- package/src/features/upscaling/domain/types/index.ts +2 -0
- package/src/features/upscaling/domain/types/provider.types.ts +23 -0
- package/src/features/upscaling/domain/types/upscale.types.ts +56 -0
- package/src/features/upscaling/index.ts +40 -1
- package/src/features/upscaling/infrastructure/index.ts +1 -0
- package/src/features/upscaling/infrastructure/services/index.ts +7 -0
- package/src/features/upscaling/infrastructure/services/upscale-executor.ts +64 -0
- package/src/features/upscaling/infrastructure/services/upscale-provider-registry.ts +77 -0
- package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +168 -0
- package/src/features/upscaling/presentation/components/UpscaleResultView.tsx +94 -0
- package/src/features/upscaling/presentation/components/index.ts +4 -0
- package/src/features/upscaling/presentation/hooks/index.ts +5 -0
- package/src/features/upscaling/presentation/hooks/useUpscaleFeature.ts +133 -0
- package/src/features/upscaling/presentation/index.ts +2 -0
- package/src/index.ts +6 -1
- package/src/features/upscaling/domain/entities.ts +0 -42
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upscale Provider Types
|
|
3
|
+
* Interface for provider implementations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { UpscaleRequest, UpscaleResult } from "./upscale.types";
|
|
7
|
+
|
|
8
|
+
export interface IUpscaleProvider {
|
|
9
|
+
readonly providerId: string;
|
|
10
|
+
readonly providerName: string;
|
|
11
|
+
|
|
12
|
+
isAvailable(): boolean;
|
|
13
|
+
|
|
14
|
+
upscale(
|
|
15
|
+
request: UpscaleRequest,
|
|
16
|
+
onProgress?: (progress: number) => void,
|
|
17
|
+
): Promise<UpscaleResult>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UpscaleProviderEntry {
|
|
21
|
+
provider: IUpscaleProvider;
|
|
22
|
+
priority: number;
|
|
23
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upscale Feature Types
|
|
3
|
+
* Request, Result, Config types for upscaling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type UpscaleScaleFactor = 2 | 4 | 8;
|
|
7
|
+
|
|
8
|
+
export interface UpscaleOptions {
|
|
9
|
+
scaleFactor?: UpscaleScaleFactor;
|
|
10
|
+
enhanceFaces?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UpscaleRequest {
|
|
14
|
+
imageUri: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
options?: UpscaleOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UpscaleResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
imageUrl?: string;
|
|
22
|
+
imageBase64?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
requestId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UpscaleFeatureState {
|
|
28
|
+
imageUri: string | null;
|
|
29
|
+
processedUrl: string | null;
|
|
30
|
+
isProcessing: boolean;
|
|
31
|
+
progress: number;
|
|
32
|
+
error: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UpscaleTranslations {
|
|
36
|
+
uploadTitle: string;
|
|
37
|
+
uploadSubtitle: string;
|
|
38
|
+
uploadChange: string;
|
|
39
|
+
uploadAnalyzing: string;
|
|
40
|
+
description: string;
|
|
41
|
+
processingText: string;
|
|
42
|
+
processButtonText: string;
|
|
43
|
+
successText: string;
|
|
44
|
+
saveButtonText: string;
|
|
45
|
+
tryAnotherText: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface UpscaleFeatureConfig {
|
|
49
|
+
providerId?: string;
|
|
50
|
+
defaultScaleFactor?: UpscaleScaleFactor;
|
|
51
|
+
creditCost?: number;
|
|
52
|
+
onImageSelect?: (uri: string) => void;
|
|
53
|
+
onProcessingStart?: () => void;
|
|
54
|
+
onProcessingComplete?: (result: UpscaleResult) => void;
|
|
55
|
+
onError?: (error: string) => void;
|
|
56
|
+
}
|
|
@@ -1 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Upscaling Feature
|
|
3
|
+
* Provider-agnostic image upscaling feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Domain Types
|
|
7
|
+
export type {
|
|
8
|
+
UpscaleScaleFactor,
|
|
9
|
+
UpscaleOptions,
|
|
10
|
+
UpscaleRequest,
|
|
11
|
+
UpscaleResult,
|
|
12
|
+
UpscaleFeatureState,
|
|
13
|
+
UpscaleTranslations,
|
|
14
|
+
UpscaleFeatureConfig,
|
|
15
|
+
IUpscaleProvider,
|
|
16
|
+
UpscaleProviderEntry,
|
|
17
|
+
} from "./domain";
|
|
18
|
+
|
|
19
|
+
// Infrastructure Services
|
|
20
|
+
export {
|
|
21
|
+
upscaleProviderRegistry,
|
|
22
|
+
executeUpscale,
|
|
23
|
+
getAvailableProvider,
|
|
24
|
+
hasUpscaleProvider,
|
|
25
|
+
} from "./infrastructure";
|
|
26
|
+
export type { ExecuteUpscaleOptions } from "./infrastructure";
|
|
27
|
+
|
|
28
|
+
// Presentation Hooks
|
|
29
|
+
export { useUpscaleFeature } from "./presentation";
|
|
30
|
+
export type {
|
|
31
|
+
UseUpscaleFeatureProps,
|
|
32
|
+
UseUpscaleFeatureReturn,
|
|
33
|
+
} from "./presentation";
|
|
34
|
+
|
|
35
|
+
// Presentation Components
|
|
36
|
+
export { UpscaleFeature, UpscaleResultView } from "./presentation";
|
|
37
|
+
export type {
|
|
38
|
+
UpscaleFeatureProps,
|
|
39
|
+
UpscaleResultViewProps,
|
|
40
|
+
} from "./presentation";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./services";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upscale Executor
|
|
3
|
+
* Executes upscale operations using registered providers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { upscaleProviderRegistry } from "./upscale-provider-registry";
|
|
7
|
+
import type { UpscaleRequest, UpscaleResult } from "../../domain/types";
|
|
8
|
+
|
|
9
|
+
declare const __DEV__: boolean;
|
|
10
|
+
|
|
11
|
+
export interface ExecuteUpscaleOptions {
|
|
12
|
+
providerId?: string;
|
|
13
|
+
onProgress?: (progress: number) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function executeUpscale(
|
|
17
|
+
request: UpscaleRequest,
|
|
18
|
+
options?: ExecuteUpscaleOptions,
|
|
19
|
+
): Promise<UpscaleResult> {
|
|
20
|
+
const provider = upscaleProviderRegistry.get(options?.providerId);
|
|
21
|
+
|
|
22
|
+
if (!provider) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: "No upscale provider available",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!provider.isAvailable()) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: `Provider ${provider.providerId} is not available`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log(`[UpscaleExecutor] Using provider: ${provider.providerId}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
return await provider.upscale(request, options?.onProgress);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
|
|
46
|
+
if (__DEV__) {
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.error("[UpscaleExecutor] Error:", message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: message,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getAvailableProvider(providerId?: string) {
|
|
59
|
+
return upscaleProviderRegistry.get(providerId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function hasUpscaleProvider(providerId: string): boolean {
|
|
63
|
+
return upscaleProviderRegistry.hasProvider(providerId);
|
|
64
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upscale Provider Registry
|
|
3
|
+
* Manages registered upscale providers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
IUpscaleProvider,
|
|
8
|
+
UpscaleProviderEntry,
|
|
9
|
+
} from "../../domain/types";
|
|
10
|
+
|
|
11
|
+
declare const __DEV__: boolean;
|
|
12
|
+
|
|
13
|
+
class UpscaleProviderRegistry {
|
|
14
|
+
private providers: Map<string, UpscaleProviderEntry> = new Map();
|
|
15
|
+
private defaultProviderId: string | null = null;
|
|
16
|
+
|
|
17
|
+
register(provider: IUpscaleProvider, priority = 0): void {
|
|
18
|
+
this.providers.set(provider.providerId, { provider, priority });
|
|
19
|
+
|
|
20
|
+
if (__DEV__) {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log(
|
|
23
|
+
`[UpscaleRegistry] Registered provider: ${provider.providerId}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
unregister(providerId: string): void {
|
|
29
|
+
this.providers.delete(providerId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setDefault(providerId: string): void {
|
|
33
|
+
if (!this.providers.has(providerId)) {
|
|
34
|
+
throw new Error(`Provider ${providerId} not registered`);
|
|
35
|
+
}
|
|
36
|
+
this.defaultProviderId = providerId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(providerId?: string): IUpscaleProvider | null {
|
|
40
|
+
const id = providerId || this.defaultProviderId;
|
|
41
|
+
if (!id) return this.getHighestPriority();
|
|
42
|
+
|
|
43
|
+
const entry = this.providers.get(id);
|
|
44
|
+
return entry?.provider || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getAvailable(): IUpscaleProvider | null {
|
|
48
|
+
const sorted = this.getSortedProviders();
|
|
49
|
+
return sorted.find((p) => p.isAvailable()) || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
hasProvider(providerId: string): boolean {
|
|
53
|
+
return this.providers.has(providerId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getProviderIds(): string[] {
|
|
57
|
+
return Array.from(this.providers.keys());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private getHighestPriority(): IUpscaleProvider | null {
|
|
61
|
+
const sorted = this.getSortedProviders();
|
|
62
|
+
return sorted[0] || null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private getSortedProviders(): IUpscaleProvider[] {
|
|
66
|
+
return Array.from(this.providers.values())
|
|
67
|
+
.sort((a, b) => b.priority - a.priority)
|
|
68
|
+
.map((entry) => entry.provider);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
reset(): void {
|
|
72
|
+
this.providers.clear();
|
|
73
|
+
this.defaultProviderId = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const upscaleProviderRegistry = new UpscaleProviderRegistry();
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpscaleFeature Component
|
|
3
|
+
* Main upscale feature UI component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useCallback, useMemo } from "react";
|
|
7
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
useAppDesignTokens,
|
|
10
|
+
PhotoUploadCard,
|
|
11
|
+
AtomicText,
|
|
12
|
+
AtomicButton,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
import { UpscaleResultView } from "./UpscaleResultView";
|
|
15
|
+
import type {
|
|
16
|
+
UpscaleTranslations,
|
|
17
|
+
UpscaleFeatureConfig,
|
|
18
|
+
} from "../../domain/types";
|
|
19
|
+
|
|
20
|
+
export interface UpscaleFeatureProps {
|
|
21
|
+
imageUri: string | null;
|
|
22
|
+
processedUrl: string | null;
|
|
23
|
+
isProcessing: boolean;
|
|
24
|
+
progress: number;
|
|
25
|
+
error: string | null;
|
|
26
|
+
translations: UpscaleTranslations;
|
|
27
|
+
config?: UpscaleFeatureConfig;
|
|
28
|
+
onSelectImage: () => void;
|
|
29
|
+
onProcess: () => void;
|
|
30
|
+
onSave: () => void;
|
|
31
|
+
onReset: () => void;
|
|
32
|
+
renderProcessingModal?: (props: {
|
|
33
|
+
visible: boolean;
|
|
34
|
+
progress: number;
|
|
35
|
+
}) => React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
39
|
+
imageUri,
|
|
40
|
+
processedUrl,
|
|
41
|
+
isProcessing,
|
|
42
|
+
progress,
|
|
43
|
+
error,
|
|
44
|
+
translations,
|
|
45
|
+
onSelectImage,
|
|
46
|
+
onProcess,
|
|
47
|
+
onSave,
|
|
48
|
+
onReset,
|
|
49
|
+
renderProcessingModal,
|
|
50
|
+
}) => {
|
|
51
|
+
const tokens = useAppDesignTokens();
|
|
52
|
+
|
|
53
|
+
const photoTranslations = useMemo(
|
|
54
|
+
() => ({
|
|
55
|
+
tapToUpload: translations.uploadTitle,
|
|
56
|
+
selectPhoto: translations.uploadSubtitle,
|
|
57
|
+
change: translations.uploadChange,
|
|
58
|
+
analyzing: translations.uploadAnalyzing,
|
|
59
|
+
}),
|
|
60
|
+
[translations],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const handleProcess = useCallback(() => {
|
|
64
|
+
onProcess();
|
|
65
|
+
}, [onProcess]);
|
|
66
|
+
|
|
67
|
+
if (processedUrl) {
|
|
68
|
+
return (
|
|
69
|
+
<ScrollView
|
|
70
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
71
|
+
contentContainerStyle={styles.content}
|
|
72
|
+
showsVerticalScrollIndicator={false}
|
|
73
|
+
>
|
|
74
|
+
<UpscaleResultView
|
|
75
|
+
imageUrl={processedUrl}
|
|
76
|
+
translations={translations}
|
|
77
|
+
onSave={onSave}
|
|
78
|
+
onReset={onReset}
|
|
79
|
+
/>
|
|
80
|
+
</ScrollView>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<ScrollView
|
|
87
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
88
|
+
contentContainerStyle={styles.content}
|
|
89
|
+
showsVerticalScrollIndicator={false}
|
|
90
|
+
>
|
|
91
|
+
<AtomicText
|
|
92
|
+
type="bodyLarge"
|
|
93
|
+
style={[styles.description, { color: tokens.colors.textSecondary }]}
|
|
94
|
+
>
|
|
95
|
+
{translations.description}
|
|
96
|
+
</AtomicText>
|
|
97
|
+
|
|
98
|
+
<PhotoUploadCard
|
|
99
|
+
imageUri={imageUri}
|
|
100
|
+
onPress={onSelectImage}
|
|
101
|
+
isValidating={isProcessing}
|
|
102
|
+
disabled={isProcessing}
|
|
103
|
+
translations={photoTranslations}
|
|
104
|
+
config={{
|
|
105
|
+
aspectRatio: 1,
|
|
106
|
+
borderRadius: 24,
|
|
107
|
+
showValidationStatus: false,
|
|
108
|
+
allowChange: true,
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
{error && (
|
|
113
|
+
<View
|
|
114
|
+
style={[
|
|
115
|
+
styles.errorContainer,
|
|
116
|
+
{ backgroundColor: `${tokens.colors.error}15` },
|
|
117
|
+
]}
|
|
118
|
+
>
|
|
119
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.error }}>
|
|
120
|
+
{error}
|
|
121
|
+
</AtomicText>
|
|
122
|
+
</View>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
<View style={styles.buttonContainer}>
|
|
126
|
+
<AtomicButton
|
|
127
|
+
title={
|
|
128
|
+
isProcessing
|
|
129
|
+
? translations.processingText
|
|
130
|
+
: translations.processButtonText
|
|
131
|
+
}
|
|
132
|
+
onPress={handleProcess}
|
|
133
|
+
disabled={!imageUri || isProcessing}
|
|
134
|
+
variant="primary"
|
|
135
|
+
size="lg"
|
|
136
|
+
/>
|
|
137
|
+
</View>
|
|
138
|
+
</ScrollView>
|
|
139
|
+
|
|
140
|
+
{renderProcessingModal?.({ visible: isProcessing, progress })}
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const styles = StyleSheet.create({
|
|
146
|
+
container: {
|
|
147
|
+
flex: 1,
|
|
148
|
+
},
|
|
149
|
+
content: {
|
|
150
|
+
paddingVertical: 16,
|
|
151
|
+
},
|
|
152
|
+
description: {
|
|
153
|
+
textAlign: "center",
|
|
154
|
+
marginHorizontal: 24,
|
|
155
|
+
marginBottom: 24,
|
|
156
|
+
lineHeight: 24,
|
|
157
|
+
},
|
|
158
|
+
errorContainer: {
|
|
159
|
+
marginHorizontal: 24,
|
|
160
|
+
marginBottom: 16,
|
|
161
|
+
padding: 16,
|
|
162
|
+
borderRadius: 12,
|
|
163
|
+
},
|
|
164
|
+
buttonContainer: {
|
|
165
|
+
marginHorizontal: 24,
|
|
166
|
+
marginTop: 8,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpscaleResultView Component
|
|
3
|
+
* Displays the upscaled image result
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Image, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicButton,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import type { UpscaleTranslations } from "../../domain/types";
|
|
14
|
+
|
|
15
|
+
export interface UpscaleResultViewProps {
|
|
16
|
+
imageUrl: string;
|
|
17
|
+
translations: Pick<
|
|
18
|
+
UpscaleTranslations,
|
|
19
|
+
"successText" | "saveButtonText" | "tryAnotherText"
|
|
20
|
+
>;
|
|
21
|
+
onSave: () => void;
|
|
22
|
+
onReset: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const UpscaleResultView: React.FC<UpscaleResultViewProps> = ({
|
|
26
|
+
imageUrl,
|
|
27
|
+
translations,
|
|
28
|
+
onSave,
|
|
29
|
+
onReset,
|
|
30
|
+
}) => {
|
|
31
|
+
const tokens = useAppDesignTokens();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={styles.container}>
|
|
35
|
+
<AtomicText
|
|
36
|
+
type="headlineMedium"
|
|
37
|
+
style={[styles.title, { color: tokens.colors.success }]}
|
|
38
|
+
>
|
|
39
|
+
{translations.successText}
|
|
40
|
+
</AtomicText>
|
|
41
|
+
|
|
42
|
+
<View
|
|
43
|
+
style={[
|
|
44
|
+
styles.imageCard,
|
|
45
|
+
{ backgroundColor: tokens.colors.surfaceSecondary },
|
|
46
|
+
]}
|
|
47
|
+
>
|
|
48
|
+
<Image
|
|
49
|
+
source={{ uri: imageUrl }}
|
|
50
|
+
style={styles.image}
|
|
51
|
+
resizeMode="contain"
|
|
52
|
+
/>
|
|
53
|
+
</View>
|
|
54
|
+
|
|
55
|
+
<View style={styles.actions}>
|
|
56
|
+
<AtomicButton
|
|
57
|
+
title={translations.saveButtonText}
|
|
58
|
+
onPress={onSave}
|
|
59
|
+
variant="primary"
|
|
60
|
+
size="lg"
|
|
61
|
+
/>
|
|
62
|
+
<AtomicButton
|
|
63
|
+
title={translations.tryAnotherText}
|
|
64
|
+
onPress={onReset}
|
|
65
|
+
variant="secondary"
|
|
66
|
+
size="lg"
|
|
67
|
+
/>
|
|
68
|
+
</View>
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const styles = StyleSheet.create({
|
|
74
|
+
container: {
|
|
75
|
+
flex: 1,
|
|
76
|
+
paddingHorizontal: 24,
|
|
77
|
+
},
|
|
78
|
+
title: {
|
|
79
|
+
textAlign: "center",
|
|
80
|
+
marginBottom: 24,
|
|
81
|
+
},
|
|
82
|
+
imageCard: {
|
|
83
|
+
borderRadius: 24,
|
|
84
|
+
overflow: "hidden",
|
|
85
|
+
marginBottom: 24,
|
|
86
|
+
},
|
|
87
|
+
image: {
|
|
88
|
+
width: "100%",
|
|
89
|
+
aspectRatio: 1,
|
|
90
|
+
},
|
|
91
|
+
actions: {
|
|
92
|
+
gap: 12,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useUpscaleFeature Hook
|
|
3
|
+
* Manages upscale feature state and actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import { executeUpscale } from "../../infrastructure/services";
|
|
8
|
+
import type {
|
|
9
|
+
UpscaleFeatureState,
|
|
10
|
+
UpscaleFeatureConfig,
|
|
11
|
+
UpscaleResult,
|
|
12
|
+
} from "../../domain/types";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
export interface UseUpscaleFeatureProps {
|
|
17
|
+
config?: UpscaleFeatureConfig;
|
|
18
|
+
userId: string;
|
|
19
|
+
onSelectImage: () => Promise<string | null>;
|
|
20
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseUpscaleFeatureReturn extends UpscaleFeatureState {
|
|
24
|
+
selectImage: () => Promise<void>;
|
|
25
|
+
process: () => Promise<void>;
|
|
26
|
+
save: () => Promise<void>;
|
|
27
|
+
reset: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const initialState: UpscaleFeatureState = {
|
|
31
|
+
imageUri: null,
|
|
32
|
+
processedUrl: null,
|
|
33
|
+
isProcessing: false,
|
|
34
|
+
progress: 0,
|
|
35
|
+
error: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function useUpscaleFeature(
|
|
39
|
+
props: UseUpscaleFeatureProps,
|
|
40
|
+
): UseUpscaleFeatureReturn {
|
|
41
|
+
const { config, userId, onSelectImage, onSaveImage } = props;
|
|
42
|
+
const [state, setState] = useState<UpscaleFeatureState>(initialState);
|
|
43
|
+
|
|
44
|
+
const selectImage = useCallback(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const uri = await onSelectImage();
|
|
47
|
+
if (uri) {
|
|
48
|
+
setState((prev) => ({ ...prev, imageUri: uri, error: null }));
|
|
49
|
+
config?.onImageSelect?.(uri);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
54
|
+
}
|
|
55
|
+
}, [onSelectImage, config]);
|
|
56
|
+
|
|
57
|
+
const handleProgress = useCallback((progress: number) => {
|
|
58
|
+
setState((prev) => ({ ...prev, progress }));
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const process = useCallback(async () => {
|
|
62
|
+
if (!state.imageUri) return;
|
|
63
|
+
|
|
64
|
+
setState((prev) => ({
|
|
65
|
+
...prev,
|
|
66
|
+
isProcessing: true,
|
|
67
|
+
progress: 0,
|
|
68
|
+
error: null,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
config?.onProcessingStart?.();
|
|
72
|
+
|
|
73
|
+
if (__DEV__) {
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log("[useUpscaleFeature] Starting upscale process");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result: UpscaleResult = await executeUpscale(
|
|
79
|
+
{
|
|
80
|
+
imageUri: state.imageUri,
|
|
81
|
+
userId,
|
|
82
|
+
options: { scaleFactor: config?.defaultScaleFactor || 2 },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
providerId: config?.providerId,
|
|
86
|
+
onProgress: handleProgress,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (result.success && result.imageUrl) {
|
|
91
|
+
const url = result.imageUrl;
|
|
92
|
+
setState((prev) => ({
|
|
93
|
+
...prev,
|
|
94
|
+
isProcessing: false,
|
|
95
|
+
processedUrl: url,
|
|
96
|
+
progress: 100,
|
|
97
|
+
}));
|
|
98
|
+
config?.onProcessingComplete?.(result);
|
|
99
|
+
} else {
|
|
100
|
+
const errorMessage = result.error || "Processing failed";
|
|
101
|
+
setState((prev) => ({
|
|
102
|
+
...prev,
|
|
103
|
+
isProcessing: false,
|
|
104
|
+
error: errorMessage,
|
|
105
|
+
progress: 0,
|
|
106
|
+
}));
|
|
107
|
+
config?.onError?.(errorMessage);
|
|
108
|
+
}
|
|
109
|
+
}, [state.imageUri, userId, config, handleProgress]);
|
|
110
|
+
|
|
111
|
+
const save = useCallback(async () => {
|
|
112
|
+
if (!state.processedUrl) return;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await onSaveImage(state.processedUrl);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
119
|
+
}
|
|
120
|
+
}, [state.processedUrl, onSaveImage]);
|
|
121
|
+
|
|
122
|
+
const reset = useCallback(() => {
|
|
123
|
+
setState(initialState);
|
|
124
|
+
}, []);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...state,
|
|
128
|
+
selectImage,
|
|
129
|
+
process,
|
|
130
|
+
save,
|
|
131
|
+
reset,
|
|
132
|
+
};
|
|
133
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -297,9 +297,14 @@ export * from "./domains/creations";
|
|
|
297
297
|
export * from "./domains/face-detection";
|
|
298
298
|
|
|
299
299
|
// =============================================================================
|
|
300
|
-
//
|
|
300
|
+
// FEATURES - Background
|
|
301
301
|
// =============================================================================
|
|
302
302
|
|
|
303
303
|
export * from "./features/background";
|
|
304
304
|
|
|
305
|
+
// =============================================================================
|
|
306
|
+
// FEATURES - Upscaling
|
|
307
|
+
// =============================================================================
|
|
308
|
+
|
|
309
|
+
export * from "./features/upscaling";
|
|
305
310
|
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Upscaling Domain Entities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface UpscaleConfig {
|
|
6
|
-
/**
|
|
7
|
-
* The factor to upscale the image by
|
|
8
|
-
* @default 2
|
|
9
|
-
*/
|
|
10
|
-
scaleFactor?: 2 | 4;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Whether to enhance faces during upscaling
|
|
14
|
-
* @default false
|
|
15
|
-
*/
|
|
16
|
-
enhanceFaces?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface UpscaleRequest {
|
|
20
|
-
/**
|
|
21
|
-
* The image to upscale.
|
|
22
|
-
* Can be a Base64 string or a remote URL.
|
|
23
|
-
*/
|
|
24
|
-
image: string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Optional configuration
|
|
28
|
-
*/
|
|
29
|
-
options?: UpscaleConfig;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface UpscaleResult {
|
|
33
|
-
/**
|
|
34
|
-
* The upscaled image URL or Base64
|
|
35
|
-
*/
|
|
36
|
-
imageUrl: string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Metadata about the generation
|
|
40
|
-
*/
|
|
41
|
-
metadata?: Record<string, unknown>;
|
|
42
|
-
}
|