mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-18 11:44:18 +00:00
feat: trace plugin installation
This commit is contained in:
@@ -4,13 +4,16 @@ import PluginInstalledComponent, {
|
||||
} from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
|
||||
import PluginMarketComponent from '@/app/home/plugins/plugin-market/PluginMarketComponent';
|
||||
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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PlusIcon, ChevronDownIcon, UploadIcon, StoreIcon } from 'lucide-react';
|
||||
import {
|
||||
PlusIcon,
|
||||
ChevronDownIcon,
|
||||
UploadIcon,
|
||||
StoreIcon,
|
||||
Download,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -25,7 +28,7 @@ import {
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { GithubIcon } from 'lucide-react';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { toast } from 'sonner';
|
||||
@@ -33,6 +36,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
enum PluginInstallStatus {
|
||||
WAIT_INPUT = 'wait_input',
|
||||
ASK_CONFIRM = 'ask_confirm',
|
||||
INSTALLING = 'installing',
|
||||
ERROR = 'error',
|
||||
}
|
||||
@@ -46,56 +50,72 @@ export default function PluginConfigPage() {
|
||||
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
||||
const [installError, setInstallError] = useState<string | null>(null);
|
||||
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 pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
function handleModalConfirm() {
|
||||
installPlugin(githubURL);
|
||||
}
|
||||
function installPlugin(url: string) {
|
||||
setPluginInstallStatus(PluginInstallStatus.INSTALLING);
|
||||
httpClient
|
||||
.installPluginFromGithub(url)
|
||||
.then((resp) => {
|
||||
const taskId = resp.task_id;
|
||||
function watchTask(taskId: number) {
|
||||
let alreadySuccess = false;
|
||||
console.log('taskId:', taskId);
|
||||
|
||||
let alreadySuccess = false;
|
||||
console.log('taskId:', taskId);
|
||||
|
||||
// 每秒拉取一次任务状态
|
||||
const interval = setInterval(() => {
|
||||
httpClient.getAsyncTask(taskId).then((resp) => {
|
||||
console.log('task status:', resp);
|
||||
if (resp.runtime.done) {
|
||||
clearInterval(interval);
|
||||
if (resp.runtime.exception) {
|
||||
setInstallError(resp.runtime.exception);
|
||||
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||
} else {
|
||||
// success
|
||||
if (!alreadySuccess) {
|
||||
toast.success(t('plugins.installSuccess'));
|
||||
alreadySuccess = true;
|
||||
}
|
||||
setGithubURL('');
|
||||
setModalOpen(false);
|
||||
pluginInstalledRef.current?.refreshPluginList();
|
||||
}
|
||||
// 每秒拉取一次任务状态
|
||||
const interval = setInterval(() => {
|
||||
httpClient.getAsyncTask(taskId).then((resp) => {
|
||||
console.log('task status:', resp);
|
||||
if (resp.runtime.done) {
|
||||
clearInterval(interval);
|
||||
if (resp.runtime.exception) {
|
||||
setInstallError(resp.runtime.exception);
|
||||
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||
} else {
|
||||
// success
|
||||
if (!alreadySuccess) {
|
||||
toast.success(t('plugins.installSuccess'));
|
||||
alreadySuccess = true;
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('error when install plugin:', err);
|
||||
setInstallError(err.message);
|
||||
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||
setGithubURL('');
|
||||
setModalOpen(false);
|
||||
pluginInstalledRef.current?.refreshPluginList();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 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 => {
|
||||
@@ -111,21 +131,10 @@ export default function PluginConfigPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadModalOpen(true);
|
||||
setUploadStatus(UploadModalStatus.UPLOADING);
|
||||
setUploadError(null);
|
||||
|
||||
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);
|
||||
}
|
||||
setModalOpen(true);
|
||||
setPluginInstallStatus(PluginInstallStatus.INSTALLING);
|
||||
setInstallError(null);
|
||||
installPlugin('local', { file });
|
||||
},
|
||||
[t],
|
||||
);
|
||||
@@ -250,8 +259,8 @@ export default function PluginConfigPage() {
|
||||
<DialogContent className="w-[500px] p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-4">
|
||||
<GithubIcon className="size-6" />
|
||||
<span>{t('plugins.installFromGithub')}</span>
|
||||
<Download className="size-6" />
|
||||
<span>{t('plugins.installPlugin')}</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
||||
@@ -277,12 +286,13 @@ export default function PluginConfigPage() {
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
||||
{(pluginInstallStatus === PluginInstallStatus.WAIT_INPUT ||
|
||||
pluginInstallStatus === PluginInstallStatus.ASK_CONFIRM) && (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => setModalOpen(false)}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleModalConfirm}>
|
||||
<Button onClick={() => handleModalConfirm()}>
|
||||
{t('common.confirm')}
|
||||
</Button>
|
||||
</>
|
||||
@@ -296,14 +306,6 @@ export default function PluginConfigPage() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 上传状态弹窗 */}
|
||||
<PluginUploadDialog
|
||||
open={uploadModalOpen}
|
||||
onOpenChange={setUploadModalOpen}
|
||||
status={uploadStatus}
|
||||
error={uploadError}
|
||||
/>
|
||||
|
||||
{/* 拖拽提示覆盖层 */}
|
||||
{isDragOver && (
|
||||
<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',
|
||||
arrange: 'Sort Plugins',
|
||||
install: 'Install',
|
||||
installFromGithub: 'Install Plugin from GitHub',
|
||||
installPlugin: 'Install Plugin',
|
||||
onlySupportGithub: 'Currently only supports installation from GitHub',
|
||||
enterGithubLink: 'Enter GitHub link of the plugin',
|
||||
installing: 'Installing plugin...',
|
||||
|
||||
@@ -142,7 +142,7 @@ const jaJP = {
|
||||
marketplace: 'プラグインマーケット',
|
||||
arrange: '並び替え',
|
||||
install: 'インストール',
|
||||
installFromGithub: 'GitHubからプラグインをインストール',
|
||||
installPlugin: 'プラグインをインストール',
|
||||
onlySupportGithub: '現在はGitHubからのインストールのみサポートしています',
|
||||
enterGithubLink: 'プラグインのGitHubリンクを入力してください',
|
||||
installing: 'プラグインをインストール中...',
|
||||
|
||||
@@ -139,7 +139,7 @@ const zhHans = {
|
||||
marketplace: '插件市场',
|
||||
arrange: '编排',
|
||||
install: '安装',
|
||||
installFromGithub: '从 GitHub 安装插件',
|
||||
installPlugin: '安装插件',
|
||||
onlySupportGithub: '目前仅支持从 GitHub 安装',
|
||||
enterGithubLink: '请输入插件的Github链接',
|
||||
installing: '正在安装插件...',
|
||||
|
||||
Reference in New Issue
Block a user