@umituz/react-native-ai-generation-content 1.28.7 → 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.
|
|
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
|
|
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
|
-
|
|
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
|
-
{
|
|
77
|
+
{statusMessage}
|
|
62
78
|
</AtomicText>
|
|
63
79
|
|
|
64
|
-
{/* Progress Bar */}
|
|
65
80
|
<View style={styles.progressContainer}>
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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",
|