File size: 2,967 Bytes
38ceaf9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916c5da
 
 
 
 
 
38ceaf9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d7708a
38ceaf9
 
 
 
 
 
 
 
 
 
 
 
 
916c5da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38ceaf9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { z } from "zod";

const runTypes = z.object({
  run_id: z.string(),
});

const runOutputTypes = z.object({
  id: z.string(),
  status: z.enum(["success", "failed", "running", "uploading"]),
  outputs: z.array(
    z.object({
      data: z.any(),
    })
  ),
});

const uploadFileTypes = z.object({
  upload_url: z.string(),
  file_id: z.string(),
  download_url: z.string(),
})

export class ComfyDeployClient {
  apiBase: string = "https://www.comfydeploy.com/api";
  apiToken: string;

  constructor({ apiBase, apiToken }: { apiBase?: string; apiToken: string }) {
    if (apiBase)
      this.apiBase = `${apiBase}/api`;
    this.apiToken = apiToken;
  }

  async run({
    deployment_id,
    inputs,
  }: {
    deployment_id: string;
    inputs?: Record<string, string>;
  }) {
    return fetch(`${this.apiBase}/run`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        authorization: `Bearer ${this.apiToken}`,
      },
      body: JSON.stringify({
        deployment_id: deployment_id,
        inputs: inputs,
      }),
      cache: "no-store"
    })
      .then((response) => response.json())
      .then((json) => runTypes.parse(json))
      .catch((err) => {
        console.error(err);
        return null;
      });
  }

  async getRun(run_id: string) {
    return await fetch(`${this.apiBase}/run?run_id=${run_id}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        authorization: `Bearer ${this.apiToken}`,
      },
      cache: "no-store"
    })
      .then((response) => response.json())
      .then((json) => runOutputTypes.parse(json))
      .catch((err) => {
        console.error(err);
        return null;
      });
  }

  async runSync(props: {
    deployment_id: string;
    inputs?: Record<string, string>;
  }) {
    const runResult = await this.run(props);
    if (!runResult) return null;

    // 5 minutes
    const timeout = 60 * 5;
    const interval = 1000;

    let run: Awaited<ReturnType<typeof this.getRun>> = null;
    for (let i = 0; i < timeout; i++) {
      run = await this.getRun(runResult.run_id);
      if (run && run.status == "success") {
        break;
      }
      await new Promise((resolve) => setTimeout(resolve, interval));
    }

    if (!run) {
      return {
        id: runResult.run_id,
      };
    }

    return run;
  }

  async getUploadUrl(type: string, file_size: number) {
    const obj = {
      type: type,
      file_size: file_size.toString(),
    };
    const url = new URL(`${this.apiBase}/upload-url`);
    url.search = new URLSearchParams(obj).toString();
    
    return await fetch(url.href, {
      method: "GET",
      headers: {
        authorization: `Bearer ${this.apiToken}`,
      },
      cache: "no-store"
    })
      .then((response) => response.json())
      .then((json) => uploadFileTypes.parse(json))
      .catch((err) => {
        console.error(err);
        return null;
      });
  }
}