diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index 6260a613..c8bfd761 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -165,6 +165,10 @@ export function ChatSidebar({ const pendingRegenerationRef = useRef<{ text: string } | null>(null) + const pendingSendSettingsRef = useRef<{ + model: ModelId + reasoningEffort: ReasoningEffort + } | null>(null) const [regenerationBaseLength, setRegenerationBaseLength] = useState< number | null >(null) @@ -246,24 +250,30 @@ export function ChatSidebar({ new DefaultChatTransport({ api: `${chatApiBase}/chat`, credentials: "include", - prepareSendMessagesRequest: ({ messages }) => ({ - body: { - messages, - metadata: { - chatId: chatIdRef.current, - projectId: selectedProjectRef.current, - spaceMode: - selectedProjectRef.current === AUTO_CHAT_SPACE_ID - ? "auto" - : "manual", - enableSpaceDiscovery: - selectedProjectRef.current === AUTO_CHAT_SPACE_ID, - model: selectedModelRef.current, - reasoningEffort: reasoningEffortRef.current, - truncateFromMessageId: truncateFromMessageIdRef.current, + prepareSendMessagesRequest: ({ messages }) => { + const sendSettings = pendingSendSettingsRef.current + pendingSendSettingsRef.current = null + + return { + body: { + messages, + metadata: { + chatId: chatIdRef.current, + projectId: selectedProjectRef.current, + spaceMode: + selectedProjectRef.current === AUTO_CHAT_SPACE_ID + ? "auto" + : "manual", + enableSpaceDiscovery: + selectedProjectRef.current === AUTO_CHAT_SPACE_ID, + model: sendSettings?.model ?? selectedModelRef.current, + reasoningEffort: + sendSettings?.reasoningEffort ?? reasoningEffortRef.current, + truncateFromMessageId: truncateFromMessageIdRef.current, + }, }, - }, - }), + } + }, }), [chatApiBase], ) @@ -405,10 +415,10 @@ export function ChatSidebar({ if (messageIndex === -1) return truncateFromMessageIdRef.current = messageId - selectedModelRef.current = model - reasoningEffortRef.current = nextReasoningEffort - setSelectedModel(model) - setReasoningEffort(nextReasoningEffort) + pendingSendSettingsRef.current = { + model, + reasoningEffort: nextReasoningEffort, + } clearError() pendingRegenerationRef.current = { text: trimmed, diff --git a/apps/web/components/chat/message/user-message.tsx b/apps/web/components/chat/message/user-message.tsx index 3a58f603..016b4b07 100644 --- a/apps/web/components/chat/message/user-message.tsx +++ b/apps/web/components/chat/message/user-message.tsx @@ -90,6 +90,7 @@ export const UserMessage = memo(function UserMessage({ selectedModel={editModel} onModelChange={handleEditModelChange} minimal + dropdownDirection="down" /> void /** Compact pill matching inline send control. */ minimal?: boolean + dropdownDirection?: "up" | "down" } export default function ChatModelSelector({ selectedModel: selectedModelProp, onModelChange, minimal = false, + dropdownDirection = "up", }: ChatModelSelectorProps = {}) { const [internalModel, setInternalModel] = useState("claude-sonnet-4.6") @@ -41,6 +43,7 @@ export default function ChatModelSelector({ const selectedModel = selectedModelProp ?? internalModel const currentModelData = modelNames[selectedModel] + const selectedModelLabel = `${currentModelData.name} ${currentModelData.version}` const handleModelSelect = (modelId: ModelId) => { if (onModelChange) { @@ -56,71 +59,102 @@ export default function ChatModelSelector({ ) : ( ) return (
{trigger} {isOpen && ( -
-
+
+
{models.map((model) => { const modelData = modelNames[model.id] + const isSelected = selectedModel === model.id return ( ) })} diff --git a/apps/web/components/chat/reasoning-selector.tsx b/apps/web/components/chat/reasoning-selector.tsx index 070f3a24..223f6f27 100644 --- a/apps/web/components/chat/reasoning-selector.tsx +++ b/apps/web/components/chat/reasoning-selector.tsx @@ -1,7 +1,13 @@ "use client" import { useEffect, useRef, useState } from "react" -import { BrainIcon, CheckIcon, MoreHorizontalIcon, ZapIcon } from "lucide-react" +import { + BrainIcon, + CheckIcon, + ChevronDownIcon, + MoreHorizontalIcon, + ZapIcon, +} from "lucide-react" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" import { reasoningOptions, type ReasoningEffort } from "@/lib/models" @@ -25,6 +31,7 @@ export function ReasoningSelector({ const containerRef = useRef(null) const selected = reasoningOptions.find((option) => option.id === value) const SelectedIcon = value === "thinking" ? BrainIcon : ZapIcon + const selectedLabel = selected?.label ?? "Reasoning" useEffect(() => { if (!isOpen) return @@ -48,7 +55,10 @@ export function ReasoningSelector({ return (
@@ -77,16 +91,11 @@ export function ReasoningSelector({ {isOpen && (
-
- - Reasoning effort - -
-
+
{reasoningOptions.map((option) => { const Icon = option.id === "thinking" ? BrainIcon : ZapIcon const isSelected = option.id === value @@ -96,16 +105,30 @@ export function ReasoningSelector({ type="button" onClick={() => handleSelect(option.id)} className={cn( - "flex w-full cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-left transition-colors", - isSelected ? "bg-[#293952]/60" : "hover:bg-[#293952]/40", + "flex w-full cursor-pointer items-center gap-2.5 rounded-lg px-3 py-2.5 text-left transition-colors", + isSelected + ? "bg-[#E6E6E6] text-[#101010]" + : "text-white hover:bg-white/10", )} > - - - {option.label} - + +
+
+ {option.label} +
+
{isSelected && ( - + )} )