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

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 (33) hide show
  1. package/package.json +1 -1
  2. package/src/features/image-to-video/domain/constants/animation.constants.ts +47 -0
  3. package/src/features/image-to-video/domain/constants/duration.constants.ts +13 -0
  4. package/src/features/image-to-video/domain/constants/form.constants.ts +22 -0
  5. package/src/features/image-to-video/domain/constants/index.ts +23 -0
  6. package/src/features/image-to-video/domain/constants/music.constants.ts +53 -0
  7. package/src/features/image-to-video/domain/index.ts +4 -0
  8. package/src/features/image-to-video/domain/types/animation.types.ts +20 -0
  9. package/src/features/image-to-video/domain/types/config.types.ts +56 -0
  10. package/src/features/image-to-video/domain/types/duration.types.ts +11 -0
  11. package/src/features/image-to-video/domain/types/form.types.ts +35 -0
  12. package/src/features/image-to-video/domain/types/image-to-video.types.ts +18 -0
  13. package/src/features/image-to-video/domain/types/index.ts +25 -0
  14. package/src/features/image-to-video/domain/types/music.types.ts +21 -0
  15. package/src/features/text-to-image/domain/types/config.types.ts +9 -5
  16. package/src/features/text-to-image/domain/types/index.ts +4 -4
  17. package/src/features/text-to-image/index.ts +4 -4
  18. package/src/features/text-to-image/presentation/hooks/useGeneration.ts +5 -5
  19. package/src/features/text-to-voice/index.ts +34 -6
  20. package/src/features/text-to-voice/infrastructure/services/index.ts +0 -1
  21. package/src/features/text-to-voice/presentation/components/TextToVoiceAudioPlayer.tsx +81 -0
  22. package/src/features/text-to-voice/presentation/components/TextToVoiceErrorMessage.tsx +57 -0
  23. package/src/features/text-to-voice/presentation/components/TextToVoiceExamplePrompts.tsx +77 -0
  24. package/src/features/text-to-voice/presentation/components/TextToVoiceGenerateButton.tsx +87 -0
  25. package/src/features/text-to-voice/presentation/components/TextToVoiceHeader.tsx +73 -0
  26. package/src/features/text-to-voice/presentation/components/TextToVoiceOptionalInput.tsx +73 -0
  27. package/src/features/text-to-voice/presentation/components/TextToVoiceTextInput.tsx +85 -0
  28. package/src/features/text-to-voice/presentation/components/index.ts +7 -0
  29. package/src/features/text-to-voice/presentation/hooks/index.ts +5 -4
  30. package/src/features/text-to-voice/presentation/hooks/useTextToVoiceForm.ts +91 -0
  31. package/src/features/text-to-voice/presentation/hooks/useTextToVoiceGeneration.ts +116 -0
  32. package/src/features/text-to-voice/presentation/index.ts +1 -0
  33. package/src/features/text-to-voice/presentation/hooks/useTextToVoiceFeature.ts +0 -105
@@ -0,0 +1,57 @@
1
+ /**
2
+ * TextToVoiceErrorMessage Component
3
+ * Error message display
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { TextToVoiceErrorMessageProps } from "../../domain/types";
14
+
15
+ export const TextToVoiceErrorMessage: React.FC<TextToVoiceErrorMessageProps> = ({
16
+ message,
17
+ style,
18
+ }) => {
19
+ const tokens = useAppDesignTokens();
20
+
21
+ return (
22
+ <View
23
+ style={[
24
+ styles.errorCard,
25
+ {
26
+ backgroundColor: `${tokens.colors.error}20`,
27
+ borderColor: tokens.colors.error,
28
+ },
29
+ style,
30
+ ]}
31
+ >
32
+ <AtomicIcon name="alert-circle-outline" size="sm" color="error" />
33
+ <AtomicText
34
+ type="bodySmall"
35
+ style={[styles.errorText, { color: tokens.colors.error }]}
36
+ >
37
+ {message}
38
+ </AtomicText>
39
+ </View>
40
+ );
41
+ };
42
+
43
+ const styles = StyleSheet.create({
44
+ errorCard: {
45
+ flexDirection: "row",
46
+ alignItems: "center",
47
+ margin: 16,
48
+ marginTop: 0,
49
+ padding: 12,
50
+ borderRadius: 8,
51
+ borderWidth: 1,
52
+ },
53
+ errorText: {
54
+ marginLeft: 8,
55
+ flex: 1,
56
+ },
57
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * TextToVoiceExamplePrompts Component
3
+ * Horizontal scrolling example prompts
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
8
+ import { useLocalization } from "@umituz/react-native-localization";
9
+ import {
10
+ AtomicText,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { TextToVoiceExamplePromptsProps } from "../../domain/types";
14
+
15
+ export const TextToVoiceExamplePrompts: React.FC<
16
+ TextToVoiceExamplePromptsProps
17
+ > = ({ prompts, onSelectPrompt, labelKey, style }) => {
18
+ const { t } = useLocalization();
19
+ const tokens = useAppDesignTokens();
20
+
21
+ if (prompts.length === 0) {
22
+ return null;
23
+ }
24
+
25
+ return (
26
+ <View style={[styles.section, style]}>
27
+ <AtomicText
28
+ type="bodyMedium"
29
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
30
+ >
31
+ {t(labelKey)}
32
+ </AtomicText>
33
+ <ScrollView
34
+ horizontal
35
+ showsHorizontalScrollIndicator={false}
36
+ style={styles.scrollView}
37
+ >
38
+ {prompts.map((prompt, index) => (
39
+ <TouchableOpacity
40
+ key={index}
41
+ style={[styles.promptCard, { backgroundColor: tokens.colors.surface }]}
42
+ onPress={() => onSelectPrompt(prompt)}
43
+ >
44
+ <AtomicText
45
+ type="bodySmall"
46
+ style={{ color: tokens.colors.textPrimary }}
47
+ numberOfLines={2}
48
+ >
49
+ {prompt}
50
+ </AtomicText>
51
+ </TouchableOpacity>
52
+ ))}
53
+ </ScrollView>
54
+ </View>
55
+ );
56
+ };
57
+
58
+ const styles = StyleSheet.create({
59
+ section: {
60
+ marginBottom: 24,
61
+ },
62
+ label: {
63
+ fontWeight: "600",
64
+ marginBottom: 12,
65
+ },
66
+ scrollView: {
67
+ marginHorizontal: -16,
68
+ paddingHorizontal: 16,
69
+ },
70
+ promptCard: {
71
+ padding: 12,
72
+ borderRadius: 8,
73
+ marginRight: 12,
74
+ minWidth: 150,
75
+ maxWidth: 200,
76
+ },
77
+ });
@@ -0,0 +1,87 @@
1
+ /**
2
+ * TextToVoiceGenerateButton Component
3
+ * Generate button with loading state
4
+ */
5
+
6
+ import React from "react";
7
+ import { TouchableOpacity, ActivityIndicator, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import { useLocalization } from "@umituz/react-native-localization";
14
+ import type { TextToVoiceGenerateButtonProps } from "../../domain/types";
15
+
16
+ export const TextToVoiceGenerateButton: React.FC<
17
+ TextToVoiceGenerateButtonProps
18
+ > = ({
19
+ isGenerating,
20
+ progress,
21
+ disabled,
22
+ onPress,
23
+ buttonTextKey,
24
+ generatingTextKey,
25
+ style,
26
+ }) => {
27
+ const { t } = useLocalization();
28
+ const tokens = useAppDesignTokens();
29
+
30
+ return (
31
+ <TouchableOpacity
32
+ style={[
33
+ styles.button,
34
+ {
35
+ backgroundColor: tokens.colors.primary,
36
+ opacity: disabled ? 0.6 : 1,
37
+ },
38
+ style,
39
+ ]}
40
+ onPress={onPress}
41
+ disabled={disabled}
42
+ >
43
+ {isGenerating ? (
44
+ <>
45
+ <ActivityIndicator
46
+ color={tokens.colors.onPrimary}
47
+ style={styles.loader}
48
+ />
49
+ <AtomicText
50
+ type="bodyMedium"
51
+ style={[styles.buttonText, { color: tokens.colors.onPrimary }]}
52
+ >
53
+ {t(generatingTextKey)} {progress.toFixed(0)}%
54
+ </AtomicText>
55
+ </>
56
+ ) : (
57
+ <>
58
+ <AtomicIcon name="Volume2" size="md" color="onPrimary" />
59
+ <AtomicText
60
+ type="bodyMedium"
61
+ style={[styles.buttonText, { color: tokens.colors.onPrimary }]}
62
+ >
63
+ {t(buttonTextKey)}
64
+ </AtomicText>
65
+ </>
66
+ )}
67
+ </TouchableOpacity>
68
+ );
69
+ };
70
+
71
+ const styles = StyleSheet.create({
72
+ button: {
73
+ flexDirection: "row",
74
+ alignItems: "center",
75
+ justifyContent: "center",
76
+ marginHorizontal: 16,
77
+ padding: 16,
78
+ borderRadius: 12,
79
+ },
80
+ loader: {
81
+ marginRight: 8,
82
+ },
83
+ buttonText: {
84
+ fontWeight: "600",
85
+ marginLeft: 8,
86
+ },
87
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * TextToVoiceHeader Component
3
+ * Header with icon and description
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import { useLocalization } from "@umituz/react-native-localization";
14
+ import type { TextToVoiceHeaderProps } from "../../domain/types";
15
+
16
+ export const TextToVoiceHeader: React.FC<TextToVoiceHeaderProps> = ({
17
+ descriptionKey,
18
+ iconName,
19
+ headerContent,
20
+ style,
21
+ }) => {
22
+ const { t } = useLocalization();
23
+ const tokens = useAppDesignTokens();
24
+
25
+ return (
26
+ <View style={[styles.container, style]}>
27
+ {headerContent || (
28
+ <View style={styles.content}>
29
+ <View
30
+ style={[
31
+ styles.iconContainer,
32
+ { backgroundColor: `${tokens.colors.primary}20` },
33
+ ]}
34
+ >
35
+ <AtomicIcon
36
+ name={iconName as "Mic"}
37
+ size="lg"
38
+ color="primary"
39
+ />
40
+ </View>
41
+ <AtomicText
42
+ type="bodySmall"
43
+ style={[styles.description, { color: tokens.colors.textSecondary }]}
44
+ >
45
+ {t(descriptionKey)}
46
+ </AtomicText>
47
+ </View>
48
+ )}
49
+ </View>
50
+ );
51
+ };
52
+
53
+ const styles = StyleSheet.create({
54
+ container: {
55
+ alignItems: "center",
56
+ marginBottom: 16,
57
+ },
58
+ content: {
59
+ alignItems: "center",
60
+ },
61
+ iconContainer: {
62
+ width: 56,
63
+ height: 56,
64
+ borderRadius: 28,
65
+ alignItems: "center",
66
+ justifyContent: "center",
67
+ marginBottom: 12,
68
+ },
69
+ description: {
70
+ marginTop: 8,
71
+ textAlign: "center",
72
+ },
73
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * TextToVoiceOptionalInput Component
3
+ * Optional text input with hint
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
+ import type { TextToVoiceOptionalInputProps } from "../../domain/types";
13
+
14
+ export const TextToVoiceOptionalInput: React.FC<
15
+ TextToVoiceOptionalInputProps
16
+ > = ({ title, value, onChangeText, placeholder, hint, style }) => {
17
+ const tokens = useAppDesignTokens();
18
+
19
+ return (
20
+ <View
21
+ style={[styles.card, { backgroundColor: tokens.colors.surface }, style]}
22
+ >
23
+ <AtomicText
24
+ type="bodyMedium"
25
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
26
+ >
27
+ {title}
28
+ </AtomicText>
29
+ <TextInput
30
+ style={[
31
+ styles.textInput,
32
+ {
33
+ backgroundColor: tokens.colors.backgroundPrimary,
34
+ color: tokens.colors.textPrimary,
35
+ borderColor: tokens.colors.borderLight,
36
+ },
37
+ ]}
38
+ value={value}
39
+ onChangeText={onChangeText}
40
+ placeholder={placeholder}
41
+ placeholderTextColor={tokens.colors.textTertiary}
42
+ />
43
+ <AtomicText
44
+ type="bodySmall"
45
+ style={[styles.hint, { color: tokens.colors.textTertiary }]}
46
+ >
47
+ {hint}
48
+ </AtomicText>
49
+ </View>
50
+ );
51
+ };
52
+
53
+ const styles = StyleSheet.create({
54
+ card: {
55
+ margin: 16,
56
+ marginTop: 0,
57
+ padding: 16,
58
+ borderRadius: 12,
59
+ },
60
+ label: {
61
+ fontWeight: "600",
62
+ marginBottom: 8,
63
+ },
64
+ textInput: {
65
+ borderWidth: 1,
66
+ borderRadius: 8,
67
+ padding: 12,
68
+ minHeight: 44,
69
+ },
70
+ hint: {
71
+ marginTop: 4,
72
+ },
73
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * TextToVoiceTextInput Component
3
+ * Text input for voice generation with character count
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
+ import { useLocalization } from "@umituz/react-native-localization";
13
+ import type { TextToVoiceTextInputProps } from "../../domain/types";
14
+
15
+ export const TextToVoiceTextInput: React.FC<TextToVoiceTextInputProps> = ({
16
+ value,
17
+ onChangeText,
18
+ maxLength = 5000,
19
+ labelKey,
20
+ placeholderKey,
21
+ characterCountKey,
22
+ style,
23
+ }) => {
24
+ const { t } = useLocalization();
25
+ const tokens = useAppDesignTokens();
26
+
27
+ return (
28
+ <View
29
+ style={[styles.card, { backgroundColor: tokens.colors.surface }, style]}
30
+ >
31
+ <AtomicText
32
+ type="bodyMedium"
33
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
34
+ >
35
+ {t(labelKey)}
36
+ </AtomicText>
37
+ <TextInput
38
+ style={[
39
+ styles.textInput,
40
+ {
41
+ backgroundColor: tokens.colors.backgroundPrimary,
42
+ color: tokens.colors.textPrimary,
43
+ borderColor: tokens.colors.borderLight,
44
+ },
45
+ ]}
46
+ value={value}
47
+ onChangeText={onChangeText}
48
+ placeholder={t(placeholderKey)}
49
+ placeholderTextColor={tokens.colors.textTertiary}
50
+ multiline
51
+ numberOfLines={6}
52
+ maxLength={maxLength}
53
+ />
54
+ <AtomicText
55
+ type="bodySmall"
56
+ style={[styles.characterCount, { color: tokens.colors.textTertiary }]}
57
+ >
58
+ {t(characterCountKey, { count: value.length, max: maxLength })}
59
+ </AtomicText>
60
+ </View>
61
+ );
62
+ };
63
+
64
+ const styles = StyleSheet.create({
65
+ card: {
66
+ margin: 16,
67
+ marginTop: 0,
68
+ padding: 16,
69
+ borderRadius: 12,
70
+ },
71
+ label: {
72
+ fontWeight: "600",
73
+ marginBottom: 8,
74
+ },
75
+ textInput: {
76
+ borderWidth: 1,
77
+ borderRadius: 8,
78
+ padding: 12,
79
+ minHeight: 120,
80
+ textAlignVertical: "top",
81
+ },
82
+ characterCount: {
83
+ marginTop: 4,
84
+ },
85
+ });
@@ -0,0 +1,7 @@
1
+ export { TextToVoiceTextInput } from "./TextToVoiceTextInput";
2
+ export { TextToVoiceOptionalInput } from "./TextToVoiceOptionalInput";
3
+ export { TextToVoiceExamplePrompts } from "./TextToVoiceExamplePrompts";
4
+ export { TextToVoiceGenerateButton } from "./TextToVoiceGenerateButton";
5
+ export { TextToVoiceAudioPlayer } from "./TextToVoiceAudioPlayer";
6
+ export { TextToVoiceErrorMessage } from "./TextToVoiceErrorMessage";
7
+ export { TextToVoiceHeader } from "./TextToVoiceHeader";
@@ -1,5 +1,6 @@
1
- export { useTextToVoiceFeature } from "./useTextToVoiceFeature";
1
+ export { useTextToVoiceForm } from "./useTextToVoiceForm";
2
+ export { useTextToVoiceGeneration } from "./useTextToVoiceGeneration";
2
3
  export type {
3
- UseTextToVoiceFeatureProps,
4
- UseTextToVoiceFeatureReturn,
5
- } from "./useTextToVoiceFeature";
4
+ UseTextToVoiceGenerationProps,
5
+ UseTextToVoiceGenerationReturn,
6
+ } from "./useTextToVoiceGeneration";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * useTextToVoiceForm Hook
3
+ * Manages form state for text-to-voice generation
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import type {
8
+ TextToVoiceFormState,
9
+ TextToVoiceFormReturn,
10
+ TextToVoiceFormConfig,
11
+ TextToVoiceRequest,
12
+ } from "../../domain/types";
13
+
14
+ const DEFAULT_FORM_STATE: TextToVoiceFormState = {
15
+ text: "",
16
+ model: "",
17
+ voice: "",
18
+ speed: 1.0,
19
+ stability: 0.5,
20
+ similarityBoost: 0.75,
21
+ };
22
+
23
+ export function useTextToVoiceForm(
24
+ config?: TextToVoiceFormConfig,
25
+ ): TextToVoiceFormReturn {
26
+ const initialState: TextToVoiceFormState = {
27
+ text: config?.initialText ?? DEFAULT_FORM_STATE.text,
28
+ model: config?.initialModel ?? DEFAULT_FORM_STATE.model,
29
+ voice: config?.initialVoice ?? DEFAULT_FORM_STATE.voice,
30
+ speed: config?.initialSpeed ?? DEFAULT_FORM_STATE.speed,
31
+ stability: config?.initialStability ?? DEFAULT_FORM_STATE.stability,
32
+ similarityBoost:
33
+ config?.initialSimilarityBoost ?? DEFAULT_FORM_STATE.similarityBoost,
34
+ };
35
+
36
+ const [formState, setFormState] = useState<TextToVoiceFormState>(initialState);
37
+
38
+ const setText = useCallback((text: string) => {
39
+ setFormState((prev) => ({ ...prev, text }));
40
+ }, []);
41
+
42
+ const setModel = useCallback((model: string) => {
43
+ setFormState((prev) => ({ ...prev, model }));
44
+ }, []);
45
+
46
+ const setVoice = useCallback((voice: string) => {
47
+ setFormState((prev) => ({ ...prev, voice }));
48
+ }, []);
49
+
50
+ const setSpeed = useCallback((speed: number) => {
51
+ setFormState((prev) => ({ ...prev, speed }));
52
+ }, []);
53
+
54
+ const setStability = useCallback((stability: number) => {
55
+ setFormState((prev) => ({ ...prev, stability }));
56
+ }, []);
57
+
58
+ const setSimilarityBoost = useCallback((similarityBoost: number) => {
59
+ setFormState((prev) => ({ ...prev, similarityBoost }));
60
+ }, []);
61
+
62
+ const buildRequest = useCallback((): TextToVoiceRequest => {
63
+ return {
64
+ text: formState.text.trim(),
65
+ userId: "",
66
+ model: formState.model || undefined,
67
+ options: {
68
+ voice: formState.voice || undefined,
69
+ speed: formState.speed,
70
+ stability: formState.stability,
71
+ similarityBoost: formState.similarityBoost,
72
+ },
73
+ };
74
+ }, [formState]);
75
+
76
+ const resetForm = useCallback(() => {
77
+ setFormState(initialState);
78
+ }, [initialState]);
79
+
80
+ return {
81
+ formState,
82
+ setText,
83
+ setModel,
84
+ setVoice,
85
+ setSpeed,
86
+ setStability,
87
+ setSimilarityBoost,
88
+ buildRequest,
89
+ resetForm,
90
+ };
91
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * useTextToVoiceGeneration Hook
3
+ * Handles text-to-voice generation flow
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import { executeTextToVoice } from "../../infrastructure/services";
8
+ import type {
9
+ TextToVoiceGenerationState,
10
+ TextToVoiceRequest,
11
+ TextToVoiceResult,
12
+ VoiceGeneration,
13
+ TextToVoiceFeatureConfig,
14
+ } from "../../domain/types";
15
+
16
+ export interface UseTextToVoiceGenerationProps {
17
+ config: TextToVoiceFeatureConfig;
18
+ onSuccess?: (generation: VoiceGeneration) => void;
19
+ onError?: (error: string) => void;
20
+ }
21
+
22
+ export interface UseTextToVoiceGenerationReturn {
23
+ state: TextToVoiceGenerationState;
24
+ generate: (request: TextToVoiceRequest) => Promise<VoiceGeneration | null>;
25
+ resetState: () => void;
26
+ }
27
+
28
+ const initialState: TextToVoiceGenerationState = {
29
+ isGenerating: false,
30
+ progress: 0,
31
+ audioUrl: null,
32
+ error: null,
33
+ requestId: null,
34
+ };
35
+
36
+ export function useTextToVoiceGeneration(
37
+ props: UseTextToVoiceGenerationProps,
38
+ ): UseTextToVoiceGenerationReturn {
39
+ const { config, onSuccess, onError } = props;
40
+ const [state, setState] = useState<TextToVoiceGenerationState>(initialState);
41
+
42
+ const resetState = useCallback(() => {
43
+ setState(initialState);
44
+ }, []);
45
+
46
+ const generate = useCallback(
47
+ async (request: TextToVoiceRequest): Promise<VoiceGeneration | null> => {
48
+ if (!request.text.trim()) {
49
+ const error = "Text is required";
50
+ setState((prev) => ({ ...prev, error }));
51
+ onError?.(error);
52
+ return null;
53
+ }
54
+
55
+ setState({
56
+ isGenerating: true,
57
+ progress: 0,
58
+ audioUrl: null,
59
+ error: null,
60
+ requestId: null,
61
+ });
62
+
63
+ config.onProcessingStart?.();
64
+
65
+ const result: TextToVoiceResult = await executeTextToVoice(request, {
66
+ model: config.model,
67
+ buildInput: config.buildInput,
68
+ extractResult: config.extractResult,
69
+ onProgress: (progress) => {
70
+ setState((prev) => ({ ...prev, progress }));
71
+ },
72
+ });
73
+
74
+ if (result.success && result.audioUrl) {
75
+ const generation: VoiceGeneration = {
76
+ id: result.requestId || Date.now().toString(),
77
+ text: request.text,
78
+ audioUrl: result.audioUrl,
79
+ provider: config.providerId || "unknown",
80
+ createdAt: new Date().toISOString(),
81
+ };
82
+
83
+ setState({
84
+ isGenerating: false,
85
+ progress: 100,
86
+ audioUrl: result.audioUrl,
87
+ error: null,
88
+ requestId: generation.id,
89
+ });
90
+
91
+ config.onProcessingComplete?.(result);
92
+ onSuccess?.(generation);
93
+
94
+ return generation;
95
+ }
96
+
97
+ const error = result.error || "Generation failed";
98
+ setState({
99
+ isGenerating: false,
100
+ progress: 0,
101
+ audioUrl: null,
102
+ error,
103
+ requestId: null,
104
+ });
105
+
106
+ config.onError?.(error);
107
+ onError?.(error);
108
+ config.onProcessingComplete?.(result);
109
+
110
+ return null;
111
+ },
112
+ [config, onSuccess, onError],
113
+ );
114
+
115
+ return { state, generate, resetState };
116
+ }
@@ -1 +1,2 @@
1
1
  export * from "./hooks";
2
+ export * from "./components";