@@ -10,6 +10,70 @@ import { formatTokens } from '../lib/formatTokens';
1010
1111const { t } = useI18n ();
1212
13+ interface CapabilityBadgeMeta {
14+ readonly labelKey: string ;
15+ readonly path: string ;
16+ }
17+
18+ const capabilityBadgeMeta: Record <string , CapabilityBadgeMeta > = {
19+ image_in: {
20+ labelKey: ' model.capabilities.imageIn' ,
21+ path: ' M3 3.5h10v9H3z M4.5 10l2.3-2.3 2 2 1.4-1.4 2.8 2.8' ,
22+ },
23+ image_out: {
24+ labelKey: ' model.capabilities.imageOut' ,
25+ path: ' M2.5 4h8v8h-8z M8 2.5h5v5 M10 2.5l3 3' ,
26+ },
27+ vision: {
28+ labelKey: ' model.capabilities.vision' ,
29+ path: ' M2.5 8s2-3 5.5-3 5.5 3 5.5 3-2 3-5.5 3-5.5-3-5.5-3z M8 6.7a1.3 1.3 0 1 0 0 2.6 1.3 1.3 0 0 0 0-2.6z' ,
30+ },
31+ video_in: {
32+ labelKey: ' model.capabilities.videoIn' ,
33+ path: ' M2.5 4.5h8v7h-8z M10.5 7l3-2v6l-3-2z' ,
34+ },
35+ audio_in: {
36+ labelKey: ' model.capabilities.audioIn' ,
37+ path: ' M6 3v6a2 2 0 1 1-4 0V7 M2 9a6 6 0 0 0 12 0 M8 15v-3 M5.5 15h5' ,
38+ },
39+ audio_out: {
40+ labelKey: ' model.capabilities.audioOut' ,
41+ path: ' M3 6h2.5L9 3v10l-3.5-3H3z M11 6a3 3 0 0 1 0 4 M12.5 4.5a5 5 0 0 1 0 7' ,
42+ },
43+ thinking: {
44+ labelKey: ' model.capabilities.thinking' ,
45+ path: ' M5.2 10.7a4 4 0 1 1 5.6 0c-.5.4-.8.8-.8 1.3H6c0-.5-.3-.9-.8-1.3z M6 14h4' ,
46+ },
47+ always_thinking: {
48+ labelKey: ' model.capabilities.alwaysThinking' ,
49+ path: ' M8 2.5a5.5 5.5 0 1 0 5.5 5.5 M8 5.5v3l2 1' ,
50+ },
51+ tool_use: {
52+ labelKey: ' model.capabilities.toolUse' ,
53+ path: ' M3 3.5h10v9H3z M5 6.5l2 1.5-2 1.5 M8.5 9.5h2' ,
54+ },
55+ fast_mode: {
56+ labelKey: ' model.capabilities.fastMode' ,
57+ path: ' M9.5 1.8 3.5 9h4l-1 5.2L12.5 7h-4z' ,
58+ },
59+ };
60+
61+ const adaptiveThinkingBadge: CapabilityBadgeMeta = {
62+ labelKey: ' model.capabilities.adaptiveThinking' ,
63+ path: ' M3 8a5 5 0 0 1 8.7-3.4 M11.7 4.6V2.5 M11.7 4.6H9.6 M13 8a5 5 0 0 1-8.7 3.4 M4.3 11.4v2.1 M4.3 11.4h2.1' ,
64+ };
65+
66+ function capabilityGlyph(capability : string ): string | undefined {
67+ return capabilityBadgeMeta [capability ]?.path ;
68+ }
69+
70+ function capabilityTitle(capability : string ): string {
71+ const meta = capabilityBadgeMeta [capability ];
72+ return meta === undefined
73+ ? t (' model.capabilities.unknown' , { capability })
74+ : t (meta .labelKey );
75+ }
76+
1377const props = defineProps <{
1478 models: AppModel [];
1579 current: string ;
@@ -223,8 +287,57 @@ function selectTab(tabId: string): void {
223287 </span >
224288 <span class =" model-provider" >{{ m.provider }}</span >
225289 <span class =" model-ctx" >{{ t('model.contextSuffix', { size: formatTokens(m.maxContextSize) }) }}</span >
226- <span v-if =" m.capabilities && m.capabilities.length > 0" class =" caps" >
227- {{ m.capabilities.join(', ') }}
290+ <span
291+ v-if =" (m.capabilities && m.capabilities.length > 0) || m.adaptiveThinking"
292+ class =" caps"
293+ :aria-label =" t('model.capabilities.label')"
294+ >
295+ <span
296+ v-for =" (capability, capabilityIndex) in m.capabilities ?? []"
297+ :key =" `${capability}-${capabilityIndex}`"
298+ class =" cap-badge"
299+ :class =" { 'is-unknown': capabilityGlyph(capability) === undefined }"
300+ :data-capability =" capability"
301+ :title =" capabilityTitle(capability)"
302+ :aria-label =" capabilityTitle(capability)"
303+ >
304+ <svg
305+ v-if =" capabilityGlyph(capability)"
306+ viewBox =" 0 0 16 16"
307+ width =" 12"
308+ height =" 12"
309+ fill =" none"
310+ stroke =" currentColor"
311+ stroke-width =" 1.25"
312+ stroke-linecap =" round"
313+ stroke-linejoin =" round"
314+ aria-hidden =" true"
315+ >
316+ <path :d =" capabilityGlyph(capability)" />
317+ </svg >
318+ <span v-else >{{ capability }}</span >
319+ </span >
320+ <span
321+ v-if =" m.adaptiveThinking"
322+ class =" cap-badge is-adaptive"
323+ data-capability =" adaptive-thinking"
324+ :title =" t(adaptiveThinkingBadge.labelKey)"
325+ :aria-label =" t(adaptiveThinkingBadge.labelKey)"
326+ >
327+ <svg
328+ viewBox =" 0 0 16 16"
329+ width =" 12"
330+ height =" 12"
331+ fill =" none"
332+ stroke =" currentColor"
333+ stroke-width =" 1.25"
334+ stroke-linecap =" round"
335+ stroke-linejoin =" round"
336+ aria-hidden =" true"
337+ >
338+ <path :d =" adaptiveThinkingBadge.path" />
339+ </svg >
340+ </span >
228341 </span >
229342 <button
230343 type =" button"
@@ -316,16 +429,19 @@ function selectTab(tabId: string): void {
316429 flex : none ;
317430}
318431.search-input {
432+ /* Default 14px: 14 + 13 = 27px. */
319433 width : 100% ;
434+ height : calc (var (--ui-font-size ) + 13px );
320435 box-sizing : border-box ;
321436 font-family : var (--mono );
322- font-size : calc (var (--ui-font-size ) - 1.5 px );
323- padding : 5 px 8px ;
437+ font-size : calc (var (--ui-font-size ) - 1 px );
438+ padding : 0 8px ;
324439 border : 1px solid var (--line );
325- border-radius : 3 px ;
440+ border-radius : var ( --r-sm ) ;
326441 background : var (--panel );
327442 color : var (--ink );
328443 outline : none ;
444+ line-height : 1 ;
329445}
330446
331447.tab-strip {
@@ -369,14 +485,21 @@ function selectTab(tabId: string): void {
369485}
370486
371487.model-row {
488+ /* Default 14px: 14 + 18 = 32px; 14 - 1 = 13px. */
372489 display : flex ;
373490 align-items : center ;
491+ box-sizing : border-box ;
492+ height : calc (var (--ui-font-size ) + 18px );
374493 gap : 8px ;
375- padding : 7px 14px ;
494+ padding : 0 8px ;
495+ border-radius : var (--r-md );
376496 cursor : pointer ;
377- font-size : calc (var (--ui-font-size ) - 1.5px );
497+ font-size : calc (var (--ui-font-size ) - 1px );
498+ line-height : 1 ;
378499 color : var (--text );
379500 min-width : 0 ;
501+ content-visibility : auto ;
502+ contain-intrinsic-size : calc (var (--ui-font-size ) + 18px );
380503}
381504.model-row :hover , .model-row.is-selected {
382505 background : var (--soft );
@@ -430,13 +553,45 @@ function selectTab(tabId: string): void {
430553 flex : none ;
431554}
432555.caps {
433- color : var (--blue );
556+ display : inline-flex ;
557+ align-items : center ;
558+ gap : 4px ;
559+ flex : none ;
560+ min-width : 0 ;
561+ max-width : 180px ;
562+ overflow : hidden ;
563+ color : var (--muted );
564+ }
565+ .cap-badge {
566+ display : inline-flex ;
567+ align-items : center ;
568+ justify-content : center ;
569+ flex : none ;
570+ box-sizing : border-box ;
571+ min-width : 16px ;
572+ height : 18px ;
573+ padding : 0 4px ;
574+ border : 1px solid color-mix(in srgb , var (--line ) 65% , var (--panel ));
575+ border-radius : var (--r-xs );
576+ background : color-mix(in srgb , var (--panel2 ) 45% , var (--panel ));
577+ color : var (--muted );
434578 font-size : max (9px , calc (var (--ui-font-size ) - 4px ));
435- border : 1px solid var (--bd );
436- border-radius : 3px ;
437- padding : 1px 5px ;
579+ line-height : 1 ;
580+ white-space : nowrap ;
581+ }
582+ .cap-badge svg {
583+ display : block ;
438584 flex : none ;
439585}
586+ .cap-badge.is-adaptive {
587+ border-style : dashed ;
588+ color : var (--dim );
589+ }
590+ .cap-badge.is-unknown {
591+ max-width : 120px ;
592+ overflow : hidden ;
593+ text-overflow : ellipsis ;
594+ }
440595.star-btn {
441596 flex : none ;
442597 display : flex ;
@@ -505,7 +660,7 @@ function selectTab(tabId: string): void {
505660 max-height : calc (100 dvh - 24px );
506661 }
507662 .model-provider ,
508- .caps {
663+ .model-provider {
509664 display : none ;
510665 }
511666}
0 commit comments