mirror of https://github.com/langgenius/dify.git
chat add workflow process
This commit is contained in:
parent
38a1ea139a
commit
4bef2eed25
|
|
@ -13,6 +13,7 @@ import AgentContent from './agent-content'
|
|||
import BasicContent from './basic-content'
|
||||
import SuggestedQuestions from './suggested-questions'
|
||||
import More from './more'
|
||||
import WorkflowProcess from './workflow-process'
|
||||
import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import LoadingAnim from '@/app/components/app/chat/loading-anim'
|
||||
import Citation from '@/app/components/app/chat/citation'
|
||||
|
|
@ -46,6 +47,7 @@ const Answer: FC<AnswerProps> = ({
|
|||
agent_thoughts,
|
||||
more,
|
||||
annotation,
|
||||
workflowProcess,
|
||||
} = item
|
||||
const hasAgentThoughts = !!agent_thoughts?.length
|
||||
|
||||
|
|
@ -70,7 +72,12 @@ const Answer: FC<AnswerProps> = ({
|
|||
<div className='chat-answer-container grow w-0 group ml-4'>
|
||||
<div className='relative pr-10'>
|
||||
<AnswerTriangle className='absolute -left-2 top-0 w-2 h-3 text-gray-100' />
|
||||
<div className='group relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900'>
|
||||
<div
|
||||
className={`
|
||||
group relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900
|
||||
${workflowProcess && 'w-full'}
|
||||
`}
|
||||
>
|
||||
{
|
||||
!responding && (
|
||||
<Operation
|
||||
|
|
@ -81,6 +88,11 @@ const Answer: FC<AnswerProps> = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
workflowProcess && (
|
||||
<WorkflowProcess data={workflowProcess} />
|
||||
)
|
||||
}
|
||||
{
|
||||
responding && !content && !hasAgentThoughts && (
|
||||
<div className='flex items-center justify-center w-6 h-5'>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { WorkflowProcess } from '../../types'
|
||||
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import NodePanel from '@/app/components/workflow/run/node'
|
||||
|
||||
type WorkflowProcessProps = {
|
||||
data: WorkflowProcess
|
||||
}
|
||||
const WorkflowProcessItem = ({
|
||||
data,
|
||||
}: WorkflowProcessProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [collapse, setCollapse] = useState(true)
|
||||
const running = data.status === WorkflowRunningStatus.Running
|
||||
const succeeded = data.status === WorkflowRunningStatus.Succeeded
|
||||
const failed = data.status === WorkflowRunningStatus.Failed || data.status === WorkflowRunningStatus.Stopped
|
||||
|
||||
const background = useMemo(() => {
|
||||
if (running && !collapse)
|
||||
return 'linear-gradient(180deg, #E1E4EA 0%, #EAECF0 100%)'
|
||||
|
||||
if (succeeded && !collapse)
|
||||
return 'linear-gradient(180deg, #ECFDF3 0%, #F6FEF9 100%)'
|
||||
|
||||
if (failed && !collapse)
|
||||
return 'linear-gradient(180deg, #FEE4E2 0%, #FEF3F2 100%)'
|
||||
}, [running, succeeded, failed, collapse])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
px-3 w-full rounded-xl border-[0.5px] border-black/[0.08]
|
||||
${collapse ? 'py-[7px]' : 'py-2'}
|
||||
${collapse && 'bg-white'}
|
||||
`}
|
||||
style={{
|
||||
background,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='flex items-center h-[18px] cursor-pointer'
|
||||
onClick={() => setCollapse(!collapse)}
|
||||
>
|
||||
{
|
||||
running && (
|
||||
<Loading02 className='shrink-0 mr-1 w-3 h-3 text-[#667085] animate-spin' />
|
||||
)
|
||||
}
|
||||
{
|
||||
succeeded && (
|
||||
<CheckCircle className='shrink-0 mr-1 w-3 h-3 text-[#12B76A]' />
|
||||
)
|
||||
}
|
||||
{
|
||||
failed && (
|
||||
<AlertCircle className='shrink-0 mr-1 w-3 h-3 text-[#F04438]' />
|
||||
)
|
||||
}
|
||||
<div className='grow text-xs font-medium text-gray-700'>
|
||||
{t('workflow.common.workflowProcess')}
|
||||
</div>
|
||||
<ChevronRight className='ml-1 w-3 h-3 text-gray-500' />
|
||||
</div>
|
||||
{
|
||||
!collapse && (
|
||||
<div className='mt-1.5'>
|
||||
{
|
||||
data.tracing.map(node => (
|
||||
<div key={node.id} className='mb-1 last-of-type:mb-0'>
|
||||
<NodePanel
|
||||
className='!p-0'
|
||||
nodeInfo={node}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowProcessItem
|
||||
|
|
@ -52,7 +52,7 @@ export type ChatConfig = Omit<ModelConfig, 'model'> & {
|
|||
|
||||
export type WorkflowProcess = {
|
||||
status: WorkflowRunningStatus
|
||||
nodes: NodeTracing[]
|
||||
tracing: NodeTracing[]
|
||||
}
|
||||
|
||||
export type ChatItem = IChatItem & {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ export const useWorkflowRun = () => {
|
|||
draft.result = {
|
||||
...draft?.result,
|
||||
...data,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { produce, setAutoFreeze } from 'immer'
|
||||
import { useWorkflowRun } from '../../hooks'
|
||||
import { WorkflowRunningStatus } from '../../types'
|
||||
import type {
|
||||
ChatItem,
|
||||
Inputs,
|
||||
|
|
@ -261,13 +262,50 @@ export const useChat = (
|
|||
})
|
||||
handleUpdateChatList(newChatList)
|
||||
},
|
||||
onWorkflowStarted: () => {
|
||||
onWorkflowStarted: ({ workflow_run_id }) => {
|
||||
responseItem.workflow_run_id = workflow_run_id
|
||||
responseItem.workflowProcess = {
|
||||
status: WorkflowRunningStatus.Running,
|
||||
tracing: [],
|
||||
}
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...responseItem,
|
||||
}
|
||||
}))
|
||||
},
|
||||
onWorkflowFinished: () => {
|
||||
onWorkflowFinished: ({ data }) => {
|
||||
responseItem.workflowProcess!.status = data.status as WorkflowRunningStatus
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...responseItem,
|
||||
}
|
||||
}))
|
||||
},
|
||||
onNodeStarted: () => {
|
||||
onNodeStarted: ({ data }) => {
|
||||
responseItem.workflowProcess!.tracing!.push(data as any)
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...responseItem,
|
||||
}
|
||||
}))
|
||||
},
|
||||
onNodeFinished: () => {
|
||||
onNodeFinished: ({ data }) => {
|
||||
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
responseItem.workflowProcess!.tracing[currentIndex] = data as any
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...responseItem,
|
||||
}
|
||||
}))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import type { NodeTracing } from '@/types/workflow'
|
|||
type Props = {
|
||||
nodeInfo: NodeTracing
|
||||
collapsed?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const NodePanel: FC<Props> = ({ nodeInfo, collapsed = true }) => {
|
||||
const NodePanel: FC<Props> = ({ nodeInfo, collapsed = true, className }) => {
|
||||
const [collapseState, setCollapseState] = useState<boolean>(collapsed)
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ const NodePanel: FC<Props> = ({ nodeInfo, collapsed = true }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='px-4 py-1'>
|
||||
<div className={`px-4 py-1 ${className}`}>
|
||||
<div className='group transition-all bg-white border border-gray-100 rounded-2xl shadow-xs hover:shadow-md'>
|
||||
<div
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const translation = {
|
|||
setVarValuePlaceholder: 'Set variable',
|
||||
needConnecttip: 'This step is not connected to anything',
|
||||
maxTreeDepth: 'Maximum limit of {{depth}} nodes per branch',
|
||||
workflowProcess: 'Workflow Process',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} is required',
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const translation = {
|
|||
setVarValuePlaceholder: '设置变量值',
|
||||
needConnecttip: '此节点尚未连接到其他节点',
|
||||
maxTreeDepth: '每个分支最大限制 {{depth}} 个节点',
|
||||
workflowProcess: '工作流',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} 不能为空',
|
||||
|
|
|
|||
Loading…
Reference in New Issue