@umituz/react-native-ai-generation-content 1.37.14 → 1.37.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/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +2 -70
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +2 -67
- package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.types.ts +2 -4
- package/src/domains/generation/wizard/infrastructure/utils/creation-persistence.util.ts +109 -0
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +60 -55
- package/src/domains/scenarios/index.ts +1 -0
- package/src/domains/scenarios/infrastructure/scenario-helpers.ts +36 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.37.
|
|
3
|
+
"version": "1.37.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",
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Image Generation Strategy
|
|
3
|
-
* Handles image-specific generation logic
|
|
3
|
+
* Handles image-specific generation logic (execution only)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
|
|
7
6
|
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
8
7
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
9
8
|
import { DEFAULT_STYLE_VALUE, IMAGE_PROCESSING_PROMPTS } from "./wizard-strategy.constants";
|
|
@@ -80,14 +79,8 @@ function applyStyleEnhancements(prompt: string, wizardData: Record<string, unkno
|
|
|
80
79
|
// Strategy Factory
|
|
81
80
|
// ============================================================================
|
|
82
81
|
|
|
83
|
-
declare const __DEV__: boolean;
|
|
84
|
-
|
|
85
82
|
export function createImageStrategy(options: CreateImageStrategyOptions): WizardStrategy {
|
|
86
|
-
const { scenario
|
|
87
|
-
const repository = createCreationsRepository(collectionName);
|
|
88
|
-
|
|
89
|
-
let lastInputRef: WizardImageInput | null = null;
|
|
90
|
-
let processingCreationId: string | null = null;
|
|
83
|
+
const { scenario } = options;
|
|
91
84
|
|
|
92
85
|
return {
|
|
93
86
|
execute: async (input: unknown) => {
|
|
@@ -96,8 +89,6 @@ export function createImageStrategy(options: CreateImageStrategyOptions): Wizard
|
|
|
96
89
|
throw new Error("Model is required for image generation");
|
|
97
90
|
}
|
|
98
91
|
|
|
99
|
-
lastInputRef = imageInput;
|
|
100
|
-
|
|
101
92
|
const result = await executeImageGeneration(imageInput, scenario.model);
|
|
102
93
|
|
|
103
94
|
if (!result.success || !result.imageUrl) {
|
|
@@ -108,64 +99,5 @@ export function createImageStrategy(options: CreateImageStrategyOptions): Wizard
|
|
|
108
99
|
},
|
|
109
100
|
|
|
110
101
|
getCreditCost: () => 1,
|
|
111
|
-
|
|
112
|
-
saveAsProcessing: async (uid: string, input: unknown) => {
|
|
113
|
-
const imageInput = input as WizardImageInput;
|
|
114
|
-
const creationId = `${scenario.id}_${Date.now()}`;
|
|
115
|
-
processingCreationId = creationId;
|
|
116
|
-
|
|
117
|
-
await repository.create(uid, {
|
|
118
|
-
id: creationId,
|
|
119
|
-
uri: "",
|
|
120
|
-
type: scenario.id,
|
|
121
|
-
prompt: imageInput.prompt,
|
|
122
|
-
status: "processing" as const,
|
|
123
|
-
createdAt: new Date(),
|
|
124
|
-
isShared: false,
|
|
125
|
-
isFavorite: false,
|
|
126
|
-
metadata: { scenarioId: scenario.id, scenarioTitle: scenario.title },
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
130
|
-
console.log("[ImageStrategy] Saved as processing", { creationId });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return creationId;
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
save: async (result: unknown, uid: string, creationId?: string) => {
|
|
137
|
-
const input = lastInputRef;
|
|
138
|
-
const imageResult = result as { imageUrl?: string };
|
|
139
|
-
if (!input || !scenario?.id || !imageResult.imageUrl) return;
|
|
140
|
-
|
|
141
|
-
const idToUpdate = creationId || processingCreationId;
|
|
142
|
-
|
|
143
|
-
if (idToUpdate) {
|
|
144
|
-
// Update existing processing creation to completed
|
|
145
|
-
await repository.update(uid, idToUpdate, {
|
|
146
|
-
uri: imageResult.imageUrl,
|
|
147
|
-
status: "completed" as const,
|
|
148
|
-
output: { imageUrl: imageResult.imageUrl },
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
152
|
-
console.log("[ImageStrategy] Updated to completed", { creationId: idToUpdate });
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
// Fallback: create new (shouldn't happen normally)
|
|
156
|
-
await repository.create(uid, {
|
|
157
|
-
id: `${scenario.id}_${Date.now()}`,
|
|
158
|
-
uri: imageResult.imageUrl,
|
|
159
|
-
type: scenario.id,
|
|
160
|
-
prompt: input.prompt,
|
|
161
|
-
status: "completed" as const,
|
|
162
|
-
createdAt: new Date(),
|
|
163
|
-
isShared: false,
|
|
164
|
-
isFavorite: false,
|
|
165
|
-
metadata: { scenarioId: scenario.id, scenarioTitle: scenario.title },
|
|
166
|
-
output: { imageUrl: imageResult.imageUrl },
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
102
|
};
|
|
171
103
|
}
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Video Generation Strategy
|
|
3
|
-
* Handles video-specific generation logic
|
|
3
|
+
* Handles video-specific generation logic (execution only)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { executeVideoFeature } from "../../../../../infrastructure/services/video-feature-executor.service";
|
|
7
|
-
import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
|
|
8
7
|
import { buildUnifiedPrompt } from "./shared/unified-prompt-builder";
|
|
9
8
|
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
10
9
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
@@ -71,17 +70,12 @@ export async function buildVideoInput(
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
export function createVideoStrategy(options: CreateVideoStrategyOptions): WizardStrategy {
|
|
74
|
-
const { scenario
|
|
75
|
-
const repository = createCreationsRepository(collectionName);
|
|
73
|
+
const { scenario } = options;
|
|
76
74
|
const videoFeatureType = getVideoFeatureType(scenario.id);
|
|
77
75
|
|
|
78
|
-
let lastInputRef: WizardVideoInput | null = null;
|
|
79
|
-
let processingCreationId: string | null = null;
|
|
80
|
-
|
|
81
76
|
return {
|
|
82
77
|
execute: async (input: unknown) => {
|
|
83
78
|
const videoInput = input as WizardVideoInput;
|
|
84
|
-
lastInputRef = videoInput;
|
|
85
79
|
|
|
86
80
|
const result = await executeVideoFeature(videoFeatureType, {
|
|
87
81
|
sourceImageBase64: videoInput.sourceImageBase64,
|
|
@@ -102,64 +96,5 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
|
|
|
102
96
|
},
|
|
103
97
|
|
|
104
98
|
getCreditCost: () => 1,
|
|
105
|
-
|
|
106
|
-
saveAsProcessing: async (uid: string, input: unknown) => {
|
|
107
|
-
const videoInput = input as WizardVideoInput;
|
|
108
|
-
const creationId = `${scenario.id}_${Date.now()}`;
|
|
109
|
-
processingCreationId = creationId;
|
|
110
|
-
|
|
111
|
-
await repository.create(uid, {
|
|
112
|
-
id: creationId,
|
|
113
|
-
uri: "",
|
|
114
|
-
type: scenario.id,
|
|
115
|
-
prompt: videoInput.prompt,
|
|
116
|
-
status: "processing" as const,
|
|
117
|
-
createdAt: new Date(),
|
|
118
|
-
isShared: false,
|
|
119
|
-
isFavorite: false,
|
|
120
|
-
metadata: { scenarioId: scenario.id, scenarioTitle: scenario.title },
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
124
|
-
console.log("[VideoStrategy] Saved as processing", { creationId });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return creationId;
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
save: async (result: unknown, uid: string, creationId?: string) => {
|
|
131
|
-
const input = lastInputRef;
|
|
132
|
-
const videoResult = result as { videoUrl?: string };
|
|
133
|
-
if (!input || !scenario?.id || !videoResult.videoUrl) return;
|
|
134
|
-
|
|
135
|
-
const idToUpdate = creationId || processingCreationId;
|
|
136
|
-
|
|
137
|
-
if (idToUpdate) {
|
|
138
|
-
// Update existing processing creation to completed
|
|
139
|
-
await repository.update(uid, idToUpdate, {
|
|
140
|
-
uri: videoResult.videoUrl,
|
|
141
|
-
status: "completed" as const,
|
|
142
|
-
output: { videoUrl: videoResult.videoUrl },
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
146
|
-
console.log("[VideoStrategy] Updated to completed", { creationId: idToUpdate });
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
// Fallback: create new (shouldn't happen normally)
|
|
150
|
-
await repository.create(uid, {
|
|
151
|
-
id: `${scenario.id}_${Date.now()}`,
|
|
152
|
-
uri: videoResult.videoUrl,
|
|
153
|
-
type: scenario.id,
|
|
154
|
-
prompt: input.prompt,
|
|
155
|
-
status: "completed" as const,
|
|
156
|
-
createdAt: new Date(),
|
|
157
|
-
isShared: false,
|
|
158
|
-
isFavorite: false,
|
|
159
|
-
metadata: { scenarioId: scenario.id, scenarioTitle: scenario.title },
|
|
160
|
-
output: { videoUrl: videoResult.videoUrl },
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
99
|
};
|
|
165
100
|
}
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export interface WizardStrategy {
|
|
7
|
+
/** Execute the generation - returns result with URLs */
|
|
7
8
|
execute: (input: unknown) => Promise<{ imageUrl?: string; videoUrl?: string }>;
|
|
9
|
+
/** Get credit cost for this generation */
|
|
8
10
|
getCreditCost: () => number;
|
|
9
|
-
/** Save as processing when generation starts - returns creation ID */
|
|
10
|
-
saveAsProcessing?: (userId: string, input: unknown) => Promise<string>;
|
|
11
|
-
/** Update to completed when generation finishes */
|
|
12
|
-
save?: (result: unknown, userId: string, creationId?: string) => Promise<void>;
|
|
13
11
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Persistence Utility
|
|
3
|
+
* Handles all Firestore creation operations for wizard generation
|
|
4
|
+
* Single source of truth for processing → completed flow
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
|
|
8
|
+
|
|
9
|
+
declare const __DEV__: boolean;
|
|
10
|
+
|
|
11
|
+
export interface CreationPersistenceConfig {
|
|
12
|
+
readonly collectionName?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ProcessingCreationData {
|
|
16
|
+
readonly scenarioId: string;
|
|
17
|
+
readonly scenarioTitle: string;
|
|
18
|
+
readonly prompt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CompletedCreationData {
|
|
22
|
+
readonly uri: string;
|
|
23
|
+
readonly imageUrl?: string;
|
|
24
|
+
readonly videoUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a creation persistence handler for a wizard flow
|
|
29
|
+
*/
|
|
30
|
+
export function createCreationPersistence(config: CreationPersistenceConfig = {}) {
|
|
31
|
+
const { collectionName = "creations" } = config;
|
|
32
|
+
const repository = createCreationsRepository(collectionName);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
/**
|
|
36
|
+
* Save creation with status="processing" when generation starts
|
|
37
|
+
* Returns the creation ID for later update
|
|
38
|
+
*/
|
|
39
|
+
saveAsProcessing: async (
|
|
40
|
+
userId: string,
|
|
41
|
+
data: ProcessingCreationData,
|
|
42
|
+
): Promise<string> => {
|
|
43
|
+
const creationId = `${data.scenarioId}_${Date.now()}`;
|
|
44
|
+
|
|
45
|
+
await repository.create(userId, {
|
|
46
|
+
id: creationId,
|
|
47
|
+
uri: "",
|
|
48
|
+
type: data.scenarioId,
|
|
49
|
+
prompt: data.prompt,
|
|
50
|
+
status: "processing" as const,
|
|
51
|
+
createdAt: new Date(),
|
|
52
|
+
isShared: false,
|
|
53
|
+
isFavorite: false,
|
|
54
|
+
metadata: {
|
|
55
|
+
scenarioId: data.scenarioId,
|
|
56
|
+
scenarioTitle: data.scenarioTitle,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
61
|
+
console.log("[CreationPersistence] Saved as processing", { creationId });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return creationId;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Update creation to status="completed" when generation finishes
|
|
69
|
+
*/
|
|
70
|
+
updateToCompleted: async (
|
|
71
|
+
userId: string,
|
|
72
|
+
creationId: string,
|
|
73
|
+
data: CompletedCreationData,
|
|
74
|
+
): Promise<void> => {
|
|
75
|
+
await repository.update(userId, creationId, {
|
|
76
|
+
uri: data.uri,
|
|
77
|
+
status: "completed" as const,
|
|
78
|
+
output: {
|
|
79
|
+
imageUrl: data.imageUrl,
|
|
80
|
+
videoUrl: data.videoUrl,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
85
|
+
console.log("[CreationPersistence] Updated to completed", { creationId });
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Update creation to status="failed" when generation fails
|
|
91
|
+
*/
|
|
92
|
+
updateToFailed: async (
|
|
93
|
+
userId: string,
|
|
94
|
+
creationId: string,
|
|
95
|
+
error: string,
|
|
96
|
+
): Promise<void> => {
|
|
97
|
+
await repository.update(userId, creationId, {
|
|
98
|
+
status: "failed" as const,
|
|
99
|
+
metadata: { error },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
103
|
+
console.log("[CreationPersistence] Updated to failed", { creationId, error });
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type CreationPersistence = ReturnType<typeof createCreationPersistence>;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useWizardGeneration Hook
|
|
3
|
-
* Wizard generation
|
|
4
|
-
* Saves
|
|
3
|
+
* Wizard generation with Firestore persistence
|
|
4
|
+
* - Saves status="processing" at start
|
|
5
|
+
* - Updates to status="completed" on success
|
|
6
|
+
* - Updates to status="failed" on error
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { useEffect, useRef, useMemo, useCallback } from "react";
|
|
8
10
|
import { useGenerationOrchestrator } from "../../../../../presentation/hooks/generation";
|
|
9
11
|
import { createWizardStrategy, buildWizardInput } from "../../infrastructure/strategies";
|
|
12
|
+
import { createCreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
|
|
10
13
|
import type {
|
|
11
14
|
UseWizardGenerationProps,
|
|
12
15
|
UseWizardGenerationReturn,
|
|
@@ -36,47 +39,62 @@ export const useWizardGeneration = (
|
|
|
36
39
|
} = props;
|
|
37
40
|
|
|
38
41
|
const hasStarted = useRef(false);
|
|
39
|
-
const
|
|
42
|
+
const creationIdRef = useRef<string | null>(null);
|
|
43
|
+
const inputRef = useRef<{ prompt: string } | null>(null);
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log("[useWizardGeneration] Initialized", {
|
|
44
|
-
scenarioId: scenario.id,
|
|
45
|
-
outputType: scenario.outputType,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}, [scenario.id, scenario.outputType]);
|
|
45
|
+
// Persistence utility - separate from strategy
|
|
46
|
+
const persistence = useMemo(() => createCreationPersistence(), []);
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
scenario,
|
|
53
|
-
collectionName: "creations",
|
|
54
|
-
});
|
|
55
|
-
}, [scenario]);
|
|
48
|
+
// Strategy - only handles execution
|
|
49
|
+
const strategy = useMemo(() => createWizardStrategy({ scenario }), [scenario]);
|
|
56
50
|
|
|
57
51
|
const handleSuccess = useCallback(
|
|
58
|
-
(result: unknown) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
async (result: unknown) => {
|
|
53
|
+
const typedResult = result as { imageUrl?: string; videoUrl?: string };
|
|
54
|
+
const creationId = creationIdRef.current;
|
|
55
|
+
|
|
56
|
+
// Update to completed in Firestore
|
|
57
|
+
if (creationId && userId) {
|
|
58
|
+
try {
|
|
59
|
+
await persistence.updateToCompleted(userId, creationId, {
|
|
60
|
+
uri: typedResult.imageUrl || typedResult.videoUrl || "",
|
|
61
|
+
imageUrl: typedResult.imageUrl,
|
|
62
|
+
videoUrl: typedResult.videoUrl,
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
66
|
+
console.error("[useWizardGeneration] updateToCompleted error:", err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
|
-
|
|
70
|
+
|
|
71
|
+
creationIdRef.current = null;
|
|
72
|
+
inputRef.current = null;
|
|
65
73
|
onSuccess?.(result);
|
|
66
74
|
},
|
|
67
|
-
[onSuccess],
|
|
75
|
+
[userId, persistence, onSuccess],
|
|
68
76
|
);
|
|
69
77
|
|
|
70
78
|
const handleError = useCallback(
|
|
71
|
-
(err: { message: string }) => {
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
async (err: { message: string }) => {
|
|
80
|
+
const creationId = creationIdRef.current;
|
|
81
|
+
|
|
82
|
+
// Update to failed in Firestore
|
|
83
|
+
if (creationId && userId) {
|
|
84
|
+
try {
|
|
85
|
+
await persistence.updateToFailed(userId, creationId, err.message);
|
|
86
|
+
} catch (updateErr) {
|
|
87
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
88
|
+
console.error("[useWizardGeneration] updateToFailed error:", updateErr);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
74
91
|
}
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
|
|
93
|
+
creationIdRef.current = null;
|
|
94
|
+
inputRef.current = null;
|
|
77
95
|
onError?.(err.message);
|
|
78
96
|
},
|
|
79
|
-
[onError],
|
|
97
|
+
[userId, persistence, onError],
|
|
80
98
|
);
|
|
81
99
|
|
|
82
100
|
const { generate, isGenerating } = useGenerationOrchestrator(strategy, {
|
|
@@ -89,13 +107,6 @@ export const useWizardGeneration = (
|
|
|
89
107
|
|
|
90
108
|
useEffect(() => {
|
|
91
109
|
if (isGeneratingStep && !hasStarted.current && !isGenerating) {
|
|
92
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
93
|
-
console.log("[useWizardGeneration] Starting generation", {
|
|
94
|
-
scenarioId: scenario.id,
|
|
95
|
-
wizardDataKeys: Object.keys(wizardData),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
110
|
hasStarted.current = true;
|
|
100
111
|
|
|
101
112
|
buildWizardInput(wizardData, scenario)
|
|
@@ -106,11 +117,18 @@ export const useWizardGeneration = (
|
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
inputRef.current = input as { prompt: string };
|
|
121
|
+
|
|
122
|
+
// Save to Firestore with status="processing"
|
|
123
|
+
const typedInput = input as { prompt?: string };
|
|
124
|
+
if (userId && typedInput.prompt) {
|
|
111
125
|
try {
|
|
112
|
-
const creationId = await
|
|
113
|
-
|
|
126
|
+
const creationId = await persistence.saveAsProcessing(userId, {
|
|
127
|
+
scenarioId: scenario.id,
|
|
128
|
+
scenarioTitle: scenario.title || scenario.id,
|
|
129
|
+
prompt: typedInput.prompt,
|
|
130
|
+
});
|
|
131
|
+
creationIdRef.current = creationId;
|
|
114
132
|
|
|
115
133
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
116
134
|
console.log("[useWizardGeneration] Saved as processing:", creationId);
|
|
@@ -119,16 +137,12 @@ export const useWizardGeneration = (
|
|
|
119
137
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
120
138
|
console.error("[useWizardGeneration] saveAsProcessing error:", err);
|
|
121
139
|
}
|
|
122
|
-
// Continue with generation even if save fails
|
|
123
140
|
}
|
|
124
141
|
}
|
|
125
142
|
|
|
126
143
|
generate(input);
|
|
127
144
|
})
|
|
128
145
|
.catch((error) => {
|
|
129
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
130
|
-
console.error("[useWizardGeneration] Input build error:", error);
|
|
131
|
-
}
|
|
132
146
|
hasStarted.current = false;
|
|
133
147
|
onError?.(error.message);
|
|
134
148
|
});
|
|
@@ -137,16 +151,7 @@ export const useWizardGeneration = (
|
|
|
137
151
|
if (!isGeneratingStep && hasStarted.current) {
|
|
138
152
|
hasStarted.current = false;
|
|
139
153
|
}
|
|
140
|
-
}, [
|
|
141
|
-
isGeneratingStep,
|
|
142
|
-
scenario,
|
|
143
|
-
wizardData,
|
|
144
|
-
isGenerating,
|
|
145
|
-
generate,
|
|
146
|
-
onError,
|
|
147
|
-
strategy,
|
|
148
|
-
userId,
|
|
149
|
-
]);
|
|
154
|
+
}, [isGeneratingStep, scenario, wizardData, isGenerating, generate, onError, userId, persistence]);
|
|
150
155
|
|
|
151
156
|
return { isGenerating };
|
|
152
157
|
};
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Scenario, ScenarioOutputType } from "../domain/Scenario";
|
|
7
|
+
import { WizardInputType } from "../configs/wizard-input.types";
|
|
8
|
+
import { detectWizardInputType } from "../configs/wizard-input-detector";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Configuration for creating app-specific scenarios
|
|
@@ -17,6 +19,8 @@ export interface AppScenarioConfig {
|
|
|
17
19
|
readonly excludeIds?: readonly string[];
|
|
18
20
|
/** Optional filter to include only certain category IDs */
|
|
19
21
|
readonly includeCategories?: readonly string[];
|
|
22
|
+
/** Optional filter to exclude wizard input types (uses pattern detection) */
|
|
23
|
+
readonly excludeWizardInputTypes?: readonly WizardInputType[];
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -24,25 +28,24 @@ export interface AppScenarioConfig {
|
|
|
24
28
|
* Apps use this to set their desired outputType and model
|
|
25
29
|
*
|
|
26
30
|
* @example
|
|
27
|
-
* // Video generation app
|
|
31
|
+
* // Video generation app - exclude dual image scenarios
|
|
28
32
|
* const scenarios = createScenariosForApp(SCENARIOS, {
|
|
29
33
|
* outputType: "video",
|
|
30
|
-
* model: "fal-ai/veo3/image-to-video"
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // Image generation app
|
|
35
|
-
* const scenarios = createScenariosForApp(SCENARIOS, {
|
|
36
|
-
* outputType: "image",
|
|
37
|
-
* model: "fal-ai/nano-banana/edit",
|
|
38
|
-
* excludeIds: ["custom"]
|
|
34
|
+
* model: "fal-ai/veo3/image-to-video",
|
|
35
|
+
* excludeWizardInputTypes: [WizardInputType.DUAL_IMAGE]
|
|
39
36
|
* });
|
|
40
37
|
*/
|
|
41
38
|
export const createScenariosForApp = (
|
|
42
39
|
scenarios: readonly Scenario[],
|
|
43
40
|
config: AppScenarioConfig,
|
|
44
41
|
): Scenario[] => {
|
|
45
|
-
const {
|
|
42
|
+
const {
|
|
43
|
+
outputType,
|
|
44
|
+
model,
|
|
45
|
+
excludeIds,
|
|
46
|
+
includeCategories,
|
|
47
|
+
excludeWizardInputTypes,
|
|
48
|
+
} = config;
|
|
46
49
|
|
|
47
50
|
return scenarios
|
|
48
51
|
.filter((scenario) => {
|
|
@@ -59,6 +62,13 @@ export const createScenariosForApp = (
|
|
|
59
62
|
) {
|
|
60
63
|
return false;
|
|
61
64
|
}
|
|
65
|
+
// Filter by wizard input types using pattern detection
|
|
66
|
+
if (excludeWizardInputTypes && excludeWizardInputTypes.length > 0) {
|
|
67
|
+
const detectedType = detectWizardInputType(scenario.id);
|
|
68
|
+
if (excludeWizardInputTypes.includes(detectedType)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
return true;
|
|
63
73
|
})
|
|
64
74
|
.map((scenario) => ({
|
|
@@ -70,7 +80,6 @@ export const createScenariosForApp = (
|
|
|
70
80
|
|
|
71
81
|
/**
|
|
72
82
|
* Filters scenarios by output type (if they have one set)
|
|
73
|
-
* Useful for apps that have mixed scenarios with different output types
|
|
74
83
|
*
|
|
75
84
|
* @example
|
|
76
85
|
* const videoScenarios = filterScenariosByOutputType(scenarios, "video");
|
|
@@ -91,12 +100,26 @@ export const filterScenariosByCategory = (
|
|
|
91
100
|
category: string,
|
|
92
101
|
): Scenario[] => scenarios.filter((s) => s.category === category);
|
|
93
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Filters scenarios by wizard input type using pattern detection
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* const singleImageScenarios = filterScenariosByWizardInputType(
|
|
108
|
+
* scenarios,
|
|
109
|
+
* [WizardInputType.SINGLE_IMAGE]
|
|
110
|
+
* );
|
|
111
|
+
*/
|
|
112
|
+
export const filterScenariosByWizardInputType = (
|
|
113
|
+
scenarios: readonly Scenario[],
|
|
114
|
+
inputTypes: readonly WizardInputType[],
|
|
115
|
+
): Scenario[] =>
|
|
116
|
+
scenarios.filter((s) => inputTypes.includes(detectWizardInputType(s.id)));
|
|
117
|
+
|
|
94
118
|
/**
|
|
95
119
|
* Gets unique categories from scenarios
|
|
96
120
|
*
|
|
97
121
|
* @example
|
|
98
122
|
* const categories = getScenarioCategories(scenarios);
|
|
99
|
-
* // ["wedding", "fantasy", "adventure", ...]
|
|
100
123
|
*/
|
|
101
124
|
export const getScenarioCategories = (
|
|
102
125
|
scenarios: readonly Scenario[],
|