UI cleanup

This commit is contained in:
Matt Rubens
2025-01-07 04:21:21 -05:00
parent 840276b297
commit c3fa10b367
3 changed files with 235 additions and 121 deletions

View File

@@ -44,9 +44,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
},
ref,
) => {
const { filePaths, apiConfiguration } = useExtensionState()
const { filePaths, apiConfiguration, currentApiConfigName, listApiConfigMeta } = useExtensionState()
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const [gitCommits, setGitCommits] = useState<any[]>([])
const [showDropdown, setShowDropdown] = useState(false)
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (showDropdown) {
setShowDropdown(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => document.removeEventListener("mousedown", handleClickOutside)
}, [showDropdown])
// Handle enhanced prompt response
useEffect(() => {
@@ -656,6 +668,54 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}}
/>
)}
{(listApiConfigMeta || []).length > 1 && (
<div
style={{
position: "absolute",
left: 25,
bottom: 14,
zIndex: 2
}}
>
<select
value={currentApiConfigName}
onChange={(e) => vscode.postMessage({
type: "loadApiConfiguration",
text: e.target.value
})}
style={{
fontSize: "11px",
cursor: "pointer",
backgroundColor: "transparent",
border: "none",
color: "var(--vscode-input-foreground)",
opacity: 0.6,
outline: "none",
paddingLeft: 14,
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e\")",
backgroundRepeat: "no-repeat",
backgroundPosition: "left 0px center",
backgroundSize: "10px"
}}
>
{(listApiConfigMeta || [])?.map((config) => (
<option
key={config.name}
value={config.name}
style={{
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)"
}}
>
{config.name}
</option>
))}
</select>
</div>
)}
<div className="button-row" style={{ position: "absolute", right: 20, display: "flex", alignItems: "center", height: 31, bottom: 8, zIndex: 2, justifyContent: "flex-end" }}>
<span style={{ display: "flex", alignItems: "center", gap: 12 }}>
{apiConfiguration?.apiProvider === "openrouter" && (

View File

@@ -1,5 +1,5 @@
import { VSCodeButton, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { memo, useState } from "react"
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { memo, useEffect, useRef, useState } from "react"
import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage"
interface ApiConfigManagerProps {
@@ -9,7 +9,6 @@ interface ApiConfigManagerProps {
onDeleteConfig: (configName: string) => void
onRenameConfig: (oldName: string, newName: string) => void
onUpsertConfig: (configName: string) => void
// setDraftNewConfig: (mode: boolean) => void
}
const ApiConfigManager = ({
@@ -19,145 +18,206 @@ const ApiConfigManager = ({
onDeleteConfig,
onRenameConfig,
onUpsertConfig,
// setDraftNewConfig,
}: ApiConfigManagerProps) => {
const [isNewMode, setIsNewMode] = useState(false);
const [isRenameMode, setIsRenameMode] = useState(false);
const [newConfigName, setNewConfigName] = useState("");
const [renamedConfigName, setRenamedConfigName] = useState("");
const [editState, setEditState] = useState<'new' | 'rename' | null>(null);
const [inputValue, setInputValue] = useState("");
const inputRef = useRef<HTMLInputElement>();
const handleNewConfig = () => {
setIsNewMode(true);
setNewConfigName("");
// setDraftNewConfig(true)
};
const handleSaveNewConfig = () => {
if (newConfigName.trim()) {
onUpsertConfig(newConfigName.trim());
setIsNewMode(false);
setNewConfigName("");
// setDraftNewConfig(false)
// Focus input when entering edit mode
useEffect(() => {
if (editState) {
setTimeout(() => inputRef.current?.focus(), 0);
}
};
}, [editState]);
const handleCancelNewConfig = () => {
setIsNewMode(false);
setNewConfigName("");
// setDraftNewConfig(false)
// Reset edit state when current profile changes
useEffect(() => {
setEditState(null);
setInputValue("");
}, [currentApiConfigName]);
const handleStartNew = () => {
setEditState('new');
setInputValue("");
};
const handleStartRename = () => {
setIsRenameMode(true);
setRenamedConfigName(currentApiConfigName || "");
setEditState('rename');
setInputValue(currentApiConfigName || "");
};
const handleSaveRename = () => {
if (renamedConfigName.trim() && currentApiConfigName) {
onRenameConfig(currentApiConfigName, renamedConfigName.trim());
setIsRenameMode(false);
setRenamedConfigName("");
const handleCancel = () => {
setEditState(null);
setInputValue("");
};
const handleSave = () => {
const trimmedValue = inputValue.trim();
if (!trimmedValue) return;
if (editState === 'new') {
onUpsertConfig(trimmedValue);
} else if (editState === 'rename' && currentApiConfigName) {
onRenameConfig(currentApiConfigName, trimmedValue);
}
setEditState(null);
setInputValue("");
};
const handleCancelRename = () => {
setIsRenameMode(false);
setRenamedConfigName("");
const handleDelete = () => {
if (!currentApiConfigName || !listApiConfigMeta || listApiConfigMeta.length <= 1) return;
// Let the extension handle both deletion and selection
onDeleteConfig(currentApiConfigName);
};
const isOnlyProfile = listApiConfigMeta?.length === 1;
return (
<div>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
API Configuration
<div style={{ marginBottom: 5 }}>
<div style={{
display: "flex",
flexDirection: "column",
gap: "2px"
}}>
<label htmlFor="config-profile">
<span style={{ fontWeight: "500" }}>Configuration Profile</span>
</label>
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
{isNewMode ? (
<>
{editState ? (
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
<VSCodeTextField
value={newConfigName}
onInput={(e: any) => setNewConfigName(e.target.value)}
placeholder="Enter configuration name"
ref={inputRef as any}
value={inputValue}
onInput={(e: any) => setInputValue(e.target.value)}
placeholder={editState === 'new' ? "Enter profile name" : "Enter new name"}
style={{ flexGrow: 1 }}
onKeyDown={(e: any) => {
if (e.key === 'Enter' && inputValue.trim()) {
handleSave();
} else if (e.key === 'Escape') {
handleCancel();
}
}}
/>
<VSCodeButton
appearance="secondary"
disabled={!newConfigName.trim()}
onClick={handleSaveNewConfig}
appearance="icon"
disabled={!inputValue.trim()}
onClick={handleSave}
title="Save"
style={{
padding: 0,
margin: 0,
height: '28px',
width: '28px',
minWidth: '28px'
}}
>
<span className="codicon codicon-check" /> Save
<span className="codicon codicon-check" />
</VSCodeButton>
<VSCodeButton
appearance="secondary"
onClick={handleCancelNewConfig}
appearance="icon"
onClick={handleCancel}
title="Cancel"
style={{
padding: 0,
margin: 0,
height: '28px',
width: '28px',
minWidth: '28px'
}}
>
<span className="codicon codicon-close" /> Cancel
<span className="codicon codicon-close" />
</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>
</>
</div>
) : (
<>
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
<select
id="config-profile"
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)",
paddingRight: "24px",
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)",
border: "1px solid var(--vscode-dropdown-border)",
borderRadius: "2px",
height: "28px"
}}>
height: "28px",
cursor: "pointer",
outline: "none"
}}
>
{listApiConfigMeta?.map((config) => (
<option key={config.name} value={config.name}>{config.name} {config.apiProvider ? `(${config.apiProvider})` : ""}
<option
key={config.name}
value={config.name}
>
{config.name}
</option>
))}
</select>
<VSCodeButton
appearance="secondary"
onClick={handleNewConfig}
appearance="icon"
onClick={handleStartNew}
title="Add profile"
style={{
padding: 0,
margin: 0,
height: '28px',
width: '28px',
minWidth: '28px'
}}
>
<span className="codicon codicon-add" /> New
<span className="codicon codicon-add" />
</VSCodeButton>
{currentApiConfigName && (
<>
<VSCodeButton
appearance="secondary"
disabled={!currentApiConfigName}
appearance="icon"
onClick={handleStartRename}
title="Rename profile"
style={{
padding: 0,
margin: 0,
height: '28px',
width: '28px',
minWidth: '28px'
}}
>
<span className="codicon codicon-edit" /> Rename
<span className="codicon codicon-edit" />
</VSCodeButton>
<VSCodeButton
appearance="secondary"
disabled={!currentApiConfigName}
onClick={() => onDeleteConfig(currentApiConfigName!)}
appearance="icon"
onClick={handleDelete}
title={isOnlyProfile ? "Cannot delete the only profile" : "Delete profile"}
disabled={isOnlyProfile}
style={{
padding: 0,
margin: 0,
height: '28px',
width: '28px',
minWidth: '28px'
}}
>
<span className="codicon codicon-trash" /> Delete
<span className="codicon codicon-trash" />
</VSCodeButton>
</>
)}
</div>
<VSCodeDivider style={{ margin: "15px 0" }} />
<p style={{
fontSize: "12px",
margin: "5px 0 12px",
color: "var(--vscode-descriptionForeground)"
}}>
Save different API configurations to quickly switch between providers and settings
</p>
</>
)}
</div>
</div>
)
}

View File

@@ -164,6 +164,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
<div
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
<div style={{ marginBottom: 5 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>Provider Settings</h3>
<ApiConfigManager
currentApiConfigName={currentApiConfigName}
listApiConfigMeta={listApiConfigMeta}
@@ -193,14 +194,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
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
showModelOptions={true}
apiErrorMessage={apiErrorMessage}