mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-22 20:36:08 +00:00
feat: youhua frontend
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
<a href="https://link.langbot.app/zh/docs/guide">文档</a> |
|
<a href="https://link.langbot.app/zh/docs/guide">文档</a> |
|
||||||
<a href="https://link.langbot.app/zh/docs/api">API</a> |
|
<a href="https://link.langbot.app/zh/docs/api">API</a> |
|
||||||
<a href="https://space.langbot.app/cloud">Cloud</a> |
|
<a href="https://space.langbot.app/cloud">Cloud</a> |
|
||||||
<a href="https://space.langbot.app">插件市场</a> |
|
<a href="https://space.langbot.app">拓展市场</a> |
|
||||||
<a href="https://langbot.featurebase.app/roadmap">路线图</a>
|
<a href="https://langbot.featurebase.app/roadmap">路线图</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ function MarketplaceContent() {
|
|||||||
} = usePluginInstallTasks();
|
} = usePluginInstallTasks();
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [installInfo, setInstallInfo] = useState<Record<string, string>>({});
|
const [installInfo, setInstallInfo] = useState<Record<string, string>>({});
|
||||||
|
const [installExtensionType, setInstallExtensionType] = useState<'plugin' | 'mcp' | 'skill'>('plugin');
|
||||||
const [pluginInstallStatus, setPluginInstallStatus] =
|
const [pluginInstallStatus, setPluginInstallStatus] =
|
||||||
useState<PluginInstallStatus>(PluginInstallStatus.ASK_CONFIRM);
|
useState<PluginInstallStatus>(PluginInstallStatus.ASK_CONFIRM);
|
||||||
const [installError, setInstallError] = useState<string | null>(null);
|
const [installError, setInstallError] = useState<string | null>(null);
|
||||||
@@ -96,6 +97,7 @@ function MarketplaceContent() {
|
|||||||
plugin_name: plugin.name,
|
plugin_name: plugin.name,
|
||||||
plugin_version: plugin.latest_version,
|
plugin_version: plugin.latest_version,
|
||||||
});
|
});
|
||||||
|
setInstallExtensionType(plugin.type || 'plugin');
|
||||||
setPluginInstallStatus(PluginInstallStatus.ASK_CONFIRM);
|
setPluginInstallStatus(PluginInstallStatus.ASK_CONFIRM);
|
||||||
setInstallError(null);
|
setInstallError(null);
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
@@ -119,6 +121,7 @@ function MarketplaceContent() {
|
|||||||
taskId,
|
taskId,
|
||||||
pluginName: pluginDisplayName,
|
pluginName: pluginDisplayName,
|
||||||
source: 'marketplace',
|
source: 'marketplace',
|
||||||
|
extensionType: installExtensionType,
|
||||||
});
|
});
|
||||||
setSelectedTaskId(taskKey);
|
setSelectedTaskId(taskKey);
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
|||||||
+15
-2
@@ -28,6 +28,7 @@ export interface PluginInstallTask {
|
|||||||
source: 'github' | 'marketplace' | 'local';
|
source: 'github' | 'marketplace' | 'local';
|
||||||
stage: InstallStage;
|
stage: InstallStage;
|
||||||
overallProgress: number; // 0-100
|
overallProgress: number; // 0-100
|
||||||
|
extensionType: 'plugin' | 'mcp' | 'skill'; // type of extension being installed
|
||||||
fileSize?: number; // bytes, if known
|
fileSize?: number; // bytes, if known
|
||||||
// Download progress
|
// Download progress
|
||||||
downloadCurrent?: number; // bytes downloaded so far
|
downloadCurrent?: number; // bytes downloaded so far
|
||||||
@@ -53,6 +54,7 @@ interface PluginInstallTaskContextValue {
|
|||||||
taskId: number;
|
taskId: number;
|
||||||
pluginName: string;
|
pluginName: string;
|
||||||
source: 'github' | 'marketplace' | 'local';
|
source: 'github' | 'marketplace' | 'local';
|
||||||
|
extensionType: 'plugin' | 'mcp' | 'skill';
|
||||||
fileSize?: number;
|
fileSize?: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
removeTask: (id: string) => void;
|
removeTask: (id: string) => void;
|
||||||
@@ -131,7 +133,7 @@ function extractSourceFromName(
|
|||||||
* Check if a backend task name is a plugin install task.
|
* Check if a backend task name is a plugin install task.
|
||||||
*/
|
*/
|
||||||
function isPluginInstallTask(name: string): boolean {
|
function isPluginInstallTask(name: string): boolean {
|
||||||
return name.startsWith('plugin-install-');
|
return name.startsWith('plugin-install-') || name.startsWith('mcp-install-') || name.startsWith('skill-install-');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,13 +167,21 @@ function asyncTaskToPluginInstallTask(task: AsyncTask): PluginInstallTask {
|
|||||||
overallProgress = Math.min(95, stageToProgress(stage));
|
overallProgress = Math.min(95, stageToProgress(stage));
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginName = str(md.plugin_name) || task.label || `${source} plugin`;
|
const pluginName = str(md.plugin_name) || task.label || `${source} extension`;
|
||||||
|
|
||||||
|
let extensionType: 'plugin' | 'mcp' | 'skill' = 'plugin';
|
||||||
|
if (task.name.startsWith('mcp-install-')) {
|
||||||
|
extensionType = 'mcp';
|
||||||
|
} else if (task.name.startsWith('skill-install-')) {
|
||||||
|
extensionType = 'skill';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `${source}-${task.id}`,
|
id: `${source}-${task.id}`,
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
pluginName,
|
pluginName,
|
||||||
source,
|
source,
|
||||||
|
extensionType,
|
||||||
stage,
|
stage,
|
||||||
overallProgress,
|
overallProgress,
|
||||||
downloadCurrent: num(md.download_current),
|
downloadCurrent: num(md.download_current),
|
||||||
@@ -388,6 +398,7 @@ export function PluginInstallTaskProvider({
|
|||||||
converted.startedAt = existing.startedAt;
|
converted.startedAt = existing.startedAt;
|
||||||
converted.pluginName = existing.pluginName;
|
converted.pluginName = existing.pluginName;
|
||||||
converted.fileSize = existing.fileSize;
|
converted.fileSize = existing.fileSize;
|
||||||
|
converted.extensionType = existing.extensionType;
|
||||||
updatedTasks[idx] = converted;
|
updatedTasks[idx] = converted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,6 +444,7 @@ export function PluginInstallTaskProvider({
|
|||||||
taskId: number;
|
taskId: number;
|
||||||
pluginName: string;
|
pluginName: string;
|
||||||
source: 'github' | 'marketplace' | 'local';
|
source: 'github' | 'marketplace' | 'local';
|
||||||
|
extensionType: 'plugin' | 'mcp' | 'skill';
|
||||||
fileSize?: number;
|
fileSize?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const taskKey = `${params.source}-${params.taskId}`;
|
const taskKey = `${params.source}-${params.taskId}`;
|
||||||
@@ -445,6 +457,7 @@ export function PluginInstallTaskProvider({
|
|||||||
taskId: params.taskId,
|
taskId: params.taskId,
|
||||||
pluginName: params.pluginName,
|
pluginName: params.pluginName,
|
||||||
source: params.source,
|
source: params.source,
|
||||||
|
extensionType: params.extensionType,
|
||||||
stage: InstallStage.DOWNLOADING,
|
stage: InstallStage.DOWNLOADING,
|
||||||
overallProgress: 5,
|
overallProgress: 5,
|
||||||
fileSize: params.fileSize,
|
fileSize: params.fileSize,
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
X,
|
X,
|
||||||
ListTodo,
|
ListTodo,
|
||||||
|
Wrench,
|
||||||
|
AudioWaveform,
|
||||||
|
Book,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@@ -35,6 +38,12 @@ const STAGE_ICONS: Record<string, React.ElementType> = {
|
|||||||
[InstallStage.ERROR]: XCircle,
|
[InstallStage.ERROR]: XCircle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const EXTENSION_TYPE_ICONS: Record<string, React.ElementType> = {
|
||||||
|
plugin: Wrench,
|
||||||
|
mcp: AudioWaveform,
|
||||||
|
skill: Book,
|
||||||
|
};
|
||||||
|
|
||||||
function TaskQueueItem({
|
function TaskQueueItem({
|
||||||
task,
|
task,
|
||||||
onClick,
|
onClick,
|
||||||
@@ -49,6 +58,40 @@ function TaskQueueItem({
|
|||||||
const isError = task.stage === InstallStage.ERROR;
|
const isError = task.stage === InstallStage.ERROR;
|
||||||
const isRunning = !isDone && !isError;
|
const isRunning = !isDone && !isError;
|
||||||
const StageIcon = STAGE_ICONS[task.stage] || Download;
|
const StageIcon = STAGE_ICONS[task.stage] || Download;
|
||||||
|
const TypeIcon = EXTENSION_TYPE_ICONS[task.extensionType] || Wrench;
|
||||||
|
|
||||||
|
const getTypeBadgeClass = () => {
|
||||||
|
switch (task.extensionType) {
|
||||||
|
case 'mcp':
|
||||||
|
return 'border-sky-500 text-sky-600 dark:border-sky-400 dark:text-sky-300';
|
||||||
|
case 'skill':
|
||||||
|
return 'border-emerald-500 text-emerald-600 dark:border-emerald-400 dark:text-emerald-300';
|
||||||
|
default:
|
||||||
|
return 'border-violet-500 text-violet-600 dark:border-violet-400 dark:text-violet-300';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeLabel = () => {
|
||||||
|
switch (task.extensionType) {
|
||||||
|
case 'mcp':
|
||||||
|
return 'MCP';
|
||||||
|
case 'skill':
|
||||||
|
return t('common.skill');
|
||||||
|
default:
|
||||||
|
return t('market.typePlugin');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInstallCompleteMessage = () => {
|
||||||
|
switch (task.extensionType) {
|
||||||
|
case 'mcp':
|
||||||
|
return t('plugins.installProgress.installCompleteMCP');
|
||||||
|
case 'skill':
|
||||||
|
return t('plugins.installProgress.installCompleteSkill');
|
||||||
|
default:
|
||||||
|
return t('plugins.installProgress.installCompletePlugin');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const stageLabel = (() => {
|
const stageLabel = (() => {
|
||||||
switch (task.stage) {
|
switch (task.stage) {
|
||||||
@@ -61,7 +104,7 @@ function TaskQueueItem({
|
|||||||
case InstallStage.LAUNCHING:
|
case InstallStage.LAUNCHING:
|
||||||
return t('plugins.installProgress.launching');
|
return t('plugins.installProgress.launching');
|
||||||
case InstallStage.DONE:
|
case InstallStage.DONE:
|
||||||
return t('plugins.installProgress.completed');
|
return isDone ? getInstallCompleteMessage() : t('plugins.installProgress.completed');
|
||||||
case InstallStage.ERROR:
|
case InstallStage.ERROR:
|
||||||
return t('plugins.installProgress.failed');
|
return t('plugins.installProgress.failed');
|
||||||
default:
|
default:
|
||||||
@@ -93,7 +136,16 @@ function TaskQueueItem({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-sm font-medium truncate">{task.pluginName}</div>
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="text-sm font-medium truncate">{task.pluginName}</div>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn('text-[0.6rem] px-1 py-0 flex-shrink-0', getTypeBadgeClass())}
|
||||||
|
>
|
||||||
|
<TypeIcon className="w-3 h-3 mr-0.5" />
|
||||||
|
{getTypeLabel()}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-muted-foreground">{stageLabel}</span>
|
<span className="text-xs text-muted-foreground">{stageLabel}</span>
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
import { ExtensionCardVO, ExtensionType } from './ExtensionCardVO';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { BugIcon, ExternalLink, Ellipsis, Trash, ArrowUp } from 'lucide-react';
|
||||||
|
import { getCloudServiceClientSync, systemInfo } from '@/app/infra/http';
|
||||||
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import {
|
||||||
|
MCPSessionStatus,
|
||||||
|
} from '@/app/infra/entities/api';
|
||||||
|
|
||||||
|
type ExtensionCardComponentProps = {
|
||||||
|
cardVO: ExtensionCardVO;
|
||||||
|
onCardClick: () => void;
|
||||||
|
onDeleteClick: (cardVO: ExtensionCardVO) => void;
|
||||||
|
onUpgradeClick?: (cardVO: ExtensionCardVO) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ExtensionCardComponent({
|
||||||
|
cardVO,
|
||||||
|
onCardClick,
|
||||||
|
onDeleteClick,
|
||||||
|
onUpgradeClick,
|
||||||
|
}: ExtensionCardComponentProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
|
|
||||||
|
const getTypeBadgeColor = (type: ExtensionType) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'mcp':
|
||||||
|
return 'border-sky-500 text-sky-600 dark:border-sky-400 dark:text-sky-300';
|
||||||
|
case 'skill':
|
||||||
|
return 'border-emerald-500 text-emerald-600 dark:border-emerald-400 dark:text-emerald-300';
|
||||||
|
default:
|
||||||
|
return 'border-violet-500 text-violet-600 dark:border-violet-400 dark:text-violet-300';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeLabel = (type: ExtensionType) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'mcp':
|
||||||
|
return 'MCP';
|
||||||
|
case 'skill':
|
||||||
|
return t('common.skill');
|
||||||
|
default:
|
||||||
|
return t('market.typePlugin');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status?: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case MCPSessionStatus.CONNECTED:
|
||||||
|
return 'text-green-600';
|
||||||
|
case MCPSessionStatus.CONNECTING:
|
||||||
|
return 'text-yellow-600';
|
||||||
|
case MCPSessionStatus.ERROR:
|
||||||
|
return 'text-red-600';
|
||||||
|
default:
|
||||||
|
return 'text-gray-600';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPluginContent = () => (
|
||||||
|
<>
|
||||||
|
<div className="text-[0.7rem] text-[#666] dark:text-[#999] truncate w-full">
|
||||||
|
{cardVO.author} / {cardVO.name}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-start gap-[0.4rem] flex-wrap max-w-full">
|
||||||
|
<div className="text-[1.2rem] text-black dark:text-[#f0f0f0] truncate max-w-[10rem]">
|
||||||
|
{cardVO.label}
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] flex-shrink-0"
|
||||||
|
>
|
||||||
|
v{cardVO.version}
|
||||||
|
</Badge>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={`text-[0.7rem] flex-shrink-0 ${getTypeBadgeColor(cardVO.type)}`}
|
||||||
|
>
|
||||||
|
{getTypeLabel(cardVO.type)}
|
||||||
|
</Badge>
|
||||||
|
{cardVO.debug && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] border-orange-400 text-orange-400 flex-shrink-0"
|
||||||
|
>
|
||||||
|
<BugIcon className="w-4 h-4" />
|
||||||
|
{t('plugins.debugging')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{!cardVO.debug && (
|
||||||
|
<>
|
||||||
|
{cardVO.install_source === 'github' && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] border-blue-400 text-blue-400 flex-shrink-0"
|
||||||
|
>
|
||||||
|
{t('plugins.fromGithub')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{cardVO.install_source === 'local' && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] border-green-400 text-green-400 flex-shrink-0"
|
||||||
|
>
|
||||||
|
{t('plugins.fromLocal')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{cardVO.install_source === 'marketplace' && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] border-purple-400 text-purple-400 flex-shrink-0"
|
||||||
|
>
|
||||||
|
{t('plugins.fromMarketplace')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-[0.8rem] text-[#666] line-clamp-2 dark:text-[#999] w-full">
|
||||||
|
{cardVO.description}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderMCPContent = () => (
|
||||||
|
<>
|
||||||
|
<div className="text-[0.7rem] text-[#666] dark:text-[#999] truncate w-full">
|
||||||
|
MCP Server
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-start gap-[0.4rem] flex-wrap max-w-full">
|
||||||
|
<div className="text-[1.2rem] text-black dark:text-[#f0f0f0] truncate max-w-[10rem]">
|
||||||
|
{cardVO.label}
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={`text-[0.7rem] flex-shrink-0 ${getTypeBadgeColor('mcp')}`}
|
||||||
|
>
|
||||||
|
MCP
|
||||||
|
</Badge>
|
||||||
|
{cardVO.mode && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] border-gray-400 text-gray-600 dark:text-gray-300 flex-shrink-0"
|
||||||
|
>
|
||||||
|
{cardVO.mode.toUpperCase()}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={`text-[0.7rem] flex-shrink-0 ${
|
||||||
|
cardVO.enabled
|
||||||
|
? 'border-green-400 text-green-600 dark:text-green-400'
|
||||||
|
: 'border-gray-400 text-gray-600 dark:text-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cardVO.enabled ? t('mcp.statusConnected') : t('mcp.statusDisabled')}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-[0.8rem] text-[#666] line-clamp-2 dark:text-[#999] w-full">
|
||||||
|
{cardVO.description || t('mcp.noToolsFound')}
|
||||||
|
{cardVO.tools !== undefined && cardVO.tools > 0 && (
|
||||||
|
<span className="ml-1">{t('mcp.toolCount', { count: cardVO.tools })}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderSkillContent = () => (
|
||||||
|
<>
|
||||||
|
<div className="text-[0.7rem] text-[#666] dark:text-[#999] truncate w-full">
|
||||||
|
Skill
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-start gap-[0.4rem] flex-wrap max-w-full">
|
||||||
|
<div className="text-[1.2rem] text-black dark:text-[#f0f0f0] truncate max-w-[10rem]">
|
||||||
|
{cardVO.label}
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={`text-[0.7rem] flex-shrink-0 ${getTypeBadgeColor('skill')}`}
|
||||||
|
>
|
||||||
|
{t('common.skill')}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-[0.8rem] text-[#666] line-clamp-2 dark:text-[#999] w-full">
|
||||||
|
{cardVO.description}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="w-[100%] h-[10rem] bg-white rounded-[10px] border border-[#e4e4e7] dark:border-[#27272a] p-[1.2rem] cursor-pointer dark:bg-[#1f1f22] relative transition-all duration-200 hover:border-[#a1a1aa] dark:hover:border-[#3f3f46]"
|
||||||
|
onClick={() => onCardClick()}
|
||||||
|
>
|
||||||
|
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
|
||||||
|
<img
|
||||||
|
src={cardVO.iconURL || httpClient.getPluginIconURL(cardVO.author, cardVO.name)}
|
||||||
|
alt="extension icon"
|
||||||
|
className="w-16 h-16 rounded-[8%] flex-shrink-0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0 h-full flex flex-col items-start justify-between gap-[0.6rem]">
|
||||||
|
<div className="flex flex-col items-start justify-start w-full min-w-0 flex-1 overflow-hidden">
|
||||||
|
{cardVO.type === 'plugin' && renderPluginContent()}
|
||||||
|
{cardVO.type === 'mcp' && renderMCPContent()}
|
||||||
|
{cardVO.type === 'skill' && renderSkillContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="flex flex-col items-center justify-between h-full relative z-20 flex-shrink-0"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center"></div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<DropdownMenu
|
||||||
|
open={dropdownOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setDropdownOpen(open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="relative">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="bg-white dark:bg-[#1f1f22] hover:bg-gray-100 dark:hover:bg-[#2a2a2d]"
|
||||||
|
>
|
||||||
|
<Ellipsis className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
{cardVO.hasUpdate && (
|
||||||
|
<div className="absolute -top-0.5 -right-0.5 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white dark:border-[#1f1f22]"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
{cardVO.type === 'plugin' && cardVO.install_source === 'marketplace' && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="flex flex-row items-center justify-start gap-[0.4rem] cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onUpgradeClick) {
|
||||||
|
onUpgradeClick(cardVO);
|
||||||
|
}
|
||||||
|
setDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowUp className="w-4 h-4" />
|
||||||
|
<span>{t('plugins.update')}</span>
|
||||||
|
{cardVO.hasUpdate && (
|
||||||
|
<Badge className="ml-auto bg-red-500 hover:bg-red-500 text-white text-[0.6rem] px-1.5 py-0 h-4">
|
||||||
|
{t('plugins.new')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{cardVO.type === 'plugin' && (cardVO.install_source === 'github' || cardVO.install_source === 'marketplace') && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="flex flex-row items-center justify-start gap-[0.4rem] cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (cardVO.install_source === 'github') {
|
||||||
|
window.open(cardVO.install_info?.github_url as string, '_blank');
|
||||||
|
} else if (cardVO.install_source === 'marketplace') {
|
||||||
|
window.open(
|
||||||
|
getCloudServiceClientSync().getPluginMarketplaceURL(
|
||||||
|
systemInfo.cloud_service_url,
|
||||||
|
cardVO.author,
|
||||||
|
cardVO.name,
|
||||||
|
),
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-4 h-4" />
|
||||||
|
<span>{t('plugins.viewSource')}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="flex flex-row items-center justify-start gap-[0.4rem] cursor-pointer text-red-600 focus:text-red-600"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDeleteClick(cardVO);
|
||||||
|
setDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash className="w-4 h-4" />
|
||||||
|
<span>
|
||||||
|
{cardVO.type === 'mcp'
|
||||||
|
? t('mcp.deleteServer')
|
||||||
|
: cardVO.type === 'skill'
|
||||||
|
? t('skills.delete')
|
||||||
|
: t('plugins.delete')}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
export type ExtensionType = 'plugin' | 'mcp' | 'skill';
|
||||||
|
|
||||||
|
export interface IExtensionCardVO {
|
||||||
|
id: string;
|
||||||
|
author: string;
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
version: string;
|
||||||
|
enabled: boolean;
|
||||||
|
type: ExtensionType;
|
||||||
|
iconURL?: string;
|
||||||
|
install_source?: string;
|
||||||
|
install_info?: Record<string, unknown>;
|
||||||
|
status?: string;
|
||||||
|
debug?: boolean;
|
||||||
|
hasUpdate?: boolean;
|
||||||
|
runtimeStatus?: 'connecting' | 'connected' | 'error' | 'disabled';
|
||||||
|
tools?: number;
|
||||||
|
mode?: 'stdio' | 'sse' | 'http';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtensionCardVO implements IExtensionCardVO {
|
||||||
|
id: string;
|
||||||
|
author: string;
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
version: string;
|
||||||
|
enabled: boolean;
|
||||||
|
type: ExtensionType;
|
||||||
|
iconURL?: string;
|
||||||
|
install_source?: string;
|
||||||
|
install_info?: Record<string, unknown>;
|
||||||
|
status?: string;
|
||||||
|
debug?: boolean;
|
||||||
|
hasUpdate?: boolean;
|
||||||
|
runtimeStatus?: 'connecting' | 'connected' | 'error' | 'disabled';
|
||||||
|
tools?: number;
|
||||||
|
mode?: 'stdio' | 'sse' | 'http';
|
||||||
|
|
||||||
|
constructor(prop: IExtensionCardVO) {
|
||||||
|
this.id = prop.id;
|
||||||
|
this.author = prop.author;
|
||||||
|
this.label = prop.label;
|
||||||
|
this.name = prop.name;
|
||||||
|
this.description = prop.description;
|
||||||
|
this.version = prop.version;
|
||||||
|
this.enabled = prop.enabled;
|
||||||
|
this.type = prop.type;
|
||||||
|
this.iconURL = prop.iconURL;
|
||||||
|
this.install_source = prop.install_source;
|
||||||
|
this.install_info = prop.install_info;
|
||||||
|
this.status = prop.status;
|
||||||
|
this.debug = prop.debug;
|
||||||
|
this.hasUpdate = prop.hasUpdate;
|
||||||
|
this.runtimeStatus = prop.runtimeStatus;
|
||||||
|
this.tools = prop.tools;
|
||||||
|
this.mode = prop.mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
+214
-125
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { PluginCardVO } from '@/app/home/plugins/components/plugin-installed/PluginCardVO';
|
import { ExtensionCardVO, ExtensionType } from './ExtensionCardVO';
|
||||||
import PluginCardComponent from '@/app/home/plugins/components/plugin-installed/plugin-card/PluginCardComponent';
|
import ExtensionCardComponent from './ExtensionCardComponent';
|
||||||
import styles from '@/app/home/plugins/plugins.module.css';
|
import styles from '@/app/home/plugins/plugins.module.css';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
import { getCloudServiceClientSync } from '@/app/infra/http';
|
import { getCloudServiceClientSync } from '@/app/infra/http';
|
||||||
@@ -22,42 +22,59 @@ import { toast } from 'sonner';
|
|||||||
import { useAsyncTask, AsyncTaskStatus } from '@/hooks/useAsyncTask';
|
import { useAsyncTask, AsyncTaskStatus } from '@/hooks/useAsyncTask';
|
||||||
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||||
import { Puzzle } from 'lucide-react';
|
import { Puzzle } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
ToggleGroup,
|
||||||
|
ToggleGroupItem,
|
||||||
|
} from '@/components/ui/toggle-group';
|
||||||
|
import { Wrench, AudioWaveform, Book } from 'lucide-react';
|
||||||
|
import { MCPSessionStatus } from '@/app/infra/entities/api';
|
||||||
|
|
||||||
export interface PluginInstalledComponentRef {
|
export interface PluginInstalledComponentRef {
|
||||||
refreshPluginList: () => void;
|
refreshPluginList: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PluginOperationType {
|
enum ExtensionOperationType {
|
||||||
DELETE = 'DELETE',
|
DELETE = 'DELETE',
|
||||||
UPDATE = 'UPDATE',
|
UPDATE = 'UPDATE',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FilterType = 'all' | ExtensionType;
|
||||||
|
|
||||||
|
const FilterOptions = [
|
||||||
|
{ value: 'all' as FilterType, labelKey: 'market.filters.allFormats', icon: null },
|
||||||
|
{ value: 'plugin' as FilterType, labelKey: 'market.typePlugin', icon: Wrench },
|
||||||
|
{ value: 'mcp' as FilterType, labelKey: 'market.typeMCP', icon: AudioWaveform },
|
||||||
|
{ value: 'skill' as FilterType, labelKey: 'market.typeSkill', icon: Book },
|
||||||
|
];
|
||||||
|
|
||||||
const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { refreshPlugins } = useSidebarData();
|
const { refreshPlugins, refreshMCPServers, refreshSkills } = useSidebarData();
|
||||||
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
|
const [extensionList, setExtensionList] = useState<ExtensionCardVO[]>([]);
|
||||||
|
const [filterType, setFilterType] = useState<FilterType>('all');
|
||||||
const [showOperationModal, setShowOperationModal] = useState(false);
|
const [showOperationModal, setShowOperationModal] = useState(false);
|
||||||
const [operationType, setOperationType] = useState<PluginOperationType>(
|
const [operationType, setOperationType] = useState<ExtensionOperationType>(
|
||||||
PluginOperationType.DELETE,
|
ExtensionOperationType.DELETE,
|
||||||
);
|
);
|
||||||
const [targetPlugin, setTargetPlugin] = useState<PluginCardVO | null>(null);
|
const [targetExtension, setTargetExtension] = useState<ExtensionCardVO | null>(null);
|
||||||
const [deleteData, setDeleteData] = useState<boolean>(false);
|
const [deleteData, setDeleteData] = useState<boolean>(false);
|
||||||
|
|
||||||
const asyncTask = useAsyncTask({
|
const asyncTask = useAsyncTask({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
const successMessage =
|
const successMessage =
|
||||||
operationType === PluginOperationType.DELETE
|
operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.deleteSuccess')
|
? t('plugins.deleteSuccess')
|
||||||
: t('plugins.updateSuccess');
|
: t('plugins.updateSuccess');
|
||||||
toast.success(successMessage);
|
toast.success(successMessage);
|
||||||
setShowOperationModal(false);
|
setShowOperationModal(false);
|
||||||
getPluginList();
|
getExtensionList();
|
||||||
refreshPlugins();
|
refreshPlugins();
|
||||||
|
refreshMCPServers();
|
||||||
|
refreshSkills();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
// Error is already handled in the hook state
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,131 +83,171 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function initData() {
|
function initData() {
|
||||||
getPluginList();
|
getExtensionList();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPluginList() {
|
async function getExtensionList() {
|
||||||
try {
|
try {
|
||||||
// 获取已安装插件列表
|
|
||||||
const installedPluginsResp = await httpClient.getPlugins();
|
|
||||||
const installedPlugins = installedPluginsResp.plugins;
|
|
||||||
|
|
||||||
// 获取市场插件列表
|
|
||||||
const client = getCloudServiceClientSync();
|
const client = getCloudServiceClientSync();
|
||||||
const marketplaceResp = await client.getMarketplacePlugins(1, 100);
|
|
||||||
const marketplacePlugins = marketplaceResp.plugins;
|
|
||||||
|
|
||||||
// 创建市场插件映射,便于快速查找
|
const [installedPluginsResp, marketplaceResp, mcpResp, skillsResp] = await Promise.all([
|
||||||
const marketplacePluginMap = new Map();
|
httpClient.getPlugins().catch(() => ({ plugins: [] })),
|
||||||
marketplacePlugins.forEach((plugin) => {
|
client.getMarketplacePlugins(1, 100).catch(() => ({ plugins: [] })),
|
||||||
|
httpClient.getMCPServers().catch(() => ({ servers: [] })),
|
||||||
|
httpClient.getSkills().catch(() => ({ skills: [] })),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const marketplacePluginMap = new Map<string, any>();
|
||||||
|
marketplaceResp.plugins.forEach((plugin: any) => {
|
||||||
const key = `${plugin.author}/${plugin.name}`;
|
const key = `${plugin.author}/${plugin.name}`;
|
||||||
marketplacePluginMap.set(key, plugin);
|
marketplacePluginMap.set(key, plugin);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 转换并比较版本号
|
const extensions: ExtensionCardVO[] = [];
|
||||||
const pluginCards = installedPlugins.map((plugin) => {
|
|
||||||
const marketplaceKey = `${plugin.manifest.manifest.metadata.author}/${plugin.manifest.manifest.metadata.name}`;
|
|
||||||
const marketplacePlugin = marketplacePluginMap.get(marketplaceKey);
|
|
||||||
const cardVO = new PluginCardVO({
|
|
||||||
author: plugin.manifest.manifest.metadata.author ?? '',
|
|
||||||
label: extractI18nObject(plugin.manifest.manifest.metadata.label),
|
|
||||||
description: extractI18nObject(
|
|
||||||
plugin.manifest.manifest.metadata.description ?? {
|
|
||||||
en_US: '',
|
|
||||||
zh_Hans: '',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
debug: plugin.debug,
|
|
||||||
enabled: plugin.enabled,
|
|
||||||
name: plugin.manifest.manifest.metadata.name,
|
|
||||||
version: plugin.manifest.manifest.metadata.version ?? '',
|
|
||||||
status: plugin.status,
|
|
||||||
components: plugin.components,
|
|
||||||
priority: plugin.priority,
|
|
||||||
install_source: plugin.install_source,
|
|
||||||
install_info: plugin.install_info,
|
|
||||||
type: marketplacePlugin?.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查是否来自市场且有更新
|
for (const plugin of installedPluginsResp.plugins) {
|
||||||
if (cardVO.install_source === 'marketplace' && marketplacePlugin) {
|
const meta = plugin.manifest.manifest.metadata;
|
||||||
|
const author = meta.author ?? '';
|
||||||
|
const name = meta.name;
|
||||||
|
const marketplaceKey = `${author}/${name}`;
|
||||||
|
const marketplacePlugin = marketplacePluginMap.get(marketplaceKey);
|
||||||
|
|
||||||
|
let hasUpdate = false;
|
||||||
|
if (plugin.install_source === 'marketplace' && marketplacePlugin) {
|
||||||
if (marketplacePlugin.latest_version) {
|
if (marketplacePlugin.latest_version) {
|
||||||
cardVO.hasUpdate = isNewerVersion(
|
hasUpdate = isNewerVersion(
|
||||||
marketplacePlugin.latest_version,
|
marketplacePlugin.latest_version,
|
||||||
cardVO.version,
|
meta.version ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cardVO;
|
extensions.push(new ExtensionCardVO({
|
||||||
});
|
id: marketplaceKey,
|
||||||
|
author,
|
||||||
|
label: extractI18nObject(meta.label) || name,
|
||||||
|
name,
|
||||||
|
description: extractI18nObject(meta.description ?? { en_US: '', zh_Hans: '' }),
|
||||||
|
version: meta.version ?? '',
|
||||||
|
enabled: plugin.enabled,
|
||||||
|
type: marketplacePlugin?.type || 'plugin',
|
||||||
|
iconURL: httpClient.getPluginIconURL(author, name),
|
||||||
|
install_source: plugin.install_source,
|
||||||
|
install_info: plugin.install_info,
|
||||||
|
status: plugin.status,
|
||||||
|
debug: plugin.debug,
|
||||||
|
hasUpdate,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
setPluginList(pluginCards);
|
for (const server of mcpResp.servers) {
|
||||||
|
extensions.push(new ExtensionCardVO({
|
||||||
|
id: `mcp-${server.name}`,
|
||||||
|
author: '',
|
||||||
|
label: server.name.replace(/__/g, '/'),
|
||||||
|
name: server.name,
|
||||||
|
description: '',
|
||||||
|
version: '',
|
||||||
|
enabled: server.enable,
|
||||||
|
type: 'mcp',
|
||||||
|
iconURL: httpClient.getPluginIconURL('mcp', server.name),
|
||||||
|
status: server.runtime_info?.status,
|
||||||
|
runtimeStatus: server.runtime_info?.status,
|
||||||
|
tools: server.runtime_info?.tool_count || 0,
|
||||||
|
mode: server.mode,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const skill of skillsResp.skills) {
|
||||||
|
extensions.push(new ExtensionCardVO({
|
||||||
|
id: `skill-${skill.name}`,
|
||||||
|
author: '',
|
||||||
|
label: skill.display_name || skill.name,
|
||||||
|
name: skill.name,
|
||||||
|
description: skill.description || '',
|
||||||
|
version: '',
|
||||||
|
enabled: true,
|
||||||
|
type: 'skill',
|
||||||
|
iconURL: httpClient.getPluginIconURL('skill', skill.name),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setExtensionList(extensions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取插件列表失败:', error);
|
console.error('Failed to fetch extension list:', error);
|
||||||
// 失败时仍显示已安装插件,不影响用户体验
|
setExtensionList([]);
|
||||||
const installedPluginsResp = await httpClient.getPlugins();
|
|
||||||
setPluginList(
|
|
||||||
installedPluginsResp.plugins.map((plugin) => {
|
|
||||||
return new PluginCardVO({
|
|
||||||
author: plugin.manifest.manifest.metadata.author ?? '',
|
|
||||||
label: extractI18nObject(plugin.manifest.manifest.metadata.label),
|
|
||||||
description: extractI18nObject(
|
|
||||||
plugin.manifest.manifest.metadata.description ?? {
|
|
||||||
en_US: '',
|
|
||||||
zh_Hans: '',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
debug: plugin.debug,
|
|
||||||
enabled: plugin.enabled,
|
|
||||||
name: plugin.manifest.manifest.metadata.name,
|
|
||||||
version: plugin.manifest.manifest.metadata.version ?? '',
|
|
||||||
status: plugin.status,
|
|
||||||
components: plugin.components,
|
|
||||||
priority: plugin.priority,
|
|
||||||
install_source: plugin.install_source,
|
|
||||||
install_info: plugin.install_info,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
refreshPluginList: getPluginList,
|
refreshPluginList: getExtensionList,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function handlePluginClick(plugin: PluginCardVO) {
|
function handleExtensionClick(extension: ExtensionCardVO) {
|
||||||
const pluginId = `${plugin.author}/${plugin.name}`;
|
if (extension.type === 'mcp') {
|
||||||
navigate(`/home/plugins?id=${encodeURIComponent(pluginId)}`);
|
navigate(`/home/mcp`);
|
||||||
|
} else if (extension.type === 'skill') {
|
||||||
|
navigate(`/home/skills`);
|
||||||
|
} else {
|
||||||
|
const extensionId = `${extension.author}/${extension.name}`;
|
||||||
|
navigate(`/home/plugins?id=${encodeURIComponent(extensionId)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePluginDelete(plugin: PluginCardVO) {
|
function handleExtensionDelete(extension: ExtensionCardVO) {
|
||||||
setTargetPlugin(plugin);
|
setTargetExtension(extension);
|
||||||
setOperationType(PluginOperationType.DELETE);
|
setOperationType(ExtensionOperationType.DELETE);
|
||||||
setShowOperationModal(true);
|
setShowOperationModal(true);
|
||||||
setDeleteData(false);
|
setDeleteData(false);
|
||||||
asyncTask.reset();
|
asyncTask.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePluginUpdate(plugin: PluginCardVO) {
|
function handleExtensionUpdate(extension: ExtensionCardVO) {
|
||||||
setTargetPlugin(plugin);
|
setTargetExtension(extension);
|
||||||
setOperationType(PluginOperationType.UPDATE);
|
setOperationType(ExtensionOperationType.UPDATE);
|
||||||
setShowOperationModal(true);
|
setShowOperationModal(true);
|
||||||
asyncTask.reset();
|
asyncTask.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeOperation() {
|
function executeOperation() {
|
||||||
if (!targetPlugin) return;
|
if (!targetExtension) return;
|
||||||
|
|
||||||
|
if (targetExtension.type === 'mcp') {
|
||||||
|
httpClient.deleteMCPServer(targetExtension.name)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(t('mcp.deleteSuccess'));
|
||||||
|
setShowOperationModal(false);
|
||||||
|
getExtensionList();
|
||||||
|
refreshMCPServers();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(t('mcp.deleteError') + error.message);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetExtension.type === 'skill') {
|
||||||
|
httpClient.deleteSkill(targetExtension.name)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(t('skills.deleteSuccess'));
|
||||||
|
setShowOperationModal(false);
|
||||||
|
getExtensionList();
|
||||||
|
refreshSkills();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(t('skills.deleteError') + error.message);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const apiCall =
|
const apiCall =
|
||||||
operationType === PluginOperationType.DELETE
|
operationType === ExtensionOperationType.DELETE
|
||||||
? httpClient.removePlugin(
|
? httpClient.removePlugin(
|
||||||
targetPlugin.author,
|
targetExtension.author,
|
||||||
targetPlugin.name,
|
targetExtension.name,
|
||||||
deleteData,
|
deleteData,
|
||||||
)
|
)
|
||||||
: httpClient.upgradePlugin(targetPlugin.author, targetPlugin.name);
|
: httpClient.upgradePlugin(targetExtension.author, targetExtension.name);
|
||||||
|
|
||||||
apiCall
|
apiCall
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -198,13 +255,32 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
operationType === PluginOperationType.DELETE
|
operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.deleteError') + error.message
|
? t('plugins.deleteError') + error.message
|
||||||
: t('plugins.updateError') + error.message;
|
: t('plugins.updateError') + error.message;
|
||||||
toast.error(errorMessage);
|
toast.error(errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredExtensions = extensionList.filter((ext) => {
|
||||||
|
if (filterType === 'all') return true;
|
||||||
|
return ext.type === filterType;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDeleteConfirmMessage = () => {
|
||||||
|
if (!targetExtension) return '';
|
||||||
|
if (targetExtension.type === 'mcp') {
|
||||||
|
return t('mcp.confirmDeleteServer');
|
||||||
|
}
|
||||||
|
if (targetExtension.type === 'skill') {
|
||||||
|
return t('skills.deleteConfirmation');
|
||||||
|
}
|
||||||
|
return t('plugins.confirmDeletePlugin', {
|
||||||
|
author: targetExtension.author,
|
||||||
|
name: targetExtension.name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -212,7 +288,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setShowOperationModal(false);
|
setShowOperationModal(false);
|
||||||
setTargetPlugin(null);
|
setTargetExtension(null);
|
||||||
asyncTask.reset();
|
asyncTask.reset();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -220,7 +296,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{operationType === PluginOperationType.DELETE
|
{operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.deleteConfirm')
|
? t('plugins.deleteConfirm')
|
||||||
: t('plugins.updateConfirm')}
|
: t('plugins.updateConfirm')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
@@ -228,18 +304,8 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
|
{asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
<div>{getDeleteConfirmMessage()}</div>
|
||||||
{operationType === PluginOperationType.DELETE
|
{operationType === ExtensionOperationType.DELETE && targetExtension?.type === 'plugin' && (
|
||||||
? t('plugins.confirmDeletePlugin', {
|
|
||||||
author: targetPlugin?.author ?? '',
|
|
||||||
name: targetPlugin?.name ?? '',
|
|
||||||
})
|
|
||||||
: t('plugins.confirmUpdatePlugin', {
|
|
||||||
author: targetPlugin?.author ?? '',
|
|
||||||
name: targetPlugin?.name ?? '',
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{operationType === PluginOperationType.DELETE && (
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="delete-data"
|
id="delete-data"
|
||||||
@@ -260,14 +326,14 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
)}
|
)}
|
||||||
{asyncTask.status === AsyncTaskStatus.RUNNING && (
|
{asyncTask.status === AsyncTaskStatus.RUNNING && (
|
||||||
<div>
|
<div>
|
||||||
{operationType === PluginOperationType.DELETE
|
{operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.deleting')
|
? t('plugins.deleting')
|
||||||
: t('plugins.updating')}
|
: t('plugins.updating')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{asyncTask.status === AsyncTaskStatus.ERROR && (
|
{asyncTask.status === AsyncTaskStatus.ERROR && (
|
||||||
<div>
|
<div>
|
||||||
{operationType === PluginOperationType.DELETE
|
{operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.deleteError')
|
? t('plugins.deleteError')
|
||||||
: t('plugins.updateError')}
|
: t('plugins.updateError')}
|
||||||
<div className="text-red-500">{asyncTask.error}</div>
|
<div className="text-red-500">{asyncTask.error}</div>
|
||||||
@@ -280,7 +346,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowOperationModal(false);
|
setShowOperationModal(false);
|
||||||
setTargetPlugin(null);
|
setTargetExtension(null);
|
||||||
asyncTask.reset();
|
asyncTask.reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -290,7 +356,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
{asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
|
{asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
|
||||||
<Button
|
<Button
|
||||||
variant={
|
variant={
|
||||||
operationType === PluginOperationType.DELETE
|
operationType === ExtensionOperationType.DELETE
|
||||||
? 'destructive'
|
? 'destructive'
|
||||||
: 'default'
|
: 'default'
|
||||||
}
|
}
|
||||||
@@ -298,7 +364,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
executeOperation();
|
executeOperation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{operationType === PluginOperationType.DELETE
|
{operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.confirmDelete')
|
? t('plugins.confirmDelete')
|
||||||
: t('plugins.confirmUpdate')}
|
: t('plugins.confirmUpdate')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -306,13 +372,13 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
{asyncTask.status === AsyncTaskStatus.RUNNING && (
|
{asyncTask.status === AsyncTaskStatus.RUNNING && (
|
||||||
<Button
|
<Button
|
||||||
variant={
|
variant={
|
||||||
operationType === PluginOperationType.DELETE
|
operationType === ExtensionOperationType.DELETE
|
||||||
? 'destructive'
|
? 'destructive'
|
||||||
: 'default'
|
: 'default'
|
||||||
}
|
}
|
||||||
disabled
|
disabled
|
||||||
>
|
>
|
||||||
{operationType === PluginOperationType.DELETE
|
{operationType === ExtensionOperationType.DELETE
|
||||||
? t('plugins.deleting')
|
? t('plugins.deleting')
|
||||||
: t('plugins.updating')}
|
: t('plugins.updating')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -332,21 +398,44 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{pluginList.length === 0 ? (
|
<div className="px-[0.8rem] pb-4">
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
value={filterType}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (value) setFilterType(value as FilterType);
|
||||||
|
}}
|
||||||
|
className="justify-start"
|
||||||
|
>
|
||||||
|
{FilterOptions.map((option) => (
|
||||||
|
<ToggleGroupItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
variant="outline"
|
||||||
|
className="px-4 py-2"
|
||||||
|
>
|
||||||
|
{option.icon && <option.icon className="w-4 h-4 mr-2" />}
|
||||||
|
{t(option.labelKey)}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
))}
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredExtensions.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center text-gray-500 min-h-[60vh] w-full gap-2">
|
<div className="flex flex-col items-center justify-center text-gray-500 min-h-[60vh] w-full gap-2">
|
||||||
<Puzzle className="h-[3rem] w-[3rem]" />
|
<Puzzle className="h-[3rem] w-[3rem]" />
|
||||||
<div className="text-lg mb-2">{t('plugins.noPluginInstalled')}</div>
|
<div className="text-lg mb-2">{t('plugins.noPluginInstalled')}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`${styles.pluginListContainer}`}>
|
<div className={`${styles.pluginListContainer}`}>
|
||||||
{pluginList.map((vo, index) => {
|
{filteredExtensions.map((vo, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={vo.id || index}>
|
||||||
<PluginCardComponent
|
<ExtensionCardComponent
|
||||||
cardVO={vo}
|
cardVO={vo}
|
||||||
onCardClick={() => handlePluginClick(vo)}
|
onCardClick={() => handleExtensionClick(vo)}
|
||||||
onDeleteClick={() => handlePluginDelete(vo)}
|
onDeleteClick={() => handleExtensionDelete(vo)}
|
||||||
onUpgradeClick={() => handlePluginUpdate(vo)}
|
onUpgradeClick={vo.type === 'plugin' ? () => handleExtensionUpdate(vo) : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -358,4 +447,4 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default PluginInstalledComponent;
|
export default PluginInstalledComponent;
|
||||||
@@ -315,6 +315,7 @@ function PluginListView() {
|
|||||||
taskId,
|
taskId,
|
||||||
pluginName: pluginDisplayName,
|
pluginName: pluginDisplayName,
|
||||||
source: 'github',
|
source: 'github',
|
||||||
|
extensionType: 'plugin',
|
||||||
fileSize: assetSize,
|
fileSize: assetSize,
|
||||||
});
|
});
|
||||||
setSelectedTaskId(taskKey);
|
setSelectedTaskId(taskKey);
|
||||||
@@ -337,6 +338,7 @@ function PluginListView() {
|
|||||||
taskId,
|
taskId,
|
||||||
pluginName: fileName,
|
pluginName: fileName,
|
||||||
source: 'local',
|
source: 'local',
|
||||||
|
extensionType: 'plugin',
|
||||||
fileSize: fileSize,
|
fileSize: fileSize,
|
||||||
});
|
});
|
||||||
setSelectedTaskId(taskKey);
|
setSelectedTaskId(taskKey);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ HTTP Client 已经重构为更清晰的架构,将通用方法与业务逻辑
|
|||||||
|
|
||||||
- **BaseHttpClient.ts** - 基础 HTTP 客户端类,包含所有通用的 HTTP 方法和拦截器配置
|
- **BaseHttpClient.ts** - 基础 HTTP 客户端类,包含所有通用的 HTTP 方法和拦截器配置
|
||||||
- **BackendClient.ts** - 后端服务客户端,处理与后端 API 的所有交互
|
- **BackendClient.ts** - 后端服务客户端,处理与后端 API 的所有交互
|
||||||
- **CloudServiceClient.ts** - 云服务客户端,处理与 cloud service 的交互(如插件市场)
|
- **CloudServiceClient.ts** - 云服务客户端,处理与 cloud service 的交互(如拓展市场)
|
||||||
- **index.ts** - 主入口文件,管理客户端实例的创建和导出
|
- **index.ts** - 主入口文件,管理客户端实例的创建和导出
|
||||||
- **HttpClient.ts** - 仅用于向后兼容的文件(已废弃)
|
- **HttpClient.ts** - 仅用于向后兼容的文件(已废弃)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ const enUS = {
|
|||||||
sidebar: {
|
sidebar: {
|
||||||
home: 'Home',
|
home: 'Home',
|
||||||
extensions: 'Extensions',
|
extensions: 'Extensions',
|
||||||
installedPlugins: 'Installed Plugins',
|
installedPlugins: 'Installed Extensions',
|
||||||
pluginMarket: 'Marketplace',
|
pluginMarket: 'Extension Market',
|
||||||
mcpServers: 'MCP Servers',
|
mcpServers: 'MCP Servers',
|
||||||
pluginPages: 'Plugin Pages',
|
pluginPages: 'Plugin Pages',
|
||||||
pluginPagesTooltip: 'Visual pages provided by installed plugins',
|
pluginPagesTooltip: 'Visual pages provided by installed plugins',
|
||||||
@@ -429,7 +429,7 @@ const enUS = {
|
|||||||
createPlugin: 'Create Plugin',
|
createPlugin: 'Create Plugin',
|
||||||
editPlugin: 'Edit Plugin',
|
editPlugin: 'Edit Plugin',
|
||||||
installed: 'Installed',
|
installed: 'Installed',
|
||||||
marketplace: 'Marketplace',
|
marketplace: 'Extension Market',
|
||||||
arrange: 'Sort Plugins',
|
arrange: 'Sort Plugins',
|
||||||
install: 'Install',
|
install: 'Install',
|
||||||
installPlugin: 'Install Plugin',
|
installPlugin: 'Install Plugin',
|
||||||
@@ -571,22 +571,28 @@ const enUS = {
|
|||||||
assetSize: 'Size: {{size}}',
|
assetSize: 'Size: {{size}}',
|
||||||
confirmInstall: 'Confirm Install',
|
confirmInstall: 'Confirm Install',
|
||||||
installFromGithubDesc: 'Install plugin from GitHub Release',
|
installFromGithubDesc: 'Install plugin from GitHub Release',
|
||||||
goToMarketplace: 'Go to Marketplace',
|
goToMarketplace: 'Go to Extension Market',
|
||||||
installProgress: {
|
installProgress: {
|
||||||
title: 'Installing {{name}}',
|
title: 'Installing {{name}}',
|
||||||
titleGeneric: 'Plugin Installation',
|
titleGeneric: 'Extension Installation',
|
||||||
|
titlePlugin: 'Installing Plugin {{name}}',
|
||||||
|
titleMCP: 'Installing MCP Server {{name}}',
|
||||||
|
titleSkill: 'Installing Skill {{name}}',
|
||||||
overallProgress: 'Overall Progress',
|
overallProgress: 'Overall Progress',
|
||||||
downloading: 'Downloading Plugin',
|
downloading: 'Downloading',
|
||||||
installingDeps: 'Installing Dependencies',
|
installingDeps: 'Installing Dependencies',
|
||||||
initializing: 'Initializing Settings',
|
initializing: 'Initializing Settings',
|
||||||
launching: 'Launching Plugin',
|
launching: 'Launching',
|
||||||
completed: 'Completed',
|
completed: 'Completed',
|
||||||
failed: 'Failed',
|
failed: 'Failed',
|
||||||
downloadSize: 'Package size: {{size}}',
|
downloadSize: 'Package size: {{size}}',
|
||||||
depsInfo: '{{count}} dependencies to install',
|
depsInfo: '{{count}} dependencies to install',
|
||||||
depsProgress:
|
depsProgress:
|
||||||
'{{installed}}/{{total}} installed · {{remaining}} remaining',
|
'{{installed}}/{{total}} installed · {{remaining}} remaining',
|
||||||
installComplete: 'Plugin installed successfully',
|
installComplete: 'Installation successful',
|
||||||
|
installCompletePlugin: 'Plugin installed successfully',
|
||||||
|
installCompleteMCP: 'MCP Server installed successfully',
|
||||||
|
installCompleteSkill: 'Skill installed successfully',
|
||||||
dismiss: 'Dismiss',
|
dismiss: 'Dismiss',
|
||||||
background: 'Run in Background',
|
background: 'Run in Background',
|
||||||
taskQueue: 'Install Tasks',
|
taskQueue: 'Install Tasks',
|
||||||
@@ -935,7 +941,7 @@ const enUS = {
|
|||||||
builtInParser: 'Provided by Knowledge engine',
|
builtInParser: 'Provided by Knowledge engine',
|
||||||
noParserAvailable:
|
noParserAvailable:
|
||||||
'No parser supports this file type. Please install a parser plugin that can handle this format.',
|
'No parser supports this file type. Please install a parser plugin that can handle this format.',
|
||||||
installParserHint: 'Browse parser plugins in Marketplace →',
|
installParserHint: 'Browse parser plugins in Extension Market →',
|
||||||
confirmUpload: 'Upload',
|
confirmUpload: 'Upload',
|
||||||
cancelUpload: 'Cancel',
|
cancelUpload: 'Cancel',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ const zhHans = {
|
|||||||
sidebar: {
|
sidebar: {
|
||||||
home: '首页',
|
home: '首页',
|
||||||
extensions: '扩展',
|
extensions: '扩展',
|
||||||
installedPlugins: '已安装插件',
|
installedPlugins: '已安装扩展',
|
||||||
pluginMarket: '插件市场',
|
pluginMarket: '拓展市场',
|
||||||
mcpServers: 'MCP 服务器',
|
mcpServers: 'MCP 服务器',
|
||||||
pluginPages: '插件页面',
|
pluginPages: '插件页面',
|
||||||
pluginPagesTooltip: '由已安装的插件提供的可视化页面',
|
pluginPagesTooltip: '由已安装的插件提供的可视化页面',
|
||||||
@@ -412,7 +412,7 @@ const zhHans = {
|
|||||||
createPlugin: '创建插件',
|
createPlugin: '创建插件',
|
||||||
editPlugin: '编辑插件',
|
editPlugin: '编辑插件',
|
||||||
installed: '已安装',
|
installed: '已安装',
|
||||||
marketplace: '插件市场',
|
marketplace: '拓展市场',
|
||||||
arrange: '编排',
|
arrange: '编排',
|
||||||
install: '安装',
|
install: '安装',
|
||||||
installPlugin: '安装插件',
|
installPlugin: '安装插件',
|
||||||
@@ -546,21 +546,27 @@ const zhHans = {
|
|||||||
assetSize: '大小: {{size}}',
|
assetSize: '大小: {{size}}',
|
||||||
confirmInstall: '确认安装',
|
confirmInstall: '确认安装',
|
||||||
installFromGithubDesc: '从 GitHub Release 安装插件',
|
installFromGithubDesc: '从 GitHub Release 安装插件',
|
||||||
goToMarketplace: '前往插件市场',
|
goToMarketplace: '前往拓展市场',
|
||||||
installProgress: {
|
installProgress: {
|
||||||
title: '正在安装 {{name}}',
|
title: '正在安装 {{name}}',
|
||||||
titleGeneric: '插件安装',
|
titleGeneric: '扩展安装',
|
||||||
|
titlePlugin: '正在安装插件 {{name}}',
|
||||||
|
titleMCP: '正在安装 MCP 服务器 {{name}}',
|
||||||
|
titleSkill: '正在安装技能 {{name}}',
|
||||||
overallProgress: '总体进度',
|
overallProgress: '总体进度',
|
||||||
downloading: '下载插件',
|
downloading: '下载中',
|
||||||
installingDeps: '安装依赖',
|
installingDeps: '安装依赖',
|
||||||
initializing: '初始化配置',
|
initializing: '初始化配置',
|
||||||
launching: '启动插件',
|
launching: '启动中',
|
||||||
completed: '已完成',
|
completed: '已完成',
|
||||||
failed: '安装失败',
|
failed: '安装失败',
|
||||||
downloadSize: '包大小: {{size}}',
|
downloadSize: '包大小: {{size}}',
|
||||||
depsInfo: '共 {{count}} 个依赖需要安装',
|
depsInfo: '共 {{count}} 个依赖需要安装',
|
||||||
depsProgress: '已安装 {{installed}}/{{total}} · 剩余 {{remaining}} 个',
|
depsProgress: '已安装 {{installed}}/{{total}} · 剩余 {{remaining}} 个',
|
||||||
installComplete: '插件安装成功',
|
installComplete: '安装成功',
|
||||||
|
installCompletePlugin: '插件安装成功',
|
||||||
|
installCompleteMCP: 'MCP 服务器安装成功',
|
||||||
|
installCompleteSkill: '技能安装成功',
|
||||||
dismiss: '关闭',
|
dismiss: '关闭',
|
||||||
background: '后台运行',
|
background: '后台运行',
|
||||||
taskQueue: '安装任务',
|
taskQueue: '安装任务',
|
||||||
@@ -895,7 +901,7 @@ const zhHans = {
|
|||||||
builtInParser: '由知识引擎提供',
|
builtInParser: '由知识引擎提供',
|
||||||
noParserAvailable:
|
noParserAvailable:
|
||||||
'没有解析器支持此文件类型,请安装支持该格式的解析器插件。',
|
'没有解析器支持此文件类型,请安装支持该格式的解析器插件。',
|
||||||
installParserHint: '前往插件市场安装解析器 →',
|
installParserHint: '前往拓展市场安装解析器 →',
|
||||||
confirmUpload: '上传',
|
confirmUpload: '上传',
|
||||||
cancelUpload: '取消',
|
cancelUpload: '取消',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user