fix(ui): adapt the jev dashboard pieces to stable/1.102.x

(cherry picked from commit 4bfac88281)
This commit is contained in:
mateo-berri 2026-09-22 17:40:03 -07:00 • committed by mateo
parent 4bfd03e3de
commit ba74b6a3b6
7 changed files with 24 additions and 94 deletions

View file

@ -1,3 +1,4 @@
import { transitionClassifierType } from "./classifier_type_transition";
import JevClassifierConfig from "./JevClassifierConfig";
import { Info } from "lucide-react";
import { SimpleTooltip } from "@/components/ui/tooltip";
@ -33,12 +34,10 @@ import {
DEFAULT_CLASSIFIER_FALLBACK,
DEFAULT_CLASSIFIER_TIMEOUT_MS,
DEFAULT_CLASSIFICATION_RUBRIC,
NEW_CLASSIFIER_CLASSIFICATION_RUBRIC,
ClassificationRubric,
effectiveTierLabel,
heuristicScoringRole,
usesLlmClassifier,
DEFAULT_HEURISTIC_FIRST_MAX_TIER,
usesClassifierContext,
DEFAULT_HYBRID_BOUNDARY_MARGIN,
HEURISTIC_FIRST_MAX_TIER_KEYS,
@ -270,34 +269,8 @@ const ClassificationMethodConfig: React.FC<ClassificationMethodConfigProps> = ({
const explicitlySupportedClassifierEfforts = effortOptionsByModel[classifierModel];
const handleClassifierTypeChange = (classifierType: ClassifierType) => {
const nextValue: ComplexityRouterConfigValue = {
...value,
classifier_type: classifierType,
classifier_llm_config: usesLlmClassifier(classifierType)
? value.classifier_llm_config ?? {
model: "",
timeout_ms: DEFAULT_CLASSIFIER_TIMEOUT_MS,
classification_rubric: NEW_CLASSIFIER_CLASSIFICATION_RUBRIC,
}
: undefined,
classifier_context_window_size: usesLlmClassifier(classifierType)
? value.classifier_context_window_size ?? DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE
: undefined,
classifier_context_budget_chars: usesLlmClassifier(classifierType)
? value.classifier_context_budget_chars ?? DEFAULT_CLASSIFIER_CONTEXT_BUDGET_CHARS
: undefined,
classifier_context_include_assistant_turns: usesLlmClassifier(classifierType)
? value.classifier_context_include_assistant_turns
: undefined,
classifier_fallback: usesLlmClassifier(classifierType) ? value.classifier_fallback : undefined,
heuristic_first_max_tier:
classifierType === "heuristic_first"
? value.heuristic_first_max_tier ?? DEFAULT_HEURISTIC_FIRST_MAX_TIER
: undefined,
hybrid_boundary_margin:
classifierType === "hybrid" ? value.hybrid_boundary_margin ?? DEFAULT_HYBRID_BOUNDARY_MARGIN : undefined,
};
onChange(nextValue);
onChange(transitionClassifierType(value, classifierType));
};
const handleHeuristicFirstMaxTierChange = (tier: string) => {

View file

@ -3,7 +3,6 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { fireEvent, renderWithProviders, screen } from "../../../tests/test-utils";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import ClassificationMethodConfig from "./ClassificationMethodConfig";
import AutoRouterClassifierTabs from "./AutoRouterClassifierTabs";
import JevEditor from "./JevClassifierConfig";
import { type ComplexityRouterConfigValue } from "./ComplexityRouterConfig";
import {
@ -50,7 +49,7 @@ const initial: ComplexityRouterConfigValue = {
function Form() {
const [value, setValue] = useState(initial);
return (
<AutoRouterClassifierTabs value={value} onChange={setValue}>
<>
<ClassificationMethodConfig
value={value}
onChange={setValue}
@ -90,7 +89,7 @@ function Form() {
>
Probe current config
</button>
</AutoRouterClassifierTabs>
</>
);
}
@ -103,7 +102,6 @@ describe("JEV classifier editor", () => {
expect(screen.getByText("Classifier Prompt")).toBeInTheDocument();
expect(screen.getByRole("switch", { name: "Use images for classification" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("radio", { name: /JEV Classifier/ }));
expect(screen.getByRole("tab", { name: "Complexity" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByLabelText("JEV Model")).toHaveValue("jev-latest");
expect(screen.getByLabelText("JEV Instructions")).toBeDisabled();
expect(screen.queryByLabelText("Classifier Model")).not.toBeInTheDocument();

View file

@ -531,6 +531,17 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
setIsTestModalVisible(true);
};
const jevConnectionTestParams =
effectiveClassifierType(complexityRouterConfig) === "jev"
? {
prompt: JEV_CONNECTION_TEST_PROMPT,
config: buildComplexityRouterConfig(complexityRouterConfigParams),
defaultModel: resolveComplexityDefaultModel(complexityRouterConfig, complexityRouterConfig.default_model),
routerName: watchedName,
teamId: requiresTeamScope ? watchedTeamId ?? undefined : undefined,
}
: undefined;
return (
<TooltipProvider>
<Card>
@ -801,20 +812,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
testId={connectionTestId}
accessToken={accessToken}
targets={testTargets}
jevRequest={
effectiveClassifierType(complexityRouterConfig) === "jev"
? buildAutoRouterRoutingTestRequest({
prompt: JEV_CONNECTION_TEST_PROMPT,
config: buildComplexityRouterConfig(complexityRouterConfigParams),
defaultModel: resolveComplexityDefaultModel(
complexityRouterConfig,
complexityRouterConfig.default_model,
),
routerName: watchedName,
teamId: requiresTeamScope ? watchedTeamId ?? undefined : undefined,
})
: undefined
}
jevRequest={jevConnectionTestParams && buildAutoRouterRoutingTestRequest(jevConnectionTestParams)}
onTestComplete={() => setIsTestingConnection(false)}
/>
</TooltipProvider>

View file

@ -66,39 +66,6 @@ describe("transitionClassifierType", () => {
expect(result).toMatchObject(expectedSettings);
});
it.each(["capability", "llm_v2"] as const)("requires explicit policy input for a new %s classifier", (target) => {
const result = transitionClassifierType(standard, target);
expect(result.classifier_llm_config).toEqual({ model: "judge", timeout_ms: 20000 });
expect(result.classifier_fallback).toBeUndefined();
if (target === "capability") {
expect(result.capability_classifier_config?.base_threshold).toBeNaN();
} else {
expect(result.llm_v2_config).toMatchObject({ efficient_profile: "", capable_profile: "", harness: "" });
expect(result.llm_v2_config?.max_quality_gap).toBeNaN();
}
expect(standard.tiers.MEDIUM).toEqual(["middle"]);
expect(standard.classifier_llm_config?.classification_rubric).toBe("business");
});
it.each([
["capability", "llm"],
["capability", "heuristic_first"],
["capability", "hybrid"],
["llm_v2", "llm"],
["llm_v2", "heuristic_first"],
["llm_v2", "hybrid"],
] as const)("restores the complexity rubric from %s to %s while preserving the judge", (source, target) => {
const forecast = transitionClassifierType(standard, source);
const result = transitionClassifierType(forecast, target);
expect(result.classifier_llm_config).toEqual({
model: "judge",
timeout_ms: 20000,
classification_rubric: "agentic",
});
expect(result.capability_classifier_config).toBeUndefined();
expect(result.llm_v2_config).toBeUndefined();
});
it("clears the inactive non-reasoning pool and plan floor when switching to local classification", () => {
const initial: ComplexityRouterConfigValue = {
...standard,

View file

@ -21,6 +21,7 @@ export const transitionClassifierType = (
const judgeConfig = value.classifier_llm_config ?? { model: "", timeout_ms: DEFAULT_CLASSIFIER_TIMEOUT_MS };
const nextValue: ComplexityRouterConfigValue = {
...value,
classifier_type: classifierType,
jev_classifier_config:
classifierType === "jev" ? value.jev_classifier_config ?? defaultJevClassifierConfig() : undefined,
classification_prompt: classifierType === "jev" ? undefined : value.classification_prompt,

View file

@ -1,15 +1,7 @@
export type ClassifierType =
| "heuristic"
| "heuristic_v2"
| "llm"
| "jev"
| "heuristic_first"
| "hybrid"
| "capability"
| "llm_v2";
export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "jev" | "heuristic_first" | "hybrid";
export const usesLlmClassifier = (classifierType: ClassifierType): boolean =>
(["llm", "heuristic_first", "hybrid", "capability", "llm_v2"] as const).some((type) => type === classifierType);
(["llm", "heuristic_first", "hybrid"] as const).some((type) => type === classifierType);
export const usesClassifierContext = (classifierType: ClassifierType): boolean =>
classifierType === "jev" || usesLlmClassifier(classifierType);

View file

@ -63,7 +63,9 @@ import ComplexityRouterConfig, {
ClassifierLLMConfig,
ClassifierType,
ComplexityRouterConfigValue,
ComplexityTiers,
effectiveClassifierType,
heuristicScoringRole,
DEFAULT_ADAPTIVE_WEIGHTS,
DEFAULT_SESSION_AFFINITY,
DEFAULT_DEPLOYMENT_AFFINITY,
@ -340,9 +342,8 @@ export const buildUpdatedComplexityRouterConfig = (
keywordMatching?: KeywordMatchingState,
): Record<string, unknown> => {
const isManaged = (key: string): boolean => {
if (key === "classifier_context_per_turn_chars") {
if (key === "classifier_context_per_turn_chars")
return !usesClassifierContext(effectiveClassifierType(value)) || Object.prototype.hasOwnProperty.call(value, key);
}
if (MANAGED_COMPLEXITY_ROUTER_KEYS.has(key)) return true;
if (keywordMatching !== undefined && KEYWORD_MATCHING_KEYS.has(key)) return true;
return customTechnicalKeywords !== undefined && key === "custom_technical_keywords";