diff --git a/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx b/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx index 6f2bedd9d4..b7de4c83ba 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx +++ b/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx @@ -41,6 +41,11 @@ interface LlmChatContent { enableExtendedThinking?: boolean; } +/** Extended model info with cost description for dropdown display */ +interface ModelOption extends LlmModelInfo { + costDescription?: string; +} + export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); @@ -49,7 +54,7 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) { const [streamingThinking, setStreamingThinking] = useState(""); const [toolActivity, setToolActivity] = useState(null); const [pendingCitations, setPendingCitations] = useState([]); - const [availableModels, setAvailableModels] = useState([]); + const [availableModels, setAvailableModels] = useState([]); const [selectedModel, setSelectedModel] = useState(""); const [enableWebSearch, setEnableWebSearch] = useState(true); const [enableNoteTools, setEnableNoteTools] = useState(false); @@ -62,7 +67,14 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) { // Fetch available models on mount useEffect(() => { getAvailableModels().then(models => { - setAvailableModels(models); + // Add cost description for display + const modelsWithDescription = models.map(m => ({ + ...m, + costDescription: m.costMultiplier + ? `${m.costMultiplier}x cost` + : undefined + })); + setAvailableModels(modelsWithDescription); // Set default model if not already selected if (!selectedModel) { const defaultModel = models.find(m => m.isDefault) || models[0]; @@ -384,6 +396,7 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) { values={availableModels} keyProperty="id" titleProperty="name" + descriptionProperty="costDescription" currentValue={selectedModel} onChange={handleModelChange} disabled={isStreaming} diff --git a/apps/server/src/services/llm/providers/anthropic.ts b/apps/server/src/services/llm/providers/anthropic.ts index 1b74db2844..61da343669 100644 --- a/apps/server/src/services/llm/providers/anthropic.ts +++ b/apps/server/src/services/llm/providers/anthropic.ts @@ -8,10 +8,18 @@ import type { LlmProvider, LlmProviderConfig, ModelInfo, ModelPricing, StreamRes const DEFAULT_MODEL = "claude-sonnet-4-20250514"; const DEFAULT_MAX_TOKENS = 8096; +/** + * Calculate effective cost for comparison (weighted average: 1 input + 3 output). + * Output is weighted more heavily as it's typically the dominant cost factor. + */ +function effectiveCost(pricing: ModelPricing): number { + return (pricing.input + 3 * pricing.output) / 4; +} + /** * Available Anthropic models with pricing (USD per million tokens). */ -const AVAILABLE_MODELS: ModelInfo[] = [ +const BASE_MODELS: Omit[] = [ // Claude 4 family { id: "claude-sonnet-4-20250514", @@ -37,6 +45,15 @@ const AVAILABLE_MODELS: ModelInfo[] = [ } ]; +// Find cheapest model as baseline for cost multiplier +const baselineCost = Math.min(...BASE_MODELS.map(m => effectiveCost(m.pricing))); + +// Build models with cost multipliers +const AVAILABLE_MODELS: ModelInfo[] = BASE_MODELS.map(m => ({ + ...m, + costMultiplier: Math.round((effectiveCost(m.pricing) / baselineCost) * 10) / 10 +})); + // Build pricing lookup from available models const MODEL_PRICING: Record = Object.fromEntries( AVAILABLE_MODELS.map(m => [m.id, m.pricing]) diff --git a/apps/server/src/services/llm/types.ts b/apps/server/src/services/llm/types.ts index 852d2e645d..f3e87a8406 100644 --- a/apps/server/src/services/llm/types.ts +++ b/apps/server/src/services/llm/types.ts @@ -42,6 +42,8 @@ export interface ModelInfo { pricing: ModelPricing; /** Whether this is the default model */ isDefault?: boolean; + /** Cost multiplier relative to the cheapest model (1x = cheapest) */ + costMultiplier?: number; } export interface LlmProvider { diff --git a/packages/commons/src/lib/llm_api.ts b/packages/commons/src/lib/llm_api.ts index 5f52ff01af..81e93d1eef 100644 --- a/packages/commons/src/lib/llm_api.ts +++ b/packages/commons/src/lib/llm_api.ts @@ -63,6 +63,8 @@ export interface LlmModelInfo { pricing: LlmModelPricing; /** Whether this is the default model */ isDefault?: boolean; + /** Cost multiplier relative to the cheapest model (1x = cheapest) */ + costMultiplier?: number; } /**