From b35dfa32efad17a6acb01ccea6a241cc8d1ab2c5 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Fri, 3 Jul 2026 23:13:37 +0800 Subject: [PATCH] perf(channels): streamline channel test dialog layout - split model test status from result details so failures and latency no longer crowd one column. - move batch progress into toast updates to keep the dialog height stable during tests. - consolidate the channel title and model actions to reduce vertical churn. --- .../dialogs/channel-test-dialog.tsx | 315 ++++++++++-------- 1 file changed, 176 insertions(+), 139 deletions(-) diff --git a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx index 5bf22921..312e941c 100644 --- a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx @@ -17,10 +17,10 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useQueryClient } from '@tanstack/react-query' -import { - type ColumnDef, - type RowSelectionState, - type Table as TanStackTable, +import type { + ColumnDef, + RowSelectionState, + Table as TanStackTable, } from '@tanstack/react-table' import { Check, @@ -31,7 +31,14 @@ import { Settings, Trash2, } from 'lucide-react' -import { type ChangeEvent, useCallback, useMemo, useRef, useState } from 'react' +import { + type ChangeEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' @@ -54,7 +61,6 @@ import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -import { Progress } from '@/components/ui/progress' import { Select, SelectContent, @@ -224,7 +230,7 @@ function sleep(ms: number) { } function normalizeInlineError(errorText: string) { - return errorText.replace(/\s+/g, ' ').trim() + return errorText.replaceAll(/\s+/g, ' ').trim() } function getFirstErrorLine(errorText: string) { @@ -281,9 +287,11 @@ function getTestTableColumnClass(columnId: string) { case 'select': return 'w-10 min-w-10' case 'model': - return 'w-auto whitespace-nowrap' + return 'w-auto min-w-48 whitespace-nowrap' case 'status': - return 'w-70 min-w-70 max-w-70 whitespace-normal' + return 'w-28 min-w-28 whitespace-nowrap' + case 'result': + return 'w-80 min-w-80 max-w-80 whitespace-normal' case 'actions': return 'bg-popover w-24 min-w-24 whitespace-nowrap sm:w-28 sm:min-w-28' default: @@ -320,6 +328,9 @@ function ChannelTestDialogContent({ const queryClient = useQueryClient() const currentChannelId = currentRow.id const batchStopRequestedRef = useRef(false) + const batchProgressToastIdRef = useRef | null>(null) const [endpointType, setEndpointType] = useState('auto') const [isStreamTest, setIsStreamTest] = useState(false) const [searchTerm, setSearchTerm] = useState('') @@ -352,6 +363,39 @@ function ChannelTestDialogContent({ [t] ) + const dismissBatchProgressToast = useCallback(() => { + if (batchProgressToastIdRef.current === null) return + + toast.dismiss(batchProgressToastIdRef.current) + batchProgressToastIdRef.current = null + }, []) + + useEffect(() => { + if (!batchProgress) { + dismissBatchProgressToast() + return + } + + const title = isBatchStopRequested + ? t('Stopping batch test...') + : t('Batch testing models...') + const completedText = t('{{completed}}/{{total}} completed', { + completed: batchProgress.completed, + total: batchProgress.total, + }) + const resultText = t('{{success}} succeeded, {{failed}} failed', { + success: batchProgress.success, + failed: batchProgress.failed, + }) + + batchProgressToastIdRef.current = toast.loading(title, { + id: batchProgressToastIdRef.current ?? undefined, + description: `${completedText} ยท ${resultText}`, + }) + }, [batchProgress, dismissBatchProgressToast, isBatchStopRequested, t]) + + useEffect(() => dismissBatchProgressToast, [dismissBatchProgressToast]) + const resetState = useCallback(() => { batchStopRequestedRef.current = true setEndpointType('auto') @@ -571,9 +615,9 @@ function ChannelTestDialogContent({ const handleBatchTest = useCallback( async (modelsToTest: string[]) => { - const uniqueModels = Array.from( - new Set(modelsToTest.map((model) => model.trim()).filter(Boolean)) - ) + const uniqueModels = [ + ...new Set(modelsToTest.map((model) => model.trim()).filter(Boolean)), + ] if (!uniqueModels.length) return batchStopRequestedRef.current = false @@ -661,6 +705,7 @@ function ChannelTestDialogContent({ const stopped = batchStopRequestedRef.current && completedCount < uniqueModels.length + dismissBatchProgressToast() if (stopped) { toast.info( t( @@ -699,7 +744,13 @@ function ChannelTestDialogContent({ refreshChannelLists(resultPatch) } }, - [refreshChannelLists, t, testSingleModel, updateTestResult] + [ + dismissBatchProgressToast, + refreshChannelLists, + t, + testSingleModel, + updateTestResult, + ] ) const handleSelectSuccessfulModels = useCallback(() => { @@ -838,11 +889,22 @@ function ChannelTestDialogContent({ { id: 'status', header: t('Status'), + cell: ({ row }) => { + const model = row.original.model + const result = testResults[model] + return + }, + enableSorting: false, + size: 112, + }, + { + id: 'result', + header: t('Result'), cell: ({ row }) => { const model = row.original.model const result = testResults[model] return ( - - {t('Test connectivity for:')} - {currentRow.name} - + title={ + + {t('Test Channel Connection')}: + {currentRow.name} + } - contentClassName='max-h-[90vh] overflow-hidden sm:max-w-3xl' + contentClassName='max-h-[90vh] overflow-hidden sm:max-w-4xl' contentHeight='auto' bodyClassName='space-y-4' footer={ - <> - - + } >
@@ -983,12 +1042,60 @@ function ChannelTestDialogContent({
-
-
+
+

{t('Channel models')}

{t('Select models to run batch tests.')}

+
+ {isBatchTesting ? ( + + ) : ( + <> + + {successModels.length > 0 && ( + + )} + {failedModels.length > 0 && ( + + )} + + )} +
- {isBatchTesting ? ( - - ) : ( - - )}
- {batchProgress && ( - - )} - - {!isAnyTesting && - (successModels.length > 0 || failedModels.length > 0) && ( -
- {successModels.length > 0 && ( - - )} - {failedModels.length > 0 && ( - - )} -
- )} -
- + + } @@ -1123,46 +1176,36 @@ function ChannelTestDialogContent({ ) } -function BatchProgressSummary({ - progress, - isStopping, -}: { - progress: BatchProgress - isStopping: boolean -}) { +function TestStatusCell({ result }: { result?: TestResult }) { const { t } = useTranslation() - const progressValue = - progress.total > 0 - ? Math.min(100, Math.round((progress.completed / progress.total) * 100)) - : 0 - return ( -
-
-

- {isStopping - ? t('Stopping batch test...') - : t('Batch testing models...')} -

-

- {t('{{completed}}/{{total}} completed', { - completed: progress.completed, - total: progress.total, - })} -

-
- -

- {t('{{success}} succeeded, {{failed}} failed', { - success: progress.success, - failed: progress.failed, - })} -

-
- ) + if (!result || result.status === 'idle') { + return ( + + ) + } + + if (result.status === 'testing') { + return ( + + + + {t('Testing...')} + + + ) + } + + if (result.status === 'success') { + return ( + + ) + } + + return } -function TestStatusCell({ +function TestResultCell({ result, model, onOpenDetails, @@ -1174,9 +1217,7 @@ function TestStatusCell({ const { t } = useTranslation() if (!result || result.status === 'idle') { - return ( - - ) + return - } if (result.status === 'testing') { @@ -1189,20 +1230,17 @@ function TestStatusCell({ } if (result.status === 'success') { - return ( -
- - {typeof result.responseTime === 'number' && ( - - {formatResponseTime(result.responseTime, t)} - - )} -
+ return typeof result.responseTime === 'number' ? ( + + {formatResponseTime(result.responseTime, t)} + + ) : ( + - ) } return ( - - -

+

+

{summary}

-
+
{isModelPriceError && (