This commit addresses "Max retries exceeded" errors during stress testing where all accounts would become exhausted simultaneously due to short per-second rate limits triggering cascading failures. ## Rate Limit Parser (`rate-limit-parser.js`) - Remove 2s buffer enforcement that caused cascading failures when API returned short reset times (200-600ms). Now adds 200ms buffer for sub-500ms resets - Add `parseRateLimitReason()` for smart backoff based on error type: QUOTA_EXHAUSTED, RATE_LIMIT_EXCEEDED, MODEL_CAPACITY_EXHAUSTED, SERVER_ERROR ## Message/Streaming Handlers - Add per-account+model rate limit state tracking with exponential backoff - For short rate limits (< 1 second), wait and retry on same account instead of switching - prevents thundering herd when all accounts hit per-second limits - Add throttle wait support for fallback modes (emergency/lastResort) - Add `calculateSmartBackoff()` with progressive tiers by error type ## HybridStrategy (`hybrid-strategy.js`) - Refactor `#getCandidates()` to return 4 fallback levels: - `normal`: All filters pass (health, tokens, quota) - `quota`: Bypass critical quota check - `emergency`: Bypass health check when ALL accounts unhealthy - `lastResort`: Bypass BOTH health AND token bucket checks - Add throttle wait times: 500ms for lastResort, 250ms for emergency - Fix LRU calculation to use seconds (matches opencode-antigravity-auth) ## Health Tracker - Increase `recoveryPerHour` from 2 to 10 for faster recovery (1 hour vs 5 hours) ## Account Manager - Add consecutive failure tracking: `getConsecutiveFailures()`, `incrementConsecutiveFailures()`, `resetConsecutiveFailures()` - Add cooldown mechanism separate from rate limits with `CooldownReason` - Reset consecutive failures on successful request ## Base Strategy - Add `isAccountCoolingDown()` check in `isAccountUsable()` ## Constants - Replace fixed `CAPACITY_RETRY_DELAY_MS` with progressive `CAPACITY_BACKOFF_TIERS_MS` - Add `BACKOFF_BY_ERROR_TYPE` for smart backoff - Add `QUOTA_EXHAUSTED_BACKOFF_TIERS_MS` for progressive quota backoff - Add `MIN_BACKOFF_MS` floor to prevent "Available in 0s" loops - Increase `MAX_CAPACITY_RETRIES` from 3 to 5 - Reduce `RATE_LIMIT_DEDUP_WINDOW_MS` from 5s to 2s ## Frontend - Remove `capacityRetryDelayMs` config (replaced by progressive tiers) - Update default `maxCapacityRetries` display from 3 to 5 ## Testing - Add `tests/stress-test.cjs` for concurrent request stress testing Co-Authored-By: Claude <noreply@anthropic.com>
413 lines
16 KiB
JavaScript
413 lines
16 KiB
JavaScript
/**
|
|
* Indonesian (Bahasa Indonesia) Translations
|
|
*
|
|
* Panduan Terminologi IT:
|
|
* - Istilah teknis umum tetap dalam bahasa Inggris (OAuth, API, token, proxy, debug, cache, dll.)
|
|
* - Istilah UI umum diterjemahkan (Simpan, Hapus, Batal, dll.)
|
|
* - Pesan error dan status menggunakan campuran yang konsisten
|
|
*/
|
|
window.translations = window.translations || {};
|
|
window.translations.id = {
|
|
// Navigation
|
|
dashboard: "Dashboard",
|
|
models: "Model",
|
|
accounts: "Akun",
|
|
logs: "Log",
|
|
settings: "Pengaturan",
|
|
|
|
// Status
|
|
online: "ONLINE",
|
|
offline: "OFFLINE",
|
|
totalAccounts: "TOTAL AKUN",
|
|
active: "AKTIF",
|
|
operational: "Berjalan Normal",
|
|
rateLimited: "DIBATASI",
|
|
quotasDepleted: "{count}/{total} Kuota Habis",
|
|
quotasDepletedTitle: "KUOTA HABIS",
|
|
outOfTracked: "Dari {total} Terpantau",
|
|
cooldown: "Cooldown",
|
|
|
|
// Search & Filters
|
|
searchPlaceholder: "Cari model...",
|
|
allAccounts: "Semua Akun",
|
|
stat: "STATUS",
|
|
modelIdentity: "IDENTITAS MODEL",
|
|
globalQuota: "KUOTA GLOBAL",
|
|
nextReset: "RESET BERIKUTNYA",
|
|
distribution: "DISTRIBUSI AKUN",
|
|
|
|
// System Configuration
|
|
systemConfig: "Konfigurasi Sistem",
|
|
language: "Bahasa",
|
|
pollingInterval: "Interval Polling",
|
|
maxDisplayLogs: "Maks. Log Ditampilkan",
|
|
showExhausted: "Tampilkan Model Habis",
|
|
showExhaustedDesc: "Tampilkan model meskipun kuotanya sudah 0%.",
|
|
compactMode: "Mode Ringkas",
|
|
compactModeDesc: "Tampilan tabel lebih padat untuk informasi lebih banyak.",
|
|
saveChanges: "Simpan Perubahan",
|
|
|
|
// Logs
|
|
autoScroll: "Auto-scroll",
|
|
clearLogs: "Hapus Log",
|
|
|
|
// Account Management
|
|
accountManagement: "Kelola Akun",
|
|
manageTokens: "Kelola token akun Google dan status otorisasi",
|
|
addAccount: "Tambah Akun",
|
|
status: "STATUS",
|
|
enabled: "AKTIF",
|
|
health: "STATUS",
|
|
accountEmail: "AKUN (EMAIL)",
|
|
source: "SUMBER",
|
|
projectId: "PROJECT ID",
|
|
sessionState: "STATUS SESI",
|
|
operations: "AKSI",
|
|
delete: "Hapus",
|
|
confirmDelete: "Yakin ingin menghapus akun ini?",
|
|
cannotDeleteDatabase: "Tidak bisa dihapus: Akun ini dari database Antigravity (read-only)",
|
|
connectGoogle: "Hubungkan Akun Google",
|
|
reauthenticated: "diautentikasi ulang",
|
|
added: "ditambahkan",
|
|
successfully: "berhasil",
|
|
accountAddedSuccess: "Akun berhasil ditambahkan",
|
|
accountReauthSuccess: "Akun berhasil diautentikasi ulang",
|
|
failedToGetAuthUrl: "Gagal mendapatkan URL autentikasi",
|
|
failedToStartOAuth: "Gagal memulai proses OAuth",
|
|
oauthInProgress: "Proses OAuth sedang berjalan. Silakan selesaikan autentikasi di jendela popup...",
|
|
family: "Jenis",
|
|
model: "Model",
|
|
activeSuffix: "Aktif",
|
|
manualReload: "Muat Ulang Konfigurasi",
|
|
|
|
// Tabs
|
|
tabInterface: "Tampilan",
|
|
tabClaude: "Claude CLI",
|
|
tabModels: "Model",
|
|
tabServer: "Server",
|
|
|
|
// Dashboard
|
|
linkedAccounts: "Akun Terhubung",
|
|
noSignal: "TIDAK ADA SINYAL",
|
|
establishingUplink: "MENGHUBUNGKAN...",
|
|
goToAccounts: "Lihat Akun",
|
|
|
|
// Settings - Models
|
|
modelsDesc: "Atur visibilitas model, pin, dan routing request.",
|
|
modelsPageDesc: "Kuota dan status real-time untuk semua model yang tersedia.",
|
|
showHidden: "Tampilkan Model Tersembunyi",
|
|
hideHidden: "Sembunyikan Model Tersembunyi",
|
|
hiddenOn: "Tersembunyi: YA",
|
|
hiddenOff: "Tersembunyi: TIDAK",
|
|
modelId: "ID Model",
|
|
actions: "Aksi",
|
|
pinToTop: "Pin ke atas",
|
|
toggleVisibility: "Tampilkan/Sembunyikan",
|
|
noModels: "MODEL TIDAK DITEMUKAN",
|
|
modelMappingHint: "Routing model di sisi server. Pengguna Claude Code: lihat tab 'Claude CLI' untuk pengaturan di sisi klien.",
|
|
modelMapping: "Mapping (Target Model ID)",
|
|
|
|
// Settings - Claude
|
|
proxyConnection: "Koneksi Proxy",
|
|
modelSelection: "Pilihan Model",
|
|
defaultModelAliases: "ALIAS MODEL DEFAULT",
|
|
opusAlias: "Alias Opus",
|
|
sonnetAlias: "Alias Sonnet",
|
|
haikuAlias: "Alias Haiku",
|
|
claudeSettingsAlertPrefix: "Pengaturan di bawah langsung mengubah",
|
|
claudeSettingsAlertSuffix: "Restart Claude CLI untuk menerapkan.",
|
|
applyToClaude: "Simpan ke Claude CLI",
|
|
|
|
// Presets
|
|
configPresets: "Preset Konfigurasi",
|
|
saveAsPreset: "Simpan sebagai Preset",
|
|
deletePreset: "Hapus Preset",
|
|
loadPreset: "Muat preset",
|
|
load: "Muat",
|
|
presetHint: "Pilih preset untuk dimuat. Klik \"Simpan ke Claude CLI\" untuk menyimpan.",
|
|
presetLoaded: "Preset dimuat. Klik \"Simpan ke Claude CLI\" untuk menyimpan.",
|
|
presetSaved: "Preset tersimpan",
|
|
presetDeleted: "Preset terhapus",
|
|
unsavedChangesTitle: "Ada Perubahan Belum Disimpan",
|
|
unsavedChangesMessage: "Konfigurasi saat ini tidak cocok dengan preset tersimpan. Jika beralih, perubahan akan hilang.",
|
|
loadAnyway: "Tetap Muat",
|
|
savePresetTitle: "Simpan Preset",
|
|
savePresetDesc: "Simpan konfigurasi saat ini sebagai preset.",
|
|
presetName: "Nama Preset",
|
|
presetNamePlaceholder: "contoh: Setup Kantor",
|
|
savePreset: "Simpan Preset",
|
|
|
|
// Settings - Server
|
|
port: "Port",
|
|
uiVersion: "Versi UI",
|
|
debugMode: "Mode Debug",
|
|
environment: "Environment",
|
|
serverReadOnly: "Pengaturan dikelola melalui config.json. Restart server untuk menerapkan.",
|
|
advancedSettings: "Pengaturan Lanjutan",
|
|
reloadConfigTitle: "Muat Ulang Konfigurasi Akun",
|
|
reloadConfigDesc: "Paksa muat ulang accounts.json dari disk",
|
|
reload: "Muat Ulang",
|
|
|
|
// Config Specific
|
|
primaryModel: "Model Utama",
|
|
subAgentModel: "Model Sub-agent",
|
|
advancedOverrides: "Timpa Model Default",
|
|
opusModel: "Model Opus",
|
|
sonnetModel: "Model Sonnet",
|
|
haikuModel: "Model Haiku",
|
|
authToken: "Token Auth",
|
|
saveConfig: "Simpan ke Claude CLI",
|
|
envVar: "Env",
|
|
|
|
// System
|
|
systemName: "ANTIGRAVITY",
|
|
systemDesc: "CLAUDE PROXY SYSTEM",
|
|
connectGoogleDesc: "Hubungkan akun Google Workspace untuk meningkatkan kuota API. Akun akan digunakan untuk meneruskan request Claude via Antigravity.",
|
|
useCliCommand: "Gunakan Perintah CLI",
|
|
close: "Tutup",
|
|
requestVolume: "Volume Request",
|
|
filter: "Filter",
|
|
all: "Semua",
|
|
none: "Tidak Ada",
|
|
noDataTracked: "Belum ada data tercatat",
|
|
selectFamilies: "Pilih jenis model untuk ditampilkan",
|
|
selectModels: "Pilih model untuk ditampilkan",
|
|
noLogsMatch: "Tidak ada log yang sesuai filter",
|
|
connecting: "MENGHUBUNGKAN",
|
|
main: "Utama",
|
|
system: "Sistem",
|
|
refreshData: "Refresh Data",
|
|
connectionLost: "Koneksi Terputus",
|
|
lastUpdated: "Terakhir Diperbarui",
|
|
grepLogs: "cari log...",
|
|
noMatchingModels: "Tidak ada model yang cocok",
|
|
typeToSearch: "Ketik untuk mencari...",
|
|
or: "ATAU",
|
|
|
|
// Account Operations
|
|
refreshingAccount: "Memperbarui {email}...",
|
|
refreshedAccount: "{email} berhasil diperbarui",
|
|
refreshFailed: "Gagal memperbarui",
|
|
accountToggled: "Akun {email} {status}",
|
|
toggleFailed: "Gagal mengubah status",
|
|
reauthenticating: "Autentikasi ulang {email}...",
|
|
authUrlFailed: "Gagal mendapatkan URL autentikasi",
|
|
deletedAccount: "{email} berhasil dihapus",
|
|
deleteFailed: "Gagal menghapus",
|
|
accountsReloaded: "Akun berhasil dimuat ulang",
|
|
reloadFailed: "Gagal memuat ulang",
|
|
|
|
// Claude Config
|
|
claudeConfigSaved: "Konfigurasi Claude tersimpan",
|
|
claudeConfigRestored: "Claude CLI dikembalikan ke default",
|
|
saveConfigFailed: "Gagal menyimpan konfigurasi",
|
|
restoreConfigFailed: "Gagal mengembalikan konfigurasi",
|
|
restoreDefault: "Reset ke Default",
|
|
confirmRestoreTitle: "Konfirmasi Reset",
|
|
confirmRestoreMessage: "Yakin ingin mengembalikan Claude CLI ke pengaturan default? Konfigurasi proxy akan dihapus.",
|
|
confirmRestore: "Ya, Reset",
|
|
|
|
// Status Labels
|
|
claudeActive: "Claude Aktif",
|
|
claudeEmpty: "Claude Kosong",
|
|
geminiActive: "Gemini Aktif",
|
|
geminiEmpty: "Gemini Kosong",
|
|
synced: "SINKRON",
|
|
syncing: "SINKRONISASI...",
|
|
|
|
// Time range labels
|
|
last1Hour: "1 Jam Terakhir",
|
|
last6Hours: "6 Jam Terakhir",
|
|
last24Hours: "24 Jam Terakhir",
|
|
last7Days: "7 Hari Terakhir",
|
|
allTime: "Semua Waktu",
|
|
groupBy: "Kelompokkan",
|
|
|
|
// Additional
|
|
reloading: "Memuat ulang...",
|
|
reloaded: "Dimuat ulang",
|
|
lines: "baris",
|
|
enabledSeeLogs: "Aktif (Lihat Log)",
|
|
production: "Produksi",
|
|
configSaved: "Konfigurasi Tersimpan",
|
|
enterPassword: "Masukkan Password Web UI:",
|
|
ready: "SIAP",
|
|
depleted: "Habis",
|
|
timeH: "j",
|
|
timeM: "m",
|
|
familyClaude: "Claude",
|
|
familyGemini: "Gemini",
|
|
familyOther: "Lainnya",
|
|
enabledStatus: "diaktifkan",
|
|
disabledStatus: "dinonaktifkan",
|
|
logLevelInfo: "INFO",
|
|
logLevelSuccess: "OK",
|
|
logLevelWarn: "WARN",
|
|
logLevelError: "ERROR",
|
|
totalColon: "Total:",
|
|
todayColon: "Hari Ini:",
|
|
hour1Colon: "1j:",
|
|
frequentModels: "Sering Dipakai",
|
|
smartTitle: "Otomatis pilih 5 model paling sering dipakai (24 jam)",
|
|
activeCount: "{count} Aktif",
|
|
allCaps: "SEMUA",
|
|
claudeCaps: "CLAUDE",
|
|
geminiCaps: "GEMINI",
|
|
|
|
// System Info
|
|
systemInfo: "Informasi Sistem",
|
|
refresh: "Refresh",
|
|
runtimeConfig: "Konfigurasi Runtime",
|
|
debugDesc: "Aktifkan log detail (Lihat tab Log)",
|
|
networkRetry: "Pengaturan Retry Jaringan",
|
|
maxRetries: "Maks. Retry",
|
|
retryBaseDelay: "Jeda Awal Retry (ms)",
|
|
retryMaxDelay: "Jeda Maks. Retry (ms)",
|
|
persistentSessions: "Sesi Persisten",
|
|
persistTokenDesc: "Simpan sesi OAuth ke disk agar startup lebih cepat",
|
|
rateLimiting: "Rate Limiting & Timeout Akun",
|
|
defaultCooldown: "Cooldown Default",
|
|
defaultCooldownDesc: "Cooldown bawaan jika API tidak memberikan waktu reset.",
|
|
maxWaitThreshold: "Batas Tunggu Maksimal",
|
|
maxWaitDesc: "Jika semua akun terkena rate limit lebih lama dari ini, langsung gagal.",
|
|
// Error Handling Tuning
|
|
errorHandlingTuning: "Penyetelan Penanganan Error",
|
|
rateLimitDedupWindow: "Jendela Deduplikasi Rate Limit",
|
|
rateLimitDedupWindowDesc: "Mencegah badai retry ketika beberapa permintaan terkena rate limit bersamaan.",
|
|
maxConsecutiveFailures: "Maks. Kegagalan Berturut-turut",
|
|
maxConsecutiveFailuresDesc: "Jumlah kegagalan berturut-turut sebelum menerapkan cooldown diperpanjang.",
|
|
extendedCooldown: "Cooldown Diperpanjang",
|
|
extendedCooldownDesc: "Durasi cooldown setelah mencapai maks. kegagalan berturut-turut.",
|
|
maxCapacityRetries: "Maks. Retry Kapasitas",
|
|
maxCapacityRetriesDesc: "Maksimum retry untuk kehabisan kapasitas sebelum ganti akun.",
|
|
saveConfigServer: "Simpan Konfigurasi",
|
|
serverRestartAlert: "Tersimpan ke {path}. Restart server untuk menerapkan.",
|
|
|
|
// Password
|
|
changePassword: "Ubah Password WebUI",
|
|
changePasswordDesc: "Ubah password untuk akses dashboard ini",
|
|
currentPassword: "Password Saat Ini",
|
|
newPassword: "Password Baru",
|
|
confirmNewPassword: "Konfirmasi Password Baru",
|
|
passwordEmptyDesc: "Kosongkan jika belum ada password",
|
|
passwordLengthDesc: "Minimal 6 karakter",
|
|
passwordConfirmDesc: "Masukkan ulang password baru",
|
|
cancel: "Batal",
|
|
passwordsNotMatch: "Password tidak cocok",
|
|
passwordTooShort: "Password minimal 6 karakter",
|
|
|
|
// Dashboard drill-down
|
|
clickToViewAllAccounts: "Klik untuk lihat semua akun",
|
|
clickToViewModels: "Klik untuk lihat halaman Model",
|
|
clickToViewLimitedAccounts: "Klik untuk lihat akun yang dibatasi",
|
|
clickToFilterClaude: "Klik untuk filter model Claude",
|
|
clickToFilterGemini: "Klik untuk filter model Gemini",
|
|
|
|
// Accounts page
|
|
searchAccounts: "Cari akun...",
|
|
noAccountsYet: "Belum Ada Akun",
|
|
noAccountsDesc: "Mulai dengan menambahkan akun Google via OAuth, atau gunakan perintah CLI untuk import kredensial.",
|
|
addFirstAccount: "Tambah Akun Pertama",
|
|
noSearchResults: "Tidak ada akun yang cocok",
|
|
clearSearch: "Reset Pencarian",
|
|
disabledAccountsNote: "<strong>Akun nonaktif</strong> tidak akan digunakan untuk routing tapi tetap tersimpan di konfigurasi. Statistik dashboard hanya mencakup akun aktif.",
|
|
dangerousOperation: "⚠️ Operasi Berbahaya",
|
|
confirmDeletePrompt: "Yakin ingin menghapus akun",
|
|
deleteWarning: "⚠️ Tindakan ini tidak bisa dibatalkan. Semua pengaturan dan riwayat akan dihapus permanen.",
|
|
|
|
// OAuth progress
|
|
oauthWaiting: "Menunggu otorisasi OAuth...",
|
|
oauthWaitingDesc: "Silakan selesaikan autentikasi di jendela popup. Proses ini bisa memakan waktu hingga 2 menit.",
|
|
oauthCancelled: "Otorisasi OAuth dibatalkan",
|
|
oauthTimeout: "⏱️ Waktu otorisasi OAuth habis. Silakan coba lagi.",
|
|
oauthWindowClosed: "Jendela OAuth ditutup. Otorisasi mungkin tidak lengkap.",
|
|
cancelOAuth: "Batal",
|
|
|
|
// MCP CLI & Gemini 1M
|
|
mcpCliExperimental: "MCP CLI (Eksperimental)",
|
|
mcpCliDesc: "Aktifkan integrasi MCP eksperimental untuk penggunaan tool yang lebih stabil dengan konsumsi konteks lebih rendah.",
|
|
gemini1mMode: "Mode Konteks Gemini 1M",
|
|
gemini1mDesc: "Tambahkan suffix [1m] ke model Gemini untuk context window 1M.",
|
|
gemini1mWarning: "⚠ Konteks besar dapat menurunkan performa Gemini-3-Pro.",
|
|
clickToSet: "Klik untuk atur...",
|
|
|
|
// Quota Distribution
|
|
quotaDistribution: "Distribusi Kuota",
|
|
resetsIn: "Reset dalam {time}",
|
|
noQuotaData: "Data kuota belum tersedia untuk akun ini.",
|
|
// Manual OAuth Mode
|
|
manualMode: "Mode Manual",
|
|
manualModeDesc: "(untuk lingkungan di mana callback tidak bisa dicapai)",
|
|
authLinkLabel: "Link Otorisasi:",
|
|
linkCopied: "Link disalin ke clipboard",
|
|
pasteCallbackLabel: "Tempel callback URL atau kode:",
|
|
pasteCallbackPlaceholder: "http://localhost:51121/oauth-callback?code=... atau 4/0xxx...",
|
|
completeAuth: "Selesaikan Otorisasi",
|
|
authFailed: "Otorisasi gagal",
|
|
// Import/Export
|
|
export: "Ekspor",
|
|
import: "Impor",
|
|
exportAccounts: "Ekspor Akun",
|
|
importAccounts: "Impor Akun",
|
|
exportSuccess: "Berhasil mengekspor {count} akun",
|
|
exportFailed: "Gagal mengekspor",
|
|
importSuccess: "Impor selesai:",
|
|
importFailed: "Gagal mengimpor",
|
|
|
|
// Completed TODOs
|
|
pageTitle: "Antigravity Console",
|
|
live: "Live",
|
|
tier: "Tier",
|
|
quota: "Kuota",
|
|
tierUltra: "Ultra",
|
|
tierPro: "Pro",
|
|
tierFree: "Gratis",
|
|
menu: "Menu",
|
|
github: "GitHub",
|
|
noData: "Tidak ada data",
|
|
fix: "Perbaiki",
|
|
|
|
// Error Messages
|
|
operationFailed: "Operasi gagal",
|
|
unknownError: "Error tidak diketahui",
|
|
presetNameRequired: "Nama preset wajib diisi",
|
|
saveFailed: "Gagal menyimpan",
|
|
failedToSavePreset: "Gagal menyimpan preset",
|
|
noPresetSelected: "Tidak ada preset dipilih",
|
|
deletePresetConfirm: "Hapus preset \"{name}\"?",
|
|
deleteFailed: "Gagal menghapus",
|
|
failedToDeletePreset: "Gagal menghapus preset",
|
|
failedToChangePassword: "Gagal mengubah password",
|
|
passwordChangedSuccess: "Password berhasil diubah",
|
|
debugModeToggled: "Mode debug {status}",
|
|
tokenCacheToggled: "Cache token {status}",
|
|
failedToUpdateTokenCache: "Gagal memperbarui cache token",
|
|
failedToUpdateDebugMode: "Gagal memperbarui mode debug",
|
|
failedToRefreshAccount: "Gagal memperbarui akun",
|
|
failedToDeleteAccount: "Gagal menghapus akun",
|
|
failedToReloadAccounts: "Gagal memuat ulang akun",
|
|
failedToUpdateModelConfig: "Gagal memperbarui konfigurasi model",
|
|
fieldUpdated: "{displayName} diubah menjadi {value}",
|
|
failedToUpdateField: "Gagal memperbarui {displayName}",
|
|
|
|
// Validation
|
|
mustBeValidNumber: "{fieldName} harus berupa angka valid",
|
|
mustBeAtLeast: "{fieldName} minimal {min}",
|
|
mustBeAtMost: "{fieldName} maksimal {max}",
|
|
cannotBeEmpty: "{fieldName} tidak boleh kosong",
|
|
mustBeTrueOrFalse: "Nilai harus true atau false",
|
|
|
|
// Account Selection Strategy translations
|
|
accountSelectionStrategy: "Strategi Pemilihan Akun",
|
|
selectionStrategy: "Strategi Pemilihan",
|
|
strategyStickyLabel: "Tetap (Optimisasi Cache)",
|
|
strategyRoundRobinLabel: "Bergilir (Load Balanced)",
|
|
strategyHybridLabel: "Hibrida (Distribusi Cerdas)",
|
|
strategyStickyDesc: "Tetap di akun yang sama hingga terkena rate limit. Terbaik untuk cache prompt.",
|
|
strategyRoundRobinDesc: "Berputar ke akun berikutnya setiap permintaan. Throughput maksimum.",
|
|
strategyHybridDesc: "Pemilihan cerdas berdasarkan kesehatan, token, dan kesegaran.",
|
|
strategyUpdated: "Strategi diubah ke: {strategy}",
|
|
failedToUpdateStrategy: "Gagal memperbarui strategi",
|
|
invalidStrategy: "Strategi tidak valid dipilih",
|
|
};
|