@umituz/react-native-ai-pruna-provider 1.0.65 → 1.0.66
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 +1 -1
- package/src/application/services/pruna-service.ts +0 -10
- package/src/domain/entities/error.types.ts +1 -0
- package/src/domain/services/error-mapper.domain-service.ts +4 -7
- package/src/domain/value-objects/api-key.value.ts +0 -5
- package/src/domain/value-objects/model-id.value.ts +0 -12
- package/src/domain/value-objects/session-id.value.ts +0 -4
- package/src/index.ts +1 -1
- package/src/infrastructure/logging/pruna-logger.ts +0 -11
- package/src/infrastructure/services/pruna-api-client.ts +34 -16
- package/src/infrastructure/services/pruna-provider.ts +0 -21
- package/src/infrastructure/services/request-store.ts +36 -33
- package/src/infrastructure/storage/file-storage.ts +1 -1
- package/src/infrastructure/utils/constants/mime.constants.ts +2 -3
- package/src/infrastructure/utils/log-collector.ts +10 -12
- package/src/init/createAiProviderInitModule.ts +4 -1
- package/src/init/initializePrunaProvider.ts +4 -1
- package/src/presentation/hooks/use-pruna-generation.ts +1 -5
- package/src/index.new.ts +0 -65
- package/src/presentation/hooks/use-pruna-generation.new.ts +0 -182
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.66",
|
|
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",
|
|
@@ -66,16 +66,6 @@ export class PrunaService {
|
|
|
66
66
|
const apiKey = this.ensureInitialized();
|
|
67
67
|
return generateImageEditUseCase.execute(input, apiKey.toString(), signal);
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
reset(): void {
|
|
71
|
-
this.apiKey = null;
|
|
72
|
-
this.initialized = false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
getSessionLogs(sessionId: string): unknown[] {
|
|
76
|
-
// Return logs for debugging
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
69
|
}
|
|
80
70
|
|
|
81
71
|
export const prunaService = new PrunaService();
|
|
@@ -178,18 +178,15 @@ export class ErrorMapperService {
|
|
|
178
178
|
stack?: string,
|
|
179
179
|
statusCode?: number
|
|
180
180
|
): PrunaErrorInfo {
|
|
181
|
-
|
|
181
|
+
return {
|
|
182
182
|
type,
|
|
183
183
|
messageKey,
|
|
184
184
|
retryable,
|
|
185
185
|
originalError,
|
|
186
|
+
...(originalErrorName && { originalErrorName }),
|
|
187
|
+
...(stack && { stack }),
|
|
188
|
+
...(statusCode !== undefined && { statusCode }),
|
|
186
189
|
};
|
|
187
|
-
|
|
188
|
-
if (originalErrorName) error.originalErrorName = originalErrorName;
|
|
189
|
-
if (stack) error.stack = stack;
|
|
190
|
-
if (statusCode !== undefined) error.statusCode = statusCode;
|
|
191
|
-
|
|
192
|
-
return error;
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
private static extractMessage(error: unknown): string {
|
|
@@ -26,16 +26,4 @@ export class ModelId {
|
|
|
26
26
|
toString(): string {
|
|
27
27
|
return this.value;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
isImage(): boolean {
|
|
31
|
-
return this.value === 'p-image';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
isImageEdit(): boolean {
|
|
35
|
-
return this.value === 'p-image-edit';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
isVideo(): boolean {
|
|
39
|
-
return this.value === 'p-video';
|
|
40
|
-
}
|
|
41
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export type { PrunaProvider as PrunaProviderType } from "./infrastructure/servic
|
|
|
52
52
|
export {
|
|
53
53
|
cleanupRequestStore,
|
|
54
54
|
stopAutomaticCleanup,
|
|
55
|
+
cleanupRequestIdMappings,
|
|
55
56
|
} from "./infrastructure/services/request-store";
|
|
56
57
|
export type { ActiveRequest } from "./infrastructure/services/request-store";
|
|
57
58
|
|
|
@@ -102,7 +103,6 @@ export {
|
|
|
102
103
|
MIME_AUDIO_WAV,
|
|
103
104
|
MIME_AUDIO_FLAC,
|
|
104
105
|
MIME_AUDIO_MP4,
|
|
105
|
-
MIME_APPLICATION_OCTET,
|
|
106
106
|
MIME_DEFAULT,
|
|
107
107
|
MIME_TO_EXTENSION,
|
|
108
108
|
getExtensionForMime,
|
|
@@ -87,14 +87,3 @@ export class PrunaLogger {
|
|
|
87
87
|
// Singleton instance
|
|
88
88
|
export const logger = PrunaLogger.getInstance();
|
|
89
89
|
|
|
90
|
-
// Convenience factory function
|
|
91
|
-
export function createLogger(tag: string) {
|
|
92
|
-
return {
|
|
93
|
-
info: (sessionId: string, message: string, data?: Record<string, unknown>) =>
|
|
94
|
-
logger.info(sessionId, tag, message, data),
|
|
95
|
-
warn: (sessionId: string, message: string, data?: Record<string, unknown>) =>
|
|
96
|
-
logger.warn(sessionId, tag, message, data),
|
|
97
|
-
error: (sessionId: string, message: string, data?: Record<string, unknown>) =>
|
|
98
|
-
logger.error(sessionId, tag, message, data),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
@@ -435,7 +435,10 @@ export async function pollForResult(
|
|
|
435
435
|
});
|
|
436
436
|
|
|
437
437
|
if (!statusRes.ok) {
|
|
438
|
-
|
|
438
|
+
// Only log non-retryable errors to reduce log noise
|
|
439
|
+
if (statusRes.status >= 400 && statusRes.status < 500) {
|
|
440
|
+
generationLogCollector.warn(sessionId, TAG, `Poll HTTP ${statusRes.status}, skipping...`);
|
|
441
|
+
}
|
|
439
442
|
continue;
|
|
440
443
|
}
|
|
441
444
|
|
|
@@ -449,21 +452,20 @@ export async function pollForResult(
|
|
|
449
452
|
}
|
|
450
453
|
} else if (statusData.status === 'failed') {
|
|
451
454
|
const errorMessage = statusData.error || "Generation failed during processing.";
|
|
452
|
-
generationLogCollector.error(sessionId, TAG, `Polling
|
|
455
|
+
generationLogCollector.error(sessionId, TAG, `Polling failed: ${errorMessage}`);
|
|
453
456
|
throw new Error(errorMessage);
|
|
454
457
|
}
|
|
455
458
|
|
|
456
|
-
//
|
|
457
|
-
if ((i + 1) %
|
|
458
|
-
generationLogCollector.log(sessionId, TAG, `
|
|
459
|
+
// Log progress only every 30 attempts to reduce overhead (every ~90 seconds)
|
|
460
|
+
if ((i + 1) % 30 === 0) {
|
|
461
|
+
generationLogCollector.log(sessionId, TAG, `Still processing (attempt ${i + 1}/${maxAttempts})`);
|
|
459
462
|
}
|
|
460
463
|
} catch (error) {
|
|
461
464
|
if (error instanceof Error && error.message.includes("cancelled by user")) {
|
|
462
465
|
throw error;
|
|
463
466
|
}
|
|
464
|
-
// Non-fatal poll error — continue polling
|
|
467
|
+
// Non-fatal poll error — continue polling silently
|
|
465
468
|
if (error instanceof Error && !error.message.includes("failed during processing")) {
|
|
466
|
-
generationLogCollector.warn(sessionId, TAG, `Poll attempt ${i + 1} error: ${error.message}`);
|
|
467
469
|
continue;
|
|
468
470
|
}
|
|
469
471
|
throw error;
|
|
@@ -478,15 +480,31 @@ export async function pollForResult(
|
|
|
478
480
|
* Checks multiple possible locations (priority order).
|
|
479
481
|
*/
|
|
480
482
|
export function extractUri(data: PrunaPredictionResponse): string | null {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
483
|
+
// Priority 1: Direct generation URL
|
|
484
|
+
if (data.generation_url) return data.generation_url;
|
|
485
|
+
|
|
486
|
+
// Priority 2: Output object URL
|
|
487
|
+
if (data.output && typeof data.output === 'object' && !Array.isArray(data.output)) {
|
|
488
|
+
const outputObj = data.output as { url?: string };
|
|
489
|
+
if (outputObj.url) return outputObj.url;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Priority 3: Output as string
|
|
493
|
+
if (typeof data.output === 'string') return data.output;
|
|
494
|
+
|
|
495
|
+
// Priority 4: Video URL
|
|
496
|
+
if (data.video_url) return data.video_url;
|
|
497
|
+
|
|
498
|
+
// Priority 5: First array element
|
|
499
|
+
if (Array.isArray(data.output) && data.output.length > 0) {
|
|
500
|
+
const firstElement = data.output[0];
|
|
501
|
+
if (typeof firstElement === 'string') return firstElement;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Priority 6: Data field
|
|
505
|
+
if (data.data) return data.data;
|
|
506
|
+
|
|
507
|
+
return null;
|
|
490
508
|
}
|
|
491
509
|
|
|
492
510
|
/**
|
|
@@ -300,33 +300,12 @@ export class PrunaProvider implements IAIProvider {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
reset(): void {
|
|
304
|
-
cancelAllRequests();
|
|
305
|
-
this.lastRequestKey = null;
|
|
306
|
-
this.apiKey = null;
|
|
307
|
-
this.initialized = false;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
303
|
cancelCurrentRequest(): void {
|
|
311
304
|
if (this.lastRequestKey) {
|
|
312
305
|
cancelRequest(this.lastRequestKey);
|
|
313
306
|
this.lastRequestKey = null;
|
|
314
307
|
}
|
|
315
308
|
}
|
|
316
|
-
|
|
317
|
-
hasRunningRequest(): boolean {
|
|
318
|
-
return hasActiveRequests();
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
getSessionLogs(sessionId?: string): LogEntry[] {
|
|
322
|
-
if (!sessionId) return [];
|
|
323
|
-
return generationLogCollector.getEntries(sessionId);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
endLogSession(sessionId?: string): LogEntry[] {
|
|
327
|
-
if (!sessionId) return [];
|
|
328
|
-
return generationLogCollector.endSession(sessionId);
|
|
329
|
-
}
|
|
330
309
|
}
|
|
331
310
|
|
|
332
311
|
export const prunaProvider = new PrunaProvider();
|
|
@@ -57,19 +57,16 @@ function getRequestKeyCache(): RequestKeyCache {
|
|
|
57
57
|
return globalObj[REQUEST_KEY_CACHE_KEY] as RequestKeyCache;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function sortKeys(obj: unknown): unknown {
|
|
61
|
-
if (obj === null || typeof obj !== "object") return obj;
|
|
62
|
-
if (Array.isArray(obj)) return obj.map(sortKeys);
|
|
63
|
-
const sorted: Record<string, unknown> = {};
|
|
64
|
-
for (const key of Object.keys(obj as Record<string, unknown>).sort()) {
|
|
65
|
-
sorted[key] = sortKeys((obj as Record<string, unknown>)[key]);
|
|
66
|
-
}
|
|
67
|
-
return sorted;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
60
|
function generateCacheKey(model: string, input: Record<string, unknown>): string {
|
|
71
|
-
// Fast hash using
|
|
72
|
-
|
|
61
|
+
// Fast deterministic hash using sorted property names
|
|
62
|
+
// Much faster than deep object sorting + JSON.stringify
|
|
63
|
+
const keys = Object.keys(input).sort();
|
|
64
|
+
let hash = model;
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
const value = input[key];
|
|
67
|
+
hash += `|${key}:${typeof value === 'object' ? JSON.stringify(value) : String(value)}`;
|
|
68
|
+
}
|
|
69
|
+
return hash;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
export function createRequestKey(model: string, input: Record<string, unknown>): string {
|
|
@@ -83,20 +80,11 @@ export function createRequestKey(model: string, input: Record<string, unknown>):
|
|
|
83
80
|
return cached.key;
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
// Cache miss or expired - generate new key
|
|
87
|
-
// Use
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
// Use base64 encoding for safer string representation
|
|
92
|
-
// This eliminates collision risk entirely while maintaining readability
|
|
93
|
-
const safeInputStr = inputStr.replace(/[^a-zA-Z0-9]/g, '_');
|
|
94
|
-
|
|
95
|
-
// Use first 64 chars to keep key length manageable while maintaining uniqueness
|
|
96
|
-
const prefix = safeInputStr.substring(0, 64);
|
|
97
|
-
const suffix = safeInputStr.length > 64 ? safeInputStr.slice(-64) : '';
|
|
98
|
-
|
|
99
|
-
const requestKey = `${model}:${prefix}${suffix ? '...' + suffix : ''}`;
|
|
83
|
+
// Cache miss or expired - generate new key using simple hash
|
|
84
|
+
// Use timestamp + random suffix for uniqueness (crypto-friendly)
|
|
85
|
+
const timestamp = Date.now();
|
|
86
|
+
const randomSuffix = Math.random().toString(36).substring(2, 10);
|
|
87
|
+
const requestKey = `${model}_${cacheKey.substring(0, 16)}_${timestamp}_${randomSuffix}`;
|
|
100
88
|
|
|
101
89
|
// Store in cache with LRU eviction
|
|
102
90
|
cache.set(cacheKey, { key: requestKey, timestamp: now });
|
|
@@ -112,13 +100,6 @@ export function createRequestKey(model: string, input: Record<string, unknown>):
|
|
|
112
100
|
return requestKey;
|
|
113
101
|
}
|
|
114
102
|
|
|
115
|
-
export function clearRequestKeyCache(): void {
|
|
116
|
-
const globalObj = globalThis as Record<string, unknown>;
|
|
117
|
-
if (globalObj[REQUEST_KEY_CACHE_KEY]) {
|
|
118
|
-
(globalObj[REQUEST_KEY_CACHE_KEY] as RequestKeyCache).clear();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
103
|
export function getExistingRequest<T>(key: string): ActiveRequest<T> | undefined {
|
|
123
104
|
return getRequestStore().get(key) as ActiveRequest<T> | undefined;
|
|
124
105
|
}
|
|
@@ -172,6 +153,9 @@ export function cleanupRequestStore(maxAge: number = MAX_REQUEST_AGE): number {
|
|
|
172
153
|
stopCleanupTimer();
|
|
173
154
|
}
|
|
174
155
|
|
|
156
|
+
// Also cleanup orphaned requestId mappings
|
|
157
|
+
cleanupRequestIdMappings(maxAge);
|
|
158
|
+
|
|
175
159
|
return cleanedCount;
|
|
176
160
|
}
|
|
177
161
|
|
|
@@ -229,6 +213,25 @@ export function removeRequestIdMapping(requestId: string): void {
|
|
|
229
213
|
getRequestIdMap().delete(requestId);
|
|
230
214
|
}
|
|
231
215
|
|
|
216
|
+
/** Cleanup old requestId mappings to prevent unbounded memory growth */
|
|
217
|
+
export function cleanupRequestIdMappings(maxAge: number = MAX_REQUEST_AGE): number {
|
|
218
|
+
const requestStore = getRequestStore();
|
|
219
|
+
const requestIdMap = getRequestIdMap();
|
|
220
|
+
const now = Date.now();
|
|
221
|
+
let cleanedCount = 0;
|
|
222
|
+
|
|
223
|
+
// Remove mappings for requests that no longer exist or are too old
|
|
224
|
+
for (const [requestId] of requestIdMap.entries()) {
|
|
225
|
+
const request = requestStore.get(requestId);
|
|
226
|
+
if (!request || (now - request.createdAt > maxAge)) {
|
|
227
|
+
requestIdMap.delete(requestId);
|
|
228
|
+
cleanedCount++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return cleanedCount;
|
|
233
|
+
}
|
|
234
|
+
|
|
232
235
|
// Clear any leftover timer on module load (hot reload safety)
|
|
233
236
|
if (typeof globalThis !== "undefined") {
|
|
234
237
|
const existingTimer = getCleanupTimer();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { httpClient } from "../api/http-client";
|
|
7
7
|
import { logger } from "../logging/pruna-logger";
|
|
8
|
-
import { PRUNA_FILES_URL, UPLOAD_CONFIG } from "
|
|
8
|
+
import { PRUNA_FILES_URL, UPLOAD_CONFIG } from "../services/pruna-provider.constants";
|
|
9
9
|
|
|
10
10
|
export class FileStorageService {
|
|
11
11
|
async uploadFile(
|
|
@@ -15,8 +15,7 @@ export const MIME_AUDIO_FLAC = 'audio/flac' as const;
|
|
|
15
15
|
export const MIME_AUDIO_MP4 = 'audio/mp4' as const;
|
|
16
16
|
|
|
17
17
|
// ── Fallback ────────────────────────────────────────────────
|
|
18
|
-
export const
|
|
19
|
-
export const MIME_DEFAULT = MIME_APPLICATION_OCTET;
|
|
18
|
+
export const MIME_DEFAULT = 'application/octet-stream' as const;
|
|
20
19
|
|
|
21
20
|
/** Maps MIME type → file extension for upload naming */
|
|
22
21
|
export const MIME_TO_EXTENSION: Readonly<Record<string, string>> = {
|
|
@@ -27,7 +26,7 @@ export const MIME_TO_EXTENSION: Readonly<Record<string, string>> = {
|
|
|
27
26
|
[MIME_AUDIO_WAV]: 'wav',
|
|
28
27
|
[MIME_AUDIO_FLAC]: 'flac',
|
|
29
28
|
[MIME_AUDIO_MP4]: 'm4a',
|
|
30
|
-
[
|
|
29
|
+
[MIME_DEFAULT]: 'bin',
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -30,20 +30,21 @@ const MAX_SESSIONS = 50;
|
|
|
30
30
|
|
|
31
31
|
class GenerationLogCollector {
|
|
32
32
|
private sessions = new Map<string, Session>();
|
|
33
|
-
private sessionQueue
|
|
33
|
+
private sessionQueue = new Set<string>(); // O(1) lookup and deletion
|
|
34
34
|
|
|
35
35
|
startSession(): string {
|
|
36
|
-
// Evict oldest session if limit exceeded
|
|
37
|
-
if (this.sessionQueue.
|
|
38
|
-
const oldestKey = this.sessionQueue.
|
|
36
|
+
// Evict oldest session if limit exceeded
|
|
37
|
+
if (this.sessionQueue.size >= MAX_SESSIONS) {
|
|
38
|
+
const oldestKey = this.sessionQueue.keys().next().value;
|
|
39
39
|
if (oldestKey) {
|
|
40
40
|
this.sessions.delete(oldestKey);
|
|
41
|
+
this.sessionQueue.delete(oldestKey);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const id = `pruna_session_${++sessionCounter}_${Date.now()}`;
|
|
45
46
|
this.sessions.set(id, { startTime: Date.now(), entries: [] });
|
|
46
|
-
this.sessionQueue.
|
|
47
|
+
this.sessionQueue.add(id);
|
|
47
48
|
return id;
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -60,20 +61,17 @@ class GenerationLogCollector {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
getEntries(sessionId: string): LogEntry[] {
|
|
63
|
-
return
|
|
64
|
+
return this.sessions.get(sessionId)?.entries ?? [];
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
endSession(sessionId: string): LogEntry[] {
|
|
67
68
|
const session = this.sessions.get(sessionId);
|
|
68
69
|
if (!session) return [];
|
|
69
|
-
const entries =
|
|
70
|
+
const entries = session.entries;
|
|
70
71
|
this.sessions.delete(sessionId);
|
|
71
72
|
|
|
72
|
-
// Remove from queue as well
|
|
73
|
-
|
|
74
|
-
if (queueIndex !== -1) {
|
|
75
|
-
this.sessionQueue.splice(queueIndex, 1);
|
|
76
|
-
}
|
|
73
|
+
// Remove from queue as well (O(1) with Set)
|
|
74
|
+
this.sessionQueue.delete(sessionId);
|
|
77
75
|
|
|
78
76
|
return entries;
|
|
79
77
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { providerRegistry } from '@umituz/react-native-ai-generation-content';
|
|
7
7
|
import { prunaProvider } from '../infrastructure/services/pruna-provider';
|
|
8
|
+
import { logger } from '../infrastructure/logging/pruna-logger';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* InitModule interface (from @umituz/react-native-design-system)
|
|
@@ -89,7 +90,9 @@ export function createAiProviderInitModule(
|
|
|
89
90
|
|
|
90
91
|
return true;
|
|
91
92
|
} catch (error) {
|
|
92
|
-
|
|
93
|
+
logger.error('app-init', 'ai-provider', 'Pruna initialization failed', {
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
});
|
|
93
96
|
throw error;
|
|
94
97
|
}
|
|
95
98
|
},
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { providerRegistry } from '@umituz/react-native-ai-generation-content';
|
|
7
7
|
import { prunaProvider } from '../infrastructure/services/pruna-provider';
|
|
8
|
+
import { logger } from '../infrastructure/logging/pruna-logger';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Initializes Pruna provider and registers it with providerRegistry in one call.
|
|
@@ -33,7 +34,9 @@ export function initializePrunaProvider(config: {
|
|
|
33
34
|
|
|
34
35
|
return true;
|
|
35
36
|
} catch (error) {
|
|
36
|
-
|
|
37
|
+
logger.error('app-init', 'pruna-provider', 'Initialization failed', {
|
|
38
|
+
error: error instanceof Error ? error.message : String(error),
|
|
39
|
+
});
|
|
37
40
|
throw error;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -34,11 +34,7 @@ function convertJobStatusToPrunaQueueStatus(status: JobStatus, currentRequestId:
|
|
|
34
34
|
return {
|
|
35
35
|
status: status.status as PrunaQueueStatus["status"],
|
|
36
36
|
requestId: status.requestId ?? currentRequestId ?? "",
|
|
37
|
-
logs: status.logs
|
|
38
|
-
message: log.message,
|
|
39
|
-
level: log.level,
|
|
40
|
-
timestamp: log.timestamp,
|
|
41
|
-
})),
|
|
37
|
+
logs: status.logs as PrunaQueueStatus["logs"], // Type-safe cast, no array copy
|
|
42
38
|
};
|
|
43
39
|
}
|
|
44
40
|
|
package/src/index.new.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pruna AI Provider - Refactored with DDD Architecture
|
|
3
|
-
*
|
|
4
|
-
* @module react-native-ai-pruna-provider
|
|
5
|
-
*
|
|
6
|
-
* Architecture:
|
|
7
|
-
* - Domain: Business logic, value objects, domain services
|
|
8
|
-
* - Application: Use cases, orchestration, DTOs
|
|
9
|
-
* - Infrastructure: API clients, storage, logging
|
|
10
|
-
* - Presentation: React hooks, UI integration
|
|
11
|
-
*
|
|
12
|
-
* Key Features:
|
|
13
|
-
* - Clean separation of concerns
|
|
14
|
-
* - Maximum 150 lines per file
|
|
15
|
-
* - No code duplication
|
|
16
|
-
* - Type-safe with TypeScript
|
|
17
|
-
* - Comprehensive error handling
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
// Core Service
|
|
21
|
-
export { prunaService, PrunaService } from './application/services/pruna-service';
|
|
22
|
-
export type {
|
|
23
|
-
PrunaConfig,
|
|
24
|
-
PrunaModel,
|
|
25
|
-
} from './application/services/pruna-service';
|
|
26
|
-
|
|
27
|
-
// React Hooks
|
|
28
|
-
export { usePrunaGeneration } from './presentation/hooks/use-pruna-generation.new';
|
|
29
|
-
export type { PrunaGenerationState } from './presentation/hooks/use-pruna-generation.new';
|
|
30
|
-
|
|
31
|
-
// DTOs
|
|
32
|
-
export type {
|
|
33
|
-
PrunaImageGenerationRequest,
|
|
34
|
-
PrunaVideoGenerationRequest,
|
|
35
|
-
PrunaImageEditRequest,
|
|
36
|
-
PrunaGenerationResponse,
|
|
37
|
-
PrunaGenerationOptions,
|
|
38
|
-
PrunaGenerationError,
|
|
39
|
-
} from './application/dto/pruna.dto';
|
|
40
|
-
|
|
41
|
-
// Domain Layer
|
|
42
|
-
export { SessionId } from './domain/value-objects/session-id.value';
|
|
43
|
-
export { ApiKey } from './domain/value-objects/api-key.value';
|
|
44
|
-
export { ModelId, PrunaModelId } from './domain/value-objects/model-id.value';
|
|
45
|
-
export { ValidationService, ValidationResult } from './domain/services/validation.domain-service';
|
|
46
|
-
export { ErrorMapperService } from './domain/services/error-mapper.domain-service';
|
|
47
|
-
|
|
48
|
-
// Infrastructure Layer
|
|
49
|
-
export { logger, createLogger, LogLevel } from './infrastructure/logging/pruna-logger';
|
|
50
|
-
export { httpClient, HttpClient } from './infrastructure/api/http-client';
|
|
51
|
-
export { fileStorageService, FileStorageService } from './infrastructure/storage/file-storage';
|
|
52
|
-
|
|
53
|
-
// Use Cases (for advanced usage)
|
|
54
|
-
export { generateImageUseCase, GenerateImageUseCase } from './application/use-cases/generate-image.use-case';
|
|
55
|
-
export { generateVideoUseCase, GenerateVideoUseCase } from './application/use-cases/generate-video.use-case';
|
|
56
|
-
export { generateImageEditUseCase, GenerateImageEditUseCase } from './application/use-cases/generate-image-edit.use-case';
|
|
57
|
-
|
|
58
|
-
// Legacy Exports (for backward compatibility)
|
|
59
|
-
export { prunaProvider as PrunaProvider } from './infrastructure/services/pruna-provider';
|
|
60
|
-
export type {
|
|
61
|
-
IAIProvider,
|
|
62
|
-
AIProviderConfig,
|
|
63
|
-
SubscribeOptions,
|
|
64
|
-
RunOptions,
|
|
65
|
-
} from './domain/types';
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pruna Generation Hook (Refactored)
|
|
3
|
-
* Clean React hook using new DDD architecture
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback, useRef } from 'react';
|
|
7
|
-
import { prunaService } from '../../application/services/pruna-service';
|
|
8
|
-
import type {
|
|
9
|
-
PrunaImageGenerationRequest,
|
|
10
|
-
PrunaVideoGenerationRequest,
|
|
11
|
-
PrunaImageEditRequest,
|
|
12
|
-
PrunaGenerationResponse,
|
|
13
|
-
PrunaGenerationError,
|
|
14
|
-
} from '../../application/dto/pruna.dto';
|
|
15
|
-
|
|
16
|
-
export interface PrunaGenerationState {
|
|
17
|
-
isGenerating: boolean;
|
|
18
|
-
progress: number;
|
|
19
|
-
status: string;
|
|
20
|
-
error: PrunaGenerationError | null;
|
|
21
|
-
result: PrunaGenerationResponse | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function usePrunaGeneration() {
|
|
25
|
-
const [state, setState] = useState<PrunaGenerationState>({
|
|
26
|
-
isGenerating: false,
|
|
27
|
-
progress: 0,
|
|
28
|
-
status: 'idle',
|
|
29
|
-
error: null,
|
|
30
|
-
result: null,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const abortControllerRef = useRef<AbortController | null>(null);
|
|
34
|
-
|
|
35
|
-
const generateImage = useCallback(async (request: PrunaImageGenerationRequest) => {
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
abortControllerRef.current = controller;
|
|
38
|
-
|
|
39
|
-
setState({
|
|
40
|
-
isGenerating: true,
|
|
41
|
-
progress: 0,
|
|
42
|
-
status: 'starting',
|
|
43
|
-
error: null,
|
|
44
|
-
result: null,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const response = await prunaService.generateImage(request, controller.signal);
|
|
49
|
-
setState({
|
|
50
|
-
isGenerating: false,
|
|
51
|
-
progress: 100,
|
|
52
|
-
status: 'completed',
|
|
53
|
-
error: null,
|
|
54
|
-
result: response,
|
|
55
|
-
});
|
|
56
|
-
return response;
|
|
57
|
-
} catch (error) {
|
|
58
|
-
const errorObj: PrunaGenerationError = {
|
|
59
|
-
type: 'unknown',
|
|
60
|
-
message: error instanceof Error ? error.message : String(error),
|
|
61
|
-
retryable: false,
|
|
62
|
-
originalError: error instanceof Error ? error.stack : undefined,
|
|
63
|
-
};
|
|
64
|
-
setState({
|
|
65
|
-
isGenerating: false,
|
|
66
|
-
progress: 0,
|
|
67
|
-
status: 'error',
|
|
68
|
-
error: errorObj,
|
|
69
|
-
result: null,
|
|
70
|
-
});
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
}, []);
|
|
74
|
-
|
|
75
|
-
const generateVideo = useCallback(async (request: PrunaVideoGenerationRequest) => {
|
|
76
|
-
const controller = new AbortController();
|
|
77
|
-
abortControllerRef.current = controller;
|
|
78
|
-
|
|
79
|
-
setState({
|
|
80
|
-
isGenerating: true,
|
|
81
|
-
progress: 0,
|
|
82
|
-
status: 'starting',
|
|
83
|
-
error: null,
|
|
84
|
-
result: null,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const response = await prunaService.generateVideo(request, controller.signal);
|
|
89
|
-
setState({
|
|
90
|
-
isGenerating: false,
|
|
91
|
-
progress: 100,
|
|
92
|
-
status: 'completed',
|
|
93
|
-
error: null,
|
|
94
|
-
result: response,
|
|
95
|
-
});
|
|
96
|
-
return response;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
const errorObj: PrunaGenerationError = {
|
|
99
|
-
type: 'unknown',
|
|
100
|
-
message: error instanceof Error ? error.message : String(error),
|
|
101
|
-
retryable: false,
|
|
102
|
-
originalError: error instanceof Error ? error.stack : undefined,
|
|
103
|
-
};
|
|
104
|
-
setState({
|
|
105
|
-
isGenerating: false,
|
|
106
|
-
progress: 0,
|
|
107
|
-
status: 'error',
|
|
108
|
-
error: errorObj,
|
|
109
|
-
result: null,
|
|
110
|
-
});
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
const generateImageEdit = useCallback(async (request: PrunaImageEditRequest) => {
|
|
116
|
-
const controller = new AbortController();
|
|
117
|
-
abortControllerRef.current = controller;
|
|
118
|
-
|
|
119
|
-
setState({
|
|
120
|
-
isGenerating: true,
|
|
121
|
-
progress: 0,
|
|
122
|
-
status: 'starting',
|
|
123
|
-
error: null,
|
|
124
|
-
result: null,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const response = await prunaService.generateImageEdit(request, controller.signal);
|
|
129
|
-
setState({
|
|
130
|
-
isGenerating: false,
|
|
131
|
-
progress: 100,
|
|
132
|
-
status: 'completed',
|
|
133
|
-
error: null,
|
|
134
|
-
result: response,
|
|
135
|
-
});
|
|
136
|
-
return response;
|
|
137
|
-
} catch (error) {
|
|
138
|
-
const errorObj: PrunaGenerationError = {
|
|
139
|
-
type: 'unknown',
|
|
140
|
-
message: error instanceof Error ? error.message : String(error),
|
|
141
|
-
retryable: false,
|
|
142
|
-
originalError: error instanceof Error ? error.stack : undefined,
|
|
143
|
-
};
|
|
144
|
-
setState({
|
|
145
|
-
isGenerating: false,
|
|
146
|
-
progress: 0,
|
|
147
|
-
status: 'error',
|
|
148
|
-
error: errorObj,
|
|
149
|
-
result: null,
|
|
150
|
-
});
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}, []);
|
|
154
|
-
|
|
155
|
-
const cancel = useCallback(() => {
|
|
156
|
-
abortControllerRef.current?.abort();
|
|
157
|
-
setState(prev => ({
|
|
158
|
-
...prev,
|
|
159
|
-
isGenerating: false,
|
|
160
|
-
status: 'cancelled',
|
|
161
|
-
}));
|
|
162
|
-
}, []);
|
|
163
|
-
|
|
164
|
-
const reset = useCallback(() => {
|
|
165
|
-
setState({
|
|
166
|
-
isGenerating: false,
|
|
167
|
-
progress: 0,
|
|
168
|
-
status: 'idle',
|
|
169
|
-
error: null,
|
|
170
|
-
result: null,
|
|
171
|
-
});
|
|
172
|
-
}, []);
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
state,
|
|
176
|
-
generateImage,
|
|
177
|
-
generateVideo,
|
|
178
|
-
generateImageEdit,
|
|
179
|
-
cancel,
|
|
180
|
-
reset,
|
|
181
|
-
};
|
|
182
|
-
}
|