Browse Source

UI cleanup

Matt Rubens 1 year ago
parent
commit
c3fa10b367

+ 61 - 1
webview-ui/src/components/chat/ChatTextArea.tsx

@@ -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" && (

+ 173 - 113
webview-ui/src/components/settings/ApiConfigManager.tsx

@@ -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
-            </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 ? (
-                    <>
+        <div style={{ marginBottom: 5 }}>
+            <div style={{ 
+                display: "flex", 
+                flexDirection: "column",
+                gap: "2px"
+            }}>
+                <label htmlFor="config-profile">
+                    <span style={{ fontWeight: "500" }}>Configuration Profile</span>
+                </label>
+
+                {editState ? (
+                    <div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
                         <VSCodeTextField
-                            value={renamedConfigName}
-                            onInput={(e: any) => setRenamedConfigName(e.target.value)}
-                            placeholder="Enter new 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={!renamedConfigName.trim()}
-                            onClick={handleSaveRename}
+                            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={handleCancelRename}
+                            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>
-                    </>
+                    </div>
                 ) : (
                     <>
-                        <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 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",
+                                    paddingRight: "24px",
+                                    backgroundColor: "var(--vscode-dropdown-background)",
+                                    color: "var(--vscode-dropdown-foreground)",
+                                    border: "1px solid var(--vscode-dropdown-border)",
+                                    borderRadius: "2px",
+                                    height: "28px",
+                                    cursor: "pointer",
+                                    outline: "none"
+                                }}
+                            >
+                                {listApiConfigMeta?.map((config) => (
+                                    <option 
+                                        key={config.name} 
+                                        value={config.name}
+                                    >
+                                        {config.name}
+                                    </option>
+                                ))}
+                            </select>
+                            <VSCodeButton
+                                appearance="icon"
+                                onClick={handleStartNew}
+                                title="Add profile"
+                                style={{
+                                    padding: 0,
+                                    margin: 0,
+                                    height: '28px',
+                                    width: '28px',
+                                    minWidth: '28px'
+                                }}
+                            >
+                                <span className="codicon codicon-add" />
+                            </VSCodeButton>
+                            {currentApiConfigName && (
+                                <>
+                                    <VSCodeButton
+                                        appearance="icon"
+                                        onClick={handleStartRename}
+                                        title="Rename profile"
+                                        style={{
+                                            padding: 0,
+                                            margin: 0,
+                                            height: '28px',
+                                            width: '28px',
+                                            minWidth: '28px'
+                                        }}
+                                    >
+                                        <span className="codicon codicon-edit" />
+                                    </VSCodeButton>
+                                    <VSCodeButton
+                                        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" />
+                                    </VSCodeButton>
+                                </>
+                            )}
+                        </div>
+                        <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>
-            <VSCodeDivider style={{ margin: "15px 0" }} />
         </div>
     )
 }

+ 1 - 7
webview-ui/src/components/settings/SettingsView.tsx

@@ -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}