mirror of
https://github.com/cline/cline.git
synced 2026-09-06 12:28:08 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8c0033bf7 | |||
| 8bf86592f0 | |||
| c08ee165ee | |||
| 85d723db29 | |||
| 8397aee744 | |||
| c921a7c2b3 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Break up ApiOptions.tsx, simplifying provider adding process, and clean up styles for each provider option
|
||||
@@ -28,13 +28,14 @@ import { vscode } from "@/utils/vscode"
|
||||
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
|
||||
import Thumbnails from "@/components/common/Thumbnails"
|
||||
import Tooltip from "@/components/common/Tooltip"
|
||||
import ApiOptions, { normalizeApiConfiguration } from "@/components/settings/ApiOptions"
|
||||
import ApiOptions from "@/components/settings/ApiOptions/ApiOptions"
|
||||
import { MAX_IMAGES_PER_MESSAGE } from "@/components/chat/ChatView"
|
||||
import ContextMenu from "@/components/chat/ContextMenu"
|
||||
import SlashCommandMenu from "@/components/chat/SlashCommandMenu"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import ServersToggleModal from "./ServersToggleModal"
|
||||
import ClineRulesToggleModal from "../cline-rules/ClineRulesToggleModal"
|
||||
import { normalizeApiConfiguration } from "@/utils/providers"
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
inputValue: string
|
||||
|
||||
@@ -19,7 +19,6 @@ import { getApiMetrics } from "@shared/getApiMetrics"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import HistoryPreview from "@/components/history/HistoryPreview"
|
||||
import { normalizeApiConfiguration } from "@/components/settings/ApiOptions"
|
||||
import Announcement from "@/components/chat/Announcement"
|
||||
import AutoApproveMenu from "@/components/chat/AutoApproveMenu"
|
||||
import BrowserSessionRow from "@/components/chat/BrowserSessionRow"
|
||||
@@ -27,6 +26,7 @@ import ChatRow from "@/components/chat/ChatRow"
|
||||
import ChatTextArea from "@/components/chat/ChatTextArea"
|
||||
import TaskHeader from "@/components/chat/TaskHeader"
|
||||
import TelemetryBanner from "@/components/common/TelemetryBanner"
|
||||
import { normalizeApiConfiguration } from "@/utils/providers"
|
||||
|
||||
interface ChatViewProps {
|
||||
isHidden: boolean
|
||||
|
||||
@@ -8,7 +8,7 @@ import { formatLargeNumber } from "@/utils/format"
|
||||
import { formatSize } from "@/utils/format"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import Thumbnails from "@/components/common/Thumbnails"
|
||||
import { normalizeApiConfiguration } from "@/components/settings/ApiOptions"
|
||||
import { normalizeApiConfiguration } from "@/utils/providers"
|
||||
import { validateSlashCommand } from "@/utils/slash-commands"
|
||||
|
||||
interface TaskHeaderProps {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,141 @@
|
||||
import { memo, useCallback, useMemo } from "react"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import OpenRouterModelPicker from "./model/OpenRouterModelPicker"
|
||||
import RequestyModelPicker from "./model/RequestyModelPicker"
|
||||
import ProviderSelectDropdown from "./ProviderSelectDropdown"
|
||||
import { normalizeApiConfiguration } from "@/utils/providers"
|
||||
import * as ProviderOptions from "./providers"
|
||||
import OpenRouterProviderSorter from "./OpenRouterProviderSorter"
|
||||
import ModelPicker from "./model/ModelPicker"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
apiErrorMessage?: string
|
||||
modelIdErrorMessage?: string
|
||||
isPopup?: boolean
|
||||
}
|
||||
|
||||
interface ProviderOptionKey {
|
||||
id: string
|
||||
component: keyof typeof ProviderOptions
|
||||
}
|
||||
|
||||
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup }: ApiOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
const newValue = event.target.value
|
||||
|
||||
// Update local state
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
[field]: newValue,
|
||||
})
|
||||
|
||||
// If the field is the provider, save it immediately
|
||||
// Necessary for favorite model selection to work without undoing provider changes
|
||||
if (field === "apiProvider") {
|
||||
vscode.postMessage({
|
||||
type: "apiConfiguration",
|
||||
apiConfiguration: {
|
||||
...apiConfiguration,
|
||||
apiProvider: newValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
|
||||
return normalizeApiConfiguration(apiConfiguration)
|
||||
}, [apiConfiguration])
|
||||
|
||||
const providerOptionsList: ProviderOptionKey[] = [
|
||||
{ id: "cline", component: "ClineOptions" },
|
||||
{ id: "asksage", component: "AskSageOptions" },
|
||||
{ id: "anthropic", component: "AnthropicOptions" },
|
||||
{ id: "openai-native", component: "OpenAIOptions" },
|
||||
{ id: "deepseek", component: "DeepseekOptions" },
|
||||
{ id: "qwen", component: "QwenOptions" },
|
||||
{ id: "doubao", component: "DoubaoOptions" },
|
||||
{ id: "mistral", component: "MistralOptions" },
|
||||
{ id: "openrouter", component: "OpenRouterOptions" },
|
||||
{ id: "bedrock", component: "BedrockOptions" },
|
||||
{ id: "vertex", component: "VertexOptions" },
|
||||
{ id: "gemini", component: "GeminiOptions" },
|
||||
{ id: "openai", component: "OpenAICompatOptions" },
|
||||
{ id: "requesty", component: "RequestyOptions" },
|
||||
{ id: "together", component: "TogetherOptions" },
|
||||
{ id: "vscode-lm", component: "VscodeLMOptions" },
|
||||
{ id: "lmstudio", component: "LMStudioOptions" },
|
||||
{ id: "litellm", component: "LiteLLMOptions" },
|
||||
{ id: "ollama", component: "OllamaOptions" },
|
||||
{ id: "xai", component: "XAIOptions" },
|
||||
{ id: "sambanova", component: "SambaNovaOptions" },
|
||||
]
|
||||
|
||||
// Render the provider options based on the selected provider
|
||||
const renderProviderOptions = useCallback(() => {
|
||||
const providerOption = providerOptionsList.find((option) => option.id === selectedProvider)
|
||||
if (!providerOption) return null
|
||||
|
||||
const ProviderOptionsComponent = ProviderOptions[providerOption.component]
|
||||
|
||||
return <ProviderOptionsComponent handleInputChange={handleInputChange} />
|
||||
}, [selectedProvider, handleInputChange])
|
||||
|
||||
const usesSpecialModelPickers = ["openrouter", "cline", "openai", "ollama", "lmstudio", "vscode-lm", "litellm", "requesty"]
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5, marginBottom: isPopup ? -10 : 0 }}>
|
||||
<ProviderSelectDropdown selectedProvider={selectedProvider} onChange={handleInputChange("apiProvider")} />
|
||||
|
||||
{renderProviderOptions()}
|
||||
|
||||
{apiErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{(selectedProvider === "openrouter" || selectedProvider === "cline") && showModelOptions && (
|
||||
<>
|
||||
<OpenRouterProviderSorter />
|
||||
<OpenRouterModelPicker isPopup={isPopup} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedProvider === "requesty" && showModelOptions && <RequestyModelPicker isPopup={isPopup} />}
|
||||
|
||||
{/* Default model picker for providers that aren't handled separately */}
|
||||
{!(selectedProvider in usesSpecialModelPickers) && showModelOptions && (
|
||||
<ModelPicker
|
||||
selectedProvider={selectedProvider}
|
||||
selectedModelId={selectedModelId}
|
||||
selectedModelInfo={selectedModelInfo}
|
||||
isPopup={isPopup}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{modelIdErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{modelIdErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ApiOptions)
|
||||
@@ -0,0 +1,18 @@
|
||||
import styled from "styled-components"
|
||||
|
||||
// Higher than the OpenRouterModelPicker's z-index
|
||||
export const DROPDOWN_Z_INDEX = 1002
|
||||
|
||||
const DropdownContainer = styled.div<{ zIndex?: number }>`
|
||||
position: relative;
|
||||
z-index: ${(props) => props.zIndex || DROPDOWN_Z_INDEX};
|
||||
|
||||
// Force dropdowns to open downward
|
||||
& vscode-dropdown::part(listbox) {
|
||||
position: absolute !important;
|
||||
top: 100% !important;
|
||||
bottom: auto !important;
|
||||
}
|
||||
`
|
||||
|
||||
export default DropdownContainer
|
||||
@@ -0,0 +1,62 @@
|
||||
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useState } from "react"
|
||||
import DropdownContainer from "./DropdownContainer"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./model/OpenRouterModelPicker"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
|
||||
const OpenRouterProviderSorter = () => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
const [providerSortingSelected, setProviderSortingSelected] = useState(!!apiConfiguration?.openRouterProviderSorting)
|
||||
return (
|
||||
<>
|
||||
<VSCodeCheckbox
|
||||
style={{ marginTop: -10 }}
|
||||
checked={providerSortingSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setProviderSortingSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
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) => {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(OpenRouterProviderSorter)
|
||||
@@ -0,0 +1,59 @@
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ApiProvider } from "@shared/api"
|
||||
import DropdownContainer from "./DropdownContainer"
|
||||
|
||||
// Provider list array with name and id
|
||||
export const providerList = [
|
||||
{ id: "cline", name: "Cline" },
|
||||
{ id: "openrouter", name: "OpenRouter" },
|
||||
{ id: "anthropic", name: "Anthropic" },
|
||||
{ id: "bedrock", name: "AWS Bedrock" },
|
||||
{ id: "openai", name: "OpenAI Compatible" },
|
||||
{ id: "vertex", name: "GCP Vertex AI" },
|
||||
{ id: "gemini", name: "Google Gemini" },
|
||||
{ id: "deepseek", name: "DeepSeek" },
|
||||
{ id: "mistral", name: "Mistral" },
|
||||
{ id: "openai-native", name: "OpenAI" },
|
||||
{ id: "vscode-lm", name: "VS Code LM API" },
|
||||
{ id: "requesty", name: "Requesty" },
|
||||
{ id: "together", name: "Together" },
|
||||
{ id: "qwen", name: "Alibaba Qwen" },
|
||||
{ id: "doubao", name: "Bytedance Doubao" },
|
||||
{ id: "lmstudio", name: "LM Studio" },
|
||||
{ id: "ollama", name: "Ollama" },
|
||||
{ id: "litellm", name: "LiteLLM" },
|
||||
{ id: "asksage", name: "AskSage" },
|
||||
{ id: "xai", name: "xAI" },
|
||||
{ id: "sambanova", name: "SambaNova" },
|
||||
]
|
||||
|
||||
interface ProviderDropdownProps {
|
||||
selectedProvider: ApiProvider
|
||||
onChange: (event: any) => void
|
||||
}
|
||||
|
||||
const ProviderSelectDropdown = ({ selectedProvider, onChange }: ProviderDropdownProps) => {
|
||||
return (
|
||||
<DropdownContainer className="dropdown-container mb-2">
|
||||
<label htmlFor="api-provider">
|
||||
<span style={{ fontWeight: 500 }}>API Provider</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="api-provider"
|
||||
value={selectedProvider}
|
||||
onChange={onChange}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
position: "relative",
|
||||
}}>
|
||||
{providerList.map((provider) => (
|
||||
<VSCodeOption key={provider.id} value={provider.id}>
|
||||
{provider.name}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProviderSelectDropdown
|
||||
@@ -0,0 +1,132 @@
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useEffect, useRef, useState } from "react"
|
||||
import { useRemark } from "react-remark"
|
||||
import styled from "styled-components"
|
||||
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
|
||||
|
||||
interface ModelDescriptionMarkdownProps {
|
||||
markdown?: string
|
||||
key: string
|
||||
isExpanded: boolean
|
||||
setIsExpanded: (isExpanded: boolean) => void
|
||||
isPopup?: boolean
|
||||
}
|
||||
|
||||
const StyledMarkdown = styled.div`
|
||||
font-family:
|
||||
var(--vscode-font-family),
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
|
||||
p,
|
||||
li,
|
||||
ol,
|
||||
ul {
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ModelDescriptionMarkdown = ({ markdown, key, isExpanded, setIsExpanded, isPopup }: ModelDescriptionMarkdownProps) => {
|
||||
const [reactContent, setMarkdown] = useRemark()
|
||||
const [showSeeMore, setShowSeeMore] = useState(false)
|
||||
const textContainerRef = useRef<HTMLDivElement>(null)
|
||||
const textRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setMarkdown(markdown || "")
|
||||
}, [markdown, setMarkdown])
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current && textContainerRef.current) {
|
||||
const { scrollHeight } = textRef.current
|
||||
const { clientHeight } = textContainerRef.current
|
||||
const isOverflowing = scrollHeight > clientHeight
|
||||
setShowSeeMore(isOverflowing)
|
||||
}
|
||||
}, [reactContent, setIsExpanded])
|
||||
|
||||
return (
|
||||
<StyledMarkdown key={key} style={{ display: "inline-block", marginBottom: 0 }}>
|
||||
<div
|
||||
ref={textContainerRef}
|
||||
style={{
|
||||
overflowY: isExpanded ? "auto" : "hidden",
|
||||
position: "relative",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
<div
|
||||
ref={textRef}
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: isExpanded ? "unset" : 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
{reactContent}
|
||||
</div>
|
||||
{!isExpanded && showSeeMore && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: 30,
|
||||
height: "1.2em",
|
||||
background: "linear-gradient(to right, transparent, var(--vscode-sideBar-background))",
|
||||
}}
|
||||
/>
|
||||
<VSCodeLink
|
||||
style={{
|
||||
fontSize: "inherit",
|
||||
paddingRight: 0,
|
||||
paddingLeft: 3,
|
||||
backgroundColor: isPopup ? CODE_BLOCK_BG_COLOR : "var(--vscode-sideBar-background)",
|
||||
}}
|
||||
onClick={() => setIsExpanded(true)}>
|
||||
See more
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StyledMarkdown>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ModelDescriptionMarkdown)
|
||||
@@ -0,0 +1,29 @@
|
||||
import { memo } from "react"
|
||||
|
||||
interface ModelInfoSupportsItemProps {
|
||||
isSupported: boolean
|
||||
supportsLabel: string
|
||||
doesNotSupportLabel: string
|
||||
}
|
||||
|
||||
const ModelInfoSupportsItem = ({ isSupported, supportsLabel, doesNotSupportLabel }: ModelInfoSupportsItemProps) => (
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: isSupported ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
<i
|
||||
className={`codicon codicon-${isSupported ? "check" : "x"}`}
|
||||
style={{
|
||||
marginRight: 4,
|
||||
marginBottom: isSupported ? 1 : -1,
|
||||
fontSize: isSupported ? 11 : 13,
|
||||
fontWeight: 700,
|
||||
display: "inline-block",
|
||||
verticalAlign: "bottom",
|
||||
}}></i>
|
||||
{isSupported ? supportsLabel : doesNotSupportLabel}
|
||||
</span>
|
||||
)
|
||||
|
||||
export default memo(ModelInfoSupportsItem)
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Fragment, memo } from "react"
|
||||
import { ModelInfo, geminiModels } from "@shared/api"
|
||||
import { formatTiers } from "@/utils/providers"
|
||||
import { formatPrice } from "@/utils/format"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import ModelDescriptionMarkdown from "./ModelDescriptionMarkdown"
|
||||
import ModelInfoSupportsItem from "./ModelInfoSupportsItem"
|
||||
|
||||
interface ModelInfoViewProps {
|
||||
selectedModelId: string
|
||||
modelInfo: ModelInfo
|
||||
isDescriptionExpanded: boolean
|
||||
setIsDescriptionExpanded: (isExpanded: boolean) => void
|
||||
isPopup?: boolean
|
||||
}
|
||||
|
||||
const ModelInfoView = ({
|
||||
selectedModelId,
|
||||
modelInfo,
|
||||
isDescriptionExpanded,
|
||||
setIsDescriptionExpanded,
|
||||
isPopup,
|
||||
}: ModelInfoViewProps) => {
|
||||
const isGemini = Object.keys(geminiModels).includes(selectedModelId)
|
||||
|
||||
// Create elements for tiered pricing separately
|
||||
const inputPriceElement = modelInfo.inputPriceTiers ? (
|
||||
<Fragment key="inputPriceTiers">
|
||||
<span style={{ fontWeight: 500 }}>Input price:</span>
|
||||
<br />
|
||||
{formatTiers(modelInfo.inputPriceTiers).map((tierString, i, arr) => (
|
||||
<Fragment key={`inputTierFrag${i}`}>
|
||||
<span style={{ paddingLeft: "15px" }}>{tierString}</span>
|
||||
{i < arr.length - 1 && <br />}
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
) : modelInfo.inputPrice !== undefined && modelInfo.inputPrice > 0 ? (
|
||||
<span key="inputPrice">
|
||||
<span style={{ fontWeight: 500 }}>Input price:</span> {formatPrice(modelInfo.inputPrice)}/million tokens
|
||||
</span>
|
||||
) : null
|
||||
|
||||
const outputPriceElement = modelInfo.outputPriceTiers ? (
|
||||
<Fragment key="outputPriceTiers">
|
||||
<span style={{ fontWeight: 500 }}>Output price:</span>
|
||||
<span style={{ fontStyle: "italic" }}> (based on input tokens)</span>
|
||||
<br />
|
||||
{formatTiers(modelInfo.outputPriceTiers).map((tierString, i, arr) => (
|
||||
<Fragment key={`outputTierFrag${i}`}>
|
||||
<span style={{ paddingLeft: "15px" }}>{tierString}</span>
|
||||
{i < arr.length - 1 && <br />}
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
) : modelInfo.outputPrice !== undefined && modelInfo.outputPrice > 0 ? (
|
||||
<span key="outputPrice">
|
||||
<span style={{ fontWeight: 500 }}>Output price:</span> {formatPrice(modelInfo.outputPrice)}/million tokens
|
||||
</span>
|
||||
) : null
|
||||
|
||||
const infoItems = [
|
||||
modelInfo.description && (
|
||||
<ModelDescriptionMarkdown
|
||||
key="description"
|
||||
markdown={modelInfo.description}
|
||||
isExpanded={isDescriptionExpanded}
|
||||
setIsExpanded={setIsDescriptionExpanded}
|
||||
isPopup={isPopup}
|
||||
/>
|
||||
),
|
||||
<ModelInfoSupportsItem
|
||||
key="supportsImages"
|
||||
isSupported={modelInfo.supportsImages ?? false}
|
||||
supportsLabel="Supports images"
|
||||
doesNotSupportLabel="Does not support images"
|
||||
/>,
|
||||
<ModelInfoSupportsItem
|
||||
key="supportsComputerUse"
|
||||
isSupported={modelInfo.supportsComputerUse ?? false}
|
||||
supportsLabel="Supports computer use"
|
||||
doesNotSupportLabel="Does not support computer use"
|
||||
/>,
|
||||
!isGemini && (
|
||||
<ModelInfoSupportsItem
|
||||
key="supportsPromptCache"
|
||||
isSupported={modelInfo.supportsPromptCache}
|
||||
supportsLabel="Supports prompt caching"
|
||||
doesNotSupportLabel="Does not support prompt caching"
|
||||
/>
|
||||
),
|
||||
modelInfo.maxTokens !== undefined && modelInfo.maxTokens > 0 && (
|
||||
<span key="maxTokens">
|
||||
<span style={{ fontWeight: 500 }}>Max output:</span> {modelInfo.maxTokens?.toLocaleString()} tokens
|
||||
</span>
|
||||
),
|
||||
inputPriceElement, // Add the generated input price block
|
||||
modelInfo.supportsPromptCache && modelInfo.cacheWritesPrice && (
|
||||
<span key="cacheWritesPrice">
|
||||
<span style={{ fontWeight: 500 }}>Cache writes price:</span> {formatPrice(modelInfo.cacheWritesPrice || 0)}
|
||||
/million tokens
|
||||
</span>
|
||||
),
|
||||
modelInfo.supportsPromptCache && modelInfo.cacheReadsPrice && (
|
||||
<span key="cacheReadsPrice">
|
||||
<span style={{ fontWeight: 500 }}>Cache reads price:</span> {formatPrice(modelInfo.cacheReadsPrice || 0)}/million
|
||||
tokens
|
||||
</span>
|
||||
),
|
||||
outputPriceElement, // Add the generated output price block
|
||||
isGemini && (
|
||||
<span key="geminiInfo" style={{ fontStyle: "italic" }}>
|
||||
* Free up to {selectedModelId && selectedModelId.includes("flash") ? "15" : "2"} requests per minute. After that,
|
||||
billing depends on prompt size.{" "}
|
||||
<VSCodeLink href="https://ai.google.dev/pricing" style={{ display: "inline", fontSize: "inherit" }}>
|
||||
For more info, see pricing details.
|
||||
</VSCodeLink>
|
||||
</span>
|
||||
),
|
||||
].filter(Boolean)
|
||||
|
||||
return (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "2px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{infoItems.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
{item}
|
||||
{index < infoItems.length - 1 && <br />}
|
||||
</Fragment>
|
||||
))}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ModelInfoView)
|
||||
@@ -0,0 +1,175 @@
|
||||
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useState } from "react"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ModelInfo,
|
||||
anthropicModels,
|
||||
bedrockModels,
|
||||
vertexModels,
|
||||
geminiModels,
|
||||
openAiNativeModels,
|
||||
deepSeekModels,
|
||||
mainlandQwenModels,
|
||||
internationalQwenModels,
|
||||
doubaoModels,
|
||||
mistralModels,
|
||||
askSageModels,
|
||||
xaiModels,
|
||||
sambanovaModels,
|
||||
} from "@shared/api"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import DropdownContainer from "../DropdownContainer"
|
||||
import ThinkingBudgetSlider from "./ThinkingBudgetSlider"
|
||||
import ModelInfoView from "./ModelInfoView"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
|
||||
|
||||
const DROPDOWN_Z_INDEX = OPENROUTER_MODEL_PICKER_Z_INDEX - 1
|
||||
|
||||
interface ModelPickerProps {
|
||||
selectedProvider: string
|
||||
selectedModelId: string
|
||||
selectedModelInfo: any
|
||||
isPopup?: boolean
|
||||
handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void
|
||||
}
|
||||
|
||||
type ModelList = Record<string, ModelInfo>
|
||||
|
||||
type ModelMap = Record<string, ModelList>
|
||||
|
||||
const ModelPicker = ({ selectedProvider, selectedModelId, selectedModelInfo, isPopup, handleInputChange }: ModelPickerProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
const [reasoningEffortSelected, setReasoningEffortSelected] = useState(!!apiConfiguration?.reasoningEffort)
|
||||
|
||||
const showThinkingBudgetSlider =
|
||||
(selectedProvider === "anthropic" && selectedModelId === "claude-3-7-sonnet-20250219") ||
|
||||
(selectedProvider === "bedrock" && selectedModelId === "anthropic.claude-3-7-sonnet-20250219-v1:0") ||
|
||||
(selectedProvider === "vertex" && selectedModelId === "claude-3-7-sonnet@20250219")
|
||||
|
||||
const showReasoningEffort = selectedProvider === "xai" && selectedModelId.includes("3-mini")
|
||||
|
||||
const createModelDropdown = () => {
|
||||
let models: ModelList = {}
|
||||
|
||||
const modelMap: ModelMap = {
|
||||
anthropic: anthropicModels,
|
||||
bedrock: bedrockModels,
|
||||
vertex: vertexModels,
|
||||
gemini: geminiModels,
|
||||
"openai-native": openAiNativeModels,
|
||||
deepseek: deepSeekModels,
|
||||
doubao: doubaoModels,
|
||||
mistral: mistralModels,
|
||||
asksage: askSageModels,
|
||||
xai: xaiModels,
|
||||
sambanova: sambanovaModels,
|
||||
}
|
||||
|
||||
// Handle special case for qwen separately
|
||||
if (selectedProvider === "qwen") {
|
||||
models = apiConfiguration?.qwenApiLine === "china" ? mainlandQwenModels : internationalQwenModels
|
||||
} else {
|
||||
models = modelMap[selectedProvider] || {}
|
||||
}
|
||||
|
||||
return (
|
||||
<VSCodeDropdown
|
||||
id="model-id"
|
||||
value={selectedModelId}
|
||||
onChange={handleInputChange("apiModelId")}
|
||||
style={{ width: "100%" }}>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{Object.keys(models).map((modelId) => (
|
||||
<VSCodeOption
|
||||
key={modelId}
|
||||
value={modelId}
|
||||
style={{
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
maxWidth: "100%",
|
||||
}}>
|
||||
{modelId}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 2} className="dropdown-container">
|
||||
<label htmlFor="model-id">
|
||||
<span style={{ fontWeight: 500 }}>Model</span>
|
||||
</label>
|
||||
{createModelDropdown()}
|
||||
</DropdownContainer>
|
||||
|
||||
{showThinkingBudgetSlider && (
|
||||
<ThinkingBudgetSlider apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
)}
|
||||
|
||||
{showReasoningEffort && (
|
||||
<>
|
||||
<VSCodeCheckbox
|
||||
style={{ marginTop: 0 }}
|
||||
checked={reasoningEffortSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setReasoningEffortSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
reasoningEffort: "",
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Modify reasoning effort
|
||||
</VSCodeCheckbox>
|
||||
|
||||
{reasoningEffortSelected && (
|
||||
<div>
|
||||
<label htmlFor="reasoning-effort-dropdown">
|
||||
<span style={{}}>Reasoning Effort</span>
|
||||
</label>
|
||||
<DropdownContainer className="dropdown-container" zIndex={DROPDOWN_Z_INDEX - 100}>
|
||||
<VSCodeDropdown
|
||||
id="reasoning-effort-dropdown"
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
value={apiConfiguration?.reasoningEffort || "high"}
|
||||
onChange={(e: any) => {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
reasoningEffort: e.target.value,
|
||||
})
|
||||
}}>
|
||||
<VSCodeOption value="low">low</VSCodeOption>
|
||||
<VSCodeOption value="high">high</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
marginBottom: 0,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
High effort may produce more thorough analysis but takes longer and uses more tokens.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<ModelInfoView
|
||||
selectedModelId={selectedModelId}
|
||||
modelInfo={selectedModelInfo}
|
||||
isDescriptionExpanded={isDescriptionExpanded}
|
||||
setIsDescriptionExpanded={setIsDescriptionExpanded}
|
||||
isPopup={isPopup}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ModelPicker)
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { useRemark } from "react-remark"
|
||||
import styled from "styled-components"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { highlight } from "../history/HistoryView"
|
||||
import { highlight } from "@/components/history/HistoryView"
|
||||
|
||||
const OpenAiModelPicker: React.FC = () => {
|
||||
const { apiConfiguration, setApiConfiguration, openAiModels } = useExtensionState()
|
||||
+6
-162
@@ -1,17 +1,16 @@
|
||||
import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import Fuse from "fuse.js"
|
||||
import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useRemark } from "react-remark"
|
||||
import React, { KeyboardEvent, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useMount } from "react-use"
|
||||
import styled from "styled-components"
|
||||
import { openRouterDefaultModelId } from "@shared/api"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { highlight } from "../history/HistoryView"
|
||||
import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions"
|
||||
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
|
||||
import { highlight } from "../../../history/HistoryView"
|
||||
import ModelInfoView from "./ModelInfoView"
|
||||
import ThinkingBudgetSlider from "./ThinkingBudgetSlider"
|
||||
import FeaturedModelCard from "./FeaturedModelCard"
|
||||
import FeaturedModelCard from "../../FeaturedModelCard"
|
||||
import { normalizeApiConfiguration } from "@/utils/providers"
|
||||
|
||||
// Star icon for favorites
|
||||
const StarIcon = ({ isFavorite, onClick }: { isFavorite: boolean; onClick: (e: React.MouseEvent) => void }) => {
|
||||
@@ -212,7 +211,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div style={{ display: "flex", flexDirection: "column", marginTop: 10 }}>
|
||||
<label htmlFor="model-search">
|
||||
<span style={{ fontWeight: 500 }}>Model</span>
|
||||
</label>
|
||||
@@ -381,158 +380,3 @@ const DropdownItem = styled.div<{ isSelected: boolean }>`
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
}
|
||||
`
|
||||
|
||||
// Markdown
|
||||
|
||||
const StyledMarkdown = styled.div`
|
||||
font-family:
|
||||
var(--vscode-font-family),
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
|
||||
p,
|
||||
li,
|
||||
ol,
|
||||
ul {
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ModelDescriptionMarkdown = memo(
|
||||
({
|
||||
markdown,
|
||||
key,
|
||||
isExpanded,
|
||||
setIsExpanded,
|
||||
isPopup,
|
||||
}: {
|
||||
markdown?: string
|
||||
key: string
|
||||
isExpanded: boolean
|
||||
setIsExpanded: (isExpanded: boolean) => void
|
||||
isPopup?: boolean
|
||||
}) => {
|
||||
const [reactContent, setMarkdown] = useRemark()
|
||||
// const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [showSeeMore, setShowSeeMore] = useState(false)
|
||||
const textContainerRef = useRef<HTMLDivElement>(null)
|
||||
const textRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setMarkdown(markdown || "")
|
||||
}, [markdown, setMarkdown])
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current && textContainerRef.current) {
|
||||
const { scrollHeight } = textRef.current
|
||||
const { clientHeight } = textContainerRef.current
|
||||
const isOverflowing = scrollHeight > clientHeight
|
||||
setShowSeeMore(isOverflowing)
|
||||
// if (!isOverflowing) {
|
||||
// setIsExpanded(false)
|
||||
// }
|
||||
}
|
||||
}, [reactContent, setIsExpanded])
|
||||
|
||||
return (
|
||||
<StyledMarkdown key={key} style={{ display: "inline-block", marginBottom: 0 }}>
|
||||
<div
|
||||
ref={textContainerRef}
|
||||
style={{
|
||||
overflowY: isExpanded ? "auto" : "hidden",
|
||||
position: "relative",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
<div
|
||||
ref={textRef}
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: isExpanded ? "unset" : 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
// whiteSpace: "pre-wrap",
|
||||
// wordBreak: "break-word",
|
||||
// overflowWrap: "anywhere",
|
||||
}}>
|
||||
{reactContent}
|
||||
</div>
|
||||
{!isExpanded && showSeeMore && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: 30,
|
||||
height: "1.2em",
|
||||
background: "linear-gradient(to right, transparent, var(--vscode-sideBar-background))",
|
||||
}}
|
||||
/>
|
||||
<VSCodeLink
|
||||
style={{
|
||||
// cursor: "pointer",
|
||||
// color: "var(--vscode-textLink-foreground)",
|
||||
fontSize: "inherit",
|
||||
paddingRight: 0,
|
||||
paddingLeft: 3,
|
||||
backgroundColor: isPopup ? CODE_BLOCK_BG_COLOR : "var(--vscode-sideBar-background)",
|
||||
}}
|
||||
onClick={() => setIsExpanded(true)}>
|
||||
See more
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* {isExpanded && showSeeMore && (
|
||||
<div
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
color: "var(--vscode-textLink-foreground)",
|
||||
marginLeft: "auto",
|
||||
textAlign: "right",
|
||||
paddingRight: 2,
|
||||
}}
|
||||
onClick={() => setIsExpanded(false)}>
|
||||
See less
|
||||
</div>
|
||||
)} */}
|
||||
</StyledMarkdown>
|
||||
)
|
||||
},
|
||||
)
|
||||
+8
-7
@@ -4,13 +4,14 @@ import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from
|
||||
import { useRemark } from "react-remark"
|
||||
import { useMount } from "react-use"
|
||||
import styled from "styled-components"
|
||||
import { requestyDefaultModelId } from "../../../../src/shared/api"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { highlight } from "../history/HistoryView"
|
||||
import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions"
|
||||
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import { requestyDefaultModelId } from "../../../../../../src/shared/api"
|
||||
import { useExtensionState } from "../../../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../../../utils/vscode"
|
||||
import { highlight } from "../../../history/HistoryView"
|
||||
import ModelInfoView from "./ModelInfoView"
|
||||
import { CODE_BLOCK_BG_COLOR } from "../../../common/CodeBlock"
|
||||
import ThinkingBudgetSlider from "./ThinkingBudgetSlider"
|
||||
import { normalizeApiConfiguration } from "@/utils/providers"
|
||||
|
||||
export interface RequestyModelPickerProps {
|
||||
isPopup?: boolean
|
||||
@@ -145,7 +146,7 @@ const RequestyModelPicker: React.FC<RequestyModelPickerProps> = ({ isPopup }) =>
|
||||
}, [selectedModelId])
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%" }}>
|
||||
<div style={{ width: "100%", marginTop: 10 }}>
|
||||
<style>
|
||||
{`
|
||||
.model-item-highlight {
|
||||
@@ -0,0 +1,68 @@
|
||||
import { VSCodeCheckbox, VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useState } from "react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const AnthropicOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.apiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("apiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Anthropic API Key</span>
|
||||
</VSCodeTextField>
|
||||
|
||||
<VSCodeCheckbox
|
||||
checked={anthropicBaseUrlSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setAnthropicBaseUrlSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
anthropicBaseUrl: "",
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Use custom base URL
|
||||
</VSCodeCheckbox>
|
||||
|
||||
{anthropicBaseUrlSelected && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.anthropicBaseUrl || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
type="url"
|
||||
onInput={handleInputChange("anthropicBaseUrl")}
|
||||
placeholder="Default: https://api.anthropic.com"
|
||||
/>
|
||||
)}
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.apiKey && (
|
||||
<VSCodeLink
|
||||
href="https://console.anthropic.com/settings/keys"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get an Anthropic API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnthropicOptions
|
||||
@@ -0,0 +1,39 @@
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { askSageDefaultURL } from "@shared/api"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const AskSageOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.asksageApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("asksageApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>AskSage API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
</p>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.asksageApiUrl || askSageDefaultURL}
|
||||
style={{ width: "100%" }}
|
||||
type="url"
|
||||
onInput={handleInputChange("asksageApiUrl")}
|
||||
placeholder="Enter AskSage API URL...">
|
||||
<span style={{ fontWeight: 500 }}>AskSage API URL</span>
|
||||
</VSCodeTextField>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AskSageOptions
|
||||
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
VSCodeTextField,
|
||||
VSCodeRadioGroup,
|
||||
VSCodeRadio,
|
||||
VSCodeCheckbox,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import DropdownContainer from "../DropdownContainer"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../model/OpenRouterModelPicker"
|
||||
import { useState } from "react"
|
||||
|
||||
const BedrockOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpoint)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}>
|
||||
<VSCodeRadioGroup
|
||||
value={apiConfiguration?.awsUseProfile ? "profile" : "credentials"}
|
||||
onChange={(e) => {
|
||||
const value = (e.target as HTMLInputElement)?.value
|
||||
const useProfile = value === "profile"
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
awsUseProfile: useProfile,
|
||||
})
|
||||
}}>
|
||||
<VSCodeRadio value="credentials">AWS Credentials</VSCodeRadio>
|
||||
<VSCodeRadio value="profile">AWS Profile</VSCodeRadio>
|
||||
</VSCodeRadioGroup>
|
||||
|
||||
{apiConfiguration?.awsUseProfile ? (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsProfile || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("awsProfile")}
|
||||
placeholder="Enter profile name (default if empty)">
|
||||
<span style={{ fontWeight: 500 }}>AWS Profile Name</span>
|
||||
</VSCodeTextField>
|
||||
) : (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsAccessKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsAccessKey")}
|
||||
placeholder="Enter Access Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Access Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSecretKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsSecretKey")}
|
||||
placeholder="Enter Secret Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Secret Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSessionToken || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("awsSessionToken")}
|
||||
placeholder="Enter Session Token...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Session Token</span>
|
||||
</VSCodeTextField>
|
||||
</>
|
||||
)}
|
||||
<DropdownContainer zIndex={OPENROUTER_MODEL_PICKER_Z_INDEX - 1} className="dropdown-container">
|
||||
<label htmlFor="aws-region-dropdown">
|
||||
<span style={{ fontWeight: 500 }}>AWS Region</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="aws-region-dropdown"
|
||||
value={apiConfiguration?.awsRegion || ""}
|
||||
style={{ width: "100%" }}
|
||||
onChange={handleInputChange("awsRegion")}>
|
||||
<VSCodeOption value="">Select a region...</VSCodeOption>
|
||||
{/* The user will have to choose a region that supports the model they use, but this shouldn't be a problem since they'd have to request access for it in that region in the first place. */}
|
||||
<VSCodeOption value="us-east-1">us-east-1</VSCodeOption>
|
||||
<VSCodeOption value="us-east-2">us-east-2</VSCodeOption>
|
||||
{/* <VSCodeOption value="us-west-1">us-west-1</VSCodeOption> */}
|
||||
<VSCodeOption value="us-west-2">us-west-2</VSCodeOption>
|
||||
{/* <VSCodeOption value="af-south-1">af-south-1</VSCodeOption> */}
|
||||
{/* <VSCodeOption value="ap-east-1">ap-east-1</VSCodeOption> */}
|
||||
<VSCodeOption value="ap-south-1">ap-south-1</VSCodeOption>
|
||||
<VSCodeOption value="ap-northeast-1">ap-northeast-1</VSCodeOption>
|
||||
<VSCodeOption value="ap-northeast-2">ap-northeast-2</VSCodeOption>
|
||||
<VSCodeOption value="ap-northeast-3">ap-northeast-3</VSCodeOption>
|
||||
<VSCodeOption value="ap-southeast-1">ap-southeast-1</VSCodeOption>
|
||||
<VSCodeOption value="ap-southeast-2">ap-southeast-2</VSCodeOption>
|
||||
<VSCodeOption value="ca-central-1">ca-central-1</VSCodeOption>
|
||||
<VSCodeOption value="eu-central-1">eu-central-1</VSCodeOption>
|
||||
<VSCodeOption value="eu-central-2">eu-central-2</VSCodeOption>
|
||||
<VSCodeOption value="eu-west-1">eu-west-1</VSCodeOption>
|
||||
<VSCodeOption value="eu-west-2">eu-west-2</VSCodeOption>
|
||||
<VSCodeOption value="eu-west-3">eu-west-3</VSCodeOption>
|
||||
<VSCodeOption value="eu-north-1">eu-north-1</VSCodeOption>
|
||||
{/* <VSCodeOption value="me-south-1">me-south-1</VSCodeOption> */}
|
||||
<VSCodeOption value="sa-east-1">sa-east-1</VSCodeOption>
|
||||
<VSCodeOption value="us-gov-east-1">us-gov-east-1</VSCodeOption>
|
||||
<VSCodeOption value="us-gov-west-1">us-gov-west-1</VSCodeOption>
|
||||
{/* <VSCodeOption value="us-gov-east-1">us-gov-east-1</VSCodeOption> */}
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<VSCodeCheckbox
|
||||
checked={awsEndpointSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setAwsEndpointSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
awsBedrockEndpoint: "",
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Use custom VPC endpoint
|
||||
</VSCodeCheckbox>
|
||||
|
||||
{awsEndpointSelected && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsBedrockEndpoint || ""}
|
||||
style={{ width: "100%", marginTop: 3, marginBottom: 5 }}
|
||||
type="url"
|
||||
onInput={handleInputChange("awsBedrockEndpoint")}
|
||||
placeholder="Enter VPC Endpoint URL (optional)"
|
||||
/>
|
||||
)}
|
||||
|
||||
<VSCodeCheckbox
|
||||
checked={apiConfiguration?.awsUseCrossRegionInference || false}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
awsUseCrossRegionInference: isChecked,
|
||||
})
|
||||
}}>
|
||||
Use cross-region inference
|
||||
</VSCodeCheckbox>
|
||||
|
||||
<VSCodeCheckbox
|
||||
checked={apiConfiguration?.awsBedrockUsePromptCache || false}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
awsBedrockUsePromptCache: isChecked,
|
||||
})
|
||||
}}>
|
||||
Use prompt caching
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{apiConfiguration?.awsUseProfile ? (
|
||||
<>
|
||||
Using AWS Profile credentials from ~/.aws/credentials. Leave profile name empty to use the default
|
||||
profile. These credentials are only used locally to make API requests from this extension.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Authenticate by either providing the keys above or use the default AWS credential providers, i.e.
|
||||
~/.aws/credentials or environment variables. These credentials are only used locally to make API requests
|
||||
from this extension.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BedrockOptions
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ClineAccountInfoCard } from "../../ClineAccountInfoCard"
|
||||
|
||||
const ClineOptions = () => {
|
||||
return (
|
||||
<div style={{ marginBottom: 14, marginTop: 4 }}>
|
||||
<ClineAccountInfoCard />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClineOptions
|
||||
@@ -0,0 +1,40 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const DeepseekOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.deepSeekApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("deepSeekApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>DeepSeek API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.deepSeekApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://www.deepseek.com/"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a DeepSeek API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeepseekOptions
|
||||
@@ -0,0 +1,40 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const DoubaoOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.doubaoApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("doubaoApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Doubao API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.doubaoApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://console.volcengine.com/home"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a Doubao API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DoubaoOptions
|
||||
@@ -0,0 +1,68 @@
|
||||
import { VSCodeTextField, VSCodeLink, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import { useState } from "react"
|
||||
|
||||
const GeminiOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
const [geminiBaseUrlSelected, setGeminiBaseUrlSelected] = useState(!!apiConfiguration?.geminiBaseUrl)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.geminiApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("geminiApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Gemini API Key</span>
|
||||
</VSCodeTextField>
|
||||
|
||||
<VSCodeCheckbox
|
||||
checked={geminiBaseUrlSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setGeminiBaseUrlSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
geminiBaseUrl: "",
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Use custom base URL
|
||||
</VSCodeCheckbox>
|
||||
|
||||
{geminiBaseUrlSelected && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.geminiBaseUrl || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
type="url"
|
||||
onInput={handleInputChange("geminiBaseUrl")}
|
||||
placeholder="Default: https://generativelanguage.googleapis.com"
|
||||
/>
|
||||
)}
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.geminiApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://aistudio.google.com/apikey"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a Gemini API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GeminiOptions
|
||||
@@ -0,0 +1,80 @@
|
||||
import { VSCodeTextField, VSCodeLink, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useEvent, useInterval } from "react-use"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import type { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
|
||||
const LMStudioOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
|
||||
|
||||
// Request LM Studio models
|
||||
const requestLmStudioModels = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
type: "requestLmStudioModels",
|
||||
text: apiConfiguration?.lmStudioBaseUrl,
|
||||
})
|
||||
}, [apiConfiguration?.lmStudioBaseUrl])
|
||||
|
||||
// Request LM Studio models when component mounts
|
||||
useEffect(() => {
|
||||
requestLmStudioModels()
|
||||
}, [requestLmStudioModels])
|
||||
|
||||
// Poll LM Studio models periodically
|
||||
useInterval(requestLmStudioModels, 2000)
|
||||
|
||||
// Handle message events for LM Studio models
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
if (message.type === "lmStudioModels" && message.lmStudioModels) {
|
||||
setLmStudioModels(message.lmStudioModels)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.lmStudioBaseUrl || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="url"
|
||||
onInput={handleInputChange("lmStudioBaseUrl")}
|
||||
placeholder={"Default: http://localhost:1234"}>
|
||||
<span style={{ fontWeight: 500 }}>Base URL (optional)</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.lmStudioModelId || ""}
|
||||
style={{ width: "100%", marginTop: 10 }}
|
||||
onInput={handleInputChange("lmStudioModelId")}
|
||||
placeholder={"e.g. meta-llama-3.1-8b-instruct"}>
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
LM Studio allows you to run models locally on your computer. For instructions on how to get started, see their
|
||||
<VSCodeLink href="https://lmstudio.ai/docs" style={{ display: "inline", fontSize: "inherit" }}>
|
||||
quickstart guide.
|
||||
</VSCodeLink>
|
||||
You will also need to start LM Studio's{" "}
|
||||
<VSCodeLink href="https://lmstudio.ai/docs/basics/server" style={{ display: "inline", fontSize: "inherit" }}>
|
||||
local server
|
||||
</VSCodeLink>{" "}
|
||||
feature to use it with this extension.{" "}
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude models.
|
||||
Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LMStudioOptions
|
||||
@@ -0,0 +1,84 @@
|
||||
import { VSCodeTextField, VSCodeLink, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import ThinkingBudgetSlider from "../model/ThinkingBudgetSlider"
|
||||
|
||||
const LiteLLMOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.liteLlmApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("liteLlmApiKey")}
|
||||
placeholder="Default: noop">
|
||||
<span style={{ fontWeight: 500 }}>API Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.liteLlmBaseUrl || ""}
|
||||
style={{ width: "100%", marginTop: 10 }}
|
||||
type="url"
|
||||
onInput={handleInputChange("liteLlmBaseUrl")}
|
||||
placeholder={"Default: http://localhost:4000"}>
|
||||
<span style={{ fontWeight: 500 }}>Base URL (optional)</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.liteLlmModelId || ""}
|
||||
style={{ width: "100%", marginTop: 10 }}
|
||||
onInput={handleInputChange("liteLlmModelId")}
|
||||
placeholder={"e.g. gpt-4"}>
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</VSCodeTextField>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", marginTop: 10, marginBottom: 10 }}>
|
||||
<VSCodeCheckbox
|
||||
checked={apiConfiguration?.liteLlmUsePromptCache || false}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
liteLlmUsePromptCache: isChecked,
|
||||
})
|
||||
}}
|
||||
style={{ fontWeight: 500, color: "var(--vscode-charts-green)" }}>
|
||||
Use prompt caching (GA)
|
||||
</VSCodeCheckbox>
|
||||
<p style={{ fontSize: "12px", marginTop: 3, color: "var(--vscode-charts-green)" }}>
|
||||
Prompt caching requires a supported provider and model
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ThinkingBudgetSlider apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Extended thinking is available for models as Sonnet-3-7, o3-mini, Deepseek R1, etc. More info on{" "}
|
||||
<VSCodeLink
|
||||
href="https://docs.litellm.ai/docs/reasoning_content"
|
||||
style={{ display: "inline", fontSize: "inherit" }}>
|
||||
thinking mode configuration
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
LiteLLM provides a unified interface to access various LLM providers' models. See their{" "}
|
||||
<VSCodeLink href="https://docs.litellm.ai/docs/" style={{ display: "inline", fontSize: "inherit" }}>
|
||||
quickstart guide
|
||||
</VSCodeLink>{" "}
|
||||
for more information.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LiteLLMOptions
|
||||
@@ -0,0 +1,40 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const MistralOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.mistralApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("mistralApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Mistral API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.mistralApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://console.mistral.ai/codestral"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a Mistral API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MistralOptions
|
||||
@@ -0,0 +1,84 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useEvent, useInterval } from "react-use"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import type { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
|
||||
const OllamaOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const [ollamaModels, setOllamaModels] = useState<string[]>([])
|
||||
|
||||
// Request Ollama models
|
||||
const requestOllamaModels = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
type: "requestOllamaModels",
|
||||
text: apiConfiguration?.ollamaBaseUrl,
|
||||
})
|
||||
}, [apiConfiguration?.ollamaBaseUrl])
|
||||
|
||||
// Request Ollama models when component mounts
|
||||
useEffect(() => {
|
||||
requestOllamaModels()
|
||||
}, [requestOllamaModels])
|
||||
|
||||
// Poll Ollama models periodically
|
||||
useInterval(requestOllamaModels, 2000)
|
||||
|
||||
// Handle message events for Ollama models
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
if (message.type === "ollamaModels" && message.ollamaModels) {
|
||||
setOllamaModels(message.ollamaModels)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.ollamaBaseUrl || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="url"
|
||||
onInput={handleInputChange("ollamaBaseUrl")}
|
||||
placeholder={"Default: http://localhost:11434"}>
|
||||
<span style={{ fontWeight: 500 }}>Base URL (optional)</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.ollamaModelId || ""}
|
||||
style={{ width: "100%", marginTop: 10 }}
|
||||
onInput={handleInputChange("ollamaModelId")}
|
||||
placeholder={"e.g. llama3.1"}>
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.ollamaApiOptionsCtxNum || "32768"}
|
||||
style={{ width: "100%", marginTop: 10 }}
|
||||
onInput={handleInputChange("ollamaApiOptionsCtxNum")}
|
||||
placeholder={"e.g. 32768"}>
|
||||
<span style={{ fontWeight: 500 }}>Model Context Window</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Ollama allows you to run models locally on your computer. For instructions on how to get started, see their
|
||||
<VSCodeLink
|
||||
href="https://github.com/ollama/ollama/blob/main/README.md"
|
||||
style={{ display: "inline", fontSize: "inherit" }}>
|
||||
quickstart guide.
|
||||
</VSCodeLink>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude models.
|
||||
Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OllamaOptions
|
||||
@@ -0,0 +1,338 @@
|
||||
import { VSCodeCheckbox, VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useState } from "react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { openAiModelInfoSaneDefaults, azureOpenAiDefaultApiVersion } from "@shared/api"
|
||||
import { getAsVar, VSC_DESCRIPTION_FOREGROUND } from "@/utils/vscStyles"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const OpenAICompatOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
||||
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
|
||||
const [modelConfigurationSelected, setModelConfigurationSelected] = useState(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiBaseUrl || ""}
|
||||
style={{ width: "100%", marginBottom: 10 }}
|
||||
type="url"
|
||||
onInput={handleInputChange("openAiBaseUrl")}
|
||||
placeholder={"Enter base URL..."}>
|
||||
<span style={{ fontWeight: 500 }}>Base URL</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiApiKey || ""}
|
||||
style={{ width: "100%", marginBottom: 10 }}
|
||||
type="password"
|
||||
onInput={handleInputChange("openAiApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>API Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiModelId || ""}
|
||||
style={{ width: "100%", marginBottom: 10 }}
|
||||
onInput={handleInputChange("openAiModelId")}
|
||||
placeholder={"Enter Model ID..."}>
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</VSCodeTextField>
|
||||
|
||||
{/* OpenAI Compatible Custom Headers */}
|
||||
{(() => {
|
||||
const headerEntries = Object.entries(apiConfiguration?.openAiHeaders ?? {})
|
||||
return (
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span style={{ fontWeight: 500 }}>Custom Headers</span>
|
||||
<VSCodeButton
|
||||
onClick={() => {
|
||||
const currentHeaders = { ...(apiConfiguration?.openAiHeaders || {}) }
|
||||
const headerCount = Object.keys(currentHeaders).length
|
||||
const newKey = `header${headerCount + 1}`
|
||||
currentHeaders[newKey] = ""
|
||||
handleInputChange("openAiHeaders")({
|
||||
target: {
|
||||
value: currentHeaders,
|
||||
},
|
||||
})
|
||||
}}>
|
||||
Add Header
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
<div>
|
||||
{headerEntries.map(([key, value], index) => (
|
||||
<div key={index} style={{ display: "flex", gap: 5, marginTop: 5 }}>
|
||||
<VSCodeTextField
|
||||
value={key}
|
||||
style={{ width: "40%" }}
|
||||
placeholder="Header name"
|
||||
onInput={(e: any) => {
|
||||
const currentHeaders = apiConfiguration?.openAiHeaders ?? {}
|
||||
const newValue = e.target.value
|
||||
if (newValue && newValue !== key) {
|
||||
const { [key]: _, ...rest } = currentHeaders
|
||||
handleInputChange("openAiHeaders")({
|
||||
target: {
|
||||
value: {
|
||||
...rest,
|
||||
[newValue]: value,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<VSCodeTextField
|
||||
value={value}
|
||||
style={{ width: "40%" }}
|
||||
placeholder="Header value"
|
||||
onInput={(e: any) => {
|
||||
handleInputChange("openAiHeaders")({
|
||||
target: {
|
||||
value: {
|
||||
...(apiConfiguration?.openAiHeaders ?? {}),
|
||||
[key]: e.target.value,
|
||||
},
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => {
|
||||
const { [key]: _, ...rest } = apiConfiguration?.openAiHeaders ?? {}
|
||||
handleInputChange("openAiHeaders")({
|
||||
target: {
|
||||
value: rest,
|
||||
},
|
||||
})
|
||||
}}>
|
||||
Remove
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
||||
<VSCodeCheckbox
|
||||
checked={azureApiVersionSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setAzureApiVersionSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
azureApiVersion: "",
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Set Azure API version
|
||||
</VSCodeCheckbox>
|
||||
{azureApiVersionSelected && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.azureApiVersion || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
onInput={handleInputChange("azureApiVersion")}
|
||||
placeholder={`Default: ${azureOpenAiDefaultApiVersion}`}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
color: getAsVar(VSC_DESCRIPTION_FOREGROUND),
|
||||
display: "flex",
|
||||
margin: "10px 0",
|
||||
cursor: "pointer",
|
||||
alignItems: "center",
|
||||
}}
|
||||
onClick={() => setModelConfigurationSelected((val) => !val)}>
|
||||
<span
|
||||
className={`codicon ${modelConfigurationSelected ? "codicon-chevron-down" : "codicon-chevron-right"}`}
|
||||
style={{
|
||||
marginRight: "4px",
|
||||
}}></span>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 700,
|
||||
textTransform: "uppercase",
|
||||
}}>
|
||||
Model Configuration
|
||||
</span>
|
||||
</div>
|
||||
{modelConfigurationSelected && (
|
||||
<>
|
||||
<VSCodeCheckbox
|
||||
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
const modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo.supportsImages = isChecked
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
Supports Images
|
||||
</VSCodeCheckbox>
|
||||
<VSCodeCheckbox
|
||||
checked={!!apiConfiguration?.openAiModelInfo?.supportsComputerUse}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
let modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo = { ...modelInfo, supportsComputerUse: isChecked }
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
Supports Computer Use
|
||||
</VSCodeCheckbox>
|
||||
<VSCodeCheckbox
|
||||
checked={!!apiConfiguration?.openAiModelInfo?.isR1FormatRequired}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
let modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo = { ...modelInfo, isR1FormatRequired: isChecked }
|
||||
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
Enable R1 messages format
|
||||
</VSCodeCheckbox>
|
||||
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
|
||||
<VSCodeTextField
|
||||
value={
|
||||
apiConfiguration?.openAiModelInfo?.contextWindow
|
||||
? apiConfiguration.openAiModelInfo.contextWindow.toString()
|
||||
: openAiModelInfoSaneDefaults.contextWindow?.toString()
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
onInput={(input: any) => {
|
||||
const modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo.contextWindow = Number(input.target.value)
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
<span style={{ fontWeight: 500 }}>Context Window Size</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={
|
||||
apiConfiguration?.openAiModelInfo?.maxTokens
|
||||
? apiConfiguration.openAiModelInfo.maxTokens.toString()
|
||||
: openAiModelInfoSaneDefaults.maxTokens?.toString()
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
onInput={(input: any) => {
|
||||
const modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo.maxTokens = input.target.value
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
|
||||
</VSCodeTextField>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
|
||||
<VSCodeTextField
|
||||
value={
|
||||
apiConfiguration?.openAiModelInfo?.inputPrice
|
||||
? apiConfiguration.openAiModelInfo.inputPrice.toString()
|
||||
: openAiModelInfoSaneDefaults.inputPrice?.toString()
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
onInput={(input: any) => {
|
||||
const modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo.inputPrice = input.target.value
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
<span style={{ fontWeight: 500 }}>Input Price / 1M tokens</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={
|
||||
apiConfiguration?.openAiModelInfo?.outputPrice
|
||||
? apiConfiguration.openAiModelInfo.outputPrice.toString()
|
||||
: openAiModelInfoSaneDefaults.outputPrice?.toString()
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
onInput={(input: any) => {
|
||||
const modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
modelInfo.outputPrice = input.target.value
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
<span style={{ fontWeight: 500 }}>Output Price / 1M tokens</span>
|
||||
</VSCodeTextField>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
|
||||
<VSCodeTextField
|
||||
value={
|
||||
apiConfiguration?.openAiModelInfo?.temperature
|
||||
? apiConfiguration.openAiModelInfo.temperature.toString()
|
||||
: openAiModelInfoSaneDefaults.temperature?.toString()
|
||||
}
|
||||
onInput={(input: any) => {
|
||||
const modelInfo = apiConfiguration?.openAiModelInfo
|
||||
? apiConfiguration.openAiModelInfo
|
||||
: { ...openAiModelInfoSaneDefaults }
|
||||
|
||||
// Check if the input ends with a decimal point or has trailing zeros after decimal
|
||||
const value = input.target.value
|
||||
const shouldPreserveFormat = value.endsWith(".") || (value.includes(".") && value.endsWith("0"))
|
||||
|
||||
modelInfo.temperature =
|
||||
value === ""
|
||||
? openAiModelInfoSaneDefaults.temperature
|
||||
: shouldPreserveFormat
|
||||
? value // Keep as string to preserve decimal format
|
||||
: parseFloat(value)
|
||||
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelInfo: modelInfo,
|
||||
})
|
||||
}}>
|
||||
<span style={{ fontWeight: 500 }}>Temperature</span>
|
||||
</VSCodeTextField>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude models.
|
||||
Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenAICompatOptions
|
||||
@@ -0,0 +1,40 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const OpenAIOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiNativeApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("openAiNativeApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>OpenAI API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.openAiNativeApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://platform.openai.com/api-keys"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get an OpenAI API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenAIOptions
|
||||
@@ -0,0 +1,37 @@
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import { getOpenRouterAuthUrl } from "@/utils/providers"
|
||||
import VSCodeButtonLink from "../../../common/VSCodeButtonLink"
|
||||
|
||||
const OpenRouterOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration, uriScheme } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openRouterApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("openRouterApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>OpenRouter API Key</span>
|
||||
</VSCodeTextField>
|
||||
{!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>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenRouterOptions
|
||||
@@ -0,0 +1,66 @@
|
||||
import { VSCodeTextField, VSCodeLink, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import DropdownContainer from "../DropdownContainer"
|
||||
|
||||
const QwenOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DropdownContainer className="dropdown-container" style={{ position: "inherit" }}>
|
||||
<label htmlFor="qwen-line-provider">
|
||||
<span style={{ fontWeight: 500, marginTop: 5 }}>Alibaba API Line</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="qwen-line-provider"
|
||||
value={apiConfiguration?.qwenApiLine || "china"}
|
||||
onChange={handleInputChange("qwenApiLine")}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
position: "relative",
|
||||
}}>
|
||||
<VSCodeOption value="china">China API</VSCodeOption>
|
||||
<VSCodeOption value="international">International API</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Please select the appropriate API interface based on your location. If you are in China, choose the China API
|
||||
interface. Otherwise, choose the International API interface.
|
||||
</p>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.qwenApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("qwenApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Qwen API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.qwenApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://bailian.console.aliyun.com/"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a Qwen API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QwenOptions
|
||||
@@ -0,0 +1,23 @@
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const RequestyOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.requestyApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("requestyApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>API Key</span>
|
||||
</VSCodeTextField>
|
||||
{!apiConfiguration?.requestyApiKey && <a href="https://app.requesty.ai/manage-api">Get API Key</a>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RequestyOptions
|
||||
@@ -0,0 +1,40 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const SambaNovaOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.sambanovaApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("sambanovaApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>SambaNova API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.sambanovaApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://sambanova.ai/"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a SambaNova API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SambaNovaOptions
|
||||
@@ -0,0 +1,40 @@
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const TogetherOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.togetherApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("togetherApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>API Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.togetherModelId || ""}
|
||||
style={{ width: "100%", marginTop: 10 }}
|
||||
onInput={handleInputChange("togetherModelId")}
|
||||
placeholder={"Enter Model ID..."}>
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude models.
|
||||
Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TogetherOptions
|
||||
@@ -0,0 +1,63 @@
|
||||
import { VSCodeTextField, VSCodeLink, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import DropdownContainer from "../DropdownContainer"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../model/OpenRouterModelPicker"
|
||||
|
||||
const VertexOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.vertexProjectId || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("vertexProjectId")}
|
||||
placeholder="Enter Project ID...">
|
||||
<span style={{ fontWeight: 500 }}>Google Cloud Project ID</span>
|
||||
</VSCodeTextField>
|
||||
<DropdownContainer zIndex={OPENROUTER_MODEL_PICKER_Z_INDEX - 1} className="dropdown-container">
|
||||
<label htmlFor="vertex-region-dropdown">
|
||||
<span style={{ fontWeight: 500 }}>Google Cloud Region</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="vertex-region-dropdown"
|
||||
value={apiConfiguration?.vertexRegion || ""}
|
||||
style={{ width: "100%" }}
|
||||
onChange={handleInputChange("vertexRegion")}>
|
||||
<VSCodeOption value="">Select a region...</VSCodeOption>
|
||||
<VSCodeOption value="us-east5">us-east5</VSCodeOption>
|
||||
<VSCodeOption value="us-central1">us-central1</VSCodeOption>
|
||||
<VSCodeOption value="europe-west1">europe-west1</VSCodeOption>
|
||||
<VSCodeOption value="europe-west4">europe-west4</VSCodeOption>
|
||||
<VSCodeOption value="asia-southeast1">asia-southeast1</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
To use Google Cloud Vertex AI, you need to
|
||||
<VSCodeLink
|
||||
href="https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#before_you_begin"
|
||||
style={{ display: "inline", fontSize: "inherit" }}>
|
||||
{"1) create a Google Cloud account › enable the Vertex AI API › enable the desired Claude models,"}
|
||||
</VSCodeLink>{" "}
|
||||
<VSCodeLink
|
||||
href="https://cloud.google.com/docs/authentication/provide-credentials-adc#google-idp"
|
||||
style={{ display: "inline", fontSize: "inherit" }}>
|
||||
{"2) install the Google Cloud CLI › configure Application Default Credentials."}
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VertexOptions
|
||||
@@ -0,0 +1,109 @@
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useEvent, useInterval } from "react-use"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import DropdownContainer from "../DropdownContainer"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../model/OpenRouterModelPicker"
|
||||
import type { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import * as vscodemodels from "vscode"
|
||||
|
||||
declare module "vscode" {
|
||||
interface LanguageModelChatSelector {
|
||||
vendor?: string
|
||||
family?: string
|
||||
version?: string
|
||||
id?: string
|
||||
}
|
||||
}
|
||||
|
||||
const VscodeLMOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const [vsCodeLmModels, setVsCodeLmModels] = useState<vscodemodels.LanguageModelChatSelector[]>([])
|
||||
|
||||
// Request VS Code LM models
|
||||
const requestVsCodeLmModels = useCallback(() => {
|
||||
vscode.postMessage({ type: "requestVsCodeLmModels" })
|
||||
}, [])
|
||||
|
||||
// Request VS Code LM models when component mounts
|
||||
useEffect(() => {
|
||||
requestVsCodeLmModels()
|
||||
}, [requestVsCodeLmModels])
|
||||
|
||||
// Poll VS Code LM models periodically
|
||||
useInterval(requestVsCodeLmModels, 2000)
|
||||
|
||||
// Handle message events for VS Code LM models
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
if (message.type === "vsCodeLmModels" && message.vsCodeLmModels) {
|
||||
setVsCodeLmModels(message.vsCodeLmModels)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DropdownContainer zIndex={OPENROUTER_MODEL_PICKER_Z_INDEX - 2} className="dropdown-container">
|
||||
<label htmlFor="vscode-lm-model">
|
||||
<span style={{ fontWeight: 500 }}>Language Model</span>
|
||||
</label>
|
||||
{vsCodeLmModels.length > 0 ? (
|
||||
<VSCodeDropdown
|
||||
id="vscode-lm-model"
|
||||
value={
|
||||
apiConfiguration?.vsCodeLmModelSelector
|
||||
? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}`
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
const [vendor, family] = value.split("/")
|
||||
handleInputChange("vsCodeLmModelSelector")({
|
||||
target: {
|
||||
value: { vendor, family },
|
||||
},
|
||||
})
|
||||
}}
|
||||
style={{ width: "100%" }}>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{vsCodeLmModels.map((model) => (
|
||||
<VSCodeOption key={`${model.vendor}/${model.family}`} value={`${model.vendor}/${model.family}`}>
|
||||
{model.vendor} - {model.family}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
) : (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
The VS Code Language Model API allows you to run models provided by other VS Code extensions (including
|
||||
but not limited to GitHub Copilot). The easiest way to get started is to install the Copilot extension
|
||||
from the VS Marketplace and enabling Claude 3.7 Sonnet.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-errorForeground)",
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
Note: This is a very experimental integration and may not work as expected.
|
||||
</p>
|
||||
</DropdownContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VscodeLMOptions
|
||||
@@ -0,0 +1,49 @@
|
||||
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ProviderOptionsProps } from "./types/ProviderOptions"
|
||||
|
||||
const XAIOptions = ({ handleInputChange }: ProviderOptionsProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.xaiApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("xaiApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>X AI API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.xaiApiKey && (
|
||||
<VSCodeLink href="https://x.ai" style={{ display: "inline", fontSize: "inherit" }}>
|
||||
You can get an X AI API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
{/* Note: To fully implement this, you would need to add a handler in ClineProvider.ts */}
|
||||
{/* {apiConfiguration?.xaiApiKey && (
|
||||
<button
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "requestXAIModels",
|
||||
text: apiConfiguration?.xaiApiKey,
|
||||
})
|
||||
}}
|
||||
style={{ margin: "5px 0 0 0" }}
|
||||
className="vscode-button">
|
||||
Fetch Available Models
|
||||
</button>
|
||||
)} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default XAIOptions
|
||||
@@ -0,0 +1,21 @@
|
||||
export { default as AnthropicOptions } from "./AnthropicOptions"
|
||||
export { default as AskSageOptions } from "./AskSageOptions"
|
||||
export { default as BedrockOptions } from "./BedrockOptions"
|
||||
export { default as ClineOptions } from "./ClineOptions"
|
||||
export { default as DeepseekOptions } from "./DeepseekOptions"
|
||||
export { default as DoubaoOptions } from "./DoubaoOptions"
|
||||
export { default as GeminiOptions } from "./GeminiOptions"
|
||||
export { default as LiteLLMOptions } from "./LiteLLMOptions"
|
||||
export { default as LMStudioOptions } from "./LMStudioOptions"
|
||||
export { default as MistralOptions } from "./MistralOptions"
|
||||
export { default as OllamaOptions } from "./OllamaOptions"
|
||||
export { default as OpenAICompatOptions } from "./OpenAICompatOptions"
|
||||
export { default as OpenAIOptions } from "./OpenAIOptions"
|
||||
export { default as OpenRouterOptions } from "./OpenRouterOptions"
|
||||
export { default as QwenOptions } from "./QwenOptions"
|
||||
export { default as RequestyOptions } from "./RequestyOptions"
|
||||
export { default as SambaNovaOptions } from "./SambaNovaOptions"
|
||||
export { default as TogetherOptions } from "./TogetherOptions"
|
||||
export { default as VertexOptions } from "./VertexOptions"
|
||||
export { default as VscodeLMOptions } from "./VscodeLMOptions"
|
||||
export { default as XAIOptions } from "./XAIOptions"
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
|
||||
export interface ProviderOptionsProps {
|
||||
handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useEffect, useRef, useState } from "react"
|
||||
import { useRemark } from "react-remark"
|
||||
import styled from "styled-components"
|
||||
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
|
||||
|
||||
const StyledMarkdown = styled.div`
|
||||
font-family:
|
||||
var(--vscode-font-family),
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
|
||||
p,
|
||||
li,
|
||||
ol,
|
||||
ul {
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ModelDescriptionMarkdown = memo(
|
||||
({
|
||||
markdown,
|
||||
key,
|
||||
isExpanded,
|
||||
setIsExpanded,
|
||||
isPopup,
|
||||
}: {
|
||||
markdown?: string
|
||||
key: string
|
||||
isExpanded: boolean
|
||||
setIsExpanded: (isExpanded: boolean) => void
|
||||
isPopup?: boolean
|
||||
}) => {
|
||||
const [reactContent, setMarkdown] = useRemark()
|
||||
const [showSeeMore, setShowSeeMore] = useState(false)
|
||||
const textContainerRef = useRef<HTMLDivElement>(null)
|
||||
const textRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setMarkdown(markdown || "")
|
||||
}, [markdown, setMarkdown])
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current && textContainerRef.current) {
|
||||
const { scrollHeight } = textRef.current
|
||||
const { clientHeight } = textContainerRef.current
|
||||
const isOverflowing = scrollHeight > clientHeight
|
||||
setShowSeeMore(isOverflowing)
|
||||
}
|
||||
}, [reactContent, setIsExpanded])
|
||||
|
||||
return (
|
||||
<StyledMarkdown key={key} style={{ display: "inline-block", marginBottom: 0 }}>
|
||||
<div
|
||||
ref={textContainerRef}
|
||||
style={{
|
||||
overflowY: isExpanded ? "auto" : "hidden",
|
||||
position: "relative",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
<div
|
||||
ref={textRef}
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: isExpanded ? "unset" : 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
{reactContent}
|
||||
</div>
|
||||
{!isExpanded && showSeeMore && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: 30,
|
||||
height: "1.2em",
|
||||
background: "linear-gradient(to right, transparent, var(--vscode-sideBar-background))",
|
||||
}}
|
||||
/>
|
||||
<VSCodeLink
|
||||
style={{
|
||||
fontSize: "inherit",
|
||||
paddingRight: 0,
|
||||
paddingLeft: 3,
|
||||
backgroundColor: isPopup ? CODE_BLOCK_BG_COLOR : "var(--vscode-sideBar-background)",
|
||||
}}
|
||||
onClick={() => setIsExpanded(true)}>
|
||||
See more
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StyledMarkdown>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default ModelDescriptionMarkdown
|
||||
@@ -4,7 +4,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { validateApiConfiguration, validateModelId } from "@/utils/validate"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import SettingsButton from "@/components/common/SettingsButton"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
import ApiOptions from "./ApiOptions/ApiOptions"
|
||||
import { TabButton } from "../mcp/configuration/McpConfigurationView"
|
||||
import { useEvent } from "react-use"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
@@ -182,7 +182,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<div className="mb-[5px] mt-4">
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
className="w-full"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
import ApiOptions from "../ApiOptions"
|
||||
import ApiOptions from "../ApiOptions/ApiOptions"
|
||||
import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext"
|
||||
|
||||
vi.mock("../../../context/ExtensionStateContext", async (importOriginal) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useEffect, useState, memo } from "react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { validateApiConfiguration } from "@/utils/validate"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import ApiOptions from "@/components/settings/ApiOptions"
|
||||
import ApiOptions from "@/components/settings/ApiOptions/ApiOptions"
|
||||
import ClineLogoWhite from "@/assets/ClineLogoWhite"
|
||||
|
||||
const WelcomeView = memo(() => {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import prettyBytes from "pretty-bytes"
|
||||
|
||||
export function formatPrice(price: number) {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
export function formatLargeNumber(num: number): string {
|
||||
if (num >= 1e9) {
|
||||
return (num / 1e9).toFixed(1) + "b"
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ApiProvider,
|
||||
ModelInfo,
|
||||
anthropicModels,
|
||||
anthropicDefaultModelId,
|
||||
bedrockModels,
|
||||
bedrockDefaultModelId,
|
||||
vertexModels,
|
||||
vertexDefaultModelId,
|
||||
geminiModels,
|
||||
geminiDefaultModelId,
|
||||
openAiNativeModels,
|
||||
openAiNativeDefaultModelId,
|
||||
deepSeekModels,
|
||||
deepSeekDefaultModelId,
|
||||
mainlandQwenModels,
|
||||
internationalQwenModels,
|
||||
mainlandQwenDefaultModelId,
|
||||
internationalQwenDefaultModelId,
|
||||
doubaoModels,
|
||||
doubaoDefaultModelId,
|
||||
mistralModels,
|
||||
mistralDefaultModelId,
|
||||
askSageModels,
|
||||
askSageDefaultModelId,
|
||||
xaiModels,
|
||||
xaiDefaultModelId,
|
||||
sambanovaModels,
|
||||
sambanovaDefaultModelId,
|
||||
openRouterDefaultModelId,
|
||||
openRouterDefaultModelInfo,
|
||||
requestyDefaultModelId,
|
||||
requestyDefaultModelInfo,
|
||||
openAiModelInfoSaneDefaults,
|
||||
liteLlmModelInfoSaneDefaults,
|
||||
} from "@shared/api"
|
||||
import { formatPrice } from "./format"
|
||||
|
||||
export function getOpenRouterAuthUrl(uriScheme?: string) {
|
||||
return `https://openrouter.ai/auth?callback_url=${uriScheme || "vscode"}://saoudrizwan.claude-dev/openrouter`
|
||||
}
|
||||
|
||||
export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): {
|
||||
selectedProvider: ApiProvider
|
||||
selectedModelId: string
|
||||
selectedModelInfo: ModelInfo
|
||||
} {
|
||||
const provider = apiConfiguration?.apiProvider || "anthropic"
|
||||
const modelId = apiConfiguration?.apiModelId
|
||||
|
||||
const getProviderData = (models: Record<string, ModelInfo>, defaultId: string) => {
|
||||
let selectedModelId: string
|
||||
let selectedModelInfo: ModelInfo
|
||||
if (modelId && modelId in models) {
|
||||
selectedModelId = modelId
|
||||
selectedModelInfo = models[modelId]
|
||||
} else {
|
||||
selectedModelId = defaultId
|
||||
selectedModelInfo = models[defaultId]
|
||||
}
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId,
|
||||
selectedModelInfo,
|
||||
}
|
||||
}
|
||||
switch (provider) {
|
||||
case "anthropic":
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
case "bedrock":
|
||||
return getProviderData(bedrockModels, bedrockDefaultModelId)
|
||||
case "vertex":
|
||||
return getProviderData(vertexModels, vertexDefaultModelId)
|
||||
case "gemini":
|
||||
return getProviderData(geminiModels, geminiDefaultModelId)
|
||||
case "openai-native":
|
||||
return getProviderData(openAiNativeModels, openAiNativeDefaultModelId)
|
||||
case "deepseek":
|
||||
return getProviderData(deepSeekModels, deepSeekDefaultModelId)
|
||||
case "qwen":
|
||||
const qwenModels = apiConfiguration?.qwenApiLine === "china" ? mainlandQwenModels : internationalQwenModels
|
||||
const qwenDefaultId =
|
||||
apiConfiguration?.qwenApiLine === "china" ? mainlandQwenDefaultModelId : internationalQwenDefaultModelId
|
||||
return getProviderData(qwenModels, qwenDefaultId)
|
||||
case "doubao":
|
||||
return getProviderData(doubaoModels, doubaoDefaultModelId)
|
||||
case "mistral":
|
||||
return getProviderData(mistralModels, mistralDefaultModelId)
|
||||
case "asksage":
|
||||
return getProviderData(askSageModels, askSageDefaultModelId)
|
||||
case "openrouter":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
|
||||
selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
|
||||
}
|
||||
case "requesty":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId,
|
||||
selectedModelInfo: apiConfiguration?.requestyModelInfo || requestyDefaultModelInfo,
|
||||
}
|
||||
case "cline":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
|
||||
selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
|
||||
}
|
||||
case "openai":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.openAiModelId || "",
|
||||
selectedModelInfo: apiConfiguration?.openAiModelInfo || openAiModelInfoSaneDefaults,
|
||||
}
|
||||
case "ollama":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.ollamaModelId || "",
|
||||
selectedModelInfo: openAiModelInfoSaneDefaults,
|
||||
}
|
||||
case "lmstudio":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.lmStudioModelId || "",
|
||||
selectedModelInfo: openAiModelInfoSaneDefaults,
|
||||
}
|
||||
case "vscode-lm":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.vsCodeLmModelSelector
|
||||
? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}`
|
||||
: "",
|
||||
selectedModelInfo: {
|
||||
...openAiModelInfoSaneDefaults,
|
||||
supportsImages: false, // VSCode LM API currently doesn't support images
|
||||
},
|
||||
}
|
||||
case "litellm":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.liteLlmModelId || "",
|
||||
selectedModelInfo: liteLlmModelInfoSaneDefaults,
|
||||
}
|
||||
case "xai":
|
||||
return getProviderData(xaiModels, xaiDefaultModelId)
|
||||
case "sambanova":
|
||||
return getProviderData(sambanovaModels, sambanovaDefaultModelId)
|
||||
default:
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array of formatted tier strings
|
||||
export const formatTiers = (tiers: ModelInfo["inputPriceTiers"]): string[] => {
|
||||
if (!tiers || tiers.length === 0) {
|
||||
return []
|
||||
}
|
||||
return tiers.map((tier, index, arr) => {
|
||||
const prevLimit = index > 0 ? arr[index - 1].tokenLimit : 0
|
||||
const limitText =
|
||||
tier.tokenLimit === Infinity
|
||||
? `> ${prevLimit.toLocaleString()}` // Assumes sorted and Infinity is last
|
||||
: `<= ${tier.tokenLimit.toLocaleString()}`
|
||||
return `${formatPrice(tier.price)}/million tokens (${limitText} tokens)`
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user