Skip to content

Commit 6e22d26

Browse files
authored
feat: implement search functionality in configuration components and update UI (#5168)
1 parent 4c285fb commit 6e22d26

5 files changed

Lines changed: 146 additions & 23 deletions

File tree

dashboard/src/components/config/AstrBotCoreConfigWrapper.vue

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22
<div :class="$vuetify.display.mobile ? '' : 'd-flex'">
33
<v-tabs v-model="tab" :direction="$vuetify.display.mobile ? 'horizontal' : 'vertical'"
44
:align-tabs="$vuetify.display.mobile ? 'left' : 'start'" color="deep-purple-accent-4" class="config-tabs">
5-
<v-tab v-for="(val, key, index) in metadata" :key="index" :value="index"
5+
<v-tab v-for="section in visibleSections" :key="section.key" :value="section.key"
66
style="font-weight: 1000; font-size: 15px">
7-
{{ tm(metadata[key]['name']) }}
7+
{{ tm(section.value['name']) }}
88
</v-tab>
99
</v-tabs>
1010
<v-tabs-window v-model="tab" class="config-tabs-window" :style="readonly ? 'pointer-events: none; opacity: 0.6;' : ''">
11-
<v-tabs-window-item v-for="(val, key, index) in metadata" v-show="index == tab" :key="index">
11+
<v-tabs-window-item v-for="section in visibleSections" :key="section.key" :value="section.key">
1212
<v-container fluid>
13-
<div v-for="(val2, key2, index2) in metadata[key]['metadata']" :key="key2">
13+
<div v-for="(val2, key2, index2) in section.value['metadata']" :key="key2">
1414
<!-- Support both traditional and JSON selector metadata -->
15-
<AstrBotConfigV4 :metadata="{ [key2]: metadata[key]['metadata'][key2] }" :iterable="config_data"
16-
:metadataKey="key2">
15+
<AstrBotConfigV4
16+
:metadata="{ [key2]: section.value['metadata'][key2] }"
17+
:iterable="config_data"
18+
:metadataKey="key2"
19+
:search-keyword="searchKeyword"
20+
>
1721
</AstrBotConfigV4>
1822
</div>
1923
</v-container>
@@ -31,6 +35,11 @@
3135

3236
</v-tabs-window>
3337
</div>
38+
<v-container v-if="visibleSections.length === 0" fluid class="px-0">
39+
<v-alert type="info" variant="tonal">
40+
{{ tm('search.noResult') }}
41+
</v-alert>
42+
</v-container>
3443
</template>
3544

3645
<script>
@@ -56,6 +65,10 @@ export default {
5665
readonly: {
5766
type: Boolean,
5867
default: false
68+
},
69+
searchKeyword: {
70+
type: String,
71+
default: ''
5972
}
6073
},
6174
setup() {
@@ -76,11 +89,63 @@ export default {
7689
},
7790
data() {
7891
return {
79-
tab: 0, // 用于切换配置标签页
92+
tab: null, // 当前激活的配置标签页 key
93+
}
94+
},
95+
computed: {
96+
normalizedSearchKeyword() {
97+
return String(this.searchKeyword || '').trim().toLowerCase();
98+
},
99+
visibleSections() {
100+
if (!this.metadata || typeof this.metadata !== 'object') {
101+
return [];
102+
}
103+
const allSections = Object.entries(this.metadata).map(([key, value]) => ({ key, value }));
104+
if (!this.normalizedSearchKeyword) {
105+
return allSections;
106+
}
107+
return allSections.filter((section) => this.sectionHasSearchMatch(section.value));
80108
}
81109
},
110+
watch: {
111+
visibleSections(newSections) {
112+
const sectionKeys = newSections.map((section) => section.key);
113+
if (!sectionKeys.includes(this.tab)) {
114+
this.tab = sectionKeys[0] ?? null;
115+
}
116+
}
117+
},
118+
mounted() {
119+
const sectionKeys = this.visibleSections.map((section) => section.key);
120+
this.tab = sectionKeys[0] ?? null;
121+
},
82122
methods: {
83-
// 如果需要添加其他方法,可以在这里添加
123+
sectionHasSearchMatch(section) {
124+
const keyword = this.normalizedSearchKeyword;
125+
if (!keyword) {
126+
return true;
127+
}
128+
const sectionMetadata = section?.metadata || {};
129+
return Object.values(sectionMetadata).some((metaItem) => this.metaObjectHasSearchMatch(metaItem, keyword));
130+
},
131+
metaObjectHasSearchMatch(metaObject, keyword) {
132+
if (!metaObject || typeof metaObject !== 'object') {
133+
return false;
134+
}
135+
const target = [
136+
this.tm(metaObject.description || ''),
137+
this.tm(metaObject.hint || ''),
138+
...Object.entries(metaObject.items || {}).flatMap(([itemKey, itemMeta]) => ([
139+
itemKey,
140+
this.tm(itemMeta?.description || ''),
141+
this.tm(itemMeta?.hint || '')
142+
]))
143+
]
144+
.join(' ')
145+
.toLowerCase();
146+
147+
return target.includes(keyword);
148+
}
84149
}
85150
}
86151
</script>
@@ -112,4 +177,4 @@ export default {
112177
margin-top: 16px;
113178
}
114179
}
115-
</style>
180+
</style>

dashboard/src/components/shared/AstrBotConfigV4.vue

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const props = defineProps({
1919
metadataKey: {
2020
type: String,
2121
required: true
22+
},
23+
searchKeyword: {
24+
type: String,
25+
default: ''
2226
}
2327
})
2428
@@ -124,16 +128,27 @@ function saveEditedContent() {
124128
}
125129
126130
function shouldShowItem(itemMeta, itemKey) {
127-
if (!itemMeta?.condition) {
128-
return true
129-
}
130-
for (const [conditionKey, expectedValue] of Object.entries(itemMeta.condition)) {
131-
const actualValue = getValueBySelector(props.iterable, conditionKey)
132-
if (actualValue !== expectedValue) {
133-
return false
131+
if (itemMeta?.condition) {
132+
for (const [conditionKey, expectedValue] of Object.entries(itemMeta.condition)) {
133+
const actualValue = getValueBySelector(props.iterable, conditionKey)
134+
if (actualValue !== expectedValue) {
135+
return false
136+
}
134137
}
135138
}
136-
return true
139+
140+
const keyword = String(props.searchKeyword || '').trim().toLowerCase()
141+
if (!keyword) {
142+
return true
143+
}
144+
145+
const searchableText = [
146+
itemKey,
147+
translateIfKey(itemMeta?.description || ''),
148+
translateIfKey(itemMeta?.hint || '')
149+
].join(' ').toLowerCase()
150+
151+
return searchableText.includes(keyword)
137152
}
138153
139154
// 检查最外层的 object 是否应该显示
@@ -148,7 +163,10 @@ function shouldShowSection() {
148163
return false
149164
}
150165
}
151-
return true
166+
167+
const sectionItems = props.metadata?.[props.metadataKey]?.items || {}
168+
const hasVisibleItems = Object.entries(sectionItems).some(([itemKey, itemMeta]) => shouldShowItem(itemMeta, itemKey))
169+
return hasVisibleItems
152170
}
153171
154172
function hasVisibleItemsAfter(items, currentIndex) {
@@ -436,9 +454,13 @@ function getSpecialSubtype(value) {
436454
}
437455
438456
.property-info,
439-
.type-indicator,
457+
.type-indicator {
458+
padding: 4px 8px;
459+
}
460+
440461
.config-input {
441-
padding: 4px;
462+
padding-left: 24px;
463+
padding-right: 24px;
442464
}
443465
}
444466
</style>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
"normalConfig": "Basic",
7070
"systemConfig": "System"
7171
},
72+
"search": {
73+
"placeholder": "Search config items (key/description/hint)",
74+
"noResult": "No matching config items found"
75+
},
7276
"configManagement": {
7377
"title": "Configuration Management",
7478
"description": "AstrBot supports separate configuration files for different bots. The `default` configuration is used by default.",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
"normalConfig": "普通",
7070
"systemConfig": "系统"
7171
},
72+
"search": {
73+
"placeholder": "搜索配置项(字段名/描述/提示)",
74+
"noResult": "未找到匹配的配置项"
75+
},
7276
"configManagement": {
7377
"title": "配置文件管理",
7478
"description": "AstrBot 支持针对不同机器人分别设置配置文件。默认会使用 `default` 配置。",

dashboard/src/views/ConfigPage.vue

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@
44
<div v-if="selectedConfigID || isSystemConfig" class="mt-4 config-panel"
55
style="display: flex; flex-direction: column; align-items: start;">
66

7-
<div class="d-flex flex-row pr-4"
7+
<div class="config-toolbar d-flex flex-row pr-4"
88
style="margin-bottom: 16px; align-items: center; gap: 12px; width: 100%; justify-content: space-between;">
9-
<div class="d-flex flex-row align-center" style="gap: 12px;">
10-
<v-select style="min-width: 130px;" v-model="selectedConfigID" :items="configSelectItems" item-title="name" :disabled="initialConfigId !== null"
9+
<div class="config-toolbar-controls d-flex flex-row align-center" style="gap: 12px;">
10+
<v-select class="config-select" style="min-width: 130px;" v-model="selectedConfigID" :items="configSelectItems" item-title="name" :disabled="initialConfigId !== null"
1111
v-if="!isSystemConfig" item-value="id" :label="tm('configSelection.selectConfig')" hide-details density="compact" rounded="md"
1212
variant="outlined" @update:model-value="onConfigSelect">
1313
</v-select>
14+
<v-text-field
15+
class="config-search-input"
16+
v-model="configSearchKeyword"
17+
prepend-inner-icon="mdi-magnify"
18+
:label="tm('search.placeholder')"
19+
hide-details
20+
density="compact"
21+
rounded="md"
22+
variant="outlined"
23+
style="min-width: 280px;"
24+
/>
1425
<!-- <a style="color: inherit;" href="https://blog.astrbot.app/posts/what-is-changed-in-4.0.0/#%E5%A4%9A%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6" target="_blank"><v-btn icon="mdi-help-circle" size="small" variant="plain"></v-btn></a> -->
1526

1627
</div>
@@ -34,6 +45,7 @@
3445
<AstrBotCoreConfigWrapper
3546
:metadata="metadata"
3647
:config_data="config_data"
48+
:search-keyword="configSearchKeyword"
3749
/>
3850

3951
<v-tooltip :text="tm('actions.save')" location="left">
@@ -290,6 +302,7 @@ export default {
290302
291303
// 配置类型切换
292304
configType: 'normal', // 'normal' 或 'system'
305+
configSearchKeyword: '',
293306
294307
// 系统配置开关
295308
isSystemConfig: false,
@@ -702,6 +715,21 @@ export default {
702715
.config-panel {
703716
width: 100%;
704717
}
718+
719+
.config-toolbar {
720+
padding-right: 0 !important;
721+
}
722+
723+
.config-toolbar-controls {
724+
width: 100%;
725+
flex-wrap: wrap;
726+
}
727+
728+
.config-select,
729+
.config-search-input {
730+
width: 100%;
731+
min-width: 0 !important;
732+
}
705733
}
706734
707735
/* 测试聊天抽屉样式 */

0 commit comments

Comments
 (0)