UNPKG

node-red-contrib-custom-chatgpt

Version:

A Node-RED node that interacts with OpenAI machine learning models to generate text and image outputs like 'ChatGPT', 'DALL·E 2', and 'DALL·E 3'.

407 lines (385 loc) 17.1 kB
module.exports = (RED) => { // Import required modules const { OpenAI } = require("openai"); // Define a list of acceptable topics for the node const ACCEPT_TOPIC_LIST = [ "gpt-4o-mini", "gpt4o", "image", "turbo", "gpt4", "dalle3", ].map((item) => item.toLowerCase()); // Main function that gets executed when a message is received const main = function (config) { const node = this; RED.nodes.createNode(node, config); // Handle incoming messages node.on("input", async(msg) => { // Extract API key from credentials or message const API_KEY = this.credentials.API_KEY || msg.API_KEY; // Initialize OpenAI configuration with the provided API key const configuration = { apiKey: API_KEY, }; // Handle BaseUrl configuration if provided if (config.BaseUrl) { try { // Update the base path if a valid URL is provided const url = new URL(config.BaseUrl); if (url.pathname === "/") { url.pathname = "/v1"; } configuration.basePath = url.toString(); } catch { // Display an error status if BaseUrl is not a valid URL node.status({ fill: "red", shape: "dot", text: `BaseUrl(${config.BaseUrl}) isn't a valid url`, }); return; } } // Initialize the OpenAI API client const openai = new OpenAI({apiKey: API_KEY}); // Set node status to indicate processing node.status({ fill: "green", shape: "dot", text: "Processing...", }); // Check and normalize the provided topic in the message if (config.topic !== "__EMPTY__") { msg.topic = config.topic; } if (msg.topic) { msg.topic = msg.topic.toLowerCase(); } // Validate if the provided topic is in the accepted topic list if (!ACCEPT_TOPIC_LIST.includes(msg.topic) && msg.topic !== "__empty__") { // Set node status and log error if the topic is incorrect node.status({ fill: "red", shape: "dot", text: "msg.topic is incorrect", }); node.error(`msg.topic must be a string set to one of the following values: ${ACCEPT_TOPIC_LIST.map((item) => ` '${item}' `).join(", ")}`); // Send the message node.send(msg); } else if (msg.topic === "image") { // Process messages with the "image" topic try { // Make a request to OpenAI API for image creation const response = await openai.images.generate({ prompt: msg.payload, n: parseInt(msg.n) || 1, size: msg.size || "256x256", response_format: msg.format || "url", }); // Process the response based on the specified format if (msg.format === "b64_json") { msg.payload = response.data[0].b64_json; } else { msg.payload = response.data[0].url; } // Set additional properties in the message msg.full = response; // Set node status node.status({ fill: "blue", shape: "dot", text: "Response complete", }); // Send the message node.send(msg); // Handle errors } catch (error) { // Set node status node.status({ fill: "red", shape: "dot", text: "Error", }); if (error.response) { node.error(error.response, msg); } else { node.error(error.message, msg); } } } else if (msg.topic === "dalle3") { // Process messages with the "dalle3" topic try { // Make a request to OpenAI API for image creation const response = await openai.images.generate({ model: "dall-e-3", prompt: msg.payload, n: parseInt(msg.n) || 1, size: msg.size || "1024x1024", }); msg.payload = response.data[0].url; // Set additional properties in the message msg.full = response; // Set node status node.status({ fill: "blue", shape: "dot", text: "Response complete", }); // Send the message node.send(msg); // Handle errors } catch (error) { // Set node status node.status({ fill: "red", shape: "dot", text: "Error", }); if (error.response) { node.error(error.response, msg); } else { node.error(error.message, msg); } } } else if (msg.topic === "turbo") { // Process messages with the "turbo" topic try { if (typeof msg.history === "undefined") msg.history = []; msg.topic = "turbo"; const input = { role: "user", content: msg.payload, }; msg.history.push(input); // Request completion from gpt-3.5-turbo model const response = await openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: msg.history, temperature: parseInt(msg.temperature) || 1, top_p: parseInt(msg.top_p) || 1, n: parseInt(msg.n) || 1, stream: msg.stream || false, stop: msg.stop || null, max_tokens: parseInt(msg.max_tokens) || 4000, presence_penalty: parseInt(msg.presence_penalty) || 0, frequency_penalty: parseInt(msg.frequency_penalty) || 0, }); const trimmedContent = response.choices[0].message.content.trim(); const result = { role: "assistant", content: trimmedContent, }; msg.history.push(result); // Extract completed text from the response msg.payload = response.choices[0].message.content; msg.full = response; // Set node status node.status({ fill: "blue", shape: "dot", text: "Response complete", }); // Send the message node.send(msg); // Handle errors } catch (error) { // Set node status node.status({ fill: "red", shape: "dot", text: "Error", }); if (error.response) { node.error(error.response, msg); } else { node.error(error.message, msg); } } } else if (msg.topic === "gpt4") { // Process messages with the "gpt4" topic try { // Handle GPT-4 conversation logic if (typeof msg.history === "undefined") msg.history = []; msg.topic = "gpt4"; const input = { role: "user", content: msg.payload, }; msg.history.push(input); // Request completion from GPT-4 model const response = await openai.chat.completions.create({ model: "gpt-4", messages: msg.history, temperature: parseInt(msg.temperature) || 1, top_p: parseInt(msg.top_p) || 1, n: parseInt(msg.n) || 1, stream: msg.stream || false, stop: msg.stop || null, max_tokens: parseInt(msg.max_tokens) || 4000, presence_penalty: parseInt(msg.presence_penalty) || 0, frequency_penalty: parseInt(msg.frequency_penalty) || 0, }); const trimmedContent = response.choices[0].message.content.trim(); const result = { role: "assistant", content: trimmedContent, }; msg.history.push(result); // Extract completed text from the response msg.payload = response.choices[0].message.content; msg.full = response; // Set node status node.status({ fill: "blue", shape: "dot", text: "Response complete", }); // Send the message node.send(msg); // Handle errors } catch (error) { // Set node status node.status({ fill: "red", shape: "dot", text: "Error", }); if (error.response) { node.error(error.response, msg); } else { node.error(error.message, msg); } } } else if (msg.topic === "gpt4o") { // Process messages with the "gpt4o" topic try { // Handle GPT-4 conversation logic if (typeof msg.history === "undefined") msg.history = []; msg.topic = msg.model ? msg.model : "gpt4o"; const input = { role: "user", content: msg.payload, }; msg.history.push(input); // Request completion from GPT-4 model const response = await openai.chat.completions.create({ model: msg.model ? msg.model : "gpt-4o", messages: msg.history, temperature: parseInt(msg.temperature) || 1, top_p: parseInt(msg.top_p) || 1, n: parseInt(msg.n) || 1, stream: msg.stream || false, stop: msg.stop || null, max_tokens: parseInt(msg.max_tokens) || 4000, presence_penalty: parseInt(msg.presence_penalty) || 0, frequency_penalty: parseInt(msg.frequency_penalty) || 0, }); const trimmedContent = response.choices[0].message.content.trim(); const result = { role: "assistant", content: trimmedContent, }; msg.history.push(result); // Extract completed text from the response msg.payload = response.choices[0].message.content; msg.full = response; // Set node status node.status({ fill: "blue", shape: "dot", text: "Response complete", }); // Send the message node.send(msg); // Handle errors } catch (error) { // Set node status node.status({ fill: "red", shape: "dot", text: "Error", }); if (error.response) { node.error(error.response, msg); } else { node.error(error.message, msg); } } } else { // Process messages with the "gpt-4o-mini" topic try { // Handle GPT-4 conversation logic if (typeof msg.history === "undefined") msg.history = []; msg.topic = msg.model ? msg.model : "gpt-4o-mini"; const input = { role: "user", content: msg.payload, }; msg.history.push(input); // Request completion from GPT-4 model const response = await openai.chat.completions.create({ model: msg.model ? msg.model : "gpt-4o-mini", messages: msg.history, temperature: parseInt(msg.temperature) || 1, top_p: parseInt(msg.top_p) || 1, n: parseInt(msg.n) || 1, stream: msg.stream || false, stop: msg.stop || null, max_tokens: parseInt(msg.max_tokens) || 4000, presence_penalty: parseInt(msg.presence_penalty) || 0, frequency_penalty: parseInt(msg.frequency_penalty) || 0, }); const trimmedContent = response.choices[0].message.content.trim(); const result = { role: "assistant", content: trimmedContent, }; msg.history.push(result); // Extract completed text from the response msg.payload = response.choices[0].message.content; msg.full = response; // Set node status node.status({ fill: "blue", shape: "dot", text: "Response complete", }); // Send the message node.send(msg); // Handle errors } catch (error) { // Set node status node.status({ fill: "red", shape: "dot", text: "Error", }); if (error.response) { node.error(error.response, msg); } else { node.error(error.message, msg); } } } }); // Clear the node status on node close node.on("close", () => { node.status({}); }); }; // Register the node type with Node-RED RED.nodes.registerType("chatgpt", main, { credentials: { API_KEY: { type: "text" }, }, }); };