diff --git a/src/web-ui/src/flow_chat/components/ChatInput.tsx b/src/web-ui/src/flow_chat/components/ChatInput.tsx index 2f76febb0..a35ea5be4 100644 --- a/src/web-ui/src/flow_chat/components/ChatInput.tsx +++ b/src/web-ui/src/flow_chat/components/ChatInput.tsx @@ -2022,6 +2022,19 @@ export const ChatInput: React.FC = ({ return; } + const nativeEvt = e.nativeEvent as KeyboardEvent; + // IME-owned keys must stay with the input method. In particular, Escape + // closes the Chinese/Japanese/Korean candidate window and must not cancel + // the running BitFun session. + const isComposing = + isImeComposingRef.current + || nativeEvt.isComposing + || nativeEvt.keyCode === 229; + + if (e.key === 'Escape' && isComposing) { + return; + } + if (slashCommandState.isActive) { if (!(slashCommandState.kind === 'modes' && !canSwitchModes)) { const items = @@ -2195,18 +2208,6 @@ export const ChatInput: React.FC = ({ } } - const nativeEvt = e.nativeEvent as KeyboardEvent; - // IME-safe Enter detection (see useImeEnterGuard for the rationale): - // - our own composition flag covers browsers where `isComposing` is flaky - // - `keyCode === 229` is the W3C "composition keyCode" still emitted by - // every evergreen browser while the IME owns the key, even after - // `isComposing` has flipped back to false. Replaces the previous - // 120ms time-window guard which would swallow legitimate fast Enters. - const isComposing = - isImeComposingRef.current - || nativeEvt.isComposing - || nativeEvt.keyCode === 229; - if (e.key === 'Enter' && !e.shiftKey) { if (isComposing) { return; @@ -2368,6 +2369,9 @@ export const ChatInput: React.FC = ({ target.isContentEditable || target.closest('[contenteditable="true"]') !== null; + const isImeOwnedKey = e.key === 'Escape' && (e.isComposing || e.keyCode === 229); + if (isImeOwnedKey) return; + if (e.key === 'Escape' && derivedState?.canCancel) { if (isEditable) return; e.preventDefault(); diff --git a/src/web-ui/src/flow_chat/components/RichTextInput.test.tsx b/src/web-ui/src/flow_chat/components/RichTextInput.test.tsx index 0ab41a385..17b6de882 100644 --- a/src/web-ui/src/flow_chat/components/RichTextInput.test.tsx +++ b/src/web-ui/src/flow_chat/components/RichTextInput.test.tsx @@ -133,4 +133,33 @@ describeWithJsdom('RichTextInput external sync', () => { expect(editor.textContent).toBe('server rewrite'); expect(editor.firstChild).not.toBe(originalTextNode); }); + + it('keeps Escape owned by IME composition', async () => { + const onKeyDown = vi.fn(); + + await act(async () => { + root.render( + {}} + onKeyDown={onKeyDown} + contexts={emptyContexts} + onRemoveContext={() => {}} + /> + ); + }); + + const editor = container.querySelector('.rich-text-input'); + expect(editor).toBeInstanceOf(HTMLDivElement); + + await act(async () => { + editor!.dispatchEvent(new window.KeyboardEvent('keydown', { + key: 'Escape', + keyCode: 229, + bubbles: true, + })); + }); + + expect(onKeyDown).not.toHaveBeenCalled(); + }); }); diff --git a/src/web-ui/src/flow_chat/components/RichTextInput.tsx b/src/web-ui/src/flow_chat/components/RichTextInput.tsx index 4818e7589..4e30c0d0e 100644 --- a/src/web-ui/src/flow_chat/components/RichTextInput.tsx +++ b/src/web-ui/src/flow_chat/components/RichTextInput.tsx @@ -552,8 +552,8 @@ export const RichTextInput = React.forwardRef { - const nativeIsComposing = (e.nativeEvent as KeyboardEvent).isComposing; - const composing = nativeIsComposing || isComposingRef.current; + const nativeEvent = e.nativeEvent as KeyboardEvent; + const composing = nativeEvent.isComposing || isComposingRef.current || nativeEvent.keyCode === 229; if (!composing && e.key === 'Backspace' && internalRef.current) { const selection = window.getSelection(); @@ -580,7 +580,7 @@ export const RichTextInput = React.forwardRef( [round.items] ); + const latestCompletedToolEndTime = useMemo(() => { + return sortedItems.reduce((latest, item) => { + if (item.type !== 'tool' || item.status !== 'completed') return latest; + const endTime = (item as FlowToolItem).endTime; + return typeof endTime === 'number' ? Math.max(latest, endTime) : latest; + }, 0); + }, [sortedItems]); + const [transientNowMs, setTransientNowMs] = useState(() => Date.now()); + + useEffect(() => { + if (latestCompletedToolEndTime <= 0) return; + + const remainingMs = latestCompletedToolEndTime + COMPLETED_TOOL_TRANSIENT_MS - Date.now(); + if (remainingMs <= 0) { + setTransientNowMs(Date.now()); + return; + } + + setTransientNowMs(Date.now()); + const timeoutId = window.setTimeout(() => { + setTransientNowMs(Date.now()); + }, remainingMs); + + return () => window.clearTimeout(timeoutId); + }, [latestCompletedToolEndTime]); + // Group items in two passes: // 1) group subagent items // 2) group normal items into explore/critical via anchor tool @@ -139,8 +165,9 @@ export const ModelRoundItem = React.memo( isStreaming: round.isStreaming, disableExploreGrouping: round.renderHints?.disableExploreGrouping === true, isCollapsibleTool, + nowMs: transientNowMs, }); - }, [round.isStreaming, round.renderHints?.disableExploreGrouping, sortedItems]); + }, [round.isStreaming, round.renderHints?.disableExploreGrouping, sortedItems, transientNowMs]); const extractDialogTurnContent = useCallback(() => { const flowChatStore = FlowChatStore.getInstance(); @@ -667,11 +694,19 @@ const FlowItemRenderer: React.FC = ({ item, turnId, isLas ); - case 'tool': + case 'tool': { + const toolItem = item as FlowToolItem; + const isCompletedTool = toolItem.status === 'completed'; + const toolClassName = [ + 'flowchat-flow-item', + 'flowchat-flow-item--tool-transition', + isCompletedTool ? 'flowchat-flow-item--tool-completed' : 'flowchat-flow-item--tool-active', + ].join(' '); + return wrapContent( -
+
{ if (onToolConfirm) { await onToolConfirm(toolId, updatedInput, permissionOptionId, approve); @@ -698,6 +733,7 @@ const FlowItemRenderer: React.FC = ({ item, turnId, isLas />
); + } default: return null; diff --git a/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.test.ts b/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.test.ts index ef404d396..ea2f540ba 100644 --- a/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.test.ts +++ b/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.test.ts @@ -14,21 +14,30 @@ function makeTextItem(id: string): FlowTextItem { }; } -function makeReadTool(id: string): FlowToolItem { +function makeReadTool( + id: string, + status: FlowToolItem['status'] = 'completed', + endTime?: number, +): FlowToolItem { return { id, type: 'tool', toolName: 'Read', timestamp: 1001, - status: 'completed', + status, toolCall: { id, input: { file_path: 'src/main.rs' }, }, - toolResult: { - result: 'file contents', - success: true, - }, + ...(status === 'completed' + ? { + toolResult: { + result: 'file contents', + success: true, + }, + } + : {}), + ...(endTime !== undefined ? { endTime } : {}), }; } @@ -105,4 +114,74 @@ describe('buildModelRoundItemGroups', () => { }, ]); }); + + it('keeps an active collapsible tool outside the preceding explore group', () => { + const completedTool = makeReadTool('tool-1'); + const runningTool = makeReadTool('tool-2', 'running'); + + const groups = buildModelRoundItemGroups({ + items: [completedTool, runningTool], + isStreaming: true, + disableExploreGrouping: false, + isCollapsibleTool: toolName => toolName === 'Read', + }); + + expect(groups).toEqual([ + { + type: 'explore', + items: [completedTool], + isLast: false, + }, + { + type: 'critical', + item: runningTool, + }, + ]); + }); + + it('keeps a just-completed collapsible tool visible before merging it', () => { + const completedTool = makeReadTool('tool-1'); + const justCompletedTool = makeReadTool('tool-2', 'completed', 10_000); + + const groups = buildModelRoundItemGroups({ + items: [completedTool, justCompletedTool], + isStreaming: true, + disableExploreGrouping: false, + isCollapsibleTool: toolName => toolName === 'Read', + nowMs: 10_200, + }); + + expect(groups).toEqual([ + { + type: 'explore', + items: [completedTool], + isLast: false, + }, + { + type: 'critical', + item: justCompletedTool, + }, + ]); + }); + + it('merges a completed collapsible tool after the transition window', () => { + const completedTool = makeReadTool('tool-1'); + const settledTool = makeReadTool('tool-2', 'completed', 10_000); + + const groups = buildModelRoundItemGroups({ + items: [completedTool, settledTool], + isStreaming: true, + disableExploreGrouping: false, + isCollapsibleTool: toolName => toolName === 'Read', + nowMs: 11_001, + }); + + expect(groups).toEqual([ + { + type: 'explore', + items: [completedTool, settledTool], + isLast: true, + }, + ]); + }); }); diff --git a/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.ts b/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.ts index babc4ee16..5ee611826 100644 --- a/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.ts +++ b/src/web-ui/src/flow_chat/components/modern/modelRoundItemGrouping.ts @@ -1,5 +1,7 @@ import type { FlowItem, FlowToolItem } from '../../types/flow-chat'; +export const COMPLETED_TOOL_TRANSIENT_MS = 1000; + export type ModelRoundItemGroup = | { type: 'explore'; items: FlowItem[]; isLast: boolean } | { type: 'critical'; item: FlowItem } @@ -10,6 +12,7 @@ interface BuildModelRoundItemGroupsInput { isStreaming: boolean; disableExploreGrouping: boolean; isCollapsibleTool: (toolName: string) => boolean; + nowMs?: number; } function hasActiveStreamingNarrative(items: FlowItem[]): boolean { @@ -21,11 +24,23 @@ function hasActiveStreamingNarrative(items: FlowItem[]): boolean { }); } +function isActiveToolItem(item: FlowItem): boolean { + if (item.type !== 'tool') return false; + return item.status !== 'completed' && item.status !== 'cancelled' && item.status !== 'error'; +} + +function isRecentlyCompletedToolItem(item: FlowItem, nowMs: number): boolean { + if (item.type !== 'tool' || item.status !== 'completed') return false; + const endTime = (item as FlowToolItem).endTime; + return typeof endTime === 'number' && nowMs - endTime < COMPLETED_TOOL_TRANSIENT_MS; +} + export function buildModelRoundItemGroups({ items, isStreaming, disableExploreGrouping, isCollapsibleTool, + nowMs = Date.now(), }: BuildModelRoundItemGroupsInput): ModelRoundItemGroup[] { const deferExploreGrouping = disableExploreGrouping || (isStreaming && hasActiveStreamingNarrative(items)); const intermediateGroups: Array< @@ -110,7 +125,7 @@ export function buildModelRoundItemGroups({ const isExploreTool = isCollapsibleTool(toolName); if (isExploreTool) { - if (deferExploreGrouping) { + if (deferExploreGrouping || isActiveToolItem(item) || isRecentlyCompletedToolItem(item, nowMs)) { flushExploreBuffer(false); flushPendingAsCritical(); finalGroups.push({ type: 'critical', item }); diff --git a/src/web-ui/src/flow_chat/store/modernFlowChatStore.test.ts b/src/web-ui/src/flow_chat/store/modernFlowChatStore.test.ts index d27c0fd52..b27cf09d0 100644 --- a/src/web-ui/src/flow_chat/store/modernFlowChatStore.test.ts +++ b/src/web-ui/src/flow_chat/store/modernFlowChatStore.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import type { FlowTextItem, FlowToolItem, FlowUserSteeringItem, ModelRound, Session } from '../types/flow-chat'; vi.mock('./FlowChatStore', () => ({ @@ -35,21 +35,31 @@ function makeReadTool(id: string): FlowToolItem { return makeTool(id, 'Read'); } -function makeTool(id: string, toolName: string): FlowToolItem { +function makeTool( + id: string, + toolName: string, + status: FlowToolItem['status'] = 'completed', + endTime?: number, +): FlowToolItem { return { id, type: 'tool', toolName, timestamp: 1001, - status: 'completed', + status, toolCall: { id, input: { file_path: 'src/main.rs' }, }, - toolResult: { - result: 'file contents', - success: true, - }, + ...(status === 'completed' + ? { + toolResult: { + result: 'file contents', + success: true, + }, + } + : {}), + ...(endTime !== undefined ? { endTime } : {}), }; } @@ -106,6 +116,10 @@ function makeSession(overrides: Partial = {}): Session { } describe('sessionToVirtualItems explore grouping', () => { + afterEach(() => { + vi.useRealTimers(); + }); + it('groups normal rounds containing only collapsible tools and narrative', () => { const session = makeSession({ sessionId: 'normal-session' }); @@ -200,6 +214,155 @@ describe('sessionToVirtualItems explore grouping', () => { }); }); + it('keeps the active collapsible tool visible after a collapsed explore group', () => { + const session = makeSession({ + sessionId: 'active-tool-session', + dialogTurns: [{ + id: 'turn-1', + sessionId: 'active-tool-session', + userMessage: { + id: 'user-1', + content: 'Help', + timestamp: 900, + }, + modelRounds: [ + makeRound({ id: 'round-1', isStreaming: false, isComplete: true }), + makeRound({ + id: 'round-2', + items: [makeTool('tool-2', 'Read', 'running')], + isStreaming: true, + isComplete: false, + status: 'streaming', + }), + ], + status: 'processing', + startTime: 900, + }], + }); + + const items = sessionToVirtualItems(session); + + expect(items.map(item => item.type)).toEqual(['user-message', 'explore-group', 'model-round']); + expect(items[1]).toMatchObject({ + type: 'explore-group', + data: { + groupId: 'round-1', + wasCutByCritical: true, + }, + }); + expect(items[2]).toMatchObject({ + type: 'model-round', + data: { + id: 'round-2', + }, + }); + }); + + it('keeps a just-completed collapsible tool as a model round before merging it', () => { + vi.useFakeTimers(); + vi.setSystemTime(10_200); + const session = makeSession({ + sessionId: 'just-completed-tool-session', + dialogTurns: [{ + id: 'turn-1', + sessionId: 'just-completed-tool-session', + userMessage: { + id: 'user-1', + content: 'Help', + timestamp: 900, + }, + modelRounds: [ + makeRound({ id: 'round-1', isStreaming: false, isComplete: true }), + makeRound({ + id: 'round-2', + items: [makeTool('tool-2', 'Read', 'completed', 10_000)], + isStreaming: false, + isComplete: true, + status: 'completed', + }), + ], + status: 'processing', + startTime: 900, + }], + }); + + const items = sessionToVirtualItems(session); + + expect(items.map(item => item.type)).toEqual(['user-message', 'explore-group', 'model-round']); + expect(items[2]).toMatchObject({ + type: 'model-round', + data: { + id: 'round-2', + }, + }); + }); + + it('keeps the same explore group id when a completed trailing tool is merged in', () => { + const baseTurn = { + id: 'turn-1', + sessionId: 'stable-group-session', + userMessage: { + id: 'user-1', + content: 'Help', + timestamp: 900, + }, + modelRounds: [ + makeRound({ id: 'round-1', isStreaming: false, isComplete: true }), + makeRound({ + id: 'round-2', + items: [makeTool('tool-2', 'Read', 'running')], + isStreaming: true, + isComplete: false, + status: 'streaming', + }), + ], + status: 'processing' as const, + startTime: 900, + }; + const activeSession = makeSession({ + sessionId: 'stable-group-session', + dialogTurns: [baseTurn], + }); + const completedSession = makeSession({ + sessionId: 'stable-group-session-completed', + dialogTurns: [{ + ...baseTurn, + sessionId: 'stable-group-session-completed', + modelRounds: [ + baseTurn.modelRounds[0], + makeRound({ + id: 'round-2', + items: [makeTool('tool-2', 'Read', 'completed')], + isStreaming: false, + isComplete: true, + status: 'completed', + }), + ], + }], + }); + + const activeItems = sessionToVirtualItems(activeSession); + const completedItems = sessionToVirtualItems(completedSession); + + expect(activeItems[1]).toMatchObject({ + type: 'explore-group', + data: { + groupId: 'round-1', + }, + }); + expect(completedItems[1]).toMatchObject({ + type: 'explore-group', + data: { + groupId: 'round-1', + allItems: [ + expect.objectContaining({ id: 'text-1' }), + expect.objectContaining({ id: 'tool-1' }), + expect.objectContaining({ id: 'tool-2' }), + ], + }, + }); + }); + it('auto-collapses completed trailing explore groups', () => { const session = makeSession(); diff --git a/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts b/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts index d333d9b48..e086f851a 100644 --- a/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts +++ b/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts @@ -9,6 +9,7 @@ import { useShallow } from 'zustand/react/shallow'; import { immer } from 'zustand/middleware/immer'; import type { Session, DialogTurn, ModelRound, FlowItem, FlowToolItem, FlowUserSteeringItem } from '../types/flow-chat'; import { isCollapsibleTool, READ_TOOL_NAMES, SEARCH_TOOL_NAMES, COMMAND_TOOL_NAMES } from '../tool-cards'; +import { COMPLETED_TOOL_TRANSIENT_MS } from '../components/modern/modelRoundItemGrouping'; import { flowChatStore } from './FlowChatStore'; import { createLogger } from '@/shared/utils/logger'; @@ -102,7 +103,22 @@ function hasActiveStreamingNarrative(round: ModelRound): boolean { }); } -function isExploreOnlyRound(round: ModelRound): boolean { +function hasActiveTool(round: ModelRound): boolean { + return round.items.some(item => { + if (item.type !== 'tool') return false; + return item.status !== 'completed' && item.status !== 'cancelled' && item.status !== 'error'; + }); +} + +function hasRecentlyCompletedTool(round: ModelRound, nowMs: number): boolean { + return round.items.some(item => { + if (item.type !== 'tool' || item.status !== 'completed') return false; + const endTime = (item as FlowToolItem).endTime; + return typeof endTime === 'number' && nowMs - endTime < COMPLETED_TOOL_TRANSIENT_MS; + }); +} + +function isExploreOnlyRound(round: ModelRound, nowMs: number): boolean { if (!round.items || round.items.length === 0) return false; if (round.renderHints?.disableExploreGrouping === true) { @@ -112,6 +128,10 @@ function isExploreOnlyRound(round: ModelRound): boolean { if (round.isStreaming && hasActiveStreamingNarrative(round)) { return false; } + + if (hasActiveTool(round) || hasRecentlyCompletedTool(round, nowMs)) { + return false; + } const hasCollapsibleTool = round.items.some(item => item.type === 'tool' && isCollapsibleTool((item as FlowToolItem).toolName) @@ -194,6 +214,7 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] { cachedDialogTurnsRef = session.dialogTurns; const items: VirtualItem[] = []; + const nowMs = Date.now(); session.dialogTurns.forEach(turn => { if (turn.userMessage) { @@ -254,7 +275,7 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] { let currentGroup: TempExploreGroup | null = null; rounds.forEach((round, index) => { - const exploreOnly = isExploreOnlyRound(round); + const exploreOnly = isExploreOnlyRound(round, nowMs); if (exploreOnly) { const stats = computeRoundStats(round); if (currentGroup) { @@ -315,9 +336,11 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] { group.endIndex < rounds.length - 1 || (isTurnComplete && !isGroupStreaming); + const groupId = group.rounds[0]?.id ?? `explore-group-${turn.id}-${group.startIndex}`; + if (wasCutByCritical) { log.debug('explore-group marked wasCutByCritical', { - groupId: group.rounds.map(r => r.id).join('-'), + groupId, endIndex: group.endIndex, totalRounds: rounds.length, isTurnComplete, @@ -329,7 +352,7 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] { type: 'explore-group', turnId: turn.id, data: { - groupId: group.rounds.map(r => r.id).join('-'), + groupId, rounds: group.rounds, allItems: group.allItems, stats: { diff --git a/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx b/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx index 18a9e90f1..646b30fe4 100644 --- a/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx +++ b/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx @@ -104,6 +104,7 @@ function renderTerminalExpandedContent(params: {
diff --git a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx index ce927b63a..daa7dbf73 100644 --- a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx @@ -199,6 +199,30 @@ function parseOptionalPositiveIntegerInput(value: string): number | null | undef return parsed; } +const DEEPSEEK_REASONING_EFFORT_MODE_PREFIX = 'deepseek-effort:'; + +function getDeepSeekReasoningModeSelectValue(draft: SelectedModelDraft): string { + if (draft.reasoningMode === 'enabled' && draft.reasoningEffort) { + return `${DEEPSEEK_REASONING_EFFORT_MODE_PREFIX}${draft.reasoningEffort}`; + } + + return draft.reasoningMode; +} + +function getUpdatesFromDeepSeekReasoningModeSelectValue(value: string): Partial { + if (value.startsWith(DEEPSEEK_REASONING_EFFORT_MODE_PREFIX)) { + return { + reasoningMode: 'enabled', + reasoningEffort: value.slice(DEEPSEEK_REASONING_EFFORT_MODE_PREFIX.length), + }; + } + + return { + reasoningMode: value as ReasoningMode, + reasoningEffort: undefined, + }; +} + /** Last line of defense: same logical model name once per save; prefer draft tied to an existing config id. */ function dedupeSelectedModelDraftsByModelName(drafts: SelectedModelDraft[]): SelectedModelDraft[] { const out: SelectedModelDraft[] = []; @@ -346,7 +370,7 @@ const AIModelConfig: React.FC = () => { [] ); - const deepSeekReasoningEffortOptions = useMemo( + const deepSeekReasoningEffortOptions = useMemo( () => [ { label: 'High', value: 'high' }, { label: 'Max', value: 'max' }, @@ -361,7 +385,16 @@ const AIModelConfig: React.FC = () => { { label: t('thinking.optionDisabled'), value: 'disabled' }, ]; - if ( + if (supportsDeepSeekReasoningEffort({ name: editingConfig?.name, base_url: editingConfig?.base_url, model_name: modelName })) { + options.splice( + 1, + 1, + ...deepSeekReasoningEffortOptions.map(option => ({ + label: `${t('thinking.optionEnabled')} ยท ${option.label}`, + value: `${DEEPSEEK_REASONING_EFFORT_MODE_PREFIX}${option.value}`, + })) + ); + } else if ( supportsAnthropicReasoning(provider) && (supportsAnthropicAdaptive(modelName) || currentMode === 'adaptive') ) { @@ -369,7 +402,7 @@ const AIModelConfig: React.FC = () => { } return options; - }, [t]); + }, [deepSeekReasoningEffortOptions, editingConfig?.base_url, editingConfig?.name, t]); const categoryOptions = useMemo( () => [ @@ -1556,11 +1589,12 @@ const AIModelConfig: React.FC = () => { }; const reasoningEffortOptions = getDraftReasoningEffortOptions(reasoningCapabilityConfig); const showReasoningModeControl = !supportsResponsesReasoning(editingConfig.provider); + const supportsDeepSeekEffort = supportsDeepSeekReasoningEffort(reasoningCapabilityConfig); const showReasoningEffortControl = reasoningEffortOptions.length > 0 + && !supportsDeepSeekEffort && ( supportsResponsesReasoning(editingConfig.provider) || (supportsAnthropicReasoning(editingConfig.provider) && draft.reasoningMode === 'adaptive') - || (supportsDeepSeekReasoningEffort(reasoningCapabilityConfig) && draft.reasoningMode !== 'disabled') ); const showThinkingBudgetControl = supportsAnthropicReasoning(editingConfig.provider) && draft.reasoningMode === 'enabled' @@ -1680,8 +1714,13 @@ const AIModelConfig: React.FC = () => {
{t('thinking.mode')}