@umituz/react-native-ai-generation-content 1.20.20 → 1.20.22

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.20.20",
3
+ "version": "1.20.22",
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",
@@ -78,7 +78,7 @@ export const StarRatingPicker: React.FC<StarRatingPickerProps> = ({
78
78
  minHeight: 80,
79
79
  borderWidth: 1,
80
80
  borderColor: tokens.colors.border,
81
- borderRadius: tokens.shapes.radius.md,
81
+ borderRadius: tokens.radius.md,
82
82
  padding: tokens.spacing.md,
83
83
  color: tokens.colors.textPrimary,
84
84
  backgroundColor: tokens.colors.background,
@@ -33,6 +33,7 @@ export interface PartnerStepScreenTranslations {
33
33
  readonly maxFileSize: string;
34
34
  readonly error: string;
35
35
  readonly uploadFailed: string;
36
+ readonly aiDisclosure?: string;
36
37
  }
37
38
 
38
39
  export interface PartnerStepScreenConfig {
@@ -175,6 +176,16 @@ export const PartnerStepScreen: React.FC<PartnerStepScreenProps> = ({
175
176
  maxNameLength={config.maxNameLength}
176
177
  />
177
178
  )}
179
+ {translations.aiDisclosure && (
180
+ <View style={styles.disclosureContainer}>
181
+ <AtomicText
182
+ type="labelSmall"
183
+ style={[styles.disclosureText, { color: tokens.colors.textSecondary }]}
184
+ >
185
+ {translations.aiDisclosure}
186
+ </AtomicText>
187
+ </View>
188
+ )}
178
189
  </ScreenLayout>
179
190
  </View>
180
191
  );
@@ -205,4 +216,16 @@ const createStyles = (tokens: DesignTokens) =>
205
216
  fontWeight: "800",
206
217
  marginRight: 4,
207
218
  },
219
+ disclosureContainer: {
220
+ marginTop: 24,
221
+ marginHorizontal: 24,
222
+ padding: 16,
223
+ borderRadius: 12,
224
+ backgroundColor: tokens.colors.surfaceVariant + "40",
225
+ },
226
+ disclosureText: {
227
+ textAlign: "center",
228
+ lineHeight: 18,
229
+ opacity: 0.8,
230
+ },
208
231
  });
package/src/index.ts CHANGED
@@ -69,6 +69,7 @@ export type { ModerationResult, ModerationConfig, SynchronousGenerationInput, Sy
69
69
  export {
70
70
  useGeneration, usePendingJobs, useBackgroundGeneration,
71
71
  useGenerationFlow, useAIFeatureCallbacks,
72
+ useAIGenerateState, AIGenerateStep,
72
73
  useGenerationOrchestrator, useImageGeneration, useVideoGeneration,
73
74
  createGenerationError, getAlertMessage, parseError,
74
75
  } from "./presentation/hooks";
@@ -82,6 +83,7 @@ export type {
82
83
  GenerationError, GenerationErrorType, AlertMessages, UseGenerationOrchestratorReturn,
83
84
  SingleImageInput, DualImageInput, ImageGenerationInput, ImageGenerationConfig,
84
85
  DualImageVideoInput, VideoGenerationConfig,
86
+ UploadedImage,
85
87
  } from "./presentation/hooks";
86
88
 
87
89
  export {
@@ -92,6 +94,7 @@ export {
92
94
  GenerateButton, ResultDisplay, AIGenerationResult, ErrorDisplay, FeatureHeader,
93
95
  AIGenScreenHeader, CreditBadge, PhotoUploadCard, SettingsSheet, StyleSelector,
94
96
  AspectRatioSelector, DurationSelector, GridSelector, StylePresetsGrid, AIGenerationForm,
97
+ AIGenerationConfig,
95
98
  createAspectRatioOptions, createDurationOptions, createStyleOptions, createStyleOptionsFromConfig,
96
99
  ASPECT_RATIO_IDS, COMMON_DURATIONS,
97
100
  } from "./presentation/components";
@@ -122,6 +125,7 @@ export type {
122
125
  AspectRatioSelectorProps, DurationSelectorProps, GridSelectorProps, GridSelectorOption,
123
126
  StyleOption, AspectRatioOption, DurationValue, AspectRatioTranslations, DurationOption,
124
127
  StyleTranslations, AIGenerationFormProps, AIGenerationFormTranslations,
128
+ AIGenerationConfigProps,
125
129
  } from "./presentation/components";
126
130
 
127
131
  export { DEFAULT_SINGLE_PHOTO_FLOW, DEFAULT_DUAL_PHOTO_FLOW } from "./presentation/types/flow-config.types";
@@ -0,0 +1,83 @@
1
+
2
+ import React from "react";
3
+ import { View, Image, StyleSheet } from "react-native";
4
+ import { AIGenerationHero } from "./AIGenerationHero";
5
+ import { AIGenerationForm } from "./AIGenerationForm";
6
+ import type { AIGenerationFormProps } from "./AIGenerationForm.types";
7
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
8
+
9
+ export interface AIGenerationConfigProps extends Omit<AIGenerationFormProps, "isGenerating" | "progress"> {
10
+ readonly heroTitle: string;
11
+ readonly heroSubtitle: string;
12
+ readonly heroIcon?: string;
13
+ readonly images?: { uri: string; previewUrl?: string }[];
14
+ readonly isGenerating?: boolean;
15
+ readonly progress?: number;
16
+ }
17
+
18
+ export const AIGenerationConfig: React.FC<AIGenerationConfigProps> = ({
19
+ heroTitle,
20
+ heroSubtitle,
21
+ heroIcon = "sparkles-outline",
22
+ images = [],
23
+ isGenerating = false,
24
+ progress = 0,
25
+ ...formProps
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+
29
+ return (
30
+ <View style={styles.container}>
31
+ <AIGenerationHero
32
+ title={heroTitle}
33
+ subtitle={heroSubtitle}
34
+ iconName={heroIcon}
35
+ />
36
+
37
+ {images.length > 0 && (
38
+ <View style={styles.imagePreviewContainer}>
39
+ {images.map((img, index) => (
40
+ <View key={index} style={styles.imageWrapper}>
41
+ <Image
42
+ source={{ uri: img.previewUrl || img.uri }}
43
+ style={styles.previewImage}
44
+ />
45
+ </View>
46
+ ))}
47
+ </View>
48
+ )}
49
+
50
+ <AIGenerationForm
51
+ isGenerating={isGenerating}
52
+ progress={progress}
53
+ {...formProps}
54
+ />
55
+ </View>
56
+ );
57
+ };
58
+
59
+ const styles = StyleSheet.create({
60
+ container: {
61
+ flex: 1,
62
+ },
63
+ imagePreviewContainer: {
64
+ flexDirection: "row",
65
+ flexWrap: "wrap",
66
+ gap: 12,
67
+ paddingHorizontal: 16,
68
+ marginBottom: 24,
69
+ justifyContent: "center",
70
+ },
71
+ imageWrapper: {
72
+ width: 80,
73
+ height: 80,
74
+ borderRadius: 12,
75
+ overflow: "hidden",
76
+ borderWidth: 1,
77
+ borderColor: "rgba(255,255,255,0.1)",
78
+ },
79
+ previewImage: {
80
+ width: "100%",
81
+ height: "100%",
82
+ },
83
+ });
@@ -0,0 +1,347 @@
1
+
2
+ import React, { useMemo, useCallback, useEffect } from "react";
3
+ import { View, ScrollView, Platform, TouchableOpacity, Image } from "react-native";
4
+ import {
5
+ useAppDesignTokens,
6
+ AtomicText,
7
+ AtomicIcon,
8
+ ScreenLayout,
9
+ AtomicKeyboardAvoidingView,
10
+ } from "@umituz/react-native-design-system";
11
+
12
+ import { AIGenScreenHeader } from "../headers/AIGenScreenHeader";
13
+ import { PartnerStepScreen } from "../../../features/partner-upload/presentation/screens/PartnerStepScreen";
14
+ import { AIGenerationConfig } from "../AIGenerationConfig";
15
+ import { GenerationProgressContent } from "../GenerationProgressContent";
16
+ import { AIGenerationResult } from "../display/AIGenerationResult";
17
+ import { useAIGenerateState, AIGenerateStep } from "../../hooks/generation/useAIGenerateState";
18
+ import { getAIFeatureConfig, hasAIFeature } from "../../screens/ai-feature/registry";
19
+
20
+ export interface AIGenerateWizardFlowProps {
21
+ readonly featureType: string;
22
+ readonly translations: {
23
+ headerTitle: string;
24
+ uploadSubtitle: string;
25
+ uploadSubtitle2: string;
26
+ continue: string;
27
+ tapToUpload: string;
28
+ selectPhoto: string;
29
+ change: string;
30
+ analyzing: string;
31
+ error: string;
32
+ uploadFailed: string;
33
+ aiDisclosure: string;
34
+ heroTitle: string;
35
+ heroSubtitle: string;
36
+ presetsTitle: string;
37
+ showAdvancedLabel: string;
38
+ hideAdvancedLabel: string;
39
+ promptTitle: string;
40
+ promptPlaceholder: string;
41
+ styleTitle: string;
42
+ durationTitle: string;
43
+ generateButton: string;
44
+ generatingButton: string;
45
+ processingTitle: string;
46
+ processingMessage: string;
47
+ processingHint: string;
48
+ successTitle: string;
49
+ saveButton: string;
50
+ tryAgainButton: string;
51
+ fileTooLarge: string;
52
+ maxFileSize: string;
53
+ };
54
+ readonly styleOptions: any[];
55
+ readonly presets: any[];
56
+ readonly durationOptions: number[];
57
+ readonly onGenerate: (data: {
58
+ prompt: string;
59
+ style: string;
60
+ duration: number;
61
+ images: { uri: string }[];
62
+ }) => Promise<string | null>;
63
+ readonly onBack?: () => void;
64
+ readonly t: (key: string) => string;
65
+ }
66
+
67
+ export const AIGenerateWizardFlow: React.FC<AIGenerateWizardFlowProps> = ({
68
+ featureType,
69
+ translations,
70
+ styleOptions,
71
+ presets,
72
+ durationOptions,
73
+ onGenerate,
74
+ onBack: onBackProp,
75
+ t,
76
+ }) => {
77
+ const tokens = useAppDesignTokens();
78
+ const {
79
+ currentStep,
80
+ setCurrentStep,
81
+ images,
82
+ setStepImage,
83
+ prompt,
84
+ setPrompt,
85
+ selectedStyle,
86
+ setSelectedStyle,
87
+ selectedDuration,
88
+ setSelectedDuration,
89
+ showAdvanced,
90
+ toggleAdvanced,
91
+ isGenerating,
92
+ setIsGenerating,
93
+ progress,
94
+ setProgress,
95
+ result,
96
+ setResult,
97
+ } = useAIGenerateState();
98
+
99
+ const imageCountRequired = useMemo(() => {
100
+ if (!featureType || !hasAIFeature(featureType)) return 0;
101
+ const config = getAIFeatureConfig(featureType as any);
102
+ if (config.mode === "dual" || config.mode === "dual-video") return 2;
103
+ if (config.mode === "single" || config.mode === "single-with-prompt")
104
+ return 1;
105
+ return 0;
106
+ }, [featureType]);
107
+
108
+ useEffect(() => {
109
+ if (currentStep === AIGenerateStep.INFO) {
110
+ if (imageCountRequired > 0) {
111
+ setCurrentStep(AIGenerateStep.UPLOAD_1);
112
+ } else {
113
+ setCurrentStep(AIGenerateStep.CONFIG);
114
+ }
115
+ }
116
+ }, [featureType, imageCountRequired, setCurrentStep, currentStep]);
117
+
118
+ const handleBack = useCallback(() => {
119
+ if (currentStep === AIGenerateStep.UPLOAD_1) {
120
+ onBackProp?.();
121
+ } else if (currentStep === AIGenerateStep.UPLOAD_2) {
122
+ setCurrentStep(AIGenerateStep.UPLOAD_1);
123
+ } else if (currentStep === AIGenerateStep.CONFIG) {
124
+ if (imageCountRequired > 1) {
125
+ setCurrentStep(AIGenerateStep.UPLOAD_2);
126
+ } else if (imageCountRequired > 0) {
127
+ setCurrentStep(AIGenerateStep.UPLOAD_1);
128
+ } else {
129
+ onBackProp?.();
130
+ }
131
+ } else if (currentStep === AIGenerateStep.RESULT) {
132
+ setCurrentStep(AIGenerateStep.CONFIG);
133
+ }
134
+ }, [currentStep, setCurrentStep, imageCountRequired, onBackProp]);
135
+
136
+ const handleNext = useCallback(() => {
137
+ if (currentStep === AIGenerateStep.UPLOAD_1) {
138
+ if (imageCountRequired > 1) {
139
+ setCurrentStep(AIGenerateStep.UPLOAD_2);
140
+ } else {
141
+ setCurrentStep(AIGenerateStep.CONFIG);
142
+ }
143
+ } else if (currentStep === AIGenerateStep.UPLOAD_2) {
144
+ setCurrentStep(AIGenerateStep.CONFIG);
145
+ }
146
+ }, [currentStep, setCurrentStep, imageCountRequired]);
147
+
148
+ const handleGenerate = useCallback(async () => {
149
+ setIsGenerating(true);
150
+ setProgress(10);
151
+ setCurrentStep(AIGenerateStep.GENERATING);
152
+
153
+ try {
154
+ const output = await onGenerate({
155
+ prompt,
156
+ style: selectedStyle,
157
+ duration: selectedDuration,
158
+ images,
159
+ });
160
+ setResult(output);
161
+ setCurrentStep(AIGenerateStep.RESULT);
162
+ } catch (error) {
163
+ // Error handling should be added here
164
+ console.error("Generation failed", error);
165
+ setCurrentStep(AIGenerateStep.CONFIG);
166
+ } finally {
167
+ setIsGenerating(false);
168
+ setProgress(0);
169
+ }
170
+ }, [onGenerate, prompt, selectedStyle, selectedDuration, images, setCurrentStep, setIsGenerating, setProgress, setResult]);
171
+
172
+ switch (currentStep) {
173
+ case AIGenerateStep.UPLOAD_1:
174
+ case AIGenerateStep.UPLOAD_2: {
175
+ const isStep2 = currentStep === AIGenerateStep.UPLOAD_2;
176
+ return (
177
+ <PartnerStepScreen
178
+ t={t}
179
+ onBack={handleBack}
180
+ onContinue={(img: any) => {
181
+ setStepImage(isStep2 ? 1 : 0, {
182
+ uri: img.uri,
183
+ previewUrl: img.previewUrl || img.uri,
184
+ });
185
+ handleNext();
186
+ }}
187
+ translations={{
188
+ title: translations.headerTitle,
189
+ subtitle: isStep2 ? translations.uploadSubtitle2 : translations.uploadSubtitle,
190
+ continue: translations.continue,
191
+ tapToUpload: translations.tapToUpload,
192
+ selectPhoto: translations.selectPhoto,
193
+ change: translations.change,
194
+ analyzing: translations.analyzing,
195
+ fileTooLarge: translations.fileTooLarge,
196
+ maxFileSize: translations.maxFileSize,
197
+ error: translations.error,
198
+ uploadFailed: translations.uploadFailed,
199
+ aiDisclosure: translations.aiDisclosure,
200
+ }}
201
+ initialName=""
202
+ config={{
203
+ showFaceDetection: featureType === "face-swap",
204
+ showNameInput: false,
205
+ showPhotoTips: true,
206
+ }}
207
+ />
208
+ );
209
+ }
210
+
211
+ case AIGenerateStep.GENERATING:
212
+ return (
213
+ <ScreenLayout
214
+ header={
215
+ <AIGenScreenHeader
216
+ title={translations.headerTitle}
217
+ onNavigationPress={handleBack}
218
+ />
219
+ }
220
+ >
221
+ <View style={{ flex: 1, padding: tokens.spacing.xl }}>
222
+ <GenerationProgressContent
223
+ progress={progress}
224
+ title={translations.processingTitle}
225
+ message={translations.processingMessage}
226
+ hint={translations.processingHint}
227
+ />
228
+ </View>
229
+ </ScreenLayout>
230
+ );
231
+
232
+ case AIGenerateStep.RESULT:
233
+ return (
234
+ <ScreenLayout
235
+ header={
236
+ <AIGenScreenHeader
237
+ title={translations.headerTitle}
238
+ onNavigationPress={handleBack}
239
+ />
240
+ }
241
+ >
242
+ <AIGenerationResult
243
+ successText={translations.successTitle}
244
+ primaryAction={{
245
+ label: translations.saveButton,
246
+ onPress: () => {},
247
+ icon: "download",
248
+ }}
249
+ secondaryAction={{
250
+ label: translations.tryAgainButton,
251
+ onPress: () => setCurrentStep(AIGenerateStep.CONFIG),
252
+ icon: "refresh",
253
+ }}
254
+ >
255
+ <Image
256
+ source={{ uri: result || "" }}
257
+ style={{ width: "100%", aspectRatio: 2 / 3, borderRadius: 16 }}
258
+ resizeMode="cover"
259
+ />
260
+ </AIGenerationResult>
261
+ </ScreenLayout>
262
+ );
263
+
264
+ default:
265
+ return (
266
+ <AtomicKeyboardAvoidingView
267
+ offset={Platform.OS === "ios" ? 94 : 0}
268
+ >
269
+ <ScrollView
270
+ style={{ backgroundColor: tokens.colors.backgroundPrimary }}
271
+ contentContainerStyle={{ paddingBottom: 100 }}
272
+ showsVerticalScrollIndicator={false}
273
+ keyboardShouldPersistTaps="handled"
274
+ >
275
+ <AIGenScreenHeader
276
+ title={translations.headerTitle}
277
+ onNavigationPress={handleBack}
278
+ rightContent={
279
+ <TouchableOpacity
280
+ onPress={handleGenerate}
281
+ disabled={isGenerating || !prompt.trim()}
282
+ activeOpacity={0.7}
283
+ style={{
284
+ flexDirection: "row",
285
+ alignItems: "center",
286
+ backgroundColor: !isGenerating && prompt.trim() ? tokens.colors.primary : tokens.colors.surfaceVariant,
287
+ paddingHorizontal: tokens.spacing.md,
288
+ paddingVertical: tokens.spacing.xs,
289
+ borderRadius: tokens.borders.radius.full,
290
+ opacity: !isGenerating && prompt.trim() ? 1 : 0.5,
291
+ }}
292
+ >
293
+ <AtomicText
294
+ type="bodyMedium"
295
+ style={{
296
+ fontWeight: "800",
297
+ color: !isGenerating && prompt.trim() ? tokens.colors.onPrimary : tokens.colors.textSecondary,
298
+ marginRight: 4,
299
+ }}
300
+ >
301
+ {isGenerating ? translations.generatingButton : translations.generateButton}
302
+ </AtomicText>
303
+ <AtomicIcon
304
+ name={isGenerating ? "refresh" : "sparkles"}
305
+ size="sm"
306
+ color={!isGenerating && prompt.trim() ? "onPrimary" : "textSecondary"}
307
+ />
308
+ </TouchableOpacity>
309
+ }
310
+ />
311
+ <AIGenerationConfig
312
+ heroTitle={translations.heroTitle}
313
+ heroSubtitle={translations.heroSubtitle}
314
+ isGenerating={isGenerating}
315
+ progress={progress}
316
+ presets={presets}
317
+ onPresetPress={handleGenerate as any}
318
+ prompt={prompt}
319
+ onPromptChange={setPrompt}
320
+ styles={styleOptions}
321
+ selectedStyle={selectedStyle}
322
+ onStyleSelect={setSelectedStyle}
323
+ duration={selectedDuration}
324
+ durationOptions={durationOptions}
325
+ onDurationSelect={setSelectedDuration}
326
+ showAdvanced={showAdvanced}
327
+ onAdvancedToggle={toggleAdvanced}
328
+ onGenerate={handleGenerate}
329
+ images={images}
330
+ hideGenerateButton={true}
331
+ translations={{
332
+ presetsTitle: translations.presetsTitle,
333
+ showAdvancedLabel: translations.showAdvancedLabel,
334
+ hideAdvancedLabel: translations.hideAdvancedLabel,
335
+ promptTitle: translations.promptTitle,
336
+ promptPlaceholder: translations.promptPlaceholder,
337
+ styleTitle: translations.styleTitle,
338
+ durationTitle: translations.durationTitle,
339
+ generateButton: translations.generateButton,
340
+ generatingButton: translations.generatingButton,
341
+ }}
342
+ />
343
+ </ScrollView>
344
+ </AtomicKeyboardAvoidingView>
345
+ );
346
+ }
347
+ };
@@ -49,6 +49,15 @@ export const AIGenScreenHeader: React.FC<AIGenScreenHeaderProps> = ({
49
49
  return (
50
50
  <View style={styles.header}>
51
51
  <View style={styles.headerTop}>
52
+ {onNavigationPress && (
53
+ <TouchableOpacity
54
+ onPress={onNavigationPress}
55
+ style={[buttonStyle, { marginRight: 12 }]}
56
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
57
+ >
58
+ <AtomicIcon name={iconName} size="md" color={iconColor} />
59
+ </TouchableOpacity>
60
+ )}
52
61
  <View style={styles.titleContainer}>
53
62
  <AtomicText
54
63
  type={titleType}
@@ -62,15 +71,6 @@ export const AIGenScreenHeader: React.FC<AIGenScreenHeaderProps> = ({
62
71
  </View>
63
72
  <View style={styles.headerActions}>
64
73
  {rightContent}
65
- {onNavigationPress && (
66
- <TouchableOpacity
67
- onPress={onNavigationPress}
68
- style={buttonStyle}
69
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
70
- >
71
- <AtomicIcon name={iconName} size="md" color={iconColor} />
72
- </TouchableOpacity>
73
- )}
74
74
  </View>
75
75
  </View>
76
76
  {showDescription && description && (
@@ -8,6 +8,8 @@ export { AIGenerationHero } from "./AIGenerationHero";
8
8
  export * from "./StylePresetsGrid";
9
9
  export * from "./AIGenerationForm";
10
10
  export * from "./AIGenerationForm.types";
11
+ export * from "./AIGenerationConfig";
12
+ export * from "./flows/AIGenerateWizardFlow";
11
13
 
12
14
  export type { GenerationProgressContentProps } from "./GenerationProgressContent";
13
15
  export type { GenerationProgressBarProps } from "./GenerationProgressBar";
@@ -70,7 +70,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
70
70
 
71
71
  const offlineStore = useOfflineStore();
72
72
  const { showError, showSuccess } = useAlert();
73
- const defaultCredits = useDeductCredit({ userId, onCreditsExhausted });
73
+ const defaultCredits = useDeductCredit({ userId, onCreditsExhausted }) as any;
74
74
 
75
75
  // Use provided credit callbacks or default to useDeductCredit hook
76
76
  const checkCredits = credits?.checkCredits ?? defaultCredits.checkCredits;
@@ -0,0 +1,81 @@
1
+
2
+ export enum AIGenerateStep {
3
+ INFO = "INFO",
4
+ UPLOAD_1 = "UPLOAD_1",
5
+ UPLOAD_2 = "UPLOAD_2",
6
+ CONFIG = "CONFIG",
7
+ GENERATING = "GENERATING",
8
+ RESULT = "RESULT",
9
+ }
10
+
11
+ export interface UploadedImage {
12
+ uri: string;
13
+ previewUrl?: string;
14
+ name?: string;
15
+ }
16
+
17
+ import { useState, useCallback } from "react";
18
+
19
+ export function useAIGenerateState() {
20
+ const [currentStep, setCurrentStep] = useState<AIGenerateStep>(
21
+ AIGenerateStep.INFO,
22
+ );
23
+ const [images, setImages] = useState<UploadedImage[]>([]);
24
+ const [prompt, setPrompt] = useState("");
25
+ const [selectedStyle, setSelectedStyle] = useState("modern");
26
+ const [selectedDuration, setSelectedDuration] = useState(15);
27
+ const [showAdvanced, setShowAdvanced] = useState(false);
28
+ const [isGenerating, setIsGenerating] = useState(false);
29
+ const [progress, setProgress] = useState(0);
30
+ const [result, setResult] = useState<string | null>(null);
31
+
32
+ const toggleAdvanced = useCallback(() => {
33
+ setShowAdvanced((prev) => !prev);
34
+ }, []);
35
+
36
+ const goToStep = useCallback((step: AIGenerateStep) => {
37
+ setCurrentStep(step);
38
+ }, []);
39
+
40
+ const setStepImage = useCallback((index: number, image: UploadedImage) => {
41
+ setImages((prev) => {
42
+ const next = [...prev];
43
+ next[index] = image;
44
+ return next;
45
+ });
46
+ }, []);
47
+
48
+ const reset = useCallback(() => {
49
+ setImages([]);
50
+ setPrompt("");
51
+ setSelectedStyle("modern");
52
+ setSelectedDuration(15);
53
+ setIsGenerating(false);
54
+ setProgress(0);
55
+ setResult(null);
56
+ setCurrentStep(AIGenerateStep.INFO);
57
+ }, []);
58
+
59
+ return {
60
+ currentStep,
61
+ setCurrentStep: goToStep,
62
+ images,
63
+ setImages,
64
+ setStepImage,
65
+ prompt,
66
+ setPrompt,
67
+ selectedStyle,
68
+ setSelectedStyle,
69
+ selectedDuration,
70
+ setSelectedDuration,
71
+ showAdvanced,
72
+ toggleAdvanced,
73
+ isGenerating,
74
+ setIsGenerating,
75
+ progress,
76
+ setProgress,
77
+ result,
78
+ setResult,
79
+ reset,
80
+ };
81
+ }
@@ -59,3 +59,6 @@ export type {
59
59
  AIFeatureCallbacks,
60
60
  AIFeatureGenerationResult,
61
61
  } from "./useAIFeatureCallbacks";
62
+
63
+ export { useAIGenerateState, AIGenerateStep } from "./generation/useAIGenerateState";
64
+ export type { UploadedImage } from "./generation/useAIGenerateState";