-
-
- {buildIconGridCells.map((cell) => (
- 0 ? 'rounded-[1px] bg-[#98A2B2]' : 'invisible'}
- style={{ opacity: cell.opacity }}
- />
- ))}
-
-
-
-
-
- {t(($) => $['agentDetail.configure.build.empty.title'])}
-
-
-
-
-
- }
+ <>
+
+
+ {buildIconGridCells.map((cell) => (
+
0 ? 'rounded-[1px] bg-[#98A2B2]' : 'invisible'}
+ style={{ opacity: cell.opacity }}
/>
-
- {communityEditionBuildModeTip}
-
-
+ ))}
-
- {t(($) => $['agentDetail.configure.build.empty.description'])}
-
- {inputNode}
+
-
+
+
+ {t(($) => $['agentDetail.configure.build.empty.title'])}
+
+
+
+
+
+ }
+ />
+
+ {communityEditionBuildModeTip}
+
+
+
+
+ {t(($) => $['agentDetail.configure.build.empty.description'])}
+
+ >
)
}
@@ -98,9 +95,7 @@ export function AgentBuildChat(props: AgentBuildChatProps) {
inputPlaceholder={t(($) => $['agentDetail.configure.build.inputPlaceholder'])}
inputAutoFocus={false}
sendButtonLabel={t(($) => $['agentDetail.configure.build.startBuild'])}
- renderEmptyState={(emptyStateProps: AgentChatRuntimeEmptyStateProps) => (
-
- )}
+ renderEmptyState={() =>
}
/>
)
}
diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat-config.ts b/web/features/agent-v2/agent-detail/configure/components/preview/chat-config.ts
new file mode 100644
index 00000000000..766493e9c1d
--- /dev/null
+++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat-config.ts
@@ -0,0 +1,263 @@
+import type {
+ AgentCliToolConfig,
+ AgentSoulConfig,
+ AgentSoulDifyToolConfig,
+} from '@dify/contracts/api/console/agent/types.gen'
+import type { InputForm } from '@/app/components/base/chat/chat/type'
+import type { ChatConfig } from '@/app/components/base/chat/types'
+import type { FileUpload } from '@/app/components/base/features/types'
+import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
+import type { Inputs } from '@/models/debug'
+import { InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types'
+import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
+import { ENABLE_AGENT_CLI_TOOLS } from '@/features/agent-v2/agent-detail/configure/feature-flags'
+import { PromptMode } from '@/models/debug'
+import { AgentStrategy, ModelModeType, RETRIEVE_TYPE, TransferMethod } from '@/types/app'
+
+export type AgentPreviewChatConfig = ChatConfig & {
+ model: {
+ provider: string
+ name: string
+ mode: ModelModeType
+ completion_params: {
+ max_tokens: number
+ temperature: number
+ top_p: number
+ echo: boolean
+ stop: string[]
+ presence_penalty: number
+ frequency_penalty: number
+ }
+ }
+}
+
+const defaultSystemParameters: ChatConfig['system_parameters'] = {
+ audio_file_size_limit: 0,
+ file_size_limit: 0,
+ image_file_size_limit: 0,
+ video_file_size_limit: 0,
+ workflow_file_upload_limit: 0,
+}
+
+const disabledFileUploadConfig = {
+ enabled: false,
+ allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
+ allowed_file_types: [],
+ fileUploadConfig: defaultSystemParameters,
+ image: {
+ enabled: false,
+ transfer_methods: [],
+ number_limits: 0,
+ image_file_size_limit: 0,
+ detail: 'low',
+ },
+ max_length: 0,
+ number_limits: 0,
+} as ChatConfig['file_upload'] & {
+ fileUploadConfig: ChatConfig['system_parameters']
+}
+
+const defaultFileUploadMethods = [TransferMethod.local_file, TransferMethod.remote_url]
+
+const toPreviewFileUploadConfig = (fileUpload: FileUpload | undefined) => {
+ if (!fileUpload?.enabled) return disabledFileUploadConfig
+
+ const allowedFileUploadMethods = fileUpload.allowed_file_upload_methods?.length
+ ? fileUpload.allowed_file_upload_methods
+ : defaultFileUploadMethods
+ const numberLimits = fileUpload.number_limits ?? fileUpload.image?.number_limits ?? 3
+
+ return {
+ ...disabledFileUploadConfig,
+ ...fileUpload,
+ enabled: true,
+ allowed_file_types: fileUpload.allowed_file_types?.length
+ ? fileUpload.allowed_file_types
+ : [SupportUploadFileTypes.image],
+ allowed_file_upload_methods: allowedFileUploadMethods,
+ number_limits: numberLimits,
+ fileUploadConfig: fileUpload.fileUploadConfig ?? defaultSystemParameters,
+ image: {
+ ...disabledFileUploadConfig.image,
+ ...fileUpload.image,
+ enabled: fileUpload.image?.enabled ?? true,
+ transfer_methods: fileUpload.image?.transfer_methods?.length
+ ? fileUpload.image.transfer_methods
+ : allowedFileUploadMethods,
+ number_limits: numberLimits,
+ },
+ } as ChatConfig['file_upload']
+}
+
+const getModelSettings = (agentSoulConfig?: AgentSoulConfig) =>
+ agentSoulConfig?.model?.model_settings ?? {}
+
+const toEnabledConfig = (config?: { enabled?: boolean } | null) => ({
+ ...config,
+ enabled: config?.enabled ?? false,
+})
+
+const toInputType = (type: string): InputVarType => {
+ if (type === InputVarType.paragraph) return InputVarType.paragraph
+ if (type === InputVarType.select) return InputVarType.select
+ if (type === InputVarType.number) return InputVarType.number
+ if (type === InputVarType.json || type === InputVarType.jsonObject) return InputVarType.json
+
+ return InputVarType.textInput
+}
+
+const toInputForm = (variable: NonNullable
[number]) => {
+ const variableKey = String(variable.name)
+
+ return {
+ ...variable,
+ key: variableKey,
+ label: variableKey,
+ variable: variableKey,
+ type: toInputType(variable.type),
+ required: variable.required ?? true,
+ hide: false,
+ }
+}
+
+export const getAgentSoulInputsForm = (agentSoulConfig?: AgentSoulConfig) =>
+ (agentSoulConfig?.app_variables ?? []).map(toInputForm)
+
+export const getAgentSoulInputs = (inputsForm: InputForm[]) => {
+ return inputsForm.reduce((acc, input) => {
+ acc[input.variable] = (input.default ?? '') as Inputs[string]
+ return acc
+ }, {})
+}
+
+const toAgentTool = (tool: AgentSoulDifyToolConfig) => ({
+ provider_id: tool.provider_id ?? tool.provider ?? tool.plugin_id ?? '',
+ provider_type: tool.provider_type ?? 'builtin',
+ provider_name: tool.provider ?? '',
+ tool_name: tool.tool_name,
+ tool_label: tool.name ?? tool.tool_name,
+ tool_parameters: tool.runtime_parameters ?? {},
+ enabled: tool.enabled ?? true,
+ credential_id: tool.credential_ref?.id,
+})
+
+const toCliTool = (tool: AgentCliToolConfig) => ({
+ provider_id: 'agent-v2-cli',
+ provider_type: 'builtin',
+ provider_name: 'CLI',
+ tool_name: tool.tool_name ?? tool.name ?? '',
+ tool_label: tool.name ?? tool.tool_name ?? '',
+ tool_parameters: {
+ command: tool.command,
+ },
+ enabled: tool.enabled ?? true,
+})
+
+const toLegacyPreviewDatasetConfigs = (
+ knowledge?: AgentSoulConfig['knowledge'],
+): NonNullable => {
+ // Temporary preview adapter: composer state is knowledge.sets, but this
+ // legacy chat preview contract still accepts one flat dataset_configs block.
+ // Preview currently flattens the first configured set only.
+ const previewKnowledgeSet = knowledge?.sets?.[0]
+ const datasets = previewKnowledgeSet?.datasets ?? []
+ const retrieval = previewKnowledgeSet?.retrieval
+
+ return {
+ retrieval_model: retrieval?.mode === 'single' ? RETRIEVE_TYPE.oneWay : RETRIEVE_TYPE.multiWay,
+ reranking_model: {
+ reranking_provider_name: retrieval?.reranking_model?.provider ?? '',
+ reranking_model_name: retrieval?.reranking_model?.model ?? '',
+ },
+ top_k: retrieval?.top_k ?? 4,
+ score_threshold_enabled:
+ retrieval?.score_threshold !== undefined && retrieval?.score_threshold !== null,
+ score_threshold: retrieval?.score_threshold ?? 0.8,
+ datasets: {
+ datasets: datasets
+ .map((dataset) => ({
+ enabled: true,
+ id: dataset.id ?? '',
+ }))
+ .filter((dataset) => dataset.id),
+ },
+ }
+}
+
+export const buildChatConfig = ({
+ agentSoulConfig,
+ currentModel,
+ prompt,
+}: {
+ agentSoulConfig?: AgentSoulConfig
+ currentModel?: AgentComposerModel
+ prompt: string
+}): AgentPreviewChatConfig => {
+ const modelSettings = currentModel?.model_settings ?? getModelSettings(agentSoulConfig)
+ const appFeatures = agentSoulConfig?.app_features ?? {}
+ const difyTools = agentSoulConfig?.tools?.dify_tools ?? []
+ const cliTools = ENABLE_AGENT_CLI_TOOLS ? (agentSoulConfig?.tools?.cli_tools ?? []) : []
+
+ return {
+ pre_prompt: prompt || agentSoulConfig?.prompt?.system_prompt || '',
+ prompt_type: PromptMode.simple,
+ chat_prompt_config: DEFAULT_CHAT_PROMPT_CONFIG,
+ completion_prompt_config: DEFAULT_COMPLETION_PROMPT_CONFIG,
+ user_input_form: (agentSoulConfig?.app_variables ?? []).map((variable) => ({
+ 'text-input': {
+ default: String(variable.default ?? ''),
+ label: variable.name,
+ variable: variable.name,
+ required: variable.required ?? true,
+ max_length: 48,
+ hide: false,
+ },
+ })),
+ dataset_query_variable: '',
+ opening_statement: appFeatures.opening_statement ?? '',
+ suggested_questions: appFeatures.suggested_questions ?? [],
+ suggested_questions_after_answer: toEnabledConfig(
+ appFeatures.suggested_questions_after_answer,
+ ) as ChatConfig['suggested_questions_after_answer'],
+ more_like_this: { enabled: false },
+ text_to_speech: toEnabledConfig(appFeatures.text_to_speech) as ChatConfig['text_to_speech'],
+ speech_to_text: toEnabledConfig(appFeatures.speech_to_text),
+ retriever_resource: toEnabledConfig(appFeatures.retriever_resource),
+ sensitive_word_avoidance: toEnabledConfig(appFeatures.sensitive_word_avoidance),
+ annotation_reply: {
+ id: '',
+ enabled: false,
+ score_threshold: 0.9,
+ embedding_model: {
+ embedding_provider_name: '',
+ embedding_model_name: '',
+ },
+ },
+ agent_mode: {
+ enabled: difyTools.length > 0 || cliTools.length > 0,
+ strategy: AgentStrategy.functionCall,
+ tools: [
+ ...difyTools.map(toAgentTool),
+ ...cliTools.map(toCliTool),
+ ] as ChatConfig['agent_mode']['tools'],
+ },
+ model: {
+ provider: currentModel?.provider ?? agentSoulConfig?.model?.model_provider ?? '',
+ name: currentModel?.model ?? agentSoulConfig?.model?.model ?? '',
+ mode: ModelModeType.chat,
+ completion_params: {
+ temperature: modelSettings.temperature ?? 0.7,
+ top_p: modelSettings.top_p ?? 1,
+ max_tokens: modelSettings.max_tokens ?? 512,
+ presence_penalty: modelSettings.presence_penalty ?? 0,
+ frequency_penalty: modelSettings.frequency_penalty ?? 0,
+ stop: modelSettings.stop ?? [],
+ echo: false,
+ },
+ },
+ dataset_configs: toLegacyPreviewDatasetConfigs(agentSoulConfig?.knowledge),
+ file_upload: toPreviewFileUploadConfig(appFeatures.file_upload as FileUpload | undefined),
+ system_parameters: defaultSystemParameters,
+ supportCitationHitInfo: true,
+ }
+}
diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat-conversation.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/chat-conversation.tsx
new file mode 100644
index 00000000000..50a1a46189a
--- /dev/null
+++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat-conversation.tsx
@@ -0,0 +1,309 @@
+'use client'
+
+import type { AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen'
+import type { Ref } from 'react'
+import type { AgentPreviewChatConfig } from './chat-config'
+import type { InputForm } from '@/app/components/base/chat/chat/type'
+import type { ChatItem, ChatItemInTree, OnSend } from '@/app/components/base/chat/types'
+import type { FileEntity } from '@/app/components/base/file-uploader/types'
+import type { SpeechToTextTarget } from '@/app/components/base/voice-input/types'
+import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
+import type { Inputs } from '@/models/debug'
+import { Avatar } from '@langgenius/dify-ui/avatar'
+import { cn } from '@langgenius/dify-ui/cn'
+import { useQueryClient } from '@tanstack/react-query'
+import { useAtomValue } from 'jotai'
+import { useCallback, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react'
+import { AgentRosterResponseContent } from '@/app/components/base/chat/chat/answer/agent-roster-response-content'
+import { useChat } from '@/app/components/base/chat/chat/hooks'
+import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
+import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
+import { userProfileAtom } from '@/context/account-state'
+import dynamic from '@/next/dynamic'
+import { consoleClient, consoleQuery } from '@/service/client'
+import { buildChatConfig, getAgentSoulInputs, getAgentSoulInputsForm } from './chat-config'
+
+const Chat = dynamic(() => import('@/app/components/base/chat/chat'), { ssr: false })
+
+const stopAgentChatMessageResponding = (agentId: string, taskId: string) => {
+ return consoleClient.agent.byAgentId.chatMessages.byTaskId.stop.post({
+ params: {
+ agent_id: agentId,
+ task_id: taskId,
+ },
+ })
+}
+
+const fetchAgentSuggestedQuestions = (agentId: string, messageId: string) => {
+ return consoleClient.agent.byAgentId.chatMessages.byMessageId.suggestedQuestions.get({
+ params: {
+ agent_id: agentId,
+ message_id: messageId,
+ },
+ })
+}
+
+export type AgentPreviewChatRuntimeState = {
+ isEmptyChat: boolean
+ isResponding: boolean
+ isSendPending: boolean
+}
+
+export type AgentPreviewChatController = {
+ send: OnSend
+}
+
+export function AgentPreviewChatConversation({
+ ref,
+ agentId,
+ agentSoulConfig,
+ clearChatList,
+ config,
+ conversationId,
+ currentModel,
+ draftType,
+ initialChatTree,
+ inputs,
+ inputsForm,
+ sendButtonLabel,
+ speechToTextTarget,
+ onBeforeSpeechToText,
+ onClearChatListChange,
+ onConversationComplete,
+ onConversationIdChange,
+ onCurrentSessionConversationIdChange,
+ onRuntimeStateChange,
+ onSaveDraftBeforeRun,
+ onSendInterrupted,
+}: {
+ ref: Ref
+ agentId: string
+ agentSoulConfig?: AgentSoulConfig
+ clearChatList: boolean
+ config: AgentPreviewChatConfig
+ conversationId?: string | null
+ currentModel?: AgentComposerModel
+ draftType?: 'debug_build'
+ initialChatTree: ChatItemInTree[]
+ inputs: Inputs
+ inputsForm: InputForm[]
+ sendButtonLabel?: string
+ speechToTextTarget: SpeechToTextTarget
+ onBeforeSpeechToText?: () => Promise
+ onClearChatListChange: (clearChatList: boolean) => void
+ onConversationComplete?: (conversationId: string, workflowRunId?: string) => void
+ onConversationIdChange?: (conversationId: string) => void
+ onCurrentSessionConversationIdChange: (conversationId: string) => void
+ onRuntimeStateChange: (state: AgentPreviewChatRuntimeState) => void
+ onSaveDraftBeforeRun?: () => Promise
+ onSendInterrupted?: () => void
+}) {
+ const queryClient = useQueryClient()
+ const userProfile = useAtomValue(userProfileAtom)
+ const sendInterruptedRef = useRef(false)
+ const [isSendPending, setIsSendPending] = useState(false)
+ const notifySendInterrupted = useCallback(() => {
+ if (sendInterruptedRef.current) return
+
+ sendInterruptedRef.current = true
+ onSendInterrupted?.()
+ }, [onSendInterrupted])
+ const { textGenerationModelList } =
+ useTextGenerationCurrentProviderAndModelAndModelList(currentModel)
+ const {
+ chatList,
+ setTargetMessageId,
+ isResponding,
+ handleSend,
+ suggestedQuestions,
+ handleStop,
+ handleAnnotationAdded,
+ handleAnnotationEdited,
+ handleAnnotationRemoved,
+ } = useChat(
+ config,
+ {
+ inputs,
+ inputsForm,
+ },
+ initialChatTree,
+ (taskId) => {
+ void stopAgentChatMessageResponding(agentId, taskId)
+ },
+ clearChatList,
+ onClearChatListChange,
+ conversationId ?? undefined,
+ { isNewAgent: true },
+ )
+
+ const doSend: OnSend = useCallback(
+ async (message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => {
+ sendInterruptedRef.current = false
+ setIsSendPending(true)
+ let sendStarted = false
+
+ try {
+ const preparedAgentSoulConfig = await onSaveDraftBeforeRun?.()
+ const runtimeAgentSoulConfig = preparedAgentSoulConfig || agentSoulConfig
+ const runtimeInputsForm = preparedAgentSoulConfig
+ ? getAgentSoulInputsForm(runtimeAgentSoulConfig)
+ : inputsForm
+ const runtimeInputs = preparedAgentSoulConfig
+ ? getAgentSoulInputs(runtimeInputsForm)
+ : inputs
+ const runtimeConfig = preparedAgentSoulConfig
+ ? buildChatConfig({
+ agentSoulConfig: runtimeAgentSoulConfig,
+ currentModel: undefined,
+ prompt: runtimeAgentSoulConfig?.prompt?.system_prompt ?? '',
+ })
+ : config
+
+ const currentProvider = textGenerationModelList.find(
+ (item) => item.provider === runtimeConfig.model.provider,
+ )
+ const selectedModel = currentProvider?.models.find(
+ (model) => model.model === runtimeConfig.model.name,
+ )
+ const supportVision = selectedModel?.features?.includes(ModelFeatureEnum.vision)
+ const data: Record = {
+ query: message,
+ inputs: runtimeInputs,
+ overrideInputsForm: runtimeInputsForm,
+ parent_message_id:
+ (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null,
+ }
+ if (draftType) data.draft_type = draftType
+
+ if (files?.length && supportVision) data.files = files
+
+ handleSend(`agent/${agentId}/chat-messages`, data as Parameters[1], {
+ onGetConversationMessages: async (conversationId) => {
+ return queryClient.fetchQuery({
+ ...consoleQuery.agent.byAgentId.chatMessages.get.queryOptions({
+ input: {
+ params: {
+ agent_id: agentId,
+ },
+ query: {
+ conversation_id: conversationId,
+ },
+ },
+ }),
+ staleTime: 0,
+ })
+ },
+ onGetSuggestedQuestions: (responseItemId) =>
+ fetchAgentSuggestedQuestions(agentId, responseItemId),
+ onUnhandledEvent: (event) => {
+ if (event.event !== 'error' || typeof event.message !== 'string') return
+
+ return {
+ conversationId:
+ typeof event.conversation_id === 'string' ? event.conversation_id : undefined,
+ messageId: typeof event.message_id === 'string' ? event.message_id : undefined,
+ errorMessage: event.message,
+ errorCode: typeof event.code === 'string' ? event.code : undefined,
+ }
+ },
+ onConversationComplete: (completedConversationId, workflowRunId) => {
+ if (completedConversationId && completedConversationId !== conversationId)
+ onCurrentSessionConversationIdChange(completedConversationId)
+ onConversationIdChange?.(completedConversationId)
+ onConversationComplete?.(completedConversationId, workflowRunId)
+ },
+ onSendSettled: (hasError) => {
+ setIsSendPending(false)
+ if (hasError) notifySendInterrupted()
+ },
+ })
+ sendStarted = true
+ } catch {
+ return false
+ } finally {
+ if (!sendStarted) setIsSendPending(false)
+ }
+ },
+ [
+ agentId,
+ agentSoulConfig,
+ chatList,
+ config,
+ conversationId,
+ draftType,
+ handleSend,
+ inputs,
+ inputsForm,
+ notifySendInterrupted,
+ onConversationComplete,
+ onConversationIdChange,
+ onCurrentSessionConversationIdChange,
+ onSaveDraftBeforeRun,
+ queryClient,
+ textGenerationModelList,
+ ],
+ )
+
+ const doStopResponding = useCallback(() => {
+ handleStop()
+ notifySendInterrupted()
+ }, [handleStop, notifySendInterrupted])
+
+ const doRegenerate = useCallback(
+ (chatItem: ChatItem, editedQuestion?: { message: string; files?: FileEntity[] }) => {
+ const question = editedQuestion
+ ? chatItem
+ : chatList.find((item) => item.id === chatItem.parentMessageId)
+ if (!question) return
+
+ const parentAnswer = chatList.find((item) => item.id === question.parentMessageId)
+ doSend(
+ editedQuestion ? editedQuestion.message : question.content,
+ editedQuestion ? editedQuestion.files : question.message_files,
+ true,
+ isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
+ )
+ },
+ [chatList, doSend],
+ )
+ const isEmptyChat = chatList.length === 0
+ const sendButtonLoading = isEmptyChat && !!sendButtonLabel && (isSendPending || isResponding)
+ useImperativeHandle(ref, () => ({ send: doSend }), [doSend])
+ useLayoutEffect(() => {
+ onRuntimeStateChange({
+ isEmptyChat,
+ isResponding,
+ isSendPending,
+ })
+ }, [isEmptyChat, isResponding, isSendPending, onRuntimeStateChange])
+
+ return (
+ setTargetMessageId(siblingMessageId)}
+ onStopResponding={doStopResponding}
+ noChatInput
+ showRegenerate
+ questionIcon={}
+ onAnnotationEdited={handleAnnotationEdited}
+ onAnnotationAdded={handleAnnotationAdded}
+ onAnnotationRemoved={handleAnnotationRemoved}
+ renderAgentContent={AgentRosterResponseContent}
+ noSpacing
+ />
+ )
+}
diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat-history.ts b/web/features/agent-v2/agent-detail/configure/components/preview/chat-history.ts
new file mode 100644
index 00000000000..66e5d7532f0
--- /dev/null
+++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat-history.ts
@@ -0,0 +1,161 @@
+import type {
+ AgentThought,
+ MessageDetailResponse,
+} from '@dify/contracts/api/console/agent/types.gen'
+import type { FeedbackType, IChatItem, ThoughtItem } from '@/app/components/base/chat/chat/type'
+import type { ChatItemInTree } from '@/app/components/base/chat/types'
+import type { FileEntity } from '@/app/components/base/file-uploader/types'
+import type { MessageRating } from '@/models/log'
+import type { TransferMethod } from '@/types/app'
+import type { FileResponse } from '@/types/workflow'
+import { buildChatItemTree } from '@/app/components/base/chat/utils'
+import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
+import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
+
+const toFileResponse = (
+ file: NonNullable[number],
+): FileResponse => ({
+ related_id: file.id ?? file.upload_file_id,
+ extension: '',
+ filename: file.filename,
+ size: file.size ?? 0,
+ mime_type: file.mime_type ?? '',
+ transfer_method: file.transfer_method as TransferMethod,
+ type: file.type,
+ url: file.url ?? '',
+ upload_file_id: file.upload_file_id ?? '',
+ remote_url: file.url ?? '',
+})
+
+const toLogMessages = (
+ message: MessageDetailResponse['message'],
+ answer: string,
+ files: MessageDetailResponse['message_files'],
+) => {
+ if (!Array.isArray(message)) return []
+
+ const logMessages = message as IChatItem['log']
+ if (logMessages?.at(-1)?.role === 'assistant') return logMessages
+
+ return [
+ ...(logMessages ?? []),
+ {
+ role: 'assistant',
+ text: answer,
+ files: getProcessedFilesFromResponse(
+ (files?.filter((file) => file.belongs_to === 'assistant') || []).map(toFileResponse),
+ ),
+ },
+ ]
+}
+
+function toToolLabels(value: AgentThought['tool_labels']): ThoughtItem['tool_labels'] {
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
+
+ const toolLabels: NonNullable = {}
+ for (const [name, label] of Object.entries(value)) {
+ if (!label || typeof label !== 'object' || Array.isArray(label)) continue
+
+ const enUS = 'en_US' in label ? label.en_US : undefined
+ const zhHans = 'zh_Hans' in label ? label.zh_Hans : undefined
+ if (typeof enUS !== 'string' || typeof zhHans !== 'string') continue
+
+ toolLabels[name] = { en_US: enUS, zh_Hans: zhHans }
+ for (const [locale, localizedLabel] of Object.entries(label)) {
+ if (typeof localizedLabel === 'string') toolLabels[name][locale] = localizedLabel
+ }
+ }
+
+ return Object.keys(toolLabels).length ? toolLabels : undefined
+}
+
+const toAgentThoughtItem = (thought: AgentThought, conversationId: string): ThoughtItem => ({
+ id: thought.id,
+ tool: thought.tool ?? '',
+ thought: thought.thought ?? '',
+ answer: thought.answer ?? '',
+ tool_input: thought.tool_input ?? '',
+ tool_labels: toToolLabels(thought.tool_labels),
+ message_id: thought.message_id,
+ conversation_id: conversationId,
+ observation: thought.observation ?? '',
+ position: thought.position,
+ files: thought.files,
+})
+
+const toFeedback = (
+ feedback: NonNullable[number] | undefined,
+): FeedbackType | undefined => {
+ if (!feedback) return undefined
+
+ const rating = feedback.rating as MessageRating
+ if (rating !== 'like' && rating !== 'dislike' && rating !== null) return undefined
+
+ return {
+ rating,
+ content: feedback.content,
+ }
+}
+
+export function getLastWorkflowRunId(messages: MessageDetailResponse[]) {
+ for (let index = messages.length - 1; index >= 0; index--) {
+ const workflowRunId = messages[index]?.workflow_run_id
+ if (workflowRunId) return workflowRunId
+ }
+
+ return null
+}
+
+export function getFormattedAgentDebugChatTree(
+ messages: MessageDetailResponse[],
+): ChatItemInTree[] {
+ const chatList: IChatItem[] = []
+
+ messages.forEach((item) => {
+ const answer = item.answer ?? ''
+ const questionFiles = item.message_files?.filter((file) => file.belongs_to === 'user') || []
+ const answerFiles = item.message_files?.filter((file) => file.belongs_to === 'assistant') || []
+ const answerTokens = item.answer_tokens ?? 0
+ const messageTokens = item.message_tokens ?? 0
+ const latency = item.provider_response_latency ?? 0
+
+ chatList.push({
+ id: `question-${item.id}`,
+ content: item.query,
+ isAnswer: false,
+ message_files: getProcessedFilesFromResponse(questionFiles.map(toFileResponse)),
+ parentMessageId: item.parent_message_id || undefined,
+ })
+ chatList.push({
+ id: item.id,
+ content: answer,
+ agent_thoughts: addFileInfos(
+ sortAgentSorts(
+ (item.agent_thoughts ?? []).map((thought) =>
+ toAgentThoughtItem(thought, item.conversation_id),
+ ),
+ ),
+ item.message_files as unknown as FileEntity[],
+ ),
+ feedback: toFeedback(item.feedbacks?.find((feedback) => feedback.from_source === 'user')),
+ isAnswer: true,
+ log: toLogMessages(item.message, answer, item.message_files),
+ message_files: getProcessedFilesFromResponse(answerFiles.map(toFileResponse)),
+ parentMessageId: `question-${item.id}`,
+ workflow_run_id: item.workflow_run_id ?? undefined,
+ conversationId: item.conversation_id,
+ input: {
+ inputs: item.inputs,
+ query: item.query,
+ },
+ more: {
+ time: '',
+ tokens: answerTokens + messageTokens,
+ latency: latency.toFixed(2),
+ tokens_per_second: latency > 0 ? (answerTokens / latency).toFixed(2) : undefined,
+ },
+ })
+ })
+
+ return buildChatItemTree(chatList)
+}
diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx
index 47be15ebef8..8b504f56db3 100644
--- a/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx
@@ -1,475 +1,13 @@
'use client'
-import type {
- AgentCliToolConfig,
- AgentIconType,
- AgentSoulConfig,
- AgentSoulDifyToolConfig,
- AgentThought,
- MessageDetailResponse,
-} from '@dify/contracts/api/console/agent/types.gen'
+import type { AgentIconType, AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen'
import type { ReactNode } from 'react'
-import type {
- FeedbackType,
- IChatItem,
- InputForm,
- ThoughtItem,
-} from '@/app/components/base/chat/chat/type'
-import type { ChatConfig, ChatItem, ChatItemInTree, OnSend } from '@/app/components/base/chat/types'
-import type { FileUpload } from '@/app/components/base/features/types'
-import type { FileEntity } from '@/app/components/base/file-uploader/types'
-import type { SpeechToTextTarget } from '@/app/components/base/voice-input/types'
-import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
-import type { Inputs } from '@/models/debug'
-import type { MessageRating } from '@/models/log'
-import type { FileResponse } from '@/types/workflow'
-import { Avatar } from '@langgenius/dify-ui/avatar'
-import { cn } from '@langgenius/dify-ui/cn'
-import { skipToken, useQuery, useQueryClient } from '@tanstack/react-query'
-import { useAtomValue } from 'jotai'
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { AgentRosterResponseContent } from '@/app/components/base/chat/chat/answer/agent-roster-response-content'
-import ChatInputArea from '@/app/components/base/chat/chat/chat-input-area'
-import { useChat } from '@/app/components/base/chat/chat/hooks'
-import {
- buildChatItemTree,
- getLastAnswer,
- isValidGeneratedAnswer,
-} from '@/app/components/base/chat/utils'
-import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
+import { skipToken, useQuery } from '@tanstack/react-query'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import Loading from '@/app/components/base/loading'
-import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
-import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
-import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
-import { InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types'
-import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
-import { userProfileAtom } from '@/context/account-state'
-import { agentComposerModelAtom } from '@/features/agent-v2/agent-composer/store-modules/model'
-import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt'
-import { ENABLE_AGENT_CLI_TOOLS } from '@/features/agent-v2/agent-detail/configure/feature-flags'
-import { PromptMode } from '@/models/debug'
-import dynamic from '@/next/dynamic'
-import { consoleClient, consoleQuery } from '@/service/client'
-import { AgentStrategy, ModelModeType, RETRIEVE_TYPE, TransferMethod } from '@/types/app'
-
-const Chat = dynamic(() => import('@/app/components/base/chat/chat'), { ssr: false })
-
-type AgentPreviewChatConfig = ChatConfig & {
- model: {
- provider: string
- name: string
- mode: ModelModeType
- completion_params: {
- max_tokens: number
- temperature: number
- top_p: number
- echo: boolean
- stop: string[]
- presence_penalty: number
- frequency_penalty: number
- }
- }
-}
-
-const defaultSystemParameters: ChatConfig['system_parameters'] = {
- audio_file_size_limit: 0,
- file_size_limit: 0,
- image_file_size_limit: 0,
- video_file_size_limit: 0,
- workflow_file_upload_limit: 0,
-}
-
-const disabledFileUploadConfig = {
- enabled: false,
- allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
- allowed_file_types: [],
- fileUploadConfig: defaultSystemParameters,
- image: {
- enabled: false,
- transfer_methods: [],
- number_limits: 0,
- image_file_size_limit: 0,
- detail: 'low',
- },
- max_length: 0,
- number_limits: 0,
-} as ChatConfig['file_upload'] & {
- fileUploadConfig: ChatConfig['system_parameters']
-}
-
-const defaultFileUploadMethods = [TransferMethod.local_file, TransferMethod.remote_url]
-
-const toPreviewFileUploadConfig = (fileUpload: FileUpload | undefined) => {
- if (!fileUpload?.enabled) return disabledFileUploadConfig
-
- const allowedFileUploadMethods = fileUpload.allowed_file_upload_methods?.length
- ? fileUpload.allowed_file_upload_methods
- : defaultFileUploadMethods
- const numberLimits = fileUpload.number_limits ?? fileUpload.image?.number_limits ?? 3
-
- return {
- ...disabledFileUploadConfig,
- ...fileUpload,
- enabled: true,
- allowed_file_types: fileUpload.allowed_file_types?.length
- ? fileUpload.allowed_file_types
- : [SupportUploadFileTypes.image],
- allowed_file_upload_methods: allowedFileUploadMethods,
- number_limits: numberLimits,
- fileUploadConfig: fileUpload.fileUploadConfig ?? defaultSystemParameters,
- image: {
- ...disabledFileUploadConfig.image,
- ...fileUpload.image,
- enabled: fileUpload.image?.enabled ?? true,
- transfer_methods: fileUpload.image?.transfer_methods?.length
- ? fileUpload.image.transfer_methods
- : allowedFileUploadMethods,
- number_limits: numberLimits,
- },
- } as ChatConfig['file_upload']
-}
-
-const getModelSettings = (agentSoulConfig?: AgentSoulConfig) =>
- agentSoulConfig?.model?.model_settings ?? {}
-
-const toEnabledConfig = (config?: { enabled?: boolean } | null) => ({
- ...config,
- enabled: config?.enabled ?? false,
-})
-
-const toInputType = (type: string): InputVarType => {
- if (type === InputVarType.paragraph) return InputVarType.paragraph
- if (type === InputVarType.select) return InputVarType.select
- if (type === InputVarType.number) return InputVarType.number
- if (type === InputVarType.json || type === InputVarType.jsonObject) return InputVarType.json
-
- return InputVarType.textInput
-}
-
-const toInputForm = (variable: NonNullable[number]) => {
- const variableKey = String(variable.name)
-
- return {
- ...variable,
- key: variableKey,
- label: variableKey,
- variable: variableKey,
- type: toInputType(variable.type),
- required: variable.required ?? true,
- hide: false,
- }
-}
-
-const getAgentSoulInputsForm = (agentSoulConfig?: AgentSoulConfig) =>
- (agentSoulConfig?.app_variables ?? []).map(toInputForm)
-
-const getAgentSoulInputs = (inputsForm: InputForm[]) => {
- return inputsForm.reduce((acc, input) => {
- acc[input.variable] = (input.default ?? '') as Inputs[string]
- return acc
- }, {})
-}
-
-const toAgentTool = (tool: AgentSoulDifyToolConfig) => ({
- provider_id: tool.provider_id ?? tool.provider ?? tool.plugin_id ?? '',
- provider_type: tool.provider_type ?? 'builtin',
- provider_name: tool.provider ?? '',
- tool_name: tool.tool_name,
- tool_label: tool.name ?? tool.tool_name,
- tool_parameters: tool.runtime_parameters ?? {},
- enabled: tool.enabled ?? true,
- credential_id: tool.credential_ref?.id,
-})
-
-const toCliTool = (tool: AgentCliToolConfig) => ({
- provider_id: 'agent-v2-cli',
- provider_type: 'builtin',
- provider_name: 'CLI',
- tool_name: tool.tool_name ?? tool.name ?? '',
- tool_label: tool.name ?? tool.tool_name ?? '',
- tool_parameters: {
- command: tool.command,
- },
- enabled: tool.enabled ?? true,
-})
-
-const toLegacyPreviewDatasetConfigs = (
- knowledge?: AgentSoulConfig['knowledge'],
-): NonNullable => {
- // Temporary preview adapter: composer state is knowledge.sets, but this
- // legacy chat preview contract still accepts one flat dataset_configs block.
- // Preview currently flattens the first configured set only.
- const previewKnowledgeSet = knowledge?.sets?.[0]
- const datasets = previewKnowledgeSet?.datasets ?? []
- const retrieval = previewKnowledgeSet?.retrieval
-
- return {
- retrieval_model: retrieval?.mode === 'single' ? RETRIEVE_TYPE.oneWay : RETRIEVE_TYPE.multiWay,
- reranking_model: {
- reranking_provider_name: retrieval?.reranking_model?.provider ?? '',
- reranking_model_name: retrieval?.reranking_model?.model ?? '',
- },
- top_k: retrieval?.top_k ?? 4,
- score_threshold_enabled:
- retrieval?.score_threshold !== undefined && retrieval?.score_threshold !== null,
- score_threshold: retrieval?.score_threshold ?? 0.8,
- datasets: {
- datasets: datasets
- .map((dataset) => ({
- enabled: true,
- id: dataset.id ?? '',
- }))
- .filter((dataset) => dataset.id),
- },
- }
-}
-
-const stopAgentChatMessageResponding = (agentId: string, taskId: string) => {
- return consoleClient.agent.byAgentId.chatMessages.byTaskId.stop.post({
- params: {
- agent_id: agentId,
- task_id: taskId,
- },
- })
-}
-
-const toFileResponse = (
- file: NonNullable[number],
-): FileResponse => ({
- related_id: file.id ?? file.upload_file_id,
- extension: '',
- filename: file.filename,
- size: file.size ?? 0,
- mime_type: file.mime_type ?? '',
- transfer_method: file.transfer_method as TransferMethod,
- type: file.type,
- url: file.url ?? '',
- upload_file_id: file.upload_file_id ?? '',
- remote_url: file.url ?? '',
-})
-
-const toLogMessages = (
- message: MessageDetailResponse['message'],
- answer: string,
- files: MessageDetailResponse['message_files'],
-) => {
- if (!Array.isArray(message)) return []
-
- const logMessages = message as IChatItem['log']
- if (logMessages?.at(-1)?.role === 'assistant') return logMessages
-
- return [
- ...(logMessages ?? []),
- {
- role: 'assistant',
- text: answer,
- files: getProcessedFilesFromResponse(
- (files?.filter((file) => file.belongs_to === 'assistant') || []).map(toFileResponse),
- ),
- },
- ]
-}
-
-function toToolLabels(value: AgentThought['tool_labels']): ThoughtItem['tool_labels'] {
- if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
-
- const toolLabels: NonNullable = {}
- for (const [name, label] of Object.entries(value)) {
- if (!label || typeof label !== 'object' || Array.isArray(label)) continue
-
- const enUS = 'en_US' in label ? label.en_US : undefined
- const zhHans = 'zh_Hans' in label ? label.zh_Hans : undefined
- if (typeof enUS !== 'string' || typeof zhHans !== 'string') continue
-
- toolLabels[name] = { en_US: enUS, zh_Hans: zhHans }
- for (const [locale, localizedLabel] of Object.entries(label)) {
- if (typeof localizedLabel === 'string') toolLabels[name][locale] = localizedLabel
- }
- }
-
- return Object.keys(toolLabels).length ? toolLabels : undefined
-}
-
-const toAgentThoughtItem = (thought: AgentThought, conversationId: string): ThoughtItem => ({
- id: thought.id,
- tool: thought.tool ?? '',
- thought: thought.thought ?? '',
- answer: thought.answer ?? '',
- tool_input: thought.tool_input ?? '',
- tool_labels: toToolLabels(thought.tool_labels),
- message_id: thought.message_id,
- conversation_id: conversationId,
- observation: thought.observation ?? '',
- position: thought.position,
- files: thought.files,
-})
-
-const toFeedback = (
- feedback: NonNullable[number] | undefined,
-): FeedbackType | undefined => {
- if (!feedback) return undefined
-
- const rating = feedback.rating as MessageRating
- if (rating !== 'like' && rating !== 'dislike' && rating !== null) return undefined
-
- return {
- rating,
- content: feedback.content,
- }
-}
-
-const getAgentDebugMessageAnswer = (message: MessageDetailResponse) => {
- return message.answer ?? ''
-}
-
-function getLastWorkflowRunId(messages: MessageDetailResponse[]) {
- for (let index = messages.length - 1; index >= 0; index--) {
- const workflowRunId = messages[index]?.workflow_run_id
- if (workflowRunId) return workflowRunId
- }
-
- return null
-}
-
-function getFormattedAgentDebugChatTree(messages: MessageDetailResponse[]): ChatItemInTree[] {
- const chatList: IChatItem[] = []
-
- messages.forEach((item) => {
- const answer = getAgentDebugMessageAnswer(item)
- const questionFiles = item.message_files?.filter((file) => file.belongs_to === 'user') || []
- const answerFiles = item.message_files?.filter((file) => file.belongs_to === 'assistant') || []
- const answerTokens = item.answer_tokens ?? 0
- const messageTokens = item.message_tokens ?? 0
- const latency = item.provider_response_latency ?? 0
-
- chatList.push({
- id: `question-${item.id}`,
- content: item.query,
- isAnswer: false,
- message_files: getProcessedFilesFromResponse(questionFiles.map(toFileResponse)),
- parentMessageId: item.parent_message_id || undefined,
- })
- chatList.push({
- id: item.id,
- content: answer,
- agent_thoughts: addFileInfos(
- sortAgentSorts(
- (item.agent_thoughts ?? []).map((thought) =>
- toAgentThoughtItem(thought, item.conversation_id),
- ),
- ),
- item.message_files as unknown as FileEntity[],
- ),
- feedback: toFeedback(item.feedbacks?.find((feedback) => feedback.from_source === 'user')),
- isAnswer: true,
- log: toLogMessages(item.message, answer, item.message_files),
- message_files: getProcessedFilesFromResponse(answerFiles.map(toFileResponse)),
- parentMessageId: `question-${item.id}`,
- workflow_run_id: item.workflow_run_id ?? undefined,
- conversationId: item.conversation_id,
- input: {
- inputs: item.inputs,
- query: item.query,
- },
- more: {
- time: '',
- tokens: answerTokens + messageTokens,
- latency: latency.toFixed(2),
- tokens_per_second: latency > 0 ? (answerTokens / latency).toFixed(2) : undefined,
- },
- })
- })
-
- return buildChatItemTree(chatList)
-}
-
-const fetchAgentSuggestedQuestions = (agentId: string, messageId: string) => {
- return consoleClient.agent.byAgentId.chatMessages.byMessageId.suggestedQuestions.get({
- params: {
- agent_id: agentId,
- message_id: messageId,
- },
- })
-}
-
-const buildChatConfig = ({
- agentSoulConfig,
- currentModel,
- prompt,
-}: {
- agentSoulConfig?: AgentSoulConfig
- currentModel?: AgentComposerModel
- prompt: string
-}): AgentPreviewChatConfig => {
- const modelSettings = currentModel?.model_settings ?? getModelSettings(agentSoulConfig)
- const appFeatures = agentSoulConfig?.app_features ?? {}
- const difyTools = agentSoulConfig?.tools?.dify_tools ?? []
- const cliTools = ENABLE_AGENT_CLI_TOOLS ? (agentSoulConfig?.tools?.cli_tools ?? []) : []
-
- return {
- pre_prompt: prompt || agentSoulConfig?.prompt?.system_prompt || '',
- prompt_type: PromptMode.simple,
- chat_prompt_config: DEFAULT_CHAT_PROMPT_CONFIG,
- completion_prompt_config: DEFAULT_COMPLETION_PROMPT_CONFIG,
- user_input_form: (agentSoulConfig?.app_variables ?? []).map((variable) => ({
- 'text-input': {
- default: String(variable.default ?? ''),
- label: variable.name,
- variable: variable.name,
- required: variable.required ?? true,
- max_length: 48,
- hide: false,
- },
- })),
- dataset_query_variable: '',
- opening_statement: appFeatures.opening_statement ?? '',
- suggested_questions: appFeatures.suggested_questions ?? [],
- suggested_questions_after_answer: toEnabledConfig(
- appFeatures.suggested_questions_after_answer,
- ) as ChatConfig['suggested_questions_after_answer'],
- more_like_this: { enabled: false },
- text_to_speech: toEnabledConfig(appFeatures.text_to_speech) as ChatConfig['text_to_speech'],
- speech_to_text: toEnabledConfig(appFeatures.speech_to_text),
- retriever_resource: toEnabledConfig(appFeatures.retriever_resource),
- sensitive_word_avoidance: toEnabledConfig(appFeatures.sensitive_word_avoidance),
- annotation_reply: {
- id: '',
- enabled: false,
- score_threshold: 0.9,
- embedding_model: {
- embedding_provider_name: '',
- embedding_model_name: '',
- },
- },
- agent_mode: {
- enabled: difyTools.length > 0 || cliTools.length > 0,
- strategy: AgentStrategy.functionCall,
- tools: [
- ...difyTools.map(toAgentTool),
- ...cliTools.map(toCliTool),
- ] as ChatConfig['agent_mode']['tools'],
- },
- model: {
- provider: currentModel?.provider ?? agentSoulConfig?.model?.model_provider ?? '',
- name: currentModel?.model ?? agentSoulConfig?.model?.model ?? '',
- mode: ModelModeType.chat,
- completion_params: {
- temperature: modelSettings.temperature ?? 0.7,
- top_p: modelSettings.top_p ?? 1,
- max_tokens: modelSettings.max_tokens ?? 512,
- presence_penalty: modelSettings.presence_penalty ?? 0,
- frequency_penalty: modelSettings.frequency_penalty ?? 0,
- stop: modelSettings.stop ?? [],
- echo: false,
- },
- },
- dataset_configs: toLegacyPreviewDatasetConfigs(agentSoulConfig?.knowledge),
- file_upload: toPreviewFileUploadConfig(appFeatures.file_upload as FileUpload | undefined),
- system_parameters: defaultSystemParameters,
- supportCitationHitInfo: true,
- }
-}
+import { consoleQuery } from '@/service/client'
+import { getFormattedAgentDebugChatTree, getLastWorkflowRunId } from './chat-history'
+import { AgentPreviewChatSession } from './chat-session'
export type AgentChatRuntimeEmptyStateProps = {
agentIcon?: string | null
@@ -477,7 +15,6 @@ export type AgentChatRuntimeEmptyStateProps = {
agentIconType?: AgentIconType | null
agentName?: string
hasInstructions: boolean
- inputNode: ReactNode
}
export type AgentChatRuntimeProps = {
@@ -568,14 +105,17 @@ export function AgentChatRuntime({
)
}
- const chatSessionKey =
+ const inputSessionKey =
+ !conversationId || conversationBelongsToCurrentSession ? 'current-session' : conversationId
+ const conversationSessionKey =
!conversationId || conversationBelongsToCurrentSession
? 'current-session'
: `${conversationId}-${historyQuery.dataUpdatedAt}`
return (
)
}
-
-function AgentPreviewChatSession({
- agentId,
- agentIcon,
- agentIconBackground,
- agentIconType,
- agentName,
- agentSoulConfig,
- clearChatList,
- conversationId,
- draftType,
- initialChatTree,
- inputPlaceholder,
- inputAutoFocus,
- sendButtonLabel,
- renderEmptyState,
- onClearChatListChange,
- onConversationComplete,
- onConversationIdChange,
- onCurrentSessionConversationIdChange,
- onBeforeSpeechToText,
- onSendInterrupted,
- onSaveDraftBeforeRun,
-}: {
- agentId: string
- agentIcon?: string | null
- agentIconBackground?: string | null
- agentIconType?: AgentIconType | null
- agentName?: string
- agentSoulConfig?: AgentSoulConfig
- clearChatList: boolean
- conversationId?: string | null
- draftType?: 'debug_build'
- initialChatTree: ChatItemInTree[]
- inputPlaceholder: string
- inputAutoFocus?: boolean
- sendButtonLabel?: string
- renderEmptyState: (props: AgentChatRuntimeEmptyStateProps) => ReactNode
- onClearChatListChange: (clearChatList: boolean) => void
- onConversationComplete?: (conversationId: string, workflowRunId?: string) => void
- onConversationIdChange?: (conversationId: string) => void
- onCurrentSessionConversationIdChange: (conversationId: string) => void
- onBeforeSpeechToText?: () => Promise