mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-21 16:05:19 +08:00
fix(acceptance): improve bot setup and execution log presentation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Brain, ChevronRight, RefreshCw, Wrench } from 'lucide-react';
|
||||
import { ChevronRight, RefreshCw } from 'lucide-react';
|
||||
import AgentRunTimeline, { isReplyStep, isMockStep } from './AgentRunTimeline';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import type {
|
||||
AgentPlatformTool,
|
||||
@@ -206,7 +207,12 @@ export default function AgentMonitoringTab({
|
||||
const labels = Object.fromEntries(
|
||||
platformTools.map((tool) => [tool.name, extractI18nObject(tool.label)]),
|
||||
);
|
||||
const steps = executionSteps(events);
|
||||
const steps = executionSteps(events).filter(
|
||||
(step) => step.kind === 'tool' || step.text.trim() || step.reasoning.trim(),
|
||||
);
|
||||
const toolSteps = steps.filter((step) => step.kind === 'tool');
|
||||
const sentCount = toolSteps.filter((step) => isReplyStep(step) && !isMockStep(step) && step.status === 'completed').length;
|
||||
const sendFailed = toolSteps.some((step) => isReplyStep(step) && step.status === 'failed');
|
||||
const duration = selected ? processorRunDuration(selected) : null;
|
||||
const failureReason =
|
||||
selected?.status_reason ||
|
||||
@@ -261,7 +267,7 @@ export default function AgentMonitoringTab({
|
||||
<AlertDescription>{t('monitoring.loadError')}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<div className="grid min-h-0 flex-1 gap-4 lg:grid-cols-[minmax(16rem,0.8fr)_minmax(0,2fr)]">
|
||||
<div className="grid min-h-0 flex-1 gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
||||
<section
|
||||
className="flex min-h-0 min-w-0 flex-col gap-2"
|
||||
aria-label={t('agents.eventProcessor.runs')}
|
||||
@@ -279,13 +285,14 @@ export default function AgentMonitoringTab({
|
||||
variant="ghost"
|
||||
aria-pressed={selectedId === run.run_id}
|
||||
onClick={() => setSelectedId(run.run_id)}
|
||||
className="h-auto w-full flex-col items-stretch gap-2 rounded-none border-b p-3 text-left font-normal last:border-b-0 aria-pressed:bg-accent"
|
||||
className="h-auto w-full flex-col items-stretch gap-2 rounded-none border-b border-l-2 border-l-transparent p-4 text-left font-normal last:border-b-0 aria-pressed:border-l-primary aria-pressed:bg-primary/5"
|
||||
>
|
||||
<span className="flex items-center justify-between gap-2">
|
||||
<span className="truncate font-medium">
|
||||
{eventPatternLabel(run.metadata.event_type ?? '', t)}
|
||||
</span>
|
||||
<Badge
|
||||
status={run.status}
|
||||
variant={
|
||||
failedStatuses.has(run.status) ? 'destructive' : 'outline'
|
||||
}
|
||||
@@ -303,7 +310,7 @@ export default function AgentMonitoringTab({
|
||||
{new Date(run.created_at * 1000).toLocaleString()}
|
||||
</time>
|
||||
{debugRun(run) && (
|
||||
<Badge variant="secondary">{t('agents.debugTab')}</Badge>
|
||||
<Badge status="debug">{t('agents.debugTab')}</Badge>
|
||||
)}
|
||||
{processorRunDuration(run) !== null && (
|
||||
<span>{formatRunDuration(processorRunDuration(run)!)}</span>
|
||||
@@ -343,6 +350,7 @@ export default function AgentMonitoringTab({
|
||||
{eventPatternLabel(selected.metadata.event_type ?? '', t)}
|
||||
</h2>
|
||||
<Badge
|
||||
status={selected.status}
|
||||
variant={
|
||||
failedStatuses.has(selected.status)
|
||||
? 'destructive'
|
||||
@@ -352,7 +360,7 @@ export default function AgentMonitoringTab({
|
||||
{stateLabel(selected)}
|
||||
</Badge>
|
||||
{debugRun(selected) && (
|
||||
<Badge variant="secondary">{t('agents.debugTab')}</Badge>
|
||||
<Badge status="debug">{t('agents.debugTab')}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
||||
@@ -373,9 +381,22 @@ export default function AgentMonitoringTab({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="break-all text-xs text-muted-foreground">
|
||||
{selected.runner_id}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 divide-y rounded-lg border bg-muted/20 sm:grid-cols-3 sm:divide-x sm:divide-y-0">
|
||||
<div className="space-y-1 p-4">
|
||||
<p className="text-xs text-muted-foreground">{t('agents.monitoring.runStatus')}</p>
|
||||
<p className="text-sm font-semibold">{stateLabel(selected)}</p>
|
||||
</div>
|
||||
<div className="space-y-1 p-4">
|
||||
<p className="text-xs text-muted-foreground">{t('agents.monitoring.toolCalls')}</p>
|
||||
<p className="text-sm font-semibold tabular-nums">{toolSteps.length}</p>
|
||||
</div>
|
||||
<div className="space-y-1 p-4">
|
||||
<p className="text-xs text-muted-foreground">{t('agents.monitoring.delivery')}</p>
|
||||
<p className={`text-sm font-semibold ${sendFailed ? 'text-destructive' : ''}`}>
|
||||
{t(`agents.monitoring.${sendFailed ? 'sendFailed' : sentCount ? 'sendCount' : 'noSend'}`, { count: sentCount })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{failedStatuses.has(selected.status) && failureReason && (
|
||||
<Alert variant="destructive">
|
||||
@@ -418,72 +439,14 @@ export default function AgentMonitoringTab({
|
||||
</Card>
|
||||
<h3 className="text-sm font-medium">
|
||||
{t('agents.monitoring.execution')}
|
||||
<span className="ml-2 text-muted-foreground">{steps.length}</span>
|
||||
</h3>
|
||||
{steps.map((step, index) => (
|
||||
<Card key={index} className="gap-2 py-3">
|
||||
<CardHeader className="px-3">
|
||||
<CardTitle className="flex min-w-0 items-center gap-2 text-sm">
|
||||
{step.kind === 'tool' ? (
|
||||
<>
|
||||
<Wrench className="size-4 shrink-0" />
|
||||
<span className="break-all">
|
||||
{labels[step.name] || step.name}
|
||||
</span>
|
||||
<Badge
|
||||
variant={
|
||||
step.status === 'failed'
|
||||
? 'destructive'
|
||||
: 'outline'
|
||||
}
|
||||
>
|
||||
{t(
|
||||
`agents.debugTool${step.status === 'running' ? (activeStatuses.has(selected.status) || eventCursor !== null ? 'Running' : 'Interrupted') : step.result && typeof step.result === 'object' && 'mock' in step.result && step.result.mock === true ? (step.status === 'failed' ? 'MockFailed' : 'Simulated') : step.status === 'failed' ? 'Failed' : 'Completed'}`,
|
||||
)}
|
||||
</Badge>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Brain className="size-4" />
|
||||
{t('agents.debugTextOutput')}
|
||||
</>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 px-3">
|
||||
{step.kind === 'message' ? (
|
||||
<>
|
||||
{step.text && (
|
||||
<p className={textClass}>{step.text}</p>
|
||||
)}
|
||||
{step.reasoning && (
|
||||
<ProcessorPayload
|
||||
title={t('agents.debugReasoning')}
|
||||
value={step.reasoning}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{step.error && (
|
||||
<p className={`${textClass} text-destructive`}>
|
||||
{step.error}
|
||||
</p>
|
||||
)}
|
||||
<ProcessorPayload
|
||||
title={t('agents.debugToolArguments')}
|
||||
value={step.parameters}
|
||||
/>
|
||||
{step.result !== undefined && (
|
||||
<ProcessorPayload
|
||||
title={t('agents.debugToolResult')}
|
||||
value={step.result}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
{!steps.length && <p className="rounded-lg border border-dashed p-6 text-sm text-muted-foreground">{t('agents.monitoring.noSteps')}</p>}
|
||||
<AgentRunTimeline
|
||||
steps={steps}
|
||||
labels={labels}
|
||||
active={activeStatuses.has(selected.status) || eventCursor !== null}
|
||||
/>
|
||||
{events
|
||||
.filter((event) => event.type === 'processor.log')
|
||||
.map((event) => (
|
||||
@@ -520,6 +483,7 @@ export default function AgentMonitoringTab({
|
||||
>
|
||||
{json({
|
||||
run_id: selected.run_id,
|
||||
runner_id: selected.runner_id,
|
||||
usage: selected.usage,
|
||||
events,
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MessageSquare, Send, Wrench } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ProcessorPayload } from './PluginProcessorTrace';
|
||||
import type { ExecutionStep } from './debug-execution';
|
||||
import { toolDisplay, toolSummary } from './tool-summary';
|
||||
|
||||
export function isReplyStep(step: ExecutionStep) {
|
||||
return step.kind === 'tool' &&
|
||||
toolDisplay(step.name)?.kind === 'reply';
|
||||
}
|
||||
|
||||
export function isMockStep(step: ExecutionStep) {
|
||||
return step.kind === 'tool' && !!step.result &&
|
||||
typeof step.result === 'object' && 'mock' in step.result && step.result.mock === true;
|
||||
}
|
||||
|
||||
export default function AgentRunTimeline({ steps, labels, active }: {
|
||||
steps: ExecutionStep[];
|
||||
labels: Record<string, string>;
|
||||
active: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ol className="space-y-0">
|
||||
{steps.map((step, index) => {
|
||||
const reply = isReplyStep(step);
|
||||
const mock = isMockStep(step);
|
||||
const params = step.kind === 'tool' && step.parameters && typeof step.parameters === 'object'
|
||||
? step.parameters as Record<string, unknown> : {};
|
||||
const summary = toolSummary(step.kind === 'tool' ? step.name : '', params);
|
||||
const replyText = reply ? summary.text : '';
|
||||
const actionText = reply ? '' : summary.text;
|
||||
const Icon = step.kind === 'message' ? MessageSquare : reply ? Send : Wrench;
|
||||
const status = step.kind === 'tool' ? step.status : null;
|
||||
const statusKey = status === 'running' ? (active ? 'Running' : 'Interrupted')
|
||||
: mock ? (status === 'failed' ? 'MockFailed' : 'Simulated')
|
||||
: status === 'failed' ? 'Failed' : 'Completed';
|
||||
return (
|
||||
<li key={step.kind === 'tool' ? step.id : `message-${index}`} className="relative flex min-w-0 gap-3 pb-5 last:pb-0">
|
||||
{index < steps.length - 1 && <span className="absolute bottom-0 left-4 top-8 w-px bg-border" aria-hidden="true" />}
|
||||
<span className="relative flex size-8 shrink-0 items-center justify-center rounded-full border bg-background text-xs font-medium text-muted-foreground">{index + 1}</span>
|
||||
<div className="min-w-0 flex-1 rounded-lg border bg-background">
|
||||
<div className="flex flex-wrap items-center gap-2 border-b bg-muted/30 px-4 py-3">
|
||||
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
||||
<h4 className="min-w-0 flex-1 break-words text-sm font-medium">
|
||||
{step.kind === 'message' ? t('agents.debugTextOutput') : labels[step.name] || step.name}
|
||||
</h4>
|
||||
{status && <Badge status={status === 'failed' ? 'failed' : mock ? 'simulated' : status === 'running' && !active ? 'cancelled' : status}>{t(`agents.debugTool${statusKey}`)}</Badge>}
|
||||
</div>
|
||||
<div className="space-y-3 p-4 text-sm">
|
||||
{step.kind === 'message' ? <>
|
||||
{step.text && <p className="whitespace-pre-wrap break-words leading-7 [overflow-wrap:anywhere]">{step.text}</p>}
|
||||
{step.reasoning && <ProcessorPayload title={t('agents.debugReasoning')} value={step.reasoning} />}
|
||||
</> : <>
|
||||
{replyText && <p className="whitespace-pre-wrap break-words leading-7 [overflow-wrap:anywhere]">{replyText}</p>}
|
||||
{!reply && actionText && <p className="whitespace-pre-wrap break-words rounded-md bg-muted/50 p-3 font-mono text-xs leading-6 [overflow-wrap:anywhere]">{actionText}</p>}
|
||||
{summary.recipient && <p className="break-all text-xs text-muted-foreground">{t('agents.monitoring.recipient')}: {summary.recipient}</p>}
|
||||
{step.error && <p role="alert" className="whitespace-pre-wrap break-words text-destructive">{step.error}</p>}
|
||||
{status === 'completed' && (mock || !reply) && <p className="text-xs text-muted-foreground">{t(`agents.monitoring.${mock ? 'simulatedAction' : 'actionCompleted'}`)}</p>}
|
||||
{(step.parameters != null || step.result != null) && <div className="rounded-md border border-dashed text-muted-foreground">
|
||||
{step.parameters != null && <ProcessorPayload title={t('agents.debugToolArguments')} value={step.parameters} />}
|
||||
{step.result != null && <ProcessorPayload title={t('agents.debugToolResult')} value={step.result} />}
|
||||
</div>}
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export default function PluginProcessorTrace({
|
||||
variant={event.data.level === 'error' ? 'destructive' : 'default'}
|
||||
>
|
||||
<AlertDescription className="flex min-w-0 items-start gap-2">
|
||||
<Badge variant="outline">{String(event.data.level)}</Badge>
|
||||
<Badge status={String(event.data.level)}>{String(event.data.level)}</Badge>
|
||||
<span className="min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]">
|
||||
{String(event.data.text)}
|
||||
</span>
|
||||
|
||||
@@ -54,6 +54,7 @@ export default function ProcessorRunList({
|
||||
</span>
|
||||
<span className="flex shrink-0 flex-col items-end gap-1">
|
||||
<Badge
|
||||
status={run.status}
|
||||
variant={
|
||||
run.status === 'failed' || run.status === 'timeout'
|
||||
? 'destructive'
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
type ToolDisplay = {
|
||||
fields: readonly string[];
|
||||
kind?: 'reply';
|
||||
recipient?: string;
|
||||
};
|
||||
|
||||
// Explicit display declarations for built-in tools. Unknown tools have no
|
||||
// summary: their parameters remain available in the payload disclosure.
|
||||
const TOOL_DISPLAYS: Readonly<Record<string, ToolDisplay>> = {
|
||||
exec: { fields: ['command'] },
|
||||
read: { fields: ['path'] },
|
||||
write: { fields: ['path'] },
|
||||
edit: { fields: ['path'] },
|
||||
glob: { fields: ['pattern', 'path'] },
|
||||
grep: { fields: ['pattern', 'path'] },
|
||||
event_reply: { fields: ['text'], kind: 'reply' },
|
||||
platform_send_message: { fields: ['text'], kind: 'reply', recipient: 'target_id' },
|
||||
event_delete_message: { fields: [] },
|
||||
event_get_actor: { fields: [] },
|
||||
event_get_group: { fields: [] },
|
||||
event_get_group_member: { fields: [] },
|
||||
event_mute_member: { fields: ['duration'] },
|
||||
event_unmute_member: { fields: [] },
|
||||
event_kick_member: { fields: [] },
|
||||
event_respond_friend_request: { fields: ['approve', 'remark'] },
|
||||
event_respond_group_invite: { fields: ['approve'] },
|
||||
platform_get_message: { fields: ['chat_id', 'message_id'] },
|
||||
platform_delete_message: { fields: ['chat_id', 'message_id'] },
|
||||
platform_get_group_info: { fields: ['group_id'] },
|
||||
platform_get_group_list: { fields: [] },
|
||||
platform_get_group_member_list: { fields: ['group_id'] },
|
||||
platform_get_group_member_info: { fields: ['group_id', 'user_id'] },
|
||||
platform_set_group_name: { fields: ['group_id', 'name'] },
|
||||
platform_mute_member: { fields: ['group_id', 'user_id', 'duration'] },
|
||||
platform_unmute_member: { fields: ['group_id', 'user_id'] },
|
||||
platform_kick_member: { fields: ['group_id', 'user_id'] },
|
||||
platform_leave_group: { fields: ['group_id'] },
|
||||
platform_get_user_info: { fields: ['user_id'] },
|
||||
platform_get_friend_list: { fields: [] },
|
||||
};
|
||||
|
||||
export function toolDisplay(name: string): ToolDisplay | undefined {
|
||||
return Object.prototype.hasOwnProperty.call(TOOL_DISPLAYS, name)
|
||||
? TOOL_DISPLAYS[name] : undefined;
|
||||
}
|
||||
|
||||
export function toolSummary(name: string, parameters: Record<string, unknown>) {
|
||||
const display = toolDisplay(name);
|
||||
const fields = (display?.fields ?? []).flatMap((field) => {
|
||||
const value = parameters[field];
|
||||
if (!['string', 'number', 'boolean'].includes(typeof value) || value === '') return [];
|
||||
return [{ field, text: String(value) }];
|
||||
});
|
||||
return {
|
||||
text: fields.map(({ field, text }) =>
|
||||
display?.kind === 'reply' || display?.fields.length === 1 ? text : `${field}: ${text}`,
|
||||
).join('\n'),
|
||||
recipient: display?.recipient && typeof parameters[display.recipient] === 'string'
|
||||
? String(parameters[display.recipient]) : '',
|
||||
};
|
||||
}
|
||||
@@ -235,11 +235,11 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
<Button
|
||||
type="submit"
|
||||
form="bot-form"
|
||||
disabled={!formDirty}
|
||||
disabled={!enableLoaded || (!formDirty && botEnabled)}
|
||||
className={activeTab !== 'config' ? 'invisible' : ''}
|
||||
data-guide="bot-config-save"
|
||||
>
|
||||
{t('common.save')}
|
||||
{t(botEnabled ? 'common.save' : 'bots.saveAndEnable')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Webhook,
|
||||
Search,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import EventBindingsEditor from './EventBindingsEditor';
|
||||
@@ -305,6 +306,14 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
() => groupByCategory(activeAdapters),
|
||||
[activeAdapters],
|
||||
);
|
||||
const [adapterSearch, setAdapterSearch] = useState('');
|
||||
const adapterQuery = adapterSearch.trim().toLocaleLowerCase();
|
||||
const matchesAdapterSearch = (adapter: IChooseAdapterEntity) =>
|
||||
`${adapter.label} ${adapter.value} ${adapterDescriptionList[adapter.value] ?? ''}`
|
||||
.toLocaleLowerCase()
|
||||
.includes(adapterQuery);
|
||||
const galleryGroups = groupByCategory(activeAdapters.filter(matchesAdapterSearch));
|
||||
const galleryLegacyAdapters = legacyAdapters.filter(matchesAdapterSearch);
|
||||
|
||||
// Whether the collapsed legacy adapter group is expanded in the Select.
|
||||
const [showLegacyAdapters, setShowLegacyAdapters] = useState(false);
|
||||
@@ -537,6 +546,17 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
}
|
||||
|
||||
function selectAdapter(adapterName: string) {
|
||||
if (!form.getValues('name')?.trim()) {
|
||||
const selectedAdapter = adapterNameList.find(
|
||||
(adapter) => adapter.value === adapterName,
|
||||
);
|
||||
if (selectedAdapter) {
|
||||
form.setValue('name', selectedAdapter.label, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
form.setValue('adapter', adapterName, {
|
||||
shouldDirty: !isInitializing.current,
|
||||
shouldValidate: true,
|
||||
@@ -664,7 +684,7 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
description: form.getValues().description ?? '',
|
||||
adapter: form.getValues().adapter,
|
||||
adapter_config: form.getValues().adapter_config,
|
||||
enable: form.getValues().enable,
|
||||
enable: true,
|
||||
event_bindings: form.getValues().event_bindings ?? [],
|
||||
plugin_processors: form.getValues().plugin_processors ?? [],
|
||||
};
|
||||
@@ -672,7 +692,7 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
.updateBot(initBotId, updateBot)
|
||||
.then(() => {
|
||||
// Reset dirty baseline to current values so isDirty becomes false
|
||||
form.reset(form.getValues());
|
||||
form.reset({ ...form.getValues(), enable: true });
|
||||
onFormSubmit(form.getValues());
|
||||
toast.success(t('bots.saveSuccess'));
|
||||
})
|
||||
@@ -755,17 +775,38 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
data-testid="adapter-gallery"
|
||||
className="min-w-0 space-y-8 py-2"
|
||||
>
|
||||
<div className="max-w-2xl">
|
||||
<h2 className="text-xl font-semibold">
|
||||
{t('guidedTour.bot.adapter.title')}
|
||||
</h2>
|
||||
<div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 className="text-xl font-semibold">
|
||||
{t('guidedTour.bot.adapter.title')}
|
||||
</h2>
|
||||
<div className="relative w-full sm:w-64">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
value={adapterSearch}
|
||||
onChange={(event) => setAdapterSearch(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') event.preventDefault();
|
||||
}}
|
||||
placeholder={t('bots.searchAdapters')}
|
||||
aria-label={t('bots.searchAdapters')}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
{t('guidedTour.bot.adapter.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{groupedAdapters.map((group) => (
|
||||
{galleryGroups.length === 0 && galleryLegacyAdapters.length === 0 && (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground" role="status">
|
||||
{t('bots.noMatchingAdapters')}
|
||||
</p>
|
||||
)}
|
||||
{galleryGroups.map((group) => (
|
||||
<section
|
||||
key={group.categoryId ?? 'uncategorized'}
|
||||
data-adapter-category={
|
||||
@@ -820,7 +861,7 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
</section>
|
||||
))}
|
||||
|
||||
{legacyAdapters.length > 0 && (
|
||||
{galleryLegacyAdapters.length > 0 && (
|
||||
<section
|
||||
className="border-t pt-5"
|
||||
data-adapter-category="legacy"
|
||||
@@ -829,21 +870,21 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
type="button"
|
||||
onClick={() => setShowLegacyAdapters((value) => !value)}
|
||||
className="flex w-full items-center gap-2 py-2 text-left text-sm font-medium text-muted-foreground hover:text-foreground"
|
||||
aria-expanded={showLegacyAdapters}
|
||||
aria-expanded={showLegacyAdapters || !!adapterQuery}
|
||||
>
|
||||
{showLegacyAdapters ? (
|
||||
{showLegacyAdapters || adapterQuery ? (
|
||||
<ChevronDown className="size-4" />
|
||||
) : (
|
||||
<ChevronRight className="size-4" />
|
||||
)}
|
||||
{t('bots.legacyAdapters')}
|
||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px]">
|
||||
{legacyAdapters.length}
|
||||
{galleryLegacyAdapters.length}
|
||||
</span>
|
||||
</button>
|
||||
{showLegacyAdapters && (
|
||||
{(showLegacyAdapters || !!adapterQuery) && (
|
||||
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{legacyAdapters.map((item) => (
|
||||
{galleryLegacyAdapters.map((item) => (
|
||||
<button
|
||||
key={`legacy:${item.value}`}
|
||||
type="button"
|
||||
|
||||
@@ -222,6 +222,7 @@ function AssistantSessions(props: AssistantPanelProps) {
|
||||
|
||||
const ASSISTANT_HEIGHT_STORAGE_KEY = 'langbot-assistant-panel-height';
|
||||
const ASSISTANT_POSITION_STORAGE_KEY = 'langbot-assistant-panel-position';
|
||||
const ASSISTANT_OPEN_STORAGE_KEY = 'langbot-assistant-panel-open';
|
||||
|
||||
function AssistantPanel({
|
||||
storageKey,
|
||||
@@ -235,7 +236,20 @@ function AssistantPanel({
|
||||
const { t } = useTranslation();
|
||||
const initialModelUuid = initialModel?.uuid;
|
||||
const initialModelName = initialModel?.name;
|
||||
const [open, setOpen] = useState(true);
|
||||
const [open, setOpen] = useState(() => {
|
||||
try {
|
||||
return localStorage.getItem(ASSISTANT_OPEN_STORAGE_KEY) !== 'false';
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(ASSISTANT_OPEN_STORAGE_KEY, String(open));
|
||||
} catch {
|
||||
// The panel remains usable when browser storage is unavailable.
|
||||
}
|
||||
}, [open]);
|
||||
const [closing, setClosing] = useState(false);
|
||||
const [horizontalPosition, setHorizontalPosition] = useState(() => {
|
||||
try {
|
||||
|
||||
@@ -20,14 +20,13 @@ import type {
|
||||
ExecutionRow,
|
||||
ExecutionSection,
|
||||
} from '@/app/infra/entities/api/monitoring-executions';
|
||||
import { ExecutionValue as Value } from './ExecutionValue';
|
||||
import { ExecutionValue as Value, hasExecutionValue } from './ExecutionValue';
|
||||
import { formatRunDuration } from '@/app/home/agents/components/processor-run-timing';
|
||||
import { formatDateTime } from '../../utils/dateUtils';
|
||||
import {
|
||||
executionKindLabel,
|
||||
executionProcessorLabel,
|
||||
executionStatusLabel,
|
||||
executionStatusVariant,
|
||||
} from './ExecutionTable';
|
||||
|
||||
export type ExecutionSelection = Pick<ExecutionRow, 'id'> & {
|
||||
@@ -35,6 +34,24 @@ export type ExecutionSelection = Pick<ExecutionRow, 'id'> & {
|
||||
};
|
||||
|
||||
const mainSections: ExecutionSection[] = ['inputs', 'outputs', 'deliveries'];
|
||||
|
||||
// Keep transport metadata in the payload disclosure, not in the message body.
|
||||
function messageBody(value: unknown): unknown {
|
||||
if (typeof value === 'string') {
|
||||
if (/^\s*[\[{]/.test(value)) {
|
||||
try { return messageBody(JSON.parse(value)); } catch { /* Plain text. */ }
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (Array.isArray(value)) return value.map(messageBody).filter((item) => item != null && item !== '');
|
||||
if (!value || typeof value !== 'object') return null;
|
||||
const data = value as Record<string, unknown>;
|
||||
if (['Image', 'File', 'Voice', 'At', 'AtAll', 'Quote'].includes(String(data.type))) return value;
|
||||
for (const key of ['text', 'content', 'message_chain', 'message']) {
|
||||
if (data[key] != null) return messageBody(data[key]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const secondarySections: ExecutionSection[] = [
|
||||
'related',
|
||||
'conversation',
|
||||
@@ -157,7 +174,7 @@ export default function ExecutionDetailSheet({
|
||||
{executionProcessorLabel(related)} ·{' '}
|
||||
{executionKindLabel(related, t)}
|
||||
</span>
|
||||
<Badge variant={executionStatusVariant(related.status_group)}>
|
||||
<Badge status={related.status_group}>
|
||||
{executionStatusLabel(related, t)}
|
||||
</Badge>
|
||||
</Button>
|
||||
@@ -169,6 +186,11 @@ export default function ExecutionDetailSheet({
|
||||
'deliveries',
|
||||
'conversation',
|
||||
].includes(section);
|
||||
const body = section === 'inputs'
|
||||
? typeof item.content === 'string' ? item.content
|
||||
: item.content && typeof item.content === 'object' && 'text' in item.content
|
||||
? item.content.text : null
|
||||
: messageBody(item.content);
|
||||
const title = content
|
||||
? [
|
||||
item.actor_name || item.actor_id,
|
||||
@@ -188,7 +210,7 @@ export default function ExecutionDetailSheet({
|
||||
item.id,
|
||||
);
|
||||
return (
|
||||
<div className="min-w-0 space-y-2 rounded-lg border p-3">
|
||||
<div className="min-w-0 space-y-3 rounded-lg border bg-background p-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span className="break-all">{title}</span>
|
||||
<span>
|
||||
@@ -200,19 +222,17 @@ export default function ExecutionDetailSheet({
|
||||
</span>
|
||||
{item.status && (
|
||||
<Badge
|
||||
variant={executionStatusVariant(
|
||||
['error', 'failed'].includes(item.status)
|
||||
? 'failed'
|
||||
: item.status,
|
||||
)}
|
||||
status={item.status}
|
||||
>
|
||||
{item.status}
|
||||
{t(`monitoring.execution.status.${['success', 'delivered'].includes(item.status) ? 'completed' : item.status}`, { defaultValue: item.status })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{content ? (
|
||||
<>
|
||||
<Value value={item.content} />
|
||||
<div className="text-sm leading-7">
|
||||
{hasExecutionValue(body) ? <Value value={body} /> : <p className="text-muted-foreground">{t('monitoring.unified.noRecords')}</p>}
|
||||
</div>
|
||||
<Value value={item.attachments} />
|
||||
{item.origin === 'generated' && section === 'conversation' && (
|
||||
<Badge variant="outline">{t('monitoring.unified.outputs')}</Badge>
|
||||
@@ -222,9 +242,12 @@ export default function ExecutionDetailSheet({
|
||||
{String(item.delivery.delivery_error)}
|
||||
</p>
|
||||
) : null}
|
||||
{Array.isArray(item.metadata?.routes) && (
|
||||
<Value value={item.metadata.routes} />
|
||||
)}
|
||||
<details className="border-t pt-2">
|
||||
<summary className="cursor-pointer text-xs text-muted-foreground">{t('monitoring.unified.payload')}</summary>
|
||||
<div className="mt-3 max-h-80 overflow-auto rounded-md bg-muted/40 p-3">
|
||||
<Value value={item} />
|
||||
</div>
|
||||
</details>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -291,12 +314,15 @@ export default function ExecutionDetailSheet({
|
||||
);
|
||||
const title = `${t(`monitoring.unified.${section}`)} (${page.items.length}${page.has_more ? '+' : ''})`;
|
||||
return primary ? (
|
||||
<section key={section}>
|
||||
<h3 className="text-sm font-semibold">{title}</h3>
|
||||
<section key={section} className="space-y-3">
|
||||
<h3 className="flex items-center gap-2 text-sm font-semibold">
|
||||
<span className="flex size-6 items-center justify-center rounded-full bg-muted text-xs text-muted-foreground">{mainSections.indexOf(section) + 1}</span>
|
||||
{title}
|
||||
</h3>
|
||||
{body}
|
||||
</section>
|
||||
) : (
|
||||
<details key={section} className="rounded-lg border p-3">
|
||||
<details key={section} open={section === 'errors' ? true : undefined} className={`rounded-lg border p-3 ${section === 'errors' ? 'border-red-500/25 bg-red-500/5' : ''}`}>
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
{title}
|
||||
</summary>
|
||||
@@ -355,14 +381,14 @@ export default function ExecutionDetailSheet({
|
||||
{record && (
|
||||
<>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant={executionStatusVariant(record.status_group)}>
|
||||
<Badge status={record.status_group}>
|
||||
{executionStatusLabel(record, t)}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{executionKindLabel(record, t)}
|
||||
</Badge>
|
||||
{record.debug && (
|
||||
<Badge variant="secondary">
|
||||
<Badge status="debug">
|
||||
{t('monitoring.execution.debug')}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -383,7 +409,21 @@ export default function ExecutionDetailSheet({
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
</p>
|
||||
{record.status_reason && (
|
||||
<div className="grid grid-cols-3 divide-x rounded-lg border bg-muted/20">
|
||||
<div className="space-y-1 p-3">
|
||||
<p className="text-xs text-muted-foreground">{t('monitoring.execution.detail.duration')}</p>
|
||||
<p className="text-sm font-semibold tabular-nums">{record.duration_ms != null ? formatRunDuration(record.duration_ms) : '—'}</p>
|
||||
</div>
|
||||
<div className="space-y-1 p-3">
|
||||
<p className="text-xs text-muted-foreground">{t('monitoring.llmCalls.totalTokens')}</p>
|
||||
<p className="text-sm font-semibold tabular-nums">{record.usage?.total_tokens?.toLocaleString() ?? '—'}</p>
|
||||
</div>
|
||||
<div className="space-y-1 p-3">
|
||||
<p className="text-xs text-muted-foreground">{t('monitoring.unified.deliveries')}</p>
|
||||
<p className="text-sm font-semibold tabular-nums">{visible?.pages.deliveries?.items.length ?? 0}{visible?.pages.deliveries?.has_more ? '+' : ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
{record.status_reason && !['stop', 'completed', 'success'].includes(record.status_reason) && (
|
||||
<p className="rounded-md bg-muted p-3 text-sm">
|
||||
{record.status_reason}
|
||||
</p>
|
||||
|
||||
@@ -161,11 +161,11 @@ export default function ExecutionTable({
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<Badge variant={executionStatusVariant(row.status_group)}>
|
||||
<Badge status={row.status_group}>
|
||||
{executionStatusLabel(row, t)}
|
||||
</Badge>
|
||||
{row.debug && (
|
||||
<Badge variant="secondary">
|
||||
<Badge status="debug">
|
||||
{t('monitoring.execution.debug')}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -28,19 +28,34 @@ const badgeVariants = cva(
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
status,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<'span'> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean; status?: string }) {
|
||||
const Comp = asChild ? Slot : 'span';
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
className={cn(badgeVariants({ variant: status ? 'outline' : variant }), status && statusBadgeClass(status), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function statusBadgeClass(status: string): string {
|
||||
if (['completed', 'success', 'succeeded', 'delivered', 'ready', 'enabled', 'connected'].includes(status))
|
||||
return 'border-emerald-500/25 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300';
|
||||
if (['failed', 'error', 'timeout', 'disconnected'].includes(status))
|
||||
return 'border-red-500/25 bg-red-500/10 text-red-700 dark:text-red-300';
|
||||
if (['running', 'active', 'connecting'].includes(status))
|
||||
return 'border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300';
|
||||
if (['queued', 'pending', 'created', 'claimed', 'waiting', 'warning'].includes(status))
|
||||
return 'border-amber-500/25 bg-amber-500/10 text-amber-800 dark:text-amber-300';
|
||||
if (['mock', 'simulated', 'debug'].includes(status))
|
||||
return 'border-violet-500/25 bg-violet-500/10 text-violet-700 dark:text-violet-300';
|
||||
return 'border-border bg-muted text-muted-foreground';
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
|
||||
@@ -781,6 +781,9 @@ const enUS = {
|
||||
deleteConfirmation: 'Are you sure you want to delete this bot?',
|
||||
platformAdapter: 'Platform/Adapter Selection',
|
||||
selectAdapter: 'Select Adapter',
|
||||
saveAndEnable: 'Save and enable',
|
||||
searchAdapters: 'Search adapters',
|
||||
noMatchingAdapters: 'No matching adapters found',
|
||||
legacyAdapters: 'Legacy adapters',
|
||||
legacyAdapterBadge: 'Legacy',
|
||||
legacyAdaptersHint:
|
||||
@@ -1126,6 +1129,16 @@ const enUS = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "Run status",
|
||||
toolCalls: "Tool calls",
|
||||
delivery: "Message delivery",
|
||||
sendFailed: "Send failed",
|
||||
sendCount: "{{count}} send calls succeeded",
|
||||
noSend: "No send recorded",
|
||||
noSteps: "No execution steps recorded yet.",
|
||||
recipient: "Recipient",
|
||||
actionCompleted: "Tool completed successfully.",
|
||||
simulatedAction: "Simulated action; no real message was sent.",
|
||||
description:
|
||||
'Follow each task from its triggering event through model output and tool execution.',
|
||||
empty:
|
||||
|
||||
@@ -797,6 +797,9 @@ const esES = {
|
||||
deleteConfirmation: '¿Estás seguro de que deseas eliminar este Bot?',
|
||||
platformAdapter: 'Selección de plataforma/adaptador',
|
||||
selectAdapter: 'Seleccionar adaptador',
|
||||
saveAndEnable: 'Guardar y activar',
|
||||
searchAdapters: 'Buscar adaptadores',
|
||||
noMatchingAdapters: 'No se encontraron adaptadores coincidentes',
|
||||
legacyAdapters: 'Adaptadores heredados',
|
||||
legacyAdapterBadge: 'Heredado',
|
||||
legacyAdaptersHint:
|
||||
@@ -1148,6 +1151,16 @@ const esES = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "Estado de ejecución",
|
||||
toolCalls: "Llamadas a herramientas",
|
||||
delivery: "Envío de mensajes",
|
||||
sendFailed: "Error de envío",
|
||||
sendCount: "{{count}} llamadas de envío correctas",
|
||||
noSend: "Sin envíos registrados",
|
||||
noSteps: "Aún no hay pasos registrados.",
|
||||
recipient: "Destinatario",
|
||||
actionCompleted: "Herramienta ejecutada correctamente.",
|
||||
simulatedAction: "Acción simulada; no se envió ningún mensaje real.",
|
||||
description:
|
||||
'Consulta el evento, la salida del modelo y las herramientas de cada tarea.',
|
||||
empty:
|
||||
|
||||
@@ -784,6 +784,9 @@ const jaJP = {
|
||||
deleteConfirmation: '本当にこのボットを削除しますか?',
|
||||
platformAdapter: 'プラットフォーム/アダプター選択',
|
||||
selectAdapter: 'アダプターを選択',
|
||||
saveAndEnable: '保存して有効化',
|
||||
searchAdapters: 'アダプターを検索',
|
||||
noMatchingAdapters: '一致するアダプターが見つかりません',
|
||||
legacyAdapters: '旧式アダプター',
|
||||
legacyAdapterBadge: '旧式',
|
||||
legacyAdaptersHint:
|
||||
@@ -1136,6 +1139,16 @@ const jaJP = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "実行状態",
|
||||
toolCalls: "ツール呼び出し",
|
||||
delivery: "メッセージ送信",
|
||||
sendFailed: "送信失敗",
|
||||
sendCount: "送信 API 呼び出し成功:{{count}} 回",
|
||||
noSend: "送信記録なし",
|
||||
noSteps: "実行ステップはまだありません。",
|
||||
recipient: "送信先",
|
||||
actionCompleted: "ツールの実行が完了しました。",
|
||||
simulatedAction: "シミュレーションです。実際のメッセージは送信されていません。",
|
||||
description:
|
||||
'各タスクのトリガーイベント、モデル出力、ツール実行を確認します。',
|
||||
empty:
|
||||
|
||||
@@ -792,6 +792,9 @@ const ruRU = {
|
||||
deleteConfirmation: 'Вы уверены, что хотите удалить этого бота?',
|
||||
platformAdapter: 'Выбор платформы/адаптера',
|
||||
selectAdapter: 'Выберите адаптер',
|
||||
saveAndEnable: 'Сохранить и включить',
|
||||
searchAdapters: 'Поиск адаптеров',
|
||||
noMatchingAdapters: 'Подходящие адаптеры не найдены',
|
||||
legacyAdapters: 'Устаревшие адаптеры',
|
||||
legacyAdapterBadge: 'Устаревший',
|
||||
legacyAdaptersHint:
|
||||
@@ -1146,6 +1149,16 @@ const ruRU = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "Статус выполнения",
|
||||
toolCalls: "Вызовы инструментов",
|
||||
delivery: "Отправка сообщений",
|
||||
sendFailed: "Ошибка отправки",
|
||||
sendCount: "Успешных вызовов отправки: {{count}}",
|
||||
noSend: "Отправки не зарегистрированы",
|
||||
noSteps: "Шаги выполнения ещё не зарегистрированы.",
|
||||
recipient: "Получатель",
|
||||
actionCompleted: "Инструмент выполнен успешно.",
|
||||
simulatedAction: "Действие смоделировано; реальное сообщение не отправлено.",
|
||||
description:
|
||||
'Просмотр события, ответа модели и вызовов инструментов для каждой задачи.',
|
||||
empty:
|
||||
|
||||
@@ -768,6 +768,9 @@ const thTH = {
|
||||
deleteConfirmation: 'คุณแน่ใจหรือไม่ว่าต้องการลบ Bot นี้?',
|
||||
platformAdapter: 'การเลือกแพลตฟอร์ม/อะแดปเตอร์',
|
||||
selectAdapter: 'เลือกอะแดปเตอร์',
|
||||
saveAndEnable: 'บันทึกและเปิดใช้งาน',
|
||||
searchAdapters: 'ค้นหาอะแดปเตอร์',
|
||||
noMatchingAdapters: 'ไม่พบอะแดปเตอร์ที่ตรงกัน',
|
||||
legacyAdapters: 'อะแดปเตอร์รุ่นเก่า',
|
||||
legacyAdapterBadge: 'เก่า',
|
||||
legacyAdaptersHint:
|
||||
@@ -1106,6 +1109,16 @@ const thTH = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "สถานะการทำงาน",
|
||||
toolCalls: "การเรียกเครื่องมือ",
|
||||
delivery: "การส่งข้อความ",
|
||||
sendFailed: "ส่งไม่สำเร็จ",
|
||||
sendCount: "เรียกส่งสำเร็จ {{count}} ครั้ง",
|
||||
noSend: "ไม่มีบันทึกการส่ง",
|
||||
noSteps: "ยังไม่มีบันทึกขั้นตอนการทำงาน",
|
||||
recipient: "ผู้รับ",
|
||||
actionCompleted: "เครื่องมือทำงานสำเร็จ",
|
||||
simulatedAction: "การจำลอง ไม่ได้ส่งข้อความจริง",
|
||||
description:
|
||||
'ดูเหตุการณ์เริ่มต้น ผลลัพธ์โมเดล และการเรียกเครื่องมือของแต่ละงาน',
|
||||
empty: 'ยังไม่มีการทำงาน เริ่มเหตุการณ์หรือทดสอบการดีบักเพื่อดูบันทึก',
|
||||
|
||||
@@ -782,6 +782,9 @@ const viVN = {
|
||||
deleteConfirmation: 'Bạn có chắc chắn muốn xóa Bot này không?',
|
||||
platformAdapter: 'Nền tảng/Lựa chọn Adapter',
|
||||
selectAdapter: 'Chọn Adapter',
|
||||
saveAndEnable: 'Lưu và bật',
|
||||
searchAdapters: 'Tìm kiếm adapter',
|
||||
noMatchingAdapters: 'Không tìm thấy adapter phù hợp',
|
||||
legacyAdapters: 'Adapter cũ',
|
||||
legacyAdapterBadge: 'Cũ',
|
||||
legacyAdaptersHint:
|
||||
@@ -1124,6 +1127,16 @@ const viVN = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "Trạng thái thực thi",
|
||||
toolCalls: "Lượt gọi công cụ",
|
||||
delivery: "Gửi tin nhắn",
|
||||
sendFailed: "Gửi thất bại",
|
||||
sendCount: "{{count}} lượt gọi gửi thành công",
|
||||
noSend: "Chưa ghi nhận lượt gửi",
|
||||
noSteps: "Chưa ghi nhận bước thực thi.",
|
||||
recipient: "Người nhận",
|
||||
actionCompleted: "Công cụ đã thực thi thành công.",
|
||||
simulatedAction: "Thao tác mô phỏng; không gửi tin nhắn thật.",
|
||||
description:
|
||||
'Xem sự kiện kích hoạt, đầu ra mô hình và quá trình gọi công cụ của mỗi tác vụ.',
|
||||
empty: 'Chưa có lượt chạy. Kích hoạt sự kiện hoặc chạy thử gỡ lỗi.',
|
||||
|
||||
@@ -734,6 +734,9 @@ const zhHans = {
|
||||
deleteConfirmation: '你确定要删除这个机器人吗?',
|
||||
platformAdapter: '平台/适配器选择',
|
||||
selectAdapter: '选择适配器',
|
||||
saveAndEnable: '保存并启用',
|
||||
searchAdapters: '搜索适配器',
|
||||
noMatchingAdapters: '未找到匹配的适配器',
|
||||
legacyAdapters: '旧版适配器',
|
||||
legacyAdapterBadge: '过时',
|
||||
legacyAdaptersHint:
|
||||
@@ -1060,6 +1063,16 @@ const zhHans = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "执行状态",
|
||||
toolCalls: "工具调用",
|
||||
delivery: "消息发送",
|
||||
sendFailed: "发送失败",
|
||||
sendCount: "{{count}} 次发送调用成功",
|
||||
noSend: "暂无发送记录",
|
||||
noSteps: "暂未记录执行步骤。",
|
||||
recipient: "接收目标",
|
||||
actionCompleted: "工具已执行完成。",
|
||||
simulatedAction: "模拟执行,未发送真实消息。",
|
||||
description: '查看每次任务的触发事件、模型输出和工具执行过程。',
|
||||
empty: '暂无运行记录。触发平台事件或运行调试后,可在这里查看。',
|
||||
input: '触发输入',
|
||||
|
||||
@@ -732,6 +732,9 @@ const zhHant = {
|
||||
deleteConfirmation: '您確定要刪除這個機器人嗎?',
|
||||
platformAdapter: '平台/適配器選擇',
|
||||
selectAdapter: '選擇適配器',
|
||||
saveAndEnable: '儲存並啟用',
|
||||
searchAdapters: '搜尋適配器',
|
||||
noMatchingAdapters: '找不到符合條件的適配器',
|
||||
legacyAdapters: '舊版適配器',
|
||||
legacyAdapterBadge: '過時',
|
||||
legacyAdaptersHint:
|
||||
@@ -1059,6 +1062,16 @@ const zhHant = {
|
||||
},
|
||||
agents: {
|
||||
monitoring: {
|
||||
runStatus: "執行狀態",
|
||||
toolCalls: "工具呼叫",
|
||||
delivery: "訊息傳送",
|
||||
sendFailed: "傳送失敗",
|
||||
sendCount: "{{count}} 次傳送呼叫成功",
|
||||
noSend: "暫無傳送記錄",
|
||||
noSteps: "尚未記錄執行步驟。",
|
||||
recipient: "接收目標",
|
||||
actionCompleted: "工具已執行完成。",
|
||||
simulatedAction: "模擬執行,未傳送真實訊息。",
|
||||
description: '查看每次任務的觸發事件、模型輸出和工具執行過程。',
|
||||
empty: '尚無執行紀錄。觸發平台事件或執行除錯後,可在此查看。',
|
||||
input: '觸發輸入',
|
||||
|
||||
Reference in New Issue
Block a user