import type { CodexModelOptions, CodexReasoningEffort, ProviderModelOptions, ThreadId, } from "@t3tools/contracts"; import { getDefaultReasoningEffort, getReasoningEffortOptions, normalizeCodexModelOptions, resolveReasoningEffortForProvider, } from "@t3tools/shared/model"; import { memo, useState } from "react"; import { ChevronDownIcon } from "lucide-react"; import { useComposerDraftStore, useComposerThreadDraft } from "../../composerDraftStore"; import { Button } from "../ui/button"; import { Menu, MenuGroup, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator as MenuDivider, MenuTrigger, } from "../ui/menu"; const CODEX_REASONING_LABELS: Record = { low: "Low", medium: "Medium", high: "High", xhigh: "Extra High", }; function getSelectedCodexTraits(modelOptions: CodexModelOptions | null | undefined): { effort: CodexReasoningEffort; fastModeEnabled: boolean; } { const defaultReasoningEffort = getDefaultReasoningEffort("codex"); return { effort: resolveReasoningEffortForProvider("codex", modelOptions?.reasoningEffort) ?? defaultReasoningEffort, fastModeEnabled: modelOptions?.fastMode === true, }; } function CodexTraitsMenuContentImpl(props: { threadId: ThreadId }) { const draft = useComposerThreadDraft(props.threadId); const modelOptions = draft.modelOptions?.codex; const setModelOptions = useComposerDraftStore((store) => store.setModelOptions); const options = getReasoningEffortOptions("codex"); const defaultReasoningEffort = getDefaultReasoningEffort("codex"); const { effort, fastModeEnabled } = getSelectedCodexTraits(modelOptions); const setCodexModelOptions = (nextCodexModelOptions: CodexModelOptions | undefined) => { const { codex: _discardedCodex, ...otherProviderModelOptions } = draft.modelOptions ?? {}; const nextProviderModelOptions: ProviderModelOptions | undefined = nextCodexModelOptions ? { ...otherProviderModelOptions, codex: nextCodexModelOptions } : Object.keys(otherProviderModelOptions).length > 0 ? otherProviderModelOptions : undefined; setModelOptions(props.threadId, nextProviderModelOptions); }; return ( <>
Reasoning
{ if (!value) return; const nextEffort = options.find((option) => option === value); if (!nextEffort) return; setCodexModelOptions( normalizeCodexModelOptions({ ...modelOptions, reasoningEffort: nextEffort, }), ); }} > {options.map((option) => ( {CODEX_REASONING_LABELS[option]} {option === defaultReasoningEffort ? " (default)" : ""} ))}
Fast Mode
{ setCodexModelOptions( normalizeCodexModelOptions({ ...modelOptions, fastMode: value === "on", }), ); }} > off on
); } export const CodexTraitsMenuContent = memo(CodexTraitsMenuContentImpl); export const CodexTraitsPicker = memo(function CodexTraitsPicker(props: { threadId: ThreadId }) { const [isMenuOpen, setIsMenuOpen] = useState(false); const modelOptions = useComposerThreadDraft(props.threadId).modelOptions?.codex; const { effort, fastModeEnabled } = getSelectedCodexTraits(modelOptions); const triggerLabel = [CODEX_REASONING_LABELS[effort], ...(fastModeEnabled ? ["Fast"] : [])] .filter(Boolean) .join(" ยท "); return ( { setIsMenuOpen(open); }} > } > {triggerLabel} ); });