mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
Merge remote-tracking branch 'origin/react-build-watcher' into recovered-code
Added a watcher that syncs react with watch-client without crashing#
This commit is contained in:
commit
2a519157da
47 changed files with 1759 additions and 1133 deletions
|
|
@ -10,6 +10,7 @@
|
|||
"jsdoc",
|
||||
"header",
|
||||
"local"
|
||||
// "react" // Void
|
||||
],
|
||||
"rules": {
|
||||
"constructor-super": "warn",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ There are a few ways to contribute:
|
|||
- 💡 Make suggestions in our [Discord](https://discord.gg/RSNjgaugJs).
|
||||
- ⭐️ If you want to build your AI tool into Void, feel free to get in touch! It's very easy to extend Void, and the UX you create will be much more natural than a VSCode Extension.
|
||||
|
||||
Most of Void's code lives in `src/vs/workbench/contrib/void/browser/` and `src/vs/platform/void/`.
|
||||
Most of Void's code lives in `src/vs/workbench/contrib/void/browser/` and `src/vs/platform/void/`.
|
||||
|
||||
|
||||
|
||||
|
|
@ -75,6 +75,7 @@ Alternatively, if you want to build Void from the terminal, instead of pressing
|
|||
- Make sure you follow the prerequisite steps.
|
||||
- Make sure you have the same NodeJS version as `.nvmrc`.
|
||||
- If you make any React changes, you must re-run `npm run buildreact` and re-build.
|
||||
- If you get `"TypeError: Failed to fetch dynamically imported module: vscode-file://vscode-app/.../workbench.desktop.main.js", source: file:///.../bootstrap-window.js`, make sure all imports end with `.js`.
|
||||
- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). For building questions, you can also refer to VSCode's full [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
|
||||
|
||||
|
||||
|
|
@ -101,7 +102,7 @@ We don't usually recommend bundling. Instead, you should probably just build. If
|
|||
|
||||
# Guidelines
|
||||
|
||||
We're always glad to talk about new ideas, help you get set up, and make sure your changes align with our vision for the project! Feel free to shoot Mat or Andrew a message, or start chatting with us in the `#contributing` channel of our [Discord](https://discord.gg/RSNjgaugJs).
|
||||
We're always glad to talk about new ideas, help you get set up, and make sure your changes align with our vision for the project! Feel free to shoot Mat or Andrew a message, or start chatting with us in the `#contributing` channel of our [Discord](https://discord.gg/RSNjgaugJs).
|
||||
|
||||
|
||||
## Submitting a Pull Request
|
||||
|
|
|
|||
15
src/vs/platform/void/browser/void.contribution.ts
Normal file
15
src/vs/platform/void/browser/void.contribution.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
|
||||
// ---------- common ----------
|
||||
|
||||
// llmMessage
|
||||
import '../common/llmMessageService.js'
|
||||
|
||||
// voidSettings
|
||||
import '../common/voidSettingsService.js'
|
||||
|
||||
// refreshModel
|
||||
import '../common/refreshModelService.js'
|
||||
|
||||
// metrics
|
||||
import '../common/metricsService.js'
|
||||
|
|
@ -11,7 +11,7 @@ import { generateUuid } from '../../../base/common/uuid.js';
|
|||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { IVoidConfigStateService } from './voidConfigService.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
// import { INotificationService } from '../../notification/common/notification.js';
|
||||
|
||||
// calls channel to implement features
|
||||
|
|
@ -42,7 +42,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
|
||||
constructor(
|
||||
@IMainProcessService private readonly mainProcessService: IMainProcessService, // used as a renderer (only usable on client side)
|
||||
@IVoidConfigStateService private readonly voidConfigStateService: IVoidConfigStateService,
|
||||
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
|
||||
// @INotificationService private readonly notificationService: INotificationService,
|
||||
) {
|
||||
super()
|
||||
|
|
@ -79,7 +79,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
const { featureName } = proxyParams
|
||||
|
||||
// end early if no provider
|
||||
const modelSelection = this.voidConfigStateService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelection = this.voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
if (modelSelection === null) {
|
||||
onError({ message: 'Please add a Provider in Settings!', fullError: null })
|
||||
return null
|
||||
|
|
@ -92,7 +92,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
this.onFinalMessageHooks_llm[requestId_] = onFinalMessage
|
||||
this.onErrorHooks_llm[requestId_] = onError
|
||||
|
||||
const { settingsOfProvider } = this.voidConfigStateService.state
|
||||
const { settingsOfProvider } = this.voidSettingsService.state
|
||||
|
||||
// params will be stripped of all its functions over the IPC channel
|
||||
this.channel.call('sendLLMMessage', {
|
||||
|
|
@ -116,7 +116,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
ollamaList = (params: ServiceOllamaListParams) => {
|
||||
const { onSuccess, onError, ...proxyParams } = params
|
||||
|
||||
const { settingsOfProvider } = this.voidConfigStateService.state
|
||||
const { settingsOfProvider } = this.voidSettingsService.state
|
||||
|
||||
// add state for request id
|
||||
const requestId_ = generateUuid();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IRange } from '../../../editor/common/core/range'
|
||||
import { ProviderName, SettingsOfProvider } from './voidConfigTypes'
|
||||
import { ProviderName, SettingsOfProvider } from './voidSettingsTypes.js'
|
||||
|
||||
|
||||
export type OnText = (p: { newText: string, fullText: string }) => void
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
|
||||
import { IVoidConfigStateService } from './voidConfigService.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
import { ILLMMessageService } from './llmMessageService.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
|
|
@ -38,7 +38,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
|
||||
|
||||
constructor(
|
||||
@IVoidConfigStateService private readonly voidConfigStateService: IVoidConfigStateService,
|
||||
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
|
||||
@ILLMMessageService private readonly llmMessageService: ILLMMessageService,
|
||||
) {
|
||||
super()
|
||||
|
|
@ -46,11 +46,11 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
// on mount, refresh ollama models
|
||||
this.refreshOllamaModels()
|
||||
|
||||
// every time ollama.enabled changes, refresh ollama models
|
||||
let relevantVals = () => [this.voidConfigStateService.state.settingsOfProvider.ollama.enabled, this.voidConfigStateService.state.settingsOfProvider.ollama.endpoint]
|
||||
// every time ollama.enabled changes, refresh ollama models, like useEffect
|
||||
let relevantVals = () => [this.voidSettingsService.state.settingsOfProvider.ollama.enabled, this.voidSettingsService.state.settingsOfProvider.ollama.endpoint]
|
||||
let prevVals = relevantVals()
|
||||
this._register(
|
||||
this.voidConfigStateService.onDidChangeState(() => { // we might want to debounce this
|
||||
this.voidSettingsService.onDidChangeState(() => { // we might want to debounce this
|
||||
const newVals = relevantVals()
|
||||
if (!eq(prevVals, newVals)) {
|
||||
this.refreshOllamaModels()
|
||||
|
|
@ -75,7 +75,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
this._cancelTimeout()
|
||||
|
||||
// if ollama is disabled, obivously done
|
||||
if (this.voidConfigStateService.state.settingsOfProvider.ollama.enabled !== 'true') {
|
||||
if (!this.voidSettingsService.state.settingsOfProvider.ollama.enabled) {
|
||||
this._setState('done')
|
||||
return
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
|
||||
this.llmMessageService.ollamaList({
|
||||
onSuccess: ({ models }) => {
|
||||
this.voidConfigStateService.setSettingOfProvider('ollama', 'models', models.map(model => model.name))
|
||||
this.voidSettingsService.setDefaultModels('ollama', models.map(model => model.name))
|
||||
this._setState('done')
|
||||
},
|
||||
onError: ({ error }) => {
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// llmMessage
|
||||
import './llmMessageService.js'
|
||||
|
||||
// voidConfig
|
||||
import './voidConfigService.js'
|
||||
|
||||
// refreshModel
|
||||
import './refreshModelService.js'
|
||||
|
||||
// metrics
|
||||
import './metricsService.js'
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
|
||||
|
||||
export const defaultAnthropicModels = [
|
||||
'claude-3-5-sonnet-20240620',
|
||||
// 'claude-3-opus-20240229',
|
||||
// 'claude-3-sonnet-20240229',
|
||||
// 'claude-3-haiku-20240307'
|
||||
]
|
||||
|
||||
|
||||
export const defaultOpenAIModels = [
|
||||
'o1-preview',
|
||||
'o1-mini',
|
||||
'gpt-4o',
|
||||
'gpt-4o-mini',
|
||||
// 'gpt-4o-2024-05-13',
|
||||
// 'gpt-4o-2024-08-06',
|
||||
// 'gpt-4o-mini-2024-07-18',
|
||||
// 'gpt-4-turbo',
|
||||
// 'gpt-4-turbo-2024-04-09',
|
||||
// 'gpt-4-turbo-preview',
|
||||
// 'gpt-4-0125-preview',
|
||||
// 'gpt-4-1106-preview',
|
||||
// 'gpt-4',
|
||||
// 'gpt-4-0613',
|
||||
// 'gpt-3.5-turbo-0125',
|
||||
// 'gpt-3.5-turbo',
|
||||
// 'gpt-3.5-turbo-1106',
|
||||
]
|
||||
|
||||
|
||||
|
||||
export const defaultGroqModels = [
|
||||
"mixtral-8x7b-32768",
|
||||
"llama2-70b-4096",
|
||||
"gemma-7b-it"
|
||||
]
|
||||
|
||||
|
||||
export const defaultGeminiModels = [
|
||||
'gemini-1.5-flash',
|
||||
'gemini-1.5-pro',
|
||||
'gemini-1.5-flash-8b',
|
||||
'gemini-1.0-pro'
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const dummyModelData = {
|
||||
anthropic: ['claude 3.5'],
|
||||
openAI: ['gpt 4o'],
|
||||
ollama: ['llama 3.2', 'codestral'],
|
||||
openRouter: ['qwen 2.5'],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { deepClone } from '../../../base/common/objects.js';
|
||||
import { IEncryptionService } from '../../encryption/common/encryptionService.js';
|
||||
import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
|
||||
import { defaultVoidProviderState, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName } from './voidConfigTypes.js';
|
||||
|
||||
|
||||
const STORAGE_KEY = 'void.voidConfigStateII'
|
||||
|
||||
type SetSettingOfProviderFn = <S extends SettingName>(
|
||||
providerName: ProviderName,
|
||||
settingName: S,
|
||||
newVal: SettingsOfProvider[ProviderName][S extends keyof SettingsOfProvider[ProviderName] ? S : never],
|
||||
) => Promise<void>;
|
||||
|
||||
type SetModelSelectionOfFeature = <K extends FeatureName>(
|
||||
featureName: K,
|
||||
newVal: ModelSelectionOfFeature[K],
|
||||
) => Promise<void>;
|
||||
|
||||
|
||||
type VoidConfigState = {
|
||||
readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider
|
||||
readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature
|
||||
}
|
||||
|
||||
export interface IVoidConfigStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly state: VoidConfigState;
|
||||
onDidChangeState: Event<void>;
|
||||
setSettingOfProvider: SetSettingOfProviderFn;
|
||||
setModelSelectionOfFeature: SetModelSelectionOfFeature;
|
||||
}
|
||||
|
||||
|
||||
const defaultState = () => {
|
||||
const d: VoidConfigState = {
|
||||
settingsOfProvider: deepClone(defaultVoidProviderState),
|
||||
modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null }
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
|
||||
export const IVoidConfigStateService = createDecorator<IVoidConfigStateService>('VoidConfigStateService');
|
||||
class VoidConfigService extends Disposable implements IVoidConfigStateService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
|
||||
|
||||
state: VoidConfigState;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IEncryptionService private readonly _encryptionService: IEncryptionService,
|
||||
// could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
|
||||
// @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// at the start, we haven't read the partial config yet, but we need to set state to something
|
||||
this.state = defaultState()
|
||||
|
||||
// read and update the actual state immediately
|
||||
this._readVoidConfigState().then(voidConfigState => {
|
||||
this._setState(voidConfigState)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private async _readVoidConfigState(): Promise<VoidConfigState> {
|
||||
const encryptedPartialConfig = this._storageService.get(STORAGE_KEY, StorageScope.APPLICATION)
|
||||
|
||||
if (!encryptedPartialConfig)
|
||||
return defaultState()
|
||||
|
||||
const voidConfigStateStr = await this._encryptionService.decrypt(encryptedPartialConfig)
|
||||
return JSON.parse(voidConfigStateStr)
|
||||
}
|
||||
|
||||
|
||||
private async _storeVoidConfigState(voidConfigState: VoidConfigState) {
|
||||
const encryptedVoidConfigStr = await this._encryptionService.encrypt(JSON.stringify(voidConfigState))
|
||||
this._storageService.store(STORAGE_KEY, encryptedVoidConfigStr, StorageScope.APPLICATION, StorageTarget.USER);
|
||||
}
|
||||
|
||||
setSettingOfProvider: SetSettingOfProviderFn = async (providerName, settingName, newVal) => {
|
||||
const newState: VoidConfigState = {
|
||||
...this.state,
|
||||
settingsOfProvider: {
|
||||
...this.state.settingsOfProvider,
|
||||
[providerName]: {
|
||||
...this.state.settingsOfProvider[providerName],
|
||||
[settingName]: newVal,
|
||||
}
|
||||
},
|
||||
}
|
||||
// console.log('NEW STATE I', JSON.stringify(newState, null, 2))
|
||||
|
||||
await this._storeVoidConfigState(newState)
|
||||
this._setState(newState)
|
||||
}
|
||||
|
||||
setModelSelectionOfFeature: SetModelSelectionOfFeature = async (featureName, newVal) => {
|
||||
const newState: VoidConfigState = {
|
||||
...this.state,
|
||||
modelSelectionOfFeature: {
|
||||
...this.state.modelSelectionOfFeature,
|
||||
[featureName]: newVal
|
||||
}
|
||||
}
|
||||
// console.log('NEW STATE II', JSON.stringify(newState, null, 2))
|
||||
|
||||
await this._storeVoidConfigState(newState)
|
||||
this._setState(newState)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// internal function to update state, should be called every time state changes
|
||||
private async _setState(voidConfigState: VoidConfigState) {
|
||||
this.state = voidConfigState
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidConfigStateService, VoidConfigService, InstantiationType.Eager);
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { defaultAnthropicModels, defaultGeminiModels, defaultGroqModels, defaultOpenAIModels } from './voidConfigModelDefaults.js'
|
||||
|
||||
|
||||
|
||||
export const voidProviderDefaults = {
|
||||
anthropic: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAI: {
|
||||
apiKey: '',
|
||||
},
|
||||
ollama: {
|
||||
endpoint: 'http://127.0.0.1:11434',
|
||||
},
|
||||
openRouter: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAICompatible: {
|
||||
apiKey: '',
|
||||
endpoint: '',
|
||||
},
|
||||
gemini: {
|
||||
apiKey: '',
|
||||
},
|
||||
groq: {
|
||||
apiKey: ''
|
||||
}
|
||||
} as const
|
||||
|
||||
|
||||
export const voidInitModelOptions = {
|
||||
anthropic: {
|
||||
models: defaultAnthropicModels,
|
||||
},
|
||||
openAI: {
|
||||
models: defaultOpenAIModels,
|
||||
},
|
||||
ollama: {
|
||||
models: [],
|
||||
},
|
||||
openRouter: {
|
||||
models: [], // any string
|
||||
},
|
||||
openAICompatible: {
|
||||
models: [],
|
||||
},
|
||||
gemini: {
|
||||
models: defaultGeminiModels,
|
||||
},
|
||||
groq: {
|
||||
models: defaultGroqModels,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ProviderName = keyof typeof voidProviderDefaults
|
||||
export const providerNames = Object.keys(voidProviderDefaults) as ProviderName[]
|
||||
|
||||
|
||||
|
||||
// state
|
||||
export type SettingsOfProvider = {
|
||||
[providerName in ProviderName]: (
|
||||
{
|
||||
[optionName in keyof typeof voidProviderDefaults[providerName]]: string
|
||||
}
|
||||
&
|
||||
{
|
||||
enabled: string, // 'true' | 'false'
|
||||
maxTokens: string,
|
||||
|
||||
models: string[], // if null, user can type in any string as a model
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
type UnionOfKeys<T> = T extends T ? keyof T : never;
|
||||
|
||||
export type SettingName = UnionOfKeys<SettingsOfProvider[ProviderName]>
|
||||
|
||||
|
||||
|
||||
type DisplayInfo = {
|
||||
title: string,
|
||||
type: string,
|
||||
placeholder: string,
|
||||
}
|
||||
|
||||
export const titleOfProviderName = (providerName: ProviderName) => {
|
||||
if (providerName === 'anthropic')
|
||||
return 'Anthropic'
|
||||
else if (providerName === 'openAI')
|
||||
return 'OpenAI'
|
||||
else if (providerName === 'ollama')
|
||||
return 'Ollama'
|
||||
else if (providerName === 'openRouter')
|
||||
return 'OpenRouter'
|
||||
else if (providerName === 'openAICompatible')
|
||||
return 'OpenAI-Compatible'
|
||||
else if (providerName === 'gemini')
|
||||
return 'Gemini'
|
||||
else if (providerName === 'groq')
|
||||
return 'Groq'
|
||||
|
||||
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
|
||||
}
|
||||
|
||||
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => {
|
||||
if (settingName === 'apiKey') {
|
||||
return {
|
||||
title: 'API Key',
|
||||
type: 'string',
|
||||
placeholder: providerName === 'anthropic' ? 'sk-ant-key...' : // sk-ant-api03-key
|
||||
providerName === 'openAI' ? 'sk-proj-key...' :
|
||||
providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key
|
||||
providerName === 'gemini' ? 'key...' :
|
||||
providerName === 'groq' ? 'gsk_key...' :
|
||||
providerName === 'openAICompatible' ? 'sk-key...' :
|
||||
'(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'endpoint') {
|
||||
return {
|
||||
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
|
||||
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
|
||||
: '(never)',
|
||||
type: 'string',
|
||||
placeholder: providerName === 'ollama' ? voidProviderDefaults.ollama.endpoint
|
||||
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
|
||||
: '(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'maxTokens') {
|
||||
return {
|
||||
title: 'Max Tokens',
|
||||
type: 'number',
|
||||
placeholder: '1024',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'enabled') {
|
||||
return {
|
||||
title: 'Enabled?',
|
||||
type: 'boolean',
|
||||
placeholder: '(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'models') {
|
||||
return {
|
||||
title: 'Available Models',
|
||||
type: '(never)',
|
||||
placeholder: '(never)',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`displayInfo: Unknown setting name: "${settingName}"`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// used when waiting and for a type reference
|
||||
export const defaultVoidProviderState: SettingsOfProvider = {
|
||||
anthropic: {
|
||||
...voidProviderDefaults.anthropic,
|
||||
...voidInitModelOptions.anthropic,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
},
|
||||
openAI: {
|
||||
...voidProviderDefaults.openAI,
|
||||
...voidInitModelOptions.openAI,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
},
|
||||
ollama: {
|
||||
...voidProviderDefaults.ollama,
|
||||
...voidInitModelOptions.ollama,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
},
|
||||
openRouter: {
|
||||
...voidProviderDefaults.openRouter,
|
||||
...voidInitModelOptions.openRouter,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
},
|
||||
openAICompatible: {
|
||||
...voidProviderDefaults.openAICompatible,
|
||||
...voidInitModelOptions.openAICompatible,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
},
|
||||
gemini: {
|
||||
...voidProviderDefaults.gemini,
|
||||
...voidInitModelOptions.gemini,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
},
|
||||
groq: {
|
||||
...voidProviderDefaults.groq,
|
||||
...voidInitModelOptions.groq,
|
||||
enabled: 'false',
|
||||
maxTokens: '',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this is a state
|
||||
export type ModelSelectionOfFeature = {
|
||||
'Ctrl+L': {
|
||||
providerName: ProviderName,
|
||||
modelName: string,
|
||||
} | null,
|
||||
'Ctrl+K': {
|
||||
providerName: ProviderName,
|
||||
modelName: string,
|
||||
} | null,
|
||||
'Autocomplete': {
|
||||
providerName: ProviderName,
|
||||
modelName: string,
|
||||
} | null,
|
||||
}
|
||||
export type FeatureName = keyof ModelSelectionOfFeature
|
||||
export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] as const
|
||||
|
||||
238
src/vs/platform/void/common/voidSettingsService.ts
Normal file
238
src/vs/platform/void/common/voidSettingsService.ts
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { deepClone } from '../../../base/common/objects.js';
|
||||
import { IEncryptionService } from '../../encryption/common/encryptionService.js';
|
||||
import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, ModelInfo } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
const STORAGE_KEY = 'void.voidSettingsI'
|
||||
|
||||
type SetSettingOfProviderFn = <S extends SettingName>(
|
||||
providerName: ProviderName,
|
||||
settingName: S,
|
||||
newVal: SettingsOfProvider[ProviderName][S extends keyof SettingsOfProvider[ProviderName] ? S : never],
|
||||
) => Promise<void>;
|
||||
|
||||
type SetModelSelectionOfFeature = <K extends FeatureName>(
|
||||
featureName: K,
|
||||
newVal: ModelSelectionOfFeature[K],
|
||||
options?: { doNotApplyEffects?: true }
|
||||
) => Promise<void>;
|
||||
|
||||
|
||||
|
||||
export type ModelOption = { text: string, value: ModelSelection }
|
||||
|
||||
|
||||
|
||||
export type VoidSettingsState = {
|
||||
readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider
|
||||
readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature
|
||||
|
||||
readonly _modelOptions: ModelOption[] // computed based on the two above items
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface IVoidSettingsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly state: VoidSettingsState;
|
||||
onDidChangeState: Event<void>;
|
||||
setSettingOfProvider: SetSettingOfProviderFn;
|
||||
setModelSelectionOfFeature: SetModelSelectionOfFeature;
|
||||
|
||||
setDefaultModels(providerName: ProviderName, modelNames: string[]): void;
|
||||
toggleModelHidden(providerName: ProviderName, modelName: string): void;
|
||||
addModel(providerName: ProviderName, modelName: string): void;
|
||||
deleteModel(providerName: ProviderName, modelName: string): boolean;
|
||||
}
|
||||
|
||||
|
||||
let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => {
|
||||
let modelOptions: ModelOption[] = []
|
||||
for (const providerName of providerNames) {
|
||||
const providerConfig = settingsOfProvider[providerName]
|
||||
if (!providerConfig.enabled) continue // if disabled, don't display model options
|
||||
for (const { modelName, isHidden } of providerConfig.models) {
|
||||
if (isHidden) continue
|
||||
modelOptions.push({ text: `${modelName} (${providerName})`, value: { providerName, modelName } })
|
||||
}
|
||||
}
|
||||
return modelOptions
|
||||
}
|
||||
|
||||
|
||||
const defaultState = () => {
|
||||
const d: VoidSettingsState = {
|
||||
settingsOfProvider: deepClone(defaultSettingsOfProvider),
|
||||
modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null },
|
||||
_modelOptions: _computeModelOptions(defaultSettingsOfProvider), // computed
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
|
||||
export const IVoidSettingsService = createDecorator<IVoidSettingsService>('VoidSettingsService');
|
||||
class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
|
||||
|
||||
state: VoidSettingsState;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IEncryptionService private readonly _encryptionService: IEncryptionService,
|
||||
// could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
|
||||
// @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// at the start, we haven't read the partial config yet, but we need to set state to something
|
||||
this.state = defaultState()
|
||||
|
||||
// read and update the actual state immediately
|
||||
this._readState().then(s => {
|
||||
this.state = s
|
||||
this._onDidChangeState.fire()
|
||||
})
|
||||
}
|
||||
|
||||
private async _readState(): Promise<VoidSettingsState> {
|
||||
const encryptedState = this._storageService.get(STORAGE_KEY, StorageScope.APPLICATION)
|
||||
|
||||
if (!encryptedState)
|
||||
return defaultState()
|
||||
|
||||
const stateStr = await this._encryptionService.decrypt(encryptedState)
|
||||
return JSON.parse(stateStr)
|
||||
}
|
||||
|
||||
|
||||
private async _storeState() {
|
||||
const state = this.state
|
||||
const encryptedState = await this._encryptionService.encrypt(JSON.stringify(state))
|
||||
this._storageService.store(STORAGE_KEY, encryptedState, StorageScope.APPLICATION, StorageTarget.USER);
|
||||
}
|
||||
|
||||
setSettingOfProvider: SetSettingOfProviderFn = async (providerName, settingName, newVal) => {
|
||||
|
||||
const newModelSelectionOfFeature = this.state.modelSelectionOfFeature
|
||||
|
||||
const newSettingsOfProvider = {
|
||||
...this.state.settingsOfProvider,
|
||||
[providerName]: {
|
||||
...this.state.settingsOfProvider[providerName],
|
||||
[settingName]: newVal,
|
||||
}
|
||||
}
|
||||
|
||||
// if changed models or enabled a provider, recompute models list
|
||||
const modelsListChanged = settingName === 'models' || settingName === 'enabled'
|
||||
const newModelsList = modelsListChanged ? _computeModelOptions(newSettingsOfProvider) : this.state._modelOptions
|
||||
|
||||
const newState: VoidSettingsState = {
|
||||
modelSelectionOfFeature: newModelSelectionOfFeature,
|
||||
settingsOfProvider: newSettingsOfProvider,
|
||||
_modelOptions: newModelsList,
|
||||
}
|
||||
|
||||
// this must go above this.setanythingelse()
|
||||
this.state = newState
|
||||
|
||||
// if the user-selected model is no longer in the list, update the selection for each feature that needs it to something relevant (the 0th model available, or null)
|
||||
if (modelsListChanged) {
|
||||
for (const featureName of featureNames) {
|
||||
|
||||
const currentSelection = newModelSelectionOfFeature[featureName]
|
||||
const selnIdx = currentSelection === null ? -1 : newModelsList.findIndex(m => modelSelectionsEqual(m.value, currentSelection))
|
||||
|
||||
if (selnIdx === -1) {
|
||||
if (newModelsList.length !== 0)
|
||||
this.setModelSelectionOfFeature(featureName, newModelsList[0].value, { doNotApplyEffects: true })
|
||||
else
|
||||
this.setModelSelectionOfFeature(featureName, null, { doNotApplyEffects: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this._storeState()
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
|
||||
setModelSelectionOfFeature: SetModelSelectionOfFeature = async (featureName, newVal, options) => {
|
||||
const newState: VoidSettingsState = {
|
||||
...this.state,
|
||||
modelSelectionOfFeature: {
|
||||
...this.state.modelSelectionOfFeature,
|
||||
[featureName]: newVal
|
||||
}
|
||||
}
|
||||
|
||||
this.state = newState
|
||||
|
||||
if (options?.doNotApplyEffects)
|
||||
return
|
||||
|
||||
await this._storeState()
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
|
||||
|
||||
setDefaultModels(providerName: ProviderName, newDefaultModelNames: string[]) {
|
||||
const { models } = this.state.settingsOfProvider[providerName]
|
||||
const newDefaultModels = modelInfoOfDefaultNames(newDefaultModelNames)
|
||||
const newModels = [
|
||||
...newDefaultModels,
|
||||
...models.filter(m => !m.isDefault), // keep any non-default models
|
||||
]
|
||||
this.setSettingOfProvider(providerName, 'models', newModels)
|
||||
}
|
||||
toggleModelHidden(providerName: ProviderName, modelName: string) {
|
||||
const { models } = this.state.settingsOfProvider[providerName]
|
||||
const modelIdx = models.findIndex(m => m.modelName === modelName)
|
||||
if (modelIdx === -1) return
|
||||
const newModels: ModelInfo[] = [
|
||||
...models.slice(0, modelIdx),
|
||||
{ ...models[modelIdx], isHidden: !models[modelIdx].isHidden },
|
||||
...models.slice(modelIdx + 1, Infinity)
|
||||
]
|
||||
this.setSettingOfProvider(providerName, 'models', newModels)
|
||||
}
|
||||
addModel(providerName: ProviderName, modelName: string) {
|
||||
const { models } = this.state.settingsOfProvider[providerName]
|
||||
const existingIdx = models.findIndex(m => m.modelName === modelName)
|
||||
if (existingIdx !== -1) return // if exists, do nothing
|
||||
const newModels = [
|
||||
...models,
|
||||
{ modelName, isDefault: false, isHidden: false }
|
||||
]
|
||||
this.setSettingOfProvider(providerName, 'models', newModels)
|
||||
}
|
||||
deleteModel(providerName: ProviderName, modelName: string): boolean {
|
||||
const { models } = this.state.settingsOfProvider[providerName]
|
||||
const delIdx = models.findIndex(m => m.modelName === modelName)
|
||||
if (delIdx === -1) return false
|
||||
const newModels = [
|
||||
...models.slice(0, delIdx), // delete the idx
|
||||
...models.slice(delIdx + 1, Infinity)
|
||||
]
|
||||
this.setSettingOfProvider(providerName, 'models', newModels)
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
registerSingleton(IVoidSettingsService, VoidSettingsService, InstantiationType.Eager);
|
||||
308
src/vs/platform/void/common/voidSettingsTypes.ts
Normal file
308
src/vs/platform/void/common/voidSettingsTypes.ts
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
|
||||
export type ModelInfo = {
|
||||
modelName: string,
|
||||
isDefault: boolean, // whether or not it's a default for its provider
|
||||
isHidden: boolean, // whether or not the user is hiding it
|
||||
}
|
||||
|
||||
|
||||
export const modelInfoOfDefaultNames = (modelNames: string[]): ModelInfo[] => {
|
||||
const isHidden = modelNames.length >= 10 // hide all models if there are a ton of them, and make user enable them individually
|
||||
return modelNames.map((modelName, i) => ({ modelName, isDefault: true, isHidden }))
|
||||
}
|
||||
|
||||
// https://docs.anthropic.com/en/docs/about-claude/models
|
||||
export const defaultAnthropicModels = modelInfoOfDefaultNames([
|
||||
'claude-3-5-sonnet-20241022',
|
||||
'claude-3-5-haiku-20241022',
|
||||
'claude-3-opus-20240229',
|
||||
'claude-3-sonnet-20240229',
|
||||
// 'claude-3-haiku-20240307',
|
||||
])
|
||||
|
||||
|
||||
// https://platform.openai.com/docs/models/gp
|
||||
export const defaultOpenAIModels = modelInfoOfDefaultNames([
|
||||
'o1-preview',
|
||||
'o1-mini',
|
||||
'gpt-4o',
|
||||
'gpt-4o-mini',
|
||||
// 'gpt-4o-2024-05-13',
|
||||
// 'gpt-4o-2024-08-06',
|
||||
// 'gpt-4o-mini-2024-07-18',
|
||||
// 'gpt-4-turbo',
|
||||
// 'gpt-4-turbo-2024-04-09',
|
||||
// 'gpt-4-turbo-preview',
|
||||
// 'gpt-4-0125-preview',
|
||||
// 'gpt-4-1106-preview',
|
||||
// 'gpt-4',
|
||||
// 'gpt-4-0613',
|
||||
// 'gpt-3.5-turbo-0125',
|
||||
// 'gpt-3.5-turbo',
|
||||
// 'gpt-3.5-turbo-1106',
|
||||
])
|
||||
|
||||
|
||||
|
||||
// https://console.groq.com/docs/models
|
||||
export const defaultGroqModels = modelInfoOfDefaultNames([
|
||||
"mixtral-8x7b-32768",
|
||||
"llama2-70b-4096",
|
||||
"gemma-7b-it"
|
||||
])
|
||||
|
||||
|
||||
export const defaultGeminiModels = modelInfoOfDefaultNames([
|
||||
'gemini-1.5-flash',
|
||||
'gemini-1.5-pro',
|
||||
'gemini-1.5-flash-8b',
|
||||
'gemini-1.0-pro'
|
||||
])
|
||||
|
||||
|
||||
|
||||
// export const parseMaxTokensStr = (maxTokensStr: string) => {
|
||||
// // parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
||||
// const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
||||
// if (Number.isNaN(int))
|
||||
// return undefined
|
||||
// return int
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
export const anthropicMaxPossibleTokens = (modelName: string) => {
|
||||
if (modelName === 'claude-3-5-sonnet-20241022'
|
||||
|| modelName === 'claude-3-5-haiku-20241022')
|
||||
return 8192
|
||||
if (modelName === 'claude-3-opus-20240229'
|
||||
|| modelName === 'claude-3-sonnet-20240229'
|
||||
|| modelName === 'claude-3-haiku-20240307')
|
||||
return 4096
|
||||
return 1024 // return a reasonably small number if they're using a different model
|
||||
}
|
||||
|
||||
|
||||
type UnionOfKeys<T> = T extends T ? keyof T : never;
|
||||
|
||||
|
||||
|
||||
export const customProviderSettingsDefaults = {
|
||||
anthropic: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAI: {
|
||||
apiKey: '',
|
||||
},
|
||||
ollama: {
|
||||
endpoint: 'http://127.0.0.1:11434',
|
||||
},
|
||||
openRouter: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAICompatible: {
|
||||
apiKey: '',
|
||||
endpoint: '',
|
||||
},
|
||||
gemini: {
|
||||
apiKey: '',
|
||||
},
|
||||
groq: {
|
||||
apiKey: ''
|
||||
}
|
||||
} as const
|
||||
|
||||
export type ProviderName = keyof typeof customProviderSettingsDefaults
|
||||
export const providerNames = Object.keys(customProviderSettingsDefaults) as ProviderName[]
|
||||
|
||||
|
||||
type CustomSettingName = UnionOfKeys<typeof customProviderSettingsDefaults[ProviderName]>
|
||||
|
||||
type CustomProviderSettings<providerName extends ProviderName> = {
|
||||
[k in CustomSettingName]: k extends keyof typeof customProviderSettingsDefaults[providerName] ? string : undefined
|
||||
}
|
||||
|
||||
type CommonProviderSettings = {
|
||||
enabled: boolean,
|
||||
models: ModelInfo[], // if null, user can type in any string as a model
|
||||
}
|
||||
|
||||
type SettingsForProvider<providerName extends ProviderName> = CustomProviderSettings<providerName> & CommonProviderSettings
|
||||
|
||||
// part of state
|
||||
export type SettingsOfProvider = {
|
||||
[providerName in ProviderName]: SettingsForProvider<providerName>
|
||||
}
|
||||
|
||||
|
||||
export type SettingName = keyof SettingsForProvider<ProviderName>
|
||||
|
||||
|
||||
|
||||
export const titleOfProviderName = (providerName: ProviderName) => {
|
||||
if (providerName === 'anthropic')
|
||||
return 'Anthropic'
|
||||
else if (providerName === 'openAI')
|
||||
return 'OpenAI'
|
||||
else if (providerName === 'ollama')
|
||||
return 'Ollama'
|
||||
else if (providerName === 'openRouter')
|
||||
return 'OpenRouter'
|
||||
else if (providerName === 'openAICompatible')
|
||||
return 'OpenAI-Compatible'
|
||||
else if (providerName === 'gemini')
|
||||
return 'Gemini'
|
||||
else if (providerName === 'groq')
|
||||
return 'Groq'
|
||||
|
||||
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
|
||||
}
|
||||
|
||||
type DisplayInfo = {
|
||||
title: string,
|
||||
placeholder: string,
|
||||
}
|
||||
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => {
|
||||
if (settingName === 'apiKey') {
|
||||
return {
|
||||
title: 'API Key',
|
||||
placeholder: providerName === 'anthropic' ? 'sk-ant-key...' : // sk-ant-api03-key
|
||||
providerName === 'openAI' ? 'sk-proj-key...' :
|
||||
providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key
|
||||
providerName === 'gemini' ? 'key...' :
|
||||
providerName === 'groq' ? 'gsk_key...' :
|
||||
providerName === 'openAICompatible' ? 'sk-key...' :
|
||||
'(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'endpoint') {
|
||||
return {
|
||||
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
|
||||
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
|
||||
: '(never)',
|
||||
placeholder: providerName === 'ollama' ? customProviderSettingsDefaults.ollama.endpoint
|
||||
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
|
||||
: '(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'enabled') {
|
||||
return {
|
||||
title: '(never)',
|
||||
placeholder: '(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'models') {
|
||||
return {
|
||||
title: '(never)',
|
||||
placeholder: '(never)',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`displayInfo: Unknown setting name: "${settingName}"`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const defaultCustomSettings: Record<CustomSettingName, undefined> = {
|
||||
apiKey: undefined,
|
||||
endpoint: undefined,
|
||||
}
|
||||
|
||||
export const voidInitModelOptions = {
|
||||
anthropic: {
|
||||
models: defaultAnthropicModels,
|
||||
},
|
||||
openAI: {
|
||||
models: defaultOpenAIModels,
|
||||
},
|
||||
ollama: {
|
||||
models: [],
|
||||
},
|
||||
openRouter: {
|
||||
models: [], // any string
|
||||
},
|
||||
openAICompatible: {
|
||||
models: [],
|
||||
},
|
||||
gemini: {
|
||||
models: defaultGeminiModels,
|
||||
},
|
||||
groq: {
|
||||
models: defaultGroqModels,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// used when waiting and for a type reference
|
||||
export const defaultSettingsOfProvider: SettingsOfProvider = {
|
||||
anthropic: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.anthropic,
|
||||
...voidInitModelOptions.anthropic,
|
||||
enabled: false,
|
||||
},
|
||||
openAI: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.openAI,
|
||||
...voidInitModelOptions.openAI,
|
||||
enabled: false,
|
||||
},
|
||||
gemini: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.gemini,
|
||||
...voidInitModelOptions.gemini,
|
||||
enabled: false,
|
||||
},
|
||||
groq: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.groq,
|
||||
...voidInitModelOptions.groq,
|
||||
enabled: false,
|
||||
},
|
||||
ollama: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.ollama,
|
||||
...voidInitModelOptions.ollama,
|
||||
enabled: false,
|
||||
},
|
||||
openRouter: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.openRouter,
|
||||
...voidInitModelOptions.openRouter,
|
||||
enabled: false,
|
||||
},
|
||||
openAICompatible: {
|
||||
...defaultCustomSettings,
|
||||
...customProviderSettingsDefaults.openAICompatible,
|
||||
...voidInitModelOptions.openAICompatible,
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export type ModelSelection = { providerName: ProviderName, modelName: string }
|
||||
|
||||
export const modelSelectionsEqual = (m1: ModelSelection, m2: ModelSelection) => {
|
||||
return m1.modelName === m2.modelName && m1.providerName === m2.providerName
|
||||
}
|
||||
|
||||
// this is a state
|
||||
export type ModelSelectionOfFeature = {
|
||||
'Ctrl+L': ModelSelection | null,
|
||||
'Ctrl+K': ModelSelection | null,
|
||||
'Autocomplete': ModelSelection | null,
|
||||
}
|
||||
export type FeatureName = keyof ModelSelectionOfFeature
|
||||
export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] as const
|
||||
|
||||
|
|
@ -4,9 +4,8 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import { parseMaxTokensStr } from './util.js';
|
||||
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
|
||||
import { displayInfoOfSettingName } from '../../common/voidConfigTypes.js';
|
||||
import { anthropicMaxPossibleTokens } from '../../common/voidSettingsTypes.js';
|
||||
|
||||
// Anthropic
|
||||
type LLMMessageAnthropic = {
|
||||
|
|
@ -17,9 +16,9 @@ export const sendAnthropicMsg: _InternalSendLLMMessageFnType = ({ messages, onTe
|
|||
|
||||
const thisConfig = settingsOfProvider.anthropic
|
||||
|
||||
const maxTokens = parseMaxTokensStr(thisConfig.maxTokens)
|
||||
const maxTokens = anthropicMaxPossibleTokens(modelName)
|
||||
if (maxTokens === undefined) {
|
||||
onError({ message: `Please set a value for ${displayInfoOfSettingName('anthropic', 'maxTokens').title}.`, fullError: null })
|
||||
onError({ message: `Please set a value for Max Tokens.`, fullError: null })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
import Groq from 'groq-sdk';
|
||||
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
|
||||
import { parseMaxTokensStr } from './util.js';
|
||||
|
||||
// Groq
|
||||
export const sendGroqMsg: _InternalSendLLMMessageFnType = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
|
||||
|
|
@ -24,7 +23,7 @@ export const sendGroqMsg: _InternalSendLLMMessageFnType = async ({ messages, onT
|
|||
model: modelName,
|
||||
stream: true,
|
||||
temperature: 0.7,
|
||||
max_tokens: parseMaxTokensStr(thisConfig.maxTokens),
|
||||
// max_tokens: parseMaxTokensStr(thisConfig.maxTokens),
|
||||
})
|
||||
.then(async response => {
|
||||
_setAborter(() => response.controller.abort())
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
import { Ollama } from 'ollama';
|
||||
import { _InternalOllamaListFnType, _InternalSendLLMMessageFnType, ModelResponse } from '../../common/llmMessageTypes.js';
|
||||
import { parseMaxTokensStr } from './util.js';
|
||||
|
||||
export const ollamaList: _InternalOllamaListFnType = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => {
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ export const sendOllamaMsg: _InternalSendLLMMessageFnType = ({ messages, onText,
|
|||
model: modelName,
|
||||
messages: messages,
|
||||
stream: true,
|
||||
options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens
|
||||
// options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens
|
||||
})
|
||||
.then(async stream => {
|
||||
_setAborter(() => stream.abort())
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import OpenAI from 'openai';
|
||||
import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js';
|
||||
import { parseMaxTokensStr } from './util.js';
|
||||
// import { parseMaxTokensStr } from './util.js';
|
||||
|
||||
|
||||
// OpenAI, OpenRouter, OpenAICompatible
|
||||
|
|
@ -20,7 +20,7 @@ export const sendOpenAIMsg: _InternalSendLLMMessageFnType = ({ messages, onText,
|
|||
if (providerName === 'openAI') {
|
||||
const thisConfig = settingsOfProvider.openAI
|
||||
openai = new OpenAI({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true });
|
||||
options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
|
||||
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
|
||||
}
|
||||
else if (providerName === 'openRouter') {
|
||||
const thisConfig = settingsOfProvider.openRouter
|
||||
|
|
@ -31,12 +31,12 @@ export const sendOpenAIMsg: _InternalSendLLMMessageFnType = ({ messages, onText,
|
|||
'X-Title': 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
|
||||
},
|
||||
});
|
||||
options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
|
||||
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
|
||||
}
|
||||
else if (providerName === 'openAICompatible') {
|
||||
const thisConfig = settingsOfProvider.openAICompatible
|
||||
openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true })
|
||||
options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) }
|
||||
options = { model: modelName, messages: messages, stream: true, /*max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)*/ }
|
||||
}
|
||||
else {
|
||||
console.error(`sendOpenAIMsg: invalid providerName: ${providerName}`)
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const parseMaxTokensStr = (maxTokensStr: string) => {
|
||||
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
||||
const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
||||
if (Number.isNaN(int))
|
||||
return undefined
|
||||
return int
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
void-imports/
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { diffLines } from './react/out/util/diffLines.js'
|
||||
import { diffLines } from '../react/out/diff/index.js'
|
||||
|
||||
export type ComputedDiff = {
|
||||
type: 'edit';
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isMacintosh } from '../../../../base/common/platform.js';
|
||||
import { isMacintosh } from '../../../../../base/common/platform.js';
|
||||
|
||||
// import { OperatingSystem, OS } from '../../../../base/common/platform.js';
|
||||
// OS === OperatingSystem.Macintosh
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
||||
import { IModelService } from '../../../../../editor/common/services/model.js';
|
||||
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
|
||||
import { IContextViewService, IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
|
||||
import { IFileService } from '../../../../../platform/files/common/files.js';
|
||||
import { IHoverService } from '../../../../../platform/hover/browser/hover.js';
|
||||
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
|
||||
import { ILLMMessageService } from '../../../../../platform/void/common/llmMessageService.js';
|
||||
import { IRefreshModelService } from '../../../../../platform/void/common/refreshModelService.js';
|
||||
import { IVoidSettingsService } from '../../../../../platform/void/common/voidSettingsService.js';
|
||||
import { IInlineDiffsService } from '../inlineDiffsService.js';
|
||||
import { ISidebarStateService } from '../sidebarStateService.js';
|
||||
import { IThreadHistoryService } from '../threadHistoryService.js';
|
||||
|
||||
export type ReactServicesType = {
|
||||
sidebarStateService: ISidebarStateService;
|
||||
settingsStateService: IVoidSettingsService;
|
||||
threadsStateService: IThreadHistoryService;
|
||||
fileService: IFileService;
|
||||
modelService: IModelService;
|
||||
inlineDiffService: IInlineDiffsService;
|
||||
llmMessageService: ILLMMessageService;
|
||||
clipboardService: IClipboardService;
|
||||
refreshModelService: IRefreshModelService;
|
||||
|
||||
themeService: IThemeService,
|
||||
hoverService: IHoverService,
|
||||
|
||||
contextViewService: IContextViewService;
|
||||
contextMenuService: IContextMenuService;
|
||||
}
|
||||
|
||||
|
||||
export const getReactServices = (accessor: ServicesAccessor): ReactServicesType => {
|
||||
return {
|
||||
settingsStateService: accessor.get(IVoidSettingsService),
|
||||
sidebarStateService: accessor.get(ISidebarStateService),
|
||||
threadsStateService: accessor.get(IThreadHistoryService),
|
||||
fileService: accessor.get(IFileService),
|
||||
modelService: accessor.get(IModelService),
|
||||
inlineDiffService: accessor.get(IInlineDiffsService),
|
||||
llmMessageService: accessor.get(ILLMMessageService),
|
||||
clipboardService: accessor.get(IClipboardService),
|
||||
themeService: accessor.get(IThemeService),
|
||||
hoverService: accessor.get(IHoverService),
|
||||
refreshModelService: accessor.get(IRefreshModelService),
|
||||
contextViewService: accessor.get(IContextViewService),
|
||||
contextMenuService: accessor.get(IContextMenuService),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -11,9 +11,8 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows
|
|||
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
// import { throttle } from '../../../../base/common/decorators.js';
|
||||
// import { IVoidConfigStateService } from './registerConfig.js';
|
||||
import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js';
|
||||
import { ComputedDiff, findDiffs } from './findDiffs.js';
|
||||
import { ComputedDiff, findDiffs } from './helpers/findDiffs.js';
|
||||
import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { registerColor } from '../../../../platform/theme/common/colorUtils.js';
|
||||
|
|
@ -144,7 +143,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
|||
|
||||
constructor(
|
||||
// @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right
|
||||
// @IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService,
|
||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CodeSelection } from '../registerThreads.js';
|
||||
import { CodeSelection } from '../threadHistoryService.js';
|
||||
|
||||
export const stringifySelections = (selections: CodeSelection[]) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FeatureName, featureNames, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js'
|
||||
import { dummyModelData } from '../../../../../../../platform/void/common/voidConfigModelDefaults.js'
|
||||
import { useConfigState, useRefreshModelState, useService } from '../util/services.js'
|
||||
import { VoidSelectBox } from './inputs.js'
|
||||
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
|
||||
|
||||
|
||||
export const ModelSelectionOfFeature = ({ featureName }: { featureName: FeatureName }) => {
|
||||
|
||||
const voidConfigService = useService('configStateService')
|
||||
const voidConfigState = useConfigState()
|
||||
|
||||
const modelOptions: { text: string, value: [string, string] }[] = []
|
||||
|
||||
for (const providerName of providerNames) {
|
||||
const providerConfig = voidConfigState[providerName]
|
||||
if (providerConfig.enabled !== 'true') continue
|
||||
providerConfig.models?.forEach(model => {
|
||||
modelOptions.push({ text: `${model} (${providerName})`, value: [providerName, model] })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const isDummy = modelOptions.length === 0
|
||||
if (isDummy) {
|
||||
for (const [providerName, models] of Object.entries(dummyModelData)) {
|
||||
for (let model of models) {
|
||||
modelOptions.push({ text: `${model} (${providerName})`, value: ['dummy', 'dummy'] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let weChangedText = false
|
||||
|
||||
return <>
|
||||
<h2>{featureName}</h2>
|
||||
{
|
||||
<VoidSelectBox
|
||||
options={modelOptions}
|
||||
onChangeSelection={useCallback((newVal: [string, string]) => {
|
||||
if (isDummy) return // don't set state to the dummy value
|
||||
if (weChangedText) return
|
||||
|
||||
voidConfigService.setModelSelectionOfFeature(featureName, { providerName: newVal[0] as ProviderName, modelName: newVal[1] })
|
||||
}, [voidConfigService, featureName, isDummy])}
|
||||
// we are responsible for setting the initial state here. always sync instance when state changes.
|
||||
onCreateInstance={useCallback((instance: SelectBox) => {
|
||||
const syncInstance = () => {
|
||||
const settingsAtProvider = voidConfigService.state.modelSelectionOfFeature[featureName]
|
||||
const index = modelOptions.findIndex(v => v.value[0] === settingsAtProvider?.providerName && v.value[1] === settingsAtProvider?.modelName)
|
||||
if (index !== -1) {
|
||||
weChangedText = true
|
||||
instance.select(index)
|
||||
weChangedText = false
|
||||
}
|
||||
}
|
||||
syncInstance()
|
||||
const disposable = voidConfigService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
}, [voidConfigService, modelOptions, featureName])}
|
||||
/>}
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
const RefreshModels = () => {
|
||||
const refreshModelState = useRefreshModelState()
|
||||
const refreshModelService = useService('refreshModelService')
|
||||
|
||||
return <>
|
||||
<button onClick={() => refreshModelService.refreshOllamaModels()}>
|
||||
refresh
|
||||
</button>
|
||||
{refreshModelState === 'loading' ? 'loading...' : '✅'}
|
||||
</>
|
||||
}
|
||||
|
||||
export const ModelSelectionSettings = () => {
|
||||
return <>
|
||||
{featureNames.map(featureName => <ModelSelectionOfFeature
|
||||
key={featureName}
|
||||
featureName={featureName}
|
||||
/>)}
|
||||
|
||||
<RefreshModels />
|
||||
</>
|
||||
}
|
||||
|
||||
|
|
@ -8,24 +8,22 @@ import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
|||
// import { SidebarSettings } from './SidebarSettings.js';
|
||||
|
||||
|
||||
import { useSidebarState } from '../util/services.js';
|
||||
import { useIsDark, useSidebarState } from '../util/services.js';
|
||||
// import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||
// import { SidebarChat } from './SidebarChat.js';
|
||||
|
||||
import '../styles.css'
|
||||
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||
import { SidebarChat } from './SidebarChat.js';
|
||||
import { ModelSelectionSettings } from './ModelSelectionSettings.js';
|
||||
import { VoidProviderSettings } from './VoidProviderSettings.js';
|
||||
import ErrorBoundary from './ErrorBoundary.js';
|
||||
|
||||
const Sidebar = () => {
|
||||
export const Sidebar = ({ className }: { className: string }) => {
|
||||
const sidebarState = useSidebarState()
|
||||
const { isHistoryOpen, currentTab: tab } = sidebarState
|
||||
|
||||
// className='@@void-scope'
|
||||
return <div className='@@void-scope'>
|
||||
<div className={`flex flex-col w-full px-2 py-2`}>
|
||||
const isDark = useIsDark()
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ width: '100%', height: '100%' }}>
|
||||
<div className={`flex flex-col px-2 py-2 w-full h-full`}>
|
||||
|
||||
{/* <span onClick={() => {
|
||||
const tabs = ['chat', 'settings', 'threadSelector']
|
||||
|
|
@ -33,27 +31,27 @@ const Sidebar = () => {
|
|||
sidebarStateService.setState({ currentTab: tabs[(index + 1) % tabs.length] as any })
|
||||
}}>clickme {tab}</span> */}
|
||||
|
||||
<div className={`mb-2 ${isHistoryOpen ? '' : 'hidden'}`}>
|
||||
<div className={`mb-2 w-full ${isHistoryOpen ? '' : 'hidden'}`}>
|
||||
<ErrorBoundary>
|
||||
<SidebarThreadSelector />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className={`${tab === 'chat' ? '' : 'hidden'}`}>
|
||||
<div className={`w-full h-full ${tab === 'chat' ? '' : 'hidden'}`}>
|
||||
<ErrorBoundary>
|
||||
<SidebarChat />
|
||||
</ErrorBoundary>
|
||||
|
||||
<ErrorBoundary>
|
||||
{/* <ErrorBoundary>
|
||||
<ModelSelectionSettings />
|
||||
</ErrorBoundary>
|
||||
</ErrorBoundary> */}
|
||||
</div>
|
||||
|
||||
<div className={`${tab === 'settings' ? '' : 'hidden'}`}>
|
||||
{/* <div className={`w-full h-full ${tab === 'settings' ? '' : 'hidden'}`}>
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -61,7 +59,3 @@ const Sidebar = () => {
|
|||
|
||||
}
|
||||
|
||||
|
||||
const mountFn = mountFnGenerator(Sidebar)
|
||||
export default mountFn
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
import React, { FormEvent, Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
import { useConfigState, useService, useSidebarState, useThreadsState } from '../util/services.js';
|
||||
import { useSettingsState, useService, useSidebarState, useThreadsState } from '../util/services.js';
|
||||
import { generateDiffInstructions } from '../../../prompt/systemPrompts.js';
|
||||
import { userInstructionsStr } from '../../../prompt/stringifySelections.js';
|
||||
import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../registerThreads.js';
|
||||
import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../threadHistoryService.js';
|
||||
|
||||
import { BlockCode } from '../markdown/BlockCode.js';
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
|
||||
|
|
@ -19,10 +19,10 @@ import { EndOfLinePreference } from '../../../../../../../editor/common/model.js
|
|||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
import { ErrorDisplay } from './ErrorDisplay.js';
|
||||
import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
|
||||
import { getCmdKey } from '../../../getCmdKey.js'
|
||||
import { getCmdKey } from '../../../helpers/getCmdKey.js'
|
||||
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { VoidInputBox } from './inputs.js';
|
||||
import { ModelSelectionOfFeature } from './ModelSelectionSettings.js';
|
||||
import { VoidInputBox } from '../util/inputs.js';
|
||||
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
|
||||
|
||||
|
||||
const IconX = ({ size, className = '' }: { size: number, className?: string }) => {
|
||||
|
|
@ -58,8 +58,8 @@ const IconArrowUp = ({ size, className = '' }: { size: number, className?: strin
|
|||
>
|
||||
<path
|
||||
fill="black"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.1918 8.90615C15.6381 8.45983 16.3618 8.45983 16.8081 8.90615L21.9509 14.049C22.3972 14.4953 22.3972 15.2189 21.9509 15.6652C21.5046 16.1116 20.781 16.1116 20.3347 15.6652L17.1428 12.4734V22.2857C17.1428 22.9169 16.6311 23.4286 15.9999 23.4286C15.3688 23.4286 14.8571 22.9169 14.8571 22.2857V12.4734L11.6652 15.6652C11.2189 16.1116 10.4953 16.1116 10.049 15.6652C9.60265 15.2189 9.60265 14.4953 10.049 14.049L15.1918 8.90615Z"
|
||||
></path>
|
||||
</svg>
|
||||
|
|
@ -86,6 +86,53 @@ const IconSquare = ({ size, className = '' }: { size: number, className?: string
|
|||
};
|
||||
|
||||
|
||||
const ScrollToBottomContainer = ({ children, className, style }: { children: React.ReactNode, className?: string, style?: React.CSSProperties }) => {
|
||||
const [isAtBottom, setIsAtBottom] = useState(true); // Start at bottom
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (divRef.current) {
|
||||
divRef.current.scrollTop = divRef.current.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
const div = divRef.current;
|
||||
if (!div) return;
|
||||
|
||||
const isBottom = Math.abs(
|
||||
div.scrollHeight - div.clientHeight - div.scrollTop
|
||||
) < 4;
|
||||
|
||||
setIsAtBottom(isBottom);
|
||||
};
|
||||
|
||||
// When children change (new messages added)
|
||||
useEffect(() => {
|
||||
if (isAtBottom) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [children, isAtBottom]); // Dependency on children to detect new messages
|
||||
|
||||
// Initial scroll to bottom
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
// options={{ vertical: ScrollbarVisibility.Auto, horizontal: ScrollbarVisibility.Auto }}
|
||||
ref={divRef}
|
||||
onScroll={onScroll}
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// read files from VSCode
|
||||
const VSReadFile = async (modelService: IModelService, uri: URI): Promise<string | null> => {
|
||||
const model = modelService.getModel(uri)
|
||||
|
|
@ -112,68 +159,66 @@ export const SelectedFiles = (
|
|||
|
||||
return (
|
||||
!!selections && selections.length !== 0 && (
|
||||
<div className='flex flex-wrap gap-4'>
|
||||
{selections.map((selection, i) => (
|
||||
<Fragment key={i}>
|
||||
{/* selected file summary */}
|
||||
<div
|
||||
// className="relative rounded rounded-e-2xl flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default"
|
||||
className={`grid grid-rows-2 gap-1 relative
|
||||
<div
|
||||
className='flex flex-wrap gap-4 p-2 text-left'
|
||||
>
|
||||
{selections.map((selection, i) => {
|
||||
|
||||
const showSelectionText = selection.selectionStr && selectionIsOpened[i]
|
||||
|
||||
return (
|
||||
<div key={i} // container for `selectionSummary` and `selectionText`
|
||||
className={`${showSelectionText ? 'w-full' : ''}`}
|
||||
>
|
||||
{/* selection summary */}
|
||||
<div
|
||||
// className="relative rounded rounded-e-2xl flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default"
|
||||
className={`grid grid-rows-2 gap-1 relative
|
||||
select-none
|
||||
bg-vscode-badge-bg border border-vscode-button-border rounded-md
|
||||
w-fit h-fit min-w-[80px] p-1
|
||||
w-fit h-fit min-w-[81px] p-1
|
||||
`}
|
||||
onClick={() => {
|
||||
setSelectionIsOpened(s => {
|
||||
const newS = [...s]
|
||||
newS[i] = !newS[i]
|
||||
return newS
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
<span className='truncate'>
|
||||
{/* file name */}
|
||||
{getBasename(selection.fileURI.fsPath)}
|
||||
{/* selection range */}
|
||||
{selection.selectionStr !== null ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''}
|
||||
</span>
|
||||
|
||||
{/* type of selection */}
|
||||
<span className='truncate text-opacity-75'>{selection.selectionStr !== null ? 'Selection' : 'File'}</span>
|
||||
|
||||
{/* X button */}
|
||||
{type === 'staging' && // hoveredIdx === i
|
||||
<span className='absolute right-0 top-0 translate-x-[50%] translate-y-[-50%] cursor-pointer bg-white rounded-full border border-vscode-input-border z-1'
|
||||
onClick={() => {
|
||||
if (type !== 'staging') return;
|
||||
setStaging([...selections.slice(0, i), ...selections.slice(i + 1)])
|
||||
}}
|
||||
>
|
||||
<IconX size={16} className="p-[2px] stroke-[3]" />
|
||||
onClick={() => {
|
||||
setSelectionIsOpened(s => {
|
||||
const newS = [...s]
|
||||
newS[i] = !newS[i]
|
||||
return newS
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className='truncate'>
|
||||
{/* file name */}
|
||||
{getBasename(selection.fileURI.fsPath)}
|
||||
{/* selection range */}
|
||||
{selection.selectionStr !== null ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''}
|
||||
</span>
|
||||
|
||||
{/* type of selection */}
|
||||
<span className='truncate text-opacity-75'>{selection.selectionStr !== null ? 'Selection' : 'File'}</span>
|
||||
|
||||
{/* X button */}
|
||||
{type === 'staging' && // hoveredIdx === i
|
||||
<span className='absolute right-0 top-0 translate-x-[50%] translate-y-[-50%] cursor-pointer bg-white rounded-full border border-vscode-input-border z-1'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (type !== 'staging') return;
|
||||
setStaging([...selections.slice(0, i), ...selections.slice(i + 1)])
|
||||
setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)])
|
||||
}}
|
||||
>
|
||||
<IconX size={16} className="p-[2px] stroke-[3]" />
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
{/* selection text */}
|
||||
{showSelectionText &&
|
||||
<div className='w-full'>
|
||||
<BlockCode text={selection.selectionStr!} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{/* selection full text */}
|
||||
{selection.selectionStr && selectionIsOpened[i] &&
|
||||
<BlockCode
|
||||
text={selection.selectionStr}
|
||||
// buttonsOnHover={(<button
|
||||
// // onClick={() => { // clear the selection string but keep the file
|
||||
// // setStaging([...selections.slice(0, i), { ...selection, selectionStr: null }, ...selections.slice(i + 1, Infinity)])
|
||||
// // }}
|
||||
// onClick={() => {
|
||||
// if (type !== 'staging') return
|
||||
// setStaging([...selections.slice(0, i), ...selections.slice(i + 1, Infinity)])
|
||||
// }}
|
||||
// className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
// >Remove</button>
|
||||
// )}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
|
|
@ -202,7 +247,7 @@ const ChatBubble = ({ chatMessage }: {
|
|||
}
|
||||
|
||||
return <div className={`${role === 'user' ? 'text-right' : 'text-left'}`}>
|
||||
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full`}>
|
||||
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full overflow-auto`}>
|
||||
{chatbubbleContents}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -228,9 +273,6 @@ export const SidebarChat = () => {
|
|||
return () => disposables.forEach(d => d.dispose())
|
||||
}, [sidebarStateService, inputBoxRef])
|
||||
|
||||
// config state
|
||||
const voidConfigState = useConfigState()
|
||||
|
||||
// threads state
|
||||
const threadsState = useThreadsState()
|
||||
const threadsStateService = useService('threadsStateService')
|
||||
|
|
@ -248,9 +290,10 @@ export const SidebarChat = () => {
|
|||
|
||||
// state of current message
|
||||
const [instructions, setInstructions] = useState('') // the user's instructions
|
||||
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
|
||||
const isDisabled = !instructions.trim()
|
||||
const formRef = useRef<HTMLFormElement | null>(null)
|
||||
const [formHeight, setFormHeight] = useState(0) // TODO should use resize observer instead
|
||||
const [sidebarHeight, setSidebarHeight] = useState(0)
|
||||
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
|
||||
|
||||
|
||||
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
|
|
@ -351,39 +394,50 @@ export const SidebarChat = () => {
|
|||
|
||||
}
|
||||
|
||||
|
||||
const currentThread = threadsStateService.getCurrentThread(threadsState)
|
||||
|
||||
const selections = threadsState._currentStagingSelections
|
||||
|
||||
const previousMessages = currentThread?.messages ?? []
|
||||
|
||||
return <>
|
||||
<div className="overflow-x-hidden space-y-4">
|
||||
// const [_test_messages, _set_test_messages] = useState<string[]>([])
|
||||
|
||||
return <div
|
||||
ref={(ref) => { if (ref) { setSidebarHeight(ref.clientHeight); } }}
|
||||
className={`w-full h-full`}
|
||||
>
|
||||
<ScrollToBottomContainer
|
||||
className={`overflow-x-hidden overflow-y-auto`}
|
||||
style={{ maxHeight: sidebarHeight - formHeight - 30 }}
|
||||
>
|
||||
{/* previous messages */}
|
||||
{previousMessages.map((message, i) =>
|
||||
<ChatBubble key={i} chatMessage={message} />
|
||||
)}
|
||||
{previousMessages.map((message, i) => <ChatBubble key={i} chatMessage={message} />)}
|
||||
|
||||
{/* message stream */}
|
||||
<ChatBubble chatMessage={{ role: 'assistant', content: messageStream, displayContent: messageStream || null }} />
|
||||
</div>
|
||||
|
||||
{/* {_test_messages.map((_, i) => <div key={i}>div {i}</div>)}
|
||||
<div>{`totalHeight: ${sidebarHeight - formHeight - 30}`}</div>
|
||||
<div>{`sidebarHeight: ${sidebarHeight}`}</div>
|
||||
<div>{`formHeight: ${formHeight}`}</div>
|
||||
<button type='button' onClick={() => { _set_test_messages(d => [...d, 'asdasdsadasd']) }}>add div</button> */}
|
||||
|
||||
</ScrollToBottomContainer>
|
||||
|
||||
|
||||
{/* input box */}
|
||||
<div // this div is used to position the input box properly
|
||||
className={`right-0 left-0 m-2
|
||||
${previousMessages.length === 0 ? '' : 'absolute bottom-0'}
|
||||
`}
|
||||
className={`right-0 left-0 m-2 z-[999] ${previousMessages.length > 0 ? 'absolute bottom-0' : ''}`}
|
||||
>
|
||||
<form
|
||||
ref={formRef}
|
||||
className={`flex flex-col gap-2 p-2 relative input text-left shrink-0
|
||||
transition-all duration-200
|
||||
rounded-md
|
||||
bg-vscode-input-bg
|
||||
border border-vscode-commandcenter-border hover:border-vscode-commandcenter-active-border
|
||||
`}
|
||||
ref={(ref) => { if (ref) { setFormHeight(ref.clientHeight); } }}
|
||||
className={`
|
||||
flex flex-col gap-2 p-2 relative input text-left shrink-0
|
||||
transition-all duration-200
|
||||
rounded-md
|
||||
bg-vscode-input-bg
|
||||
border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border
|
||||
`}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
onSubmit(e)
|
||||
|
|
@ -393,9 +447,14 @@ export const SidebarChat = () => {
|
|||
console.log('submit!')
|
||||
onSubmit(e)
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (e.currentTarget === e.target) {
|
||||
inputBoxRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* top row */}
|
||||
<div className=''>
|
||||
<>
|
||||
{/* selections */}
|
||||
{(selections && selections.length !== 0) &&
|
||||
<SelectedFiles type='staging' selections={selections} setStaging={threadsStateService.setStaging.bind(threadsStateService)} />
|
||||
|
|
@ -410,10 +469,24 @@ export const SidebarChat = () => {
|
|||
showDismiss={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/* middle row */}
|
||||
<div className=''>
|
||||
<div
|
||||
className={
|
||||
// // overwrite vscode styles (generated with this code):
|
||||
// `bg-transparent outline-none text-vscode-input-fg min-h-[81px] max-h-[500px]`
|
||||
// .split(' ')
|
||||
// .map(style => `@@[&_textarea]:!void-${style}`) // apply styles to ancestor input and textarea elements
|
||||
// .join(' ') +
|
||||
// ` outline-none`
|
||||
// .split(' ')
|
||||
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`) // apply styles to ancestor input and textarea elements
|
||||
// .join(' ');
|
||||
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-min-h-[81px] @@[&_textarea]:!void-max-h-[500px]@@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
|
||||
}
|
||||
>
|
||||
|
||||
{/* text input */}
|
||||
<VoidInputBox
|
||||
placeholder={`${getCmdKey()}+L to select`}
|
||||
|
|
@ -424,41 +497,45 @@ export const SidebarChat = () => {
|
|||
</div>
|
||||
|
||||
{/* bottom row */}
|
||||
<div className='flex flex-row justify-between items-end'>
|
||||
<div
|
||||
className='flex flex-row justify-between items-end gap-1'
|
||||
>
|
||||
{/* submit options */}
|
||||
<div>
|
||||
<ModelSelectionOfFeature featureName='Ctrl+L' />
|
||||
<div className='w-[250px]'>
|
||||
<ModelDropdown featureName='Ctrl+L' />
|
||||
</div>
|
||||
|
||||
{/* submit / stop button */}
|
||||
{isLoading ?
|
||||
// stop button
|
||||
<button
|
||||
className="p-[5px] bg-white rounded-full cursor-pointer"
|
||||
className={`size-[20px] rounded-full bg-white cursor-pointer flex items-center justify-center`}
|
||||
onClick={onAbort}
|
||||
type='button'
|
||||
>
|
||||
<IconSquare size={24} className="stroke-[2]" />
|
||||
<IconSquare size={16} className="stroke-[2]" />
|
||||
</button>
|
||||
:
|
||||
// submit button (up arrow)
|
||||
<button
|
||||
className={`${isDisabled ? 'bg-vscode-disabled-fg cursor-not-allowed' : 'bg-white cursor-pointer'}
|
||||
rounded-full
|
||||
shrink-0 grow-0
|
||||
`}
|
||||
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
|
||||
${isDisabled ?
|
||||
'bg-vscode-disabled-fg' // cursor-not-allowed
|
||||
: 'bg-white' // cursor-pointer
|
||||
}
|
||||
`}
|
||||
disabled={isDisabled}
|
||||
type='submit'
|
||||
>
|
||||
<IconArrowUp size={24} className="stroke-[2]" />
|
||||
<IconArrowUp size={20} className="stroke-[2]" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
</div >
|
||||
</div >
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const SidebarThreadSelector = () => {
|
|||
const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? -1 : 1)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1 overflow-y-auto h-[30vh]">
|
||||
<div className="flex flex-col gap-y-1 max-h-[400px] overflow-y-auto">
|
||||
|
||||
{/* X button at top right */}
|
||||
<div className="text-right">
|
||||
|
|
@ -49,7 +49,7 @@ export const SidebarThreadSelector = () => {
|
|||
</div>
|
||||
|
||||
{/* a list of all the past threads */}
|
||||
<div className='flex flex-col gap-y-1 max-h-80 overflow-y-auto'>
|
||||
<div className='flex flex-col gap-y-1 overflow-y-auto'>
|
||||
{sortedThreadIds.map((threadId) => {
|
||||
if (!allThreads)
|
||||
return <>Error: Threads not found.</>
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { titleOfProviderName, displayInfoOfSettingName, ProviderName, providerNames, featureNames, SettingsOfProvider, SettingName, defaultVoidProviderState } from '../../../../../../../platform/void/common/voidConfigTypes.js'
|
||||
import { VoidInputBox } from './inputs.js'
|
||||
import { useConfigState, useService } from '../util/services.js'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import ErrorBoundary from './ErrorBoundary.js'
|
||||
|
||||
|
||||
const Setting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
|
||||
|
||||
const { title, type, placeholder } = displayInfoOfSettingName(providerName, settingName)
|
||||
const voidConfigService = useService('configStateService')
|
||||
|
||||
|
||||
let weChangedText = false
|
||||
|
||||
return <><ErrorBoundary>
|
||||
<label>{title}</label>
|
||||
<VoidInputBox
|
||||
placeholder={placeholder}
|
||||
onChangeText={useCallback((newVal) => {
|
||||
if (weChangedText) return
|
||||
|
||||
voidConfigService.setSettingOfProvider(providerName, settingName, newVal)
|
||||
// if we just disabeld this provider, we should unselect all models that use it
|
||||
if (settingName === 'enabled' && newVal !== 'true') {
|
||||
for (let featureName of featureNames) {
|
||||
if (voidConfigService.state.modelSelectionOfFeature[featureName]?.providerName === providerName)
|
||||
voidConfigService.setModelSelectionOfFeature(featureName, null)
|
||||
}
|
||||
}
|
||||
}, [voidConfigService, providerName, settingName])}
|
||||
|
||||
// we are responsible for setting the initial value. always sync the instance whenever there's a change to state.
|
||||
onCreateInstance={useCallback((instance: InputBox) => {
|
||||
const syncInstance = () => {
|
||||
const settingsAtProvider = voidConfigService.state.settingsOfProvider[providerName];
|
||||
const stateVal = settingsAtProvider[settingName as keyof typeof settingsAtProvider]
|
||||
weChangedText = true
|
||||
instance.value = stateVal as string
|
||||
weChangedText = false
|
||||
}
|
||||
syncInstance()
|
||||
const disposable = voidConfigService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
}, [voidConfigService, providerName, settingName])}
|
||||
multiline={false}
|
||||
/>
|
||||
</ErrorBoundary></>
|
||||
|
||||
}
|
||||
|
||||
|
||||
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
||||
const voidConfigState = useConfigState()
|
||||
const { models, ...others } = voidConfigState[providerName]
|
||||
|
||||
return <>
|
||||
<h1 className='text-xl'>{titleOfProviderName(providerName)}</h1>
|
||||
{/* settings besides models (e.g. api key) */}
|
||||
{Object.keys(others).map((sName, i) => {
|
||||
const settingName = sName as keyof typeof others
|
||||
return <Setting key={settingName} providerName={providerName} settingName={settingName} />
|
||||
})}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
export const VoidProviderSettings = () => {
|
||||
|
||||
return <>
|
||||
{providerNames.map(providerName =>
|
||||
<SettingsForProvider key={providerName} providerName={providerName} />
|
||||
)}
|
||||
|
||||
|
||||
</>
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
import { Sidebar } from './Sidebar.js'
|
||||
|
||||
export const mountSidebar = mountFnGenerator(Sidebar)
|
||||
|
||||
|
||||
|
|
@ -5,19 +5,23 @@
|
|||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useService } from '../util/services.js';
|
||||
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { defaultCheckboxStyles, defaultInputBoxStyles, defaultSelectBoxStyles, defaultToggleStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
|
||||
import { SelectBox, unthemedSelectBoxStyles } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
|
||||
import { IInputBoxStyles, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
|
||||
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
|
||||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
import { DomScrollableElement } from '../../../../../../../base/browser/ui/scrollbar/scrollableElement.js';
|
||||
import { ScrollableElementCreationOptions } from '../../../../../../../base/browser/ui/scrollbar/scrollableElementOptions.js';
|
||||
|
||||
|
||||
|
||||
export const WidgetComponent = <CtorParams extends any[], Instance>({ ctor, propsFn, dispose, onCreateInstance }
|
||||
export const WidgetComponent = <CtorParams extends any[], Instance>({ ctor, propsFn, dispose, onCreateInstance, children, className }
|
||||
: {
|
||||
ctor: { new(...params: CtorParams): Instance },
|
||||
propsFn: (container: HTMLDivElement) => CtorParams,
|
||||
onCreateInstance: (instance: Instance) => IDisposable[],
|
||||
dispose: (instance: Instance) => void,
|
||||
children?: React.ReactNode,
|
||||
className?: string
|
||||
}
|
||||
) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -31,20 +35,20 @@ export const WidgetComponent = <CtorParams extends any[], Instance>({ ctor, prop
|
|||
}
|
||||
}, [ctor, propsFn, dispose, onCreateInstance, containerRef])
|
||||
|
||||
return <div ref={containerRef} className='w-full' />
|
||||
return <div ref={containerRef} className={className === undefined ? `w-full` : className}>{children}</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, multiline }: {
|
||||
export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, multiline, styles }: {
|
||||
onChangeText: (value: string) => void;
|
||||
styles?: Partial<IInputBoxStyles>,
|
||||
onCreateInstance?: (instance: InputBox) => void | IDisposable[];
|
||||
inputBoxRef?: { current: InputBox | null };
|
||||
placeholder: string;
|
||||
multiline: boolean;
|
||||
}) => {
|
||||
|
||||
|
||||
const contextViewProvider = useService('contextViewService');
|
||||
|
||||
return <WidgetComponent
|
||||
|
|
@ -55,7 +59,9 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
|
|||
{
|
||||
inputBoxStyles: {
|
||||
...defaultInputBoxStyles,
|
||||
inputBackground: 'transparent',
|
||||
// inputBackground: 'transparent',
|
||||
// inputBorder: 'none',
|
||||
...styles,
|
||||
},
|
||||
placeholder,
|
||||
tooltip: '',
|
||||
|
|
@ -100,6 +106,7 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
|
|||
let containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
return <WidgetComponent
|
||||
className='text-ellipsis whitespace-nowrap pr-6'
|
||||
ctor={SelectBox}
|
||||
propsFn={useCallback((container) => {
|
||||
containerRef.current = container
|
||||
|
|
@ -125,7 +132,7 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
|
|||
instance.render(containerRef.current)
|
||||
|
||||
disposables.push(
|
||||
instance.onDidSelect(e => { onChangeSelection(options[e.index].value ); })
|
||||
instance.onDidSelect(e => { onChangeSelection(options[e.index].value); })
|
||||
)
|
||||
|
||||
if (onCreateInstance) {
|
||||
|
|
@ -142,6 +149,32 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
|
|||
};
|
||||
|
||||
|
||||
// export const VoidScrollableElt = ({ options, children }: { options: ScrollableElementCreationOptions, children: React.ReactNode }) => {
|
||||
// const instanceRef = useRef<DomScrollableElement | null>(null);
|
||||
// const [childrenPortal, setChildrenPortal] = useState<React.ReactNode | null>(null)
|
||||
|
||||
// return <>
|
||||
// <WidgetComponent
|
||||
// ctor={DomScrollableElement}
|
||||
// propsFn={useCallback((container) => {
|
||||
// return [container, options] as const;
|
||||
// }, [options])}
|
||||
// onCreateInstance={useCallback((instance: DomScrollableElement) => {
|
||||
// instanceRef.current = instance;
|
||||
// setChildrenPortal(createPortal(children, instance.getDomNode()))
|
||||
// return []
|
||||
// }, [setChildrenPortal, children])}
|
||||
// dispose={useCallback((instance: DomScrollableElement) => {
|
||||
// console.log('calling dispose!!!!')
|
||||
// // instance.dispose();
|
||||
// // instance.getDomNode().remove()
|
||||
// }, [])}
|
||||
// >{children}</WidgetComponent>
|
||||
|
||||
// {childrenPortal}
|
||||
|
||||
// </>
|
||||
// }
|
||||
|
||||
// export const VoidSelectBox = <T,>({ onChangeSelection, initVal, selectBoxRef, options }: {
|
||||
// initVal: T;
|
||||
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import * as ReactDOM from 'react-dom/client'
|
||||
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';
|
||||
import { _registerServices } from './services.js';
|
||||
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js';
|
||||
|
||||
|
||||
export const mountFnGenerator = (Component: React.FC) => (rootElement: HTMLElement, services: ReactServicesType) => {
|
||||
export const mountFnGenerator = (Component: (params: any) => React.ReactNode) => (rootElement: HTMLElement, services: ReactServicesType) => {
|
||||
if (typeof document === 'undefined') {
|
||||
console.error('index.tsx error: document was undefined')
|
||||
return
|
||||
|
|
@ -17,8 +17,9 @@ export const mountFnGenerator = (Component: React.FC) => (rootElement: HTMLEleme
|
|||
|
||||
const disposables = _registerServices(services)
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
root.render(<Component />);
|
||||
root.render(<Component />); // tailwind dark theme indicator
|
||||
|
||||
return disposables
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,43 +4,53 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { VoidSidebarState, ReactServicesType } from '../../../registerSidebar.js'
|
||||
import { ThreadsState } from '../../../registerThreads.js'
|
||||
import { SettingsOfProvider } from '../../../../../../../platform/void/common/voidConfigTypes.js'
|
||||
import { ThreadsState } from '../../../threadHistoryService.js'
|
||||
import { SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { RefreshModelState } from '../../../../../../../platform/void/common/refreshModelService.js'
|
||||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
|
||||
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js'
|
||||
import { VoidSidebarState } from '../../../sidebarStateService.js'
|
||||
import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js'
|
||||
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
|
||||
|
||||
|
||||
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
|
||||
|
||||
let services: ReactServicesType
|
||||
|
||||
// even if React hasn't mounted yet, these variables are always updated to the latest state:
|
||||
// even if React hasn't mounted yet, the variables are always updated to the latest state.
|
||||
// React listens by adding a setState function to these listeners.
|
||||
let sidebarState: VoidSidebarState
|
||||
let threadsState: ThreadsState
|
||||
let settingsOfProvider: SettingsOfProvider
|
||||
let refreshModelState: RefreshModelState
|
||||
|
||||
// React listens by adding a setState function to these:
|
||||
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
|
||||
|
||||
let threadsState: ThreadsState
|
||||
const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set()
|
||||
const settingsOfProviderListeners: Set<(s: SettingsOfProvider) => void> = new Set()
|
||||
|
||||
let settingsState: VoidSettingsState
|
||||
const settingsStateListeners: Set<(s: VoidSettingsState) => void> = new Set()
|
||||
|
||||
let refreshModelState: RefreshModelState
|
||||
const refreshModelStateListeners: Set<(s: RefreshModelState) => void> = new Set()
|
||||
|
||||
let colorThemeState: ColorScheme
|
||||
const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
|
||||
|
||||
// must call this before you can use any of the hooks below
|
||||
// this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it!
|
||||
|
||||
let wasCalled = false
|
||||
|
||||
export const _registerServices = (services_: ReactServicesType) => {
|
||||
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
if (wasCalled) console.error(`⚠️ Void _registerServices was called again! It should only be called once.`)
|
||||
// don't register services twice
|
||||
if (wasCalled) {
|
||||
return
|
||||
// console.error(`⚠️ Void _registerServices was called again! It should only be called once.`)
|
||||
}
|
||||
wasCalled = true
|
||||
|
||||
services = services_
|
||||
const { sidebarStateService, configStateService, threadsStateService, refreshModelService } = services
|
||||
const { sidebarStateService, settingsStateService, threadsStateService, refreshModelService, themeService } = services
|
||||
|
||||
sidebarState = sidebarStateService.state
|
||||
disposables.push(
|
||||
|
|
@ -58,11 +68,11 @@ export const _registerServices = (services_: ReactServicesType) => {
|
|||
})
|
||||
)
|
||||
|
||||
settingsOfProvider = configStateService.state.settingsOfProvider
|
||||
settingsState = settingsStateService.state
|
||||
disposables.push(
|
||||
configStateService.onDidChangeState(() => {
|
||||
settingsOfProvider = configStateService.state.settingsOfProvider
|
||||
settingsOfProviderListeners.forEach(l => l(settingsOfProvider))
|
||||
settingsStateService.onDidChangeState(() => {
|
||||
settingsState = settingsStateService.state
|
||||
settingsStateListeners.forEach(l => l(settingsState))
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -74,6 +84,14 @@ export const _registerServices = (services_: ReactServicesType) => {
|
|||
})
|
||||
)
|
||||
|
||||
colorThemeState = themeService.getColorTheme().type
|
||||
disposables.push(
|
||||
themeService.onDidColorThemeChange(theme => {
|
||||
colorThemeState = theme.type
|
||||
colorThemeStateListeners.forEach(l => l(colorThemeState))
|
||||
})
|
||||
)
|
||||
|
||||
return disposables
|
||||
}
|
||||
|
||||
|
|
@ -98,12 +116,12 @@ export const useSidebarState = () => {
|
|||
return s
|
||||
}
|
||||
|
||||
export const useConfigState = () => {
|
||||
const [s, ss] = useState(settingsOfProvider)
|
||||
export const useSettingsState = () => {
|
||||
const [s, ss] = useState(settingsState)
|
||||
useEffect(() => {
|
||||
ss(settingsOfProvider)
|
||||
settingsOfProviderListeners.add(ss)
|
||||
return () => { settingsOfProviderListeners.delete(ss) }
|
||||
ss(settingsState)
|
||||
settingsStateListeners.add(ss)
|
||||
return () => { settingsStateListeners.delete(ss) }
|
||||
}, [ss])
|
||||
return s
|
||||
}
|
||||
|
|
@ -128,3 +146,21 @@ export const useRefreshModelState = () => {
|
|||
}, [ss])
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const useIsDark = () => {
|
||||
const [s, ss] = useState(colorThemeState)
|
||||
useEffect(() => {
|
||||
ss(colorThemeState)
|
||||
colorThemeStateListeners.add(ss)
|
||||
return () => { colorThemeStateListeners.delete(ss) }
|
||||
}, [ss])
|
||||
|
||||
// s is the theme, return isDark instead of s
|
||||
const isDark = s === ColorScheme.DARK || s === ColorScheme.HIGH_CONTRAST_DARK
|
||||
return isDark
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FeatureName, featureNames, ModelSelection, modelSelectionsEqual, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { useSettingsState, useRefreshModelState, useService } from '../util/services.js'
|
||||
import { VoidSelectBox } from '../util/inputs.js'
|
||||
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
|
||||
|
||||
|
||||
const ModelSelectBox = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const voidSettingsService = useService('settingsStateService')
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
let weChangedText = false
|
||||
|
||||
return <VoidSelectBox
|
||||
options={settingsState._modelOptions}
|
||||
onChangeSelection={useCallback((newVal: ModelSelection) => {
|
||||
if (weChangedText) return
|
||||
voidSettingsService.setModelSelectionOfFeature(featureName, newVal)
|
||||
}, [voidSettingsService, featureName])}
|
||||
// we are responsible for setting the initial state here. always sync instance when state changes.
|
||||
onCreateInstance={useCallback((instance: SelectBox) => {
|
||||
const syncInstance = () => {
|
||||
const modelsListRef = voidSettingsService.state._modelOptions // as a ref
|
||||
const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
|
||||
weChangedText = true
|
||||
instance.select(selectionIdx === -1 ? 0 : selectionIdx)
|
||||
weChangedText = false
|
||||
}
|
||||
syncInstance()
|
||||
const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
}, [voidSettingsService, featureName])}
|
||||
/>
|
||||
}
|
||||
|
||||
const DummySelectBox = () => {
|
||||
return <VoidSelectBox
|
||||
options={[{ text: 'Please add a model!', value: null }]}
|
||||
onChangeSelection={() => { }}
|
||||
/>
|
||||
}
|
||||
|
||||
export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const settingsState = useSettingsState()
|
||||
return <>
|
||||
{settingsState._modelOptions.length === 0 ? <DummySelectBox /> : <ModelSelectBox featureName={featureName} />}
|
||||
</>
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, titleOfProviderName, providerNames, ModelInfo } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidInputBox, VoidSelectBox } from '../util/inputs.js'
|
||||
import { useIsDark, useRefreshModelState, useService, useSettingsState } from '../util/services.js'
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
|
||||
|
||||
// models
|
||||
|
||||
const RefreshableModels = () => {
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
const refreshModelState = useRefreshModelState()
|
||||
const refreshModelService = useService('refreshModelService')
|
||||
|
||||
if (!settingsState.settingsOfProvider.ollama.enabled)
|
||||
return null
|
||||
|
||||
return <div>
|
||||
<button onClick={() => refreshModelService.refreshOllamaModels()}>refresh Ollama built-in models</button>
|
||||
{refreshModelState === 'loading' ? 'loading...' : 'good!'}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
||||
const settingsStateService = useService('settingsStateService')
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
const providerNameRef = useRef<ProviderName | null>(null)
|
||||
const modelNameRef = useRef<string | null>(null)
|
||||
|
||||
const [errorString, setErrorString] = useState('')
|
||||
|
||||
const providerOptions = useMemo(() => providerNames.map(providerName => ({ text: titleOfProviderName(providerName), value: providerName })), [providerNames])
|
||||
|
||||
return <>
|
||||
<div className='flex justify-center items-center gap-4'>
|
||||
{/* model */}
|
||||
<div className='max-w-40 w-full'>
|
||||
<VoidInputBox
|
||||
placeholder='Model Name'
|
||||
onChangeText={useCallback((modelName) => { modelNameRef.current = modelName }, [])}
|
||||
multiline={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* provider */}
|
||||
<div className='max-w-40 w-full'>
|
||||
<VoidSelectBox
|
||||
onCreateInstance={useCallback(() => { providerNameRef.current = providerOptions[0].value }, [providerOptions])} // initialize state
|
||||
onChangeSelection={useCallback((providerName: ProviderName) => { providerNameRef.current = providerName }, [])}
|
||||
options={providerOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* button */}
|
||||
<div className='max-w-40 w-full'>
|
||||
<button
|
||||
onClick={() => {
|
||||
const providerName = providerNameRef.current
|
||||
const modelName = modelNameRef.current
|
||||
|
||||
if (providerName === null) {
|
||||
setErrorString('Please select a provider.')
|
||||
return
|
||||
}
|
||||
if (!modelName) {
|
||||
setErrorString('Please enter a model name.')
|
||||
return
|
||||
}
|
||||
// if model already exists here
|
||||
if (settingsState.settingsOfProvider[providerName].models.find(m => m.modelName === modelName)) {
|
||||
setErrorString(`This model already exists under ${providerName}.`)
|
||||
return
|
||||
}
|
||||
|
||||
settingsStateService.addModel(providerName, modelName)
|
||||
onSubmit()
|
||||
|
||||
}}>Add model</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!errorString ? null : <div className='text-center text-red-500'>
|
||||
{errorString}
|
||||
</div>}
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
const AddModelButton = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return <>
|
||||
{open ?
|
||||
<AddModelMenu onSubmit={() => { setOpen(false) }} />
|
||||
: <button onClick={() => setOpen(true)}>Add Model</button>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
export const ModelDump = () => {
|
||||
|
||||
const settingsStateService = useService('settingsStateService')
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
// a dump of all the enabled providers' models
|
||||
const modelDump: (ModelInfo & { providerName: ProviderName, providerEnabled: boolean })[] = []
|
||||
for (let providerName of providerNames) {
|
||||
const providerSettings = settingsState.settingsOfProvider[providerName]
|
||||
// if (!providerSettings.enabled) continue
|
||||
modelDump.push(...providerSettings.models.map(model => ({ ...model, providerName, providerEnabled: providerSettings.enabled })))
|
||||
}
|
||||
|
||||
return <div className=''>
|
||||
{modelDump.map(m => {
|
||||
const { isHidden, isDefault, modelName, providerName, providerEnabled } = m
|
||||
|
||||
return <div key={`${modelName}${providerName}`} className='flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-200/10 py-1 px-3 rounded-sm overflow-hidden cursor-default'>
|
||||
{/* left part is width:full */}
|
||||
<div className='w-full flex items-center gap-4'>
|
||||
<span>{`${modelName} (${providerName})`}</span>
|
||||
</div>
|
||||
{/* right part is anything that fits */}
|
||||
<div className='w-fit flex items-center gap-4'>
|
||||
<span className='opacity-50 whitespace-nowrap'>{isDefault ? '' : '(custom model)'}</span>
|
||||
<button disabled={!providerEnabled} onClick={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}>{(!providerEnabled || isHidden) ? '❌' : '✅'}</button>
|
||||
<div className='w-5 flex items-center justify-center'>
|
||||
{isDefault ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName) }}><X className='size-4' /></button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
// providers
|
||||
|
||||
const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
|
||||
|
||||
const { title, placeholder } = displayInfoOfSettingName(providerName, settingName)
|
||||
const voidSettingsService = useService('settingsStateService')
|
||||
|
||||
|
||||
let weChangedTextRef = false
|
||||
|
||||
return <><ErrorBoundary>
|
||||
<label>{title}</label>
|
||||
<VoidInputBox
|
||||
placeholder={placeholder}
|
||||
onChangeText={useCallback((newVal) => {
|
||||
if (weChangedTextRef) return
|
||||
voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
|
||||
}, [voidSettingsService, providerName, settingName])}
|
||||
|
||||
// we are responsible for setting the initial value. always sync the instance whenever there's a change to state.
|
||||
onCreateInstance={useCallback((instance: InputBox) => {
|
||||
const syncInstance = () => {
|
||||
const settingsAtProvider = voidSettingsService.state.settingsOfProvider[providerName];
|
||||
const stateVal = settingsAtProvider[settingName as SettingName]
|
||||
// console.log('SYNCING TO', providerName, settingName, stateVal)
|
||||
weChangedTextRef = true
|
||||
instance.value = stateVal as string
|
||||
weChangedTextRef = false
|
||||
}
|
||||
syncInstance()
|
||||
const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
}, [voidSettingsService, providerName, settingName])}
|
||||
multiline={false}
|
||||
/>
|
||||
</ErrorBoundary></>
|
||||
|
||||
}
|
||||
|
||||
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
||||
const voidSettingsState = useSettingsState()
|
||||
const voidSettingsService = useService('settingsStateService')
|
||||
|
||||
const { models, enabled, ...others } = voidSettingsState.settingsOfProvider[providerName]
|
||||
|
||||
return <>
|
||||
|
||||
<div className='flex items-center gap-4'>
|
||||
<h3 className='text-xl'>{titleOfProviderName(providerName)}</h3>
|
||||
<button onClick={() => { voidSettingsService.setSettingOfProvider(providerName, 'enabled', !enabled) }}>{enabled ? '✅' : '❌'}</button>
|
||||
</div>
|
||||
{/* settings besides models (e.g. api key) */}
|
||||
{Object.keys(others).map((sName, i) => {
|
||||
const settingName = sName as keyof typeof others
|
||||
return <ProviderSetting key={settingName} providerName={providerName} settingName={settingName} />
|
||||
})}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
export const VoidProviderSettings = () => {
|
||||
return <>
|
||||
{providerNames.map(providerName =>
|
||||
<SettingsForProvider key={providerName} providerName={providerName} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
|
||||
// full settings
|
||||
|
||||
export const Settings = () => {
|
||||
const isDark = useIsDark()
|
||||
|
||||
const [tab, setTab] = useState<'models' | 'features'>('models')
|
||||
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
|
||||
<div className='w-full h-full px-10 py-10 select-none'>
|
||||
|
||||
<div className='max-w-5xl mx-auto'>
|
||||
|
||||
<h1 className='text-2xl w-full'>Void Settings</h1>
|
||||
|
||||
{/* separator */}
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
|
||||
<div className='flex items-stretch'>
|
||||
|
||||
{/* tabs */}
|
||||
<div className='flex flex-col w-full max-w-32'>
|
||||
<button className={`text-left p-1 my-0.5 rounded-sm overflow-hidden ${tab === 'models' ? 'bg-vscode-button-hover-bg' : 'bg-vscode-button-active-bg'} hover:bg-vscode-button-hover-bg active:bg-vscode-button-active-bg`}
|
||||
onClick={() => { setTab('models') }}
|
||||
>Models</button>
|
||||
<button className={`text-left p-1 my-0.5 rounded-sm overflow-hidden ${tab === 'features' ? 'bg-vscode-button-hover-bg' : 'bg-vscode-button-active-bg'} hover:bg-vscode-button-hover-bg active:bg-vscode-button-active-bg`}
|
||||
onClick={() => { setTab('features') }}
|
||||
>Features</button>
|
||||
</div>
|
||||
|
||||
{/* separator */}
|
||||
<div className='w-[1px] mx-4' />
|
||||
|
||||
|
||||
{/* content */}
|
||||
<div className='w-full overflow-y-auto'>
|
||||
|
||||
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
|
||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<ErrorBoundary>
|
||||
<ModelDump />
|
||||
<AddModelButton />
|
||||
<RefreshableModels />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<VoidProviderSettings />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className={`${tab !== 'features' ? 'hidden' : ''}`}>
|
||||
<h2 className={`text-3xl mb-2`} onClick={() => { setTab('features') }}>Features</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
import { Settings } from './Settings.js'
|
||||
|
||||
export const mountVoidSettings = mountFnGenerator(Settings)
|
||||
|
||||
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'selector', // '{prefix-}dark' className is used to identify `dark:`
|
||||
content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file
|
||||
theme: {
|
||||
extend: {
|
||||
|
|
@ -38,31 +39,31 @@ module.exports = {
|
|||
"input-bg": "var(--vscode-input-background)",
|
||||
"input-border": "var(--vscode-input-border)",
|
||||
"input-fg": "var(--vscode-input-foreground)",
|
||||
"input-placeholder-fg": "input-var(--vscode-placeholderForeground)",
|
||||
"input-active-bg": "inputOption-var(--vscode-activeBackground)",
|
||||
"input-option-active-border": "inputOption-var(--vscode-activeBorder)",
|
||||
"input-option-active-fg": "inputOption-var(--vscode-activeForeground)",
|
||||
"input-option-hover-bg": "inputOption-var(--vscode-hoverBackground)",
|
||||
"input-validation-error-bg": "inputValidation-var(--vscode-errorBackground)",
|
||||
"input-validation-error-fg": "inputValidation-var(--vscode-errorForeground)",
|
||||
"input-validation-error-border": "inputValidation-var(--vscode-errorBorder)",
|
||||
"input-validation-info-bg": "inputValidation-var(--vscode-infoBackground)",
|
||||
"input-validation-info-fg": "inputValidation-var(--vscode-infoForeground)",
|
||||
"input-validation-info-border": "inputValidation-var(--vscode-infoBorder)",
|
||||
"input-validation-warning-bg": "inputValidation-var(--vscode-warningBackground)",
|
||||
"input-validation-warning-fg": "inputValidation-var(--vscode-warningForeground)",
|
||||
"input-validation-warning-border": "inputValidation-var(--vscode-warningBorder)",
|
||||
"input-placeholder-fg": "var(--vscode-placeholderForeground)",
|
||||
"input-active-bg": "var(--vscode-activeBackground)",
|
||||
"input-option-active-border": "var(--vscode-activeBorder)",
|
||||
"input-option-active-fg": "var(--vscode-activeForeground)",
|
||||
"input-option-hover-bg": "var(--vscode-hoverBackground)",
|
||||
"input-validation-error-bg": "var(--vscode-errorBackground)",
|
||||
"input-validation-error-fg": "var(--vscode-errorForeground)",
|
||||
"input-validation-error-border": "var(--vscode-errorBorder)",
|
||||
"input-validation-info-bg": "var(--vscode-infoBackground)",
|
||||
"input-validation-info-fg": "var(--vscode-infoForeground)",
|
||||
"input-validation-info-border": "var(--vscode-infoBorder)",
|
||||
"input-validation-warning-bg": "var(--vscode-warningBackground)",
|
||||
"input-validation-warning-fg": "var(--vscode-warningForeground)",
|
||||
"input-validation-warning-border": "var(--vscode-warningBorder)",
|
||||
|
||||
// command center colors (the top bar)
|
||||
"commandcenter-fg": "commandCenter.foreground",
|
||||
"commandcenter-active-fg": "commandCenter.activeForeground",
|
||||
"commandcenter-bg": "commandCenter.background",
|
||||
"commandcenter-active-bg": "commandCenter.activeBackground",
|
||||
"commandcenter-border": "commandCenter.border",
|
||||
"commandcenter-inactive-fg": "commandCenter.inactiveForeground",
|
||||
"commandcenter-inactive-border": "commandCenter.inactiveBorder",
|
||||
"commandcenter-active-border": "commandCenter.activeBorder",
|
||||
"commandcenter-debugging-bg": "commandCenter.debuggingBackground",
|
||||
"commandcenter-fg": "var(--vscode-commandCenter-foreground)",
|
||||
"commandcenter-active-fg": "var(--vscode-commandCenter-activeForeground)",
|
||||
"commandcenter-bg": "var(--vscode-commandCenter-background)",
|
||||
"commandcenter-active-bg": "var(--vscode-commandCenter-activeBackground)",
|
||||
"commandcenter-border": "var(--vscode-commandCenter-border)",
|
||||
"commandcenter-inactive-fg": "var(--vscode-commandCenter-inactiveForeground)",
|
||||
"commandcenter-inactive-border": "var(--vscode-commandCenter-inactiveBorder)",
|
||||
"commandcenter-active-border": "var(--vscode-commandCenter-activeBorder)",
|
||||
"commandcenter-debugging-bg": "var(--vscode-commandCenter-debuggingBackground)",
|
||||
|
||||
// badge colors
|
||||
"badge-fg": "var(--vscode-badge-foreground)",
|
||||
|
|
@ -84,7 +85,6 @@ module.exports = {
|
|||
"checkbox-border": "var(--vscode-checkbox-border)",
|
||||
"checkbox-select-bg": "var(--vscode-checkbox-selectBackground)",
|
||||
|
||||
|
||||
// sidebar colors
|
||||
"sidebar-bg": "var(--vscode-sideBar-background)",
|
||||
"sidebar-fg": "var(--vscode-sideBar-foreground)",
|
||||
|
|
@ -101,7 +101,6 @@ module.exports = {
|
|||
"sidebar-stickyscroll-border": "var(--vscode-sideBarStickyScroll-border)",
|
||||
"sidebar-stickyscroll-shadow": "var(--vscode-sideBarStickyScroll-shadow)",
|
||||
|
||||
|
||||
// other colors (these are partially complete)
|
||||
|
||||
// editor colors
|
||||
|
|
@ -113,7 +112,6 @@ module.exports = {
|
|||
"editorwidget-bg": "var(--vscode-editorWidget-background)",
|
||||
"editorwidget-border": "var(--vscode-editorWidget-border)",
|
||||
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import { defineConfig } from 'tsup'
|
|||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'./src2/sidebar-tsx/Sidebar.tsx',
|
||||
'./src2/util/diffLines.tsx',
|
||||
'./src2/sidebar-tsx/index.tsx',
|
||||
'./src2/void-settings-tsx/index.tsx',
|
||||
'./src2/diff/index.tsx',
|
||||
],
|
||||
outDir: './out',
|
||||
format: ['esm'],
|
||||
|
|
|
|||
|
|
@ -1,247 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import {
|
||||
Extensions as ViewContainerExtensions, IViewContainersRegistry,
|
||||
ViewContainerLocation, IViewsRegistry, Extensions as ViewExtensions,
|
||||
IViewDescriptorService,
|
||||
} from '../../../common/views.js';
|
||||
|
||||
import * as nls from '../../../../nls.js';
|
||||
|
||||
// import { Codicon } from '../../../../base/common/codicons.js';
|
||||
// import { localize } from '../../../../nls.js';
|
||||
// import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
|
||||
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
|
||||
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
|
||||
|
||||
import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js';
|
||||
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { IThreadHistoryService } from './registerThreads.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
|
||||
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
|
||||
import mountFn from './react/out/sidebar-tsx/Sidebar.js';
|
||||
|
||||
import { IVoidConfigStateService } from '../../../../platform/void/common/voidConfigService.js';
|
||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
import { IInlineDiffsService } from './registerInlineDiffs.js';
|
||||
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||
import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
|
||||
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { IRefreshModelService } from '../../../../platform/void/common/refreshModelService.js';
|
||||
|
||||
|
||||
// compare against search.contribution.ts and debug.contribution.ts, scm.contribution.ts (source control)
|
||||
|
||||
export type VoidSidebarState = {
|
||||
isHistoryOpen: boolean;
|
||||
currentTab: 'chat' | 'settings';
|
||||
}
|
||||
|
||||
export type ReactServicesType = {
|
||||
sidebarStateService: IVoidSidebarStateService;
|
||||
configStateService: IVoidConfigStateService;
|
||||
threadsStateService: IThreadHistoryService;
|
||||
fileService: IFileService;
|
||||
modelService: IModelService;
|
||||
inlineDiffService: IInlineDiffsService;
|
||||
llmMessageService: ILLMMessageService;
|
||||
clipboardService: IClipboardService;
|
||||
refreshModelService: IRefreshModelService;
|
||||
|
||||
themeService: IThemeService,
|
||||
hoverService: IHoverService,
|
||||
|
||||
contextViewService: IContextViewService;
|
||||
contextMenuService: IContextMenuService;
|
||||
}
|
||||
|
||||
// ---------- Define viewpane ----------
|
||||
|
||||
class VoidSidebarViewPane extends ViewPane {
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IHoverService hoverService: IHoverService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override renderBody(parent: HTMLElement): void {
|
||||
super.renderBody(parent);
|
||||
parent.style.overflow = 'auto'
|
||||
parent.style.userSelect = 'text'
|
||||
|
||||
// gets set immediately
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const services: ReactServicesType = {
|
||||
configStateService: accessor.get(IVoidConfigStateService),
|
||||
sidebarStateService: accessor.get(IVoidSidebarStateService),
|
||||
threadsStateService: accessor.get(IThreadHistoryService),
|
||||
fileService: accessor.get(IFileService),
|
||||
modelService: accessor.get(IModelService),
|
||||
inlineDiffService: accessor.get(IInlineDiffsService),
|
||||
llmMessageService: accessor.get(ILLMMessageService),
|
||||
clipboardService: accessor.get(IClipboardService),
|
||||
themeService: accessor.get(IThemeService),
|
||||
hoverService: accessor.get(IHoverService),
|
||||
refreshModelService: accessor.get(IRefreshModelService),
|
||||
contextViewService: accessor.get(IContextViewService),
|
||||
contextMenuService: accessor.get(IContextMenuService),
|
||||
}
|
||||
|
||||
// mount react
|
||||
const disposables: IDisposable[] | undefined = mountFn(parent, services);
|
||||
disposables?.forEach(d => this._register(d))
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------- Register viewpane inside the void container ----------
|
||||
|
||||
// const voidThemeIcon = Codicon.symbolObject;
|
||||
// const voidViewIcon = registerIcon('void-view-icon', voidThemeIcon, localize('voidViewIcon', 'View icon of the Void chat view.'));
|
||||
|
||||
// called VIEWLET_ID in other places for some reason
|
||||
export const VOID_VIEW_CONTAINER_ID = 'workbench.view.void'
|
||||
export const VOID_VIEW_ID = VOID_VIEW_CONTAINER_ID // simplicity
|
||||
|
||||
// Register view container
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
const viewContainer = viewContainerRegistry.registerViewContainer({
|
||||
id: VOID_VIEW_CONTAINER_ID,
|
||||
title: nls.localize2('void', 'Void Chat'), // this is used to say "Void" (Ctrl + L)
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VOID_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]),
|
||||
hideIfEmpty: false,
|
||||
// icon: voidViewIcon,
|
||||
order: 1,
|
||||
}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true, });
|
||||
|
||||
|
||||
|
||||
// Register search default location to the container (sidebar)
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([{
|
||||
id: VOID_VIEW_ID,
|
||||
hideByDefault: false, // start open
|
||||
// containerIcon: voidViewIcon,
|
||||
name: nls.localize2('void chat', "Chat"), // this says ... : CHAT
|
||||
ctorDescriptor: new SyncDescriptor(VoidSidebarViewPane),
|
||||
canToggleVisibility: false,
|
||||
canMoveView: true,
|
||||
openCommandActionDescriptor: {
|
||||
id: viewContainer.id,
|
||||
keybindings: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyL,
|
||||
},
|
||||
order: 1
|
||||
},
|
||||
}], viewContainer);
|
||||
|
||||
|
||||
|
||||
// ---------- Register service that manages sidebar's state ----------
|
||||
|
||||
export interface IVoidSidebarStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly state: VoidSidebarState; // readonly to the user
|
||||
setState(newState: Partial<VoidSidebarState>): void;
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
onDidFocusChat: Event<void>;
|
||||
onDidBlurChat: Event<void>;
|
||||
fireFocusChat(): void;
|
||||
fireBlurChat(): void;
|
||||
|
||||
openSidebarView(): void;
|
||||
}
|
||||
|
||||
export const IVoidSidebarStateService = createDecorator<IVoidSidebarStateService>('voidSidebarStateService');
|
||||
class VoidSidebarStateService extends Disposable implements IVoidSidebarStateService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID = 'voidSidebarStateService';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
private readonly _onFocusChat = new Emitter<void>();
|
||||
readonly onDidFocusChat: Event<void> = this._onFocusChat.event;
|
||||
|
||||
private readonly _onBlurChat = new Emitter<void>();
|
||||
readonly onDidBlurChat: Event<void> = this._onBlurChat.event;
|
||||
|
||||
|
||||
// state
|
||||
state: VoidSidebarState
|
||||
|
||||
constructor(
|
||||
@IViewsService private readonly _viewsService: IViewsService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
this.state = { isHistoryOpen: false, currentTab: 'chat', }
|
||||
}
|
||||
|
||||
|
||||
setState(newState: Partial<VoidSidebarState>) {
|
||||
// make sure view is open if the tab changes
|
||||
if ('currentTab' in newState) {
|
||||
this.openSidebarView()
|
||||
}
|
||||
|
||||
this.state = { ...this.state, ...newState }
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
fireFocusChat() {
|
||||
this._onFocusChat.fire()
|
||||
}
|
||||
|
||||
fireBlurChat() {
|
||||
this._onBlurChat.fire()
|
||||
}
|
||||
|
||||
openSidebarView() {
|
||||
this._viewsService.openViewContainer(VOID_VIEW_CONTAINER_ID);
|
||||
this._viewsService.openView(VOID_VIEW_ID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IVoidSidebarStateService, VoidSidebarStateService, InstantiationType.Eager);
|
||||
|
|
@ -11,16 +11,18 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
|
|||
|
||||
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { CodeStagingSelection, IThreadHistoryService } from './registerThreads.js';
|
||||
import { CodeStagingSelection, IThreadHistoryService } from './threadHistoryService.js';
|
||||
// import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
import { IVoidSidebarStateService, VOID_VIEW_ID } from './registerSidebar.js';
|
||||
import { VOID_VIEW_ID } from './sidebarPane.js';
|
||||
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
|
||||
// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
||||
import { ISidebarStateService } from './sidebarStateService.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { OPEN_VOID_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
|
||||
|
||||
// ---------- Register commands and keybindings ----------
|
||||
|
|
@ -61,7 +63,7 @@ registerAction2(class extends Action2 {
|
|||
if (!model)
|
||||
return
|
||||
|
||||
const stateService = accessor.get(IVoidSidebarStateService)
|
||||
const stateService = accessor.get(ISidebarStateService)
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
|
||||
metricsService.capture('Chat Navigation', { type: 'Ctrl+L' })
|
||||
|
|
@ -73,23 +75,33 @@ registerAction2(class extends Action2 {
|
|||
accessor.get(IEditorService).activeTextEditorControl?.getSelection()
|
||||
)
|
||||
|
||||
// add selection
|
||||
const threadHistoryService = accessor.get(IThreadHistoryService)
|
||||
const currentStaging = threadHistoryService.state._currentStagingSelections
|
||||
const currentStagingEltIdx = currentStaging?.findIndex(s =>
|
||||
s.fileURI.fsPath === model.uri.fsPath
|
||||
&& s.range?.startLineNumber === selectionRange?.startLineNumber
|
||||
&& s.range?.endLineNumber === selectionRange?.endLineNumber
|
||||
)
|
||||
|
||||
if (selectionRange) {
|
||||
const selection: CodeStagingSelection = {
|
||||
selectionStr: getContentInRange(model, selectionRange),
|
||||
|
||||
const selectionStr = getContentInRange(model, selectionRange)
|
||||
|
||||
const selection: CodeStagingSelection = selectionStr === null || selectionRange.startLineNumber > selectionRange.endLineNumber ? {
|
||||
type: 'File',
|
||||
fileURI: model.uri,
|
||||
selectionStr: null,
|
||||
range: null,
|
||||
} : {
|
||||
type: 'Selection',
|
||||
fileURI: model.uri,
|
||||
selectionStr: selectionStr,
|
||||
range: selectionRange,
|
||||
}
|
||||
|
||||
// overwrite selections that match with this one (compares by `fileURI` and line numbers in `range`)
|
||||
// add selection to staging
|
||||
const threadHistoryService = accessor.get(IThreadHistoryService)
|
||||
const currentStaging = threadHistoryService.state._currentStagingSelections
|
||||
const currentStagingEltIdx = currentStaging?.findIndex(s =>
|
||||
s.fileURI.fsPath === model.uri.fsPath
|
||||
&& s.range?.startLineNumber === selection.range?.startLineNumber
|
||||
&& s.range?.endLineNumber === selection.range?.endLineNumber
|
||||
)
|
||||
|
||||
// if matches with existing selection, overwrite
|
||||
if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
|
||||
threadHistoryService.setStaging([
|
||||
...currentStaging!.slice(0, currentStagingEltIdx),
|
||||
|
|
@ -97,6 +109,7 @@ registerAction2(class extends Action2 {
|
|||
...currentStaging!.slice(currentStagingEltIdx + 1, Infinity)
|
||||
])
|
||||
}
|
||||
// if no match, add
|
||||
else {
|
||||
threadHistoryService.setStaging([...(currentStaging ?? []), selection])
|
||||
}
|
||||
|
|
@ -117,7 +130,7 @@ registerAction2(class extends Action2 {
|
|||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const stateService = accessor.get(IVoidSidebarStateService)
|
||||
const stateService = accessor.get(ISidebarStateService)
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
|
||||
metricsService.capture('Chat Navigation', { type: 'New Chat' })
|
||||
|
|
@ -140,7 +153,7 @@ registerAction2(class extends Action2 {
|
|||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const stateService = accessor.get(IVoidSidebarStateService)
|
||||
const stateService = accessor.get(ISidebarStateService)
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
|
||||
metricsService.capture('Chat Navigation', { type: 'History' })
|
||||
|
|
@ -150,23 +163,19 @@ registerAction2(class extends Action2 {
|
|||
}
|
||||
})
|
||||
|
||||
// Settings (API config) menu button
|
||||
|
||||
// Settings gear
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'void.viewSettings',
|
||||
id: 'void.settingsAction',
|
||||
title: 'Void Settings',
|
||||
icon: { id: 'settings-gear' },
|
||||
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const stateService = accessor.get(IVoidSidebarStateService)
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
|
||||
metricsService.capture('Chat Navigation', { type: 'Settings' })
|
||||
|
||||
stateService.setState({ isHistoryOpen: false, currentTab: stateService.state.currentTab === 'settings' ? 'chat' : 'settings' })
|
||||
stateService.fireBlurChat()
|
||||
const commandService = accessor.get(ICommandService)
|
||||
commandService.executeCommand(OPEN_VOID_SETTINGS_ACTION_ID)
|
||||
}
|
||||
})
|
||||
150
src/vs/workbench/contrib/void/browser/sidebarPane.ts
Normal file
150
src/vs/workbench/contrib/void/browser/sidebarPane.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import {
|
||||
Extensions as ViewContainerExtensions, IViewContainersRegistry,
|
||||
ViewContainerLocation, IViewsRegistry, Extensions as ViewExtensions,
|
||||
IViewDescriptorService,
|
||||
} from '../../../common/views.js';
|
||||
|
||||
import * as nls from '../../../../nls.js';
|
||||
|
||||
// import { Codicon } from '../../../../base/common/codicons.js';
|
||||
// import { localize } from '../../../../nls.js';
|
||||
// import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
|
||||
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
|
||||
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
// import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
|
||||
|
||||
import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js';
|
||||
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
|
||||
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
|
||||
import { mountSidebar } from './react/out/sidebar-tsx/index.js';
|
||||
|
||||
import { getReactServices } from './helpers/reactServicesHelper.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Orientation } from '../../../../base/browser/ui/sash/sash.js';
|
||||
// import { Orientation } from '../../../../base/browser/ui/sash/sash.js';
|
||||
// import { Codicon } from '../../../../base/common/codicons.js';
|
||||
// import { Codicon } from '../../../../base/common/codicons.js';
|
||||
|
||||
|
||||
// compare against search.contribution.ts and debug.contribution.ts, scm.contribution.ts (source control)
|
||||
|
||||
// ---------- Define viewpane ----------
|
||||
|
||||
class SidebarViewPane extends ViewPane {
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IHoverService hoverService: IHoverService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override renderBody(parent: HTMLElement): void {
|
||||
super.renderBody(parent);
|
||||
// parent.style.overflow = 'auto'
|
||||
parent.style.userSelect = 'text'
|
||||
|
||||
// gets set immediately
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const services = getReactServices(accessor)
|
||||
|
||||
// mount react
|
||||
const disposables: IDisposable[] | undefined = mountSidebar(parent, services);
|
||||
disposables?.forEach(d => this._register(d))
|
||||
});
|
||||
}
|
||||
|
||||
override layoutBody(height: number, width: number): void {
|
||||
super.layoutBody(height, width)
|
||||
this.element.style.height = `${height}px`
|
||||
this.element.style.width = `${width}px`
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------- Register viewpane inside the void container ----------
|
||||
|
||||
// const voidThemeIcon = Codicon.symbolObject;
|
||||
// const voidViewIcon = registerIcon('void-view-icon', voidThemeIcon, localize('voidViewIcon', 'View icon of the Void chat view.'));
|
||||
|
||||
// called VIEWLET_ID in other places for some reason
|
||||
export const VOID_VIEW_CONTAINER_ID = 'workbench.view.void'
|
||||
export const VOID_VIEW_ID = VOID_VIEW_CONTAINER_ID
|
||||
|
||||
// Register view container
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
const container = viewContainerRegistry.registerViewContainer({
|
||||
id: VOID_VIEW_CONTAINER_ID,
|
||||
title: nls.localize2('voidContainer', 'Void Chat'), // this is used to say "Void" (Ctrl + L)
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VOID_VIEW_CONTAINER_ID, {
|
||||
mergeViewWithContainerWhenSingleView: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
}]),
|
||||
hideIfEmpty: false,
|
||||
order: 1,
|
||||
|
||||
rejectAddedViews: true,
|
||||
icon: Codicon.symbolMethod,
|
||||
|
||||
|
||||
}, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true, isDefault: true });
|
||||
|
||||
|
||||
|
||||
// Register search default location to the container (sidebar)
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([{
|
||||
id: VOID_VIEW_ID,
|
||||
hideByDefault: false, // start open
|
||||
// containerIcon: voidViewIcon,
|
||||
name: nls.localize2('voidChat', ''), // this says ... : CHAT
|
||||
ctorDescriptor: new SyncDescriptor(SidebarViewPane),
|
||||
canToggleVisibility: false,
|
||||
canMoveView: false, // can't move this out of its container
|
||||
weight: 80,
|
||||
order: 1,
|
||||
// singleViewPaneContainerTitle: 'hi',
|
||||
|
||||
// openCommandActionDescriptor: {
|
||||
// id: VOID_VIEW_CONTAINER_ID,
|
||||
// keybindings: {
|
||||
// primary: KeyMod.CtrlCmd | KeyCode.KeyL,
|
||||
// },
|
||||
// order: 1
|
||||
// },
|
||||
}], container);
|
||||
|
||||
84
src/vs/workbench/contrib/void/browser/sidebarStateService.ts
Normal file
84
src/vs/workbench/contrib/void/browser/sidebarStateService.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { VOID_VIEW_CONTAINER_ID, VOID_VIEW_ID } from './sidebarPane.js';
|
||||
|
||||
|
||||
// service that manages sidebar's state
|
||||
export type VoidSidebarState = {
|
||||
isHistoryOpen: boolean;
|
||||
currentTab: 'chat';
|
||||
}
|
||||
|
||||
export interface ISidebarStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly state: VoidSidebarState; // readonly to the user
|
||||
setState(newState: Partial<VoidSidebarState>): void;
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
onDidFocusChat: Event<void>;
|
||||
onDidBlurChat: Event<void>;
|
||||
fireFocusChat(): void;
|
||||
fireBlurChat(): void;
|
||||
|
||||
openSidebarView(): void;
|
||||
}
|
||||
|
||||
export const ISidebarStateService = createDecorator<ISidebarStateService>('voidSidebarStateService');
|
||||
class VoidSidebarStateService extends Disposable implements ISidebarStateService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
static readonly ID = 'voidSidebarStateService';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
||||
|
||||
private readonly _onFocusChat = new Emitter<void>();
|
||||
readonly onDidFocusChat: Event<void> = this._onFocusChat.event;
|
||||
|
||||
private readonly _onBlurChat = new Emitter<void>();
|
||||
readonly onDidBlurChat: Event<void> = this._onBlurChat.event;
|
||||
|
||||
|
||||
// state
|
||||
state: VoidSidebarState
|
||||
|
||||
constructor(
|
||||
@IViewsService private readonly _viewsService: IViewsService,
|
||||
) {
|
||||
super()
|
||||
|
||||
// initial state
|
||||
this.state = { isHistoryOpen: false, currentTab: 'chat', }
|
||||
}
|
||||
|
||||
|
||||
setState(newState: Partial<VoidSidebarState>) {
|
||||
// make sure view is open if the tab changes
|
||||
if ('currentTab' in newState) {
|
||||
this.openSidebarView()
|
||||
}
|
||||
|
||||
this.state = { ...this.state, ...newState }
|
||||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
fireFocusChat() {
|
||||
this._onFocusChat.fire()
|
||||
}
|
||||
|
||||
fireBlurChat() {
|
||||
this._onBlurChat.fire()
|
||||
}
|
||||
|
||||
openSidebarView() {
|
||||
this._viewsService.openViewContainer(VOID_VIEW_CONTAINER_ID);
|
||||
this._viewsService.openView(VOID_VIEW_ID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(ISidebarStateService, VoidSidebarStateService, InstantiationType.Eager);
|
||||
|
|
@ -10,7 +10,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
|||
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { IAutocompleteService } from './registerAutocomplete.js';
|
||||
import { IAutocompleteService } from './autocompleteService.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
|
||||
export type CodeSelection = {
|
||||
|
|
@ -22,9 +22,15 @@ export type CodeSelection = {
|
|||
|
||||
// if selectionStr is null, it means to use the entire file at send time
|
||||
export type CodeStagingSelection = {
|
||||
fileURI: URI;
|
||||
selectionStr: string | null;
|
||||
range: IRange;
|
||||
type: 'Selection',
|
||||
fileURI: URI,
|
||||
selectionStr: string,
|
||||
range: IRange
|
||||
} | {
|
||||
type: 'File',
|
||||
fileURI: URI,
|
||||
selectionStr: null,
|
||||
range: null
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -74,7 +80,7 @@ const newThreadObject = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const THREAD_STORAGE_KEY = 'void.threadsHistory'
|
||||
const THREAD_STORAGE_KEY = 'void.threadHistory'
|
||||
|
||||
export interface IThreadHistoryService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
|
@ -3,20 +3,23 @@
|
|||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// register keybinds
|
||||
import './registerActions.js'
|
||||
|
||||
// register inline diffs
|
||||
import './registerInlineDiffs.js'
|
||||
import './inlineDiffsService.js'
|
||||
|
||||
// register Sidebar chat
|
||||
import './registerSidebar.js'
|
||||
// register Sidebar pane, state, actions (keybinds, menus)
|
||||
import './sidebarActions.js'
|
||||
import './sidebarPane.js'
|
||||
import './sidebarStateService.js'
|
||||
|
||||
// register Thread History
|
||||
import './registerThreads.js'
|
||||
import './threadHistoryService.js'
|
||||
|
||||
// register Autocomplete
|
||||
import './registerAutocomplete.js'
|
||||
import './autocompleteService.js'
|
||||
|
||||
// settings pane
|
||||
import './voidSettingsPane.js'
|
||||
|
||||
// register css
|
||||
import './media/void.css'
|
||||
|
|
|
|||
162
src/vs/workbench/contrib/void/browser/voidSettingsPane.ts
Normal file
162
src/vs/workbench/contrib/void/browser/voidSettingsPane.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { EditorInput } from '../../../common/editor/editorInput.js';
|
||||
import * as nls from '../../../../nls.js';
|
||||
import { EditorExtensions } from '../../../common/editor.js';
|
||||
import { EditorPane } from '../../../browser/parts/editor/editorPane.js';
|
||||
import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
import { IStorageService } from '../../../../platform/storage/common/storage.js';
|
||||
import { Dimension } from '../../../../base/browser/dom.js';
|
||||
import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
|
||||
|
||||
import { mountVoidSettings } from './react/out/void-settings-tsx/index.js'
|
||||
import { getReactServices } from './helpers/reactServicesHelper.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
|
||||
|
||||
|
||||
// refer to preferences.contribution.ts keybindings editor
|
||||
|
||||
class VoidSettingsInput extends EditorInput {
|
||||
|
||||
static readonly ID: string = 'workbench.input.void.settings';
|
||||
|
||||
readonly resource = URI.from({
|
||||
scheme: 'void-editor-settings',
|
||||
path: 'void-settings' // Give it a unique path
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override get typeId(): string {
|
||||
return VoidSettingsInput.ID;
|
||||
}
|
||||
|
||||
override getName(): string {
|
||||
return nls.localize('voidSettingsInputsName', 'Void Settings');
|
||||
}
|
||||
|
||||
override getIcon() {
|
||||
return Codicon.checklist // symbol for the actual editor pane
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class VoidSettingsPane extends EditorPane {
|
||||
static readonly ID = 'workbench.test.myCustomPane';
|
||||
|
||||
private _scrollbar: DomScrollableElement | undefined;
|
||||
|
||||
constructor(
|
||||
group: IEditorGroup,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(VoidSettingsPane.ID, group, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
parent.style.height = '100%';
|
||||
parent.style.width = '100%';
|
||||
|
||||
const scrollableContent = document.createElement('div');
|
||||
scrollableContent.style.height = '100%';
|
||||
scrollableContent.style.width = '100%';
|
||||
|
||||
this._scrollbar = this._register(new DomScrollableElement(scrollableContent, {}));
|
||||
parent.appendChild(this._scrollbar.getDomNode());
|
||||
this._scrollbar.scanDomNode();
|
||||
|
||||
// Mount React into the scrollable content
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const services = getReactServices(accessor);
|
||||
const disposables: IDisposable[] | undefined = mountVoidSettings(scrollableContent, services);
|
||||
|
||||
setTimeout(() => { // this is a complete hack and I don't really understand how scrollbar works here
|
||||
this._scrollbar?.scanDomNode();
|
||||
}, 1000)
|
||||
disposables?.forEach(d => this._register(d));
|
||||
});
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
if (!this._scrollbar) return;
|
||||
|
||||
this._scrollbar.getDomNode().style.height = `${dimension.height}px`;
|
||||
this._scrollbar.getDomNode().style.width = `${dimension.width}px`;
|
||||
this._scrollbar.scanDomNode();
|
||||
|
||||
}
|
||||
|
||||
|
||||
override get minimumWidth() { return 700 }
|
||||
|
||||
}
|
||||
|
||||
// register Settings pane
|
||||
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
|
||||
EditorPaneDescriptor.create(VoidSettingsPane, VoidSettingsPane.ID, nls.localize('VoidSettingsPane', "Void Settings Pane")),
|
||||
[new SyncDescriptor(VoidSettingsInput)]
|
||||
);
|
||||
|
||||
|
||||
export const OPEN_VOID_SETTINGS_ACTION_ID = 'workbench.action.openVoidSettings'
|
||||
// register the gear on the top right
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: OPEN_VOID_SETTINGS_ACTION_ID,
|
||||
title: nls.localize2('voidSettings', "Void: Settings"),
|
||||
f1: true,
|
||||
icon: Codicon.settingsGear,
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.LayoutControlMenuSubmenu,
|
||||
group: 'z_end',
|
||||
},
|
||||
{
|
||||
id: MenuId.LayoutControlMenu,
|
||||
when: ContextKeyExpr.equals('config.workbench.layoutControl.type', 'both'),
|
||||
group: 'z_end'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const input = instantiationService.createInstance(VoidSettingsInput);
|
||||
await editorService.openEditor(input);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// add to settings gear on bottom left
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '0_command',
|
||||
command: {
|
||||
id: OPEN_VOID_SETTINGS_ACTION_ID,
|
||||
title: nls.localize('voidSettings', "Void Settings")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
|
@ -17,7 +17,7 @@ import './browser/workbench.contribution.js';
|
|||
//#region --- Void
|
||||
// Void added this:
|
||||
import './contrib/void/browser/void.contribution.js';
|
||||
import '../platform/void/common/void.contribution.js';
|
||||
import '../platform/void/browser/void.contribution.js';
|
||||
//#endregion
|
||||
|
||||
|
||||
|
|
@ -334,7 +334,7 @@ import './contrib/surveys/browser/nps.contribution.js';
|
|||
import './contrib/surveys/browser/languageSurveys.contribution.js';
|
||||
|
||||
// Welcome
|
||||
import './contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js';
|
||||
// import './contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js'; // Void commented this out (removes Welcome page on start)
|
||||
import './contrib/welcomeWalkthrough/browser/walkThrough.contribution.js';
|
||||
import './contrib/welcomeViews/common/viewsWelcome.contribution.js';
|
||||
import './contrib/welcomeViews/common/newFile.contribution.js';
|
||||
|
|
|
|||
Loading…
Reference in a new issue