Skip to content

Commit

Permalink
Updated markdown parser to remove md-in-progress container on complete
Browse files Browse the repository at this point in the history
  • Loading branch information
salmenus committed Jun 7, 2024
1 parent c2913c4 commit ef904ea
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 20 deletions.
4 changes: 4 additions & 0 deletions packages/css/themes/src/common/components/Message.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.nlux-comp-message > .nlux-markdownStream-root {
width: 100%;

.md-in-progress:empty {
display: none;
}

This comment has been minimized.

Copy link
@salmenus

salmenus Jun 7, 2024

Author Member

We hide when empty — but also remove on complete

}

.nlux-comp-message .nlux-markdown-container {
Expand Down
45 changes: 26 additions & 19 deletions packages/shared/src/markdown/stream/streamParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {parseMdSnapshot} from '../snapshot/snapshotParser';
import {attachCopyClickListener} from '../copyToClipboard/attachCopyClickListener';

const defaultDelayInMsBeforeComplete = 2000;
const defaultDelayInMsBetweenBufferChecks = 2;
const defaultDelayInMsBetweenBufferChecks = 8;

const getScheduler = (type: 'timeout' | 'animationFrame') => {
if (type === 'timeout') {
Expand Down Expand Up @@ -68,26 +68,29 @@ export const createMdStreamRenderer: StandardStreamParser = (

const delayBetweenBufferChecks = (
!options?.skipStreamingAnimation && options?.streamingAnimationSpeed && options.streamingAnimationSpeed >= 0
) ? options.streamingAnimationSpeed : defaultDelayInMsBetweenBufferChecks;

let timeSinceLastProcessing: number | undefined = undefined;
let currentMarkdown = '';
let previousHtml: string | undefined = undefined;
) ? options.streamingAnimationSpeed : (options?.skipStreamingAnimation ? 0 : defaultDelayInMsBetweenBufferChecks);

const parsingContext: {
timeSinceLastProcessing: number;
currentMarkdown: string;
previousHtml: string | undefined;
} = {
timeSinceLastProcessing: new Date().getTime(),
currentMarkdown: '',
previousHtml: undefined,
};

let parsingInterval: number | undefined = setInterval(() => {
const nowTime = new Date().getTime();
if (buffer.length === 0) {
if (
streamIsComplete
|| (timeSinceLastProcessing && nowTime - timeSinceLastProcessing > defaultDelayInMsBeforeComplete)
) {
if (streamIsComplete || nowTime - parsingContext.timeSinceLastProcessing > defaultDelayInMsBeforeComplete) {
completeParsing();
}

return;
}

timeSinceLastProcessing = nowTime;
parsingContext.timeSinceLastProcessing = nowTime;
const chunk = buffer.shift();
if (!chunk) {
return;
Expand All @@ -100,17 +103,21 @@ export const createMdStreamRenderer: StandardStreamParser = (
// - Text that is committed to the DOM and will not change (example: `# Hello World!\n\n`)

// Append the new chunk to the raw text and parse
const markdownToParse = currentMarkdown + chunk;
const markdownToParse = parsingContext.currentMarkdown + chunk;
const parsedHtml = parseMdSnapshot(markdownToParse, options).trim();

if (typeof parsedHtml !== 'string') {
// Remove the last chunk if parsing failed
currentMarkdown = currentMarkdown.slice(0, -chunk.length);
parsingContext.currentMarkdown = parsingContext.currentMarkdown.slice(0, -chunk.length);
warn('Markdown parsing failed');
return;
}

if (previousHtml && parsedHtml.length > previousHtml.length && parsedHtml.startsWith(previousHtml)) {
if (
parsingContext.previousHtml &&
parsedHtml.length > parsingContext.previousHtml.length &&
parsedHtml.startsWith(parsingContext.previousHtml)
) {
// Case 1: No changes to the previous HTML — And new HTML added on top of it
// Which means the new chunk added new HTML content outside the last parsed markdown
// Which means that the last parsed markdown is complete and should be committed to the DOM
Expand All @@ -119,12 +126,12 @@ export const createMdStreamRenderer: StandardStreamParser = (
commitWipContent();

// Extract new HTML and insert it into WIP container
const currentHtml = parsedHtml.slice(previousHtml.length).trim();
const currentHtml = parsedHtml.slice(parsingContext.previousHtml.length).trim();
wipContainer.innerHTML = options?.htmlSanitizer ? options.htmlSanitizer(currentHtml) : currentHtml;

// Focus on everything that is new
currentMarkdown = chunk;
previousHtml = undefined;
parsingContext.currentMarkdown = chunk;
parsingContext.previousHtml = undefined;
} else {
// Case 2: Changes to the previous HTML
// This means that new chunk goes inside previous HTML and no root level changes
Expand All @@ -133,8 +140,8 @@ export const createMdStreamRenderer: StandardStreamParser = (
wipContainer.innerHTML = options?.htmlSanitizer ? options.htmlSanitizer(parsedHtml) : parsedHtml;

// Update the current markdown and previous HTML for the next iteration
currentMarkdown = markdownToParse;
previousHtml = parsedHtml;
parsingContext.currentMarkdown = markdownToParse;
parsingContext.previousHtml = parsedHtml;
}
});
}, delayBetweenBufferChecks) as unknown as number;
Expand Down
2 changes: 1 addition & 1 deletion samples/aiChat/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function App() {
const [useCustomPersonaOptions, setUseCustomPersonaOptions] = useState(true);
const [conversationLayout, setConversationLayout] = useState<ConversationLayout>('list');
const [dataTransferMode, setDataTransferMode] = useState<DataTransferMode>('stream');
const [theme, setTheme] = useState<ThemeId>('dev');
const [theme, setTheme] = useState<ThemeId>('nova');
const [colorScheme, setColorScheme] = useState<'light' | 'dark' | 'auto'>('dark');

const onUseCustomResponseComponentChange = useCallback((e) => setUseCustomResponseComponent(e.target.checked),
Expand Down

0 comments on commit ef904ea

Please sign in to comment.