mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-02-16 23:35:46 +00:00
Compare commits
1 Commits
experiment
...
jamison/me
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bee6536f8 |
@@ -122,15 +122,15 @@ function MessageEditing({
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div
|
||||
className={cn(
|
||||
className={
|
||||
"w-full h-full border rounded-16 overflow-hidden p-3 flex flex-col gap-2"
|
||||
)}
|
||||
}
|
||||
>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className={cn(
|
||||
className={
|
||||
"w-full h-full resize-none outline-none bg-transparent overflow-y-scroll whitespace-normal break-word"
|
||||
)}
|
||||
}
|
||||
aria-multiline
|
||||
role="textarea"
|
||||
value={editedContent}
|
||||
@@ -226,108 +226,106 @@ export default function HumanMessage({
|
||||
return (
|
||||
<div
|
||||
id="onyx-human-message"
|
||||
className="pt-5 pb-1 w-full lg:px-5 flex justify-center -mr-6 relative"
|
||||
className="text-user-text pt-5 pb-1 w-full flex justify-end -mr-6 relative"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div className={cn("text-user-text max-w-[790px] px-4 w-full")}>
|
||||
<FileDisplay alignBubble files={files || []} />
|
||||
<div className="flex flex-wrap justify-end break-words">
|
||||
{isEditing ? (
|
||||
<MessageEditing
|
||||
content={content}
|
||||
onSubmitEdit={(editedContent) => {
|
||||
onEdit?.(editedContent);
|
||||
setContent(editedContent);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
onCancelEdit={() => setIsEditing(false)}
|
||||
/>
|
||||
) : typeof content === "string" ? (
|
||||
<>
|
||||
<div className="md:max-w-[25rem] flex basis-[100%] md:basis-auto justify-end md:order-1">
|
||||
<div
|
||||
className={
|
||||
"max-w-[25rem] whitespace-break-spaces rounded-t-16 rounded-bl-16 bg-background-tint-02 py-2 px-3"
|
||||
}
|
||||
>
|
||||
<Text mainContentBody>{content}</Text>
|
||||
</div>
|
||||
<FileDisplay alignBubble files={files || []} />
|
||||
<div className="flex flex-wrap justify-end break-words">
|
||||
{isEditing ? (
|
||||
<MessageEditing
|
||||
content={content}
|
||||
onSubmitEdit={(editedContent) => {
|
||||
onEdit?.(editedContent);
|
||||
setContent(editedContent);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
onCancelEdit={() => setIsEditing(false)}
|
||||
/>
|
||||
) : typeof content === "string" ? (
|
||||
<>
|
||||
<div className="md:max-w-[25rem] flex basis-[100%] md:basis-auto justify-end md:order-1">
|
||||
<div
|
||||
className={
|
||||
"max-w-[25rem] whitespace-break-spaces rounded-t-16 rounded-bl-16 bg-background-tint-02 py-2 px-3"
|
||||
}
|
||||
>
|
||||
<Text mainContentBody>{content}</Text>
|
||||
</div>
|
||||
{onEdit &&
|
||||
isHovered &&
|
||||
!isEditing &&
|
||||
(!files || files.length === 0) ? (
|
||||
<div className="flex flex-row gap-1 p-1">
|
||||
<CopyIconButton
|
||||
getCopyText={() => content}
|
||||
tertiary
|
||||
data-testid="HumanMessage/copy-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgEdit}
|
||||
tertiary
|
||||
tooltip="Edit"
|
||||
onClick={() => {
|
||||
setIsEditing(true);
|
||||
setIsHovered(false);
|
||||
}}
|
||||
data-testid="HumanMessage/edit-button"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-7 h-10" />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{onEdit &&
|
||||
isHovered &&
|
||||
!isEditing &&
|
||||
(!files || files.length === 0) ? (
|
||||
<div className="my-auto">
|
||||
<IconButton
|
||||
icon={SvgEdit}
|
||||
onClick={() => {
|
||||
setIsEditing(true);
|
||||
setIsHovered(false);
|
||||
}}
|
||||
tertiary
|
||||
tooltip="Edit"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-[27px]" />
|
||||
)}
|
||||
<div className="ml-auto rounded-lg p-1">{content}</div>
|
||||
</>
|
||||
)}
|
||||
<div className="md:min-w-[100%] flex justify-end order-1 mt-1">
|
||||
{currentMessageInd !== undefined &&
|
||||
onMessageSelection &&
|
||||
otherMessagesCanSwitchTo &&
|
||||
otherMessagesCanSwitchTo.length > 1 && (
|
||||
<MessageSwitcher
|
||||
disableForStreaming={disableSwitchingForStreaming}
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
stopGenerating();
|
||||
const prevMessage = getPreviousMessage();
|
||||
if (prevMessage !== undefined) {
|
||||
onMessageSelection(prevMessage);
|
||||
}
|
||||
}}
|
||||
handleNext={() => {
|
||||
stopGenerating();
|
||||
const nextMessage = getNextMessage();
|
||||
if (nextMessage !== undefined) {
|
||||
onMessageSelection(nextMessage);
|
||||
}
|
||||
}}
|
||||
</div>
|
||||
{onEdit &&
|
||||
isHovered &&
|
||||
!isEditing &&
|
||||
(!files || files.length === 0) ? (
|
||||
<div className="flex flex-row gap-1 p-1">
|
||||
<CopyIconButton
|
||||
getCopyText={() => content}
|
||||
tertiary
|
||||
data-testid="HumanMessage/copy-button"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<IconButton
|
||||
icon={SvgEdit}
|
||||
tertiary
|
||||
tooltip="Edit"
|
||||
onClick={() => {
|
||||
setIsEditing(true);
|
||||
setIsHovered(false);
|
||||
}}
|
||||
data-testid="HumanMessage/edit-button"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-7 h-10" />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{onEdit &&
|
||||
isHovered &&
|
||||
!isEditing &&
|
||||
(!files || files.length === 0) ? (
|
||||
<div className="my-auto">
|
||||
<IconButton
|
||||
icon={SvgEdit}
|
||||
onClick={() => {
|
||||
setIsEditing(true);
|
||||
setIsHovered(false);
|
||||
}}
|
||||
tertiary
|
||||
tooltip="Edit"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-[27px]" />
|
||||
)}
|
||||
<div className="ml-auto rounded-lg p-1">{content}</div>
|
||||
</>
|
||||
)}
|
||||
<div className="md:min-w-[100%] flex justify-end order-1 mt-1">
|
||||
{currentMessageInd !== undefined &&
|
||||
onMessageSelection &&
|
||||
otherMessagesCanSwitchTo &&
|
||||
otherMessagesCanSwitchTo.length > 1 && (
|
||||
<MessageSwitcher
|
||||
disableForStreaming={disableSwitchingForStreaming}
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
stopGenerating();
|
||||
const prevMessage = getPreviousMessage();
|
||||
if (prevMessage !== undefined) {
|
||||
onMessageSelection(prevMessage);
|
||||
}
|
||||
}}
|
||||
handleNext={() => {
|
||||
stopGenerating();
|
||||
const nextMessage = getNextMessage();
|
||||
if (nextMessage !== undefined) {
|
||||
onMessageSelection(nextMessage);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -444,199 +444,192 @@ export default function AIMessage({
|
||||
data-testid={displayComplete ? "onyx-ai-message" : undefined}
|
||||
className="pb-5 md:pt-5 relative flex"
|
||||
>
|
||||
<div className="mx-auto w-[min(50rem,100%)] px-4 max-w-message-max">
|
||||
<div className="flex items-start">
|
||||
<AgentAvatar agent={chatState.assistant} size={24} />
|
||||
<div className="max-w-message-max break-words pl-4">
|
||||
<div
|
||||
ref={markdownRef}
|
||||
className="overflow-x-visible max-w-content-max focus:outline-none select-text"
|
||||
onCopy={(e) => {
|
||||
if (markdownRef.current) {
|
||||
handleCopy(e, markdownRef as RefObject<HTMLDivElement>);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{groupedPackets.length === 0 ? (
|
||||
// Show blinking dot when no content yet but message is generating
|
||||
<BlinkingDot addMargin />
|
||||
) : (
|
||||
(() => {
|
||||
// Simple split: tools vs non-tools
|
||||
const toolGroups = groupedPackets.filter(
|
||||
(group) =>
|
||||
group.packets[0] &&
|
||||
isToolPacket(group.packets[0], false)
|
||||
);
|
||||
<div className="flex items-start">
|
||||
<AgentAvatar agent={chatState.assistant} size={24} />
|
||||
<div className="max-w-message-max break-words pl-4">
|
||||
<div
|
||||
ref={markdownRef}
|
||||
className="overflow-x-visible max-w-content-max focus:outline-none select-text"
|
||||
onCopy={(e) => {
|
||||
if (markdownRef.current) {
|
||||
handleCopy(e, markdownRef as RefObject<HTMLDivElement>);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{groupedPackets.length === 0 ? (
|
||||
// Show blinking dot when no content yet but message is generating
|
||||
<BlinkingDot addMargin />
|
||||
) : (
|
||||
(() => {
|
||||
// Simple split: tools vs non-tools
|
||||
const toolGroups = groupedPackets.filter(
|
||||
(group) =>
|
||||
group.packets[0] && isToolPacket(group.packets[0], false)
|
||||
);
|
||||
|
||||
// Non-tools include messages AND image generation
|
||||
const displayGroups =
|
||||
finalAnswerComing || toolGroups.length === 0
|
||||
? groupedPackets.filter(
|
||||
(group) =>
|
||||
group.packets[0] &&
|
||||
isDisplayPacket(group.packets[0])
|
||||
)
|
||||
: [];
|
||||
// Non-tools include messages AND image generation
|
||||
const displayGroups =
|
||||
finalAnswerComing || toolGroups.length === 0
|
||||
? groupedPackets.filter(
|
||||
(group) =>
|
||||
group.packets[0] &&
|
||||
isDisplayPacket(group.packets[0])
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Render tool groups in multi-tool renderer */}
|
||||
{toolGroups.length > 0 && (
|
||||
<MultiToolRenderer
|
||||
packetGroups={toolGroups}
|
||||
chatState={effectiveChatState}
|
||||
isComplete={finalAnswerComing}
|
||||
isFinalAnswerComing={finalAnswerComingRef.current}
|
||||
stopPacketSeen={stopPacketSeen}
|
||||
isStreaming={globalChatState === "streaming"}
|
||||
onAllToolsDisplayed={() =>
|
||||
setFinalAnswerComing(true)
|
||||
return (
|
||||
<>
|
||||
{/* Render tool groups in multi-tool renderer */}
|
||||
{toolGroups.length > 0 && (
|
||||
<MultiToolRenderer
|
||||
packetGroups={toolGroups}
|
||||
chatState={effectiveChatState}
|
||||
isComplete={finalAnswerComing}
|
||||
isFinalAnswerComing={finalAnswerComingRef.current}
|
||||
stopPacketSeen={stopPacketSeen}
|
||||
isStreaming={globalChatState === "streaming"}
|
||||
onAllToolsDisplayed={() => setFinalAnswerComing(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Render all display groups (messages + image generation) in main area */}
|
||||
{displayGroups.map((displayGroup, index) => (
|
||||
<RendererComponent
|
||||
key={`${displayGroup.turn_index}-${displayGroup.tab_index}`}
|
||||
packets={displayGroup.packets}
|
||||
chatState={effectiveChatState}
|
||||
onComplete={() => {
|
||||
// if we've reverted to final answer not coming, don't set display complete
|
||||
// this happens when using claude and a tool calling packet comes after
|
||||
// some message packets
|
||||
// Only mark complete on the last display group
|
||||
if (
|
||||
finalAnswerComingRef.current &&
|
||||
index === displayGroups.length - 1
|
||||
) {
|
||||
setDisplayComplete(true);
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Render all display groups (messages + image generation) in main area */}
|
||||
{displayGroups.map((displayGroup, index) => (
|
||||
<RendererComponent
|
||||
key={`${displayGroup.turn_index}-${displayGroup.tab_index}`}
|
||||
packets={displayGroup.packets}
|
||||
chatState={effectiveChatState}
|
||||
onComplete={() => {
|
||||
// if we've reverted to final answer not coming, don't set display complete
|
||||
// this happens when using claude and a tool calling packet comes after
|
||||
// some message packets
|
||||
// Only mark complete on the last display group
|
||||
if (
|
||||
finalAnswerComingRef.current &&
|
||||
index === displayGroups.length - 1
|
||||
) {
|
||||
setDisplayComplete(true);
|
||||
}
|
||||
}}
|
||||
animate={false}
|
||||
stopPacketSeen={stopPacketSeen}
|
||||
>
|
||||
{({ content }) => <div>{content}</div>}
|
||||
</RendererComponent>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
})()
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Feedback buttons - only show when streaming is complete */}
|
||||
{stopPacketSeen && displayComplete && (
|
||||
<div className="flex md:flex-row justify-between items-center w-full mt-1 transition-transform duration-300 ease-in-out transform opacity-100">
|
||||
<TooltipGroup>
|
||||
<div className="flex items-center gap-x-0.5">
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1">
|
||||
<MessageSwitcher
|
||||
currentPage={(currentMessageInd ?? 0) + 1}
|
||||
totalPages={otherMessagesCanSwitchTo?.length || 0}
|
||||
handlePrevious={() => {
|
||||
const prevMessage = getPreviousMessage();
|
||||
if (
|
||||
prevMessage !== undefined &&
|
||||
onMessageSelection
|
||||
) {
|
||||
onMessageSelection(prevMessage);
|
||||
}
|
||||
}}
|
||||
handleNext={() => {
|
||||
const nextMessage = getNextMessage();
|
||||
if (
|
||||
nextMessage !== undefined &&
|
||||
onMessageSelection
|
||||
) {
|
||||
onMessageSelection(nextMessage);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CopyIconButton
|
||||
getCopyText={() =>
|
||||
convertMarkdownTablesToTsv(getTextContent(rawPackets))
|
||||
}
|
||||
getHtmlContent={() =>
|
||||
markdownRef.current?.innerHTML || ""
|
||||
}
|
||||
tertiary
|
||||
data-testid="AIMessage/copy-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgThumbsUp}
|
||||
onClick={() => handleFeedbackClick("like")}
|
||||
tertiary
|
||||
transient={isFeedbackTransient("like")}
|
||||
tooltip={
|
||||
currentFeedback === "like"
|
||||
? "Remove Like"
|
||||
: "Good Response"
|
||||
}
|
||||
data-testid="AIMessage/like-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgThumbsDown}
|
||||
onClick={() => handleFeedbackClick("dislike")}
|
||||
tertiary
|
||||
transient={isFeedbackTransient("dislike")}
|
||||
tooltip={
|
||||
currentFeedback === "dislike"
|
||||
? "Remove Dislike"
|
||||
: "Bad Response"
|
||||
}
|
||||
data-testid="AIMessage/dislike-button"
|
||||
/>
|
||||
|
||||
{chatState.regenerate && llmManager && (
|
||||
<div data-testid="AIMessage/regenerate">
|
||||
<LLMPopover
|
||||
llmManager={llmManager}
|
||||
currentModelName={chatState.overriddenModel}
|
||||
onSelect={(modelName) => {
|
||||
const llmDescriptor =
|
||||
parseLlmDescriptor(modelName);
|
||||
chatState.regenerate!(llmDescriptor);
|
||||
}}
|
||||
folded
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeId &&
|
||||
(citations.length > 0 || documentMap.size > 0) && (
|
||||
<CitedSourcesToggle
|
||||
citations={citations}
|
||||
documentMap={documentMap}
|
||||
nodeId={nodeId}
|
||||
onToggle={(toggledNodeId) => {
|
||||
// Toggle sidebar if clicking on the same message
|
||||
if (
|
||||
selectedMessageForDocDisplay ===
|
||||
toggledNodeId &&
|
||||
documentSidebarVisible
|
||||
) {
|
||||
updateCurrentDocumentSidebarVisible(false);
|
||||
updateCurrentSelectedNodeForDocDisplay(null);
|
||||
} else {
|
||||
updateCurrentSelectedNodeForDocDisplay(
|
||||
toggledNodeId
|
||||
);
|
||||
updateCurrentDocumentSidebarVisible(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TooltipGroup>
|
||||
</div>
|
||||
}}
|
||||
animate={false}
|
||||
stopPacketSeen={stopPacketSeen}
|
||||
>
|
||||
{({ content }) => <div>{content}</div>}
|
||||
</RendererComponent>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
})()
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Feedback buttons - only show when streaming is complete */}
|
||||
{stopPacketSeen && displayComplete && (
|
||||
<div className="flex md:flex-row justify-between items-center w-full mt-1 transition-transform duration-300 ease-in-out transform opacity-100">
|
||||
<TooltipGroup>
|
||||
<div className="flex items-center gap-x-0.5">
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1">
|
||||
<MessageSwitcher
|
||||
currentPage={(currentMessageInd ?? 0) + 1}
|
||||
totalPages={otherMessagesCanSwitchTo?.length || 0}
|
||||
handlePrevious={() => {
|
||||
const prevMessage = getPreviousMessage();
|
||||
if (
|
||||
prevMessage !== undefined &&
|
||||
onMessageSelection
|
||||
) {
|
||||
onMessageSelection(prevMessage);
|
||||
}
|
||||
}}
|
||||
handleNext={() => {
|
||||
const nextMessage = getNextMessage();
|
||||
if (
|
||||
nextMessage !== undefined &&
|
||||
onMessageSelection
|
||||
) {
|
||||
onMessageSelection(nextMessage);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CopyIconButton
|
||||
getCopyText={() =>
|
||||
convertMarkdownTablesToTsv(getTextContent(rawPackets))
|
||||
}
|
||||
getHtmlContent={() =>
|
||||
markdownRef.current?.innerHTML || ""
|
||||
}
|
||||
tertiary
|
||||
data-testid="AIMessage/copy-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgThumbsUp}
|
||||
onClick={() => handleFeedbackClick("like")}
|
||||
tertiary
|
||||
transient={isFeedbackTransient("like")}
|
||||
tooltip={
|
||||
currentFeedback === "like"
|
||||
? "Remove Like"
|
||||
: "Good Response"
|
||||
}
|
||||
data-testid="AIMessage/like-button"
|
||||
/>
|
||||
<IconButton
|
||||
icon={SvgThumbsDown}
|
||||
onClick={() => handleFeedbackClick("dislike")}
|
||||
tertiary
|
||||
transient={isFeedbackTransient("dislike")}
|
||||
tooltip={
|
||||
currentFeedback === "dislike"
|
||||
? "Remove Dislike"
|
||||
: "Bad Response"
|
||||
}
|
||||
data-testid="AIMessage/dislike-button"
|
||||
/>
|
||||
|
||||
{chatState.regenerate && llmManager && (
|
||||
<div data-testid="AIMessage/regenerate">
|
||||
<LLMPopover
|
||||
llmManager={llmManager}
|
||||
currentModelName={chatState.overriddenModel}
|
||||
onSelect={(modelName) => {
|
||||
const llmDescriptor = parseLlmDescriptor(modelName);
|
||||
chatState.regenerate!(llmDescriptor);
|
||||
}}
|
||||
folded
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeId &&
|
||||
(citations.length > 0 || documentMap.size > 0) && (
|
||||
<CitedSourcesToggle
|
||||
citations={citations}
|
||||
documentMap={documentMap}
|
||||
nodeId={nodeId}
|
||||
onToggle={(toggledNodeId) => {
|
||||
// Toggle sidebar if clicking on the same message
|
||||
if (
|
||||
selectedMessageForDocDisplay === toggledNodeId &&
|
||||
documentSidebarVisible
|
||||
) {
|
||||
updateCurrentDocumentSidebarVisible(false);
|
||||
updateCurrentSelectedNodeForDocDisplay(null);
|
||||
} else {
|
||||
updateCurrentSelectedNodeForDocDisplay(
|
||||
toggledNodeId
|
||||
);
|
||||
updateCurrentDocumentSidebarVisible(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TooltipGroup>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function SharedChatDisplay({
|
||||
</div>
|
||||
|
||||
{isMounted ? (
|
||||
<div className="w-full px-8">
|
||||
<div className="max-w-[50rem] m-auto">
|
||||
{messages.map((message, i) => {
|
||||
if (message.type === "user") {
|
||||
return (
|
||||
|
||||
@@ -198,133 +198,137 @@ const ChatUI = React.forwardRef(
|
||||
<div
|
||||
key={currentChatSessionId}
|
||||
ref={scrollContainerRef}
|
||||
className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden default-scrollbar"
|
||||
className="flex flex-1 justify-center min-h-0 px-6 overflow-y-auto overflow-x-hidden default-scrollbar"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{messages.map((message, i) => {
|
||||
const messageReactComponentKey = `message-${message.nodeId}`;
|
||||
const parentMessage = message.parentNodeId
|
||||
? messageTree?.get(message.parentNodeId)
|
||||
: null;
|
||||
<div>
|
||||
{messages.map((message, i) => {
|
||||
const messageReactComponentKey = `message-${message.nodeId}`;
|
||||
const parentMessage = message.parentNodeId
|
||||
? messageTree?.get(message.parentNodeId)
|
||||
: null;
|
||||
|
||||
if (message.type === "user") {
|
||||
const nextMessage =
|
||||
messages.length > i + 1 ? messages[i + 1] : null;
|
||||
if (message.type === "user") {
|
||||
const nextMessage =
|
||||
messages.length > i + 1 ? messages[i + 1] : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
id={messageReactComponentKey}
|
||||
key={messageReactComponentKey}
|
||||
>
|
||||
<HumanMessage
|
||||
disableSwitchingForStreaming={
|
||||
(nextMessage && nextMessage.is_generating) || false
|
||||
}
|
||||
stopGenerating={stopGenerating}
|
||||
content={message.message}
|
||||
files={message.files}
|
||||
messageId={message.messageId}
|
||||
onEdit={(editedContent) => {
|
||||
if (
|
||||
message.messageId !== undefined &&
|
||||
message.messageId !== null
|
||||
) {
|
||||
handleEditWithMessageId(
|
||||
editedContent,
|
||||
message.messageId
|
||||
);
|
||||
}
|
||||
}}
|
||||
otherMessagesCanSwitchTo={
|
||||
parentMessage?.childrenNodeIds ?? emptyChildrenIds
|
||||
}
|
||||
onMessageSelection={onMessageSelection}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (message.type === "assistant") {
|
||||
if ((error || loadError) && i === messages.length - 1) {
|
||||
return (
|
||||
<div
|
||||
key={`error-${message.nodeId}`}
|
||||
className="max-w-message-max mx-auto"
|
||||
id={messageReactComponentKey}
|
||||
key={messageReactComponentKey}
|
||||
>
|
||||
<ErrorBanner
|
||||
resubmit={handleResubmitLastMessage}
|
||||
error={error || loadError || ""}
|
||||
errorCode={message.errorCode || undefined}
|
||||
isRetryable={message.isRetryable ?? true}
|
||||
details={message.errorDetails || undefined}
|
||||
stackTrace={message.stackTrace || undefined}
|
||||
<HumanMessage
|
||||
disableSwitchingForStreaming={
|
||||
(nextMessage && nextMessage.is_generating) || false
|
||||
}
|
||||
stopGenerating={stopGenerating}
|
||||
content={message.message}
|
||||
files={message.files}
|
||||
messageId={message.messageId}
|
||||
onEdit={(editedContent) => {
|
||||
if (
|
||||
message.messageId !== undefined &&
|
||||
message.messageId !== null
|
||||
) {
|
||||
handleEditWithMessageId(
|
||||
editedContent,
|
||||
message.messageId
|
||||
);
|
||||
}
|
||||
}}
|
||||
otherMessagesCanSwitchTo={
|
||||
parentMessage?.childrenNodeIds ?? emptyChildrenIds
|
||||
}
|
||||
onMessageSelection={onMessageSelection}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (message.type === "assistant") {
|
||||
if ((error || loadError) && i === messages.length - 1) {
|
||||
return (
|
||||
<div
|
||||
key={`error-${message.nodeId}`}
|
||||
className="max-w-message-max mx-auto"
|
||||
>
|
||||
<ErrorBanner
|
||||
resubmit={handleResubmitLastMessage}
|
||||
error={error || loadError || ""}
|
||||
errorCode={message.errorCode || undefined}
|
||||
isRetryable={message.isRetryable ?? true}
|
||||
details={message.errorDetails || undefined}
|
||||
stackTrace={message.stackTrace || undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: it's fine to use the previous entry in messageHistory
|
||||
// since this is a "parsed" version of the message tree
|
||||
// so the previous message is guaranteed to be the parent of the current message
|
||||
const previousMessage = i !== 0 ? messages[i - 1] : null;
|
||||
const regenerate =
|
||||
message.messageId !== undefined && previousMessage
|
||||
? createRegenerator({
|
||||
messageId: message.messageId,
|
||||
parentMessage: previousMessage,
|
||||
})
|
||||
: undefined;
|
||||
const chatStateData = {
|
||||
assistant: liveAssistant,
|
||||
docs: message.documents ?? emptyDocs,
|
||||
citations: message.citations,
|
||||
setPresentingDocument,
|
||||
regenerate,
|
||||
overriddenModel: llmManager.currentLlm?.modelName,
|
||||
researchType: message.researchType,
|
||||
};
|
||||
return (
|
||||
<div
|
||||
id={`message-${message.nodeId}`}
|
||||
key={messageReactComponentKey}
|
||||
>
|
||||
<AIMessage
|
||||
rawPackets={message.packets}
|
||||
chatState={chatStateData}
|
||||
nodeId={message.nodeId}
|
||||
messageId={message.messageId}
|
||||
currentFeedback={message.currentFeedback}
|
||||
llmManager={llmManager}
|
||||
otherMessagesCanSwitchTo={
|
||||
parentMessage?.childrenNodeIds ?? emptyChildrenIds
|
||||
}
|
||||
onMessageSelection={onMessageSelection}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
||||
// NOTE: it's fine to use the previous entry in messageHistory
|
||||
// since this is a "parsed" version of the message tree
|
||||
// so the previous message is guaranteed to be the parent of the current message
|
||||
const previousMessage = i !== 0 ? messages[i - 1] : null;
|
||||
const regenerate =
|
||||
message.messageId !== undefined && previousMessage
|
||||
? createRegenerator({
|
||||
messageId: message.messageId,
|
||||
parentMessage: previousMessage,
|
||||
})
|
||||
: undefined;
|
||||
const chatStateData = {
|
||||
assistant: liveAssistant,
|
||||
docs: message.documents ?? emptyDocs,
|
||||
citations: message.citations,
|
||||
setPresentingDocument,
|
||||
regenerate,
|
||||
overriddenModel: llmManager.currentLlm?.modelName,
|
||||
researchType: message.researchType,
|
||||
};
|
||||
return (
|
||||
<div
|
||||
id={`message-${message.nodeId}`}
|
||||
key={messageReactComponentKey}
|
||||
>
|
||||
<AIMessage
|
||||
rawPackets={message.packets}
|
||||
chatState={chatStateData}
|
||||
nodeId={message.nodeId}
|
||||
messageId={message.messageId}
|
||||
currentFeedback={message.currentFeedback}
|
||||
llmManager={llmManager}
|
||||
otherMessagesCanSwitchTo={
|
||||
parentMessage?.childrenNodeIds ?? emptyChildrenIds
|
||||
}
|
||||
onMessageSelection={onMessageSelection}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
{(((error !== null || loadError !== null) &&
|
||||
messages[messages.length - 1]?.type === "user") ||
|
||||
messages[messages.length - 1]?.type === "error") && (
|
||||
<div className="max-w-message-max mx-auto">
|
||||
<ErrorBanner
|
||||
resubmit={handleResubmitLastMessage}
|
||||
error={error || loadError || ""}
|
||||
errorCode={
|
||||
messages[messages.length - 1]?.errorCode || undefined
|
||||
}
|
||||
isRetryable={
|
||||
messages[messages.length - 1]?.isRetryable ?? true
|
||||
}
|
||||
details={
|
||||
messages[messages.length - 1]?.errorDetails || undefined
|
||||
}
|
||||
stackTrace={
|
||||
messages[messages.length - 1]?.stackTrace || undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(((error !== null || loadError !== null) &&
|
||||
messages[messages.length - 1]?.type === "user") ||
|
||||
messages[messages.length - 1]?.type === "error") && (
|
||||
<div className="max-w-message-max mx-auto">
|
||||
<ErrorBanner
|
||||
resubmit={handleResubmitLastMessage}
|
||||
error={error || loadError || ""}
|
||||
errorCode={
|
||||
messages[messages.length - 1]?.errorCode || undefined
|
||||
}
|
||||
isRetryable={messages[messages.length - 1]?.isRetryable ?? true}
|
||||
details={
|
||||
messages[messages.length - 1]?.errorDetails || undefined
|
||||
}
|
||||
stackTrace={
|
||||
messages[messages.length - 1]?.stackTrace || undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={endDivRef} />
|
||||
<div ref={endDivRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user