Merge pull request #406 from voideditor/model-selection

Onboarding, SSH
This commit is contained in:
Andrew Pareles 2025-04-16 04:27:13 -07:00 committed by GitHub
commit f09f538b05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 588 additions and 365 deletions

View file

@ -86,7 +86,7 @@ function prepareDebPackage(arch) {
const dependencies = await dependenciesGenerator.getDependencies('deb', binaryDir, product.applicationName, debArch);
gulp.src('resources/linux/debian/control.template', { base: '.' })
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
.pipe(replace('@@VERSION@@', `${product.voidVersion}.${packageJson.release}`))
.pipe(replace('@@ARCHITECTURE@@', debArch))
.pipe(replace('@@DEPENDS@@', dependencies.join(', ')))
.pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', ')))
@ -201,7 +201,7 @@ function prepareRpmPackage(arch) {
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@NAME_LONG@@', product.nameLong))
.pipe(replace('@@ICON@@', product.linuxIconName))
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
.pipe(replace('@@VERSION@@', `${product.voidVersion}.${packageJson.release}`))
.pipe(replace('@@ARCHITECTURE@@', rpmArch))
.pipe(replace('@@LICENSE@@', product.licenseName))
.pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@'))
@ -277,7 +277,7 @@ function prepareSnapPackage(arch) {
const snapcraft = gulp.src('resources/linux/snap/snapcraft.yaml', { base: '.' })
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
.pipe(replace('@@VERSION@@', `${product.voidVersion}.${packageJson.release}`))
// Possible run-on values https://snapcraft.io/docs/architectures
.pipe(replace('@@ARCHITECTURE@@', arch === 'x64' ? 'amd64' : arch))
.pipe(rename('snap/snapcraft.yaml'));

View file

@ -22,10 +22,12 @@ import { ColorScheme } from '../../web.api.js';
import { OpenFileFolderAction, OpenFolderAction } from '../../actions/workspaceActions.js';
import { IWindowOpenable } from '../../../../platform/window/common/window.js';
import { splitRecentLabel } from '../../../../base/common/labels.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
/* eslint-disable */ // Void
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/actionIDs.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../contrib/void/browser/voidSettingsPane.js';
import { VIEWLET_ID as REMOTE_EXPLORER_VIEWLET_ID } from '../../../contrib/remote/browser/remoteExplorer.js';
/* eslint-enable */
// interface WatermarkEntry {
@ -98,6 +100,7 @@ export class EditorGroupWatermark extends Disposable {
@ICommandService private readonly commandService: ICommandService,
@IHostService private readonly hostService: IHostService,
@ILabelService private readonly labelService: ILabelService,
@IViewsService private readonly viewsService: IViewsService,
) {
super();
@ -182,14 +185,21 @@ export class EditorGroupWatermark extends Disposable {
// Void - if the workbench is empty, show open
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
// Create a flex container for buttons with vertical direction
const buttonContainer = $('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.flexDirection = 'column'; // Change to column for vertical stacking
buttonContainer.style.alignItems = 'center'; // Center the buttons horizontally
buttonContainer.style.gap = '8px'; // Reduce gap between buttons from 16px to 8px
buttonContainer.style.marginBottom = '16px';
voidIconBox.appendChild(buttonContainer);
// Open a folder
const openFolderButton = h('button')
openFolderButton.root.classList.add('void-watermark-button')
openFolderButton.root.classList.add('void-openfolder-button')
openFolderButton.root.style.display = 'block'
openFolderButton.root.style.marginLeft = 'auto'
openFolderButton.root.style.marginRight = 'auto'
openFolderButton.root.style.marginBottom = '16px'
openFolderButton.root.textContent = 'Open a folder'
openFolderButton.root.style.width = '124px' // Set width to 124px as requested
openFolderButton.root.textContent = 'Open Folder'
openFolderButton.root.onclick = () => {
this.commandService.executeCommand(isMacintosh && isNative ? OpenFileFolderAction.ID : OpenFolderAction.ID)
// if (this.contextKeyService.contextMatchesRules(ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace')))) {
@ -198,7 +208,19 @@ export class EditorGroupWatermark extends Disposable {
// this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder');
// }
}
voidIconBox.appendChild(openFolderButton.root);
buttonContainer.appendChild(openFolderButton.root);
// Open SSH button
const openSSHButton = h('button')
openSSHButton.root.classList.add('void-openssh-button')
openSSHButton.root.style.display = 'block'
openSSHButton.root.style.backgroundColor = '#5a5a5a' // Made darker than the default gray
openSSHButton.root.style.width = '124px' // Set width to 124px as requested
openSSHButton.root.textContent = 'Open SSH'
openSSHButton.root.onclick = () => {
this.viewsService.openViewContainer(REMOTE_EXPLORER_VIEWLET_ID);
}
buttonContainer.appendChild(openSSHButton.root);
// Recents
@ -244,6 +266,9 @@ export class EditorGroupWatermark extends Disposable {
const dirSpan = $('span');
dirSpan.style.paddingLeft = '4px';
dirSpan.style.whiteSpace = 'nowrap';
dirSpan.style.overflow = 'hidden';
dirSpan.style.maxWidth = '300px';
dirSpan.innerText = parentPath;
dirSpan.title = fullPath;

View file

@ -50,6 +50,7 @@
}
/* light */
.void-void-icon,
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .letterpress {
width: 100%;
max-height: 100%;
@ -60,14 +61,17 @@
background-repeat: no-repeat;
}
.void-void-icon,
.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
}
.void-void-icon,
.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
}
.void-void-icon,
.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
}

View file

@ -792,7 +792,6 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
const featureName: FeatureName = 'Autocomplete'
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
const aiInstructions = this._settingsService.state.globalSettings.aiInstructions
// set parameters of `newAutocompletion` appropriately
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
@ -804,8 +803,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
prefix: llmPrefix,
suffix: llmSuffix,
stopTokens: stopTokens,
},
aiInstructions
}
}),
modelSelection,
modelSelectionOptions,

View file

@ -13,6 +13,8 @@ import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { ChatMode, FeatureName, ModelSelection } from '../common/voidSettingsTypes.js';
import { IDirectoryStrService } from './directoryStrService.js';
import { ITerminalToolService } from './terminalToolService.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { URI } from '../../../../base/common/uri.js';
@ -412,9 +414,9 @@ const prepareMessages = ({
export interface IConvertToLLMMessageService {
readonly _serviceBrand: undefined;
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined };
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined }
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }>
prepareFIMMessage(opts: { messages: LLMFIMMessage, aiInstructions: string, }): { prefix: string, suffix: string, stopTokens: string[] }
prepareFIMMessage(opts: { messages: LLMFIMMessage, }): { prefix: string, suffix: string, stopTokens: string[] }
}
@ -431,10 +433,35 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
@ITerminalToolService private readonly terminalToolService: ITerminalToolService,
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
@IVoidModelService private readonly voidModelService: IVoidModelService,
) {
super()
}
// Read .voidinstructions files from workspace folders
private _getVoidInstructionsFileContents(): string {
const workspaceFolders = this.workspaceContextService.getWorkspace().folders;
let voidInstructions = '';
for (const folder of workspaceFolders) {
const uri = URI.joinPath(folder.uri, '.voidinstructions')
const { model } = this.voidModelService.getModel(uri)
if (!model) continue
voidInstructions += model.getValue() + '\n\n';
}
return voidInstructions.trim();
}
// Get combined AI instructions from settings and .voidinstructions files
private _getCombinedAIInstructions(): string {
const globalAIInstructions = this.voidSettingsService.state.globalSettings.aiInstructions;
const voidInstructionsFileContent = this._getVoidInstructionsFileContents();
const ans: string[] = []
if (globalAIInstructions) ans.push(globalAIInstructions)
if (voidInstructionsFileContent) ans.push(voidInstructionsFileContent)
return ans.join('\n\n')
}
// system message
private _generateChatMessagesSystemMessage = async (chatMode: ChatMode, specialToolFormat: 'openai-style' | 'anthropic-style' | undefined) => {
@ -502,7 +529,9 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
} = getModelCapabilities(providerName, modelName)
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
@ -518,7 +547,6 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
maxOutputTokens,
})
return { messages, separateSystemMessage };
}
prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection }) => {
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
@ -531,7 +559,9 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
const systemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat)
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName]
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions)
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
@ -548,19 +578,20 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
maxOutputTokens,
})
return { messages, separateSystemMessage };
}
// --- FIM ---
prepareFIMMessage: IConvertToLLMMessageService['prepareFIMMessage'] = ({ messages, aiInstructions }) => {
prepareFIMMessage: IConvertToLLMMessageService['prepareFIMMessage'] = ({ messages }) => {
// Get combined AI instructions with the provided aiInstructions as the base
const combinedInstructions = this._getCombinedAIInstructions();
let prefix = `\
${!aiInstructions ? '' : `\
${!combinedInstructions ? '' : `\
// Instructions:
// Do not output an explanation. Try to avoid outputting comments. Only output the middle code.
${aiInstructions.split('\n').map(line => `//${line}`).join('\n')}`}
${combinedInstructions.split('\n').map(line => `//${line}`).join('\n')}`}
${messages.prefix}`
@ -573,7 +604,6 @@ ${messages.prefix}`
}
// pick one and delete the other:
registerSingleton(IConvertToLLMMessageService, ConvertToLLMMessageService, InstantiationType.Eager);

View file

@ -0,0 +1,37 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { IVoidModelService } from '../common/voidModelService.js';
class ConvertContribWorkbenchContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.void.convertcontrib'
_serviceBrand: undefined;
constructor(
@IVoidModelService private readonly voidModelService: IVoidModelService,
@IWorkspaceContextService private readonly workspaceContext: IWorkspaceContextService,
) {
super()
const initializeURI = (uri: URI) => {
this.workspaceContext.getWorkspace()
const voidInstrsURI = URI.joinPath(uri, '.voidinstructions')
this.voidModelService.initializeModel(voidInstrsURI)
}
// call
this._register(this.workspaceContext.onDidChangeWorkspaceFolders((e) => {
[...e.changed, ...e.added].forEach(w => { initializeURI(w.uri) })
}))
this.workspaceContext.getWorkspace().folders.forEach(w => { initializeURI(w.uri) })
}
}
registerWorkbenchContribution2(ConvertContribWorkbenchContribution.ID, ConvertContribWorkbenchContribution, WorkbenchPhase.BlockRestore);

View file

@ -3,7 +3,7 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
.monaco-editor .void-sweepIdxBG {
.monaco-editor .void-sweepIdxBG {
background-color: var(--vscode-void-sweepIdxBG);
}
@ -23,10 +23,30 @@
background-color: var(--vscode-void-redBG);
}
.void-watermark-button {
margin: 8px 0;
/* Renamed from void-watermark-button to void-openfolder-button */
.void-openfolder-button {
padding: 8px 20px;
background-color: #3b82f6;
background-color: #306dce;
color: white;
border: none;
border-radius: 4px;
outline: none !important;
box-shadow: none !important;
cursor: pointer;
transition: background-color 0.2s ease;
}
.void-openfolder-button:hover {
background-color: #2563eb;
}
.void-openfolder-button:active {
background-color: #2563eb;
}
/* Added for Open SSH button with slightly darker color */
.void-openssh-button {
padding: 8px 20px;
background-color: #656565; /* Slightly darker than the #5a5a5a in the TS file */
color: white;
border: none;
border-radius: 4px;
@ -35,43 +55,41 @@
cursor: pointer;
transition: background-color 0.2s ease;
}
.void-watermark-button:hover {
background-color: #2563eb;
.void-openssh-button:hover {
background-color: #474747; /* Darker on hover */
}
.void-watermark-button:active {
background-color: #2563eb;
.void-openssh-button:active {
background-color: #474747;
}
.void-settings-watermark-button {
margin: 8px 0;
padding: 8px 20px;
background-color: var(--vscode-input-background);
margin: 8px 0;
padding: 8px 20px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: none;
border-radius: 4px;
outline: none !important;
border: none;
border-radius: 4px;
outline: none !important;
box-shadow: none !important;
cursor: pointer;
transition: all 0.2s ease;
cursor: pointer;
transition: all 0.2s ease;
}
.void-settings-watermark-button:hover {
filter: brightness(1.1);
}
.void-settings-watermark-button:active {
filter: brightness(1.1);
}
.void-link {
color: #3b82f6;
cursor: pointer;
transition: all 0.2s ease;
}
.void-link:hover {
opacity: 80%;
}
@ -86,7 +104,8 @@
.void-scope,
.void-scope * {
scrollbar-width: thin !important;
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important;
/* For Firefox */
}
.void-scope::-webkit-scrollbar,
@ -133,13 +152,16 @@
background-color: var(--vscode-editor-background);
--scrollbar-vertical-width: 14px;
--scrollbar-horizontal-height: 6px;
overflow: auto; /* Ensure scrollbars are shown when needed */
overflow: auto;
/* Ensure scrollbars are shown when needed */
}
.void-scrollable-element,
.void-scrollable-element * {
scrollbar-width: thin !important; /* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
scrollbar-width: thin !important;
/* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important;
/* For Firefox */
}
.void-scrollable-element::-webkit-scrollbar,

View file

@ -5,6 +5,7 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { ErrorDisplay } from './ErrorDisplay.js';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
interface Props {
children: ReactNode;
@ -51,11 +52,12 @@ class ErrorBoundary extends Component<Props, State> {
// Use ErrorDisplay component as the default error UI
return (
<ErrorDisplay
message={this.state.error + ''}
fullError={this.state.error}
onDismiss={this.props.onDismiss || null}
/>
<WarningBox text={this.state.error + ''} />
// <ErrorDisplay
// message={this.state.error + ''}
// fullError={this.state.error}
// onDismiss={this.props.onDismiss || null}
// />
);
}

View file

@ -29,6 +29,7 @@ import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
import { MAX_FILE_CHARS_PAGE } from '../../../toolsService.js';
import ErrorBoundary from './ErrorBoundary.js';
@ -2483,7 +2484,7 @@ export const SidebarChat = () => {
const currCheckpointIdx = chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? undefined // if not exist, treat like checkpoint is last message (infinity)
const previousMessagesHTML = useMemo(() => {
const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint')
// const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint')
// tool request shows up as Editing... if in progress
return previousMessages.map((message, i) => {
return <ChatBubble
@ -2620,14 +2621,20 @@ export const SidebarChat = () => {
<div ref={sidebarRef} className='w-full h-full flex flex-col overflow-hidden'>
{/* History selector */}
<div className={`w-full ${isHistoryOpen ? '' : 'hidden'} ring-2 ring-widget-shadow ring-inset z-10`}>
<SidebarThreadSelector />
<ErrorBoundary>
<SidebarThreadSelector />
</ErrorBoundary>
</div>
<div className='flex-1 flex flex-col overflow-hidden'>
<div className={`flex-1 overflow-hidden ${previousMessages.length === 0 ? 'h-0 max-h-0 pb-2' : ''}`}>
{messagesHTML}
<ErrorBoundary>
{messagesHTML}
</ErrorBoundary>
</div>
{inputForm}
<ErrorBoundary>
{inputForm}
</ErrorBoundary>
</div>
</div>
)

View file

@ -3,13 +3,14 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useAccessor, useIsDark, useSettingsState } from '../util/services.js';
import { Brain, Check, DollarSign, ExternalLink, Lock, X } from 'lucide-react';
import { Brain, Check, ChevronRight, DollarSign, ExternalLink, Lock, X } from 'lucide-react';
import { displayInfoOfProviderName, ProviderName, providerNames, refreshableProviderNames } from '../../../../common/voidSettingsTypes.js';
import { getModelCapabilities, ollamaRecommendedModels } from '../../../../common/modelCapabilities.js';
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
import { AddModelInputBox, AnimatedCheckmarkButton, ollamaSetupInstructions, OneClickSwitchButton, SettingsForProvider } from '../void-settings-tsx/Settings.js';
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js';
const OVERRIDE_VALUE = false
@ -34,6 +35,30 @@ export const VoidOnboarding = () => {
)
}
const VoidIcon = () => {
const accessor = useAccessor()
const themeService = accessor.get('IThemeService')
const divRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
// void icon style
const updateTheme = () => {
const theme = themeService.getColorTheme().type
const isDark = theme === ColorScheme.DARK || theme === ColorScheme.HIGH_CONTRAST_DARK
if (divRef.current) {
divRef.current.style.maxWidth = '220px'
divRef.current.style.opacity = '50%'
divRef.current.style.filter = isDark ? '' : 'invert(1)' //brightness(.5)
}
}
updateTheme()
const d = themeService.onDidColorThemeChange(updateTheme)
return () => d.dispose()
}, [])
return <div ref={divRef} className='@@void-void-icon' />
}
const FADE_DURATION_MS = 2000
@ -109,7 +134,7 @@ const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.Butto
className="px-6 py-2 bg-zinc-100 enabled:hover:bg-zinc-100 disabled:bg-zinc-100/40 disabled:cursor-not-allowed rounded text-black duration-600 transition-all"
{...props.disabled && {
'data-tooltip-id': 'void-tooltip',
'data-tooltip-content': 'Disabled (Please enter all required fields or choose another provider)',
'data-tooltip-content': 'Please enter all required fields or choose another provider',
'data-tooltip-place': 'top',
}}
{...props}
@ -144,7 +169,7 @@ const OnboardingPageShell = ({ top, bottom, content, hasMaxWidth = true, classNa
<div className={`min-h-full text-lg flex flex-col gap-4 w-full mx-auto ${hasMaxWidth ? 'max-w-[600px]' : ''} ${className}`}>
{top && <FadeIn className='w-full mb-auto pt-16'>{top}</FadeIn>}
{content && <FadeIn className='w-full my-auto'>{content}</FadeIn>}
{bottom && <div className='w-full mt-auto pb-8'>{bottom}</div>}
{bottom && <div className='w-full pb-8'>{bottom}</div>}
</div>
)
}
@ -157,7 +182,7 @@ const OllamaDownloadOrRemoveModelButton = ({ modelName, isModelInstalled, sizeGb
href={`https://ollama.com/library/${modelName}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center text-void-fg-2 hover:text-void-fg-1"
className="flex items-center justify-center text-void-fg-2 hover:text-void-fg-1"
>
<ExternalLink className="w-3.5 h-3.5" />
</a>
@ -353,7 +378,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
<td className="py-2 px-3"><YesNoText val={!!true} /></td>
<td className="py-2 px-3"><YesNoText val={!!supportsFIM} /></td>
{/* <td className="py-2 px-3"><YesNoText val={!!reasoningCapabilities} /></td> */}
{isDetectableLocally && <td className="py-2 px-3">{!!isDownloaded ? <Check className="w-4 h-4" /> : <></>}</td>}
{isDetectableLocally && <td className="py-2 px-3 flex items-center justify-center">{!!isDownloaded ? <Check className="w-4 h-4" /> : <></>}</td>}
{providerName === 'ollama' && <th className="py-2 px-3">
<OllamaDownloadOrRemoveModelButton modelName={modelName} isModelInstalled={infoOfModelName[modelName].isDownloaded} sizeGb={downloadable && downloadable.sizeGb} />
</th>}
@ -376,6 +401,57 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
const PrimaryActionButton = ({ children, className, ringSize, ...props }: { children: React.ReactNode, ringSize?: undefined | 'xl' | 'screen' } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
type='button'
className={`
flex items-center justify-center
text-white dark:text-black
bg-black/90 dark:bg-white/90
${ringSize === 'xl' ? `
gap-2 px-16 py-8
hover:ring-8 active:ring-8
transition-all duration-300 ease-in-out
`
: ringSize === 'screen' ? `
gap-2 px-16 py-8
ring-[3000px]
transition-all duration-1000 ease-in-out
`: ringSize === undefined ? `
gap-1 px-4 py-2
hover:ring-2 active:ring-2
transition-all duration-300 ease-in-out
`: ''}
hover:ring-black/90 dark:hover:ring-white/90
active:ring-black/90 dark:active:ring-white/90
rounded-lg
group
${className}
`}
{...props}
>
{children}
<ChevronRight
className={`
transition-all duration-300 ease-in-out
transform
group-hover:translate-x-1
group-active:translate-x-1
`}
/>
</button>
)
}
type WantToUseOption = 'smart' | 'private' | 'cheap' | 'all'
const VoidOnboardingContent = () => {
@ -447,6 +523,19 @@ const VoidOnboardingContent = () => {
</div>
const lastPagePrevAndNextButtons = <div className="max-w-[600px] w-full mx-auto flex flex-col items-end">
<div className="flex items-center gap-2">
<PreviousButton
onClick={() => { setPageIndex(pageIndex - 1) }}
/>
<PrimaryActionButton
onClick={() => { voidSettingsService.setGlobalSetting('isOnboardingComplete', true); }}
ringSize={voidSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined}
>Enter the Void</PrimaryActionButton>
</div>
</div>
// cannot be md
const basicDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
smart: "Models with the best performance on benchmarks.",
@ -459,7 +548,7 @@ const VoidOnboardingContent = () => {
const detailedDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
smart: "Most intelligent and best for agent mode.",
private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor.com) for help setting up at your company.",
cheap: "Great deals like Gemini 2.5 Pro or self-host a model with Ollama or vLLM for free.",
cheap: "Use great deals like Gemini 2.5 Pro, or self-host a model with Ollama or vLLM for free.",
all: "",
}
@ -490,17 +579,26 @@ const VoidOnboardingContent = () => {
const contentOfIdx: { [pageIndex: number]: React.ReactNode } = {
0: <OnboardingPageShell
content={
<>
<div className='flex flex-col items-center gap-8'>
<div className="text-5xl font-light text-center">Welcome to Void</div>
{/* Slice of Void image */}
<div className='max-w-md w-full h-[30vh] mx-auto flex items-center justify-center'>
<VoidIcon />
</div>
<FadeIn
delayMs={1500}
className="text-center"
onClick={() => { setPageIndex(pageIndex + 1) }}
delayMs={1000}
>
Get Started
<PrimaryActionButton
onClick={() => { setPageIndex(pageIndex + 1) }}
>
Get Started
</PrimaryActionButton>
</FadeIn>
</>
</div>
}
/>,
1: <OnboardingPageShell
@ -516,7 +614,7 @@ const VoidOnboardingContent = () => {
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[800px] mx-auto mt-8">
<button
onClick={() => { setWantToUseOption('smart'); setPageIndex(pageIndex + 1); }}
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:border-void-border-1 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:brightness-110 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
>
<div className="flex items-center mb-3">
<Brain size={24} className="text-void-fg-2 mr-2" />
@ -527,7 +625,7 @@ const VoidOnboardingContent = () => {
<button
onClick={() => { setWantToUseOption('private'); setPageIndex(pageIndex + 1); }}
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:border-void-border-1 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:brightness-110 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
>
<div className="flex items-center mb-3">
<Lock size={24} className="text-void-fg-2 mr-2" />
@ -538,7 +636,7 @@ const VoidOnboardingContent = () => {
<button
onClick={() => { setWantToUseOption('cheap'); setPageIndex(pageIndex + 1); }}
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:border-void-border-1 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:brightness-110 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
>
<div className="flex items-center mb-3">
<DollarSign size={24} className="text-void-fg-2 mr-2" />
@ -550,6 +648,11 @@ const VoidOnboardingContent = () => {
</div>}
bottom={
<div className='mx-auto w-full max-w-[800px]'>
<PreviousButton onClick={() => { setPageIndex(pageIndex - 1) }} />
</div>
}
/>,
2: <OnboardingPageShell
top={
@ -665,7 +768,7 @@ const VoidOnboardingContent = () => {
{/* ModelsTable and ProviderFields */}
{selectedProviderName && <div className='mt-4'>
{selectedProviderName && <div className='mt-4 w-fit mx-auto'>
{/* Models Table */}
@ -673,12 +776,13 @@ const VoidOnboardingContent = () => {
{/* Add provider section - simplified styling */}
<div className='mb-5 mt-8'>
<div className='mb-5 mt-8 mx-auto'>
<div className=''>
Add {displayInfoOfProviderName(selectedProviderName).title}
{selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
<div className='my-4'>
{selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
</div>
</div>
@ -733,35 +837,33 @@ const VoidOnboardingContent = () => {
</div>
</div>
}
bottom={prevAndNextButtons}
/>,
4: <OnboardingPageShell
content={
<>
<div className="text-5xl font-light text-center">Jump in</div>
<div
className="text-center"
onClick={() => {
// TODO make a fadeout effect
voidSettingsService.setGlobalSetting('isOnboardingComplete', true)
}}
>
Enter the Void
</div>
</>
}
bottom={
<PreviousButton
onClick={() => { setPageIndex(pageIndex - 1) }}
/>
}
bottom={lastPagePrevAndNextButtons}
// bottom={prevAndNextButtons}
/>,
// 4: <OnboardingPageShell
// content={
// <>
// <div
// className='flex justify-center'
// >
// <PrimaryActionButton
// onClick={() => { voidSettingsService.setGlobalSetting('isOnboardingComplete', true); }}
// ringSize={voidSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined}
// className='text-4xl'
// >Enter the Void</PrimaryActionButton>
// </div>
// </>
// }
// bottom={
// <PreviousButton
// onClick={() => { setPageIndex(pageIndex - 1) }}
// />
// }
// />,
}
return <div key={pageIndex} className="w-full h-full text-left mx-auto overflow-y-auto flex flex-col items-center justify-around">
return <div key={pageIndex} className="w-full h-full text-left mx-auto overflow-y-scroll flex flex-col items-center justify-around">
{contentOfIdx[pageIndex]}
</div>

View file

@ -12,6 +12,7 @@ import { IconWarning } from '../sidebar-tsx/SidebarChat.js'
import { VOID_OPEN_SETTINGS_ACTION_ID, VOID_TOGGLE_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'
import { modelFilterOfFeatureName, ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
import { WarningBox } from './WarningBox.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
if (m1.length !== m2.length) return false
@ -92,5 +93,7 @@ export const ModelDropdown = ({ featureName, className }: { featureName: Feature
: 'Provider required'
} />
return <MemoizedModelDropdown featureName={featureName} className={className} />
return <ErrorBoundary>
<MemoizedModelDropdown featureName={featureName} className={className} />
</ErrorBoundary>
}

View file

@ -572,182 +572,6 @@ const RedoOnboardingButton = ({ className }: { className?: string }) => {
}
export const FeaturesTab = () => {
const voidSettingsState = useSettingsState()
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
return <>
<h2 className={`text-3xl mb-2`}>Models</h2>
<ErrorBoundary>
<ModelDump />
<AddModelInputBox className='mt-4' compact />
<RedoOnboardingButton className='mt-2 mb-4' />
<AutoDetectLocalModelsToggle />
<RefreshableModels />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-12`}>Local Providers</h2>
{/* <h3 className={`opacity-50 mb-2`}>{`Keep your data private by hosting AI locally on your computer.`}</h3> */}
{/* <h3 className={`opacity-50 mb-2`}>{`Instructions:`}</h3> */}
{/* <h3 className={`mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3> */}
<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}
</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>
{/* <h3 className={`opacity-50 mb-2`}>{`Access models like ChatGPT and Claude. We recommend using Anthropic or OpenAI as providers, or Groq as a faster alternative.`}</h3> */}
<ErrorBoundary>
<VoidProviderSettings providerNames={nonlocalProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
<ErrorBoundary>
{/* L1 */}
<div className='flex items-start justify-around mt-4 my-4 gap-x-8'>
{/* FIM */}
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
<span>
Experimental.{' '}
</span>
<span
className='hover:brightness-110'
data-tooltip-id='void-tooltip'
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
data-tooltip-class-name='void-max-w-[20px]'
>
Only works with FIM models.*
</span>
</div>
<div className='my-2'>
{/* Enable Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.enableAutocomplete}
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
</div>
{/* Model Dropdown */}
<div className={`my-2 ${!voidSettingsState.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>
</div>
</div>
{/* Apply */}
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Settings that control the behavior of the Apply button and the Edit tool.</div>
<div className='my-2'>
{/* Sync to Chat Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.syncApplyToChat}
onChange={(newVal) => voidSettingsService.setGlobalSetting('syncApplyToChat', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.syncApplyToChat ? 'Same as Chat model' : 'Different model'}</span>
</div>
{/* Model Dropdown */}
<div className={`my-2 ${voidSettingsState.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>
</div>
{/* L2 */}
<div className='flex items-start justify-around my-4 gap-x-8'>
{/* Tools Section */}
<div className='w-full'>
<h4 className={`text-base`}>Tools</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
<div className='my-2'>
{/* Auto Accept Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.autoApprove}
onChange={(newVal) => voidSettingsService.setGlobalSetting('autoApprove', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.autoApprove ? 'Auto-approve' : 'Auto-approve'}</span>
</div>
{/* Tool Lint Errors Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.includeToolLintErrors}
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Fix lint errors`}</span>
</div>
</div>
</div>
<div className='w-full'>
<h4 className={`text-base`}>Editor</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Settings that control the visibility of suggestions and widgets in the code editor.`}</div>
<div className='my-2'>
{/* Auto Accept Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.showInlineSuggestions}
onChange={(newVal) => voidSettingsService.setGlobalSetting('showInlineSuggestions', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.showInlineSuggestions ? 'Show suggestions on select' : 'Show suggestions on select'}</span>
</div>
</div>
</div>
</div>
<div className='py-8' />
</ErrorBoundary>
</>
}
type TransferEditorType = 'VS Code' | 'Cursor' | 'Windsurf'
// https://github.com/VSCodium/vscodium/blob/master/docs/index.md#migrating-from-visual-studio-code-to-vscodium
// https://code.visualstudio.com/docs/editor/extension-marketplace#_where-are-extensions-installed
@ -954,113 +778,264 @@ export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }:
}
const GeneralTab = () => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const environmentService = accessor.get('IEnvironmentService')
const nativeHostService = accessor.get('INativeHostService')
return <>
<div className=''>
<h2 className={`text-3xl mb-2`}>One-Click Switch</h2>
<h4 className={`text-void-fg-3 mb-4`}>{`Transfer your settings from another editor to Void in one click.`}</h4>
<div className='flex flex-col gap-4'>
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
<OneClickSwitchButton className='w-48' fromEditor="Cursor" />
<OneClickSwitchButton className='w-48' fromEditor="Windsurf" />
</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>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openSettings') }}>
General Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}>
Keyboard Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.selectTheme') }}>
Theme Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
Open Logs
</VoidButtonBgDarken>
</div>
</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`}>{`Instructions to include on all AI requests.`}</h4>
<AIInstructionsBox />
</div>
</>
}
// full settings
export const Settings = () => {
const isDark = useIsDark()
const [tab, setTab] = useState<TabName>('models')
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const environmentService = accessor.get('IEnvironmentService')
const nativeHostService = accessor.get('INativeHostService')
const settingsState = useSettingsState()
const voidSettingsService = accessor.get('IVoidSettingsService')
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'>
<div className='max-w-5xl mx-auto'>
<div className='max-w-xl mx-auto'>
<h1 className='text-2xl w-full'>{`Void's Settings`}</h1>
{/* separator */}
<div className='w-full h-[1px] my-4' />
<div className='flex items-stretch'>
{/* Models section (formerly FeaturesTab) */}
<ErrorBoundary>
<h2 className={`text-3xl mb-2`}>Models</h2>
<ModelDump />
<AddModelInputBox className='mt-4' compact />
<RedoOnboardingButton className='mt-2 mb-4' />
<AutoDetectLocalModelsToggle />
<RefreshableModels />
</ErrorBoundary>
{/* tabs */}
<div className='flex flex-col w-full max-w-32'>
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'models' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('models') }}
>Models</button>
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'general' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('general') }}
>General</button>
<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}
</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>
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
{/* L1 */}
<div className='flex items-start justify-around my-4 gap-x-8'>
<ErrorBoundary>
{/* FIM */}
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
<span>
Experimental.{' '}
</span>
<span
className='hover:brightness-110'
data-tooltip-id='void-tooltip'
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
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 mb-4'>Settings that control the behavior of the Apply button and the Edit tool.</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>
</div>
{/* L2 */}
<div className='flex items-start justify-around my-4 gap-x-8'>
{/* Tools Section */}
<div className='w-full'>
<h4 className={`text-base`}>Tools</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</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.autoApprove}
onChange={(newVal) => voidSettingsService.setGlobalSetting('autoApprove', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.autoApprove ? 'Auto-approve' : 'Auto-approve'}</span>
</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>
{/* separator */}
<div className='w-[1px] mx-4' />
{/* content */}
<div className='w-full min-w-[550px]'>
<div className='w-full'>
<h4 className={`text-base`}>Editor</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Settings that control the visibility of suggestions and widgets in the code editor.`}</div>
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
<FeaturesTab />
<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 className={`${tab !== 'general' ? 'hidden' : ''}`}>
<GeneralTab />
</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 settings from another editor to Void in one click.`}</h4>
<div className='flex flex-col gap-4'>
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
<OneClickSwitchButton className='w-48' fromEditor="Cursor" />
<OneClickSwitchButton className='w-48' fromEditor="Windsurf" />
</div>
</ErrorBoundary>
</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='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openSettings') }}>
General Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}>
Keyboard Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.selectTheme') }}>
Theme Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' 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={`
System instructions to include with all AI requests.
Alternatively, place a \`.voidinstructions\` file in the root of your workspace.
`} chatMessageLocation={undefined} />
</h4>
<ErrorBoundary>
<AIInstructionsBox />
</ErrorBoundary>
</div>
</div>
</div>
</div>
}

View file

@ -102,8 +102,7 @@ export const VoidTooltip = () => {
id="void-tooltip-ollama-settings"
border='1px solid rgba(100,100,100,.2)'
opacity={1}
openOnClick
openEvents={{ mouseover: true }}
openEvents={{ mouseover: true, click: true, focus: true }}
place='right'
style={{ pointerEvents: 'all', userSelect: 'text', fontSize: 11 }}
>

View file

@ -32,6 +32,7 @@ import './media/void.css'
// update (frontend part, also see platform/)
import './voidUpdateActions.js'
import './convertToLLMMessageWorkbenchContrib.js'
// tools
import './toolsService.js'

View file

@ -362,8 +362,6 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar
registerSingleton(IVoidCommandBarService, VoidCommandBarService, InstantiationType.Delayed); // delayed is needed here :(
// registerWorkbenchContribution2(VoidCommandBarService.ID, VoidCommandBarService, WorkbenchPhase.BlockRestore);
export type VoidCommandBarProps = {
uri: URI | null;

View file

@ -783,7 +783,7 @@ const groqSettings: VoidStaticProviderInfo = {
}
const ollamaModelOptions = {
'qwen2.5-coder:3b': {
'qwen2.5-coder:1.5b': {
contextWindow: 32_000,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
@ -792,6 +792,15 @@ const ollamaModelOptions = {
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'llama3.1': {
contextWindow: 128_000,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
downloadable: { sizeGb: 4.9 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'qwen2.5-coder': {
contextWindow: 128_000,
maxOutputTokens: null,
@ -822,7 +831,7 @@ const ollamaModelOptions = {
} as const satisfies Record<string, VoidStaticModelInfo>
export const ollamaRecommendedModels = ['qwen2.5-coder:3b', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[]
export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[]

View file

@ -245,6 +245,17 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
}
// the stored data structure might be outdated, so we need to update it here
readS = {
...readS,
settingsOfProvider: {
...defaultSettingsOfProvider,
...readS.settingsOfProvider,
mistral: { // we added mistral
...defaultSettingsOfProvider.mistral,
...readS.settingsOfProvider.mistral,
},
} // we added mistral
}
this.state = readS
this.state = _stateWithUpdatedDefaultModels(this.state)
this.state = _validatedModelState(this.state);