[AIWriteSupporter] Node Express Server Example

Sample Node/Express server endpoints that proxy AI requests for the plugin.

Text AI

GPT — using @waylaidwanderer/fetch-event-source

modules/FetchEventSource.js:

const { fetch, Headers, Request, Response } = require('fetch-undici');

if (!globalThis.fetch) {
  globalThis.fetch = fetch;
  globalThis.Headers = Headers;
  globalThis.Request = Request;
  globalThis.Response = Response;
}

module.exports = require('@waylaidwanderer/fetch-event-source');

server.js:

const express = require('express');
const bodyParser = require('body-parser');
const { fetchEventSource } = require('./modules/FetchEventSource');

const GPT_API_URL = '';   // e.g. 'https://api.openai.com/v1/chat/completions'
const GPT_API_KEY = '';

const app = express();
const router = express.Router();

router.post('/request', (req, res) => {
  const bodyData = Object.assign({}, req.body, {
    model: 'gpt-3.5-turbo',
    stream: true
  });

  requestGPT(res, bodyData)
    .then(() => res.end())
    .catch(error => res.status(error.status).json(error).end());
});

function requestGPT(res, bodyData) {
  const abortController = new AbortController();
  return new Promise(async (resolve, reject) => {
    try {
      await fetchEventSource(GPT_API_URL, {
        method: 'post',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${GPT_API_KEY}`         // OpenAI
          // 'Api-Key': GPT_API_KEY                         // Azure OpenAI
        },
        body: JSON.stringify(bodyData),
        signal: abortController.signal,
        onopen: async (openResponse) => {
          if (openResponse.status === 200) {
            res.on('close', () => { abortController.abort(); resolve(); });
            res.set({
              'Cache-Control': 'no-cache',
              'Connection': 'keep-alive',
              'Content-Type': 'text/event-stream'
            });
            res.flushHeaders();
            return;
          }
          let error;
          try { error = await openResponse.json(); }
          catch (e) { error = e; }
          error.status = openResponse.status;
          throw error;
        },
        onclose: () => resolve(),
        onerror: (error) => { reject(error); throw error; },
        onmessage: (message) => {
          if (!message.data || message.event === 'ping') return;
          res.write('data:' + message.data + '\n\n');
        }
      });
    } catch (e) { reject(e); }
  });
}

app.use(bodyParser.json());
app.use('/', router);
app.listen(8080);

Gemini — using axios

const express = require('express');
const axios = require('axios');
const bodyParser = require('body-parser');

const GEMINI_URL   = '';   // e.g. 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:streamGenerateContent'
const GEMINI_PARAM = '';   // e.g. 'alt=sse&'
const GEMINI_KEY   = '';

const app = express();
const router = express.Router();

router.post('/request', (req, res) => {
  requestGeminiServer(res, req.body)
    .then(() => res.end())
    .catch(err => res.status(err.status || 500).json(err).end());
});

function requestGeminiServer(res, bodyData) {
  const urlWithParam = `${GEMINI_URL}?${GEMINI_PARAM}key=${GEMINI_KEY}`;
  const source = axios.CancelToken.source();
  return new Promise((resolve, reject) => {
    try {
      axios.post(urlWithParam, bodyData, {
        headers: { 'Content-Type': 'application/json' },
        responseType: 'stream',
        cancelToken: source.token
      }).then(geminiRes => geminiRes.data.pipe(res)).catch(reject);

      res.on('close', () => { source.cancel('Cancel request.'); resolve(); });
    } catch (e) { reject(e); }
  });
}

app.use(bodyParser.json());
app.use('/', router);
app.listen(8080);

Image AI

gpt-image (axios)

Release 3.4.0 and above.

A Node Express server example using axios.

const express = require('express');
const axios = require('axios');
const bodyParser = require('body-parser');

const GPT_IMAGE_URL = '';   // e.g. 'https://api.openai.com/v1/images/generations'
const GPT_IMAGE_API_KEY = '';

const app = express();
const router = express.Router();

router.post('/request', (request, response) => {
    const bodyData = request.body;

    requestGPTImageServer(response, bodyData)
        .then((data) => response.json(data))
        .catch((error) => response.status(error.status || 500).json(error).end());
});

/**
 * Forwards the request to the OpenAI image generation API (/v1/images/generations).
 * @param {Response} response
 * @param {Object} bodyData
 * @returns {Promise}
 */
function requestGPTImageServer(response, bodyData) {
    const source = axios.CancelToken.source();
    const url = GPT_IMAGE_URL;

    return new Promise((resolve, reject) => {
        try {
            axios.post(url, bodyData, {
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${GPT_IMAGE_API_KEY}`
                },
                cancelToken: source.token
            }).then(res => {
                resolve(res.data);
            }).catch(error => {
                reject({
                    status: error.response ? error.response.status : 500,
                    message: error.message
                });
            });

            response.on('close', () => {
                source.cancel('Cancel request.');
                resolve();
            });
        } catch (e) {
            reject(e);
        }
    });
}

app.use(bodyParser.json());
app.use('/', router);
app.listen(8080);