diff --git a/app/sse.js b/app/sse.js new file mode 100644 index 0000000000..72f49dfc4b --- /dev/null +++ b/app/sse.js @@ -0,0 +1,218 @@ +/** + * Copyright (C) 2016 Maxime Petazzoni . + * All rights reserved. + */ + +var SSE = function (url, options) { + if (!(this instanceof SSE)) { + return new SSE(url, options); + } + + this.INITIALIZING = -1; + this.CONNECTING = 0; + this.OPEN = 1; + this.CLOSED = 2; + + this.url = url; + + options = options || {}; + this.headers = options.headers || {}; + this.payload = options.payload !== undefined ? options.payload : ''; + this.method = options.method || (this.payload && 'POST' || 'GET'); + this.withCredentials = !!options.withCredentials; + + this.FIELD_SEPARATOR = ':'; + this.listeners = {}; + + this.xhr = null; + this.readyState = this.INITIALIZING; + this.progress = 0; + this.chunk = ''; + + this.addEventListener = function(type, listener) { + if (this.listeners[type] === undefined) { + this.listeners[type] = []; + } + + if (this.listeners[type].indexOf(listener) === -1) { + this.listeners[type].push(listener); + } + }; + + this.removeEventListener = function(type, listener) { + if (this.listeners[type] === undefined) { + return; + } + + var filtered = []; + this.listeners[type].forEach(function(element) { + if (element !== listener) { + filtered.push(element); + } + }); + if (filtered.length === 0) { + delete this.listeners[type]; + } else { + this.listeners[type] = filtered; + } + }; + + this.dispatchEvent = function(e) { + if (!e) { + return true; + } + + e.source = this; + + var onHandler = 'on' + e.type; + if (this.hasOwnProperty(onHandler)) { + this[onHandler].call(this, e); + if (e.defaultPrevented) { + return false; + } + } + + if (this.listeners[e.type]) { + return this.listeners[e.type].every(function(callback) { + callback(e); + return !e.defaultPrevented; + }); + } + + return true; + }; + + this._setReadyState = function(state) { + var event = new CustomEvent('readystatechange'); + event.readyState = state; + this.readyState = state; + this.dispatchEvent(event); + }; + + this._onStreamFailure = function(e) { + var event = new CustomEvent('error'); + event.data = e.currentTarget.response; + this.dispatchEvent(event); + this.close(); + } + + this._onStreamAbort = function(e) { + this.dispatchEvent(new CustomEvent('abort')); + this.close(); + } + + this._onStreamProgress = function(e) { + if (!this.xhr) { + return; + } + + if (this.xhr.status !== 200) { + this._onStreamFailure(e); + return; + } + + if (this.readyState == this.CONNECTING) { + this.dispatchEvent(new CustomEvent('open')); + this._setReadyState(this.OPEN); + } + + var data = this.xhr.responseText.substring(this.progress); + this.progress += data.length; + data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) { + if (part.trim().length === 0) { + this.dispatchEvent(this._parseEventChunk(this.chunk.trim())); + this.chunk = ''; + } else { + this.chunk += part; + } + }.bind(this)); + }; + + this._onStreamLoaded = function(e) { + this._onStreamProgress(e); + + // Parse the last chunk. + this.dispatchEvent(this._parseEventChunk(this.chunk)); + this.chunk = ''; + }; + + /** + * Parse a received SSE event chunk into a constructed event object. + */ + this._parseEventChunk = function(chunk) { + if (!chunk || chunk.length === 0) { + return null; + } + + var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'}; + chunk.split(/\n|\r\n|\r/).forEach(function(line) { + line = line.trimRight(); + var index = line.indexOf(this.FIELD_SEPARATOR); + if (index <= 0) { + // Line was either empty, or started with a separator and is a comment. + // Either way, ignore. + return; + } + + var field = line.substring(0, index); + if (!(field in e)) { + return; + } + + var value = line.substring(index + 1).trimLeft(); + if (field === 'data') { + e[field] += value; + } else { + e[field] = value; + } + }.bind(this)); + + var event = new CustomEvent(e.event); + event.data = e.data; + event.id = e.id; + return event; + }; + + this._checkStreamClosed = function() { + if (!this.xhr) { + return; + } + + if (this.xhr.readyState === XMLHttpRequest.DONE) { + this._setReadyState(this.CLOSED); + } + }; + + this.stream = function() { + this._setReadyState(this.CONNECTING); + + this.xhr = new XMLHttpRequest(); + this.xhr.addEventListener('progress', this._onStreamProgress.bind(this)); + this.xhr.addEventListener('load', this._onStreamLoaded.bind(this)); + this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this)); + this.xhr.addEventListener('error', this._onStreamFailure.bind(this)); + this.xhr.addEventListener('abort', this._onStreamAbort.bind(this)); + this.xhr.open(this.method, this.url); + for (var header in this.headers) { + this.xhr.setRequestHeader(header, this.headers[header]); + } + this.xhr.withCredentials = this.withCredentials; + this.xhr.send(this.payload); + }; + + this.close = function() { + if (this.readyState === this.CLOSED) { + return; + } + + this.xhr.abort(); + this.xhr = null; + this._setReadyState(this.CLOSED); + }; +}; + +// Export our SSE module for npm.js +if (typeof exports !== 'undefined') { + // exports.SSE = SSE; + module.exports = { SSE }; +} diff --git a/server/index.js b/server/index.js index 3a5f56535d..6feaec1ab3 100644 --- a/server/index.js +++ b/server/index.js @@ -1,8 +1,10 @@ const path = require('path'); const express = require('express'); -const { ask } = require('./app/chatgpt'); +const { ask } = require('../app/chatgpt'); const app = express(); const port = 3050; +const cors = require('cors'); +app.use(cors()); app.use(express.json()); const projectPath = path.join(__dirname, '..'); @@ -14,21 +16,21 @@ app.get('/', function (req, res) { }); app.post('/ask', (req, res) => { - console.log(req.body); + console.log(req.body, 'we in here'); - // res.writeHead(200, { - // Connection: 'keep-alive', - // 'Content-Type': 'text/event-stream', - // 'Cache-Control': 'no-cache, no-transform', - // 'Access-Control-Allow-Origin':'*', - // 'X-Accel-Buffering':'no' - // }); - // res.write('data: This is chunk 1\n'); - // res.write('data: This is chunk 2\n'); - // setTimeout(() => { - // res.write('data: This is chunk 3\n'); - // res.end(); - // }, 3500); + res.writeHead(200, { + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'Access-Control-Allow-Origin':'*', + 'X-Accel-Buffering':'no' + }); + res.write('event: message\ndata: This is chunk 1\n\n'); + res.write('event: message\ndata: This is chunk 2\n\n'); + setTimeout(() => { + res.write('event: message\ndata: This is chunk 3\n\n'); + res.end(); + }, 3500); }); app.listen(port, () => {