diff --git a/src/components/chat-window.vue b/src/components/chat-window.vue index dda64af..004cbd7 100644 --- a/src/components/chat-window.vue +++ b/src/components/chat-window.vue @@ -163,9 +163,9 @@ export default { if (this.isActive) return; const msg = this.userMessage; console.log('submit: ', msg); - + this.isLoading = true; this.$forceUpdate(); - + // if (this.selectedPlugins?.length > 0) { // try{ // console.log('chat:submit: Selected plugins: ', this.selectedPlugins); @@ -191,7 +191,7 @@ export default { // } try{ - const config = { + const config = { frequency_penalty: this.bot?.frequency_penalty, presence_penalty: this.bot?.presence_penalty, temperature: this.bot?.temperature, @@ -214,7 +214,7 @@ export default { this.userMessage = ''; this.messages.push(newUserMsg) } - + const newMsg = {role:'assistant', content: ''}; this.messages.push(newMsg); this.scrollChatToBottom(); @@ -230,15 +230,21 @@ export default { if (this.selectedPlugins?.length > 0) { await this.altGpt.performSmartCompletion(messages, this.selectedPlugins, config, onDelta, priming, this.pluginsSettings); } else { - await this.openAI.createChatCompletionStream(messages, config, priming, onDelta); + // await this.openAI.createChatCompletionStream(messages, config, priming, onDelta); + await this.openAI.createChatCompletionRes( + messages, + config, + priming, + onDelta + ); } this.$forceUpdate(); - + this.messages.push(this.messages.pop()); // so it'll be stored properly in cache // newText = newMsg.content; } catch(err) { - let msg = err?.error?.message || err?.message; + let msg = err?.error?.message || err?.message || err; if (err.statusText) msg = `${err?.statusText} (${err?.statusCode})`; helpers.toast(`Failed to process request: ${msg || ''}`, 'is-danger', 'is-top'); @@ -334,5 +340,4 @@ export default { p { margin-top:0; margin-bottom:0; } img { min-width:40px; min-height:40px; border: 1px solid #999; background-color:#555 ; } } - diff --git a/src/scripts/ts/modules/altGPT.ts b/src/scripts/ts/modules/altGPT.ts index d347c1b..cc0318b 100644 --- a/src/scripts/ts/modules/altGPT.ts +++ b/src/scripts/ts/modules/altGPT.ts @@ -172,8 +172,7 @@ export class AltGPT { }); const _priming = `You are a bot designed to assist machines in answering user inquiries based solely on the given context. If relevant to user's inquiry, extract full links from the context and maintain them unchanged. If there are multiple outcomes, present them as bullet points, each accompanied by the pertinent link. If the supplied context is empty or yields no results, state that the search produced no findings and recommend refining the query. If the answer is not included, respond with 'Hmm, I'm not sure...'. ${priming}`; const _config = { ...this.basicConfig, ...config }; - const res = await this.openAI.createChatCompletionStream(_messages, _config, _priming, onDelta); - + const res = await this.openAI.createChatCompletionRes(_messages, _config, _priming, onDelta); return res; } diff --git a/src/scripts/ts/modules/openAI.ts b/src/scripts/ts/modules/openAI.ts index c1afc30..37f1446 100644 --- a/src/scripts/ts/modules/openAI.ts +++ b/src/scripts/ts/modules/openAI.ts @@ -6,180 +6,403 @@ import { IConvMessage } from '../types/IConvMessage.js'; import { OpenAIResponseChunk } from '../types/OpenAIResponseChunk.js'; export class OpenAI { - public openaiLib: typeof openai; - public openaiApi; - - public constructor(public options?: Partial) { - this.options = { ...new ModuleOptions(), ...options }; - this.openaiLib = openai; - - const configuration = new openai.Configuration({ - apiKey: '', - }); - this.openaiApi = new openai.OpenAIApi(configuration); - } - - public async makeCompletion(prompt: string): Promise> { - const response = await this.openaiApi.createCompletion({ - model: 'text-davinci-003', // 'text-davinci-003', - prompt: prompt, - temperature: 0.1, - max_tokens: 512, - top_p: 1.0, - frequency_penalty: 0.0, - presence_penalty: 0.0, - // stop: [''], - }); - return response; - } - - public async createChatCompletion(messages: IConvMessage[], config, priming?: string) { - const p = libx.newPromise(); - const url = `https://api.openai.com/v1/chat/completions`; - const apiKey = config.apikey; - const payload = { - "messages": [ - { - role: 'system', - content: priming ?? `You are an assistant bot.`, - }, - ...messages - ], - ...{ - // defaults: - frequency_penalty: 0, - presence_penalty: 0, - temperature: 0.8, - max_tokens: 256, // 512, - model: "gpt-3.5-turbo", - ...config, // override - stream: false, - // user: 'userId', - top_p: 1.0, - n: 1, - } - }; - delete payload.apikey; - - const res: any = await fetch(url, { - method: "POST", - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - ...payload, - "stream": false, - }), - }); - - const json = await res.json(); - - if (json.choices && json.choices.length > 0) { - return json.choices[0]?.message?.content || ''; - } - - return json ?? res; - } - - public async createChatCompletionStream(messages: IConvMessage[], config, priming?: string, onDelta?: (data) => void, onProgress?: (wholeData) => void) { - const p = libx.newPromise(); - const url = `https://api.openai.com/v1/chat/completions`; - const apiKey = config.apikey; - const payload = { - "messages": [ - { - role: 'system', - content: priming ?? `You are an assistant bot.`, - }, - ...messages - ], - ...{ - // defaults: - frequency_penalty: 0, - presence_penalty: 0, - temperature: 0.8, - max_tokens: 256, // 512, - model: "gpt-3.5-turbo", - ...config, // override - stream: true, - // user: 'userId', - top_p: 1.0, - n: 1, - } - }; - delete payload.apikey; - - const eventSource = new SSE(url, { - method: "POST", - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - }, - payload: JSON.stringify({ - ...payload, - "stream": true, - }), - }) as SSE; - - let contents = ''; - - eventSource.addEventListener('error', (event: any) => { - if (!contents) { - libx.log.e('error: ', event, contents) - p.reject(JSON.parse(event.data)); - } - }); - - eventSource.addEventListener('message', async (event: any) => { - if (event.data === '[DONE]') { - libx.log.d('message: done: ', event, contents) - p.resolve(contents); - return; - } - - try { - const chunk = this.parseChunk(event.data); - if (chunk.choices && chunk.choices.length > 0) { - const newData = chunk.choices[0]?.delta?.content || ''; - contents += newData; - if (onDelta) onDelta(newData); - if (onProgress) onProgress(contents); - } - } catch (err) { - console.error(err); - p.reject(err); - } - }); - - eventSource.stream(); - return p; - } - - private parseChunk(buffer: any): OpenAIResponseChunk { - const chunk = buffer.toString().split(/\s*data\:\s*/); - // for (let content of chunk) { - // content = content.trim(); - // } - // const chunk = buffer.toString().replace('data: ', '').trim(); - - if (libx.isEmptyString(chunk) || chunk === '[DONE]') { - return { - done: true, - }; - } - - const parsed = JSON.parse(chunk); - - return { - id: parsed.id, - done: false, - choices: parsed.choices, - model: parsed.model, - }; - } + public openaiLib: typeof openai; + public openaiApi; + + public constructor(public options?: Partial) { + this.options = { ...new ModuleOptions(), ...options }; + this.openaiLib = openai; + + const configuration = new openai.Configuration({ + apiKey: '', + }); + this.openaiApi = new openai.OpenAIApi(configuration); + } + + public async makeCompletion(prompt: string): Promise> { + const response = await this.openaiApi.createCompletion({ + model: 'text-davinci-003', // 'text-davinci-003', + prompt: prompt, + temperature: 0.1, + max_tokens: 512, + top_p: 1.0, + frequency_penalty: 0.0, + presence_penalty: 0.0, + // stop: [''], + }); + return response; + } + + public async createChatCompletion(messages: IConvMessage[], config, priming?: string) { + const p = libx.newPromise(); + const url = `https://api.openai.com/v1/chat/completions`; + const apiKey = config.apikey; + const payload = { + "messages": [ + { + role: 'system', + content: priming ?? `You are an assistant bot.`, + }, + ...messages + ], + ...{ + // defaults: + frequency_penalty: 0, + presence_penalty: 0, + temperature: 0.8, + max_tokens: 256, // 512, + model: "gpt-3.5-turbo", + ...config, // override + stream: false, + // user: 'userId', + top_p: 1.0, + n: 1, + } + }; + delete payload.apikey; + + const res: any = await fetch(url, { + method: "POST", + headers: { + 'Accept': 'application/json, text/plain, */*', + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...payload, + "stream": false, + }), + }); + + const json = await res.json(); + + if (json.choices && json.choices.length > 0) { + return json.choices[0]?.message?.content || ''; + } + + return json ?? res; + } + + public async createChatCompletionStream({ + url, + headers, + payload, + p, + getNewData, + onDelta, + onProgress, + }) { + const eventSource = new SSE(url, { + method: "POST", + headers: headers, + payload: JSON.stringify({ + ...payload, + stream: true, + }), + }) as SSE; + + let contents = ""; + + eventSource.addEventListener("error", (event: any) => { + if (!contents) { + let errorMessage; + try { + errorMessage = JSON.parse(event.data); + } catch (e) { + errorMessage = event.data; + } + + libx.log.e("error: ", event, contents); + p.reject(errorMessage); + } + }); + + eventSource.addEventListener("message", async (event: any) => { + console.log("🚀 ~ file: openAI.ts:140 ~ OpenAI ~ eventSource.addEventListener ~ event:", event); + if (event?.data === "[DONE]") { + // Handle this special case, maybe close the event source or do some other logic + libx.log.d("message: done: ", event, contents); + p.resolve(contents); + return; + } + + if (!event.data || event.data.trim() === "") { + console.log("Received empty or invalid data."); + return; // Skip processing for empty data + } + + try { + const newData = getNewData(event.data); + if (newData) { + contents += newData; + if (onDelta) onDelta(newData); + if (onProgress) onProgress(contents); + } + } catch (err) { + console.error(err); + p.reject(err); + } + }); + + eventSource.stream(); + + return p; + } + + public async createChatCompletionHTTP({ + url, + headers, + payload, + getNewData, + onDelta, + }) { + const response = await fetch(url, { + method: "POST", + headers: headers, + body: JSON.stringify(payload), + }); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + let httpResponse = await response.json(); + let res = getNewData(httpResponse); + return onDelta(res); + } + + public async createChatCompletionRes( + messages: IConvMessage[], + config, + priming?: string, + onDelta?: (data) => void, + onProgress?: (wholeData) => void + ) { + const p = libx.newPromise(); + + //// payloads + const setupPayloadForGPT = (model) => { + return { + messages: [ + { + role: "system", + content: priming ?? `You are an assistant bot.`, + }, + ...messages, + ], + // messages: [ + // messages.length > 0 + // ? { role: "user", content: messages[messages.length - 1].content } + // : "", + // ], + ...{ + frequency_penalty: 0, + presence_penalty: 0, + temperature: 0.8, + max_tokens: 256, + model, + ...config, + stream: true, + top_p: 1.0, + n: 1, + }, + }; + }; + + const setupPayloadForDavinci = (model) => { + const userMessages = messages.filter((m) => m.role === "user"); + const prompt = + userMessages.length > 0 + ? userMessages[userMessages.length - 1].content + : ""; + return { + prompt, + frequency_penalty: 0, + presence_penalty: 0, + temperature: 0.8, + max_tokens: 256, + model, + top_p: 1.0, + n: 1, + ...config, + }; + }; + + const setupPayloadForCohere = (model) => { + return { + prompt: + messages.length > 0 ? messages[messages.length - 1].content : "", + model, + max_tokens: 256, + // stream: true, + ...config, + }; + }; + + const setupPayloadForAnthropic = (model) => { + const systemPrompt = `\n\nHuman: ${messages.length > 0 ? messages[messages.length - 1].content : "" + }\n\nAssistant:`; + const newConfig = { + provider: "anthropic", + model: model, + maxTokens: 300, + stream: true, + frequencyPenalty: config.frequencyPenalty || 0, + presencePenalty: config.presencePenalty || 0, + temperature: config.temperature || 0.8, + }; + + return { + systemPrompt, + config: newConfig, + stream: true, + + }; + }; + const setupPayloadForAI21 = (model) => { + return { + prompt: + messages.length > 0 ? messages[messages.length - 1].content : "", + model_type: model, + maxTokens: 256, + ...config, + }; + }; + + //// handle responses + const handleGPTResponse = (data) => { + try { + const chunk = JSON.parse(data); + if (chunk.choices && chunk.choices.length > 0) { + return chunk.choices[0]?.delta?.content || ""; + } + } catch (err) { + console.error("Error parsing GPT response", err); + } + return ""; + }; + + const handleDavinciResponse = (data) => { + return data.choices[0].text.trim(); + }; + + const handleCohereResponse = (chunk) => { + if (chunk.generations && chunk.generations.length > 0) { + return chunk.generations[0].text; + } + throw new Error("No completion found in Cohere response."); + }; + + const handleAnthropicResponse = (data) => { + try { + const chunk = JSON.parse(data); + return chunk.completion || ""; + } catch (err) { + console.error("Error parsing Anthropics response", err); + return ""; + } + }; + + const handleAI21Response = (httpResponse) => { + return httpResponse?.completions[0]?.data?.text; + }; + + let url: string; + let payload: any; + let getNewData: (data: string) => string; + let headers = { + Accept: "application/json, text/plain, */*", + "Content-Type": "application/json", + }; + + switch (config.model) { + case "gpt-3.5-turbo-0301": + case "gpt-4-0314": + url = `https://api.openai.com/v1/chat/completions`; + payload = setupPayloadForGPT(config.model); + getNewData = (data) => handleGPTResponse(data); + headers["Authorization"] = `Bearer ${config.apikey}`; + break; + case "text-davinci-002": + url = "https://api.openai.com/v1/completions"; + payload = setupPayloadForDavinci(config.model); + getNewData = (data) => handleDavinciResponse(data); + headers["Authorization"] = `Bearer ${config.apikey}`; + break; + case "command-xlarge-nightly": + case "xlarge": + url = `https://api.cohere.ai/v1/generate`; + payload = setupPayloadForCohere(config.model); + getNewData = (data) => handleCohereResponse(data); + headers["Authorization"] = `Bearer ${config.apikey}`; + break; + case "claude-instant-v1.0": + // url = `https://api.anthropic.com/v1/complete`; + // url = `http://localhost:3001/api`; + url = `https://ws-edge-v2.feedox.workers.dev/completion/650fb33a815c45d4191a1e094628ec7d/` + payload = setupPayloadForAnthropic(config.model); + getNewData = (data) => handleAnthropicResponse(data); + headers["x-api-key"] = config.apikey; + headers["anthropic-version"] = "2023-06-01"; + break; + case "j2-grande-instruct": + url = `https://api.ai21.com/studio/v1/j2-${config.model_type || "mid" + }/complete`; + payload = setupPayloadForAI21(config.model); + getNewData = (data) => handleAI21Response(data); + headers["Authorization"] = `Bearer ${config.apikey}`; + break; + // Add more cases for other models in the future as needed + default: + throw new Error("Unsupported model type."); + } + + delete payload.apikey; + + console.log("Config:", config); + console.log("Messages:", messages); + + if (payload.stream) { + return this.createChatCompletionStream({ + url, + headers, + payload, + p, + getNewData, + onDelta, + onProgress, + }); + } else { + return this.createChatCompletionHTTP({ + url, + headers, + payload, + getNewData, + onDelta, + }); + } + } + + private parseChunk(buffer: any): OpenAIResponseChunk { + const chunk = buffer.toString().split(/\s*data\:\s*/); + // for (let content of chunk) { + // content = content.trim(); + // } + // const chunk = buffer.toString().replace('data: ', '').trim(); + + if (libx.isEmptyString(chunk) || chunk === "[DONE]") { + return { + done: true, + }; + } + + const parsed = JSON.parse(chunk); + + return { + id: parsed.id, + done: false, + choices: parsed.choices, + model: parsed.model, + }; + } } export class ModuleOptions { }