diff --git a/packages/ui/src/components/chat/MarkdownRenderer.tsx b/packages/ui/src/components/chat/MarkdownRenderer.tsx index 97bb6ab5..8779546c 100644 --- a/packages/ui/src/components/chat/MarkdownRenderer.tsx +++ b/packages/ui/src/components/chat/MarkdownRenderer.tsx @@ -1279,7 +1279,7 @@ export const MarkdownRenderer: React.FC = ({ variant = 'assistant', onShowPopup, }) => { - const { files, editor, runtime } = useRuntimeAPIs(); + const { editor, runtime } = useRuntimeAPIs(); const streamdownContainerRef = React.useRef(null); const effectiveDirectory = useEffectiveDirectory() ?? ''; const mermaidBlocks = React.useMemo(() => extractMermaidBlocks(content), [content]); @@ -1354,7 +1354,7 @@ export const SimpleMarkdownRenderer: React.FC<{ onShowPopup, allowMermaidWheelZoom = false, }) => { - const { files, editor, runtime } = useRuntimeAPIs(); + const { editor, runtime } = useRuntimeAPIs(); const renderedContent = React.useMemo( () => (stripFrontmatter ? stripLeadingFrontmatter(content) : content), [content, stripFrontmatter], diff --git a/packages/ui/src/components/chat/ModelControls.tsx b/packages/ui/src/components/chat/ModelControls.tsx index 97422066..22fbc844 100644 --- a/packages/ui/src/components/chat/ModelControls.tsx +++ b/packages/ui/src/components/chat/ModelControls.tsx @@ -413,6 +413,8 @@ export const ModelControls: React.FC = ({ const [desktopModelQuery, setDesktopModelQuery] = React.useState(''); const [modelSelectedIndex, setModelSelectedIndex] = React.useState(0); const modelItemRefs = React.useRef<(HTMLDivElement | null)[]>([]); + const [pendingThinkingVariants, setPendingThinkingVariants] = React.useState>(new Map()); + const [adjustedThinkingModels, setAdjustedThinkingModels] = React.useState>(new Set()); React.useEffect(() => { if (activeMobilePanel === 'model') { @@ -441,6 +443,8 @@ export const ModelControls: React.FC = ({ if (!isModelSelectorOpen) { setDesktopModelQuery(''); setModelSelectedIndex(0); + setPendingThinkingVariants(new Map()); + setAdjustedThinkingModels(new Set()); // Restore focus to chat input when model selector closes if (wasOpen && !isCompact) { @@ -1887,12 +1891,32 @@ export const ModelControls: React.FC = ({ const showProviderLogo = keyPrefix === 'fav' || keyPrefix === 'recent'; - // Build animated metadata slides for desktop + // Check if model supports thinking variants - variants are on the model object, not metadata + const modelVariants = (model as { variants?: Record } | undefined)?.variants; + const hasThinkingVariants = modelVariants && Object.keys(modelVariants).length > 0; + const mapKey = `${providerID}:${modelID}`; + const wasAdjusted = adjustedThinkingModels.has(mapKey); + const pendingVariant = pendingThinkingVariants.get(mapKey); + const effectiveVariant = pendingVariant ?? (isSelected ? currentVariant : undefined); + + // Build thinking variant display - only show for models that were adjusted with arrow keys + let thinkingDisplay: React.ReactNode = null; + if (hasThinkingVariants && wasAdjusted && (isHighlighted || isSelected)) { + const displayLabel = effectiveVariant + ? effectiveVariant.charAt(0).toUpperCase() + effectiveVariant.slice(1) + : 'Default'; + thinkingDisplay = ( + + Thinking: {displayLabel} + + ); + } + + // Build animated metadata slides for desktop (price/capabilities) - only shown when not showing thinking const priceText = formatCompactPrice(metadata); const hasPrice = priceText !== null; const hasCapabilities = indicatorIcons.length > 0; - // Build slides array: price first, then capabilities const slides: React.ReactNode[] = []; if (hasPrice) { slides.push( @@ -1919,9 +1943,9 @@ export const ModelControls: React.FC = ({ ); } - // Rotate metadata in interactive desktop-style pickers (web/desktop), keep VS Code static. const supportsRotatingMetadata = !isVSCodeRuntime; - const shouldAnimate = supportsRotatingMetadata && slides.length > 1 && (isHighlighted || isSelected); + const shouldShowThinking = hasThinkingVariants && wasAdjusted; + const shouldAnimate = supportsRotatingMetadata && slides.length > 1 && (isHighlighted || isSelected) && !shouldShowThinking; const staticSlideIndex = !supportsRotatingMetadata && hasCapabilities && hasPrice ? 1 : 0; const staticMetadataSlide = slides[staticSlideIndex]; @@ -1950,8 +1974,12 @@ export const ModelControls: React.FC = ({ ) : null}
- {/* Metadata slot: animated TextLoop for desktop highlighted/selected rows, static otherwise */} - {slides.length > 0 && ( + {/* Metadata slot: thinking variant for adjusted models, otherwise price/capabilities carousel */} + {shouldShowThinking && (isHighlighted || isSelected) ? ( +
+ {thinkingDisplay} +
+ ) : slides.length > 0 ? (
= ({ )}
- )} + ) : null} {isSelected && ( )} @@ -2071,6 +2099,13 @@ export const ModelControls: React.FC = ({ const totalItems = flatModelList.length; + // Check if currently highlighted model supports thinking variants + const highlightedItem = flatModelList[modelSelectedIndex]; + const highlightedSupportsThinking = highlightedItem ? (() => { + const modelVariants = (highlightedItem.model as { variants?: Record } | undefined)?.variants; + return modelVariants && Object.keys(modelVariants).length > 0; + })() : false; + // Handle keyboard navigation const handleModelKeyDown = (e: React.KeyboardEvent) => { e.stopPropagation(); @@ -2091,11 +2126,55 @@ export const ModelControls: React.FC = ({ const prevIndex = (modelSelectedIndex - 1 + Math.max(1, totalItems)) % Math.max(1, totalItems); modelItemRefs.current[prevIndex]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 0); + } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { + e.preventDefault(); + const selectedItem = flatModelList[modelSelectedIndex]; + if (!selectedItem) return; + + const { providerID, modelID, model } = selectedItem; + const modelVariants = (model as { variants?: Record } | undefined)?.variants; + if (!modelVariants) return; + + const variantKeys = Object.keys(modelVariants); + if (variantKeys.length === 0) return; + + const mapKey = `${providerID}:${modelID}`; + const currentPending = pendingThinkingVariants.get(mapKey); + const activeModelVariant = currentPending ?? (currentProviderId === providerID && currentModelId === modelID ? currentVariant : undefined); + + const variantsWithDefault: Array = [undefined, ...variantKeys]; + const currentVariantIndex = variantsWithDefault.indexOf(activeModelVariant); + const safeCurrentIndex = currentVariantIndex >= 0 ? currentVariantIndex : 0; + const direction = e.key === 'ArrowRight' ? 1 : -1; + const nextVariantIndex = (safeCurrentIndex + direction + variantsWithDefault.length) % variantsWithDefault.length; + const nextVariant = variantsWithDefault[nextVariantIndex]; + + setPendingThinkingVariants((prev) => { + const next = new Map(prev); + next.set(mapKey, nextVariant); + return next; + }); + setAdjustedThinkingModels((prev) => { + const next = new Set(prev); + next.add(mapKey); + return next; + }); } else if (e.key === 'Enter') { e.preventDefault(); const selectedItem = flatModelList[modelSelectedIndex]; if (selectedItem) { - handleProviderAndModelChange(selectedItem.providerID, selectedItem.modelID); + const { providerID, modelID } = selectedItem; + const mapKey = `${providerID}:${modelID}`; + const pendingVariant = pendingThinkingVariants.get(mapKey); + const wasAdjusted = adjustedThinkingModels.has(mapKey); + + handleProviderAndModelChange(providerID, modelID); + + if (wasAdjusted) { + setTimeout(() => { + handleVariantSelect(pendingVariant); + }, 0); + } } } else if (e.key === 'Escape') { e.preventDefault(); @@ -2292,7 +2371,7 @@ export const ModelControls: React.FC = ({ {/* Keyboard hints footer */}
- ↑↓ navigate • Enter select • Esc close + ↑↓ navigate{highlightedSupportsThinking ? ' • ←→ thinking' : ''} • Enter select • Esc close
diff --git a/packages/ui/src/components/ui/HelpDialog.tsx b/packages/ui/src/components/ui/HelpDialog.tsx index 597cdf7f..890b0257 100644 --- a/packages/ui/src/components/ui/HelpDialog.tsx +++ b/packages/ui/src/components/ui/HelpDialog.tsx @@ -88,9 +88,19 @@ export const HelpDialog: React.FC = () => { icon: RiAiGenerate2, keys: '', }, + { + keys: ["↑↓"], + description: "Navigate Models (in picker)", + icon: RiAiGenerate2, + }, + { + keys: ["←→"], + description: "Adjust Thinking Mode (in picker, when supported)", + icon: RiBrainAi3Line, + }, { id: 'cycle_thinking_variant', - description: "Cycle Thinking Variant", + description: "Cycle Thinking Variant (global shortcut)", icon: RiBrainAi3Line, keys: '', },