UNPKG

apphouse

Version:

Component library for React that uses observable state management and theme-able components.

208 lines (185 loc) 5.86 kB
import { makeAutoObservable } from 'mobx'; import {} from 'openai'; import { Chat, List, getErrorMessage } from '..'; import { getUniqueId } from '../utils/string/getUniqueId'; import { ChatHistory } from './Chat'; import { ChatCompletionMessageParam } from 'openai/resources/index.mjs'; enum OpenAIErrorCodes { invalidRequest = 'invalid-request', contextLengthExceeded = 'context_length_exceeded' } export interface OpenAiChatApi { chatCompletion: (history: ChatHistory) => Promise<any>; createAudioTranscription: (file: File) => Promise<any>; uploadFile: (file: File) => Promise<any>; } export type ChatCompletionMessage = ChatCompletionMessageParam & { id: string; }; export type ChatRoomHistory = List<ChatCompletionMessage>; export class OpenAiChat { chat: Chat; loadingAiResponse: boolean; api: OpenAiChatApi; constructor(api: OpenAiChatApi) { this.api = api; const openAiUsers = [ { id: 'user', name: 'User', role: 'user' }, { id: 'assistant', name: 'Assistant', role: 'assistant' }, { id: 'error', name: 'Error', role: 'system' }, { id: 'system', name: 'System', role: 'system' } ]; this.chat = new Chat(openAiUsers, []); this.loadingAiResponse = false; this.chat.addMessage({ id: getUniqueId(), userId: 'assistant', content: 'Hello, how can I help you today?' }); makeAutoObservable(this); } get lastAssistantMessageIndex() { const lastAssistantMessageIndex = this.history.values.map( (message, index) => { if (message.userId === 'assistant') { return index; } return 0; } ); return lastAssistantMessageIndex[lastAssistantMessageIndex.length - 1]; } get history() { return this.chat.history; } /** * The api does not include an id for the message, so we strip these out */ get chatAiChatHistory() { return this.chat.history.values.map(({ id, ...message }) => message); } addMessage = (message: ChatCompletionMessage) => { this.history.set({ id: message.id, userId: message.role, content: message.content || ('' as any) }); }; requestAudioTranscription = async (file: File) => { this.addMessage({ id: getUniqueId(), role: 'user', content: `request transcription for ${file.name}` }); this.setAiLoading(true); const response = await this.api.createAudioTranscription(file); if (response.type === 'error') { this.setAiLoading(false); this.addMessage({ id: getUniqueId(), role: 'system', content: response.message || '' }); } else { this.setAiLoading(false); this.addMessage({ id: getUniqueId(), role: 'assistant', content: response.message || '' }); } }; uploadFile = async (file: File) => { this.addMessage({ id: getUniqueId(), role: 'user', content: `uploading file for fine tunning ${file.name}` }); this.setAiLoading(true); try { const response = await this.api.uploadFile(file); if (response?.type === 'error') { this.handleErrorMessage(response); this.setAiLoading(false); } else { this.setAiLoading(false); this.addMessage({ id: getUniqueId(), role: 'assistant', content: String(response?.message) || '' }); } } catch (error) { this.handleErrorMessage(error); this.setAiLoading(false); } }; sendUserMessage = async (message: string) => { this.addMessage({ id: getUniqueId(), role: 'user', content: message }); this.setAiLoading(true); try { const response = await this.api.chatCompletion(this.history); if (response.type === 'error') { this.handleErrorMessage(response); try { this.setAiLoading(false); } catch (error) { console.log(error); } } else { this.addMessage({ id: getUniqueId(), role: 'assistant', content: response.message || '' }); this.setAiLoading(false); } } catch (error) { this.handleErrorMessage(error); this.setAiLoading(false); } }; setAiLoading = (loading: boolean) => { this.loadingAiResponse = loading; }; /** * Handle error when content length is exceeded * code: "context_length_exceeded" * message: "This model's maximum context length is 4097 tokens. However, your messages resulted in 4176 tokens. Please reduce the length of the messages." * param: "messages" * type: "invalid_request_error" */ private handleErrorContentLengthExceeded = (message?: string) => { // When the user sends a message it takes into account the messages // in the history. // Lets try to make the history smaller by clearing old messages and send the message again. if (this.history.length > 1) { this.history.shift(); // let's try to send the message again this.sendUserMessage(message || ''); } else { this.handleErrorMessage({ code: 'context_length_exceeded', type: 'invalid_request_error', message: "This model's maximum context length is 4097 tokens. However, your messages resulted in 4176 tokens. Please reduce the length of the messages." }); } }; private resolveErrorMessage = (error: any) => { const message = getErrorMessage(error); this.addMessage({ id: getUniqueId(), role: 'system', content: message || '' }); return message; }; private handleErrorMessage = async (error: any, text?: string) => { if (error.code === OpenAIErrorCodes.contextLengthExceeded) { this.handleErrorContentLengthExceeded(text); return; } return this.resolveErrorMessage(error); }; }