@umituz/react-native-ai-generation-content 1.35.6 → 1.35.8
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/GalleryEmptyStates.tsx +3 -3
- package/src/domains/creations/presentation/components/GalleryHeader.tsx +36 -2
- package/src/domains/creations/presentation/hooks/useGalleryFilters.ts +24 -3
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +21 -43
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +10 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.35.
|
|
3
|
+
"version": "1.35.8",
|
|
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",
|
|
@@ -102,9 +102,9 @@ export function GalleryEmptyStates({
|
|
|
102
102
|
return (
|
|
103
103
|
<View style={styles.centerContainer}>
|
|
104
104
|
<EmptyState
|
|
105
|
-
title={t("common.no_results")
|
|
106
|
-
description={t("common.no_results_description")
|
|
107
|
-
actionLabel={t("common.clear_all")
|
|
105
|
+
title={t("common.no_results")}
|
|
106
|
+
description={t("common.no_results_description")}
|
|
107
|
+
actionLabel={t("common.clear_all")}
|
|
108
108
|
onAction={onClearFilters}
|
|
109
109
|
/>
|
|
110
110
|
</View>
|
|
@@ -19,6 +19,10 @@ interface GalleryHeaderProps {
|
|
|
19
19
|
readonly filterButtons?: FilterButtonConfig[];
|
|
20
20
|
readonly showFilter?: boolean;
|
|
21
21
|
readonly style?: ViewStyle;
|
|
22
|
+
/** Number of pending/processing jobs to show as badge */
|
|
23
|
+
readonly pendingCount?: number;
|
|
24
|
+
/** Label for pending badge tooltip */
|
|
25
|
+
readonly pendingLabel?: string;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
|
|
@@ -28,6 +32,8 @@ export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
|
|
|
28
32
|
filterButtons = [],
|
|
29
33
|
showFilter = true,
|
|
30
34
|
style,
|
|
35
|
+
pendingCount = 0,
|
|
36
|
+
pendingLabel,
|
|
31
37
|
}) => {
|
|
32
38
|
const tokens = useAppDesignTokens();
|
|
33
39
|
const styles = useStyles(tokens);
|
|
@@ -35,9 +41,20 @@ export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
|
|
|
35
41
|
return (
|
|
36
42
|
<View style={[styles.headerArea, style]}>
|
|
37
43
|
<View>
|
|
38
|
-
<
|
|
44
|
+
<View style={styles.titleRow}>
|
|
45
|
+
<AtomicText style={styles.title}>{title}</AtomicText>
|
|
46
|
+
{pendingCount > 0 && (
|
|
47
|
+
<View style={[styles.pendingBadge, { backgroundColor: tokens.colors.primary }]}>
|
|
48
|
+
<AtomicIcon name="Loader" size="xs" color="onPrimary" />
|
|
49
|
+
<AtomicText style={[styles.pendingBadgeText, { color: tokens.colors.onPrimary }]}>
|
|
50
|
+
{pendingCount}
|
|
51
|
+
</AtomicText>
|
|
52
|
+
</View>
|
|
53
|
+
)}
|
|
54
|
+
</View>
|
|
39
55
|
<AtomicText style={styles.subtitle}>
|
|
40
56
|
{count} {countLabel}
|
|
57
|
+
{pendingCount > 0 && pendingLabel ? ` · ${pendingCount} ${pendingLabel}` : ""}
|
|
41
58
|
</AtomicText>
|
|
42
59
|
</View>
|
|
43
60
|
{showFilter && filterButtons.length > 0 && (
|
|
@@ -83,11 +100,28 @@ const useStyles = (tokens: DesignTokens) =>
|
|
|
83
100
|
paddingVertical: tokens.spacing.sm,
|
|
84
101
|
marginBottom: tokens.spacing.sm,
|
|
85
102
|
},
|
|
103
|
+
titleRow: {
|
|
104
|
+
flexDirection: "row",
|
|
105
|
+
alignItems: "center",
|
|
106
|
+
gap: tokens.spacing.sm,
|
|
107
|
+
marginBottom: 4,
|
|
108
|
+
},
|
|
86
109
|
title: {
|
|
87
110
|
fontSize: 20,
|
|
88
111
|
fontWeight: "700",
|
|
89
112
|
color: tokens.colors.textPrimary,
|
|
90
|
-
|
|
113
|
+
},
|
|
114
|
+
pendingBadge: {
|
|
115
|
+
flexDirection: "row",
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
gap: 4,
|
|
118
|
+
paddingHorizontal: 8,
|
|
119
|
+
paddingVertical: 4,
|
|
120
|
+
borderRadius: 12,
|
|
121
|
+
},
|
|
122
|
+
pendingBadgeText: {
|
|
123
|
+
fontSize: 12,
|
|
124
|
+
fontWeight: "600",
|
|
91
125
|
},
|
|
92
126
|
subtitle: {
|
|
93
127
|
fontSize: 14,
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* SOLID: Coordinates filter hooks without business logic
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useState, useCallback } from "react";
|
|
7
|
+
import { useState, useCallback, useMemo } from "react";
|
|
8
8
|
import type { Creation } from "../../domain/entities/Creation";
|
|
9
9
|
import type { FilterOption } from "../../domain/types/creation-filter";
|
|
10
|
+
import type { BackgroundJob } from "../../../../domain/entities/job.types";
|
|
10
11
|
import { useFilter } from "./useFilter";
|
|
11
12
|
import { useCreationsFilter } from "./useCreationsFilter";
|
|
12
13
|
|
|
@@ -15,6 +16,8 @@ interface UseGalleryFiltersProps {
|
|
|
15
16
|
readonly statusOptions: FilterOption[];
|
|
16
17
|
readonly mediaOptions: FilterOption[];
|
|
17
18
|
readonly t: (key: string) => string;
|
|
19
|
+
/** Pending background jobs to include in status counts */
|
|
20
|
+
readonly pendingJobs?: BackgroundJob[];
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
interface UseGalleryFiltersReturn {
|
|
@@ -36,12 +39,30 @@ export function useGalleryFilters({
|
|
|
36
39
|
creations,
|
|
37
40
|
statusOptions,
|
|
38
41
|
mediaOptions,
|
|
39
|
-
t
|
|
42
|
+
t,
|
|
43
|
+
pendingJobs = [],
|
|
40
44
|
}: UseGalleryFiltersProps): UseGalleryFiltersReturn {
|
|
41
45
|
const [statusFilterVisible, setStatusFilterVisible] = useState(false);
|
|
42
46
|
const [mediaFilterVisible, setMediaFilterVisible] = useState(false);
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
// Calculate pending jobs count for status filter
|
|
49
|
+
const processingJobsCount = useMemo(() => {
|
|
50
|
+
return pendingJobs.filter(
|
|
51
|
+
(job) => job.status === "processing" || job.status === "queued",
|
|
52
|
+
).length;
|
|
53
|
+
}, [pendingJobs]);
|
|
54
|
+
|
|
55
|
+
// Enrich status options with dynamic counts
|
|
56
|
+
const enrichedStatusOptions = useMemo(() => {
|
|
57
|
+
return statusOptions.map((option) => {
|
|
58
|
+
if (option.id === "processing" && processingJobsCount > 0) {
|
|
59
|
+
return { ...option, count: processingJobsCount };
|
|
60
|
+
}
|
|
61
|
+
return option;
|
|
62
|
+
});
|
|
63
|
+
}, [statusOptions, processingJobsCount]);
|
|
64
|
+
|
|
65
|
+
const statusFilter = useFilter({ options: enrichedStatusOptions, t });
|
|
45
66
|
const mediaFilter = useFilter({ options: mediaOptions, t });
|
|
46
67
|
|
|
47
68
|
const { filtered, isFiltered, activeFiltersCount } = useCreationsFilter({
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { useCreations } from "../hooks/useCreations";
|
|
14
14
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
15
15
|
import { useGalleryFilters } from "../hooks/useGalleryFilters";
|
|
16
|
-
import { GalleryHeader, CreationCard, GalleryEmptyStates
|
|
16
|
+
import { GalleryHeader, CreationCard, GalleryEmptyStates } from "../components";
|
|
17
17
|
import { ResultPreviewScreen } from "../../../result-preview/presentation/components/ResultPreviewScreen";
|
|
18
18
|
import { StarRatingPicker } from "../../../result-preview/presentation/components/StarRatingPicker";
|
|
19
19
|
import { useResultActions } from "../../../result-preview/presentation/hooks/useResultActions";
|
|
@@ -33,10 +33,8 @@ interface CreationsGalleryScreenProps {
|
|
|
33
33
|
readonly onEmptyAction?: () => void;
|
|
34
34
|
readonly emptyActionLabel?: string;
|
|
35
35
|
readonly showFilter?: boolean;
|
|
36
|
-
/** Show pending generation jobs
|
|
36
|
+
/** Show pending generation jobs badge in header */
|
|
37
37
|
readonly showPendingJobs?: boolean;
|
|
38
|
-
/** Title for the pending jobs section */
|
|
39
|
-
readonly pendingJobsTitle?: string;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
export function CreationsGalleryScreen({
|
|
@@ -49,7 +47,6 @@ export function CreationsGalleryScreen({
|
|
|
49
47
|
emptyActionLabel,
|
|
50
48
|
showFilter = config.showFilter ?? true,
|
|
51
49
|
showPendingJobs = true,
|
|
52
|
-
pendingJobsTitle,
|
|
53
50
|
}: CreationsGalleryScreenProps) {
|
|
54
51
|
const tokens = useAppDesignTokens();
|
|
55
52
|
const { share } = useSharing();
|
|
@@ -61,7 +58,7 @@ export function CreationsGalleryScreen({
|
|
|
61
58
|
const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
|
|
62
59
|
|
|
63
60
|
// Background jobs for pending generations
|
|
64
|
-
const { jobs: pendingJobs
|
|
61
|
+
const { jobs: pendingJobs } = usePendingJobs();
|
|
65
62
|
|
|
66
63
|
// Auto-select creation when initialCreationId is provided
|
|
67
64
|
useEffect(() => {
|
|
@@ -91,7 +88,7 @@ export function CreationsGalleryScreen({
|
|
|
91
88
|
const showStatusFilter = config.filterConfig?.showStatusFilter ?? true;
|
|
92
89
|
const showMediaFilter = config.filterConfig?.showMediaFilter ?? true;
|
|
93
90
|
|
|
94
|
-
const filters = useGalleryFilters({ creations, statusOptions, mediaOptions, t });
|
|
91
|
+
const filters = useGalleryFilters({ creations, statusOptions, mediaOptions, t, pendingJobs });
|
|
95
92
|
|
|
96
93
|
useAppFocusEffect(useCallback(() => { void refetch(); }, [refetch]));
|
|
97
94
|
|
|
@@ -190,49 +187,30 @@ export function CreationsGalleryScreen({
|
|
|
190
187
|
/>
|
|
191
188
|
), [handleShareCard, handleDelete, handleFavorite, handleCardPress, getScenarioTitle]);
|
|
192
189
|
|
|
193
|
-
//
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
completed: t("generator.status.completed") || "Completed",
|
|
199
|
-
failed: t("generator.status.failed") || "Failed",
|
|
200
|
-
}), [t]);
|
|
190
|
+
// Calculate active pending jobs count once
|
|
191
|
+
const activePendingCount = useMemo(() => {
|
|
192
|
+
if (!showPendingJobs) return 0;
|
|
193
|
+
return pendingJobs.filter((j) => j.status === "processing" || j.status === "queued").length;
|
|
194
|
+
}, [showPendingJobs, pendingJobs]);
|
|
201
195
|
|
|
202
196
|
const renderHeader = useMemo(() => {
|
|
203
|
-
const hasPendingJobs = showPendingJobs && pendingJobs.length > 0;
|
|
204
197
|
const hasCreations = creations && creations.length > 0;
|
|
205
|
-
|
|
206
|
-
if (!hasPendingJobs && !hasCreations && !isLoading) return null;
|
|
198
|
+
if (!hasCreations && !isLoading) return null;
|
|
207
199
|
|
|
208
200
|
return (
|
|
209
|
-
<View>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{/* Gallery Header */}
|
|
222
|
-
{hasCreations && (
|
|
223
|
-
<View style={[styles.header, { backgroundColor: tokens.colors.surface, borderBottomColor: tokens.colors.border }]}>
|
|
224
|
-
<GalleryHeader
|
|
225
|
-
title={t(config.translations.title)}
|
|
226
|
-
count={filters.filtered.length}
|
|
227
|
-
countLabel={t(config.translations.photoCount)}
|
|
228
|
-
showFilter={showFilter}
|
|
229
|
-
filterButtons={filterButtons}
|
|
230
|
-
/>
|
|
231
|
-
</View>
|
|
232
|
-
)}
|
|
201
|
+
<View style={[styles.header, { backgroundColor: tokens.colors.surface, borderBottomColor: tokens.colors.border }]}>
|
|
202
|
+
<GalleryHeader
|
|
203
|
+
title={t(config.translations.title)}
|
|
204
|
+
count={filters.filtered.length}
|
|
205
|
+
countLabel={t(config.translations.photoCount)}
|
|
206
|
+
showFilter={showFilter}
|
|
207
|
+
filterButtons={filterButtons}
|
|
208
|
+
pendingCount={activePendingCount}
|
|
209
|
+
pendingLabel={t("creations.processing")}
|
|
210
|
+
/>
|
|
233
211
|
</View>
|
|
234
212
|
);
|
|
235
|
-
}, [creations, isLoading, filters.filtered.length, showFilter, filterButtons, t, config, tokens,
|
|
213
|
+
}, [creations, isLoading, filters.filtered.length, showFilter, filterButtons, t, config, tokens, activePendingCount]);
|
|
236
214
|
|
|
237
215
|
const renderEmpty = useMemo(() => (
|
|
238
216
|
<GalleryEmptyStates
|
|
@@ -30,7 +30,8 @@ export interface UseWizardGenerationProps {
|
|
|
30
30
|
readonly wizardData: Record<string, unknown>;
|
|
31
31
|
readonly userId?: string;
|
|
32
32
|
readonly isGeneratingStep: boolean;
|
|
33
|
-
|
|
33
|
+
/** Required - alert messages for error states */
|
|
34
|
+
readonly alertMessages: AlertMessages;
|
|
34
35
|
readonly onSuccess?: (result: unknown) => void;
|
|
35
36
|
readonly onError?: (error: string) => void;
|
|
36
37
|
readonly onCreditsExhausted?: () => void;
|
|
@@ -40,8 +41,6 @@ export interface UseWizardGenerationProps {
|
|
|
40
41
|
|
|
41
42
|
export interface UseWizardGenerationReturn {
|
|
42
43
|
readonly isGenerating: boolean;
|
|
43
|
-
/** Current job ID if tracking is enabled */
|
|
44
|
-
readonly currentJobId: string | null;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export const useWizardGeneration = (
|
|
@@ -69,7 +68,7 @@ export const useWizardGeneration = (
|
|
|
69
68
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
70
69
|
console.log("[useWizardGeneration] Initialized", {
|
|
71
70
|
scenarioId: scenario.id,
|
|
72
|
-
outputType: scenario.outputType
|
|
71
|
+
outputType: scenario.outputType,
|
|
73
72
|
trackAsBackgroundJob,
|
|
74
73
|
});
|
|
75
74
|
}
|
|
@@ -125,13 +124,7 @@ export const useWizardGeneration = (
|
|
|
125
124
|
strategy,
|
|
126
125
|
{
|
|
127
126
|
userId,
|
|
128
|
-
alertMessages
|
|
129
|
-
networkError: "No internet connection",
|
|
130
|
-
policyViolation: "Content policy violation",
|
|
131
|
-
saveFailed: "Failed to save",
|
|
132
|
-
creditFailed: "Failed to deduct credits",
|
|
133
|
-
unknown: "An error occurred",
|
|
134
|
-
},
|
|
127
|
+
alertMessages,
|
|
135
128
|
onCreditsExhausted,
|
|
136
129
|
onSuccess: handleSuccess,
|
|
137
130
|
onError: handleError,
|
|
@@ -158,7 +151,7 @@ export const useWizardGeneration = (
|
|
|
158
151
|
}
|
|
159
152
|
|
|
160
153
|
// Create background job for tracking
|
|
161
|
-
if (trackAsBackgroundJob) {
|
|
154
|
+
if (trackAsBackgroundJob && scenario.outputType) {
|
|
162
155
|
const jobId = `wizard-${scenario.id}-${Date.now()}`;
|
|
163
156
|
currentJobIdRef.current = jobId;
|
|
164
157
|
|
|
@@ -166,10 +159,10 @@ export const useWizardGeneration = (
|
|
|
166
159
|
id: jobId,
|
|
167
160
|
input: {
|
|
168
161
|
scenarioId: scenario.id,
|
|
169
|
-
scenarioTitle: scenario.title
|
|
170
|
-
outputType: scenario.outputType
|
|
162
|
+
scenarioTitle: scenario.title,
|
|
163
|
+
outputType: scenario.outputType,
|
|
171
164
|
},
|
|
172
|
-
type: scenario.outputType
|
|
165
|
+
type: scenario.outputType,
|
|
173
166
|
status: "processing",
|
|
174
167
|
progress: 10,
|
|
175
168
|
});
|
|
@@ -186,7 +179,7 @@ export const useWizardGeneration = (
|
|
|
186
179
|
console.error("[useWizardGeneration] Input build error:", error);
|
|
187
180
|
}
|
|
188
181
|
hasStarted.current = false;
|
|
189
|
-
onError?.(error.message
|
|
182
|
+
onError?.(error.message);
|
|
190
183
|
});
|
|
191
184
|
}
|
|
192
185
|
|
|
@@ -204,8 +197,5 @@ export const useWizardGeneration = (
|
|
|
204
197
|
addJob,
|
|
205
198
|
]);
|
|
206
199
|
|
|
207
|
-
return {
|
|
208
|
-
isGenerating,
|
|
209
|
-
currentJobId: currentJobIdRef.current,
|
|
210
|
-
};
|
|
200
|
+
return { isGenerating };
|
|
211
201
|
};
|