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 <[email protected]>",
         | 
| 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 <[email protected]>",
         | 
| 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({
         |