Skip to content

Commit 90a3a21

Browse files
authored
fix: Template config optimization (#8228)
* fix: improve template list config handling * feat(webui): show template list display item * feat(webui): allow hiding template list hints * docs: document template list metadata fields * fix: support file fields in template list configs
1 parent 0e973bd commit 90a3a21

7 files changed

Lines changed: 230 additions & 13 deletions

File tree

astrbot/dashboard/routes/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def _validate_template_list(value, meta, path_key, errors, validate_fn) -> None:
9595
validate_fn(
9696
item,
9797
template_meta.get("items", {}),
98-
path=f"{item_path}.",
98+
path=f"{path_key}.templates.{template_key}.",
9999
)
100100

101101

astrbot/dashboard/routes/util.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,39 @@ def get_schema_item(schema: dict | None, key_path: str) -> dict | None:
1616
同时支持:
1717
- 扁平 schema(直接 key 命中)
1818
- 嵌套 object schema({type: "object", items: {...}})
19+
- template_list schema(<field>.templates.<template>.items)
1920
"""
2021

2122
if not isinstance(schema, dict) or not key_path:
2223
return None
2324
if key_path in schema:
2425
return schema.get(key_path)
2526

26-
current = schema
2727
parts = key_path.split(".")
28-
for idx, part in enumerate(parts):
28+
current = schema
29+
idx = 0
30+
while idx < len(parts):
31+
part = parts[idx]
2932
if part not in current:
3033
return None
3134
meta = current.get(part)
3235
if idx == len(parts) - 1:
3336
return meta
3437
if not isinstance(meta, dict) or meta.get("type") != "object":
35-
return None
38+
if not isinstance(meta, dict) or meta.get("type") != "template_list":
39+
return None
40+
if idx + 2 >= len(parts) or parts[idx + 1] != "templates":
41+
return None
42+
template_meta = meta.get("templates", {}).get(parts[idx + 2])
43+
if not isinstance(template_meta, dict):
44+
return None
45+
if idx + 2 == len(parts) - 1:
46+
return template_meta
47+
current = template_meta.get("items", {})
48+
idx += 3
49+
continue
3650
current = meta.get("items", {})
51+
idx += 1
3752
return None
3853

3954

dashboard/src/components/shared/AstrBotConfigV4.vue

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,25 @@ const props = defineProps({
2525
searchKeyword: {
2626
type: String,
2727
default: ''
28+
},
29+
pluginName: {
30+
type: String,
31+
default: ''
32+
},
33+
pluginI18n: {
34+
type: Object,
35+
default: () => ({})
36+
},
37+
pathPrefix: {
38+
type: String,
39+
default: ''
2840
}
2941
})
3042
3143
const { t } = useI18n()
3244
const { getRaw } = useModuleI18n('features/config-metadata')
3345
const { tm: tmConfig } = useModuleI18n('features/config')
34-
const { translateIfKey } = useConfigTextResolver()
46+
const { translateIfKey, resolveConfigText } = useConfigTextResolver(props)
3547
3648
const hintMarkdown = new MarkdownIt({
3749
linkify: true,
@@ -114,6 +126,18 @@ function createSelectorModel(selector) {
114126
})
115127
}
116128
129+
function getItemPath(key) {
130+
return props.pathPrefix ? `${props.pathPrefix}.${key}` : key
131+
}
132+
133+
function getItemDescription(itemKey, itemMeta) {
134+
return resolveConfigText(getItemPath(itemKey), 'description', itemMeta?.description) || itemKey
135+
}
136+
137+
function getItemHint(itemKey, itemMeta) {
138+
return resolveConfigText(getItemPath(itemKey), 'hint', itemMeta?.hint)
139+
}
140+
117141
function openEditorDialog(key, value, theme, language) {
118142
currentEditingKey.value = key
119143
currentEditingLanguage.value = language || 'json'
@@ -143,8 +167,8 @@ function shouldShowItem(itemMeta, itemKey) {
143167
144168
const searchableText = [
145169
itemKey,
146-
translateIfKey(itemMeta?.description || ''),
147-
translateIfKey(itemMeta?.hint || '')
170+
getItemDescription(itemKey, itemMeta),
171+
getItemHint(itemKey, itemMeta)
148172
].join(' ').toLowerCase()
149173
150174
return searchableText.includes(keyword)
@@ -259,13 +283,13 @@ function getSpecialSubtype(value) {
259283
<v-col cols="12" sm="6" class="property-info">
260284
<v-list-item density="compact">
261285
<v-list-item-title class="property-name">
262-
{{ translateIfKey(itemMeta?.description) || itemKey }}
286+
{{ getItemDescription(itemKey, itemMeta) }}
263287
<span class="property-key">({{ itemKey }})</span>
264288
</v-list-item-title>
265289
266290
<v-list-item-subtitle class="property-hint">
267291
<span v-if="itemMeta?.obvious_hint && itemMeta?.hint" class="important-hint">‼️</span>
268-
<span v-html="renderHint(itemMeta?.hint)"></span>
292+
<span v-html="renderHint(getItemHint(itemKey, itemMeta))"></span>
269293
</v-list-item-subtitle>
270294
</v-list-item>
271295
</v-col>
@@ -274,12 +298,18 @@ function getSpecialSubtype(value) {
274298
v-if="itemMeta?.type === 'template_list'"
275299
v-model="createSelectorModel(itemKey).value"
276300
:templates="itemMeta?.templates || {}"
301+
:plugin-name="pluginName"
302+
:plugin-i18n="pluginI18n"
303+
:config-path="getItemPath(itemKey)"
277304
class="config-field"
278305
/>
279306
<ConfigItemRenderer
280307
v-else
281308
v-model="createSelectorModel(itemKey).value"
282309
:item-meta="itemMeta || null"
310+
:plugin-name="pluginName"
311+
:plugin-i18n="pluginI18n"
312+
:config-key="getItemPath(itemKey)"
283313
:show-fullscreen-btn="!!itemMeta?.editor_mode"
284314
@open-fullscreen="openEditorDialog(itemKey, iterable, itemMeta?.editor_theme, itemMeta?.editor_language)"
285315
/>
@@ -339,13 +369,13 @@ function getSpecialSubtype(value) {
339369
<v-col cols="12" sm="6" class="property-info">
340370
<v-list-item density="compact">
341371
<v-list-item-title class="property-name">
342-
{{ translateIfKey(itemMeta?.description) || itemKey }}
372+
{{ getItemDescription(itemKey, itemMeta) }}
343373
<span class="property-key">({{ itemKey }})</span>
344374
</v-list-item-title>
345375
346376
<v-list-item-subtitle class="property-hint">
347377
<span v-if="itemMeta?.obvious_hint && itemMeta?.hint" class="important-hint">‼️</span>
348-
<span v-html="renderHint(itemMeta?.hint)"></span>
378+
<span v-html="renderHint(getItemHint(itemKey, itemMeta))"></span>
349379
</v-list-item-subtitle>
350380
</v-list-item>
351381
</v-col>
@@ -354,12 +384,18 @@ function getSpecialSubtype(value) {
354384
v-if="itemMeta?.type === 'template_list'"
355385
v-model="createSelectorModel(itemKey).value"
356386
:templates="itemMeta?.templates || {}"
387+
:plugin-name="pluginName"
388+
:plugin-i18n="pluginI18n"
389+
:config-path="getItemPath(itemKey)"
357390
class="config-field"
358391
/>
359392
<ConfigItemRenderer
360393
v-else
361394
v-model="createSelectorModel(itemKey).value"
362395
:item-meta="itemMeta || null"
396+
:plugin-name="pluginName"
397+
:plugin-i18n="pluginI18n"
398+
:config-key="getItemPath(itemKey)"
363399
:show-fullscreen-btn="!!itemMeta?.editor_mode"
364400
@open-fullscreen="openEditorDialog(itemKey, iterable, itemMeta?.editor_theme, itemMeta?.editor_language)"
365401
/>

dashboard/src/components/shared/TemplateListEditor.vue

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@
5757
</v-btn>
5858
<div class="d-flex flex-column">
5959
<v-list-item-title class="property-name">{{ templateLabel(entry.__template_key) }}</v-list-item-title>
60-
<v-list-item-subtitle class="property-hint" v-if="getTemplate(entry)?.hint || getTemplate(entry)?.description">
61-
{{ templateText(entry.__template_key, 'hint', getTemplate(entry)?.hint || getTemplate(entry)?.description) }}
60+
<v-list-item-subtitle class="property-hint entry-display-text" v-if="templateDisplayText(entry)">
61+
{{ templateDisplayText(entry) }}
62+
</v-list-item-subtitle>
63+
<v-list-item-subtitle class="property-hint" v-if="templateHintText(entry)">
64+
{{ templateHintText(entry) }}
6265
</v-list-item-subtitle>
6366
</div>
6467
</div>
@@ -201,6 +204,7 @@ const defaultValueMap = {
201204
string: '',
202205
text: '',
203206
list: [],
207+
file: [],
204208
object: {},
205209
template_list: []
206210
}
@@ -348,6 +352,49 @@ function getTemplate(entry) {
348352
return props.templates?.[key] || null
349353
}
350354
355+
function templateHintText(entry) {
356+
const template = getTemplate(entry)
357+
if (!template || template.hide_hint_in_list) return ''
358+
return templateText(entry.__template_key, 'hint', template.hint || template.description || '')
359+
}
360+
361+
function getItemMetaBySelector(itemsMeta = {}, selector = '') {
362+
const keys = selector.split('.').filter(Boolean)
363+
let currentItems = itemsMeta
364+
let currentMeta = null
365+
366+
for (let i = 0; i < keys.length; i++) {
367+
currentMeta = currentItems?.[keys[i]]
368+
if (!currentMeta) return null
369+
if (i < keys.length - 1) {
370+
if (currentMeta.type !== 'object') return null
371+
currentItems = currentMeta.items || {}
372+
}
373+
}
374+
375+
return currentMeta
376+
}
377+
378+
function templateDisplayText(entry) {
379+
const template = getTemplate(entry)
380+
const displayItem = template?.display_item
381+
if (!template || typeof displayItem !== 'string' || !displayItem) return ''
382+
383+
const displayMeta = getItemMetaBySelector(template.items || {}, displayItem)
384+
if (displayMeta?.type !== 'string') return ''
385+
386+
const value = getValueBySelector(entry, displayItem)
387+
if (typeof value !== 'string' || !value.trim()) return ''
388+
389+
const label = templateItemText(
390+
entry.__template_key,
391+
displayItem,
392+
'description',
393+
displayMeta.description || displayItem,
394+
)
395+
return `${label}: ${value.trim()}`
396+
}
397+
351398
function getValueBySelector(obj, selector) {
352399
const keys = selector.split('.')
353400
let current = obj
@@ -450,6 +497,11 @@ function hasVisibleItemsAfter(entries, currentIndex, entry) {
450497
margin-top: 2px;
451498
}
452499
500+
.entry-display-text {
501+
color: var(--v-theme-primary);
502+
font-weight: 500;
503+
}
504+
453505
.property-key {
454506
font-size: 0.85em;
455507
opacity: 0.7;

docs/en/dev/star/guides/plugin-config.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,14 @@ Plugin developers can add a template-style configuration to `_conf_schema` in th
146146
"template_1": {
147147
"name": "Template One",
148148
"hint":"hint",
149+
"display_item": "attr_name",
150+
"hide_hint_in_list": true,
149151
"items": {
152+
"attr_name": {
153+
"description": "Attribute Name",
154+
"type": "string",
155+
"default": ""
156+
},
150157
"attr_a": {
151158
"description": "Attribute A",
152159
"type": "int",
@@ -187,6 +194,7 @@ Saved config example:
187194
"field_id": [
188195
{
189196
"__template_key": "template_1",
197+
"attr_name": "",
190198
"attr_a": 10,
191199
"attr_b": true
192200
},
@@ -198,6 +206,11 @@ Saved config example:
198206
]
199207
```
200208

209+
Templates also support these optional fields:
210+
211+
- `display_item`: Specifies the key of a `string` item inside the template `items`. When set, the WebUI shows that field's current value in the collapsed list of added template entries, for example `Attribute Name: my-adapter`, making it easier to distinguish multiple entries created from the same template. Dot paths are supported for fields inside nested objects, for example `meta.name`.
212+
- `hide_hint_in_list`: When set to `true`, the WebUI hides the template `hint` in the collapsed list of added template entries. The template selection dropdown still shows the `hint`, and hints for fields inside the expanded entry are not affected.
213+
201214
<img width="1000" alt="image" src="https://github.com/user-attachments/assets/74876d30-11a4-491b-a7a0-8ebe8d603782" />
202215

203216

docs/zh/dev/star/guides/plugin-config.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,14 @@ AstrBot 提供了“强大”的配置解析和可视化功能。能够让用户
146146
"template_1": {
147147
"name": "Template One",
148148
"hint":"hint",
149+
"display_item": "attr_name",
150+
"hide_hint_in_list": true,
149151
"items": {
152+
"attr_name": {
153+
"description": "Attribute Name",
154+
"type": "string",
155+
"default": ""
156+
},
150157
"attr_a": {
151158
"description": "Attribute A",
152159
"type": "int",
@@ -187,6 +194,7 @@ AstrBot 提供了“强大”的配置解析和可视化功能。能够让用户
187194
"field_id": [
188195
{
189196
"__template_key": "template_1",
197+
"attr_name": "",
190198
"attr_a": 10,
191199
"attr_b": true
192200
},
@@ -198,6 +206,11 @@ AstrBot 提供了“强大”的配置解析和可视化功能。能够让用户
198206
]
199207
```
200208

209+
模板本身还支持以下可选字段:
210+
211+
- `display_item`: 指定模板 `items` 中一个 `string` 类型字段的 key。设置后,WebUI 会在已添加模板条目的折叠列表中显示该字段当前值,例如 `Attribute Name: my-adapter`,便于添加多个同类型模板时快速区分。支持用点号选择嵌套 object 中的字段,例如 `meta.name`
212+
- `hide_hint_in_list`: 设置为 `true` 时,WebUI 会在已添加模板条目的折叠列表中隐藏该模板的 `hint`。添加模板时的下拉菜单仍会显示 `hint`,展开条目后各配置项自己的 `hint` 也不受影响。
213+
201214
<img width="1000" alt="image" src="https://github.com/user-attachments/assets/74876d30-11a4-491b-a7a0-8ebe8d603782" />
202215

203216
## 在插件中使用配置

0 commit comments

Comments
 (0)