mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-23 09:46:53 +08:00
Chore: chat log refactor (#5523)
This commit is contained in:
69
web/app/components/base/chat/chat/thought/index.tsx
Normal file
69
web/app/components/base/chat/chat/thought/index.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import type { ThoughtItem, ToolInfoInThought } from '../type'
|
||||
import Tool from '@/app/components/base/chat/chat/thought/tool'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
|
||||
export type IThoughtProps = {
|
||||
thought: ThoughtItem
|
||||
allToolIcons: Record<string, string | Emoji>
|
||||
isFinished: boolean
|
||||
}
|
||||
|
||||
function getValue(value: string, isValueArray: boolean, index: number) {
|
||||
if (isValueArray) {
|
||||
try {
|
||||
return JSON.parse(value)[index]
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const Thought: FC<IThoughtProps> = ({
|
||||
thought,
|
||||
allToolIcons,
|
||||
isFinished,
|
||||
}) => {
|
||||
const { locale } = useContext(I18n)
|
||||
const language = getLanguage(locale)
|
||||
|
||||
const [toolNames, isValueArray]: [string[], boolean] = (() => {
|
||||
try {
|
||||
if (Array.isArray(JSON.parse(thought.tool)))
|
||||
return [JSON.parse(thought.tool), true]
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return [[thought.tool], false]
|
||||
})()
|
||||
|
||||
const toolThoughtList = toolNames.map((toolName, index) => {
|
||||
return {
|
||||
name: toolName,
|
||||
label: thought.tool_labels?.toolName?.language ?? toolName,
|
||||
input: getValue(thought.tool_input, isValueArray, index),
|
||||
output: getValue(thought.observation, isValueArray, index),
|
||||
isFinished,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='my-2 space-y-2'>
|
||||
{toolThoughtList.map((item: ToolInfoInThought, index) => (
|
||||
<Tool
|
||||
key={index}
|
||||
payload={item}
|
||||
allToolIcons={allToolIcons}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Thought)
|
||||
28
web/app/components/base/chat/chat/thought/panel.tsx
Normal file
28
web/app/components/base/chat/chat/thought/panel.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
isRequest: boolean
|
||||
toolName: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const Panel: FC<Props> = ({
|
||||
isRequest,
|
||||
toolName,
|
||||
content,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='rounded-md bg-gray-100 overflow-hidden border border-black/5'>
|
||||
<div className='flex items-center px-2 py-1 leading-[18px] bg-gray-50 uppercase text-xs font-medium text-gray-500'>
|
||||
{t(`tools.thought.${isRequest ? 'requestTitle' : 'responseTitle'}`)} {toolName}
|
||||
</div>
|
||||
<div className='p-2 border-t border-black/5 leading-4 text-xs text-gray-700'>{content}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Panel)
|
||||
106
web/app/components/base/chat/chat/thought/tool.tsx
Normal file
106
web/app/components/base/chat/chat/thought/tool.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import cn from 'classnames'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type { ToolInfoInThought } from '../type'
|
||||
import Panel from './panel'
|
||||
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
|
||||
type Props = {
|
||||
payload: ToolInfoInThought
|
||||
allToolIcons?: Record<string, string | Emoji>
|
||||
}
|
||||
|
||||
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
|
||||
if (toolName.startsWith('dataset_'))
|
||||
return <DataSetIcon className='shrink-0'></DataSetIcon>
|
||||
const icon = allToolIcons[toolName]
|
||||
if (!icon)
|
||||
return null
|
||||
return (
|
||||
typeof icon === 'string'
|
||||
? (
|
||||
<div
|
||||
className='w-3 h-3 bg-cover bg-center rounded-[3px] shrink-0'
|
||||
style={{
|
||||
backgroundImage: `url(${icon})`,
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
: (
|
||||
<AppIcon
|
||||
className='rounded-[3px] shrink-0'
|
||||
size='xs'
|
||||
icon={icon?.content}
|
||||
background={icon?.background}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
const Tool: FC<Props> = ({
|
||||
payload,
|
||||
allToolIcons = {},
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { name, label, input, isFinished, output } = payload
|
||||
const toolName = name.startsWith('dataset_') ? t('dataset.knowledge') : name
|
||||
const toolLabel = name.startsWith('dataset_') ? t('dataset.knowledge') : label
|
||||
const [isShowDetail, setIsShowDetail] = useState(false)
|
||||
const icon = getIcon(name, allToolIcons) as any
|
||||
return (
|
||||
<div>
|
||||
<div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto bg-white rounded-md')}>
|
||||
<div
|
||||
className={cn('flex items-center h-7 px-2 cursor-pointer')}
|
||||
onClick={() => setIsShowDetail(!isShowDetail)}
|
||||
>
|
||||
{!isFinished && (
|
||||
<RiLoader2Line className='w-3 h-3 text-gray-500 animate-spin shrink-0' />
|
||||
)}
|
||||
{isFinished && !isShowDetail && (
|
||||
<CheckCircle className='w-3 h-3 text-[#12B76A] shrink-0' />
|
||||
)}
|
||||
{isFinished && isShowDetail && (
|
||||
icon
|
||||
)}
|
||||
<span className='mx-1 text-xs font-medium text-gray-500 shrink-0'>
|
||||
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
|
||||
</span>
|
||||
<span
|
||||
className='text-xs font-medium text-gray-700 truncate'
|
||||
title={toolLabel}
|
||||
>
|
||||
{toolLabel}
|
||||
</span>
|
||||
<RiArrowDownSLine
|
||||
className={cn(isShowDetail && 'rotate-180', 'ml-1 w-3 h-3 text-gray-500 select-none cursor-pointer shrink-0')}
|
||||
/>
|
||||
</div>
|
||||
{isShowDetail && (
|
||||
<div className='border-t border-black/5 p-2 space-y-2 '>
|
||||
<Panel
|
||||
isRequest={true}
|
||||
toolName={toolName}
|
||||
content={input} />
|
||||
{output && (
|
||||
<Panel
|
||||
isRequest={false}
|
||||
toolName={toolName}
|
||||
content={output as string} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Tool)
|
||||
Reference in New Issue
Block a user