mirror of
https://github.com/langgenius/dify.git
synced 2026-07-27 23:18:33 +08:00
feat: add amptitude to new agent (#39608)
Co-authored-by: 林玮 (Jade Lin) <linw1995@icloud.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
28d174603f
commit
52428df1bd
@ -17,6 +17,12 @@ import { isValidGeneratedAnswer } from '../../utils'
|
||||
import ChatWrapper from '../chat-wrapper'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
|
||||
const mockTrackEvent = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: mockTrackEvent,
|
||||
}))
|
||||
|
||||
vi.mock('../../chat/hooks', () => ({
|
||||
useChat: vi.fn(),
|
||||
}))
|
||||
@ -75,6 +81,7 @@ vi.mock('@/hooks/use-timestamp', () => ({
|
||||
type ChatHookReturn = ReturnType<typeof useChat>
|
||||
|
||||
const mockAppData = {
|
||||
mode: 'advanced-chat',
|
||||
site: {
|
||||
title: 'Test Chat',
|
||||
chat_color_theme: 'blue',
|
||||
@ -745,7 +752,34 @@ describe('ChatWrapper', () => {
|
||||
expect(fetchChatList).toHaveBeenCalledWith('conversation-1', 'webApp', 'test-app-id')
|
||||
})
|
||||
|
||||
it('should not fetch current conversation messages for non-new-agent chat', async () => {
|
||||
it('should track the start action when a new agent web app sends a message', async () => {
|
||||
const handleSend = vi.fn()
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
handleSend,
|
||||
chatList: [
|
||||
{ id: '1', isOpeningStatement: true, content: 'Welcome', suggestedQuestions: ['Q1'] },
|
||||
],
|
||||
suggestedQuestions: ['Q1'],
|
||||
} as unknown as ChatHookReturn)
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '',
|
||||
isInstalledApp: false,
|
||||
isNewAgent: true,
|
||||
})
|
||||
|
||||
render(<ChatWrapper />)
|
||||
|
||||
fireEvent.click(await screen.findByText('Q1'))
|
||||
|
||||
expect(handleSend).toHaveBeenCalled()
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('webapp_run', {
|
||||
app_mode: 'agent-v2',
|
||||
})
|
||||
})
|
||||
|
||||
it('should track the site response mode without fetching messages for a regular web app', async () => {
|
||||
const handleSend = vi.fn()
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
@ -768,6 +802,9 @@ describe('ChatWrapper', () => {
|
||||
|
||||
const options = handleSend.mock.calls[0]![2]
|
||||
expect(options.onGetConversationMessages).toBeUndefined()
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('webapp_run', {
|
||||
app_mode: 'advanced-chat',
|
||||
})
|
||||
})
|
||||
|
||||
it('should call fetchSuggestedQuestions in doSwitchSibling', async () => {
|
||||
|
||||
@ -6,6 +6,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import InputsForm from '@/app/components/base/chat/chat-with-history/inputs-form'
|
||||
@ -221,6 +222,9 @@ const ChatWrapper = () => {
|
||||
onConversationComplete: isHistoryConversation ? undefined : handleNewConversationCompleted,
|
||||
isPublicAPI: appSourceType === AppSourceType.webApp,
|
||||
})
|
||||
const appMode = isNewAgent ? 'agent-v2' : appData?.mode
|
||||
if (appSourceType === AppSourceType.webApp && appMode)
|
||||
trackEvent('webapp_run', { app_mode: appMode })
|
||||
},
|
||||
[
|
||||
inputsForms,
|
||||
@ -234,6 +238,7 @@ const ChatWrapper = () => {
|
||||
isHistoryConversation,
|
||||
handleNewConversationCompleted,
|
||||
isNewAgent,
|
||||
appData?.mode,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -11,6 +11,12 @@ import { useChat } from '../../chat/hooks'
|
||||
import ChatWrapper from '../chat-wrapper'
|
||||
import { useEmbeddedChatbotContext } from '../context'
|
||||
|
||||
const mockTrackEvent = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: mockTrackEvent,
|
||||
}))
|
||||
|
||||
vi.mock('../context', () => ({
|
||||
useEmbeddedChatbotContext: vi.fn(),
|
||||
}))
|
||||
@ -132,6 +138,7 @@ const createContextValue = (
|
||||
appMeta: { tool_icons: {} },
|
||||
appData: {
|
||||
app_id: 'app-1',
|
||||
mode: 'chat',
|
||||
can_replace_logo: true,
|
||||
custom_config: {
|
||||
remove_webapp_brand: false,
|
||||
@ -533,6 +540,7 @@ describe('EmbeddedChatbot chat-wrapper', () => {
|
||||
expect(fetchSuggestedQuestions).toHaveBeenCalledWith('resp-2', AppSourceType.tryApp, 'app-1')
|
||||
expect(handleStop).toHaveBeenCalled()
|
||||
expect(screen.queryByRole('img', { name: 'Alice' })).not.toBeInTheDocument()
|
||||
expect(mockTrackEvent).not.toHaveBeenCalled()
|
||||
|
||||
cleanup()
|
||||
vi.mocked(useEmbeddedChatbotContext).mockReturnValue(
|
||||
@ -739,6 +747,9 @@ describe('EmbeddedChatbot chat-wrapper', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'send through chat' }))
|
||||
|
||||
expect(handleSend).toHaveBeenCalled()
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('webapp_run', {
|
||||
app_mode: 'chat',
|
||||
})
|
||||
const options = handleSend.mock.calls[0]?.[2] as {
|
||||
onConversationComplete?: (id: string) => void
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||
@ -207,6 +208,8 @@ const ChatWrapper = () => {
|
||||
onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted,
|
||||
isPublicAPI: appSourceType === AppSourceType.webApp,
|
||||
})
|
||||
if (appSourceType === AppSourceType.webApp && appData?.mode)
|
||||
trackEvent('webapp_run', { app_mode: appData.mode })
|
||||
},
|
||||
[
|
||||
currentConversationId,
|
||||
@ -217,6 +220,7 @@ const ChatWrapper = () => {
|
||||
appSourceType,
|
||||
appId,
|
||||
handleNewConversationCompleted,
|
||||
appData?.mode,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ import { useResultSender } from '../use-result-sender'
|
||||
const {
|
||||
buildResultRequestDataMock,
|
||||
createWorkflowStreamHandlersMock,
|
||||
mockTrackEvent,
|
||||
mockWebAppState,
|
||||
sendCompletionMessageMock,
|
||||
sendWorkflowMessageMock,
|
||||
sleepMock,
|
||||
@ -19,12 +21,27 @@ const {
|
||||
} = vi.hoisted(() => ({
|
||||
buildResultRequestDataMock: vi.fn(),
|
||||
createWorkflowStreamHandlersMock: vi.fn(),
|
||||
mockTrackEvent: vi.fn(),
|
||||
mockWebAppState: {
|
||||
appInfo: {
|
||||
mode: 'completion',
|
||||
},
|
||||
},
|
||||
sendCompletionMessageMock: vi.fn(),
|
||||
sendWorkflowMessageMock: vi.fn(),
|
||||
sleepMock: vi.fn(),
|
||||
validateResultRequestMock: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: mockTrackEvent,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/web-app-context', () => ({
|
||||
useWebAppStore: (selector: (state: typeof mockWebAppState) => unknown) =>
|
||||
selector(mockWebAppState),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/share', async () => {
|
||||
const actual = await vi.importActual<typeof import('@/service/share')>('@/service/share')
|
||||
return {
|
||||
@ -226,6 +243,7 @@ const renderSender = ({
|
||||
describe('useResultSender', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockWebAppState.appInfo.mode = 'completion'
|
||||
validateResultRequestMock.mockReturnValue({ canSend: true })
|
||||
buildResultRequestDataMock.mockReturnValue({ inputs: { name: 'Alice' } })
|
||||
createWorkflowStreamHandlersMock.mockReturnValue({ onWorkflowFinished: vi.fn() })
|
||||
@ -273,6 +291,7 @@ describe('useResultSender', () => {
|
||||
})
|
||||
expect(buildResultRequestDataMock).not.toHaveBeenCalled()
|
||||
expect(sendCompletionMessageMock).not.toHaveBeenCalled()
|
||||
expect(mockTrackEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should send completion requests when controlSend changes and process callbacks', async () => {
|
||||
@ -307,6 +326,9 @@ describe('useResultSender', () => {
|
||||
expect(harness.runState.clearMoreLikeThis).toHaveBeenCalledTimes(1)
|
||||
expect(onShowRes).toHaveBeenCalledTimes(1)
|
||||
expect(onRunStart).toHaveBeenCalledTimes(1)
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('webapp_run', {
|
||||
app_mode: 'completion',
|
||||
})
|
||||
expect(sendCompletionMessageMock).toHaveBeenCalledWith(
|
||||
{ inputs: { name: 'Alice' } },
|
||||
expect.objectContaining({
|
||||
@ -346,6 +368,7 @@ describe('useResultSender', () => {
|
||||
|
||||
it('should trigger workflow sends on retry and report workflow request failures', async () => {
|
||||
const harness = createRunStateHarness()
|
||||
mockWebAppState.appInfo.mode = 'workflow'
|
||||
sendWorkflowMessageMock.mockRejectedValue(new Error('workflow failed'))
|
||||
|
||||
const { rerender, notify } = renderSender({
|
||||
@ -385,6 +408,9 @@ describe('useResultSender', () => {
|
||||
})
|
||||
})
|
||||
expect(harness.runState.clearMoreLikeThis).not.toHaveBeenCalled()
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith('webapp_run', {
|
||||
app_mode: 'workflow',
|
||||
})
|
||||
})
|
||||
|
||||
it('should configure workflow handlers for installed apps as non-public', async () => {
|
||||
@ -411,6 +437,7 @@ describe('useResultSender', () => {
|
||||
AppSourceTypeEnum.installedApp,
|
||||
'app-1',
|
||||
)
|
||||
expect(mockTrackEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should stringify non-Error workflow failures', async () => {
|
||||
|
||||
@ -4,7 +4,9 @@ import type { ResultRunStateController } from './use-result-run-state'
|
||||
import type { PromptConfig } from '@/models/debug'
|
||||
import type { VisionFile, VisionSettings } from '@/types/app'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { AppSourceType, sendCompletionMessage, sendWorkflowMessage } from '@/service/share'
|
||||
import { sleep } from '@/utils'
|
||||
import { buildResultRequestData, validateResultRequest } from '../result-request'
|
||||
@ -58,6 +60,7 @@ export const useResultSender = ({
|
||||
visionConfig,
|
||||
}: UseResultSenderOptions) => {
|
||||
const { clearMoreLikeThis } = runState
|
||||
const appMode = useWebAppStore((state) => state.appInfo?.mode)
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
if (runState.isResponding) {
|
||||
@ -96,6 +99,9 @@ export const useResultSender = ({
|
||||
|
||||
runState.setRespondingTrue()
|
||||
|
||||
if (appSourceType === AppSourceType.webApp && appMode)
|
||||
trackEvent('webapp_run', { app_mode: appMode })
|
||||
|
||||
let isEnd = false
|
||||
let isTimeout = false
|
||||
let completionChunks: string[] = []
|
||||
@ -196,6 +202,7 @@ export const useResultSender = ({
|
||||
return true
|
||||
}, [
|
||||
appId,
|
||||
appMode,
|
||||
appSourceType,
|
||||
completionFiles,
|
||||
inputs,
|
||||
|
||||
@ -68,6 +68,8 @@ const toastMock = vi.hoisted(() => ({
|
||||
success: vi.fn(),
|
||||
}))
|
||||
|
||||
const trackEventMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
const modelHooksState = vi.hoisted(() => ({
|
||||
defaultTextGenerationModel: {
|
||||
provider: {
|
||||
@ -194,6 +196,10 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: toastMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: trackEventMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
systemFeatures: {
|
||||
@ -1252,6 +1258,7 @@ describe('AgentConfigurePage', () => {
|
||||
'prompt:edited draft prompt',
|
||||
)
|
||||
expect(mocks.checkoutBuildDraft).not.toHaveBeenCalled()
|
||||
expect(trackEventMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should stay in Preview when resetting the Build conversation fails', async () => {
|
||||
@ -1374,6 +1381,7 @@ describe('AgentConfigurePage', () => {
|
||||
expect(screen.getByRole('region', { name: 'preview-chat' })).toHaveTextContent(
|
||||
'draftType:draft',
|
||||
)
|
||||
expect(trackEventMock).not.toHaveBeenCalled()
|
||||
expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent(
|
||||
'readonly:no',
|
||||
)
|
||||
@ -1904,7 +1912,7 @@ describe('AgentConfigurePage', () => {
|
||||
expect(screen.getByRole('region', { name: 'build-draft-bar' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not checkout again when sending build chat from active build draft mode', async () => {
|
||||
it('should track the run without checking out again in active build draft mode', async () => {
|
||||
const queryClient = new QueryClient()
|
||||
mocks.queryState.composer = {
|
||||
data: {
|
||||
@ -1955,6 +1963,7 @@ describe('AgentConfigurePage', () => {
|
||||
expect(screen.getByRole('region', { name: 'build-chat' })).toHaveTextContent('sent:yes')
|
||||
})
|
||||
expect(mocks.checkoutBuildDraft).not.toHaveBeenCalled()
|
||||
expect(trackEventMock).toHaveBeenCalledWith('agent_build_mode_run')
|
||||
})
|
||||
|
||||
it('should show the working directory action after the first build reply completes', async () => {
|
||||
|
||||
@ -18,6 +18,8 @@ const toastMock = vi.hoisted(() => ({
|
||||
success: vi.fn(),
|
||||
}))
|
||||
|
||||
const trackEventMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
const composerPutMutationFn = vi.hoisted(() =>
|
||||
vi.fn(
|
||||
async (variables: {
|
||||
@ -119,6 +121,10 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: toastMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: trackEventMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
agent: {
|
||||
@ -159,9 +165,11 @@ vi.mock('@/service/client', () => ({
|
||||
}))
|
||||
|
||||
function renderUseAgentConfigureSync({
|
||||
agentName = 'Agent',
|
||||
baseConfig,
|
||||
currentModel,
|
||||
}: {
|
||||
agentName?: Parameters<typeof useAgentConfigureSync>[0]['agentName']
|
||||
baseConfig?: Parameters<typeof useAgentConfigureSync>[0]['baseConfig']
|
||||
currentModel?: Parameters<typeof useAgentConfigureSync>[0]['currentModel']
|
||||
} = {}) {
|
||||
@ -183,6 +191,7 @@ function renderUseAgentConfigureSync({
|
||||
() =>
|
||||
useAgentConfigureSync({
|
||||
agentId: 'agent-1',
|
||||
agentName,
|
||||
baseConfig,
|
||||
currentModel,
|
||||
enabled: true,
|
||||
@ -762,6 +771,12 @@ describe('useAgentConfigureSync', () => {
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
})
|
||||
expect(trackEventMock).toHaveBeenCalledWith('app_published_time', {
|
||||
action_mode: 'app',
|
||||
app_id: 'agent-1',
|
||||
app_name: 'Agent',
|
||||
app_mode: 'agent-v2',
|
||||
})
|
||||
expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess')
|
||||
})
|
||||
|
||||
@ -781,6 +796,7 @@ describe('useAgentConfigureSync', () => {
|
||||
|
||||
expect(composerPutMutationFn).not.toHaveBeenCalled()
|
||||
expect(publishAgentMutationFn).not.toHaveBeenCalled()
|
||||
expect(trackEventMock).not.toHaveBeenCalled()
|
||||
expect(toastMock.error).toHaveBeenCalledWith('common.modelProvider.selectModel')
|
||||
})
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { ScopeProvider } from 'jotai-scope'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { agentSoulConfigToFormState } from '@/features/agent-v2/agent-composer/conversions'
|
||||
import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
|
||||
@ -328,6 +329,7 @@ function AgentConfigurePageComposerContent({
|
||||
useAgentConfigureModelOptions()
|
||||
const { draftSavedAt, isPublishing, publishDraft, saveDraft } = useAgentConfigureSync({
|
||||
agentId,
|
||||
agentName: agentQuery.data?.name,
|
||||
baseConfig: agentSoulConfig,
|
||||
currentModel,
|
||||
enabled: composerQuery.isSuccess && !selectedVersionId && !buildDraft.isActive,
|
||||
@ -564,11 +566,13 @@ function AgentConfigurePageComposerContent({
|
||||
throw new Error('Agent model is required.')
|
||||
}
|
||||
|
||||
return runBuildPreparation({
|
||||
const preparedBuildDraft = await runBuildPreparation({
|
||||
generation: buildCallbackGeneration,
|
||||
markBuildChatStarted: true,
|
||||
prepare: buildDraftActions.prepareBuildDraftBeforeRun,
|
||||
})
|
||||
trackEvent('agent_build_mode_run')
|
||||
return preparedBuildDraft
|
||||
}
|
||||
: saveDraft
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import isEqual from 'fast-deep-equal'
|
||||
import { useSetAtom, useStore } from 'jotai'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-serial-async-callback'
|
||||
import { formStateToAgentSoulConfig } from '@/features/agent-v2/agent-composer/conversions'
|
||||
import {
|
||||
@ -29,11 +30,13 @@ const DRAFT_AUTOSAVE_WAIT = 5000
|
||||
|
||||
export function useAgentConfigureSync({
|
||||
agentId,
|
||||
agentName,
|
||||
baseConfig,
|
||||
currentModel,
|
||||
enabled,
|
||||
}: {
|
||||
agentId: string
|
||||
agentName?: string | null
|
||||
baseConfig?: AgentSoulConfig
|
||||
currentModel?: DefaultModel
|
||||
enabled: boolean
|
||||
@ -303,6 +306,12 @@ export function useAgentConfigureSync({
|
||||
const publishedDraft = draft
|
||||
setOriginalDraft(publishedDraft)
|
||||
setPublishedDraft(publishedDraft)
|
||||
trackEvent('app_published_time', {
|
||||
action_mode: 'app',
|
||||
app_id: agentId,
|
||||
app_name: agentName,
|
||||
app_mode: 'agent-v2',
|
||||
})
|
||||
toast.success(tCommon(($) => $['api.actionSuccess']))
|
||||
} finally {
|
||||
publishInFlightRef.current = false
|
||||
@ -310,6 +319,7 @@ export function useAgentConfigureSync({
|
||||
}
|
||||
}, [
|
||||
agentId,
|
||||
agentName,
|
||||
debouncedSaveDraft,
|
||||
getKnowledgeValidationMessage,
|
||||
publishAgent,
|
||||
|
||||
@ -14,6 +14,8 @@ const toastMock = vi.hoisted(() => ({
|
||||
|
||||
const routerPushMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
const trackCreateAppMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@tanstack/react-query', () => ({
|
||||
useMutation: () => ({
|
||||
isPending: mutationMock.isPending,
|
||||
@ -31,6 +33,10 @@ vi.mock('@/next/navigation', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/create-app-tracking', () => ({
|
||||
trackCreateApp: trackCreateAppMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
agent: {
|
||||
@ -106,6 +112,10 @@ describe('CreateAgentDialog', () => {
|
||||
})
|
||||
|
||||
expect(toastMock.success).toHaveBeenCalledWith('agentV2.roster.createSuccess')
|
||||
expect(trackCreateAppMock).toHaveBeenCalledWith({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
})
|
||||
expect(routerPushMock).toHaveBeenCalledWith('/agents/agent-1/configure')
|
||||
})
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { trackCreateApp } from '@/utils/create-app-tracking'
|
||||
import { getAgentDetailPath } from '../../agent-detail/routes'
|
||||
import { defaultAgentIcon } from './agent-form'
|
||||
import { AgentFormFields } from './agent-form-fields'
|
||||
@ -76,6 +77,10 @@ export function CreateAgentDialog({ open, onOpenChange }: CreateAgentDialogProps
|
||||
},
|
||||
{
|
||||
onSuccess: (createdAgent) => {
|
||||
trackCreateApp({
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
})
|
||||
toast.success(t(($) => $['roster.createSuccess']))
|
||||
handleOpenChange(false)
|
||||
router.push(getAgentDetailPath(createdAgent.id, 'configure'))
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { AppMode } from '@dify/contracts/api/web/types.gen'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
|
||||
@ -36,6 +37,7 @@ export type AppMeta = {
|
||||
export type CustomConfigValueType = string | number | boolean | null | undefined
|
||||
export type AppData = {
|
||||
app_id: string
|
||||
mode?: AppMode
|
||||
can_replace_logo?: boolean
|
||||
custom_config: Record<string, CustomConfigValueType> | null
|
||||
enable_site?: boolean
|
||||
|
||||
@ -141,6 +141,23 @@ describe('create-app-tracking', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should preserve agent v2 mode as its own app mode', () => {
|
||||
expect(
|
||||
buildCreateAppEventPayload(
|
||||
{
|
||||
source: 'studio_blank',
|
||||
appMode: 'agent-v2',
|
||||
},
|
||||
null,
|
||||
new Date(2026, 3, 13, 9, 8, 9),
|
||||
),
|
||||
).toEqual({
|
||||
source: 'studio_blank',
|
||||
app_mode: 'agent-v2',
|
||||
time: '04-13-09:08:09',
|
||||
})
|
||||
})
|
||||
|
||||
it('should fold legacy non-agent modes into chatflow', () => {
|
||||
expect(
|
||||
buildCreateAppEventPayload(
|
||||
|
||||
@ -18,7 +18,7 @@ type SearchParamReader = {
|
||||
get: (name: string) => string | null
|
||||
}
|
||||
|
||||
type OriginalCreateAppMode = 'workflow' | 'chatflow' | 'agent'
|
||||
type OriginalCreateAppMode = 'workflow' | 'chatflow' | 'agent' | 'agent-v2'
|
||||
|
||||
type CreateAppSource =
|
||||
| 'external'
|
||||
@ -78,6 +78,8 @@ const formatCreateAppTime = (date: Date) => {
|
||||
const mapOriginalCreateAppMode = (appMode: string): OriginalCreateAppMode => {
|
||||
if (appMode === AppModeEnum.WORKFLOW) return 'workflow'
|
||||
|
||||
if (appMode === 'agent-v2') return 'agent-v2'
|
||||
|
||||
if (appMode === AppModeEnum.AGENT_CHAT || appMode === 'agent') return 'agent'
|
||||
|
||||
return 'chatflow'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user