diff --git a/frontend/web/src/admin/modules/Plugins.tsx b/frontend/web/src/admin/modules/Plugins.tsx index e3d1d3b..fb05344 100644 --- a/frontend/web/src/admin/modules/Plugins.tsx +++ b/frontend/web/src/admin/modules/Plugins.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useAsyncData } from "../../lib/useAsyncData"; import { adminListPlugins, adminSetPluginPolicy, adminPluginUninstall, getStoredRole } from "../../lib/api"; import type { AdminPluginRecord } from "../../lib/types"; @@ -8,7 +8,26 @@ import { AdminTable, Notice, PageHeader, type AdminColumn } from "../components" import { Button, Surface, Switch, Tag } from "../../components/primitives"; import { InstalledPluginsPanel } from "./plugins/InstalledPlugins"; -/** Columns for the registered-capability table (GET /admin/plugins). */ +type CapKind = "tool" | "skill" | "channel"; + +const KIND_LABELS: Record = { + tool: "工具 Tool", + skill: "技能 Skill", + channel: "渠道 Channel", +}; + +// Channel adapters are not gated by the tool policy (group/stranger markers are a +// tool concept; a channel is configured via its own 渠道管理 page), so the +// channel block shows identity + a pointer rather than the policy switches. +function channelColumns(): AdminColumn[] { + return [ + { key: "name", title: "名称", render: (p) => {p.name} }, + { key: "desc", title: "描述", render: (p) => {p.description || "—"} }, + { key: "admin", title: "配置", render: () => 渠道配置见「渠道管理」页 }, + ]; +} + +/** Columns for a tool / skill row (has environment markers, owner can edit). */ function registeredColumns( isOwner: boolean, busy: string | null, @@ -17,7 +36,7 @@ function registeredColumns( ): AdminColumn[] { return [ { key: "name", title: "名称", render: (p) => {p.name} }, - { key: "kind", title: "类型", render: (p) => {p.kind} }, + { key: "kind", title: "类型", render: (p) => {(KIND_LABELS as Record)[p.kind] ?? p.kind} }, { key: "desc", title: "描述", render: (p) => {p.description || "—"} }, { key: "policy", @@ -97,13 +116,23 @@ export function PluginsModule(): React.ReactNode { .finally(() => setBusy(null)); }; - const columns = registeredColumns(isOwner, busy, setPolicy, uninstall); + const byKind = useMemo(() => { + const groups: Record = { tool: [], skill: [], channel: [] }; + for (const cap of data ?? []) { + const k = cap.kind as CapKind; + if (groups[k]) groups[k].push(cap); + } + return groups; + }, [data]); + + const toolSkillColumns = registeredColumns(isOwner, busy, setPolicy, uninstall); + const channelCols = channelColumns(); return (
{!isOwner ? 当前角色为 {getStoredRole()},工具策略/插件安装仅 owner 可写(读取/查看可见)。 : null} @@ -112,22 +141,49 @@ export function PluginsModule(): React.ReactNode { - +

已注册能力

- GET /api/v1/admin/plugins + GET /api/v1/admin/plugins · 按工具 / 技能 / 渠道分组 +
+ + {(["tool", "skill"] as CapKind[]).map((kind) => { + const rows = byKind[kind]; + return ( +
+
+ {KIND_LABELS[kind]} + {rows.length} 个 +
+ `${kind}:${p.name}`} + /> +
+ ); + })} + +
+
+ {KIND_LABELS.channel} + {byKind.channel.length} 个 +
+ `channel:${p.name}`} + />
- p.name} - />
- 工具 schema / 技能为后端 M10+ 提供;此页展示已注册能力及其环境可见性标记(群聊 / 陌生人默认关闭)。 + 工具 schema / 技能为后端 M10+ 提供;此页按类型分组展示已注册能力。群聊/陌生人环境可见性标记(默认关闭)仅对工具/技能生效。
); }