diff --git a/src/components/dialog/content/ExecutionErrorDialogContent.vue b/src/components/dialog/content/ExecutionErrorDialogContent.vue index e513b24c8..7cee141ed 100644 --- a/src/components/dialog/content/ExecutionErrorDialogContent.vue +++ b/src/components/dialog/content/ExecutionErrorDialogContent.vue @@ -38,7 +38,6 @@ diff --git a/src/constants/serverConfig.ts b/src/constants/serverConfig.ts index 032150369..0eb8a273a 100644 --- a/src/constants/serverConfig.ts +++ b/src/constants/serverConfig.ts @@ -10,15 +10,17 @@ import { VramManagement } from '@/types/serverArgs' +export type ServerConfigValue = string | number | true | null | undefined + export interface ServerConfig extends FormItem { id: string defaultValue: T category?: string[] // Override the default value getter with a custom function. - getValue?: (value: T) => Record + getValue?: (value: T) => Record } -export const WEB_ONLY_CONFIG_ITEMS: ServerConfig[] = [ +export const WEB_ONLY_CONFIG_ITEMS: ServerConfig[] = [ // We only need these settings in the web version. Desktop app manages them already. { id: 'listen', @@ -43,21 +45,21 @@ export const SERVER_CONFIG_ITEMS: ServerConfig[] = [ name: 'TLS Key File: Path to TLS key file for HTTPS', category: ['Network'], type: 'text', - defaultValue: undefined + defaultValue: '' }, { id: 'tls-certfile', name: 'TLS Certificate File: Path to TLS certificate file for HTTPS', category: ['Network'], type: 'text', - defaultValue: undefined + defaultValue: '' }, { id: 'enable-cors-header', name: 'Enable CORS header: Use "*" for all origins or specify domain', category: ['Network'], type: 'text', - defaultValue: undefined + defaultValue: '' }, { id: 'max-upload-size', @@ -97,7 +99,7 @@ export const SERVER_CONFIG_ITEMS: ServerConfig[] = [ name: 'CUDA device index to use', category: ['CUDA'], type: 'number', - defaultValue: undefined + defaultValue: null }, { id: 'cuda-malloc', @@ -253,7 +255,7 @@ export const SERVER_CONFIG_ITEMS: ServerConfig[] = [ name: 'DirectML device index', category: ['Memory'], type: 'number', - defaultValue: undefined + defaultValue: null }, { id: 'disable-ipex-optimize', @@ -295,10 +297,10 @@ export const SERVER_CONFIG_ITEMS: ServerConfig[] = [ }, { id: 'cache-lru', - name: 'Use LRU caching with a maximum of N node results cached. (0 to disable).', + name: 'Use LRU caching with a maximum of N node results cached.', category: ['Cache'], type: 'number', - defaultValue: 0, + defaultValue: null, tooltip: 'May use more RAM/VRAM.' }, @@ -366,7 +368,7 @@ export const SERVER_CONFIG_ITEMS: ServerConfig[] = [ name: 'Reserved VRAM (GB)', category: ['Memory'], type: 'number', - defaultValue: undefined, + defaultValue: null, tooltip: 'Set the amount of vram in GB you want to reserve for use by your OS/other software. By default some amount is reverved depending on your OS.' }, diff --git a/src/hooks/clipboardHooks.ts b/src/hooks/clipboardHooks.ts new file mode 100644 index 000000000..c2d06b325 --- /dev/null +++ b/src/hooks/clipboardHooks.ts @@ -0,0 +1,37 @@ +import { useClipboard } from '@vueuse/core' +import { useToast } from 'primevue/usetoast' + +export function useCopyToClipboard() { + const { copy, isSupported } = useClipboard() + const toast = useToast() + + const copyToClipboard = async (text: string) => { + if (isSupported) { + try { + await copy(text) + toast.add({ + severity: 'success', + summary: 'Success', + detail: 'Copied to clipboard', + life: 3000 + }) + } catch (err) { + toast.add({ + severity: 'error', + summary: 'Error', + detail: 'Failed to copy report' + }) + } + } else { + toast.add({ + severity: 'error', + summary: 'Error', + detail: 'Clipboard API not supported in your browser' + }) + } + } + + return { + copyToClipboard + } +} diff --git a/src/stores/serverConfigStore.ts b/src/stores/serverConfigStore.ts index cf07fc5ec..980b3acb0 100644 --- a/src/stores/serverConfigStore.ts +++ b/src/stores/serverConfigStore.ts @@ -1,4 +1,4 @@ -import { ServerConfig } from '@/constants/serverConfig' +import { ServerConfig, ServerConfigValue } from '@/constants/serverConfig' import { defineStore } from 'pinia' import { computed, ref } from 'vue' @@ -14,18 +14,26 @@ export type ServerConfigWithValue = ServerConfig & { } export const useServerConfigStore = defineStore('serverConfig', () => { - const serverConfigById = ref>>({}) + const serverConfigById = ref< + Record> + >({}) const serverConfigs = computed(() => { return Object.values(serverConfigById.value) }) - const modifiedConfigs = computed[]>(() => { - return serverConfigs.value.filter((config) => { - return config.initialValue !== config.value - }) - }) - + const modifiedConfigs = computed[]>( + () => { + return serverConfigs.value.filter((config) => { + return config.initialValue !== config.value + }) + } + ) + const revertChanges = () => { + for (const config of modifiedConfigs.value) { + config.value = config.initialValue + } + } const serverConfigsByCategory = computed< - Record[]> + Record[]> >(() => { return serverConfigs.value.reduce( (acc, config) => { @@ -34,15 +42,17 @@ export const useServerConfigStore = defineStore('serverConfig', () => { acc[category].push(config) return acc }, - {} as Record[]> + {} as Record[]> ) }) - const serverConfigValues = computed>(() => { + const serverConfigValues = computed>(() => { return Object.fromEntries( serverConfigs.value.map((config) => { return [ config.id, - config.value === config.defaultValue || !config.value + config.value === config.defaultValue || + config.value === null || + config.value === undefined ? undefined : config.value ] @@ -50,10 +60,18 @@ export const useServerConfigStore = defineStore('serverConfig', () => { ) }) const launchArgs = computed>(() => { - return Object.assign( + const args: Record< + string, + Omit + > = Object.assign( {}, ...serverConfigs.value.map((config) => { - if (config.value === config.defaultValue || !config.value) { + // Filter out configs that have the default value or undefined | null value + if ( + config.value === config.defaultValue || + config.value === null || + config.value === undefined + ) { return {} } return config.getValue @@ -61,11 +79,29 @@ export const useServerConfigStore = defineStore('serverConfig', () => { : { [config.id]: config.value } }) ) + + // Convert true to empty string + // Convert number to string + return Object.fromEntries( + Object.entries(args).map(([key, value]) => { + if (value === true) { + return [key, ''] + } + return [key, value.toString()] + }) + ) as Record + }) + const commandLineArgs = computed(() => { + return Object.entries(launchArgs.value) + .map(([key, value]) => [`--${key}`, value]) + .flat() + .filter((arg: string) => arg !== '') + .join(' ') }) function loadServerConfig( - configs: ServerConfig[], - values: Record + configs: ServerConfig[], + values: Record ) { for (const config of configs) { const value = values[config.id] ?? config.defaultValue @@ -84,6 +120,8 @@ export const useServerConfigStore = defineStore('serverConfig', () => { serverConfigsByCategory, serverConfigValues, launchArgs, + commandLineArgs, + revertChanges, loadServerConfig } }) diff --git a/tests-ui/tests/fast/store/serverConfigStore.test.ts b/tests-ui/tests/fast/store/serverConfigStore.test.ts index 79f5ebe49..d5d4a5dea 100644 --- a/tests-ui/tests/fast/store/serverConfigStore.test.ts +++ b/tests-ui/tests/fast/store/serverConfigStore.test.ts @@ -170,21 +170,80 @@ describe('useServerConfigStore', () => { const configs: ServerConfig[] = [ { ...dummyFormItem, id: 'test.config1', defaultValue: 'default1' }, { ...dummyFormItem, id: 'test.config2', defaultValue: 'default2' }, - { ...dummyFormItem, id: 'test.config3', defaultValue: 'default3' } + { ...dummyFormItem, id: 'test.config3', defaultValue: 'default3' }, + { ...dummyFormItem, id: 'test.config4', defaultValue: null } ] store.loadServerConfig(configs, { 'test.config1': undefined, 'test.config2': null, - 'test.config3': '' + 'test.config3': '', + 'test.config4': 0 }) - expect(Object.keys(store.launchArgs)).toHaveLength(0) - expect(Object.keys(store.serverConfigValues)).toEqual([ - 'test.config1', - 'test.config2', - 'test.config3' + expect(Object.keys(store.launchArgs)).toEqual([ + 'test.config3', + 'test.config4' ]) + expect(Object.values(store.launchArgs)).toEqual(['', '0']) + expect(store.serverConfigById['test.config3'].value).toBe('') + expect(store.serverConfigById['test.config4'].value).toBe(0) + expect(Object.values(store.serverConfigValues)).toEqual([ + undefined, + undefined, + '', + 0 + ]) + }) + + it('should convert true to empty string in launch arguments', () => { + store.loadServerConfig( + [ + { + ...dummyFormItem, + id: 'test.config1', + defaultValue: 0 + } + ], + { + 'test.config1': true + } + ) + expect(store.launchArgs['test.config1']).toBe('') + expect(store.commandLineArgs).toBe('--test.config1') + }) + + it('should convert number to string in launch arguments', () => { + store.loadServerConfig( + [ + { + ...dummyFormItem, + id: 'test.config1', + defaultValue: 1 + } + ], + { + 'test.config1': 123 + } + ) + expect(store.launchArgs['test.config1']).toBe('123') + expect(store.commandLineArgs).toBe('--test.config1 123') + }) + + it('should drop nullish values in launch arguments', () => { + store.loadServerConfig( + [ + { + ...dummyFormItem, + id: 'test.config1', + defaultValue: 1 + } + ], + { + 'test.config1': null + } + ) + expect(Object.keys(store.launchArgs)).toHaveLength(0) }) it('should track modified configs', () => {