diff --git a/docs/i18n-inventory.md b/docs/i18n-inventory.md index 6dbfbf2..738f237 100644 --- a/docs/i18n-inventory.md +++ b/docs/i18n-inventory.md @@ -1,6 +1,6 @@ # i18n inventory (egdev6/quorb#66) -Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `c64cc863ec51e3d2d82a00cbc295db13ff38c51a` and `src-tauri/src/` = `77cd2af35c177d8a5d6b0b992a0319b0abb769c5`. +Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `87a618afd56fd64dcf0e329e20991f3808ff0a37` and `src-tauri/src/` = `210ed4f47e1041f3ef18253a37b170d8beb5efa8`. Any commit whose `git rev-parse :src` and `:src-tauri/src` match has these exact lines. Do not edit by hand: regenerate, or verify with `node scripts/i18n-inventory.mjs --check`. @@ -17,9 +17,9 @@ look like text. | Area | Distinct texts | Sites | |---|---:|---:| -| React (`src/`) | 573 | 699 | -| Rust (`src-tauri/src/`) | 291 | 345 | -| Total | 833 | 1044 | +| React (`src/`) | 590 | 720 | +| Rust (`src-tauri/src/`) | 303 | 359 | +| Total | 862 | 1079 | ## src-tauri/src/channel.rs @@ -49,8 +49,8 @@ look like text. | Line | Text | |---:|---| -| 280 | codex login | -| 563 | Cuenta {} | +| 281 | codex login | +| 591 | Cuenta {} | ## src-tauri/src/detail_geometry.rs @@ -271,18 +271,37 @@ look like text. | 127 | HTTP {code} en {method} | | 156 | model:{name} | +## src-tauri/src/providers/minimax.rs + +| Line | Text | +|---:|---| +| 20 | Añade tu Subscription Key de MiniMax en Ajustes. | +| 30 | Subscription Key de MiniMax rechazada. | +| 31 | HTTP {code} en /v1/token_plan/remains | +| 35 | Respuesta inesperada: {e} | +| 59 | MiniMax respondió bien pero su formato no coincide con el que se espera; puede haber cambiado el contrato. | +| 94 | MiniMax devolvió una respuesta sin base_resp | +| 97 | MiniMax devolvió una respuesta con base_resp vacío | +| 105 | MiniMax rechazó la consulta (status_code {code}) | +| 105 | MiniMax: {m} | +| 137 | 5 h | +| 200 | Saldo PAYG | +| 204 | USD | +| 222 | saldo {balance:.2} $ | +| 223 | saldo no disponible | + ## src-tauri/src/providers/mod.rs | Line | Text | |---:|---| -| 192 | Proveedor desconocido: {other} | -| 229 | No existe {} | -| 238 | No encuentro credenciales en: {} | -| 243 | No puedo leer {}: {e} | -| 244 | JSON inválido en {}: {e} | -| 286 | Tiempo de espera agotado | -| 288 | Sin conexión con el servidor | -| 290 | Error de red: {e} | +| 197 | Proveedor desconocido: {other} | +| 234 | No existe {} | +| 243 | No encuentro credenciales en: {} | +| 248 | No puedo leer {}: {e} | +| 249 | JSON inválido en {}: {e} | +| 291 | Tiempo de espera agotado | +| 293 | Sin conexión con el servidor | +| 295 | Error de red: {e} | ## src-tauri/src/providers/nan.rs @@ -416,7 +435,7 @@ look like text. | Line | Text | |---:|---| -| 71 | Clave desconocida: {key} | +| 79 | Clave desconocida: {key} | ## src-tauri/src/window_clip.rs @@ -509,447 +528,468 @@ look like text. | Line | Text | |---:|---| -| 82 | Versión actual | -| 85 | Actualización manual en macOS. No se buscan ni se instalan actualizaciones desde la app. | -| 86 | No se pudo determinar el modo de actualización. Reinicia Quorb para reintentar. | -| 87 | Consultando el modo de actualización… | -| 93 | Versión instalada | -| 103 | Hay una versión nueva: | -| 103 | Se descarga, se comprueba la firma y Quorb se reinicia. | -| 107 | Instalando${…} | -| 107 | Instalar y reiniciar | -| 121 | · estás en la última versión | -| 124 | Buscando… | -| 124 | Buscar ahora | -| 129 | Buscar actualizaciones solo | -| 129 | Al arrancar y cada 6 h. Si hay una nueva, aparece un icono de descarga en la tira. | -| 132 | Llegan firmadas desde | -| 134 | GitHub Releases | -| 136 | ; si la firma no coincide, no se instalan. | -| 202 | Guardada en el almacén seguro del sistema | -| 202 | Eliminada | -| 220 | •••••••• (configurada) | -| 220 | Pega aquí la key | -| 226 | Guardar | -| 228 | Eliminar key | -| 230 | Quitar | -| 260 | Cuenta de NaN Cloud conectada | -| 271 | Desconectar | -| 279 | Conectar tu cuenta (recomendado) | -| 282 | tu@correo.com | -| 290 | Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí. | -| 294 | Enviar enlace | -| 306 | Conectado como ${…} | -| 313 | Conectar | -| 316 | Volver | -| 321 | Igual que «nan auth login --email»: NaN te manda un enlace de un solo uso. No lo abras en el navegador (se gastaría): cópialo y pégalo aquí. La sesión se guarda en el almacén de credenciales del sistema y da los mismos datos que tu dashboard. | -| 333 | Probar la API de NaN con la key | -| 348 | Consultando… | -| 348 | Ver respuestas de /v1/models y /v1/usage | -| 368 | 9Router desconectado | -| 373 | Conectado | -| 386 | 9Router conectado · volver a conectar | -| 386 | Conectar con el dashboard | -| 392 | •••••••• (configurada) | -| 392 | Contraseña del dashboard | -| 397 | Guardar y probar | -| 400 | Desconectar 9Router | -| 402 | Quitar | -| 406 | La contraseña del dashboard de 9Router (por defecto «123456» hasta que la cambies). Se guarda en el almacén seguro del sistema. | -| 414 | Confeti | -| 415 | Glow de peligro | -| 416 | Efecto propio (vídeo o imagen animada) | -| 417 | Ninguno | -| 429 | Vídeo o imagen animada | -| 457 | Efecto | -| 472 | Probar | -| 476 | Archivo | -| 478 | Ningún archivo | -| 487 | Elegir… | -| 491 | Quitar | -| 513 | ${…}: ${…}. Pulsa para cambiarlo | -| 513 | sin atajo | -| 530 | Pulsa la combinación… (Esc cancela) | -| 530 | Sin atajo | -| 536 | Cambiar | -| 540 | Quitar | -| 569 | Falta | -| 569 | en este equipo. | -| 569 | Quorb inicia sesión con su login oficial. Instálalo desde | -| 570 | (necesita ${…}) | -| 570 | ; Quorb lo detecta solo al terminar. | -| 575 | Copiado | -| 575 | Copiar | -| 611 | Datos recibidos | -| 660 | Comprobando la conexión… | -| 661 | Funciona | -| 662 | Falta completar un paso | -| 663 | Sin sesión | -| 663 | Atención | -| 664 | No funciona | -| 669 | No se pudo comprobar ahora: ${…}. Vuelve a intentarlo. | -| 671 | Guarda los cambios para comprobar esta cuenta. | -| 680 | · oculta en el widget | -| 683 | Consultando con lo guardado… | -| 689 | Tienes cambios sin guardar: la comprobación usa lo guardado. | -| 701 | Guardar y comprobar | -| 705 | Comprobar ahora | -| 715 | Iniciar sesión | -| 797 | Instala | -| 797 | su CLI | -| 798 | Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione. | -| 803 | Fichero de sesión encontrado | -| 803 | Inicia sesión con la otra cuenta | -| 803 | Inicia sesión | -| 805 | Se abre una terminal con | -| 806 | (si no te lo pide, escribe | -| 806 | /login | -| 807 | (elige «Login with Google») | -| 807 | . El navegador hace el login oficial y el CLI guarda la sesión, que Quorb solo lee. | -| 809 | El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo. | -| 810 | Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada. | -| 814 | Volver a abrir el login | -| 814 | Iniciar sesión | -| 814 | Guardar e iniciar sesión | -| 827 | Comprobación | -| 828 | Se hace sola en cuanto guardes e inicies sesión. | -| 878 | Comprobación | -| 879 | Guarda la cuenta para comprobar que funciona. | -| 882 | Guardar y comprobar | -| 888 | Cuenta adicional | -| 890 | Nombre | -| 890 | Se ve en la tira junto al proveedor. | -| 894 | Mostrar en el widget | -| 896 | Cómo conectar esta cuenta | -| 898 | Ponle nombre | -| 899 | Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»). | -| 909 | Pega el token de esta cuenta | -| 910 | La app de Cursor guarda una sola sesión (la principal). Entra en cursor.com con esta cuenta y copia la cookie WorkosCursorSessionToken. | -| 911 | Token de la cuenta | -| 920 | Usuario y plan de GitHub | -| 922 | Usuario de GitHub | -| 925 | Plan | -| 927 | Free · 50 | -| 928 | Pro · 300 | -| 929 | Pro+ · 1500 | -| 930 | Business · 300 | -| 931 | Enterprise · 1000 | -| 932 | Otra cantidad | -| 937 | Peticiones premium incluidas al mes | -| 943 | Token de solo lectura | -| 946 | Token de GitHub de esta cuenta (fine-grained) | -| 949 | Créalo con esta cuenta en | -| 949 | GitHub › Fine-grained tokens | -| 949 | con el permiso «Plan» (solo lectura). | -| 959 | Conecta la cuenta o pega una API key | -| 961 | API key (alternativa) | -| 964 | Plan | -| 965 | Plan | -| 965 | La URL base y el resto de ajustes avanzados son los de la cuenta principal. | -| 967 | nan_community | -| 968 | nan_member | -| 969 | GLM Premium | -| 980 | API key | -| 983 | API key | -| 986 | Puede ser de otra cuenta o de la misma con otro límite. | -| 986 | openrouter.ai › Keys | -| 996 | Admin key de esta organización | -| 999 | Admin key | -| 1002 | Cada organización tiene su propia | -| 1002 | Admin key | -| 1006 | Presupuesto mensual (opcional) | -| 1007 | USD al mes | -| 1018 | Avanzado · carpeta de sesión | -| 1020 | Cada cuenta guarda su sesión en su propia carpeta, así no toca la principal: el login se abre con | -| 1021 | apuntando a ella. Si la comprobación pide renovar la sesión, «Volver a abrir el login» abre de nuevo el CLI. | -| 1023 | Carpeta de sesión | -| 1023 | Vacío = | -| 1023 | . Puedes usar una que ya tengas. Quorb lee | -| 1023 | dentro de ella. | -| 1031 | Eliminar esta cuenta | -| 1033 | Al guardar se borran también sus claves del almacén seguro del sistema. | -| 1040 | Claude | -| 1040 | Pro / Max · sesión de 5 h, semanal y por modelo | -| 1041 | ChatGPT y Codex | -| 1041 | Plus / Pro · sesión y semanal de Codex | -| 1042 | Gemini | -| 1042 | Cuota diaria por modelo | -| 1043 | Cursor | -| 1043 | Uso del plan en el ciclo y on-demand | -| 1044 | GitHub Copilot | -| 1044 | Peticiones premium del mes | -| 1045 | NaN Builders | -| 1045 | Cuota del plan y tokens por día | -| 1046 | Saldo y gasto por clave | -| 1047 | Anthropic API | -| 1047 | Gasto del mes con clave de admin | -| 1048 | OpenAI API | -| 1048 | Gasto del mes con clave de admin | -| 1050 | Pasarela local · uso y gasto agregados | -| 1055 | Proveedores | -| 1055 | Suscripciones y APIs | -| 1055 | Qué se consulta y cómo se conecta cada proveedor. | -| 1056 | Apariencia | -| 1056 | Apariencia | -| 1056 | Tamaño y color de los anillos, y dónde vive la tira. | -| 1057 | Alertas | -| 1057 | Alertas | -| 1057 | Qué pasa al superar un límite y cuando se reinicia. | -| 1058 | Atajos | -| 1058 | Atajos | -| 1058 | Combinaciones de teclado que funcionan desde cualquier app. | -| 1059 | Actualizaciones | -| 1059 | Actualizaciones | -| 1059 | Versión instalada, actualizaciones automáticas y el proyecto. | -| 1075 | Izquierda | -| 1076 | Derecha | -| 1077 | Arriba | -| 1078 | Abajo | -| 1085 | Activo | -| 1086 | Falta conectar | -| 1087 | Desactivado | -| 1170 | Quorb | -| 1178 | Secciones de ajustes | -| 1195 | Activos pero sin configurar | -| 1207 | Expandir el menú | -| 1207 | Contraer el menú | -| 1208 | Expandir el menú | -| 1208 | Contraer el menú | -| 1215 | Contraer | -| 1636 | · ${…} cuentas | -| 1641 | Conectar | -| 1641 | Configurar | -| 1672 | Arrancar al iniciar sesión | -| 1679 | No se pudo leer «Arrancar al iniciar sesión»: | -| 1686 | Reintentar | -| 1696 | Sesión de escritorio: | -| 1696 | Wayland | -| 1705 | con límites: | -| 1705 | sin límites conocidos: | -| 1714 | Proveedores | -| 1717 | Proveedores | -| 1721 | Consultar cada | -| 1723 | min | -| 1726 | Suscripciones | -| 1728 | APIs y créditos | -| 1730 | Para cambiar el orden de los anillos, arrástralos en la tira. | -| 1752 | Configuración | -| 1756 | Cerrar la configuración | -| 1767 | Cuentas | -| 1768 | Principal | -| 1782 | Los subproveedores de 9Router se descubren solos desde su historial de uso; no se añaden a mano. | -| 1785 | + Añadir cuenta | -| 1819 | Suscripción · OAuth | -| 1820 | Claude (Pro / Max) | -| 1821 | Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens. | -| 1822 | Fichero de credenciales | -| 1823 | Vacío = | -| 1823 | %USERPROFILE%\\.claude\\.credentials.json | -| 1823 | . Para WSL: | -| 1823 | \\\\wsl.localhost\\Ubuntu\\home\\TU_USUARIO\\.claude\\.credentials.json | -| 1826 | Suscripción · OAuth | -| 1827 | ChatGPT (Plus / Pro · Codex) | -| 1828 | Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex. | -| 1829 | Fichero auth.json | -| 1830 | Vacío = | -| 1830 | %CODEX_HOME% | -| 1830 | o | -| 1830 | %USERPROFILE%\\.codex\\auth.json | -| 1833 | Suscripción · sesión de Gemini CLI | -| 1834 | Gemini (Google AI Pro / Code Assist) | -| 1837 | Cuotas diarias por modelo. Quorb lee la sesión que guarda | -| 1837 | Gemini CLI | -| 1837 | . Endpoints no oficiales. | -| 1840 | Fichero de credenciales | -| 1841 | Vacío = | -| 1841 | %USERPROFILE%\\.gemini\\oauth_creds.json | -| 1846 | Mostrar en el widget | -| 1848 | Cómo conectar la cuenta | -| 1862 | Avanzado · fichero de sesión y comando de login | -| 1866 | Comando de login | -| 1875 | Suscripción · sesión de la app | -| 1875 | Cursor | -| 1876 | Mostrar en el widget | -| 1877 | Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial. | -| 1878 | Cómo conectar la cuenta | -| 1880 | Inicia sesión en la app de Cursor | -| 1882 | Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene prioridad sobre la sesión de la app. | -| 1885 | Token manual (opcional) | -| 1890 | Avanzado · base de datos de Cursor | -| 1891 | Base de datos de Cursor | -| 1891 | Vacío = | -| 1891 | %APPDATA%\\Cursor\\User\\globalStorage\\state.vscdb | -| 1899 | Suscripción · API oficial | -| 1899 | GitHub Copilot | -| 1900 | Mostrar en el widget | -| 1901 | Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC). | -| 1902 | Cómo conectar la cuenta | -| 1904 | Usuario y plan de GitHub | -| 1906 | Usuario de GitHub | -| 1907 | egdev6 | -| 1909 | Plan | -| 1911 | Free · 50 | -| 1912 | Pro · 300 | -| 1913 | Pro+ · 1500 | -| 1914 | Business · 300 | -| 1915 | Enterprise · 1000 | -| 1916 | Otra cantidad | -| 1921 | Peticiones premium incluidas al mes | -| 1927 | Token de solo lectura | -| 1930 | Token de GitHub (fine-grained) | -| 1933 | Créalo en | -| 1933 | GitHub › Fine-grained tokens | -| 1933 | con el permiso de cuenta «Plan»: solo lectura. | -| 1942 | Pago por uso · API oficial | -| 1942 | OpenRouter | -| 1943 | Mostrar en el widget | -| 1944 | Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta. | -| 1945 | Cómo conectar la cuenta | -| 1947 | API key | -| 1950 | API key | -| 1953 | Créala en | -| 1953 | openrouter.ai › Keys | -| 1962 | Suscripción · cuenta o API key | -| 1962 | NaN Builders | -| 1963 | Mostrar en el widget | -| 1965 | Con la cuenta conectada ves lo mismo que en tu dashboard: tokens usados frente a la cuota de cada modelo, cuándo se reinicia y la serie diaria. Sin cuenta, la API key usa | -| 1966 | /v1/usage | -| 1966 | y las cuotas publicadas. | -| 1966 | Docs | -| 1968 | Cómo conectar la cuenta | -| 1970 | Conecta tu cuenta o pega una API key | -| 1974 | API key (alternativa) | -| 1977 | Créala en | -| 1977 | cloud.nan.builders | -| 1977 | › API Keys | -| 1981 | Plan | -| 1982 | Plan | -| 1984 | nan_community | -| 1985 | nan_member | -| 1986 | GLM Premium | -| 1993 | Avanzado · URL, facturación y endpoint de consumo | -| 1994 | Base URL | -| 1997 | Día de inicio de facturación | -| 1997 | Solo para GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key. | -| 2001 | Endpoint de consumo · URL | -| 2001 | Se llama con tu key como Bearer y debe devolver JSON. | -| 2005 | JSON Pointer · usado | -| 2005 | p. ej. /usage/used_tokens | -| 2008 | JSON Pointer · límite | -| 2008 | p. ej. /usage/quota_tokens | -| 2012 | Etiqueta | -| 2013 | Consumo | -| 2020 | Pasarela local · uso agregado | -| 2020 | 9Router | -| 2022 | Mostrar en el widget | -| 2027 | Quorb lee el uso y el gasto que | -| 2027 | 9Router | -| 2027 | ya registra en tu equipo: peticiones, tokens y coste por proveedor del periodo. Además del orbe agregado, cada proveedor que haya visto (Claude, Kiro, GLM…) aparece como su propia cuenta. | -| 2030 | Cómo conectar 9Router | -| 2032 | Conecta tu dashboard local | -| 2036 | Periodo y desglose | -| 2037 | Periodo de uso | -| 2037 | Rango que muestra el orbe y su historial. | -| 2039 | Hoy | -| 2040 | Últimas 24 h | -| 2041 | Últimos 7 días | -| 2042 | Últimos 30 días | -| 2043 | Últimos 60 días | -| 2044 | Todo | -| 2048 | Mostrar cada sub-proveedor como su propia cuenta | -| 2052 | Gasto mínimo por sub-proveedor (USD) | -| 2052 | No muestra un sub-orbe hasta que su gasto en el periodo supere esta cifra. 0 = cualquiera que gaste. | -| 2061 | Presupuesto (USD) | -| 2061 | Opcional: pinta la barra de gasto del periodo frente al tope que pongas. | -| 2074 | Avanzado · dirección del servidor | -| 2075 | Base URL | -| 2075 | Dónde escucha 9Router (por defecto http://localhost:20128). | -| 2085 | Suborbes ocultos de 9Router | -| 2089 | · mostrar | -| 2093 | Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos. | -| 2105 | Pago por uso · Admin key | -| 2105 | Anthropic API | -| 2105 | OpenAI API | -| 2106 | Mostrar gasto del mes | -| 2107 | Cómo conectar la cuenta | -| 2109 | Admin key | -| 2112 | Admin API key (sk-ant-admin…) | -| 2112 | Admin key | -| 2117 | Las APIs de coste no admiten OAuth: hace falta una | -| 2117 | Admin key | -| 2117 | (solo lectura de la organización). | -| 2119 | Créala en | -| 2119 | Organization › Admin keys | -| 2125 | Presupuesto mensual (opcional) | -| 2126 | USD al mes | -| 2143 | Listo | -| 2145 | Guardando… | -| 2145 | Guardar cambios | -| 2154 | Anillos | -| 2156 | Vista previa a ${…} px | -| 2164 | Tamaño del anillo | -| 2164 | Restablecer ( | -| 2164 | px) | -| 2169 | Color de acento | -| 2172 | Usar ${…} | -| 2174 | Elegir cualquier color (RGB) | -| 2179 | Colores resultantes | -| 2188 | Es el del anillo exterior y la primera barra; los otros dos tonos salen solos. Los avisos y el exceso conservan sus colores de alerta. | -| 2189 | Restablecer rojo egdev | -| 2192 | Opacidad de la tira y el panel | -| 2196 | Círculos pequeños | -| 2196 | Un solo anillo por suscripción: el límite más cercano. | -| 2197 | Mostrar resets en los círculos | -| 2197 | El saldo de resets siempre sigue disponible en el panel de detalle. | -| 2201 | Tira | -| 2203 | Borde | -| 2211 | También puedes arrastrarla desde su asa: se pega al borde más cercano. | -| 2213 | Posición en el borde | -| 2217 | Ocultar sola | -| 2217 | Deja una franja en el borde y vuelve al acercar el ratón o cuando hay un aviso. | -| 2218 | Siempre encima | -| 2218 | Por encima del resto de ventanas. | -| 2222 | Personalización no aplicada | -| 2224 | Reintentar | -| 2233 | Español | -| 2234 | English | -| 2249 | Cuándo avisar | -| 2251 | Activar avisos | -| 2252 | Notificación del sistema | -| 2252 | Además del efecto en pantalla. | -| 2255 | 1 · Al superar el límite | -| 2256 | Avisar al llegar a | -| 2256 | Una vez por ventana (sesión de 5 h, semana, mes…). | -| 2262 | 2 · Al reiniciarse | -| 2264 | Avisar cuando el límite superado se reinicia | -| 2264 | «Claude · Sesión 5 h: disponible de nuevo» | -| 2272 | Efecto propio | -| 2274 | Un vídeo WebM (VP9) con canal alfa o una imagen animada GIF, WebP o APNG con transparencia. Se muestra centrado encima de todo, sin bloquear el ratón; el vídeo dura lo que dure (máx. 20 s) y las imágenes 4,5 s. | -| 2277 | Tamaño | -| 2279 | Pequeño | -| 2280 | Mediano | -| 2281 | Pantalla completa | -| 2285 | Con sonido | -| 2285 | Solo para vídeos. | -| 2292 | Atajos globales | -| 2294 | Activar atajos | -| 2294 | Funcionan aunque Quorb no tenga el foco. | -| 2300 | Atajos de teclado | -| 2303 | Acción | -| 2304 | Atajo | -| 2305 | Acciones | -| 2309 | Mostrar u ocultar la tira | -| 2310 | Abrir el más cargado | -| 2311 | Actualizar ahora | -| 2320 | Pulsa un atajo o «Cambiar» y teclea la combinación. Se aplican al guardar. | -| 2329 | Proyecto | -| 2330 | Quorb es open source (MIT). Si te ahorra un susto con los límites, una estrella en GitHub ayuda a que lo encuentre más gente. | -| 2332 | GitHub | -| 2333 | Pedir un proveedor | -| 2334 | egdev.es | -| 2345 | Cerrar | -| 2347 | Guardando… | -| 2347 | Guardar cambios | +| 84 | Versión actual | +| 87 | Actualización manual en macOS. No se buscan ni se instalan actualizaciones desde la app. | +| 88 | No se pudo determinar el modo de actualización. Reinicia Quorb para reintentar. | +| 89 | Consultando el modo de actualización… | +| 95 | Versión instalada | +| 105 | Hay una versión nueva: | +| 105 | Se descarga, se comprueba la firma y Quorb se reinicia. | +| 109 | Instalando${…} | +| 109 | Instalar y reiniciar | +| 123 | · estás en la última versión | +| 126 | Buscando… | +| 126 | Buscar ahora | +| 131 | Buscar actualizaciones solo | +| 131 | Al arrancar y cada 6 h. Si hay una nueva, aparece un icono de descarga en la tira. | +| 134 | Llegan firmadas desde | +| 136 | GitHub Releases | +| 138 | ; si la firma no coincide, no se instalan. | +| 204 | Guardada en el almacén seguro del sistema | +| 204 | Eliminada | +| 222 | •••••••• (configurada) | +| 222 | Pega aquí la key | +| 228 | Guardar | +| 230 | Eliminar key | +| 232 | Quitar | +| 262 | Cuenta de NaN Cloud conectada | +| 273 | Desconectar | +| 281 | Conectar tu cuenta (recomendado) | +| 284 | tu@correo.com | +| 292 | Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí. | +| 296 | Enviar enlace | +| 308 | Conectado como ${…} | +| 315 | Conectar | +| 318 | Volver | +| 323 | Igual que «nan auth login --email»: NaN te manda un enlace de un solo uso. No lo abras en el navegador (se gastaría): cópialo y pégalo aquí. La sesión se guarda en el almacén de credenciales del sistema y da los mismos datos que tu dashboard. | +| 335 | Probar la API de NaN con la key | +| 350 | Consultando… | +| 350 | Ver respuestas de /v1/models y /v1/usage | +| 370 | 9Router desconectado | +| 375 | Conectado | +| 388 | 9Router conectado · volver a conectar | +| 388 | Conectar con el dashboard | +| 394 | •••••••• (configurada) | +| 394 | Contraseña del dashboard | +| 399 | Guardar y probar | +| 402 | Desconectar 9Router | +| 404 | Quitar | +| 408 | La contraseña del dashboard de 9Router (por defecto «123456» hasta que la cambies). Se guarda en el almacén seguro del sistema. | +| 416 | Confeti | +| 417 | Glow de peligro | +| 418 | Efecto propio (vídeo o imagen animada) | +| 419 | Ninguno | +| 431 | Vídeo o imagen animada | +| 459 | Efecto | +| 474 | Probar | +| 478 | Archivo | +| 480 | Ningún archivo | +| 489 | Elegir… | +| 493 | Quitar | +| 515 | ${…}: ${…}. Pulsa para cambiarlo | +| 515 | sin atajo | +| 532 | Pulsa la combinación… (Esc cancela) | +| 532 | Sin atajo | +| 538 | Cambiar | +| 542 | Quitar | +| 571 | Falta | +| 571 | en este equipo. | +| 571 | Quorb inicia sesión con su login oficial. Instálalo desde | +| 572 | (necesita ${…}) | +| 572 | ; Quorb lo detecta solo al terminar. | +| 577 | Copiado | +| 577 | Copiar | +| 613 | Datos recibidos | +| 662 | Comprobando la conexión… | +| 663 | Funciona | +| 664 | Falta completar un paso | +| 665 | Sin sesión | +| 665 | Atención | +| 666 | No funciona | +| 671 | No se pudo comprobar ahora: ${…}. Vuelve a intentarlo. | +| 673 | Guarda los cambios para comprobar esta cuenta. | +| 682 | · oculta en el widget | +| 685 | Consultando con lo guardado… | +| 691 | Tienes cambios sin guardar: la comprobación usa lo guardado. | +| 703 | Guardar y comprobar | +| 707 | Comprobar ahora | +| 717 | Iniciar sesión | +| 799 | Instala | +| 799 | su CLI | +| 800 | Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione. | +| 805 | Fichero de sesión encontrado | +| 805 | Inicia sesión con la otra cuenta | +| 805 | Inicia sesión | +| 807 | Se abre una terminal con | +| 808 | (si no te lo pide, escribe | +| 808 | /login | +| 809 | (elige «Login with Google») | +| 809 | . El navegador hace el login oficial y el CLI guarda la sesión, que Quorb solo lee. | +| 811 | El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo. | +| 812 | Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada. | +| 816 | Volver a abrir el login | +| 816 | Iniciar sesión | +| 816 | Guardar e iniciar sesión | +| 829 | Comprobación | +| 830 | Se hace sola en cuanto guardes e inicies sesión. | +| 880 | Comprobación | +| 881 | Guarda la cuenta para comprobar que funciona. | +| 884 | Guardar y comprobar | +| 890 | Cuenta adicional | +| 892 | Nombre | +| 892 | Se ve en la tira junto al proveedor. | +| 896 | Mostrar en el widget | +| 898 | Cómo conectar esta cuenta | +| 900 | Ponle nombre | +| 901 | Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»). | +| 911 | Pega el token de esta cuenta | +| 912 | La app de Cursor guarda una sola sesión (la principal). Entra en cursor.com con esta cuenta y copia la cookie WorkosCursorSessionToken. | +| 913 | Token de la cuenta | +| 922 | Usuario y plan de GitHub | +| 924 | Usuario de GitHub | +| 927 | Plan | +| 929 | Free · 50 | +| 930 | Pro · 300 | +| 931 | Pro+ · 1500 | +| 932 | Business · 300 | +| 933 | Enterprise · 1000 | +| 934 | Otra cantidad | +| 939 | Peticiones premium incluidas al mes | +| 945 | Token de solo lectura | +| 948 | Token de GitHub de esta cuenta (fine-grained) | +| 951 | Créalo con esta cuenta en | +| 951 | GitHub › Fine-grained tokens | +| 951 | con el permiso «Plan» (solo lectura). | +| 961 | Conecta la cuenta o pega una API key | +| 963 | API key (alternativa) | +| 966 | Plan | +| 967 | Plan | +| 967 | La URL base y el resto de ajustes avanzados son los de la cuenta principal. | +| 969 | nan_community | +| 970 | nan_member | +| 971 | GLM Premium | +| 982 | API key | +| 985 | API key | +| 988 | Puede ser de otra cuenta o de la misma con otro límite. | +| 988 | openrouter.ai › Keys | +| 998 | Admin key de esta organización | +| 1001 | Admin key | +| 1004 | Cada organización tiene su propia | +| 1004 | Admin key | +| 1008 | Presupuesto mensual (opcional) | +| 1009 | USD al mes | +| 1020 | Avanzado · carpeta de sesión | +| 1022 | Cada cuenta guarda su sesión en su propia carpeta, así no toca la principal: el login se abre con | +| 1023 | apuntando a ella. Si la comprobación pide renovar la sesión, «Volver a abrir el login» abre de nuevo el CLI. | +| 1025 | Carpeta de sesión | +| 1025 | Vacío = | +| 1025 | . Puedes usar una que ya tengas. Quorb lee | +| 1025 | dentro de ella. | +| 1033 | Eliminar esta cuenta | +| 1035 | Al guardar se borran también sus claves del almacén seguro del sistema. | +| 1042 | Claude | +| 1042 | Pro / Max · sesión de 5 h, semanal y por modelo | +| 1043 | ChatGPT y Codex | +| 1043 | Plus / Pro · sesión y semanal de Codex | +| 1044 | Gemini | +| 1044 | Cuota diaria por modelo | +| 1045 | Cursor | +| 1045 | Uso del plan en el ciclo y on-demand | +| 1046 | GitHub Copilot | +| 1046 | Peticiones premium del mes | +| 1047 | Cuota del plan M/Token y saldo de pago por uso | +| 1048 | NaN Builders | +| 1048 | Cuota del plan y tokens por día | +| 1049 | Saldo y gasto por clave | +| 1050 | Anthropic API | +| 1050 | Gasto del mes con clave de admin | +| 1051 | OpenAI API | +| 1051 | Gasto del mes con clave de admin | +| 1053 | Pasarela local · uso y gasto agregados | +| 1058 | Proveedores | +| 1058 | Suscripciones y APIs | +| 1058 | Qué se consulta y cómo se conecta cada proveedor. | +| 1059 | Apariencia | +| 1059 | Apariencia | +| 1059 | Tamaño y color de los anillos, y dónde vive la tira. | +| 1060 | Alertas | +| 1060 | Alertas | +| 1060 | Qué pasa al superar un límite y cuando se reinicia. | +| 1061 | Atajos | +| 1061 | Atajos | +| 1061 | Combinaciones de teclado que funcionan desde cualquier app. | +| 1062 | Actualizaciones | +| 1062 | Actualizaciones | +| 1062 | Versión instalada, actualizaciones automáticas y el proyecto. | +| 1078 | Izquierda | +| 1079 | Derecha | +| 1080 | Arriba | +| 1081 | Abajo | +| 1088 | Activo | +| 1089 | Falta conectar | +| 1090 | Desactivado | +| 1176 | Quorb | +| 1184 | Secciones de ajustes | +| 1201 | Activos pero sin configurar | +| 1213 | Expandir el menú | +| 1213 | Contraer el menú | +| 1214 | Expandir el menú | +| 1214 | Contraer el menú | +| 1221 | Contraer | +| 1642 | · ${…} cuentas | +| 1647 | Conectar | +| 1647 | Configurar | +| 1678 | Arrancar al iniciar sesión | +| 1685 | No se pudo leer «Arrancar al iniciar sesión»: | +| 1692 | Reintentar | +| 1702 | Sesión de escritorio: | +| 1702 | Wayland | +| 1711 | con límites: | +| 1711 | sin límites conocidos: | +| 1720 | Proveedores | +| 1723 | Proveedores | +| 1727 | Consultar cada | +| 1729 | min | +| 1732 | Suscripciones | +| 1734 | APIs y créditos | +| 1736 | Para cambiar el orden de los anillos, arrástralos en la tira. | +| 1758 | Configuración | +| 1762 | Cerrar la configuración | +| 1773 | Cuentas | +| 1774 | Principal | +| 1788 | Los subproveedores de 9Router se descubren solos desde su historial de uso; no se añaden a mano. | +| 1791 | + Añadir cuenta | +| 1825 | Suscripción · OAuth | +| 1826 | Claude (Pro / Max) | +| 1827 | Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens. | +| 1828 | Fichero de credenciales | +| 1829 | Vacío = | +| 1829 | %USERPROFILE%\\.claude\\.credentials.json | +| 1829 | . Para WSL: | +| 1829 | \\\\wsl.localhost\\Ubuntu\\home\\TU_USUARIO\\.claude\\.credentials.json | +| 1832 | Suscripción · OAuth | +| 1833 | ChatGPT (Plus / Pro · Codex) | +| 1834 | Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex. | +| 1835 | Fichero auth.json | +| 1836 | Vacío = | +| 1836 | %CODEX_HOME% | +| 1836 | o | +| 1836 | %USERPROFILE%\\.codex\\auth.json | +| 1839 | Suscripción · sesión de Gemini CLI | +| 1840 | Gemini (Google AI Pro / Code Assist) | +| 1843 | Cuotas diarias por modelo. Quorb lee la sesión que guarda | +| 1843 | Gemini CLI | +| 1843 | . Endpoints no oficiales. | +| 1846 | Fichero de credenciales | +| 1847 | Vacío = | +| 1847 | %USERPROFILE%\\.gemini\\oauth_creds.json | +| 1852 | Mostrar en el widget | +| 1854 | Cómo conectar la cuenta | +| 1868 | Avanzado · fichero de sesión y comando de login | +| 1872 | Comando de login | +| 1881 | Suscripción · sesión de la app | +| 1881 | Cursor | +| 1882 | Mostrar en el widget | +| 1883 | Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial. | +| 1884 | Cómo conectar la cuenta | +| 1886 | Inicia sesión en la app de Cursor | +| 1888 | Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene prioridad sobre la sesión de la app. | +| 1891 | Token manual (opcional) | +| 1896 | Avanzado · base de datos de Cursor | +| 1897 | Base de datos de Cursor | +| 1897 | Vacío = | +| 1897 | %APPDATA%\\Cursor\\User\\globalStorage\\state.vscdb | +| 1905 | Suscripción · API oficial | +| 1905 | GitHub Copilot | +| 1906 | Mostrar en el widget | +| 1907 | Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC). | +| 1908 | Cómo conectar la cuenta | +| 1910 | Usuario y plan de GitHub | +| 1912 | Usuario de GitHub | +| 1913 | egdev6 | +| 1915 | Plan | +| 1917 | Free · 50 | +| 1918 | Pro · 300 | +| 1919 | Pro+ · 1500 | +| 1920 | Business · 300 | +| 1921 | Enterprise · 1000 | +| 1922 | Otra cantidad | +| 1927 | Peticiones premium incluidas al mes | +| 1933 | Token de solo lectura | +| 1936 | Token de GitHub (fine-grained) | +| 1939 | Créalo en | +| 1939 | GitHub › Fine-grained tokens | +| 1939 | con el permiso de cuenta «Plan»: solo lectura. | +| 1948 | Suscripción · API oficial | +| 1948 | MiniMax | +| 1949 | Mostrar en el widget | +| 1951 | Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son | +| 1952 | dos credenciales distintas | +| 1952 | en la consola: la primera da acceso al plan y la segunda al saldo. | +| 1954 | Cómo conectar la cuenta | +| 1956 | Subscription Key del plan | +| 1959 | Subscription Key | +| 1962 | Del panel M/Token en | +| 1962 | minimax.io | +| 1962 | . En la región de China, en | +| 1962 | minimaxi.com | +| 1962 | (el host por región se cambia en Ajustes avanzados). | +| 1966 | API key de pago por uso (opcional) | +| 1969 | API key PAYG | +| 1972 | Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade. | +| 1978 | Avanzado · URL del API | +| 1980 | Base URL | +| 1981 | Las claves son por región: global en api.minimax.io, China en api.minimaxi.com. Si el anillo sale con error 1004 o 2049, prueba con la de tu región. | +| 1994 | Pago por uso · API oficial | +| 1994 | OpenRouter | +| 1995 | Mostrar en el widget | +| 1996 | Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta. | +| 1997 | Cómo conectar la cuenta | +| 1999 | API key | +| 2002 | API key | +| 2005 | Créala en | +| 2005 | openrouter.ai › Keys | +| 2014 | Suscripción · cuenta o API key | +| 2014 | NaN Builders | +| 2015 | Mostrar en el widget | +| 2017 | Con la cuenta conectada ves lo mismo que en tu dashboard: tokens usados frente a la cuota de cada modelo, cuándo se reinicia y la serie diaria. Sin cuenta, la API key usa | +| 2018 | /v1/usage | +| 2018 | y las cuotas publicadas. | +| 2018 | Docs | +| 2020 | Cómo conectar la cuenta | +| 2022 | Conecta tu cuenta o pega una API key | +| 2026 | API key (alternativa) | +| 2029 | Créala en | +| 2029 | cloud.nan.builders | +| 2029 | › API Keys | +| 2033 | Plan | +| 2034 | Plan | +| 2036 | nan_community | +| 2037 | nan_member | +| 2038 | GLM Premium | +| 2045 | Avanzado · URL, facturación y endpoint de consumo | +| 2046 | Base URL | +| 2049 | Día de inicio de facturación | +| 2049 | Solo para GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key. | +| 2053 | Endpoint de consumo · URL | +| 2053 | Se llama con tu key como Bearer y debe devolver JSON. | +| 2057 | JSON Pointer · usado | +| 2057 | p. ej. /usage/used_tokens | +| 2060 | JSON Pointer · límite | +| 2060 | p. ej. /usage/quota_tokens | +| 2064 | Etiqueta | +| 2065 | Consumo | +| 2072 | Pasarela local · uso agregado | +| 2072 | 9Router | +| 2074 | Mostrar en el widget | +| 2079 | Quorb lee el uso y el gasto que | +| 2079 | 9Router | +| 2079 | ya registra en tu equipo: peticiones, tokens y coste por proveedor del periodo. Además del orbe agregado, cada proveedor que haya visto (Claude, Kiro, GLM…) aparece como su propia cuenta. | +| 2082 | Cómo conectar 9Router | +| 2084 | Conecta tu dashboard local | +| 2088 | Periodo y desglose | +| 2089 | Periodo de uso | +| 2089 | Rango que muestra el orbe y su historial. | +| 2091 | Hoy | +| 2092 | Últimas 24 h | +| 2093 | Últimos 7 días | +| 2094 | Últimos 30 días | +| 2095 | Últimos 60 días | +| 2096 | Todo | +| 2100 | Mostrar cada sub-proveedor como su propia cuenta | +| 2104 | Gasto mínimo por sub-proveedor (USD) | +| 2104 | No muestra un sub-orbe hasta que su gasto en el periodo supere esta cifra. 0 = cualquiera que gaste. | +| 2113 | Presupuesto (USD) | +| 2113 | Opcional: pinta la barra de gasto del periodo frente al tope que pongas. | +| 2126 | Avanzado · dirección del servidor | +| 2127 | Base URL | +| 2127 | Dónde escucha 9Router (por defecto http://localhost:20128). | +| 2137 | Suborbes ocultos de 9Router | +| 2141 | · mostrar | +| 2145 | Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos. | +| 2157 | Pago por uso · Admin key | +| 2157 | Anthropic API | +| 2157 | OpenAI API | +| 2158 | Mostrar gasto del mes | +| 2159 | Cómo conectar la cuenta | +| 2161 | Admin key | +| 2164 | Admin API key (sk-ant-admin…) | +| 2164 | Admin key | +| 2169 | Las APIs de coste no admiten OAuth: hace falta una | +| 2169 | Admin key | +| 2169 | (solo lectura de la organización). | +| 2171 | Créala en | +| 2171 | Organization › Admin keys | +| 2177 | Presupuesto mensual (opcional) | +| 2178 | USD al mes | +| 2195 | Listo | +| 2197 | Guardando… | +| 2197 | Guardar cambios | +| 2206 | Anillos | +| 2208 | Vista previa a ${…} px | +| 2216 | Tamaño del anillo | +| 2216 | Restablecer ( | +| 2216 | px) | +| 2221 | Color de acento | +| 2224 | Usar ${…} | +| 2226 | Elegir cualquier color (RGB) | +| 2231 | Colores resultantes | +| 2240 | Es el del anillo exterior y la primera barra; los otros dos tonos salen solos. Los avisos y el exceso conservan sus colores de alerta. | +| 2241 | Restablecer rojo egdev | +| 2244 | Opacidad de la tira y el panel | +| 2248 | Círculos pequeños | +| 2248 | Un solo anillo por suscripción: el límite más cercano. | +| 2249 | Mostrar resets en los círculos | +| 2249 | El saldo de resets siempre sigue disponible en el panel de detalle. | +| 2253 | Tira | +| 2255 | Borde | +| 2263 | También puedes arrastrarla desde su asa: se pega al borde más cercano. | +| 2265 | Posición en el borde | +| 2269 | Ocultar sola | +| 2269 | Deja una franja en el borde y vuelve al acercar el ratón o cuando hay un aviso. | +| 2270 | Siempre encima | +| 2270 | Por encima del resto de ventanas. | +| 2274 | Personalización no aplicada | +| 2276 | Reintentar | +| 2285 | Español | +| 2286 | English | +| 2301 | Cuándo avisar | +| 2303 | Activar avisos | +| 2304 | Notificación del sistema | +| 2304 | Además del efecto en pantalla. | +| 2307 | 1 · Al superar el límite | +| 2308 | Avisar al llegar a | +| 2308 | Una vez por ventana (sesión de 5 h, semana, mes…). | +| 2314 | 2 · Al reiniciarse | +| 2316 | Avisar cuando el límite superado se reinicia | +| 2316 | «Claude · Sesión 5 h: disponible de nuevo» | +| 2324 | Efecto propio | +| 2326 | Un vídeo WebM (VP9) con canal alfa o una imagen animada GIF, WebP o APNG con transparencia. Se muestra centrado encima de todo, sin bloquear el ratón; el vídeo dura lo que dure (máx. 20 s) y las imágenes 4,5 s. | +| 2329 | Tamaño | +| 2331 | Pequeño | +| 2332 | Mediano | +| 2333 | Pantalla completa | +| 2337 | Con sonido | +| 2337 | Solo para vídeos. | +| 2344 | Atajos globales | +| 2346 | Activar atajos | +| 2346 | Funcionan aunque Quorb no tenga el foco. | +| 2352 | Atajos de teclado | +| 2355 | Acción | +| 2356 | Atajo | +| 2357 | Acciones | +| 2361 | Mostrar u ocultar la tira | +| 2362 | Abrir el más cargado | +| 2363 | Actualizar ahora | +| 2372 | Pulsa un atajo o «Cambiar» y teclea la combinación. Se aplican al guardar. | +| 2381 | Proyecto | +| 2382 | Quorb es open source (MIT). Si te ahorra un susto con los límites, una estrella en GitHub ayuda a que lo encuentre más gente. | +| 2384 | GitHub | +| 2385 | Pedir un proveedor | +| 2386 | egdev.es | +| 2397 | Cerrar | +| 2399 | Guardando… | +| 2399 | Guardar cambios | ## src/Widget.tsx @@ -1075,7 +1115,7 @@ look like text. | Line | Text | |---:|---| | 35 | viewBox="${…}" | -| 129 | API | +| 132 | API | ## src/components/RingGauge.tsx diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index d4b1f38..d735168 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -82,6 +82,7 @@ pub struct AppConfig { pub anthropic_api: ApiCostConfig, pub openai_api: ApiCostConfig, pub nan: NanConfig, + pub minimax: MiniMaxConfig, pub nine_router: NineRouterConfig, /// Cuentas adicionales de un proveedor (la principal es la de arriba). pub accounts: Vec, @@ -295,6 +296,7 @@ impl Default for AppConfig { anthropic_api: ApiCostConfig::default(), openai_api: ApiCostConfig::default(), nan: NanConfig::default(), + minimax: MiniMaxConfig::default(), nine_router: NineRouterConfig::default(), accounts: vec![], } @@ -425,6 +427,28 @@ pub struct ApiCostConfig { pub monthly_budget_usd: Option, } +/// MiniMax. `base_url` cubre las dos regiones: global `api.minimax.io` y +/// China `api.minimaxi.com`. +/// +/// Verificado contra la API en los cuatro hosts: `api.` y `www.` de ambas +/// regiones responden el mismo JSON con el sobre `base_resp`, así que cualquiera +/// de los dos sirve. Se usa `api.` por semántica (es el host de la API, no el +/// sitio web). La documentación oficial del Token Plan muestra `www.minimax.io` +/// en su ejemplo de `curl`, y ese host también responde, por si alguien prefiere +/// copiarlo tal cual desde Ajustes. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(default, rename_all = "camelCase")] +pub struct MiniMaxConfig { + pub enabled: bool, + pub base_url: String, +} + +impl Default for MiniMaxConfig { + fn default() -> Self { + Self { enabled: false, base_url: "https://api.minimax.io".into() } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(default, rename_all = "camelCase")] pub struct NanConfig { @@ -523,6 +547,10 @@ impl AppConfig { self.nan.base_url = NanConfig::default().base_url; } self.nan.base_url = self.nan.base_url.trim().trim_end_matches('/').to_string(); + if self.minimax.base_url.trim().is_empty() { + self.minimax.base_url = MiniMaxConfig::default().base_url; + } + self.minimax.base_url = self.minimax.base_url.trim().trim_end_matches('/').to_string(); self.nan.billing_day = self.nan.billing_day.clamp(1, 28); if self.nine_router.base_url.trim().is_empty() { self.nine_router.base_url = NineRouterConfig::default().base_url; diff --git a/src-tauri/src/providers/minimax.rs b/src-tauri/src/providers/minimax.rs new file mode 100644 index 0000000..1b444c9 --- /dev/null +++ b/src-tauri/src/providers/minimax.rs @@ -0,0 +1,362 @@ +//! MiniMax — API oficial. +//! · `GET {base}/v1/token_plan/remains` → cuota del plan (Subscription Key del plan M/Token) +//! · `GET {base}/account/query_balance` → saldo PAYG (API key de pago por uso, best-effort) +//! +//! Son **dos credenciales distintas** en la consola de MiniMax: la Subscription Key +//! del plan y la API key de pago por uso. Cada medidor usa la suya. +//! +//! MiniMax **responde HTTP 200 incluso cuando la operación falla**: el estado real +//! viaja en el sobre `base_resp`. Ver [[envelope]]. + +use super::{net_error, Who}; +use crate::config::MiniMaxConfig; +use crate::model::{Kind, Meter, Status, UsageReport}; +use crate::secrets; +use serde_json::Value; + +pub async fn fetch(cfg: &MiniMaxConfig, who: Who<'_>, http: &reqwest::Client) -> UsageReport { + let report = who.report("MiniMax", Kind::Subscription); + let Some(key) = who.secret(secrets::MINIMAX_SUBSCRIPTION_KEY) else { + return report.fail(Status::Unconfigured, "Añade tu Subscription Key de MiniMax en Ajustes."); + }; + let base = cfg.base_url.trim_end_matches('/'); + + let res = match http.get(format!("{base}/v1/token_plan/remains")).bearer_auth(&key).send().await { + Ok(r) => r, + Err(e) => return report.fail(Status::Error, net_error(e)), + }; + match res.status().as_u16() { + 200 => {} + 401 | 403 => return report.fail(Status::Unconfigured, "Subscription Key de MiniMax rechazada."), + code => return report.fail(Status::Error, format!("HTTP {code} en /v1/token_plan/remains")), + } + let plan: Value = match res.json().await { + Ok(v) => v, + Err(e) => return report.fail(Status::Error, format!("Respuesta inesperada: {e}")), + }; + if let Err(msg) = envelope(&plan) { + return report.fail(Status::Error, msg); + } + + // El saldo PAYG va con otra clave y es un endpoint que no está en la + // documentación pública oficial: si falla, el informe del plan sigue válido. + // Guardamos si había clave para poder distinguir «no configuré el saldo» de + // «lo configuré y falló»: sin eso el usuario no tiene forma de saberlo. + let payg_key = who.secret(secrets::MINIMAX_API_KEY); + let balance = match payg_key.as_deref() { + Some(api_key) => balance(http, base, api_key).await, + None => None, + }; + + let mut report = report; + let parsed = parse(&plan, balance.as_ref(), payg_key.is_some()); + // Un informe sin medidores y sin mensaje no dice nada: sería un orbe en cero + // que el usuario lee como «no he consumido». Si MiniMax renombra los campos, + // esto lo delata en vez de fallar en silencio. + if parsed.meters.is_empty() && parsed.message.is_none() { + return report.fail( + Status::Warning, + "MiniMax respondió bien pero su formato no coincide con el que se espera; puede haber cambiado el contrato.", + ); + } + report.meters = parsed.meters; + report.message = parsed.message; + report +} + +pub(crate) struct Parsed { + pub meters: Vec, + pub message: Option, +} + +/// Lectura **best-effort** del saldo: cualquier fallo devuelve `None` y deja el +/// informe del plan intacto. No reintenta ni distingue causas, porque ninguna +/// cambia lo que el usuario ve. +async fn balance(http: &reqwest::Client, base: &str, api_key: &str) -> Option { + let res = http.get(format!("{base}/account/query_balance")).bearer_auth(api_key).send().await.ok()?; + if !res.status().is_success() { + return None; + } + let value: Value = res.json().await.ok()?; + envelope(&value).ok()?; + Some(value) +} + +/// MiniMax devuelve **HTTP 200 con el fallo dentro de `base_resp`**. Sin este +/// chequeo, una clave caducada o un plan inexistente producen un informe vacío +/// que parece correcto, que es peor que un fallo visible. +/// +/// Estricto con el sobre ausente: un `token_plan/remains` que no lo trae no es un +/// payload que sabemos leer, así que se reporta como error en vez de devolver +/// cero medidores en silencio. +fn envelope(v: &Value) -> Result<(), String> { + let Some(base) = v.get("base_resp") else { + return Err("MiniMax devolvió una respuesta sin base_resp".into()); + }; + if base.is_null() { + return Err("MiniMax devolvió una respuesta con base_resp vacío".into()); + } + match num(base.get("status_code")) { + Some(0.0) => Ok(()), + Some(code) => Err(base + .get("status_msg") + .and_then(Value::as_str) + .filter(|m| !m.is_empty()) + .map_or_else(|| format!("MiniMax rechazó la consulta (status_code {code})"), |m| format!("MiniMax: {m}"))), + // Sin código no hay veredicto: no se inventa un fallo ni un éxito. + None => Ok(()), + } +} + +/// Los importes de MiniMax llegan como cadenas decimales en varios endpoints y +/// como números en otros. Aceptamos ambos sin adivinar. +fn num(v: Option<&Value>) -> Option { + v.and_then(|x| x.as_f64().or_else(|| x.as_str().and_then(|s| s.trim().parse().ok()))) +} + + +/// Las dos ventanas del Token Plan. +/// +/// Campos **del payload real** (GET `/v1/token_plan/remains`), no de documentación: +/// los contadores vienen dentro de `model_remains[]`, uno por categoría de modelo +/// (`general`, `video`…), y el campo fiable es `current_*_remaining_percent`, un +/// porcentaje **restante**. Los contadores `_usage_count` / `_total_count` no sirven: +/// en `general` vienen a 0 mientras el porcentaje restante dice 81, así que dividir +/// uno por otro daría 0 % con el 19 % consumido. +struct Window { + id: &'static str, + label: &'static str, + remaining: &'static str, + start: &'static str, + end: &'static str, +} + +const WINDOWS: [Window; 2] = [ + Window { + id: "5h", + label: "5 h", + remaining: "current_interval_remaining_percent", + start: "start_time", + end: "end_time", + }, + Window { + id: "weekly", + label: "semanal", + remaining: "current_weekly_remaining_percent", + start: "weekly_start_time", + end: "weekly_end_time", + }, +]; + +/// MiniMax manda los tiempos en **milisegundos** desde epoch. Con segundos, una +/// ventana de 2026 caería en el año 58.000. +fn millis(v: Option<&Value>) -> Option { + num(v).map(|n| (n / 1000.0) as i64) +} + +pub(crate) fn parse(plan: &Value, balance: Option<&Value>, payg_configured: bool) -> Parsed { + let mut meters = vec![]; + + // El payload real trae `model_remains[]`: **una entrada por categoría de + // modelo**, no una barra compartida. Por eso el id del medidor lleva el + // nombre del modelo: `general` y `video` consumen cuotas distintas y hay que + // poder distinguirlos en el popup y en el historial. + // + // El porcentaje sale de `100 - current_*_remaining_percent`, que es un + // **restante**. Los contadores `_usage_count` / `_total_count` existen pero no + // son fiables (en `general` vienen a 0 con el 81 % restante), así que + // dividirlos daría un anillo en cero con la cuota realmente consumida. + for entry in plan.get("model_remains").and_then(Value::as_array).into_iter().flatten() { + let Some(model) = entry.get("model_name").and_then(Value::as_str) else { continue }; + let slug: String = model + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c.to_ascii_lowercase() } else { '_' }) + .collect(); + for window in WINDOWS { + let Some(remaining) = num(entry.get(window.remaining)) else { continue }; + let start = millis(entry.get(window.start)); + let end = millis(entry.get(window.end)); + meters.push(Meter { + id: format!("token_plan_{slug}_{}", window.id), + label: format!("{model} · {label}", label = window.label), + percent: Some((100.0 - remaining).clamp(0.0, 100.0)), + // El payload no trae totales en unidades utilizables, así que no + // se inventan `used`/`limit`: solo el porcentaje es verificable. + used: None, + limit: None, + unit: None, + resets_at: end.and_then(|at| chrono::DateTime::from_timestamp(at, 0)).map(|at| at.to_rfc3339()), + window_seconds: start.zip(end).map(|(s, e)| e - s), + }); + } + } + + // El saldo PAYG no es un porcentaje: no hay «total» contra el que medirlo. + // Va como medidor sin `percent`, que la UI ya sabe mostrar sin añadir un anillo. + let available = balance.and_then(|b| num(b.get("available_amount"))); + if let Some(amount) = available { + meters.push(Meter { + id: "payg_balance".into(), + label: "Saldo PAYG".into(), + percent: None, + used: Some(amount), + limit: None, + unit: Some("USD".into()), + resets_at: None, + window_seconds: None, + }); + } + + // El mensaje resume el **peor** medidor del plan, no el primero: con varias + // categorías de modelo y dos ventanas cada una, decir solo el primero esconde + // el que está al límite. + let worst = meters + .iter() + .filter(|m| m.percent.is_some()) + .max_by(|a, b| a.percent.partial_cmp(&b.percent).unwrap_or(std::cmp::Ordering::Equal)); + let plan = worst.map(|m| format!("{} {:.0}%", m.label, m.percent.unwrap_or(0.0))); + // Con la API key puesta y el saldo sin llegar, el usuario no puede + // distinguir «no lo configuré» de «lo configuré y falla». Decírselo es la + // diferencia entre depurar una clave y pensar que MiniMax no publica el saldo. + let saldo = match available { + Some(balance) => Some(format!("saldo {balance:.2} $")), + None if payg_configured => Some("saldo no disponible".to_string()), + None => None, + }; + let message = match (plan, saldo) { + (Some(plan), Some(saldo)) => Some(format!("{plan} · {saldo}")), + (Some(plan), None) => Some(plan), + (None, Some(saldo)) => Some(saldo), + (None, None) => None, + }; + Parsed { meters, message } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + /// Envuelve un cuerpo **en el mismo nivel** que `base_resp`, como lo hace + /// MiniMax: `model_remains` y el sobre son hermanos. + fn ok(mut body: Value) -> Value { + body.as_object_mut() + .expect("el fixture debe ser un objeto") + .insert("base_resp".into(), json!({"status_code": 0, "status_msg": "success"})); + body + } + + /// **Payload real** de `GET /v1/token_plan/remains` con una Subscription Key + /// válida,recortado a los campos que el parser usa. Reproduce los tres rasgos + /// que rompían el parser anterior: los datos viven en `model_remains[]`, hay + /// una entrada por categoría de modelo, y los contadores vienen a 0 mientras + /// el porcentaje restante dice otra cosa. + fn real() -> Value { + json!({ + "model_remains": [ + { + "start_time": 1_792_681_200_000i64, "end_time": 1_792_699_200_000i64, + "remains_time": 4_587_190, + "current_interval_total_count": 0, "current_interval_usage_count": 0, + "model_name": "general", + "current_weekly_total_count": 0, "current_weekly_usage_count": 0, + "weekly_start_time": 1_792_368_000_000i64, "weekly_end_time": 1_792_972_800_000i64, + "current_interval_status": 1, "current_interval_remaining_percent": 81, + "current_weekly_status": 1, "current_weekly_remaining_percent": 95 + }, + { + "start_time": 1_792_627_200_000i64, "end_time": 1_792_713_600_000i64, + "current_interval_total_count": 3, "current_interval_usage_count": 0, + "model_name": "video", + "current_weekly_total_count": 21, "current_weekly_usage_count": 0, + "current_interval_remaining_percent": 100, + "current_weekly_remaining_percent": 100 + } + ], + "base_resp": { "status_code": 0, "status_msg": "success" } + }) + } + + #[test] + fn lee_el_porcentaje_del_payload_real_y_no_de_los_contadores_a_cero() { + let p = parse(&real(), None, false); + // Los contadores de `general` valen 0/0; si el parser los usara daría 0 %. + let general = p.meters.iter().find(|m| m.id == "token_plan_general_5h").expect("medidor de general"); + assert_eq!(general.percent, Some(19.0), "81 % restante = 19 % consumido"); + assert_eq!(general.used, None, "el payload no da totales en unidades utilizables"); + assert_eq!(general.limit, None); + // 5 h y semanal, por cada categoría. + assert_eq!( + p.meters.iter().map(|m| m.id.as_str()).collect::>(), + ["token_plan_general_5h", "token_plan_general_weekly", "token_plan_video_5h", "token_plan_video_weekly"] + ); + assert_eq!(p.meters.iter().find(|m| m.id == "token_plan_general_weekly").unwrap().percent, Some(5.0)); + assert_eq!(p.meters.iter().find(|m| m.id == "token_plan_video_5h").unwrap().percent, Some(0.0)); + // El peor medidor manda en el mensaje: general 5 h al 19 %, no video al 0 %. + assert_eq!(p.message.as_deref(), Some("general · 5 h 19%")); + } + + #[test] + fn interpreta_los_tiempos_como_milisegundos() { + let p = parse(&real(), None, false); + let general = &p.meters[0]; + assert_eq!(general.resets_at.as_deref(), Some("2026-10-22T20:00:00+00:00")); + assert_eq!(general.window_seconds, Some(18_000), "la ventana de 5 h son 5 horas, no 5 millones"); + assert_eq!(p.meters[1].resets_at.as_deref(), Some("2026-10-26T00:00:00+00:00")); + } + + #[test] + fn un_solo_modelo_no_inventa_los_demas() { + let p = parse(&real(), None, false); + // 4 medidores: 2 categorías x 2 ventanas. Ni uno de más. + assert_eq!(p.meters.len(), 4); + let vacio = parse(&json!({"model_remains": [], "base_resp": {"status_code": 0}}), None, false); + assert!(vacio.meters.is_empty()); + let sin_array = parse(&ok(json!({})), None, false); + assert!(sin_array.meters.is_empty() && sin_array.message.is_none()); + } + + #[test] + fn rejects_a_200_carrying_a_failed_base_resp() { + let v = json!({"base_resp": {"status_code": 1004, "status_msg": "invalid api key"}}); + assert_eq!(envelope(&v).unwrap_err(), "MiniMax: invalid api key"); + let sin_mensaje = json!({"base_resp": {"status_code": 1008}}); + assert_eq!(envelope(&sin_mensaje).unwrap_err(), "MiniMax rechazó la consulta (status_code 1008)"); + assert!(envelope(&real()).is_ok(), "una respuesta real de éxito trae base_resp con status_code 0"); + assert!(envelope(&json!({"model_remains": []})).is_err(), "sin sobre no se afirma un éxito sin verificar"); + assert!(envelope(&json!({"base_resp": null})).is_err()); + assert!(envelope(&json!({"base_resp": {}})).is_ok(), "sin código no hay veredicto en ninguna dirección"); + } + + #[test] + fn saldo_payg_es_un_valor_y_nunca_una_anillo() { + let p = parse( + &real(), + Some(&ok(json!({"available_amount": "12.50", "cash_balance": "10.00", "voucher_balance": "2.50"}))), + true, + ); + let balance = p.meters.iter().find(|m| m.id == "payg_balance").expect("medidor de saldo"); + assert_eq!(balance.percent, None, "un saldo no es un porcentaje consumido"); + assert_eq!(balance.used, Some(12.50)); + assert_eq!(balance.unit.as_deref(), Some("USD")); + assert_eq!(p.message.as_deref(), Some("general · 5 h 19% · saldo 12.50 $")); + } + + #[test] + fn distingue_saldo_sin_configurar_de_configurado_y_fallido() { + assert_eq!(parse(&real(), None, false).message.as_deref(), Some("general · 5 h 19%")); + assert_eq!(parse(&real(), None, true).message.as_deref(), Some("general · 5 h 19% · saldo no disponible")); + assert_eq!(parse(&ok(json!({"model_remains": []})), None, true).message.as_deref(), Some("saldo no disponible")); + } + + #[test] + fn satura_un_porcentaje_que_supera_el_rango() { + // Porcentaje restante negativo o > 100: el payload viene sucio. + let sucio = json!({"model_remains": [ + {"model_name": "general", "current_interval_remaining_percent": -20, "current_weekly_remaining_percent": 140} + ], "base_resp": {"status_code": 0}}); + let p = parse(&sucio, None, false); + assert_eq!(p.meters[0].percent, Some(100.0), "restante negativo satura a 100 % consumido"); + assert_eq!(p.meters[1].percent, Some(0.0), "restante > 100 no produce un negativo"); + } +} diff --git a/src-tauri/src/providers/mod.rs b/src-tauri/src/providers/mod.rs index 23ccc4e..1a5db06 100644 --- a/src-tauri/src/providers/mod.rs +++ b/src-tauri/src/providers/mod.rs @@ -8,6 +8,7 @@ pub mod copilot; pub mod cursor; pub mod gemini; pub mod nan; +pub mod minimax; pub mod nan_cloud; pub mod nine_router; pub mod nine_router_creds; @@ -83,6 +84,9 @@ pub async fn fetch_all(cfg: &AppConfig, http: &reqwest::Client) -> Vec Opt "copilot" => copilot::fetch(&cfg.copilot, Who::main("copilot"), http).await, "nan" => nan::fetch(&cfg.nan, Who::main("nan"), http).await, "openrouter" => openrouter::fetch(&cfg.openrouter, Who::main("openrouter"), http).await, + "minimax" => minimax::fetch(&cfg.minimax, Who::main("minimax"), http).await, "nine_router" => nine_router::fetch(&cfg.nine_router, Who::main("nine_router"), http).await, "anthropic_api" => anthropic_api::fetch(&cfg.anthropic_api, Who::main("anthropic_api"), http).await, "openai_api" => openai_api::fetch(&cfg.openai_api, Who::main("openai_api"), http).await, diff --git a/src-tauri/src/secrets.rs b/src-tauri/src/secrets.rs index bf04ffb..7d686b6 100644 --- a/src-tauri/src/secrets.rs +++ b/src-tauri/src/secrets.rs @@ -13,6 +13,11 @@ pub const NAN_API_KEY: &str = "nan_api_key"; pub const NAN_SESSION: &str = "nan_session"; pub const OPENROUTER_API_KEY: &str = "openrouter_api_key"; +/// Subscription Key del plan M/Token de MiniMax (endpoint `token_plan/remains`). +pub const MINIMAX_SUBSCRIPTION_KEY: &str = "minimax_subscription_key"; +/// API key de pago por uso de MiniMax (endpoint `query_balance`). Son credenciales +/// distintas en la consola: esta no da acceso al plan y la anterior no lee el saldo. +pub const MINIMAX_API_KEY: &str = "minimax_api_key"; /// Token fine-grained de GitHub con permiso «Plan» (lectura) para la facturación de Copilot. pub const GITHUB_TOKEN: &str = "github_token"; /// Token de Cursor pegado a mano (si no, se lee de la app). @@ -25,12 +30,14 @@ pub const NINE_ROUTER_SESSION: &str = "nine_router_session"; /// coincide con el destino actual, no se envían (evita credenciales de A en B). pub const NINE_ROUTER_BOUND_URL: &str = "nine_router_bound_url"; -pub const ALL: [&str; 10] = [ +pub const ALL: [&str; 12] = [ ANTHROPIC_ADMIN_KEY, OPENAI_ADMIN_KEY, NAN_API_KEY, NAN_SESSION, OPENROUTER_API_KEY, + MINIMAX_SUBSCRIPTION_KEY, + MINIMAX_API_KEY, GITHUB_TOKEN, CURSOR_TOKEN, NINE_ROUTER_PASSWORD, @@ -44,6 +51,7 @@ pub fn for_provider(provider: &str) -> &'static [&'static str] { "cursor" => &[CURSOR_TOKEN], "copilot" => &[GITHUB_TOKEN], "openrouter" => &[OPENROUTER_API_KEY], + "minimax" => &[MINIMAX_SUBSCRIPTION_KEY, MINIMAX_API_KEY], "anthropic_api" => &[ANTHROPIC_ADMIN_KEY], "openai_api" => &[OPENAI_ADMIN_KEY], "nan" => &[NAN_API_KEY, NAN_SESSION], diff --git a/src/Settings.tsx b/src/Settings.tsx index f124b36..6183689 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -37,6 +37,8 @@ const LINKS = { nanDocs: 'https://nan.builders/docs', githubToken: 'https://github.com/settings/personal-access-tokens/new', openrouterKeys: 'https://openrouter.ai/settings/keys', + minimaxKeys: 'https://www.minimax.io/', + minimaxKeysCn: 'https://www.minimaxi.com/', geminiCli: 'https://github.com/google-gemini/gemini-cli', releases: 'https://github.com/egdev6/quorb/releases', repo: 'https://github.com/egdev6/quorb', @@ -1042,6 +1044,7 @@ const PROVIDER_TABS = [ { id: 'gemini', name: 'Gemini', group: 'sub', desc: 'Cuota diaria por modelo' }, { id: 'cursor', name: 'Cursor', group: 'sub', desc: 'Uso del plan en el ciclo y on-demand' }, { id: 'copilot', name: 'GitHub Copilot', group: 'sub', desc: 'Peticiones premium del mes' }, + { id: 'minimax', name: 'MiniMax', group: 'sub', desc: 'Cuota del plan M/Token y saldo de pago por uso' }, { id: 'nan', name: 'NaN Builders', group: 'api', desc: 'Cuota del plan y tokens por día' }, { id: 'openrouter', name: 'OpenRouter', group: 'api', desc: 'Saldo y gasto por clave' }, { id: 'anthropic_api', name: 'Anthropic API', group: 'api', desc: 'Gasto del mes con clave de admin' }, @@ -1113,6 +1116,9 @@ function mainState(id: ProviderTab, cfg: AppConfig, s: SecretStatus): ProviderSt return on(cfg.nan.enabled, !!s.nan_session || !!s.nan_api_key); case 'openrouter': return on(cfg.openrouter.enabled, !!s.openrouter_api_key); + case 'minimax': + // Solo la Subscription Key es obligatoria; el saldo PAYG es un extra opcional. + return on(cfg.minimax.enabled, !!s.minimax_subscription_key); case 'anthropic_api': return on(cfg.anthropicApi.enabled, !!s.anthropic_admin_key); case 'openai_api': @@ -1938,6 +1944,52 @@ export function Settings() { )} + {tab === 'minimax' && ( +
+ setVisibility('minimax', v)} /> +

+ Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son + dos credenciales distintas en la consola: la primera da acceso al plan y la segunda al saldo. +

+
    +
  1. + Subscription Key del plan + Del panel M/Token en minimax.io. En la región de China, en minimaxi.com (el host por región se cambia en Ajustes avanzados).} + /> +
  2. +
  3. + API key de pago por uso (opcional) + Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade.} + /> +
  4. + +
+
+ Avanzado · URL del API + + set('minimax', { baseUrl: e.target.value })} + /> + +
+
+ )} {tab === 'openrouter' && (
setVisibility('openrouter', v)} /> diff --git a/src/assets/logos/minimax.svg b/src/assets/logos/minimax.svg new file mode 100644 index 0000000..97345b8 --- /dev/null +++ b/src/assets/logos/minimax.svg @@ -0,0 +1,9 @@ + + + diff --git a/src/components/ProviderLogo.tsx b/src/components/ProviderLogo.tsx index 8569bdf..27e2f01 100644 --- a/src/components/ProviderLogo.tsx +++ b/src/components/ProviderLogo.tsx @@ -55,6 +55,8 @@ const ALIASES: Record = { cursor: ['cursor'], copilot: ['copilot', 'github-copilot', 'microsoft-copilot', 'github'], openrouter: ['openrouter'], + // `minimax-cn` es el nombre del nodo de 9Router para la región de China. + minimax: ['minimax', 'minimax-cn', 'minimax_cn', 'minimaxi'], nine_router: ['nine_router', '9router'], // Nombres de nodos personalizados de 9Router → logo del proveedor real. zhipu: ['zhipu', 'z-ai', 'glm', 'glm-cn'], @@ -107,6 +109,7 @@ const MONOGRAM: Record = { cursor: { text: 'Cu' }, copilot: { text: 'Co' }, openrouter: { text: 'OR' }, + minimax: { text: 'MM' }, nine_router: { text: '9R' }, }; diff --git a/src/lib/settings-visibility.ts b/src/lib/settings-visibility.ts index d1ea83e..c6924a7 100644 --- a/src/lib/settings-visibility.ts +++ b/src/lib/settings-visibility.ts @@ -1,12 +1,12 @@ import type { AppConfig } from './types'; import { isNineRouterId } from './visibility'; -type MainKey = 'claude' | 'codex' | 'gemini' | 'cursor' | 'copilot' | 'nan' | 'openrouter' | 'anthropicApi' | 'openaiApi'; +type MainKey = 'claude' | 'codex' | 'gemini' | 'cursor' | 'copilot' | 'nan' | 'openrouter' | 'minimax' | 'anthropicApi' | 'openaiApi'; /** Clave de configuración de cada cuenta principal; el id del informe puede ser distinto. */ export const CONFIG_KEY: Readonly> = { claude: 'claude', codex: 'codex', gemini: 'gemini', cursor: 'cursor', - copilot: 'copilot', nan: 'nan', openrouter: 'openrouter', + copilot: 'copilot', nan: 'nan', openrouter: 'openrouter', minimax: 'minimax', anthropic_api: 'anthropicApi', openai_api: 'openaiApi', }; diff --git a/src/lib/types.ts b/src/lib/types.ts index a913414..0959b87 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -55,6 +55,12 @@ export interface ApiCostConfig { monthlyBudgetUsd: number | null; } +/** MiniMax. `baseUrl` cubre las dos regiones: global `api.minimax.io` y China `api.minimaxi.com`. */ +export interface MiniMaxConfig { + enabled: boolean; + baseUrl: string; +} + export interface NanConfig { enabled: boolean; baseUrl: string; @@ -154,6 +160,7 @@ export interface AppConfig { anthropicApi: ApiCostConfig; openaiApi: ApiCostConfig; nan: NanConfig; + minimax: MiniMaxConfig; nineRouter: NineRouterConfig; /** Cuentas adicionales de un proveedor (la principal es la de su campo) */ accounts: Account[]; @@ -195,6 +202,8 @@ export type SecretKey = | 'nan_api_key' | 'nan_session' | 'openrouter_api_key' + | 'minimax_subscription_key' + | 'minimax_api_key' | 'github_token' | 'cursor_token' | 'nine_router_password' diff --git a/src/lib/widget-refresh.test.ts b/src/lib/widget-refresh.test.ts index 8bfab07..fbbff98 100644 --- a/src/lib/widget-refresh.test.ts +++ b/src/lib/widget-refresh.test.ts @@ -13,6 +13,7 @@ function cfg(over: Partial = {}): AppConfig { copilot: { enabled: true }, openrouter: { enabled: true }, nan: { enabled: true }, + minimax: { enabled: false }, anthropicApi: { enabled: false }, openaiApi: { enabled: false }, nineRouter: { enabled: true, baseUrl: 'http://localhost:20128', period: '7d', showSubProviders: true, minSpendUsd: 0, monthlyBudgetUsd: null }, diff --git a/src/lib/widget-refresh.ts b/src/lib/widget-refresh.ts index f0ff467..ce59320 100644 --- a/src/lib/widget-refresh.ts +++ b/src/lib/widget-refresh.ts @@ -8,7 +8,7 @@ import type { AppConfig } from './types'; */ export function dataKey(c: AppConfig | null): string { return c - ? JSON.stringify([c.claude, c.codex, c.gemini, c.cursor, c.copilot, c.openrouter, c.anthropicApi, c.openaiApi, c.nan, c.nineRouter]) + ? JSON.stringify([c.claude, c.codex, c.gemini, c.cursor, c.copilot, c.openrouter, c.anthropicApi, c.openaiApi, c.nan, c.minimax, c.nineRouter]) : ''; }