@umituz/react-native-ai-generation-content 1.37.13 → 1.37.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.37.13",
3
+ "version": "1.37.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,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";
@@ -81,10 +80,7 @@ function applyStyleEnhancements(prompt: string, wizardData: Record<string, unkno
81
80
  // ============================================================================
82
81
 
83
82
  export function createImageStrategy(options: CreateImageStrategyOptions): WizardStrategy {
84
- const { scenario, collectionName = "creations" } = options;
85
- const repository = createCreationsRepository(collectionName);
86
-
87
- let lastInputRef: WizardImageInput | null = null;
83
+ const { scenario } = options;
88
84
 
89
85
  return {
90
86
  execute: async (input: unknown) => {
@@ -93,8 +89,6 @@ export function createImageStrategy(options: CreateImageStrategyOptions): Wizard
93
89
  throw new Error("Model is required for image generation");
94
90
  }
95
91
 
96
- lastInputRef = imageInput;
97
-
98
92
  const result = await executeImageGeneration(imageInput, scenario.model);
99
93
 
100
94
  if (!result.success || !result.imageUrl) {
@@ -105,29 +99,5 @@ export function createImageStrategy(options: CreateImageStrategyOptions): Wizard
105
99
  },
106
100
 
107
101
  getCreditCost: () => 1,
108
-
109
- save: async (result: unknown, uid) => {
110
- const input = lastInputRef;
111
- const imageResult = result as { imageUrl?: string };
112
- if (!input || !scenario?.id || !imageResult.imageUrl) return;
113
-
114
- const creation = {
115
- id: `${scenario.id}_${Date.now()}`,
116
- uri: imageResult.imageUrl,
117
- type: scenario.id,
118
- prompt: input.prompt,
119
- status: "completed" as const,
120
- createdAt: new Date(),
121
- isShared: false,
122
- isFavorite: false,
123
- metadata: {
124
- scenarioId: scenario.id,
125
- scenarioTitle: scenario.title,
126
- },
127
- output: { imageUrl: imageResult.imageUrl },
128
- };
129
-
130
- await repository.create(uid, creation);
131
- },
132
102
  };
133
103
  }
@@ -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,16 +70,12 @@ export async function buildVideoInput(
71
70
  }
72
71
 
73
72
  export function createVideoStrategy(options: CreateVideoStrategyOptions): WizardStrategy {
74
- const { scenario, collectionName = "creations" } = options;
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
-
80
76
  return {
81
77
  execute: async (input: unknown) => {
82
78
  const videoInput = input as WizardVideoInput;
83
- lastInputRef = videoInput;
84
79
 
85
80
  const result = await executeVideoFeature(videoFeatureType, {
86
81
  sourceImageBase64: videoInput.sourceImageBase64,
@@ -101,24 +96,5 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
101
96
  },
102
97
 
103
98
  getCreditCost: () => 1,
104
-
105
- save: async (result: unknown, uid) => {
106
- const input = lastInputRef;
107
- const videoResult = result as { videoUrl?: string };
108
- if (!input || !scenario?.id || !videoResult.videoUrl) return;
109
-
110
- await repository.create(uid, {
111
- id: `${scenario.id}_${Date.now()}`,
112
- uri: videoResult.videoUrl,
113
- type: scenario.id,
114
- prompt: input.prompt,
115
- status: "completed" as const,
116
- createdAt: new Date(),
117
- isShared: false,
118
- isFavorite: false,
119
- metadata: { scenarioId: scenario.id, scenarioTitle: scenario.title },
120
- output: { videoUrl: videoResult.videoUrl },
121
- });
122
- },
123
99
  };
124
100
  }
@@ -4,7 +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?: (result: unknown, userId: string) => Promise<void>;
10
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,13 +1,15 @@
1
1
  /**
2
2
  * useWizardGeneration Hook
3
- * Wizard generation using orchestrator + strategy factory pattern
4
- * Includes background job tracking for CreationsGallery display
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
- import { usePendingJobs } from "../../../../../presentation/hooks/use-pending-jobs";
10
11
  import { createWizardStrategy, buildWizardInput } from "../../infrastructure/strategies";
12
+ import { createCreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
11
13
  import type {
12
14
  UseWizardGenerationProps,
13
15
  UseWizardGenerationReturn,
@@ -34,63 +36,65 @@ export const useWizardGeneration = (
34
36
  onSuccess,
35
37
  onError,
36
38
  onCreditsExhausted,
37
- trackAsBackgroundJob = true,
38
39
  } = props;
39
40
 
40
41
  const hasStarted = useRef(false);
41
- const currentJobIdRef = useRef<string | null>(null);
42
+ const creationIdRef = useRef<string | null>(null);
43
+ const inputRef = useRef<{ prompt: string } | null>(null);
42
44
 
43
- const { addJob, updateJob, removeJob } = usePendingJobs();
45
+ // Persistence utility - separate from strategy
46
+ const persistence = useMemo(() => createCreationPersistence(), []);
44
47
 
45
- useEffect(() => {
46
- if (typeof __DEV__ !== "undefined" && __DEV__) {
47
- console.log("[useWizardGeneration] Initialized", {
48
- scenarioId: scenario.id,
49
- outputType: scenario.outputType,
50
- trackAsBackgroundJob,
51
- });
52
- }
53
- }, [scenario.id, scenario.outputType, trackAsBackgroundJob]);
54
-
55
- const strategy = useMemo(() => {
56
- return createWizardStrategy({
57
- scenario,
58
- collectionName: "creations",
59
- });
60
- }, [scenario]);
48
+ // Strategy - only handles execution
49
+ const strategy = useMemo(() => createWizardStrategy({ scenario }), [scenario]);
61
50
 
62
51
  const handleSuccess = useCallback(
63
- (result: unknown) => {
64
- if (typeof __DEV__ !== "undefined" && __DEV__) {
65
- console.log("[useWizardGeneration] Success");
66
- }
67
-
68
- if (trackAsBackgroundJob && currentJobIdRef.current) {
69
- removeJob(currentJobIdRef.current);
70
- currentJobIdRef.current = null;
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
+ }
71
69
  }
72
70
 
71
+ creationIdRef.current = null;
72
+ inputRef.current = null;
73
73
  onSuccess?.(result);
74
74
  },
75
- [trackAsBackgroundJob, removeJob, onSuccess],
75
+ [userId, persistence, onSuccess],
76
76
  );
77
77
 
78
78
  const handleError = useCallback(
79
- (err: { message: string }) => {
80
- if (typeof __DEV__ !== "undefined" && __DEV__) {
81
- console.log("[useWizardGeneration] Error:", err.message);
82
- }
83
-
84
- if (trackAsBackgroundJob && currentJobIdRef.current) {
85
- updateJob({
86
- id: currentJobIdRef.current,
87
- updates: { status: "failed", error: err.message, progress: 0 },
88
- });
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
+ }
89
91
  }
90
92
 
93
+ creationIdRef.current = null;
94
+ inputRef.current = null;
91
95
  onError?.(err.message);
92
96
  },
93
- [trackAsBackgroundJob, updateJob, onError],
97
+ [userId, persistence, onError],
94
98
  );
95
99
 
96
100
  const { generate, isGenerating } = useGenerationOrchestrator(strategy, {
@@ -103,50 +107,42 @@ export const useWizardGeneration = (
103
107
 
104
108
  useEffect(() => {
105
109
  if (isGeneratingStep && !hasStarted.current && !isGenerating) {
106
- if (typeof __DEV__ !== "undefined" && __DEV__) {
107
- console.log("[useWizardGeneration] Starting generation", {
108
- scenarioId: scenario.id,
109
- wizardDataKeys: Object.keys(wizardData),
110
- });
111
- }
112
-
113
110
  hasStarted.current = true;
114
111
 
115
112
  buildWizardInput(wizardData, scenario)
116
- .then((input) => {
113
+ .then(async (input) => {
117
114
  if (!input) {
118
115
  hasStarted.current = false;
119
116
  onError?.("Failed to build generation input");
120
117
  return;
121
118
  }
122
119
 
123
- if (trackAsBackgroundJob && scenario.outputType) {
124
- const jobId = `wizard-${scenario.id}-${Date.now()}`;
125
- currentJobIdRef.current = jobId;
120
+ inputRef.current = input as { prompt: string };
126
121
 
127
- addJob({
128
- id: jobId,
129
- input: {
122
+ // Save to Firestore with status="processing"
123
+ const typedInput = input as { prompt?: string };
124
+ if (userId && typedInput.prompt) {
125
+ try {
126
+ const creationId = await persistence.saveAsProcessing(userId, {
130
127
  scenarioId: scenario.id,
131
- scenarioTitle: scenario.title,
132
- outputType: scenario.outputType,
133
- },
134
- type: scenario.outputType,
135
- status: "processing",
136
- progress: 10,
137
- });
138
-
139
- if (typeof __DEV__ !== "undefined" && __DEV__) {
140
- console.log("[useWizardGeneration] Created background job:", jobId);
128
+ scenarioTitle: scenario.title || scenario.id,
129
+ prompt: typedInput.prompt,
130
+ });
131
+ creationIdRef.current = creationId;
132
+
133
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
134
+ console.log("[useWizardGeneration] Saved as processing:", creationId);
135
+ }
136
+ } catch (err) {
137
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
138
+ console.error("[useWizardGeneration] saveAsProcessing error:", err);
139
+ }
141
140
  }
142
141
  }
143
142
 
144
143
  generate(input);
145
144
  })
146
145
  .catch((error) => {
147
- if (typeof __DEV__ !== "undefined" && __DEV__) {
148
- console.error("[useWizardGeneration] Input build error:", error);
149
- }
150
146
  hasStarted.current = false;
151
147
  onError?.(error.message);
152
148
  });
@@ -155,16 +151,7 @@ export const useWizardGeneration = (
155
151
  if (!isGeneratingStep && hasStarted.current) {
156
152
  hasStarted.current = false;
157
153
  }
158
- }, [
159
- isGeneratingStep,
160
- scenario,
161
- wizardData,
162
- isGenerating,
163
- generate,
164
- onError,
165
- trackAsBackgroundJob,
166
- addJob,
167
- ]);
154
+ }, [isGeneratingStep, scenario, wizardData, isGenerating, generate, onError, userId, persistence]);
168
155
 
169
156
  return { isGenerating };
170
157
  };