diff --git a/evals/packages/types/src/roo-code.ts b/evals/packages/types/src/roo-code.ts index 7c982f2944..95d97e6cb6 100644 --- a/evals/packages/types/src/roo-code.ts +++ b/evals/packages/types/src/roo-code.ts @@ -357,6 +357,7 @@ export const providerSettingsSchema = z.object({ lmStudioSpeculativeDecodingEnabled: z.boolean().optional(), // Gemini geminiApiKey: z.string().optional(), + geminiFreeTier: z.boolean().optional(), googleGeminiBaseUrl: z.string().optional(), // OpenAI Native openAiNativeApiKey: z.string().optional(), @@ -444,6 +445,8 @@ const providerSettingsRecord: ProviderSettingsRecord = { lmStudioSpeculativeDecodingEnabled: undefined, // Gemini geminiApiKey: undefined, + geminiFreeTier: undefined, + geminiModelInfo: undefined, // Keep this uncommented googleGeminiBaseUrl: undefined, // OpenAI Native openAiNativeApiKey: undefined, @@ -538,6 +541,7 @@ export const globalSettingsSchema = z.object({ language: languagesSchema.optional(), telemetrySetting: telemetrySettingsSchema.optional(), + geminiFreeTier: z.boolean().optional(), // Added Gemini Free Tier setting mcpEnabled: z.boolean().optional(), enableMcpServerCreation: z.boolean().optional(), @@ -613,6 +617,7 @@ const globalSettingsRecord: GlobalSettingsRecord = { language: undefined, telemetrySetting: undefined, + geminiFreeTier: undefined, // Added Gemini Free Tier setting mcpEnabled: undefined, enableMcpServerCreation: undefined, diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 3a1a22439e..e202ee731b 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1835,15 +1835,18 @@ export class Cline extends EventEmitter { tokensOut: outputTokens, cacheWrites: cacheWriteTokens, cacheReads: cacheReadTokens, + // Check for Gemini free tier before calculating cost cost: - totalCost ?? - calculateApiCostAnthropic( - this.api.getModel().info, - inputTokens, - outputTokens, - cacheWriteTokens, - cacheReadTokens, - ), + this.apiConfiguration.apiProvider === "gemini" && this.apiConfiguration.geminiFreeTier === true + ? 0 + : (totalCost ?? + calculateApiCostAnthropic( + this.api.getModel().info, + inputTokens, + outputTokens, + cacheWriteTokens, + cacheReadTokens, + )), cancelReason, streamingFailedMessage, } satisfies ClineApiReqInfo) diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 212a673b95..5dbff3365c 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -153,13 +153,45 @@ export class ProviderSettingsManager { * Preserves the ID from the input 'config' object if it exists, * otherwise generates a new one (for creation scenarios). */ + /** + * Save a config with the given name. + * Preserves the ID from the input 'config' object if it exists, + * otherwise generates a new one (for creation scenarios). + * + * Note: Special care is taken to ensure boolean values (including `true`) + * are properly preserved during serialization/deserialization. + */ public async saveConfig(name: string, config: ProviderSettingsWithId) { try { return await this.lock(async () => { const providerProfiles = await this.load() // Preserve the existing ID if this is an update to an existing config. const existingId = providerProfiles.apiConfigs[name]?.id - providerProfiles.apiConfigs[name] = { ...config, id: config.id || existingId || this.generateId() } + + // Create a deep copy of the config to ensure all properties (including booleans) are preserved + const configCopy = JSON.parse(JSON.stringify(config)) + + // Ensure apiConfigs exists + if (!providerProfiles.apiConfigs) { + providerProfiles.apiConfigs = {} + } + + // Create a new apiConfigs object with both existing configs and the new/updated one + providerProfiles.apiConfigs = { + ...providerProfiles.apiConfigs, + [name]: { + ...configCopy, + id: config.id || existingId || this.generateId(), + }, + } + + // Debug log to inspect providerProfiles before storing + console.log("[saveConfig] providerProfiles before store:", { + currentApiConfigName: providerProfiles.currentApiConfigName, + apiConfigs: providerProfiles.apiConfigs, + modeApiConfigs: providerProfiles.modeApiConfigs, + }) + await this.store(providerProfiles) }) } catch (error) { @@ -321,38 +353,122 @@ export class ProviderSettingsManager { return this.defaultProviderProfiles } - const providerProfiles = providerProfilesSchema - .extend({ - apiConfigs: z.record(z.string(), z.any()), - }) - .parse(JSON.parse(content)) - - const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce( - (acc, [key, apiConfig]) => { - const result = providerSettingsWithIdSchema.safeParse(apiConfig) - return result.success ? { ...acc, [key]: result.data } : acc - }, - {} as Record, - ) - - return { - ...providerProfiles, - apiConfigs: Object.fromEntries( - Object.entries(apiConfigs).filter(([_, apiConfig]) => apiConfig !== null), - ), + // Parse the content with a reviver function to ensure boolean values are preserved + const parsedContent = JSON.parse(content, (key, value) => { + // Return the value as is, ensuring booleans are preserved + return value + }) + + // Validate the parsed content using safeParse to allow for recovery + const validationResult = providerProfilesSchema.safeParse(parsedContent) + + if (validationResult.success) { + return validationResult.data + } else { + // Validation failed, attempt recovery if errors are only in apiConfigs + const zodError = validationResult.error + telemetryService.captureSchemaValidationError({ schemaName: "ProviderProfiles", error: zodError }) + + const nonApiConfigErrors = zodError.issues.filter((issue) => issue.path[0] !== "apiConfigs") + + if (nonApiConfigErrors.length === 0 && parsedContent.apiConfigs) { + // Errors are only within apiConfigs, attempt filtering + try { + console.warn( + "ProviderSettingsManager: Invalid entries found in apiConfigs during load. Attempting to filter and recover.", + JSON.stringify( + zodError.issues.filter((issue) => issue.path[0] === "apiConfigs"), + null, + 2, + ), + ) + + const filteredApiConfigs: Record = {} + for (const [name, config] of Object.entries(parsedContent.apiConfigs)) { + // Explicitly check if config is a valid object before parsing + if (typeof config !== "object" || config === null) { + console.warn( + `ProviderSettingsManager: Skipping invalid profile '${name}' during load: Expected object, received ${typeof config}.`, + ) + continue // Skip this entry entirely + } + // Now safeParse the object + const profileValidation = providerSettingsWithIdSchema.safeParse(config) + if (profileValidation.success) { + filteredApiConfigs[name] = profileValidation.data + } else { + console.warn( + `ProviderSettingsManager: Removing invalid profile '${name}' during load. Issues:`, + JSON.stringify(profileValidation.error.issues, null, 2), + ) + } + } + + // Ensure the currentApiConfigName still points to a valid config if possible + let currentApiConfigName = parsedContent.currentApiConfigName + // Check if the original current name exists AND is valid after filtering + if (!filteredApiConfigs[currentApiConfigName]) { + const originalName = parsedContent.currentApiConfigName + const availableNames = Object.keys(filteredApiConfigs) + // Fallback logic: try 'default', then first available, then manager's default + currentApiConfigName = availableNames.includes("default") + ? "default" + : availableNames[0] || this.defaultProviderProfiles.currentApiConfigName + + if (originalName && originalName !== currentApiConfigName) { + console.warn( + `ProviderSettingsManager: Current API config '${originalName}' was invalid or removed. Switched to '${currentApiConfigName}'.`, + ) + } else if (!originalName) { + console.warn( + `ProviderSettingsManager: Original currentApiConfigName was missing or invalid. Switched to '${currentApiConfigName}'.`, + ) + } + // Persisting this change immediately might be better, but requires storing here. + // Let's defer persistence to the next save/store operation for simplicity. + } + + // Return a recovered object + return { + currentApiConfigName: currentApiConfigName, + apiConfigs: filteredApiConfigs, + modeApiConfigs: parsedContent.modeApiConfigs || this.defaultModeApiConfigs, + migrations: parsedContent.migrations || this.defaultProviderProfiles.migrations, + } + } catch (recoveryError) { + console.error("ProviderSettingsManager: Error occurred during recovery logic:", recoveryError) + // Re-throw the recovery error to be caught by the outer catch block + throw recoveryError // Ensures it's caught by the final catch + } + } else { + // Errors exist outside apiConfigs or apiConfigs is missing, cannot recover safely + console.error( + "ProviderSettingsManager: Unrecoverable Zod validation failed during load. Issues:", + JSON.stringify(zodError.issues, null, 2), + ) + // Throw a specific error for unrecoverable Zod issues + throw new Error(`Unrecoverable validation errors in provider profiles structure: ${zodError}`) + } } } catch (error) { - if (error instanceof ZodError) { - telemetryService.captureSchemaValidationError({ schemaName: "ProviderProfiles", error }) - } - + // Catch non-Zod errors or errors during recovery logic + console.error("ProviderSettingsManager: Error during load or recovery:", error) throw new Error(`Failed to read provider profiles from secrets: ${error}`) } } private async store(providerProfiles: ProviderProfiles) { try { - await this.context.secrets.store(this.secretsKey, JSON.stringify(providerProfiles, null, 2)) + // Use a custom replacer function to ensure boolean values are preserved + const replacer = (key: string, value: any) => { + // Explicitly handle boolean values to ensure they're preserved + if (value === true || value === false) { + return value + } + return value + } + + await this.context.secrets.store(this.secretsKey, JSON.stringify(providerProfiles, replacer, 2)) } catch (error) { throw new Error(`Failed to write provider profiles to secrets: ${error}`) } diff --git a/src/core/config/__tests__/ProviderSettingsManager.test.ts b/src/core/config/__tests__/ProviderSettingsManager.test.ts index 91f5adbdf9..16797063f1 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.test.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.test.ts @@ -300,6 +300,65 @@ describe("ProviderSettingsManager", () => { "Failed to save config: Error: Failed to write provider profiles to secrets: Error: Storage failed", ) }) + + it("should save boolean values correctly, even when true", async () => { + // Create a clean initial state + const initialState = { + currentApiConfigName: "default", + apiConfigs: { + default: { id: "default-id" }, + }, + modeApiConfigs: { + code: "default", + architect: "default", + ask: "default", + }, + migrations: { + rateLimitSecondsMigrated: true, + }, + } + + // Mock the initial state - use mockResolvedValue instead of mockResolvedValueOnce + // to ensure it returns the same value for all calls during the test + mockSecrets.get.mockResolvedValue(JSON.stringify(initialState)) + + // Create a config with geminiFreeTier set to true + const configWithTrueBoolean: ProviderSettings = { + apiProvider: "gemini", + geminiFreeTier: true, + } + + // Clear any previous store calls + mockSecrets.store.mockClear() + + // Save the config + await providerSettingsManager.saveConfig("test", configWithTrueBoolean) + + // Verify store was called + expect(mockSecrets.store).toHaveBeenCalled() + + // Get what was stored + const storeCall = mockSecrets.store.mock.calls[0] + expect(storeCall[0]).toBe("roo_cline_config_api_config") + + const storedData = JSON.parse(storeCall[1]) + + // Verify the structure + expect(storedData).toHaveProperty("apiConfigs.test") + expect(storedData.apiConfigs.test).toHaveProperty("apiProvider", "gemini") + expect(storedData.apiConfigs.test).toHaveProperty("geminiFreeTier", true) + + // Now test loading the config + mockSecrets.get.mockReset() + mockSecrets.get.mockResolvedValueOnce(JSON.stringify(storedData)) + + // Load the config + const loadedConfig = await providerSettingsManager.loadConfig("test") + + // Verify the loaded config + expect(loadedConfig).toHaveProperty("apiProvider", "gemini") + expect(loadedConfig).toHaveProperty("geminiFreeTier", true) + }) }) describe("DeleteConfig", () => { diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index eb778c80ae..ea5169d9d9 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -129,6 +129,7 @@ type ProviderSettings = { lmStudioSpeculativeDecodingEnabled?: boolean | undefined geminiApiKey?: string | undefined googleGeminiBaseUrl?: string | undefined + geminiFreeTier?: boolean | undefined openAiNativeApiKey?: string | undefined mistralApiKey?: string | undefined mistralCodestralUrl?: string | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index 3a53a2f9ff..9303ea9b72 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -130,6 +130,7 @@ type ProviderSettings = { lmStudioSpeculativeDecodingEnabled?: boolean | undefined geminiApiKey?: string | undefined googleGeminiBaseUrl?: string | undefined + geminiFreeTier?: boolean | undefined openAiNativeApiKey?: string | undefined mistralApiKey?: string | undefined mistralCodestralUrl?: string | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 80b6bbe197..2b57b4957f 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -377,6 +377,7 @@ export const providerSettingsSchema = z.object({ // Gemini geminiApiKey: z.string().optional(), googleGeminiBaseUrl: z.string().optional(), + geminiFreeTier: z.boolean().optional(), // OpenAI Native openAiNativeApiKey: z.string().optional(), // Mistral @@ -466,6 +467,7 @@ const providerSettingsRecord: ProviderSettingsRecord = { // Gemini geminiApiKey: undefined, googleGeminiBaseUrl: undefined, + geminiFreeTier: undefined, // OpenAI Native openAiNativeApiKey: undefined, // Mistral @@ -494,7 +496,7 @@ const providerSettingsRecord: ProviderSettingsRecord = { fakeAi: undefined, } -export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Keys[] +export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as (keyof ProviderSettings)[] /** * GlobalSettings diff --git a/src/services/checkpoints/__tests__/ShadowCheckpointService.test.ts b/src/services/checkpoints/__tests__/ShadowCheckpointService.test.ts index ecf791e949..fd55c4ac1d 100644 --- a/src/services/checkpoints/__tests__/ShadowCheckpointService.test.ts +++ b/src/services/checkpoints/__tests__/ShadowCheckpointService.test.ts @@ -56,6 +56,9 @@ describe.each([ [RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"], [RepoPerWorkspaceCheckpointService, "RepoPerWorkspaceCheckpointService"], ])("CheckpointService", (klass, prefix) => { + // Increase timeout for Git operations + jest.setTimeout(20000) + const taskId = "test-task" let workspaceGit: SimpleGit @@ -86,6 +89,7 @@ describe.each([ describe(`${klass.name}#getDiff`, () => { it("returns the correct diff between commits", async () => { + jest.setTimeout(20000) // Increase timeout for Git operations await fs.writeFile(testFile, "Ahoy, world!") const commit1 = await service.saveCheckpoint("Ahoy, world!") expect(commit1?.commit).toBeTruthy() diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 558d01f958..2dcf5fd09c 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -122,13 +122,8 @@ const TaskHeader: React.FC = ({ }, [task.text, windowWidth]) const isCostAvailable = useMemo(() => { - return ( - apiConfiguration?.apiProvider !== "openai" && - apiConfiguration?.apiProvider !== "ollama" && - apiConfiguration?.apiProvider !== "lmstudio" && - apiConfiguration?.apiProvider !== "gemini" - ) - }, [apiConfiguration?.apiProvider]) + return totalCost !== null && totalCost !== undefined && totalCost > 0 && !isNaN(totalCost) + }, [totalCost]) const shouldShowPromptCacheInfo = doesModelSupportPromptCache && apiConfiguration?.apiProvider !== "openrouter" diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx new file mode 100644 index 0000000000..41d45bfeac --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx @@ -0,0 +1,114 @@ +import React from "react" +import { render, screen } from "@testing-library/react" +import TaskHeader from "../TaskHeader" +import { ApiConfiguration } from "../../../../../src/shared/api" + +// Mock the vscode API +jest.mock("@/utils/vscode", () => ({ + vscode: { + postMessage: jest.fn(), + }, +})) + +// Mock the ExtensionStateContext +jest.mock("../../../context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + apiConfiguration: { + apiProvider: "anthropic", + apiKey: "test-api-key", // Add relevant fields + apiModelId: "claude-3-opus-20240229", // Add relevant fields + } as ApiConfiguration, // Optional: Add type assertion if ApiConfiguration is imported + currentTaskItem: null, + }), +})) + +describe("TaskHeader", () => { + const defaultProps = { + task: { text: "Test task", images: [] }, + tokensIn: 100, + tokensOut: 50, + doesModelSupportPromptCache: true, + totalCost: 0.05, + contextTokens: 200, + onClose: jest.fn(), + } + + it("should display cost when totalCost is greater than 0", () => { + render( + , + ) + expect(screen.getByText("$0.0500")).toBeInTheDocument() + }) + + it("should not display cost when totalCost is 0", () => { + render( + , + ) + expect(screen.queryByText("$0.0000")).not.toBeInTheDocument() + }) + + it("should not display cost when totalCost is null", () => { + render( + , + ) + expect(screen.queryByText(/\$/)).not.toBeInTheDocument() + }) + + it("should not display cost when totalCost is undefined", () => { + render( + , + ) + expect(screen.queryByText(/\$/)).not.toBeInTheDocument() + }) + + it("should not display cost when totalCost is NaN", () => { + render( + , + ) + expect(screen.queryByText(/\$/)).not.toBeInTheDocument() + }) +}) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 21f40c92af..339fe57a79 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -77,7 +77,6 @@ const ApiOptions = ({ setErrorMessage, }: ApiOptionsProps) => { const { t } = useAppTranslation() - const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) const [vsCodeLmModels, setVsCodeLmModels] = useState([]) @@ -767,6 +766,19 @@ const ApiOptions = ({ /> )} +
+ { + setApiConfigurationField("geminiFreeTier", checked) + }}> + {t("settings:providers.useFreeTier")} + + {/* Keep description separate as in original */} +
+ {t("settings:providers.useFreeTierDescription")} +
+
)} @@ -1669,6 +1681,7 @@ const ApiOptions = ({ modelInfo={selectedModelInfo} isDescriptionExpanded={isDescriptionExpanded} setIsDescriptionExpanded={setIsDescriptionExpanded} + apiConfiguration={apiConfiguration} // Pass the config down /> void + apiConfiguration?: ApiConfiguration // Added optional apiConfiguration prop } export const ModelInfoView = ({ @@ -21,9 +22,12 @@ export const ModelInfoView = ({ modelInfo, isDescriptionExpanded, setIsDescriptionExpanded, + apiConfiguration, // Destructure the new prop }: ModelInfoViewProps) => { const { t } = useAppTranslation() const isGemini = useMemo(() => Object.keys(geminiModels).includes(selectedModelId), [selectedModelId]) + // Determine if Gemini free tier is active + const isGeminiFreeTier = apiConfiguration?.apiProvider === "gemini" && apiConfiguration?.geminiFreeTier === true const infoItems = [ ), - modelInfo.inputPrice !== undefined && modelInfo.inputPrice > 0 && ( + // Display input price (show $0 if free tier is active) + modelInfo.inputPrice !== undefined && ( <> {t("settings:modelInfo.inputPrice")}:{" "} - {formatPrice(modelInfo.inputPrice)} / 1M tokens + {formatPrice(isGeminiFreeTier ? 0 : modelInfo.inputPrice)} / 1M tokens ), - modelInfo.outputPrice !== undefined && modelInfo.outputPrice > 0 && ( + // Display output price (show $0 if free tier is active) + modelInfo.outputPrice !== undefined && ( <> {t("settings:modelInfo.outputPrice")}:{" "} - {formatPrice(modelInfo.outputPrice)} / 1M tokens + {formatPrice(isGeminiFreeTier ? 0 : modelInfo.outputPrice)} / 1M tokens ), modelInfo.supportsPromptCache && modelInfo.cacheReadsPrice && ( diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index af009b4337..be2a37eb30 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Obtenir clau API de DeepSeek", "geminiApiKey": "Clau API de Gemini", "getGeminiApiKey": "Obtenir clau API de Gemini", + "useFreeTier": "Utilitza el nivell gratuït", + "useFreeTierDescription": "Estableix els preus d'entrada i sortida a zero per al càlcul de costos.", "openAiApiKey": "Clau API d'OpenAI", "openAiBaseUrl": "URL base", "getOpenAiApiKey": "Obtenir clau API d'OpenAI", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 11ff0d5a06..8261007921 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten", "geminiApiKey": "Gemini API-Schlüssel", "getGeminiApiKey": "Gemini API-Schlüssel erhalten", + "useFreeTier": "Kostenlose Stufe nutzen", + "useFreeTierDescription": "Setzt Eingabe- und Ausgabepreise für Kostenzwecke auf null.", "openAiApiKey": "OpenAI API-Schlüssel", "openAiBaseUrl": "Basis-URL", "getOpenAiApiKey": "OpenAI API-Schlüssel erhalten", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index b494ea01e5..f691916f33 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Get DeepSeek API Key", "geminiApiKey": "Gemini API Key", "getGeminiApiKey": "Get Gemini API Key", + "useFreeTier": "Use Free Tier", + "useFreeTierDescription": "Sets input and output prices to zero for cost calculation purposes.", "openAiApiKey": "OpenAI API Key", "openAiBaseUrl": "Base URL", "getOpenAiApiKey": "Get OpenAI API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 0b1f40d5a2..aa7b8709ef 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Obtener clave API de DeepSeek", "geminiApiKey": "Clave API de Gemini", "getGeminiApiKey": "Obtener clave API de Gemini", + "useFreeTier": "Usar nivel gratuito", + "useFreeTierDescription": "Establece los precios de entrada y salida a cero para fines de cálculo de costos.", "openAiApiKey": "Clave API de OpenAI", "openAiBaseUrl": "URL base", "getOpenAiApiKey": "Obtener clave API de OpenAI", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index d2499a90ad..521be5b611 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Obtenir la clé API DeepSeek", "geminiApiKey": "Clé API Gemini", "getGeminiApiKey": "Obtenir la clé API Gemini", + "useFreeTier": "Utiliser le niveau gratuit", + "useFreeTierDescription": "Définit les prix d'entrée et de sortie à zéro pour le calcul des coûts.", "openAiApiKey": "Clé API OpenAI", "openAiBaseUrl": "URL de base", "getOpenAiApiKey": "Obtenir la clé API OpenAI", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 8572ad1008..115d54ecb1 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें", "geminiApiKey": "Gemini API कुंजी", "getGeminiApiKey": "Gemini API कुंजी प्राप्त करें", + "useFreeTier": "मुफ़्त टियर का उपयोग करें", + "useFreeTierDescription": "लागत गणना उद्देश्यों के लिए इनपुट और आउटपुट कीमतों को शून्य पर सेट करता है।", "openAiApiKey": "OpenAI API कुंजी", "openAiBaseUrl": "बेस URL", "getOpenAiApiKey": "OpenAI API कुंजी प्राप्त करें", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 50282e98f9..e6a465c0a9 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Ottieni chiave API DeepSeek", "geminiApiKey": "Chiave API Gemini", "getGeminiApiKey": "Ottieni chiave API Gemini", + "useFreeTier": "Usa livello gratuito", + "useFreeTierDescription": "Imposta i prezzi di input e output a zero per il calcolo dei costi.", "openAiApiKey": "Chiave API OpenAI", "openAiBaseUrl": "URL base", "getOpenAiApiKey": "Ottieni chiave API OpenAI", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index e41d8e361c..c8821bd165 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "DeepSeek APIキーを取得", "geminiApiKey": "Gemini APIキー", "getGeminiApiKey": "Gemini APIキーを取得", + "useFreeTier": "無料枠を使用", + "useFreeTierDescription": "コスト計算のために入力および出力価格をゼロに設定します。", "openAiApiKey": "OpenAI APIキー", "openAiBaseUrl": "ベースURL", "getOpenAiApiKey": "OpenAI APIキーを取得", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 05e7aa2944..88497b94e8 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "DeepSeek API 키 받기", "geminiApiKey": "Gemini API 키", "getGeminiApiKey": "Gemini API 키 받기", + "useFreeTier": "무료 등급 사용", + "useFreeTierDescription": "비용 계산 목적으로 입력 및 출력 가격을 0으로 설정합니다.", "openAiApiKey": "OpenAI API 키", "openAiBaseUrl": "기본 URL", "getOpenAiApiKey": "OpenAI API 키 받기", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 2d27e9a85c..95756b9ace 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek", "geminiApiKey": "Klucz API Gemini", "getGeminiApiKey": "Uzyskaj klucz API Gemini", + "useFreeTier": "Użyj poziomu bezpłatnego", + "useFreeTierDescription": "Ustawia ceny wejściowe i wyjściowe na zero do celów obliczania kosztów.", "openAiApiKey": "Klucz API OpenAI", "openAiBaseUrl": "URL bazowy", "getOpenAiApiKey": "Uzyskaj klucz API OpenAI", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 9181d8fdb3..763948d474 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "Obter chave de API DeepSeek", "geminiApiKey": "Chave de API Gemini", "getGeminiApiKey": "Obter chave de API Gemini", + "useFreeTier": "Usar camada gratuita", + "useFreeTierDescription": "Define os preços de entrada e saída como zero para fins de cálculo de custos.", "openAiApiKey": "Chave de API OpenAI", "openAiBaseUrl": "URL Base", "getOpenAiApiKey": "Obter chave de API OpenAI", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 5f26eea0b9..631afb4ab6 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "DeepSeek API Anahtarı Al", "geminiApiKey": "Gemini API Anahtarı", "getGeminiApiKey": "Gemini API Anahtarı Al", + "useFreeTier": "Ücretsiz katmanı kullan", + "useFreeTierDescription": "Maliyet hesaplama amacıyla giriş ve çıkış fiyatlarını sıfıra ayarlar.", "openAiApiKey": "OpenAI API Anahtarı", "openAiBaseUrl": "Temel URL", "getOpenAiApiKey": "OpenAI API Anahtarı Al", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 824635fbf6..d104fcd22d 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -120,6 +120,8 @@ "getDeepSeekApiKey": "Lấy khóa API DeepSeek", "geminiApiKey": "Khóa API Gemini", "getGeminiApiKey": "Lấy khóa API Gemini", + "useFreeTier": "Sử dụng bậc miễn phí", + "useFreeTierDescription": "Đặt giá đầu vào và đầu ra thành 0 cho mục đích tính toán chi phí.", "openAiApiKey": "Khóa API OpenAI", "openAiBaseUrl": "URL cơ sở", "getOpenAiApiKey": "Lấy khóa API OpenAI", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 97067ffa62..de1bb3ace2 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "获取 DeepSeek API 密钥", "geminiApiKey": "Gemini API 密钥", "getGeminiApiKey": "获取 Gemini API 密钥", + "useFreeTier": "使用免费层级", + "useFreeTierDescription": "将输入和输出价格设置为零以用于成本计算。", "openAiApiKey": "OpenAI API 密钥", "openAiBaseUrl": "OpenAI 基础 URL", "getOpenAiApiKey": "获取 OpenAI API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index cf99713793..9885b5a46e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -121,6 +121,8 @@ "getDeepSeekApiKey": "取得 DeepSeek API 金鑰", "geminiApiKey": "Gemini API 金鑰", "getGeminiApiKey": "取得 Gemini API 金鑰", + "useFreeTier": "使用免費層級", + "useFreeTierDescription": "將輸入和輸出價格設定為零以用於成本計算。", "openAiApiKey": "OpenAI API 金鑰", "openAiBaseUrl": "基礎 URL", "getOpenAiApiKey": "取得 OpenAI API 金鑰",