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:
Junyan Qin
2026-06-26 19:52:01 +08:00
committed by huanghuoguoguo
parent 68d43834bd
commit 95f3409d55
4 changed files with 85 additions and 35 deletions
@@ -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]);