Message Render: cleanup diffing pipeline

This commit is contained in:
Enrico Ros
2024-02-07 01:31:07 -08:00
parent 3ce2e86a66
commit ec39c58474
5 changed files with 47 additions and 32 deletions
+5 -5
View File
@@ -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: {
<ChatMessageMemo
key={'msg-' + message.id}
message={message}
diffPreviousText={message === diffMessage ? diffText : undefined}
diffPreviousText={message === diffTargetMessage ? diffPrevText : undefined}
isBottom={idx === count - 1}
isImagining={isImagining} isSpeaking={isSpeaking}
onConversationBranch={handleConversationBranch}
@@ -1,6 +1,5 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { cleanupEfficiency, Diff as TextDiff, makeDiff } from '@sanity/diff-match-patch';
import { Avatar, Box, CircularProgress, IconButton, ListDivider, ListItem, ListItemDecorator, MenuItem, Switch, Tooltip, Typography } from '@mui/joy';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
@@ -32,6 +31,7 @@ import { useUIPreferencesStore } from '~/common/state/store-ui';
import { BlocksRenderer, editBlocksSx } from './blocks/BlocksRenderer';
import { useChatShowTextDiff } from '../../store-app-chat';
import { useSanityTextDiffs } from './blocks/RenderTextDiff';
// Enable the menu on text selection
@@ -166,21 +166,6 @@ function explainErrorInMessage(text: string, isAssistant: boolean, modelId?: str
return { errorMessage, isAssistantError };
}
function useSanityTextDiffs(text: string, diffText: string | undefined, enabled: boolean) {
const [diffs, setDiffs] = React.useState<TextDiff[] | null>(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}
@@ -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(() => (
{
@@ -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<TextDiff[] | null>(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
@@ -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))