@umituz/react-native-ai-pruna-provider 1.0.64 → 1.0.65
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/dto/pruna.dto.ts +58 -0
- package/src/application/services/pruna-service.ts +81 -0
- package/src/application/use-cases/generate-image-edit.use-case.ts +165 -0
- package/src/application/use-cases/generate-image.use-case.ts +125 -0
- package/src/application/use-cases/generate-video.use-case.ts +147 -0
- package/src/domain/services/error-mapper.domain-service.ts +204 -0
- package/src/domain/services/validation.domain-service.ts +93 -0
- package/src/domain/value-objects/api-key.value.ts +30 -0
- package/src/domain/value-objects/model-id.value.ts +41 -0
- package/src/domain/value-objects/session-id.value.ts +22 -0
- package/src/index.new.ts +65 -0
- package/src/infrastructure/api/http-client.ts +111 -0
- package/src/infrastructure/logging/pruna-logger.ts +100 -0
- package/src/infrastructure/storage/file-storage.ts +97 -0
- package/src/presentation/hooks/use-pruna-generation.new.ts +182 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Storage Infrastructure
|
|
3
|
+
* Handles file uploads to Pruna's storage service
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { httpClient } from "../api/http-client";
|
|
7
|
+
import { logger } from "../logging/pruna-logger";
|
|
8
|
+
import { PRUNA_FILES_URL, UPLOAD_CONFIG } from "../../services/pruna-provider.constants";
|
|
9
|
+
|
|
10
|
+
export class FileStorageService {
|
|
11
|
+
async uploadFile(
|
|
12
|
+
base64Data: string,
|
|
13
|
+
apiKey: string,
|
|
14
|
+
sessionId: string
|
|
15
|
+
): Promise<string> {
|
|
16
|
+
const log = logger;
|
|
17
|
+
|
|
18
|
+
// Validation
|
|
19
|
+
if (!base64Data?.trim()) {
|
|
20
|
+
log.error(sessionId, 'file-storage', 'Empty file data');
|
|
21
|
+
throw new Error("File data is empty. Provide a base64 string or URL.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Already a URL
|
|
25
|
+
if (base64Data.startsWith('http')) {
|
|
26
|
+
log.info(sessionId, 'file-storage', 'File already a URL', {
|
|
27
|
+
url: base64Data.substring(0, 80) + '...',
|
|
28
|
+
});
|
|
29
|
+
return base64Data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Process base64
|
|
33
|
+
const rawBase64 = this.extractBase64(base64Data);
|
|
34
|
+
const dataUri = this.createDataUri(rawBase64);
|
|
35
|
+
const formData = this.createFormData(dataUri);
|
|
36
|
+
|
|
37
|
+
log.info(sessionId, 'file-storage', 'Uploading file', {
|
|
38
|
+
size: Math.round(rawBase64.length / 1024) + 'KB',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await httpClient.request<{ urls?: { get?: string }; id?: string }>(
|
|
43
|
+
{
|
|
44
|
+
url: PRUNA_FILES_URL,
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { apikey: apiKey },
|
|
47
|
+
body: formData,
|
|
48
|
+
timeout: UPLOAD_CONFIG.timeoutMs,
|
|
49
|
+
},
|
|
50
|
+
sessionId,
|
|
51
|
+
'file-storage'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const fileUrl = response.data.urls?.get ||
|
|
55
|
+
(response.data.id ? `${PRUNA_FILES_URL}/${response.data.id}` : PRUNA_FILES_URL);
|
|
56
|
+
|
|
57
|
+
log.info(sessionId, 'file-storage', 'Upload complete', {
|
|
58
|
+
url: fileUrl.substring(0, 80) + '...',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return fileUrl;
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
log.error(sessionId, 'file-storage', 'Upload failed', {
|
|
65
|
+
error: error instanceof Error ? error.message : String(error),
|
|
66
|
+
});
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private extractBase64(data: string): string {
|
|
72
|
+
const base64Index = data.indexOf('base64,');
|
|
73
|
+
return base64Index !== -1 ? data.substring(base64Index + 7) : data;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private createDataUri(base64: string): string {
|
|
77
|
+
return `data:image/jpeg;base64,${base64}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private createFormData(dataUri: string): FormData {
|
|
81
|
+
const formData = new FormData();
|
|
82
|
+
const timestamp = Date.now();
|
|
83
|
+
const randomId = Math.random().toString(36).substring(2, 8);
|
|
84
|
+
const fileName = `vivoim_${timestamp}_${randomId}.jpg`;
|
|
85
|
+
|
|
86
|
+
(formData as unknown as { append: (name: string, value: { uri: string; type: string; name: string }) => void })
|
|
87
|
+
.append('content', {
|
|
88
|
+
uri: dataUri,
|
|
89
|
+
type: 'image/jpeg',
|
|
90
|
+
name: fileName,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return formData;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const fileStorageService = new FileStorageService();
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
}
|