@umituz/react-native-ai-generation-content 1.26.29 → 1.26.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.26.29",
3
+ "version": "1.26.31",
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,32 @@
1
+ /**
2
+ * Wizard Strategy Constants
3
+ * Centralized configuration values for wizard-based generation
4
+ */
5
+
6
+ import type { VideoFeatureType } from "../../../../../domain/interfaces";
7
+
8
+ /** Generation timeout in milliseconds (2 minutes) */
9
+ export const GENERATION_TIMEOUT_MS = 120000;
10
+
11
+ /** Base64 image format prefix */
12
+ export const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
13
+
14
+ /** Photo key prefix in wizard data */
15
+ export const PHOTO_KEY_PREFIX = "photo_";
16
+
17
+ /** Default style value (no custom style applied) */
18
+ export const DEFAULT_STYLE_VALUE = "original";
19
+
20
+ /** Model input defaults */
21
+ export const MODEL_INPUT_DEFAULTS = {
22
+ aspectRatio: "1:1",
23
+ outputFormat: "jpeg",
24
+ numImages: 1,
25
+ enableSafetyChecker: false,
26
+ } as const;
27
+
28
+ /** Video feature type patterns for scenario detection */
29
+ export const VIDEO_FEATURE_PATTERNS: Record<string, VideoFeatureType> = {
30
+ kiss: "ai-kiss",
31
+ hug: "ai-hug",
32
+ };
@@ -10,6 +10,14 @@ import type { VideoFeatureType } from "../../../../../domain/interfaces";
10
10
  import { executeVideoFeature } from "../../../../../infrastructure/services/video-feature-executor.service";
11
11
  import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
12
12
  import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
13
+ import {
14
+ GENERATION_TIMEOUT_MS,
15
+ BASE64_IMAGE_PREFIX,
16
+ PHOTO_KEY_PREFIX,
17
+ DEFAULT_STYLE_VALUE,
18
+ MODEL_INPUT_DEFAULTS,
19
+ VIDEO_FEATURE_PATTERNS,
20
+ } from "./wizard-strategy.constants";
13
21
 
14
22
  declare const __DEV__: boolean;
15
23
 
@@ -56,10 +64,8 @@ async function executeImageGeneration(
56
64
  }
57
65
 
58
66
  try {
59
- onProgress?.(5);
60
-
61
67
  const formatBase64 = (base64: string): string => {
62
- return base64.startsWith("data:") ? base64 : `data:image/jpeg;base64,${base64}`;
68
+ return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
63
69
  };
64
70
 
65
71
  const imageUrls = input.photos.map(formatBase64);
@@ -68,17 +74,15 @@ async function executeImageGeneration(
68
74
  return { success: false, error: "At least one image required" };
69
75
  }
70
76
 
71
- onProgress?.(10);
72
-
73
77
  const enhancedPrompt = `Create a photorealistic image featuring the exact two people from the provided photos. Use the person from @image1 and the person from @image2 exactly as they appear in the reference images - maintain their facial features, expressions, and identity. ${input.prompt}. Professional photography, high quality, detailed, natural lighting, photorealistic rendering.`;
74
78
 
75
79
  const modelInput = {
76
80
  image_urls: imageUrls,
77
81
  prompt: enhancedPrompt,
78
- aspect_ratio: "1:1",
79
- output_format: "jpeg",
80
- num_images: 1,
81
- enable_safety_checker: false,
82
+ aspect_ratio: MODEL_INPUT_DEFAULTS.aspectRatio,
83
+ output_format: MODEL_INPUT_DEFAULTS.outputFormat,
84
+ num_images: MODEL_INPUT_DEFAULTS.numImages,
85
+ enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
82
86
  };
83
87
 
84
88
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -92,17 +96,13 @@ async function executeImageGeneration(
92
96
 
93
97
  let lastStatus = "";
94
98
  const result = await provider.subscribe(model, modelInput, {
95
- timeoutMs: 120000,
99
+ timeoutMs: GENERATION_TIMEOUT_MS,
96
100
  onQueueUpdate: (status) => {
97
101
  if (status.status === lastStatus) return;
98
102
  lastStatus = status.status;
99
- if (status.status === "IN_QUEUE") onProgress?.(20);
100
- else if (status.status === "IN_PROGRESS") onProgress?.(50);
101
103
  },
102
104
  });
103
105
 
104
- onProgress?.(90);
105
-
106
106
  const rawResult = result as Record<string, unknown>;
107
107
  const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
108
108
  const imageUrl = data?.images?.[0]?.url;
@@ -129,7 +129,7 @@ async function extractPhotosFromWizardData(
129
129
  ): Promise<string[] | null> {
130
130
  // Find ALL photo keys dynamically (photo_1, photo_2, photo_3, ...)
131
131
  const photoKeys = Object.keys(wizardData)
132
- .filter((k) => k.includes("photo_"))
132
+ .filter((k) => k.includes(PHOTO_KEY_PREFIX))
133
133
  .sort(); // Sort to maintain order (photo_1, photo_2, ...)
134
134
 
135
135
  if (photoKeys.length === 0) {
@@ -214,13 +214,13 @@ async function buildImageGenerationInput(
214
214
 
215
215
  // Art style (single select)
216
216
  const artStyle = wizardData.selection_art_style as string | undefined;
217
- if (artStyle && artStyle !== "original") {
217
+ if (artStyle && artStyle !== DEFAULT_STYLE_VALUE) {
218
218
  styleEnhancements.push(`Art style: ${artStyle}`);
219
219
  }
220
220
 
221
221
  // Artist style (single select)
222
222
  const artist = wizardData.selection_artist_style as string | undefined;
223
- if (artist && artist !== "original") {
223
+ if (artist && artist !== DEFAULT_STYLE_VALUE) {
224
224
  styleEnhancements.push(`Artist style: ${artist}`);
225
225
  }
226
226
 
@@ -296,13 +296,13 @@ async function buildGenerationInput(
296
296
  function getVideoFeatureType(scenarioId: string): VideoFeatureType {
297
297
  const id = scenarioId.toLowerCase();
298
298
 
299
- if (id.includes("kiss")) return "ai-kiss";
300
- if (id.includes("hug")) return "ai-hug";
301
-
302
- if (typeof __DEV__ !== "undefined" && __DEV__) {
303
- console.warn(`[WizardStrategy] Unknown scenario type "${scenarioId}", defaulting to ai-hug`);
299
+ for (const [pattern, featureType] of Object.entries(VIDEO_FEATURE_PATTERNS)) {
300
+ if (id.includes(pattern)) {
301
+ return featureType;
302
+ }
304
303
  }
305
- return "ai-hug";
304
+
305
+ throw new Error(`Unknown video feature type for scenario "${scenarioId}". Add pattern to VIDEO_FEATURE_PATTERNS.`);
306
306
  }
307
307
 
308
308
  // ============================================================================
package/src/index.ts CHANGED
@@ -35,12 +35,12 @@ export {
35
35
  } from "./infrastructure/config";
36
36
 
37
37
  export {
38
- providerRegistry, generationOrchestrator, pollJob, createJobPoller, generationWrapper,
39
- createGenerationWrapper, executeImageFeature, hasImageFeatureSupport, executeVideoFeature, hasVideoFeatureSupport,
38
+ providerRegistry, generationOrchestrator, pollJob, createJobPoller,
39
+ executeImageFeature, hasImageFeatureSupport, executeVideoFeature, hasVideoFeatureSupport,
40
40
  } from "./infrastructure/services";
41
41
 
42
42
  export type {
43
- OrchestratorConfig, PollJobOptions, PollJobResult, WrapperConfig, ImageResultExtractor,
43
+ OrchestratorConfig, PollJobOptions, PollJobResult, ImageResultExtractor,
44
44
  ExecuteImageFeatureOptions, ImageFeatureResult, ImageFeatureRequest,
45
45
  ExecuteVideoFeatureOptions, VideoFeatureResult, VideoFeatureRequest,
46
46
  } from "./infrastructure/services";
@@ -50,26 +50,23 @@ export type { CreditCheckConfig, HistoryConfig, HistoryEntry } from "./infrastru
50
50
 
51
51
  export {
52
52
  classifyError, isTransientError, isPermanentError, isResultNotReady, calculatePollingInterval,
53
- createPollingDelay, getProgressForStatus, interpolateProgress, createProgressTracker,
54
- calculatePollingProgress, checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
53
+ createPollingDelay, checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
55
54
  validateResult, extractOutputUrl, extractOutputUrls, extractVideoUrl, extractThumbnailUrl,
56
55
  extractAudioUrl, extractImageUrls, cleanBase64, addBase64Prefix, preparePhoto, preparePhotos,
57
56
  isValidBase64, getBase64Size, getBase64SizeMB, prepareImage, createDevCallbacks, createFeatureUtils,
58
57
  showVideoGenerationSuccess, handleGenerationError, showContentModerationWarning,
59
58
  saveMediaToGallery, shareMedia, createSaveHandler, createShareHandler, createMediaHandlers,
59
+ mapJobStatusToGenerationStatus, enhancePromptWithLanguage, getSupportedLanguages, getLanguageName,
60
60
  } from "./infrastructure/utils";
61
61
 
62
62
  export { distinctBy } from "./utils/arrayUtils";
63
63
 
64
64
  export type {
65
- IntervalOptions, ProgressOptions, StatusCheckResult, ResultValidation, ValidateResultOptions,
65
+ IntervalOptions, StatusCheckResult, ResultValidation, ValidateResultOptions,
66
66
  PhotoInput, PreparedImage, ImageSelector, VideoSaver, AlertFunction, FeatureUtilsConfig, VideoAlertFunction,
67
67
  MediaActionResult, MediaActionTranslations, ToastConfig,
68
68
  } from "./infrastructure/utils";
69
69
 
70
- export { enhancePromptWithLanguage, getSupportedLanguages, getLanguageName, ModerationWrapper, generateSynchronously } from "./infrastructure/wrappers";
71
- export type { ModerationResult, ModerationConfig, SynchronousGenerationInput, SynchronousGenerationConfig } from "./infrastructure/wrappers";
72
-
73
70
  export {
74
71
  useGeneration, usePendingJobs, useBackgroundGeneration,
75
72
  useGenerationFlow, useAIFeatureCallbacks,
@@ -2,10 +2,8 @@
2
2
  * Infrastructure Constants
3
3
  */
4
4
 
5
- export {
6
- IMAGE_PROGRESS,
7
- VIDEO_PROGRESS,
8
- POLLING_PROGRESS,
9
- VIDEO_TIMEOUT_MS,
10
- MAX_TRANSIENT_ERRORS,
11
- } from "./progress.constants";
5
+ /** Video generation timeout in milliseconds (5 minutes) */
6
+ export const VIDEO_TIMEOUT_MS = 300000;
7
+
8
+ /** Maximum consecutive transient errors before failing */
9
+ export const MAX_TRANSIENT_ERRORS = 5;
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * Generation Orchestrator Service
3
3
  * Provider-agnostic AI generation workflow
4
+ * Reports only real status - no fake progress
4
5
  */
5
6
 
6
7
  import type {
7
8
  GenerationRequest,
8
9
  GenerationResult,
9
- GenerationProgress,
10
10
  PollingConfig,
11
11
  } from "../../domain/entities";
12
12
  import { classifyError } from "../utils/error-classifier.util";
13
- import { ProgressManager } from "./progress-manager";
14
13
  import { pollJob } from "./job-poller.service";
15
14
  import { ProviderValidator } from "./provider-validator";
16
15
 
@@ -22,7 +21,6 @@ export interface OrchestratorConfig {
22
21
  }
23
22
 
24
23
  class GenerationOrchestratorService {
25
- private progressManager = new ProgressManager();
26
24
  private providerValidator = new ProviderValidator();
27
25
  private pollingConfig?: Partial<PollingConfig>;
28
26
  private onStatusUpdateCallback?: (requestId: string, status: string) => Promise<void>;
@@ -53,20 +51,9 @@ class GenerationOrchestratorService {
53
51
  });
54
52
  }
55
53
 
56
- const updateProgress = (
57
- stage: GenerationProgress["stage"],
58
- subProgress = 0,
59
- ) => {
60
- this.progressManager.updateProgress(stage, subProgress, request.onProgress);
61
- };
62
-
63
54
  try {
64
- updateProgress("preparing");
65
-
66
55
  const submission = await provider.submitJob(request.model, request.input);
67
56
 
68
- updateProgress("submitting");
69
-
70
57
  if (__DEV__) {
71
58
  console.log("[Orchestrator] Job submitted:", {
72
59
  requestId: submission.requestId,
@@ -74,33 +61,17 @@ class GenerationOrchestratorService {
74
61
  });
75
62
  }
76
63
 
77
- updateProgress("generating");
78
-
79
64
  const pollResult = await pollJob<T>({
80
65
  provider,
81
66
  model: request.model,
82
67
  requestId: submission.requestId,
83
68
  config: this.pollingConfig,
84
69
  onStatusChange: async (status) => {
85
- if (this.onStatusUpdateCallback) {
86
- await this.onStatusUpdateCallback(submission.requestId, status.status);
87
- }
70
+ if (this.onStatusUpdateCallback) {
71
+ await this.onStatusUpdateCallback(submission.requestId, status.status);
72
+ }
88
73
  },
89
- onProgress: (progress: number) => {
90
- // We map polling progress (0-100) to our "generating" stage progress
91
- // Since we can't easily access the internal 'status' and 'attempt' here nicely without changing pollJob signature to pass them to onProgress
92
- // But pollJob onProgress passes a number.
93
- // The original code used: this.progressManager.updateProgressFromStatus(status, attempt, config...)
94
- // pollJob calculates progress internally.
95
- // We can just use the numeric progress directly for the converting.
96
-
97
- // Actually, the original code had access to `status` object inside the callback.
98
- // pollJob abstracts that away.
99
- // However, progressManager.updateProgress takes (stage, subProgress).
100
- // So we can just pass 'generating' and the percentage.
101
-
102
- this.progressManager.updateProgress("generating", progress, request.onProgress);
103
- }
74
+ onProgress: request.onProgress,
104
75
  });
105
76
 
106
77
  if (!pollResult.success) {
@@ -108,9 +79,6 @@ class GenerationOrchestratorService {
108
79
  }
109
80
 
110
81
  const result = pollResult.data as T;
111
-
112
- updateProgress("completed");
113
-
114
82
  const duration = Date.now() - startTime;
115
83
 
116
84
  if (__DEV__) {
@@ -7,7 +7,6 @@
7
7
  import { providerRegistry } from "./provider-registry.service";
8
8
  import { cleanBase64, extractErrorMessage } from "../utils";
9
9
  import { extractImageResult } from "../utils/url-extractor";
10
- import { IMAGE_PROGRESS } from "../constants";
11
10
  import type { ImageResultExtractor } from "../utils/url-extractor";
12
11
  import type { ImageFeatureType, ImageFeatureInputData } from "../../domain/interfaces";
13
12
 
@@ -69,8 +68,6 @@ export async function executeImageFeature(
69
68
  }
70
69
 
71
70
  try {
72
- onProgress?.(IMAGE_PROGRESS.START);
73
-
74
71
  const inputData: ImageFeatureInputData = {
75
72
  imageBase64: request.imageBase64 ? cleanBase64(request.imageBase64) : "",
76
73
  targetImageBase64: request.targetImageBase64
@@ -80,20 +77,13 @@ export async function executeImageFeature(
80
77
  options: request.options,
81
78
  };
82
79
 
83
- onProgress?.(IMAGE_PROGRESS.INPUT_PREPARED);
84
-
85
80
  const input = provider.buildImageFeatureInput(featureType, inputData);
86
-
87
- onProgress?.(IMAGE_PROGRESS.REQUEST_SENT);
88
-
89
81
  const result = await provider.run(model, input);
90
82
 
91
- onProgress?.(IMAGE_PROGRESS.RESULT_RECEIVED);
92
-
93
83
  const extractor = extractResult ?? extractImageResult;
94
84
  const imageUrl = extractor(result);
95
85
 
96
- onProgress?.(IMAGE_PROGRESS.COMPLETE);
86
+ onProgress?.(100);
97
87
 
98
88
  if (!imageUrl) {
99
89
  return { success: false, error: "No image in response" };
@@ -7,11 +7,6 @@ export { generationOrchestrator } from "./generation-orchestrator.service";
7
7
  export type { OrchestratorConfig } from "./generation-orchestrator.service";
8
8
  export { pollJob, createJobPoller } from "./job-poller.service";
9
9
  export type { PollJobOptions, PollJobResult } from "./job-poller.service";
10
- export {
11
- generationWrapper,
12
- createGenerationWrapper,
13
- } from "./generation-wrapper.service";
14
- export type { WrapperConfig } from "./generation-wrapper.service";
15
10
  export {
16
11
  executeImageFeature,
17
12
  hasImageFeatureSupport,
@@ -1,21 +1,15 @@
1
1
  /**
2
2
  * Job Poller Service
3
3
  * Provider-agnostic job polling with exponential backoff
4
+ * Reports only real status - no fake progress
4
5
  */
5
6
 
6
- import type { IAIProvider } from "../../domain/interfaces"; // eslint-disable-line @typescript-eslint/no-unused-vars
7
- import type { PollingConfig } from "../../domain/entities"; // eslint-disable-line @typescript-eslint/no-unused-vars
8
7
  import { DEFAULT_POLLING_CONFIG } from "../../domain/entities";
9
8
  import { calculatePollingInterval } from "../utils/polling-interval.util";
10
9
  import { checkStatusForErrors, isJobComplete } from "../utils/status-checker.util";
11
10
  import { validateResult } from "../utils/result-validator.util";
12
11
  import { isTransientError } from "../utils/error-classifier.util";
13
- import { calculateProgressFromJobStatus } from "../utils/progress-calculator.util";
14
12
  import type { PollJobOptions, PollJobResult } from "./job-poller.types";
15
- import { createJobPoller } from "./job-poller-factory"; // eslint-disable-line @typescript-eslint/no-unused-vars
16
-
17
- // IAIProvider and PollingConfig are used indirectly through provider methods
18
- // createJobPoller is re-exported
19
13
 
20
14
  declare const __DEV__: boolean;
21
15
 
@@ -23,6 +17,7 @@ const MAX_CONSECUTIVE_TRANSIENT_ERRORS = 5;
23
17
 
24
18
  /**
25
19
  * Poll job until completion with exponential backoff
20
+ * Only reports 100% on actual completion
26
21
  */
27
22
  export async function pollJob<T = unknown>(
28
23
  options: PollJobOptions,
@@ -42,7 +37,6 @@ export async function pollJob<T = unknown>(
42
37
 
43
38
  const startTime = Date.now();
44
39
  let consecutiveTransientErrors = 0;
45
- let lastProgress = 0;
46
40
 
47
41
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
48
42
  if (signal?.aborted) {
@@ -76,15 +70,7 @@ export async function pollJob<T = unknown>(
76
70
 
77
71
  consecutiveTransientErrors = 0;
78
72
 
79
- const progress = calculateProgressFromJobStatus(status, attempt, maxAttempts);
80
- if (progress > lastProgress) {
81
- lastProgress = progress;
82
- onProgress?.(progress);
83
- }
84
-
85
73
  if (isJobComplete(status)) {
86
- onProgress?.(90);
87
-
88
74
  const result = await provider.getJobResult<T>(model, requestId);
89
75
 
90
76
  const validation = validateResult(result);
@@ -7,7 +7,7 @@
7
7
  import { providerRegistry } from "./provider-registry.service";
8
8
  import { cleanBase64, extractErrorMessage } from "../utils";
9
9
  import { extractVideoResult } from "../utils/url-extractor";
10
- import { VIDEO_PROGRESS, VIDEO_TIMEOUT_MS } from "../constants";
10
+ import { VIDEO_TIMEOUT_MS } from "../constants";
11
11
  import type { VideoFeatureType, VideoFeatureInputData } from "../../domain/interfaces";
12
12
  import type {
13
13
  ExecuteVideoFeatureOptions,
@@ -36,7 +36,7 @@ export async function executeVideoFeature(
36
36
  return { success: false, error: "AI provider not initialized" };
37
37
  }
38
38
 
39
- const { extractResult, onProgress } = options ?? {};
39
+ const { extractResult, onProgress, onStatusChange } = options ?? {};
40
40
 
41
41
  const model = provider.getVideoFeatureModel(featureType);
42
42
 
@@ -45,8 +45,6 @@ export async function executeVideoFeature(
45
45
  }
46
46
 
47
47
  try {
48
- onProgress?.(VIDEO_PROGRESS.START);
49
-
50
48
  const inputData: VideoFeatureInputData = {
51
49
  sourceImageBase64: cleanBase64(request.sourceImageBase64),
52
50
  targetImageBase64: cleanBase64(request.targetImageBase64),
@@ -54,36 +52,26 @@ export async function executeVideoFeature(
54
52
  options: request.options,
55
53
  };
56
54
 
57
- onProgress?.(VIDEO_PROGRESS.INPUT_PREPARED);
58
-
59
55
  const input = provider.buildVideoFeatureInput(featureType, inputData);
60
56
 
61
- onProgress?.(VIDEO_PROGRESS.REQUEST_SENT);
62
-
63
57
  const result = await provider.subscribe(model, input, {
64
58
  timeoutMs: VIDEO_TIMEOUT_MS,
65
59
  onQueueUpdate: (status) => {
66
60
  if (__DEV__) {
67
- console.log(`[Video:${featureType}] Queue update:`, status.status);
68
- }
69
- if (status.status === "IN_QUEUE") {
70
- onProgress?.(VIDEO_PROGRESS.IN_QUEUE);
71
- } else if (status.status === "IN_PROGRESS") {
72
- onProgress?.(VIDEO_PROGRESS.IN_PROGRESS);
61
+ console.log(`[Video:${featureType}] Queue status:`, status.status);
73
62
  }
63
+ onStatusChange?.(status.status);
74
64
  },
75
65
  });
76
66
 
77
- onProgress?.(VIDEO_PROGRESS.RESULT_RECEIVED);
78
-
79
67
  const extractor = extractResult ?? extractVideoResult;
80
68
  const videoUrl = extractor(result);
81
69
 
82
- onProgress?.(VIDEO_PROGRESS.COMPLETE);
70
+ onProgress?.(100);
83
71
 
84
72
  if (!videoUrl) {
85
73
  if (__DEV__) {
86
- console.log(`[Video:${featureType}] No video URL found in result:`, JSON.stringify(result, null, 2));
74
+ console.log(`[Video:${featureType}] No video URL found in result`);
87
75
  }
88
76
  return { success: false, error: "No video in response" };
89
77
  }
@@ -10,6 +10,7 @@ import type { VideoResultExtractor } from "../utils/url-extractor";
10
10
  export interface ExecuteVideoFeatureOptions {
11
11
  extractResult?: VideoResultExtractor;
12
12
  onProgress?: (progress: number) => void;
13
+ onStatusChange?: (status: string) => void;
13
14
  }
14
15
 
15
16
  /**
@@ -13,3 +13,4 @@ export * from "./photo-generation";
13
13
  export * from "./feature-utils";
14
14
  export * from "./video-helpers";
15
15
  export * from "./media-actions.util";
16
+ export * from "./language.util";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Language Wrapper
2
+ * Language Utilities
3
3
  * Enhances prompts with language instructions for multi-language support
4
4
  */
5
5
 
@@ -1,111 +1,19 @@
1
1
  /**
2
2
  * Progress Calculator Utility
3
- * Calculates progress based on generation stage
4
- * Maps provider status to generation progress
3
+ * Maps provider status to generation status
4
+ * Only reports real status - no fake progress calculations
5
5
  */
6
6
 
7
7
  import type { GenerationStatus } from "../../domain/entities";
8
- import {
9
- DEFAULT_PROGRESS_STAGES,
10
- type ProgressStageConfig,
11
- } from "../../domain/entities";
12
8
  import type { AIJobStatusType } from "../../domain/interfaces/ai-provider.interface";
13
- import type { JobStatus } from "../../domain/interfaces";
14
-
15
- export interface ProgressOptions {
16
- status: GenerationStatus;
17
- stages?: ProgressStageConfig[];
18
- subProgress?: number;
19
- }
20
-
21
- export function getProgressForStatus(options: ProgressOptions): number {
22
- const { status, stages = DEFAULT_PROGRESS_STAGES, subProgress = 0 } = options;
23
-
24
- const stage = stages.find((s) => s.status === status);
25
-
26
- if (!stage) {
27
- return 0;
28
- }
29
-
30
- const range = stage.maxProgress - stage.minProgress;
31
- const adjustedProgress = stage.minProgress + range * (subProgress / 100);
32
-
33
- return Math.round(adjustedProgress);
34
- }
35
-
36
- export function interpolateProgress(
37
- startTime: number,
38
- estimatedDurationMs: number,
39
- minProgress: number,
40
- maxProgress: number,
41
- ): number {
42
- const elapsed = Date.now() - startTime;
43
- const ratio = Math.min(elapsed / estimatedDurationMs, 1);
44
-
45
- const eased = 1 - Math.pow(1 - ratio, 2);
46
-
47
- const progress = minProgress + (maxProgress - minProgress) * eased;
48
- return Math.round(progress);
49
- }
50
-
51
- /**
52
- * Calculate progress for polling-based operations
53
- * Returns a rounded integer between minProgress and maxProgress
54
- *
55
- * @param currentAttempt - Current polling attempt (1-based)
56
- * @param maxAttempts - Maximum number of polling attempts
57
- * @param minProgress - Starting progress percentage (default: 10)
58
- * @param maxProgress - Maximum progress percentage before completion (default: 95)
59
- * @returns Rounded progress percentage
60
- */
61
- export function calculatePollingProgress(
62
- currentAttempt: number,
63
- maxAttempts: number,
64
- minProgress: number = 10,
65
- maxProgress: number = 95,
66
- ): number {
67
- const ratio = currentAttempt / maxAttempts;
68
- const progress = minProgress + ratio * (maxProgress - minProgress);
69
- return Math.round(Math.min(maxProgress, progress));
70
- }
71
-
72
- export function createProgressTracker(stages?: ProgressStageConfig[]) {
73
- const effectiveStages = stages ?? DEFAULT_PROGRESS_STAGES;
74
- let currentStatus: GenerationStatus = "idle";
75
- let stageStartTime = Date.now();
76
-
77
- return {
78
- setStatus(status: GenerationStatus): number {
79
- currentStatus = status;
80
- stageStartTime = Date.now();
81
- return getProgressForStatus({ status, stages: effectiveStages });
82
- },
83
-
84
- getProgress(subProgress = 0): number {
85
- return getProgressForStatus({
86
- status: currentStatus,
87
- stages: effectiveStages,
88
- subProgress,
89
- });
90
- },
91
-
92
- getCurrentStatus(): GenerationStatus {
93
- return currentStatus;
94
- },
95
-
96
- getElapsedTime(): number {
97
- return Date.now() - stageStartTime;
98
- },
99
- };
100
- }
101
9
 
102
10
  /**
103
11
  * Maps provider job status to generation status
104
12
  * Provider: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED
105
- * Generation: idle, preparing, submitting, generating, polling, finalizing, completed, failed
13
+ * Generation: idle, preparing, submitting, generating, completed, failed
106
14
  */
107
15
  export function mapJobStatusToGenerationStatus(
108
- jobStatus: AIJobStatusType
16
+ jobStatus: AIJobStatusType,
109
17
  ): GenerationStatus {
110
18
  const statusMap: Record<AIJobStatusType, GenerationStatus> = {
111
19
  IN_QUEUE: "submitting",
@@ -115,40 +23,3 @@ export function mapJobStatusToGenerationStatus(
115
23
  };
116
24
  return statusMap[jobStatus] ?? "generating";
117
25
  }
118
-
119
- /**
120
- * Calculate progress from provider job status
121
- * Uses default progress stages for consistent progress reporting
122
- */
123
- export function getProgressFromJobStatus(
124
- jobStatus: AIJobStatusType,
125
- stages: ProgressStageConfig[] = DEFAULT_PROGRESS_STAGES
126
- ): number {
127
- const generationStatus = mapJobStatusToGenerationStatus(jobStatus);
128
- return getProgressForStatus({ status: generationStatus, stages });
129
- }
130
-
131
- /**
132
- * Calculate progress percentage from job status and attempt number
133
- * Used by job poller for granular progress reporting during polling
134
- */
135
- export function calculateProgressFromJobStatus(
136
- status: JobStatus,
137
- attempt: number,
138
- maxAttempts: number,
139
- ): number {
140
- const statusString = String(status.status).toUpperCase();
141
-
142
- switch (statusString) {
143
- case "IN_QUEUE":
144
- return 30 + Math.min(attempt * 2, 10);
145
- case "IN_PROGRESS":
146
- return 50 + Math.min(attempt * 3, 30);
147
- case "COMPLETED":
148
- return 90;
149
- case "FAILED":
150
- return 0;
151
- default:
152
- return 20 + Math.min((attempt / maxAttempts) * 30, 30);
153
- }
154
- }