diff --git a/apps/jingrow/frontend/src/views/dev/AppMarketplace.vue b/apps/jingrow/frontend/src/views/dev/AppMarketplace.vue index b087851..6aa9b75 100644 --- a/apps/jingrow/frontend/src/views/dev/AppMarketplace.vue +++ b/apps/jingrow/frontend/src/views/dev/AppMarketplace.vue @@ -140,6 +140,7 @@ import { NInput, NButton, NIcon, NSpin, NEmpty, NSelect, NPagination, useMessage import { Icon } from '@iconify/vue' import axios from 'axios' import { t } from '@/shared/i18n' +import { get_session_api_headers } from '@/shared/api/auth' const message = useMessage() const router = useRouter() @@ -212,9 +213,33 @@ function viewAppDetail(app: any) { router.push(`/app-marketplace/${app.name}`) } -function installApp(_app: any) { - // TODO: 实现安装应用 - message.info(t('Install feature coming soon')) +async function installApp(app: any) { + try { + if (!app.file_url) { + message.error(t('应用文件URL不存在')) + return + } + + const response = await axios.post('/jingrow/install-from-url', new URLSearchParams({ + url: app.file_url + }), { + headers: { + ...get_session_api_headers(), + 'Content-Type': 'application/x-www-form-urlencoded' + } + }) + + if (response.data.success) { + message.success(t('应用安装成功')) + // 刷新列表或跳转到已安装应用页面 + router.push('/installed-apps') + } else { + message.error(response.data.error || t('安装失败')) + } + } catch (error: any) { + console.error('Install app error:', error) + message.error(error.response?.data?.detail || t('安装失败')) + } } function getImageUrl(imageUrl: string): string { diff --git a/apps/jingrow/jingrow/api/local_app_installer.py b/apps/jingrow/jingrow/api/local_app_installer.py index 1190f22..99c3d9b 100644 --- a/apps/jingrow/jingrow/api/local_app_installer.py +++ b/apps/jingrow/jingrow/api/local_app_installer.py @@ -441,6 +441,115 @@ async def get_installed_apps(request: Request): raise HTTPException(status_code=500, detail=str(e)) +@router.post("/jingrow/install-from-url") +async def install_from_url(url: str = Form(...)): + """从URL安装应用或扩展包""" + try: + # 下载文件 + import tempfile + import requests + + current = Path(__file__).resolve() + root = current.parents[4] + tmp_dir = root / "tmp" + tmp_dir.mkdir(parents=True, exist_ok=True) + + # 创建临时文件 + temp_filename = f"download_{uuid.uuid4().hex[:8]}{Path(url).suffix}" + temp_file_path = tmp_dir / temp_filename + + # 下载文件 + response = requests.get(url, stream=True, timeout=300) + response.raise_for_status() + + with open(temp_file_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + # 使用现有的 install_app 函数安装 + try: + result = install_app(str(temp_file_path), None) + + if result.get('success'): + app_name_result = result.get('app_name') + 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 + from jingrow.utils.jingrow_api import get_single_pagetype + pagetype_result = get_single_pagetype("Local Installed Apps") + if pagetype_result.get('success'): + config = pagetype_result.get('config', {}) + apps_list = config.get('local_installed_apps', []) + else: + apps_list = [] + + # 检查是否已存在,如果存在则更新,否则添加 + app_exists = False + for app in apps_list: + if app.get('app_name', '') == app_name_result: + app['app_version'] = '1.0.0' + app['git_branch'] = 'main' + app_exists = True + break + + if not app_exists: + apps_list.append({ + 'app_name': app_name_result, + 'app_version': '1.0.0', + 'git_branch': 'main' + }) + + # 更新数据库 + await _update_local_installed_apps(apps_list) + + # 2. 调用 sync_app_files 同步文件到数据库 + if app_dir: + current = Path(__file__).resolve() + root = current.parents[4] + apps_dir = root / "apps" + backend_dir = apps_dir / app_name_result / app_name_result + if not backend_dir.exists(): + backend_dir = apps_dir / app_name_result + + if backend_dir.exists(): + try: + api_url = f"{Config.jingrow_server_url}/api/action/jingrow.ai.utils.jlocal.sync_app_files" + requests.post( + api_url, + json={'app_name': app_name_result, 'app_path': str(backend_dir), 'force': True}, + headers=get_jingrow_api_headers(), + timeout=60 + ) + except Exception: + pass + + except Exception: + pass + + return result + else: + raise HTTPException(status_code=400, detail=result.get('error', '安装失败')) + finally: + # 清理下载的文件 + try: + if temp_file_path.exists(): + os.unlink(temp_file_path) + except: + pass + + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @router.post("/jingrow/uninstall-extension/{package_name}") async def uninstall_extension(request: Request, package_name: str): """卸载扩展包 - 先获取模块列表,卸载数据库,再删除本地目录"""