@umituz/react-native-ai-generation-content 1.20.14 → 1.20.15

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.14",
3
+ "version": "1.20.15",
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",
@@ -1,85 +1,29 @@
1
1
  /**
2
2
  * useCoupleFutureFlow Hook
3
3
  * Optimized: Merged flow + generation logic
4
- * Uses centralized orchestrator directly
4
+ * Uses centralized orchestrator with lifecycle management
5
5
  */
6
6
 
7
- import { useCallback, useEffect, useRef, useMemo } from "react";
8
- import { InteractionManager } from "react-native";
7
+ import { useEffect, useRef, useMemo } from "react";
9
8
  import {
10
9
  useGenerationOrchestrator,
11
10
  type GenerationStrategy,
12
- type AlertMessages,
13
11
  } from "../../../../presentation/hooks/generation";
14
12
  import { executeCoupleFuture } from "../../infrastructure/executor";
15
13
  import { buildGenerationInputFromConfig } from "../../infrastructure/generationUtils";
16
14
  import { createCreationsRepository } from "../../../../domains/creations/infrastructure/adapters";
17
- import type { UploadedImage } from "../../../partner-upload/domain/types";
18
15
  import type { CoupleFutureInput } from "../../domain/types";
19
- import type { Creation } from "../../../../domains/creations/domain/entities/Creation";
16
+ import type { CoupleFutureFlowProps } from "./useCoupleFutureFlow.types";
17
+ import { useCoupleFutureHandlers } from "./useCoupleFutureHandlers";
20
18
 
21
- declare const __DEV__: boolean;
19
+ export type {
20
+ CoupleFutureFlowConfig,
21
+ CoupleFutureFlowState,
22
+ CoupleFutureFlowActions,
23
+ CoupleFutureFlowProps,
24
+ } from "./useCoupleFutureFlow.types";
22
25
 
23
- export interface CoupleFutureFlowConfig<TStep, TScenarioId> {
24
- steps: {
25
- SCENARIO: TStep;
26
- SCENARIO_PREVIEW: TStep;
27
- COUPLE_FEATURE_SELECTOR: TStep;
28
- TEXT_INPUT: TStep;
29
- PARTNER_A: TStep;
30
- PARTNER_B: TStep;
31
- GENERATING: TStep;
32
- };
33
- customScenarioId: TScenarioId;
34
- }
35
-
36
- export interface CoupleFutureFlowState<TStep, TScenarioId> {
37
- step: TStep;
38
- selectedScenarioId: TScenarioId | null;
39
- selectedFeature: string | null;
40
- partnerA: unknown;
41
- partnerB: unknown;
42
- partnerAName: string;
43
- partnerBName: string;
44
- customPrompt: string | null;
45
- visualStyle: string | null;
46
- selection: unknown;
47
- isProcessing: boolean;
48
- scenarioConfig: unknown;
49
- selectedScenarioData: { requiresPhoto?: boolean } | null;
50
- }
51
-
52
- export interface CoupleFutureFlowActions<TStep, TScenarioId, TResult> {
53
- setStep: (step: TStep) => void;
54
- selectScenario: (id: TScenarioId) => void;
55
- setPartnerA: (image: unknown) => void;
56
- setPartnerAName: (name: string) => void;
57
- setPartnerB: (image: unknown) => void;
58
- setPartnerBName: (name: string) => void;
59
- setCustomPrompt: (prompt: string) => void;
60
- setVisualStyle: (style: string) => void;
61
- startGeneration: () => void;
62
- generationSuccess: (result: TResult) => void;
63
- generationError: (error: string) => void;
64
- requireFeature: (callback: () => void) => void;
65
- onNavigateToHistory: () => void;
66
- }
67
-
68
- export interface CoupleFutureFlowProps<TStep, TScenarioId, TResult> {
69
- userId?: string;
70
- config: CoupleFutureFlowConfig<TStep, TScenarioId>;
71
- state: CoupleFutureFlowState<TStep, TScenarioId>;
72
- actions: CoupleFutureFlowActions<TStep, TScenarioId, TResult>;
73
- generationConfig: {
74
- visualStyleModifiers: Record<string, string>;
75
- defaultPartnerAName: string;
76
- defaultPartnerBName: string;
77
- };
78
- alertMessages: AlertMessages;
79
- processResult: (imageUrl: string, input: CoupleFutureInput) => TResult;
80
- buildCreation: (result: TResult, input: CoupleFutureInput) => Creation | null;
81
- onCreditsExhausted: () => void;
82
- }
26
+ declare const __DEV__: boolean;
83
27
 
84
28
  export const useCoupleFutureFlow = <TStep, TScenarioId, TResult>(
85
29
  props: CoupleFutureFlowProps<TStep, TScenarioId, TResult>,
@@ -116,37 +60,68 @@ export const useCoupleFutureFlow = <TStep, TScenarioId, TResult>(
116
60
  [processResult, buildCreation, repository],
117
61
  );
118
62
 
119
- // Use orchestrator directly
63
+ // Use orchestrator with centralized lifecycle management
120
64
  const { generate, isGenerating, progress } = useGenerationOrchestrator(strategy, {
121
65
  userId,
122
66
  alertMessages,
123
67
  onCreditsExhausted,
124
68
  onSuccess: (result) => {
125
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[CoupleFutureFlow] 🎉 Success");
69
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
70
+ console.log("[CoupleFutureFlow] 🎉 Success - calling generationSuccess");
71
+ }
126
72
  actions.generationSuccess(result as TResult);
127
- InteractionManager.runAfterInteractions(() => setTimeout(() => actions.onNavigateToHistory(), 300));
128
73
  },
129
74
  onError: (err) => {
130
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[CoupleFutureFlow] ❌ Error:", err.message);
75
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
76
+ console.log("[CoupleFutureFlow] ❌ Error:", err.message);
77
+ }
131
78
  actions.generationError(err.message);
132
79
  },
80
+ // Centralized lifecycle management - navigation handled by orchestrator
81
+ lifecycle: {
82
+ onComplete: (status) => {
83
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
84
+ console.log("[CoupleFutureFlow] 📍 Lifecycle onComplete:", status);
85
+ }
86
+ if (status === "success") {
87
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
88
+ console.log("[CoupleFutureFlow] 🚀 Navigating to History");
89
+ }
90
+ actions.onNavigateToHistory();
91
+ }
92
+ },
93
+ completeDelay: 500,
94
+ },
133
95
  });
134
96
 
135
97
  // Trigger generation when step changes to GENERATING
136
98
  useEffect(() => {
99
+ const step = state.step;
100
+ const isProcessing = state.isProcessing;
101
+
137
102
  if (typeof __DEV__ !== "undefined" && __DEV__) {
138
- console.log("[CoupleFutureFlow] 📍 Step changed:", {
139
- currentStep: state.step,
103
+ console.log("[CoupleFutureFlow] 📍 Step effect triggered:", {
104
+ currentStep: step,
140
105
  targetStep: config.steps.GENERATING,
141
- isProcessing: state.isProcessing,
106
+ isProcessing,
142
107
  hasStarted: hasStarted.current,
143
108
  });
144
109
  }
145
- if (state.step !== config.steps.GENERATING) {
110
+
111
+ if (step !== config.steps.GENERATING) {
146
112
  hasStarted.current = false;
147
113
  return;
148
114
  }
149
- if (!state.isProcessing || hasStarted.current) return;
115
+
116
+ if (!isProcessing || hasStarted.current) {
117
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
118
+ console.log("[CoupleFutureFlow] ⏭️ Skipping generation:", {
119
+ reason: !isProcessing ? "not processing" : "already started",
120
+ });
121
+ }
122
+ return;
123
+ }
124
+
150
125
  hasStarted.current = true;
151
126
 
152
127
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -167,87 +142,25 @@ export const useCoupleFutureFlow = <TStep, TScenarioId, TResult>(
167
142
  visualStyles: generationConfig.visualStyleModifiers,
168
143
  customScenarioId: config.customScenarioId as string,
169
144
  });
170
- if (input) generate(input);
171
- }, [state, config, generationConfig, generate]);
172
-
173
- // Handlers
174
- const handleScenarioSelect = useCallback(
175
- (id: string) => {
176
- actions.selectScenario(id as TScenarioId);
177
- actions.setStep(config.steps.SCENARIO_PREVIEW);
178
- },
179
- [actions, config.steps.SCENARIO_PREVIEW],
180
- );
181
-
182
- const handleScenarioPreviewBack = useCallback(() => actions.setStep(config.steps.SCENARIO), [actions, config.steps.SCENARIO]);
183
-
184
- const handleScenarioPreviewContinue = useCallback(() => {
185
- if (state.selectedFeature) actions.setStep(config.steps.COUPLE_FEATURE_SELECTOR);
186
- else if (state.selectedScenarioId === config.customScenarioId || state.selectedScenarioData?.requiresPhoto === false)
187
- actions.setStep(config.steps.TEXT_INPUT);
188
- else actions.setStep(config.steps.PARTNER_A);
189
- }, [actions, config, state.selectedFeature, state.selectedScenarioId, state.selectedScenarioData]);
190
-
191
- const handlePartnerAContinue = useCallback(
192
- (image: UploadedImage, name: string) => {
193
- actions.setPartnerA(image);
194
- actions.setPartnerAName(name);
195
- actions.setStep(config.steps.PARTNER_B);
196
- },
197
- [actions, config.steps.PARTNER_B],
198
- );
199
145
 
200
- const handlePartnerABack = useCallback(() => {
201
- actions.setStep(state.selectedScenarioId === config.customScenarioId ? config.steps.TEXT_INPUT : config.steps.SCENARIO_PREVIEW);
202
- }, [actions, config, state.selectedScenarioId]);
203
-
204
- const handlePartnerBContinue = useCallback(
205
- (image: UploadedImage, name: string) => {
146
+ if (input) {
206
147
  if (typeof __DEV__ !== "undefined" && __DEV__) {
207
- console.log("[CoupleFutureFlow] 🎬 handlePartnerBContinue called, invoking requireFeature");
148
+ console.log("[CoupleFutureFlow] 🚀 Starting generation with input");
208
149
  }
209
- actions.requireFeature(() => {
210
- if (typeof __DEV__ !== "undefined" && __DEV__) {
211
- console.log("[CoupleFutureFlow] 🚀 Pending action executing: setPartnerB, setPartnerBName, startGeneration");
212
- }
213
- actions.setPartnerB(image);
214
- actions.setPartnerBName(name);
215
- actions.startGeneration();
216
- if (typeof __DEV__ !== "undefined" && __DEV__) {
217
- console.log("[CoupleFutureFlow] ✅ startGeneration called successfully");
218
- }
219
- });
220
- },
221
- [actions],
222
- );
223
-
224
- const handlePartnerBBack = useCallback(() => actions.setStep(config.steps.PARTNER_A), [actions, config.steps.PARTNER_A]);
225
-
226
- const handleMagicPromptContinue = useCallback(
227
- (prompt: string, style: string) => {
228
- actions.setCustomPrompt(prompt);
229
- actions.setVisualStyle(style);
230
- if (state.selectedScenarioId === config.customScenarioId) actions.setStep(config.steps.PARTNER_A);
231
- else actions.startGeneration();
232
- },
233
- [actions, config, state.selectedScenarioId],
234
- );
150
+ generate(input);
151
+ } else {
152
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
153
+ console.log("[CoupleFutureFlow] ⚠️ No input built, skipping generation");
154
+ }
155
+ }
156
+ }, [state.step, state.isProcessing, state.partnerA, state.partnerB, state.partnerAName, state.partnerBName, state.scenarioConfig, state.customPrompt, state.visualStyle, state.selection, config.steps.GENERATING, config.customScenarioId, generationConfig, generate]);
235
157
 
236
- const handleMagicPromptBack = useCallback(() => actions.setStep(config.steps.SCENARIO_PREVIEW), [actions, config.steps.SCENARIO_PREVIEW]);
158
+ // Use extracted handlers hook
159
+ const handlers = useCoupleFutureHandlers({ config, state, actions });
237
160
 
238
161
  return {
239
162
  isGenerating,
240
163
  progress,
241
- handlers: {
242
- handleScenarioSelect,
243
- handleScenarioPreviewBack,
244
- handleScenarioPreviewContinue,
245
- handlePartnerAContinue,
246
- handlePartnerABack,
247
- handlePartnerBContinue,
248
- handlePartnerBBack,
249
- handleMagicPromptContinue,
250
- handleMagicPromptBack,
251
- },
164
+ handlers,
252
165
  };
253
166
  };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * useCoupleFutureFlow Types
3
+ * Type definitions for couple future flow hook
4
+ */
5
+
6
+ import type { AlertMessages } from "../../../../presentation/hooks/generation";
7
+ import type { CoupleFutureInput } from "../../domain/types";
8
+ import type { Creation } from "../../../../domains/creations/domain/entities/Creation";
9
+
10
+ export interface CoupleFutureFlowConfig<TStep, TScenarioId> {
11
+ steps: {
12
+ SCENARIO: TStep;
13
+ SCENARIO_PREVIEW: TStep;
14
+ COUPLE_FEATURE_SELECTOR: TStep;
15
+ TEXT_INPUT: TStep;
16
+ PARTNER_A: TStep;
17
+ PARTNER_B: TStep;
18
+ GENERATING: TStep;
19
+ };
20
+ customScenarioId: TScenarioId;
21
+ }
22
+
23
+ export interface CoupleFutureFlowState<TStep, TScenarioId> {
24
+ step: TStep;
25
+ selectedScenarioId: TScenarioId | null;
26
+ selectedFeature: string | null;
27
+ partnerA: unknown;
28
+ partnerB: unknown;
29
+ partnerAName: string;
30
+ partnerBName: string;
31
+ customPrompt: string | null;
32
+ visualStyle: string | null;
33
+ selection: unknown;
34
+ isProcessing: boolean;
35
+ scenarioConfig: unknown;
36
+ selectedScenarioData: { requiresPhoto?: boolean } | null;
37
+ }
38
+
39
+ export interface CoupleFutureFlowActions<TStep, TScenarioId, TResult> {
40
+ setStep: (step: TStep) => void;
41
+ selectScenario: (id: TScenarioId) => void;
42
+ setPartnerA: (image: unknown) => void;
43
+ setPartnerAName: (name: string) => void;
44
+ setPartnerB: (image: unknown) => void;
45
+ setPartnerBName: (name: string) => void;
46
+ setCustomPrompt: (prompt: string) => void;
47
+ setVisualStyle: (style: string) => void;
48
+ startGeneration: () => void;
49
+ generationSuccess: (result: TResult) => void;
50
+ generationError: (error: string) => void;
51
+ requireFeature: (callback: () => void) => void;
52
+ onNavigateToHistory: () => void;
53
+ }
54
+
55
+ export interface CoupleFutureFlowProps<TStep, TScenarioId, TResult> {
56
+ userId?: string;
57
+ config: CoupleFutureFlowConfig<TStep, TScenarioId>;
58
+ state: CoupleFutureFlowState<TStep, TScenarioId>;
59
+ actions: CoupleFutureFlowActions<TStep, TScenarioId, TResult>;
60
+ generationConfig: {
61
+ visualStyleModifiers: Record<string, string>;
62
+ defaultPartnerAName: string;
63
+ defaultPartnerBName: string;
64
+ };
65
+ alertMessages: AlertMessages;
66
+ processResult: (imageUrl: string, input: CoupleFutureInput) => TResult;
67
+ buildCreation: (result: TResult, input: CoupleFutureInput) => Creation | null;
68
+ onCreditsExhausted: () => void;
69
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * useCoupleFutureHandlers Hook
3
+ * Step navigation handlers for couple future flow
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import type { UploadedImage } from "../../../partner-upload/domain/types";
8
+ import type { CoupleFutureFlowConfig, CoupleFutureFlowState, CoupleFutureFlowActions } from "./useCoupleFutureFlow.types";
9
+
10
+ declare const __DEV__: boolean;
11
+
12
+ interface UseCoupleFutureHandlersProps<TStep, TScenarioId, TResult> {
13
+ config: CoupleFutureFlowConfig<TStep, TScenarioId>;
14
+ state: CoupleFutureFlowState<TStep, TScenarioId>;
15
+ actions: CoupleFutureFlowActions<TStep, TScenarioId, TResult>;
16
+ }
17
+
18
+ export function useCoupleFutureHandlers<TStep, TScenarioId, TResult>(
19
+ props: UseCoupleFutureHandlersProps<TStep, TScenarioId, TResult>,
20
+ ) {
21
+ const { config, state, actions } = props;
22
+
23
+ const handleScenarioSelect = useCallback(
24
+ (id: string) => {
25
+ actions.selectScenario(id as TScenarioId);
26
+ actions.setStep(config.steps.SCENARIO_PREVIEW);
27
+ },
28
+ [actions, config.steps.SCENARIO_PREVIEW],
29
+ );
30
+
31
+ const handleScenarioPreviewBack = useCallback(
32
+ () => actions.setStep(config.steps.SCENARIO),
33
+ [actions, config.steps.SCENARIO],
34
+ );
35
+
36
+ const handleScenarioPreviewContinue = useCallback(() => {
37
+ if (state.selectedFeature) {
38
+ actions.setStep(config.steps.COUPLE_FEATURE_SELECTOR);
39
+ } else if (
40
+ state.selectedScenarioId === config.customScenarioId ||
41
+ state.selectedScenarioData?.requiresPhoto === false
42
+ ) {
43
+ actions.setStep(config.steps.TEXT_INPUT);
44
+ } else {
45
+ actions.setStep(config.steps.PARTNER_A);
46
+ }
47
+ }, [actions, config, state.selectedFeature, state.selectedScenarioId, state.selectedScenarioData]);
48
+
49
+ const handlePartnerAContinue = useCallback(
50
+ (image: UploadedImage, name: string) => {
51
+ actions.setPartnerA(image);
52
+ actions.setPartnerAName(name);
53
+ actions.setStep(config.steps.PARTNER_B);
54
+ },
55
+ [actions, config.steps.PARTNER_B],
56
+ );
57
+
58
+ const handlePartnerABack = useCallback(() => {
59
+ const targetStep =
60
+ state.selectedScenarioId === config.customScenarioId
61
+ ? config.steps.TEXT_INPUT
62
+ : config.steps.SCENARIO_PREVIEW;
63
+ actions.setStep(targetStep);
64
+ }, [actions, config, state.selectedScenarioId]);
65
+
66
+ const handlePartnerBContinue = useCallback(
67
+ (image: UploadedImage, name: string) => {
68
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
69
+ console.log("[CoupleFutureHandlers] 🎬 handlePartnerBContinue called");
70
+ }
71
+ actions.requireFeature(() => {
72
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
73
+ console.log("[CoupleFutureHandlers] 🚀 Executing: setPartnerB, setPartnerBName, startGeneration");
74
+ }
75
+ actions.setPartnerB(image);
76
+ actions.setPartnerBName(name);
77
+ actions.startGeneration();
78
+ });
79
+ },
80
+ [actions],
81
+ );
82
+
83
+ const handlePartnerBBack = useCallback(
84
+ () => actions.setStep(config.steps.PARTNER_A),
85
+ [actions, config.steps.PARTNER_A],
86
+ );
87
+
88
+ const handleMagicPromptContinue = useCallback(
89
+ (prompt: string, style: string) => {
90
+ actions.setCustomPrompt(prompt);
91
+ actions.setVisualStyle(style);
92
+ if (state.selectedScenarioId === config.customScenarioId) {
93
+ actions.setStep(config.steps.PARTNER_A);
94
+ } else {
95
+ actions.startGeneration();
96
+ }
97
+ },
98
+ [actions, config, state.selectedScenarioId],
99
+ );
100
+
101
+ const handleMagicPromptBack = useCallback(
102
+ () => actions.setStep(config.steps.SCENARIO_PREVIEW),
103
+ [actions, config.steps.SCENARIO_PREVIEW],
104
+ );
105
+
106
+ return {
107
+ handleScenarioSelect,
108
+ handleScenarioPreviewBack,
109
+ handleScenarioPreviewContinue,
110
+ handlePartnerAContinue,
111
+ handlePartnerABack,
112
+ handlePartnerBContinue,
113
+ handlePartnerBBack,
114
+ handleMagicPromptContinue,
115
+ handleMagicPromptBack,
116
+ };
117
+ }
@@ -16,7 +16,7 @@ export interface UseHDTouchUpFeatureProps {
16
16
  export function useHDTouchUpFeature(props: UseHDTouchUpFeatureProps): BaseSingleImageHookReturn {
17
17
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
18
18
 
19
- return useSingleImageFeature<HDTouchUpFeatureConfig, HDTouchUpResult>(
19
+ return useSingleImageFeature<HDTouchUpFeatureConfig>(
20
20
  { config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
21
21
  { buildInput: (imageBase64) => ({ imageBase64 }) },
22
22
  );
@@ -16,7 +16,7 @@ export interface UsePhotoRestoreFeatureProps {
16
16
  export function usePhotoRestoreFeature(props: UsePhotoRestoreFeatureProps): BaseSingleImageHookReturn {
17
17
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
18
18
 
19
- return useSingleImageFeature<PhotoRestoreFeatureConfig, PhotoRestoreResult>(
19
+ return useSingleImageFeature<PhotoRestoreFeatureConfig>(
20
20
  { config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
21
21
  { buildInput: (imageBase64) => ({ imageBase64 }) },
22
22
  );
@@ -16,7 +16,7 @@ export interface UseRemoveBackgroundFeatureProps {
16
16
  export function useRemoveBackgroundFeature(props: UseRemoveBackgroundFeatureProps): BaseSingleImageHookReturn {
17
17
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
18
18
 
19
- return useSingleImageFeature<RemoveBackgroundFeatureConfig, RemoveBackgroundResult>(
19
+ return useSingleImageFeature<RemoveBackgroundFeatureConfig>(
20
20
  { config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
21
21
  {
22
22
  buildInput: (imageBase64, cfg) => ({
@@ -16,7 +16,7 @@ export interface UseUpscaleFeatureProps {
16
16
  export function useUpscaleFeature(props: UseUpscaleFeatureProps): BaseSingleImageHookReturn {
17
17
  const { config, onSelectImage, onSaveImage, onBeforeProcess } = props;
18
18
 
19
- return useSingleImageFeature<UpscaleFeatureConfig, UpscaleResult>(
19
+ return useSingleImageFeature<UpscaleFeatureConfig>(
20
20
  { config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
21
21
  {
22
22
  buildInput: (imageBase64, cfg) => ({
@@ -24,6 +24,7 @@ export type {
24
24
  ModerationCallbacks,
25
25
  ModerationResult,
26
26
  CreditCallbacks,
27
+ LifecycleConfig,
27
28
  } from "./types";
28
29
 
29
30
  export type {
@@ -7,9 +7,10 @@
7
7
  * - Error handling
8
8
  * - Alert display
9
9
  * - Progress tracking
10
+ * - Lifecycle management (navigation, cleanup)
10
11
  */
11
12
 
12
- import { useState, useCallback, useRef } from "react";
13
+ import { useState, useCallback, useRef, useEffect } from "react";
13
14
  import { useOfflineStore, useAlert } from "@umituz/react-native-design-system";
14
15
  import { useDeductCredit } from "@umituz/react-native-subscription";
15
16
  import { createGenerationError, getAlertMessage, parseError } from "./errors";
@@ -17,6 +18,7 @@ import type {
17
18
  GenerationStrategy,
18
19
  GenerationConfig,
19
20
  GenerationState,
21
+ GenerationError,
20
22
  UseGenerationOrchestratorReturn,
21
23
  } from "./types";
22
24
 
@@ -30,24 +32,42 @@ const INITIAL_STATE = {
30
32
  error: null,
31
33
  };
32
34
 
35
+ const DEFAULT_COMPLETE_DELAY = 500;
36
+ const DEFAULT_RESET_DELAY = 1000;
37
+
33
38
  export const useGenerationOrchestrator = <TInput, TResult>(
34
39
  strategy: GenerationStrategy<TInput, TResult>,
35
40
  config: GenerationConfig,
36
41
  ): UseGenerationOrchestratorReturn<TInput, TResult> => {
37
- const { userId, alertMessages, onCreditsExhausted, onSuccess, onError, auth, moderation, credits } = config;
42
+ const {
43
+ userId,
44
+ alertMessages,
45
+ onCreditsExhausted,
46
+ onSuccess,
47
+ onError,
48
+ auth,
49
+ moderation,
50
+ credits,
51
+ lifecycle,
52
+ } = config;
38
53
 
39
54
  if (typeof __DEV__ !== "undefined" && __DEV__) {
40
- console.log("[Orchestrator] Hook initialized:", {
55
+ console.log("[Orchestrator] 🎬 Hook initialized:", {
41
56
  userId,
42
57
  hasAuth: !!auth,
43
58
  hasModeration: !!moderation,
44
59
  hasCreditsCallbacks: !!credits,
60
+ hasLifecycle: !!lifecycle,
45
61
  });
46
62
  }
47
63
 
48
64
  const [state, setState] = useState<GenerationState<TResult>>(INITIAL_STATE);
49
65
  const isGeneratingRef = useRef(false);
50
66
  const pendingInputRef = useRef<TInput | null>(null);
67
+ const completeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
68
+ const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
69
+ const isMountedRef = useRef(true);
70
+
51
71
  const offlineStore = useOfflineStore();
52
72
  const { showError, showSuccess } = useAlert();
53
73
  const defaultCredits = useDeductCredit({ userId, onCreditsExhausted });
@@ -57,6 +77,81 @@ export const useGenerationOrchestrator = <TInput, TResult>(
57
77
  const deductCredit = credits?.deductCredits ?? defaultCredits.deductCredit;
58
78
  const handleCreditsExhausted = credits?.onCreditsExhausted ?? onCreditsExhausted;
59
79
 
80
+ // Cleanup on unmount
81
+ useEffect(() => {
82
+ isMountedRef.current = true;
83
+ return () => {
84
+ isMountedRef.current = false;
85
+ if (completeTimeoutRef.current) {
86
+ clearTimeout(completeTimeoutRef.current);
87
+ completeTimeoutRef.current = null;
88
+ }
89
+ if (resetTimeoutRef.current) {
90
+ clearTimeout(resetTimeoutRef.current);
91
+ resetTimeoutRef.current = null;
92
+ }
93
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
94
+ console.log("[Orchestrator] 🧹 Cleanup: cleared all timeouts");
95
+ }
96
+ };
97
+ }, []);
98
+
99
+ // Handle lifecycle completion
100
+ const handleLifecycleComplete = useCallback(
101
+ (status: "success" | "error", result?: TResult, error?: GenerationError) => {
102
+ if (!lifecycle?.onComplete) return;
103
+
104
+ const delay = lifecycle.completeDelay ?? DEFAULT_COMPLETE_DELAY;
105
+
106
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
107
+ console.log("[Orchestrator] ⏱️ Scheduling lifecycle.onComplete:", { status, delay });
108
+ }
109
+
110
+ // Clear any existing timeout
111
+ if (completeTimeoutRef.current) {
112
+ clearTimeout(completeTimeoutRef.current);
113
+ }
114
+
115
+ completeTimeoutRef.current = setTimeout(() => {
116
+ if (!isMountedRef.current) {
117
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
118
+ console.log("[Orchestrator] ⚠️ Component unmounted, skipping onComplete");
119
+ }
120
+ return;
121
+ }
122
+
123
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
124
+ console.log("[Orchestrator] 📍 Calling lifecycle.onComplete:", status);
125
+ }
126
+
127
+ lifecycle.onComplete?.(status, result, error);
128
+
129
+ // Auto-reset if configured
130
+ if (lifecycle.autoReset) {
131
+ const resetDelay = lifecycle.resetDelay ?? DEFAULT_RESET_DELAY;
132
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
133
+ console.log("[Orchestrator] 🔄 Scheduling auto-reset in:", resetDelay);
134
+ }
135
+
136
+ if (resetTimeoutRef.current) {
137
+ clearTimeout(resetTimeoutRef.current);
138
+ }
139
+
140
+ resetTimeoutRef.current = setTimeout(() => {
141
+ if (isMountedRef.current) {
142
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
143
+ console.log("[Orchestrator] 🔄 Auto-reset triggered");
144
+ }
145
+ setState(INITIAL_STATE);
146
+ isGeneratingRef.current = false;
147
+ }
148
+ }, resetDelay);
149
+ }
150
+ }, delay);
151
+ },
152
+ [lifecycle],
153
+ );
154
+
60
155
  // Core execution logic (after all checks pass)
61
156
  const executeGeneration = useCallback(
62
157
  async (input: TInput) => {
@@ -71,18 +166,24 @@ export const useGenerationOrchestrator = <TInput, TResult>(
71
166
  if (typeof __DEV__ !== "undefined" && __DEV__) {
72
167
  console.log("[Orchestrator] 📊 Progress update:", progress);
73
168
  }
74
- setState((prev) => ({ ...prev, progress }));
169
+ if (isMountedRef.current) {
170
+ setState((prev) => ({ ...prev, progress }));
171
+ }
75
172
  });
76
173
 
77
174
  if (typeof __DEV__ !== "undefined" && __DEV__) {
78
175
  console.log("[Orchestrator] ✅ strategy.execute() completed");
79
176
  }
80
177
 
81
- setState((prev) => ({ ...prev, progress: 70 }));
178
+ if (isMountedRef.current) {
179
+ setState((prev) => ({ ...prev, progress: 70 }));
180
+ }
82
181
 
83
182
  // Save result
84
183
  if (strategy.save && userId) {
85
- setState((prev) => ({ ...prev, status: "saving" }));
184
+ if (isMountedRef.current) {
185
+ setState((prev) => ({ ...prev, status: "saving" }));
186
+ }
86
187
  if (typeof __DEV__ !== "undefined" && __DEV__) {
87
188
  console.log("[Orchestrator] 💾 Saving result...");
88
189
  }
@@ -99,7 +200,9 @@ export const useGenerationOrchestrator = <TInput, TResult>(
99
200
  }
100
201
  }
101
202
 
102
- setState((prev) => ({ ...prev, progress: 90 }));
203
+ if (isMountedRef.current) {
204
+ setState((prev) => ({ ...prev, progress: 90 }));
205
+ }
103
206
 
104
207
  // Deduct credit
105
208
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -108,7 +211,9 @@ export const useGenerationOrchestrator = <TInput, TResult>(
108
211
  await deductCredit(creditCost);
109
212
 
110
213
  // Success
111
- setState({ status: "success", isGenerating: false, progress: 100, result, error: null });
214
+ if (isMountedRef.current) {
215
+ setState({ status: "success", isGenerating: false, progress: 100, result, error: null });
216
+ }
112
217
 
113
218
  if (typeof __DEV__ !== "undefined" && __DEV__) {
114
219
  console.log("[Orchestrator] 🎉 Generation SUCCESS");
@@ -118,9 +223,13 @@ export const useGenerationOrchestrator = <TInput, TResult>(
118
223
  void showSuccess("Success", alertMessages.success);
119
224
  }
120
225
 
226
+ // Call onSuccess callback
121
227
  onSuccess?.(result);
228
+
229
+ // Handle lifecycle completion
230
+ handleLifecycleComplete("success", result);
122
231
  },
123
- [strategy, userId, alertMessages, deductCredit, showSuccess, onSuccess],
232
+ [strategy, userId, alertMessages, deductCredit, showSuccess, onSuccess, handleLifecycleComplete],
124
233
  );
125
234
 
126
235
  const generate = useCallback(
@@ -217,7 +326,9 @@ export const useGenerationOrchestrator = <TInput, TResult>(
217
326
  console.log("[Orchestrator] User cancelled after moderation warning");
218
327
  }
219
328
  isGeneratingRef.current = false;
220
- setState(INITIAL_STATE);
329
+ if (isMountedRef.current) {
330
+ setState(INITIAL_STATE);
331
+ }
221
332
  },
222
333
  async () => {
223
334
  // User continued - execute generation
@@ -228,9 +339,12 @@ export const useGenerationOrchestrator = <TInput, TResult>(
228
339
  await executeGeneration(input);
229
340
  } catch (err) {
230
341
  const error = parseError(err);
231
- setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
342
+ if (isMountedRef.current) {
343
+ setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
344
+ }
232
345
  void showError("Error", getAlertMessage(error, alertMessages));
233
346
  onError?.(error);
347
+ handleLifecycleComplete("error", undefined, error);
234
348
  } finally {
235
349
  isGeneratingRef.current = false;
236
350
  }
@@ -253,9 +367,14 @@ export const useGenerationOrchestrator = <TInput, TResult>(
253
367
  if (typeof __DEV__ !== "undefined" && __DEV__) {
254
368
  console.log("[Orchestrator] ❌ Generation ERROR:", error);
255
369
  }
256
- setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
370
+ if (isMountedRef.current) {
371
+ setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
372
+ }
257
373
  void showError("Error", getAlertMessage(error, alertMessages));
258
374
  onError?.(error);
375
+
376
+ // Handle lifecycle completion for errors
377
+ handleLifecycleComplete("error", undefined, error);
259
378
  } finally {
260
379
  isGeneratingRef.current = false;
261
380
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -274,6 +393,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
274
393
  executeGeneration,
275
394
  showError,
276
395
  onError,
396
+ handleLifecycleComplete,
277
397
  ],
278
398
  );
279
399
 
@@ -71,6 +71,25 @@ export interface CreditCallbacks {
71
71
  onCreditsExhausted?: () => void;
72
72
  }
73
73
 
74
+ /**
75
+ * Lifecycle configuration for generation flow
76
+ * Centralizes post-generation behavior (navigation, cleanup, etc.)
77
+ */
78
+ export interface LifecycleConfig {
79
+ /** Callback after generation completes (success or error) - for navigation, state updates */
80
+ onComplete?: (
81
+ status: "success" | "error",
82
+ result?: unknown,
83
+ error?: GenerationError,
84
+ ) => void;
85
+ /** Delay before calling onComplete (ms) - allows UI to show success state */
86
+ completeDelay?: number;
87
+ /** Auto-reset state after completion */
88
+ autoReset?: boolean;
89
+ /** Delay before auto-reset (ms) */
90
+ resetDelay?: number;
91
+ }
92
+
74
93
  export interface GenerationConfig {
75
94
  userId: string | undefined;
76
95
  alertMessages: AlertMessages;
@@ -83,6 +102,8 @@ export interface GenerationConfig {
83
102
  moderation?: ModerationCallbacks;
84
103
  /** Optional credit callbacks - if provided, overrides default useDeductCredit */
85
104
  credits?: CreditCallbacks;
105
+ /** Lifecycle configuration for post-generation behavior */
106
+ lifecycle?: LifecycleConfig;
86
107
  }
87
108
 
88
109
  export interface GenerationState<TResult> {