@umituz/react-native-ai-generation-content 1.34.2 → 1.35.1
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/generation/wizard/presentation/components/GenericWizardFlow.tsx +211 -121
- package/src/domains/scenarios/index.ts +10 -0
- package/src/domains/scenarios/infrastructure/scenario-registry.ts +101 -0
- package/src/infrastructure/providers/generation-config.provider.tsx +14 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.1",
|
|
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,16 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generic Wizard Flow Component
|
|
3
3
|
* Config-driven wizard for AI generation features
|
|
4
|
+
* Supports both scenario object and scenarioId (resolved from registry)
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import React, { useMemo, useCallback, useEffect, useRef, useState } from "react";
|
|
7
8
|
import { View, StyleSheet } from "react-native";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
useAlert,
|
|
12
|
+
AlertType,
|
|
13
|
+
AlertMode,
|
|
14
|
+
} from "@umituz/react-native-design-system";
|
|
9
15
|
import { useFlow } from "../../../infrastructure/flow/useFlow";
|
|
10
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
StepType,
|
|
18
|
+
type StepDefinition,
|
|
19
|
+
} from "../../../../../domain/entities/flow-config.types";
|
|
11
20
|
import type { WizardFeatureConfig } from "../../domain/entities/wizard-config.types";
|
|
12
21
|
import { buildFlowStepsFromWizard } from "../../infrastructure/builders/dynamic-step-builder";
|
|
13
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
useWizardGeneration,
|
|
24
|
+
type WizardScenarioData,
|
|
25
|
+
} from "../hooks/useWizardGeneration";
|
|
14
26
|
import type { AlertMessages } from "../../../../../presentation/hooks/generation/types";
|
|
15
27
|
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
16
28
|
import type { Creation } from "../../../../creations/domain/entities/Creation";
|
|
@@ -19,17 +31,25 @@ import { useResultActions } from "../../../../result-preview/presentation/hooks/
|
|
|
19
31
|
import { validateScenario } from "../utilities/validateScenario";
|
|
20
32
|
import { WizardStepRenderer } from "./WizardStepRenderer";
|
|
21
33
|
import { StarRatingPicker } from "../../../../result-preview/presentation/components/StarRatingPicker";
|
|
34
|
+
import {
|
|
35
|
+
getConfiguredScenario,
|
|
36
|
+
getDefaultOutputType,
|
|
37
|
+
} from "../../../../scenarios/infrastructure/scenario-registry";
|
|
22
38
|
|
|
23
39
|
declare const __DEV__: boolean;
|
|
24
40
|
|
|
25
41
|
export interface GenericWizardFlowProps {
|
|
26
42
|
readonly featureConfig: WizardFeatureConfig;
|
|
27
43
|
readonly scenario?: WizardScenarioData;
|
|
44
|
+
readonly scenarioId?: string;
|
|
28
45
|
readonly userId?: string;
|
|
29
46
|
readonly alertMessages?: AlertMessages;
|
|
30
47
|
readonly skipResultStep?: boolean;
|
|
31
48
|
readonly onStepChange?: (stepId: string, stepType: StepType | string) => void;
|
|
32
|
-
readonly onGenerationStart?: (
|
|
49
|
+
readonly onGenerationStart?: (
|
|
50
|
+
data: Record<string, unknown>,
|
|
51
|
+
proceedToGenerating: () => void,
|
|
52
|
+
) => void;
|
|
33
53
|
readonly onGenerationComplete?: (result: unknown) => void;
|
|
34
54
|
readonly onGenerationError?: (error: string) => void;
|
|
35
55
|
readonly onCreditsExhausted?: () => void;
|
|
@@ -42,42 +62,137 @@ export interface GenericWizardFlowProps {
|
|
|
42
62
|
readonly renderResult?: (result: unknown) => React.ReactElement | null;
|
|
43
63
|
}
|
|
44
64
|
|
|
45
|
-
export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = (props) => {
|
|
66
|
+
const {
|
|
67
|
+
featureConfig,
|
|
68
|
+
scenario: scenarioProp,
|
|
69
|
+
scenarioId,
|
|
70
|
+
userId,
|
|
71
|
+
alertMessages,
|
|
72
|
+
skipResultStep = false,
|
|
73
|
+
onStepChange,
|
|
74
|
+
onGenerationStart,
|
|
75
|
+
onGenerationComplete,
|
|
76
|
+
onGenerationError,
|
|
77
|
+
onCreditsExhausted,
|
|
78
|
+
onBack,
|
|
79
|
+
onTryAgain,
|
|
80
|
+
t,
|
|
81
|
+
renderPreview,
|
|
82
|
+
renderGenerating,
|
|
83
|
+
renderResult,
|
|
84
|
+
} = props;
|
|
85
|
+
|
|
64
86
|
const tokens = useAppDesignTokens();
|
|
65
87
|
const alert = useAlert();
|
|
88
|
+
|
|
89
|
+
// Resolve scenario from prop or registry
|
|
90
|
+
const scenario = useMemo<WizardScenarioData | undefined>(() => {
|
|
91
|
+
if (scenarioProp) {
|
|
92
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
93
|
+
console.log("[GenericWizardFlow] Using scenario from prop:", {
|
|
94
|
+
id: scenarioProp.id,
|
|
95
|
+
outputType: scenarioProp.outputType,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return scenarioProp;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (scenarioId) {
|
|
102
|
+
const found = getConfiguredScenario(scenarioId);
|
|
103
|
+
if (found) {
|
|
104
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
105
|
+
console.log("[GenericWizardFlow] Resolved from registry:", {
|
|
106
|
+
id: found.id,
|
|
107
|
+
outputType: found.outputType,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return found;
|
|
111
|
+
}
|
|
112
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
113
|
+
console.warn("[GenericWizardFlow] Scenario not in registry:", scenarioId);
|
|
114
|
+
}
|
|
115
|
+
return { id: scenarioId, outputType: getDefaultOutputType() };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return undefined;
|
|
119
|
+
}, [scenarioProp, scenarioId]);
|
|
120
|
+
|
|
121
|
+
const validatedScenario = useMemo(
|
|
122
|
+
() => validateScenario(scenario),
|
|
123
|
+
[scenario],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<WizardFlowContent
|
|
128
|
+
featureConfig={featureConfig}
|
|
129
|
+
scenario={scenario}
|
|
130
|
+
validatedScenario={validatedScenario}
|
|
131
|
+
userId={userId}
|
|
132
|
+
alertMessages={alertMessages}
|
|
133
|
+
skipResultStep={skipResultStep}
|
|
134
|
+
onStepChange={onStepChange}
|
|
135
|
+
onGenerationStart={onGenerationStart}
|
|
136
|
+
onGenerationComplete={onGenerationComplete}
|
|
137
|
+
onGenerationError={onGenerationError}
|
|
138
|
+
onCreditsExhausted={onCreditsExhausted}
|
|
139
|
+
onBack={onBack}
|
|
140
|
+
onTryAgain={onTryAgain}
|
|
141
|
+
t={t}
|
|
142
|
+
tokens={tokens}
|
|
143
|
+
alert={alert}
|
|
144
|
+
renderPreview={renderPreview}
|
|
145
|
+
renderGenerating={renderGenerating}
|
|
146
|
+
renderResult={renderResult}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
interface WizardFlowContentProps
|
|
152
|
+
extends Omit<GenericWizardFlowProps, "scenarioId" | "translations"> {
|
|
153
|
+
readonly validatedScenario: WizardScenarioData;
|
|
154
|
+
readonly tokens: ReturnType<typeof useAppDesignTokens>;
|
|
155
|
+
readonly alert: ReturnType<typeof useAlert>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const WizardFlowContent: React.FC<WizardFlowContentProps> = (props) => {
|
|
159
|
+
const {
|
|
160
|
+
featureConfig,
|
|
161
|
+
scenario,
|
|
162
|
+
validatedScenario,
|
|
163
|
+
userId,
|
|
164
|
+
alertMessages,
|
|
165
|
+
skipResultStep = false,
|
|
166
|
+
onStepChange,
|
|
167
|
+
onGenerationStart,
|
|
168
|
+
onGenerationComplete,
|
|
169
|
+
onGenerationError,
|
|
170
|
+
onCreditsExhausted,
|
|
171
|
+
onBack,
|
|
172
|
+
onTryAgain,
|
|
173
|
+
t,
|
|
174
|
+
tokens,
|
|
175
|
+
alert,
|
|
176
|
+
renderPreview,
|
|
177
|
+
renderGenerating,
|
|
178
|
+
renderResult,
|
|
179
|
+
} = props;
|
|
180
|
+
|
|
66
181
|
const [currentCreation, setCurrentCreation] = useState<Creation | null>(null);
|
|
67
182
|
const [showRatingPicker, setShowRatingPicker] = useState(false);
|
|
68
183
|
const [hasRated, setHasRated] = useState(false);
|
|
69
|
-
const [, setIsGeneratingDismissed] = useState(false);
|
|
70
184
|
const prevStepIdRef = useRef<string | undefined>(undefined);
|
|
71
|
-
|
|
72
185
|
const repository = useMemo(() => createCreationsRepository("creations"), []);
|
|
73
186
|
|
|
74
|
-
const flowSteps = useMemo<StepDefinition[]>(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
187
|
+
const flowSteps = useMemo<StepDefinition[]>(
|
|
188
|
+
() =>
|
|
189
|
+
buildFlowStepsFromWizard(featureConfig, {
|
|
190
|
+
includePreview: true,
|
|
191
|
+
includeGenerating: true,
|
|
192
|
+
includeResult: !skipResultStep,
|
|
193
|
+
}),
|
|
194
|
+
[featureConfig, skipResultStep],
|
|
195
|
+
);
|
|
81
196
|
|
|
82
197
|
const flow = useFlow({ steps: flowSteps, initialStepIndex: 0 });
|
|
83
198
|
const {
|
|
@@ -92,30 +207,25 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
92
207
|
setResult,
|
|
93
208
|
} = flow;
|
|
94
209
|
|
|
95
|
-
const resultImageUrl =
|
|
210
|
+
const resultImageUrl =
|
|
211
|
+
currentCreation?.output?.imageUrl || currentCreation?.uri || "";
|
|
96
212
|
const resultVideoUrl = currentCreation?.output?.videoUrl || "";
|
|
97
|
-
const { isSaving, isSharing, handleDownload, handleShare } = useResultActions(
|
|
98
|
-
imageUrl: resultImageUrl,
|
|
99
|
-
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const validatedScenario = useMemo(() => validateScenario(scenario), [scenario]);
|
|
103
|
-
|
|
104
|
-
const handleGenerationComplete = useCallback((result: unknown) => {
|
|
105
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
106
|
-
console.log("[GenericWizardFlow] Generation completed", { skipResultStep });
|
|
107
|
-
}
|
|
108
|
-
setResult(result);
|
|
109
|
-
setCurrentCreation(result as Creation);
|
|
110
|
-
|
|
111
|
-
// Call onGenerationComplete first so parent can navigate if needed
|
|
112
|
-
onGenerationComplete?.(result);
|
|
213
|
+
const { isSaving, isSharing, handleDownload, handleShare } = useResultActions(
|
|
214
|
+
{ imageUrl: resultImageUrl, videoUrl: resultVideoUrl },
|
|
215
|
+
);
|
|
113
216
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
217
|
+
const handleGenerationComplete = useCallback(
|
|
218
|
+
(result: unknown) => {
|
|
219
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
220
|
+
console.log("[WizardFlowContent] Generation completed");
|
|
221
|
+
}
|
|
222
|
+
setResult(result);
|
|
223
|
+
setCurrentCreation(result as Creation);
|
|
224
|
+
onGenerationComplete?.(result);
|
|
225
|
+
if (!skipResultStep) nextStep();
|
|
226
|
+
},
|
|
227
|
+
[setResult, nextStep, onGenerationComplete, skipResultStep],
|
|
228
|
+
);
|
|
119
229
|
|
|
120
230
|
useWizardGeneration({
|
|
121
231
|
scenario: validatedScenario,
|
|
@@ -129,92 +239,74 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
129
239
|
});
|
|
130
240
|
|
|
131
241
|
useEffect(() => {
|
|
132
|
-
if (currentStep && onStepChange) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
prevStepIdRef.current = currentStepId;
|
|
136
|
-
onStepChange(currentStep.id, currentStep.type);
|
|
137
|
-
// Reset dismissed state when entering generating step
|
|
138
|
-
if (currentStep.type === StepType.GENERATING) {
|
|
139
|
-
setIsGeneratingDismissed(false);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
242
|
+
if (currentStep && onStepChange && prevStepIdRef.current !== currentStep.id) {
|
|
243
|
+
prevStepIdRef.current = currentStep.id;
|
|
244
|
+
onStepChange(currentStep.id, currentStep.type);
|
|
142
245
|
}
|
|
143
|
-
}, [currentStep,
|
|
246
|
+
}, [currentStep, onStepChange]);
|
|
144
247
|
|
|
145
|
-
// Handle dismiss generating - go back but generation continues in background
|
|
146
248
|
const handleDismissGenerating = useCallback(() => {
|
|
147
249
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
148
|
-
console.log("[
|
|
250
|
+
console.log("[WizardFlowContent] Dismissing - generation continues");
|
|
149
251
|
}
|
|
150
|
-
setIsGeneratingDismissed(true);
|
|
151
|
-
// Show alert that generation continues
|
|
152
252
|
alert.show(
|
|
153
253
|
AlertType.INFO,
|
|
154
254
|
AlertMode.TOAST,
|
|
155
255
|
t("generator.backgroundTitle"),
|
|
156
|
-
t("generator.backgroundMessage")
|
|
256
|
+
t("generator.backgroundMessage"),
|
|
157
257
|
);
|
|
158
|
-
// Go back to previous step (or close)
|
|
159
258
|
onBack?.();
|
|
160
259
|
}, [alert, t, onBack]);
|
|
161
260
|
|
|
162
261
|
const handleBack = useCallback(() => {
|
|
163
|
-
if (currentStepIndex === 0)
|
|
164
|
-
|
|
165
|
-
} else {
|
|
166
|
-
previousStep();
|
|
167
|
-
}
|
|
262
|
+
if (currentStepIndex === 0) onBack?.();
|
|
263
|
+
else previousStep();
|
|
168
264
|
}, [currentStepIndex, previousStep, onBack]);
|
|
169
265
|
|
|
170
|
-
// Wrapper for nextStep that checks onGenerationStart before transitioning to GENERATING
|
|
171
266
|
const handleNextStep = useCallback(() => {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (nextStepDef?.type === StepType.GENERATING) {
|
|
177
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
178
|
-
console.log("[GenericWizardFlow] About to enter GENERATING step, calling onGenerationStart");
|
|
179
|
-
}
|
|
180
|
-
if (onGenerationStart) {
|
|
181
|
-
onGenerationStart(customData, () => {
|
|
182
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
183
|
-
console.log("[GenericWizardFlow] onGenerationStart callback invoked, proceeding to generation");
|
|
184
|
-
}
|
|
185
|
-
nextStep();
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
267
|
+
const nextStepDef = flowSteps[currentStepIndex + 1];
|
|
268
|
+
if (nextStepDef?.type === StepType.GENERATING && onGenerationStart) {
|
|
269
|
+
onGenerationStart(customData, nextStep);
|
|
270
|
+
return;
|
|
189
271
|
}
|
|
190
|
-
|
|
191
272
|
nextStep();
|
|
192
273
|
}, [currentStepIndex, flowSteps, customData, onGenerationStart, nextStep]);
|
|
193
274
|
|
|
194
|
-
const handlePhotoContinue = useCallback(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
setShowRatingPicker(true);
|
|
202
|
-
}, []);
|
|
203
|
-
|
|
204
|
-
const handleSubmitRating = useCallback(async (rating: number, description: string) => {
|
|
205
|
-
if (!currentCreation?.id || !userId) return;
|
|
206
|
-
const success = await repository.rate(userId, currentCreation.id, rating, description);
|
|
207
|
-
if (success) {
|
|
208
|
-
setHasRated(true);
|
|
209
|
-
alert.show(AlertType.SUCCESS, AlertMode.TOAST, t("result.rateSuccessTitle"), t("result.rateSuccessMessage"));
|
|
210
|
-
}
|
|
211
|
-
setShowRatingPicker(false);
|
|
212
|
-
}, [currentCreation, userId, repository, alert, t]);
|
|
275
|
+
const handlePhotoContinue = useCallback(
|
|
276
|
+
(stepId: string, image: UploadedImage) => {
|
|
277
|
+
setCustomData(stepId, image);
|
|
278
|
+
handleNextStep();
|
|
279
|
+
},
|
|
280
|
+
[setCustomData, handleNextStep],
|
|
281
|
+
);
|
|
213
282
|
|
|
214
|
-
const
|
|
283
|
+
const handleSubmitRating = useCallback(
|
|
284
|
+
async (rating: number, description: string) => {
|
|
285
|
+
if (!currentCreation?.id || !userId) return;
|
|
286
|
+
const success = await repository.rate(
|
|
287
|
+
userId,
|
|
288
|
+
currentCreation.id,
|
|
289
|
+
rating,
|
|
290
|
+
description,
|
|
291
|
+
);
|
|
292
|
+
if (success) {
|
|
293
|
+
setHasRated(true);
|
|
294
|
+
alert.show(
|
|
295
|
+
AlertType.SUCCESS,
|
|
296
|
+
AlertMode.TOAST,
|
|
297
|
+
t("result.rateSuccessTitle"),
|
|
298
|
+
t("result.rateSuccessMessage"),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
setShowRatingPicker(false);
|
|
302
|
+
},
|
|
303
|
+
[currentCreation, userId, repository, alert, t],
|
|
304
|
+
);
|
|
215
305
|
|
|
216
306
|
return (
|
|
217
|
-
<View
|
|
307
|
+
<View
|
|
308
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
309
|
+
>
|
|
218
310
|
<WizardStepRenderer
|
|
219
311
|
step={currentStep}
|
|
220
312
|
scenario={scenario}
|
|
@@ -223,13 +315,13 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
223
315
|
generationResult={generationResult}
|
|
224
316
|
isSaving={isSaving}
|
|
225
317
|
isSharing={isSharing}
|
|
226
|
-
showRating={
|
|
318
|
+
showRating={Boolean(userId) && !hasRated}
|
|
227
319
|
onNext={handleNextStep}
|
|
228
320
|
onBack={handleBack}
|
|
229
321
|
onPhotoContinue={handlePhotoContinue}
|
|
230
322
|
onDownload={handleDownload}
|
|
231
323
|
onShare={handleShare}
|
|
232
|
-
onRate={
|
|
324
|
+
onRate={() => setShowRatingPicker(true)}
|
|
233
325
|
onTryAgain={onTryAgain}
|
|
234
326
|
onDismissGenerating={handleDismissGenerating}
|
|
235
327
|
t={t}
|
|
@@ -251,7 +343,5 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
251
343
|
};
|
|
252
344
|
|
|
253
345
|
const styles = StyleSheet.create({
|
|
254
|
-
container: {
|
|
255
|
-
flex: 1,
|
|
256
|
-
},
|
|
346
|
+
container: { flex: 1 },
|
|
257
347
|
});
|
|
@@ -21,6 +21,16 @@ export {
|
|
|
21
21
|
} from "./infrastructure/scenario-helpers";
|
|
22
22
|
export type { AppScenarioConfig } from "./infrastructure/scenario-helpers";
|
|
23
23
|
|
|
24
|
+
// Scenario Registry - Singleton for app configuration
|
|
25
|
+
export {
|
|
26
|
+
configureScenarios,
|
|
27
|
+
getConfiguredScenario,
|
|
28
|
+
getDefaultOutputType,
|
|
29
|
+
isScenariosConfigured,
|
|
30
|
+
getAllConfiguredScenarios,
|
|
31
|
+
} from "./infrastructure/scenario-registry";
|
|
32
|
+
export type { ConfiguredScenario } from "./infrastructure/scenario-registry";
|
|
33
|
+
|
|
24
34
|
// Utils
|
|
25
35
|
export { createStoryTemplate } from "./infrastructure/utils/scenario-utils";
|
|
26
36
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario Registry
|
|
3
|
+
* Singleton registry for app-configured scenarios
|
|
4
|
+
* Apps configure once at startup, package uses internally
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Scenario, ScenarioOutputType } from "../domain/Scenario";
|
|
8
|
+
|
|
9
|
+
declare const __DEV__: boolean;
|
|
10
|
+
|
|
11
|
+
/** Configured scenario with required outputType */
|
|
12
|
+
export interface ConfiguredScenario extends Scenario {
|
|
13
|
+
readonly outputType: ScenarioOutputType;
|
|
14
|
+
readonly [key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ScenarioRegistryState {
|
|
18
|
+
scenarios: Map<string, ConfiguredScenario>;
|
|
19
|
+
defaultOutputType: ScenarioOutputType;
|
|
20
|
+
isConfigured: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const state: ScenarioRegistryState = {
|
|
24
|
+
scenarios: new Map(),
|
|
25
|
+
defaultOutputType: "video",
|
|
26
|
+
isConfigured: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Configure scenarios for this app
|
|
31
|
+
* Call once at app startup
|
|
32
|
+
*/
|
|
33
|
+
export const configureScenarios = (
|
|
34
|
+
scenarios: readonly ConfiguredScenario[],
|
|
35
|
+
defaultOutputType: ScenarioOutputType = "video",
|
|
36
|
+
): void => {
|
|
37
|
+
state.scenarios.clear();
|
|
38
|
+
|
|
39
|
+
for (const scenario of scenarios) {
|
|
40
|
+
state.scenarios.set(scenario.id, scenario);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
state.defaultOutputType = defaultOutputType;
|
|
44
|
+
state.isConfigured = true;
|
|
45
|
+
|
|
46
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
47
|
+
console.log("[ScenarioRegistry] Configured:", {
|
|
48
|
+
count: scenarios.length,
|
|
49
|
+
defaultOutputType,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get scenario by ID from configured scenarios
|
|
56
|
+
*/
|
|
57
|
+
export const getConfiguredScenario = (
|
|
58
|
+
id: string,
|
|
59
|
+
): ConfiguredScenario | undefined => {
|
|
60
|
+
const found = state.scenarios.get(id);
|
|
61
|
+
|
|
62
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
63
|
+
console.log("[ScenarioRegistry] getConfiguredScenario:", {
|
|
64
|
+
id,
|
|
65
|
+
found: !!found,
|
|
66
|
+
outputType: found?.outputType,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return found;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get default output type for this app
|
|
75
|
+
*/
|
|
76
|
+
export const getDefaultOutputType = (): ScenarioOutputType => {
|
|
77
|
+
return state.defaultOutputType;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if scenarios are configured
|
|
82
|
+
*/
|
|
83
|
+
export const isScenariosConfigured = (): boolean => {
|
|
84
|
+
return state.isConfigured;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get all configured scenarios
|
|
89
|
+
*/
|
|
90
|
+
export const getAllConfiguredScenarios = (): readonly ConfiguredScenario[] => {
|
|
91
|
+
return Array.from(state.scenarios.values());
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset registry (for testing)
|
|
96
|
+
*/
|
|
97
|
+
export const resetScenarioRegistry = (): void => {
|
|
98
|
+
state.scenarios.clear();
|
|
99
|
+
state.defaultOutputType = "video";
|
|
100
|
+
state.isConfigured = false;
|
|
101
|
+
};
|
|
@@ -1,100 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Config Provider
|
|
3
|
-
* Provides app-specific
|
|
4
|
-
*
|
|
3
|
+
* Provides app-specific AI models configuration
|
|
4
|
+
* For scenarios, use configureScenarios() from scenario-registry
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React, { createContext, useContext, type ReactNode } from "react";
|
|
8
8
|
|
|
9
9
|
declare const __DEV__: boolean;
|
|
10
10
|
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Types
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
11
|
export interface GenerationModels {
|
|
16
|
-
/** Image generation with face identity preservation (couple photos) */
|
|
17
12
|
readonly imageCoupleMultiRef?: string;
|
|
18
|
-
/** Text-to-image generation */
|
|
19
13
|
readonly imageTextToImage?: string;
|
|
20
|
-
/** Image-to-video generation */
|
|
21
14
|
readonly imageToVideo?: string;
|
|
22
|
-
/** Text-to-video generation */
|
|
23
15
|
readonly textToVideo?: string;
|
|
24
|
-
/** AI Kiss video */
|
|
25
16
|
readonly aiKiss?: string;
|
|
26
|
-
/** AI Hug video */
|
|
27
17
|
readonly aiHug?: string;
|
|
28
|
-
/** Face swap */
|
|
29
18
|
readonly faceSwap?: string;
|
|
30
|
-
/** Meme generation (caption) */
|
|
31
19
|
readonly memeCaption?: string;
|
|
32
|
-
/** Meme generation (image) */
|
|
33
20
|
readonly memeImage?: string;
|
|
34
|
-
/** Text to voice */
|
|
35
21
|
readonly textToVoice?: string;
|
|
36
22
|
}
|
|
37
23
|
|
|
38
24
|
export interface GenerationConfigValue {
|
|
39
|
-
/** AI models configuration from app */
|
|
40
25
|
readonly models: GenerationModels;
|
|
41
|
-
/** Get model for specific feature type */
|
|
42
26
|
readonly getModel: (featureType: keyof GenerationModels) => string;
|
|
43
27
|
}
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const GenerationConfigContext = createContext<GenerationConfigValue | null>(null);
|
|
50
|
-
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// Provider
|
|
53
|
-
// ============================================================================
|
|
29
|
+
const GenerationConfigContext = createContext<GenerationConfigValue | null>(
|
|
30
|
+
null,
|
|
31
|
+
);
|
|
54
32
|
|
|
55
33
|
export interface GenerationConfigProviderProps {
|
|
56
34
|
readonly children: ReactNode;
|
|
57
35
|
readonly models: GenerationModels;
|
|
58
36
|
}
|
|
59
37
|
|
|
60
|
-
export const GenerationConfigProvider: React.FC<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}) => {
|
|
38
|
+
export const GenerationConfigProvider: React.FC<
|
|
39
|
+
GenerationConfigProviderProps
|
|
40
|
+
> = ({ children, models }) => {
|
|
64
41
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
65
42
|
const configuredModels = Object.entries(models)
|
|
66
43
|
.filter(([, value]) => !!value)
|
|
67
44
|
.map(([key]) => key);
|
|
68
|
-
|
|
69
|
-
console.log("[GenerationConfigProvider] Initialized with models:", {
|
|
70
|
-
configured: configuredModels,
|
|
71
|
-
imageCoupleMultiRef: models.imageCoupleMultiRef || "not configured",
|
|
72
|
-
imageTextToImage: models.imageTextToImage || "not configured",
|
|
73
|
-
imageToVideo: models.imageToVideo || "not configured",
|
|
74
|
-
textToVideo: models.textToVideo || "not configured",
|
|
75
|
-
});
|
|
45
|
+
console.log("[GenerationConfigProvider] Models:", configuredModels);
|
|
76
46
|
}
|
|
77
47
|
|
|
78
48
|
const getModel = (featureType: keyof GenerationModels): string => {
|
|
79
49
|
const model = models[featureType];
|
|
80
50
|
if (!model) {
|
|
81
|
-
const
|
|
82
|
-
(key) => models[key as keyof GenerationModels]
|
|
51
|
+
const available = Object.keys(models).filter(
|
|
52
|
+
(key) => models[key as keyof GenerationModels],
|
|
83
53
|
);
|
|
84
|
-
|
|
85
54
|
throw new Error(
|
|
86
|
-
`Model not configured
|
|
87
|
-
`This app only supports: ${availableModels.join(", ") || "none"}.\n` +
|
|
88
|
-
`Please configure '${featureType}' in your GenerationConfigProvider if you need it.`
|
|
55
|
+
`Model not configured: ${featureType}. Available: ${available.join(", ") || "none"}`,
|
|
89
56
|
);
|
|
90
57
|
}
|
|
91
58
|
return model;
|
|
92
59
|
};
|
|
93
60
|
|
|
94
|
-
const value: GenerationConfigValue = {
|
|
95
|
-
models,
|
|
96
|
-
getModel,
|
|
97
|
-
};
|
|
61
|
+
const value: GenerationConfigValue = { models, getModel };
|
|
98
62
|
|
|
99
63
|
return (
|
|
100
64
|
<GenerationConfigContext.Provider value={value}>
|
|
@@ -103,19 +67,12 @@ export const GenerationConfigProvider: React.FC<GenerationConfigProviderProps> =
|
|
|
103
67
|
);
|
|
104
68
|
};
|
|
105
69
|
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// Hook
|
|
108
|
-
// ============================================================================
|
|
109
|
-
|
|
110
70
|
export const useGenerationConfig = (): GenerationConfigValue => {
|
|
111
71
|
const context = useContext(GenerationConfigContext);
|
|
112
|
-
|
|
113
72
|
if (!context) {
|
|
114
73
|
throw new Error(
|
|
115
|
-
"useGenerationConfig must be used within GenerationConfigProvider
|
|
116
|
-
"Wrap your app with <GenerationConfigProvider models={{...}}>"
|
|
74
|
+
"useGenerationConfig must be used within GenerationConfigProvider",
|
|
117
75
|
);
|
|
118
76
|
}
|
|
119
|
-
|
|
120
77
|
return context;
|
|
121
78
|
};
|