@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.26.55",
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 { StepType, type StepDefinition } from "../../../../../domain/entities/flow-config.types";
8
- import type { WizardStepConfig } from "../../domain/entities/wizard-config.types";
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 interface WizardStepRendererProps {
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 as unknown as ScenarioData}
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
- return renderResult(generationResult);
99
- }
100
- const creation = generationResult as Record<string, unknown>;
101
- const output = creation?.output as Record<string, unknown> | undefined;
102
- const imageUrl = output?.imageUrl || creation?.uri || creation?.imageUrl || "";
103
- if (!imageUrl || typeof imageUrl !== "string") return null;
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={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={onTryAgain || onBack}
112
- onNavigateBack={onTryAgain || onBack}
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 as WizardStepConfig;
128
- const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
129
- const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
130
- const existingPhoto = customData[step.id] as UploadedImage | undefined;
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
- flex: 1,
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
- const displayImageUrl = useMemo(() => {
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
- if (!displayImageUrl) return null;
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
- <AtomicText style={styles.title}>{translations.yourResult}</AtomicText>
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
- {recentCreations && recentCreations.length > 0 && translations.recentCreations && translations.viewAll && (
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
- /** Result data to display */
98
- imageUrl: string;
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 */