@yetter/client 0.0.12 → 0.0.13
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/README.md +52 -3
- package/dist/api.d.ts +3 -0
- package/dist/api.js +110 -60
- package/dist/client.d.ts +27 -52
- package/dist/client.js +264 -262
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +3 -0
- package/package.json +6 -1
- package/bowow2.jpeg +0 -0
- package/examples/stream.ts +0 -54
- package/examples/submit.ts +0 -80
- package/examples/subscribe.ts +0 -41
- package/examples/upload-and-generate.ts +0 -39
- package/src/api.ts +0 -159
- package/src/client.ts +0 -697
- package/src/index.ts +0 -12
- package/src/types.ts +0 -192
- package/tsconfig.json +0 -13
package/dist/client.js
CHANGED
|
@@ -1,37 +1,126 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { YetterImageClient } from "./api.js";
|
|
3
|
-
import { EventSourcePolyfill } from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
import { EventSourcePolyfill } from "event-source-polyfill";
|
|
4
|
+
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
5
|
+
const DEFAULT_UPLOAD_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6
|
+
const STREAM_HEARTBEAT_TIMEOUT_MS = 120000;
|
|
7
|
+
const DEFAULT_ENDPOINT = "https://api.yetter.ai";
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
function getEnvApiKey() {
|
|
12
|
+
return process.env.YTR_API_KEY || process.env.REACT_APP_YTR_API_KEY || "";
|
|
13
|
+
}
|
|
14
|
+
export class YetterClient {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.queue = {
|
|
17
|
+
submit: async (model, options) => {
|
|
18
|
+
const client = this.createApiClient();
|
|
19
|
+
return client.generate({
|
|
20
|
+
model,
|
|
21
|
+
...options.input,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
status: async (model, options) => {
|
|
25
|
+
const client = this.createApiClient();
|
|
26
|
+
const endpoint = client.getApiEndpoint();
|
|
27
|
+
const statusUrl = `${endpoint}/${model}/requests/${options.requestId}/status`;
|
|
28
|
+
const statusData = await client.getStatus({ url: statusUrl });
|
|
29
|
+
return {
|
|
30
|
+
data: statusData,
|
|
31
|
+
requestId: options.requestId,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
result: async (model, options) => {
|
|
35
|
+
const client = this.createApiClient();
|
|
36
|
+
const endpoint = client.getApiEndpoint();
|
|
37
|
+
const responseUrl = `${endpoint}/${model}/requests/${options.requestId}`;
|
|
38
|
+
const responseData = await client.getResponse({ url: responseUrl });
|
|
39
|
+
return {
|
|
40
|
+
data: responseData,
|
|
41
|
+
requestId: options.requestId,
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
this.apiKey = "";
|
|
46
|
+
this.endpoint = options.endpoint || DEFAULT_ENDPOINT;
|
|
47
|
+
const envKey = getEnvApiKey();
|
|
48
|
+
if (envKey) {
|
|
49
|
+
this.apiKey = `Key ${envKey}`;
|
|
50
|
+
}
|
|
51
|
+
if (typeof options.apiKey === "string") {
|
|
52
|
+
this.setApiKey(options.apiKey, options.is_bearer);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
setApiKey(rawApiKey, isBearer) {
|
|
56
|
+
const normalizedApiKey = rawApiKey.trim();
|
|
57
|
+
if (!normalizedApiKey) {
|
|
58
|
+
this.apiKey = "";
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.apiKey = isBearer ? `Bearer ${normalizedApiKey}` : `Key ${normalizedApiKey}`;
|
|
62
|
+
}
|
|
63
|
+
assertApiKeyConfigured() {
|
|
64
|
+
if (!this.apiKey) {
|
|
65
|
+
throw new Error("API key is not configured. Call configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
createApiClient() {
|
|
69
|
+
this.assertApiKeyConfigured();
|
|
70
|
+
return new YetterImageClient({
|
|
71
|
+
apiKey: this.apiKey,
|
|
72
|
+
endpoint: this.endpoint,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
getUploadTimeoutMs(options) {
|
|
76
|
+
if (typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0) {
|
|
77
|
+
return options.timeout;
|
|
78
|
+
}
|
|
79
|
+
return DEFAULT_UPLOAD_TIMEOUT_MS;
|
|
80
|
+
}
|
|
81
|
+
async putWithTimeout(url, body, headers, timeoutMs) {
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
84
|
+
try {
|
|
85
|
+
return await fetch(url, {
|
|
86
|
+
method: "PUT",
|
|
87
|
+
headers,
|
|
88
|
+
body,
|
|
89
|
+
signal: controller.signal,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === "AbortError") {
|
|
94
|
+
throw new Error(`Upload request timed out after ${timeoutMs}ms`);
|
|
12
95
|
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
configure(options) {
|
|
103
|
+
if (typeof options.apiKey === "string") {
|
|
104
|
+
this.setApiKey(options.apiKey, options.is_bearer);
|
|
13
105
|
}
|
|
14
106
|
if (options.endpoint) {
|
|
15
|
-
|
|
107
|
+
this.endpoint = options.endpoint;
|
|
16
108
|
}
|
|
17
109
|
}
|
|
18
|
-
|
|
110
|
+
async subscribe(model, options) {
|
|
19
111
|
var _b;
|
|
20
|
-
|
|
21
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
22
|
-
}
|
|
23
|
-
const client = new YetterImageClient({
|
|
24
|
-
apiKey: _a.apiKey,
|
|
25
|
-
endpoint: _a.endpoint
|
|
26
|
-
});
|
|
112
|
+
const client = this.createApiClient();
|
|
27
113
|
const generateResponse = await client.generate({
|
|
28
|
-
model
|
|
114
|
+
model,
|
|
29
115
|
...options.input,
|
|
30
116
|
});
|
|
31
117
|
let status = generateResponse.status;
|
|
32
118
|
let lastStatusResponse;
|
|
33
119
|
const startTime = Date.now();
|
|
34
|
-
const timeoutMilliseconds = 30 * 60 * 1000;
|
|
120
|
+
const timeoutMilliseconds = 30 * 60 * 1000;
|
|
121
|
+
const pollIntervalMs = typeof options.pollIntervalMs === "number" && Number.isFinite(options.pollIntervalMs) && options.pollIntervalMs > 0
|
|
122
|
+
? options.pollIntervalMs
|
|
123
|
+
: DEFAULT_POLL_INTERVAL_MS;
|
|
35
124
|
while (status !== "COMPLETED" && status !== "ERROR" && status !== "CANCELLED") {
|
|
36
125
|
if (Date.now() - startTime > timeoutMilliseconds) {
|
|
37
126
|
console.warn(`Subscription timed out after 30 minutes for request ID: ${generateResponse.request_id}. Attempting to cancel.`);
|
|
@@ -53,6 +142,9 @@ export class yetter {
|
|
|
53
142
|
if (options.onQueueUpdate) {
|
|
54
143
|
options.onQueueUpdate(lastStatusResponse);
|
|
55
144
|
}
|
|
145
|
+
if (status !== "COMPLETED" && status !== "ERROR" && status !== "CANCELLED") {
|
|
146
|
+
await sleep(pollIntervalMs);
|
|
147
|
+
}
|
|
56
148
|
}
|
|
57
149
|
catch (error) {
|
|
58
150
|
console.error("Error during status polling:", error);
|
|
@@ -60,35 +152,28 @@ export class yetter {
|
|
|
60
152
|
}
|
|
61
153
|
}
|
|
62
154
|
if (status === "ERROR") {
|
|
63
|
-
const errorMessage = ((_b = lastStatusResponse === null || lastStatusResponse === void 0 ? void 0 : lastStatusResponse.logs) === null || _b === void 0 ? void 0 : _b.map(log => log.message).join("\n")) || "Image generation failed.";
|
|
155
|
+
const errorMessage = ((_b = lastStatusResponse === null || lastStatusResponse === void 0 ? void 0 : lastStatusResponse.logs) === null || _b === void 0 ? void 0 : _b.map((log) => log.message).join("\n")) || "Image generation failed.";
|
|
64
156
|
throw new Error(errorMessage);
|
|
65
157
|
}
|
|
66
|
-
|
|
158
|
+
if (status === "CANCELLED") {
|
|
67
159
|
throw new Error("Image generation was cancelled by user.");
|
|
68
160
|
}
|
|
69
|
-
|
|
161
|
+
return client.getResponse({
|
|
70
162
|
url: generateResponse.response_url,
|
|
71
163
|
});
|
|
72
|
-
return finalResponse;
|
|
73
164
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
77
|
-
}
|
|
78
|
-
const client = new YetterImageClient({
|
|
79
|
-
apiKey: _a.apiKey,
|
|
80
|
-
endpoint: _a.endpoint
|
|
81
|
-
});
|
|
165
|
+
async stream(model, options) {
|
|
166
|
+
const client = this.createApiClient();
|
|
82
167
|
const initialApiResponse = await client.generate({
|
|
83
|
-
model
|
|
168
|
+
model,
|
|
84
169
|
...options.input,
|
|
85
170
|
});
|
|
86
171
|
const requestId = initialApiResponse.request_id;
|
|
87
172
|
const responseUrl = initialApiResponse.response_url;
|
|
173
|
+
const statusUrl = initialApiResponse.status_url;
|
|
88
174
|
const cancelUrl = initialApiResponse.cancel_url;
|
|
89
175
|
const sseStreamUrl = `${client.getApiEndpoint()}/${model}/requests/${requestId}/status/stream`;
|
|
90
176
|
let eventSource;
|
|
91
|
-
// Setup the promise for the done() method
|
|
92
177
|
let resolveDonePromise;
|
|
93
178
|
let rejectDonePromise;
|
|
94
179
|
const donePromise = new Promise((resolve, reject) => {
|
|
@@ -96,8 +181,6 @@ export class yetter {
|
|
|
96
181
|
rejectDonePromise = reject;
|
|
97
182
|
});
|
|
98
183
|
const controller = {
|
|
99
|
-
// This will be used by the async iterator to pull events
|
|
100
|
-
// It needs a way to buffer events or signal availability
|
|
101
184
|
events: [],
|
|
102
185
|
resolvers: [],
|
|
103
186
|
isClosed: false,
|
|
@@ -124,7 +207,6 @@ export class yetter {
|
|
|
124
207
|
else {
|
|
125
208
|
this.events.push(event);
|
|
126
209
|
}
|
|
127
|
-
// Check for terminal events to resolve/reject the donePromise
|
|
128
210
|
this.currentStatus = event.status;
|
|
129
211
|
},
|
|
130
212
|
error(err) {
|
|
@@ -159,7 +241,7 @@ export class yetter {
|
|
|
159
241
|
if (this.isClosed)
|
|
160
242
|
return;
|
|
161
243
|
this.isClosed = true;
|
|
162
|
-
this.resolvers.forEach(resolve => resolve({ value: undefined, done: true }));
|
|
244
|
+
this.resolvers.forEach((resolve) => resolve({ value: undefined, done: true }));
|
|
163
245
|
this.resolvers = [];
|
|
164
246
|
eventSource === null || eventSource === void 0 ? void 0 : eventSource.close();
|
|
165
247
|
},
|
|
@@ -170,73 +252,98 @@ export class yetter {
|
|
|
170
252
|
if (this.isClosed) {
|
|
171
253
|
return Promise.resolve({ value: undefined, done: true });
|
|
172
254
|
}
|
|
173
|
-
return new Promise(resolve => this.resolvers.push(resolve));
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
eventSource = new EventSourcePolyfill(sseStreamUrl, {
|
|
177
|
-
headers: { 'Authorization': `${_a.apiKey}` },
|
|
178
|
-
});
|
|
179
|
-
eventSource.onopen = (event) => {
|
|
180
|
-
console.log("SSE Connection Opened:", event);
|
|
255
|
+
return new Promise((resolve) => this.resolvers.push(resolve));
|
|
256
|
+
},
|
|
181
257
|
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
258
|
+
if (initialApiResponse.status === "COMPLETED") {
|
|
259
|
+
controller.push(initialApiResponse);
|
|
260
|
+
const finalResponse = await client.getResponse({ url: responseUrl });
|
|
261
|
+
controller.done(finalResponse);
|
|
262
|
+
}
|
|
263
|
+
else if (initialApiResponse.status === "ERROR") {
|
|
264
|
+
controller.push(initialApiResponse);
|
|
265
|
+
controller.error(new Error(`Stream reported ERROR for ${requestId}`));
|
|
266
|
+
}
|
|
267
|
+
else if (initialApiResponse.status === "CANCELLED") {
|
|
268
|
+
controller.push(initialApiResponse);
|
|
269
|
+
controller.cancel();
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
eventSource = new EventSourcePolyfill(sseStreamUrl, {
|
|
273
|
+
headers: { Authorization: this.apiKey },
|
|
274
|
+
heartbeatTimeout: STREAM_HEARTBEAT_TIMEOUT_MS,
|
|
275
|
+
});
|
|
276
|
+
eventSource.onopen = (event) => {
|
|
277
|
+
console.log("SSE Connection Opened:", event);
|
|
278
|
+
};
|
|
279
|
+
eventSource.addEventListener("data", (event) => {
|
|
280
|
+
try {
|
|
281
|
+
const statusData = JSON.parse(event.data);
|
|
282
|
+
controller.push(statusData);
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
console.error("Error parsing SSE 'data' event:", e, "Raw data:", event.data);
|
|
286
|
+
controller.error(new Error(`Error parsing SSE 'data' event: ${e.message}`));
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
eventSource.addEventListener("done", async (event) => {
|
|
290
|
+
try {
|
|
203
291
|
try {
|
|
204
292
|
eventSource === null || eventSource === void 0 ? void 0 : eventSource.close();
|
|
205
293
|
}
|
|
206
294
|
catch { }
|
|
207
|
-
|
|
208
|
-
|
|
295
|
+
if (controller.currentStatus === "COMPLETED") {
|
|
296
|
+
const response = await client.getResponse({ url: responseUrl });
|
|
297
|
+
controller.done(response);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (controller.currentStatus === "CANCELLED") {
|
|
301
|
+
controller.cancel();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (controller.currentStatus === "ERROR") {
|
|
305
|
+
controller.error(new Error(`Stream reported ERROR for ${requestId}`));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const latestStatus = await client.getStatus({ url: statusUrl });
|
|
309
|
+
controller.push(latestStatus);
|
|
310
|
+
if (latestStatus.status === "COMPLETED") {
|
|
311
|
+
const response = await client.getResponse({ url: responseUrl });
|
|
312
|
+
controller.done(response);
|
|
313
|
+
}
|
|
314
|
+
else if (latestStatus.status === "CANCELLED") {
|
|
315
|
+
controller.cancel();
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
controller.error(new Error(`Stream ended without terminal status for ${requestId}`));
|
|
319
|
+
}
|
|
209
320
|
}
|
|
210
|
-
|
|
211
|
-
|
|
321
|
+
catch (e) {
|
|
322
|
+
console.error("Error parsing SSE 'done' event:", e, "Raw data:", event.data);
|
|
323
|
+
controller.error(new Error(`Error parsing SSE 'done' event: ${e.message}`));
|
|
212
324
|
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (initialApiResponse.status === "COMPLETED") {
|
|
236
|
-
controller.push(initialApiResponse);
|
|
237
|
-
}
|
|
238
|
-
else if (initialApiResponse.status === "ERROR") {
|
|
239
|
-
controller.push(initialApiResponse);
|
|
325
|
+
});
|
|
326
|
+
eventSource.addEventListener("error", (event) => {
|
|
327
|
+
const rawData = event === null || event === void 0 ? void 0 : event.data;
|
|
328
|
+
if (!rawData) {
|
|
329
|
+
console.warn("SSE transport error event; waiting for reconnect.");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
console.log("SSE application 'error' event received, raw data:", rawData);
|
|
333
|
+
controller.error(new Error(`Stream reported ERROR for ${requestId}: ${rawData}`));
|
|
334
|
+
});
|
|
335
|
+
eventSource.onerror = (err) => {
|
|
336
|
+
var _b;
|
|
337
|
+
const message = ((_b = err === null || err === void 0 ? void 0 : err.error) === null || _b === void 0 ? void 0 : _b.message) || (err === null || err === void 0 ? void 0 : err.message) || "";
|
|
338
|
+
const isIdleTimeout = typeof message === "string" &&
|
|
339
|
+
message.includes("No activity within") &&
|
|
340
|
+
message.includes("Reconnecting");
|
|
341
|
+
if (isIdleTimeout) {
|
|
342
|
+
console.warn("SSE idle timeout; letting EventSource auto-reconnect.", err);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
console.warn("SSE connection issue (onerror); waiting for auto-reconnect.", err);
|
|
346
|
+
};
|
|
240
347
|
}
|
|
241
348
|
return {
|
|
242
349
|
async *[Symbol.asyncIterator]() {
|
|
@@ -260,56 +367,19 @@ export class yetter {
|
|
|
260
367
|
getRequestId: () => requestId,
|
|
261
368
|
};
|
|
262
369
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
* @param options Upload configuration options
|
|
268
|
-
* @returns Promise resolving to upload result with public URL
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
* ```typescript
|
|
272
|
-
* // Node.js
|
|
273
|
-
* const result = await yetter.uploadFile("/path/to/image.jpg", {
|
|
274
|
-
* onProgress: (pct) => console.log(`Upload: ${pct}%`)
|
|
275
|
-
* });
|
|
276
|
-
*
|
|
277
|
-
* // Browser
|
|
278
|
-
* const fileInput = document.querySelector('input[type="file"]');
|
|
279
|
-
* const file = fileInput.files[0];
|
|
280
|
-
* const result = await yetter.uploadFile(file, {
|
|
281
|
-
* onProgress: (pct) => updateProgressBar(pct)
|
|
282
|
-
* });
|
|
283
|
-
* ```
|
|
284
|
-
*/
|
|
285
|
-
static async uploadFile(fileOrPath, options = {}) {
|
|
286
|
-
if (!_a.apiKey) {
|
|
287
|
-
throw new Error("API key is not configured. Call yetter.configure()");
|
|
288
|
-
}
|
|
289
|
-
if (typeof fileOrPath === 'string') {
|
|
290
|
-
// Node.js path
|
|
291
|
-
return _a._uploadFromPath(fileOrPath, options);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
// Browser File/Blob
|
|
295
|
-
return _a._uploadFromBlob(fileOrPath, options);
|
|
370
|
+
async uploadFile(fileOrPath, options = {}) {
|
|
371
|
+
this.assertApiKeyConfigured();
|
|
372
|
+
if (typeof fileOrPath === "string") {
|
|
373
|
+
return this._uploadFromPath(fileOrPath, options);
|
|
296
374
|
}
|
|
375
|
+
return this._uploadFromBlob(fileOrPath, options);
|
|
297
376
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
* This is an alias for uploadFile for better clarity in browser contexts
|
|
301
|
-
*/
|
|
302
|
-
static async uploadBlob(file, options = {}) {
|
|
303
|
-
return _a.uploadFile(file, options);
|
|
377
|
+
async uploadBlob(file, options = {}) {
|
|
378
|
+
return this.uploadFile(file, options);
|
|
304
379
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
static async _uploadFromPath(filePath, options) {
|
|
309
|
-
// Dynamic import for Node.js modules
|
|
310
|
-
const fs = await import('fs');
|
|
311
|
-
const mime = await import('mime-types');
|
|
312
|
-
// Validate file exists
|
|
380
|
+
async _uploadFromPath(filePath, options) {
|
|
381
|
+
const fs = await import("fs");
|
|
382
|
+
const mime = await import("mime-types");
|
|
313
383
|
if (!fs.existsSync(filePath)) {
|
|
314
384
|
throw new Error(`File not found: ${filePath}`);
|
|
315
385
|
}
|
|
@@ -317,24 +387,19 @@ export class yetter {
|
|
|
317
387
|
const fileSize = stats.size;
|
|
318
388
|
const fileName = filePath.split(/[\\/]/).pop() || "upload";
|
|
319
389
|
const mimeType = mime.default.lookup(filePath) || "application/octet-stream";
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
endpoint: _a.endpoint,
|
|
323
|
-
});
|
|
324
|
-
// Step 1: Request presigned URL(s)
|
|
390
|
+
const uploadTimeoutMs = this.getUploadTimeoutMs(options);
|
|
391
|
+
const client = this.createApiClient();
|
|
325
392
|
const uploadUrlResponse = await client.getUploadUrl({
|
|
326
393
|
file_name: fileName,
|
|
327
394
|
content_type: mimeType,
|
|
328
395
|
size: fileSize,
|
|
329
396
|
});
|
|
330
|
-
// Step 2: Upload file content
|
|
331
397
|
if (uploadUrlResponse.mode === "single") {
|
|
332
|
-
await
|
|
398
|
+
await this._uploadFileSingle(filePath, uploadUrlResponse.put_url, mimeType, fileSize, options, fs, uploadTimeoutMs);
|
|
333
399
|
}
|
|
334
400
|
else {
|
|
335
|
-
await
|
|
401
|
+
await this._uploadFileMultipart(filePath, uploadUrlResponse.part_urls, uploadUrlResponse.part_size, fileSize, options, fs, uploadTimeoutMs);
|
|
336
402
|
}
|
|
337
|
-
// Step 3: Notify completion
|
|
338
403
|
const result = await client.uploadComplete({
|
|
339
404
|
key: uploadUrlResponse.key,
|
|
340
405
|
});
|
|
@@ -343,32 +408,23 @@ export class yetter {
|
|
|
343
408
|
}
|
|
344
409
|
return result;
|
|
345
410
|
}
|
|
346
|
-
|
|
347
|
-
* Upload file from Blob/File object (browser)
|
|
348
|
-
*/
|
|
349
|
-
static async _uploadFromBlob(file, options) {
|
|
411
|
+
async _uploadFromBlob(file, options) {
|
|
350
412
|
const fileSize = file.size;
|
|
351
|
-
const fileName = options.filename ||
|
|
352
|
-
(file instanceof File ? file.name : "blob-upload");
|
|
413
|
+
const fileName = options.filename || (file instanceof File ? file.name : "blob-upload");
|
|
353
414
|
const mimeType = file.type || "application/octet-stream";
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
endpoint: _a.endpoint,
|
|
357
|
-
});
|
|
358
|
-
// Step 1: Request presigned URL(s)
|
|
415
|
+
const uploadTimeoutMs = this.getUploadTimeoutMs(options);
|
|
416
|
+
const client = this.createApiClient();
|
|
359
417
|
const uploadUrlResponse = await client.getUploadUrl({
|
|
360
418
|
file_name: fileName,
|
|
361
419
|
content_type: mimeType,
|
|
362
420
|
size: fileSize,
|
|
363
421
|
});
|
|
364
|
-
// Step 2: Upload file content
|
|
365
422
|
if (uploadUrlResponse.mode === "single") {
|
|
366
|
-
await
|
|
423
|
+
await this._uploadBlobSingle(file, uploadUrlResponse.put_url, mimeType, fileSize, options, uploadTimeoutMs);
|
|
367
424
|
}
|
|
368
425
|
else {
|
|
369
|
-
await
|
|
426
|
+
await this._uploadBlobMultipart(file, uploadUrlResponse.part_urls, uploadUrlResponse.part_size, fileSize, options, uploadTimeoutMs);
|
|
370
427
|
}
|
|
371
|
-
// Step 3: Notify completion
|
|
372
428
|
const result = await client.uploadComplete({
|
|
373
429
|
key: uploadUrlResponse.key,
|
|
374
430
|
});
|
|
@@ -377,19 +433,12 @@ export class yetter {
|
|
|
377
433
|
}
|
|
378
434
|
return result;
|
|
379
435
|
}
|
|
380
|
-
|
|
381
|
-
* Upload file using single PUT request (Node.js, private helper)
|
|
382
|
-
*/
|
|
383
|
-
static async _uploadFileSingle(filePath, presignedUrl, contentType, totalSize, options, fs) {
|
|
436
|
+
async _uploadFileSingle(filePath, presignedUrl, contentType, totalSize, options, fs, timeoutMs) {
|
|
384
437
|
const fileBuffer = fs.readFileSync(filePath);
|
|
385
|
-
const response = await
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
"Content-Length": String(totalSize),
|
|
390
|
-
},
|
|
391
|
-
body: fileBuffer,
|
|
392
|
-
});
|
|
438
|
+
const response = await this.putWithTimeout(presignedUrl, fileBuffer, {
|
|
439
|
+
"Content-Type": contentType,
|
|
440
|
+
"Content-Length": String(totalSize),
|
|
441
|
+
}, timeoutMs);
|
|
393
442
|
if (!response.ok) {
|
|
394
443
|
const errorText = await response.text();
|
|
395
444
|
throw new Error(`Single-part upload failed (${response.status}): ${errorText}`);
|
|
@@ -398,10 +447,7 @@ export class yetter {
|
|
|
398
447
|
options.onProgress(90);
|
|
399
448
|
}
|
|
400
449
|
}
|
|
401
|
-
|
|
402
|
-
* Upload file using multipart upload (Node.js, private helper)
|
|
403
|
-
*/
|
|
404
|
-
static async _uploadFileMultipart(filePath, partUrls, partSize, totalSize, options, fs) {
|
|
450
|
+
async _uploadFileMultipart(filePath, partUrls, partSize, totalSize, options, fs, timeoutMs) {
|
|
405
451
|
const sortedParts = [...partUrls].sort((a, b) => a.part_number - b.part_number);
|
|
406
452
|
const fileHandle = fs.openSync(filePath, "r");
|
|
407
453
|
try {
|
|
@@ -414,17 +460,12 @@ export class yetter {
|
|
|
414
460
|
if (chunk.length === 0) {
|
|
415
461
|
break;
|
|
416
462
|
}
|
|
417
|
-
const response = await
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
"Content-Length": String(chunk.length),
|
|
421
|
-
},
|
|
422
|
-
body: chunk,
|
|
423
|
-
});
|
|
463
|
+
const response = await this.putWithTimeout(part.url, chunk, {
|
|
464
|
+
"Content-Length": String(chunk.length),
|
|
465
|
+
}, timeoutMs);
|
|
424
466
|
if (!response.ok) {
|
|
425
467
|
const errorText = await response.text();
|
|
426
|
-
throw new Error(`Multipart upload failed at part ${part.part_number} `
|
|
427
|
-
`(${response.status}): ${errorText}`);
|
|
468
|
+
throw new Error(`Multipart upload failed at part ${part.part_number} (${response.status}): ${errorText}`);
|
|
428
469
|
}
|
|
429
470
|
if (options.onProgress) {
|
|
430
471
|
const progress = Math.min(90, Math.floor(((i + 1) / sortedParts.length) * 90));
|
|
@@ -436,18 +477,10 @@ export class yetter {
|
|
|
436
477
|
fs.closeSync(fileHandle);
|
|
437
478
|
}
|
|
438
479
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const response = await fetch(presignedUrl, {
|
|
444
|
-
method: "PUT",
|
|
445
|
-
headers: {
|
|
446
|
-
"Content-Type": contentType,
|
|
447
|
-
"Content-Length": String(totalSize),
|
|
448
|
-
},
|
|
449
|
-
body: blob,
|
|
450
|
-
});
|
|
480
|
+
async _uploadBlobSingle(blob, presignedUrl, contentType, _totalSize, options, timeoutMs) {
|
|
481
|
+
const response = await this.putWithTimeout(presignedUrl, blob, {
|
|
482
|
+
"Content-Type": contentType,
|
|
483
|
+
}, timeoutMs);
|
|
451
484
|
if (!response.ok) {
|
|
452
485
|
const errorText = await response.text();
|
|
453
486
|
throw new Error(`Single-part upload failed (${response.status}): ${errorText}`);
|
|
@@ -456,10 +489,7 @@ export class yetter {
|
|
|
456
489
|
options.onProgress(90);
|
|
457
490
|
}
|
|
458
491
|
}
|
|
459
|
-
|
|
460
|
-
* Upload blob using multipart upload (browser, private helper)
|
|
461
|
-
*/
|
|
462
|
-
static async _uploadBlobMultipart(blob, partUrls, partSize, totalSize, options) {
|
|
492
|
+
async _uploadBlobMultipart(blob, partUrls, partSize, totalSize, options, timeoutMs) {
|
|
463
493
|
const sortedParts = [...partUrls].sort((a, b) => a.part_number - b.part_number);
|
|
464
494
|
for (let i = 0; i < sortedParts.length; i++) {
|
|
465
495
|
const part = sortedParts[i];
|
|
@@ -469,17 +499,10 @@ export class yetter {
|
|
|
469
499
|
if (chunk.size === 0) {
|
|
470
500
|
break;
|
|
471
501
|
}
|
|
472
|
-
const response = await
|
|
473
|
-
method: "PUT",
|
|
474
|
-
headers: {
|
|
475
|
-
"Content-Length": String(chunk.size),
|
|
476
|
-
},
|
|
477
|
-
body: chunk,
|
|
478
|
-
});
|
|
502
|
+
const response = await this.putWithTimeout(part.url, chunk, {}, timeoutMs);
|
|
479
503
|
if (!response.ok) {
|
|
480
504
|
const errorText = await response.text();
|
|
481
|
-
throw new Error(`Multipart upload failed at part ${part.part_number} `
|
|
482
|
-
`(${response.status}): ${errorText}`);
|
|
505
|
+
throw new Error(`Multipart upload failed at part ${part.part_number} (${response.status}): ${errorText}`);
|
|
483
506
|
}
|
|
484
507
|
if (options.onProgress) {
|
|
485
508
|
const progress = Math.min(90, Math.floor(((i + 1) / sortedParts.length) * 90));
|
|
@@ -488,54 +511,33 @@ export class yetter {
|
|
|
488
511
|
}
|
|
489
512
|
}
|
|
490
513
|
}
|
|
514
|
+
export class yetter {
|
|
515
|
+
static configure(options) {
|
|
516
|
+
this.client.configure(options);
|
|
517
|
+
}
|
|
518
|
+
static subscribe(model, options) {
|
|
519
|
+
return this.client.subscribe(model, options);
|
|
520
|
+
}
|
|
521
|
+
static stream(model, options) {
|
|
522
|
+
return this.client.stream(model, options);
|
|
523
|
+
}
|
|
524
|
+
static uploadFile(fileOrPath, options = {}) {
|
|
525
|
+
return this.client.uploadFile(fileOrPath, options);
|
|
526
|
+
}
|
|
527
|
+
static uploadBlob(file, options = {}) {
|
|
528
|
+
return this.client.uploadBlob(file, options);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
491
531
|
_a = yetter;
|
|
492
|
-
yetter.
|
|
493
|
-
yetter.endpoint = "https://api.yetter.ai";
|
|
532
|
+
yetter.client = new YetterClient();
|
|
494
533
|
yetter.queue = {
|
|
495
534
|
submit: async (model, options) => {
|
|
496
|
-
|
|
497
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
498
|
-
}
|
|
499
|
-
const client = new YetterImageClient({
|
|
500
|
-
apiKey: _a.apiKey,
|
|
501
|
-
endpoint: _a.endpoint
|
|
502
|
-
});
|
|
503
|
-
const generateResponse = await client.generate({
|
|
504
|
-
model: model,
|
|
505
|
-
...options.input,
|
|
506
|
-
});
|
|
507
|
-
return generateResponse;
|
|
535
|
+
return _a.client.queue.submit(model, options);
|
|
508
536
|
},
|
|
509
537
|
status: async (model, options) => {
|
|
510
|
-
|
|
511
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
512
|
-
}
|
|
513
|
-
const client = new YetterImageClient({
|
|
514
|
-
apiKey: _a.apiKey,
|
|
515
|
-
endpoint: _a.endpoint
|
|
516
|
-
});
|
|
517
|
-
const endpoint = client.getApiEndpoint();
|
|
518
|
-
const statusUrl = `${endpoint}/${model}/requests/${options.requestId}/status`;
|
|
519
|
-
const statusData = await client.getStatus({ url: statusUrl });
|
|
520
|
-
return {
|
|
521
|
-
data: statusData,
|
|
522
|
-
requestId: options.requestId,
|
|
523
|
-
};
|
|
538
|
+
return _a.client.queue.status(model, options);
|
|
524
539
|
},
|
|
525
540
|
result: async (model, options) => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
const client = new YetterImageClient({
|
|
530
|
-
apiKey: _a.apiKey,
|
|
531
|
-
endpoint: _a.endpoint
|
|
532
|
-
});
|
|
533
|
-
const endpoint = client.getApiEndpoint();
|
|
534
|
-
const responseUrl = `${endpoint}/${model}/requests/${options.requestId}`;
|
|
535
|
-
const responseData = await client.getResponse({ url: responseUrl });
|
|
536
|
-
return {
|
|
537
|
-
data: responseData,
|
|
538
|
-
requestId: options.requestId,
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
+
return _a.client.queue.result(model, options);
|
|
542
|
+
},
|
|
541
543
|
};
|