@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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @fe-free/ai
2
2
 
3
+ ## 4.1.11
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: ai
8
+ - @fe-free/core@4.1.11
9
+ - @fe-free/icons@4.1.11
10
+ - @fe-free/tool@4.1.11
11
+
3
12
  ## 4.1.10
4
13
 
5
14
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/ai",
3
- "version": "4.1.10",
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.10"
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.10",
30
- "@fe-free/tool": "4.1.10"
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",
@@ -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 loading =
54
- chatStatus === EnumChatMessageStatus.PENDING || chatStatus === EnumChatMessageStatus.STREAMING;
57
+ const debounceCacheMessages = useDebounce(messages, { wait: 500 });
55
58
 
59
+ // init from cache
56
60
  useEffect(() => {
57
- addMessage({
58
- uuid: generateUUID(),
59
- user: {
60
- text: 'hello',
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
- updateMessage({
104
- ...message,
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 className="h-[800px] w-[500px] border border-red-500">
119
- <Chat
120
- end={
121
- <div
122
- className="p-2"
123
- onFocus={() => {
124
- console.log('onFocus');
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
- onBlur={() => {
127
- console.log('onBlur');
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
- <MSender
131
- value={senderValue}
132
- onChange={(v) => setSenderValue(v)}
133
- loading={loading}
134
- onSubmit={handleSubmit}
135
- />
136
- </div>
137
- }
138
- >
139
- <Messages
140
- messages={messages}
141
- renderMessageOfUser={({ message }) => (
142
- <div className="p-2">
143
- <div className="rounded-xl bg-primary p-2 text-white">{message.user?.text}</div>
144
- </div>
145
- )}
146
- renderMessageOfAI={({ message }) => (
147
- <div className="p-2">
148
- <div>{message.status}</div>
149
- <pre className="whitespace-pre-wrap">{message.ai?.data?.text}</pre>
150
- <div className="flex gap-2">
151
- <MessageActions.Copy value={message.ai?.data?.text || ''} />
152
- <MessageActions.Like
153
- onClick={async () => {
154
- // some thing
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 { ChatMessage } from '../store/types';
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 } = props;
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
- <div key={message.uuid} data-uuid={message.uuid} className="flex flex-col">
58
- <div className="flex justify-end">{renderMessageOfUser?.({ message })}</div>
59
- <div className="flex justify-start">{renderMessageOfAI?.({ message })}</div>
60
- </div>
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
  );
@@ -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 ChatStore<Value, AIData> {
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
  }
@@ -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
- updatedAt?: number;
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 };