feat: support channel start in sub thread

This commit is contained in:
zhayujie
2026-02-13 12:38:52 +08:00
parent a24b26a1ef
commit 46945942e1
8 changed files with 382 additions and 43 deletions

162
app.py
View File

@@ -7,11 +7,152 @@ import time
from channel import channel_factory from channel import channel_factory
from common import const from common import const
from config import load_config from common.log import logger
from config import load_config, conf
from plugins import * from plugins import *
import threading import threading
# Global channel manager for restart support
_channel_mgr = None
def get_channel_manager():
return _channel_mgr
class ChannelManager:
"""
Manage the lifecycle of a channel, supporting restart from sub-threads.
The channel.startup() runs in a daemon thread so that the main thread
remains available and a new channel can be started at any time.
"""
def __init__(self):
self._channel = None
self._channel_thread = None
self._lock = threading.Lock()
@property
def channel(self):
return self._channel
def start(self, channel_name: str, first_start: bool = False):
"""
Create and start a channel in a sub-thread.
If first_start is True, plugins and linkai client will also be initialized.
"""
with self._lock:
channel = channel_factory.create_channel(channel_name)
self._channel = channel
if first_start:
if channel_name in ["wx", "wxy", "terminal", "wechatmp", "web",
"wechatmp_service", "wechatcom_app", "wework",
const.FEISHU, const.DINGTALK]:
PluginManager().load_plugins()
if conf().get("use_linkai"):
try:
from common import linkai_client
threading.Thread(target=linkai_client.start, args=(channel, self), daemon=True).start()
except Exception as e:
pass
# Run channel.startup() in a daemon thread so we can restart later
self._channel_thread = threading.Thread(
target=self._run_channel, args=(channel,), daemon=True
)
self._channel_thread.start()
logger.info(f"[ChannelManager] Channel '{channel_name}' started in sub-thread")
def _run_channel(self, channel):
try:
channel.startup()
except Exception as e:
logger.error(f"[ChannelManager] Channel startup error: {e}")
logger.exception(e)
def stop(self):
"""
Stop the current channel. Since most channel startup() methods block
on an HTTP server or stream client, we stop by terminating the thread.
"""
with self._lock:
if self._channel is None:
return
channel_type = getattr(self._channel, 'channel_type', 'unknown')
logger.info(f"[ChannelManager] Stopping channel '{channel_type}'...")
# Try graceful stop if channel implements it
try:
if hasattr(self._channel, 'stop'):
self._channel.stop()
except Exception as e:
logger.warning(f"[ChannelManager] Error during channel stop: {e}")
self._channel = None
self._channel_thread = None
def restart(self, new_channel_name: str):
"""
Restart the channel with a new channel type.
Can be called from any thread (e.g. linkai config callback).
"""
logger.info(f"[ChannelManager] Restarting channel to '{new_channel_name}'...")
self.stop()
# Clear singleton cache so a fresh channel instance is created
_clear_singleton_cache(new_channel_name)
time.sleep(1) # Brief pause to allow resources to release
self.start(new_channel_name, first_start=False)
logger.info(f"[ChannelManager] Channel restarted to '{new_channel_name}' successfully")
def _clear_singleton_cache(channel_name: str):
"""
Clear the singleton cache for the channel class so that
a new instance can be created with updated config.
"""
cls_map = {
"wx": "channel.wechat.wechat_channel.WechatChannel",
"wxy": "channel.wechat.wechaty_channel.WechatyChannel",
"wcf": "channel.wechat.wcf_channel.WechatfChannel",
"web": "channel.web.web_channel.WebChannel",
"wechatmp": "channel.wechatmp.wechatmp_channel.WechatMPChannel",
"wechatmp_service": "channel.wechatmp.wechatmp_channel.WechatMPChannel",
"wechatcom_app": "channel.wechatcom.wechatcomapp_channel.WechatComAppChannel",
"wework": "channel.wework.wework_channel.WeworkChannel",
const.FEISHU: "channel.feishu.feishu_channel.FeiShuChanel",
const.DINGTALK: "channel.dingtalk.dingtalk_channel.DingTalkChanel",
}
module_path = cls_map.get(channel_name)
if not module_path:
return
# The singleton decorator stores instances in a closure dict keyed by class.
# We need to find the actual class and clear it from the closure.
try:
parts = module_path.rsplit(".", 1)
module_name, class_name = parts[0], parts[1]
import importlib
module = importlib.import_module(module_name)
# The module-level name is the wrapper function from @singleton
wrapper = getattr(module, class_name, None)
if wrapper and hasattr(wrapper, '__closure__') and wrapper.__closure__:
for cell in wrapper.__closure__:
try:
cell_contents = cell.cell_contents
if isinstance(cell_contents, dict):
cell_contents.clear()
logger.debug(f"[ChannelManager] Cleared singleton cache for {class_name}")
break
except ValueError:
pass
except Exception as e:
logger.warning(f"[ChannelManager] Failed to clear singleton cache: {e}")
def sigterm_handler_wrap(_signo): def sigterm_handler_wrap(_signo):
old_handler = signal.getsignal(_signo) old_handler = signal.getsignal(_signo)
@@ -25,22 +166,8 @@ def sigterm_handler_wrap(_signo):
signal.signal(_signo, func) signal.signal(_signo, func)
def start_channel(channel_name: str):
channel = channel_factory.create_channel(channel_name)
if channel_name in ["wx", "wxy", "terminal", "wechatmp", "web", "wechatmp_service", "wechatcom_app", "wework",
const.FEISHU, const.DINGTALK]:
PluginManager().load_plugins()
if conf().get("use_linkai"):
try:
from common import linkai_client
threading.Thread(target=linkai_client.start, args=(channel,)).start()
except Exception as e:
pass
channel.startup()
def run(): def run():
global _channel_mgr
try: try:
# load config # load config
load_config() load_config()
@@ -58,7 +185,8 @@ def run():
if channel_name == "wxy": if channel_name == "wxy":
os.environ["WECHATY_LOG"] = "warn" os.environ["WECHATY_LOG"] = "warn"
start_channel(channel_name) _channel_mgr = ChannelManager()
_channel_mgr.start(channel_name, first_start=True)
while True: while True:
time.sleep(1) time.sleep(1)

View File

@@ -19,6 +19,12 @@ class Channel(object):
""" """
raise NotImplementedError raise NotImplementedError
def stop(self):
"""
stop channel gracefully, called before restart
"""
pass
def handle_text(self, msg): def handle_text(self, msg):
""" """
process received msg process received msg

View File

@@ -90,13 +90,9 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
dingtalk_client_secret = conf().get('dingtalk_client_secret') dingtalk_client_secret = conf().get('dingtalk_client_secret')
def setup_logger(self): def setup_logger(self):
logger = logging.getLogger() # Suppress verbose logs from dingtalk_stream SDK
handler = logging.StreamHandler() logging.getLogger("dingtalk_stream").setLevel(logging.WARNING)
handler.setFormatter( return logging.getLogger("DingTalk")
logging.Formatter('%(asctime)s %(name)-8s %(levelname)-8s %(message)s [%(filename)s:%(lineno)d]'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -104,6 +100,7 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
self.logger = self.setup_logger() self.logger = self.setup_logger()
# 历史消息id暂存用于幂等控制 # 历史消息id暂存用于幂等控制
self.receivedMsgs = ExpiredDict(conf().get("expires_in_seconds", 3600)) self.receivedMsgs = ExpiredDict(conf().get("expires_in_seconds", 3600))
self._stream_client = None
logger.debug("[DingTalk] client_id={}, client_secret={} ".format( logger.debug("[DingTalk] client_id={}, client_secret={} ".format(
self.dingtalk_client_id, self.dingtalk_client_secret)) self.dingtalk_client_id, self.dingtalk_client_secret))
# 无需群校验和前缀 # 无需群校验和前缀
@@ -119,9 +116,19 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
def startup(self): def startup(self):
credential = dingtalk_stream.Credential(self.dingtalk_client_id, self.dingtalk_client_secret) credential = dingtalk_stream.Credential(self.dingtalk_client_id, self.dingtalk_client_secret)
client = dingtalk_stream.DingTalkStreamClient(credential) client = dingtalk_stream.DingTalkStreamClient(credential)
self._stream_client = client
client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self) client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self)
logger.info("[DingTalk] ✅ Stream connected, ready to receive messages") logger.info("[DingTalk] ✅ Stream connected, ready to receive messages")
client.start_forever() client.start_forever()
def stop(self):
if self._stream_client:
try:
self._stream_client.stop()
logger.info("[DingTalk] Stream client stopped")
except Exception as e:
logger.warning(f"[DingTalk] Error stopping stream client: {e}")
self._stream_client = None
def get_access_token(self): def get_access_token(self):
""" """

View File

@@ -12,6 +12,7 @@
""" """
import json import json
import logging
import os import os
import ssl import ssl
import threading import threading
@@ -32,6 +33,9 @@ from common.log import logger
from common.singleton import singleton from common.singleton import singleton
from config import conf from config import conf
# Suppress verbose logs from Lark SDK
logging.getLogger("Lark").setLevel(logging.WARNING)
URL_VERIFICATION = "url_verification" URL_VERIFICATION = "url_verification"
# 尝试导入飞书SDK,如果未安装则websocket模式不可用 # 尝试导入飞书SDK,如果未安装则websocket模式不可用
@@ -56,6 +60,7 @@ class FeiShuChanel(ChatChannel):
super().__init__() super().__init__()
# 历史消息id暂存用于幂等控制 # 历史消息id暂存用于幂等控制
self.receivedMsgs = ExpiredDict(60 * 60 * 7.1) self.receivedMsgs = ExpiredDict(60 * 60 * 7.1)
self._http_server = None
logger.debug("[FeiShu] app_id={}, app_secret={}, verification_token={}, event_mode={}".format( logger.debug("[FeiShu] app_id={}, app_secret={}, verification_token={}, event_mode={}".format(
self.feishu_app_id, self.feishu_app_secret, self.feishu_token, self.feishu_event_mode)) self.feishu_app_id, self.feishu_app_secret, self.feishu_token, self.feishu_event_mode))
# 无需群校验和前缀 # 无需群校验和前缀
@@ -73,6 +78,15 @@ class FeiShuChanel(ChatChannel):
else: else:
self._startup_webhook() self._startup_webhook()
def stop(self):
if self._http_server:
try:
self._http_server.stop()
logger.info("[FeiShu] HTTP server stopped")
except Exception as e:
logger.warning(f"[FeiShu] Error stopping HTTP server: {e}")
self._http_server = None
def _startup_webhook(self): def _startup_webhook(self):
"""启动HTTP服务器接收事件(webhook模式)""" """启动HTTP服务器接收事件(webhook模式)"""
logger.debug("[FeiShu] Starting in webhook mode...") logger.debug("[FeiShu] Starting in webhook mode...")
@@ -81,7 +95,14 @@ class FeiShuChanel(ChatChannel):
) )
app = web.application(urls, globals(), autoreload=False) app = web.application(urls, globals(), autoreload=False)
port = conf().get("feishu_port", 9891) port = conf().get("feishu_port", 9891)
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) func = web.httpserver.StaticMiddleware(app.wsgifunc())
func = web.httpserver.LogMiddleware(func)
server = web.httpserver.WSGIServer(("0.0.0.0", port), func)
self._http_server = server
try:
server.start()
except (KeyboardInterrupt, SystemExit):
server.stop()
def _startup_websocket(self): def _startup_websocket(self):
"""启动长连接接收事件(websocket模式)""" """启动长连接接收事件(websocket模式)"""
@@ -138,7 +159,7 @@ class FeiShuChanel(ChatChannel):
self.feishu_app_id, self.feishu_app_id,
self.feishu_app_secret, self.feishu_app_secret,
event_handler=event_handler, event_handler=event_handler,
log_level=lark.LogLevel.DEBUG if conf().get("debug") else lark.LogLevel.INFO log_level=lark.LogLevel.DEBUG if conf().get("debug") else lark.LogLevel.WARNING
) )
logger.debug("[FeiShu] Websocket client starting...") logger.debug("[FeiShu] Websocket client starting...")

View File

@@ -50,6 +50,7 @@ class WebChannel(ChatChannel):
self.msg_id_counter = 0 # 添加消息ID计数器 self.msg_id_counter = 0 # 添加消息ID计数器
self.session_queues = {} # 存储session_id到队列的映射 self.session_queues = {} # 存储session_id到队列的映射
self.request_to_session = {} # 存储request_id到session_id的映射 self.request_to_session = {} # 存储request_id到session_id的映射
self._http_server = None
def _generate_msg_id(self): def _generate_msg_id(self):
@@ -235,13 +236,24 @@ class WebChannel(ChatChannel):
logging.getLogger("web").setLevel(logging.ERROR) logging.getLogger("web").setLevel(logging.ERROR)
logging.getLogger("web.httpserver").setLevel(logging.ERROR) logging.getLogger("web.httpserver").setLevel(logging.ERROR)
# 抑制 web.py 默认的服务器启动消息 # Build WSGI app with middleware (same as runsimple but without print)
old_stdout = sys.stdout func = web.httpserver.StaticMiddleware(app.wsgifunc())
sys.stdout = io.StringIO() func = web.httpserver.LogMiddleware(func)
server = web.httpserver.WSGIServer(("0.0.0.0", port), func)
self._http_server = server
try: try:
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) server.start()
finally: except (KeyboardInterrupt, SystemExit):
sys.stdout = old_stdout server.stop()
def stop(self):
if self._http_server:
try:
self._http_server.stop()
logger.info("[WebChannel] HTTP server stopped")
except Exception as e:
logger.warning(f"[WebChannel] Error stopping HTTP server: {e}")
self._http_server = None
class RootHandler: class RootHandler:

View File

@@ -36,6 +36,7 @@ class WechatComAppChannel(ChatChannel):
self.agent_id = conf().get("wechatcomapp_agent_id") self.agent_id = conf().get("wechatcomapp_agent_id")
self.token = conf().get("wechatcomapp_token") self.token = conf().get("wechatcomapp_token")
self.aes_key = conf().get("wechatcomapp_aes_key") self.aes_key = conf().get("wechatcomapp_aes_key")
self._http_server = None
logger.info( logger.info(
"[wechatcom] Initializing WeCom app channel, corp_id: {}, agent_id: {}".format(self.corp_id, self.agent_id) "[wechatcom] Initializing WeCom app channel, corp_id: {}, agent_id: {}".format(self.corp_id, self.agent_id)
) )
@@ -51,13 +52,24 @@ class WechatComAppChannel(ChatChannel):
logger.info("[wechatcom] 📡 Listening on http://0.0.0.0:{}/wxcomapp/".format(port)) logger.info("[wechatcom] 📡 Listening on http://0.0.0.0:{}/wxcomapp/".format(port))
logger.info("[wechatcom] 🤖 Ready to receive messages") logger.info("[wechatcom] 🤖 Ready to receive messages")
# Suppress web.py's default server startup message # Build WSGI app with middleware (same as runsimple but without print)
old_stdout = sys.stdout func = web.httpserver.StaticMiddleware(app.wsgifunc())
sys.stdout = io.StringIO() func = web.httpserver.LogMiddleware(func)
server = web.httpserver.WSGIServer(("0.0.0.0", port), func)
self._http_server = server
try: try:
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) server.start()
finally: except (KeyboardInterrupt, SystemExit):
sys.stdout = old_stdout server.stop()
def stop(self):
if self._http_server:
try:
self._http_server.stop()
logger.info("[wechatcom] HTTP server stopped")
except Exception as e:
logger.warning(f"[wechatcom] Error stopping HTTP server: {e}")
self._http_server = None
def send(self, reply: Reply, context: Context): def send(self, reply: Reply, context: Context):
receiver = context["receiver"] receiver = context["receiver"]

View File

@@ -41,6 +41,7 @@ class WechatMPChannel(ChatChannel):
super().__init__() super().__init__()
self.passive_reply = passive_reply self.passive_reply = passive_reply
self.NOT_SUPPORT_REPLYTYPE = [] self.NOT_SUPPORT_REPLYTYPE = []
self._http_server = None
appid = conf().get("wechatmp_app_id") appid = conf().get("wechatmp_app_id")
secret = conf().get("wechatmp_app_secret") secret = conf().get("wechatmp_app_secret")
token = conf().get("wechatmp_token") token = conf().get("wechatmp_token")
@@ -69,7 +70,23 @@ class WechatMPChannel(ChatChannel):
urls = ("/wx", "channel.wechatmp.active_reply.Query") urls = ("/wx", "channel.wechatmp.active_reply.Query")
app = web.application(urls, globals(), autoreload=False) app = web.application(urls, globals(), autoreload=False)
port = conf().get("wechatmp_port", 8080) port = conf().get("wechatmp_port", 8080)
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) func = web.httpserver.StaticMiddleware(app.wsgifunc())
func = web.httpserver.LogMiddleware(func)
server = web.httpserver.WSGIServer(("0.0.0.0", port), func)
self._http_server = server
try:
server.start()
except (KeyboardInterrupt, SystemExit):
server.stop()
def stop(self):
if self._http_server:
try:
self._http_server.stop()
logger.info("[wechatmp] HTTP server stopped")
except Exception as e:
logger.warning(f"[wechatmp] Error stopping HTTP server: {e}")
self._http_server = None
def start_loop(self, loop): def start_loop(self, loop):
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)

View File

@@ -2,9 +2,12 @@ from bridge.context import Context, ContextType
from bridge.reply import Reply, ReplyType from bridge.reply import Reply, ReplyType
from common.log import logger from common.log import logger
from linkai import LinkAIClient, PushMsg from linkai import LinkAIClient, PushMsg
from config import conf, pconf, plugin_config, available_setting, write_plugin_config from config import conf, pconf, plugin_config, available_setting, write_plugin_config, get_root
from plugins import PluginManager from plugins import PluginManager
import threading
import time import time
import json
import os
chat_client: LinkAIClient chat_client: LinkAIClient
@@ -15,6 +18,7 @@ class ChatClient(LinkAIClient):
super().__init__(api_key, host) super().__init__(api_key, host)
self.channel = channel self.channel = channel
self.client_type = channel.channel_type self.client_type = channel.channel_type
self.channel_mgr = None
def on_message(self, push_msg: PushMsg): def on_message(self, push_msg: PushMsg):
session_id = push_msg.session_id session_id = push_msg.session_id
@@ -34,9 +38,12 @@ class ChatClient(LinkAIClient):
return return
local_config = conf() local_config = conf()
need_restart_channel = False
for key in config.keys(): for key in config.keys():
if key in available_setting and config.get(key) is not None: if key in available_setting and config.get(key) is not None:
local_config[key] = config.get(key) local_config[key] = config.get(key)
# 语音配置 # 语音配置
reply_voice_mode = config.get("reply_voice_mode") reply_voice_mode = config.get("reply_voice_mode")
if reply_voice_mode: if reply_voice_mode:
@@ -50,6 +57,55 @@ class ChatClient(LinkAIClient):
local_config["always_reply_voice"] = False local_config["always_reply_voice"] = False
local_config["voice_reply_voice"] = False local_config["voice_reply_voice"] = False
# Model configuration
if config.get("model"):
local_config["model"] = config.get("model")
# Channel configuration
if config.get("channelType"):
if local_config.get("channel_type") != config.get("channelType"):
local_config["channel_type"] = config.get("channelType")
need_restart_channel = True
# Channel-specific app credentials
current_channel_type = local_config.get("channel_type", "")
if config.get("app_id") is not None:
if current_channel_type == "feishu":
if local_config.get("feishu_app_id") != config.get("app_id"):
local_config["feishu_app_id"] = config.get("app_id")
need_restart_channel = True
elif current_channel_type == "dingtalk":
if local_config.get("dingtalk_client_id") != config.get("app_id"):
local_config["dingtalk_client_id"] = config.get("app_id")
need_restart_channel = True
elif current_channel_type == "wechatmp" or current_channel_type == "wechatmp_service":
if local_config.get("wechatmp_app_id") != config.get("app_id"):
local_config["wechatmp_app_id"] = config.get("app_id")
need_restart_channel = True
elif current_channel_type == "wechatcom_app":
if local_config.get("wechatcomapp_agent_id") != config.get("app_id"):
local_config["wechatcomapp_agent_id"] = config.get("app_id")
need_restart_channel = True
if config.get("app_secret"):
if current_channel_type == "feishu":
if local_config.get("feishu_app_secret") != config.get("app_secret"):
local_config["feishu_app_secret"] = config.get("app_secret")
need_restart_channel = True
elif current_channel_type == "dingtalk":
if local_config.get("dingtalk_client_secret") != config.get("app_secret"):
local_config["dingtalk_client_secret"] = config.get("app_secret")
need_restart_channel = True
elif current_channel_type == "wechatmp" or current_channel_type == "wechatmp_service":
if local_config.get("wechatmp_app_secret") != config.get("app_secret"):
local_config["wechatmp_app_secret"] = config.get("app_secret")
need_restart_channel = True
elif current_channel_type == "wechatcom_app":
if local_config.get("wechatcomapp_secret") != config.get("app_secret"):
local_config["wechatcomapp_secret"] = config.get("app_secret")
need_restart_channel = True
if config.get("admin_password"): if config.get("admin_password"):
if not pconf("Godcmd"): if not pconf("Godcmd"):
write_plugin_config({"Godcmd": {"password": config.get("admin_password"), "admin_users": []} }) write_plugin_config({"Godcmd": {"password": config.get("admin_password"), "admin_users": []} })
@@ -71,11 +127,67 @@ class ChatClient(LinkAIClient):
elif config.get("text_to_image") and config.get("text_to_image") in ["dall-e-2", "dall-e-3"]: elif config.get("text_to_image") and config.get("text_to_image") in ["dall-e-2", "dall-e-3"]:
if pconf("linkai")["midjourney"]: if pconf("linkai")["midjourney"]:
pconf("linkai")["midjourney"]["use_image_create_prefix"] = False pconf("linkai")["midjourney"]["use_image_create_prefix"] = False
# Save configuration to config.json file
self._save_config_to_file(local_config)
if need_restart_channel:
self._restart_channel(local_config.get("channel_type", ""))
def _restart_channel(self, new_channel_type: str):
"""
Restart the channel via ChannelManager when channel type changes.
"""
if self.channel_mgr:
logger.info(f"[LinkAI] Restarting channel to '{new_channel_type}'...")
threading.Thread(target=self._do_restart_channel, args=(self.channel_mgr, new_channel_type), daemon=True).start()
else:
logger.warning("[LinkAI] ChannelManager not available, please restart the application manually")
def _do_restart_channel(self, mgr, new_channel_type: str):
"""
Perform the channel restart in a separate thread to avoid blocking the config callback.
"""
try:
mgr.restart(new_channel_type)
# Update the linkai client's channel reference
if mgr.channel:
self.channel = mgr.channel
self.client_type = mgr.channel.channel_type
logger.info(f"[LinkAI] Channel reference updated to '{new_channel_type}'")
except Exception as e:
logger.error(f"[LinkAI] Channel restart failed: {e}")
def _save_config_to_file(self, local_config: dict):
"""
Save configuration to config.json file
"""
try:
config_path = os.path.join(get_root(), "config.json")
if not os.path.exists(config_path):
logger.warning(f"[LinkAI] config.json not found at {config_path}, skip saving")
return
# Read current config file
with open(config_path, "r", encoding="utf-8") as f:
file_config = json.load(f)
# Update file config with memory config
file_config.update(dict(local_config))
# Write back to file
with open(config_path, "w", encoding="utf-8") as f:
json.dump(file_config, f, indent=4, ensure_ascii=False)
logger.info("[LinkAI] Configuration saved to config.json successfully")
except Exception as e:
logger.error(f"[LinkAI] Failed to save configuration to config.json: {e}")
def start(channel): def start(channel, channel_mgr=None):
global chat_client global chat_client
chat_client = ChatClient(api_key=conf().get("linkai_api_key"), host="", channel=channel) chat_client = ChatClient(api_key=conf().get("linkai_api_key"), channel=channel)
chat_client.channel_mgr = channel_mgr
chat_client.config = _build_config() chat_client.config = _build_config()
chat_client.start() chat_client.start()
time.sleep(1.5) time.sleep(1.5)
@@ -97,14 +209,38 @@ def _build_config():
"nick_name_black_list": local_conf.get("nick_name_black_list"), "nick_name_black_list": local_conf.get("nick_name_black_list"),
"speech_recognition": "Y" if local_conf.get("speech_recognition") else "N", "speech_recognition": "Y" if local_conf.get("speech_recognition") else "N",
"text_to_image": local_conf.get("text_to_image"), "text_to_image": local_conf.get("text_to_image"),
"image_create_prefix": local_conf.get("image_create_prefix") "image_create_prefix": local_conf.get("image_create_prefix"),
"model": local_conf.get("model"),
"agent_max_context_turns": local_conf.get("agent_max_context_turns"),
"agent_max_context_tokens": local_conf.get("agent_max_context_tokens"),
"agent_max_steps": local_conf.get("agent_max_steps"),
"channelType": local_conf.get("channel_type")
} }
if local_conf.get("always_reply_voice"): if local_conf.get("always_reply_voice"):
config["reply_voice_mode"] = "always_reply_voice" config["reply_voice_mode"] = "always_reply_voice"
elif local_conf.get("voice_reply_voice"): elif local_conf.get("voice_reply_voice"):
config["reply_voice_mode"] = "voice_reply_voice" config["reply_voice_mode"] = "voice_reply_voice"
if pconf("linkai"): if pconf("linkai"):
config["group_app_map"] = pconf("linkai").get("group_app_map") config["group_app_map"] = pconf("linkai").get("group_app_map")
if plugin_config.get("Godcmd"): if plugin_config.get("Godcmd"):
config["admin_password"] = plugin_config.get("Godcmd").get("password") config["admin_password"] = plugin_config.get("Godcmd").get("password")
# Add channel-specific app credentials
current_channel_type = local_conf.get("channel_type", "")
if current_channel_type == "feishu":
config["app_id"] = local_conf.get("feishu_app_id")
config["app_secret"] = local_conf.get("feishu_app_secret")
elif current_channel_type == "dingtalk":
config["app_id"] = local_conf.get("dingtalk_client_id")
config["app_secret"] = local_conf.get("dingtalk_client_secret")
elif current_channel_type == "wechatmp" or current_channel_type == "wechatmp_service":
config["app_id"] = local_conf.get("wechatmp_app_id")
config["app_secret"] = local_conf.get("wechatmp_app_secret")
elif current_channel_type == "wechatcom_app":
config["app_id"] = local_conf.get("wechatcomapp_agent_id")
config["app_secret"] = local_conf.get("wechatcomapp_secret")
return config return config