From 6800e6d3c2f889fed99112e33a166eabdc2b2996 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:57:25 +0000 Subject: [PATCH 1/2] feat: implement tier-based default model selection - Add getDefaultModelForUserTier function to determine default model based on billing status - Free users: Llama 3.3 70B (existing default) - Starter users: Gemma 3 27B - Pro/Team users: DeepSeek R1 0528 671B - Update LocalStateProvider to automatically set tier-appropriate default on billing status load - Only updates default if user hasn't manually changed from the initial default - Update persistChat and addChat to use tier-based defaults Co-authored-by: Marks --- frontend/src/state/LocalStateContext.tsx | 54 ++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/frontend/src/state/LocalStateContext.tsx b/frontend/src/state/LocalStateContext.tsx index bd6d7751e..cdf185cfe 100644 --- a/frontend/src/state/LocalStateContext.tsx +++ b/frontend/src/state/LocalStateContext.tsx @@ -1,5 +1,5 @@ import { useOpenSecret } from "@opensecret/react"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { BillingStatus } from "@/billing/billingApi"; import { LocalStateContext, Chat, HistoryItem, OpenSecretModel } from "./LocalStateContextDef"; import { aliasModelName } from "@/utils/utils"; @@ -14,6 +14,34 @@ export { export const DEFAULT_MODEL_ID = "llama3-3-70b"; +/** + * Determines the default model based on user's billing status + * Free users: Llama 3.3 70B (existing default) + * Starter users: Gemma 3 27B + * Pro/Team users: DeepSeek R1 0528 671B + */ +export function getDefaultModelForUserTier(billingStatus: BillingStatus | null): string { + if (!billingStatus) { + // No billing status = free user + return DEFAULT_MODEL_ID; // "llama3-3-70b" + } + + const planName = billingStatus.product_name?.toLowerCase() || ""; + + // Pro and Team users get DeepSeek 671B + if (planName.includes("pro") || planName.includes("max") || planName.includes("team")) { + return "deepseek-r1-0528"; + } + + // Starter users get Gemma + if (planName.includes("starter")) { + return "google/gemma-3-27b-it"; + } + + // Default fallback for free users or unknown plans + return DEFAULT_MODEL_ID; +} + export const LocalStateProvider = ({ children }: { children: React.ReactNode }) => { /** The model that should be assumed when a chat doesn't yet have one */ const llamaModel: OpenSecretModel = { @@ -38,10 +66,28 @@ export const LocalStateProvider = ({ children }: { children: React.ReactNode }) const { get, put, list, del } = useOpenSecret(); + // Track if this is the first time billing status is set (fresh app launch) + const [hasInitializedDefaultModel, setHasInitializedDefaultModel] = useState(false); + + // Update default model when billing status is first loaded (fresh app launch only) + useEffect(() => { + if (localState.billingStatus && !hasInitializedDefaultModel) { + const tierBasedDefault = getDefaultModelForUserTier(localState.billingStatus); + // Only update if the current model is still the default (hasn't been changed by user) + if ( + localState.model === DEFAULT_MODEL_ID || + localState.model === aliasModelName(import.meta.env.VITE_DEV_MODEL_OVERRIDE) + ) { + setLocalState((prev) => ({ ...prev, model: tierBasedDefault })); + } + setHasInitializedDefaultModel(true); + } + }, [localState.billingStatus, hasInitializedDefaultModel, localState.model]); + async function persistChat(chat: Chat) { const chatToSave = { - /** If a model is missing, assume the default Llama and write it now */ - model: aliasModelName(chat.model) || DEFAULT_MODEL_ID, + /** If a model is missing, use the tier-based default or fallback to Llama */ + model: aliasModelName(chat.model) || getDefaultModelForUserTier(localState.billingStatus), ...chat }; @@ -114,7 +160,7 @@ export const LocalStateProvider = ({ children }: { children: React.ReactNode }) id: window.crypto.randomUUID(), title, messages: [], - model: localState.model + model: localState.model || getDefaultModelForUserTier(localState.billingStatus) }; await persistChat(newChat); return newChat.id; From 81d4370337ff070e8d33aff6194afbbc9b9a70ad Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:42:35 +0000 Subject: [PATCH 2/2] fix: address critical code review issues in tier-based model selection - Fix dev model override preservation by only updating when model equals DEFAULT_MODEL_ID - Fix chat model backfill by correcting object spread order to preserve computed model value - Gate console logging to DEV mode and sanitize logged data for security - Update setBillingStatus type to accept BillingStatus | null for consistency Co-authored-by: Marks --- frontend/src/state/LocalStateContext.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/state/LocalStateContext.tsx b/frontend/src/state/LocalStateContext.tsx index cdf185cfe..a3abaaf9a 100644 --- a/frontend/src/state/LocalStateContext.tsx +++ b/frontend/src/state/LocalStateContext.tsx @@ -74,10 +74,7 @@ export const LocalStateProvider = ({ children }: { children: React.ReactNode }) if (localState.billingStatus && !hasInitializedDefaultModel) { const tierBasedDefault = getDefaultModelForUserTier(localState.billingStatus); // Only update if the current model is still the default (hasn't been changed by user) - if ( - localState.model === DEFAULT_MODEL_ID || - localState.model === aliasModelName(import.meta.env.VITE_DEV_MODEL_OVERRIDE) - ) { + if (localState.model === DEFAULT_MODEL_ID) { setLocalState((prev) => ({ ...prev, model: tierBasedDefault })); } setHasInitializedDefaultModel(true); @@ -86,12 +83,14 @@ export const LocalStateProvider = ({ children }: { children: React.ReactNode }) async function persistChat(chat: Chat) { const chatToSave = { + ...chat, /** If a model is missing, use the tier-based default or fallback to Llama */ - model: aliasModelName(chat.model) || getDefaultModelForUserTier(localState.billingStatus), - ...chat + model: aliasModelName(chat.model) || getDefaultModelForUserTier(localState.billingStatus) }; - console.log("Persisting chat:", chatToSave); + if (import.meta.env.DEV) { + console.debug("Persisting chat:", { id: chatToSave.id, title: chatToSave.title }); + } try { // Save the chat to storage await put(`chat_${chat.id}`, JSON.stringify(chatToSave)); @@ -143,7 +142,7 @@ export const LocalStateProvider = ({ children }: { children: React.ReactNode }) setLocalState((prev) => ({ ...prev, userImages: images })); } - function setBillingStatus(status: BillingStatus) { + function setBillingStatus(status: BillingStatus | null) { setLocalState((prev) => ({ ...prev, billingStatus: status })); }