@umituz/react-native-ai-generation-content 1.17.308 → 1.17.310
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/features/couple-future/index.ts +1 -1
- package/src/features/couple-future/presentation/hooks/useCoupleFutureGeneration.ts +54 -64
- package/src/index.ts +10 -7
- package/src/presentation/hooks/generation/errors.ts +58 -0
- package/src/presentation/hooks/generation/index.ts +42 -0
- package/src/presentation/hooks/generation/orchestrator.ts +146 -0
- package/src/presentation/hooks/generation/types.ts +72 -0
- package/src/presentation/hooks/generation/useImageGeneration.ts +157 -0
- package/src/presentation/hooks/generation/useVideoGeneration.ts +107 -0
- package/src/presentation/hooks/index.ts +25 -32
- package/src/presentation/hooks/base/index.ts +0 -9
- package/src/presentation/hooks/base/types.ts +0 -47
- package/src/presentation/hooks/base/use-dual-image-feature.ts +0 -170
- package/src/presentation/hooks/base/use-image-with-prompt-feature.ts +0 -167
- package/src/presentation/hooks/base/use-single-image-feature.ts +0 -154
- package/src/presentation/hooks/base/utils/feature-state.factory.ts +0 -133
- package/src/presentation/hooks/generation-callbacks.types.ts +0 -42
- package/src/presentation/hooks/photo-generation.types.ts +0 -61
- package/src/presentation/hooks/useGenerationCallbacksBuilder.ts +0 -126
- package/src/presentation/hooks/useGenerationCredits.ts +0 -77
- package/src/presentation/hooks/usePhotoGeneration.ts +0 -207
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.310",
|
|
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,46 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useCoupleFutureGeneration Hook
|
|
3
|
-
* Couple future generation
|
|
3
|
+
* Couple future generation using centralized orchestrator
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useCallback } from "react";
|
|
7
|
-
import {
|
|
6
|
+
import { useMemo, useCallback } from "react";
|
|
7
|
+
import {
|
|
8
|
+
useGenerationOrchestrator,
|
|
9
|
+
type GenerationStrategy,
|
|
10
|
+
type AlertMessages,
|
|
11
|
+
} from "../../../../presentation/hooks/generation";
|
|
8
12
|
import { executeCoupleFuture } from "../../infrastructure/executor";
|
|
9
13
|
import type { CoupleFutureInput } from "../../domain/types";
|
|
10
|
-
import type {
|
|
11
|
-
PhotoGenerationConfig,
|
|
12
|
-
PhotoGenerationError,
|
|
13
|
-
} from "../../../../presentation/hooks/photo-generation.types";
|
|
14
14
|
import { createCreationsRepository } from "../../../../domains/creations/infrastructure/adapters";
|
|
15
15
|
import type { Creation } from "../../../../domains/creations/domain/entities/Creation";
|
|
16
16
|
|
|
17
|
-
export interface
|
|
18
|
-
TInput extends CoupleFutureInput,
|
|
19
|
-
TResult,
|
|
20
|
-
> {
|
|
17
|
+
export interface CoupleFutureConfig<TResult> {
|
|
21
18
|
userId: string | undefined;
|
|
22
|
-
processResult: (
|
|
23
|
-
|
|
24
|
-
input: TInput,
|
|
25
|
-
) => Promise<TResult> | TResult;
|
|
26
|
-
buildCreation?: (result: TResult, input: TInput) => Creation | null;
|
|
19
|
+
processResult: (imageUrl: string, input: CoupleFutureInput) => TResult;
|
|
20
|
+
buildCreation?: (result: TResult, input: CoupleFutureInput) => Creation | null;
|
|
27
21
|
onCreditsExhausted?: () => void;
|
|
28
22
|
onSuccess?: (result: TResult) => void;
|
|
29
23
|
onError?: (error: string) => void;
|
|
30
|
-
alertMessages:
|
|
31
|
-
networkError: string;
|
|
32
|
-
policyViolation: string;
|
|
33
|
-
saveFailed: string;
|
|
34
|
-
creditFailed: string;
|
|
35
|
-
unknown: string;
|
|
36
|
-
};
|
|
24
|
+
alertMessages: AlertMessages;
|
|
37
25
|
}
|
|
38
26
|
|
|
39
|
-
export const useCoupleFutureGeneration = <
|
|
40
|
-
|
|
41
|
-
TResult,
|
|
42
|
-
>(
|
|
43
|
-
config: UseCoupleFutureGenerationConfig<TInput, TResult>,
|
|
27
|
+
export const useCoupleFutureGeneration = <TResult>(
|
|
28
|
+
config: CoupleFutureConfig<TResult>,
|
|
44
29
|
) => {
|
|
45
30
|
const {
|
|
46
31
|
userId,
|
|
@@ -52,49 +37,54 @@ export const useCoupleFutureGeneration = <
|
|
|
52
37
|
alertMessages,
|
|
53
38
|
} = config;
|
|
54
39
|
|
|
55
|
-
const repository =
|
|
40
|
+
const repository = useMemo(
|
|
56
41
|
() => createCreationsRepository("creations"),
|
|
57
42
|
[],
|
|
58
43
|
);
|
|
59
44
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
partnerBBase64: input.partnerBBase64,
|
|
72
|
-
prompt: input.prompt,
|
|
73
|
-
},
|
|
74
|
-
{ onProgress },
|
|
75
|
-
);
|
|
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
|
+
);
|
|
76
56
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
57
|
+
if (!result.success || !result.imageUrl) {
|
|
58
|
+
throw new Error(result.error || "Generation failed");
|
|
59
|
+
}
|
|
80
60
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
);
|
|
75
|
+
|
|
76
|
+
const handleError = useCallback(
|
|
77
|
+
(error: { message: string }) => {
|
|
94
78
|
onError?.(error.message);
|
|
95
79
|
},
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
[onError],
|
|
81
|
+
);
|
|
98
82
|
|
|
99
|
-
return
|
|
83
|
+
return useGenerationOrchestrator(strategy, {
|
|
84
|
+
userId,
|
|
85
|
+
alertMessages,
|
|
86
|
+
onCreditsExhausted,
|
|
87
|
+
onSuccess: onSuccess as (result: unknown) => void,
|
|
88
|
+
onError: handleError,
|
|
89
|
+
});
|
|
100
90
|
};
|
package/src/index.ts
CHANGED
|
@@ -67,18 +67,21 @@ 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,
|
|
71
|
-
useGenerationFlow,
|
|
70
|
+
useGeneration, usePendingJobs, useBackgroundGeneration,
|
|
71
|
+
useGenerationFlow, useAIFeatureCallbacks,
|
|
72
|
+
useGenerationOrchestrator, useImageGeneration, useVideoGeneration,
|
|
73
|
+
createGenerationError, getAlertMessage, parseError,
|
|
72
74
|
} from "./presentation/hooks";
|
|
73
75
|
|
|
74
76
|
export type {
|
|
75
77
|
UseGenerationOptions, UseGenerationReturn, UsePendingJobsOptions, UsePendingJobsReturn,
|
|
76
78
|
UseBackgroundGenerationOptions, UseBackgroundGenerationReturn, DirectExecutionResult,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
UseGenerationFlowOptions, UseGenerationFlowReturn,
|
|
80
|
+
AIFeatureCallbacksConfig, AIFeatureCallbacks, AIFeatureGenerationResult,
|
|
81
|
+
GenerationStrategy, GenerationConfig, GenerationState, OrchestratorStatus,
|
|
82
|
+
GenerationError, GenerationErrorType, AlertMessages, UseGenerationOrchestratorReturn,
|
|
83
|
+
SingleImageInput, DualImageInput, ImageGenerationInput, ImageGenerationConfig,
|
|
84
|
+
DualImageVideoInput, VideoGenerationConfig,
|
|
82
85
|
} from "./presentation/hooks";
|
|
83
86
|
|
|
84
87
|
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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation Module
|
|
3
|
+
* Feature-agnostic AI generation orchestration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Core orchestrator
|
|
7
|
+
export { useGenerationOrchestrator } from "./orchestrator";
|
|
8
|
+
|
|
9
|
+
// Generic feature hooks
|
|
10
|
+
export { useImageGeneration } from "./useImageGeneration";
|
|
11
|
+
export { useVideoGeneration } from "./useVideoGeneration";
|
|
12
|
+
|
|
13
|
+
// Types
|
|
14
|
+
export type {
|
|
15
|
+
GenerationStrategy,
|
|
16
|
+
GenerationConfig,
|
|
17
|
+
GenerationState,
|
|
18
|
+
OrchestratorStatus,
|
|
19
|
+
GenerationError,
|
|
20
|
+
GenerationErrorType,
|
|
21
|
+
AlertMessages,
|
|
22
|
+
UseGenerationOrchestratorReturn,
|
|
23
|
+
} from "./types";
|
|
24
|
+
|
|
25
|
+
export type {
|
|
26
|
+
SingleImageInput,
|
|
27
|
+
DualImageInput,
|
|
28
|
+
ImageGenerationInput,
|
|
29
|
+
ImageGenerationConfig,
|
|
30
|
+
} from "./useImageGeneration";
|
|
31
|
+
|
|
32
|
+
export type {
|
|
33
|
+
DualImageVideoInput,
|
|
34
|
+
VideoGenerationConfig,
|
|
35
|
+
} from "./useVideoGeneration";
|
|
36
|
+
|
|
37
|
+
// Error utilities
|
|
38
|
+
export {
|
|
39
|
+
createGenerationError,
|
|
40
|
+
getAlertMessage,
|
|
41
|
+
parseError,
|
|
42
|
+
} 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
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useImageGeneration Hook
|
|
3
|
+
* Generic image generation hook for ANY image feature
|
|
4
|
+
* Uses centralized orchestrator for credit/error handling
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useMemo, useCallback } from "react";
|
|
8
|
+
import { useGenerationOrchestrator } from "./orchestrator";
|
|
9
|
+
import type { GenerationStrategy, AlertMessages } from "./types";
|
|
10
|
+
import { executeImageFeature } from "../../../infrastructure/services";
|
|
11
|
+
import type { ImageFeatureType } from "../../../domain/interfaces";
|
|
12
|
+
import { createCreationsRepository } from "../../../domains/creations/infrastructure/adapters";
|
|
13
|
+
import type { Creation } from "../../../domains/creations/domain/entities/Creation";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generic input for single image features
|
|
17
|
+
*/
|
|
18
|
+
export interface SingleImageInput {
|
|
19
|
+
imageBase64: string;
|
|
20
|
+
prompt?: string;
|
|
21
|
+
options?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generic input for dual image features (face-swap, etc.)
|
|
26
|
+
*/
|
|
27
|
+
export interface DualImageInput {
|
|
28
|
+
sourceImageBase64: string;
|
|
29
|
+
targetImageBase64: string;
|
|
30
|
+
options?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ImageGenerationInput = SingleImageInput | DualImageInput;
|
|
34
|
+
|
|
35
|
+
export interface ImageGenerationConfig<TInput extends ImageGenerationInput, TResult> {
|
|
36
|
+
/** Feature type (face-swap, upscale, remove-background, etc.) */
|
|
37
|
+
featureType: ImageFeatureType;
|
|
38
|
+
/** User ID for credit operations */
|
|
39
|
+
userId: string | undefined;
|
|
40
|
+
/** Transform image URL to result type */
|
|
41
|
+
processResult: (imageUrl: string, input: TInput) => TResult;
|
|
42
|
+
/** Build input for executor from generic input */
|
|
43
|
+
buildExecutorInput?: (input: TInput) => {
|
|
44
|
+
imageBase64?: string;
|
|
45
|
+
targetImageBase64?: string;
|
|
46
|
+
prompt?: string;
|
|
47
|
+
options?: Record<string, unknown>;
|
|
48
|
+
};
|
|
49
|
+
/** Optional: Build creation for saving */
|
|
50
|
+
buildCreation?: (result: TResult, input: TInput) => Creation | null;
|
|
51
|
+
/** Credit cost (default: 1) */
|
|
52
|
+
creditCost?: number;
|
|
53
|
+
/** Alert messages for errors */
|
|
54
|
+
alertMessages: AlertMessages;
|
|
55
|
+
/** Callbacks */
|
|
56
|
+
onCreditsExhausted?: () => void;
|
|
57
|
+
onSuccess?: (result: TResult) => void;
|
|
58
|
+
onError?: (error: string) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Default input builder for single image
|
|
63
|
+
*/
|
|
64
|
+
const defaultSingleImageBuilder = (input: SingleImageInput) => ({
|
|
65
|
+
imageBase64: input.imageBase64,
|
|
66
|
+
prompt: input.prompt,
|
|
67
|
+
options: input.options,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Default input builder for dual image
|
|
72
|
+
*/
|
|
73
|
+
const defaultDualImageBuilder = (input: DualImageInput) => ({
|
|
74
|
+
imageBase64: input.sourceImageBase64,
|
|
75
|
+
targetImageBase64: input.targetImageBase64,
|
|
76
|
+
options: input.options,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if input is dual image type
|
|
81
|
+
*/
|
|
82
|
+
const isDualImageInput = (input: ImageGenerationInput): input is DualImageInput => {
|
|
83
|
+
return "sourceImageBase64" in input && "targetImageBase64" in input;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const useImageGeneration = <
|
|
87
|
+
TInput extends ImageGenerationInput,
|
|
88
|
+
TResult,
|
|
89
|
+
>(config: ImageGenerationConfig<TInput, TResult>) => {
|
|
90
|
+
const {
|
|
91
|
+
featureType,
|
|
92
|
+
userId,
|
|
93
|
+
processResult,
|
|
94
|
+
buildExecutorInput,
|
|
95
|
+
buildCreation,
|
|
96
|
+
creditCost = 1,
|
|
97
|
+
alertMessages,
|
|
98
|
+
onCreditsExhausted,
|
|
99
|
+
onSuccess,
|
|
100
|
+
onError,
|
|
101
|
+
} = config;
|
|
102
|
+
|
|
103
|
+
const repository = useMemo(
|
|
104
|
+
() => createCreationsRepository("creations"),
|
|
105
|
+
[],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const strategy: GenerationStrategy<TInput, TResult> = useMemo(
|
|
109
|
+
() => ({
|
|
110
|
+
execute: async (input, onProgress) => {
|
|
111
|
+
// Build executor input
|
|
112
|
+
const executorInput = buildExecutorInput
|
|
113
|
+
? buildExecutorInput(input)
|
|
114
|
+
: isDualImageInput(input)
|
|
115
|
+
? defaultDualImageBuilder(input)
|
|
116
|
+
: defaultSingleImageBuilder(input as SingleImageInput);
|
|
117
|
+
|
|
118
|
+
const result = await executeImageFeature(
|
|
119
|
+
featureType,
|
|
120
|
+
executorInput,
|
|
121
|
+
{ onProgress },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (!result.success || !result.imageUrl) {
|
|
125
|
+
throw new Error(result.error || "Image generation failed");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return processResult(result.imageUrl, input);
|
|
129
|
+
},
|
|
130
|
+
getCreditCost: () => creditCost,
|
|
131
|
+
save: buildCreation
|
|
132
|
+
? async (result, uid) => {
|
|
133
|
+
const creation = buildCreation(result, {} as TInput);
|
|
134
|
+
if (creation) {
|
|
135
|
+
await repository.create(uid, creation);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
: undefined,
|
|
139
|
+
}),
|
|
140
|
+
[featureType, processResult, buildExecutorInput, buildCreation, repository, creditCost],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const handleError = useCallback(
|
|
144
|
+
(error: { message: string }) => {
|
|
145
|
+
onError?.(error.message);
|
|
146
|
+
},
|
|
147
|
+
[onError],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return useGenerationOrchestrator(strategy, {
|
|
151
|
+
userId,
|
|
152
|
+
alertMessages,
|
|
153
|
+
onCreditsExhausted,
|
|
154
|
+
onSuccess: onSuccess as (result: unknown) => void,
|
|
155
|
+
onError: handleError,
|
|
156
|
+
});
|
|
157
|
+
};
|