diff --git a/app/src/components/meetings/ActiveMeetingBanner.tsx b/app/src/components/meetings/ActiveMeetingBanner.tsx index 3376e66db..16dce29f0 100644 --- a/app/src/components/meetings/ActiveMeetingBanner.tsx +++ b/app/src/components/meetings/ActiveMeetingBanner.tsx @@ -6,7 +6,7 @@ * each component within the repo's ~500-line guideline. Behavior is identical * to the original; it just lives in its own file now. */ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { type MascotFace, RiveMascot } from '../../features/human/Mascot'; import { useT } from '../../lib/i18n/I18nContext'; @@ -24,9 +24,20 @@ import { } from '../../store/backendMeetSlice'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import Button from '../ui/Button'; +import { Spinner } from '../ui/icons'; type Toast = { type: 'success' | 'error' | 'info'; title: string; message?: string }; +/** + * Safety net for the pending "Leaving…" state. The leave RPC only confirms the + * `bot:leave` event was enqueued on the socket — it does not wait for the bot to + * actually leave. On the happy path `status` flips to ended/error (which unmounts + * this banner) well within this window. If no such transition ever arrives (e.g. + * the socket drops or the bot fails to emit its completion event), we re-enable + * the button after this timeout so the user can retry instead of being stuck. + */ +export const LEAVE_SAFETY_TIMEOUT_MS = 10_000; + export interface ActiveMeetingBannerProps { onToast?: (toast: Toast) => void; } @@ -74,20 +85,38 @@ export function ActiveMeetingBanner({ onToast }: ActiveMeetingBannerProps) { }, [meetUrl]); const [leaving, setLeaving] = useState(false); + const leaveTimerRef = useRef | null>(null); + + // Clear the safety timer on unmount (the happy path: the banner unmounts when + // `status` flips to ended/error). No setState here — just timer cleanup. + useEffect(() => { + return () => { + if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current); + }; + }, []); const handleLeave = async () => { if (leaving) return; setLeaving(true); try { await leaveBackendMeetBot('user-requested'); + // Stay in the pending "Leaving…" state on success: when the bot actually + // leaves, `status` flips to ended/error and this banner unmounts (the + // parent only renders it while joining/active), so the flag never lingers. + // The RPC only confirms the leave was enqueued, not that the bot left, so + // arm a safety timeout that re-enables the button if no transition arrives. + if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current); + leaveTimerRef.current = setTimeout(() => { + leaveTimerRef.current = null; + setLeaving(false); + }, LEAVE_SAFETY_TIMEOUT_MS); } catch (err) { + setLeaving(false); onToast?.({ type: 'error', - title: t('skills.meetingBots.couldNotStartTitle'), + title: t('skills.meetingBots.couldNotLeaveTitle'), message: String(err), }); - } finally { - setLeaving(false); } }; @@ -118,8 +147,13 @@ export function ActiveMeetingBanner({ onToast }: ActiveMeetingBannerProps) { {t('skills.meetingBots.liveBadge')} {canLeave && ( - )} {isDone && ( diff --git a/app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx b/app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx index 39fea64b3..70ee918cd 100644 --- a/app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx +++ b/app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx @@ -1,9 +1,9 @@ -import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react'; +import { act, cleanup, fireEvent, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { setBackendMeetJoined, setBackendMeetLeft } from '../../../store/backendMeetSlice'; import { renderWithProviders } from '../../../test/test-utils'; -import { ActiveMeetingBanner } from '../ActiveMeetingBanner'; +import { ActiveMeetingBanner, LEAVE_SAFETY_TIMEOUT_MS } from '../ActiveMeetingBanner'; const leaveMock = vi.fn(); @@ -81,6 +81,86 @@ describe('ActiveMeetingBanner', () => { }); }); + it('shows a pending "Leaving…" state while the leave request is in flight', async () => { + // Hold the leave request open so the pending state stays visible. + let resolveLeave: () => void = () => {}; + leaveMock.mockImplementationOnce( + () => + new Promise(resolve => { + resolveLeave = resolve; + }) + ); + renderWithProviders(, { preloadedState: activeState }); + + const leaveBtn = screen.getByRole('button', { name: /leave/i }); + fireEvent.click(leaveBtn); + + // While in flight the label switches to "Leaving…" and the button is disabled. + await waitFor(() => { + expect(screen.getByRole('button', { name: /leaving/i })).toBeDisabled(); + }); + expect(screen.queryByRole('button', { name: /^leave$/i })).not.toBeInTheDocument(); + + resolveLeave(); + }); + + it('keeps the pending state after the request resolves until status changes', async () => { + leaveMock.mockResolvedValueOnce(undefined); + const { store } = renderWithProviders(, { preloadedState: activeState }); + + fireEvent.click(screen.getByRole('button', { name: /leave/i })); + + // Even after the request resolves, the pending state holds until the bot leaves. + await waitFor(() => { + expect(screen.getByRole('button', { name: /leaving/i })).toBeInTheDocument(); + }); + + // Once the meeting ends, the pending Leave button is replaced by Close. + store.dispatch(setBackendMeetLeft({ reason: 'done' })); + await waitFor(() => { + expect(screen.queryByRole('button', { name: /leaving/i })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument(); + }); + }); + + it('re-enables the Leave button via safety timeout if no left event arrives', async () => { + vi.useFakeTimers(); + try { + leaveMock.mockResolvedValueOnce(undefined); + renderWithProviders(, { preloadedState: activeState }); + + fireEvent.click(screen.getByRole('button', { name: /leave/i })); + // Flush the resolved leave promise → pending "Leaving…" + safety timer armed. + await act(async () => { + await vi.advanceTimersByTimeAsync(0); + }); + expect(screen.getByRole('button', { name: /leaving/i })).toBeDisabled(); + + // No status transition arrives; once the safety timeout elapses the button + // returns to an enabled "Leave" so the user can retry. + await act(async () => { + await vi.advanceTimersByTimeAsync(LEAVE_SAFETY_TIMEOUT_MS + 100); + }); + expect(screen.getByRole('button', { name: /^leave$/i })).toBeEnabled(); + } finally { + vi.useRealTimers(); + } + }); + + it('clears the pending state and toasts when leave fails', async () => { + leaveMock.mockRejectedValueOnce(new Error('Network error')); + const onToast = vi.fn(); + renderWithProviders(, { preloadedState: activeState }); + + fireEvent.click(screen.getByRole('button', { name: /leave/i })); + + await waitFor(() => { + expect(onToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })); + }); + // Button returns to the enabled "Leave" state so the user can retry. + expect(screen.getByRole('button', { name: /^leave$/i })).toBeEnabled(); + }); + it('renders ended state with Close button', () => { const { store } = renderWithProviders(, { preloadedState: activeState }); diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 24e67e268..06ceab1c0 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -5030,6 +5030,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'عنوان اللافتة', 'skills.meetingBots.busyTitle': 'OpenHuman مشغول', 'skills.meetingBots.comingSoon': 'قريبًا', + 'skills.meetingBots.couldNotLeaveTitle': 'تعذّر مغادرة الاجتماع', 'skills.meetingBots.couldNotStartTitle': 'تعذّر بدء OpenHuman', 'skills.meetingBots.displayName': 'الاسم المعروض', 'skills.meetingBots.failedToStart': 'فشل تشغيل OpenHuman.', @@ -5079,6 +5080,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'انتهى الاجتماع', 'skills.meetingBots.liveStatusError': 'فشل الانضمام', 'skills.meetingBots.leaveButton': 'مغادرة', + 'skills.meetingBots.leavingButton': 'جارٍ المغادرة…', 'skills.meetingBots.respondToParticipant': 'اسمك في هذا الاجتماع', 'skills.meetingBots.respondToParticipantHint': 'مثال: أحمد (اسمك في المكالمة)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 03277459b..8be5522de 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -5134,6 +5134,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'ব্যানার শিরোনাম', 'skills.meetingBots.busyTitle': 'OpenHuman ব্যস্ত', 'skills.meetingBots.comingSoon': 'শীঘ্রই আসছে', + 'skills.meetingBots.couldNotLeaveTitle': 'মিটিং থেকে বেরোনো যায়নি', 'skills.meetingBots.couldNotStartTitle': 'OpenHuman শুরু করা যায়নি', 'skills.meetingBots.displayName': 'প্রদর্শন নাম', 'skills.meetingBots.failedToStart': 'OpenHuman শুরু করতে ব্যর্থ।', @@ -5184,6 +5185,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'মিটিং শেষ', 'skills.meetingBots.liveStatusError': 'যোগ দিতে ব্যর্থ', 'skills.meetingBots.leaveButton': 'ছেড়ে দিন', + 'skills.meetingBots.leavingButton': 'বেরিয়ে যাচ্ছে…', 'skills.meetingBots.respondToParticipant': 'এই মিটিংয়ে আপনার নাম', 'skills.meetingBots.respondToParticipantHint': 'যেমন: রিয়া (কলে আপনার প্রদর্শনী নাম)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 51ff08e1f..93bec2b2d 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -5265,6 +5265,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Sende OpenHuman an eine Besprechung', 'skills.meetingBots.busyTitle': 'OpenHuman ist beschäftigt', 'skills.meetingBots.comingSoon': 'Kommt bald', + 'skills.meetingBots.couldNotLeaveTitle': 'Meeting konnte nicht verlassen werden', 'skills.meetingBots.couldNotStartTitle': 'OpenHuman konnte nicht gestartet werden', 'skills.meetingBots.displayName': 'Anzeigename', 'skills.meetingBots.failedToStart': 'OpenHuman konnte nicht gestartet werden.', @@ -5317,6 +5318,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Meeting beendet', 'skills.meetingBots.liveStatusError': 'Beitritt fehlgeschlagen', 'skills.meetingBots.leaveButton': 'Verlassen', + 'skills.meetingBots.leavingButton': 'Wird verlassen…', 'skills.meetingBots.respondToParticipant': 'Ihr Name in diesem Meeting', 'skills.meetingBots.respondToParticipantHint': 'z. B. Max (Ihr Anzeigename im Anruf)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index 147cbb3ad..6540a1d9a 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -5780,6 +5780,7 @@ const en: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Send OpenHuman to your next meeting', 'skills.meetingBots.busyTitle': 'OpenHuman is busy', 'skills.meetingBots.comingSoon': 'coming soon', + 'skills.meetingBots.couldNotLeaveTitle': 'Couldn’t leave the meeting', 'skills.meetingBots.couldNotStartTitle': 'Could not start OpenHuman', 'skills.meetingBots.displayName': 'Display name', 'skills.meetingBots.failedToStart': 'Failed to start OpenHuman.', @@ -5831,6 +5832,7 @@ const en: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Meeting ended', 'skills.meetingBots.liveStatusError': 'Failed to join', 'skills.meetingBots.leaveButton': 'Leave', + 'skills.meetingBots.leavingButton': 'Leaving…', 'skills.meetingBots.respondToParticipant': 'Your Name in This Meeting', 'skills.meetingBots.respondToParticipantHint': 'e.g. Alice (your display name in the call)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index a26120761..717f3c68e 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -5230,6 +5230,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Título del banner', 'skills.meetingBots.busyTitle': 'OpenHuman está ocupado', 'skills.meetingBots.comingSoon': 'Próximamente', + 'skills.meetingBots.couldNotLeaveTitle': 'No se pudo salir de la reunión', 'skills.meetingBots.couldNotStartTitle': 'No se pudo iniciar OpenHuman', 'skills.meetingBots.displayName': 'Nombre de visualización', 'skills.meetingBots.failedToStart': 'No se pudo iniciar OpenHuman.', @@ -5281,6 +5282,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Reunión finalizada', 'skills.meetingBots.liveStatusError': 'Error al unirse', 'skills.meetingBots.leaveButton': 'Salir', + 'skills.meetingBots.leavingButton': 'Saliendo…', 'skills.meetingBots.respondToParticipant': 'Tu nombre en esta reunión', 'skills.meetingBots.respondToParticipantHint': 'p. ej. Ana (tu nombre visible en la llamada)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index 00578d3ac..9f052acfa 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -5250,6 +5250,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Titre de la bannière', 'skills.meetingBots.busyTitle': 'OpenHuman est occupé', 'skills.meetingBots.comingSoon': 'Bientôt disponible', + 'skills.meetingBots.couldNotLeaveTitle': 'Impossible de quitter la réunion', 'skills.meetingBots.couldNotStartTitle': 'Impossible de démarrer OpenHuman', 'skills.meetingBots.displayName': "Nom d'affichage", 'skills.meetingBots.failedToStart': "Échec du démarrage d'OpenHuman.", @@ -5302,6 +5303,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Réunion terminée', 'skills.meetingBots.liveStatusError': 'Échec de connexion', 'skills.meetingBots.leaveButton': 'Quitter', + 'skills.meetingBots.leavingButton': 'Sortie en cours…', 'skills.meetingBots.respondToParticipant': 'Votre nom dans cette réunion', 'skills.meetingBots.respondToParticipantHint': 'ex. Alice (votre nom affiché dans l\u2019appel)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 7b1591878..2b3da261b 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -5137,6 +5137,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'बैनर शीर्षक', 'skills.meetingBots.busyTitle': 'OpenHuman व्यस्त है', 'skills.meetingBots.comingSoon': 'जल्द आ रहा है', + 'skills.meetingBots.couldNotLeaveTitle': 'मीटिंग से नहीं निकल सके', 'skills.meetingBots.couldNotStartTitle': 'OpenHuman प्रारंभ नहीं हो सका', 'skills.meetingBots.displayName': 'डिस्प्ले नाम', 'skills.meetingBots.failedToStart': 'OpenHuman शुरू नहीं हो पाया।', @@ -5189,6 +5190,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'मीटिंग समाप्त', 'skills.meetingBots.liveStatusError': 'शामिल होने में विफल', 'skills.meetingBots.leaveButton': 'छोड़ें', + 'skills.meetingBots.leavingButton': 'छोड़ रहे हैं…', 'skills.meetingBots.respondToParticipant': 'इस मीटिंग में आपका नाम', 'skills.meetingBots.respondToParticipantHint': 'जैसे: अनीता (कॉल में आपका प्रदर्शन नाम)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 0e216205a..a2e422605 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -5152,6 +5152,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Judul banner', 'skills.meetingBots.busyTitle': 'OpenHuman sedang sibuk', 'skills.meetingBots.comingSoon': 'Segera hadir', + 'skills.meetingBots.couldNotLeaveTitle': 'Tidak dapat keluar dari rapat', 'skills.meetingBots.couldNotStartTitle': 'Tidak bisa memulai OpenHuman', 'skills.meetingBots.displayName': 'Nama tampilan', 'skills.meetingBots.failedToStart': 'Gagal memulai OpenHuman.', @@ -5203,6 +5204,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Rapat selesai', 'skills.meetingBots.liveStatusError': 'Gagal bergabung', 'skills.meetingBots.leaveButton': 'Keluar', + 'skills.meetingBots.leavingButton': 'Keluar\u2026', 'skills.meetingBots.respondToParticipant': 'Nama Anda di Rapat Ini', 'skills.meetingBots.respondToParticipantHint': 'mis. Budi (nama tampilan Anda di panggilan)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index cf9836788..68c3f740f 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -5219,6 +5219,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Titolo banner', 'skills.meetingBots.busyTitle': 'OpenHuman è occupato', 'skills.meetingBots.comingSoon': 'In arrivo', + 'skills.meetingBots.couldNotLeaveTitle': 'Impossibile uscire dalla riunione', 'skills.meetingBots.couldNotStartTitle': 'Impossibile avviare OpenHuman', 'skills.meetingBots.displayName': 'Nome visualizzato', 'skills.meetingBots.failedToStart': 'Avvio di OpenHuman fallito.', @@ -5270,6 +5271,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Riunione terminata', 'skills.meetingBots.liveStatusError': 'Partecipazione fallita', 'skills.meetingBots.leaveButton': 'Esci', + 'skills.meetingBots.leavingButton': 'Uscita in corso…', 'skills.meetingBots.respondToParticipant': 'Il tuo nome in questa riunione', 'skills.meetingBots.respondToParticipantHint': 'es. Mario (il tuo nome visualizzato nella chiamata)', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 2d3e16b3c..c7f536313 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -5085,6 +5085,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'OpenHuman을 회의에 보내기', 'skills.meetingBots.busyTitle': 'OpenHuman이 바쁩니다', 'skills.meetingBots.comingSoon': '곧 제공 예정', + 'skills.meetingBots.couldNotLeaveTitle': '회의에서 나갈 수 없습니다', 'skills.meetingBots.couldNotStartTitle': 'OpenHuman을 시작할 수 없습니다', 'skills.meetingBots.displayName': '표시 이름', 'skills.meetingBots.failedToStart': 'OpenHuman 시작에 실패했습니다.', @@ -5135,6 +5136,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': '회의 종료', 'skills.meetingBots.liveStatusError': '참가 실패', 'skills.meetingBots.leaveButton': '나가기', + 'skills.meetingBots.leavingButton': '나가는 중…', 'skills.meetingBots.respondToParticipant': '이 회의에서 내 이름', 'skills.meetingBots.respondToParticipantHint': '예: 김철수 (통화에서 표시되는 이름)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index d5ad2c662..28dae69fc 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -5205,6 +5205,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Wyślij OpenHuman na spotkanie', 'skills.meetingBots.busyTitle': 'OpenHuman jest zajęty', 'skills.meetingBots.comingSoon': 'Wkrótce', + 'skills.meetingBots.couldNotLeaveTitle': 'Nie można opuścić spotkania', 'skills.meetingBots.couldNotStartTitle': 'Nie udało się uruchomić OpenHuman', 'skills.meetingBots.displayName': 'Wyświetlana nazwa', 'skills.meetingBots.failedToStart': 'Nie udało się uruchomić OpenHuman.', @@ -5257,6 +5258,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Spotkanie zakończone', 'skills.meetingBots.liveStatusError': 'Nie można dołączyć', 'skills.meetingBots.leaveButton': 'Wyjdź', + 'skills.meetingBots.leavingButton': 'Opuszczanie…', 'skills.meetingBots.respondToParticipant': 'Twoje imię na tym spotkaniu', 'skills.meetingBots.respondToParticipantHint': 'np. Anna (Twoja nazwa wyświetlana podczas rozmowy)', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index e51811bde..b15db6e20 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -5222,6 +5222,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Título do banner', 'skills.meetingBots.busyTitle': 'OpenHuman está ocupado', 'skills.meetingBots.comingSoon': 'Em breve', + 'skills.meetingBots.couldNotLeaveTitle': 'Não foi possível sair da reunião', 'skills.meetingBots.couldNotStartTitle': 'Não foi possível iniciar o OpenHuman', 'skills.meetingBots.displayName': 'Nome de exibição', 'skills.meetingBots.failedToStart': 'Falha ao iniciar o OpenHuman.', @@ -5273,6 +5274,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Reunião encerrada', 'skills.meetingBots.liveStatusError': 'Falha ao entrar', 'skills.meetingBots.leaveButton': 'Sair', + 'skills.meetingBots.leavingButton': 'Saindo…', 'skills.meetingBots.respondToParticipant': 'Seu nome nesta reunião', 'skills.meetingBots.respondToParticipantHint': 'ex. João (seu nome exibido na chamada)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index e83058d48..f8f57f62e 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -5181,6 +5181,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': 'Заголовок баннера', 'skills.meetingBots.busyTitle': 'OpenHuman занят', 'skills.meetingBots.comingSoon': 'Скоро', + 'skills.meetingBots.couldNotLeaveTitle': 'Не удалось покинуть встречу', 'skills.meetingBots.couldNotStartTitle': 'Не удалось запустить OpenHuman', 'skills.meetingBots.displayName': 'Отображаемое имя', 'skills.meetingBots.failedToStart': 'Не удалось запустить OpenHuman.', @@ -5230,6 +5231,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': 'Встреча завершена', 'skills.meetingBots.liveStatusError': 'Ошибка подключения', 'skills.meetingBots.leaveButton': 'Выйти', + 'skills.meetingBots.leavingButton': 'Выход…', 'skills.meetingBots.respondToParticipant': 'Ваше имя на этой встрече', 'skills.meetingBots.respondToParticipantHint': 'напр. Иван (ваше отображаемое имя в звонке)', 'skills.meetingBots.respondToParticipantDesc': diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 3e135fb62..4b69d9453 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -4880,6 +4880,7 @@ const messages: TranslationMap = { 'skills.meetingBots.bannerTitle': '会议机器人', 'skills.meetingBots.busyTitle': 'OpenHuman 正忙', 'skills.meetingBots.comingSoon': '即将推出', + 'skills.meetingBots.couldNotLeaveTitle': '无法离开会议', 'skills.meetingBots.couldNotStartTitle': '无法启动 OpenHuman', 'skills.meetingBots.displayName': '显示名称', 'skills.meetingBots.failedToStart': '启动 OpenHuman 失败。', @@ -4928,6 +4929,7 @@ const messages: TranslationMap = { 'skills.meetingBots.liveStatusEnded': '会议已结束', 'skills.meetingBots.liveStatusError': '加入失败', 'skills.meetingBots.leaveButton': '离开', + 'skills.meetingBots.leavingButton': '正在离开…', 'skills.meetingBots.respondToParticipant': '您在此会议中的姓名', 'skills.meetingBots.respondToParticipantHint': '例如:小明(通话中的显示名称)', 'skills.meetingBots.respondToParticipantDesc':