@umituz/react-native-ai-pruna-provider 1.0.0

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.
@@ -0,0 +1,169 @@
1
+ /**
2
+ * usePrunaGeneration Hook
3
+ * React hook for Pruna AI generation operations
4
+ */
5
+
6
+ import { useState, useCallback, useRef, useEffect } from "react";
7
+ import { prunaProvider } from "../../infrastructure/services/pruna-provider";
8
+ import { mapPrunaError } from "../../infrastructure/utils/pruna-error-handler.util";
9
+ import { PrunaGenerationStateManager } from "../../infrastructure/utils/pruna-generation-state-manager.util";
10
+ import type { PrunaJobInput, PrunaQueueStatus } from "../../domain/entities/pruna.types";
11
+ import type { PrunaErrorInfo } from "../../domain/entities/error.types";
12
+ import type { JobStatus, AILogEntry } from "../../domain/types";
13
+
14
+ export interface UsePrunaGenerationOptions {
15
+ timeoutMs?: number;
16
+ onProgress?: (status: PrunaQueueStatus) => void;
17
+ onError?: (error: PrunaErrorInfo) => void;
18
+ }
19
+
20
+ export interface UsePrunaGenerationResult<T> {
21
+ data: T | null;
22
+ error: PrunaErrorInfo | null;
23
+ isLoading: boolean;
24
+ isRetryable: boolean;
25
+ requestId: string | null;
26
+ isCancelling: boolean;
27
+ generate: (model: string, input: PrunaJobInput) => Promise<T | null>;
28
+ retry: () => Promise<T | null>;
29
+ cancel: () => void;
30
+ reset: () => void;
31
+ }
32
+
33
+ function convertJobStatusToPrunaQueueStatus(status: JobStatus, currentRequestId: string | null): PrunaQueueStatus {
34
+ return {
35
+ status: status.status as PrunaQueueStatus["status"],
36
+ requestId: status.requestId ?? currentRequestId ?? "",
37
+ logs: status.logs?.map((log: AILogEntry) => ({
38
+ message: log.message,
39
+ level: log.level,
40
+ timestamp: log.timestamp,
41
+ })),
42
+ };
43
+ }
44
+
45
+ export function usePrunaGeneration<T = unknown>(
46
+ options?: UsePrunaGenerationOptions
47
+ ): UsePrunaGenerationResult<T> {
48
+ const [data, setData] = useState<T | null>(null);
49
+ const [error, setError] = useState<PrunaErrorInfo | null>(null);
50
+ const [isLoading, setIsLoading] = useState(false);
51
+ const [isCancelling, setIsCancelling] = useState(false);
52
+
53
+ const stateManagerRef = useRef<PrunaGenerationStateManager<T> | null>(null);
54
+ const optionsRef = useRef(options);
55
+
56
+ useEffect(() => {
57
+ optionsRef.current = options;
58
+ }, [options]);
59
+
60
+ useEffect(() => {
61
+ const stateManager = new PrunaGenerationStateManager<T>({
62
+ onProgress: (status) => {
63
+ optionsRef.current?.onProgress?.(status);
64
+ },
65
+ });
66
+
67
+ stateManager.setIsMounted(true);
68
+ stateManagerRef.current = stateManager;
69
+
70
+ return () => {
71
+ if (stateManagerRef.current) {
72
+ stateManagerRef.current.setIsMounted(false);
73
+ stateManagerRef.current = null;
74
+ }
75
+
76
+ if (prunaProvider.hasRunningRequest()) {
77
+ try {
78
+ prunaProvider.cancelCurrentRequest();
79
+ } catch (error) {
80
+ console.warn('[usePrunaGeneration] Error cancelling request on unmount:', error);
81
+ }
82
+ }
83
+ };
84
+ }, []);
85
+
86
+ const generate = useCallback(
87
+ async (model: string, input: PrunaJobInput): Promise<T | null> => {
88
+ const stateManager = stateManagerRef.current;
89
+ if (!stateManager || !stateManager.checkMounted()) return null;
90
+
91
+ stateManager.setLastRequest(model, input);
92
+ setIsLoading(true);
93
+ setError(null);
94
+ setData(null);
95
+ stateManager.setCurrentRequestId(null);
96
+ setIsCancelling(false);
97
+
98
+ try {
99
+ const result = await prunaProvider.subscribe<T>(model, input, {
100
+ timeoutMs: optionsRef.current?.timeoutMs,
101
+ onQueueUpdate: (status: JobStatus) => {
102
+ const prunaStatus = convertJobStatusToPrunaQueueStatus(
103
+ status,
104
+ stateManager.getCurrentRequestId()
105
+ );
106
+ stateManager.handleQueueUpdate(prunaStatus);
107
+ },
108
+ });
109
+
110
+ if (!stateManager.checkMounted()) return null;
111
+ setData(result);
112
+ return result;
113
+ } catch (err) {
114
+ if (!stateManager.checkMounted()) return null;
115
+ const errorInfo = mapPrunaError(err);
116
+ setError(errorInfo);
117
+ optionsRef.current?.onError?.(errorInfo);
118
+ return null;
119
+ } finally {
120
+ if (stateManager.checkMounted()) {
121
+ setIsLoading(false);
122
+ setIsCancelling(false);
123
+ }
124
+ }
125
+ },
126
+ []
127
+ );
128
+
129
+ const retry = useCallback(async (): Promise<T | null> => {
130
+ const stateManager = stateManagerRef.current;
131
+ if (!stateManager) return null;
132
+
133
+ const lastRequest = stateManager.getLastRequest();
134
+ if (!lastRequest) return null;
135
+
136
+ return generate(lastRequest.endpoint, lastRequest.input);
137
+ }, [generate]);
138
+
139
+ const cancel = useCallback(() => {
140
+ if (prunaProvider.hasRunningRequest()) {
141
+ setIsCancelling(true);
142
+ prunaProvider.cancelCurrentRequest();
143
+ }
144
+ }, []);
145
+
146
+ const reset = useCallback(() => {
147
+ cancel();
148
+ setData(null);
149
+ setError(null);
150
+ setIsLoading(false);
151
+ setIsCancelling(false);
152
+ stateManagerRef.current?.clearLastRequest();
153
+ }, [cancel]);
154
+
155
+ const requestId = stateManagerRef.current?.getCurrentRequestId() ?? null;
156
+
157
+ return {
158
+ data,
159
+ error,
160
+ isLoading,
161
+ isRetryable: error?.retryable ?? false,
162
+ requestId,
163
+ isCancelling,
164
+ generate,
165
+ retry,
166
+ cancel,
167
+ reset,
168
+ };
169
+ }