Skip to content

Instantly share code, notes, and snippets.

@ariviere
Created July 22, 2024 23:41
Show Gist options
  • Save ariviere/2187c4ce7c96eade872734df11e892a1 to your computer and use it in GitHub Desktop.
Save ariviere/2187c4ce7c96eade872734df11e892a1 to your computer and use it in GitHub Desktop.
Theta EdgeCloud Sketch to Object JS API
const url = 'YOUR_API_URL';
let eventSource = null;
/**
* Generate a 2D image from a sketch
* @param {File} file - The sketch image file
* @param {string} prompt - The prompt for the image generation
* @param {string} negativePrompt - The negative prompt for the image generation
* @returns {string} the path of the generated 2D image
*/
export const generate2d = async (file, prompt, negativePrompt) => {
const sessionHash = Math.random().toString(36).substring(2, 12);
const sketchPath = await uploadImage(file);
const sketchPngPath = await createEventSource(
sessionHash,
0,
sketchToSketchPngBody(sketchPath, getUrlFromPath(sketchPath), file, sessionHash))
const image2dPath = await createEventSource(
sessionHash,
1,
sketchPngTo2dBody(sketchPngPath, getUrlFromPath(sketchPngPath), file, sessionHash, prompt, negativePrompt))
return await createEventSource(
sessionHash,
url,
2,
image2dToImage2d2Body(image2dPath, getUrlFromPath(image2dPath), file, sessionHash));
}
/**
* Generate a 3D object from a 2D image
* @param {string} imagePath - The path of the 2D image
* @returns {string} the path of the generated 3D object
*/
const generate3d = async (imagePath) => {
const sessionHash = Math.random().toString(36).substring(2, 12);
return await createEventSource(
sessionHash,
4,
image2dToObjBody(imagePath, getUrlFromPath(imagePath), sessionHash));
}
const uploadImage = async (file) => {
const formData = new FormData();
formData.append('files', file, file.name);
try {
const response = await fetch(`${url}/upload`, {
method: 'POST',
body: formData
})
const json = await response?.json();
return json?.[0]
} catch (e) {
console.log(e)
}
}
const createEventSource = async (sessionHash, fnIndex, body) => {
return new Promise((resolve) => {
if (eventSource) {
eventSource.close()
eventSource = null
}
eventSource = new EventSource(`${url}/queue/join?fn_index=${fnIndex}&session_hash=${sessionHash}`);
eventSource.onmessage = (e) => {
const data = JSON.parse(e.data)
switch (data.msg) {
case 'process_completed':
if (data.success) {
resolve(data.output.data[0].path)
} else {
// onError event
}
eventSource.close()
break;
case 'send_data':
queueData(url, body, data.event_id)
break;
case 'estimation':
// onEstimation event
break;
default:
break;
}
};
eventSource.onerror = (e) => {
if (e.readyState === EventSource.CONNECTING) {
return
}
// onError event
eventSource.close()
};
});
}
const queueData = async (body, eventId) => {
const response = await fetch(`${url}/queue/data`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({...body, event_id: eventId}),
});
const json = await response?.json();
if (!response.ok) {
let errorMessage = json?.detail || 'Failed to generate'
if (errorMessage.toLowerCase().includes('queue is full')) {
errorMessage = 'The queue is currently full. Please try again later.'
}
throw new Error(errorMessage)
}
}
const getUrlFromPath = (path) => {
return `${url}/file=${path}`
}
const sketchToSketchPngBody = (sketchPath, sketchUrl, file, sessionHash) => {
return {
data: [
{
"background": {
"mime_type": "",
"orig_name": "background.png",
"path": sketchPath,
"size": file.size,
"url": sketchUrl
},
"composite": {
"mime_type": "",
"orig_name": "composite.png",
"path": sketchPath,
"size": file.size,
"url": sketchUrl
},
}
],
session_hash: sessionHash,
fn_index: 0,
trigger_id: 30,
event_data: null,
}
}
const sketchPngTo2dBody = (sketchPath, sketchUrl, file, sessionHash, prompt, negativePrompt) => {
return {
"data": [
{
"mime_type": null,
"orig_name": "image.png",
"path": sketchPath,
"size": null,
"url": sketchUrl
},
"stablediffusionapi/rev-animated-v122-eol",
"lllyasviel/control_v11p_sd15_lineart",
512,
512,
true,
1,
prompt,
negativePrompt,
1,
7.5,
30,
"DDIM",
0,
"Lineart"
],
"event_data": null,
"fn_index": 1,
"session_hash": sessionHash,
"trigger_id": 30
}
}
const image2dToImage2d2Body = (image2dPath, image2dUrl, file, sessionHash) => {
return {
"data": [
{
"mime_type": null,
"orig_name": "image.png",
"path": image2dPath,
"size": null,
"url": image2dUrl
},
true,
0.85,
],
"event_data": null,
"fn_index": 2,
"session_hash": sessionHash,
"trigger_id": 30
}
}
const image2dToObjBody = (image2dPath, image2dUrl, sessionHash) => {
return {
"data": [
{
"mime_type": null,
"orig_name": "image.png",
"path": image2dPath,
"size": null,
"url": image2dUrl
},
256,
],
"event_data": null,
"fn_index": 4,
"session_hash": sessionHash,
"trigger_id": 35
}
}
@yosun
Copy link

yosun commented Jul 23, 2024

was able to upload using curl from your js

curl --location '{{URL}}}/upload' \ --header 'Content-Type: application/json' \ --form 'files=@"{{FILE}}"'

returns [ "{{server local filename}}" ]

and this can be accessed as a URL
{{URL}}/file={{server local filename}}"

however can't seem to get the meat of the API endpoints to work

{{URL}}/queue/join?fn_index=1&session_hash=4f227bb3-fd53-402c-b140-e69ee4e0f1ca

{
"detail": "Method Not Allowed"
}

@yosun
Copy link

yosun commented Jul 23, 2024

Also queue/data?fn_index=1&session_hash= ...

{
"detail": [
{
"type": "model_attributes_type",
"loc": [
"body"
],
"msg": "Input should be a valid dictionary or object to extract fields from",

@ariviere
Copy link
Author

The JS code uses an EventSource object to keep the connection open. The equivalent using curl for your case is

curl -N -H "Accept: text/event-stream" {{URL}}/queue/join?fn_index=1&session_hash=4f227bb3-fd53-402c-b140-e69ee4e0f1ca

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment