Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 46 additions & 33 deletions astrbot/dashboard/routes/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = {
Expand All @@ -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)

# 将消息放入会话特定的队列
Expand Down
76 changes: 48 additions & 28 deletions astrbot/dashboard/routes/live_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ async def _handle_chat_message(
llm_checkpoint_id = str(uuid.uuid4())

try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider simplifying the new flush logic by defining flush_pending_bot_message as a default no-op within the try block and using it directly instead of introducing a separate pending_bot_message_flusher variable and None checks.

You can simplify the new logic while keeping the behavior by dropping pending_bot_message_flusher entirely and making flush_pending_bot_message always defined inside the try block.

Instead of:

try:
    pending_bot_message_flusher = None
    ...
    async def flush_pending_bot_message():
        ...
    pending_bot_message_flusher = flush_pending_bot_message
    ...
    if should_save:
        saved_record = await flush_pending_bot_message()
    ...
finally:
    try:
        if pending_bot_message_flusher is not None:
            await pending_bot_message_flusher()
    except Exception as e:
        ...

you can do:

try:
    async def flush_pending_bot_message():
        return None  # default no-op; overridden below

    chat_queue = webchat_queue_mgr.get_or_create_queue(session_id)
    await chat_queue.put(...)
    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"[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

    while True:
        if session.should_interrupt:
            session.should_interrupt = False
            await flush_pending_bot_message()
            break

        ...
        if should_save:
            saved_record = await flush_pending_bot_message()
            ...
finally:
    try:
        await flush_pending_bot_message()
    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)

Benefits:

  • Removes the pending_bot_message_flusher indirection and None checks.
  • Keeps the safety property that the flush function is always defined, even if an exception occurs before the accumulator is initialized.
  • Keeps all existing behavior (flush on interrupt, normal completion, and in finally).

Given you mentioned similar logic in chat.py, you might later extract this pattern into a small helper (e.g., a BotMessageFlushContext class that encapsulates message_accumulator, agent_stats, refs, and flush()), but even without that, this change locally reduces complexity without changing semantics.

pending_bot_message_flusher = None
chat_queue = webchat_queue_mgr.get_or_create_queue(session_id)
await chat_queue.put(
(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -614,10 +630,6 @@ async def _handle_chat_message(
},
)

message_accumulator = BotMessageAccumulator()
agent_stats = {}
refs = {}

if msg_type == "end":
break

Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<meta name="robots" content="noindex, nofollow" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Outfit&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap"
href="https://fonts.googleapis.com/css2?family=Outfit&family=Noto+Sans+SC:wght@100..900&display=swap"
/>
<!-- VAD (Voice Activity Detection) Libraries -->
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js"></script>
Expand Down
66 changes: 61 additions & 5 deletions dashboard/src/assets/mdi-subset/materialdesignicons-subset.css
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -236,6 +236,10 @@
content: "\F0167";
}

.mdi-code-braces::before {
content: "\F0169";
}

.mdi-code-json::before {
content: "\F0626";
}
Expand Down Expand Up @@ -324,6 +328,10 @@
content: "\F1634";
}

.mdi-database-search-outline::before {
content: "\F1636";
}

.mdi-delete::before {
content: "\F01B4";
}
Expand Down Expand Up @@ -396,6 +404,10 @@
content: "\F022E";
}

.mdi-file-delimited-outline::before {
content: "\F0EA5";
}

.mdi-file-document::before {
content: "\F0219";
}
Expand All @@ -416,6 +428,10 @@
content: "\F021C";
}

.mdi-file-music-outline::before {
content: "\F0E2A";
}

.mdi-file-outline::before {
content: "\F0224";
}
Expand All @@ -436,6 +452,10 @@
content: "\F0A4D";
}

.mdi-file-video-outline::before {
content: "\F0E2C";
}

.mdi-file-word-box::before {
content: "\F022D";
}
Expand Down Expand Up @@ -536,6 +556,10 @@
content: "\F0EFE";
}

.mdi-image-outline::before {
content: "\F0976";
}

.mdi-import::before {
content: "\F02FA";
}
Expand Down Expand Up @@ -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";
}
Expand Down Expand Up @@ -688,6 +740,10 @@
content: "\F03D6";
}

.mdi-package-variant-closed::before {
content: "\F03D7";
}

.mdi-page-first::before {
content: "\F0600";
}
Expand Down Expand Up @@ -812,10 +868,6 @@
content: "\F167A";
}

.mdi-send::before {
content: "\F048A";
}

.mdi-server::before {
content: "\F048B";
}
Expand Down Expand Up @@ -1004,6 +1056,10 @@
content: "\F05B7";
}

.mdi-wrench-outline::before {
content: "\F0BE0";
}

.mdi-zip-box::before {
content: "\F05C4";
}
Expand Down
Binary file not shown.
Binary file not shown.
Loading
Loading