apphouse
Version:
Component library for React that uses observable state management and theme-able components.
238 lines (220 loc) • 6.21 kB
text/typescript
import { makeAutoObservable } from 'mobx';
import { fetchMe } from '../utils/network/fetchMe';
import { getErrorMessage } from '../utils/Error';
import OpenAI, { ClientOptions } from 'openai';
export interface SpeechCreateParams {
voice: string;
}
const APPHOUSE_PROD_AI_ENDPOINT = 'us-central1-apphouse-cc674';
export const TEST_HOST = 'http://127.0.0.1:5001/apphouse-cc674/us-central1/';
export const PROD_HOST = `https://${APPHOUSE_PROD_AI_ENDPOINT}.cloudfunctions.net/`;
export const DEFAULT_AUDIO_VOICE = 'alloy';
interface OpenAiFunctions {
getThemeColorsV2: 'getThemeColorsV2';
getDocumentAppConfig: 'getDocumentAppConfig';
getDocumentAppForm: 'getDocumentAppForm';
getDocumentTemplate: 'getDocumentTemplate';
askOpenAi: 'askOpenAi';
getAiImage: 'getAiImage';
}
export interface AssistantResponse {
type: 'success' | 'error';
message: string;
}
interface OpenAiFetchProps {
/**
* If url is not provided, the function name will be used to construct the url
* @example https://us-central1-my-project.cloudfunctions.net/my-function
*/
url?: string;
/**
* The prompt to be sent to the openAi api
*/
prompt?: string;
/**
* The name of the function to be called
*/
functionName: keyof OpenAiFunctions;
}
/**
* OpenAi class to be used to fetch data from the openai api
*/
export class OpenAi {
response: any = null;
loading = false;
error: string | null = null;
host: string;
openaiClient?: OpenAI;
constructor(
prod = false,
host?: string,
configuration?: ClientOptions | undefined
) {
this.loading = false;
this.error = null;
this.host = host || prod ? PROD_HOST : TEST_HOST;
this.openaiClient = undefined;
if (configuration) {
this.init(configuration);
}
makeAutoObservable(this);
}
setHost(host: string) {
this.host = host;
}
/**
* Ask the openai api a question
* @param request the request to send to the openai api
* @param resource any extra background information to provide to the api
* @returns
*/
ask = async (request: string, resource?: string) => {
const response = this.fetch({
prompt: JSON.stringify({
resource: resource || 'your best bet',
requirement: request
}),
functionName: 'askOpenAi'
});
return response;
};
fetch = async (fetchProps: OpenAiFetchProps) => {
const { functionName, url, prompt } = fetchProps;
const apiUrl = (url || this.host) + functionName;
console.log({ apiUrl });
this.loading = true;
const response = await fetchMe(apiUrl, { prompt });
this.loading = false;
console.log(response);
if (typeof response === 'string') {
this.error = response;
return undefined;
} else {
try {
const res = response?.json();
return res?.then((r) => {
console.log(r);
if (r?.message) {
try {
return JSON.parse(r?.message);
} catch (e) {
console.log(e);
return r;
}
} else {
console.log(r);
return r;
}
});
} catch (e) {
console.log(e);
return getErrorMessage(e);
}
}
};
/**
* Initialize openAi Client
* @param config the openai client configuration
*/
init = (config: ClientOptions) => {
this.openaiClient = new OpenAI(config);
};
/**
* Convert text to audio and start downloading mp3 file
* @param text The text to convert to audio
*/
toAudio = async (
text: string,
voiceSelection: SpeechCreateParams['voice']
) => {
const voice = voiceSelection as any;
try {
const response = await this.openaiClient?.audio.speech.create(
{
input: text,
model: 'tts-1',
voice: voice,
response_format: 'mp3'
},
{ __binaryResponse: true }
);
console.log('toAudio response', response);
// convert readable stream to blob from response and create a url for user to download
const stream = response?.body;
if (stream) {
const reader = stream.getReader();
const chunks = [];
let done = false;
while (!done) {
const { value, done: doneValue } = await reader.read();
done = doneValue;
if (value) {
// @ts-ignore
chunks.push(value);
}
}
const blob = new Blob(chunks, { type: 'audio/mp3' });
const url = URL.createObjectURL(blob);
return { blob, url };
}
} catch (error) {
console.error('Error converting text to audio:', error);
// Handle the error as needed
return {
type: 'error',
message: getErrorMessage(error) || 'Unknown error'
};
}
};
/**
* Create a transcription of an audio file
* @param file the audio file to transcribe
* @returns the transcription of the audio file
*/
createAudioTranscription = async (file: File): Promise<AssistantResponse> => {
try {
await this.openaiClient?.audio.transcriptions
.create({
model: 'whisper-1',
file: file
})
.then((res) => {
console.log('res', res);
return {
type: 'success' as any,
message: res?.text || ''
};
})
.catch((err) => {
console.log('error', { err });
const e = {
type: 'error' as any,
message: getErrorMessage(err) || 'Unknown error'
};
return e;
});
} catch (error) {
return {
type: 'error',
message: getErrorMessage(error) || 'Unknown error'
};
}
return {
type: 'error',
message: 'Unknown error'
};
};
}
export const openAi = new OpenAi(true);
/**
* A hook to use the openai instance.
* Note: This is a singleton object and should be used with caution.
* This will be a global object and will be shared across the application.
* @returns the openai instance
*/
export const useOpenAi = (config?: ClientOptions) => {
if (config) {
openAi.init(config);
}
return openAi;
};