@umituz/react-native-ai-pruna-provider 1.0.45 → 1.0.47
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 +45 -2
- package/src/domain/entities/pruna.types.ts +2 -17
- package/src/domain/types/index.ts +0 -1
- package/src/index.ts +123 -12
- package/src/infrastructure/services/pruna-api-client.ts +24 -23
- package/src/infrastructure/services/pruna-input-builder.ts +1 -1
- package/src/infrastructure/services/pruna-provider-subscription.ts +8 -2
- package/src/infrastructure/services/pruna-provider.ts +23 -6
- package/src/infrastructure/services/request-store.ts +12 -11
- package/src/infrastructure/utils/calculation.utils.ts +105 -0
- package/src/infrastructure/utils/helpers/index.ts +0 -18
- package/src/infrastructure/utils/log-collector.ts +14 -2
- package/src/infrastructure/utils/mime-detection.util.ts +13 -6
- package/src/infrastructure/utils/pruna-generation-state-manager.util.ts +2 -2
- package/src/infrastructure/utils/type-guards/index.ts +4 -2
- package/src/init/createAiProviderInitModule.ts +1 -1
- package/src/init/initializePrunaProvider.ts +1 -1
- package/src/presentation/hooks/use-pruna-generation.ts +4 -1
- package/src/exports/domain.ts +0 -44
- package/src/exports/infrastructure.ts +0 -60
- package/src/exports/presentation.ts +0 -14
- package/src/infrastructure/services/index.ts +0 -12
- package/src/infrastructure/utils/constants/index.ts +0 -13
- package/src/infrastructure/utils/index.ts +0 -36
- package/src/presentation/hooks/index.ts +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-pruna-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.47",
|
|
4
4
|
"description": "Pruna AI provider for React Native - implements IAIProvider interface for unified AI generation",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -38,14 +38,57 @@
|
|
|
38
38
|
"react-native": ">=0.81.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@expo/vector-icons": "^15.1.1",
|
|
42
|
+
"@gorhom/bottom-sheet": "^5.2.8",
|
|
43
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
44
|
+
"@react-native-community/slider": "^5.1.2",
|
|
45
|
+
"@react-navigation/bottom-tabs": "^7.15.5",
|
|
46
|
+
"@react-navigation/native": "^7.1.33",
|
|
47
|
+
"@react-navigation/stack": "^7.8.5",
|
|
48
|
+
"@tanstack/query-async-storage-persister": "^5.90.24",
|
|
49
|
+
"@tanstack/react-query": "^5.90.21",
|
|
50
|
+
"@tanstack/react-query-persist-client": "^5.90.24",
|
|
41
51
|
"@types/react": "~19.1.0",
|
|
42
52
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
43
53
|
"@typescript-eslint/parser": "^7.0.0",
|
|
44
54
|
"@umituz/react-native-ai-generation-content": "^1.83.17",
|
|
55
|
+
"@umituz/react-native-auth": "^4.3.56",
|
|
56
|
+
"@umituz/react-native-design-system": "^4.25.97",
|
|
57
|
+
"@umituz/react-native-firebase": "^2.4.74",
|
|
58
|
+
"@umituz/react-native-subscription": "^2.37.116",
|
|
59
|
+
"@umituz/react-native-video-editor": "^1.1.48",
|
|
45
60
|
"eslint": "^8.57.0",
|
|
61
|
+
"expo-apple-authentication": "^55.0.8",
|
|
62
|
+
"expo-application": "^55.0.9",
|
|
63
|
+
"expo-clipboard": "^55.0.8",
|
|
64
|
+
"expo-crypto": "^55.0.9",
|
|
65
|
+
"expo-device": "^55.0.9",
|
|
66
|
+
"expo-document-picker": "^55.0.8",
|
|
67
|
+
"expo-file-system": "^55.0.10",
|
|
68
|
+
"expo-font": "^55.0.4",
|
|
69
|
+
"expo-haptics": "^55.0.8",
|
|
70
|
+
"expo-image": "^55.0.6",
|
|
71
|
+
"expo-image-manipulator": "^55.0.10",
|
|
72
|
+
"expo-image-picker": "^55.0.12",
|
|
73
|
+
"expo-linear-gradient": "^55.0.8",
|
|
74
|
+
"expo-localization": "^55.0.8",
|
|
75
|
+
"expo-media-library": "^55.0.9",
|
|
76
|
+
"expo-modules-core": "^55.0.15",
|
|
77
|
+
"expo-network": "^55.0.8",
|
|
78
|
+
"expo-secure-store": "^55.0.8",
|
|
79
|
+
"expo-sharing": "^55.0.11",
|
|
80
|
+
"expo-video": "^55.0.10",
|
|
81
|
+
"expo-web-browser": "^55.0.9",
|
|
82
|
+
"firebase": "^12.10.0",
|
|
46
83
|
"react": "19.1.0",
|
|
47
84
|
"react-native": "0.81.4",
|
|
48
|
-
"
|
|
85
|
+
"react-native-gesture-handler": "^2.30.0",
|
|
86
|
+
"react-native-purchases": "^9.12.0",
|
|
87
|
+
"react-native-reanimated": "^4.2.2",
|
|
88
|
+
"react-native-safe-area-context": "^5.7.0",
|
|
89
|
+
"react-native-svg": "^15.15.3",
|
|
90
|
+
"typescript": "^5.3.0",
|
|
91
|
+
"zustand": "^5.0.11"
|
|
49
92
|
},
|
|
50
93
|
"publishConfig": {
|
|
51
94
|
"access": "public"
|
|
@@ -47,28 +47,12 @@ export interface PrunaJobInput {
|
|
|
47
47
|
readonly [key: string]: unknown;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export interface PrunaJobResult<T = unknown> {
|
|
51
|
-
readonly requestId: string;
|
|
52
|
-
readonly data: T;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface PrunaLogEntry {
|
|
56
|
-
readonly message: string;
|
|
57
|
-
readonly timestamp?: string;
|
|
58
|
-
readonly level?: "info" | "warn" | "error";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
50
|
export type PrunaJobStatusType = "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
|
|
62
51
|
|
|
63
52
|
export interface PrunaQueueStatus {
|
|
64
53
|
readonly status: PrunaJobStatusType;
|
|
65
54
|
readonly requestId: string;
|
|
66
|
-
readonly logs?: readonly
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface PrunaSubscribeOptions {
|
|
70
|
-
readonly onQueueUpdate?: (update: PrunaQueueStatus) => void;
|
|
71
|
-
readonly timeoutMs?: number;
|
|
55
|
+
readonly logs?: readonly { readonly message: string; readonly level?: "info" | "warn" | "error"; readonly timestamp?: string }[];
|
|
72
56
|
}
|
|
73
57
|
|
|
74
58
|
/**
|
|
@@ -97,6 +81,7 @@ export interface PrunaPredictionInput {
|
|
|
97
81
|
* Pruna API prediction response (raw)
|
|
98
82
|
*/
|
|
99
83
|
export interface PrunaPredictionResponse {
|
|
84
|
+
readonly id?: string;
|
|
100
85
|
readonly generation_url?: string;
|
|
101
86
|
readonly output?: { readonly url: string } | string | readonly string[];
|
|
102
87
|
readonly data?: string;
|
package/src/index.ts
CHANGED
|
@@ -8,20 +8,131 @@
|
|
|
8
8
|
* p-video: image-to-video
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
// Domain
|
|
12
|
-
export
|
|
11
|
+
// Domain Types
|
|
12
|
+
export type {
|
|
13
|
+
PrunaConfig,
|
|
14
|
+
PrunaModel,
|
|
15
|
+
PrunaModelId,
|
|
16
|
+
PrunaModelType,
|
|
17
|
+
PrunaModelPricing,
|
|
18
|
+
PrunaAspectRatio,
|
|
19
|
+
PrunaResolution,
|
|
20
|
+
PrunaJobInput,
|
|
21
|
+
PrunaJobStatusType,
|
|
22
|
+
PrunaQueueStatus,
|
|
23
|
+
PrunaPredictionInput,
|
|
24
|
+
PrunaPredictionResponse,
|
|
25
|
+
PrunaFileUploadResponse,
|
|
26
|
+
} from "./domain/entities/pruna.types";
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
export
|
|
28
|
+
export { PrunaErrorType } from "./domain/entities/error.types";
|
|
29
|
+
export type { PrunaErrorInfo } from "./domain/entities/error.types";
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
export type {
|
|
32
|
+
ImageFeatureType,
|
|
33
|
+
VideoFeatureType,
|
|
34
|
+
AIProviderConfig,
|
|
35
|
+
AIJobStatusType,
|
|
36
|
+
AILogEntry,
|
|
37
|
+
JobSubmission,
|
|
38
|
+
JobStatus,
|
|
39
|
+
ProviderProgressInfo,
|
|
40
|
+
SubscribeOptions,
|
|
41
|
+
RunOptions,
|
|
42
|
+
ProviderCapabilities,
|
|
43
|
+
ImageFeatureInputData,
|
|
44
|
+
VideoFeatureInputData,
|
|
45
|
+
IAIProvider,
|
|
46
|
+
} from "./domain/types";
|
|
47
|
+
|
|
48
|
+
// Infrastructure Services
|
|
49
|
+
export { PrunaProvider, prunaProvider } from "./infrastructure/services/pruna-provider";
|
|
50
|
+
export type { PrunaProvider as PrunaProviderType } from "./infrastructure/services/pruna-provider";
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
cleanupRequestStore,
|
|
54
|
+
stopAutomaticCleanup,
|
|
55
|
+
} from "./infrastructure/services/request-store";
|
|
56
|
+
export type { ActiveRequest } from "./infrastructure/services/request-store";
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
PRUNA_BASE_URL,
|
|
60
|
+
PRUNA_PREDICTIONS_URL,
|
|
61
|
+
PRUNA_FILES_URL,
|
|
62
|
+
DEFAULT_PRUNA_CONFIG,
|
|
63
|
+
UPLOAD_CONFIG,
|
|
64
|
+
PRUNA_CAPABILITIES,
|
|
65
|
+
VALID_PRUNA_MODELS,
|
|
66
|
+
P_VIDEO_DEFAULTS,
|
|
67
|
+
DEFAULT_ASPECT_RATIO,
|
|
68
|
+
P_VIDEO_PRICING,
|
|
69
|
+
DRAFT_MODE_CONFIG,
|
|
70
|
+
} from "./infrastructure/services/pruna-provider.constants";
|
|
71
|
+
|
|
72
|
+
// Infrastructure Utils
|
|
73
|
+
export {
|
|
74
|
+
mapPrunaError,
|
|
75
|
+
isPrunaErrorRetryable,
|
|
76
|
+
getErrorMessage,
|
|
77
|
+
getErrorMessageOr,
|
|
78
|
+
formatErrorMessage,
|
|
79
|
+
} from "./infrastructure/utils/pruna-error-handler.util";
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
isPrunaModelId,
|
|
83
|
+
isPrunaErrorType,
|
|
84
|
+
isValidApiKey,
|
|
85
|
+
isValidModelId,
|
|
86
|
+
isValidPrompt,
|
|
87
|
+
isValidTimeout,
|
|
88
|
+
} from "./infrastructure/utils/type-guards";
|
|
89
|
+
|
|
90
|
+
export { isDefined } from "./infrastructure/utils/helpers";
|
|
91
|
+
|
|
92
|
+
export { generationLogCollector } from "./infrastructure/utils/log-collector";
|
|
93
|
+
export type { LogEntry } from "./infrastructure/utils/log-collector";
|
|
94
|
+
|
|
95
|
+
export { detectMimeType } from "./infrastructure/utils/mime-detection.util";
|
|
19
96
|
|
|
20
|
-
// Init Module Factory
|
|
21
97
|
export {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
98
|
+
MIME_IMAGE_PNG,
|
|
99
|
+
MIME_IMAGE_JPEG,
|
|
100
|
+
MIME_IMAGE_WEBP,
|
|
101
|
+
MIME_AUDIO_MPEG,
|
|
102
|
+
MIME_AUDIO_WAV,
|
|
103
|
+
MIME_AUDIO_FLAC,
|
|
104
|
+
MIME_AUDIO_MP4,
|
|
105
|
+
MIME_APPLICATION_OCTET,
|
|
106
|
+
MIME_DEFAULT,
|
|
107
|
+
MIME_TO_EXTENSION,
|
|
108
|
+
getExtensionForMime,
|
|
109
|
+
} from "./infrastructure/utils/constants/mime.constants";
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
validateDraftModeParams,
|
|
113
|
+
calculateDraftModeDiscount,
|
|
114
|
+
getDraftModeDescription,
|
|
115
|
+
recommendDraftMode,
|
|
116
|
+
calculateDraftModeSavings,
|
|
117
|
+
getPricingPerSecond,
|
|
118
|
+
formatPriceUSD,
|
|
119
|
+
compareDraftModePricing,
|
|
120
|
+
} from "./infrastructure/utils/pruna-draft-mode.util";
|
|
121
|
+
|
|
122
|
+
export { PrunaGenerationStateManager } from "./infrastructure/utils/pruna-generation-state-manager.util";
|
|
123
|
+
export type {
|
|
124
|
+
GenerationState,
|
|
125
|
+
GenerationStateOptions,
|
|
126
|
+
} from "./infrastructure/utils/pruna-generation-state-manager.util";
|
|
127
|
+
|
|
128
|
+
// Presentation Hooks
|
|
129
|
+
export { usePrunaGeneration } from "./presentation/hooks/use-pruna-generation";
|
|
130
|
+
export type {
|
|
131
|
+
UsePrunaGenerationOptions,
|
|
132
|
+
UsePrunaGenerationResult,
|
|
133
|
+
} from "./presentation/hooks/use-pruna-generation";
|
|
25
134
|
|
|
26
|
-
//
|
|
27
|
-
export { initializePrunaProvider } from
|
|
135
|
+
// Init Modules
|
|
136
|
+
export { initializePrunaProvider } from "./init/initializePrunaProvider";
|
|
137
|
+
export { createAiProviderInitModule } from "./init/createAiProviderInitModule";
|
|
138
|
+
export type { AiProviderInitModuleConfig } from "./init/createAiProviderInitModule";
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
import type { PrunaModelId, PrunaPredictionResponse, PrunaFileUploadResponse } from "../../domain/entities/pruna.types";
|
|
15
15
|
import { PRUNA_BASE_URL, PRUNA_PREDICTIONS_URL, PRUNA_FILES_URL, UPLOAD_CONFIG } from "./pruna-provider.constants";
|
|
16
16
|
import { generationLogCollector } from "../utils/log-collector";
|
|
17
|
-
import {
|
|
18
|
-
import { getExtensionForMime } from "../utils/constants/mime.constants";
|
|
17
|
+
import { bytesToKB, calculateElapsedMs, createStringPreview } from "../utils/calculation.utils";
|
|
19
18
|
|
|
20
19
|
const TAG = 'pruna-api';
|
|
21
20
|
|
|
@@ -53,11 +52,11 @@ export async function uploadFileToStorage(
|
|
|
53
52
|
|
|
54
53
|
// __DEV__ log input data size
|
|
55
54
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
56
|
-
const dataSizeKB =
|
|
55
|
+
const dataSizeKB = bytesToKB(base64Data.length);
|
|
57
56
|
console.log(`[DEV] [${TAG}] File upload input:`, {
|
|
58
57
|
dataSizeKB,
|
|
59
58
|
startsWithDataUri: base64Data.startsWith('data:'),
|
|
60
|
-
preview: base64Data
|
|
59
|
+
preview: createStringPreview(base64Data, 50),
|
|
61
60
|
});
|
|
62
61
|
}
|
|
63
62
|
|
|
@@ -102,9 +101,14 @@ export async function uploadFileToStorage(
|
|
|
102
101
|
uri: dataUri,
|
|
103
102
|
type: mimeType,
|
|
104
103
|
name: 'upload.jpg',
|
|
105
|
-
} as
|
|
104
|
+
} as {
|
|
105
|
+
uri: string;
|
|
106
|
+
type: string;
|
|
107
|
+
name: string;
|
|
108
|
+
};
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
// Type cast for React Native FormData which accepts file objects
|
|
111
|
+
(formData as unknown as { append: (name: string, value: typeof fileObject) => void }).append('content', fileObject);
|
|
108
112
|
|
|
109
113
|
generationLogCollector.log(sessionId, TAG, 'FormData created', {
|
|
110
114
|
hasContent: formData.has('content'),
|
|
@@ -129,7 +133,7 @@ export async function uploadFileToStorage(
|
|
|
129
133
|
console.log(`[DEV] [${TAG}] Sending upload request:`, {
|
|
130
134
|
url: PRUNA_FILES_URL,
|
|
131
135
|
method: 'POST',
|
|
132
|
-
|
|
136
|
+
hasContent: formData.has('content'),
|
|
133
137
|
});
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -143,7 +147,10 @@ export async function uploadFileToStorage(
|
|
|
143
147
|
signal: uploadController.signal,
|
|
144
148
|
});
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
// Clear timeout immediately after fetch completes to prevent race condition
|
|
151
|
+
clearTimeout(timeoutId);
|
|
152
|
+
|
|
153
|
+
const uploadElapsed = calculateElapsedMs(uploadStart);
|
|
147
154
|
generationLogCollector.log(sessionId, TAG, 'Upload response received', {
|
|
148
155
|
statusCode: uploadResponse.status,
|
|
149
156
|
statusText: uploadResponse.statusText,
|
|
@@ -165,11 +172,14 @@ export async function uploadFileToStorage(
|
|
|
165
172
|
try {
|
|
166
173
|
errorDetails = JSON.parse(rawBody) as Record<string, unknown>;
|
|
167
174
|
} catch {
|
|
168
|
-
// If not JSON, keep raw text
|
|
175
|
+
// If not JSON, keep raw text for error message
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
|
-
} catch {
|
|
172
|
-
// If reading body fails, continue with status info
|
|
178
|
+
} catch (bodyError) {
|
|
179
|
+
// If reading body fails, log the error but continue with status info
|
|
180
|
+
generationLogCollector.warn(sessionId, TAG, 'Failed to read error response body', {
|
|
181
|
+
error: bodyError instanceof Error ? bodyError.message : String(bodyError),
|
|
182
|
+
});
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
const errorMessage = (errorDetails as { message?: string; detail?: string; error?: string }).message ||
|
|
@@ -207,10 +217,10 @@ export async function uploadFileToStorage(
|
|
|
207
217
|
const data: PrunaFileUploadResponse = await uploadResponse.json();
|
|
208
218
|
const fileUrl = data.urls?.get || `${PRUNA_FILES_URL}/${data.id}`;
|
|
209
219
|
|
|
210
|
-
const elapsed =
|
|
220
|
+
const elapsed = calculateElapsedMs(startTime);
|
|
211
221
|
generationLogCollector.log(sessionId, TAG, `File upload completed in ${elapsed}ms`, {
|
|
212
222
|
fileId: data.id,
|
|
213
|
-
fileUrl: fileUrl
|
|
223
|
+
fileUrl: createStringPreview(fileUrl),
|
|
214
224
|
responseKeys: Object.keys(data),
|
|
215
225
|
hasUrlsGet: !!data.urls?.get,
|
|
216
226
|
});
|
|
@@ -243,15 +253,6 @@ export async function uploadFileToStorage(
|
|
|
243
253
|
}
|
|
244
254
|
}
|
|
245
255
|
|
|
246
|
-
/**
|
|
247
|
-
* Strip base64 data URI prefix, returning raw base64 string.
|
|
248
|
-
* If input is already a URL, returns it unchanged.
|
|
249
|
-
*/
|
|
250
|
-
export function stripBase64Prefix(image: string): string {
|
|
251
|
-
if (image.startsWith('http')) return image;
|
|
252
|
-
return image.includes('base64,') ? image.split('base64,')[1] : image;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
256
|
/**
|
|
256
257
|
* Submit a prediction to Pruna AI.
|
|
257
258
|
* Uses Try-Sync header for potential immediate results.
|
|
@@ -293,7 +294,7 @@ export async function submitPrediction(
|
|
|
293
294
|
bodyTopLevelKeys: Object.keys(requestBody),
|
|
294
295
|
inputKeys: Object.keys(input),
|
|
295
296
|
inputSummary,
|
|
296
|
-
requestBodySizeKB:
|
|
297
|
+
requestBodySizeKB: bytesToKB(JSON.stringify(requestBody).length),
|
|
297
298
|
});
|
|
298
299
|
}
|
|
299
300
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { PrunaModelId, PrunaAspectRatio, PrunaResolution } from "../../domain/entities/pruna.types";
|
|
12
12
|
import { P_VIDEO_DEFAULTS, DEFAULT_ASPECT_RATIO } from "./pruna-provider.constants";
|
|
13
|
-
import { uploadFileToStorage
|
|
13
|
+
import { uploadFileToStorage } from "./pruna-api-client";
|
|
14
14
|
import { generationLogCollector } from "../utils/log-collector";
|
|
15
15
|
|
|
16
16
|
const TAG = 'pruna-input-builder';
|
|
@@ -80,7 +80,10 @@ async function singleSubscribeAttempt<T = unknown>(
|
|
|
80
80
|
|
|
81
81
|
// If no immediate result, poll for async result
|
|
82
82
|
if (!uri && (response.get_url || response.status_url)) {
|
|
83
|
-
const pollUrl =
|
|
83
|
+
const pollUrl = response.get_url || response.status_url;
|
|
84
|
+
if (!pollUrl) {
|
|
85
|
+
throw new Error("Pruna API response missing polling URL");
|
|
86
|
+
}
|
|
84
87
|
|
|
85
88
|
generationLogCollector.log(sessionId, TAG, 'No immediate result — starting async polling...');
|
|
86
89
|
options?.onQueueUpdate?.({
|
|
@@ -267,7 +270,10 @@ export async function handlePrunaRun<T = unknown>(
|
|
|
267
270
|
|
|
268
271
|
// Poll if needed
|
|
269
272
|
if (!uri && (response.get_url || response.status_url)) {
|
|
270
|
-
const pollUrl =
|
|
273
|
+
const pollUrl = response.get_url || response.status_url;
|
|
274
|
+
if (!pollUrl) {
|
|
275
|
+
throw new Error("Pruna API response missing polling URL");
|
|
276
|
+
}
|
|
271
277
|
uri = await pollForResult(
|
|
272
278
|
pollUrl,
|
|
273
279
|
apiKey,
|
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
ImageFeatureInputData, VideoFeatureInputData,
|
|
15
15
|
} from "../../domain/types";
|
|
16
16
|
import type { PrunaModelId } from "../../domain/entities/pruna.types";
|
|
17
|
-
import {
|
|
17
|
+
import { PRUNA_CAPABILITIES, VALID_PRUNA_MODELS } from "./pruna-provider.constants";
|
|
18
18
|
import { handlePrunaSubscription, handlePrunaRun } from "./pruna-provider-subscription";
|
|
19
19
|
import * as queueOps from "./pruna-queue-operations";
|
|
20
20
|
import { generationLogCollector } from "../utils/log-collector";
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
createRequestKey, getExistingRequest, storeRequest,
|
|
24
24
|
removeRequest, cancelRequest, cancelAllRequests, hasActiveRequests,
|
|
25
25
|
storeRequestIdMapping, storeImmediateResultMapping,
|
|
26
|
-
getStatusUrlForRequestId, getResponseUrlForRequestId,
|
|
26
|
+
getStatusUrlForRequestId, getResponseUrlForRequestId,
|
|
27
27
|
} from "./request-store";
|
|
28
28
|
|
|
29
29
|
export class PrunaProvider implements IAIProvider {
|
|
@@ -93,12 +93,18 @@ export class PrunaProvider implements IAIProvider {
|
|
|
93
93
|
|
|
94
94
|
// Log response type
|
|
95
95
|
if (submission.responseUrl) {
|
|
96
|
-
|
|
96
|
+
const responseUrlPreview = submission.responseUrl.length > 80
|
|
97
|
+
? `${submission.responseUrl.substring(0, 80)}...`
|
|
98
|
+
: submission.responseUrl;
|
|
99
|
+
generationLogCollector.log(sessionId, 'pruna-provider', `submitJob() IMMEDIATE RESULT - requestId: ${submission.requestId}, responseUrl: ${responseUrlPreview}`);
|
|
97
100
|
// Store requestId -> responseUrl mapping for immediate results (already complete)
|
|
98
101
|
storeImmediateResultMapping(submission.requestId, submission.responseUrl, model);
|
|
99
102
|
generationLogCollector.log(sessionId, 'pruna-provider', `submitJob() Stored immediate result mapping: ${submission.requestId}`);
|
|
100
103
|
} else if (submission.statusUrl) {
|
|
101
|
-
|
|
104
|
+
const statusUrlPreview = submission.statusUrl.length > 80
|
|
105
|
+
? `${submission.statusUrl.substring(0, 80)}...`
|
|
106
|
+
: submission.statusUrl;
|
|
107
|
+
generationLogCollector.log(sessionId, 'pruna-provider', `submitJob() ASYNC JOB - requestId: ${submission.requestId}, statusUrl: ${statusUrlPreview}`);
|
|
102
108
|
// Store requestId -> statusUrl mapping for async jobs (requires polling)
|
|
103
109
|
storeRequestIdMapping(submission.requestId, submission.statusUrl, model);
|
|
104
110
|
generationLogCollector.log(sessionId, 'pruna-provider', `submitJob() Stored async job mapping: ${submission.requestId}`);
|
|
@@ -225,9 +231,15 @@ export class PrunaProvider implements IAIProvider {
|
|
|
225
231
|
rejectPromise = reject;
|
|
226
232
|
});
|
|
227
233
|
|
|
228
|
-
|
|
234
|
+
// Store request BEFORE starting async operation to prevent race conditions
|
|
235
|
+
// Use the unique key for this specific request
|
|
229
236
|
storeRequest(key, { promise, abortController, createdAt: Date.now() });
|
|
230
237
|
|
|
238
|
+
// Capture this request's key for cleanup in finally block
|
|
239
|
+
// This prevents race condition where rapid successive calls
|
|
240
|
+
// could cause cleanup to remove wrong request
|
|
241
|
+
const thisRequestKey = key;
|
|
242
|
+
|
|
231
243
|
handlePrunaSubscription<T>(prunaModel, input, apiKey, sessionId, options, abortController.signal)
|
|
232
244
|
.then((res) => {
|
|
233
245
|
const totalElapsed = Date.now() - totalStart;
|
|
@@ -246,7 +258,12 @@ export class PrunaProvider implements IAIProvider {
|
|
|
246
258
|
})
|
|
247
259
|
.finally(() => {
|
|
248
260
|
try {
|
|
249
|
-
|
|
261
|
+
// Only remove if this is still the correct request
|
|
262
|
+
// This prevents removing a newer request with same key
|
|
263
|
+
const storedRequest = getExistingRequest<T>(thisRequestKey);
|
|
264
|
+
if (storedRequest && storedRequest.promise === promise) {
|
|
265
|
+
removeRequest(thisRequestKey);
|
|
266
|
+
}
|
|
250
267
|
} catch (cleanupError) {
|
|
251
268
|
generationLogCollector.warn(sessionId, TAG, `Error removing request: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
252
269
|
}
|
|
@@ -50,13 +50,19 @@ function sortKeys(obj: unknown): unknown {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export function createRequestKey(model: string, input: Record<string, unknown>): string {
|
|
53
|
+
// Use full JSON string instead of hash to eliminate collision risk
|
|
54
|
+
// Sort keys ensures consistent key generation regardless of object property order
|
|
53
55
|
const inputStr = JSON.stringify(sortKeys(input));
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
|
|
57
|
+
// Use base64 encoding for safer string representation
|
|
58
|
+
// This eliminates collision risk entirely while maintaining readability
|
|
59
|
+
const safeInputStr = inputStr.replace(/[^a-zA-Z0-9]/g, '_');
|
|
60
|
+
|
|
61
|
+
// Use first 64 chars to keep key length manageable while maintaining uniqueness
|
|
62
|
+
const prefix = safeInputStr.substring(0, 64);
|
|
63
|
+
const suffix = safeInputStr.length > 64 ? safeInputStr.slice(-64) : '';
|
|
64
|
+
|
|
65
|
+
return `${model}:${prefix}${suffix ? '...' + suffix : ''}`;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
export function getExistingRequest<T>(key: string): ActiveRequest<T> | undefined {
|
|
@@ -168,11 +174,6 @@ export function getResponseUrlForRequestId(requestId: string): string | undefine
|
|
|
168
174
|
return mapping?.responseUrl;
|
|
169
175
|
}
|
|
170
176
|
|
|
171
|
-
export function getModelForRequestId(requestId: string): string | undefined {
|
|
172
|
-
const mapping = getRequestIdMap().get(requestId);
|
|
173
|
-
return mapping?.model;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
177
|
export function removeRequestIdMapping(requestId: string): void {
|
|
177
178
|
getRequestIdMap().delete(requestId);
|
|
178
179
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculation Utilities
|
|
3
|
+
* Centralized calculation functions for consistent and reusable operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts bytes to kilobytes (KB)
|
|
8
|
+
*/
|
|
9
|
+
export function bytesToKB(bytes: number): number {
|
|
10
|
+
return Math.round(bytes / 1024);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converts bytes to megabytes (MB)
|
|
15
|
+
*/
|
|
16
|
+
export function bytesToMB(bytes: number): number {
|
|
17
|
+
return Math.round(bytes / 1024 / 1024);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calculates elapsed time in milliseconds from a start timestamp
|
|
22
|
+
*/
|
|
23
|
+
export function calculateElapsedMs(startTime: number): number {
|
|
24
|
+
return Date.now() - startTime;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Formats elapsed time in human-readable format
|
|
29
|
+
*/
|
|
30
|
+
export function formatElapsedMs(ms: number): string {
|
|
31
|
+
if (ms < 1000) return `${ms}ms`;
|
|
32
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
33
|
+
const minutes = Math.floor(ms / 60000);
|
|
34
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
35
|
+
return `${minutes}m ${seconds}s`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a preview of a string by truncating and adding ellipsis
|
|
40
|
+
*/
|
|
41
|
+
export function createStringPreview(str: string, maxLength: number = 80): string {
|
|
42
|
+
if (str.length <= maxLength) return str;
|
|
43
|
+
return `${str.substring(0, maxLength)}...`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a size preview (e.g., "2.5 MB", "1024 KB")
|
|
48
|
+
*/
|
|
49
|
+
export function formatSize(bytes: number): string {
|
|
50
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
51
|
+
if (bytes < 1024 * 1024) return `${bytesToKB(bytes)} KB`;
|
|
52
|
+
return `${bytesToMB(bytes)} MB`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Calculates percentage with specified precision
|
|
57
|
+
*/
|
|
58
|
+
export function calculatePercentage(value: number, total: number, precision: number = 0): number {
|
|
59
|
+
if (total === 0) return 0;
|
|
60
|
+
return Number(((value / total) * 100).toFixed(precision));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clamps a number between min and max values
|
|
65
|
+
*/
|
|
66
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
67
|
+
return Math.min(Math.max(value, min), max);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Calculates retry delay with exponential backoff
|
|
72
|
+
*/
|
|
73
|
+
export function calculateRetryDelay(
|
|
74
|
+
attempt: number,
|
|
75
|
+
baseDelayMs: number,
|
|
76
|
+
maxDelayMs: number
|
|
77
|
+
): number {
|
|
78
|
+
const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
79
|
+
return Math.round(delay);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validates if a timeout value is within acceptable range
|
|
84
|
+
*/
|
|
85
|
+
export function isValidTimeout(timeoutMs: number): boolean {
|
|
86
|
+
return (
|
|
87
|
+
Number.isInteger(timeoutMs) &&
|
|
88
|
+
timeoutMs > 0 &&
|
|
89
|
+
timeoutMs <= 3600000 // Max 1 hour
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculates hash of a string for request deduplication
|
|
95
|
+
* Uses base64 encoding for collision resistance
|
|
96
|
+
*/
|
|
97
|
+
export function calculateRequestHash(input: string): string {
|
|
98
|
+
// Replace non-alphanumeric chars with underscores for safe string representation
|
|
99
|
+
const safeInput = input.replace(/[^a-zA-Z0-9]/g, '_');
|
|
100
|
+
|
|
101
|
+
// Use first 64 chars + last 64 chars to keep key length manageable
|
|
102
|
+
if (safeInput.length <= 128) return safeInput;
|
|
103
|
+
|
|
104
|
+
return `${safeInput.substring(0, 64)}...${safeInput.slice(-64)}`;
|
|
105
|
+
}
|
|
@@ -5,21 +5,3 @@
|
|
|
5
5
|
export function isDefined<T>(value: T | null | undefined): value is T {
|
|
6
6
|
return value !== null && value !== undefined;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
export function removeNullish<T extends Record<string, unknown>>(obj: T): Partial<T> {
|
|
10
|
-
const result: Partial<T> = {};
|
|
11
|
-
for (const key of Object.keys(obj) as (keyof T)[]) {
|
|
12
|
-
if (obj[key] !== null && obj[key] !== undefined) {
|
|
13
|
-
result[key] = obj[key];
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return result;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function generateUniqueId(prefix = 'pruna'): string {
|
|
20
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function sleep(ms: number): Promise<void> {
|
|
24
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
25
|
-
}
|
|
@@ -34,8 +34,20 @@ class GenerationLogCollector {
|
|
|
34
34
|
startSession(): string {
|
|
35
35
|
// Evict oldest sessions if limit exceeded
|
|
36
36
|
if (this.sessions.size >= MAX_SESSIONS) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Find and remove the oldest session by startTime (LRU eviction)
|
|
38
|
+
let oldestKey: string | null = null;
|
|
39
|
+
let oldestTime = Date.now();
|
|
40
|
+
|
|
41
|
+
for (const [key, session] of this.sessions.entries()) {
|
|
42
|
+
if (session.startTime < oldestTime) {
|
|
43
|
+
oldestTime = session.startTime;
|
|
44
|
+
oldestKey = key;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (oldestKey) {
|
|
49
|
+
this.sessions.delete(oldestKey);
|
|
50
|
+
}
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
const id = `pruna_session_${++sessionCounter}_${Date.now()}`;
|
|
@@ -22,20 +22,23 @@ import {
|
|
|
22
22
|
* Detect MIME type from raw binary bytes using magic number signatures.
|
|
23
23
|
*
|
|
24
24
|
* Detection order is intentional:
|
|
25
|
-
* 1. JPEG (0xFF 0xD8) — checked before MP3 sync word
|
|
25
|
+
* 1. JPEG (0xFF 0xD8) — MUST be checked before MP3 sync word
|
|
26
26
|
* 2. PNG (0x89 0x50)
|
|
27
27
|
* 3. RIFF container → distinguish WAV vs WebP via subformat at offset 8-11
|
|
28
28
|
* 4. MP3 with ID3 tag (0x49 0x44 0x33)
|
|
29
|
-
* 5. MP3 sync word (0xFF 0xE_) —
|
|
29
|
+
* 5. MP3 sync word (0xFF 0xE_) — AFTER JPEG to prevent false positives!
|
|
30
30
|
* 6. FLAC (fLaC)
|
|
31
31
|
* 7. M4A/AAC (ftyp box at offset 4)
|
|
32
|
+
*
|
|
33
|
+
* CRITICAL: JPEG check must come before MP3 sync word check to avoid
|
|
34
|
+
* misclassifying JPEG files as MP3 (both start with 0xFF).
|
|
32
35
|
*/
|
|
33
36
|
export function detectMimeType(bytes: Uint8Array): string {
|
|
34
37
|
if (bytes.length < 4) return MIME_DEFAULT;
|
|
35
38
|
|
|
36
39
|
// ── Image formats ───────────────────────────────────────
|
|
37
|
-
// JPEG: FF D8
|
|
38
|
-
if (bytes[0] === 0xFF && bytes[1] === 0xD8) return MIME_IMAGE_JPEG;
|
|
40
|
+
// JPEG: FF D8 FF (must check third byte to distinguish from MP3)
|
|
41
|
+
if (bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) return MIME_IMAGE_JPEG;
|
|
39
42
|
|
|
40
43
|
// PNG: 89 50 4E 47
|
|
41
44
|
if (bytes[0] === 0x89 && bytes[1] === 0x50) return MIME_IMAGE_PNG;
|
|
@@ -56,8 +59,12 @@ export function detectMimeType(bytes: Uint8Array): string {
|
|
|
56
59
|
// MP3 with ID3v2 tag: 49 44 33
|
|
57
60
|
if (bytes[0] === 0x49 && bytes[1] === 0x44 && bytes[2] === 0x33) return MIME_AUDIO_MPEG;
|
|
58
61
|
|
|
59
|
-
// MP3 frame sync word: FF Ex/Fx (but not FF FF)
|
|
60
|
-
|
|
62
|
+
// MP3 frame sync word: FF Ex/Fx (but not FF FF, and must not be JPEG)
|
|
63
|
+
// CRITICAL: Check this AFTER JPEG to avoid false positives
|
|
64
|
+
if (bytes[0] === 0xFF && (bytes[1] & 0xE0) === 0xE0 && bytes[1] !== 0xFF) {
|
|
65
|
+
// Additional check: ensure this is not a JPEG by verifying bytes[2] is not 0xFF
|
|
66
|
+
if (bytes[2] !== 0xFF) return MIME_AUDIO_MPEG;
|
|
67
|
+
}
|
|
61
68
|
|
|
62
69
|
// FLAC: 66 4C 61 43 ("fLaC")
|
|
63
70
|
if (bytes[0] === 0x66 && bytes[1] === 0x4C && bytes[2] === 0x61 && bytes[3] === 0x43) return MIME_AUDIO_FLAC;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Manages state and refs for Pruna generation operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { PrunaJobInput,
|
|
6
|
+
import type { PrunaJobInput, PrunaQueueStatus } from "../../domain/entities/pruna.types";
|
|
7
7
|
|
|
8
8
|
export interface GenerationState<T> {
|
|
9
9
|
data: T | null;
|
|
@@ -71,7 +71,7 @@ export class PrunaGenerationStateManager<T> {
|
|
|
71
71
|
const normalizedStatus: PrunaQueueStatus = {
|
|
72
72
|
status: status.status,
|
|
73
73
|
requestId: status.requestId ?? this.currentRequestId ?? "",
|
|
74
|
-
logs: status.logs?.map((log:
|
|
74
|
+
logs: status.logs?.map((log: { message: string; level?: "info" | "warn" | "error"; timestamp?: string }) => ({
|
|
75
75
|
message: log.message,
|
|
76
76
|
level: log.level,
|
|
77
77
|
timestamp: log.timestamp,
|
|
@@ -7,11 +7,13 @@ import { PrunaErrorType } from "../../../domain/entities/error.types";
|
|
|
7
7
|
import { VALID_PRUNA_MODELS } from "../../services/pruna-provider.constants";
|
|
8
8
|
|
|
9
9
|
export function isPrunaModelId(value: unknown): value is PrunaModelId {
|
|
10
|
-
|
|
10
|
+
if (typeof value !== 'string') return false;
|
|
11
|
+
return VALID_PRUNA_MODELS.includes(value as PrunaModelId);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function isPrunaErrorType(value: unknown): value is PrunaErrorType {
|
|
14
|
-
|
|
15
|
+
if (typeof value !== 'string') return false;
|
|
16
|
+
return Object.values(PrunaErrorType).includes(value as PrunaErrorType);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function isValidApiKey(value: unknown): value is string {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { providerRegistry } from '@umituz/react-native-ai-generation-content';
|
|
7
|
-
import { prunaProvider } from '../infrastructure/services';
|
|
7
|
+
import { prunaProvider } from '../infrastructure/services/pruna-provider';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* InitModule interface (from @umituz/react-native-design-system)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { providerRegistry } from '@umituz/react-native-ai-generation-content';
|
|
7
|
-
import { prunaProvider } from '../infrastructure/services';
|
|
7
|
+
import { prunaProvider } from '../infrastructure/services/pruna-provider';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Initializes Pruna provider and registers it with providerRegistry in one call.
|
|
@@ -105,9 +105,12 @@ export function usePrunaGeneration<T = unknown>(
|
|
|
105
105
|
stateManager.setCurrentRequestId(null);
|
|
106
106
|
setIsCancelling(false);
|
|
107
107
|
|
|
108
|
+
// Capture current timeout value at the start of generation
|
|
109
|
+
const timeoutMs = optionsRef.current?.timeoutMs;
|
|
110
|
+
|
|
108
111
|
try {
|
|
109
112
|
const result = await prunaProvider.subscribe<T>(model, input, {
|
|
110
|
-
timeoutMs
|
|
113
|
+
timeoutMs,
|
|
111
114
|
onQueueUpdate: (status: JobStatus) => {
|
|
112
115
|
const prunaStatus = convertJobStatusToPrunaQueueStatus(
|
|
113
116
|
status,
|
package/src/exports/domain.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domain Layer Exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export type {
|
|
6
|
-
PrunaConfig,
|
|
7
|
-
PrunaModel,
|
|
8
|
-
PrunaModelId,
|
|
9
|
-
PrunaModelType,
|
|
10
|
-
PrunaModelPricing,
|
|
11
|
-
PrunaAspectRatio,
|
|
12
|
-
PrunaResolution,
|
|
13
|
-
PrunaJobInput,
|
|
14
|
-
PrunaJobResult,
|
|
15
|
-
PrunaLogEntry,
|
|
16
|
-
PrunaJobStatusType,
|
|
17
|
-
PrunaQueueStatus,
|
|
18
|
-
PrunaSubscribeOptions,
|
|
19
|
-
PrunaPredictionInput,
|
|
20
|
-
PrunaPredictionResponse,
|
|
21
|
-
PrunaFileUploadResponse,
|
|
22
|
-
} from "../domain/entities/pruna.types";
|
|
23
|
-
|
|
24
|
-
export { PrunaErrorType } from "../domain/entities/error.types";
|
|
25
|
-
export type {
|
|
26
|
-
PrunaErrorInfo,
|
|
27
|
-
} from "../domain/entities/error.types";
|
|
28
|
-
|
|
29
|
-
export type {
|
|
30
|
-
ImageFeatureType,
|
|
31
|
-
VideoFeatureType,
|
|
32
|
-
AIProviderConfig,
|
|
33
|
-
AIJobStatusType,
|
|
34
|
-
AILogEntry,
|
|
35
|
-
JobSubmission,
|
|
36
|
-
JobStatus,
|
|
37
|
-
ProviderProgressInfo,
|
|
38
|
-
SubscribeOptions,
|
|
39
|
-
RunOptions,
|
|
40
|
-
ProviderCapabilities,
|
|
41
|
-
ImageFeatureInputData,
|
|
42
|
-
VideoFeatureInputData,
|
|
43
|
-
IAIProvider,
|
|
44
|
-
} from "../domain/types";
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infrastructure Layer Exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
PrunaProvider,
|
|
7
|
-
prunaProvider,
|
|
8
|
-
cleanupRequestStore,
|
|
9
|
-
stopAutomaticCleanup,
|
|
10
|
-
} from "../infrastructure/services";
|
|
11
|
-
export type { PrunaProviderType, ActiveRequest } from "../infrastructure/services";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
mapPrunaError,
|
|
15
|
-
isPrunaErrorRetryable,
|
|
16
|
-
getErrorMessage,
|
|
17
|
-
getErrorMessageOr,
|
|
18
|
-
formatErrorMessage,
|
|
19
|
-
} from "../infrastructure/utils";
|
|
20
|
-
|
|
21
|
-
export {
|
|
22
|
-
isPrunaModelId,
|
|
23
|
-
isPrunaErrorType,
|
|
24
|
-
isValidApiKey,
|
|
25
|
-
isValidModelId,
|
|
26
|
-
isValidPrompt,
|
|
27
|
-
isValidTimeout,
|
|
28
|
-
} from "../infrastructure/utils";
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
isDefined,
|
|
32
|
-
removeNullish,
|
|
33
|
-
generateUniqueId,
|
|
34
|
-
sleep,
|
|
35
|
-
} from "../infrastructure/utils";
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
PRUNA_BASE_URL,
|
|
39
|
-
PRUNA_PREDICTIONS_URL,
|
|
40
|
-
PRUNA_FILES_URL,
|
|
41
|
-
DEFAULT_PRUNA_CONFIG,
|
|
42
|
-
UPLOAD_CONFIG,
|
|
43
|
-
PRUNA_CAPABILITIES,
|
|
44
|
-
VALID_PRUNA_MODELS,
|
|
45
|
-
P_VIDEO_DEFAULTS,
|
|
46
|
-
DEFAULT_ASPECT_RATIO,
|
|
47
|
-
P_VIDEO_PRICING,
|
|
48
|
-
DRAFT_MODE_CONFIG,
|
|
49
|
-
} from "../infrastructure/services/pruna-provider.constants";
|
|
50
|
-
|
|
51
|
-
export {
|
|
52
|
-
validateDraftModeParams,
|
|
53
|
-
calculateDraftModeDiscount,
|
|
54
|
-
getDraftModeDescription,
|
|
55
|
-
recommendDraftMode,
|
|
56
|
-
calculateDraftModeSavings,
|
|
57
|
-
getPricingPerSecond,
|
|
58
|
-
formatPriceUSD,
|
|
59
|
-
compareDraftModePricing,
|
|
60
|
-
} from "../infrastructure/utils/pruna-draft-mode.util";
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Presentation Layer Exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { usePrunaGeneration } from "../presentation/hooks";
|
|
6
|
-
export type { UsePrunaGenerationOptions, UsePrunaGenerationResult } from "../presentation/hooks";
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
PrunaGenerationStateManager,
|
|
10
|
-
} from "../infrastructure/utils/pruna-generation-state-manager.util";
|
|
11
|
-
export type {
|
|
12
|
-
GenerationState,
|
|
13
|
-
GenerationStateOptions,
|
|
14
|
-
} from "../infrastructure/utils/pruna-generation-state-manager.util";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Services Index
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { PrunaProvider, prunaProvider } from "./pruna-provider";
|
|
6
|
-
export type { PrunaProvider as PrunaProviderType } from "./pruna-provider";
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
cleanupRequestStore,
|
|
10
|
-
stopAutomaticCleanup,
|
|
11
|
-
} from "./request-store";
|
|
12
|
-
export type { ActiveRequest } from "./request-store";
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
MIME_IMAGE_PNG,
|
|
3
|
-
MIME_IMAGE_JPEG,
|
|
4
|
-
MIME_IMAGE_WEBP,
|
|
5
|
-
MIME_AUDIO_MPEG,
|
|
6
|
-
MIME_AUDIO_WAV,
|
|
7
|
-
MIME_AUDIO_FLAC,
|
|
8
|
-
MIME_AUDIO_MP4,
|
|
9
|
-
MIME_APPLICATION_OCTET,
|
|
10
|
-
MIME_DEFAULT,
|
|
11
|
-
MIME_TO_EXTENSION,
|
|
12
|
-
getExtensionForMime,
|
|
13
|
-
} from "./mime.constants";
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utils Index
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
mapPrunaError,
|
|
7
|
-
isPrunaErrorRetryable,
|
|
8
|
-
getErrorMessage,
|
|
9
|
-
getErrorMessageOr,
|
|
10
|
-
formatErrorMessage,
|
|
11
|
-
} from "./pruna-error-handler.util";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
isPrunaModelId,
|
|
15
|
-
isPrunaErrorType,
|
|
16
|
-
isValidApiKey,
|
|
17
|
-
isValidModelId,
|
|
18
|
-
isValidPrompt,
|
|
19
|
-
isValidTimeout,
|
|
20
|
-
} from "./type-guards";
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
isDefined,
|
|
24
|
-
removeNullish,
|
|
25
|
-
generateUniqueId,
|
|
26
|
-
sleep,
|
|
27
|
-
} from "./helpers";
|
|
28
|
-
|
|
29
|
-
export { generationLogCollector } from "./log-collector";
|
|
30
|
-
export type { LogEntry } from "./log-collector";
|
|
31
|
-
|
|
32
|
-
export { detectMimeType } from "./mime-detection.util";
|
|
33
|
-
export {
|
|
34
|
-
MIME_TO_EXTENSION,
|
|
35
|
-
getExtensionForMime,
|
|
36
|
-
} from "./constants/mime.constants";
|