Browse Source

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

sam hoang 11 months ago
parent
commit
ca5d3c0ab0
1 changed files with 393 additions and 171 deletions
  1. 393 171
      webview-ui/src/components/settings/ApiOptions.tsx

+ 393 - 171
webview-ui/src/components/settings/ApiOptions.tsx

@@ -1,4 +1,4 @@
-import { Checkbox, Dropdown } from "vscrui"
+import { Checkbox, Dropdown, Pane } from "vscrui"
 import type { DropdownOption } from "vscrui"
 import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
 import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react"
@@ -559,203 +559,425 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
 						/>
 					)}
 
-					{/* Model Info Configuration */}
 					<div
 						style={{
 							marginTop: 15,
-							padding: 10,
-							border: "1px solid var(--vscode-input-border)",
-							borderRadius: 4,
-						}}>
-						<div style={{ marginBottom: 10 }}>
-							<span style={{ fontWeight: 500, fontSize: "14px" }}>Model Configuration</span>
+						}}
+					/>
+					<Pane
+						title="Model Configuration"
+						open={false}
+						actions={[
+							{
+								iconName: "refresh",
+								onClick: () =>
+									handleInputChange("openAiCusModelInfo")({
+										target: { value: openAiModelInfoSaneDefaults },
+									}),
+							},
+						]}>
+						<div
+							style={{
+								padding: 15,
+								backgroundColor: "var(--vscode-editor-background)",
+							}}>
 							<p
 								style={{
 									fontSize: "12px",
 									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
 							</p>
-						</div>
 
-						{/* Capabilities Section */}
-						<div style={{ marginBottom: 15 }}>
-							<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,
+							{/* Capabilities Section */}
+							<div
+								style={{
+									marginBottom: 20,
+									padding: 12,
+									backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
+									borderRadius: 4,
+								}}>
+								<span
+									style={{
+										fontWeight: 500,
+										fontSize: "12px",
+										display: "block",
+										marginBottom: 12,
+										color: "var(--vscode-editor-foreground)",
+									}}>
+									Model Capabilities
+								</span>
+								<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
+									<div className="token-config-field">
+										<VSCodeTextField
+											value={
+												apiConfiguration?.openAiCusModelInfo?.maxTokens?.toString() ||
+												openAiModelInfoSaneDefaults.maxTokens?.toString() ||
+												""
+											}
+											type="text"
+											style={{
+												width: "100%",
+												borderColor: (() => {
+													const value = apiConfiguration?.openAiCusModelInfo?.maxTokens
+													if (!value) return "var(--vscode-input-border)"
+													return value > 0
+														? "var(--vscode-charts-green)"
+														: "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,
+														},
 													},
-												},
-											})
-										}}>
-										Supports Images
-									</Checkbox>
+												})
+											}}
+											placeholder="e.g. 4096">
+											<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
+										</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>
 
-									<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,
+									<div className="token-config-field">
+										<VSCodeTextField
+											value={
+												apiConfiguration?.openAiCusModelInfo?.contextWindow?.toString() ||
+												openAiModelInfoSaneDefaults.contextWindow?.toString() ||
+												""
+											}
+											type="text"
+											style={{
+												width: "100%",
+												borderColor: (() => {
+													const value = apiConfiguration?.openAiCusModelInfo?.contextWindow
+													if (!value) return "var(--vscode-input-border)"
+													return value > 0
+														? "var(--vscode-charts-green)"
+														: "var(--vscode-errorForeground)"
+												})(),
+											}}
+											title="Total number of tokens (input + output) the model can process in a single request"
+											onChange={(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={{
+												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",
 										}}>
-										Supports Computer Use
-									</Checkbox>
+										<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>
-						</div>
 
-						{/* Pricing Section */}
-						<div>
-							<span
+							{/* Pricing Section */}
+							<div
 								style={{
-									fontWeight: 500,
-									fontSize: "12px",
-									color: "var(--vscode-descriptionForeground)",
+									backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
+									padding: "12px",
+									borderRadius: "4px",
+									marginTop: "15px",
 								}}>
-								Pricing (USD per million tokens)
-							</span>
-							<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 5 }}>
-								{/* Input/Output Prices */}
-								<div style={{ display: "flex", gap: 10 }}>
-									<VSCodeTextField
-										value={
-											apiConfiguration?.openAiCusModelInfo?.inputPrice?.toString() ??
-											openAiModelInfoSaneDefaults.inputPrice?.toString() ??
-											""
-										}
-										type="text"
-										style={{ width: "100%" }}
-										title="Cost per million tokens in the input/prompt"
-										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,
+								<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">
-										<span style={{ fontWeight: 500 }}>Input Price</span>
-									</VSCodeTextField>
+												})
+											}}
+											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>
 
-									<VSCodeTextField
-										value={
-											apiConfiguration?.openAiCusModelInfo?.outputPrice?.toString() ||
-											openAiModelInfoSaneDefaults.outputPrice?.toString() ||
-											""
-										}
-										type="text"
-										style={{ width: "100%" }}
-										title="Cost per million tokens in the model's response"
-										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,
+									<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">
-										<span style={{ fontWeight: 500 }}>Output Price</span>
-									</VSCodeTextField>
+												})
+											}}
+											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>
+					</Pane>
 
-					{/* TODO: model info here */}
+					{/* end Model Info Configuration */}
 
 					<p
 						style={{