@umituz/react-native-ai-generation-content 1.28.8 → 1.29.0

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.28.8",
3
+ "version": "1.29.0",
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",
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Indeterminate Progress Bar Component
3
+ * Pulses left to right to indicate ongoing progress without specific percentage
4
+ * Uses setInterval + state updates (not Animated API) for compatibility
5
+ */
6
+
7
+ import React, { useState, useEffect, useRef } from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+
10
+ export interface IndeterminateProgressBarProps {
11
+ readonly backgroundColor: string;
12
+ readonly fillColor: string;
13
+ }
14
+
15
+ export const IndeterminateProgressBar: React.FC<IndeterminateProgressBarProps> = ({
16
+ backgroundColor,
17
+ fillColor,
18
+ }) => {
19
+ const [position, setPosition] = useState(0);
20
+ const directionRef = useRef(1);
21
+
22
+ useEffect(() => {
23
+ const interval = setInterval(() => {
24
+ setPosition((prev) => {
25
+ const next = prev + directionRef.current * 2;
26
+ if (next >= 70) {
27
+ directionRef.current = -1;
28
+ return 70;
29
+ }
30
+ if (next <= 0) {
31
+ directionRef.current = 1;
32
+ return 0;
33
+ }
34
+ return next;
35
+ });
36
+ }, 30);
37
+
38
+ return () => clearInterval(interval);
39
+ }, []);
40
+
41
+ return (
42
+ <View style={[styles.progressBar, { backgroundColor }]}>
43
+ <View
44
+ style={[
45
+ styles.progressFill,
46
+ {
47
+ backgroundColor: fillColor,
48
+ left: `${position}%`,
49
+ },
50
+ ]}
51
+ />
52
+ </View>
53
+ );
54
+ };
55
+
56
+ const styles = StyleSheet.create({
57
+ progressBar: {
58
+ height: 8,
59
+ borderRadius: 4,
60
+ overflow: "hidden",
61
+ position: "relative",
62
+ },
63
+ progressFill: {
64
+ height: "100%",
65
+ borderRadius: 4,
66
+ position: "absolute",
67
+ width: "30%",
68
+ },
69
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * useGenerationPhase Hook
3
+ * Derives generation phase from elapsed time for UX feedback
4
+ *
5
+ * Best Practice: Since actual API progress is unknown (FAL only returns IN_QUEUE/IN_PROGRESS),
6
+ * we use time-based phases to provide meaningful feedback to users.
7
+ */
8
+
9
+ import { useState, useEffect, useRef } from "react";
10
+
11
+ export type GenerationPhase = "queued" | "processing" | "finalizing";
12
+
13
+ interface UseGenerationPhaseOptions {
14
+ /** Time in ms before transitioning from "queued" to "processing" (default: 5000) */
15
+ readonly queuedDuration?: number;
16
+ }
17
+
18
+ /**
19
+ * Hook to derive generation phase from elapsed time
20
+ * - 0-5s: "queued" (Waiting in queue...)
21
+ * - 5s+: "processing" (Generating your content...)
22
+ *
23
+ * Note: "finalizing" phase is not time-based, it should be set when
24
+ * the API reports completion (not implemented here as it requires status callback)
25
+ */
26
+ export function useGenerationPhase(options?: UseGenerationPhaseOptions): GenerationPhase {
27
+ const { queuedDuration = 5000 } = options ?? {};
28
+
29
+ const [phase, setPhase] = useState<GenerationPhase>("queued");
30
+ const startTimeRef = useRef(Date.now());
31
+
32
+ useEffect(() => {
33
+ const interval = setInterval(() => {
34
+ const elapsed = Date.now() - startTimeRef.current;
35
+
36
+ if (elapsed < queuedDuration) {
37
+ setPhase("queued");
38
+ } else {
39
+ setPhase("processing");
40
+ }
41
+ }, 1000);
42
+
43
+ return () => clearInterval(interval);
44
+ }, [queuedDuration]);
45
+
46
+ return phase;
47
+ }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Generic Generating Screen
3
- * Shows progress while AI generates content
4
- * Uses scenario-specific messages with fallback to generic messages
3
+ * Shows indeterminate progress while AI generates content
4
+ * Uses status messages instead of fake percentages (UX best practice)
5
5
  */
6
6
 
7
7
  import React, { useMemo } from "react";
8
8
  import { View, StyleSheet, ActivityIndicator } from "react-native";
9
9
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
10
+ import { useGenerationPhase } from "../hooks/useGenerationPhase";
11
+ import { IndeterminateProgressBar } from "../components/IndeterminateProgressBar";
10
12
 
11
13
  export interface GeneratingScreenProps {
12
14
  readonly progress: number;
@@ -25,16 +27,17 @@ export interface GeneratingScreenProps {
25
27
  }
26
28
 
27
29
  export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
28
- progress,
30
+ progress: _progress,
29
31
  scenario,
30
32
  t,
31
33
  onCancel: _onCancel,
32
34
  }) => {
33
35
  const tokens = useAppDesignTokens();
36
+ const phase = useGenerationPhase();
34
37
 
35
38
  if (typeof __DEV__ !== "undefined" && __DEV__) {
36
39
  console.log("[GeneratingScreen] Rendering", {
37
- progress,
40
+ phase,
38
41
  scenarioId: scenario?.id,
39
42
  });
40
43
  }
@@ -48,6 +51,19 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
48
51
  };
49
52
  }, [scenario, t]);
50
53
 
54
+ const statusMessage = useMemo(() => {
55
+ switch (phase) {
56
+ case "queued":
57
+ return t("generator.status.queued") || "Waiting in queue...";
58
+ case "processing":
59
+ return t("generator.status.processing") || "Generating your content...";
60
+ case "finalizing":
61
+ return t("generator.status.finalizing") || "Almost done...";
62
+ default:
63
+ return messages.waitMessage;
64
+ }
65
+ }, [phase, t, messages.waitMessage]);
66
+
51
67
  return (
52
68
  <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
53
69
  <View style={styles.content}>
@@ -58,35 +74,22 @@ export const GeneratingScreen: React.FC<GeneratingScreenProps> = ({
58
74
  </AtomicText>
59
75
 
60
76
  <AtomicText type="bodyMedium" style={[styles.message, { color: tokens.colors.textSecondary }]}>
61
- {messages.waitMessage}
77
+ {statusMessage}
62
78
  </AtomicText>
63
79
 
64
- {/* Progress Bar */}
65
80
  <View style={styles.progressContainer}>
66
- <View style={[styles.progressBar, { backgroundColor: tokens.colors.surfaceVariant }]}>
67
- <View
68
- style={[
69
- styles.progressFill,
70
- {
71
- backgroundColor: tokens.colors.primary,
72
- width: `${Math.min(100, Math.max(0, progress))}%`,
73
- },
74
- ]}
75
- />
76
- </View>
77
- <AtomicText type="bodySmall" style={[styles.progressText, { color: tokens.colors.textSecondary }]}>
78
- {Math.round(progress)}%
79
- </AtomicText>
81
+ <IndeterminateProgressBar
82
+ backgroundColor={tokens.colors.surfaceVariant}
83
+ fillColor={tokens.colors.primary}
84
+ />
80
85
  </View>
81
86
 
82
- {/* Scenario Info */}
83
87
  {scenario && (
84
88
  <AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
85
89
  {scenario.title || scenario.id}
86
90
  </AtomicText>
87
91
  )}
88
92
 
89
- {/* Hint */}
90
93
  <AtomicText type="bodySmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
91
94
  {messages.hint}
92
95
  </AtomicText>
@@ -117,19 +120,6 @@ const styles = StyleSheet.create({
117
120
  progressContainer: {
118
121
  width: "100%",
119
122
  marginTop: 24,
120
- gap: 8,
121
- },
122
- progressBar: {
123
- height: 8,
124
- borderRadius: 4,
125
- overflow: "hidden",
126
- },
127
- progressFill: {
128
- height: "100%",
129
- borderRadius: 4,
130
- },
131
- progressText: {
132
- textAlign: "center",
133
123
  },
134
124
  hint: {
135
125
  textAlign: "center",
@@ -17,21 +17,6 @@ import type {
17
17
 
18
18
  declare const __DEV__: boolean;
19
19
 
20
- /**
21
- * Calculate progress based on elapsed time
22
- * Video generation typically takes 2-5 minutes
23
- * Progress goes from 10% to 90% over the expected duration
24
- */
25
- function calculateTimeBasedProgress(startTime: number, expectedDurationMs: number = 180000): number {
26
- const elapsed = Date.now() - startTime;
27
- const progressRange = 80; // Progress from 10% to 90%
28
- const baseProgress = 10;
29
-
30
- // Calculate progress based on elapsed time (capped at 90%)
31
- const timeProgress = Math.min((elapsed / expectedDurationMs) * progressRange, progressRange);
32
- return Math.round(baseProgress + timeProgress);
33
- }
34
-
35
20
  /**
36
21
  * Execute any video feature using the active provider
37
22
  * Uses subscribe for video features to handle long-running generation with progress updates
@@ -69,9 +54,6 @@ export async function executeVideoFeature(
69
54
 
70
55
  const input = provider.buildVideoFeatureInput(featureType, inputData);
71
56
 
72
- // Track start time for progress calculation
73
- const startTime = Date.now();
74
-
75
57
  const result = await provider.subscribe(model, input, {
76
58
  timeoutMs: VIDEO_TIMEOUT_MS,
77
59
  onQueueUpdate: (status) => {
@@ -79,12 +61,6 @@ export async function executeVideoFeature(
79
61
  console.log(`[Video:${featureType}] Queue status:`, status.status);
80
62
  }
81
63
  onStatusChange?.(status.status);
82
-
83
- // Calculate and report progress based on elapsed time
84
- if (status.status === "IN_PROGRESS" && onProgress) {
85
- const progress = calculateTimeBasedProgress(startTime);
86
- onProgress(progress);
87
- }
88
64
  },
89
65
  });
90
66