Browse Source

chore: support Zendesk widget (#25517)

tags/1.9.0
Nite Knite 1 month ago
parent
commit
07d067d828
No account linked to committer's email address

+ 4
- 4
web/app/components/base/ga/index.tsx View File

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 (
<> <>
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"
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>
</> </>



+ 21
- 0
web/app/components/base/zendesk/index.tsx View File

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)

+ 23
- 0
web/app/components/base/zendesk/utils.ts View File

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)
}

+ 8
- 0
web/app/layout.tsx View File

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'
[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 (
</BrowserInitializer> </BrowserInitializer>
</ThemeProvider> </ThemeProvider>
<RoutePrefixHandle /> <RoutePrefixHandle />
<Zendesk />
</body> </body>
</html> </html>
) )

+ 42
- 33
web/config/index.ts View File

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).
Valid "{{TOOL_NAME_KEY}}" values: "Final Answer" or {{tool_names}}
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}}


Provide only ONE action per $JSON_BLOB, as shown:
Provide only ONE action per $JSON_BLOB, as shown:


\`\`\`
{
"{{TOOL_NAME_KEY}}": $TOOL_NAME,
"{{ACTION_INPUT_KEY}}": $ACTION_INPUT
}
\`\`\`

Follow this format:

Question: input question to answer
Thought: consider previous and subsequent steps
Action:
\`\`\`
$JSON_BLOB
\`\`\`
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
\`\`\`
{
"{{TOOL_NAME_KEY}}": "Final Answer",
"{{ACTION_INPUT_KEY}}": "Final response to human"
}
\`\`\`
\`\`\`
{
"{{TOOL_NAME_KEY}}": $TOOL_NAME,
"{{ACTION_INPUT_KEY}}": $ACTION_INPUT
}
\`\`\`

Follow this format:


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:.`,
Question: input question to answer
Thought: consider previous and subsequent steps
Action:
\`\`\`
$JSON_BLOB
\`\`\`
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
\`\`\`
{
"{{TOOL_NAME_KEY}}": "Final Answer",
"{{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:.`,
completion: ` completion: `
Respond to the human as helpfully and accurately as possible.
Respond to the human as helpfully and accurately as possible.


{{instruction}} {{instruction}}


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, ''),
}

+ 40
- 0
web/context/app-context.tsx View File

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
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,

+ 13
- 0
web/context/provider-context.tsx View File

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[]
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')

+ 6
- 0
web/types/feature.ts View File

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',
} }

Loading…
Cancel
Save