| | |
| | | return check |
| | | } |
| | | |
| | | const validatePrivacyPolicy = (privacyPolicy: string | null) => { |
| | | if (privacyPolicy === null || privacyPolicy?.length === 0) |
| | | return true |
| | | |
| | | return privacyPolicy.startsWith('http://') || privacyPolicy.startsWith('https://') |
| | | } |
| | | |
| | | if (inputInfo !== null) { |
| | | if (!validateColorHex(inputInfo.chatColorTheme)) { |
| | | notify({ type: 'error', message: t(`${prefixSettings}.invalidHexMessage`) }) |
| | | return |
| | | } |
| | | if (!validatePrivacyPolicy(inputInfo.privacyPolicy)) { |
| | | notify({ type: 'error', message: t(`${prefixSettings}.invalidPrivacyPolicy`) }) |
| | | return |
| | | } |
| | | } |
| | |
| | | className='max-w-[520px] p-0' |
| | | > |
| | | {/* header */} |
| | | <div className='pb-3 pl-6 pr-5 pt-5'> |
| | | <div className='pl-6 pt-5 pr-5 pb-3'> |
| | | <div className='flex items-center gap-1'> |
| | | <div className='title-2xl-semi-bold grow text-text-primary'>{t(`${prefixSettings}.title`)}</div> |
| | | <div className='grow text-text-primary title-2xl-semi-bold'>{t(`${prefixSettings}.title`)}</div> |
| | | <ActionButton className='shrink-0' onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4' /> |
| | | <RiCloseLine className='w-4 h-4' /> |
| | | </ActionButton> |
| | | </div> |
| | | <div className='system-xs-regular mt-0.5 text-text-tertiary'> |
| | | <div className='mt-0.5 text-text-tertiary system-xs-regular'> |
| | | <span>{t(`${prefixSettings}.modalTip`)}</span> |
| | | <Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/en/guides/application-publishing/launch-your-webapp-quickly/README'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> |
| | | <Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/guides/application-publishing/launch-your-webapp-quickly#setting-up-your-ai-site'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> |
| | | </div> |
| | | </div> |
| | | {/* form body */} |
| | | <div className='space-y-5 px-6 py-3'> |
| | | <div className='px-6 py-3 space-y-5'> |
| | | {/* name & icon */} |
| | | <div className='flex gap-4'> |
| | | <div className='grow'> |
| | | <div className={cn('system-sm-semibold mb-1 py-1 text-text-secondary')}>{t(`${prefixSettings}.webName`)}</div> |
| | | <div className={cn('mb-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webName`)}</div> |
| | | <Input |
| | | className='w-full' |
| | | value={inputInfo.title} |
| | |
| | | </div> |
| | | {/* description */} |
| | | <div className='relative'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.webDesc`)}</div> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webDesc`)}</div> |
| | | <Textarea |
| | | className='mt-1' |
| | | value={inputInfo.desc} |
| | | onChange={e => onDesChange(e.target.value)} |
| | | placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string} |
| | | /> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.webDescTip`)}</p> |
| | | <p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.webDescTip`)}</p> |
| | | </div> |
| | | <Divider className="my-0 h-px" /> |
| | | <Divider className="h-px my-0" /> |
| | | {/* answer icon */} |
| | | {isChat && ( |
| | | <div className='w-full'> |
| | | <div className='flex items-center justify-between'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t('app.answerIcon.title')}</div> |
| | | <div className='flex justify-between items-center'> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t('app.answerIcon.title')}</div> |
| | | <Switch |
| | | defaultValue={inputInfo.use_icon_as_answer_icon} |
| | | onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })} |
| | | /> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t('app.answerIcon.description')}</p> |
| | | <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t('app.answerIcon.description')}</p> |
| | | </div> |
| | | )} |
| | | {/* language */} |
| | | <div className='flex items-center'> |
| | | <div className={cn('system-sm-semibold grow py-1 text-text-secondary')}>{t(`${prefixSettings}.language`)}</div> |
| | | <div className={cn('grow py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.language`)}</div> |
| | | <SimpleSelect |
| | | wrapperClassName='w-[200px]' |
| | | items={languages.filter(item => item.supported)} |
| | | defaultValue={language} |
| | | onSelect={item => setLanguage(item.value as Language)} |
| | | notClearable |
| | | /> |
| | | </div> |
| | | {/* theme color */} |
| | | {isChat && ( |
| | | <div className='flex items-center'> |
| | | <div className='grow'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.chatColorTheme`)}</div> |
| | | <div className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.chatColorThemeDesc`)}</div> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.chatColorTheme`)}</div> |
| | | <div className='pb-0.5 body-xs-regular text-text-tertiary'>{t(`${prefixSettings}.chatColorThemeDesc`)}</div> |
| | | </div> |
| | | <div className='shrink-0'> |
| | | <Input |
| | |
| | | onChange={onChange('chatColorTheme')} |
| | | placeholder='E.g #A020F0' |
| | | /> |
| | | <div className='flex items-center justify-between'> |
| | | <div className='flex justify-between items-center'> |
| | | <p className={cn('body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.chatColorThemeInverted`)}</p> |
| | | <Switch defaultValue={inputInfo.chatColorThemeInverted} onChange={v => setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}></Switch> |
| | | </div> |
| | |
| | | )} |
| | | {/* workflow detail */} |
| | | <div className='w-full'> |
| | | <div className='flex items-center justify-between'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.workflow.subTitle`)}</div> |
| | | <div className='flex justify-between items-center'> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.workflow.subTitle`)}</div> |
| | | <Switch |
| | | disabled={!(appInfo.mode === 'workflow' || appInfo.mode === 'advanced-chat')} |
| | | defaultValue={inputInfo.show_workflow_steps} |
| | | onChange={v => setInputInfo({ ...inputInfo, show_workflow_steps: v })} |
| | | /> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.workflow.showDesc`)}</p> |
| | | <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.workflow.showDesc`)}</p> |
| | | </div> |
| | | {/* SSO */} |
| | | {systemFeatures.enable_web_sso_switch_component && ( |
| | | <> |
| | | <Divider className="my-0 h-px" /> |
| | | <Divider className="h-px my-0" /> |
| | | <div className='w-full'> |
| | | <p className='system-xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${prefixSettings}.sso.label`)}</p> |
| | | <div className='flex items-center justify-between'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.sso.title`)}</div> |
| | | <p className='mb-1 system-xs-medium-uppercase text-text-tertiary'>{t(`${prefixSettings}.sso.label`)}</p> |
| | | <div className='flex justify-between items-center'> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.sso.title`)}</div> |
| | | <Tooltip |
| | | disabled={systemFeatures.sso_enforced_for_web} |
| | | popupContent={ |
| | |
| | | <Switch disabled={!systemFeatures.sso_enforced_for_web || !isCurrentWorkspaceEditor} defaultValue={systemFeatures.sso_enforced_for_web && inputInfo.enable_sso} onChange={v => setInputInfo({ ...inputInfo, enable_sso: v })}></Switch> |
| | | </Tooltip> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p> |
| | | <p className='pb-0.5 body-xs-regular text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p> |
| | | </div> |
| | | </> |
| | | )} |
| | | {/* more settings switch */} |
| | | <Divider className="my-0 h-px" /> |
| | | <Divider className="h-px my-0" /> |
| | | {!isShowMore && ( |
| | | <div className='flex cursor-pointer items-center' onClick={() => setIsShowMore(true)}> |
| | | <div className='flex items-center cursor-pointer' onClick={() => setIsShowMore(true)}> |
| | | <div className='grow'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.entry`)}</div> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}</p> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.entry`)}</div> |
| | | <p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}</p> |
| | | </div> |
| | | <RiArrowRightSLine className='ml-1 h-4 w-4 shrink-0 text-text-secondary' /> |
| | | <RiArrowRightSLine className='shrink-0 ml-1 w-4 h-4 text-text-secondary'/> |
| | | </div> |
| | | )} |
| | | {/* more settings */} |
| | |
| | | {/* copyright */} |
| | | <div className='w-full'> |
| | | <div className='flex items-center'> |
| | | <div className='flex grow items-center'> |
| | | <div className={cn('system-sm-semibold mr-1 py-1 text-text-secondary')}>{t(`${prefixSettings}.more.copyright`)}</div> |
| | | <div className='grow flex items-center'> |
| | | <div className={cn('mr-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.copyright`)}</div> |
| | | {/* upgrade button */} |
| | | {enableBilling && isFreePlan && ( |
| | | <div className='h-[18px] select-none'> |
| | | <div className='select-none h-[18px]'> |
| | | <PremiumBadge size='s' color='blue' allowHover={true} onClick={handlePlanClick}> |
| | | <SparklesSoft className='flex h-3.5 w-3.5 items-center py-[1px] pl-[3px] text-components-premium-badge-indigo-text-stop-0' /> |
| | | <SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' /> |
| | | <div className='system-xs-medium'> |
| | | <span className='p-1'> |
| | | {t('billing.upgradeBtn.encourageShort')} |
| | |
| | | /> |
| | | </Tooltip> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.more.copyrightTip`)}</p> |
| | | <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.more.copyrightTip`)}</p> |
| | | {inputInfo.copyrightSwitchValue && ( |
| | | <Input |
| | | className='mt-2 h-10' |
| | |
| | | </div> |
| | | {/* privacy policy */} |
| | | <div className='w-full'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.privacyPolicy`)}</div> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.privacyPolicy`)}</div> |
| | | <p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}> |
| | | <Trans |
| | | i18nKey={`${prefixSettings}.more.privacyPolicyTip`} |
| | | components={{ privacyPolicyLink: <Link href={'https://dify.ai/privacy'} target='_blank' rel='noopener noreferrer' className='text-text-accent' /> }} |
| | | components={{ privacyPolicyLink: <Link href={'https://docs.dify.ai/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer' className='text-text-accent' /> }} |
| | | /> |
| | | </p> |
| | | <Input |
| | |
| | | </div> |
| | | {/* custom disclaimer */} |
| | | <div className='w-full'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.customDisclaimer`)}</div> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.more.customDisclaimerTip`)}</p> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.customDisclaimer`)}</div> |
| | | <p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.more.customDisclaimerTip`)}</p> |
| | | <Textarea |
| | | className='mt-1' |
| | | value={inputInfo.customDisclaimer} |
| | |
| | | )} |
| | | </div> |
| | | {/* footer */} |
| | | <div className='flex justify-end p-6 pt-5'> |
| | | <div className='p-6 pt-5 flex justify-end'> |
| | | <Button className='mr-2' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | |
| | | </Modal > |
| | | {showAppIconPicker && ( |
| | | <div onClick={e => e.stopPropagation()}> |
| | | <AppIconPicker |
| | | onSelect={(payload) => { |
| | | setAppIcon(payload) |
| | |
| | | setShowAppIconPicker(false) |
| | | }} |
| | | /> |
| | | </div> |
| | | )} |
| | | </Modal> |
| | | </> |
| | | |
| | | ) |
| | | } |
| | | export default React.memo(SettingsModal) |