mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-08 19:48:51 +01:00
- Updated the initial response handling to avoid pre-initializing content types, enabling dynamic creation of content parts based on incoming delta events. This change supports various content types such as think and text.
152 lines
4.6 KiB
TypeScript
152 lines
4.6 KiB
TypeScript
import { memo, useMemo } from 'react';
|
|
import { ContentTypes } from 'librechat-data-provider';
|
|
import type {
|
|
TMessageContentParts,
|
|
SearchResultData,
|
|
TAttachment,
|
|
Agents,
|
|
} from 'librechat-data-provider';
|
|
import { MessageContext, SearchContext } from '~/Providers';
|
|
import { EditTextPart, EmptyText } from './Parts';
|
|
import MemoryArtifacts from './MemoryArtifacts';
|
|
import Sources from '~/components/Web/Sources';
|
|
import { mapAttachments } from '~/utils/map';
|
|
import Container from './Container';
|
|
import Part from './Part';
|
|
|
|
type ContentPartsProps = {
|
|
content: Array<TMessageContentParts | undefined> | undefined;
|
|
messageId: string;
|
|
conversationId?: string | null;
|
|
attachments?: TAttachment[];
|
|
searchResults?: { [key: string]: SearchResultData };
|
|
isCreatedByUser: boolean;
|
|
isLast: boolean;
|
|
isSubmitting: boolean;
|
|
isLatestMessage?: boolean;
|
|
edit?: boolean;
|
|
enterEdit?: (cancel?: boolean) => void | null | undefined;
|
|
siblingIdx?: number;
|
|
setSiblingIdx?:
|
|
| ((value: number) => void | React.Dispatch<React.SetStateAction<number>>)
|
|
| null
|
|
| undefined;
|
|
};
|
|
|
|
const ContentParts = memo(
|
|
({
|
|
content,
|
|
messageId,
|
|
conversationId,
|
|
attachments,
|
|
searchResults,
|
|
isCreatedByUser,
|
|
isLast,
|
|
isSubmitting,
|
|
isLatestMessage,
|
|
edit,
|
|
enterEdit,
|
|
siblingIdx,
|
|
setSiblingIdx,
|
|
}: ContentPartsProps) => {
|
|
const attachmentMap = useMemo(() => mapAttachments(attachments ?? []), [attachments]);
|
|
|
|
const effectiveIsSubmitting = isLatestMessage ? isSubmitting : false;
|
|
|
|
if (!content) {
|
|
return null;
|
|
}
|
|
if (edit === true && enterEdit && setSiblingIdx) {
|
|
return (
|
|
<>
|
|
{content.map((part, idx) => {
|
|
if (!part) {
|
|
return null;
|
|
}
|
|
const isTextPart =
|
|
part?.type === ContentTypes.TEXT ||
|
|
typeof (part as unknown as Agents.MessageContentText)?.text !== 'string';
|
|
const isThinkPart =
|
|
part?.type === ContentTypes.THINK ||
|
|
typeof (part as unknown as Agents.ReasoningDeltaUpdate)?.think !== 'string';
|
|
if (!isTextPart && !isThinkPart) {
|
|
return null;
|
|
}
|
|
|
|
const isToolCall =
|
|
part.type === ContentTypes.TOOL_CALL || part['tool_call_ids'] != null;
|
|
if (isToolCall) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<EditTextPart
|
|
index={idx}
|
|
part={part as Agents.MessageContentText | Agents.ReasoningDeltaUpdate}
|
|
messageId={messageId}
|
|
isSubmitting={isSubmitting}
|
|
enterEdit={enterEdit}
|
|
siblingIdx={siblingIdx ?? null}
|
|
setSiblingIdx={setSiblingIdx}
|
|
key={`edit-${messageId}-${idx}`}
|
|
/>
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
}
|
|
|
|
/** Show cursor placeholder when content is empty but actively submitting */
|
|
const showEmptyCursor = content.length === 0 && effectiveIsSubmitting;
|
|
|
|
return (
|
|
<>
|
|
<SearchContext.Provider value={{ searchResults }}>
|
|
<MemoryArtifacts attachments={attachments} />
|
|
<Sources messageId={messageId} conversationId={conversationId || undefined} />
|
|
{showEmptyCursor && (
|
|
<Container>
|
|
<EmptyText />
|
|
</Container>
|
|
)}
|
|
{content.map((part, idx) => {
|
|
if (!part) {
|
|
return null;
|
|
}
|
|
|
|
const toolCallId =
|
|
(part?.[ContentTypes.TOOL_CALL] as Agents.ToolCall | undefined)?.id ?? '';
|
|
const partAttachments = attachmentMap[toolCallId];
|
|
|
|
return (
|
|
<MessageContext.Provider
|
|
key={`provider-${messageId}-${idx}`}
|
|
value={{
|
|
messageId,
|
|
isExpanded: true,
|
|
conversationId,
|
|
partIndex: idx,
|
|
nextType: content[idx + 1]?.type,
|
|
isSubmitting: effectiveIsSubmitting,
|
|
isLatestMessage,
|
|
}}
|
|
>
|
|
<Part
|
|
part={part}
|
|
attachments={partAttachments}
|
|
isSubmitting={effectiveIsSubmitting}
|
|
key={`part-${messageId}-${idx}`}
|
|
isCreatedByUser={isCreatedByUser}
|
|
isLast={idx === content.length - 1}
|
|
showCursor={idx === content.length - 1 && isLast}
|
|
/>
|
|
</MessageContext.Provider>
|
|
);
|
|
})}
|
|
</SearchContext.Provider>
|
|
</>
|
|
);
|
|
},
|
|
);
|
|
|
|
export default ContentParts;
|