diff --git a/src/repositories/conversation_repository.py b/src/repositories/conversation_repository.py index 4b0cd3b76..018fb1806 100644 --- a/src/repositories/conversation_repository.py +++ b/src/repositories/conversation_repository.py @@ -12,11 +12,26 @@ from src.utils import logger from src.utils.datetime_utils import utc_now_naive +MAX_CONVERSATION_TITLE_LENGTH = 255 + class ConversationRepository: def __init__(self, db_session: AsyncSession): self.db = db_session + def _normalize_title(self, title: str | None) -> str | None: + if title is None: + return None + normalized = str(title).strip() + if not normalized: + return "" + if len(normalized) > MAX_CONVERSATION_TITLE_LENGTH: + logger.warning( + f"Conversation title too long ({len(normalized)}), truncate to {MAX_CONVERSATION_TITLE_LENGTH}" + ) + return normalized[:MAX_CONVERSATION_TITLE_LENGTH] + return normalized + async def create_conversation( self, user_id: str, @@ -31,11 +46,13 @@ async def create_conversation( metadata = (metadata or {}).copy() metadata.setdefault("attachments", []) + normalized_title = self._normalize_title(title) + conversation = Conversation( thread_id=thread_id, user_id=str(user_id), agent_id=agent_id, - title=title or "New Conversation", + title=normalized_title or "New Conversation", status="active", extra_metadata=metadata, ) @@ -203,8 +220,9 @@ async def update_conversation( if not conversation: return None - if title is not None: - conversation.title = title + normalized_title = self._normalize_title(title) + if normalized_title is not None: + conversation.title = normalized_title if status is not None: conversation.status = status diff --git a/test/test_conversation_repository.py b/test/test_conversation_repository.py new file mode 100644 index 000000000..8729223f1 --- /dev/null +++ b/test/test_conversation_repository.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from src.repositories.conversation_repository import ConversationRepository, MAX_CONVERSATION_TITLE_LENGTH + + +def test_normalize_title_truncates_when_too_long(): + repo = ConversationRepository(None) # type: ignore[arg-type] + raw = "a" * (MAX_CONVERSATION_TITLE_LENGTH + 50) + + normalized = repo._normalize_title(raw) + + assert normalized is not None + assert len(normalized) == MAX_CONVERSATION_TITLE_LENGTH + assert normalized == "a" * MAX_CONVERSATION_TITLE_LENGTH + + +def test_normalize_title_trims_spaces(): + repo = ConversationRepository(None) # type: ignore[arg-type] + + normalized = repo._normalize_title(" hello world ") + + assert normalized == "hello world" diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index d777dca22..b2468efaf 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -626,12 +626,15 @@ const deleteThread = async (threadId) => { const updateThread = async (threadId, title) => { if (!threadId || !title) return + const normalizedTitle = String(title).replace(/\s+/g, ' ').trim().slice(0, 255) + if (!normalizedTitle) return + chatState.isRenamingThread = true try { - await threadApi.updateThread(threadId, title) + await threadApi.updateThread(threadId, normalizedTitle) const thread = threads.value.find((t) => t.id === threadId) if (thread) { - thread.title = title + thread.title = normalizedTitle } } catch (error) { console.error('Failed to update thread:', error) @@ -750,7 +753,10 @@ const sendMessage = async ({ // 如果是新对话,用消息内容作为标题 if ((threadMessages.value[threadId] || []).length === 0) { - updateThread(threadId, text) + const autoTitle = text.replace(/\s+/g, ' ').trim().slice(0, 255) + if (autoTitle) { + void updateThread(threadId, autoTitle).catch(() => {}) + } } const requestData = {