mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 01:46:07 +00:00
feat(agent-platform): productize bot event route testing
This commit is contained in:
@@ -377,6 +377,8 @@ export default function BotForm({
|
||||
description: form.getValues().description ?? '',
|
||||
adapter: form.getValues().adapter,
|
||||
adapter_config: form.getValues().adapter_config,
|
||||
enable: form.getValues().enable,
|
||||
event_bindings: form.getValues().event_bindings ?? [],
|
||||
};
|
||||
httpClient
|
||||
.createBot(newBot)
|
||||
@@ -444,27 +446,7 @@ export default function BotForm({
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Card 2: Event Routing (edit mode only) */}
|
||||
{initBotId && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('bots.eventRouting')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('bots.eventRoutingDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EventBindingsEditor
|
||||
form={form}
|
||||
botId={initBotId}
|
||||
supportedEvents={adapterSupportedEvents[currentAdapter] || []}
|
||||
agentOptions={agentNameList}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Card 4: Adapter Configuration */}
|
||||
{/* Card 2: Adapter Configuration */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('bots.adapterConfig')}</CardTitle>
|
||||
@@ -668,6 +650,26 @@ export default function BotForm({
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Card 3: Event Routing */}
|
||||
{currentAdapter && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('bots.eventRouting')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('bots.eventRoutingDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EventBindingsEditor
|
||||
form={form}
|
||||
botId={initBotId}
|
||||
supportedEvents={adapterSupportedEvents[currentAdapter] || []}
|
||||
agentOptions={agentNameList}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
Agent,
|
||||
BotRouteDryRunResult,
|
||||
BotEventRouteStatus,
|
||||
BotRouteTestResult,
|
||||
} from '@/app/infra/entities/api';
|
||||
import { backendClient } from '@/app/infra/http';
|
||||
|
||||
@@ -240,11 +241,98 @@ function routeStatusBadgeClass(
|
||||
return 'border-border bg-muted/40 text-muted-foreground';
|
||||
}
|
||||
|
||||
function localizedFailureReason(
|
||||
failureCode: string | null | undefined,
|
||||
fallback: string | null | undefined,
|
||||
t: TFunction,
|
||||
) {
|
||||
if (!failureCode) return fallback || '';
|
||||
const key = `bots.routeFailure.${failureCode}`;
|
||||
const label = t(key);
|
||||
return label === key ? fallback || failureCode : label;
|
||||
}
|
||||
|
||||
function routeStatusDetail(
|
||||
status: BotEventRouteStatus | undefined,
|
||||
t: TFunction,
|
||||
) {
|
||||
if (!status) return '';
|
||||
if (status.failure_code) {
|
||||
return localizedFailureReason(status.failure_code, status.reason, t);
|
||||
}
|
||||
if (status.last_status) {
|
||||
const key = `bots.routeStatusDetail.${status.last_status}`;
|
||||
const label = t(key);
|
||||
if (label !== key) return label;
|
||||
}
|
||||
return status.reason || formatRouteStatusTime(status.timestamp);
|
||||
}
|
||||
|
||||
function diagnosticDetailLabel(
|
||||
detail: Record<string, unknown>,
|
||||
fallbackIndex: number,
|
||||
t: TFunction,
|
||||
) {
|
||||
const order =
|
||||
typeof detail.binding_index === 'number'
|
||||
? detail.binding_index
|
||||
: typeof detail.order === 'number'
|
||||
? detail.order
|
||||
: fallbackIndex;
|
||||
const route = t('bots.dryRunRuleIndex', { index: order + 1 });
|
||||
if (detail.selected) {
|
||||
return t('bots.dryRunDiagnosticSelected', { route });
|
||||
}
|
||||
const failureCode =
|
||||
typeof detail.failure_code === 'string' ? detail.failure_code : null;
|
||||
const reason = localizedFailureReason(
|
||||
failureCode,
|
||||
typeof detail.reason === 'string' ? detail.reason : null,
|
||||
t,
|
||||
);
|
||||
if (detail.matched) {
|
||||
return t('bots.dryRunDiagnosticMatched', { route, reason });
|
||||
}
|
||||
return t('bots.dryRunDiagnosticSkipped', { route, reason });
|
||||
}
|
||||
|
||||
function formatRouteStatusTime(timestamp: number | null | undefined) {
|
||||
if (!timestamp) return '';
|
||||
return new Date(timestamp * 1000).toLocaleString();
|
||||
}
|
||||
|
||||
function samplePayloadForEvent(eventType: string): Record<string, unknown> {
|
||||
if (eventType === 'message.received') {
|
||||
return {
|
||||
message_text: 'Hello',
|
||||
chat_type: 'private',
|
||||
chat_id: 'test-user',
|
||||
user_id: 'test-user',
|
||||
};
|
||||
}
|
||||
if (eventType === 'group.member_joined') {
|
||||
return {
|
||||
group_id: 'test-group',
|
||||
group_name: 'Test Group',
|
||||
user_id: 'test-user',
|
||||
user_name: 'Test User',
|
||||
};
|
||||
}
|
||||
if (eventType === 'group.member_left') {
|
||||
return {
|
||||
group_id: 'test-group',
|
||||
group_name: 'Test Group',
|
||||
user_id: 'test-user',
|
||||
user_name: 'Test User',
|
||||
is_kicked: false,
|
||||
};
|
||||
}
|
||||
if (eventType === 'platform.specific') {
|
||||
return { action: 'test', data: {} };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// ── target combobox (type + target merged, with search + groups) ───────────────
|
||||
|
||||
// Encoded value: "discard", "agent:<uuid>", "pipeline:<uuid>"
|
||||
@@ -639,21 +727,29 @@ function RouteDryRunDialog({
|
||||
bindings,
|
||||
eventOptions,
|
||||
agentOptions,
|
||||
onRouteStatusUpdate,
|
||||
}: {
|
||||
botId?: string;
|
||||
bindings: EventBinding[];
|
||||
eventOptions: string[];
|
||||
agentOptions: Agent[];
|
||||
onRouteStatusUpdate?: (statuses: BotEventRouteStatus[]) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const firstEvent = eventOptions[0] ?? DEFAULT_EVENTS[0];
|
||||
const [open, setOpen] = useState(false);
|
||||
const [eventType, setEventType] = useState(firstEvent);
|
||||
const [payloadText, setPayloadText] = useState('{\n "message_text": ""\n}');
|
||||
const [payloadText, setPayloadText] = useState(() =>
|
||||
JSON.stringify(samplePayloadForEvent(firstEvent), null, 2),
|
||||
);
|
||||
const [advancedPayloadOpen, setAdvancedPayloadOpen] = useState(false);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [isDispatching, setIsDispatching] = useState(false);
|
||||
const [payloadError, setPayloadError] = useState<string | null>(null);
|
||||
const [runError, setRunError] = useState<string | null>(null);
|
||||
const [result, setResult] = useState<BotRouteDryRunResult | null>(null);
|
||||
const [dispatchResult, setDispatchResult] =
|
||||
useState<BotRouteTestResult | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!eventOptions.includes(eventType)) {
|
||||
@@ -661,6 +757,13 @@ function RouteDryRunDialog({
|
||||
}
|
||||
}, [eventOptions, eventType, firstEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
setPayloadText(JSON.stringify(samplePayloadForEvent(eventType), null, 2));
|
||||
setPayloadError(null);
|
||||
setResult(null);
|
||||
setDispatchResult(null);
|
||||
}, [eventType]);
|
||||
|
||||
function resolveTargetName(resultTarget?: BotRouteDryRunResult['target']) {
|
||||
if (!resultTarget) return '';
|
||||
if (resultTarget.target_name) return resultTarget.target_name;
|
||||
@@ -669,11 +772,8 @@ function RouteDryRunDialog({
|
||||
return agent ? targetLabel(agent) : (resultTarget.target_uuid ?? '');
|
||||
}
|
||||
|
||||
async function runDryRun() {
|
||||
function parsePayload(): Record<string, unknown> | null {
|
||||
setPayloadError(null);
|
||||
setRunError(null);
|
||||
setResult(null);
|
||||
|
||||
let payload: Record<string, unknown> = {};
|
||||
if (payloadText.trim()) {
|
||||
try {
|
||||
@@ -684,14 +784,24 @@ function RouteDryRunDialog({
|
||||
Array.isArray(parsed)
|
||||
) {
|
||||
setPayloadError(t('bots.dryRunPayloadObjectError'));
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
payload = parsed as Record<string, unknown>;
|
||||
} catch {
|
||||
setPayloadError(t('bots.dryRunPayloadJsonError'));
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function runDryRun() {
|
||||
setRunError(null);
|
||||
setResult(null);
|
||||
setDispatchResult(null);
|
||||
|
||||
const payload = parsePayload();
|
||||
if (payload === null) return;
|
||||
|
||||
if (!botId) {
|
||||
setRunError(t('bots.dryRunNeedsSavedBot'));
|
||||
@@ -717,6 +827,43 @@ function RouteDryRunDialog({
|
||||
}
|
||||
}
|
||||
|
||||
async function dispatchTestEvent() {
|
||||
setRunError(null);
|
||||
setDispatchResult(null);
|
||||
|
||||
const payload = parsePayload();
|
||||
if (payload === null) return;
|
||||
|
||||
if (!botId) {
|
||||
setRunError(t('bots.dryRunNeedsSavedBot'));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDispatching(true);
|
||||
try {
|
||||
const testResult = await backendClient.testBotEventRoute(botId, {
|
||||
event_type: eventType,
|
||||
payload,
|
||||
});
|
||||
setDispatchResult(testResult);
|
||||
onRouteStatusUpdate?.(testResult.route_status?.routes || []);
|
||||
if (!testResult.dispatched) {
|
||||
setRunError(
|
||||
localizedFailureReason(
|
||||
testResult.failure_code,
|
||||
testResult.reason,
|
||||
t,
|
||||
) || t('bots.routeTestFailed'),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const err = error as { msg?: string };
|
||||
setRunError(err.msg || t('bots.routeTestFailed'));
|
||||
} finally {
|
||||
setIsDispatching(false);
|
||||
}
|
||||
}
|
||||
|
||||
const targetName = result ? resolveTargetName(result.target) : '';
|
||||
|
||||
return (
|
||||
@@ -738,7 +885,7 @@ function RouteDryRunDialog({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-3 sm:grid-cols-[220px_1fr]">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium">
|
||||
{t('bots.dryRunEventType')}
|
||||
@@ -756,23 +903,55 @@ function RouteDryRunDialog({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium">
|
||||
{t('bots.dryRunPayload')}
|
||||
</label>
|
||||
<Textarea
|
||||
value={payloadText}
|
||||
onChange={(e) => setPayloadText(e.target.value)}
|
||||
className="min-h-[118px] font-mono text-xs"
|
||||
spellCheck={false}
|
||||
placeholder='{"message_text": "hello"}'
|
||||
/>
|
||||
{payloadError ? (
|
||||
<p className="text-xs text-destructive">{payloadError}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('bots.dryRunPayloadHint')}
|
||||
</p>
|
||||
<div className="rounded-md border bg-muted/20 px-3 py-2.5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium">
|
||||
{t('bots.dryRunSampleReady')}
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs leading-relaxed text-muted-foreground">
|
||||
{t('bots.dryRunSampleDescription', {
|
||||
event: eventLabel(eventType, t),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 shrink-0 px-2 text-xs"
|
||||
onClick={() => setAdvancedPayloadOpen((value) => !value)}
|
||||
>
|
||||
{advancedPayloadOpen ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{advancedPayloadOpen
|
||||
? t('bots.dryRunHidePayload')
|
||||
: t('bots.dryRunEditPayload')}
|
||||
</Button>
|
||||
</div>
|
||||
{advancedPayloadOpen && (
|
||||
<div className="mt-3 space-y-1.5 border-t pt-3">
|
||||
<label className="text-xs font-medium">
|
||||
{t('bots.dryRunPayload')}
|
||||
</label>
|
||||
<Textarea
|
||||
value={payloadText}
|
||||
onChange={(e) => setPayloadText(e.target.value)}
|
||||
className="min-h-[118px] font-mono text-xs"
|
||||
spellCheck={false}
|
||||
placeholder='{"message_text": "hello"}'
|
||||
/>
|
||||
{payloadError ? (
|
||||
<p className="text-xs text-destructive">{payloadError}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('bots.dryRunPayloadHint')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -832,23 +1011,54 @@ function RouteDryRunDialog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{result.diagnostic_steps.length > 0 && (
|
||||
{(result.diagnostic_details?.length ||
|
||||
result.diagnostic_steps.length > 0) && (
|
||||
<div className="mt-3">
|
||||
<div className="mb-1.5 flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
<ListChecks className="h-3.5 w-3.5" />
|
||||
{t('bots.dryRunDiagnostics')}
|
||||
</div>
|
||||
<ol className="space-y-1 pl-5 text-xs text-muted-foreground">
|
||||
{result.diagnostic_steps.map((step, index) => (
|
||||
<li key={`${index}:${step}`} className="list-decimal">
|
||||
{step}
|
||||
</li>
|
||||
))}
|
||||
{result.diagnostic_details?.length
|
||||
? result.diagnostic_details.map((detail, index) => (
|
||||
<li
|
||||
key={`${index}:${String(detail.binding_id ?? '')}`}
|
||||
className="list-decimal"
|
||||
>
|
||||
{diagnosticDetailLabel(detail, index, t)}
|
||||
</li>
|
||||
))
|
||||
: result.diagnostic_steps.map((step, index) => (
|
||||
<li
|
||||
key={`${index}:${step}`}
|
||||
className="list-decimal"
|
||||
>
|
||||
{step}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dispatchResult?.dispatched && (
|
||||
<Alert>
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
{t('bots.routeTestDispatched', {
|
||||
count: dispatchResult.suppressed_outputs?.length || 0,
|
||||
})}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Alert className="border-amber-200 bg-amber-50/60 text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/20 dark:text-amber-200">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
{t('bots.routeTestSideEffectWarning')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -859,10 +1069,25 @@ function RouteDryRunDialog({
|
||||
>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
<Button type="button" onClick={runDryRun} disabled={isRunning}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={runDryRun}
|
||||
disabled={isRunning || isDispatching}
|
||||
>
|
||||
<Play className="h-4 w-4 mr-1" />
|
||||
{isRunning ? t('bots.dryRunRunning') : t('bots.dryRunAction')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={dispatchTestEvent}
|
||||
disabled={isRunning || isDispatching}
|
||||
>
|
||||
<Activity className="h-4 w-4 mr-1" />
|
||||
{isDispatching
|
||||
? t('bots.routeTestRunning')
|
||||
: t('bots.routeTestAction')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -905,6 +1130,7 @@ function BindingCardContent({
|
||||
const filterCount = (binding.filters as FilterRow[] | undefined)?.length ?? 0;
|
||||
const pipelineAllowed = isMessageEventPattern(binding.event_pattern);
|
||||
const statusTime = formatRouteStatusTime(routeStatus?.timestamp);
|
||||
const statusDetail = routeStatusDetail(routeStatus, t);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border bg-card">
|
||||
@@ -1002,9 +1228,11 @@ function BindingCardContent({
|
||||
</Badge>
|
||||
<span
|
||||
className="max-w-full truncate text-[11px] text-muted-foreground"
|
||||
title={routeStatus?.reason || routeStatus?.message || statusTime}
|
||||
title={
|
||||
statusTime ? `${statusDetail} · ${statusTime}` : statusDetail
|
||||
}
|
||||
>
|
||||
{routeStatus?.failure_code || routeStatus?.reason || statusTime}
|
||||
{statusDetail || statusTime}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1325,6 +1553,7 @@ export default function EventBindingsEditor({
|
||||
bindings={bindings}
|
||||
eventOptions={dryRunEventOptions}
|
||||
agentOptions={agentOptions}
|
||||
onRouteStatusUpdate={setRouteStatuses}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -252,6 +252,11 @@ export interface BotRouteDryRunRequest {
|
||||
event_bindings?: EventBinding[];
|
||||
}
|
||||
|
||||
export interface BotRouteTestRequest {
|
||||
event_type: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface BotRouteDryRunTarget {
|
||||
target_type: EventBinding['target_type'];
|
||||
target_uuid?: string | null;
|
||||
@@ -306,6 +311,17 @@ export interface BotEventRouteStatusResponse {
|
||||
stale_routes: BotEventRouteStatus[];
|
||||
}
|
||||
|
||||
export interface BotRouteTestResult {
|
||||
dispatched: boolean;
|
||||
event_type: string;
|
||||
status?: BotEventRouteStatus['last_status'];
|
||||
binding_id?: string | null;
|
||||
failure_code?: string | null;
|
||||
reason?: string | null;
|
||||
suppressed_outputs: Array<Record<string, unknown>>;
|
||||
route_status: BotEventRouteStatusResponse;
|
||||
}
|
||||
|
||||
export interface ApiRespKnowledgeBases {
|
||||
bases: KnowledgeBase[];
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ import {
|
||||
ApiRespSkill,
|
||||
BotRouteDryRunRequest,
|
||||
BotRouteDryRunResult,
|
||||
BotRouteTestRequest,
|
||||
BotRouteTestResult,
|
||||
BotEventRouteStatusResponse,
|
||||
} from '@/app/infra/entities/api';
|
||||
import { Plugin } from '@/app/infra/entities/plugin';
|
||||
@@ -473,6 +475,16 @@ export class BackendClient extends BaseHttpClient {
|
||||
return this.get(`/api/v1/platform/bots/${botId}/event-routes/status`);
|
||||
}
|
||||
|
||||
public testBotEventRoute(
|
||||
botId: string,
|
||||
request: BotRouteTestRequest,
|
||||
): Promise<BotRouteTestResult> {
|
||||
return this.post(
|
||||
`/api/v1/platform/bots/${botId}/event-routes/test`,
|
||||
request,
|
||||
);
|
||||
}
|
||||
|
||||
public deleteBot(uuid: string): Promise<object> {
|
||||
return this.delete(`/api/v1/platform/bots/${uuid}`);
|
||||
}
|
||||
|
||||
@@ -401,19 +401,53 @@ const enUS = {
|
||||
discarded: 'Discarded',
|
||||
failed: 'Failed',
|
||||
not_matched: 'Not matched',
|
||||
test_started: 'Testing',
|
||||
},
|
||||
routeStatusDetail: {
|
||||
matched: 'This route matched the event.',
|
||||
delivered: 'The processor received the event.',
|
||||
discarded: 'The event was intentionally discarded.',
|
||||
failed: 'The route could not finish.',
|
||||
not_matched: 'No configured route matched the event.',
|
||||
test_started: 'The saved route is running.',
|
||||
},
|
||||
routeFailure: {
|
||||
binding_disabled: 'This route is disabled.',
|
||||
event_pattern_mismatch: 'The event does not match this route.',
|
||||
filters_mismatch: 'The sample data does not meet the route conditions.',
|
||||
lower_priority: 'Another matching route has higher priority.',
|
||||
route_not_found: 'No route matched this event.',
|
||||
processor_incompatible:
|
||||
'The selected processor cannot handle this event.',
|
||||
processor_not_found: 'The selected processor is unavailable.',
|
||||
processor_disabled: 'The selected processor is disabled.',
|
||||
runner_failed: 'The Agent runner failed while processing the event.',
|
||||
delivery_failed: 'The processor finished, but delivery failed.',
|
||||
},
|
||||
routeTestAction: 'Run saved route',
|
||||
routeTestRunning: 'Running…',
|
||||
routeTestFailed: 'Failed to run the saved route. Try again later.',
|
||||
routeTestDispatched:
|
||||
'The saved route ran successfully. {{count}} platform actions were blocked.',
|
||||
routeTestSideEffectWarning:
|
||||
'Running the saved route executes its processor. Platform messaging actions are blocked, but tools and external services may still have side effects.',
|
||||
dryRunTitle: 'Test event route',
|
||||
dryRunDescription:
|
||||
'Choose an event and provide a sample payload to see which processor the current route setup will use.',
|
||||
'Preview the current form without running a processor, or run the saved route to validate the live configuration.',
|
||||
dryRunEventType: 'Event type',
|
||||
dryRunPayload: 'Sample payload JSON',
|
||||
dryRunSampleReady: 'Sample event is ready',
|
||||
dryRunSampleDescription:
|
||||
'LangBot prepared example data for {{event}}. Most route tests can use it as-is.',
|
||||
dryRunEditPayload: 'Edit advanced data',
|
||||
dryRunHidePayload: 'Hide advanced data',
|
||||
dryRunPayload: 'Advanced event data (JSON)',
|
||||
dryRunPayloadHint:
|
||||
'Use simple fields such as message_text, chat_type, and chat_id to test conditions.',
|
||||
dryRunPayloadJsonError: 'Enter valid JSON.',
|
||||
dryRunPayloadObjectError: 'Payload must be a JSON object.',
|
||||
dryRunNeedsSavedBot: 'Save the bot before testing routes.',
|
||||
dryRunFailed: 'Route test failed. Try again later.',
|
||||
dryRunAction: 'Run test',
|
||||
dryRunAction: 'Preview route',
|
||||
dryRunRunning: 'Testing…',
|
||||
dryRunMatched: 'Route matched',
|
||||
dryRunNotMatched: 'No route matched',
|
||||
@@ -423,6 +457,9 @@ const enUS = {
|
||||
dryRunRuleIndex: 'Route {{index}}',
|
||||
dryRunNoRule: 'No matched rule',
|
||||
dryRunDiagnostics: 'Diagnostic steps',
|
||||
dryRunDiagnosticSelected: '{{route}} was selected.',
|
||||
dryRunDiagnosticMatched: '{{route}} matched. {{reason}}',
|
||||
dryRunDiagnosticSkipped: '{{route}} was skipped. {{reason}}',
|
||||
eventCustom: 'Custom event',
|
||||
eventWildcard: 'All events',
|
||||
eventNamespaceWildcard: '{{namespace}}.*',
|
||||
|
||||
@@ -408,12 +408,47 @@ const jaJP = {
|
||||
discarded: '破棄済み',
|
||||
failed: '失敗',
|
||||
not_matched: '未一致',
|
||||
test_started: 'テスト中',
|
||||
},
|
||||
routeStatusDetail: {
|
||||
matched: 'このルートがイベントに一致しました。',
|
||||
delivered: 'プロセッサーがイベントを受信しました。',
|
||||
discarded: '設定に従ってイベントを破棄しました。',
|
||||
failed: 'ルートを完了できませんでした。',
|
||||
not_matched: '設定済みルートに一致しませんでした。',
|
||||
test_started: '保存済みルートを実行しています。',
|
||||
},
|
||||
routeFailure: {
|
||||
binding_disabled: 'このルートは無効です。',
|
||||
event_pattern_mismatch: 'イベントがこのルートに一致しません。',
|
||||
filters_mismatch: 'サンプルデータがルート条件を満たしていません。',
|
||||
lower_priority: '別の一致ルートの優先度が高く設定されています。',
|
||||
route_not_found: 'このイベントに一致するルートがありません。',
|
||||
processor_incompatible:
|
||||
'選択したプロセッサーはこのイベントを処理できません。',
|
||||
processor_not_found: '選択したプロセッサーを利用できません。',
|
||||
processor_disabled: '選択したプロセッサーは無効です。',
|
||||
runner_failed: 'Agent Runner がイベント処理中に失敗しました。',
|
||||
delivery_failed: '処理は完了しましたが、結果の配信に失敗しました。',
|
||||
},
|
||||
routeTestAction: '保存済みルートを実行',
|
||||
routeTestRunning: '実行中…',
|
||||
routeTestFailed:
|
||||
'保存済みルートの実行に失敗しました。後でもう一度お試しください。',
|
||||
routeTestDispatched:
|
||||
'保存済みルートを実行しました。{{count}} 件のプラットフォーム操作を抑制しました。',
|
||||
routeTestSideEffectWarning:
|
||||
'保存済みルートを実行するとプロセッサーが動作します。プラットフォームのメッセージ操作は抑制されますが、ツールや外部サービスには副作用が生じる場合があります。',
|
||||
dryRunTitle: 'イベントルートをテスト',
|
||||
dryRunDescription:
|
||||
'イベントとサンプルペイロードを指定し、現在のルート設定でどのプロセッサーに送られるか確認します。',
|
||||
'プロセッサーを実行せずに現在のフォームをプレビューするか、保存済みルートを実行して設定を検証します。',
|
||||
dryRunEventType: 'イベントタイプ',
|
||||
dryRunPayload: 'サンプルペイロード JSON',
|
||||
dryRunSampleReady: 'サンプルイベントを準備しました',
|
||||
dryRunSampleDescription:
|
||||
'LangBot が「{{event}}」用のサンプルデータを準備しました。通常はそのままテストできます。',
|
||||
dryRunEditPayload: '詳細データを編集',
|
||||
dryRunHidePayload: '詳細データを閉じる',
|
||||
dryRunPayload: '詳細イベントデータ(JSON)',
|
||||
dryRunPayloadHint:
|
||||
'message_text、chat_type、chat_id などの簡単なフィールドで条件をテストできます。',
|
||||
dryRunPayloadJsonError: '有効な JSON を入力してください。',
|
||||
@@ -421,7 +456,7 @@ const jaJP = {
|
||||
'ペイロードは JSON オブジェクトである必要があります。',
|
||||
dryRunNeedsSavedBot: 'ルートをテストする前にボットを保存してください。',
|
||||
dryRunFailed: 'ルートテストに失敗しました。後でもう一度お試しください。',
|
||||
dryRunAction: 'テスト実行',
|
||||
dryRunAction: 'ルートをプレビュー',
|
||||
dryRunRunning: 'テスト中…',
|
||||
dryRunMatched: 'ルートに一致しました',
|
||||
dryRunNotMatched: '一致するルートはありません',
|
||||
@@ -431,6 +466,9 @@ const jaJP = {
|
||||
dryRunRuleIndex: 'ルート {{index}}',
|
||||
dryRunNoRule: '一致したルールなし',
|
||||
dryRunDiagnostics: '診断ステップ',
|
||||
dryRunDiagnosticSelected: '{{route}}を選択しました。',
|
||||
dryRunDiagnosticMatched: '{{route}}が一致しました。{{reason}}',
|
||||
dryRunDiagnosticSkipped: '{{route}}をスキップしました。{{reason}}',
|
||||
eventCustom: 'カスタムイベント',
|
||||
eventWildcard: 'すべてのイベント',
|
||||
eventNamespaceWildcard: '{{namespace}}.*',
|
||||
|
||||
@@ -386,19 +386,51 @@ const zhHans = {
|
||||
discarded: '已丢弃',
|
||||
failed: '失败',
|
||||
not_matched: '未命中',
|
||||
test_started: '测试中',
|
||||
},
|
||||
routeStatusDetail: {
|
||||
matched: '此路由已命中事件。',
|
||||
delivered: '处理器已收到事件。',
|
||||
discarded: '此事件已按路由配置丢弃。',
|
||||
failed: '此路由未能完成。',
|
||||
not_matched: '没有已配置路由命中此事件。',
|
||||
test_started: '正在运行已保存路由。',
|
||||
},
|
||||
routeFailure: {
|
||||
binding_disabled: '此路由已禁用。',
|
||||
event_pattern_mismatch: '事件与此路由不匹配。',
|
||||
filters_mismatch: '示例数据不满足路由条件。',
|
||||
lower_priority: '另一条命中路由的优先级更高。',
|
||||
route_not_found: '没有路由命中此事件。',
|
||||
processor_incompatible: '所选处理器无法处理此事件。',
|
||||
processor_not_found: '所选处理器不可用。',
|
||||
processor_disabled: '所选处理器已禁用。',
|
||||
runner_failed: 'Agent Runner 处理事件时失败。',
|
||||
delivery_failed: '处理器已完成,但结果投递失败。',
|
||||
},
|
||||
routeTestAction: '运行已保存路由',
|
||||
routeTestRunning: '运行中…',
|
||||
routeTestFailed: '运行已保存路由失败,请稍后重试。',
|
||||
routeTestDispatched: '已保存路由运行成功,{{count}} 个平台操作已被阻止。',
|
||||
routeTestSideEffectWarning:
|
||||
'运行已保存路由会执行处理器。平台消息操作会被阻止,但工具和外部服务仍可能产生副作用。',
|
||||
dryRunTitle: '测试事件路由',
|
||||
dryRunDescription:
|
||||
'选择一个事件并提供示例载荷,检查当前路由配置会命中哪个处理器。',
|
||||
'可以先预览当前表单而不运行处理器,也可以运行已保存路由来验证线上配置。',
|
||||
dryRunEventType: '事件类型',
|
||||
dryRunPayload: '示例载荷 JSON',
|
||||
dryRunSampleReady: '示例事件已准备好',
|
||||
dryRunSampleDescription:
|
||||
'LangBot 已为“{{event}}”准备示例数据,大多数路由测试可直接使用。',
|
||||
dryRunEditPayload: '编辑高级数据',
|
||||
dryRunHidePayload: '收起高级数据',
|
||||
dryRunPayload: '高级事件数据(JSON)',
|
||||
dryRunPayloadHint:
|
||||
'可填写 message_text、chat_type、chat_id 等简单字段,用于匹配触发条件。',
|
||||
dryRunPayloadJsonError: '请输入合法 JSON。',
|
||||
dryRunPayloadObjectError: '载荷必须是 JSON 对象。',
|
||||
dryRunNeedsSavedBot: '请先保存机器人后再测试路由。',
|
||||
dryRunFailed: '路由测试失败,请稍后重试。',
|
||||
dryRunAction: '开始测试',
|
||||
dryRunAction: '预览路由',
|
||||
dryRunRunning: '测试中…',
|
||||
dryRunMatched: '已命中路由',
|
||||
dryRunNotMatched: '未命中路由',
|
||||
@@ -408,6 +440,9 @@ const zhHans = {
|
||||
dryRunRuleIndex: '第 {{index}} 条路由',
|
||||
dryRunNoRule: '无命中规则',
|
||||
dryRunDiagnostics: '诊断步骤',
|
||||
dryRunDiagnosticSelected: '已选择{{route}}。',
|
||||
dryRunDiagnosticMatched: '{{route}}已命中。{{reason}}',
|
||||
dryRunDiagnosticSkipped: '{{route}}已跳过。{{reason}}',
|
||||
eventCustom: '自定义事件',
|
||||
eventWildcard: '全部事件',
|
||||
eventNamespaceWildcard: '{{namespace}}.*',
|
||||
|
||||
Reference in New Issue
Block a user