* feat: Fixed the issue where the greeting would disappear when clicking on a new dialog * feat: replace favicon with logo.svg * feat: if the backend returns 401, it will jump to the login page. * feat: unavailable llm models appear disabled * feat: display chunk token number when category of knowledge as generaltags/v0.1.0
| @@ -11,6 +11,7 @@ export default defineConfig({ | |||
| esbuildMinifyIIFE: true, | |||
| icons: {}, | |||
| hash: true, | |||
| favicons: ['/logo.svg'], | |||
| history: { | |||
| type: 'browser', | |||
| }, | |||
| @@ -0,0 +1,29 @@ | |||
| <svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M3.43265 20.7677C4.15835 21.5062 4.15834 22.7035 3.43262 23.4419L3.39546 23.4797C2.66974 24.2182 1.49312 24.2182 0.767417 23.4797C0.0417107 22.7412 0.0417219 21.544 0.767442 20.8055L0.804608 20.7677C1.53033 20.0292 2.70694 20.0293 3.43265 20.7677Z" | |||
| fill="#B2DDFF" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M12.1689 21.3375C12.8933 22.0773 12.8912 23.2746 12.1641 24.0117L7.01662 29.2307C6.2896 29.9678 5.11299 29.9657 4.38859 29.2259C3.66419 28.4861 3.66632 27.2888 4.39334 26.5517L9.54085 21.3327C10.2679 20.5956 11.4445 20.5977 12.1689 21.3375Z" | |||
| fill="#53B1FD" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M19.1551 30.3217C19.7244 29.4528 20.8781 29.218 21.7321 29.7973L21.8436 29.8729C22.6975 30.4522 22.9283 31.6262 22.359 32.4952C21.7897 33.3641 20.6359 33.5989 19.782 33.0196L19.6705 32.944C18.8165 32.3647 18.5858 31.1907 19.1551 30.3217Z" | |||
| fill="#B2DDFF" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M31.4184 20.6544C32.1441 21.3929 32.1441 22.5902 31.4184 23.3286L28.8911 25.9003C28.1654 26.6388 26.9887 26.6388 26.263 25.9003C25.5373 25.1619 25.5373 23.9646 26.263 23.2261L28.7903 20.6544C29.516 19.916 30.6927 19.916 31.4184 20.6544Z" | |||
| fill="#53B1FD" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M31.4557 11.1427C32.1814 11.8812 32.1814 13.0785 31.4557 13.8169L12.7797 32.8209C12.054 33.5594 10.8774 33.5594 10.1517 32.8209C9.42599 32.0825 9.42599 30.8852 10.1517 30.1467L28.8277 11.1427C29.5534 10.4043 30.73 10.4043 31.4557 11.1427Z" | |||
| fill="#1570EF" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M27.925 5.29994C28.6508 6.0384 28.6508 7.23568 27.925 7.97414L17.184 18.9038C16.4583 19.6423 15.2817 19.6423 14.556 18.9038C13.8303 18.1653 13.8303 16.9681 14.556 16.2296L25.297 5.29994C26.0227 4.56148 27.1993 4.56148 27.925 5.29994Z" | |||
| fill="#1570EF" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M22.256 1.59299C22.9822 2.33095 22.983 3.52823 22.2578 4.26718L8.45055 18.3358C7.72533 19.0748 6.54871 19.0756 5.82251 18.3376C5.09631 17.5996 5.09552 16.4024 5.82075 15.6634L19.6279 1.59478C20.3532 0.855827 21.5298 0.855022 22.256 1.59299Z" | |||
| fill="#1570EF" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M8.58225 6.09619C9.30671 6.83592 9.30469 8.0332 8.57772 8.77038L3.17006 14.2541C2.4431 14.9913 1.26649 14.9893 0.542025 14.2495C-0.182438 13.5098 -0.180413 12.3125 0.546548 11.5753L5.95421 6.09159C6.68117 5.3544 7.85778 5.35646 8.58225 6.09619Z" | |||
| fill="#53B1FD" /> | |||
| <path fill-rule="evenodd" clip-rule="evenodd" | |||
| d="M11.893 0.624023C12.9193 0.624023 13.7513 1.47063 13.7513 2.51497V2.70406C13.7513 3.7484 12.9193 4.59501 11.893 4.59501C10.8667 4.59501 10.0347 3.7484 10.0347 2.70406V2.51497C10.0347 1.47063 10.8667 0.624023 11.893 0.624023Z" | |||
| fill="#B2DDFF" /> | |||
| </svg> | |||
| @@ -30,6 +30,7 @@ export const useSelectLlmOptions = () => { | |||
| options: value.map((x) => ({ | |||
| label: x.llm_name, | |||
| value: x.llm_name, | |||
| disabled: !x.available, | |||
| })), | |||
| }; | |||
| }); | |||
| @@ -10,10 +10,13 @@ import { | |||
| import { | |||
| Button, | |||
| Divider, | |||
| Flex, | |||
| Form, | |||
| Input, | |||
| InputNumber, | |||
| Radio, | |||
| Select, | |||
| Slider, | |||
| Space, | |||
| Typography, | |||
| Upload, | |||
| @@ -80,6 +83,7 @@ const Configuration = () => { | |||
| 'permission', | |||
| 'embd_id', | |||
| 'parser_id', | |||
| 'parser_config.chunk_token_num', | |||
| ]), | |||
| avatar: fileList, | |||
| }); | |||
| @@ -144,6 +148,7 @@ const Configuration = () => { | |||
| name="embd_id" | |||
| label="Embedding Model" | |||
| rules={[{ required: true }]} | |||
| tooltip="xx" | |||
| > | |||
| <Select | |||
| placeholder="Please select a country" | |||
| @@ -153,6 +158,7 @@ const Configuration = () => { | |||
| <Form.Item | |||
| name="parser_id" | |||
| label="Knowledge base category" | |||
| tooltip="xx" | |||
| rules={[{ required: true }]} | |||
| > | |||
| <Select placeholder="Please select a country"> | |||
| @@ -163,6 +169,48 @@ const Configuration = () => { | |||
| ))} | |||
| </Select> | |||
| </Form.Item> | |||
| <Form.Item noStyle dependencies={['parser_id']}> | |||
| {({ getFieldValue }) => { | |||
| const parserId = getFieldValue('parser_id'); | |||
| if (parserId === 'general') { | |||
| return ( | |||
| <Form.Item label="Chunk token number" tooltip="xxx"> | |||
| <Flex gap={20} align="center"> | |||
| <Flex flex={1}> | |||
| <Form.Item | |||
| name={['parser_config', 'chunk_token_num']} | |||
| noStyle | |||
| initialValue={128} | |||
| rules={[ | |||
| { required: true, message: 'Province is required' }, | |||
| ]} | |||
| > | |||
| <Slider className={styles.variableSlider} max={2048} /> | |||
| </Form.Item> | |||
| </Flex> | |||
| <Form.Item | |||
| name={['parser_config', 'chunk_token_num']} | |||
| noStyle | |||
| initialValue={128} | |||
| rules={[ | |||
| { required: true, message: 'Street is required' }, | |||
| ]} | |||
| > | |||
| <InputNumber | |||
| className={styles.sliderInputNumber} | |||
| max={2048} | |||
| min={0} | |||
| /> | |||
| </Form.Item> | |||
| </Flex> | |||
| </Form.Item> | |||
| ); | |||
| } | |||
| return null; | |||
| }} | |||
| </Form.Item> | |||
| <Form.Item> | |||
| <div className={styles.buttonWrapper}> | |||
| <Space> | |||
| @@ -27,4 +27,7 @@ | |||
| .buttonWrapper { | |||
| text-align: right; | |||
| } | |||
| .variableSlider { | |||
| width: 100%; | |||
| } | |||
| } | |||
| @@ -1,19 +1,18 @@ | |||
| import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg'; | |||
| import { IModalManagerChildrenProps } from '@/components/modal-manager'; | |||
| import { IDialog } from '@/interfaces/database/chat'; | |||
| import { Divider, Flex, Form, Modal, Segmented, UploadFile } from 'antd'; | |||
| import { SegmentedValue } from 'antd/es/segmented'; | |||
| import omit from 'lodash/omit'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import AssistantSetting from './assistant-setting'; | |||
| import ModelSetting from './model-setting'; | |||
| import PromptEngine from './prompt-engine'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | |||
| import { variableEnabledFieldMap } from '../constants'; | |||
| import { useFetchDialog, useResetCurrentDialog, useSetDialog } from '../hooks'; | |||
| import { IPromptConfigParameters } from '../interface'; | |||
| import { excludeUnEnabledVariables } from '../utils'; | |||
| import AssistantSetting from './assistant-setting'; | |||
| import { useFetchModelId } from './hooks'; | |||
| import ModelSetting from './model-setting'; | |||
| import PromptEngine from './prompt-engine'; | |||
| import styles from './index.less'; | |||
| enum ConfigurationSegmented { | |||
| @@ -45,22 +44,27 @@ const validateMessages = { | |||
| }; | |||
| interface IProps extends IModalManagerChildrenProps { | |||
| id: string; | |||
| initialDialog: IDialog; | |||
| loading: boolean; | |||
| onOk: (dialog: IDialog) => void; | |||
| clearDialog: () => void; | |||
| } | |||
| const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| const ChatConfigurationModal = ({ | |||
| visible, | |||
| hideModal, | |||
| initialDialog, | |||
| loading, | |||
| onOk, | |||
| clearDialog, | |||
| }: IProps) => { | |||
| const [form] = Form.useForm(); | |||
| const [value, setValue] = useState<ConfigurationSegmented>( | |||
| ConfigurationSegmented.AssistantSetting, | |||
| ); | |||
| const promptEngineRef = useRef<Array<IPromptConfigParameters>>([]); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']); | |||
| const modelId = useFetchModelId(visible); | |||
| const setDialog = useSetDialog(); | |||
| const currentDialog = useFetchDialog(id, visible); | |||
| const { resetCurrentDialog } = useResetCurrentDialog(); | |||
| const handleOk = async () => { | |||
| const values = await form.validateFields(); | |||
| const nextValues: any = omit(values, [ | |||
| @@ -78,7 +82,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| } | |||
| const finalValues = { | |||
| dialog_id: id, | |||
| dialog_id: initialDialog.id, | |||
| ...nextValues, | |||
| prompt_config: { | |||
| ...nextValues.prompt_config, | |||
| @@ -87,13 +91,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| }, | |||
| icon, | |||
| }; | |||
| console.info(promptEngineRef.current); | |||
| console.info(nextValues); | |||
| console.info(finalValues); | |||
| const retcode: number = await setDialog(finalValues); | |||
| if (retcode === 0) { | |||
| hideModal(); | |||
| } | |||
| onOk(finalValues); | |||
| }; | |||
| const handleCancel = () => { | |||
| @@ -105,7 +103,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| }; | |||
| const handleModalAfterClose = () => { | |||
| resetCurrentDialog(); | |||
| clearDialog(); | |||
| form.resetFields(); | |||
| }; | |||
| @@ -124,19 +122,19 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| useEffect(() => { | |||
| if (visible) { | |||
| const icon = currentDialog.icon; | |||
| const icon = initialDialog.icon; | |||
| let fileList: UploadFile[] = []; | |||
| if (icon) { | |||
| fileList = [{ uid: '1', name: 'file', thumbUrl: icon, status: 'done' }]; | |||
| } | |||
| form.setFieldsValue({ | |||
| ...currentDialog, | |||
| ...initialDialog, | |||
| icon: fileList, | |||
| llm_id: currentDialog.llm_id ?? modelId, | |||
| llm_id: initialDialog.llm_id ?? modelId, | |||
| }); | |||
| } | |||
| }, [currentDialog, form, visible, modelId]); | |||
| }, [initialDialog, form, visible, modelId]); | |||
| return ( | |||
| <Modal | |||
| @@ -54,7 +54,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { | |||
| name="llm_id" | |||
| rules={[{ required: true, message: 'Please select!' }]} | |||
| > | |||
| <Select options={modelOptions} /> | |||
| <Select options={modelOptions} showSearch /> | |||
| </Form.Item> | |||
| <Divider></Divider> | |||
| <Form.Item | |||
| @@ -45,24 +45,42 @@ export const useSetDialog = () => { | |||
| return setDialog; | |||
| }; | |||
| export const useFetchDialog = (dialogId: string, visible: boolean): IDialog => { | |||
| const dispatch = useDispatch(); | |||
| export const useSelectCurrentDialog = () => { | |||
| const currentDialog: IDialog = useSelector( | |||
| (state: any) => state.chatModel.currentDialog, | |||
| ); | |||
| const fetchDialog = useCallback(() => { | |||
| if (dialogId) { | |||
| dispatch({ | |||
| type: 'chatModel/getDialog', | |||
| payload: { dialog_id: dialogId }, | |||
| }); | |||
| } | |||
| }, [dispatch, dialogId]); | |||
| return currentDialog; | |||
| }; | |||
| export const useFetchDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| const fetchDialog = useCallback( | |||
| (dialogId: string, needToBeSaved = true) => { | |||
| if (dialogId) { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/getDialog', | |||
| payload: { dialog_id: dialogId, needToBeSaved }, | |||
| }); | |||
| } | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| return fetchDialog; | |||
| }; | |||
| export const useFetchDialogOnMount = ( | |||
| dialogId: string, | |||
| visible: boolean, | |||
| ): IDialog => { | |||
| const currentDialog: IDialog = useSelectCurrentDialog(); | |||
| const fetchDialog = useFetchDialog(); | |||
| useEffect(() => { | |||
| if (dialogId && visible) { | |||
| fetchDialog(); | |||
| fetchDialog(dialogId); | |||
| } | |||
| }, [dialogId, fetchDialog, visible]); | |||
| @@ -123,14 +141,6 @@ export const useSelectPromptConfigParameters = (): VariableTableDataType[] => { | |||
| return finalParameters; | |||
| }; | |||
| export const useSelectCurrentDialog = () => { | |||
| const currentDialog: IDialog = useSelector( | |||
| (state: any) => state.chatModel.currentDialog, | |||
| ); | |||
| return currentDialog; | |||
| }; | |||
| export const useRemoveDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| @@ -231,6 +241,57 @@ export const useHandleItemHover = () => { | |||
| }; | |||
| }; | |||
| export const useEditDialog = () => { | |||
| const [dialog, setDialog] = useState<IDialog>({} as IDialog); | |||
| const fetchDialog = useFetchDialog(); | |||
| const submitDialog = useSetDialog(); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']); | |||
| const { | |||
| visible: dialogEditVisible, | |||
| hideModal: hideDialogEditModal, | |||
| showModal: showDialogEditModal, | |||
| } = useSetModalState(); | |||
| const onDialogEditOk = useCallback( | |||
| async (dialog: IDialog) => { | |||
| const ret = await submitDialog(dialog); | |||
| if (ret === 0) { | |||
| hideDialogEditModal(); | |||
| } | |||
| }, | |||
| [submitDialog, hideDialogEditModal], | |||
| ); | |||
| const handleShowDialogEditModal = useCallback( | |||
| async (dialogId?: string) => { | |||
| if (dialogId) { | |||
| const ret = await fetchDialog(dialogId, false); | |||
| if (ret.retcode === 0) { | |||
| setDialog(ret.data); | |||
| } | |||
| } | |||
| showDialogEditModal(); | |||
| }, | |||
| [showDialogEditModal, fetchDialog], | |||
| ); | |||
| const clearDialog = useCallback(() => { | |||
| setDialog({} as IDialog); | |||
| }, []); | |||
| return { | |||
| dialogSettingLoading: loading, | |||
| initialDialog: dialog, | |||
| onDialogEditOk, | |||
| dialogEditVisible, | |||
| hideDialogEditModal, | |||
| showDialogEditModal: handleShowDialogEditModal, | |||
| clearDialog, | |||
| }; | |||
| }; | |||
| //#region conversation | |||
| export const useFetchConversationList = () => { | |||
| @@ -20,8 +20,9 @@ import ChatContainer from './chat-container'; | |||
| import { | |||
| useClickConversationCard, | |||
| useClickDialogCard, | |||
| useEditDialog, | |||
| useFetchConversationList, | |||
| useFetchDialog, | |||
| useFetchDialogOnMount, | |||
| useGetChatSearchParams, | |||
| useHandleItemHover, | |||
| useRemoveConversation, | |||
| @@ -60,8 +61,17 @@ const Chat = () => { | |||
| hideConversationRenameModal, | |||
| showConversationRenameModal, | |||
| } = useRenameConversation(); | |||
| const { | |||
| dialogSettingLoading, | |||
| initialDialog, | |||
| onDialogEditOk, | |||
| dialogEditVisible, | |||
| clearDialog, | |||
| hideDialogEditModal, | |||
| showDialogEditModal, | |||
| } = useEditDialog(); | |||
| useFetchDialog(dialogId, true); | |||
| useFetchDialogOnMount(dialogId, true); | |||
| const handleAppCardEnter = (id: string) => () => { | |||
| handleItemEnter(id); | |||
| @@ -76,10 +86,7 @@ const Chat = () => { | |||
| (info: any) => { | |||
| info?.domEvent?.preventDefault(); | |||
| info?.domEvent?.stopPropagation(); | |||
| // if (dialogId) { | |||
| setCurrentDialog(dialogId ?? ''); | |||
| // } | |||
| showModal(); | |||
| showDialogEditModal(dialogId); | |||
| }; | |||
| const handleRemoveDialog = | |||
| @@ -276,10 +283,13 @@ const Chat = () => { | |||
| <Divider type={'vertical'} className={styles.divider}></Divider> | |||
| <ChatContainer></ChatContainer> | |||
| <ChatConfigurationModal | |||
| visible={visible} | |||
| showModal={showModal} | |||
| hideModal={hideModal} | |||
| id={currentDialog.id} | |||
| visible={dialogEditVisible} | |||
| initialDialog={initialDialog} | |||
| showModal={showDialogEditModal} | |||
| hideModal={hideDialogEditModal} | |||
| loading={dialogSettingLoading} | |||
| onOk={onDialogEditOk} | |||
| clearDialog={clearDialog} | |||
| ></ChatConfigurationModal> | |||
| <RenameModal | |||
| visible={conversationRenameVisible} | |||
| @@ -63,16 +63,21 @@ const model: DvaModel<ChatModelState> = { | |||
| effects: { | |||
| *getDialog({ payload }, { call, put }) { | |||
| const { data } = yield call(chatService.getDialog, payload); | |||
| if (data.retcode === 0) { | |||
| const needToBeSaved = | |||
| payload.needToBeSaved === undefined ? true : payload.needToBeSaved; | |||
| const { data } = yield call(chatService.getDialog, { | |||
| dialog_id: payload.dialog_id, | |||
| }); | |||
| if (data.retcode === 0 && needToBeSaved) { | |||
| yield put({ type: 'setCurrentDialog', payload: data.data }); | |||
| } | |||
| return data; | |||
| }, | |||
| *setDialog({ payload }, { call, put }) { | |||
| const { data } = yield call(chatService.setDialog, payload); | |||
| if (data.retcode === 0) { | |||
| yield put({ type: 'listDialog' }); | |||
| message.success('Created successfully !'); | |||
| message.success(payload.dialog_id ? 'Modified!' : 'Created!'); | |||
| } | |||
| return data.retcode; | |||
| }, | |||
| @@ -1,3 +0,0 @@ | |||
| import { createBrowserHistory } from 'history'; | |||
| export const history = createBrowserHistory(); | |||
| @@ -1,11 +1,8 @@ | |||
| import { message, notification } from 'antd'; | |||
| import { RequestMethod, extend } from 'umi-request'; | |||
| import { Authorization } from '@/constants/authorization'; | |||
| import api from '@/utils/api'; | |||
| import authorizationUtil from '@/utils/authorizationUtil'; | |||
| const { login } = api; | |||
| import { message, notification } from 'antd'; | |||
| import { history } from 'umi'; | |||
| import { RequestMethod, extend } from 'umi-request'; | |||
| const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message | |||
| @@ -120,7 +117,7 @@ request.interceptors.response.use(async (response: any, options) => { | |||
| duration: 3, | |||
| }); | |||
| authorizationUtil.removeAll(); | |||
| // history.push('/login'); // Will not jump to the login page | |||
| history.push('/login'); // Will not jump to the login page | |||
| } else if (data.retcode !== 0) { | |||
| if (data.retcode === 100) { | |||
| message.error(data.retmsg); | |||