Commit c33d7a14 authored by 金伟强's avatar 金伟强

feat: add loading effect

parent 93d7adf2
...@@ -44,9 +44,10 @@ export type IAnswerProps = { ...@@ -44,9 +44,10 @@ export type IAnswerProps = {
isResponsing?: boolean isResponsing?: boolean
answerIconClassName?: string answerIconClassName?: string
thoughts?: ThoughtItem[] thoughts?: ThoughtItem[]
isThinking?: boolean
} }
// The component needs to maintain its own state to control whether to display input component // The component needs to maintain its own state to control whether to display input component
const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName, thoughts }) => { const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName, thoughts, isThinking }) => {
const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item
const [showEdit, setShowEdit] = useState(false) const [showEdit, setShowEdit] = useState(false)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
...@@ -204,7 +205,7 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba ...@@ -204,7 +205,7 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
: ( : (
<div> <div>
{(thoughts && thoughts.length > 0) && ( {(thoughts && thoughts.length > 0) && (
<Thought list={thoughts || []}/> <Thought list={thoughts || []} isThinking={isThinking} />
)} )}
<Markdown content={content} /> <Markdown content={content} />
</div> </div>
......
...@@ -47,6 +47,7 @@ export type IChatProps = { ...@@ -47,6 +47,7 @@ export type IChatProps = {
isShowSpeechToText?: boolean isShowSpeechToText?: boolean
answerIconClassName?: string answerIconClassName?: string
isShowConfigElem?: boolean isShowConfigElem?: boolean
isThoughting?: boolean
} }
const Chat: FC<IChatProps> = ({ const Chat: FC<IChatProps> = ({
...@@ -156,6 +157,7 @@ const Chat: FC<IChatProps> = ({ ...@@ -156,6 +157,7 @@ const Chat: FC<IChatProps> = ({
if (item.isAnswer) { if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id const isLast = item.id === chatList[chatList.length - 1].id
const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]') const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]')
const isThinking = item.agent_thoughts && item.agent_thoughts?.length > 0 && !item.agent_thoughts.find(item => item.thought !== '[DONE]')
return <Answer return <Answer
key={item.id} key={item.id}
item={item} item={item}
...@@ -167,6 +169,7 @@ const Chat: FC<IChatProps> = ({ ...@@ -167,6 +169,7 @@ const Chat: FC<IChatProps> = ({
isResponsing={isResponsing && isLast} isResponsing={isResponsing && isLast}
answerIconClassName={answerIconClassName} answerIconClassName={answerIconClassName}
thoughts={thoughts} thoughts={thoughts}
isThinking={isThinking}
/> />
} }
return <Question key={item.id} id={item.id} content={item.content} more={item.more} useCurrentUserAvatar={useCurrentUserAvatar} /> return <Question key={item.id} id={item.id} content={item.content} more={item.more} useCurrentUserAvatar={useCurrentUserAvatar} />
......
...@@ -5,13 +5,14 @@ import cn from 'classnames' ...@@ -5,13 +5,14 @@ import cn from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { ThoughtItem } from '../type' import type { ThoughtItem } from '../type'
import s from './style.module.css' import s from './style.module.css'
import { DataSet, Search, ThoughtList, WebReader } from '@/app/components/base/icons/src/public/thought' import { DataSet, Loading as LodingIcon, Search, ThoughtList, WebReader } from '@/app/components/base/icons/src/public/thought'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows' import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
// https://www.freecodecamp.org/news/how-to-write-a-regular-expression-for-a-url/ // https://www.freecodecamp.org/news/how-to-write-a-regular-expression-for-a-url/
const urlRegex = /(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?\/[a-zA-Z0-9]{2,}|((https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?/gi const urlRegex = /(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?\/[a-zA-Z0-9]{2,}|((https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?/gi
export type IThoughtProps = { export type IThoughtProps = {
list: ThoughtItem[] list: ThoughtItem[]
isThinking?: boolean
} }
const getIcon = (toolId: string) => { const getIcon = (toolId: string) => {
...@@ -27,6 +28,7 @@ const getIcon = (toolId: string) => { ...@@ -27,6 +28,7 @@ const getIcon = (toolId: string) => {
const Thought: FC<IThoughtProps> = ({ const Thought: FC<IThoughtProps> = ({
list, list,
isThinking,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isShowDetail, setIsShowDetail] = React.useState(false) const [isShowDetail, setIsShowDetail] = React.useState(false)
...@@ -62,8 +64,11 @@ const Thought: FC<IThoughtProps> = ({ ...@@ -62,8 +64,11 @@ const Thought: FC<IThoughtProps> = ({
return ( return (
<div className={cn(s.wrap, !isShowDetail && s.wrapHoverEffect, 'inline-block mb-2 px-2 py-0.5 rounded-md text-xs text-gray-500 font-medium')} > <div className={cn(s.wrap, !isShowDetail && s.wrapHoverEffect, 'inline-block mb-2 px-2 py-0.5 rounded-md text-xs text-gray-500 font-medium')} >
<div className='flex items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsShowDetail(!isShowDetail)} > <div className='flex items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsShowDetail(!isShowDetail)} >
<ThoughtList /> {!isThinking ? <ThoughtList /> : <div className='animate-spin'><LodingIcon /></div>}
<div>{t(`explore.universalChat.thought.${isShowDetail ? 'hide' : 'show'}`)}{t('explore.universalChat.thought.processOfThought')}</div> <div dangerouslySetInnerHTML= {{
__html: isThinking ? getThoughtText(list[0]) : (t(`explore.universalChat.thought.${isShowDetail ? 'hide' : 'show'}`) + t('explore.universalChat.thought.processOfThought')),
}}
></div>
<ChevronDown className={isShowDetail ? 'rotate-180' : '' } /> <ChevronDown className={isShowDetail ? 'rotate-180' : '' } />
</div> </div>
{isShowDetail && ( {isShowDetail && (
......
...@@ -21,6 +21,7 @@ export type ThoughtItem = { ...@@ -21,6 +21,7 @@ export type ThoughtItem = {
tool: string // plugin or dataset tool: string // plugin or dataset
thought: string thought: string
tool_input: string tool_input: string
message_id: string
} }
export type IChatItem = { export type IChatItem = {
id: string id: string
......
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7998_4025)">
<path d="M6 1.125V2.375M6 9V11M2.875 6H1.125M10.625 6H9.875M9.22855 9.22855L8.875 8.875M9.33211 2.70789L8.625 3.415M2.46079 9.53921L3.875 8.125M2.56434 2.60434L3.625 3.665" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_7998_4025">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_7998_4025)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6 1.125V2.375M6 9V11M2.875 6H1.125M10.625 6H9.875M9.22855 9.22855L8.875 8.875M9.33211 2.70789L8.625 3.415M2.46079 9.53921L3.875 8.125M2.56434 2.60434L3.625 3.665",
"stroke": "#667085",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_7998_4025"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "12",
"height": "12",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "Loading"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Loading.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
export default Icon
export { default as DataSet } from './DataSet' export { default as DataSet } from './DataSet'
export { default as Loading } from './Loading'
export { default as Search } from './Search' export { default as Search } from './Search'
export { default as ThoughtList } from './ThoughtList' export { default as ThoughtList } from './ThoughtList'
export { default as WebReader } from './WebReader' export { default as WebReader } from './WebReader'
...@@ -480,7 +480,7 @@ const Main: FC<IMainProps> = () => { ...@@ -480,7 +480,7 @@ const Main: FC<IMainProps> = () => {
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => { (draft) => {
if (!draft.find(item => item.id === questionId)) if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem }) draft.push({ ...questionItem } as any)
draft.push({ ...responseItem }) draft.push({ ...responseItem })
}) })
...@@ -508,6 +508,7 @@ const Main: FC<IMainProps> = () => { ...@@ -508,6 +508,7 @@ const Main: FC<IMainProps> = () => {
onThought(thought) { onThought(thought) {
if (thought.thought === '[DONE]') if (thought.thought === '[DONE]')
return return
responseItem.id = thought.message_id;
// thought finished then start to return message // thought finished then start to return message
(responseItem as any).agent_thoughts.push(thought) (responseItem as any).agent_thoughts.push(thought)
const newListWithAnswer = produce( const newListWithAnswer = produce(
...@@ -515,9 +516,9 @@ const Main: FC<IMainProps> = () => { ...@@ -515,9 +516,9 @@ const Main: FC<IMainProps> = () => {
(draft) => { (draft) => {
if (!draft.find(item => item.id === questionId)) if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem }) draft.push({ ...questionItem })
draft.push({ ...responseItem }) draft.push({ ...responseItem })
}) })
// console.log('start render thought')
setChatList(newListWithAnswer) setChatList(newListWithAnswer)
}, },
onError() { onError() {
......
/* eslint-disable no-new, prefer-promise-reject-errors */ /* eslint-disable no-new, prefer-promise-reject-errors */
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config' import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import type { ThoughtItem } from '@/app/components/app/chat/type'
const TIME_OUT = 100000 const TIME_OUT = 100000
...@@ -30,7 +31,7 @@ export type IOnDataMoreInfo = { ...@@ -30,7 +31,7 @@ export type IOnDataMoreInfo = {
} }
export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void
export type IOnThought = (though: { id: string; tool: string; thought: string }) => void export type IOnThought = (though: ThoughtItem) => void
export type IOnCompleted = (hasError?: boolean) => void export type IOnCompleted = (hasError?: boolean) => void
export type IOnError = (msg: string) => void export type IOnError = (msg: string) => void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment