mirror of
https://github.com/langgenius/dify.git
synced 2026-05-12 15:58:19 +08:00
Merge 6cbe0aefe7 into 19bf36a716
This commit is contained in:
commit
95487d37f9
@ -1524,14 +1524,6 @@
|
|||||||
"count": 2
|
"count": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web/app/components/base/markdown-blocks/think-block.tsx": {
|
|
||||||
"react/set-state-in-effect": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"ts/no-explicit-any": {
|
|
||||||
"count": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"web/app/components/base/markdown-blocks/video-block.tsx": {
|
"web/app/components/base/markdown-blocks/video-block.tsx": {
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 5
|
"count": 5
|
||||||
|
|||||||
@ -163,6 +163,59 @@ describe('ThinkBlock', () => {
|
|||||||
expect(screen.getByText(/Thought/)).toBeInTheDocument()
|
expect(screen.getByText(/Thought/)).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('preserves the final elapsed time when completion is detected after timers were paused', () => {
|
||||||
|
const startTime = new Date('2026-05-08T00:00:00.000Z')
|
||||||
|
vi.setSystemTime(startTime)
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<ChatContextProvider
|
||||||
|
config={undefined}
|
||||||
|
isResponding={true}
|
||||||
|
chatList={[]}
|
||||||
|
showPromptLog={false}
|
||||||
|
questionIcon={undefined}
|
||||||
|
answerIcon={undefined}
|
||||||
|
onSend={undefined}
|
||||||
|
onRegenerate={undefined}
|
||||||
|
onAnnotationEdited={undefined}
|
||||||
|
onAnnotationAdded={undefined}
|
||||||
|
onAnnotationRemoved={undefined}
|
||||||
|
onFeedback={undefined}
|
||||||
|
>
|
||||||
|
<ThinkBlock data-think={true}>
|
||||||
|
<p>Thinking content</p>
|
||||||
|
</ThinkBlock>
|
||||||
|
</ChatContextProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
vi.setSystemTime(new Date(startTime.getTime() + 1500))
|
||||||
|
})
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<ChatContextProvider
|
||||||
|
config={undefined}
|
||||||
|
isResponding={true}
|
||||||
|
chatList={[]}
|
||||||
|
showPromptLog={false}
|
||||||
|
questionIcon={undefined}
|
||||||
|
answerIcon={undefined}
|
||||||
|
onSend={undefined}
|
||||||
|
onRegenerate={undefined}
|
||||||
|
onAnnotationEdited={undefined}
|
||||||
|
onAnnotationAdded={undefined}
|
||||||
|
onAnnotationRemoved={undefined}
|
||||||
|
onFeedback={undefined}
|
||||||
|
>
|
||||||
|
<ThinkBlock data-think={true}>
|
||||||
|
<p>Thinking content[ENDTHINKFLAG]</p>
|
||||||
|
</ThinkBlock>
|
||||||
|
</ChatContextProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByText(/Thought\(1\.5s\)/)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
it('should stop timer when isResponding is undefined (historical conversation outside active response)', () => {
|
it('should stop timer when isResponding is undefined (historical conversation outside active response)', () => {
|
||||||
// Render without ChatContextProvider — simulates historical conversation
|
// Render without ChatContextProvider — simulates historical conversation
|
||||||
render(
|
render(
|
||||||
|
|||||||
@ -4,53 +4,61 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useChatContext } from '../chat/chat/context'
|
import { useChatContext } from '../chat/chat/context'
|
||||||
|
|
||||||
const hasEndThink = (children: any): boolean => {
|
const hasEndThink = (children: React.ReactNode): boolean => {
|
||||||
if (typeof children === 'string')
|
if (typeof children === 'string')
|
||||||
return children.includes('[ENDTHINKFLAG]')
|
return children.includes('[ENDTHINKFLAG]')
|
||||||
|
|
||||||
if (Array.isArray(children))
|
if (Array.isArray(children))
|
||||||
return children.some(child => hasEndThink(child))
|
return children.some(child => hasEndThink(child))
|
||||||
|
|
||||||
if (children?.props?.children)
|
if (React.isValidElement<{ children?: React.ReactNode }>(children) && children.props.children)
|
||||||
return hasEndThink(children.props.children)
|
return hasEndThink(children.props.children)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeEndThink = (children: any): any => {
|
const removeEndThink = (children: React.ReactNode): React.ReactNode => {
|
||||||
if (typeof children === 'string')
|
if (typeof children === 'string')
|
||||||
return children.replace('[ENDTHINKFLAG]', '')
|
return children.replace('[ENDTHINKFLAG]', '')
|
||||||
|
|
||||||
if (Array.isArray(children))
|
if (Array.isArray(children))
|
||||||
return children.map(child => removeEndThink(child))
|
return children.map(child => removeEndThink(child))
|
||||||
|
|
||||||
if (children?.props?.children) {
|
if (React.isValidElement<{ children?: React.ReactNode }>(children) && children.props.children) {
|
||||||
return React.cloneElement(
|
return React.cloneElement(
|
||||||
children,
|
children,
|
||||||
{
|
undefined,
|
||||||
...children.props,
|
removeEndThink(children.props.children),
|
||||||
children: removeEndThink(children.props.children),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
|
|
||||||
const useThinkTimer = (children: any) => {
|
const getElapsedSeconds = (startTime: number, endTime: number) => {
|
||||||
|
return Math.floor((endTime - startTime) / 100) / 10
|
||||||
|
}
|
||||||
|
|
||||||
|
const useThinkTimer = (children: React.ReactNode) => {
|
||||||
const { isResponding } = useChatContext()
|
const { isResponding } = useChatContext()
|
||||||
const endThinkDetected = hasEndThink(children)
|
const endThinkDetected = hasEndThink(children)
|
||||||
const [startTime] = useState(() => Date.now())
|
const [startTime] = useState(() => Date.now())
|
||||||
const [elapsedTime, setElapsedTime] = useState(0)
|
const [elapsedTime, setElapsedTime] = useState(0)
|
||||||
const [isComplete, setIsComplete] = useState(() => endThinkDetected)
|
|
||||||
const timerRef = useRef<NodeJS.Timeout | null>(null)
|
const timerRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
const finalElapsedTimeRef = useRef<number | null>(null)
|
||||||
|
const completionDetected = endThinkDetected || !isResponding
|
||||||
|
|
||||||
|
if (completionDetected && finalElapsedTimeRef.current === null)
|
||||||
|
finalElapsedTimeRef.current = getElapsedSeconds(startTime, Date.now())
|
||||||
|
|
||||||
|
const isComplete = finalElapsedTimeRef.current !== null
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isComplete)
|
if (isComplete)
|
||||||
return
|
return
|
||||||
|
|
||||||
timerRef.current = setInterval(() => {
|
timerRef.current = setInterval(() => {
|
||||||
setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10)
|
setElapsedTime(getElapsedSeconds(startTime, Date.now()))
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -59,15 +67,12 @@ const useThinkTimer = (children: any) => {
|
|||||||
}
|
}
|
||||||
}, [startTime, isComplete])
|
}, [startTime, isComplete])
|
||||||
|
|
||||||
useEffect(() => {
|
return {
|
||||||
// Stop timer when:
|
elapsedTime: finalElapsedTimeRef.current === null
|
||||||
// 1. Content has [ENDTHINKFLAG] marker (normal completion)
|
? elapsedTime
|
||||||
// 2. isResponding is not true (false = user clicked stop, undefined = historical conversation)
|
: Math.max(elapsedTime, finalElapsedTimeRef.current),
|
||||||
if (endThinkDetected || !isResponding)
|
isComplete,
|
||||||
setIsComplete(true)
|
}
|
||||||
}, [endThinkDetected, isResponding])
|
|
||||||
|
|
||||||
return { elapsedTime, isComplete }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ThinkBlockProps = React.ComponentProps<'details'> & {
|
type ThinkBlockProps = React.ComponentProps<'details'> & {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user