feat(processors): add explicitly bound plugin event processors

This commit is contained in:
RockChinQ
2026-09-08 00:43:35 +08:00
parent 812eb09ee4
commit 237fa6545d
50 changed files with 1968 additions and 289 deletions
+98 -80
View File
@@ -23,6 +23,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import PipelineDetailContent from '@/app/home/pipelines/PipelineDetailContent';
import EventProcessorDetailContent from './EventProcessorDetailContent';
import AgentCreateContent from './components/AgentCreateContent';
import AgentDebugPanel from './components/AgentDebugPanel';
import AgentFormComponent, {
@@ -167,87 +168,104 @@ export default function AgentDetailContent({ id }: { id: string }) {
return (
<>
<ProcessorDetailWorkbench
key={id}
title={`${agent.emoji || '🤖'} ${agent.name}`}
titleBadge={
supportedEventPatterns.length === 0 ? (
<Badge
variant="outline"
role="status"
className="shrink-0 gap-1 rounded-full border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
>
<AlertTriangle className="size-3" />
{t('agents.noEventsConfiguredBadge')}
</Badge>
) : undefined
}
titleAction={
canManage ? (
<EntityTitleEditButton onClick={() => setBasicInfoOpen(true)} />
) : undefined
}
status={runnerStatus}
saveLabel={t('common.save')}
saveFormId="agent-form"
canSave={canManage}
isDirty={formDirty}
isSaving={formSaving}
headerActions={
canManage ? (
<Button
type="button"
variant="destructive"
disabled={formSaving || deleting}
onClick={() => setDeleteConfirmOpen(true)}
>
<Trash2 className="size-4" />
{t('common.delete')}
</Button>
) : undefined
}
configTitle={t('pipelines.configuration')}
configContent={
<fieldset className="contents" disabled={!canManage}>
<AgentFormComponent
ref={agentFormRef}
agentId={id}
availableEventTypes={availableEventTypes}
onFinish={(updatedAgent) => {
if (updatedAgent) {
setAgent((current) =>
current ? { ...current, ...updatedAgent } : current,
);
{agent.kind === 'event_processor' ? (
<EventProcessorDetailContent
key={id}
id={id}
agent={agent}
canManage={canManage}
onDelete={() => setDeleteConfirmOpen(true)}
onEdit={() => setBasicInfoOpen(true)}
onSaved={() => {
void httpClient
.getAgent(id)
.then((response) => setAgent(response.agent));
void refreshPipelines();
}}
/>
) : (
<ProcessorDetailWorkbench
key={id}
title={`${agent.emoji || '🤖'} ${agent.name}`}
titleBadge={
supportedEventPatterns.length === 0 ? (
<Badge
variant="outline"
role="status"
className="shrink-0 gap-1 rounded-full border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
>
<AlertTriangle className="size-3" />
{t('agents.noEventsConfiguredBadge')}
</Badge>
) : undefined
}
titleAction={
canManage ? (
<EntityTitleEditButton onClick={() => setBasicInfoOpen(true)} />
) : undefined
}
status={runnerStatus}
saveLabel={t('common.save')}
saveFormId="agent-form"
canSave={canManage}
isDirty={formDirty}
isSaving={formSaving}
headerActions={
canManage ? (
<Button
type="button"
variant="destructive"
disabled={formSaving || deleting}
onClick={() => setDeleteConfirmOpen(true)}
>
<Trash2 className="size-4" />
{t('common.delete')}
</Button>
) : undefined
}
configTitle={t('pipelines.configuration')}
configContent={
<fieldset className="contents" disabled={!canManage}>
<AgentFormComponent
ref={agentFormRef}
agentId={id}
availableEventTypes={availableEventTypes}
onFinish={(updatedAgent) => {
if (updatedAgent) {
setAgent((current) =>
current ? { ...current, ...updatedAgent } : current,
);
}
refreshPipelines();
}}
onDirtyChange={setFormDirty}
onSavingChange={setFormSaving}
onRunnerStatusChange={setRunnerStatus}
onSupportedEventPatternsChange={setSupportedEventPatterns}
onPlatformToolsChange={setPlatformTools}
/>
</fieldset>
}
debugTitle={canOperate ? t('agents.debugTab') : undefined}
debugDescription={t('agents.debugPlatformNotice')}
debugContent={
canOperate ? (
<AgentDebugPanel
agentId={id}
platformTools={platformTools}
hasUnsavedChanges={formDirty}
beforeRun={async () => agentFormRef.current?.save() ?? false}
onOpenRunnerConfig={() =>
agentFormRef.current?.openSection('runner_config')
}
refreshPipelines();
}}
onDirtyChange={setFormDirty}
onSavingChange={setFormSaving}
onRunnerStatusChange={setRunnerStatus}
onSupportedEventPatternsChange={setSupportedEventPatterns}
onPlatformToolsChange={setPlatformTools}
/>
</fieldset>
}
debugTitle={canOperate ? t('agents.debugTab') : undefined}
debugDescription={t('agents.debugPlatformNotice')}
debugContent={
canOperate ? (
<AgentDebugPanel
agentId={id}
platformTools={platformTools}
hasUnsavedChanges={formDirty}
beforeRun={async () => agentFormRef.current?.save() ?? false}
onOpenRunnerConfig={() =>
agentFormRef.current?.openSection('runner_config')
}
supportedEventPatterns={supportedEventPatterns}
availableEventTypes={availableEventTypes}
/>
) : undefined
}
unsavedLabel={t('pipelines.unsavedChanges')}
/>
supportedEventPatterns={supportedEventPatterns}
availableEventTypes={availableEventTypes}
/>
) : undefined
}
unsavedLabel={t('pipelines.unsavedChanges')}
/>
)}
<EntityBasicInfoDialog
open={basicInfoOpen}
onOpenChange={setBasicInfoOpen}
@@ -0,0 +1,437 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { FileCode2, RefreshCw, Settings2, Trash2, Pencil } from 'lucide-react';
import { toast } from 'sonner';
import type {
Agent,
AgentPlatformTool,
EventProcessorDescriptor,
ProcessorRun,
ProcessorRunEvent,
} from '@/app/infra/entities/api';
import { httpClient } from '@/app/infra/http/HttpClient';
import { Button } from '@/components/ui/button';
import { extractI18nObject } from '@/i18n/I18nProvider';
import { Badge } from '@/components/ui/badge';
import EventProcessorSettings from './components/EventProcessorSettings';
export default function EventProcessorDetailContent({
agent,
id,
canManage,
onDelete,
onEdit,
onSaved,
}: {
agent: Agent;
id: string;
canManage: boolean;
onDelete: () => void;
onEdit: () => void;
onSaved: () => void;
}) {
const { t } = useTranslation();
const [platformTools, setPlatformTools] = useState<AgentPlatformTool[]>([]);
const toolLabels = Object.fromEntries(
platformTools.map((tool) => [tool.name, extractI18nObject(tool.label)]),
);
const [components, setComponents] = useState<EventProcessorDescriptor[]>([]);
const [componentRef, setComponentRef] = useState(agent.component_ref ?? '');
const initialParameters =
(
(agent.config?.runner_config ?? {}) as Record<
string,
Record<string, unknown>
>
)[agent.component_ref ?? ''] ?? {};
const [parameters, setParameters] = useState(initialParameters);
const [runs, setRuns] = useState<ProcessorRun[]>([]);
const [cursor, setCursor] = useState<number | null>(null);
const [selected, setSelected] = useState<ProcessorRun | null>(null);
const [events, setEvents] = useState<ProcessorRunEvent[]>([]);
const [eventCursor, setEventCursor] = useState<number | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [pagingRuns, setPagingRuns] = useState(false);
const [pagingEvents, setPagingEvents] = useState(false);
const [configOpen, setConfigOpen] = useState(false);
const [failed, setFailed] = useState(false);
const validate = useRef<(() => Promise<boolean>) | null>(null);
const requestVersion = useRef(0);
const available = components.some((item) => item.id === agent.component_ref);
const load = useCallback(async () => {
setFailed(false);
try {
const [metadata, page] = await Promise.all([
httpClient.getAgentMetadata(),
httpClient.getProcessorRuns(id),
]);
setComponents(metadata.event_processors ?? []);
setPlatformTools(metadata.platform_tools ?? []);
setRuns(page.items);
setCursor(page.has_more ? page.next_cursor : null);
} catch {
setFailed(true);
} finally {
setLoading(false);
}
}, [id]);
useEffect(() => {
void load();
}, [load]);
useEffect(
() => () => {
requestVersion.current += 1;
},
[id],
);
async function openRun(run: ProcessorRun) {
const version = ++requestVersion.current;
setSelected(run);
setEvents([]);
setEventCursor(null);
try {
const page = await httpClient.getProcessorRunEvents(id, run.run_id);
if (version !== requestVersion.current) return;
setSelected(page.run);
setEvents(page.items);
setEventCursor(page.has_more ? page.next_cursor : null);
} catch {
if (version === requestVersion.current)
toast.error(t('agents.eventProcessor.loadError'));
}
}
async function loadMoreEvents() {
if (!selected || eventCursor === null || pagingEvents) return;
setPagingEvents(true);
const runId = selected.run_id;
const version = requestVersion.current;
try {
const page = await httpClient.getProcessorRunEvents(
id,
runId,
eventCursor,
);
if (version !== requestVersion.current) return;
setEvents((current) => [...current, ...page.items]);
setEventCursor(page.has_more ? page.next_cursor : null);
} catch {
toast.error(t('agents.eventProcessor.loadError'));
} finally {
setPagingEvents(false);
}
}
async function loadMoreRuns() {
if (cursor === null || pagingRuns) return;
setPagingRuns(true);
try {
const page = await httpClient.getProcessorRuns(id, cursor);
setRuns((current) => [
...new Map(
[...current, ...page.items].map((run) => [run.run_id, run]),
).values(),
]);
setCursor(page.has_more ? page.next_cursor : null);
} catch {
toast.error(t('agents.eventProcessor.loadError'));
} finally {
setPagingRuns(false);
}
}
useEffect(() => {
let cancelled = false;
let busy = false;
const timer = window.setInterval(async () => {
if (busy || document.hidden) return;
busy = true;
try {
const page = await httpClient.getProcessorRuns(id);
if (cancelled) return;
setRuns((current) =>
[
...new Map(
[...current, ...page.items].map((run) => [run.run_id, run]),
).values(),
].sort((a, b) => b.created_at - a.created_at),
);
if (
selected &&
!['completed', 'failed', 'cancelled'].includes(selected.status) &&
eventCursor === null
) {
const trace = await httpClient.getProcessorRunEvents(
id,
selected.run_id,
events.at(-1)?.sequence,
);
if (cancelled) return;
setSelected(trace.run);
setEvents((current) => [
...new Map(
[...current, ...trace.items].map((event) => [
event.sequence,
event,
]),
).values(),
]);
setEventCursor(trace.has_more ? trace.next_cursor : null);
}
} catch {
/* Keep existing records visible across transient refresh failures. */
} finally {
busy = false;
}
}, 3000);
return () => {
cancelled = true;
window.clearInterval(timer);
};
}, [id, selected, eventCursor, events]);
async function save() {
if (!componentRef || !((await validate.current?.()) ?? true)) return;
setSaving(true);
try {
await httpClient.updateAgent(id, {
component_ref: componentRef,
config: {
...agent.config,
runner: { id: componentRef },
runner_config: { [componentRef]: parameters },
},
});
toast.success(t('agents.saveSuccess'));
onSaved();
setConfigOpen(false);
await load();
} catch {
toast.error(t('agents.saveError'));
} finally {
setSaving(false);
}
}
function payload(value: unknown) {
return (
<pre className="mt-2 whitespace-pre-wrap break-all rounded-md bg-muted/50 p-3 text-xs">
{JSON.stringify(value, null, 2)}
</pre>
);
}
return (
<div className="flex h-full min-h-0 flex-col gap-4">
<header className="flex flex-wrap items-center gap-3">
<FileCode2 className="size-6" />
<h1 className="text-2xl font-semibold">{agent.name}</h1>
{canManage && (
<Button
variant="ghost"
size="icon"
onClick={onEdit}
aria-label={t('common.edit')}
>
<Pencil className="size-4" />
</Button>
)}
<Badge variant="outline">{t('agents.eventProcessor.type')}</Badge>
{!loading && !failed && !available && (
<Badge variant="destructive">
{t('agents.eventProcessor.unavailable')}
</Badge>
)}
<div className="ml-auto flex gap-2">
<Button
variant="outline"
onClick={() => {
void load();
if (selected) void openRun(selected);
}}
aria-label={t('agents.eventProcessor.refresh')}
>
<RefreshCw className="size-4" />
</Button>
{canManage && (
<>
<Button
variant="outline"
onClick={() => setConfigOpen(!configOpen)}
>
<Settings2 className="size-4" />
{t('pipelines.configuration')}
</Button>
<Button variant="destructive" onClick={onDelete}>
<Trash2 className="size-4" />
{t('common.delete')}
</Button>
</>
)}
</div>
</header>
<p className="shrink-0 break-all text-xs text-muted-foreground">
{agent.component_ref}
</p>
{configOpen && (
<div className="max-h-[45vh] shrink-0 overflow-y-auto rounded-xl border p-4">
<EventProcessorSettings
components={components}
value={componentRef}
parameters={parameters}
onChange={(value) => {
setComponentRef(value);
setParameters({});
validate.current = null;
}}
onParametersChange={setParameters}
onValidate={(fn) => {
validate.current = fn;
}}
/>
<Button
className="mt-4"
disabled={
saving || !components.some((item) => item.id === componentRef)
}
onClick={() => void save()}
>
{t('common.save')}
</Button>
</div>
)}
{failed && (
<p role="alert" className="text-destructive">
{t('agents.eventProcessor.loadError')}
</p>
)}
<div className="grid min-h-0 flex-1 gap-4 md:grid-cols-[minmax(240px,0.7fr)_minmax(0,1.3fr)]">
<section className="min-h-0 overflow-y-auto rounded-xl border p-4">
<h2 className="mb-3 font-semibold">
{t('agents.eventProcessor.runs')}
</h2>
{loading ? (
<p>{t('common.loading')}</p>
) : runs.length === 0 && !failed ? (
<div className="space-y-3 text-sm text-muted-foreground">
<p>{t('agents.eventProcessor.noRuns')}</p>
<Link className="text-primary underline" to="/home/bots">
{t('agents.eventProcessor.bindBot')}
</Link>
</div>
) : (
runs.map((run) => (
<button
key={run.run_id}
onClick={() => void openRun(run)}
className={`mb-2 block w-full rounded-lg border p-3 text-left text-sm ${selected?.run_id === run.run_id ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'}`}
>
<span className="block break-all font-medium">
{run.metadata.event_type}
</span>
<span className="mt-1 flex flex-wrap justify-between gap-1 text-xs text-muted-foreground">
<span>
{new Date(run.created_at * 1000).toLocaleString()}
</span>
<span>
{t(`agents.eventProcessor.status_${run.status}`, {
defaultValue: run.status,
})}
</span>
</span>
</button>
))
)}
{cursor !== null && (
<Button
variant="ghost"
disabled={pagingRuns}
onClick={() => void loadMoreRuns()}
>
{t('agents.eventProcessor.loadMore')}
</Button>
)}
</section>
<section className="min-h-0 overflow-y-auto rounded-xl border p-4">
<h2 className="mb-3 font-semibold">
{t('agents.eventProcessor.trace')}
</h2>
{!selected ? (
<p className="text-sm text-muted-foreground">
{t('agents.eventProcessor.selectRun')}
</p>
) : (
<div className="space-y-3">
<details className="rounded-lg border p-3">
<summary className="cursor-pointer text-sm font-medium">
{t('agents.eventProcessor.input')}
</summary>
{payload(selected.metadata.input_event)}
</details>
{selected.metadata.delivery != null && (
<details className="rounded-lg border p-3">
<summary className="cursor-pointer text-sm font-medium">
{t('agents.eventProcessor.destination')}
</summary>
{payload(selected.metadata.delivery)}
</details>
)}
{events.map((event) =>
event.type === 'processor.log' ? (
<div
key={event.sequence}
className="rounded-lg bg-muted/40 p-3 text-sm"
>
<span className="mr-2 text-xs text-muted-foreground">
{String(event.data.level)}
</span>
<span className="whitespace-pre-wrap break-words">
{String(event.data.text)}
</span>
</div>
) : (
<details
key={event.sequence}
className="rounded-lg border p-3"
>
<summary className="cursor-pointer break-all text-sm font-medium">
{t(
`agents.eventProcessor.trace_${event.type.replaceAll('.', '_')}`,
{ defaultValue: event.type },
)}
{typeof event.data.tool_name === 'string' && (
<span className="ml-2 text-muted-foreground">
{toolLabels[event.data.tool_name] ||
event.data.tool_name}
</span>
)}
</summary>
{payload(event.data)}
</details>
),
)}
{selected.status === 'failed' && selected.status_reason && (
<p className="break-words text-sm text-destructive">
{selected.status_reason}
</p>
)}
{eventCursor !== null && (
<Button
variant="ghost"
disabled={pagingEvents}
onClick={() => void loadMoreEvents()}
>
{t('agents.eventProcessor.loadMore')}
</Button>
)}
</div>
)}
</section>
</div>
</div>
);
}
@@ -1,12 +1,13 @@
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import { Bot, Workflow } from 'lucide-react';
import { Bot, Workflow, FileCode2 } from 'lucide-react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { AgentKind } from '@/app/infra/entities/api';
import { AgentKind, EventProcessorDescriptor } from '@/app/infra/entities/api';
import EventProcessorSettings from './EventProcessorSettings';
import { Button } from '@/components/ui/button';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import {
@@ -35,6 +36,23 @@ export default function AgentCreateContent({
}) {
const { t } = useTranslation();
const [kind, setKind] = useState<AgentKind>('agent');
const [components, setComponents] = useState<EventProcessorDescriptor[]>([]);
const [componentRef, setComponentRef] = useState('');
const [parameters, setParameters] = useState<Record<string, unknown>>({});
const validateParameters = useRef<(() => Promise<boolean>) | null>(null);
useEffect(() => {
if (kind !== 'event_processor') return;
let cancelled = false;
httpClient
.getAgentMetadata()
.then((metadata) => {
if (!cancelled) setComponents(metadata.event_processors ?? []);
})
.catch(() => toast.error(t('agents.eventProcessor.loadError')));
return () => {
cancelled = true;
};
}, [kind, t]);
const formSchema = z.object({
name: z.string().min(1, { message: t('agents.nameRequired') }),
description: z.string().optional(),
@@ -51,8 +69,14 @@ export default function AgentCreateContent({
});
function handleKindChange(nextKind: AgentKind) {
const previousDefaultEmoji = kind === 'pipeline' ? '⚙️' : '🤖';
const nextDefaultEmoji = nextKind === 'pipeline' ? '⚙️' : '🤖';
const previousDefaultEmoji =
kind === 'pipeline' ? '⚙️' : kind === 'event_processor' ? '⚡' : '🤖';
const nextDefaultEmoji =
nextKind === 'pipeline'
? '⚙️'
: nextKind === 'event_processor'
? '⚡'
: '🤖';
setKind(nextKind);
const currentEmoji = form.getValues('emoji');
if (!currentEmoji || currentEmoji === previousDefaultEmoji) {
@@ -60,10 +84,24 @@ export default function AgentCreateContent({
}
}
function handleSubmit(values: FormValues) {
async function handleSubmit(values: FormValues) {
if (
kind === 'event_processor' &&
(!componentRef || !((await validateParameters.current?.()) ?? true))
)
return;
httpClient
.createAgent({
kind,
...(kind === 'event_processor'
? {
component_ref: componentRef,
config: {
runner: { id: componentRef },
runner_config: { [componentRef]: parameters },
},
}
: {}),
name: values.name,
description: values.description ?? '',
emoji: values.emoji || (kind === 'pipeline' ? '⚙️' : '🤖'),
@@ -90,13 +128,26 @@ export default function AgentCreateContent({
title: t('agents.pipelineType'),
description: t('agents.pipelineTypeDescription'),
},
{
kind: 'event_processor' as const,
icon: FileCode2,
title: t('agents.eventProcessor.type'),
description: t('agents.eventProcessor.description'),
},
];
return (
<div className="flex h-full flex-col">
<div className="flex items-center justify-between pb-4 shrink-0">
<h1 className="text-xl font-semibold">{t('agents.create')}</h1>
<Button type="submit" form="agent-create-form">
<Button
type="submit"
form="agent-create-form"
disabled={
form.formState.isSubmitting ||
(kind === 'event_processor' && !componentRef)
}
>
{t('common.submit')}
</Button>
</div>
@@ -158,6 +209,22 @@ export default function AgentCreateContent({
</ToggleGroup>
</section>
{kind === 'event_processor' && (
<EventProcessorSettings
components={components}
value={componentRef}
parameters={parameters}
onChange={(value) => {
setComponentRef(value);
setParameters({});
validateParameters.current = null;
}}
onParametersChange={setParameters}
onValidate={(validate) => {
validateParameters.current = validate;
}}
/>
)}
<Card>
<CardHeader>
<CardTitle>{t('agents.basicInfo')}</CardTitle>
@@ -0,0 +1,91 @@
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import type { EventProcessorDescriptor } from '@/app/infra/entities/api';
import { extractI18nObject } from '@/i18n/I18nProvider';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
export default function EventProcessorSettings({
components,
value,
parameters,
onChange,
onParametersChange,
onValidate,
}: {
components: EventProcessorDescriptor[];
value: string;
parameters: Record<string, unknown>;
onChange: (value: string) => void;
onParametersChange: (value: Record<string, unknown>) => void;
onValidate?: (validate: () => Promise<boolean>) => void;
}) {
const { t } = useTranslation();
const selected = components.find((item) => item.id === value);
return (
<div className="space-y-4">
<div className="space-y-2">
<label
className="text-sm font-medium"
htmlFor="event-processor-component"
>
{t('agents.eventProcessor.component')}
</label>
<Select value={value} onValueChange={onChange}>
<SelectTrigger id="event-processor-component">
<SelectValue
placeholder={t('agents.eventProcessor.selectComponent')}
/>
</SelectTrigger>
<SelectContent>
{value && !selected && (
<SelectItem value={value}>
{t('agents.eventProcessor.unavailable')}
</SelectItem>
)}
{components.map((component) => (
<SelectItem key={component.id} value={component.id}>
{extractI18nObject({
en_US: component.id,
zh_Hans: component.id,
...component.label,
})}{' '}
· {component.plugin_author}/{component.plugin_name}
</SelectItem>
))}
</SelectContent>
</Select>
{components.length === 0 && (
<p className="text-sm text-muted-foreground">
{t('agents.eventProcessor.noComponents')}{' '}
<Link className="text-primary underline" to="/home/plugins">
{t('agents.eventProcessor.installPlugin')}
</Link>
</p>
)}
</div>
{selected && (
<p className="break-words text-xs text-muted-foreground">
{selected.supported_event_patterns.join(' · ')}
</p>
)}
{selected && selected.config_schema.length > 0 && (
<DynamicFormComponent
key={value}
itemConfigList={selected.config_schema}
initialValues={parameters}
onSubmit={(values) =>
onParametersChange(values as Record<string, unknown>)
}
onValidate={onValidate}
/>
)}
</div>
);
}
@@ -374,5 +374,29 @@ function PipelineDiagram() {
}
export default function ProcessorTypeDiagram({ kind }: { kind: AgentKind }) {
const { t } = useTranslation();
if (kind === 'event_processor')
return (
<div className="flex h-full flex-col justify-center gap-6 rounded-xl border bg-muted/20 p-8">
<h3 className="text-lg font-semibold">
{t('agents.eventProcessor.type')}
</h3>
<p className="text-sm text-muted-foreground">
{t('agents.eventProcessor.description')}
</p>
{['input', 'component', 'trace'].map((step, index) => (
<div
key={step}
className="flex items-center gap-3 rounded-lg border bg-background p-4"
>
<span className="text-primary">{index + 1}</span>
<span>{t(`agents.eventProcessor.${step}`)}</span>
</div>
))}
<p className="text-sm text-muted-foreground">
{t('agents.eventProcessor.activation')}
</p>
</div>
);
return kind === 'agent' ? <AgentDiagram /> : <PipelineDiagram />;
}
@@ -75,7 +75,12 @@ const getFormSchema = (t: (key: string) => string) =>
z.object({
id: z.string().optional(),
event_pattern: z.string(),
target_type: z.enum(['agent', 'pipeline', 'discard']),
target_type: z.enum([
'agent',
'pipeline',
'event_processor',
'discard',
]),
target_uuid: z.string(),
filters: z.array(z.record(z.string(), z.any())).optional(),
priority: z.number(),
@@ -35,6 +35,7 @@ import {
UserMinus,
UserPlus,
Workflow,
FileCode2,
XCircle,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
@@ -518,6 +519,11 @@ function TargetCombobox({
const pipelines = pipelineAllowed
? agentOptions.filter((a) => a.kind === 'pipeline')
: [];
const eventProcessors = agentOptions.filter(
(item) =>
item.kind === 'event_processor' &&
agentSupportsEventPattern(item, binding.event_pattern),
);
function currentLabel() {
if (targetType === 'discard')
@@ -531,7 +537,9 @@ function TargetCombobox({
if (agent)
return (
<span className="flex items-center gap-1.5">
{agent.kind === 'pipeline' ? (
{agent.kind === 'event_processor' ? (
<FileCode2 className="size-3.5" />
) : agent.kind === 'pipeline' ? (
<Workflow className="size-3.5" />
) : (
<Bot className="size-3.5" />
@@ -585,6 +593,26 @@ function TargetCombobox({
))}
</CommandGroup>
)}
{eventProcessors.length > 0 && (
<CommandGroup heading={t('agents.eventProcessor.type')}>
{eventProcessors.map((item) => (
<CommandItem
key={item.uuid}
value={`event_processor:${item.uuid}:${item.name}`}
onSelect={() =>
select(encodeTarget('event_processor', item.uuid || ''))
}
>
<FileCode2 className="mr-2 size-3.5 shrink-0" />
<span className="truncate">{targetLabel(item)}</span>
{current ===
encodeTarget('event_processor', item.uuid || '') && (
<Check className="ml-auto size-3.5 shrink-0" />
)}
</CommandItem>
))}
</CommandGroup>
)}
{pipelines.length > 0 && (
<CommandGroup heading={t('bots.targetPipeline')}>
{pipelines.map((a) => (
@@ -730,13 +730,16 @@ function NavItems({
const showAgentGroupHeaders =
isAgents && !inPopover && sidebarData.agentsGroupByKind;
const agentGroupOrder: Array<'agent' | 'pipeline'> = [
'agent',
'pipeline',
];
const agentGroupLabelKey: Record<'agent' | 'pipeline', string> = {
const agentGroupOrder: Array<
'agent' | 'pipeline' | 'event_processor'
> = ['agent', 'pipeline', 'event_processor'];
const agentGroupLabelKey: Record<
'agent' | 'pipeline' | 'event_processor',
string
> = {
agent: 'agents.kindBadgeAgent',
pipeline: 'agents.kindBadgePipeline',
event_processor: 'agents.eventProcessor.type',
};
const groupOrder: Array<'plugin' | 'mcp' | 'skill'> = [
@@ -889,12 +892,16 @@ function NavItems({
<span
className="ml-auto flex shrink-0 items-center text-muted-foreground"
title={
item.kind === 'pipeline'
? t('agents.kindBadgePipeline')
: t('agents.kindBadgeAgent')
item.kind === 'event_processor'
? t('agents.eventProcessor.type')
: item.kind === 'pipeline'
? t('agents.kindBadgePipeline')
: t('agents.kindBadgeAgent')
}
>
{item.kind === 'pipeline' ? (
{item.kind === 'event_processor' ? (
<span className="text-xs"></span>
) : item.kind === 'pipeline' ? (
<Workflow className="size-3.5" />
) : (
<Bot className="size-3.5" />
@@ -31,7 +31,7 @@ export interface SidebarEntityItem {
// Set when this item appears in the unified extensions list
extensionType?: 'plugin' | 'mcp' | 'skill';
// Agent-specific: distinguishes Agent processors from Pipelines
kind?: 'agent' | 'pipeline';
kind?: 'agent' | 'pipeline' | 'event_processor';
}
// Plugin page registered by a plugin
+39 -2
View File
@@ -162,7 +162,43 @@ export interface ApiRespPipelines {
pipelines: Pipeline[];
}
export type AgentKind = 'agent' | 'pipeline';
export type AgentKind = 'agent' | 'pipeline' | 'event_processor';
export interface EventProcessorDescriptor {
id: string;
label: Record<string, string>;
plugin_author: string;
plugin_name: string;
config_schema: import('../form/dynamic').IDynamicFormItemSchema[];
supported_event_patterns: string[];
}
export interface ProcessorRun {
run_id: string;
status: string;
status_reason?: string;
created_at: number;
metadata: { event_type?: string; input_event?: unknown; delivery?: unknown };
}
export interface ProcessorRunEvent {
sequence: number;
type: string;
data: Record<string, unknown>;
}
export interface ProcessorRunPage {
items: ProcessorRun[];
next_cursor: number | null;
has_more: boolean;
}
export interface ProcessorRunEventPage {
run: ProcessorRun;
items: ProcessorRunEvent[];
next_cursor: number | null;
has_more: boolean;
}
export interface AgentCapability {
supported_event_patterns: string[];
@@ -192,6 +228,7 @@ export interface ApiRespAgent {
}
export interface GetAgentMetadataResponseData {
event_processors?: EventProcessorDescriptor[];
runner_config?: PipelineConfigTab;
platform_tools: AgentPlatformTool[];
host_tools?: PluginTool[] | null;
@@ -274,7 +311,7 @@ export interface Bot {
export interface EventBinding {
id?: string;
event_pattern: string;
target_type: 'agent' | 'pipeline' | 'discard';
target_type: AgentKind | 'discard';
target_uuid: string;
filters?: Array<Record<string, unknown>>;
priority: number;
+19
View File
@@ -268,6 +268,25 @@ export class BackendClient extends BaseHttpClient {
return this.get('/api/v1/agents/_/metadata');
}
public getProcessorRuns(
uuid: string,
beforeId?: number,
): Promise<import('../entities/api').ProcessorRunPage> {
return this.get(
`/api/v1/agents/${encodeURIComponent(uuid)}/runs${beforeId === undefined ? '' : `?before_id=${beforeId}`}`,
);
}
public getProcessorRunEvents(
uuid: string,
runId: string,
afterSequence?: number,
): Promise<import('../entities/api').ProcessorRunEventPage> {
return this.get(
`/api/v1/agents/${encodeURIComponent(uuid)}/runs/${encodeURIComponent(runId)}/events${afterSequence === undefined ? '' : `?after_sequence=${afterSequence}`}`,
);
}
public createAgent(agent: Agent): Promise<{ uuid: string; kind: string }> {
return this.post('/api/v1/agents', agent);
}
+36 -7
View File
@@ -395,20 +395,19 @@ const enUS = {
commonScenarios: 'Common scenarios',
dragEventRoute: 'Drag route {{index}}',
behaviorReplyMessages: 'Reply to messages',
behaviorReplyMessagesDescription:
'Send incoming messages to an Agent or Pipeline.',
behaviorReplyMessagesDescription: 'Send incoming messages to a processor.',
behaviorWelcomeMembers: 'Welcome new members',
behaviorWelcomeMembersDescription:
'Run an Agent when someone joins a group.',
'Run a processor when someone joins a group.',
behaviorHandleDepartures: 'Handle member departures',
behaviorHandleDeparturesDescription:
'Run an Agent when someone leaves or is removed.',
'Run a processor when someone leaves or is removed.',
behaviorReviewFriendRequests: 'Review friend requests',
behaviorReviewFriendRequestsDescription:
'Let an Agent decide how to handle a new request.',
'Send new friend requests to a processor.',
behaviorHandleModeration: 'Handle moderation events',
behaviorHandleModerationDescription:
'Run an Agent when a group member is restricted.',
'Run a processor when a group member is restricted.',
behaviorCustom: 'Configure another event',
behaviorCustomDescription:
'Add a route and choose from every event supported by this adapter.',
@@ -704,6 +703,36 @@ const enUS = {
},
},
agents: {
eventProcessor: {
type: 'Event processor',
description: 'Process platform events using plugin code.',
component: 'Plugin component',
selectComponent: 'Select an event processor',
unavailable: 'Component unavailable',
noComponents: 'No event processor components installed.',
installPlugin: 'Install a plugin',
loadError: 'Unable to load processor details.',
refresh: 'Refresh',
runs: 'Runs',
noRuns: 'No runs yet. Bind a Bot event to start.',
bindBot: 'Bind Bot events',
trace: 'Logs and message flow',
selectRun: 'Select a run to view details.',
input: 'Incoming event',
destination: 'Delivery destination',
loadMore: 'Load more',
activation: 'Install a plugin, create an instance, then bind Bot events.',
status_pending: 'Pending',
status_running: 'Running',
status_completed: 'Completed',
status_failed: 'Failed',
status_cancelled: 'Cancelled',
status_queued: 'Queued',
trace_run_completed: 'Run completed',
trace_run_failed: 'Run failed',
trace_tool_call_started: 'Action started',
trace_tool_call_completed: 'Action result',
},
debugData: {
title: 'Event data',
form: 'Common fields',
@@ -735,7 +764,7 @@ const enUS = {
description: 'Create reusable processors and use them in bot event routing',
create: 'Create Processor',
editAgent: 'Edit Agent',
selectFromSidebar: 'Select an Agent or Pipeline from the sidebar',
selectFromSidebar: 'Select a processor from the sidebar',
agentType: 'Agent',
agentTypeDescription:
'Use a runner to handle messages, group members, friends, feedback, and other platform events. Best for scenarios that need autonomous decisions, tool use, or non-message events.',
+32 -1
View File
@@ -506,6 +506,37 @@ const esES = {
},
},
agents: {
eventProcessor: {
type: 'Procesador de eventos',
description: 'Procesa eventos con código del plugin.',
component: 'Componente del plugin',
selectComponent: 'Seleccionar un procesador',
unavailable: 'Componente no disponible',
noComponents: 'No hay componentes de eventos instalados.',
installPlugin: 'Instalar un plugin',
loadError: 'No se pudieron cargar los detalles.',
refresh: 'Actualizar',
runs: 'Ejecuciones',
noRuns: 'Sin ejecuciones. Vincula eventos de un Bot para empezar.',
bindBot: 'Vincular eventos del Bot',
trace: 'Registros y flujo de mensajes',
selectRun: 'Selecciona una ejecución para ver los detalles.',
input: 'Evento recibido',
destination: 'Destino de entrega',
loadMore: 'Cargar más',
activation:
'Instala un plugin, crea una instancia y vincula eventos del Bot.',
status_pending: 'Pendiente',
status_running: 'En ejecución',
status_completed: 'Completado',
status_failed: 'Error',
status_cancelled: 'Cancelado',
status_queued: 'En cola',
trace_run_completed: 'Ejecución completada',
trace_run_failed: 'Ejecución fallida',
trace_tool_call_started: 'Acción iniciada',
trace_tool_call_completed: 'Resultado de la acción',
},
debugData: {
title: 'Datos del evento',
form: 'Campos comunes',
@@ -538,7 +569,7 @@ const esES = {
'Crea procesadores reutilizables y úsalos en el enrutamiento de eventos del bot',
create: 'Crear procesador',
editAgent: 'Editar Agent',
selectFromSidebar: 'Selecciona un Agent o Pipeline desde la barra lateral',
selectFromSidebar: 'Selecciona un procesador en la barra lateral',
agentType: 'Agent',
agentTypeDescription:
'Usa un runner para procesar mensajes, miembros de grupo, amigos, retroalimentación y otros eventos de plataforma.',
+37 -7
View File
@@ -402,19 +402,19 @@ const jaJP = {
dragEventRoute: 'ルート {{index}} をドラッグ',
behaviorReplyMessages: '受信メッセージに返信',
behaviorReplyMessagesDescription:
'受信メッセージを Agent または Pipeline で処理します。',
'受信メッセージをプロセッサーに渡します。',
behaviorWelcomeMembers: '新しいメンバーを歓迎',
behaviorWelcomeMembersDescription:
'メンバーがグループに参加したときに Agent を実行します。',
'グループへの参加時にプロセッサーを実行します。',
behaviorHandleDepartures: 'メンバーの退出を処理',
behaviorHandleDeparturesDescription:
'メンバーが退出または削除されたときに Agent を実行します。',
'グループからの退出時にプロセッサーを実行します。',
behaviorReviewFriendRequests: '友だち申請を確認',
behaviorReviewFriendRequestsDescription:
'新しい申請の処理方法を Agent に判断させます。',
'新しい友達リクエストをプロセッサーに渡します。',
behaviorHandleModeration: 'モデレーションイベントを処理',
behaviorHandleModerationDescription:
'グループメンバー制限されたときに Agent を実行します。',
'グループメンバー制限時にプロセッサーを実行します。',
behaviorCustom: '別のイベントを設定',
behaviorCustomDescription:
'ルートを追加し、このアダプターが対応する全イベントから選択します。',
@@ -716,6 +716,37 @@ const jaJP = {
},
},
agents: {
eventProcessor: {
type: 'イベントプロセッサー',
description: 'プラグインのコードでイベントを処理します。',
component: 'プラグインコンポーネント',
selectComponent: 'イベントプロセッサーを選択',
unavailable: 'コンポーネントを利用できません',
noComponents: 'イベントプロセッサーがインストールされていません。',
installPlugin: 'プラグインをインストール',
loadError: '詳細を読み込めません。',
refresh: '更新',
runs: '実行履歴',
noRuns: '実行履歴はありません。Bot イベントを紐付けて開始します。',
bindBot: 'Bot イベントを紐付ける',
trace: 'ログとメッセージの流れ',
selectRun: '実行履歴を選択して詳細を表示します。',
input: '受信イベント',
destination: '送信先',
loadMore: 'さらに読み込む',
activation:
'プラグインをインストールし、インスタンスを作成して Bot イベントを紐付けます。',
status_pending: '待機中',
status_running: '実行中',
status_completed: '完了',
status_failed: '失敗',
status_cancelled: 'キャンセル済み',
status_queued: 'キュー待ち',
trace_run_completed: '実行完了',
trace_run_failed: '実行失敗',
trace_tool_call_started: 'アクション開始',
trace_tool_call_completed: 'アクション結果',
},
debugData: {
title: 'イベントデータ',
form: '基本項目',
@@ -772,8 +803,7 @@ const jaJP = {
'再利用可能なプロセッサーを作成し、ボットのイベントルーティングで使用します',
create: 'プロセッサーを作成',
editAgent: 'Agent を編集',
selectFromSidebar:
'サイドバーから Agent または Pipeline を選択してください',
selectFromSidebar: 'サイドバーからプロセッサーを選択',
agentType: 'Agent',
agentTypeDescription:
'Runner を使ってメッセージ、グループメンバー、友だち、フィードバックなどのプラットフォームイベントを処理します。自律的な判断、ツール利用、メッセージ以外のイベント対応が必要な場合に適しています。',
+32 -1
View File
@@ -503,6 +503,37 @@ const ruRU = {
},
},
agents: {
eventProcessor: {
type: 'Обработчик событий',
description: 'Обрабатывает события кодом плагина.',
component: 'Компонент плагина',
selectComponent: 'Выберите обработчик',
unavailable: 'Компонент недоступен',
noComponents: 'Компоненты обработки событий не установлены.',
installPlugin: 'Установить плагин',
loadError: 'Не удалось загрузить данные.',
refresh: 'Обновить',
runs: 'Запуски',
noRuns: 'Запусков пока нет. Привяжите события бота.',
bindBot: 'Привязать события бота',
trace: 'Журнал и поток сообщений',
selectRun: 'Выберите запуск для просмотра.',
input: 'Входящее событие',
destination: 'Получатель',
loadMore: 'Загрузить ещё',
activation:
'Установите плагин, создайте экземпляр и привяжите события бота.',
status_pending: 'Ожидание',
status_running: 'Выполняется',
status_completed: 'Завершено',
status_failed: 'Ошибка',
status_cancelled: 'Отменено',
status_queued: 'В очереди',
trace_run_completed: 'Выполнение завершено',
trace_run_failed: 'Ошибка выполнения',
trace_tool_call_started: 'Действие начато',
trace_tool_call_completed: 'Результат действия',
},
debugData: {
title: 'Данные события',
form: 'Основные поля',
@@ -535,7 +566,7 @@ const ruRU = {
'Создавайте переиспользуемые обработчики и используйте их в маршрутизации событий бота',
create: 'Создать обработчик',
editAgent: 'Редактировать Agent',
selectFromSidebar: 'Выберите Agent или Pipeline на боковой панели',
selectFromSidebar: 'Выберите обработчик на боковой панели',
agentType: 'Agent',
agentTypeDescription:
'Используйте runner для обработки сообщений, участников групп, друзей, обратной связи и других событий платформы.',
+31 -1
View File
@@ -490,6 +490,36 @@ const thTH = {
},
},
agents: {
eventProcessor: {
type: 'ตัวประมวลผลเหตุการณ์',
description: 'ประมวลผลเหตุการณ์ด้วยโค้ดปลั๊กอิน',
component: 'ส่วนประกอบปลั๊กอิน',
selectComponent: 'เลือกตัวประมวลผลเหตุการณ์',
unavailable: 'ส่วนประกอบไม่พร้อมใช้งาน',
noComponents: 'ยังไม่ได้ติดตั้งส่วนประกอบประมวลผลเหตุการณ์',
installPlugin: 'ติดตั้งปลั๊กอิน',
loadError: 'ไม่สามารถโหลดรายละเอียดได้',
refresh: 'รีเฟรช',
runs: 'ประวัติการทำงาน',
noRuns: 'ยังไม่มีการทำงาน เชื่อมโยงเหตุการณ์บอทเพื่อเริ่มต้น',
bindBot: 'เชื่อมโยงเหตุการณ์บอท',
trace: 'บันทึกและเส้นทางข้อความ',
selectRun: 'เลือกการทำงานเพื่อดูรายละเอียด',
input: 'เหตุการณ์ขาเข้า',
destination: 'ปลายทางการส่ง',
loadMore: 'โหลดเพิ่มเติม',
activation: 'ติดตั้งปลั๊กอิน สร้างอินสแตนซ์ แล้วเชื่อมโยงเหตุการณ์บอท',
status_pending: 'รอดำเนินการ',
status_running: 'กำลังทำงาน',
status_completed: 'เสร็จสิ้น',
status_failed: 'ล้มเหลว',
status_cancelled: 'ยกเลิกแล้ว',
status_queued: 'อยู่ในคิว',
trace_run_completed: 'ทำงานเสร็จสิ้น',
trace_run_failed: 'การทำงานล้มเหลว',
trace_tool_call_started: 'เริ่มดำเนินการ',
trace_tool_call_completed: 'ผลการดำเนินการ',
},
debugData: {
title: 'ข้อมูลเหตุการณ์',
form: 'ฟิลด์ทั่วไป',
@@ -521,7 +551,7 @@ const thTH = {
description: 'สร้างตัวประมวลผลที่ใช้ซ้ำได้และใช้ในเส้นทางเหตุการณ์ของบอท',
create: 'สร้างตัวประมวลผล',
editAgent: 'แก้ไข Agent',
selectFromSidebar: 'เลือก Agent หรือ Pipeline จากแถบด้านข้าง',
selectFromSidebar: 'เลือกตัวประมวลผลจากแถบด้านข้าง',
agentType: 'Agent',
agentTypeDescription:
'ใช้ runner เพื่อประมวลผลข้อความ สมาชิกกลุ่ม เพื่อน ฟีดแบ็ก และเหตุการณ์แพลตฟอร์มอื่นๆ',
+31 -1
View File
@@ -499,6 +499,36 @@ const viVN = {
},
},
agents: {
eventProcessor: {
type: 'Bộ xử lý sự kiện',
description: 'Xử lý sự kiện bằng mã plugin.',
component: 'Thành phần plugin',
selectComponent: 'Chọn bộ xử lý sự kiện',
unavailable: 'Thành phần không khả dụng',
noComponents: 'Chưa cài thành phần xử lý sự kiện.',
installPlugin: 'Cài plugin',
loadError: 'Không thể tải chi tiết.',
refresh: 'Làm mới',
runs: 'Lịch sử chạy',
noRuns: 'Chưa có lần chạy nào. Liên kết sự kiện Bot để bắt đầu.',
bindBot: 'Liên kết sự kiện Bot',
trace: 'Nhật ký và luồng tin nhắn',
selectRun: 'Chọn một lần chạy để xem chi tiết.',
input: 'Sự kiện đầu vào',
destination: 'Đích gửi',
loadMore: 'Tải thêm',
activation: 'Cài plugin, tạo phiên bản rồi liên kết sự kiện Bot.',
status_pending: 'Đang chờ',
status_running: 'Đang chạy',
status_completed: 'Hoàn tất',
status_failed: 'Thất bại',
status_cancelled: 'Đã hủy',
status_queued: 'Trong hàng đợi',
trace_run_completed: 'Chạy hoàn tất',
trace_run_failed: 'Chạy thất bại',
trace_tool_call_started: 'Bắt đầu hành động',
trace_tool_call_completed: 'Kết quả hành động',
},
debugData: {
title: 'Dữ liệu sự kiện',
form: 'Trường thường dùng',
@@ -531,7 +561,7 @@ const viVN = {
'Tạo bộ xử lý có thể tái sử dụng và dùng chúng trong định tuyến sự kiện của bot',
create: 'Tạo bộ xử lý',
editAgent: 'Chỉnh sửa Agent',
selectFromSidebar: 'Chọn một Agent hoặc Pipeline từ thanh bên',
selectFromSidebar: 'Chọn bộ xử lý từ thanh bên',
agentType: 'Agent',
agentTypeDescription:
'Dùng runner để xử lý tin nhắn, thành viên nhóm, bạn bè, phản hồi và các sự kiện nền tảng khác.',
+36 -8
View File
@@ -378,17 +378,15 @@ const zhHans = {
commonScenarios: '常用场景',
dragEventRoute: '拖动第 {{index}} 条路由',
behaviorReplyMessages: '回复收到的消息',
behaviorReplyMessagesDescription:
'把收到的消息交给 Agent 或 Pipeline 处理。',
behaviorReplyMessagesDescription: '把收到的消息交给处理器。',
behaviorWelcomeMembers: '欢迎新成员',
behaviorWelcomeMembersDescription: '有人加入群组时运行 Agent。',
behaviorWelcomeMembersDescription: '有人加入群组时运行处理器。',
behaviorHandleDepartures: '处理成员离群',
behaviorHandleDeparturesDescription: '有人离开或被移出群组时运行 Agent。',
behaviorHandleDeparturesDescription: '有人离开或被移出群组时运行处理器。',
behaviorReviewFriendRequests: '审核好友请求',
behaviorReviewFriendRequestsDescription:
'让 Agent 决定如何处理新的好友请求。',
behaviorReviewFriendRequestsDescription: '把新的好友请求交给处理器。',
behaviorHandleModeration: '处理群管理事件',
behaviorHandleModerationDescription: '群成员受到限制时运行 Agent。',
behaviorHandleModerationDescription: '群成员受到限制时运行处理器。',
behaviorCustom: '配置其他事件',
behaviorCustomDescription: '添加路由,并从此适配器支持的全部事件中选择。',
eventPattern: '事件',
@@ -670,6 +668,36 @@ const zhHans = {
},
},
agents: {
eventProcessor: {
type: '事件处理器',
description: '通过插件代码处理平台事件。',
component: '插件组件',
selectComponent: '选择事件处理器组件',
unavailable: '组件不可用',
noComponents: '尚未安装事件处理器组件。',
installPlugin: '安装插件',
loadError: '无法加载处理器详情。',
refresh: '刷新',
runs: '运行记录',
noRuns: '暂无运行记录,绑定机器人事件后开始处理。',
bindBot: '绑定机器人事件',
trace: '日志与消息流向',
selectRun: '选择一条运行记录查看详情。',
input: '传入事件',
destination: '投递目标',
loadMore: '加载更多',
activation: '安装插件,创建实例,再绑定机器人事件。',
status_pending: '待执行',
status_running: '运行中',
status_completed: '已完成',
status_failed: '失败',
status_cancelled: '已取消',
status_queued: '排队中',
trace_run_completed: '运行完成',
trace_run_failed: '运行失败',
trace_tool_call_started: '开始执行动作',
trace_tool_call_completed: '动作结果',
},
debugData: {
title: '事件数据',
form: '常用字段',
@@ -701,7 +729,7 @@ const zhHans = {
description: '创建可复用的处理器,并在机器人事件路由中使用',
create: '创建处理器',
editAgent: '编辑 Agent',
selectFromSidebar: '从侧边栏选择一个 Agent 或 Pipeline',
selectFromSidebar: '从侧边栏选择一个处理器',
agentType: 'Agent',
agentTypeDescription:
'通过运行器处理消息、群成员、好友、反馈等平台事件。适合需要自主判断、调用工具或响应非消息事件的场景。',
+31 -1
View File
@@ -474,6 +474,36 @@ const zhHant = {
},
},
agents: {
eventProcessor: {
type: '事件處理器',
description: '透過外掛程式碼處理平台事件。',
component: '外掛元件',
selectComponent: '選擇事件處理器元件',
unavailable: '元件無法使用',
noComponents: '尚未安裝事件處理器元件。',
installPlugin: '安裝外掛',
loadError: '無法載入處理器詳情。',
refresh: '重新整理',
runs: '執行記錄',
noRuns: '尚無執行記錄,綁定機器人事件後開始處理。',
bindBot: '綁定機器人事件',
trace: '日誌與訊息流向',
selectRun: '選擇一筆執行記錄查看詳情。',
input: '傳入事件',
destination: '傳送目標',
loadMore: '載入更多',
activation: '安裝外掛、建立實例,再綁定機器人事件。',
status_pending: '待執行',
status_running: '執行中',
status_completed: '已完成',
status_failed: '失敗',
status_cancelled: '已取消',
status_queued: '排隊中',
trace_run_completed: '執行完成',
trace_run_failed: '執行失敗',
trace_tool_call_started: '開始執行動作',
trace_tool_call_completed: '動作結果',
},
debugData: {
title: '事件資料',
form: '常用欄位',
@@ -505,7 +535,7 @@ const zhHant = {
description: '建立可重用的處理器,並在機器人事件路由中使用',
create: '建立處理器',
editAgent: '編輯 Agent',
selectFromSidebar: '從側邊欄選擇一個 Agent 或 Pipeline',
selectFromSidebar: '從側邊欄選擇一個處理器',
agentType: 'Agent',
agentTypeDescription: '透過執行器處理訊息、群成員、好友、回饋等平台事件。',
pipelineType: '流程線',