add settings UI

This commit is contained in:
Andrew 2024-10-16 19:32:21 -07:00
parent d3b862ae91
commit 154a82ab0b
8 changed files with 95 additions and 27 deletions

View file

@ -47,7 +47,7 @@
"icon": "$(history)"
},
{
"command": "void.openSettings",
"command": "void.toggleSettings",
"title": "Void settings",
"icon": "$(settings-gear)"
}
@ -95,7 +95,7 @@
"group": "navigation"
},
{
"command": "void.openSettings",
"command": "void.toggleSettings",
"when": "view == 'void.viewnumberone'",
"group": "navigation"
}

View file

@ -28,7 +28,7 @@ export class DisplayChangesProvider implements vscode.CodeLensProvider {
// used internally by vscode
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
const docUriStr = document.uri.toString()
return this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses)
return this._diffsOfDocument[docUriStr]?.flatMap(diff => diff.lenses) ?? []
}
// declared by us, registered with vscode.languages.registerCodeLensProvider()

View file

@ -54,10 +54,6 @@ export function activate(context: vscode.ExtensionContext) {
displayChangesProvider.rejectDiff(params)
}));
context.subscriptions.push(vscode.commands.registerCommand('void.openSettings', async () => {
vscode.commands.executeCommand('workbench.action.openSettings', '@ext:void.void');
}));
// 5. Receive messages from sidebar
webviewProvider.webview.then(
webview => {
@ -69,6 +65,9 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('void.toggleThreadSelector', async () => {
webview.postMessage({ type: 'toggleThreadSelector' } satisfies MessageToSidebar)
}))
context.subscriptions.push(vscode.commands.registerCommand('void.toggleSettings', async () => {
webview.postMessage({ type: 'toggleSettings' } satisfies MessageToSidebar)
}));
// Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`)
webview.onDidReceiveMessage(async (m: MessageFromSidebar) => {

View file

@ -46,6 +46,7 @@ type MessageToSidebar = (
| { type: 'allThreads', threads: ChatThreads }
| { type: 'startNewThread' }
| { type: 'toggleThreadSelector' }
| { type: 'toggleSettings' }
)
// sidebar -> editor

View file

@ -25,6 +25,14 @@ const Sidebar = () => {
setTab('threadSelector')
})
// if they toggled settings
useOnVSCodeMessage('toggleSettings', (m) => {
if (tab === 'settings')
setTab('chat')
else
setTab('settings')
})
// Receive messages from the VSCode extension
useEffect(() => {

View file

@ -1,19 +1,70 @@
import React, { useState } from "react";
import { useVoidConfig } from "./contextForConfig";
import { configFields, useVoidConfig, VoidConfigField } from "./contextForConfig";
const SettingOfFieldAndParam = ({ field, param }: { field: VoidConfigField, param: string }) => {
const { voidConfig, partialVoidConfig, voidConfigInfo, setConfigParam } = useVoidConfig()
const [val, setVal] = useState<string | undefined>(partialVoidConfig[field]?.[param])
const { enumArr } = voidConfigInfo[field][param]
const updateState = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => { setVal(e.target.value); };
const commitConfigParam = () => { if (val) setConfigParam(field, param, val); };
// string
if (enumArr === undefined) {
return (
<div>
<label>{param}</label>
<input
type="text"
value={val}
onChange={updateState}
onBlur={commitConfigParam}
/>
</div>
)
}
// enum
else {
return (
<div>
<label>{param}</label>
<select
value={val}
onChange={updateState}
onBlur={commitConfigParam}
>
{enumArr.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)
}
}
export const SidebarSettings = () => {
const { voidConfig, setConfigParam } = useVoidConfig()
const { voidConfig, voidConfigInfo } = useVoidConfig()
const current_field = voidConfig.default['whichApi'] as VoidConfigField
const params = Object.keys(voidConfigInfo[current_field])
// only show the settings relevant to the current field
// voidConfig.default.whichApi
return null
return (
<div>
{params.map((param) => (
<SettingOfFieldAndParam
key={param}
field={current_field}
param={param}
/>
))}
</div>
)
}

View file

@ -17,8 +17,8 @@ const configString = (description: string, defaultVal: string) => {
}
}
const configFields = [
// fields you can customize (don't forget 'default' - it isn't included here!)
export const configFields = [
'anthropic',
'openAI',
'greptile',
@ -253,17 +253,18 @@ const voidConfigInfo: Record<
// this is the type that comes with metadata like desc, default val, etc
type VoidConfigInfo = typeof voidConfigInfo
export type VoidConfigField = keyof typeof voidConfigInfo // typeof configFields[number]
// this is the type that specifies the user's actual config
export type PartialVoidConfig = {
[K in keyof typeof voidConfigInfo]?: {
[P in keyof typeof voidConfigInfo[K]]?: string
[P in keyof typeof voidConfigInfo[K]]?: typeof voidConfigInfo[K][P]['defaultVal']
}
}
export type VoidConfig = {
[K in keyof typeof voidConfigInfo]: {
[P in keyof typeof voidConfigInfo[K]]: string
[P in keyof typeof voidConfigInfo[K]]: typeof voidConfigInfo[K][P]['defaultVal']
}
}
@ -271,7 +272,7 @@ export type VoidConfig = {
const getVoidConfig = (currentConfig: PartialVoidConfig): VoidConfig => {
const config = {} as PartialVoidConfig
for (let field of configFields) {
for (let field of [...configFields, 'default'] as const) {
config[field] = {}
for (let prop in voidConfigInfo[field]) {
config[field][prop] = currentConfig[field]?.[prop] || voidConfigInfo[field][prop].defaultVal
@ -296,16 +297,20 @@ const useInstantState = <T,>(initVal: T) => {
type SetConfigParamType = <K extends VoidConfigField>(field: K, param: keyof VoidConfigInfo[K], newVal: string) => void
type ConfigValueType = {
voidConfig: VoidConfig,
setConfigParam: <K extends typeof configFields[number]>(field: K, param: keyof VoidConfigInfo[K], newVal: string) => void
voidConfigInfo: VoidConfigInfo,
partialVoidConfig: PartialVoidConfig,
setConfigParam: SetConfigParamType
}
const ConfigContext = createContext<ConfigValueType>(undefined as unknown as ConfigValueType)
export function ConfigProvider({ children }: { children: ReactNode }) {
const [partialVoidConfig, setPartialVoidConfig] = useInstantState<PartialVoidConfig>({}) // only used internally here, and to communicate with the extension
const [partialVoidConfig, setPartialVoidConfig] = useInstantState<PartialVoidConfig>({}) // the user's selections
const [voidConfig, setVoidConfig] = useState<VoidConfig>(defaultVoidConfig)
@ -323,6 +328,8 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
return (<ConfigContext.Provider
value={{
voidConfig,
voidConfigInfo,
partialVoidConfig: partialVoidConfig.current ?? {},
setConfigParam: (field, param, newVal) => {
const newPartialConfig: PartialVoidConfig = {
...partialVoidConfig.current,

View file

@ -12,7 +12,8 @@ const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
"partialVoidConfig": [],
"startNewThread": [],
"allThreads": [],
"toggleThreadSelector": []
"toggleThreadSelector": [],
"toggleSettings": [],
}
// messageType -> id -> res
@ -22,7 +23,8 @@ const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = {
"partialVoidConfig": {},
"startNewThread": {},
"allThreads": {},
"toggleThreadSelector": {}
"toggleThreadSelector": {},
"toggleSettings": {},
}