style: chat message render markdown by default
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { StopOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import React from 'react';
|
||||
import { Button } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Roles } from '../../config';
|
||||
import { MessageItem, MessageItemAction } from '../../config/types';
|
||||
import '../../style/content-item.less';
|
||||
import MessageActions from './message-actions';
|
||||
import MessageBody from './message-body';
|
||||
import ThinkParser from './think-parser';
|
||||
|
||||
interface MessageItemProps {
|
||||
data: MessageItem;
|
||||
@@ -15,10 +20,81 @@ interface MessageItemProps {
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
const contentRenderOptions = [
|
||||
{ label: 'Markdown', value: 'markdown' },
|
||||
{ label: 'Plain Text', value: 'plain' }
|
||||
];
|
||||
interface SaveActionsProps {
|
||||
handleSave: () => void;
|
||||
handleCancel: () => void;
|
||||
}
|
||||
|
||||
interface ThoughtStatusProps {
|
||||
collapsed: boolean;
|
||||
setCollapsed: (collapsed: boolean) => void;
|
||||
isThinking: boolean;
|
||||
}
|
||||
|
||||
const SaveActions: React.FC<SaveActionsProps> = ({
|
||||
handleSave,
|
||||
handleCancel
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div className="save-actions">
|
||||
<div className="actions-wrap gap-8">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<StopOutlined />}
|
||||
onClick={handleCancel}
|
||||
style={{ borderRadius: 'var(--border-radius-base)' }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.button.cancel' })}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<IconFont type="icon-save3" />}
|
||||
onClick={handleSave}
|
||||
style={{ borderRadius: 'var(--border-radius-base)' }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.button.save' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ThoughtStatus: React.FC<ThoughtStatusProps> = ({
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
isThinking
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div
|
||||
className="flex-center"
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
borderRadius: 'var(--border-radius-base)',
|
||||
width: 'max-content',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
className="flex-center"
|
||||
variant="filled"
|
||||
color="default"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
>
|
||||
<span>
|
||||
{isThinking
|
||||
? intl.formatMessage({ id: 'playground.chat.thinking' })
|
||||
: intl.formatMessage({ id: 'playground.chat.aithought' })}
|
||||
</span>
|
||||
<IconFont type="icon-down" rotate={collapsed ? 0 : 180} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ContentItem: React.FC<MessageItemProps> = ({
|
||||
updateMessage,
|
||||
@@ -27,35 +103,141 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
||||
data,
|
||||
editable,
|
||||
showTitle = true,
|
||||
actions = ['upload', 'delete', 'copy']
|
||||
actions = ['upload', 'delete', 'copy', 'edit']
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [showMarkdown, setShowMarkdown] = React.useState(true);
|
||||
const messageRef = React.useRef<any>(null);
|
||||
const thinkerRef = React.useRef<any>(null);
|
||||
const [collapsed, setCollapsed] = React.useState(false);
|
||||
|
||||
const handleOnEdit = () => {
|
||||
setShowMarkdown(false);
|
||||
messageRef.current?.setEditContent(data.content);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
messageRef.current?.handleSaveEdit();
|
||||
setShowMarkdown(true);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
messageRef.current?.handleCancelEdit();
|
||||
setShowMarkdown(true);
|
||||
};
|
||||
|
||||
const handleUpdateMessage = (message: MessageItem) => {
|
||||
updateMessage?.(message);
|
||||
if (data.role === Roles.Assistant) {
|
||||
thinkerRef.current?.reset();
|
||||
}
|
||||
};
|
||||
|
||||
const reasoningContent = useMemo(() => {
|
||||
if (data.role === Roles.User) {
|
||||
return {
|
||||
thought: '',
|
||||
isThinking: false,
|
||||
result: ''
|
||||
};
|
||||
}
|
||||
if (!thinkerRef.current && showMarkdown) {
|
||||
thinkerRef.current = new ThinkParser();
|
||||
}
|
||||
if (showMarkdown) {
|
||||
const res = thinkerRef.current.parse(data.content);
|
||||
return res;
|
||||
}
|
||||
return {
|
||||
thought: '',
|
||||
isThinking: false,
|
||||
result: data.content
|
||||
};
|
||||
}, [data.content, showMarkdown, data.role]);
|
||||
|
||||
const renderTitle = useMemo(() => {
|
||||
if (!showTitle) {
|
||||
return null;
|
||||
}
|
||||
let roleTitle: React.ReactNode = intl.formatMessage({
|
||||
id: `playground.${data.role}`
|
||||
});
|
||||
let avatar = (
|
||||
<IconFont
|
||||
type="icon-user-filled"
|
||||
className="font-size-16 m-r-5 text-tertiary"
|
||||
/>
|
||||
);
|
||||
|
||||
if (data.role === Roles.Assistant) {
|
||||
avatar = (
|
||||
<IconFont
|
||||
type="icon-assistant-filled"
|
||||
className="font-size-16 m-r-5"
|
||||
style={{ color: 'var(--ant-blue-4)' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.title) {
|
||||
roleTitle = data.title;
|
||||
}
|
||||
return (
|
||||
<div className="role flex-center">
|
||||
{avatar}
|
||||
{roleTitle}
|
||||
{data.role === Roles.Assistant && reasoningContent.thought && (
|
||||
<ThoughtStatus
|
||||
isThinking={reasoningContent.isThinking}
|
||||
collapsed={collapsed}
|
||||
setCollapsed={setCollapsed}
|
||||
></ThoughtStatus>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
data.role,
|
||||
data.title,
|
||||
intl,
|
||||
showTitle,
|
||||
reasoningContent.thought,
|
||||
reasoningContent.isThinking,
|
||||
collapsed
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="content-item">
|
||||
<div className="content-item-title">{showTitle && renderTitle}</div>
|
||||
<div
|
||||
className="content-item-role"
|
||||
style={{ display: !showTitle && !actions.length ? 'none' : 'flex' }}
|
||||
style={{ display: !actions.length ? 'none' : 'flex' }}
|
||||
>
|
||||
{showTitle && (
|
||||
<div className="role">
|
||||
{data.title ??
|
||||
intl.formatMessage({ id: `playground.${data.role}` })}
|
||||
</div>
|
||||
{showMarkdown ? (
|
||||
<MessageActions
|
||||
data={data}
|
||||
actions={actions}
|
||||
loading={loading}
|
||||
onDelete={onDelete}
|
||||
onEdit={handleOnEdit}
|
||||
updateMessage={updateMessage}
|
||||
></MessageActions>
|
||||
) : (
|
||||
<SaveActions
|
||||
handleSave={handleSave}
|
||||
handleCancel={handleCancel}
|
||||
></SaveActions>
|
||||
)}
|
||||
<MessageActions
|
||||
data={data}
|
||||
actions={actions}
|
||||
onDelete={onDelete}
|
||||
updateMessage={updateMessage}
|
||||
></MessageActions>
|
||||
</div>
|
||||
<MessageBody
|
||||
ref={messageRef}
|
||||
editable={editable}
|
||||
showMarkdown={showMarkdown}
|
||||
data={data}
|
||||
loading={loading}
|
||||
actions={actions}
|
||||
updateMessage={updateMessage}
|
||||
collapsed={collapsed}
|
||||
reasoningContent={reasoningContent}
|
||||
updateMessage={handleUpdateMessage}
|
||||
></MessageBody>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user