mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-30 14:17:14 +00:00
feat: trace plugin installation
This commit is contained in:
@@ -13,6 +13,8 @@ class PluginSetting(Base):
|
|||||||
enabled = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=True)
|
enabled = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=True)
|
||||||
priority = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0)
|
priority = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0)
|
||||||
config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=dict)
|
config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=dict)
|
||||||
|
install_source = sqlalchemy.Column(sqlalchemy.String(255), nullable=False, default='github')
|
||||||
|
install_info = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=dict)
|
||||||
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import sqlalchemy
|
||||||
|
from .. import migration
|
||||||
|
|
||||||
|
|
||||||
|
@migration.migration_class(5)
|
||||||
|
class DBMigratePluginInstallSource(migration.DBMigration):
|
||||||
|
"""插件安装来源"""
|
||||||
|
|
||||||
|
async def upgrade(self):
|
||||||
|
"""升级"""
|
||||||
|
# add new column install_source, use default value 'github', via alter table
|
||||||
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.text(
|
||||||
|
"ALTER TABLE plugin_settings ADD COLUMN install_source VARCHAR(255) NOT NULL DEFAULT 'github'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# add new column install_info, use default value {}, via alter table
|
||||||
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.text("ALTER TABLE plugin_settings ADD COLUMN install_info JSON NOT NULL DEFAULT '{}'")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def downgrade(self):
|
||||||
|
"""降级"""
|
||||||
|
pass
|
||||||
+10
-1
@@ -105,7 +105,16 @@ class PluginRuntimeConnector:
|
|||||||
install_info: dict[str, Any],
|
install_info: dict[str, Any],
|
||||||
task_context: taskmgr.TaskContext | None = None,
|
task_context: taskmgr.TaskContext | None = None,
|
||||||
):
|
):
|
||||||
return await self.handler.install_plugin(install_source.value, install_info)
|
async for ret in self.handler.install_plugin(install_source.value, install_info):
|
||||||
|
current_action = ret.get('current_action', None)
|
||||||
|
if current_action is not None:
|
||||||
|
if task_context is not None:
|
||||||
|
task_context.set_current_action(current_action)
|
||||||
|
|
||||||
|
trace = ret.get('trace', None)
|
||||||
|
if trace is not None:
|
||||||
|
if task_context is not None:
|
||||||
|
task_context.trace(trace)
|
||||||
|
|
||||||
async def list_plugins(self) -> list[dict[str, Any]]:
|
async def list_plugins(self) -> list[dict[str, Any]]:
|
||||||
return await self.handler.list_plugins()
|
return await self.handler.list_plugins()
|
||||||
|
|||||||
+49
-3
@@ -39,6 +39,43 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
super().__init__(connection, disconnect_callback)
|
super().__init__(connection, disconnect_callback)
|
||||||
self.ap = ap
|
self.ap = ap
|
||||||
|
|
||||||
|
@self.action(RuntimeToLangBotAction.INITIALIZE_PLUGIN_SETTINGS)
|
||||||
|
async def initialize_plugin_settings(data: dict[str, Any]) -> handler.ActionResponse:
|
||||||
|
"""Initialize plugin settings"""
|
||||||
|
# check if exists plugin setting
|
||||||
|
plugin_author = data['plugin_author']
|
||||||
|
plugin_name = data['plugin_name']
|
||||||
|
install_source = data['install_source']
|
||||||
|
install_info = data['install_info']
|
||||||
|
|
||||||
|
result = await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.select(persistence_plugin.PluginSetting)
|
||||||
|
.where(persistence_plugin.PluginSetting.plugin_author == plugin_author)
|
||||||
|
.where(persistence_plugin.PluginSetting.plugin_name == plugin_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.first() is not None:
|
||||||
|
# delete plugin setting
|
||||||
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.delete(persistence_plugin.PluginSetting)
|
||||||
|
.where(persistence_plugin.PluginSetting.plugin_author == plugin_author)
|
||||||
|
.where(persistence_plugin.PluginSetting.plugin_name == plugin_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# create plugin setting
|
||||||
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.insert(persistence_plugin.PluginSetting).values(
|
||||||
|
plugin_author=plugin_author,
|
||||||
|
plugin_name=plugin_name,
|
||||||
|
install_source=install_source,
|
||||||
|
install_info=install_info,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return handler.ActionResponse.success(
|
||||||
|
data={},
|
||||||
|
)
|
||||||
|
|
||||||
@self.action(RuntimeToLangBotAction.GET_PLUGIN_SETTINGS)
|
@self.action(RuntimeToLangBotAction.GET_PLUGIN_SETTINGS)
|
||||||
async def get_plugin_settings(data: dict[str, Any]) -> handler.ActionResponse:
|
async def get_plugin_settings(data: dict[str, Any]) -> handler.ActionResponse:
|
||||||
"""Get plugin settings"""
|
"""Get plugin settings"""
|
||||||
@@ -56,6 +93,8 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
'enabled': False,
|
'enabled': False,
|
||||||
'priority': 0,
|
'priority': 0,
|
||||||
'plugin_config': {},
|
'plugin_config': {},
|
||||||
|
'install_source': 'local',
|
||||||
|
'install_info': {},
|
||||||
}
|
}
|
||||||
|
|
||||||
setting = result.first()
|
setting = result.first()
|
||||||
@@ -64,6 +103,8 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
data['enabled'] = setting.enabled
|
data['enabled'] = setting.enabled
|
||||||
data['priority'] = setting.priority
|
data['priority'] = setting.priority
|
||||||
data['plugin_config'] = setting.config
|
data['plugin_config'] = setting.config
|
||||||
|
data['install_source'] = setting.install_source
|
||||||
|
data['install_info'] = setting.install_info
|
||||||
|
|
||||||
return handler.ActionResponse.success(
|
return handler.ActionResponse.success(
|
||||||
data=data,
|
data=data,
|
||||||
@@ -373,17 +414,22 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
timeout=10,
|
timeout=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def install_plugin(self, install_source: str, install_info: dict[str, Any]) -> dict[str, Any]:
|
async def install_plugin(
|
||||||
|
self, install_source: str, install_info: dict[str, Any]
|
||||||
|
) -> typing.AsyncGenerator[dict[str, Any], None]:
|
||||||
"""Install plugin"""
|
"""Install plugin"""
|
||||||
return await self.call_action(
|
gen = self.call_action_generator(
|
||||||
LangBotToRuntimeAction.INSTALL_PLUGIN,
|
LangBotToRuntimeAction.INSTALL_PLUGIN,
|
||||||
{
|
{
|
||||||
'install_source': install_source,
|
'install_source': install_source,
|
||||||
'install_info': install_info,
|
'install_info': install_info,
|
||||||
},
|
},
|
||||||
timeout=10,
|
timeout=120,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async for ret in gen:
|
||||||
|
yield ret
|
||||||
|
|
||||||
async def list_plugins(self) -> list[dict[str, Any]]:
|
async def list_plugins(self) -> list[dict[str, Any]]:
|
||||||
"""List plugins"""
|
"""List plugins"""
|
||||||
result = await self.call_action(
|
result = await self.call_action(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
semantic_version = 'v4.0.7'
|
semantic_version = 'v4.0.7'
|
||||||
|
|
||||||
required_database_version = 4
|
required_database_version = 5
|
||||||
"""标记本版本所需要的数据库结构版本,用于判断数据库迁移"""
|
"""标记本版本所需要的数据库结构版本,用于判断数据库迁移"""
|
||||||
|
|
||||||
debug_mode = False
|
debug_mode = False
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import PluginInstalledComponent, {
|
|||||||
} from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
|
} from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
|
||||||
import PluginMarketComponent from '@/app/home/plugins/plugin-market/PluginMarketComponent';
|
import PluginMarketComponent from '@/app/home/plugins/plugin-market/PluginMarketComponent';
|
||||||
import PluginSortDialog from '@/app/home/plugins/plugin-sort/PluginSortDialog';
|
import PluginSortDialog from '@/app/home/plugins/plugin-sort/PluginSortDialog';
|
||||||
import PluginUploadDialog, {
|
|
||||||
UploadModalStatus,
|
|
||||||
} from '@/app/home/plugins/plugin-upload-dialog/PluginUploadDialog';
|
|
||||||
import styles from './plugins.module.css';
|
import styles from './plugins.module.css';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PlusIcon, ChevronDownIcon, UploadIcon, StoreIcon } from 'lucide-react';
|
import {
|
||||||
|
PlusIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
UploadIcon,
|
||||||
|
StoreIcon,
|
||||||
|
Download,
|
||||||
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -25,7 +28,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { GithubIcon } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
import { useState, useRef, useCallback } from 'react';
|
import { useState, useRef, useCallback } from 'react';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -33,6 +36,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
enum PluginInstallStatus {
|
enum PluginInstallStatus {
|
||||||
WAIT_INPUT = 'wait_input',
|
WAIT_INPUT = 'wait_input',
|
||||||
|
ASK_CONFIRM = 'ask_confirm',
|
||||||
INSTALLING = 'installing',
|
INSTALLING = 'installing',
|
||||||
ERROR = 'error',
|
ERROR = 'error',
|
||||||
}
|
}
|
||||||
@@ -46,56 +50,72 @@ export default function PluginConfigPage() {
|
|||||||
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
||||||
const [installError, setInstallError] = useState<string | null>(null);
|
const [installError, setInstallError] = useState<string | null>(null);
|
||||||
const [githubURL, setGithubURL] = useState('');
|
const [githubURL, setGithubURL] = useState('');
|
||||||
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
|
||||||
const [uploadStatus, setUploadStatus] = useState<UploadModalStatus>(
|
|
||||||
UploadModalStatus.UPLOADING,
|
|
||||||
);
|
|
||||||
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
|
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
function handleModalConfirm() {
|
function watchTask(taskId: number) {
|
||||||
installPlugin(githubURL);
|
let alreadySuccess = false;
|
||||||
}
|
console.log('taskId:', taskId);
|
||||||
function installPlugin(url: string) {
|
|
||||||
setPluginInstallStatus(PluginInstallStatus.INSTALLING);
|
|
||||||
httpClient
|
|
||||||
.installPluginFromGithub(url)
|
|
||||||
.then((resp) => {
|
|
||||||
const taskId = resp.task_id;
|
|
||||||
|
|
||||||
let alreadySuccess = false;
|
// 每秒拉取一次任务状态
|
||||||
console.log('taskId:', taskId);
|
const interval = setInterval(() => {
|
||||||
|
httpClient.getAsyncTask(taskId).then((resp) => {
|
||||||
// 每秒拉取一次任务状态
|
console.log('task status:', resp);
|
||||||
const interval = setInterval(() => {
|
if (resp.runtime.done) {
|
||||||
httpClient.getAsyncTask(taskId).then((resp) => {
|
clearInterval(interval);
|
||||||
console.log('task status:', resp);
|
if (resp.runtime.exception) {
|
||||||
if (resp.runtime.done) {
|
setInstallError(resp.runtime.exception);
|
||||||
clearInterval(interval);
|
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||||
if (resp.runtime.exception) {
|
} else {
|
||||||
setInstallError(resp.runtime.exception);
|
// success
|
||||||
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
if (!alreadySuccess) {
|
||||||
} else {
|
toast.success(t('plugins.installSuccess'));
|
||||||
// success
|
alreadySuccess = true;
|
||||||
if (!alreadySuccess) {
|
|
||||||
toast.success(t('plugins.installSuccess'));
|
|
||||||
alreadySuccess = true;
|
|
||||||
}
|
|
||||||
setGithubURL('');
|
|
||||||
setModalOpen(false);
|
|
||||||
pluginInstalledRef.current?.refreshPluginList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
setGithubURL('');
|
||||||
}, 1000);
|
setModalOpen(false);
|
||||||
})
|
pluginInstalledRef.current?.refreshPluginList();
|
||||||
.catch((err) => {
|
}
|
||||||
console.log('error when install plugin:', err);
|
}
|
||||||
setInstallError(err.message);
|
|
||||||
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
|
||||||
});
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModalConfirm() {
|
||||||
|
installPlugin('github', { url: githubURL });
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPlugin(
|
||||||
|
installSource: string,
|
||||||
|
installInfo: Record<string, any>,
|
||||||
|
) {
|
||||||
|
setPluginInstallStatus(PluginInstallStatus.INSTALLING);
|
||||||
|
if (installSource === 'github') {
|
||||||
|
httpClient
|
||||||
|
.installPluginFromGithub(installInfo.url)
|
||||||
|
.then((resp) => {
|
||||||
|
const taskId = resp.task_id;
|
||||||
|
watchTask(taskId);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('error when install plugin:', err);
|
||||||
|
setInstallError(err.message);
|
||||||
|
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||||
|
});
|
||||||
|
} else if (installSource === 'local') {
|
||||||
|
httpClient
|
||||||
|
.installPluginFromLocal(installInfo.file)
|
||||||
|
.then((resp) => {
|
||||||
|
const taskId = resp.task_id;
|
||||||
|
watchTask(taskId);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('error when install plugin:', err);
|
||||||
|
setInstallError(err.message);
|
||||||
|
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateFileType = (file: File): boolean => {
|
const validateFileType = (file: File): boolean => {
|
||||||
@@ -111,21 +131,10 @@ export default function PluginConfigPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUploadModalOpen(true);
|
setModalOpen(true);
|
||||||
setUploadStatus(UploadModalStatus.UPLOADING);
|
setPluginInstallStatus(PluginInstallStatus.INSTALLING);
|
||||||
setUploadError(null);
|
setInstallError(null);
|
||||||
|
installPlugin('local', { file });
|
||||||
try {
|
|
||||||
// 暂时直接显示成功,等后续实现进度显示
|
|
||||||
setTimeout(() => {
|
|
||||||
setUploadStatus(UploadModalStatus.SUCCESS);
|
|
||||||
toast.success(t('plugins.uploadSuccess'));
|
|
||||||
pluginInstalledRef.current?.refreshPluginList();
|
|
||||||
}, 1000);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
setUploadError((err as Error)?.message || t('plugins.uploadFailed'));
|
|
||||||
setUploadStatus(UploadModalStatus.ERROR);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
@@ -250,8 +259,8 @@ export default function PluginConfigPage() {
|
|||||||
<DialogContent className="w-[500px] p-6">
|
<DialogContent className="w-[500px] p-6">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-4">
|
<DialogTitle className="flex items-center gap-4">
|
||||||
<GithubIcon className="size-6" />
|
<Download className="size-6" />
|
||||||
<span>{t('plugins.installFromGithub')}</span>
|
<span>{t('plugins.installPlugin')}</span>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
||||||
@@ -277,12 +286,13 @@ export default function PluginConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
{(pluginInstallStatus === PluginInstallStatus.WAIT_INPUT ||
|
||||||
|
pluginInstallStatus === PluginInstallStatus.ASK_CONFIRM) && (
|
||||||
<>
|
<>
|
||||||
<Button variant="outline" onClick={() => setModalOpen(false)}>
|
<Button variant="outline" onClick={() => setModalOpen(false)}>
|
||||||
{t('common.cancel')}
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleModalConfirm}>
|
<Button onClick={() => handleModalConfirm()}>
|
||||||
{t('common.confirm')}
|
{t('common.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@@ -296,14 +306,6 @@ export default function PluginConfigPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* 上传状态弹窗 */}
|
|
||||||
<PluginUploadDialog
|
|
||||||
open={uploadModalOpen}
|
|
||||||
onOpenChange={setUploadModalOpen}
|
|
||||||
status={uploadStatus}
|
|
||||||
error={uploadError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 拖拽提示覆盖层 */}
|
{/* 拖拽提示覆盖层 */}
|
||||||
{isDragOver && (
|
{isDragOver && (
|
||||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center z-50 pointer-events-none">
|
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center z-50 pointer-events-none">
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogFooter,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { UploadIcon } from 'lucide-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export enum UploadModalStatus {
|
|
||||||
UPLOADING = 'uploading',
|
|
||||||
SUCCESS = 'success',
|
|
||||||
ERROR = 'error',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PluginUploadDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onOpenChange: (open: boolean) => void;
|
|
||||||
status: UploadModalStatus;
|
|
||||||
error?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PluginUploadDialog({
|
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
status,
|
|
||||||
error,
|
|
||||||
}: PluginUploadDialogProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
||||||
<DialogContent className="w-[400px] p-6">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="flex items-center gap-4">
|
|
||||||
<UploadIcon className="size-6" />
|
|
||||||
<span>{t('plugins.uploadLocalPlugin')}</span>
|
|
||||||
</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="mt-4">
|
|
||||||
{status === UploadModalStatus.UPLOADING && (
|
|
||||||
<p className="mb-2">{t('plugins.uploadingPlugin')}</p>
|
|
||||||
)}
|
|
||||||
{status === UploadModalStatus.SUCCESS && (
|
|
||||||
<p className="mb-2 text-green-600">{t('plugins.uploadSuccess')}</p>
|
|
||||||
)}
|
|
||||||
{status === UploadModalStatus.ERROR && (
|
|
||||||
<>
|
|
||||||
<p className="mb-2">{t('plugins.uploadFailed')}</p>
|
|
||||||
<p className="mb-2 text-red-500">{error}</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
{(status === UploadModalStatus.SUCCESS ||
|
|
||||||
status === UploadModalStatus.ERROR) && (
|
|
||||||
<Button variant="default" onClick={() => onOpenChange(false)}>
|
|
||||||
{t('common.close')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -142,7 +142,7 @@ const enUS = {
|
|||||||
marketplace: 'Marketplace',
|
marketplace: 'Marketplace',
|
||||||
arrange: 'Sort Plugins',
|
arrange: 'Sort Plugins',
|
||||||
install: 'Install',
|
install: 'Install',
|
||||||
installFromGithub: 'Install Plugin from GitHub',
|
installPlugin: 'Install Plugin',
|
||||||
onlySupportGithub: 'Currently only supports installation from GitHub',
|
onlySupportGithub: 'Currently only supports installation from GitHub',
|
||||||
enterGithubLink: 'Enter GitHub link of the plugin',
|
enterGithubLink: 'Enter GitHub link of the plugin',
|
||||||
installing: 'Installing plugin...',
|
installing: 'Installing plugin...',
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const jaJP = {
|
|||||||
marketplace: 'プラグインマーケット',
|
marketplace: 'プラグインマーケット',
|
||||||
arrange: '並び替え',
|
arrange: '並び替え',
|
||||||
install: 'インストール',
|
install: 'インストール',
|
||||||
installFromGithub: 'GitHubからプラグインをインストール',
|
installPlugin: 'プラグインをインストール',
|
||||||
onlySupportGithub: '現在はGitHubからのインストールのみサポートしています',
|
onlySupportGithub: '現在はGitHubからのインストールのみサポートしています',
|
||||||
enterGithubLink: 'プラグインのGitHubリンクを入力してください',
|
enterGithubLink: 'プラグインのGitHubリンクを入力してください',
|
||||||
installing: 'プラグインをインストール中...',
|
installing: 'プラグインをインストール中...',
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ const zhHans = {
|
|||||||
marketplace: '插件市场',
|
marketplace: '插件市场',
|
||||||
arrange: '编排',
|
arrange: '编排',
|
||||||
install: '安装',
|
install: '安装',
|
||||||
installFromGithub: '从 GitHub 安装插件',
|
installPlugin: '安装插件',
|
||||||
onlySupportGithub: '目前仅支持从 GitHub 安装',
|
onlySupportGithub: '目前仅支持从 GitHub 安装',
|
||||||
enterGithubLink: '请输入插件的Github链接',
|
enterGithubLink: '请输入插件的Github链接',
|
||||||
installing: '正在安装插件...',
|
installing: '正在安装插件...',
|
||||||
|
|||||||
Reference in New Issue
Block a user