balibabu
commited on
Commit
·
cb33b9e
1
Parent(s):
028fe40
feat: Support for conversational streaming (#809)
Browse files### What problem does this PR solve?
feat: Support for conversational streaming
#709
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/.env +1 -0
- web/package-lock.json +9 -0
- web/package.json +2 -1
- web/src/components/new-document-link.tsx +1 -1
- web/src/hooks/chatHooks.ts +3 -16
- web/src/hooks/logicHooks.ts +52 -53
- web/src/interfaces/database/chat.ts +5 -0
- web/src/locales/en.ts +2 -0
- web/src/locales/zh-traditional.ts +2 -0
- web/src/locales/zh.ts +2 -0
- web/src/pages/chat/chat-container/index.tsx +32 -29
- web/src/pages/chat/hooks.ts +91 -40
- web/src/pages/chat/interface.ts +2 -1
- web/src/pages/chat/markdown-content/index.less +20 -0
- web/src/pages/chat/markdown-content/index.tsx +9 -2
- web/src/pages/chat/share/index.tsx +1 -41
- web/src/pages/chat/share/large.tsx +105 -30
- web/src/pages/chat/shared-hooks.ts +48 -19
- web/src/pages/chat/utils.ts +20 -1
- web/src/utils/authorizationUtil.ts +11 -2
- web/src/utils/request.ts +8 -21
web/.env
CHANGED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
PORT=9222
|
web/package-lock.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
| 15 |
"axios": "^1.6.3",
|
| 16 |
"classnames": "^2.5.1",
|
| 17 |
"dayjs": "^1.11.10",
|
|
|
|
| 18 |
"i18next": "^23.7.16",
|
| 19 |
"js-base64": "^3.7.5",
|
| 20 |
"jsencrypt": "^3.3.2",
|
|
@@ -10206,6 +10207,14 @@
|
|
| 10206 |
"node": ">=0.8.x"
|
| 10207 |
}
|
| 10208 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10209 |
"node_modules/evp_bytestokey": {
|
| 10210 |
"version": "1.0.3",
|
| 10211 |
"resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
|
|
|
| 15 |
"axios": "^1.6.3",
|
| 16 |
"classnames": "^2.5.1",
|
| 17 |
"dayjs": "^1.11.10",
|
| 18 |
+
"eventsource-parser": "^1.1.2",
|
| 19 |
"i18next": "^23.7.16",
|
| 20 |
"js-base64": "^3.7.5",
|
| 21 |
"jsencrypt": "^3.3.2",
|
|
|
|
| 10207 |
"node": ">=0.8.x"
|
| 10208 |
}
|
| 10209 |
},
|
| 10210 |
+
"node_modules/eventsource-parser": {
|
| 10211 |
+
"version": "1.1.2",
|
| 10212 |
+
"resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
|
| 10213 |
+
"integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
|
| 10214 |
+
"engines": {
|
| 10215 |
+
"node": ">=14.18"
|
| 10216 |
+
}
|
| 10217 |
+
},
|
| 10218 |
"node_modules/evp_bytestokey": {
|
| 10219 |
"version": "1.0.3",
|
| 10220 |
"resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
web/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
"author": "zhaofengchao <13723060510@163.com>",
|
| 4 |
"scripts": {
|
| 5 |
"build": "umi build",
|
| 6 |
-
"dev": "cross-env
|
| 7 |
"postinstall": "umi setup",
|
| 8 |
"lint": "umi lint --eslint-only",
|
| 9 |
"setup": "umi setup",
|
|
@@ -19,6 +19,7 @@
|
|
| 19 |
"axios": "^1.6.3",
|
| 20 |
"classnames": "^2.5.1",
|
| 21 |
"dayjs": "^1.11.10",
|
|
|
|
| 22 |
"i18next": "^23.7.16",
|
| 23 |
"js-base64": "^3.7.5",
|
| 24 |
"jsencrypt": "^3.3.2",
|
|
|
|
| 3 |
"author": "zhaofengchao <13723060510@163.com>",
|
| 4 |
"scripts": {
|
| 5 |
"build": "umi build",
|
| 6 |
+
"dev": "cross-env UMI_DEV_SERVER_COMPRESS=none umi dev",
|
| 7 |
"postinstall": "umi setup",
|
| 8 |
"lint": "umi lint --eslint-only",
|
| 9 |
"setup": "umi setup",
|
|
|
|
| 19 |
"axios": "^1.6.3",
|
| 20 |
"classnames": "^2.5.1",
|
| 21 |
"dayjs": "^1.11.10",
|
| 22 |
+
"eventsource-parser": "^1.1.2",
|
| 23 |
"i18next": "^23.7.16",
|
| 24 |
"js-base64": "^3.7.5",
|
| 25 |
"jsencrypt": "^3.3.2",
|
web/src/components/new-document-link.tsx
CHANGED
|
@@ -18,7 +18,7 @@ const NewDocumentLink = ({
|
|
| 18 |
onClick={!preventDefault ? undefined : (e) => e.preventDefault()}
|
| 19 |
href={link}
|
| 20 |
rel="noreferrer"
|
| 21 |
-
style={{ color }}
|
| 22 |
>
|
| 23 |
{children}
|
| 24 |
</a>
|
|
|
|
| 18 |
onClick={!preventDefault ? undefined : (e) => e.preventDefault()}
|
| 19 |
href={link}
|
| 20 |
rel="noreferrer"
|
| 21 |
+
style={{ color, wordBreak: 'break-all' }}
|
| 22 |
>
|
| 23 |
{children}
|
| 24 |
</a>
|
web/src/hooks/chatHooks.ts
CHANGED
|
@@ -154,6 +154,9 @@ export const useRemoveConversation = () => {
|
|
| 154 |
return removeConversation;
|
| 155 |
};
|
| 156 |
|
|
|
|
|
|
|
|
|
|
| 157 |
export const useCompleteConversation = () => {
|
| 158 |
const dispatch = useDispatch();
|
| 159 |
|
|
@@ -283,20 +286,4 @@ export const useFetchSharedConversation = () => {
|
|
| 283 |
return fetchSharedConversation;
|
| 284 |
};
|
| 285 |
|
| 286 |
-
export const useCompleteSharedConversation = () => {
|
| 287 |
-
const dispatch = useDispatch();
|
| 288 |
-
|
| 289 |
-
const completeSharedConversation = useCallback(
|
| 290 |
-
(payload: any) => {
|
| 291 |
-
return dispatch<any>({
|
| 292 |
-
type: 'chatModel/completeExternalConversation',
|
| 293 |
-
payload: payload,
|
| 294 |
-
});
|
| 295 |
-
},
|
| 296 |
-
[dispatch],
|
| 297 |
-
);
|
| 298 |
-
|
| 299 |
-
return completeSharedConversation;
|
| 300 |
-
};
|
| 301 |
-
|
| 302 |
//#endregion
|
|
|
|
| 154 |
return removeConversation;
|
| 155 |
};
|
| 156 |
|
| 157 |
+
/*
|
| 158 |
+
@deprecated
|
| 159 |
+
*/
|
| 160 |
export const useCompleteConversation = () => {
|
| 161 |
const dispatch = useDispatch();
|
| 162 |
|
|
|
|
| 286 |
return fetchSharedConversation;
|
| 287 |
};
|
| 288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
//#endregion
|
web/src/hooks/logicHooks.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
| 1 |
import { Authorization } from '@/constants/authorization';
|
| 2 |
import { LanguageTranslationMap } from '@/constants/common';
|
| 3 |
import { Pagination } from '@/interfaces/common';
|
|
|
|
| 4 |
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
| 5 |
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
| 6 |
import api from '@/utils/api';
|
| 7 |
-
import
|
| 8 |
-
import { getSearchValue } from '@/utils/commonUtil';
|
| 9 |
import { PaginationProps } from 'antd';
|
| 10 |
import axios from 'axios';
|
|
|
|
| 11 |
import { useCallback, useEffect, useMemo, useState } from 'react';
|
| 12 |
import { useTranslation } from 'react-i18next';
|
| 13 |
import { useDispatch } from 'umi';
|
|
@@ -138,62 +139,60 @@ export const useFetchAppConf = () => {
|
|
| 138 |
return appConf;
|
| 139 |
};
|
| 140 |
|
| 141 |
-
export const
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
const
|
| 145 |
-
|
| 146 |
-
url || '/sse/createSseEmitter?clientId=123456',
|
| 147 |
-
);
|
| 148 |
-
|
| 149 |
-
source.onopen = function () {
|
| 150 |
-
console.log('Connection to the server was opened.');
|
| 151 |
-
};
|
| 152 |
-
|
| 153 |
-
source.onmessage = function (event: any) {
|
| 154 |
-
setContent(event.data);
|
| 155 |
-
};
|
| 156 |
-
|
| 157 |
-
source.onerror = function (error) {
|
| 158 |
-
console.error('Error occurred:', error);
|
| 159 |
-
};
|
| 160 |
-
}, [url]);
|
| 161 |
-
|
| 162 |
-
return { connect, content };
|
| 163 |
-
};
|
| 164 |
|
| 165 |
-
export const useConnectWithSseNext = () => {
|
| 166 |
-
const [content, setContent] = useState<string>('');
|
| 167 |
-
const sharedId = getSearchValue('shared_id');
|
| 168 |
-
const authorization = sharedId
|
| 169 |
-
? 'Bearer ' + sharedId
|
| 170 |
-
: authorizationUtil.getAuthorization();
|
| 171 |
const send = useCallback(
|
| 172 |
async (body: any) => {
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
}
|
| 193 |
-
return response;
|
| 194 |
},
|
| 195 |
-
[
|
| 196 |
);
|
| 197 |
|
| 198 |
-
return { send,
|
| 199 |
};
|
|
|
|
| 1 |
import { Authorization } from '@/constants/authorization';
|
| 2 |
import { LanguageTranslationMap } from '@/constants/common';
|
| 3 |
import { Pagination } from '@/interfaces/common';
|
| 4 |
+
import { IAnswer } from '@/interfaces/database/chat';
|
| 5 |
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
| 6 |
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
| 7 |
import api from '@/utils/api';
|
| 8 |
+
import { getAuthorization } from '@/utils/authorizationUtil';
|
|
|
|
| 9 |
import { PaginationProps } from 'antd';
|
| 10 |
import axios from 'axios';
|
| 11 |
+
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
| 12 |
import { useCallback, useEffect, useMemo, useState } from 'react';
|
| 13 |
import { useTranslation } from 'react-i18next';
|
| 14 |
import { useDispatch } from 'umi';
|
|
|
|
| 139 |
return appConf;
|
| 140 |
};
|
| 141 |
|
| 142 |
+
export const useSendMessageWithSse = (
|
| 143 |
+
url: string = api.completeConversation,
|
| 144 |
+
) => {
|
| 145 |
+
const [answer, setAnswer] = useState<IAnswer>({} as IAnswer);
|
| 146 |
+
const [done, setDone] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
const send = useCallback(
|
| 149 |
async (body: any) => {
|
| 150 |
+
try {
|
| 151 |
+
setDone(false);
|
| 152 |
+
const response = await fetch(url, {
|
| 153 |
+
method: 'POST',
|
| 154 |
+
headers: {
|
| 155 |
+
[Authorization]: getAuthorization(),
|
| 156 |
+
'Content-Type': 'application/json',
|
| 157 |
+
},
|
| 158 |
+
body: JSON.stringify(body),
|
| 159 |
+
});
|
| 160 |
+
|
| 161 |
+
const reader = response?.body
|
| 162 |
+
?.pipeThrough(new TextDecoderStream())
|
| 163 |
+
.pipeThrough(new EventSourceParserStream())
|
| 164 |
+
.getReader();
|
| 165 |
+
|
| 166 |
+
while (true) {
|
| 167 |
+
const x = await reader?.read();
|
| 168 |
+
if (x) {
|
| 169 |
+
const { done, value } = x;
|
| 170 |
+
try {
|
| 171 |
+
const val = JSON.parse(value?.data || '');
|
| 172 |
+
const d = val?.data;
|
| 173 |
+
if (typeof d !== 'boolean') {
|
| 174 |
+
console.info('data:', d);
|
| 175 |
+
setAnswer(d);
|
| 176 |
+
}
|
| 177 |
+
} catch (e) {
|
| 178 |
+
console.warn(e);
|
| 179 |
+
}
|
| 180 |
+
if (done) {
|
| 181 |
+
console.info('done');
|
| 182 |
+
break;
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
console.info('done?');
|
| 187 |
+
setDone(true);
|
| 188 |
+
return response;
|
| 189 |
+
} catch (e) {
|
| 190 |
+
setDone(true);
|
| 191 |
+
console.warn(e);
|
| 192 |
}
|
|
|
|
| 193 |
},
|
| 194 |
+
[url],
|
| 195 |
);
|
| 196 |
|
| 197 |
+
return { send, answer, done };
|
| 198 |
};
|
web/src/interfaces/database/chat.ts
CHANGED
|
@@ -72,6 +72,11 @@ export interface IReference {
|
|
| 72 |
total: number;
|
| 73 |
}
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
export interface Docagg {
|
| 76 |
count: number;
|
| 77 |
doc_id: string;
|
|
|
|
| 72 |
total: number;
|
| 73 |
}
|
| 74 |
|
| 75 |
+
export interface IAnswer {
|
| 76 |
+
answer: string;
|
| 77 |
+
reference: IReference;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
export interface Docagg {
|
| 81 |
count: number;
|
| 82 |
doc_id: string;
|
web/src/locales/en.ts
CHANGED
|
@@ -25,6 +25,7 @@ export default {
|
|
| 25 |
comingSoon: 'Coming Soon',
|
| 26 |
download: 'Download',
|
| 27 |
close: 'Close',
|
|
|
|
| 28 |
},
|
| 29 |
login: {
|
| 30 |
login: 'Sign in',
|
|
@@ -381,6 +382,7 @@ export default {
|
|
| 381 |
partialTitle: 'Partial Embed',
|
| 382 |
extensionTitle: 'Chrome Extension',
|
| 383 |
tokenError: 'Please create API Token first!',
|
|
|
|
| 384 |
},
|
| 385 |
setting: {
|
| 386 |
profile: 'Profile',
|
|
|
|
| 25 |
comingSoon: 'Coming Soon',
|
| 26 |
download: 'Download',
|
| 27 |
close: 'Close',
|
| 28 |
+
preview: 'Preview',
|
| 29 |
},
|
| 30 |
login: {
|
| 31 |
login: 'Sign in',
|
|
|
|
| 382 |
partialTitle: 'Partial Embed',
|
| 383 |
extensionTitle: 'Chrome Extension',
|
| 384 |
tokenError: 'Please create API Token first!',
|
| 385 |
+
searching: 'searching...',
|
| 386 |
},
|
| 387 |
setting: {
|
| 388 |
profile: 'Profile',
|
web/src/locales/zh-traditional.ts
CHANGED
|
@@ -25,6 +25,7 @@ export default {
|
|
| 25 |
comingSoon: '即將推出',
|
| 26 |
download: '下載',
|
| 27 |
close: '关闭',
|
|
|
|
| 28 |
},
|
| 29 |
login: {
|
| 30 |
login: '登入',
|
|
@@ -352,6 +353,7 @@ export default {
|
|
| 352 |
partialTitle: '部分嵌入',
|
| 353 |
extensionTitle: 'Chrome 插件',
|
| 354 |
tokenError: '請先創建 Api Token!',
|
|
|
|
| 355 |
},
|
| 356 |
setting: {
|
| 357 |
profile: '概述',
|
|
|
|
| 25 |
comingSoon: '即將推出',
|
| 26 |
download: '下載',
|
| 27 |
close: '关闭',
|
| 28 |
+
preview: '預覽',
|
| 29 |
},
|
| 30 |
login: {
|
| 31 |
login: '登入',
|
|
|
|
| 353 |
partialTitle: '部分嵌入',
|
| 354 |
extensionTitle: 'Chrome 插件',
|
| 355 |
tokenError: '請先創建 Api Token!',
|
| 356 |
+
searching: '搜索中',
|
| 357 |
},
|
| 358 |
setting: {
|
| 359 |
profile: '概述',
|
web/src/locales/zh.ts
CHANGED
|
@@ -25,6 +25,7 @@ export default {
|
|
| 25 |
comingSoon: '即将推出',
|
| 26 |
download: '下载',
|
| 27 |
close: '关闭',
|
|
|
|
| 28 |
},
|
| 29 |
login: {
|
| 30 |
login: '登录',
|
|
@@ -369,6 +370,7 @@ export default {
|
|
| 369 |
partialTitle: '部分嵌入',
|
| 370 |
extensionTitle: 'Chrome 插件',
|
| 371 |
tokenError: '请先创建 Api Token!',
|
|
|
|
| 372 |
},
|
| 373 |
setting: {
|
| 374 |
profile: '概要',
|
|
|
|
| 25 |
comingSoon: '即将推出',
|
| 26 |
download: '下载',
|
| 27 |
close: '关闭',
|
| 28 |
+
preview: '预览',
|
| 29 |
},
|
| 30 |
login: {
|
| 31 |
login: '登录',
|
|
|
|
| 370 |
partialTitle: '部分嵌入',
|
| 371 |
extensionTitle: 'Chrome 插件',
|
| 372 |
tokenError: '请先创建 Api Token!',
|
| 373 |
+
searching: '搜索中',
|
| 374 |
},
|
| 375 |
setting: {
|
| 376 |
profile: '概要',
|
web/src/pages/chat/chat-container/index.tsx
CHANGED
|
@@ -6,16 +6,7 @@ import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
|
|
| 6 |
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
| 7 |
import { IReference, Message } from '@/interfaces/database/chat';
|
| 8 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 9 |
-
import {
|
| 10 |
-
Avatar,
|
| 11 |
-
Button,
|
| 12 |
-
Drawer,
|
| 13 |
-
Flex,
|
| 14 |
-
Input,
|
| 15 |
-
List,
|
| 16 |
-
Skeleton,
|
| 17 |
-
Spin,
|
| 18 |
-
} from 'antd';
|
| 19 |
import classNames from 'classnames';
|
| 20 |
import { useMemo } from 'react';
|
| 21 |
import {
|
|
@@ -32,20 +23,24 @@ import SvgIcon from '@/components/svg-icon';
|
|
| 32 |
import { useTranslate } from '@/hooks/commonHooks';
|
| 33 |
import { useGetDocumentUrl } from '@/hooks/documentHooks';
|
| 34 |
import { getExtension, isPdf } from '@/utils/documentUtils';
|
|
|
|
| 35 |
import styles from './index.less';
|
| 36 |
|
| 37 |
const MessageItem = ({
|
| 38 |
item,
|
| 39 |
reference,
|
|
|
|
| 40 |
clickDocumentButton,
|
| 41 |
}: {
|
| 42 |
item: Message;
|
| 43 |
reference: IReference;
|
|
|
|
| 44 |
clickDocumentButton: (documentId: string, chunk: IChunk) => void;
|
| 45 |
}) => {
|
| 46 |
const userInfo = useSelectUserInfo();
|
| 47 |
const fileThumbnails = useSelectFileThumbnails();
|
| 48 |
const getDocumentUrl = useGetDocumentUrl();
|
|
|
|
| 49 |
|
| 50 |
const isAssistant = item.role === MessageType.Assistant;
|
| 51 |
|
|
@@ -53,6 +48,14 @@ const MessageItem = ({
|
|
| 53 |
return reference?.doc_aggs ?? [];
|
| 54 |
}, [reference?.doc_aggs]);
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
return (
|
| 57 |
<div
|
| 58 |
className={classNames(styles.messageItem, {
|
|
@@ -85,15 +88,11 @@ const MessageItem = ({
|
|
| 85 |
<Flex vertical gap={8} flex={1}>
|
| 86 |
<b>{isAssistant ? '' : userInfo.nickname}</b>
|
| 87 |
<div className={styles.messageText}>
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
></MarkdownContent>
|
| 94 |
-
) : (
|
| 95 |
-
<Skeleton active className={styles.messageEmpty} />
|
| 96 |
-
)}
|
| 97 |
</div>
|
| 98 |
{isAssistant && referenceDocumentList.length > 0 && (
|
| 99 |
<List
|
|
@@ -139,13 +138,19 @@ const ChatContainer = () => {
|
|
| 139 |
currentConversation: conversation,
|
| 140 |
addNewestConversation,
|
| 141 |
removeLatestMessage,
|
|
|
|
| 142 |
} = useFetchConversationOnMount();
|
| 143 |
const {
|
| 144 |
handleInputChange,
|
| 145 |
handlePressEnter,
|
| 146 |
value,
|
| 147 |
loading: sendLoading,
|
| 148 |
-
} = useSendMessage(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
| 150 |
useClickDrawer();
|
| 151 |
const disabled = useGetSendButtonDisabled();
|
|
@@ -159,19 +164,17 @@ const ChatContainer = () => {
|
|
| 159 |
<Flex flex={1} vertical className={styles.messageContainer}>
|
| 160 |
<div>
|
| 161 |
<Spin spinning={loading}>
|
| 162 |
-
{conversation?.message?.map((message) => {
|
| 163 |
-
const assistantMessages = conversation?.message
|
| 164 |
-
?.filter((x) => x.role === MessageType.Assistant)
|
| 165 |
-
.slice(1);
|
| 166 |
-
const referenceIndex = assistantMessages.findIndex(
|
| 167 |
-
(x) => x.id === message.id,
|
| 168 |
-
);
|
| 169 |
-
const reference = conversation.reference[referenceIndex];
|
| 170 |
return (
|
| 171 |
<MessageItem
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
key={message.id}
|
| 173 |
item={message}
|
| 174 |
-
reference={
|
| 175 |
clickDocumentButton={clickDocumentButton}
|
| 176 |
></MessageItem>
|
| 177 |
);
|
|
|
|
| 6 |
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
| 7 |
import { IReference, Message } from '@/interfaces/database/chat';
|
| 8 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 9 |
+
import { Avatar, Button, Drawer, Flex, Input, List, Spin } from 'antd';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
import classNames from 'classnames';
|
| 11 |
import { useMemo } from 'react';
|
| 12 |
import {
|
|
|
|
| 23 |
import { useTranslate } from '@/hooks/commonHooks';
|
| 24 |
import { useGetDocumentUrl } from '@/hooks/documentHooks';
|
| 25 |
import { getExtension, isPdf } from '@/utils/documentUtils';
|
| 26 |
+
import { buildMessageItemReference } from '../utils';
|
| 27 |
import styles from './index.less';
|
| 28 |
|
| 29 |
const MessageItem = ({
|
| 30 |
item,
|
| 31 |
reference,
|
| 32 |
+
loading = false,
|
| 33 |
clickDocumentButton,
|
| 34 |
}: {
|
| 35 |
item: Message;
|
| 36 |
reference: IReference;
|
| 37 |
+
loading?: boolean;
|
| 38 |
clickDocumentButton: (documentId: string, chunk: IChunk) => void;
|
| 39 |
}) => {
|
| 40 |
const userInfo = useSelectUserInfo();
|
| 41 |
const fileThumbnails = useSelectFileThumbnails();
|
| 42 |
const getDocumentUrl = useGetDocumentUrl();
|
| 43 |
+
const { t } = useTranslate('chat');
|
| 44 |
|
| 45 |
const isAssistant = item.role === MessageType.Assistant;
|
| 46 |
|
|
|
|
| 48 |
return reference?.doc_aggs ?? [];
|
| 49 |
}, [reference?.doc_aggs]);
|
| 50 |
|
| 51 |
+
const content = useMemo(() => {
|
| 52 |
+
let text = item.content;
|
| 53 |
+
if (text === '') {
|
| 54 |
+
text = t('searching');
|
| 55 |
+
}
|
| 56 |
+
return loading ? text?.concat('~~2$$') : text;
|
| 57 |
+
}, [item.content, loading, t]);
|
| 58 |
+
|
| 59 |
return (
|
| 60 |
<div
|
| 61 |
className={classNames(styles.messageItem, {
|
|
|
|
| 88 |
<Flex vertical gap={8} flex={1}>
|
| 89 |
<b>{isAssistant ? '' : userInfo.nickname}</b>
|
| 90 |
<div className={styles.messageText}>
|
| 91 |
+
<MarkdownContent
|
| 92 |
+
content={content}
|
| 93 |
+
reference={reference}
|
| 94 |
+
clickDocumentButton={clickDocumentButton}
|
| 95 |
+
></MarkdownContent>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
</div>
|
| 97 |
{isAssistant && referenceDocumentList.length > 0 && (
|
| 98 |
<List
|
|
|
|
| 138 |
currentConversation: conversation,
|
| 139 |
addNewestConversation,
|
| 140 |
removeLatestMessage,
|
| 141 |
+
addNewestAnswer,
|
| 142 |
} = useFetchConversationOnMount();
|
| 143 |
const {
|
| 144 |
handleInputChange,
|
| 145 |
handlePressEnter,
|
| 146 |
value,
|
| 147 |
loading: sendLoading,
|
| 148 |
+
} = useSendMessage(
|
| 149 |
+
conversation,
|
| 150 |
+
addNewestConversation,
|
| 151 |
+
removeLatestMessage,
|
| 152 |
+
addNewestAnswer,
|
| 153 |
+
);
|
| 154 |
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
| 155 |
useClickDrawer();
|
| 156 |
const disabled = useGetSendButtonDisabled();
|
|
|
|
| 164 |
<Flex flex={1} vertical className={styles.messageContainer}>
|
| 165 |
<div>
|
| 166 |
<Spin spinning={loading}>
|
| 167 |
+
{conversation?.message?.map((message, i) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
return (
|
| 169 |
<MessageItem
|
| 170 |
+
loading={
|
| 171 |
+
message.role === MessageType.Assistant &&
|
| 172 |
+
sendLoading &&
|
| 173 |
+
conversation?.message.length - 1 === i
|
| 174 |
+
}
|
| 175 |
key={message.id}
|
| 176 |
item={message}
|
| 177 |
+
reference={buildMessageItemReference(conversation, message)}
|
| 178 |
clickDocumentButton={clickDocumentButton}
|
| 179 |
></MessageItem>
|
| 180 |
);
|
web/src/pages/chat/hooks.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import { MessageType } from '@/constants/chat';
|
| 2 |
import { fileIconMap } from '@/constants/common';
|
| 3 |
import {
|
| 4 |
-
useCompleteConversation,
|
| 5 |
useCreateToken,
|
| 6 |
useFetchConversation,
|
| 7 |
useFetchConversationList,
|
|
@@ -24,8 +23,14 @@ import {
|
|
| 24 |
useShowDeleteConfirm,
|
| 25 |
useTranslate,
|
| 26 |
} from '@/hooks/commonHooks';
|
|
|
|
| 27 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
| 28 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 30 |
import { getFileExtension } from '@/utils';
|
| 31 |
import { message } from 'antd';
|
|
@@ -380,31 +385,56 @@ export const useSelectCurrentConversation = () => {
|
|
| 380 |
const dialog = useSelectCurrentDialog();
|
| 381 |
const { conversationId, dialogId } = useGetChatSearchParams();
|
| 382 |
|
| 383 |
-
const addNewestConversation = useCallback(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
setCurrentConversation((pre) => {
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
};
|
| 402 |
});
|
| 403 |
}, []);
|
| 404 |
|
| 405 |
const removeLatestMessage = useCallback(() => {
|
|
|
|
| 406 |
setCurrentConversation((pre) => {
|
| 407 |
-
const nextMessages = pre.message
|
| 408 |
return {
|
| 409 |
...pre,
|
| 410 |
message: nextMessages,
|
|
@@ -441,7 +471,12 @@ export const useSelectCurrentConversation = () => {
|
|
| 441 |
}
|
| 442 |
}, [conversation, conversationId]);
|
| 443 |
|
| 444 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
};
|
| 446 |
|
| 447 |
export const useScrollToBottom = (currentConversation: IClientConversation) => {
|
|
@@ -464,8 +499,12 @@ export const useScrollToBottom = (currentConversation: IClientConversation) => {
|
|
| 464 |
export const useFetchConversationOnMount = () => {
|
| 465 |
const { conversationId } = useGetChatSearchParams();
|
| 466 |
const fetchConversation = useFetchConversation();
|
| 467 |
-
const {
|
| 468 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
const ref = useScrollToBottom(currentConversation);
|
| 470 |
|
| 471 |
const fetchConversationOnMount = useCallback(() => {
|
|
@@ -483,6 +522,7 @@ export const useFetchConversationOnMount = () => {
|
|
| 483 |
addNewestConversation,
|
| 484 |
ref,
|
| 485 |
removeLatestMessage,
|
|
|
|
| 486 |
};
|
| 487 |
};
|
| 488 |
|
|
@@ -504,25 +544,22 @@ export const useHandleMessageInputChange = () => {
|
|
| 504 |
|
| 505 |
export const useSendMessage = (
|
| 506 |
conversation: IClientConversation,
|
| 507 |
-
addNewestConversation: (message: string) => void,
|
| 508 |
removeLatestMessage: () => void,
|
|
|
|
| 509 |
) => {
|
| 510 |
-
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
| 511 |
-
'completeConversation',
|
| 512 |
-
]);
|
| 513 |
const { setConversation } = useSetConversation();
|
| 514 |
const { conversationId } = useGetChatSearchParams();
|
| 515 |
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
| 516 |
|
| 517 |
const fetchConversation = useFetchConversation();
|
| 518 |
-
const completeConversation = useCompleteConversation();
|
| 519 |
|
| 520 |
const { handleClickConversation } = useClickConversationCard();
|
| 521 |
-
|
| 522 |
|
| 523 |
const sendMessage = useCallback(
|
| 524 |
async (message: string, id?: string) => {
|
| 525 |
-
const
|
| 526 |
conversation_id: id ?? conversationId,
|
| 527 |
messages: [
|
| 528 |
...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
|
|
@@ -533,27 +570,33 @@ export const useSendMessage = (
|
|
| 533 |
],
|
| 534 |
});
|
| 535 |
|
| 536 |
-
if (
|
| 537 |
if (id) {
|
|
|
|
| 538 |
// new conversation
|
| 539 |
handleClickConversation(id);
|
| 540 |
} else {
|
| 541 |
-
|
|
|
|
| 542 |
}
|
| 543 |
} else {
|
|
|
|
|
|
|
| 544 |
// cancel loading
|
| 545 |
setValue(message);
|
|
|
|
| 546 |
removeLatestMessage();
|
| 547 |
}
|
|
|
|
| 548 |
},
|
| 549 |
[
|
| 550 |
conversation?.message,
|
| 551 |
conversationId,
|
| 552 |
-
fetchConversation,
|
| 553 |
handleClickConversation,
|
| 554 |
removeLatestMessage,
|
| 555 |
setValue,
|
| 556 |
-
|
| 557 |
],
|
| 558 |
);
|
| 559 |
|
|
@@ -572,19 +615,27 @@ export const useSendMessage = (
|
|
| 572 |
[conversationId, setConversation, sendMessage],
|
| 573 |
);
|
| 574 |
|
| 575 |
-
|
| 576 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
setValue('');
|
| 578 |
-
addNewestConversation(value);
|
| 579 |
handleSendMessage(value.trim());
|
| 580 |
}
|
| 581 |
-
|
|
|
|
| 582 |
|
| 583 |
return {
|
| 584 |
handlePressEnter,
|
| 585 |
handleInputChange,
|
| 586 |
value,
|
| 587 |
-
loading,
|
| 588 |
};
|
| 589 |
};
|
| 590 |
|
|
|
|
| 1 |
import { MessageType } from '@/constants/chat';
|
| 2 |
import { fileIconMap } from '@/constants/common';
|
| 3 |
import {
|
|
|
|
| 4 |
useCreateToken,
|
| 5 |
useFetchConversation,
|
| 6 |
useFetchConversationList,
|
|
|
|
| 23 |
useShowDeleteConfirm,
|
| 24 |
useTranslate,
|
| 25 |
} from '@/hooks/commonHooks';
|
| 26 |
+
import { useSendMessageWithSse } from '@/hooks/logicHooks';
|
| 27 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
| 28 |
+
import {
|
| 29 |
+
IAnswer,
|
| 30 |
+
IConversation,
|
| 31 |
+
IDialog,
|
| 32 |
+
IStats,
|
| 33 |
+
} from '@/interfaces/database/chat';
|
| 34 |
import { IChunk } from '@/interfaces/database/knowledge';
|
| 35 |
import { getFileExtension } from '@/utils';
|
| 36 |
import { message } from 'antd';
|
|
|
|
| 385 |
const dialog = useSelectCurrentDialog();
|
| 386 |
const { conversationId, dialogId } = useGetChatSearchParams();
|
| 387 |
|
| 388 |
+
const addNewestConversation = useCallback(
|
| 389 |
+
(message: string, answer: string = '') => {
|
| 390 |
+
setCurrentConversation((pre) => {
|
| 391 |
+
return {
|
| 392 |
+
...pre,
|
| 393 |
+
message: [
|
| 394 |
+
...pre.message,
|
| 395 |
+
{
|
| 396 |
+
role: MessageType.User,
|
| 397 |
+
content: message,
|
| 398 |
+
id: uuid(),
|
| 399 |
+
} as IMessage,
|
| 400 |
+
{
|
| 401 |
+
role: MessageType.Assistant,
|
| 402 |
+
content: answer,
|
| 403 |
+
id: uuid(),
|
| 404 |
+
reference: [],
|
| 405 |
+
} as IMessage,
|
| 406 |
+
],
|
| 407 |
+
};
|
| 408 |
+
});
|
| 409 |
+
},
|
| 410 |
+
[],
|
| 411 |
+
);
|
| 412 |
+
|
| 413 |
+
const addNewestAnswer = useCallback((answer: IAnswer) => {
|
| 414 |
setCurrentConversation((pre) => {
|
| 415 |
+
const latestMessage = pre.message?.at(-1);
|
| 416 |
+
|
| 417 |
+
if (latestMessage) {
|
| 418 |
+
return {
|
| 419 |
+
...pre,
|
| 420 |
+
message: [
|
| 421 |
+
...pre.message.slice(0, -1),
|
| 422 |
+
{
|
| 423 |
+
...latestMessage,
|
| 424 |
+
content: answer.answer,
|
| 425 |
+
reference: answer.reference,
|
| 426 |
+
} as IMessage,
|
| 427 |
+
],
|
| 428 |
+
};
|
| 429 |
+
}
|
| 430 |
+
return pre;
|
|
|
|
| 431 |
});
|
| 432 |
}, []);
|
| 433 |
|
| 434 |
const removeLatestMessage = useCallback(() => {
|
| 435 |
+
console.info('removeLatestMessage');
|
| 436 |
setCurrentConversation((pre) => {
|
| 437 |
+
const nextMessages = pre.message?.slice(0, -2) ?? [];
|
| 438 |
return {
|
| 439 |
...pre,
|
| 440 |
message: nextMessages,
|
|
|
|
| 471 |
}
|
| 472 |
}, [conversation, conversationId]);
|
| 473 |
|
| 474 |
+
return {
|
| 475 |
+
currentConversation,
|
| 476 |
+
addNewestConversation,
|
| 477 |
+
removeLatestMessage,
|
| 478 |
+
addNewestAnswer,
|
| 479 |
+
};
|
| 480 |
};
|
| 481 |
|
| 482 |
export const useScrollToBottom = (currentConversation: IClientConversation) => {
|
|
|
|
| 499 |
export const useFetchConversationOnMount = () => {
|
| 500 |
const { conversationId } = useGetChatSearchParams();
|
| 501 |
const fetchConversation = useFetchConversation();
|
| 502 |
+
const {
|
| 503 |
+
currentConversation,
|
| 504 |
+
addNewestConversation,
|
| 505 |
+
removeLatestMessage,
|
| 506 |
+
addNewestAnswer,
|
| 507 |
+
} = useSelectCurrentConversation();
|
| 508 |
const ref = useScrollToBottom(currentConversation);
|
| 509 |
|
| 510 |
const fetchConversationOnMount = useCallback(() => {
|
|
|
|
| 522 |
addNewestConversation,
|
| 523 |
ref,
|
| 524 |
removeLatestMessage,
|
| 525 |
+
addNewestAnswer,
|
| 526 |
};
|
| 527 |
};
|
| 528 |
|
|
|
|
| 544 |
|
| 545 |
export const useSendMessage = (
|
| 546 |
conversation: IClientConversation,
|
| 547 |
+
addNewestConversation: (message: string, answer?: string) => void,
|
| 548 |
removeLatestMessage: () => void,
|
| 549 |
+
addNewestAnswer: (answer: IAnswer) => void,
|
| 550 |
) => {
|
|
|
|
|
|
|
|
|
|
| 551 |
const { setConversation } = useSetConversation();
|
| 552 |
const { conversationId } = useGetChatSearchParams();
|
| 553 |
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
| 554 |
|
| 555 |
const fetchConversation = useFetchConversation();
|
|
|
|
| 556 |
|
| 557 |
const { handleClickConversation } = useClickConversationCard();
|
| 558 |
+
const { send, answer, done } = useSendMessageWithSse();
|
| 559 |
|
| 560 |
const sendMessage = useCallback(
|
| 561 |
async (message: string, id?: string) => {
|
| 562 |
+
const res: Response = await send({
|
| 563 |
conversation_id: id ?? conversationId,
|
| 564 |
messages: [
|
| 565 |
...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
|
|
|
|
| 570 |
],
|
| 571 |
});
|
| 572 |
|
| 573 |
+
if (res.status === 200) {
|
| 574 |
if (id) {
|
| 575 |
+
console.info('111');
|
| 576 |
// new conversation
|
| 577 |
handleClickConversation(id);
|
| 578 |
} else {
|
| 579 |
+
console.info('222');
|
| 580 |
+
// fetchConversation(conversationId);
|
| 581 |
}
|
| 582 |
} else {
|
| 583 |
+
console.info('333');
|
| 584 |
+
|
| 585 |
// cancel loading
|
| 586 |
setValue(message);
|
| 587 |
+
console.info('removeLatestMessage111');
|
| 588 |
removeLatestMessage();
|
| 589 |
}
|
| 590 |
+
console.info('false');
|
| 591 |
},
|
| 592 |
[
|
| 593 |
conversation?.message,
|
| 594 |
conversationId,
|
| 595 |
+
// fetchConversation,
|
| 596 |
handleClickConversation,
|
| 597 |
removeLatestMessage,
|
| 598 |
setValue,
|
| 599 |
+
send,
|
| 600 |
],
|
| 601 |
);
|
| 602 |
|
|
|
|
| 615 |
[conversationId, setConversation, sendMessage],
|
| 616 |
);
|
| 617 |
|
| 618 |
+
useEffect(() => {
|
| 619 |
+
if (answer.answer) {
|
| 620 |
+
addNewestAnswer(answer);
|
| 621 |
+
console.info('true?');
|
| 622 |
+
console.info('send msg:', answer.answer);
|
| 623 |
+
}
|
| 624 |
+
}, [answer, addNewestAnswer]);
|
| 625 |
+
|
| 626 |
+
const handlePressEnter = useCallback(() => {
|
| 627 |
+
if (done) {
|
| 628 |
setValue('');
|
|
|
|
| 629 |
handleSendMessage(value.trim());
|
| 630 |
}
|
| 631 |
+
addNewestConversation(value);
|
| 632 |
+
}, [addNewestConversation, handleSendMessage, done, setValue, value]);
|
| 633 |
|
| 634 |
return {
|
| 635 |
handlePressEnter,
|
| 636 |
handleInputChange,
|
| 637 |
value,
|
| 638 |
+
loading: !done,
|
| 639 |
};
|
| 640 |
};
|
| 641 |
|
web/src/pages/chat/interface.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { IConversation, Message } from '@/interfaces/database/chat';
|
| 2 |
import { FormInstance } from 'antd';
|
| 3 |
|
| 4 |
export interface ISegmentedContentProps {
|
|
@@ -24,6 +24,7 @@ export type IPromptConfigParameters = Omit<VariableTableDataType, 'variable'>;
|
|
| 24 |
|
| 25 |
export interface IMessage extends Message {
|
| 26 |
id: string;
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
export interface IClientConversation extends IConversation {
|
|
|
|
| 1 |
+
import { IConversation, IReference, Message } from '@/interfaces/database/chat';
|
| 2 |
import { FormInstance } from 'antd';
|
| 3 |
|
| 4 |
export interface ISegmentedContentProps {
|
|
|
|
| 24 |
|
| 25 |
export interface IMessage extends Message {
|
| 26 |
id: string;
|
| 27 |
+
reference?: IReference; // the latest news has reference
|
| 28 |
}
|
| 29 |
|
| 30 |
export interface IClientConversation extends IConversation {
|
web/src/pages/chat/markdown-content/index.less
CHANGED
|
@@ -23,3 +23,23 @@
|
|
| 23 |
.referenceIcon {
|
| 24 |
padding: 0 6px;
|
| 25 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
.referenceIcon {
|
| 24 |
padding: 0 6px;
|
| 25 |
}
|
| 26 |
+
|
| 27 |
+
.cursor {
|
| 28 |
+
display: inline-block;
|
| 29 |
+
width: 1px;
|
| 30 |
+
height: 16px;
|
| 31 |
+
background-color: black;
|
| 32 |
+
animation: blink 0.6s infinite;
|
| 33 |
+
vertical-align: text-top;
|
| 34 |
+
@keyframes blink {
|
| 35 |
+
0% {
|
| 36 |
+
opacity: 1;
|
| 37 |
+
}
|
| 38 |
+
50% {
|
| 39 |
+
opacity: 0;
|
| 40 |
+
}
|
| 41 |
+
100% {
|
| 42 |
+
opacity: 1;
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
web/src/pages/chat/markdown-content/index.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import { visitParents } from 'unist-util-visit-parents';
|
|
| 16 |
import styles from './index.less';
|
| 17 |
|
| 18 |
const reg = /(#{2}\d+\${2})/g;
|
|
|
|
| 19 |
|
| 20 |
const getChunkIndex = (match: string) => Number(match.slice(2, -2));
|
| 21 |
// TODO: The display of the table is inconsistent with the display previously placed in the MessageItem.
|
|
@@ -61,7 +62,7 @@ const MarkdownContent = ({
|
|
| 61 |
(chunkIndex: number) => {
|
| 62 |
const chunks = reference?.chunks ?? [];
|
| 63 |
const chunkItem = chunks[chunkIndex];
|
| 64 |
-
const document = reference?.doc_aggs
|
| 65 |
(x) => x?.doc_id === chunkItem?.doc_id,
|
| 66 |
);
|
| 67 |
const documentId = document?.doc_id;
|
|
@@ -129,7 +130,7 @@ const MarkdownContent = ({
|
|
| 129 |
|
| 130 |
const renderReference = useCallback(
|
| 131 |
(text: string) => {
|
| 132 |
-
|
| 133 |
const chunkIndex = getChunkIndex(match);
|
| 134 |
return (
|
| 135 |
<Popover content={getPopoverContent(chunkIndex)}>
|
|
@@ -137,6 +138,12 @@ const MarkdownContent = ({
|
|
| 137 |
</Popover>
|
| 138 |
);
|
| 139 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
},
|
| 141 |
[getPopoverContent],
|
| 142 |
);
|
|
|
|
| 16 |
import styles from './index.less';
|
| 17 |
|
| 18 |
const reg = /(#{2}\d+\${2})/g;
|
| 19 |
+
const curReg = /(~{2}\d+\${2})/g;
|
| 20 |
|
| 21 |
const getChunkIndex = (match: string) => Number(match.slice(2, -2));
|
| 22 |
// TODO: The display of the table is inconsistent with the display previously placed in the MessageItem.
|
|
|
|
| 62 |
(chunkIndex: number) => {
|
| 63 |
const chunks = reference?.chunks ?? [];
|
| 64 |
const chunkItem = chunks[chunkIndex];
|
| 65 |
+
const document = reference?.doc_aggs?.find(
|
| 66 |
(x) => x?.doc_id === chunkItem?.doc_id,
|
| 67 |
);
|
| 68 |
const documentId = document?.doc_id;
|
|
|
|
| 130 |
|
| 131 |
const renderReference = useCallback(
|
| 132 |
(text: string) => {
|
| 133 |
+
let replacedText = reactStringReplace(text, reg, (match, i) => {
|
| 134 |
const chunkIndex = getChunkIndex(match);
|
| 135 |
return (
|
| 136 |
<Popover content={getPopoverContent(chunkIndex)}>
|
|
|
|
| 138 |
</Popover>
|
| 139 |
);
|
| 140 |
});
|
| 141 |
+
|
| 142 |
+
replacedText = reactStringReplace(replacedText, curReg, (match, i) => (
|
| 143 |
+
<span className={styles.cursor} key={i}></span>
|
| 144 |
+
));
|
| 145 |
+
|
| 146 |
+
return replacedText;
|
| 147 |
},
|
| 148 |
[getPopoverContent],
|
| 149 |
);
|
web/src/pages/chat/share/index.tsx
CHANGED
|
@@ -1,51 +1,11 @@
|
|
| 1 |
-
import { useEffect } from 'react';
|
| 2 |
-
import {
|
| 3 |
-
useCreateSharedConversationOnMount,
|
| 4 |
-
useSelectCurrentSharedConversation,
|
| 5 |
-
useSendSharedMessage,
|
| 6 |
-
} from '../shared-hooks';
|
| 7 |
import ChatContainer from './large';
|
| 8 |
|
| 9 |
import styles from './index.less';
|
| 10 |
|
| 11 |
const SharedChat = () => {
|
| 12 |
-
const { conversationId } = useCreateSharedConversationOnMount();
|
| 13 |
-
const {
|
| 14 |
-
currentConversation,
|
| 15 |
-
addNewestConversation,
|
| 16 |
-
removeLatestMessage,
|
| 17 |
-
ref,
|
| 18 |
-
loading,
|
| 19 |
-
setCurrentConversation,
|
| 20 |
-
} = useSelectCurrentSharedConversation(conversationId);
|
| 21 |
-
|
| 22 |
-
const {
|
| 23 |
-
handlePressEnter,
|
| 24 |
-
handleInputChange,
|
| 25 |
-
value,
|
| 26 |
-
loading: sendLoading,
|
| 27 |
-
} = useSendSharedMessage(
|
| 28 |
-
currentConversation,
|
| 29 |
-
addNewestConversation,
|
| 30 |
-
removeLatestMessage,
|
| 31 |
-
setCurrentConversation,
|
| 32 |
-
);
|
| 33 |
-
|
| 34 |
-
useEffect(() => {
|
| 35 |
-
console.info(location.href);
|
| 36 |
-
}, []);
|
| 37 |
-
|
| 38 |
return (
|
| 39 |
<div className={styles.chatWrapper}>
|
| 40 |
-
<ChatContainer
|
| 41 |
-
value={value}
|
| 42 |
-
handleInputChange={handleInputChange}
|
| 43 |
-
handlePressEnter={handlePressEnter}
|
| 44 |
-
loading={loading}
|
| 45 |
-
sendLoading={sendLoading}
|
| 46 |
-
conversation={currentConversation}
|
| 47 |
-
ref={ref}
|
| 48 |
-
></ChatContainer>
|
| 49 |
</div>
|
| 50 |
);
|
| 51 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import ChatContainer from './large';
|
| 2 |
|
| 3 |
import styles from './index.less';
|
| 4 |
|
| 5 |
const SharedChat = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
return (
|
| 7 |
<div className={styles.chatWrapper}>
|
| 8 |
+
<ChatContainer></ChatContainer>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</div>
|
| 10 |
);
|
| 11 |
};
|
web/src/pages/chat/share/large.tsx
CHANGED
|
@@ -1,18 +1,50 @@
|
|
| 1 |
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
| 2 |
import { MessageType } from '@/constants/chat';
|
| 3 |
import { useTranslate } from '@/hooks/commonHooks';
|
| 4 |
-
import { Message } from '@/interfaces/database/chat';
|
| 5 |
-
import { Avatar, Button, Flex, Input,
|
| 6 |
import classNames from 'classnames';
|
| 7 |
-
import { useSelectConversationLoading } from '../hooks';
|
| 8 |
|
| 9 |
-
import
|
| 10 |
-
import
|
| 11 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
import styles from './index.less';
|
| 13 |
|
| 14 |
-
const MessageItem = ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
const isAssistant = item.role === MessageType.Assistant;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
return (
|
| 18 |
<div
|
|
@@ -45,12 +77,43 @@ const MessageItem = ({ item }: { item: Message }) => {
|
|
| 45 |
<Flex vertical gap={8} flex={1}>
|
| 46 |
<b>{isAssistant ? '' : 'You'}</b>
|
| 47 |
<div className={styles.messageText}>
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
</Flex>
|
| 55 |
</div>
|
| 56 |
</section>
|
|
@@ -58,28 +121,31 @@ const MessageItem = ({ item }: { item: Message }) => {
|
|
| 58 |
);
|
| 59 |
};
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
const
|
| 72 |
-
{
|
| 73 |
handlePressEnter,
|
| 74 |
handleInputChange,
|
| 75 |
value,
|
| 76 |
loading: sendLoading,
|
|
|
|
| 77 |
conversation,
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
|
| 84 |
return (
|
| 85 |
<>
|
|
@@ -87,9 +153,18 @@ const ChatContainer = (
|
|
| 87 |
<Flex flex={1} vertical className={styles.messageContainer}>
|
| 88 |
<div>
|
| 89 |
<Spin spinning={loading}>
|
| 90 |
-
{conversation?.message?.map((message) => {
|
| 91 |
return (
|
| 92 |
-
<MessageItem
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
);
|
| 94 |
})}
|
| 95 |
</Spin>
|
|
|
|
| 1 |
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
| 2 |
import { MessageType } from '@/constants/chat';
|
| 3 |
import { useTranslate } from '@/hooks/commonHooks';
|
| 4 |
+
import { IReference, Message } from '@/interfaces/database/chat';
|
| 5 |
+
import { Avatar, Button, Flex, Input, List, Spin } from 'antd';
|
| 6 |
import classNames from 'classnames';
|
|
|
|
| 7 |
|
| 8 |
+
import NewDocumentLink from '@/components/new-document-link';
|
| 9 |
+
import SvgIcon from '@/components/svg-icon';
|
| 10 |
+
import { useGetDocumentUrl } from '@/hooks/documentHooks';
|
| 11 |
+
import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
|
| 12 |
+
import { getExtension, isPdf } from '@/utils/documentUtils';
|
| 13 |
+
import { forwardRef, useMemo } from 'react';
|
| 14 |
+
import MarkdownContent from '../markdown-content';
|
| 15 |
+
import {
|
| 16 |
+
useCreateSharedConversationOnMount,
|
| 17 |
+
useSelectCurrentSharedConversation,
|
| 18 |
+
useSendSharedMessage,
|
| 19 |
+
} from '../shared-hooks';
|
| 20 |
+
import { buildMessageItemReference } from '../utils';
|
| 21 |
import styles from './index.less';
|
| 22 |
|
| 23 |
+
const MessageItem = ({
|
| 24 |
+
item,
|
| 25 |
+
reference,
|
| 26 |
+
loading = false,
|
| 27 |
+
}: {
|
| 28 |
+
item: Message;
|
| 29 |
+
reference: IReference;
|
| 30 |
+
loading?: boolean;
|
| 31 |
+
}) => {
|
| 32 |
const isAssistant = item.role === MessageType.Assistant;
|
| 33 |
+
const { t } = useTranslate('chat');
|
| 34 |
+
const fileThumbnails = useSelectFileThumbnails();
|
| 35 |
+
const getDocumentUrl = useGetDocumentUrl();
|
| 36 |
+
|
| 37 |
+
const referenceDocumentList = useMemo(() => {
|
| 38 |
+
return reference?.doc_aggs ?? [];
|
| 39 |
+
}, [reference?.doc_aggs]);
|
| 40 |
+
|
| 41 |
+
const content = useMemo(() => {
|
| 42 |
+
let text = item.content;
|
| 43 |
+
if (text === '') {
|
| 44 |
+
text = t('searching');
|
| 45 |
+
}
|
| 46 |
+
return loading ? text?.concat('~~2$$') : text;
|
| 47 |
+
}, [item.content, loading, t]);
|
| 48 |
|
| 49 |
return (
|
| 50 |
<div
|
|
|
|
| 77 |
<Flex vertical gap={8} flex={1}>
|
| 78 |
<b>{isAssistant ? '' : 'You'}</b>
|
| 79 |
<div className={styles.messageText}>
|
| 80 |
+
<MarkdownContent
|
| 81 |
+
reference={reference}
|
| 82 |
+
clickDocumentButton={() => {}}
|
| 83 |
+
content={content}
|
| 84 |
+
></MarkdownContent>
|
| 85 |
</div>
|
| 86 |
+
{isAssistant && referenceDocumentList.length > 0 && (
|
| 87 |
+
<List
|
| 88 |
+
bordered
|
| 89 |
+
dataSource={referenceDocumentList}
|
| 90 |
+
renderItem={(item) => {
|
| 91 |
+
const fileThumbnail = fileThumbnails[item.doc_id];
|
| 92 |
+
const fileExtension = getExtension(item.doc_name);
|
| 93 |
+
return (
|
| 94 |
+
<List.Item>
|
| 95 |
+
<Flex gap={'small'} align="center">
|
| 96 |
+
{fileThumbnail ? (
|
| 97 |
+
<img src={fileThumbnail}></img>
|
| 98 |
+
) : (
|
| 99 |
+
<SvgIcon
|
| 100 |
+
name={`file-icon/${fileExtension}`}
|
| 101 |
+
width={24}
|
| 102 |
+
></SvgIcon>
|
| 103 |
+
)}
|
| 104 |
+
|
| 105 |
+
<NewDocumentLink
|
| 106 |
+
link={getDocumentUrl(item.doc_id)}
|
| 107 |
+
preventDefault={!isPdf(item.doc_name)}
|
| 108 |
+
>
|
| 109 |
+
{item.doc_name}
|
| 110 |
+
</NewDocumentLink>
|
| 111 |
+
</Flex>
|
| 112 |
+
</List.Item>
|
| 113 |
+
);
|
| 114 |
+
}}
|
| 115 |
+
/>
|
| 116 |
+
)}
|
| 117 |
</Flex>
|
| 118 |
</div>
|
| 119 |
</section>
|
|
|
|
| 121 |
);
|
| 122 |
};
|
| 123 |
|
| 124 |
+
const ChatContainer = () => {
|
| 125 |
+
const { t } = useTranslate('chat');
|
| 126 |
+
const { conversationId } = useCreateSharedConversationOnMount();
|
| 127 |
+
const {
|
| 128 |
+
currentConversation: conversation,
|
| 129 |
+
addNewestConversation,
|
| 130 |
+
removeLatestMessage,
|
| 131 |
+
ref,
|
| 132 |
+
loading,
|
| 133 |
+
setCurrentConversation,
|
| 134 |
+
addNewestAnswer,
|
| 135 |
+
} = useSelectCurrentSharedConversation(conversationId);
|
| 136 |
|
| 137 |
+
const {
|
|
|
|
| 138 |
handlePressEnter,
|
| 139 |
handleInputChange,
|
| 140 |
value,
|
| 141 |
loading: sendLoading,
|
| 142 |
+
} = useSendSharedMessage(
|
| 143 |
conversation,
|
| 144 |
+
addNewestConversation,
|
| 145 |
+
removeLatestMessage,
|
| 146 |
+
setCurrentConversation,
|
| 147 |
+
addNewestAnswer,
|
| 148 |
+
);
|
| 149 |
|
| 150 |
return (
|
| 151 |
<>
|
|
|
|
| 153 |
<Flex flex={1} vertical className={styles.messageContainer}>
|
| 154 |
<div>
|
| 155 |
<Spin spinning={loading}>
|
| 156 |
+
{conversation?.message?.map((message, i) => {
|
| 157 |
return (
|
| 158 |
+
<MessageItem
|
| 159 |
+
key={message.id}
|
| 160 |
+
item={message}
|
| 161 |
+
reference={buildMessageItemReference(conversation, message)}
|
| 162 |
+
loading={
|
| 163 |
+
message.role === MessageType.Assistant &&
|
| 164 |
+
sendLoading &&
|
| 165 |
+
conversation?.message.length - 1 === i
|
| 166 |
+
}
|
| 167 |
+
></MessageItem>
|
| 168 |
);
|
| 169 |
})}
|
| 170 |
</Spin>
|
web/src/pages/chat/shared-hooks.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
import { MessageType } from '@/constants/chat';
|
| 2 |
import {
|
| 3 |
-
useCompleteSharedConversation,
|
| 4 |
useCreateSharedConversation,
|
| 5 |
useFetchSharedConversation,
|
| 6 |
} from '@/hooks/chatHooks';
|
|
|
|
| 7 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
|
|
|
|
|
|
| 8 |
import omit from 'lodash/omit';
|
| 9 |
import {
|
| 10 |
Dispatch,
|
|
@@ -76,6 +78,27 @@ export const useSelectCurrentSharedConversation = (conversationId: string) => {
|
|
| 76 |
});
|
| 77 |
}, []);
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
const removeLatestMessage = useCallback(() => {
|
| 80 |
setCurrentConversation((pre) => {
|
| 81 |
const nextMessages = pre.message.slice(0, -2);
|
|
@@ -106,6 +129,7 @@ export const useSelectCurrentSharedConversation = (conversationId: string) => {
|
|
| 106 |
loading,
|
| 107 |
ref,
|
| 108 |
setCurrentConversation,
|
|
|
|
| 109 |
};
|
| 110 |
};
|
| 111 |
|
|
@@ -114,20 +138,19 @@ export const useSendSharedMessage = (
|
|
| 114 |
addNewestConversation: (message: string) => void,
|
| 115 |
removeLatestMessage: () => void,
|
| 116 |
setCurrentConversation: Dispatch<SetStateAction<IClientConversation>>,
|
|
|
|
| 117 |
) => {
|
| 118 |
const conversationId = conversation.id;
|
| 119 |
-
const loading = useOneNamespaceEffectsLoading('chatModel', [
|
| 120 |
-
'completeExternalConversation',
|
| 121 |
-
]);
|
| 122 |
const setConversation = useCreateSharedConversation();
|
| 123 |
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
| 124 |
|
| 125 |
-
const
|
| 126 |
-
|
|
|
|
| 127 |
|
| 128 |
const sendMessage = useCallback(
|
| 129 |
async (message: string, id?: string) => {
|
| 130 |
-
const
|
| 131 |
conversation_id: id ?? conversationId,
|
| 132 |
quote: false,
|
| 133 |
messages: [
|
|
@@ -139,11 +162,11 @@ export const useSendSharedMessage = (
|
|
| 139 |
],
|
| 140 |
});
|
| 141 |
|
| 142 |
-
if (
|
| 143 |
-
const data = await fetchConversation(conversationId);
|
| 144 |
-
if (data.retcode === 0) {
|
| 145 |
-
|
| 146 |
-
}
|
| 147 |
} else {
|
| 148 |
// cancel loading
|
| 149 |
setValue(message);
|
|
@@ -153,11 +176,11 @@ export const useSendSharedMessage = (
|
|
| 153 |
[
|
| 154 |
conversationId,
|
| 155 |
conversation?.message,
|
| 156 |
-
fetchConversation,
|
| 157 |
removeLatestMessage,
|
| 158 |
setValue,
|
| 159 |
-
|
| 160 |
-
setCurrentConversation,
|
| 161 |
],
|
| 162 |
);
|
| 163 |
|
|
@@ -176,18 +199,24 @@ export const useSendSharedMessage = (
|
|
| 176 |
[conversationId, setConversation, sendMessage],
|
| 177 |
);
|
| 178 |
|
| 179 |
-
|
| 180 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
setValue('');
|
| 182 |
addNewestConversation(value);
|
| 183 |
handleSendMessage(value.trim());
|
| 184 |
}
|
| 185 |
-
};
|
| 186 |
|
| 187 |
return {
|
| 188 |
handlePressEnter,
|
| 189 |
handleInputChange,
|
| 190 |
value,
|
| 191 |
-
loading,
|
| 192 |
};
|
| 193 |
};
|
|
|
|
| 1 |
import { MessageType } from '@/constants/chat';
|
| 2 |
import {
|
|
|
|
| 3 |
useCreateSharedConversation,
|
| 4 |
useFetchSharedConversation,
|
| 5 |
} from '@/hooks/chatHooks';
|
| 6 |
+
import { useSendMessageWithSse } from '@/hooks/logicHooks';
|
| 7 |
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
| 8 |
+
import { IAnswer } from '@/interfaces/database/chat';
|
| 9 |
+
import api from '@/utils/api';
|
| 10 |
import omit from 'lodash/omit';
|
| 11 |
import {
|
| 12 |
Dispatch,
|
|
|
|
| 78 |
});
|
| 79 |
}, []);
|
| 80 |
|
| 81 |
+
const addNewestAnswer = useCallback((answer: IAnswer) => {
|
| 82 |
+
setCurrentConversation((pre) => {
|
| 83 |
+
const latestMessage = pre.message?.at(-1);
|
| 84 |
+
|
| 85 |
+
if (latestMessage) {
|
| 86 |
+
return {
|
| 87 |
+
...pre,
|
| 88 |
+
message: [
|
| 89 |
+
...pre.message.slice(0, -1),
|
| 90 |
+
{
|
| 91 |
+
...latestMessage,
|
| 92 |
+
content: answer.answer,
|
| 93 |
+
reference: answer.reference,
|
| 94 |
+
} as IMessage,
|
| 95 |
+
],
|
| 96 |
+
};
|
| 97 |
+
}
|
| 98 |
+
return pre;
|
| 99 |
+
});
|
| 100 |
+
}, []);
|
| 101 |
+
|
| 102 |
const removeLatestMessage = useCallback(() => {
|
| 103 |
setCurrentConversation((pre) => {
|
| 104 |
const nextMessages = pre.message.slice(0, -2);
|
|
|
|
| 129 |
loading,
|
| 130 |
ref,
|
| 131 |
setCurrentConversation,
|
| 132 |
+
addNewestAnswer,
|
| 133 |
};
|
| 134 |
};
|
| 135 |
|
|
|
|
| 138 |
addNewestConversation: (message: string) => void,
|
| 139 |
removeLatestMessage: () => void,
|
| 140 |
setCurrentConversation: Dispatch<SetStateAction<IClientConversation>>,
|
| 141 |
+
addNewestAnswer: (answer: IAnswer) => void,
|
| 142 |
) => {
|
| 143 |
const conversationId = conversation.id;
|
|
|
|
|
|
|
|
|
|
| 144 |
const setConversation = useCreateSharedConversation();
|
| 145 |
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
| 146 |
|
| 147 |
+
const { send, answer, done } = useSendMessageWithSse(
|
| 148 |
+
api.completeExternalConversation,
|
| 149 |
+
);
|
| 150 |
|
| 151 |
const sendMessage = useCallback(
|
| 152 |
async (message: string, id?: string) => {
|
| 153 |
+
const res: Response = await send({
|
| 154 |
conversation_id: id ?? conversationId,
|
| 155 |
quote: false,
|
| 156 |
messages: [
|
|
|
|
| 162 |
],
|
| 163 |
});
|
| 164 |
|
| 165 |
+
if (res?.status === 200) {
|
| 166 |
+
// const data = await fetchConversation(conversationId);
|
| 167 |
+
// if (data.retcode === 0) {
|
| 168 |
+
// setCurrentConversation(data.data);
|
| 169 |
+
// }
|
| 170 |
} else {
|
| 171 |
// cancel loading
|
| 172 |
setValue(message);
|
|
|
|
| 176 |
[
|
| 177 |
conversationId,
|
| 178 |
conversation?.message,
|
| 179 |
+
// fetchConversation,
|
| 180 |
removeLatestMessage,
|
| 181 |
setValue,
|
| 182 |
+
send,
|
| 183 |
+
// setCurrentConversation,
|
| 184 |
],
|
| 185 |
);
|
| 186 |
|
|
|
|
| 199 |
[conversationId, setConversation, sendMessage],
|
| 200 |
);
|
| 201 |
|
| 202 |
+
useEffect(() => {
|
| 203 |
+
if (answer.answer) {
|
| 204 |
+
addNewestAnswer(answer);
|
| 205 |
+
}
|
| 206 |
+
}, [answer, addNewestAnswer]);
|
| 207 |
+
|
| 208 |
+
const handlePressEnter = useCallback(() => {
|
| 209 |
+
if (done) {
|
| 210 |
setValue('');
|
| 211 |
addNewestConversation(value);
|
| 212 |
handleSendMessage(value.trim());
|
| 213 |
}
|
| 214 |
+
}, [addNewestConversation, done, handleSendMessage, setValue, value]);
|
| 215 |
|
| 216 |
return {
|
| 217 |
handlePressEnter,
|
| 218 |
handleInputChange,
|
| 219 |
value,
|
| 220 |
+
loading: !done,
|
| 221 |
};
|
| 222 |
};
|
web/src/pages/chat/utils.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
|
|
| 1 |
import { IConversation, IReference } from '@/interfaces/database/chat';
|
| 2 |
import { EmptyConversationId, variableEnabledFieldMap } from './constants';
|
|
|
|
| 3 |
|
| 4 |
export const excludeUnEnabledVariables = (values: any) => {
|
| 5 |
const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> =
|
|
@@ -20,7 +22,7 @@ export const getDocumentIdsFromConversionReference = (data: IConversation) => {
|
|
| 20 |
const documentIds = data.reference.reduce(
|
| 21 |
(pre: Array<string>, cur: IReference) => {
|
| 22 |
cur.doc_aggs
|
| 23 |
-
|
| 24 |
.forEach((x) => {
|
| 25 |
if (pre.every((y) => y !== x)) {
|
| 26 |
pre.push(x);
|
|
@@ -32,3 +34,20 @@ export const getDocumentIdsFromConversionReference = (data: IConversation) => {
|
|
| 32 |
);
|
| 33 |
return documentIds.join(',');
|
| 34 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { MessageType } from '@/constants/chat';
|
| 2 |
import { IConversation, IReference } from '@/interfaces/database/chat';
|
| 3 |
import { EmptyConversationId, variableEnabledFieldMap } from './constants';
|
| 4 |
+
import { IClientConversation, IMessage } from './interface';
|
| 5 |
|
| 6 |
export const excludeUnEnabledVariables = (values: any) => {
|
| 7 |
const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> =
|
|
|
|
| 22 |
const documentIds = data.reference.reduce(
|
| 23 |
(pre: Array<string>, cur: IReference) => {
|
| 24 |
cur.doc_aggs
|
| 25 |
+
?.map((x) => x.doc_id)
|
| 26 |
.forEach((x) => {
|
| 27 |
if (pre.every((y) => y !== x)) {
|
| 28 |
pre.push(x);
|
|
|
|
| 34 |
);
|
| 35 |
return documentIds.join(',');
|
| 36 |
};
|
| 37 |
+
|
| 38 |
+
export const buildMessageItemReference = (
|
| 39 |
+
conversation: IClientConversation,
|
| 40 |
+
message: IMessage,
|
| 41 |
+
) => {
|
| 42 |
+
const assistantMessages = conversation.message
|
| 43 |
+
?.filter((x) => x.role === MessageType.Assistant)
|
| 44 |
+
.slice(1);
|
| 45 |
+
const referenceIndex = assistantMessages.findIndex(
|
| 46 |
+
(x) => x.id === message.id,
|
| 47 |
+
);
|
| 48 |
+
const reference = message?.reference
|
| 49 |
+
? message?.reference
|
| 50 |
+
: conversation.reference[referenceIndex];
|
| 51 |
+
|
| 52 |
+
return reference;
|
| 53 |
+
};
|
web/src/utils/authorizationUtil.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { Authorization, Token, UserInfo } from '@/constants/authorization';
|
| 2 |
-
|
| 3 |
const KeySet = [Authorization, Token, UserInfo];
|
| 4 |
|
| 5 |
const storage = {
|
|
@@ -21,7 +21,7 @@ const storage = {
|
|
| 21 |
setToken: (value: string) => {
|
| 22 |
localStorage.setItem(Token, value);
|
| 23 |
},
|
| 24 |
-
setUserInfo: (value: string |
|
| 25 |
let valueStr = typeof value !== 'string' ? JSON.stringify(value) : value;
|
| 26 |
localStorage.setItem(UserInfo, valueStr);
|
| 27 |
},
|
|
@@ -46,4 +46,13 @@ const storage = {
|
|
| 46 |
},
|
| 47 |
};
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
export default storage;
|
|
|
|
| 1 |
import { Authorization, Token, UserInfo } from '@/constants/authorization';
|
| 2 |
+
import { getSearchValue } from './commonUtil';
|
| 3 |
const KeySet = [Authorization, Token, UserInfo];
|
| 4 |
|
| 5 |
const storage = {
|
|
|
|
| 21 |
setToken: (value: string) => {
|
| 22 |
localStorage.setItem(Token, value);
|
| 23 |
},
|
| 24 |
+
setUserInfo: (value: string | Record<string, unknown>) => {
|
| 25 |
let valueStr = typeof value !== 'string' ? JSON.stringify(value) : value;
|
| 26 |
localStorage.setItem(UserInfo, valueStr);
|
| 27 |
},
|
|
|
|
| 46 |
},
|
| 47 |
};
|
| 48 |
|
| 49 |
+
export const getAuthorization = () => {
|
| 50 |
+
const sharedId = getSearchValue('shared_id');
|
| 51 |
+
const authorization = sharedId
|
| 52 |
+
? 'Bearer ' + sharedId
|
| 53 |
+
: storage.getAuthorization() || '';
|
| 54 |
+
|
| 55 |
+
return authorization;
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
export default storage;
|
web/src/utils/request.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
import { Authorization } from '@/constants/authorization';
|
| 2 |
import i18n from '@/locales/config';
|
| 3 |
-
import authorizationUtil from '@/utils/authorizationUtil';
|
| 4 |
import { message, notification } from 'antd';
|
| 5 |
import { history } from 'umi';
|
| 6 |
import { RequestMethod, extend } from 'umi-request';
|
| 7 |
-
import { convertTheKeysOfTheObjectToSnake
|
| 8 |
|
| 9 |
-
const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.';
|
| 10 |
|
| 11 |
const RetcodeMessage = {
|
| 12 |
200: i18n.t('message.200'),
|
|
@@ -41,9 +41,7 @@ type ResultCode =
|
|
| 41 |
| 502
|
| 42 |
| 503
|
| 43 |
| 504;
|
| 44 |
-
|
| 45 |
-
* 异常处理程序
|
| 46 |
-
*/
|
| 47 |
interface ResponseType {
|
| 48 |
retcode: number;
|
| 49 |
data: any;
|
|
@@ -55,7 +53,6 @@ const errorHandler = (error: {
|
|
| 55 |
message: string;
|
| 56 |
}): Response => {
|
| 57 |
const { response } = error;
|
| 58 |
-
// 手动中断请求 abort
|
| 59 |
if (error.message === ABORT_REQUEST_ERR_MESSAGE) {
|
| 60 |
console.log('user abort request');
|
| 61 |
} else {
|
|
@@ -77,20 +74,13 @@ const errorHandler = (error: {
|
|
| 77 |
return response;
|
| 78 |
};
|
| 79 |
|
| 80 |
-
/**
|
| 81 |
-
* 配置request请求时的默认参数
|
| 82 |
-
*/
|
| 83 |
const request: RequestMethod = extend({
|
| 84 |
-
errorHandler,
|
| 85 |
timeout: 300000,
|
| 86 |
getResponse: true,
|
| 87 |
});
|
| 88 |
|
| 89 |
request.interceptors.request.use((url: string, options: any) => {
|
| 90 |
-
const sharedId = getSearchValue('shared_id');
|
| 91 |
-
const authorization = sharedId
|
| 92 |
-
? 'Bearer ' + sharedId
|
| 93 |
-
: authorizationUtil.getAuthorization();
|
| 94 |
const data = convertTheKeysOfTheObjectToSnake(options.data);
|
| 95 |
const params = convertTheKeysOfTheObjectToSnake(options.params);
|
| 96 |
|
|
@@ -101,7 +91,9 @@ request.interceptors.request.use((url: string, options: any) => {
|
|
| 101 |
data,
|
| 102 |
params,
|
| 103 |
headers: {
|
| 104 |
-
...(options.skipToken
|
|
|
|
|
|
|
| 105 |
...options.headers,
|
| 106 |
},
|
| 107 |
interceptors: true,
|
|
@@ -109,16 +101,11 @@ request.interceptors.request.use((url: string, options: any) => {
|
|
| 109 |
};
|
| 110 |
});
|
| 111 |
|
| 112 |
-
/*
|
| 113 |
-
* 请求response拦截器
|
| 114 |
-
* */
|
| 115 |
-
|
| 116 |
request.interceptors.response.use(async (response: any, options) => {
|
| 117 |
if (options.responseType === 'blob') {
|
| 118 |
return response;
|
| 119 |
}
|
| 120 |
const data: ResponseType = await response.clone().json();
|
| 121 |
-
// response 拦截
|
| 122 |
|
| 123 |
if (data.retcode === 401 || data.retcode === 401) {
|
| 124 |
notification.error({
|
|
|
|
| 1 |
import { Authorization } from '@/constants/authorization';
|
| 2 |
import i18n from '@/locales/config';
|
| 3 |
+
import authorizationUtil, { getAuthorization } from '@/utils/authorizationUtil';
|
| 4 |
import { message, notification } from 'antd';
|
| 5 |
import { history } from 'umi';
|
| 6 |
import { RequestMethod, extend } from 'umi-request';
|
| 7 |
+
import { convertTheKeysOfTheObjectToSnake } from './commonUtil';
|
| 8 |
|
| 9 |
+
const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.';
|
| 10 |
|
| 11 |
const RetcodeMessage = {
|
| 12 |
200: i18n.t('message.200'),
|
|
|
|
| 41 |
| 502
|
| 42 |
| 503
|
| 43 |
| 504;
|
| 44 |
+
|
|
|
|
|
|
|
| 45 |
interface ResponseType {
|
| 46 |
retcode: number;
|
| 47 |
data: any;
|
|
|
|
| 53 |
message: string;
|
| 54 |
}): Response => {
|
| 55 |
const { response } = error;
|
|
|
|
| 56 |
if (error.message === ABORT_REQUEST_ERR_MESSAGE) {
|
| 57 |
console.log('user abort request');
|
| 58 |
} else {
|
|
|
|
| 74 |
return response;
|
| 75 |
};
|
| 76 |
|
|
|
|
|
|
|
|
|
|
| 77 |
const request: RequestMethod = extend({
|
| 78 |
+
errorHandler,
|
| 79 |
timeout: 300000,
|
| 80 |
getResponse: true,
|
| 81 |
});
|
| 82 |
|
| 83 |
request.interceptors.request.use((url: string, options: any) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
const data = convertTheKeysOfTheObjectToSnake(options.data);
|
| 85 |
const params = convertTheKeysOfTheObjectToSnake(options.params);
|
| 86 |
|
|
|
|
| 91 |
data,
|
| 92 |
params,
|
| 93 |
headers: {
|
| 94 |
+
...(options.skipToken
|
| 95 |
+
? undefined
|
| 96 |
+
: { [Authorization]: getAuthorization() }),
|
| 97 |
...options.headers,
|
| 98 |
},
|
| 99 |
interceptors: true,
|
|
|
|
| 101 |
};
|
| 102 |
});
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
request.interceptors.response.use(async (response: any, options) => {
|
| 105 |
if (options.responseType === 'blob') {
|
| 106 |
return response;
|
| 107 |
}
|
| 108 |
const data: ResponseType = await response.clone().json();
|
|
|
|
| 109 |
|
| 110 |
if (data.retcode === 401 || data.retcode === 401) {
|
| 111 |
notification.error({
|