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