mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
fix(usage): reduce price input step to 0.0001 for sub-cent precision (#2793)
The step was 0.01, preventing input of prices like DeepSeek's cache read cost ($0.0028/million tokens). Extract step value to a constant and apply to all four price fields. Closes #2503
This commit is contained in:
@@ -16,6 +16,8 @@ interface PricingEditModalProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PRICE_INPUT_STEP = "0.0001";
|
||||||
|
|
||||||
export function PricingEditModal({
|
export function PricingEditModal({
|
||||||
open,
|
open,
|
||||||
model,
|
model,
|
||||||
@@ -151,7 +153,7 @@ export function PricingEditModal({
|
|||||||
<Input
|
<Input
|
||||||
id="inputCost"
|
id="inputCost"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step={PRICE_INPUT_STEP}
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.inputCost}
|
value={formData.inputCost}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -168,7 +170,7 @@ export function PricingEditModal({
|
|||||||
<Input
|
<Input
|
||||||
id="outputCost"
|
id="outputCost"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step={PRICE_INPUT_STEP}
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.outputCost}
|
value={formData.outputCost}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -188,7 +190,7 @@ export function PricingEditModal({
|
|||||||
<Input
|
<Input
|
||||||
id="cacheReadCost"
|
id="cacheReadCost"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step={PRICE_INPUT_STEP}
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.cacheReadCost}
|
value={formData.cacheReadCost}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -208,7 +210,7 @@ export function PricingEditModal({
|
|||||||
<Input
|
<Input
|
||||||
id="cacheCreationCost"
|
id="cacheCreationCost"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step={PRICE_INPUT_STEP}
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.cacheCreationCost}
|
value={formData.cacheCreationCost}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { PricingEditModal } from "@/components/usage/PricingEditModal";
|
||||||
|
import type { ModelPricing } from "@/types/usage";
|
||||||
|
|
||||||
|
vi.mock("react-i18next", () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string, options?: string | { defaultValue?: string }) =>
|
||||||
|
typeof options === "string" ? options : options?.defaultValue ?? key,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("sonner", () => ({
|
||||||
|
toast: {
|
||||||
|
success: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/query/usage", () => ({
|
||||||
|
useUpdateModelPricing: () => ({
|
||||||
|
mutateAsync: vi.fn(),
|
||||||
|
isPending: false,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/common/FullScreenPanel", () => ({
|
||||||
|
FullScreenPanel: ({
|
||||||
|
children,
|
||||||
|
footer,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
footer?: React.ReactNode;
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
{children}
|
||||||
|
{footer}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const model: ModelPricing = {
|
||||||
|
modelId: "deepseek-v4",
|
||||||
|
displayName: "DeepSeek V4",
|
||||||
|
inputCostPerMillion: "1",
|
||||||
|
outputCostPerMillion: "3",
|
||||||
|
cacheReadCostPerMillion: "0.0028",
|
||||||
|
cacheCreationCostPerMillion: "0",
|
||||||
|
};
|
||||||
|
|
||||||
|
const PRICE_FIELDS = [
|
||||||
|
{ id: "inputCost", label: "输入成本" },
|
||||||
|
{ id: "outputCost", label: "输出成本" },
|
||||||
|
{ id: "cacheReadCost", label: "缓存读取成本" },
|
||||||
|
{ id: "cacheCreationCost", label: "缓存写入成本" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
describe("PricingEditModal", () => {
|
||||||
|
it("all price inputs have step=0.0001", () => {
|
||||||
|
render(<PricingEditModal open model={model} onClose={() => {}} />);
|
||||||
|
|
||||||
|
for (const { id } of PRICE_FIELDS) {
|
||||||
|
const input = screen.getByLabelText(/每百万 tokens/ as unknown as string, {
|
||||||
|
selector: `#${id}`,
|
||||||
|
}) as HTMLInputElement;
|
||||||
|
expect(input).toHaveAttribute("step", "0.0001");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts precise cache read cost like 0.0028", () => {
|
||||||
|
render(<PricingEditModal open model={model} onClose={() => {}} />);
|
||||||
|
|
||||||
|
const cacheReadInput = document.getElementById(
|
||||||
|
"cacheReadCost",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
expect(cacheReadInput.value).toBe("0.0028");
|
||||||
|
expect(cacheReadInput.checkValidity()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows user to input sub-cent prices via change event", () => {
|
||||||
|
render(
|
||||||
|
<PricingEditModal open model={model} onClose={() => {}} isNew />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const cacheReadInput = document.getElementById(
|
||||||
|
"cacheReadCost",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
|
||||||
|
fireEvent.change(cacheReadInput, { target: { value: "0.0015" } });
|
||||||
|
expect(cacheReadInput.value).toBe("0.0015");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user