mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 01:46:07 +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:
committed by
huanghuoguoguo
parent
a275740fbd
commit
8aedb57340
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useMemo, useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import type { TFunction } from 'i18next';
|
||||||
import { UseFormReturn } from 'react-hook-form';
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@@ -114,15 +115,9 @@ const ELEMENTS = [
|
|||||||
'Quote',
|
'Quote',
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_EVENTS = [
|
// Adapters that don't declare `supported_events` (e.g. legacy adapters)
|
||||||
'message.received',
|
// only emit message.received, so that's the sole fallback option.
|
||||||
'feedback.received',
|
const DEFAULT_EVENTS = ['message.received'];
|
||||||
'group.member_joined',
|
|
||||||
'group.member_left',
|
|
||||||
'friend.request_received',
|
|
||||||
'bot.invited_to_group',
|
|
||||||
'platform.specific',
|
|
||||||
];
|
|
||||||
|
|
||||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -156,6 +151,20 @@ function eventNamespaces(events: string[]) {
|
|||||||
return Array.from(ns).sort();
|
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) {
|
function targetLabel(agent: Agent) {
|
||||||
return `${agent.emoji ? `${agent.emoji} ` : ''}${agent.name}`;
|
return `${agent.emoji ? `${agent.emoji} ` : ''}${agent.name}`;
|
||||||
}
|
}
|
||||||
@@ -509,10 +518,6 @@ function BindingCardContent({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<span className="text-xs font-medium text-muted-foreground shrink-0">
|
|
||||||
IF
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={binding.event_pattern}
|
value={binding.event_pattern}
|
||||||
onValueChange={(eventPattern) => {
|
onValueChange={(eventPattern) => {
|
||||||
@@ -535,32 +540,12 @@ function BindingCardContent({
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{eventOptions.map((event) => (
|
{eventOptions.map((event) => (
|
||||||
<SelectItem key={event} value={event}>
|
<SelectItem key={event} value={event}>
|
||||||
{event === '*'
|
{eventLabel(event, t)}
|
||||||
? t('bots.eventWildcard')
|
|
||||||
: event.endsWith('.*')
|
|
||||||
? t('bots.eventNamespaceWildcard', {
|
|
||||||
namespace: event.replace('.*', ''),
|
|
||||||
})
|
|
||||||
: event}
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</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 */}
|
{/* conditions toggle */}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -582,6 +567,20 @@ function BindingCardContent({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</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 */}
|
{/* disable/enable toggle */}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -669,7 +668,7 @@ export default function EventBindingsEditor({
|
|||||||
const eventOptions = useMemo(() => {
|
const eventOptions = useMemo(() => {
|
||||||
const concrete =
|
const concrete =
|
||||||
supportedEvents.length > 0 ? supportedEvents : DEFAULT_EVENTS;
|
supportedEvents.length > 0 ? supportedEvents : DEFAULT_EVENTS;
|
||||||
return ['*', ...eventNamespaces(concrete), ...concrete].filter(
|
return [...eventNamespaces(concrete), ...concrete].filter(
|
||||||
(e, i, a) => a.indexOf(e) === i,
|
(e, i, a) => a.indexOf(e) === i,
|
||||||
);
|
);
|
||||||
}, [supportedEvents]);
|
}, [supportedEvents]);
|
||||||
|
|||||||
@@ -388,6 +388,23 @@ const enUS = {
|
|||||||
eventCustom: 'Custom event',
|
eventCustom: 'Custom event',
|
||||||
eventWildcard: 'All events',
|
eventWildcard: 'All events',
|
||||||
eventNamespaceWildcard: '{{namespace}}.*',
|
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',
|
conditions: 'Conditions',
|
||||||
conditionsDescription:
|
conditionsDescription:
|
||||||
'All conditions must match to trigger this binding. Leave empty to always trigger.',
|
'All conditions must match to trigger this binding. Leave empty to always trigger.',
|
||||||
|
|||||||
@@ -387,6 +387,23 @@ const jaJP = {
|
|||||||
eventCustom: 'カスタムイベント',
|
eventCustom: 'カスタムイベント',
|
||||||
eventWildcard: 'すべてのイベント',
|
eventWildcard: 'すべてのイベント',
|
||||||
eventNamespaceWildcard: '{{namespace}}.*',
|
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: '条件付きルーティングルール',
|
routingRules: '条件付きルーティングルール',
|
||||||
routingRulesDescription:
|
routingRulesDescription:
|
||||||
'ルールは順番に評価され、最初に一致したルールのパイプラインにルーティングされます。一致しない場合はデフォルトパイプラインが使用されます。',
|
'ルールは順番に評価され、最初に一致したルールのパイプラインにルーティングされます。一致しない場合はデフォルトパイプラインが使用されます。',
|
||||||
|
|||||||
@@ -372,6 +372,23 @@ const zhHans = {
|
|||||||
eventCustom: '自定义事件',
|
eventCustom: '自定义事件',
|
||||||
eventWildcard: '全部事件',
|
eventWildcard: '全部事件',
|
||||||
eventNamespaceWildcard: '{{namespace}}.*',
|
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: '触发条件',
|
conditions: '触发条件',
|
||||||
conditionsDescription: '满足所有条件时才触发此绑定,不添加则无条件触发。',
|
conditionsDescription: '满足所有条件时才触发此绑定,不添加则无条件触发。',
|
||||||
conditionsEmpty: '无条件,始终触发。',
|
conditionsEmpty: '无条件,始终触发。',
|
||||||
|
|||||||
Reference in New Issue
Block a user