@umituz/react-native-ai-generation-content 1.12.26 → 1.12.31

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.12.26",
3
+ "version": "1.12.31",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Default Image Processing Modes
3
+ * Pre-configured modes for common image processing tasks
4
+ * Apps can use these defaults or provide their own configurations
5
+ */
6
+
7
+ import type { ImageProcessingMode, ModeCatalog, ModeConfig } from "../entities/processing-modes.types";
8
+
9
+ export const DEFAULT_PROCESSING_MODES: ModeCatalog = {
10
+ clean_white: {
11
+ id: "clean_white",
12
+ icon: "square",
13
+ cost: 1,
14
+ premium: false,
15
+ requiresPrompt: false,
16
+ aiPrompt: `Replace the background with a clean, pure white background.
17
+ Keep the subject perfectly intact with professional edges.
18
+ Add subtle shadows beneath the subject for a grounded look.
19
+ This should look like a professional product or portrait photo.`,
20
+ },
21
+ portrait_blur: {
22
+ id: "portrait_blur",
23
+ icon: "person",
24
+ cost: 1,
25
+ premium: false,
26
+ requiresPrompt: false,
27
+ aiPrompt: `Create a professional portrait with a beautifully blurred background (bokeh effect).
28
+ Keep the subject in sharp focus with clean edges.
29
+ The background should have a soft, creamy blur similar to f/1.4 depth of field.
30
+ Maintain natural skin tones and lighting on the subject.`,
31
+ },
32
+ creative_scene: {
33
+ id: "creative_scene",
34
+ icon: "image",
35
+ cost: 2,
36
+ premium: false,
37
+ requiresPrompt: true,
38
+ aiPrompt: `Transform this image by placing the subject in a new creative scene.
39
+ The subject must remain completely unchanged - same pose, expression, clothing.
40
+ Seamlessly blend the subject into the new background with matched lighting.
41
+ Create a professional, realistic composite that looks natural.`,
42
+ },
43
+ transparent: {
44
+ id: "transparent",
45
+ icon: "remove-circle",
46
+ cost: 1,
47
+ premium: false,
48
+ requiresPrompt: false,
49
+ aiPrompt: `Remove the background completely from this image.
50
+ Keep the main subject perfectly intact with clean edges.
51
+ Output should have a transparent or solid white background.
52
+ Maintain all original details, colors, and lighting on the subject.`,
53
+ },
54
+ enhance: {
55
+ id: "enhance",
56
+ icon: "sparkles",
57
+ cost: 2,
58
+ premium: false,
59
+ requiresPrompt: false,
60
+ aiPrompt: `Enhance this image with professional quality improvements.
61
+ Improve lighting, color balance, and overall image quality.
62
+ Remove any imperfections while maintaining natural appearance.
63
+ The result should look professionally retouched but realistic.`,
64
+ },
65
+ remove_object: {
66
+ id: "remove_object",
67
+ icon: "trash",
68
+ cost: 2,
69
+ premium: true,
70
+ requiresPrompt: true,
71
+ aiPrompt: `Remove unwanted objects from this image.
72
+ Fill the removed areas with appropriate background content.
73
+ The result should look natural with no visible artifacts.
74
+ Maintain the overall composition and quality of the image.`,
75
+ },
76
+ replace_object: {
77
+ id: "replace_object",
78
+ icon: "swap-horizontal",
79
+ cost: 3,
80
+ premium: true,
81
+ requiresPrompt: true,
82
+ aiPrompt: `Replace the specified object in this image with a new one.
83
+ The replacement should blend naturally with the scene.
84
+ Match the lighting, perspective, and style of the original.
85
+ Ensure the result looks realistic and professionally edited.`,
86
+ },
87
+ relight: {
88
+ id: "relight",
89
+ icon: "sunny",
90
+ cost: 2,
91
+ premium: true,
92
+ requiresPrompt: true,
93
+ aiPrompt: `Relight this image with professional studio lighting.
94
+ Apply dramatic, flattering light that enhances the subject.
95
+ Add subtle shadows and highlights for depth and dimension.
96
+ The result should look like a professional studio photograph.`,
97
+ },
98
+ };
99
+
100
+ /**
101
+ * Get mode configuration by ID
102
+ * Returns default transparent mode if not found
103
+ */
104
+ export const getModeConfig = (
105
+ mode: string,
106
+ customModes?: ModeCatalog
107
+ ): ModeConfig => {
108
+ const modes = customModes || DEFAULT_PROCESSING_MODES;
109
+ const key = mode.replace("-", "_") as ImageProcessingMode;
110
+ return modes[key] || DEFAULT_PROCESSING_MODES.transparent;
111
+ };
112
+
113
+ /**
114
+ * Filter modes by premium status
115
+ */
116
+ export const getFreeModes = (modes: ModeCatalog = DEFAULT_PROCESSING_MODES): ModeCatalog => {
117
+ return Object.fromEntries(
118
+ Object.entries(modes).filter(([, config]) => !config.premium)
119
+ );
120
+ };
121
+
122
+ /**
123
+ * Filter modes by premium status
124
+ */
125
+ export const getPremiumModes = (modes: ModeCatalog = DEFAULT_PROCESSING_MODES): ModeCatalog => {
126
+ return Object.fromEntries(
127
+ Object.entries(modes).filter(([, config]) => config.premium)
128
+ );
129
+ };
130
+
131
+ /**
132
+ * Get modes that require custom prompts
133
+ */
134
+ export const getPromptRequiredModes = (modes: ModeCatalog = DEFAULT_PROCESSING_MODES): ModeCatalog => {
135
+ return Object.fromEntries(
136
+ Object.entries(modes).filter(([, config]) => config.requiresPrompt)
137
+ );
138
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Image Processing Mode Types
3
+ * Generic types for image processing modes across apps
4
+ */
5
+
6
+ export type ImageProcessingMode =
7
+ | "clean_white"
8
+ | "portrait_blur"
9
+ | "creative_scene"
10
+ | "transparent"
11
+ | "enhance"
12
+ | "remove_object"
13
+ | "replace_object"
14
+ | "relight";
15
+
16
+ export interface ModeConfig {
17
+ readonly id: string;
18
+ readonly icon: string;
19
+ readonly cost: number;
20
+ readonly premium: boolean;
21
+ readonly requiresPrompt: boolean;
22
+ readonly aiPrompt: string;
23
+ }
24
+
25
+ export interface ModeCatalog {
26
+ readonly [key: string]: ModeConfig;
27
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Gallery Empty States
3
+ * Handles different empty state scenarios for gallery
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ActivityIndicator, StyleSheet } from "react-native";
8
+ import type { DesignTokens } from "@umituz/react-native-design-system";
9
+ import { EmptyState } from "./EmptyState";
10
+ import type { Creation } from "../../domain/entities/Creation";
11
+
12
+ interface GalleryEmptyStatesProps {
13
+ isLoading: boolean;
14
+ creations: Creation[] | undefined;
15
+ isFiltered: boolean;
16
+ tokens: DesignTokens;
17
+ t: (key: string) => string;
18
+ emptyTitle: string;
19
+ emptyDescription: string;
20
+ emptyActionLabel?: string;
21
+ onEmptyAction?: () => void;
22
+ onClearFilters: () => void;
23
+ }
24
+
25
+ export function GalleryEmptyStates({
26
+ isLoading,
27
+ creations,
28
+ isFiltered,
29
+ tokens,
30
+ t,
31
+ emptyTitle,
32
+ emptyDescription,
33
+ emptyActionLabel,
34
+ onEmptyAction,
35
+ onClearFilters,
36
+ }: GalleryEmptyStatesProps) {
37
+ const styles = createStyles(tokens);
38
+
39
+ // 1. Loading State
40
+ if (isLoading && (!creations || creations?.length === 0)) {
41
+ return (
42
+ <View style={styles.centerContainer}>
43
+ <ActivityIndicator size="large" color={tokens.colors.primary} />
44
+ </View>
45
+ );
46
+ }
47
+
48
+ // 2. System Empty State (User has NO creations at all)
49
+ if (!creations || creations?.length === 0) {
50
+ return (
51
+ <View style={styles.centerContainer}>
52
+ <EmptyState
53
+ title={emptyTitle}
54
+ description={emptyDescription}
55
+ actionLabel={emptyActionLabel}
56
+ onAction={onEmptyAction}
57
+ />
58
+ </View>
59
+ );
60
+ }
61
+
62
+ // 3. Filter Empty State (User has creations, but filter returns none)
63
+ if (isFiltered) {
64
+ return (
65
+ <View style={styles.centerContainer}>
66
+ <EmptyState
67
+ title={t("common.no_results") || "No results"}
68
+ description={t("common.no_results_description") || "Try changing your filters"}
69
+ actionLabel={t("common.clear_all") || "Clear All"}
70
+ onAction={onClearFilters}
71
+ />
72
+ </View>
73
+ );
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ const createStyles = (tokens: DesignTokens) => StyleSheet.create({
80
+ centerContainer: {
81
+ flex: 1,
82
+ justifyContent: 'center',
83
+ alignItems: 'center',
84
+ minHeight: 400,
85
+ paddingHorizontal: tokens.spacing.xl,
86
+ },
87
+ });
@@ -4,6 +4,7 @@
4
4
 
5
5
  export { GalleryHeader } from "./GalleryHeader";
6
6
  export { EmptyState } from "./EmptyState";
7
+ export { GalleryEmptyStates } from "./GalleryEmptyStates";
7
8
  export { FilterChips } from "./FilterChips";
8
9
  export { CreationsHomeCard } from "./CreationsHomeCard";
9
10
  export { CreationCard } from "./CreationCard";
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo, useCallback, useState } from "react";
2
- import { View, StyleSheet, ActivityIndicator, type LayoutChangeEvent } from "react-native";
2
+ import { View, StyleSheet, type LayoutChangeEvent } from "react-native";
3
3
  import { useAppDesignTokens, useAlert, AlertType, AlertMode, useSharing, type DesignTokens } from "@umituz/react-native-design-system";
4
4
  import { BottomSheetModal } from '@gorhom/bottom-sheet';
5
5
  import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -7,7 +7,7 @@ import { useFocusEffect } from "@react-navigation/native";
7
7
  import { useCreations } from "../hooks/useCreations";
8
8
  import { useDeleteCreation } from "../hooks/useDeleteCreation";
9
9
  import { useCreationsFilter } from "../hooks/useCreationsFilter";
10
- import { GalleryHeader, EmptyState, CreationsGrid, FilterBottomSheet, CreationImageViewer } from "../components";
10
+ import { GalleryHeader, CreationsGrid, FilterBottomSheet, CreationImageViewer, GalleryEmptyStates } from "../components";
11
11
  import { getTranslatedTypes, getFilterCategoriesFromConfig } from "../utils/filterUtils";
12
12
  import type { Creation } from "../../domain/entities/Creation";
13
13
  import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
@@ -97,43 +97,20 @@ export function CreationsGalleryScreen({
97
97
 
98
98
  const styles = useStyles(tokens);
99
99
 
100
- // Define empty state content based on state
101
- const renderEmptyComponent = useMemo(() => {
102
- // 1. Loading State
103
- if (isLoading && (!creations || creations?.length === 0)) {
104
- return (
105
- <View style={styles.centerContainer}>
106
- <ActivityIndicator size="large" color={tokens.colors.primary} />
107
- </View>
108
- );
109
- }
110
-
111
- // 2. System Empty State (User has NO creations at all)
112
- if (!creations || creations?.length === 0) {
113
- return (
114
- <View style={styles.centerContainer}>
115
- <EmptyState
116
- title={t(config.translations.empty)}
117
- description={t(config.translations.emptyDescription)}
118
- actionLabel={emptyActionLabel}
119
- onAction={onEmptyAction}
120
- />
121
- </View>
122
- );
123
- }
124
-
125
- // 3. Filter Empty State (User has creations, but filter returns none)
126
- return (
127
- <View style={styles.centerContainer}>
128
- <EmptyState
129
- title={t("common.no_results") || "No results"}
130
- description={t("common.no_results_description") || "Try changing your filters"}
131
- actionLabel={t("common.clear_all") || "Clear All"}
132
- onAction={clearFilters}
133
- />
134
- </View>
135
- );
136
- }, [isLoading, creations, config, t, emptyActionLabel, onEmptyAction, clearFilters, styles.centerContainer, tokens.colors.primary]);
100
+ const renderEmptyComponent = useMemo(() => (
101
+ <GalleryEmptyStates
102
+ isLoading={isLoading}
103
+ creations={creations}
104
+ isFiltered={isFiltered}
105
+ tokens={tokens}
106
+ t={t}
107
+ emptyTitle={t(config.translations.empty)}
108
+ emptyDescription={t(config.translations.emptyDescription)}
109
+ emptyActionLabel={emptyActionLabel}
110
+ onEmptyAction={onEmptyAction}
111
+ onClearFilters={clearFilters}
112
+ />
113
+ ), [isLoading, creations, isFiltered, tokens, t, config, emptyActionLabel, onEmptyAction, clearFilters]);
137
114
 
138
115
  if (selectedCreation) {
139
116
  return (
@@ -0,0 +1,142 @@
1
+ /**
2
+ * AI Service Processor
3
+ * Handles processing of different AI service types
4
+ */
5
+
6
+ import type { FaceSwapConfig } from '../../domain/entities/FaceSwapConfig';
7
+ import type { PhotoRestorationConfig } from '../../domain/entities/PhotoRestorationConfig';
8
+ import type { ImageEnhancementConfig } from '../../domain/entities/ImageEnhancementConfig';
9
+ import type { StyleTransferConfig } from '../../domain/entities/StyleTransferConfig';
10
+ import type { BackgroundRemovalConfig } from '../../domain/entities/BackgroundRemovalConfig';
11
+ import type { TextGenerationConfig } from '../../domain/entities/TextGenerationConfig';
12
+ import type { ColorizationConfig } from '../../domain/entities/ColorizationConfig';
13
+ import type {
14
+ IFaceSwapService,
15
+ IPhotoRestorationService,
16
+ IImageEnhancementService,
17
+ IStyleTransferService,
18
+ IBackgroundRemovalService,
19
+ ITextGenerationService,
20
+ IColorizationService,
21
+ } from '../../domain/repositories/IAIPromptServices';
22
+ import type { AIPromptResult } from '../../domain/entities/types';
23
+ import type { AIPromptTemplate } from '../../domain/entities/AIPromptTemplate';
24
+
25
+ export type AIConfig =
26
+ | { type: 'face-swap'; config: FaceSwapConfig }
27
+ | { type: 'photo-restoration'; config: PhotoRestorationConfig }
28
+ | { type: 'image-enhancement'; config: ImageEnhancementConfig }
29
+ | { type: 'style-transfer'; config: StyleTransferConfig }
30
+ | { type: 'background-removal'; config: BackgroundRemovalConfig }
31
+ | { type: 'text-generation'; config: TextGenerationConfig }
32
+ | { type: 'colorization'; config: ColorizationConfig };
33
+
34
+ export interface AIServices {
35
+ faceSwap: IFaceSwapService;
36
+ photoRestoration: IPhotoRestorationService;
37
+ imageEnhancement: IImageEnhancementService;
38
+ styleTransfer: IStyleTransferService;
39
+ backgroundRemoval: IBackgroundRemovalService;
40
+ textGeneration: ITextGenerationService;
41
+ colorization: IColorizationService;
42
+ }
43
+
44
+ export interface ProcessResult {
45
+ template: AIPromptTemplate;
46
+ prompt: string;
47
+ config: Record<string, unknown>;
48
+ }
49
+
50
+ export class AIServiceProcessor {
51
+ constructor(private services: AIServices) { }
52
+
53
+ async process(aiConfig: AIConfig): Promise<ProcessResult> {
54
+ const { templateResult, promptResult } = await this.executeService(aiConfig);
55
+
56
+ if (!templateResult?.success || !templateResult.data) {
57
+ throw new Error('Failed to generate template');
58
+ }
59
+
60
+ if (!promptResult?.success || !promptResult.data) {
61
+ throw new Error('Failed to generate prompt');
62
+ }
63
+
64
+ return {
65
+ template: templateResult.data,
66
+ prompt: promptResult.data,
67
+ config: aiConfig.config as unknown as Record<string, unknown>,
68
+ };
69
+ }
70
+
71
+ async getAvailableStyles(serviceType: string): Promise<string[]> {
72
+ switch (serviceType) {
73
+ case 'face-swap':
74
+ return await this.services.faceSwap.getAvailableStyles();
75
+ case 'style-transfer':
76
+ return await this.services.styleTransfer.getAvailableStyles();
77
+ default:
78
+ return [];
79
+ }
80
+ }
81
+
82
+ private async executeService(aiConfig: AIConfig): Promise<{
83
+ templateResult: AIPromptResult<AIPromptTemplate> | undefined;
84
+ promptResult: AIPromptResult<string> | undefined;
85
+ }> {
86
+ let templateResult: AIPromptResult<AIPromptTemplate> | undefined;
87
+ let promptResult: AIPromptResult<string> | undefined;
88
+
89
+ switch (aiConfig.type) {
90
+ case 'face-swap':
91
+ templateResult = await this.services.faceSwap.generateTemplate(aiConfig.config);
92
+ if (templateResult.success && templateResult.data) {
93
+ promptResult = await this.services.faceSwap.generatePrompt(templateResult.data, aiConfig.config);
94
+ }
95
+ break;
96
+
97
+ case 'photo-restoration':
98
+ templateResult = await this.services.photoRestoration.generateTemplate(aiConfig.config);
99
+ if (templateResult.success && templateResult.data) {
100
+ promptResult = await this.services.photoRestoration.generatePrompt(templateResult.data, aiConfig.config);
101
+ }
102
+ break;
103
+
104
+ case 'image-enhancement':
105
+ templateResult = await this.services.imageEnhancement.generateTemplate(aiConfig.config);
106
+ if (templateResult.success && templateResult.data) {
107
+ promptResult = await this.services.imageEnhancement.generatePrompt(templateResult.data, aiConfig.config);
108
+ }
109
+ break;
110
+
111
+ case 'style-transfer':
112
+ templateResult = await this.services.styleTransfer.generateTemplate(aiConfig.config);
113
+ if (templateResult.success && templateResult.data) {
114
+ promptResult = await this.services.styleTransfer.generatePrompt(templateResult.data, aiConfig.config);
115
+ }
116
+ break;
117
+
118
+ case 'background-removal':
119
+ templateResult = await this.services.backgroundRemoval.generateTemplate(aiConfig.config);
120
+ if (templateResult.success && templateResult.data) {
121
+ promptResult = await this.services.backgroundRemoval.generatePrompt(templateResult.data, aiConfig.config);
122
+ }
123
+ break;
124
+
125
+ case 'text-generation':
126
+ templateResult = await this.services.textGeneration.generateTemplate(aiConfig.config);
127
+ if (templateResult.success && templateResult.data) {
128
+ promptResult = await this.services.textGeneration.generatePrompt(templateResult.data, aiConfig.config);
129
+ }
130
+ break;
131
+
132
+ case 'colorization':
133
+ templateResult = await this.services.colorization.generateTemplate(aiConfig.config);
134
+ if (templateResult.success && templateResult.data) {
135
+ promptResult = await this.services.colorization.generatePrompt(templateResult.data, aiConfig.config);
136
+ }
137
+ break;
138
+ }
139
+
140
+ return { templateResult, promptResult };
141
+ }
142
+ }
@@ -1,50 +1,12 @@
1
- import { useState, useCallback } from 'react';
2
- import type {
3
- FaceSwapConfig
4
- } from '../../domain/entities/FaceSwapConfig';
5
- import type {
6
- PhotoRestorationConfig
7
- } from '../../domain/entities/PhotoRestorationConfig';
8
- import type {
9
- ImageEnhancementConfig
10
- } from '../../domain/entities/ImageEnhancementConfig';
11
- import type {
12
- StyleTransferConfig
13
- } from '../../domain/entities/StyleTransferConfig';
14
- import type {
15
- BackgroundRemovalConfig
16
- } from '../../domain/entities/BackgroundRemovalConfig';
17
- import type {
18
- TextGenerationConfig
19
- } from '../../domain/entities/TextGenerationConfig';
20
- import type {
21
- ColorizationConfig
22
- } from '../../domain/entities/ColorizationConfig';
23
- import type {
24
- IFaceSwapService,
25
- IPhotoRestorationService,
26
- IImageEnhancementService,
27
- IStyleTransferService,
28
- IBackgroundRemovalService,
29
- ITextGenerationService,
30
- IColorizationService
31
- } from '../../domain/repositories/IAIPromptServices';
1
+ import { useState, useCallback, useMemo } from 'react';
32
2
  import type { ITemplateRepository } from '../../domain/repositories/ITemplateRepository';
33
3
  import type { IPromptHistoryRepository } from '../../domain/repositories/IPromptHistoryRepository';
34
4
  import type { GeneratedPrompt } from '../../domain/entities/GeneratedPrompt';
35
5
  import { createGeneratedPrompt } from '../../domain/entities/GeneratedPrompt';
36
6
  import { useAsyncState } from './useAsyncState';
37
- import type { AIPromptResult } from '../../domain/entities/types';
38
- import type { AIPromptTemplate } from '../../domain/entities/AIPromptTemplate';
7
+ import { AIServiceProcessor, type AIConfig, type AIServices } from '../../infrastructure/services/AIServiceProcessor';
39
8
 
40
- export type AIConfig =
41
- | { type: 'face-swap'; config: FaceSwapConfig }
42
- | { type: 'photo-restoration'; config: PhotoRestorationConfig }
43
- | { type: 'image-enhancement'; config: ImageEnhancementConfig }
44
- | { type: 'style-transfer'; config: StyleTransferConfig }
45
- | { type: 'background-removal'; config: BackgroundRemovalConfig }
46
- | { type: 'text-generation'; config: TextGenerationConfig }
47
- | { type: 'colorization'; config: ColorizationConfig };
9
+ export type { AIConfig };
48
10
 
49
11
  export interface UseAIServicesState {
50
12
  generatedPrompt: GeneratedPrompt | null;
@@ -60,15 +22,7 @@ export interface UseAIServicesActions {
60
22
  }
61
23
 
62
24
  export const useAIServices = (
63
- services: {
64
- faceSwap: IFaceSwapService;
65
- photoRestoration: IPhotoRestorationService;
66
- imageEnhancement: IImageEnhancementService;
67
- styleTransfer: IStyleTransferService;
68
- backgroundRemoval: IBackgroundRemovalService;
69
- textGeneration: ITextGenerationService;
70
- colorization: IColorizationService;
71
- },
25
+ services: AIServices,
72
26
  repositories: {
73
27
  template: ITemplateRepository;
74
28
  history: IPromptHistoryRepository;
@@ -85,110 +39,39 @@ export const useAIServices = (
85
39
 
86
40
  const [currentService, setCurrentService] = useState<string | null>(null);
87
41
 
42
+ const processor = useMemo(() => new AIServiceProcessor(services), [services]);
43
+
88
44
  const processRequest = useCallback(async (aiConfig: AIConfig): Promise<void> => {
89
45
  clearError();
90
46
  setCurrentService(aiConfig.type);
91
47
 
92
48
  try {
93
- let templateResult: AIPromptResult<AIPromptTemplate> | undefined;
94
- let promptResult: AIPromptResult<string> | undefined;
95
-
96
- switch (aiConfig.type) {
97
- case 'face-swap':
98
- templateResult = await services.faceSwap.generateTemplate(aiConfig.config);
99
- if (templateResult.success && templateResult.data) {
100
- promptResult = await services.faceSwap.generatePrompt(templateResult.data, aiConfig.config);
101
- }
102
- break;
103
-
104
- case 'photo-restoration':
105
- templateResult = await services.photoRestoration.generateTemplate(aiConfig.config);
106
- if (templateResult.success && templateResult.data) {
107
- promptResult = await services.photoRestoration.generatePrompt(templateResult.data, aiConfig.config);
108
- }
109
- break;
110
-
111
- case 'image-enhancement':
112
- templateResult = await services.imageEnhancement.generateTemplate(aiConfig.config);
113
- if (templateResult.success && templateResult.data) {
114
- promptResult = await services.imageEnhancement.generatePrompt(templateResult.data, aiConfig.config);
115
- }
116
- break;
117
-
118
- case 'style-transfer':
119
- templateResult = await services.styleTransfer.generateTemplate(aiConfig.config);
120
- if (templateResult.success && templateResult.data) {
121
- promptResult = await services.styleTransfer.generatePrompt(templateResult.data, aiConfig.config);
122
- }
123
- break;
124
-
125
- case 'background-removal':
126
- templateResult = await services.backgroundRemoval.generateTemplate(aiConfig.config);
127
- if (templateResult.success && templateResult.data) {
128
- promptResult = await services.backgroundRemoval.generatePrompt(templateResult.data, aiConfig.config);
129
- }
130
- break;
131
-
132
- case 'text-generation':
133
- templateResult = await services.textGeneration.generateTemplate(aiConfig.config);
134
- if (templateResult.success && templateResult.data) {
135
- promptResult = await services.textGeneration.generatePrompt(templateResult.data, aiConfig.config);
136
- }
137
- break;
138
-
139
- case 'colorization':
140
- templateResult = await services.colorization.generateTemplate(aiConfig.config);
141
- if (templateResult.success && templateResult.data) {
142
- promptResult = await services.colorization.generatePrompt(templateResult.data, aiConfig.config);
143
- }
144
- break;
145
-
146
- default:
147
- setError('Unknown AI service type');
148
- return;
149
- }
150
-
151
- if (!templateResult?.success || !templateResult.data) {
152
- setError('Failed to generate template');
153
- return;
154
- }
155
-
156
- if (!promptResult?.success || !promptResult.data) {
157
- setError('Failed to generate prompt');
158
- return;
159
- }
49
+ const result = await processor.process(aiConfig);
160
50
 
161
51
  const newPrompt = createGeneratedPrompt({
162
- templateId: templateResult.data.id,
163
- generatedText: promptResult.data,
164
- variables: aiConfig.config as unknown as Record<string, unknown>,
52
+ templateId: result.template.id,
53
+ generatedText: result.prompt,
54
+ variables: result.config,
165
55
  });
166
56
 
167
57
  await repositories.history.save(newPrompt);
168
58
  setGeneratedPrompt(newPrompt);
169
59
 
170
- } catch {
171
- setError('An unexpected error occurred');
60
+ } catch (err) {
61
+ setError(err instanceof Error ? err.message : 'An unexpected error occurred');
172
62
  } finally {
173
63
  setCurrentService(null);
174
64
  }
175
- }, [services, repositories, setError, setGeneratedPrompt, clearError]);
65
+ }, [processor, repositories, setError, setGeneratedPrompt, clearError]);
176
66
 
177
67
  const getAvailableStyles = useCallback(async (serviceType: string): Promise<string[]> => {
178
68
  try {
179
- switch (serviceType) {
180
- case 'face-swap':
181
- return await services.faceSwap.getAvailableStyles();
182
- case 'style-transfer':
183
- return await services.styleTransfer.getAvailableStyles();
184
- default:
185
- return [];
186
- }
69
+ return await processor.getAvailableStyles(serviceType);
187
70
  } catch {
188
71
  setError('Failed to load available styles');
189
72
  return [];
190
73
  }
191
- }, [services, setError]);
74
+ }, [processor, setError]);
192
75
 
193
76
  const clearPrompt = useCallback(() => {
194
77
  setGeneratedPrompt(null);
package/src/index.ts CHANGED
@@ -60,6 +60,24 @@ export type {
60
60
 
61
61
  export { DEFAULT_POLLING_CONFIG, DEFAULT_PROGRESS_STAGES, DEFAULT_QUEUE_CONFIG } from "./domain/entities";
62
62
 
63
+ // =============================================================================
64
+ // DOMAIN LAYER - Processing Modes
65
+ // =============================================================================
66
+
67
+ export type {
68
+ ImageProcessingMode,
69
+ ModeConfig,
70
+ ModeCatalog,
71
+ } from "./domain/entities/processing-modes.types";
72
+
73
+ export {
74
+ DEFAULT_PROCESSING_MODES,
75
+ getModeConfig,
76
+ getFreeModes,
77
+ getPremiumModes,
78
+ getPromptRequiredModes,
79
+ } from "./domain/constants/processing-modes.constants";
80
+
63
81
  // =============================================================================
64
82
  // INFRASTRUCTURE LAYER - Services
65
83
  // =============================================================================
@@ -9,15 +9,10 @@ import type {
9
9
  GenerationProgress,
10
10
  PollingConfig,
11
11
  } from "../../domain/entities";
12
- import { DEFAULT_POLLING_CONFIG } from "../../domain/entities";
13
- import type { IAIProvider, JobStatus } from "../../domain/interfaces";
14
- import { providerRegistry } from "./provider-registry.service";
15
- import {
16
- classifyError,
17
- isTransientError,
18
- } from "../utils/error-classifier.util";
19
- import { createPollingDelay } from "../utils/polling-interval.util";
20
- import { createProgressTracker } from "../utils/progress-calculator.util";
12
+ import { classifyError } from "../utils/error-classifier.util";
13
+ import { ProgressManager } from "./progress-manager";
14
+ import { JobPoller, type PollerConfig } from "./job-poller";
15
+ import { ProviderValidator } from "./provider-validator";
21
16
 
22
17
  declare const __DEV__: boolean;
23
18
 
@@ -27,7 +22,9 @@ export interface OrchestratorConfig {
27
22
  }
28
23
 
29
24
  class GenerationOrchestratorService {
30
- private config: OrchestratorConfig = {};
25
+ private progressManager = new ProgressManager();
26
+ private jobPoller = new JobPoller();
27
+ private providerValidator = new ProviderValidator();
31
28
 
32
29
  configure(config: OrchestratorConfig): void {
33
30
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -37,14 +34,19 @@ class GenerationOrchestratorService {
37
34
  hasStatusUpdate: !!config.onStatusUpdate,
38
35
  });
39
36
  }
40
- this.config = { ...this.config, ...config };
37
+
38
+ const pollerConfig: PollerConfig = {
39
+ polling: config.polling,
40
+ onStatusUpdate: config.onStatusUpdate,
41
+ };
42
+
43
+ this.jobPoller.configure(pollerConfig);
41
44
  }
42
45
 
43
46
  async generate<T = unknown>(
44
47
  request: GenerationRequest,
45
48
  ): Promise<GenerationResult<T>> {
46
- const provider = this.getProvider();
47
- const progressTracker = createProgressTracker();
49
+ const provider = this.providerValidator.getProvider();
48
50
  const startTime = Date.now();
49
51
 
50
52
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -60,11 +62,7 @@ class GenerationOrchestratorService {
60
62
  stage: GenerationProgress["stage"],
61
63
  subProgress = 0,
62
64
  ) => {
63
- const progress = progressTracker.setStatus(stage);
64
- request.onProgress?.({
65
- stage,
66
- progress: progress + subProgress,
67
- });
65
+ this.progressManager.updateProgress(stage, subProgress, request.onProgress);
68
66
  };
69
67
 
70
68
  try {
@@ -84,11 +82,19 @@ class GenerationOrchestratorService {
84
82
 
85
83
  updateProgress("generating");
86
84
 
87
- const result = await this.pollForResult<T>(
85
+ const result = await this.jobPoller.pollForResult<T>(
88
86
  provider,
89
87
  request.model,
90
88
  submission.requestId,
91
89
  request.onProgress,
90
+ (status, attempt, config) => {
91
+ this.progressManager.updateProgressFromStatus(
92
+ status,
93
+ attempt,
94
+ config,
95
+ request.onProgress,
96
+ );
97
+ },
92
98
  );
93
99
 
94
100
  updateProgress("completed");
@@ -139,149 +145,6 @@ class GenerationOrchestratorService {
139
145
  };
140
146
  }
141
147
  }
142
-
143
- private async pollForResult<T>(
144
- provider: IAIProvider,
145
- model: string,
146
- requestId: string,
147
- onProgress?: (progress: GenerationProgress) => void,
148
- ): Promise<T> {
149
- if (typeof __DEV__ !== "undefined" && __DEV__) {
150
- // eslint-disable-next-line no-console
151
- console.log("[Orchestrator] pollForResult() started", {
152
- provider: provider.providerId,
153
- model,
154
- requestId,
155
- });
156
- }
157
-
158
- const config = {
159
- ...DEFAULT_POLLING_CONFIG,
160
- ...this.config.polling,
161
- };
162
-
163
- let consecutiveErrors = 0;
164
-
165
- for (let attempt = 0; attempt < config.maxAttempts; attempt++) {
166
- await createPollingDelay(attempt, config);
167
-
168
- if (typeof __DEV__ !== "undefined" && __DEV__ && attempt % 5 === 0) {
169
- // eslint-disable-next-line no-console
170
- console.log("[Orchestrator] pollForResult() attempt", {
171
- attempt,
172
- maxAttempts: config.maxAttempts,
173
- });
174
- }
175
-
176
- try {
177
- const status = await provider.getJobStatus(model, requestId);
178
-
179
- consecutiveErrors = 0;
180
-
181
- this.updateProgressFromStatus(status, attempt, config, onProgress);
182
-
183
- if (status.status === "COMPLETED") {
184
- if (typeof __DEV__ !== "undefined" && __DEV__) {
185
- // eslint-disable-next-line no-console
186
- console.log("[Orchestrator] pollForResult() job COMPLETED", {
187
- requestId,
188
- attempt,
189
- });
190
- }
191
- return provider.getJobResult<T>(model, requestId);
192
- }
193
-
194
- if (status.status === "FAILED") {
195
- throw new Error("Job failed on provider");
196
- }
197
-
198
- await this.config.onStatusUpdate?.(requestId, status.status);
199
- } catch (error) {
200
- if (isTransientError(error)) {
201
- consecutiveErrors++;
202
-
203
- if (consecutiveErrors >= config.maxConsecutiveErrors) {
204
- throw error;
205
- }
206
-
207
- continue;
208
- }
209
-
210
- throw error;
211
- }
212
- }
213
-
214
- throw new Error(
215
- `Polling timeout after ${config.maxAttempts} attempts`,
216
- );
217
- }
218
-
219
- private updateProgressFromStatus(
220
- status: JobStatus,
221
- attempt: number,
222
- config: PollingConfig,
223
- onProgress?: (progress: GenerationProgress) => void,
224
- ): void {
225
- const baseProgress = 25;
226
- const maxProgress = 85;
227
- const range = maxProgress - baseProgress;
228
-
229
- let progress: number;
230
-
231
- if (status.status === "IN_QUEUE") {
232
- progress = baseProgress + range * 0.2;
233
- } else if (status.status === "IN_PROGRESS") {
234
- const ratio = Math.min(attempt / (config.maxAttempts * 0.7), 1);
235
- progress = baseProgress + range * (0.2 + 0.6 * ratio);
236
- } else {
237
- progress = baseProgress;
238
- }
239
-
240
- onProgress?.({
241
- stage: "generating",
242
- progress: Math.round(progress),
243
- eta: status.eta,
244
- });
245
- }
246
-
247
- private getProvider(): IAIProvider {
248
- if (typeof __DEV__ !== "undefined" && __DEV__) {
249
- // eslint-disable-next-line no-console
250
- console.log("[Orchestrator] getProvider() called");
251
- }
252
-
253
- const provider = providerRegistry.getActiveProvider();
254
-
255
- if (!provider) {
256
- if (typeof __DEV__ !== "undefined" && __DEV__) {
257
- // eslint-disable-next-line no-console
258
- console.error("[Orchestrator] No active provider found!");
259
- }
260
- throw new Error(
261
- "No active AI provider. Register and set a provider first.",
262
- );
263
- }
264
-
265
- if (!provider.isInitialized()) {
266
- if (typeof __DEV__ !== "undefined" && __DEV__) {
267
- // eslint-disable-next-line no-console
268
- console.error("[Orchestrator] Provider not initialized:", provider.providerId);
269
- }
270
- throw new Error(
271
- `Provider ${provider.providerId} is not initialized.`,
272
- );
273
- }
274
-
275
- if (typeof __DEV__ !== "undefined" && __DEV__) {
276
- // eslint-disable-next-line no-console
277
- console.log("[Orchestrator] getProvider() returning:", {
278
- providerId: provider.providerId,
279
- isInitialized: provider.isInitialized(),
280
- });
281
- }
282
-
283
- return provider;
284
- }
285
148
  }
286
149
 
287
150
  export const generationOrchestrator = new GenerationOrchestratorService();
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Job Poller
3
+ * Handles polling logic for job status
4
+ */
5
+
6
+ import type { IAIProvider, JobStatus } from "../../domain/interfaces";
7
+ import { DEFAULT_POLLING_CONFIG, type PollingConfig, type GenerationProgress } from "../../domain/entities";
8
+ import { isTransientError } from "../utils/error-classifier.util";
9
+ import { createPollingDelay } from "../utils/polling-interval.util";
10
+
11
+ declare const __DEV__: boolean;
12
+
13
+ export interface PollerConfig {
14
+ polling?: Partial<PollingConfig>;
15
+ onStatusUpdate?: (requestId: string, status: string) => Promise<void>;
16
+ }
17
+
18
+ export class JobPoller {
19
+ private config: PollerConfig = {};
20
+
21
+ configure(config: PollerConfig): void {
22
+ this.config = { ...this.config, ...config };
23
+ }
24
+
25
+ async pollForResult<T>(
26
+ provider: IAIProvider,
27
+ model: string,
28
+ requestId: string,
29
+ onProgress?: (progress: GenerationProgress) => void,
30
+ onStatusUpdate?: (status: JobStatus, attempt: number, config: PollingConfig) => void,
31
+ ): Promise<T> {
32
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
33
+ // eslint-disable-next-line no-console
34
+ console.log("[JobPoller] pollForResult() started", {
35
+ provider: provider.providerId,
36
+ model,
37
+ requestId,
38
+ });
39
+ }
40
+
41
+ const config = {
42
+ ...DEFAULT_POLLING_CONFIG,
43
+ ...this.config.polling,
44
+ };
45
+
46
+ let consecutiveErrors = 0;
47
+
48
+ for (let attempt = 0; attempt < config.maxAttempts; attempt++) {
49
+ await createPollingDelay(attempt, config);
50
+
51
+ if (typeof __DEV__ !== "undefined" && __DEV__ && attempt % 5 === 0) {
52
+ // eslint-disable-next-line no-console
53
+ console.log("[JobPoller] pollForResult() attempt", {
54
+ attempt,
55
+ maxAttempts: config.maxAttempts,
56
+ });
57
+ }
58
+
59
+ try {
60
+ const status = await provider.getJobStatus(model, requestId);
61
+
62
+ consecutiveErrors = 0;
63
+
64
+ onStatusUpdate?.(status, attempt, config);
65
+
66
+ if (status.status === "COMPLETED") {
67
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
68
+ // eslint-disable-next-line no-console
69
+ console.log("[JobPoller] pollForResult() job COMPLETED", {
70
+ requestId,
71
+ attempt,
72
+ });
73
+ }
74
+ return provider.getJobResult<T>(model, requestId);
75
+ }
76
+
77
+ if (status.status === "FAILED") {
78
+ throw new Error("Job failed on provider");
79
+ }
80
+
81
+ await this.config.onStatusUpdate?.(requestId, status.status);
82
+ } catch (error) {
83
+ if (isTransientError(error)) {
84
+ consecutiveErrors++;
85
+
86
+ if (consecutiveErrors >= config.maxConsecutiveErrors) {
87
+ throw error;
88
+ }
89
+
90
+ continue;
91
+ }
92
+
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ throw new Error(
98
+ `Polling timeout after ${config.maxAttempts} attempts`,
99
+ );
100
+ }
101
+ }
102
+
103
+ export const jobPoller = new JobPoller();
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Progress Manager
3
+ * Handles progress tracking and updates during generation
4
+ */
5
+
6
+ import type { GenerationProgress, PollingConfig } from "../../domain/entities";
7
+ import type { JobStatus } from "../../domain/interfaces";
8
+ import { createProgressTracker } from "../utils/progress-calculator.util";
9
+
10
+ export class ProgressManager {
11
+ private progressTracker = createProgressTracker();
12
+
13
+ updateProgress(
14
+ stage: GenerationProgress["stage"],
15
+ subProgress: number,
16
+ onProgress?: (progress: GenerationProgress) => void,
17
+ ): void {
18
+ const progress = this.progressTracker.setStatus(stage);
19
+ onProgress?.({
20
+ stage,
21
+ progress: progress + subProgress,
22
+ });
23
+ }
24
+
25
+ updateProgressFromStatus(
26
+ status: JobStatus,
27
+ attempt: number,
28
+ config: PollingConfig,
29
+ onProgress?: (progress: GenerationProgress) => void,
30
+ ): void {
31
+ const baseProgress = 25;
32
+ const maxProgress = 85;
33
+ const range = maxProgress - baseProgress;
34
+
35
+ let progress: number;
36
+
37
+ if (status.status === "IN_QUEUE") {
38
+ progress = baseProgress + range * 0.2;
39
+ } else if (status.status === "IN_PROGRESS") {
40
+ const ratio = Math.min(attempt / (config.maxAttempts * 0.7), 1);
41
+ progress = baseProgress + range * (0.2 + 0.6 * ratio);
42
+ } else {
43
+ progress = baseProgress;
44
+ }
45
+
46
+ onProgress?.({
47
+ stage: "generating",
48
+ progress: Math.round(progress),
49
+ eta: status.eta,
50
+ });
51
+ }
52
+
53
+ reset(): void {
54
+ this.progressTracker = createProgressTracker();
55
+ }
56
+ }
57
+
58
+ export const progressManager = new ProgressManager();
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Provider Validator
3
+ * Validates provider availability and initialization
4
+ */
5
+
6
+ import type { IAIProvider } from "../../domain/interfaces";
7
+ import { providerRegistry } from "./provider-registry.service";
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ export class ProviderValidator {
12
+ getProvider(): IAIProvider {
13
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
14
+ // eslint-disable-next-line no-console
15
+ console.log("[ProviderValidator] getProvider() called");
16
+ }
17
+
18
+ const provider = providerRegistry.getActiveProvider();
19
+
20
+ if (!provider) {
21
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
22
+ // eslint-disable-next-line no-console
23
+ console.error("[ProviderValidator] No active provider found!");
24
+ }
25
+ throw new Error(
26
+ "No active AI provider. Register and set a provider first.",
27
+ );
28
+ }
29
+
30
+ if (!provider.isInitialized()) {
31
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
32
+ // eslint-disable-next-line no-console
33
+ console.error("[ProviderValidator] Provider not initialized:", provider.providerId);
34
+ }
35
+ throw new Error(
36
+ `Provider ${provider.providerId} is not initialized.`,
37
+ );
38
+ }
39
+
40
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
41
+ // eslint-disable-next-line no-console
42
+ console.log("[ProviderValidator] getProvider() returning:", {
43
+ providerId: provider.providerId,
44
+ isInitialized: provider.isInitialized(),
45
+ });
46
+ }
47
+
48
+ return provider;
49
+ }
50
+ }
51
+
52
+ export const providerValidator = new ProviderValidator();