feat(channels): 其它适配器专属通用壳(design/17 §2)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 新增 GenericAdapterPanel.tsx: 非KOOK平台专属壳(平台标签+能力徽标+平台专属功能区诚实占位+
  主动推送能力提示),读 record.accounts[].capabilities,未实装区标「待实现」不伪造数据
- 新增 channelCapabilities.ts: 能力标签元数据+辅助(friends/user_chats/channels/guilds/
  message_test 与 Markdown/Image/Voice/Reaction/TypingHint/Proactive 中文映射;未知回退原值)
- AdapterDetailPage.tsx: platform==='kook' 渲染 KookPanel, 否则渲染 GenericAdapterPanel
- 前向兼容: 后端后续为其它平台补 capabilities 声明即点亮; 零新依赖; 移动端flex-wrap/overflow
- 验收: npm run build(tsc+vite) 99模块全绿; 文件均<500行
This commit is contained in:
Aska committed 2026-09-07 18:28:21 +08:00
1 parent 5ec4c9814d
commit c43b2f1d5d
3 files changed
+167 -2

No files matched your search

@@ -0,0 +1,110 @@
import type { AdminChannelRecord } from "../../../lib/types";
import { Notice } from "../../components";
import { Surface, Tag } from "../../../components/primitives";
import { PLATFORM_LABEL } from "../channelFields";
import { adminOpsOf, capabilityMeta, hasCapability } from "./channelCapabilities";
/** Generic per-adapter shell for platforms without a specialised panel (design/17
* §2). KOOK gets KookPanel; every other platform renders this honest shell. It
* shows the platform label + account, the capabilities the adapter declares
* (from `record.accounts[].capabilities`), a placeholder for platform-specific
* feature areas (still to be wired), and a proactive-push note. It never
* fabricates data, buttons, or friend/channel lists — non-KOOK platforms have no
* backend /view endpoint yet, so the page only displays capability + placeholders
* + the generic connect/disconnect/log/identity actions rendered above it. */
export function GenericAdapterPanel({
platform,
accountId,
record,
}: {
platform: string;
accountId?: string;
record?: AdminChannelRecord;
}): React.ReactNode {
const label = PLATFORM_LABEL[platform] ?? platform;
const account = record?.accounts?.find((a) =>
accountId === undefined ? (!a.id || a.id === "default") : a.id === accountId,
);
const caps = account?.capabilities ?? [];
const accountName =
account?.botNickname ||
(account?.botUsername ? `@${account.botUsername}` : accountId ?? "default");
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-2">
<Tag tone="accent">{label} 平台能力</Tag>
<Tag tone="neutral">{accountName}</Tag>
<span className="ml-auto text-xs text-[var(--mu-text-lo)]">通用壳 · 平台专属端点未接入</span>
</div>
<CapabilityBadges caps={caps} />
<FeaturePlaceholder caps={caps} />
<ProactiveNote caps={caps} />
</div>
);
}
/** Capability badges: what this adapter declares it supports. Empty → honest note. */
function CapabilityBadges({ caps }: { caps: readonly string[] }): React.ReactNode {
return (
<Surface gap="sm" className="space-y-3">
<h3 className="text-sm font-semibold">平台能力</h3>
{caps.length === 0 ? (
<Notice>该适配器尚未声明能力列表;平台专属端点未接入,当前仅提供通用操作(连接 / 断开 / 消息日志 / 身份测试)。</Notice>
) : (
<div className="flex flex-wrap gap-2">
{caps.map((c) => {
const m = capabilityMeta(c);
return (
<Tag key={c} tone={m.tone}>
{m.label}
</Tag>
);
})}
</div>
)}
</Surface>
);
}
/** Placeholder for future platform-specific feature areas (friends/channels/members/
* test-send). Only the admin-operations the adapter actually declares surface here,
* each marked as not yet wired. */
function FeaturePlaceholder({ caps }: { caps: readonly string[] }): React.ReactNode {
const adminOps = adminOpsOf(caps);
return (
<Surface gap="sm" className="space-y-3">
<h3 className="text-sm font-semibold">平台专属功能区(占位)</h3>
{adminOps.length === 0 ? (
<Notice>该平台暂无已声明的专属功能模块。频道 / 成员 / 发送测试等平台能力将随后续接入后在此显示。</Notice>
) : (
<>
<div className="flex flex-wrap gap-2">
{adminOps.map((op) => (
<Tag key={op} tone={capabilityMeta(op).tone}>
{capabilityMeta(op).label} · 后续接入
</Tag>
))}
</div>
<Notice tone="warn">以上模块为占位展示,对应后端专属端点尚未接入,当前不提供真实数据 / 操作。</Notice>
</>
)}
</Surface>
);
}
/** Proactive-push note: declared → placeholder; otherwise an honest "not supported". */
function ProactiveNote({ caps }: { caps: readonly string[] }): React.ReactNode {
const supported = hasCapability(caps, "Proactive");
return (
<Surface gap="sm" className="space-y-3">
<h3 className="text-sm font-semibold">主动推送</h3>
{supported ? (
<Notice tone="warn">主动推送(占位,后端待接):该适配器声明了主动推送能力,定时 / 主动消息功能待后端接入。</Notice>
) : (
<Notice>当前适配器未声明主动推送能力(Proactive),不支持主动 / 定时消息;仅在被对话触发时响应。</Notice>
)}
</Surface>
);
}