-
Notifications
You must be signed in to change notification settings - Fork 250
/
MessageContent.tsx
156 lines (148 loc) · 4.43 KB
/
MessageContent.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { Transition } from '@headlessui/react';
import { PropsWithChildren } from 'react';
import { CitationTextHighlighter } from '@/components/Citations/CitationTextHighlighter';
import { DataTable } from '@/components/DataTable';
import { MarkdownImage } from '@/components/MarkdownImage';
import { Icon } from '@/components/Shared';
import { Markdown, Text } from '@/components/Shared';
import { UploadedFile } from '@/components/UploadedFile';
import {
type ChatMessage,
MessageType,
isAbortedMessage,
isErroredMessage,
isFulfilledOrTypingMessage,
isLoadingMessage,
} from '@/types/message';
import { cn } from '@/utils';
type Props = {
isLast: boolean;
message: ChatMessage;
onRetry?: VoidFunction;
};
const BOT_ERROR_MESSAGE = 'Unable to generate a response since an error was encountered. ';
export const MessageContent: React.FC<Props> = ({ isLast, message, onRetry }) => {
const isUser = message.type === MessageType.USER;
const isLoading = isLoadingMessage(message);
const isBotError = isErroredMessage(message);
const isUserError = isUser && message.error;
const isAborted = isAbortedMessage(message);
const isTypingOrFulfilledMessage = isFulfilledOrTypingMessage(message);
let content: React.ReactNode = null;
if (isUserError) {
content = (
<>
<Text>{message.text}</Text>
<MessageInfo type="error">
{message.error}
{isLast && (
<button className="underline underline-offset-1" type="button" onClick={onRetry}>
Retry?
</button>
)}
</MessageInfo>
</>
);
} else if (isUser) {
content = (
<>
<Markdown text={message.text} />
{message.files && message.files.length > 0 && (
<div className="flex flex-wrap gap-2 py-2">
{message.files.map((file) => (
<UploadedFile key={file.id} file={file} />
))}
</div>
)}
</>
);
} else if (isLoading) {
const hasLoadingMessage = message.text.length > 0;
content = (
<Text className={cn('flex min-w-0 text-volcanic-700')} as="span">
{hasLoadingMessage && (
<Transition
appear={true}
show={true}
enterFrom="opacity-0"
enterTo="opacity-full"
enter="transition-opacity ease-in-out duration-500"
>
{message.text}
</Transition>
)}
{!hasLoadingMessage && (
<span className="w-max">
<div className="animate-typing-ellipsis overflow-hidden whitespace-nowrap pr-1">
...
</div>
</span>
)}
</Text>
);
} else if (isBotError) {
content = (
<>
{message.text.length > 0 ? (
<Markdown text={message.text} />
) : (
<Text className={cn('text-volcanic-700')}>{BOT_ERROR_MESSAGE}</Text>
)}
<MessageInfo type="error">{message.error}</MessageInfo>
</>
);
} else {
const hasCitations =
isTypingOrFulfilledMessage && message.citations && message.citations.length > 0;
content = (
<>
<Markdown
className={cn({
'text-volcanic-700': isAborted,
})}
text={message.text}
customComponents={{
img: MarkdownImage as any,
cite: CitationTextHighlighter as any,
table: DataTable as any,
}}
renderLaTex={!hasCitations}
/>
{isAborted && (
<MessageInfo>
This generation was stopped.{' '}
{isLast && isAborted && (
<button className="underline underline-offset-1" type="button" onClick={onRetry}>
Retry?
</button>
)}
</MessageInfo>
)}
</>
);
}
return (
<div className="flex w-full flex-col justify-center gap-y-1 py-1">
<Text
as="div"
className="flex flex-col gap-y-1 whitespace-pre-wrap [overflow-wrap:anywhere] md:max-w-4xl"
>
{content}
</Text>
</div>
);
};
const MessageInfo = ({
type = 'default',
children,
}: PropsWithChildren & { type?: 'default' | 'error' }) => (
<div
className={cn('flex items-start gap-1', {
'text-volcanic-700': type === 'default',
'text-danger-500': type === 'error',
})}
>
<Icon name="warning" size="md" className="flex items-center text-p" />
<Text as="span">{children}</Text>
</div>
);