@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.26.56",
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
- * Extract media URL from generation result
13
- * Supports multiple result formats from different AI providers
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 || typeof result !== "object") return null;
19
+ if (!isRecord(result)) return null;
17
20
 
18
- const data = result as Record<string, unknown>;
19
- const output = data.output as Record<string, unknown> | undefined;
21
+ const output = isRecord(result.output) ? result.output : undefined;
22
+ const sources = output ? [output, result] : [result];
20
23
 
21
- // Try to get video URL first
22
- const videoUrl = output?.videoUrl || data.videoUrl || data.video_url;
23
- if (videoUrl && typeof videoUrl === "string") {
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
- // Try to get image URL
28
- const imageUrl = output?.imageUrl || data.imageUrl || data.image_url || data.uri;
29
- if (imageUrl && typeof imageUrl === "string") {
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, type StepDefinition } from "../../../../../domain/entities/flow-config.types";
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 interface WizardStepRendererProps {
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 as unknown as ScenarioData}
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 mediaType = getMediaTypeFromUrl(media.url);
106
- const isVideo = media.isVideo || mediaType === "video";
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={onTryAgain || onBack}
117
- onNavigateBack={onTryAgain || onBack}
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 as WizardStepConfig;
133
- const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
134
- const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
135
- 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]);
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
- flex: 1,
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
- <AtomicText style={styles.title}>{translations.yourResult}</AtomicText>
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
- {recentCreations && recentCreations.length > 0 && translations.recentCreations && translations.viewAll && (
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
+ }