@umituz/react-native-ai-generation-content 1.17.16 → 1.17.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +1 -4
  2. package/src/domains/creations/presentation/components/CreationDetail/DetailImage.tsx +14 -14
  3. package/src/domains/creations/presentation/components/CreationDetail/DetailVideo.tsx +51 -58
  4. package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +36 -29
  5. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +98 -69
  6. package/src/features/text-to-image/index.ts +92 -4
  7. package/src/features/text-to-image/presentation/components/AspectRatioSelector.tsx +98 -0
  8. package/src/features/text-to-image/presentation/components/ExamplePrompts.tsx +88 -0
  9. package/src/features/text-to-image/presentation/components/ImageSizeSelector.tsx +98 -0
  10. package/src/features/text-to-image/presentation/components/NumImagesSelector.tsx +93 -0
  11. package/src/features/text-to-image/presentation/components/OutputFormatSelector.tsx +98 -0
  12. package/src/features/text-to-image/presentation/components/SettingsSheet.tsx +139 -0
  13. package/src/features/text-to-image/presentation/components/StyleSelector.tsx +110 -0
  14. package/src/features/text-to-image/presentation/components/TextToImageGenerateButton.tsx +84 -0
  15. package/src/features/text-to-image/presentation/components/TextToImagePromptInput.tsx +90 -0
  16. package/src/features/text-to-image/presentation/components/index.ts +44 -0
  17. package/src/features/text-to-image/presentation/hooks/index.ts +25 -0
  18. package/src/features/text-to-image/presentation/hooks/useFormState.ts +103 -0
  19. package/src/features/text-to-image/presentation/hooks/useGeneration.ts +98 -0
  20. package/src/features/text-to-image/presentation/hooks/useTextToImageForm.ts +58 -0
  21. package/src/features/text-to-image/presentation/index.ts +6 -0
  22. package/src/features/text-to-voice/domain/types/component.types.ts +91 -0
  23. package/src/features/text-to-voice/domain/types/config.types.ts +34 -0
  24. package/src/features/text-to-voice/domain/types/form.types.ts +39 -0
  25. package/src/features/text-to-voice/domain/types/generation.types.ts +43 -0
  26. package/src/features/text-to-voice/domain/types/index.ts +31 -3
  27. package/src/features/text-to-voice/infrastructure/services/text-to-voice-executor.ts +2 -10
  28. package/src/features/text-to-voice/domain/types/text-to-voice.types.ts +0 -65
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Settings Sheet Component
3
+ * Modal sheet for advanced image generation settings
4
+ */
5
+
6
+ import React from "react";
7
+ import {
8
+ View,
9
+ Modal,
10
+ Pressable,
11
+ StyleSheet,
12
+ ScrollView,
13
+ type GestureResponderEvent,
14
+ } from "react-native";
15
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
16
+ import {
17
+ AtomicText,
18
+ AtomicButton,
19
+ useAppDesignTokens,
20
+ } from "@umituz/react-native-design-system";
21
+
22
+ export interface SettingsSheetProps {
23
+ visible: boolean;
24
+ onClose: () => void;
25
+ title: string;
26
+ doneLabel: string;
27
+ children: React.ReactNode;
28
+ }
29
+
30
+ export const SettingsSheet: React.FC<SettingsSheetProps> = ({
31
+ visible,
32
+ onClose,
33
+ title,
34
+ doneLabel,
35
+ children,
36
+ }) => {
37
+ const tokens = useAppDesignTokens();
38
+ const insets = useSafeAreaInsets();
39
+
40
+ const handleBackdropPress = () => {
41
+ onClose();
42
+ };
43
+
44
+ const handleSheetPress = (e: GestureResponderEvent) => {
45
+ e.stopPropagation();
46
+ };
47
+
48
+ return (
49
+ <Modal
50
+ visible={visible}
51
+ transparent
52
+ animationType="slide"
53
+ onRequestClose={onClose}
54
+ >
55
+ <Pressable style={styles.backdrop} onPress={handleBackdropPress}>
56
+ <Pressable
57
+ style={[
58
+ styles.sheet,
59
+ {
60
+ backgroundColor: tokens.colors.surface,
61
+ paddingBottom: insets.bottom + 16,
62
+ },
63
+ ]}
64
+ onPress={handleSheetPress}
65
+ >
66
+ <View
67
+ style={[styles.handle, { backgroundColor: tokens.colors.border }]}
68
+ />
69
+
70
+ <View style={styles.header}>
71
+ <AtomicText
72
+ type="titleMedium"
73
+ style={[styles.title, { color: tokens.colors.textPrimary }]}
74
+ >
75
+ {title}
76
+ </AtomicText>
77
+ <AtomicButton
78
+ variant="secondary"
79
+ size="sm"
80
+ onPress={onClose}
81
+ style={styles.doneButton}
82
+ >
83
+ <AtomicText
84
+ type="bodyMedium"
85
+ style={[styles.doneText, { color: tokens.colors.primary }]}
86
+ >
87
+ {doneLabel}
88
+ </AtomicText>
89
+ </AtomicButton>
90
+ </View>
91
+
92
+ <ScrollView contentContainerStyle={styles.content}>
93
+ {children}
94
+ </ScrollView>
95
+ </Pressable>
96
+ </Pressable>
97
+ </Modal>
98
+ );
99
+ };
100
+
101
+ const styles = StyleSheet.create({
102
+ backdrop: {
103
+ flex: 1,
104
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
105
+ justifyContent: "flex-end",
106
+ },
107
+ sheet: {
108
+ borderTopLeftRadius: 16,
109
+ borderTopRightRadius: 16,
110
+ maxHeight: "80%",
111
+ },
112
+ handle: {
113
+ width: 40,
114
+ height: 4,
115
+ borderRadius: 2,
116
+ alignSelf: "center",
117
+ marginTop: 8,
118
+ marginBottom: 8,
119
+ },
120
+ header: {
121
+ flexDirection: "row",
122
+ justifyContent: "space-between",
123
+ alignItems: "center",
124
+ paddingHorizontal: 20,
125
+ paddingBottom: 16,
126
+ },
127
+ title: {
128
+ fontWeight: "600",
129
+ },
130
+ doneButton: {
131
+ paddingHorizontal: 0,
132
+ },
133
+ doneText: {
134
+ fontWeight: "600",
135
+ },
136
+ content: {
137
+ padding: 20,
138
+ },
139
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Style Selector Component
3
+ * Horizontal scrollable list of style options
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import type { StyleOption } from "../../domain/types/form.types";
13
+
14
+ export interface StyleSelectorProps {
15
+ options: StyleOption[];
16
+ value: string;
17
+ onChange: (styleId: string) => void;
18
+ label: string;
19
+ }
20
+
21
+ export const StyleSelector: React.FC<StyleSelectorProps> = ({
22
+ options,
23
+ value,
24
+ onChange,
25
+ label,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+
29
+ return (
30
+ <View style={styles.container}>
31
+ <AtomicText
32
+ type="bodyMedium"
33
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
34
+ >
35
+ {label}
36
+ </AtomicText>
37
+ <ScrollView
38
+ horizontal
39
+ showsHorizontalScrollIndicator={false}
40
+ contentContainerStyle={styles.scrollContent}
41
+ >
42
+ {options.map((style) => {
43
+ const isSelected = value === style.id;
44
+ return (
45
+ <TouchableOpacity
46
+ key={style.id}
47
+ style={[
48
+ styles.card,
49
+ {
50
+ backgroundColor: isSelected
51
+ ? tokens.colors.primary
52
+ : tokens.colors.surface,
53
+ borderColor: isSelected
54
+ ? tokens.colors.primary
55
+ : tokens.colors.borderLight,
56
+ },
57
+ ]}
58
+ onPress={() => onChange(style.id)}
59
+ activeOpacity={0.7}
60
+ >
61
+ <AtomicText
62
+ type="bodyMedium"
63
+ style={{
64
+ color: isSelected ? "#FFFFFF" : tokens.colors.textPrimary,
65
+ fontWeight: isSelected ? "600" : "400",
66
+ }}
67
+ >
68
+ {style.name}
69
+ </AtomicText>
70
+ {style.description && (
71
+ <AtomicText
72
+ type="labelSmall"
73
+ style={{
74
+ color: isSelected
75
+ ? "rgba(255,255,255,0.8)"
76
+ : tokens.colors.textSecondary,
77
+ marginTop: 4,
78
+ }}
79
+ numberOfLines={1}
80
+ >
81
+ {style.description}
82
+ </AtomicText>
83
+ )}
84
+ </TouchableOpacity>
85
+ );
86
+ })}
87
+ </ScrollView>
88
+ </View>
89
+ );
90
+ };
91
+
92
+ const styles = StyleSheet.create({
93
+ container: {
94
+ marginBottom: 24,
95
+ },
96
+ label: {
97
+ fontWeight: "600",
98
+ marginBottom: 12,
99
+ },
100
+ scrollContent: {
101
+ paddingRight: 16,
102
+ },
103
+ card: {
104
+ padding: 12,
105
+ borderRadius: 12,
106
+ borderWidth: 2,
107
+ marginRight: 12,
108
+ minWidth: 100,
109
+ },
110
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Text-to-Image Generate Button Component
3
+ * Button to trigger image generation with cost display
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicButton,
11
+ AtomicIcon,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ export interface TextToImageGenerateButtonProps {
15
+ onPress: () => void;
16
+ onSettingsPress?: () => void;
17
+ disabled: boolean;
18
+ label: string;
19
+ costLabel?: string;
20
+ showSettings?: boolean;
21
+ }
22
+
23
+ export const TextToImageGenerateButton: React.FC<TextToImageGenerateButtonProps> = ({
24
+ onPress,
25
+ onSettingsPress,
26
+ disabled,
27
+ label,
28
+ costLabel,
29
+ showSettings = true,
30
+ }) => {
31
+
32
+ return (
33
+ <View style={styles.container}>
34
+ <View style={styles.buttonRow}>
35
+ <AtomicButton
36
+ onPress={onPress}
37
+ disabled={disabled}
38
+ variant="primary"
39
+ size="md"
40
+ style={styles.generateButton}
41
+ >
42
+ <AtomicText type="bodyLarge" style={styles.buttonText}>
43
+ {label}
44
+ {costLabel && ` (${costLabel})`}
45
+ </AtomicText>
46
+ </AtomicButton>
47
+
48
+ {showSettings && onSettingsPress && (
49
+ <AtomicButton
50
+ onPress={onSettingsPress}
51
+ variant="secondary"
52
+ size="md"
53
+ style={styles.settingsButton}
54
+ >
55
+ <AtomicIcon name="settings-outline" size="md" color="primary" />
56
+ </AtomicButton>
57
+ )}
58
+ </View>
59
+ </View>
60
+ );
61
+ };
62
+
63
+ const styles = StyleSheet.create({
64
+ container: {
65
+ marginBottom: 24,
66
+ },
67
+ buttonRow: {
68
+ flexDirection: "row",
69
+ gap: 12,
70
+ },
71
+ generateButton: {
72
+ flex: 1,
73
+ paddingVertical: 16,
74
+ },
75
+ buttonText: {
76
+ color: "#FFFFFF",
77
+ fontWeight: "600",
78
+ },
79
+ settingsButton: {
80
+ width: 56,
81
+ justifyContent: "center",
82
+ alignItems: "center",
83
+ },
84
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Text-to-Image Prompt Input Component
3
+ * Text input for entering generation prompts
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TextInput, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+
13
+ export interface TextToImagePromptInputProps {
14
+ value: string;
15
+ onChangeText: (text: string) => void;
16
+ label: string;
17
+ placeholder: string;
18
+ characterCountLabel?: string;
19
+ minHeight?: number;
20
+ maxLength?: number;
21
+ }
22
+
23
+ export const TextToImagePromptInput: React.FC<TextToImagePromptInputProps> = ({
24
+ value,
25
+ onChangeText,
26
+ label,
27
+ placeholder,
28
+ characterCountLabel,
29
+ minHeight = 100,
30
+ maxLength,
31
+ }) => {
32
+ const tokens = useAppDesignTokens();
33
+
34
+ return (
35
+ <View style={styles.container}>
36
+ <AtomicText
37
+ type="bodyMedium"
38
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
39
+ >
40
+ {label}
41
+ </AtomicText>
42
+ <TextInput
43
+ style={[
44
+ styles.input,
45
+ {
46
+ backgroundColor: tokens.colors.surface,
47
+ color: tokens.colors.textPrimary,
48
+ borderColor: tokens.colors.borderLight,
49
+ minHeight,
50
+ },
51
+ ]}
52
+ placeholder={placeholder}
53
+ placeholderTextColor={tokens.colors.textSecondary}
54
+ value={value}
55
+ onChangeText={onChangeText}
56
+ multiline
57
+ numberOfLines={4}
58
+ textAlignVertical="top"
59
+ maxLength={maxLength}
60
+ />
61
+ {characterCountLabel && (
62
+ <AtomicText
63
+ type="labelSmall"
64
+ style={[styles.charCount, { color: tokens.colors.textSecondary }]}
65
+ >
66
+ {characterCountLabel}
67
+ </AtomicText>
68
+ )}
69
+ </View>
70
+ );
71
+ };
72
+
73
+ const styles = StyleSheet.create({
74
+ container: {
75
+ marginBottom: 24,
76
+ },
77
+ label: {
78
+ fontWeight: "600",
79
+ marginBottom: 12,
80
+ },
81
+ input: {
82
+ borderWidth: 1,
83
+ borderRadius: 12,
84
+ padding: 16,
85
+ fontSize: 16,
86
+ },
87
+ charCount: {
88
+ marginTop: 8,
89
+ },
90
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Text-to-Image Presentation Components
3
+ * All component exports for text-to-image feature
4
+ */
5
+
6
+ // Input Components
7
+ export { TextToImagePromptInput } from "./TextToImagePromptInput";
8
+ export type { TextToImagePromptInputProps } from "./TextToImagePromptInput";
9
+
10
+ export { ExamplePrompts as TextToImageExamplePrompts } from "./ExamplePrompts";
11
+ export type { ExamplePromptsProps as TextToImageExamplePromptsProps } from "./ExamplePrompts";
12
+
13
+ // Selector Components
14
+ export { NumImagesSelector as TextToImageNumImagesSelector } from "./NumImagesSelector";
15
+ export type { NumImagesSelectorProps as TextToImageNumImagesSelectorProps } from "./NumImagesSelector";
16
+
17
+ export { StyleSelector as TextToImageStyleSelector } from "./StyleSelector";
18
+ export type { StyleSelectorProps as TextToImageStyleSelectorProps } from "./StyleSelector";
19
+
20
+ export { AspectRatioSelector as TextToImageAspectRatioSelector } from "./AspectRatioSelector";
21
+ export type {
22
+ AspectRatioSelectorProps as TextToImageAspectRatioSelectorProps,
23
+ AspectRatioOption as TextToImageAspectRatioOption,
24
+ } from "./AspectRatioSelector";
25
+
26
+ export { ImageSizeSelector as TextToImageSizeSelector } from "./ImageSizeSelector";
27
+ export type {
28
+ ImageSizeSelectorProps as TextToImageSizeSelectorProps,
29
+ ImageSizeOption as TextToImageSizeOption,
30
+ } from "./ImageSizeSelector";
31
+
32
+ export { OutputFormatSelector as TextToImageOutputFormatSelector } from "./OutputFormatSelector";
33
+ export type {
34
+ OutputFormatSelectorProps as TextToImageOutputFormatSelectorProps,
35
+ OutputFormatOption as TextToImageOutputFormatOption,
36
+ } from "./OutputFormatSelector";
37
+
38
+ // Action Components
39
+ export { TextToImageGenerateButton } from "./TextToImageGenerateButton";
40
+ export type { TextToImageGenerateButtonProps } from "./TextToImageGenerateButton";
41
+
42
+ // Sheet Components
43
+ export { SettingsSheet as TextToImageSettingsSheet } from "./SettingsSheet";
44
+ export type { SettingsSheetProps as TextToImageSettingsSheetProps } from "./SettingsSheet";
@@ -1,3 +1,28 @@
1
+ /**
2
+ * Text-to-Image Presentation Hooks
3
+ * All hook exports for text-to-image feature
4
+ */
5
+
6
+ // Form State Hook
7
+ export { useFormState } from "./useFormState";
8
+ export type { UseFormStateOptions, UseFormStateReturn } from "./useFormState";
9
+
10
+ // Generation Hook
11
+ export { useGeneration } from "./useGeneration";
12
+ export type {
13
+ GenerationState,
14
+ UseGenerationOptions,
15
+ UseGenerationReturn,
16
+ } from "./useGeneration";
17
+
18
+ // Combined Form Hook
19
+ export { useTextToImageForm } from "./useTextToImageForm";
20
+ export type {
21
+ UseTextToImageFormOptions,
22
+ UseTextToImageFormReturn,
23
+ } from "./useTextToImageForm";
24
+
25
+ // Provider-based Feature Hook (existing)
1
26
  export { useTextToImageFeature } from "./useTextToImageFeature";
2
27
  export type {
3
28
  UseTextToImageFeatureProps,
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Text-to-Image Form State Hook
3
+ * Manages form state for text-to-image generation
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+ import type {
8
+ AspectRatio,
9
+ ImageSize,
10
+ NumImages,
11
+ OutputFormat,
12
+ TextToImageFormState,
13
+ TextToImageFormActions,
14
+ TextToImageFormDefaults,
15
+ } from "../../domain/types/form.types";
16
+ import { DEFAULT_FORM_VALUES } from "../../domain/constants/options.constants";
17
+
18
+ export interface UseFormStateOptions {
19
+ defaults?: TextToImageFormDefaults;
20
+ }
21
+
22
+ export interface UseFormStateReturn {
23
+ state: TextToImageFormState;
24
+ actions: TextToImageFormActions;
25
+ }
26
+
27
+ export function useFormState(options?: UseFormStateOptions): UseFormStateReturn {
28
+ const defaults = useMemo(
29
+ () => ({ ...DEFAULT_FORM_VALUES, ...options?.defaults }),
30
+ [options?.defaults]
31
+ );
32
+
33
+ const [prompt, setPrompt] = useState("");
34
+ const [aspectRatio, setAspectRatio] = useState<AspectRatio>(
35
+ defaults.aspectRatio ?? "9:16"
36
+ );
37
+ const [size, setSize] = useState<ImageSize>(defaults.size ?? "512x512");
38
+ const [numImages, setNumImages] = useState<NumImages>(defaults.numImages ?? 1);
39
+ const [negativePrompt, setNegativePrompt] = useState("");
40
+ const [guidanceScale, setGuidanceScale] = useState(defaults.guidanceScale ?? 7.5);
41
+ const [selectedModel, setSelectedModel] = useState<string | null>(null);
42
+ const [outputFormat, setOutputFormat] = useState<OutputFormat>(
43
+ defaults.outputFormat ?? "png"
44
+ );
45
+ const [selectedStyle, setSelectedStyle] = useState(
46
+ defaults.selectedStyle ?? "realistic"
47
+ );
48
+
49
+ const reset = useCallback(() => {
50
+ setPrompt("");
51
+ setAspectRatio(defaults.aspectRatio ?? "9:16");
52
+ setSize(defaults.size ?? "512x512");
53
+ setNumImages(defaults.numImages ?? 1);
54
+ setNegativePrompt("");
55
+ setGuidanceScale(defaults.guidanceScale ?? 7.5);
56
+ setSelectedModel(null);
57
+ setOutputFormat(defaults.outputFormat ?? "png");
58
+ setSelectedStyle(defaults.selectedStyle ?? "realistic");
59
+ }, [defaults]);
60
+
61
+ const state: TextToImageFormState = useMemo(
62
+ () => ({
63
+ prompt,
64
+ aspectRatio,
65
+ size,
66
+ numImages,
67
+ negativePrompt,
68
+ guidanceScale,
69
+ selectedModel,
70
+ outputFormat,
71
+ selectedStyle,
72
+ }),
73
+ [
74
+ prompt,
75
+ aspectRatio,
76
+ size,
77
+ numImages,
78
+ negativePrompt,
79
+ guidanceScale,
80
+ selectedModel,
81
+ outputFormat,
82
+ selectedStyle,
83
+ ]
84
+ );
85
+
86
+ const actions: TextToImageFormActions = useMemo(
87
+ () => ({
88
+ setPrompt,
89
+ setAspectRatio,
90
+ setSize,
91
+ setNumImages,
92
+ setNegativePrompt,
93
+ setGuidanceScale,
94
+ setSelectedModel,
95
+ setOutputFormat,
96
+ setSelectedStyle,
97
+ reset,
98
+ }),
99
+ [reset]
100
+ );
101
+
102
+ return { state, actions };
103
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Text-to-Image Generation Hook
3
+ * Orchestrates generation with app-provided callbacks
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import type {
8
+ TextToImageFormState,
9
+ TextToImageCallbacks,
10
+ GenerationResult,
11
+ GenerationRequest,
12
+ } from "../../domain/types";
13
+
14
+ export interface GenerationState {
15
+ isGenerating: boolean;
16
+ progress: number;
17
+ error: string | null;
18
+ }
19
+
20
+ export interface UseGenerationOptions {
21
+ formState: TextToImageFormState;
22
+ callbacks: TextToImageCallbacks;
23
+ onPromptCleared?: () => void;
24
+ }
25
+
26
+ export interface UseGenerationReturn {
27
+ generationState: GenerationState;
28
+ totalCost: number;
29
+ handleGenerate: () => Promise<GenerationResult | null>;
30
+ }
31
+
32
+ const initialState: GenerationState = {
33
+ isGenerating: false,
34
+ progress: 0,
35
+ error: null,
36
+ };
37
+
38
+ export function useGeneration(options: UseGenerationOptions): UseGenerationReturn {
39
+ const { formState, callbacks, onPromptCleared } = options;
40
+ const [generationState, setGenerationState] = useState<GenerationState>(initialState);
41
+
42
+ const totalCost = callbacks.calculateCost(formState.numImages, formState.selectedModel);
43
+
44
+ const handleGenerate = useCallback(async (): Promise<GenerationResult | null> => {
45
+ const trimmedPrompt = formState.prompt.trim();
46
+
47
+ if (!trimmedPrompt) {
48
+ setGenerationState((prev) => ({ ...prev, error: "Prompt is required" }));
49
+ return null;
50
+ }
51
+
52
+ if (!callbacks.isAuthenticated()) {
53
+ callbacks.onAuthRequired?.();
54
+ return null;
55
+ }
56
+
57
+ if (!callbacks.canAfford(totalCost)) {
58
+ callbacks.onCreditsRequired?.(totalCost);
59
+ return null;
60
+ }
61
+
62
+ setGenerationState({ isGenerating: true, progress: 0, error: null });
63
+
64
+ const request: GenerationRequest = {
65
+ prompt: trimmedPrompt,
66
+ model: formState.selectedModel ?? undefined,
67
+ aspectRatio: formState.aspectRatio,
68
+ size: formState.size,
69
+ negativePrompt: formState.negativePrompt.trim() || undefined,
70
+ guidanceScale: formState.guidanceScale,
71
+ numImages: formState.numImages,
72
+ style: formState.selectedStyle,
73
+ outputFormat: formState.outputFormat,
74
+ };
75
+
76
+ try {
77
+ const result = await callbacks.executeGeneration(request);
78
+
79
+ if (result.success === true) {
80
+ callbacks.onSuccess?.(result.imageUrls);
81
+ onPromptCleared?.();
82
+ setGenerationState({ isGenerating: false, progress: 100, error: null });
83
+ } else {
84
+ setGenerationState({ isGenerating: false, progress: 0, error: result.error });
85
+ callbacks.onError?.(result.error);
86
+ }
87
+
88
+ return result;
89
+ } catch (error) {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ setGenerationState({ isGenerating: false, progress: 0, error: message });
92
+ callbacks.onError?.(message);
93
+ return null;
94
+ }
95
+ }, [formState, callbacks, totalCost, onPromptCleared]);
96
+
97
+ return { generationState, totalCost, handleGenerate };
98
+ }