From da96666734bf0db2695d2a387423b5e9da5e634b Mon Sep 17 00:00:00 2001 From: Ovidijus Parsiunas Date: Mon, 29 Apr 2024 23:25:31 +0100 Subject: [PATCH] ability to simulate a stream for html --- component/src/utils/HTTP/stream.ts | 23 ++++++++++++------- .../src/views/chat/messages/html/htmlUtils.ts | 19 +++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/component/src/utils/HTTP/stream.ts b/component/src/utils/HTTP/stream.ts index b5b66c3d9..702a12e53 100644 --- a/component/src/utils/HTTP/stream.ts +++ b/component/src/utils/HTTP/stream.ts @@ -1,6 +1,7 @@ import {EventSourceMessage, fetchEventSource} from '@microsoft/fetch-event-source'; import {MessageStream} from '../../views/chat/messages/stream/messageStream'; import {ServiceIO, StreamHandlers} from '../../services/serviceIO'; +import {HTMLUtils} from '../../views/chat/messages/html/htmlUtils'; import {Messages} from '../../views/chat/messages/messages'; import {Response as ResponseI} from '../../types/response'; import {Stream as StreamI} from '../../types/stream'; @@ -91,21 +92,27 @@ export class Stream { public static simulate(messages: Messages, sh: StreamHandlers, result: ResponseI) { const simulationSH = sh as unknown as SimulationSH; - // reason for not streaming html is because there is no standard way to split it - if (result.files || result.html) messages.addNewMessage({sendUpdate: false, ignoreText: true, ...result}, false); + if (result.files) messages.addNewMessage({sendUpdate: false, ignoreText: true, ...result}, false); if (result.text) { sh.onOpen(); - const responseText = result.text.split(''); // important to split by char for Chinese characters - Stream.populateMessages(responseText, new MessageStream(messages), simulationSH); + const responseTextStrings = result.text.split(''); // important to split by char for Chinese characters + Stream.populateMessages(responseTextStrings, new MessageStream(messages), simulationSH, 'text'); + } + if (result.html) { + sh.onOpen(); + const responseHTMLStrings = HTMLUtils.splitHTML(result.html); + Stream.populateMessages(responseHTMLStrings, new MessageStream(messages), simulationSH, 'html'); } } - private static populateMessages(responseText: string[], stream: MessageStream, sh: SimulationSH, charIndex = 0) { - const character = responseText[charIndex]; + // prettier-ignore + private static populateMessages( + responseStrings: string[], stream: MessageStream, sh: SimulationSH, type: 'text'|'html', charIndex = 0) { + const character = responseStrings[charIndex]; if (character) { - stream.upsertStreamedMessage({text: character}); + stream.upsertStreamedMessage({[type]: character}); const timeout = setTimeout(() => { - Stream.populateMessages(responseText, stream, sh, charIndex + 1); + Stream.populateMessages(responseStrings, stream, sh, type, charIndex + 1); }, sh.simulationInterim || 6); sh.abortStream.abort = () => { Stream.abort(timeout, stream, sh.onClose); diff --git a/component/src/views/chat/messages/html/htmlUtils.ts b/component/src/views/chat/messages/html/htmlUtils.ts index 0424c1330..a10df0e84 100644 --- a/component/src/views/chat/messages/html/htmlUtils.ts +++ b/component/src/views/chat/messages/html/htmlUtils.ts @@ -43,4 +43,23 @@ export class HTMLUtils { HTMLDeepChatElements.applyDeepChatUtilities(messages, messages.htmlClassUtilities, outmostElement); HTMLUtils.applyCustomClassUtilities(messages.htmlClassUtilities, outmostElement); } + + private static traverseNodes(node: ChildNode, topLevelElements: string[]) { + if (node.nodeType === Node.ELEMENT_NODE) { + topLevelElements.push((node as HTMLElement).outerHTML); + } + node.childNodes.forEach((childNode) => { + HTMLUtils.traverseNodes(childNode, topLevelElements); + }); + } + + public static splitHTML(htmlString: string) { + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, 'text/html'); + const topLevelElements: string[] = []; + doc.body.childNodes.forEach((childNode) => { + HTMLUtils.traverseNodes(childNode, topLevelElements); + }); + return topLevelElements; + } }