fix(cursor): support ACP parameterized model picker (#969)

* test: reproduce issue #968

* fix: support Cursor parameterized model picker (closes #968)
This commit is contained in:
SSU-WEI HUANG
2026-06-29 11:41:21 +08:00
committed by GitHub
parent 8493ec92f4
commit 5ade952218
8 changed files with 418 additions and 10 deletions
@@ -0,0 +1,31 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { ModelEffortSettingsSection } from './HappyComposer';
vi.mock('@/lib/use-translation', () => ({
useTranslation: () => ({
t: (key: string) => key === 'misc.variant' ? 'Variant' : key
})
}));
describe('ModelEffortSettingsSection', () => {
it('renders Cursor variant choices and marks the selected variant', () => {
render(
<ModelEffortSettingsSection
agentFlavor="cursor"
options={[
{ value: 'composer-2.5', label: 'Composer 2.5' },
{ value: 'composer-2.5-fast', label: 'Composer 2.5 Fast' }
]}
selectedValue="composer-2.5"
controlsDisabled={false}
onChange={() => {}}
/>
);
expect(screen.getByText('Variant')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^Composer 2.5$/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Composer 2.5 Fast/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^Composer 2.5$/ }).innerHTML).toContain('bg-[var(--app-link)]');
});
});
@@ -81,6 +81,57 @@ export type ComposerSendError = {
const defaultSuggestionHandler = async (): Promise<Suggestion[]> => []
export function ModelEffortSettingsSection(props: {
agentFlavor?: string | null
options: Array<{ value: string; label: string }>
selectedValue: string | null | undefined
controlsDisabled: boolean
onChange: (value: string) => void
}) {
const { t } = useTranslation()
const { agentFlavor, options, selectedValue, controlsDisabled, onChange } = props
return (
<div className="py-2">
<div className="px-3 pb-1 text-xs font-semibold text-[var(--app-hint)]">
{agentFlavor === 'cursor' ? t('misc.variant') : t('misc.effort')}
</div>
{options.map((option) => {
const isSelected = selectedValue === option.value
return (
<button
key={option.value}
type="button"
disabled={controlsDisabled}
className={`flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors ${
controlsDisabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer hover:bg-[var(--app-secondary-bg)]'
}`}
onClick={() => onChange(option.value)}
onMouseDown={(e) => e.preventDefault()}
>
<div
className={`flex h-4 w-4 items-center justify-center rounded-full border-2 ${
isSelected
? 'border-[var(--app-link)]'
: 'border-[var(--app-hint)]'
}`}
>
{isSelected && (
<div className="h-2 w-2 rounded-full bg-[var(--app-link)]" />
)}
</div>
<span className={isSelected ? 'text-[var(--app-link)]' : ''}>
{option.label}
</span>
</button>
)
})}
</div>
)
}
export function HappyComposer(props: {
sessionId?: string
disabled?: boolean
@@ -984,6 +1035,24 @@ export function HappyComposer(props: {
</div>
) : null}
{showModelSettings && showModelEffortSettings ? (
<div className="mx-3 h-px bg-[var(--app-divider)]" />
) : null}
{showModelEffortSettings ? (
<ModelEffortSettingsSection
agentFlavor={agentFlavor}
options={modelEffortOptions!}
selectedValue={selectedModelVariant ?? model}
controlsDisabled={controlsDisabled}
onChange={handleModelEffortChange}
/>
) : null}
{(showModelSettings || showModelEffortSettings) && showModelReasoningEffortSettings ? (
<div className="mx-3 h-px bg-[var(--app-divider)]" />
) : null}
{(showModelSettings || showModelEffortSettings || showModelReasoningEffortSettings) && showEffortSettings ? (
<div className="mx-3 h-px bg-[var(--app-divider)]" />
) : null}