@umituz/react-native-ai-generation-content 1.72.10 → 1.72.12
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/domains/creations/presentation/components/CreationCard.utils.ts +2 -3
- package/src/domains/creations/presentation/components/CreationsFilterBar.tsx +41 -5
- package/src/domains/creations/presentation/hooks/filterHelpers.ts +5 -17
- package/src/domains/image-to-video/presentation/hooks/useFormState.ts +30 -58
- package/src/domains/image-to-video/presentation/hooks/useGeneration.ts +41 -71
- package/src/domains/text-to-image/presentation/hooks/useFormState.ts +34 -81
- package/src/exports/presentation.ts +5 -5
- package/src/index.ts +3 -0
- package/src/presentation/components/GenerationProgressContent.tsx +5 -2
- package/src/presentation/components/PendingJobCard.tsx +9 -2
- package/src/presentation/components/README.md +6 -5
- package/src/presentation/components/index.ts +0 -4
- package/src/shared/components/common/ProgressBar.tsx +99 -0
- package/src/shared/components/common/index.ts +5 -0
- package/src/shared/components/index.ts +5 -0
- package/src/shared/hooks/factories/createFormStateHook.ts +119 -0
- package/src/shared/hooks/factories/createGenerationHook.ts +253 -0
- package/src/shared/hooks/factories/index.ts +21 -0
- package/src/shared/hooks/index.ts +5 -0
- package/src/shared/index.ts +14 -0
- package/src/shared/utils/date/index.ts +11 -0
- package/src/shared/utils/date/normalization.ts +60 -0
- package/src/shared/utils/filters/createFilterButtons.ts +60 -0
- package/src/shared/utils/filters/index.ts +11 -0
- package/src/shared/utils/index.ts +6 -0
- package/src/domains/creations/presentation/components/filter-bar-utils.ts +0 -96
- package/src/presentation/components/GenerationProgressBar.tsx +0 -78
- package/src/presentation/components/PendingJobProgressBar.tsx +0 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.72.
|
|
3
|
+
"version": "1.72.12",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useMemo } from "react";
|
|
6
|
+
import { normalizeToDate } from "../../../../shared/utils/date";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Format creation date for display
|
|
@@ -12,9 +13,7 @@ export function useCreationDateFormatter(
|
|
|
12
13
|
formatDate?: (date: Date) => string
|
|
13
14
|
): string {
|
|
14
15
|
return useMemo(() => {
|
|
15
|
-
const date = createdAt
|
|
16
|
-
? createdAt
|
|
17
|
-
: new Date(createdAt);
|
|
16
|
+
const date = normalizeToDate(createdAt);
|
|
18
17
|
|
|
19
18
|
if (formatDate) {
|
|
20
19
|
return formatDate(date);
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
AtomicText,
|
|
11
11
|
AtomicIcon,
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
|
-
import
|
|
13
|
+
import { createFilterButtons } from "../../../../shared/utils/filters";
|
|
14
|
+
import type { CreationsFilterBarProps, MediaFilterLabels, StatusFilterLabels, FilterButton } from "./CreationsFilterBar.types";
|
|
14
15
|
|
|
15
16
|
export function CreationsFilterBar({
|
|
16
17
|
filters,
|
|
@@ -133,7 +134,42 @@ export function CreationsFilterBar({
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
export type { FilterButton, CreationsFilterBarProps } from "./CreationsFilterBar.types";
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Helper to create media filter buttons
|
|
140
|
+
* Uses shared generic filter factory
|
|
141
|
+
*/
|
|
142
|
+
export function createMediaFilterButtons(
|
|
143
|
+
activeFilter: string,
|
|
144
|
+
onSelect: (filter: string) => void,
|
|
145
|
+
labels: MediaFilterLabels
|
|
146
|
+
): FilterButton[] {
|
|
147
|
+
const items = [
|
|
148
|
+
{ id: "all" as const, label: labels.all, icon: "grid" },
|
|
149
|
+
{ id: "image" as const, label: labels.images, icon: "image" },
|
|
150
|
+
{ id: "video" as const, label: labels.videos, icon: "film" },
|
|
151
|
+
{ id: "voice" as const, label: labels.voice, icon: "mic" },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
return createFilterButtons(items, activeFilter, onSelect);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Helper to create status filter buttons
|
|
159
|
+
* Uses shared generic filter factory
|
|
160
|
+
*/
|
|
161
|
+
export function createStatusFilterButtons(
|
|
162
|
+
activeFilter: string,
|
|
163
|
+
onSelect: (filter: string) => void,
|
|
164
|
+
labels: StatusFilterLabels
|
|
165
|
+
): FilterButton[] {
|
|
166
|
+
const items = [
|
|
167
|
+
{ id: "all" as const, label: labels.all, icon: "options" },
|
|
168
|
+
{ id: "completed" as const, label: labels.completed, icon: "checkmark-circle" },
|
|
169
|
+
{ id: "processing" as const, label: labels.processing, icon: "refresh" },
|
|
170
|
+
{ id: "pending" as const, label: labels.pending, icon: "time" },
|
|
171
|
+
{ id: "failed" as const, label: labels.failed, icon: "close-circle" },
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
return createFilterButtons(items, activeFilter, onSelect);
|
|
175
|
+
}
|
|
@@ -7,6 +7,7 @@ import type { CreationFilter } from "../../domain/types";
|
|
|
7
7
|
import { isTypeInCategory } from "../../domain/types";
|
|
8
8
|
import type { CreationCategory, CreationTypeId } from "../../domain/types";
|
|
9
9
|
import type { FilterableCreation } from "./advancedFilter.types";
|
|
10
|
+
import { normalizeDateToTimestamp, compareDates } from "../../../../shared/utils/date";
|
|
10
11
|
|
|
11
12
|
export function filterByType<T extends FilterableCreation>(
|
|
12
13
|
creations: T[],
|
|
@@ -55,16 +56,14 @@ export function filterByDateRange<T extends FilterableCreation>(
|
|
|
55
56
|
|
|
56
57
|
if (startDate) {
|
|
57
58
|
result = result.filter((c) => {
|
|
58
|
-
const createdAt =
|
|
59
|
-
c.createdAt instanceof Date ? c.createdAt.getTime() : c.createdAt || 0;
|
|
59
|
+
const createdAt = normalizeDateToTimestamp(c.createdAt);
|
|
60
60
|
return createdAt >= startDate;
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
if (endDate) {
|
|
65
65
|
result = result.filter((c) => {
|
|
66
|
-
const createdAt =
|
|
67
|
-
c.createdAt instanceof Date ? c.createdAt.getTime() : c.createdAt || 0;
|
|
66
|
+
const createdAt = normalizeDateToTimestamp(c.createdAt);
|
|
68
67
|
return createdAt <= endDate;
|
|
69
68
|
});
|
|
70
69
|
}
|
|
@@ -85,13 +84,9 @@ export function sortCreations<T extends FilterableCreation>(
|
|
|
85
84
|
|
|
86
85
|
switch (sortField) {
|
|
87
86
|
case "createdAt":
|
|
88
|
-
|
|
89
|
-
bVal = b.createdAt;
|
|
90
|
-
break;
|
|
87
|
+
return compareDates(a.createdAt, b.createdAt, sortOrder);
|
|
91
88
|
case "updatedAt":
|
|
92
|
-
|
|
93
|
-
bVal = b.updatedAt;
|
|
94
|
-
break;
|
|
89
|
+
return compareDates(a.updatedAt, b.updatedAt, sortOrder);
|
|
95
90
|
case "type":
|
|
96
91
|
aVal = a.type;
|
|
97
92
|
bVal = b.type;
|
|
@@ -104,9 +99,6 @@ export function sortCreations<T extends FilterableCreation>(
|
|
|
104
99
|
return 0;
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
if (aVal instanceof Date) aVal = aVal.getTime();
|
|
108
|
-
if (bVal instanceof Date) bVal = bVal.getTime();
|
|
109
|
-
|
|
110
102
|
if (aVal === undefined && bVal === undefined) return 0;
|
|
111
103
|
if (aVal === undefined) return 1;
|
|
112
104
|
if (bVal === undefined) return -1;
|
|
@@ -117,10 +109,6 @@ export function sortCreations<T extends FilterableCreation>(
|
|
|
117
109
|
: aVal.localeCompare(bVal);
|
|
118
110
|
}
|
|
119
111
|
|
|
120
|
-
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
121
|
-
return sortOrder === "desc" ? bVal - aVal : aVal - bVal;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
112
|
return 0;
|
|
125
113
|
});
|
|
126
114
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Form State Hook for Image-to-Video
|
|
3
3
|
* Manages form state with actions
|
|
4
|
+
* Now powered by generic form state factory
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { useCallback, useMemo } from "react";
|
|
8
|
+
import { createFormStateHook } from "../../../../shared/hooks/factories";
|
|
7
9
|
import type {
|
|
8
10
|
ImageToVideoFormState,
|
|
9
11
|
ImageToVideoFormActions,
|
|
10
12
|
ImageToVideoFormDefaults,
|
|
11
|
-
AnimationStyleId,
|
|
12
|
-
VideoDuration,
|
|
13
13
|
} from "../../domain/types";
|
|
14
14
|
|
|
15
15
|
export interface UseFormStateOptions {
|
|
@@ -18,78 +18,50 @@ export interface UseFormStateOptions {
|
|
|
18
18
|
|
|
19
19
|
export interface UseFormStateReturn {
|
|
20
20
|
state: ImageToVideoFormState;
|
|
21
|
-
actions: ImageToVideoFormActions
|
|
21
|
+
actions: ImageToVideoFormActions & {
|
|
22
|
+
addImages: (images: string[]) => void;
|
|
23
|
+
removeImage: (index: number) => void;
|
|
24
|
+
};
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
// Create the form state hook using the factory
|
|
28
|
+
const useImageToVideoFormStateInternal = createFormStateHook<
|
|
29
|
+
ImageToVideoFormState,
|
|
30
|
+
ImageToVideoFormDefaults
|
|
31
|
+
>({
|
|
32
|
+
createInitialState: (defaults) => ({
|
|
26
33
|
selectedImages: [],
|
|
27
34
|
animationStyle: defaults.animationStyle ?? "none",
|
|
28
35
|
duration: defaults.duration ?? 3,
|
|
29
36
|
motionPrompt: "",
|
|
30
|
-
}
|
|
31
|
-
}
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
32
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Image-to-Video form state hook
|
|
42
|
+
* Manages form fields with additional image array helpers
|
|
43
|
+
*/
|
|
33
44
|
export function useFormState(options: UseFormStateOptions): UseFormStateReturn {
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
const [state, setState] = useState<ImageToVideoFormState>(() =>
|
|
37
|
-
createInitialState(defaults)
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const setSelectedImages = useCallback((images: string[]) => {
|
|
41
|
-
setState((prev) => ({ ...prev, selectedImages: images }));
|
|
42
|
-
}, []);
|
|
45
|
+
const { state, actions: baseActions } = useImageToVideoFormStateInternal(options);
|
|
43
46
|
|
|
47
|
+
// Add custom array helpers
|
|
44
48
|
const addImages = useCallback((images: string[]) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
selectedImages: [...prev.selectedImages, ...images],
|
|
48
|
-
}));
|
|
49
|
-
}, []);
|
|
49
|
+
baseActions.setSelectedImages([...state.selectedImages, ...images]);
|
|
50
|
+
}, [state.selectedImages, baseActions]);
|
|
50
51
|
|
|
51
52
|
const removeImage = useCallback((index: number) => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}, []);
|
|
57
|
-
|
|
58
|
-
const setAnimationStyle = useCallback((style: AnimationStyleId) => {
|
|
59
|
-
setState((prev) => ({ ...prev, animationStyle: style }));
|
|
60
|
-
}, []);
|
|
61
|
-
|
|
62
|
-
const setDuration = useCallback((duration: VideoDuration) => {
|
|
63
|
-
setState((prev) => ({ ...prev, duration }));
|
|
64
|
-
}, []);
|
|
65
|
-
|
|
66
|
-
const setMotionPrompt = useCallback((prompt: string) => {
|
|
67
|
-
setState((prev) => ({ ...prev, motionPrompt: prompt }));
|
|
68
|
-
}, []);
|
|
53
|
+
baseActions.setSelectedImages(
|
|
54
|
+
state.selectedImages.filter((_, i) => i !== index)
|
|
55
|
+
);
|
|
56
|
+
}, [state.selectedImages, baseActions]);
|
|
69
57
|
|
|
70
|
-
const
|
|
71
|
-
setState(createInitialState(defaults));
|
|
72
|
-
}, [defaults]);
|
|
73
|
-
|
|
74
|
-
const actions = useMemo<ImageToVideoFormActions>(
|
|
58
|
+
const actions = useMemo(
|
|
75
59
|
() => ({
|
|
76
|
-
|
|
60
|
+
...baseActions,
|
|
77
61
|
addImages,
|
|
78
62
|
removeImage,
|
|
79
|
-
setAnimationStyle,
|
|
80
|
-
setDuration,
|
|
81
|
-
setMotionPrompt,
|
|
82
|
-
reset,
|
|
83
63
|
}),
|
|
84
|
-
[
|
|
85
|
-
setSelectedImages,
|
|
86
|
-
addImages,
|
|
87
|
-
removeImage,
|
|
88
|
-
setAnimationStyle,
|
|
89
|
-
setDuration,
|
|
90
|
-
setMotionPrompt,
|
|
91
|
-
reset,
|
|
92
|
-
]
|
|
64
|
+
[baseActions, addImages, removeImage]
|
|
93
65
|
);
|
|
94
66
|
|
|
95
67
|
return { state, actions };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Hook for Image-to-Video
|
|
3
3
|
* Manages generation state and execution with abort support
|
|
4
|
+
* Now powered by generic generation hook factory
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
import { createGenerationHook } from "../../../../shared/hooks/factories";
|
|
7
9
|
import type {
|
|
8
10
|
ImageToVideoFormState,
|
|
9
11
|
ImageToVideoGenerationState,
|
|
@@ -23,85 +25,53 @@ export interface UseGenerationReturn {
|
|
|
23
25
|
isReady: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Create the generation hook using the factory
|
|
29
|
+
const useImageToVideoGenerationInternal = createGenerationHook<
|
|
30
|
+
ImageToVideoFormState,
|
|
31
|
+
void
|
|
32
|
+
>({
|
|
33
|
+
execute: async (_request) => {
|
|
34
|
+
// This will be called via callbacks
|
|
35
|
+
// The actual execution happens in the callbacks layer
|
|
36
|
+
},
|
|
37
|
+
validate: (request) => {
|
|
38
|
+
if (request.selectedImages.length === 0) {
|
|
39
|
+
return "No images selected";
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
transformError: (error) => {
|
|
44
|
+
return error instanceof Error ? error.message : String(error);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
31
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Image-to-Video generation hook
|
|
50
|
+
* Manages generation state with validation and error handling
|
|
51
|
+
*/
|
|
32
52
|
export function useGeneration(options: UseGenerationOptions): UseGenerationReturn {
|
|
33
53
|
const { formState, callbacks } = options;
|
|
34
54
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
isMountedRef.current = true;
|
|
45
|
-
return () => {
|
|
46
|
-
isMountedRef.current = false;
|
|
47
|
-
abortControllerRef.current?.abort();
|
|
48
|
-
};
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
// Stabilize callbacks to prevent unnecessary re-renders
|
|
52
|
-
const onErrorRef = useRef(callbacks.onError);
|
|
53
|
-
const onGenerateRef = useRef(callbacks.onGenerate);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
onErrorRef.current = callbacks.onError;
|
|
57
|
-
onGenerateRef.current = callbacks.onGenerate;
|
|
58
|
-
}, [callbacks.onError, callbacks.onGenerate]);
|
|
59
|
-
|
|
60
|
-
const setProgress = useCallback((progress: number) => {
|
|
61
|
-
if (!isMountedRef.current) return;
|
|
62
|
-
setGenerationState((prev) => ({ ...prev, progress }));
|
|
63
|
-
}, []);
|
|
64
|
-
|
|
65
|
-
const setError = useCallback((error: string | null) => {
|
|
66
|
-
if (!isMountedRef.current) return;
|
|
67
|
-
setGenerationState((prev) => ({ ...prev, error, isGenerating: false }));
|
|
68
|
-
}, []);
|
|
69
|
-
|
|
70
|
-
const handleGenerate = useCallback(async () => {
|
|
71
|
-
if (formState.selectedImages.length === 0) {
|
|
72
|
-
onErrorRef.current?.("No images selected");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Create new AbortController for this generation
|
|
77
|
-
abortControllerRef.current = new AbortController();
|
|
78
|
-
|
|
79
|
-
setGenerationState({
|
|
80
|
-
isGenerating: true,
|
|
81
|
-
progress: 0,
|
|
82
|
-
error: null,
|
|
83
|
-
});
|
|
55
|
+
const {
|
|
56
|
+
generationState,
|
|
57
|
+
handleGenerate: baseHandleGenerate,
|
|
58
|
+
setProgress,
|
|
59
|
+
setError,
|
|
60
|
+
} = useImageToVideoGenerationInternal({
|
|
61
|
+
onError: callbacks.onError,
|
|
62
|
+
});
|
|
84
63
|
|
|
64
|
+
const handleGenerate = async () => {
|
|
65
|
+
await baseHandleGenerate(formState);
|
|
66
|
+
// Execute the actual generation via callbacks
|
|
85
67
|
try {
|
|
86
|
-
await
|
|
87
|
-
|
|
88
|
-
if (!isMountedRef.current || abortControllerRef.current.signal.aborted) return;
|
|
89
|
-
|
|
90
|
-
setGenerationState((prev) => ({ ...prev, isGenerating: false, progress: 100 }));
|
|
68
|
+
await callbacks.onGenerate(formState);
|
|
91
69
|
} catch (error) {
|
|
92
|
-
if (!isMountedRef.current || abortControllerRef.current.signal.aborted) return;
|
|
93
|
-
|
|
94
70
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
progress: 0,
|
|
98
|
-
error: errorMessage,
|
|
99
|
-
});
|
|
100
|
-
onErrorRef.current?.(errorMessage);
|
|
101
|
-
} finally {
|
|
102
|
-
abortControllerRef.current = null;
|
|
71
|
+
setError(errorMessage);
|
|
72
|
+
callbacks.onError?.(errorMessage);
|
|
103
73
|
}
|
|
104
|
-
}
|
|
74
|
+
};
|
|
105
75
|
|
|
106
76
|
const isReady = useMemo(
|
|
107
77
|
() => formState.selectedImages.length > 0 && !generationState.isGenerating,
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text-to-Image Form State Hook
|
|
3
3
|
* Manages form state for text-to-image generation
|
|
4
|
+
* Now powered by generic form state factory
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { createFormStateHook } from "../../../../shared/hooks/factories";
|
|
7
8
|
import type {
|
|
8
|
-
AspectRatio,
|
|
9
|
-
ImageSize,
|
|
10
|
-
NumImages,
|
|
11
|
-
OutputFormat,
|
|
12
9
|
TextToImageFormState,
|
|
13
10
|
TextToImageFormActions,
|
|
14
11
|
TextToImageFormDefaults,
|
|
@@ -23,95 +20,51 @@ export interface UseFormStateReturn {
|
|
|
23
20
|
actions: TextToImageFormActions;
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
function validateDefaults(
|
|
27
|
-
if (!
|
|
23
|
+
function validateDefaults(state: TextToImageFormState): void {
|
|
24
|
+
if (!state.aspectRatio) {
|
|
28
25
|
throw new Error("useFormState: defaults.aspectRatio is required");
|
|
29
26
|
}
|
|
30
|
-
if (!
|
|
27
|
+
if (!state.size) {
|
|
31
28
|
throw new Error("useFormState: defaults.size is required");
|
|
32
29
|
}
|
|
33
|
-
if (!
|
|
30
|
+
if (!state.numImages) {
|
|
34
31
|
throw new Error("useFormState: defaults.numImages is required");
|
|
35
32
|
}
|
|
36
|
-
if (
|
|
33
|
+
if (state.guidanceScale === undefined) {
|
|
37
34
|
throw new Error("useFormState: defaults.guidanceScale is required");
|
|
38
35
|
}
|
|
39
|
-
if (!
|
|
36
|
+
if (!state.outputFormat) {
|
|
40
37
|
throw new Error("useFormState: defaults.outputFormat is required");
|
|
41
38
|
}
|
|
42
|
-
if (!
|
|
39
|
+
if (!state.selectedStyle) {
|
|
43
40
|
throw new Error("useFormState: defaults.selectedStyle is required");
|
|
44
41
|
}
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
setSize(defaults.size);
|
|
67
|
-
setNumImages(defaults.numImages);
|
|
68
|
-
setNegativePrompt("");
|
|
69
|
-
setGuidanceScale(defaults.guidanceScale);
|
|
70
|
-
setSelectedModel(null);
|
|
71
|
-
setOutputFormat(defaults.outputFormat);
|
|
72
|
-
setSelectedStyle(defaults.selectedStyle);
|
|
73
|
-
}, [defaults]);
|
|
74
|
-
|
|
75
|
-
const state: TextToImageFormState = useMemo(
|
|
76
|
-
() => ({
|
|
77
|
-
prompt,
|
|
78
|
-
aspectRatio,
|
|
79
|
-
size,
|
|
80
|
-
numImages,
|
|
81
|
-
negativePrompt,
|
|
82
|
-
guidanceScale,
|
|
83
|
-
selectedModel,
|
|
84
|
-
outputFormat,
|
|
85
|
-
selectedStyle,
|
|
86
|
-
}),
|
|
87
|
-
[
|
|
88
|
-
prompt,
|
|
89
|
-
aspectRatio,
|
|
90
|
-
size,
|
|
91
|
-
numImages,
|
|
92
|
-
negativePrompt,
|
|
93
|
-
guidanceScale,
|
|
94
|
-
selectedModel,
|
|
95
|
-
outputFormat,
|
|
96
|
-
selectedStyle,
|
|
97
|
-
]
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const actions: TextToImageFormActions = useMemo(
|
|
101
|
-
() => ({
|
|
102
|
-
setPrompt,
|
|
103
|
-
setAspectRatio,
|
|
104
|
-
setSize,
|
|
105
|
-
setNumImages,
|
|
106
|
-
setNegativePrompt,
|
|
107
|
-
setGuidanceScale,
|
|
108
|
-
setSelectedModel,
|
|
109
|
-
setOutputFormat,
|
|
110
|
-
setSelectedStyle,
|
|
111
|
-
reset,
|
|
112
|
-
}),
|
|
113
|
-
[reset]
|
|
114
|
-
);
|
|
44
|
+
// Create the form state hook using the factory
|
|
45
|
+
const useTextToImageFormStateInternal = createFormStateHook<
|
|
46
|
+
TextToImageFormState,
|
|
47
|
+
TextToImageFormDefaults,
|
|
48
|
+
TextToImageFormActions
|
|
49
|
+
>({
|
|
50
|
+
createInitialState: (defaults) => ({
|
|
51
|
+
prompt: "",
|
|
52
|
+
aspectRatio: defaults.aspectRatio!,
|
|
53
|
+
size: defaults.size!,
|
|
54
|
+
numImages: defaults.numImages!,
|
|
55
|
+
negativePrompt: "",
|
|
56
|
+
guidanceScale: defaults.guidanceScale!,
|
|
57
|
+
selectedModel: null,
|
|
58
|
+
outputFormat: defaults.outputFormat!,
|
|
59
|
+
selectedStyle: defaults.selectedStyle!,
|
|
60
|
+
}),
|
|
61
|
+
validate: validateDefaults,
|
|
62
|
+
});
|
|
115
63
|
|
|
116
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Text-to-Image form state hook
|
|
66
|
+
* Manages all form fields for text-to-image generation
|
|
67
|
+
*/
|
|
68
|
+
export function useFormState(options: UseFormStateOptions): UseFormStateReturn {
|
|
69
|
+
return useTextToImageFormStateInternal(options);
|
|
117
70
|
}
|
|
@@ -28,20 +28,20 @@ export type {
|
|
|
28
28
|
|
|
29
29
|
// Components
|
|
30
30
|
export {
|
|
31
|
-
GenerationProgressContent,
|
|
32
|
-
|
|
31
|
+
GenerationProgressContent, PendingJobCard,
|
|
32
|
+
PendingJobCardActions, GenerationResultContent, ResultHeader,
|
|
33
33
|
ResultImageCard, ResultStoryCard, ResultActions, DEFAULT_RESULT_CONFIG, PhotoStep,
|
|
34
34
|
DualImagePicker, PromptInput, AIGenerationHero, ExamplePrompts, ModerationSummary,
|
|
35
35
|
GenerateButton, ResultDisplay, AIGenerationResult, ErrorDisplay, FeatureHeader,
|
|
36
36
|
AIGenScreenHeader, CreditBadge, PhotoUploadCard, SettingsSheet, StyleSelector,
|
|
37
37
|
AspectRatioSelector, DurationSelector, GridSelector, StylePresetsGrid, AIGenerationForm,
|
|
38
|
-
AIGenerationConfig, ModelSelector,
|
|
38
|
+
AIGenerationConfig, ModelSelector,
|
|
39
39
|
createAspectRatioOptions, createDurationOptions, createStyleOptions, createStyleOptionsFromConfig,
|
|
40
40
|
ASPECT_RATIO_IDS, COMMON_DURATIONS,
|
|
41
41
|
} from "../presentation/components";
|
|
42
42
|
export type {
|
|
43
|
-
GenerationProgressContentProps,
|
|
44
|
-
|
|
43
|
+
GenerationProgressContentProps, PendingJobCardProps, StatusLabels,
|
|
44
|
+
PendingJobCardActionsProps, GenerationResultData, GenerationResultContentProps,
|
|
45
45
|
ResultHeaderProps, ResultImageCardProps, ResultStoryCardProps, ResultActionsProps, ResultConfig,
|
|
46
46
|
ResultHeaderConfig, ResultImageConfig, ResultStoryConfig, ResultActionsConfig, ResultLayoutConfig,
|
|
47
47
|
ResultActionButton, PhotoStepProps, DualImagePickerProps, PromptInputProps, AIGenerationHeroProps,
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ProgressCloseButton } from "./ProgressCloseButton";
|
|
|
11
11
|
import { ProgressHeader } from "./ProgressHeader";
|
|
12
12
|
import { ProgressHint } from "./ProgressHint";
|
|
13
13
|
import { ProgressDismissButton } from "./ProgressDismissButton";
|
|
14
|
-
import {
|
|
14
|
+
import { ProgressBar } from "../../shared/components/common";
|
|
15
15
|
import { generationProgressContentStyles } from "./GenerationProgressContent.styles";
|
|
16
16
|
import type { GenerationProgressContentProps } from "./GenerationProgressContent.types";
|
|
17
17
|
|
|
@@ -56,11 +56,14 @@ export const GenerationProgressContent: React.FC<
|
|
|
56
56
|
textColor={textColor}
|
|
57
57
|
/>
|
|
58
58
|
|
|
59
|
-
<
|
|
59
|
+
<ProgressBar
|
|
60
60
|
progress={progress}
|
|
61
|
+
showPercentage={true}
|
|
61
62
|
textColor={tokens.colors.primary}
|
|
62
63
|
progressColor={progressColor}
|
|
63
64
|
backgroundColor={progressBackgroundColor}
|
|
65
|
+
height={8}
|
|
66
|
+
marginBottom={16}
|
|
64
67
|
/>
|
|
65
68
|
|
|
66
69
|
<ProgressHint
|
|
@@ -7,7 +7,7 @@ import React from "react";
|
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { AtomicText, AtomicSpinner, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import type { BackgroundJob } from "../../domains/background/domain/entities/job.types";
|
|
10
|
-
import {
|
|
10
|
+
import { ProgressBar } from "../../shared/components/common";
|
|
11
11
|
import { PendingJobCardActions } from "./PendingJobCardActions";
|
|
12
12
|
|
|
13
13
|
export interface StatusLabels {
|
|
@@ -110,7 +110,14 @@ export function PendingJobCard<TInput = unknown, TResult = unknown>({
|
|
|
110
110
|
<AtomicText style={styles.statusText}>
|
|
111
111
|
{statusText}
|
|
112
112
|
</AtomicText>
|
|
113
|
-
{!isFailed &&
|
|
113
|
+
{!isFailed && (
|
|
114
|
+
<ProgressBar
|
|
115
|
+
progress={job.progress}
|
|
116
|
+
showPercentage={false}
|
|
117
|
+
height={4}
|
|
118
|
+
marginBottom={0}
|
|
119
|
+
/>
|
|
120
|
+
)}
|
|
114
121
|
</View>
|
|
115
122
|
{renderActions ? (
|
|
116
123
|
renderActions(job)
|
|
@@ -50,17 +50,18 @@ import { GenerationProgressContent } from '@umituz/react-native-ai-generation-co
|
|
|
50
50
|
/>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
###
|
|
53
|
+
### ProgressBar
|
|
54
54
|
|
|
55
|
-
Progress bar component:
|
|
55
|
+
Progress bar component (from shared utilities):
|
|
56
56
|
|
|
57
57
|
```tsx
|
|
58
|
-
import {
|
|
58
|
+
import { ProgressBar } from '@umituz/react-native-ai-generation-content';
|
|
59
59
|
|
|
60
|
-
<
|
|
60
|
+
<ProgressBar
|
|
61
61
|
progress={progress}
|
|
62
|
+
showPercentage={true}
|
|
62
63
|
height={8}
|
|
63
|
-
|
|
64
|
+
progressColor="#4CAF50"
|
|
64
65
|
/>
|
|
65
66
|
```
|
|
66
67
|
|