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