feat: agent log (#3537)

Co-authored-by: jyong <718720800@qq.com>
This commit is contained in:
KVOJJJin 2024-04-17 10:30:52 +08:00 committed by GitHub
parent 9b8861e3e1
commit e70482dfc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 732 additions and 14 deletions

View File

@ -11,8 +11,9 @@ const Log: FC<LogProps> = ({
logItem,
}) => {
const { t } = useTranslation()
const { setCurrentLogItem, setShowPromptLogModal, setShowMessageLogModal } = useAppStore()
const { workflow_run_id: runID } = logItem
const { setCurrentLogItem, setShowPromptLogModal, setShowAgentLogModal, setShowMessageLogModal } = useAppStore()
const { workflow_run_id: runID, agent_thoughts } = logItem
const isAgent = agent_thoughts && agent_thoughts.length > 0
return (
<div
@ -23,12 +24,14 @@ const Log: FC<LogProps> = ({
setCurrentLogItem(logItem)
if (runID)
setShowMessageLogModal(true)
else if (isAgent)
setShowAgentLogModal(true)
else
setShowPromptLogModal(true)
}}
>
<File02 className='mr-1 w-4 h-4' />
<div className='text-xs leading-4'>{runID ? t('appLog.viewLog') : t('appLog.promptLog')}</div>
<div className='text-xs leading-4'>{runID ? t('appLog.viewLog') : isAgent ? t('appLog.agentLog') : t('appLog.promptLog')}</div>
</div>
)
}

View File

@ -83,6 +83,9 @@ export type IChatItem = {
agent_thoughts?: ThoughtItem[]
message_files?: VisionFile[]
workflow_run_id?: string
// for agent log
conversationId?: string
input?: any
}
export type MessageEnd = {

View File

@ -473,7 +473,7 @@ const Debug: FC<IDebug> = ({
)}
</div>
)}
{showPromptLogModal && (
{mode === AppType.completion && showPromptLogModal && (
<PromptLogModal
width={width}
currentLogItem={currentLogItem}

View File

@ -35,6 +35,7 @@ import ModelName from '@/app/components/header/account-setting/model-provider-pa
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import TextGeneration from '@/app/components/app/text-generate/item'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import AgentLogModal from '@/app/components/base/agent-log-modal'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
import MessageLogModal from '@/app/components/base/message-log-modal'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -76,7 +77,7 @@ const PARAM_MAP = {
}
// Format interface data for easy display
const getFormattedChatList = (messages: ChatMessage[]) => {
const getFormattedChatList = (messages: ChatMessage[], conversationId: string) => {
const newChatList: IChatItem[] = []
messages.forEach((item: ChatMessage) => {
newChatList.push({
@ -107,6 +108,11 @@ const getFormattedChatList = (messages: ChatMessage[]) => {
: []),
],
workflow_run_id: item.workflow_run_id,
conversationId,
input: {
inputs: item.inputs,
query: item.query,
},
more: {
time: dayjs.unix(item.created_at).format('hh:mm A'),
tokens: item.answer_tokens + item.message_tokens,
@ -148,7 +154,7 @@ type IDetailPanel<T> = {
function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionConversationFullDetailResponse>({ detail, onFeedback }: IDetailPanel<T>) {
const { onClose, appDetail } = useContext(DrawerContext)
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showMessageLogModal, setShowMessageLogModal } = useAppStore()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal, showMessageLogModal, setShowMessageLogModal } = useAppStore()
const { t } = useTranslation()
const [items, setItems] = React.useState<IChatItem[]>([])
const [hasMore, setHasMore] = useState(true)
@ -172,7 +178,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
const varValues = messageRes.data[0].inputs
setVarValues(varValues)
}
const newItems = [...getFormattedChatList(messageRes.data), ...items]
const newItems = [...getFormattedChatList(messageRes.data, detail.id), ...items]
if (messageRes.has_more === false && detail?.model_config?.configs?.introduction) {
newItems.unshift({
id: 'introduction',
@ -401,6 +407,16 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
}}
/>
)}
{showAgentLogModal && (
<AgentLogModal
width={width}
currentLogItem={currentLogItem}
onCancel={() => {
setCurrentLogItem()
setShowAgentLogModal(false)
}}
/>
)}
{showMessageLogModal && (
<MessageLogModal
width={width}
@ -607,7 +623,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
onClose={onCloseDrawer}
mask={isMobile}
footer={null}
panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'
panelClassname='mt-16 mx-2 sm:mr-2 mb-4 !p-0 !max-w-[640px] rounded-xl'
>
<DrawerContext.Provider value={{
onClose: onCloseDrawer,

View File

@ -7,6 +7,7 @@ type State = {
appSidebarExpand: string
currentLogItem?: IChatItem
showPromptLogModal: boolean
showAgentLogModal: boolean
showMessageLogModal: boolean
}
@ -15,6 +16,7 @@ type Action = {
setAppSiderbarExpand: (state: string) => void
setCurrentLogItem: (item?: IChatItem) => void
setShowPromptLogModal: (showPromptLogModal: boolean) => void
setShowAgentLogModal: (showAgentLogModal: boolean) => void
setShowMessageLogModal: (showMessageLogModal: boolean) => void
}
@ -27,6 +29,8 @@ export const useStore = create<State & Action>(set => ({
setCurrentLogItem: currentLogItem => set(() => ({ currentLogItem })),
showPromptLogModal: false,
setShowPromptLogModal: showPromptLogModal => set(() => ({ showPromptLogModal })),
showAgentLogModal: false,
setShowAgentLogModal: showAgentLogModal => set(() => ({ showAgentLogModal })),
showMessageLogModal: false,
setShowMessageLogModal: showMessageLogModal => set(() => ({ showMessageLogModal })),
}))

View File

@ -0,0 +1,132 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import { flatten, uniq } from 'lodash-es'
import cn from 'classnames'
import ResultPanel from './result'
import TracingPanel from './tracing'
import { ToastContext } from '@/app/components/base/toast'
import Loading from '@/app/components/base/loading'
import { fetchAgentLogDetail } from '@/service/log'
import type { AgentIteration, AgentLogDetailResponse } from '@/models/log'
import { useStore as useAppStore } from '@/app/components/app/store'
import type { IChatItem } from '@/app/components/app/chat/type'
export type AgentLogDetailProps = {
activeTab?: 'DETAIL' | 'TRACING'
conversationID: string
log: IChatItem
messageID: string
}
const AgentLogDetail: FC<AgentLogDetailProps> = ({
activeTab = 'DETAIL',
conversationID,
messageID,
log,
}) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const [currentTab, setCurrentTab] = useState<string>(activeTab)
const { appDetail } = useAppStore()
const [loading, setLoading] = useState<boolean>(true)
const [runDetail, setRunDetail] = useState<AgentLogDetailResponse>()
const [list, setList] = useState<AgentIteration[]>([])
const tools = useMemo(() => {
const res = uniq(flatten(runDetail?.iterations.map((iteration: any) => {
return iteration.tool_calls.map((tool: any) => tool.tool_name).filter(Boolean)
})).filter(Boolean))
return res
}, [runDetail])
const getLogDetail = useCallback(async (appID: string, conversationID: string, messageID: string) => {
try {
const res = await fetchAgentLogDetail({
appID,
params: {
conversation_id: conversationID,
message_id: messageID,
},
})
setRunDetail(res)
setList(res.iterations)
}
catch (err) {
notify({
type: 'error',
message: `${err}`,
})
}
}, [notify])
const getData = async (appID: string, conversationID: string, messageID: string) => {
setLoading(true)
await getLogDetail(appID, conversationID, messageID)
setLoading(false)
}
const switchTab = async (tab: string) => {
setCurrentTab(tab)
}
useEffect(() => {
// fetch data
if (appDetail)
getData(appDetail.id, conversationID, messageID)
}, [appDetail, conversationID, messageID])
return (
<div className='grow relative flex flex-col'>
{/* tab */}
<div className='shrink-0 flex items-center px-4 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'>
<div
className={cn(
'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer',
currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-gray-700',
)}
onClick={() => switchTab('DETAIL')}
>{t('runLog.detail')}</div>
<div
className={cn(
'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer',
currentTab === 'TRACING' && '!border-[rgb(21,94,239)] text-gray-700',
)}
onClick={() => switchTab('TRACING')}
>{t('runLog.tracing')}</div>
</div>
{/* panel detal */}
<div className={cn('grow bg-white h-0 overflow-y-auto rounded-b-2xl', currentTab !== 'DETAIL' && '!bg-gray-50')}>
{loading && (
<div className='flex h-full items-center justify-center bg-white'>
<Loading />
</div>
)}
{!loading && currentTab === 'DETAIL' && runDetail && (
<ResultPanel
inputs={log.input}
outputs={log.content}
status={runDetail.meta.status}
error={runDetail.meta.error}
elapsed_time={runDetail.meta.elapsed_time}
total_tokens={runDetail.meta.total_tokens}
created_at={runDetail.meta.start_time}
created_by={runDetail.meta.executor}
agentMode={runDetail.meta.agent_mode}
tools={tools}
iterations={runDetail.iterations.length}
/>
)}
{!loading && currentTab === 'TRACING' && (
<TracingPanel
list={list}
/>
)}
</div>
</div>
)
}
export default AgentLogDetail

View File

@ -0,0 +1,61 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useEffect, useRef, useState } from 'react'
import { useClickAway } from 'ahooks'
import AgentLogDetail from './detail'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import type { IChatItem } from '@/app/components/app/chat/type'
type AgentLogModalProps = {
currentLogItem?: IChatItem
width: number
onCancel: () => void
}
const AgentLogModal: FC<AgentLogModalProps> = ({
currentLogItem,
width,
onCancel,
}) => {
const { t } = useTranslation()
const ref = useRef(null)
const [mounted, setMounted] = useState(false)
useClickAway(() => {
if (mounted)
onCancel()
}, ref)
useEffect(() => {
setMounted(true)
}, [])
if (!currentLogItem || !currentLogItem.conversationId)
return null
return (
<div
className={cn('relative flex flex-col py-3 bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl z-10')}
style={{
width: 480,
position: 'fixed',
top: 56 + 8,
left: 8 + (width - 480),
bottom: 16,
}}
ref={ref}
>
<h1 className='shrink-0 px-4 py-1 text-md font-semibold text-gray-900'>{t('appLog.runDetail.workflowTitle')}</h1>
<span className='absolute right-3 top-4 p-1 cursor-pointer z-20' onClick={onCancel}>
<XClose className='w-4 h-4 text-gray-500' />
</span>
<AgentLogDetail
conversationID={currentLogItem.conversationId}
messageID={currentLogItem.id}
log={currentLogItem}
/>
</div>
)
}
export default AgentLogModal

View File

@ -0,0 +1,50 @@
'use client'
import { useTranslation } from 'react-i18next'
import type { FC } from 'react'
import cn from 'classnames'
import ToolCall from './tool-call'
import type { AgentIteration } from '@/models/log'
type Props = {
isFinal: boolean
index: number
iterationInfo: AgentIteration
}
const Iteration: FC<Props> = ({ iterationInfo, isFinal, index }) => {
const { t } = useTranslation()
return (
<div className={cn('px-4 py-2')}>
<div className='flex items-center'>
{isFinal && (
<div className='shrink-0 mr-3 text-gray-500 text-xs leading-[18px] font-semibold'>{t('appLog.agentLogDetail.finalProcessing')}</div>
)}
{!isFinal && (
<div className='shrink-0 mr-3 text-gray-500 text-xs leading-[18px] font-semibold'>{`${t('appLog.agentLogDetail.iteration').toUpperCase()} ${index}`}</div>
)}
<div className='grow h-[1px] bg-gradient-to-r from-[#f3f4f6] to-gray-50'></div>
</div>
<ToolCall
isLLM
isFinal={isFinal}
tokens={iterationInfo.tokens}
observation={iterationInfo.tool_raw.outputs}
finalAnswer={iterationInfo.thought}
toolCall={{
status: 'success',
tool_icon: null,
}}
/>
{iterationInfo.tool_calls.map((toolCall, index) => (
<ToolCall
isLLM={false}
key={index}
toolCall={toolCall}
/>
))}
</div>
)
}
export default Iteration

View File

@ -0,0 +1,126 @@
'use client'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import StatusPanel from '@/app/components/workflow/run/status'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
type ResultPanelProps = {
status: string
elapsed_time?: number
total_tokens?: number
error?: string
inputs?: any
outputs?: any
created_by?: string
created_at?: string
agentMode?: string
tools?: string[]
iterations?: number
}
const ResultPanel: FC<ResultPanelProps> = ({
status,
elapsed_time,
total_tokens,
error,
inputs,
outputs,
created_by,
created_at = 0,
agentMode,
tools,
iterations,
}) => {
const { t } = useTranslation()
return (
<div className='bg-white py-2'>
<div className='px-4 py-2'>
<StatusPanel
status='succeeded'
time={elapsed_time}
tokens={total_tokens}
error={error}
/>
</div>
<div className='px-4 py-2 flex flex-col gap-2'>
<CodeEditor
readOnly
title={<div>INPUT</div>}
language={CodeLanguage.json}
value={inputs}
isJSONStringifyBeauty
/>
<CodeEditor
readOnly
title={<div>OUTPUT</div>}
language={CodeLanguage.json}
value={outputs}
isJSONStringifyBeauty
/>
</div>
<div className='px-4 py-2'>
<div className='h-[0.5px] bg-black opacity-5' />
</div>
<div className='px-4 py-2'>
<div className='relative'>
<div className='h-6 leading-6 text-gray-500 text-xs font-medium'>{t('runLog.meta.title')}</div>
<div className='py-1'>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.status')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>SUCCESS</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.executor')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{created_by || 'N/A'}</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.startTime')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{dayjs(created_at).format('YYYY-MM-DD hh:mm:ss')}</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.time')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{`${elapsed_time?.toFixed(3)}s`}</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.tokens')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{`${total_tokens || 0} Tokens`}</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('appLog.agentLogDetail.agentMode')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{agentMode === 'function_call' ? t('appDebug.agent.agentModeType.functionCall') : t('appDebug.agent.agentModeType.ReACT')}</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('appLog.agentLogDetail.toolUsed')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{tools?.length ? tools?.join(', ') : 'Null'}</span>
</div>
</div>
<div className='flex'>
<div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('appLog.agentLogDetail.iterations')}</div>
<div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'>
<span>{iterations}</span>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default ResultPanel

View File

@ -0,0 +1,140 @@
'use client'
import type { FC } from 'react'
import { useState } from 'react'
import cn from 'classnames'
import { useContext } from 'use-context-selector'
import BlockIcon from '@/app/components/workflow/block-icon'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { CheckCircle } from '@/app/components/base/icons/src/vender/line/general'
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
import type { ToolCall } from '@/models/log'
import { BlockEnum } from '@/app/components/workflow/types'
import I18n from '@/context/i18n'
type Props = {
toolCall: ToolCall
isLLM: boolean
isFinal?: boolean
tokens?: number
observation?: any
finalAnswer?: any
}
const ToolCallItem: FC<Props> = ({ toolCall, isLLM = false, isFinal, tokens, observation, finalAnswer }) => {
const [collapseState, setCollapseState] = useState<boolean>(true)
const { locale } = useContext(I18n)
const toolName = isLLM ? 'LLM' : (toolCall.tool_label[locale] || toolCall.tool_label[locale.replaceAll('-', '_')])
const getTime = (time: number) => {
if (time < 1)
return `${(time * 1000).toFixed(3)} ms`
if (time > 60)
return `${parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s`
return `${time.toFixed(3)} s`
}
const getTokenCount = (tokens: number) => {
if (tokens < 1000)
return tokens
if (tokens >= 1000 && tokens < 1000000)
return `${parseFloat((tokens / 1000).toFixed(3))}K`
if (tokens >= 1000000)
return `${parseFloat((tokens / 1000000).toFixed(3))}M`
}
return (
<div className={cn('py-1')}>
<div className={cn('group transition-all bg-white border border-gray-100 rounded-2xl shadow-xs hover:shadow-md')}>
<div
className={cn(
'flex items-center py-3 pl-[6px] pr-3 cursor-pointer',
!collapseState && '!pb-2',
)}
onClick={() => setCollapseState(!collapseState)}
>
<ChevronRight
className={cn(
'shrink-0 w-3 h-3 mr-1 text-gray-400 transition-all group-hover:text-gray-500',
!collapseState && 'rotate-90',
)}
/>
<BlockIcon className={cn('shrink-0 mr-2')} type={isLLM ? BlockEnum.LLM : BlockEnum.Tool} toolIcon={toolCall.tool_icon} />
<div className={cn(
'grow text-gray-700 text-[13px] leading-[16px] font-semibold truncate',
)} title={toolName}>{toolName}</div>
<div className='shrink-0 text-gray-500 text-xs leading-[18px]'>
{toolCall.time_cost && (
<span>{getTime(toolCall.time_cost || 0)}</span>
)}
{isLLM && (
<span>{`${getTokenCount(tokens || 0)} tokens`}</span>
)}
</div>
{toolCall.status === 'success' && (
<CheckCircle className='shrink-0 ml-2 w-3.5 h-3.5 text-[#12B76A]' />
)}
{toolCall.status === 'error' && (
<AlertCircle className='shrink-0 ml-2 w-3.5 h-3.5 text-[#F04438]' />
)}
</div>
{!collapseState && (
<div className='pb-2'>
<div className={cn('px-[10px] py-1')}>
{toolCall.status === 'error' && (
<div className='px-3 py-[10px] bg-[#fef3f2] rounded-lg border-[0.5px] border-[rbga(0,0,0,0.05)] text-xs leading-[18px] text-[#d92d20] shadow-xs'>{toolCall.error}</div>
)}
</div>
{toolCall.tool_input && (
<div className={cn('px-[10px] py-1')}>
<CodeEditor
readOnly
title={<div>INPUT</div>}
language={CodeLanguage.json}
value={toolCall.tool_input}
isJSONStringifyBeauty
/>
</div>
)}
{toolCall.tool_output && (
<div className={cn('px-[10px] py-1')}>
<CodeEditor
readOnly
title={<div>OUTPUT</div>}
language={CodeLanguage.json}
value={toolCall.tool_output}
isJSONStringifyBeauty
/>
</div>
)}
{isLLM && (
<div className={cn('px-[10px] py-1')}>
<CodeEditor
readOnly
title={<div>OBSERVATION</div>}
language={CodeLanguage.json}
value={observation}
isJSONStringifyBeauty
/>
</div>
)}
{isLLM && (
<div className={cn('px-[10px] py-1')}>
<CodeEditor
readOnly
title={<div>{isFinal ? 'FINAL ANSWER' : 'THOUGHT'}</div>}
language={CodeLanguage.json}
value={finalAnswer}
isJSONStringifyBeauty
/>
</div>
)}
</div>
)}
</div>
</div>
)
}
export default ToolCallItem

View File

@ -0,0 +1,25 @@
'use client'
import type { FC } from 'react'
import Iteration from './iteration'
import type { AgentIteration } from '@/models/log'
type TracingPanelProps = {
list: AgentIteration[]
}
const TracingPanel: FC<TracingPanelProps> = ({ list }) => {
return (
<div className='bg-gray-50'>
{list.map((iteration, index) => (
<Iteration
key={index}
index={index + 1}
isFinal={index + 1 === list.length}
iterationInfo={iteration}
/>
))}
</div>
)
}
export default TracingPanel

View File

@ -322,6 +322,7 @@ export const useChat = (
}
draft[index] = {
...draft[index],
content: newResponseItem.answer,
log: [
...newResponseItem.message,
...(newResponseItem.message[newResponseItem.message.length - 1].role !== 'assistant'
@ -339,6 +340,12 @@ export const useChat = (
tokens: newResponseItem.answer_tokens + newResponseItem.message_tokens,
latency: newResponseItem.provider_response_latency.toFixed(2),
},
// for agent log
conversationId: connversationId.current,
input: {
inputs: newResponseItem.inputs,
query: newResponseItem.query,
},
}
}
})

View File

@ -26,6 +26,7 @@ import { ChatContextProvider } from './context'
import type { Emoji } from '@/app/components/tools/types'
import Button from '@/app/components/base/button'
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import AgentLogModal from '@/app/components/base/agent-log-modal'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -78,7 +79,7 @@ const Chat: FC<ChatProps> = ({
chatAnswerContainerInner,
}) => {
const { t } = useTranslation()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore()
const [width, setWidth] = useState(0)
const chatContainerRef = useRef<HTMLDivElement>(null)
const chatContainerInnerRef = useRef<HTMLDivElement>(null)
@ -259,6 +260,16 @@ const Chat: FC<ChatProps> = ({
}}
/>
)}
{showAgentLogModal && (
<AgentLogModal
width={width}
currentLogItem={currentLogItem}
onCancel={() => {
setCurrentLogItem()
setShowAgentLogModal(false)
}}
/>
)}
</div>
</ChatContextProvider>
)

View File

@ -59,6 +59,7 @@ export type WorkflowProcess = {
export type ChatItem = IChatItem & {
isError?: boolean
workflowProcess?: WorkflowProcess
conversationId?: string
}
export type OnSend = (message: string, files?: VisionFile[]) => void

View File

@ -39,12 +39,12 @@ const MessageLogModal: FC<MessageLogModalProps> = ({
<div
className={cn('relative flex flex-col py-3 bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl z-10')}
style={{
width,
width: fixedWidth ? width : 480,
...(!fixedWidth
? {
position: 'fixed',
top: 56 + 8,
left: 8,
left: 8 + (width - 480),
bottom: 16,
}
: {

View File

@ -34,7 +34,13 @@ const PromptLogModal: FC<PromptLogModalProps> = ({
return (
<div
className='fixed top-16 left-2 bottom-2 flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl z-10'
style={{ width }}
style={{
width: 480,
position: 'fixed',
top: 56 + 8,
left: 8 + (width - 480),
bottom: 16,
}}
ref={ref}
>
<div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-b-gray-100'>

View File

@ -64,6 +64,22 @@ const translation = {
not_annotated: 'Nicht annotiert',
},
},
workflowTitle: 'Workflow-Protokolle',
workflowSubtitle: 'Das Protokoll hat den Vorgang von Automate aufgezeichnet.',
runDetail: {
title: 'Konversationsprotokoll',
workflowTitle: 'Protokolldetail',
},
promptLog: 'Prompt-Protokoll',
agentLog: 'Agentenprotokoll',
viewLog: 'Protokoll anzeigen',
agentLogDetail: {
agentMode: 'Agentenmodus',
toolUsed: 'Verwendetes Werkzeug',
iterations: 'Iterationen',
iteration: 'Iteration',
finalProcessing: 'Endverarbeitung',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: 'Log Detail',
},
promptLog: 'Prompt Log',
agentLog: 'Agent Log',
viewLog: 'View Log',
agentLogDetail: {
agentMode: 'Agent Mode',
toolUsed: 'Tool Used',
iterations: 'Iterations',
iteration: 'Iteration',
finalProcessing: 'Final Processing',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: 'Détail du journal',
},
promptLog: 'Journal de consigne',
agentLog: 'Journal des agents',
viewLog: 'Voir le journal',
agentLogDetail: {
agentMode: 'Mode Agent',
toolUsed: 'Outil utilisé',
iterations: 'Itérations',
iteration: 'Itération',
finalProcessing: 'Traitement final',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: 'ログの詳細',
},
promptLog: 'プロンプトログ',
agentLog: 'エージェントログ',
viewLog: 'ログを表示',
agentLogDetail: {
agentMode: 'エージェントモード',
toolUsed: '使用したツール',
iterations: '反復',
iteration: '反復',
finalProcessing: '最終処理',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: 'Detalhes do Registro',
},
promptLog: 'Registro de Prompt',
agentLog: 'Registro do agente',
viewLog: 'Ver Registro',
agenteLogDetail: {
agentMode: 'Modo Agente',
toolUsed: 'Ferramenta usada',
iterações: 'Iterações',
iteração: 'Iteração',
finalProcessing: 'Processamento Final',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: 'Деталі Журналу',
},
promptLog: 'Журнал Запитань',
viewLog: 'Переглянути Журнал',
agentLog: 'Журнал агента',
viewLog: 'Переглянути журнал',
agentLogDetail: {
agentMode: 'Режим агента',
toolUsed: 'Використаний інструмент',
iterations: 'Ітерації',
iteration: 'Ітерація',
finalProcessing: 'Остаточна обробка',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: 'Chi Tiết Nhật Ký',
},
promptLog: 'Nhật Ký Nhắc Nhở',
viewLog: 'Xem Nhật Ký',
AgentLog: 'Nhật ký đại lý',
viewLog: 'Xem nhật ký',
agentLogDetail: {
AgentMode: 'Chế độ đại lý',
toolUsed: 'Công cụ được sử dụng',
iterations: 'Lặp lại',
iteration: 'Lặp lại',
finalProcessing: 'Xử lý cuối cùng',
},
}
export default translation

View File

@ -77,7 +77,15 @@ const translation = {
workflowTitle: '日志详情',
},
promptLog: 'Prompt 日志',
agentLog: 'Agent 日志',
viewLog: '查看日志',
agentLogDetail: {
agentMode: 'Agent 模式',
toolUsed: '使用工具',
iterations: '迭代次数',
iteration: '迭代',
finalProcessing: '最终处理',
},
}
export default translation

View File

@ -4,6 +4,7 @@ import type {
Edge,
Node,
} from '@/app/components/workflow/types'
// Log type contains key:string conversation_id:string created_at:string quesiton:string answer:string
export type Conversation = {
id: string
@ -292,3 +293,57 @@ export type WorkflowRunDetailResponse = {
created_at: number
finished_at: number
}
export type AgentLogMeta = {
status: string
executor: string
start_time: string
elapsed_time: number
total_tokens: number
agent_mode: string
iterations: number
error?: string
}
export type ToolCall = {
status: string
error?: string | null
time_cost?: number
tool_icon: any
tool_input?: any
tool_output?: any
tool_name?: string
tool_label?: any
tool_parameters?: any
}
export type AgentIteration = {
created_at: string
files: string[]
thought: string
tokens: number
tool_calls: ToolCall[]
tool_raw: {
inputs: string
outputs: string
}
}
export type AgentLogFile = {
id: string
type: string
url: string
name: string
belongs_to: string
}
export type AgentLogDetailRequest = {
conversation_id: string
message_id: string
}
export type AgentLogDetailResponse = {
meta: AgentLogMeta
iterations: AgentIteration[]
files: AgentLogFile[]
}

View File

@ -1,6 +1,8 @@
import type { Fetcher } from 'swr'
import { get, post } from './base'
import type {
AgentLogDetailRequest,
AgentLogDetailResponse,
AnnotationsCountResponse,
ChatConversationFullDetailResponse,
ChatConversationsRequest,
@ -73,3 +75,7 @@ export const fetchRunDetail = ({ appID, runID }: { appID: string; runID: string
export const fetchTracingList: Fetcher<NodeTracingListResponse, { url: string }> = ({ url }) => {
return get<NodeTracingListResponse>(url)
}
export const fetchAgentLogDetail = ({ appID, params }: { appID: string; params: AgentLogDetailRequest }) => {
return get<AgentLogDetailResponse>(`/apps/${appID}/agent/logs`, { params })
}