Skip to content

Commit 43a9262

Browse files
committed
feat: add collapsed item support in configuration UI and update localization for more settings
1 parent ab66910 commit 43a9262

5 files changed

Lines changed: 204 additions & 66 deletions

File tree

astrbot/core/config/default.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3487,11 +3487,13 @@ class ChatProviderTemplate(TypedDict):
34873487
"provider_tts_settings.dual_output": {
34883488
"description": "开启 TTS 时同时输出语音和文字内容",
34893489
"type": "bool",
3490+
"collapsed": True,
34903491
},
34913492
"provider_settings.reachability_check": {
34923493
"description": "提供商可达性检测",
34933494
"type": "bool",
34943495
"hint": "/provider 命令列出模型时是否并发检测连通性。开启后会主动调用模型测试连通性,可能产生额外 token 消耗。",
3496+
"collapsed": True,
34953497
},
34963498
"provider_settings.max_quoted_fallback_images": {
34973499
"description": "引用图片回退解析上限",
@@ -3500,6 +3502,7 @@ class ChatProviderTemplate(TypedDict):
35003502
"condition": {
35013503
"provider_settings.agent_runner_type": "local",
35023504
},
3505+
"collapsed": True,
35033506
},
35043507
"provider_settings.quoted_message_parser.max_component_chain_depth": {
35053508
"description": "引用解析组件链深度",
@@ -3508,6 +3511,7 @@ class ChatProviderTemplate(TypedDict):
35083511
"condition": {
35093512
"provider_settings.agent_runner_type": "local",
35103513
},
3514+
"collapsed": True,
35113515
},
35123516
"provider_settings.quoted_message_parser.max_forward_node_depth": {
35133517
"description": "引用解析转发节点深度",
@@ -3516,6 +3520,7 @@ class ChatProviderTemplate(TypedDict):
35163520
"condition": {
35173521
"provider_settings.agent_runner_type": "local",
35183522
},
3523+
"collapsed": True,
35193524
},
35203525
"provider_settings.quoted_message_parser.max_forward_fetch": {
35213526
"description": "引用解析转发拉取上限",
@@ -3524,6 +3529,7 @@ class ChatProviderTemplate(TypedDict):
35243529
"condition": {
35253530
"provider_settings.agent_runner_type": "local",
35263531
},
3532+
"collapsed": True,
35273533
},
35283534
"provider_settings.quoted_message_parser.warn_on_action_failure": {
35293535
"description": "引用解析 action 失败告警",
@@ -3532,6 +3538,7 @@ class ChatProviderTemplate(TypedDict):
35323538
"condition": {
35333539
"provider_settings.agent_runner_type": "local",
35343540
},
3541+
"collapsed": True,
35353542
},
35363543
},
35373544
"condition": {

dashboard/src/components/shared/AstrBotConfigV4.vue

Lines changed: 191 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const props = defineProps({
2929
3030
const { t } = useI18n()
3131
const { tm, getRaw } = useModuleI18n('features/config-metadata')
32+
const { tm: tmConfig } = useModuleI18n('features/config')
3233
3334
const hintMarkdown = new MarkdownIt({
3435
linkify: true,
@@ -69,6 +70,7 @@ const getTranslatedLabels = (itemMeta) => {
6970
}
7071
7172
const dialog = ref(false)
73+
const showCollapsedItems = ref(false)
7274
const currentEditingKey = ref('')
7375
const currentEditingLanguage = ref('json')
7476
const currentEditingTheme = ref('vs-light')
@@ -152,6 +154,36 @@ function shouldShowItem(itemMeta, itemKey) {
152154
return searchableText.includes(keyword)
153155
}
154156
157+
function getVisibleItemEntries(collapsed = false) {
158+
const sectionItems = props.metadata?.[props.metadataKey]?.items || {}
159+
return Object.entries(sectionItems).filter(([itemKey, itemMeta]) => {
160+
const isCollapsed = Boolean(itemMeta?.collapsed)
161+
return isCollapsed === collapsed && shouldShowItem(itemMeta, itemKey)
162+
})
163+
}
164+
165+
function hasCollapsedItems() {
166+
return getVisibleItemEntries(true).length > 0
167+
}
168+
169+
function hasVisibleEntriesAfter(entries, currentIndex) {
170+
return currentIndex < entries.length - 1
171+
}
172+
173+
function areCollapsedItemsVisible() {
174+
if (!hasCollapsedItems()) {
175+
return false
176+
}
177+
if (String(props.searchKeyword || '').trim()) {
178+
return true
179+
}
180+
return showCollapsedItems.value
181+
}
182+
183+
function toggleCollapsedItems() {
184+
showCollapsedItems.value = !showCollapsedItems.value
185+
}
186+
155187
// 检查最外层的 object 是否应该显示
156188
function shouldShowSection() {
157189
const sectionMeta = props.metadata[props.metadataKey]
@@ -222,72 +254,153 @@ function getSpecialSubtype(value) {
222254
223255
<!-- Object Type Configuration with JSON Selector Support -->
224256
<div v-if="metadata[metadataKey]?.type === 'object'" class="object-config">
225-
<div v-for="(itemMeta, itemKey, index) in metadata[metadataKey].items" :key="itemKey" class="config-item">
226-
<!-- Check if itemKey is a JSON selector -->
227-
<template v-if="shouldShowItem(itemMeta, itemKey)">
228-
<!-- JSON Selector Property -->
229-
<v-row v-if="!itemMeta?.invisible" class="config-row">
230-
<v-col cols="12" sm="6" class="property-info">
231-
<v-list-item density="compact">
232-
<v-list-item-title class="property-name">
233-
{{ translateIfKey(itemMeta?.description) || itemKey }}
234-
<span class="property-key">({{ itemKey }})</span>
235-
</v-list-item-title>
236-
237-
<v-list-item-subtitle class="property-hint">
238-
<span v-if="itemMeta?.obvious_hint && itemMeta?.hint" class="important-hint">‼️</span>
239-
<span v-html="renderHint(itemMeta?.hint)"></span>
240-
</v-list-item-subtitle>
241-
</v-list-item>
242-
</v-col>
243-
<v-col cols="12" sm="6" class="config-input">
244-
<TemplateListEditor
245-
v-if="itemMeta?.type === 'template_list'"
246-
v-model="createSelectorModel(itemKey).value"
247-
:templates="itemMeta?.templates || {}"
248-
class="config-field"
249-
/>
250-
<ConfigItemRenderer
251-
v-else
252-
v-model="createSelectorModel(itemKey).value"
253-
:item-meta="itemMeta || null"
254-
:show-fullscreen-btn="!!itemMeta?.editor_mode"
255-
@open-fullscreen="openEditorDialog(itemKey, iterable, itemMeta?.editor_theme, itemMeta?.editor_language)"
256-
/>
257-
</v-col>
258-
</v-row>
259-
260-
<!-- Plugin Set Selector 全宽显示区域 -->
261-
<v-row v-if="!itemMeta?.invisible && itemMeta?._special === 'select_plugin_set'"
262-
class="plugin-set-display-row">
263-
<v-col cols="12" class="plugin-set-display">
264-
<div v-if="createSelectorModel(itemKey).value && createSelectorModel(itemKey).value.length > 0"
265-
class="selected-plugins-full-width">
266-
<div class="plugins-header">
267-
<small class="text-grey">{{ t('core.shared.pluginSetSelector.selectedPluginsLabel') }}</small>
268-
</div>
269-
<div class="d-flex flex-wrap ga-2 mt-2">
270-
<v-chip v-for="plugin in (createSelectorModel(itemKey).value || [])" :key="plugin" size="small" label
271-
color="primary" variant="outlined">
272-
{{ plugin === '*' ? t('core.shared.pluginSetSelector.allPluginsLabel') : plugin }}
273-
</v-chip>
274-
</div>
257+
<div
258+
v-for="([itemKey, itemMeta], index) in getVisibleItemEntries(false)"
259+
:key="itemKey"
260+
class="config-item"
261+
>
262+
<v-row v-if="!itemMeta?.invisible" class="config-row">
263+
<v-col cols="12" sm="6" class="property-info">
264+
<v-list-item density="compact">
265+
<v-list-item-title class="property-name">
266+
{{ translateIfKey(itemMeta?.description) || itemKey }}
267+
<span class="property-key">({{ itemKey }})</span>
268+
</v-list-item-title>
269+
270+
<v-list-item-subtitle class="property-hint">
271+
<span v-if="itemMeta?.obvious_hint && itemMeta?.hint" class="important-hint">‼️</span>
272+
<span v-html="renderHint(itemMeta?.hint)"></span>
273+
</v-list-item-subtitle>
274+
</v-list-item>
275+
</v-col>
276+
<v-col cols="12" sm="6" class="config-input">
277+
<TemplateListEditor
278+
v-if="itemMeta?.type === 'template_list'"
279+
v-model="createSelectorModel(itemKey).value"
280+
:templates="itemMeta?.templates || {}"
281+
class="config-field"
282+
/>
283+
<ConfigItemRenderer
284+
v-else
285+
v-model="createSelectorModel(itemKey).value"
286+
:item-meta="itemMeta || null"
287+
:show-fullscreen-btn="!!itemMeta?.editor_mode"
288+
@open-fullscreen="openEditorDialog(itemKey, iterable, itemMeta?.editor_theme, itemMeta?.editor_language)"
289+
/>
290+
</v-col>
291+
</v-row>
292+
293+
<v-row v-if="!itemMeta?.invisible && itemMeta?._special === 'select_plugin_set'"
294+
class="plugin-set-display-row">
295+
<v-col cols="12" class="plugin-set-display">
296+
<div v-if="createSelectorModel(itemKey).value && createSelectorModel(itemKey).value.length > 0"
297+
class="selected-plugins-full-width">
298+
<div class="plugins-header">
299+
<small class="text-grey">{{ t('core.shared.pluginSetSelector.selectedPluginsLabel') }}</small>
300+
</div>
301+
<div class="d-flex flex-wrap ga-2 mt-2">
302+
<v-chip v-for="plugin in (createSelectorModel(itemKey).value || [])" :key="plugin" size="small" label
303+
color="primary" variant="outlined">
304+
{{ plugin === '*' ? t('core.shared.pluginSetSelector.allPluginsLabel') : plugin }}
305+
</v-chip>
275306
</div>
276-
</v-col>
277-
</v-row>
307+
</div>
308+
</v-col>
309+
</v-row>
310+
311+
<v-row
312+
v-if="!itemMeta?.invisible && itemMeta?._special === 'select_persona' && itemKey === 'provider_settings.default_personality'"
313+
class="persona-preview-row"
314+
>
315+
<v-col cols="12" class="persona-preview-display">
316+
<PersonaQuickPreview :model-value="createSelectorModel(itemKey).value" />
317+
</v-col>
318+
</v-row>
278319
279-
<!-- Default Persona Quick Preview 全宽显示区域 -->
280-
<v-row
281-
v-if="!itemMeta?.invisible && itemMeta?._special === 'select_persona' && itemKey === 'provider_settings.default_personality'"
282-
class="persona-preview-row"
283-
>
284-
<v-col cols="12" class="persona-preview-display">
285-
<PersonaQuickPreview :model-value="createSelectorModel(itemKey).value" />
286-
</v-col>
287-
</v-row>
288-
</template>
289320
<v-divider class="config-divider"
290-
v-if="shouldShowItem(itemMeta, itemKey) && hasVisibleItemsAfter(metadata[metadataKey].items, index)"></v-divider>
321+
v-if="hasVisibleEntriesAfter(getVisibleItemEntries(false), index)"></v-divider>
322+
</div>
323+
324+
<div v-if="hasCollapsedItems()" class="collapsed-config-section">
325+
<div class="collapsed-config-toggle-row">
326+
<span
327+
class="collapsed-config-toggle"
328+
@click="toggleCollapsedItems"
329+
>
330+
{{ tmConfig('sections.moreConfig') }}
331+
<v-icon end size="18">
332+
{{ areCollapsedItemsVisible() ? 'mdi-chevron-up' : 'mdi-chevron-down' }}
333+
</v-icon>
334+
</span>
335+
</div>
336+
<div v-if="areCollapsedItemsVisible()">
337+
<div
338+
v-for="([itemKey, itemMeta], index) in getVisibleItemEntries(true)"
339+
:key="itemKey"
340+
class="config-item"
341+
>
342+
<v-row v-if="!itemMeta?.invisible" class="config-row">
343+
<v-col cols="12" sm="6" class="property-info">
344+
<v-list-item density="compact">
345+
<v-list-item-title class="property-name">
346+
{{ translateIfKey(itemMeta?.description) || itemKey }}
347+
<span class="property-key">({{ itemKey }})</span>
348+
</v-list-item-title>
349+
350+
<v-list-item-subtitle class="property-hint">
351+
<span v-if="itemMeta?.obvious_hint && itemMeta?.hint" class="important-hint">‼️</span>
352+
<span v-html="renderHint(itemMeta?.hint)"></span>
353+
</v-list-item-subtitle>
354+
</v-list-item>
355+
</v-col>
356+
<v-col cols="12" sm="6" class="config-input">
357+
<TemplateListEditor
358+
v-if="itemMeta?.type === 'template_list'"
359+
v-model="createSelectorModel(itemKey).value"
360+
:templates="itemMeta?.templates || {}"
361+
class="config-field"
362+
/>
363+
<ConfigItemRenderer
364+
v-else
365+
v-model="createSelectorModel(itemKey).value"
366+
:item-meta="itemMeta || null"
367+
:show-fullscreen-btn="!!itemMeta?.editor_mode"
368+
@open-fullscreen="openEditorDialog(itemKey, iterable, itemMeta?.editor_theme, itemMeta?.editor_language)"
369+
/>
370+
</v-col>
371+
</v-row>
372+
373+
<v-row v-if="!itemMeta?.invisible && itemMeta?._special === 'select_plugin_set'"
374+
class="plugin-set-display-row">
375+
<v-col cols="12" class="plugin-set-display">
376+
<div v-if="createSelectorModel(itemKey).value && createSelectorModel(itemKey).value.length > 0"
377+
class="selected-plugins-full-width">
378+
<div class="plugins-header">
379+
<small class="text-grey">{{ t('core.shared.pluginSetSelector.selectedPluginsLabel') }}</small>
380+
</div>
381+
<div class="d-flex flex-wrap ga-2 mt-2">
382+
<v-chip v-for="plugin in (createSelectorModel(itemKey).value || [])" :key="plugin" size="small" label
383+
color="primary" variant="outlined">
384+
{{ plugin === '*' ? t('core.shared.pluginSetSelector.allPluginsLabel') : plugin }}
385+
</v-chip>
386+
</div>
387+
</div>
388+
</v-col>
389+
</v-row>
390+
391+
<v-row
392+
v-if="!itemMeta?.invisible && itemMeta?._special === 'select_persona' && itemKey === 'provider_settings.default_personality'"
393+
class="persona-preview-row"
394+
>
395+
<v-col cols="12" class="persona-preview-display">
396+
<PersonaQuickPreview :model-value="createSelectorModel(itemKey).value" />
397+
</v-col>
398+
</v-row>
399+
400+
<v-divider class="config-divider"
401+
v-if="hasVisibleEntriesAfter(getVisibleItemEntries(true), index)"></v-divider>
402+
</div>
403+
</div>
291404
</div>
292405
293406
</div>
@@ -460,6 +573,21 @@ function getSpecialSubtype(value) {
460573
padding: 12px;
461574
}
462575
576+
.collapsed-config-section {
577+
width: 100%;
578+
}
579+
580+
.collapsed-config-toggle-row {
581+
padding: 8px 8px 4px;
582+
}
583+
584+
.collapsed-config-toggle {
585+
min-height: auto;
586+
cursor: pointer;
587+
margin-left: 16px;
588+
font-size: 0.875rem;
589+
}
590+
463591
.plugins-header {
464592
margin-bottom: 4px;
465593
}

dashboard/src/i18n/locales/en-US/features/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"advanced": "Advanced Settings",
4343
"security": "Security Settings",
4444
"appearance": "Appearance Settings",
45-
"notification": "Notification Settings"
45+
"notification": "Notification Settings",
46+
"moreConfig": "More Settings"
4647
},
4748
"general": {
4849
"botName": "Bot Name",

dashboard/src/i18n/locales/ru-RU/features/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"advanced": "Расширенные",
4343
"security": "Безопасность",
4444
"appearance": "Внешний вид",
45-
"notification": "Уведомления"
45+
"notification": "Уведомления",
46+
"moreConfig": "Дополнительные настройки"
4647
},
4748
"general": {
4849
"botName": "Имя бота",

dashboard/src/i18n/locales/zh-CN/features/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"advanced": "高级设置",
4343
"security": "安全设置",
4444
"appearance": "外观设置",
45-
"notification": "通知设置"
45+
"notification": "通知设置",
46+
"moreConfig": "更多配置"
4647
},
4748
"general": {
4849
"botName": "机器人名称",

0 commit comments

Comments
 (0)