@fractalq/client 2.1.1
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/LICENSE +21 -0
- package/README.md +55 -0
- package/examples/async-callback.js +88 -0
- package/examples/basic-usage.js +29 -0
- package/examples/image-upscaling.js +48 -0
- package/examples/pipeline-processing.js +51 -0
- package/examples/react-component.jsx +97 -0
- package/examples/smoke-config.mjs +26 -0
- package/index.d.ts +81 -0
- package/index.js +45 -0
- package/package.json +65 -0
- package/src/client/FractalQClient.js +132 -0
- package/src/client/authorization.js +11 -0
- package/src/client/build-direct-body.js +10 -0
- package/src/client/build-proxy-body.js +26 -0
- package/src/client/effective-job-id.js +9 -0
- package/src/client/execute-response.js +18 -0
- package/src/client/execute-url.js +9 -0
- package/src/client/http-error.js +12 -0
- package/src/client/resolve-base-url.js +39 -0
- package/src/client/temp-inputs.js +20 -0
- package/src/config/api-key.js +13 -0
- package/src/config/base-url.js +17 -0
- package/src/config/index.js +21 -0
- package/src/config/node-url.js +8 -0
- package/src/config/session-lookup.js +26 -0
- package/src/image/ImageProcessor.js +86 -0
- package/src/image/extract-upscaled-output.js +5 -0
- package/src/image/file-to-base64.js +15 -0
- package/src/image/index.js +2 -0
- package/src/image/prepare-image-input.js +11 -0
- package/src/image/resolve-callback-url.js +10 -0
- package/src/pipeline/PipelineProcessor.js +32 -0
- package/src/pipeline/apply-stage-result.js +9 -0
- package/src/pipeline/index.js +2 -0
- package/src/pipeline/merge-node-inputs.js +9 -0
- package/src/storage/firebase/image-to-blob.js +21 -0
- package/src/storage/firebase/index.js +2 -0
- package/src/storage/firebase/instance.js +52 -0
- package/src/storage/firebase/upload-task.js +31 -0
- package/src/storage/firebase/upload-to-firebase.js +33 -0
- package/src/storage/shared/base64-data-url-to-blob.js +24 -0
- package/src/storage/temp/TempFileManager.js +102 -0
- package/src/storage/temp/generate-filename.js +22 -0
- package/src/storage/temp/index.js +2 -0
- package/src/storage/temp/input-to-blob.js +18 -0
- package/src/storage/temp/mime-extension-map.js +12 -0
- package/src/storage/temp/upload-core.js +49 -0
- package/useFractalQ.d.ts +29 -0
- package/useFractalQ.js +126 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run resumable upload with optional progress callback (0–1).
|
|
5
|
+
*/
|
|
6
|
+
export function runResumableUpload(storageInstance, path, fileName, blob, onProgress) {
|
|
7
|
+
const storageRef = ref(storageInstance, `${path}/${fileName}`);
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const uploadTask = uploadBytesResumable(storageRef, blob);
|
|
10
|
+
uploadTask.on(
|
|
11
|
+
'state_changed',
|
|
12
|
+
(snapshot) => {
|
|
13
|
+
if (onProgress) {
|
|
14
|
+
onProgress(snapshot.bytesTransferred / snapshot.totalBytes);
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
(error) => {
|
|
18
|
+
console.error('Firebase upload error:', error);
|
|
19
|
+
reject(new Error(`Firebase upload failed: ${error.message}`));
|
|
20
|
+
},
|
|
21
|
+
async () => {
|
|
22
|
+
try {
|
|
23
|
+
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
|
|
24
|
+
resolve(downloadURL);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
reject(new Error(`Failed to get download URL: ${error.message}`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getStorageInstance } from './instance.js';
|
|
2
|
+
import { imageDataToBlob } from './image-to-blob.js';
|
|
3
|
+
import { runResumableUpload } from './upload-task.js';
|
|
4
|
+
|
|
5
|
+
export async function uploadToFirebase({
|
|
6
|
+
imageData,
|
|
7
|
+
path,
|
|
8
|
+
fileName,
|
|
9
|
+
onProgress,
|
|
10
|
+
firebaseStorage,
|
|
11
|
+
firebaseConfig,
|
|
12
|
+
}) {
|
|
13
|
+
try {
|
|
14
|
+
const storageInstance = getStorageInstance({ firebaseStorage, firebaseConfig });
|
|
15
|
+
if (!storageInstance) {
|
|
16
|
+
throw new Error('Firebase Storage is not initialized. Provide firebaseStorage or firebaseConfig in options.');
|
|
17
|
+
}
|
|
18
|
+
const blob = await imageDataToBlob(imageData);
|
|
19
|
+
return runResumableUpload(storageInstance, path, fileName, blob, onProgress);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('uploadToFirebase error:', error);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function uploadFileToFirebase(file, path, onProgress = null) {
|
|
27
|
+
return uploadToFirebase({
|
|
28
|
+
imageData: file,
|
|
29
|
+
path,
|
|
30
|
+
fileName: file.name,
|
|
31
|
+
onProgress,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a data URL into a Blob (browser).
|
|
3
|
+
*/
|
|
4
|
+
export function base64DataUrlToBlob(base64String) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
try {
|
|
7
|
+
const matches = base64String.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
|
|
8
|
+
if (!matches || matches.length !== 3) {
|
|
9
|
+
throw new Error('Invalid base64 string format');
|
|
10
|
+
}
|
|
11
|
+
const mimeType = matches[1];
|
|
12
|
+
const base64Data = matches[2];
|
|
13
|
+
const byteCharacters = atob(base64Data);
|
|
14
|
+
const byteNumbers = new Array(byteCharacters.length);
|
|
15
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
16
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
17
|
+
}
|
|
18
|
+
const byteArray = new Uint8Array(byteNumbers);
|
|
19
|
+
resolve(new Blob([byteArray], { type: mimeType }));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
reject(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporary file storage keyed by job id (Firebase Storage).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ref, deleteObject } from 'firebase/storage';
|
|
6
|
+
import { uploadBlobToTempPath } from './upload-core.js';
|
|
7
|
+
|
|
8
|
+
export class TempFileManager {
|
|
9
|
+
constructor(storageInstance, options = {}) {
|
|
10
|
+
if (!storageInstance) {
|
|
11
|
+
throw new Error('Firebase Storage instance is required');
|
|
12
|
+
}
|
|
13
|
+
this.storage = storageInstance;
|
|
14
|
+
this.tempPathPrefix = options.tempPathPrefix || 'temp/fractalq';
|
|
15
|
+
this.autoCleanup = options.autoCleanup !== false;
|
|
16
|
+
/** @type {Map<string, string[]>} */
|
|
17
|
+
this.trackedFiles = new Map();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async uploadTempFile(fileData, jobId, fileName = null, onProgress = null) {
|
|
21
|
+
if (!jobId) {
|
|
22
|
+
throw new Error('Job ID is required for temporary file management');
|
|
23
|
+
}
|
|
24
|
+
return uploadBlobToTempPath({
|
|
25
|
+
storage: this.storage,
|
|
26
|
+
tempPathPrefix: this.tempPathPrefix,
|
|
27
|
+
jobId,
|
|
28
|
+
fileName,
|
|
29
|
+
fileData,
|
|
30
|
+
onProgress,
|
|
31
|
+
trackFile: (id, path) => this.trackFile(id, path),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
trackFile(jobId, filePath) {
|
|
36
|
+
if (!this.trackedFiles.has(jobId)) {
|
|
37
|
+
this.trackedFiles.set(jobId, []);
|
|
38
|
+
}
|
|
39
|
+
this.trackedFiles.get(jobId).push(filePath);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async cleanupJob(jobId) {
|
|
43
|
+
if (!this.trackedFiles.has(jobId)) {
|
|
44
|
+
console.log(`ℹ️ No files tracked for job: ${jobId}`);
|
|
45
|
+
return { deleted: 0, errors: 0 };
|
|
46
|
+
}
|
|
47
|
+
const filePaths = this.trackedFiles.get(jobId);
|
|
48
|
+
let deleted = 0;
|
|
49
|
+
let errors = 0;
|
|
50
|
+
console.log(`🧹 Cleaning up ${filePaths.length} temporary files for job: ${jobId}`);
|
|
51
|
+
|
|
52
|
+
for (const filePath of filePaths) {
|
|
53
|
+
try {
|
|
54
|
+
const fileRef = ref(this.storage, filePath);
|
|
55
|
+
await deleteObject(fileRef);
|
|
56
|
+
deleted++;
|
|
57
|
+
console.log(`✅ Deleted temporary file: ${filePath}`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code !== 'storage/object-not-found') {
|
|
60
|
+
console.error(`❌ Failed to delete ${filePath}:`, error.message);
|
|
61
|
+
errors++;
|
|
62
|
+
} else {
|
|
63
|
+
deleted++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
ref(this.storage, `${this.tempPathPrefix}/${jobId}`);
|
|
70
|
+
} catch (_) {}
|
|
71
|
+
|
|
72
|
+
this.trackedFiles.delete(jobId);
|
|
73
|
+
console.log(`✅ Cleanup complete for job ${jobId}: ${deleted} deleted, ${errors} errors`);
|
|
74
|
+
return { deleted, errors };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async deleteFile(filePath) {
|
|
78
|
+
try {
|
|
79
|
+
const fileRef = ref(this.storage, filePath);
|
|
80
|
+
await deleteObject(fileRef);
|
|
81
|
+
console.log(`✅ Deleted file: ${filePath}`);
|
|
82
|
+
return true;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error.code === 'storage/object-not-found') {
|
|
85
|
+
console.log(`ℹ️ File already deleted: ${filePath}`);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
console.error(`❌ Failed to delete ${filePath}:`, error.message);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getTrackedFiles(jobId) {
|
|
94
|
+
return this.trackedFiles.get(jobId) || [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
clearTracking(jobId) {
|
|
98
|
+
this.trackedFiles.delete(jobId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default TempFileManager;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MIME_TO_EXT } from './mime-extension-map.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Infer extension from fileData; build unique name under job folder.
|
|
5
|
+
*/
|
|
6
|
+
export function generateTempFileName(fileData) {
|
|
7
|
+
const timestamp = Date.now();
|
|
8
|
+
const random = Math.random().toString(36).substring(2, 9);
|
|
9
|
+
let ext = 'bin';
|
|
10
|
+
if (fileData instanceof File) {
|
|
11
|
+
ext = fileData.name.split('.').pop() || 'bin';
|
|
12
|
+
} else if (fileData instanceof Blob) {
|
|
13
|
+
ext = fileData.type.split('/').pop() || 'bin';
|
|
14
|
+
} else if (typeof fileData === 'string' && fileData.startsWith('data:')) {
|
|
15
|
+
const mimeMatch = fileData.match(/^data:([^;]+)/);
|
|
16
|
+
if (mimeMatch) {
|
|
17
|
+
const mimeType = mimeMatch[1];
|
|
18
|
+
ext = MIME_TO_EXT[mimeType] || mimeType.split('/').pop() || 'bin';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return `${timestamp}_${random}.${ext}`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { base64DataUrlToBlob } from '../shared/base64-data-url-to-blob.js';
|
|
2
|
+
|
|
3
|
+
export async function tempInputToBlob(fileData) {
|
|
4
|
+
if (fileData instanceof Blob || fileData instanceof File) {
|
|
5
|
+
return fileData;
|
|
6
|
+
}
|
|
7
|
+
if (typeof fileData === 'string') {
|
|
8
|
+
if (fileData.startsWith('data:')) {
|
|
9
|
+
return base64DataUrlToBlob(fileData);
|
|
10
|
+
}
|
|
11
|
+
if (fileData.startsWith('http://') || fileData.startsWith('https://')) {
|
|
12
|
+
const response = await fetch(fileData);
|
|
13
|
+
return response.blob();
|
|
14
|
+
}
|
|
15
|
+
return base64DataUrlToBlob(`data:application/octet-stream;base64,${fileData}`);
|
|
16
|
+
}
|
|
17
|
+
throw new Error('Invalid file data format');
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Map data-URL / MIME hints to file extensions for temp uploads. */
|
|
2
|
+
export const MIME_TO_EXT = {
|
|
3
|
+
'image/jpeg': 'jpg',
|
|
4
|
+
'image/jpg': 'jpg',
|
|
5
|
+
'image/png': 'png',
|
|
6
|
+
'image/gif': 'gif',
|
|
7
|
+
'image/webp': 'webp',
|
|
8
|
+
'video/mp4': 'mp4',
|
|
9
|
+
'video/webm': 'webm',
|
|
10
|
+
'model/gltf-binary': 'glb',
|
|
11
|
+
'model/gltf+json': 'gltf',
|
|
12
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
|
|
2
|
+
import { generateTempFileName } from './generate-filename.js';
|
|
3
|
+
import { tempInputToBlob } from './input-to-blob.js';
|
|
4
|
+
|
|
5
|
+
export async function uploadBlobToTempPath({
|
|
6
|
+
storage,
|
|
7
|
+
tempPathPrefix,
|
|
8
|
+
jobId,
|
|
9
|
+
fileName,
|
|
10
|
+
fileData,
|
|
11
|
+
onProgress,
|
|
12
|
+
trackFile,
|
|
13
|
+
}) {
|
|
14
|
+
const resolvedName = fileName || generateTempFileName(fileData);
|
|
15
|
+
const tempPath = `${tempPathPrefix}/${jobId}/${resolvedName}`;
|
|
16
|
+
const storageRef = ref(storage, tempPath);
|
|
17
|
+
const blob = await tempInputToBlob(fileData);
|
|
18
|
+
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const uploadTask = uploadBytesResumable(storageRef, blob);
|
|
21
|
+
uploadTask.on(
|
|
22
|
+
'state_changed',
|
|
23
|
+
(snapshot) => {
|
|
24
|
+
if (onProgress) {
|
|
25
|
+
onProgress(snapshot.bytesTransferred / snapshot.totalBytes);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
(error) => {
|
|
29
|
+
console.error('Temp file upload error:', error);
|
|
30
|
+
reject(new Error(`Failed to upload temporary file: ${error.message}`));
|
|
31
|
+
},
|
|
32
|
+
async () => {
|
|
33
|
+
try {
|
|
34
|
+
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
|
|
35
|
+
trackFile(jobId, tempPath);
|
|
36
|
+
console.log(`✅ Temporary file uploaded: ${tempPath}`);
|
|
37
|
+
resolve({
|
|
38
|
+
url: downloadURL,
|
|
39
|
+
path: tempPath,
|
|
40
|
+
fileName: resolvedName,
|
|
41
|
+
jobId,
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
reject(new Error(`Failed to get download URL: ${error.message}`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
package/useFractalQ.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExecuteNodeOptions,
|
|
3
|
+
FractalQ,
|
|
4
|
+
FractalQClientOptions,
|
|
5
|
+
FractalQResponse,
|
|
6
|
+
} from './index.js';
|
|
7
|
+
|
|
8
|
+
export function useFractalQ(options?: FractalQClientOptions): {
|
|
9
|
+
client: FractalQ;
|
|
10
|
+
executeNode: (
|
|
11
|
+
nodeId: string | null,
|
|
12
|
+
inputs?: Record<string, unknown>,
|
|
13
|
+
executeOptions?: ExecuteNodeOptions
|
|
14
|
+
) => Promise<FractalQResponse>;
|
|
15
|
+
upscaleAndUpload: (
|
|
16
|
+
image: File | Blob | string,
|
|
17
|
+
uploadOptions?: Record<string, unknown>
|
|
18
|
+
) => Promise<string | Record<string, unknown>>;
|
|
19
|
+
executePipeline: (
|
|
20
|
+
nodePipeline: Array<Record<string, unknown>>,
|
|
21
|
+
pipelineOptions?: Record<string, unknown>
|
|
22
|
+
) => Promise<{ success: boolean; data?: unknown }>;
|
|
23
|
+
loading: boolean;
|
|
24
|
+
error: string | null;
|
|
25
|
+
progress: unknown;
|
|
26
|
+
reset: () => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default useFractalQ;
|
package/useFractalQ.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Hook for FractalQ Client
|
|
3
|
+
*
|
|
4
|
+
* Provides a convenient hook for using FractalQ in React components
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback, useRef } from 'react';
|
|
8
|
+
import FractalQ from './index.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* React hook for FractalQ API
|
|
12
|
+
* @param {Object} options - Client options
|
|
13
|
+
* @returns {Object} Hook interface
|
|
14
|
+
*/
|
|
15
|
+
export function useFractalQ(options = {}) {
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const [progress, setProgress] = useState(null);
|
|
19
|
+
|
|
20
|
+
// Create client instance (reuse if options don't change)
|
|
21
|
+
const clientRef = useRef(null);
|
|
22
|
+
if (!clientRef.current) {
|
|
23
|
+
clientRef.current = new FractalQ(options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute a node
|
|
28
|
+
*/
|
|
29
|
+
const executeNode = useCallback(async (nodeId, inputs = {}, executeOptions = {}) => {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
setError(null);
|
|
32
|
+
setProgress(null);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await clientRef.current.execute(nodeId, inputs, executeOptions);
|
|
36
|
+
setLoading(false);
|
|
37
|
+
return result;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setError(err.message);
|
|
40
|
+
setLoading(false);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Upscale and upload image
|
|
47
|
+
*/
|
|
48
|
+
const upscaleAndUpload = useCallback(async (image, uploadOptions = {}) => {
|
|
49
|
+
setLoading(true);
|
|
50
|
+
setError(null);
|
|
51
|
+
setProgress({ step: 'initializing', progress: 0 });
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const url = await clientRef.current.upscaleAndUpload(image, {
|
|
55
|
+
...uploadOptions,
|
|
56
|
+
onProgress: (progressInfo) => {
|
|
57
|
+
setProgress(progressInfo);
|
|
58
|
+
if (uploadOptions.onProgress) {
|
|
59
|
+
uploadOptions.onProgress(progressInfo);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
setLoading(false);
|
|
65
|
+
setProgress({ step: 'complete', progress: 1 });
|
|
66
|
+
return url;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
setError(err.message);
|
|
69
|
+
setLoading(false);
|
|
70
|
+
setProgress(null);
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Execute pipeline
|
|
77
|
+
*/
|
|
78
|
+
const executePipeline = useCallback(async (nodePipeline, pipelineOptions = {}) => {
|
|
79
|
+
setLoading(true);
|
|
80
|
+
setError(null);
|
|
81
|
+
setProgress(null);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const result = await clientRef.current.executePipeline(nodePipeline, {
|
|
85
|
+
...pipelineOptions,
|
|
86
|
+
onProgress: (progressInfo) => {
|
|
87
|
+
setProgress(progressInfo);
|
|
88
|
+
if (pipelineOptions.onProgress) {
|
|
89
|
+
pipelineOptions.onProgress(progressInfo);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
setLoading(false);
|
|
95
|
+
return result;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
setError(err.message);
|
|
98
|
+
setLoading(false);
|
|
99
|
+
setProgress(null);
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reset state
|
|
106
|
+
*/
|
|
107
|
+
const reset = useCallback(() => {
|
|
108
|
+
setLoading(false);
|
|
109
|
+
setError(null);
|
|
110
|
+
setProgress(null);
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
client: clientRef.current,
|
|
115
|
+
executeNode,
|
|
116
|
+
upscaleAndUpload,
|
|
117
|
+
executePipeline,
|
|
118
|
+
loading,
|
|
119
|
+
error,
|
|
120
|
+
progress,
|
|
121
|
+
reset,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default useFractalQ;
|
|
126
|
+
|