mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-07-27 22:25:52 +00:00
feat(platforms): auto-discover channels for agent cards
This commit is contained in:
+41
-44
@@ -207,7 +207,7 @@ function getFeishuUserOpenIds(agentIds: string[], sessionsMap: Map<string, any>)
|
||||
function getChannelDirectPeerIds(
|
||||
agentIds: string[],
|
||||
sessionsMap: Map<string, any>,
|
||||
channel: "telegram" | "whatsapp"
|
||||
channel: string
|
||||
): Record<string, string> {
|
||||
const map: Record<string, string> = {};
|
||||
const pattern = new RegExp(`^agent:[^:]+:${channel}:direct:(.+)$`);
|
||||
@@ -328,16 +328,21 @@ export async function GET() {
|
||||
|
||||
// 从预读的 sessions 数据获取飞书用户 open_id
|
||||
const feishuUserOpenIds = getFeishuUserOpenIds(agentIds, sessionsMap);
|
||||
const telegramDirectPeerIds = getChannelDirectPeerIds(agentIds, sessionsMap, "telegram");
|
||||
const whatsappDirectPeerIds = getChannelDirectPeerIds(agentIds, sessionsMap, "whatsapp");
|
||||
const discordDmAllowFrom = channels.discord?.dm?.allowFrom || [];
|
||||
|
||||
// Build a set of agent IDs that have explicit feishu bindings
|
||||
const boundFeishuAgentIds = new Set(
|
||||
const enabledChannelNames: string[] = Object.entries(channels)
|
||||
.filter(([, cfg]) => cfg && typeof cfg === "object" && (cfg as any).enabled !== false)
|
||||
.map(([channelName]) => channelName);
|
||||
const boundChannelNames: string[] = Array.from(new Set(
|
||||
bindings
|
||||
.filter((b: any) => b.match?.channel === "feishu")
|
||||
.map((b: any) => b.agentId)
|
||||
);
|
||||
.map((b: any) => b.match?.channel)
|
||||
.filter((v: any): v is string => typeof v === "string" && v.length > 0)
|
||||
));
|
||||
const discoverChannelNames: string[] = Array.from(new Set([...enabledChannelNames, ...boundChannelNames]));
|
||||
const directPeerIdsByChannel: Record<string, Record<string, string>> = {};
|
||||
for (const channelName of discoverChannelNames) {
|
||||
if (channelName === "feishu") continue;
|
||||
directPeerIdsByChannel[channelName] = getChannelDirectPeerIds(agentIds, sessionsMap, channelName);
|
||||
}
|
||||
const discordDmAllowFrom = channels.discord?.dm?.allowFrom || [];
|
||||
|
||||
// 构建 agent 详情
|
||||
const agents = await Promise.all(agentList.map(async (agent: any) => {
|
||||
@@ -349,6 +354,11 @@ export async function GET() {
|
||||
|
||||
// 查找绑定的平台
|
||||
const platforms: { name: string; accountId?: string; appId?: string; botOpenId?: string; botUserId?: string }[] = [];
|
||||
const addPlatform = (platform: { name: string; accountId?: string; appId?: string; botOpenId?: string; botUserId?: string }) => {
|
||||
if (!platform?.name) return;
|
||||
const exists = platforms.some((p) => p.name === platform.name && (p.accountId || "") === (platform.accountId || ""));
|
||||
if (!exists) platforms.push(platform);
|
||||
};
|
||||
|
||||
// 检查飞书绑定 (explicit binding)
|
||||
const feishuBinding = bindings.find(
|
||||
@@ -359,7 +369,7 @@ export async function GET() {
|
||||
const acc = feishuAccounts[accountId];
|
||||
const appId = acc?.appId;
|
||||
const userOpenId = feishuUserOpenIds[id] || null;
|
||||
platforms.push({ name: "feishu", accountId, appId, ...(userOpenId && { botOpenId: userOpenId }) });
|
||||
addPlatform({ name: "feishu", accountId, appId, ...(userOpenId && { botOpenId: userOpenId }) });
|
||||
}
|
||||
|
||||
// If no explicit binding, check if there's a feishu account matching this agent id
|
||||
@@ -367,7 +377,7 @@ export async function GET() {
|
||||
const acc = feishuAccounts[id];
|
||||
const appId = acc?.appId;
|
||||
const userOpenId = feishuUserOpenIds[id] || null;
|
||||
platforms.push({ name: "feishu", accountId: id, appId, ...(userOpenId && { botOpenId: userOpenId }) });
|
||||
addPlatform({ name: "feishu", accountId: id, appId, ...(userOpenId && { botOpenId: userOpenId }) });
|
||||
}
|
||||
|
||||
// main agent 特殊处理:默认绑定所有未显式绑定的 channel
|
||||
@@ -378,43 +388,30 @@ export async function GET() {
|
||||
const acc = feishuAccounts["main"];
|
||||
const appId = acc?.appId || channels.feishu?.appId;
|
||||
const userOpenId = feishuUserOpenIds["main"] || null;
|
||||
platforms.push({ name: "feishu", accountId: "main", appId, ...(userOpenId && { botOpenId: userOpenId }) });
|
||||
addPlatform({ name: "feishu", accountId: "main", appId, ...(userOpenId && { botOpenId: userOpenId }) });
|
||||
}
|
||||
if (channels.discord && channels.discord.enabled !== false) {
|
||||
const botUserId = discordDmAllowFrom[0] || null;
|
||||
platforms.push({ name: "discord", ...(botUserId && { botUserId }) });
|
||||
}
|
||||
if (channels.telegram && channels.telegram.enabled !== false) {
|
||||
const botUserId = telegramDirectPeerIds[id] || null;
|
||||
platforms.push({ name: "telegram", ...(botUserId && { botUserId }) });
|
||||
}
|
||||
if (channels.whatsapp && channels.whatsapp.enabled !== false) {
|
||||
const botUserId = whatsappDirectPeerIds[id] || null;
|
||||
platforms.push({ name: "whatsapp", ...(botUserId && { botUserId }) });
|
||||
|
||||
// main agent 默认展示所有已启用 channel(feishu 已单独处理)
|
||||
for (const channelName of enabledChannelNames) {
|
||||
if (channelName === "feishu") continue;
|
||||
const botUserId = directPeerIdsByChannel[channelName]?.[id]
|
||||
|| (channelName === "discord" ? (discordDmAllowFrom[0] || null) : null);
|
||||
addPlatform({ name: channelName, ...(botUserId && { botUserId }) });
|
||||
}
|
||||
}
|
||||
|
||||
// Also detect discord for non-main agents if they have discord bindings
|
||||
// 非 main agent:按显式 bindings 展示 channel(自动支持新增 channel)
|
||||
if (id !== "main") {
|
||||
const discordBinding = bindings.find(
|
||||
(b: any) => b.agentId === id && b.match?.channel === "discord"
|
||||
);
|
||||
if (discordBinding) {
|
||||
platforms.push({ name: "discord" });
|
||||
}
|
||||
const telegramBinding = bindings.find(
|
||||
(b: any) => b.agentId === id && b.match?.channel === "telegram"
|
||||
);
|
||||
if (telegramBinding) {
|
||||
const botUserId = telegramDirectPeerIds[id] || null;
|
||||
platforms.push({ name: "telegram", ...(botUserId && { botUserId }) });
|
||||
}
|
||||
const whatsappBinding = bindings.find(
|
||||
(b: any) => b.agentId === id && b.match?.channel === "whatsapp"
|
||||
);
|
||||
if (whatsappBinding) {
|
||||
const botUserId = whatsappDirectPeerIds[id] || null;
|
||||
platforms.push({ name: "whatsapp", ...(botUserId && { botUserId }) });
|
||||
const seenBindingChannels = new Set<string>();
|
||||
for (const binding of bindings) {
|
||||
if (binding?.agentId !== id) continue;
|
||||
const channelName = binding?.match?.channel;
|
||||
if (!channelName || channelName === "feishu") continue;
|
||||
if (seenBindingChannels.has(channelName)) continue;
|
||||
seenBindingChannels.add(channelName);
|
||||
const botUserId = directPeerIdsByChannel[channelName]?.[id] || null;
|
||||
const accountId = typeof binding?.match?.accountId === "string" ? binding.match.accountId : undefined;
|
||||
addPlatform({ name: channelName, ...(accountId && { accountId }), ...(botUserId && { botUserId }) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,50 +154,52 @@ function PlatformBadge({
|
||||
}) {
|
||||
const pName = platform.name;
|
||||
const badgeWidthClass = "w-[8.25rem]";
|
||||
const remoteLogoSrc = pName === "feishu"
|
||||
? "https://cdn.simpleicons.org/lark/2E5BFF"
|
||||
: pName === "telegram"
|
||||
? "https://cdn.simpleicons.org/telegram/26A5E4"
|
||||
: pName === "whatsapp"
|
||||
? "https://cdn.simpleicons.org/whatsapp/25D366"
|
||||
: "https://cdn.simpleicons.org/discord/5865F2";
|
||||
const logoFallbackSrc = pName === "feishu"
|
||||
? "/assets/platform-logos/feishu-favicon.png?v=1"
|
||||
: pName === "telegram"
|
||||
? "/assets/platform-logos/telegram.svg"
|
||||
: pName === "whatsapp"
|
||||
? "/assets/platform-logos/whatsapp.svg"
|
||||
: "/assets/platform-logos/discord.svg";
|
||||
const logoSizeClass = pName === "feishu" ? "w-[1.09375rem] h-[1.09375rem]" : "w-3.5 h-3.5";
|
||||
const knownMeta: Record<string, { remoteLogoSrc: string; logoFallbackSrc: string; badgeStyle: string; logoSizeClass?: string }> = {
|
||||
feishu: {
|
||||
remoteLogoSrc: "https://cdn.simpleicons.org/lark/2E5BFF",
|
||||
logoFallbackSrc: "/assets/platform-logos/feishu-favicon.png?v=1",
|
||||
badgeStyle: "bg-blue-500/20 text-blue-300 border border-blue-500/30 hover:bg-blue-500/40 hover:border-blue-400",
|
||||
logoSizeClass: "w-[1.09375rem] h-[1.09375rem]",
|
||||
},
|
||||
discord: {
|
||||
remoteLogoSrc: "https://cdn.simpleicons.org/discord/5865F2",
|
||||
logoFallbackSrc: "/assets/platform-logos/discord.svg",
|
||||
badgeStyle: "bg-purple-500/20 text-purple-300 border border-purple-500/30 hover:bg-purple-500/40 hover:border-purple-400",
|
||||
},
|
||||
telegram: {
|
||||
remoteLogoSrc: "https://cdn.simpleicons.org/telegram/26A5E4",
|
||||
logoFallbackSrc: "/assets/platform-logos/telegram.svg",
|
||||
badgeStyle: "bg-sky-500/20 text-sky-300 border border-sky-500/30 hover:bg-sky-500/40 hover:border-sky-400",
|
||||
},
|
||||
whatsapp: {
|
||||
remoteLogoSrc: "https://cdn.simpleicons.org/whatsapp/25D366",
|
||||
logoFallbackSrc: "/assets/platform-logos/whatsapp.svg",
|
||||
badgeStyle: "bg-green-500/20 text-green-300 border border-green-500/30 hover:bg-green-500/40 hover:border-green-400",
|
||||
},
|
||||
qqbot: {
|
||||
remoteLogoSrc: "https://cdn.simpleicons.org/tencentqq/12B7F5",
|
||||
logoFallbackSrc: "/assets/platform-logos/telegram.svg",
|
||||
badgeStyle: "bg-blue-500/20 text-blue-300 border border-blue-500/30 hover:bg-blue-500/40 hover:border-blue-400",
|
||||
},
|
||||
};
|
||||
const meta = knownMeta[pName];
|
||||
const logoSizeClass = meta?.logoSizeClass || "w-3.5 h-3.5";
|
||||
|
||||
let sessionKey: string;
|
||||
if (pName === "feishu" && platform.botOpenId) {
|
||||
sessionKey = `agent:${agentId}:feishu:direct:${platform.botOpenId}`;
|
||||
} else if (pName === "discord" && platform.botUserId) {
|
||||
sessionKey = `agent:${agentId}:discord:direct:${platform.botUserId}`;
|
||||
} else if (pName === "telegram" && platform.botUserId) {
|
||||
sessionKey = `agent:${agentId}:telegram:direct:${platform.botUserId}`;
|
||||
} else if (pName === "whatsapp" && platform.botUserId) {
|
||||
sessionKey = `agent:${agentId}:whatsapp:direct:${platform.botUserId}`;
|
||||
} else if (platform.botUserId) {
|
||||
sessionKey = `agent:${agentId}:${pName}:direct:${platform.botUserId}`;
|
||||
} else {
|
||||
sessionKey = `agent:${agentId}:main`;
|
||||
}
|
||||
let sessionUrl = buildGatewayUrl(gatewayPort, "/chat", { session: sessionKey }, gatewayHost);
|
||||
if (gatewayToken) sessionUrl = buildGatewayUrl(gatewayPort, "/chat", { session: sessionKey, token: gatewayToken }, gatewayHost);
|
||||
|
||||
const badgeStyle = pName === "feishu"
|
||||
? "bg-blue-500/20 text-blue-300 border border-blue-500/30 hover:bg-blue-500/40 hover:border-blue-400"
|
||||
: pName === "telegram"
|
||||
? "bg-sky-500/20 text-sky-300 border border-sky-500/30 hover:bg-sky-500/40 hover:border-sky-400"
|
||||
: pName === "whatsapp"
|
||||
? "bg-green-500/20 text-green-300 border border-green-500/30 hover:bg-green-500/40 hover:border-green-400"
|
||||
: "bg-purple-500/20 text-purple-300 border border-purple-500/30 hover:bg-purple-500/40 hover:border-purple-400";
|
||||
|
||||
const labelRaw = pName === "feishu" ? t("platform.feishu")
|
||||
: pName === "telegram" ? t("platform.telegram")
|
||||
: pName === "whatsapp" ? t("platform.whatsapp")
|
||||
: t("platform.discord");
|
||||
const label = labelRaw.replace(/^[^\p{L}\p{N}]+/u, "").trim();
|
||||
const badgeStyle = meta?.badgeStyle || "bg-gray-500/20 text-gray-300 border border-gray-500/30 hover:bg-gray-500/40 hover:border-gray-400";
|
||||
const translated = t(`platform.${pName}`);
|
||||
const labelRaw = translated !== `platform.${pName}` ? translated : pName;
|
||||
const label = labelRaw.replace(/^[^\p{L}\p{N}]+/u, "").trim() || pName;
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center gap-1.5 max-w-full">
|
||||
@@ -209,16 +211,22 @@ function PlatformBadge({
|
||||
title={t("agent.openChat")}
|
||||
className={`inline-flex items-center gap-0.5 px-2 py-0.5 rounded-full text-xs font-medium cursor-pointer transition-all hover:scale-105 hover:shadow-md min-w-0 ${badgeWidthClass} ${badgeStyle}`}
|
||||
>
|
||||
<img
|
||||
src={remoteLogoSrc}
|
||||
alt={`${label} logo`}
|
||||
className={`${logoSizeClass} shrink-0`}
|
||||
onError={(e) => {
|
||||
if (e.currentTarget.dataset.fallbackApplied === "1") return;
|
||||
e.currentTarget.dataset.fallbackApplied = "1";
|
||||
e.currentTarget.src = logoFallbackSrc;
|
||||
}}
|
||||
/>
|
||||
{meta ? (
|
||||
<img
|
||||
src={meta.remoteLogoSrc}
|
||||
alt={`${label} logo`}
|
||||
className={`${logoSizeClass} shrink-0`}
|
||||
onError={(e) => {
|
||||
if (e.currentTarget.dataset.fallbackApplied === "1") return;
|
||||
e.currentTarget.dataset.fallbackApplied = "1";
|
||||
e.currentTarget.src = meta.logoFallbackSrc;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="inline-flex w-3.5 h-3.5 items-center justify-center rounded bg-white/10 text-[9px] font-semibold shrink-0">
|
||||
{pName.slice(0, 2).toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
<span className="shrink-0">{label}</span>
|
||||
{pName === "feishu" && platform.accountId && (
|
||||
<span className="opacity-60 truncate max-w-[4.5rem]">({platform.accountId})</span>
|
||||
|
||||
@@ -117,6 +117,7 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||
"platform.discord": "🎮 Discord",
|
||||
"platform.telegram": "✈️ Telegram",
|
||||
"platform.whatsapp": "💬 WhatsApp",
|
||||
"platform.qqbot": "🐧 QQBot",
|
||||
|
||||
// time range
|
||||
"range.daily": "按天",
|
||||
@@ -373,6 +374,7 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||
"platform.discord": "🎮 Discord",
|
||||
"platform.telegram": "✈️ Telegram",
|
||||
"platform.whatsapp": "💬 WhatsApp",
|
||||
"platform.qqbot": "🐧 QQBot",
|
||||
|
||||
// time range
|
||||
"range.daily": "Daily",
|
||||
|
||||
Reference in New Issue
Block a user