重构会话消息处理和显示逻辑

This commit is contained in:
xintaofei
2026-03-10 19:32:44 +08:00
parent aa1ff9a6df
commit 91636ada7f
13 changed files with 1429 additions and 1629 deletions

View File

@@ -22,8 +22,8 @@ import type {
interface TabItemInternal {
id: string
kind: "conversation" | "new_conversation"
conversationId?: number
kind: "conversation"
conversationId: number | null
agentType: AgentType
title: string
isPinned: boolean
@@ -43,18 +43,13 @@ interface TabContextValue {
title?: string
) => void
closeTab: (tabId: string) => void
closeConversationTab: (conversationId: number, agentType: AgentType) => void
closeOtherTabs: (tabId: string) => void
closeAllTabs: () => void
switchTab: (tabId: string) => void
pinTab: (tabId: string) => void
openNewConversationTab: (agentType: AgentType, workingDir: string) => void
promoteNewConversationTab: (
tabId: string,
conversationId: number,
agentType: AgentType,
title: string
) => void
linkTabConversation: (
bindConversationTab: (
tabId: string,
conversationId: number,
agentType: AgentType,
@@ -83,13 +78,6 @@ function makeConversationTabId(
function makeNewConversationTabId(): string {
return `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
}
/**
* Find a tab that represents the given conversation, regardless of whether
* it has been promoted to a canonical id yet. Checks canonical id first,
* then falls back to matching by conversationId + agentType (covers the
* linked-but-not-yet-promoted new_conversation tabs).
*/
function findTabIndexForConversation(
tabs: TabItemInternal[],
agentType: AgentType,
@@ -130,7 +118,7 @@ export function TabProvider({ children }: TabProviderProps) {
return [
{
id: tabId,
kind: "conversation" as const,
kind: "conversation",
conversationId: selectedConversation.id,
agentType: selectedConversation.agentType,
title: t("loadingConversation"),
@@ -187,7 +175,7 @@ export function TabProvider({ children }: TabProviderProps) {
const restoredTabs: TabItemInternal[] = opened.map((oc) => ({
id: makeConversationTabId(oc.agent_type, oc.conversation_id),
kind: "conversation" as const,
kind: "conversation",
conversationId: oc.conversation_id,
agentType: oc.agent_type,
title: t("loadingConversation"),
@@ -300,13 +288,20 @@ export function TabProvider({ children }: TabProviderProps) {
cancelNewConversation()
return
}
if (tab.kind === "conversation" && tab.conversationId != null) {
if (tab.conversationId != null) {
selectConversation(tab.conversationId, tab.agentType)
} else if (tab.kind === "new_conversation" && tab.workingDir) {
startNewConversation(tab.agentType, tab.workingDir)
} else {
const workingDir = tab.workingDir ?? folder?.path
if (!workingDir) {
clearSelection()
cancelNewConversation()
return
}
startNewConversation(tab.agentType, workingDir)
}
},
[
folder?.path,
selectConversation,
clearSelection,
startNewConversation,
@@ -386,10 +381,11 @@ export function TabProvider({ children }: TabProviderProps) {
[activateConversationPane, selectConversation, t]
)
const makeReplacementNewConversationTab = useCallback(
const makeReplacementDraftTab = useCallback(
(preferred?: TabItemInternal): TabItemInternal => ({
id: makeNewConversationTabId(),
kind: "new_conversation",
kind: "conversation",
conversationId: null,
agentType: preferred?.agentType ?? "codex",
title: t("newConversation"),
isPinned: true,
@@ -410,7 +406,7 @@ export function TabProvider({ children }: TabProviderProps) {
const next = prev.filter((t) => t.id !== tabId)
if (next.length === 0) {
const replacementTab = makeReplacementNewConversationTab(closingTab)
const replacementTab = makeReplacementDraftTab(closingTab)
neighborToSync = replacementTab
return [replacementTab]
}
@@ -433,11 +429,19 @@ export function TabProvider({ children }: TabProviderProps) {
activateConversationPane()
}
},
[
activateConversationPane,
makeReplacementNewConversationTab,
syncFolderContext,
]
[activateConversationPane, makeReplacementDraftTab, syncFolderContext]
)
const closeConversationTab = useCallback(
(conversationId: number, agentType: AgentType) => {
const target = rawTabsRef.current.find(
(tab) =>
tab.conversationId === conversationId && tab.agentType === agentType
)
if (!target) return
closeTab(target.id)
},
[closeTab]
)
const closeOtherTabs = useCallback(
@@ -459,21 +463,17 @@ export function TabProvider({ children }: TabProviderProps) {
const closeAllTabs = useCallback(() => {
const seedTab =
rawTabsRef.current.find(
(t) => t.kind === "new_conversation" && t.workingDir
(t) => t.conversationId == null && t.workingDir
) ??
rawTabsRef.current.find((t) => t.id === activeTabIdRef.current) ??
rawTabsRef.current[0]
const replacementTab = makeReplacementNewConversationTab(seedTab)
const replacementTab = makeReplacementDraftTab(seedTab)
setTabs([replacementTab])
setActiveTabId(replacementTab.id)
syncFolderContext(replacementTab)
activateConversationPane()
}, [
activateConversationPane,
makeReplacementNewConversationTab,
syncFolderContext,
])
}, [activateConversationPane, makeReplacementDraftTab, syncFolderContext])
const switchTab = useCallback(
(tabId: string) => {
@@ -501,10 +501,7 @@ export function TabProvider({ children }: TabProviderProps) {
const openNewConversationTab = useCallback(
(agentType: AgentType, workingDir: string) => {
const existingTab = rawTabsRef.current.find(
(t) =>
t.kind === "new_conversation" &&
t.agentType === agentType &&
!t.conversationId
(t) => t.conversationId == null && t.agentType === agentType
)
if (existingTab) {
@@ -517,7 +514,8 @@ export function TabProvider({ children }: TabProviderProps) {
const tabId = makeNewConversationTabId()
const newTab: TabItemInternal = {
id: tabId,
kind: "new_conversation",
kind: "conversation",
conversationId: null,
agentType,
title: t("newConversation"),
isPinned: true,
@@ -532,71 +530,45 @@ export function TabProvider({ children }: TabProviderProps) {
[activateConversationPane, startNewConversation, syncFolderContext, t]
)
const linkTabConversation = useCallback(
const bindConversationTab = useCallback(
(
tabId: string,
conversationId: number,
agentType: AgentType,
title: string
) => {
let nextActiveTabId: string | null = null
setTabs((prev) =>
prev.map((t) =>
t.id === tabId ? { ...t, conversationId, agentType, title } : t
)
prev.flatMap((tab) => {
if (tab.id === tabId) {
const nextTab = { ...tab, conversationId, agentType, title }
return [nextTab]
}
if (
tab.conversationId === conversationId &&
tab.agentType === agentType
) {
if (activeTabIdRef.current === tabId) {
nextActiveTabId = tab.id
}
return []
}
return [tab]
})
)
},
[]
)
const promoteNewConversationTab = useCallback(
(
tabId: string,
conversationId: number,
agentType: AgentType,
title: string
) => {
let activateId: string | undefined
setTabs((prev) => {
const index = prev.findIndex((t) => t.id === tabId)
if (index < 0) return prev
const newId = makeConversationTabId(agentType, conversationId)
// Check if a *different* tab already represents this conversation
const dupeIndex = findTabIndexForConversation(
prev,
agentType,
conversationId
if (nextActiveTabId) {
setActiveTabId(nextActiveTabId)
const target = rawTabsRef.current.find(
(tab) => tab.id === nextActiveTabId
)
if (dupeIndex >= 0 && dupeIndex !== index) {
activateId = prev[dupeIndex].id
return prev.filter((t) => t.id !== tabId)
if (target) {
syncFolderContext(target)
}
const promoted: TabItemInternal = {
...prev[index],
id: newId,
kind: "conversation",
conversationId,
agentType,
title,
isPinned: true,
}
activateId = newId
const updated = [...prev]
updated[index] = promoted
return updated
})
if (activateId) {
setActiveTabId(activateId)
selectConversation(conversationId, agentType)
activateConversationPane()
}
},
[activateConversationPane, selectConversation]
[syncFolderContext]
)
const value = useMemo(
@@ -605,13 +577,13 @@ export function TabProvider({ children }: TabProviderProps) {
activeTabId,
openTab,
closeTab,
closeConversationTab,
closeOtherTabs,
closeAllTabs,
switchTab,
pinTab,
openNewConversationTab,
promoteNewConversationTab,
linkTabConversation,
bindConversationTab,
reorderTabs,
}),
[
@@ -619,13 +591,13 @@ export function TabProvider({ children }: TabProviderProps) {
activeTabId,
openTab,
closeTab,
closeConversationTab,
closeOtherTabs,
closeAllTabs,
switchTab,
pinTab,
openNewConversationTab,
promoteNewConversationTab,
linkTabConversation,
bindConversationTab,
reorderTabs,
]
)