@yetter/client
v0.0.14
Published
The Yetter JS Client provides a convenient way to interact with the Yetter API for image generation. It supports different modes of operation: subscribing to real-time updates, submitting jobs to a queue, and streaming events.
Readme
Yetter JS Client
The Yetter JS Client provides a convenient way to interact with the Yetter API for image generation. It supports different modes of operation: subscribing to real-time updates, submitting jobs to a queue, and streaming events.
Installation
npm install @yetter/clientTesting
Run the local test suite:
npm testTests are automatically omitted from npm releases because package.json uses a files whitelist (dist, README.md).
Authentication
The client requires a Yetter API key for authentication. Ensure you have the YTR_API_KEY or REACT_APP_YTR_API_KEY environment variable set:
export YTR_API_KEY="your_api_key_here"
export REACT_APP_YTR_API_KEY="your_api_key_here"Alternatively, you can configure the API key (and optionally the API endpoint) programmatically using the yetter.configure() method. This is useful if you prefer not to use environment variables or need to set the key at runtime.
import { yetter } from "@yetter/client";
yetter.configure({
apiKey: "your_api_key_here",
// endpoint: "https://custom.api.yetter.ai" // if you need to override the default endpoint
});If yetter.configure() is used, it will override any API key found in environment variables for subsequent API calls. If neither environment variables are set nor yetter.configure() is called with an API key, an error will be thrown when attempting to make an API call.
Instance Client (Recommended for Concurrent Apps)
For server or multi-tenant workloads, prefer creating isolated client instances instead of sharing global static state.
import { YetterClient } from "@yetter/client";
const teamAClient = new YetterClient({ apiKey: process.env.TEAM_A_YTR_API_KEY! });
const teamBClient = new YetterClient({ apiKey: process.env.TEAM_B_YTR_API_KEY! });
const result = await teamAClient.subscribe("ytr-ai/qwen/image-edit/i2i", {
input: {
prompt: "Transform this image to watercolor painting style",
image_url: ["https://example.com/input.jpg"],
num_inference_steps: 28,
},
});Running i2i Example Scripts
For image-to-image models (such as ytr-ai/qwen/image-edit/i2i), provide both a prompt and an input image.
export YTR_API_KEY="your_api_key_here"
export YTR_MODEL="ytr-ai/qwen/image-edit/i2i"
export YTR_IMAGE_PATH="./bowow2.jpeg"
export YTR_PROMPT="Transform this image to watercolor painting style"Then run one of:
npm run test:submit
npm run test:subscribe
npm run test:streamCore Functionalities
The client is available via the yetter object imported from yetter-js (or the relevant path to client.js/client.ts if used directly).
import { yetter } from "@yetter/client";1. yetter.subscribe()
This function submits an image generation request and long-polls for the result. It provides updates on the queue status and logs if requested.
Features:
- Submits a generation request.
- Polls for status updates until the job is "COMPLETED" or "ERROR".
- Timeout after 30 minutes, with an attempt to cancel the job.
- Optional
onQueueUpdatecallback for real-time feedback on queue position and status. - Optional
logsflag to include logs in status updates. - Optional
pollIntervalMsto control status polling interval (default: 2000ms).
Example (from examples/subscribe.ts):
import { yetter } from "@yetter/client";
async function main() {
const model = "ytr-ai/flux/dev"; // Replace with your desired model
try {
console.log("\n--- Starting Subscribe Test ---");
const result = await yetter.subscribe(model, {
input: {
prompt: "a vibrant coral reef, underwater photography",
num_inference_steps: 28, // Required
},
logs: true,
onQueueUpdate: (update) => {
console.log(`[Queue Update] Status: ${update.status}, Position: ${update.queue_position}`);
if (update.status === "IN_PROGRESS" && update.logs) {
console.log("Logs:");
update.logs.map((log) => log.message).forEach(logMessage => console.log(` - ${logMessage}`));
} else if (update.status === "COMPLETED") {
console.log("Processing completed!");
} else if (update.status === "ERROR") {
console.error("Processing failed. Logs:", update.logs);
}
},
});
console.log("\n--- Subscribe Test Result ---");
console.log("Results:", result); // Contains image data, prompt, etc.
} catch (err: any) {
console.error("\n--- Subscribe Test Failed ---");
console.error("Error during subscribe:", err.message || err);
// ... error handling ...
}
}
main();2. yetter.stream()
This function submits an image generation request and returns an async iterable stream of events using Server-Sent Events (SSE). This allows for real-time updates on the job's progress, status, and logs.
Features:
- Initiates a request and establishes an SSE connection.
- Automatically falls back to status polling if SSE transport repeatedly fails.
- Provides an
AsyncIterator(Symbol.asyncIterator) to loop through status events (StreamEvent). - A
done()method: Returns a Promise that resolves with the finalGetResponseResponseafter the server emitsevent: done(successful completion), or rejects if the server emitsevent: erroror the final response cannot be fetched. - A
cancel()method: Requests cancellation on the backend; the stream naturally ends when the server emitsdata(withstatus: "CANCELLED") followed byevent: done. - A
getRequestId()method: Returns the request ID for the stream.
Transport options (StreamOptions):
disableSse?: boolean- skip SSE and use polling only.pollIntervalMs?: number- polling interval for fallback/forced polling (default:2000).sseMaxConsecutiveErrors?: number- number of transport errors tolerated before fallback (default:3).
Events (StreamEvent):
Each event pushed by the stream is an object typically including:
status: Current status (e.g.,"IN_QUEUE","IN_PROGRESS","COMPLETED","CANCELLED","ERROR").- Other model-specific data.
Example (from examples/stream.ts):
import { yetter } from "@yetter/client"; // Adjust path as needed
async function main() {
const model = "ytr-ai/flux/dev";
let streamRequestId = "";
try {
console.log("\n--- Starting Stream Test ---");
const streamInstance = await yetter.stream(model, {
input: {
prompt: "a bioluminescent forest at night, fantasy art",
num_inference_steps: 28, // Required
},
});
streamRequestId = streamInstance.getRequestId();
console.log(`Stream initiated for Request ID: ${streamRequestId}`);
// Iterate over stream events (informational; termination is driven by server 'done'/'error' events)
for await (const event of streamInstance) {
console.log(
`[STREAM EVENT - ${streamRequestId}] Status: ${event.status}, QPos: ${event.queue_position}`
);
if (event.logs && event.logs.length > 0) {
console.log(` Logs for ${streamRequestId}:`);
event.logs.forEach(log => console.log(` - ${log.message}`));
}
}
console.log(`Stream for ${streamRequestId} finished iterating events.`);
// Final result becomes available after server emits `event: done`
const result = await streamInstance.done();
console.log("\n--- Stream Test Final Result ---");
console.log("Generated Images:", result.images);
} catch (err: any) {
console.error(`\n--- Stream Test Failed (Request ID: ${streamRequestId || 'UNKNOWN'}) ---`);
console.error("Error during stream test:", err.message || err);
}
}
main();3. yetter.queue
This namespace provides methods for managing jobs in a queue-based workflow. This is useful if you want to submit a job and check its status or retrieve its result later, without maintaining a persistent connection.
yetter.queue.submit(model, options)
Submits a job to the queue.
model: The model ID (e.g.,"ytr-ai/flux/dev").options.input: An object containing the input parameters for the model (e.g.,{ prompt: "your prompt" }).
Returns a promise that resolves to an object containing request_id, status, and queue_position.
yetter.queue.status(model, options)
Checks the status of a previously submitted job.
model: The model ID.options.requestId: Therequest_idobtained fromqueue.submit().
Returns a promise that resolves to an object containing the status data and requestId.
yetter.queue.result(model, options)
Retrieves the result of a completed job.
model: The model ID.options.requestId: Therequest_idof the completed job.
Returns a promise that resolves to an object containing the result data (e.g., images, prompt) and requestId.
Example (from examples/submit.ts):
import { yetter } from "@yetter/client";
async function main() {
const model = "ytr-ai/flux/dev";
try {
console.log("\n--- Starting Queue Submit Test ---");
const { request_id } = await yetter.queue.submit(model, {
input: {
prompt: "a fluffy white kitten playing with a yarn ball",
num_inference_steps: 28, // Required
},
});
console.log("Request ID:", request_id);
if (request_id) {
console.log(`\n--- Polling for status of Request ID: ${request_id} (10-minute timeout) ---`);
// Polling logic:
let success = false;
const startTime = Date.now();
const timeoutMilliseconds = 10 * 60 * 1000; // 10 minutes
const pollIntervalMilliseconds = 10 * 1000; // Poll every 10 seconds
while (Date.now() - startTime < timeoutMilliseconds) {
const statusResult = await yetter.queue.status(model, { requestId: request_id });
const currentStatus = statusResult.data.status;
console.log(`[${new Date().toLocaleTimeString()}] Status: ${currentStatus}, QPos: ${statusResult.data.queue_position}`);
if (currentStatus === "COMPLETED") {
const finalResult = await yetter.queue.result(model, { requestId: request_id });
console.log("\n--- Get Result Test Succeeded ---");
console.log("Image Data:", finalResult.data.images);
success = true;
break;
} else if (currentStatus === "ERROR") {
console.error(`Request ${request_id} FAILED. Logs:`, statusResult.data.logs);
break;
}
await new Promise(resolve => setTimeout(resolve, pollIntervalMilliseconds));
}
if (!success) console.error(`Polling Timed Out or Failed for ${request_id}`);
}
} catch (err: any) {
console.error("\n--- Queue Submit Test Failed ---", err.message);
}
}
main();4. Uploading Input Images
For models that require an input image (e.g., image-to-image, style transfer), you can upload your image using yetter.uploadFile() (Node.js) or yetter.uploadBlob() (browser).
Features:
- Support for both Node.js filesystem paths and browser File/Blob objects
- Automatic single-part or multipart upload based on file size
- Progress tracking with optional callback
- Returns a public URL to use in generation requests
Node.js Example
import { yetter } from "@yetter/client";
yetter.configure({ apiKey: process.env.YTR_API_KEY });
// Upload image
const uploadResult = await yetter.uploadFile("./input-image.jpg", {
onProgress: (percent) => console.log(`Upload: ${percent}%`),
});
// Use in generation
const result = await yetter.subscribe("ytr-ai/model/i2i", {
input: {
prompt: "Transform to anime style",
image_url: [uploadResult.url],
},
});Browser Example
// HTML: <input type="file" id="imageInput" accept="image/*">
const fileInput = document.getElementById('imageInput') as HTMLInputElement;
fileInput.addEventListener('change', async () => {
const file = fileInput.files![0];
const uploadResult = await yetter.uploadBlob(file, {
onProgress: (pct) => updateProgressBar(pct),
});
console.log("Uploaded:", uploadResult.url);
});Upload Options:
onProgress?: (progress: number) => void- Progress callback (0-100)timeout?: number- Timeout in milliseconds (default: 5 minutes)filename?: string- Custom filename (browser uploads)
Error Handling
The client functions generally throw errors for API issues, network problems, or failed generation requests. Ensure you wrap API calls in try...catch blocks to handle potential errors gracefully. Specific error messages or details (like logs for failed jobs) are often included in the thrown error object or the final status response.
The stream() function's done() promise will reject on failure or premature closure, and iterating the stream might also throw if connection errors occur.
