Skip to content

Commit 5d6fc17

Browse files
committed
feat(ui): OpenAI Native service tier dropdown + tiered pricing table; hide redundant price rows
1 parent c25cfde commit 5d6fc17

File tree

3 files changed

+128
-16
lines changed

3 files changed

+128
-16
lines changed

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,11 @@ const ApiOptions = ({
496496
)}
497497

498498
{selectedProvider === "openai-native" && (
499-
<OpenAI apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
499+
<OpenAI
500+
apiConfiguration={apiConfiguration}
501+
setApiConfigurationField={setApiConfigurationField}
502+
selectedModelInfo={selectedModelInfo}
503+
/>
500504
)}
501505

502506
{selectedProvider === "mistral" && (

webview-ui/src/components/settings/ModelInfoView.tsx

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ export const ModelInfoView = ({
2525
}: ModelInfoViewProps) => {
2626
const { t } = useAppTranslation()
2727

28-
const infoItems = [
28+
// Show tiered pricing table for OpenAI Native when model supports non-standard tiers
29+
const allowedTiers =
30+
(modelInfo?.allowedServiceTiers || []).filter((tier) => tier === "flex" || tier === "priority") ?? []
31+
const tierPricing = modelInfo?.serviceTierPricing
32+
const shouldShowTierPricingTable = apiProvider === "openai-native" && allowedTiers.length > 0 && !!tierPricing
33+
const fmt = (n?: number) => (typeof n === "number" ? `${formatPrice(n)}` : "—")
34+
35+
const baseInfoItems = [
2936
typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && (
3037
<>
3138
<span className="font-medium">{t("settings:modelInfo.contextWindow")}</span>{" "}
@@ -53,6 +60,21 @@ export const ModelInfoView = ({
5360
supportsLabel={t("settings:modelInfo.supportsPromptCache")}
5461
doesNotSupportLabel={t("settings:modelInfo.noPromptCache")}
5562
/>,
63+
apiProvider === "gemini" && (
64+
<span className="italic">
65+
{selectedModelId.includes("pro-preview")
66+
? t("settings:modelInfo.gemini.billingEstimate")
67+
: t("settings:modelInfo.gemini.freeRequests", {
68+
count: selectedModelId && selectedModelId.includes("flash") ? 15 : 2,
69+
})}{" "}
70+
<VSCodeLink href="https://ai.google.dev/pricing" className="text-sm">
71+
{t("settings:modelInfo.gemini.pricingDetails")}
72+
</VSCodeLink>
73+
</span>
74+
),
75+
].filter(Boolean)
76+
77+
const priceInfoItems = [
5678
modelInfo?.inputPrice !== undefined && modelInfo.inputPrice > 0 && (
5779
<>
5880
<span className="font-medium">{t("settings:modelInfo.inputPrice")}:</span>{" "}
@@ -77,20 +99,10 @@ export const ModelInfoView = ({
7799
{formatPrice(modelInfo.cacheWritesPrice || 0)} / 1M tokens
78100
</>
79101
),
80-
apiProvider === "gemini" && (
81-
<span className="italic">
82-
{selectedModelId.includes("pro-preview")
83-
? t("settings:modelInfo.gemini.billingEstimate")
84-
: t("settings:modelInfo.gemini.freeRequests", {
85-
count: selectedModelId && selectedModelId.includes("flash") ? 15 : 2,
86-
})}{" "}
87-
<VSCodeLink href="https://ai.google.dev/pricing" className="text-sm">
88-
{t("settings:modelInfo.gemini.pricingDetails")}
89-
</VSCodeLink>
90-
</span>
91-
),
92102
].filter(Boolean)
93103

104+
const infoItems = shouldShowTierPricingTable ? baseInfoItems : [...baseInfoItems, ...priceInfoItems]
105+
94106
return (
95107
<>
96108
{modelInfo?.description && (
@@ -106,6 +118,62 @@ export const ModelInfoView = ({
106118
<div key={index}>{item}</div>
107119
))}
108120
</div>
121+
122+
{shouldShowTierPricingTable && (
123+
<div className="mt-2">
124+
<div className="text-xs text-vscode-descriptionForeground mb-1">
125+
Pricing by service tier (price per 1M tokens)
126+
</div>
127+
<div className="border border-vscode-dropdown-border rounded-xs overflow-hidden">
128+
<table className="w-full text-sm">
129+
<thead className="bg-vscode-dropdown-background">
130+
<tr>
131+
<th className="text-left px-3 py-1.5">Tier</th>
132+
<th className="text-right px-3 py-1.5">Input</th>
133+
<th className="text-right px-3 py-1.5">Output</th>
134+
<th className="text-right px-3 py-1.5">Cache reads</th>
135+
</tr>
136+
</thead>
137+
<tbody>
138+
<tr className="border-t border-vscode-dropdown-border/60">
139+
<td className="px-3 py-1.5">Standard</td>
140+
<td className="px-3 py-1.5 text-right">{fmt(modelInfo?.inputPrice)}</td>
141+
<td className="px-3 py-1.5 text-right">{fmt(modelInfo?.outputPrice)}</td>
142+
<td className="px-3 py-1.5 text-right">{fmt(modelInfo?.cacheReadsPrice)}</td>
143+
</tr>
144+
{allowedTiers.includes("flex") && (
145+
<tr className="border-t border-vscode-dropdown-border/60">
146+
<td className="px-3 py-1.5">Flex</td>
147+
<td className="px-3 py-1.5 text-right">
148+
{fmt(tierPricing?.flex?.inputPrice ?? modelInfo?.inputPrice)}
149+
</td>
150+
<td className="px-3 py-1.5 text-right">
151+
{fmt(tierPricing?.flex?.outputPrice ?? modelInfo?.outputPrice)}
152+
</td>
153+
<td className="px-3 py-1.5 text-right">
154+
{fmt(tierPricing?.flex?.cacheReadsPrice ?? modelInfo?.cacheReadsPrice)}
155+
</td>
156+
</tr>
157+
)}
158+
{allowedTiers.includes("priority") && (
159+
<tr className="border-t border-vscode-dropdown-border/60">
160+
<td className="px-3 py-1.5">Priority</td>
161+
<td className="px-3 py-1.5 text-right">
162+
{fmt(tierPricing?.priority?.inputPrice ?? modelInfo?.inputPrice)}
163+
</td>
164+
<td className="px-3 py-1.5 text-right">
165+
{fmt(tierPricing?.priority?.outputPrice ?? modelInfo?.outputPrice)}
166+
</td>
167+
<td className="px-3 py-1.5 text-right">
168+
{fmt(tierPricing?.priority?.cacheReadsPrice ?? modelInfo?.cacheReadsPrice)}
169+
</td>
170+
</tr>
171+
)}
172+
</tbody>
173+
</table>
174+
</div>
175+
</div>
176+
)}
109177
</>
110178
)
111179
}

webview-ui/src/components/settings/providers/OpenAI.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ import { useCallback, useState } from "react"
22
import { Checkbox } from "vscrui"
33
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
44

5-
import type { ProviderSettings } from "@roo-code/types"
5+
import type { ModelInfo, ProviderSettings } from "@roo-code/types"
66

77
import { useAppTranslation } from "@src/i18n/TranslationContext"
88
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
9+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@src/components/ui"
910

1011
import { inputEventTransform } from "../transforms"
1112

1213
type OpenAIProps = {
1314
apiConfiguration: ProviderSettings
1415
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
16+
selectedModelInfo?: ModelInfo
1517
}
1618

17-
export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIProps) => {
19+
export const OpenAI = ({ apiConfiguration, setApiConfigurationField, selectedModelInfo }: OpenAIProps) => {
1820
const { t } = useAppTranslation()
1921

2022
const [openAiNativeBaseUrlSelected, setOpenAiNativeBaseUrlSelected] = useState(
@@ -72,6 +74,44 @@ export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIPro
7274
{t("settings:providers.getOpenAiApiKey")}
7375
</VSCodeButtonLink>
7476
)}
77+
78+
{(() => {
79+
const allowedTiers = (selectedModelInfo?.allowedServiceTiers || []).filter(
80+
(t) => t === "flex" || t === "priority",
81+
)
82+
if (allowedTiers.length === 0) return null
83+
84+
return (
85+
<div className="flex flex-col gap-1 mt-2" data-testid="openai-service-tier">
86+
<div className="flex items-center gap-1">
87+
<label className="block font-medium mb-1">Service tier</label>
88+
<StandardTooltip content="For faster processing of API requests, try the priority processing service tier. For lower prices with higher latency, try the flex processing tier.">
89+
<i className="codicon codicon-info text-vscode-descriptionForeground text-xs" />
90+
</StandardTooltip>
91+
</div>
92+
93+
<Select
94+
value={apiConfiguration.openAiNativeServiceTier || "default"}
95+
onValueChange={(value) =>
96+
setApiConfigurationField(
97+
"openAiNativeServiceTier",
98+
value as ProviderSettings["openAiNativeServiceTier"],
99+
)
100+
}>
101+
<SelectTrigger className="w-full">
102+
<SelectValue placeholder={t("settings:common.select")} />
103+
</SelectTrigger>
104+
<SelectContent>
105+
<SelectItem value="default">Standard</SelectItem>
106+
{allowedTiers.includes("flex") && <SelectItem value="flex">Flex</SelectItem>}
107+
{allowedTiers.includes("priority") && (
108+
<SelectItem value="priority">Priority</SelectItem>
109+
)}
110+
</SelectContent>
111+
</Select>
112+
</div>
113+
)
114+
})()}
75115
</>
76116
)
77117
}

0 commit comments

Comments
 (0)