| @@ -109,6 +109,7 @@ class VariableEntity(BaseModel): | |||
| description: str = "" | |||
| type: VariableEntityType | |||
| required: bool = False | |||
| hide: bool = False | |||
| max_length: Optional[int] = None | |||
| options: Sequence[str] = Field(default_factory=list) | |||
| allowed_file_types: Sequence[FileType] = Field(default_factory=list) | |||
| @@ -234,9 +234,14 @@ const ConfigModal: FC<IConfigModalProps> = ({ | |||
| )} | |||
| <div className='!mt-5 flex h-6 items-center space-x-2'> | |||
| <Checkbox checked={tempPayload.required} onCheck={() => handlePayloadChange('required')(!tempPayload.required)} /> | |||
| <Checkbox checked={tempPayload.required} disabled={tempPayload.hide} onCheck={() => handlePayloadChange('required')(!tempPayload.required)} /> | |||
| <span className='system-sm-semibold text-text-secondary'>{t('appDebug.variableConfig.required')}</span> | |||
| </div> | |||
| <div className='!mt-5 flex h-6 items-center space-x-2'> | |||
| <Checkbox checked={tempPayload.hide} disabled={tempPayload.required} onCheck={() => handlePayloadChange('hide')(!tempPayload.hide)} /> | |||
| <span className='system-sm-semibold text-text-secondary'>{t('appDebug.variableConfig.hide')}</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <ModalFoot | |||
| @@ -47,6 +47,7 @@ const ChatWrapper = () => { | |||
| clearChatList, | |||
| setClearChatList, | |||
| setIsResponding, | |||
| allInputsHidden, | |||
| } = useChatWithHistoryContext() | |||
| const appConfig = useMemo(() => { | |||
| const config = appParams || {} | |||
| @@ -81,6 +82,9 @@ const ChatWrapper = () => { | |||
| ) | |||
| const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current | |||
| const inputDisabled = useMemo(() => { | |||
| if (allInputsHidden) | |||
| return false | |||
| let hasEmptyInput = '' | |||
| let fileIsUploading = false | |||
| const requiredVars = inputsForms.filter(({ required }) => required) | |||
| @@ -110,7 +114,7 @@ const ChatWrapper = () => { | |||
| if (fileIsUploading) | |||
| return true | |||
| return false | |||
| }, [inputsFormValue, inputsForms]) | |||
| }, [inputsFormValue, inputsForms, allInputsHidden]) | |||
| useEffect(() => { | |||
| if (currentChatInstanceRef.current) | |||
| @@ -161,7 +165,7 @@ const ChatWrapper = () => { | |||
| const [collapsed, setCollapsed] = useState(!!currentConversationId) | |||
| const chatNode = useMemo(() => { | |||
| if (!inputsForms.length) | |||
| if (allInputsHidden || !inputsForms.length) | |||
| return null | |||
| if (isMobile) { | |||
| if (!currentConversationId) | |||
| @@ -171,7 +175,7 @@ const ChatWrapper = () => { | |||
| else { | |||
| return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} /> | |||
| } | |||
| }, [inputsForms.length, isMobile, currentConversationId, collapsed]) | |||
| }, [inputsForms.length, isMobile, currentConversationId, collapsed, allInputsHidden]) | |||
| const welcome = useMemo(() => { | |||
| const welcomeMessage = chatList.find(item => item.isOpeningStatement) | |||
| @@ -181,7 +185,7 @@ const ChatWrapper = () => { | |||
| return null | |||
| if (!welcomeMessage) | |||
| return null | |||
| if (!collapsed && inputsForms.length > 0) | |||
| if (!collapsed && inputsForms.length > 0 && !allInputsHidden) | |||
| return null | |||
| if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { | |||
| return ( | |||
| @@ -218,7 +222,7 @@ const ChatWrapper = () => { | |||
| </div> | |||
| </div> | |||
| ) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden]) | |||
| const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) | |||
| ? <AnswerIcon | |||
| @@ -60,6 +60,7 @@ export type ChatWithHistoryContextValue = { | |||
| setIsResponding: (state: boolean) => void, | |||
| currentConversationInputs: Record<string, any> | null, | |||
| setCurrentConversationInputs: (v: Record<string, any>) => void, | |||
| allInputsHidden: boolean, | |||
| } | |||
| export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ | |||
| @@ -95,5 +96,6 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue> | |||
| setIsResponding: noop, | |||
| currentConversationInputs: {}, | |||
| setCurrentConversationInputs: noop, | |||
| allInputsHidden: false, | |||
| }) | |||
| export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) | |||
| @@ -240,6 +240,11 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| } | |||
| }) | |||
| }, [appParams]) | |||
| const allInputsHidden = useMemo(() => { | |||
| return inputsForms.length > 0 && inputsForms.every(item => item.hide === true) | |||
| }, [inputsForms]) | |||
| useEffect(() => { | |||
| const conversationInputs: Record<string, any> = {} | |||
| @@ -304,6 +309,9 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| const { notify } = useToastContext() | |||
| const checkInputsRequired = useCallback((silent?: boolean) => { | |||
| if (allInputsHidden) | |||
| return true | |||
| let hasEmptyInput = '' | |||
| let fileIsUploading = false | |||
| const requiredVars = inputsForms.filter(({ required }) => required) | |||
| @@ -339,7 +347,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| } | |||
| return true | |||
| }, [inputsForms, notify, t]) | |||
| }, [inputsForms, notify, t, allInputsHidden]) | |||
| const handleStartChat = useCallback((callback: any) => { | |||
| if (checkInputsRequired()) { | |||
| setShowNewConversationItemInList(true) | |||
| @@ -507,5 +515,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| setIsResponding, | |||
| currentConversationInputs, | |||
| setCurrentConversationInputs, | |||
| allInputsHidden, | |||
| } | |||
| } | |||
| @@ -161,6 +161,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ | |||
| setIsResponding, | |||
| currentConversationInputs, | |||
| setCurrentConversationInputs, | |||
| allInputsHidden, | |||
| } = useChatWithHistory(installedAppInfo) | |||
| return ( | |||
| @@ -206,6 +207,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ | |||
| setIsResponding, | |||
| currentConversationInputs, | |||
| setCurrentConversationInputs, | |||
| allInputsHidden, | |||
| }}> | |||
| <ChatWithHistory className={className} /> | |||
| </ChatWithHistoryContext.Provider> | |||
| @@ -36,9 +36,11 @@ const InputsFormContent = ({ showTip }: Props) => { | |||
| }) | |||
| }, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs]) | |||
| const visibleInputsForms = inputsForms.filter(form => form.hide !== true) | |||
| return ( | |||
| <div className='space-y-4'> | |||
| {inputsForms.map(form => ( | |||
| {visibleInputsForms.map(form => ( | |||
| <div key={form.variable} className='space-y-1'> | |||
| <div className='flex h-6 items-center gap-1'> | |||
| <div className='system-md-semibold text-text-secondary'>{form.label}</div> | |||
| @@ -21,9 +21,14 @@ const InputsFormNode = ({ | |||
| isMobile, | |||
| currentConversationId, | |||
| handleStartChat, | |||
| allInputsHidden, | |||
| themeBuilder, | |||
| inputsForms, | |||
| } = useChatWithHistoryContext() | |||
| if (allInputsHidden || inputsForms.length === 0) | |||
| return null | |||
| return ( | |||
| <div className={cn('flex flex-col items-center px-4 pt-6', isMobile && 'pt-4')}> | |||
| <div className={cn( | |||
| @@ -143,5 +143,6 @@ export type InputForm = { | |||
| label: string | |||
| variable: any | |||
| required: boolean | |||
| hide: boolean | |||
| [key: string]: any | |||
| } | |||
| @@ -48,6 +48,7 @@ const ChatWrapper = () => { | |||
| clearChatList, | |||
| setClearChatList, | |||
| setIsResponding, | |||
| allInputsHidden, | |||
| } = useEmbeddedChatbotContext() | |||
| const appConfig = useMemo(() => { | |||
| const config = appParams || {} | |||
| @@ -82,6 +83,9 @@ const ChatWrapper = () => { | |||
| ) | |||
| const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current | |||
| const inputDisabled = useMemo(() => { | |||
| if (allInputsHidden) | |||
| return false | |||
| let hasEmptyInput = '' | |||
| let fileIsUploading = false | |||
| const requiredVars = inputsForms.filter(({ required }) => required) | |||
| @@ -111,7 +115,7 @@ const ChatWrapper = () => { | |||
| if (fileIsUploading) | |||
| return true | |||
| return false | |||
| }, [inputsFormValue, inputsForms]) | |||
| }, [inputsFormValue, inputsForms, allInputsHidden]) | |||
| useEffect(() => { | |||
| if (currentChatInstanceRef.current) | |||
| @@ -160,7 +164,7 @@ const ChatWrapper = () => { | |||
| const [collapsed, setCollapsed] = useState(!!currentConversationId) | |||
| const chatNode = useMemo(() => { | |||
| if (!inputsForms.length) | |||
| if (allInputsHidden || !inputsForms.length) | |||
| return null | |||
| if (isMobile) { | |||
| if (!currentConversationId) | |||
| @@ -170,7 +174,7 @@ const ChatWrapper = () => { | |||
| else { | |||
| return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} /> | |||
| } | |||
| }, [inputsForms.length, isMobile, currentConversationId, collapsed]) | |||
| }, [inputsForms.length, isMobile, currentConversationId, collapsed, allInputsHidden]) | |||
| const welcome = useMemo(() => { | |||
| const welcomeMessage = chatList.find(item => item.isOpeningStatement) | |||
| @@ -180,7 +184,7 @@ const ChatWrapper = () => { | |||
| return null | |||
| if (!welcomeMessage) | |||
| return null | |||
| if (!collapsed && inputsForms.length > 0) | |||
| if (!collapsed && inputsForms.length > 0 && !allInputsHidden) | |||
| return null | |||
| if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { | |||
| return ( | |||
| @@ -215,7 +219,7 @@ const ChatWrapper = () => { | |||
| </div> | |||
| </div> | |||
| ) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden]) | |||
| const answerIcon = isDify() | |||
| ? <LogoAvatar className='relative shrink-0' /> | |||
| @@ -53,6 +53,7 @@ export type EmbeddedChatbotContextValue = { | |||
| setIsResponding: (state: boolean) => void, | |||
| currentConversationInputs: Record<string, any> | null, | |||
| setCurrentConversationInputs: (v: Record<string, any>) => void, | |||
| allInputsHidden: boolean | |||
| } | |||
| export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ | |||
| @@ -82,5 +83,6 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue> | |||
| setIsResponding: noop, | |||
| currentConversationInputs: {}, | |||
| setCurrentConversationInputs: noop, | |||
| allInputsHidden: false, | |||
| }) | |||
| export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) | |||
| @@ -235,6 +235,10 @@ export const useEmbeddedChatbot = () => { | |||
| }) | |||
| }, [initInputs, appParams]) | |||
| const allInputsHidden = useMemo(() => { | |||
| return inputsForms.length > 0 && inputsForms.every(item => item.hide === true) | |||
| }, [inputsForms]) | |||
| useEffect(() => { | |||
| // init inputs from url params | |||
| (async () => { | |||
| @@ -306,6 +310,9 @@ export const useEmbeddedChatbot = () => { | |||
| const { notify } = useToastContext() | |||
| const checkInputsRequired = useCallback((silent?: boolean) => { | |||
| if (allInputsHidden) | |||
| return true | |||
| let hasEmptyInput = '' | |||
| let fileIsUploading = false | |||
| const requiredVars = inputsForms.filter(({ required }) => required) | |||
| @@ -341,7 +348,7 @@ export const useEmbeddedChatbot = () => { | |||
| } | |||
| return true | |||
| }, [inputsForms, notify, t]) | |||
| }, [inputsForms, notify, t, allInputsHidden]) | |||
| const handleStartChat = useCallback((callback?: any) => { | |||
| if (checkInputsRequired()) { | |||
| setShowNewConversationItemInList(true) | |||
| @@ -417,5 +424,6 @@ export const useEmbeddedChatbot = () => { | |||
| setIsResponding, | |||
| currentConversationInputs, | |||
| setCurrentConversationInputs, | |||
| allInputsHidden, | |||
| } | |||
| } | |||
| @@ -168,6 +168,7 @@ const EmbeddedChatbotWrapper = () => { | |||
| setIsResponding, | |||
| currentConversationInputs, | |||
| setCurrentConversationInputs, | |||
| allInputsHidden, | |||
| } = useEmbeddedChatbot() | |||
| return <EmbeddedChatbotContext.Provider value={{ | |||
| @@ -206,6 +207,7 @@ const EmbeddedChatbotWrapper = () => { | |||
| setIsResponding, | |||
| currentConversationInputs, | |||
| setCurrentConversationInputs, | |||
| allInputsHidden, | |||
| }}> | |||
| <Chatbot /> | |||
| </EmbeddedChatbotContext.Provider> | |||
| @@ -36,9 +36,11 @@ const InputsFormContent = ({ showTip }: Props) => { | |||
| }) | |||
| }, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs]) | |||
| const visibleInputsForms = inputsForms.filter(form => form.hide !== true) | |||
| return ( | |||
| <div className='space-y-4'> | |||
| {inputsForms.map(form => ( | |||
| {visibleInputsForms.map(form => ( | |||
| <div key={form.variable} className='space-y-1'> | |||
| <div className='flex h-6 items-center gap-1'> | |||
| <div className='system-md-semibold text-text-secondary'>{form.label}</div> | |||
| @@ -22,8 +22,13 @@ const InputsFormNode = ({ | |||
| currentConversationId, | |||
| themeBuilder, | |||
| handleStartChat, | |||
| allInputsHidden, | |||
| inputsForms, | |||
| } = useEmbeddedChatbotContext() | |||
| if (allInputsHidden || inputsForms.length === 0) | |||
| return null | |||
| return ( | |||
| <div className={cn('mb-6 flex flex-col items-center px-4 pt-6', isMobile && 'mb-4 pt-4')}> | |||
| <div className={cn( | |||
| @@ -39,6 +39,7 @@ const DebugAndPreview = () => { | |||
| const nodes = useNodes<StartNodeType>() | |||
| const startNode = nodes.find(node => node.data.type === BlockEnum.Start) | |||
| const variables = startNode?.data.variables || [] | |||
| const visibleVariables = variables.filter(v => v.hide !== true) | |||
| const [showConversationVariableModal, setShowConversationVariableModal] = useState(false) | |||
| @@ -107,7 +108,7 @@ const DebugAndPreview = () => { | |||
| </ActionButton> | |||
| </Tooltip> | |||
| )} | |||
| {variables.length > 0 && ( | |||
| {visibleVariables.length > 0 && ( | |||
| <div className='relative'> | |||
| <Tooltip | |||
| popupContent={t('workflow.panel.userInputField')} | |||
| @@ -17,6 +17,7 @@ const UserInput = () => { | |||
| const nodes = useNodes<StartNodeType>() | |||
| const startNode = nodes.find(node => node.data.type === BlockEnum.Start) | |||
| const variables = startNode?.data.variables || [] | |||
| const visibleVariables = variables.filter(v => v.hide !== true) | |||
| const handleValueChange = (variable: string, v: string) => { | |||
| const { | |||
| @@ -29,13 +30,13 @@ const UserInput = () => { | |||
| }) | |||
| } | |||
| if (!variables.length) | |||
| if (!visibleVariables.length) | |||
| return null | |||
| return ( | |||
| <div className={cn('sticky top-0 z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}> | |||
| <div className='px-4 pb-4 pt-3'> | |||
| {variables.map((variable, index) => ( | |||
| {visibleVariables.map((variable, index) => ( | |||
| <div | |||
| key={variable.variable} | |||
| className='mb-4 last-of-type:mb-0' | |||
| @@ -198,6 +198,7 @@ export type InputVar = { | |||
| hint?: string | |||
| options?: string[] | |||
| value_selector?: ValueSelector | |||
| hide: boolean | |||
| } & Partial<UploadFileSetting> | |||
| export type ModelConfig = { | |||
| @@ -368,6 +368,7 @@ const translation = { | |||
| 'inputPlaceholder': 'Please input', | |||
| 'content': 'Content', | |||
| 'required': 'Required', | |||
| 'hide': 'Hide', | |||
| 'file': { | |||
| supportFileTypes: 'Support File Types', | |||
| image: { | |||