feat: knowledge pipeline (#25360)
Signed-off-by: -LAN- <laipz8200@outlook.com> Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: jyong <718720800@qq.com> Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com> Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: quicksand <quicksandzn@gmail.com> Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com> Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: Yongtao Huang <yongtaoh2022@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: nite-knite <nkCoding@gmail.com> Co-authored-by: Hanqing Zhao <sherry9277@gmail.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Harry <xh001x@hotmail.com>
This commit is contained in:
@@ -21,7 +21,7 @@ const AgentLogTrigger = ({
|
||||
<div
|
||||
className='cursor-pointer rounded-[10px] bg-components-button-tertiary-bg'
|
||||
onClick={() => {
|
||||
onShowAgentOrToolLog({ id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren)
|
||||
onShowAgentOrToolLog({ message_id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren)
|
||||
}}
|
||||
>
|
||||
<div className='system-2xs-medium-uppercase flex items-center px-3 pt-2 text-text-tertiary'>
|
||||
|
||||
@@ -16,7 +16,7 @@ const AgentResultPanel = ({
|
||||
}: AgentResultPanelProps) => {
|
||||
const { t } = useTranslation()
|
||||
const top = agentOrToolLogItemStack[agentOrToolLogItemStack.length - 1]
|
||||
const list = agentOrToolLogListMap[top.id]
|
||||
const list = agentOrToolLogListMap[top.message_id]
|
||||
|
||||
return (
|
||||
<div className='overflow-y-auto bg-background-section'>
|
||||
@@ -29,7 +29,7 @@ const AgentResultPanel = ({
|
||||
{
|
||||
list.map(item => (
|
||||
<AgentLogItem
|
||||
key={item.id}
|
||||
key={item.message_id}
|
||||
item={item}
|
||||
onShowAgentOrToolLog={onShowAgentOrToolLog}
|
||||
/>
|
||||
|
||||
@@ -59,9 +59,9 @@ export const useLogs = () => {
|
||||
agentOrToolLogItemStackRef.current = []
|
||||
return
|
||||
}
|
||||
const { id, children } = detail
|
||||
const { message_id: id, children } = detail
|
||||
let currentAgentOrToolLogItemStack = agentOrToolLogItemStackRef.current.slice()
|
||||
const index = currentAgentOrToolLogItemStack.findIndex(logItem => logItem.id === id)
|
||||
const index = currentAgentOrToolLogItemStack.findIndex(logItem => logItem.message_id === id)
|
||||
|
||||
if (index > -1)
|
||||
currentAgentOrToolLogItemStack = currentAgentOrToolLogItemStack.slice(0, index + 1)
|
||||
|
||||
@@ -12,19 +12,24 @@ import Loading from '@/app/components/base/loading'
|
||||
import { fetchRunDetail, fetchTracingList } from '@/service/log'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import type { WorkflowRunDetailResponse } from '@/models/log'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
export type RunProps = {
|
||||
hideResult?: boolean
|
||||
activeTab?: 'RESULT' | 'DETAIL' | 'TRACING'
|
||||
runID: string
|
||||
getResultCallback?: (result: WorkflowRunDetailResponse) => void
|
||||
runDetailUrl: string
|
||||
tracingListUrl: string
|
||||
}
|
||||
|
||||
const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getResultCallback }) => {
|
||||
const RunPanel: FC<RunProps> = ({
|
||||
hideResult,
|
||||
activeTab = 'RESULT',
|
||||
getResultCallback,
|
||||
runDetailUrl,
|
||||
tracingListUrl,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const [currentTab, setCurrentTab] = useState<string>(activeTab)
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [runDetail, setRunDetail] = useState<WorkflowRunDetailResponse>()
|
||||
const [list, setList] = useState<NodeTracing[]>([])
|
||||
@@ -37,12 +42,9 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
return 'N/A'
|
||||
}, [runDetail])
|
||||
|
||||
const getResult = useCallback(async (appID: string, runID: string) => {
|
||||
const getResult = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetchRunDetail({
|
||||
appID,
|
||||
runID,
|
||||
})
|
||||
const res = await fetchRunDetail(runDetailUrl)
|
||||
setRunDetail(res)
|
||||
if (getResultCallback)
|
||||
getResultCallback(res)
|
||||
@@ -53,12 +55,12 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
message: `${err}`,
|
||||
})
|
||||
}
|
||||
}, [notify, getResultCallback])
|
||||
}, [notify, getResultCallback, runDetailUrl])
|
||||
|
||||
const getTracingList = useCallback(async (appID: string, runID: string) => {
|
||||
const getTracingList = useCallback(async () => {
|
||||
try {
|
||||
const { data: nodeList } = await fetchTracingList({
|
||||
url: `/apps/${appID}/workflow-runs/${runID}/node-executions`,
|
||||
url: tracingListUrl,
|
||||
})
|
||||
setList(nodeList)
|
||||
}
|
||||
@@ -68,27 +70,27 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
message: `${err}`,
|
||||
})
|
||||
}
|
||||
}, [notify])
|
||||
}, [notify, tracingListUrl])
|
||||
|
||||
const getData = async (appID: string, runID: string) => {
|
||||
const getData = useCallback(async () => {
|
||||
setLoading(true)
|
||||
await getResult(appID, runID)
|
||||
await getTracingList(appID, runID)
|
||||
await getResult()
|
||||
await getTracingList()
|
||||
setLoading(false)
|
||||
}
|
||||
}, [getResult, getTracingList])
|
||||
|
||||
const switchTab = async (tab: string) => {
|
||||
setCurrentTab(tab)
|
||||
if (tab === 'RESULT')
|
||||
appDetail?.id && await getResult(appDetail.id, runID)
|
||||
appDetail?.id && await getTracingList(appDetail.id, runID)
|
||||
runDetailUrl && await getResult()
|
||||
tracingListUrl && await getTracingList()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// fetch data
|
||||
if (appDetail && runID)
|
||||
getData(appDetail.id, runID)
|
||||
}, [appDetail, runID])
|
||||
if (runDetailUrl && tracingListUrl)
|
||||
getData()
|
||||
}, [runDetailUrl, tracingListUrl])
|
||||
|
||||
const [height, setHeight] = useState(0)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
@@ -147,7 +149,10 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
{!loading && currentTab === 'DETAIL' && runDetail && (
|
||||
<ResultPanel
|
||||
inputs={runDetail.inputs}
|
||||
inputs_truncated={runDetail.inputs_truncated}
|
||||
outputs={runDetail.outputs}
|
||||
outputs_truncated={runDetail.outputs_truncated}
|
||||
outputs_full_content={runDetail.outputs_full_content}
|
||||
status={runDetail.status}
|
||||
error={runDetail.error}
|
||||
elapsed_time={runDetail.elapsed_time}
|
||||
|
||||
@@ -30,6 +30,7 @@ import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/err
|
||||
import { hasRetryNode } from '@/app/components/workflow/utils'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import LargeDataAlert from '../variable-inspect/large-data-alert'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -231,6 +232,7 @@ const NodePanel: FC<Props> = ({
|
||||
language={CodeLanguage.json}
|
||||
value={nodeInfo.inputs}
|
||||
isJSONStringifyBeauty
|
||||
footer={nodeInfo.inputs_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -254,6 +256,7 @@ const NodePanel: FC<Props> = ({
|
||||
value={nodeInfo.outputs}
|
||||
isJSONStringifyBeauty
|
||||
tip={<ErrorHandleTip type={nodeInfo.execution_metadata?.error_strategy} />}
|
||||
footer={nodeInfo.outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={nodeInfo.outputs_full_content?.download_url} className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,12 +16,19 @@ import { IterationLogTrigger } from '@/app/components/workflow/run/iteration-log
|
||||
import { LoopLogTrigger } from '@/app/components/workflow/run/loop-log'
|
||||
import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log'
|
||||
import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log'
|
||||
import LargeDataAlert from '../variable-inspect/large-data-alert'
|
||||
|
||||
export type ResultPanelProps = {
|
||||
nodeInfo?: NodeTracing
|
||||
inputs?: string
|
||||
inputs_truncated?: boolean
|
||||
process_data?: string
|
||||
process_data_truncated?: boolean
|
||||
outputs?: string | Record<string, any>
|
||||
outputs_truncated?: boolean
|
||||
outputs_full_content?: {
|
||||
download_url: string
|
||||
}
|
||||
status: string
|
||||
error?: string
|
||||
elapsed_time?: number
|
||||
@@ -42,8 +49,12 @@ export type ResultPanelProps = {
|
||||
const ResultPanel: FC<ResultPanelProps> = ({
|
||||
nodeInfo,
|
||||
inputs,
|
||||
inputs_truncated,
|
||||
process_data,
|
||||
process_data_truncated,
|
||||
outputs,
|
||||
outputs_truncated,
|
||||
outputs_full_content,
|
||||
status,
|
||||
error,
|
||||
elapsed_time,
|
||||
@@ -118,6 +129,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
language={CodeLanguage.json}
|
||||
value={inputs}
|
||||
isJSONStringifyBeauty
|
||||
footer={inputs_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
{process_data && (
|
||||
<CodeEditor
|
||||
@@ -126,6 +138,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
language={CodeLanguage.json}
|
||||
value={process_data}
|
||||
isJSONStringifyBeauty
|
||||
footer={process_data_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
)}
|
||||
{(outputs || status === 'running') && (
|
||||
@@ -136,6 +149,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
value={outputs}
|
||||
isJSONStringifyBeauty
|
||||
tip={<ErrorHandleTip type={execution_metadata?.error_strategy} />}
|
||||
footer={outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={outputs_full_content?.download_url} className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,10 +9,16 @@ const remove = (node: AgentLogItemWithChildren, removeId: string) => {
|
||||
if (!children || children.length === 0)
|
||||
return
|
||||
|
||||
const hasCircle = !!children.find(c => c.id === removeId)
|
||||
const hasCircle = !!children.find((c) => {
|
||||
const childId = c.message_id || (c as any).id
|
||||
return childId === removeId
|
||||
})
|
||||
if (hasCircle) {
|
||||
node.hasCircle = true
|
||||
node.children = node.children.filter(c => c.id !== removeId)
|
||||
node.children = node.children.filter((c) => {
|
||||
const childId = c.message_id || (c as any).id
|
||||
return childId !== removeId
|
||||
})
|
||||
children = node.children
|
||||
}
|
||||
|
||||
@@ -28,9 +34,10 @@ const removeRepeatedSiblings = (list: AgentLogItemWithChildren[]) => {
|
||||
const result: AgentLogItemWithChildren[] = []
|
||||
const addedItemIds: string[] = []
|
||||
list.forEach((item) => {
|
||||
if (!addedItemIds.includes(item.id)) {
|
||||
const itemId = item.message_id || (item as any).id
|
||||
if (itemId && !addedItemIds.includes(itemId)) {
|
||||
result.push(item)
|
||||
addedItemIds.push(item.id)
|
||||
addedItemIds.push(itemId)
|
||||
}
|
||||
})
|
||||
return result
|
||||
@@ -38,16 +45,26 @@ const removeRepeatedSiblings = (list: AgentLogItemWithChildren[]) => {
|
||||
|
||||
const removeCircleLogItem = (log: AgentLogItemWithChildren) => {
|
||||
const newLog = cloneDeep(log)
|
||||
|
||||
// If no children, return as is
|
||||
if (!newLog.children || newLog.children.length === 0)
|
||||
return newLog
|
||||
|
||||
newLog.children = removeRepeatedSiblings(newLog.children)
|
||||
let { id, children } = newLog
|
||||
if (!children || children.length === 0)
|
||||
return log
|
||||
const id = newLog.message_id || (newLog as any).id
|
||||
let { children } = newLog
|
||||
|
||||
// check one step circle
|
||||
const hasOneStepCircle = !!children.find(c => c.id === id)
|
||||
const hasOneStepCircle = !!children.find((c) => {
|
||||
const childId = c.message_id || (c as any).id
|
||||
return childId === id
|
||||
})
|
||||
if (hasOneStepCircle) {
|
||||
newLog.hasCircle = true
|
||||
newLog.children = newLog.children.filter(c => c.id !== id)
|
||||
newLog.children = newLog.children.filter((c) => {
|
||||
const childId = c.message_id || (c as any).id
|
||||
return childId !== id
|
||||
})
|
||||
children = newLog.children
|
||||
}
|
||||
|
||||
@@ -62,21 +79,54 @@ const listToTree = (logs: AgentLogItem[]) => {
|
||||
if (!logs || logs.length === 0)
|
||||
return []
|
||||
|
||||
const tree: AgentLogItemWithChildren[] = []
|
||||
logs.forEach((log) => {
|
||||
const hasParent = !!log.parent_id
|
||||
if (hasParent) {
|
||||
const parent = logs.find(item => item.id === log.parent_id) as AgentLogItemWithChildren
|
||||
if (parent) {
|
||||
if (!parent.children)
|
||||
parent.children = []
|
||||
parent.children.push(log as AgentLogItemWithChildren)
|
||||
}
|
||||
}
|
||||
else {
|
||||
tree.push(log as AgentLogItemWithChildren)
|
||||
// First pass: identify all unique items and track parent-child relationships
|
||||
const itemsById = new Map<string, any>()
|
||||
const childrenById = new Map<string, any[]>()
|
||||
|
||||
logs.forEach((item) => {
|
||||
const itemId = item.message_id || (item as any).id
|
||||
|
||||
// Only add to itemsById if not already there (keep first occurrence)
|
||||
if (itemId && !itemsById.has(itemId))
|
||||
itemsById.set(itemId, item)
|
||||
|
||||
// Initialize children array for this ID if needed
|
||||
if (itemId && !childrenById.has(itemId))
|
||||
childrenById.set(itemId, [])
|
||||
|
||||
// If this item has a parent, add it to parent's children list
|
||||
if (item.parent_id) {
|
||||
if (!childrenById.has(item.parent_id))
|
||||
childrenById.set(item.parent_id, [])
|
||||
|
||||
childrenById.get(item.parent_id)!.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
// Second pass: build tree structure
|
||||
const tree: AgentLogItemWithChildren[] = []
|
||||
|
||||
// Find root nodes (items without parents)
|
||||
itemsById.forEach((item) => {
|
||||
const hasParent = !!item.parent_id
|
||||
if (!hasParent) {
|
||||
const itemId = item.message_id || (item as any).id
|
||||
const children = childrenById.get(itemId)
|
||||
if (children && children.length > 0)
|
||||
item.children = children
|
||||
|
||||
tree.push(item as AgentLogItemWithChildren)
|
||||
}
|
||||
})
|
||||
|
||||
// Add children property to all items that have children
|
||||
itemsById.forEach((item) => {
|
||||
const itemId = item.message_id || (item as any).id
|
||||
const children = childrenById.get(itemId)
|
||||
if (children && children.length > 0)
|
||||
item.children = children
|
||||
})
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user