mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(meetings): show pending state on active-meeting Leave button (#4348)
This commit is contained in:
@@ -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<ReturnType<typeof setTimeout> | 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')}
|
||||
</span>
|
||||
{canLeave && (
|
||||
<Button variant="secondary" size="sm" onClick={handleLeave} disabled={leaving}>
|
||||
{t('skills.meetingBots.leaveButton')}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleLeave}
|
||||
disabled={leaving}
|
||||
leadingIcon={leaving ? <Spinner className="h-3.5 w-3.5" /> : undefined}>
|
||||
{leaving ? t('skills.meetingBots.leavingButton') : t('skills.meetingBots.leaveButton')}
|
||||
</Button>
|
||||
)}
|
||||
{isDone && (
|
||||
|
||||
@@ -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<void>(resolve => {
|
||||
resolveLeave = resolve;
|
||||
})
|
||||
);
|
||||
renderWithProviders(<ActiveMeetingBanner />, { 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(<ActiveMeetingBanner />, { 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(<ActiveMeetingBanner />, { 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(<ActiveMeetingBanner onToast={onToast} />, { 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(<ActiveMeetingBanner />, { preloadedState: activeState });
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user