-
-
{t('clusterInfo.supervisor')}
-
-
-
-
- {t('clusterInfo.item')}
- {t('clusterInfo.value')}
-
-
-
-
- {supervisorSummary.map((row) => (
-
- {row.label}
- {row.total ? (
- <>
- {row.value}
- {row.total}
- >
- ) : (
- {row.value}
- )}
-
- ))}
-
-
-
-
-
-
-
{t('clusterInfo.workers')}
-
-
-
-
- {t('clusterInfo.item')}
- {t('clusterInfo.value')}
-
-
-
-
- {workersSummary.map((row) => (
-
- {row.label}
- {row.total ? (
- <>
- {row.value}
- {row.total}
- >
- ) : (
- {row.value}
- )}
-
- ))}
-
-
-
-
+
+ {t('clusterInfo.supervisor')}
+ {renderSummary(supervisorSummary)}
+
-
-
- {t('clusterInfo.workerDetails')}
-
-
+
+ {t('clusterInfo.workers')}
+ {renderSummary(workersSummary)}
+
+
+ {tokenRouterEnabled && (
+
+ {t('clusterInfo.routers')}
+ {renderSummary(routerSummary)}
+
+ )}
+
+
+ {t('clusterInfo.workerDetails')}
+
@@ -261,12 +305,12 @@ export default function ClusterInfo() {
- {workerDetails.map((row, index) => (
-
+ {workerDetails.map((row) => (
+
{t('clusterInfo.worker')}
{row.ip_address}
{row.cpuUsage}
- {row.cpu_count}
+ {row.cpu_count ?? '-'}
{row.cpuMemUsage}
{row.cpuMemTotal}
{row.gpu_count}
@@ -278,7 +322,74 @@ export default function ClusterInfo() {
-
+
+
+ {tokenRouterEnabled && (
+
+
+ {t('clusterInfo.routerNodeDetails')}
+
+
+
+
+
+ {t('clusterInfo.nodeType')}
+ {t('clusterInfo.address')}
+ {t('clusterInfo.cpuUsage')}
+ {t('clusterInfo.cpuTotal')}
+ {t('clusterInfo.memUsage')}
+ {t('clusterInfo.memTotal')}
+
+
+
+ {routers.length === 0 ? (
+
+
+ {t('clusterInfo.noRouterInstances')}
+
+
+ ) : (
+ routers.map((router) => {
+ const cpuUsage =
+ typeof router.cpu_count === 'number' &&
+ typeof router.cpu_available === 'number'
+ ? (router.cpu_count - router.cpu_available).toFixed(2)
+ : '-';
+ const addressTitle =
+ router.node_id && router.node_id !== router.ip_address
+ ? `${router.ip_address}\n${router.node_id}`
+ : router.ip_address;
+ return (
+
+ {t('clusterInfo.routerNode')}
+
+ {router.ip_address || '-'}
+
+ {cpuUsage}
+
+ {typeof router.cpu_count === 'number'
+ ? router.cpu_count.toFixed(2)
+ : '-'}
+
+
+ {typeof router.mem_used === 'number'
+ ? formatFileSize(router.mem_used)
+ : '-'}
+
+
+ {typeof router.mem_total === 'number'
+ ? formatFileSize(router.mem_total)
+ : '-'}
+
+
+ );
+ })
+ )}
+
+
+
+
+ )}
);
diff --git a/frontend/src/components/pages/token-router/advanced-routing-editor.tsx b/frontend/src/components/pages/token-router/advanced-routing-editor.tsx
index 45364c7677..d907bc4231 100644
--- a/frontend/src/components/pages/token-router/advanced-routing-editor.tsx
+++ b/frontend/src/components/pages/token-router/advanced-routing-editor.tsx
@@ -15,11 +15,16 @@ import type {
TokenRouterRoutingRule,
} from '@/types/services';
+import { BackendModelSelect } from './backend-model-select';
import type { TypedRouterDraft } from './router-config-normalizer';
interface Props {
value: TypedRouterDraft;
candidates: TokenRouterBackendCandidate[];
+ candidatesLoading?: boolean;
+ candidatesLoadFailed?: boolean;
+ candidateErrors?: string[];
+ tokenizerCompatibilityVerified?: boolean;
onChange: (value: TypedRouterDraft) => void;
}
@@ -34,17 +39,17 @@ const conditionOptions = (anyLabel: string, trueLabel: string, falseLabel: strin
{ value: 'false', label: falseLabel },
];
-export function AdvancedRoutingEditor({ value, candidates, onChange }: Props) {
+export function AdvancedRoutingEditor({
+ value,
+ candidates,
+ candidatesLoading = false,
+ candidatesLoadFailed = false,
+ candidateErrors = [],
+ tokenizerCompatibilityVerified = true,
+ onChange,
+}: Props) {
const { t } = useI18n();
const backendIds = value.backends.map((backend) => backend.id).filter(Boolean);
- const candidateOptions = candidates.map((candidate) => ({
- value: candidate.model_uid,
- label: `${candidate.model_uid}${candidate.model_engine ? ` · ${candidate.model_engine}` : ''}`,
- description: candidate.eligible
- ? candidate.compatibility_reason
- : candidate.ineligible_reasons.join('; '),
- disabled: !candidate.eligible,
- }));
const updateBackend = (index: number, patch: Partial
) => {
const backends = value.backends.map((backend, itemIndex) =>
@@ -176,11 +181,13 @@ export function AdvancedRoutingEditor({ value, candidates, onChange }: Props) {
/>
-