From 8b6cc7446da19ce2129600a7c1aa9760656f5981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Wed, 11 Jun 2025 10:17:16 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(deploy):=20=E4=B8=BA=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=9C=8D=E5=8A=A1=E6=B7=BB=E5=8A=A0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- .../main/deploy/types/deployment.types.ts | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 electron/main/deploy/types/deployment.types.ts diff --git a/electron/main/deploy/types/deployment.types.ts b/electron/main/deploy/types/deployment.types.ts new file mode 100644 index 0000000..301c076 --- /dev/null +++ b/electron/main/deploy/types/deployment.types.ts @@ -0,0 +1,105 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +// licensed under the Mulan PSL v2. + +export interface DeploymentConfig { + repoUrl: string; + branch: string; + deployPath: string; +} + +export interface ModelConfig { + endpoint: string; + key: string; + name: string; + ctxLength?: number; + maxTokens?: number; +} + +export interface EmbeddingConfig { + type: 'openai' | 'mindie'; + endpoint: string; + key: string; + name: string; +} + +// 与 localDeploy.vue 中的 RuleForm 保持一致 +export interface ModelFormData { + url: string; + modelName: string; + apiKey: string; +} + +export interface DeploymentFormData { + ruleForm: ModelFormData; + embeddingRuleForm: ModelFormData; +} + +export interface DeploymentParams { + mainModel: ModelConfig; + embeddingModel: EmbeddingConfig; +} + +export interface DeploymentStatus { + status: + | 'idle' + | 'preparing' + | 'cloning' + | 'configuring' + | 'deploying' + | 'success' + | 'error'; + message: string; + progress: number; + currentStep?: string; + estimatedTime?: number; +} + +export interface DeploymentResult { + success: boolean; + message: string; + deploymentId?: string; + services?: { + mainModel: ModelConfig; + embeddingModel: EmbeddingConfig; + }; + logs?: string[]; + error?: DeploymentError; +} + +export interface DeploymentError { + code: string; + message: string; + details?: any; + recoverable?: boolean; + userAction?: string; + timestamp?: Date; +} + +export interface SystemCheck { + platform: string; + architecture: string; + nodeVersion: string; + availableCommands: string[]; + missingCommands: string[]; + passed: boolean; + diskSpace?: number; + networkConnected?: boolean; +} + +export interface RepositoryInfo { + branch: string; + commit: string; + timestamp: Date; + valid: boolean; +} + +export interface DeploymentOptions { + repoUrl?: string; + branch?: string; + useCache?: boolean; + forceClean?: boolean; + timeout?: number; + dryRun?: boolean; + skipSystemCheck?: boolean; + customConfigPath?: string; +} -- Gitee From a85aef48602d04f70e1365e90f182623a6c58c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Mon, 9 Jun 2025 10:08:24 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E9=83=A8=E7=BD=B2=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=20IPC=20&=20=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/main/common/ipc.ts | 34 +++++++++++++++++++ electron/main/index.ts | 3 +- electron/main/window/welcome.ts | 3 ++ electron/preload/shared.ts | 5 ++- electron/preload/types.d.ts | 19 +++++++++++ electron/preload/welcome.ts | 59 +++++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 2 deletions(-) diff --git a/electron/main/common/ipc.ts b/electron/main/common/ipc.ts index bb36f9c..90fb15d 100644 --- a/electron/main/common/ipc.ts +++ b/electron/main/common/ipc.ts @@ -11,9 +11,13 @@ import { ipcMain, BrowserWindow } from 'electron'; import { toggleTheme, setSystemTheme } from './theme'; import { getConfigManager, DesktopConfig } from './config'; import { completeWelcomeFlow, showWelcomeWindow } from '../window/welcome'; +import { DeploymentIPCHandler } from '../deploy/main/DeploymentIPCHandler'; import https from 'https'; import http from 'http'; +// 全局部署服务IPC处理程序实例 +let deploymentIPCHandler: DeploymentIPCHandler | null = null; + /** * 注册所有IPC监听器 */ @@ -22,6 +26,7 @@ export function registerIpcListeners(): void { registerWindowControlListeners(); registerConfigListeners(); registerWelcomeListeners(); + registerDeploymentListeners(); } /** @@ -241,3 +246,32 @@ async function validateServerConnection(url: string): Promise<{ }; } } + +/** + * 注册部署服务相关的IPC监听器 + */ +function registerDeploymentListeners(): void { + // 初始化部署服务IPC处理程序 + if (!deploymentIPCHandler) { + deploymentIPCHandler = new DeploymentIPCHandler(); + } +} + +/** + * 设置部署服务的主窗口引用 + */ +export function setDeploymentMainWindow(window: BrowserWindow): void { + if (deploymentIPCHandler) { + deploymentIPCHandler.setMainWindow(window); + } +} + +/** + * 清理部署服务IPC处理程序 + */ +export function cleanupDeploymentHandlers(): void { + if (deploymentIPCHandler) { + deploymentIPCHandler.cleanup(); + deploymentIPCHandler = null; + } +} diff --git a/electron/main/index.ts b/electron/main/index.ts index 8da22e9..81e00db 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -25,7 +25,7 @@ import { unregisterAllShortcuts, } from './common/shortcuts'; import { buildAppMenu } from './common/menu'; -import { registerIpcListeners } from './common/ipc'; +import { registerIpcListeners, cleanupDeploymentHandlers } from './common/ipc'; // 允许本地部署时使用无效证书,仅在 Electron 主进程下生效 if (process.versions.electron) { @@ -73,6 +73,7 @@ app.on('will-quit', () => { app.on('window-all-closed', () => { ipcMain.removeAllListeners(); + cleanupDeploymentHandlers(); if (process.platform !== 'darwin') { app.quit(); } diff --git a/electron/main/window/welcome.ts b/electron/main/window/welcome.ts index fb31606..6e133a1 100644 --- a/electron/main/window/welcome.ts +++ b/electron/main/window/welcome.ts @@ -11,6 +11,7 @@ import { BrowserWindow } from 'electron'; import * as path from 'node:path'; import { getConfigManager } from '../common/config'; +import { setDeploymentMainWindow } from '../common/ipc'; /** * 欢迎窗口引用 @@ -60,6 +61,8 @@ export function createWelcomeWindow(): BrowserWindow { welcomeWindow.once('ready-to-show', () => { if (welcomeWindow && !welcomeWindow.isDestroyed()) { welcomeWindow.show(); + // 设置部署服务的主窗口引用 + setDeploymentMainWindow(welcomeWindow); } }); diff --git a/electron/preload/shared.ts b/electron/preload/shared.ts index c48faf3..7286910 100644 --- a/electron/preload/shared.ts +++ b/electron/preload/shared.ts @@ -20,7 +20,10 @@ import type { ServerValidationResult } from './types'; * IPC 通道验证 */ export function validateIPC(channel: string): true | never { - if (!channel || !channel.startsWith('copilot:')) { + if ( + !channel || + (!channel.startsWith('copilot:') && !channel.startsWith('deployment:')) + ) { // 允许窗口状态变化事件通过验证 if ( channel === 'window-maximized-change' || diff --git a/electron/preload/types.d.ts b/electron/preload/types.d.ts index eb849c0..cfdde9c 100644 --- a/electron/preload/types.d.ts +++ b/electron/preload/types.d.ts @@ -89,6 +89,25 @@ export interface DesktopAppWelcomeAPI { cancel(): Promise; }; + // 部署服务 + deployment: { + startDeploymentFromForm(formData: { + url: string; + modelName: string; + apiKey: string; + embeddingForm: { + url: string; + modelName: string; + apiKey: string; + }; + }): Promise; + stopDeployment(): Promise; + getStatus(): Promise; + onStatusChange(callback: (status: any) => void): void; + removeStatusListener(): void; + cleanup(): Promise; + }; + // 系统信息 system: { platform: string; diff --git a/electron/preload/welcome.ts b/electron/preload/welcome.ts index 4029f26..0f0e932 100644 --- a/electron/preload/welcome.ts +++ b/electron/preload/welcome.ts @@ -11,6 +11,62 @@ import { contextBridge } from 'electron'; import { sharedConfigAPI, systemAPI, utilsAPI, safeIPC } from './shared'; +// 导入部署服务API +const deploymentAPI = { + /** + * 从前端表单开始部署 + */ + startDeploymentFromForm: (formData: { + url: string; + modelName: string; + apiKey: string; + embeddingForm: { + url: string; + modelName: string; + apiKey: string; + }; + }): Promise => { + return safeIPC.invoke('deployment:startFromForm', formData); + }, + + /** + * 停止部署 + */ + stopDeployment: (): Promise => { + return safeIPC.invoke('deployment:stop'); + }, + + /** + * 获取部署状态 + */ + getStatus: (): Promise => { + return safeIPC.invoke('deployment:getStatus'); + }, + + /** + * 监听部署状态变化 + */ + onStatusChange: (callback: (status: any) => void): void => { + safeIPC.on('deployment:statusChanged', (_event, status) => { + callback(status); + }); + }, + + /** + * 移除状态变化监听器 + */ + removeStatusListener: (): void => { + safeIPC.removeAllListeners('deployment:statusChanged'); + }, + + /** + * 清理部署文件 + */ + cleanup: (): Promise => { + return safeIPC.invoke('deployment:cleanup'); + }, +}; + /** * 欢迎界面专用的 preload 脚本 * 提供配置管理和欢迎流程相关的 API,使用统一的后端验证接口 @@ -67,6 +123,9 @@ const welcomeAPI = { // 欢迎流程管理(专用实现) welcome: welcomeFlowAPI, + // 部署服务(新增) + deployment: deploymentAPI, + // 系统信息(使用共享模块) system: systemAPI, -- Gitee From 63350064cee1ee8a441563a465b7dcfb481c8e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Mon, 9 Jun 2025 10:09:48 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E9=83=A8=E7=BD=B2=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=BC=96=E8=AF=91=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/welcome/deployServiceConnector.js | 250 +++++++++++++++++++++ electron/welcome/welcome.html | 13 +- package.json | 1 + scripts/build-preload.ts | 2 + scripts/build-welcome.ts | 28 ++- scripts/rollup.config.ts | 2 + tsconfig.json | 4 +- 7 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 electron/welcome/deployServiceConnector.js diff --git a/electron/welcome/deployServiceConnector.js b/electron/welcome/deployServiceConnector.js new file mode 100644 index 0000000..551dfed --- /dev/null +++ b/electron/welcome/deployServiceConnector.js @@ -0,0 +1,250 @@ +/** + * 本地部署服务连接器 + * 由于不能修改 localDeploy.vue,这个文件作为桥梁连接前端表单和部署服务 + */ + +// 等待 DOM 加载完成 +document.addEventListener('DOMContentLoaded', function () { + // 检查是否在 Electron 环境中 + if ( + typeof window.eulercopilotWelcome === 'undefined' || + typeof window.eulercopilotWelcome.deployment === 'undefined' + ) { + console.warn('部署服务不可用,请在 Electron 环境中运行'); + return; + } + + // 监听部署状态变化 + window.eulercopilotWelcome.deployment.onStatusChange((status) => { + console.log('部署状态更新:', status); + + // 可以在这里添加状态显示逻辑 + updateDeploymentStatus(status); + }); + + // 拦截并增强原有的 handleConfirm 方法 + enhanceHandleConfirm(); +}); + +/** + * 更新部署状态显示 + */ +function updateDeploymentStatus(status) { + // 创建或更新状态显示元素 + let statusElement = document.getElementById('deployment-status'); + if (!statusElement) { + statusElement = document.createElement('div'); + statusElement.id = 'deployment-status'; + statusElement.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + z-index: 9999; + max-width: 300px; + `; + document.body.appendChild(statusElement); + } + + // 根据状态更新显示内容 + const statusText = getStatusText(status); + const progressBar = + status.progress > 0 + ? ` +
+
+
+ ` + : ''; + + statusElement.innerHTML = ` +
部署状态
+
${statusText}
+
${status.message}
+ ${progressBar} + + `; + + // 如果部署完成或出错,5秒后自动隐藏 + if (status.status === 'success' || status.status === 'error') { + setTimeout(() => { + if (statusElement && statusElement.parentNode) { + statusElement.parentNode.removeChild(statusElement); + } + }, 5000); + } +} + +/** + * 获取状态文本 + */ +function getStatusText(status) { + const statusMap = { + idle: '待机', + preparing: '准备中', + cloning: '克隆仓库', + configuring: '配置中', + deploying: '部署中', + success: '部署成功', + error: '部署失败', + }; + return statusMap[status.status] || status.status; +} + +/** + * 获取状态颜色 + */ +function getStatusColor(status) { + const colorMap = { + idle: '#666', + preparing: '#1890ff', + cloning: '#1890ff', + configuring: '#1890ff', + deploying: '#1890ff', + success: '#52c41a', + error: '#ff4d4f', + }; + return colorMap[status] || '#666'; +} + +/** + * 关闭状态显示 + */ +function closeDeploymentStatus() { + const statusElement = document.getElementById('deployment-status'); + if (statusElement && statusElement.parentNode) { + statusElement.parentNode.removeChild(statusElement); + } +} + +/** + * 增强原有的 handleConfirm 方法 + */ +function enhanceHandleConfirm() { + // 这里需要等待 Vue 应用挂载完成 + setTimeout(() => { + // 查找确定按钮并添加点击事件监听 + const confirmButton = document.querySelector( + '.submit-btn .el-button[type="primary"]', + ); + if (confirmButton) { + confirmButton.addEventListener('click', async function (event) { + // 阻止原有事件 + event.stopImmediatePropagation(); + + // 获取表单数据 + const formData = getFormData(); + if (!formData) { + console.error('无法获取表单数据'); + return; + } + + try { + // 验证表单 + if (!validateFormData(formData)) { + return; + } + + // 开始部署 + await window.eulercopilotWelcome.deployment.startDeploymentFromForm( + formData, + ); + + console.log('部署已启动'); + } catch (error) { + console.error('部署启动失败:', error); + alert('部署启动失败: ' + error.message); + } + }); + } + }, 1000); +} + +/** + * 获取表单数据 + */ +function getFormData() { + try { + // 需要更精确的选择器,基于表单结构 + const mainModelUrl = document.querySelector( + '.model-ruleForm:first-child input[placeholder="请输入"]', + )?.value; + const mainModelName = document.querySelector( + '.model-ruleForm:first-child .el-form-item:nth-child(3) input', + )?.value; + const mainModelApiKey = document.querySelector( + '.model-ruleForm:first-child .el-form-item:nth-child(4) input', + )?.value; + + const embeddingUrl = document.querySelector( + '.model-ruleForm:last-child input[placeholder="请输入"]', + )?.value; + const embeddingModelName = document.querySelector( + '.model-ruleForm:last-child .el-form-item:nth-child(3) input', + )?.value; + const embeddingApiKey = document.querySelector( + '.model-ruleForm:last-child .el-form-item:nth-child(4) input', + )?.value; + + return { + url: mainModelUrl, + modelName: mainModelName, + apiKey: mainModelApiKey, + embeddingForm: { + url: embeddingUrl, + modelName: embeddingModelName, + apiKey: embeddingApiKey, + }, + }; + } catch (error) { + console.error('获取表单数据失败:', error); + return null; + } +} + +/** + * 验证表单数据 + */ +function validateFormData(formData) { + if (!formData.url) { + alert('请输入大模型 URL'); + return false; + } + if (!formData.modelName) { + alert('请输入大模型名称'); + return false; + } + if (!formData.apiKey) { + alert('请输入大模型 API Key'); + return false; + } + if (!formData.embeddingForm.url) { + alert('请输入 Embedding 模型 URL'); + return false; + } + if (!formData.embeddingForm.modelName) { + alert('请输入 Embedding 模型名称'); + return false; + } + if (!formData.embeddingForm.apiKey) { + alert('请输入 Embedding 模型 API Key'); + return false; + } + return true; +} + +// 暴露到全局以便调用 +window.closeDeploymentStatus = closeDeploymentStatus; diff --git a/electron/welcome/welcome.html b/electron/welcome/welcome.html index 780dcc7..01b6488 100644 --- a/electron/welcome/welcome.html +++ b/electron/welcome/welcome.html @@ -1,9 +1,10 @@ + - + 欢迎使用 openEuler Intelligence +
- + + + + - + + \ No newline at end of file diff --git a/package.json b/package.json index 23294e3..cf7aa58 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@dagrejs/dagre": "1.1.2", "@element-plus/icons-vue": "^2.3.1", "@microsoft/fetch-event-source": "^2.0.1", + "@types/js-yaml": "^4.0.9", "axios": "1.7.9", "codemirror": "^6.0.1", "dayjs": "1.11.9", diff --git a/scripts/build-preload.ts b/scripts/build-preload.ts index 7eb339b..d44031f 100644 --- a/scripts/build-preload.ts +++ b/scripts/build-preload.ts @@ -55,6 +55,8 @@ function createRollupConfig(entry: PreloadEntry): RollupOptions { sourceMap: false, noEmitOnError: true, include: compilationInclude, + target: 'ES2022', + lib: ['ES2022', 'DOM'], }), alias({ entries: { diff --git a/scripts/build-welcome.ts b/scripts/build-welcome.ts index c1fca4b..50e0b27 100644 --- a/scripts/build-welcome.ts +++ b/scripts/build-welcome.ts @@ -4,7 +4,13 @@ import vue from '@vitejs/plugin-vue'; import chalk from 'chalk'; import ora from 'ora'; import minimist from 'minimist'; -import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs'; +import { + mkdirSync, + existsSync, + readFileSync, + writeFileSync, + copyFileSync, +} from 'fs'; const argv = minimist(process.argv.slice(2)); const TAG = '[build-welcome.ts]'; @@ -40,11 +46,17 @@ const buildWelcome = async (watch = false) => { resolve: { alias: { '@': resolve(__dirname, '../src'), + // 添加别名让 deploy 能被正确解析 + '@deploy': resolve(__dirname, '../electron/main/deploy'), }, }, define: { 'process.env.NODE_ENV': watch ? '"development"' : '"production"', }, + // 确保 TypeScript 文件被正确处理 + esbuild: { + target: 'es2020', + }, }); // 复制并更新 HTML 文件 @@ -53,6 +65,20 @@ const buildWelcome = async (watch = false) => { mkdirSync(distDir, { recursive: true }); } + // 复制 deployServiceConnector.js 文件 + const connectorSrc = resolve( + __dirname, + '../electron/welcome/deployServiceConnector.js', + ); + const connectorDest = resolve(distDir, 'deployServiceConnector.js'); + if (existsSync(connectorSrc)) { + copyFileSync(connectorSrc, connectorDest); + } else { + console.warn( + `Warning: deployServiceConnector.js not found at ${connectorSrc}`, + ); + } + // 读取原始 HTML 文件 let htmlContent = readFileSync( resolve(__dirname, '../electron/welcome/welcome.html'), diff --git a/scripts/rollup.config.ts b/scripts/rollup.config.ts index acca2c6..2f390da 100644 --- a/scripts/rollup.config.ts +++ b/scripts/rollup.config.ts @@ -40,6 +40,8 @@ export default function (opts: ConfigOptions) { sourceMap: sourcemap, noEmitOnError: true, include: compilationInclude, + target: 'ES2022', // 支持私有字段语法 + lib: ['ES2022', 'DOM'], // 总是包含 DOM 库,因为 preload 文件需要访问 window }), alias({ entries: { diff --git a/tsconfig.json b/tsconfig.json index 5ad27fa..e8245a1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,9 @@ "paths": { "src*": ["src/*"], "@/*": ["src/*"], - "src/*": ["./src/*"] + "src/*": ["./src/*"], + "@deploy": ["electron/main/deploy"], + "@deploy/*": ["electron/main/deploy/*"] }, "lib": ["esnext", "dom", "dom.iterable", "scripthost"] }, -- Gitee From 3e252f532dbc6abc78f04d5c06740122c8ab7f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Mon, 9 Jun 2025 10:32:22 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=9C=8D=E5=8A=A1=E6=8E=A5=E5=8F=A3=E5=92=8C=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86=EF=BC=8C=E4=BC=98=E5=8C=96=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- .../main/deploy/core/DeploymentService.ts | 80 +++++- electron/preload/types.d.ts | 10 +- electron/preload/welcome.ts | 10 +- electron/welcome/deployServiceConnector.js | 249 ++---------------- electron/welcome/localDeploy.vue | 170 +++++++++--- electron/welcome/timeLine.vue | 201 ++++++++++++-- 6 files changed, 411 insertions(+), 309 deletions(-) diff --git a/electron/main/deploy/core/DeploymentService.ts b/electron/main/deploy/core/DeploymentService.ts index c9dc18e..fde1c3a 100644 --- a/electron/main/deploy/core/DeploymentService.ts +++ b/electron/main/deploy/core/DeploymentService.ts @@ -9,7 +9,6 @@ import { getCachePath } from '../../common/cache-conf'; import type { DeploymentParams, DeploymentStatus, - DeploymentConfig, } from '../types/deployment.types'; import { EnvironmentChecker } from './EnvironmentChecker'; import { ValuesYamlManager } from './ValuesYamlManager'; @@ -72,10 +71,12 @@ export class DeploymentService { */ async startDeployment(params: DeploymentParams): Promise { try { + // 第一阶段:准备安装环境 this.updateStatus({ status: 'preparing', - message: '准备部署环境...', + message: '准备安装环境...', progress: 0, + currentStep: 'preparing-environment', }); // 1. 检查环境 @@ -87,19 +88,31 @@ export class DeploymentService { // 3. 配置 values.yaml await this.configureValues(params); - // 4. 执行部署脚本 + // 4. 执行部署脚本中的工具安装部分 + await this.installTools(); + + // 更新准备环境完成状态 + this.updateStatus({ + message: '准备安装环境完成', + progress: 25, + currentStep: 'environment-ready', + }); + + // 第二到第四阶段:按顺序安装各个服务 await this.executeDeploymentScripts(); this.updateStatus({ status: 'success', message: '部署完成!', progress: 100, + currentStep: 'completed', }); } catch (error) { this.updateStatus({ status: 'error', message: `部署失败: ${error instanceof Error ? error.message : String(error)}`, progress: 0, + currentStep: 'failed', }); throw error; } @@ -189,26 +202,72 @@ export class DeploymentService { }); } + /** + * 安装工具(准备环境的一部分) + */ + private async installTools(): Promise { + this.updateStatus({ + status: 'preparing', + message: '安装必要工具...', + progress: 15, + currentStep: 'installing-tools', + }); + + const scriptsPath = path.join(this.deploymentPath, 'deploy/scripts'); + const toolsScriptPath = path.join( + scriptsPath, + '2-install-tools/install_tools.sh', + ); + + // 检查脚本文件是否存在 + if (fs.existsSync(toolsScriptPath)) { + // 给脚本添加执行权限并执行 + await execAsync( + `chmod +x "${toolsScriptPath}" && bash "${toolsScriptPath}"`, + { + cwd: scriptsPath, + timeout: 300000, // 5分钟超时 + }, + ); + } + + this.updateStatus({ + message: '工具安装完成', + progress: 20, + }); + } + /** * 执行部署脚本 */ private async executeDeploymentScripts(): Promise { const scriptsPath = path.join(this.deploymentPath, 'deploy/scripts'); - // 按照需求只执行指定的脚本 + // 按照 timeLine.vue 中的步骤定义,执行指定的脚本(排除工具安装,因为已在准备环境阶段执行) const scripts = [ - { name: '2-install-tools', path: '2-install-tools/install_tools.sh' }, { name: '6-install-databases', path: '6-install-databases/install_databases.sh', + displayName: '数据库服务', + step: 'install-databases', + progressStart: 30, + progressEnd: 50, }, { name: '7-install-authhub', path: '7-install-authhub/install_authhub.sh', + displayName: 'AuthHub 服务', + step: 'install-authhub', + progressStart: 50, + progressEnd: 75, }, { name: '8-install-EulerCopilot', path: '8-install-EulerCopilot/install_eulercopilot.sh', + displayName: 'Intelligence 服务', + step: 'install-intelligence', + progressStart: 75, + progressEnd: 95, }, ]; @@ -217,8 +276,9 @@ export class DeploymentService { this.updateStatus({ status: 'deploying', - message: `执行 ${script.name}...`, - progress: 70 + i * 7, + message: `正在安装 ${script.displayName}...`, + progress: script.progressStart, + currentStep: script.step, }); const scriptPath = path.join(scriptsPath, script.path); @@ -233,6 +293,12 @@ export class DeploymentService { cwd: scriptsPath, timeout: 300000, // 5分钟超时 }); + + // 更新完成状态 + this.updateStatus({ + message: `${script.displayName} 安装完成`, + progress: script.progressEnd, + }); } } diff --git a/electron/preload/types.d.ts b/electron/preload/types.d.ts index cfdde9c..c7f212c 100644 --- a/electron/preload/types.d.ts +++ b/electron/preload/types.d.ts @@ -92,10 +92,12 @@ export interface DesktopAppWelcomeAPI { // 部署服务 deployment: { startDeploymentFromForm(formData: { - url: string; - modelName: string; - apiKey: string; - embeddingForm: { + ruleForm: { + url: string; + modelName: string; + apiKey: string; + }; + embeddingRuleForm: { url: string; modelName: string; apiKey: string; diff --git a/electron/preload/welcome.ts b/electron/preload/welcome.ts index 0f0e932..196944f 100644 --- a/electron/preload/welcome.ts +++ b/electron/preload/welcome.ts @@ -17,10 +17,12 @@ const deploymentAPI = { * 从前端表单开始部署 */ startDeploymentFromForm: (formData: { - url: string; - modelName: string; - apiKey: string; - embeddingForm: { + ruleForm: { + url: string; + modelName: string; + apiKey: string; + }; + embeddingRuleForm: { url: string; modelName: string; apiKey: string; diff --git a/electron/welcome/deployServiceConnector.js b/electron/welcome/deployServiceConnector.js index 551dfed..17a385a 100644 --- a/electron/welcome/deployServiceConnector.js +++ b/electron/welcome/deployServiceConnector.js @@ -1,10 +1,13 @@ /** * 本地部署服务连接器 * 由于不能修改 localDeploy.vue,这个文件作为桥梁连接前端表单和部署服务 + * 注意:此文件已被 localDeploy.vue 中的直接调用取代,保留用于兼容性 */ // 等待 DOM 加载完成 document.addEventListener('DOMContentLoaded', function () { + console.log('部署服务连接器已加载(兼容性版本)'); + // 检查是否在 Electron 环境中 if ( typeof window.eulercopilotWelcome === 'undefined' || @@ -14,237 +17,25 @@ document.addEventListener('DOMContentLoaded', function () { return; } - // 监听部署状态变化 - window.eulercopilotWelcome.deployment.onStatusChange((status) => { - console.log('部署状态更新:', status); - - // 可以在这里添加状态显示逻辑 - updateDeploymentStatus(status); - }); - - // 拦截并增强原有的 handleConfirm 方法 - enhanceHandleConfirm(); + console.log('部署服务 API 可用'); }); -/** - * 更新部署状态显示 - */ -function updateDeploymentStatus(status) { - // 创建或更新状态显示元素 - let statusElement = document.getElementById('deployment-status'); - if (!statusElement) { - statusElement = document.createElement('div'); - statusElement.id = 'deployment-status'; - statusElement.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: white; - border: 1px solid #ddd; - border-radius: 4px; - padding: 16px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - z-index: 9999; - max-width: 300px; - `; - document.body.appendChild(statusElement); - } - - // 根据状态更新显示内容 - const statusText = getStatusText(status); - const progressBar = - status.progress > 0 - ? ` -
-
-
- ` - : ''; - - statusElement.innerHTML = ` -
部署状态
-
${statusText}
-
${status.message}
- ${progressBar} - - `; - - // 如果部署完成或出错,5秒后自动隐藏 - if (status.status === 'success' || status.status === 'error') { - setTimeout(() => { - if (statusElement && statusElement.parentNode) { - statusElement.parentNode.removeChild(statusElement); +// 保留一些实用函数用于调试 +window.deploymentUtils = { + getFormData: function () { + console.log('获取表单数据的实用函数(调试用)'); + return null; + }, + + getDeploymentStatus: async function () { + if (window.eulercopilotWelcome && window.eulercopilotWelcome.deployment) { + try { + return await window.eulercopilotWelcome.deployment.getStatus(); + } catch (error) { + console.error('获取部署状态失败:', error); + return null; } - }, 5000); - } -} - -/** - * 获取状态文本 - */ -function getStatusText(status) { - const statusMap = { - idle: '待机', - preparing: '准备中', - cloning: '克隆仓库', - configuring: '配置中', - deploying: '部署中', - success: '部署成功', - error: '部署失败', - }; - return statusMap[status.status] || status.status; -} - -/** - * 获取状态颜色 - */ -function getStatusColor(status) { - const colorMap = { - idle: '#666', - preparing: '#1890ff', - cloning: '#1890ff', - configuring: '#1890ff', - deploying: '#1890ff', - success: '#52c41a', - error: '#ff4d4f', - }; - return colorMap[status] || '#666'; -} - -/** - * 关闭状态显示 - */ -function closeDeploymentStatus() { - const statusElement = document.getElementById('deployment-status'); - if (statusElement && statusElement.parentNode) { - statusElement.parentNode.removeChild(statusElement); - } -} - -/** - * 增强原有的 handleConfirm 方法 - */ -function enhanceHandleConfirm() { - // 这里需要等待 Vue 应用挂载完成 - setTimeout(() => { - // 查找确定按钮并添加点击事件监听 - const confirmButton = document.querySelector( - '.submit-btn .el-button[type="primary"]', - ); - if (confirmButton) { - confirmButton.addEventListener('click', async function (event) { - // 阻止原有事件 - event.stopImmediatePropagation(); - - // 获取表单数据 - const formData = getFormData(); - if (!formData) { - console.error('无法获取表单数据'); - return; - } - - try { - // 验证表单 - if (!validateFormData(formData)) { - return; - } - - // 开始部署 - await window.eulercopilotWelcome.deployment.startDeploymentFromForm( - formData, - ); - - console.log('部署已启动'); - } catch (error) { - console.error('部署启动失败:', error); - alert('部署启动失败: ' + error.message); - } - }); } - }, 1000); -} - -/** - * 获取表单数据 - */ -function getFormData() { - try { - // 需要更精确的选择器,基于表单结构 - const mainModelUrl = document.querySelector( - '.model-ruleForm:first-child input[placeholder="请输入"]', - )?.value; - const mainModelName = document.querySelector( - '.model-ruleForm:first-child .el-form-item:nth-child(3) input', - )?.value; - const mainModelApiKey = document.querySelector( - '.model-ruleForm:first-child .el-form-item:nth-child(4) input', - )?.value; - - const embeddingUrl = document.querySelector( - '.model-ruleForm:last-child input[placeholder="请输入"]', - )?.value; - const embeddingModelName = document.querySelector( - '.model-ruleForm:last-child .el-form-item:nth-child(3) input', - )?.value; - const embeddingApiKey = document.querySelector( - '.model-ruleForm:last-child .el-form-item:nth-child(4) input', - )?.value; - - return { - url: mainModelUrl, - modelName: mainModelName, - apiKey: mainModelApiKey, - embeddingForm: { - url: embeddingUrl, - modelName: embeddingModelName, - apiKey: embeddingApiKey, - }, - }; - } catch (error) { - console.error('获取表单数据失败:', error); return null; - } -} - -/** - * 验证表单数据 - */ -function validateFormData(formData) { - if (!formData.url) { - alert('请输入大模型 URL'); - return false; - } - if (!formData.modelName) { - alert('请输入大模型名称'); - return false; - } - if (!formData.apiKey) { - alert('请输入大模型 API Key'); - return false; - } - if (!formData.embeddingForm.url) { - alert('请输入 Embedding 模型 URL'); - return false; - } - if (!formData.embeddingForm.modelName) { - alert('请输入 Embedding 模型名称'); - return false; - } - if (!formData.embeddingForm.apiKey) { - alert('请输入 Embedding 模型 API Key'); - return false; - } - return true; -} - -// 暴露到全局以便调用 -window.closeDeploymentStatus = closeDeploymentStatus; + }, +}; diff --git a/electron/welcome/localDeploy.vue b/electron/welcome/localDeploy.vue index 7f16531..cf76587 100644 --- a/electron/welcome/localDeploy.vue +++ b/electron/welcome/localDeploy.vue @@ -22,14 +22,35 @@ {{ $t('localDeploy.model') }} success - - + + - - + + - - + + - +