From d716a35bd5ed32fc8e4437cbb1eaca56b2f2d9df Mon Sep 17 00:00:00 2001 From: simon3000 Date: Tue, 6 Dec 2022 08:38:32 +0100 Subject: [PATCH 1/5] Conversation support --- src/chatgpt-api.ts | 81 +++++++++++++++++++++++++++++++++++++++- src/demo-conversation.ts | 47 +++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/demo-conversation.ts diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index 7598960..b78733e 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -10,6 +10,57 @@ const KEY_ACCESS_TOKEN = 'accessToken' const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' +class Conversation { + protected _api: ChatGPTAPI + protected _conversationId: string = undefined + protected _parentMessageId: string = undefined + + constructor(api: ChatGPTAPI) { + this._api = api + } + + /** + * Sends a message to ChatGPT, waits for the response to resolve, and returns + * the response. If the conversation has not yet been started, this will + * automatically start the conversation. + * @param message - The plaintext message to send. + * @param opts.onProgress - Optional listener which will be called every time the partial response is updated + * @param opts.onConversationResponse - Optional listener which will be called every time a conversation response is received + * @returns The plaintext response from ChatGPT. + */ + async sendMessage( + message: string, + opts: { + onProgress?: (partialResponse: string) => void + onConversationResponse?: ( + response: types.ConversationResponseEvent + ) => void + } = {} + ) { + const { onProgress, onConversationResponse } = opts + if (!this._conversationId) { + return this._api.sendMessage(message, { + onProgress, + onConversationResponse: (response) => { + this._conversationId = response.conversation_id + this._parentMessageId = response.message.id + onConversationResponse?.(response) + } + }) + } + + return this._api.sendMessage(message, { + conversationId: this._conversationId, + parentMessageId: this._parentMessageId, + onProgress, + onConversationResponse: (response) => { + this._parentMessageId = response.message.id + onConversationResponse?.(response) + } + }) + } +} + export class ChatGPTAPI { protected _sessionToken: string protected _markdown: boolean @@ -82,15 +133,25 @@ export class ChatGPTAPI { * @param message - The plaintext message to send. * @param opts.conversationId - Optional ID of the previous message in a conversation * @param opts.onProgress - Optional listener which will be called every time the partial response is updated + * @param opts.onConversationResponse - Optional listener which will be called every time the partial response is updated with the full conversation response */ async sendMessage( message: string, opts: { conversationId?: string + parentMessageId?: string onProgress?: (partialResponse: string) => void + onConversationResponse?: ( + response: types.ConversationResponseEvent + ) => void } = {} ): Promise { - const { conversationId = uuidv4(), onProgress } = opts + const { + conversationId, + parentMessageId = uuidv4(), + onProgress, + onConversationResponse + } = opts const accessToken = await this.refreshAccessToken() @@ -107,7 +168,11 @@ export class ChatGPTAPI { } ], model: 'text-davinci-002-render', - parent_message_id: conversationId + parent_message_id: parentMessageId + } + + if (conversationId) { + body.conversation_id = conversationId } const url = `${this._backendApiBaseUrl}/conversation` @@ -134,6 +199,9 @@ export class ChatGPTAPI { try { const parsedData: types.ConversationResponseEvent = JSON.parse(data) + if (onConversationResponse) { + onConversationResponse(parsedData) + } const message = parsedData.message // console.log('event', JSON.stringify(parsedData, null, 2)) @@ -197,4 +265,13 @@ export class ChatGPTAPI { throw new Error(`ChatGPT failed to refresh auth token. ${err.toString()}`) } } + + /** + * Get a new Conversation instance, which can be used to send multiple messages as part of a single conversation. + * + * @returns a new Conversation instance + */ + getConversation() { + return new Conversation(this) + } } diff --git a/src/demo-conversation.ts b/src/demo-conversation.ts new file mode 100644 index 0000000..cd02b0b --- /dev/null +++ b/src/demo-conversation.ts @@ -0,0 +1,47 @@ +import dotenv from 'dotenv-safe' +import { oraPromise } from 'ora' + +import { ChatGPTAPI } from '.' + +dotenv.config() + +/** + * Example CLI for testing functionality. + * + * ``` + * npx tsx src/demo.ts + * ``` + */ +async function main() { + const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN }) + await api.ensureAuth() + + const conversation = api.getConversation() + + const prompt = 'What is OpenAI?' + + const response = await oraPromise(conversation.sendMessage(prompt), { + text: prompt + }) + + console.log(response) + + const prompt2 = 'Continue' + + console.log( + await oraPromise(conversation.sendMessage(prompt2), { + text: prompt2 + }) + ) + + console.log( + await oraPromise(conversation.sendMessage(prompt2), { + text: prompt2 + }) + ) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) From 9e891f529a7fc99ab1b2bdfae75f7bdf19b6febd Mon Sep 17 00:00:00 2001 From: simon3000 Date: Tue, 6 Dec 2022 08:52:24 +0100 Subject: [PATCH 2/5] added some comment --- src/chatgpt-api.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index b78733e..0638694 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -10,19 +10,35 @@ const KEY_ACCESS_TOKEN = 'accessToken' const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' +/** + * A conversation wrapper around the ChatGPT API. This allows you to send + * multiple messages to ChatGPT and receive responses, without having to + * manually pass the conversation ID and parent message ID for each message. + */ class Conversation { protected _api: ChatGPTAPI protected _conversationId: string = undefined protected _parentMessageId: string = undefined + /** + * Creates a new conversation wrapper around the ChatGPT API. + * @param api - The ChatGPT API instance to use. + */ constructor(api: ChatGPTAPI) { this._api = api } /** * Sends a message to ChatGPT, waits for the response to resolve, and returns - * the response. If the conversation has not yet been started, this will - * automatically start the conversation. + * the response. + * If this is the first message in the conversation, the conversation ID and + * parent message ID will be automatically set. + * This allows you to send multiple messages to ChatGPT and receive responses, + * without having to manually pass the conversation ID and parent message ID + * for each message. + * If you want to manually pass the conversation ID and parent message ID, + * use `api.sendMessage` instead. + * * @param message - The plaintext message to send. * @param opts.onProgress - Optional listener which will be called every time the partial response is updated * @param opts.onConversationResponse - Optional listener which will be called every time a conversation response is received From df94c61313f73fbc04341667a6b9e9fb64fdc1a4 Mon Sep 17 00:00:00 2001 From: simon3000 Date: Tue, 6 Dec 2022 14:46:55 +0100 Subject: [PATCH 3/5] optional params for Conversation --- src/chatgpt-api.ts | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index 0638694..5d98b5c 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -16,16 +16,21 @@ const USER_AGENT = * manually pass the conversation ID and parent message ID for each message. */ class Conversation { - protected _api: ChatGPTAPI - protected _conversationId: string = undefined - protected _parentMessageId: string = undefined + api: ChatGPTAPI + conversationId: string = undefined + parentMessageId: string = undefined /** * Creates a new conversation wrapper around the ChatGPT API. * @param api - The ChatGPT API instance to use. */ - constructor(api: ChatGPTAPI) { - this._api = api + constructor( + api: ChatGPTAPI, + opts: { conversationId?: string; parentMessageId?: string } = {} + ) { + this.api = api + this.conversationId = opts.conversationId + this.parentMessageId = opts.parentMessageId } /** @@ -54,23 +59,23 @@ class Conversation { } = {} ) { const { onProgress, onConversationResponse } = opts - if (!this._conversationId) { - return this._api.sendMessage(message, { + if (!this.conversationId) { + return this.api.sendMessage(message, { onProgress, onConversationResponse: (response) => { - this._conversationId = response.conversation_id - this._parentMessageId = response.message.id + this.conversationId = response.conversation_id + this.parentMessageId = response.message.id onConversationResponse?.(response) } }) } - return this._api.sendMessage(message, { - conversationId: this._conversationId, - parentMessageId: this._parentMessageId, + return this.api.sendMessage(message, { + conversationId: this.conversationId, + parentMessageId: this.parentMessageId, onProgress, onConversationResponse: (response) => { - this._parentMessageId = response.message.id + this.parentMessageId = response.message.id onConversationResponse?.(response) } }) @@ -285,9 +290,13 @@ export class ChatGPTAPI { /** * Get a new Conversation instance, which can be used to send multiple messages as part of a single conversation. * + * @param opts.conversationId - Optional Data of the previous message in a conversation + * @param opts.parentMessageId - Optional Data of the previous message in a conversation * @returns a new Conversation instance */ - getConversation() { - return new Conversation(this) + getConversation( + opts: { conversationId?: string; parentMessageId?: string } = {} + ) { + return new Conversation(this, opts) } } From 8012354411152893e607f96d3783f1f7bd3e6652 Mon Sep 17 00:00:00 2001 From: simon3000 Date: Tue, 6 Dec 2022 16:35:57 +0100 Subject: [PATCH 4/5] Update demo-conversation.ts --- src/demo-conversation.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/demo-conversation.ts b/src/demo-conversation.ts index cd02b0b..c0199b8 100644 --- a/src/demo-conversation.ts +++ b/src/demo-conversation.ts @@ -26,7 +26,7 @@ async function main() { console.log(response) - const prompt2 = 'Continue' + const prompt2 = 'Did they made OpenGPT?' console.log( await oraPromise(conversation.sendMessage(prompt2), { @@ -34,9 +34,19 @@ async function main() { }) ) + const prompt3 = 'Who founded this institute?' + console.log( - await oraPromise(conversation.sendMessage(prompt2), { - text: prompt2 + await oraPromise(conversation.sendMessage(prompt3), { + text: prompt3 + }) + ) + + const prompt4 = 'Who is that?' + + console.log( + await oraPromise(conversation.sendMessage(prompt4), { + text: prompt4 }) ) } From 8fb30b155b2dba7203c5a87f6f02219cc8dd9bc2 Mon Sep 17 00:00:00 2001 From: simon3000 Date: Tue, 6 Dec 2022 16:38:10 +0100 Subject: [PATCH 5/5] conversation always save conversationId --- src/chatgpt-api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index 5d98b5c..32a39a1 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -75,6 +75,7 @@ class Conversation { parentMessageId: this.parentMessageId, onProgress, onConversationResponse: (response) => { + this.conversationId = response.conversation_id this.parentMessageId = response.message.id onConversationResponse?.(response) }