mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
fix(web): fix session title generation fallback and reset Bridge on config change
This commit is contained in:
@@ -12,12 +12,30 @@ from typing import Optional
|
|||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
def _truncate_fallback_title(user_message: str, max_len: int = 30) -> str:
|
||||||
|
"""Pick the first non-empty line of the user message and truncate it."""
|
||||||
|
if not user_message:
|
||||||
|
return "New Chat"
|
||||||
|
first_line = ""
|
||||||
|
for line in user_message.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
first_line = line
|
||||||
|
break
|
||||||
|
if not first_line:
|
||||||
|
return "New Chat"
|
||||||
|
if len(first_line) > max_len:
|
||||||
|
first_line = first_line[:max_len].rstrip() + "..."
|
||||||
|
return first_line
|
||||||
|
|
||||||
|
|
||||||
def generate_session_title(user_message: str, assistant_reply: str = "") -> str:
|
def generate_session_title(user_message: str, assistant_reply: str = "") -> str:
|
||||||
"""
|
"""
|
||||||
Generate a short session title by calling the current bot's reply_text.
|
Generate a short session title by calling the current bot's reply_text.
|
||||||
Falls back to a truncated user message if the LLM call fails.
|
Falls back to the first line of the user message if the LLM call fails
|
||||||
|
or returns an obvious error sentinel.
|
||||||
"""
|
"""
|
||||||
fallback = user_message[:50].split("\n")[0].strip() or "New Chat"
|
fallback = _truncate_fallback_title(user_message)
|
||||||
try:
|
try:
|
||||||
from bridge.bridge import Bridge
|
from bridge.bridge import Bridge
|
||||||
from models.session_manager import Session
|
from models.session_manager import Session
|
||||||
@@ -36,8 +54,19 @@ def generate_session_title(user_message: str, assistant_reply: str = "") -> str:
|
|||||||
)}
|
)}
|
||||||
]
|
]
|
||||||
|
|
||||||
result = bot.reply_text(session)
|
result = bot.reply_text(session) or {}
|
||||||
|
# When bots fail (network error, auth error, rate limit, etc.) they
|
||||||
|
# typically return completion_tokens=0 with a sentinel content like
|
||||||
|
# "请再问我一次吧" / "我现在有点累了". Treat that as failure.
|
||||||
|
completion_tokens = result.get("completion_tokens", 0) or 0
|
||||||
raw = (result.get("content") or "").strip()
|
raw = (result.get("content") or "").strip()
|
||||||
|
if completion_tokens <= 0:
|
||||||
|
logger.warning(
|
||||||
|
f"[SessionService] Title generation got empty completion "
|
||||||
|
f"(completion_tokens={completion_tokens}, content='{raw[:50]}'), "
|
||||||
|
f"using fallback")
|
||||||
|
return fallback
|
||||||
|
|
||||||
title = re.sub(r'<think>.*?</think>', '', raw, flags=re.DOTALL).strip().strip('"\'')
|
title = re.sub(r'<think>.*?</think>', '', raw, flags=re.DOTALL).strip().strip('"\'')
|
||||||
logger.info(f"[SessionService] Title generation result: '{title}' (len={len(title)})")
|
logger.info(f"[SessionService] Title generation result: '{title}' (len={len(title)})")
|
||||||
if title and len(title) <= 50:
|
if title and len(title) <= 50:
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ def _extract_tool_results(content: Any) -> Dict[str, str]:
|
|||||||
|
|
||||||
def _group_into_display_turns(
|
def _group_into_display_turns(
|
||||||
rows: List[tuple],
|
rows: List[tuple],
|
||||||
|
include_thinking: bool = True,
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Convert raw (role, content_json, created_at) DB rows into display turns.
|
Convert raw (role, content_json, created_at) DB rows into display turns.
|
||||||
@@ -216,6 +217,8 @@ def _group_into_display_turns(
|
|||||||
continue
|
continue
|
||||||
btype = block.get("type")
|
btype = block.get("type")
|
||||||
if btype == "thinking":
|
if btype == "thinking":
|
||||||
|
if not include_thinking:
|
||||||
|
continue
|
||||||
txt = block.get("thinking", "").strip()
|
txt = block.get("thinking", "").strip()
|
||||||
if txt:
|
if txt:
|
||||||
steps.append({"type": "thinking", "content": txt})
|
steps.append({"type": "thinking", "content": txt})
|
||||||
@@ -601,9 +604,17 @@ class ConversationStore:
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
# Honour the current enable_thinking switch when building display turns
|
||||||
|
# so that toggling it off hides previously-saved thinking blocks too.
|
||||||
|
try:
|
||||||
|
from config import conf
|
||||||
|
include_thinking = bool(conf().get("enable_thinking", False))
|
||||||
|
except Exception:
|
||||||
|
include_thinking = False
|
||||||
|
|
||||||
# Strip seq for display grouping, but record max seq per visible user group
|
# Strip seq for display grouping, but record max seq per visible user group
|
||||||
plain_rows = [(role, content, created_at) for _seq, role, content, created_at in rows]
|
plain_rows = [(role, content, created_at) for _seq, role, content, created_at in rows]
|
||||||
visible = _group_into_display_turns(plain_rows)
|
visible = _group_into_display_turns(plain_rows, include_thinking=include_thinking)
|
||||||
|
|
||||||
# Build a mapping: find the seq of each visible user message to annotate context boundary.
|
# Build a mapping: find the seq of each visible user message to annotate context boundary.
|
||||||
# Walk through rows to find visible user message seqs in order.
|
# Walk through rows to find visible user message seqs in order.
|
||||||
|
|||||||
@@ -608,18 +608,55 @@ class AgentBridge:
|
|||||||
from config import conf
|
from config import conf
|
||||||
if not conf().get("conversation_persistence", True):
|
if not conf().get("conversation_persistence", True):
|
||||||
return
|
return
|
||||||
|
# When deep-thinking display is disabled, strip "thinking" content
|
||||||
|
# blocks before persisting so they don't resurface on history reload.
|
||||||
|
# The in-memory message list keeps them intact for this run's
|
||||||
|
# multi-turn LLM context.
|
||||||
|
thinking_enabled = bool(conf().get("enable_thinking", False))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
thinking_enabled = False
|
||||||
|
|
||||||
|
messages_to_store = new_messages
|
||||||
|
if not thinking_enabled:
|
||||||
|
messages_to_store = self._strip_thinking_blocks(new_messages)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from agent.memory import get_conversation_store
|
from agent.memory import get_conversation_store
|
||||||
get_conversation_store().append_messages(
|
get_conversation_store().append_messages(
|
||||||
session_id, new_messages, channel_type=channel_type
|
session_id, messages_to_store, channel_type=channel_type
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"[AgentBridge] Failed to persist messages for session={session_id}: {e}"
|
f"[AgentBridge] Failed to persist messages for session={session_id}: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _strip_thinking_blocks(messages: list) -> list:
|
||||||
|
"""Return a shallow copy of messages with assistant "thinking" blocks removed."""
|
||||||
|
cleaned = []
|
||||||
|
for msg in messages:
|
||||||
|
if not isinstance(msg, dict):
|
||||||
|
cleaned.append(msg)
|
||||||
|
continue
|
||||||
|
if msg.get("role") != "assistant":
|
||||||
|
cleaned.append(msg)
|
||||||
|
continue
|
||||||
|
content = msg.get("content")
|
||||||
|
if not isinstance(content, list):
|
||||||
|
cleaned.append(msg)
|
||||||
|
continue
|
||||||
|
filtered_blocks = [
|
||||||
|
b for b in content
|
||||||
|
if not (isinstance(b, dict) and b.get("type") == "thinking")
|
||||||
|
]
|
||||||
|
if len(filtered_blocks) == len(content):
|
||||||
|
cleaned.append(msg)
|
||||||
|
else:
|
||||||
|
new_msg = dict(msg)
|
||||||
|
new_msg["content"] = filtered_blocks
|
||||||
|
cleaned.append(new_msg)
|
||||||
|
return cleaned
|
||||||
|
|
||||||
def clear_session(self, session_id: str):
|
def clear_session(self, session_id: str):
|
||||||
"""
|
"""
|
||||||
Clear a specific session's agent and conversation history
|
Clear a specific session's agent and conversation history
|
||||||
|
|||||||
@@ -936,6 +936,19 @@ class ConfigHandler:
|
|||||||
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
|
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
logger.info(f"[WebChannel] Config updated: {list(applied.keys())}")
|
logger.info(f"[WebChannel] Config updated: {list(applied.keys())}")
|
||||||
|
|
||||||
|
# Reset Bridge so that bot routing reflects the new config.
|
||||||
|
# Without this, Bridge keeps its cached bot instance (e.g. LinkAIBot)
|
||||||
|
# even after the user switches bot_type / use_linkai / model in UI.
|
||||||
|
bridge_routing_keys = {"bot_type", "use_linkai", "model"}
|
||||||
|
if any(k in applied for k in bridge_routing_keys):
|
||||||
|
try:
|
||||||
|
from bridge.bridge import Bridge
|
||||||
|
Bridge().reset_bot()
|
||||||
|
logger.info("[WebChannel] Bridge bot routing reset due to config change")
|
||||||
|
except Exception as reset_err:
|
||||||
|
logger.warning(f"[WebChannel] Failed to reset bridge: {reset_err}")
|
||||||
|
|
||||||
return json.dumps({"status": "success", "applied": applied}, ensure_ascii=False)
|
return json.dumps({"status": "success", "applied": applied}, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating config: {e}")
|
logger.error(f"Error updating config: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user