diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 81b4469f24..e823dd5dc6 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -22605,6 +22605,53 @@ "supports_vision": true, "supports_web_search": true }, + "openrouter/google/gemini-3-flash-preview": { + "cache_read_input_token_cost": 5e-08, + "input_cost_per_audio_token": 1e-06, + "input_cost_per_token": 5e-07, + "litellm_provider": "openrouter", + "max_audio_length_hours": 8.4, + "max_audio_per_prompt": 1, + "max_images_per_prompt": 3000, + "max_input_tokens": 1048576, + "max_output_tokens": 65535, + "max_pdf_size_mb": 30, + "max_tokens": 65535, + "max_video_length": 1, + "max_videos_per_prompt": 10, + "mode": "chat", + "output_cost_per_reasoning_token": 3e-06, + "output_cost_per_token": 3e-06, + "rpm": 2000, + "source": "https://ai.google.dev/pricing/gemini-3", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image", + "audio", + "video" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_vision": true, + "supports_web_search": true, + "tpm": 800000 + }, "openrouter/google/gemini-pro-1.5": { "input_cost_per_image": 0.00265, "input_cost_per_token": 2.5e-06, diff --git a/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/export_utils.ts b/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/export_utils.ts index e02e828845..a205efa3bf 100644 --- a/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/export_utils.ts +++ b/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/export_utils.ts @@ -89,7 +89,7 @@ export const exportToPDF = (result: CostEstimateResponse): void => { -

LLM Cost Estimate Report

+

🚅 LiteLLM Cost Estimate Report

Model: ${result.model}

@@ -214,7 +214,7 @@ export const exportToPDF = (result: CostEstimateResponse): void => { export const exportToCSV = (result: CostEstimateResponse): void => { const rows = [ - ["LLM Cost Estimate Report"], + ["🚅 LiteLLM Cost Estimate Report"], [""], ["Configuration"], ["Model", result.model], diff --git a/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/index.tsx b/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/index.tsx index fff6475e8a..426d832bfe 100644 --- a/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/index.tsx +++ b/ui/litellm-dashboard/src/components/CostTrackingSettings/pricing_calculator/index.tsx @@ -1,31 +1,207 @@ -import React, { useCallback } from "react"; -import PricingForm from "./pricing_form"; -import CostResults from "./cost_results"; -import { useCostEstimate } from "./use_cost_estimate"; -import { PricingCalculatorProps, PricingFormValues } from "./types"; +import React, { useState, useCallback } from "react"; +import { Table, Select, InputNumber, Button, Radio } from "antd"; +import { DeleteOutlined, PlusOutlined } from "@ant-design/icons"; +import { PricingCalculatorProps, ModelEntry } from "./types"; +import MultiCostResults from "./multi_cost_results"; +import { useMultiCostEstimate } from "./use_multi_cost_estimate"; + +type TimePeriod = "day" | "month"; + +const generateId = () => `entry-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + +const createDefaultEntry = (): ModelEntry => ({ + id: generateId(), + model: "", + input_tokens: 1000, + output_tokens: 500, + num_requests_per_day: undefined, + num_requests_per_month: undefined, +}); const PricingCalculator: React.FC = ({ accessToken, models, }) => { - const { loading, result, debouncedFetch } = useCostEstimate(accessToken); + const [entries, setEntries] = useState([createDefaultEntry()]); + const [timePeriod, setTimePeriod] = useState("month"); + const { debouncedFetchForEntry, removeEntry, getMultiModelResult } = + useMultiCostEstimate(accessToken); - const handleValuesChange = useCallback( - (_changedValues: Partial, allValues: PricingFormValues) => { - if (allValues.model) { - debouncedFetch(allValues); - } + const handleEntryChange = useCallback( + (id: string, field: keyof ModelEntry, value: string | number | undefined) => { + setEntries((prev) => { + const updated = prev.map((entry) => + entry.id === id ? { ...entry, [field]: value } : entry + ); + const changedEntry = updated.find((e) => e.id === id); + if (changedEntry && changedEntry.model) { + debouncedFetchForEntry(changedEntry); + } + return updated; + }); }, - [debouncedFetch] + [debouncedFetchForEntry] ); + const handleTimePeriodChange = useCallback((period: TimePeriod) => { + setTimePeriod(period); + // Clear the opposite field for all entries when switching + setEntries((prev) => + prev.map((entry) => ({ + ...entry, + num_requests_per_day: period === "day" ? entry.num_requests_per_day : undefined, + num_requests_per_month: period === "month" ? entry.num_requests_per_month : undefined, + })) + ); + }, []); + + const handleAddEntry = useCallback(() => { + setEntries((prev) => [...prev, createDefaultEntry()]); + }, []); + + const handleRemoveEntry = useCallback( + (id: string) => { + setEntries((prev) => prev.filter((entry) => entry.id !== id)); + removeEntry(id); + }, + [removeEntry] + ); + + const multiModelResult = getMultiModelResult(entries); + + const columns = [ + { + title: "Model", + dataIndex: "model", + key: "model", + width: "35%", + render: (_: string, record: ModelEntry) => ( +