Feat: copyright modification (#12707)

This commit is contained in:
KVOJJJin
2025-01-14 10:00:57 +08:00
committed by GitHub
parent 6e0fb055d1
commit 435eddd867
15 changed files with 550 additions and 155 deletions

View File

@@ -1,26 +1,33 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { ChevronRightIcon } from '@heroicons/react/20/solid' import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react'
import Link from 'next/link' import Link from 'next/link'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useContextSelector } from 'use-context-selector' import { useContext, useContextSelector } from 'use-context-selector'
import s from './style.module.css' import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea' import Textarea from '@/app/components/base/textarea'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import PremiumBadge from '@/app/components/base/premium-badge'
import { SimpleSelect } from '@/app/components/base/select' import { SimpleSelect } from '@/app/components/base/select'
import type { AppDetailResponse } from '@/models/app' import type { AppDetailResponse } from '@/models/app'
import type { AppIconType, AppSSO, Language } from '@/types/app' import type { AppIconType, AppSSO, Language } from '@/types/app'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { languages } from '@/i18n/language' import { LanguagesSupported, languages } from '@/i18n/language'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import AppContext, { useAppContext } from '@/context/app-context' import AppContext, { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import AppIconPicker from '@/app/components/base/app-icon-picker' import AppIconPicker from '@/app/components/base/app-icon-picker'
import I18n from '@/context/i18n'
import cn from '@/utils/classnames'
export type ISettingsModalProps = { export type ISettingsModalProps = {
isChat: boolean isChat: boolean
@@ -84,6 +91,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
chatColorTheme: chat_color_theme, chatColorTheme: chat_color_theme,
chatColorThemeInverted: chat_color_theme_inverted, chatColorThemeInverted: chat_color_theme_inverted,
copyright, copyright,
copyrightSwitchValue: !!copyright,
privacyPolicy: privacy_policy, privacyPolicy: privacy_policy,
customDisclaimer: custom_disclaimer, customDisclaimer: custom_disclaimer,
show_workflow_steps, show_workflow_steps,
@@ -93,6 +101,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
const [language, setLanguage] = useState(default_language) const [language, setLanguage] = useState(default_language)
const [saveLoading, setSaveLoading] = useState(false) const [saveLoading, setSaveLoading] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n)
const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const [appIcon, setAppIcon] = useState<AppIconSelection>( const [appIcon, setAppIcon] = useState<AppIconSelection>(
@@ -100,7 +109,16 @@ const SettingsModal: FC<ISettingsModalProps> = ({
? { type: 'image', url: icon_url!, fileId: icon } ? { type: 'image', url: icon_url!, fileId: icon }
: { type: 'emoji', icon, background: icon_background! }, : { type: 'emoji', icon, background: icon_background! },
) )
const isChatBot = appInfo.mode === 'chat' || appInfo.mode === 'advanced-chat' || appInfo.mode === 'agent-chat'
const { enableBilling, plan } = useProviderContext()
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
const isFreePlan = plan.type === 'sandbox'
const handlePlanClick = useCallback(() => {
if (isFreePlan)
setShowPricingModal()
else
setShowAccountSettingModal({ payload: 'billing' })
}, [isFreePlan, setShowAccountSettingModal, setShowPricingModal])
useEffect(() => { useEffect(() => {
setInputInfo({ setInputInfo({
@@ -109,6 +127,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
chatColorTheme: chat_color_theme, chatColorTheme: chat_color_theme,
chatColorThemeInverted: chat_color_theme_inverted, chatColorThemeInverted: chat_color_theme_inverted,
copyright, copyright,
copyrightSwitchValue: !!copyright,
privacyPolicy: privacy_policy, privacyPolicy: privacy_policy,
customDisclaimer: custom_disclaimer, customDisclaimer: custom_disclaimer,
show_workflow_steps, show_workflow_steps,
@@ -158,7 +177,11 @@ const SettingsModal: FC<ISettingsModalProps> = ({
chat_color_theme: inputInfo.chatColorTheme, chat_color_theme: inputInfo.chatColorTheme,
chat_color_theme_inverted: inputInfo.chatColorThemeInverted, chat_color_theme_inverted: inputInfo.chatColorThemeInverted,
prompt_public: false, prompt_public: false,
copyright: inputInfo.copyright, copyright: isFreePlan
? ''
: inputInfo.copyrightSwitchValue
? inputInfo.copyright
: '',
privacy_policy: inputInfo.privacyPolicy, privacy_policy: inputInfo.privacyPolicy,
custom_disclaimer: inputInfo.customDisclaimer, custom_disclaimer: inputInfo.customDisclaimer,
icon_type: appIcon.type, icon_type: appIcon.type,
@@ -192,141 +215,232 @@ const SettingsModal: FC<ISettingsModalProps> = ({
return ( return (
<> <>
<Modal <Modal
title={t(`${prefixSettings}.title`)}
isShow={isShow} isShow={isShow}
closable={false}
onClose={onHide} onClose={onHide}
className={`${s.settingsModal}`} className='max-w-[520px] p-0'
> >
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.webName`)}</div> {/* header */}
<div className='flex mt-2'> <div className='pl-6 pt-5 pr-5 pb-3'>
<AppIcon size='large' <div className='flex items-center gap-1'>
onClick={() => { setShowAppIconPicker(true) }} <div className='grow text-text-primary title-2xl-semi-bold'>{t(`${prefixSettings}.title`)}</div>
className='cursor-pointer !mr-3 self-center' <ActionButton className='shrink-0' onClick={onHide}>
iconType={appIcon.type} <RiCloseLine className='w-4 h-4' />
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon} </ActionButton>
background={appIcon.type === 'image' ? undefined : appIcon.background} </div>
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined} <div className='mt-0.5 text-text-tertiary system-xs-regular'>
/> <span>{t(`${prefixSettings}.modalTip`)}</span>
<Input <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>
className='grow h-10' </div>
value={inputInfo.title}
onChange={onChange('title')}
placeholder={t('app.appNamePlaceholder') || ''}
/>
</div> </div>
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div> {/* form body */}
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p> <div className='px-6 py-3 space-y-5'>
<Textarea {/* name & icon */}
className='mt-2' <div className='flex gap-4'>
value={inputInfo.desc} <div className='grow'>
onChange={e => onDesChange(e.target.value)} <div className={cn('mb-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webName`)}</div>
placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string} <Input
/> className='w-full'
{isChatBot && ( value={inputInfo.title}
<div className='w-full mt-4'> onChange={onChange('title')}
<div className='flex justify-between items-center'> placeholder={t('app.appNamePlaceholder') || ''}
<div className={`font-medium ${s.settingTitle} text-gray-900 `}>{t('app.answerIcon.title')}</div>
<Switch
defaultValue={inputInfo.use_icon_as_answer_icon}
onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
/> />
</div> </div>
<p className='body-xs-regular text-gray-500'>{t('app.answerIcon.description')}</p> <AppIcon
</div> size='xxl'
)} onClick={() => { setShowAppIconPicker(true) }}
<div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div> className='mt-2 cursor-pointer'
<SimpleSelect iconType={appIcon.type}
items={languages.filter(item => item.supported)} icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
defaultValue={language} background={appIcon.type === 'image' ? undefined : appIcon.background}
onSelect={item => setLanguage(item.value as Language)} imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
/>
<div className='w-full mt-8'>
<p className='system-xs-medium text-gray-500'>{t(`${prefixSettings}.workflow.title`)}</p>
<div className='flex justify-between items-center'>
<div className='font-medium system-sm-semibold flex-grow text-gray-900'>{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> </div>
<p className='body-xs-regular text-gray-500'>{t(`${prefixSettings}.workflow.showDesc`)}</p> {/* description */}
</div> <div className='relative'>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webDesc`)}</div>
{isChat && <> <div className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.chatColorTheme`)}</div> <Textarea
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.chatColorThemeDesc`)}</p> className='mt-1'
<Input value={inputInfo.desc}
className='mt-2 h-10' onChange={e => onDesChange(e.target.value)}
value={inputInfo.chatColorTheme ?? ''} placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string}
onChange={onChange('chatColorTheme')} />
placeholder='E.g #A020F0' <p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.webDescTip`)}</p>
/>
<div className="mt-1 flex justify-between items-center">
<p className={`ml-2 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.chatColorThemeInverted`)}</p>
<Switch defaultValue={inputInfo.chatColorThemeInverted} onChange={v => setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}></Switch>
</div> </div>
</>} <Divider className="h-px my-0" />
{systemFeatures.enable_web_sso_switch_component && <div className='w-full mt-8'> {/* answer icon */}
<p className='system-xs-medium text-gray-500'>{t(`${prefixSettings}.sso.label`)}</p> {isChat && (
<div className='flex justify-between items-center'> <div className='w-full'>
<div className='font-medium system-sm-semibold flex-grow text-gray-900'>{t(`${prefixSettings}.sso.title`)}</div> <div className='flex justify-between items-center'>
<Tooltip <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t('app.answerIcon.title')}</div>
disabled={systemFeatures.sso_enforced_for_web} <Switch
popupContent={ defaultValue={inputInfo.use_icon_as_answer_icon}
<div className='w-[180px]'>{t(`${prefixSettings}.sso.tooltip`)}</div> onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
} />
asChild={false} </div>
> <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t('app.answerIcon.description')}</p>
<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 text-gray-500'>{t(`${prefixSettings}.sso.description`)}</p>
</div>}
{!isShowMore && <div className='w-full cursor-pointer mt-8' onClick={() => setIsShowMore(true)}>
<div className='flex justify-between'>
<div className={`font-medium ${s.settingTitle} flex-grow text-gray-900`}>{t(`${prefixSettings}.more.entry`)}</div>
<div className='flex-shrink-0 w-4 h-4 text-gray-500'>
<ChevronRightIcon />
</div> </div>
</div> )}
<p className={`mt-1 ${s.policy} text-gray-500`}>{t(`${prefixSettings}.more.copyright`)} & {t(`${prefixSettings}.more.privacyPolicy`)}</p> {/* language */}
</div>} <div className='flex items-center'>
{isShowMore && <> <div className={cn('grow py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.language`)}</div>
<hr className='w-full mt-6' /> <SimpleSelect
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.copyright`)}</div> wrapperClassName='w-[200px]'
<Input items={languages.filter(item => item.supported)}
className='mt-2 h-10' defaultValue={language}
value={inputInfo.copyright} onSelect={item => setLanguage(item.value as Language)}
onChange={onChange('copyright')}
placeholder={t(`${prefixSettings}.more.copyRightPlaceholder`) as string}
/>
<div className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.privacyPolicy`)}</div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>
<Trans
i18nKey={`${prefixSettings}.more.privacyPolicyTip`}
components={{ privacyPolicyLink: <Link href={'https://docs.dify.ai/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer' className='text-primary-600' /> }}
/> />
</p> </div>
<Input {/* theme color */}
className='mt-2 h-10' {isChat && (
value={inputInfo.privacyPolicy} <div className='flex items-center'>
onChange={onChange('privacyPolicy')} <div className='grow'>
placeholder={t(`${prefixSettings}.more.privacyPolicyPlaceholder`) as string} <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 className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.customDisclaimer`)}</div> </div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.more.customDisclaimerTip`)}</p> <div className='shrink-0'>
<Input <Input
className='mt-2 h-10' className='mb-1 w-[200px]'
value={inputInfo.customDisclaimer} value={inputInfo.chatColorTheme ?? ''}
onChange={onChange('customDisclaimer')} onChange={onChange('chatColorTheme')}
placeholder={t(`${prefixSettings}.more.customDisclaimerPlaceholder`) as string} placeholder='E.g #A020F0'
/> />
</>} <div className='flex justify-between items-center'>
<div className='mt-10 flex justify-end'> <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>
</div>
</div>
)}
{/* workflow detail */}
<div className='w-full'>
<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='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.workflow.showDesc`)}</p>
</div>
{/* SSO */}
{systemFeatures.enable_web_sso_switch_component && (
<>
<Divider className="h-px my-0" />
<div className='w-full'>
<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={
<div className='w-[180px]'>{t(`${prefixSettings}.sso.tooltip`)}</div>
}
asChild={false}
>
<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='pb-0.5 body-xs-regular text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p>
</div>
</>
)}
{/* more settings switch */}
<Divider className="h-px my-0" />
{!isShowMore && (
<div className='flex items-center cursor-pointer' onClick={() => setIsShowMore(true)}>
<div className='grow'>
<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='shrink-0 ml-1 w-4 h-4 text-text-secondary'/>
</div>
)}
{/* more settings */}
{isShowMore && (
<>
{/* copyright */}
<div className='w-full'>
<div className='flex items-center'>
<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='select-none h-[18px]'>
<PremiumBadge size='s' color='blue' allowHover={true} onClick={handlePlanClick}>
<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')}
</span>
</div>
</PremiumBadge>
</div>
)}
</div>
<Tooltip
disabled={!isFreePlan}
popupContent={
<div className='w-[260px]'>{t(`${prefixSettings}.more.copyrightTooltip`)}</div>
}
asChild={false}
>
<Switch
disabled={isFreePlan}
defaultValue={inputInfo.copyrightSwitchValue}
onChange={v => setInputInfo({ ...inputInfo, copyrightSwitchValue: v })}
/>
</Tooltip>
</div>
<p className='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.more.copyrightTip`)}</p>
{inputInfo.copyrightSwitchValue && (
<Input
className='mt-2 h-10'
value={inputInfo.copyright}
onChange={onChange('copyright')}
placeholder={t(`${prefixSettings}.more.copyRightPlaceholder`) as string}
/>
)}
</div>
{/* privacy policy */}
<div className='w-full'>
<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://docs.dify.ai/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer' className='text-text-accent' /> }}
/>
</p>
<Input
className='mt-1'
value={inputInfo.privacyPolicy}
onChange={onChange('privacyPolicy')}
placeholder={t(`${prefixSettings}.more.privacyPolicyPlaceholder`) as string}
/>
</div>
{/* custom disclaimer */}
<div className='w-full'>
<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}
onChange={onChange('customDisclaimer')}
placeholder={t(`${prefixSettings}.more.customDisclaimerPlaceholder`) as string}
/>
</div>
</>
)}
</div>
{/* footer */}
<div className='p-6 pt-5 flex justify-end'>
<Button className='mr-2' onClick={onHide}>{t('common.operation.cancel')}</Button> <Button className='mr-2' onClick={onHide}>{t('common.operation.cancel')}</Button>
<Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button> <Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button>
</div> </div>
{showAppIconPicker && <AppIconPicker </Modal >
{showAppIconPicker && (
<AppIconPicker
onSelect={(payload) => { onSelect={(payload) => {
setAppIcon(payload) setAppIcon(payload)
setShowAppIconPicker(false) setShowAppIconPicker(false)
@@ -337,8 +451,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
: { type: 'emoji', icon, background: icon_background! }) : { type: 'emoji', icon, background: icon_background! })
setShowAppIconPicker(false) setShowAppIconPicker(false)
}} }}
/>} />
</Modal > )}
</> </>
) )

View File

@@ -1,18 +0,0 @@
.settingsModal {
max-width: 32.5rem !important;
}
.settingTitle {
line-height: 21px;
font-size: 0.875rem;
}
.settingsTip {
line-height: 1.125rem;
font-size: 0.75rem;
}
.policy {
font-size: 0.75rem;
line-height: 1.125rem;
}

View File

@@ -115,9 +115,11 @@ const Sidebar = () => {
) )
} }
</div> </div>
<div className='px-4 pb-4 text-xs text-gray-400'> {appData?.site.copyright && (
© {appData?.site.copyright || appData?.site.title} {(new Date()).getFullYear()} <div className='px-4 pb-4 text-xs text-gray-400'>
</div> © {(new Date()).getFullYear()} {appData?.site.copyright}
</div>
)}
{!!showConfirm && ( {!!showConfirm && (
<Confirm <Confirm
title={t('share.chat.deleteConversation.title')} title={t('share.chat.deleteConversation.title')}

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="46" height="24" viewBox="0 0 46 24" fill="none">
<path opacity="0.5" d="M-6.5 8C-6.5 3.58172 -2.91828 0 1.5 0H45.5L33.0248 24H1.49999C-2.91829 24 -6.5 20.4183 -6.5 16V8Z" fill="url(#paint0_linear_6333_42118)"/>
<defs>
<linearGradient id="paint0_linear_6333_42118" x1="1.81679" y1="5.47784e-07" x2="101.257" y2="30.3866" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.12"/>
<stop offset="1" stop-color="white" stop-opacity="0.3"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="sparkles-soft">
<path id="Vector" opacity="0.5" d="M10.9963 1.36798C10.9839 1.25339 10.8909 1.16677 10.7802 1.16666C10.6695 1.16654 10.5763 1.25295 10.5636 1.36752C10.5045 1.90085 10.3525 2.26673 10.1143 2.5149C9.87599 2.76307 9.52476 2.92145 9.01275 2.98296C8.90277 2.99618 8.81983 3.09324 8.81995 3.20856C8.82006 3.32388 8.90322 3.42076 9.0132 3.43373C9.51653 3.49312 9.87583 3.65148 10.1201 3.90135C10.3631 4.14986 10.518 4.51523 10.563 5.04321C10.573 5.16035 10.6673 5.25012 10.7802 5.24999C10.8931 5.24986 10.9872 5.15987 10.9969 5.0427C11.0401 4.52364 11.1949 4.15004 11.4394 3.89528C11.684 3.64052 12.0426 3.47926 12.5409 3.43433C12.6534 3.42419 12.7398 3.32619 12.7399 3.20858C12.7401 3.09097 12.6539 2.99277 12.5414 2.98236C12.0346 2.93546 11.6838 2.77407 11.4452 2.52098C11.2054 2.2665 11.0533 1.89229 10.9963 1.36798Z" fill="#F5F8FF"/>
<path id="Vector_2" d="M7.13646 2.85102C7.10442 2.55638 6.8653 2.33365 6.5806 2.33334C6.29595 2.33304 6.05633 2.55526 6.02374 2.84984C5.87186 4.22127 5.48089 5.1621 4.86827 5.80025C4.25565 6.43838 3.35245 6.84566 2.03587 7.00386C1.75307 7.03781 1.53975 7.28742 1.54004 7.58393C1.54033 7.88049 1.75415 8.12958 2.03701 8.16294C3.33132 8.31566 4.25509 8.72289 4.88328 9.36543C5.50807 10.0045 5.90647 10.9439 6.02222 12.3016C6.04793 12.6029 6.29035 12.8337 6.58066 12.8333C6.87102 12.833 7.11294 12.6016 7.13797 12.3003C7.24885 10.9656 7.64695 10.0049 8.27583 9.34979C8.90477 8.69471 9.82698 8.28002 11.1083 8.16452C11.3976 8.13844 11.6197 7.88644 11.62 7.58399C11.6204 7.28159 11.3988 7.02906 11.1096 7.00229C9.8062 6.88171 8.90432 6.46673 8.29084 5.81589C7.674 5.16152 7.28306 4.19926 7.13646 2.85102Z" fill="#F5F8FF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,67 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"xmlns": "http://www.w3.org/2000/svg",
"width": "46",
"height": "24",
"viewBox": "0 0 46 24",
"fill": "none"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"opacity": "0.5",
"d": "M-6.5 8C-6.5 3.58172 -2.91828 0 1.5 0H45.5L33.0248 24H1.49999C-2.91829 24 -6.5 20.4183 -6.5 16V8Z",
"fill": "url(#paint0_linear_6333_42118)"
},
"children": []
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "linearGradient",
"attributes": {
"id": "paint0_linear_6333_42118",
"x1": "1.81679",
"y1": "5.47784e-07",
"x2": "101.257",
"y2": "30.3866",
"gradientUnits": "userSpaceOnUse"
},
"children": [
{
"type": "element",
"name": "stop",
"attributes": {
"stop-color": "white",
"stop-opacity": "0.12"
},
"children": []
},
{
"type": "element",
"name": "stop",
"attributes": {
"offset": "1",
"stop-color": "white",
"stop-opacity": "0.3"
},
"children": []
}
]
}
]
}
]
},
"name": "Highlight"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Highlight.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Highlight'
export default Icon

View File

@@ -0,0 +1,47 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "14",
"viewBox": "0 0 14 14",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "sparkles-soft"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector",
"opacity": "0.5",
"d": "M10.9963 1.36798C10.9839 1.25339 10.8909 1.16677 10.7802 1.16666C10.6695 1.16654 10.5763 1.25295 10.5636 1.36752C10.5045 1.90085 10.3525 2.26673 10.1143 2.5149C9.87599 2.76307 9.52476 2.92145 9.01275 2.98296C8.90277 2.99618 8.81983 3.09324 8.81995 3.20856C8.82006 3.32388 8.90322 3.42076 9.0132 3.43373C9.51653 3.49312 9.87583 3.65148 10.1201 3.90135C10.3631 4.14986 10.518 4.51523 10.563 5.04321C10.573 5.16035 10.6673 5.25012 10.7802 5.24999C10.8931 5.24986 10.9872 5.15987 10.9969 5.0427C11.0401 4.52364 11.1949 4.15004 11.4394 3.89528C11.684 3.64052 12.0426 3.47926 12.5409 3.43433C12.6534 3.42419 12.7398 3.32619 12.7399 3.20858C12.7401 3.09097 12.6539 2.99277 12.5414 2.98236C12.0346 2.93546 11.6838 2.77407 11.4452 2.52098C11.2054 2.2665 11.0533 1.89229 10.9963 1.36798Z",
"fill": "#F5F8FF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector_2",
"d": "M7.13646 2.85102C7.10442 2.55638 6.8653 2.33365 6.5806 2.33334C6.29595 2.33304 6.05633 2.55526 6.02374 2.84984C5.87186 4.22127 5.48089 5.1621 4.86827 5.80025C4.25565 6.43838 3.35245 6.84566 2.03587 7.00386C1.75307 7.03781 1.53975 7.28742 1.54004 7.58393C1.54033 7.88049 1.75415 8.12958 2.03701 8.16294C3.33132 8.31566 4.25509 8.72289 4.88328 9.36543C5.50807 10.0045 5.90647 10.9439 6.02222 12.3016C6.04793 12.6029 6.29035 12.8337 6.58066 12.8333C6.87102 12.833 7.11294 12.6016 7.13797 12.3003C7.24885 10.9656 7.64695 10.0049 8.27583 9.34979C8.90477 8.69471 9.82698 8.28002 11.1083 8.16452C11.3976 8.13844 11.6197 7.88644 11.62 7.58399C11.6204 7.28159 11.3988 7.02906 11.1096 7.00229C9.8062 6.88171 8.90432 6.46673 8.29084 5.81589C7.674 5.16152 7.28306 4.19926 7.13646 2.85102Z",
"fill": "#F5F8FF"
},
"children": []
}
]
}
]
},
"name": "SparklesSoft"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './SparklesSoft.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'SparklesSoft'
export default Icon

View File

@@ -2,9 +2,11 @@ export { default as D } from './D'
export { default as DiagonalDividingLine } from './DiagonalDividingLine' export { default as DiagonalDividingLine } from './DiagonalDividingLine'
export { default as Dify } from './Dify' export { default as Dify } from './Dify'
export { default as Github } from './Github' export { default as Github } from './Github'
export { default as Highlight } from './Highlight'
export { default as Line3 } from './Line3' export { default as Line3 } from './Line3'
export { default as Lock } from './Lock' export { default as Lock } from './Lock'
export { default as MessageChatSquare } from './MessageChatSquare' export { default as MessageChatSquare } from './MessageChatSquare'
export { default as MultiPathRetrieval } from './MultiPathRetrieval' export { default as MultiPathRetrieval } from './MultiPathRetrieval'
export { default as NTo1Retrieval } from './NTo1Retrieval' export { default as NTo1Retrieval } from './NTo1Retrieval'
export { default as Notion } from './Notion' export { default as Notion } from './Notion'
export { default as SparklesSoft } from './SparklesSoft'

View File

@@ -0,0 +1,48 @@
@tailwind components;
@layer components {
.premium-badge {
@apply inline-flex justify-center items-center rounded-full border box-border border-[rgba(255,255,255,0.8)] text-white
}
/* m is for the regular button */
.premium-badge-m {
@apply border shadow-lg !p-1 h-6 w-auto
}
.premium-badge-s {
@apply border-[0.5px] shadow-xs !px-1 !py-[3px] h-[18px] w-auto
}
.premium-badge-blue {
@apply bg-gradient-to-r from-[#5289ffe6] to-[#155aefe6] bg-util-colors-blue-blue-200
}
.premium-badge-indigo {
@apply bg-gradient-to-r from-[#8098f9e6] to-[#444ce7e6] bg-util-colors-indigo-indigo-200
}
.premium-badge-gray {
@apply bg-gradient-to-r from-[#98a2b2e6] to-[#676f83e6] bg-util-colors-gray-gray-200
}
.premium-badge-orange {
@apply bg-gradient-to-r from-[#ff692ee6] to-[#e04f16e6] bg-util-colors-orange-orange-200
}
.premium-badge-blue.allowHover:hover {
@apply bg-gradient-to-r from-[#296dffe6] to-[#004aebe6] bg-util-colors-blue-blue-300 cursor-pointer
}
.premium-badge-indigo.allowHover:hover {
@apply bg-gradient-to-r from-[#6172f3e6] to-[#2d31a6e6] bg-util-colors-indigo-indigo-300 cursor-pointer
}
.premium-badge-gray.allowHover:hover {
@apply bg-gradient-to-r from-[#676f83e6] to-[#354052e6] bg-util-colors-gray-gray-300 cursor-pointer
}
.premium-badge-orange.allowHover:hover {
@apply bg-gradient-to-r from-[#ff4405e6] to-[#b93815e6] bg-util-colors-orange-orange-300 cursor-pointer
}
}

View File

@@ -0,0 +1,78 @@
import type { CSSProperties, ReactNode } from 'react'
import React from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import { Highlight } from '@/app/components/base/icons/src/public/common'
import classNames from '@/utils/classnames'
import './index.css'
const PremiumBadgeVariants = cva(
'premium-badge',
{
variants: {
size: {
s: 'premium-badge-s',
m: 'premium-badge-m',
},
color: {
blue: 'premium-badge-blue',
indigo: 'premium-badge-indigo',
gray: 'premium-badge-gray',
orange: 'premium-badge-orange',
},
allowHover: {
true: 'allowHover',
false: '',
},
},
defaultVariants: {
size: 'm',
color: 'blue',
allowHover: false,
},
},
)
type PremiumBadgeProps = {
size?: 's' | 'm'
color?: 'blue' | 'indigo' | 'gray' | 'orange'
allowHover?: boolean
styleCss?: CSSProperties
children?: ReactNode
} & React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof PremiumBadgeVariants>
const PremiumBadge: React.FC<PremiumBadgeProps> = ({
className,
size,
color,
allowHover,
styleCss,
children,
...props
}) => {
return (
<div
className={classNames(
PremiumBadgeVariants({ size, color, allowHover, className }),
'relative text-nowrap',
)}
style={styleCss}
{...props}
>
{children}
<Highlight
className={classNames(
'absolute top-0 opacity-50 hover:opacity-80',
size === 's' ? 'h-4.5 w-12' : 'h-6 w-12',
)}
style={{
right: '50%',
transform: 'translateX(10%)',
}}
/>
</div>
)
}
PremiumBadge.displayName = 'PremiumBadge'
export default PremiumBadge
export { PremiumBadge, PremiumBadgeVariants }

View File

@@ -644,10 +644,12 @@ const TextGeneration: FC<IMainProps> = ({
isInstalledApp ? 'left-[248px]' : 'left-8', isInstalledApp ? 'left-[248px]' : 'left-8',
'fixed bottom-4 flex space-x-2 text-gray-400 font-normal text-xs', 'fixed bottom-4 flex space-x-2 text-gray-400 font-normal text-xs',
)}> )}>
<div className="">© {siteInfo.copyright || siteInfo.title} {(new Date()).getFullYear()}</div> {siteInfo.copyright && (
<div className="">© {(new Date()).getFullYear()} {siteInfo.copyright}</div>
)}
{siteInfo.privacy_policy && ( {siteInfo.privacy_policy && (
<> <>
<div>·</div> {siteInfo.copyright && <div>·</div>}
<div>{t('share.chat.privacyPolicyLeft')} <div>{t('share.chat.privacyPolicyLeft')}
<a <a
className='text-gray-500 px-1' className='text-gray-500 px-1'

View File

@@ -38,7 +38,8 @@ const translation = {
preUseReminder: 'Please enable WebApp before continuing.', preUseReminder: 'Please enable WebApp before continuing.',
settings: { settings: {
entry: 'Settings', entry: 'Settings',
title: 'WebApp Settings', title: 'Web App Settings',
modalTip: 'Client-side web app settings. ',
webName: 'WebApp Name', webName: 'WebApp Name',
webDesc: 'WebApp Description', webDesc: 'WebApp Description',
webDescTip: 'This text will be displayed on the client side, providing basic guidance on how to use the application', webDescTip: 'This text will be displayed on the client side, providing basic guidance on how to use the application',
@@ -56,7 +57,7 @@ const translation = {
chatColorThemeInverted: 'Inverted', chatColorThemeInverted: 'Inverted',
invalidHexMessage: 'Invalid hex value', invalidHexMessage: 'Invalid hex value',
sso: { sso: {
label: 'SSO Authentication', label: 'SSO Enforcement',
title: 'WebApp SSO', title: 'WebApp SSO',
description: 'All users are required to login with SSO before using WebApp', description: 'All users are required to login with SSO before using WebApp',
tooltip: 'Contact the administrator to enable WebApp SSO', tooltip: 'Contact the administrator to enable WebApp SSO',
@@ -64,6 +65,8 @@ const translation = {
more: { more: {
entry: 'Show more settings', entry: 'Show more settings',
copyright: 'Copyright', copyright: 'Copyright',
copyrightTip: 'Display copyright information in the webapp',
copyrightTooltip: 'Please upgrade to Professional plan or above',
copyRightPlaceholder: 'Enter the name of the author or organization', copyRightPlaceholder: 'Enter the name of the author or organization',
privacyPolicy: 'Privacy Policy', privacyPolicy: 'Privacy Policy',
privacyPolicyPlaceholder: 'Enter the privacy policy link', privacyPolicyPlaceholder: 'Enter the privacy policy link',

View File

@@ -39,6 +39,7 @@ const translation = {
settings: { settings: {
entry: '设置', entry: '设置',
title: 'WebApp 设置', title: 'WebApp 设置',
modalTip: '客户端 WebApp 设置。',
webName: 'WebApp 名称', webName: 'WebApp 名称',
webDesc: 'WebApp 描述', webDesc: 'WebApp 描述',
webDescTip: '以下文字将展示在客户端中,对应用进行说明和使用上的基本引导', webDescTip: '以下文字将展示在客户端中,对应用进行说明和使用上的基本引导',
@@ -64,6 +65,8 @@ const translation = {
more: { more: {
entry: '展示更多设置', entry: '展示更多设置',
copyright: '版权', copyright: '版权',
copyrightTip: '在 WebApp 中展示版权信息',
copyrightTooltip: '请升级到专业版或者更高',
copyRightPlaceholder: '请输入作者或组织名称', copyRightPlaceholder: '请输入作者或组织名称',
privacyPolicy: '隐私政策', privacyPolicy: '隐私政策',
privacyPolicyPlaceholder: '请输入隐私政策链接', privacyPolicyPlaceholder: '请输入隐私政策链接',