fix: 历史回合误显示活跃状态与流式段间隐形空白
This commit is contained in:
@@ -48,13 +48,17 @@ function renderTurns(
|
|||||||
if (message.role === 'assistant') {
|
if (message.role === 'assistant') {
|
||||||
let j = i + 1
|
let j = i + 1
|
||||||
while (j < messages.length && messages[j].role === 'assistant') j += 1
|
while (j < messages.length && messages[j].role === 'assistant') j += 1
|
||||||
|
// turnActive/streaming 是会话级信号:只有延伸到消息列表末尾的最后一个
|
||||||
|
// assistant 组是「活跃回合」。透传给历史回合会让它们误显示
|
||||||
|
// 「已工作/处理中」并保持过程区展开。
|
||||||
|
const isLive = j === messages.length
|
||||||
items.push(
|
items.push(
|
||||||
<AssistantTurn
|
<AssistantTurn
|
||||||
key={`${message.key}-turn`}
|
key={`${message.key}-turn`}
|
||||||
messages={messages.slice(i, j)}
|
messages={messages.slice(i, j)}
|
||||||
tools={tools}
|
tools={tools}
|
||||||
streaming={streaming}
|
streaming={streaming && isLive}
|
||||||
turnActive={turnActive}
|
turnActive={turnActive && isLive}
|
||||||
pendingApprovals={pendingApprovals}
|
pendingApprovals={pendingApprovals}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -65,8 +69,8 @@ function renderTurns(
|
|||||||
key={message.key}
|
key={message.key}
|
||||||
message={message}
|
message={message}
|
||||||
tools={tools}
|
tools={tools}
|
||||||
streaming={streaming}
|
streaming={streaming && i === messages.length - 1}
|
||||||
turnActive={turnActive}
|
turnActive={turnActive && i === messages.length - 1}
|
||||||
pendingApprovals={pendingApprovals}
|
pendingApprovals={pendingApprovals}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -177,15 +177,18 @@ export function AssistantTurn({
|
|||||||
const streamNodes: ReactNode[] = []
|
const streamNodes: ReactNode[] = []
|
||||||
let visibleThinkingRows = 0
|
let visibleThinkingRows = 0
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.segments.forEach((seg, i) => {
|
const segments = stream.segments
|
||||||
|
segments.forEach((seg, i) => {
|
||||||
if (seg.kind === 'thinking') {
|
if (seg.kind === 'thinking') {
|
||||||
if (!showStreamThinking) return
|
if (!showStreamThinking) return
|
||||||
const rows = thinkingRows(seg.text)
|
const rows = thinkingRows(seg.text)
|
||||||
if (rows.length === 0) return
|
if (rows.length === 0) return
|
||||||
visibleThinkingRows += rows.length
|
visibleThinkingRows += rows.length
|
||||||
|
// 该思考段之后已有正文段在流:思考已完成,表头从「思考中…」切回「思考过程」
|
||||||
|
const followedByText = segments.some((s, k) => k > i && s.kind === 'text' && s.text.length > 0)
|
||||||
streamNodes.push(
|
streamNodes.push(
|
||||||
<div key={`seg-${i}`} className="mb-3">
|
<div key={`seg-${i}`} className="mb-3">
|
||||||
<Thinking rows={rows} streaming={streaming} />
|
<Thinking rows={rows} streaming={streaming && !followedByText} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -48,29 +48,29 @@ export function StreamingText({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作行:流式结束后淡入 */}
|
{/* 操作行:流式结束后淡入。流式期间不渲染(opacity:0 也会占位,
|
||||||
<div
|
* 每个正文段后会挂一块约 32px 的隐形空白,且完成后卸载造成布局跳动) */}
|
||||||
className="mt-2 flex items-center gap-0.5 transition-opacity duration-[400ms]"
|
{done && (
|
||||||
style={{ opacity: done ? 1 : 0, pointerEvents: done ? undefined : 'none' }}
|
<div className="mt-2 flex items-center gap-0.5" style={{ animation: 'fade-in 400ms ease both' }}>
|
||||||
>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
aria-label={copied ? '已复制' : '复制回答'}
|
||||||
aria-label={copied ? '已复制' : '复制回答'}
|
onClick={copy}
|
||||||
onClick={copy}
|
className="flex size-6 items-center justify-center rounded-[6px] text-ink-3 transition-colors duration-100 hover:bg-hover-2 hover:text-ink-2"
|
||||||
className="flex size-6 items-center justify-center rounded-[6px] text-ink-3 transition-colors duration-100 hover:bg-hover-2 hover:text-ink-2"
|
>
|
||||||
>
|
{copied ? (
|
||||||
{copied ? (
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
<path d="M20 6L9 17l-5-5" />
|
||||||
<path d="M20 6L9 17l-5-5" />
|
</svg>
|
||||||
</svg>
|
) : (
|
||||||
) : (
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
<rect x="9" y="9" width="12" height="12" rx="2.5" />
|
||||||
<rect x="9" y="9" width="12" height="12" rx="2.5" />
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
</svg>
|
||||||
</svg>
|
)}
|
||||||
)}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* Follow-ups:完成后的跟进问题列表 */}
|
{/* Follow-ups:完成后的跟进问题列表 */}
|
||||||
{done && followUps && followUps.length > 0 && (
|
{done && followUps && followUps.length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user