Files
IdeA/frontend/src/features/plugins/ProjectPluginsPanel.tsx

155 lines
6.5 KiB
TypeScript

import { Button, Panel, Spinner } from "@/shared";
import { useProjectPlugins } from "./useProjectPlugins";
export interface ProjectPluginsPanelProps {
projectId: string;
onAssignAgents?: () => void;
}
function countLabel(count: number, singular: string, plural: string): string {
return `${count} ${count === 1 ? singular : plural}`;
}
export function ProjectPluginsPanel({
projectId,
onAssignAgents,
}: ProjectPluginsPanelProps) {
const vm = useProjectPlugins(projectId);
if (vm.loading && !vm.settings) {
return (
<Panel aria-label="plugins du projet" className="flex items-center gap-2">
<Spinner size={14} />
<span className="text-sm text-muted">Chargement des plugins du projet…</span>
</Panel>
);
}
const installedIds = new Set(vm.installed.map((plugin) => plugin.id));
const missing = (vm.settings?.plugins ?? []).filter(
(activation) => !installedIds.has(activation.pluginId),
);
return (
<section aria-label="plugins du projet" className="flex flex-col gap-3">
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-sm font-semibold text-content">Plugins du projet</h2>
<p className="mt-1 text-xs text-muted">
Activez les plugins utilisés par ce projet, puis attribuez leurs skills et
tools MCP à chaque agent.
</p>
</div>
{onAssignAgents && (
<Button size="sm" variant="primary" onClick={onAssignAgents}>
Attribuer aux agents
</Button>
)}
</div>
{vm.error && <p role="alert" className="text-sm text-danger">{vm.error}</p>}
{vm.installed.length === 0 && missing.length === 0 ? (
<Panel><p className="text-sm text-muted">Aucun plugin installé globalement.</p></Panel>
) : (
<ul className="flex flex-col gap-2">
{vm.installed.map((plugin) => {
const enabled = vm.settings?.plugins.some(
(entry) => entry.pluginId === plugin.id && entry.enabled,
) ?? false;
const skills = plugin.contributes.skills ?? [];
const servers = plugin.contributes.mcpServers ?? [];
return (
<li key={plugin.id}>
<Panel className="flex flex-col gap-3">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-semibold text-content">{plugin.displayName}</span>
<span className="text-xs text-faint">v{plugin.version}</span>
<span className="rounded bg-raised px-1.5 py-0.5 text-[0.65rem] text-muted">
Installé globalement
</span>
</div>
<p className="mt-1 text-xs text-muted">
{countLabel(skills.length, "skill", "skills")} · {countLabel(servers.length, "serveur MCP", "serveurs MCP")}
</p>
</div>
<label className="flex shrink-0 items-center gap-2 text-xs text-content">
<input
type="checkbox"
checked={enabled}
disabled={vm.busy}
onChange={(event) => void vm.setEnabled(plugin.id, event.target.checked)}
/>
Activé pour ce projet
</label>
</div>
<details>
<summary className="cursor-pointer text-xs font-medium text-primary">
Voir les capabilities
</summary>
<div className="mt-3 grid gap-3 sm:grid-cols-2">
<div>
<h3 className="text-xs font-semibold text-content">Skills fournis</h3>
{skills.length === 0 ? (
<p className="mt-1 text-xs text-muted">Aucun skill fourni.</p>
) : (
<ul className="mt-1 flex flex-col gap-1">
{skills.map((skill) => (
<li key={skill.id} className="text-xs text-muted">
<span className="font-medium text-content">{skill.name}</span>
{skill.description ? ` — ${skill.description}` : ""}
</li>
))}
</ul>
)}
</div>
<div>
<h3 className="text-xs font-semibold text-content">Tools MCP fournis</h3>
{servers.length === 0 ? (
<p className="mt-1 text-xs text-muted">Aucun serveur MCP fourni.</p>
) : (
<ul className="mt-1 flex flex-col gap-1">
{servers.map((server) => (
<li key={server.id} className="text-xs text-muted">
<span className="font-medium text-content">{server.displayName}</span>
<span className="text-faint"> · {server.id}</span>
</li>
))}
</ul>
)}
</div>
</div>
</details>
</Panel>
</li>
);
})}
{missing.map((activation) => (
<li key={activation.pluginId}>
<Panel className="flex items-center justify-between gap-4 border-warning/40">
<div>
<span className="text-sm font-semibold text-content">{activation.pluginId}</span>
<p className="text-xs text-warning">Plugin manquant · configuration conservée</p>
</div>
<label className="flex items-center gap-2 text-xs text-muted">
<input
type="checkbox"
checked={activation.enabled}
disabled={vm.busy || !activation.enabled}
onChange={(event) => void vm.setEnabled(activation.pluginId, event.target.checked)}
/>
Activé pour ce projet
</label>
</Panel>
</li>
))}
</ul>
)}
</section>
);
}