Files
cline/webview-ui/src/components/settings/providers/OpenRouterProvider.tsx
T

151 lines
5.5 KiB
TypeScript

import { ApiConfiguration } from "@shared/api"
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { DropdownContainer } from "../common/ModelSelector"
import { useState } from "react"
import { getOpenRouterAuthUrl } from "../utils/providerUtils"
import { useOpenRouterKeyInfo } from "../../ui/hooks/useOpenRouterKeyInfo"
import VSCodeButtonLink from "../../common/VSCodeButtonLink"
import OpenRouterModelPicker, { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../OpenRouterModelPicker"
import { formatPrice } from "../utils/pricingUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Component to display OpenRouter balance information
*/
const OpenRouterBalanceDisplay = ({ apiKey }: { apiKey: string }) => {
const { data: keyInfo, isLoading, error } = useOpenRouterKeyInfo(apiKey)
if (isLoading) {
return <span style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)" }}>Loading...</span>
}
if (error || !keyInfo || keyInfo.limit === null) {
// Don't show anything if there's an error, no info, or no limit set
return null
}
// Calculate remaining balance
const remainingBalance = keyInfo.limit - keyInfo.usage
const formattedBalance = formatPrice(remainingBalance)
return (
<VSCodeLink
href="https://openrouter.ai/settings/keys"
title={`Remaining balance: ${formattedBalance}\nLimit: ${formatPrice(keyInfo.limit)}\nUsage: ${formatPrice(keyInfo.usage)}`}
style={{
fontSize: "12px",
color: "var(--vscode-foreground)",
textDecoration: "none",
fontWeight: 500,
paddingLeft: 4,
cursor: "pointer",
}}>
Balance: {formattedBalance}
</VSCodeLink>
)
}
/**
* Props for the OpenRouterProvider component
*/
interface OpenRouterProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: "plan" | "act"
}
/**
* The OpenRouter provider configuration component
*/
export const OpenRouterProvider = ({ showModelOptions, isPopup, currentMode }: OpenRouterProviderProps) => {
const { apiConfiguration, uriScheme } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const [providerSortingSelected, setProviderSortingSelected] = useState(!!apiConfiguration?.openRouterProviderSorting)
return (
<div>
<div>
<DebouncedTextField
initialValue={apiConfiguration?.openRouterApiKey || ""}
onChange={(value) => handleFieldChange("openRouterApiKey", value)}
style={{ width: "100%" }}
type="password"
placeholder="Enter API Key...">
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%" }}>
<span style={{ fontWeight: 500 }}>OpenRouter API Key</span>
{apiConfiguration?.openRouterApiKey && (
<OpenRouterBalanceDisplay apiKey={apiConfiguration.openRouterApiKey} />
)}
</div>
</DebouncedTextField>
{!apiConfiguration?.openRouterApiKey && (
<VSCodeButtonLink
href={getOpenRouterAuthUrl(uriScheme)}
style={{ margin: "5px 0 0 0" }}
appearance="secondary">
Get OpenRouter API Key
</VSCodeButtonLink>
)}
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.
</p>
</div>
{showModelOptions && (
<>
<VSCodeCheckbox
style={{ marginTop: -10 }}
checked={providerSortingSelected}
onChange={(e: any) => {
const isChecked = e.target.checked === true
setProviderSortingSelected(isChecked)
if (!isChecked) {
handleFieldChange("openRouterProviderSorting", "")
}
}}>
Sort underlying provider routing
</VSCodeCheckbox>
{providerSortingSelected && (
<div style={{ marginBottom: -6 }}>
<DropdownContainer className="dropdown-container" zIndex={OPENROUTER_MODEL_PICKER_Z_INDEX + 1}>
<VSCodeDropdown
style={{ width: "100%", marginTop: 3 }}
value={apiConfiguration?.openRouterProviderSorting}
onChange={(e: any) => {
handleFieldChange("openRouterProviderSorting", e.target.value)
}}>
<VSCodeOption value="">Default</VSCodeOption>
<VSCodeOption value="price">Price</VSCodeOption>
<VSCodeOption value="throughput">Throughput</VSCodeOption>
<VSCodeOption value="latency">Latency</VSCodeOption>
</VSCodeDropdown>
</DropdownContainer>
<p style={{ fontSize: "12px", marginTop: 3, color: "var(--vscode-descriptionForeground)" }}>
{!apiConfiguration?.openRouterProviderSorting &&
"Default behavior is to load balance requests across providers (like AWS, Google Vertex, Anthropic), prioritizing price while considering provider uptime"}
{apiConfiguration?.openRouterProviderSorting === "price" &&
"Sort providers by price, prioritizing the lowest cost provider"}
{apiConfiguration?.openRouterProviderSorting === "throughput" &&
"Sort providers by throughput, prioritizing the provider with the highest throughput (may increase cost)"}
{apiConfiguration?.openRouterProviderSorting === "latency" &&
"Sort providers by response time, prioritizing the provider with the lowest latency"}
</p>
</div>
)}
<OpenRouterModelPicker isPopup={isPopup} currentMode={currentMode} />
</>
)}
</div>
)
}