mirror of
https://gitee.com/dify_ai/dify.git
synced 2024-12-01 02:38:12 +08:00
parent
9b8861e3e1
commit
e70482dfc0
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -473,7 +473,7 @@ const Debug: FC<IDebug> = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showPromptLogModal && (
|
||||
{mode === AppType.completion && showPromptLogModal && (
|
||||
<PromptLogModal
|
||||
width={width}
|
||||
currentLogItem={currentLogItem}
|
||||
|
@ -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,
|
||||
|
@ -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 })),
|
||||
}))
|
||||
|
132
web/app/components/base/agent-log-modal/detail.tsx
Normal file
132
web/app/components/base/agent-log-modal/detail.tsx
Normal 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
|
61
web/app/components/base/agent-log-modal/index.tsx
Normal file
61
web/app/components/base/agent-log-modal/index.tsx
Normal 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
|
50
web/app/components/base/agent-log-modal/iteration.tsx
Normal file
50
web/app/components/base/agent-log-modal/iteration.tsx
Normal 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
|
126
web/app/components/base/agent-log-modal/result.tsx
Normal file
126
web/app/components/base/agent-log-modal/result.tsx
Normal 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
|
140
web/app/components/base/agent-log-modal/tool-call.tsx
Normal file
140
web/app/components/base/agent-log-modal/tool-call.tsx
Normal 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
|
25
web/app/components/base/agent-log-modal/tracing.tsx
Normal file
25
web/app/components/base/agent-log-modal/tracing.tsx
Normal 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
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
: {
|
||||
|
@ -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'>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -77,7 +77,15 @@ const translation = {
|
||||
workflowTitle: 'ログの詳細',
|
||||
},
|
||||
promptLog: 'プロンプトログ',
|
||||
agentLog: 'エージェントログ',
|
||||
viewLog: 'ログを表示',
|
||||
agentLogDetail: {
|
||||
agentMode: 'エージェントモード',
|
||||
toolUsed: '使用したツール',
|
||||
iterations: '反復',
|
||||
iteration: '反復',
|
||||
finalProcessing: '最終処理',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -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
|
||||
|
@ -77,7 +77,15 @@ const translation = {
|
||||
workflowTitle: 'Деталі Журналу',
|
||||
},
|
||||
promptLog: 'Журнал Запитань',
|
||||
viewLog: 'Переглянути Журнал',
|
||||
agentLog: 'Журнал агента',
|
||||
viewLog: 'Переглянути журнал',
|
||||
agentLogDetail: {
|
||||
agentMode: 'Режим агента',
|
||||
toolUsed: 'Використаний інструмент',
|
||||
iterations: 'Ітерації',
|
||||
iteration: 'Ітерація',
|
||||
finalProcessing: 'Остаточна обробка',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -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
|
||||
|
@ -77,7 +77,15 @@ const translation = {
|
||||
workflowTitle: '日志详情',
|
||||
},
|
||||
promptLog: 'Prompt 日志',
|
||||
agentLog: 'Agent 日志',
|
||||
viewLog: '查看日志',
|
||||
agentLogDetail: {
|
||||
agentMode: 'Agent 模式',
|
||||
toolUsed: '使用工具',
|
||||
iterations: '迭代次数',
|
||||
iteration: '迭代',
|
||||
finalProcessing: '最终处理',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -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[]
|
||||
}
|
||||
|
@ -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 })
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user