chore: support Zendesk widget (#25517)

This commit is contained in:
Nite Knite 2025-09-11 13:17:50 +08:00 committed by GitHub
parent af7f67dc9c
commit 07d067d828
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 155 additions and 35 deletions

View File

@ -24,7 +24,7 @@ const GA: FC<IGAProps> = ({
if (IS_CE_EDITION) if (IS_CE_EDITION)
return null return null
const nonce = process.env.NODE_ENV === 'production' ? (headers() as unknown as UnsafeUnwrappedHeaders).get('x-nonce') : '' const nonce = process.env.NODE_ENV === 'production' ? (headers() as unknown as UnsafeUnwrappedHeaders).get('x-nonce') ?? '' : ''
return ( return (
<> <>
@ -32,7 +32,7 @@ const GA: FC<IGAProps> = ({
strategy="beforeInteractive" strategy="beforeInteractive"
async async
src={`https://www.googletagmanager.com/gtag/js?id=${gaIdMaps[gaType]}`} src={`https://www.googletagmanager.com/gtag/js?id=${gaIdMaps[gaType]}`}
nonce={nonce!} nonce={nonce ?? undefined}
></Script> ></Script>
<Script <Script
id="ga-init" id="ga-init"
@ -44,14 +44,14 @@ gtag('js', new Date());
gtag('config', '${gaIdMaps[gaType]}'); gtag('config', '${gaIdMaps[gaType]}');
`, `,
}} }}
nonce={nonce!} nonce={nonce ?? undefined}
> >
</Script> </Script>
{/* Cookie banner */} {/* Cookie banner */}
<Script <Script
id="cookieyes" id="cookieyes"
src='https://cdn-cookieyes.com/client_data/2a645945fcae53f8e025a2b1/script.js' src='https://cdn-cookieyes.com/client_data/2a645945fcae53f8e025a2b1/script.js'
nonce={nonce!} nonce={nonce ?? undefined}
></Script> ></Script>
</> </>

View File

@ -0,0 +1,21 @@
import { memo } from 'react'
import { type UnsafeUnwrappedHeaders, headers } from 'next/headers'
import Script from 'next/script'
import { IS_CE_EDITION, ZENDESK_WIDGET_KEY } from '@/config'
const Zendesk = () => {
if (IS_CE_EDITION || !ZENDESK_WIDGET_KEY)
return null
const nonce = process.env.NODE_ENV === 'production' ? (headers() as unknown as UnsafeUnwrappedHeaders).get('x-nonce') ?? '' : ''
return (
<Script
nonce={nonce ?? undefined}
id="ze-snippet"
src={`https://static.zdassets.com/ekr/snippet.js?key=${ZENDESK_WIDGET_KEY}`}
/>
)
}
export default memo(Zendesk)

View File

@ -0,0 +1,23 @@
import { IS_CE_EDITION } from '@/config'
export type ConversationField = {
id: string,
value: any,
}
declare global {
// eslint-disable-next-line ts/consistent-type-definitions
interface Window {
zE?: (
command: string,
value: string,
payload?: ConversationField[] | string | string[] | (() => any),
callback?: () => any,
) => void;
}
}
export const setZendeskConversationFields = (fields: ConversationField[], callback?: () => any) => {
if (!IS_CE_EDITION && window.zE)
window.zE('messenger:set', 'conversationFields', fields, callback)
}

View File

@ -3,6 +3,7 @@ import type { Viewport } from 'next'
import I18nServer from './components/i18n-server' import I18nServer from './components/i18n-server'
import BrowserInitializer from './components/browser-initializer' import BrowserInitializer from './components/browser-initializer'
import SentryInitializer from './components/sentry-initializer' import SentryInitializer from './components/sentry-initializer'
import Zendesk from './components/base/zendesk'
import { getLocaleOnServer } from '@/i18n-config/server' import { getLocaleOnServer } from '@/i18n-config/server'
import { TanstackQueryInitializer } from '@/context/query-client' import { TanstackQueryInitializer } from '@/context/query-client'
import { ThemeProvider } from 'next-themes' import { ThemeProvider } from 'next-themes'
@ -48,6 +49,12 @@ const LocaleLayout = async ({
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER, [DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER,
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL, [DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL, [DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_WIDGET_KEY]: process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN,
} }
return ( return (
@ -89,6 +96,7 @@ const LocaleLayout = async ({
</BrowserInitializer> </BrowserInitializer>
</ThemeProvider> </ThemeProvider>
<RoutePrefixHandle /> <RoutePrefixHandle />
<Zendesk />
</body> </body>
</html> </html>
) )

View File

@ -173,46 +173,46 @@ export const DEFAULT_AGENT_SETTING = {
export const DEFAULT_AGENT_PROMPT = { export const DEFAULT_AGENT_PROMPT = {
chat: `Respond to the human as helpfully and accurately as possible. chat: `Respond to the human as helpfully and accurately as possible.
{{instruction}} {{instruction}}
You have access to the following tools: You have access to the following tools:
{{tools}} {{tools}}
Use a json blob to specify a tool by providing an {{TOOL_NAME_KEY}} key (tool name) and an {{ACTION_INPUT_KEY}} key (tool input). Use a json blob to specify a tool by providing an {{TOOL_NAME_KEY}} key (tool name) and an {{ACTION_INPUT_KEY}} key (tool input).
Valid "{{TOOL_NAME_KEY}}" values: "Final Answer" or {{tool_names}} Valid "{{TOOL_NAME_KEY}}" values: "Final Answer" or {{tool_names}}
Provide only ONE action per $JSON_BLOB, as shown: Provide only ONE action per $JSON_BLOB, as shown:
\`\`\` \`\`\`
{ {
"{{TOOL_NAME_KEY}}": $TOOL_NAME, "{{TOOL_NAME_KEY}}": $TOOL_NAME,
"{{ACTION_INPUT_KEY}}": $ACTION_INPUT "{{ACTION_INPUT_KEY}}": $ACTION_INPUT
} }
\`\`\` \`\`\`
Follow this format: Follow this format:
Question: input question to answer Question: input question to answer
Thought: consider previous and subsequent steps Thought: consider previous and subsequent steps
Action: Action:
\`\`\` \`\`\`
$JSON_BLOB $JSON_BLOB
\`\`\` \`\`\`
Observation: action result Observation: action result
... (repeat Thought/Action/Observation N times) ... (repeat Thought/Action/Observation N times)
Thought: I know what to respond Thought: I know what to respond
Action: Action:
\`\`\` \`\`\`
{ {
"{{TOOL_NAME_KEY}}": "Final Answer", "{{TOOL_NAME_KEY}}": "Final Answer",
"{{ACTION_INPUT_KEY}}": "Final response to human" "{{ACTION_INPUT_KEY}}": "Final response to human"
} }
\`\`\` \`\`\`
Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation:.`, Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation:.`,
completion: ` completion: `
Respond to the human as helpfully and accurately as possible. Respond to the human as helpfully and accurately as possible.
{{instruction}} {{instruction}}
@ -285,3 +285,12 @@ export const ENABLE_WEBSITE_WATERCRAWL = getBooleanConfig(process.env.NEXT_PUBLI
export const VALUE_SELECTOR_DELIMITER = '@@@' export const VALUE_SELECTOR_DELIMITER = '@@@'
export const validPassword = /^(?=.*[a-zA-Z])(?=.*\d)\S{8,}$/ export const validPassword = /^(?=.*[a-zA-Z])(?=.*\d)\S{8,}$/
export const ZENDESK_WIDGET_KEY = getStringConfig(process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY, DatasetAttr.NEXT_PUBLIC_ZENDESK_WIDGET_KEY, '')
export const ZENDESK_FIELD_IDS = {
ENVIRONMENT: getStringConfig(process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT, DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT, ''),
VERSION: getStringConfig(process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION, DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION, ''),
EMAIL: getStringConfig(process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL, DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL, ''),
WORKSPACE_ID: getStringConfig(process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID, DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID, ''),
PLAN: getStringConfig(process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN, DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN, ''),
}

View File

@ -8,6 +8,8 @@ import { fetchCurrentWorkspace, fetchLangGeniusVersion, fetchUserProfile } from
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import MaintenanceNotice from '@/app/components/header/maintenance-notice' import MaintenanceNotice from '@/app/components/header/maintenance-notice'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
import { ZENDESK_FIELD_IDS } from '@/config'
export type AppContextValue = { export type AppContextValue = {
userProfile: UserProfileResponse userProfile: UserProfileResponse
@ -115,6 +117,44 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
setCurrentWorkspace(currentWorkspaceResponse) setCurrentWorkspace(currentWorkspaceResponse)
}, [currentWorkspaceResponse]) }, [currentWorkspaceResponse])
// #region Zendesk conversation fields
useEffect(() => {
if (ZENDESK_FIELD_IDS.ENVIRONMENT && langGeniusVersionInfo?.current_env) {
setZendeskConversationFields([{
id: ZENDESK_FIELD_IDS.ENVIRONMENT,
value: langGeniusVersionInfo.current_env.toLowerCase(),
}])
}
}, [langGeniusVersionInfo?.current_env])
useEffect(() => {
if (ZENDESK_FIELD_IDS.VERSION && langGeniusVersionInfo?.version) {
setZendeskConversationFields([{
id: ZENDESK_FIELD_IDS.VERSION,
value: langGeniusVersionInfo.version,
}])
}
}, [langGeniusVersionInfo?.version])
useEffect(() => {
if (ZENDESK_FIELD_IDS.EMAIL && userProfile?.email) {
setZendeskConversationFields([{
id: ZENDESK_FIELD_IDS.EMAIL,
value: userProfile.email,
}])
}
}, [userProfile?.email])
useEffect(() => {
if (ZENDESK_FIELD_IDS.WORKSPACE_ID && currentWorkspace?.id) {
setZendeskConversationFields([{
id: ZENDESK_FIELD_IDS.WORKSPACE_ID,
value: currentWorkspace.id,
}])
}
}, [currentWorkspace?.id])
// #endregion Zendesk conversation fields
return ( return (
<AppContext.Provider value={{ <AppContext.Provider value={{
userProfile, userProfile,

View File

@ -27,6 +27,8 @@ import {
useEducationStatus, useEducationStatus,
} from '@/service/use-education' } from '@/service/use-education'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
import { ZENDESK_FIELD_IDS } from '@/config'
type ProviderContextState = { type ProviderContextState = {
modelProviders: ModelProvider[] modelProviders: ModelProvider[]
@ -189,6 +191,17 @@ export const ProviderContextProvider = ({
fetchPlan() fetchPlan()
}, []) }, [])
// #region Zendesk conversation fields
useEffect(() => {
if (ZENDESK_FIELD_IDS.PLAN && plan.type) {
setZendeskConversationFields([{
id: ZENDESK_FIELD_IDS.PLAN,
value: `${plan.type}-plan`,
}])
}
}, [plan.type])
// #endregion Zendesk conversation fields
const { t } = useTranslation() const { t } = useTranslation()
useEffect(() => { useEffect(() => {
if (localStorage.getItem('anthropic_quota_notice') === 'true') if (localStorage.getItem('anthropic_quota_notice') === 'true')

View File

@ -122,4 +122,10 @@ export enum DatasetAttr {
DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER = 'data-public-enable-website-jinareader', DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER = 'data-public-enable-website-jinareader',
DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL = 'data-public-enable-website-firecrawl', DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL = 'data-public-enable-website-firecrawl',
DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL = 'data-public-enable-website-watercrawl', DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL = 'data-public-enable-website-watercrawl',
NEXT_PUBLIC_ZENDESK_WIDGET_KEY = 'next-public-zendesk-widget-key',
NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT = 'next-public-zendesk-field-id-environment',
NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION = 'next-public-zendesk-field-id-version',
NEXT_PUBLIC_ZENDESK_FIELD_ID_EMAIL = 'next-public-zendesk-field-id-email',
NEXT_PUBLIC_ZENDESK_FIELD_ID_WORKSPACE_ID = 'next-public-zendesk-field-id-workspace-id',
NEXT_PUBLIC_ZENDESK_FIELD_ID_PLAN = 'next-public-zendesk-field-id-plan',
} }