@fe-free/ai 4.1.10 → 4.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/package.json +4 -4
- package/src/ai.stories.tsx +101 -77
- package/src/messages/messages.tsx +32 -8
- package/src/store/index.ts +20 -2
- package/src/store/types.ts +15 -2
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fe-free/ai",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.11",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"author": "",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"lodash-es": "^4.17.21",
|
|
16
16
|
"uuid": "^13.0.0",
|
|
17
17
|
"zustand": "^4.5.7",
|
|
18
|
-
"@fe-free/core": "4.1.
|
|
18
|
+
"@fe-free/core": "4.1.11"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@ant-design/x-sdk": "^2.1.3",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"i18next-icu": "^2.4.1",
|
|
27
27
|
"react": "^19.2.0",
|
|
28
28
|
"react-i18next": "^16.4.0",
|
|
29
|
-
"@fe-free/icons": "4.1.
|
|
30
|
-
"@fe-free/tool": "4.1.
|
|
29
|
+
"@fe-free/icons": "4.1.11",
|
|
30
|
+
"@fe-free/tool": "4.1.11"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "echo \"Error: no test specified\" && exit 1",
|
package/src/ai.stories.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Chat,
|
|
4
4
|
createChatStore,
|
|
5
5
|
EnumChatMessageStatus,
|
|
6
|
+
EnumChatMessageType,
|
|
6
7
|
generateUUID,
|
|
7
8
|
MessageActions,
|
|
8
9
|
Messages,
|
|
@@ -10,6 +11,8 @@ import {
|
|
|
10
11
|
} from '@fe-free/ai';
|
|
11
12
|
import { sleep } from '@fe-free/tool';
|
|
12
13
|
import type { Meta } from '@storybook/react-vite';
|
|
14
|
+
import { useDebounce, useUpdateEffect } from 'ahooks';
|
|
15
|
+
import { Button, Divider } from 'antd';
|
|
13
16
|
import { set } from 'lodash-es';
|
|
14
17
|
import { useCallback, useEffect, useMemo } from 'react';
|
|
15
18
|
|
|
@@ -46,37 +49,27 @@ function Component() {
|
|
|
46
49
|
const senderValue = useChatStore((state) => state.senderValue);
|
|
47
50
|
const setSenderValue = useChatStore((state) => state.setSenderValue);
|
|
48
51
|
const messages = useChatStore((state) => state.messages);
|
|
52
|
+
const setMessages = useChatStore((state) => state.setMessages);
|
|
49
53
|
const addMessage = useChatStore((state) => state.addMessage);
|
|
50
54
|
const updateMessage = useChatStore((state) => state.updateMessage);
|
|
51
55
|
const { chatStatus } = useChatStoreComputed();
|
|
52
56
|
|
|
53
|
-
const
|
|
54
|
-
chatStatus === EnumChatMessageStatus.PENDING || chatStatus === EnumChatMessageStatus.STREAMING;
|
|
57
|
+
const debounceCacheMessages = useDebounce(messages, { wait: 500 });
|
|
55
58
|
|
|
59
|
+
// init from cache
|
|
56
60
|
useEffect(() => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
ai: {
|
|
63
|
-
data: {
|
|
64
|
-
text: '你好,\n我是AI,很高兴认识你',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
addMessage({
|
|
69
|
-
uuid: generateUUID(),
|
|
70
|
-
user: {
|
|
71
|
-
text: 'hello',
|
|
72
|
-
},
|
|
73
|
-
ai: {
|
|
74
|
-
data: {
|
|
75
|
-
text: '你\n好,\n我\n是\nAI,\n很\n高\n兴\n认\n识\n你\n你\n好,\n我\n是\nAI,\n很\n高\n兴\n认\n识\n你\n很\n高\n兴\n认\n识\n你\n',
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
});
|
|
61
|
+
const cacheMessages = localStorage.getItem('chatMessages');
|
|
62
|
+
if (cacheMessages) {
|
|
63
|
+
setMessages(JSON.parse(cacheMessages));
|
|
64
|
+
}
|
|
79
65
|
}, []);
|
|
66
|
+
// cache
|
|
67
|
+
useUpdateEffect(() => {
|
|
68
|
+
localStorage.setItem('chatMessages', JSON.stringify(debounceCacheMessages));
|
|
69
|
+
}, [debounceCacheMessages]);
|
|
70
|
+
|
|
71
|
+
const loading =
|
|
72
|
+
chatStatus === EnumChatMessageStatus.PENDING || chatStatus === EnumChatMessageStatus.STREAMING;
|
|
80
73
|
|
|
81
74
|
const handleSubmit = useCallback((v) => {
|
|
82
75
|
console.log('onSubmit', v);
|
|
@@ -100,70 +93,101 @@ function Component() {
|
|
|
100
93
|
const preText = message.ai?.data?.text || '';
|
|
101
94
|
set(message, 'ai.data.text', preText + data);
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
// 假设有 session_id
|
|
97
|
+
set(message, 'ai.session_id', '123');
|
|
98
|
+
|
|
99
|
+
updateMessage(message);
|
|
106
100
|
}
|
|
107
101
|
if (event === 'done') {
|
|
108
102
|
message.status = EnumChatMessageStatus.DONE;
|
|
109
|
-
updateMessage(
|
|
110
|
-
...message,
|
|
111
|
-
});
|
|
103
|
+
updateMessage(message);
|
|
112
104
|
}
|
|
113
105
|
},
|
|
114
106
|
});
|
|
115
107
|
}, []);
|
|
116
108
|
|
|
117
109
|
return (
|
|
118
|
-
<div
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
110
|
+
<div>
|
|
111
|
+
<div>
|
|
112
|
+
<Button
|
|
113
|
+
onClick={() => {
|
|
114
|
+
addMessage({
|
|
115
|
+
uuid: generateUUID(),
|
|
116
|
+
type: EnumChatMessageType.SYSTEM,
|
|
117
|
+
system: {
|
|
118
|
+
data: {
|
|
119
|
+
type: 'new_session',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
Add New Session
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="h-[800px] w-[500px] border border-red-500">
|
|
129
|
+
<Chat
|
|
130
|
+
end={
|
|
131
|
+
<div
|
|
132
|
+
className="p-2"
|
|
133
|
+
onFocus={() => {
|
|
134
|
+
console.log('onFocus');
|
|
135
|
+
}}
|
|
136
|
+
onBlur={() => {
|
|
137
|
+
console.log('onBlur');
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<MSender
|
|
141
|
+
value={senderValue}
|
|
142
|
+
onChange={(v) => setSenderValue(v)}
|
|
143
|
+
loading={loading}
|
|
144
|
+
onSubmit={handleSubmit}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
}
|
|
148
|
+
>
|
|
149
|
+
<Messages
|
|
150
|
+
messages={messages}
|
|
151
|
+
renderMessageOfSystem={({ message }) => {
|
|
152
|
+
if (message.system?.data?.type === 'new_session') {
|
|
153
|
+
return <Divider>让我们聊点新内容吧</Divider>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
125
157
|
}}
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
renderMessageOfUser={({ message }) => {
|
|
159
|
+
return (
|
|
160
|
+
<div className="p-2">
|
|
161
|
+
<div className="rounded-xl bg-primary p-2 text-white">{message.user?.text}</div>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
128
164
|
}}
|
|
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
|
-
/>
|
|
157
|
-
<MessageActions.Dislike
|
|
158
|
-
onClick={async () => {
|
|
159
|
-
// some thing
|
|
160
|
-
}}
|
|
161
|
-
/>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
)}
|
|
165
|
-
/>
|
|
166
|
-
</Chat>
|
|
165
|
+
renderMessageOfAI={({ message }) => {
|
|
166
|
+
return (
|
|
167
|
+
<div className="p-2">
|
|
168
|
+
<div>
|
|
169
|
+
status: {message.status} session_id: {message.ai?.session_id}
|
|
170
|
+
</div>
|
|
171
|
+
<pre className="whitespace-pre-wrap">{message.ai?.data?.text}</pre>
|
|
172
|
+
<div className="flex gap-2">
|
|
173
|
+
<MessageActions.Copy value={message.ai?.data?.text || ''} />
|
|
174
|
+
<MessageActions.Like
|
|
175
|
+
onClick={async () => {
|
|
176
|
+
// some thing
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
179
|
+
<MessageActions.Dislike
|
|
180
|
+
onClick={async () => {
|
|
181
|
+
// some thing
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
</Chat>
|
|
190
|
+
</div>
|
|
167
191
|
</div>
|
|
168
192
|
);
|
|
169
193
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { PageLayout } from '@fe-free/core';
|
|
2
2
|
import { useEffect, useMemo, useRef } from 'react';
|
|
3
|
-
import type
|
|
3
|
+
import { EnumChatMessageType, type ChatMessage } from '../store/types';
|
|
4
4
|
|
|
5
5
|
interface MessagesProps<AIData> {
|
|
6
6
|
messages?: ChatMessage<AIData>[];
|
|
7
|
+
/** 含所有 */
|
|
8
|
+
renderMessage?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
|
|
9
|
+
/** 系统消息 */
|
|
10
|
+
renderMessageOfSystem?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
|
|
11
|
+
/** 用户消息 */
|
|
7
12
|
renderMessageOfUser?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
|
|
13
|
+
/** AI消息 */
|
|
8
14
|
renderMessageOfAI?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
12
|
-
const { messages, renderMessageOfUser, renderMessageOfAI } =
|
|
18
|
+
const { messages, renderMessage, renderMessageOfSystem, renderMessageOfUser, renderMessageOfAI } =
|
|
19
|
+
props;
|
|
13
20
|
|
|
14
21
|
const ref = useRef<HTMLDivElement>(null);
|
|
15
22
|
|
|
@@ -53,12 +60,29 @@ function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
|
53
60
|
return (
|
|
54
61
|
<PageLayout>
|
|
55
62
|
<div ref={ref} className="flex h-full flex-col overflow-y-auto">
|
|
56
|
-
{messages?.map((message) =>
|
|
57
|
-
|
|
58
|
-
<div className="flex
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
{messages?.map((message) => {
|
|
64
|
+
return (
|
|
65
|
+
<div key={message.uuid} data-uuid={message.uuid} className="flex flex-col">
|
|
66
|
+
{renderMessage ? (
|
|
67
|
+
renderMessage?.({ message })
|
|
68
|
+
) : (
|
|
69
|
+
<>
|
|
70
|
+
{message.type === EnumChatMessageType.SYSTEM && message.system && (
|
|
71
|
+
<div className="flex justify-center">
|
|
72
|
+
{renderMessageOfSystem?.({ message })}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
{message.type !== EnumChatMessageType.SYSTEM && message.user && (
|
|
76
|
+
<div className="flex justify-end">{renderMessageOfUser?.({ message })}</div>
|
|
77
|
+
)}
|
|
78
|
+
{message.type !== EnumChatMessageType.SYSTEM && message.ai && (
|
|
79
|
+
<div className="flex justify-start">{renderMessageOfAI?.({ message })}</div>
|
|
80
|
+
)}
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
62
86
|
</div>
|
|
63
87
|
</PageLayout>
|
|
64
88
|
);
|
package/src/store/index.ts
CHANGED
|
@@ -2,30 +2,47 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import { create } from 'zustand';
|
|
3
3
|
import type { ChatMessage } from './types';
|
|
4
4
|
|
|
5
|
-
interface
|
|
5
|
+
interface BaseSenderValue {
|
|
6
|
+
text?: string;
|
|
7
|
+
files?: string[];
|
|
8
|
+
}
|
|
9
|
+
interface ChatStore<Value extends BaseSenderValue | undefined, AIData> {
|
|
6
10
|
senderValue?: Value;
|
|
7
11
|
setSenderValue: (senderValue?: Value) => void;
|
|
8
12
|
|
|
9
13
|
messages: ChatMessage<AIData>[];
|
|
14
|
+
setMessages: (messages: ChatMessage<AIData>[]) => void;
|
|
10
15
|
addMessage: (message: ChatMessage<AIData>) => void;
|
|
11
16
|
updateMessage: (message: ChatMessage<AIData>) => void;
|
|
12
17
|
|
|
13
18
|
reset: () => void;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
function createChatStore<Value, AIData>() {
|
|
21
|
+
function createChatStore<Value extends BaseSenderValue | undefined, AIData>() {
|
|
17
22
|
const useChatStore = create<ChatStore<Value, AIData>>((set, get, store) => ({
|
|
18
23
|
senderValue: undefined,
|
|
19
24
|
setSenderValue: (senderValue) => {
|
|
20
25
|
set(() => ({ senderValue }));
|
|
21
26
|
},
|
|
22
27
|
messages: [],
|
|
28
|
+
setMessages: (messages) => {
|
|
29
|
+
set(() => ({
|
|
30
|
+
messages: messages.map((message) => ({
|
|
31
|
+
// 如果没有,则用当前的时间
|
|
32
|
+
createdAt: Date.now(),
|
|
33
|
+
updatedAt: Date.now(),
|
|
34
|
+
...message,
|
|
35
|
+
})),
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
23
38
|
addMessage: (message) => {
|
|
24
39
|
set((state) => ({
|
|
25
40
|
messages: [
|
|
26
41
|
...state.messages,
|
|
27
42
|
{
|
|
28
43
|
...message,
|
|
44
|
+
// 覆盖
|
|
45
|
+
createdAt: Date.now(),
|
|
29
46
|
updatedAt: Date.now(),
|
|
30
47
|
},
|
|
31
48
|
],
|
|
@@ -37,6 +54,7 @@ function createChatStore<Value, AIData>() {
|
|
|
37
54
|
if (m.uuid === message.uuid) {
|
|
38
55
|
return {
|
|
39
56
|
...message,
|
|
57
|
+
// 覆盖
|
|
40
58
|
updatedAt: Date.now(),
|
|
41
59
|
};
|
|
42
60
|
}
|
package/src/store/types.ts
CHANGED
|
@@ -9,6 +9,10 @@ enum EnumChatMessageStatus {
|
|
|
9
9
|
ERROR = 'error',
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface ChatMessageOfSystem {
|
|
13
|
+
data?: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
interface ChatMessageOfUser {
|
|
13
17
|
text?: string;
|
|
14
18
|
files?: string[];
|
|
@@ -16,15 +20,24 @@ interface ChatMessageOfUser {
|
|
|
16
20
|
|
|
17
21
|
interface ChatMessageOfAI<AIData> {
|
|
18
22
|
data?: AIData;
|
|
23
|
+
/** 按需存取 */
|
|
24
|
+
session_id?: string;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
interface ChatMessage<AIData> {
|
|
22
28
|
uuid: string;
|
|
23
|
-
|
|
24
|
-
type?: EnumChatMessageType;
|
|
29
|
+
|
|
25
30
|
status?: EnumChatMessageStatus;
|
|
31
|
+
|
|
32
|
+
type?: EnumChatMessageType;
|
|
33
|
+
system?: ChatMessageOfSystem;
|
|
26
34
|
user?: ChatMessageOfUser;
|
|
27
35
|
ai?: ChatMessageOfAI<AIData>;
|
|
36
|
+
|
|
37
|
+
/** 自动生成 */
|
|
38
|
+
createdAt?: number;
|
|
39
|
+
/** 自动更新 */
|
|
40
|
+
updatedAt?: number;
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
export { EnumChatMessageStatus, EnumChatMessageType };
|