Skip to content

Commit 6ccbbe9

Browse files
committed
fix: Ensure session titles reactively update and improve fallback title generation logic
- Use `updatedAt` in the `SessionSidebar` key to force recomposition on session updates. - Avoid caching mutable session objects in Compose to prevent stale title values. - Enhance title generation to use the first assistant message as a fallback if no user message exists. - Refine log messages for improved clarity in `ChatHistoryManager`.
1 parent f579d25 commit 6ccbbe9

File tree

2 files changed

+42
-20
lines changed

2 files changed

+42
-20
lines changed

mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/llm/ChatHistoryManager.kt

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,19 @@ class ChatHistoryManager {
4444
var titlesFixed = 0
4545
loadedSessions.forEach { session ->
4646
// Fix sessions with null title by generating from first user message
47-
if (session.title == null) {
47+
// or first assistant message if no user message exists
48+
if (session.title == null && session.messages.isNotEmpty()) {
4849
val firstUserMessage = session.messages.firstOrNull { it.role == MessageRole.USER }
4950
if (firstUserMessage != null) {
5051
session.title = generateTitleFromContent(firstUserMessage.content)
5152
titlesFixed++
53+
} else {
54+
// Fallback: use first assistant message if no user message
55+
val firstAssistantMessage = session.messages.firstOrNull { it.role == MessageRole.ASSISTANT }
56+
if (firstAssistantMessage != null) {
57+
session.title = generateTitleFromContent(firstAssistantMessage.content)
58+
titlesFixed++
59+
}
5260
}
5361
}
5462
sessions[session.id] = session
@@ -57,21 +65,21 @@ class ChatHistoryManager {
5765
// Save if we fixed any titles
5866
if (titlesFixed > 0) {
5967
saveSessions()
60-
println("Fixed $titlesFixed session titles")
68+
println("Fixed $titlesFixed session titles")
6169
}
6270

6371
// 如果有会话,设置最新的为当前会话
6472
if (sessions.isNotEmpty()) {
6573
currentSessionId = sessions.values.maxByOrNull { it.updatedAt }?.id
6674
}
6775

68-
println("Loaded ${sessions.size} chat sessions from disk")
76+
println("Loaded ${sessions.size} chat sessions from disk")
6977
initialized = true
7078

7179
// 通知 UI 更新,确保初始加载的会话能够显示
7280
_sessionsUpdateTrigger.value++
7381
} catch (e: Exception) {
74-
println("⚠️ Failed to initialize ChatHistoryManager: ${e.message}")
82+
println("Failed to initialize ChatHistoryManager: ${e.message}")
7583
initialized = true
7684
}
7785
}
@@ -247,24 +255,34 @@ class ChatHistoryManager {
247255

248256
/**
249257
* 批量添加多个 Message 对象到当前会话
250-
* 如果是第一条用户消息且 title 为空,自动设置 title
258+
* 如果 title 为空,自动从第一条用户消息设置 title
259+
* 如果没有用户消息,则从第一条助手消息设置 title
251260
* 完成后立即同步保存到磁盘
252261
*/
253262
suspend fun addMessages(messages: List<Message>) {
254263
if (messages.isEmpty()) return
255264

256265
val session = getCurrentSession()
257266

258-
// Auto-generate title from first user message if title is null
259-
val firstUserMessage = messages.firstOrNull { it.role == MessageRole.USER }
260-
if (firstUserMessage != null && session.title == null) {
261-
// Set title from first user message content
262-
session.title = generateTitleFromContent(firstUserMessage.content)
263-
}
264-
267+
// Add messages first
265268
session.messages.addAll(messages)
266269
session.updatedAt = kotlinx.datetime.Clock.System.now().toEpochMilliseconds()
267270

271+
// Auto-generate title from first user message in session if title is still null
272+
// Check session.messages (which now includes the new messages) for the first user message
273+
if (session.title == null && session.messages.isNotEmpty()) {
274+
val firstUserMessage = session.messages.firstOrNull { it.role == MessageRole.USER }
275+
if (firstUserMessage != null) {
276+
session.title = generateTitleFromContent(firstUserMessage.content)
277+
} else {
278+
// Fallback: use first assistant message if no user message
279+
val firstAssistantMessage = session.messages.firstOrNull { it.role == MessageRole.ASSISTANT }
280+
if (firstAssistantMessage != null) {
281+
session.title = generateTitleFromContent(firstAssistantMessage.content)
282+
}
283+
}
284+
}
285+
268286
// 立即同步保存
269287
saveSessions()
270288
}

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/SessionSidebar.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,9 @@ private fun ExpandedSessionSidebarContent(
255255
)
256256
}
257257

258-
items(localSessions, key = { "local_${it.id}" }) { session ->
258+
// Include updatedAt in key to force recomposition when session is updated
259+
// (e.g., when title is set after first user message)
260+
items(localSessions, key = { "local_${it.id}_${it.updatedAt}" }) { session ->
259261
LocalSessionItem(
260262
session = session,
261263
isSelected = session.id == currentSessionId,
@@ -347,13 +349,15 @@ private fun LocalSessionItem(
347349
MaterialTheme.colorScheme.onSurface
348350
}
349351

350-
// 获取会话标题(第一条用户消息的摘要)
351-
val title = remember(session) {
352-
session.title ?: run {
353-
val firstUserMessage = session.messages.firstOrNull { it.role == MessageRole.USER }
354-
firstUserMessage?.content?.take(50) ?: "New Chat"
355-
}
356-
}
352+
// Get session title (prefer explicit title, fallback to first user message snippet).
353+
// IMPORTANT: do NOT use `remember(session)` here because `session` is a mutable object.
354+
// If `session.title` updates later (e.g. after first user message), Compose would keep the old cached value.
355+
val title =
356+
session.title?.takeIf { it.isNotBlank() }
357+
?: run {
358+
val firstUserMessage = session.messages.firstOrNull { it.role == MessageRole.USER }
359+
firstUserMessage?.content?.take(50) ?: "New Chat"
360+
}
357361

358362
// 格式化时间
359363
val timeText = remember(session.updatedAt) {

0 commit comments

Comments
 (0)