Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c642763f0 | |||
| 5815c0180a | |||
| 822430535e | |||
| 020fb60d74 | |||
| 615f8c3315 | |||
| 980d7937c3 | |||
| 57d94d2d44 | |||
| 382ecaae1b | |||
| a9abdf6351 | |||
| 27c40d8428 | |||
| c067d9fbf0 | |||
| 182af95035 | |||
| 23bb28f3c0 | |||
| 5995591296 | |||
| 1d15d29a19 |
@@ -10,3 +10,5 @@ In typescript, do NOT cast to types if not neccessary. NEVER lazily cast to 'any
|
||||
Do not add or remove semicolons to any of my files. Just go with convention and make the least number of changes.
|
||||
|
||||
Never modify files outside src/vs/workbench/contrib/void without consulting with the user first.
|
||||
|
||||
All types that map from a value A to B should be called bOfA. For example, if you create a hashmap that goes from toolId to toolName, it should be called toolNameOfToolId, etc.
|
||||
|
||||
@@ -641,7 +641,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||
|
||||
// 2. if tool requires approval, break from the loop, awaiting approval
|
||||
|
||||
const approvalType = isBuiltInTool ? approvalTypeOfBuiltinToolName[toolName] : 'mcp-tools'
|
||||
const approvalType = isBuiltInTool ? approvalTypeOfBuiltinToolName[toolName] : 'MCP tools'
|
||||
if (approvalType) {
|
||||
const autoApprove = this._settingsService.state.globalSettings.autoApprove[approvalType]
|
||||
// add a tool_request because we use it for UI if a tool is loading (this should be improved in the future)
|
||||
|
||||
@@ -1616,7 +1616,7 @@ const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) =>
|
||||
</button>
|
||||
)
|
||||
|
||||
const approvalType = isABuiltinToolName(toolName) ? approvalTypeOfBuiltinToolName[toolName] : 'mcp-tools'
|
||||
const approvalType = isABuiltinToolName(toolName) ? approvalTypeOfBuiltinToolName[toolName] : 'MCP tools'
|
||||
const approvalToggle = approvalType ? <div key={approvalType} className="flex items-center ml-2 gap-x-1">
|
||||
<ToolApprovalTypeSwitch size='xs' approvalType={approvalType} desc={`Auto-approve ${approvalType}`} />
|
||||
</div> : null
|
||||
|
||||
@@ -100,7 +100,7 @@ const tabNames = ['Free', 'Paid', 'Local'] as const;
|
||||
type TabName = typeof tabNames[number] | 'Cloud/Other';
|
||||
|
||||
// Data for cloud providers tab
|
||||
const cloudProviders: ProviderName[] = ['googleVertex', 'liteLLM', 'microsoftAzure', 'openAICompatible'];
|
||||
const cloudProviders: ProviderName[] = ['googleVertex', 'liteLLM', 'microsoftAzure', 'awsBedrock', 'openAICompatible'];
|
||||
|
||||
// Data structures for provider tabs
|
||||
const providerNamesOfTab: Record<TabName, ProviderName[]> = {
|
||||
|
||||
@@ -22,6 +22,16 @@ import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransfe
|
||||
import { MCPServer } from '../../../../common/mcpServiceTypes.js';
|
||||
import { useMCPServiceState } from '../util/services.js';
|
||||
|
||||
type Tab =
|
||||
| 'models'
|
||||
| 'localProviders'
|
||||
| 'providers'
|
||||
| 'featureOptions'
|
||||
| 'mcp'
|
||||
| 'general'
|
||||
| 'all';
|
||||
|
||||
|
||||
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
||||
|
||||
return <div className='flex items-center text-void-fg-3 px-3 py-0.5 rounded-sm overflow-hidden gap-2'>
|
||||
@@ -1018,6 +1028,20 @@ const MCPServersList = () => {
|
||||
|
||||
export const Settings = () => {
|
||||
const isDark = useIsDark()
|
||||
// ─── sidebar nav ──────────────────────────
|
||||
const [selectedSection, setSelectedSection] =
|
||||
useState<Tab>('models');
|
||||
|
||||
const navItems: { tab: Tab; label: string }[] = [
|
||||
{ tab: 'models', label: 'Models' },
|
||||
{ tab: 'localProviders', label: 'Local Providers' },
|
||||
{ tab: 'providers', label: 'Other Providers' },
|
||||
{ tab: 'featureOptions', label: 'Feature Options' },
|
||||
{ tab: 'general', label: 'General' },
|
||||
{ tab: 'mcp', label: 'MCP' },
|
||||
{ tab: 'all', label: 'All Settings' },
|
||||
];
|
||||
const shouldShowTab = (tab: Tab) => selectedSection === 'all' || selectedSection === tab;
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const environmentService = accessor.get('IEnvironmentService')
|
||||
@@ -1092,317 +1116,378 @@ export const Settings = () => {
|
||||
}
|
||||
|
||||
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ height: '100%', width: '100%' }}>
|
||||
<div className='overflow-y-auto w-full h-full px-10 py-10 select-none'>
|
||||
return (
|
||||
<div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ height: '100%', width: '100%', overflow: 'auto' }}>
|
||||
<div className="flex flex-col md:flex-row w-full gap-6 max-w-[900px] mx-auto mb-32" style={{ minHeight: '80vh' }}>
|
||||
{/* ────────────── SIDEBAR ────────────── */}
|
||||
|
||||
<div className='max-w-xl mx-auto'>
|
||||
<aside className="md:w-1/4 w-full p-6 shrink-0">
|
||||
{/* vertical tab list */}
|
||||
<div className="flex flex-col gap-2 mt-12">
|
||||
{navItems.map(({ tab, label }) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => {
|
||||
if (tab === 'all') {
|
||||
setSelectedSection('all');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
setSelectedSection(tab);
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
py-2 px-4 rounded-md text-left transition-all duration-200
|
||||
${selectedSection === tab
|
||||
? 'bg-[#0e70c0]/80 text-white font-medium shadow-sm'
|
||||
: 'bg-void-bg-2 hover:bg-void-bg-2/80 text-void-fg-1'}
|
||||
`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<h1 className='text-2xl w-full'>{`Void's Settings`}</h1>
|
||||
|
||||
<div className='w-full h-[1px] my-2' />
|
||||
|
||||
{/* Models section (formerly FeaturesTab) */}
|
||||
<ErrorBoundary>
|
||||
<RedoOnboardingButton />
|
||||
</ErrorBoundary>
|
||||
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
|
||||
{/* Models section (formerly FeaturesTab) */}
|
||||
<ErrorBoundary>
|
||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<ModelDump />
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
<AutoDetectLocalModelsToggle />
|
||||
<RefreshableModels />
|
||||
</ErrorBoundary>
|
||||
|
||||
|
||||
<h2 className={`text-3xl mb-2 mt-12`}>Local Providers</h2>
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
|
||||
|
||||
<div className='opacity-80 mb-4'>
|
||||
<OllamaSetupInstructions sayWeAutoDetect={true} />
|
||||
</div>
|
||||
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings providerNames={localProviderNames} />
|
||||
</ErrorBoundary>
|
||||
|
||||
<h2 className={`text-3xl mb-2 mt-12`}>Providers</h2>
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access models from Anthropic, OpenAI, OpenRouter, and more.`}</h3>
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings providerNames={nonlocalProviderNames} />
|
||||
</ErrorBoundary>
|
||||
{/* ───────────── MAIN PANE ───────────── */}
|
||||
<main className="flex-1 p-6 select-none">
|
||||
|
||||
|
||||
|
||||
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
|
||||
<div className='max-w-3xl'>
|
||||
|
||||
<div className='flex flex-col gap-y-8 my-4'>
|
||||
<ErrorBoundary>
|
||||
{/* FIM */}
|
||||
<div>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>
|
||||
<span>
|
||||
Experimental.{' '}
|
||||
</span>
|
||||
<span
|
||||
className='hover:brightness-110'
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content='We recommend using the largest qwen2.5-coder model you can with Ollama (try qwen2.5-coder:3b).'
|
||||
data-tooltip-class-name='void-max-w-[300px]'
|
||||
>
|
||||
Only works with FIM models.*
|
||||
</span>
|
||||
<h1 className='text-2xl w-full'>{`Void's Settings`}</h1>
|
||||
|
||||
<div className='w-full h-[1px] my-2' />
|
||||
|
||||
{/* Models section (formerly FeaturesTab) */}
|
||||
<ErrorBoundary>
|
||||
<RedoOnboardingButton />
|
||||
</ErrorBoundary>
|
||||
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
|
||||
{/* All sections in flex container with gap-12 */}
|
||||
<div className='flex flex-col gap-12'>
|
||||
{/* Models section (formerly FeaturesTab) */}
|
||||
<div className={shouldShowTab('models') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<ModelDump />
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
<AutoDetectLocalModelsToggle />
|
||||
<RefreshableModels />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Enable Switch */}
|
||||
{/* Local Providers section */}
|
||||
<div className={shouldShowTab('localProviders') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.enableAutocomplete}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
|
||||
<h2 className={`text-3xl mb-2`}>Local Providers</h2>
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
|
||||
|
||||
<div className='opacity-80 mb-4'>
|
||||
<OllamaSetupInstructions sayWeAutoDetect={true} />
|
||||
</div>
|
||||
|
||||
<VoidProviderSettings providerNames={localProviderNames} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{/* Other Providers section */}
|
||||
<div className={shouldShowTab('providers') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<h2 className={`text-3xl mb-2`}>Other Providers</h2>
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access models from Anthropic, OpenAI, OpenRouter, and more.`}</h3>
|
||||
|
||||
<VoidProviderSettings providerNames={nonlocalProviderNames} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{/* Feature Options section */}
|
||||
<div className={shouldShowTab('featureOptions') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<h2 className={`text-3xl mb-2`}>Feature Options</h2>
|
||||
|
||||
<div className='flex flex-col gap-y-8 my-4'>
|
||||
<ErrorBoundary>
|
||||
{/* FIM */}
|
||||
<div>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>
|
||||
<span>
|
||||
Experimental.{' '}
|
||||
</span>
|
||||
<span
|
||||
className='hover:brightness-110'
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content='We recommend using the largest qwen2.5-coder model you can with Ollama (try qwen2.5-coder:3b).'
|
||||
data-tooltip-class-name='void-max-w-[20px]'
|
||||
>
|
||||
Only works with FIM models.*
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Enable Switch */}
|
||||
<ErrorBoundary>
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.enableAutocomplete}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* Model Dropdown */}
|
||||
<ErrorBoundary>
|
||||
<div className={`my-2 ${!settingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}`}>
|
||||
<ModelDropdown featureName={'Autocomplete'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* Apply */}
|
||||
<ErrorBoundary>
|
||||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>Settings that control the behavior of the Apply button.</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Sync to Chat Switch */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.syncApplyToChat}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('syncApplyToChat', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.syncApplyToChat ? 'Same as Chat model' : 'Different model'}</span>
|
||||
</div>
|
||||
|
||||
{/* Model Dropdown */}
|
||||
<div className={`my-2 ${settingsState.globalSettings.syncApplyToChat ? 'hidden' : ''}`}>
|
||||
<ModelDropdown featureName={'Apply'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Fast Apply Method Dropdown */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<FastApplyMethodDropdown />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Tools Section */}
|
||||
<div>
|
||||
<h4 className={`text-base`}>Tools</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
<ErrorBoundary>
|
||||
{[...toolApprovalTypes].map((approvalType) => {
|
||||
return <div key={approvalType} className="flex items-center gap-x-2 my-2">
|
||||
<ToolApprovalTypeSwitch size='xs' approvalType={approvalType} desc={`Auto-approve ${approvalType}`} />
|
||||
</div>
|
||||
})}
|
||||
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* Tool Lint Errors Switch */}
|
||||
<ErrorBoundary>
|
||||
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.includeToolLintErrors}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Fix lint errors`}</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>Editor</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>{`Settings that control the visibility of Void suggestions in the code editor.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
<ErrorBoundary>
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.showInlineSuggestions}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('showInlineSuggestions', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.showInlineSuggestions ? 'Show suggestions on select' : 'Show suggestions on select'}</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{/* Model Dropdown */}
|
||||
<ErrorBoundary>
|
||||
<div className={`my-2 ${!settingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}`}>
|
||||
<ModelDropdown featureName={'Autocomplete'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
|
||||
{/* General section */}
|
||||
<div className={`${shouldShowTab('general') ? `` : 'hidden'} flex flex-col gap-12`}>
|
||||
{/* One-Click Switch section */}
|
||||
<div>
|
||||
<ErrorBoundary>
|
||||
<h2 className='text-3xl mb-2'>One-Click Switch</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer your editor settings into Void.`}</h4>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
|
||||
<OneClickSwitchButton className='w-48' fromEditor="Cursor" />
|
||||
<OneClickSwitchButton className='w-48' fromEditor="Windsurf" />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{/* Import/Export section */}
|
||||
<div>
|
||||
<h2 className='text-3xl mb-2'>Import/Export</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer Void's settings and chats in and out of Void.`}</h4>
|
||||
<div className='flex flex-col gap-8'>
|
||||
{/* Settings Subcategory */}
|
||||
<div className='flex flex-col gap-2 max-w-48 w-full'>
|
||||
<input key={2 * s} ref={fileInputSettingsRef} type='file' accept='.json' className='hidden' onChange={handleUpload('Settings')} />
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => { fileInputSettingsRef.current?.click() }}>
|
||||
Import Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => onDownload('Settings')}>
|
||||
Export Settings
|
||||
</VoidButtonBgDarken>
|
||||
<ConfirmButton className='px-4 py-1 w-full' onConfirm={() => { voidSettingsService.resetState(); }}>
|
||||
Reset Settings
|
||||
</ConfirmButton>
|
||||
</div>
|
||||
{/* Chats Subcategory */}
|
||||
<div className='flex flex-col gap-2 w-full max-w-48'>
|
||||
<input key={2 * s + 1} ref={fileInputChatsRef} type='file' accept='.json' className='hidden' onChange={handleUpload('Chats')} />
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => { fileInputChatsRef.current?.click() }}>
|
||||
Import Chats
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => onDownload('Chats')}>
|
||||
Export Chats
|
||||
</VoidButtonBgDarken>
|
||||
<ConfirmButton className='px-4 py-1 w-full' onConfirm={() => { chatThreadsService.resetState(); }}>
|
||||
Reset Chats
|
||||
</ConfirmButton>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* Apply */}
|
||||
<ErrorBoundary>
|
||||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>Settings that control the behavior of the Apply button.</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Sync to Chat Switch */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.syncApplyToChat}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('syncApplyToChat', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.syncApplyToChat ? 'Same as Chat model' : 'Different model'}</span>
|
||||
</div>
|
||||
|
||||
{/* Model Dropdown */}
|
||||
<div className={`my-2 ${settingsState.globalSettings.syncApplyToChat ? 'hidden' : ''}`}>
|
||||
<ModelDropdown featureName={'Apply'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
|
||||
|
||||
|
||||
{/* Built-in Settings section */}
|
||||
<div>
|
||||
<h2 className={`text-3xl mb-2`}>Built-in Settings</h2>
|
||||
<h4 className={`text-void-fg-3 mb-4`}>{`IDE settings, keyboard settings, and theme customization.`}</h4>
|
||||
|
||||
<ErrorBoundary>
|
||||
<div className='flex flex-col gap-2 justify-center max-w-48 w-full'>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { commandService.executeCommand('workbench.action.openSettings') }}>
|
||||
General Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}>
|
||||
Keyboard Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { commandService.executeCommand('workbench.action.selectTheme') }}>
|
||||
Theme Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
|
||||
Open Logs
|
||||
</VoidButtonBgDarken>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Fast Apply Method Dropdown */}
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<FastApplyMethodDropdown />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Tools Section */}
|
||||
<div>
|
||||
<h4 className={`text-base`}>Tools</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
<ErrorBoundary>
|
||||
{[...toolApprovalTypes].map((approvalType) => {
|
||||
return <div key={approvalType} className="flex items-center gap-x-2 my-2">
|
||||
<ToolApprovalTypeSwitch size='xs' approvalType={approvalType} desc={`Auto-approve ${approvalType}`} />
|
||||
</div>
|
||||
})}
|
||||
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* Tool Lint Errors Switch */}
|
||||
<ErrorBoundary>
|
||||
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.includeToolLintErrors}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Fix lint errors`}</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>Editor</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>{`Settings that control the visibility of Void suggestions in the code editor.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
<ErrorBoundary>
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.showInlineSuggestions}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('showInlineSuggestions', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.showInlineSuggestions ? 'Show suggestions on select' : 'Show suggestions on select'}</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* General section (formerly GeneralTab) */}
|
||||
<div className='mt-12'>
|
||||
<ErrorBoundary>
|
||||
<h2 className='text-3xl mb-2 mt-12'>One-Click Switch</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer your editor settings into Void.`}</h4>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
|
||||
<OneClickSwitchButton className='w-48' fromEditor="Cursor" />
|
||||
<OneClickSwitchButton className='w-48' fromEditor="Windsurf" />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{/* Import/Export section, as its own block right after One-Click Switch */}
|
||||
<div className='mt-12'>
|
||||
<h2 className='text-3xl mb-2'>Import/Export</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer Void's settings and chats in and out of Void.`}</h4>
|
||||
<div className='flex flex-col gap-8'>
|
||||
{/* Settings Subcategory */}
|
||||
<div className='flex flex-col gap-2 max-w-48 w-full'>
|
||||
<input key={2 * s} ref={fileInputSettingsRef} type='file' accept='.json' className='hidden' onChange={handleUpload('Settings')} />
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => { fileInputSettingsRef.current?.click() }}>
|
||||
Import Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => onDownload('Settings')}>
|
||||
Export Settings
|
||||
</VoidButtonBgDarken>
|
||||
<ConfirmButton className='px-4 py-1 w-full' onConfirm={() => { voidSettingsService.resetState(); }}>
|
||||
Reset Settings
|
||||
</ConfirmButton>
|
||||
</div>
|
||||
{/* Chats Subcategory */}
|
||||
<div className='flex flex-col gap-2 w-full max-w-48'>
|
||||
<input key={2 * s + 1} ref={fileInputChatsRef} type='file' accept='.json' className='hidden' onChange={handleUpload('Chats')} />
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => { fileInputChatsRef.current?.click() }}>
|
||||
Import Chats
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full' onClick={() => onDownload('Chats')}>
|
||||
Export Chats
|
||||
</VoidButtonBgDarken>
|
||||
<ConfirmButton className='px-4 py-1 w-full' onConfirm={() => { chatThreadsService.resetState(); }}>
|
||||
Reset Chats
|
||||
</ConfirmButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className='mt-12'>
|
||||
|
||||
<h2 className={`text-3xl mb-2`}>Built-in Settings</h2>
|
||||
<h4 className={`text-void-fg-3 mb-4`}>{`IDE settings, keyboard settings, and theme customization.`}</h4>
|
||||
|
||||
<ErrorBoundary>
|
||||
<div className='flex flex-col gap-2 justify-center max-w-48 w-full'>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { commandService.executeCommand('workbench.action.openSettings') }}>
|
||||
General Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}>
|
||||
Keyboard Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { commandService.executeCommand('workbench.action.selectTheme') }}>
|
||||
Theme Settings
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
|
||||
Open Logs
|
||||
</VoidButtonBgDarken>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='mt-12 max-w-[600px]'>
|
||||
<h2 className={`text-3xl mb-2`}>AI Instructions</h2>
|
||||
|
||||
<h4 className={`text-void-fg-3 mb-4`}>
|
||||
<ChatMarkdownRender inPTag={true} string={`
|
||||
{/* AI Instructions section */}
|
||||
<div className='max-w-[600px]'>
|
||||
<h2 className={`text-3xl mb-2`}>AI Instructions</h2>
|
||||
<h4 className={`text-void-fg-3 mb-4`}>
|
||||
<ChatMarkdownRender inPTag={true} string={`
|
||||
System instructions to include with all AI requests.
|
||||
Alternatively, place a \`.voidrules\` file in the root of your workspace.
|
||||
`} chatMessageLocation={undefined} />
|
||||
</h4>
|
||||
<ErrorBoundary>
|
||||
<AIInstructionsBox />
|
||||
</ErrorBoundary>
|
||||
{/* --- Disable System Message Toggle --- */}
|
||||
<div className='my-4'>
|
||||
<ErrorBoundary>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.disableSystemMessage}
|
||||
onChange={(newValue) => {
|
||||
voidSettingsService.setGlobalSetting('disableSystemMessage', newValue);
|
||||
}}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>
|
||||
{settingsState.globalSettings.disableSystemMessage ? 'Disable system message' : 'Disable system message'}
|
||||
</span>
|
||||
</h4>
|
||||
<ErrorBoundary>
|
||||
<AIInstructionsBox />
|
||||
</ErrorBoundary>
|
||||
{/* --- Disable System Message Toggle --- */}
|
||||
<div className='my-4'>
|
||||
<ErrorBoundary>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={!!settingsState.globalSettings.disableSystemMessage}
|
||||
onChange={(newValue) => {
|
||||
voidSettingsService.setGlobalSetting('disableSystemMessage', newValue);
|
||||
}}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>
|
||||
{'Disable system message'}
|
||||
</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
<div className='text-void-fg-3 text-xs mt-1'>
|
||||
{`When disabled, Void will not include anything in the system message except for content you specified above.`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
<div className='text-void-fg-3 text-xs mt-1'>
|
||||
{`When disabled, Void will not include anything in the system message except for content you specified above.`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-12'>
|
||||
<h2 className='text-3xl mb-2'>MCP</h2>
|
||||
<h4 className={`text-void-fg-3 mb-4`}>
|
||||
<ChatMarkdownRender inPTag={true} string={`
|
||||
|
||||
|
||||
{/* MCP section */}
|
||||
<div className={shouldShowTab('mcp') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<h2 className='text-3xl mb-2'>MCP</h2>
|
||||
<h4 className={`text-void-fg-3 mb-4`}>
|
||||
<ChatMarkdownRender inPTag={true} string={`
|
||||
Use Model Context Protocol to provide Agent mode with more tools.
|
||||
`} chatMessageLocation={undefined} />
|
||||
</h4>
|
||||
<div className='my-2'>
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full max-w-48' onClick={async () => { await mcpService.revealMCPConfigFile() }}>
|
||||
Add MCP Server
|
||||
</VoidButtonBgDarken>
|
||||
</div>
|
||||
</h4>
|
||||
<div className='my-2'>
|
||||
<VoidButtonBgDarken className='px-4 py-1 w-full max-w-48' onClick={async () => { await mcpService.revealMCPConfigFile() }}>
|
||||
Add MCP Server
|
||||
</VoidButtonBgDarken>
|
||||
</div>
|
||||
|
||||
<ErrorBoundary>
|
||||
<MCPServersList />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<ErrorBoundary>
|
||||
<MCPServersList />
|
||||
</ErrorBoundary>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,12 @@ export const defaultProviderSettings = {
|
||||
apiKey: '',
|
||||
azureApiVersion: '2024-05-01-preview',
|
||||
},
|
||||
awsBedrock: {
|
||||
apiKey: '',
|
||||
region: 'us-east-1', // add region setting
|
||||
endpoint: '', // optionally allow overriding default
|
||||
},
|
||||
|
||||
} as const
|
||||
|
||||
|
||||
@@ -145,6 +151,7 @@ export const defaultModelsOfProvider = {
|
||||
openAICompatible: [], // fallback
|
||||
googleVertex: [],
|
||||
microsoftAzure: [],
|
||||
awsBedrock: [],
|
||||
liteLLM: [],
|
||||
|
||||
|
||||
@@ -1098,6 +1105,18 @@ const microsoftAzureSettings: VoidStaticProviderInfo = {
|
||||
},
|
||||
}
|
||||
|
||||
// ---------------- AWS BEDROCK ----------------
|
||||
const awsBedrockModelOptions = {
|
||||
} as const satisfies Record<string, VoidStaticModelInfo>
|
||||
|
||||
const awsBedrockSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: awsBedrockModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// ---------------- VLLM, OLLAMA, OPENAICOMPAT (self-hosted / local) ----------------
|
||||
const ollamaModelOptions = {
|
||||
@@ -1437,6 +1456,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi
|
||||
|
||||
googleVertex: googleVertexSettings,
|
||||
microsoftAzure: microsoftAzureSettings,
|
||||
awsBedrock: awsBedrockSettings,
|
||||
} as const
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export type ShallowDirectoryItem = {
|
||||
}
|
||||
|
||||
|
||||
export const approvalTypeOfBuiltinToolName: Partial<{ [T in BuiltinToolName]?: 'edits' | 'terminal' | 'mcp-tools' }> = {
|
||||
export const approvalTypeOfBuiltinToolName: Partial<{ [T in BuiltinToolName]?: 'edits' | 'terminal' | 'MCP tools' }> = {
|
||||
'create_file_or_folder': 'edits',
|
||||
'delete_file_or_folder': 'edits',
|
||||
'rewrite_file': 'edits',
|
||||
@@ -35,7 +35,7 @@ export type ToolApprovalType = NonNullable<(typeof approvalTypeOfBuiltinToolName
|
||||
|
||||
export const toolApprovalTypes = new Set<ToolApprovalType>([
|
||||
...Object.values(approvalTypeOfBuiltinToolName),
|
||||
'mcp-tools',
|
||||
'MCP tools',
|
||||
])
|
||||
|
||||
|
||||
|
||||
@@ -103,6 +103,9 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
|
||||
else if (providerName === 'microsoftAzure') {
|
||||
return { title: 'Microsoft Azure OpenAI', }
|
||||
}
|
||||
else if (providerName === 'awsBedrock') {
|
||||
return { title: 'AWS Bedrock', }
|
||||
}
|
||||
|
||||
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
|
||||
}
|
||||
@@ -120,6 +123,7 @@ export const subTextMdOfProviderName = (providerName: ProviderName): string => {
|
||||
if (providerName === 'openAICompatible') return `Use any provider that's OpenAI-compatible (use this for llama.cpp and more).`
|
||||
if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with Void. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).'
|
||||
if (providerName === 'microsoftAzure') return 'Read more about endpoints [here](https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions?view=rest-aifoundry-model-inference-2024-05-01-preview&tabs=HTTP), and get your API key [here](https://learn.microsoft.com/en-us/azure/search/search-security-api-keys?tabs=rest-use%2Cportal-find%2Cportal-query#find-existing-keys).'
|
||||
if (providerName === 'awsBedrock') return 'Connect via a LiteLLM proxy or the AWS [Bedrock-Access-Gateway](https://github.com/aws-samples/bedrock-access-gateway). LiteLLM Bedrock setup docs are [here](https://docs.litellm.ai/docs/providers/bedrock).'
|
||||
if (providerName === 'ollama') return 'Read more about custom [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).'
|
||||
if (providerName === 'vLLM') return 'Read more about custom [Endpoints here](https://docs.vllm.ai/en/latest/getting_started/quickstart.html#openai-compatible-server).'
|
||||
if (providerName === 'lmStudio') return 'Read more about custom [Endpoints here](https://lmstudio.ai/docs/app/api/endpoints/openai).'
|
||||
@@ -151,7 +155,8 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
||||
providerName === 'mistral' ? 'api-key...' :
|
||||
providerName === 'googleVertex' ? 'AIzaSy...' :
|
||||
providerName === 'microsoftAzure' ? 'key-...' :
|
||||
'',
|
||||
providerName === 'awsBedrock' ? 'key-...' :
|
||||
'',
|
||||
|
||||
isPasswordField: true,
|
||||
}
|
||||
@@ -165,14 +170,16 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
||||
providerName === 'googleVertex' ? 'baseURL' :
|
||||
providerName === 'microsoftAzure' ? 'baseURL' :
|
||||
providerName === 'liteLLM' ? 'baseURL' :
|
||||
'(never)',
|
||||
providerName === 'awsBedrock' ? 'Endpoint' :
|
||||
'(never)',
|
||||
|
||||
placeholder: providerName === 'ollama' ? defaultProviderSettings.ollama.endpoint
|
||||
: providerName === 'vLLM' ? defaultProviderSettings.vLLM.endpoint
|
||||
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
|
||||
: providerName === 'lmStudio' ? defaultProviderSettings.lmStudio.endpoint
|
||||
: providerName === 'liteLLM' ? 'http://localhost:4000'
|
||||
: '(never)',
|
||||
: providerName === 'awsBedrock' ? 'http://localhost:4000/v1'
|
||||
: '(never)',
|
||||
|
||||
|
||||
}
|
||||
@@ -185,7 +192,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
||||
return {
|
||||
title: 'Region',
|
||||
placeholder: providerName === 'googleVertex' ? defaultProviderSettings.googleVertex.region
|
||||
: ''
|
||||
: providerName === 'awsBedrock'
|
||||
? defaultProviderSettings.awsBedrock.region
|
||||
: ''
|
||||
}
|
||||
}
|
||||
else if (settingName === 'azureApiVersion') {
|
||||
@@ -340,6 +349,12 @@ export const defaultSettingsOfProvider: SettingsOfProvider = {
|
||||
...modelInfoOfDefaultModelNames(defaultModelsOfProvider.microsoftAzure),
|
||||
_didFillInProviderSettings: undefined,
|
||||
},
|
||||
awsBedrock: { // aggregator (serves models from multiple providers)
|
||||
...defaultCustomSettings,
|
||||
...defaultProviderSettings.awsBedrock,
|
||||
...modelInfoOfDefaultModelNames(defaultModelsOfProvider.awsBedrock),
|
||||
_didFillInProviderSettings: undefined,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -122,6 +122,29 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
|
||||
const options = { endpoint, apiKey: thisConfig.apiKey, apiVersion };
|
||||
return new AzureOpenAI({ ...options, ...commonPayloadOpts });
|
||||
}
|
||||
else if (providerName === 'awsBedrock') {
|
||||
/**
|
||||
* We treat Bedrock as *OpenAI-compatible only through a proxy*:
|
||||
* • LiteLLM default → http://localhost:4000/v1
|
||||
* • Bedrock-Access-Gateway → https://<api-id>.execute-api.<region>.amazonaws.com/openai/
|
||||
*
|
||||
* The native Bedrock runtime endpoint
|
||||
* https://bedrock-runtime.<region>.amazonaws.com
|
||||
* is **NOT** OpenAI-compatible, so we do *not* fall back to it here.
|
||||
*/
|
||||
const { endpoint, apiKey } = settingsOfProvider.awsBedrock
|
||||
|
||||
// ① use the user-supplied proxy if present
|
||||
// ② otherwise default to local LiteLLM
|
||||
let baseURL = endpoint || 'http://localhost:4000/v1'
|
||||
|
||||
// Normalize: make sure we end with “/v1”
|
||||
if (!baseURL.endsWith('/v1'))
|
||||
baseURL = baseURL.replace(/\/+$/, '') + '/v1'
|
||||
|
||||
return new OpenAI({ baseURL, apiKey, ...commonPayloadOpts })
|
||||
}
|
||||
|
||||
|
||||
else if (providerName === 'deepseek') {
|
||||
const thisConfig = settingsOfProvider[providerName]
|
||||
@@ -906,6 +929,12 @@ export const sendLLMMessageToProviderImplementation = {
|
||||
sendFIM: null,
|
||||
list: null,
|
||||
},
|
||||
awsBedrock: {
|
||||
sendChat: (params) => _sendOpenAICompatibleChat(params),
|
||||
sendFIM: null,
|
||||
list: null,
|
||||
},
|
||||
|
||||
} satisfies CallFnOfProvider
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user