mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 12:51:17 -05:00
feat: config manager using secret store
This commit is contained in:
165
webview-ui/src/components/settings/ApiConfigManager.tsx
Normal file
165
webview-ui/src/components/settings/ApiConfigManager.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { VSCodeButton, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useState } from "react"
|
||||
import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage"
|
||||
|
||||
interface ApiConfigManagerProps {
|
||||
currentApiConfigName?: string
|
||||
listApiConfigMeta?: ApiConfigMeta[]
|
||||
onSelectConfig: (configName: string) => void
|
||||
onDeleteConfig: (configName: string) => void
|
||||
onRenameConfig: (oldName: string, newName: string) => void
|
||||
onUpsertConfig: (configName: string) => void
|
||||
// setDraftNewConfig: (mode: boolean) => void
|
||||
}
|
||||
|
||||
const ApiConfigManager = ({
|
||||
currentApiConfigName,
|
||||
listApiConfigMeta,
|
||||
onSelectConfig,
|
||||
onDeleteConfig,
|
||||
onRenameConfig,
|
||||
onUpsertConfig,
|
||||
// setDraftNewConfig,
|
||||
}: ApiConfigManagerProps) => {
|
||||
const [isNewMode, setIsNewMode] = useState(false);
|
||||
const [isRenameMode, setIsRenameMode] = useState(false);
|
||||
const [newConfigName, setNewConfigName] = useState("");
|
||||
const [renamedConfigName, setRenamedConfigName] = useState("");
|
||||
|
||||
const handleNewConfig = () => {
|
||||
setIsNewMode(true);
|
||||
setNewConfigName("");
|
||||
// setDraftNewConfig(true)
|
||||
};
|
||||
|
||||
const handleSaveNewConfig = () => {
|
||||
if (newConfigName.trim()) {
|
||||
onUpsertConfig(newConfigName.trim());
|
||||
setIsNewMode(false);
|
||||
setNewConfigName("");
|
||||
// setDraftNewConfig(false)
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelNewConfig = () => {
|
||||
setIsNewMode(false);
|
||||
setNewConfigName("");
|
||||
// setDraftNewConfig(false)
|
||||
};
|
||||
|
||||
const handleStartRename = () => {
|
||||
setIsRenameMode(true);
|
||||
setRenamedConfigName(currentApiConfigName || "");
|
||||
};
|
||||
|
||||
const handleSaveRename = () => {
|
||||
if (renamedConfigName.trim() && currentApiConfigName) {
|
||||
onRenameConfig(currentApiConfigName, renamedConfigName.trim());
|
||||
setIsRenameMode(false);
|
||||
setRenamedConfigName("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelRename = () => {
|
||||
setIsRenameMode(false);
|
||||
setRenamedConfigName("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
|
||||
API Configuration
|
||||
</label>
|
||||
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
|
||||
{isNewMode ? (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={newConfigName}
|
||||
onInput={(e: any) => setNewConfigName(e.target.value)}
|
||||
placeholder="Enter configuration name"
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
disabled={!newConfigName.trim()}
|
||||
onClick={handleSaveNewConfig}
|
||||
>
|
||||
<span className="codicon codicon-check" /> Save
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={handleCancelNewConfig}
|
||||
>
|
||||
<span className="codicon codicon-close" /> Cancel
|
||||
</VSCodeButton>
|
||||
</>
|
||||
) : isRenameMode ? (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={renamedConfigName}
|
||||
onInput={(e: any) => setRenamedConfigName(e.target.value)}
|
||||
placeholder="Enter new name"
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
disabled={!renamedConfigName.trim()}
|
||||
onClick={handleSaveRename}
|
||||
>
|
||||
<span className="codicon codicon-check" /> Save
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={handleCancelRename}
|
||||
>
|
||||
<span className="codicon codicon-close" /> Cancel
|
||||
</VSCodeButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<select
|
||||
value={currentApiConfigName}
|
||||
onChange={(e) => onSelectConfig(e.target.value)}
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: "2px",
|
||||
height: "28px"
|
||||
}}>
|
||||
{listApiConfigMeta?.map((config) => (
|
||||
<option key={config.name} value={config.name}>{config.name} {config.apiProvider ? `(${config.apiProvider})` : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={handleNewConfig}
|
||||
>
|
||||
<span className="codicon codicon-add" /> New
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
disabled={!currentApiConfigName}
|
||||
onClick={handleStartRename}
|
||||
>
|
||||
<span className="codicon codicon-edit" /> Rename
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
disabled={!currentApiConfigName}
|
||||
onClick={() => onDeleteConfig(currentApiConfigName!)}
|
||||
>
|
||||
<span className="codicon codicon-trash" /> Delete
|
||||
</VSCodeButton>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<VSCodeDivider style={{ margin: "15px 0" }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ApiConfigManager)
|
||||
@@ -5,6 +5,7 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
import McpEnabledToggle from "../mcp/McpEnabledToggle"
|
||||
import ApiConfigManager from "./ApiConfigManager"
|
||||
|
||||
const IS_DEV = false // FIXME: use flags when packaging
|
||||
|
||||
@@ -55,10 +56,15 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setAlwaysApproveResubmit,
|
||||
requestDelaySeconds,
|
||||
setRequestDelaySeconds,
|
||||
currentApiConfigName,
|
||||
listApiConfigMeta,
|
||||
} = useExtensionState()
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [commandInput, setCommandInput] = useState("")
|
||||
// const [draftNewMode, setDraftNewMode] = useState(false)
|
||||
|
||||
|
||||
const handleSubmit = () => {
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration)
|
||||
const modelIdValidationResult = validateModelId(apiConfiguration, glamaModels, openRouterModels)
|
||||
@@ -89,6 +95,13 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
vscode.postMessage({ type: "mcpEnabled", bool: mcpEnabled })
|
||||
vscode.postMessage({ type: "alwaysApproveResubmit", bool: alwaysApproveResubmit })
|
||||
vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
|
||||
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
|
||||
vscode.postMessage({
|
||||
type: "upsertApiConfiguration",
|
||||
text: currentApiConfigName,
|
||||
apiConfiguration
|
||||
})
|
||||
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
@@ -150,6 +163,42 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
</div>
|
||||
<div
|
||||
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<ApiConfigManager
|
||||
currentApiConfigName={currentApiConfigName}
|
||||
listApiConfigMeta={listApiConfigMeta}
|
||||
onSelectConfig={(configName: string) => {
|
||||
vscode.postMessage({
|
||||
type: "loadApiConfiguration",
|
||||
text: configName
|
||||
})
|
||||
}}
|
||||
onDeleteConfig={(configName: string) => {
|
||||
vscode.postMessage({
|
||||
type: "deleteApiConfiguration",
|
||||
text: configName
|
||||
})
|
||||
}}
|
||||
onRenameConfig={(oldName: string, newName: string) => {
|
||||
vscode.postMessage({
|
||||
type: "renameApiConfiguration",
|
||||
values: {oldName, newName},
|
||||
apiConfiguration
|
||||
})
|
||||
}}
|
||||
onUpsertConfig={(configName: string) => {
|
||||
vscode.postMessage({
|
||||
type: "upsertApiConfiguration",
|
||||
text: configName,
|
||||
apiConfiguration
|
||||
})
|
||||
}}
|
||||
// setDraftNewConfig={(mode: boolean) => {
|
||||
// setDraftNewMode(mode)
|
||||
// }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>Provider Settings</h3>
|
||||
<ApiOptions
|
||||
|
||||
@@ -11,6 +11,16 @@ jest.mock('../../../utils/vscode', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock ApiConfigManager component
|
||||
jest.mock('../ApiConfigManager', () => ({
|
||||
__esModule: true,
|
||||
default: ({ currentApiConfigName, listApiConfigMeta, onSelectConfig, onDeleteConfig, onRenameConfig, onUpsertConfig }: any) => (
|
||||
<div data-testid="api-config-management">
|
||||
<span>Current config: {currentApiConfigName}</span>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
|
||||
// Mock VSCode components
|
||||
jest.mock('@vscode/webview-ui-toolkit/react', () => ({
|
||||
VSCodeButton: ({ children, onClick, appearance }: any) => (
|
||||
@@ -185,6 +195,18 @@ describe('SettingsView - Sound Settings', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('SettingsView - API Configuration', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders ApiConfigManagement with correct props', () => {
|
||||
renderSettingsView()
|
||||
|
||||
expect(screen.getByTestId('api-config-management')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('SettingsView - Allowed Commands', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
Reference in New Issue
Block a user