@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.35.6",
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") || "No results"}
106
- description={t("common.no_results_description") || "Try changing your filters"}
107
- actionLabel={t("common.clear_all") || "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
- <AtomicText style={styles.title}>{title}</AtomicText>
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
- marginBottom: 4,
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
- const statusFilter = useFilter({ options: statusOptions, t });
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, PendingJobsSection } from "../components";
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 at the top */
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, removeJob } = usePendingJobs();
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
- // Status labels for pending jobs
194
- const pendingJobStatusLabels = useMemo(() => ({
195
- queued: t("generator.status.queued") || "Waiting in queue...",
196
- processing: t("generator.status.processing") || "Processing...",
197
- uploading: t("generator.status.uploading") || "Uploading...",
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
- {/* Pending Jobs Section */}
211
- {showPendingJobs && (
212
- <PendingJobsSection
213
- jobs={pendingJobs}
214
- onCancel={removeJob}
215
- title={pendingJobsTitle || t("creations.pendingJobs") || "Processing..."}
216
- statusLabels={pendingJobStatusLabels}
217
- getTypeLabel={getScenarioTitle}
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, showPendingJobs, pendingJobs, removeJob, pendingJobsTitle, pendingJobStatusLabels, getScenarioTitle]);
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
- readonly alertMessages?: AlertMessages;
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 || "video",
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: 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 || scenario.id,
170
- outputType: scenario.outputType || "video",
162
+ scenarioTitle: scenario.title,
163
+ outputType: scenario.outputType,
171
164
  },
172
- type: scenario.outputType || "video",
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 || "Failed to prepare generation");
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
  };