@omniloy/sofia-sdk 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4313,7 +4313,7 @@ const {
4313
4313
  mergeConfig: mergeConfig$1,
4314
4314
  } = axios;
4315
4315
 
4316
- const version$2 = "1.0.3";
4316
+ const version$2 = "1.0.4";
4317
4317
 
4318
4318
  const handleApiError$1 = async (error) => {
4319
4319
  return Promise.reject(error);
@@ -4928,6 +4928,23 @@ const ar = {
4928
4928
  "Disable field": "تعطيل الحقل",
4929
4929
  "Enable field": "تمكين الحقل",
4930
4930
  "Customize field prompt": "تخصيص أمر الحقل",
4931
+ "Microphone disconnected": "تم فصل الميكروفون",
4932
+ "The microphone was disconnected during recording": "تم فصل الميكروفون أثناء التسجيل",
4933
+ "Microphone reconnected": "تم إعادة توصيل الميكروفون",
4934
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "تم فصل الميكروفون لفترة وجيزة. قد يكون جزء من التسجيل بعد \"{lastPhrase}\" قد فُقد.",
4935
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "تم فصل الميكروفون لفترة وجيزة. قد يكون جزء من التسجيل قد فُقد.",
4936
+ "Recording interrupted": "تم إيقاف التسجيل",
4937
+ "The server closed the session unexpectedly. Please start recording again.": "أغلق الخادم الجلسة بشكل غير متوقع. يرجى بدء التسجيل مرة أخرى.",
4938
+ "Microphone changed": "تم تغيير الميكروفون",
4939
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "الميكروفون الذي اخترته غير متاح. يستمر التسجيل باستخدام الميكروفون الافتراضي.",
4940
+ "No microphone available": "لا يوجد ميكروفون متاح",
4941
+ "Connect a microphone and try again.": "قم بتوصيل ميكروفون وحاول مرة أخرى.",
4942
+ "Connection lost": "انقطع الاتصال",
4943
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "انقطع اتصال الإنترنت أثناء التسجيل. قد لا يصل الصوت إلى الخادم حتى يتم استعادة الاتصال.",
4944
+ "Connection restored": "تم استعادة الاتصال",
4945
+ "Internet connection has been restored. Recording continues normally.": "تم استعادة اتصال الإنترنت. يستمر التسجيل بشكل طبيعي.",
4946
+ "Slow connection": "اتصال بطيء",
4947
+ "Your internet connection appears to be slow. Some audio may be delayed.": "يبدو أن اتصالك بالإنترنت بطيء. قد يتأخر بعض الصوت.",
4931
4948
  };
4932
4949
 
4933
4950
  const Add$b = "Afegir";
@@ -5204,6 +5221,23 @@ const ca = {
5204
5221
  "Disable field": "Desactivar camp",
5205
5222
  "Enable field": "Activar camp",
5206
5223
  "Customize field prompt": "Personalitzar prompt del camp",
5224
+ "Microphone disconnected": "Micròfon desconnectat",
5225
+ "The microphone was disconnected during recording": "El micròfon s'ha desconnectat durant la gravació",
5226
+ "Microphone reconnected": "Micròfon reconnectat",
5227
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "El micròfon s'ha desconnectat breument. Part de la gravació després de \"{lastPhrase}\" pot haver-se perdut.",
5228
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "El micròfon s'ha desconnectat breument. Part de la gravació pot haver-se perdut.",
5229
+ "Recording interrupted": "Gravació interrompuda",
5230
+ "The server closed the session unexpectedly. Please start recording again.": "El servidor ha tancat la sessió inesperadament. Si us plau, inicia la gravació de nou.",
5231
+ "Microphone changed": "Micròfon canviat",
5232
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "El micròfon seleccionat no estava disponible. La gravació continua amb el micròfon per defecte.",
5233
+ "No microphone available": "No hi ha cap micròfon disponible",
5234
+ "Connect a microphone and try again.": "Connecta un micròfon i torna-ho a provar.",
5235
+ "Connection lost": "Connexió perduda",
5236
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "S'ha perdut la connexió a Internet durant la gravació. L'àudio podria no arribar al servidor fins que es restableixi la connexió.",
5237
+ "Connection restored": "Connexió restablerta",
5238
+ "Internet connection has been restored. Recording continues normally.": "La connexió a Internet s'ha restablert. La gravació continua normalment.",
5239
+ "Slow connection": "Connexió lenta",
5240
+ "Your internet connection appears to be slow. Some audio may be delayed.": "La teva connexió a Internet sembla lenta. Part de l'àudio podria retardar-se.",
5207
5241
  };
5208
5242
 
5209
5243
  const Add$a = "Afegir";
@@ -5480,6 +5514,23 @@ const ca_valencia = {
5480
5514
  "Disable field": "Desactivar camp",
5481
5515
  "Enable field": "Activar camp",
5482
5516
  "Customize field prompt": "Personalitzar prompt del camp",
5517
+ "Microphone disconnected": "Micròfon desconnectat",
5518
+ "The microphone was disconnected during recording": "El micròfon s'ha desconnectat durant la gravació",
5519
+ "Microphone reconnected": "Micròfon reconnectat",
5520
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "El micròfon s'ha desconnectat breument. Part de la gravació després de \"{lastPhrase}\" pot haver-se perdut.",
5521
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "El micròfon s'ha desconnectat breument. Part de la gravació pot haver-se perdut.",
5522
+ "Recording interrupted": "Gravació interrompuda",
5523
+ "The server closed the session unexpectedly. Please start recording again.": "El servidor ha tancat la sessió inesperadament. Per favor, inicia la gravació de nou.",
5524
+ "Microphone changed": "Micròfon canviat",
5525
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "El micròfon seleccionat no estava disponible. La gravació continua amb el micròfon per defecte.",
5526
+ "No microphone available": "No hi ha cap micròfon disponible",
5527
+ "Connect a microphone and try again.": "Connecta un micròfon i torna-ho a provar.",
5528
+ "Connection lost": "Connexió perduda",
5529
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "S'ha perdut la connexió a Internet durant la gravació. L'àudio podria no arribar al servidor fins que es restableixi la connexió.",
5530
+ "Connection restored": "Connexió restablerta",
5531
+ "Internet connection has been restored. Recording continues normally.": "La connexió a Internet s'ha restablert. La gravació continua normalment.",
5532
+ "Slow connection": "Connexió lenta",
5533
+ "Your internet connection appears to be slow. Some audio may be delayed.": "La teva connexió a Internet sembla lenta. Part de l'àudio podria retardar-se.",
5483
5534
  };
5484
5535
 
5485
5536
  const Add$9 = "Přidat";
@@ -5756,6 +5807,23 @@ const cs = {
5756
5807
  "Disable field": "Zakázat pole",
5757
5808
  "Enable field": "Povolit pole",
5758
5809
  "Customize field prompt": "Přizpůsobit prompt pole",
5810
+ "Microphone disconnected": "Mikrofon odpojen",
5811
+ "The microphone was disconnected during recording": "Mikrofon byl odpojen během nahrávání",
5812
+ "Microphone reconnected": "Mikrofon znovu připojen",
5813
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "Mikrofon byl krátce odpojen. Část nahrávky po \"{lastPhrase}\" mohla být ztracena.",
5814
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "Mikrofon byl krátce odpojen. Část nahrávky mohla být ztracena.",
5815
+ "Recording interrupted": "Nahrávání přerušeno",
5816
+ "The server closed the session unexpectedly. Please start recording again.": "Server neočekávaně ukončil relaci. Zahajte nahrávání znovu.",
5817
+ "Microphone changed": "Mikrofon změněn",
5818
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "Vybraný mikrofon nebyl dostupný. Nahrávání pokračuje s výchozím mikrofonem.",
5819
+ "No microphone available": "Žádný mikrofon není k dispozici",
5820
+ "Connect a microphone and try again.": "Připojte mikrofon a zkuste to znovu.",
5821
+ "Connection lost": "Spojení ztraceno",
5822
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "Během nahrávání bylo ztraceno internetové připojení. Zvuk nemusí dorazit na server, dokud nebude připojení obnoveno.",
5823
+ "Connection restored": "Spojení obnoveno",
5824
+ "Internet connection has been restored. Recording continues normally.": "Internetové připojení bylo obnoveno. Nahrávání pokračuje normálně.",
5825
+ "Slow connection": "Pomalé připojení",
5826
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Vaše internetové připojení se zdá být pomalé. Část zvuku může být zpožděna.",
5759
5827
  };
5760
5828
 
5761
5829
  const Add$8 = "Add";
@@ -6032,6 +6100,23 @@ const en_GB = {
6032
6100
  "Disable field": "Disable field",
6033
6101
  "Enable field": "Enable field",
6034
6102
  "Customize field prompt": "Customise field prompt",
6103
+ "Microphone disconnected": "Microphone disconnected",
6104
+ "The microphone was disconnected during recording": "The microphone was disconnected during recording",
6105
+ "Microphone reconnected": "Microphone reconnected",
6106
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.",
6107
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "The microphone was briefly disconnected. Part of the recording may have been lost.",
6108
+ "Recording interrupted": "Recording interrupted",
6109
+ "The server closed the session unexpectedly. Please start recording again.": "The server closed the session unexpectedly. Please start recording again.",
6110
+ "Microphone changed": "Microphone changed",
6111
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "Your selected microphone was unavailable. Recording continues with the default microphone.",
6112
+ "No microphone available": "No microphone available",
6113
+ "Connect a microphone and try again.": "Connect a microphone and try again.",
6114
+ "Connection lost": "Connection lost",
6115
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.",
6116
+ "Connection restored": "Connection restored",
6117
+ "Internet connection has been restored. Recording continues normally.": "Internet connection has been restored. Recording continues normally.",
6118
+ "Slow connection": "Slow connection",
6119
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Your internet connection appears to be slow. Some audio may be delayed.",
6035
6120
  };
6036
6121
 
6037
6122
  const Add$7 = "Add";
@@ -6308,6 +6393,23 @@ const en_US = {
6308
6393
  "Disable field": "Disable field",
6309
6394
  "Enable field": "Enable field",
6310
6395
  "Customize field prompt": "Customize field prompt",
6396
+ "Microphone disconnected": "Microphone disconnected",
6397
+ "The microphone was disconnected during recording": "The microphone was disconnected during recording",
6398
+ "Microphone reconnected": "Microphone reconnected",
6399
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.",
6400
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "The microphone was briefly disconnected. Part of the recording may have been lost.",
6401
+ "Recording interrupted": "Recording interrupted",
6402
+ "The server closed the session unexpectedly. Please start recording again.": "The server closed the session unexpectedly. Please start recording again.",
6403
+ "Microphone changed": "Microphone changed",
6404
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "Your selected microphone was unavailable. Recording continues with the default microphone.",
6405
+ "No microphone available": "No microphone available",
6406
+ "Connect a microphone and try again.": "Connect a microphone and try again.",
6407
+ "Connection lost": "Connection lost",
6408
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.",
6409
+ "Connection restored": "Connection restored",
6410
+ "Internet connection has been restored. Recording continues normally.": "Internet connection has been restored. Recording continues normally.",
6411
+ "Slow connection": "Slow connection",
6412
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Your internet connection appears to be slow. Some audio may be delayed.",
6311
6413
  };
6312
6414
 
6313
6415
  const Add$6 = "Añadir";
@@ -6584,6 +6686,23 @@ const es = {
6584
6686
  "Disable field": "Desactivar campo",
6585
6687
  "Enable field": "Activar campo",
6586
6688
  "Customize field prompt": "Personalizar prompt del campo",
6689
+ "Microphone disconnected": "Micrófono desconectado",
6690
+ "The microphone was disconnected during recording": "El micrófono se desconectó durante la grabación",
6691
+ "Microphone reconnected": "Micrófono reconectado",
6692
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "El micrófono se desconectó brevemente. Parte de la grabación después de \"{lastPhrase}\" puede haberse perdido.",
6693
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "El micrófono se desconectó brevemente. Parte de la grabación puede haberse perdido.",
6694
+ "Recording interrupted": "Grabación interrumpida",
6695
+ "The server closed the session unexpectedly. Please start recording again.": "El servidor cerró la sesión inesperadamente. Vuelve a iniciar la grabación.",
6696
+ "Microphone changed": "Micrófono cambiado",
6697
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "El micrófono seleccionado no estaba disponible. La grabación continúa con el micrófono predeterminado.",
6698
+ "No microphone available": "No hay ningún micrófono disponible",
6699
+ "Connect a microphone and try again.": "Conecta un micrófono e inténtalo de nuevo.",
6700
+ "Connection lost": "Conexión perdida",
6701
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "Se perdió la conexión a internet durante la grabación. El audio podría no llegar al servidor hasta que se restablezca la conexión.",
6702
+ "Connection restored": "Conexión restablecida",
6703
+ "Internet connection has been restored. Recording continues normally.": "La conexión a internet se ha restablecido. La grabación continúa normalmente.",
6704
+ "Slow connection": "Conexión lenta",
6705
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Tu conexión a internet parece ser lenta. Parte del audio podría retrasarse.",
6587
6706
  };
6588
6707
 
6589
6708
  const Add$5 = "Ajouter";
@@ -6860,6 +6979,23 @@ const fr = {
6860
6979
  "Disable field": "Désactiver le champ",
6861
6980
  "Enable field": "Activer le champ",
6862
6981
  "Customize field prompt": "Personnaliser le prompt du champ",
6982
+ "Microphone disconnected": "Microphone déconnecté",
6983
+ "The microphone was disconnected during recording": "Le microphone a été déconnecté pendant l'enregistrement",
6984
+ "Microphone reconnected": "Microphone reconnecté",
6985
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "Le microphone a été brièvement déconnecté. Une partie de l'enregistrement après \"{lastPhrase}\" a pu être perdue.",
6986
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "Le microphone a été brièvement déconnecté. Une partie de l'enregistrement a pu être perdue.",
6987
+ "Recording interrupted": "Enregistrement interrompu",
6988
+ "The server closed the session unexpectedly. Please start recording again.": "Le serveur a fermé la session de manière inattendue. Veuillez relancer l'enregistrement.",
6989
+ "Microphone changed": "Microphone modifié",
6990
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "Le microphone sélectionné n'était pas disponible. L'enregistrement continue avec le microphone par défaut.",
6991
+ "No microphone available": "Aucun microphone disponible",
6992
+ "Connect a microphone and try again.": "Connectez un microphone et réessayez.",
6993
+ "Connection lost": "Connexion perdue",
6994
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "La connexion Internet a été perdue pendant l'enregistrement. L'audio pourrait ne pas atteindre le serveur tant que la connexion n'est pas rétablie.",
6995
+ "Connection restored": "Connexion rétablie",
6996
+ "Internet connection has been restored. Recording continues normally.": "La connexion Internet a été rétablie. L'enregistrement continue normalement.",
6997
+ "Slow connection": "Connexion lente",
6998
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Votre connexion Internet semble lente. Une partie de l'audio pourrait être retardée.",
6863
6999
  };
6864
7000
 
6865
7001
  const Add$4 = "जोड़ें";
@@ -7118,6 +7254,23 @@ const hi = {
7118
7254
  "Disable field": "फ़ील्ड अक्षम करें",
7119
7255
  "Enable field": "फ़ील्ड सक्षम करें",
7120
7256
  "Customize field prompt": "फ़ील्ड प्रॉम्प्ट अनुकूलित करें",
7257
+ "Microphone disconnected": "माइक्रोफ़ोन डिस्कनेक्ट हो गया",
7258
+ "The microphone was disconnected during recording": "रिकॉर्डिंग के दौरान माइक्रोफ़ोन डिस्कनेक्ट हो गया",
7259
+ "Microphone reconnected": "माइक्रोफ़ोन फिर से कनेक्ट हो गया",
7260
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "माइक्रोफ़ोन कुछ समय के लिए डिस्कनेक्ट हो गया था। \"{lastPhrase}\" के बाद की रिकॉर्डिंग का कुछ हिस्सा खो गया हो सकता है।",
7261
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "माइक्रोफ़ोन कुछ समय के लिए डिस्कनेक्ट हो गया था। रिकॉर्डिंग का कुछ हिस्सा खो गया हो सकता है।",
7262
+ "Recording interrupted": "रिकॉर्डिंग बाधित हुई",
7263
+ "The server closed the session unexpectedly. Please start recording again.": "सर्वर ने सत्र अप्रत्याशित रूप से बंद कर दिया। कृपया रिकॉर्डिंग फिर से शुरू करें।",
7264
+ "Microphone changed": "माइक्रोफ़ोन बदल गया",
7265
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "आपका चुना गया माइक्रोफ़ोन उपलब्ध नहीं था। रिकॉर्डिंग डिफ़ॉल्ट माइक्रोफ़ोन के साथ जारी है।",
7266
+ "No microphone available": "कोई माइक्रोफ़ोन उपलब्ध नहीं है",
7267
+ "Connect a microphone and try again.": "माइक्रोफ़ोन कनेक्ट करें और पुनः प्रयास करें।",
7268
+ "Connection lost": "कनेक्शन टूट गया",
7269
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "रिकॉर्डिंग के दौरान इंटरनेट कनेक्शन टूट गया। कनेक्शन बहाल होने तक ऑडियो सर्वर तक नहीं पहुँच सकता।",
7270
+ "Connection restored": "कनेक्शन बहाल हो गया",
7271
+ "Internet connection has been restored. Recording continues normally.": "इंटरनेट कनेक्शन बहाल हो गया है। रिकॉर्डिंग सामान्य रूप से जारी है।",
7272
+ "Slow connection": "धीमा कनेक्शन",
7273
+ "Your internet connection appears to be slow. Some audio may be delayed.": "आपका इंटरनेट कनेक्शन धीमा लग रहा है। कुछ ऑडियो में देरी हो सकती है।",
7121
7274
  };
7122
7275
 
7123
7276
  const Add$3 = "Tambahkan";
@@ -7375,6 +7528,23 @@ const id = {
7375
7528
  "Disable field": "Nonaktifkan bidang",
7376
7529
  "Enable field": "Aktifkan bidang",
7377
7530
  "Customize field prompt": "Sesuaikan prompt bidang",
7531
+ "Microphone disconnected": "Mikrofon terputus",
7532
+ "The microphone was disconnected during recording": "Mikrofon terputus saat merekam",
7533
+ "Microphone reconnected": "Mikrofon tersambung kembali",
7534
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "Mikrofon sempat terputus. Sebagian rekaman setelah \"{lastPhrase}\" mungkin hilang.",
7535
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "Mikrofon sempat terputus. Sebagian rekaman mungkin hilang.",
7536
+ "Recording interrupted": "Rekaman terganggu",
7537
+ "The server closed the session unexpectedly. Please start recording again.": "Server menutup sesi secara tak terduga. Silakan mulai merekam lagi.",
7538
+ "Microphone changed": "Mikrofon berubah",
7539
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "Mikrofon yang Anda pilih tidak tersedia. Rekaman berlanjut dengan mikrofon default.",
7540
+ "No microphone available": "Tidak ada mikrofon yang tersedia",
7541
+ "Connect a microphone and try again.": "Sambungkan mikrofon dan coba lagi.",
7542
+ "Connection lost": "Koneksi terputus",
7543
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "Koneksi internet terputus saat merekam. Audio mungkin tidak sampai ke server hingga koneksi dipulihkan.",
7544
+ "Connection restored": "Koneksi dipulihkan",
7545
+ "Internet connection has been restored. Recording continues normally.": "Koneksi internet telah dipulihkan. Perekaman berlanjut secara normal.",
7546
+ "Slow connection": "Koneksi lambat",
7547
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Koneksi internet Anda tampak lambat. Sebagian audio mungkin tertunda.",
7378
7548
  };
7379
7549
 
7380
7550
  const Add$2 = "ਸ਼ਾਮਲ ਕਰੋ";
@@ -7651,6 +7821,23 @@ const pa = {
7651
7821
  "Disable field": "ਫੀਲਡ ਬੰਦ ਕਰੋ",
7652
7822
  "Enable field": "ਫੀਲਡ ਚਾਲੂ ਕਰੋ",
7653
7823
  "Customize field prompt": "ਫੀਲਡ ਪ੍ਰੋਂਪਟ ਕਸਟਮਾਈਜ਼ ਕਰੋ",
7824
+ "Microphone disconnected": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ",
7825
+ "The microphone was disconnected during recording": "ਰਿਕਾਰਡਿੰਗ ਦੌਰਾਨ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ",
7826
+ "Microphone reconnected": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੁਬਾਰਾ ਕਨੈਕਟ ਹੋ ਗਿਆ",
7827
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਥੋੜ੍ਹੇ ਸਮੇਂ ਲਈ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਸੀ। \"{lastPhrase}\" ਤੋਂ ਬਾਅਦ ਦੀ ਰਿਕਾਰਡਿੰਗ ਦਾ ਕੁਝ ਹਿੱਸਾ ਗੁਆਚ ਗਿਆ ਹੋ ਸਕਦਾ ਹੈ।",
7828
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਥੋੜ੍ਹੇ ਸਮੇਂ ਲਈ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਸੀ। ਰਿਕਾਰਡਿੰਗ ਦਾ ਕੁਝ ਹਿੱਸਾ ਗੁਆਚ ਗਿਆ ਹੋ ਸਕਦਾ ਹੈ।",
7829
+ "Recording interrupted": "ਰਿਕਾਰਡਿੰਗ ਵਿੱਚ ਰੁਕਾਵਟ",
7830
+ "The server closed the session unexpectedly. Please start recording again.": "ਸਰਵਰ ਨੇ ਸੈਸ਼ਨ ਅਚਾਨਕ ਬੰਦ ਕਰ ਦਿੱਤਾ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ।",
7831
+ "Microphone changed": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਦਲ ਗਿਆ",
7832
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "ਤੁਹਾਡਾ ਚੁਣਿਆ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਨਹੀਂ ਸੀ। ਰਿਕਾਰਡਿੰਗ ਡਿਫ਼ਾਲਟ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨਾਲ ਜਾਰੀ ਹੈ।",
7833
+ "No microphone available": "ਕੋਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਨਹੀਂ ਹੈ",
7834
+ "Connect a microphone and try again.": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਜੋੜੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
7835
+ "Connection lost": "ਕਨੈਕਸ਼ਨ ਟੁੱਟ ਗਿਆ",
7836
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "ਰਿਕਾਰਡਿੰਗ ਦੌਰਾਨ ਇੰਟਰਨੈਟ ਕਨੈਕਸ਼ਨ ਟੁੱਟ ਗਿਆ। ਕਨੈਕਸ਼ਨ ਬਹਾਲ ਹੋਣ ਤੱਕ ਆਡੀਓ ਸਰਵਰ ਤੱਕ ਨਹੀਂ ਪਹੁੰਚ ਸਕਦਾ।",
7837
+ "Connection restored": "ਕਨੈਕਸ਼ਨ ਬਹਾਲ ਹੋ ਗਿਆ",
7838
+ "Internet connection has been restored. Recording continues normally.": "ਇੰਟਰਨੈਟ ਕਨੈਕਸ਼ਨ ਬਹਾਲ ਹੋ ਗਿਆ ਹੈ। ਰਿਕਾਰਡਿੰਗ ਆਮ ਤੌਰ 'ਤੇ ਜਾਰੀ ਹੈ।",
7839
+ "Slow connection": "ਹੌਲੀ ਕਨੈਕਸ਼ਨ",
7840
+ "Your internet connection appears to be slow. Some audio may be delayed.": "ਤੁਹਾਡਾ ਇੰਟਰਨੈਟ ਕਨੈਕਸ਼ਨ ਹੌਲੀ ਲੱਗ ਰਿਹਾ ਹੈ। ਕੁਝ ਆਡੀਓ ਵਿੱਚ ਦੇਰੀ ਹੋ ਸਕਦੀ ਹੈ।",
7654
7841
  };
7655
7842
 
7656
7843
  const Add$1 = "Lägg till";
@@ -7927,6 +8114,23 @@ const sv = {
7927
8114
  "Disable field": "Inaktivera fält",
7928
8115
  "Enable field": "Aktivera fält",
7929
8116
  "Customize field prompt": "Anpassa fältprompt",
8117
+ "Microphone disconnected": "Mikrofon frånkopplad",
8118
+ "The microphone was disconnected during recording": "Mikrofonen kopplades från under inspelningen",
8119
+ "Microphone reconnected": "Mikrofon återansluten",
8120
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "Mikrofonen kopplades kortvarigt från. Delar av inspelningen efter \"{lastPhrase}\" kan ha gått förlorade.",
8121
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "Mikrofonen kopplades kortvarigt från. Delar av inspelningen kan ha gått förlorade.",
8122
+ "Recording interrupted": "Inspelning avbruten",
8123
+ "The server closed the session unexpectedly. Please start recording again.": "Servern stängde sessionen oväntat. Starta inspelningen igen.",
8124
+ "Microphone changed": "Mikrofon ändrad",
8125
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "Den valda mikrofonen var inte tillgänglig. Inspelningen fortsätter med standardmikrofonen.",
8126
+ "No microphone available": "Ingen mikrofon tillgänglig",
8127
+ "Connect a microphone and try again.": "Anslut en mikrofon och försök igen.",
8128
+ "Connection lost": "Anslutning förlorad",
8129
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "Internetanslutningen förlorades under inspelningen. Ljud kanske inte når servern förrän anslutningen har återställts.",
8130
+ "Connection restored": "Anslutning återställd",
8131
+ "Internet connection has been restored. Recording continues normally.": "Internetanslutningen har återställts. Inspelningen fortsätter normalt.",
8132
+ "Slow connection": "Långsam anslutning",
8133
+ "Your internet connection appears to be slow. Some audio may be delayed.": "Din internetanslutning verkar vara långsam. Visst ljud kan fördröjas.",
7930
8134
  };
7931
8135
 
7932
8136
  const Add = "شامل کریں";
@@ -8203,6 +8407,23 @@ const ur = {
8203
8407
  "Disable field": "فیلڈ غیر فعال کریں",
8204
8408
  "Enable field": "فیلڈ فعال کریں",
8205
8409
  "Customize field prompt": "فیلڈ پرامپٹ حسب ضرورت بنائیں",
8410
+ "Microphone disconnected": "مائیکروفون منقطع ہو گیا",
8411
+ "The microphone was disconnected during recording": "ریکارڈنگ کے دوران مائیکروفون منقطع ہو گیا",
8412
+ "Microphone reconnected": "مائیکروفون دوبارہ جوڑ دیا گیا",
8413
+ "The microphone was briefly disconnected. Part of the recording after \"{lastPhrase}\" may have been lost.": "مائیکروفون مختصر وقت کے لیے منقطع ہو گیا تھا۔ \"{lastPhrase}\" کے بعد ریکارڈنگ کا کچھ حصہ ضائع ہو سکتا ہے۔",
8414
+ "The microphone was briefly disconnected. Part of the recording may have been lost.": "مائیکروفون مختصر وقت کے لیے منقطع ہو گیا تھا۔ ریکارڈنگ کا کچھ حصہ ضائع ہو سکتا ہے۔",
8415
+ "Recording interrupted": "ریکارڈنگ میں رکاوٹ",
8416
+ "The server closed the session unexpectedly. Please start recording again.": "سرور نے سیشن غیر متوقع طور پر بند کر دیا۔ براہ کرم دوبارہ ریکارڈنگ شروع کریں۔",
8417
+ "Microphone changed": "مائیکروفون تبدیل ہو گیا",
8418
+ "Your selected microphone was unavailable. Recording continues with the default microphone.": "آپ کا منتخب کردہ مائیکروفون دستیاب نہیں تھا۔ ریکارڈنگ پہلے سے طے شدہ مائیکروفون کے ساتھ جاری ہے۔",
8419
+ "No microphone available": "کوئی مائیکروفون دستیاب نہیں",
8420
+ "Connect a microphone and try again.": "مائیکروفون جوڑیں اور دوبارہ کوشش کریں۔",
8421
+ "Connection lost": "کنکشن ٹوٹ گیا",
8422
+ "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.": "ریکارڈنگ کے دوران انٹرنیٹ کنکشن ٹوٹ گیا۔ کنکشن بحال ہونے تک آڈیو سرور تک نہیں پہنچ سکتا۔",
8423
+ "Connection restored": "کنکشن بحال ہو گیا",
8424
+ "Internet connection has been restored. Recording continues normally.": "انٹرنیٹ کنکشن بحال ہو گیا ہے۔ ریکارڈنگ معمول کے مطابق جاری ہے۔",
8425
+ "Slow connection": "سست کنکشن",
8426
+ "Your internet connection appears to be slow. Some audio may be delayed.": "آپ کا انٹرنیٹ کنکشن سست لگ رہا ہے۔ کچھ آڈیو میں تاخیر ہو سکتی ہے۔",
8206
8427
  };
8207
8428
 
8208
8429
  var LanguageCode = /* @__PURE__ */ ((LanguageCode2) => {
@@ -15433,6 +15654,8 @@ const initialState$1 = {
15433
15654
  audioContext: null,
15434
15655
  processor: null,
15435
15656
  source: null,
15657
+ processorStream: null,
15658
+ processorSocket: null,
15436
15659
  isServerReady: false,
15437
15660
  uuid: "",
15438
15661
  isActive: false,
@@ -15805,18 +16028,19 @@ const AudioRecorderApi = {
15805
16028
  }
15806
16029
  },
15807
16030
  record: (socket, uuid, language) => {
15808
- socket.onopen = function() {
15809
- socket.send(
15810
- JSON.stringify({
15811
- uid: uuid,
15812
- language,
15813
- task: "transcribe",
15814
- model: "medium",
15815
- medical_practice: "medical",
15816
- use_vad: true
15817
- })
15818
- );
15819
- };
16031
+ const config = JSON.stringify({
16032
+ uid: uuid,
16033
+ language,
16034
+ task: "transcribe",
16035
+ model: "medium",
16036
+ medical_practice: "medical",
16037
+ use_vad: true
16038
+ });
16039
+ if (socket.readyState === WebSocket.OPEN) {
16040
+ socket.send(config);
16041
+ return;
16042
+ }
16043
+ socket.addEventListener("open", () => socket.send(config), { once: true });
15820
16044
  },
15821
16045
  recordFinal: (socket) => {
15822
16046
  socket.send(
@@ -15907,8 +16131,13 @@ const useWebSocketConnection = ({
15907
16131
  currentSocketRef,
15908
16132
  preconnectedSocketRef,
15909
16133
  onConsumePreconnectedSocket,
15910
- addSegments
16134
+ addSegments,
16135
+ onUnexpectedClose
15911
16136
  }) => {
16137
+ const onUnexpectedCloseRef = React.useRef(onUnexpectedClose);
16138
+ React.useEffect(() => {
16139
+ onUnexpectedCloseRef.current = onUnexpectedClose;
16140
+ }, [onUnexpectedClose]);
15912
16141
  const addSegmentsRef = React.useRef(addSegments);
15913
16142
  React.useEffect(() => {
15914
16143
  addSegmentsRef.current = addSegments;
@@ -16026,12 +16255,14 @@ const useWebSocketConnection = ({
16026
16255
  );
16027
16256
  return;
16028
16257
  }
16029
- if (event.code !== 1e3 && event.code !== 1005) {
16258
+ const isUnexpected = event.code !== 1e3 && event.code !== 1005;
16259
+ if (isUnexpected) {
16030
16260
  if (event.code === 1006) {
16031
16261
  logger.webSocketError(
16032
16262
  "WSS URL is unreachable. Please verify your wssurl property points to a valid WebSocket endpoint."
16033
16263
  );
16034
16264
  }
16265
+ onUnexpectedCloseRef.current?.(event);
16035
16266
  }
16036
16267
  setState((prev) => ({
16037
16268
  ...prev,
@@ -16123,6 +16354,7 @@ const useWebSocketConnection = ({
16123
16354
  logger.webSocketError(
16124
16355
  `WebSocket closed unexpectedly (code: ${event.code})`
16125
16356
  );
16357
+ onUnexpectedCloseRef.current?.(event);
16126
16358
  }
16127
16359
  setState((prev) => ({
16128
16360
  ...prev,
@@ -16182,34 +16414,72 @@ const useWebSocketConnection = ({
16182
16414
  return { startRecording };
16183
16415
  };
16184
16416
 
16185
- const RECONNECT_DELAY = 3e3;
16417
+ const INITIAL_RECONNECT_DELAY = 3e3;
16418
+ const MAX_RECONNECT_DELAY = 6e4;
16419
+ const MAX_RECONNECT_ATTEMPTS = 5;
16420
+ const NON_RETRYABLE_CLOSE_CODES = /* @__PURE__ */ new Set([1e3, 1008, 1011]);
16186
16421
  const useWebSocketPreconnect = ({
16187
16422
  wssUrl,
16188
- enabled = true
16423
+ enabled = true,
16424
+ isRecordingActive = false
16189
16425
  }) => {
16190
16426
  const [socket, setSocket] = React.useState(null);
16191
16427
  const [status, setStatus] = React.useState("disconnected");
16192
16428
  const reconnectTimeoutRef = React.useRef(null);
16193
16429
  const mountedRef = React.useRef(true);
16430
+ const reconnectAttemptsRef = React.useRef(0);
16431
+ const socketRef = React.useRef(null);
16432
+ const isRecordingActiveRef = React.useRef(isRecordingActive);
16433
+ React.useEffect(() => {
16434
+ isRecordingActiveRef.current = isRecordingActive;
16435
+ }, [isRecordingActive]);
16194
16436
  const clearTimers = React.useCallback(() => {
16195
16437
  if (reconnectTimeoutRef.current) {
16196
16438
  clearTimeout(reconnectTimeoutRef.current);
16197
16439
  reconnectTimeoutRef.current = null;
16198
16440
  }
16199
16441
  }, []);
16442
+ const getBackoffDelay = React.useCallback((attempt) => {
16443
+ const exponential = INITIAL_RECONNECT_DELAY * Math.pow(2, attempt);
16444
+ return Math.min(exponential, MAX_RECONNECT_DELAY);
16445
+ }, []);
16200
16446
  const connect = React.useCallback(() => {
16201
16447
  if (!wssUrl || !enabled || !mountedRef.current) return;
16202
16448
  clearTimers();
16203
16449
  setStatus("connecting");
16450
+ const scheduleRetry = (reason) => {
16451
+ if (!mountedRef.current || !enabled) return;
16452
+ if (isRecordingActiveRef.current) return;
16453
+ const attempt = reconnectAttemptsRef.current;
16454
+ if (attempt >= MAX_RECONNECT_ATTEMPTS) {
16455
+ logger.webSocketError(
16456
+ `WebSocket preconnect gave up after ${attempt} attempts (${reason})`
16457
+ );
16458
+ setStatus("paused");
16459
+ return;
16460
+ }
16461
+ const delay = getBackoffDelay(attempt);
16462
+ reconnectAttemptsRef.current = attempt + 1;
16463
+ logger.info(
16464
+ `WebSocket ${reason}, retry ${attempt + 1}/${MAX_RECONNECT_ATTEMPTS} in ${delay}ms`
16465
+ );
16466
+ reconnectTimeoutRef.current = setTimeout(() => {
16467
+ if (mountedRef.current && enabled && !isRecordingActiveRef.current) {
16468
+ connect();
16469
+ }
16470
+ }, delay);
16471
+ };
16204
16472
  try {
16205
16473
  const newSocket = new WebSocket(wssUrl);
16206
16474
  newSocket.binaryType = "arraybuffer";
16475
+ socketRef.current = newSocket;
16207
16476
  newSocket.onopen = () => {
16208
- if (!mountedRef.current) {
16477
+ if (!mountedRef.current || socketRef.current !== newSocket) {
16209
16478
  newSocket.close();
16210
16479
  return;
16211
16480
  }
16212
16481
  logger.info("WebSocket pre-connected successfully");
16482
+ reconnectAttemptsRef.current = 0;
16213
16483
  setSocket(newSocket);
16214
16484
  setStatus("connected");
16215
16485
  };
@@ -16218,61 +16488,56 @@ const useWebSocketPreconnect = ({
16218
16488
  };
16219
16489
  newSocket.onclose = (event) => {
16220
16490
  if (!mountedRef.current) return;
16491
+ if (socketRef.current !== newSocket && socketRef.current !== null)
16492
+ return;
16221
16493
  clearTimers();
16494
+ socketRef.current = null;
16222
16495
  setSocket(null);
16223
16496
  setStatus("disconnected");
16224
- if (enabled) {
16497
+ if (!enabled) return;
16498
+ if (NON_RETRYABLE_CLOSE_CODES.has(event.code)) {
16225
16499
  logger.info(
16226
- `WebSocket closed (code: ${event.code}), scheduling reconnect...`
16500
+ `WebSocket closed cleanly (code: ${event.code}); pausing preconnect until next user action`
16227
16501
  );
16228
- reconnectTimeoutRef.current = setTimeout(() => {
16229
- if (mountedRef.current && enabled) {
16230
- connect();
16231
- }
16232
- }, RECONNECT_DELAY);
16502
+ return;
16233
16503
  }
16504
+ scheduleRetry(`closed (code: ${event.code})`);
16234
16505
  };
16235
16506
  } catch (error) {
16236
16507
  logger.webSocketError("Failed to create pre-connection:", error);
16237
16508
  setStatus("disconnected");
16238
- if (enabled) {
16239
- reconnectTimeoutRef.current = setTimeout(() => {
16240
- if (mountedRef.current && enabled) {
16241
- connect();
16242
- }
16243
- }, RECONNECT_DELAY);
16244
- }
16509
+ scheduleRetry("creation error");
16245
16510
  }
16246
- }, [wssUrl, enabled, clearTimers]);
16511
+ }, [wssUrl, enabled, clearTimers, getBackoffDelay]);
16247
16512
  const reconnect = React.useCallback(() => {
16248
- if (socket) {
16249
- socket.close();
16513
+ reconnectAttemptsRef.current = 0;
16514
+ if (socketRef.current) {
16515
+ socketRef.current.close(1e3, "Manual reconnect");
16516
+ socketRef.current = null;
16250
16517
  }
16251
16518
  connect();
16252
- }, [socket, connect]);
16519
+ }, [connect]);
16253
16520
  const consumeSocket = React.useCallback(() => {
16254
16521
  setSocket(null);
16255
16522
  setStatus("disconnected");
16523
+ reconnectAttemptsRef.current = 0;
16256
16524
  clearTimers();
16257
- reconnectTimeoutRef.current = setTimeout(() => {
16258
- if (mountedRef.current && enabled) {
16259
- connect();
16260
- }
16261
- }, RECONNECT_DELAY);
16262
- }, [enabled, connect, clearTimers]);
16525
+ socketRef.current = null;
16526
+ }, [clearTimers]);
16263
16527
  React.useEffect(() => {
16264
16528
  mountedRef.current = true;
16265
- if (enabled && wssUrl) {
16529
+ if (enabled && wssUrl && !isRecordingActive) {
16266
16530
  connect();
16267
16531
  }
16268
16532
  return () => {
16269
16533
  mountedRef.current = false;
16270
16534
  clearTimers();
16271
- if (socket && socket.readyState === WebSocket.OPEN) {
16272
- socket.close(1e3, "Component unmounted");
16535
+ if (socketRef.current) {
16536
+ socketRef.current.close(1e3, "Preconnect cleanup");
16537
+ socketRef.current = null;
16273
16538
  }
16274
16539
  };
16275
- }, [enabled, wssUrl]);
16540
+ }, [enabled, wssUrl, isRecordingActive]);
16276
16541
  return {
16277
16542
  socket,
16278
16543
  status,
@@ -16461,13 +16726,7 @@ var ToastVariation = /* @__PURE__ */ ((ToastVariation2) => {
16461
16726
  ToastVariation2["danger"] = "danger";
16462
16727
  return ToastVariation2;
16463
16728
  })(ToastVariation || {});
16464
- const ToastDefaultState = {
16465
- title: "",
16466
- message: "",
16467
- visible: false,
16468
- variation: "default" /* default */,
16469
- duration: 3e3
16470
- };
16729
+ const MAX_TOASTS = 3;
16471
16730
  const showErrorToast = (setToastState, formatMessage) => {
16472
16731
  setToastState({
16473
16732
  variation: "danger" /* danger */,
@@ -16500,11 +16759,15 @@ const handleMicros = async (setState) => {
16500
16759
  ...prevState,
16501
16760
  devices: mics.map(({ deviceId, label }) => ({ deviceId, label }))
16502
16761
  }));
16503
- if (mics.length > 0)
16504
- setState((prevState) => ({
16762
+ if (mics.length === 0) return;
16763
+ setState((prevState) => {
16764
+ const currentIsValid = !!prevState.selectedDevice && prevState.selectedDevice !== "no-permission" && prevState.selectedDevice !== "no-devices" && prevState.selectedDevice !== "error" && mics.some((m) => m.deviceId === prevState.selectedDevice);
16765
+ if (currentIsValid) return prevState;
16766
+ return {
16505
16767
  ...prevState,
16506
16768
  selectedDevice: mics[0].deviceId
16507
- }));
16769
+ };
16770
+ });
16508
16771
  };
16509
16772
  const getMicros = async (setHasPermission, setOptions, setDevices, icon, setToastState, microText, notFoundText, errorText, toastText) => {
16510
16773
  try {
@@ -16568,6 +16831,18 @@ const requestMicrophonePermission = async (permission, setState, action, setToas
16568
16831
  }
16569
16832
  } else action();
16570
16833
  };
16834
+ const isNoMicrophoneError = (error) => {
16835
+ if (!(error instanceof DOMException)) return false;
16836
+ return error.name === "NotFoundError" || error.name === "OverconstrainedError" || error.name === "NotReadableError" || error.name === "AbortError";
16837
+ };
16838
+ const setNoMicrophoneToast = (setToastState, title, message) => {
16839
+ setToastState({
16840
+ variation: ToastVariation.danger,
16841
+ title,
16842
+ message,
16843
+ visible: true
16844
+ });
16845
+ };
16571
16846
  const setDeniedPermissionToast = (setToastState, text) => {
16572
16847
  setToastState({
16573
16848
  variation: ToastVariation.danger,
@@ -16611,7 +16886,8 @@ const useRecordingActions = ({
16611
16886
  appointmentData,
16612
16887
  actions,
16613
16888
  refs,
16614
- toastText
16889
+ toastText,
16890
+ noMicToast
16615
16891
  }) => {
16616
16892
  const { stream, audioContext, animationFrameId, uuid, seconds } = recordingState;
16617
16893
  const {
@@ -16788,9 +17064,18 @@ const useRecordingActions = ({
16788
17064
  const newStream = await getMediaStream(selectedDevice);
16789
17065
  setStream(newStream);
16790
17066
  getMicrophones(newStream);
16791
- } catch {
16792
- setDeniedPermissionToast(setToastState, toastText);
16793
- logger.audioError("Microphone access denied.");
17067
+ } catch (error) {
17068
+ if (isNoMicrophoneError(error)) {
17069
+ setNoMicrophoneToast(
17070
+ setToastState,
17071
+ noMicToast.title,
17072
+ noMicToast.message
17073
+ );
17074
+ logger.audioError("No microphone available:", error);
17075
+ } else {
17076
+ setDeniedPermissionToast(setToastState, toastText);
17077
+ logger.audioError("Microphone access denied.", error);
17078
+ }
16794
17079
  }
16795
17080
  }
16796
17081
  }, [
@@ -16801,6 +17086,7 @@ const useRecordingActions = ({
16801
17086
  getMicrophones,
16802
17087
  setToastState,
16803
17088
  toastText,
17089
+ noMicToast,
16804
17090
  setStream,
16805
17091
  isStoppingRef,
16806
17092
  prepareForNewRecording
@@ -16811,13 +17097,18 @@ const useRecordingActions = ({
16811
17097
  };
16812
17098
  };
16813
17099
 
17100
+ const SILENCE_THRESHOLD = 1e-4;
17101
+ const SILENCE_FRAMES_LIMIT = 30;
17102
+ const NO_SEND_WATCHDOG_MS = 8e3;
17103
+ const NO_SEND_WATCHDOG_CHECK_MS = 1e3;
16814
17104
  const useAudioProcessor = ({
16815
17105
  stream,
16816
17106
  socket,
16817
17107
  isServerReady,
16818
17108
  enabled,
16819
17109
  connectionLoading,
16820
- setAudioContext
17110
+ setAudioContext,
17111
+ onDeviceLost
16821
17112
  }) => {
16822
17113
  const processorRef = React.useRef(null);
16823
17114
  const sourceRef = React.useRef(null);
@@ -16826,9 +17117,16 @@ const useAudioProcessor = ({
16826
17117
  React.useEffect(() => {
16827
17118
  isServerReadyRef.current = isServerReady;
16828
17119
  }, [isServerReady]);
17120
+ const onDeviceLostRef = React.useRef(onDeviceLost);
17121
+ React.useEffect(() => {
17122
+ onDeviceLostRef.current = onDeviceLost;
17123
+ }, [onDeviceLost]);
17124
+ const consecutiveSilentFramesRef = React.useRef(0);
17125
+ const deviceLostFiredRef = React.useRef(false);
17126
+ const lastSendAtRef = React.useRef(0);
16829
17127
  React.useEffect(() => {
16830
17128
  const session = getSessionState();
16831
- if (session.processor && session.source && session.audioContext && session.stream === stream && session.socket === socket) {
17129
+ if (session.processor && session.source && session.audioContext && session.processorStream === stream && session.processorSocket === socket) {
16832
17130
  processorRef.current = session.processor;
16833
17131
  sourceRef.current = session.source;
16834
17132
  audioContextRef.current = session.audioContext;
@@ -16852,12 +17150,40 @@ const useAudioProcessor = ({
16852
17150
  }
16853
17151
  audioContextRef.current = null;
16854
17152
  }
16855
- updateSessionState({ processor: null, source: null });
17153
+ updateSessionState({
17154
+ processor: null,
17155
+ source: null,
17156
+ processorStream: null,
17157
+ processorSocket: null
17158
+ });
16856
17159
  if (!stream || !enabled || connectionLoading) return;
17160
+ const audioTracks = stream.getAudioTracks();
17161
+ const handleTrackEnded = () => {
17162
+ if (deviceLostFiredRef.current) return;
17163
+ deviceLostFiredRef.current = true;
17164
+ logger.warn("Audio track ended — microphone disconnected");
17165
+ onDeviceLostRef.current?.();
17166
+ };
17167
+ const handleTrackMute = () => {
17168
+ logger.warn("Audio track muted — possible transient device glitch");
17169
+ };
17170
+ const handleTrackUnmute = () => {
17171
+ logger.info("Audio track unmuted — device recovered");
17172
+ consecutiveSilentFramesRef.current = 0;
17173
+ deviceLostFiredRef.current = false;
17174
+ };
16857
17175
  try {
16858
17176
  const audioContext = new AudioContext();
16859
17177
  audioContextRef.current = audioContext;
16860
17178
  setAudioContext(audioContext);
17179
+ if (audioContext.state === "suspended") {
17180
+ logger.warn(
17181
+ "AudioContext started suspended — calling resume(). This usually means the browser did not consider the current execution context a user gesture."
17182
+ );
17183
+ audioContext.resume().catch(
17184
+ (err) => logger.audioError("Failed to resume AudioContext:", err)
17185
+ );
17186
+ }
16861
17187
  const source = audioContext.createMediaStreamSource(stream);
16862
17188
  sourceRef.current = source;
16863
17189
  const analyser = audioContext.createAnalyser();
@@ -16868,28 +17194,68 @@ const useAudioProcessor = ({
16868
17194
  source.connect(analyser);
16869
17195
  const processor = audioContext.createScriptProcessor(4096, 1, 1);
16870
17196
  processorRef.current = processor;
17197
+ consecutiveSilentFramesRef.current = 0;
17198
+ deviceLostFiredRef.current = false;
17199
+ lastSendAtRef.current = Date.now();
16871
17200
  processor.onaudioprocess = (event) => {
16872
17201
  if (!isServerReadyRef.current || !socket) return;
17202
+ if (deviceLostFiredRef.current) return;
16873
17203
  const input = event.inputBuffer.getChannelData(0);
17204
+ let isSilent = true;
17205
+ for (let i = 0; i < input.length; i++) {
17206
+ if (Math.abs(input[i]) > SILENCE_THRESHOLD) {
17207
+ isSilent = false;
17208
+ break;
17209
+ }
17210
+ }
17211
+ if (isSilent) {
17212
+ consecutiveSilentFramesRef.current++;
17213
+ if (consecutiveSilentFramesRef.current >= SILENCE_FRAMES_LIMIT && !deviceLostFiredRef.current) {
17214
+ deviceLostFiredRef.current = true;
17215
+ logger.warn(
17216
+ `Detected ${SILENCE_FRAMES_LIMIT} consecutive silent frames — microphone may be disconnected`
17217
+ );
17218
+ onDeviceLostRef.current?.();
17219
+ return;
17220
+ }
17221
+ } else {
17222
+ consecutiveSilentFramesRef.current = 0;
17223
+ }
16874
17224
  if (socket.readyState === WebSocket.OPEN) {
16875
17225
  const audioData16kHz = resampleTo16kHZ(
16876
17226
  input,
16877
17227
  audioContext.sampleRate
16878
17228
  );
16879
17229
  socket.send(audioData16kHz);
17230
+ lastSendAtRef.current = Date.now();
16880
17231
  }
16881
17232
  };
17233
+ audioTracks.forEach((t) => {
17234
+ t.addEventListener("ended", handleTrackEnded);
17235
+ t.addEventListener("mute", handleTrackMute);
17236
+ t.addEventListener("unmute", handleTrackUnmute);
17237
+ });
16882
17238
  source.connect(processor);
16883
17239
  processor.connect(audioContext.destination);
16884
17240
  const gain = audioContext.createGain();
16885
17241
  gain.gain.value = 1e-3;
16886
17242
  analyser.connect(gain);
16887
17243
  gain.connect(audioContext.destination);
16888
- updateSessionState({ processor, source });
17244
+ updateSessionState({
17245
+ processor,
17246
+ source,
17247
+ processorStream: stream,
17248
+ processorSocket: socket
17249
+ });
16889
17250
  } catch (error) {
16890
17251
  logger.audioError("Error setting up audio context:", error);
16891
17252
  }
16892
17253
  return () => {
17254
+ audioTracks.forEach((t) => {
17255
+ t.removeEventListener("ended", handleTrackEnded);
17256
+ t.removeEventListener("mute", handleTrackMute);
17257
+ t.removeEventListener("unmute", handleTrackUnmute);
17258
+ });
16893
17259
  const current = getSessionState();
16894
17260
  if (current.processor === processorRef.current && current.source === sourceRef.current && current.isActive) {
16895
17261
  return;
@@ -16902,9 +17268,31 @@ const useAudioProcessor = ({
16902
17268
  sourceRef.current.disconnect();
16903
17269
  sourceRef.current = null;
16904
17270
  }
16905
- updateSessionState({ processor: null, source: null });
17271
+ updateSessionState({
17272
+ processor: null,
17273
+ source: null,
17274
+ processorStream: null,
17275
+ processorSocket: null
17276
+ });
16906
17277
  };
16907
17278
  }, [stream, socket, enabled, connectionLoading, setAudioContext]);
17279
+ React.useEffect(() => {
17280
+ if (!enabled || connectionLoading || !socket || !isServerReady) return;
17281
+ lastSendAtRef.current = Date.now();
17282
+ const interval = setInterval(() => {
17283
+ if (deviceLostFiredRef.current) return;
17284
+ if (socket.readyState !== WebSocket.OPEN) return;
17285
+ const sinceLastSend = Date.now() - lastSendAtRef.current;
17286
+ if (sinceLastSend > NO_SEND_WATCHDOG_MS) {
17287
+ deviceLostFiredRef.current = true;
17288
+ logger.warn(
17289
+ `No audio sent to server for ${sinceLastSend}ms — microphone input appears stuck`
17290
+ );
17291
+ onDeviceLostRef.current?.();
17292
+ }
17293
+ }, NO_SEND_WATCHDOG_CHECK_MS);
17294
+ return () => clearInterval(interval);
17295
+ }, [enabled, connectionLoading, socket, isServerReady]);
16908
17296
  const cleanup = React.useCallback(() => {
16909
17297
  if (processorRef.current) {
16910
17298
  processorRef.current.disconnect();
@@ -16914,7 +17302,12 @@ const useAudioProcessor = ({
16914
17302
  sourceRef.current.disconnect();
16915
17303
  sourceRef.current = null;
16916
17304
  }
16917
- updateSessionState({ processor: null, source: null });
17305
+ updateSessionState({
17306
+ processor: null,
17307
+ source: null,
17308
+ processorStream: null,
17309
+ processorSocket: null
17310
+ });
16918
17311
  if (audioContextRef.current) {
16919
17312
  try {
16920
17313
  if (audioContextRef.current.state !== "closed") {
@@ -16949,6 +17342,19 @@ const useAudioRecording = (options = {}) => {
16949
17342
  const toastText = formatMessage({
16950
17343
  id: "Please enable the microphone or reset the permissions"
16951
17344
  });
17345
+ const noMicToast = React.useMemo(
17346
+ () => ({
17347
+ title: formatMessage({
17348
+ id: "No microphone available",
17349
+ defaultMessage: "No microphone available"
17350
+ }),
17351
+ message: formatMessage({
17352
+ id: "Connect a microphone and try again.",
17353
+ defaultMessage: "Connect a microphone and try again."
17354
+ })
17355
+ }),
17356
+ [formatMessage]
17357
+ );
16952
17358
  const isCurrentlyRecording = enabled && !connectionLoading;
16953
17359
  const state = useAudioRecordingState(appointmentData, isCurrentlyRecording);
16954
17360
  const {
@@ -16970,7 +17376,8 @@ const useAudioRecording = (options = {}) => {
16970
17376
  ]);
16971
17377
  const { socket: preconnectedSocket, consumeSocket } = useWebSocketPreconnect({
16972
17378
  wssUrl,
16973
- enabled: !!wssUrl
17379
+ enabled: !!wssUrl && permission === "granted",
17380
+ isRecordingActive: isCurrentlyRecording
16974
17381
  });
16975
17382
  const preconnectedSocketRef = React.useRef(null);
16976
17383
  React.useEffect(() => {
@@ -16986,6 +17393,10 @@ const useAudioRecording = (options = {}) => {
16986
17393
  React.useEffect(() => {
16987
17394
  segmentsRef.current = segments;
16988
17395
  }, [segments]);
17396
+ const handleServerCloseRef = React.useRef(null);
17397
+ const handleUnexpectedClose = React.useCallback((event) => {
17398
+ handleServerCloseRef.current?.(event);
17399
+ }, []);
16989
17400
  const { startRecording } = useWebSocketConnection({
16990
17401
  wssUrl,
16991
17402
  language,
@@ -16998,19 +17409,26 @@ const useAudioRecording = (options = {}) => {
16998
17409
  currentSocketRef,
16999
17410
  preconnectedSocketRef,
17000
17411
  onConsumePreconnectedSocket: consumeSocket,
17001
- addSegments
17412
+ addSegments,
17413
+ onUnexpectedClose: handleUnexpectedClose
17002
17414
  });
17003
17415
  const { cleanupPromise } = useAudioRecordingCleanup();
17416
+ const stopOnDeviceLostRef = React.useRef(null);
17417
+ const handleDeviceLost = React.useCallback(() => {
17418
+ stopOnDeviceLostRef.current?.();
17419
+ }, []);
17004
17420
  const { cleanup: audioProcessorCleanup } = useAudioProcessor({
17005
17421
  stream: state.stream,
17006
17422
  socket: state.socket,
17007
17423
  isServerReady: state.isServerReady,
17008
17424
  enabled,
17009
17425
  connectionLoading,
17010
- setAudioContext: state.setAudioContext
17426
+ setAudioContext: state.setAudioContext,
17427
+ onDeviceLost: handleDeviceLost
17011
17428
  });
17012
- const { handleToggleRecording } = useRecordingActions({
17429
+ const { handleToggleRecording, stopCapture } = useRecordingActions({
17013
17430
  recordingState: {
17431
+ socket: state.socket,
17014
17432
  stream: state.stream,
17015
17433
  audioContext: state.audioContext,
17016
17434
  animationFrameId: state.animationFrameId,
@@ -17044,8 +17462,322 @@ const useAudioRecording = (options = {}) => {
17044
17462
  currentSocketRef,
17045
17463
  segmentsRef
17046
17464
  },
17047
- toastText
17465
+ toastText,
17466
+ noMicToast
17048
17467
  });
17468
+ const micDisconnectedText = formatMessage({
17469
+ id: "Microphone disconnected",
17470
+ defaultMessage: "Microphone disconnected"
17471
+ });
17472
+ const micDisconnectedMessage = formatMessage({
17473
+ id: "The microphone was disconnected during recording",
17474
+ defaultMessage: "The microphone was disconnected during recording"
17475
+ });
17476
+ const micReconnectedText = formatMessage({
17477
+ id: "Microphone reconnected",
17478
+ defaultMessage: "Microphone reconnected"
17479
+ });
17480
+ const recordingInterruptedText = formatMessage({
17481
+ id: "Recording interrupted",
17482
+ defaultMessage: "Recording interrupted"
17483
+ });
17484
+ const recordingInterruptedMessage = formatMessage({
17485
+ id: "The server closed the session unexpectedly. Please start recording again.",
17486
+ defaultMessage: "The server closed the session unexpectedly. Please start recording again."
17487
+ });
17488
+ const micFallbackText = formatMessage({
17489
+ id: "Microphone changed",
17490
+ defaultMessage: "Microphone changed"
17491
+ });
17492
+ const micFallbackMessage = formatMessage({
17493
+ id: "Your selected microphone was unavailable. Recording continues with the default microphone.",
17494
+ defaultMessage: "Your selected microphone was unavailable. Recording continues with the default microphone."
17495
+ });
17496
+ React.useEffect(() => {
17497
+ handleServerCloseRef.current = (event) => {
17498
+ if (state.isStoppingRef.current) return;
17499
+ logger.warn(
17500
+ `Server closed WebSocket unexpectedly (code: ${event.code}) — stopping session`
17501
+ );
17502
+ stopCapture();
17503
+ setToastState({
17504
+ variation: ToastVariation.danger,
17505
+ title: recordingInterruptedText,
17506
+ message: recordingInterruptedMessage,
17507
+ visible: true,
17508
+ duration: 0
17509
+ });
17510
+ };
17511
+ }, [
17512
+ stopCapture,
17513
+ state.isStoppingRef,
17514
+ setToastState,
17515
+ recordingInterruptedText,
17516
+ recordingInterruptedMessage
17517
+ ]);
17518
+ const selectedDeviceRef = React.useRef(selectedDevice);
17519
+ React.useEffect(() => {
17520
+ selectedDeviceRef.current = selectedDevice;
17521
+ }, [selectedDevice]);
17522
+ const lastRecoveryAtRef = React.useRef(0);
17523
+ const RECOVERY_LOOP_GUARD_MS = 1e4;
17524
+ const didFallbackRef = React.useRef(false);
17525
+ const prevIsRecordingRef = React.useRef(false);
17526
+ React.useEffect(() => {
17527
+ if (isCurrentlyRecording && !prevIsRecordingRef.current) {
17528
+ didFallbackRef.current = false;
17529
+ }
17530
+ prevIsRecordingRef.current = isCurrentlyRecording;
17531
+ }, [isCurrentlyRecording]);
17532
+ React.useEffect(() => {
17533
+ stopOnDeviceLostRef.current = async () => {
17534
+ if (state.isStoppingRef.current) return;
17535
+ if (Date.now() - lastRecoveryAtRef.current < RECOVERY_LOOP_GUARD_MS) {
17536
+ if (!didFallbackRef.current) {
17537
+ logger.warn(
17538
+ "Ghost device detected — attempting to switch to a different microphone before giving up"
17539
+ );
17540
+ try {
17541
+ const fallbackStream = await getMediaStream(null);
17542
+ if (state.isStoppingRef.current) {
17543
+ fallbackStream.getTracks().forEach((t) => t.stop());
17544
+ return;
17545
+ }
17546
+ const fallbackDeviceId = fallbackStream.getAudioTracks()[0]?.getSettings().deviceId;
17547
+ const currentStreamDeviceId = state.stream?.getAudioTracks()[0]?.getSettings().deviceId;
17548
+ if (fallbackDeviceId && fallbackDeviceId !== currentStreamDeviceId) {
17549
+ logger.info(
17550
+ `Switched to fallback microphone (${fallbackDeviceId}) after ghost on ${currentStreamDeviceId}`
17551
+ );
17552
+ didFallbackRef.current = true;
17553
+ lastRecoveryAtRef.current = Date.now();
17554
+ state.setStream(fallbackStream);
17555
+ setToastState({
17556
+ variation: ToastVariation.danger,
17557
+ title: micFallbackText,
17558
+ message: micFallbackMessage,
17559
+ visible: true,
17560
+ duration: 0
17561
+ });
17562
+ return;
17563
+ }
17564
+ fallbackStream.getTracks().forEach((t) => t.stop());
17565
+ logger.warn(
17566
+ "Fallback getMediaStream returned the same ghost device — no alternative available"
17567
+ );
17568
+ } catch (error) {
17569
+ logger.warn(
17570
+ "Fallback getMediaStream failed — no alternative available",
17571
+ error
17572
+ );
17573
+ }
17574
+ } else {
17575
+ logger.warn(
17576
+ "Ghost device detected on the fallback too — stopping recording"
17577
+ );
17578
+ }
17579
+ stopCapture();
17580
+ setToastState({
17581
+ variation: ToastVariation.danger,
17582
+ title: micDisconnectedText,
17583
+ message: micDisconnectedMessage,
17584
+ visible: true,
17585
+ duration: 0
17586
+ });
17587
+ return;
17588
+ }
17589
+ const currentSegments = segmentsRef.current;
17590
+ let lastPhrase = "";
17591
+ for (let i = currentSegments.length - 1; i >= 0; i--) {
17592
+ const texts = currentSegments[i].text;
17593
+ if (texts?.length) {
17594
+ const last = texts[texts.length - 1];
17595
+ if (last?.text) {
17596
+ lastPhrase = last.text.length > 60 ? last.text.substring(0, 60) + "…" : last.text;
17597
+ break;
17598
+ }
17599
+ }
17600
+ }
17601
+ const MAX_RETRIES = 3;
17602
+ const RETRY_DELAY = 2e3;
17603
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
17604
+ await sleep(RETRY_DELAY);
17605
+ if (state.isStoppingRef.current) return;
17606
+ try {
17607
+ const requestedDevice = selectedDeviceRef.current;
17608
+ const newStream = await getMediaStream(requestedDevice);
17609
+ if (state.isStoppingRef.current) {
17610
+ newStream.getTracks().forEach((t) => t.stop());
17611
+ return;
17612
+ }
17613
+ const actualDeviceId = newStream.getAudioTracks()[0]?.getSettings().deviceId;
17614
+ const usedFallback = !!requestedDevice && requestedDevice !== "no-permission" && requestedDevice !== "no-devices" && requestedDevice !== "error" && !!actualDeviceId && actualDeviceId !== requestedDevice;
17615
+ if (usedFallback) {
17616
+ logger.warn(
17617
+ `Selected microphone (${requestedDevice}) unavailable — recording continues with fallback device (${actualDeviceId})`
17618
+ );
17619
+ } else {
17620
+ logger.info(`Microphone recovered after ${attempt + 1} attempt(s)`);
17621
+ }
17622
+ lastRecoveryAtRef.current = Date.now();
17623
+ state.setStream(newStream);
17624
+ if (usedFallback) {
17625
+ setToastState({
17626
+ variation: ToastVariation.danger,
17627
+ title: micFallbackText,
17628
+ message: micFallbackMessage,
17629
+ visible: true,
17630
+ duration: 0
17631
+ });
17632
+ }
17633
+ const reconnectedMessage = lastPhrase ? formatMessage(
17634
+ {
17635
+ id: 'The microphone was briefly disconnected. Part of the recording after "{lastPhrase}" may have been lost.',
17636
+ defaultMessage: 'The microphone was briefly disconnected. Part of the recording after "{lastPhrase}" may have been lost.'
17637
+ },
17638
+ { lastPhrase }
17639
+ ) : formatMessage({
17640
+ id: "The microphone was briefly disconnected. Part of the recording may have been lost.",
17641
+ defaultMessage: "The microphone was briefly disconnected. Part of the recording may have been lost."
17642
+ });
17643
+ setToastState({
17644
+ variation: ToastVariation.default,
17645
+ title: micReconnectedText,
17646
+ message: reconnectedMessage,
17647
+ visible: true,
17648
+ duration: 0
17649
+ });
17650
+ return;
17651
+ } catch {
17652
+ logger.debug(
17653
+ `Microphone recovery attempt ${attempt + 1}/${MAX_RETRIES} failed`
17654
+ );
17655
+ }
17656
+ }
17657
+ if (state.isStoppingRef.current) return;
17658
+ stopCapture();
17659
+ setToastState({
17660
+ variation: ToastVariation.danger,
17661
+ title: micDisconnectedText,
17662
+ message: micDisconnectedMessage,
17663
+ visible: true,
17664
+ duration: 0
17665
+ });
17666
+ };
17667
+ }, [
17668
+ setToastState,
17669
+ micDisconnectedText,
17670
+ micDisconnectedMessage,
17671
+ micReconnectedText,
17672
+ micFallbackText,
17673
+ micFallbackMessage,
17674
+ formatMessage,
17675
+ segmentsRef,
17676
+ state.isStoppingRef,
17677
+ state.setStream,
17678
+ state.stream,
17679
+ stopCapture
17680
+ ]);
17681
+ const connectionLostText = formatMessage({
17682
+ id: "Connection lost",
17683
+ defaultMessage: "Connection lost"
17684
+ });
17685
+ const connectionLostMessage = formatMessage({
17686
+ id: "Internet connection was lost during recording. Audio may not reach the server until the connection is restored.",
17687
+ defaultMessage: "Internet connection was lost during recording. Audio may not reach the server until the connection is restored."
17688
+ });
17689
+ const connectionRestoredText = formatMessage({
17690
+ id: "Connection restored",
17691
+ defaultMessage: "Connection restored"
17692
+ });
17693
+ const connectionRestoredMessage = formatMessage({
17694
+ id: "Internet connection has been restored. Recording continues normally.",
17695
+ defaultMessage: "Internet connection has been restored. Recording continues normally."
17696
+ });
17697
+ const slowConnectionText = formatMessage({
17698
+ id: "Slow connection",
17699
+ defaultMessage: "Slow connection"
17700
+ });
17701
+ const slowConnectionMessage = formatMessage({
17702
+ id: "Your internet connection appears to be slow. Some audio may be delayed.",
17703
+ defaultMessage: "Your internet connection appears to be slow. Some audio may be delayed."
17704
+ });
17705
+ React.useEffect(() => {
17706
+ if (!isCurrentlyRecording) return;
17707
+ const handleOffline = () => {
17708
+ logger.warn("Network offline detected during recording");
17709
+ setToastState({
17710
+ variation: ToastVariation.danger,
17711
+ title: connectionLostText,
17712
+ message: connectionLostMessage,
17713
+ visible: true,
17714
+ duration: 0
17715
+ });
17716
+ };
17717
+ const handleOnline = () => {
17718
+ logger.info("Network back online during recording");
17719
+ setToastState({
17720
+ variation: ToastVariation.default,
17721
+ title: connectionRestoredText,
17722
+ message: connectionRestoredMessage,
17723
+ visible: true,
17724
+ duration: 0
17725
+ });
17726
+ };
17727
+ window.addEventListener("offline", handleOffline);
17728
+ window.addEventListener("online", handleOnline);
17729
+ return () => {
17730
+ window.removeEventListener("offline", handleOffline);
17731
+ window.removeEventListener("online", handleOnline);
17732
+ };
17733
+ }, [
17734
+ isCurrentlyRecording,
17735
+ setToastState,
17736
+ connectionLostText,
17737
+ connectionLostMessage,
17738
+ connectionRestoredText,
17739
+ connectionRestoredMessage
17740
+ ]);
17741
+ React.useEffect(() => {
17742
+ if (!isCurrentlyRecording || !state.socket) return;
17743
+ const GROWING_TICKS_LIMIT = 5;
17744
+ const MIN_BUFFER = 5e3;
17745
+ let prevBuffered = 0;
17746
+ let growingTicks = 0;
17747
+ let warned = false;
17748
+ const interval = setInterval(() => {
17749
+ if (!state.socket || state.socket.readyState !== WebSocket.OPEN) return;
17750
+ if (!navigator.onLine) return;
17751
+ const current = state.socket.bufferedAmount;
17752
+ if (current > prevBuffered && current > MIN_BUFFER) {
17753
+ growingTicks++;
17754
+ if (growingTicks >= GROWING_TICKS_LIMIT && !warned) {
17755
+ warned = true;
17756
+ logger.warn(
17757
+ `WebSocket buffer growing for ${growingTicks}s (${current} bytes) — slow connection`
17758
+ );
17759
+ setToastState({
17760
+ variation: ToastVariation.danger,
17761
+ title: slowConnectionText,
17762
+ message: slowConnectionMessage,
17763
+ visible: true,
17764
+ duration: 5e3
17765
+ });
17766
+ }
17767
+ } else {
17768
+ growingTicks = 0;
17769
+ warned = false;
17770
+ }
17771
+ prevBuffered = current;
17772
+ }, 1e3);
17773
+ return () => clearInterval(interval);
17774
+ }, [
17775
+ isCurrentlyRecording,
17776
+ state.socket,
17777
+ setToastState,
17778
+ slowConnectionText,
17779
+ slowConnectionMessage
17780
+ ]);
17049
17781
  const isDisabled = disabled || connectionLoading || chatMicrophoneActive;
17050
17782
  return {
17051
17783
  stream: state.stream,
@@ -26478,7 +27210,7 @@ class PromptCache {
26478
27210
  const promptCacheSingleton = new PromptCache();
26479
27211
 
26480
27212
  // Update using pnpm bump-version
26481
- const __version__ = "0.5.18";
27213
+ const __version__ = "0.5.19";
26482
27214
 
26483
27215
  // Inlined from https://github.com/flexdinesh/browser-or-node
26484
27216
  let globalEnv;
@@ -29407,6 +30139,24 @@ class Client {
29407
30139
  }
29408
30140
  return outputs;
29409
30141
  }
30142
+ /**
30143
+ * Filter content from new_token events to prevent streaming LLM output
30144
+ * from being uploaded via events.
30145
+ */
30146
+ _filterNewTokenEvents(events) {
30147
+ if (!events || events.length === 0) {
30148
+ return events;
30149
+ }
30150
+ return events.map((event) => {
30151
+ if (event.name === "new_token") {
30152
+ // Remove the kwargs containing the token data
30153
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
30154
+ const { kwargs: _, ...rest } = event;
30155
+ return rest;
30156
+ }
30157
+ return event;
30158
+ });
30159
+ }
29410
30160
  async prepareRunCreateOrUpdateInputs(run) {
29411
30161
  const runParams = { ...run };
29412
30162
  if (runParams.inputs !== undefined) {
@@ -29415,6 +30165,9 @@ class Client {
29415
30165
  if (runParams.outputs !== undefined) {
29416
30166
  runParams.outputs = await this.processOutputs(runParams.outputs);
29417
30167
  }
30168
+ if (runParams.events !== undefined) {
30169
+ runParams.events = this._filterNewTokenEvents(runParams.events);
30170
+ }
29418
30171
  return runParams;
29419
30172
  }
29420
30173
  async _getResponse(path, queryParams) {
@@ -30259,6 +31012,9 @@ class Client {
30259
31012
  if (run.outputs) {
30260
31013
  run.outputs = await this.processOutputs(run.outputs);
30261
31014
  }
31015
+ if (run.events) {
31016
+ run.events = this._filterNewTokenEvents(run.events);
31017
+ }
30262
31018
  // TODO: Untangle types
30263
31019
  const data = { ...run, id: runId };
30264
31020
  if (!this._filterForSampling([data], true).length) {
@@ -141426,8 +142182,21 @@ const MicrophoneButton = ({
141426
142182
  getMicrophones(newStream);
141427
142183
  } catch (error) {
141428
142184
  logger.audioError("Microphone error:", error);
141429
- showErrorToast(setToastState, formatMessage);
141430
- setDeniedPermissionToast(setToastState, toastText);
142185
+ if (isNoMicrophoneError(error)) {
142186
+ setNoMicrophoneToast(
142187
+ setToastState,
142188
+ formatMessage({
142189
+ id: "No microphone available",
142190
+ defaultMessage: "No microphone available"
142191
+ }),
142192
+ formatMessage({
142193
+ id: "Connect a microphone and try again.",
142194
+ defaultMessage: "Connect a microphone and try again."
142195
+ })
142196
+ );
142197
+ } else {
142198
+ setDeniedPermissionToast(setToastState, toastText);
142199
+ }
141431
142200
  }
141432
142201
  }
141433
142202
  };
@@ -142910,30 +143679,52 @@ const Chat$1 = () => {
142910
143679
  return /* @__PURE__ */ jsx$2(ChatView, {});
142911
143680
  };
142912
143681
 
142913
- const Toast = () => {
142914
- const { message, visible, setToastState, title, variation, duration } = useToastContext();
143682
+ const ToastItem = ({ toast, id, removeToast }) => {
142915
143683
  const [show, setShow] = React.useState(false);
143684
+ const dismiss = React.useCallback(() => {
143685
+ setShow(false);
143686
+ setTimeout(() => removeToast(id), 300);
143687
+ }, [removeToast, id]);
142916
143688
  React.useEffect(() => {
142917
- if (visible) {
142918
- setTimeout(() => setShow(true), 10);
142919
- const timer = setTimeout(() => {
142920
- setShow(false);
142921
- setTimeout(() => setToastState(ToastDefaultState), 300);
142922
- }, duration);
142923
- return () => clearTimeout(timer);
143689
+ const showTimer = setTimeout(() => setShow(true), 10);
143690
+ if (toast.duration && toast.duration > 0) {
143691
+ const dismissTimer = setTimeout(dismiss, toast.duration);
143692
+ return () => {
143693
+ clearTimeout(showTimer);
143694
+ clearTimeout(dismissTimer);
143695
+ };
142924
143696
  }
142925
- }, [visible, duration, setToastState]);
142926
- return /* @__PURE__ */ jsx$2(
143697
+ return () => clearTimeout(showTimer);
143698
+ }, [toast.duration, dismiss]);
143699
+ const isPersistent = toast.duration === 0;
143700
+ return /* @__PURE__ */ jsxs(
142927
143701
  "div",
142928
143702
  {
142929
- className: `omniscribe_toast-container ${variation === ToastVariation.danger ? "omniscribe_toast-container-danger" : "omniscribe_toast-container-black"} ${show ? "omniscribe_toast-container-show" : "omniscribe_toast-container-hide"}`,
142930
- children: /* @__PURE__ */ jsxs("div", { className: "omniscribe_toast-subcontainer", children: [
142931
- /* @__PURE__ */ jsx$2("span", { className: "tomniscribe_toast-title", children: title }),
142932
- /* @__PURE__ */ jsx$2("span", { className: "omniscribe_toast-msg", children: message })
142933
- ] })
143703
+ role: "alert",
143704
+ className: `omniscribe_toast-item ${toast.variation === ToastVariation.danger ? "omniscribe_toast-item-danger" : "omniscribe_toast-item-black"} ${show ? "omniscribe_toast-item-show" : "omniscribe_toast-item-hide"}`,
143705
+ children: [
143706
+ /* @__PURE__ */ jsxs("div", { className: "omniscribe_toast-subcontainer", children: [
143707
+ /* @__PURE__ */ jsx$2("span", { className: "omniscribe_toast-title", children: toast.title }),
143708
+ /* @__PURE__ */ jsx$2("span", { className: "omniscribe_toast-msg", children: toast.message })
143709
+ ] }),
143710
+ isPersistent && /* @__PURE__ */ jsx$2(
143711
+ "button",
143712
+ {
143713
+ className: "omniscribe_toast-close",
143714
+ onClick: dismiss,
143715
+ "aria-label": "Close",
143716
+ children: "✕"
143717
+ }
143718
+ )
143719
+ ]
142934
143720
  }
142935
143721
  );
142936
143722
  };
143723
+ const Toast = () => {
143724
+ const { toasts, removeToast } = useToastContext();
143725
+ if (toasts.length === 0) return null;
143726
+ return /* @__PURE__ */ jsx$2("div", { className: "omniscribe_toast-stack", "aria-live": "polite", children: toasts.map((t) => /* @__PURE__ */ jsx$2(ToastItem, { toast: t, id: t.id, removeToast }, t.id)) });
143727
+ };
142937
143728
 
142938
143729
  const ArrowIcon = ({
142939
143730
  className,
@@ -145259,7 +146050,6 @@ const AppSkeleton = () => {
145259
146050
  const WIDTH = "650px";
145260
146051
  const HEIGHT = "665px";
145261
146052
  const App = () => {
145262
- const toast = useToastContext();
145263
146053
  const { isLoading: isSettingsLoading } = useSettingsContext();
145264
146054
  const [isChat, setIsChat] = React.useState(true);
145265
146055
  const [showConfig, setShowConfig] = React.useState(false);
@@ -145297,47 +146087,72 @@ const App = () => {
145297
146087
  }
145298
146088
  ),
145299
146089
  renderContent(),
145300
- toast?.visible && /* @__PURE__ */ jsx$2(Toast, {})
146090
+ /* @__PURE__ */ jsx$2(Toast, {})
145301
146091
  ] }) });
145302
146092
  };
145303
146093
 
146094
+ let nextId = 0;
145304
146095
  const ToastProvider = ({
145305
146096
  toast,
145306
146097
  children
145307
146098
  }) => {
145308
- const [{ duration, visible, title, message, variation }, setToastState] = React.useState({
145309
- title: "",
145310
- message: "",
145311
- visible: false,
145312
- variation: ToastVariation.default,
145313
- duration: 3e3
145314
- });
146099
+ const [toasts, setToasts] = React.useState([]);
146100
+ const addToast = React.useCallback((t) => {
146101
+ if (!t.visible) return;
146102
+ setToasts((prev) => {
146103
+ const isDupe = prev.some(
146104
+ (existing) => existing.title === t.title && existing.message === t.message
146105
+ );
146106
+ if (isDupe && t.duration !== 0) {
146107
+ return prev;
146108
+ }
146109
+ const base = isDupe ? prev.filter(
146110
+ (existing) => !(existing.title === t.title && existing.message === t.message)
146111
+ ) : prev;
146112
+ const id = String(++nextId);
146113
+ const next = [...base, { duration: 3e3, ...t, id }];
146114
+ return next.length > MAX_TOASTS ? next.slice(-MAX_TOASTS) : next;
146115
+ });
146116
+ }, []);
146117
+ const removeToast = React.useCallback((id) => {
146118
+ setToasts((prev) => prev.filter((t) => t.id !== id));
146119
+ }, []);
146120
+ const setToastState = React.useCallback(
146121
+ (action) => {
146122
+ if (action.visible) {
146123
+ addToast(action);
146124
+ }
146125
+ },
146126
+ [addToast]
146127
+ );
145315
146128
  React.useEffect(() => {
145316
146129
  if (toast) {
145317
- const Toast = typeof toast === "string" ? JSON.parse(toast) : toast;
145318
- setToastState({
145319
- title: Toast.title,
145320
- message: Toast.message,
145321
- visible: Toast.visible,
145322
- variation: Toast.variation,
145323
- duration: Toast.duration ?? 3e3
145324
- });
145325
- }
145326
- }, [toast]);
145327
- return /* @__PURE__ */ jsx$2(
145328
- ToastContext.Provider,
145329
- {
145330
- value: {
145331
- visible,
145332
- title,
145333
- message,
145334
- variation,
145335
- duration,
145336
- setToastState
145337
- },
145338
- children
146130
+ try {
146131
+ const parsed = typeof toast === "string" ? JSON.parse(toast) : toast;
146132
+ if (parsed.visible) {
146133
+ addToast({
146134
+ title: parsed.title ?? "",
146135
+ message: parsed.message ?? "",
146136
+ visible: true,
146137
+ variation: parsed.variation ?? ToastVariation.default,
146138
+ duration: parsed.duration ?? 3e3
146139
+ });
146140
+ }
146141
+ } catch {
146142
+ logger.warn("ToastProvider: invalid JSON in toast prop");
146143
+ }
145339
146144
  }
146145
+ }, [toast, addToast]);
146146
+ const contextValue = React.useMemo(
146147
+ () => ({
146148
+ toasts,
146149
+ addToast,
146150
+ removeToast,
146151
+ setToastState
146152
+ }),
146153
+ [toasts, addToast, removeToast, setToastState]
145340
146154
  );
146155
+ return /* @__PURE__ */ jsx$2(ToastContext.Provider, { value: contextValue, children });
145341
146156
  };
145342
146157
 
145343
146158
  const TranscriptorProvider = ({
@@ -146277,6 +147092,16 @@ function ProcessingThreadsProvider({
146277
147092
  return /* @__PURE__ */ jsx$2(ProcessingThreadsContext.Provider, { value, children });
146278
147093
  }
146279
147094
 
147095
+ const appendClientVersion = (url) => {
147096
+ if (!url) return url;
147097
+ try {
147098
+ const parsed = new URL(url);
147099
+ parsed.searchParams.set("client_version", version$2);
147100
+ return parsed.toString();
147101
+ } catch {
147102
+ return url;
147103
+ }
147104
+ };
146280
147105
  const ApiConfigProvider = ({
146281
147106
  children,
146282
147107
  clientBaseUrl,
@@ -146288,7 +147113,7 @@ const ApiConfigProvider = ({
146288
147113
  }) => {
146289
147114
  const [{ baseUrl, langGraphBaseUrl, apiKey, wssUrl }, setConfig] = React.useState({
146290
147115
  baseUrl: clientBaseUrl,
146291
- wssUrl: clientWssUrl,
147116
+ wssUrl: appendClientVersion(clientWssUrl),
146292
147117
  langGraphBaseUrl: clientBaseUrl + "/lang-graph/",
146293
147118
  apiKey: clientApiKey
146294
147119
  });
@@ -146299,7 +147124,7 @@ const ApiConfigProvider = ({
146299
147124
  if (clientBaseUrl && clientApiKey) {
146300
147125
  setConfig({
146301
147126
  baseUrl: clientBaseUrl,
146302
- wssUrl: clientWssUrl,
147127
+ wssUrl: appendClientVersion(clientWssUrl),
146303
147128
  langGraphBaseUrl: clientBaseUrl + "/lang-graph/",
146304
147129
  apiKey: clientApiKey
146305
147130
  });