From e69afd0340a028707c57597535fd02c663681330 Mon Sep 17 00:00:00 2001 From: xmanrui <841206367@qq.com> Date: Sat, 7 Mar 2026 02:54:40 +0800 Subject: [PATCH] feat(platforms): auto-discover channels for agent cards --- app/api/config/route.ts | 85 +++++++++++++++---------------- app/components/agent-card.tsx | 96 +++++++++++++++++++---------------- lib/i18n.tsx | 2 + 3 files changed, 95 insertions(+), 88 deletions(-) diff --git a/app/api/config/route.ts b/app/api/config/route.ts index 182f71b..7f329d2 100644 --- a/app/api/config/route.ts +++ b/app/api/config/route.ts @@ -207,7 +207,7 @@ function getFeishuUserOpenIds(agentIds: string[], sessionsMap: Map) function getChannelDirectPeerIds( agentIds: string[], sessionsMap: Map, - channel: "telegram" | "whatsapp" + channel: string ): Record { const map: Record = {}; 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> = {}; + 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(); + 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 }) }); } } diff --git a/app/components/agent-card.tsx b/app/components/agent-card.tsx index f52441b..63733e6 100644 --- a/app/components/agent-card.tsx +++ b/app/components/agent-card.tsx @@ -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 = { + 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 (
@@ -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}`} > - {`${label} { - if (e.currentTarget.dataset.fallbackApplied === "1") return; - e.currentTarget.dataset.fallbackApplied = "1"; - e.currentTarget.src = logoFallbackSrc; - }} - /> + {meta ? ( + {`${label} { + if (e.currentTarget.dataset.fallbackApplied === "1") return; + e.currentTarget.dataset.fallbackApplied = "1"; + e.currentTarget.src = meta.logoFallbackSrc; + }} + /> + ) : ( + + {pName.slice(0, 2).toUpperCase()} + + )} {label} {pName === "feishu" && platform.accountId && ( ({platform.accountId}) diff --git a/lib/i18n.tsx b/lib/i18n.tsx index 68c5fe8..4a69cc9 100644 --- a/lib/i18n.tsx +++ b/lib/i18n.tsx @@ -117,6 +117,7 @@ const translations: Record> = { "platform.discord": "🎮 Discord", "platform.telegram": "✈️ Telegram", "platform.whatsapp": "💬 WhatsApp", + "platform.qqbot": "🐧 QQBot", // time range "range.daily": "按天", @@ -373,6 +374,7 @@ const translations: Record> = { "platform.discord": "🎮 Discord", "platform.telegram": "✈️ Telegram", "platform.whatsapp": "💬 WhatsApp", + "platform.qqbot": "🐧 QQBot", // time range "range.daily": "Daily",