@umituz/react-native-ai-generation-content 1.17.309 → 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/index.ts +6 -4
- package/src/presentation/hooks/generation/index.ts +19 -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 +8 -12
- 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/useGenerationCallbacksBuilder.ts +0 -126
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",
|
package/src/index.ts
CHANGED
|
@@ -68,18 +68,20 @@ export type { ModerationResult, ModerationConfig, SynchronousGenerationInput, Sy
|
|
|
68
68
|
|
|
69
69
|
export {
|
|
70
70
|
useGeneration, usePendingJobs, useBackgroundGeneration,
|
|
71
|
-
useGenerationFlow,
|
|
72
|
-
useGenerationOrchestrator,
|
|
71
|
+
useGenerationFlow, useAIFeatureCallbacks,
|
|
72
|
+
useGenerationOrchestrator, useImageGeneration, useVideoGeneration,
|
|
73
|
+
createGenerationError, getAlertMessage, parseError,
|
|
73
74
|
} from "./presentation/hooks";
|
|
74
75
|
|
|
75
76
|
export type {
|
|
76
77
|
UseGenerationOptions, UseGenerationReturn, UsePendingJobsOptions, UsePendingJobsReturn,
|
|
77
78
|
UseBackgroundGenerationOptions, UseBackgroundGenerationReturn, DirectExecutionResult,
|
|
78
|
-
UseGenerationFlowOptions, UseGenerationFlowReturn,
|
|
79
|
-
GenerationCallbacksConfig, GenerationCallbacks, UseGenerationCallbacksBuilderOptions,
|
|
79
|
+
UseGenerationFlowOptions, UseGenerationFlowReturn,
|
|
80
80
|
AIFeatureCallbacksConfig, AIFeatureCallbacks, AIFeatureGenerationResult,
|
|
81
81
|
GenerationStrategy, GenerationConfig, GenerationState, OrchestratorStatus,
|
|
82
82
|
GenerationError, GenerationErrorType, AlertMessages, UseGenerationOrchestratorReturn,
|
|
83
|
+
SingleImageInput, DualImageInput, ImageGenerationInput, ImageGenerationConfig,
|
|
84
|
+
DualImageVideoInput, VideoGenerationConfig,
|
|
83
85
|
} from "./presentation/hooks";
|
|
84
86
|
|
|
85
87
|
export {
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
* Feature-agnostic AI generation orchestration
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// Core orchestrator
|
|
6
7
|
export { useGenerationOrchestrator } from "./orchestrator";
|
|
7
8
|
|
|
9
|
+
// Generic feature hooks
|
|
10
|
+
export { useImageGeneration } from "./useImageGeneration";
|
|
11
|
+
export { useVideoGeneration } from "./useVideoGeneration";
|
|
12
|
+
|
|
13
|
+
// Types
|
|
8
14
|
export type {
|
|
9
15
|
GenerationStrategy,
|
|
10
16
|
GenerationConfig,
|
|
@@ -16,6 +22,19 @@ export type {
|
|
|
16
22
|
UseGenerationOrchestratorReturn,
|
|
17
23
|
} from "./types";
|
|
18
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
|
|
19
38
|
export {
|
|
20
39
|
createGenerationError,
|
|
21
40
|
getAlertMessage,
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useVideoGeneration Hook
|
|
3
|
+
* Generic video generation hook for dual-image video features (ai-hug, ai-kiss)
|
|
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 { executeVideoFeature } from "../../../infrastructure/services";
|
|
11
|
+
import type { VideoFeatureType } from "../../../domain/interfaces";
|
|
12
|
+
import { createCreationsRepository } from "../../../domains/creations/infrastructure/adapters";
|
|
13
|
+
import type { Creation } from "../../../domains/creations/domain/entities/Creation";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Input for dual image video features (ai-hug, ai-kiss)
|
|
17
|
+
*/
|
|
18
|
+
export interface DualImageVideoInput {
|
|
19
|
+
sourceImageBase64: string;
|
|
20
|
+
targetImageBase64: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VideoGenerationConfig<TResult> {
|
|
24
|
+
/** Feature type (ai-hug, ai-kiss) */
|
|
25
|
+
featureType: VideoFeatureType;
|
|
26
|
+
/** User ID for credit operations */
|
|
27
|
+
userId: string | undefined;
|
|
28
|
+
/** Transform video URL to result type */
|
|
29
|
+
processResult: (videoUrl: string, input: DualImageVideoInput) => TResult;
|
|
30
|
+
/** Optional: Build creation for saving */
|
|
31
|
+
buildCreation?: (result: TResult, input: DualImageVideoInput) => Creation | null;
|
|
32
|
+
/** Credit cost (default: 1) */
|
|
33
|
+
creditCost?: number;
|
|
34
|
+
/** Alert messages for errors */
|
|
35
|
+
alertMessages: AlertMessages;
|
|
36
|
+
/** Callbacks */
|
|
37
|
+
onCreditsExhausted?: () => void;
|
|
38
|
+
onSuccess?: (result: TResult) => void;
|
|
39
|
+
onError?: (error: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const useVideoGeneration = <TResult>(
|
|
43
|
+
config: VideoGenerationConfig<TResult>,
|
|
44
|
+
) => {
|
|
45
|
+
const {
|
|
46
|
+
featureType,
|
|
47
|
+
userId,
|
|
48
|
+
processResult,
|
|
49
|
+
buildCreation,
|
|
50
|
+
creditCost = 1,
|
|
51
|
+
alertMessages,
|
|
52
|
+
onCreditsExhausted,
|
|
53
|
+
onSuccess,
|
|
54
|
+
onError,
|
|
55
|
+
} = config;
|
|
56
|
+
|
|
57
|
+
const repository = useMemo(
|
|
58
|
+
() => createCreationsRepository("creations"),
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const strategy: GenerationStrategy<DualImageVideoInput, TResult> = useMemo(
|
|
63
|
+
() => ({
|
|
64
|
+
execute: async (input, onProgress) => {
|
|
65
|
+
const result = await executeVideoFeature(
|
|
66
|
+
featureType,
|
|
67
|
+
{
|
|
68
|
+
sourceImageBase64: input.sourceImageBase64,
|
|
69
|
+
targetImageBase64: input.targetImageBase64,
|
|
70
|
+
},
|
|
71
|
+
{ onProgress },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!result.success || !result.videoUrl) {
|
|
75
|
+
throw new Error(result.error || "Video generation failed");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return processResult(result.videoUrl, input);
|
|
79
|
+
},
|
|
80
|
+
getCreditCost: () => creditCost,
|
|
81
|
+
save: buildCreation
|
|
82
|
+
? async (result, uid) => {
|
|
83
|
+
const creation = buildCreation(result, {} as DualImageVideoInput);
|
|
84
|
+
if (creation) {
|
|
85
|
+
await repository.create(uid, creation);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
: undefined,
|
|
89
|
+
}),
|
|
90
|
+
[featureType, processResult, buildCreation, repository, creditCost],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const handleError = useCallback(
|
|
94
|
+
(error: { message: string }) => {
|
|
95
|
+
onError?.(error.message);
|
|
96
|
+
},
|
|
97
|
+
[onError],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return useGenerationOrchestrator(strategy, {
|
|
101
|
+
userId,
|
|
102
|
+
alertMessages,
|
|
103
|
+
onCreditsExhausted,
|
|
104
|
+
onSuccess: onSuccess as (result: unknown) => void,
|
|
105
|
+
onError: handleError,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* Presentation Hooks
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
// Base Feature Hooks (Provider-Agnostic)
|
|
6
|
-
export * from "./base";
|
|
7
|
-
|
|
8
5
|
// Generation Orchestrator (Centralized)
|
|
9
6
|
export {
|
|
10
7
|
useGenerationOrchestrator,
|
|
8
|
+
useImageGeneration,
|
|
9
|
+
useVideoGeneration,
|
|
11
10
|
createGenerationError,
|
|
12
11
|
getAlertMessage,
|
|
13
12
|
parseError,
|
|
@@ -21,6 +20,12 @@ export type {
|
|
|
21
20
|
GenerationErrorType,
|
|
22
21
|
AlertMessages,
|
|
23
22
|
UseGenerationOrchestratorReturn,
|
|
23
|
+
SingleImageInput,
|
|
24
|
+
DualImageInput,
|
|
25
|
+
ImageGenerationInput,
|
|
26
|
+
ImageGenerationConfig,
|
|
27
|
+
DualImageVideoInput,
|
|
28
|
+
VideoGenerationConfig,
|
|
24
29
|
} from "./generation";
|
|
25
30
|
|
|
26
31
|
export { useGeneration } from "./use-generation";
|
|
@@ -48,15 +53,6 @@ export type {
|
|
|
48
53
|
UseGenerationFlowReturn,
|
|
49
54
|
} from "./useGenerationFlow";
|
|
50
55
|
|
|
51
|
-
export { useGenerationCallbacksBuilder } from "./useGenerationCallbacksBuilder";
|
|
52
|
-
export type {
|
|
53
|
-
CreditType,
|
|
54
|
-
GenerationExecutionResult,
|
|
55
|
-
GenerationCallbacksConfig,
|
|
56
|
-
GenerationCallbacks,
|
|
57
|
-
UseGenerationCallbacksBuilderOptions,
|
|
58
|
-
} from "./generation-callbacks.types";
|
|
59
|
-
|
|
60
56
|
export { useAIFeatureCallbacks } from "./useAIFeatureCallbacks";
|
|
61
57
|
export type {
|
|
62
58
|
AIFeatureCallbacksConfig,
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base Feature Hook Types
|
|
3
|
-
* Provider-agnostic types for feature hooks
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Result from AI processing
|
|
8
|
-
*/
|
|
9
|
-
export interface FeatureProcessResult {
|
|
10
|
-
readonly success: boolean;
|
|
11
|
-
readonly outputUrl?: string;
|
|
12
|
-
readonly error?: string;
|
|
13
|
-
readonly metadata?: Record<string, unknown>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Base state shared by all feature hooks
|
|
18
|
-
*/
|
|
19
|
-
export interface BaseFeatureState {
|
|
20
|
-
readonly isProcessing: boolean;
|
|
21
|
-
readonly progress: number;
|
|
22
|
-
readonly error: string | null;
|
|
23
|
-
readonly processedUrl: string | null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Base actions shared by all feature hooks
|
|
28
|
-
*/
|
|
29
|
-
export interface BaseFeatureActions {
|
|
30
|
-
readonly reset: () => void;
|
|
31
|
-
readonly clearError: () => void;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Progress callback type
|
|
36
|
-
*/
|
|
37
|
-
export type OnProgressCallback = (progress: number) => void;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Image selection callback - provided by app
|
|
41
|
-
*/
|
|
42
|
-
export type OnSelectImageCallback = () => Promise<string | null>;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Save callback - provided by app
|
|
46
|
-
*/
|
|
47
|
-
export type OnSaveCallback = (url: string) => Promise<void>;
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useDualImageFeature Hook
|
|
3
|
-
* Provider-agnostic hook for dual image processing features
|
|
4
|
-
* Examples: AI Hug, AI Kiss, Face Swap
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useCallback, useState } from "react";
|
|
8
|
-
import type {
|
|
9
|
-
BaseFeatureState,
|
|
10
|
-
BaseFeatureActions,
|
|
11
|
-
FeatureProcessResult,
|
|
12
|
-
OnProgressCallback,
|
|
13
|
-
OnSelectImageCallback,
|
|
14
|
-
OnSaveCallback,
|
|
15
|
-
} from "./types";
|
|
16
|
-
import { createFeatureStateHandlers, executeProcess, executeSave } from "./utils/feature-state.factory";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Request passed to processRequest callback
|
|
20
|
-
*/
|
|
21
|
-
export interface DualImageProcessRequest {
|
|
22
|
-
readonly firstImageUri: string;
|
|
23
|
-
readonly secondImageUri: string;
|
|
24
|
-
readonly onProgress: OnProgressCallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Configuration for dual image feature
|
|
29
|
-
*/
|
|
30
|
-
export interface UseDualImageFeatureConfig {
|
|
31
|
-
readonly onSelectFirstImage: OnSelectImageCallback;
|
|
32
|
-
readonly onSelectSecondImage: OnSelectImageCallback;
|
|
33
|
-
readonly processRequest: (
|
|
34
|
-
request: DualImageProcessRequest,
|
|
35
|
-
) => Promise<FeatureProcessResult>;
|
|
36
|
-
readonly onSave?: OnSaveCallback;
|
|
37
|
-
readonly onError?: (error: string) => void;
|
|
38
|
-
readonly onSuccess?: (url: string) => void;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* State for dual image feature
|
|
43
|
-
*/
|
|
44
|
-
export interface DualImageFeatureState extends BaseFeatureState {
|
|
45
|
-
readonly firstImageUri: string | null;
|
|
46
|
-
readonly secondImageUri: string | null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Return type for dual image feature hook
|
|
51
|
-
*/
|
|
52
|
-
export interface UseDualImageFeatureReturn
|
|
53
|
-
extends DualImageFeatureState,
|
|
54
|
-
BaseFeatureActions {
|
|
55
|
-
readonly selectFirstImage: () => Promise<void>;
|
|
56
|
-
readonly selectSecondImage: () => Promise<void>;
|
|
57
|
-
readonly process: () => Promise<void>;
|
|
58
|
-
readonly save: () => Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const initialState: DualImageFeatureState = {
|
|
62
|
-
firstImageUri: null,
|
|
63
|
-
secondImageUri: null,
|
|
64
|
-
processedUrl: null,
|
|
65
|
-
isProcessing: false,
|
|
66
|
-
progress: 0,
|
|
67
|
-
error: null,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export function useDualImageFeature(
|
|
71
|
-
config: UseDualImageFeatureConfig,
|
|
72
|
-
): UseDualImageFeatureReturn {
|
|
73
|
-
const [state, setState] = useState<DualImageFeatureState>(initialState);
|
|
74
|
-
|
|
75
|
-
const { reset, clearError } = createFeatureStateHandlers({
|
|
76
|
-
setState,
|
|
77
|
-
initialState,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const selectFirstImage = useCallback(async (): Promise<void> => {
|
|
81
|
-
try {
|
|
82
|
-
const uri = await config.onSelectFirstImage();
|
|
83
|
-
if (uri) {
|
|
84
|
-
setState((prev) => ({
|
|
85
|
-
...prev,
|
|
86
|
-
firstImageUri: uri,
|
|
87
|
-
error: null,
|
|
88
|
-
processedUrl: null,
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
} catch (err) {
|
|
92
|
-
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
93
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
94
|
-
config.onError?.(message);
|
|
95
|
-
}
|
|
96
|
-
}, [config]);
|
|
97
|
-
|
|
98
|
-
const selectSecondImage = useCallback(async (): Promise<void> => {
|
|
99
|
-
try {
|
|
100
|
-
const uri = await config.onSelectSecondImage();
|
|
101
|
-
if (uri) {
|
|
102
|
-
setState((prev) => ({
|
|
103
|
-
...prev,
|
|
104
|
-
secondImageUri: uri,
|
|
105
|
-
error: null,
|
|
106
|
-
processedUrl: null,
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
} catch (err) {
|
|
110
|
-
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
111
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
112
|
-
config.onError?.(message);
|
|
113
|
-
}
|
|
114
|
-
}, [config]);
|
|
115
|
-
|
|
116
|
-
const process = useCallback(async (): Promise<void> => {
|
|
117
|
-
if (!state.firstImageUri || !state.secondImageUri) {
|
|
118
|
-
const message = "error.noImages";
|
|
119
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
120
|
-
config.onError?.(message);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const result = await executeProcess<FeatureProcessResult>({
|
|
125
|
-
canProcess: () => !!state.firstImageUri && !!state.secondImageUri,
|
|
126
|
-
setError: (error: string | null) => setState((prev) => ({ ...prev, error })),
|
|
127
|
-
setProcessing: (isProcessing: boolean) => setState((prev) => ({ ...prev, isProcessing })),
|
|
128
|
-
onError: config.onError,
|
|
129
|
-
processFn: () =>
|
|
130
|
-
config.processRequest({
|
|
131
|
-
firstImageUri: state.firstImageUri!,
|
|
132
|
-
secondImageUri: state.secondImageUri!,
|
|
133
|
-
onProgress: (progress) => setState((prev) => ({ ...prev, progress })),
|
|
134
|
-
}),
|
|
135
|
-
onSuccess: (result: FeatureProcessResult) => {
|
|
136
|
-
if (result.outputUrl) {
|
|
137
|
-
setState((prev) => ({ ...prev, processedUrl: result.outputUrl ?? null }));
|
|
138
|
-
config.onSuccess?.(result.outputUrl);
|
|
139
|
-
} else {
|
|
140
|
-
const message = result.error || "error.processing";
|
|
141
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
142
|
-
config.onError?.(message);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (!result) {
|
|
148
|
-
setState((prev) => ({ ...prev, progress: 0 }));
|
|
149
|
-
}
|
|
150
|
-
}, [state.firstImageUri, state.secondImageUri, config]);
|
|
151
|
-
|
|
152
|
-
const save = useCallback(async (): Promise<void> => {
|
|
153
|
-
await executeSave({
|
|
154
|
-
processedUrl: state.processedUrl,
|
|
155
|
-
onSave: config.onSave,
|
|
156
|
-
setError: (error) => setState((prev) => ({ ...prev, error })),
|
|
157
|
-
onError: config.onError,
|
|
158
|
-
});
|
|
159
|
-
}, [state.processedUrl, config]);
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
...state,
|
|
163
|
-
selectFirstImage,
|
|
164
|
-
selectSecondImage,
|
|
165
|
-
process,
|
|
166
|
-
save,
|
|
167
|
-
reset,
|
|
168
|
-
clearError,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useImageWithPromptFeature Hook
|
|
3
|
-
* Provider-agnostic hook for image + prompt processing features
|
|
4
|
-
* Examples: Inpainting, Style Transfer, Background Replacement
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useCallback, useState } from "react";
|
|
8
|
-
import type {
|
|
9
|
-
BaseFeatureState,
|
|
10
|
-
BaseFeatureActions,
|
|
11
|
-
FeatureProcessResult,
|
|
12
|
-
OnProgressCallback,
|
|
13
|
-
OnSelectImageCallback,
|
|
14
|
-
OnSaveCallback,
|
|
15
|
-
} from "./types";
|
|
16
|
-
import { createFeatureStateHandlers, executeProcess, executeSave } from "./utils/feature-state.factory";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Request passed to processRequest callback
|
|
20
|
-
*/
|
|
21
|
-
export interface ImageWithPromptProcessRequest {
|
|
22
|
-
readonly imageUri: string;
|
|
23
|
-
readonly prompt: string;
|
|
24
|
-
readonly onProgress: OnProgressCallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Configuration for image with prompt feature
|
|
29
|
-
*/
|
|
30
|
-
export interface UseImageWithPromptFeatureConfig {
|
|
31
|
-
readonly onSelectImage: OnSelectImageCallback;
|
|
32
|
-
readonly processRequest: (
|
|
33
|
-
request: ImageWithPromptProcessRequest
|
|
34
|
-
) => Promise<FeatureProcessResult>;
|
|
35
|
-
readonly onSave?: OnSaveCallback;
|
|
36
|
-
readonly onError?: (error: string) => void;
|
|
37
|
-
readonly onSuccess?: (url: string) => void;
|
|
38
|
-
readonly requirePrompt?: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* State for image with prompt feature
|
|
43
|
-
*/
|
|
44
|
-
export interface ImageWithPromptFeatureState extends BaseFeatureState {
|
|
45
|
-
readonly imageUri: string | null;
|
|
46
|
-
readonly prompt: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Return type for image with prompt feature hook
|
|
51
|
-
*/
|
|
52
|
-
export interface UseImageWithPromptFeatureReturn
|
|
53
|
-
extends ImageWithPromptFeatureState,
|
|
54
|
-
BaseFeatureActions {
|
|
55
|
-
readonly selectImage: () => Promise<void>;
|
|
56
|
-
readonly setPrompt: (prompt: string) => void;
|
|
57
|
-
readonly process: () => Promise<void>;
|
|
58
|
-
readonly save: () => Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const initialState: ImageWithPromptFeatureState = {
|
|
62
|
-
imageUri: null,
|
|
63
|
-
prompt: "",
|
|
64
|
-
processedUrl: null,
|
|
65
|
-
isProcessing: false,
|
|
66
|
-
progress: 0,
|
|
67
|
-
error: null,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export function useImageWithPromptFeature(
|
|
71
|
-
config: UseImageWithPromptFeatureConfig,
|
|
72
|
-
): UseImageWithPromptFeatureReturn {
|
|
73
|
-
const [state, setState] = useState<ImageWithPromptFeatureState>(initialState);
|
|
74
|
-
|
|
75
|
-
const { reset, clearError } = createFeatureStateHandlers({
|
|
76
|
-
setState,
|
|
77
|
-
initialState,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const selectImage = useCallback(async (): Promise<void> => {
|
|
81
|
-
try {
|
|
82
|
-
const uri = await config.onSelectImage();
|
|
83
|
-
if (uri) {
|
|
84
|
-
setState((prev) => ({
|
|
85
|
-
...prev,
|
|
86
|
-
imageUri: uri,
|
|
87
|
-
error: null,
|
|
88
|
-
processedUrl: null,
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
} catch (err) {
|
|
92
|
-
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
93
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
94
|
-
config.onError?.(message);
|
|
95
|
-
}
|
|
96
|
-
}, [config]);
|
|
97
|
-
|
|
98
|
-
const setPrompt = useCallback((prompt: string) => {
|
|
99
|
-
setState((prev) => ({ ...prev, prompt }));
|
|
100
|
-
}, []);
|
|
101
|
-
|
|
102
|
-
const process = useCallback(async (): Promise<void> => {
|
|
103
|
-
if (!state.imageUri) {
|
|
104
|
-
const message = "error.noImage";
|
|
105
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
106
|
-
config.onError?.(message);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (config.requirePrompt && !state.prompt.trim()) {
|
|
111
|
-
const message = "error.noPrompt";
|
|
112
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
113
|
-
config.onError?.(message);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const result = await executeProcess<FeatureProcessResult>({
|
|
118
|
-
canProcess: () => {
|
|
119
|
-
if (!state.imageUri) return false;
|
|
120
|
-
if (config.requirePrompt) return !!state.prompt.trim();
|
|
121
|
-
return true;
|
|
122
|
-
},
|
|
123
|
-
setError: (error: string | null) => setState((prev) => ({ ...prev, error })),
|
|
124
|
-
setProcessing: (isProcessing: boolean) => setState((prev) => ({ ...prev, isProcessing })),
|
|
125
|
-
onError: config.onError,
|
|
126
|
-
processFn: () =>
|
|
127
|
-
config.processRequest({
|
|
128
|
-
imageUri: state.imageUri!,
|
|
129
|
-
prompt: state.prompt.trim(),
|
|
130
|
-
onProgress: (progress) => setState((prev) => ({ ...prev, progress })),
|
|
131
|
-
}),
|
|
132
|
-
onSuccess: (result: FeatureProcessResult) => {
|
|
133
|
-
if (result.outputUrl) {
|
|
134
|
-
setState((prev) => ({ ...prev, processedUrl: result.outputUrl ?? null }));
|
|
135
|
-
config.onSuccess?.(result.outputUrl);
|
|
136
|
-
} else {
|
|
137
|
-
const message = result.error || "error.processing";
|
|
138
|
-
setState((prev) => ({ ...prev, error: message }));
|
|
139
|
-
config.onError?.(message);
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
if (!result) {
|
|
145
|
-
setState((prev) => ({ ...prev, progress: 0 }));
|
|
146
|
-
}
|
|
147
|
-
}, [state.imageUri, state.prompt, config]);
|
|
148
|
-
|
|
149
|
-
const save = useCallback(async (): Promise<void> => {
|
|
150
|
-
await executeSave({
|
|
151
|
-
processedUrl: state.processedUrl,
|
|
152
|
-
onSave: config.onSave,
|
|
153
|
-
setError: (error) => setState((prev) => ({ ...prev, error })),
|
|
154
|
-
onError: config.onError,
|
|
155
|
-
});
|
|
156
|
-
}, [state.processedUrl, config]);
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
...state,
|
|
160
|
-
selectImage,
|
|
161
|
-
setPrompt,
|
|
162
|
-
process,
|
|
163
|
-
save,
|
|
164
|
-
reset,
|
|
165
|
-
clearError,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSingleImageFeature Hook
|
|
3
|
-
* Provider-agnostic hook for single image processing features
|
|
4
|
-
* App provides processRequest callback with their AI provider
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useCallback, useState } from "react";
|
|
8
|
-
import type {
|
|
9
|
-
BaseFeatureState,
|
|
10
|
-
BaseFeatureActions,
|
|
11
|
-
FeatureProcessResult,
|
|
12
|
-
OnProgressCallback,
|
|
13
|
-
OnSelectImageCallback,
|
|
14
|
-
OnSaveCallback,
|
|
15
|
-
} from "./types";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Request passed to processRequest callback
|
|
19
|
-
*/
|
|
20
|
-
export interface SingleImageProcessRequest {
|
|
21
|
-
readonly imageUri: string;
|
|
22
|
-
readonly onProgress: OnProgressCallback;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Configuration for single image feature
|
|
27
|
-
*/
|
|
28
|
-
export interface UseSingleImageFeatureConfig {
|
|
29
|
-
readonly onSelectImage: OnSelectImageCallback;
|
|
30
|
-
readonly processRequest: (
|
|
31
|
-
request: SingleImageProcessRequest
|
|
32
|
-
) => Promise<FeatureProcessResult>;
|
|
33
|
-
readonly onSave?: OnSaveCallback;
|
|
34
|
-
readonly onError?: (error: string) => void;
|
|
35
|
-
readonly onSuccess?: (url: string) => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* State for single image feature
|
|
40
|
-
*/
|
|
41
|
-
export interface SingleImageFeatureState extends BaseFeatureState {
|
|
42
|
-
readonly imageUri: string | null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Return type for single image feature hook
|
|
47
|
-
*/
|
|
48
|
-
export interface UseSingleImageFeatureReturn
|
|
49
|
-
extends SingleImageFeatureState,
|
|
50
|
-
BaseFeatureActions {
|
|
51
|
-
readonly selectImage: () => Promise<void>;
|
|
52
|
-
readonly process: () => Promise<void>;
|
|
53
|
-
readonly save: () => Promise<void>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function useSingleImageFeature(
|
|
57
|
-
config: UseSingleImageFeatureConfig
|
|
58
|
-
): UseSingleImageFeatureReturn {
|
|
59
|
-
const [imageUri, setImageUri] = useState<string | null>(null);
|
|
60
|
-
const [processedUrl, setProcessedUrl] = useState<string | null>(null);
|
|
61
|
-
const [isProcessing, setIsProcessing] = useState(false);
|
|
62
|
-
const [progress, setProgress] = useState(0);
|
|
63
|
-
const [error, setError] = useState<string | null>(null);
|
|
64
|
-
|
|
65
|
-
const selectImage = useCallback(async (): Promise<void> => {
|
|
66
|
-
try {
|
|
67
|
-
const uri = await config.onSelectImage();
|
|
68
|
-
if (uri) {
|
|
69
|
-
setImageUri(uri);
|
|
70
|
-
setError(null);
|
|
71
|
-
setProcessedUrl(null);
|
|
72
|
-
}
|
|
73
|
-
} catch (err) {
|
|
74
|
-
const message = err instanceof Error ? err.message : "error.selectImage";
|
|
75
|
-
setError(message);
|
|
76
|
-
config.onError?.(message);
|
|
77
|
-
}
|
|
78
|
-
}, [config]);
|
|
79
|
-
|
|
80
|
-
const process = useCallback(async (): Promise<void> => {
|
|
81
|
-
if (!imageUri) {
|
|
82
|
-
const message = "error.noImage";
|
|
83
|
-
setError(message);
|
|
84
|
-
config.onError?.(message);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
setIsProcessing(true);
|
|
89
|
-
setProgress(0);
|
|
90
|
-
setError(null);
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const result = await config.processRequest({
|
|
94
|
-
imageUri,
|
|
95
|
-
onProgress: setProgress,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (result.success && result.outputUrl) {
|
|
99
|
-
setProcessedUrl(result.outputUrl);
|
|
100
|
-
config.onSuccess?.(result.outputUrl);
|
|
101
|
-
} else {
|
|
102
|
-
const message = result.error || "error.processing";
|
|
103
|
-
setError(message);
|
|
104
|
-
config.onError?.(message);
|
|
105
|
-
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
const message = err instanceof Error ? err.message : "error.processing";
|
|
108
|
-
setError(message);
|
|
109
|
-
config.onError?.(message);
|
|
110
|
-
} finally {
|
|
111
|
-
setIsProcessing(false);
|
|
112
|
-
setProgress(0);
|
|
113
|
-
}
|
|
114
|
-
}, [imageUri, config]);
|
|
115
|
-
|
|
116
|
-
const save = useCallback(async (): Promise<void> => {
|
|
117
|
-
if (!processedUrl || !config.onSave) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
await config.onSave(processedUrl);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
const message = err instanceof Error ? err.message : "error.save";
|
|
125
|
-
setError(message);
|
|
126
|
-
config.onError?.(message);
|
|
127
|
-
}
|
|
128
|
-
}, [processedUrl, config]);
|
|
129
|
-
|
|
130
|
-
const reset = useCallback((): void => {
|
|
131
|
-
setImageUri(null);
|
|
132
|
-
setProcessedUrl(null);
|
|
133
|
-
setIsProcessing(false);
|
|
134
|
-
setProgress(0);
|
|
135
|
-
setError(null);
|
|
136
|
-
}, []);
|
|
137
|
-
|
|
138
|
-
const clearError = useCallback((): void => {
|
|
139
|
-
setError(null);
|
|
140
|
-
}, []);
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
imageUri,
|
|
144
|
-
processedUrl,
|
|
145
|
-
isProcessing,
|
|
146
|
-
progress,
|
|
147
|
-
error,
|
|
148
|
-
selectImage,
|
|
149
|
-
process,
|
|
150
|
-
save,
|
|
151
|
-
reset,
|
|
152
|
-
clearError,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feature State Factory
|
|
3
|
-
* Factory functions for creating feature state handlers
|
|
4
|
-
* Reduces code duplication across feature hooks
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useCallback } from "react";
|
|
8
|
-
|
|
9
|
-
export interface FeatureStateActions {
|
|
10
|
-
reset: () => void;
|
|
11
|
-
clearError: () => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface CreateStateHandlersParams<TState> {
|
|
15
|
-
setState: React.Dispatch<React.SetStateAction<TState>>;
|
|
16
|
-
initialState: TState;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Creates reset and clearError handlers for feature state
|
|
21
|
-
*/
|
|
22
|
-
export function createFeatureStateHandlers<TState extends { error: string | null }>({
|
|
23
|
-
setState,
|
|
24
|
-
initialState,
|
|
25
|
-
}: CreateStateHandlersParams<TState>): FeatureStateActions {
|
|
26
|
-
const reset = useCallback(() => {
|
|
27
|
-
setState(initialState);
|
|
28
|
-
}, [setState, initialState]);
|
|
29
|
-
|
|
30
|
-
const clearError = useCallback(() => {
|
|
31
|
-
setState((prev) => ({
|
|
32
|
-
...prev,
|
|
33
|
-
error: null,
|
|
34
|
-
}));
|
|
35
|
-
}, [setState]);
|
|
36
|
-
|
|
37
|
-
return { reset, clearError };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Creates error handler with logging
|
|
42
|
-
*/
|
|
43
|
-
export interface ErrorHandlerParams {
|
|
44
|
-
setError: (error: string | null) => void;
|
|
45
|
-
onError?: (error: string) => void;
|
|
46
|
-
errorKey: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createErrorHandler({
|
|
50
|
-
setError,
|
|
51
|
-
onError,
|
|
52
|
-
errorKey,
|
|
53
|
-
}: ErrorHandlerParams) {
|
|
54
|
-
return useCallback((error: unknown) => {
|
|
55
|
-
const message = error instanceof Error ? error.message : errorKey;
|
|
56
|
-
setError(message);
|
|
57
|
-
onError?.(message);
|
|
58
|
-
}, [setError, onError, errorKey]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Creates process handler with common logic
|
|
63
|
-
*/
|
|
64
|
-
export interface ProcessHandlerParams<TResult> {
|
|
65
|
-
canProcess: () => boolean;
|
|
66
|
-
setError: (error: string | null) => void;
|
|
67
|
-
setProcessing: (processing: boolean) => void;
|
|
68
|
-
onError?: (error: string) => void;
|
|
69
|
-
processFn: () => Promise<TResult>;
|
|
70
|
-
onSuccess?: (result: TResult) => void;
|
|
71
|
-
onProgress?: (progress: number) => void;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function executeProcess<TResult>({
|
|
75
|
-
canProcess,
|
|
76
|
-
setError,
|
|
77
|
-
setProcessing,
|
|
78
|
-
onError,
|
|
79
|
-
processFn,
|
|
80
|
-
onSuccess,
|
|
81
|
-
onProgress,
|
|
82
|
-
}: ProcessHandlerParams<TResult>): Promise<TResult | null> {
|
|
83
|
-
if (!canProcess()) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setProcessing(true);
|
|
88
|
-
setError(null);
|
|
89
|
-
onProgress?.(0);
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const result = await processFn();
|
|
93
|
-
onProgress?.(100);
|
|
94
|
-
onSuccess?.(result);
|
|
95
|
-
return result;
|
|
96
|
-
} catch (err) {
|
|
97
|
-
const message = err instanceof Error ? err.message : "error.processing";
|
|
98
|
-
setError(message);
|
|
99
|
-
onError?.(message);
|
|
100
|
-
return null;
|
|
101
|
-
} finally {
|
|
102
|
-
setProcessing(false);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Creates save handler
|
|
108
|
-
*/
|
|
109
|
-
export interface SaveHandlerParams {
|
|
110
|
-
processedUrl: string | null;
|
|
111
|
-
onSave?: (url: string) => Promise<void>;
|
|
112
|
-
setError: (error: string | null) => void;
|
|
113
|
-
onError?: (error: string) => void;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function executeSave({
|
|
117
|
-
processedUrl,
|
|
118
|
-
onSave,
|
|
119
|
-
setError,
|
|
120
|
-
onError,
|
|
121
|
-
}: SaveHandlerParams): Promise<void> {
|
|
122
|
-
if (!processedUrl || !onSave) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
await onSave(processedUrl);
|
|
128
|
-
} catch (err) {
|
|
129
|
-
const message = err instanceof Error ? err.message : "error.save";
|
|
130
|
-
setError(message);
|
|
131
|
-
onError?.(message);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generation Callbacks Types
|
|
3
|
-
* Type definitions for unified generation flow
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type CreditType = "image" | "text" | "video" | "audio";
|
|
7
|
-
|
|
8
|
-
export interface GenerationExecutionResult<T = unknown> {
|
|
9
|
-
success: boolean;
|
|
10
|
-
data?: T;
|
|
11
|
-
error?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface GenerationCallbacksConfig<TRequest, TResult> {
|
|
15
|
-
userId: string | null;
|
|
16
|
-
isAuthenticated: boolean;
|
|
17
|
-
creditBalance: number;
|
|
18
|
-
creditCost: number;
|
|
19
|
-
creditType: CreditType;
|
|
20
|
-
executor: (request: TRequest) => Promise<GenerationExecutionResult<TResult>>;
|
|
21
|
-
deductCredit: (type: CreditType) => Promise<void>;
|
|
22
|
-
openPaywall: () => void;
|
|
23
|
-
showAuthModal?: () => void;
|
|
24
|
-
saveCreation?: (result: TResult) => Promise<void>;
|
|
25
|
-
onNavigateAfterSuccess?: () => void;
|
|
26
|
-
invalidateQueryKeys?: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface GenerationCallbacks<TRequest, TResult> {
|
|
30
|
-
canAfford: (cost?: number) => boolean;
|
|
31
|
-
isAuthenticated: () => boolean;
|
|
32
|
-
calculateCost: (multiplier?: number) => number;
|
|
33
|
-
execute: (request: TRequest) => Promise<GenerationExecutionResult<TResult>>;
|
|
34
|
-
onAuthRequired: () => void;
|
|
35
|
-
onCreditsRequired: () => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface UseGenerationCallbacksBuilderOptions<TRequest, TResult> {
|
|
39
|
-
config: GenerationCallbacksConfig<TRequest, TResult>;
|
|
40
|
-
onSuccess?: (result: TResult) => void;
|
|
41
|
-
onError?: (error: string) => void;
|
|
42
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Generation Callbacks Builder
|
|
3
|
-
* Unified hook for ALL generation types (image, video, audio, text)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useCallback, useMemo, useRef } from "react";
|
|
7
|
-
import { useQueryClient } from "@umituz/react-native-design-system";
|
|
8
|
-
import type {
|
|
9
|
-
GenerationExecutionResult,
|
|
10
|
-
GenerationCallbacks,
|
|
11
|
-
UseGenerationCallbacksBuilderOptions,
|
|
12
|
-
} from "./generation-callbacks.types";
|
|
13
|
-
|
|
14
|
-
declare const __DEV__: boolean;
|
|
15
|
-
|
|
16
|
-
export function useGenerationCallbacksBuilder<TRequest, TResult>(
|
|
17
|
-
options: UseGenerationCallbacksBuilderOptions<TRequest, TResult>,
|
|
18
|
-
): { callbacks: GenerationCallbacks<TRequest, TResult> } {
|
|
19
|
-
const { config, onSuccess, onError } = options;
|
|
20
|
-
const queryClient = useQueryClient();
|
|
21
|
-
const isExecutingRef = useRef(false);
|
|
22
|
-
|
|
23
|
-
const canAfford = useCallback(
|
|
24
|
-
(cost?: number): boolean => {
|
|
25
|
-
const actualCost = cost ?? config.creditCost;
|
|
26
|
-
return config.creditBalance >= actualCost;
|
|
27
|
-
},
|
|
28
|
-
[config.creditBalance, config.creditCost],
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const isAuthenticated = useCallback(
|
|
32
|
-
(): boolean => config.isAuthenticated,
|
|
33
|
-
[config.isAuthenticated],
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const calculateCost = useCallback(
|
|
37
|
-
(multiplier = 1): number => config.creditCost * multiplier,
|
|
38
|
-
[config.creditCost],
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const onAuthRequired = useCallback(() => {
|
|
42
|
-
if (config.showAuthModal) {
|
|
43
|
-
config.showAuthModal();
|
|
44
|
-
} else {
|
|
45
|
-
config.openPaywall();
|
|
46
|
-
}
|
|
47
|
-
}, [config]);
|
|
48
|
-
|
|
49
|
-
const onCreditsRequired = useCallback(() => {
|
|
50
|
-
config.openPaywall();
|
|
51
|
-
}, [config]);
|
|
52
|
-
|
|
53
|
-
const execute = useCallback(
|
|
54
|
-
async (request: TRequest): Promise<GenerationExecutionResult<TResult>> => {
|
|
55
|
-
if (isExecutingRef.current) {
|
|
56
|
-
return { success: false, error: "Generation already in progress" };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!config.isAuthenticated || !config.userId) {
|
|
60
|
-
onAuthRequired();
|
|
61
|
-
return { success: false, error: "Authentication required" };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!canAfford()) {
|
|
65
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
66
|
-
|
|
67
|
-
console.log("[Generation] Insufficient credits", {
|
|
68
|
-
balance: config.creditBalance,
|
|
69
|
-
cost: config.creditCost,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
onCreditsRequired();
|
|
73
|
-
return { success: false, error: "Insufficient credits" };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
isExecutingRef.current = true;
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const result = await config.executor(request);
|
|
80
|
-
|
|
81
|
-
if (!result.success || !result.data) {
|
|
82
|
-
return { success: false, error: result.error || "Generation failed" };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
await config.deductCredit(config.creditType);
|
|
86
|
-
|
|
87
|
-
if (config.saveCreation) {
|
|
88
|
-
try {
|
|
89
|
-
await config.saveCreation(result.data);
|
|
90
|
-
} catch {
|
|
91
|
-
// Silent fail for save
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const keys = config.invalidateQueryKeys ?? ["creations"];
|
|
96
|
-
keys.forEach((key) => queryClient.invalidateQueries({ queryKey: [key] }));
|
|
97
|
-
|
|
98
|
-
onSuccess?.(result.data);
|
|
99
|
-
config.onNavigateAfterSuccess?.();
|
|
100
|
-
|
|
101
|
-
return { success: true, data: result.data };
|
|
102
|
-
} catch (error) {
|
|
103
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
104
|
-
onError?.(message);
|
|
105
|
-
return { success: false, error: message };
|
|
106
|
-
} finally {
|
|
107
|
-
isExecutingRef.current = false;
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
[config, canAfford, onAuthRequired, onCreditsRequired, queryClient, onSuccess, onError],
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const callbacks = useMemo<GenerationCallbacks<TRequest, TResult>>(
|
|
114
|
-
() => ({
|
|
115
|
-
canAfford,
|
|
116
|
-
isAuthenticated,
|
|
117
|
-
calculateCost,
|
|
118
|
-
execute,
|
|
119
|
-
onAuthRequired,
|
|
120
|
-
onCreditsRequired,
|
|
121
|
-
}),
|
|
122
|
-
[canAfford, isAuthenticated, calculateCost, execute, onAuthRequired, onCreditsRequired],
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
return { callbacks };
|
|
126
|
-
}
|