diff --git a/src/extension/webview-api/controllers/settings-controller.ts b/src/extension/webview-api/controllers/settings-controller.ts index ebddce6..fcbc410 100644 --- a/src/extension/webview-api/controllers/settings-controller.ts +++ b/src/extension/webview-api/controllers/settings-controller.ts @@ -1,4 +1,9 @@ -import type { SettingKey, SettingValue } from '@shared/entities' +import { settingKeyItemConfigMap } from '@shared/entities' +import type { + SettingKey, + SettingsSaveType, + SettingValue +} from '@shared/entities' import { globalSettingsDB, workspaceSettingsDB } from '../lowdb/settings-db' import { Controller } from '../types' @@ -58,6 +63,33 @@ export class SettingsController extends Controller { } } + private async getSaveType(key: SettingKey): Promise { + return settingKeyItemConfigMap[key].saveType + } + + async getSetting(req: { + key: SettingKey + }): Promise | null> { + const saveType = await this.getSaveType(req.key) + return saveType === 'global' + ? await globalSettingsDB.getSetting(req.key) + : await workspaceSettingsDB.getSetting(req.key) + } + + async setSettings(req: { + settings: Partial>> + }): Promise { + const { settings } = req + for (const [key, value] of Object.entries(settings)) { + const saveType = await this.getSaveType(key as SettingKey) + if (saveType === 'global') { + await globalSettingsDB.setSetting(key as SettingKey, value) + } else { + await workspaceSettingsDB.setSetting(key as SettingKey, value) + } + } + } + async getMergedSettings(): Promise> { const globalSettings = await globalSettingsDB.getAllSettings() const workspaceSettings = await workspaceSettingsDB.getAllSettings() diff --git a/src/extension/webview-api/lowdb/settings-db.ts b/src/extension/webview-api/lowdb/settings-db.ts index 7cbf8cd..ea14eaa 100644 --- a/src/extension/webview-api/lowdb/settings-db.ts +++ b/src/extension/webview-api/lowdb/settings-db.ts @@ -1,13 +1,13 @@ import path from 'path' import { aidePaths } from '@extension/file-utils/paths' import { + settingKeyItemConfigMap, settingsConfig, SettingsEntity, - type SettingCategory, type SettingKey, type Settings, type SettingValue -} from '@shared/entities/settings-entity' +} from '@shared/entities' import { BaseDB } from './base-db' @@ -28,7 +28,6 @@ class SettingsDB extends BaseDB { ): Promise { const existingSettings = await this.getAll() const existing = existingSettings.find(s => s.key === key) - const config = settingsConfig[key] if (existing) { return this.update(existing.id, { @@ -40,7 +39,6 @@ class SettingsDB extends BaseDB { const setting = new SettingsEntity({ key, value, - category: config.category, updatedAt: Date.now() }).entity @@ -54,7 +52,7 @@ class SettingsDB extends BaseDB { const setting = settings.find(s => s.key === key) return setting ? (setting.value as SettingValue) - : (settingsConfig[key].defaultValue as SettingValue) + : settingKeyItemConfigMap[key].renderOptions.defaultValue } async getAllSettings(): Promise> { @@ -78,13 +76,8 @@ class SettingsDB extends BaseDB { return { ...defaults, ...userSettings } } - async getSettingsByCategory(category: SettingCategory): Promise { - const settings = await this.getAll() - return settings.filter(setting => setting.category === category) - } - getSettingConfig(key: K) { - return settingsConfig[key] + return settingKeyItemConfigMap[key] } getAllSettingConfigs() { diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts index b60b32f..6ea9657 100644 --- a/src/shared/entities/index.ts +++ b/src/shared/entities/index.ts @@ -5,4 +5,4 @@ export * from './chat-context-entity' export * from './chat-session-entity' export * from './conversation-entity' export * from './doc-site-entity' -export * from './settings-entity' +export * from './setting-entity' diff --git a/src/shared/entities/setting-entity/entity.ts b/src/shared/entities/setting-entity/entity.ts new file mode 100644 index 0000000..eaa9e43 --- /dev/null +++ b/src/shared/entities/setting-entity/entity.ts @@ -0,0 +1,22 @@ +import { v4 as uuidv4 } from 'uuid' + +import { BaseEntity, type IBaseEntity } from '../base-entity' +import type { SettingKey, SettingValue } from './setting-config' + +export interface Settings extends IBaseEntity { + key: SettingKey + value: SettingValue + updatedAt: number +} + +export class SettingsEntity extends BaseEntity { + protected getDefaults(data?: Partial): Settings { + return { + id: uuidv4(), + key: 'unknown' as SettingKey, + value: 'unknown', + updatedAt: Date.now(), + ...data + } + } +} diff --git a/src/shared/entities/setting-entity/index.ts b/src/shared/entities/setting-entity/index.ts new file mode 100644 index 0000000..fcf2a30 --- /dev/null +++ b/src/shared/entities/setting-entity/index.ts @@ -0,0 +1,5 @@ +export * from './entity' +export * from './render-options' +export * from './setting-config' +export * from './setting-items-config' +export * from './types' diff --git a/src/shared/entities/setting-entity/render-options.ts b/src/shared/entities/setting-entity/render-options.ts new file mode 100644 index 0000000..ddf90c2 --- /dev/null +++ b/src/shared/entities/setting-entity/render-options.ts @@ -0,0 +1,51 @@ +export type SettingsSaveType = 'global' | 'workspace' + +interface BaseRenderOptions { + type: FormType + label: string + description: string + placeholder?: string + defaultValue: ValueType +} + +export type InputRenderOptions = BaseRenderOptions<'input', string> +export type TextareaRenderOptions = BaseRenderOptions<'textarea', string> +export type SwitchRenderOptions = BaseRenderOptions<'switch', boolean> +export type NumberInputRenderOptions = BaseRenderOptions<'numberInput', number> +export type SelectInputRenderOptions = BaseRenderOptions< + 'selectInput', + string +> & { + options: Array +} +export type ArrayInputRenderOptions = BaseRenderOptions<'arrayInput', any[]> +export type ObjectInputRenderOptions = BaseRenderOptions< + 'objectInput', + Record +> +export type ModelManagementRenderOptions = BaseRenderOptions< + 'modelManagement', + any +> +export type DocIndexingRenderOptions = BaseRenderOptions<'docManagement', any> +export type CodebaseIndexingRenderOptions = BaseRenderOptions< + 'codebaseIndexing', + any +> + +export type RenderOptions = + | InputRenderOptions + | TextareaRenderOptions + | SwitchRenderOptions + | NumberInputRenderOptions + | SelectInputRenderOptions + | ArrayInputRenderOptions + | ObjectInputRenderOptions + | ModelManagementRenderOptions + | DocIndexingRenderOptions + | CodebaseIndexingRenderOptions + +export type RenderOptionsType = RenderOptions['type'] +export type RenderOptionsMap = { + [T in RenderOptionsType]: Extract +} diff --git a/src/shared/entities/setting-entity/setting-config.ts b/src/shared/entities/setting-entity/setting-config.ts new file mode 100644 index 0000000..56effe8 --- /dev/null +++ b/src/shared/entities/setting-entity/setting-config.ts @@ -0,0 +1,126 @@ +import type { ValueUnion } from '@shared/types/common' + +import * as settingItemsConfig from './setting-items-config' +import { + aiCommandAutoRunConfig, + aiCommandConfig, + aiCommandCopyBeforeRunConfig, + aiPromptConfig, + apiConcurrencyConfig, + autoRememberConvertLanguagePairsConfig, + codeViewerHelperPromptConfig, + convertLanguagePairsConfig, + docManagementConfig, + expertCodeEnhancerPromptListConfig, + ignorePatternsConfig, + modelsConfig, + openaiBaseUrlConfig, + openaiKeyConfig, + openaiModelConfig, + readClipboardImageConfig, + respectGitIgnoreConfig, + useSystemProxyConfig +} from './setting-items-config' +import type { SettingConfig, SettingConfigItem } from './types' + +// Setting groups and pages configuration +export const settingsConfig: SettingConfig = { + pages: [ + { + id: 'general', + label: 'General', + settings: [ + openaiKeyConfig, + openaiModelConfig, + openaiBaseUrlConfig, + apiConcurrencyConfig, + useSystemProxyConfig, + ignorePatternsConfig, + respectGitIgnoreConfig + ] + } + ], + groups: [ + { + id: 'chat', + label: 'Chat', + pages: [ + { + id: 'chatModel', + label: 'AI Models', + settings: [modelsConfig] + }, + { + id: 'chatDoc', + label: 'Doc Sites Indexing', + settings: [docManagementConfig] + } + ] + }, + { + id: 'tools', + label: 'Tools', + pages: [ + { + id: 'copyAsPrompt', + label: 'Copy As Prompt', + settings: [aiPromptConfig] + }, + { + id: 'codeConvert', + label: 'Code Convert', + settings: [ + convertLanguagePairsConfig, + autoRememberConvertLanguagePairsConfig + ] + }, + { + id: 'codeViewerHelper', + label: 'Code Viewer Helper', + settings: [codeViewerHelperPromptConfig] + }, + { + id: 'expertCodeEnhancer', + label: 'Expert Code Enhancer', + settings: [expertCodeEnhancerPromptListConfig] + }, + { + id: 'smartPaste', + label: 'Smart Paste', + settings: [readClipboardImageConfig] + }, + { + id: 'askAI', + label: 'Ask AI', + settings: [ + aiCommandConfig, + aiCommandCopyBeforeRunConfig, + aiCommandAutoRunConfig + ] + } + ] + } + ] +} + +type SettingItemsConfig = typeof settingItemsConfig +export type SettingKey = ValueUnion['key'] + +type SettingItemConfigFromKey = SettingConfigItem< + Extract, { key: K }>['renderOptions']['type'] +> + +export type SettingValue = + SettingItemConfigFromKey['renderOptions']['defaultValue'] + +export const settingKeyItemConfigMap = Object.values(settingItemsConfig).reduce( + (acc, item) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + acc[item.key] = item + return acc + }, + {} as { + [K in SettingKey]: SettingItemConfigFromKey + } +) diff --git a/src/shared/entities/setting-entity/setting-items-config.ts b/src/shared/entities/setting-entity/setting-items-config.ts new file mode 100644 index 0000000..3425c60 --- /dev/null +++ b/src/shared/entities/setting-entity/setting-items-config.ts @@ -0,0 +1,214 @@ +import type { SettingConfigItem } from './types' + +// General settings +export const openaiKeyConfig = { + key: 'openaiKey', + saveType: 'global', + renderOptions: { + type: 'input', + label: 'OpenAI Key', + description: + "OpenAI Key, click to view online documentation or I Don't Have an OpenAI Key", + placeholder: 'Enter your OpenAI Key', + defaultValue: '' + } +} as const satisfies SettingConfigItem<'input'> + +export const openaiModelConfig = { + key: 'openaiModel', + saveType: 'global', + renderOptions: { + type: 'selectInput', + label: 'OpenAI Model', + description: 'OpenAI Model, click to view online documentation', + options: ['claude-3.5-sonnet', 'gpt-4'], + defaultValue: 'claude-3.5-sonnet' + } +} as const satisfies SettingConfigItem<'selectInput'> + +export const openaiBaseUrlConfig = { + key: 'openaiBaseUrl', + saveType: 'global', + renderOptions: { + type: 'input', + label: 'OpenAI Base URL', + description: 'OpenAI Base URL, click to view online documentation', + defaultValue: 'https://api.openai.com/v1' + } +} as const satisfies SettingConfigItem<'input'> + +export const apiConcurrencyConfig = { + key: 'apiConcurrency', + saveType: 'global', + renderOptions: { + type: 'numberInput', + label: 'API Concurrency', + description: 'API request concurrency, click to view online documentation', + defaultValue: 3 + } +} as const satisfies SettingConfigItem<'numberInput'> + +export const useSystemProxyConfig = { + key: 'useSystemProxy', + saveType: 'global', + renderOptions: { + type: 'switch', + label: 'Use System Proxy', + description: + 'Use global proxy (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY), you need to restart VSCode to take effect after changing this setting', + defaultValue: false + } +} as const satisfies SettingConfigItem<'switch'> + +export const ignorePatternsConfig = { + key: 'ignorePatterns', + saveType: 'workspace', + renderOptions: { + type: 'arrayInput', + label: 'Ignore Patterns', + description: 'Ignored file name patterns, supports glob syntax', + defaultValue: [ + '**/node_modules/**', + '**/.git/**', + '**/dist/**', + '**/build/**' + ] + } +} as const satisfies SettingConfigItem<'arrayInput'> + +export const respectGitIgnoreConfig = { + key: 'respectGitIgnore', + saveType: 'workspace', + renderOptions: { + type: 'switch', + label: 'Respect .gitignore', + description: 'Respect .gitignore file', + defaultValue: true + } +} as const satisfies SettingConfigItem<'switch'> + +// Chat settings +export const modelsConfig = { + key: 'models', + saveType: 'global', + renderOptions: { + type: 'modelManagement', + label: 'Models', + description: 'Models', + defaultValue: {} + } +} as const satisfies SettingConfigItem<'modelManagement'> + +export const docManagementConfig = { + key: 'docManagement', + saveType: 'global', + renderOptions: { + type: 'docManagement', + label: 'Doc Sites Indexing', + description: 'Doc Sites Indexing', + defaultValue: {} + } +} as const satisfies SettingConfigItem<'docManagement'> + +// Tool settings +export const aiPromptConfig = { + key: 'aiPrompt', + saveType: 'global', + renderOptions: { + type: 'textarea', + label: 'AI Prompt Template', + description: + 'Template for copied content, use #{content} as a variable for file content', + defaultValue: '#{content}' + } +} as const satisfies SettingConfigItem<'textarea'> + +export const convertLanguagePairsConfig = { + key: 'convertLanguagePairs', + saveType: 'workspace', + renderOptions: { + type: 'objectInput', + label: 'Convert Language Pairs', + description: 'Default convert language pairs', + defaultValue: {} + } +} as const satisfies SettingConfigItem<'objectInput'> + +export const autoRememberConvertLanguagePairsConfig = { + key: 'autoRememberConvertLanguagePairs', + saveType: 'global', + renderOptions: { + type: 'switch', + label: 'Auto Remember Convert Language Pairs', + description: 'Automatically remember convert language pairs', + defaultValue: true + } +} as const satisfies SettingConfigItem<'switch'> + +export const codeViewerHelperPromptConfig = { + key: 'codeViewerHelperPrompt', + saveType: 'global', + renderOptions: { + type: 'textarea', + label: 'Code Viewer Helper Prompt', + description: 'Code viewer helper AI prompt template', + defaultValue: '' + } +} as const satisfies SettingConfigItem<'textarea'> + +export const expertCodeEnhancerPromptListConfig = { + key: 'expertCodeEnhancerPromptList', + saveType: 'global', + renderOptions: { + type: 'arrayInput', + label: 'Expert Code Enhancer Prompt List', + description: 'Expert code enhancer AI prompt template list', + defaultValue: [] + } +} as const satisfies SettingConfigItem<'arrayInput'> + +export const readClipboardImageConfig = { + key: 'readClipboardImage', + saveType: 'global', + renderOptions: { + type: 'switch', + label: 'Read Clipboard Image', + description: + 'Allow reading clipboard images as AI context in certain scenarios', + defaultValue: false + } +} as const satisfies SettingConfigItem<'switch'> + +export const aiCommandConfig = { + key: 'aiCommand', + saveType: 'global', + renderOptions: { + type: 'textarea', + label: 'AI Command Template', + description: + 'Custom ✨ Aide: Ask AI command template. Available variables: #{filesRelativePath}, #{filesFullPath}, #{content}', + defaultValue: '' + } +} as const satisfies SettingConfigItem<'textarea'> + +export const aiCommandCopyBeforeRunConfig = { + key: 'aiCommandCopyBeforeRun', + saveType: 'global', + renderOptions: { + type: 'switch', + label: 'Copy AI Command Before Run', + description: 'Copy AI command to clipboard before ✨ Aide: Ask AI running', + defaultValue: false + } +} as const satisfies SettingConfigItem<'switch'> + +export const aiCommandAutoRunConfig = { + key: 'aiCommandAutoRun', + saveType: 'global', + renderOptions: { + type: 'switch', + label: 'Auto Run AI Command', + description: 'Automatically run AI command when clicking ✨ Aide: Ask AI', + defaultValue: false + } +} as const satisfies SettingConfigItem<'switch'> diff --git a/src/shared/entities/setting-entity/types.ts b/src/shared/entities/setting-entity/types.ts new file mode 100644 index 0000000..6b795a3 --- /dev/null +++ b/src/shared/entities/setting-entity/types.ts @@ -0,0 +1,31 @@ +import type { + RenderOptionsMap, + RenderOptionsType, + SettingsSaveType +} from './render-options' + +export interface SettingGroup { + id: string + label: string + pages: SettingPage[] +} + +export interface SettingPage { + id: string + label: string + settings: SettingConfigItem[] + relatedSettings?: SettingConfigItem[] +} + +export interface SettingConfig { + pages?: SettingPage[] // general settings + groups: SettingGroup[] +} + +export interface SettingConfigItem< + T extends RenderOptionsType = RenderOptionsType +> { + key: string + saveType: SettingsSaveType + renderOptions: RenderOptionsMap[T] +} diff --git a/src/shared/entities/settings-entity.ts b/src/shared/entities/settings-entity.ts deleted file mode 100644 index 3a1acd9..0000000 --- a/src/shared/entities/settings-entity.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { v4 as uuidv4 } from 'uuid' - -import { BaseEntity, type IBaseEntity } from './base-entity' - -export interface Settings extends IBaseEntity { - key: SettingKey - value: SettingValue - category: SettingCategory - updatedAt: number -} - -export class SettingsEntity extends BaseEntity { - protected getDefaults(data?: Partial): Settings { - return { - id: uuidv4(), - key: 'unknown' as SettingKey, - value: 'unknown', - category: 'appearance' as SettingCategory, - updatedAt: Date.now(), - ...data - } - } -} - -export type SettingsSaveType = 'global' | 'workspace' -export type SettingsOption = string | { label: string; value: string } - -export interface SettingsConfigItem { - saveType: SettingsSaveType - label: string - description: string - type: 'string' | 'boolean' | 'number' | 'array' | 'object' | 'select' - options?: SettingsOption[] - defaultValue: any - category: string - isCustomRender?: boolean -} - -export const settingsConfig = { - // General Settings - models: { - saveType: 'global', - label: 'Models', - description: 'Models', - type: 'object', - defaultValue: {}, - category: 'general', - isCustomRender: true - }, - openaiKey: { - saveType: 'global', - label: 'OpenAI Key', - description: - "OpenAI Key, click to view online documentation or I Don't Have an OpenAI Key", - type: 'string', - defaultValue: '', - category: 'general' - }, - openaiModel: { - saveType: 'global', - label: 'OpenAI Model', - description: 'OpenAI Model, click to view online documentation', - type: 'select', - options: ['claude-3.5-sonnet', 'gpt-4'], - defaultValue: 'claude-3.5-sonnet', - category: 'general' - }, - openaiBaseUrl: { - saveType: 'global', - label: 'OpenAI Base URL', - description: 'OpenAI Base URL, click to view online documentation', - type: 'string', - defaultValue: 'https://api.openai.com/v1', - category: 'general' - }, - apiConcurrency: { - saveType: 'global', - label: 'API Concurrency', - description: 'API request concurrency, click to view online documentation', - type: 'number', - defaultValue: 3, - category: 'general' - }, - useSystemProxy: { - saveType: 'global', - label: 'Use System Proxy', - description: - 'Use global proxy (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY), you need to restart VSCode to take effect after changing this setting', - type: 'boolean', - defaultValue: false, - category: 'general' - }, - - // File Settings - ignorePatterns: { - saveType: 'workspace', - label: 'Ignore Patterns', - description: 'Ignored file name patterns, supports glob syntax', - type: 'array', - defaultValue: [ - '**/node_modules/**', - '**/.git/**', - '**/dist/**', - '**/build/**' - ], - category: 'general' - }, - respectGitIgnore: { - saveType: 'workspace', - label: 'Respect .gitignore', - description: 'Respect .gitignore file', - type: 'boolean', - defaultValue: true, - category: 'general' - }, - - // Tools Settings - aiPrompt: { - saveType: 'global', - label: 'AI Prompt Template', - description: - 'Template for copied content, use #{content} as a variable for file content', - type: 'string', - defaultValue: '#{content}', - category: 'copyAsPrompt' - }, - convertLanguagePairs: { - saveType: 'workspace', - label: 'Convert Language Pairs', - description: 'Default convert language pairs', - type: 'object', - defaultValue: {}, - category: 'codeConvert' - }, - autoRememberConvertLanguagePairs: { - saveType: 'global', - label: 'Auto Remember Convert Language Pairs', - description: 'Automatically remember convert language pairs', - type: 'boolean', - defaultValue: true, - category: 'codeConvert' - }, - codeViewerHelperPrompt: { - saveType: 'global', - label: 'Code Viewer Helper Prompt', - description: 'Code viewer helper AI prompt template', - type: 'string', - defaultValue: '', - category: 'codeViewerHelper' - }, - expertCodeEnhancerPromptList: { - saveType: 'global', - label: 'Expert Code Enhancer Prompt List', - description: 'Expert code enhancer AI prompt template list', - type: 'array', - defaultValue: [], - category: 'expertCodeEnhancer' - }, - readClipboardImage: { - saveType: 'global', - label: 'Read Clipboard Image', - description: - 'Allow reading clipboard images as AI context in certain scenarios', - type: 'boolean', - defaultValue: false, - category: 'smartPaste' - }, - - // AI Command Settings - aiCommand: { - saveType: 'global', - label: 'AI Command Template', - description: - 'Custom ✨ Aide: Ask AI command template. Available variables: #{filesRelativePath}, #{filesFullPath}, #{content}', - type: 'string', - defaultValue: '', - category: 'askAI' - }, - aiCommandCopyBeforeRun: { - saveType: 'global', - label: 'Copy AI Command Before Run', - description: 'Copy AI command to clipboard before ✨ Aide: Ask AI running', - type: 'boolean', - defaultValue: false, - category: 'askAI' - }, - aiCommandAutoRun: { - saveType: 'global', - label: 'Auto Run AI Command', - description: 'Automatically run AI command when clicking ✨ Aide: Ask AI', - type: 'boolean', - defaultValue: false, - category: 'askAI' - } -} as const satisfies Record - -// Type helpers -type SettingValueType = { - string: string - boolean: boolean - number: number - array: any[] - object: Record - select: string -} - -export type SettingKey = keyof typeof settingsConfig -export type SettingCategory = (typeof settingsConfig)[SettingKey]['category'] -export type SettingValue = - SettingValueType[(typeof settingsConfig)[K]['type']] diff --git a/src/shared/plugins/base/client/client-plugin-context.ts b/src/shared/plugins/base/client/client-plugin-context.ts index a3712b2..711e3e7 100644 --- a/src/shared/plugins/base/client/client-plugin-context.ts +++ b/src/shared/plugins/base/client/client-plugin-context.ts @@ -53,7 +53,7 @@ export class ClientPluginContext { } getQueryClient() { - return this.registry.queryClient + return this?.registry?.queryClient } registerCommand(command: string, callback: (...args: any[]) => void): void { diff --git a/src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx b/src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx index 1dc5682..ee5a9bb 100644 --- a/src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx +++ b/src/shared/plugins/doc-plugin/client/doc-client-plugin.tsx @@ -46,7 +46,11 @@ export class DocClientPlugin implements ClientPlugin { private async getMentionOptions(): Promise { if (!this.context) return [] - const docSites = await this.context.getQueryClient().fetchQuery({ + const queryClient = this?.context?.getQueryClient?.() + + if (!queryClient) return [] + + const docSites = await queryClient.fetchQuery({ queryKey: ['realtime', 'docSites'], queryFn: () => api.doc.getDocSites({}) }) diff --git a/src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx b/src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx index 61c5856..55ec17b 100644 --- a/src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx +++ b/src/shared/plugins/fs-plugin/client/fs-client-plugin.tsx @@ -117,22 +117,26 @@ export class FsClientPlugin implements ClientPlugin { private async getMentionOptions(): Promise { if (!this.context) return [] - const files = await this.context.getQueryClient().fetchQuery({ + const queryClient = this?.context?.getQueryClient?.() + + if (!queryClient) return [] + + const files = await queryClient.fetchQuery({ queryKey: ['realtime', 'files'], queryFn: () => api.file.traverseWorkspaceFiles({ filesOrFolders: ['./'] }) }) - const folders = await this.context.getQueryClient().fetchQuery({ + const folders = await queryClient.fetchQuery({ queryKey: ['realtime', 'folders'], queryFn: () => api.file.traverseWorkspaceFolders({ folders: ['./'] }) }) - const editorErrors = await this.context.getQueryClient().fetchQuery({ + const editorErrors = await queryClient.fetchQuery({ queryKey: ['realtime', 'editorErrors'], queryFn: () => api.file.getCurrentEditorErrors({}) }) - const treesInfo = await this.context.getQueryClient().fetchQuery({ + const treesInfo = await queryClient.fetchQuery({ queryKey: ['realtime', 'treesInfo'], queryFn: () => api.file.getWorkspaceTreesInfo({ depth: 5 }) }) diff --git a/src/shared/plugins/git-plugin/client/git-client-plugin.tsx b/src/shared/plugins/git-plugin/client/git-client-plugin.tsx index cde8dc5..ae2189f 100644 --- a/src/shared/plugins/git-plugin/client/git-client-plugin.tsx +++ b/src/shared/plugins/git-plugin/client/git-client-plugin.tsx @@ -42,7 +42,11 @@ export class GitClientPlugin implements ClientPlugin { private async getMentionOptions(): Promise { if (!this.context) return [] - const gitCommits = await this.context.getQueryClient().fetchQuery({ + const queryClient = this?.context?.getQueryClient?.() + + if (!queryClient) return [] + + const gitCommits = await queryClient.fetchQuery({ queryKey: ['realtime', 'git-commits'], queryFn: () => api.git.getHistoryCommits({ diff --git a/src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx b/src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx index 0a326c5..1f21a1e 100644 --- a/src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx +++ b/src/shared/plugins/terminal-plugin/client/terminal-client-plugin.tsx @@ -44,7 +44,11 @@ export class TerminalClientPlugin implements ClientPlugin { private async getMentionOptions(): Promise { if (!this.context) return [] - const terminals = await this.context.getQueryClient().fetchQuery({ + const queryClient = this?.context?.getQueryClient?.() + + if (!queryClient) return [] + + const terminals = await queryClient.fetchQuery({ queryKey: ['realtime', 'terminals'], queryFn: () => api.terminal.getTerminalsForMention({}) }) diff --git a/src/shared/types/common.ts b/src/shared/types/common.ts index bc6a974..784b8b0 100644 --- a/src/shared/types/common.ts +++ b/src/shared/types/common.ts @@ -1,2 +1,3 @@ export type UnPromise = T extends Promise ? U : T export type MaybePromise = T | Promise +export type ValueUnion = T[keyof T] diff --git a/src/webview/components/global-search/global-search.tsx b/src/webview/components/global-search/global-search.tsx index e2206c5..14d685d 100644 --- a/src/webview/components/global-search/global-search.tsx +++ b/src/webview/components/global-search/global-search.tsx @@ -3,15 +3,11 @@ import { Command, CommandDialog, CommandEmpty, + CommandHook, CommandInput, CommandItem, CommandList } from '@webview/components/ui/command' -import { - Popover, - PopoverContent, - PopoverTrigger -} from '@webview/components/ui/popover' import { Tabs, TabsContent, @@ -47,7 +43,7 @@ export interface SearchItem extends Partial { interface GlobalSearchProps { categories: SearchCategory[] - categoriesIsResult?: boolean + useInnerFilter?: boolean onOpenChange?: (open: boolean) => void open?: boolean activeCategory?: string @@ -65,7 +61,7 @@ const keyboardShortcuts: ShortcutInfo[] = [ export const GlobalSearch: React.FC = ({ categories, - categoriesIsResult, + useInnerFilter = true, onOpenChange, open: openProp, activeCategory: activeCategoryProp, @@ -101,7 +97,7 @@ export const GlobalSearch: React.FC = ({ ? categories.flatMap(c => c.items) : categories.find(c => c.id === activeCategory)?.items || [] - if (categoriesIsResult) { + if (useInnerFilter) { setFilteredItems(items) } else { setFilteredItems( @@ -116,7 +112,7 @@ export const GlobalSearch: React.FC = ({ }) ) } - }, [activeCategory, searchQuery, categories, categoriesIsResult]) + }, [activeCategory, searchQuery, categories, useInnerFilter]) const handleOpenChange = (open: boolean) => { setIsOpen(open) @@ -145,21 +141,29 @@ export const GlobalSearch: React.FC = ({ }) return ( - +
{ - const target = filteredItems.find(item => item.id === val) - target && setFocusedItem(target) - }} + shouldFilter={useInnerFilter} + className="border rounded-md h-auto" > + { + const target = filteredItems.find(item => item.id === val) + target && setFocusedItem(target) + }} + /> = ({ ))} + - - - -
- - e.preventDefault()} - onCloseAutoFocus={e => e.preventDefault()} - onKeyDown={e => e.stopPropagation()} - > - {focusedItem?.renderPreview?.()} - - +
+ {isOpen && focusedItem?.renderPreview ? ( +
+ {focusedItem.renderPreview()} +
+ ) : null} +
) diff --git a/src/webview/components/settings/setting-item-renderer.tsx b/src/webview/components/settings/setting-item-renderer.tsx new file mode 100644 index 0000000..35ada10 --- /dev/null +++ b/src/webview/components/settings/setting-item-renderer.tsx @@ -0,0 +1,127 @@ +import { useState } from 'react' +import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons' +import type { SettingConfigItem } from '@shared/entities' +import { Button } from '@webview/components/ui/button' +import { Input } from '@webview/components/ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@webview/components/ui/select' +import { Switch } from '@webview/components/ui/switch' +import { Textarea } from '@webview/components/ui/textarea' + +import { AIProviderManagement } from './custom-renders/ai-provider-management' +import { CodebaseIndexing } from './custom-renders/codebase' +import { DocManagement } from './custom-renders/doc-management' + +interface SettingItemRendererProps { + value: any + onChange: (value: any) => void + disabled?: boolean + config: SettingConfigItem +} + +export const SettingItemRenderer = ({ + value, + onChange, + disabled, + config +}: SettingItemRendererProps) => { + const [showSecret, setShowSecret] = useState(false) + + const val = value ?? config.renderOptions.defaultValue + const inputProps = { + disabled, + value: val, + onChange: (e: any) => onChange(e.target.value), + placeholder: config.renderOptions.placeholder ?? '', + className: 'text-sm' + } + + switch (config.renderOptions.type) { + case 'input': + return ( +
+ + +
+ ) + + case 'textarea': + return