-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
287 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { EmpathicVoice as FernClient } from "../../api/resources/empathicVoice/client/Client"; | ||
import { ChatClient } from "./chat/ChatClient"; | ||
|
||
export class EmpathicVoice extends FernClient { | ||
|
||
protected _chat: ChatClient | undefined; | ||
|
||
public get chat(): ChatClient { | ||
return (this._chat ??= new ChatClient(this._options)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import * as Hume from "../../../api"; | ||
import * as serializers from "../../../serialization"; | ||
import * as core from "../../../core"; | ||
import * as errors from "../../../errors"; | ||
import qs from "qs"; | ||
import WebSocket from "ws"; | ||
import { base64Encode } from "../../base64Encode"; | ||
import { StreamSocket } from "./StreamSocket"; | ||
|
||
export declare namespace ChatClient { | ||
interface Options { | ||
apiKey?: core.Supplier<string | undefined>; | ||
clientSecret?: core.Supplier<string | undefined>; | ||
} | ||
|
||
interface ConnectArgs { | ||
/** The ID of the configuration. */ | ||
configId: string; | ||
|
||
/** The version of the configuration. */ | ||
configVersion: string; | ||
|
||
onOpen?: (event: WebSocket.Event) => void; | ||
onMessage?: (message: Hume.empathicVoice.SubscribeEvent) => void; | ||
onError?: (error: Hume.empathicVoice.Error_) => void; | ||
onClose?: (event: WebSocket.Event) => void; | ||
} | ||
} | ||
|
||
export class ChatClient { | ||
constructor(protected readonly _options: ChatClient.Options) {} | ||
|
||
public async connect(args: ChatClient.ConnectArgs): Promise<StreamSocket> { | ||
const queryParams: Record<string, string | string[] | object | object[]> = {}; | ||
|
||
queryParams["accessToken"] = await this.fetchAccessToken(); | ||
queryParams["apiKey"] = core.Supplier.get(this._options.apiKey); | ||
queryParams["config_id"] = args.configId; | ||
queryParams["config_id"] = args.configVersion; | ||
|
||
const websocket = new WebSocket(`wss://api.hume.ai/v0/evi/chat${qs.stringify(queryParams)}`, { | ||
timeout: 10 | ||
}); | ||
|
||
websocket.addEventListener("open", (event) => { | ||
args.onOpen?.(event); | ||
}); | ||
|
||
websocket.addEventListener("error", (e) => { | ||
args.onError?.({ | ||
type: "error", | ||
code: e.type, | ||
message: e.message, | ||
slug: "websocket-error" | ||
}); | ||
}); | ||
|
||
websocket.addEventListener("message", async ({ data }) => { | ||
parse(data, { | ||
onMessage: args.onMessage, | ||
onError: args.onError, | ||
}); | ||
}); | ||
|
||
websocket.addEventListener("close", (event) => { | ||
args.onClose?.(event); | ||
}); | ||
|
||
return new StreamSocket({ | ||
websocket, | ||
}); | ||
} | ||
|
||
|
||
private async fetchAccessToken(): Promise<string> { | ||
const apiKey = await core.Supplier.get(this._options.apiKey); | ||
const clientSecret = await core.Supplier.get(this._options.clientSecret); | ||
|
||
const authString = `${apiKey}:${clientSecret}`; | ||
const encoded = base64Encode(authString); | ||
|
||
const response = await core.fetcher({ | ||
url: `https://api.hume.ai/oauth2-cc/token`, | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/x-www-form-urlencoded", | ||
Authorization: `Basic ${encoded}`, | ||
}, | ||
body: new URLSearchParams({ | ||
grant_type: "client_credentials", | ||
}).toString(), | ||
}); | ||
|
||
if (!response.ok) { | ||
if (response.error.reason === "status-code") { | ||
throw new errors.HumeError({ | ||
statusCode: response.error.statusCode, | ||
body: response.error.body, | ||
}); | ||
} | ||
|
||
switch (response.error.reason) { | ||
case "non-json": | ||
throw new errors.HumeError({ | ||
statusCode: response.error.statusCode, | ||
body: response.error.rawBody, | ||
}); | ||
case "timeout": | ||
throw new errors.HumeTimeoutError(); | ||
case "unknown": | ||
throw new errors.HumeError({ | ||
message: response.error.errorMessage, | ||
}); | ||
} | ||
} | ||
|
||
return (response.body as any).access_token as string; | ||
}; | ||
} | ||
|
||
export async function parse( | ||
data: WebSocket.Data, | ||
args: { | ||
onMessage?: (message: Hume.empathicVoice.SubscribeEvent) => void; | ||
onError?: (error: Hume.empathicVoice.Error_) => void; | ||
} = {} | ||
): Promise<Hume.empathicVoice.SubscribeEvent | undefined> { | ||
const message = JSON.parse(data as string); | ||
|
||
const parsedResponse = await serializers.empathicVoice.SubscribeEvent.parse(message, { | ||
unrecognizedObjectKeys: "passthrough", | ||
allowUnrecognizedUnionMembers: true, | ||
allowUnrecognizedEnumValues: true, | ||
breadcrumbsPrefix: ["response"], | ||
}); | ||
if (parsedResponse.ok) { | ||
args.onMessage?.(parsedResponse.value); | ||
|
||
if (parsedResponse.value.type === "error") { | ||
args.onError?.(parsedResponse.value); | ||
} | ||
|
||
return parsedResponse.value; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import WebSocket from "ws"; | ||
import * as Hume from "../../../api"; | ||
import * as serializers from "../../../serialization"; | ||
|
||
export declare namespace StreamSocket { | ||
interface Args { | ||
websocket: WebSocket; | ||
} | ||
} | ||
|
||
export class StreamSocket { | ||
public readonly websocket: WebSocket; | ||
|
||
constructor({ websocket }: StreamSocket.Args) { | ||
this.websocket = websocket; | ||
} | ||
|
||
/** | ||
* Send audio input | ||
**/ | ||
public async sendAudioInput(message: Hume.empathicVoice.AudioInput): Promise<void> { | ||
await this.send(message); | ||
} | ||
|
||
/** | ||
* Send session settings | ||
*/ | ||
public async sendSessionSettings(message: Hume.empathicVoice.SessionSettings): Promise<void> { | ||
await this.send(message); | ||
} | ||
|
||
/** | ||
* Send text input | ||
*/ | ||
public async sendTextInput(message: Hume.empathicVoice.TextInput): Promise<void> { | ||
await this.send(message); | ||
} | ||
|
||
/** | ||
* | ||
* Send TTS input | ||
* | ||
*/ | ||
public async sendTtsInput(message: Hume.empathicVoice.TtsInput): Promise<void> { | ||
await this.send(message); | ||
} | ||
|
||
/** | ||
* Closes the underlying socket. | ||
*/ | ||
public close(): void { | ||
this.websocket.close(); | ||
} | ||
|
||
private async send( | ||
payload: Hume.empathicVoice.PublishEvent | ||
): Promise<void> { | ||
await this.tillSocketOpen(); | ||
const jsonPayload = await serializers.expressionMeasurement.StreamData.jsonOrThrow(payload, { | ||
unrecognizedObjectKeys: "strip", | ||
}); | ||
this.websocket.send(JSON.stringify(jsonPayload)); | ||
} | ||
|
||
private async tillSocketOpen(): Promise<WebSocket> { | ||
if (this.websocket.readyState === WebSocket.OPEN) { | ||
return this.websocket; | ||
} | ||
return new Promise((resolve, reject) => { | ||
this.websocket.addEventListener("open", () => { | ||
resolve(this.websocket); | ||
}); | ||
|
||
this.websocket.addEventListener("error", (event) => { | ||
reject(event); | ||
}); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { HumeClient } from "../../src/" | ||
|
||
describe("Streaming Expression Measurement", () => { | ||
it("Emotional Language Text", async () => { | ||
const hume = new HumeClient({ | ||
apiKey: "<>" | ||
}); | ||
const job = await hume.expressionMeasurement.batch.startInferenceJob({ | ||
models: { | ||
face: {} | ||
}, | ||
urls: ["https://hume-tutorials.s3.amazonaws.com/faces.zip"] | ||
}); | ||
await job.awaitCompletion(); | ||
const predictions = await hume.expressionMeasurement.batch.getJobPredictions(job.jobId); | ||
console.log(JSON.stringify(predictions, null, 2)); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { HumeClient } from "../../src/" | ||
|
||
const samples = [ | ||
"Mary had a little lamb,", | ||
"Its fleece was white as snow.", | ||
"Everywhere the child went,", | ||
"The little lamb was sure to go." | ||
]; | ||
|
||
describe("Streaming Expression Measurement", () => { | ||
it.skip("Emotional Language Text", async () => { | ||
const hume = new HumeClient({ | ||
apiKey: "<>" | ||
}); | ||
const socket = hume.expressionMeasurement.stream.connect({ | ||
config: { | ||
language: {} | ||
} | ||
}) | ||
for (const sample of samples) { | ||
const result = await socket.sendText({ text: sample }) | ||
console.log(result) | ||
} | ||
}, 100000); | ||
}); |