mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-26 15:34:26 +00:00
feat(bots): refine event binding editor UI and i18n
- Move conditions toggle between event select and arrow; drop "IF" label - Remove "all events" (*) option from event select - Add i18n labels for concrete event names (zh/en/ja) via bots.eventNames - Narrow fallback event set to message.received for adapters without declared supported_events (legacy adapters only emit message.received) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import {
|
||||
ArrowRight,
|
||||
@@ -114,15 +115,9 @@ const ELEMENTS = [
|
||||
'Quote',
|
||||
];
|
||||
|
||||
const DEFAULT_EVENTS = [
|
||||
'message.received',
|
||||
'feedback.received',
|
||||
'group.member_joined',
|
||||
'group.member_left',
|
||||
'friend.request_received',
|
||||
'bot.invited_to_group',
|
||||
'platform.specific',
|
||||
];
|
||||
// Adapters that don't declare `supported_events` (e.g. legacy adapters)
|
||||
// only emit message.received, so that's the sole fallback option.
|
||||
const DEFAULT_EVENTS = ['message.received'];
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -156,6 +151,20 @@ function eventNamespaces(events: string[]) {
|
||||
return Array.from(ns).sort();
|
||||
}
|
||||
|
||||
// Localized label for an event pattern. Concrete events look up
|
||||
// `bots.eventNames.<event_with_underscores>`, falling back to the raw
|
||||
// string when no translation exists (e.g. custom/unknown events).
|
||||
function eventLabel(event: string, t: TFunction) {
|
||||
if (event === '*') return t('bots.eventWildcard');
|
||||
if (event.endsWith('.*'))
|
||||
return t('bots.eventNamespaceWildcard', {
|
||||
namespace: event.replace('.*', ''),
|
||||
});
|
||||
const key = `bots.eventNames.${event.replace(/\./g, '_')}`;
|
||||
const label = t(key);
|
||||
return label === key ? event : label;
|
||||
}
|
||||
|
||||
function targetLabel(agent: Agent) {
|
||||
return `${agent.emoji ? `${agent.emoji} ` : ''}${agent.name}`;
|
||||
}
|
||||
@@ -509,10 +518,6 @@ function BindingCardContent({
|
||||
</button>
|
||||
)}
|
||||
|
||||
<span className="text-xs font-medium text-muted-foreground shrink-0">
|
||||
IF
|
||||
</span>
|
||||
|
||||
<Select
|
||||
value={binding.event_pattern}
|
||||
onValueChange={(eventPattern) => {
|
||||
@@ -535,32 +540,12 @@ function BindingCardContent({
|
||||
<SelectContent>
|
||||
{eventOptions.map((event) => (
|
||||
<SelectItem key={event} value={event}>
|
||||
{event === '*'
|
||||
? t('bots.eventWildcard')
|
||||
: event.endsWith('.*')
|
||||
? t('bots.eventNamespaceWildcard', {
|
||||
namespace: event.replace('.*', ''),
|
||||
})
|
||||
: event}
|
||||
{eventLabel(event, t)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
|
||||
<TargetCombobox
|
||||
binding={binding}
|
||||
agentOptions={agentOptions}
|
||||
onUpdate={(patch) => onUpdate(globalIndex, patch)}
|
||||
/>
|
||||
|
||||
{!pipelineAllowed && binding.target_type === 'pipeline' && (
|
||||
<span className="text-xs text-destructive shrink-0">
|
||||
{t('bots.unsupportedPipelineEvent')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* conditions toggle */}
|
||||
<Button
|
||||
type="button"
|
||||
@@ -582,6 +567,20 @@ function BindingCardContent({
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
|
||||
<TargetCombobox
|
||||
binding={binding}
|
||||
agentOptions={agentOptions}
|
||||
onUpdate={(patch) => onUpdate(globalIndex, patch)}
|
||||
/>
|
||||
|
||||
{!pipelineAllowed && binding.target_type === 'pipeline' && (
|
||||
<span className="text-xs text-destructive shrink-0">
|
||||
{t('bots.unsupportedPipelineEvent')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* disable/enable toggle */}
|
||||
<Button
|
||||
type="button"
|
||||
@@ -669,7 +668,7 @@ export default function EventBindingsEditor({
|
||||
const eventOptions = useMemo(() => {
|
||||
const concrete =
|
||||
supportedEvents.length > 0 ? supportedEvents : DEFAULT_EVENTS;
|
||||
return ['*', ...eventNamespaces(concrete), ...concrete].filter(
|
||||
return [...eventNamespaces(concrete), ...concrete].filter(
|
||||
(e, i, a) => a.indexOf(e) === i,
|
||||
);
|
||||
}, [supportedEvents]);
|
||||
|
||||
@@ -388,6 +388,23 @@ const enUS = {
|
||||
eventCustom: 'Custom event',
|
||||
eventWildcard: 'All events',
|
||||
eventNamespaceWildcard: '{{namespace}}.*',
|
||||
eventNames: {
|
||||
message_received: 'Message received',
|
||||
message_edited: 'Message edited',
|
||||
message_deleted: 'Message deleted',
|
||||
message_reaction: 'Message reaction',
|
||||
feedback_received: 'Feedback received',
|
||||
friend_request_received: 'Friend request received',
|
||||
friend_added: 'Friend added',
|
||||
group_member_joined: 'Member joined group',
|
||||
group_member_left: 'Member left group',
|
||||
group_member_banned: 'Member banned',
|
||||
bot_invited_to_group: 'Bot invited to group',
|
||||
bot_removed_from_group: 'Bot removed from group',
|
||||
bot_muted: 'Bot muted',
|
||||
bot_unmuted: 'Bot unmuted',
|
||||
platform_specific: 'Platform-specific event',
|
||||
},
|
||||
conditions: 'Conditions',
|
||||
conditionsDescription:
|
||||
'All conditions must match to trigger this binding. Leave empty to always trigger.',
|
||||
|
||||
@@ -387,6 +387,23 @@ const jaJP = {
|
||||
eventCustom: 'カスタムイベント',
|
||||
eventWildcard: 'すべてのイベント',
|
||||
eventNamespaceWildcard: '{{namespace}}.*',
|
||||
eventNames: {
|
||||
message_received: 'メッセージ受信',
|
||||
message_edited: 'メッセージ編集',
|
||||
message_deleted: 'メッセージ削除',
|
||||
message_reaction: 'メッセージリアクション',
|
||||
feedback_received: 'フィードバック受信',
|
||||
friend_request_received: '友達リクエスト受信',
|
||||
friend_added: '友達追加',
|
||||
group_member_joined: 'メンバー参加',
|
||||
group_member_left: 'メンバー退出',
|
||||
group_member_banned: 'メンバーBAN',
|
||||
bot_invited_to_group: 'ボットがグループに招待された',
|
||||
bot_removed_from_group: 'ボットがグループから削除された',
|
||||
bot_muted: 'ボットがミュートされた',
|
||||
bot_unmuted: 'ボットのミュート解除',
|
||||
platform_specific: 'プラットフォーム固有イベント',
|
||||
},
|
||||
routingRules: '条件付きルーティングルール',
|
||||
routingRulesDescription:
|
||||
'ルールは順番に評価され、最初に一致したルールのパイプラインにルーティングされます。一致しない場合はデフォルトパイプラインが使用されます。',
|
||||
|
||||
@@ -372,6 +372,23 @@ const zhHans = {
|
||||
eventCustom: '自定义事件',
|
||||
eventWildcard: '全部事件',
|
||||
eventNamespaceWildcard: '{{namespace}}.*',
|
||||
eventNames: {
|
||||
message_received: '收到消息',
|
||||
message_edited: '消息被编辑',
|
||||
message_deleted: '消息被删除',
|
||||
message_reaction: '消息表态',
|
||||
feedback_received: '收到反馈',
|
||||
friend_request_received: '收到好友请求',
|
||||
friend_added: '好友添加成功',
|
||||
group_member_joined: '成员加入群组',
|
||||
group_member_left: '成员离开群组',
|
||||
group_member_banned: '成员被封禁',
|
||||
bot_invited_to_group: '机器人被邀入群',
|
||||
bot_removed_from_group: '机器人被移出群',
|
||||
bot_muted: '机器人被禁言',
|
||||
bot_unmuted: '机器人被解除禁言',
|
||||
platform_specific: '平台特定事件',
|
||||
},
|
||||
conditions: '触发条件',
|
||||
conditionsDescription: '满足所有条件时才触发此绑定,不添加则无条件触发。',
|
||||
conditionsEmpty: '无条件,始终触发。',
|
||||
|
||||
Reference in New Issue
Block a user