mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
UI cleanup
This commit is contained in:
@@ -44,9 +44,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { filePaths, apiConfiguration } = useExtensionState()
|
const { filePaths, apiConfiguration, currentApiConfigName, listApiConfigMeta } = useExtensionState()
|
||||||
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
|
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
|
||||||
const [gitCommits, setGitCommits] = useState<any[]>([])
|
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
|
// Handle enhanced prompt response
|
||||||
useEffect(() => {
|
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" }}>
|
<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 }}>
|
<span style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||||
{apiConfiguration?.apiProvider === "openrouter" && (
|
{apiConfiguration?.apiProvider === "openrouter" && (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { VSCodeButton, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||||
import { memo, useState } from "react"
|
import { memo, useEffect, useRef, useState } from "react"
|
||||||
import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage"
|
import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage"
|
||||||
|
|
||||||
interface ApiConfigManagerProps {
|
interface ApiConfigManagerProps {
|
||||||
@@ -9,7 +9,6 @@ interface ApiConfigManagerProps {
|
|||||||
onDeleteConfig: (configName: string) => void
|
onDeleteConfig: (configName: string) => void
|
||||||
onRenameConfig: (oldName: string, newName: string) => void
|
onRenameConfig: (oldName: string, newName: string) => void
|
||||||
onUpsertConfig: (configName: string) => void
|
onUpsertConfig: (configName: string) => void
|
||||||
// setDraftNewConfig: (mode: boolean) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApiConfigManager = ({
|
const ApiConfigManager = ({
|
||||||
@@ -19,145 +18,206 @@ const ApiConfigManager = ({
|
|||||||
onDeleteConfig,
|
onDeleteConfig,
|
||||||
onRenameConfig,
|
onRenameConfig,
|
||||||
onUpsertConfig,
|
onUpsertConfig,
|
||||||
// setDraftNewConfig,
|
|
||||||
}: ApiConfigManagerProps) => {
|
}: ApiConfigManagerProps) => {
|
||||||
const [isNewMode, setIsNewMode] = useState(false);
|
const [editState, setEditState] = useState<'new' | 'rename' | null>(null);
|
||||||
const [isRenameMode, setIsRenameMode] = useState(false);
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [newConfigName, setNewConfigName] = useState("");
|
const inputRef = useRef<HTMLInputElement>();
|
||||||
const [renamedConfigName, setRenamedConfigName] = useState("");
|
|
||||||
|
|
||||||
const handleNewConfig = () => {
|
// Focus input when entering edit mode
|
||||||
setIsNewMode(true);
|
useEffect(() => {
|
||||||
setNewConfigName("");
|
if (editState) {
|
||||||
// setDraftNewConfig(true)
|
setTimeout(() => inputRef.current?.focus(), 0);
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveNewConfig = () => {
|
|
||||||
if (newConfigName.trim()) {
|
|
||||||
onUpsertConfig(newConfigName.trim());
|
|
||||||
setIsNewMode(false);
|
|
||||||
setNewConfigName("");
|
|
||||||
// setDraftNewConfig(false)
|
|
||||||
}
|
}
|
||||||
};
|
}, [editState]);
|
||||||
|
|
||||||
const handleCancelNewConfig = () => {
|
// Reset edit state when current profile changes
|
||||||
setIsNewMode(false);
|
useEffect(() => {
|
||||||
setNewConfigName("");
|
setEditState(null);
|
||||||
// setDraftNewConfig(false)
|
setInputValue("");
|
||||||
|
}, [currentApiConfigName]);
|
||||||
|
|
||||||
|
const handleStartNew = () => {
|
||||||
|
setEditState('new');
|
||||||
|
setInputValue("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartRename = () => {
|
const handleStartRename = () => {
|
||||||
setIsRenameMode(true);
|
setEditState('rename');
|
||||||
setRenamedConfigName(currentApiConfigName || "");
|
setInputValue(currentApiConfigName || "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveRename = () => {
|
const handleCancel = () => {
|
||||||
if (renamedConfigName.trim() && currentApiConfigName) {
|
setEditState(null);
|
||||||
onRenameConfig(currentApiConfigName, renamedConfigName.trim());
|
setInputValue("");
|
||||||
setIsRenameMode(false);
|
};
|
||||||
setRenamedConfigName("");
|
|
||||||
|
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 = () => {
|
const handleDelete = () => {
|
||||||
setIsRenameMode(false);
|
if (!currentApiConfigName || !listApiConfigMeta || listApiConfigMeta.length <= 1) return;
|
||||||
setRenamedConfigName("");
|
|
||||||
|
// Let the extension handle both deletion and selection
|
||||||
|
onDeleteConfig(currentApiConfigName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isOnlyProfile = listApiConfigMeta?.length === 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ marginBottom: 5 }}>
|
||||||
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
|
<div style={{
|
||||||
API Configuration
|
display: "flex",
|
||||||
</label>
|
flexDirection: "column",
|
||||||
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
|
gap: "2px"
|
||||||
{isNewMode ? (
|
}}>
|
||||||
<>
|
<label htmlFor="config-profile">
|
||||||
|
<span style={{ fontWeight: "500" }}>Configuration Profile</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{editState ? (
|
||||||
|
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
|
||||||
<VSCodeTextField
|
<VSCodeTextField
|
||||||
value={newConfigName}
|
ref={inputRef as any}
|
||||||
onInput={(e: any) => setNewConfigName(e.target.value)}
|
value={inputValue}
|
||||||
placeholder="Enter configuration name"
|
onInput={(e: any) => setInputValue(e.target.value)}
|
||||||
|
placeholder={editState === 'new' ? "Enter profile name" : "Enter new name"}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
|
onKeyDown={(e: any) => {
|
||||||
|
if (e.key === 'Enter' && inputValue.trim()) {
|
||||||
|
handleSave();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<VSCodeButton
|
<VSCodeButton
|
||||||
appearance="secondary"
|
appearance="icon"
|
||||||
disabled={!newConfigName.trim()}
|
disabled={!inputValue.trim()}
|
||||||
onClick={handleSaveNewConfig}
|
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>
|
||||||
<VSCodeButton
|
<VSCodeButton
|
||||||
appearance="secondary"
|
appearance="icon"
|
||||||
onClick={handleCancelNewConfig}
|
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>
|
</VSCodeButton>
|
||||||
</>
|
</div>
|
||||||
) : 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
|
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
|
||||||
value={currentApiConfigName}
|
<select
|
||||||
onChange={(e) => onSelectConfig(e.target.value)}
|
id="config-profile"
|
||||||
style={{
|
value={currentApiConfigName}
|
||||||
flexGrow: 1,
|
onChange={(e) => onSelectConfig(e.target.value)}
|
||||||
padding: "4px 8px",
|
style={{
|
||||||
backgroundColor: "var(--vscode-input-background)",
|
flexGrow: 1,
|
||||||
color: "var(--vscode-input-foreground)",
|
padding: "4px 8px",
|
||||||
border: "1px solid var(--vscode-input-border)",
|
paddingRight: "24px",
|
||||||
borderRadius: "2px",
|
backgroundColor: "var(--vscode-dropdown-background)",
|
||||||
height: "28px"
|
color: "var(--vscode-dropdown-foreground)",
|
||||||
}}>
|
border: "1px solid var(--vscode-dropdown-border)",
|
||||||
{listApiConfigMeta?.map((config) => (
|
borderRadius: "2px",
|
||||||
<option key={config.name} value={config.name}>{config.name} {config.apiProvider ? `(${config.apiProvider})` : ""}
|
height: "28px",
|
||||||
</option>
|
cursor: "pointer",
|
||||||
))}
|
outline: "none"
|
||||||
</select>
|
}}
|
||||||
<VSCodeButton
|
>
|
||||||
appearance="secondary"
|
{listApiConfigMeta?.map((config) => (
|
||||||
onClick={handleNewConfig}
|
<option
|
||||||
>
|
key={config.name}
|
||||||
<span className="codicon codicon-add" /> New
|
value={config.name}
|
||||||
</VSCodeButton>
|
>
|
||||||
<VSCodeButton
|
{config.name}
|
||||||
appearance="secondary"
|
</option>
|
||||||
disabled={!currentApiConfigName}
|
))}
|
||||||
onClick={handleStartRename}
|
</select>
|
||||||
>
|
<VSCodeButton
|
||||||
<span className="codicon codicon-edit" /> Rename
|
appearance="icon"
|
||||||
</VSCodeButton>
|
onClick={handleStartNew}
|
||||||
<VSCodeButton
|
title="Add profile"
|
||||||
appearance="secondary"
|
style={{
|
||||||
disabled={!currentApiConfigName}
|
padding: 0,
|
||||||
onClick={() => onDeleteConfig(currentApiConfigName!)}
|
margin: 0,
|
||||||
>
|
height: '28px',
|
||||||
<span className="codicon codicon-trash" /> Delete
|
width: '28px',
|
||||||
</VSCodeButton>
|
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>
|
</div>
|
||||||
<VSCodeDivider style={{ margin: "15px 0" }} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
<div
|
<div
|
||||||
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
|
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
|
||||||
<div style={{ marginBottom: 5 }}>
|
<div style={{ marginBottom: 5 }}>
|
||||||
|
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>Provider Settings</h3>
|
||||||
<ApiConfigManager
|
<ApiConfigManager
|
||||||
currentApiConfigName={currentApiConfigName}
|
currentApiConfigName={currentApiConfigName}
|
||||||
listApiConfigMeta={listApiConfigMeta}
|
listApiConfigMeta={listApiConfigMeta}
|
||||||
@@ -193,14 +194,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
apiConfiguration
|
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
|
<ApiOptions
|
||||||
showModelOptions={true}
|
showModelOptions={true}
|
||||||
apiErrorMessage={apiErrorMessage}
|
apiErrorMessage={apiErrorMessage}
|
||||||
|
|||||||
Reference in New Issue
Block a user