@umituz/react-native-ai-generation-content 1.61.26 → 1.61.28

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.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/src/core/index.ts +14 -12
  3. package/src/domain/entities/generation.types.ts +1 -0
  4. package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +16 -4
  5. package/src/domains/generation/domain/generation.types.ts +1 -2
  6. package/src/domains/generation/index.ts +2 -1
  7. package/src/domains/generation/infrastructure/executors/image-executor.ts +1 -1
  8. package/src/domains/generation/infrastructure/executors/text-to-image-executor.ts +1 -1
  9. package/src/domains/generation/infrastructure/executors/video-executor.ts +1 -1
  10. package/src/domains/generation/infrastructure/flow/useFlowStore.ts +2 -1
  11. package/src/domains/generation/wizard/presentation/hooks/useGenerationPhase.ts +2 -3
  12. package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +4 -0
  13. package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +4 -2
  14. package/src/exports/infrastructure.ts +1 -1
  15. package/src/features/image-to-video/presentation/components/DurationSelector.tsx +1 -1
  16. package/src/infrastructure/orchestration/GenerationOrchestrator.ts +1 -1
  17. package/src/infrastructure/orchestration/index.ts +2 -1
  18. package/src/infrastructure/orchestration/orchestrator.types.ts +0 -1
  19. package/src/infrastructure/providers/generation-config.provider.tsx +4 -4
  20. package/src/infrastructure/services/generation-orchestrator.service.ts +1 -0
  21. package/src/infrastructure/services/job-poller.service.ts +2 -4
  22. package/src/infrastructure/services/multi-image-generation.executor.ts +6 -11
  23. package/src/infrastructure/utils/polling-interval.util.ts +1 -14
  24. package/src/presentation/components/AIGenerationForm.tsx +3 -3
  25. package/src/presentation/components/selectors/AspectRatioSelector.tsx +3 -3
  26. package/src/presentation/components/selectors/DurationSelector.tsx +3 -3
  27. package/src/presentation/components/selectors/GridSelector.tsx +5 -5
  28. package/src/presentation/components/selectors/StyleSelector.tsx +3 -3
  29. package/src/presentation/hooks/use-background-generation.ts +8 -10
  30. package/src/presentation/hooks/use-generation.ts +8 -1
  31. package/src/core/types/error.types.ts +0 -9
  32. package/src/core/types/index.ts +0 -8
  33. package/src/core/types/provider.types.ts +0 -39
  34. package/src/core/types/result.types.ts +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.61.26",
3
+ "version": "1.61.28",
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/core/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  // Result Pattern
16
- export type { Result, Success, Failure } from "./types/result.types";
16
+ export type { Result, Success, Failure } from "../domain/types/result.types";
17
17
  export {
18
18
  success,
19
19
  failure,
@@ -23,11 +23,11 @@ export {
23
23
  andThen,
24
24
  unwrap,
25
25
  unwrapOr,
26
- } from "./types/result.types";
26
+ } from "../domain/types/result.types";
27
27
 
28
28
  // Error Types
29
- export { AIErrorType } from "./types/error.types";
30
- export type { AIErrorInfo, AIErrorMessages } from "./types/error.types";
29
+ export { AIErrorType } from "../domain/entities/error.types";
30
+ export type { AIErrorInfo, AIErrorMessages } from "../domain/entities/error.types";
31
31
 
32
32
  // Provider Types
33
33
  export type {
@@ -50,12 +50,14 @@ export type {
50
50
  // Input Data
51
51
  ImageFeatureInputData,
52
52
  VideoFeatureInputData,
53
- // Provider Interfaces
54
- IAIProviderLifecycle,
55
- IAIProviderCapabilities,
56
- IAIProviderJobManager,
57
- IAIProviderExecutor,
58
- IAIProviderImageFeatures,
59
- IAIProviderVideoFeatures,
53
+ // Main Provider Interface
60
54
  IAIProvider,
61
- } from "./types/provider.types";
55
+ } from "../domain/interfaces/ai-provider.interface";
56
+
57
+ // Segregated provider sub-interfaces
58
+ export type { IAIProviderLifecycle } from "../domain/interfaces/provider-lifecycle.interface";
59
+ export type { IAIProviderCapabilities } from "../domain/interfaces/provider-capabilities.interface";
60
+ export type { IAIProviderJobManager } from "../domain/interfaces/provider-job-manager.interface";
61
+ export type { IAIProviderExecutor } from "../domain/interfaces/provider-executor.interface";
62
+ export type { IAIProviderImageFeatures } from "../domain/interfaces/provider-image-features.interface";
63
+ export type { IAIProviderVideoFeatures } from "../domain/interfaces/provider-video-features.interface";
@@ -53,4 +53,5 @@ export interface GenerationRequest {
53
53
  userId?: string;
54
54
  capability?: GenerationCapability;
55
55
  onProgress?: (progress: GenerationProgress) => void;
56
+ signal?: AbortSignal;
56
57
  }
@@ -5,7 +5,7 @@
5
5
  * Uses provider registry internally - no need to pass FAL functions
6
6
  */
7
7
 
8
- import { useEffect, useRef, useCallback } from "react";
8
+ import { useEffect, useRef, useCallback, useMemo } from "react";
9
9
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
10
10
  import { QUEUE_STATUS, CREATION_STATUS } from "../../../../domain/constants/queue-status.constants";
11
11
  import {
@@ -43,9 +43,21 @@ export function useProcessingJobsPoller(
43
43
  const pollingRef = useRef<Set<string>>(new Set());
44
44
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
45
45
 
46
- // Find creations that need polling
47
- const processingJobs = creations.filter(
48
- (c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
46
+ // Find creations that need polling - stabilize reference with useMemo
47
+ const processingJobIds = useMemo(
48
+ () => creations
49
+ .filter((c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model)
50
+ .map((c) => c.id)
51
+ .join(","),
52
+ [creations],
53
+ );
54
+
55
+ const processingJobs = useMemo(
56
+ () => creations.filter(
57
+ (c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
58
+ ),
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ [processingJobIds],
49
61
  );
50
62
 
51
63
  const pollJob = useCallback(
@@ -13,11 +13,10 @@ export interface GenerationOptions {
13
13
  }
14
14
 
15
15
  // ============================================================================
16
- // Generation Result (re-exported from canonical source)
16
+ // Generation Result (canonical source: domain/entities/generation.types)
17
17
  // ============================================================================
18
18
 
19
19
  import type { GenerationResult as _GenerationResult } from "../../../domain/entities/generation.types";
20
- export type { GenerationResult } from "../../../domain/entities/generation.types";
21
20
 
22
21
  // ============================================================================
23
22
  // Input Types (by generation type)
@@ -20,7 +20,6 @@ export type {
20
20
  export type {
21
21
  GenerationExecutor,
22
22
  GenerationOptions,
23
- GenerationResult,
24
23
  ImageGenerationInput,
25
24
  ImageGenerationOutput,
26
25
  VideoGenerationInput,
@@ -31,6 +30,8 @@ export type {
31
30
  TextToImageOutput,
32
31
  } from "./domain/generation.types";
33
32
 
33
+ export type { GenerationResult } from "../../domain/entities/generation.types";
34
+
34
35
  export { ExecutorFactory, type GenerationType as ExecutorGenerationType } from "./infrastructure/executors/executor-factory";
35
36
 
36
37
  // Wizard Domain
@@ -8,8 +8,8 @@ import type {
8
8
  ImageGenerationInput,
9
9
  ImageGenerationOutput,
10
10
  GenerationOptions,
11
- GenerationResult,
12
11
  } from "../../domain/generation.types";
12
+ import type { GenerationResult } from "../../../../domain/entities/generation.types";
13
13
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
14
14
 
15
15
  declare const __DEV__: boolean;
@@ -6,8 +6,8 @@
6
6
  import type {
7
7
  GenerationExecutor,
8
8
  GenerationOptions,
9
- GenerationResult,
10
9
  } from "../../domain/generation.types";
10
+ import type { GenerationResult } from "../../../../domain/entities/generation.types";
11
11
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
12
12
 
13
13
  declare const __DEV__: boolean;
@@ -8,8 +8,8 @@ import type {
8
8
  VideoGenerationInput,
9
9
  VideoGenerationOutput,
10
10
  GenerationOptions,
11
- GenerationResult,
12
11
  } from "../../domain/generation.types";
12
+ import type { GenerationResult } from "../../../../domain/entities/generation.types";
13
13
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
14
14
 
15
15
  declare const __DEV__: boolean;
@@ -133,7 +133,8 @@ export const createFlowStore = (config: FlowStoreConfig) => {
133
133
  },
134
134
 
135
135
  updateProgress: (progress: number) => {
136
- set({ generationProgress: progress, generationStatus: progress > 0 ? "generating" : "preparing" });
136
+ const generationStatus = progress >= 100 ? "completed" : progress > 0 ? "generating" : "preparing";
137
+ set({ generationProgress: progress, generationStatus });
137
138
  },
138
139
 
139
140
  setResult: (result: unknown) => {
@@ -33,10 +33,9 @@ export function useGenerationPhase(options?: UseGenerationPhaseOptions): Generat
33
33
  const interval = setInterval(() => {
34
34
  const elapsed = Date.now() - startTimeRef.current;
35
35
 
36
- if (elapsed < queuedDuration) {
37
- setPhase("queued");
38
- } else {
36
+ if (elapsed >= queuedDuration) {
39
37
  setPhase("processing");
38
+ clearInterval(interval);
40
39
  }
41
40
  }, 1000);
42
41
 
@@ -41,6 +41,7 @@ export function useVideoQueueGeneration(
41
41
  const requestIdRef = useRef<string | null>(null);
42
42
  const modelRef = useRef<string | null>(null);
43
43
  const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
44
+ const isGeneratingRef = useRef(false);
44
45
  const [isGenerating, setIsGenerating] = useState(false);
45
46
 
46
47
  // Cleanup polling on unmount
@@ -57,6 +58,7 @@ export function useVideoQueueGeneration(
57
58
  creationIdRef.current = null;
58
59
  requestIdRef.current = null;
59
60
  modelRef.current = null;
61
+ isGeneratingRef.current = false;
60
62
  setIsGenerating(false);
61
63
  }, []);
62
64
 
@@ -138,6 +140,8 @@ export function useVideoQueueGeneration(
138
140
  const startGeneration = useCallback(
139
141
  async (input: unknown, prompt: string) => {
140
142
  if (!strategy.submitToQueue) { onError?.("Queue submission not available"); return; }
143
+ if (isGeneratingRef.current) return;
144
+ isGeneratingRef.current = true;
141
145
  setIsGenerating(true);
142
146
 
143
147
  // Save to Firestore FIRST (enables background visibility)
@@ -186,8 +186,10 @@ export const useWizardGeneration = (
186
186
  scenario,
187
187
  wizardData,
188
188
  isVideoMode,
189
- videoGeneration,
190
- photoGeneration,
189
+ videoGeneration.isGenerating,
190
+ videoGeneration.startGeneration,
191
+ photoGeneration.isGenerating,
192
+ photoGeneration.startGeneration,
191
193
  onError,
192
194
  ]);
193
195
 
@@ -29,7 +29,7 @@ export type {
29
29
  // Utils
30
30
  export {
31
31
  classifyError, isTransientError, isPermanentError, isResultNotReady, calculatePollingInterval,
32
- createPollingDelay, checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
32
+ checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
33
33
  validateResult, extractOutputUrl, extractOutputUrls, extractVideoUrl, extractThumbnailUrl,
34
34
  extractAudioUrl, extractImageUrls, cleanBase64, addBase64Prefix, preparePhoto, preparePhotos,
35
35
  isValidBase64, getBase64Size, getBase64SizeMB, prepareImage, createDevCallbacks, createFeatureUtils,
@@ -100,7 +100,7 @@ const componentStyles = StyleSheet.create({
100
100
  flex: 1,
101
101
  paddingVertical: 16,
102
102
  borderRadius: 12,
103
- borderWidth: 1,
103
+ borderWidth: 2,
104
104
  alignItems: "center",
105
105
  justifyContent: "center",
106
106
  },
@@ -12,9 +12,9 @@ import {
12
12
  } from "../../domains/content-moderation";
13
13
  import type {
14
14
  OrchestratorConfig,
15
- GenerationCapability,
16
15
  GenerationMetadata,
17
16
  } from "./orchestrator.types";
17
+ import type { GenerationCapability } from "../../domain/entities/generation.types";
18
18
  import {
19
19
  NetworkUnavailableError,
20
20
  InsufficientCreditsError,
@@ -13,10 +13,11 @@ export type {
13
13
  NetworkService,
14
14
  AuthService,
15
15
  GenerationMetadata,
16
- GenerationCapability,
17
16
  OrchestratorConfig,
18
17
  } from "./orchestrator.types";
19
18
 
19
+ export type { GenerationCapability } from "../../domain/entities/generation.types";
20
+
20
21
  // Errors
21
22
  export {
22
23
  NetworkUnavailableError,
@@ -31,7 +31,6 @@ export interface GenerationMetadata {
31
31
  }
32
32
 
33
33
  import type { GenerationCapability } from "../../domain/entities/generation.types";
34
- export type { GenerationCapability } from "../../domain/entities/generation.types";
35
34
 
36
35
  export interface OrchestratorConfig {
37
36
  creditService: CreditService;
@@ -4,7 +4,7 @@
4
4
  * For scenarios, use configureScenarios() from scenario-registry
5
5
  */
6
6
 
7
- import React, { createContext, useContext, type ReactNode } from "react";
7
+ import React, { createContext, useContext, useCallback, useMemo, type ReactNode } from "react";
8
8
 
9
9
  declare const __DEV__: boolean;
10
10
 
@@ -43,7 +43,7 @@ export const GenerationConfigProvider: React.FC<
43
43
  console.log("[GenerationConfigProvider] Models:", configuredModels);
44
44
  }
45
45
 
46
- const getModel = (featureType: keyof GenerationModels): string => {
46
+ const getModel = useCallback((featureType: keyof GenerationModels): string => {
47
47
  const model = models[featureType];
48
48
  if (!model) {
49
49
  const available = Object.keys(models).filter(
@@ -54,9 +54,9 @@ export const GenerationConfigProvider: React.FC<
54
54
  );
55
55
  }
56
56
  return model;
57
- };
57
+ }, [models]);
58
58
 
59
- const value: GenerationConfigValue = { models, getModel };
59
+ const value = useMemo<GenerationConfigValue>(() => ({ models, getModel }), [models, getModel]);
60
60
 
61
61
  return (
62
62
  <GenerationConfigContext.Provider value={value}>
@@ -66,6 +66,7 @@ class GenerationOrchestratorService {
66
66
  model: request.model,
67
67
  requestId: submission.requestId,
68
68
  config: this.pollingConfig,
69
+ signal: request.signal,
69
70
  onStatusChange: async (status) => {
70
71
  if (this.onStatusUpdateCallback) {
71
72
  await this.onStatusUpdateCallback(submission.requestId, status.status);
@@ -13,8 +13,6 @@ import type { PollJobOptions, PollJobResult } from "./job-poller.types";
13
13
 
14
14
  declare const __DEV__: boolean;
15
15
 
16
- const MAX_CONSECUTIVE_TRANSIENT_ERRORS = 5;
17
-
18
16
  /**
19
17
  * Poll job until completion with exponential backoff
20
18
  * Only reports 100% on actual completion
@@ -33,7 +31,7 @@ export async function pollJob<T = unknown>(
33
31
  } = options;
34
32
 
35
33
  const pollingConfig = { ...DEFAULT_POLLING_CONFIG, ...config };
36
- const { maxAttempts } = pollingConfig;
34
+ const { maxAttempts, maxConsecutiveErrors } = pollingConfig;
37
35
 
38
36
  const startTime = Date.now();
39
37
  let consecutiveTransientErrors = 0;
@@ -96,7 +94,7 @@ export async function pollJob<T = unknown>(
96
94
  if (isTransientError(error)) {
97
95
  consecutiveTransientErrors++;
98
96
 
99
- if (consecutiveTransientErrors >= MAX_CONSECUTIVE_TRANSIENT_ERRORS) {
97
+ if (consecutiveTransientErrors >= maxConsecutiveErrors) {
100
98
  return {
101
99
  success: false,
102
100
  error: error instanceof Error ? error : new Error(String(error)),
@@ -4,16 +4,14 @@
4
4
  * Sends image_urls array as required by FAL AI nano-banana/edit model
5
5
  */
6
6
 
7
- import { providerRegistry } from "./provider-registry.service";
7
+ import { validateProvider } from "../utils/provider-validator.util";
8
+ import { formatBase64 } from "../utils/base64.util";
8
9
 
9
10
  declare const __DEV__: boolean;
10
11
 
11
12
  /** Generation timeout in milliseconds (2 minutes) */
12
13
  const GENERATION_TIMEOUT_MS = 120000;
13
14
 
14
- /** Base64 image format prefix */
15
- const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
16
-
17
15
  /** Default model input values */
18
16
  const MODEL_INPUT_DEFAULTS = {
19
17
  aspectRatio: "1:1",
@@ -41,10 +39,6 @@ export interface MultiImageGenerationResult {
41
39
  readonly error?: string;
42
40
  }
43
41
 
44
- function formatBase64(base64: string): string {
45
- return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
46
- }
47
-
48
42
  /**
49
43
  * Execute image generation with multiple input images
50
44
  * Sends image_urls array as required by FAL AI API
@@ -52,10 +46,11 @@ function formatBase64(base64: string): string {
52
46
  export async function executeMultiImageGeneration(
53
47
  input: MultiImageGenerationInput,
54
48
  ): Promise<MultiImageGenerationResult> {
55
- const provider = providerRegistry.getActiveProvider();
56
- if (!provider?.isInitialized()) {
57
- return { success: false, error: "AI provider not initialized" };
49
+ const validation = validateProvider("MultiImageExecutor");
50
+ if (!validation.success) {
51
+ return { success: false, error: validation.error };
58
52
  }
53
+ const provider = validation.provider;
59
54
 
60
55
  try {
61
56
  const imageUrls = input.photos.map(formatBase64);
@@ -27,17 +27,4 @@ export function calculatePollingInterval(options: IntervalOptions): number {
27
27
 
28
28
  const interval = initialIntervalMs * Math.pow(backoffMultiplier, attempt - 1);
29
29
  return Math.min(interval, maxIntervalMs);
30
- }
31
-
32
- export function createPollingDelay(
33
- attempt: number,
34
- config?: Partial<PollingConfig>,
35
- ): Promise<void> {
36
- const interval = calculatePollingInterval({ attempt, config });
37
-
38
- if (interval === 0) {
39
- return Promise.resolve();
40
- }
41
-
42
- return new Promise((resolve) => setTimeout(resolve, interval));
43
- }
30
+ }
@@ -113,7 +113,7 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
113
113
  styles={styleOptions}
114
114
  selectedStyle={selectedStyle}
115
115
  onStyleSelect={onStyleSelect}
116
- title={translations.styleTitle}
116
+ label={translations.styleTitle}
117
117
  />
118
118
  )}
119
119
 
@@ -122,7 +122,7 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
122
122
  ratios={aspectRatios}
123
123
  selectedRatio={selectedAspectRatio}
124
124
  onRatioSelect={onAspectRatioSelect}
125
- title={translations.aspectRatioTitle}
125
+ label={translations.aspectRatioTitle}
126
126
  />
127
127
  )}
128
128
 
@@ -131,7 +131,7 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
131
131
  duration={duration}
132
132
  durationOptions={durationOptions}
133
133
  onDurationSelect={onDurationSelect}
134
- title={translations.durationTitle}
134
+ label={translations.durationTitle}
135
135
  formatLabel={formatDurationLabel}
136
136
  />
137
137
  )}
@@ -16,14 +16,14 @@ export interface AspectRatioSelectorProps {
16
16
  ratios: AspectRatioOption[];
17
17
  selectedRatio: string;
18
18
  onRatioSelect: (ratio: string) => void;
19
- title: string;
19
+ label: string;
20
20
  }
21
21
 
22
22
  export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
23
23
  ratios,
24
24
  selectedRatio,
25
25
  onRatioSelect,
26
- title,
26
+ label,
27
27
  }) => {
28
28
  const tokens = useAppDesignTokens();
29
29
 
@@ -37,7 +37,7 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
37
37
  marginBottom: 12,
38
38
  }}
39
39
  >
40
- {title}
40
+ {label}
41
41
  </AtomicText>
42
42
  <View style={componentStyles.aspectRatioGrid}>
43
43
  {ratios.map((ratio) => (
@@ -15,7 +15,7 @@ export interface DurationSelectorProps<T extends DurationValue> {
15
15
  duration: T;
16
16
  durationOptions: readonly T[];
17
17
  onDurationSelect: (duration: T) => void;
18
- title: string;
18
+ label: string;
19
19
  formatLabel?: (duration: T) => string;
20
20
  }
21
21
 
@@ -23,7 +23,7 @@ export function DurationSelector<T extends DurationValue>({
23
23
  duration,
24
24
  durationOptions,
25
25
  onDurationSelect,
26
- title,
26
+ label,
27
27
  formatLabel = (d) => `${d}s`,
28
28
  }: DurationSelectorProps<T>): React.ReactElement {
29
29
  const tokens = useAppDesignTokens();
@@ -38,7 +38,7 @@ export function DurationSelector<T extends DurationValue>({
38
38
  marginBottom: 12,
39
39
  }}
40
40
  >
41
- {title}
41
+ {label}
42
42
  </AtomicText>
43
43
  <View style={componentStyles.durationGrid}>
44
44
  {durationOptions.map((sec) => (
@@ -16,7 +16,7 @@ export interface GridSelectorProps<T> {
16
16
  readonly options: readonly GridSelectorOption<T>[];
17
17
  readonly selectedValue: T;
18
18
  readonly onSelect: (value: T) => void;
19
- readonly title?: string;
19
+ readonly label?: string;
20
20
  readonly columns?: number;
21
21
  readonly disabled?: boolean;
22
22
  readonly style?: ViewStyle;
@@ -26,7 +26,7 @@ export function GridSelector<T>({
26
26
  options,
27
27
  selectedValue,
28
28
  onSelect,
29
- title,
29
+ label,
30
30
  columns = 2,
31
31
  disabled = false,
32
32
  style,
@@ -35,7 +35,7 @@ export function GridSelector<T>({
35
35
 
36
36
  return (
37
37
  <View style={[styles.section, style]}>
38
- {title && (
38
+ {label && (
39
39
  <AtomicText
40
40
  type="bodyMedium"
41
41
  style={{
@@ -44,7 +44,7 @@ export function GridSelector<T>({
44
44
  marginBottom: 12,
45
45
  }}
46
46
  >
47
- {title}
47
+ {label}
48
48
  </AtomicText>
49
49
  )}
50
50
  <View style={styles.grid}>
@@ -58,7 +58,7 @@ export function GridSelector<T>({
58
58
  {
59
59
  width: `${100 / columns - 4}%`,
60
60
  backgroundColor: isSelected
61
- ? tokens.colors.primary + "15"
61
+ ? tokens.colors.primary + "20"
62
62
  : tokens.colors.surface,
63
63
  borderColor: isSelected
64
64
  ? tokens.colors.primary
@@ -16,14 +16,14 @@ export interface StyleSelectorProps {
16
16
  styles: StyleOption[];
17
17
  selectedStyle: string;
18
18
  onStyleSelect: (styleId: string) => void;
19
- title: string;
19
+ label: string;
20
20
  }
21
21
 
22
22
  export const StyleSelector: React.FC<StyleSelectorProps> = ({
23
23
  styles,
24
24
  selectedStyle,
25
25
  onStyleSelect,
26
- title,
26
+ label,
27
27
  }) => {
28
28
  const tokens = useAppDesignTokens();
29
29
 
@@ -37,7 +37,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
37
37
  marginBottom: 12,
38
38
  }}
39
39
  >
40
- {title}
40
+ {label}
41
41
  </AtomicText>
42
42
  <ScrollView
43
43
  horizontal
@@ -62,17 +62,17 @@ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
62
62
  queryKey: config.queryKey,
63
63
  });
64
64
 
65
+ const { executor, onProgress, onJobComplete, onJobError, onAllComplete } = options;
66
+
65
67
  const executeDirectly = useCallback(
66
68
  async (input: TInput): Promise<DirectExecutionResult<TResult>> => {
67
- const { executor } = options;
68
-
69
69
  setIsProcessing(true);
70
70
  setProgress(0);
71
71
 
72
72
  try {
73
73
  const result = await executor.execute(input, (p) => {
74
74
  setProgress(p);
75
- options.onProgress?.(p);
75
+ onProgress?.(p);
76
76
  });
77
77
 
78
78
  setProgress(100);
@@ -84,13 +84,11 @@ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
84
84
  setIsProcessing(false);
85
85
  }
86
86
  },
87
- [options],
87
+ [executor, onProgress],
88
88
  );
89
89
 
90
90
  const executeJob = useCallback(
91
91
  async (jobId: string, input: TInput) => {
92
- const { executor } = options;
93
-
94
92
  try {
95
93
  updateJob({
96
94
  id: jobId,
@@ -114,7 +112,7 @@ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
114
112
  const completedJob = getJob(jobId);
115
113
  if (completedJob) {
116
114
  await executor.onComplete?.(completedJob);
117
- options.onJobComplete?.(completedJob);
115
+ onJobComplete?.(completedJob);
118
116
  }
119
117
 
120
118
  removeJob(jobId);
@@ -132,16 +130,16 @@ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
132
130
  failedJob,
133
131
  error instanceof Error ? error : new Error(errorMsg),
134
132
  );
135
- options.onJobError?.(failedJob);
133
+ onJobError?.(failedJob);
136
134
  }
137
135
  } finally {
138
136
  activeJobsRef.current.delete(jobId);
139
137
  if (activeJobsRef.current.size === 0) {
140
- options.onAllComplete?.();
138
+ onAllComplete?.();
141
139
  }
142
140
  }
143
141
  },
144
- [options, updateJob, removeJob, getJob],
142
+ [executor, onJobComplete, onJobError, onAllComplete, updateJob, removeJob, getJob],
145
143
  );
146
144
 
147
145
  const startJob = useCallback(
@@ -3,7 +3,7 @@
3
3
  * React hook for AI generation with progress tracking
4
4
  */
5
5
 
6
- import { useState, useCallback, useRef } from "react";
6
+ import { useState, useCallback, useRef, useEffect } from "react";
7
7
  import type {
8
8
  GenerationRequest,
9
9
  GenerationResult,
@@ -39,6 +39,13 @@ export function useGeneration<T = unknown>(
39
39
 
40
40
  const abortRef = useRef(false);
41
41
 
42
+ // Abort on unmount to prevent state updates after unmount
43
+ useEffect(() => {
44
+ return () => {
45
+ abortRef.current = true;
46
+ };
47
+ }, []);
48
+
42
49
  const handleProgress = useCallback(
43
50
  (prog: GenerationProgress) => {
44
51
  if (abortRef.current) return;
@@ -1,9 +0,0 @@
1
- /**
2
- * AI Generation Error Types
3
- * Re-exports from canonical domain source
4
- *
5
- * @module @umituz/react-native-ai-generation-content/core
6
- */
7
-
8
- export { AIErrorType } from "../../domain/entities/error.types";
9
- export type { AIErrorInfo, AIErrorMessages } from "../../domain/entities/error.types";
@@ -1,8 +0,0 @@
1
- /**
2
- * Core Types Index
3
- * @module @umituz/react-native-ai-generation-content/core
4
- */
5
-
6
- export * from "./result.types";
7
- export * from "./error.types";
8
- export * from "./provider.types";
@@ -1,39 +0,0 @@
1
- /**
2
- * AI Provider Types - Core interfaces for AI generation providers
3
- * Re-exports from canonical domain sources
4
- *
5
- * @module @umituz/react-native-ai-generation-content/core
6
- */
7
-
8
- // Main provider types from ai-provider.interface
9
- export type {
10
- // Feature Types
11
- ImageFeatureType,
12
- VideoFeatureType,
13
- // Config
14
- AIProviderConfig,
15
- // Status
16
- AIJobStatusType,
17
- AILogEntry,
18
- JobSubmission,
19
- JobStatus,
20
- // Progress
21
- ProviderProgressInfo,
22
- SubscribeOptions,
23
- RunOptions,
24
- // Capabilities
25
- ProviderCapabilities,
26
- // Input Data
27
- ImageFeatureInputData,
28
- VideoFeatureInputData,
29
- // Main Provider Interface
30
- IAIProvider,
31
- } from "../../domain/interfaces/ai-provider.interface";
32
-
33
- // Segregated provider sub-interfaces
34
- export type { IAIProviderLifecycle } from "../../domain/interfaces/provider-lifecycle.interface";
35
- export type { IAIProviderCapabilities } from "../../domain/interfaces/provider-capabilities.interface";
36
- export type { IAIProviderJobManager } from "../../domain/interfaces/provider-job-manager.interface";
37
- export type { IAIProviderExecutor } from "../../domain/interfaces/provider-executor.interface";
38
- export type { IAIProviderImageFeatures } from "../../domain/interfaces/provider-image-features.interface";
39
- export type { IAIProviderVideoFeatures } from "../../domain/interfaces/provider-video-features.interface";
@@ -1,18 +0,0 @@
1
- /**
2
- * Result Type Pattern for Functional Error Handling
3
- * Re-exports from canonical domain source
4
- *
5
- * @module @umituz/react-native-ai-generation-content/core
6
- */
7
-
8
- export type { Result, Success, Failure } from "../../domain/types/result.types";
9
- export {
10
- success,
11
- failure,
12
- isSuccess,
13
- isFailure,
14
- mapResult,
15
- andThen,
16
- unwrap,
17
- unwrapOr,
18
- } from "../../domain/types/result.types";