@iblai/iblai-js 2.5.5 → 2.5.7
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.
|
@@ -65343,24 +65343,29 @@ var voiceTabLabels$3 = {
|
|
|
65343
65343
|
title: "Agent Voice",
|
|
65344
65344
|
description: "The voice your agent uses to read out replies in chat.",
|
|
65345
65345
|
providerLabel: "Voice source",
|
|
65346
|
-
providerTooltip: "Browser
|
|
65346
|
+
providerTooltip: "Browser and ibl.ai speak on the listener's own device. OpenAI and Google use a custom voice you pick below.",
|
|
65347
65347
|
providerOptions: {
|
|
65348
65348
|
browser: {
|
|
65349
65349
|
label: "Browser",
|
|
65350
65350
|
description: "Speak through the user's own device. No setup needed."
|
|
65351
65351
|
},
|
|
65352
|
+
google: {
|
|
65353
|
+
label: "Google",
|
|
65354
|
+
description: "High-quality voices from Google."
|
|
65355
|
+
},
|
|
65356
|
+
iblai: {
|
|
65357
|
+
label: "ibl.ai",
|
|
65358
|
+
description: "High-quality voices from ibl.ai"
|
|
65359
|
+
},
|
|
65352
65360
|
openai: {
|
|
65353
65361
|
label: "OpenAI",
|
|
65354
65362
|
description: "High-quality voices from OpenAI."
|
|
65355
|
-
},
|
|
65356
|
-
google: {
|
|
65357
|
-
label: "Google",
|
|
65358
|
-
description: "Natural-sounding voices from Google."
|
|
65359
65363
|
}
|
|
65360
65364
|
},
|
|
65361
65365
|
voicePickerLabel: {
|
|
65362
65366
|
openai: "OpenAI voice",
|
|
65363
|
-
google: "Google voice"
|
|
65367
|
+
google: "Google voice",
|
|
65368
|
+
iblai: "ibl.ai voice"
|
|
65364
65369
|
},
|
|
65365
65370
|
searchPlaceholder: "Search voices…",
|
|
65366
65371
|
emptyState: "No voices match your search.",
|
|
@@ -72387,24 +72392,29 @@ var voiceTabLabels$2 = {
|
|
|
72387
72392
|
title: "Voix de l'agent",
|
|
72388
72393
|
description: "La voix utilisée par votre agent pour lire les réponses dans le chat.",
|
|
72389
72394
|
providerLabel: "Source de la voix",
|
|
72390
|
-
providerTooltip: "Le navigateur
|
|
72395
|
+
providerTooltip: "Le navigateur et ibl.ai parlent sur l'appareil de l'auditeur. OpenAI et Google utilisent une voix personnalisée que vous choisissez ci-dessous.",
|
|
72391
72396
|
providerOptions: {
|
|
72392
72397
|
browser: {
|
|
72393
72398
|
label: "Navigateur",
|
|
72394
72399
|
description: "Parle via l'appareil de l'utilisateur. Aucune configuration requise."
|
|
72395
72400
|
},
|
|
72401
|
+
google: {
|
|
72402
|
+
label: "Google",
|
|
72403
|
+
description: "Voix de haute qualité de Google."
|
|
72404
|
+
},
|
|
72405
|
+
iblai: {
|
|
72406
|
+
label: "ibl.ai",
|
|
72407
|
+
description: "Voix de haute qualité d'ibl.ai"
|
|
72408
|
+
},
|
|
72396
72409
|
openai: {
|
|
72397
72410
|
label: "OpenAI",
|
|
72398
72411
|
description: "Voix de haute qualité d'OpenAI."
|
|
72399
|
-
},
|
|
72400
|
-
google: {
|
|
72401
|
-
label: "Google",
|
|
72402
|
-
description: "Voix naturelles de Google."
|
|
72403
72412
|
}
|
|
72404
72413
|
},
|
|
72405
72414
|
voicePickerLabel: {
|
|
72406
72415
|
openai: "Voix OpenAI",
|
|
72407
|
-
google: "Voix Google"
|
|
72416
|
+
google: "Voix Google",
|
|
72417
|
+
iblai: "Voix ibl.ai"
|
|
72408
72418
|
},
|
|
72409
72419
|
searchPlaceholder: "Rechercher des voix…",
|
|
72410
72420
|
emptyState: "Aucune voix ne correspond à votre recherche.",
|
|
@@ -79265,24 +79275,29 @@ var voiceTabLabels$1 = {
|
|
|
79265
79275
|
title: "Voz del agente",
|
|
79266
79276
|
description: "La voz que usa tu agente para leer las respuestas en el chat.",
|
|
79267
79277
|
providerLabel: "Fuente de voz",
|
|
79268
|
-
providerTooltip: "El navegador
|
|
79278
|
+
providerTooltip: "El navegador e ibl.ai hablan en el dispositivo del oyente. OpenAI y Google usan una voz personalizada que eliges a continuación.",
|
|
79269
79279
|
providerOptions: {
|
|
79270
79280
|
browser: {
|
|
79271
79281
|
label: "Navegador",
|
|
79272
79282
|
description: "Habla a través del dispositivo del usuario. No requiere configuración."
|
|
79273
79283
|
},
|
|
79284
|
+
google: {
|
|
79285
|
+
label: "Google",
|
|
79286
|
+
description: "Voces de alta calidad de Google."
|
|
79287
|
+
},
|
|
79288
|
+
iblai: {
|
|
79289
|
+
label: "ibl.ai",
|
|
79290
|
+
description: "Voces de alta calidad de ibl.ai"
|
|
79291
|
+
},
|
|
79274
79292
|
openai: {
|
|
79275
79293
|
label: "OpenAI",
|
|
79276
79294
|
description: "Voces de alta calidad de OpenAI."
|
|
79277
|
-
},
|
|
79278
|
-
google: {
|
|
79279
|
-
label: "Google",
|
|
79280
|
-
description: "Voces naturales de Google."
|
|
79281
79295
|
}
|
|
79282
79296
|
},
|
|
79283
79297
|
voicePickerLabel: {
|
|
79284
79298
|
openai: "Voz de OpenAI",
|
|
79285
|
-
google: "Voz de Google"
|
|
79299
|
+
google: "Voz de Google",
|
|
79300
|
+
iblai: "Voz de ibl.ai"
|
|
79286
79301
|
},
|
|
79287
79302
|
searchPlaceholder: "Buscar voces…",
|
|
79288
79303
|
emptyState: "Ninguna voz coincide con tu búsqueda.",
|
|
@@ -86143,24 +86158,29 @@ var voiceTabLabels = {
|
|
|
86143
86158
|
title: "智能体声音",
|
|
86144
86159
|
description: "您的智能体在聊天中朗读回复时使用的声音。",
|
|
86145
86160
|
providerLabel: "声音来源",
|
|
86146
|
-
providerTooltip: "
|
|
86161
|
+
providerTooltip: "浏览器和 ibl.ai 在听者自己的设备上发声。OpenAI 和 Google 使用您在下方选择的自定义声音。",
|
|
86147
86162
|
providerOptions: {
|
|
86148
86163
|
browser: {
|
|
86149
86164
|
label: "浏览器",
|
|
86150
86165
|
description: "通过用户自己的设备发声,无需任何设置。"
|
|
86151
86166
|
},
|
|
86167
|
+
google: {
|
|
86168
|
+
label: "Google",
|
|
86169
|
+
description: "来自 Google 的高质量声音。"
|
|
86170
|
+
},
|
|
86171
|
+
iblai: {
|
|
86172
|
+
label: "ibl.ai",
|
|
86173
|
+
description: "来自 ibl.ai 的高质量声音"
|
|
86174
|
+
},
|
|
86152
86175
|
openai: {
|
|
86153
86176
|
label: "OpenAI",
|
|
86154
86177
|
description: "来自 OpenAI 的高质量声音。"
|
|
86155
|
-
},
|
|
86156
|
-
google: {
|
|
86157
|
-
label: "Google",
|
|
86158
|
-
description: "来自 Google 的自然声音。"
|
|
86159
86178
|
}
|
|
86160
86179
|
},
|
|
86161
86180
|
voicePickerLabel: {
|
|
86162
86181
|
openai: "OpenAI 声音",
|
|
86163
|
-
google: "Google 声音"
|
|
86182
|
+
google: "Google 声音",
|
|
86183
|
+
iblai: "ibl.ai 语音"
|
|
86164
86184
|
},
|
|
86165
86185
|
searchPlaceholder: "搜索声音…",
|
|
86166
86186
|
emptyState: "没有与您搜索匹配的声音。",
|
|
@@ -192428,7 +192448,7 @@ const defaultTabs = [
|
|
|
192428
192448
|
value: 'monetization',
|
|
192429
192449
|
},*/
|
|
192430
192450
|
];
|
|
192431
|
-
const className = 'inline-flex items-center justify-center whitespace-nowrap rounded-[5px] px-2 sm:px-2.5
|
|
192451
|
+
const className = 'inline-flex h-7 shrink-0 items-center justify-center whitespace-nowrap rounded-[5px] px-2 sm:px-2.5 py-0 text-sm text-gray-500 ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-blue-600 data-[state=active]:font-semibold font-medium data-[state=active]:bg-[#f5f7fb] data-[state=active]:shadow-none cursor-pointer';
|
|
192432
192452
|
function AnalyticsLayout({ children, currentPath, basePath, onTabChange, tabs = defaultTabs, activeTabClassName = '', beforeDataReports, excludeTabs = [], showPicker = false, tenantKey, scopedMentor, onScopedMentorChange, }) {
|
|
192433
192453
|
var _a, _b;
|
|
192434
192454
|
const { currentTenant } = useCurrentTenant();
|
|
@@ -192521,12 +192541,12 @@ function AnalyticsLayout({ children, currentPath, basePath, onTabChange, tabs =
|
|
|
192521
192541
|
return;
|
|
192522
192542
|
onTabChange(tabValue);
|
|
192523
192543
|
};
|
|
192524
|
-
return (jsx(AnalyticsScopeProvider, { value: scopeValue, children: jsxs("div", { className: "flex flex-col flex-1 min-h-0 h-full overflow-hidden overscroll-none bg-[#f5f7fb]", children: [jsx("div", { className: "p-6 pb-0 pt-4
|
|
192544
|
+
return (jsx(AnalyticsScopeProvider, { value: scopeValue, children: jsxs("div", { className: "flex flex-col flex-1 min-h-0 h-full overflow-hidden overscroll-none bg-[#f5f7fb]", children: [jsx("div", { className: "p-6 pb-0 pt-4", children: jsxs("div", { className: "mb-4 flex flex-col gap-2 sm:mb-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3", children: [jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [showPicker && (jsx("div", { className: "shrink-0", children: jsx(AnalyticsAgentPicker, { tenantKey: (_b = tenantKey !== null && tenantKey !== void 0 ? tenantKey : currentTenant === null || currentTenant === void 0 ? void 0 : currentTenant.key) !== null && _b !== void 0 ? _b : '', value: activeScopedMentor, onChange: handleScopedMentorChange }) })), jsx("div", { className: "min-w-0 flex-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: jsx(Tabs, { className: "bg-[#f5f7fb]", value: activeTab, children: jsx(TabsList, { className: "w-max bg-[#f5f7fb]", children: tabs
|
|
192525
192545
|
.filter((tab) => !excludeTabs.includes(tab.value) && canAccessTab(tab.value))
|
|
192526
192546
|
.map((tab) => {
|
|
192527
192547
|
const isActive = activeTab === tab.value;
|
|
192528
192548
|
return (jsx(TabsTrigger, { value: tab.value, className: cn(className, isActive && activeTabClassName), onClick: () => handleTabClick(tab.value), children: tab.label }, tab.value));
|
|
192529
|
-
}) }) })] }), jsxs("div", { className: "flex items-center gap-
|
|
192549
|
+
}) }) }) })] }), jsxs("div", { className: "flex w-full shrink-0 items-center justify-end gap-3 sm:w-auto", children: [beforeDataReports, canAccessTab('reports') && (jsx(Tabs, { className: "bg-[#f5f7fb]", value: activeTab, children: jsx(TabsList, { className: "bg-[#f5f7fb]", children: jsx(TabsTrigger, { value: "reports", className: cn(className, activeTab === 'reports' && activeTabClassName), onClick: () => handleTabClick('reports'), children: "Data Reports" }) }) }))] })] }) }), jsx("div", { className: "flex-1 overflow-auto overscroll-contain scrollbar-none px-6 pb-6 pt-0", children: isActiveTabAllowed ? children : null })] }) }));
|
|
192530
192550
|
}
|
|
192531
192551
|
|
|
192532
192552
|
const accessTimeData = [
|
|
@@ -229627,7 +229647,7 @@ function GroupsFilterDropdown({ groups, selectedGroupIds, onSelectionChange, isL
|
|
|
229627
229647
|
? ((_a = groups.find((g) => g.id === selectedGroupIds[0])) === null || _a === void 0 ? void 0 : _a.name) ||
|
|
229628
229648
|
t('analyticsGroupsFilterDropdown.oneGroup')
|
|
229629
229649
|
: t('analyticsGroupsFilterDropdown.multipleGroups', { count: selectedCount });
|
|
229630
|
-
return (jsxs(Popover, { open: open, onOpenChange: setOpen, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button$1, { variant: "outline", role: "combobox", "aria-expanded": open, className: cn('
|
|
229650
|
+
return (jsxs(Popover, { open: open, onOpenChange: setOpen, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button$1, { variant: "outline", role: "combobox", "aria-expanded": open, className: cn('h-9 max-w-full justify-between border-gray-200 bg-white text-sm font-medium hover:bg-gray-50 sm:min-w-[160px]', selectedCount > 0 && 'border-blue-300 bg-blue-50 hover:bg-blue-100'), disabled: isLoading, children: [jsx("span", { className: "truncate", children: isLoading ? t('analyticsGroupsFilterDropdown.loading') : displayText }), jsxs("div", { className: "flex items-center gap-1 ml-2", children: [selectedCount > 0 && (jsx(X$1, { className: "h-3 w-3 text-gray-400 hover:text-gray-600", onClick: handleClearAll })), jsx(ChevronDown, { className: "h-4 w-4 text-gray-400" })] })] }) }), jsx(PopoverContent, { className: "w-[220px] p-0", align: "start", children: jsx("div", { className: "max-h-[300px] overflow-auto", children: groups.length === 0 ? (jsx("div", { className: "p-3 text-sm text-gray-500 text-center", children: t('analyticsGroupsFilterDropdown.noGroupsAvailable') })) : (jsx("div", { className: "p-1", children: groups.map((group) => {
|
|
229631
229651
|
const isSelected = selectedGroupIds.includes(group.id);
|
|
229632
229652
|
return (jsxs("div", { className: cn('flex items-center gap-2 px-2 py-1.5 cursor-pointer rounded hover:bg-gray-100', isSelected && 'bg-blue-50 hover:bg-blue-100'), onClick: () => handleToggleGroup(group.id), children: [jsx(Checkbox, { checked: isSelected, className: "pointer-events-none" }), jsx("span", { className: "text-sm truncate flex-1", children: group.name }), isSelected && jsx(Check, { className: "h-4 w-4 text-blue-600" })] }, group.id));
|
|
229633
229653
|
}) })) }) })] }));
|
|
@@ -102168,24 +102168,29 @@ var voiceTabLabels$3 = {
|
|
|
102168
102168
|
title: "Agent Voice",
|
|
102169
102169
|
description: "The voice your agent uses to read out replies in chat.",
|
|
102170
102170
|
providerLabel: "Voice source",
|
|
102171
|
-
providerTooltip: "Browser
|
|
102171
|
+
providerTooltip: "Browser and ibl.ai speak on the listener's own device. OpenAI and Google use a custom voice you pick below.",
|
|
102172
102172
|
providerOptions: {
|
|
102173
102173
|
browser: {
|
|
102174
102174
|
label: "Browser",
|
|
102175
102175
|
description: "Speak through the user's own device. No setup needed."
|
|
102176
102176
|
},
|
|
102177
|
+
google: {
|
|
102178
|
+
label: "Google",
|
|
102179
|
+
description: "High-quality voices from Google."
|
|
102180
|
+
},
|
|
102181
|
+
iblai: {
|
|
102182
|
+
label: "ibl.ai",
|
|
102183
|
+
description: "High-quality voices from ibl.ai"
|
|
102184
|
+
},
|
|
102177
102185
|
openai: {
|
|
102178
102186
|
label: "OpenAI",
|
|
102179
102187
|
description: "High-quality voices from OpenAI."
|
|
102180
|
-
},
|
|
102181
|
-
google: {
|
|
102182
|
-
label: "Google",
|
|
102183
|
-
description: "Natural-sounding voices from Google."
|
|
102184
102188
|
}
|
|
102185
102189
|
},
|
|
102186
102190
|
voicePickerLabel: {
|
|
102187
102191
|
openai: "OpenAI voice",
|
|
102188
|
-
google: "Google voice"
|
|
102192
|
+
google: "Google voice",
|
|
102193
|
+
iblai: "ibl.ai voice"
|
|
102189
102194
|
},
|
|
102190
102195
|
searchPlaceholder: "Search voices…",
|
|
102191
102196
|
emptyState: "No voices match your search.",
|
|
@@ -109212,24 +109217,29 @@ var voiceTabLabels$2 = {
|
|
|
109212
109217
|
title: "Voix de l'agent",
|
|
109213
109218
|
description: "La voix utilisée par votre agent pour lire les réponses dans le chat.",
|
|
109214
109219
|
providerLabel: "Source de la voix",
|
|
109215
|
-
providerTooltip: "Le navigateur
|
|
109220
|
+
providerTooltip: "Le navigateur et ibl.ai parlent sur l'appareil de l'auditeur. OpenAI et Google utilisent une voix personnalisée que vous choisissez ci-dessous.",
|
|
109216
109221
|
providerOptions: {
|
|
109217
109222
|
browser: {
|
|
109218
109223
|
label: "Navigateur",
|
|
109219
109224
|
description: "Parle via l'appareil de l'utilisateur. Aucune configuration requise."
|
|
109220
109225
|
},
|
|
109226
|
+
google: {
|
|
109227
|
+
label: "Google",
|
|
109228
|
+
description: "Voix de haute qualité de Google."
|
|
109229
|
+
},
|
|
109230
|
+
iblai: {
|
|
109231
|
+
label: "ibl.ai",
|
|
109232
|
+
description: "Voix de haute qualité d'ibl.ai"
|
|
109233
|
+
},
|
|
109221
109234
|
openai: {
|
|
109222
109235
|
label: "OpenAI",
|
|
109223
109236
|
description: "Voix de haute qualité d'OpenAI."
|
|
109224
|
-
},
|
|
109225
|
-
google: {
|
|
109226
|
-
label: "Google",
|
|
109227
|
-
description: "Voix naturelles de Google."
|
|
109228
109237
|
}
|
|
109229
109238
|
},
|
|
109230
109239
|
voicePickerLabel: {
|
|
109231
109240
|
openai: "Voix OpenAI",
|
|
109232
|
-
google: "Voix Google"
|
|
109241
|
+
google: "Voix Google",
|
|
109242
|
+
iblai: "Voix ibl.ai"
|
|
109233
109243
|
},
|
|
109234
109244
|
searchPlaceholder: "Rechercher des voix…",
|
|
109235
109245
|
emptyState: "Aucune voix ne correspond à votre recherche.",
|
|
@@ -116090,24 +116100,29 @@ var voiceTabLabels$1 = {
|
|
|
116090
116100
|
title: "Voz del agente",
|
|
116091
116101
|
description: "La voz que usa tu agente para leer las respuestas en el chat.",
|
|
116092
116102
|
providerLabel: "Fuente de voz",
|
|
116093
|
-
providerTooltip: "El navegador
|
|
116103
|
+
providerTooltip: "El navegador e ibl.ai hablan en el dispositivo del oyente. OpenAI y Google usan una voz personalizada que eliges a continuación.",
|
|
116094
116104
|
providerOptions: {
|
|
116095
116105
|
browser: {
|
|
116096
116106
|
label: "Navegador",
|
|
116097
116107
|
description: "Habla a través del dispositivo del usuario. No requiere configuración."
|
|
116098
116108
|
},
|
|
116109
|
+
google: {
|
|
116110
|
+
label: "Google",
|
|
116111
|
+
description: "Voces de alta calidad de Google."
|
|
116112
|
+
},
|
|
116113
|
+
iblai: {
|
|
116114
|
+
label: "ibl.ai",
|
|
116115
|
+
description: "Voces de alta calidad de ibl.ai"
|
|
116116
|
+
},
|
|
116099
116117
|
openai: {
|
|
116100
116118
|
label: "OpenAI",
|
|
116101
116119
|
description: "Voces de alta calidad de OpenAI."
|
|
116102
|
-
},
|
|
116103
|
-
google: {
|
|
116104
|
-
label: "Google",
|
|
116105
|
-
description: "Voces naturales de Google."
|
|
116106
116120
|
}
|
|
116107
116121
|
},
|
|
116108
116122
|
voicePickerLabel: {
|
|
116109
116123
|
openai: "Voz de OpenAI",
|
|
116110
|
-
google: "Voz de Google"
|
|
116124
|
+
google: "Voz de Google",
|
|
116125
|
+
iblai: "Voz de ibl.ai"
|
|
116111
116126
|
},
|
|
116112
116127
|
searchPlaceholder: "Buscar voces…",
|
|
116113
116128
|
emptyState: "Ninguna voz coincide con tu búsqueda.",
|
|
@@ -122968,24 +122983,29 @@ var voiceTabLabels = {
|
|
|
122968
122983
|
title: "智能体声音",
|
|
122969
122984
|
description: "您的智能体在聊天中朗读回复时使用的声音。",
|
|
122970
122985
|
providerLabel: "声音来源",
|
|
122971
|
-
providerTooltip: "
|
|
122986
|
+
providerTooltip: "浏览器和 ibl.ai 在听者自己的设备上发声。OpenAI 和 Google 使用您在下方选择的自定义声音。",
|
|
122972
122987
|
providerOptions: {
|
|
122973
122988
|
browser: {
|
|
122974
122989
|
label: "浏览器",
|
|
122975
122990
|
description: "通过用户自己的设备发声,无需任何设置。"
|
|
122976
122991
|
},
|
|
122992
|
+
google: {
|
|
122993
|
+
label: "Google",
|
|
122994
|
+
description: "来自 Google 的高质量声音。"
|
|
122995
|
+
},
|
|
122996
|
+
iblai: {
|
|
122997
|
+
label: "ibl.ai",
|
|
122998
|
+
description: "来自 ibl.ai 的高质量声音"
|
|
122999
|
+
},
|
|
122977
123000
|
openai: {
|
|
122978
123001
|
label: "OpenAI",
|
|
122979
123002
|
description: "来自 OpenAI 的高质量声音。"
|
|
122980
|
-
},
|
|
122981
|
-
google: {
|
|
122982
|
-
label: "Google",
|
|
122983
|
-
description: "来自 Google 的自然声音。"
|
|
122984
123003
|
}
|
|
122985
123004
|
},
|
|
122986
123005
|
voicePickerLabel: {
|
|
122987
123006
|
openai: "OpenAI 声音",
|
|
122988
|
-
google: "Google 声音"
|
|
123007
|
+
google: "Google 声音",
|
|
123008
|
+
iblai: "ibl.ai 语音"
|
|
122989
123009
|
},
|
|
122990
123010
|
searchPlaceholder: "搜索声音…",
|
|
122991
123011
|
emptyState: "没有与您搜索匹配的声音。",
|
|
@@ -272019,23 +272039,28 @@ const AGENT_VOICE_TAB_LABELS = {
|
|
|
272019
272039
|
title: 'Agent Voice',
|
|
272020
272040
|
description: 'The voice your agent uses to read out replies in chat.',
|
|
272021
272041
|
providerLabel: 'Voice source',
|
|
272022
|
-
providerTooltip: "Browser
|
|
272042
|
+
providerTooltip: "Browser and ibl.ai speak on the listener's own device. OpenAI and Google use a custom voice you pick below.",
|
|
272023
272043
|
providerOptions: {
|
|
272024
272044
|
browser: {
|
|
272025
272045
|
label: 'Browser',
|
|
272026
272046
|
description: "Speak through the user's own device. No setup needed.",
|
|
272027
272047
|
},
|
|
272048
|
+
google: {
|
|
272049
|
+
label: 'Google',
|
|
272050
|
+
description: 'High-quality voices from Google.',
|
|
272051
|
+
},
|
|
272052
|
+
iblai: {
|
|
272053
|
+
label: 'ibl.ai',
|
|
272054
|
+
description: 'High-quality voices from ibl.ai',
|
|
272055
|
+
},
|
|
272028
272056
|
openai: {
|
|
272029
272057
|
label: 'OpenAI',
|
|
272030
272058
|
description: 'High-quality voices from OpenAI.',
|
|
272031
272059
|
},
|
|
272032
|
-
google: {
|
|
272033
|
-
label: 'Google',
|
|
272034
|
-
description: 'Natural-sounding voices from Google.',
|
|
272035
|
-
},
|
|
272036
272060
|
},
|
|
272037
272061
|
voicePickerLabel: {
|
|
272038
272062
|
openai: 'OpenAI voice',
|
|
272063
|
+
iblai: 'ibl.ai voice',
|
|
272039
272064
|
google: 'Google voice',
|
|
272040
272065
|
},
|
|
272041
272066
|
searchPlaceholder: 'Search voices…',
|
|
@@ -272144,18 +272169,23 @@ function buildVoiceTabLabels(t) {
|
|
|
272144
272169
|
label: t('voiceTabLabels.mentorVoice.providerOptions.browser.label'),
|
|
272145
272170
|
description: t('voiceTabLabels.mentorVoice.providerOptions.browser.description'),
|
|
272146
272171
|
},
|
|
272147
|
-
openai: {
|
|
272148
|
-
label: t('voiceTabLabels.mentorVoice.providerOptions.openai.label'),
|
|
272149
|
-
description: t('voiceTabLabels.mentorVoice.providerOptions.openai.description'),
|
|
272150
|
-
},
|
|
272151
272172
|
google: {
|
|
272152
272173
|
label: t('voiceTabLabels.mentorVoice.providerOptions.google.label'),
|
|
272153
272174
|
description: t('voiceTabLabels.mentorVoice.providerOptions.google.description'),
|
|
272154
272175
|
},
|
|
272176
|
+
iblai: {
|
|
272177
|
+
label: t('voiceTabLabels.mentorVoice.providerOptions.iblai.label'),
|
|
272178
|
+
description: t('voiceTabLabels.mentorVoice.providerOptions.iblai.description'),
|
|
272179
|
+
},
|
|
272180
|
+
openai: {
|
|
272181
|
+
label: t('voiceTabLabels.mentorVoice.providerOptions.openai.label'),
|
|
272182
|
+
description: t('voiceTabLabels.mentorVoice.providerOptions.openai.description'),
|
|
272183
|
+
},
|
|
272155
272184
|
},
|
|
272156
272185
|
voicePickerLabel: {
|
|
272157
272186
|
openai: t('voiceTabLabels.mentorVoice.voicePickerLabel.openai'),
|
|
272158
272187
|
google: t('voiceTabLabels.mentorVoice.voicePickerLabel.google'),
|
|
272188
|
+
iblai: t('voiceTabLabels.mentorVoice.voicePickerLabel.iblai'),
|
|
272159
272189
|
},
|
|
272160
272190
|
searchPlaceholder: t('voiceTabLabels.mentorVoice.searchPlaceholder'),
|
|
272161
272191
|
emptyState: t('voiceTabLabels.mentorVoice.emptyState'),
|
|
@@ -272322,6 +272352,46 @@ function toTitleCase(name) {
|
|
|
272322
272352
|
.map((word) => (word.length > 0 ? word[0].toUpperCase() + word.slice(1).toLowerCase() : word))
|
|
272323
272353
|
.join(' ');
|
|
272324
272354
|
}
|
|
272355
|
+
/**
|
|
272356
|
+
* On-device catalogue rows are stored under the model's own voice id -- two
|
|
272357
|
+
* letters of accent+gender, an underscore, then the voice's name
|
|
272358
|
+
* (`af_heart`, `bm_george`, `ef_dora`). Only the name is shown.
|
|
272359
|
+
*
|
|
272360
|
+
* Derived rather than mapped: the catalogue carries 50+ voices across several
|
|
272361
|
+
* languages and grows whenever the model does, so a hand-written table would
|
|
272362
|
+
* silently fall back to raw ids for everything it had not caught up with --
|
|
272363
|
+
* which is exactly what it did.
|
|
272364
|
+
*/
|
|
272365
|
+
const KOKORO_VOICE_ID = /^[a-z]{2}_([a-z]+)$/;
|
|
272366
|
+
/**
|
|
272367
|
+
* What to show for a voice. Only the on-device provider needs unpacking;
|
|
272368
|
+
* every other catalogue already stores something human-readable, so it just
|
|
272369
|
+
* gets its casing normalised.
|
|
272370
|
+
*
|
|
272371
|
+
* A name that does not match the id shape falls through to the raw value, so
|
|
272372
|
+
* an unexpected row still renders instead of showing blank.
|
|
272373
|
+
*/
|
|
272374
|
+
function voiceDisplayName(voice) {
|
|
272375
|
+
var _a;
|
|
272376
|
+
const name = (_a = voice.name) !== null && _a !== void 0 ? _a : '';
|
|
272377
|
+
if (isIblaiVoice(voice)) {
|
|
272378
|
+
const match = KOKORO_VOICE_ID.exec(name);
|
|
272379
|
+
if (match)
|
|
272380
|
+
return toTitleCase(match[1]);
|
|
272381
|
+
}
|
|
272382
|
+
return toTitleCase(name);
|
|
272383
|
+
}
|
|
272384
|
+
/**
|
|
272385
|
+
* Whether a catalogue row is an on-device (Kokoro) voice.
|
|
272386
|
+
*
|
|
272387
|
+
* The cast is deliberate: `VoiceProviderEnum` in the generated API client
|
|
272388
|
+
* predates this provider, so a direct comparison is a type error even though
|
|
272389
|
+
* the backend already returns `"iblai"` -- the same lag that leaves
|
|
272390
|
+
* `voice_provider` and `iblai_voice` off the generated mentor settings type.
|
|
272391
|
+
*/
|
|
272392
|
+
function isIblaiVoice(voice) {
|
|
272393
|
+
return voice.provider === 'iblai';
|
|
272394
|
+
}
|
|
272325
272395
|
|
|
272326
272396
|
const PAGE_SIZE$2 = 20;
|
|
272327
272397
|
function VoicePicker({ org, userId, provider, selectedVoiceId, onSelect, disabled, labels, }) {
|
|
@@ -272367,10 +272437,36 @@ function VoicePicker({ org, userId, provider, selectedVoiceId, onSelect, disable
|
|
|
272367
272437
|
!voices.some((v) => v.id === pinnedVoice.id);
|
|
272368
272438
|
const { playingId, isLoading: isPreviewLoading, play, stop } = useAudioPreview();
|
|
272369
272439
|
React__default.useEffect(() => () => stop(), [provider, stop]);
|
|
272370
|
-
|
|
272371
|
-
|
|
272372
|
-
|
|
272373
|
-
|
|
272440
|
+
/**
|
|
272441
|
+
* Width the scrollbar takes out of the list's content box.
|
|
272442
|
+
*
|
|
272443
|
+
* Measured rather than assumed: it is ~15px on Windows and most Linux
|
|
272444
|
+
* desktops but 0 wherever the platform draws overlay scrollbars (macOS by
|
|
272445
|
+
* default), so a hardcoded value would misalign the list on half the
|
|
272446
|
+
* machines that see it. Used below to pull the list's right edge out by
|
|
272447
|
+
* exactly that much, so the cards line up with the search box above them
|
|
272448
|
+
* and the scrollbar sits in the dialog's own padding.
|
|
272449
|
+
*/
|
|
272450
|
+
const listRef = React__default.useRef(null);
|
|
272451
|
+
const [scrollbarWidth, setScrollbarWidth] = React__default.useState(0);
|
|
272452
|
+
React__default.useLayoutEffect(() => {
|
|
272453
|
+
const el = listRef.current;
|
|
272454
|
+
if (!el)
|
|
272455
|
+
return;
|
|
272456
|
+
const measure = () => setScrollbarWidth(el.offsetWidth - el.clientWidth);
|
|
272457
|
+
measure();
|
|
272458
|
+
const observer = new ResizeObserver(measure);
|
|
272459
|
+
observer.observe(el);
|
|
272460
|
+
return () => observer.disconnect();
|
|
272461
|
+
}, [voices.length]);
|
|
272462
|
+
return (jsxs("div", { className: "space-y-3", children: [jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" }), jsx(Input, { type: "search", placeholder: labels.searchPlaceholder, className: "w-full pl-9", value: search, onChange: (e) => setSearch(e.target.value), disabled: disabled, "aria-label": labels.searchPlaceholder, "data-testid": "voice-picker-search" })] }), isError && jsx("p", { className: "text-sm text-red-500", children: labels.errorState }), isFetching ? (jsx("div", { className: "flex items-center justify-center py-8", children: jsx(Spinner, {}) })) : voices.length === 0 && !showPinned ? (jsx("div", { className: "rounded-md border border-dashed border-gray-200 py-8 text-center text-sm text-gray-500", children: labels.emptyState })) : (jsxs(Fragment$1, { children: [showPinned && pinnedVoice && (jsx("div", { "data-testid": "pinned-voice-row", children: jsx(VoiceRow, { voice: pinnedVoice, isSelected: true, isPlaying: playingId === pinnedVoice.id, showPlayLoader: playingId === pinnedVoice.id && isPreviewLoading, disabled: !!disabled, onSelect: onSelect, play: play, labels: labels }) })), jsx("div", { ref: listRef,
|
|
272463
|
+
// Vertical padding keeps the selected card's border from being
|
|
272464
|
+
// clipped by `overflow-y-auto` on the first and last rows. There is
|
|
272465
|
+
// deliberately no horizontal padding: the cards are meant to line
|
|
272466
|
+
// up with the search box, and the negative margin below moves the
|
|
272467
|
+
// scrollbar out of that alignment rather than letting it eat into
|
|
272468
|
+
// the last column.
|
|
272469
|
+
className: "grid max-h-[360px] grid-cols-1 gap-2 overflow-y-auto py-1 sm:grid-cols-2", style: { marginRight: -scrollbarWidth }, role: "radiogroup", "aria-label": t('voiceTabVoicePicker.availableVoicesAriaLabel'), children: voices.map((voice) => (jsx(VoiceRow, { voice: voice, isSelected: selectedVoiceId === voice.id, isPlaying: playingId === voice.id, showPlayLoader: playingId === voice.id && isPreviewLoading, disabled: !!disabled, onSelect: onSelect, play: play, labels: labels }, voice.id))) }), totalPages > 1 && (jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-gray-100 pt-3 text-xs text-gray-600", "data-testid": "voice-pagination", children: [jsxs("span", { "aria-live": "polite", children: [t('voiceTabVoicePicker.paginationPage'), " ", jsx("strong", { children: page }), ' ', t('voiceTabVoicePicker.paginationOf'), " ", jsx("strong", { children: totalPages }), jsx("span", { className: "ml-1 text-gray-400", children: t('voiceTabVoicePicker.paginationVoiceCount', { total }) })] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs(Button$1, { type: "button", variant: "outline", size: "sm", disabled: disabled || page <= 1, onClick: () => setPage((p) => Math.max(1, p - 1)), "aria-label": t('voiceTabVoicePicker.prevPageAriaLabel'), "data-testid": "voice-prev-page", children: [jsx(ChevronLeft, { className: "h-4 w-4" }), t('voiceTabVoicePicker.prevButton')] }), jsxs(Button$1, { type: "button", variant: "outline", size: "sm", disabled: disabled || page >= totalPages, onClick: () => setPage((p) => Math.min(totalPages, p + 1)), "aria-label": t('voiceTabVoicePicker.nextPageAriaLabel'), "data-testid": "voice-next-page", children: [t('voiceTabVoicePicker.nextButton'), jsx(ChevronRight, { className: "h-4 w-4" })] })] })] }))] }))] }));
|
|
272374
272470
|
}
|
|
272375
272471
|
/**
|
|
272376
272472
|
* A single voice row. Rendered as a `<div role="radio">` so the inner
|
|
@@ -272381,19 +272477,24 @@ function VoicePicker({ org, userId, provider, selectedVoiceId, onSelect, disable
|
|
|
272381
272477
|
* clipped by the parent scroll container's `overflow-y-auto`.
|
|
272382
272478
|
*/
|
|
272383
272479
|
function VoiceRow({ voice, isSelected, isPlaying, showPlayLoader, disabled, onSelect, play, labels, }) {
|
|
272384
|
-
|
|
272480
|
+
// Whether a second line renders under the name. It decides the cross-axis
|
|
272481
|
+
// alignment: with a description the 36px badge should sit level with the
|
|
272482
|
+
// first line (`items-start`), but on a single-line card that pushes the
|
|
272483
|
+
// badge 4px below the name and preview button, which reads as crooked.
|
|
272484
|
+
const hasMeta = !isIblaiVoice(voice) && Boolean(voice.language || voice.description);
|
|
272485
|
+
return (jsxs("div", { role: "radio", tabIndex: disabled ? -1 : 0, "aria-checked": isSelected, "aria-disabled": disabled || undefined, "aria-label": voiceDisplayName(voice), "data-voice-id": voice.id, "data-selected": isSelected || undefined, onClick: () => !disabled && onSelect(voice), onKeyDown: (e) => {
|
|
272385
272486
|
if (disabled)
|
|
272386
272487
|
return;
|
|
272387
272488
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
272388
272489
|
e.preventDefault();
|
|
272389
272490
|
onSelect(voice);
|
|
272390
272491
|
}
|
|
272391
|
-
}, className: cn('flex cursor-pointer
|
|
272492
|
+
}, className: cn('flex cursor-pointer gap-3 rounded-lg border-2 p-3 text-left transition-all hover:shadow-sm focus-visible:outline-blue-500', hasMeta ? 'items-start' : 'items-center', isSelected
|
|
272392
272493
|
? 'border-blue-500 bg-blue-50/40'
|
|
272393
|
-
: 'border-gray-200 bg-white hover:border-gray-300', disabled && 'cursor-not-allowed opacity-60'), children: [jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-600", children: jsx(Volume2, { className: "h-4 w-4", "aria-hidden": "true" }) }), jsxs("div", { className: "min-w-0 flex-1", children: [jsxs("div", { className: "flex items-center justify-between gap-2", children: [jsx("span", { className: "truncate text-sm font-medium text-gray-900", children:
|
|
272394
|
-
|
|
272395
|
-
|
|
272396
|
-
|
|
272494
|
+
: 'border-gray-200 bg-white hover:border-gray-300', disabled && 'cursor-not-allowed opacity-60'), children: [jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-600", children: jsx(Volume2, { className: "h-4 w-4", "aria-hidden": "true" }) }), jsxs("div", { className: "min-w-0 flex-1", children: [jsxs("div", { className: "flex items-center justify-between gap-2", children: [jsx("span", { className: "truncate text-sm font-medium text-gray-900", children: voiceDisplayName(voice) }), jsx("span", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center", children: voice.audio_url && (jsx(Button$1, { type: "button", variant: "ghost", size: "icon", className: "h-7 w-7 flex-shrink-0 text-gray-500 hover:text-blue-600", onClick: (e) => {
|
|
272495
|
+
e.stopPropagation();
|
|
272496
|
+
play(voice.id, voice.audio_url);
|
|
272497
|
+
}, disabled: disabled, "aria-label": labels.previewAria(voiceDisplayName(voice), isPlaying ? 'stop' : 'play'), children: showPlayLoader ? (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" })) : isPlaying ? (jsx(Pause, { className: "h-4 w-4" })) : (jsx(Play, { className: "h-4 w-4" })) })) })] }), hasMeta && (jsx("p", { className: "mt-1 line-clamp-2 text-xs text-gray-500", children: [voice.language, voice.description].filter(Boolean).join(' · ') }))] })] }));
|
|
272397
272498
|
}
|
|
272398
272499
|
|
|
272399
272500
|
/**
|
|
@@ -272433,8 +272534,10 @@ function VoicePickerTrigger({ org, userId, provider, selectedVoice, onSelect, di
|
|
|
272433
272534
|
if (isOpen)
|
|
272434
272535
|
stop();
|
|
272435
272536
|
}, [isOpen, stop]);
|
|
272436
|
-
const displayName = selectedVoice ?
|
|
272437
|
-
|
|
272537
|
+
const displayName = selectedVoice ? voiceDisplayName(selectedVoice) : null;
|
|
272538
|
+
// See the note in voice-picker.tsx: the on-device rows all carry the same
|
|
272539
|
+
// locale, so showing it adds nothing.
|
|
272540
|
+
const language = selectedVoice && isIblaiVoice(selectedVoice) ? null : (selectedVoice === null || selectedVoice === void 0 ? void 0 : selectedVoice.language) || null;
|
|
272438
272541
|
const audioUrl = (selectedVoice === null || selectedVoice === void 0 ? void 0 : selectedVoice.audio_url) || '';
|
|
272439
272542
|
const isPlaying = selectedVoice != null && playingId === selectedVoice.id;
|
|
272440
272543
|
const showPlayLoader = isPlaying && isPreviewLoading;
|
|
@@ -272738,6 +272841,29 @@ function PromptCard$1({ label, placeholder, value, disabled, renderContent, onEd
|
|
|
272738
272841
|
return (jsxs("div", { className: "overflow-hidden rounded-lg bg-gray-50", "data-testid": testId, children: [jsx("div", { className: "flex items-center justify-between border-b border-gray-200 p-[1.12rem]", children: jsxs("div", { className: "flex items-center gap-2", children: [jsx("h3", { className: "text-sm font-medium text-gray-900", children: label }), headerExtra] }) }), jsxs("div", { className: "flex items-start gap-2 p-4", children: [jsx("div", { className: "mt-1 flex-shrink-0", "aria-hidden": "true", children: "\uD83D\uDCDD" }), jsx("div", { className: "flex h-[180px] flex-1 flex-col", children: jsx("div", { className: "mb-4 flex-grow overflow-y-auto", tabIndex: 0, role: "region", "aria-label": `${label} content`, children: isEmpty ? (jsx("p", { className: "text-sm text-gray-400 italic", children: placeholder })) : (renderContent(value)) }) })] }), jsxs("div", { className: "flex w-full gap-2 px-4 pb-4", children: [jsxs(Button$1, { variant: "outline", size: "sm", className: "h-8 flex-1 py-5", disabled: disabled, onClick: onEdit, "aria-label": `Edit ${label}`, children: [jsx(SquarePen, { className: "mr-2 h-4 w-4" }), "Edit"] }), jsx(CopyButton, { text: value, disabled: disabled })] })] }));
|
|
272739
272842
|
}
|
|
272740
272843
|
|
|
272844
|
+
/**
|
|
272845
|
+
* Providers whose voice comes from the server-side voices catalogue, and so
|
|
272846
|
+
* need a voice picker plus a voice id on save. Only `browser` is excluded: it
|
|
272847
|
+
* speaks through whatever voice the listener's own OS provides, which we
|
|
272848
|
+
* neither choose nor know.
|
|
272849
|
+
*
|
|
272850
|
+
* `iblai` synthesises on-device but still picks from the catalogue, because
|
|
272851
|
+
* the backend stores `iblai_voice` as a foreign key into the same table --
|
|
272852
|
+
* sending it a voice name is rejected with "A valid integer is required".
|
|
272853
|
+
*/
|
|
272854
|
+
const CATALOGUE_VOICE_PROVIDERS = ['openai', 'google', 'iblai'];
|
|
272855
|
+
/**
|
|
272856
|
+
* Voice used when a mentor has never had one chosen.
|
|
272857
|
+
*
|
|
272858
|
+
* Must stay in step with the consuming app's own fallback (mentorai's
|
|
272859
|
+
* `DEFAULT_VOICE` in `lib/tts/config.ts`): the agent speaks that voice whether
|
|
272860
|
+
* or not anything is saved, so showing an empty picker would tell the admin
|
|
272861
|
+
* "nothing selected" while the listener hears Heart.
|
|
272862
|
+
*/
|
|
272863
|
+
const DEFAULT_IBLAI_VOICE = 'af_heart';
|
|
272864
|
+
function usesVoiceCatalogue(provider) {
|
|
272865
|
+
return CATALOGUE_VOICE_PROVIDERS.includes(provider);
|
|
272866
|
+
}
|
|
272741
272867
|
const SUB_TAB_VALUES = {
|
|
272742
272868
|
voice: 'voice',
|
|
272743
272869
|
callConfig: 'call-config',
|
|
@@ -272764,7 +272890,7 @@ function readVoiceName(value) {
|
|
|
272764
272890
|
return null;
|
|
272765
272891
|
}
|
|
272766
272892
|
function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', renderPromptContent, tenantKey: tenantKeyProp, mentorId: mentorIdProp, username: usernameProp, enableRBAC: enableRBACProp, }) {
|
|
272767
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
272893
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
272768
272894
|
const ctx = useAgentSettingsOptional();
|
|
272769
272895
|
const tenantKey = (_a = tenantKeyProp !== null && tenantKeyProp !== void 0 ? tenantKeyProp : ctx === null || ctx === void 0 ? void 0 : ctx.tenantKey) !== null && _a !== void 0 ? _a : '';
|
|
272770
272896
|
const mentorId = (_b = mentorIdProp !== null && mentorIdProp !== void 0 ? mentorIdProp : ctx === null || ctx === void 0 ? void 0 : ctx.mentorId) !== null && _b !== void 0 ? _b : '';
|
|
@@ -272786,6 +272912,10 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272786
272912
|
// (with audio_url for the play button) and send the id on save.
|
|
272787
272913
|
const savedOpenaiName = (_f = readVoiceName(mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.openai_voice)) !== null && _f !== void 0 ? _f : null;
|
|
272788
272914
|
const savedGoogleName = (_g = readVoiceName(mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.google_voice)) !== null && _g !== void 0 ? _g : null;
|
|
272915
|
+
// Falls back to the default rather than null so the trigger shows the voice
|
|
272916
|
+
// that will actually be used. Nothing is written to the mentor by this: an
|
|
272917
|
+
// unsaved default and an explicitly-chosen Heart behave identically.
|
|
272918
|
+
const savedIblaiName = (_h = readVoiceName(mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.iblai_voice)) !== null && _h !== void 0 ? _h : DEFAULT_IBLAI_VOICE;
|
|
272789
272919
|
const { voice: resolvedOpenaiVoice } = useResolveVoiceByName({
|
|
272790
272920
|
org: tenantKey,
|
|
272791
272921
|
userId: username,
|
|
@@ -272798,28 +272928,37 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272798
272928
|
provider: 'google',
|
|
272799
272929
|
name: savedGoogleName,
|
|
272800
272930
|
});
|
|
272931
|
+
const { voice: resolvedIblaiVoice } = useResolveVoiceByName({
|
|
272932
|
+
org: tenantKey,
|
|
272933
|
+
userId: username,
|
|
272934
|
+
provider: 'iblai',
|
|
272935
|
+
name: savedIblaiName,
|
|
272936
|
+
});
|
|
272801
272937
|
const savedInstructions = typeof (mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.voice_instructions) === 'string' ? mentorSettings.voice_instructions : '';
|
|
272802
272938
|
// Picked voices override the resolved server values once the user
|
|
272803
272939
|
// makes a choice. Null means "use what came from the server".
|
|
272804
272940
|
const [provider, setProvider] = React__default.useState(initialProvider);
|
|
272805
272941
|
const [pickedOpenaiVoice, setPickedOpenaiVoice] = React__default.useState(null);
|
|
272806
272942
|
const [pickedGoogleVoice, setPickedGoogleVoice] = React__default.useState(null);
|
|
272943
|
+
const [pickedIblaiVoice, setPickedIblaiVoice] = React__default.useState(null);
|
|
272807
272944
|
const [instructions, setInstructions] = React__default.useState(savedInstructions);
|
|
272808
272945
|
const [selectedPrompt, setSelectedPrompt] = React__default.useState(null);
|
|
272809
272946
|
React__default.useEffect(() => {
|
|
272810
272947
|
setProvider(initialProvider);
|
|
272811
272948
|
setPickedOpenaiVoice(null);
|
|
272812
272949
|
setPickedGoogleVoice(null);
|
|
272950
|
+
setPickedIblaiVoice(null);
|
|
272813
272951
|
setInstructions(savedInstructions);
|
|
272814
272952
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
272815
272953
|
}, [mentorId, rawMentorSettings]);
|
|
272816
|
-
const effectiveOpenaiVoice = (
|
|
272817
|
-
const effectiveGoogleVoice = (
|
|
272954
|
+
const effectiveOpenaiVoice = (_j = pickedOpenaiVoice !== null && pickedOpenaiVoice !== void 0 ? pickedOpenaiVoice : resolvedOpenaiVoice) !== null && _j !== void 0 ? _j : null;
|
|
272955
|
+
const effectiveGoogleVoice = (_k = pickedGoogleVoice !== null && pickedGoogleVoice !== void 0 ? pickedGoogleVoice : resolvedGoogleVoice) !== null && _k !== void 0 ? _k : null;
|
|
272956
|
+
const effectiveIblaiVoice = (_l = pickedIblaiVoice !== null && pickedIblaiVoice !== void 0 ? pickedIblaiVoice : resolvedIblaiVoice) !== null && _l !== void 0 ? _l : null;
|
|
272818
272957
|
const [editMentor, { isLoading: isSavingMentor }] = useEditMentorMutation();
|
|
272819
272958
|
// "Enable voice calls" master toggle. Optimistic local mirror of the
|
|
272820
272959
|
// server value so the switch responds instantly; re-synced whenever the
|
|
272821
272960
|
// settings response changes.
|
|
272822
|
-
const serverVoiceCallEnabled = (
|
|
272961
|
+
const serverVoiceCallEnabled = (_m = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.show_voice_call) !== null && _m !== void 0 ? _m : true;
|
|
272823
272962
|
const [voiceCallEnabled, setVoiceCallEnabled] = React__default.useState(serverVoiceCallEnabled);
|
|
272824
272963
|
const [isTogglingVoiceCall, setIsTogglingVoiceCall] = React__default.useState(false);
|
|
272825
272964
|
React__default.useEffect(() => {
|
|
@@ -272855,33 +272994,48 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272855
272994
|
const isDirty = provider !== initialProvider ||
|
|
272856
272995
|
pickedOpenaiVoice != null ||
|
|
272857
272996
|
pickedGoogleVoice != null ||
|
|
272997
|
+
pickedIblaiVoice != null ||
|
|
272858
272998
|
instructionsDirty;
|
|
272859
272999
|
/**
|
|
272860
|
-
* Provider chips
|
|
272861
|
-
*
|
|
272862
|
-
*
|
|
272863
|
-
* for
|
|
273000
|
+
* Provider chips, ordered by lowercasing each label and then sorting
|
|
273001
|
+
* alphabetically, so a newly added provider slots itself in without a
|
|
273002
|
+
* hand-edit. We reuse the vendor logos already shipped in the host's
|
|
273003
|
+
* static-root (see `getLLMProviderDetails` for the canonical paths).
|
|
273004
|
+
* Browser falls back to a Lucide globe — there's no vendor logo for
|
|
273005
|
+
* "device speech engine".
|
|
272864
273006
|
*/
|
|
272865
|
-
const providerOptions =
|
|
272866
|
-
|
|
272867
|
-
|
|
272868
|
-
|
|
272869
|
-
|
|
272870
|
-
|
|
272871
|
-
|
|
272872
|
-
|
|
272873
|
-
|
|
272874
|
-
|
|
272875
|
-
|
|
272876
|
-
|
|
272877
|
-
|
|
272878
|
-
|
|
272879
|
-
|
|
272880
|
-
|
|
272881
|
-
|
|
272882
|
-
|
|
272883
|
-
|
|
272884
|
-
|
|
273007
|
+
const providerOptions = React__default.useMemo(() => {
|
|
273008
|
+
const options = [
|
|
273009
|
+
{
|
|
273010
|
+
value: 'browser',
|
|
273011
|
+
label: labels.mentorVoice.providerOptions.browser.label,
|
|
273012
|
+
description: labels.mentorVoice.providerOptions.browser.description,
|
|
273013
|
+
logo: { type: 'icon', Icon: Globe },
|
|
273014
|
+
},
|
|
273015
|
+
{
|
|
273016
|
+
value: 'google',
|
|
273017
|
+
label: labels.mentorVoice.providerOptions.google.label,
|
|
273018
|
+
description: labels.mentorVoice.providerOptions.google.description,
|
|
273019
|
+
logo: { type: 'image', src: '/llm-google-provider.svg', alt: 'Google' },
|
|
273020
|
+
},
|
|
273021
|
+
{
|
|
273022
|
+
value: 'iblai',
|
|
273023
|
+
label: labels.mentorVoice.providerOptions.iblai.label,
|
|
273024
|
+
description: labels.mentorVoice.providerOptions.iblai.description,
|
|
273025
|
+
logo: { type: 'image', src: '/llm-iblai-provider.png', alt: 'ibl.ai' },
|
|
273026
|
+
},
|
|
273027
|
+
{
|
|
273028
|
+
value: 'openai',
|
|
273029
|
+
label: labels.mentorVoice.providerOptions.openai.label,
|
|
273030
|
+
description: labels.mentorVoice.providerOptions.openai.description,
|
|
273031
|
+
logo: { type: 'image', src: '/llm-openai-provider-2.svg', alt: 'OpenAI' },
|
|
273032
|
+
},
|
|
273033
|
+
];
|
|
273034
|
+
// Lowercase first, then compare. A case-sensitive sort would rank every
|
|
273035
|
+
// capitalised label ahead of the lowercase "ibl.ai" and push it past
|
|
273036
|
+
// OpenAI.
|
|
273037
|
+
return [...options].sort((a, b) => a.label.toLowerCase().localeCompare(b.label.toLowerCase()));
|
|
273038
|
+
}, [labels.mentorVoice.providerOptions]);
|
|
272885
273039
|
const handleSaveVoice = async () => {
|
|
272886
273040
|
try {
|
|
272887
273041
|
const formData = { voice_provider: provider };
|
|
@@ -272894,6 +273048,9 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272894
273048
|
if (provider === 'google' && pickedGoogleVoice) {
|
|
272895
273049
|
formData.google_voice = pickedGoogleVoice.id;
|
|
272896
273050
|
}
|
|
273051
|
+
if (provider === 'iblai' && pickedIblaiVoice) {
|
|
273052
|
+
formData.iblai_voice = pickedIblaiVoice.id;
|
|
273053
|
+
}
|
|
272897
273054
|
// Same "absent = keep" contract: only send voice_instructions when
|
|
272898
273055
|
// the user actually edited it. Clearing sends "" (never null) —
|
|
272899
273056
|
// that's what resets the stored value server-side.
|
|
@@ -272914,16 +273071,21 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272914
273071
|
toast.error(labels.toasts.voiceError);
|
|
272915
273072
|
}
|
|
272916
273073
|
};
|
|
273074
|
+
// Only `browser` has no voice to show -- it uses the listener's OS voice.
|
|
272917
273075
|
const selectedVoice = provider === 'openai'
|
|
272918
273076
|
? effectiveOpenaiVoice
|
|
272919
273077
|
: provider === 'google'
|
|
272920
273078
|
? effectiveGoogleVoice
|
|
272921
|
-
:
|
|
273079
|
+
: provider === 'iblai'
|
|
273080
|
+
? effectiveIblaiVoice
|
|
273081
|
+
: null;
|
|
272922
273082
|
const onSelectVoice = (voice) => {
|
|
272923
273083
|
if (provider === 'openai')
|
|
272924
273084
|
setPickedOpenaiVoice(voice);
|
|
272925
273085
|
else if (provider === 'google')
|
|
272926
273086
|
setPickedGoogleVoice(voice);
|
|
273087
|
+
else if (provider === 'iblai')
|
|
273088
|
+
setPickedIblaiVoice(voice);
|
|
272927
273089
|
};
|
|
272928
273090
|
// Mirrors `AgentScreenShareTab`: hosts can render prompt content as
|
|
272929
273091
|
// Markdown via `renderPromptContent`; plain text otherwise.
|
|
@@ -272934,19 +273096,19 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272934
273096
|
};
|
|
272935
273097
|
return (jsxs(Fragment$1, { children: [jsx("div", { className: "flex h-[73px] flex-shrink-0 items-center border-b border-gray-200 bg-white p-4 lg:block", children: jsxs("div", { children: [jsx("h3", { className: "mb-1 text-base font-medium text-gray-900", children: labels.header.title }), jsx("p", { className: "text-xs text-gray-600", children: labels.header.description })] }) }), jsx("div", { className: "flex-1 space-y-4 p-3 lg:p-4", style: { overflowY: 'auto', overflowX: 'hidden' }, "data-testid": "voice-tab-body", children: isMentorSettingsLoading ? (jsx("div", { className: "flex items-center justify-center py-12", "data-testid": "voice-tab-loading", children: jsx(Spinner, {}) })) : (jsxs(CapabilityGate, { title: labels.capability.title, description: labels.capability.description, offHint: labels.capability.offHint, enabled: voiceCallEnabled, onToggle: handleToggleVoiceCall, disabled: isDisabled || isTogglingVoiceCall, ariaLabel: labels.capability.title, toggleTestId: "voice-capability-toggle", children: [jsxs(Tabs, { defaultValue: SUB_TAB_VALUES[defaultSubTab], className: "w-full", children: [jsxs(TabsList, { "aria-label": labels.header.title, "data-testid": "voice-sub-tabs", children: [jsx(TabsTrigger, { value: SUB_TAB_VALUES.voice, "data-testid": "voice-sub-tab-voice", children: labels.subTabs.voice }), jsx(TabsTrigger, { value: SUB_TAB_VALUES.callConfig, "data-testid": "voice-sub-tab-call-config", children: labels.subTabs.callConfig })] }), jsxs(TabsContent, { value: SUB_TAB_VALUES.voice, className: "space-y-4", children: [jsx("p", { className: "text-xs text-gray-600", children: labels.mentorVoice.description }), jsx(WithFormPermissions, { name: "voice_provider",
|
|
272936
273098
|
// @ts-expect-error permissions.field is opaque
|
|
272937
|
-
permissions: (
|
|
273099
|
+
permissions: (_o = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _o === void 0 ? void 0 : _o.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label$1, { className: "text-sm font-medium text-[#646464]", children: labels.mentorVoice.providerLabel }), jsx(FieldTooltip, { text: labels.mentorVoice.providerTooltip })] }), jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", role: "radiogroup", "aria-label": labels.mentorVoice.providerLabel, children: providerOptions.map((option) => {
|
|
272938
273100
|
const isSelected = provider === option.value;
|
|
272939
273101
|
const isDisabledOption = isDisabled || disabled;
|
|
272940
273102
|
return (jsxs("button", { type: "button", role: "radio", "aria-checked": isSelected, "aria-label": option.label, disabled: isDisabledOption, "data-testid": `voice-provider-${option.value}`, "data-voice-provider": option.value, onClick: () => setProvider(option.value), className: cn('flex flex-col items-start gap-2 rounded-lg border-2 p-3 text-left transition-all hover:shadow-sm', isSelected
|
|
272941
273103
|
? 'border-blue-500 bg-blue-50/40'
|
|
272942
273104
|
: 'border-gray-200 bg-white hover:border-gray-300', isDisabledOption && 'cursor-not-allowed opacity-60'), children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center", children: option.logo.type === 'image' ? (jsx(Image$4, { src: option.logo.src, alt: option.logo.alt, width: 24, height: 24, className: "h-6 w-6 object-contain" })) : (jsx(option.logo.Icon, { className: "h-5 w-5 text-blue-600", "aria-hidden": "true" })) }), jsx("span", { className: "text-sm font-medium text-gray-900", children: option.label })] }), jsx("p", { className: "text-xs text-gray-500", children: option.description })] }, option.value));
|
|
272943
|
-
}) })] })) }), (provider
|
|
273105
|
+
}) })] })) }), usesVoiceCatalogue(provider) && (jsx(WithFormPermissions, { name: `${provider}_voice`,
|
|
272944
273106
|
// @ts-expect-error permissions.field is opaque
|
|
272945
|
-
permissions: (
|
|
273107
|
+
permissions: (_p = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _p === void 0 ? void 0 : _p.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsx(Label$1, { className: "text-sm font-medium text-[#646464]", children: t('tabsVoiceTab.voiceLabel') }), jsx(VoicePickerTrigger, { org: tenantKey, userId: username, provider: provider, selectedVoice: selectedVoice, onSelect: onSelectVoice, disabled: isDisabled || disabled, labels: labels.mentorVoice, modalTitle: t('tabsVoiceTab.selectVoiceModalTitle', {
|
|
272946
273108
|
voiceType: labels.mentorVoice.voicePickerLabel[provider],
|
|
272947
273109
|
}), modalDescription: t('tabsVoiceTab.selectVoiceModalDescription'), testId: "mentor-voice-trigger" })] })) })), (provider === 'openai' || provider === 'google') && (jsx(WithFormPermissions, { name: "voice_instructions",
|
|
272948
273110
|
// @ts-expect-error permissions.field is opaque
|
|
272949
|
-
permissions: (
|
|
273111
|
+
permissions: (_q = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _q === void 0 ? void 0 : _q.field, enableRBAC: enableRBAC, children: ({ disabled }) => {
|
|
272950
273112
|
const isFieldDisabled = isDisabled || disabled;
|
|
272951
273113
|
const presetKeys = ['warm', 'calm', 'energetic'];
|
|
272952
273114
|
return (jsxs("div", { className: "space-y-2", children: [jsx(PromptCard$1, { label: labels.mentorVoice.instructions.label, placeholder: labels.mentorVoice.instructions.placeholder, value: instructions, disabled: isFieldDisabled, renderContent: renderInstructionsContent, testId: "voice-instructions-card", headerExtra: jsx(FieldTooltip, { text: labels.mentorVoice.instructions.tooltip }), onEdit: () => setSelectedPrompt({
|
|
@@ -272959,7 +273121,7 @@ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', render
|
|
|
272959
273121
|
}) }), jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsx("p", { id: "voice-instructions-help", className: "text-xs text-gray-500", children: labels.mentorVoice.instructions.helpText }), jsxs("span", { id: "voice-instructions-counter", "aria-live": "polite", "data-testid": "voice-instructions-counter", className: cn('flex-shrink-0 text-xs text-gray-400', instructionsOverCap && 'text-red-500'), children: [instructions.length, "/", VOICE_INSTRUCTIONS_MAX] })] }), jsxs("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": labels.mentorVoice.instructions.presetsLabel, children: [jsxs("span", { className: "text-xs text-gray-500", children: [labels.mentorVoice.instructions.presetsLabel, ":"] }), presetKeys.map((key) => (jsx("button", { type: "button", disabled: isFieldDisabled, "data-testid": `voice-instructions-preset-${key}`, onClick: () => setInstructions(labels.mentorVoice.instructions.presets[key].text), className: cn('inline-flex items-center rounded-full border border-gray-300 bg-white px-3 py-1 text-xs text-gray-700 transition-colors hover:bg-gray-50', isFieldDisabled && 'cursor-not-allowed opacity-50'), children: labels.mentorVoice.instructions.presets[key].label }, key)))] })] }));
|
|
272960
273122
|
} })), jsx("div", { className: "flex items-center justify-end border-t border-gray-100 pt-3", children: jsx(Button$1, { type: "button", onClick: handleSaveVoice, disabled: isDisabled || !isDirty || instructionsOverCap, "data-testid": "voice-save-button", className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] text-white hover:opacity-90", children: isSavingMentor
|
|
272961
273123
|
? labels.mentorVoice.savingButton
|
|
272962
|
-
: labels.mentorVoice.saveButton }) })] }), jsxs(TabsContent, { value: SUB_TAB_VALUES.callConfig, className: "space-y-4", children: [jsx("p", { className: "text-xs text-gray-600", children: labels.callConfig.description }), mentorId && username && tenantKey && (jsx(CallConfigSection, { org: tenantKey, userId: username, mentorId: mentorId, isDisabled: isDisabled, labels: labels.callConfig, voicePickerLabels: labels.mentorVoice, initialConfig: (
|
|
273124
|
+
: labels.mentorVoice.saveButton }) })] }), jsxs(TabsContent, { value: SUB_TAB_VALUES.callConfig, className: "space-y-4", children: [jsx("p", { className: "text-xs text-gray-600", children: labels.callConfig.description }), mentorId && username && tenantKey && (jsx(CallConfigSection, { org: tenantKey, userId: username, mentorId: mentorId, isDisabled: isDisabled, labels: labels.callConfig, voicePickerLabels: labels.mentorVoice, initialConfig: (_r = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.call_configuration) !== null && _r !== void 0 ? _r : null, renderPromptContent: renderPromptContent, toastLabels: {
|
|
272963
273125
|
saved: labels.toasts.callConfigSaved,
|
|
272964
273126
|
error: labels.toasts.callConfigError,
|
|
272965
273127
|
} }))] })] }), selectedPrompt && (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iblai/iblai-js",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.7",
|
|
4
4
|
"description": "Unified JavaScript SDK for IBL.ai — re-exports data-layer, web-containers, and web-utils under a single package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -76,10 +76,10 @@
|
|
|
76
76
|
"axios": "1.13.6",
|
|
77
77
|
"dotenv": "16.6.1",
|
|
78
78
|
"winston": "3.19.0",
|
|
79
|
-
"@iblai/mcp": "1.10.
|
|
79
|
+
"@iblai/mcp": "1.10.5",
|
|
80
80
|
"@iblai/data-layer": "1.12.106",
|
|
81
|
-
"@iblai/web-containers": "1.18.
|
|
82
|
-
"@iblai/web-utils": "2.1.
|
|
81
|
+
"@iblai/web-containers": "1.18.7",
|
|
82
|
+
"@iblai/web-utils": "2.1.17"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"@iblai/iblai-api": "4.166.0-ai",
|