Optimize string concatenation in loops: replace += with list.join() (#3246)

* Initial plan

* Fix string concatenation performance issues in loops

Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com>

* Address code review feedback: Fix plugin list logic and add comment

Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com>

* Improve comment clarity for at_parts accumulation

Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com>
This commit is contained in:
Copilot
2025-11-02 13:00:59 +08:00
committed by GitHub
parent 92abc43c9d
commit e190bbeeed
18 changed files with 151 additions and 117 deletions

View File

@@ -250,14 +250,15 @@ async def migration_persona_data(
try: try:
begin_dialogs = persona.get("begin_dialogs", []) begin_dialogs = persona.get("begin_dialogs", [])
mood_imitation_dialogs = persona.get("mood_imitation_dialogs", []) mood_imitation_dialogs = persona.get("mood_imitation_dialogs", [])
mood_prompt = "" parts = []
user_turn = True user_turn = True
for mood_dialog in mood_imitation_dialogs: for mood_dialog in mood_imitation_dialogs:
if user_turn: if user_turn:
mood_prompt += f"A: {mood_dialog}\n" parts.append(f"A: {mood_dialog}\n")
else: else:
mood_prompt += f"B: {mood_dialog}\n" parts.append(f"B: {mood_dialog}\n")
user_turn = not user_turn user_turn = not user_turn
mood_prompt = "".join(parts)
system_prompt = persona.get("prompt", "") system_prompt = persona.get("prompt", "")
if mood_prompt: if mood_prompt:
system_prompt += f"Here are few shots of dialogs, you need to imitate the tone of 'B' in the following dialogs to respond:\n {mood_prompt}" system_prompt += f"Here are few shots of dialogs, you need to imitate the tone of 'B' in the following dialogs to respond:\n {mood_prompt}"

View File

@@ -21,8 +21,9 @@ class BaiduAipStrategy(ContentSafetyStrategy):
if "data" not in res: if "data" not in res:
return False, "" return False, ""
count = len(res["data"]) count = len(res["data"])
info = f"百度审核服务发现 {count} 处违规:\n" parts = [f"百度审核服务发现 {count} 处违规:\n"]
for i in res["data"]: for i in res["data"]:
info += f"{i['msg']}\n" parts.append(f"{i['msg']}\n")
info += "\n判断结果:" + res["conclusion"] parts.append("\n判断结果:" + res["conclusion"])
info = "".join(parts)
return False, info return False, info

View File

@@ -246,12 +246,13 @@ class ResultDecorateStage(Stage):
elif ( elif (
result.use_t2i_ is None and self.ctx.astrbot_config["t2i"] result.use_t2i_ is None and self.ctx.astrbot_config["t2i"]
) or result.use_t2i_: ) or result.use_t2i_:
plain_str = "" parts = []
for comp in result.chain: for comp in result.chain:
if isinstance(comp, Plain): if isinstance(comp, Plain):
plain_str += "\n\n" + comp.text parts.append("\n\n" + comp.text)
else: else:
break break
plain_str = "".join(parts)
if plain_str and len(plain_str) > self.t2i_word_threshold: if plain_str and len(plain_str) > self.t2i_word_threshold:
render_start = time.time() render_start = time.time()
try: try:

View File

@@ -91,33 +91,34 @@ class AstrMessageEvent(abc.ABC):
return self.message_str return self.message_str
def _outline_chain(self, chain: list[BaseMessageComponent] | None) -> str: def _outline_chain(self, chain: list[BaseMessageComponent] | None) -> str:
outline = ""
if not chain: if not chain:
return outline return ""
parts = []
for i in chain: for i in chain:
if isinstance(i, Plain): if isinstance(i, Plain):
outline += i.text parts.append(i.text)
elif isinstance(i, Image): elif isinstance(i, Image):
outline += "[图片]" parts.append("[图片]")
elif isinstance(i, Face): elif isinstance(i, Face):
outline += f"[表情:{i.id}]" parts.append(f"[表情:{i.id}]")
elif isinstance(i, At): elif isinstance(i, At):
outline += f"[At:{i.qq}]" parts.append(f"[At:{i.qq}]")
elif isinstance(i, AtAll): elif isinstance(i, AtAll):
outline += "[At:全体成员]" parts.append("[At:全体成员]")
elif isinstance(i, Forward): elif isinstance(i, Forward):
# 转发消息 # 转发消息
outline += "[转发消息]" parts.append("[转发消息]")
elif isinstance(i, Reply): elif isinstance(i, Reply):
# 引用回复 # 引用回复
if i.message_str: if i.message_str:
outline += f"[引用消息({i.sender_nickname}: {i.message_str})]" parts.append(f"[引用消息({i.sender_nickname}: {i.message_str})]")
else: else:
outline += "[引用消息]" parts.append("[引用消息]")
else: else:
outline += f"[{i.type}]" parts.append(f"[{i.type}]")
outline += " " parts.append(" ")
return outline return "".join(parts)
def get_message_outline(self) -> str: def get_message_outline(self) -> str:
"""获取消息概要。 """获取消息概要。

View File

@@ -315,6 +315,8 @@ class AiocqhttpAdapter(Platform):
abm.message.append(a) abm.message.append(a)
elif t == "at": elif t == "at":
first_at_self_processed = False first_at_self_processed = False
# Accumulate @ mention text for efficient concatenation
at_parts = []
for m in m_group: for m in m_group:
try: try:
@@ -354,13 +356,15 @@ class AiocqhttpAdapter(Platform):
first_at_self_processed = True first_at_self_processed = True
else: else:
# 非第一个@机器人或@其他用户添加到message_str # 非第一个@机器人或@其他用户添加到message_str
message_str += f" @{nickname}({m['data']['qq']}) " at_parts.append(f" @{nickname}({m['data']['qq']}) ")
else: else:
abm.message.append(At(qq=str(m["data"]["qq"]), name="")) abm.message.append(At(qq=str(m["data"]["qq"]), name=""))
except ActionFailed as e: except ActionFailed as e:
logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。") logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
except BaseException as e: except BaseException as e:
logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。") logger.error(f"获取 @ 用户信息失败: {e},此消息段将被忽略。")
message_str += "".join(at_parts)
else: else:
for m in m_group: for m in m_group:
a = ComponentTypes[t](**m["data"]) a = ComponentTypes[t](**m["data"])

View File

@@ -113,18 +113,18 @@ class DiscordPlatformEvent(AstrMessageEvent):
message: MessageChain, message: MessageChain,
) -> tuple[str, list[discord.File], discord.ui.View | None, list[discord.Embed]]: ) -> tuple[str, list[discord.File], discord.ui.View | None, list[discord.Embed]]:
"""将 MessageChain 解析为 Discord 发送所需的内容""" """将 MessageChain 解析为 Discord 发送所需的内容"""
content = "" content_parts = []
files = [] files = []
view = None view = None
embeds = [] embeds = []
reference_message_id = None reference_message_id = None
for i in message.chain: # 遍历消息链 for i in message.chain: # 遍历消息链
if isinstance(i, Plain): # 如果是文字类型的 if isinstance(i, Plain): # 如果是文字类型的
content += i.text content_parts.append(i.text)
elif isinstance(i, Reply): elif isinstance(i, Reply):
reference_message_id = i.id reference_message_id = i.id
elif isinstance(i, At): elif isinstance(i, At):
content += f"<@{i.qq}>" content_parts.append(f"<@{i.qq}>")
elif isinstance(i, Image): elif isinstance(i, Image):
logger.debug(f"[Discord] 开始处理 Image 组件: {i}") logger.debug(f"[Discord] 开始处理 Image 组件: {i}")
try: try:
@@ -238,6 +238,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
else: else:
logger.debug(f"[Discord] 忽略了不支持的消息组件: {i.type}") logger.debug(f"[Discord] 忽略了不支持的消息组件: {i.type}")
content = "".join(content_parts)
if len(content) > 2000: if len(content) > 2000:
logger.warning("[Discord] 消息内容超过2000字符将被截断。") logger.warning("[Discord] 消息内容超过2000字符将被截断。")
content = content[:2000] content = content[:2000]

View File

@@ -222,39 +222,41 @@ class SlackAdapter(Platform):
if element.get("type") == "rich_text_section": if element.get("type") == "rich_text_section":
# 处理富文本段落 # 处理富文本段落
section_elements = element.get("elements", []) section_elements = element.get("elements", [])
text_content = "" text_parts = []
for section_element in section_elements: for section_element in section_elements:
element_type = section_element.get("type", "") element_type = section_element.get("type", "")
if element_type == "text": if element_type == "text":
# 普通文本 # 普通文本
text_content += section_element.get("text", "") text_parts.append(section_element.get("text", ""))
elif element_type == "user": elif element_type == "user":
# @用户提及 # @用户提及
user_id = section_element.get("user_id", "") user_id = section_element.get("user_id", "")
if user_id: if user_id:
# 将之前的文本内容先添加到组件中 # 将之前的文本内容先添加到组件中
text_content = "".join(text_parts)
if text_content.strip(): if text_content.strip():
message_components.append( message_components.append(
Plain(text=text_content), Plain(text=text_content),
) )
text_content = "" text_parts = []
# 添加@提及组件 # 添加@提及组件
message_components.append(At(qq=user_id, name="")) message_components.append(At(qq=user_id, name=""))
elif element_type == "channel": elif element_type == "channel":
# #频道提及 # #频道提及
channel_id = section_element.get("channel_id", "") channel_id = section_element.get("channel_id", "")
text_content += f"#{channel_id}" text_parts.append(f"#{channel_id}")
elif element_type == "link": elif element_type == "link":
# 链接 # 链接
url = section_element.get("url", "") url = section_element.get("url", "")
link_text = section_element.get("text", url) link_text = section_element.get("text", url)
text_content += f"[{link_text}]({url})" text_parts.append(f"[{link_text}]({url})")
elif element_type == "emoji": elif element_type == "emoji":
# 表情符号 # 表情符号
emoji_name = section_element.get("name", "") emoji_name = section_element.get("name", "")
text_content += f":{emoji_name}:" text_parts.append(f":{emoji_name}:")
text_content = "".join(text_parts)
if text_content.strip(): if text_content.strip():
message_components.append(Plain(text=text_content)) message_components.append(Plain(text=text_content))

View File

@@ -148,14 +148,15 @@ class SlackMessageEvent(AstrMessageEvent):
) )
except Exception: except Exception:
# 如果块发送失败,尝试只发送文本 # 如果块发送失败,尝试只发送文本
fallback_text = "" parts = []
for segment in message.chain: for segment in message.chain:
if isinstance(segment, Plain): if isinstance(segment, Plain):
fallback_text += segment.text parts.append(segment.text)
elif isinstance(segment, File): elif isinstance(segment, File):
fallback_text += f" [文件: {segment.name}] " parts.append(f" [文件: {segment.name}] ")
elif isinstance(segment, Image): elif isinstance(segment, Image):
fallback_text += " [图片] " parts.append(" [图片] ")
fallback_text = "".join(parts)
if self.get_group_id(): if self.get_group_id():
await self.web_client.chat_postMessage( await self.web_client.chat_postMessage(

View File

@@ -146,14 +146,15 @@ class ProviderDashscope(ProviderOpenAIOfficial):
# RAG 引用脚标格式化 # RAG 引用脚标格式化
output_text = re.sub(r"<ref>\[(\d+)\]</ref>", r"[\1]", output_text) output_text = re.sub(r"<ref>\[(\d+)\]</ref>", r"[\1]", output_text)
if self.output_reference and response.output.get("doc_references", None): if self.output_reference and response.output.get("doc_references", None):
ref_str = "" ref_parts = []
for ref in response.output.get("doc_references", []) or []: for ref in response.output.get("doc_references", []) or []:
ref_title = ( ref_title = (
ref.get("title", "") ref.get("title", "")
if ref.get("title") if ref.get("title")
else ref.get("doc_name", "") else ref.get("doc_name", "")
) )
ref_str += f"{ref['index_id']}. {ref_title}\n" ref_parts.append(f"{ref['index_id']}. {ref_title}\n")
ref_str = "".join(ref_parts)
output_text += f"\n\n回答来源:\n{ref_str}" output_text += f"\n\n回答来源:\n{ref_str}"
llm_response = LLMResponse("assistant") llm_response = LLMResponse("assistant")

View File

@@ -51,15 +51,15 @@ class CommandFilter(HandlerFilter):
self._cmpl_cmd_names: list | None = None self._cmpl_cmd_names: list | None = None
def print_types(self): def print_types(self):
result = "" parts = []
for k, v in self.handler_params.items(): for k, v in self.handler_params.items():
if isinstance(v, type): if isinstance(v, type):
result += f"{k}({v.__name__})," parts.append(f"{k}({v.__name__}),")
elif isinstance(v, types.UnionType) or typing.get_origin(v) is typing.Union: elif isinstance(v, types.UnionType) or typing.get_origin(v) is typing.Union:
result += f"{k}({v})," parts.append(f"{k}({v}),")
else: else:
result += f"{k}({type(v).__name__})={v}," parts.append(f"{k}({type(v).__name__})={v},")
result = result.rstrip(",") result = "".join(parts).rstrip(",")
return result return result
def init_handler_md(self, handle_md: StarHandlerMetadata): def init_handler_md(self, handle_md: StarHandlerMetadata):

View File

@@ -66,7 +66,7 @@ class CommandGroupFilter(HandlerFilter):
event: AstrMessageEvent | None = None, event: AstrMessageEvent | None = None,
cfg: AstrBotConfig | None = None, cfg: AstrBotConfig | None = None,
) -> str: ) -> str:
result = "" parts = []
for sub_filter in sub_command_filters: for sub_filter in sub_command_filters:
if isinstance(sub_filter, CommandFilter): if isinstance(sub_filter, CommandFilter):
custom_filter_pass = True custom_filter_pass = True
@@ -74,31 +74,32 @@ class CommandGroupFilter(HandlerFilter):
custom_filter_pass = sub_filter.custom_filter_ok(event, cfg) custom_filter_pass = sub_filter.custom_filter_ok(event, cfg)
if custom_filter_pass: if custom_filter_pass:
cmd_th = sub_filter.print_types() cmd_th = sub_filter.print_types()
result += f"{prefix}├── {sub_filter.command_name}" line = f"{prefix}├── {sub_filter.command_name}"
if cmd_th: if cmd_th:
result += f" ({cmd_th})" line += f" ({cmd_th})"
else: else:
result += " (无参数指令)" line += " (无参数指令)"
if sub_filter.handler_md and sub_filter.handler_md.desc: if sub_filter.handler_md and sub_filter.handler_md.desc:
result += f": {sub_filter.handler_md.desc}" line += f": {sub_filter.handler_md.desc}"
result += "\n" parts.append(line + "\n")
elif isinstance(sub_filter, CommandGroupFilter): elif isinstance(sub_filter, CommandGroupFilter):
custom_filter_pass = True custom_filter_pass = True
if event and cfg: if event and cfg:
custom_filter_pass = sub_filter.custom_filter_ok(event, cfg) custom_filter_pass = sub_filter.custom_filter_ok(event, cfg)
if custom_filter_pass: if custom_filter_pass:
result += f"{prefix}├── {sub_filter.group_name}" parts.append(f"{prefix}├── {sub_filter.group_name}\n")
result += "\n" parts.append(
result += sub_filter.print_cmd_tree( sub_filter.print_cmd_tree(
sub_filter.sub_command_filters, sub_filter.sub_command_filters,
prefix + "", prefix + "",
event=event, event=event,
cfg=cfg, cfg=cfg,
)
) )
return result return "".join(parts)
def custom_filter_ok(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool: def custom_filter_ok(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
for custom_filter in self.custom_filter_list: for custom_filter in self.custom_filter_list:

View File

@@ -213,11 +213,12 @@ class AstrBotDashboard:
raise Exception(f"端口 {port} 已被占用") raise Exception(f"端口 {port} 已被占用")
display = f"\n ✨✨✨\n AstrBot v{VERSION} WebUI 已启动,可访问\n\n" parts = [f"\n ✨✨✨\n AstrBot v{VERSION} WebUI 已启动,可访问\n\n"]
display += f" ➜ 本地: http://localhost:{port}\n" parts.append(f" ➜ 本地: http://localhost:{port}\n")
for ip in ip_addr: for ip in ip_addr:
display += f" ➜ 网络: http://{ip}:{port}\n" parts.append(f" ➜ 网络: http://{ip}:{port}\n")
display += " ➜ 默认用户名和密码: astrbot\n ✨✨✨\n" parts.append(" ➜ 默认用户名和密码: astrbot\n ✨✨✨\n")
display = "".join(parts)
if not ip_addr: if not ip_addr:
display += ( display += (

View File

@@ -134,12 +134,13 @@ class ConversationCommands:
size_per_page, size_per_page,
) )
history = "" parts = []
for context in contexts: for context in contexts:
if len(context) > 150: if len(context) > 150:
context = context[:150] + "..." context = context[:150] + "..."
history += f"{context}\n" parts.append(f"{context}\n")
history = "".join(parts)
ret = ( ret = (
f"当前对话历史记录:" f"当前对话历史记录:"
f"{history or '无历史记录'}\n\n" f"{history or '无历史记录'}\n\n"
@@ -154,7 +155,7 @@ class ConversationCommands:
provider = self.context.get_using_provider(message.unified_msg_origin) provider = self.context.get_using_provider(message.unified_msg_origin)
if provider and provider.meta().type == "dify": if provider and provider.meta().type == "dify":
"""原有的Dify处理逻辑保持不变""" """原有的Dify处理逻辑保持不变"""
ret = "Dify 对话列表:\n" parts = ["Dify 对话列表:\n"]
assert isinstance(provider, ProviderDify) assert isinstance(provider, ProviderDify)
data = await provider.api_client.get_chat_convs(message.unified_msg_origin) data = await provider.api_client.get_chat_convs(message.unified_msg_origin)
idx = 1 idx = 1
@@ -162,12 +163,17 @@ class ConversationCommands:
ts_h = datetime.datetime.fromtimestamp(conv["updated_at"]).strftime( ts_h = datetime.datetime.fromtimestamp(conv["updated_at"]).strftime(
"%m-%d %H:%M", "%m-%d %H:%M",
) )
ret += f"{idx}. {conv['name']}({conv['id'][:4]})\n 上次更新:{ts_h}\n" parts.append(
f"{idx}. {conv['name']}({conv['id'][:4]})\n 上次更新:{ts_h}\n"
)
idx += 1 idx += 1
if idx == 1: if idx == 1:
ret += "没有找到任何对话。" parts.append("没有找到任何对话。")
dify_cid = provider.conversation_ids.get(message.unified_msg_origin, None) dify_cid = provider.conversation_ids.get(message.unified_msg_origin, None)
ret += f"\n\n用户: {message.unified_msg_origin}\n当前对话: {dify_cid}\n使用 /switch <序号> 切换对话。" parts.append(
f"\n\n用户: {message.unified_msg_origin}\n当前对话: {dify_cid}\n使用 /switch <序号> 切换对话。"
)
ret = "".join(parts)
message.set_result(MessageEventResult().message(ret)) message.set_result(MessageEventResult().message(ret))
return return
@@ -185,7 +191,7 @@ class ConversationCommands:
end_idx = start_idx + size_per_page end_idx = start_idx + size_per_page
conversations_paged = conversations_all[start_idx:end_idx] conversations_paged = conversations_all[start_idx:end_idx]
ret = "对话列表:\n---\n" parts = ["对话列表:\n---\n"]
"""全局序号从当前页的第一个开始""" """全局序号从当前页的第一个开始"""
global_index = start_idx + 1 global_index = start_idx + 1
@@ -204,10 +210,13 @@ class ConversationCommands:
) )
persona_id = persona["name"] persona_id = persona["name"]
title = _titles.get(conv.cid, "新对话") title = _titles.get(conv.cid, "新对话")
ret += f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_id}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n" parts.append(
f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_id}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n"
)
global_index += 1 global_index += 1
ret += "---\n" parts.append("---\n")
ret = "".join(parts)
curr_cid = await self.context.conversation_manager.get_curr_conversation_id( curr_cid = await self.context.conversation_manager.get_curr_conversation_id(
message.unified_msg_origin, message.unified_msg_origin,
) )

View File

@@ -59,10 +59,11 @@ class PersonaCommands:
.use_t2i(False), .use_t2i(False),
) )
elif l[1] == "list": elif l[1] == "list":
msg = "人格列表:\n" parts = ["人格列表:\n"]
for persona in self.context.provider_manager.personas: for persona in self.context.provider_manager.personas:
msg += f"- {persona['name']}\n" parts.append(f"- {persona['name']}\n")
msg += "\n\n*输入 `/persona view 人格名` 查看人格详细信息" parts.append("\n\n*输入 `/persona view 人格名` 查看人格详细信息")
msg = "".join(parts)
message.set_result(MessageEventResult().message(msg)) message.set_result(MessageEventResult().message(msg))
elif l[1] == "view": elif l[1] == "view":
if len(l) == 2: if len(l) == 2:

View File

@@ -13,14 +13,17 @@ class PluginCommands:
async def plugin_ls(self, event: AstrMessageEvent): async def plugin_ls(self, event: AstrMessageEvent):
"""获取已经安装的插件列表。""" """获取已经安装的插件列表。"""
plugin_list_info = "已加载的插件:\n" parts = ["已加载的插件:\n"]
for plugin in self.context.get_all_stars(): for plugin in self.context.get_all_stars():
plugin_list_info += f"- `{plugin.name}` By {plugin.author}: {plugin.desc}" line = f"- `{plugin.name}` By {plugin.author}: {plugin.desc}"
if not plugin.activated: if not plugin.activated:
plugin_list_info += " (未启用)" line += " (未启用)"
plugin_list_info += "\n" parts.append(line + "\n")
if plugin_list_info.strip() == "":
if len(parts) == 1:
plugin_list_info = "没有加载任何插件。" plugin_list_info = "没有加载任何插件。"
else:
plugin_list_info = "".join(parts)
plugin_list_info += "\n使用 /plugin help <插件名> 查看插件帮助和加载的指令。\n使用 /plugin on/off <插件名> 启用或者禁用插件。" plugin_list_info += "\n使用 /plugin help <插件名> 查看插件帮助和加载的指令。\n使用 /plugin on/off <插件名> 启用或者禁用插件。"
event.set_result( event.set_result(
@@ -103,14 +106,14 @@ class PluginCommands:
command_names.append(filter_.group_name) command_names.append(filter_.group_name)
if len(command_handlers) > 0: if len(command_handlers) > 0:
help_msg += "\n\n🔧 指令列表:\n" parts = ["\n\n🔧 指令列表:\n"]
for i in range(len(command_handlers)): for i in range(len(command_handlers)):
help_msg += f"- {command_names[i]}" line = f"- {command_names[i]}"
if command_handlers[i].desc: if command_handlers[i].desc:
help_msg += f": {command_handlers[i].desc}" line += f": {command_handlers[i].desc}"
help_msg += "\n" parts.append(line + "\n")
parts.append("\nTip: 指令的触发需要添加唤醒前缀,默认为 /。")
help_msg += "\nTip: 指令的触发需要添加唤醒前缀,默认为 /。" help_msg += "".join(parts)
ret = f"🧩 插件 {plugin_name} 帮助信息:\n" + help_msg ret = f"🧩 插件 {plugin_name} 帮助信息:\n" + help_msg
ret += "更多帮助信息请查看插件仓库 README。" ret += "更多帮助信息请查看插件仓库 README。"

View File

@@ -19,38 +19,39 @@ class ProviderCommands:
umo = event.unified_msg_origin umo = event.unified_msg_origin
if idx is None: if idx is None:
ret = "## 载入的 LLM 提供商\n" parts = ["## 载入的 LLM 提供商\n"]
for idx, llm in enumerate(self.context.get_all_providers()): for idx, llm in enumerate(self.context.get_all_providers()):
id_ = llm.meta().id id_ = llm.meta().id
ret += f"{idx + 1}. {id_} ({llm.meta().model})" line = f"{idx + 1}. {id_} ({llm.meta().model})"
provider_using = self.context.get_using_provider(umo=umo) provider_using = self.context.get_using_provider(umo=umo)
if provider_using and provider_using.meta().id == id_: if provider_using and provider_using.meta().id == id_:
ret += " (当前使用)" line += " (当前使用)"
ret += "\n" parts.append(line + "\n")
tts_providers = self.context.get_all_tts_providers() tts_providers = self.context.get_all_tts_providers()
if tts_providers: if tts_providers:
ret += "\n## 载入的 TTS 提供商\n" parts.append("\n## 载入的 TTS 提供商\n")
for idx, tts in enumerate(tts_providers): for idx, tts in enumerate(tts_providers):
id_ = tts.meta().id id_ = tts.meta().id
ret += f"{idx + 1}. {id_}" line = f"{idx + 1}. {id_}"
tts_using = self.context.get_using_tts_provider(umo=umo) tts_using = self.context.get_using_tts_provider(umo=umo)
if tts_using and tts_using.meta().id == id_: if tts_using and tts_using.meta().id == id_:
ret += " (当前使用)" line += " (当前使用)"
ret += "\n" parts.append(line + "\n")
stt_providers = self.context.get_all_stt_providers() stt_providers = self.context.get_all_stt_providers()
if stt_providers: if stt_providers:
ret += "\n## 载入的 STT 提供商\n" parts.append("\n## 载入的 STT 提供商\n")
for idx, stt in enumerate(stt_providers): for idx, stt in enumerate(stt_providers):
id_ = stt.meta().id id_ = stt.meta().id
ret += f"{idx + 1}. {id_}" line = f"{idx + 1}. {id_}"
stt_using = self.context.get_using_stt_provider(umo=umo) stt_using = self.context.get_using_stt_provider(umo=umo)
if stt_using and stt_using.meta().id == id_: if stt_using and stt_using.meta().id == id_:
ret += " (当前使用)" line += " (当前使用)"
ret += "\n" parts.append(line + "\n")
ret += "\n使用 /provider <序号> 切换 LLM 提供商。" parts.append("\n使用 /provider <序号> 切换 LLM 提供商。")
ret = "".join(parts)
if tts_providers: if tts_providers:
ret += "\n使用 /provider tts <序号> 切换 TTS 提供商。" ret += "\n使用 /provider tts <序号> 切换 TTS 提供商。"
@@ -128,16 +129,17 @@ class ProviderCommands:
.use_t2i(False), .use_t2i(False),
) )
return return
i = 1 parts = ["下面列出了此模型提供商可用模型:"]
ret = "下面列出了此模型提供商可用模型:" for i, model in enumerate(models, 1):
for model in models: parts.append(f"\n{i}. {model}")
ret += f"\n{i}. {model}"
i += 1
curr_model = prov.get_model() or "" curr_model = prov.get_model() or ""
ret += f"\n当前模型: [{curr_model}]" parts.append(f"\n当前模型: [{curr_model}]")
parts.append(
"\nTips: 使用 /model <模型名/编号>,即可实时更换模型。如目标模型不存在于上表,请输入模型名。"
)
ret += "\nTips: 使用 /model <模型名/编号>,即可实时更换模型。如目标模型不存在于上表,请输入模型名。" ret = "".join(parts)
message.set_result(MessageEventResult().message(ret).use_t2i(False)) message.set_result(MessageEventResult().message(ret).use_t2i(False))
elif isinstance(idx_or_name, int): elif isinstance(idx_or_name, int):
models = [] models = []
@@ -180,14 +182,15 @@ class ProviderCommands:
if index is None: if index is None:
keys_data = prov.get_keys() keys_data = prov.get_keys()
curr_key = prov.get_current_key() curr_key = prov.get_current_key()
ret = "Key:" parts = ["Key:"]
for i, k in enumerate(keys_data): for i, k in enumerate(keys_data, 1):
ret += f"\n{i + 1}. {k[:8]}" parts.append(f"\n{i}. {k[:8]}")
ret += f"\n当前 Key: {curr_key[:8]}" parts.append(f"\n当前 Key: {curr_key[:8]}")
ret += "\n当前模型: " + prov.get_model() parts.append("\n当前模型: " + prov.get_model())
ret += "\n使用 /key <idx> 切换 Key。" parts.append("\n使用 /key <idx> 切换 Key。")
ret = "".join(parts)
message.set_result(MessageEventResult().message(ret).use_t2i(False)) message.set_result(MessageEventResult().message(ret).use_t2i(False))
else: else:
keys_data = prov.get_keys() keys_data = prov.get_keys()

View File

@@ -119,13 +119,13 @@ class LongTermMemory:
if event.get_message_type() == MessageType.GROUP_MESSAGE: if event.get_message_type() == MessageType.GROUP_MESSAGE:
datetime_str = datetime.datetime.now().strftime("%H:%M:%S") datetime_str = datetime.datetime.now().strftime("%H:%M:%S")
final_message = f"[{event.message_obj.sender.nickname}/{datetime_str}]: " parts = [f"[{event.message_obj.sender.nickname}/{datetime_str}]: "]
cfg = self.cfg(event) cfg = self.cfg(event)
for comp in event.get_messages(): for comp in event.get_messages():
if isinstance(comp, Plain): if isinstance(comp, Plain):
final_message += f" {comp.text}" parts.append(f" {comp.text}")
elif isinstance(comp, Image): elif isinstance(comp, Image):
if cfg["image_caption"]: if cfg["image_caption"]:
try: try:
@@ -137,11 +137,13 @@ class LongTermMemory:
cfg["image_caption_provider_id"], cfg["image_caption_provider_id"],
cfg["image_caption_prompt"], cfg["image_caption_prompt"],
) )
final_message += f" [Image: {caption}]" parts.append(f" [Image: {caption}]")
except Exception as e: except Exception as e:
logger.error(f"获取图片描述失败: {e}") logger.error(f"获取图片描述失败: {e}")
else: else:
final_message += " [Image]" parts.append(" [Image]")
final_message = "".join(parts)
logger.debug(f"ltm | {event.unified_msg_origin} | {final_message}") logger.debug(f"ltm | {event.unified_msg_origin} | {final_message}")
self.session_chats[event.unified_msg_origin].append(final_message) self.session_chats[event.unified_msg_origin].append(final_message)
if len(self.session_chats[event.unified_msg_origin]) > cfg["max_cnt"]: if len(self.session_chats[event.unified_msg_origin]) > cfg["max_cnt"]:

View File

@@ -203,14 +203,15 @@ class Main(star.Star):
if not reminders: if not reminders:
yield event.plain_result("没有正在进行的待办事项。") yield event.plain_result("没有正在进行的待办事项。")
else: else:
reminder_str = "正在进行的待办事项:\n" parts = ["正在进行的待办事项:\n"]
for i, reminder in enumerate(reminders): for i, reminder in enumerate(reminders):
time_ = reminder.get("datetime", "") time_ = reminder.get("datetime", "")
if not time_: if not time_:
cron_expr = reminder.get("cron", "") cron_expr = reminder.get("cron", "")
time_ = reminder.get("cron_h", "") + f"(Cron: {cron_expr})" time_ = reminder.get("cron_h", "") + f"(Cron: {cron_expr})"
reminder_str += f"{i + 1}. {reminder['text']} - {time_}\n" parts.append(f"{i + 1}. {reminder['text']} - {time_}\n")
reminder_str += "\n使用 /reminder rm <id> 删除待办事项。\n" parts.append("\n使用 /reminder rm <id> 删除待办事项。\n")
reminder_str = "".join(parts)
yield event.plain_result(reminder_str) yield event.plain_result(reminder_str)
@reminder.command("rm") @reminder.command("rm")