mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-26 12:17:14 +00:00
refactor(agent): move delete action to header
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { useCurrentWorkspace } from '@/app/infra/http';
|
||||
import { Agent } from '@/app/infra/entities/api';
|
||||
@@ -11,6 +12,15 @@ import EntityBasicInfoDialog, {
|
||||
EntityBasicInfoValues,
|
||||
} from '@/app/home/components/entity-basic-info/EntityBasicInfoDialog';
|
||||
import EntityTitleEditButton from '@/app/home/components/entity-basic-info/EntityTitleEditButton';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import PipelineDetailContent from '@/app/home/pipelines/PipelineDetailContent';
|
||||
import AgentCreateContent from './components/AgentCreateContent';
|
||||
import AgentDebugPanel from './components/AgentDebugPanel';
|
||||
@@ -34,6 +44,8 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
||||
const [formDirty, setFormDirty] = useState(false);
|
||||
const [formSaving, setFormSaving] = useState(false);
|
||||
const [basicInfoOpen, setBasicInfoOpen] = useState(false);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [runnerStatus, setRunnerStatus] = useState<AgentRunnerStatus | null>(
|
||||
null,
|
||||
);
|
||||
@@ -111,6 +123,25 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAgent() {
|
||||
setDeleting(true);
|
||||
try {
|
||||
await httpClient.deleteAgent(id);
|
||||
toast.success(t('agents.deleteSuccess'));
|
||||
setDeleteConfirmOpen(false);
|
||||
await refreshPipelines();
|
||||
navigate('/home/agents');
|
||||
} catch (error) {
|
||||
const message =
|
||||
typeof error === 'object' && error && 'msg' in error
|
||||
? String((error as { msg?: string }).msg || '')
|
||||
: '';
|
||||
toast.error(t('agents.deleteError') + message);
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProcessorDetailWorkbench
|
||||
@@ -127,6 +158,19 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
||||
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}>
|
||||
@@ -141,10 +185,6 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
||||
}
|
||||
refreshPipelines();
|
||||
}}
|
||||
onDeleted={() => {
|
||||
refreshPipelines();
|
||||
navigate('/home/agents');
|
||||
}}
|
||||
onDirtyChange={setFormDirty}
|
||||
onSavingChange={setFormSaving}
|
||||
onRunnerStatusChange={setRunnerStatus}
|
||||
@@ -181,6 +221,35 @@ export default function AgentDetailContent({ id }: { id: string }) {
|
||||
defaultEmoji="🤖"
|
||||
onSave={saveBasicInfo}
|
||||
/>
|
||||
<Dialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common.confirmDelete')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('agents.deleteConfirmation')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={deleting}
|
||||
onClick={() => setDeleteConfirmOpen(false)}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
disabled={deleting}
|
||||
onClick={deleteAgent}
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
{t('common.confirmDelete')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import { Bot, Power, SlidersHorizontal, Trash2, Zap } from 'lucide-react';
|
||||
import { Bot, SlidersHorizontal, Zap } from 'lucide-react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { Agent, ApiRespPluginSystemStatus } from '@/app/infra/entities/api';
|
||||
import {
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
} from '@/app/infra/entities/pipeline';
|
||||
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
|
||||
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import {
|
||||
@@ -32,13 +31,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -58,17 +50,12 @@ export interface AgentRunnerStatus {
|
||||
interface AgentFormComponentProps {
|
||||
agentId: string;
|
||||
onFinish: (agent?: Partial<Agent>) => void;
|
||||
onDeleted: () => void;
|
||||
onDirtyChange?: (dirty: boolean) => void;
|
||||
onSavingChange?: (saving: boolean) => void;
|
||||
onRunnerStatusChange?: (status: AgentRunnerStatus) => void;
|
||||
}
|
||||
|
||||
export type AgentConfigSection =
|
||||
| 'events'
|
||||
| 'runner'
|
||||
| 'runner_config'
|
||||
| 'basic';
|
||||
export type AgentConfigSection = 'events' | 'runner' | 'runner_config';
|
||||
|
||||
export interface AgentFormHandle {
|
||||
openSection: (section: AgentConfigSection) => void;
|
||||
@@ -114,7 +101,6 @@ function AgentFormComponent(
|
||||
{
|
||||
agentId,
|
||||
onFinish,
|
||||
onDeleted,
|
||||
onDirtyChange,
|
||||
onSavingChange,
|
||||
onRunnerStatusChange,
|
||||
@@ -128,8 +114,6 @@ function AgentFormComponent(
|
||||
useState<ApiRespPluginSystemStatus | null>(null);
|
||||
const [pluginStatusLoading, setPluginStatusLoading] = useState(true);
|
||||
const [pluginStatusError, setPluginStatusError] = useState(false);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [activeSection, setActiveSection] =
|
||||
useState<AgentConfigSection>('events');
|
||||
const isSavingRef = useRef(false);
|
||||
@@ -283,11 +267,6 @@ function AgentFormComponent(
|
||||
: t('pipelines.configuration'),
|
||||
icon: SlidersHorizontal,
|
||||
},
|
||||
{
|
||||
name: 'basic',
|
||||
label: t('common.management'),
|
||||
icon: Power,
|
||||
},
|
||||
];
|
||||
|
||||
const runnerStatus = useMemo<AgentRunnerStatus>(() => {
|
||||
@@ -472,7 +451,6 @@ function AgentFormComponent(
|
||||
};
|
||||
|
||||
isSavingRef.current = true;
|
||||
setIsSaving(true);
|
||||
onSavingChange?.(true);
|
||||
try {
|
||||
await httpClient.updateAgent(agentId, agent);
|
||||
@@ -489,7 +467,6 @@ function AgentFormComponent(
|
||||
return false;
|
||||
} finally {
|
||||
isSavingRef.current = false;
|
||||
setIsSaving(false);
|
||||
onSavingChange?.(false);
|
||||
}
|
||||
},
|
||||
@@ -533,192 +510,110 @@ function AgentFormComponent(
|
||||
[form, saveValues],
|
||||
);
|
||||
|
||||
function confirmDelete() {
|
||||
httpClient
|
||||
.deleteAgent(agentId)
|
||||
.then(() => {
|
||||
toast.success(t('agents.deleteSuccess'));
|
||||
setShowDeleteConfirm(false);
|
||||
onDeleted();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(t('agents.deleteError') + err.msg);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full p-0 flex flex-col">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="agent-form"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="mb-2 flex h-full min-h-0 min-w-0 flex-1 flex-col"
|
||||
>
|
||||
<nav className="mb-4 shrink-0 space-y-2 border-b pb-4">
|
||||
<Tabs
|
||||
value={activeSection}
|
||||
onValueChange={(value) =>
|
||||
setActiveSection(value as AgentConfigSection)
|
||||
}
|
||||
>
|
||||
<div className="overflow-x-auto">
|
||||
<TabsList className="grid min-w-[42rem] w-full grid-cols-[repeat(3,minmax(0,1fr))_auto]">
|
||||
{primarySections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={section.name}
|
||||
value={section.name}
|
||||
className={
|
||||
section.name === 'basic'
|
||||
? 'px-4 text-muted-foreground data-[state=active]:text-foreground'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Icon />
|
||||
{section.label}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
<div className="h-full p-0 flex flex-col">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="agent-form"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="mb-2 flex h-full min-h-0 min-w-0 flex-1 flex-col"
|
||||
>
|
||||
<nav className="mb-4 shrink-0 space-y-2 border-b pb-4">
|
||||
<Tabs
|
||||
value={activeSection}
|
||||
onValueChange={(value) =>
|
||||
setActiveSection(value as AgentConfigSection)
|
||||
}
|
||||
>
|
||||
<div className="overflow-x-auto">
|
||||
<TabsList className="grid min-w-[34rem] w-full grid-cols-3">
|
||||
{primarySections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<TabsTrigger key={section.name} value={section.name}>
|
||||
<Icon />
|
||||
{section.label}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
</div>
|
||||
</Tabs>
|
||||
</nav>
|
||||
|
||||
<div className="min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden">
|
||||
<div className="mx-auto w-full min-w-0 max-w-5xl space-y-6 pb-8">
|
||||
{activeSection === 'runner' && (
|
||||
<div className="space-y-6">
|
||||
{runnerSelectorStage
|
||||
? renderDynamicStage(runnerSelectorStage)
|
||||
: !runnerConfigSchema && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('agents.runnerSettings')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.noRunnerMetadata')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</Tabs>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<div className="min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden">
|
||||
<div className="mx-auto w-full min-w-0 max-w-5xl space-y-6 pb-8">
|
||||
{activeSection === 'runner' && (
|
||||
<div className="space-y-6">
|
||||
{runnerSelectorStage
|
||||
? renderDynamicStage(runnerSelectorStage)
|
||||
: !runnerConfigSchema && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{t('agents.runnerSettings')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.noRunnerMetadata')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSection === 'runner_config' && (
|
||||
<div className="space-y-6">
|
||||
{activeRunnerStage ? (
|
||||
renderDynamicStage(activeRunnerStage)
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('agents.runnerSettings')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.noRunnerMetadata')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSection === 'events' && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('agents.bindableEvents')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.bindableEventsDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="supported_event_patterns_text"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('agents.supportedEvents')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
className="min-h-32 font-mono text-sm"
|
||||
placeholder={'*\nmessage.received\ngroup.*'}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('agents.supportedEventsDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{activeSection === 'basic' && (
|
||||
<div className="space-y-6">
|
||||
<Card className="border-destructive/50">
|
||||
{activeSection === 'runner_config' && (
|
||||
<div className="space-y-6">
|
||||
{activeRunnerStage ? (
|
||||
renderDynamicStage(activeRunnerStage)
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-destructive">
|
||||
{t('agents.dangerZone')}
|
||||
</CardTitle>
|
||||
<CardTitle>{t('agents.runnerSettings')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.dangerZoneDescription')}
|
||||
{t('agents.noRunnerMetadata')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium">
|
||||
{t('agents.deleteAgentAction')}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('agents.deleteAgentHint')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
disabled={isSaving}
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
>
|
||||
<Trash2 className="mr-1.5 size-4" />
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common.confirmDelete')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">{t('agents.deleteConfirmation')}</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={confirmDelete}>
|
||||
{t('common.confirmDelete')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
{activeSection === 'events' && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('agents.bindableEvents')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('agents.bindableEventsDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="supported_event_patterns_text"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('agents.supportedEvents')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
className="min-h-32 font-mono text-sm"
|
||||
placeholder={'*\nmessage.received\ngroup.*'}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('agents.supportedEventsDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface ProcessorDetailStatus {
|
||||
interface ProcessorDetailWorkbenchProps {
|
||||
title: string;
|
||||
titleAction?: ReactNode;
|
||||
headerActions?: ReactNode;
|
||||
status?: ProcessorDetailStatus | null;
|
||||
saveLabel: string;
|
||||
saveFormId: string;
|
||||
@@ -43,6 +44,7 @@ interface ProcessorDetailWorkbenchProps {
|
||||
export default function ProcessorDetailWorkbench({
|
||||
title,
|
||||
titleAction,
|
||||
headerActions,
|
||||
status,
|
||||
saveLabel,
|
||||
saveFormId,
|
||||
@@ -138,6 +140,7 @@ export default function ProcessorDetailWorkbench({
|
||||
{saveLabel}
|
||||
</Button>
|
||||
)}
|
||||
{activeView === 'workbench' && headerActions}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,9 +50,8 @@ test.describe('processor detail workbench', () => {
|
||||
);
|
||||
await expect(flow.getByRole('tab').nth(1)).toContainText('Runner');
|
||||
await expect(flow.getByRole('tab').nth(2)).toContainText('Local Agent');
|
||||
const agentManagement = flow.getByRole('tab').nth(3);
|
||||
await expect(agentManagement).toContainText('Management');
|
||||
await expect(agentManagement).toHaveClass(/text-muted-foreground/);
|
||||
await expect(flow.getByRole('tab')).toHaveCount(3);
|
||||
await expect(flow.getByText('Management')).toHaveCount(0);
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /agent-workbench/ }),
|
||||
@@ -60,6 +59,15 @@ test.describe('processor detail workbench', () => {
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit basic information' }),
|
||||
).toBeVisible();
|
||||
const saveButton = page.getByRole('button', { name: 'Save' });
|
||||
const deleteButton = page.getByRole('button', { name: 'Delete' });
|
||||
await expect(saveButton).toBeVisible();
|
||||
await expect(deleteButton).toBeVisible();
|
||||
const saveBox = await saveButton.boundingBox();
|
||||
const deleteBox = await deleteButton.boundingBox();
|
||||
expect(saveBox).not.toBeNull();
|
||||
expect(deleteBox).not.toBeNull();
|
||||
expect(deleteBox!.x).toBeGreaterThan(saveBox!.x);
|
||||
await expect(configPanel.getByLabel('Name')).toHaveCount(0);
|
||||
await expect(configPanel.getByLabel('Icon')).toHaveCount(0);
|
||||
await expect(configPanel.getByLabel('Description')).toHaveCount(0);
|
||||
@@ -83,12 +91,14 @@ test.describe('processor detail workbench', () => {
|
||||
await expect(
|
||||
configPanel.getByText('Local Agent', { exact: true }).last(),
|
||||
).toBeVisible();
|
||||
await agentManagement.click();
|
||||
await expect(configPanel.getByText('Danger Zone')).toBeVisible();
|
||||
await expect(configPanel.getByText('Availability')).toHaveCount(0);
|
||||
await expect(
|
||||
configPanel.getByRole('switch', { name: 'Enable Agent' }),
|
||||
).toHaveCount(0);
|
||||
|
||||
await deleteButton.click();
|
||||
const deleteDialog = page.getByRole('dialog');
|
||||
await expect(deleteDialog).toContainText(
|
||||
'Are you sure you want to delete this Agent?',
|
||||
);
|
||||
await deleteDialog.getByRole('button', { name: 'Cancel' }).click();
|
||||
await expect(deleteDialog).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('agent saves edits before debugging and shows the real output', async ({
|
||||
@@ -130,6 +140,27 @@ test.describe('processor detail workbench', () => {
|
||||
expect(requests).toEqual(['save', 'debug']);
|
||||
});
|
||||
|
||||
test('agent deletion is confirmed from the header and returns to the list', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, { authenticated: true });
|
||||
await page.goto('/home/agents?id=agent-workbench');
|
||||
|
||||
const deleteRequest = page.waitForRequest(
|
||||
(request) =>
|
||||
request.method() === 'DELETE' &&
|
||||
new URL(request.url()).pathname === '/api/v1/agents/agent-workbench',
|
||||
);
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
await page
|
||||
.getByRole('dialog')
|
||||
.getByRole('button', { name: 'Confirm Delete' })
|
||||
.click();
|
||||
|
||||
await deleteRequest;
|
||||
await expect(page).toHaveURL(/\/home\/agents$/);
|
||||
});
|
||||
|
||||
test('agent turns runner failures into an actionable message', async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user