增加生成进度功能
parent
41cb391fcd
commit
91417f0408
@ -1,3 +1,3 @@
|
|||||||
defaults.url=https://sentry.io/
|
defaults.url=https://sentry.io/
|
||||||
defaults.org=anlatan
|
defaults.org=naifu
|
||||||
defaults.project=novelai
|
defaults.project=naifu
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
export function usePrevious<T>(value: T): T | undefined {
|
||||||
|
const ref = useRef<typeof value>()
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = value
|
||||||
|
})
|
||||||
|
return ref.current
|
||||||
|
}
|
@ -0,0 +1,224 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Maxime Petazzoni <maxime.petazzoni@bulix.org>.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type SSEOptions = Partial<{
|
||||||
|
headers: Record<string, any>;
|
||||||
|
payload: any;
|
||||||
|
method: string;
|
||||||
|
withCredentials: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type SSECallback = (e: any) => any;
|
||||||
|
|
||||||
|
export class SSE {
|
||||||
|
public INITIALIZING = -1;
|
||||||
|
public CONNECTING = 0;
|
||||||
|
public OPEN = 1;
|
||||||
|
public CLOSED = 2;
|
||||||
|
|
||||||
|
public url: string;
|
||||||
|
public headers: Record<string, any>;
|
||||||
|
public payload: any;
|
||||||
|
public method: string;
|
||||||
|
public withCredentials: boolean;
|
||||||
|
|
||||||
|
public FIELD_SEPARATOR = ':';
|
||||||
|
public listeners: Record<string, SSECallback[]> = {};
|
||||||
|
|
||||||
|
public xhr?: XMLHttpRequest;
|
||||||
|
public readyState: number = this.INITIALIZING;
|
||||||
|
public progress: number = 0;
|
||||||
|
public chunk: string = '';
|
||||||
|
|
||||||
|
constructor(url: string, options: SSEOptions = {}) {
|
||||||
|
this.url = url;
|
||||||
|
|
||||||
|
this.headers = options.headers || {};
|
||||||
|
this.payload = options.payload !== undefined ? options.payload : '';
|
||||||
|
this.method = options.method || (this.payload && 'POST' || 'GET');
|
||||||
|
this.withCredentials = !!options.withCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEventListener(type: string, listener: SSECallback) {
|
||||||
|
if (this.listeners[type] === undefined) {
|
||||||
|
this.listeners[type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.listeners[type].indexOf(listener) === -1) {
|
||||||
|
this.listeners[type].push(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeEventListener(type: string, listener: SSECallback) {
|
||||||
|
if (this.listeners[type] === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered: SSECallback[] = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispatchEvent(e: any | null) {
|
||||||
|
if (!e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.source = this;
|
||||||
|
|
||||||
|
var onHandler = 'on' + e.type;
|
||||||
|
if (this.hasOwnProperty(onHandler)) {
|
||||||
|
(this as any)[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setReadyState(state: number) {
|
||||||
|
var event: any = new CustomEvent('readystatechange');
|
||||||
|
event.readyState = state;
|
||||||
|
this.readyState = state;
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _onStreamFailure(e: ProgressEvent<XMLHttpRequestEventTarget>) {
|
||||||
|
var event: any = new CustomEvent('error');
|
||||||
|
event.data = (e.currentTarget as any)?.response;
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStreamAbort(e: ProgressEvent<XMLHttpRequestEventTarget>) {
|
||||||
|
this.dispatchEvent(new CustomEvent('abort'));
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStreamProgress(e: ProgressEvent<XMLHttpRequestEventTarget>) {
|
||||||
|
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((part) => {
|
||||||
|
if (part.trim().length === 0) {
|
||||||
|
this.dispatchEvent(this._parseEventChunk(this.chunk.trim()));
|
||||||
|
this.chunk = '';
|
||||||
|
} else {
|
||||||
|
this.chunk += part;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStreamLoaded(e: ProgressEvent<XMLHttpRequestEventTarget>) {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private _parseEventChunk(chunk: string | null) {
|
||||||
|
if (!chunk || chunk.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = { 'id': null, 'retry': null, 'data': '', 'event': 'message' };
|
||||||
|
chunk.split(/\n|\r\n|\r/).forEach((line) => {
|
||||||
|
line = line.trimEnd();
|
||||||
|
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 as any)[field] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var event: any = new CustomEvent(e.event);
|
||||||
|
event.data = e.data;
|
||||||
|
event.id = e.id;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _checkStreamClosed () {
|
||||||
|
if (!this.xhr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
this._setReadyState(this.CLOSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stream() {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
if (this.readyState === this.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xhr?.abort();
|
||||||
|
this.xhr = undefined;
|
||||||
|
this._setReadyState(this.CLOSED);
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue