Переглянути джерело

chore: support Zendesk widget (#25517)

tags/1.9.0
Nite Knite 1 місяць тому
джерело
коміт
07d067d828
Аккаунт користувача з таким Email не знайдено

+ 4
- 4
web/app/components/base/ga/index.tsx Переглянути файл

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


+ 21
- 0
web/app/components/base/zendesk/index.tsx Переглянути файл

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

+ 23
- 0
web/app/components/base/zendesk/utils.ts Переглянути файл

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

+ 8
- 0
web/app/layout.tsx Переглянути файл

@@ -3,6 +3,7 @@ import type { Viewport } from 'next'
import I18nServer from './components/i18n-server'
import BrowserInitializer from './components/browser-initializer'
import SentryInitializer from './components/sentry-initializer'
import Zendesk from './components/base/zendesk'
import { getLocaleOnServer } from '@/i18n-config/server'
import { TanstackQueryInitializer } from '@/context/query-client'
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_FIRECRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
[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 (
@@ -89,6 +96,7 @@ const LocaleLayout = async ({
</BrowserInitializer>
</ThemeProvider>
<RoutePrefixHandle />
<Zendesk />
</body>
</html>
)

+ 42
- 33
web/config/index.ts Переглянути файл

@@ -173,46 +173,46 @@ export const DEFAULT_AGENT_SETTING = {
export const DEFAULT_AGENT_PROMPT = {
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: `
Respond to the human as helpfully and accurately as possible.
Respond to the human as helpfully and accurately as possible.

{{instruction}}

@@ -285,3 +285,12 @@ export const ENABLE_WEBSITE_WATERCRAWL = getBooleanConfig(process.env.NEXT_PUBLI
export const VALUE_SELECTOR_DELIMITER = '@@@'

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 Переглянути файл

@@ -8,6 +8,8 @@ import { fetchCurrentWorkspace, fetchLangGeniusVersion, fetchUserProfile } from
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import MaintenanceNotice from '@/app/components/header/maintenance-notice'
import { noop } from 'lodash-es'
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
import { ZENDESK_FIELD_IDS } from '@/config'

export type AppContextValue = {
userProfile: UserProfileResponse
@@ -115,6 +117,44 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
setCurrentWorkspace(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 (
<AppContext.Provider value={{
userProfile,

+ 13
- 0
web/context/provider-context.tsx Переглянути файл

@@ -27,6 +27,8 @@ import {
useEducationStatus,
} from '@/service/use-education'
import { noop } from 'lodash-es'
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
import { ZENDESK_FIELD_IDS } from '@/config'

type ProviderContextState = {
modelProviders: ModelProvider[]
@@ -189,6 +191,17 @@ export const ProviderContextProvider = ({
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()
useEffect(() => {
if (localStorage.getItem('anthropic_quota_notice') === 'true')

+ 6
- 0
web/types/feature.ts Переглянути файл

@@ -122,4 +122,10 @@ export enum DatasetAttr {
DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER = 'data-public-enable-website-jinareader',
DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL = 'data-public-enable-website-firecrawl',
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',
}

Завантаження…
Відмінити
Зберегти