optimize: session list loading

This commit is contained in:
xintaofei
2026-04-01 13:41:22 +08:00
parent b98f50340f
commit 3a5d720cc9
3 changed files with 85 additions and 36 deletions

View File

@@ -144,7 +144,8 @@ const ConversationTabView = memo(function ConversationTabView({
const t = useTranslations("Folder.conversation")
const tWelcome = useTranslations("Folder.chat.welcomeInputPanel")
const sharedT = useTranslations("Folder.chat.shared")
const { folder, folderId, refreshConversations } = useFolderContext()
const { folder, folderId, refreshConversations, updateConversationLocal } =
useFolderContext()
const { tabs, bindConversationTab, setTabRuntimeConversationId, pinTab } =
useTabContext()
const { setSessionStats } = useSessionStats()
@@ -371,18 +372,17 @@ const ConversationTabView = memo(function ConversationTabView({
}
if (targetStatus) {
updateConversationStatus(persistedId, targetStatus)
.then(() => refreshConversations())
.catch((e: unknown) =>
console.error("[ConversationTabView] update status:", e)
)
updateConversationLocal(persistedId, { status: targetStatus })
updateConversationStatus(persistedId, targetStatus).catch((e: unknown) =>
console.error("[ConversationTabView] update status:", e)
)
}
}, [
completeTurn,
connStatus,
effectiveConversationId,
refreshConversations,
syncTurnMetadata,
updateConversationLocal,
])
// Auto-send queued messages when agent finishes responding.
@@ -473,16 +473,18 @@ const ConversationTabView = memo(function ConversationTabView({
if (!persistedId) return
if (connStatus === "disconnected") {
statusUpdatedRef.current = true
updateConversationStatus(persistedId, "completed")
.then(() => refreshConversations())
.catch((e) => console.error("[ConversationTabView] update status:", e))
updateConversationLocal(persistedId, { status: "completed" })
updateConversationStatus(persistedId, "completed").catch((e) =>
console.error("[ConversationTabView] update status:", e)
)
} else if (connStatus === "error") {
statusUpdatedRef.current = true
updateConversationStatus(persistedId, "cancelled")
.then(() => refreshConversations())
.catch((e) => console.error("[ConversationTabView] update status:", e))
updateConversationLocal(persistedId, { status: "cancelled" })
updateConversationStatus(persistedId, "cancelled").catch((e) =>
console.error("[ConversationTabView] update status:", e)
)
}
}, [connStatus, refreshConversations])
}, [connStatus, updateConversationLocal])
useEffect(() => {
if (dbConversationId == null) return
@@ -561,11 +563,11 @@ const ConversationTabView = memo(function ConversationTabView({
const persistedId = dbConvIdRef.current
if (persistedId) {
updateConversationStatus(persistedId, "in_progress")
.then(() => refreshConversations())
.catch((e: unknown) =>
updateConversationLocal(persistedId, { status: "in_progress" })
updateConversationStatus(persistedId, "in_progress").catch(
(e: unknown) =>
console.error("[ConversationTabView] update status:", e)
)
)
statusUpdatedRef.current = false
return
}
@@ -610,11 +612,14 @@ const ConversationTabView = memo(function ConversationTabView({
// of setting "in_progress" (which would never be updated).
const initialStatus = deferredStatusRef.current ?? "in_progress"
deferredStatusRef.current = null
updateConversationStatus(newConversationId, initialStatus)
.then(() => refreshConversations())
.catch((e: unknown) =>
refreshConversations()
updateConversationLocal(newConversationId, {
status: initialStatus,
})
updateConversationStatus(newConversationId, initialStatus).catch(
(e: unknown) =>
console.error("[ConversationTabView] update status:", e)
)
)
})
.catch((e: unknown) =>
console.error("[ConversationTabView] create conversation:", e)
@@ -643,6 +648,7 @@ const ConversationTabView = memo(function ConversationTabView({
tWelcome,
tabId,
trySaveExternalId,
updateConversationLocal,
]
)
@@ -686,7 +692,7 @@ const ConversationTabView = memo(function ConversationTabView({
sessionIdRef.current = forkedSessionId
setExternalId(effectiveConversationId, forkedSessionId)
await refreshConversations()
refreshConversations()
// Send the message on the forked session (S2)
handleSend(draft, selectedModeIdArg)
} catch (err) {
@@ -1004,8 +1010,13 @@ export function ConversationDetailPanel() {
getSession,
removeConversation: runtimeRemoveConversation,
} = useConversationRuntime()
const { folder, newConversation, conversations, refreshConversations } =
useFolderContext()
const {
folder,
newConversation,
conversations,
refreshConversations,
updateConversationLocal,
} = useFolderContext()
const {
tabs,
activeTabId,
@@ -1042,6 +1053,7 @@ export function ConversationDetailPanel() {
const runtimeCompleteTurnRef = useRef(runtimeCompleteTurn)
const runtimeRemoveConversationRef = useRef(runtimeRemoveConversation)
const refreshConversationsRef = useRef(refreshConversations)
const updateConversationLocalRef = useRef(updateConversationLocal)
useEffect(() => {
getConversationIdByExternalIdRef.current = getConversationIdByExternalId
}, [getConversationIdByExternalId])
@@ -1057,6 +1069,9 @@ export function ConversationDetailPanel() {
useEffect(() => {
refreshConversationsRef.current = refreshConversations
}, [refreshConversations])
useEffect(() => {
updateConversationLocalRef.current = updateConversationLocal
}, [updateConversationLocal])
// Background turn_complete handler: for conversations not open in tabs.
// Registered once — uses refs to avoid re-creating the listener on every
@@ -1108,14 +1123,16 @@ export function ConversationDetailPanel() {
summary?.id ??
(matchedConversationId > 0 ? matchedConversationId : null)
if (dbId && (!summary || summary.status === "in_progress")) {
updateConversationStatus(dbId, "pending_review")
.then(() => refreshConversationsRef.current())
.catch((error: unknown) =>
updateConversationLocalRef.current(dbId, {
status: "pending_review",
})
updateConversationStatus(dbId, "pending_review").catch(
(error: unknown) =>
console.error(
"[ConversationDetailPanel] background update status:",
error
)
)
)
}
})
)

View File

@@ -186,6 +186,7 @@ export function SidebarConversationList({
selectedConversation,
folderId,
refreshConversations,
updateConversationLocal,
} = useFolderContext()
const { openTab, closeConversationTab, openNewConversationTab } =
@@ -347,10 +348,10 @@ export function SidebarConversationList({
const handleStatusChange = useCallback(
async (id: number, status: ConversationStatus) => {
updateConversationLocal(id, { status })
await updateConversationStatus(id, status)
refreshConversations()
},
[refreshConversations]
[updateConversationLocal]
)
const handleNewConversation = useCallback(() => {
@@ -391,12 +392,15 @@ export function SidebarConversationList({
if (completingReview || reviewConversationCount === 0) return
setCompletingReview(true)
try {
// Optimistic: update all locally first
for (const conversation of reviewConversations) {
updateConversationLocal(conversation.id, { status: "completed" })
}
await Promise.all(
reviewConversations.map((conversation) =>
updateConversationStatus(conversation.id, "completed")
)
)
refreshConversations()
toast.success(
t("toasts.reviewCompleted", { count: reviewConversationCount })
)
@@ -404,6 +408,8 @@ export function SidebarConversationList({
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
toast.error(t("toasts.completeReviewFailed", { message: msg }))
// Revert on error — refetch from server
refreshConversations()
} finally {
setCompletingReview(false)
}
@@ -412,13 +418,14 @@ export function SidebarConversationList({
reviewConversationCount,
reviewConversations,
refreshConversations,
updateConversationLocal,
t,
])
return (
<div className="flex flex-col flex-1 min-h-0">
<div className="relative flex flex-col flex-1 min-h-0">
{(loading || refreshing) && (
<div className="flex items-center justify-center py-1">
<div className="absolute top-0 left-0 right-0 flex items-center justify-center py-1 z-10 pointer-events-none">
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
</div>
)}