@iblai/iblai-js 2.7.1 → 2.7.2
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.
- package/dist/web-containers/source/index.esm.js +90012 -89817
- package/dist/web-containers/source/next/index.esm.js +859 -231
- package/package.json +3 -3
|
@@ -99502,18 +99502,47 @@ var llmTabLabels$3 = {
|
|
|
99502
99502
|
title: "LLM Configuration",
|
|
99503
99503
|
description: "Configure the language model settings for your agent."
|
|
99504
99504
|
},
|
|
99505
|
+
infoBox: "This is the brain behind your agent — the AI model that powers every reply. Bigger models reason better on tricky questions; lighter ones are faster and cost less. Not sure? The default is a solid all-rounder.",
|
|
99505
99506
|
search: {
|
|
99506
99507
|
placeholder: "Search Providers"
|
|
99507
99508
|
},
|
|
99509
|
+
providerLogoAlt: "{providerName} logo",
|
|
99508
99510
|
toasts: {
|
|
99509
99511
|
updateSuccess: "LLM updated successfully",
|
|
99510
99512
|
updateError: "Failed to update LLM"
|
|
99511
99513
|
},
|
|
99512
99514
|
providerModal: {
|
|
99513
99515
|
title: "LLM Selection",
|
|
99514
|
-
description: "Select one of the
|
|
99516
|
+
description: "Select one of the agents provided by {providerName}",
|
|
99515
99517
|
searchPlaceholder: "Search",
|
|
99516
|
-
helpText: "Choose your preferred LLM from the available provider to tailor your experience."
|
|
99518
|
+
helpText: "Choose your preferred LLM from the available provider to tailor your experience.",
|
|
99519
|
+
providerIconAlt: "{providerName} icon",
|
|
99520
|
+
tooLargeTitle: "This model may be too large for your system",
|
|
99521
|
+
tooLargeDescription: "{modelName} needs about {modelSize}. It may run very slowly or fail to load on this machine. Download anyway?",
|
|
99522
|
+
cancel: "Cancel",
|
|
99523
|
+
downloadAnyway: "Download anyway",
|
|
99524
|
+
alreadyDownloadingTitle: "A model is already downloading",
|
|
99525
|
+
unnamedModel: "A model",
|
|
99526
|
+
alreadyDownloadingDescription: "{modelName} is being downloaded. Only one on-device model downloads at a time — wait for it to finish, or click {modelName} in the list to cancel it.",
|
|
99527
|
+
gotIt: "Got it",
|
|
99528
|
+
announceDownloaded: "{modelName} downloaded",
|
|
99529
|
+
announceCancelled: "Download cancelled",
|
|
99530
|
+
announceFailed: "Download failed",
|
|
99531
|
+
announceStarted: "Started downloading {modelName}",
|
|
99532
|
+
localModel: {
|
|
99533
|
+
onDevice: "On-device",
|
|
99534
|
+
starting: "Starting…",
|
|
99535
|
+
cancel: "Cancel",
|
|
99536
|
+
inUse: "In use",
|
|
99537
|
+
downloadFailedRetry: "Download failed — retry",
|
|
99538
|
+
ariaDownload: "Download {modelName}, {modelSize}, on-device model",
|
|
99539
|
+
ariaStarting: "Starting download of {modelName}",
|
|
99540
|
+
ariaDownloading: "Cancel download of {modelName}, {percent} percent",
|
|
99541
|
+
ariaInstalled: "Use {modelName}, on-device model",
|
|
99542
|
+
ariaSelected: "Selected {modelName}, on-device model, in use",
|
|
99543
|
+
ariaError: "Retry download of {modelName}",
|
|
99544
|
+
ariaErrorWithReason: "Retry download of {modelName}, {reason}"
|
|
99545
|
+
}
|
|
99517
99546
|
}
|
|
99518
99547
|
};
|
|
99519
99548
|
var llmsTabCreateLlmModal$3 = {
|
|
@@ -101834,9 +101863,6 @@ var tabsHumanSupportTab$3 = {
|
|
|
101834
101863
|
ticketListLabel: "Support ticket list",
|
|
101835
101864
|
ticketDetailLabel: "Support ticket details"
|
|
101836
101865
|
};
|
|
101837
|
-
var tabsLlmTab$3 = {
|
|
101838
|
-
providerLogoAlt: "{name} logo"
|
|
101839
|
-
};
|
|
101840
101866
|
var tabsPoliciestab$3 = {
|
|
101841
101867
|
searchPoliciesPlaceholder: "Search policies",
|
|
101842
101868
|
searchPoliciesLabel: "Search policies",
|
|
@@ -103745,7 +103771,6 @@ var en = {
|
|
|
103745
103771
|
tabsGroupstab: tabsGroupstab$3,
|
|
103746
103772
|
tabsHistoryTab: tabsHistoryTab$3,
|
|
103747
103773
|
tabsHumanSupportTab: tabsHumanSupportTab$3,
|
|
103748
|
-
tabsLlmTab: tabsLlmTab$3,
|
|
103749
103774
|
tabsPoliciestab: tabsPoliciestab$3,
|
|
103750
103775
|
tabsPrivacyTab: tabsPrivacyTab$3,
|
|
103751
103776
|
tabsPromptsTab: tabsPromptsTab$3,
|
|
@@ -106640,18 +106665,47 @@ var llmTabLabels$2 = {
|
|
|
106640
106665
|
title: "Configuration LLM",
|
|
106641
106666
|
description: "Configurez les paramètres du modèle de langage pour votre agent."
|
|
106642
106667
|
},
|
|
106668
|
+
infoBox: "C'est le cerveau de votre agent — le modèle d'IA qui alimente chaque réponse. Les modèles plus grands raisonnent mieux sur les questions complexes ; les plus légers sont plus rapides et moins coûteux. Pas sûr ? Le modèle par défaut est un excellent choix polyvalent.",
|
|
106643
106669
|
search: {
|
|
106644
106670
|
placeholder: "Rechercher des fournisseurs"
|
|
106645
106671
|
},
|
|
106672
|
+
providerLogoAlt: "Logo {providerName}",
|
|
106646
106673
|
toasts: {
|
|
106647
106674
|
updateSuccess: "LLM mis à jour avec succès",
|
|
106648
106675
|
updateError: "Échec de la mise à jour du LLM"
|
|
106649
106676
|
},
|
|
106650
106677
|
providerModal: {
|
|
106651
106678
|
title: "Sélection du LLM",
|
|
106652
|
-
description: "Sélectionnez l'un des
|
|
106679
|
+
description: "Sélectionnez l'un des agents proposés par {providerName}",
|
|
106653
106680
|
searchPlaceholder: "Rechercher",
|
|
106654
|
-
helpText: "Choisissez votre LLM préféré parmi les fournisseurs disponibles pour personnaliser votre expérience."
|
|
106681
|
+
helpText: "Choisissez votre LLM préféré parmi les fournisseurs disponibles pour personnaliser votre expérience.",
|
|
106682
|
+
providerIconAlt: "icône {providerName}",
|
|
106683
|
+
tooLargeTitle: "Ce modèle est peut-être trop volumineux pour votre système",
|
|
106684
|
+
tooLargeDescription: "{modelName} nécessite environ {modelSize}. Il peut être très lent ou ne pas se charger sur cette machine. Télécharger quand même ?",
|
|
106685
|
+
cancel: "Annuler",
|
|
106686
|
+
downloadAnyway: "Télécharger quand même",
|
|
106687
|
+
alreadyDownloadingTitle: "Un modèle est déjà en cours de téléchargement",
|
|
106688
|
+
unnamedModel: "Un modèle",
|
|
106689
|
+
alreadyDownloadingDescription: "{modelName} est en cours de téléchargement. Un seul modèle sur l'appareil est téléchargé à la fois : attendez la fin ou cliquez sur {modelName} dans la liste pour l'annuler.",
|
|
106690
|
+
gotIt: "Compris",
|
|
106691
|
+
announceDownloaded: "{modelName} téléchargé",
|
|
106692
|
+
announceCancelled: "Téléchargement annulé",
|
|
106693
|
+
announceFailed: "Échec du téléchargement",
|
|
106694
|
+
announceStarted: "Téléchargement de {modelName} démarré",
|
|
106695
|
+
localModel: {
|
|
106696
|
+
onDevice: "Sur l'appareil",
|
|
106697
|
+
starting: "Démarrage…",
|
|
106698
|
+
cancel: "Annuler",
|
|
106699
|
+
inUse: "Utilisé",
|
|
106700
|
+
downloadFailedRetry: "Échec du téléchargement — réessayer",
|
|
106701
|
+
ariaDownload: "Télécharger {modelName}, {modelSize}, modèle sur l'appareil",
|
|
106702
|
+
ariaStarting: "Démarrage du téléchargement de {modelName}",
|
|
106703
|
+
ariaDownloading: "Annuler le téléchargement de {modelName}, {percent} pour cent",
|
|
106704
|
+
ariaInstalled: "Utiliser {modelName}, modèle sur l'appareil",
|
|
106705
|
+
ariaSelected: "{modelName} sélectionné, modèle sur l'appareil, utilisé",
|
|
106706
|
+
ariaError: "Réessayer le téléchargement de {modelName}",
|
|
106707
|
+
ariaErrorWithReason: "Réessayer le téléchargement de {modelName}, {reason}"
|
|
106708
|
+
}
|
|
106655
106709
|
}
|
|
106656
106710
|
};
|
|
106657
106711
|
var llmsTabCreateLlmModal$2 = {
|
|
@@ -108972,9 +109026,6 @@ var tabsHumanSupportTab$2 = {
|
|
|
108972
109026
|
ticketListLabel: "Liste des tickets de support",
|
|
108973
109027
|
ticketDetailLabel: "Détails du ticket de support"
|
|
108974
109028
|
};
|
|
108975
|
-
var tabsLlmTab$2 = {
|
|
108976
|
-
providerLogoAlt: "Logo {name}"
|
|
108977
|
-
};
|
|
108978
109029
|
var tabsPoliciestab$2 = {
|
|
108979
109030
|
searchPoliciesPlaceholder: "Rechercher des politiques",
|
|
108980
109031
|
searchPoliciesLabel: "Rechercher des politiques",
|
|
@@ -110718,7 +110769,6 @@ var fr = {
|
|
|
110718
110769
|
tabsGroupstab: tabsGroupstab$2,
|
|
110719
110770
|
tabsHistoryTab: tabsHistoryTab$2,
|
|
110720
110771
|
tabsHumanSupportTab: tabsHumanSupportTab$2,
|
|
110721
|
-
tabsLlmTab: tabsLlmTab$2,
|
|
110722
110772
|
tabsPoliciestab: tabsPoliciestab$2,
|
|
110723
110773
|
tabsPrivacyTab: tabsPrivacyTab$2,
|
|
110724
110774
|
tabsPromptsTab: tabsPromptsTab$2,
|
|
@@ -113612,18 +113662,47 @@ var llmTabLabels$1 = {
|
|
|
113612
113662
|
title: "Configuración de LLM",
|
|
113613
113663
|
description: "Configure los ajustes del modelo de lenguaje para su agente."
|
|
113614
113664
|
},
|
|
113665
|
+
infoBox: "Este es el cerebro de tu agente: el modelo de IA que impulsa cada respuesta. Los modelos más grandes razonan mejor las preguntas complejas; los más ligeros son más rápidos y cuestan menos. ¿No estás seguro? El predeterminado es una opción versátil y confiable.",
|
|
113615
113666
|
search: {
|
|
113616
113667
|
placeholder: "Buscar proveedores"
|
|
113617
113668
|
},
|
|
113669
|
+
providerLogoAlt: "Logo de {providerName}",
|
|
113618
113670
|
toasts: {
|
|
113619
113671
|
updateSuccess: "LLM actualizado correctamente",
|
|
113620
113672
|
updateError: "Error al actualizar el LLM"
|
|
113621
113673
|
},
|
|
113622
113674
|
providerModal: {
|
|
113623
113675
|
title: "Selección de LLM",
|
|
113624
|
-
description: "Seleccione uno de los
|
|
113676
|
+
description: "Seleccione uno de los agentes proporcionados por {providerName}",
|
|
113625
113677
|
searchPlaceholder: "Buscar",
|
|
113626
|
-
helpText: "Elija su LLM preferido entre los proveedores disponibles para personalizar su experiencia."
|
|
113678
|
+
helpText: "Elija su LLM preferido entre los proveedores disponibles para personalizar su experiencia.",
|
|
113679
|
+
providerIconAlt: "ícono de {providerName}",
|
|
113680
|
+
tooLargeTitle: "Este modelo puede ser demasiado grande para su sistema",
|
|
113681
|
+
tooLargeDescription: "{modelName} necesita aproximadamente {modelSize}. Puede funcionar muy lentamente o no cargarse en este equipo. ¿Descargar de todos modos?",
|
|
113682
|
+
cancel: "Cancelar",
|
|
113683
|
+
downloadAnyway: "Descargar de todos modos",
|
|
113684
|
+
alreadyDownloadingTitle: "Ya se está descargando un modelo",
|
|
113685
|
+
unnamedModel: "Un modelo",
|
|
113686
|
+
alreadyDownloadingDescription: "{modelName} se está descargando. Solo se descarga un modelo en el dispositivo a la vez: espere a que termine o haga clic en {modelName} en la lista para cancelarlo.",
|
|
113687
|
+
gotIt: "Entendido",
|
|
113688
|
+
announceDownloaded: "{modelName} descargado",
|
|
113689
|
+
announceCancelled: "Descarga cancelada",
|
|
113690
|
+
announceFailed: "Error en la descarga",
|
|
113691
|
+
announceStarted: "Descarga de {modelName} iniciada",
|
|
113692
|
+
localModel: {
|
|
113693
|
+
onDevice: "En el dispositivo",
|
|
113694
|
+
starting: "Iniciando…",
|
|
113695
|
+
cancel: "Cancelar",
|
|
113696
|
+
inUse: "En uso",
|
|
113697
|
+
downloadFailedRetry: "Error en la descarga: reintentar",
|
|
113698
|
+
ariaDownload: "Descargar {modelName}, {modelSize}, modelo en el dispositivo",
|
|
113699
|
+
ariaStarting: "Iniciando la descarga de {modelName}",
|
|
113700
|
+
ariaDownloading: "Cancelar la descarga de {modelName}, {percent} por ciento",
|
|
113701
|
+
ariaInstalled: "Usar {modelName}, modelo en el dispositivo",
|
|
113702
|
+
ariaSelected: "{modelName} seleccionado, modelo en el dispositivo, en uso",
|
|
113703
|
+
ariaError: "Reintentar la descarga de {modelName}",
|
|
113704
|
+
ariaErrorWithReason: "Reintentar la descarga de {modelName}, {reason}"
|
|
113705
|
+
}
|
|
113627
113706
|
}
|
|
113628
113707
|
};
|
|
113629
113708
|
var llmsTabCreateLlmModal$1 = {
|
|
@@ -115944,9 +116023,6 @@ var tabsHumanSupportTab$1 = {
|
|
|
115944
116023
|
ticketListLabel: "Lista de tickets de soporte",
|
|
115945
116024
|
ticketDetailLabel: "Detalles del ticket de soporte"
|
|
115946
116025
|
};
|
|
115947
|
-
var tabsLlmTab$1 = {
|
|
115948
|
-
providerLogoAlt: "Logo de {name}"
|
|
115949
|
-
};
|
|
115950
116026
|
var tabsPoliciestab$1 = {
|
|
115951
116027
|
searchPoliciesPlaceholder: "Buscar políticas",
|
|
115952
116028
|
searchPoliciesLabel: "Buscar políticas",
|
|
@@ -117690,7 +117766,6 @@ var es = {
|
|
|
117690
117766
|
tabsGroupstab: tabsGroupstab$1,
|
|
117691
117767
|
tabsHistoryTab: tabsHistoryTab$1,
|
|
117692
117768
|
tabsHumanSupportTab: tabsHumanSupportTab$1,
|
|
117693
|
-
tabsLlmTab: tabsLlmTab$1,
|
|
117694
117769
|
tabsPoliciestab: tabsPoliciestab$1,
|
|
117695
117770
|
tabsPrivacyTab: tabsPrivacyTab$1,
|
|
117696
117771
|
tabsPromptsTab: tabsPromptsTab$1,
|
|
@@ -120584,18 +120659,47 @@ var llmTabLabels = {
|
|
|
120584
120659
|
title: "LLM 配置",
|
|
120585
120660
|
description: "为您的智能体配置语言模型设置。"
|
|
120586
120661
|
},
|
|
120662
|
+
infoBox: "这是您智能体的大脑——为每次回复提供动力的 AI 模型。更大的模型在处理复杂问题时推理能力更强;更轻量的模型则更快、成本更低。不确定?默认模型是可靠的全能之选。",
|
|
120587
120663
|
search: {
|
|
120588
120664
|
placeholder: "搜索提供商"
|
|
120589
120665
|
},
|
|
120666
|
+
providerLogoAlt: "{providerName} 徽标",
|
|
120590
120667
|
toasts: {
|
|
120591
120668
|
updateSuccess: "LLM 更新成功",
|
|
120592
120669
|
updateError: "LLM 更新失败"
|
|
120593
120670
|
},
|
|
120594
120671
|
providerModal: {
|
|
120595
120672
|
title: "LLM 选择",
|
|
120596
|
-
description: "
|
|
120673
|
+
description: "从 {providerName} 提供的代理中选择一个",
|
|
120597
120674
|
searchPlaceholder: "搜索",
|
|
120598
|
-
helpText: "从可用提供商中选择您偏好的 LLM,以定制您的使用体验。"
|
|
120675
|
+
helpText: "从可用提供商中选择您偏好的 LLM,以定制您的使用体验。",
|
|
120676
|
+
providerIconAlt: "{providerName} 图标",
|
|
120677
|
+
tooLargeTitle: "该模型可能对您的系统而言过大",
|
|
120678
|
+
tooLargeDescription: "{modelName} 约需 {modelSize}。它在此设备上可能运行非常缓慢或无法加载。仍要下载吗?",
|
|
120679
|
+
cancel: "取消",
|
|
120680
|
+
downloadAnyway: "仍要下载",
|
|
120681
|
+
alreadyDownloadingTitle: "已有模型正在下载",
|
|
120682
|
+
unnamedModel: "某个模型",
|
|
120683
|
+
alreadyDownloadingDescription: "{modelName} 正在下载。一次只能下载一个设备端模型——请等待其完成,或点击列表中的 {modelName} 以取消下载。",
|
|
120684
|
+
gotIt: "知道了",
|
|
120685
|
+
announceDownloaded: "{modelName} 已下载",
|
|
120686
|
+
announceCancelled: "下载已取消",
|
|
120687
|
+
announceFailed: "下载失败",
|
|
120688
|
+
announceStarted: "已开始下载 {modelName}",
|
|
120689
|
+
localModel: {
|
|
120690
|
+
onDevice: "设备端",
|
|
120691
|
+
starting: "正在启动…",
|
|
120692
|
+
cancel: "取消",
|
|
120693
|
+
inUse: "使用中",
|
|
120694
|
+
downloadFailedRetry: "下载失败——重试",
|
|
120695
|
+
ariaDownload: "下载 {modelName},{modelSize},设备端模型",
|
|
120696
|
+
ariaStarting: "正在开始下载 {modelName}",
|
|
120697
|
+
ariaDownloading: "取消下载 {modelName},{percent}%",
|
|
120698
|
+
ariaInstalled: "使用 {modelName},设备端模型",
|
|
120699
|
+
ariaSelected: "已选择 {modelName},设备端模型,使用中",
|
|
120700
|
+
ariaError: "重试下载 {modelName}",
|
|
120701
|
+
ariaErrorWithReason: "重试下载 {modelName},{reason}"
|
|
120702
|
+
}
|
|
120599
120703
|
}
|
|
120600
120704
|
};
|
|
120601
120705
|
var llmsTabCreateLlmModal = {
|
|
@@ -122916,9 +123020,6 @@ var tabsHumanSupportTab = {
|
|
|
122916
123020
|
ticketListLabel: "支持工单列表",
|
|
122917
123021
|
ticketDetailLabel: "支持工单详情"
|
|
122918
123022
|
};
|
|
122919
|
-
var tabsLlmTab = {
|
|
122920
|
-
providerLogoAlt: "{name} 标志"
|
|
122921
|
-
};
|
|
122922
123023
|
var tabsPoliciestab = {
|
|
122923
123024
|
searchPoliciesPlaceholder: "搜索策略",
|
|
122924
123025
|
searchPoliciesLabel: "搜索策略",
|
|
@@ -124662,7 +124763,6 @@ var zh = {
|
|
|
124662
124763
|
tabsGroupstab: tabsGroupstab,
|
|
124663
124764
|
tabsHistoryTab: tabsHistoryTab,
|
|
124664
124765
|
tabsHumanSupportTab: tabsHumanSupportTab,
|
|
124665
|
-
tabsLlmTab: tabsLlmTab,
|
|
124666
124766
|
tabsPoliciestab: tabsPoliciestab,
|
|
124667
124767
|
tabsPrivacyTab: tabsPrivacyTab,
|
|
124668
124768
|
tabsPromptsTab: tabsPromptsTab,
|
|
@@ -130883,6 +130983,7 @@ Progress.displayName = Root$9.displayName;
|
|
|
130883
130983
|
|
|
130884
130984
|
const LOCAL_LLM_ENABLED_KEY = 'ibl_local_llm_enabled';
|
|
130885
130985
|
const LOCAL_LLM_MODEL_KEY = 'ibl_local_llm_model';
|
|
130986
|
+
const LOCAL_LLM_TOOL_SUPPORT_KEY = 'ibl_local_llm_tool_support';
|
|
130886
130987
|
/**
|
|
130887
130988
|
* Curated catalog of small, on-device-friendly models from the Ollama library
|
|
130888
130989
|
* (https://ollama.com/library). The model manager itself (Ollama) is not a
|
|
@@ -130940,6 +131041,12 @@ const LOCAL_MODELS = [
|
|
|
130940
131041
|
tool_support: true,
|
|
130941
131042
|
},
|
|
130942
131043
|
];
|
|
131044
|
+
/**
|
|
131045
|
+
* Fraction of the machine's memory above which a model is treated as "too large"
|
|
131046
|
+
* and a confirmation is required before downloading. TESTING VALUE — set low (1%)
|
|
131047
|
+
* so the warning is easy to trigger; raise toward real capacity (e.g. 0.8) later.
|
|
131048
|
+
*/
|
|
131049
|
+
const MODEL_SIZE_WARN_FRACTION = 0.01;
|
|
130943
131050
|
/** Multipliers for the size units used in the catalog (binary / 1024-based). */
|
|
130944
131051
|
const SIZE_UNIT_BYTES = {
|
|
130945
131052
|
B: 1,
|
|
@@ -130961,6 +131068,31 @@ function parseModelSizeBytes(size) {
|
|
|
130961
131068
|
return null;
|
|
130962
131069
|
return value * ((_a = SIZE_UNIT_BYTES[match[2].toUpperCase()]) !== null && _a !== void 0 ? _a : 1);
|
|
130963
131070
|
}
|
|
131071
|
+
/**
|
|
131072
|
+
* The memory a model can use on this machine: the larger of system RAM and GPU
|
|
131073
|
+
* VRAM (a model runs in one or the other). Returns 0 when memory is unknown.
|
|
131074
|
+
*/
|
|
131075
|
+
function usableMemoryBytes(systemMemory) {
|
|
131076
|
+
if (!systemMemory)
|
|
131077
|
+
return 0;
|
|
131078
|
+
return Math.max(systemMemory.ram_total, systemMemory.vram_total);
|
|
131079
|
+
}
|
|
131080
|
+
/**
|
|
131081
|
+
* Whether `model` is large enough relative to this machine's memory to warrant a
|
|
131082
|
+
* "might not run" confirmation. False when memory or the model size is unknown,
|
|
131083
|
+
* so the download simply proceeds rather than warning on incomplete data.
|
|
131084
|
+
*/
|
|
131085
|
+
function modelExceedsCapacity(model, systemMemory) {
|
|
131086
|
+
const capacity = usableMemoryBytes(systemMemory);
|
|
131087
|
+
const modelBytes = parseModelSizeBytes(model.size);
|
|
131088
|
+
if (modelBytes == null)
|
|
131089
|
+
return false;
|
|
131090
|
+
// Unknown memory (capacity 0) → don't warn; the download simply proceeds
|
|
131091
|
+
// rather than blocking on incomplete data.
|
|
131092
|
+
if (capacity <= 0)
|
|
131093
|
+
return false;
|
|
131094
|
+
return modelBytes > capacity * MODEL_SIZE_WARN_FRACTION;
|
|
131095
|
+
}
|
|
130964
131096
|
/**
|
|
130965
131097
|
* Default minimum model size (GB) that can use the Cowork
|
|
130966
131098
|
* (GhostOS MCP/tools). Smaller models don't support tools/MCP well, so the
|
|
@@ -131008,6 +131140,33 @@ function getLocalLLMModel() {
|
|
|
131008
131140
|
return null;
|
|
131009
131141
|
return localStorage.getItem(LOCAL_LLM_MODEL_KEY);
|
|
131010
131142
|
}
|
|
131143
|
+
/**
|
|
131144
|
+
* Set the model the user has chosen to chat with (Ollama pull id).
|
|
131145
|
+
*/
|
|
131146
|
+
function setLocalLLMModel(modelId) {
|
|
131147
|
+
if (typeof window === 'undefined')
|
|
131148
|
+
return;
|
|
131149
|
+
localStorage.setItem(LOCAL_LLM_MODEL_KEY, modelId);
|
|
131150
|
+
}
|
|
131151
|
+
/**
|
|
131152
|
+
* Whether the model the user selected supports tools / function calling
|
|
131153
|
+
* (persisted alongside the model id). The host (e.g. ghost-os) reads this to
|
|
131154
|
+
* route chat to the MCP/tool proxy (:8000) vs plain Ollama (:11434). Defaults to
|
|
131155
|
+
* false when unknown. See {@link LocalModel.tool_support}.
|
|
131156
|
+
*/
|
|
131157
|
+
function getLocalLLMToolSupport() {
|
|
131158
|
+
if (typeof window === 'undefined')
|
|
131159
|
+
return false;
|
|
131160
|
+
return localStorage.getItem(LOCAL_LLM_TOOL_SUPPORT_KEY) === 'true';
|
|
131161
|
+
}
|
|
131162
|
+
/**
|
|
131163
|
+
* Persist whether the selected model supports tools (set when a model is chosen).
|
|
131164
|
+
*/
|
|
131165
|
+
function setLocalLLMToolSupport(toolSupport) {
|
|
131166
|
+
if (typeof window === 'undefined')
|
|
131167
|
+
return;
|
|
131168
|
+
localStorage.setItem(LOCAL_LLM_TOOL_SUPPORT_KEY, String(toolSupport));
|
|
131169
|
+
}
|
|
131011
131170
|
function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false, foundryModels = [], selectedFoundryModel = null, foundryStatus, onInstallOllama, onStopManager, onInstallFoundry, onCheckStatus, onSelectFoundryModel, }) {
|
|
131012
131171
|
var _a, _b, _c, _d, _e;
|
|
131013
131172
|
const t = useT();
|
|
@@ -225279,62 +225438,215 @@ function CreateApiModal$1({ isOpen, onClose, tenantKey, username }) {
|
|
|
225279
225438
|
: t('apiTabLabels.createModal.submit') })) }) })] })] }) }), apiKey && jsx(ApiKeyModal$1, { isOpen: true, onClose: () => setApiKey(null), apiKey: apiKey })] }));
|
|
225280
225439
|
}
|
|
225281
225440
|
|
|
225441
|
+
/**
|
|
225442
|
+
* Small pure utilities colocated with the LLM tab.
|
|
225443
|
+
* Ported from standalone's `@/lib/utils` and `@/lib/error`.
|
|
225444
|
+
*/
|
|
225445
|
+
// Canonical provider name for any label — a backend key ("azure_openai"), a
|
|
225446
|
+
// human label ("Microsoft"), or a local model's provider ("Meta"). Folds every
|
|
225447
|
+
// alias onto one identity so the same provider from different sources compares
|
|
225448
|
+
// equal. See getProviderName.
|
|
225449
|
+
const PROVIDER_NAME_BY_ALIAS = {
|
|
225450
|
+
microsoft: 'azure_openai',
|
|
225451
|
+
azureopenai: 'azure_openai',
|
|
225452
|
+
openai: 'openai',
|
|
225453
|
+
google: 'google',
|
|
225454
|
+
gemini: 'google',
|
|
225455
|
+
meta: 'llama',
|
|
225456
|
+
metallama: 'llama',
|
|
225457
|
+
llama: 'llama',
|
|
225458
|
+
mistral: 'mistral',
|
|
225459
|
+
mistralai: 'mistral',
|
|
225460
|
+
nvidia: 'nvidia',
|
|
225461
|
+
iblchatnvidia: 'nvidia',
|
|
225462
|
+
deepseek: 'deepseek',
|
|
225463
|
+
anthropic: 'anthropic',
|
|
225464
|
+
iblchatanthropic: 'anthropic',
|
|
225465
|
+
claude: 'anthropic',
|
|
225466
|
+
groq: 'groq',
|
|
225467
|
+
perplexity: 'perplexity',
|
|
225468
|
+
xai: 'xai',
|
|
225469
|
+
bedrock: 'bedrock',
|
|
225470
|
+
amazonbedrock: 'bedrock',
|
|
225471
|
+
amazon: 'bedrock',
|
|
225472
|
+
iblchatbedrock: 'bedrock',
|
|
225473
|
+
alibaba: 'alibaba',
|
|
225474
|
+
qwen: 'alibaba',
|
|
225475
|
+
ibm: 'ibm',
|
|
225476
|
+
granite: 'ibm',
|
|
225477
|
+
// ibl.ai's own hosted provider. The backend key is `iblai` (NameEnum.IBLAI);
|
|
225478
|
+
// the extra aliases cover the `IBLChat<Vendor>` house spelling and the
|
|
225479
|
+
// dotted/spaced brand forms, which all normalize to one of these.
|
|
225480
|
+
iblai: 'iblai',
|
|
225481
|
+
ibl: 'iblai',
|
|
225482
|
+
iblchatibl: 'iblai',
|
|
225483
|
+
iblchatiblai: 'iblai',
|
|
225484
|
+
};
|
|
225485
|
+
/**
|
|
225486
|
+
* Canonical provider name for any label, case- and punctuation-insensitive.
|
|
225487
|
+
* Folds backend keys and human/display labels onto one identity — e.g.
|
|
225488
|
+
* `getProviderName('Microsoft') === 'azure_openai'`,
|
|
225489
|
+
* `getProviderName('Meta') === 'llama'`, `getProviderName('Google') === 'google'`.
|
|
225490
|
+
* Unknown providers return their normalized (lowercased, alphanumeric-only) form.
|
|
225491
|
+
*
|
|
225492
|
+
* This is the single source of truth for provider identity, which matters as
|
|
225493
|
+
* soon as on-device models enter the picture: the local catalog labels its
|
|
225494
|
+
* providers the human way ("Meta", "Microsoft", "Alibaba") while the backend LLM
|
|
225495
|
+
* list uses wire keys ("llama", "azure_openai"). Comparing the two raw would
|
|
225496
|
+
* render a duplicate card for the same vendor.
|
|
225497
|
+
*/
|
|
225498
|
+
function getProviderName(llmProvider) {
|
|
225499
|
+
var _a;
|
|
225500
|
+
const normalized = (llmProvider !== null && llmProvider !== void 0 ? llmProvider : '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
225501
|
+
return (_a = PROVIDER_NAME_BY_ALIAS[normalized]) !== null && _a !== void 0 ? _a : normalized;
|
|
225502
|
+
}
|
|
225503
|
+
/**
|
|
225504
|
+
* Whether two Ollama model refs name the same model, comparing by base name and
|
|
225505
|
+
* ignoring a trailing `:tag` (so "llama3.2" ≡ "llama3.2:latest", and
|
|
225506
|
+
* "granite4.1:8b" ≡ "granite4.1"). The download state and Ollama's /api/tags may
|
|
225507
|
+
* carry a tag suffix the catalog id doesn't (or vice-versa); an exact `===` then
|
|
225508
|
+
* fails to recognize a row's own in-flight download — the row shows "Download"
|
|
225509
|
+
* with no progress while the pull runs. Base-name matching is safe here because
|
|
225510
|
+
* every catalog model has a unique base (llama3.2, qwen3, mistral, granite4.1,
|
|
225511
|
+
* gpt-oss, gemma4), so it can never light up the wrong row.
|
|
225512
|
+
*/
|
|
225513
|
+
function sameModel(a, b) {
|
|
225514
|
+
const base = (ref) => (ref !== null && ref !== void 0 ? ref : '').toLowerCase().split(':')[0];
|
|
225515
|
+
const ba = base(a);
|
|
225516
|
+
return ba.length > 0 && ba === base(b);
|
|
225517
|
+
}
|
|
225518
|
+
/**
|
|
225519
|
+
* Whether a catalog model id (e.g. "granite4.1:8b") is among the installed
|
|
225520
|
+
* Ollama tags (e.g. "granite4.1:latest").
|
|
225521
|
+
*
|
|
225522
|
+
* Deliberately NOT the profile tab's `isModelInstalled`, which matches an exact
|
|
225523
|
+
* tag or the `<id>:` prefix: that is one-directional and misses a catalog id
|
|
225524
|
+
* that itself carries a tag ("granite4.1:8b" against an installed
|
|
225525
|
+
* "granite4.1:latest"). The picker needs the symmetric base-name comparison of
|
|
225526
|
+
* {@link sameModel} so a pulled model is recognized whichever side wears the tag.
|
|
225527
|
+
*/
|
|
225528
|
+
function isLocalModelInstalled(modelId, installedTags) {
|
|
225529
|
+
return !!(installedTags === null || installedTags === void 0 ? void 0 : installedTags.some((tag) => sameModel(tag, modelId)));
|
|
225530
|
+
}
|
|
225531
|
+
function canSwitchProvider(providers, name) {
|
|
225532
|
+
const provider = providers.find((p) => p.name === name);
|
|
225533
|
+
return !!(provider === null || provider === void 0 ? void 0 : provider.chat_models) && provider.chat_models.length > 0;
|
|
225534
|
+
}
|
|
225535
|
+
/**
|
|
225536
|
+
* Whether we hold a usable LLM key for this provider — its own credentials, or
|
|
225537
|
+
* the tenant's main keys when it is allowed to borrow them. An *absent*
|
|
225538
|
+
* `main_has_credentials` means the backend did not report on the main keys and
|
|
225539
|
+
* is treated as available (matching standalone's `@/lib/utils` and
|
|
225540
|
+
* `claw-sandbox`'s copy); only an explicit `false` rules the main keys out.
|
|
225541
|
+
*/
|
|
225542
|
+
function canSwitchLLm(llm) {
|
|
225543
|
+
if (llm === null || llm === void 0 ? void 0 : llm.has_credentials) {
|
|
225544
|
+
return true;
|
|
225545
|
+
}
|
|
225546
|
+
return ((llm === null || llm === void 0 ? void 0 : llm.can_use_main_keys) &&
|
|
225547
|
+
((llm === null || llm === void 0 ? void 0 : llm.main_has_credentials) === true || (llm === null || llm === void 0 ? void 0 : llm.main_has_credentials) === undefined));
|
|
225548
|
+
}
|
|
225549
|
+
/**
|
|
225550
|
+
* Compares two raw provider names by the label the user actually sees on the
|
|
225551
|
+
* card, not by the backend key — the two diverge often enough to matter
|
|
225552
|
+
* (`bedrock` renders as "Amazon", `azure_openai` as "Microsoft", `iblai` as
|
|
225553
|
+
* "ibl.ai"). Comparison is case-insensitive (`sensitivity: 'base'`) so casing
|
|
225554
|
+
* never produces a surprising order. `getDetails` is the host-provided resolver
|
|
225555
|
+
* the grid renders with, so the sort always matches what is on screen; unknown
|
|
225556
|
+
* providers fall back to their raw name as the display name.
|
|
225557
|
+
*/
|
|
225558
|
+
function compareLLMProvidersByDisplayName(a, b, getDetails) {
|
|
225559
|
+
return getDetails(a).name.localeCompare(getDetails(b).name, undefined, {
|
|
225560
|
+
sensitivity: 'base',
|
|
225561
|
+
});
|
|
225562
|
+
}
|
|
225563
|
+
/**
|
|
225564
|
+
* Whether the user can reach *any* model of this provider.
|
|
225565
|
+
*
|
|
225566
|
+
* This is exactly the provider-level half of the per-model `isDisabled` rule in
|
|
225567
|
+
* `llm-tab/llm-provider-modal.tsx`: a model row there is disabled when
|
|
225568
|
+
* `!canSwitchLLm(provider) || !canSwitchProvider(llms, provider.name)`, and both
|
|
225569
|
+
* of those depend only on the provider — so when either fails, *every* model row
|
|
225570
|
+
* of that provider is disabled. `canSwitchProvider` is just "has at least one
|
|
225571
|
+
* `chat_models` entry", which is checked inline here to keep the helper pure and
|
|
225572
|
+
* free of the surrounding provider list.
|
|
225573
|
+
*
|
|
225574
|
+
* `canSwitchLLm` can return `undefined` (its `can_use_main_keys` branch), hence
|
|
225575
|
+
* the explicit `Boolean(...)`.
|
|
225576
|
+
*/
|
|
225577
|
+
function canAccessProvider(provider) {
|
|
225578
|
+
var _a, _b;
|
|
225579
|
+
return Boolean(canSwitchLLm(provider !== null && provider !== void 0 ? provider : {})) && ((_b = (_a = provider === null || provider === void 0 ? void 0 : provider.chat_models) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
|
|
225580
|
+
}
|
|
225581
|
+
/**
|
|
225582
|
+
* Orders LLM provider cards as two alphabetical groups: providers the user can
|
|
225583
|
+
* actually use (see {@link canAccessProvider}) first, then the ones they can't —
|
|
225584
|
+
* each group sorted alphabetically by display name.
|
|
225585
|
+
*
|
|
225586
|
+
* The grouping predicate is deliberately the same one the grid grays cards with,
|
|
225587
|
+
* so the two always line up: group 1 renders normally, group 2 renders grayed.
|
|
225588
|
+
* Grouping on credentials alone would let a provider that has a key but ships no
|
|
225589
|
+
* chat models sort into the "usable" group while rendering grayed.
|
|
225590
|
+
*
|
|
225591
|
+
* Pure and non-mutating: RTK Query results are frozen, so the input is copied
|
|
225592
|
+
* before sorting. `Array.prototype.sort` is stable, so providers that tie on
|
|
225593
|
+
* both group and display name keep their original relative order.
|
|
225594
|
+
*/
|
|
225595
|
+
function sortLLMProvidersByCredentials(providers, getDetails) {
|
|
225596
|
+
return [...providers].sort((a, b) => {
|
|
225597
|
+
const aUsable = canAccessProvider(a);
|
|
225598
|
+
const bUsable = canAccessProvider(b);
|
|
225599
|
+
if (aUsable !== bUsable)
|
|
225600
|
+
return aUsable ? -1 : 1;
|
|
225601
|
+
return compareLLMProvidersByDisplayName(a.name, b.name, getDetails);
|
|
225602
|
+
});
|
|
225603
|
+
}
|
|
225604
|
+
function extractErrorMessage$1(error, fallback) {
|
|
225605
|
+
var _a, _b;
|
|
225606
|
+
const apiError = error;
|
|
225607
|
+
return ((_a = apiError === null || apiError === void 0 ? void 0 : apiError.data) === null || _a === void 0 ? void 0 : _a.error) || ((_b = apiError === null || apiError === void 0 ? void 0 : apiError.error) === null || _b === void 0 ? void 0 : _b.error) || fallback;
|
|
225608
|
+
}
|
|
225609
|
+
|
|
225610
|
+
// Logo + display name keyed by canonical provider name (see getProviderName).
|
|
225611
|
+
const PROVIDER_DETAILS_BY_NAME = {
|
|
225612
|
+
groq: { logo: '/llm-groq-provider.png', name: 'Groq' },
|
|
225613
|
+
nvidia: { logo: '/llm-nvidia-provider.webp', name: 'NVIDIA' },
|
|
225614
|
+
azure_openai: { logo: '/llm-microsoft-provider.png', name: 'Microsoft' },
|
|
225615
|
+
openai: { logo: '/llm-openai-provider-2.svg', name: 'OpenAI' },
|
|
225616
|
+
mistral: { logo: '/llm-mistral-provider.jpeg', name: 'Mistral' },
|
|
225617
|
+
google: { logo: '/llm-google-provider.svg', name: 'Google' },
|
|
225618
|
+
llama: { logo: '/llm-llama-provider.jpeg', name: 'Meta' },
|
|
225619
|
+
anthropic: { logo: '/llm-claude-provider.png', name: 'Anthropic' },
|
|
225620
|
+
perplexity: { logo: '/llm-perplexity-provider.webp', name: 'Perplexity' },
|
|
225621
|
+
deepseek: { logo: '/llm-deepseek-provider.png', name: 'DeepSeek' },
|
|
225622
|
+
xai: { logo: '/llm-xai-provider.jpg', name: 'xAI' },
|
|
225623
|
+
bedrock: { logo: '/llm-amazon-provider.png', name: 'Amazon' },
|
|
225624
|
+
alibaba: { logo: '/llm-alibaba-provider.png', name: 'Alibaba' },
|
|
225625
|
+
ibm: { logo: '/llm-ibm-provider.png', name: 'IBM' },
|
|
225626
|
+
iblai: { logo: '/llm-iblai-provider.png', name: 'ibl.ai' },
|
|
225627
|
+
};
|
|
225282
225628
|
/**
|
|
225283
225629
|
* Returns the canonical logo path and display name for an LLM provider.
|
|
225630
|
+
*
|
|
225631
|
+
* Resolution goes through {@link getProviderName}, so a backend wire key
|
|
225632
|
+
* (`azure_openai`), a human label (`Microsoft`) and an on-device catalog's
|
|
225633
|
+
* provider string (`Meta`, `Alibaba`) all land on the same entry — the provider
|
|
225634
|
+
* grid merges cloud and on-device providers into one list, so the two spellings
|
|
225635
|
+
* have to agree.
|
|
225636
|
+
*
|
|
225284
225637
|
* `logo` is a static-root path (e.g. `/llm-amazon-provider.png`); the package
|
|
225285
225638
|
* ships the canonical assets under `@iblai/web-containers/public/` — copy them
|
|
225286
225639
|
* into your app's static-root (`public/` for Next.js) so the paths resolve.
|
|
225287
225640
|
*/
|
|
225288
225641
|
function getLLMProviderDetails(llmProvider, llmName) {
|
|
225289
|
-
|
|
225290
|
-
|
|
225291
|
-
|
|
225292
|
-
|
|
225293
|
-
|
|
225294
|
-
|
|
225295
|
-
|
|
225296
|
-
|
|
225297
|
-
return { logo: '/llm-openai-provider-2.svg', name: 'OpenAI' };
|
|
225298
|
-
case 'mistral':
|
|
225299
|
-
return { logo: '/llm-mistral-provider.jpeg', name: 'Mistral' };
|
|
225300
|
-
case 'google':
|
|
225301
|
-
if (llmName)
|
|
225302
|
-
return { logo: '/llm-gemini-provider.png', name: 'Google' };
|
|
225303
|
-
return { logo: '/llm-google-provider.svg', name: 'Google' };
|
|
225304
|
-
case 'llama':
|
|
225305
|
-
return { logo: '/llm-llama-provider.jpeg', name: 'Meta' };
|
|
225306
|
-
case 'perplexity':
|
|
225307
|
-
return { logo: '/llm-perplexity-provider.webp', name: 'Perplexity' };
|
|
225308
|
-
case 'deepseek':
|
|
225309
|
-
return { logo: '/llm-deepseek-provider.png', name: 'DeepSeek' };
|
|
225310
|
-
case 'xai':
|
|
225311
|
-
return { logo: '/llm-xai-provider.jpg', name: 'xAI' };
|
|
225312
|
-
case 'IBLChatAnthropic':
|
|
225313
|
-
case 'anthropic':
|
|
225314
|
-
return { logo: '/llm-claude-provider.png', name: 'Anthropic' };
|
|
225315
|
-
case 'IBLChatNvidia':
|
|
225316
|
-
case 'nvidia':
|
|
225317
|
-
return { logo: '/llm-nvidia-provider.webp', name: 'NVIDIA' };
|
|
225318
|
-
case 'bedrock':
|
|
225319
|
-
case 'amazon-bedrock':
|
|
225320
|
-
case 'amazon_bedrock':
|
|
225321
|
-
case 'IBLChatBedrock':
|
|
225322
|
-
return { logo: '/llm-amazon-provider.png', name: 'Amazon' };
|
|
225323
|
-
// ibl.ai's own hosted provider. The backend key is `iblai` (NameEnum.IBLAI);
|
|
225324
|
-
// the other cases cover the `IBLChat<Vendor>` house spelling and the
|
|
225325
|
-
// dotted/dashed/underscored brand forms seen in labels and older payloads.
|
|
225326
|
-
case 'iblai':
|
|
225327
|
-
case 'IBLAI':
|
|
225328
|
-
case 'ibl.ai':
|
|
225329
|
-
case 'ibl_ai':
|
|
225330
|
-
case 'ibl-ai':
|
|
225331
|
-
case 'ibl':
|
|
225332
|
-
case 'IBLChatIBL':
|
|
225333
|
-
case 'IBLChatIBLAI':
|
|
225334
|
-
return { logo: '/llm-iblai-provider.png', name: 'ibl.ai' };
|
|
225335
|
-
default:
|
|
225336
|
-
return { logo: '/llm-generic-provider.png', name: llmProvider };
|
|
225337
|
-
}
|
|
225642
|
+
var _a;
|
|
225643
|
+
const name = getProviderName(llmProvider);
|
|
225644
|
+
// OpenAI and Google use a model-specific logo when a concrete model is named.
|
|
225645
|
+
if (name === 'openai' && llmName)
|
|
225646
|
+
return { logo: '/llm-openai-provider.jpg', name: 'OpenAI' };
|
|
225647
|
+
if (name === 'google' && llmName)
|
|
225648
|
+
return { logo: '/llm-gemini-provider.png', name: 'Google' };
|
|
225649
|
+
return (_a = PROVIDER_DETAILS_BY_NAME[name]) !== null && _a !== void 0 ? _a : { logo: '/llm-generic-provider.png', name: llmProvider };
|
|
225338
225650
|
}
|
|
225339
225651
|
|
|
225340
225652
|
// Dynamic schema based on selected provider
|
|
@@ -262271,7 +262583,7 @@ const resourceTypes = [
|
|
|
262271
262583
|
* rate limit.
|
|
262272
262584
|
*/
|
|
262273
262585
|
const SPEND_CAP_EXCEEDED_ERROR_CODE = 'spend_cap_exceeded';
|
|
262274
|
-
const extractErrorMessage
|
|
262586
|
+
const extractErrorMessage = (error, fallback) => {
|
|
262275
262587
|
var _a, _b, _c;
|
|
262276
262588
|
const apiError = error;
|
|
262277
262589
|
if (((_a = apiError === null || apiError === void 0 ? void 0 : apiError.data) === null || _a === void 0 ? void 0 : _a.error_code) === SPEND_CAP_EXCEEDED_ERROR_CODE) {
|
|
@@ -262350,7 +262662,7 @@ function GithubFileUploadModal() {
|
|
|
262350
262662
|
}
|
|
262351
262663
|
catch (error) {
|
|
262352
262664
|
console.error(JSON.stringify(error));
|
|
262353
|
-
const errorMessage = extractErrorMessage
|
|
262665
|
+
const errorMessage = extractErrorMessage(error, t('resourceModalGithubFileUploadModal.errorAddingDocument'));
|
|
262354
262666
|
toast.error(errorMessage);
|
|
262355
262667
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
262356
262668
|
}
|
|
@@ -262417,7 +262729,7 @@ function LocalFileUploadModal({ resource, maxFileSizeMB = DEFAULT_MAX_FILE_SIZE_
|
|
|
262417
262729
|
}
|
|
262418
262730
|
catch (error) {
|
|
262419
262731
|
console.error(JSON.stringify(error));
|
|
262420
|
-
const errorMessage = extractErrorMessage
|
|
262732
|
+
const errorMessage = extractErrorMessage(error, t('resourceModalLocalFileUploadModal.errorAddingDocument'));
|
|
262421
262733
|
toast.error(errorMessage);
|
|
262422
262734
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
262423
262735
|
}
|
|
@@ -262520,7 +262832,7 @@ function UrlUploadModal({ resource }) {
|
|
|
262520
262832
|
}
|
|
262521
262833
|
catch (error) {
|
|
262522
262834
|
console.error(JSON.stringify(error));
|
|
262523
|
-
const errorMessage = extractErrorMessage
|
|
262835
|
+
const errorMessage = extractErrorMessage(error, t('resourceModalUrlUploadModal.errorAddingTrainingDocument'));
|
|
262524
262836
|
toast.error(errorMessage);
|
|
262525
262837
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
262526
262838
|
}
|
|
@@ -262608,7 +262920,7 @@ function useWebsiteCrawlerResource(resource) {
|
|
|
262608
262920
|
}
|
|
262609
262921
|
catch (error) {
|
|
262610
262922
|
console.error(JSON.stringify(error));
|
|
262611
|
-
const errorMessage = extractErrorMessage
|
|
262923
|
+
const errorMessage = extractErrorMessage(error, 'Error submitting web crawl data');
|
|
262612
262924
|
toast.error(errorMessage);
|
|
262613
262925
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
262614
262926
|
}
|
|
@@ -262798,7 +263110,7 @@ const useGoogleDrivePicker = ({ tenantKey, mentorId }) => {
|
|
|
262798
263110
|
}
|
|
262799
263111
|
catch (error) {
|
|
262800
263112
|
console.error(JSON.stringify(error));
|
|
262801
|
-
const errorMessage = extractErrorMessage
|
|
263113
|
+
const errorMessage = extractErrorMessage(error, 'Error adding training document');
|
|
262802
263114
|
toast.error(errorMessage);
|
|
262803
263115
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
262804
263116
|
}
|
|
@@ -263103,7 +263415,7 @@ const useDropboxPicker = ({ tenantKey, mentorId, cancel = () => { }, linkType =
|
|
|
263103
263415
|
}
|
|
263104
263416
|
catch (error) {
|
|
263105
263417
|
console.error(JSON.stringify(error));
|
|
263106
|
-
const errorMessage = extractErrorMessage
|
|
263418
|
+
const errorMessage = extractErrorMessage(error, 'Error adding training document');
|
|
263107
263419
|
toast.error(errorMessage);
|
|
263108
263420
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
263109
263421
|
}
|
|
@@ -263328,7 +263640,7 @@ const useOneDrivePicker = ({ tenantKey, mentorId }) => {
|
|
|
263328
263640
|
}
|
|
263329
263641
|
catch (error) {
|
|
263330
263642
|
console.error(JSON.stringify(error));
|
|
263331
|
-
const errorMessage = extractErrorMessage
|
|
263643
|
+
const errorMessage = extractErrorMessage(error, 'Error adding training document');
|
|
263332
263644
|
toast.error(errorMessage);
|
|
263333
263645
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
263334
263646
|
}
|
|
@@ -270320,86 +270632,51 @@ var copyMentorModal = /*#__PURE__*/Object.freeze({
|
|
|
270320
270632
|
});
|
|
270321
270633
|
|
|
270322
270634
|
/**
|
|
270323
|
-
*
|
|
270324
|
-
*
|
|
270325
|
-
|
|
270326
|
-
|
|
270327
|
-
|
|
270328
|
-
return !!(provider === null || provider === void 0 ? void 0 : provider.chat_models) && provider.chat_models.length > 0;
|
|
270329
|
-
}
|
|
270330
|
-
/**
|
|
270331
|
-
* Whether we hold a usable LLM key for this provider — its own credentials, or
|
|
270332
|
-
* the tenant's main keys when it is allowed to borrow them. An *absent*
|
|
270333
|
-
* `main_has_credentials` means the backend did not report on the main keys and
|
|
270334
|
-
* is treated as available (matching standalone's `@/lib/utils` and
|
|
270335
|
-
* `claw-sandbox`'s copy); only an explicit `false` rules the main keys out.
|
|
270336
|
-
*/
|
|
270337
|
-
function canSwitchLLm(llm) {
|
|
270338
|
-
if (llm === null || llm === void 0 ? void 0 : llm.has_credentials) {
|
|
270339
|
-
return true;
|
|
270340
|
-
}
|
|
270341
|
-
return ((llm === null || llm === void 0 ? void 0 : llm.can_use_main_keys) &&
|
|
270342
|
-
((llm === null || llm === void 0 ? void 0 : llm.main_has_credentials) === true || (llm === null || llm === void 0 ? void 0 : llm.main_has_credentials) === undefined));
|
|
270343
|
-
}
|
|
270344
|
-
/**
|
|
270345
|
-
* Compares two raw provider names by the label the user actually sees on the
|
|
270346
|
-
* card, not by the backend key — the two diverge often enough to matter
|
|
270347
|
-
* (`bedrock` renders as "Amazon", `azure_openai` as "Microsoft", `iblai` as
|
|
270348
|
-
* "ibl.ai"). Comparison is case-insensitive (`sensitivity: 'base'`) so casing
|
|
270349
|
-
* never produces a surprising order. `getDetails` is the host-provided resolver
|
|
270350
|
-
* the grid renders with, so the sort always matches what is on screen; unknown
|
|
270351
|
-
* providers fall back to their raw name as the display name.
|
|
270352
|
-
*/
|
|
270353
|
-
function compareLLMProvidersByDisplayName(a, b, getDetails) {
|
|
270354
|
-
return getDetails(a).name.localeCompare(getDetails(b).name, undefined, {
|
|
270355
|
-
sensitivity: 'base',
|
|
270356
|
-
});
|
|
270357
|
-
}
|
|
270358
|
-
/**
|
|
270359
|
-
* Whether the user can reach *any* model of this provider.
|
|
270360
|
-
*
|
|
270361
|
-
* This is exactly the provider-level half of the per-model `isDisabled` rule in
|
|
270362
|
-
* `llm-tab/llm-provider-modal.tsx`: a model row there is disabled when
|
|
270363
|
-
* `!canSwitchLLm(provider) || !canSwitchProvider(llms, provider.name)`, and both
|
|
270364
|
-
* of those depend only on the provider — so when either fails, *every* model row
|
|
270365
|
-
* of that provider is disabled. `canSwitchProvider` is just "has at least one
|
|
270366
|
-
* `chat_models` entry", which is checked inline here to keep the helper pure and
|
|
270367
|
-
* free of the surrounding provider list.
|
|
270368
|
-
*
|
|
270369
|
-
* `canSwitchLLm` can return `undefined` (its `can_use_main_keys` branch), hence
|
|
270370
|
-
* the explicit `Boolean(...)`.
|
|
270635
|
+
* Window event any surface dispatches after changing the device-global
|
|
270636
|
+
* on-device selection — a model pick in the LLM tab's provider modal, or the
|
|
270637
|
+
* Local Models master toggle in Profile → Advanced. `storage` events only fire
|
|
270638
|
+
* cross-tab, so this covers the same-tab case (e.g. a nav-bar badge that has to
|
|
270639
|
+
* update the instant a model is picked in a modal above it).
|
|
270371
270640
|
*/
|
|
270372
|
-
|
|
270373
|
-
var _a, _b;
|
|
270374
|
-
return Boolean(canSwitchLLm(provider !== null && provider !== void 0 ? provider : {})) && ((_b = (_a = provider === null || provider === void 0 ? void 0 : provider.chat_models) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
|
|
270375
|
-
}
|
|
270641
|
+
const LOCAL_LLM_CHANGED_EVENT = 'ibl:local-llm-changed';
|
|
270376
270642
|
/**
|
|
270377
|
-
*
|
|
270378
|
-
*
|
|
270379
|
-
*
|
|
270380
|
-
*
|
|
270381
|
-
*
|
|
270382
|
-
* so the two always line up: group 1 renders normally, group 2 renders grayed.
|
|
270383
|
-
* Grouping on credentials alone would let a provider that has a key but ships no
|
|
270384
|
-
* chat models sort into the "usable" group while rendering grayed.
|
|
270385
|
-
*
|
|
270386
|
-
* Pure and non-mutating: RTK Query results are frozen, so the input is copied
|
|
270387
|
-
* before sorting. `Array.prototype.sort` is stable, so providers that tie on
|
|
270388
|
-
* both group and display name keep their original relative order.
|
|
270643
|
+
* Reactive view of the on-device model selection (`ibl_local_llm_enabled` +
|
|
270644
|
+
* `ibl_local_llm_model` in localStorage). Reads on mount — so it is
|
|
270645
|
+
* SSR/hydration-safe (nothing resolves until the client mounts) — and re-reads
|
|
270646
|
+
* on cross-tab `storage` events and same-tab {@link LOCAL_LLM_CHANGED_EVENT}
|
|
270647
|
+
* events.
|
|
270389
270648
|
*/
|
|
270390
|
-
function
|
|
270391
|
-
|
|
270392
|
-
|
|
270393
|
-
|
|
270394
|
-
|
|
270395
|
-
return aUsable ? -1 : 1;
|
|
270396
|
-
return compareLLMProvidersByDisplayName(a.name, b.name, getDetails);
|
|
270649
|
+
function useSelectedLocalModel() {
|
|
270650
|
+
const [sel, setSel] = useState({
|
|
270651
|
+
isLocal: false,
|
|
270652
|
+
model: null,
|
|
270653
|
+
modelId: null,
|
|
270397
270654
|
});
|
|
270398
|
-
|
|
270399
|
-
|
|
270400
|
-
|
|
270401
|
-
|
|
270402
|
-
|
|
270655
|
+
useEffect(() => {
|
|
270656
|
+
const read = () => {
|
|
270657
|
+
var _a;
|
|
270658
|
+
const enabled = isLocalLLMEnabled();
|
|
270659
|
+
const id = getLocalLLMModel();
|
|
270660
|
+
const model = id ? ((_a = LOCAL_MODELS.find((m) => m.id === id)) !== null && _a !== void 0 ? _a : null) : null;
|
|
270661
|
+
// Self-heal the derived tool-support cache. Local chat routes on
|
|
270662
|
+
// `ibl_local_llm_tool_support`, but an older selection may have left it
|
|
270663
|
+
// stale/false — which makes streaming reject a genuinely tool-capable
|
|
270664
|
+
// model. The catalog is the source of truth; reconcile here. Write only on
|
|
270665
|
+
// a real mismatch so it can't loop.
|
|
270666
|
+
if (enabled && model && getLocalLLMToolSupport() !== model.tool_support) {
|
|
270667
|
+
setLocalLLMToolSupport(model.tool_support);
|
|
270668
|
+
}
|
|
270669
|
+
setSel({ isLocal: enabled && !!id, model, modelId: id });
|
|
270670
|
+
};
|
|
270671
|
+
read();
|
|
270672
|
+
window.addEventListener('storage', read);
|
|
270673
|
+
window.addEventListener(LOCAL_LLM_CHANGED_EVENT, read);
|
|
270674
|
+
return () => {
|
|
270675
|
+
window.removeEventListener('storage', read);
|
|
270676
|
+
window.removeEventListener(LOCAL_LLM_CHANGED_EVENT, read);
|
|
270677
|
+
};
|
|
270678
|
+
}, []);
|
|
270679
|
+
return sel;
|
|
270403
270680
|
}
|
|
270404
270681
|
|
|
270405
270682
|
const AGENT_LLM_TAB_LABELS = {
|
|
@@ -270407,18 +270684,47 @@ const AGENT_LLM_TAB_LABELS = {
|
|
|
270407
270684
|
title: 'LLM Configuration',
|
|
270408
270685
|
description: 'Configure the language model settings for your agent.',
|
|
270409
270686
|
},
|
|
270687
|
+
infoBox: 'This is the brain behind your agent — the AI model that powers every reply. Bigger models reason better on tricky questions; lighter ones are faster and cost less. Not sure? The default is a solid all-rounder.',
|
|
270410
270688
|
search: {
|
|
270411
270689
|
placeholder: 'Search Providers',
|
|
270412
270690
|
},
|
|
270691
|
+
providerLogoAlt: (providerName) => `${providerName} logo`,
|
|
270413
270692
|
toasts: {
|
|
270414
270693
|
updateSuccess: 'LLM updated successfully',
|
|
270415
270694
|
updateError: 'Failed to update LLM',
|
|
270416
270695
|
},
|
|
270417
270696
|
providerModal: {
|
|
270418
270697
|
title: 'LLM Selection',
|
|
270419
|
-
description:
|
|
270698
|
+
description: (providerName) => `Select one of the agents provided by ${providerName}`,
|
|
270420
270699
|
searchPlaceholder: 'Search',
|
|
270421
270700
|
helpText: 'Choose your preferred LLM from the available provider to tailor your experience.',
|
|
270701
|
+
providerIconAlt: (providerName) => `${providerName} icon`,
|
|
270702
|
+
tooLargeTitle: 'This model may be too large for your system',
|
|
270703
|
+
tooLargeDescription: (modelName, modelSize) => `${modelName} needs about ${modelSize}. It may run very slowly or fail to load on this machine. Download anyway?`,
|
|
270704
|
+
cancel: 'Cancel',
|
|
270705
|
+
downloadAnyway: 'Download anyway',
|
|
270706
|
+
alreadyDownloadingTitle: 'A model is already downloading',
|
|
270707
|
+
unnamedModel: 'A model',
|
|
270708
|
+
alreadyDownloadingDescription: (modelName) => `${modelName} is being downloaded. Only one on-device model downloads at a time — wait for it to finish, or click ${modelName} in the list to cancel it.`,
|
|
270709
|
+
gotIt: 'Got it',
|
|
270710
|
+
announceDownloaded: (modelName) => `${modelName} downloaded`,
|
|
270711
|
+
announceCancelled: 'Download cancelled',
|
|
270712
|
+
announceFailed: 'Download failed',
|
|
270713
|
+
announceStarted: (modelName) => `Started downloading ${modelName}`,
|
|
270714
|
+
localModel: {
|
|
270715
|
+
onDevice: 'On-device',
|
|
270716
|
+
starting: 'Starting…',
|
|
270717
|
+
cancel: 'Cancel',
|
|
270718
|
+
inUse: 'In use',
|
|
270719
|
+
downloadFailedRetry: 'Download failed — retry',
|
|
270720
|
+
ariaDownload: (modelName, modelSize) => `Download ${modelName}, ${modelSize}, on-device model`,
|
|
270721
|
+
ariaStarting: (modelName) => `Starting download of ${modelName}`,
|
|
270722
|
+
ariaDownloading: (modelName, percent) => `Cancel download of ${modelName}, ${percent} percent`,
|
|
270723
|
+
ariaInstalled: (modelName) => `Use ${modelName}, on-device model`,
|
|
270724
|
+
ariaSelected: (modelName) => `Selected ${modelName}, on-device model, in use`,
|
|
270725
|
+
ariaError: (modelName) => `Retry download of ${modelName}`,
|
|
270726
|
+
ariaErrorWithReason: (modelName, reason) => `Retry download of ${modelName}, ${reason}`,
|
|
270727
|
+
},
|
|
270422
270728
|
},
|
|
270423
270729
|
};
|
|
270424
270730
|
function buildLLMTabLabels(t) {
|
|
@@ -270427,18 +270733,47 @@ function buildLLMTabLabels(t) {
|
|
|
270427
270733
|
title: t('llmTabLabels.header.title'),
|
|
270428
270734
|
description: t('llmTabLabels.header.description'),
|
|
270429
270735
|
},
|
|
270736
|
+
infoBox: t('llmTabLabels.infoBox'),
|
|
270430
270737
|
search: {
|
|
270431
270738
|
placeholder: t('llmTabLabels.search.placeholder'),
|
|
270432
270739
|
},
|
|
270740
|
+
providerLogoAlt: (providerName) => t('llmTabLabels.providerLogoAlt', { providerName }),
|
|
270433
270741
|
toasts: {
|
|
270434
270742
|
updateSuccess: t('llmTabLabels.toasts.updateSuccess'),
|
|
270435
270743
|
updateError: t('llmTabLabels.toasts.updateError'),
|
|
270436
270744
|
},
|
|
270437
270745
|
providerModal: {
|
|
270438
270746
|
title: t('llmTabLabels.providerModal.title'),
|
|
270439
|
-
description: t('llmTabLabels.providerModal.description'),
|
|
270747
|
+
description: (providerName) => t('llmTabLabels.providerModal.description', { providerName }),
|
|
270440
270748
|
searchPlaceholder: t('llmTabLabels.providerModal.searchPlaceholder'),
|
|
270441
270749
|
helpText: t('llmTabLabels.providerModal.helpText'),
|
|
270750
|
+
providerIconAlt: (providerName) => t('llmTabLabels.providerModal.providerIconAlt', { providerName }),
|
|
270751
|
+
tooLargeTitle: t('llmTabLabels.providerModal.tooLargeTitle'),
|
|
270752
|
+
tooLargeDescription: (modelName, modelSize) => t('llmTabLabels.providerModal.tooLargeDescription', { modelName, modelSize }),
|
|
270753
|
+
cancel: t('llmTabLabels.providerModal.cancel'),
|
|
270754
|
+
downloadAnyway: t('llmTabLabels.providerModal.downloadAnyway'),
|
|
270755
|
+
alreadyDownloadingTitle: t('llmTabLabels.providerModal.alreadyDownloadingTitle'),
|
|
270756
|
+
unnamedModel: t('llmTabLabels.providerModal.unnamedModel'),
|
|
270757
|
+
alreadyDownloadingDescription: (modelName) => t('llmTabLabels.providerModal.alreadyDownloadingDescription', { modelName }),
|
|
270758
|
+
gotIt: t('llmTabLabels.providerModal.gotIt'),
|
|
270759
|
+
announceDownloaded: (modelName) => t('llmTabLabels.providerModal.announceDownloaded', { modelName }),
|
|
270760
|
+
announceCancelled: t('llmTabLabels.providerModal.announceCancelled'),
|
|
270761
|
+
announceFailed: t('llmTabLabels.providerModal.announceFailed'),
|
|
270762
|
+
announceStarted: (modelName) => t('llmTabLabels.providerModal.announceStarted', { modelName }),
|
|
270763
|
+
localModel: {
|
|
270764
|
+
onDevice: t('llmTabLabels.providerModal.localModel.onDevice'),
|
|
270765
|
+
starting: t('llmTabLabels.providerModal.localModel.starting'),
|
|
270766
|
+
cancel: t('llmTabLabels.providerModal.localModel.cancel'),
|
|
270767
|
+
inUse: t('llmTabLabels.providerModal.localModel.inUse'),
|
|
270768
|
+
downloadFailedRetry: t('llmTabLabels.providerModal.localModel.downloadFailedRetry'),
|
|
270769
|
+
ariaDownload: (modelName, modelSize) => t('llmTabLabels.providerModal.localModel.ariaDownload', { modelName, modelSize }),
|
|
270770
|
+
ariaStarting: (modelName) => t('llmTabLabels.providerModal.localModel.ariaStarting', { modelName }),
|
|
270771
|
+
ariaDownloading: (modelName, percent) => t('llmTabLabels.providerModal.localModel.ariaDownloading', { modelName, percent }),
|
|
270772
|
+
ariaInstalled: (modelName) => t('llmTabLabels.providerModal.localModel.ariaInstalled', { modelName }),
|
|
270773
|
+
ariaSelected: (modelName) => t('llmTabLabels.providerModal.localModel.ariaSelected', { modelName }),
|
|
270774
|
+
ariaError: (modelName) => t('llmTabLabels.providerModal.localModel.ariaError', { modelName }),
|
|
270775
|
+
ariaErrorWithReason: (modelName, reason) => t('llmTabLabels.providerModal.localModel.ariaErrorWithReason', { modelName, reason }),
|
|
270776
|
+
},
|
|
270442
270777
|
},
|
|
270443
270778
|
};
|
|
270444
270779
|
}
|
|
@@ -270450,38 +270785,287 @@ function resolveLLMTabLabels(override, t) {
|
|
|
270450
270785
|
return deepMerge(base, override);
|
|
270451
270786
|
}
|
|
270452
270787
|
|
|
270453
|
-
|
|
270788
|
+
/**
|
|
270789
|
+
* A local (on-device) model presented in the same list as cloud models. It uses
|
|
270790
|
+
* the same provider logo as the cloud rows; the model name gets its own line and
|
|
270791
|
+
* the on-device badge, size, and download/status affordance sit on a subtitle
|
|
270792
|
+
* line beneath it. One click toggles download ⇄ cancel; once installed a click
|
|
270793
|
+
* selects it.
|
|
270794
|
+
*/
|
|
270795
|
+
function LocalModelRow({ name, size, logo, status, progress, disabled = false, disabledReason, errorMessage, onActivate, labels = AGENT_LLM_TAB_LABELS.providerModal.localModel, }) {
|
|
270796
|
+
const selected = status === 'selected';
|
|
270797
|
+
const downloading = status === 'downloading' || status === 'starting';
|
|
270798
|
+
// A resting row (nothing downloading) can still be disabled when another
|
|
270799
|
+
// model is pulling; a row that is itself downloading stays clickable (cancel).
|
|
270800
|
+
const isBusyDisabled = disabled && !downloading && !selected;
|
|
270801
|
+
const inert = isBusyDisabled || selected;
|
|
270802
|
+
const pct = Math.round(Math.max(0, Math.min(100, progress)));
|
|
270803
|
+
const ariaLabel = (() => {
|
|
270804
|
+
switch (status) {
|
|
270805
|
+
case 'not-installed':
|
|
270806
|
+
return labels.ariaDownload(name, size);
|
|
270807
|
+
case 'starting':
|
|
270808
|
+
return labels.ariaStarting(name);
|
|
270809
|
+
case 'downloading':
|
|
270810
|
+
return labels.ariaDownloading(name, pct);
|
|
270811
|
+
case 'installed':
|
|
270812
|
+
return labels.ariaInstalled(name);
|
|
270813
|
+
case 'selected':
|
|
270814
|
+
return labels.ariaSelected(name);
|
|
270815
|
+
case 'error':
|
|
270816
|
+
return errorMessage
|
|
270817
|
+
? labels.ariaErrorWithReason(name, errorMessage)
|
|
270818
|
+
: labels.ariaError(name);
|
|
270819
|
+
}
|
|
270820
|
+
})();
|
|
270821
|
+
return (jsxs("button", { type: "button", "data-testid": "llm-local-model-row", "data-model": name, "data-status": status, disabled: inert, "aria-label": ariaLabel, title: isBusyDisabled ? disabledReason : ariaLabel, onClick: () => {
|
|
270822
|
+
if (!inert)
|
|
270823
|
+
onActivate();
|
|
270824
|
+
}, className: cn(
|
|
270825
|
+
// `isolate` makes the button its own stacking context so the absolute
|
|
270826
|
+
// progress fill (z-0) layers above the button's background and below the
|
|
270827
|
+
// content (z-10) — without it the fill can render behind the background.
|
|
270828
|
+
'group relative isolate flex items-center gap-3 overflow-hidden rounded-lg border p-4 text-left transition-colors', selected
|
|
270829
|
+
? 'cursor-default border-blue-500 bg-blue-50'
|
|
270830
|
+
: isBusyDisabled
|
|
270831
|
+
? 'cursor-not-allowed border-gray-200 opacity-60'
|
|
270832
|
+
: 'cursor-pointer border-gray-200 hover:border-blue-500 hover:bg-blue-50'), children: [downloading && (jsx("span", { "aria-hidden": "true", "data-testid": "llm-local-model-progress", className: "pointer-events-none absolute inset-y-0 left-0 z-0 bg-blue-500/40 motion-safe:transition-[width] motion-safe:duration-300",
|
|
270833
|
+
// Minimum width so the bar is visible the moment a download starts
|
|
270834
|
+
// (progress 0 / "starting"), then tracks real progress.
|
|
270835
|
+
style: { width: `${status === 'starting' ? 6 : Math.max(pct, 6)}%` } })), jsx("span", { className: "relative z-10 flex h-8 w-8 flex-shrink-0 items-center justify-center overflow-hidden rounded-lg", children: jsx(Image$4, { src: logo, alt: "", "aria-hidden": "true", width: 32, height: 32, className: cn('h-full w-full object-contain', {
|
|
270836
|
+
grayscale: isBusyDisabled,
|
|
270837
|
+
}), loading: "lazy" }) }), jsxs("span", { className: "relative z-10 flex min-w-0 flex-1 flex-col gap-1.5", children: [jsx("span", { className: "truncate text-sm font-medium text-[#646464] lowercase", children: name }), jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-sm text-gray-500", children: [jsx("span", { className: "flex-shrink-0 rounded bg-gray-100 px-2 py-0.5 text-sm font-medium text-gray-500", children: labels.onDevice }), status === 'not-installed' && (jsxs(Fragment$1, { children: [jsx("span", { "aria-hidden": "true", children: "\u00B7" }), jsx("span", { children: size }), jsx(Download, { className: "h-4 w-4", "aria-hidden": "true" })] })), status === 'starting' && (jsxs(Fragment$1, { children: [jsx("span", { "aria-hidden": "true", children: "\u00B7" }), jsx("span", { children: labels.starting })] })), status === 'downloading' && (jsxs(Fragment$1, { children: [jsx("span", { "aria-hidden": "true", children: "\u00B7" }), jsxs("span", { className: "tabular-nums group-hover:hidden group-focus-visible:hidden", children: [pct, "%"] }), jsxs("span", { className: "hidden items-center gap-1 text-blue-600 group-hover:flex group-focus-visible:flex", children: [jsx(X$2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), labels.cancel] })] })), status === 'selected' && (jsxs("span", { className: "flex items-center gap-1 text-blue-600", children: [jsx(Check, { className: "h-4 w-4", "aria-hidden": "true" }), labels.inUse] })), status === 'error' && (jsxs(Fragment$1, { children: [jsx("span", { "aria-hidden": "true", children: "\u00B7" }), jsx("span", { className: "truncate text-red-500", children: errorMessage || labels.downloadFailedRetry }), jsx(RotateCcw, { className: "h-4 w-4 flex-shrink-0 text-red-500", "aria-hidden": "true" })] }))] })] })] }));
|
|
270838
|
+
}
|
|
270839
|
+
|
|
270840
|
+
function LLMProviderModal({ isOpen, onClose, onSelect, llmProvider, isSelecting, mentorSettings, llms, getLLMProviderDetails: getLLMProviderDetails$1 = getLLMProviderDetails, labels = AGENT_LLM_TAB_LABELS.providerModal, }) {
|
|
270841
|
+
var _a, _b;
|
|
270454
270842
|
const [searchQuery, setSearchQuery] = React__default.useState('');
|
|
270843
|
+
// On-device model download state (Tauri desktop only; `isAvailable` is false
|
|
270844
|
+
// on web, which hides every local row below).
|
|
270845
|
+
const { isAvailable, state: downloadState, ollamaStatus, systemMemory, startDownload, cancelDownload, } = useModelDownload();
|
|
270846
|
+
// Mirror the device-global local selection so picks re-render immediately.
|
|
270847
|
+
const [localSel, setLocalSel] = React__default.useState(() => ({
|
|
270848
|
+
enabled: isLocalLLMEnabled(),
|
|
270849
|
+
model: getLocalLLMModel(),
|
|
270850
|
+
}));
|
|
270851
|
+
// Model awaiting the "may be too large" confirmation before its download
|
|
270852
|
+
// starts; null when no confirmation is pending.
|
|
270853
|
+
const [pendingModel, setPendingModel] = React__default.useState(null);
|
|
270854
|
+
// Name of the model currently downloading, shown in an info dialog when the
|
|
270855
|
+
// user clicks a DIFFERENT model (one on-device download at a time). Null when
|
|
270856
|
+
// no such notice is showing.
|
|
270857
|
+
const [busyDownloadName, setBusyDownloadName] = React__default.useState(null);
|
|
270858
|
+
// The model THIS picker asked to download. Modal-local (not the shared, churny
|
|
270859
|
+
// download state) so it survives the hook's once-per-load reset / remounts /
|
|
270860
|
+
// reloads that wipe `downloadState.activeModel` — the pull's progress events
|
|
270861
|
+
// carry no model id, so once `activeModel` is lost nothing restores it and the
|
|
270862
|
+
// row can't recognize its own in-flight download (bar advances, row still says
|
|
270863
|
+
// "Download"). Cleared only on a terminal status, so the reset's transient
|
|
270864
|
+
// `idle` doesn't drop it mid-pull.
|
|
270865
|
+
const [requestedModel, setRequestedModel] = React__default.useState(null);
|
|
270866
|
+
React__default.useEffect(() => {
|
|
270867
|
+
if (downloadState.status === 'completed' ||
|
|
270868
|
+
downloadState.status === 'cancelled' ||
|
|
270869
|
+
downloadState.status === 'error') {
|
|
270870
|
+
setRequestedModel(null);
|
|
270871
|
+
}
|
|
270872
|
+
}, [downloadState.status]);
|
|
270873
|
+
// Which on-device model is downloading right now: prefer this picker's own
|
|
270874
|
+
// request, fall back to the shared state's id when present.
|
|
270875
|
+
const downloadingModel = requestedModel !== null && requestedModel !== void 0 ? requestedModel : downloadState.activeModel;
|
|
270876
|
+
// The last on-device model whose download reported `completed`. Held sticky so
|
|
270877
|
+
// its row shows "installed" straight through the gap between the terminal
|
|
270878
|
+
// `completed` event and the /api/tags refresh landing — otherwise the completed
|
|
270879
|
+
// handler's checkStatus churns downloadState.status (completed → checking →
|
|
270880
|
+
// idle) first and the row flickers back to a "Download" button.
|
|
270881
|
+
const [lastCompleted, setLastCompleted] = React__default.useState(null);
|
|
270882
|
+
React__default.useEffect(() => {
|
|
270883
|
+
if (downloadState.status === 'completed' && downloadingModel) {
|
|
270884
|
+
setLastCompleted(downloadingModel);
|
|
270885
|
+
}
|
|
270886
|
+
}, [downloadState.status, downloadingModel]);
|
|
270455
270887
|
const filteredLLMs = React__default.useMemo(() => {
|
|
270456
|
-
|
|
270888
|
+
var _a, _b;
|
|
270889
|
+
// Guard `chat_models` (not just `llmProvider`): a provider can come back from
|
|
270890
|
+
// the API without a models array, and an undefined `.filter` here throws in
|
|
270891
|
+
// render and unmounts the whole dialog. Coalesce so the `.map` consumer below
|
|
270892
|
+
// always gets an array.
|
|
270893
|
+
return ((_b = (_a = llmProvider === null || llmProvider === void 0 ? void 0 : llmProvider.chat_models) === null || _a === void 0 ? void 0 : _a.filter((llm) => {
|
|
270894
|
+
var _a;
|
|
270895
|
+
const query = searchQuery.toLowerCase();
|
|
270896
|
+
// Match either label: rows are shown by `display_name`, so searching
|
|
270897
|
+
// what you can read has to work, but the raw key stays searchable for
|
|
270898
|
+
// anyone who knows it (e.g. "gpt-4.1" or "amazon.nova").
|
|
270899
|
+
return (llm.llm_name.toLowerCase().includes(query) ||
|
|
270900
|
+
((_a = llm.display_name) !== null && _a !== void 0 ? _a : '').toLowerCase().includes(query));
|
|
270901
|
+
})) !== null && _b !== void 0 ? _b : []);
|
|
270457
270902
|
}, [searchQuery, llmProvider]);
|
|
270903
|
+
// Local models belonging to THIS provider (merged into the same list).
|
|
270904
|
+
const localModels = React__default.useMemo(() => {
|
|
270905
|
+
if (!isAvailable)
|
|
270906
|
+
return [];
|
|
270907
|
+
const key = getProviderName(llmProvider.name);
|
|
270908
|
+
return LOCAL_MODELS.filter((m) => getProviderName(m.provider) === key &&
|
|
270909
|
+
m.name.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
270910
|
+
}, [isAvailable, llmProvider.name, searchQuery]);
|
|
270458
270911
|
const switchLLMAllowed = canSwitchLLm(llmProvider);
|
|
270459
270912
|
const switchProviderAllowed = canSwitchProvider(llms, llmProvider.name);
|
|
270460
|
-
|
|
270461
|
-
|
|
270462
|
-
|
|
270463
|
-
|
|
270464
|
-
|
|
270465
|
-
|
|
270466
|
-
|
|
270467
|
-
|
|
270468
|
-
|
|
270469
|
-
|
|
270470
|
-
|
|
270471
|
-
|
|
270472
|
-
|
|
270473
|
-
|
|
270474
|
-
|
|
270475
|
-
|
|
270476
|
-
|
|
270477
|
-
|
|
270478
|
-
|
|
270479
|
-
|
|
270480
|
-
|
|
270481
|
-
|
|
270913
|
+
// Only an actual pull counts as "busy". A status refresh ('checking') must NOT
|
|
270914
|
+
// — otherwise a stale `activeModel` makes a row flash a progress bar every time
|
|
270915
|
+
// status is re-checked.
|
|
270916
|
+
const busyDownloading = downloadState.status === 'downloading';
|
|
270917
|
+
const statusFor = (m) => {
|
|
270918
|
+
if (localSel.enabled && localSel.model === m.id)
|
|
270919
|
+
return 'selected';
|
|
270920
|
+
const active = sameModel(downloadingModel, m.id);
|
|
270921
|
+
if (active && downloadState.status === 'error')
|
|
270922
|
+
return 'error';
|
|
270923
|
+
if (active && busyDownloading)
|
|
270924
|
+
return downloadState.progress > 0 ? 'downloading' : 'starting';
|
|
270925
|
+
// `lastCompleted` bridges the just-finished → tags-refreshed gap so the row
|
|
270926
|
+
// stays "installed" instead of flickering back to "Download".
|
|
270927
|
+
if (sameModel(lastCompleted, m.id) ||
|
|
270928
|
+
isLocalModelInstalled(m.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models))
|
|
270929
|
+
return 'installed';
|
|
270930
|
+
return 'not-installed';
|
|
270931
|
+
};
|
|
270932
|
+
// A cloud model is "in use" when it's the mentor's selected LLM and local mode
|
|
270933
|
+
// is off.
|
|
270934
|
+
const isCloudActive = (llm) => (mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_name) === llm.llm_name &&
|
|
270935
|
+
(mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_provider) === llmProvider.name &&
|
|
270936
|
+
!localSel.enabled;
|
|
270937
|
+
// Order the WHOLE list — cloud + local — available (usable now) first,
|
|
270938
|
+
// unavailable last, with the in-use model leading. "Available" for a cloud
|
|
270939
|
+
// model means the provider is switchable (has credentials + models); for a
|
|
270940
|
+
// local model it's the install state. So an installed on-device model outranks
|
|
270941
|
+
// cloud models whose provider has no credentials. Ranked by state, not the
|
|
270942
|
+
// transient download status, so rows don't jump mid-pull; a stable sort keeps
|
|
270943
|
+
// the original (catalog) order within each rank.
|
|
270944
|
+
const cloudRank = (llm) => isCloudActive(llm) ? 0 : switchLLMAllowed && switchProviderAllowed ? 1 : 2;
|
|
270945
|
+
const localRank = (m) => {
|
|
270946
|
+
const s = statusFor(m);
|
|
270947
|
+
return s === 'selected' ? 0 : s === 'installed' ? 1 : 2;
|
|
270948
|
+
};
|
|
270949
|
+
const sortedModels = [
|
|
270950
|
+
...filteredLLMs.map((llm) => ({ kind: 'cloud', llm, rank: cloudRank(llm) })),
|
|
270951
|
+
...localModels.map((model) => ({ kind: 'local', model, rank: localRank(model) })),
|
|
270952
|
+
].sort((a, b) => a.rank - b.rank);
|
|
270953
|
+
// Preserve the "model may be too large" prompt before a download starts. Opens
|
|
270954
|
+
// the confirmation AlertDialog below (layered on top of the picker), rather
|
|
270955
|
+
// than a toast. `modelExceedsCapacity` compares the model's size to this
|
|
270956
|
+
// machine's RAM/VRAM; when memory is unknown it returns false, so the download
|
|
270957
|
+
// simply proceeds rather than blocking on incomplete data.
|
|
270958
|
+
const startLocalDownload = (m) => {
|
|
270959
|
+
if (modelExceedsCapacity(m, systemMemory)) {
|
|
270960
|
+
setPendingModel(m);
|
|
270961
|
+
return;
|
|
270962
|
+
}
|
|
270963
|
+
setRequestedModel(m.id);
|
|
270964
|
+
startDownload(m.id);
|
|
270965
|
+
};
|
|
270966
|
+
const activateLocal = (m, status) => {
|
|
270967
|
+
var _a;
|
|
270968
|
+
switch (status) {
|
|
270969
|
+
case 'not-installed':
|
|
270970
|
+
case 'error':
|
|
270971
|
+
// Only one on-device model downloads at a time. If ANY pull is already
|
|
270972
|
+
// running, don't start another — starting concurrent pulls floods every
|
|
270973
|
+
// useModelDownload instance with progress events and can hang the app.
|
|
270974
|
+
// Block even when the running model is unknown (activeModel wiped); the
|
|
270975
|
+
// user cancels by clicking the downloading row itself. Selecting an
|
|
270976
|
+
// already-installed model is unaffected (it starts no pull).
|
|
270977
|
+
if (busyDownloading && !sameModel(downloadingModel, m.id)) {
|
|
270978
|
+
const dl = LOCAL_MODELS.find((x) => sameModel(x.id, downloadingModel));
|
|
270979
|
+
setBusyDownloadName((_a = dl === null || dl === void 0 ? void 0 : dl.name) !== null && _a !== void 0 ? _a : labels.unnamedModel);
|
|
270980
|
+
return;
|
|
270981
|
+
}
|
|
270982
|
+
startLocalDownload(m);
|
|
270983
|
+
break;
|
|
270984
|
+
case 'starting':
|
|
270985
|
+
case 'downloading':
|
|
270986
|
+
cancelDownload();
|
|
270987
|
+
break;
|
|
270988
|
+
case 'installed':
|
|
270989
|
+
// Use this on-device model — device-global, mutually exclusive with cloud.
|
|
270990
|
+
setLocalLLMModel(m.id);
|
|
270991
|
+
setLocalLLMEnabled(true);
|
|
270992
|
+
// Persist tool-calling support so local chat routes to the MCP/tool
|
|
270993
|
+
// bridge. Without this the flag stays at its default (false) and
|
|
270994
|
+
// streaming chat rejects the model with "tool_support=false" even though
|
|
270995
|
+
// the catalog marks it tool-capable.
|
|
270996
|
+
setLocalLLMToolSupport(m.tool_support);
|
|
270997
|
+
setLocalSel({ enabled: true, model: m.id });
|
|
270998
|
+
// Notify same-tab listeners (e.g. a nav-bar on-device badge).
|
|
270999
|
+
window.dispatchEvent(new Event(LOCAL_LLM_CHANGED_EVENT));
|
|
271000
|
+
break;
|
|
271001
|
+
}
|
|
271002
|
+
};
|
|
271003
|
+
const selectCloud = (llmName) => {
|
|
271004
|
+
// Picking a cloud model turns local mode off so routing uses the cloud LLM.
|
|
271005
|
+
if (localSel.enabled) {
|
|
271006
|
+
setLocalLLMEnabled(false);
|
|
271007
|
+
setLocalSel((prev) => ({ ...prev, enabled: false }));
|
|
271008
|
+
window.dispatchEvent(new Event(LOCAL_LLM_CHANGED_EVENT));
|
|
271009
|
+
}
|
|
271010
|
+
onSelect(llmProvider.name, llmName);
|
|
271011
|
+
};
|
|
271012
|
+
// Announce download lifecycle changes once (not every %) for screen readers.
|
|
271013
|
+
const activeLocal = LOCAL_MODELS.find((m) => sameModel(m.id, downloadingModel));
|
|
271014
|
+
const liveMessage = !activeLocal
|
|
271015
|
+
? ''
|
|
271016
|
+
: downloadState.status === 'completed'
|
|
271017
|
+
? labels.announceDownloaded(activeLocal.name)
|
|
271018
|
+
: downloadState.status === 'cancelled'
|
|
271019
|
+
? labels.announceCancelled
|
|
271020
|
+
: downloadState.status === 'error'
|
|
271021
|
+
? labels.announceFailed
|
|
271022
|
+
: downloadState.status === 'checking' ||
|
|
271023
|
+
(downloadState.status === 'downloading' && downloadState.progress === 0)
|
|
271024
|
+
? labels.announceStarted(activeLocal.name)
|
|
271025
|
+
: '';
|
|
271026
|
+
return (jsx(Dialog, { open: isOpen, onOpenChange: onClose, children: jsxs(DialogContent, { className: "max-w-4xl space-y-6 p-4", children: [jsx(DialogDescription, { className: "sr-only", children: labels.description(llmProvider.name) }), jsx(DialogHeader, { children: jsx(DialogTitle, { className: "ibl-dialog-title", children: labels.title }) }), jsx("p", { className: "text-sm text-gray-600", children: labels.helpText }), 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, { placeholder: labels.searchPlaceholder, className: "py-6 pl-9", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), autoComplete: "off", disabled: isSelecting })] }), jsx("div", { className: "sr-only", "aria-live": "polite", role: "status", children: liveMessage }), jsx("div", { className: "grid max-h-[60vh] grid-cols-1 gap-4 overflow-y-auto pr-2 sm:grid-cols-2 lg:grid-cols-3", children: sortedModels.map((item) => {
|
|
271027
|
+
if (item.kind === 'cloud') {
|
|
271028
|
+
const llm = item.llm;
|
|
271029
|
+
const isActive = isCloudActive(llm);
|
|
271030
|
+
const providerDetails = getLLMProviderDetails$1(llmProvider.name, llm.llm_name);
|
|
271031
|
+
const isDisabled = !switchLLMAllowed || isSelecting || isActive || !switchProviderAllowed;
|
|
271032
|
+
return (jsxs("button", { "data-model": llm.llm_name, disabled: isDisabled, onClick: () => {
|
|
271033
|
+
selectCloud(llm.llm_name);
|
|
271034
|
+
}, className: cn('flex cursor-pointer items-center gap-3 rounded-lg border border-gray-200 p-4 transition-colors', {
|
|
271035
|
+
// Matches the provider grid's inactive treatment: tinted
|
|
271036
|
+
// card, faded logo, full-strength label.
|
|
271037
|
+
'cursor-not-allowed border-gray-100 bg-gray-50': isDisabled && !isActive,
|
|
271038
|
+
'hover:border-blue-500 hover:bg-blue-50': !isDisabled,
|
|
271039
|
+
'cursor-not-allowed border-blue-500 bg-blue-50': isActive,
|
|
271040
|
+
}), children: [jsx("span", { className: "flex h-8 w-8 items-center justify-center overflow-hidden rounded-lg", children: jsx(Image$4, { src: providerDetails.logo, alt: labels.providerIconAlt(providerDetails.name), className: cn('h-full w-full object-contain', {
|
|
271041
|
+
// `grayscale` alone is a no-op on already-black marks,
|
|
271042
|
+
// so fade them as well — same rule as the provider grid.
|
|
271043
|
+
'opacity-40 grayscale': isDisabled && !isActive,
|
|
271044
|
+
}), width: 32, height: 32, loading: "lazy" }) }), jsx("span", { className: "text-left text-sm font-medium text-gray-900", children: llm.display_name || llm.llm_name })] }, `cloud-${llm.llm_name}`));
|
|
271045
|
+
}
|
|
271046
|
+
const m = item.model;
|
|
271047
|
+
const status = statusFor(m);
|
|
271048
|
+
return (jsx(LocalModelRow, { name: m.name, size: m.size, logo: getLLMProviderDetails$1(llmProvider.name, m.name).logo, status: status, progress: sameModel(downloadingModel, m.id) ? downloadState.progress : 0, onActivate: () => activateLocal(m, status), labels: labels.localModel }, `local-${m.id}`));
|
|
271049
|
+
}) }), jsx(AlertDialog, { open: !!pendingModel, onOpenChange: (open) => {
|
|
271050
|
+
if (!open)
|
|
271051
|
+
setPendingModel(null);
|
|
271052
|
+
}, children: jsxs(AlertDialogContent, { children: [jsxs(AlertDialogHeader, { children: [jsx(AlertDialogTitle, { children: labels.tooLargeTitle }), jsx(AlertDialogDescription, { children: labels.tooLargeDescription((_a = pendingModel === null || pendingModel === void 0 ? void 0 : pendingModel.name) !== null && _a !== void 0 ? _a : '', (_b = pendingModel === null || pendingModel === void 0 ? void 0 : pendingModel.size) !== null && _b !== void 0 ? _b : '') })] }), jsxs(AlertDialogFooter, { children: [jsx(AlertDialogCancel, { onClick: () => setPendingModel(null), children: labels.cancel }), jsx(AlertDialogAction, { onClick: () => {
|
|
271053
|
+
if (pendingModel) {
|
|
271054
|
+
setRequestedModel(pendingModel.id);
|
|
271055
|
+
startDownload(pendingModel.id);
|
|
271056
|
+
}
|
|
271057
|
+
setPendingModel(null);
|
|
271058
|
+
}, children: labels.downloadAnyway })] })] }) }), jsx(AlertDialog, { open: !!busyDownloadName, onOpenChange: (open) => {
|
|
271059
|
+
if (!open)
|
|
271060
|
+
setBusyDownloadName(null);
|
|
271061
|
+
}, children: jsxs(AlertDialogContent, { children: [jsxs(AlertDialogHeader, { children: [jsx(AlertDialogTitle, { children: labels.alreadyDownloadingTitle }), jsx(AlertDialogDescription, { children: labels.alreadyDownloadingDescription(busyDownloadName !== null && busyDownloadName !== void 0 ? busyDownloadName : '') })] }), jsx(AlertDialogFooter, { children: jsx(AlertDialogAction, { onClick: () => setBusyDownloadName(null), children: labels.gotIt }) })] }) })] }) }));
|
|
271062
|
+
}
|
|
271063
|
+
|
|
271064
|
+
function AgentLLMTab({ showConfigurationHeader = true, mentorId: mentorIdOverride, getLLMProviderDetails: getLLMProviderDetails$1 = getLLMProviderDetails, labels: labelsOverride, }) {
|
|
270482
271065
|
var _a;
|
|
270483
271066
|
const t = useT();
|
|
270484
|
-
const { tenantKey, mentorId, username, enableRBAC } = useAgentSettings();
|
|
271067
|
+
const { tenantKey, mentorId: contextMentorId, username, enableRBAC } = useAgentSettings();
|
|
271068
|
+
const mentorId = mentorIdOverride || contextMentorId;
|
|
270485
271069
|
const labels = React__default.useMemo(() => resolveLLMTabLabels(labelsOverride, t), [labelsOverride, t]);
|
|
270486
271070
|
const [searchQuery, setSearchQuery] = React__default.useState('');
|
|
270487
271071
|
const [selectedLLMProvider, setSelectedLLMProvider] = React__default.useState(null);
|
|
@@ -270499,13 +271083,44 @@ function AgentLLMTab({ showConfigurationHeader = true, getLLMProviderDetails, la
|
|
|
270499
271083
|
}, {
|
|
270500
271084
|
skip: !tenantKey || !username,
|
|
270501
271085
|
});
|
|
271086
|
+
// Providers that only offer on-device (local) models — surfaced as grid cards
|
|
271087
|
+
// (Tauri desktop only) so their downloadable models are reachable even when the
|
|
271088
|
+
// backend LLM list has no matching cloud provider.
|
|
271089
|
+
const localOnlyProviders = React__default.useMemo(() => {
|
|
271090
|
+
if (!isTauriApp$1())
|
|
271091
|
+
return [];
|
|
271092
|
+
const cloudKeys = new Set((llmProviders !== null && llmProviders !== void 0 ? llmProviders : []).map((p) => getProviderName(p.name)));
|
|
271093
|
+
const seen = new Set();
|
|
271094
|
+
const result = [];
|
|
271095
|
+
for (const model of LOCAL_MODELS) {
|
|
271096
|
+
const key = getProviderName(model.provider);
|
|
271097
|
+
if (cloudKeys.has(key) || seen.has(key))
|
|
271098
|
+
continue;
|
|
271099
|
+
seen.add(key);
|
|
271100
|
+
result.push({ key, provider: model.provider });
|
|
271101
|
+
}
|
|
271102
|
+
// Alphabetical among themselves; the whole local-only block still renders
|
|
271103
|
+
// after the API providers.
|
|
271104
|
+
return result.sort((a, b) => compareLLMProvidersByDisplayName(a.provider, b.provider, getLLMProviderDetails$1));
|
|
271105
|
+
}, [llmProviders, getLLMProviderDetails$1]);
|
|
270502
271106
|
// Two alphabetical groups: providers we have a usable LLM key for first, then
|
|
270503
271107
|
// the ones we don't. Ordering is independent of the search filter below, so it
|
|
270504
271108
|
// holds for whatever the filter leaves.
|
|
270505
|
-
const sortedLLMProviders = React__default.useMemo(() => sortLLMProvidersByCredentials(llmProviders !== null && llmProviders !== void 0 ? llmProviders : [], getLLMProviderDetails), [llmProviders, getLLMProviderDetails]);
|
|
271109
|
+
const sortedLLMProviders = React__default.useMemo(() => sortLLMProvidersByCredentials(llmProviders !== null && llmProviders !== void 0 ? llmProviders : [], getLLMProviderDetails$1), [llmProviders, getLLMProviderDetails$1]);
|
|
270506
271110
|
const [editMentor, { isLoading: isEditingMentor }] = useEditMentorMutation();
|
|
270507
271111
|
const isDisabled = isMentorSettingsLoading || isLoadingLLMProviders || isEditingMentor;
|
|
270508
271112
|
const isLoading = isMentorSettingsLoading || isLoadingLLMProviders;
|
|
271113
|
+
// The highlighted (selected) provider card must reflect the model chat
|
|
271114
|
+
// actually uses. When on-device mode is on, that's the selected local model's
|
|
271115
|
+
// provider (e.g. Llama 3.2 → Meta) — NOT the agent's stale cloud
|
|
271116
|
+
// `llm_provider` (which stays e.g. OpenAI). Falls back to the cloud provider
|
|
271117
|
+
// when local mode is off. Reactive so it updates the instant a model is picked.
|
|
271118
|
+
const selectedLocal = useSelectedLocalModel();
|
|
271119
|
+
const activeProviderKey = selectedLocal.isLocal && selectedLocal.model
|
|
271120
|
+
? getProviderName(selectedLocal.model.provider)
|
|
271121
|
+
: (mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_provider)
|
|
271122
|
+
? getProviderName(mentorSettings.llm_provider)
|
|
271123
|
+
: '';
|
|
270509
271124
|
async function updateMentorLLM(llmProvider, llmName) {
|
|
270510
271125
|
try {
|
|
270511
271126
|
await editMentor({
|
|
@@ -270522,57 +271137,70 @@ function AgentLLMTab({ showConfigurationHeader = true, getLLMProviderDetails, la
|
|
|
270522
271137
|
}
|
|
270523
271138
|
catch (error) {
|
|
270524
271139
|
console.error(JSON.stringify(error));
|
|
270525
|
-
const errorMessage = extractErrorMessage(error, labels.toasts.updateError);
|
|
271140
|
+
const errorMessage = extractErrorMessage$1(error, labels.toasts.updateError);
|
|
270526
271141
|
toast.error(errorMessage);
|
|
270527
271142
|
console.error(JSON.stringify({ tenant: tenantKey, error }));
|
|
270528
271143
|
}
|
|
270529
271144
|
}
|
|
270530
|
-
return (jsxs(Fragment$1, { children: [showConfigurationHeader && (jsx("div", { className: "flex hidden 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-700", children: labels.header.description })] }) })),
|
|
271145
|
+
return (jsxs(Fragment$1, { children: [showConfigurationHeader && (jsx("div", { className: "flex hidden 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-700", children: labels.header.description })] }) })), jsxs("div", { className: cn('flex-1 space-y-4 pt-3 pb-3 lg:pt-4 lg:pb-4', showConfigurationHeader ? 'px-3 lg:px-4' : 'px-0 lg:px-0'), style: {
|
|
270531
271146
|
overflowY: 'auto',
|
|
270532
271147
|
overflowX: 'hidden',
|
|
270533
|
-
}, children: jsxs("div", { className: "space-y-6", 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-500" }), jsx(Input, { type: "search", placeholder: labels.search.placeholder, className: "w-full py-6 pl-9 md:w-1/2", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), disabled: isDisabled })] }), isLoading ? (jsx("div", { className: "flex items-center justify-center py-10", children: jsx(Spinner, {}) })) : (jsx(WithFormPermissions, { name: "llm_provider",
|
|
270534
|
-
|
|
270535
|
-
|
|
270536
|
-
|
|
270537
|
-
|
|
270538
|
-
|
|
270539
|
-
|
|
270540
|
-
|
|
270541
|
-
|
|
270542
|
-
|
|
270543
|
-
|
|
270544
|
-
|
|
270545
|
-
|
|
270546
|
-
|
|
270547
|
-
|
|
270548
|
-
|
|
270549
|
-
|
|
270550
|
-
|
|
270551
|
-
|
|
270552
|
-
|
|
270553
|
-
|
|
270554
|
-
|
|
270555
|
-
|
|
270556
|
-
|
|
270557
|
-
|
|
270558
|
-
|
|
270559
|
-
|
|
270560
|
-
|
|
270561
|
-
|
|
270562
|
-
|
|
270563
|
-
|
|
270564
|
-
|
|
270565
|
-
|
|
270566
|
-
|
|
270567
|
-
|
|
270568
|
-
|
|
270569
|
-
|
|
270570
|
-
|
|
270571
|
-
|
|
270572
|
-
|
|
270573
|
-
|
|
270574
|
-
|
|
270575
|
-
|
|
271148
|
+
}, children: [jsx("div", { className: "rounded-md border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600", "data-testid": "llm-info-box", children: labels.infoBox }), jsxs("div", { className: "space-y-6", 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-500" }), jsx(Input, { type: "search", placeholder: labels.search.placeholder, className: "w-full py-6 pl-9 md:w-1/2", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), disabled: isDisabled })] }), isLoading ? (jsx("div", { className: "flex items-center justify-center py-10", children: jsx(Spinner, {}) })) : (jsx(WithFormPermissions, { name: "llm_provider",
|
|
271149
|
+
// @ts-ignore
|
|
271150
|
+
permissions: (_a = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _a === void 0 ? void 0 : _a.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: [sortedLLMProviders
|
|
271151
|
+
.filter((model) => model.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
|
271152
|
+
.map((model) => {
|
|
271153
|
+
const providerDetails = getLLMProviderDetails$1(model.name);
|
|
271154
|
+
const isActive = !!activeProviderKey && getProviderName(model.name) === activeProviderKey;
|
|
271155
|
+
// The generated LLM response type doesn't declare the
|
|
271156
|
+
// credential flags the backend actually sends, so read them
|
|
271157
|
+
// through the access-shaped view the helper expects.
|
|
271158
|
+
const isGrayed = !canAccessProvider(model) && !isActive;
|
|
271159
|
+
return (jsxs("div", { "data-testid": "llm-provider-card", "data-provider": model.name, "data-disabled": isGrayed ? 'true' : 'false', className: cn('flex cursor-pointer items-center gap-3 rounded-lg border border-gray-200 bg-white p-4 shadow-sm transition-shadow hover:shadow-md', {
|
|
271160
|
+
'border-blue-500': isActive,
|
|
271161
|
+
// Tint the whole card too: desaturating a logo that
|
|
271162
|
+
// is already black changes nothing, so the card
|
|
271163
|
+
// itself has to carry some of the inactive signal.
|
|
271164
|
+
'border-gray-100 bg-gray-50': isGrayed,
|
|
271165
|
+
}), onClick: () => {
|
|
271166
|
+
if (isDisabled || disabled)
|
|
271167
|
+
return;
|
|
271168
|
+
setSelectedLLMProvider({
|
|
271169
|
+
id: model.id,
|
|
271170
|
+
name: model.name,
|
|
271171
|
+
logo: model.logo,
|
|
271172
|
+
description: model.description,
|
|
271173
|
+
chat_models: model.chat_models,
|
|
271174
|
+
// @ts-ignore - has_credentials property does not exist on LLMResponse type
|
|
271175
|
+
has_credentials: model === null || model === void 0 ? void 0 : model.has_credentials,
|
|
271176
|
+
// @ts-ignore - main_has_credentials property does not exist on LLMResponse type
|
|
271177
|
+
main_has_credentials: model === null || model === void 0 ? void 0 : model.main_has_credentials,
|
|
271178
|
+
// @ts-ignore - can_use_main_keys property does not exist on LLMResponse type
|
|
271179
|
+
can_use_main_keys: model === null || model === void 0 ? void 0 : model.can_use_main_keys,
|
|
271180
|
+
});
|
|
271181
|
+
}, children: [jsx("div", { className: "h-8 w-8 flex-shrink-0", children: jsx(Image$4, { src: providerDetails.logo, alt: labels.providerLogoAlt(providerDetails.name), className: cn('h-full w-full object-contain', {
|
|
271182
|
+
// `grayscale` only desaturates, so already-black
|
|
271183
|
+
// marks (OpenAI, xAI) looked untouched. Fading is
|
|
271184
|
+
// what actually reads as inactive at any hue.
|
|
271185
|
+
'opacity-40 grayscale': isGrayed,
|
|
271186
|
+
}), width: 32, height: 32, loading: "lazy" }) }), jsx("span", { className: "text-sm font-medium text-gray-900", children: providerDetails.name })] }, model.id));
|
|
271187
|
+
}), localOnlyProviders.map((lp) => {
|
|
271188
|
+
const details = getLLMProviderDetails$1(lp.provider);
|
|
271189
|
+
return (jsxs("div", { "data-testid": "llm-provider-card", "data-provider": lp.provider, "data-disabled": "false", className: cn('flex cursor-pointer items-center gap-3 rounded-lg border border-gray-200 bg-white p-4 shadow-sm transition-shadow hover:shadow-md', { 'border-blue-500': lp.key === activeProviderKey }), onClick: () => {
|
|
271190
|
+
if (isDisabled || disabled)
|
|
271191
|
+
return;
|
|
271192
|
+
setSelectedLLMProvider({
|
|
271193
|
+
id: -1,
|
|
271194
|
+
name: lp.provider,
|
|
271195
|
+
logo: details.logo,
|
|
271196
|
+
description: null,
|
|
271197
|
+
chat_models: [],
|
|
271198
|
+
});
|
|
271199
|
+
}, children: [jsx("div", { className: "h-8 w-8 flex-shrink-0", children: jsx(Image$4, { src: details.logo, alt: labels.providerLogoAlt(details.name), className: "h-full w-full object-contain", width: 32, height: 32, loading: "lazy" }) }), jsx("span", { className: "text-sm font-medium text-gray-900", children: details.name })] }, `local-${lp.key}`));
|
|
271200
|
+
})] })) })), selectedLLMProvider && mentorSettings && (jsx(LLMProviderModal, { isOpen: true, onClose: () => setSelectedLLMProvider(null), onSelect: updateMentorLLM, llmProvider: selectedLLMProvider, isSelecting: isEditingMentor, mentorSettings: {
|
|
271201
|
+
llm_name: mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_name,
|
|
271202
|
+
llm_provider: mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_provider,
|
|
271203
|
+
}, llms: (llmProviders !== null && llmProviders !== void 0 ? llmProviders : []), getLLMProviderDetails: getLLMProviderDetails$1, labels: labels.providerModal }))] })] })] }));
|
|
270576
271204
|
}
|
|
270577
271205
|
|
|
270578
271206
|
function useTools({ labels }) {
|
|
@@ -337443,5 +338071,5 @@ var index_es = /*#__PURE__*/Object.freeze({
|
|
|
337443
338071
|
vectorsRatio: vectorsRatio
|
|
337444
338072
|
});
|
|
337445
338073
|
|
|
337446
|
-
export { AGENT_ACCESS_TAB_LABELS, AGENT_ANALYTICS_TAB_LABELS, AGENT_API_TAB_LABELS, AGENT_DATASETS_TAB_LABELS, AGENT_DISCLAIMERS_TAB_LABELS, AGENT_EMBED_TAB_LABELS, AGENT_EVALUATION_TAB_LABELS, AGENT_GRADER_TAB_LABELS, AGENT_HISTORY_TAB_LABELS, AGENT_HUMAN_SUPPORT_TAB_LABELS, AGENT_LLM_TAB_LABELS, AGENT_LTI_TAB_LABELS, AGENT_MEMORY_TAB_LABELS, AGENT_PICKER_LABELS, AGENT_PRIVACY_TAB_LABELS, AGENT_PROMPTS_TAB_LABELS, AGENT_SAFETY_TAB_LABELS, AGENT_SCREENSHARE_TAB_LABELS, AGENT_SEARCH_LABELS, AGENT_SETTINGS_TAB_LABELS, AGENT_SKILLS_TAB_LABELS, AGENT_SPEND_CAPS_TAB_LABELS, AGENT_TASKS_TAB_LABELS, AGENT_TOOLS_TAB_LABELS, AGENT_VOICE_TAB_LABELS, Account, AgentAccessTab, AgentAnalyticsTab, AgentApiTab, AgentCard, AgentDatasetsTab, AgentDisclaimersTab, AgentEmbedTab, AgentEmptyState, AgentEvaluationTab, AgentGraderTab, AgentHistoryTab, AgentHumanSupportTab, AgentLLMTab, AgentLtiTab, AgentMemoryTab, AgentPrivacyTab, AgentPromptsTab, AgentSafetyTab, AgentScreenShareTab, AgentSearch, AgentSearchFilters, AgentSearchInput, AgentSettingsProvider, AgentSettingsTab, AgentSkillsTab, AgentSpendCapsTab, AgentTasksTab, AgentToolsTab, AgentVoiceTab, AppSidebar, CATALOGS, Chat, ClientErrorPage, ConversationStarters$1 as ConversationStarters, CopyButton, CopyMentorModal, CourseBox, CourseContentLayout, CourseContentTabPage, CreatePathwayModal, CredentialDetailModal, CredentialMiniBox, DEFAULT_ANALYTICS_HUB_TABS, DEFAULT_LOCALE, DeleteMentorModal, EMPTY_PROGRAM_SETTINGS_FORM, EdxIframe, ErrorPage, GRADING_TOOL_NAME, GreetingMethod, McpTab, MediaBox, MentorSelectionGrid, NavBar, OrganizationTab, PLATFORM_SIDEBAR_FLYOUT_ITEM_COLOR, PLATFORM_SIDEBAR_FLYOUT_TITLE_COLOR, PLATFORM_SIDEBAR_NAV_ACTIVE_BG_OPEN, PLATFORM_SIDEBAR_NAV_MUTED, PathwayDetailModal, PlatformAccountSheet, PlatformSidebar, PlatformSidebarCollapsedLabelFlyout, PlatformSidebarCollapsedNavFlyout, PlatformSidebarFooter, PlatformSidebarNavDivider, PlatformSidebarNavSection, PlatformSidebarSubNavItem, ProfileInfoCards, ProfileTabs, ProgramDetailModal, ProjectsSidebarDropdown, SPEND_CAPS_SUB_TABS, SUPPORTED_LOCALES, SidebarInset, SidebarProvider, SidebarTrigger, SpendCapUsage, SsoLogin, StarButton, UserProfileDropdown, UserProfileModal, WebContainersI18nProvider, formatDateString, getCatalog, getLLMProviderDetails, initializeLocalStorageWithObject, isValidLocale, resolveAgentPickerLabels, resolveAgentSearchLabels, resolveSettingsTabLabels, useAgentSearch, useAgentSearchWithPagination, useAgentSettings, useAgentStar, useGetChatDetails, usePlatformSidebarFooterActions, usePlatformSidebarNavCallback, useSidebar, useT, useTimeTracker, useWebContainersI18n, useWebContainersLocale };
|
|
338074
|
+
export { AGENT_ACCESS_TAB_LABELS, AGENT_ANALYTICS_TAB_LABELS, AGENT_API_TAB_LABELS, AGENT_DATASETS_TAB_LABELS, AGENT_DISCLAIMERS_TAB_LABELS, AGENT_EMBED_TAB_LABELS, AGENT_EVALUATION_TAB_LABELS, AGENT_GRADER_TAB_LABELS, AGENT_HISTORY_TAB_LABELS, AGENT_HUMAN_SUPPORT_TAB_LABELS, AGENT_LLM_TAB_LABELS, AGENT_LTI_TAB_LABELS, AGENT_MEMORY_TAB_LABELS, AGENT_PICKER_LABELS, AGENT_PRIVACY_TAB_LABELS, AGENT_PROMPTS_TAB_LABELS, AGENT_SAFETY_TAB_LABELS, AGENT_SCREENSHARE_TAB_LABELS, AGENT_SEARCH_LABELS, AGENT_SETTINGS_TAB_LABELS, AGENT_SKILLS_TAB_LABELS, AGENT_SPEND_CAPS_TAB_LABELS, AGENT_TASKS_TAB_LABELS, AGENT_TOOLS_TAB_LABELS, AGENT_VOICE_TAB_LABELS, Account, AgentAccessTab, AgentAnalyticsTab, AgentApiTab, AgentCard, AgentDatasetsTab, AgentDisclaimersTab, AgentEmbedTab, AgentEmptyState, AgentEvaluationTab, AgentGraderTab, AgentHistoryTab, AgentHumanSupportTab, AgentLLMTab, AgentLtiTab, AgentMemoryTab, AgentPrivacyTab, AgentPromptsTab, AgentSafetyTab, AgentScreenShareTab, AgentSearch, AgentSearchFilters, AgentSearchInput, AgentSettingsProvider, AgentSettingsTab, AgentSkillsTab, AgentSpendCapsTab, AgentTasksTab, AgentToolsTab, AgentVoiceTab, AppSidebar, CATALOGS, Chat, ClientErrorPage, ConversationStarters$1 as ConversationStarters, CopyButton, CopyMentorModal, CourseBox, CourseContentLayout, CourseContentTabPage, CreatePathwayModal, CredentialDetailModal, CredentialMiniBox, DEFAULT_ANALYTICS_HUB_TABS, DEFAULT_LOCALE, DeleteMentorModal, EMPTY_PROGRAM_SETTINGS_FORM, EdxIframe, ErrorPage, GRADING_TOOL_NAME, GreetingMethod, LLMProviderModal, LocalModelRow, McpTab, MediaBox, MentorSelectionGrid, NavBar, OrganizationTab, PLATFORM_SIDEBAR_FLYOUT_ITEM_COLOR, PLATFORM_SIDEBAR_FLYOUT_TITLE_COLOR, PLATFORM_SIDEBAR_NAV_ACTIVE_BG_OPEN, PLATFORM_SIDEBAR_NAV_MUTED, PathwayDetailModal, PlatformAccountSheet, PlatformSidebar, PlatformSidebarCollapsedLabelFlyout, PlatformSidebarCollapsedNavFlyout, PlatformSidebarFooter, PlatformSidebarNavDivider, PlatformSidebarNavSection, PlatformSidebarSubNavItem, ProfileInfoCards, ProfileTabs, ProgramDetailModal, ProjectsSidebarDropdown, SPEND_CAPS_SUB_TABS, SUPPORTED_LOCALES, SidebarInset, SidebarProvider, SidebarTrigger, SpendCapUsage, SsoLogin, StarButton, UserProfileDropdown, UserProfileModal, WebContainersI18nProvider, formatDateString, getCatalog, getLLMProviderDetails, getProviderName, initializeLocalStorageWithObject, isValidLocale, resolveAgentPickerLabels, resolveAgentSearchLabels, resolveLLMTabLabels, resolveSettingsTabLabels, useAgentSearch, useAgentSearchWithPagination, useAgentSettings, useAgentStar, useGetChatDetails, usePlatformSidebarFooterActions, usePlatformSidebarNavCallback, useSidebar, useT, useTimeTracker, useWebContainersI18n, useWebContainersLocale };
|
|
337447
338075
|
//# sourceMappingURL=index.esm.js.map
|