2 import { Lightbulb, LightbulbOff, Check, Info } from '@lucide/svelte';
3 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
4 import * as Tooltip from '$lib/components/ui/tooltip';
5 import { ReasoningEffort } from '$lib/enums';
6 import { REASONING_EFFORT_TOKENS } from '$lib/constants/reasoning-effort-tokens';
7 import { REASONING_EFFORT_LEVELS } from '$lib/constants/reasoning-effort';
8 import type { ReasoningEffortLevel } from '$lib/types';
11 checkModelSupportsThinking,
15 } from '$lib/stores/models.svelte';
16 import { chatStore } from '$lib/stores/chat.svelte';
17 import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
18 import { isRouterMode } from '$lib/stores/server.svelte';
19 import type { DatabaseMessage } from '$lib/types/database';
21 let subOpen = $state(false);
23 let conversationModel = $derived(
24 chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
27 let modelSupportsThinkingFromMessages = $derived.by(() => {
28 const modelId = isRouterMode() ? modelsStore.selectedModelName || conversationModel : null;
29 if (!modelId) return false;
31 const messages = conversationsStore.activeMessages;
34 (m) => m.role === 'assistant' && m.model === modelId && !!m.reasoningContent
38 let modelSupportsThinking = $derived.by(() => {
43 const modelId = modelsStore.selectedModelName || conversationModel;
44 return checkModelSupportsThinking(modelId ?? '') || modelSupportsThinkingFromMessages;
47 return supportsThinking() || modelSupportsThinkingFromMessages;
50 let thinkingEnabled = $derived(conversationsStore.getThinkingEnabled());
51 let currentEffort = $derived(conversationsStore.getReasoningEffort());
52 let isOff = $derived(!thinkingEnabled);
54 function isSelected(item: ReasoningEffortLevel): boolean {
55 if (item.isOff) return isOff;
56 return thinkingEnabled && currentEffort === item.value;
59 function handleSelection(item: ReasoningEffortLevel) {
61 conversationsStore.setThinkingEnabled(false);
63 conversationsStore.setThinkingEnabled(true);
64 conversationsStore.setReasoningEffort(item.value as ReasoningEffort);
70 {#if modelSupportsThinking}
71 <DropdownMenu.Sub bind:open={subOpen}>
72 <DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
74 <Lightbulb class="h-4 w-4 shrink-0 text-amber-400" />
76 <LightbulbOff class="h-4 w-4 shrink-0 text-muted-foreground" />
79 <span class="text-sm inline-flex gap-2 {!thinkingEnabled ? 'text-muted-foreground' : ''}">
82 <span class="capitalize text-muted-foreground">
83 {thinkingEnabled ? currentEffort : 'off'}
86 </DropdownMenu.SubTrigger>
88 <DropdownMenu.SubContent
89 class="w-60 bg-popover p-1.5 text-popover-foreground shadow-md outline-none"
91 {#each REASONING_EFFORT_LEVELS as level (level.value)}
94 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"
95 class:bg-accent={isSelected(level)}
96 onclick={() => handleSelection(level)}
98 {#if isSelected(level)}
99 <Check class="h-4 w-4 shrink-0 text-foreground" />
101 <div class="h-4 w-4 shrink-0"></div>
104 <span class="flex-1">{level.label}</span>
107 <span class="text-[11px] text-muted-foreground opacity-60">
108 {REASONING_EFFORT_TOKENS[level.value] === -1
110 : `Max ${REASONING_EFFORT_TOKENS[level.value].toLocaleString()} tokens`}
117 <Info class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
119 <Tooltip.Content side="left">
120 <p>Maximum reasoning effort with extended context usage</p>
126 </DropdownMenu.SubContent>