mirror of
https://github.com/cline/cline.git
synced 2026-09-19 02:05:44 +08:00
151 lines
5.5 KiB
TypeScript
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>
|
|
)
|
|
}
|