|
1 | 1 | import { useAnalytics } from '@app/component/analytics-context'; |
| 2 | +import { useHasPaidAccess } from '@core/auth/license'; |
2 | 3 | import type { ChatSendInput } from '@core/component/AI/component/input/buildRequest'; |
3 | 4 | import { ModelSelector } from '@core/component/AI/component/input/ModelSelector'; |
| 5 | +import { |
| 6 | + defaultModelForPlan, |
| 7 | + FREE_MODELS, |
| 8 | + PAID_MODELS, |
| 9 | +} from '@core/component/AI/constant'; |
4 | 10 | import { useChatInputContext } from '@core/component/AI/context'; |
5 | | -import { Model, type ToolSet } from '@core/component/AI/types'; |
| 11 | +import type { Model, ToolSet } from '@core/component/AI/types'; |
6 | 12 | import type { EditorConfigBuilder } from '@core/component/LexicalMarkdown/builder/MarkdownConfigBuilder'; |
7 | 13 | import { MarkdownShell } from '@core/component/LexicalMarkdown/builder/MarkdownShell'; |
8 | 14 | import { toast } from '@core/component/Toast/Toast'; |
@@ -48,20 +54,26 @@ export function ChatInput(props: ChatInputComponentProps) { |
48 | 54 | const model = input.model; |
49 | 55 | const generating = input.isGenerating; |
50 | 56 | const { showPaywall } = usePaywallState(); |
| 57 | + const hasPaidAccess = useHasPaidAccess(); |
51 | 58 |
|
52 | | - // Every model is offered in the picker; the backend enforces plan |
53 | | - // entitlements on send (a free user picking a pro model is rejected there). |
| 59 | + // Free and paid users see different selector lists. Free users get only the |
| 60 | + // free models (Haiku); premium models aren't shown at all. The 403 -> paywall |
| 61 | + // path is the backstop for anything that slips through to the backend. |
54 | 62 | const modelOptions = createMemo(() => |
55 | | - Object.values(Model).map((id) => ({ id, available: true })) |
| 63 | + (hasPaidAccess() ? PAID_MODELS : FREE_MODELS).map((id) => ({ |
| 64 | + id, |
| 65 | + available: true, |
| 66 | + })) |
56 | 67 | ); |
57 | 68 |
|
58 | | - // If the selected model isn't a known id (e.g. a stale persisted value), |
59 | | - // fall back to the first one so we never send something unroutable. |
| 69 | + // Keep the selected model valid for the current plan: if it isn't a known id |
| 70 | + // (e.g. a stale persisted value) or isn't available to this user (e.g. a free |
| 71 | + // user defaulted to Opus), fall back to the plan default so we never send |
| 72 | + // something unroutable or something the backend rejects. |
60 | 73 | createEffect(() => { |
61 | 74 | const options = modelOptions(); |
62 | | - if (options.some((o) => o.id === model())) return; |
63 | | - const [first] = options; |
64 | | - if (first) input.setModel(first.id); |
| 75 | + if (options.some((o) => o.id === model() && o.available)) return; |
| 76 | + input.setModel(defaultModelForPlan(hasPaidAccess())); |
65 | 77 | }); |
66 | 78 |
|
67 | 79 | let containerRef!: HTMLDivElement; |
|
0 commit comments