feat(openai-compatible): tune UI UX custom model info

This commit is contained in:
sam hoang
2025-01-19 15:19:21 +07:00
parent 9d5e6a2082
commit ca5d3c0ab0

View File

@@ -1,4 +1,4 @@
import { Checkbox, Dropdown } from "vscrui" import { Checkbox, Dropdown, Pane } from "vscrui"
import type { DropdownOption } from "vscrui" import type { DropdownOption } from "vscrui"
import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react" import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react"
@@ -559,203 +559,425 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
/> />
)} )}
{/* Model Info Configuration */}
<div <div
style={{ style={{
marginTop: 15, marginTop: 15,
padding: 10, }}
border: "1px solid var(--vscode-input-border)", />
borderRadius: 4, <Pane
}}> title="Model Configuration"
<div style={{ marginBottom: 10 }}> open={false}
<span style={{ fontWeight: 500, fontSize: "14px" }}>Model Configuration</span> actions={[
{
iconName: "refresh",
onClick: () =>
handleInputChange("openAiCusModelInfo")({
target: { value: openAiModelInfoSaneDefaults },
}),
},
]}>
<div
style={{
padding: 15,
backgroundColor: "var(--vscode-editor-background)",
}}>
<p <p
style={{ style={{
fontSize: "12px", fontSize: "12px",
color: "var(--vscode-descriptionForeground)", color: "var(--vscode-descriptionForeground)",
margin: "5px 0", margin: "0 0 15px 0",
lineHeight: "1.4",
}}> }}>
Configure the capabilities and pricing for your custom OpenAI-compatible model Configure the capabilities and pricing for your custom OpenAI-compatible model
</p> </p>
</div>
{/* Capabilities Section */} {/* Capabilities Section */}
<div style={{ marginBottom: 15 }}> <div
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 5 }}>
<VSCodeTextField
value={
apiConfiguration?.openAiCusModelInfo?.maxTokens?.toString() ||
openAiModelInfoSaneDefaults.maxTokens?.toString() ||
""
}
type="text"
style={{ width: "100%" }}
title="Maximum number of tokens the model can generate in a single response"
onInput={(e: any) => {
const value = parseInt(e.target.value)
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
maxTokens: isNaN(value) ? undefined : value,
},
},
})
}}
placeholder="e.g. 4096">
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
</VSCodeTextField>
<VSCodeTextField
value={
apiConfiguration?.openAiCusModelInfo?.contextWindow?.toString() ||
openAiModelInfoSaneDefaults.contextWindow?.toString() ||
""
}
type="text"
style={{ width: "100%" }}
title="Total number of tokens (input + output) the model can process in a single request"
onInput={(e: any) => {
const parsed = parseInt(e.target.value)
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
contextWindow:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.contextWindow
: parsed,
},
},
})
}}
placeholder="e.g. 128000">
<span style={{ fontWeight: 500 }}>Context Window Size</span>
</VSCodeTextField>
<div style={{ display: "flex", gap: 20, marginTop: 5 }}>
<Checkbox
checked={
apiConfiguration?.openAiCusModelInfo?.supportsImages ??
openAiModelInfoSaneDefaults.supportsImages
}
title="Enable if the model can process and understand images in the input"
onChange={(checked: boolean) => {
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
supportsImages: checked,
},
},
})
}}>
Supports Images
</Checkbox>
<Checkbox
checked={apiConfiguration?.openAiCusModelInfo?.supportsComputerUse ?? false}
title="Enable if the model can interact with the computer (execute commands, modify files, etc.)"
onChange={(checked: boolean) => {
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
supportsComputerUse: checked,
},
},
})
}}>
Supports Computer Use
</Checkbox>
</div>
</div>
</div>
{/* Pricing Section */}
<div>
<span
style={{ style={{
fontWeight: 500, marginBottom: 20,
fontSize: "12px", padding: 12,
color: "var(--vscode-descriptionForeground)", backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
borderRadius: 4,
}}> }}>
Pricing (USD per million tokens) <span
</span> style={{
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 5 }}> fontWeight: 500,
{/* Input/Output Prices */} fontSize: "12px",
<div style={{ display: "flex", gap: 10 }}> display: "block",
<VSCodeTextField marginBottom: 12,
value={ color: "var(--vscode-editor-foreground)",
apiConfiguration?.openAiCusModelInfo?.inputPrice?.toString() ?? }}>
openAiModelInfoSaneDefaults.inputPrice?.toString() ?? Model Capabilities
"" </span>
} <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
type="text" <div className="token-config-field">
style={{ width: "100%" }} <VSCodeTextField
title="Cost per million tokens in the input/prompt" value={
onChange={(e: any) => { apiConfiguration?.openAiCusModelInfo?.maxTokens?.toString() ||
const parsed = parseFloat(e.target.value) openAiModelInfoSaneDefaults.maxTokens?.toString() ||
handleInputChange("openAiCusModelInfo")({ ""
target: { }
value: { type="text"
...(apiConfiguration?.openAiCusModelInfo ?? style={{
openAiModelInfoSaneDefaults), width: "100%",
inputPrice: borderColor: (() => {
e.target.value === "" const value = apiConfiguration?.openAiCusModelInfo?.maxTokens
? undefined if (!value) return "var(--vscode-input-border)"
: isNaN(parsed) return value > 0
? openAiModelInfoSaneDefaults.inputPrice ? "var(--vscode-charts-green)"
: parsed, : "var(--vscode-errorForeground)"
})(),
}}
title="Maximum number of tokens the model can generate in a single response"
onChange={(e: any) => {
const value = parseInt(e.target.value)
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
maxTokens: isNaN(value) ? undefined : value,
},
}, },
}, })
}) }}
}} placeholder="e.g. 4096">
placeholder="e.g. 0.0001"> <span style={{ fontWeight: 500 }}>Max Output Tokens</span>
<span style={{ fontWeight: 500 }}>Input Price</span> </VSCodeTextField>
</VSCodeTextField> <div
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginTop: 4,
display: "flex",
alignItems: "center",
gap: 4,
}}>
<i className="codicon codicon-info" style={{ fontSize: "12px" }}></i>
<span>
Maximum number of tokens the model can generate in a response. Higher
values allow longer outputs but may increase costs.
</span>
</div>
</div>
<VSCodeTextField <div className="token-config-field">
value={ <VSCodeTextField
apiConfiguration?.openAiCusModelInfo?.outputPrice?.toString() || value={
openAiModelInfoSaneDefaults.outputPrice?.toString() || apiConfiguration?.openAiCusModelInfo?.contextWindow?.toString() ||
"" openAiModelInfoSaneDefaults.contextWindow?.toString() ||
} ""
type="text" }
style={{ width: "100%" }} type="text"
title="Cost per million tokens in the model's response" style={{
onChange={(e: any) => { width: "100%",
const parsed = parseFloat(e.target.value) borderColor: (() => {
handleInputChange("openAiCusModelInfo")({ const value = apiConfiguration?.openAiCusModelInfo?.contextWindow
target: { if (!value) return "var(--vscode-input-border)"
value: { return value > 0
...(apiConfiguration?.openAiCusModelInfo || ? "var(--vscode-charts-green)"
openAiModelInfoSaneDefaults), : "var(--vscode-errorForeground)"
outputPrice: })(),
e.target.value === "" }}
? undefined title="Total number of tokens (input + output) the model can process in a single request"
: isNaN(parsed) onChange={(e: any) => {
? openAiModelInfoSaneDefaults.outputPrice const parsed = parseInt(e.target.value)
: parsed, handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
contextWindow:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.contextWindow
: parsed,
},
}, },
}, })
}) }}
}} placeholder="e.g. 128000">
placeholder="e.g. 0.0002"> <span style={{ fontWeight: 500 }}>Context Window Size</span>
<span style={{ fontWeight: 500 }}>Output Price</span> </VSCodeTextField>
</VSCodeTextField> <div
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginTop: 4,
display: "flex",
alignItems: "center",
gap: 4,
}}>
<i className="codicon codicon-info" style={{ fontSize: "12px" }}></i>
<span>
Total tokens (input + output) the model can process. Larger windows
allow processing more content but may increase memory usage.
</span>
</div>
</div>
<div
style={{
backgroundColor: "var(--vscode-editor-background)",
padding: "12px",
borderRadius: "4px",
marginTop: "8px",
border: "1px solid var(--vscode-input-border)",
transition: "background-color 0.2s ease",
}}>
<span
style={{
fontSize: "11px",
fontWeight: 500,
color: "var(--vscode-editor-foreground)",
display: "block",
marginBottom: "10px",
}}>
Model Features
</span>
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
<div className="feature-toggle">
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Checkbox
checked={
apiConfiguration?.openAiCusModelInfo?.supportsImages ??
openAiModelInfoSaneDefaults.supportsImages
}
onChange={(checked: boolean) => {
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
supportsImages: checked,
},
},
})
}}>
<span style={{ fontWeight: 500 }}>Image Support</span>
</Checkbox>
<i
className="codicon codicon-info"
title="Enable if the model can process and understand images in the input. Required for image-based assistance and visual code understanding."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
<p
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginLeft: "24px",
marginTop: "4px",
lineHeight: "1.4",
}}>
Allows the model to analyze and understand images, essential for
visual code assistance
</p>
</div>
<div
className="feature-toggle"
style={{
borderTop: "1px solid var(--vscode-input-border)",
paddingTop: "12px",
}}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Checkbox
checked={
apiConfiguration?.openAiCusModelInfo?.supportsComputerUse ??
false
}
onChange={(checked: boolean) => {
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
supportsComputerUse: checked,
},
},
})
}}>
<span style={{ fontWeight: 500 }}>Computer Interaction</span>
</Checkbox>
<i
className="codicon codicon-info"
title="Enable if the model can interact with your computer through commands and file operations. Required for automated tasks and file modifications."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
<p
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginLeft: "24px",
marginTop: "4px",
lineHeight: "1.4",
}}>
Enables the model to execute commands and modify files for automated
assistance
</p>
</div>
</div>
</div>
</div>
</div>
{/* Pricing Section */}
<div
style={{
backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
padding: "12px",
borderRadius: "4px",
marginTop: "15px",
}}>
<div style={{ marginBottom: "12px" }}>
<span
style={{
fontWeight: 500,
fontSize: "12px",
color: "var(--vscode-editor-foreground)",
display: "block",
marginBottom: "4px",
}}>
Model Pricing
</span>
<span
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
display: "block",
}}>
Configure token-based pricing in USD per million tokens
</span>
</div>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
backgroundColor: "var(--vscode-editor-background)",
padding: "12px",
borderRadius: "4px",
}}>
<div className="price-input">
<VSCodeTextField
value={
apiConfiguration?.openAiCusModelInfo?.inputPrice?.toString() ??
openAiModelInfoSaneDefaults.inputPrice?.toString() ??
""
}
type="text"
style={{
width: "100%",
borderColor: (() => {
const value = apiConfiguration?.openAiCusModelInfo?.inputPrice
if (!value && value !== 0) return "var(--vscode-input-border)"
return value >= 0
? "var(--vscode-charts-green)"
: "var(--vscode-errorForeground)"
})(),
}}
onChange={(e: any) => {
const parsed = parseFloat(e.target.value)
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ??
openAiModelInfoSaneDefaults),
inputPrice:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.inputPrice
: parsed,
},
},
})
}}
placeholder="e.g. 0.0001">
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<span style={{ fontWeight: 500 }}>Input Price</span>
<i
className="codicon codicon-info"
title="Cost per million tokens in the input/prompt. This affects the cost of sending context and instructions to the model."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
</VSCodeTextField>
</div>
<div className="price-input">
<VSCodeTextField
value={
apiConfiguration?.openAiCusModelInfo?.outputPrice?.toString() ||
openAiModelInfoSaneDefaults.outputPrice?.toString() ||
""
}
type="text"
style={{
width: "100%",
borderColor: (() => {
const value = apiConfiguration?.openAiCusModelInfo?.outputPrice
if (!value && value !== 0) return "var(--vscode-input-border)"
return value >= 0
? "var(--vscode-charts-green)"
: "var(--vscode-errorForeground)"
})(),
}}
onChange={(e: any) => {
const parsed = parseFloat(e.target.value)
handleInputChange("openAiCusModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCusModelInfo ||
openAiModelInfoSaneDefaults),
outputPrice:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.outputPrice
: parsed,
},
},
})
}}
placeholder="e.g. 0.0002">
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<span style={{ fontWeight: 500 }}>Output Price</span>
<i
className="codicon codicon-info"
title="Cost per million tokens in the model's response. This affects the cost of generated content and completions."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
</VSCodeTextField>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </Pane>
{/* TODO: model info here */} {/* end Model Info Configuration */}
<p <p
style={{ style={{