diff --git a/api/configs/enterprise/__init__.py b/api/configs/enterprise/__init__.py
index b3a93d9773..705ea67bcb 100644
--- a/api/configs/enterprise/__init__.py
+++ b/api/configs/enterprise/__init__.py
@@ -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(
diff --git a/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx b/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx
index ac919ffb05..03e5506b13 100644
--- a/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx
+++ b/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx
@@ -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()
+
+ 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()
+
+ 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()
+ })
})
diff --git a/web/app/(humanInputLayout)/form/[token]/branding-footer.tsx b/web/app/(humanInputLayout)/form/[token]/branding-footer.tsx
new file mode 100644
index 0000000000..bd99dde592
--- /dev/null
+++ b/web/app/(humanInputLayout)/form/[token]/branding-footer.tsx
@@ -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 (
+
+
+
{t('chat.poweredBy', { ns: 'share' })}
+ {replaceWebappLogo
+ ?

+ :
}
+
+
+ )
+}
+
+export default BrandingFooter
diff --git a/web/app/(humanInputLayout)/form/[token]/form-status-card.tsx b/web/app/(humanInputLayout)/form/[token]/form-status-card.tsx
index 03efebe1e3..99da6ec2fc 100644
--- a/web/app/(humanInputLayout)/form/[token]/form-status-card.tsx
+++ b/web/app/(humanInputLayout)/form/[token]/form-status-card.tsx
@@ -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 = ({
)}
- {!removeWebappBrand && (
-
-
-
{t('chat.poweredBy', { ns: 'share' })}
-
-
-
- )}
+
)
diff --git a/web/app/(humanInputLayout)/form/[token]/form.tsx b/web/app/(humanInputLayout)/form/[token]/form.tsx
index ca78b135f9..da50497703 100644
--- a/web/app/(humanInputLayout)/form/[token]/form.tsx
+++ b/web/app/(humanInputLayout)/form/[token]/form.tsx
@@ -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}
/>
)
}
diff --git a/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx b/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx
index 304e1c625d..9810d0a5b1 100644
--- a/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx
+++ b/web/app/(humanInputLayout)/form/[token]/loaded-form-content.tsx
@@ -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, 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>(() =>
initializeInputs(renderedFormInputs, formData.resolved_default_values),
)
const contentList = useMemo(() => {
- return splitByOutputVar(formData.form_content)
+ const contentCounts = new Map()
+
+ 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 = ({
- {contentList.map((content, index) => (
+ {contentList.map(({ key, content }) => (
- {!removeWebappBrand && (
-
-
-
{t('chat.poweredBy', { ns: 'share' })}
-
-
-
- )}
+
)