fix: fix human input form logo replace (#37452)

This commit is contained in:
wangxiaolei 2026-06-15 15:27:25 +08:00 committed by GitHub
parent 0c5b3fd0f2
commit 3eaa534e99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 108 additions and 24 deletions

View File

@ -16,7 +16,7 @@ class EnterpriseFeatureConfig(BaseSettings):
CAN_REPLACE_LOGO: bool = Field(
description="Allow customization of the enterprise logo.",
default=False,
default=True,
)
ENTERPRISE_REQUEST_TIMEOUT: int = Field(

View File

@ -354,4 +354,49 @@ describe('Human input share form', () => {
await user.click(screen.getByRole('button', { name: 'share-update-summary' }))
expect(approveButton).toBeEnabled()
})
it('should hide branding when remove_webapp_brand is enabled', () => {
mockUseGetHumanInputForm.mockReturnValue({
data: {
...formData,
site: {
...formData.site,
custom_config: {
remove_webapp_brand: true,
replace_webapp_logo: null,
},
},
},
isLoading: false,
error: null,
})
render(<FormContent />)
expect(screen.queryByText('share.chat.poweredBy')).not.toBeInTheDocument()
expect(screen.queryByText('dify-logo')).not.toBeInTheDocument()
})
it('should render the custom branding logo when replace_webapp_logo is provided', () => {
mockUseGetHumanInputForm.mockReturnValue({
data: {
...formData,
site: {
...formData.site,
custom_config: {
remove_webapp_brand: false,
replace_webapp_logo: 'https://example.com/custom-logo.png',
},
},
},
isLoading: false,
error: null,
})
render(<FormContent />)
expect(screen.getByText('share.chat.poweredBy')).toBeInTheDocument()
expect(screen.getByRole('img', { name: 'logo' })).toHaveAttribute('src', 'https://example.com/custom-logo.png')
expect(screen.queryByText('dify-logo')).not.toBeInTheDocument()
})
})

View File

@ -0,0 +1,30 @@
import { useTranslation } from 'react-i18next'
import DifyLogo from '@/app/components/base/logo/dify-logo'
type BrandingFooterProps = {
removeWebappBrand?: boolean
replaceWebappLogo?: string | null
}
const BrandingFooter = ({
removeWebappBrand,
replaceWebappLogo,
}: BrandingFooterProps) => {
const { t } = useTranslation()
if (removeWebappBrand)
return null
return (
<div className="flex flex-row-reverse px-2 py-3">
<div className="flex shrink-0 items-center gap-1.5 px-1">
<div className="system-2xs-medium-uppercase text-text-tertiary">{t('chat.poweredBy', { ns: 'share' })}</div>
{replaceWebappLogo
? <img src={replaceWebappLogo} alt="logo" className="block h-5 w-auto" />
: <DifyLogo size="small" />}
</div>
</div>
)
}
export default BrandingFooter

View File

@ -1,7 +1,7 @@
import type { ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import BrandingFooter from './branding-footer'
type FormStatusCardProps = {
iconClassName: string
@ -9,6 +9,7 @@ type FormStatusCardProps = {
subtitle?: ReactNode
submissionID?: string
removeWebappBrand?: boolean
replaceWebappLogo?: string | null
}
const FormStatusCard = ({
@ -17,6 +18,7 @@ const FormStatusCard = ({
subtitle,
submissionID,
removeWebappBrand,
replaceWebappLogo,
}: FormStatusCardProps) => {
const { t } = useTranslation()
@ -39,14 +41,10 @@ const FormStatusCard = ({
</div>
)}
</div>
{!removeWebappBrand && (
<div className="flex flex-row-reverse px-2 py-3">
<div className="flex shrink-0 items-center gap-1.5 px-1">
<div className="system-2xs-medium-uppercase text-text-tertiary">{t('chat.poweredBy', { ns: 'share' })}</div>
<DifyLogo size="small" />
</div>
</div>
)}
<BrandingFooter
removeWebappBrand={removeWebappBrand}
replaceWebappLogo={replaceWebappLogo}
/>
</div>
</div>
)

View File

@ -35,6 +35,9 @@ const FormContent = () => {
const { isSubmitting, submit, success } = useFormSubmit(token)
const removeWebappBrand = formData?.site?.custom_config?.remove_webapp_brand === true
const replaceWebappLogo = typeof formData?.site?.custom_config?.replace_webapp_logo === 'string'
? formData.site.custom_config.replace_webapp_logo
: null
const expired = (error as HumanInputFormError | null)?.code === 'human_input_form_expired'
const submitted = (error as HumanInputFormError | null)?.code === 'human_input_form_submitted'
@ -54,6 +57,7 @@ const FormContent = () => {
subtitle={t('humanInput.recorded', { ns: 'share' })}
submissionID={token}
removeWebappBrand={removeWebappBrand}
replaceWebappLogo={replaceWebappLogo}
/>
)
}
@ -105,6 +109,7 @@ const FormContent = () => {
isSubmitting={isSubmitting}
onSubmit={submit}
removeWebappBrand={removeWebappBrand}
replaceWebappLogo={replaceWebappLogo}
/>
)
}

View File

@ -6,18 +6,18 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { produce } from 'immer'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import ContentItem from '@/app/components/base/chat/chat/answer/human-input-content/content-item'
import ExpirationTime from '@/app/components/base/chat/chat/answer/human-input-content/expiration-time'
import { getButtonStyle, getRenderedFormInputs, hasInvalidRequiredHumanInput, initializeInputs, splitByOutputVar } from '@/app/components/base/chat/chat/answer/human-input-content/utils'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import BrandingFooter from './branding-footer'
type LoadedFormContentProps = {
formData: FormData
isSubmitting: boolean
onSubmit: (inputs: Record<string, HumanInputFieldValue>, actionID: string, formInputs: FormData['inputs']) => void
removeWebappBrand?: boolean
replaceWebappLogo?: string | null
}
const LoadedFormContent = ({
@ -25,15 +25,25 @@ const LoadedFormContent = ({
isSubmitting,
onSubmit,
removeWebappBrand,
replaceWebappLogo,
}: LoadedFormContentProps) => {
const { t } = useTranslation()
const renderedFormInputs = getRenderedFormInputs(formData.inputs, formData.form_content)
const [inputs, setInputs] = useState<Record<string, HumanInputFieldValue>>(() =>
initializeInputs(renderedFormInputs, formData.resolved_default_values),
)
const contentList = useMemo(() => {
return splitByOutputVar(formData.form_content)
const contentCounts = new Map<string, number>()
return splitByOutputVar(formData.form_content).map((content) => {
const occurrence = (contentCounts.get(content) || 0) + 1
contentCounts.set(content, occurrence)
return {
key: `${content}-${occurrence}`,
content,
}
})
}, [formData.form_content])
const handleInputsChange = (name: string, value: HumanInputFieldValue) => {
@ -63,9 +73,9 @@ const LoadedFormContent = ({
</div>
<div className="h-0 w-full grow overflow-y-auto">
<div className="rounded-[20px] border border-divider-subtle bg-chat-bubble-bg p-4 shadow-lg backdrop-blur-xs">
{contentList.map((content, index) => (
{contentList.map(({ key, content }) => (
<ContentItem
key={index}
key={key}
content={content}
formInputFields={formData.inputs}
inputs={inputs}
@ -86,14 +96,10 @@ const LoadedFormContent = ({
</div>
<ExpirationTime expirationTime={formData.expiration_time * 1000} />
</div>
{!removeWebappBrand && (
<div className="flex flex-row-reverse px-2 py-3">
<div className="flex shrink-0 items-center gap-1.5 px-1">
<div className="system-2xs-medium-uppercase text-text-tertiary">{t('chat.poweredBy', { ns: 'share' })}</div>
<DifyLogo size="small" />
</div>
</div>
)}
<BrandingFooter
removeWebappBrand={removeWebappBrand}
replaceWebappLogo={replaceWebappLogo}
/>
</div>
</div>
)