-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(site): fix price threshold display and debounce model price lookup #28298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ import { | |
| TooltipContent, | ||
| TooltipTrigger, | ||
| } from "#/components/Tooltip/Tooltip"; | ||
| import { useDebouncedValue } from "#/hooks/debounce"; | ||
| import { normalizeProvider } from "#/modules/aiModels/helpers"; | ||
| import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; | ||
| import { cn } from "#/utils/cn"; | ||
|
|
@@ -683,14 +684,25 @@ export const PricingEstimateFields: FC<{ | |
| const aibridgeEntitled = Boolean(useFeatureVisibility().aibridge); | ||
| const normalizedProvider = normalizeProvider(provider); | ||
| const trimmedModel = model.trim(); | ||
| // Debounce the pair so the lookup never mixes a new provider with the | ||
| // previous model identifier. | ||
| const debouncedLookup = useDebouncedValue( | ||
| { provider: normalizedProvider, model: trimmedModel }, | ||
| 500, | ||
| ); | ||
| const entitledWithProvider = aibridgeEntitled && normalizedProvider !== ""; | ||
| const livePricesQuery = useQuery({ | ||
| ...aiModelPrices(normalizedProvider, trimmedModel), | ||
| enabled: | ||
| aibridgeEntitled && normalizedProvider !== "" && trimmedModel !== "", | ||
| ...aiModelPrices(debouncedLookup.provider, debouncedLookup.model), | ||
| enabled: entitledWithProvider && debouncedLookup.model !== "", | ||
| }); | ||
| const debouncePending = | ||
| entitledWithProvider && | ||
| (debouncedLookup.provider !== normalizedProvider || | ||
| debouncedLookup.model !== trimmedModel); | ||
| const livePriceLoading = | ||
| livePricesQuery.fetchStatus !== "idle" && !livePricesQuery.isSuccess; | ||
| const livePrice = livePricesQuery.data?.[0]; | ||
| debouncePending || | ||
| (livePricesQuery.fetchStatus !== "idle" && !livePricesQuery.isSuccess); | ||
| const livePrice = debouncePending ? undefined : livePricesQuery.data?.[0]; | ||
|
|
||
| const knownModel = | ||
| findKnownModelByCanonicalId(normalizedProvider, trimmedModel) ?? | ||
|
|
@@ -709,7 +721,7 @@ export const PricingEstimateFields: FC<{ | |
| } | ||
| : knownModel; | ||
|
|
||
| if (livePricesQuery.isError) { | ||
| if (!debouncePending && livePricesQuery.isError) { | ||
| return ( | ||
| <p className="m-0 flex items-center gap-1.5 text-xs text-content-secondary sm:col-span-full"> | ||
| <InfoIcon className="size-3.5 shrink-0" /> | ||
|
|
@@ -735,13 +747,15 @@ export const PricingEstimateFields: FC<{ | |
| {priceEstimateFields.map(([label, key]) => { | ||
| const cost = costs?.[key]; | ||
| const fieldId = `${fieldIdPrefix}-${label.toLowerCase().replace(/\s+/g, "-")}`; | ||
| const displayValue = | ||
| cost === undefined ? "" : formatPricePerMillionTokens(cost).slice(1); | ||
| const price = | ||
| cost === undefined ? undefined : formatPricePerMillionTokens(cost); | ||
| return ( | ||
| <div key={label} className="flex min-w-0 flex-col gap-1.5"> | ||
| <FieldLabel htmlFor={fieldId} label={label} /> | ||
| <InputGroup className="cursor-not-allowed bg-surface-secondary"> | ||
| <InputGroupAddon align="inline-start">$</InputGroupAddon> | ||
| <InputGroupAddon align="inline-start"> | ||
| {price?.belowThreshold ? "<$" : "$"} | ||
| </InputGroupAddon> | ||
|
Comment on lines
+756
to
+758
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a price below the threshold, Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 449103e. Below-threshold price inputs now carry Reply generated by Coder Agents on behalf of @DanielleMaywood. |
||
| {livePriceLoading ? ( | ||
| <Skeleton | ||
| aria-label={`${label} price loading`} | ||
|
|
@@ -751,10 +765,18 @@ export const PricingEstimateFields: FC<{ | |
| <InputGroupInput | ||
| id={fieldId} | ||
| className="min-w-0 cursor-not-allowed text-content-secondary" | ||
| value={displayValue} | ||
| value={price?.value ?? ""} | ||
| aria-describedby={ | ||
| price?.belowThreshold ? `${fieldId}-threshold` : undefined | ||
| } | ||
| readOnly | ||
| /> | ||
| )} | ||
| {price?.belowThreshold && !livePriceLoading && ( | ||
| <span id={`${fieldId}-threshold`} className="sr-only"> | ||
| {`less than $${price.value} USD per million tokens`} | ||
| </span> | ||
| )} | ||
| <InputGroupAddon align="inline-end"> | ||
| <span className="text-xs text-content-disabled"> | ||
| USD/1M tokens | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the current live-price lookup has failed and the user changes to another nonempty model,
debouncePendingbecomes true but the query remains keyed to the old model for 500 ms. ItsisErrorstate therefore wins at the return below, showing "Couldn't load pricing" for the newly typed model even though its request has not started. Suppress the old query's error while the debounce is pending so this interval uses the intended loading state.AGENTS.md reference: site/AGENTS.md:L16-L17
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ec8aeee. The previous model's error state is now suppressed while the debounce is pending, so changing the identifier after a failure shows the loading placeholder instead of "Couldn't load pricing." until the new request fires. Covered by the
CostEstimateErrorClearsWhileDebouncePendingstory.Reply generated by Coder Agents on behalf of @DanielleMaywood.