mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Message Render: cleanup diffing pipeline
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user