@zodic/shared 0.0.346 → 0.0.348
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 +97 -46
- 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,49 @@ 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 = (label: string) => {
|
|
843
|
+
const timestamp = Date.now();
|
|
844
|
+
const key = `${label.toLowerCase().replace(/ /g, '_')}_${timestamp}.jpg`;
|
|
845
|
+
return key;
|
|
846
|
+
};
|
|
847
|
+
|
|
825
848
|
// Helper function to upload an image to storage and get a public URL
|
|
826
|
-
const uploadImageToStorage = async (
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
// For now, we'll throw an error if this isn't implemented
|
|
836
|
-
throw new Error(`Storage service not implemented for ${label}`);
|
|
849
|
+
const uploadImageToStorage = async (
|
|
850
|
+
imageData: ArrayBuffer,
|
|
851
|
+
label: string
|
|
852
|
+
): Promise<string> => {
|
|
853
|
+
const key = getKey(label);
|
|
854
|
+
await env.PHOTOS_BUCKET.put(key, imageData);
|
|
855
|
+
const imageUrl = `https://pub-c4bb4b3e4ccd46b59eed7fa434c59b60.r2.dev/${key}`;
|
|
856
|
+
return imageUrl;
|
|
837
857
|
};
|
|
838
|
-
|
|
858
|
+
|
|
839
859
|
try {
|
|
840
860
|
// Validate image URLs
|
|
841
861
|
await validateImageUrl(sourceImageUrl, 'Source image URL');
|
|
842
862
|
await validateImageUrl(targetImageUrl, 'Target image URL');
|
|
843
|
-
|
|
863
|
+
|
|
844
864
|
// Resize images by calling the Express server
|
|
845
|
-
const resizedSourceData = await resizeImage(
|
|
846
|
-
|
|
847
|
-
|
|
865
|
+
const resizedSourceData = await resizeImage(
|
|
866
|
+
sourceImageUrl,
|
|
867
|
+
'Source image'
|
|
868
|
+
);
|
|
869
|
+
const resizedTargetData = await resizeImage(
|
|
870
|
+
targetImageUrl,
|
|
871
|
+
'Target image'
|
|
872
|
+
);
|
|
873
|
+
|
|
848
874
|
// Upload resized images to storage to get public URLs
|
|
849
|
-
const resizedSourceUrl = await uploadImageToStorage(
|
|
850
|
-
|
|
851
|
-
|
|
875
|
+
const resizedSourceUrl = await uploadImageToStorage(
|
|
876
|
+
resizedSourceData,
|
|
877
|
+
'user_photo'
|
|
878
|
+
);
|
|
879
|
+
const resizedTargetUrl = await uploadImageToStorage(
|
|
880
|
+
resizedTargetData,
|
|
881
|
+
'generated_image'
|
|
882
|
+
);
|
|
883
|
+
|
|
852
884
|
const body = JSON.stringify({
|
|
853
885
|
model: 'Qubico/image-toolkit',
|
|
854
886
|
type: 'face-swap',
|
|
@@ -858,28 +890,34 @@ export const Api = (env: BackendBindings) => ({
|
|
|
858
890
|
},
|
|
859
891
|
config: {
|
|
860
892
|
webhook_config: {
|
|
861
|
-
endpoint:
|
|
893
|
+
endpoint:
|
|
894
|
+
'https://zodic-backend.lucdelbel.workers.dev/api/webhook/faceswap',
|
|
862
895
|
// secret: '',
|
|
863
896
|
},
|
|
864
897
|
},
|
|
865
898
|
});
|
|
866
|
-
|
|
867
|
-
console.log('Sending FaceSwap request:', {
|
|
868
|
-
|
|
899
|
+
|
|
900
|
+
console.log('Sending FaceSwap request:', {
|
|
901
|
+
resizedSourceUrl,
|
|
902
|
+
resizedTargetUrl,
|
|
903
|
+
});
|
|
904
|
+
|
|
869
905
|
const response = await fetch(endpoint, {
|
|
870
906
|
method: 'POST',
|
|
871
907
|
headers,
|
|
872
908
|
body,
|
|
873
909
|
});
|
|
874
|
-
|
|
910
|
+
|
|
875
911
|
// Log the HTTP status and headers for debugging
|
|
876
912
|
console.log('PiAPI FaceSwap Response Status:', response.status);
|
|
877
|
-
console.log('PiAPI FaceSwap Response Headers:', [
|
|
878
|
-
|
|
913
|
+
console.log('PiAPI FaceSwap Response Headers:', [
|
|
914
|
+
...response.headers.entries(),
|
|
915
|
+
]);
|
|
916
|
+
|
|
879
917
|
// Read the response body as text first to inspect it
|
|
880
918
|
const responseText = await response.text();
|
|
881
919
|
console.log('PiAPI FaceSwap Raw Response Body:', responseText);
|
|
882
|
-
|
|
920
|
+
|
|
883
921
|
// Check for the expected 201 Created status
|
|
884
922
|
if (response.status !== 201) {
|
|
885
923
|
let errorData;
|
|
@@ -889,32 +927,45 @@ export const Api = (env: BackendBindings) => ({
|
|
|
889
927
|
errorData = { message: responseText || 'Unknown error' };
|
|
890
928
|
}
|
|
891
929
|
console.error('Error from PiAPI FaceSwap:', errorData);
|
|
892
|
-
throw new Error(
|
|
930
|
+
throw new Error(
|
|
931
|
+
`PiAPI FaceSwap Error: ${response.status} - ${
|
|
932
|
+
errorData.message || 'Unknown error'
|
|
933
|
+
}`
|
|
934
|
+
);
|
|
893
935
|
}
|
|
894
|
-
|
|
936
|
+
|
|
895
937
|
// Check if the response body is empty
|
|
896
938
|
if (!responseText) {
|
|
897
939
|
console.error('PiAPI FaceSwap response body is empty');
|
|
898
940
|
throw new Error('PiAPI FaceSwap Error: Empty response body');
|
|
899
941
|
}
|
|
900
|
-
|
|
942
|
+
|
|
901
943
|
// Try to parse the response as JSON
|
|
902
944
|
let data;
|
|
903
945
|
try {
|
|
904
|
-
data = JSON.parse(responseText) as {
|
|
946
|
+
data = JSON.parse(responseText) as {
|
|
947
|
+
task_id: string;
|
|
948
|
+
message: string;
|
|
949
|
+
};
|
|
905
950
|
} catch (err) {
|
|
906
|
-
console.error(
|
|
951
|
+
console.error(
|
|
952
|
+
'Failed to parse PiAPI FaceSwap response as JSON:',
|
|
953
|
+
responseText
|
|
954
|
+
);
|
|
907
955
|
throw new Error('PiAPI FaceSwap Error: Invalid JSON response');
|
|
908
956
|
}
|
|
909
|
-
|
|
957
|
+
|
|
910
958
|
// Validate the response structure
|
|
911
959
|
if (!data.task_id || !data.message) {
|
|
912
|
-
console.error(
|
|
960
|
+
console.error(
|
|
961
|
+
'PiAPI FaceSwap response missing required fields:',
|
|
962
|
+
data
|
|
963
|
+
);
|
|
913
964
|
throw new Error('PiAPI FaceSwap Error: Invalid response structure');
|
|
914
965
|
}
|
|
915
|
-
|
|
966
|
+
|
|
916
967
|
console.log('FaceSwap task created successfully:', data);
|
|
917
|
-
|
|
968
|
+
|
|
918
969
|
return {
|
|
919
970
|
taskId: data.task_id,
|
|
920
971
|
message: data.message,
|