From 47e5a9fcd8d01b67d4b3500f39b7ef817be179a6 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 03:56:58 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(welcome):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=AA=8C=E8=AF=81=E7=8A=B6=E6=80=81=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=EF=BC=8C=E4=BC=98=E5=8C=96=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E7=94=A8=E6=88=B7=E5=8F=8D=E9=A6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/welcome/lang/en.ts | 9 + electron/welcome/lang/zh.ts | 8 + electron/welcome/localDeploy.vue | 331 ++++++++++++++++++++++++++++- electron/welcome/onlineService.vue | 65 +++++- electron/welcome/timeLine.vue | 9 +- 5 files changed, 410 insertions(+), 12 deletions(-) diff --git a/electron/welcome/lang/en.ts b/electron/welcome/lang/en.ts index 4e11c32..d79422a 100644 --- a/electron/welcome/lang/en.ts +++ b/electron/welcome/lang/en.ts @@ -8,6 +8,7 @@ export default { pleaseInput: 'Please Input', validUrl: 'Please enter a valid URL', validationFailure: 'Validation failure', + connectionFailed: 'Connection failed', }, localDeploy: { model: 'Large model', @@ -24,6 +25,14 @@ export default { stopInstall: 'Stop installation', complete: 'Complete', retry: 'Retry', + validationFailed: 'Validation Failed', + authError: 'Authentication failed, please check if the API Key is correct', + functionCallNotSupported: + 'Function Call not supported, please use a model that supports this feature', + connectionError: 'Connection failed, please check if the URL is correct', + modelError: 'Model validation failed', + llmValidationFailed: 'LLM validation failed', + embeddingValidationFailed: 'Embedding model validation failed', }, onlineService: { serviceUrl: 'Backend Service Links', diff --git a/electron/welcome/lang/zh.ts b/electron/welcome/lang/zh.ts index 8ee6c69..6bf89e1 100644 --- a/electron/welcome/lang/zh.ts +++ b/electron/welcome/lang/zh.ts @@ -8,6 +8,7 @@ export default { pleaseInput: '请输入', validUrl: '请输入有效的 URL', validationFailure: '检验失败', + connectionFailed: '连接失败', }, localDeploy: { model: '大模型', @@ -24,6 +25,13 @@ export default { stopInstall: '停止安装', complete: '完成', retry: '重试', + validationFailed: '校验失败', + authError: '鉴权失败,请检查 API Key 是否正确', + functionCallNotSupported: '不支持 Function Call,请使用支持此功能的模型', + connectionError: '连接失败,请检查 URL 是否正确', + modelError: '模型验证失败', + llmValidationFailed: '大模型校验失败', + embeddingValidationFailed: 'Embedding 模型校验失败', }, onlineService: { serviceUrl: '后端服务链接', diff --git a/electron/welcome/localDeploy.vue b/electron/welcome/localDeploy.vue index e9fde60..bd9d54e 100644 --- a/electron/welcome/localDeploy.vue +++ b/electron/welcome/localDeploy.vue @@ -20,7 +20,28 @@ >
{{ $t('localDeploy.model') }} - success + validating + success + error
{{ $t('localDeploy.embeddingModel') }} - success + validating + success + error
import { reactive, ref, watch } from 'vue'; import type { FormInstance, FormRules } from 'element-plus'; +import { ElMessageBox } from 'element-plus'; import copyIcon from './assets/svgs/copy_icon.svg'; import successIcon from './assets/svgs/success.svg'; +import errorIcon from './assets/svgs/error.svg'; +import loadingIcon from './assets/svgs/upload-loading.svg'; import TimeLine from './timeLine.vue'; import leftArrowIcon from './assets/svgs/left_arrow.svg'; import i18n from './lang/index'; @@ -164,6 +209,14 @@ const embeddingRuleFormRef = ref(); const isConfirmDisabled = ref(true); const isTimeLine = ref(false); +// 模型验证状态 +const llmValidationStatus = ref<'none' | 'validating' | 'success' | 'error'>( + 'none', +); +const embeddingValidationStatus = ref< + 'none' | 'validating' | 'success' | 'error' +>('none'); + const rules = reactive>({ url: [ { @@ -211,6 +264,10 @@ watch( // 如果两个表单都有效,则启用按钮 isConfirmDisabled.value = !(isRuleFormValid && isEmbeddingRuleFormValid); + + // 当表单数据变化时,重置验证状态 + llmValidationStatus.value = 'none'; + embeddingValidationStatus.value = 'none'; }, { immediate: true, deep: true }, // 立即执行一次初始检查 ); @@ -219,7 +276,207 @@ const copyText = (ruleForm: RuleForm) => { embeddingRuleForm.url = ruleForm.url; embeddingRuleForm.apiKey = ruleForm.apiKey; }; + +// OpenAI API 校验函数 +const validateOpenAIModel = async ( + url: string, + apiKey: string, + modelName: string, + isLLM: boolean = true, +) => { + try { + // 格式化 URL + const baseURL = url.replace(/\/+$/, ''); + + // 首先尝试检查模型列表(如果支持的话) + let modelExists = true; // 默认假设模型存在,除非确认不存在 + + try { + const apiURL = baseURL.includes('/v1') + ? `${baseURL}/models` + : `${baseURL}/v1/models`; + + const modelsResponse = await fetch(apiURL, { + method: 'GET', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + }); + + if (modelsResponse.ok) { + const modelsData = await modelsResponse.json(); + const availableModels = modelsData.data || []; + + // 如果成功获取到模型列表,则检查模型是否存在 + modelExists = availableModels.some( + (model: any) => model.id === modelName, + ); + + if (!modelExists) { + throw new Error('model_not_found'); + } + } else if (modelsResponse.status === 401) { + throw new Error('auth'); + } + // 如果是其他错误(如404),可能是不支持models接口,继续后续验证 + } catch (modelsError: any) { + // 如果是认证错误,直接抛出 + if (modelsError.message === 'auth') { + throw modelsError; + } + if (modelsError.message === 'model_not_found') { + throw modelsError; + } + // 其他错误(如不支持models接口)则继续进行实际调用测试 + console.warn( + 'Models API not supported or failed, proceeding with direct testing', + ); + } + + if (isLLM) { + const chatURL = baseURL.includes('/v1') + ? `${baseURL}/chat/completions` + : `${baseURL}/v1/chat/completions`; + + // 首先测试基本的聊天功能 + const basicChatTest = { + model: modelName, + messages: [{ role: 'user', content: 'Hello' }], + max_tokens: 10, + }; + + const basicResponse = await fetch(chatURL, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(basicChatTest), + }); + + if (!basicResponse.ok) { + if (basicResponse.status === 401) { + throw new Error('auth'); + } + throw new Error('connection'); + } + + // 然后测试 function call 支持 + const testFunctionCall = { + model: modelName, + messages: [{ role: 'user', content: 'Test function call support' }], + functions: [ + { + name: 'test_function', + description: 'A test function', + parameters: { + type: 'object', + properties: { + test: { type: 'string', description: 'Test parameter' }, + }, + required: ['test'], + }, + }, + ], + max_tokens: 10, + }; + + const functionResponse = await fetch(chatURL, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(testFunctionCall), + }); + + if (!functionResponse.ok) { + if (functionResponse.status === 400) { + // 检查错误信息是否表明不支持 function call + const errorData = await functionResponse.json().catch(() => null); + if ( + errorData?.error?.message?.toLowerCase().includes('function') || + errorData?.error?.message?.toLowerCase().includes('tool') + ) { + throw new Error('function_call_not_supported'); + } + } + throw new Error('function_call_test_failed'); + } + } else { + // 对于 embedding 模型,测试 embeddings 接口 + const embeddingURL = baseURL.includes('/v1') + ? `${baseURL}/embeddings` + : `${baseURL}/v1/embeddings`; + + const embeddingTest = { + model: modelName, + input: 'Test embedding', + }; + + const embeddingResponse = await fetch(embeddingURL, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(embeddingTest), + }); + + if (!embeddingResponse.ok) { + if (embeddingResponse.status === 401) { + throw new Error('auth'); + } + throw new Error('connection'); + } + } + + return { success: true }; + } catch (error: any) { + return { + success: false, + error: error.message || 'unknown', + type: error.message, + }; + } +}; + +// 显示错误消息 +const showValidationError = (modelType: string, errorType: string) => { + let message = ''; + const modelLabel = + modelType === 'llm' + ? i18n.global.t('localDeploy.model') + : i18n.global.t('localDeploy.embeddingModel'); + const modelName = + modelType === 'llm' ? ruleForm.modelName : embeddingRuleForm.modelName; + + switch (errorType) { + case 'auth': + message = `${modelLabel}: ${i18n.global.t('localDeploy.authError')}`; + break; + case 'function_call_not_supported': + message = `${modelLabel}: ${i18n.global.t('localDeploy.functionCallNotSupported')}`; + break; + case 'connection': + case 'function_call_test_failed': + message = `${modelLabel}: ${i18n.global.t('localDeploy.connectionError')}`; + break; + case 'model_not_found': + message = `${modelLabel}: 模型 "${modelName}" 不存在`; + break; + default: + message = `${modelLabel}: ${i18n.global.t('localDeploy.modelError')}`; + } + + ElMessageBox.alert(message, i18n.global.t('localDeploy.validationFailed'), { + confirmButtonText: i18n.global.t('welcome.confirm'), + type: 'error', + }); +}; const validateForm = async () => { + // 首先进行基础表单校验 const [ruleFormValid] = await new Promise<[boolean, any]>((resolve) => { ruleFormRef.value?.validate((valid, fields) => resolve([valid, fields])); }); @@ -235,7 +492,62 @@ const validateForm = async () => { if (!ruleFormValid || !embeddingRuleFormValid) { return false; } - return true; + + // 重置验证状态 + llmValidationStatus.value = 'validating'; + embeddingValidationStatus.value = 'validating'; + + let validationSuccess = true; + + try { + // 校验 LLM + const llmResult = await validateOpenAIModel( + ruleForm.url, + ruleForm.apiKey, + ruleForm.modelName, + true, + ); + + if (llmResult.success) { + llmValidationStatus.value = 'success'; + } else { + llmValidationStatus.value = 'error'; + showValidationError('llm', llmResult.type || 'unknown'); + validationSuccess = false; + } + + // 校验 Embedding 模型 + const embeddingResult = await validateOpenAIModel( + embeddingRuleForm.url, + embeddingRuleForm.apiKey, + embeddingRuleForm.modelName, + false, + ); + + if (embeddingResult.success) { + embeddingValidationStatus.value = 'success'; + } else { + embeddingValidationStatus.value = 'error'; + showValidationError('embedding', embeddingResult.type || 'unknown'); + validationSuccess = false; + } + } catch (error) { + console.error('Model validation error:', error); + llmValidationStatus.value = 'error'; + embeddingValidationStatus.value = 'error'; + validationSuccess = false; + + ElMessageBox.alert( + i18n.global.t('localDeploy.modelError'), + i18n.global.t('localDeploy.validationFailed'), + { + confirmButtonText: i18n.global.t('welcome.confirm'), + type: 'error', + }, + ); + } + + return validationSuccess; }; const handleConfirm = async () => { @@ -320,7 +632,20 @@ const handleBack = () => { .el-input__icon { cursor: pointer; } + .loading-icon { + animation: rotate 1s linear infinite; + } } + +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .submit-btn { width: 100vw; display: flex; diff --git a/electron/welcome/onlineService.vue b/electron/welcome/onlineService.vue index 31d1957..dbc838e 100644 --- a/electron/welcome/onlineService.vue +++ b/electron/welcome/onlineService.vue @@ -5,7 +5,9 @@ {{ $t('welcome.back') }} -
{{ $t('welcome.localDeploy') }}
+
+ {{ $t('welcome.onlineService') }} +
import { reactive, ref, onMounted } from 'vue'; import type { FormInstance, FormRules } from 'element-plus'; +import { ElMessageBox } from 'element-plus'; import leftArrowIcon from './assets/svgs/left_arrow.svg'; import i18n from './lang/index'; @@ -120,15 +123,61 @@ onMounted(() => { const handleConfirm = async (formEl: FormInstance | undefined) => { if (!formEl) return; - await formEl.validate((valid, fields) => { - if (!valid) { - console.error('表单验证失败:', fields); + + // 先进行表单验证 + const isFormValid = await new Promise((resolve) => { + formEl.validate((valid, fields) => { + if (!valid) { + console.error('表单验证失败:', fields); + resolve(false); + return; + } + resolve(true); + }); + }); + + if (!isFormValid) return; + + // 验证服务器连接 + if (window.eulercopilotWelcome?.config) { + try { + const { isValid, error } = + await window.eulercopilotWelcome.config.validateServer(ruleForm.url); + + if (!isValid) { + // 弹窗提醒用户连接失败 + await ElMessageBox.alert( + error || i18n.global.t('welcome.validationFailure'), + i18n.global.t('welcome.connectionFailed'), + { + confirmButtonText: i18n.global.t('welcome.confirm'), + type: 'error', + }, + ); + return; + } + + // 验证成功,设置代理 URL + await window.eulercopilotWelcome.config.setProxyUrl(ruleForm.url); + } catch (error) { + // 网络错误或其他异常 + console.error('服务器验证失败:', error); + await ElMessageBox.alert( + i18n.global.t('welcome.validationFailure'), + i18n.global.t('welcome.connectionFailed'), + { + confirmButtonText: i18n.global.t('welcome.confirm'), + type: 'error', + }, + ); return; } - if (window.eulercopilotWelcome?.config) { - window.eulercopilotWelcome.config.setProxyUrl(ruleForm.url); - } - }); + } + + // 完成欢迎页面流程 + if (window.eulercopilotWelcome?.welcome) { + await window.eulercopilotWelcome.welcome.complete(); + } }; const handleBack = () => { diff --git a/electron/welcome/timeLine.vue b/electron/welcome/timeLine.vue index 89d477c..8f8626f 100644 --- a/electron/welcome/timeLine.vue +++ b/electron/welcome/timeLine.vue @@ -177,10 +177,17 @@ const updateActivitiesStatus = (status: any) => { } else if (currentStep === 'failed' || currentStep === 'stopped') { // 失败或停止状态 activities.value.forEach((act) => { - if (act.type !== 'success') { + if (act.type === 'running') { act.type = 'failed'; } }); + } else if (currentStep === 'idle') { + // 空闲状态 + activities.value.forEach((act) => { + if (act.type !== 'success') { + act.type = 'default'; + } + }); } else { // 未知步骤 console.warn(`未知的部署步骤: ${currentStep}`); -- Gitee From 1e7318b3dfe4b74e2ca7c1cbad56ba594e998cbf 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 03:57:10 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(language):=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E6=A3=80=E6=B5=8B=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E5=B9=B3=E5=8F=B0=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E8=AF=86=E5=88=AB=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/welcome/lang/index.ts | 164 ++++++++++++++++++++++++++++++++- electron/welcome/main.ts | 17 +++- 2 files changed, 177 insertions(+), 4 deletions(-) diff --git a/electron/welcome/lang/index.ts b/electron/welcome/lang/index.ts index 736df67..2c1acbd 100644 --- a/electron/welcome/lang/index.ts +++ b/electron/welcome/lang/index.ts @@ -3,7 +3,130 @@ import { createI18n } from 'vue-i18n'; import zh from './zh'; import en from './en'; -const locale = 'zh'; +/** + * 开发模式日志输出 + * 只在开发环境下输出日志 + */ +function devLog(...args: any[]) { + if (import.meta.env.DEV || process.env.NODE_ENV === 'development') { + console.log(...args); + } +} + +/** + * 开发模式警告输出 + * 只在开发环境下输出警告 + */ +function devWarn(...args: any[]) { + if (import.meta.env.DEV || process.env.NODE_ENV === 'development') { + console.warn(...args); + } +} + +/** + * 检测系统语言 + * 支持多种操作系统的语言检测 + * @returns {'zh' | 'en'} 语言代码 'zh' 或 'en' + */ +function detectSystemLanguage(): 'zh' | 'en' { + let systemLanguage: 'zh' | 'en' = 'zh'; // 默认中文 + + try { + // 优先级1: 检查 Electron 环境中的系统信息 + if (typeof window !== 'undefined' && window.eulercopilotWelcome?.system) { + const { platform, env } = window.eulercopilotWelcome.system; + + devLog(`检测到系统平台: ${platform}`); + + // 根据不同操作系统检测语言 + if (platform === 'win32') { + // Windows 系统语言检测 + const winLang = env.LANG || env.LC_ALL || env.LANGUAGE || ''; + if ( + winLang.toLowerCase().includes('zh') || + winLang.toLowerCase().includes('chinese') || + winLang.toLowerCase().includes('chs') || + winLang.toLowerCase().includes('cn') + ) { + systemLanguage = 'zh'; + } else if (winLang && !winLang.toLowerCase().includes('zh')) { + systemLanguage = 'en'; + } + } else if (platform === 'darwin') { + // macOS 系统语言检测 + const macLang = + env.LANG || env.LC_ALL || env.LC_MESSAGES || env.LANGUAGE || ''; + if ( + macLang.toLowerCase().includes('zh') || + macLang.toLowerCase().includes('chinese') || + macLang.toLowerCase().includes('cn') + ) { + systemLanguage = 'zh'; + } else if (macLang && !macLang.toLowerCase().includes('zh')) { + systemLanguage = 'en'; + } + } else if (platform === 'linux') { + // Linux 系统语言检测 + const linuxLang = + env.LANG || env.LC_ALL || env.LC_MESSAGES || env.LANGUAGE || ''; + if ( + linuxLang.toLowerCase().includes('zh') || + linuxLang.toLowerCase().includes('chinese') || + linuxLang.toLowerCase().includes('cn') + ) { + systemLanguage = 'zh'; + } else if (linuxLang && !linuxLang.toLowerCase().includes('zh')) { + systemLanguage = 'en'; + } + } + + devLog(`根据环境变量检测到语言: ${systemLanguage}`); + return systemLanguage; // 如果 Electron 环境可用,直接返回结果 + } + + // 优先级2: 浏览器语言检测 (作为后备方案) + if (typeof navigator !== 'undefined' && navigator.language) { + const browserLang = navigator.language.toLowerCase(); + + devLog(`浏览器语言: ${browserLang}`); + + // 检测是否为中文相关语言 + if ( + browserLang.startsWith('zh') || + browserLang.includes('china') || + browserLang.includes('chinese') || + browserLang === 'zh-cn' || + browserLang === 'zh-tw' || + browserLang === 'zh-hk' + ) { + systemLanguage = 'zh'; + } + // 检测是否为英文相关语言 + else if ( + browserLang.startsWith('en') || + browserLang.includes('english') || + browserLang.includes('us') || + browserLang.includes('gb') + ) { + systemLanguage = 'en'; + } + // 其他语言默认使用英文 + else { + systemLanguage = 'en'; + } + } + + devLog(`最终检测到的系统语言: ${systemLanguage}`); + } catch (error) { + devWarn('检测系统语言失败,使用默认中文:', error); + systemLanguage = 'zh'; + } + + return systemLanguage; +} + +// 检测系统语言,默认中文 +const locale = detectSystemLanguage(); const i18n_welcome = createI18n({ legacy: false, // 设置为 false,启用 composition API 模式 @@ -13,4 +136,41 @@ const i18n_welcome = createI18n({ en, }, }); -export default i18n_welcome; \ No newline at end of file + +/** + * 在 Electron 环境准备好后重新检测语言 + * 这个函数会在应用初始化后调用,确保能获取到正确的系统语言 + */ +export function redetectLanguageOnReady() { + // 等待一小段时间让 Electron preload 脚本完成初始化 + setTimeout(() => { + const newLocale = detectSystemLanguage(); + if (newLocale !== i18n_welcome.global.locale.value) { + devLog( + `重新检测到语言变化,从 ${i18n_welcome.global.locale.value} 切换到 ${newLocale}`, + ); + i18n_welcome.global.locale.value = newLocale; + } + }, 100); +} + +/** + * 切换语言 + * @param {string} newLocale 新的语言代码 'zh' 或 'en' + */ +export function changeLanguage(newLocale: 'zh' | 'en') { + if (i18n_welcome.global.locale) { + i18n_welcome.global.locale.value = newLocale; + devLog(`语言已切换到: ${newLocale}`); + } +} + +/** + * 获取当前语言 + * @returns {'zh' | 'en'} 当前语言代码 + */ +export function getCurrentLanguage(): 'zh' | 'en' { + return i18n_welcome.global.locale.value as 'zh' | 'en'; +} + +export default i18n_welcome; diff --git a/electron/welcome/main.ts b/electron/welcome/main.ts index 79899ab..fada465 100644 --- a/electron/welcome/main.ts +++ b/electron/welcome/main.ts @@ -4,11 +4,24 @@ import { createApp } from 'vue'; import WelcomeComponent from './index.vue'; import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css'; -import i18n_welcome from './lang'; +import i18n_welcome, { redetectLanguageOnReady } from './lang'; + +/** + * 开发模式日志输出 + * 只在开发环境下输出日志 + */ +function devLog(...args: any[]) { + if (import.meta.env.DEV || process.env.NODE_ENV === 'development') { + console.log(...args); + } +} // 创建 Vue 应用并挂载 const app: App = createApp(WelcomeComponent); app.use(ElementPlus).use(i18n_welcome); app.mount('#app'); -console.log('Welcome Vue app initialized'); +// 在应用挂载后重新检测语言 +redetectLanguageOnReady(); + +devLog('Welcome Vue app initialized'); -- Gitee From bdc1e2e3de4d38421f396795cfd8215ceb053343 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 09:51:10 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(deploy):=20=E5=A2=9E=E5=BC=BA=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=E7=84=A6=E7=82=B9=E7=8A=B6=E6=80=81=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA=E4=B8=8E=E8=87=AA=E5=8A=A8=E9=AA=8C=E8=AF=81=20LLM=20?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- electron/welcome/localDeploy.vue | 219 ++++++++++++++++++------------- 1 file changed, 130 insertions(+), 89 deletions(-) diff --git a/electron/welcome/localDeploy.vue b/electron/welcome/localDeploy.vue index bd9d54e..2a55e26 100644 --- a/electron/welcome/localDeploy.vue +++ b/electron/welcome/localDeploy.vue @@ -51,6 +51,11 @@
@@ -116,6 +131,11 @@