[refactor] Improve settings domain organization (#5550)

* refactor: move settingStore to platform/settings

Move src/stores/settingStore.ts to src/platform/settings/settingStore.ts
to separate platform infrastructure from domain logic following DDD principles.

Updates all import references across ~70 files to maintain compatibility.

* fix: update remaining settingStore imports after rebase

* fix: complete remaining settingStore import updates

* fix: update vi.mock paths for settingStore in tests

Update all test files to mock the new settingStore location at
@/platform/settings/settingStore instead of @/stores/settingStore

* fix: resolve remaining settingStore imports and unused imports after rebase

* fix: update settingStore mock path in SelectionToolbox test

Fix vi.mock path from @/stores/settingStore to @/platform/settings/settingStore
to resolve failing Load3D viewer button test.

* refactor: complete comprehensive settings migration to platform layer

This commit completes the migration of all settings-related code to the platform layer
as part of the Domain-Driven Design (DDD) architecture refactoring.

- constants/coreSettings.ts → platform/settings/constants/coreSettings.ts
- types/settingTypes.ts → platform/settings/types.ts
- stores/settingStore.ts → platform/settings/settingStore.ts (already moved)

- composables/setting/useSettingUI.ts → platform/settings/composables/useSettingUI.ts
- composables/setting/useSettingSearch.ts → platform/settings/composables/useSettingSearch.ts
- composables/useLitegraphSettings.ts → platform/settings/composables/useLitegraphSettings.ts

- components/dialog/content/SettingDialogContent.vue → platform/settings/components/SettingDialogContent.vue
- components/dialog/content/setting/SettingItem.vue → platform/settings/components/SettingItem.vue
- components/dialog/content/setting/SettingGroup.vue → platform/settings/components/SettingGroup.vue
- components/dialog/content/setting/SettingsPanel.vue → platform/settings/components/SettingsPanel.vue
- components/dialog/content/setting/ColorPaletteMessage.vue → platform/settings/components/ColorPaletteMessage.vue
- components/dialog/content/setting/ExtensionPanel.vue → platform/settings/components/ExtensionPanel.vue
- components/dialog/content/setting/ServerConfigPanel.vue → platform/settings/components/ServerConfigPanel.vue

- ~100+ import statements updated across the codebase
- Test file imports corrected
- Component imports fixed in dialog service and command menubar
- Composable imports updated in GraphCanvas.vue

```
src/platform/settings/
├── components/          # All settings UI components
├── composables/         # Settings-related composables
├── constants/          # Core settings definitions
├── types.ts           # Settings type definitions
└── settingStore.ts    # Central settings state management
```

 TypeScript compilation successful
 All tests passing (settings store, search functionality, UI components)
 Production build successful
 Domain boundaries properly established

This migration consolidates all settings functionality into a cohesive platform domain,
improving maintainability and following DDD principles for better code organization.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: format and lint after rebase conflict resolution

* fix: update remaining import paths to platform settings

- Fix browser test import: extensionAPI.spec.ts
- Fix script import: collect-i18n-general.ts
- Complete settings migration import path updates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Byrne
2025-09-15 03:53:08 -07:00
committed by GitHub
parent e3bb29ceb8
commit 4c8c4a1ad4
118 changed files with 161 additions and 157 deletions

View File

@@ -12,8 +12,8 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useSettingStore } from '@/stores/settingStore'
const settingStore = useSettingStore()
const workflowStore = useWorkflowStore()

View File

@@ -94,9 +94,9 @@ import Message from 'primevue/message'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { ConfirmationDialogType } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'
import { useSettingStore } from '@/stores/settingStore'
const props = defineProps<{
message: string

View File

@@ -39,7 +39,7 @@ import { useI18n } from 'vue-i18n'
import ElectronFileDownload from '@/components/common/ElectronFileDownload.vue'
import FileDownload from '@/components/common/FileDownload.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { useSettingStore } from '@/stores/settingStore'
import { useSettingStore } from '@/platform/settings/settingStore'
import { isElectron } from '@/utils/envUtil'
// TODO: Read this from server internal API rather than hardcoding here

View File

@@ -1,190 +0,0 @@
<template>
<div class="settings-container">
<ScrollPanel class="settings-sidebar shrink-0 p-2 w-48 2xl:w-64">
<SearchBox
v-model:modelValue="searchQuery"
class="settings-search-box w-full mb-2"
:placeholder="$t('g.searchSettings') + '...'"
:debounce-time="128"
@search="handleSearch"
/>
<Listbox
v-model="activeCategory"
:options="groupedMenuTreeNodes"
option-label="translatedLabel"
option-group-label="label"
option-group-children="children"
scroll-height="100%"
:option-disabled="
(option: SettingTreeNode) =>
!queryIsEmpty && !searchResultsCategories.has(option.label ?? '')
"
class="border-none w-full"
>
<template #optiongroup>
<Divider class="my-0" />
</template>
</Listbox>
</ScrollPanel>
<Divider layout="vertical" class="mx-1 2xl:mx-4 hidden md:flex" />
<Divider layout="horizontal" class="flex md:hidden" />
<Tabs :value="tabValue" :lazy="true" class="settings-content h-full w-full">
<TabPanels class="settings-tab-panels h-full w-full pr-0">
<PanelTemplate value="Search Results">
<SettingsPanel :setting-groups="searchResults" />
</PanelTemplate>
<PanelTemplate
v-for="category in settingCategories"
:key="category.key"
:value="category.label ?? ''"
>
<template #header>
<CurrentUserMessage v-if="tabValue === 'Comfy'" />
<ColorPaletteMessage v-if="tabValue === 'Appearance'" />
</template>
<SettingsPanel :setting-groups="sortedGroups(category)" />
</PanelTemplate>
<Suspense v-for="panel in panels" :key="panel.node.key">
<component :is="panel.component" />
<template #fallback>
<div>{{ $t('g.loadingPanel', { panel: panel.node.label }) }}</div>
</template>
</Suspense>
</TabPanels>
</Tabs>
</div>
</template>
<script setup lang="ts">
import Divider from 'primevue/divider'
import Listbox from 'primevue/listbox'
import ScrollPanel from 'primevue/scrollpanel'
import TabPanels from 'primevue/tabpanels'
import Tabs from 'primevue/tabs'
import { computed, watch } from 'vue'
import SearchBox from '@/components/common/SearchBox.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSettingSearch } from '@/composables/setting/useSettingSearch'
import { useSettingUI } from '@/composables/setting/useSettingUI'
import { SettingTreeNode } from '@/stores/settingStore'
import { ISettingGroup, SettingParams } from '@/types/settingTypes'
import { flattenTree } from '@/utils/treeUtil'
import ColorPaletteMessage from './setting/ColorPaletteMessage.vue'
import CurrentUserMessage from './setting/CurrentUserMessage.vue'
import PanelTemplate from './setting/PanelTemplate.vue'
import SettingsPanel from './setting/SettingsPanel.vue'
const { defaultPanel } = defineProps<{
defaultPanel?:
| 'about'
| 'keybinding'
| 'extension'
| 'server-config'
| 'user'
| 'credits'
}>()
const {
activeCategory,
defaultCategory,
settingCategories,
groupedMenuTreeNodes,
panels
} = useSettingUI(defaultPanel)
const {
searchQuery,
searchResultsCategories,
queryIsEmpty,
inSearch,
handleSearch: handleSearchBase,
getSearchResults
} = useSettingSearch()
const authActions = useFirebaseAuthActions()
// Sort groups for a category
const sortedGroups = (category: SettingTreeNode): ISettingGroup[] => {
return [...(category.children ?? [])]
.sort((a, b) => a.label.localeCompare(b.label))
.map((group) => ({
label: group.label,
settings: flattenTree<SettingParams>(group).sort((a, b) => {
const sortOrderA = a.sortOrder ?? 0
const sortOrderB = b.sortOrder ?? 0
return sortOrderB - sortOrderA
})
}))
}
const handleSearch = (query: string) => {
handleSearchBase(query.trim())
activeCategory.value = query ? null : defaultCategory.value
}
// Get search results
const searchResults = computed<ISettingGroup[]>(() =>
getSearchResults(activeCategory.value)
)
const tabValue = computed<string>(() =>
inSearch.value ? 'Search Results' : activeCategory.value?.label ?? ''
)
// Don't allow null category to be set outside of search.
// In search mode, the active category can be null to show all search results.
watch(activeCategory, (_, oldValue) => {
if (!tabValue.value) {
activeCategory.value = oldValue
}
if (activeCategory.value?.key === 'credits') {
void authActions.fetchBalance()
}
})
</script>
<style>
.settings-tab-panels {
padding-top: 0 !important;
}
</style>
<style scoped>
.settings-container {
display: flex;
height: 70vh;
width: 60vw;
max-width: 64rem;
overflow: hidden;
}
.settings-content {
overflow-x: auto;
}
@media (max-width: 768px) {
.settings-container {
flex-direction: column;
height: auto;
width: 80vw;
}
.settings-sidebar {
width: 100%;
}
.settings-content {
height: 350px;
}
}
/* Hide the first group separator */
.settings-sidebar :deep(.p-listbox-option-group:nth-child(1)) {
display: none;
}
</style>

View File

@@ -1,61 +0,0 @@
<template>
<Message severity="info" icon="pi pi-palette" pt:text="w-full">
<div class="flex items-center justify-between">
<div>
{{ $t('settingsCategories.ColorPalette') }}
</div>
<div class="actions">
<Select
v-model="activePaletteId"
class="w-44"
:options="palettes"
option-label="name"
option-value="id"
/>
<Button
icon="pi pi-file-export"
text
:title="$t('g.export')"
@click="colorPaletteService.exportColorPalette(activePaletteId)"
/>
<Button
icon="pi pi-file-import"
text
:title="$t('g.import')"
@click="importCustomPalette"
/>
<Button
icon="pi pi-trash"
severity="danger"
text
:title="$t('g.delete')"
:disabled="!colorPaletteStore.isCustomPalette(activePaletteId)"
@click="colorPaletteService.deleteCustomColorPalette(activePaletteId)"
/>
</div>
</div>
</Message>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import Message from 'primevue/message'
import Select from 'primevue/select'
import { useColorPaletteService } from '@/services/colorPaletteService'
import { useSettingStore } from '@/stores/settingStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
const settingStore = useSettingStore()
const colorPaletteStore = useColorPaletteStore()
const colorPaletteService = useColorPaletteService()
const { palettes, activePaletteId } = storeToRefs(colorPaletteStore)
const importCustomPalette = async () => {
const palette = await colorPaletteService.importColorPalette()
if (palette) {
await settingStore.set('Comfy.ColorPalette', palette.id)
}
}
</script>

View File

@@ -1,239 +0,0 @@
<template>
<PanelTemplate value="Extension" class="extension-panel">
<template #header>
<SearchBox
v-model="filters['global'].value"
:placeholder="$t('g.searchExtensions') + '...'"
/>
<Message
v-if="hasChanges"
severity="info"
pt:text="w-full"
class="max-h-96 overflow-y-auto"
>
<ul>
<li v-for="ext in changedExtensions" :key="ext.name">
<span>
{{ extensionStore.isExtensionEnabled(ext.name) ? '[-]' : '[+]' }}
</span>
{{ ext.name }}
</li>
</ul>
<div class="flex justify-end">
<Button
:label="$t('g.reloadToApplyChanges')"
outlined
severity="danger"
@click="applyChanges"
/>
</div>
</Message>
</template>
<div class="mb-3 flex gap-2">
<SelectButton v-model="filterType" :options="filterTypes" />
</div>
<DataTable
v-model:selection="selectedExtensions"
:value="filteredExtensions"
striped-rows
size="small"
:filters="filters"
selection-mode="multiple"
data-key="name"
>
<Column selection-mode="multiple" :frozen="true" style="width: 3rem" />
<Column :header="$t('g.extensionName')" sortable field="name">
<template #body="slotProps">
{{ slotProps.data.name }}
<Tag
v-if="extensionStore.isCoreExtension(slotProps.data.name)"
value="Core"
/>
<Tag v-else value="Custom" severity="info" />
</template>
</Column>
<Column
:pt="{
headerCell: 'flex items-center justify-end',
bodyCell: 'flex items-center justify-end'
}"
>
<template #header>
<Button
icon="pi pi-ellipsis-h"
text
severity="secondary"
@click="menu?.show($event)"
/>
<ContextMenu ref="menu" :model="contextMenuItems" />
</template>
<template #body="slotProps">
<ToggleSwitch
v-model="editingEnabledExtensions[slotProps.data.name]"
:disabled="extensionStore.isExtensionReadOnly(slotProps.data.name)"
@change="updateExtensionStatus"
/>
</template>
</Column>
</DataTable>
</PanelTemplate>
</template>
<script setup lang="ts">
import { FilterMatchMode } from '@primevue/core/api'
import Button from 'primevue/button'
import Column from 'primevue/column'
import ContextMenu from 'primevue/contextmenu'
import DataTable from 'primevue/datatable'
import Message from 'primevue/message'
import SelectButton from 'primevue/selectbutton'
import Tag from 'primevue/tag'
import ToggleSwitch from 'primevue/toggleswitch'
import { computed, onMounted, ref } from 'vue'
import SearchBox from '@/components/common/SearchBox.vue'
import { useExtensionStore } from '@/stores/extensionStore'
import { useSettingStore } from '@/stores/settingStore'
import PanelTemplate from './PanelTemplate.vue'
const filterTypes = ['All', 'Core', 'Custom']
const filterType = ref('All')
const selectedExtensions = ref<Array<any>>([])
const filters = ref({
global: { value: '', matchMode: FilterMatchMode.CONTAINS }
})
const extensionStore = useExtensionStore()
const settingStore = useSettingStore()
const editingEnabledExtensions = ref<Record<string, boolean>>({})
const filteredExtensions = computed(() => {
const extensions = extensionStore.extensions
switch (filterType.value) {
case 'Core':
return extensions.filter((ext) =>
extensionStore.isCoreExtension(ext.name)
)
case 'Custom':
return extensions.filter(
(ext) => !extensionStore.isCoreExtension(ext.name)
)
default:
return extensions
}
})
onMounted(() => {
extensionStore.extensions.forEach((ext) => {
editingEnabledExtensions.value[ext.name] =
extensionStore.isExtensionEnabled(ext.name)
})
})
const changedExtensions = computed(() => {
return extensionStore.extensions.filter(
(ext) =>
editingEnabledExtensions.value[ext.name] !==
extensionStore.isExtensionEnabled(ext.name)
)
})
const hasChanges = computed(() => {
return changedExtensions.value.length > 0
})
const updateExtensionStatus = async () => {
const editingDisabledExtensionNames = Object.entries(
editingEnabledExtensions.value
)
.filter(([_, enabled]) => !enabled)
.map(([name]) => name)
await settingStore.set('Comfy.Extension.Disabled', [
...extensionStore.inactiveDisabledExtensionNames,
...editingDisabledExtensionNames
])
}
const enableAllExtensions = async () => {
extensionStore.extensions.forEach((ext) => {
if (extensionStore.isExtensionReadOnly(ext.name)) return
editingEnabledExtensions.value[ext.name] = true
})
await updateExtensionStatus()
}
const disableAllExtensions = async () => {
extensionStore.extensions.forEach((ext) => {
if (extensionStore.isExtensionReadOnly(ext.name)) return
editingEnabledExtensions.value[ext.name] = false
})
await updateExtensionStatus()
}
const disableThirdPartyExtensions = async () => {
extensionStore.extensions.forEach((ext) => {
if (extensionStore.isCoreExtension(ext.name)) return
editingEnabledExtensions.value[ext.name] = false
})
await updateExtensionStatus()
}
const applyChanges = () => {
// Refresh the page to apply changes
window.location.reload()
}
const menu = ref<InstanceType<typeof ContextMenu>>()
const contextMenuItems = [
{
label: 'Enable Selected',
icon: 'pi pi-check',
command: async () => {
selectedExtensions.value.forEach((ext) => {
if (!extensionStore.isExtensionReadOnly(ext.name)) {
editingEnabledExtensions.value[ext.name] = true
}
})
await updateExtensionStatus()
}
},
{
label: 'Disable Selected',
icon: 'pi pi-times',
command: async () => {
selectedExtensions.value.forEach((ext) => {
if (!extensionStore.isExtensionReadOnly(ext.name)) {
editingEnabledExtensions.value[ext.name] = false
}
})
await updateExtensionStatus()
}
},
{
separator: true
},
{
label: 'Enable All',
icon: 'pi pi-check',
command: enableAllExtensions
},
{
label: 'Disable All',
icon: 'pi pi-times',
command: disableAllExtensions
},
{
label: 'Disable 3rd Party',
icon: 'pi pi-times',
command: disableThirdPartyExtensions,
disabled: !extensionStore.hasThirdPartyExtensions
}
]
</script>

View File

@@ -1,127 +0,0 @@
<template>
<PanelTemplate value="Server-Config" class="server-config-panel">
<template #header>
<div class="flex flex-col gap-2">
<Message
v-if="modifiedConfigs.length > 0"
severity="info"
pt:text="w-full"
>
<p>
{{ $t('serverConfig.modifiedConfigs') }}
</p>
<ul>
<li v-for="config in modifiedConfigs" :key="config.id">
{{ config.name }}: {{ config.initialValue }} {{ config.value }}
</li>
</ul>
<div class="flex justify-end gap-2">
<Button
:label="$t('serverConfig.revertChanges')"
outlined
@click="revertChanges"
/>
<Button
:label="$t('serverConfig.restart')"
outlined
severity="danger"
@click="restartApp"
/>
</div>
</Message>
<Message v-if="commandLineArgs" severity="secondary" pt:text="w-full">
<template #icon>
<i-lucide:terminal class="text-xl font-bold" />
</template>
<div class="flex items-center justify-between">
<p>{{ commandLineArgs }}</p>
<Button
icon="pi pi-clipboard"
severity="secondary"
text
@click="copyCommandLineArgs"
/>
</div>
</Message>
</div>
</template>
<div
v-for="([label, items], i) in Object.entries(serverConfigsByCategory)"
:key="label"
>
<Divider v-if="i > 0" />
<h3>{{ $t(`serverConfigCategories.${label}`, label) }}</h3>
<div v-for="item in items" :key="item.name" class="mb-4">
<FormItem
:id="item.id"
v-model:formValue="item.value"
:item="translateItem(item)"
:label-class="{
'text-highlight': item.initialValue !== item.value
}"
/>
</div>
</div>
</PanelTemplate>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import Message from 'primevue/message'
import { watch } from 'vue'
import { useI18n } from 'vue-i18n'
import FormItem from '@/components/common/FormItem.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import type { ServerConfig } from '@/constants/serverConfig'
import { useServerConfigStore } from '@/stores/serverConfigStore'
import { useSettingStore } from '@/stores/settingStore'
import type { FormItem as FormItemType } from '@/types/settingTypes'
import { electronAPI } from '@/utils/envUtil'
import PanelTemplate from './PanelTemplate.vue'
const settingStore = useSettingStore()
const serverConfigStore = useServerConfigStore()
const {
serverConfigsByCategory,
serverConfigValues,
launchArgs,
commandLineArgs,
modifiedConfigs
} = storeToRefs(serverConfigStore)
const revertChanges = () => {
serverConfigStore.revertChanges()
}
const restartApp = async () => {
await electronAPI().restartApp()
}
watch(launchArgs, async (newVal) => {
await settingStore.set('Comfy.Server.LaunchArgs', newVal)
})
watch(serverConfigValues, async (newVal) => {
await settingStore.set('Comfy.Server.ServerConfigValues', newVal)
})
const { copyToClipboard } = useCopyToClipboard()
const copyCommandLineArgs = async () => {
await copyToClipboard(commandLineArgs.value)
}
const { t } = useI18n()
const translateItem = (item: ServerConfig<any>): FormItemType => {
return {
...item,
name: t(`serverConfigItems.${item.id}.name`, item.name),
tooltip: item.tooltip
? t(`serverConfigItems.${item.id}.tooltip`, item.tooltip)
: undefined
}
}
</script>

View File

@@ -1,33 +0,0 @@
<template>
<div class="setting-group">
<Divider v-if="divider" />
<h3>
{{
$t(`settingsCategories.${normalizeI18nKey(group.label)}`, group.label)
}}
</h3>
<div
v-for="setting in group.settings.filter((s) => !s.deprecated)"
:key="setting.id"
class="setting-item mb-4"
>
<SettingItem :setting="setting" />
</div>
</div>
</template>
<script setup lang="ts">
import Divider from 'primevue/divider'
import SettingItem from '@/components/dialog/content/setting/SettingItem.vue'
import { SettingParams } from '@/types/settingTypes'
import { normalizeI18nKey } from '@/utils/formatUtil'
defineProps<{
group: {
label: string
settings: SettingParams[]
}
divider?: boolean
}>()
</script>

View File

@@ -6,7 +6,7 @@ import Tooltip from 'primevue/tooltip'
import { describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import SettingItem from './SettingItem.vue'
import SettingItem from '@/platform/settings/components/SettingItem.vue'
const i18n = createI18n({
legacy: false,

View File

@@ -1,81 +0,0 @@
<template>
<FormItem
:id="setting.id"
:item="formItem"
:form-value="settingValue"
@update:form-value="updateSettingValue"
>
<template #name-prefix>
<Tag v-if="setting.id === 'Comfy.Locale'" class="pi pi-language" />
<Tag
v-if="setting.experimental"
v-tooltip="{
value: $t('g.experimental'),
showDelay: 600
}"
>
<template #icon>
<i-material-symbols:experiment-outline />
</template>
</Tag>
</template>
</FormItem>
</template>
<script setup lang="ts">
import Tag from 'primevue/tag'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import FormItem from '@/components/common/FormItem.vue'
import { st } from '@/i18n'
import { useSettingStore } from '@/stores/settingStore'
import type { SettingOption, SettingParams } from '@/types/settingTypes'
import { normalizeI18nKey } from '@/utils/formatUtil'
const props = defineProps<{
setting: SettingParams
}>()
const { t } = useI18n()
function translateOptions(options: (SettingOption | string)[]) {
if (typeof options === 'function') {
// @ts-expect-error: Audit and deprecate usage of legacy options type:
// (value) => [string | {text: string, value: string}]
return translateOptions(options(props.setting.value ?? ''))
}
return options.map((option) => {
const optionLabel = typeof option === 'string' ? option : option.text
const optionValue = typeof option === 'string' ? option : option.value
return {
text: t(
`settings.${normalizeI18nKey(props.setting.id)}.options.${normalizeI18nKey(optionLabel)}`,
optionLabel
),
value: optionValue
}
})
}
const formItem = computed(() => {
const normalizedId = normalizeI18nKey(props.setting.id)
return {
...props.setting,
name: t(`settings.${normalizedId}.name`, props.setting.name),
tooltip: props.setting.tooltip
? st(`settings.${normalizedId}.tooltip`, props.setting.tooltip)
: undefined,
options: props.setting.options
? translateOptions(props.setting.options)
: undefined
}
})
const settingStore = useSettingStore()
const settingValue = computed(() => settingStore.get(props.setting.id))
const updateSettingValue = async (value: any) => {
await settingStore.set(props.setting.id, value)
}
</script>

View File

@@ -1,27 +0,0 @@
<template>
<div v-if="props.settingGroups.length > 0">
<SettingGroup
v-for="(group, i) in props.settingGroups"
:key="group.label"
:divider="i !== 0"
:group="group"
/>
</div>
<NoResultsPlaceholder
v-else
icon="pi pi-search"
:title="$t('g.noResultsFound')"
:message="$t('g.searchFailedMessage')"
/>
</template>
<script setup lang="ts">
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { ISettingGroup } from '@/types/settingTypes'
import SettingGroup from './SettingGroup.vue'
const props = defineProps<{
settingGroups: ISettingGroup[]
}>()
</script>

View File

@@ -75,6 +75,7 @@ import { useI18n } from 'vue-i18n'
import DotSpinner from '@/components/common/DotSpinner.vue'
import { useConflictDetection } from '@/composables/useConflictDetection'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { api } from '@/scripts/api'
import { useComfyManagerService } from '@/services/comfyManagerService'
@@ -84,7 +85,6 @@ import {
} from '@/stores/comfyManagerStore'
import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useSettingStore } from '@/stores/settingStore'
const { t } = useI18n()
const dialogStore = useDialogStore()