mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-28 13:17:14 +00:00
refactor(pipelines): clarify log view switching
This commit is contained in:
@@ -7,10 +7,12 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface ProcessorMonitoringView {
|
interface ProcessorMonitoringView {
|
||||||
label: string;
|
label: string;
|
||||||
|
workbenchLabel: string;
|
||||||
content: ReactNode;
|
content: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +69,37 @@ export default function ProcessorDetailWorkbench({
|
|||||||
const hasDebug = Boolean(debugTitle && debugContent);
|
const hasDebug = Boolean(debugTitle && debugContent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-0 min-w-0 flex-col">
|
<Tabs
|
||||||
|
value={activeView}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setActiveView(value as 'workbench' | 'monitoring')
|
||||||
|
}
|
||||||
|
className="flex h-full min-h-0 min-w-0 flex-col gap-0"
|
||||||
|
>
|
||||||
<div className="flex shrink-0 flex-wrap items-center justify-between gap-3 pb-4">
|
<div className="flex shrink-0 flex-wrap items-center justify-between gap-3 pb-4">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<h1 className="truncate text-xl font-semibold">{title}</h1>
|
<h1 className="truncate text-xl font-semibold">{title}</h1>
|
||||||
{titleAction}
|
{titleAction}
|
||||||
|
{monitoring && (
|
||||||
|
<TabsList
|
||||||
|
aria-label={`${monitoring.workbenchLabel} / ${monitoring.label}`}
|
||||||
|
className="ml-1"
|
||||||
|
>
|
||||||
|
<TabsTrigger value="workbench" className="gap-1.5 px-3">
|
||||||
|
<Settings className="size-4" />
|
||||||
|
{monitoring.workbenchLabel}
|
||||||
|
{isDirty && (
|
||||||
|
<span className="size-1.5 rounded-full bg-amber-500">
|
||||||
|
<span className="sr-only">{unsavedLabel}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="monitoring" className="gap-1.5 px-3">
|
||||||
|
<BarChart3 className="size-4" />
|
||||||
|
{monitoring.label}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
)}
|
||||||
{status && (
|
{status && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -117,20 +145,6 @@ export default function ProcessorDetailWorkbench({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{monitoring && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={activeView === 'monitoring' ? 'secondary' : 'outline'}
|
|
||||||
onClick={() =>
|
|
||||||
setActiveView((current) =>
|
|
||||||
current === 'monitoring' ? 'workbench' : 'monitoring',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<BarChart3 className="size-4" />
|
|
||||||
{monitoring.label}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{canSave && activeView === 'workbench' && (
|
{canSave && activeView === 'workbench' && (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -144,74 +158,82 @@ export default function ProcessorDetailWorkbench({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeView === 'monitoring' && monitoring ? (
|
{monitoring && (
|
||||||
<section
|
<TabsContent
|
||||||
aria-label={monitoring.label}
|
value="monitoring"
|
||||||
className="min-h-0 flex-1 overflow-y-auto rounded-xl border bg-card p-4"
|
className="mt-0 min-h-0 flex-1 overflow-hidden"
|
||||||
>
|
>
|
||||||
{monitoring.content}
|
<section
|
||||||
</section>
|
aria-label={monitoring.label}
|
||||||
) : (
|
className="h-full min-h-0 overflow-y-auto rounded-xl border bg-card p-4"
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto lg:overflow-hidden">
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'grid min-h-0 gap-3 lg:h-full',
|
|
||||||
hasDebug
|
|
||||||
? 'lg:grid-cols-[minmax(20rem,0.72fr)_minmax(0,1.28fr)]'
|
|
||||||
: 'grid-cols-1',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{hasDebug && (
|
{monitoring.content}
|
||||||
<section
|
</section>
|
||||||
aria-label={debugTitle}
|
</TabsContent>
|
||||||
className="flex min-h-[32rem] min-w-0 flex-col overflow-hidden rounded-xl border bg-card lg:min-h-0"
|
)}
|
||||||
>
|
|
||||||
<div className="flex h-12 shrink-0 items-center justify-between gap-3 border-b px-4">
|
|
||||||
<div className="flex min-w-0 items-center gap-2 font-medium">
|
|
||||||
<Bug className="size-4 shrink-0" />
|
|
||||||
<span className="truncate">{debugTitle}</span>
|
|
||||||
</div>
|
|
||||||
{debugConnected !== undefined && (
|
|
||||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'size-2 rounded-full',
|
|
||||||
debugConnected ? 'bg-emerald-500' : 'bg-destructive',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{debugConnected
|
|
||||||
? debugConnectedLabel
|
|
||||||
: debugDisconnectedLabel}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
||||||
{debugContent}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
<TabsContent
|
||||||
|
value="workbench"
|
||||||
|
className="mt-0 min-h-0 flex-1 overflow-y-auto lg:overflow-hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'grid min-h-0 gap-3 lg:h-full',
|
||||||
|
hasDebug
|
||||||
|
? 'lg:grid-cols-[minmax(20rem,0.72fr)_minmax(0,1.28fr)]'
|
||||||
|
: 'grid-cols-1',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{hasDebug && (
|
||||||
<section
|
<section
|
||||||
aria-label={configTitle}
|
aria-label={debugTitle}
|
||||||
className="flex min-h-[36rem] min-w-0 flex-col overflow-hidden rounded-xl border bg-card lg:min-h-0"
|
className="flex min-h-[32rem] min-w-0 flex-col overflow-hidden rounded-xl border bg-card lg:min-h-0"
|
||||||
>
|
>
|
||||||
<div className="flex h-12 shrink-0 items-center gap-2 border-b px-4 font-medium">
|
<div className="flex h-12 shrink-0 items-center justify-between gap-3 border-b px-4">
|
||||||
<Settings className="size-4" />
|
<div className="flex min-w-0 items-center gap-2 font-medium">
|
||||||
<span className="truncate">{configTitle}</span>
|
<Bug className="size-4 shrink-0" />
|
||||||
{isDirty && (
|
<span className="truncate">{debugTitle}</span>
|
||||||
<span className="ml-auto flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400">
|
</div>
|
||||||
<span className="size-1.5 rounded-full bg-amber-500" />
|
{debugConnected !== undefined && (
|
||||||
{unsavedLabel}
|
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'size-2 rounded-full',
|
||||||
|
debugConnected ? 'bg-emerald-500' : 'bg-destructive',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{debugConnected
|
||||||
|
? debugConnectedLabel
|
||||||
|
: debugDisconnectedLabel}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-4">
|
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
||||||
{configContent}
|
{debugContent}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
<section
|
||||||
|
aria-label={configTitle}
|
||||||
|
className="flex min-h-[36rem] min-w-0 flex-col overflow-hidden rounded-xl border bg-card lg:min-h-0"
|
||||||
|
>
|
||||||
|
<div className="flex h-12 shrink-0 items-center gap-2 border-b px-4 font-medium">
|
||||||
|
<Settings className="size-4" />
|
||||||
|
<span className="truncate">{configTitle}</span>
|
||||||
|
{isDirty && (
|
||||||
|
<span className="ml-auto flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
<span className="size-1.5 rounded-full bg-amber-500" />
|
||||||
|
{unsavedLabel}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-4">
|
||||||
|
{configContent}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</TabsContent>
|
||||||
</div>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ export default function PipelineDetailContent({
|
|||||||
canViewMonitoring
|
canViewMonitoring
|
||||||
? {
|
? {
|
||||||
label: t('pipelines.monitoring.title'),
|
label: t('pipelines.monitoring.title'),
|
||||||
|
workbenchLabel: t('pipelines.monitoring.workbench'),
|
||||||
content: (
|
content: (
|
||||||
<PipelineMonitoringTab
|
<PipelineMonitoringTab
|
||||||
pipelineId={id}
|
pipelineId={id}
|
||||||
|
|||||||
@@ -1419,7 +1419,8 @@ const enUS = {
|
|||||||
uploading: 'Uploading...',
|
uploading: 'Uploading...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: 'Dashboard',
|
title: 'Run logs',
|
||||||
|
workbench: 'Configure & debug',
|
||||||
description:
|
description:
|
||||||
'View execution logs and errors for this pipeline (last 24 hours)',
|
'View execution logs and errors for this pipeline (last 24 hours)',
|
||||||
detailedLogs: 'Detailed Logs',
|
detailedLogs: 'Detailed Logs',
|
||||||
|
|||||||
@@ -1152,7 +1152,8 @@ const esES = {
|
|||||||
uploading: 'Subiendo...',
|
uploading: 'Subiendo...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: 'Panel de control',
|
title: 'Registros de ejecución',
|
||||||
|
workbench: 'Configurar y depurar',
|
||||||
description:
|
description:
|
||||||
'Ver registros de ejecución y errores de este Pipeline (últimas 24 horas)',
|
'Ver registros de ejecución y errores de este Pipeline (últimas 24 horas)',
|
||||||
detailedLogs: 'Registros detallados',
|
detailedLogs: 'Registros detallados',
|
||||||
|
|||||||
@@ -1382,7 +1382,8 @@ const jaJP = {
|
|||||||
uploading: 'アップロード中...',
|
uploading: 'アップロード中...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: 'ダッシュボード',
|
title: '実行ログ',
|
||||||
|
workbench: '設定とデバッグ',
|
||||||
description: 'このパイプラインの実行ログとエラー情報を表示(過去24時間)',
|
description: 'このパイプラインの実行ログとエラー情報を表示(過去24時間)',
|
||||||
detailedLogs: '詳細ログ',
|
detailedLogs: '詳細ログ',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1141,7 +1141,8 @@ const ruRU = {
|
|||||||
uploading: 'Загрузка...',
|
uploading: 'Загрузка...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: 'Мониторинг',
|
title: 'Журнал выполнения',
|
||||||
|
workbench: 'Настройка и отладка',
|
||||||
description:
|
description:
|
||||||
'Просмотр журналов выполнения и ошибок конвейера (за последние 24 часа)',
|
'Просмотр журналов выполнения и ошибок конвейера (за последние 24 часа)',
|
||||||
detailedLogs: 'Подробные журналы',
|
detailedLogs: 'Подробные журналы',
|
||||||
|
|||||||
@@ -1116,7 +1116,8 @@ const thTH = {
|
|||||||
uploading: 'กำลังอัปโหลด...',
|
uploading: 'กำลังอัปโหลด...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: 'แดชบอร์ด',
|
title: 'บันทึกการทำงาน',
|
||||||
|
workbench: 'กำหนดค่าและดีบัก',
|
||||||
description:
|
description:
|
||||||
'ดูบันทึกการดำเนินการและข้อผิดพลาดของ Pipeline นี้ (24 ชั่วโมงล่าสุด)',
|
'ดูบันทึกการดำเนินการและข้อผิดพลาดของ Pipeline นี้ (24 ชั่วโมงล่าสุด)',
|
||||||
detailedLogs: 'บันทึกโดยละเอียด',
|
detailedLogs: 'บันทึกโดยละเอียด',
|
||||||
|
|||||||
@@ -1133,7 +1133,8 @@ const viVN = {
|
|||||||
uploading: 'Đang tải lên...',
|
uploading: 'Đang tải lên...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: 'Bảng điều khiển',
|
title: 'Nhật ký chạy',
|
||||||
|
workbench: 'Cấu hình và gỡ lỗi',
|
||||||
description: 'Xem nhật ký thực thi và lỗi của Pipeline này (24 giờ qua)',
|
description: 'Xem nhật ký thực thi và lỗi của Pipeline này (24 giờ qua)',
|
||||||
detailedLogs: 'Nhật ký chi tiết',
|
detailedLogs: 'Nhật ký chi tiết',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1359,7 +1359,8 @@ const zhHans = {
|
|||||||
uploading: '上传中...',
|
uploading: '上传中...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: '监控日志',
|
title: '运行日志',
|
||||||
|
workbench: '配置与调试',
|
||||||
description: '查看此流水线的运行记录和错误信息(最近24小时)',
|
description: '查看此流水线的运行记录和错误信息(最近24小时)',
|
||||||
detailedLogs: '详细日志',
|
detailedLogs: '详细日志',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1083,7 +1083,8 @@ const zhHant = {
|
|||||||
uploading: '上傳中...',
|
uploading: '上傳中...',
|
||||||
},
|
},
|
||||||
monitoring: {
|
monitoring: {
|
||||||
title: '監控日誌',
|
title: '執行日誌',
|
||||||
|
workbench: '設定與除錯',
|
||||||
description: '檢視此流程線的執行記錄和錯誤資訊(最近24小時)',
|
description: '檢視此流程線的執行記錄和錯誤資訊(最近24小時)',
|
||||||
detailedLogs: '詳細日誌',
|
detailedLogs: '詳細日誌',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ test.describe('frontend CRUD smoke flows', () => {
|
|||||||
await expect(page.getByText('No logs yet')).toBeVisible();
|
await expect(page.getByText('No logs yet')).toBeVisible();
|
||||||
|
|
||||||
await page.goto('/home/agents?id=pipeline-1');
|
await page.goto('/home/agents?id=pipeline-1');
|
||||||
await expect(page.getByRole('button', { name: 'Dashboard' })).toBeVisible();
|
await expect(page.getByRole('tab', { name: 'Run logs' })).toBeVisible();
|
||||||
await expect(page.getByRole('tab', { name: 'Debug Chat' })).toHaveCount(0);
|
await expect(page.getByRole('tab', { name: 'Debug Chat' })).toHaveCount(0);
|
||||||
await expect(page.getByRole('button', { name: /^Save$/ })).toHaveCount(0);
|
await expect(page.getByRole('button', { name: /^Save$/ })).toHaveCount(0);
|
||||||
|
|
||||||
@@ -761,19 +761,18 @@ test.describe('pipeline advanced flows', () => {
|
|||||||
page.getByRole('region', { name: 'Configuration' }),
|
page.getByRole('region', { name: 'Configuration' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
// Switch to Monitoring tab (labeled "Dashboard" in the pipeline context)
|
const viewSwitcher = page.getByRole('tablist', {
|
||||||
// Skip Debug tab as it requires WebSocket connection
|
name: 'Configure & debug / Run logs',
|
||||||
await page
|
});
|
||||||
.getByRole('button', { name: 'Dashboard', exact: true })
|
const switcherPosition = await viewSwitcher.boundingBox();
|
||||||
.last()
|
await page.getByRole('tab', { name: 'Run logs' }).click();
|
||||||
.click();
|
await expect(page.getByRole('region', { name: 'Run logs' })).toBeVisible();
|
||||||
await expect(page.getByRole('region', { name: /Dashboard/ })).toBeVisible();
|
await expect
|
||||||
|
.poll(async () => (await viewSwitcher.boundingBox())?.x)
|
||||||
|
.toBe(switcherPosition?.x);
|
||||||
|
|
||||||
// Switch back to Configuration
|
// Switch back to Configuration
|
||||||
await page
|
await page.getByRole('tab', { name: 'Configure & debug' }).click();
|
||||||
.getByRole('button', { name: 'Dashboard', exact: true })
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('region', { name: 'Configuration' }),
|
page.getByRole('region', { name: 'Configuration' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ test.describe('pipeline monitoring conversation turns', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(`/home/agents?id=${pipeline.id}`);
|
await page.goto(`/home/agents?id=${pipeline.id}`);
|
||||||
await page.getByRole('tab', { name: 'Dashboard' }).click();
|
await page.getByRole('tab', { name: 'Run logs' }).click();
|
||||||
|
|
||||||
await expect(page.getByText('2 conversation turns')).toBeVisible();
|
await expect(page.getByText('2 conversation turns')).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
Reference in New Issue
Block a user