Files
KiloStar/frontend/src/components/Plugin/HeavyPluginList.tsx
T
zhaoxiandClaude Opus 4.7 005ce566a8 feat: Provider model_settings 全链路 + 监管节点工具集 + 重型插件注入 + 前端打磨
- Provider model_settings (Provider+Model 级别参数配置): DB JSONB → API → GSM → AgentFactory.resolve → 三节点 agent.run 注入
- 新增 data/toolset/regulatory_toolset/: 监管节点专属工具(query_workflow_status / query_task_list / send_file)
- send_file 从 interactive_toolset 迁移至 regulatory_toolset,interactive 仅保留 approval
- mcp_helper 合入 GlobalPluginManager dispatch tools
- 前端 Provider 弹窗参数设置区加 JSON 编辑器(model_settings)
- 前端 Plugin 页面新增"重型插件"Tab(HeavyPluginList 占位)
- .gitignore 精简:去除系统默认项,修复 data/ 子目录追踪
- data/toolset/ 与 data/plugin/ 首次纳入版本控制

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 13:10:31 +00:00

88 lines
3.5 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Boxes, Loader2, Package } from 'lucide-react';
import apiClient from '../../api/client';
interface HeavyPlugin {
name: string;
display_name: string;
description: string;
status: string;
}
export function HeavyPluginList() {
const { t } = useTranslation();
const [plugins, setPlugins] = useState<HeavyPlugin[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
const fetchPlugins = async () => {
setLoading(true);
try {
const resp = await apiClient.get('/api/v1/plugin/list');
setPlugins(resp.data.plugins || []);
setError('');
} catch (err) {
console.error('Failed to fetch heavy plugins:', err);
setError(t('plugin.heavyPluginLoadFailed'));
setPlugins([]);
} finally {
setLoading(false);
}
};
fetchPlugins();
}, [t]);
return (
<div className="max-w-4xl mx-auto space-y-6">
<div>
<h3 className="text-lg font-bold text-text-primary">{t('plugin.heavyPluginManagement')}</h3>
<p className="text-sm text-text-muted mt-0.5">{t('plugin.heavyPluginDesc')}</p>
</div>
{loading ? (
<div className="flex flex-col items-center justify-center py-12 text-text-muted">
<Loader2 size={24} className="animate-spin mb-3" />
<span className="text-sm">{t('common.loading')}</span>
</div>
) : error ? (
<div className="p-3 bg-danger-bg text-danger text-sm rounded-xl border border-danger/20">{error}</div>
) : plugins.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 bg-bg-card rounded-2xl border border-border-primary border-dashed text-text-muted">
<Boxes size={32} className="mb-3 opacity-40" />
<span className="text-sm">{t('plugin.heavyPluginEmpty')}</span>
<span className="text-[11px] mt-1.5 opacity-70">{t('plugin.heavyPluginEmptyHint')}</span>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{plugins.map((p) => (
<div key={p.name} className="bg-bg-card border border-border-primary rounded-2xl p-5 card-hover">
<div className="flex items-start gap-3 mb-3">
<div className="w-9 h-9 rounded-xl bg-bg-secondary border border-border-secondary flex items-center justify-center text-accent shrink-0">
<Package size={18} />
</div>
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-sm text-text-primary truncate">{p.display_name || p.name}</h4>
<span className="text-[10px] text-text-muted font-mono">{p.name}</span>
</div>
<span className={`flex items-center gap-1 text-[10px] font-medium px-2 py-1 rounded-lg border ${
p.status === 'running'
? 'bg-success-bg text-success border-success/20'
: 'bg-bg-secondary text-text-muted border-border-primary'
}`}>
{p.status === 'running' && <span className="w-1 h-1 rounded-full bg-success" />}
{p.status}
</span>
</div>
{p.description && (
<p className="text-xs text-text-secondary leading-relaxed">{p.description}</p>
)}
</div>
))}
</div>
)}
</div>
);
}