修复Agent消息在响应结束后显示两条
This commit is contained in:
@@ -145,6 +145,7 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
const [draftAgentType, setDraftAgentType] = useState<AgentType>(agentType)
|
const [draftAgentType, setDraftAgentType] = useState<AgentType>(agentType)
|
||||||
const selectedAgent = conversationId != null ? agentType : draftAgentType
|
const selectedAgent = conversationId != null ? agentType : draftAgentType
|
||||||
const [modeId, setModeId] = useState<string | null>(null)
|
const [modeId, setModeId] = useState<string | null>(null)
|
||||||
|
const [sendSignal, setSendSignal] = useState(0)
|
||||||
const [agentsLoaded, setAgentsLoaded] = useState(false)
|
const [agentsLoaded, setAgentsLoaded] = useState(false)
|
||||||
const [usableAgentCount, setUsableAgentCount] = useState(0)
|
const [usableAgentCount, setUsableAgentCount] = useState(0)
|
||||||
const [agentConnectError, setAgentConnectError] = useState<string | null>(
|
const [agentConnectError, setAgentConnectError] = useState<string | null>(
|
||||||
@@ -433,6 +434,7 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
optimisticTurn,
|
optimisticTurn,
|
||||||
optimisticTurn.id
|
optimisticTurn.id
|
||||||
)
|
)
|
||||||
|
setSendSignal((prev) => prev + 1)
|
||||||
setSyncState(effectiveConversationId, "awaiting_persist")
|
setSyncState(effectiveConversationId, "awaiting_persist")
|
||||||
|
|
||||||
if (connStatus === "connected") {
|
if (connStatus === "connected") {
|
||||||
@@ -577,6 +579,7 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
conversationId={effectiveConversationId}
|
conversationId={effectiveConversationId}
|
||||||
connStatus={connStatus}
|
connStatus={connStatus}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
|
sendSignal={sendSignal}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { memo, useCallback, useEffect, useMemo } from "react"
|
import { memo, useCallback, useEffect, useMemo, useRef } from "react"
|
||||||
import { useDbMessageDetail } from "@/hooks/use-db-message-detail"
|
import { useDbMessageDetail } from "@/hooks/use-db-message-detail"
|
||||||
import { useConversationRuntime } from "@/contexts/conversation-runtime-context"
|
import { useConversationRuntime } from "@/contexts/conversation-runtime-context"
|
||||||
import { ContentPartsRenderer } from "./content-parts-renderer"
|
import { ContentPartsRenderer } from "./content-parts-renderer"
|
||||||
@@ -29,11 +29,13 @@ import {
|
|||||||
} from "@/lib/agent-plan"
|
} from "@/lib/agent-plan"
|
||||||
import type { ConnectionStatus } from "@/lib/types"
|
import type { ConnectionStatus } from "@/lib/types"
|
||||||
import { VirtualizedMessageThread } from "@/components/message/virtualized-message-thread"
|
import { VirtualizedMessageThread } from "@/components/message/virtualized-message-thread"
|
||||||
|
import { useStickToBottomContext } from "use-stick-to-bottom"
|
||||||
|
|
||||||
interface MessageListViewProps {
|
interface MessageListViewProps {
|
||||||
conversationId: number
|
conversationId: number
|
||||||
connStatus?: ConnectionStatus | null
|
connStatus?: ConnectionStatus | null
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
|
sendSignal?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResolvedMessageGroup extends MessageGroup {
|
interface ResolvedMessageGroup extends MessageGroup {
|
||||||
@@ -169,10 +171,38 @@ const PendingTypingIndicator = memo(function PendingTypingIndicator() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const AutoScrollOnSend = memo(function AutoScrollOnSend({
|
||||||
|
signal,
|
||||||
|
enabled,
|
||||||
|
}: {
|
||||||
|
signal: number
|
||||||
|
enabled: boolean
|
||||||
|
}) {
|
||||||
|
const { scrollToBottom } = useStickToBottomContext()
|
||||||
|
const lastSignalRef = useRef(signal)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled) return
|
||||||
|
if (signal === lastSignalRef.current) return
|
||||||
|
lastSignalRef.current = signal
|
||||||
|
|
||||||
|
scrollToBottom()
|
||||||
|
const rafId = requestAnimationFrame(() => {
|
||||||
|
scrollToBottom()
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(rafId)
|
||||||
|
}
|
||||||
|
}, [enabled, scrollToBottom, signal])
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
export function MessageListView({
|
export function MessageListView({
|
||||||
conversationId,
|
conversationId,
|
||||||
connStatus,
|
connStatus,
|
||||||
isActive = true,
|
isActive = true,
|
||||||
|
sendSignal = 0,
|
||||||
}: MessageListViewProps) {
|
}: MessageListViewProps) {
|
||||||
const t = useTranslations("Folder.chat.messageList")
|
const t = useTranslations("Folder.chat.messageList")
|
||||||
const sharedT = useTranslations("Folder.chat.shared")
|
const sharedT = useTranslations("Folder.chat.shared")
|
||||||
@@ -339,6 +369,7 @@ export function MessageListView({
|
|||||||
className="flex-1 min-h-0"
|
className="flex-1 min-h-0"
|
||||||
resize={shouldUseSmoothResize ? "smooth" : undefined}
|
resize={shouldUseSmoothResize ? "smooth" : undefined}
|
||||||
>
|
>
|
||||||
|
<AutoScrollOnSend signal={sendSignal} enabled={isActive} />
|
||||||
<VirtualizedMessageThread
|
<VirtualizedMessageThread
|
||||||
items={threadItems}
|
items={threadItems}
|
||||||
getItemKey={(item) => item.key}
|
getItemKey={(item) => item.key}
|
||||||
|
|||||||
@@ -221,6 +221,9 @@ function reduceHydrateDetail(
|
|||||||
const current = state.byConversationId.get(conversationId)
|
const current = state.byConversationId.get(conversationId)
|
||||||
const nextExternalId = detail.summary.external_id ?? null
|
const nextExternalId = detail.summary.external_id ?? null
|
||||||
const acceptSnapshot = shouldAcceptPersistedSnapshot(current, detail)
|
const acceptSnapshot = shouldAcceptPersistedSnapshot(current, detail)
|
||||||
|
const prevPersistedTurnCount = current?.persistedTurns.length ?? 0
|
||||||
|
const prevPersistedMessageCount = current?.persistedMessageCount ?? 0
|
||||||
|
const prevPersistedUpdatedAt = current?.persistedUpdatedAt ?? null
|
||||||
const optimisticTurns = current?.optimisticTurns ?? []
|
const optimisticTurns = current?.optimisticTurns ?? []
|
||||||
const persistedTurns = acceptSnapshot
|
const persistedTurns = acceptSnapshot
|
||||||
? detail.turns
|
? detail.turns
|
||||||
@@ -234,11 +237,20 @@ function reduceHydrateDetail(
|
|||||||
const shouldDropOptimistic =
|
const shouldDropOptimistic =
|
||||||
optimisticTurns.length > 0 &&
|
optimisticTurns.length > 0 &&
|
||||||
persistedTurns.length >= (current?.persistedTurns.length ?? 0) + 1
|
persistedTurns.length >= (current?.persistedTurns.length ?? 0) + 1
|
||||||
|
const nextUpdatedAt = detail.summary.updated_at ?? null
|
||||||
|
const hasPersistedAdvance =
|
||||||
|
acceptSnapshot &&
|
||||||
|
(detail.turns.length > prevPersistedTurnCount ||
|
||||||
|
detail.summary.message_count > prevPersistedMessageCount ||
|
||||||
|
(nextUpdatedAt !== null &&
|
||||||
|
(prevPersistedUpdatedAt === null ||
|
||||||
|
nextUpdatedAt > prevPersistedUpdatedAt)))
|
||||||
|
|
||||||
const nextSession: ConversationRuntimeSession = {
|
const nextSession: ConversationRuntimeSession = {
|
||||||
...(current ?? createEmptySession(conversationId)),
|
...(current ?? createEmptySession(conversationId)),
|
||||||
externalId: nextExternalId,
|
externalId: nextExternalId,
|
||||||
persistedTurns,
|
persistedTurns,
|
||||||
|
liveMessage: hasPersistedAdvance ? null : (current?.liveMessage ?? null),
|
||||||
optimisticTurns: shouldDropOptimistic ? [] : optimisticTurns,
|
optimisticTurns: shouldDropOptimistic ? [] : optimisticTurns,
|
||||||
syncState: shouldDropOptimistic ? "idle" : (current?.syncState ?? "idle"),
|
syncState: shouldDropOptimistic ? "idle" : (current?.syncState ?? "idle"),
|
||||||
activeTurnToken: shouldDropOptimistic
|
activeTurnToken: shouldDropOptimistic
|
||||||
@@ -370,6 +382,7 @@ function reducer(
|
|||||||
|
|
||||||
const preferFromSnapshot =
|
const preferFromSnapshot =
|
||||||
from.persistedTurns.length >= to.persistedTurns.length
|
from.persistedTurns.length >= to.persistedTurns.length
|
||||||
|
const mergedLiveMessage = to.liveMessage ?? from.liveMessage
|
||||||
|
|
||||||
const merged: ConversationRuntimeSession = {
|
const merged: ConversationRuntimeSession = {
|
||||||
...to,
|
...to,
|
||||||
@@ -379,7 +392,7 @@ function reducer(
|
|||||||
? from.persistedTurns
|
? from.persistedTurns
|
||||||
: to.persistedTurns,
|
: to.persistedTurns,
|
||||||
optimisticTurns: [...from.optimisticTurns, ...to.optimisticTurns],
|
optimisticTurns: [...from.optimisticTurns, ...to.optimisticTurns],
|
||||||
liveMessage: to.liveMessage ?? from.liveMessage,
|
liveMessage: mergedLiveMessage,
|
||||||
syncState: to.syncState !== "idle" ? to.syncState : from.syncState,
|
syncState: to.syncState !== "idle" ? to.syncState : from.syncState,
|
||||||
activeTurnToken: to.activeTurnToken ?? from.activeTurnToken,
|
activeTurnToken: to.activeTurnToken ?? from.activeTurnToken,
|
||||||
lastHydratedAt:
|
lastHydratedAt:
|
||||||
|
|||||||
Reference in New Issue
Block a user