mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Support for listing models from OpenAI-compatible providers (#1197)
* feat: Support for listing models from OpenAI-compatible providers * PR-related change: remove includeStreamOptions * prettier * remove unnecessary code * german + translation tweak
This commit is contained in:
parent
bda1a0d4d6
commit
54ef05f61b
7 changed files with 440 additions and 70 deletions
|
|
@ -580,6 +580,14 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
case "refreshOpenRouterModels":
|
||||
await this.refreshOpenRouterModels()
|
||||
break
|
||||
case "refreshOpenAiModels":
|
||||
const { apiConfiguration } = await this.getState()
|
||||
const openAiModels = await this.getOpenAiModels(
|
||||
apiConfiguration.openAiBaseUrl,
|
||||
apiConfiguration.openAiApiKey,
|
||||
)
|
||||
this.postMessageToWebview({ type: "openAiModels", openAiModels })
|
||||
break
|
||||
case "openImage":
|
||||
openImage(message.text!)
|
||||
break
|
||||
|
|
@ -839,6 +847,32 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// OpenAi
|
||||
|
||||
async getOpenAiModels(baseUrl?: string, apiKey?: string) {
|
||||
try {
|
||||
if (!baseUrl) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (!URL.canParse(baseUrl)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const config: Record<string, any> = {}
|
||||
if (apiKey) {
|
||||
config["headers"] = { Authorization: `Bearer ${apiKey}` }
|
||||
}
|
||||
|
||||
const response = await axios.get(`${baseUrl}/models`, config)
|
||||
const modelsArray = response.data?.data?.map((model: any) => model.id) || []
|
||||
const models = [...new Set<string>(modelsArray)]
|
||||
return models
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// OpenRouter
|
||||
|
||||
async handleOpenRouterCallback(code: string) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface ExtensionMessage {
|
|||
| "invoke"
|
||||
| "partialMessage"
|
||||
| "openRouterModels"
|
||||
| "openAiModels"
|
||||
| "mcpServers"
|
||||
| "relinquishControl"
|
||||
| "vsCodeLmModels"
|
||||
|
|
@ -42,6 +43,7 @@ export interface ExtensionMessage {
|
|||
filePaths?: string[]
|
||||
partialMessage?: ClineMessage
|
||||
openRouterModels?: Record<string, ModelInfo>
|
||||
openAiModels?: string[]
|
||||
mcpServers?: McpServer[]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export interface WebviewMessage {
|
|||
| "openMention"
|
||||
| "cancelTask"
|
||||
| "refreshOpenRouterModels"
|
||||
| "refreshOpenAiModels"
|
||||
| "openMcpSettings"
|
||||
| "restartMcpServer"
|
||||
| "autoApprovalSettings"
|
||||
|
|
|
|||
|
|
@ -38,9 +38,10 @@ import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
|
|||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import VSCodeButtonLink from "../common/VSCodeButtonLink"
|
||||
import OpenRouterModelPicker, { ModelDescriptionMarkdown } from "./OpenRouterModelPicker"
|
||||
import styled from "styled-components"
|
||||
import * as vscodemodels from "vscode"
|
||||
import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
|
||||
import OpenAiModelPicker from "./OpenAiModelPicker"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
|
|
@ -84,10 +85,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
[field]: event.target.value,
|
||||
})
|
||||
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
|
||||
}
|
||||
|
||||
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
|
||||
|
|
@ -97,10 +95,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
// Poll ollama/lmstudio models
|
||||
const requestLocalModels = useCallback(() => {
|
||||
if (selectedProvider === "ollama") {
|
||||
vscode.postMessage({
|
||||
type: "requestOllamaModels",
|
||||
text: apiConfiguration?.ollamaBaseUrl,
|
||||
})
|
||||
vscode.postMessage({ type: "requestOllamaModels", text: apiConfiguration?.ollamaBaseUrl })
|
||||
} else if (selectedProvider === "lmstudio") {
|
||||
vscode.postMessage({
|
||||
type: "requestLmStudioModels",
|
||||
|
|
@ -174,10 +169,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
id="api-provider"
|
||||
value={selectedProvider}
|
||||
onChange={handleInputChange("apiProvider")}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
position: "relative",
|
||||
}}>
|
||||
style={{ minWidth: 130, position: "relative", zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1 }}>
|
||||
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
||||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||
<VSCodeOption value="gemini">Google Gemini</VSCodeOption>
|
||||
|
|
@ -210,10 +202,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
const isChecked = e.target.checked === true
|
||||
setAnthropicBaseUrlSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
anthropicBaseUrl: "",
|
||||
})
|
||||
setApiConfiguration({ ...apiConfiguration, anthropicBaseUrl: "" })
|
||||
}
|
||||
}}>
|
||||
{t("useCustomBaseUrl")}
|
||||
|
|
@ -373,12 +362,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
)}
|
||||
|
||||
{selectedProvider === "bedrock" && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsAccessKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
|
|
@ -444,10 +428,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
checked={apiConfiguration?.awsUseCrossRegionInference || false}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
awsUseCrossRegionInference: isChecked,
|
||||
})
|
||||
setApiConfiguration({ ...apiConfiguration, awsUseCrossRegionInference: isChecked })
|
||||
}}>
|
||||
{t("useCrossRegionInference")}
|
||||
</VSCodeCheckbox>
|
||||
|
|
@ -463,12 +444,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
)}
|
||||
|
||||
{apiConfiguration?.apiProvider === "vertex" && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.vertexProjectId || ""}
|
||||
style={{ width: "100%" }}
|
||||
|
|
@ -558,26 +534,18 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
placeholder={t("enterApiKey")}>
|
||||
<span style={{ fontWeight: 500 }}>{t("apiKey")}</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiModelId || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("openAiModelId")}
|
||||
placeholder={t("enterModelId")}>
|
||||
<span style={{ fontWeight: 500 }}>{t("modelId")}</span>
|
||||
</VSCodeTextField>
|
||||
<span style={{ fontWeight: 500 }}>{t("model")}</span>
|
||||
<OpenAiModelPicker />
|
||||
<VSCodeCheckbox
|
||||
checked={azureApiVersionSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setAzureApiVersionSelected(isChecked)
|
||||
if (!isChecked) {
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
azureApiVersion: "",
|
||||
})
|
||||
setApiConfiguration({ ...apiConfiguration, azureApiVersion: "" })
|
||||
}
|
||||
}}>
|
||||
{t("useAzureApiVersion")}
|
||||
{t("setAzureApiVersion")}
|
||||
</VSCodeCheckbox>
|
||||
{azureApiVersionSelected && (
|
||||
<VSCodeTextField
|
||||
|
|
@ -935,12 +903,7 @@ export const ModelInfoView = ({
|
|||
].filter(Boolean)
|
||||
|
||||
return (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "2px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<p style={{ fontSize: "12px", marginTop: "2px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
{infoItems.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
{item}
|
||||
|
|
@ -997,11 +960,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
|
|||
selectedModelId = defaultId
|
||||
selectedModelInfo = models[defaultId]
|
||||
}
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId,
|
||||
selectedModelInfo,
|
||||
}
|
||||
return { selectedProvider: provider, selectedModelId, selectedModelInfo }
|
||||
}
|
||||
switch (provider) {
|
||||
case "anthropic":
|
||||
|
|
|
|||
360
webview-ui/src/components/settings/OpenAiModelPicker.tsx
Normal file
360
webview-ui/src/components/settings/OpenAiModelPicker.tsx
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
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 styled from "styled-components"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { highlight } from "../history/HistoryView"
|
||||
|
||||
const OpenAiModelPicker: React.FC = () => {
|
||||
const { apiConfiguration, setApiConfiguration, openAiModels } = useExtensionState()
|
||||
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.openAiModelId || "")
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([])
|
||||
const dropdownListRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleModelChange = (newModelId: string) => {
|
||||
// could be setting invalid model id/undefined info but validation will catch it
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
openAiModelId: newModelId,
|
||||
})
|
||||
setSearchTerm(newModelId)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
vscode.postMessage({ type: "refreshOpenAiModels" })
|
||||
}, [apiConfiguration?.openAiBaseUrl, apiConfiguration?.openAiApiKey])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsDropdownVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const modelIds = useMemo(() => {
|
||||
return openAiModels.sort((a, b) => a.localeCompare(b))
|
||||
}, [openAiModels])
|
||||
|
||||
const searchableItems = useMemo(() => {
|
||||
return modelIds.map((id) => ({
|
||||
id,
|
||||
html: id,
|
||||
}))
|
||||
}, [modelIds])
|
||||
|
||||
const fuse = useMemo(() => {
|
||||
return new Fuse(searchableItems, {
|
||||
keys: ["html"], // highlight function will update this
|
||||
threshold: 0.6,
|
||||
shouldSort: true,
|
||||
isCaseSensitive: false,
|
||||
ignoreLocation: false,
|
||||
includeMatches: true,
|
||||
minMatchCharLength: 1,
|
||||
})
|
||||
}, [searchableItems])
|
||||
|
||||
const modelSearchResults = useMemo(() => {
|
||||
let results: { id: string; html: string }[] = searchTerm
|
||||
? highlight(fuse.search(searchTerm), "model-item-highlight")
|
||||
: searchableItems
|
||||
// results.sort((a, b) => a.id.localeCompare(b.id)) NOTE: sorting like this causes ids in objects to be reordered and mismatched
|
||||
return results
|
||||
}, [searchableItems, searchTerm, fuse])
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!isDropdownVisible) return
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowDown":
|
||||
event.preventDefault()
|
||||
setSelectedIndex((prev) => (prev < modelSearchResults.length - 1 ? prev + 1 : prev))
|
||||
break
|
||||
case "ArrowUp":
|
||||
event.preventDefault()
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev))
|
||||
break
|
||||
case "Enter":
|
||||
event.preventDefault()
|
||||
if (selectedIndex >= 0 && selectedIndex < modelSearchResults.length) {
|
||||
handleModelChange(modelSearchResults[selectedIndex].id)
|
||||
setIsDropdownVisible(false)
|
||||
}
|
||||
break
|
||||
case "Escape":
|
||||
setIsDropdownVisible(false)
|
||||
setSelectedIndex(-1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIndex(-1)
|
||||
if (dropdownListRef.current) {
|
||||
dropdownListRef.current.scrollTop = 0
|
||||
}
|
||||
}, [searchTerm])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedIndex >= 0 && itemRefs.current[selectedIndex]) {
|
||||
itemRefs.current[selectedIndex]?.scrollIntoView({
|
||||
block: "nearest",
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}, [selectedIndex])
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
.model-item-highlight {
|
||||
background-color: var(--vscode-editor-findMatchHighlightBackground);
|
||||
color: inherit;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div>
|
||||
<DropdownWrapper ref={dropdownRef}>
|
||||
<VSCodeTextField
|
||||
id="model-search"
|
||||
placeholder="Search and select a model..."
|
||||
value={searchTerm}
|
||||
onInput={(e) => {
|
||||
handleModelChange((e.target as HTMLInputElement)?.value?.toLowerCase())
|
||||
setIsDropdownVisible(true)
|
||||
}}
|
||||
onFocus={() => setIsDropdownVisible(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{ width: "100%", zIndex: OPENAI_MODEL_PICKER_Z_INDEX, position: "relative" }}>
|
||||
{searchTerm && (
|
||||
<div
|
||||
className="input-icon-button codicon codicon-close"
|
||||
aria-label="Clear search"
|
||||
onClick={() => {
|
||||
handleModelChange("")
|
||||
setIsDropdownVisible(true)
|
||||
}}
|
||||
slot="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VSCodeTextField>
|
||||
{isDropdownVisible && (
|
||||
<DropdownList ref={dropdownListRef}>
|
||||
{modelSearchResults.map((item, index) => (
|
||||
<DropdownItem
|
||||
key={item.id}
|
||||
ref={(el) => (itemRefs.current[index] = el)}
|
||||
isSelected={index === selectedIndex}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
onClick={() => {
|
||||
handleModelChange(item.id)
|
||||
setIsDropdownVisible(false)
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.html,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownList>
|
||||
)}
|
||||
</DropdownWrapper>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenAiModelPicker
|
||||
|
||||
// Dropdown
|
||||
|
||||
const DropdownWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
export const OPENAI_MODEL_PICKER_Z_INDEX = 1_000
|
||||
|
||||
const DropdownList = styled.div`
|
||||
position: absolute;
|
||||
top: calc(100% - 3px);
|
||||
left: 0;
|
||||
width: calc(100% - 2px);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--vscode-dropdown-background);
|
||||
border: 1px solid var(--vscode-list-activeSelectionBackground);
|
||||
z-index: ${OPENAI_MODEL_PICKER_Z_INDEX - 1};
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
`
|
||||
|
||||
const DropdownItem = styled.div<{ isSelected: boolean }>`
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
|
||||
background-color: ${({ isSelected }) => (isSelected ? "var(--vscode-list-activeSelectionBackground)" : "inherit")};
|
||||
|
||||
&:hover {
|
||||
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,
|
||||
}: {
|
||||
markdown?: string
|
||||
key: string
|
||||
isExpanded: boolean
|
||||
setIsExpanded: (isExpanded: boolean) => void
|
||||
}) => {
|
||||
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: "var(--vscode-sideBar-background)",
|
||||
}}
|
||||
onClick={() => setIsExpanded(true)}>
|
||||
See more
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StyledMarkdown>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -15,6 +15,7 @@ interface ExtensionStateContextType extends ExtensionState {
|
|||
showWelcome: boolean
|
||||
theme: any
|
||||
openRouterModels: Record<string, ModelInfo>
|
||||
openAiModels: string[]
|
||||
mcpServers: McpServer[]
|
||||
filePaths: string[]
|
||||
setApiConfiguration: (config: ApiConfiguration) => void
|
||||
|
|
@ -45,6 +46,8 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
|
||||
})
|
||||
|
||||
const [openAiModels, setOpenAiModels] = useState<string[]>([])
|
||||
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
|
|
@ -105,6 +108,11 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
})
|
||||
break
|
||||
}
|
||||
case "openAiModels": {
|
||||
const updatedModels = message.openAiModels ?? []
|
||||
setOpenAiModels(updatedModels)
|
||||
break
|
||||
}
|
||||
case "mcpServers": {
|
||||
setMcpServers(message.mcpServers ?? [])
|
||||
break
|
||||
|
|
@ -124,6 +132,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
showWelcome,
|
||||
theme,
|
||||
openRouterModels,
|
||||
openAiModels,
|
||||
mcpServers,
|
||||
filePaths,
|
||||
setApiConfiguration: (value) =>
|
||||
|
|
|
|||
|
|
@ -24,27 +24,32 @@
|
|||
"apiKey": "API-Schlüssel",
|
||||
"enterBaseUrl": "Basis-URL eingeben...",
|
||||
"baseUrl": "Basis-URL",
|
||||
"optionalBaseUrl": "Basis-URL (optional)",
|
||||
"enterModelId": "Modell-ID eingeben...",
|
||||
"modelId": "Modell-ID",
|
||||
"useCustomBaseUrl": "Benutzerdefinierte Basis-URL verwenden",
|
||||
"apiKeyInfo": "Dieser Schlüssel wird lokal gespeichert und nur verwendet, um API-Anfragen von dieser Erweiterung zu stellen.",
|
||||
"getApiKeyMessage": "Sie können einen {{vendor}}-API-Schlüssel erhalten, indem Sie sich hier anmelden.",
|
||||
"getApiVendorKey": "{{vendor}}-API-Schlüssel",
|
||||
"getDefault": "Standard: {{defaultValue}}",
|
||||
"getApiKeyMessage": "Sie können einen {{vendor}} API-Schlüssel erhalten, indem Sie sich hier anmelden.",
|
||||
"getApiVendorKey": "{{vendor}} API-Schlüssel",
|
||||
"getCompatibleVendor": "{{vendor}} kompatibel",
|
||||
"lmStudioInfo": "LM Studio ermöglicht es Ihnen, Modelle lokal auf Ihrem Computer auszuführen. Anweisungen zum Einstieg finden Sie in ihrem <Link href=\"https://lmstudio.ai/docs\">Schnellstart-Handbuch.</Link> Sie müssen auch die <Link href=\"https://lmstudio.ai/docs/basics/server\">lokale Server</Link>-Funktion von LM Studio starten, um sie mit dieser Erweiterung zu verwenden. <ErrSpan>(<b>Hinweis:</b> Cline verwendet komplexe Prompts und funktioniert am besten mit Claude-Modellen. Weniger leistungsfähige Modelle funktionieren möglicherweise nicht wie erwartet.)</ErrSpan>",
|
||||
"ollamaInfo": "Ollama ermöglicht es Ihnen, Modelle lokal auf Ihrem Computer auszuführen. Anweisungen zum Einstieg finden Sie in ihrem <Link href=\"https://github.com/ollama/ollama/blob/main/README.md\">Schnellstart-Handbuch.</Link> <ErrorSpan>(<b>Hinweis:</b> Cline verwendet komplexe Prompts und funktioniert am besten mit Claude-Modellen. Weniger leistungsfähige Modelle funktionieren möglicherweise nicht wie erwartet.)</ErrorSpan>",
|
||||
"azureInfo": "<ErrSpan>(<b>Hinweis:</b> Cline verwendet komplexe Prompts und funktioniert am besten mit Claude-Modellen. Weniger leistungsfähige Modelle funktionieren möglicherweise nicht wie erwartet.)</ErrSpan>",
|
||||
"setAzureApiVersion": "Azure API-Version festlegen",
|
||||
"enterGcpProjectId": "Projekt-ID eingeben...",
|
||||
"gcpProjectId": "Google Cloud Projekt-ID",
|
||||
"gcpLinks": "Um Google Cloud Vertex AI zu verwenden, müssen Sie <Link href=\"https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#before_you_begin\"> 1) ein Google Cloud-Konto erstellen › die Vertex AI API aktivieren › die gewünschten Claude-Modelle aktivieren, </Link><br /> <Link href=\"https://cloud.google.com/docs/authentication/provide-credentials-adc#google-idp\"> 2) die Google Cloud CLI installieren › Anwendungsstandardanmeldeinformationen konfigurieren. </Link>",
|
||||
"enterAwsAccessKey": "Zugriffsschlüssel eingeben...",
|
||||
"awsAccessKey": "AWS-Zugriffsschlüssel",
|
||||
"enterAwsAccessKey": "Zugangsschlüssel eingeben...",
|
||||
"awsAccessKey": "AWS Zugangsschlüssel",
|
||||
"enterAwsSecretKey": "Geheimschlüssel eingeben...",
|
||||
"awsSecretKey": "AWS-Geheimschlüssel",
|
||||
"awsSecretKey": "AWS Geheimschlüssel",
|
||||
"enterAwsSessionToken": "Sitzungstoken eingeben...",
|
||||
"awsSessionToken": "AWS-Sitzungstoken",
|
||||
"awsRegion": "AWS-Region",
|
||||
"getRegion": "{{vendor}}-Region",
|
||||
"awsSessionToken": "AWS Sitzungstoken",
|
||||
"getRegion": "{{vendor}} Region",
|
||||
"selectRegion": "Region auswählen...",
|
||||
"useCrossRegionInference": "Regionsübergreifende Inferenz verwenden",
|
||||
"awsInfo": "Authentifizieren Sie sich entweder durch Eingabe der oben genannten Schlüssel oder verwenden Sie die Standard-AWS-Anmeldeinformationen, d.h. ~/.aws/credentials oder Umgebungsvariablen. Diese Anmeldeinformationen werden nur lokal verwendet, um API-Anfragen von dieser Erweiterung zu stellen.",
|
||||
"awsInfo": "Authentifizieren Sie sich entweder durch die Angabe der oben genannten Schlüssel oder verwenden Sie die Standard-AWS-Anmeldeinformationen, d.h. ~/.aws/credentials oder Umgebungsvariablen. Diese Anmeldeinformationen werden nur lokal verwendet, um API-Anfragen von dieser Erweiterung zu stellen.",
|
||||
"vscodeLanguageModelsInfo": "Die VS Code Language Model API ermöglicht es Ihnen, Modelle zu verwenden, die von anderen VS Code-Erweiterungen bereitgestellt werden (einschließlich, aber nicht beschränkt auf GitHub Copilot). Der einfachste Weg, um loszulegen, ist die Installation der Copilot-Erweiterung aus dem VS Marketplace und die Aktivierung von Claude 3.5 Sonnet.",
|
||||
"experimentalFeature": "Hinweis: Dies ist eine sehr experimentelle Integration und funktioniert möglicherweise nicht wie erwartet.",
|
||||
"supportsImages": "Unterstützt Bilder",
|
||||
|
|
@ -54,13 +59,13 @@
|
|||
"supportsPromptCache": "Unterstützt Prompt-Caching",
|
||||
"doesNotSupportPromptCache": "Unterstützt kein Prompt-Caching",
|
||||
"maxOutput": "Maximale Ausgabe",
|
||||
"tokens": "Token",
|
||||
"tokens": "Tokens",
|
||||
"inputPrice": "Eingabepreis",
|
||||
"millionTokens": "Millionen Token",
|
||||
"cacheWritesPrice": "Preis für Cache-Schreibvorgänge",
|
||||
"cacheReadsPrice": "Preis für Cache-Lesevorgänge",
|
||||
"millionTokens": "Millionen Tokens",
|
||||
"cacheWritesPrice": "Cache-Schreibpreis",
|
||||
"cacheReadsPrice": "Cache-Lesepreis",
|
||||
"outputPrice": "Ausgabepreis",
|
||||
"geminiInfo": "* Kostenlos bis zu {{selectedModelId}} Anfragen pro Minute. Danach hängt die Abrechnung von der Promptgröße ab.",
|
||||
"geminiInfo": "* Kostenlos bis zu {{selectedModelId}} Anfragen pro Minute. Danach hängt die Abrechnung von der Prompt-Größe ab.",
|
||||
"pricingDetails": "Weitere Informationen finden Sie in den Preisdaten.",
|
||||
"languageModel": "Sprachmodell"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue