Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ dist
.yarn/install-state.gz
.pnp.*

test.js

test.ts

.DS_Store

Expand Down
4 changes: 3 additions & 1 deletion src/audio/audio.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { RequestClient } from "../request";
import { createFileUploadFormData } from "../utils";
import { SpeechToTextParams, SpeechToTextResponse } from "./interfaces";
class Audio {
constructor(private readonly client: RequestClient) {}
speech_to_text(params: SpeechToTextParams): Promise<SpeechToTextResponse>;
speech_to_text(file: Blob | Buffer, params?: Omit<SpeechToTextParams, "url" | "file_store_key">): Promise<SpeechToTextResponse>;
async speech_to_text(params: SpeechToTextParams | Blob | Buffer, options?: SpeechToTextParams): Promise<SpeechToTextResponse> {
if (params instanceof Blob || params instanceof Buffer) {
return await this.client.fetchJSS("/ai/transcribe", "POST", params, options);
const formData = createFileUploadFormData(params, options);
return await this.client.fetchJSS("/ai/transcribe", "POST", formData);
}
return await this.client.fetchJSS("/ai/transcribe", "POST", params);
}
Expand Down
7 changes: 5 additions & 2 deletions src/general/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseResponse } from "../../types";
import { respToFileChoice } from "../helpers";
import { RequestClient } from "../request";
import { createFileUploadFormData } from "../utils";
import {
EmbeddingParams,
EmbeddingResponse,
Expand Down Expand Up @@ -36,7 +37,8 @@ class General {
options?: Omit<TranslateImageParams, "file_store_key" | "url">
): Promise<ReturnType<typeof respToFileChoice>> => {
if (params instanceof Blob || params instanceof Buffer) {
const resp = await this.client.fetchJSS("/ai/translate/image", "POST", params, options);
const formData = createFileUploadFormData(params, options);
const resp = await this.client.fetchJSS("/ai/translate/image", "POST", formData);
return respToFileChoice(resp);
}
const resp = await this.client.fetchJSS("/ai/translate/image", "POST", params);
Expand Down Expand Up @@ -96,7 +98,8 @@ class General {
embedding(file: Blob | Buffer, params: Omit<EmbeddingParams, "url" | "file_store_key" | "file_content">): Promise<EmbeddingResponse>;
async embedding(params: EmbeddingParams | Blob | Buffer, options?: EmbeddingParams): Promise<EmbeddingResponse> {
if (params instanceof Blob || params instanceof Buffer) {
return await this.client.fetchJSS("/embedding", "POST", params, options);
const formData = createFileUploadFormData(params, options);
return await this.client.fetchJSS("/embedding", "POST", formData);
}
return await this.client.fetchJSS("/embedding", "POST", params);
}
Expand Down
18 changes: 15 additions & 3 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { BaseConfig } from "../types";
import { removeUndefinedProperties } from "./helpers";

const baseURL = "https://api.jigsawstack.com/v1";
// const baseURL = "http://localhost:3000";

export class RequestClient {
constructor(private readonly config: BaseConfig) {}

readonly fetchJSS = async (
path: string,
method: "POST" | "GET" | "DELETE",
body?: Record<string, any> | Blob,
body?: Record<string, any> | Blob | FormData,
searchParams?: {
[key: string]: any;
},
Expand All @@ -19,17 +20,28 @@ export class RequestClient {
) => {
const disableRequestLogging = this.config?.disableRequestLogging;
const isFileUpload = body instanceof Blob || body instanceof Buffer;
const isFormData = body instanceof FormData;

searchParams = searchParams ? removeUndefinedProperties(searchParams) : undefined;

const _headers = {
"x-api-key": this.config?.apiKey,
"Content-Type": isFileUpload ? "application/octet-stream" : "application/json",
...(!isFormData && { "Content-Type": isFileUpload ? "application/octet-stream" : "application/json" }),
...headers,
["x-jigsaw-no-request-log"]: disableRequestLogging && "true",
};

const _body = isFileUpload ? body : JSON.stringify(body);
let _body;

switch (true) {
case isFileUpload:
case isFormData:
_body = body;
break;
default:
_body = JSON.stringify(body);
}

const url = `${this.config?.baseURL || baseURL}${path}`;
const urlParams = searchParams && Object.keys(searchParams).length ? `?${new URLSearchParams(searchParams).toString()}` : "";

Expand Down
36 changes: 36 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Creates FormData for file uploads with optional parameters
* @param file - The file to upload (Blob or Buffer)
* @param options - Optional parameters to include in the form data
* @param fileFieldName - The field name for the file (defaults to "file")
* @returns FormData object ready for upload
*/
export function createFileUploadFormData(file: Blob | Buffer, options?: Record<string, any>, fileFieldName: string = "file"): FormData {
const formData = new FormData();

// Convert Buffer to Blob if needed
const fileToAppend = file instanceof Buffer ? new Blob([file]) : file;
formData.append(fileFieldName, fileToAppend);

// Add options as form fields, handling arrays and objects properly
if (options) {
for (const [key, value] of Object.entries(options)) {
if (value === undefined || value === null) {
continue; // Skip undefined/null values
}

if (Array.isArray(value)) {
// Convert arrays to JSON strings
formData.append(key, JSON.stringify(value));
} else if (typeof value === "object") {
// Convert objects to JSON strings
formData.append(key, JSON.stringify(value));
} else {
// Convert primitives to strings
formData.append(key, String(value));
}
}
}

return formData;
}
12 changes: 9 additions & 3 deletions src/validate/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RequestClient } from "../request";
import { createFileUploadFormData } from "../utils";
import {
EmailValidationResponse,
NSFWParams,
NSFWValidationResponse,
ProfanityParams,
Expand All @@ -16,8 +16,14 @@ class Validate {
this.spamcheck = this.spamcheck.bind(this);
}

nsfw(params: NSFWParams | Blob | Buffer): Promise<NSFWValidationResponse> {
return this.client.fetchJSS("/validate/nsfw", "POST", params);
nsfw(params: NSFWParams): Promise<NSFWValidationResponse>;
nsfw(file: Blob | Buffer, params?: Omit<NSFWParams, "url" | "file_store_key">): Promise<NSFWValidationResponse>;
async nsfw(params: NSFWParams | Blob | Buffer, options?: NSFWParams): Promise<NSFWValidationResponse> {
if (params instanceof Blob || params instanceof Buffer) {
const formData = createFileUploadFormData(params, options);
return await this.client.fetchJSS("/validate/nsfw", "POST", formData);
}
return await this.client.fetchJSS("/validate/nsfw", "POST", params);
}

profanity = async ({ text, censor_replacement = "*" }: ProfanityParams): Promise<ProfanityValidationResponse> => {
Expand Down
23 changes: 19 additions & 4 deletions src/vision/vision.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import { RequestClient } from "../request";
import { createFileUploadFormData } from "../utils";
import { ObjectDetectionParams, ObjectDetectionResponse, VOCRParams, VOCRResponse } from "./interfaces";
class Vision {
constructor(private readonly client: RequestClient) {}
vocr = async (params: VOCRParams): Promise<VOCRResponse> => {

vocr(params: VOCRParams): Promise<VOCRResponse>;
vocr(file: Blob | Buffer, params?: Omit<VOCRParams, "url" | "file_store_key">): Promise<VOCRResponse>;
async vocr(params: VOCRParams | Blob | Buffer, options?: VOCRParams): Promise<VOCRResponse> {
if (params instanceof Blob || params instanceof Buffer) {
const formData = createFileUploadFormData(params, options);
return await this.client.fetchJSS("/vocr", "POST", formData);
}
return await this.client.fetchJSS("/vocr", "POST", params);
};
object_detection = async (params: ObjectDetectionParams): Promise<ObjectDetectionResponse> => {
}

object_detection(params: ObjectDetectionParams): Promise<ObjectDetectionResponse>;
object_detection(file: Blob | Buffer, params?: Omit<ObjectDetectionParams, "url" | "file_store_key">): Promise<ObjectDetectionResponse>;
async object_detection(params: ObjectDetectionParams | Blob | Buffer, options?: ObjectDetectionParams): Promise<ObjectDetectionResponse> {
if (params instanceof Blob || params instanceof Buffer) {
const formData = createFileUploadFormData(params, options);
return await this.client.fetchJSS("/object_detection", "POST", formData);
}
return await this.client.fetchJSS("/object_detection", "POST", params);
};
}
}

export default Vision;
16 changes: 0 additions & 16 deletions test.ts

This file was deleted.

10 changes: 10 additions & 0 deletions tests/audio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ describe("STT APIs", () => {
expectType(result.text, "string");
});

test("speech to text with file upload", async () => {
const audioResponse = await fetch("https://jigsawstack.com/preview/stt-example.wav");
const audioBlob = await audioResponse.blob();
const result = await client.audio.speech_to_text(audioBlob);

expectSuccess(result);
expectProperty(result, "text");
expectType(result.text, "string");
});

test("speech to text with auto language", async () => {
const result = await client.audio.speech_to_text({
url: audioUrl,
Expand Down
1 change: 1 addition & 0 deletions tests/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function createJigsawStackClient() {
sentiment: (params: any) => new General(client).sentiment(params),
translate: {
text: (params: any) => new General(client).translate.text(params),
image: (params: any) => new General(client).translate.image(params),
},
summary: (params: any) => new General(client).summary(params),
embedding: (params: any) => new General(client).embedding(params),
Expand Down
Loading