From cba916ae28b0c3968c1a6399f6b402741350faf4 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 27 Oct 2025 01:33:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A0hooks.py=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E5=8C=85=E4=BD=9C=E4=B8=BA=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8C=85=E5=AE=89=E8=A3=85=E5=88=B0jingrow=E9=87=8C=E9=9D=A2,?= =?UTF-8?q?=E5=8D=B8=E8=BD=BD=E6=89=A9=E5=B1=95=E5=8C=85=E6=97=B6=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E5=8D=B8=E8=BD=BDpackage=E5=8C=85=E5=90=AB=E7=9A=84?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetype/package/package_toolbar.vue | 7 +- .../jingrow/api/local_app_installer.py | 65 ++++++++++++++++ apps/jingrow/jingrow/utils/app_installer.py | 74 +++++++++++++++++++ 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/apps/jingrow/frontend/src/views/pagetype/package/package_toolbar.vue b/apps/jingrow/frontend/src/views/pagetype/package/package_toolbar.vue index 77dfc43..041fe69 100644 --- a/apps/jingrow/frontend/src/views/pagetype/package/package_toolbar.vue +++ b/apps/jingrow/frontend/src/views/pagetype/package/package_toolbar.vue @@ -110,13 +110,12 @@ const uninstallPackage = async (packageName: string) => { try { uninstalling.value = true - const response = await axios.post('/api/action/jingrow.core.pagetype.package.package.uninstall_package', { - package_name: packageName - }, { + // 调用本地API,它会先卸载数据库,再删除本地模块 + const response = await axios.post(`/jingrow/uninstall-extension/${packageName}`, {}, { headers: get_session_api_headers() }) - if (response.data.message.status === 'success') { + if (response.data.success) { message.success(props.context.t("Package '{0}' uninstalled successfully").replace('{0}', packageName)) // 卸载成功后跳转到Package列表页 diff --git a/apps/jingrow/jingrow/api/local_app_installer.py b/apps/jingrow/jingrow/api/local_app_installer.py index 205b5b2..1190f22 100644 --- a/apps/jingrow/jingrow/api/local_app_installer.py +++ b/apps/jingrow/jingrow/api/local_app_installer.py @@ -115,6 +115,10 @@ async def install_app_from_upload( backend_result = result.get('backend_result', {}) app_dir = backend_result.get('app_dir') + # 扩展包不添加到 Local Installed Apps(返回时没有 app_dir) + if not app_dir: + return result + # 对齐扫描安装的执行链 try: # 1. 添加到 Local Installed Apps PageType @@ -437,6 +441,67 @@ async def get_installed_apps(request: Request): raise HTTPException(status_code=500, detail=str(e)) +@router.post("/jingrow/uninstall-extension/{package_name}") +async def uninstall_extension(request: Request, package_name: str): + """卸载扩展包 - 先获取模块列表,卸载数据库,再删除本地目录""" + try: + # 获取 jingrow 应用目录 + apps_dir, _ = get_app_directories() + jingrow_backend_dir = apps_dir / "jingrow" / "jingrow" + + if not jingrow_backend_dir.exists(): + return {'success': False, 'error': '找不到 jingrow 应用目录'} + + # 1. 先从数据库获取模块列表 + modules = [] + try: + deps_url = f"{Config.jingrow_server_url}/api/action/jingrow.core.pagetype.package.package.get_package_dependencies" + response = requests.post( + deps_url, + json={'package_name': package_name}, + headers=get_jingrow_api_headers(), + timeout=60 + ) + + if response.status_code == 200: + result = response.json() + if result.get('message', {}).get('status') == 'success': + modules = result['message'].get('modules', []) + except Exception as e: + pass + + # 2. 调用云端API卸载数据库记录 + try: + uninstall_url = f"{Config.jingrow_server_url}/api/action/jingrow.core.pagetype.package.package.uninstall_package" + requests.post( + uninstall_url, + json={'package_name': package_name}, + headers=get_jingrow_api_headers(), + timeout=60 + ) + except Exception: + pass + + # 3. 删除本地模块目录 + removed_count = 0 + for module_name in modules: + module_dir_name = module_name.lower().replace(" ", "_").replace("-", "_") + module_dir = jingrow_backend_dir / module_dir_name + + if module_dir.exists(): + shutil.rmtree(module_dir) + removed_count += 1 + + return { + 'success': True, + 'message': f'扩展包卸载成功,删除了 {removed_count} 个本地模块目录', + 'removed_count': removed_count + } + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @router.post("/jingrow/uninstall/{app_name}") async def uninstall_app(request: Request, app_name: str): """卸载应用 - 直接删除整个app目录""" diff --git a/apps/jingrow/jingrow/utils/app_installer.py b/apps/jingrow/jingrow/utils/app_installer.py index 223ccbf..3131105 100644 --- a/apps/jingrow/jingrow/utils/app_installer.py +++ b/apps/jingrow/jingrow/utils/app_installer.py @@ -102,6 +102,16 @@ def analyze_package(temp_dir: str) -> Dict[str, Any]: except: pass + # 检查是否有 hooks.py(判断是否为独立应用) + hooks_path = os.path.join(root_dir, 'hooks.py') + if not os.path.exists(hooks_path): + # 在子目录中查找 + for root, dirs, files in os.walk(root_dir): + if 'hooks.py' in files: + hooks_path = os.path.join(root, 'hooks.py') + break + package_info['has_hooks'] = os.path.exists(hooks_path) + # 扫描文件 for root, dirs, files in os.walk(root_dir): for file in files: @@ -294,6 +304,16 @@ def install_app(uploaded_file_path: str, app_name: str = None) -> Dict[str, Any] package_info = analyze_result['data'] + # 检查是否有 hooks.py + has_hooks = package_info.get('has_hooks', False) + + if not has_hooks: + # 作为扩展包安装到 jingrow 应用内部 + result = install_package(temp_dir, package_info) + cleanup_temp_dir(temp_dir) + return result + + # 独立应用安装流程 # 确定应用名称 if not app_name: app_name = package_info.get('app_name') @@ -457,3 +477,57 @@ def install_extension_package(package_path: str) -> Dict[str, Any]: except Exception as e: return {'success': False, 'error': str(e)} + + +@handle_errors +def install_package(temp_dir: str, package_info: Dict[str, Any]) -> Dict[str, Any]: + """将扩展包安装到 jingrow 应用内部并同步到数据库""" + root_dir = package_info.get('root_dir', temp_dir) + app_name = package_info.get('app_name') + + # 获取 jingrow 应用目录 + apps_dir, _ = get_app_directories() + jingrow_backend_dir = apps_dir / "jingrow" / "jingrow" + + if not jingrow_backend_dir.exists(): + return {'success': False, 'error': '找不到 jingrow 应用目录'} + + # 检查是否有 app_name 子目录 + inner_app_dir = os.path.join(root_dir, app_name) + source_dir = inner_app_dir if os.path.exists(inner_app_dir) and os.path.isdir(inner_app_dir) else root_dir + + # 先同步到数据库 + try: + from jingrow.utils.jingrow_api import get_jingrow_api_headers + from jingrow.config import Config + import requests + + api_url = f"{Config.jingrow_server_url}/api/action/jingrow.ai.utils.jlocal.sync_app_files" + response = requests.post( + api_url, + json={'app_name': app_name, 'app_path': source_dir, 'force': True}, + headers=get_jingrow_api_headers(), + timeout=60 + ) + + if response.status_code != 200: + return {'success': False, 'error': f'同步到数据库失败: HTTP {response.status_code}'} + except Exception as e: + return {'success': False, 'error': f'同步到数据库失败: {str(e)}'} + + # 再复制文件到 jingrow(只复制模块目录,跳过配置和文档文件) + for item in os.listdir(source_dir): + if item in ['__pycache__', '.git', 'frontend'] or item.endswith('.json') or item in ['LICENSE.md', 'README.md', 'README']: + continue + + src = os.path.join(source_dir, item) + dst = jingrow_backend_dir / item + + if os.path.isdir(src): + if dst.exists(): + shutil.rmtree(dst) + shutil.copytree(src, dst) + else: + shutil.copy2(src, dst) + + return {'success': True, 'message': '扩展包已安装到 jingrow 应用', 'app_name': app_name}