]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
ui: Add a "Default" option for the reasoning selector (#25846)
authorPascal <redacted>
Wed, 22 Jul 2026 21:09:49 +0000 (23:09 +0200)
committerGitHub <redacted>
Wed, 22 Jul 2026 21:09:49 +0000 (23:09 +0200)
* ui: add Default reasoning option that defers to the server

The webui always injected enable_thinking, overriding the chat template
default and the --reasoning flag, breaking models that reason
unconditionally (e.g. Gemma 4 E4B) on a fresh client.

Default sends nothing so the server decides, Off and effort levels
force the value as before. All choices are remembered.

Also remove the boolean thinking API from the conversations store and
drop ChatFormReasoningEffortSubmenu.svelte (dead code).

* ui: close the whole menu tree on reasoning level selection

The reasoning levels were raw buttons inside the SubContent, so
selecting one only closed the submenu via manual state while the root
dropdown stayed open. DropdownMenu.Item closes the full tree on select
like the sibling entries and brings native keyboard navigation.

* ui: prevent the add menu tooltip from flashing when the dropdown closes

tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddDropdown.svelte
tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddReasoningSubmenu.svelte
tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte
tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormReasoningEffortSubmenu.svelte [deleted file]
tools/ui/src/lib/constants/reasoning-effort.ts
tools/ui/src/lib/enums/reasoning-effort.enums.ts
tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
tools/ui/src/lib/services/chat.service.ts
tools/ui/src/lib/stores/chat.svelte.ts
tools/ui/src/lib/stores/conversations.svelte.ts
tools/ui/src/lib/types/reasoning.ts

index 699605fd82fb9d7402df9e55414c76cfbc4a3f45..905c2fe6f05b9bc7938308043c66a54b84ba380d 100644 (file)
@@ -71,7 +71,9 @@
 
 <div class="flex items-center gap-1 {className}">
        <DropdownMenu.Root bind:open={dropdownOpen}>
-               <Tooltip.Root>
+               <!-- ignoreNonKeyboardFocus prevents the tooltip from flashing when the
+                    menu closes and focus returns to the trigger -->
+               <Tooltip.Root ignoreNonKeyboardFocus>
                        <Tooltip.Trigger>
                                {#snippet child({ props })}
                                        <DropdownMenu.Trigger
index 6eb29d492ae79be24dc3f27234f53c43bab9377b..4bf6d16726214e0f7e00a6dd48a34fad2a650ba4 100644 (file)
@@ -5,18 +5,18 @@
        import * as Tooltip from '$lib/components/ui/tooltip';
        import { useReasoningMenu } from '$lib/hooks/use-reasoning-menu.svelte';
 
-       let subOpen = $state(false);
-
        const reasoning = useReasoningMenu();
 </script>
 
 {#if reasoning.modelSupportsThinking}
-       <DropdownMenu.Sub bind:open={subOpen}>
+       <DropdownMenu.Sub>
                <DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
                        {#if reasoning.thinkingEnabled}
                                <Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-amber-400" />
-                       {:else}
+                       {:else if reasoning.isOff}
                                <LightbulbOff class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
+                       {:else}
+                               <Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
                        {/if}
 
                        <span
@@ -27,7 +27,7 @@
                                Reasoning
 
                                <span class="capitalize text-muted-foreground">
-                                       {reasoning.thinkingEnabled ? reasoning.currentEffort : 'off'}
+                                       {reasoning.currentEffort}
                                </span>
                        </span>
                </DropdownMenu.SubTrigger>
                >
                        {#each reasoning.levels as level (level.value)}
                                {@const tokenLabel = reasoning.tokenLabel(level)}
-                               <button
-                                       type="button"
-                                       class="flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-1.75 text-left text-sm transition-colors hover:bg-accent"
-                                       class:bg-accent={reasoning.isSelected(level)}
-                                       onclick={() => {
-                                               reasoning.select(level);
-                                               subOpen = false;
-                                       }}
+                               <DropdownMenu.Item
+                                       class="flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-1.75 text-left text-sm transition-colors hover:bg-accent {reasoning.isSelected(
+                                               level
+                                       )
+                                               ? 'bg-accent'
+                                               : ''}"
+                                       onclick={() => reasoning.select(level)}
                                >
                                        {#if reasoning.isSelected(level)}
                                                <Check class="{ICON_CLASS_DEFAULT} shrink-0 text-foreground" />
@@ -70,7 +69,7 @@
                                                        </Tooltip.Content>
                                                </Tooltip.Root>
                                        {/if}
-                               </button>
+                               </DropdownMenu.Item>
                        {/each}
                </DropdownMenu.SubContent>
        </DropdownMenu.Sub>
index c533acc750050ced8c9878048fd3411a6e318fb1..021e0e453947798c9aedc8a21d05745822a5c5da 100644 (file)
 
                                                        {#if reasoning.thinkingEnabled}
                                                                <Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-amber-400" />
-                                                       {:else}
+                                                       {:else if reasoning.isOff}
                                                                <LightbulbOff class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
+                                                       {:else}
+                                                               <Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
                                                        {/if}
 
                                                        <span class="flex-1">Reasoning</span>
 
                                                        <span class="text-xs capitalize text-muted-foreground">
-                                                               {reasoning.thinkingEnabled ? reasoning.currentEffort : 'off'}
+                                                               {reasoning.currentEffort}
                                                        </span>
                                                </Collapsible.Trigger>
 
diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormReasoningEffortSubmenu.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormReasoningEffortSubmenu.svelte
deleted file mode 100644 (file)
index 0a4277e..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-<script lang="ts">
-       import { Check, Info, Lightbulb, LightbulbOff } from '@lucide/svelte';
-       import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
-       import * as Tooltip from '$lib/components/ui/tooltip';
-       import { ReasoningEffort, MessageRole } from '$lib/enums';
-       import { REASONING_EFFORT_TOKENS } from '$lib/constants/reasoning-effort-tokens';
-       import { REASONING_EFFORT_LEVELS } from '$lib/constants/reasoning-effort';
-       import type { ReasoningEffortLevel } from '$lib/types';
-       import { DIALOG_SUBMENU_CONTENT, ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
-       import {
-               modelsStore,
-               checkModelSupportsThinking,
-               supportsThinking,
-               propsCacheVersion,
-               loadedModelIds
-       } from '$lib/stores/models.svelte';
-       import { chatStore } from '$lib/stores/chat.svelte';
-       import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
-       import { isRouterMode } from '$lib/stores/server.svelte';
-       import type { DatabaseMessage } from '$lib/types/database';
-
-       let thinkingEnabled = $derived(conversationsStore.getThinkingEnabled());
-       let currentEffort = $derived(conversationsStore.getReasoningEffort());
-       let isOff = $derived(!thinkingEnabled);
-       let subOpen = $state(false);
-
-       // Get conversation model from message history
-       let conversationModel = $derived(
-               chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
-       );
-
-       let modelSupportsThinkingFromMessages = $derived.by(() => {
-               const modelId = isRouterMode() ? modelsStore.selectedModelName || conversationModel : null;
-               if (!modelId) return false;
-
-               const messages = conversationsStore.activeMessages;
-
-               return messages.some(
-                       (m: DatabaseMessage) =>
-                               m.role === MessageRole.ASSISTANT && m.model === modelId && !!m.reasoningContent
-               );
-       });
-
-       let modelSupportsThinking = $derived.by(() => {
-               loadedModelIds();
-               propsCacheVersion();
-
-               if (isRouterMode()) {
-                       const modelId = modelsStore.selectedModelName || conversationModel;
-                       return checkModelSupportsThinking(modelId ?? '') || modelSupportsThinkingFromMessages;
-               }
-
-               return supportsThinking() || modelSupportsThinkingFromMessages;
-       });
-
-       function isSelected(item: ReasoningEffortLevel): boolean {
-               if (item.isOff) return isOff;
-
-               return thinkingEnabled && currentEffort === item.value;
-       }
-
-       function handleSelection(item: ReasoningEffortLevel) {
-               if (item.isOff) {
-                       conversationsStore.setThinkingEnabled(false);
-               } else {
-                       conversationsStore.setThinkingEnabled(true);
-                       conversationsStore.setReasoningEffort(item.value as ReasoningEffort);
-               }
-               subOpen = false;
-       }
-</script>
-
-{#if modelSupportsThinking}
-       <DropdownMenu.Sub bind:open={subOpen}>
-               <DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
-                       {#if thinkingEnabled}
-                               <Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-amber-400" />
-                       {:else}
-                               <LightbulbOff class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
-                       {/if}
-
-                       <span class="flex-1">Thinking</span>
-
-                       {#if thinkingEnabled}
-                               <span class="text-xs text-muted-foreground">{currentEffort}</span>
-                       {:else}
-                               <span class="text-xs text-muted-foreground">off</span>
-                       {/if}
-               </DropdownMenu.SubTrigger>
-
-               <DropdownMenu.SubContent class={DIALOG_SUBMENU_CONTENT}>
-                       {#each REASONING_EFFORT_LEVELS as level (level.value)}
-                               <button
-                                       type="button"
-                                       class="flex w-full cursor-pointer items-center gap-2"
-                                       class:bg-accent={isSelected(level)}
-                                       onclick={() => handleSelection(level)}
-                               >
-                                       <span class="flex-1 text-left">{level.label}</span>
-
-                                       {#if !level.isOff}
-                                               <span class="text-[11px] text-muted-foreground opacity-60">
-                                                       {REASONING_EFFORT_TOKENS[level.value] === -1
-                                                               ? 'Unlimited'
-                                                               : `Max ${REASONING_EFFORT_TOKENS[level.value].toLocaleString()} tokens`}
-                                               </span>
-                                       {/if}
-
-                                       {#if level.hasInfo}
-                                               <Tooltip.Root>
-                                                       <Tooltip.Trigger>
-                                                               <Info class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
-                                                       </Tooltip.Trigger>
-                                                       <Tooltip.Content side="left">
-                                                               <p>Maximum thinking effort with extended context usage</p>
-                                                       </Tooltip.Content>
-                                               </Tooltip.Root>
-                                       {/if}
-
-                                       {#if isSelected(level)}
-                                               <Check class="{ICON_CLASS_DEFAULT} shrink-0 text-foreground" />
-                                       {/if}
-                               </button>
-                       {/each}
-               </DropdownMenu.SubContent>
-       </DropdownMenu.Sub>
-{/if}
index 28a24420e614f9942de230398697cd6df64e18d6..f21ea588ad78d967d0aceb3d6f7ca3a35dbad31a 100644 (file)
@@ -6,6 +6,7 @@ import type { ReasoningEffortLevel } from '$lib/types';
  * Keys match the ReasoningEffort enum values for type-safe lookups.
  */
 export const REASONING_EFFORT_LABELS: Record<string, string> = {
+       [ReasoningEffort.DEFAULT]: 'Default',
        [ReasoningEffort.OFF]: 'Off',
        [ReasoningEffort.LOW]: 'Low',
        [ReasoningEffort.MEDIUM]: 'Medium',
@@ -14,7 +15,8 @@ export const REASONING_EFFORT_LABELS: Record<string, string> = {
 };
 
 export const REASONING_EFFORT_LEVELS: ReasoningEffortLevel[] = [
-       { value: ReasoningEffort.OFF, label: 'Off', isOff: true },
+       { value: ReasoningEffort.DEFAULT, label: 'Default' },
+       { value: ReasoningEffort.OFF, label: 'Off' },
        { value: ReasoningEffort.LOW, label: 'Low' },
        { value: ReasoningEffort.MEDIUM, label: 'Medium' },
        { value: ReasoningEffort.HIGH, label: 'High' },
index 172f118e7f79d878fbf1c443604f2debde4b8238..6bf86ed4ec090b489dc4f48d6b4f627f8efd9101 100644 (file)
@@ -3,6 +3,7 @@
  * These values are sent to the server and mapped to token budgets.
  */
 export enum ReasoningEffort {
+       DEFAULT = 'default',
        OFF = 'off',
        LOW = 'low',
        MEDIUM = 'medium',
index 6a459e66bd4a0d530963074c223e6d733b8208fd..ce9b77884d2aa9574083116171c54e62ae0a5bdc 100644 (file)
@@ -17,6 +17,7 @@ import { isRouterMode } from '$lib/stores/server.svelte';
 export interface UseReasoningMenuReturn {
        readonly modelSupportsThinking: boolean;
        readonly thinkingEnabled: boolean;
+       readonly isOff: boolean;
        readonly currentEffort: ReasoningEffort;
        readonly levels: ReasoningEffortLevel[];
        isSelected(level: ReasoningEffortLevel): boolean;
@@ -59,8 +60,10 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
                return supportsThinking() || modelSupportsThinkingFromMessages;
        });
 
-       const thinkingEnabled = $derived(conversationsStore.getThinkingEnabled());
        const currentEffort = $derived(conversationsStore.getReasoningEffort());
+       const thinkingEnabled = $derived(
+               currentEffort !== ReasoningEffort.OFF && currentEffort !== ReasoningEffort.DEFAULT
+       );
 
        return {
                get modelSupportsThinking() {
@@ -69,6 +72,9 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
                get thinkingEnabled() {
                        return thinkingEnabled;
                },
+               get isOff() {
+                       return currentEffort === ReasoningEffort.OFF;
+               },
                get currentEffort() {
                        return currentEffort;
                },
@@ -76,20 +82,15 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
                        return REASONING_EFFORT_LEVELS;
                },
                isSelected(level: ReasoningEffortLevel): boolean {
-                       if (level.isOff) return !thinkingEnabled;
-                       return thinkingEnabled && currentEffort === level.value;
+                       return currentEffort === level.value;
                },
                tokenLabel(level: ReasoningEffortLevel): string | null {
-                       if (level.isOff) return null;
+                       if (level.value === ReasoningEffort.DEFAULT) return 'Model default';
                        const tokens = REASONING_EFFORT_TOKENS[level.value];
+                       if (tokens === undefined) return null;
                        return tokens === -1 ? 'Unlimited' : `Max ${tokens.toLocaleString()} tokens`;
                },
                select(level: ReasoningEffortLevel): void {
-                       if (level.isOff) {
-                               conversationsStore.setThinkingEnabled(false);
-                               return;
-                       }
-                       conversationsStore.setThinkingEnabled(true);
                        conversationsStore.setReasoningEffort(level.value as ReasoningEffort);
                }
        };
index 15f4cc3b5f8bf3e218f8f2bf3cf1c835974f2cae..d2455614fbaabef92ae582e3f09278887969cf38 100644 (file)
@@ -271,10 +271,14 @@ export class ChatService {
                const reasoningBudgetTokens =
                        enableThinking && reasoningEffort ? (REASONING_EFFORT_TOKENS[reasoningEffort] ?? -1) : -1;
 
-               requestBody.chat_template_kwargs = {
-                       ...(requestBody.chat_template_kwargs ?? {}),
-                       enable_thinking: enableThinking
-               };
+               // an explicit user choice injects the kwarg, otherwise it is omitted so
+               // the server default applies (--reasoning flag or chat template)
+               if (enableThinking !== undefined) {
+                       requestBody.chat_template_kwargs = {
+                               ...(requestBody.chat_template_kwargs ?? {}),
+                               enable_thinking: enableThinking
+                       };
+               }
 
                if (reasoningBudgetTokens >= 0) {
                        requestBody.thinking_budget_tokens = reasoningBudgetTokens;
index ab418ed9cdab1f4f09e40158fc634f1b54929b81..801d6c838f2e1afbee5dc89d1822036ee6c274ab 100644 (file)
@@ -2373,9 +2373,12 @@ class ChatStore {
 
                if (currentConfig.excludeReasoningFromContext) apiOptions.excludeReasoningFromContext = true;
 
-               apiOptions.enableThinking = conversationsStore.getThinkingEnabled();
+               // an explicit reasoning choice overrides the server default, DEFAULT sends nothing
                const effort = conversationsStore.getReasoningEffort();
-               if (effort !== ReasoningEffort.OFF) apiOptions.reasoningEffort = effort;
+               if (effort !== ReasoningEffort.DEFAULT) {
+                       apiOptions.enableThinking = effort !== ReasoningEffort.OFF;
+                       if (effort !== ReasoningEffort.OFF) apiOptions.reasoningEffort = effort;
+               }
 
                if (hasValue(currentConfig.temperature))
                        apiOptions.temperature = Number(currentConfig.temperature);
index 5c54e72ace9cd8a3ca22b9517ae701c158ca5a27..9649cfe03da0c435acea9f74001d86c7daca6626 100644 (file)
@@ -80,25 +80,17 @@ class ConversationsStore {
        /** Whether the store has been initialized */
        isInitialized = $state(false);
 
-       /** Global (non-conversation-specific) thinking toggle default, derived from reasoning effort */
-       pendingThinkingEnabled = $state(false);
-
        /** Global (non-conversation-specific) reasoning effort default */
-       pendingReasoningEffort = $state<ReasoningEffort | ReasoningEffort.OFF>(
-               ConversationsStore.loadReasoningEffortDefault()
-       );
-
-       /** Last non-off reasoning effort, restored when re-enabling thinking globally */
-       private lastNonOffEffort: ReasoningEffort | null = null;
+       pendingReasoningEffort = $state<ReasoningEffort>(ConversationsStore.loadReasoningEffortDefault());
 
-       /** Load reasoning effort default from localStorage */
-       private static loadReasoningEffortDefault(): ReasoningEffort | ReasoningEffort.OFF {
-               if (typeof globalThis.localStorage === 'undefined') return ReasoningEffort.OFF;
+       /** Load reasoning effort default from localStorage, DEFAULT defers to the server */
+       private static loadReasoningEffortDefault(): ReasoningEffort {
+               if (typeof globalThis.localStorage === 'undefined') return ReasoningEffort.DEFAULT;
                try {
                        const raw = localStorage.getItem(REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY);
-                       return (raw as ReasoningEffort | ReasoningEffort.OFF) || ReasoningEffort.OFF;
+                       return (raw as ReasoningEffort) || ReasoningEffort.DEFAULT;
                } catch {
-                       return ReasoningEffort.OFF;
+                       return ReasoningEffort.DEFAULT;
                }
        }
 
@@ -235,17 +227,10 @@ class ConversationsStore {
                // servers without a per-conversation override to `mcpServers[i].enabled`,
                // and only explicit toggles are stored on the conversation.
 
-               // Inherit global thinking/reasoning defaults into the new conversation
-               const thinkingEnabled = this.getThinkingEnabled();
-               conversation.thinkingEnabled = thinkingEnabled;
-               conversation.reasoningEffort =
-                       this.pendingReasoningEffort === ReasoningEffort.OFF ? undefined : this.pendingReasoningEffort;
+               // Inherit the global reasoning default into the new conversation
+               conversation.reasoningEffort = this.pendingReasoningEffort;
                await DatabaseService.updateConversation(conversation.id, {
-                       thinkingEnabled,
-                       reasoningEffort:
-                               this.pendingReasoningEffort === ReasoningEffort.OFF
-                                       ? undefined
-                                       : this.pendingReasoningEffort
+                       reasoningEffort: this.pendingReasoningEffort
                });
 
                this.conversations = [conversation, ...this.conversations];
@@ -794,62 +779,20 @@ class ConversationsStore {
        }
 
        /**
-        * Gets the effective thinking-enabled state for the active conversation.
+        * Gets the effective reasoning effort for the active conversation.
         * Returns the conversation override if set, otherwise the global default.
+        * DEFAULT means no override is sent and the server decides.
         */
-       getThinkingEnabled(): boolean {
+       getReasoningEffort(): ReasoningEffort {
                if (this.activeConversation) {
-                       if (this.activeConversation.thinkingEnabled !== undefined) {
-                               return this.activeConversation.thinkingEnabled;
+                       if (this.activeConversation.reasoningEffort !== undefined) {
+                               return this.activeConversation.reasoningEffort;
                        }
-               }
-               return this.getReasoningEffort() !== ReasoningEffort.OFF;
-       }
-
-       /**
-        * Sets the thinking-enabled state for the active conversation.
-        * If no conversation exists, stores the global default.
-        * @param enabled - The enabled state
-        */
-       async setThinkingEnabled(enabled: boolean): Promise<void> {
-               if (!this.activeConversation) {
-                       if (enabled) {
-                               const effort = this.lastNonOffEffort ?? ReasoningEffort.LOW;
-                               this.pendingReasoningEffort = effort;
-                               this.saveReasoningEffortDefaults();
-                       } else {
-                               if (this.pendingReasoningEffort !== ReasoningEffort.OFF) {
-                                       this.lastNonOffEffort = this.pendingReasoningEffort;
-                               }
-                               this.pendingReasoningEffort = ReasoningEffort.OFF;
-                               this.saveReasoningEffortDefaults();
+                       // conversations created before the tri-state store an explicit
+                       // opt-out only as thinkingEnabled = false
+                       if (this.activeConversation.thinkingEnabled === false) {
+                               return ReasoningEffort.OFF;
                        }
-                       return;
-               }
-
-               this.activeConversation = {
-                       ...this.activeConversation,
-                       thinkingEnabled: enabled
-               };
-
-               await DatabaseService.updateConversation(this.activeConversation.id, {
-                       thinkingEnabled: enabled
-               });
-
-               const convIndex = this.conversations.findIndex((c) => c.id === this.activeConversation!.id);
-               if (convIndex !== -1) {
-                       this.conversations[convIndex].thinkingEnabled = enabled;
-                       this.conversations = [...this.conversations];
-               }
-       }
-
-       /**
-        * Gets the effective reasoning effort for the active conversation.
-        * Returns the conversation override if set, otherwise the global default.
-        */
-       getReasoningEffort(): ReasoningEffort | ReasoningEffort.OFF {
-               if (this.activeConversation) {
-                       return this.activeConversation.reasoningEffort ?? this.pendingReasoningEffort;
                }
                return this.pendingReasoningEffort;
        }
@@ -857,7 +800,7 @@ class ConversationsStore {
        /**
         * Sets the reasoning effort for the active conversation.
         * If no conversation exists, stores the global default.
-        * @param effort - The effort level ('low' | 'medium' | 'high' | 'max')
+        * @param effort - The effort level ('default' | 'off' | 'low' | 'medium' | 'high' | 'max')
         */
        async setReasoningEffort(effort: ReasoningEffort): Promise<void> {
                if (!this.activeConversation) {
index b72a3b77f1fe70437fcc59dc5d7ae32253376a8f..417c79609f8a69bcf69b83e83f46d8cb4dc6953e 100644 (file)
@@ -1,6 +1,5 @@
 export interface ReasoningEffortLevel {
        value: string;
        label: string;
-       isOff?: boolean;
        hasInfo?: boolean;
 }