diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py index d4174ffb58..986570ac34 100644 --- a/astrbot/dashboard/routes/chat.py +++ b/astrbot/dashboard/routes/chat.py @@ -766,6 +766,44 @@ async def stream(): message_accumulator = BotMessageAccumulator() agent_stats = {} refs = {} + + async def flush_pending_bot_message(): + nonlocal message_accumulator, agent_stats, refs + if not (message_accumulator.has_content() or refs or agent_stats): + return None + + message_parts_to_save = message_accumulator.build_message_parts( + include_pending_tool_calls=True + ) + plain_text = collect_plain_text_from_message_parts( + message_parts_to_save + ) + + try: + extracted_refs = self._extract_web_search_refs( + plain_text, + message_parts_to_save, + ) + except Exception as e: + logger.exception( + f"Failed to extract web search refs: {e}", + exc_info=True, + ) + extracted_refs = refs + + saved_record = await self._save_bot_message( + webchat_conv_id, + message_parts_to_save, + agent_stats, + extracted_refs, + llm_checkpoint_id, + platform_history_id, + ) + message_accumulator = BotMessageAccumulator() + agent_stats = {} + refs = {} + return saved_record + try: # Emit session_id first so clients can bind the stream immediately. session_info = { @@ -885,35 +923,7 @@ async def stream(): should_save = True if should_save: - message_parts_to_save = ( - message_accumulator.build_message_parts( - include_pending_tool_calls=True - ) - ) - plain_text = collect_plain_text_from_message_parts( - message_parts_to_save - ) - - # 提取 web_search_tavily 引用 - try: - refs = self._extract_web_search_refs( - plain_text, - message_parts_to_save, - ) - except Exception as e: - logger.exception( - f"Failed to extract web search refs: {e}", - exc_info=True, - ) - - saved_record = await self._save_bot_message( - webchat_conv_id, - message_parts_to_save, - agent_stats, - refs, - llm_checkpoint_id, - platform_history_id, - ) + saved_record = await flush_pending_bot_message() # 发送保存的消息信息给前端 if saved_record and not client_disconnected: saved_info = { @@ -930,15 +940,18 @@ async def stream(): yield f"data: {json.dumps(saved_info, ensure_ascii=False)}\n\n" except Exception: pass - message_accumulator = BotMessageAccumulator() - agent_stats = {} - refs = {} - if msg_type == "end": break except BaseException as e: logger.exception(f"WebChat stream unexpected error: {e}", exc_info=True) finally: + try: + await flush_pending_bot_message() + except Exception as e: + logger.exception( + f"Failed to persist pending webchat message: {e}", + exc_info=True, + ) webchat_queue_mgr.remove_back_queue(message_id) # 将消息放入会话特定的队列 diff --git a/astrbot/dashboard/routes/live_chat.py b/astrbot/dashboard/routes/live_chat.py index 8f4dc26fab..8fe113fcbe 100644 --- a/astrbot/dashboard/routes/live_chat.py +++ b/astrbot/dashboard/routes/live_chat.py @@ -453,6 +453,7 @@ async def _handle_chat_message( llm_checkpoint_id = str(uuid.uuid4()) try: + pending_bot_message_flusher = None chat_queue = webchat_queue_mgr.get_or_create_queue(session_id) await chat_queue.put( ( @@ -499,9 +500,47 @@ async def _handle_chat_message( agent_stats = {} refs = {} + async def flush_pending_bot_message(): + nonlocal message_accumulator, agent_stats, refs + if not (message_accumulator.has_content() or refs or agent_stats): + return None + + message_parts_to_save = message_accumulator.build_message_parts( + include_pending_tool_calls=True + ) + plain_text = collect_plain_text_from_message_parts( + message_parts_to_save + ) + try: + extracted_refs = self._extract_web_search_refs( + plain_text, + message_parts_to_save, + ) + except Exception as e: + logger.exception( + f"[Live Chat] Failed to extract web search refs: {e}", + exc_info=True, + ) + extracted_refs = refs + + saved_record = await self._save_bot_message( + session_id, + message_parts_to_save, + agent_stats, + extracted_refs, + llm_checkpoint_id, + ) + message_accumulator = BotMessageAccumulator() + agent_stats = {} + refs = {} + return saved_record + + pending_bot_message_flusher = flush_pending_bot_message + while True: if session.should_interrupt: session.should_interrupt = False + await flush_pending_bot_message() break try: @@ -574,30 +613,7 @@ async def _handle_chat_message( should_save = True if should_save: - message_parts_to_save = message_accumulator.build_message_parts( - include_pending_tool_calls=True - ) - plain_text = collect_plain_text_from_message_parts( - message_parts_to_save - ) - try: - refs = self._extract_web_search_refs( - plain_text, - message_parts_to_save, - ) - except Exception as e: - logger.exception( - f"[Live Chat] Failed to extract web search refs: {e}", - exc_info=True, - ) - - saved_record = await self._save_bot_message( - session_id, - message_parts_to_save, - agent_stats, - refs, - llm_checkpoint_id, - ) + saved_record = await flush_pending_bot_message() if saved_record: await self._send_chat_payload( session, @@ -614,10 +630,6 @@ async def _handle_chat_message( }, ) - message_accumulator = BotMessageAccumulator() - agent_stats = {} - refs = {} - if msg_type == "end": break @@ -633,6 +645,14 @@ async def _handle_chat_message( }, ) finally: + try: + if pending_bot_message_flusher is not None: + await pending_bot_message_flusher() + except Exception as e: + logger.exception( + f"[Live Chat] Failed to persist pending chat message: {e}", + exc_info=True, + ) session.is_processing = False webchat_queue_mgr.remove_back_queue(message_id) diff --git a/dashboard/index.html b/dashboard/index.html index 771f72affe..5ffa2c1178 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -9,7 +9,7 @@ diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css index 54785df237..3e1ffbc326 100644 --- a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css +++ b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css @@ -1,4 +1,4 @@ -/* Auto-generated MDI subset – 247 icons */ +/* Auto-generated MDI subset – 261 icons */ /* Do not edit manually. Run: pnpm run subset-icons */ @font-face { @@ -236,6 +236,10 @@ content: "\F0167"; } +.mdi-code-braces::before { + content: "\F0169"; +} + .mdi-code-json::before { content: "\F0626"; } @@ -324,6 +328,10 @@ content: "\F1634"; } +.mdi-database-search-outline::before { + content: "\F1636"; +} + .mdi-delete::before { content: "\F01B4"; } @@ -396,6 +404,10 @@ content: "\F022E"; } +.mdi-file-delimited-outline::before { + content: "\F0EA5"; +} + .mdi-file-document::before { content: "\F0219"; } @@ -416,6 +428,10 @@ content: "\F021C"; } +.mdi-file-music-outline::before { + content: "\F0E2A"; +} + .mdi-file-outline::before { content: "\F0224"; } @@ -436,6 +452,10 @@ content: "\F0A4D"; } +.mdi-file-video-outline::before { + content: "\F0E2C"; +} + .mdi-file-word-box::before { content: "\F022D"; } @@ -536,6 +556,10 @@ content: "\F0EFE"; } +.mdi-image-outline::before { + content: "\F0976"; +} + .mdi-import::before { content: "\F02FA"; } @@ -564,10 +588,38 @@ content: "\F0315"; } +.mdi-language-css3::before { + content: "\F031C"; +} + +.mdi-language-html5::before { + content: "\F031D"; +} + +.mdi-language-java::before { + content: "\F0B37"; +} + +.mdi-language-javascript::before { + content: "\F031E"; +} + .mdi-language-markdown::before { content: "\F0354"; } +.mdi-language-markdown-outline::before { + content: "\F0F5B"; +} + +.mdi-language-python::before { + content: "\F0320"; +} + +.mdi-language-typescript::before { + content: "\F06E6"; +} + .mdi-layers-outline::before { content: "\F09FE"; } @@ -688,6 +740,10 @@ content: "\F03D6"; } +.mdi-package-variant-closed::before { + content: "\F03D7"; +} + .mdi-page-first::before { content: "\F0600"; } @@ -812,10 +868,6 @@ content: "\F167A"; } -.mdi-send::before { - content: "\F048A"; -} - .mdi-server::before { content: "\F048B"; } @@ -1004,6 +1056,10 @@ content: "\F05B7"; } +.mdi-wrench-outline::before { + content: "\F0BE0"; +} + .mdi-zip-box::before { content: "\F05C4"; } diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff index b5c8cefd74..02f37fd732 100644 Binary files a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff and b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff differ diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 index 74e05e5454..a26ce1678c 100644 Binary files a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 and b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 differ diff --git a/dashboard/src/components/chat/Chat.vue b/dashboard/src/components/chat/Chat.vue index 1e465887d3..e5cae3da3b 100644 --- a/dashboard/src/components/chat/Chat.vue +++ b/dashboard/src/components/chat/Chat.vue @@ -1,5 +1,6 @@