@umituz/react-native-ai-generation-content 1.17.14 → 1.17.16
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 +5 -1
- package/src/domains/creations/presentation/components/CreationCard.tsx +2 -1
- package/src/domains/creations/presentation/components/CreationDetail/DetailVideo.tsx +123 -0
- package/src/domains/creations/presentation/components/CreationDetail/index.ts +1 -0
- package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +24 -4
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +59 -56
- package/src/features/text-to-image/domain/constants/index.ts +14 -0
- package/src/features/text-to-image/domain/constants/options.constants.ts +42 -0
- package/src/features/text-to-image/domain/constants/styles.constants.ts +34 -0
- package/src/features/text-to-image/domain/index.ts +6 -0
- package/src/features/text-to-image/domain/types/config.types.ts +71 -0
- package/src/features/text-to-image/domain/types/form.types.ts +58 -0
- package/src/features/text-to-image/domain/types/index.ts +29 -1
- package/src/features/text-to-image/domain/types/text-to-image.types.ts +0 -8
- package/src/index.ts +5 -1
- package/src/infrastructure/utils/result-validator.util.ts +93 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.16",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"expo-haptics": "^15.0.8",
|
|
85
85
|
"expo-image": "^3.0.11",
|
|
86
86
|
"expo-linear-gradient": "~15.0.7",
|
|
87
|
+
"expo-video": "^2.0.0",
|
|
87
88
|
"expo-localization": "^17.0.8",
|
|
88
89
|
"expo-sharing": "^14.0.8",
|
|
89
90
|
"firebase": "^12.6.0",
|
|
@@ -100,5 +101,8 @@
|
|
|
100
101
|
},
|
|
101
102
|
"publishConfig": {
|
|
102
103
|
"access": "public"
|
|
104
|
+
},
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"@umituz/react-native-ai-generation-content": "^1.17.14"
|
|
103
107
|
}
|
|
104
108
|
}
|
|
@@ -82,7 +82,8 @@ export function CreationCard({
|
|
|
82
82
|
}: CreationCardProps) {
|
|
83
83
|
const tokens = useAppDesignTokens();
|
|
84
84
|
// Support both output object and direct uri
|
|
85
|
-
|
|
85
|
+
// Prefer getPreviewUrl (which returns thumbnailUrl first) over direct uri
|
|
86
|
+
const previewUrl = getPreviewUrl(creation.output) || creation.uri;
|
|
86
87
|
const title = getCreationTitle(creation.prompt, creation.type as CreationTypeId);
|
|
87
88
|
|
|
88
89
|
// Format date
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DetailVideo Component
|
|
3
|
+
* Video player with thumbnail and play controls for creation detail view
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useCallback } from "react";
|
|
7
|
+
import { View, StyleSheet, Dimensions, TouchableOpacity } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
useAppDesignTokens,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
type DesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { Image } from "expo-image";
|
|
14
|
+
import { useVideoPlayer, VideoView } from "expo-video";
|
|
15
|
+
|
|
16
|
+
interface DetailVideoProps {
|
|
17
|
+
readonly videoUrl: string;
|
|
18
|
+
readonly thumbnailUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { width } = Dimensions.get("window");
|
|
22
|
+
|
|
23
|
+
export const DetailVideo: React.FC<DetailVideoProps> = ({
|
|
24
|
+
videoUrl,
|
|
25
|
+
thumbnailUrl,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
const styles = useStyles(tokens);
|
|
29
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
30
|
+
|
|
31
|
+
const player = useVideoPlayer(videoUrl, (p) => {
|
|
32
|
+
p.loop = true;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const handlePlay = useCallback(() => {
|
|
36
|
+
setIsPlaying(true);
|
|
37
|
+
player.play();
|
|
38
|
+
}, [player]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View style={styles.container}>
|
|
42
|
+
<View style={styles.frame}>
|
|
43
|
+
{isPlaying ? (
|
|
44
|
+
<VideoView
|
|
45
|
+
player={player}
|
|
46
|
+
style={styles.video}
|
|
47
|
+
contentFit="cover"
|
|
48
|
+
nativeControls
|
|
49
|
+
/>
|
|
50
|
+
) : (
|
|
51
|
+
<TouchableOpacity
|
|
52
|
+
style={styles.thumbnailContainer}
|
|
53
|
+
onPress={handlePlay}
|
|
54
|
+
activeOpacity={0.8}
|
|
55
|
+
>
|
|
56
|
+
{thumbnailUrl ? (
|
|
57
|
+
<Image
|
|
58
|
+
source={{ uri: thumbnailUrl }}
|
|
59
|
+
style={styles.thumbnail}
|
|
60
|
+
contentFit="cover"
|
|
61
|
+
/>
|
|
62
|
+
) : (
|
|
63
|
+
<View style={styles.placeholder} />
|
|
64
|
+
)}
|
|
65
|
+
<View style={styles.playButtonContainer}>
|
|
66
|
+
<View style={styles.playButton}>
|
|
67
|
+
<AtomicIcon name="play" customSize={32} color="onPrimary" />
|
|
68
|
+
</View>
|
|
69
|
+
</View>
|
|
70
|
+
</TouchableOpacity>
|
|
71
|
+
)}
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const useStyles = (tokens: DesignTokens) =>
|
|
78
|
+
StyleSheet.create({
|
|
79
|
+
container: {
|
|
80
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
81
|
+
marginVertical: tokens.spacing.lg,
|
|
82
|
+
},
|
|
83
|
+
frame: {
|
|
84
|
+
width: width - tokens.spacing.lg * 2,
|
|
85
|
+
height: width - tokens.spacing.lg * 2,
|
|
86
|
+
borderRadius: 24,
|
|
87
|
+
overflow: "hidden",
|
|
88
|
+
backgroundColor: tokens.colors.surface,
|
|
89
|
+
},
|
|
90
|
+
video: {
|
|
91
|
+
width: "100%",
|
|
92
|
+
height: "100%",
|
|
93
|
+
},
|
|
94
|
+
thumbnailContainer: {
|
|
95
|
+
width: "100%",
|
|
96
|
+
height: "100%",
|
|
97
|
+
justifyContent: "center",
|
|
98
|
+
alignItems: "center",
|
|
99
|
+
},
|
|
100
|
+
thumbnail: {
|
|
101
|
+
width: "100%",
|
|
102
|
+
height: "100%",
|
|
103
|
+
},
|
|
104
|
+
placeholder: {
|
|
105
|
+
width: "100%",
|
|
106
|
+
height: "100%",
|
|
107
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
108
|
+
},
|
|
109
|
+
playButtonContainer: {
|
|
110
|
+
...StyleSheet.absoluteFillObject,
|
|
111
|
+
justifyContent: "center",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
},
|
|
114
|
+
playButton: {
|
|
115
|
+
width: 64,
|
|
116
|
+
height: 64,
|
|
117
|
+
borderRadius: 32,
|
|
118
|
+
backgroundColor: tokens.colors.primary,
|
|
119
|
+
justifyContent: "center",
|
|
120
|
+
alignItems: "center",
|
|
121
|
+
paddingLeft: 4,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
3
|
import { useAppDesignTokens, type DesignTokens, ScreenLayout } from "@umituz/react-native-design-system";
|
|
4
4
|
import type { Creation } from '../../domain/entities/Creation';
|
|
5
|
+
import { hasVideoContent, getPreviewUrl } from '../../domain/utils';
|
|
5
6
|
import { DetailHeader } from '../components/CreationDetail/DetailHeader';
|
|
6
7
|
import { DetailImage } from '../components/CreationDetail/DetailImage';
|
|
8
|
+
import { DetailVideo } from '../components/CreationDetail/DetailVideo';
|
|
7
9
|
import { DetailStory } from '../components/CreationDetail/DetailStory';
|
|
8
10
|
import { DetailActions } from '../components/CreationDetail/DetailActions';
|
|
9
11
|
|
|
10
12
|
import { useCreationsProvider } from '../components/CreationsProvider';
|
|
11
13
|
|
|
14
|
+
/** Video creation types */
|
|
15
|
+
const VIDEO_TYPES = ['text-to-video', 'image-to-video'] as const;
|
|
16
|
+
|
|
12
17
|
interface CreationDetailScreenProps {
|
|
13
18
|
readonly creation: Creation;
|
|
14
19
|
readonly onClose: () => void;
|
|
@@ -45,8 +50,19 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
45
50
|
const story = metadata.story || metadata.description || "";
|
|
46
51
|
const date = metadata.date || new Date(creation.createdAt).toLocaleDateString();
|
|
47
52
|
|
|
53
|
+
// Detect if this is a video creation
|
|
54
|
+
const isVideo = useMemo(() => {
|
|
55
|
+
if (VIDEO_TYPES.includes(creation.type as typeof VIDEO_TYPES[number])) return true;
|
|
56
|
+
if (hasVideoContent(creation.output)) return true;
|
|
57
|
+
return false;
|
|
58
|
+
}, [creation.type, creation.output]);
|
|
59
|
+
|
|
48
60
|
const styles = useStyles(tokens);
|
|
49
61
|
|
|
62
|
+
// Get video URL and thumbnail for video content
|
|
63
|
+
const videoUrl = creation.output?.videoUrl || creation.uri;
|
|
64
|
+
const thumbnailUrl = getPreviewUrl(creation.output) || undefined;
|
|
65
|
+
|
|
50
66
|
return (
|
|
51
67
|
<ScreenLayout
|
|
52
68
|
scrollable={true}
|
|
@@ -61,7 +77,11 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
61
77
|
}
|
|
62
78
|
contentContainerStyle={styles.scrollContent}
|
|
63
79
|
>
|
|
64
|
-
|
|
80
|
+
{isVideo ? (
|
|
81
|
+
<DetailVideo videoUrl={videoUrl} thumbnailUrl={thumbnailUrl} />
|
|
82
|
+
) : (
|
|
83
|
+
<DetailImage uri={creation.uri} />
|
|
84
|
+
)}
|
|
65
85
|
|
|
66
86
|
{story ? (
|
|
67
87
|
<DetailStory story={story} />
|
|
@@ -70,8 +90,8 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
70
90
|
<DetailActions
|
|
71
91
|
onShare={() => onShare(creation)}
|
|
72
92
|
onDelete={() => onDelete(creation)}
|
|
73
|
-
shareLabel={t("result.shareButton")
|
|
74
|
-
deleteLabel={t("common.delete")
|
|
93
|
+
shareLabel={t("result.shareButton")}
|
|
94
|
+
deleteLabel={t("common.delete")}
|
|
75
95
|
/>
|
|
76
96
|
</ScreenLayout>
|
|
77
97
|
);
|
|
@@ -166,60 +166,63 @@ function CreationsGalleryScreenContent({
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
return (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
169
|
+
<>
|
|
170
|
+
<ScreenLayout
|
|
171
|
+
scrollable={false}
|
|
172
|
+
edges={["top"]}
|
|
173
|
+
backgroundColor={tokens.colors.background}
|
|
174
|
+
header={
|
|
175
|
+
(!creations || creations?.length === 0) && !isLoading ? null : (
|
|
176
|
+
<GalleryHeader
|
|
177
|
+
title={t(config.translations.title)}
|
|
178
|
+
count={filtered.length}
|
|
179
|
+
countLabel={t(config.translations.photoCount)}
|
|
180
|
+
isFiltered={isFiltered}
|
|
181
|
+
showFilter={showFilter}
|
|
182
|
+
filterLabel={t(config.translations.filterLabel)}
|
|
183
|
+
onFilterPress={() => {
|
|
184
|
+
if (__DEV__) {
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.log('[CreationsGallery] Filter button pressed');
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.log('[CreationsGallery] filterSheetRef.current:', filterSheetRef.current);
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.log('[CreationsGallery] allCategories:', allCategories);
|
|
191
|
+
}
|
|
192
|
+
filterSheetRef.current?.present();
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
>
|
|
198
|
+
{/* Main Content Grid - handles empty/loading via ListEmptyComponent */}
|
|
199
|
+
<CreationsGrid
|
|
200
|
+
creations={filtered}
|
|
201
|
+
isLoading={isLoading}
|
|
202
|
+
onRefresh={() => void refetch()}
|
|
203
|
+
onPress={(creation) => handleView(creation as Creation)}
|
|
204
|
+
onShare={async (creation) => handleShare(creation as Creation)}
|
|
205
|
+
onDelete={(creation) => handleDelete(creation as Creation)}
|
|
206
|
+
onFavorite={(creation) => {
|
|
207
|
+
const c = creation as Creation;
|
|
208
|
+
handleFavorite(c, !c.isFavorite);
|
|
209
|
+
}}
|
|
210
|
+
contentContainerStyle={{ paddingBottom: tokens.spacing.xl }}
|
|
211
|
+
ListEmptyComponent={renderEmptyComponent}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
<CreationImageViewer
|
|
215
|
+
creations={filtered}
|
|
216
|
+
visible={viewerVisible}
|
|
217
|
+
index={viewerIndex}
|
|
218
|
+
onDismiss={() => setViewerVisible(false)}
|
|
219
|
+
onIndexChange={setViewerIndex}
|
|
220
|
+
enableEditing={enableEditing}
|
|
221
|
+
onImageEdit={onImageEdit}
|
|
222
|
+
/>
|
|
223
|
+
</ScreenLayout>
|
|
224
|
+
|
|
225
|
+
{/* FilterBottomSheet must be outside ScreenLayout for proper portal rendering */}
|
|
223
226
|
<FilterBottomSheet
|
|
224
227
|
ref={filterSheetRef}
|
|
225
228
|
categories={allCategories}
|
|
@@ -229,9 +232,9 @@ function CreationsGalleryScreenContent({
|
|
|
229
232
|
toggleFilter(id, category?.multiSelect);
|
|
230
233
|
}}
|
|
231
234
|
onClearFilters={clearFilters}
|
|
232
|
-
title={t(config.translations.filterTitle)
|
|
235
|
+
title={t(config.translations.filterTitle)}
|
|
233
236
|
/>
|
|
234
|
-
|
|
237
|
+
</>
|
|
235
238
|
);
|
|
236
239
|
}
|
|
237
240
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Constants
|
|
3
|
+
* All constant exports for text-to-image feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { DEFAULT_IMAGE_STYLES } from "./styles.constants";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_NUM_IMAGES_OPTIONS,
|
|
10
|
+
DEFAULT_ASPECT_RATIO_OPTIONS,
|
|
11
|
+
DEFAULT_SIZE_OPTIONS,
|
|
12
|
+
DEFAULT_OUTPUT_FORMAT_OPTIONS,
|
|
13
|
+
DEFAULT_FORM_VALUES,
|
|
14
|
+
} from "./options.constants";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Options Constants
|
|
3
|
+
* Default option values for text-to-image generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AspectRatio,
|
|
8
|
+
ImageSize,
|
|
9
|
+
NumImages,
|
|
10
|
+
OutputFormat,
|
|
11
|
+
TextToImageFormDefaults,
|
|
12
|
+
} from "../types/form.types";
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_NUM_IMAGES_OPTIONS: NumImages[] = [1, 2, 3, 4];
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_ASPECT_RATIO_OPTIONS: { value: AspectRatio; label: string }[] = [
|
|
17
|
+
{ value: "9:16", label: "Portrait (9:16)" },
|
|
18
|
+
{ value: "16:9", label: "Landscape (16:9)" },
|
|
19
|
+
{ value: "1:1", label: "Square (1:1)" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_SIZE_OPTIONS: { value: ImageSize; label: string }[] = [
|
|
23
|
+
{ value: "512x512", label: "512×512" },
|
|
24
|
+
{ value: "768x768", label: "768×768" },
|
|
25
|
+
{ value: "1024x1024", label: "1024×1024" },
|
|
26
|
+
{ value: "1024x1792", label: "1024×1792" },
|
|
27
|
+
{ value: "1792x1024", label: "1792×1024" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_OUTPUT_FORMAT_OPTIONS: { value: OutputFormat; label: string }[] = [
|
|
31
|
+
{ value: "png", label: "PNG" },
|
|
32
|
+
{ value: "jpeg", label: "JPEG" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_FORM_VALUES: TextToImageFormDefaults = {
|
|
36
|
+
aspectRatio: "9:16",
|
|
37
|
+
size: "512x512",
|
|
38
|
+
numImages: 1,
|
|
39
|
+
guidanceScale: 7.5,
|
|
40
|
+
outputFormat: "png",
|
|
41
|
+
selectedStyle: "realistic",
|
|
42
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Image Styles
|
|
3
|
+
* Predefined style options for text-to-image generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { StyleOption } from "../types/form.types";
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_IMAGE_STYLES: StyleOption[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "realistic",
|
|
11
|
+
name: "Realistic",
|
|
12
|
+
description: "Photorealistic images",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "artistic",
|
|
16
|
+
name: "Artistic",
|
|
17
|
+
description: "Creative and artistic style",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "anime",
|
|
21
|
+
name: "Anime",
|
|
22
|
+
description: "Japanese animation style",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "minimalist",
|
|
26
|
+
name: "Minimalist",
|
|
27
|
+
description: "Clean and simple design",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "vintage",
|
|
31
|
+
name: "Vintage",
|
|
32
|
+
description: "Retro and classic look",
|
|
33
|
+
},
|
|
34
|
+
];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Configuration Types
|
|
3
|
+
* Callback and configuration types for app integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AspectRatio,
|
|
8
|
+
ImageSize,
|
|
9
|
+
NumImages,
|
|
10
|
+
OutputFormat,
|
|
11
|
+
StyleOption,
|
|
12
|
+
TextToImageFormDefaults,
|
|
13
|
+
} from "./form.types";
|
|
14
|
+
|
|
15
|
+
export interface GenerationRequest {
|
|
16
|
+
prompt: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
aspectRatio: AspectRatio;
|
|
19
|
+
size: ImageSize;
|
|
20
|
+
negativePrompt?: string;
|
|
21
|
+
guidanceScale: number;
|
|
22
|
+
numImages: NumImages;
|
|
23
|
+
style?: string;
|
|
24
|
+
outputFormat?: OutputFormat;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GenerationResultSuccess {
|
|
28
|
+
success: true;
|
|
29
|
+
imageUrls: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface GenerationResultError {
|
|
33
|
+
success: false;
|
|
34
|
+
error: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type GenerationResult = GenerationResultSuccess | GenerationResultError;
|
|
38
|
+
|
|
39
|
+
export interface TextToImageCallbacks {
|
|
40
|
+
executeGeneration: (request: GenerationRequest) => Promise<GenerationResult>;
|
|
41
|
+
calculateCost: (numImages: NumImages, model?: string | null) => number;
|
|
42
|
+
canAfford: (cost: number) => boolean;
|
|
43
|
+
isAuthenticated: () => boolean;
|
|
44
|
+
onAuthRequired?: () => void;
|
|
45
|
+
onCreditsRequired?: (cost: number) => void;
|
|
46
|
+
onSuccess?: (imageUrls: string[]) => void;
|
|
47
|
+
onError?: (error: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TextToImageFormConfig {
|
|
51
|
+
defaults?: TextToImageFormDefaults;
|
|
52
|
+
numImagesOptions?: NumImages[];
|
|
53
|
+
styleOptions?: StyleOption[];
|
|
54
|
+
aspectRatioOptions?: { value: AspectRatio; label: string }[];
|
|
55
|
+
sizeOptions?: { value: ImageSize; label: string }[];
|
|
56
|
+
outputFormatOptions?: { value: OutputFormat; label: string }[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TextToImageTranslations {
|
|
60
|
+
promptLabel: string;
|
|
61
|
+
promptPlaceholder: string;
|
|
62
|
+
promptCharacterCount?: string;
|
|
63
|
+
examplesLabel: string;
|
|
64
|
+
numImagesLabel: string;
|
|
65
|
+
styleLabel: string;
|
|
66
|
+
generateButton: string;
|
|
67
|
+
generateButtonMultiple?: string;
|
|
68
|
+
costLabel?: string;
|
|
69
|
+
settingsTitle?: string;
|
|
70
|
+
doneButton?: string;
|
|
71
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Form Types
|
|
3
|
+
* Generic form state types for text-to-image generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type AspectRatio = "16:9" | "9:16" | "1:1";
|
|
7
|
+
|
|
8
|
+
export type ImageSize =
|
|
9
|
+
| "512x512"
|
|
10
|
+
| "768x768"
|
|
11
|
+
| "1024x1024"
|
|
12
|
+
| "1024x1792"
|
|
13
|
+
| "1792x1024";
|
|
14
|
+
|
|
15
|
+
export type OutputFormat = "png" | "jpeg";
|
|
16
|
+
|
|
17
|
+
export type NumImages = 1 | 2 | 3 | 4;
|
|
18
|
+
|
|
19
|
+
export interface StyleOption {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
icon?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TextToImageFormState {
|
|
27
|
+
prompt: string;
|
|
28
|
+
aspectRatio: AspectRatio;
|
|
29
|
+
size: ImageSize;
|
|
30
|
+
numImages: NumImages;
|
|
31
|
+
negativePrompt: string;
|
|
32
|
+
guidanceScale: number;
|
|
33
|
+
selectedModel: string | null;
|
|
34
|
+
outputFormat: OutputFormat;
|
|
35
|
+
selectedStyle: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TextToImageFormActions {
|
|
39
|
+
setPrompt: (prompt: string) => void;
|
|
40
|
+
setAspectRatio: (ratio: AspectRatio) => void;
|
|
41
|
+
setSize: (size: ImageSize) => void;
|
|
42
|
+
setNumImages: (num: NumImages) => void;
|
|
43
|
+
setNegativePrompt: (prompt: string) => void;
|
|
44
|
+
setGuidanceScale: (scale: number) => void;
|
|
45
|
+
setSelectedModel: (model: string | null) => void;
|
|
46
|
+
setOutputFormat: (format: OutputFormat) => void;
|
|
47
|
+
setSelectedStyle: (style: string) => void;
|
|
48
|
+
reset: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TextToImageFormDefaults {
|
|
52
|
+
aspectRatio?: AspectRatio;
|
|
53
|
+
size?: ImageSize;
|
|
54
|
+
numImages?: NumImages;
|
|
55
|
+
guidanceScale?: number;
|
|
56
|
+
outputFormat?: OutputFormat;
|
|
57
|
+
selectedStyle?: string;
|
|
58
|
+
}
|
|
@@ -1,9 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Domain Types
|
|
3
|
+
* All type exports for text-to-image feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Form types
|
|
7
|
+
export type {
|
|
8
|
+
AspectRatio,
|
|
9
|
+
ImageSize,
|
|
10
|
+
OutputFormat,
|
|
11
|
+
NumImages,
|
|
12
|
+
StyleOption,
|
|
13
|
+
TextToImageFormState,
|
|
14
|
+
TextToImageFormActions,
|
|
15
|
+
TextToImageFormDefaults,
|
|
16
|
+
} from "./form.types";
|
|
17
|
+
|
|
18
|
+
// Config types
|
|
19
|
+
export type {
|
|
20
|
+
GenerationRequest,
|
|
21
|
+
GenerationResult,
|
|
22
|
+
GenerationResultSuccess,
|
|
23
|
+
GenerationResultError,
|
|
24
|
+
TextToImageCallbacks,
|
|
25
|
+
TextToImageFormConfig,
|
|
26
|
+
TextToImageTranslations,
|
|
27
|
+
} from "./config.types";
|
|
28
|
+
|
|
29
|
+
// Provider types (existing)
|
|
1
30
|
export type {
|
|
2
31
|
TextToImageOptions,
|
|
3
32
|
TextToImageRequest,
|
|
4
33
|
TextToImageResult,
|
|
5
34
|
TextToImageFeatureState,
|
|
6
|
-
TextToImageTranslations,
|
|
7
35
|
TextToImageInputBuilder,
|
|
8
36
|
TextToImageResultExtractor,
|
|
9
37
|
TextToImageFeatureConfig,
|
|
@@ -35,14 +35,6 @@ export interface TextToImageFeatureState {
|
|
|
35
35
|
error: string | null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export interface TextToImageTranslations {
|
|
39
|
-
promptPlaceholder: string;
|
|
40
|
-
generateButtonText: string;
|
|
41
|
-
processingText: string;
|
|
42
|
-
successText: string;
|
|
43
|
-
saveButtonText: string;
|
|
44
|
-
tryAnotherText: string;
|
|
45
|
-
}
|
|
46
38
|
|
|
47
39
|
export type TextToImageInputBuilder = (
|
|
48
40
|
prompt: string,
|
package/src/index.ts
CHANGED
|
@@ -137,10 +137,14 @@ export {
|
|
|
137
137
|
isJobComplete,
|
|
138
138
|
isJobProcessing,
|
|
139
139
|
isJobFailed,
|
|
140
|
-
// Result validation
|
|
140
|
+
// Result validation & URL extraction
|
|
141
141
|
validateResult,
|
|
142
142
|
extractOutputUrl,
|
|
143
143
|
extractOutputUrls,
|
|
144
|
+
extractVideoUrl,
|
|
145
|
+
extractThumbnailUrl,
|
|
146
|
+
extractAudioUrl,
|
|
147
|
+
extractImageUrls,
|
|
144
148
|
// Photo generation utils
|
|
145
149
|
cleanBase64,
|
|
146
150
|
addBase64Prefix,
|
|
@@ -230,3 +230,96 @@ export function extractOutputUrls(
|
|
|
230
230
|
|
|
231
231
|
return urls;
|
|
232
232
|
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Extract video URL from AI generation result
|
|
236
|
+
*/
|
|
237
|
+
export function extractVideoUrl(result: unknown): string | undefined {
|
|
238
|
+
return extractOutputUrl(result, [
|
|
239
|
+
"video_url",
|
|
240
|
+
"videoUrl",
|
|
241
|
+
"video",
|
|
242
|
+
"url",
|
|
243
|
+
]);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Extract thumbnail URL from AI generation result
|
|
248
|
+
*/
|
|
249
|
+
export function extractThumbnailUrl(result: unknown): string | undefined {
|
|
250
|
+
if (!result || typeof result !== "object") {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const resultObj = result as Record<string, unknown>;
|
|
255
|
+
|
|
256
|
+
// Check direct fields
|
|
257
|
+
const fields = ["thumbnail_url", "thumbnailUrl", "thumbnail", "poster"];
|
|
258
|
+
for (const field of fields) {
|
|
259
|
+
const value = resultObj[field];
|
|
260
|
+
if (typeof value === "string" && value.length > 0) {
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
if (value && typeof value === "object") {
|
|
264
|
+
const nested = value as Record<string, unknown>;
|
|
265
|
+
if (typeof nested.url === "string") {
|
|
266
|
+
return nested.url;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Extract audio URL from AI generation result
|
|
276
|
+
*/
|
|
277
|
+
export function extractAudioUrl(result: unknown): string | undefined {
|
|
278
|
+
return extractOutputUrl(result, [
|
|
279
|
+
"audio_url",
|
|
280
|
+
"audioUrl",
|
|
281
|
+
"audio",
|
|
282
|
+
"url",
|
|
283
|
+
]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Extract image URLs from AI generation result
|
|
288
|
+
*/
|
|
289
|
+
export function extractImageUrls(result: unknown): string[] {
|
|
290
|
+
if (!result || typeof result !== "object") {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const urls: string[] = [];
|
|
295
|
+
const resultObj = result as Record<string, unknown>;
|
|
296
|
+
|
|
297
|
+
// Check images array
|
|
298
|
+
if (Array.isArray(resultObj.images)) {
|
|
299
|
+
for (const img of resultObj.images) {
|
|
300
|
+
if (typeof img === "string" && img.length > 0) {
|
|
301
|
+
urls.push(img);
|
|
302
|
+
} else if (img && typeof img === "object") {
|
|
303
|
+
const imgObj = img as Record<string, unknown>;
|
|
304
|
+
if (typeof imgObj.url === "string") {
|
|
305
|
+
urls.push(imgObj.url);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check single image
|
|
312
|
+
if (urls.length === 0) {
|
|
313
|
+
const singleUrl = extractOutputUrl(result, [
|
|
314
|
+
"image_url",
|
|
315
|
+
"imageUrl",
|
|
316
|
+
"image",
|
|
317
|
+
"url",
|
|
318
|
+
]);
|
|
319
|
+
if (singleUrl) {
|
|
320
|
+
urls.push(singleUrl);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return urls;
|
|
325
|
+
}
|