mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(ui): keep focus in the add model public name input while typing
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
cdb60af024
commit
3f25e5b9f6
3 changed files with 102 additions and 82 deletions
|
|
@ -1465,9 +1465,6 @@
|
|||
"src/components/add_model/conditional_public_model_name.tsx": {
|
||||
"local/filename-pascal-case": {
|
||||
"count": 1
|
||||
},
|
||||
"local/no-complex-jsx-arrow": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/add_model/handle_add_auto_router_submit.tsx": {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
|
@ -69,4 +70,24 @@ describe("ConditionalPublicModelName", () => {
|
|||
expect(screen.getByText("my-custom-model")).toBeInTheDocument();
|
||||
expect(screen.queryByDisplayValue("custom")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the public name input focused across keystrokes", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<MountedFormHost
|
||||
defaultValues={{
|
||||
model: ["gpt-4"],
|
||||
model_mappings: [{ public_name: "gpt-4", litellm_model: "gpt-4" }],
|
||||
}}
|
||||
>
|
||||
<ConditionalPublicModelName />
|
||||
</MountedFormHost>,
|
||||
);
|
||||
|
||||
const input = screen.getByDisplayValue("gpt-4");
|
||||
await user.type(input, "-prod");
|
||||
|
||||
expect(input).toHaveValue("gpt-4-prod");
|
||||
expect(input).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,6 +36,87 @@ const modelMappingsRule = {
|
|||
|
||||
const tooltipCodeClassName = "rounded-sm bg-background/20 px-1 py-0.5 font-mono text-xs";
|
||||
|
||||
const ANTHROPIC_1M_HEADERS = JSON.stringify(
|
||||
{ extra_headers: { "anthropic-beta": "context-1m-2025-08-07" } },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
const publicNameTooltipContent = (
|
||||
<div className="flex flex-col gap-2 text-left font-normal">
|
||||
<div>The name you specify in your API calls to LiteLLM Proxy</div>
|
||||
<div>
|
||||
<strong>Example:</strong> If you name your public model{" "}
|
||||
<code className={tooltipCodeClassName}>example-name</code>, and choose{" "}
|
||||
<code className={tooltipCodeClassName}>openai/qwen-plus-latest</code> as the LiteLLM model
|
||||
</div>
|
||||
<div>
|
||||
<strong>Usage:</strong> You make an API call to the LiteLLM proxy with{" "}
|
||||
<code className={tooltipCodeClassName}>model = "example-name"</code>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Result:</strong> LiteLLM sends <code className={tooltipCodeClassName}>qwen-plus-latest</code> to the
|
||||
provider
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const PublicNameInput: React.FC<{ readonly index: number; readonly value: string }> = ({ index, value }) => {
|
||||
const form = useFormContext<MountedFormValues>();
|
||||
const selectedProvider = useWatch({ control: form.control, name: "custom_llm_provider" });
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const typed = event.target.value;
|
||||
const litellmParams = form.getValues("litellm_extra_params") as string | undefined;
|
||||
const wantsAnthropic1m =
|
||||
selectedProvider === Providers.Anthropic && typed.endsWith("-1m") && (litellmParams ?? "").trim() === "";
|
||||
|
||||
if (wantsAnthropic1m) {
|
||||
form.setValue("litellm_extra_params", ANTHROPIC_1M_HEADERS);
|
||||
}
|
||||
|
||||
const publicName = wantsAnthropic1m ? typed.slice(0, -"-1m".length) : typed;
|
||||
const current = (form.getValues("model_mappings") as ModelMapping[]) ?? [];
|
||||
form.setValue(
|
||||
"model_mappings",
|
||||
current.map((mapping, mappingIndex) =>
|
||||
mappingIndex === index ? { ...mapping, public_name: publicName } : mapping,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return <Input value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Module-level so the header and cell renderers keep a stable identity: React treats a renderer
|
||||
* declared inside the component as a new element type on every render and remounts the input,
|
||||
* which drops focus after each keystroke.
|
||||
*/
|
||||
const columns: ColumnDef<ModelMapping>[] = [
|
||||
{
|
||||
id: "public_name",
|
||||
accessorKey: "public_name",
|
||||
header: () => (
|
||||
<span className="flex items-center">
|
||||
Public Model Name
|
||||
<SimpleTooltip content={publicNameTooltipContent} width="500px" />
|
||||
</span>
|
||||
),
|
||||
cell: ({ row }) => <PublicNameInput index={row.index} value={row.original.public_name} />,
|
||||
},
|
||||
{
|
||||
id: "litellm_model",
|
||||
accessorKey: "litellm_model",
|
||||
header: () => (
|
||||
<span className="flex items-center">
|
||||
LiteLLM Model Name
|
||||
<SimpleTooltip content={<div>The model name LiteLLM will send to the LLM API</div>} width="360px" />
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const ConditionalPublicModelName: React.FC = () => {
|
||||
const form = useFormContext<MountedFormValues>();
|
||||
|
||||
|
|
@ -124,85 +205,6 @@ const ConditionalPublicModelName: React.FC = () => {
|
|||
|
||||
if (!showPublicModelName) return null;
|
||||
|
||||
const publicNameTooltipContent = (
|
||||
<div className="flex flex-col gap-2 text-left font-normal">
|
||||
<div>The name you specify in your API calls to LiteLLM Proxy</div>
|
||||
<div>
|
||||
<strong>Example:</strong> If you name your public model{" "}
|
||||
<code className={tooltipCodeClassName}>example-name</code>, and choose{" "}
|
||||
<code className={tooltipCodeClassName}>openai/qwen-plus-latest</code> as the LiteLLM model
|
||||
</div>
|
||||
<div>
|
||||
<strong>Usage:</strong> You make an API call to the LiteLLM proxy with{" "}
|
||||
<code className={tooltipCodeClassName}>model = "example-name"</code>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Result:</strong> LiteLLM sends <code className={tooltipCodeClassName}>qwen-plus-latest</code> to the
|
||||
provider
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const liteLLMModelTooltipContent = <div>The model name LiteLLM will send to the LLM API</div>;
|
||||
|
||||
const columns: ColumnDef<ModelMapping>[] = [
|
||||
{
|
||||
id: "public_name",
|
||||
accessorKey: "public_name",
|
||||
header: () => (
|
||||
<span className="flex items-center">
|
||||
Public Model Name
|
||||
<SimpleTooltip content={publicNameTooltipContent} width="500px" />
|
||||
</span>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Input
|
||||
value={row.original.public_name}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
const newMappings = [...((form.getValues("model_mappings") as ModelMapping[]) ?? [])];
|
||||
|
||||
// Check conditions for Anthropic -1m suffix handling
|
||||
const isAnthropic = selectedProvider === Providers.Anthropic;
|
||||
const endsWith1m = newValue.endsWith("-1m");
|
||||
const litellmParams = form.getValues("litellm_extra_params") as string | undefined;
|
||||
const isLitellmParamsEmpty = !litellmParams || litellmParams.trim() === "";
|
||||
|
||||
let finalPublicName = newValue;
|
||||
|
||||
if (isAnthropic && endsWith1m && isLitellmParamsEmpty) {
|
||||
// Set litellm params with extra_headers
|
||||
const litellmParamsValue = JSON.stringify(
|
||||
{ extra_headers: { "anthropic-beta": "context-1m-2025-08-07" } },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
form.setValue("litellm_extra_params", litellmParamsValue);
|
||||
|
||||
// Remove -1m suffix from public_name
|
||||
finalPublicName = newValue.slice(0, -3); // Remove "-1m" (3 characters)
|
||||
}
|
||||
|
||||
newMappings[row.index].public_name = finalPublicName;
|
||||
form.setValue("model_mappings", newMappings);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "litellm_model",
|
||||
accessorKey: "litellm_model",
|
||||
header: () => (
|
||||
<span className="flex items-center">
|
||||
LiteLLM Model Name
|
||||
<SimpleTooltip content={liteLLMModelTooltipContent} width="360px" />
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<MountedFormField
|
||||
name="model_mappings"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue