Merge remote-tracking branch 'origin/main' into feat/model-provider-refactor

This commit is contained in:
yyh
2026-03-06 14:47:56 +08:00
62 changed files with 1802 additions and 1068 deletions

View File

@ -70,6 +70,7 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
mockConfig: {
IS_CLOUD_EDITION: false,
ZENDESK_WIDGET_KEY: '',
SUPPORT_EMAIL_ADDRESS: '',
},
mockEnv: {
env: {
@ -80,6 +81,7 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
vi.mock('@/config', () => ({
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
get SUPPORT_EMAIL_ADDRESS() { return mockConfig.SUPPORT_EMAIL_ADDRESS },
IS_DEV: false,
IS_CE_EDITION: false,
}))

View File

@ -11,6 +11,10 @@ const { mockZendeskKey } = vi.hoisted(() => ({
mockZendeskKey: { value: 'test-key' },
}))
const { mockSupportEmailKey } = vi.hoisted(() => ({
mockSupportEmailKey: { value: '' },
}))
vi.mock('@/context/app-context', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/context/app-context')>()
return {
@ -33,6 +37,7 @@ vi.mock('@/config', async (importOriginal) => {
...actual,
IS_CE_EDITION: false,
get ZENDESK_WIDGET_KEY() { return mockZendeskKey.value },
get SUPPORT_EMAIL_ADDRESS() { return mockSupportEmailKey.value },
}
})
@ -84,6 +89,7 @@ describe('Support', () => {
vi.clearAllMocks()
window.zE = vi.fn()
mockZendeskKey.value = 'test-key'
mockSupportEmailKey.value = ''
vi.mocked(useAppContext).mockReturnValue(baseAppContextValue)
vi.mocked(useProviderContext).mockReturnValue({
...baseProviderContextValue,
@ -96,7 +102,7 @@ describe('Support', () => {
const renderSupport = () => {
return render(
<DropdownMenu open={true} onOpenChange={() => {}}>
<DropdownMenu open={true} onOpenChange={() => { }}>
<DropdownMenuTrigger>open</DropdownMenuTrigger>
<DropdownMenuContent>
<Support closeAccountDropdown={mockCloseAccountDropdown} />
@ -125,7 +131,7 @@ describe('Support', () => {
})
})
describe('Plan-based Channels', () => {
describe('Dedicated Channels', () => {
it('should show "Contact Us" when ZENDESK_WIDGET_KEY is present', () => {
// Act
renderSupport()
@ -166,6 +172,27 @@ describe('Support', () => {
expect(screen.getByText('common.userProfile.emailSupport')).toBeInTheDocument()
expect(screen.queryByText('common.userProfile.contactUs')).not.toBeInTheDocument()
})
it('should show email support if specified in the config', () => {
// Arrange
mockZendeskKey.value = ''
mockSupportEmailKey.value = 'support@example.com'
vi.mocked(useProviderContext).mockReturnValue({
...baseProviderContextValue,
plan: {
...baseProviderContextValue.plan,
type: Plan.sandbox,
},
})
// Act
renderSupport()
fireEvent.click(screen.getByText('common.userProfile.support'))
// Assert
expect(screen.queryByText('common.userProfile.emailSupport')).toBeInTheDocument()
expect(screen.getByText('common.userProfile.emailSupport')?.closest('a')?.getAttribute('href')).toMatch(new RegExp(`^mailto:${mockSupportEmailKey.value}`))
})
})
describe('Interactions and Links', () => {

View File

@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu'
import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils'
import { Plan } from '@/app/components/billing/type'
import { ZENDESK_WIDGET_KEY } from '@/config'
import { SUPPORT_EMAIL_ADDRESS, ZENDESK_WIDGET_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { mailToSupport } from '../utils/util'
@ -17,8 +17,8 @@ export default function Support({ closeAccountDropdown }: SupportProps) {
const { t } = useTranslation()
const { plan } = useProviderContext()
const { userProfile, langGeniusVersionInfo } = useAppContext()
const hasDedicatedChannel = plan.type !== Plan.sandbox
const hasZendeskWidget = !!ZENDESK_WIDGET_KEY?.trim()
const hasDedicatedChannel = plan.type !== Plan.sandbox || Boolean(SUPPORT_EMAIL_ADDRESS.trim())
const hasZendeskWidget = Boolean(ZENDESK_WIDGET_KEY.trim())
return (
<DropdownMenuSub>
@ -49,7 +49,7 @@ export default function Support({ closeAccountDropdown }: SupportProps) {
{hasDedicatedChannel && !hasZendeskWidget && (
<DropdownMenuItem
className="justify-between"
render={<a href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo?.current_version)} rel="noopener noreferrer" target="_blank" />}
render={<a href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo?.current_version, SUPPORT_EMAIL_ADDRESS)} rel="noopener noreferrer" target="_blank" />}
>
<MenuItemContent
iconClassName="i-ri-mail-send-line"

View File

@ -10,7 +10,7 @@ export const generateMailToLink = (email: string, subject?: string, body?: strin
return mailtoLink
}
export const mailToSupport = (account: string, plan: string, version: string) => {
export const mailToSupport = (account: string, plan: string, version: string, supportEmailAddress?: string) => {
const subject = `Technical Support Request ${plan} ${account}`
const body = `
Please do not remove the following information:
@ -21,5 +21,5 @@ export const mailToSupport = (account: string, plan: string, version: string) =>
Platform:
Problem Description:
`
return generateMailToLink('support@dify.ai', subject, body)
return generateMailToLink(supportEmailAddress || 'support@dify.ai', subject, body)
}

View File

@ -342,6 +342,12 @@ export const ZENDESK_FIELD_IDS = {
'',
),
}
export const SUPPORT_EMAIL_ADDRESS = getStringConfig(
env.NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS,
'',
)
export const APP_VERSION = pkg.version
export const IS_MARKETPLACE = env.NEXT_PUBLIC_IS_MARKETPLACE

View File

@ -115,6 +115,7 @@ const clientSchema = {
*/
NEXT_PUBLIC_SENTRY_DSN: z.string().optional(),
NEXT_PUBLIC_SITE_ABOUT: z.string().optional(),
NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS: z.email().optional(),
NEXT_PUBLIC_SUPPORT_MAIL_LOGIN: coercedBoolean.default(false),
/**
* The timeout for the text generation in millisecond
@ -184,6 +185,7 @@ export const env = createEnv({
NEXT_PUBLIC_PUBLIC_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX : getRuntimeEnvFromBody('publicApiPrefix'),
NEXT_PUBLIC_SENTRY_DSN: isServer ? process.env.NEXT_PUBLIC_SENTRY_DSN : getRuntimeEnvFromBody('sentryDsn'),
NEXT_PUBLIC_SITE_ABOUT: isServer ? process.env.NEXT_PUBLIC_SITE_ABOUT : getRuntimeEnvFromBody('siteAbout'),
NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS: isServer ? process.env.NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS : getRuntimeEnvFromBody('supportEmailAddress'),
NEXT_PUBLIC_SUPPORT_MAIL_LOGIN: isServer ? process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN : getRuntimeEnvFromBody('supportMailLogin'),
NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS: isServer ? process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS : getRuntimeEnvFromBody('textGenerationTimeoutMs'),
NEXT_PUBLIC_TOP_K_MAX_VALUE: isServer ? process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE : getRuntimeEnvFromBody('topKMaxValue'),

View File

@ -124,7 +124,7 @@
"metadata.datasetMetadata.deleteContent": "Êtes-vous sûr de vouloir supprimer les métadonnées \"{{name}}\" ?",
"metadata.datasetMetadata.deleteTitle": "Confirmer la suppression",
"metadata.datasetMetadata.description": "Vous pouvez gérer toutes les métadonnées dans cette connaissance ici. Les modifications seront synchronisées avec chaque document.",
"metadata.datasetMetadata.disabled": "handicapés",
"metadata.datasetMetadata.disabled": "Désactivé",
"metadata.datasetMetadata.name": "Nom",
"metadata.datasetMetadata.namePlaceholder": "Nom de métadonnées",
"metadata.datasetMetadata.rename": "Renommer",

View File

@ -95,7 +95,7 @@
"detailPanel.deprecation.reason.businessAdjustments": "ajustements commerciaux",
"detailPanel.deprecation.reason.noMaintainer": "aucun mainteneur",
"detailPanel.deprecation.reason.ownershipTransferred": "propriété transférée",
"detailPanel.disabled": "Handicapé",
"detailPanel.disabled": "Désactivé",
"detailPanel.endpointDeleteContent": "Souhaitez-vous supprimer {{name}} ?",
"detailPanel.endpointDeleteTip": "Supprimer le point de terminaison",
"detailPanel.endpointDisableContent": "Souhaitez-vous désactiver {{name}} ?",

View File

@ -687,7 +687,7 @@
"nodes.knowledgeRetrieval.metadata.options.automatic.subTitle": "Générer automatiquement des conditions de filtrage des métadonnées en fonction de la requête de l'utilisateur",
"nodes.knowledgeRetrieval.metadata.options.automatic.title": "Automatique",
"nodes.knowledgeRetrieval.metadata.options.disabled.subTitle": "Ne pas activer le filtrage des métadonnées",
"nodes.knowledgeRetrieval.metadata.options.disabled.title": "Handicapé",
"nodes.knowledgeRetrieval.metadata.options.disabled.title": "Désactivé",
"nodes.knowledgeRetrieval.metadata.options.manual.subTitle": "Ajouter manuellement des conditions de filtrage des métadonnées",
"nodes.knowledgeRetrieval.metadata.options.manual.title": "Manuel",
"nodes.knowledgeRetrieval.metadata.panel.add": "Ajouter une condition",

View File

@ -99,7 +99,7 @@
"cron-parser": "5.4.0",
"dayjs": "1.11.19",
"decimal.js": "10.6.0",
"dompurify": "3.3.0",
"dompurify": "3.3.2",
"echarts": "5.6.0",
"echarts-for-react": "3.0.5",
"elkjs": "0.9.3",

47
web/pnpm-lock.yaml generated
View File

@ -169,8 +169,8 @@ importers:
specifier: 10.6.0
version: 10.6.0
dompurify:
specifier: 3.3.0
version: 3.3.0
specifier: 3.3.2
version: 3.3.2
echarts:
specifier: 5.6.0
version: 5.6.0
@ -4492,8 +4492,9 @@ packages:
dompurify@3.2.7:
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
dompurify@3.3.0:
resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==}
dompurify@3.3.2:
resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==}
engines: {node: '>=20'}
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@ -4657,7 +4658,7 @@ packages:
eslint: '*'
eslint-plugin-better-tailwindcss@https://pkg.pr.new/hyoban/eslint-plugin-better-tailwindcss@a520d15:
resolution: {integrity: sha512-hbxpqInIW0Q5UIwXEuQxSBjrMd5bYttXeSPU6dfK2zpECKNIzGR+KXZZEdZaPagEMDJosSyQ9RKievmBcCAxfA==, tarball: https://pkg.pr.new/hyoban/eslint-plugin-better-tailwindcss@a520d15}
resolution: {tarball: https://pkg.pr.new/hyoban/eslint-plugin-better-tailwindcss@a520d15}
version: 4.3.1
engines: {node: ^20.19.0 || ^22.12.0 || >=23.0.0}
peerDependencies:
@ -6548,9 +6549,6 @@ packages:
resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==}
engines: {node: '>=14.18.0'}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@ -6946,9 +6944,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
seroval-plugins@1.5.0:
resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==}
engines: {node: '>=10'}
@ -7223,8 +7218,8 @@ packages:
engines: {node: '>=18'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
terser-webpack-plugin@5.3.16:
resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==}
terser-webpack-plugin@5.3.17:
resolution: {integrity: sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==}
engines: {node: '>= 10.13.0'}
peerDependencies:
'@swc/core': '*'
@ -7567,7 +7562,7 @@ packages:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vinext@https://pkg.pr.new/hyoban/vinext@556a6d6:
resolution: {integrity: sha512-Sz8RkTDsY6cnGrevlQi4nXgahu8okEGsdKY5m31d/L9tXo35bNETMHfVee5gaI2UKZS9LMcffWaTOxxINUgogQ==, tarball: https://pkg.pr.new/hyoban/vinext@556a6d6}
resolution: {tarball: https://pkg.pr.new/hyoban/vinext@556a6d6}
version: 0.0.5
engines: {node: '>=22'}
hasBin: true
@ -12198,7 +12193,7 @@ snapshots:
optionalDependencies:
'@types/trusted-types': 2.0.7
dompurify@3.3.0:
dompurify@3.3.2:
optionalDependencies:
'@types/trusted-types': 2.0.7
@ -13978,7 +13973,7 @@ snapshots:
d3-sankey: 0.12.3
dagre-d3-es: 7.0.11
dayjs: 1.11.19
dompurify: 3.3.0
dompurify: 3.3.2
katex: 0.16.25
khroma: 2.1.0
lodash-es: 4.17.23
@ -14124,8 +14119,8 @@ snapshots:
micromark-extension-mdxjs@3.0.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
acorn: 8.15.0
acorn-jsx: 5.3.2(acorn@8.15.0)
micromark-extension-mdx-expression: 3.0.1
micromark-extension-mdx-jsx: 3.0.2
micromark-extension-mdx-md: 2.0.0
@ -14776,10 +14771,6 @@ snapshots:
radash@12.1.1: {}
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
@ -15284,7 +15275,8 @@ snapshots:
dependencies:
tslib: 2.8.1
safe-buffer@5.2.1: {}
safe-buffer@5.2.1:
optional: true
sass@1.93.2:
dependencies:
@ -15335,10 +15327,6 @@ snapshots:
semver@7.7.4: {}
serialize-javascript@6.0.2:
dependencies:
randombytes: 2.1.0
seroval-plugins@1.5.0(seroval@1.5.0):
dependencies:
seroval: 1.5.0
@ -15681,12 +15669,11 @@ snapshots:
minizlib: 3.1.0
yallist: 5.0.0
terser-webpack-plugin@5.3.16(esbuild@0.27.2)(uglify-js@3.19.3)(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)):
terser-webpack-plugin@5.3.17(esbuild@0.27.2)(uglify-js@3.19.3)(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
terser: 5.46.0
webpack: 5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)
optionalDependencies:
@ -16249,7 +16236,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 4.3.3
tapable: 2.3.0
terser-webpack-plugin: 5.3.16(esbuild@0.27.2)(uglify-js@3.19.3)(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3))
terser-webpack-plugin: 5.3.17(esbuild@0.27.2)(uglify-js@3.19.3)(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3))
watchpack: 2.5.1
webpack-sources: 3.3.4
transitivePeerDependencies: