From ec39c584749dbf72e862accbce8e64acdba83cd6 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 7 Feb 2024 01:31:07 -0800 Subject: [PATCH] Message Render: cleanup diffing pipeline --- src/apps/chat/components/ChatMessageList.tsx | 10 +++--- .../chat/components/message/ChatMessage.tsx | 19 ++--------- .../message/blocks/BlocksRenderer.tsx | 8 ++--- .../message/blocks/RenderTextDiff.tsx | 34 +++++++++++++++++-- .../chat/components/message/blocks/blocks.ts | 8 ++--- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/apps/chat/components/ChatMessageList.tsx b/src/apps/chat/components/ChatMessageList.tsx index bda1cc0d0..d1b1355f8 100644 --- a/src/apps/chat/components/ChatMessageList.tsx +++ b/src/apps/chat/components/ChatMessageList.tsx @@ -148,17 +148,17 @@ export function ChatMessageList(props: { }); - // text-diff functionality, find the messages to diff with + // text-diff functionality: only diff the last message and when it's complete (not typing), and they're similar in size - const { diffMessage, diffText } = React.useMemo(() => { + const { diffTargetMessage, diffPrevText } = React.useMemo(() => { const [msgB, msgA] = conversationMessages.filter(m => m.role === 'assistant').reverse(); if (msgB?.text && msgA?.text && !msgB?.typing) { const textA = msgA.text, textB = msgB.text; const lenA = textA.length, lenB = textB.length; if (lenA > 80 && lenB > 80 && lenA > lenB / 3 && lenB > lenA / 3) - return { diffMessage: msgB, diffText: textA }; + return { diffTargetMessage: msgB, diffPrevText: textA }; } - return { diffMessage: undefined, diffText: undefined }; + return { diffTargetMessage: undefined, diffPrevText: undefined }; }, [conversationMessages]); @@ -219,7 +219,7 @@ export function ChatMessageList(props: { (null); - React.useEffect(() => { - if (!diffText || !enabled) - return setDiffs(null); - setDiffs( - cleanupEfficiency(makeDiff(diffText, text, { - timeout: 1, - checkLines: true, - }), 4), - ); - }, [text, diffText, enabled]); - return diffs; -} - export const ChatMessageMemo = React.memo(ChatMessage); @@ -473,7 +458,7 @@ function ChatMessage(props: { errorMessage={errorMessage} isBottom={props.isBottom} showDate={props.blocksShowDate === true ? messageUpdated || messageCreated || undefined : undefined} - textDiffs={textDiffs || undefined} + renderTextDiff={textDiffs || undefined} wasUserEdited={wasEdited} onContextMenu={(props.onMessageEdit && ENABLE_SELECTION_RIGHT_CLICK_MENU) ? handleBlocksContextMenu : undefined} onDoubleClick={(props.onMessageEdit && doubleClickToEdit) ? handleBlocksDoubleClick : undefined} diff --git a/src/apps/chat/components/message/blocks/BlocksRenderer.tsx b/src/apps/chat/components/message/blocks/BlocksRenderer.tsx index 14c6377cc..7445451af 100644 --- a/src/apps/chat/components/message/blocks/BlocksRenderer.tsx +++ b/src/apps/chat/components/message/blocks/BlocksRenderer.tsx @@ -50,11 +50,11 @@ export function BlocksRenderer(props: { text: string; fromRole: DMessage['role']; renderTextAsMarkdown: boolean; + renderTextDiff?: TextDiff[]; errorMessage?: React.ReactNode; isBottom?: boolean; showDate?: number; - textDiffs?: TextDiff[]; wasUserEdited?: boolean; specialDiagramMode?: boolean; @@ -69,7 +69,7 @@ export function BlocksRenderer(props: { const [forceUserExpanded, setForceUserExpanded] = React.useState(false); // derived state - const { text: _text, textDiffs, errorMessage, wasUserEdited = false } = props; + const { text: _text, errorMessage, renderTextDiff, wasUserEdited = false } = props; const fromAssistant = props.fromRole === 'assistant'; const fromSystem = props.fromRole === 'system'; const fromUser = props.fromRole === 'user'; @@ -87,9 +87,9 @@ export function BlocksRenderer(props: { }, [forceUserExpanded, fromUser, _text]); const blocks = React.useMemo(() => { - const blocks = errorMessage ? [] : parseMessageBlocks(text, fromSystem, textDiffs || null); + const blocks = errorMessage ? [] : parseMessageBlocks(text, fromSystem, renderTextDiff); return props.specialDiagramMode ? blocks.filter(block => block.type === 'code' || blocks.length === 1) : blocks; - }, [errorMessage, fromSystem, props.specialDiagramMode, text, textDiffs]); + }, [errorMessage, fromSystem, props.specialDiagramMode, renderTextDiff, text]); const codeSx: SxProps = React.useMemo(() => ( { diff --git a/src/apps/chat/components/message/blocks/RenderTextDiff.tsx b/src/apps/chat/components/message/blocks/RenderTextDiff.tsx index 35e63a299..5897ffba0 100644 --- a/src/apps/chat/components/message/blocks/RenderTextDiff.tsx +++ b/src/apps/chat/components/message/blocks/RenderTextDiff.tsx @@ -1,12 +1,42 @@ import * as React from 'react'; -import { Diff as TextDiff, DIFF_DELETE, DIFF_INSERT } from '@sanity/diff-match-patch'; +import { cleanupEfficiency, Diff as TextDiff, DIFF_DELETE, DIFF_INSERT, makeDiff } from '@sanity/diff-match-patch'; +import type { SxProps } from '@mui/joy/styles/types'; import { Box, Typography, useTheme } from '@mui/joy'; -import { SxProps } from '@mui/joy/styles/types'; import type { DiffBlock } from './blocks'; +export function useSanityTextDiffs(_text: string, _diffText: string | undefined, enabled: boolean) { + // state + const [diffs, setDiffs] = React.useState(null); + + const inputText = enabled ? _text : null; + const inputPrevText = enabled ? _diffText : null; + + // async processing of diffs + React.useEffect(() => { + if (!inputText || !inputPrevText) + return setDiffs(null); + + const callback = () => { + setDiffs( + cleanupEfficiency(makeDiff(inputPrevText, inputText, { + timeout: 1, + checkLines: true, + }), 4), + ); + }; + + // slight delay to cancel the previous operation if too close to this + const timeout = setTimeout(callback, 200); + return () => clearTimeout(timeout); + }, [inputPrevText, inputText]); + + return diffs; +} + + export const RenderTextDiff = ({ diffBlock, sx }: { diffBlock: DiffBlock; sx?: SxProps; }) => { // external state diff --git a/src/apps/chat/components/message/blocks/blocks.ts b/src/apps/chat/components/message/blocks/blocks.ts index be26ef8c1..9b1de1f97 100644 --- a/src/apps/chat/components/message/blocks/blocks.ts +++ b/src/apps/chat/components/message/blocks/blocks.ts @@ -13,12 +13,12 @@ export type LatexBlock = { type: 'latex'; latex: string; }; export type TextBlock = { type: 'text'; content: string; }; // for Text or Markdown -export function parseMessageBlocks(text: string, forceText: boolean, textDiffs: TextDiff[] | null): Block[] { - if (forceText) +export function parseMessageBlocks(text: string, disableParsing: boolean, forceTextDiffs?: TextDiff[]): Block[] { + if (disableParsing) return [{ type: 'text', content: text }]; - if (textDiffs && textDiffs.length > 0) - return [{ type: 'diff', textDiffs }]; + if (forceTextDiffs && forceTextDiffs.length >= 1) + return [{ type: 'diff', textDiffs: forceTextDiffs }]; // special case: this could be generated by a proxy that returns an HTML page instead of the API response if (heuristicIsHtml(text))