@umituz/react-native-ai-generation-content 1.20.14 → 1.20.16
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 +1 -1
- package/src/domains/result-preview/presentation/components/StarRatingPicker.tsx +1 -1
- package/src/features/couple-future/presentation/hooks/useCoupleFutureFlow.ts +63 -150
- package/src/features/couple-future/presentation/hooks/useCoupleFutureFlow.types.ts +69 -0
- package/src/features/couple-future/presentation/hooks/useCoupleFutureHandlers.ts +117 -0
- package/src/features/hd-touch-up/presentation/hooks/useHDTouchUpFeature.ts +1 -1
- package/src/features/love-message/presentation/screens/LoveMessageGeneratorScreen.tsx +7 -7
- package/src/features/photo-restoration/presentation/hooks/usePhotoRestoreFeature.ts +1 -1
- package/src/features/remove-background/presentation/hooks/useRemoveBackgroundFeature.ts +1 -1
- package/src/features/upscaling/presentation/hooks/useUpscaleFeature.ts +1 -1
- package/src/presentation/components/modals/SettingsSheet.tsx +1 -1
- package/src/presentation/hooks/generation/index.ts +1 -0
- package/src/presentation/hooks/generation/orchestrator.ts +132 -12
- package/src/presentation/hooks/generation/types.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.16",
|
|
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
|
|
4
|
+
* Uses centralized orchestrator with lifecycle management
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
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 {
|
|
16
|
+
import type { CoupleFutureFlowProps } from "./useCoupleFutureFlow.types";
|
|
17
|
+
import { useCoupleFutureHandlers } from "./useCoupleFutureHandlers";
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
export type {
|
|
20
|
+
CoupleFutureFlowConfig,
|
|
21
|
+
CoupleFutureFlowState,
|
|
22
|
+
CoupleFutureFlowActions,
|
|
23
|
+
CoupleFutureFlowProps,
|
|
24
|
+
} from "./useCoupleFutureFlow.types";
|
|
22
25
|
|
|
23
|
-
|
|
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
|
|
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__)
|
|
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__)
|
|
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
|
|
139
|
-
currentStep:
|
|
103
|
+
console.log("[CoupleFutureFlow] 📍 Step effect triggered:", {
|
|
104
|
+
currentStep: step,
|
|
140
105
|
targetStep: config.steps.GENERATING,
|
|
141
|
-
isProcessing
|
|
106
|
+
isProcessing,
|
|
142
107
|
hasStarted: hasStarted.current,
|
|
143
108
|
});
|
|
144
109
|
}
|
|
145
|
-
|
|
110
|
+
|
|
111
|
+
if (step !== config.steps.GENERATING) {
|
|
146
112
|
hasStarted.current = false;
|
|
147
113
|
return;
|
|
148
114
|
}
|
|
149
|
-
|
|
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
|
-
|
|
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]
|
|
148
|
+
console.log("[CoupleFutureFlow] 🚀 Starting generation with input");
|
|
208
149
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
|
19
|
+
return useSingleImageFeature<HDTouchUpFeatureConfig>(
|
|
20
20
|
{ config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
|
|
21
21
|
{ buildInput: (imageBase64) => ({ imageBase64 }) },
|
|
22
22
|
);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { FC, useMemo, useCallback } from "react";
|
|
7
|
-
import { View, ScrollView, StyleSheet
|
|
7
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
AtomicText,
|
|
10
10
|
AtomicButton,
|
|
@@ -98,7 +98,7 @@ export const LoveMessageGeneratorScreen: FC = () => {
|
|
|
98
98
|
|
|
99
99
|
<View style={styles.formContent}>
|
|
100
100
|
{gen.currentStep === GeneratorStep.PARTNER && (
|
|
101
|
-
<
|
|
101
|
+
<View>
|
|
102
102
|
<StepPartner
|
|
103
103
|
partnerName={gen.partnerName}
|
|
104
104
|
setPartnerName={gen.setPartnerName}
|
|
@@ -107,24 +107,24 @@ export const LoveMessageGeneratorScreen: FC = () => {
|
|
|
107
107
|
hasProfile={gen.hasProfile}
|
|
108
108
|
onEditProfile={handleNavigateToProfile}
|
|
109
109
|
/>
|
|
110
|
-
</
|
|
110
|
+
</View>
|
|
111
111
|
)}
|
|
112
112
|
|
|
113
113
|
{gen.currentStep === GeneratorStep.VIBE && (
|
|
114
|
-
<
|
|
114
|
+
<View>
|
|
115
115
|
<StepVibe
|
|
116
116
|
selectedType={gen.selectedType}
|
|
117
117
|
setSelectedType={gen.setSelectedType}
|
|
118
118
|
selectedTone={gen.selectedTone}
|
|
119
119
|
setSelectedTone={gen.setSelectedTone}
|
|
120
120
|
/>
|
|
121
|
-
</
|
|
121
|
+
</View>
|
|
122
122
|
)}
|
|
123
123
|
|
|
124
124
|
{gen.currentStep === GeneratorStep.DETAILS && (
|
|
125
|
-
<
|
|
125
|
+
<View>
|
|
126
126
|
<StepDetails details={gen.details} setDetails={gen.setDetails} />
|
|
127
|
-
</
|
|
127
|
+
</View>
|
|
128
128
|
)}
|
|
129
129
|
|
|
130
130
|
{gen.currentStep === GeneratorStep.RESULT && (
|
|
@@ -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
|
|
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
|
|
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
|
|
19
|
+
return useSingleImageFeature<UpscaleFeatureConfig>(
|
|
20
20
|
{ config: config as never, onSelectImage, onSaveImage, onBeforeProcess },
|
|
21
21
|
{
|
|
22
22
|
buildInput: (imageBase64, cfg) => ({
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
if (isMountedRef.current) {
|
|
179
|
+
setState((prev) => ({ ...prev, progress: 70 }));
|
|
180
|
+
}
|
|
82
181
|
|
|
83
182
|
// Save result
|
|
84
183
|
if (strategy.save && userId) {
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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> {
|