@umituz/react-native-ai-generation-content 1.26.67 → 1.26.69
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/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +22 -3
- package/src/domains/generation/wizard/presentation/components/GenericWizardFlow.tsx +4 -20
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +8 -40
- package/src/domains/prompts/index.ts +8 -0
- package/src/domains/prompts/infrastructure/builders/face-preservation-builder.ts +4 -50
- package/src/domains/prompts/infrastructure/builders/interaction-style-builder.ts +163 -0
- package/src/presentation/hooks/generation/orchestrator.ts +31 -185
- package/src/presentation/hooks/generation/types.ts +4 -61
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.69",
|
|
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/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
MODEL_INPUT_DEFAULTS,
|
|
16
16
|
} from "./wizard-strategy.constants";
|
|
17
17
|
import { buildFacePreservationPrompt } from "../../../../prompts/infrastructure/builders/face-preservation-builder";
|
|
18
|
+
import { buildInteractionStylePrompt, type InteractionStyle } from "../../../../prompts/infrastructure/builders/interaction-style-builder";
|
|
18
19
|
|
|
19
20
|
declare const __DEV__: boolean;
|
|
20
21
|
|
|
@@ -25,6 +26,8 @@ declare const __DEV__: boolean;
|
|
|
25
26
|
export interface ImageGenerationInput {
|
|
26
27
|
readonly photos: readonly string[];
|
|
27
28
|
readonly prompt: string;
|
|
29
|
+
/** Optional interaction style for multi-person images */
|
|
30
|
+
readonly interactionStyle?: InteractionStyle;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export interface ImageGenerationResult {
|
|
@@ -88,13 +91,26 @@ async function executeImageGeneration(
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
// Build face preservation prompt dynamically based on number of people
|
|
91
|
-
const
|
|
94
|
+
const facePrompt = buildFacePreservationPrompt({
|
|
92
95
|
scenarioPrompt: input.prompt,
|
|
93
96
|
personCount: imageUrls.length,
|
|
94
97
|
});
|
|
95
98
|
|
|
99
|
+
// Build interaction style prompt for multi-person images
|
|
100
|
+
const interactionPrompt = buildInteractionStylePrompt({
|
|
101
|
+
style: input.interactionStyle ?? "romantic",
|
|
102
|
+
personCount: imageUrls.length,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Combine prompts: face preservation + interaction style
|
|
106
|
+
const enhancedPrompt = interactionPrompt
|
|
107
|
+
? `${facePrompt}\n\n${interactionPrompt}`
|
|
108
|
+
: facePrompt;
|
|
109
|
+
|
|
96
110
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
97
|
-
console.log("[ImageStrategy]
|
|
111
|
+
console.log("[ImageStrategy] Prompt built for", imageUrls.length, "person(s)", {
|
|
112
|
+
interactionStyle: input.interactionStyle ?? "romantic",
|
|
113
|
+
});
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
const modelInput = {
|
|
@@ -164,7 +180,10 @@ export async function buildImageInput(
|
|
|
164
180
|
prompt = `${prompt}. ${styleEnhancements.join(", ")}`;
|
|
165
181
|
}
|
|
166
182
|
|
|
167
|
-
|
|
183
|
+
// Get interaction style from scenario (default: romantic for couple apps)
|
|
184
|
+
const interactionStyle = (scenario.interactionStyle as InteractionStyle) ?? "romantic";
|
|
185
|
+
|
|
186
|
+
return { photos, prompt, interactionStyle };
|
|
168
187
|
}
|
|
169
188
|
|
|
170
189
|
// ============================================================================
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generic Wizard Flow Component
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Works for:
|
|
6
|
-
* - Couple features (romantic-kiss, ai-hug, etc.)
|
|
7
|
-
* - Face swap
|
|
8
|
-
* - Image-to-video
|
|
9
|
-
* - Text-to-video
|
|
10
|
-
* - ANY future feature!
|
|
11
|
-
*
|
|
12
|
-
* NO feature-specific code here - everything driven by configuration!
|
|
3
|
+
* Config-driven wizard for AI generation features
|
|
13
4
|
*/
|
|
14
5
|
|
|
15
6
|
import React, { useMemo, useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -28,6 +19,8 @@ import { validateScenario } from "../utilities/validateScenario";
|
|
|
28
19
|
import { WizardStepRenderer } from "./WizardStepRenderer";
|
|
29
20
|
import { StarRatingPicker } from "../../../../result-preview/presentation/components/StarRatingPicker";
|
|
30
21
|
|
|
22
|
+
declare const __DEV__: boolean;
|
|
23
|
+
|
|
31
24
|
export interface GenericWizardFlowProps {
|
|
32
25
|
readonly featureConfig: WizardFeatureConfig;
|
|
33
26
|
readonly scenario?: WizardScenarioData;
|
|
@@ -38,9 +31,6 @@ export interface GenericWizardFlowProps {
|
|
|
38
31
|
readonly onGenerationComplete?: (result: unknown) => void;
|
|
39
32
|
readonly onGenerationError?: (error: string) => void;
|
|
40
33
|
readonly onCreditsExhausted?: () => void;
|
|
41
|
-
/** Auth check - orchestrator will check auth BEFORE credits */
|
|
42
|
-
readonly isAuthenticated?: () => boolean;
|
|
43
|
-
readonly onAuthRequired?: () => void;
|
|
44
34
|
readonly onRate?: (creationId: string, rating: number, description: string) => Promise<boolean>;
|
|
45
35
|
readonly onBack?: () => void;
|
|
46
36
|
readonly onTryAgain?: () => void;
|
|
@@ -61,8 +51,6 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
61
51
|
onGenerationComplete,
|
|
62
52
|
onGenerationError,
|
|
63
53
|
onCreditsExhausted,
|
|
64
|
-
isAuthenticated,
|
|
65
|
-
onAuthRequired,
|
|
66
54
|
onRate,
|
|
67
55
|
onBack,
|
|
68
56
|
onTryAgain,
|
|
@@ -130,8 +118,6 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
130
118
|
onError: onGenerationError,
|
|
131
119
|
onProgressChange: handleProgressChange,
|
|
132
120
|
onCreditsExhausted,
|
|
133
|
-
isAuthenticated,
|
|
134
|
-
onAuthRequired,
|
|
135
121
|
});
|
|
136
122
|
|
|
137
123
|
useEffect(() => {
|
|
@@ -174,9 +160,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
174
160
|
const handleSubmitRating = useCallback(async (rating: number, description: string) => {
|
|
175
161
|
if (!currentCreation?.id || !onRate) return;
|
|
176
162
|
const success = await onRate(currentCreation.id, rating, description);
|
|
177
|
-
if (success)
|
|
178
|
-
setHasRated(true);
|
|
179
|
-
}
|
|
163
|
+
if (success) setHasRated(true);
|
|
180
164
|
setShowRatingPicker(false);
|
|
181
165
|
}, [currentCreation, onRate]);
|
|
182
166
|
|
|
@@ -10,17 +10,13 @@ import { createWizardStrategy, buildWizardInput } from "../../infrastructure/str
|
|
|
10
10
|
|
|
11
11
|
declare const __DEV__: boolean;
|
|
12
12
|
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Types
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
13
|
export type WizardOutputType = "image" | "video";
|
|
18
14
|
|
|
19
15
|
export interface WizardScenarioData {
|
|
20
16
|
readonly id: string;
|
|
21
17
|
readonly aiPrompt: string;
|
|
22
18
|
readonly outputType?: WizardOutputType;
|
|
23
|
-
readonly model?: string;
|
|
19
|
+
readonly model?: string;
|
|
24
20
|
readonly title?: string;
|
|
25
21
|
readonly description?: string;
|
|
26
22
|
[key: string]: unknown;
|
|
@@ -36,9 +32,6 @@ export interface UseWizardGenerationProps {
|
|
|
36
32
|
readonly onError?: (error: string) => void;
|
|
37
33
|
readonly onProgressChange?: (progress: number) => void;
|
|
38
34
|
readonly onCreditsExhausted?: () => void;
|
|
39
|
-
/** Auth check - if user not authenticated, onAuthRequired will be called */
|
|
40
|
-
readonly isAuthenticated?: () => boolean;
|
|
41
|
-
readonly onAuthRequired?: () => void;
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
export interface UseWizardGenerationReturn {
|
|
@@ -46,10 +39,6 @@ export interface UseWizardGenerationReturn {
|
|
|
46
39
|
readonly progress: number;
|
|
47
40
|
}
|
|
48
41
|
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Hook
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
42
|
export const useWizardGeneration = (
|
|
54
43
|
props: UseWizardGenerationProps,
|
|
55
44
|
): UseWizardGenerationReturn => {
|
|
@@ -63,24 +52,19 @@ export const useWizardGeneration = (
|
|
|
63
52
|
onError,
|
|
64
53
|
onProgressChange,
|
|
65
54
|
onCreditsExhausted,
|
|
66
|
-
isAuthenticated,
|
|
67
|
-
onAuthRequired,
|
|
68
55
|
} = props;
|
|
69
56
|
|
|
70
57
|
const hasStarted = useRef(false);
|
|
71
58
|
|
|
72
|
-
// Log output type on mount
|
|
73
59
|
useEffect(() => {
|
|
74
60
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
75
61
|
console.log("[useWizardGeneration] Initialized", {
|
|
76
62
|
scenarioId: scenario.id,
|
|
77
63
|
outputType: scenario.outputType || "video",
|
|
78
|
-
hasOutputType: !!scenario.outputType,
|
|
79
64
|
});
|
|
80
65
|
}
|
|
81
66
|
}, [scenario.id, scenario.outputType]);
|
|
82
67
|
|
|
83
|
-
// Create strategy using factory
|
|
84
68
|
const strategy = useMemo(() => {
|
|
85
69
|
return createWizardStrategy({
|
|
86
70
|
scenario,
|
|
@@ -89,7 +73,6 @@ export const useWizardGeneration = (
|
|
|
89
73
|
});
|
|
90
74
|
}, [scenario, wizardData]);
|
|
91
75
|
|
|
92
|
-
// Use orchestrator with strategy
|
|
93
76
|
const { generate, isGenerating, progress } = useGenerationOrchestrator(
|
|
94
77
|
strategy,
|
|
95
78
|
{
|
|
@@ -102,10 +85,6 @@ export const useWizardGeneration = (
|
|
|
102
85
|
unknown: "An error occurred",
|
|
103
86
|
},
|
|
104
87
|
onCreditsExhausted,
|
|
105
|
-
// Auth config - check auth BEFORE credits
|
|
106
|
-
auth: isAuthenticated && onAuthRequired
|
|
107
|
-
? { isAuthenticated, onAuthRequired }
|
|
108
|
-
: undefined,
|
|
109
88
|
onSuccess: (result) => {
|
|
110
89
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
111
90
|
console.log("[useWizardGeneration] Success");
|
|
@@ -121,14 +100,10 @@ export const useWizardGeneration = (
|
|
|
121
100
|
},
|
|
122
101
|
);
|
|
123
102
|
|
|
124
|
-
// Sync progress to parent
|
|
125
103
|
useEffect(() => {
|
|
126
|
-
if (onProgressChange)
|
|
127
|
-
onProgressChange(progress);
|
|
128
|
-
}
|
|
104
|
+
if (onProgressChange) onProgressChange(progress);
|
|
129
105
|
}, [progress, onProgressChange]);
|
|
130
106
|
|
|
131
|
-
// Auto-start generation when entering generating step
|
|
132
107
|
useEffect(() => {
|
|
133
108
|
if (isGeneratingStep && !hasStarted.current && !isGenerating) {
|
|
134
109
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
@@ -138,37 +113,30 @@ export const useWizardGeneration = (
|
|
|
138
113
|
});
|
|
139
114
|
}
|
|
140
115
|
|
|
141
|
-
|
|
116
|
+
hasStarted.current = true;
|
|
117
|
+
|
|
142
118
|
buildWizardInput(wizardData, scenario)
|
|
143
119
|
.then((input) => {
|
|
144
120
|
if (!input) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
console.error("[useWizardGeneration]", error);
|
|
148
|
-
}
|
|
149
|
-
onError?.(error);
|
|
121
|
+
hasStarted.current = false;
|
|
122
|
+
onError?.("Failed to build generation input");
|
|
150
123
|
return;
|
|
151
124
|
}
|
|
152
|
-
|
|
153
125
|
generate(input);
|
|
154
|
-
hasStarted.current = true;
|
|
155
126
|
})
|
|
156
127
|
.catch((error) => {
|
|
157
128
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
158
129
|
console.error("[useWizardGeneration] Input build error:", error);
|
|
159
130
|
}
|
|
131
|
+
hasStarted.current = false;
|
|
160
132
|
onError?.(error.message || "Failed to prepare generation");
|
|
161
133
|
});
|
|
162
134
|
}
|
|
163
135
|
|
|
164
|
-
// Reset hasStarted when leaving generating step
|
|
165
136
|
if (!isGeneratingStep && hasStarted.current) {
|
|
166
137
|
hasStarted.current = false;
|
|
167
138
|
}
|
|
168
139
|
}, [isGeneratingStep, scenario, wizardData, isGenerating, generate, onError]);
|
|
169
140
|
|
|
170
|
-
return {
|
|
171
|
-
isGenerating,
|
|
172
|
-
progress,
|
|
173
|
-
};
|
|
141
|
+
return { isGenerating, progress };
|
|
174
142
|
};
|
|
@@ -102,3 +102,11 @@ export {
|
|
|
102
102
|
buildMinimalFacePreservationPrompt,
|
|
103
103
|
} from './infrastructure/builders/face-preservation-builder';
|
|
104
104
|
export type { FacePreservationOptions } from './infrastructure/builders/face-preservation-builder';
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
buildInteractionStylePrompt,
|
|
108
|
+
buildMinimalInteractionStylePrompt,
|
|
109
|
+
getInteractionRules,
|
|
110
|
+
getInteractionForbidden,
|
|
111
|
+
} from './infrastructure/builders/interaction-style-builder';
|
|
112
|
+
export type { InteractionStyle, InteractionStyleOptions } from './infrastructure/builders/interaction-style-builder';
|
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Face Preservation Prompt Builder
|
|
3
3
|
* Dynamic prompt builder for AI image generation with strict face identity preservation
|
|
4
|
-
* Supports any number of people (1, 2, 3, N)
|
|
4
|
+
* Supports any number of people (1, 2, 3, N)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* SINGLE RESPONSIBILITY: Face identity preservation ONLY
|
|
7
7
|
* - Face identity lock techniques
|
|
8
8
|
* - @imageN reference anchors
|
|
9
|
-
* - Interaction style rules (romantic, friendly, etc.)
|
|
10
9
|
* - Explicit preservation and negative constraints
|
|
10
|
+
*
|
|
11
|
+
* For interaction/positioning rules, use interaction-style-builder.ts
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
// =============================================================================
|
|
14
15
|
// Types
|
|
15
16
|
// =============================================================================
|
|
16
17
|
|
|
17
|
-
/** Interaction style between people in the image */
|
|
18
|
-
export type InteractionStyle = "romantic" | "friendly" | "professional" | "neutral";
|
|
19
|
-
|
|
20
18
|
export interface FacePreservationOptions {
|
|
21
19
|
/** The scenario/scene description */
|
|
22
20
|
scenarioPrompt: string;
|
|
23
21
|
/** Number of people in the generation */
|
|
24
22
|
personCount: number;
|
|
25
|
-
/** Interaction style between people (default: neutral) */
|
|
26
|
-
interactionStyle?: InteractionStyle;
|
|
27
23
|
/** Optional custom preservation rules from main app */
|
|
28
24
|
customRules?: string[];
|
|
29
25
|
/** Optional custom forbidden actions from main app */
|
|
@@ -49,48 +45,6 @@ const FORBIDDEN_ACTIONS = [
|
|
|
49
45
|
"Do NOT change eye color or shape",
|
|
50
46
|
] as const;
|
|
51
47
|
|
|
52
|
-
/** Interaction style rules - what TO DO for each style */
|
|
53
|
-
const INTERACTION_RULES: Record<InteractionStyle, readonly string[]> = {
|
|
54
|
-
romantic: [
|
|
55
|
-
"Close physical proximity - touching, holding hands, arms around each other",
|
|
56
|
-
"Warm, genuine, loving smiles showing happiness",
|
|
57
|
-
"Affectionate eye contact or looking at camera together happily",
|
|
58
|
-
"Natural romantic body language - leaning into each other",
|
|
59
|
-
"Intimate connection visible between the two people",
|
|
60
|
-
],
|
|
61
|
-
friendly: [
|
|
62
|
-
"Casual comfortable proximity",
|
|
63
|
-
"Genuine friendly smiles",
|
|
64
|
-
"Relaxed natural poses",
|
|
65
|
-
"Warm friendly body language",
|
|
66
|
-
],
|
|
67
|
-
professional: [
|
|
68
|
-
"Appropriate professional distance",
|
|
69
|
-
"Confident pleasant expressions",
|
|
70
|
-
"Professional posture and positioning",
|
|
71
|
-
],
|
|
72
|
-
neutral: [],
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/** Interaction style forbidden - what NOT to do for each style */
|
|
76
|
-
const INTERACTION_FORBIDDEN: Record<InteractionStyle, readonly string[]> = {
|
|
77
|
-
romantic: [
|
|
78
|
-
"Do NOT position people far apart or distant from each other",
|
|
79
|
-
"Do NOT use cold, serious, stern, or angry expressions",
|
|
80
|
-
"Do NOT create stiff, awkward, or unnatural poses",
|
|
81
|
-
"Do NOT have people looking away from each other coldly",
|
|
82
|
-
"Do NOT show disconnection or emotional distance between people",
|
|
83
|
-
],
|
|
84
|
-
friendly: [
|
|
85
|
-
"Do NOT use cold or unfriendly expressions",
|
|
86
|
-
"Do NOT create awkward distancing",
|
|
87
|
-
],
|
|
88
|
-
professional: [
|
|
89
|
-
"Do NOT use overly casual or intimate positioning",
|
|
90
|
-
],
|
|
91
|
-
neutral: [],
|
|
92
|
-
};
|
|
93
|
-
|
|
94
48
|
// =============================================================================
|
|
95
49
|
// Builder Functions
|
|
96
50
|
// =============================================================================
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction Style Prompt Builder
|
|
3
|
+
* Dynamic prompt builder for AI image generation with interaction/positioning rules
|
|
4
|
+
* Supports any number of people (1, 2, 3, N) and interaction styles
|
|
5
|
+
*
|
|
6
|
+
* Separate from face preservation - this handles:
|
|
7
|
+
* - Body positioning and proximity
|
|
8
|
+
* - Emotional expressions
|
|
9
|
+
* - Body language and poses
|
|
10
|
+
* - Interaction rules between people
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/** Interaction style between people in the image */
|
|
18
|
+
export type InteractionStyle =
|
|
19
|
+
| "romantic"
|
|
20
|
+
| "friendly"
|
|
21
|
+
| "professional"
|
|
22
|
+
| "neutral";
|
|
23
|
+
|
|
24
|
+
export interface InteractionStyleOptions {
|
|
25
|
+
/** Interaction style between people */
|
|
26
|
+
style: InteractionStyle;
|
|
27
|
+
/** Number of people in the generation */
|
|
28
|
+
personCount: number;
|
|
29
|
+
/** Optional custom rules from main app */
|
|
30
|
+
customRules?: string[];
|
|
31
|
+
/** Optional custom forbidden actions from main app */
|
|
32
|
+
customForbidden?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Constants
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
/** Interaction style rules - what TO DO for each style */
|
|
40
|
+
const INTERACTION_RULES: Record<InteractionStyle, readonly string[]> = {
|
|
41
|
+
romantic: [
|
|
42
|
+
"Close physical proximity - touching, holding hands, arms around each other",
|
|
43
|
+
"Warm, genuine, loving smiles showing happiness",
|
|
44
|
+
"Affectionate eye contact or looking at camera together happily",
|
|
45
|
+
"Natural romantic body language - leaning into each other",
|
|
46
|
+
"Intimate connection visible between the two people",
|
|
47
|
+
"Comfortable and relaxed together, natural poses",
|
|
48
|
+
],
|
|
49
|
+
friendly: [
|
|
50
|
+
"Casual comfortable proximity",
|
|
51
|
+
"Genuine friendly smiles",
|
|
52
|
+
"Relaxed natural poses",
|
|
53
|
+
"Warm friendly body language",
|
|
54
|
+
"Standing or sitting close to each other comfortably",
|
|
55
|
+
],
|
|
56
|
+
professional: [
|
|
57
|
+
"Appropriate professional distance",
|
|
58
|
+
"Confident pleasant expressions",
|
|
59
|
+
"Professional posture and positioning",
|
|
60
|
+
"Formal but friendly body language",
|
|
61
|
+
],
|
|
62
|
+
neutral: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Interaction style forbidden - what NOT to do for each style */
|
|
66
|
+
const INTERACTION_FORBIDDEN: Record<InteractionStyle, readonly string[]> = {
|
|
67
|
+
romantic: [
|
|
68
|
+
"Do NOT position people far apart or distant from each other",
|
|
69
|
+
"Do NOT use cold, serious, stern, or angry expressions",
|
|
70
|
+
"Do NOT create stiff, awkward, or unnatural poses",
|
|
71
|
+
"Do NOT have people looking away from each other coldly",
|
|
72
|
+
"Do NOT show disconnection or emotional distance between people",
|
|
73
|
+
"Do NOT make poses look forced or uncomfortable",
|
|
74
|
+
],
|
|
75
|
+
friendly: [
|
|
76
|
+
"Do NOT use cold or unfriendly expressions",
|
|
77
|
+
"Do NOT create awkward distancing",
|
|
78
|
+
"Do NOT make poses stiff or formal",
|
|
79
|
+
],
|
|
80
|
+
professional: [
|
|
81
|
+
"Do NOT use overly casual or intimate positioning",
|
|
82
|
+
"Do NOT use sloppy or unprofessional poses",
|
|
83
|
+
],
|
|
84
|
+
neutral: [],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Builder Functions
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get interaction rules for a given style
|
|
93
|
+
*/
|
|
94
|
+
export function getInteractionRules(style: InteractionStyle): readonly string[] {
|
|
95
|
+
return INTERACTION_RULES[style];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get forbidden actions for a given style
|
|
100
|
+
*/
|
|
101
|
+
export function getInteractionForbidden(style: InteractionStyle): readonly string[] {
|
|
102
|
+
return INTERACTION_FORBIDDEN[style];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build interaction style prompt section
|
|
107
|
+
* Can be appended to any prompt (face preservation, scenario, etc.)
|
|
108
|
+
*/
|
|
109
|
+
export function buildInteractionStylePrompt(
|
|
110
|
+
options: InteractionStyleOptions,
|
|
111
|
+
): string {
|
|
112
|
+
const { style, personCount, customRules, customForbidden } = options;
|
|
113
|
+
|
|
114
|
+
// No rules for neutral or single person
|
|
115
|
+
if (style === "neutral" || personCount < 2) {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const rules = [...INTERACTION_RULES[style], ...(customRules ?? [])];
|
|
120
|
+
const forbidden = [...INTERACTION_FORBIDDEN[style], ...(customForbidden ?? [])];
|
|
121
|
+
|
|
122
|
+
if (rules.length === 0 && forbidden.length === 0) {
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const sections: string[] = [];
|
|
127
|
+
|
|
128
|
+
sections.push(`INTERACTION STYLE: ${style.toUpperCase()}`);
|
|
129
|
+
|
|
130
|
+
if (rules.length > 0) {
|
|
131
|
+
sections.push(`POSITIONING RULES:\n${rules.map((r) => `- ${r}`).join("\n")}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (forbidden.length > 0) {
|
|
135
|
+
sections.push(
|
|
136
|
+
`POSITIONING FORBIDDEN:\n${forbidden.map((f) => `- ${f}`).join("\n")}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return sections.join("\n\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build a minimal interaction style prompt (for API character limits)
|
|
145
|
+
*/
|
|
146
|
+
export function buildMinimalInteractionStylePrompt(
|
|
147
|
+
options: InteractionStyleOptions,
|
|
148
|
+
): string {
|
|
149
|
+
const { style, personCount } = options;
|
|
150
|
+
|
|
151
|
+
if (style === "neutral" || personCount < 2) {
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const styleDescriptions: Record<InteractionStyle, string> = {
|
|
156
|
+
romantic: "close, touching, warm smiles, loving connection",
|
|
157
|
+
friendly: "casual proximity, friendly smiles, relaxed poses",
|
|
158
|
+
professional: "appropriate distance, confident expressions",
|
|
159
|
+
neutral: "",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return `STYLE: ${style} - ${styleDescriptions[style]}`;
|
|
163
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Orchestrator
|
|
3
|
-
* Feature-agnostic hook for AI generation with
|
|
4
|
-
* -
|
|
3
|
+
* Feature-agnostic hook for AI generation with:
|
|
4
|
+
* - Network check (via design system's useOfflineStore)
|
|
5
5
|
* - Content moderation (optional)
|
|
6
|
-
* - Credit management
|
|
7
|
-
* - Error handling
|
|
8
|
-
* - Alert display
|
|
6
|
+
* - Credit management (via subscription package)
|
|
7
|
+
* - Error handling & alerts
|
|
9
8
|
* - Progress tracking
|
|
10
|
-
* - Lifecycle management
|
|
9
|
+
* - Lifecycle management
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Auth is handled by useFeatureGate before generation starts
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
@@ -45,7 +46,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
45
46
|
onCreditsExhausted,
|
|
46
47
|
onSuccess,
|
|
47
48
|
onError,
|
|
48
|
-
auth,
|
|
49
49
|
moderation,
|
|
50
50
|
credits,
|
|
51
51
|
lifecycle,
|
|
@@ -54,7 +54,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
54
54
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
55
55
|
console.log("[Orchestrator] 🎬 Hook initialized:", {
|
|
56
56
|
userId,
|
|
57
|
-
hasAuth: !!auth,
|
|
58
57
|
hasModeration: !!moderation,
|
|
59
58
|
hasCreditsCallbacks: !!credits,
|
|
60
59
|
hasLifecycle: !!lifecycle,
|
|
@@ -72,84 +71,48 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
72
71
|
const { showError, showSuccess } = useAlert();
|
|
73
72
|
const creditHook = useDeductCredit({ userId, onCreditsExhausted });
|
|
74
73
|
|
|
75
|
-
// Wrap credit hook to match expected interface
|
|
76
74
|
const defaultCredits = {
|
|
77
75
|
checkCredits: creditHook.checkCredits,
|
|
78
76
|
deductCredit: async (amount: number): Promise<boolean> => {
|
|
79
77
|
return creditHook.deductCredit(amount);
|
|
80
|
-
}
|
|
78
|
+
},
|
|
81
79
|
};
|
|
82
80
|
|
|
83
|
-
// Use provided credit callbacks or default to useDeductCredit hook
|
|
84
81
|
const checkCredits = credits?.checkCredits ?? defaultCredits.checkCredits;
|
|
85
82
|
const deductCredit = credits?.deductCredits ?? defaultCredits.deductCredit;
|
|
86
83
|
const handleCreditsExhausted = credits?.onCreditsExhausted ?? onCreditsExhausted;
|
|
87
84
|
|
|
88
|
-
// Cleanup on unmount
|
|
89
85
|
useEffect(() => {
|
|
90
86
|
isMountedRef.current = true;
|
|
91
87
|
return () => {
|
|
92
88
|
isMountedRef.current = false;
|
|
93
|
-
if (completeTimeoutRef.current)
|
|
94
|
-
|
|
95
|
-
completeTimeoutRef.current = null;
|
|
96
|
-
}
|
|
97
|
-
if (resetTimeoutRef.current) {
|
|
98
|
-
clearTimeout(resetTimeoutRef.current);
|
|
99
|
-
resetTimeoutRef.current = null;
|
|
100
|
-
}
|
|
89
|
+
if (completeTimeoutRef.current) clearTimeout(completeTimeoutRef.current);
|
|
90
|
+
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
|
101
91
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
console.log("[Orchestrator] 🧹 Cleanup
|
|
92
|
+
console.log("[Orchestrator] 🧹 Cleanup");
|
|
103
93
|
}
|
|
104
94
|
};
|
|
105
95
|
}, []);
|
|
106
96
|
|
|
107
|
-
// Handle lifecycle completion
|
|
108
97
|
const handleLifecycleComplete = useCallback(
|
|
109
98
|
(status: "success" | "error", result?: TResult, error?: GenerationError) => {
|
|
110
99
|
if (!lifecycle?.onComplete) return;
|
|
111
100
|
|
|
112
101
|
const delay = lifecycle.completeDelay ?? DEFAULT_COMPLETE_DELAY;
|
|
113
102
|
|
|
114
|
-
if (
|
|
115
|
-
console.log("[Orchestrator] ⏱️ Scheduling lifecycle.onComplete:", { status, delay });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Clear any existing timeout
|
|
119
|
-
if (completeTimeoutRef.current) {
|
|
120
|
-
clearTimeout(completeTimeoutRef.current);
|
|
121
|
-
}
|
|
103
|
+
if (completeTimeoutRef.current) clearTimeout(completeTimeoutRef.current);
|
|
122
104
|
|
|
123
105
|
completeTimeoutRef.current = setTimeout(() => {
|
|
124
|
-
if (!isMountedRef.current)
|
|
125
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
126
|
-
console.log("[Orchestrator] ⚠️ Component unmounted, skipping onComplete");
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
132
|
-
console.log("[Orchestrator] 📍 Calling lifecycle.onComplete:", status);
|
|
133
|
-
}
|
|
106
|
+
if (!isMountedRef.current) return;
|
|
134
107
|
|
|
135
108
|
lifecycle.onComplete?.(status, result, error);
|
|
136
109
|
|
|
137
|
-
// Auto-reset if configured
|
|
138
110
|
if (lifecycle.autoReset) {
|
|
139
111
|
const resetDelay = lifecycle.resetDelay ?? DEFAULT_RESET_DELAY;
|
|
140
|
-
if (
|
|
141
|
-
console.log("[Orchestrator] 🔄 Scheduling auto-reset in:", resetDelay);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (resetTimeoutRef.current) {
|
|
145
|
-
clearTimeout(resetTimeoutRef.current);
|
|
146
|
-
}
|
|
112
|
+
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
|
147
113
|
|
|
148
114
|
resetTimeoutRef.current = setTimeout(() => {
|
|
149
115
|
if (isMountedRef.current) {
|
|
150
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
151
|
-
console.log("[Orchestrator] 🔄 Auto-reset triggered");
|
|
152
|
-
}
|
|
153
116
|
setState(INITIAL_STATE);
|
|
154
117
|
isGeneratingRef.current = false;
|
|
155
118
|
}
|
|
@@ -160,74 +123,41 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
160
123
|
[lifecycle],
|
|
161
124
|
);
|
|
162
125
|
|
|
163
|
-
// Core execution logic (after all checks pass)
|
|
164
126
|
const executeGeneration = useCallback(
|
|
165
127
|
async (input: TInput) => {
|
|
166
128
|
const creditCost = strategy.getCreditCost();
|
|
167
129
|
|
|
168
130
|
setState((prev) => ({ ...prev, status: "generating", progress: 10 }));
|
|
169
131
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
170
|
-
console.log("[Orchestrator] 🎨 Starting
|
|
132
|
+
console.log("[Orchestrator] 🎨 Starting generation");
|
|
171
133
|
}
|
|
172
134
|
|
|
173
135
|
const result = await strategy.execute(input, (progress) => {
|
|
174
|
-
if (
|
|
175
|
-
console.log("[Orchestrator] 📊 Progress update:", progress);
|
|
176
|
-
}
|
|
177
|
-
if (isMountedRef.current) {
|
|
178
|
-
setState((prev) => ({ ...prev, progress }));
|
|
179
|
-
}
|
|
136
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, progress }));
|
|
180
137
|
});
|
|
181
138
|
|
|
182
|
-
if (
|
|
183
|
-
console.log("[Orchestrator] ✅ strategy.execute() completed");
|
|
184
|
-
}
|
|
139
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, progress: 70 }));
|
|
185
140
|
|
|
186
|
-
if (isMountedRef.current) {
|
|
187
|
-
setState((prev) => ({ ...prev, progress: 70 }));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Save result
|
|
191
141
|
if (strategy.save && userId) {
|
|
192
|
-
if (isMountedRef.current) {
|
|
193
|
-
setState((prev) => ({ ...prev, status: "saving" }));
|
|
194
|
-
}
|
|
195
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
196
|
-
console.log("[Orchestrator] 💾 Saving result...");
|
|
197
|
-
}
|
|
142
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, status: "saving" }));
|
|
198
143
|
try {
|
|
199
144
|
await strategy.save(result, userId);
|
|
200
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
201
|
-
console.log("[Orchestrator] ✅ Save completed");
|
|
202
|
-
}
|
|
203
145
|
} catch (saveErr) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
146
|
+
throw createGenerationError(
|
|
147
|
+
"save",
|
|
148
|
+
"Failed to save",
|
|
149
|
+
saveErr instanceof Error ? saveErr : undefined,
|
|
150
|
+
);
|
|
208
151
|
}
|
|
209
152
|
}
|
|
210
153
|
|
|
211
|
-
if (isMountedRef.current) {
|
|
212
|
-
setState((prev) => ({ ...prev, progress: 90 }));
|
|
213
|
-
}
|
|
154
|
+
if (isMountedRef.current) setState((prev) => ({ ...prev, progress: 90 }));
|
|
214
155
|
|
|
215
|
-
// Deduct credit
|
|
216
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
217
|
-
console.log("[Orchestrator] 💳 Deducting credit:", creditCost);
|
|
218
|
-
}
|
|
219
156
|
const creditDeducted = await deductCredit(creditCost);
|
|
220
157
|
if (!creditDeducted) {
|
|
221
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
222
|
-
console.log("[Orchestrator] ❌ Credit deduction failed");
|
|
223
|
-
}
|
|
224
158
|
throw createGenerationError("credits", "Failed to deduct credits");
|
|
225
159
|
}
|
|
226
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
227
|
-
console.log("[Orchestrator] ✅ Credit deducted successfully");
|
|
228
|
-
}
|
|
229
160
|
|
|
230
|
-
// Success
|
|
231
161
|
if (isMountedRef.current) {
|
|
232
162
|
setState({ status: "success", isGenerating: false, progress: 100, result, error: null });
|
|
233
163
|
}
|
|
@@ -236,14 +166,8 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
236
166
|
console.log("[Orchestrator] 🎉 Generation SUCCESS");
|
|
237
167
|
}
|
|
238
168
|
|
|
239
|
-
if (alertMessages.success)
|
|
240
|
-
void showSuccess("Success", alertMessages.success);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Call onSuccess callback
|
|
169
|
+
if (alertMessages.success) void showSuccess("Success", alertMessages.success);
|
|
244
170
|
onSuccess?.(result);
|
|
245
|
-
|
|
246
|
-
// Handle lifecycle completion
|
|
247
171
|
handleLifecycleComplete("success", result);
|
|
248
172
|
|
|
249
173
|
return result;
|
|
@@ -255,105 +179,41 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
255
179
|
async (input: TInput) => {
|
|
256
180
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
257
181
|
console.log("[Orchestrator] 🚀 generate() called");
|
|
258
|
-
console.log("[Orchestrator] Input:", JSON.stringify(input, null, 2).slice(0, 500));
|
|
259
182
|
}
|
|
260
183
|
|
|
261
|
-
if (isGeneratingRef.current)
|
|
262
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
263
|
-
console.log("[Orchestrator] ⚠️ Already generating, skipping");
|
|
264
|
-
}
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
184
|
+
if (isGeneratingRef.current) return;
|
|
267
185
|
|
|
268
186
|
isGeneratingRef.current = true;
|
|
269
187
|
pendingInputRef.current = input;
|
|
270
188
|
setState({ ...INITIAL_STATE, status: "checking", isGenerating: true });
|
|
271
189
|
|
|
272
190
|
try {
|
|
273
|
-
// 1. Auth check (optional)
|
|
274
|
-
if (auth) {
|
|
275
|
-
setState((prev) => ({ ...prev, status: "authenticating" }));
|
|
276
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
277
|
-
console.log("[Orchestrator] 🔐 Checking authentication...");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (!auth.isAuthenticated()) {
|
|
281
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
282
|
-
console.log("[Orchestrator] ❌ Not authenticated");
|
|
283
|
-
}
|
|
284
|
-
isGeneratingRef.current = false;
|
|
285
|
-
setState(INITIAL_STATE);
|
|
286
|
-
auth.onAuthRequired?.();
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
290
|
-
console.log("[Orchestrator] ✅ Authentication passed");
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// 2. Network check
|
|
295
191
|
if (!offlineStore.isOnline) {
|
|
296
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
297
|
-
console.log("[Orchestrator] ❌ Network check failed - offline");
|
|
298
|
-
}
|
|
299
192
|
throw createGenerationError("network", "No internet connection");
|
|
300
193
|
}
|
|
301
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
302
|
-
console.log("[Orchestrator] ✅ Network check passed");
|
|
303
|
-
}
|
|
304
194
|
|
|
305
|
-
// 3. Credit check
|
|
306
195
|
const creditCost = strategy.getCreditCost();
|
|
307
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
308
|
-
console.log("[Orchestrator] 💳 Credit cost:", creditCost);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
196
|
const hasCredits = await checkCredits(creditCost);
|
|
312
197
|
if (!hasCredits) {
|
|
313
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
314
|
-
console.log("[Orchestrator] ❌ No credits, opening paywall");
|
|
315
|
-
}
|
|
316
198
|
isGeneratingRef.current = false;
|
|
317
199
|
setState(INITIAL_STATE);
|
|
318
200
|
handleCreditsExhausted?.();
|
|
319
201
|
return;
|
|
320
202
|
}
|
|
321
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
322
|
-
console.log("[Orchestrator] ✅ Credit check passed");
|
|
323
|
-
}
|
|
324
203
|
|
|
325
|
-
// 4. Content moderation (optional)
|
|
326
204
|
if (moderation) {
|
|
327
205
|
setState((prev) => ({ ...prev, status: "moderating" }));
|
|
328
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
329
|
-
console.log("[Orchestrator] 🛡️ Checking content moderation...");
|
|
330
|
-
}
|
|
331
|
-
|
|
332
206
|
const moderationResult = await moderation.checkContent(input);
|
|
333
|
-
if (!moderationResult.allowed && moderationResult.warnings.length > 0) {
|
|
334
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
335
|
-
console.log("[Orchestrator] ⚠️ Moderation warnings:", moderationResult.warnings);
|
|
336
|
-
}
|
|
337
207
|
|
|
208
|
+
if (!moderationResult.allowed && moderationResult.warnings.length > 0) {
|
|
338
209
|
if (moderation.onShowWarning) {
|
|
339
|
-
// Show warning and let user decide
|
|
340
210
|
moderation.onShowWarning(
|
|
341
211
|
moderationResult.warnings,
|
|
342
212
|
() => {
|
|
343
|
-
// User cancelled
|
|
344
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
345
|
-
console.log("[Orchestrator] User cancelled after moderation warning");
|
|
346
|
-
}
|
|
347
213
|
isGeneratingRef.current = false;
|
|
348
|
-
if (isMountedRef.current)
|
|
349
|
-
setState(INITIAL_STATE);
|
|
350
|
-
}
|
|
214
|
+
if (isMountedRef.current) setState(INITIAL_STATE);
|
|
351
215
|
},
|
|
352
216
|
async () => {
|
|
353
|
-
// User continued - execute generation
|
|
354
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
355
|
-
console.log("[Orchestrator] User continued after moderation warning");
|
|
356
|
-
}
|
|
357
217
|
try {
|
|
358
218
|
await executeGeneration(input);
|
|
359
219
|
} catch (err) {
|
|
@@ -369,41 +229,30 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
369
229
|
}
|
|
370
230
|
},
|
|
371
231
|
);
|
|
372
|
-
return;
|
|
232
|
+
return;
|
|
373
233
|
}
|
|
374
|
-
// No warning handler - block the request
|
|
375
234
|
throw createGenerationError("policy", "Content policy violation");
|
|
376
235
|
}
|
|
377
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
378
|
-
console.log("[Orchestrator] ✅ Moderation passed");
|
|
379
|
-
}
|
|
380
236
|
}
|
|
381
237
|
|
|
382
|
-
// 5. Execute generation
|
|
383
238
|
return await executeGeneration(input);
|
|
384
239
|
} catch (err) {
|
|
385
240
|
const error = parseError(err);
|
|
386
241
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
387
|
-
console.log("[Orchestrator] ❌
|
|
242
|
+
console.log("[Orchestrator] ❌ Error:", error);
|
|
388
243
|
}
|
|
389
244
|
if (isMountedRef.current) {
|
|
390
245
|
setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
|
|
391
246
|
}
|
|
392
247
|
void showError("Error", getAlertMessage(error, alertMessages));
|
|
393
248
|
onError?.(error);
|
|
394
|
-
|
|
395
|
-
// Handle lifecycle completion for errors
|
|
396
249
|
handleLifecycleComplete("error", undefined, error);
|
|
397
|
-
throw error;
|
|
250
|
+
throw error;
|
|
398
251
|
} finally {
|
|
399
252
|
isGeneratingRef.current = false;
|
|
400
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
401
|
-
console.log("[Orchestrator] 🏁 generate() finished");
|
|
402
|
-
}
|
|
403
253
|
}
|
|
404
254
|
},
|
|
405
255
|
[
|
|
406
|
-
auth,
|
|
407
256
|
moderation,
|
|
408
257
|
strategy,
|
|
409
258
|
alertMessages,
|
|
@@ -418,9 +267,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
418
267
|
);
|
|
419
268
|
|
|
420
269
|
const reset = useCallback(() => {
|
|
421
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
422
|
-
console.log("[Orchestrator] 🔄 reset() called");
|
|
423
|
-
}
|
|
424
270
|
setState(INITIAL_STATE);
|
|
425
271
|
isGeneratingRef.current = false;
|
|
426
272
|
}, []);
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
export type OrchestratorStatus =
|
|
7
7
|
| "idle"
|
|
8
8
|
| "checking"
|
|
9
|
-
| "authenticating"
|
|
10
9
|
| "moderating"
|
|
11
10
|
| "generating"
|
|
12
11
|
| "saving"
|
|
@@ -14,14 +13,8 @@ export type OrchestratorStatus =
|
|
|
14
13
|
| "error";
|
|
15
14
|
|
|
16
15
|
export interface GenerationStrategy<TInput, TResult> {
|
|
17
|
-
|
|
18
|
-
execute: (
|
|
19
|
-
input: TInput,
|
|
20
|
-
onProgress?: (progress: number) => void,
|
|
21
|
-
) => Promise<TResult>;
|
|
22
|
-
/** Credit cost for this generation */
|
|
16
|
+
execute: (input: TInput, onProgress?: (progress: number) => void) => Promise<TResult>;
|
|
23
17
|
getCreditCost: () => number;
|
|
24
|
-
/** Optional: Save result to storage */
|
|
25
18
|
save?: (result: TResult, userId: string) => Promise<void>;
|
|
26
19
|
}
|
|
27
20
|
|
|
@@ -32,61 +25,28 @@ export interface AlertMessages {
|
|
|
32
25
|
creditFailed: string;
|
|
33
26
|
unknown: string;
|
|
34
27
|
success?: string;
|
|
35
|
-
authRequired?: string;
|
|
36
28
|
}
|
|
37
29
|
|
|
38
|
-
/** Content moderation result */
|
|
39
30
|
export interface ModerationResult {
|
|
40
31
|
allowed: boolean;
|
|
41
32
|
warnings: string[];
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
/** Auth callbacks for features that need authentication */
|
|
45
|
-
export interface AuthCallbacks {
|
|
46
|
-
/** Check if user is authenticated */
|
|
47
|
-
isAuthenticated: () => boolean;
|
|
48
|
-
/** Called when auth is required */
|
|
49
|
-
onAuthRequired?: () => void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Moderation callbacks for content filtering */
|
|
53
35
|
export interface ModerationCallbacks {
|
|
54
|
-
/** Check content for policy violations */
|
|
55
36
|
checkContent: (input: unknown) => Promise<ModerationResult>;
|
|
56
|
-
|
|
57
|
-
onShowWarning?: (
|
|
58
|
-
warnings: string[],
|
|
59
|
-
onCancel: () => void,
|
|
60
|
-
onContinue: () => void,
|
|
61
|
-
) => void;
|
|
37
|
+
onShowWarning?: (warnings: string[], onCancel: () => void, onContinue: () => void) => void;
|
|
62
38
|
}
|
|
63
39
|
|
|
64
|
-
/** Credit callbacks for features that manage credits via callbacks */
|
|
65
40
|
export interface CreditCallbacks {
|
|
66
|
-
/** Check if user can afford the cost */
|
|
67
41
|
checkCredits: (cost: number) => Promise<boolean>;
|
|
68
|
-
/** Deduct credits after successful generation - returns true if successful */
|
|
69
42
|
deductCredits: (cost: number) => Promise<boolean>;
|
|
70
|
-
/** Called when credits are exhausted */
|
|
71
43
|
onCreditsExhausted?: () => void;
|
|
72
44
|
}
|
|
73
45
|
|
|
74
|
-
/**
|
|
75
|
-
* Lifecycle configuration for generation flow
|
|
76
|
-
* Centralizes post-generation behavior (navigation, cleanup, etc.)
|
|
77
|
-
*/
|
|
78
46
|
export interface LifecycleConfig {
|
|
79
|
-
|
|
80
|
-
onComplete?: (
|
|
81
|
-
status: "success" | "error",
|
|
82
|
-
result?: unknown,
|
|
83
|
-
error?: GenerationError,
|
|
84
|
-
) => void;
|
|
85
|
-
/** Delay before calling onComplete (ms) - allows UI to show success state */
|
|
47
|
+
onComplete?: (status: "success" | "error", result?: unknown, error?: GenerationError) => void;
|
|
86
48
|
completeDelay?: number;
|
|
87
|
-
/** Auto-reset state after completion */
|
|
88
49
|
autoReset?: boolean;
|
|
89
|
-
/** Delay before auto-reset (ms) */
|
|
90
50
|
resetDelay?: number;
|
|
91
51
|
}
|
|
92
52
|
|
|
@@ -96,13 +56,8 @@ export interface GenerationConfig {
|
|
|
96
56
|
onCreditsExhausted?: () => void;
|
|
97
57
|
onSuccess?: (result: unknown) => void;
|
|
98
58
|
onError?: (error: GenerationError) => void;
|
|
99
|
-
/** Optional auth callbacks - if not provided, auth is skipped */
|
|
100
|
-
auth?: AuthCallbacks;
|
|
101
|
-
/** Optional moderation callbacks - if not provided, moderation is skipped */
|
|
102
59
|
moderation?: ModerationCallbacks;
|
|
103
|
-
/** Optional credit callbacks - if provided, overrides default useDeductCredit */
|
|
104
60
|
credits?: CreditCallbacks;
|
|
105
|
-
/** Lifecycle configuration for post-generation behavior */
|
|
106
61
|
lifecycle?: LifecycleConfig;
|
|
107
62
|
}
|
|
108
63
|
|
|
@@ -120,12 +75,7 @@ export interface GenerationError {
|
|
|
120
75
|
originalError?: Error;
|
|
121
76
|
}
|
|
122
77
|
|
|
123
|
-
export type GenerationErrorType =
|
|
124
|
-
| "network"
|
|
125
|
-
| "credits"
|
|
126
|
-
| "policy"
|
|
127
|
-
| "save"
|
|
128
|
-
| "unknown";
|
|
78
|
+
export type GenerationErrorType = "network" | "credits" | "policy" | "save" | "unknown";
|
|
129
79
|
|
|
130
80
|
export interface UseGenerationOrchestratorReturn<TInput, TResult> {
|
|
131
81
|
generate: (input: TInput) => Promise<TResult | void>;
|
|
@@ -137,22 +87,15 @@ export interface UseGenerationOrchestratorReturn<TInput, TResult> {
|
|
|
137
87
|
error: GenerationError | null;
|
|
138
88
|
}
|
|
139
89
|
|
|
140
|
-
/**
|
|
141
|
-
* Generation error UI configuration
|
|
142
|
-
*/
|
|
143
90
|
export interface GenerationErrorConfig {
|
|
144
91
|
readonly showCreditInfo?: boolean;
|
|
145
92
|
readonly iconName?: string;
|
|
146
93
|
readonly iconSize?: number;
|
|
147
94
|
}
|
|
148
95
|
|
|
149
|
-
/**
|
|
150
|
-
* Generation error UI translations
|
|
151
|
-
*/
|
|
152
96
|
export interface GenerationErrorTranslations {
|
|
153
97
|
readonly title: string;
|
|
154
98
|
readonly tryAgain: string;
|
|
155
99
|
readonly chooseAnother: string;
|
|
156
100
|
readonly noCreditCharged: string;
|
|
157
101
|
}
|
|
158
|
-
|