From 4c69aa6e09c95e2d4bf4bae998fc5c38983478f4 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:24:33 -0400 Subject: [PATCH] Suggest OpenRouter free models when credits run out (#3320) --- app/src/components/home/HomeBanners.tsx | 15 +++ app/src/lib/i18n/ar.ts | 4 + app/src/lib/i18n/bn.ts | 4 + app/src/lib/i18n/de.ts | 4 + app/src/lib/i18n/en.ts | 4 + app/src/lib/i18n/es.ts | 4 + app/src/lib/i18n/fr.ts | 4 + app/src/lib/i18n/hi.ts | 4 + app/src/lib/i18n/id.ts | 4 + app/src/lib/i18n/it.ts | 4 + app/src/lib/i18n/ko.ts | 4 + app/src/lib/i18n/pl.ts | 4 + app/src/lib/i18n/pt.ts | 4 + app/src/lib/i18n/ru.ts | 4 + app/src/lib/i18n/zh-CN.ts | 3 + app/src/pages/Conversations.tsx | 54 ++++++-- app/src/pages/Home.tsx | 27 ++++ .../__tests__/Conversations.render.test.tsx | 37 ++++++ app/src/pages/__tests__/Home.test.tsx | 20 +++ .../__tests__/openrouterFreeModels.test.ts | 118 ++++++++++++++++++ app/src/services/api/openrouterFreeModels.ts | 60 +++++++++ .../inference/provider/compatible.rs | 16 +++ .../inference/provider/compatible_request.rs | 33 ++++- .../inference/provider/compatible_tests.rs | 54 ++++++++ src/openhuman/integrations/client.rs | 44 ++++++- src/openhuman/integrations/client_tests.rs | 14 +++ src/openhuman/team/ops.rs | 70 +++++++++++ 27 files changed, 604 insertions(+), 13 deletions(-) create mode 100644 app/src/services/api/__tests__/openrouterFreeModels.test.ts create mode 100644 app/src/services/api/openrouterFreeModels.ts diff --git a/app/src/components/home/HomeBanners.tsx b/app/src/components/home/HomeBanners.tsx index 02cd23234..584b7093d 100644 --- a/app/src/components/home/HomeBanners.tsx +++ b/app/src/components/home/HomeBanners.tsx @@ -12,12 +12,16 @@ export function UsageLimitBanner({ title, message, ctaLabel, + secondaryCtaLabel, + onSecondaryCtaClick, }: { tone: 'warning' | 'danger'; icon: string; title: string; message: string; ctaLabel: string; + secondaryCtaLabel?: string; + onSecondaryCtaClick?: () => void; }) { const styles = tone === 'danger' @@ -54,6 +58,17 @@ export function UsageLimitBanner({ className={`cursor-pointer border-b border-dashed font-bold ${styles.button}`}> {ctaLabel} + {secondaryCtaLabel && onSecondaryCtaClick && ( + <> + {' '} + + > + )}
diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index a72de77a9..31bf9a290 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -199,6 +199,10 @@ const messages: TranslationMap = { 'home.usageExhaustedTitle': 'لقد استنفدت حصتك', 'home.usageExhaustedBody': 'لقد نفدت حصتك المضمنة حالياً. ابدأ اشتراكاً لفتح سعة إضافية مستمرة.', 'home.usageExhaustedCta': 'ابدأ الاشتراك', + 'openrouterFree.cta': 'استخدم نماذج OpenRouter المجانية', + 'openrouterFree.saving': 'جارٍ الاتصال بـ OpenRouter...', + 'openrouterFree.error': + 'تعذر التبديل إلى نماذج OpenRouter المجانية. تحقق من تسجيل الدخول إلى OpenRouter وحاول مرة أخرى.', 'home.routinesCard': 'روتيناتك', 'home.routinesActive': '{count} نشطة', 'routines.title': 'روتيناتك', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 4ed95143d..e7f95a789 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -201,6 +201,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'আপাতত আপনার অন্তর্ভুক্ত ব্যবহার শেষ। আরও ধারাবাহিক সক্ষমতা আনলক করতে একটি সাবস্ক্রিপশন শুরু করুন।', 'home.usageExhaustedCta': 'সাবস্ক্রিপশন শুরু করুন', + 'openrouterFree.cta': 'OpenRouter-এর বিনামূল্যের মডেল ব্যবহার করুন', + 'openrouterFree.saving': 'OpenRouter সংযুক্ত হচ্ছে...', + 'openrouterFree.error': + 'OpenRouter-এর বিনামূল্যের মডেলে স্যুইচ করা যায়নি। আপনার OpenRouter সাইন-ইন পরীক্ষা করে আবার চেষ্টা করুন।', 'home.routinesCard': 'আপনার রুটিন', 'home.routinesActive': '{count}টি সক্রিয়', 'routines.title': 'তোমার বিদ্বান', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 54a95a0aa..17e3007e1 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -210,6 +210,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Dein enthaltenes Nutzungskontingent ist vorerst aufgebraucht. Starte ein Abo, um mehr laufende Kapazität freizuschalten.', 'home.usageExhaustedCta': 'Abo starten', + 'openrouterFree.cta': 'Kostenlose OpenRouter-Modelle verwenden', + 'openrouterFree.saving': 'OpenRouter wird verbunden...', + 'openrouterFree.error': + 'Der Wechsel zu kostenlosen OpenRouter-Modellen ist fehlgeschlagen. Prüfe deine OpenRouter-Anmeldung und versuche es erneut.', 'home.routinesCard': 'Deine Routinen', 'home.routinesActive': '{count} aktiv', 'routines.title': 'Ihre Routinen', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index a81f3330d..d94d5aef1 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -220,6 +220,10 @@ const en: TranslationMap = { 'home.usageExhaustedBody': "You're out of included usage for now. Start a subscription to unlock more ongoing capacity.", 'home.usageExhaustedCta': 'Get a subscription', + 'openrouterFree.cta': 'Use OpenRouter free models', + 'openrouterFree.saving': 'Connecting OpenRouter...', + 'openrouterFree.error': + 'Could not switch to OpenRouter free models. Check your OpenRouter sign-in and try again.', 'home.routinesCard': 'Your Routines', 'home.routinesActive': '{count} active', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index 0c25df57d..d95078847 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -211,6 +211,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Ya no te queda uso incluido por ahora. Inicia una suscripción para desbloquear más capacidad continua.', 'home.usageExhaustedCta': 'Obtener una suscripción', + 'openrouterFree.cta': 'Usar modelos gratis de OpenRouter', + 'openrouterFree.saving': 'Conectando OpenRouter...', + 'openrouterFree.error': + 'No se pudo cambiar a los modelos gratis de OpenRouter. Revisa tu inicio de sesión de OpenRouter e inténtalo de nuevo.', 'home.routinesCard': 'Tus rutinas', 'home.routinesActive': '{count} activas', 'routines.title': 'Tus rutinas', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index 30144596b..15f48ad92 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -209,6 +209,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Vous n’avez plus d’utilisation incluse pour le moment. Démarrez un abonnement pour débloquer davantage de capacité continue.', 'home.usageExhaustedCta': 'Prendre un abonnement', + 'openrouterFree.cta': 'Utiliser les modèles gratuits OpenRouter', + 'openrouterFree.saving': 'Connexion à OpenRouter...', + 'openrouterFree.error': + 'Impossible de passer aux modèles gratuits OpenRouter. Vérifiez votre connexion OpenRouter et réessayez.', 'home.routinesCard': 'Vos routines', 'home.routinesActive': '{count} actif(s)', 'routines.title': 'Vos Routines', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index c4eeba170..24a075758 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -201,6 +201,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'आपकी शामिल उपयोग सीमा अभी समाप्त हो चुकी है। अधिक निरंतर क्षमता अनलॉक करने के लिए सदस्यता शुरू करें।', 'home.usageExhaustedCta': 'सदस्यता शुरू करें', + 'openrouterFree.cta': 'OpenRouter के मुफ्त मॉडल इस्तेमाल करें', + 'openrouterFree.saving': 'OpenRouter कनेक्ट हो रहा है...', + 'openrouterFree.error': + 'OpenRouter के मुफ्त मॉडल पर स्विच नहीं हो सका। अपना OpenRouter साइन-इन जांचें और फिर कोशिश करें।', 'home.routinesCard': 'आपकी रूटीन', 'home.routinesActive': '{count} सक्रिय', 'routines.title': 'आपका दिनचर्या', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 63b41a8eb..6eff6e3c6 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -203,6 +203,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Kuota penggunaan yang termasuk saat ini sudah habis. Mulai langganan untuk membuka kapasitas berkelanjutan yang lebih besar.', 'home.usageExhaustedCta': 'Mulai berlangganan', + 'openrouterFree.cta': 'Gunakan model gratis OpenRouter', + 'openrouterFree.saving': 'Menghubungkan OpenRouter...', + 'openrouterFree.error': + 'Tidak dapat beralih ke model gratis OpenRouter. Periksa login OpenRouter Anda dan coba lagi.', 'home.routinesCard': 'Rutinitas Anda', 'home.routinesActive': '{count} aktif', 'routines.title': 'Rutinitas Anda', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 6c2a57d30..adb62c3f2 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -207,6 +207,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Hai esaurito per ora l’utilizzo incluso. Avvia un abbonamento per sbloccare più capacità continuativa.', 'home.usageExhaustedCta': 'Attiva un abbonamento', + 'openrouterFree.cta': 'Usa i modelli gratuiti di OpenRouter', + 'openrouterFree.saving': 'Connessione a OpenRouter...', + 'openrouterFree.error': + 'Impossibile passare ai modelli gratuiti di OpenRouter. Controlla l’accesso a OpenRouter e riprova.', 'home.routinesCard': 'Le tue routine', 'home.routinesActive': '{count} attive', 'routines.title': 'Le tue routine', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 19f14a498..5ab29ea98 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -201,6 +201,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': '현재 포함된 사용량을 모두 소진했습니다. 더 많은 지속 용량을 사용하려면 구독을 시작하세요.', 'home.usageExhaustedCta': '구독 시작', + 'openrouterFree.cta': 'OpenRouter 무료 모델 사용', + 'openrouterFree.saving': 'OpenRouter 연결 중...', + 'openrouterFree.error': + 'OpenRouter 무료 모델로 전환하지 못했습니다. OpenRouter 로그인을 확인하고 다시 시도하세요.', 'home.routinesCard': '내 루틴', 'home.routinesActive': '{count}개 활성', 'routines.title': '내 루틴', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index 5628f9800..f7102666e 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -205,6 +205,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Twój limit użycia na ten moment został wyczerpany. Wykup subskrypcję, aby odblokować większą ciągłą pojemność.', 'home.usageExhaustedCta': 'Wykup subskrypcję', + 'openrouterFree.cta': 'Użyj darmowych modeli OpenRouter', + 'openrouterFree.saving': 'Łączenie z OpenRouter...', + 'openrouterFree.error': + 'Nie udało się przełączyć na darmowe modele OpenRouter. Sprawdź logowanie do OpenRouter i spróbuj ponownie.', 'home.routinesCard': 'Twoje Rutyny', 'home.routinesActive': '{count} aktywnych', 'routines.title': 'Twoje rutyny', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index 3a75512c7..d00f95440 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -209,6 +209,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Seu uso incluído acabou por enquanto. Inicie uma assinatura para desbloquear mais capacidade contínua.', 'home.usageExhaustedCta': 'Assinar', + 'openrouterFree.cta': 'Usar modelos grátis da OpenRouter', + 'openrouterFree.saving': 'Conectando à OpenRouter...', + 'openrouterFree.error': + 'Não foi possível mudar para os modelos grátis da OpenRouter. Verifique seu login da OpenRouter e tente novamente.', 'home.routinesCard': 'Suas Rotinas', 'home.routinesActive': '{count} ativas', 'routines.title': 'Seus Hábitos', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index ca4c06e59..d497fde9c 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -203,6 +203,10 @@ const messages: TranslationMap = { 'home.usageExhaustedBody': 'Включённый объём использования пока исчерпан. Оформите подписку, чтобы получить больше постоянной мощности.', 'home.usageExhaustedCta': 'Оформить подписку', + 'openrouterFree.cta': 'Использовать бесплатные модели OpenRouter', + 'openrouterFree.saving': 'Подключение OpenRouter...', + 'openrouterFree.error': + 'Не удалось переключиться на бесплатные модели OpenRouter. Проверьте вход в OpenRouter и повторите попытку.', 'home.routinesCard': 'Ваши подпрограммы', 'home.routinesActive': '{count} активных', 'routines.title': 'Ваши рутины', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 65d202f23..a45e92520 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -192,6 +192,9 @@ const messages: TranslationMap = { 'home.usageExhaustedTitle': '您的使用额度已耗尽', 'home.usageExhaustedBody': '您当前已用完包含的使用额度。开始订阅以解锁更多持续容量。', 'home.usageExhaustedCta': '开始订阅', + 'openrouterFree.cta': '使用 OpenRouter 免费模型', + 'openrouterFree.saving': '正在连接 OpenRouter...', + 'openrouterFree.error': '无法切换到 OpenRouter 免费模型。请检查 OpenRouter 登录后重试。', 'home.routinesCard': '我的例程', 'home.routinesActive': '{count} 个运行中', 'routines.title': '你的例程', diff --git a/app/src/pages/Conversations.tsx b/app/src/pages/Conversations.tsx index 2fbf528c6..702fbe943 100644 --- a/app/src/pages/Conversations.tsx +++ b/app/src/pages/Conversations.tsx @@ -28,6 +28,7 @@ import { } from '../lib/attachments'; import { useT } from '../lib/i18n/I18nContext'; import { trackEvent } from '../services/analytics'; +import { applyOpenRouterFreeModels } from '../services/api/openrouterFreeModels'; import { threadApi } from '../services/api/threadApi'; import { chatCancel, chatSend, useRustChat } from '../services/chatService'; import { store } from '../store'; @@ -222,6 +223,7 @@ const Conversations = ({ const [sendError, setSendError] = useState+
{teamUsage.cycleBudgetUsd > 0 ? `${t('chat.weeklyLimitHit')}${teamUsage.cycleEndsAt ? ` ${t('chat.resets')} ${formatResetTime(teamUsage.cycleEndsAt)}.` : ''} ${t('chat.topUpToContinue')}` : t('chat.budgetComplete')}