feat: add UI customization settings page

- Add new UISettings component with toggles for various UI elements
- Add UI visibility state properties to global settings schema
- Integrate UI settings into the main settings view
- Update ChatTextArea to respect UI visibility settings
- Update ChatView to respect context percentage bar and auto-approve tab settings
- Add backend message handlers for persisting UI settings
- Add comprehensive localization strings for all UI settings
- Update tests to support new UI visibility properties

Implements #7149 - allows users to customize which UI elements are visible for a cleaner, more personalized interface
This commit is contained in:
Roo Code 2025-08-17 01:24:47 +00:00
parent 2a974e8bf6
commit 44efd7652b
12 changed files with 635 additions and 65 deletions

View file

@ -148,6 +148,17 @@ export const globalSettingsSchema = z.object({
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
lastModeImportPath: z.string().optional(),
// UI Settings
showEnhancePromptButton: z.boolean().optional(),
showCodebaseIndexingButton: z.boolean().optional(),
showAddImagesToMessageButton: z.boolean().optional(),
showManageSlashCommandsButton: z.boolean().optional(),
showHintText: z.boolean().optional(),
showSendButton: z.boolean().optional(),
showApiConfigurationButton: z.boolean().optional(),
showAutoApproveTab: z.boolean().optional(),
showContextPercentageBar: z.boolean().optional(),
})
export type GlobalSettings = z.infer<typeof globalSettingsSchema>

View file

@ -2618,5 +2618,42 @@ export const webviewMessageHandler = async (
}
break
}
// UI Settings handlers
case "showEnhancePromptButton":
await updateGlobalState("showEnhancePromptButton", message.bool ?? true)
await provider.postStateToWebview()
break
case "showCodebaseIndexingButton":
await updateGlobalState("showCodebaseIndexingButton", message.bool ?? true)
await provider.postStateToWebview()
break
case "showAddImagesToMessageButton":
await updateGlobalState("showAddImagesToMessageButton", message.bool ?? true)
await provider.postStateToWebview()
break
case "showManageSlashCommandsButton":
await updateGlobalState("showManageSlashCommandsButton", message.bool ?? true)
await provider.postStateToWebview()
break
case "showHintText":
await updateGlobalState("showHintText", message.bool ?? true)
await provider.postStateToWebview()
break
case "showSendButton":
await updateGlobalState("showSendButton", message.bool ?? true)
await provider.postStateToWebview()
break
case "showApiConfigurationButton":
await updateGlobalState("showApiConfigurationButton", message.bool ?? true)
await provider.postStateToWebview()
break
case "showAutoApproveTab":
await updateGlobalState("showAutoApproveTab", message.bool ?? true)
await provider.postStateToWebview()
break
case "showContextPercentageBar":
await updateGlobalState("showContextPercentageBar", message.bool ?? true)
await provider.postStateToWebview()
break
}
}

View file

@ -191,5 +191,23 @@
"preventCompletionWithOpenTodos": {
"description": "Prevent task completion when there are incomplete todos in the todo list"
}
},
"uiSettings": {
"description": "Customize which UI elements are visible in the interface",
"promptInputArea": {
"title": "Prompt Input Area Customization",
"showEnhancePromptButton": "Show 'Enhance Prompt' button",
"showCodebaseIndexingButton": "Show 'Codebase Indexing' button",
"showAddImagesToMessageButton": "Show 'Add Images to Message' button",
"showManageSlashCommandsButton": "Show 'Manage Slash Commands' button",
"showHintText": "Show hint text (\"@ to add context, / to switch modes...\")",
"showSendButton": "Show 'Send' button",
"showApiConfigurationButton": "Show 'API Configuration' button",
"showAutoApproveTab": "Show 'Auto-Approve' tab"
},
"generalUI": {
"title": "General UI Element Customization",
"showContextPercentageBar": "Show context percentage bar during chat"
}
}
}

View file

@ -270,6 +270,16 @@ export type ExtensionState = Pick<
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
| "remoteControlEnabled"
// UI Settings
| "showEnhancePromptButton"
| "showCodebaseIndexingButton"
| "showAddImagesToMessageButton"
| "showManageSlashCommandsButton"
| "showHintText"
| "showSendButton"
| "showApiConfigurationButton"
| "showAutoApproveTab"
| "showContextPercentageBar"
> & {
version: string
clineMessages: ClineMessage[]

View file

@ -211,6 +211,16 @@ export interface WebviewMessage {
| "deleteCommand"
| "createCommand"
| "insertTextIntoTextarea"
// UI Settings
| "showEnhancePromptButton"
| "showCodebaseIndexingButton"
| "showAddImagesToMessageButton"
| "showManageSlashCommandsButton"
| "showHintText"
| "showSendButton"
| "showApiConfigurationButton"
| "showAutoApproveTab"
| "showContextPercentageBar"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"

View file

@ -89,6 +89,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
taskHistory,
clineMessages,
commands,
// UI Settings
showEnhancePromptButton,
showCodebaseIndexingButton,
showAddImagesToMessageButton,
showManageSlashCommandsButton,
showHintText,
showSendButton,
showApiConfigurationButton,
} = useExtensionState()
// Find the ID and display text for the currently selected API configuration
@ -921,19 +929,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
<div className={cn("flex", "items-center", "gap-1", "min-w-0")}>
<div className="shrink-0">{renderModeSelector()}</div>
<div className={cn("flex-1", "min-w-0", "overflow-hidden")}>
<ApiConfigSelector
value={currentConfigId}
displayName={displayName}
disabled={selectApiConfigDisabled}
title={t("chat:selectApiConfig")}
onChange={handleApiConfigChange}
triggerClassName="w-full text-ellipsis overflow-hidden"
listApiConfigMeta={listApiConfigMeta || []}
pinnedApiConfigs={pinnedApiConfigs}
togglePinnedApiConfig={togglePinnedApiConfig}
/>
</div>
{showApiConfigurationButton && (
<div className={cn("flex-1", "min-w-0", "overflow-hidden")}>
<ApiConfigSelector
value={currentConfigId}
displayName={displayName}
disabled={selectApiConfigDisabled}
title={t("chat:selectApiConfig")}
onChange={handleApiConfigChange}
triggerClassName="w-full text-ellipsis overflow-hidden"
listApiConfigMeta={listApiConfigMeta || []}
pinnedApiConfigs={pinnedApiConfigs}
togglePinnedApiConfig={togglePinnedApiConfig}
/>
</div>
)}
</div>
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
@ -957,30 +967,32 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</button>
</StandardTooltip>
)}
<SlashCommandsPopover />
<IndexingStatusBadge />
<StandardTooltip content={t("chat:addImages")}>
<button
aria-label={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={!shouldDisableImages ? onSelectImages : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!shouldDisableImages && "cursor-pointer",
shouldDisableImages &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
"mr-1",
)}>
<Image className="w-4 h-4" />
</button>
</StandardTooltip>
{showManageSlashCommandsButton && <SlashCommandsPopover />}
{showCodebaseIndexingButton && <IndexingStatusBadge />}
{showAddImagesToMessageButton && (
<StandardTooltip content={t("chat:addImages")}>
<button
aria-label={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={!shouldDisableImages ? onSelectImages : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!shouldDisableImages && "cursor-pointer",
shouldDisableImages &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
"mr-1",
)}>
<Image className="w-4 h-4" />
</button>
</StandardTooltip>
)}
</div>
</div>
)
@ -1091,29 +1103,31 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onScroll={() => updateHighlights()}
/>
<div className="absolute top-1 right-1 z-30">
<StandardTooltip content={t("chat:enhancePrompt")}>
<button
aria-label={t("chat:enhancePrompt")}
disabled={false}
onClick={handleEnhancePrompt}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
</button>
</StandardTooltip>
</div>
{showEnhancePromptButton && (
<div className="absolute top-1 right-1 z-30">
<StandardTooltip content={t("chat:enhancePrompt")}>
<button
aria-label={t("chat:enhancePrompt")}
disabled={false}
onClick={handleEnhancePrompt}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
</button>
</StandardTooltip>
</div>
)}
{!isEditMode && (
{!isEditMode && showSendButton && (
<div className="absolute bottom-1 right-1 z-30">
<StandardTooltip content={t("chat:sendMessage")}>
<button
@ -1137,7 +1151,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</div>
)}
{!inputValue && !isEditMode && (
{!inputValue && !isEditMode && showHintText && (
<div
className="absolute left-2 z-30 pr-9 flex items-center h-8 font-vscode-font-family text-vscode-editor-font-size leading-vscode-editor-line-height"
style={{

View file

@ -117,6 +117,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
soundEnabled,
soundVolume,
cloudIsAuthenticated,
showContextPercentageBar,
showAutoApproveTab,
} = useExtensionState()
const messagesRef = useRef(messages)
@ -1794,6 +1796,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
buttonsDisabled={sendingDisabled}
handleCondenseContext={handleCondenseContext}
todos={latestTodos}
showContextPercentageBar={showContextPercentageBar}
/>
{hasSystemPromptOverride && (
@ -1858,7 +1861,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
// This ensures it takes its natural height when there's space
// but becomes scrollable when the viewport is too small
*/}
{!task && (
{!task && showAutoApproveTab && (
<div className="mb-1 flex-initial min-h-0">
<AutoApproveMenu />
</div>
@ -1885,9 +1888,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
initialTopMostItemIndex={groupedMessages.length - 1}
/>
</div>
<div className={`flex-initial min-h-0 ${!areButtonsVisible ? "mb-1" : ""}`}>
<AutoApproveMenu />
</div>
{showAutoApproveTab && (
<div className={`flex-initial min-h-0 ${!areButtonsVisible ? "mb-1" : ""}`}>
<AutoApproveMenu />
</div>
)}
{areButtonsVisible && (
<div
className={`flex h-9 items-center mb-1 px-[15px] ${

View file

@ -31,6 +31,7 @@ export interface TaskHeaderProps {
buttonsDisabled: boolean
handleCondenseContext: (taskId: string) => void
todos?: any[]
showContextPercentageBar?: boolean
}
const TaskHeader = ({
@ -44,6 +45,7 @@ const TaskHeader = ({
buttonsDisabled,
handleCondenseContext,
todos,
showContextPercentageBar = true,
}: TaskHeaderProps) => {
const { t } = useTranslation()
const { apiConfiguration, currentTaskItem } = useExtensionState()
@ -120,7 +122,7 @@ const TaskHeader = ({
</div>
</div>
</div>
{!isTaskExpanded && contextWindow > 0 && (
{!isTaskExpanded && contextWindow > 0 && showContextPercentageBar && (
<div className="flex items-center gap-2 text-sm" onClick={(e) => e.stopPropagation()}>
<StandardTooltip
content={

View file

@ -73,6 +73,16 @@ describe("ChatTextArea", () => {
},
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings - all enabled by default for tests
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
})
@ -83,6 +93,16 @@ describe("ChatTextArea", () => {
openedTabs: [],
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} sendingDisabled={true} />)
const enhanceButton = getEnhancePromptButton()
@ -103,6 +123,16 @@ describe("ChatTextArea", () => {
apiConfiguration,
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} inputValue="Test prompt" />)
@ -125,6 +155,16 @@ describe("ChatTextArea", () => {
},
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} inputValue="" />)
@ -147,6 +187,16 @@ describe("ChatTextArea", () => {
},
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} inputValue="Test prompt" />)
@ -174,6 +224,16 @@ describe("ChatTextArea", () => {
},
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
rerender(<ChatTextArea {...defaultProps} />)
@ -275,6 +335,16 @@ describe("ChatTextArea", () => {
filePaths: [],
openedTabs: [],
cwd: mockCwd,
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesButton: true,
showSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
mockConvertToMentionPath.mockClear()
})
@ -506,6 +576,16 @@ describe("ChatTextArea", () => {
taskHistory: [],
clineMessages: mockClineMessages,
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesButton: true,
showSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
})
@ -659,6 +739,16 @@ describe("ChatTextArea", () => {
taskHistory: [],
clineMessages: mixedClineMessages,
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
const setInputValue = vi.fn()
@ -687,6 +777,16 @@ describe("ChatTextArea", () => {
taskHistory: [],
clineMessages: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
const setInputValue = vi.fn()
@ -718,6 +818,16 @@ describe("ChatTextArea", () => {
taskHistory: [],
clineMessages: clineMessagesWithEmpty,
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
const setInputValue = vi.fn()
@ -752,6 +862,16 @@ describe("ChatTextArea", () => {
taskHistory: mockTaskHistory,
clineMessages: [], // No conversation messages
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
const setInputValue = vi.fn()
@ -789,6 +909,16 @@ describe("ChatTextArea", () => {
],
clineMessages: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesButton: true,
showSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
rerender(<ChatTextArea {...defaultProps} setInputValue={setInputValue} inputValue="" />)
@ -812,6 +942,16 @@ describe("ChatTextArea", () => {
{ type: "say", say: "user_feedback", text: "Message 2", ts: 2000 },
],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
setInputValue.mockClear()
@ -918,6 +1058,16 @@ describe("ChatTextArea", () => {
taskHistory: [],
cwd: "/test/workspace",
commands: mockCommands,
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
})
@ -1027,6 +1177,16 @@ describe("ChatTextArea", () => {
taskHistory: [],
cwd: "/test/workspace",
commands: undefined,
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesButton: true,
showSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/setup the project" />)
@ -1048,11 +1208,43 @@ describe("ChatTextArea", () => {
return screen.getByTestId("dropdown-trigger")
}
it("should be enabled independently of sendingDisabled", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
filePaths: [],
openedTabs: [],
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings - API config button must be visible
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} sendingDisabled={true} selectApiConfigDisabled={false} />)
const apiConfigDropdown = getApiConfigDropdown()
expect(apiConfigDropdown).not.toHaveAttribute("disabled")
})
it("should be disabled when selectApiConfigDisabled is true", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
filePaths: [],
openedTabs: [],
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings - API config button must be visible
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} sendingDisabled={true} selectApiConfigDisabled={true} />)
const apiConfigDropdown = getApiConfigDropdown()
expect(apiConfigDropdown).toHaveAttribute("disabled")
@ -1067,6 +1259,16 @@ describe("ChatTextArea", () => {
cwd: "/test/workspace",
customModes: [],
customModePrompts: {},
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} isEditMode={true} />)
@ -1091,6 +1293,16 @@ describe("ChatTextArea", () => {
openedTabs: [],
taskHistory: [],
cwd: "/test/workspace",
// UI visibility settings
showEnhancePromptButton: true,
showCodebaseIndexingButton: true,
showAddImagesToMessageButton: true,
showManageSlashCommandsButton: true,
showHintText: true,
showSendButton: true,
showApiConfigurationButton: true,
showAutoApproveTab: true,
showContextPercentageBar: true,
})
render(<ChatTextArea {...defaultProps} isEditMode={false} />)

View file

@ -23,6 +23,7 @@ import {
Info,
MessageSquare,
LucideIcon,
Palette,
} from "lucide-react"
import type { ProviderSettings, ExperimentId } from "@roo-code/types"
@ -65,6 +66,7 @@ import { LanguageSettings } from "./LanguageSettings"
import { About } from "./About"
import { Section } from "./Section"
import PromptsSettings from "./PromptsSettings"
import { UISettings } from "./UISettings"
import { cn } from "@/lib/utils"
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
@ -87,6 +89,7 @@ const sectionNames = [
"contextManagement",
"terminal",
"prompts",
"uiSettings",
"experimental",
"language",
"about",
@ -183,6 +186,16 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
includeDiagnosticMessages,
maxDiagnosticMessages,
includeTaskHistoryInEnhance,
// UI Settings
showEnhancePromptButton,
showCodebaseIndexingButton,
showAddImagesToMessageButton,
showManageSlashCommandsButton,
showHintText,
showSendButton,
showApiConfigurationButton,
showAutoApproveTab,
showContextPercentageBar,
} = cachedState
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
@ -342,6 +355,16 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "updateCondensingPrompt", text: customCondensingPrompt || "" })
vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} })
vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? true })
// UI Settings
vscode.postMessage({ type: "showEnhancePromptButton", bool: showEnhancePromptButton ?? true })
vscode.postMessage({ type: "showCodebaseIndexingButton", bool: showCodebaseIndexingButton ?? true })
vscode.postMessage({ type: "showAddImagesToMessageButton", bool: showAddImagesToMessageButton ?? true })
vscode.postMessage({ type: "showManageSlashCommandsButton", bool: showManageSlashCommandsButton ?? true })
vscode.postMessage({ type: "showHintText", bool: showHintText ?? true })
vscode.postMessage({ type: "showSendButton", bool: showSendButton ?? true })
vscode.postMessage({ type: "showApiConfigurationButton", bool: showApiConfigurationButton ?? true })
vscode.postMessage({ type: "showAutoApproveTab", bool: showAutoApproveTab ?? true })
vscode.postMessage({ type: "showContextPercentageBar", bool: showContextPercentageBar ?? true })
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
@ -422,6 +445,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{ id: "contextManagement", icon: Database },
{ id: "terminal", icon: SquareTerminal },
{ id: "prompts", icon: MessageSquare },
{ id: "uiSettings", icon: Palette },
{ id: "experimental", icon: FlaskConical },
{ id: "language", icon: Globe },
{ id: "about", icon: Info },
@ -718,6 +742,22 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
/>
)}
{/* UI Settings Section */}
{activeTab === "uiSettings" && (
<UISettings
showEnhancePromptButton={showEnhancePromptButton}
showCodebaseIndexingButton={showCodebaseIndexingButton}
showAddImagesToMessageButton={showAddImagesToMessageButton}
showManageSlashCommandsButton={showManageSlashCommandsButton}
showHintText={showHintText}
showSendButton={showSendButton}
showApiConfigurationButton={showApiConfigurationButton}
showAutoApproveTab={showAutoApproveTab}
showContextPercentageBar={showContextPercentageBar}
setCachedStateField={setCachedStateField}
/>
)}
{/* Experimental Section */}
{activeTab === "experimental" && (
<ExperimentalSettings setExperimentEnabled={setExperimentEnabled} experiments={experiments} />

View file

@ -0,0 +1,167 @@
import React, { HTMLAttributes } from "react"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { Palette } from "lucide-react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { cn } from "@/lib/utils"
import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
type UISettingsProps = HTMLAttributes<HTMLDivElement> & {
// Prompt Input Area settings
showEnhancePromptButton?: boolean
showCodebaseIndexingButton?: boolean
showAddImagesToMessageButton?: boolean
showManageSlashCommandsButton?: boolean
showHintText?: boolean
showSendButton?: boolean
showApiConfigurationButton?: boolean
showAutoApproveTab?: boolean
// General UI settings
showContextPercentageBar?: boolean
// Setters
setCachedStateField: SetCachedStateField<
| "showEnhancePromptButton"
| "showCodebaseIndexingButton"
| "showAddImagesToMessageButton"
| "showManageSlashCommandsButton"
| "showHintText"
| "showSendButton"
| "showApiConfigurationButton"
| "showAutoApproveTab"
| "showContextPercentageBar"
>
}
export const UISettings = ({
showEnhancePromptButton = true,
showCodebaseIndexingButton = true,
showAddImagesToMessageButton = true,
showManageSlashCommandsButton = true,
showHintText = true,
showSendButton = true,
showApiConfigurationButton = true,
showAutoApproveTab = true,
showContextPercentageBar = true,
setCachedStateField,
className,
...props
}: UISettingsProps) => {
const { t } = useAppTranslation()
return (
<div className={cn("flex flex-col gap-2", className)} {...props}>
<SectionHeader description={t("settings:uiSettings.description")}>
<div className="flex items-center gap-2">
<Palette className="w-4" />
<div>{t("settings:sections.uiSettings")}</div>
</div>
</SectionHeader>
<Section>
<div className="space-y-4">
{/* Prompt Input Area Customization */}
<div>
<h4 className="font-medium mb-3">{t("settings:uiSettings.promptInputArea.title")}</h4>
<div className="space-y-3 pl-4">
<VSCodeCheckbox
checked={showEnhancePromptButton}
onChange={(e: any) => setCachedStateField("showEnhancePromptButton", e.target.checked)}
data-testid="show-enhance-prompt-button-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showEnhancePromptButton")}
</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showCodebaseIndexingButton}
onChange={(e: any) =>
setCachedStateField("showCodebaseIndexingButton", e.target.checked)
}
data-testid="show-codebase-indexing-button-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showCodebaseIndexingButton")}
</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showAddImagesToMessageButton}
onChange={(e: any) =>
setCachedStateField("showAddImagesToMessageButton", e.target.checked)
}
data-testid="show-add-images-button-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showAddImagesToMessageButton")}
</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showManageSlashCommandsButton}
onChange={(e: any) =>
setCachedStateField("showManageSlashCommandsButton", e.target.checked)
}
data-testid="show-manage-slash-commands-button-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showManageSlashCommandsButton")}
</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showHintText}
onChange={(e: any) => setCachedStateField("showHintText", e.target.checked)}
data-testid="show-hint-text-checkbox">
<label className="block">{t("settings:uiSettings.promptInputArea.showHintText")}</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showSendButton}
onChange={(e: any) => setCachedStateField("showSendButton", e.target.checked)}
data-testid="show-send-button-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showSendButton")}
</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showApiConfigurationButton}
onChange={(e: any) =>
setCachedStateField("showApiConfigurationButton", e.target.checked)
}
data-testid="show-api-configuration-button-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showApiConfigurationButton")}
</label>
</VSCodeCheckbox>
<VSCodeCheckbox
checked={showAutoApproveTab}
onChange={(e: any) => setCachedStateField("showAutoApproveTab", e.target.checked)}
data-testid="show-auto-approve-tab-checkbox">
<label className="block">
{t("settings:uiSettings.promptInputArea.showAutoApproveTab")}
</label>
</VSCodeCheckbox>
</div>
</div>
{/* General UI Element Customization */}
<div>
<h4 className="font-medium mb-3">{t("settings:uiSettings.generalUI.title")}</h4>
<div className="space-y-3 pl-4">
<VSCodeCheckbox
checked={showContextPercentageBar}
onChange={(e: any) => setCachedStateField("showContextPercentageBar", e.target.checked)}
data-testid="show-context-percentage-bar-checkbox">
<label className="block">
{t("settings:uiSettings.generalUI.showContextPercentageBar")}
</label>
</VSCodeCheckbox>
</div>
</div>
</div>
</Section>
</div>
)
}

View file

@ -149,6 +149,25 @@ export interface ExtensionStateContextType extends ExtensionState {
setMaxDiagnosticMessages: (value: number) => void
includeTaskHistoryInEnhance?: boolean
setIncludeTaskHistoryInEnhance: (value: boolean) => void
// UI Settings
showEnhancePromptButton: boolean
setShowEnhancePromptButton: (value: boolean) => void
showCodebaseIndexingButton: boolean
setShowCodebaseIndexingButton: (value: boolean) => void
showAddImagesToMessageButton: boolean
setShowAddImagesToMessageButton: (value: boolean) => void
showManageSlashCommandsButton: boolean
setShowManageSlashCommandsButton: (value: boolean) => void
showHintText: boolean
setShowHintText: (value: boolean) => void
showSendButton: boolean
setShowSendButton: (value: boolean) => void
showApiConfigurationButton: boolean
setShowApiConfigurationButton: (value: boolean) => void
showAutoApproveTab: boolean
setShowAutoApproveTab: (value: boolean) => void
showContextPercentageBar: boolean
setShowContextPercentageBar: (value: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -522,6 +541,31 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
},
includeTaskHistoryInEnhance,
setIncludeTaskHistoryInEnhance,
// UI Settings - these come from state now
showEnhancePromptButton: state.showEnhancePromptButton ?? true,
setShowEnhancePromptButton: (value) =>
setState((prevState) => ({ ...prevState, showEnhancePromptButton: value })),
showCodebaseIndexingButton: state.showCodebaseIndexingButton ?? true,
setShowCodebaseIndexingButton: (value) =>
setState((prevState) => ({ ...prevState, showCodebaseIndexingButton: value })),
showAddImagesToMessageButton: state.showAddImagesToMessageButton ?? true,
setShowAddImagesToMessageButton: (value) =>
setState((prevState) => ({ ...prevState, showAddImagesToMessageButton: value })),
showManageSlashCommandsButton: state.showManageSlashCommandsButton ?? true,
setShowManageSlashCommandsButton: (value) =>
setState((prevState) => ({ ...prevState, showManageSlashCommandsButton: value })),
showHintText: state.showHintText ?? true,
setShowHintText: (value) => setState((prevState) => ({ ...prevState, showHintText: value })),
showSendButton: state.showSendButton ?? true,
setShowSendButton: (value) => setState((prevState) => ({ ...prevState, showSendButton: value })),
showApiConfigurationButton: state.showApiConfigurationButton ?? true,
setShowApiConfigurationButton: (value) =>
setState((prevState) => ({ ...prevState, showApiConfigurationButton: value })),
showAutoApproveTab: state.showAutoApproveTab ?? true,
setShowAutoApproveTab: (value) => setState((prevState) => ({ ...prevState, showAutoApproveTab: value })),
showContextPercentageBar: state.showContextPercentageBar ?? true,
setShowContextPercentageBar: (value) =>
setState((prevState) => ({ ...prevState, showContextPercentageBar: value })),
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>