@umituz/react-native-ai-generation-content 1.17.109 → 1.17.112

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.17.109",
3
+ "version": "1.17.112",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -23,6 +23,8 @@ export class CreationsWriter {
23
23
  metadata: creation.metadata || {},
24
24
  isShared: creation.isShared || false,
25
25
  isFavorite: creation.isFavorite || false,
26
+ status: creation.status,
27
+ output: creation.output,
26
28
  };
27
29
 
28
30
  await setDoc(docRef, data);
@@ -59,6 +61,12 @@ export class CreationsWriter {
59
61
  if (updates.prompt !== undefined) {
60
62
  updateData.prompt = updates.prompt;
61
63
  }
64
+ if (updates.status !== undefined) {
65
+ updateData.status = updates.status;
66
+ }
67
+ if (updates.output !== undefined) {
68
+ updateData.output = updates.output;
69
+ }
62
70
 
63
71
  await updateDoc(docRef, updateData);
64
72
  return true;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * CreationImagePreview Component
3
+ * Displays image preview with loading/placeholder states
4
+ */
5
+
6
+ import React, { useMemo, useState } from "react";
7
+ import { View, StyleSheet, Image, ImageErrorEventData, NativeSyntheticEvent } from "react-native";
8
+ import {
9
+ useAppDesignTokens,
10
+ AtomicIcon,
11
+ AtomicSpinner,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { CreationStatus, CreationTypeId } from "../../domain/types";
14
+ import { isInProgress, getTypeIcon } from "../../domain/utils";
15
+
16
+ export interface CreationImagePreviewProps {
17
+ /** Preview image URL */
18
+ readonly uri?: string | null;
19
+ /** Creation status */
20
+ readonly status?: CreationStatus;
21
+ /** Creation type for placeholder icon */
22
+ readonly type?: CreationTypeId;
23
+ /** Aspect ratio (default: 16/9) */
24
+ readonly aspectRatio?: number;
25
+ /** Custom height (overrides aspectRatio) */
26
+ readonly height?: number;
27
+ /** Show loading indicator when in progress */
28
+ readonly showLoadingIndicator?: boolean;
29
+ }
30
+
31
+ export function CreationImagePreview({
32
+ uri,
33
+ status = "completed",
34
+ type = "text-to-image",
35
+ aspectRatio = 16 / 9,
36
+ height,
37
+ showLoadingIndicator = true,
38
+ }: CreationImagePreviewProps) {
39
+ const tokens = useAppDesignTokens();
40
+ const inProgress = isInProgress(status);
41
+ const typeIcon = getTypeIcon(type);
42
+ const [imageError, setImageError] = useState(false);
43
+
44
+ const hasPreview = !!uri && !inProgress && !imageError;
45
+
46
+ const handleImageError = (_error: NativeSyntheticEvent<ImageErrorEventData>) => {
47
+ setImageError(true);
48
+ };
49
+
50
+ const styles = useMemo(
51
+ () =>
52
+ StyleSheet.create({
53
+ container: {
54
+ width: "100%",
55
+ aspectRatio: height ? undefined : aspectRatio,
56
+ height: height,
57
+ backgroundColor: tokens.colors.backgroundSecondary,
58
+ position: "relative",
59
+ overflow: "hidden",
60
+ },
61
+ image: {
62
+ width: "100%",
63
+ height: "100%",
64
+ },
65
+ placeholder: {
66
+ width: "100%",
67
+ height: "100%",
68
+ justifyContent: "center",
69
+ alignItems: "center",
70
+ },
71
+ loadingContainer: {
72
+ width: "100%",
73
+ height: "100%",
74
+ justifyContent: "center",
75
+ alignItems: "center",
76
+ },
77
+ loadingIcon: {
78
+ width: 64,
79
+ height: 64,
80
+ borderRadius: 32,
81
+ backgroundColor: tokens.colors.primary + "20",
82
+ justifyContent: "center",
83
+ alignItems: "center",
84
+ },
85
+ }),
86
+ [tokens, aspectRatio, height]
87
+ );
88
+
89
+ // Show loading state
90
+ if (inProgress && showLoadingIndicator) {
91
+ return (
92
+ <View style={styles.container}>
93
+ <View style={styles.loadingContainer}>
94
+ <View style={styles.loadingIcon}>
95
+ <AtomicSpinner size="lg" color="primary" />
96
+ </View>
97
+ </View>
98
+ </View>
99
+ );
100
+ }
101
+
102
+ // Show image preview
103
+ if (hasPreview) {
104
+ return (
105
+ <View style={styles.container}>
106
+ <Image
107
+ source={{ uri }}
108
+ style={styles.image}
109
+ resizeMode="cover"
110
+ onError={handleImageError}
111
+ />
112
+ </View>
113
+ );
114
+ }
115
+
116
+ // Show placeholder
117
+ return (
118
+ <View style={styles.container}>
119
+ <View style={styles.placeholder}>
120
+ <AtomicIcon name={typeIcon} color="secondary" size="xl" />
121
+ </View>
122
+ </View>
123
+ );
124
+ }
@@ -1,26 +1,31 @@
1
1
  /**
2
2
  * CreationPreview Component
3
- * Displays creation preview image with loading/placeholder states
3
+ * Smart wrapper that delegates to CreationImagePreview or CreationVideoPreview
4
+ * based on creation type
4
5
  */
5
6
 
6
- import React, { useMemo } from "react";
7
- import { View, StyleSheet, Image } from "react-native";
8
- import {
9
- useAppDesignTokens,
10
- AtomicIcon,
11
- AtomicSpinner,
12
- } from "@umituz/react-native-design-system";
7
+ import React from "react";
13
8
  import type { CreationStatus, CreationTypeId } from "../../domain/types";
14
- import { isInProgress } from "../../domain/utils";
15
- import { getTypeIcon } from "../../domain/utils";
9
+ import { CreationImagePreview } from "./CreationImagePreview";
10
+ import { CreationVideoPreview } from "./CreationVideoPreview";
11
+
12
+ /** Video creation types */
13
+ const VIDEO_TYPES: CreationTypeId[] = ["text-to-video", "image-to-video"];
14
+
15
+ /** Check if creation type is a video type */
16
+ function isVideoType(type?: CreationTypeId | string): boolean {
17
+ return VIDEO_TYPES.includes(type as CreationTypeId);
18
+ }
16
19
 
17
20
  interface CreationPreviewProps {
18
- /** Preview image URL */
21
+ /** Preview image/thumbnail URL */
19
22
  readonly uri?: string | null;
23
+ /** Thumbnail URL for videos (optional, if different from uri) */
24
+ readonly thumbnailUrl?: string | null;
20
25
  /** Creation status */
21
26
  readonly status?: CreationStatus;
22
- /** Creation type for placeholder icon */
23
- readonly type?: CreationTypeId;
27
+ /** Creation type for determining preview type */
28
+ readonly type?: CreationTypeId | string;
24
29
  /** Aspect ratio (default: 16/9) */
25
30
  readonly aspectRatio?: number;
26
31
  /** Custom height (overrides aspectRatio) */
@@ -31,88 +36,37 @@ interface CreationPreviewProps {
31
36
 
32
37
  export function CreationPreview({
33
38
  uri,
39
+ thumbnailUrl,
34
40
  status = "completed",
35
41
  type = "text-to-image",
36
42
  aspectRatio = 16 / 9,
37
43
  height,
38
44
  showLoadingIndicator = true,
39
45
  }: CreationPreviewProps) {
40
- const tokens = useAppDesignTokens();
41
- const inProgress = isInProgress(status);
42
- const typeIcon = getTypeIcon(type);
43
- const hasPreview = !!uri && !inProgress;
44
-
45
- const styles = useMemo(
46
- () =>
47
- StyleSheet.create({
48
- container: {
49
- width: "100%",
50
- aspectRatio: height ? undefined : aspectRatio,
51
- height: height,
52
- backgroundColor: tokens.colors.backgroundSecondary,
53
- position: "relative",
54
- overflow: "hidden",
55
- },
56
- image: {
57
- width: "100%",
58
- height: "100%",
59
- },
60
- placeholder: {
61
- width: "100%",
62
- height: "100%",
63
- justifyContent: "center",
64
- alignItems: "center",
65
- },
66
- loadingContainer: {
67
- width: "100%",
68
- height: "100%",
69
- justifyContent: "center",
70
- alignItems: "center",
71
- },
72
- loadingIcon: {
73
- width: 64,
74
- height: 64,
75
- borderRadius: 32,
76
- backgroundColor: tokens.colors.primary + "20",
77
- justifyContent: "center",
78
- alignItems: "center",
79
- },
80
- }),
81
- [tokens, aspectRatio, height]
82
- );
83
-
84
- // Show loading state
85
- if (inProgress && showLoadingIndicator) {
86
- return (
87
- <View style={styles.container}>
88
- <View style={styles.loadingContainer}>
89
- <View style={styles.loadingIcon}>
90
- <AtomicSpinner size="lg" color="primary" />
91
- </View>
92
- </View>
93
- </View>
94
- );
95
- }
96
-
97
- // Show image preview
98
- if (hasPreview) {
46
+ // For video types, use CreationVideoPreview
47
+ if (isVideoType(type)) {
99
48
  return (
100
- <View style={styles.container}>
101
- <Image
102
- source={{ uri }}
103
- style={styles.image}
104
- resizeMode="cover"
105
- />
106
- </View>
49
+ <CreationVideoPreview
50
+ thumbnailUrl={thumbnailUrl || uri}
51
+ videoUrl={uri}
52
+ status={status}
53
+ type={type as CreationTypeId}
54
+ aspectRatio={aspectRatio}
55
+ height={height}
56
+ showLoadingIndicator={showLoadingIndicator}
57
+ />
107
58
  );
108
59
  }
109
60
 
110
- // Show placeholder
61
+ // For image types, use CreationImagePreview
111
62
  return (
112
- <View style={styles.container}>
113
- <View style={styles.placeholder}>
114
- <AtomicIcon name={typeIcon} color="secondary" size="xl" />
115
- </View>
116
- </View>
63
+ <CreationImagePreview
64
+ uri={uri}
65
+ status={status}
66
+ type={type as CreationTypeId}
67
+ aspectRatio={aspectRatio}
68
+ height={height}
69
+ showLoadingIndicator={showLoadingIndicator}
70
+ />
117
71
  );
118
72
  }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * CreationVideoPreview Component
3
+ * Displays video preview with thumbnail and play icon overlay
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, StyleSheet, Image } from "react-native";
8
+ import {
9
+ useAppDesignTokens,
10
+ AtomicIcon,
11
+ AtomicSpinner,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { CreationStatus, CreationTypeId } from "../../domain/types";
14
+ import { isInProgress } from "../../domain/utils";
15
+
16
+ export interface CreationVideoPreviewProps {
17
+ /** Thumbnail image URL (optional) */
18
+ readonly thumbnailUrl?: string | null;
19
+ /** Video URL (for display purposes only) */
20
+ readonly videoUrl?: string | null;
21
+ /** Creation status */
22
+ readonly status?: CreationStatus;
23
+ /** Creation type for placeholder icon */
24
+ readonly type?: CreationTypeId;
25
+ /** Aspect ratio (default: 16/9) */
26
+ readonly aspectRatio?: number;
27
+ /** Custom height (overrides aspectRatio) */
28
+ readonly height?: number;
29
+ /** Show loading indicator when in progress */
30
+ readonly showLoadingIndicator?: boolean;
31
+ }
32
+
33
+ /** Check if URL is a video URL (mp4, mov, etc.) */
34
+ function isVideoUrl(url?: string | null): boolean {
35
+ if (!url) return false;
36
+ const videoExtensions = [".mp4", ".mov", ".avi", ".webm", ".mkv"];
37
+ const lowerUrl = url.toLowerCase();
38
+ return videoExtensions.some((ext) => lowerUrl.includes(ext));
39
+ }
40
+
41
+ export function CreationVideoPreview({
42
+ thumbnailUrl,
43
+ videoUrl,
44
+ status = "completed",
45
+ aspectRatio = 16 / 9,
46
+ height,
47
+ showLoadingIndicator = true,
48
+ }: CreationVideoPreviewProps) {
49
+ const tokens = useAppDesignTokens();
50
+ const inProgress = isInProgress(status);
51
+
52
+ // Only use thumbnail if it's a real image URL, not a video URL
53
+ const hasThumbnail = !!thumbnailUrl && !inProgress && !isVideoUrl(thumbnailUrl);
54
+
55
+ const styles = useMemo(
56
+ () =>
57
+ StyleSheet.create({
58
+ container: {
59
+ width: "100%",
60
+ aspectRatio: height ? undefined : aspectRatio,
61
+ height: height,
62
+ backgroundColor: tokens.colors.backgroundSecondary,
63
+ position: "relative",
64
+ overflow: "hidden",
65
+ },
66
+ thumbnail: {
67
+ width: "100%",
68
+ height: "100%",
69
+ },
70
+ placeholder: {
71
+ width: "100%",
72
+ height: "100%",
73
+ justifyContent: "center",
74
+ alignItems: "center",
75
+ },
76
+ loadingContainer: {
77
+ width: "100%",
78
+ height: "100%",
79
+ justifyContent: "center",
80
+ alignItems: "center",
81
+ },
82
+ loadingIcon: {
83
+ width: 64,
84
+ height: 64,
85
+ borderRadius: 32,
86
+ backgroundColor: tokens.colors.primary + "20",
87
+ justifyContent: "center",
88
+ alignItems: "center",
89
+ },
90
+ playIconOverlay: {
91
+ ...StyleSheet.absoluteFillObject,
92
+ justifyContent: "center",
93
+ alignItems: "center",
94
+ },
95
+ playIconContainer: {
96
+ width: 56,
97
+ height: 56,
98
+ borderRadius: 28,
99
+ backgroundColor: tokens.colors.primary,
100
+ justifyContent: "center",
101
+ alignItems: "center",
102
+ paddingLeft: 4,
103
+ },
104
+ }),
105
+ [tokens, aspectRatio, height]
106
+ );
107
+
108
+ // Show loading state
109
+ if (inProgress && showLoadingIndicator) {
110
+ return (
111
+ <View style={styles.container}>
112
+ <View style={styles.loadingContainer}>
113
+ <View style={styles.loadingIcon}>
114
+ <AtomicSpinner size="lg" color="primary" />
115
+ </View>
116
+ </View>
117
+ </View>
118
+ );
119
+ }
120
+
121
+ // Show thumbnail with play icon overlay
122
+ if (hasThumbnail) {
123
+ return (
124
+ <View style={styles.container}>
125
+ <Image
126
+ source={{ uri: thumbnailUrl }}
127
+ style={styles.thumbnail}
128
+ resizeMode="cover"
129
+ />
130
+ <View style={styles.playIconOverlay}>
131
+ <View style={styles.playIconContainer}>
132
+ <AtomicIcon name="play" customSize={24} color="onPrimary" />
133
+ </View>
134
+ </View>
135
+ </View>
136
+ );
137
+ }
138
+
139
+ // Show placeholder with play icon (no thumbnail available)
140
+ return (
141
+ <View style={styles.container}>
142
+ <View style={styles.placeholder}>
143
+ <View style={styles.playIconContainer}>
144
+ <AtomicIcon name="play" customSize={24} color="onPrimary" />
145
+ </View>
146
+ </View>
147
+ </View>
148
+ );
149
+ }
@@ -4,6 +4,8 @@
4
4
 
5
5
  // Core Components
6
6
  export { CreationPreview } from "./CreationPreview";
7
+ export { CreationImagePreview, type CreationImagePreviewProps } from "./CreationImagePreview";
8
+ export { CreationVideoPreview, type CreationVideoPreviewProps } from "./CreationVideoPreview";
7
9
  export { CreationBadges } from "./CreationBadges";
8
10
  export { CreationActions, type CreationAction } from "./CreationActions";
9
11
  export {
@@ -27,12 +27,22 @@ export interface CreationData {
27
27
  metadata?: Record<string, unknown>;
28
28
  }
29
29
 
30
+ export interface GenerationStartData {
31
+ creationId: string;
32
+ type: "text-to-video";
33
+ prompt: string;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+
30
37
  export interface TextToVideoCallbacks {
31
38
  onCreditCheck?: (cost: number) => Promise<boolean>;
32
39
  onCreditDeduct?: (cost: number) => Promise<void>;
33
40
  onAuthCheck?: () => boolean;
34
41
  onModeration?: (prompt: string) => Promise<VideoModerationResult>;
35
- onCreationSave?: (data: CreationData) => Promise<void>;
42
+ /** Called when generation starts - save a "processing" creation */
43
+ onGenerationStart?: (data: GenerationStartData) => Promise<void>;
44
+ /** Called when generation completes - update creation to "completed" */
45
+ onCreationSave?: (data: CreationData & { creationId: string }) => Promise<void>;
36
46
  onGenerate?: (result: TextToVideoResult) => void;
37
47
  onError?: (error: string) => void;
38
48
  onProgress?: (progress: number) => void;
@@ -35,6 +35,7 @@ export type {
35
35
  VideoModerationResult,
36
36
  ProjectData,
37
37
  CreationData,
38
+ GenerationStartData,
38
39
  TextToVideoCallbacks,
39
40
  TextToVideoTranslations,
40
41
  } from "./callback.types";
@@ -27,6 +27,7 @@ export type {
27
27
  VideoModerationResult,
28
28
  ProjectData,
29
29
  CreationData,
30
+ GenerationStartData,
30
31
  GenerationTabsProps,
31
32
  FrameSelectorProps,
32
33
  OptionsPanelProps,
@@ -124,6 +124,9 @@ export function useTextToVideoFeature(
124
124
 
125
125
  const executeGeneration = useCallback(
126
126
  async (prompt: string, options?: TextToVideoOptions): Promise<TextToVideoResult> => {
127
+ // Generate unique creation ID for tracking
128
+ const creationId = `text-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
129
+
127
130
  setState((prev) => ({
128
131
  ...prev,
129
132
  isProcessing: true,
@@ -133,7 +136,17 @@ export function useTextToVideoFeature(
133
136
 
134
137
  if (typeof __DEV__ !== "undefined" && __DEV__) {
135
138
  // eslint-disable-next-line no-console
136
- console.log("[TextToVideoFeature] Starting generation with prompt:", prompt);
139
+ console.log("[TextToVideoFeature] Starting generation with prompt:", prompt, "creationId:", creationId);
140
+ }
141
+
142
+ // Create "processing" creation at start
143
+ if (callbacks.onGenerationStart) {
144
+ await callbacks.onGenerationStart({
145
+ creationId,
146
+ type: "text-to-video",
147
+ prompt,
148
+ metadata: options as Record<string, unknown> | undefined,
149
+ });
137
150
  }
138
151
 
139
152
  const result = await executeTextToVideo(
@@ -163,9 +176,10 @@ export function useTextToVideoFeature(
163
176
  await callbacks.onCreditDeduct(config.creditCost);
164
177
  }
165
178
 
166
- // Save creation after successful generation
179
+ // Update creation to completed after successful generation
167
180
  if (callbacks.onCreationSave) {
168
181
  await callbacks.onCreationSave({
182
+ creationId,
169
183
  type: "text-to-video",
170
184
  videoUrl: result.videoUrl,
171
185
  thumbnailUrl: result.thumbnailUrl,
@@ -37,6 +37,8 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
37
37
  hideGenerateButton,
38
38
  progress,
39
39
  progressIcon,
40
+ isProgressModalVisible,
41
+ onCloseProgressModal,
40
42
  generateButtonProps,
41
43
  showAdvanced,
42
44
  onAdvancedToggle,
@@ -158,11 +160,13 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
158
160
 
159
161
  {/* MANDATORY: Progress Modal shows automatically when isGenerating */}
160
162
  <GenerationProgressModal
161
- visible={isGenerating}
163
+ visible={isProgressModalVisible ?? isGenerating}
162
164
  progress={progress ?? 0}
163
165
  icon={progressIcon || "sparkles-outline"}
164
166
  title={translations.progressTitle || translations.generatingButton}
165
167
  message={translations.progressMessage || translations.progressHint}
168
+ onClose={onCloseProgressModal}
169
+ backgroundHint={onCloseProgressModal ? translations.progressBackgroundHint : undefined}
166
170
  />
167
171
  </>
168
172
  );
@@ -18,6 +18,8 @@ export interface AIGenerationFormTranslations {
18
18
  progressTitle?: string;
19
19
  progressMessage?: string;
20
20
  progressHint?: string;
21
+ /** Hint for background generation (e.g., "Continue in background") */
22
+ progressBackgroundHint?: string;
21
23
  presetsTitle?: string;
22
24
  showAdvancedLabel?: string;
23
25
  hideAdvancedLabel?: string;
@@ -57,6 +59,10 @@ export interface AIGenerationFormProps extends PropsWithChildren {
57
59
  // Optional: Generation Progress
58
60
  progress?: number;
59
61
  progressIcon?: string;
62
+ /** Override modal visibility (defaults to isGenerating) */
63
+ isProgressModalVisible?: boolean;
64
+ /** Callback when user closes the progress modal (for background generation) */
65
+ onCloseProgressModal?: () => void;
60
66
 
61
67
  // Custom Generate Button Props
62
68
  generateButtonProps?: {
@@ -21,6 +21,10 @@ export interface GenerationProgressContentProps {
21
21
  readonly hint?: string;
22
22
  readonly dismissLabel?: string;
23
23
  readonly onDismiss?: () => void;
24
+ /** Close button in top-right corner for background generation */
25
+ readonly onClose?: () => void;
26
+ /** Hint text shown near close button (e.g., "Continue in background") */
27
+ readonly backgroundHint?: string;
24
28
  readonly backgroundColor?: string;
25
29
  readonly textColor?: string;
26
30
  readonly hintColor?: string;
@@ -39,6 +43,8 @@ export const GenerationProgressContent: React.FC<
39
43
  hint,
40
44
  dismissLabel,
41
45
  onDismiss,
46
+ onClose,
47
+ backgroundHint,
42
48
  backgroundColor,
43
49
  textColor,
44
50
  hintColor,
@@ -62,6 +68,17 @@ export const GenerationProgressContent: React.FC<
62
68
  },
63
69
  ]}
64
70
  >
71
+ {/* Close button in top-right corner */}
72
+ {onClose && (
73
+ <TouchableOpacity
74
+ style={styles.closeButton}
75
+ onPress={onClose}
76
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
77
+ >
78
+ <AtomicIcon name="close" size="md" color="secondary" />
79
+ </TouchableOpacity>
80
+ )}
81
+
65
82
  {icon && (
66
83
  <View style={styles.iconContainer}>
67
84
  <AtomicIcon name={icon} size="xl" color="primary" />
@@ -102,6 +119,21 @@ export const GenerationProgressContent: React.FC<
102
119
  </AtomicText>
103
120
  )}
104
121
 
122
+ {/* Background hint - clickable to close and continue in background */}
123
+ {onClose && backgroundHint && (
124
+ <TouchableOpacity
125
+ style={styles.backgroundHintButton}
126
+ onPress={onClose}
127
+ >
128
+ <AtomicText
129
+ type="bodySmall"
130
+ style={[styles.backgroundHintText, { color: tokens.colors.primary }]}
131
+ >
132
+ {backgroundHint}
133
+ </AtomicText>
134
+ </TouchableOpacity>
135
+ )}
136
+
105
137
  {onDismiss && (
106
138
  <TouchableOpacity
107
139
  style={[
@@ -130,6 +162,18 @@ const styles = StyleSheet.create({
130
162
  padding: 32,
131
163
  borderWidth: 1,
132
164
  alignItems: "center",
165
+ position: "relative",
166
+ },
167
+ closeButton: {
168
+ position: "absolute",
169
+ top: 16,
170
+ right: 16,
171
+ width: 32,
172
+ height: 32,
173
+ borderRadius: 16,
174
+ justifyContent: "center",
175
+ alignItems: "center",
176
+ zIndex: 1,
133
177
  },
134
178
  iconContainer: {
135
179
  marginBottom: 20,
@@ -149,6 +193,15 @@ const styles = StyleSheet.create({
149
193
  lineHeight: 18,
150
194
  paddingHorizontal: 8,
151
195
  },
196
+ backgroundHintButton: {
197
+ marginTop: 16,
198
+ paddingVertical: 8,
199
+ paddingHorizontal: 16,
200
+ },
201
+ backgroundHintText: {
202
+ textAlign: "center",
203
+ textDecorationLine: "underline",
204
+ },
152
205
  dismissButton: {
153
206
  marginTop: 16,
154
207
  paddingVertical: 14,
@@ -22,6 +22,7 @@ export interface GenerationProgressRenderProps {
22
22
  readonly message?: string;
23
23
  readonly hint?: string;
24
24
  readonly onDismiss?: () => void;
25
+ readonly onClose?: () => void;
25
26
  }
26
27
 
27
28
  export interface GenerationProgressModalProps
@@ -44,6 +45,8 @@ export const GenerationProgressModal: React.FC<
44
45
  hint,
45
46
  dismissLabel,
46
47
  onDismiss,
48
+ onClose,
49
+ backgroundHint,
47
50
  modalBackgroundColor,
48
51
  textColor,
49
52
  hintColor,
@@ -67,6 +70,7 @@ export const GenerationProgressModal: React.FC<
67
70
  message,
68
71
  hint,
69
72
  onDismiss,
73
+ onClose,
70
74
  })
71
75
  ) : (
72
76
  <GenerationProgressContent
@@ -77,6 +81,8 @@ export const GenerationProgressModal: React.FC<
77
81
  hint={hint}
78
82
  dismissLabel={dismissLabel}
79
83
  onDismiss={onDismiss}
84
+ onClose={onClose}
85
+ backgroundHint={backgroundHint}
80
86
  backgroundColor={modalBackgroundColor || tokens.colors.surface}
81
87
  textColor={textColor || tokens.colors.textPrimary}
82
88
  hintColor={hintColor || tokens.colors.textTertiary}