Skip to content

Commit 3f5e3a8

Browse files
committed
同对话支持多智能体
1 parent 4244e5e commit 3f5e3a8

File tree

6 files changed

+102
-35
lines changed

6 files changed

+102
-35
lines changed

src/components/chat/chat-input/index.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import {
2121
Divider,
2222
Flex,
2323
Space,
24+
Tag,
2425
Tooltip,
2526
Typography,
2627
} from 'antd';
2728
import _ from 'lodash';
2829
import {
29-
ArrowBigUp,
3030
AtSign,
3131
Command,
3232
CornerDownLeft,
@@ -40,6 +40,7 @@ import {
4040

4141
import type { MESSAGE } from '@/services/message/typings';
4242
import LexicalTextarea, { LexicalTextareaRefProperty } from '@/components/markdown/lexical/lexical-textarea';
43+
import { BOT } from '@/services/bot/typings';
4344

4445
const ChatInput: React.FC<{
4546
onSubmit?: (values: MESSAGE.GenerateCmd) => void;
@@ -49,10 +50,12 @@ const ChatInput: React.FC<{
4950
className?: string | undefined;
5051
style?: CSSProperties | undefined;
5152
}> = ({ onSubmit, onClearMemory, onStop, loading, className, style }) => {
52-
const lexicalTextareaRef = useRef<LexicalTextareaRefProperty>();
53+
const lexicalTextareaRef = useRef<LexicalTextareaRefProperty>(null);
5354
const [query, setQuery] = useState('');
5455

55-
const [clearLoading, setClearLoading] = useState(false);
56+
const [mentions, setMentions] = useState<BOT.BotEntity>();
57+
58+
const [clearMemoryLoading, setClearMemoryLoading] = useState(false);
5659
const [stopLoading, setStopLoading] = useState(false);
5760

5861
const [inputExpand, setInputExpand] = useState(false);
@@ -61,6 +64,7 @@ const ChatInput: React.FC<{
6164
const content = markdown || query;
6265
if (!_.isEmpty(_.trim(content))) {
6366
onSubmit?.({
67+
mentions: mentions?.uid ? [mentions.uid] : undefined,
6468
query: {
6569
inputs: [
6670
{
@@ -96,9 +100,16 @@ const ChatInput: React.FC<{
96100
align="flex-start"
97101
style={style}
98102
className={`flex-auto rounded-xl bg-white py-4 w-full min-h-24`}
103+
gap={12}
99104
>
105+
{/* @BOT */}
106+
{mentions && <Flex justify="flex-start" align="center" className="w-full px-4" gap={4}>
107+
<Tag bordered={false} closeIcon onClose={() => setMentions(undefined)}>@ {mentions.name}</Tag>
108+
</Flex>}
109+
110+
{/* 输入框 */}
100111
<div
101-
className={`w-full min-h-12 px-5 mb-3 overflow-y-scroll transition-all duration-500 ${inputExpand ? 'h-96' : 'max-h-40'}`}
112+
className={`w-full min-h-12 px-5 overflow-y-scroll transition-all duration-500 ${inputExpand ? 'h-96' : 'max-h-40'}`}
102113
onKeyDown={(event) => {
103114
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
104115
event.preventDefault();
@@ -117,9 +128,7 @@ const ChatInput: React.FC<{
117128
botMentionOptions={{
118129
enable: true,
119130
trigger: '@',
120-
onSelect: (botFavorite) => {
121-
console.log('===1===', botFavorite);
122-
},
131+
onSelect: setMentions,
123132
}}
124133
/>
125134
</div>
@@ -133,12 +142,12 @@ const ChatInput: React.FC<{
133142
type="text"
134143
className="p-1"
135144
disabled={loading}
136-
loading={clearLoading}
145+
loading={clearMemoryLoading}
137146
icon={<SquareSplitVertical size={18} />}
138147
onClick={() => {
139148
if (!!onClearMemory) {
140-
setClearLoading(true);
141-
onClearMemory().finally(() => setClearLoading(false));
149+
setClearMemoryLoading(true);
150+
onClearMemory().finally(() => setClearMemoryLoading(false));
142151
}
143152
}}
144153
/>

src/components/chat/chat-item/index.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import ChatMarkdown from './chat-markdown';
2323
import _ from 'lodash';
2424
import styles from './styles.module.scss';
2525
import { LoadingOutlined } from '@ant-design/icons';
26+
import { FluentEmoji, FluentEmojiProps, useControls, useCreateStore } from '@lobehub/ui';
2627

2728
const ChatItem: React.FC<{
2829
message: MESSAGE.MessageContent;
@@ -34,6 +35,20 @@ const ChatItem: React.FC<{
3435
const { initialState } = useModel('@@initialState');
3536
const emptyMessage = _.isEmpty(message?.messages) || _.every(message?.messages, msg => _.isEmpty(msg.content));
3637

38+
const store = useCreateStore();
39+
const control: FluentEmojiProps = useControls(
40+
{
41+
emoji: '🫠',
42+
size: {
43+
max: 128,
44+
min: 16,
45+
step: 1,
46+
value: 32,
47+
},
48+
},
49+
{ store },
50+
);
51+
3752
return (
3853
<>
3954
{message.sender_role === 'system' && (
@@ -79,8 +94,9 @@ const ChatItem: React.FC<{
7994
icon={{
8095
...(message.sender_role === 'user' ? (
8196
<Avatar size={32} className={`bg-user-msg font-bold`}>{initialState?.userMe?.name[0]}</Avatar>
82-
) : (
83-
<img src={'/logo.png'} alt="MODU 墨读无界" />
97+
) : (!!message.sender_info
98+
? <Avatar size={32} icon={<FluentEmoji type={'anim'} {...control} />} />
99+
: <img src={'/logo.png'} alt="MODU 墨读无界" />
84100
)),
85101
}}
86102
/>
@@ -100,9 +116,10 @@ const ChatItem: React.FC<{
100116
className="w-full mb-2 box-border"
101117
>
102118
<Typography.Text type="secondary">
103-
{message.sender_role === 'user'
119+
{message.sender_info?.name}
120+
{!!message.sender_info?.name || (message.sender_role === 'user'
104121
? initialState?.userMe?.name
105-
: 'Assistant'}
122+
: '智能助手')}
106123
{message.sender_role !== 'user' && (
107124
<Typography.Text
108125
type="secondary"

src/components/chat/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ const ChatContent: React.FC<{
150150
const parseMessageEvent = messageParser({
151151
sender_uid: initialState?.userMe?.uid,
152152
sender_role: 'user',
153+
sender_info: {
154+
uid: initialState?.userMe?.uid,
155+
name: initialState?.userMe?.name || '用户',
156+
avatar: initialState?.userMe?.avatar,
157+
role: 'user',
158+
},
153159
message_uid: ulid(),
154160
message_time: new Date().getTime(),
155161
messages: [

src/components/markdown/lexical/lexical-textarea.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ export interface LexicalTextareaRefProperty {
4040
/**
4141
* 需要保证该组件无状态
4242
*/
43-
const LexicalTextarea: React.FC<{
43+
const LexicalTextarea = forwardRef<LexicalTextareaRefProperty, {
4444
placeholder?: string,
4545
readOnly?: boolean,
4646
defaultValue?: string,
4747
showToolbar?: boolean,
4848
onChange?: (markdown: string) => void,
4949
botMentionOptions?: BotMentionOptions,
50-
}> = forwardRef(({ placeholder, readOnly, defaultValue, showToolbar, onChange, botMentionOptions }, ref) => {
50+
}>(({ placeholder, readOnly, defaultValue, showToolbar, onChange, botMentionOptions }, ref) => {
5151
const lexicalInnerEditorPopoverRef = useRef<LexicalInnerEditorRefProperty>(null);
5252

5353
useImperativeHandle(ref, () => ({
@@ -57,7 +57,7 @@ const LexicalTextarea: React.FC<{
5757
editor?.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined);
5858
},
5959
getMarkdownContent() {
60-
return lexicalInnerEditorPopoverRef.current?.getMarkdownContent();
60+
return lexicalInnerEditorPopoverRef.current?.getMarkdownContent() || '';
6161
}
6262
}));
6363

@@ -67,23 +67,23 @@ const LexicalTextarea: React.FC<{
6767
if (!readOnly) {
6868
editor?.focus(
6969
() => {
70-
// See AutoFocusPlugin
71-
// If we try and move selection to the same point with setBaseAndExtent, it won't
72-
// trigger a re-focus on the element. So in the case this occurs, we'll need to correct it.
73-
// Normally this is fine, Selection API !== Focus API, but fore the intents of the naming
74-
// of this plugin, which should preserve focus too.
75-
const activeElement = document.activeElement;
76-
const rootElement = editor.getRootElement() as HTMLDivElement;
77-
if (
78-
rootElement !== null &&
79-
(activeElement === null || !rootElement.contains(activeElement))
80-
) {
81-
// Note: preventScroll won't work in Webkit.
82-
rootElement.focus({preventScroll: true});
83-
}
70+
// See AutoFocusPlugin
71+
// If we try and move selection to the same point with setBaseAndExtent, it won't
72+
// trigger a re-focus on the element. So in the case this occurs, we'll need to correct it.
73+
// Normally this is fine, Selection API !== Focus API, but fore the intents of the naming
74+
// of this plugin, which should preserve focus too.
75+
const activeElement = document.activeElement;
76+
const rootElement = editor.getRootElement() as HTMLDivElement;
77+
if (
78+
rootElement !== null &&
79+
(activeElement === null || !rootElement.contains(activeElement))
80+
) {
81+
// Note: preventScroll won't work in Webkit.
82+
rootElement.focus({ preventScroll: true });
83+
}
8484
},
85-
{defaultSelection: 'rootStart'}
86-
);
85+
{ defaultSelection: 'rootStart' }
86+
);
8787
}
8888
}, [readOnly]);
8989

src/services/message/parser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function messageParser({
5757
currentMessage = {
5858
sender_uid: messageEvent.sender_uid,
5959
sender_role: messageEvent.sender_role,
60+
sender_info: messageEvent.sender_info,
6061
message_uid: messageEvent.message_uid,
6162
message_time: messageEvent.message_time,
6263
messages: [messageEvent.message],

src/services/message/typings.d.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import '@interface/typings';
1818

1919
declare namespace MESSAGE {
20+
type SenderRole = "user" | "assistant" | "system";
21+
2022
type Item = {
2123
/**
2224
* 类型
@@ -80,6 +82,28 @@ declare namespace MESSAGE {
8082
section_uid: string;
8183
}
8284

85+
type SenderInfo = {
86+
/**
87+
* 发送者 ID
88+
*/
89+
uid: string;
90+
91+
/**
92+
* 发送者名称
93+
*/
94+
name: string;
95+
96+
/**
97+
* 发送者头像
98+
*/
99+
avatar?: string;
100+
101+
/**
102+
* 发送者角色
103+
*/
104+
role: string;
105+
}
106+
83107
type MessageEvent = {
84108
/**
85109
* 会话 ID
@@ -94,7 +118,12 @@ declare namespace MESSAGE {
94118
/**
95119
* 发送者角色
96120
*/
97-
sender_role: "user" | "assistant" | "system";
121+
sender_role: SenderRole;
122+
123+
/**
124+
* 发送者信息
125+
*/
126+
sender_info?: SenderInfo;
98127

99128
/**
100129
* 消息 ID
@@ -126,7 +155,12 @@ declare namespace MESSAGE {
126155
/**
127156
* 发送者角色
128157
*/
129-
sender_role: "user" | "assistant" | "system";
158+
sender_role: SenderRole;
159+
160+
/**
161+
* 发送者信息
162+
*/
163+
sender_info?: SenderInfo;
130164

131165
/**
132166
* 消息 ID

0 commit comments

Comments
 (0)