更新翻译
This commit is contained in:
parent
567b2b54f8
commit
c2056e7042
@ -781,8 +781,9 @@
|
|||||||
"Failed to uninstall package": "卸载扩展包失败",
|
"Failed to uninstall package": "卸载扩展包失败",
|
||||||
"OK": "确定",
|
"OK": "确定",
|
||||||
"Package '{0}' uninstalled successfully": "扩展包 '{0}' 卸载成功",
|
"Package '{0}' uninstalled successfully": "扩展包 '{0}' 卸载成功",
|
||||||
|
"Package '{0}' installed successfully": "扩展包 '{0}' 安装成功",
|
||||||
|
|
||||||
"App Installer": "应用安装器",
|
"App Installer": "应用安装",
|
||||||
"Upload and install applications to your local Jingrow environment": "上传并安装应用到您的本地 Jingrow 环境",
|
"Upload and install applications to your local Jingrow environment": "上传并安装应用到您的本地 Jingrow 环境",
|
||||||
"Upload App Package": "上传应用包",
|
"Upload App Package": "上传应用包",
|
||||||
"Uploading...": "上传中...",
|
"Uploading...": "上传中...",
|
||||||
@ -809,6 +810,16 @@
|
|||||||
"Frontend Only": "仅前端",
|
"Frontend Only": "仅前端",
|
||||||
"Full Stack": "全栈",
|
"Full Stack": "全栈",
|
||||||
"Only ZIP files are supported": "仅支持 ZIP 文件",
|
"Only ZIP files are supported": "仅支持 ZIP 文件",
|
||||||
|
"Only ZIP and TAR.GZ files are supported": "仅支持 ZIP 和 TAR.GZ 文件",
|
||||||
|
"Click or drag package file to this area to upload": "点击或拖拽应用包文件到此区域上传",
|
||||||
|
"Support for ZIP, TAR.GZ, and GZ format": "支持 ZIP、TAR.GZ 和 GZ 格式",
|
||||||
|
"Selected Package": "选中的应用包",
|
||||||
|
"Size": "大小",
|
||||||
|
"Extension Package": "扩展包",
|
||||||
|
"Failed to save package": "保存应用包失败",
|
||||||
|
"Installing extension package...": "正在安装扩展包...",
|
||||||
|
"Failed to load local apps": "加载本地应用失败",
|
||||||
|
"Failed to install app": "安装应用失败",
|
||||||
"Please select a file first": "请先选择文件",
|
"Please select a file first": "请先选择文件",
|
||||||
"File not found": "文件未找到",
|
"File not found": "文件未找到",
|
||||||
"Failed to load apps": "加载应用失败",
|
"Failed to load apps": "加载应用失败",
|
||||||
@ -821,8 +832,6 @@
|
|||||||
"App '{0}' installed successfully": "应用 '{0}' 安装成功",
|
"App '{0}' installed successfully": "应用 '{0}' 安装成功",
|
||||||
"System App": "系统应用",
|
"System App": "系统应用",
|
||||||
"cannot be uninstalled": "不允许卸载",
|
"cannot be uninstalled": "不允许卸载",
|
||||||
"Version": "版本",
|
|
||||||
"Git Branch": "Git分支",
|
|
||||||
"This action cannot be undone.": "此操作无法撤销。",
|
"This action cannot be undone.": "此操作无法撤销。",
|
||||||
"Not installed": "未安装",
|
"Not installed": "未安装",
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
<!-- Jingrow Local 应用安装页面 -->
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-installer-page">
|
<div class="app-installer-page">
|
||||||
<!-- 页面头部 -->
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<h1 class="page-title">
|
<h1 class="page-title">
|
||||||
@ -12,7 +10,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 上传区域 -->
|
|
||||||
<div class="upload-section">
|
<div class="upload-section">
|
||||||
<n-card :title="t('Upload App Package')" class="upload-card">
|
<n-card :title="t('Upload App Package')" class="upload-card">
|
||||||
<n-upload
|
<n-upload
|
||||||
@ -40,7 +37,6 @@
|
|||||||
</n-upload-dragger>
|
</n-upload-dragger>
|
||||||
</n-upload>
|
</n-upload>
|
||||||
|
|
||||||
<!-- 文件信息显示 -->
|
|
||||||
<div v-if="fileList.length > 0" class="file-info-card">
|
<div v-if="fileList.length > 0" class="file-info-card">
|
||||||
<n-alert type="info" :bordered="false">
|
<n-alert type="info" :bordered="false">
|
||||||
<template #header>
|
<template #header>
|
||||||
@ -63,7 +59,6 @@
|
|||||||
</n-alert>
|
</n-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 应用名称输入 - 仅普通应用包需要 -->
|
|
||||||
<div class="app-name-input" v-if="fileList.length > 0 && !isExtensionFile">
|
<div class="app-name-input" v-if="fileList.length > 0 && !isExtensionFile">
|
||||||
<n-form-item :label="t('App Name (Optional)')">
|
<n-form-item :label="t('App Name (Optional)')">
|
||||||
<n-input
|
<n-input
|
||||||
@ -74,7 +69,6 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 上传按钮 -->
|
|
||||||
<div class="upload-actions" v-if="fileList.length > 0">
|
<div class="upload-actions" v-if="fileList.length > 0">
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button @click="clearFiles" :disabled="uploading">
|
<n-button @click="clearFiles" :disabled="uploading">
|
||||||
@ -91,8 +85,6 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 本地App列表 -->
|
|
||||||
<div class="local-apps-section" v-if="localApps.length > 0">
|
<div class="local-apps-section" v-if="localApps.length > 0">
|
||||||
<n-card :title="t('Local Apps Available')" class="local-apps-card">
|
<n-card :title="t('Local Apps Available')" class="local-apps-card">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
@ -105,7 +97,6 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 安装进度模态框 -->
|
|
||||||
<n-modal v-model:show="showProgressModal" preset="card" style="width: 500px">
|
<n-modal v-model:show="showProgressModal" preset="card" style="width: 500px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<h3>{{ t('Installing App') }}</h3>
|
<h3>{{ t('Installing App') }}</h3>
|
||||||
@ -142,7 +133,6 @@ import axios from 'axios'
|
|||||||
import { get_session_api_headers } from '@/shared/api/auth'
|
import { get_session_api_headers } from '@/shared/api/auth'
|
||||||
import { t } from '@/shared/i18n'
|
import { t } from '@/shared/i18n'
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const fileList = ref<UploadFileInfo[]>([])
|
const fileList = ref<UploadFileInfo[]>([])
|
||||||
const appName = ref('')
|
const appName = ref('')
|
||||||
const uploading = ref(false)
|
const uploading = ref(false)
|
||||||
@ -151,16 +141,14 @@ const installProgress = ref(0)
|
|||||||
const installMessage = ref('')
|
const installMessage = ref('')
|
||||||
const installStatus = ref<'success' | 'error' | 'info'>('info')
|
const installStatus = ref<'success' | 'error' | 'info'>('info')
|
||||||
const showProgressModal = ref(false)
|
const showProgressModal = ref(false)
|
||||||
const isExtensionFile = ref(false) // 是否是扩展包
|
const isExtensionFile = ref(false)
|
||||||
|
|
||||||
// 本地App相关
|
|
||||||
const localApps = ref<any[]>([])
|
const localApps = ref<any[]>([])
|
||||||
const loadingLocalApps = ref(false)
|
const loadingLocalApps = ref(false)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const uploadRef = ref()
|
const uploadRef = ref()
|
||||||
|
|
||||||
// 本地App表格列定义
|
|
||||||
const localAppsColumns: DataTableColumns<any> = [
|
const localAppsColumns: DataTableColumns<any> = [
|
||||||
{
|
{
|
||||||
title: t('App Name'),
|
title: t('App Name'),
|
||||||
@ -198,8 +186,6 @@ const localAppsColumns: DataTableColumns<any> = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// 方法
|
|
||||||
|
|
||||||
const beforeUpload = (data: { file: UploadFileInfo }) => {
|
const beforeUpload = (data: { file: UploadFileInfo }) => {
|
||||||
const file = data.file.file
|
const file = data.file.file
|
||||||
if (!file) return false
|
if (!file) return false
|
||||||
@ -219,14 +205,12 @@ const beforeUpload = (data: { file: UploadFileInfo }) => {
|
|||||||
const handleFileChange = (options: { fileList: UploadFileInfo[] }) => {
|
const handleFileChange = (options: { fileList: UploadFileInfo[] }) => {
|
||||||
fileList.value = options.fileList
|
fileList.value = options.fileList
|
||||||
|
|
||||||
// 检测是否是扩展包
|
|
||||||
if (fileList.value.length > 0) {
|
if (fileList.value.length > 0) {
|
||||||
const fileName = fileList.value[0].file?.name || ''
|
const fileName = fileList.value[0].file?.name || ''
|
||||||
isExtensionFile.value = fileName.toLowerCase().endsWith('.tar.gz') ||
|
isExtensionFile.value = fileName.toLowerCase().endsWith('.tar.gz') ||
|
||||||
fileName.toLowerCase().endsWith('.tgz') ||
|
fileName.toLowerCase().endsWith('.tgz') ||
|
||||||
fileName.toLowerCase().endsWith('.gz')
|
fileName.toLowerCase().endsWith('.gz')
|
||||||
|
|
||||||
// 如果是扩展包,清空应用名称(因为不需要)
|
|
||||||
if (isExtensionFile.value) {
|
if (isExtensionFile.value) {
|
||||||
appName.value = ''
|
appName.value = ''
|
||||||
}
|
}
|
||||||
@ -234,7 +218,6 @@ const handleFileChange = (options: { fileList: UploadFileInfo[] }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customUpload = async (_options: any) => {
|
const customUpload = async (_options: any) => {
|
||||||
// 这里不做实际上传,只是保存文件信息
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,11 +233,9 @@ const startUpload = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断文件类型,选择对应的 API endpoint
|
|
||||||
const fileName = file.name.toLowerCase()
|
const fileName = file.name.toLowerCase()
|
||||||
const isExtensionPackage = fileName.endsWith('.tar.gz') || fileName.endsWith('.tgz') || fileName.endsWith('.gz')
|
const isExtensionPackage = fileName.endsWith('.tar.gz') || fileName.endsWith('.tgz') || fileName.endsWith('.gz')
|
||||||
|
|
||||||
// 如果是一键安装的扩展包,直接调用 install_package
|
|
||||||
if (isExtensionPackage) {
|
if (isExtensionPackage) {
|
||||||
try {
|
try {
|
||||||
uploading.value = true
|
uploading.value = true
|
||||||
@ -264,35 +245,28 @@ const startUpload = async () => {
|
|||||||
installMessage.value = t('Uploading file...')
|
installMessage.value = t('Uploading file...')
|
||||||
installStatus.value = 'info'
|
installStatus.value = 'info'
|
||||||
|
|
||||||
// 先上传文件保存到 public/files
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
|
|
||||||
installProgress.value = 20
|
installProgress.value = 20
|
||||||
installMessage.value = t('Saving package...')
|
installMessage.value = t('Saving package...')
|
||||||
|
|
||||||
// 第一步:保存文件
|
|
||||||
const saveResponse = await axios.post('/jingrow/install-extension', formData, {
|
const saveResponse = await axios.post('/jingrow/install-extension', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
...get_session_api_headers(),
|
...get_session_api_headers(),
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
},
|
},
|
||||||
timeout: 60000 // 60秒超时
|
timeout: 60000
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Save response:', saveResponse.data)
|
|
||||||
|
|
||||||
if (!saveResponse.data.success) {
|
if (!saveResponse.data.success) {
|
||||||
throw new Error(saveResponse.data.error || t('Failed to save package'))
|
throw new Error(saveResponse.data.error || t('Failed to save package'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileUrl = saveResponse.data.file_url
|
const fileUrl = saveResponse.data.file_url
|
||||||
console.log('File URL:', fileUrl)
|
|
||||||
|
|
||||||
installProgress.value = 50
|
installProgress.value = 50
|
||||||
installMessage.value = t('Installing package...')
|
installMessage.value = t('Installing package...')
|
||||||
|
|
||||||
// 第二步:调用 install_package 安装
|
|
||||||
const installResponse = await axios.post('/api/action/jingrow.ai.utils.jlocal.install_package', {
|
const installResponse = await axios.post('/api/action/jingrow.ai.utils.jlocal.install_package', {
|
||||||
package_file_url: fileUrl
|
package_file_url: fileUrl
|
||||||
}, {
|
}, {
|
||||||
@ -316,22 +290,18 @@ const startUpload = async () => {
|
|||||||
installMessage.value = t('Installation failed!')
|
installMessage.value = t('Installation failed!')
|
||||||
installStatus.value = 'error'
|
installStatus.value = 'error'
|
||||||
|
|
||||||
console.error('Upload error:', error)
|
|
||||||
console.error('Error response data:', error.response?.data)
|
|
||||||
|
|
||||||
const errorMsg = error.response?.data?.detail ||
|
const errorMsg = error.response?.data?.detail ||
|
||||||
error.response?.data?.error ||
|
error.response?.data?.error ||
|
||||||
error.message ||
|
error.message ||
|
||||||
t('Upload failed')
|
t('Upload failed')
|
||||||
|
|
||||||
console.error('Final error message:', errorMsg)
|
|
||||||
message.error(errorMsg)
|
message.error(errorMsg)
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
installing.value = false
|
installing.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return // 扩展包使用特殊流程,提前返回
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiEndpoint = '/jingrow/install/upload'
|
const apiEndpoint = '/jingrow/install/upload'
|
||||||
@ -344,18 +314,14 @@ const startUpload = async () => {
|
|||||||
installMessage.value = t('Preparing upload...')
|
installMessage.value = t('Preparing upload...')
|
||||||
installStatus.value = 'info'
|
installStatus.value = 'info'
|
||||||
|
|
||||||
// 创建FormData
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
if (appName.value.trim() && !isExtensionPackage) {
|
if (appName.value.trim() && !isExtensionPackage) {
|
||||||
// 只有普通应用包才需要 app_name 参数
|
|
||||||
formData.append('app_name', appName.value.trim())
|
formData.append('app_name', appName.value.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
installProgress.value = 30
|
installProgress.value = 30
|
||||||
installMessage.value = isExtensionPackage ? t('Installing extension package...') : t('Uploading file...')
|
installMessage.value = isExtensionPackage ? t('Installing extension package...') : t('Uploading file...')
|
||||||
|
|
||||||
// 上传文件
|
|
||||||
const response = await axios.post(apiEndpoint, formData, {
|
const response = await axios.post(apiEndpoint, formData, {
|
||||||
headers: {
|
headers: {
|
||||||
...get_session_api_headers(),
|
...get_session_api_headers(),
|
||||||
@ -374,7 +340,6 @@ const startUpload = async () => {
|
|||||||
installStatus.value = 'success'
|
installStatus.value = 'success'
|
||||||
|
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
// 清空文件列表
|
|
||||||
clearFiles()
|
clearFiles()
|
||||||
|
|
||||||
const resultAppName = isExtensionPackage ? response.data.package_name : response.data.app_name
|
const resultAppName = isExtensionPackage ? response.data.package_name : response.data.app_name
|
||||||
@ -388,17 +353,11 @@ const startUpload = async () => {
|
|||||||
installMessage.value = t('Installation failed!')
|
installMessage.value = t('Installation failed!')
|
||||||
installStatus.value = 'error'
|
installStatus.value = 'error'
|
||||||
|
|
||||||
console.error('Upload error:', error)
|
|
||||||
console.error('Error response:', error.response)
|
|
||||||
console.error('Error details:', error.response?.data)
|
|
||||||
|
|
||||||
// 更详细的错误信息
|
|
||||||
const errorDetail = error.response?.data?.detail ||
|
const errorDetail = error.response?.data?.detail ||
|
||||||
error.response?.data?.message ||
|
error.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
t('Upload failed')
|
t('Upload failed')
|
||||||
|
|
||||||
console.error('Final error detail:', errorDetail)
|
|
||||||
message.error(errorDetail)
|
message.error(errorDetail)
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
@ -413,7 +372,6 @@ const clearFiles = () => {
|
|||||||
uploadRef.value?.clear()
|
uploadRef.value?.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化文件大小
|
|
||||||
const formatFileSize = (bytes: number): string => {
|
const formatFileSize = (bytes: number): string => {
|
||||||
if (bytes === 0) return '0 B'
|
if (bytes === 0) return '0 B'
|
||||||
const k = 1024
|
const k = 1024
|
||||||
@ -422,7 +380,6 @@ const formatFileSize = (bytes: number): string => {
|
|||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 本地App相关方法
|
|
||||||
const loadLocalApps = async () => {
|
const loadLocalApps = async () => {
|
||||||
loadingLocalApps.value = true
|
loadingLocalApps.value = true
|
||||||
try {
|
try {
|
||||||
@ -438,7 +395,6 @@ const loadLocalApps = async () => {
|
|||||||
message.error(response.data.detail || t('Failed to load local apps'))
|
message.error(response.data.detail || t('Failed to load local apps'))
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Load local apps error:', error)
|
|
||||||
message.error(error?.response?.data?.detail || error?.message || t('Failed to load local apps'))
|
message.error(error?.response?.data?.detail || error?.message || t('Failed to load local apps'))
|
||||||
} finally {
|
} finally {
|
||||||
loadingLocalApps.value = false
|
loadingLocalApps.value = false
|
||||||
@ -455,20 +411,16 @@ const installLocalApp = async (appName: string) => {
|
|||||||
|
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
message.success(t('App \'{0}\' installed successfully').replace('{0}', appName))
|
message.success(t('App \'{0}\' installed successfully').replace('{0}', appName))
|
||||||
// 重新加载本地App列表
|
|
||||||
await loadLocalApps()
|
await loadLocalApps()
|
||||||
} else {
|
} else {
|
||||||
message.error(response.data.detail || t('Failed to install app'))
|
message.error(response.data.detail || t('Failed to install app'))
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Install local app error:', error)
|
|
||||||
message.error(error?.response?.data?.detail || error?.message || t('Failed to install app'))
|
message.error(error?.response?.data?.detail || error?.message || t('Failed to install app'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生命周期
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 加载本地App列表
|
|
||||||
loadLocalApps()
|
loadLocalApps()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -567,7 +519,6 @@ onMounted(() => {
|
|||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式布局 */
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.app-installer-page {
|
.app-installer-page {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user