@umituz/react-native-ai-generation-content 1.26.56 → 1.26.57
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/utils/media-url-extractor.ts +22 -21
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +17 -52
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.types.ts +23 -0
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.utils.ts +25 -0
- package/src/domains/result-preview/presentation/components/ResultPreviewScreen.tsx +16 -34
- package/src/domains/result-preview/presentation/components/ResultPreviewScreen.utils.ts +27 -0
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.57",
|
|
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",
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
* Media URL Extractor
|
|
3
|
-
* Extracts media URL from generation result object
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
interface MediaUrlResult {
|
|
1
|
+
export interface MediaUrlResult {
|
|
7
2
|
readonly url: string;
|
|
8
3
|
readonly isVideo: boolean;
|
|
9
4
|
}
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
7
|
+
return typeof value === "object" && value !== null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function findStringValue(obj: Record<string, unknown>, keys: string[]): string | undefined {
|
|
11
|
+
for (const key of keys) {
|
|
12
|
+
const value = obj[key];
|
|
13
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
15
18
|
export function extractMediaUrl(result: unknown): MediaUrlResult | null {
|
|
16
|
-
if (!result
|
|
19
|
+
if (!isRecord(result)) return null;
|
|
17
20
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
21
|
+
const output = isRecord(result.output) ? result.output : undefined;
|
|
22
|
+
const sources = output ? [output, result] : [result];
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { url: videoUrl, isVideo: true };
|
|
24
|
+
for (const source of sources) {
|
|
25
|
+
const videoUrl = findStringValue(source, ["videoUrl", "video_url"]);
|
|
26
|
+
if (videoUrl) return { url: videoUrl, isVideo: true };
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return { url: imageUrl, isVideo: false };
|
|
29
|
+
for (const source of sources) {
|
|
30
|
+
const imageUrl = findStringValue(source, ["imageUrl", "image_url", "uri"]);
|
|
31
|
+
if (imageUrl) return { url: imageUrl, isVideo: false };
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
return null;
|
|
@@ -1,40 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wizard Step Renderer Component
|
|
3
|
-
* Renders the appropriate screen based on current step type
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React from "react";
|
|
7
2
|
import { getMediaTypeFromUrl } from "@umituz/react-native-design-system";
|
|
8
|
-
import { StepType
|
|
9
|
-
import type { WizardStepConfig } from "../../domain/entities/wizard-config.types";
|
|
10
|
-
import type { WizardScenarioData } from "../hooks/useWizardGeneration";
|
|
11
|
-
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
3
|
+
import { StepType } from "../../../../../domain/entities/flow-config.types";
|
|
12
4
|
import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
|
|
13
5
|
import { GeneratingScreen } from "../screens/GeneratingScreen";
|
|
14
6
|
import { ScenarioPreviewScreen } from "../../../../scenarios/presentation/screens/ScenarioPreviewScreen";
|
|
15
|
-
import type { ScenarioData } from "../../../../scenarios/domain/scenario.types";
|
|
16
7
|
import { ResultPreviewScreen } from "../../../../result-preview/presentation/components/ResultPreviewScreen";
|
|
17
8
|
import { extractMediaUrl } from "../../infrastructure/utils/media-url-extractor";
|
|
9
|
+
import { getWizardStepConfig, getUploadedImage } from "./WizardStepRenderer.utils";
|
|
10
|
+
import type { WizardStepRendererProps } from "./WizardStepRenderer.types";
|
|
18
11
|
|
|
19
|
-
export
|
|
20
|
-
readonly step: StepDefinition | undefined;
|
|
21
|
-
readonly scenario?: WizardScenarioData;
|
|
22
|
-
readonly customData: Record<string, unknown>;
|
|
23
|
-
readonly generationProgress: number;
|
|
24
|
-
readonly generationResult: unknown;
|
|
25
|
-
readonly isSaving: boolean;
|
|
26
|
-
readonly isSharing: boolean;
|
|
27
|
-
readonly onNext: () => void;
|
|
28
|
-
readonly onBack: () => void;
|
|
29
|
-
readonly onPhotoContinue: (stepId: string, image: UploadedImage) => void;
|
|
30
|
-
readonly onDownload: () => void;
|
|
31
|
-
readonly onShare: () => void;
|
|
32
|
-
readonly onTryAgain?: () => void;
|
|
33
|
-
readonly t: (key: string) => string;
|
|
34
|
-
readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
|
|
35
|
-
readonly renderGenerating?: (progress: number) => React.ReactElement | null;
|
|
36
|
-
readonly renderResult?: (result: unknown) => React.ReactElement | null;
|
|
37
|
-
}
|
|
12
|
+
export type { WizardStepRendererProps } from "./WizardStepRenderer.types";
|
|
38
13
|
|
|
39
14
|
export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
40
15
|
step,
|
|
@@ -64,13 +39,11 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
64
39
|
|
|
65
40
|
switch (step.type) {
|
|
66
41
|
case StepType.SCENARIO_PREVIEW: {
|
|
67
|
-
if (renderPreview)
|
|
68
|
-
return renderPreview(onNext);
|
|
69
|
-
}
|
|
42
|
+
if (renderPreview) return renderPreview(onNext);
|
|
70
43
|
if (!scenario) return null;
|
|
71
44
|
return (
|
|
72
45
|
<ScenarioPreviewScreen
|
|
73
|
-
scenario={scenario
|
|
46
|
+
scenario={scenario}
|
|
74
47
|
translations={{
|
|
75
48
|
continueButton: t("common.continue"),
|
|
76
49
|
whatToExpect: t("scenarioPreview.whatToExpect"),
|
|
@@ -83,27 +56,19 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
83
56
|
}
|
|
84
57
|
|
|
85
58
|
case StepType.GENERATING: {
|
|
86
|
-
if (renderGenerating)
|
|
87
|
-
return renderGenerating(generationProgress);
|
|
88
|
-
}
|
|
59
|
+
if (renderGenerating) return renderGenerating(generationProgress);
|
|
89
60
|
return (
|
|
90
|
-
<GeneratingScreen
|
|
91
|
-
progress={generationProgress}
|
|
92
|
-
scenario={scenario}
|
|
93
|
-
t={t}
|
|
94
|
-
/>
|
|
61
|
+
<GeneratingScreen progress={generationProgress} scenario={scenario} t={t} />
|
|
95
62
|
);
|
|
96
63
|
}
|
|
97
64
|
|
|
98
65
|
case StepType.RESULT_PREVIEW: {
|
|
99
|
-
if (renderResult)
|
|
100
|
-
return renderResult(generationResult);
|
|
101
|
-
}
|
|
66
|
+
if (renderResult) return renderResult(generationResult);
|
|
102
67
|
const media = extractMediaUrl(generationResult);
|
|
103
68
|
if (!media) return null;
|
|
104
69
|
|
|
105
|
-
const
|
|
106
|
-
const
|
|
70
|
+
const isVideo = media.isVideo || getMediaTypeFromUrl(media.url) === "video";
|
|
71
|
+
const handleTryAgain = onTryAgain ?? onBack;
|
|
107
72
|
|
|
108
73
|
return (
|
|
109
74
|
<ResultPreviewScreen
|
|
@@ -113,8 +78,8 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
113
78
|
isSharing={isSharing}
|
|
114
79
|
onDownload={onDownload}
|
|
115
80
|
onShare={onShare}
|
|
116
|
-
onTryAgain={
|
|
117
|
-
onNavigateBack={
|
|
81
|
+
onTryAgain={handleTryAgain}
|
|
82
|
+
onNavigateBack={handleTryAgain}
|
|
118
83
|
translations={{
|
|
119
84
|
title: t("generation.result.title"),
|
|
120
85
|
yourResult: t("generation.result.yourResult"),
|
|
@@ -129,10 +94,10 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
129
94
|
}
|
|
130
95
|
|
|
131
96
|
case StepType.PARTNER_UPLOAD: {
|
|
132
|
-
const wizardConfig = step.config
|
|
133
|
-
const titleKey = wizardConfig?.titleKey
|
|
134
|
-
const subtitleKey = wizardConfig?.subtitleKey
|
|
135
|
-
const existingPhoto = customData[step.id]
|
|
97
|
+
const wizardConfig = getWizardStepConfig(step.config);
|
|
98
|
+
const titleKey = wizardConfig?.titleKey ?? `wizard.steps.${step.id}.title`;
|
|
99
|
+
const subtitleKey = wizardConfig?.subtitleKey ?? `wizard.steps.${step.id}.subtitle`;
|
|
100
|
+
const existingPhoto = getUploadedImage(customData[step.id]);
|
|
136
101
|
|
|
137
102
|
return (
|
|
138
103
|
<GenericPhotoUploadScreen
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StepDefinition } from "../../../../../domain/entities/flow-config.types";
|
|
2
|
+
import type { WizardScenarioData } from "../hooks/useWizardGeneration";
|
|
3
|
+
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
4
|
+
|
|
5
|
+
export interface WizardStepRendererProps {
|
|
6
|
+
readonly step: StepDefinition | undefined;
|
|
7
|
+
readonly scenario?: WizardScenarioData;
|
|
8
|
+
readonly customData: Record<string, unknown>;
|
|
9
|
+
readonly generationProgress: number;
|
|
10
|
+
readonly generationResult: unknown;
|
|
11
|
+
readonly isSaving: boolean;
|
|
12
|
+
readonly isSharing: boolean;
|
|
13
|
+
readonly onNext: () => void;
|
|
14
|
+
readonly onBack: () => void;
|
|
15
|
+
readonly onPhotoContinue: (stepId: string, image: UploadedImage) => void;
|
|
16
|
+
readonly onDownload: () => void;
|
|
17
|
+
readonly onShare: () => void;
|
|
18
|
+
readonly onTryAgain?: () => void;
|
|
19
|
+
readonly t: (key: string) => string;
|
|
20
|
+
readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
|
|
21
|
+
readonly renderGenerating?: (progress: number) => React.ReactElement | null;
|
|
22
|
+
readonly renderResult?: (result: unknown) => React.ReactElement | null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WizardStepConfig } from "../../domain/entities/wizard-config.types";
|
|
2
|
+
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
3
|
+
|
|
4
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return typeof value === "object" && value !== null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isWizardStepConfig(value: unknown): value is WizardStepConfig {
|
|
9
|
+
return isRecord(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isUploadedImage(value: unknown): value is UploadedImage {
|
|
13
|
+
if (!isRecord(value)) return false;
|
|
14
|
+
return typeof value.uri === "string";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getWizardStepConfig(config: unknown): WizardStepConfig | undefined {
|
|
18
|
+
if (isWizardStepConfig(config)) return config;
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getUploadedImage(data: unknown): UploadedImage | undefined {
|
|
23
|
+
if (isUploadedImage(data)) return data;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ResultPreviewScreen Component
|
|
3
|
-
* Displays AI generation result with actions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React, { useMemo } from "react";
|
|
7
2
|
import { StyleSheet, View } from "react-native";
|
|
8
3
|
import {
|
|
@@ -16,6 +11,7 @@ import { ResultActionBar } from "./ResultActionBar";
|
|
|
16
11
|
import { RecentCreationsSection } from "./RecentCreationsSection";
|
|
17
12
|
import { VideoResultPlayer } from "../../../../presentation/components/display/VideoResultPlayer";
|
|
18
13
|
import type { ResultPreviewScreenProps } from "../types/result-preview.types";
|
|
14
|
+
import { formatMediaUrl, shouldShowRecentCreations } from "./ResultPreviewScreen.utils";
|
|
19
15
|
|
|
20
16
|
export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
|
|
21
17
|
imageUrl,
|
|
@@ -39,17 +35,16 @@ export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
|
|
|
39
35
|
}) => {
|
|
40
36
|
const tokens = useAppDesignTokens();
|
|
41
37
|
const isVideo = Boolean(videoUrl);
|
|
38
|
+
const displayMediaUrl = useMemo(
|
|
39
|
+
() => formatMediaUrl(videoUrl, imageUrl, isVideo),
|
|
40
|
+
[imageUrl, videoUrl, isVideo]
|
|
41
|
+
);
|
|
42
42
|
|
|
43
43
|
const styles = useMemo(
|
|
44
44
|
() =>
|
|
45
45
|
StyleSheet.create({
|
|
46
|
-
container: {
|
|
47
|
-
|
|
48
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
49
|
-
},
|
|
50
|
-
resultContainer: {
|
|
51
|
-
marginTop: tokens.spacing.lg,
|
|
52
|
-
},
|
|
46
|
+
container: { flex: 1, paddingHorizontal: tokens.spacing.lg },
|
|
47
|
+
resultContainer: { marginTop: tokens.spacing.lg },
|
|
53
48
|
title: {
|
|
54
49
|
fontSize: 18,
|
|
55
50
|
fontWeight: "700",
|
|
@@ -57,33 +52,20 @@ export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
|
|
|
57
52
|
marginBottom: tokens.spacing.md,
|
|
58
53
|
},
|
|
59
54
|
}),
|
|
60
|
-
[tokens]
|
|
55
|
+
[tokens]
|
|
61
56
|
);
|
|
62
57
|
|
|
63
|
-
const displayMediaUrl = useMemo(() => {
|
|
64
|
-
const url = videoUrl || imageUrl;
|
|
65
|
-
if (!url) return null;
|
|
66
|
-
if (!isVideo && !url.startsWith("http") && !url.startsWith("data:image")) {
|
|
67
|
-
return `data:image/jpeg;base64,${url}`;
|
|
68
|
-
}
|
|
69
|
-
return url;
|
|
70
|
-
}, [imageUrl, videoUrl, isVideo]);
|
|
71
|
-
|
|
72
58
|
if (!displayMediaUrl) return null;
|
|
73
59
|
|
|
60
|
+
const showRecent = shouldShowRecentCreations(recentCreations, translations);
|
|
61
|
+
|
|
74
62
|
return (
|
|
75
63
|
<ScreenLayout scrollable edges={["left", "right"]} backgroundColor={tokens.colors.backgroundPrimary}>
|
|
76
64
|
<NavigationHeader title={translations.title} onBackPress={onNavigateBack} />
|
|
77
65
|
<View style={[styles.container, style]}>
|
|
78
66
|
<View style={styles.resultContainer}>
|
|
79
|
-
{!hideLabel &&
|
|
80
|
-
|
|
81
|
-
)}
|
|
82
|
-
{isVideo ? (
|
|
83
|
-
<VideoResultPlayer uri={displayMediaUrl} />
|
|
84
|
-
) : (
|
|
85
|
-
<ResultImageCard imageUrl={displayMediaUrl} />
|
|
86
|
-
)}
|
|
67
|
+
{!hideLabel && <AtomicText style={styles.title}>{translations.yourResult}</AtomicText>}
|
|
68
|
+
{isVideo ? <VideoResultPlayer uri={displayMediaUrl} /> : <ResultImageCard imageUrl={displayMediaUrl} />}
|
|
87
69
|
<ResultActionBar
|
|
88
70
|
isSaving={isSaving}
|
|
89
71
|
isSharing={isSharing}
|
|
@@ -99,13 +81,13 @@ export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
|
|
|
99
81
|
showRating={showRating}
|
|
100
82
|
/>
|
|
101
83
|
</View>
|
|
102
|
-
{
|
|
84
|
+
{showRecent && (
|
|
103
85
|
<RecentCreationsSection
|
|
104
|
-
recentCreations={recentCreations}
|
|
86
|
+
recentCreations={recentCreations!}
|
|
105
87
|
onViewAll={onViewAll}
|
|
106
88
|
onCreationPress={onCreationPress}
|
|
107
|
-
title={translations.recentCreations}
|
|
108
|
-
viewAllLabel={translations.viewAll}
|
|
89
|
+
title={translations.recentCreations!}
|
|
90
|
+
viewAllLabel={translations.viewAll!}
|
|
109
91
|
/>
|
|
110
92
|
)}
|
|
111
93
|
</View>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RecentCreation, ResultPreviewTranslations } from "../types/result-preview.types";
|
|
2
|
+
|
|
3
|
+
export function formatMediaUrl(
|
|
4
|
+
videoUrl: string | undefined,
|
|
5
|
+
imageUrl: string | undefined,
|
|
6
|
+
isVideo: boolean
|
|
7
|
+
): string | null {
|
|
8
|
+
const url = videoUrl ?? imageUrl;
|
|
9
|
+
if (!url) return null;
|
|
10
|
+
|
|
11
|
+
if (!isVideo && !url.startsWith("http") && !url.startsWith("data:image")) {
|
|
12
|
+
return `data:image/jpeg;base64,${url}`;
|
|
13
|
+
}
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function shouldShowRecentCreations(
|
|
18
|
+
recentCreations: readonly RecentCreation[] | undefined,
|
|
19
|
+
translations: ResultPreviewTranslations
|
|
20
|
+
): boolean {
|
|
21
|
+
return Boolean(
|
|
22
|
+
recentCreations &&
|
|
23
|
+
recentCreations.length > 0 &&
|
|
24
|
+
translations.recentCreations &&
|
|
25
|
+
translations.viewAll
|
|
26
|
+
);
|
|
27
|
+
}
|