Refactor event parsing

This commit is contained in:
Cohee 2024-04-02 15:25:23 +03:00
parent 51b3b8bfaa
commit 8176e09d4a

View File

@ -77,29 +77,152 @@ class EventSourceStream {
}
}
const defaultDelayMs = 20;
const punctuationDelayMs = 500;
/**
* Gets a delay based on the character.
* @param {string} s The character.
* @returns {number} The delay in milliseconds.
*/
function getDelay(s) {
if (!s) {
return 0;
}
if (s == ',') {
return punctuationDelayMs / 2;
}
if (['.', '!', '?', '\n'].includes(s)) {
return punctuationDelayMs;
}
return defaultDelayMs;
}
/**
* Parses the stream data and returns the parsed data and the chunk to be sent.
* @param {object} json The JSON data.
* @returns {AsyncGenerator<{data: object, chunk: string} | null>} The parsed data and the chunk to be sent.
*/
async function* parseStreamData(json) {
// Claude
if (typeof json.delta === 'object') {
if (typeof json.delta.text === 'string' && json.delta.text.length > 0) {
for (let i = 0; i < json.delta.text.length; i++) {
const str = json.delta.text[i];
yield {
data: { ...json, delta: { text: str } },
chunk: str,
};
}
}
}
// MakerSuite
else if (Array.isArray(json.candidates)) {
for (let i = 0; i < json.candidates.length; i++) {
if (typeof json.candidates[i].content === 'string' && json.candidates[i].content.length > 0) {
for (let j = 0; j < json.candidates[i].content.length; j++) {
const str = json.candidates[i].content[j];
const candidatesClone = structuredClone(json.candidates[i]);
candidatesClone[i].content = str;
yield {
data: { ...json, candidates: candidatesClone },
chunk: str,
};
}
}
}
}
// NovelAI / KoboldCpp Classic
else if (typeof json.token === 'string' && json.token.length > 0) {
for (let i = 0; i < json.token.length; i++) {
const str = json.token[i];
yield {
data: { ...json, token: str },
chunk: str,
};
}
}
// llama.cpp?
else if (typeof json.content === 'string' && json.content.length > 0) {
for (let i = 0; i < json.content.length; i++) {
const str = json.content[i];
yield {
data: { ...json, content: str },
chunk: str,
};
}
}
// OpenAI-likes
else if (Array.isArray(json.choices)) {
const isNotPrimary = json?.choices?.[0]?.index > 0;
if (isNotPrimary || json.choices.length === 0) {
return null;
}
if (typeof json.choices[0].delta === 'object') {
if (typeof json.choices[0].delta.text === 'string' && json.choices[0].delta.text.length > 0) {
for (let j = 0; j < json.choices[0].delta.text.length; j++) {
const str = json.choices[0].delta.text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.text = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
} else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
for (let j = 0; j < json.choices[0].delta.content.length; j++) {
const str = json.choices[0].delta.content[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.content = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
}
}
else if (typeof json.choices[0].message === 'object') {
if (typeof json.choices[0].message.content === 'string' && json.choices[0].message.content.length > 0) {
for (let j = 0; j < json.choices[0].message.content.length; j++) {
const str = json.choices[0].message.content[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.message.content = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
}
}
else if (typeof json.choices[0].text === 'string' && json.choices[0].text.length > 0) {
for (let j = 0; j < json.choices[0].text.length; j++) {
const str = json.choices[0].text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.text = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
}
}
return null;
}
/**
* Like the default one, but multiplies the events by the number of letters in the event data.
*/
export class SmoothEventSourceStream extends EventSourceStream {
constructor() {
super();
const defaultDelayMs = 20;
const punctuationDelayMs = 500;
function getDelay(s) {
if (!s) {
return 0;
}
if (s == ',') {
return punctuationDelayMs / 2;
}
if (['.', '!', '?', '\n'].includes(s)) {
return punctuationDelayMs;
}
return defaultDelayMs;
}
let lastStr = '';
const transformStream = new TransformStream({
async transform(chunk, controller) {
@ -109,119 +232,19 @@ export class SmoothEventSourceStream extends EventSourceStream {
const json = JSON.parse(data);
if (!json) {
controller.enqueue(event);
return;
lastStr = '';
return controller.enqueue(event);
}
// Claude
if (typeof json.delta === 'object') {
if (typeof json.delta.text === 'string' && json.delta.text.length > 0) {
for (let i = 0; i < json.delta.text.length; i++) {
await delay(getDelay(lastStr));
const str = json.delta.text[i];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, delta: { text: str } }) }));
lastStr = str;
}
} else {
controller.enqueue(event);
for await (const parsed of parseStreamData(json)) {
if (!parsed) {
lastStr = '';
return controller.enqueue(event);
}
}
// MakerSuite
else if (Array.isArray(json.candidates)) {
for (let i = 0; i < json.candidates.length; i++) {
if (typeof json.candidates[i].content === 'string' && json.candidates[i].content.length > 0) {
for (let j = 0; j < json.candidates[i].content.length; j++) {
await delay(getDelay(lastStr));
const str = json.candidates[i].content[j];
const candidatesClone = structuredClone(json.candidates[i]);
candidatesClone[i].content = str;
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, candidates: candidatesClone }) }));
lastStr = str;
}
} else {
controller.enqueue(event);
}
}
}
// NovelAI / KoboldCpp Classic
else if (typeof json.token === 'string' && json.token.length > 0) {
for (let i = 0; i < json.token.length; i++) {
await delay(getDelay(lastStr));
const str = json.token[i];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, token: str }) }));
lastStr = str;
}
}
// llama.cpp?
else if (typeof json.content === 'string' && json.content.length > 0) {
for (let i = 0; i < json.content.length; i++) {
await delay(getDelay(lastStr));
const str = json.content[i];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, content: str }) }));
lastStr = str;
}
}
// OpenAI-likes
else if (Array.isArray(json.choices)) {
const isNotPrimary = json?.choices?.[0]?.index > 0;
if (isNotPrimary || json.choices.length === 0) {
controller.enqueue(event);
return;
}
if (typeof json.choices[0].delta === 'object') {
if (typeof json.choices[0].delta.text === 'string' && json.choices[0].delta.text.length > 0) {
for (let j = 0; j < json.choices[0].delta.text.length; j++) {
await delay(getDelay(lastStr));
const str = json.choices[0].delta.text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.text = str;
const choices = [choiceClone];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, choices }) }));
lastStr = str;
}
} else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
for (let j = 0; j < json.choices[0].delta.content.length; j++) {
await delay(getDelay(lastStr));
const str = json.choices[0].delta.content[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.content = str;
const choices = [choiceClone];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, choices }) }));
lastStr = str;
}
} else {
controller.enqueue(event);
}
}
else if (typeof json.choices[0].message === 'object') {
if (typeof json.choices[0].message.content === 'string' && json.choices[0].message.content.length > 0) {
for (let j = 0; j < json.choices[0].message.content.length; j++) {
await delay(getDelay(lastStr));
const str = json.choices[0].message.content[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.message.content = str;
const choices = [choiceClone];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, choices }) }));
lastStr = str;
}
} else {
controller.enqueue(event);
}
}
else if (typeof json.choices[0].text === 'string' && json.choices[0].text.length > 0) {
for (let j = 0; j < json.choices[0].text.length; j++) {
await delay(getDelay(lastStr));
const str = json.choices[0].text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.text = str;
const choices = [choiceClone];
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify({ ...json, choices }) }));
lastStr = str;
}
} else {
controller.enqueue(event);
}
await delay(getDelay(lastStr));
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) }));
lastStr = parsed.chunk;
}
} catch {
controller.enqueue(event);