mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-26 12:17:14 +00:00
fix(bots): restore event route drag sorting
This commit is contained in:
@@ -1264,6 +1264,7 @@ function BindingCardContent({
|
||||
onUpdate,
|
||||
onRemove,
|
||||
dragHandleProps,
|
||||
isOverlay = false,
|
||||
}: BindingCardProps) {
|
||||
const { t } = useTranslation();
|
||||
const isEnabled = binding.enabled ?? true;
|
||||
@@ -1275,13 +1276,21 @@ function BindingCardContent({
|
||||
const statusDetail = routeStatusDetail(routeStatus, t);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border bg-card">
|
||||
<div
|
||||
className={`rounded-lg border bg-card ${
|
||||
isOverlay ? 'pointer-events-none shadow-lg ring-1 ring-primary/20' : ''
|
||||
}`}
|
||||
data-drag-overlay={isOverlay ? 'true' : undefined}
|
||||
>
|
||||
{/* main row */}
|
||||
<div className="flex flex-wrap items-center gap-2 p-2.5">
|
||||
{isEnabled && (
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-grab active:cursor-grabbing shrink-0 text-muted-foreground hover:text-foreground touch-none"
|
||||
aria-label={t('bots.dragEventRoute', {
|
||||
index: globalIndex + 1,
|
||||
})}
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<GripVertical className="h-4 w-4" />
|
||||
@@ -1435,15 +1444,32 @@ function BindingCardContent({
|
||||
|
||||
// ── sortable wrapper ──────────────────────────────────────────────────────────
|
||||
|
||||
function SortableBindingCard(props: BindingCardProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, isDragging } =
|
||||
useSortable({ id: props.binding.id ?? props.globalIndex });
|
||||
interface SortableBindingCardProps extends BindingCardProps {
|
||||
sortableId: string;
|
||||
}
|
||||
|
||||
function SortableBindingCard({
|
||||
sortableId,
|
||||
...props
|
||||
}: SortableBindingCardProps) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: sortableId });
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
data-testid={`event-route-${sortableId}`}
|
||||
style={{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.3 : undefined,
|
||||
position: 'relative',
|
||||
zIndex: isDragging ? 1 : undefined,
|
||||
}}
|
||||
>
|
||||
<BindingCardContent
|
||||
@@ -1686,6 +1712,7 @@ export default function EventBindingsEditor({
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={() => setActiveId(null)}
|
||||
>
|
||||
<SortableContext
|
||||
items={idsRef.current}
|
||||
@@ -1702,6 +1729,7 @@ export default function EventBindingsEditor({
|
||||
return (
|
||||
<SortableBindingCard
|
||||
key={idsRef.current[sortIdx]}
|
||||
sortableId={idsRef.current[sortIdx]}
|
||||
binding={binding}
|
||||
globalIndex={globalIdx}
|
||||
routeStatus={
|
||||
@@ -1720,7 +1748,7 @@ export default function EventBindingsEditor({
|
||||
})}
|
||||
</div>
|
||||
</SortableContext>
|
||||
<DragOverlay dropAnimation={null}>
|
||||
<DragOverlay adjustScale={false} dropAnimation={null}>
|
||||
{activeBinding && activeGlobalIdx >= 0 ? (
|
||||
<BindingCardContent
|
||||
binding={activeBinding}
|
||||
|
||||
@@ -392,6 +392,7 @@ const enUS = {
|
||||
eventBindings: 'Event Routes',
|
||||
addEventBinding: 'Add Route',
|
||||
addBehavior: 'Add behavior',
|
||||
dragEventRoute: 'Drag route {{index}}',
|
||||
behaviorReplyMessages: 'Reply to messages',
|
||||
behaviorReplyMessagesDescription:
|
||||
'Send incoming messages to an Agent or Pipeline.',
|
||||
|
||||
@@ -398,6 +398,7 @@ const jaJP = {
|
||||
eventBindings: 'イベントルート',
|
||||
addEventBinding: 'ルートを追加',
|
||||
addBehavior: '動作を追加',
|
||||
dragEventRoute: 'ルート {{index}} をドラッグ',
|
||||
behaviorReplyMessages: '受信メッセージに返信',
|
||||
behaviorReplyMessagesDescription:
|
||||
'受信メッセージを Agent または Pipeline で処理します。',
|
||||
|
||||
@@ -375,6 +375,7 @@ const zhHans = {
|
||||
eventBindings: '事件路由',
|
||||
addEventBinding: '添加路由',
|
||||
addBehavior: '添加行为',
|
||||
dragEventRoute: '拖动第 {{index}} 条路由',
|
||||
behaviorReplyMessages: '回复收到的消息',
|
||||
behaviorReplyMessagesDescription:
|
||||
'把收到的消息交给 Agent 或 Pipeline 处理。',
|
||||
|
||||
@@ -872,6 +872,66 @@ test.describe('agent and pipeline save concurrency', () => {
|
||||
});
|
||||
|
||||
test.describe('cross-resource flows', () => {
|
||||
test('reorders bot event routes with a visible drag preview', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
withAdapterEvents: true,
|
||||
});
|
||||
|
||||
await page.goto('/home/bots?id=new');
|
||||
await selectPlaywrightAdapter(page);
|
||||
await page.locator('input[name="name"]').fill('Routing Bot');
|
||||
await submit(page);
|
||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||
|
||||
await page.getByRole('button', { name: 'Add behavior' }).click();
|
||||
await page.getByRole('menuitem', { name: /^Reply to messages/ }).click();
|
||||
await page.getByRole('button', { name: 'Add behavior' }).click();
|
||||
await page.getByRole('menuitem', { name: /^Welcome new members/ }).click();
|
||||
|
||||
const routeCards = page.locator('[data-testid^="event-route-"]');
|
||||
await expect(routeCards).toHaveCount(2);
|
||||
await expect(routeCards.nth(0)).toContainText('Message received');
|
||||
await expect(routeCards.nth(1)).toContainText('Member joined group');
|
||||
|
||||
const firstHandle = page.getByRole('button', { name: 'Drag route 1' });
|
||||
const secondCard = routeCards.nth(1);
|
||||
const handleBox = await firstHandle.boundingBox();
|
||||
const targetBox = await secondCard.boundingBox();
|
||||
expect(handleBox).not.toBeNull();
|
||||
expect(targetBox).not.toBeNull();
|
||||
|
||||
await page.mouse.move(
|
||||
handleBox!.x + handleBox!.width / 2,
|
||||
handleBox!.y + handleBox!.height / 2,
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
handleBox!.x + handleBox!.width / 2,
|
||||
handleBox!.y + handleBox!.height / 2 + 10,
|
||||
{ steps: 4 },
|
||||
);
|
||||
await expect(page.locator('[data-drag-overlay="true"]')).toBeVisible();
|
||||
await page.mouse.move(
|
||||
targetBox!.x + targetBox!.width / 2,
|
||||
targetBox!.y + targetBox!.height - 4,
|
||||
{ steps: 12 },
|
||||
);
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(page.locator('[data-drag-overlay="true"]')).toHaveCount(0);
|
||||
await expect(routeCards.nth(0)).toContainText('Member joined group');
|
||||
await expect(routeCards.nth(1)).toContainText('Message received');
|
||||
|
||||
await save(page);
|
||||
await page.reload();
|
||||
const savedRouteCards = page.locator('[data-testid^="event-route-"]');
|
||||
await expect(savedRouteCards.nth(0)).toContainText('Member joined group');
|
||||
await expect(savedRouteCards.nth(1)).toContainText('Message received');
|
||||
});
|
||||
|
||||
test('creates a pipeline then binds it to a bot', async ({ page }) => {
|
||||
await installLangBotApiMocks(page, { authenticated: true });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user