diff --git a/plugins/linkai/README.md b/plugins/linkai/README.md index 2ac80b113..93474100a 100644 --- a/plugins/linkai/README.md +++ b/plugins/linkai/README.md @@ -98,6 +98,8 @@ 如果不想创建 `plugins/linkai/config.json` 配置,可以直接通过 `$linkai sum open` 指令开启该功能。 +也可以通过私聊(全局 `config.json` 中的 `linkai_app_code`)或者群聊绑定(通过`group_app_map`参数配置)的应用来开启该功能:在LinkAI平台 [应用配置](https://link-ai.tech/console/factory) 里添加并开启**内容总结**插件。 + #### 使用 功能开启后,向机器人发送 **文件**、 **分享链接卡片**、**图片** 即可生成摘要,进一步可以与文件或链接的内容进行多轮对话。如果需要关闭某种类型的内容总结,设置 `summary`配置中的type字段即可。 diff --git a/plugins/linkai/linkai.py b/plugins/linkai/linkai.py index c38319a51..59eb0dc0a 100644 --- a/plugins/linkai/linkai.py +++ b/plugins/linkai/linkai.py @@ -9,7 +9,7 @@ from common import const import os from .utils import Util -from config import plugin_config +from config import plugin_config, conf @plugins.register( @@ -28,7 +28,7 @@ def __init__(self): # 未加载到配置,使用模板中的配置 self.config = self._load_config_template() if self.config: - self.mj_bot = MJBot(self.config.get("midjourney")) + self.mj_bot = MJBot(self.config.get("midjourney"), self._fetch_group_app_code) self.sum_config = {} if self.config: self.sum_config = self.config.get("summary") @@ -56,7 +56,8 @@ def on_handle_context(self, e_context: EventContext): return if context.type != ContextType.IMAGE: _send_info(e_context, "正在为你加速生成摘要,请稍后") - res = LinkSummary().summary_file(file_path) + app_code = self._fetch_app_code(context) + res = LinkSummary().summary_file(file_path, app_code) if not res: if context.type != ContextType.IMAGE: _set_reply_text("因为神秘力量无法获取内容,请稍后再试吧", e_context, level=ReplyType.TEXT) @@ -74,7 +75,8 @@ def on_handle_context(self, e_context: EventContext): if not LinkSummary().check_url(context.content): return _send_info(e_context, "正在为你加速生成摘要,请稍后") - res = LinkSummary().summary_url(context.content) + app_code = self._fetch_app_code(context) + res = LinkSummary().summary_url(context.content, app_code) if not res: _set_reply_text("因为神秘力量无法获取文章内容,请稍后再试吧~", e_context, level=ReplyType.TEXT) return @@ -169,7 +171,7 @@ def _process_admin_cmd(self, e_context: EventContext): return if len(cmd) == 3 and cmd[1] == "sum" and (cmd[2] == "open" or cmd[2] == "close"): - # 知识库开关指令 + # 总结对话开关指令 if not Util.is_admin(e_context): _set_reply_text("需要管理员权限执行", e_context, level=ReplyType.ERROR) return @@ -192,14 +194,34 @@ def _process_admin_cmd(self, e_context: EventContext): return def _is_summary_open(self, context) -> bool: - if not self.sum_config or not self.sum_config.get("enabled"): - return False - if context.kwargs.get("isgroup") and not self.sum_config.get("group_enabled"): - return False - support_type = self.sum_config.get("type") or ["FILE", "SHARING"] - if context.type.name not in support_type and context.type.name != "TEXT": - return False - return True + # 获取远程应用插件状态 + remote_enabled = False + if context.kwargs.get("isgroup"): + # 群聊场景只查询群对应的app_code + group_name = context.get("msg").from_user_nickname + app_code = self._fetch_group_app_code(group_name) + if app_code: + remote_enabled = Util.fetch_app_plugin(app_code, "内容总结") + else: + # 非群聊场景使用全局app_code + app_code = conf().get("linkai_app_code") + if app_code: + remote_enabled = Util.fetch_app_plugin(app_code, "内容总结") + + # 基础条件:总开关开启且消息类型符合要求 + base_enabled = ( + self.sum_config + and self.sum_config.get("enabled") + and (context.type.name in ( + self.sum_config.get("type") or ["FILE", "SHARING"]) or context.type.name == "TEXT") + ) + + # 群聊:需要满足(总开关和群开关)或远程插件开启 + if context.kwargs.get("isgroup"): + return (base_enabled and self.sum_config.get("group_enabled")) or remote_enabled + + # 非群聊:只需要满足总开关或远程插件开启 + return base_enabled or remote_enabled # LinkAI 对话任务处理 def _is_chat_task(self, e_context: EventContext): @@ -230,6 +252,19 @@ def _fetch_group_app_code(self, group_name: str) -> str: app_code = group_mapping.get(group_name) or group_mapping.get("ALL_GROUP") return app_code + def _fetch_app_code(self, context) -> str: + """ + 根据主配置或者群聊名称获取对应的应用code,优先获取群聊配置的应用code + :param context: 上下文 + :return: 应用code + """ + app_code = conf().get("linkai_app_code") + if context.kwargs.get("isgroup"): + # 群聊场景只查询群对应的app_code + group_name = context.get("msg").from_user_nickname + app_code = self._fetch_group_app_code(group_name) + return app_code + def get_help_text(self, verbose=False, **kwargs): trigger_prefix = _get_trigger_prefix() help_text = "用于集成 LinkAI 提供的知识库、Midjourney绘画、文档总结、联网搜索等能力。\n\n" diff --git a/plugins/linkai/midjourney.py b/plugins/linkai/midjourney.py index 6500e573d..7e0a28736 100644 --- a/plugins/linkai/midjourney.py +++ b/plugins/linkai/midjourney.py @@ -10,6 +10,7 @@ from plugins import EventContext, EventAction from .utils import Util + INVALID_REQUEST = 410 NOT_FOUND_ORIGIN_IMAGE = 461 NOT_FOUND_TASK = 462 @@ -67,10 +68,11 @@ def __str__(self): # midjourney bot class MJBot: - def __init__(self, config): + def __init__(self, config, fetch_group_app_code): self.base_url = conf().get("linkai_api_base", "https://api.link-ai.tech") + "/v1/img/midjourney" self.headers = {"Authorization": "Bearer " + conf().get("linkai_api_key")} self.config = config + self.fetch_group_app_code = fetch_group_app_code self.tasks = {} self.temp_dict = {} self.tasks_lock = threading.Lock() @@ -98,7 +100,7 @@ def judge_mj_task_type(self, e_context: EventContext): return TaskType.VARIATION elif cmd_list[0].lower() == f"{trigger_prefix}mjr": return TaskType.RESET - elif context.type == ContextType.IMAGE_CREATE and self.config.get("use_image_create_prefix") and self.config.get("enabled"): + elif context.type == ContextType.IMAGE_CREATE and self.config.get("use_image_create_prefix") and self._is_mj_open(context): return TaskType.GENERATE def process_mj_task(self, mj_type: TaskType, e_context: EventContext): @@ -129,8 +131,8 @@ def process_mj_task(self, mj_type: TaskType, e_context: EventContext): self._set_reply_text(f"Midjourney绘画已{tips_text}", e_context, level=ReplyType.INFO) return - if not self.config.get("enabled"): - logger.warn("Midjourney绘画未开启,请查看 plugins/linkai/config.json 中的配置") + if not self._is_mj_open(context): + logger.warn("Midjourney绘画未开启,请查看 plugins/linkai/config.json 中的配置,或者在LinkAI平台 应用中添加/打开”MJ“插件") self._set_reply_text(f"Midjourney绘画未开启", e_context, level=ReplyType.INFO) return @@ -409,6 +411,25 @@ def find_tasks_by_user_id(self, user_id) -> list: result.append(task) return result + def _is_mj_open(self, context) -> bool: + # 获取远程应用插件状态 + remote_enabled = False + if context.kwargs.get("isgroup"): + # 群聊场景只查询群对应的app_code + group_name = context.get("msg").from_user_nickname + app_code = self.fetch_group_app_code(group_name) + if app_code: + remote_enabled = Util.fetch_app_plugin(app_code, "Midjourney") + else: + # 非群聊场景使用全局app_code + app_code = conf().get("linkai_app_code") + if app_code: + remote_enabled = Util.fetch_app_plugin(app_code, "Midjourney") + + # 本地配置 + base_enabled = self.config.get("enabled") + + return base_enabled or remote_enabled def _send(channel, reply: Reply, context, retry_cnt=0): try: diff --git a/plugins/linkai/summary.py b/plugins/linkai/summary.py index 84d74bcc6..9a3c4167c 100644 --- a/plugins/linkai/summary.py +++ b/plugins/linkai/summary.py @@ -9,19 +9,21 @@ class LinkSummary: def __init__(self): pass - def summary_file(self, file_path: str): + def summary_file(self, file_path: str, app_code: str): file_body = { "file": open(file_path, "rb"), "name": file_path.split("/")[-1], + "app_code": app_code } url = self.base_url() + "/v1/summary/file" res = requests.post(url, headers=self.headers(), files=file_body, timeout=(5, 300)) return self._parse_summary_res(res) - def summary_url(self, url: str): + def summary_url(self, url: str, app_code: str): url = html.unescape(url) body = { - "url": url + "url": url, + "app_code": app_code } res = requests.post(url=self.base_url() + "/v1/summary/url", headers=self.headers(), json=body, timeout=(5, 180)) return self._parse_summary_res(res) diff --git a/plugins/linkai/utils.py b/plugins/linkai/utils.py index c874cdfb1..7d1b4af07 100644 --- a/plugins/linkai/utils.py +++ b/plugins/linkai/utils.py @@ -1,7 +1,9 @@ +import requests +from common.log import logger from config import global_config from bridge.reply import Reply, ReplyType from plugins.event import EventContext, EventAction - +from config import conf class Util: @staticmethod @@ -26,3 +28,20 @@ def set_reply_text(content: str, e_context: EventContext, level: ReplyType = Rep reply = Reply(level, content) e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS + + @staticmethod + def fetch_app_plugin(app_code: str, plugin_name: str) -> bool: + headers = {"Authorization": "Bearer " + conf().get("linkai_api_key")} + # do http request + base_url = conf().get("linkai_api_base", "https://api.link-ai.tech") + params = {"app_code": app_code} + res = requests.get(url=base_url + "/v1/app/info", params=params, headers=headers, timeout=(5, 10)) + if res.status_code == 200: + plugins = res.json().get("data").get("plugins") + for plugin in plugins: + if plugin.get("name") and plugin.get("name") == plugin_name: + return True + return False + else: + logger.warning(f"[LinkAI] find app info exception, res={res}") + return False