chat add workflow process

This commit is contained in:
StyleZhang 2024-03-20 15:44:24 +08:00
parent 38a1ea139a
commit 4bef2eed25
8 changed files with 154 additions and 8 deletions

View File

@ -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'>

View File

@ -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

View File

@ -52,7 +52,7 @@ export type ChatConfig = Omit<ModelConfig, 'model'> & {
export type WorkflowProcess = {
status: WorkflowRunningStatus
nodes: NodeTracing[]
tracing: NodeTracing[]
}
export type ChatItem = IChatItem & {

View File

@ -164,6 +164,7 @@ export const useWorkflowRun = () => {
draft.result = {
...draft?.result,
...data,
status: WorkflowRunningStatus.Running,
}
}))

View File

@ -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,
}
}))
},
},
)

View File

@ -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(

View File

@ -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',

View File

@ -28,6 +28,7 @@ const translation = {
setVarValuePlaceholder: '设置变量值',
needConnecttip: '此节点尚未连接到其他节点',
maxTreeDepth: '每个分支最大限制 {{depth}} 个节点',
workflowProcess: '工作流',
},
errorMsg: {
fieldRequired: '{{field}} 不能为空',