mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-01 16:51:53 +03:00
fix(webui): project generated titles onto per-chat sessions under unifiedSession
With unifiedSession enabled, all turns are routed to the shared unified:default session, so title generation, persistence, and change notifications all happen on that shared session. The WebUI sidebar, however, renders per-chat websocket:<id> sessions, so generated titles never reach the session the user sees and every chat stays "Untitled". Pass the origin chat_id through maybe_generate_webui_title_after_turn and add a target_session_key to maybe_generate_webui_title so the title is written to the per-chat session while conversation content still comes from the shared (routed) session. When the routed session differs from the per-chat session, the latest user/assistant exchange is used as the title prompt since the shared session mixes content from every channel. The shared session's title metadata is no longer read or written, so stale shared titles can no longer leak into per-chat sessions.
This commit is contained in:
@@ -49,6 +49,7 @@ from nanobot.session.webui_turns import (
|
||||
WebuiTurnCoordinator,
|
||||
clean_generated_title,
|
||||
maybe_generate_webui_title,
|
||||
maybe_generate_webui_title_after_turn,
|
||||
)
|
||||
from nanobot.triggers.local_session_turns import LOCAL_TRIGGER_META
|
||||
|
||||
@@ -395,6 +396,68 @@ async def test_generate_webui_title_ignores_cron_internal_turns(tmp_path: Path)
|
||||
loop.provider.chat_with_retry.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_webui_title_projects_onto_chat_session_under_unified_routing(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
loop = _make_full_loop(tmp_path)
|
||||
loop.provider.chat_with_retry = AsyncMock(
|
||||
return_value=LLMResponse(content='"查询临期 IP"', finish_reason="stop")
|
||||
)
|
||||
unified = loop.sessions.get_or_create(UNIFIED_SESSION_KEY)
|
||||
unified.metadata[WEBUI_SESSION_METADATA_KEY] = True
|
||||
unified.metadata[WEBUI_TITLE_METADATA_KEY] = "开启私聊Topic功能"
|
||||
unified.add_message("user", "很早以前的问题")
|
||||
unified.add_message("assistant", "很久以前的回答。")
|
||||
unified.add_message("user", "帮我查一下临期IP有哪些")
|
||||
unified.add_message("assistant", "以下是临期 IP 列表。")
|
||||
loop.sessions.save(unified)
|
||||
|
||||
generated = await maybe_generate_webui_title_after_turn(
|
||||
channel="websocket",
|
||||
chat_id="chat-projection",
|
||||
metadata={WEBUI_SESSION_METADATA_KEY: True},
|
||||
sessions=loop.sessions,
|
||||
session_key=UNIFIED_SESSION_KEY,
|
||||
provider=loop.provider,
|
||||
model=loop.model,
|
||||
)
|
||||
|
||||
assert generated is True
|
||||
chat = loop.sessions.get_or_create("websocket:chat-projection")
|
||||
assert chat.metadata[WEBUI_TITLE_METADATA_KEY] == "查询临期 IP"
|
||||
assert unified.metadata[WEBUI_TITLE_METADATA_KEY] == "开启私聊Topic功能"
|
||||
prompt = loop.provider.chat_with_retry.await_args.args[0][1]["content"]
|
||||
assert "帮我查一下临期IP有哪些" in prompt
|
||||
assert "很早以前的问题" not in prompt
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_projected_title_generation_skips_existing_chat_title(tmp_path: Path) -> None:
|
||||
loop = _make_full_loop(tmp_path)
|
||||
unified = loop.sessions.get_or_create(UNIFIED_SESSION_KEY)
|
||||
unified.metadata[WEBUI_SESSION_METADATA_KEY] = True
|
||||
unified.add_message("user", "帮我查一下临期IP有哪些")
|
||||
unified.add_message("assistant", "以下是临期 IP 列表。")
|
||||
chat = loop.sessions.get_or_create("websocket:chat-existing")
|
||||
chat.metadata[WEBUI_TITLE_METADATA_KEY] = "Existing title"
|
||||
loop.sessions.save(unified)
|
||||
|
||||
generated = await maybe_generate_webui_title_after_turn(
|
||||
channel="websocket",
|
||||
chat_id="chat-existing",
|
||||
metadata={WEBUI_SESSION_METADATA_KEY: True},
|
||||
sessions=loop.sessions,
|
||||
session_key=UNIFIED_SESSION_KEY,
|
||||
provider=loop.provider,
|
||||
model=loop.model,
|
||||
)
|
||||
|
||||
assert generated is False
|
||||
assert chat.metadata[WEBUI_TITLE_METADATA_KEY] == "Existing title"
|
||||
loop.provider.chat_with_retry.assert_not_awaited()
|
||||
|
||||
|
||||
def test_save_turn_keeps_multimodal_runtime_context_for_model_replay() -> None:
|
||||
loop = _mk_loop()
|
||||
session = Session(key="test:runtime-only")
|
||||
|
||||
Reference in New Issue
Block a user