@umituz/react-native-ai-generation-content 1.17.307 → 1.17.309

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.17.307",
3
+ "version": "1.17.309",
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",
@@ -15,7 +15,7 @@ export type {
15
15
  } from "./domain/types";
16
16
  export { COUPLE_FUTURE_DEFAULTS } from "./domain/types";
17
17
  export { useCoupleFutureGeneration } from "./presentation/hooks/useCoupleFutureGeneration";
18
- export type { UseCoupleFutureGenerationConfig } from "./presentation/hooks/useCoupleFutureGeneration";
18
+ export type { CoupleFutureConfig as UseCoupleFutureGenerationConfig } from "./presentation/hooks/useCoupleFutureGeneration";
19
19
  export {
20
20
  RomanticMoodSelector,
21
21
  ArtStyleSelector,
@@ -1,93 +1,90 @@
1
- import { useCallback } from "react";
2
- import { useDeductCredit } from "@umituz/react-native-subscription";
3
- import { usePhotoGeneration } from "../../../../presentation/hooks/usePhotoGeneration";
1
+ /**
2
+ * useCoupleFutureGeneration Hook
3
+ * Couple future generation using centralized orchestrator
4
+ */
5
+
6
+ import { useMemo, useCallback } from "react";
7
+ import {
8
+ useGenerationOrchestrator,
9
+ type GenerationStrategy,
10
+ type AlertMessages,
11
+ } from "../../../../presentation/hooks/generation";
4
12
  import { executeCoupleFuture } from "../../infrastructure/executor";
5
13
  import type { CoupleFutureInput } from "../../domain/types";
6
- import type {
7
- PhotoGenerationConfig,
8
- PhotoGenerationError,
9
- } from "../../../../presentation/hooks/photo-generation.types";
10
14
  import { createCreationsRepository } from "../../../../domains/creations/infrastructure/adapters";
11
15
  import type { Creation } from "../../../../domains/creations/domain/entities/Creation";
12
16
 
13
- export interface UseCoupleFutureGenerationConfig<
14
- TInput extends CoupleFutureInput,
15
- TResult,
16
- > {
17
+ export interface CoupleFutureConfig<TResult> {
17
18
  userId: string | undefined;
18
- processResult: (
19
- imageUrl: string,
20
- input: TInput,
21
- ) => Promise<TResult> | TResult;
22
- buildCreation?: (result: TResult, input: TInput) => Creation | null;
19
+ processResult: (imageUrl: string, input: CoupleFutureInput) => TResult;
20
+ buildCreation?: (result: TResult, input: CoupleFutureInput) => Creation | null;
21
+ onCreditsExhausted?: () => void;
23
22
  onSuccess?: (result: TResult) => void;
24
23
  onError?: (error: string) => void;
25
- alertMessages: {
26
- networkError: string;
27
- policyViolation: string;
28
- saveFailed: string;
29
- creditFailed: string;
30
- unknown: string;
31
- };
24
+ alertMessages: AlertMessages;
32
25
  }
33
26
 
34
- export const useCoupleFutureGeneration = <
35
- TInput extends CoupleFutureInput,
36
- TResult,
37
- >(
38
- config: UseCoupleFutureGenerationConfig<TInput, TResult>,
27
+ export const useCoupleFutureGeneration = <TResult>(
28
+ config: CoupleFutureConfig<TResult>,
39
29
  ) => {
40
30
  const {
41
31
  userId,
42
32
  processResult,
43
33
  buildCreation,
34
+ onCreditsExhausted,
44
35
  onSuccess,
45
36
  onError,
46
37
  alertMessages,
47
38
  } = config;
48
39
 
49
- const { checkCredits, deductCredit } = useDeductCredit({ userId });
50
- const repository = useCallback(
40
+ const repository = useMemo(
51
41
  () => createCreationsRepository("creations"),
52
42
  [],
53
43
  );
54
44
 
55
- const generationConfig: PhotoGenerationConfig<TInput, TResult, void> = {
56
- generate: async (input: TInput, onProgress?: (progress: number) => void) => {
57
- const result = await executeCoupleFuture(
58
- {
59
- partnerABase64: input.partnerABase64,
60
- partnerBBase64: input.partnerBBase64,
61
- prompt: input.prompt,
62
- },
63
- { onProgress },
64
- );
65
-
66
- if (!result.success || !result.imageUrl) {
67
- throw new Error(result.error || "Generation failed");
68
- }
45
+ const strategy: GenerationStrategy<CoupleFutureInput, TResult> = useMemo(
46
+ () => ({
47
+ execute: async (input, onProgress) => {
48
+ const result = await executeCoupleFuture(
49
+ {
50
+ partnerABase64: input.partnerABase64,
51
+ partnerBBase64: input.partnerBBase64,
52
+ prompt: input.prompt,
53
+ },
54
+ { onProgress },
55
+ );
69
56
 
70
- return processResult(result.imageUrl, input);
71
- },
57
+ if (!result.success || !result.imageUrl) {
58
+ throw new Error(result.error || "Generation failed");
59
+ }
72
60
 
73
- save: async (result: TResult, input: TInput) => {
74
- if (!userId || !buildCreation) {
75
- return;
76
- }
77
- const creation = buildCreation(result, input);
78
- if (creation) {
79
- await repository().create(userId, creation);
80
- }
81
- },
61
+ return processResult(result.imageUrl, input);
62
+ },
63
+ getCreditCost: () => 1,
64
+ save: buildCreation
65
+ ? async (result, uid) => {
66
+ const creation = buildCreation(result, {} as CoupleFutureInput);
67
+ if (creation) {
68
+ await repository.create(uid, creation);
69
+ }
70
+ }
71
+ : undefined,
72
+ }),
73
+ [processResult, buildCreation, repository],
74
+ );
82
75
 
83
- checkCredits: () => checkCredits(1),
84
- deductCredits: () => deductCredit(1).then(() => {}),
85
- onSuccess,
86
- onError: (error: PhotoGenerationError) => {
76
+ const handleError = useCallback(
77
+ (error: { message: string }) => {
87
78
  onError?.(error.message);
88
79
  },
89
- alertMessages,
90
- };
80
+ [onError],
81
+ );
91
82
 
92
- return usePhotoGeneration(generationConfig);
83
+ return useGenerationOrchestrator(strategy, {
84
+ userId,
85
+ alertMessages,
86
+ onCreditsExhausted,
87
+ onSuccess: onSuccess as (result: unknown) => void,
88
+ onError: handleError,
89
+ });
93
90
  };
package/src/index.ts CHANGED
@@ -67,18 +67,19 @@ export { enhancePromptWithLanguage, getSupportedLanguages, getLanguageName, Mode
67
67
  export type { ModerationResult, ModerationConfig, SynchronousGenerationInput, SynchronousGenerationConfig } from "./infrastructure/wrappers";
68
68
 
69
69
  export {
70
- useGeneration, usePendingJobs, useBackgroundGeneration, usePhotoGeneration,
70
+ useGeneration, usePendingJobs, useBackgroundGeneration,
71
71
  useGenerationFlow, useGenerationCallbacksBuilder, useAIFeatureCallbacks,
72
+ useGenerationOrchestrator, createGenerationError, getAlertMessage, parseError,
72
73
  } from "./presentation/hooks";
73
74
 
74
75
  export type {
75
76
  UseGenerationOptions, UseGenerationReturn, UsePendingJobsOptions, UsePendingJobsReturn,
76
77
  UseBackgroundGenerationOptions, UseBackgroundGenerationReturn, DirectExecutionResult,
77
- UsePhotoGenerationReturn, PhotoGenerationInput, PhotoGenerationResult, PhotoGenerationError,
78
- PhotoGenerationConfig, PhotoGenerationState, PhotoGenerationStatus, UseGenerationFlowOptions,
79
- UseGenerationFlowReturn, CreditType, GenerationExecutionResult, GenerationCallbacksConfig,
80
- GenerationCallbacks, UseGenerationCallbacksBuilderOptions, AIFeatureCallbacksConfig,
81
- AIFeatureCallbacks, AIFeatureGenerationResult,
78
+ UseGenerationFlowOptions, UseGenerationFlowReturn, CreditType, GenerationExecutionResult,
79
+ GenerationCallbacksConfig, GenerationCallbacks, UseGenerationCallbacksBuilderOptions,
80
+ AIFeatureCallbacksConfig, AIFeatureCallbacks, AIFeatureGenerationResult,
81
+ GenerationStrategy, GenerationConfig, GenerationState, OrchestratorStatus,
82
+ GenerationError, GenerationErrorType, AlertMessages, UseGenerationOrchestratorReturn,
82
83
  } from "./presentation/hooks";
83
84
 
84
85
  export {
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Generation Errors
3
+ * Centralized error handling for generation orchestration
4
+ */
5
+
6
+ import type { GenerationError, GenerationErrorType, AlertMessages } from "./types";
7
+
8
+ export const createGenerationError = (
9
+ type: GenerationErrorType,
10
+ message: string,
11
+ originalError?: Error,
12
+ ): GenerationError => ({
13
+ type,
14
+ message,
15
+ originalError,
16
+ });
17
+
18
+ export const getAlertMessage = (
19
+ error: GenerationError,
20
+ messages: AlertMessages,
21
+ ): string => {
22
+ switch (error.type) {
23
+ case "network":
24
+ return messages.networkError;
25
+ case "credits":
26
+ return messages.creditFailed;
27
+ case "policy":
28
+ return messages.policyViolation;
29
+ case "save":
30
+ return messages.saveFailed;
31
+ default:
32
+ return messages.unknown;
33
+ }
34
+ };
35
+
36
+ export const parseError = (err: unknown): GenerationError => {
37
+ if (isGenerationError(err)) {
38
+ return err;
39
+ }
40
+
41
+ if (err instanceof Error) {
42
+ if (err.name === "ContentPolicyViolationError") {
43
+ return createGenerationError("policy", err.message, err);
44
+ }
45
+ return createGenerationError("unknown", err.message, err);
46
+ }
47
+
48
+ return createGenerationError("unknown", "Generation failed");
49
+ };
50
+
51
+ const isGenerationError = (err: unknown): err is GenerationError => {
52
+ return (
53
+ typeof err === "object" &&
54
+ err !== null &&
55
+ "type" in err &&
56
+ "message" in err
57
+ );
58
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generation Module
3
+ * Feature-agnostic AI generation orchestration
4
+ */
5
+
6
+ export { useGenerationOrchestrator } from "./orchestrator";
7
+
8
+ export type {
9
+ GenerationStrategy,
10
+ GenerationConfig,
11
+ GenerationState,
12
+ OrchestratorStatus,
13
+ GenerationError,
14
+ GenerationErrorType,
15
+ AlertMessages,
16
+ UseGenerationOrchestratorReturn,
17
+ } from "./types";
18
+
19
+ export {
20
+ createGenerationError,
21
+ getAlertMessage,
22
+ parseError,
23
+ } from "./errors";
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Generation Orchestrator
3
+ * Feature-agnostic hook for AI generation with centralized:
4
+ * - Credit management
5
+ * - Error handling
6
+ * - Alert display
7
+ * - Progress tracking
8
+ */
9
+
10
+ import { useState, useCallback, useRef } from "react";
11
+ import { useOfflineStore, useAlert } from "@umituz/react-native-design-system";
12
+ import { useDeductCredit } from "@umituz/react-native-subscription";
13
+ import { createGenerationError, getAlertMessage, parseError } from "./errors";
14
+ import type {
15
+ GenerationStrategy,
16
+ GenerationConfig,
17
+ GenerationState,
18
+ UseGenerationOrchestratorReturn,
19
+ } from "./types";
20
+
21
+ const INITIAL_STATE = {
22
+ status: "idle" as const,
23
+ isGenerating: false,
24
+ progress: 0,
25
+ result: null,
26
+ error: null,
27
+ };
28
+
29
+ export const useGenerationOrchestrator = <TInput, TResult>(
30
+ strategy: GenerationStrategy<TInput, TResult>,
31
+ config: GenerationConfig,
32
+ ): UseGenerationOrchestratorReturn<TInput, TResult> => {
33
+ const { userId, alertMessages, onCreditsExhausted, onSuccess, onError } =
34
+ config;
35
+
36
+ const [state, setState] = useState<GenerationState<TResult>>(INITIAL_STATE);
37
+ const isGeneratingRef = useRef(false);
38
+ const offlineStore = useOfflineStore();
39
+ const { showError, showSuccess } = useAlert();
40
+ const { checkCredits, deductCredit } = useDeductCredit({
41
+ userId,
42
+ onCreditsExhausted,
43
+ });
44
+
45
+ const generate = useCallback(
46
+ async (input: TInput) => {
47
+ if (isGeneratingRef.current) return;
48
+
49
+ isGeneratingRef.current = true;
50
+ setState({ ...INITIAL_STATE, status: "checking", isGenerating: true });
51
+
52
+ try {
53
+ if (!offlineStore.isOnline) {
54
+ throw createGenerationError("network", "No internet connection");
55
+ }
56
+
57
+ const creditCost = strategy.getCreditCost();
58
+ const hasCredits = await checkCredits(creditCost);
59
+ if (!hasCredits) {
60
+ throw createGenerationError("credits", "Insufficient credits");
61
+ }
62
+
63
+ setState((prev) => ({ ...prev, status: "generating", progress: 10 }));
64
+
65
+ const result = await strategy.execute(input, (progress) => {
66
+ setState((prev) => ({ ...prev, progress }));
67
+ });
68
+
69
+ setState((prev) => ({ ...prev, progress: 70 }));
70
+
71
+ if (strategy.save && userId) {
72
+ setState((prev) => ({ ...prev, status: "saving" }));
73
+ try {
74
+ await strategy.save(result, userId);
75
+ } catch (saveErr) {
76
+ throw createGenerationError(
77
+ "save",
78
+ "Failed to save",
79
+ saveErr instanceof Error ? saveErr : undefined,
80
+ );
81
+ }
82
+ }
83
+
84
+ setState((prev) => ({ ...prev, progress: 90 }));
85
+
86
+ await deductCredit(creditCost);
87
+
88
+ setState({
89
+ status: "success",
90
+ isGenerating: false,
91
+ progress: 100,
92
+ result,
93
+ error: null,
94
+ });
95
+
96
+ if (alertMessages.success) {
97
+ void showSuccess("Success", alertMessages.success);
98
+ }
99
+
100
+ onSuccess?.(result);
101
+ } catch (err) {
102
+ const error = parseError(err);
103
+
104
+ setState({
105
+ status: "error",
106
+ isGenerating: false,
107
+ progress: 0,
108
+ result: null,
109
+ error,
110
+ });
111
+
112
+ void showError("Error", getAlertMessage(error, alertMessages));
113
+ onError?.(error);
114
+ } finally {
115
+ isGeneratingRef.current = false;
116
+ }
117
+ },
118
+ [
119
+ strategy,
120
+ userId,
121
+ alertMessages,
122
+ offlineStore.isOnline,
123
+ checkCredits,
124
+ deductCredit,
125
+ showError,
126
+ showSuccess,
127
+ onSuccess,
128
+ onError,
129
+ ],
130
+ );
131
+
132
+ const reset = useCallback(() => {
133
+ setState(INITIAL_STATE);
134
+ isGeneratingRef.current = false;
135
+ }, []);
136
+
137
+ return {
138
+ generate,
139
+ reset,
140
+ status: state.status,
141
+ isGenerating: state.isGenerating,
142
+ progress: state.progress,
143
+ result: state.result,
144
+ error: state.error,
145
+ };
146
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Generation Types
3
+ * Type definitions for feature-agnostic generation orchestration
4
+ */
5
+
6
+ export type OrchestratorStatus =
7
+ | "idle"
8
+ | "checking"
9
+ | "generating"
10
+ | "saving"
11
+ | "success"
12
+ | "error";
13
+
14
+ export interface GenerationStrategy<TInput, TResult> {
15
+ /** Execute the generation */
16
+ execute: (
17
+ input: TInput,
18
+ onProgress?: (progress: number) => void,
19
+ ) => Promise<TResult>;
20
+ /** Credit cost for this generation */
21
+ getCreditCost: () => number;
22
+ /** Optional: Save result to storage */
23
+ save?: (result: TResult, userId: string) => Promise<void>;
24
+ }
25
+
26
+ export interface AlertMessages {
27
+ networkError: string;
28
+ policyViolation: string;
29
+ saveFailed: string;
30
+ creditFailed: string;
31
+ unknown: string;
32
+ success?: string;
33
+ }
34
+
35
+ export interface GenerationConfig {
36
+ userId: string | undefined;
37
+ alertMessages: AlertMessages;
38
+ onCreditsExhausted?: () => void;
39
+ onSuccess?: (result: unknown) => void;
40
+ onError?: (error: GenerationError) => void;
41
+ }
42
+
43
+ export interface GenerationState<TResult> {
44
+ status: OrchestratorStatus;
45
+ isGenerating: boolean;
46
+ progress: number;
47
+ result: TResult | null;
48
+ error: GenerationError | null;
49
+ }
50
+
51
+ export interface GenerationError {
52
+ type: GenerationErrorType;
53
+ message: string;
54
+ originalError?: Error;
55
+ }
56
+
57
+ export type GenerationErrorType =
58
+ | "network"
59
+ | "credits"
60
+ | "policy"
61
+ | "save"
62
+ | "unknown";
63
+
64
+ export interface UseGenerationOrchestratorReturn<TInput, TResult> {
65
+ generate: (input: TInput) => Promise<void>;
66
+ reset: () => void;
67
+ status: OrchestratorStatus;
68
+ isGenerating: boolean;
69
+ progress: number;
70
+ result: TResult | null;
71
+ error: GenerationError | null;
72
+ }
@@ -5,6 +5,24 @@
5
5
  // Base Feature Hooks (Provider-Agnostic)
6
6
  export * from "./base";
7
7
 
8
+ // Generation Orchestrator (Centralized)
9
+ export {
10
+ useGenerationOrchestrator,
11
+ createGenerationError,
12
+ getAlertMessage,
13
+ parseError,
14
+ } from "./generation";
15
+ export type {
16
+ GenerationStrategy,
17
+ GenerationConfig,
18
+ GenerationState,
19
+ OrchestratorStatus,
20
+ GenerationError,
21
+ GenerationErrorType,
22
+ AlertMessages,
23
+ UseGenerationOrchestratorReturn,
24
+ } from "./generation";
25
+
8
26
  export { useGeneration } from "./use-generation";
9
27
  export type {
10
28
  UseGenerationOptions,
@@ -24,20 +42,6 @@ export type {
24
42
  DirectExecutionResult,
25
43
  } from "./use-background-generation";
26
44
 
27
- export { usePhotoGeneration } from "./usePhotoGeneration";
28
- export type {
29
- UsePhotoGenerationReturn,
30
- } from "./usePhotoGeneration";
31
-
32
- export type {
33
- PhotoGenerationInput,
34
- PhotoGenerationResult,
35
- PhotoGenerationError,
36
- PhotoGenerationConfig,
37
- PhotoGenerationState,
38
- PhotoGenerationStatus,
39
- } from "./photo-generation.types";
40
-
41
45
  export { useGenerationFlow } from "./useGenerationFlow";
42
46
  export type {
43
47
  UseGenerationFlowOptions,
@@ -1,50 +0,0 @@
1
- /**
2
- * Photo Generation Types
3
- * Generic types for photo-based AI generation workflows
4
- */
5
-
6
- export interface PhotoGenerationInput<TMetadata = unknown> {
7
- photos: Array<{ uri: string; base64: string }>;
8
- metadata?: TMetadata;
9
- }
10
-
11
- export interface PhotoGenerationResult<TResult = unknown> {
12
- success: boolean;
13
- data?: TResult;
14
- error?: PhotoGenerationError;
15
- }
16
-
17
- export interface PhotoGenerationError {
18
- type: "network_error" | "policy_violation" | "save_failed" | "credit_failed" | "unknown";
19
- message: string;
20
- originalError?: Error;
21
- }
22
-
23
- export interface AlertMessages {
24
- networkError: string;
25
- policyViolation: string;
26
- saveFailed: string;
27
- creditFailed: string;
28
- unknown: string;
29
- }
30
-
31
- export interface PhotoGenerationConfig<TInput, TResult, TSaveInput> {
32
- generate: (input: TInput, onProgress?: (progress: number) => void) => Promise<TResult>;
33
- save?: (result: TResult, input: TInput) => Promise<TSaveInput>;
34
- buildMetadata?: (input: TInput) => Record<string, unknown>;
35
- checkCredits?: () => Promise<boolean>;
36
- deductCredits?: () => Promise<void>;
37
- onSuccess?: (result: TResult) => void;
38
- onError?: (error: PhotoGenerationError) => void;
39
- onSaveComplete?: (saveResult: TSaveInput) => void;
40
- alertMessages: AlertMessages;
41
- }
42
-
43
- export interface PhotoGenerationState<TResult = unknown> {
44
- isGenerating: boolean;
45
- result: TResult | null;
46
- error: PhotoGenerationError | null;
47
- progress: number;
48
- }
49
-
50
- export type PhotoGenerationStatus = "idle" | "validating" | "generating" | "saving" | "success" | "error";
@@ -1,192 +0,0 @@
1
- /**
2
- * usePhotoGeneration Hook
3
- * Generic hook for photo-based AI generation workflows
4
- */
5
-
6
- import { useState, useCallback, useRef } from "react";
7
- import { useOfflineStore, useAlert } from "@umituz/react-native-design-system";
8
- import type {
9
- PhotoGenerationConfig,
10
- PhotoGenerationState,
11
- PhotoGenerationError,
12
- PhotoGenerationStatus,
13
- } from "./photo-generation.types";
14
-
15
- export interface UsePhotoGenerationReturn<TInput, TResult> extends PhotoGenerationState<TResult> {
16
- generate: (input: TInput) => Promise<void>;
17
- reset: () => void;
18
- status: PhotoGenerationStatus;
19
- }
20
-
21
- export const usePhotoGeneration = <TInput, TResult, TSaveInput = unknown>(
22
- config: PhotoGenerationConfig<TInput, TResult, TSaveInput>,
23
- ): UsePhotoGenerationReturn<TInput, TResult> => {
24
- const {
25
- generate: generateFn,
26
- save: saveFn,
27
- checkCredits,
28
- deductCredits,
29
- onSuccess,
30
- onError,
31
- onSaveComplete,
32
- alertMessages,
33
- } = config;
34
-
35
- const [state, setState] = useState<PhotoGenerationState<TResult>>({
36
- isGenerating: false,
37
- result: null,
38
- error: null,
39
- progress: 0,
40
- });
41
-
42
- const [status, setStatus] = useState<PhotoGenerationStatus>("idle");
43
- const isGeneratingRef = useRef(false);
44
- const offlineStore = useOfflineStore();
45
- const { showError } = useAlert();
46
-
47
- const createError = useCallback(
48
- (
49
- type: PhotoGenerationError["type"],
50
- message: string,
51
- originalError?: Error,
52
- ): PhotoGenerationError => ({
53
- type,
54
- message,
55
- originalError,
56
- }),
57
- [],
58
- );
59
-
60
- const generate = useCallback(
61
- async (input: TInput) => {
62
- if (isGeneratingRef.current) {
63
- return;
64
- }
65
-
66
- isGeneratingRef.current = true;
67
- setState({ isGenerating: true, result: null, error: null, progress: 0 });
68
- setStatus("validating");
69
-
70
- try {
71
- // Check network connectivity
72
- if (!offlineStore.isOnline) {
73
- throw createError("network_error", "No internet connection");
74
- }
75
-
76
- // Check credits
77
- if (checkCredits) {
78
- const hasCredits = await checkCredits();
79
- if (!hasCredits) {
80
- throw createError("credit_failed", "Insufficient credits");
81
- }
82
- }
83
-
84
- setStatus("generating");
85
- setState((prev) => ({ ...prev, progress: 20 }));
86
-
87
- // Generate without timeout - let AI provider handle its own timeout
88
- // Pass progress callback to allow provider to report real progress
89
- const result = await generateFn(input, (newProgress) => {
90
- setState((prev) => ({ ...prev, progress: newProgress }));
91
- });
92
-
93
- setState((prev) => ({ ...prev, progress: 60 }));
94
-
95
- // Save result
96
- if (saveFn) {
97
- setStatus("saving");
98
- try {
99
- const saveResult = await saveFn(result, input);
100
- onSaveComplete?.(saveResult);
101
- } catch (saveError) {
102
- throw createError(
103
- "save_failed",
104
- "Failed to save result",
105
- saveError instanceof Error ? saveError : new Error(String(saveError)),
106
- );
107
- }
108
- }
109
-
110
- setState((prev) => ({ ...prev, progress: 80 }));
111
-
112
- // Deduct credits after successful generation
113
- if (deductCredits) {
114
- try {
115
- await deductCredits();
116
- } catch {
117
- // Silently fail credit deduction as generation succeeded
118
- }
119
- }
120
-
121
- setState({
122
- isGenerating: false,
123
- result,
124
- error: null,
125
- progress: 100,
126
- });
127
- setStatus("success");
128
- onSuccess?.(result);
129
- } catch (err: unknown) {
130
- let generationError: PhotoGenerationError;
131
-
132
- if (err && typeof err === "object" && "type" in err && "message" in err) {
133
- generationError = err as PhotoGenerationError;
134
- } else if (err instanceof Error) {
135
- if (err.name === "ContentPolicyViolationError") {
136
- generationError = createError("policy_violation", "Content policy violation", err);
137
- } else {
138
- generationError = createError("unknown", err.message || "Generation failed", err);
139
- }
140
- } else {
141
- generationError = createError("unknown", "Generation failed");
142
- }
143
-
144
- setState({
145
- isGenerating: false,
146
- result: null,
147
- error: generationError,
148
- progress: 0,
149
- });
150
- setStatus("error");
151
-
152
- const errorMessage =
153
- generationError.type === "network_error" ? alertMessages.networkError :
154
- generationError.type === "policy_violation" ? alertMessages.policyViolation :
155
- generationError.type === "save_failed" ? alertMessages.saveFailed :
156
- generationError.type === "credit_failed" ? alertMessages.creditFailed :
157
- alertMessages.unknown;
158
-
159
- void showError("Error", errorMessage);
160
- onError?.(generationError);
161
- } finally {
162
- isGeneratingRef.current = false;
163
- }
164
- },
165
- [
166
- generateFn,
167
- saveFn,
168
- checkCredits,
169
- deductCredits,
170
- onSuccess,
171
- onError,
172
- onSaveComplete,
173
- createError,
174
- offlineStore,
175
- alertMessages,
176
- showError
177
- ],
178
- );
179
-
180
- const reset = useCallback(() => {
181
- setState({ isGenerating: false, result: null, error: null, progress: 0 });
182
- setStatus("idle");
183
- isGeneratingRef.current = false;
184
- }, []);
185
-
186
- return {
187
- ...state,
188
- generate,
189
- reset,
190
- status,
191
- };
192
- };