From ca3999d25165dbe99af79ffb53a5d2933f557929 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 16 Nov 2024 16:45:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E7=83=AD=E9=87=8D=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/core/app.py | 21 ++++++++++++++++++--- pkg/plugin/context.py | 1 + pkg/plugin/manager.py | 16 +++++++++++++++- web/src/App.vue | 22 ++++++++++++++++------ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/pkg/core/app.py b/pkg/core/app.py index 907acfb5..46e70775 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -5,6 +5,7 @@ import asyncio import threading import traceback import enum +import sys from ..platform import manager as im_mgr from ..provider.session import sessionmgr as llm_session_mgr @@ -106,10 +107,8 @@ class Application: pass async def run(self): - await self.plugin_mgr.initialize_plugins() - try: - + await self.plugin_mgr.initialize_plugins() # 后续可能会允许动态重启其他任务 # 故为了防止程序在非 Ctrl-C 情况下退出,这里创建一个不会结束的协程 async def never_ending(): @@ -171,5 +170,21 @@ class Application: await self.platform_mgr.initialize() self.task_mgr.create_task(self.platform_mgr.run(), name="platform-manager", scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM]) + case core_entities.LifecycleControlScope.PLUGIN.value: + self.logger.info("执行热重载 scope="+scope) + await self.plugin_mgr.destroy_plugins() + + # 删除 sys.module 中所有的 plugins/* 下的模块 + for mod in list(sys.modules.keys()): + if mod.startswith("plugins."): + del sys.modules[mod] + + self.plugin_mgr = plugin_mgr.PluginManager(self) + await self.plugin_mgr.initialize() + + await self.plugin_mgr.initialize_plugins() + + await self.plugin_mgr.load_plugins() + await self.plugin_mgr.initialize_plugins() case _: pass diff --git a/pkg/plugin/context.py b/pkg/plugin/context.py index d4cd58d8..8c9e4a06 100644 --- a/pkg/plugin/context.py +++ b/pkg/plugin/context.py @@ -98,6 +98,7 @@ class BasePlugin(metaclass=abc.ABCMeta): pass def __del__(self): + """释放/禁用插件时被调用""" pass diff --git a/pkg/plugin/manager.py b/pkg/plugin/manager.py index 19d0ee49..2b8e887d 100644 --- a/pkg/plugin/manager.py +++ b/pkg/plugin/manager.py @@ -89,6 +89,7 @@ class PluginManager: return self.ap.logger.debug(f'释放插件 {plugin.plugin_name}') + plugin.plugin_inst.__del__() await plugin.plugin_inst.destroy() plugin.plugin_inst = None plugin.status = context.RuntimeContainerStatus.MOUNTED @@ -124,6 +125,9 @@ class PluginManager: } ) + task_context.trace('重载插件..', 'reload-plugin') + await self.ap.reload(scope='plugin') + async def uninstall_plugin( self, plugin_name: str, @@ -131,10 +135,15 @@ class PluginManager: ): """卸载插件 """ - await self.installer.uninstall_plugin(plugin_name, task_context) plugin_container = self.get_plugin_by_name(plugin_name) + if plugin_container is None: + raise ValueError(f'插件 {plugin_name} 不存在') + + await self.destroy_plugin(plugin_container) + await self.installer.uninstall_plugin(plugin_name, task_context) + await self.ap.ctr_mgr.plugin.post_remove_record( { "name": plugin_name, @@ -144,6 +153,9 @@ class PluginManager: } ) + task_context.trace('重载插件..', 'reload-plugin') + await self.ap.reload(scope='plugin') + async def update_plugin( self, plugin_name: str, @@ -167,6 +179,8 @@ class PluginManager: new_version="HEAD" ) + task_context.trace('重载插件..', 'reload-plugin') + await self.ap.reload(scope='plugin') def get_plugin_by_name(self, plugin_name: str) -> context.RuntimeContainer: """通过插件名获取插件 diff --git a/web/src/App.vue b/web/src/App.vue index 9e6ff8ca..ff8ebcfa 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -73,6 +73,11 @@ + + + 重载插件 + + @@ -143,21 +148,26 @@ function openDocs() { window.open('https://docs.langbot.app', '_blank') } +const reloadScopeLabel = { + 'platform': "消息平台", + 'plugin': "插件" +} + function reload(scope) { - proxy.$axios.post('/system/reload', + let label = reloadScopeLabel[scope] + proxy.$axios.post('/system/reload', { scope: scope }, { headers: { 'Content-Type': 'application/json' } } ).then(response => { if (response.data.code === 0) { - success('消息平台已重载') + success(label+'已重载') // 关闭菜单 } else { - error('消息平台重载失败:' + response.data.message) + error(label+'重载失败:' + response.data.message) } - }).catch(error => { - console.error(error) - error('消息平台重载失败:' + error) + }).catch(err => { + error(label+'重载失败:' + err) }) }