mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-30 15:18:50 +01:00
* feat: add timer duration to showToast, show toast for preset selection * refactor: replace old /chat/ route with /c/. e2e tests will fail here * refactor: move typedefs to root of /api/ and add a few to assistant types in TS * refactor: reorganize data-provider imports, fix dependency cycle, strategize new plan to separate react dependent packages * feat: add dataService for uploading images * feat(data-provider): add mutation keys * feat: file resizing and upload * WIP: initial API image handling * fix: catch JSON.parse of localStorage tools * chore: experimental: use module-alias for absolute imports * refactor: change temp_file_id strategy * fix: updating files state by using Map and defining react query callbacks in a way that keeps them during component unmount, initial delete handling * feat: properly handle file deletion * refactor: unexpose complete filepath and resize from server for higher fidelity * fix: make sure resized height, width is saved, catch bad requests * refactor: use absolute imports * fix: prevent setOptions from being called more than once for OpenAIClient, made note to fix for PluginsClient * refactor: import supportsFiles and models vars from schemas * fix: correctly replace temp file id * refactor(BaseClient): use absolute imports, pass message 'opts' to buildMessages method, count tokens for nested objects/arrays * feat: add validateVisionModel to determine if model has vision capabilities * chore(checkBalance): update jsdoc * feat: formatVisionMessage: change message content format dependent on role and image_urls passed * refactor: add usage to File schema, make create and updateFile, correctly set and remove TTL * feat: working vision support TODO: file size, type, amount validations, making sure they are styled right, and making sure you can add images from the clipboard/dragging * feat: clipboard support for uploading images * feat: handle files on drop to screen, refactor top level view code to Presentation component so the useDragHelpers hook has ChatContext * fix(Images): replace uploaded images in place * feat: add filepath validation to protect sensitive files * fix: ensure correct file_ids are push and not the Map key values * fix(ToastContext): type issue * feat: add basic file validation * fix(useDragHelpers): correct context issue with `files` dependency * refactor: consolidate setErrors logic to setError * feat: add dialog Image overlay on image click * fix: close endpoints menu on click * chore: set detail to auto, make note for configuration * fix: react warning (button desc. of button) * refactor: optimize filepath handling, pass file_ids to images for easier re-use * refactor: optimize image file handling, allow re-using files in regen, pass more file metadata in messages * feat: lazy loading images including use of upload preview * fix: SetKeyDialog closing, stopPropagation on Dialog content click * style(EndpointMenuItem): tighten up the style, fix dark theme showing in lightmode, make menu more ux friendly * style: change maxheight of all settings textareas to 138px from 300px * style: better styling for textarea and enclosing buttons * refactor(PresetItems): swap back edit and delete icons * feat: make textarea placeholder dynamic to endpoint * style: show user hover buttons only on hover when message is streaming * fix: ordered list not going past 9, fix css * feat: add User/AI labels; style: hide loading spinner * feat: add back custom footer, change original footer text * feat: dynamic landing icons based on endpoint * chore: comment out assistants route * fix: autoScroll to newest on /c/ view * fix: Export Conversation on new UI * style: match message style of official more closely * ci: fix api jest unit tests, comment out e2e tests for now as they will fail until addressed * feat: more file validation and use blob in preview field, not filepath, to fix temp deletion * feat: filefilter for multer * feat: better AI labels based on custom name, model, and endpoint instead of `ChatGPT`
242 lines
6 KiB
JavaScript
242 lines
6 KiB
JavaScript
/* eslint-disable */
|
|
/**
|
|
* Copyright (C) 2016 Maxime Petazzoni <maxime.petazzoni@bulix.org>.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
import request from './request';
|
|
import { setTokenHeader } from './headers-helpers';
|
|
|
|
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 = async function (e) {
|
|
if (!this.xhr) {
|
|
return;
|
|
}
|
|
|
|
if (this.xhr.status === 401 && !this._retry) {
|
|
this._retry = true;
|
|
try {
|
|
const refreshResponse = await request.refreshToken();
|
|
this.headers = {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${refreshResponse.token}`,
|
|
};
|
|
setTokenHeader(refreshResponse.token);
|
|
window.dispatchEvent(new CustomEvent('tokenUpdated', { detail: refreshResponse.token }));
|
|
this.stream();
|
|
} catch (err) {
|
|
this._onStreamFailure(e);
|
|
return;
|
|
}
|
|
} else 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 { SSE };
|
|
// Export our SSE module for npm.js
|
|
// if (typeof exports !== 'undefined') {
|
|
// // exports.SSE = SSE;
|
|
// module.exports = { SSE };
|
|
// }
|