@zodic/shared 0.0.345 → 0.0.347
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/app/api/index.ts +128 -39
- package/package.json +1 -1
package/app/api/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
+
import { v4 } from 'uuid';
|
|
2
3
|
import {
|
|
3
4
|
BackendBindings,
|
|
4
5
|
BatchInputItem,
|
|
@@ -785,46 +786,115 @@ export const Api = (env: BackendBindings) => ({
|
|
|
785
786
|
'x-api-key': `${env.PIAPI_API_KEY}`,
|
|
786
787
|
'Content-Type': 'application/json',
|
|
787
788
|
};
|
|
788
|
-
|
|
789
|
+
|
|
789
790
|
// Validate image URLs before making the request
|
|
790
|
-
const validateImageUrl = async (
|
|
791
|
+
const validateImageUrl = async (
|
|
792
|
+
url: string,
|
|
793
|
+
label: string
|
|
794
|
+
): Promise<void> => {
|
|
791
795
|
try {
|
|
792
796
|
const response = await fetch(url, { method: 'HEAD' });
|
|
793
797
|
if (!response.ok) {
|
|
794
|
-
throw new Error(
|
|
798
|
+
throw new Error(
|
|
799
|
+
`${label} is inaccessible: ${response.status} ${response.statusText}`
|
|
800
|
+
);
|
|
795
801
|
}
|
|
796
802
|
const contentType = response.headers.get('Content-Type');
|
|
797
803
|
if (!contentType || !contentType.startsWith('image/')) {
|
|
798
|
-
throw new Error(
|
|
804
|
+
throw new Error(
|
|
805
|
+
`${label} is not a valid image: Content-Type is ${contentType}`
|
|
806
|
+
);
|
|
799
807
|
}
|
|
800
808
|
} catch (err: any) {
|
|
801
809
|
console.error(`Error validating ${label}:`, err.message);
|
|
802
810
|
throw new Error(`Invalid ${label}: ${err.message}`);
|
|
803
811
|
}
|
|
804
812
|
};
|
|
805
|
-
|
|
806
|
-
// Helper function to
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
813
|
+
|
|
814
|
+
// Helper function to resize an image by calling the Express server
|
|
815
|
+
const resizeImage = async (
|
|
816
|
+
url: string,
|
|
817
|
+
label: string
|
|
818
|
+
): Promise<ArrayBuffer> => {
|
|
819
|
+
const resizeEndpoint = `https://zodiako-image-mounter.onrender.com/resize-image?url=${encodeURIComponent(
|
|
820
|
+
url
|
|
821
|
+
)}`; // Replace with your Express server URL
|
|
822
|
+
try {
|
|
823
|
+
const response = await fetch(resizeEndpoint);
|
|
824
|
+
if (!response.ok) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`${label} resize failed: ${response.status} ${response.statusText}`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const contentType = response.headers.get('Content-Type');
|
|
830
|
+
if (!contentType || !contentType.startsWith('image/')) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`${label} resize response is not an image: Content-Type is ${contentType}`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
return await response.arrayBuffer();
|
|
836
|
+
} catch (err: any) {
|
|
837
|
+
console.error(`Error resizing ${label}:`, err.message);
|
|
838
|
+
throw new Error(`Failed to resize ${label}: ${err.message}`);
|
|
839
|
+
}
|
|
817
840
|
};
|
|
818
|
-
|
|
841
|
+
|
|
842
|
+
const getKey = (filePath: string) => {
|
|
843
|
+
const fileNameParts = filePath.split('.');
|
|
844
|
+
const extension = fileNameParts.pop();
|
|
845
|
+
const fileNameWithoutExtension = fileNameParts.join('.');
|
|
846
|
+
|
|
847
|
+
const newFileName = `${fileNameWithoutExtension}-${v4()}.${extension}`;
|
|
848
|
+
const key = `${newFileName.replace(/ /g, '_')}`;
|
|
849
|
+
|
|
850
|
+
return key;
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// Helper function to upload an image to storage and get a public URL
|
|
854
|
+
const uploadImageToStorage = async (
|
|
855
|
+
imageData: ArrayBuffer,
|
|
856
|
+
label: string
|
|
857
|
+
): Promise<string> => {
|
|
858
|
+
const key = getKey(label);
|
|
859
|
+
await env.PHOTOS_BUCKET.put(key, imageData);
|
|
860
|
+
const imageUrl = `https://pub-c4bb4b3e4ccd46b59eed7fa434c59b60.r2.dev/${key}`;
|
|
861
|
+
return imageUrl;
|
|
862
|
+
// Replace this with your actual storage service implementation (e.g., Cloudflare R2, AWS S3)
|
|
863
|
+
// Example for Cloudflare R2:
|
|
864
|
+
// const r2Response = await env.R2_BUCKET.put(fileName, imageData, {
|
|
865
|
+
// httpMetadata: { contentType: 'image/jpeg' },
|
|
866
|
+
// });
|
|
867
|
+
// return `https://your-r2-bucket-url/${fileName}`;
|
|
868
|
+
|
|
869
|
+
// For now, we'll throw an error if this isn't implemented
|
|
870
|
+
throw new Error(`Storage service not implemented for ${label}`);
|
|
871
|
+
};
|
|
872
|
+
|
|
819
873
|
try {
|
|
820
874
|
// Validate image URLs
|
|
821
875
|
await validateImageUrl(sourceImageUrl, 'Source image URL');
|
|
822
876
|
await validateImageUrl(targetImageUrl, 'Target image URL');
|
|
823
|
-
|
|
824
|
-
//
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
877
|
+
|
|
878
|
+
// Resize images by calling the Express server
|
|
879
|
+
const resizedSourceData = await resizeImage(
|
|
880
|
+
sourceImageUrl,
|
|
881
|
+
'Source image'
|
|
882
|
+
);
|
|
883
|
+
const resizedTargetData = await resizeImage(
|
|
884
|
+
targetImageUrl,
|
|
885
|
+
'Target image'
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
// Upload resized images to storage to get public URLs
|
|
889
|
+
const resizedSourceUrl = await uploadImageToStorage(
|
|
890
|
+
resizedSourceData,
|
|
891
|
+
'Source image'
|
|
892
|
+
);
|
|
893
|
+
const resizedTargetUrl = await uploadImageToStorage(
|
|
894
|
+
resizedTargetData,
|
|
895
|
+
'Target image'
|
|
896
|
+
);
|
|
897
|
+
|
|
828
898
|
const body = JSON.stringify({
|
|
829
899
|
model: 'Qubico/image-toolkit',
|
|
830
900
|
type: 'face-swap',
|
|
@@ -834,28 +904,34 @@ export const Api = (env: BackendBindings) => ({
|
|
|
834
904
|
},
|
|
835
905
|
config: {
|
|
836
906
|
webhook_config: {
|
|
837
|
-
endpoint:
|
|
907
|
+
endpoint:
|
|
908
|
+
'https://zodic-backend.lucdelbel.workers.dev/api/webhook/faceswap',
|
|
838
909
|
// secret: '',
|
|
839
910
|
},
|
|
840
911
|
},
|
|
841
912
|
});
|
|
842
|
-
|
|
843
|
-
console.log('Sending FaceSwap request:', {
|
|
844
|
-
|
|
913
|
+
|
|
914
|
+
console.log('Sending FaceSwap request:', {
|
|
915
|
+
resizedSourceUrl,
|
|
916
|
+
resizedTargetUrl,
|
|
917
|
+
});
|
|
918
|
+
|
|
845
919
|
const response = await fetch(endpoint, {
|
|
846
920
|
method: 'POST',
|
|
847
921
|
headers,
|
|
848
922
|
body,
|
|
849
923
|
});
|
|
850
|
-
|
|
924
|
+
|
|
851
925
|
// Log the HTTP status and headers for debugging
|
|
852
926
|
console.log('PiAPI FaceSwap Response Status:', response.status);
|
|
853
|
-
console.log('PiAPI FaceSwap Response Headers:', [
|
|
854
|
-
|
|
927
|
+
console.log('PiAPI FaceSwap Response Headers:', [
|
|
928
|
+
...response.headers.entries(),
|
|
929
|
+
]);
|
|
930
|
+
|
|
855
931
|
// Read the response body as text first to inspect it
|
|
856
932
|
const responseText = await response.text();
|
|
857
933
|
console.log('PiAPI FaceSwap Raw Response Body:', responseText);
|
|
858
|
-
|
|
934
|
+
|
|
859
935
|
// Check for the expected 201 Created status
|
|
860
936
|
if (response.status !== 201) {
|
|
861
937
|
let errorData;
|
|
@@ -865,32 +941,45 @@ export const Api = (env: BackendBindings) => ({
|
|
|
865
941
|
errorData = { message: responseText || 'Unknown error' };
|
|
866
942
|
}
|
|
867
943
|
console.error('Error from PiAPI FaceSwap:', errorData);
|
|
868
|
-
throw new Error(
|
|
944
|
+
throw new Error(
|
|
945
|
+
`PiAPI FaceSwap Error: ${response.status} - ${
|
|
946
|
+
errorData.message || 'Unknown error'
|
|
947
|
+
}`
|
|
948
|
+
);
|
|
869
949
|
}
|
|
870
|
-
|
|
950
|
+
|
|
871
951
|
// Check if the response body is empty
|
|
872
952
|
if (!responseText) {
|
|
873
953
|
console.error('PiAPI FaceSwap response body is empty');
|
|
874
954
|
throw new Error('PiAPI FaceSwap Error: Empty response body');
|
|
875
955
|
}
|
|
876
|
-
|
|
956
|
+
|
|
877
957
|
// Try to parse the response as JSON
|
|
878
958
|
let data;
|
|
879
959
|
try {
|
|
880
|
-
data = JSON.parse(responseText) as {
|
|
960
|
+
data = JSON.parse(responseText) as {
|
|
961
|
+
task_id: string;
|
|
962
|
+
message: string;
|
|
963
|
+
};
|
|
881
964
|
} catch (err) {
|
|
882
|
-
console.error(
|
|
965
|
+
console.error(
|
|
966
|
+
'Failed to parse PiAPI FaceSwap response as JSON:',
|
|
967
|
+
responseText
|
|
968
|
+
);
|
|
883
969
|
throw new Error('PiAPI FaceSwap Error: Invalid JSON response');
|
|
884
970
|
}
|
|
885
|
-
|
|
971
|
+
|
|
886
972
|
// Validate the response structure
|
|
887
973
|
if (!data.task_id || !data.message) {
|
|
888
|
-
console.error(
|
|
974
|
+
console.error(
|
|
975
|
+
'PiAPI FaceSwap response missing required fields:',
|
|
976
|
+
data
|
|
977
|
+
);
|
|
889
978
|
throw new Error('PiAPI FaceSwap Error: Invalid response structure');
|
|
890
979
|
}
|
|
891
|
-
|
|
980
|
+
|
|
892
981
|
console.log('FaceSwap task created successfully:', data);
|
|
893
|
-
|
|
982
|
+
|
|
894
983
|
return {
|
|
895
984
|
taskId: data.task_id,
|
|
896
985
|
message: data.message,
|