mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-20 15:37:27 +08:00
fix(bots): warn about incomplete processor event support
This commit is contained in:
@@ -782,6 +782,7 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
)}
|
||||
/>
|
||||
<PluginProcessorBindings
|
||||
supportedEvents={adapterSupportedEvents[currentAdapter] || []}
|
||||
value={form.watch('plugin_processors') ?? []}
|
||||
onChange={(value) =>
|
||||
form.setValue('plugin_processors', value, {
|
||||
|
||||
@@ -118,6 +118,7 @@ import { backendClient } from '@/app/infra/http';
|
||||
import {
|
||||
eventGroupLabel,
|
||||
eventNamespaces,
|
||||
eventPatternCovers,
|
||||
groupEventPatterns,
|
||||
} from '@/app/home/components/event-patterns/event-pattern-groups';
|
||||
import EventSelectOptionContent from '@/app/home/components/event-patterns/EventSelectOptionContent';
|
||||
@@ -220,17 +221,6 @@ function isMessageEventPattern(p: string) {
|
||||
return p === 'message.*' || p.startsWith('message.');
|
||||
}
|
||||
|
||||
function eventPatternCovers(sup: string, bind: string) {
|
||||
if (sup === '*') return true;
|
||||
if (sup === bind) return true;
|
||||
if (bind === '*') return false;
|
||||
if (sup.endsWith('.*')) {
|
||||
const ns = sup.replace('.*', '');
|
||||
return bind === `${ns}.*` || bind.startsWith(`${ns}.`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
interface RouteConflict {
|
||||
winnerIndex: number;
|
||||
shadowedIndex: number;
|
||||
@@ -1753,45 +1743,46 @@ export default function EventBindingsEditor({
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger
|
||||
className="items-start gap-2 py-2"
|
||||
disabled={otherEventGroups.length === 0}
|
||||
>
|
||||
<Workflow className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<span className="flex min-w-0 flex-col gap-0.5 pr-2">
|
||||
<span>{t('bots.behaviorCustom')}</span>
|
||||
<span className="text-xs font-normal text-muted-foreground">
|
||||
{t('bots.behaviorCustomDescription')}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="max-h-[min(70vh,32rem)] w-[320px] max-w-[90vw] overflow-y-auto">
|
||||
{otherEventGroups.map((group, groupIndex) => (
|
||||
<Fragment key={group.namespace}>
|
||||
{groupIndex > 0 && <DropdownMenuSeparator />}
|
||||
<DropdownMenuLabel className="px-2 py-1 text-xs font-normal text-muted-foreground">
|
||||
{eventGroupLabel(group.namespace, t)}
|
||||
</DropdownMenuLabel>
|
||||
{group.patterns.map((event) => (
|
||||
<DropdownMenuItem
|
||||
key={event}
|
||||
className="items-start py-2"
|
||||
onClick={() => addBinding(event)}
|
||||
>
|
||||
<span className="flex min-w-0 flex-col gap-0.5">
|
||||
<span>{eventLabel(event, t)}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{eventDescription(event, t)}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{otherEventGroups.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="items-start gap-2 py-2">
|
||||
<Workflow className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<span className="flex min-w-0 flex-col gap-0.5 pr-2">
|
||||
<span>{t('bots.behaviorCustom')}</span>
|
||||
<span className="text-xs font-normal text-muted-foreground">
|
||||
{t('bots.behaviorCustomDescription')}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="max-h-[min(70vh,32rem)] w-[320px] max-w-[90vw] overflow-y-auto">
|
||||
{otherEventGroups.map((group, groupIndex) => (
|
||||
<Fragment key={group.namespace}>
|
||||
{groupIndex > 0 && <DropdownMenuSeparator />}
|
||||
<DropdownMenuLabel className="px-2 py-1 text-xs font-normal text-muted-foreground">
|
||||
{eventGroupLabel(group.namespace, t)}
|
||||
</DropdownMenuLabel>
|
||||
{group.patterns.map((event) => (
|
||||
<DropdownMenuItem
|
||||
key={event}
|
||||
className="items-start py-2"
|
||||
onClick={() => addBinding(event)}
|
||||
>
|
||||
<span className="flex min-w-0 flex-col gap-0.5">
|
||||
<span>{eventLabel(event, t)}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{eventDescription(event, t)}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<RouteDryRunDialog
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Check, Plus, Settings2, ScrollText, X } from 'lucide-react';
|
||||
import {
|
||||
Check,
|
||||
Plus,
|
||||
Settings2,
|
||||
ScrollText,
|
||||
TriangleAlert,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||
import type {
|
||||
@@ -11,7 +18,10 @@ import type {
|
||||
} from '@/app/infra/entities/api';
|
||||
import { httpClient } from '@/app/infra/http';
|
||||
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||
import { eventPatternLabel } from '@/app/home/components/event-patterns/event-pattern-groups';
|
||||
import {
|
||||
eventPatternCovers,
|
||||
eventPatternLabel,
|
||||
} from '@/app/home/components/event-patterns/event-pattern-groups';
|
||||
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
|
||||
import { AuthenticatedPluginIcon } from '@/components/AuthenticatedPluginIcon';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -35,16 +45,52 @@ import {
|
||||
} from '@/components/ui/dialog';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
|
||||
function EventCompatibilityWarning({
|
||||
patterns = [],
|
||||
supportedEvents,
|
||||
}: {
|
||||
patterns?: string[];
|
||||
supportedEvents: string[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
// Legacy adapters only emit message.received when no events are declared.
|
||||
const supported = supportedEvents.length
|
||||
? supportedEvents
|
||||
: ['message.received'];
|
||||
const unsupported = [...new Set(patterns)].filter(
|
||||
(pattern) => !supported.some((event) => eventPatternCovers(event, pattern)),
|
||||
);
|
||||
if (!unsupported.length) return null;
|
||||
|
||||
return (
|
||||
<span
|
||||
role="status"
|
||||
className="mt-2 flex items-start gap-1.5 whitespace-normal text-xs text-amber-700 dark:text-amber-400"
|
||||
>
|
||||
<TriangleAlert className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
|
||||
<span>
|
||||
{t('bots.pluginSubscriptions.incompleteEvents', {
|
||||
events: unsupported
|
||||
.map((pattern) => eventPatternLabel(pattern, t))
|
||||
.join(' · '),
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PluginProcessorBindings({
|
||||
value,
|
||||
onChange,
|
||||
agents,
|
||||
onCreated,
|
||||
supportedEvents,
|
||||
}: {
|
||||
value: PluginProcessorBinding[];
|
||||
onChange: (value: PluginProcessorBinding[]) => void;
|
||||
agents: Agent[];
|
||||
onCreated: (agent: Agent) => void;
|
||||
supportedEvents: string[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { refreshPipelines } = useSidebarData();
|
||||
@@ -189,6 +235,10 @@ export default function PluginProcessorBindings({
|
||||
.map((pattern) => eventPatternLabel(pattern, t))
|
||||
.join(' · ') || t('agents.eventProcessor.unavailable')}
|
||||
</p>
|
||||
<EventCompatibilityWarning
|
||||
patterns={agent?.supported_event_patterns}
|
||||
supportedEvents={supportedEvents}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{agent &&
|
||||
@@ -329,6 +379,10 @@ export default function PluginProcessorBindings({
|
||||
.map((pattern) => eventPatternLabel(pattern, t))
|
||||
.join(' · ')}
|
||||
</span>
|
||||
<EventCompatibilityWarning
|
||||
patterns={agent.supported_event_patterns}
|
||||
supportedEvents={supportedEvents}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
@@ -392,6 +446,10 @@ export default function PluginProcessorBindings({
|
||||
.map((pattern) => eventPatternLabel(pattern, t))
|
||||
.join(' · ')}
|
||||
</span>
|
||||
<EventCompatibilityWarning
|
||||
patterns={descriptor.supported_event_patterns}
|
||||
supportedEvents={supportedEvents}
|
||||
/>
|
||||
</span>
|
||||
{componentRef === descriptor.id && (
|
||||
<Check className="size-4 shrink-0" />
|
||||
|
||||
@@ -5,6 +5,13 @@ export interface EventPatternGroup {
|
||||
patterns: string[];
|
||||
}
|
||||
|
||||
export function eventPatternCovers(supported: string, required: string) {
|
||||
if (supported === '*' || supported === required) return true;
|
||||
return (
|
||||
supported.endsWith('.*') && required.startsWith(supported.slice(0, -1))
|
||||
);
|
||||
}
|
||||
|
||||
function eventPatternNamespace(pattern: string) {
|
||||
if (pattern === '*') return '*';
|
||||
return pattern.split('.')[0] || pattern;
|
||||
|
||||
@@ -356,6 +356,8 @@ const enUS = {
|
||||
},
|
||||
bots: {
|
||||
pluginSubscriptions: {
|
||||
incompleteEvents:
|
||||
'This bot cannot fully cover these subscribed events: {{events}}. The processor may work only partially; you can still select and bind it.',
|
||||
description:
|
||||
'Automatically receive events declared by the plugin, independently of the routes above.',
|
||||
empty: 'No plugin processors are bound.',
|
||||
|
||||
@@ -362,6 +362,8 @@ const jaJP = {
|
||||
},
|
||||
bots: {
|
||||
pluginSubscriptions: {
|
||||
incompleteEvents:
|
||||
'このボットは次の監視イベントに完全には対応していません:{{events}}。一部の機能が動作しない可能性がありますが、選択して登録できます。',
|
||||
description:
|
||||
'プラグインが宣言したイベントを自動で受信し、上のルートとは独立して実行します。',
|
||||
empty: 'プラグインプロセッサーは未登録です。',
|
||||
|
||||
@@ -341,6 +341,8 @@ const zhHans = {
|
||||
},
|
||||
bots: {
|
||||
pluginSubscriptions: {
|
||||
incompleteEvents:
|
||||
'当前机器人无法完整支持以下监听事件:{{events}}。处理器的功能可能不完整,仍可选择并绑定。',
|
||||
description: '自动接收插件声明的事件,与上方事件路由独立执行。',
|
||||
empty: '尚未绑定插件处理器。',
|
||||
add: '添加插件处理器',
|
||||
|
||||
Reference in New Issue
Block a user