Просмотр исходного кода

feat(settings): Rate-limit setting updated to be per-profile (#2376)

* Rate-limit setting updated to be per-profile

* Correct rateLimitSeconds translations

* Add missing d to rateLimitSecondsMigrate

* Fix fr rate-limit translation
Ross McFarland 8 месяцев назад
Родитель
Сommit
260fc30043
27 измененных файлов с 275 добавлено и 95 удалено
  1. 5 0
      .changeset/curvy-masks-scream.md
  2. 57 2
      src/core/config/ProviderSettingsManager.ts
  3. 69 1
      src/core/config/__tests__/ProviderSettingsManager.test.ts
  4. 10 0
      src/core/config/__tests__/importExport.test.ts
  5. 1 0
      src/exports/roo-code.d.ts
  6. 1 0
      src/exports/types.ts
  7. 2 0
      src/schemas/index.ts
  8. 1 22
      webview-ui/src/components/settings/AdvancedSettings.tsx
  9. 12 5
      webview-ui/src/components/settings/ApiOptions.tsx
  10. 38 0
      webview-ui/src/components/settings/RateLimitSecondsControl.tsx
  11. 0 3
      webview-ui/src/components/settings/SettingsView.tsx
  12. 19 2
      webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx
  13. 4 4
      webview-ui/src/i18n/locales/ca/settings.json
  14. 4 4
      webview-ui/src/i18n/locales/de/settings.json
  15. 4 4
      webview-ui/src/i18n/locales/en/settings.json
  16. 4 4
      webview-ui/src/i18n/locales/es/settings.json
  17. 4 4
      webview-ui/src/i18n/locales/fr/settings.json
  18. 4 4
      webview-ui/src/i18n/locales/hi/settings.json
  19. 4 4
      webview-ui/src/i18n/locales/it/settings.json
  20. 4 4
      webview-ui/src/i18n/locales/ja/settings.json
  21. 4 4
      webview-ui/src/i18n/locales/ko/settings.json
  22. 4 4
      webview-ui/src/i18n/locales/pl/settings.json
  23. 4 4
      webview-ui/src/i18n/locales/pt-BR/settings.json
  24. 4 4
      webview-ui/src/i18n/locales/tr/settings.json
  25. 4 4
      webview-ui/src/i18n/locales/vi/settings.json
  26. 4 4
      webview-ui/src/i18n/locales/zh-CN/settings.json
  27. 4 4
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 5 - 0
.changeset/curvy-masks-scream.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": minor
+---
+
+Rate-limit setting updated to be per-profile

+ 57 - 2
src/core/config/ProviderSettingsManager.ts

@@ -13,6 +13,11 @@ export const providerProfilesSchema = z.object({
 	currentApiConfigName: z.string(),
 	apiConfigs: z.record(z.string(), providerSettingsWithIdSchema),
 	modeApiConfigs: z.record(z.string(), z.string()).optional(),
+	migrations: z
+		.object({
+			rateLimitSecondsMigrated: z.boolean().optional(),
+		})
+		.optional(),
 })
 
 export type ProviderProfiles = z.infer<typeof providerProfilesSchema>
@@ -27,8 +32,16 @@ export class ProviderSettingsManager {
 
 	private readonly defaultProviderProfiles: ProviderProfiles = {
 		currentApiConfigName: "default",
-		apiConfigs: { default: { id: this.defaultConfigId } },
+		apiConfigs: {
+			default: {
+				id: this.defaultConfigId,
+				rateLimitSeconds: 0,
+			},
+		},
 		modeApiConfigs: this.defaultModeApiConfigs,
+		migrations: {
+			rateLimitSecondsMigrated: true, // Mark as migrated on fresh installs
+		},
 	}
 
 	private readonly context: ExtensionContext
@@ -53,7 +66,7 @@ export class ProviderSettingsManager {
 	}
 
 	/**
-	 * Initialize config if it doesn't exist.
+	 * Initialize config if it doesn't exist and run migrations.
 	 */
 	public async initialize() {
 		try {
@@ -75,6 +88,18 @@ export class ProviderSettingsManager {
 					}
 				}
 
+				// Ensure migrations field exists
+				if (!providerProfiles.migrations) {
+					providerProfiles.migrations = { rateLimitSecondsMigrated: false } // Initialize with default values
+					isDirty = true
+				}
+
+				if (!providerProfiles.migrations.rateLimitSecondsMigrated) {
+					await this.migrateRateLimitSeconds(providerProfiles)
+					providerProfiles.migrations.rateLimitSecondsMigrated = true
+					isDirty = true
+				}
+
 				if (isDirty) {
 					await this.store(providerProfiles)
 				}
@@ -84,6 +109,36 @@ export class ProviderSettingsManager {
 		}
 	}
 
+	private async migrateRateLimitSeconds(providerProfiles: ProviderProfiles) {
+		try {
+			let rateLimitSeconds: number | undefined
+
+			try {
+				rateLimitSeconds = await this.context.globalState.get<number>("rateLimitSeconds")
+			} catch (error) {
+				console.error("[MigrateRateLimitSeconds] Error getting global rate limit:", error)
+			}
+
+			if (rateLimitSeconds === undefined) {
+				// Failed to get the existing value, use the default
+				rateLimitSeconds = 0
+			}
+
+			for (const [name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
+				if (apiConfig.rateLimitSeconds === undefined) {
+					console.log(
+						`[MigrateRateLimitSeconds] Applying rate limit ${rateLimitSeconds}s to profile: ${name}`,
+					)
+					apiConfig.rateLimitSeconds = rateLimitSeconds
+				}
+			}
+
+			console.log(`[MigrateRateLimitSeconds] migration complete`)
+		} catch (error) {
+			console.error(`[MigrateRateLimitSeconds] Failed to migrate rate limit settings:`, error)
+		}
+	}
+
 	/**
 	 * List all available configs with metadata.
 	 */

+ 69 - 1
src/core/config/__tests__/ProviderSettingsManager.test.ts

@@ -12,8 +12,14 @@ const mockSecrets = {
 	delete: jest.fn(),
 }
 
+const mockGlobalState = {
+	get: jest.fn(),
+	update: jest.fn(),
+}
+
 const mockContext = {
 	secrets: mockSecrets,
+	globalState: mockGlobalState,
 } as unknown as ExtensionContext
 
 describe("ProviderSettingsManager", () => {
@@ -45,6 +51,9 @@ describe("ProviderSettingsManager", () => {
 							id: "default",
 						},
 					},
+					migrations: {
+						rateLimitSecondsMigrated: true,
+					},
 				}),
 			)
 
@@ -78,6 +87,43 @@ describe("ProviderSettingsManager", () => {
 			expect(storedConfig.apiConfigs.test.id).toBeTruthy()
 		})
 
+		it("should call migrateRateLimitSeconds if it has not done so already", async () => {
+			mockGlobalState.get.mockResolvedValue(42)
+
+			mockSecrets.get.mockResolvedValue(
+				JSON.stringify({
+					currentApiConfigName: "default",
+					apiConfigs: {
+						default: {
+							config: {},
+							id: "default",
+							rateLimitSeconds: undefined,
+						},
+						test: {
+							apiProvider: "anthropic",
+							rateLimitSeconds: undefined,
+						},
+						existing: {
+							apiProvider: "anthropic",
+							// this should not really be possible, unless someone has loaded a hand edited config,
+							// but we don't overwrite so we'll check that
+							rateLimitSeconds: 43,
+						},
+					},
+					migrations: {
+						rateLimitSecondsMigrated: false,
+					},
+				}),
+			)
+
+			await providerSettingsManager.initialize()
+
+			const storedConfig = JSON.parse(mockSecrets.store.mock.calls[1][1])
+			expect(storedConfig.apiConfigs.default.rateLimitSeconds).toEqual(42)
+			expect(storedConfig.apiConfigs.test.rateLimitSeconds).toEqual(42)
+			expect(storedConfig.apiConfigs.existing.rateLimitSeconds).toEqual(43)
+		})
+
 		it("should throw error if secrets storage fails", async () => {
 			mockSecrets.get.mockRejectedValue(new Error("Storage failed"))
 
@@ -105,6 +151,9 @@ describe("ProviderSettingsManager", () => {
 					architect: "default",
 					ask: "default",
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
 			mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig))
@@ -125,6 +174,9 @@ describe("ProviderSettingsManager", () => {
 					architect: "default",
 					ask: "default",
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
 			mockSecrets.get.mockResolvedValue(JSON.stringify(emptyConfig))
@@ -201,6 +253,9 @@ describe("ProviderSettingsManager", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
 			mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig))
@@ -221,6 +276,9 @@ describe("ProviderSettingsManager", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
 			expect(mockSecrets.store).toHaveBeenCalledWith(
@@ -257,6 +315,9 @@ describe("ProviderSettingsManager", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
 			mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig))
@@ -312,8 +373,12 @@ describe("ProviderSettingsManager", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
+			mockGlobalState.get.mockResolvedValue(42)
 			mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig))
 
 			const config = await providerSettingsManager.loadConfig("test")
@@ -325,7 +390,7 @@ describe("ProviderSettingsManager", () => {
 			})
 
 			// Get the stored config to check the structure
-			const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1])
+			const storedConfig = JSON.parse(mockSecrets.store.mock.calls[1][1])
 			expect(storedConfig.currentApiConfigName).toBe("test")
 			expect(storedConfig.apiConfigs.test).toEqual({
 				apiProvider: "anthropic",
@@ -409,6 +474,9 @@ describe("ProviderSettingsManager", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 
 			mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig))

+ 10 - 0
src/core/config/__tests__/importExport.test.ts

@@ -121,6 +121,7 @@ describe("importExport", () => {
 					},
 				},
 			}
+
 			mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles)
 
 			// Mock listConfig
@@ -294,6 +295,9 @@ describe("importExport", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			}
 			mockProviderSettingsManager.export.mockResolvedValue(mockProviderProfiles)
 
@@ -345,6 +349,9 @@ describe("importExport", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			})
 
 			// Mock global settings
@@ -384,6 +391,9 @@ describe("importExport", () => {
 						id: "test-id",
 					},
 				},
+				migrations: {
+					rateLimitSecondsMigrated: false,
+				},
 			})
 
 			// Mock global settings

+ 1 - 0
src/exports/roo-code.d.ts

@@ -177,6 +177,7 @@ type ProviderSettings = {
 	modelMaxTokens?: number | undefined
 	modelMaxThinkingTokens?: number | undefined
 	includeMaxTokens?: boolean | undefined
+	rateLimitSeconds?: number | undefined
 	fakeAi?: unknown | undefined
 }
 

+ 1 - 0
src/exports/types.ts

@@ -178,6 +178,7 @@ type ProviderSettings = {
 	modelMaxTokens?: number | undefined
 	modelMaxThinkingTokens?: number | undefined
 	includeMaxTokens?: boolean | undefined
+	rateLimitSeconds?: number | undefined
 	fakeAi?: unknown | undefined
 }
 

+ 2 - 0
src/schemas/index.ts

@@ -386,6 +386,7 @@ export const providerSettingsSchema = z.object({
 	modelMaxThinkingTokens: z.number().optional(),
 	// Generic
 	includeMaxTokens: z.boolean().optional(),
+	rateLimitSeconds: z.number().optional(),
 	// Fake AI
 	fakeAi: z.unknown().optional(),
 })
@@ -470,6 +471,7 @@ const providerSettingsRecord: ProviderSettingsRecord = {
 	modelMaxThinkingTokens: undefined,
 	// Generic
 	includeMaxTokens: undefined,
+	rateLimitSeconds: undefined,
 	// Fake AI
 	fakeAi: undefined,
 }

+ 1 - 22
webview-ui/src/components/settings/AdvancedSettings.tsx

@@ -11,13 +11,11 @@ import { SectionHeader } from "./SectionHeader"
 import { Section } from "./Section"
 
 type AdvancedSettingsProps = HTMLAttributes<HTMLDivElement> & {
-	rateLimitSeconds: number
 	diffEnabled?: boolean
 	fuzzyMatchThreshold?: number
-	setCachedStateField: SetCachedStateField<"rateLimitSeconds" | "diffEnabled" | "fuzzyMatchThreshold">
+	setCachedStateField: SetCachedStateField<"diffEnabled" | "fuzzyMatchThreshold">
 }
 export const AdvancedSettings = ({
-	rateLimitSeconds,
 	diffEnabled,
 	fuzzyMatchThreshold,
 	setCachedStateField,
@@ -36,25 +34,6 @@ export const AdvancedSettings = ({
 			</SectionHeader>
 
 			<Section>
-				<div>
-					<div className="flex flex-col gap-2">
-						<span className="font-medium">{t("settings:advanced.rateLimit.label")}</span>
-						<div className="flex items-center gap-2">
-							<Slider
-								min={0}
-								max={60}
-								step={1}
-								value={[rateLimitSeconds]}
-								onValueChange={([value]) => setCachedStateField("rateLimitSeconds", value)}
-							/>
-							<span className="w-10">{rateLimitSeconds}s</span>
-						</div>
-					</div>
-					<div className="text-vscode-descriptionForeground text-sm mt-1">
-						{t("settings:advanced.rateLimit.description")}
-					</div>
-				</div>
-
 				<div>
 					<VSCodeCheckbox
 						checked={diffEnabled}

+ 12 - 5
webview-ui/src/components/settings/ApiOptions.tsx

@@ -52,6 +52,7 @@ import { VSCodeButtonLink } from "../common/VSCodeButtonLink"
 import { ModelInfoView } from "./ModelInfoView"
 import { ModelPicker } from "./ModelPicker"
 import { TemperatureControl } from "./TemperatureControl"
+import { RateLimitSecondsControl } from "./RateLimitSecondsControl"
 import { ApiErrorMessage } from "./ApiErrorMessage"
 import { ThinkingBudget } from "./ThinkingBudget"
 import { R1FormatSetting } from "./R1FormatSetting"
@@ -1623,11 +1624,17 @@ const ApiOptions = ({
 			)}
 
 			{!fromWelcomeView && (
-				<TemperatureControl
-					value={apiConfiguration?.modelTemperature}
-					onChange={handleInputChange("modelTemperature", noTransform)}
-					maxValue={2}
-				/>
+				<>
+					<TemperatureControl
+						value={apiConfiguration?.modelTemperature}
+						onChange={handleInputChange("modelTemperature", noTransform)}
+						maxValue={2}
+					/>
+					<RateLimitSecondsControl
+						value={apiConfiguration.rateLimitSeconds || 0}
+						onChange={(value) => setApiConfigurationField("rateLimitSeconds", value)}
+					/>
+				</>
 			)}
 		</div>
 	)

+ 38 - 0
webview-ui/src/components/settings/RateLimitSecondsControl.tsx

@@ -0,0 +1,38 @@
+import React, { useCallback } from "react"
+import { Slider } from "@/components/ui"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+
+interface RateLimitSecondsControlProps {
+	value: number
+	onChange: (value: number) => void
+}
+
+export const RateLimitSecondsControl: React.FC<RateLimitSecondsControlProps> = ({ value, onChange }) => {
+	const { t } = useAppTranslation()
+
+	const handleValueChange = useCallback(
+		(newValue: number) => {
+			onChange(newValue)
+		},
+		[onChange],
+	)
+
+	return (
+		<div className="flex flex-col gap-1">
+			<label className="block font-medium mb-1">{t("settings:providers.rateLimitSeconds.label")}</label>
+			<div className="flex items-center gap-2">
+				<Slider
+					value={[value]}
+					min={0}
+					max={60}
+					step={1}
+					onValueChange={(newValue) => handleValueChange(newValue[0])}
+				/>
+				<span className="w-10">{value}s</span>
+			</div>
+			<div className="text-sm text-vscode-descriptionForeground">
+				{t("settings:providers.rateLimitSeconds.description", { value })}
+			</div>
+		</div>
+	)
+}

+ 0 - 3
webview-ui/src/components/settings/SettingsView.tsx

@@ -119,7 +119,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 		maxOpenTabsContext,
 		maxWorkspaceFiles,
 		mcpEnabled,
-		rateLimitSeconds,
 		requestDelaySeconds,
 		remoteBrowserHost,
 		screenshotQuality,
@@ -241,7 +240,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 			vscode.postMessage({ type: "mcpEnabled", bool: mcpEnabled })
 			vscode.postMessage({ type: "alwaysApproveResubmit", bool: alwaysApproveResubmit })
 			vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
-			vscode.postMessage({ type: "rateLimitSeconds", value: rateLimitSeconds })
 			vscode.postMessage({ type: "maxOpenTabsContext", value: maxOpenTabsContext })
 			vscode.postMessage({ type: "maxWorkspaceFiles", value: maxWorkspaceFiles ?? 200 })
 			vscode.postMessage({ type: "showRooIgnoredFiles", bool: showRooIgnoredFiles })
@@ -489,7 +487,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 
 				<div ref={advancedRef}>
 					<AdvancedSettings
-						rateLimitSeconds={rateLimitSeconds}
 						diffEnabled={diffEnabled}
 						fuzzyMatchThreshold={fuzzyMatchThreshold}
 						setCachedStateField={setCachedStateField}

+ 19 - 2
webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx

@@ -71,6 +71,21 @@ jest.mock("../TemperatureControl", () => ({
 	),
 }))
 
+jest.mock("../RateLimitSecondsControl", () => ({
+	RateLimitSecondsControl: ({ value, onChange }: any) => (
+		<div data-testid="rate-limit-seconds-control">
+			<input
+				type="range"
+				value={value || 0}
+				onChange={(e) => onChange(parseFloat(e.target.value))}
+				min={0}
+				max={60}
+				step={1}
+			/>
+		</div>
+	),
+}))
+
 // Mock ThinkingBudget component
 jest.mock("../ThinkingBudget", () => ({
 	ThinkingBudget: ({ apiConfiguration, setApiConfigurationField, modelInfo, provider }: any) =>
@@ -101,14 +116,16 @@ const renderApiOptions = (props = {}) => {
 }
 
 describe("ApiOptions", () => {
-	it("shows temperature control by default", () => {
+	it("shows temperature and rate limit controls by default", () => {
 		renderApiOptions()
 		expect(screen.getByTestId("temperature-control")).toBeInTheDocument()
+		expect(screen.getByTestId("rate-limit-seconds-control")).toBeInTheDocument()
 	})
 
-	it("hides temperature control when fromWelcomeView is true", () => {
+	it("hides temperature and rate limit controls when fromWelcomeView is true", () => {
 		renderApiOptions({ fromWelcomeView: true })
 		expect(screen.queryByTestId("temperature-control")).not.toBeInTheDocument()
+		expect(screen.queryByTestId("rate-limit-seconds-control")).not.toBeInTheDocument()
 	})
 
 	describe("thinking functionality", () => {

+ 4 - 4
webview-ui/src/i18n/locales/ca/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Restablir als valors per defecte"
+		},
+		"rateLimitSeconds": {
+			"label": "Límit de freqüència",
+			"description": "Temps mínim entre sol·licituds d'API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Límit de freqüència",
-			"description": "Temps mínim entre sol·licituds d'API."
-		},
 		"diff": {
 			"label": "Habilitar edició mitjançant diffs",
 			"description": "Quan està habilitat, Roo podrà editar fitxers més ràpidament i rebutjarà automàticament escriptures completes de fitxers truncats. Funciona millor amb l'últim model Claude 3.7 Sonnet.",

+ 4 - 4
webview-ui/src/i18n/locales/de/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Auf Standardwerte zurücksetzen"
+		},
+		"rateLimitSeconds": {
+			"label": "Ratenbegrenzung",
+			"description": "Minimale Zeit zwischen API-Anfragen."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Ratenbegrenzung",
-			"description": "Minimale Zeit zwischen API-Anfragen."
-		},
 		"diff": {
 			"label": "Bearbeitung durch Diffs aktivieren",
 			"description": "Wenn aktiviert, kann Roo Dateien schneller bearbeiten und lehnt automatisch gekürzte vollständige Dateischreibvorgänge ab. Funktioniert am besten mit dem neuesten Claude 3.7 Sonnet-Modell.",

+ 4 - 4
webview-ui/src/i18n/locales/en/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Reset to Defaults"
+		},
+		"rateLimitSeconds": {
+			"label": "Rate limit",
+			"description": "Minimum time between API requests."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Rate limit",
-			"description": "Minimum time between API requests."
-		},
 		"diff": {
 			"label": "Enable editing through diffs",
 			"description": "When enabled, Roo will be able to edit files more quickly and will automatically reject truncated full-file writes. Works best with the latest Claude 3.7 Sonnet model.",

+ 4 - 4
webview-ui/src/i18n/locales/es/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Restablecer valores predeterminados"
+		},
+		"rateLimitSeconds": {
+			"label": "Límite de tasa",
+			"description": "Tiempo mínimo entre solicitudes de API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Límite de tasa",
-			"description": "Tiempo mínimo entre solicitudes de API."
-		},
 		"diff": {
 			"label": "Habilitar edición a través de diffs",
 			"description": "Cuando está habilitado, Roo podrá editar archivos más rápidamente y rechazará automáticamente escrituras completas de archivos truncados. Funciona mejor con el último modelo Claude 3.7 Sonnet.",

+ 4 - 4
webview-ui/src/i18n/locales/fr/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Réinitialiser les valeurs par défaut"
+		},
+		"rateLimitSeconds": {
+			"label": "Limite de débit",
+			"description": "Temps minimum entre les requêtes API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Limite de débit",
-			"description": "Temps minimum entre les requêtes API."
-		},
 		"diff": {
 			"label": "Activer l'édition via des diffs",
 			"description": "Lorsque cette option est activée, Roo pourra éditer des fichiers plus rapidement et rejettera automatiquement les écritures de fichiers complets tronqués. Fonctionne mieux avec le dernier modèle Claude 3.7 Sonnet.",

+ 4 - 4
webview-ui/src/i18n/locales/hi/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "डिफ़ॉल्ट पर रीसेट करें"
+		},
+		"rateLimitSeconds": {
+			"label": "दर सीमा",
+			"description": "API अनुरोधों के बीच न्यूनतम समय।"
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "दर सीमा",
-			"description": "API अनुरोधों के बीच न्यूनतम समय।"
-		},
 		"diff": {
 			"label": "diffs के माध्यम से संपादन सक्षम करें",
 			"description": "जब सक्षम होता है, Roo फाइलों को तेजी से संपादित कर सकेगा और स्वचालित रूप से काटे गए पूर्ण-फाइल लेखन को अस्वीकार करेगा। नवीनतम Claude 3.7 Sonnet मॉडल के साथ सबसे अच्छा काम करता है।",

+ 4 - 4
webview-ui/src/i18n/locales/it/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Ripristina valori predefiniti"
+		},
+		"rateLimitSeconds": {
+			"label": "Limite di frequenza",
+			"description": "Tempo minimo tra le richieste API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Limite di frequenza",
-			"description": "Tempo minimo tra le richieste API."
-		},
 		"diff": {
 			"label": "Abilita modifica tramite diff",
 			"description": "Quando abilitato, Roo sarà in grado di modificare i file più velocemente e rifiuterà automaticamente scritture di file completi troncati. Funziona meglio con l'ultimo modello Claude 3.7 Sonnet.",

+ 4 - 4
webview-ui/src/i18n/locales/ja/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "デフォルトにリセット"
+		},
+		"rateLimitSeconds": {
+			"label": "レート制限",
+			"description": "APIリクエスト間の最小時間。"
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "レート制限",
-			"description": "APIリクエスト間の最小時間。"
-		},
 		"diff": {
 			"label": "diff経由の編集を有効化",
 			"description": "有効にすると、Rooはファイルをより迅速に編集でき、切り詰められた全ファイル書き込みを自動的に拒否します。最新のClaude 3.7 Sonnetモデルで最良に機能します。",

+ 4 - 4
webview-ui/src/i18n/locales/ko/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "기본값으로 재설정"
+		},
+		"rateLimitSeconds": {
+			"label": "속도 제한",
+			"description": "API 요청 간 최소 시간."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "속도 제한",
-			"description": "API 요청 간 최소 시간."
-		},
 		"diff": {
 			"label": "diff를 통한 편집 활성화",
 			"description": "활성화되면 Roo는 파일을 더 빠르게 편집할 수 있으며 잘린 전체 파일 쓰기를 자동으로 거부합니다. 최신 Claude 3.7 Sonnet 모델에서 가장 잘 작동합니다.",

+ 4 - 4
webview-ui/src/i18n/locales/pl/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Przywróć domyślne"
+		},
+		"rateLimitSeconds": {
+			"label": "Limit szybkości",
+			"description": "Minimalny czas między żądaniami API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Limit szybkości",
-			"description": "Minimalny czas między żądaniami API."
-		},
 		"diff": {
 			"label": "Włącz edycję przez różnice",
 			"description": "Gdy włączone, Roo będzie w stanie edytować pliki szybciej i automatycznie odrzuci obcięte pełne zapisy plików. Działa najlepiej z najnowszym modelem Claude 3.7 Sonnet.",

+ 4 - 4
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Restaurar Padrões"
+		},
+		"rateLimitSeconds": {
+			"label": "Limite de taxa",
+			"description": "Tempo mínimo entre requisições de API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Limite de taxa",
-			"description": "Tempo mínimo entre requisições de API."
-		},
 		"diff": {
 			"label": "Ativar edição através de diffs",
 			"description": "Quando ativado, o Roo poderá editar arquivos mais rapidamente e rejeitará automaticamente escritas completas de arquivos truncados. Funciona melhor com o modelo mais recente Claude 3.7 Sonnet.",

+ 4 - 4
webview-ui/src/i18n/locales/tr/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Varsayılanlara Sıfırla"
+		},
+		"rateLimitSeconds": {
+			"label": "Hız sınırı",
+			"description": "API istekleri arasındaki minimum süre."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Hız sınırı",
-			"description": "API istekleri arasındaki minimum süre."
-		},
 		"diff": {
 			"label": "Diff'ler aracılığıyla düzenlemeyi etkinleştir",
 			"description": "Etkinleştirildiğinde, Roo dosyaları daha hızlı düzenleyebilecek ve kesik tam dosya yazımlarını otomatik olarak reddedecektir. En son Claude 3.7 Sonnet modeliyle en iyi şekilde çalışır.",

+ 4 - 4
webview-ui/src/i18n/locales/vi/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "Đặt lại về mặc định"
+		},
+		"rateLimitSeconds": {
+			"label": "Giới hạn tốc độ",
+			"description": "Thời gian tối thiểu giữa các yêu cầu API."
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "Giới hạn tốc độ",
-			"description": "Thời gian tối thiểu giữa các yêu cầu API."
-		},
 		"diff": {
 			"label": "Bật chỉnh sửa qua diff",
 			"description": "Khi được bật, Roo sẽ có thể chỉnh sửa tệp nhanh hơn và sẽ tự động từ chối ghi toàn bộ tệp bị cắt ngắn. Hoạt động tốt nhất với mô hình Claude 3.7 Sonnet mới nhất.",

+ 4 - 4
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "重置为默认值"
+		},
+		"rateLimitSeconds": {
+			"label": "请求频率限制",
+			"description": "设置API请求的最小间隔时间"
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "请求频率限制",
-			"description": "设置API请求的最小间隔时间"
-		},
 		"diff": {
 			"label": "启用diff更新",
 			"description": "启用后,Roo 将能够通过差异算法写入,避免模型输出完整文件,以降低Token消耗。与最新的 Claude 3.7 Sonnet 模型配合最佳。",

+ 4 - 4
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -218,6 +218,10 @@
 				}
 			},
 			"resetDefaults": "重設為預設值"
+		},
+		"rateLimitSeconds": {
+			"label": "速率限制",
+			"description": "API 請求間的最短時間"
 		}
 	},
 	"browser": {
@@ -298,10 +302,6 @@
 		}
 	},
 	"advanced": {
-		"rateLimit": {
-			"label": "速率限制",
-			"description": "API 請求間的最短時間"
-		},
 		"diff": {
 			"label": "透過差異比對編輯",
 			"description": "啟用後,Roo 可更快速地編輯檔案,並自動拒絕不完整的整檔覆寫。搭配最新的 Claude 3.7 Sonnet 模型效果最佳。",