fix: 思考块默认状态对齐 ZCode——流式收起、完成后展开全文
This commit is contained in:
@@ -217,11 +217,13 @@ export function AssistantTurn({
|
||||
if (seg.kind === 'thinking') {
|
||||
const rows = thinkingRows(seg.text)
|
||||
if (rows.length === 0) return
|
||||
// 该思考段之后已有正文段在流:思考已完成,表头从「正在思考」切回
|
||||
// 只有「最后一段且之后没有正文」才算仍在流式;否则一旦 pi 在回合内
|
||||
// 分多轮发出(静默间隙 streaming 抖动),已完成段会被误判回流式态
|
||||
const isLast = i === segments.length - 1
|
||||
const followedByText = segments.some((s, k) => k > i && s.kind === 'text' && s.text.length > 0)
|
||||
streamNodes.push(
|
||||
<div key={`seg-${i}`} className={collapsed && hasProcess ? 'hidden' : 'mb-3'}>
|
||||
<Thinking rows={rows} streaming={streaming && !followedByText} />
|
||||
<Thinking rows={rows} streaming={streaming && isLast && !followedByText} />
|
||||
</div>
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
/** 可展开的思考轨迹(ZCode 式实时预览)。
|
||||
* 流式收起时表头显示「正在思考 · 最新一行」持续刷新,不展示全部;
|
||||
* 展开时完整展示全部思考(不截断),新内容在尾部、由会话级自动滚动跟随,
|
||||
* 用户上滚即取消跟随(外层滚动已实现)。结束后收起为「思考过程」,
|
||||
* 流式中默认收起:表头「正在思考 · 最新一行」持续刷新,不展示全部;
|
||||
* 手动展开时限高滚动预览——新内容在尾部并自动吸底,用户上滚即取消吸底
|
||||
* (滚回底部恢复)。完成后默认展开完整展示全部思考,表头「思考 · 首行」。
|
||||
* 点击表头可手动展开/收起。rows 由真实思考文本逐行喂入。 */
|
||||
export function Thinking({
|
||||
rows,
|
||||
@@ -17,16 +17,37 @@ export function Thinking({
|
||||
activeLabel?: string
|
||||
doneLabel?: string
|
||||
}): ReactNode {
|
||||
// 流式中默认收起,完成后默认展开;用户点击后以手动状态为准
|
||||
const [manualExpanded, setManualExpanded] = useState<boolean | null>(null)
|
||||
const expanded = manualExpanded ?? streaming
|
||||
const expanded = manualExpanded ?? !streaming
|
||||
|
||||
// 流式轨迹的行必须「随到随显」:fade-up 的 both+delay 会让新行在延迟期间
|
||||
// 透明但占高,流式期间底部因此持续留出一大片空白。交错入场只保留给
|
||||
// 非流式一次性渲染的静态轨迹(挂载时决定,之后不变更,避免整段重播动画)。
|
||||
const streamedMountRef = useRef(streaming)
|
||||
|
||||
// 流式展开时的滚动吸底:新内容到达且当前贴着底部时跟随;用户上滚即取消,
|
||||
// 滚回底部恢复。收起时重置吸底,重新展开直接落在最新内容。完成后不限高。
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
const pinnedRef = useRef(true)
|
||||
const handleScroll = (): void => {
|
||||
const el = scrollRef.current
|
||||
if (!el) return
|
||||
pinnedRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 24
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!expanded) {
|
||||
pinnedRef.current = true
|
||||
return
|
||||
}
|
||||
const el = scrollRef.current
|
||||
if (el && pinnedRef.current && streaming) el.scrollTop = el.scrollHeight
|
||||
}, [rows, expanded, streaming])
|
||||
|
||||
const latest = rows.length > 0 ? rows[rows.length - 1].primary : ''
|
||||
const suffix = streaming && !expanded ? latest : ''
|
||||
const firstLine = rows.length > 0 ? rows[0].primary : ''
|
||||
const statusWord = streaming ? activeLabel : expanded ? '思考' : doneLabel
|
||||
const suffix = streaming ? (expanded ? '' : latest) : expanded ? firstLine : ''
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-95 flex-col">
|
||||
@@ -50,14 +71,14 @@ export function Thinking({
|
||||
animation: 'shimmer-text 1.4s linear infinite'
|
||||
}}
|
||||
>
|
||||
{activeLabel}
|
||||
{statusWord}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className="whitespace-nowrap text-[13px] font-medium text-ink-2"
|
||||
style={{ animation: 'fade-in 350ms ease-out both' }}
|
||||
>
|
||||
{doneLabel}
|
||||
{statusWord}
|
||||
</span>
|
||||
)}
|
||||
{suffix && (
|
||||
@@ -80,7 +101,7 @@ export function Thinking({
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* 展开轨迹:完整展示全部思考,左侧 hairline 随内容延伸 */}
|
||||
{/* 展开轨迹:流式时限高滚动预览(吸底跟随),完成后完整展示全部思考 */}
|
||||
<div
|
||||
className="grid transition-[grid-template-rows,opacity]"
|
||||
style={{
|
||||
@@ -90,7 +111,12 @@ export function Thinking({
|
||||
}}
|
||||
>
|
||||
<div className="min-h-0 overflow-hidden">
|
||||
<div className="relative mt-1 ml-[5px] pl-4">
|
||||
<div
|
||||
ref={scrollRef}
|
||||
onScroll={handleScroll}
|
||||
className={`mt-1 ml-[5px] pl-4 ${streaming ? 'max-h-[220px] overflow-y-auto' : ''}`}
|
||||
>
|
||||
<div className="relative">
|
||||
<span aria-hidden className="absolute bottom-0 left-[3px] top-0 w-px bg-line" />
|
||||
<div className="flex flex-col gap-1 py-1">
|
||||
{rows.map((row, i) => (
|
||||
@@ -113,6 +139,7 @@ export function Thinking({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user