@@ -29,6 +29,7 @@ const props = defineProps({
2929
3030const { t } = useI18n ()
3131const { tm , getRaw } = useModuleI18n (' features/config-metadata' )
32+ const { tm: tmConfig } = useModuleI18n (' features/config' )
3233
3334const hintMarkdown = new MarkdownIt ({
3435 linkify: true ,
@@ -69,6 +70,7 @@ const getTranslatedLabels = (itemMeta) => {
6970}
7071
7172const dialog = ref (false )
73+ const showCollapsedItems = ref (false )
7274const currentEditingKey = ref (' ' )
7375const currentEditingLanguage = ref (' json' )
7476const 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 是否应该显示
156188function 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}
0 commit comments