@iblai/iblai-js 2.11.0 → 2.11.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.
@@ -2132,7 +2132,7 @@ var analyticsAnalyticsOverview$3 = {
2132
2132
  topicsChartDescription: "The most discussed topics, ranked by how many messages each one generated.",
2133
2133
  activeUsersChartDescription: "Unique active users per day, showing how engagement changes over time.",
2134
2134
  llmSpend: "LLM spend",
2135
- llmSpendDescription: "Raw cost of every traced LLM call in the selected period, from the trace store. Billing totals live on the Cost & LLM tab.",
2135
+ llmSpendDescription: "Raw cost of every traced LLM call in the selected period, from the trace store. Billing totals live on the Cost tab.",
2136
2136
  allTopics: "All topics",
2137
2137
  topicsRankedFooter: "{shown} of {total} topics · ranked by message volume"
2138
2138
  };
@@ -8089,7 +8089,7 @@ var analyticsChartCard$3 = {
8089
8089
  adjustRangeHint: "This card only — the page range stays where it is."
8090
8090
  };
8091
8091
  var analyticsCostTabs$3 = {
8092
- ariaLabel: "Cost & LLM sections",
8092
+ ariaLabel: "Cost sections",
8093
8093
  spend: "Spend",
8094
8094
  usage: "Usage & latency",
8095
8095
  traces: "Traces"
@@ -9280,7 +9280,7 @@ var analyticsAnalyticsOverview$2 = {
9280
9280
  topicsChartDescription: "Les sujets les plus abordés, classés selon le nombre de messages générés.",
9281
9281
  activeUsersChartDescription: "Utilisateurs actifs uniques par jour, pour suivre l’évolution de l’engagement.",
9282
9282
  llmSpend: "Dépenses LLM",
9283
- llmSpendDescription: "Coût brut de chaque appel LLM tracé sur la période sélectionnée, issu du magasin de traces. Les totaux de facturation sont dans l'onglet Coûts et LLM.",
9283
+ llmSpendDescription: "Coût brut de chaque appel LLM tracé sur la période sélectionnée, issu du magasin de traces. Les totaux de facturation sont dans l'onglet Coûts.",
9284
9284
  allTopics: "Tous les sujets",
9285
9285
  topicsRankedFooter: "{shown} sur {total} sujets · classés par volume de messages"
9286
9286
  };
@@ -15237,7 +15237,7 @@ var analyticsChartCard$2 = {
15237
15237
  adjustRangeHint: "Cette carte uniquement — la plage de la page reste inchangée."
15238
15238
  };
15239
15239
  var analyticsCostTabs$2 = {
15240
- ariaLabel: "Sections Coûts et LLM",
15240
+ ariaLabel: "Sections Coûts",
15241
15241
  spend: "Dépenses",
15242
15242
  usage: "Utilisation et latence",
15243
15243
  traces: "Traces"
@@ -16428,7 +16428,7 @@ var analyticsAnalyticsOverview$1 = {
16428
16428
  topicsChartDescription: "Los temas más tratados, ordenados por la cantidad de mensajes que generó cada uno.",
16429
16429
  activeUsersChartDescription: "Usuarios activos únicos por día, para ver cómo cambia la participación con el tiempo.",
16430
16430
  llmSpend: "Gasto LLM",
16431
- llmSpendDescription: "Costo bruto de cada llamada LLM trazada en el período seleccionado, del almacén de trazas. Los totales de facturación están en la pestaña Costos y LLM.",
16431
+ llmSpendDescription: "Costo bruto de cada llamada LLM trazada en el período seleccionado, del almacén de trazas. Los totales de facturación están en la pestaña Costos.",
16432
16432
  allTopics: "Todos los temas",
16433
16433
  topicsRankedFooter: "{shown} de {total} temas · ordenados por volumen de mensajes"
16434
16434
  };
@@ -22385,7 +22385,7 @@ var analyticsChartCard$1 = {
22385
22385
  adjustRangeHint: "Solo esta tarjeta — el rango de la página no cambia."
22386
22386
  };
22387
22387
  var analyticsCostTabs$1 = {
22388
- ariaLabel: "Secciones de Costos y LLM",
22388
+ ariaLabel: "Secciones de Costos",
22389
22389
  spend: "Gasto",
22390
22390
  usage: "Uso y latencia",
22391
22391
  traces: "Trazas"
@@ -23576,7 +23576,7 @@ var analyticsAnalyticsOverview = {
23576
23576
  topicsChartDescription: "讨论最多的主题,按各自产生的消息数排序。",
23577
23577
  activeUsersChartDescription: "每日独立活跃用户数,展示参与度随时间的变化。",
23578
23578
  llmSpend: "LLM 支出",
23579
- llmSpendDescription: "所选时段内每次被追踪的 LLM 调用的原始成本,来自追踪存储。计费总额在成本与 LLM 页。",
23579
+ llmSpendDescription: "所选时段内每次被追踪的 LLM 调用的原始成本,来自追踪存储。计费总额在成本页。",
23580
23580
  allTopics: "所有主题",
23581
23581
  topicsRankedFooter: "共 {total} 个主题,显示 {shown} 个 · 按消息量排序"
23582
23582
  };
@@ -29533,7 +29533,7 @@ var analyticsChartCard = {
29533
29533
  adjustRangeHint: "仅此卡片 — 页面范围保持不变。"
29534
29534
  };
29535
29535
  var analyticsCostTabs = {
29536
- ariaLabel: "成本与 LLM 分区",
29536
+ ariaLabel: "成本分区",
29537
29537
  spend: "支出",
29538
29538
  usage: "用量与延迟",
29539
29539
  traces: "追踪"
@@ -192840,111 +192840,6 @@ function CreateApiModal({ isOpen, onClose, tenantKey, username }) {
192840
192840
  : t('apiTabLabels.createModal.submit') })) }) })] })] }) }), apiKey && jsx(ApiKeyModal, { isOpen: true, onClose: () => setApiKey(null), apiKey: apiKey })] }));
192841
192841
  }
192842
192842
 
192843
- /**
192844
- * Small pure utilities colocated with the LLM tab.
192845
- * Ported from standalone's `@/lib/utils` and `@/lib/error`.
192846
- */
192847
- // Canonical provider name for any label — a backend key ("azure_openai"), a
192848
- // human label ("Microsoft"), or a local model's provider ("Meta"). Folds every
192849
- // alias onto one identity so the same provider from different sources compares
192850
- // equal. See getProviderName.
192851
- const PROVIDER_NAME_BY_ALIAS = {
192852
- microsoft: 'azure_openai',
192853
- azureopenai: 'azure_openai',
192854
- openai: 'openai',
192855
- google: 'google',
192856
- gemini: 'google',
192857
- meta: 'llama',
192858
- metallama: 'llama',
192859
- llama: 'llama',
192860
- mistral: 'mistral',
192861
- mistralai: 'mistral',
192862
- nvidia: 'nvidia',
192863
- iblchatnvidia: 'nvidia',
192864
- deepseek: 'deepseek',
192865
- anthropic: 'anthropic',
192866
- iblchatanthropic: 'anthropic',
192867
- claude: 'anthropic',
192868
- groq: 'groq',
192869
- perplexity: 'perplexity',
192870
- xai: 'xai',
192871
- bedrock: 'bedrock',
192872
- amazonbedrock: 'bedrock',
192873
- amazon: 'bedrock',
192874
- iblchatbedrock: 'bedrock',
192875
- alibaba: 'alibaba',
192876
- qwen: 'alibaba',
192877
- ibm: 'ibm',
192878
- granite: 'ibm',
192879
- // ibl.ai's own hosted provider. The backend key is `iblai` (NameEnum.IBLAI);
192880
- // the extra aliases cover the `IBLChat<Vendor>` house spelling and the
192881
- // dotted/spaced brand forms, which all normalize to one of these.
192882
- iblai: 'iblai',
192883
- ibl: 'iblai',
192884
- iblchatibl: 'iblai',
192885
- iblchatiblai: 'iblai',
192886
- };
192887
- /**
192888
- * Canonical provider name for any label, case- and punctuation-insensitive.
192889
- * Folds backend keys and human/display labels onto one identity — e.g.
192890
- * `getProviderName('Microsoft') === 'azure_openai'`,
192891
- * `getProviderName('Meta') === 'llama'`, `getProviderName('Google') === 'google'`.
192892
- * Unknown providers return their normalized (lowercased, alphanumeric-only) form.
192893
- *
192894
- * This is the single source of truth for provider identity, which matters as
192895
- * soon as on-device models enter the picture: the local catalog labels its
192896
- * providers the human way ("Meta", "Microsoft", "Alibaba") while the backend LLM
192897
- * list uses wire keys ("llama", "azure_openai"). Comparing the two raw would
192898
- * render a duplicate card for the same vendor.
192899
- */
192900
- function getProviderName(llmProvider) {
192901
- var _a;
192902
- const normalized = (llmProvider !== null && llmProvider !== void 0 ? llmProvider : '').toLowerCase().replace(/[^a-z0-9]/g, '');
192903
- return (_a = PROVIDER_NAME_BY_ALIAS[normalized]) !== null && _a !== void 0 ? _a : normalized;
192904
- }
192905
-
192906
- // Logo + display name keyed by canonical provider name (see getProviderName).
192907
- const PROVIDER_DETAILS_BY_NAME = {
192908
- groq: { logo: '/llm-groq-provider.png', name: 'Groq' },
192909
- nvidia: { logo: '/llm-nvidia-provider.webp', name: 'NVIDIA' },
192910
- azure_openai: { logo: '/llm-microsoft-provider.png', name: 'Microsoft' },
192911
- openai: { logo: '/llm-openai-provider-2.svg', name: 'OpenAI' },
192912
- mistral: { logo: '/llm-mistral-provider.jpeg', name: 'Mistral' },
192913
- google: { logo: '/llm-google-provider.svg', name: 'Google' },
192914
- llama: { logo: '/llm-llama-provider.jpeg', name: 'Meta' },
192915
- anthropic: { logo: '/llm-claude-provider.png', name: 'Anthropic' },
192916
- perplexity: { logo: '/llm-perplexity-provider.webp', name: 'Perplexity' },
192917
- deepseek: { logo: '/llm-deepseek-provider.png', name: 'DeepSeek' },
192918
- xai: { logo: '/llm-xai-provider.jpg', name: 'xAI' },
192919
- bedrock: { logo: '/llm-amazon-provider.png', name: 'Amazon' },
192920
- alibaba: { logo: '/llm-alibaba-provider.png', name: 'Alibaba' },
192921
- ibm: { logo: '/llm-ibm-provider.png', name: 'IBM' },
192922
- iblai: { logo: '/llm-iblai-provider.png', name: 'ibl.ai' },
192923
- };
192924
- /**
192925
- * Returns the canonical logo path and display name for an LLM provider.
192926
- *
192927
- * Resolution goes through {@link getProviderName}, so a backend wire key
192928
- * (`azure_openai`), a human label (`Microsoft`) and an on-device catalog's
192929
- * provider string (`Meta`, `Alibaba`) all land on the same entry — the provider
192930
- * grid merges cloud and on-device providers into one list, so the two spellings
192931
- * have to agree.
192932
- *
192933
- * `logo` is a static-root path (e.g. `/llm-amazon-provider.png`); the package
192934
- * ships the canonical assets under `@iblai/web-containers/public/` — copy them
192935
- * into your app's static-root (`public/` for Next.js) so the paths resolve.
192936
- */
192937
- function getLLMProviderDetails(llmProvider, llmName) {
192938
- var _a;
192939
- const name = getProviderName(llmProvider);
192940
- // OpenAI and Google use a model-specific logo when a concrete model is named.
192941
- if (name === 'openai' && llmName)
192942
- return { logo: '/llm-openai-provider.jpg', name: 'OpenAI' };
192943
- if (name === 'google' && llmName)
192944
- return { logo: '/llm-gemini-provider.png', name: 'Google' };
192945
- return (_a = PROVIDER_DETAILS_BY_NAME[name]) !== null && _a !== void 0 ? _a : { logo: '/llm-generic-provider.png', name: llmProvider };
192946
- }
192947
-
192948
192843
  // Dynamic schema based on selected provider
192949
192844
  const createDynamicSchema$1 = (schemaFields) => {
192950
192845
  const schemaObj = {
@@ -193173,14 +193068,9 @@ function CreateLLMModal({ isOpen, onClose, tenantKey, onSuccess }) {
193173
193068
  }
193174
193069
  }, children: [jsx(SelectTrigger, { children: jsx(SelectValue, { placeholder: t('llmsTabCreateLlmModal.selectProviderPlaceholder') }) }), jsx(SelectContent, { children: availableProviders.map((provider) => {
193175
193070
  var _a, _b;
193176
- // Prefer the logo the API ships; fall back to the
193177
- // bundled provider artwork so known providers (ibl.ai
193178
- // included) never degrade to a generic key emoji.
193179
- const providerLogo = ((_a = provider === null || provider === void 0 ? void 0 : provider.service_info) === null || _a === void 0 ? void 0 : _a.logo) ||
193180
- getLLMProviderDetails(provider.name).logo;
193181
- const providerName = ((_b = provider === null || provider === void 0 ? void 0 : provider.service_info) === null || _b === void 0 ? void 0 : _b.display_name) ||
193182
- provider.name;
193183
- return (jsx(SelectItem, { value: provider.name, children: jsxs("div", { className: "flex items-center gap-2", children: [jsx("img", { src: providerLogo, alt: provider.name, className: "w-4 h-4 rounded-sm object-contain" }), providerName || provider.name] }) }, provider.name));
193071
+ const providerLogo = (_a = provider.service_info) === null || _a === void 0 ? void 0 : _a.logo;
193072
+ const providerName = ((_b = provider.service_info) === null || _b === void 0 ? void 0 : _b.display_name) || provider.name;
193073
+ return (jsx(SelectItem, { value: provider.name, children: jsxs("div", { className: "flex items-center gap-2", children: [providerLogo ? (jsx("img", { src: providerLogo, alt: providerName, className: "w-4 h-4 rounded-sm object-contain" })) : (jsx("span", { children: "\uD83D\uDD11" })), providerName] }) }, provider.name));
193184
193074
  }) })] }), shouldShowError && (jsx("p", { className: "text-red-500 text-xs", children: (_b = field.state.meta.errors[0]) === null || _b === void 0 ? void 0 : _b.message }))] }));
193185
193075
  } })), isCredentialsSchemaLoading && (jsxs("div", { className: "flex items-center justify-center py-4", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin mr-2" }), jsx("span", { className: "text-sm text-gray-600", children: t('llmsTabCreateLlmModal.loadingProviderConfigurations') })] })), showProviderForm && !isCredentialsSchemaLoading && !isAzureOpenAI && (jsx(Fragment$1, { children: schemaFields.map((fieldName) => (jsx(form.Field, { name: fieldName, children: (field) => {
193186
193076
  var _a, _b;
@@ -193267,26 +193157,18 @@ function IntegrationLLMsTab({ tenantKey }) {
193267
193157
  }
193268
193158
  };
193269
193159
  return (jsxs("div", { className: "space-y-4", children: [jsx(InfoBanner, { icon: jsx(Bot, { className: "w-5 h-5" }), children: t('llmsTabIntegrationLlmsTab.infoBannerText') }), jsx("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-x-auto", children: jsxs("table", { className: "w-full text-sm min-w-[600px]", children: [jsx("thead", { className: "bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600", children: jsxs("tr", { children: [jsx("th", { className: "px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400", children: t('llmsTabIntegrationLlmsTab.colName') }), jsx("th", { className: "px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400", children: t('llmsTabIntegrationLlmsTab.colKey') }), jsx("th", { className: "px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400 w-[50px]" })] }) }), jsx("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-600", children: isMaskedCredentialsLoading ? (jsx("tr", { children: jsx("td", { colSpan: 3, className: "px-6 py-8 text-center text-sm text-gray-500", children: t('llmsTabIntegrationLlmsTab.loading') }) })) : maskedCredentials.length === 0 ? (jsx("tr", { children: jsx("td", { colSpan: 3, className: "px-6 py-8 text-center text-sm text-gray-500", children: t('llmsTabIntegrationLlmsTab.noLlmKeysFound') }) })) : (maskedCredentials.map((cred, idx) => {
193270
- var _a, _b, _c;
193160
+ var _a, _b, _c, _d, _e;
193271
193161
  const provider = ((_a = cred.service_info) === null || _a === void 0 ? void 0 : _a.display_name) || cred.name;
193272
- // Prefer the logo the API ships; fall back to the bundled
193273
- // provider artwork so known providers (ibl.ai included) never
193274
- // degrade to a generic key emoji.
193275
- let logoUrl;
193276
- let logoAlt;
193277
- if ((_b = cred.service_info) === null || _b === void 0 ? void 0 : _b.logo) {
193278
- logoUrl = cred.service_info.logo;
193279
- logoAlt = cred.service_info.display_name || cred.service_info.name;
193280
- }
193281
- else {
193282
- logoUrl = getLLMProviderDetails(cred.name).logo;
193283
- }
193162
+ // Logos are backend-owned (`service_info.logo`); no bundled fallback.
193163
+ const logoUrl = (_b = cred.service_info) === null || _b === void 0 ? void 0 : _b.logo;
193284
193164
  const isAzure = isAzureOpenAI(cred);
193285
193165
  const azureModels = isAzure ? getAzureModels(cred) : [];
193286
193166
  const isExpanded = expandedRows.has(cred.name);
193287
- return (jsxs(React__default.Fragment, { children: [jsxs("tr", { children: [jsx("td", { className: "px-6 py-4", children: jsxs("div", { className: "flex items-center gap-2", children: [jsx("img", { src: logoUrl, alt: logoAlt || provider || cred.name, className: "w-6 h-6 rounded-sm object-contain" }), jsx("span", { className: "font-normal text-gray-900 dark:text-gray-100", children: provider || cred.name }), isAzure && azureModels.length > 0 && (jsx("button", { type: "button", onClick: () => toggleRowExpansion(cred.name), className: "p-0.5 hover:bg-gray-100 rounded", children: isExpanded ? (jsx(ChevronUp, { className: "h-4 w-4 text-gray-500" })) : (jsx(ChevronDown, { className: "h-4 w-4 text-gray-500" })) }))] }) }), jsx("td", { className: "px-6 py-4 font-mono text-sm text-gray-900 dark:text-gray-100", children: isAzure ? (jsx("span", { className: "text-xs text-gray-500", children: t('llmsTabIntegrationLlmsTab.azureModelCount', {
193167
+ return (jsxs(React__default.Fragment, { children: [jsxs("tr", { children: [jsx("td", { className: "px-6 py-4", children: jsxs("div", { className: "flex items-center gap-2", children: [logoUrl ? (jsx("img", { src: logoUrl, alt: ((_c = cred.service_info) === null || _c === void 0 ? void 0 : _c.display_name) ||
193168
+ ((_d = cred.service_info) === null || _d === void 0 ? void 0 : _d.name) ||
193169
+ cred.name, className: "w-6 h-6 rounded-sm object-contain" })) : (jsx("span", { className: "text-lg", children: '🔑' })), jsx("span", { className: "font-normal text-gray-900 dark:text-gray-100", children: provider || cred.name }), isAzure && azureModels.length > 0 && (jsx("button", { type: "button", onClick: () => toggleRowExpansion(cred.name), className: "p-0.5 hover:bg-gray-100 rounded", children: isExpanded ? (jsx(ChevronUp, { className: "h-4 w-4 text-gray-500" })) : (jsx(ChevronDown, { className: "h-4 w-4 text-gray-500" })) }))] }) }), jsx("td", { className: "px-6 py-4 font-mono text-sm text-gray-900 dark:text-gray-100", children: isAzure ? (jsx("span", { className: "text-xs text-gray-500", children: t('llmsTabIntegrationLlmsTab.azureModelCount', {
193288
193170
  count: azureModels.length,
193289
- }) })) : (String((_c = cred === null || cred === void 0 ? void 0 : cred.value) === null || _c === void 0 ? void 0 : _c.key).slice(0, 15) || '') }), jsx("td", { className: "px-6 py-4", children: jsx(Button$1, { variant: "ghost", size: "sm", className: "cursor-pointer mr-2", disabled: isDeletingIntegration || isDeletingCredential, onClick: () => handleDeleteCredential(cred.name, cred.platform, false), children: jsx(Trash, { className: "h-4 w-4" }) }) })] }), isAzure &&
193171
+ }) })) : (String((_e = cred === null || cred === void 0 ? void 0 : cred.value) === null || _e === void 0 ? void 0 : _e.key).slice(0, 15) || '') }), jsx("td", { className: "px-6 py-4", children: jsx(Button$1, { variant: "ghost", size: "sm", className: "cursor-pointer mr-2", disabled: isDeletingIntegration || isDeletingCredential, onClick: () => handleDeleteCredential(cred.name, cred.platform, false), children: jsx(Trash, { className: "h-4 w-4" }) }) })] }), isAzure &&
193290
193172
  isExpanded &&
193291
193173
  azureModels.map((model, modelIdx) => (jsxs("tr", { children: [jsx("td", { className: "px-6 py-4 pl-12", children: jsx("span", { className: "text-sm text-gray-700", children: model.modelName.toLowerCase() }) }), jsx("td", { className: "px-6 py-4 font-mono text-sm text-gray-600", children: model.maskedKey.slice(0, 15) || '' }), jsx("td", { className: "px-6 py-4", children: jsx(Button$1, { variant: "ghost", size: "sm", className: "cursor-pointer", disabled: isDeletingIntegration || isDeletingCredential, onClick: () => handleDeleteCredential(cred.name, cred.platform, false, model.modelName), children: jsx(Trash, { className: "h-4 w-4" }) }) })] }, `${cred.name}-${model.modelName}-${modelIdx}`)))] }, cred.name + idx));
193292
193174
  })) })] }) })] }));
@@ -229549,7 +229431,7 @@ const useOverview = ({ tenantKey, mentorId, usergroupIds, }) => {
229549
229431
  ...((usergroupIds === null || usergroupIds === void 0 ? void 0 : usergroupIds.length) ? { usergroup_ids: usergroupIds } : {}),
229550
229432
  });
229551
229433
  // LLM spend joins the KPI row so cost is visible before anyone opens the
229552
- // Cost & LLM tab. It follows the page range; unavailable renders as an
229434
+ // Cost tab. It follows the page range; unavailable renders as an
229553
229435
  // em-dash, never $0.00.
229554
229436
  const globalFilters = (_o = useAnalyticsGlobalFilters()) === null || _o === void 0 ? void 0 : _o.filters;
229555
229437
  const { data: llmSpendData, isLoading: isLoadingLlmSpend, error: llmSpendError, } = useGetLlmUsageMetricsQuery({
@@ -229959,7 +229841,7 @@ const defaultTabs = [
229959
229841
  },
229960
229842
  {
229961
229843
  // The unified cost surface: ClickHouse billing + Langfuse LLM usage.
229962
- label: 'Cost & LLM',
229844
+ label: 'Cost',
229963
229845
  value: 'financial',
229964
229846
  },
229965
229847
  {
@@ -230507,7 +230389,7 @@ const byProvider = (models) => {
230507
230389
  .sort((a, b) => b.total_cost - a.total_cost);
230508
230390
  };
230509
230391
  /**
230510
- * Spend view-model for the Cost & LLM tab, backed by the ClickHouse
230392
+ * Spend view-model for the Cost tab, backed by the ClickHouse
230511
230393
  * `/api/ai-analytics/` cost endpoints (KPIs, per-model, per-user) and the
230512
230394
  * Langfuse `llm-usage` metrics resource (the daily spend series). The
230513
230395
  * deprecated `/api/analytics/financial/` family is gone; the return shape is
@@ -230716,7 +230598,7 @@ const formatCompactNumber = (value) => {
230716
230598
  const formatLatencySeconds = (value) => `${value.toFixed(2)}s`;
230717
230599
  const UNAVAILABLE = '—';
230718
230600
  /**
230719
- * The Langfuse side of the Cost & LLM tab: tokens, calls, latency percentiles
230601
+ * The Langfuse side of the Cost tab: tokens, calls, latency percentiles
230720
230602
  * and spend breakdowns per service and model. Ranked per-user/per-session
230721
230603
  * totals live on the Spend sub-tab (ClickHouse) — Langfuse cannot group by
230722
230604
  * those dimensions.
@@ -230788,7 +230670,7 @@ function useDebouncedValue(value, delay) {
230788
230670
  }
230789
230671
  /**
230790
230672
  * Tenant user picker for the analytics filter rows — memory, transcripts and
230791
- * the Cost & LLM traces list.
230673
+ * the Cost traces list.
230792
230674
  * Both endpoints behind it do the searching, so the dropdown only pages in the
230793
230675
  * first {@link USERS_PAGE_SIZE} matches for whatever is typed rather than
230794
230676
  * filtering client-side.
@@ -231105,7 +230987,7 @@ function SpendTab({ tenantKey, mentorId: currentMentorId, userId, onOpenUserTrac
231105
230987
  }) }), jsxs("div", { className: "flex items-center space-x-2", children: [jsxs(Button$1, { variant: "outline", size: "sm", className: "h-8 p-2 !w-auto !inline-flex bg-transparent", onClick: handleFirstPage, disabled: costPerUserPage === 1, children: [jsx("span", { className: "sr-only", children: t('analyticsAnalyticsFinancialStats.goToFirstPage') }), jsx(ChevronsLeft, { className: "h-4 w-4" })] }), jsxs(Button$1, { variant: "outline", size: "sm", className: "h-8 p-2 !w-auto !inline-flex bg-transparent", onClick: handlePreviousPage, disabled: costPerUserPage === 1, children: [jsx("span", { className: "sr-only", children: t('analyticsAnalyticsFinancialStats.goToPreviousPage') }), jsx(ChevronLeft, { className: "h-4 w-4" })] }), generatePageNumbers().map((page, index) => (jsx(React__default.Fragment, { children: page === '...' ? (jsx("span", { className: "px-2 text-gray-500", children: "..." })) : (jsx(Button$1, { variant: costPerUserPage === page ? 'default' : 'outline', size: "sm", className: `h-8 p-3 !w-auto !inline-flex ${costPerUserPage === page ? 'bg-blue-50 text-blue-600' : 'bg-transparent'}`, onClick: () => setCostPerUserPage(page), children: page })) }, index))), jsxs(Button$1, { variant: "outline", size: "sm", className: "h-8 p-2 !w-auto !inline-flex bg-transparent", onClick: handleNextPage, disabled: costPerUserPage === detailedCostPerUserData.total_pages, children: [jsx("span", { className: "sr-only", children: t('analyticsAnalyticsFinancialStats.goToNextPage') }), jsx(ChevronRight, { className: "h-4 w-4" })] }), jsxs(Button$1, { variant: "outline", size: "sm", className: "h-8 p-2 !w-auto !inline-flex bg-transparent", onClick: handleLastPage, disabled: costPerUserPage === detailedCostPerUserData.total_pages, children: [jsx("span", { className: "sr-only", children: t('analyticsAnalyticsFinancialStats.goToLastPage') }), jsx(ChevronsRight, { className: "h-4 w-4" })] })] })] }))] }) }) })] }));
231106
230988
  }
231107
230989
  /**
231108
- * The Cost & LLM tab: one surface over two backends, split where the backends
230990
+ * The Cost tab: one surface over two backends, split where the backends
231109
230991
  * actually split. Spend is ClickHouse (billing truth: marked-up USD, ranked
231110
230992
  * per-user history); Usage & latency and Traces are the Langfuse proxy
231111
230993
  * (tokens, latency percentiles, per-call drill-down). Every card names its
@@ -237407,6 +237289,41 @@ function SignupButton({ authUrl, tenantKey, redirectTo, label = 'Sign Up', onCli
237407
237289
  }
237408
237290
  SignupButton.displayName = 'SignupButton';
237409
237291
 
237292
+ /**
237293
+ * A provider's logo as the backend serves it. Provider naming and artwork are
237294
+ * owned by the API (`LLMResponse.logo`, `ExternalServiceInfo.logo`), so there
237295
+ * is no bundled fallback image: a provider without a logo renders a neutral
237296
+ * initial instead, keeping the card layout intact.
237297
+ */
237298
+ function LLMProviderLogo({ logo, label, alt = label, size = 32, className, }) {
237299
+ if (logo) {
237300
+ return (jsx(Image$4, { src: logo, alt: alt, className: cn('h-full w-full object-contain', className), width: size, height: size, loading: "lazy" }));
237301
+ }
237302
+ return (jsx("span", { role: "img", "aria-label": alt, "data-testid": "llm-provider-logo-placeholder", className: cn('flex h-full w-full items-center justify-center rounded bg-gray-100 text-xs font-semibold text-gray-500 uppercase', className), children: label.charAt(0) }));
237303
+ }
237304
+
237305
+ /**
237306
+ * Small pure utilities colocated with the LLM tab.
237307
+ * Ported from standalone's `@/lib/utils` and `@/lib/error`.
237308
+ */
237309
+ /**
237310
+ * The label a provider renders with: the backend's `display_name` when it
237311
+ * ships one, else its raw `name`. There is deliberately no frontend map behind
237312
+ * this — naming (and the logo next to it) is owned by the backend.
237313
+ */
237314
+ function llmProviderLabel(provider) {
237315
+ return provider.display_name || provider.name;
237316
+ }
237317
+ /**
237318
+ * The label a chat model renders with: the backend's `display_name` (`GPT-4.1
237319
+ * Mini`) when it ships one, else the raw wire key (`gpt-4.1-mini`). Same rule
237320
+ * as {@link llmProviderLabel}; every model list (LLM tab, sandbox) goes through
237321
+ * here so they never drift apart.
237322
+ */
237323
+ function llmModelLabel(model) {
237324
+ return model.display_name || model.llm_name;
237325
+ }
237326
+
237410
237327
  function OverlayModal({ open, onClose, title, description, className, children, }) {
237411
237328
  const t = useT();
237412
237329
  const [mounted, setMounted] = useState(false);
@@ -237442,15 +237359,22 @@ function canSwitchProvider(providers, name) {
237442
237359
  function LLMProviderModal({ isOpen, onClose, onSelect, llmProvider, isSelecting, mentorSettings, llms, }) {
237443
237360
  const t = useT();
237444
237361
  const [searchQuery, setSearchQuery] = useState('');
237362
+ const providerLabel = llmProviderLabel(llmProvider);
237445
237363
  const filteredLLMs = useMemo(() => {
237446
- return llmProvider === null || llmProvider === void 0 ? void 0 : llmProvider.chat_models.filter((llm) => llm.llm_name.toLowerCase().includes(searchQuery.toLowerCase()));
237364
+ const query = searchQuery.toLowerCase();
237365
+ // Match either label, same as the LLM tab's picker: rows show
237366
+ // `display_name`, but the raw key stays searchable.
237367
+ return llmProvider === null || llmProvider === void 0 ? void 0 : llmProvider.chat_models.filter((llm) => {
237368
+ var _a;
237369
+ return llm.llm_name.toLowerCase().includes(query) ||
237370
+ ((_a = llm.display_name) !== null && _a !== void 0 ? _a : '').toLowerCase().includes(query);
237371
+ });
237447
237372
  }, [searchQuery, llmProvider]);
237448
237373
  const switchLLMAllowed = canSwitchLLm(llmProvider);
237449
237374
  const switchProviderAllowed = canSwitchProvider(llms, llmProvider.name);
237450
237375
  return (jsxs(OverlayModal, { open: isOpen, onClose: onClose, title: t('clawSandboxLlmProviderModal.title'), description: t('clawSandboxLlmProviderModal.description', { providerName: llmProvider.name }), className: "max-w-4xl", children: [jsx("p", { className: "text-sm text-gray-600", children: t('clawSandboxLlmProviderModal.chooseLlmHint') }), 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: t('clawSandboxLlmProviderModal.searchPlaceholder'), className: "py-6 pl-9", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), autoComplete: "off", disabled: isSelecting })] }), 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: filteredLLMs.map((llm) => {
237451
237376
  const isActive = (mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_name) === llm.llm_name &&
237452
237377
  (mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.llm_provider) === llmProvider.name;
237453
- const providerDetails = getLLMProviderDetails(llmProvider.name, llm.llm_name);
237454
237378
  const isDisabled = !switchLLMAllowed || isSelecting || isActive || !switchProviderAllowed;
237455
237379
  return (jsxs("button", { disabled: isDisabled, onClick: () => {
237456
237380
  onSelect(llmProvider.name, llm.llm_name);
@@ -237458,11 +237382,9 @@ function LLMProviderModal({ isOpen, onClose, onSelect, llmProvider, isSelecting,
237458
237382
  'cursor-not-allowed border-gray-200 bg-white': isDisabled,
237459
237383
  'hover:border-blue-500 hover:bg-blue-50': !isDisabled,
237460
237384
  'cursor-not-allowed border-blue-500 bg-blue-50': isActive,
237461
- }), children: [jsx("span", { className: "flex h-8 w-8 items-center justify-center overflow-hidden rounded-lg", children: jsx("img", { src: providerDetails.logo, alt: t('clawSandboxLlmProviderModal.providerIconAlt', {
237462
- providerName: providerDetails.name,
237463
- }), className: cn('h-full w-full object-contain', {
237464
- grayscale: isDisabled && !isActive,
237465
- }), width: 32, height: 32, loading: "lazy" }) }), jsx("span", { className: "text-left text-sm font-medium text-[#646464]", children: llm.llm_name })] }, llm.llm_name));
237385
+ }), children: [jsx("span", { className: "flex h-8 w-8 items-center justify-center overflow-hidden rounded-lg", children: jsx(LLMProviderLogo, { logo: llmProvider.logo, label: providerLabel, alt: t('clawSandboxLlmProviderModal.providerIconAlt', {
237386
+ providerName: providerLabel,
237387
+ }), className: cn({ grayscale: isDisabled && !isActive }) }) }), jsx("span", { className: "text-left text-sm font-medium text-[#646464]", children: llmModelLabel(llm) })] }, llm.llm_name));
237466
237388
  }) })] }));
237467
237389
  }
237468
237390
 
@@ -237633,6 +237555,13 @@ function SandboxConfig({ platformKey, mentorUniqueId, username }) {
237633
237555
  const availableLLMProviders = useMemo(() => (llmProviders !== null && llmProviders !== void 0 ? llmProviders : []), [llmProviders]);
237634
237556
  const filteredLLMProviders = useMemo(() => availableLLMProviders.filter((provider) => provider.name.toLowerCase().includes(llmSearchQuery.toLowerCase())), [availableLLMProviders, llmSearchQuery]);
237635
237557
  const currentModel = parseModelIdentifier(agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.model);
237558
+ const currentModelProvider = availableLLMProviders.find((provider) => provider.name === currentModel.provider);
237559
+ // Same backend `display_name` the pickers render, so the trigger reads
237560
+ // `GPT-4.1 Mini` rather than the `gpt-4.1-mini` wire key stored in `model`.
237561
+ const currentChatModel = currentModelProvider === null || currentModelProvider === void 0 ? void 0 : currentModelProvider.chat_models.find((llm) => llm.llm_name === currentModel.llmName);
237562
+ const currentModelLabel = currentChatModel
237563
+ ? llmModelLabel(currentChatModel)
237564
+ : currentModel.llmName || (agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.model);
237636
237565
  const isUpdatingLLM = isUpdatingAgent;
237637
237566
  // Push fails server-side with "No configuration to push" when every AgentConfig
237638
237567
  // field is empty. Detect that locally so we can deny the click and tell the user
@@ -237920,17 +237849,17 @@ function SandboxConfig({ platformKey, mentorUniqueId, username }) {
237920
237849
  }), children: ((_f = mentorConfig === null || mentorConfig === void 0 ? void 0 : mentorConfig.last_config_push_status) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase()) === 'success' ? (jsx(CircleCheck, { className: "h-4 w-4 text-green-600" })) : (jsx(CircleX, { className: "h-4 w-4 text-red-600" })) }) }), jsx(TooltipContent, { className: "ibl-tooltip-content", children: jsx("p", { className: "capitalize", children: mentorConfig.last_config_push_status }) })] }) }))] }), jsx(TooltipProvider, { children: jsxs(Tooltip$1, { children: [jsx(TooltipTrigger, { asChild: true, children: jsx("span", { className: "inline-flex", children: jsxs(Button$1, { variant: "outline", size: "sm", onClick: handlePush, disabled: isPushing || !hasAgentConfiguration, "aria-disabled": isPushing || !hasAgentConfiguration, children: [isPushing ? (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin mr-2" })) : (jsx(RefreshCw, { className: "h-4 w-4 mr-2" })), t('clawSandboxSandboxConfig.push')] }) }) }), !hasAgentConfiguration && (jsx(TooltipContent, { className: "ibl-tooltip-content", children: jsx("p", { children: t('clawSandboxSandboxConfig.setAgentConfigBeforePushing') }) }))] }) })] }), jsxs("div", { className: "flex items-center justify-between rounded-lg border p-6", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-gray-700", children: t('clawSandboxSandboxConfig.model') }), jsx(TooltipProvider, { children: jsxs(Tooltip$1, { children: [jsx(TooltipTrigger, { "aria-label": t('clawSandboxSandboxConfig.modelInfoAriaLabel'), children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { className: "ibl-tooltip-content", children: jsx("p", { children: t('clawSandboxSandboxConfig.modelTooltip') }) })] }) })] }), jsx("div", { className: "flex items-center gap-3", children: (agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.model) ? (jsxs("button", { type: "button", className: "flex items-center gap-2 text-sm font-medium text-[#646464] transition-colors hover:text-[#484848] disabled:opacity-50", onClick: () => {
237921
237850
  setLlmSearchQuery('');
237922
237851
  setShowLLMPicker(true);
237923
- }, disabled: isUpdatingLLM, "aria-label": t('clawSandboxSandboxConfig.changeModel'), children: [jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-white border", children: jsx("img", { src: getLLMProviderDetails(currentModel.provider, currentModel.llmName).logo, alt: t('clawSandboxSandboxConfig.providerLogoAlt', {
237852
+ }, disabled: isUpdatingLLM, "aria-label": t('clawSandboxSandboxConfig.changeModel'), children: [jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-white border", children: jsx(LLMProviderLogo, { logo: currentModelProvider === null || currentModelProvider === void 0 ? void 0 : currentModelProvider.logo, label: currentModel.llmName || currentModel.provider, alt: t('clawSandboxSandboxConfig.providerLogoAlt', {
237924
237853
  name: currentModel.llmName || currentModel.provider,
237925
- }), className: "h-5 w-5 object-contain", width: 20, height: 20, loading: "lazy" }) }), jsx("span", { className: "max-w-[200px] truncate", children: currentModel.llmName || agentConfig.model }), jsx(ChevronDown, { className: "h-4 w-4 text-gray-500" })] })) : (jsx(Button$1, { variant: "outline", size: "sm", onClick: () => {
237854
+ }), size: 20, className: "h-5 w-5" }) }), jsx("span", { className: "max-w-[200px] truncate", children: currentModelLabel }), jsx(ChevronDown, { className: "h-4 w-4 text-gray-500" })] })) : (jsx(Button$1, { variant: "outline", size: "sm", onClick: () => {
237926
237855
  setLlmSearchQuery('');
237927
237856
  setShowLLMPicker(true);
237928
237857
  }, disabled: isUpdatingLLM, children: t('clawSandboxSandboxConfig.selectModel') })) })] }), jsxs(OverlayModal, { open: showDisconnectConfirm, onClose: () => setShowDisconnectConfirm(false), title: t('clawSandboxSandboxConfig.disconnectInstanceTitle'), className: "max-w-md", children: [jsx("p", { className: "text-sm text-gray-600", children: t('clawSandboxSandboxConfig.disconnectInstanceConfirm', {
237929
237858
  name: (_g = connectedInstance === null || connectedInstance === void 0 ? void 0 : connectedInstance.name) !== null && _g !== void 0 ? _g : mentorConfig.server_name,
237930
237859
  }) }), jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [jsx(Button$1, { variant: "outline", onClick: () => setShowDisconnectConfirm(false), children: t('clawSandboxSandboxConfig.cancel') }), jsx(Button$1, { onClick: handleDisconnect, disabled: isDeleting, className: "bg-red-600 text-white hover:bg-red-700", children: isDeleting
237931
237860
  ? t('clawSandboxSandboxConfig.disconnecting')
237932
- : t('clawSandboxSandboxConfig.disconnect') })] })] }), showLLMPicker && !selectedLLMProvider && (jsx(OverlayModal, { open: !selectedLLMProvider, onClose: closeLLMPicker, title: t('clawSandboxSandboxConfig.selectProviderTitle'), description: t('clawSandboxSandboxConfig.selectProviderDescription'), className: "max-w-3xl", children: !resolvedUsername ? (jsx("p", { className: "py-6 text-center text-sm text-gray-500", children: t('clawSandboxSandboxConfig.noUsernameForProviders') })) : (jsxs(Fragment$1, { 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, { id: "llm-search", name: "llm-search", type: "search", placeholder: t('clawSandboxSandboxConfig.searchProvidersPlaceholder'), className: "w-full py-6 pl-9", value: llmSearchQuery, onChange: (e) => setLlmSearchQuery(e.target.value) })] }), isLoadingLLMProviders ? (jsx("div", { className: "flex items-center justify-center py-10", children: jsx(LoaderCircle, { className: "h-5 w-5 animate-spin text-blue-500" }) })) : isLLMProvidersError ? (jsx("p", { className: "py-6 text-center text-sm text-red-500", children: t('clawSandboxSandboxConfig.failedToLoadProviders') })) : filteredLLMProviders.length === 0 ? (jsx("p", { className: "py-6 text-center text-sm text-gray-500", children: t('clawSandboxSandboxConfig.noProvidersAvailable') })) : (jsx("div", { className: "mt-2 grid max-h-[50vh] grid-cols-1 gap-4 overflow-y-auto pr-2 sm:grid-cols-2 lg:grid-cols-3", children: filteredLLMProviders.map((provider) => {
237933
- const providerDetails = getLLMProviderDetails(provider.name);
237861
+ : t('clawSandboxSandboxConfig.disconnect') })] })] }), showLLMPicker && (jsx(OverlayModal, { open: true, onClose: closeLLMPicker, title: t('clawSandboxSandboxConfig.selectProviderTitle'), description: t('clawSandboxSandboxConfig.selectProviderDescription'), className: "max-w-3xl", children: !resolvedUsername ? (jsx("p", { className: "py-6 text-center text-sm text-gray-500", children: t('clawSandboxSandboxConfig.noUsernameForProviders') })) : (jsxs(Fragment$1, { 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, { id: "llm-search", name: "llm-search", type: "search", placeholder: t('clawSandboxSandboxConfig.searchProvidersPlaceholder'), className: "w-full py-6 pl-9", value: llmSearchQuery, onChange: (e) => setLlmSearchQuery(e.target.value) })] }), isLoadingLLMProviders ? (jsx("div", { className: "flex items-center justify-center py-10", children: jsx(LoaderCircle, { className: "h-5 w-5 animate-spin text-blue-500" }) })) : isLLMProvidersError ? (jsx("p", { className: "py-6 text-center text-sm text-red-500", children: t('clawSandboxSandboxConfig.failedToLoadProviders') })) : filteredLLMProviders.length === 0 ? (jsx("p", { className: "py-6 text-center text-sm text-gray-500", children: t('clawSandboxSandboxConfig.noProvidersAvailable') })) : (jsx("div", { className: "mt-2 grid max-h-[50vh] grid-cols-1 gap-4 overflow-y-auto pr-2 sm:grid-cols-2 lg:grid-cols-3", children: filteredLLMProviders.map((provider) => {
237862
+ const providerLabel = llmProviderLabel(provider);
237934
237863
  const isSelected = currentModel.provider === provider.name;
237935
237864
  return (jsxs("button", { type: "button", className: cn('flex cursor-pointer items-center gap-3 rounded-lg border border-gray-200 bg-white p-4 text-left shadow-sm transition-shadow hover:shadow-md', { 'border-blue-500': isSelected }), onClick: () => {
237936
237865
  setSelectedLLMProvider({
@@ -237943,10 +237872,10 @@ function SandboxConfig({ platformKey, mentorUniqueId, username }) {
237943
237872
  main_has_credentials: provider.main_has_credentials,
237944
237873
  can_use_main_keys: provider.can_use_main_keys,
237945
237874
  });
237946
- }, children: [jsx("div", { className: "h-8 w-8 flex-shrink-0", children: jsx("img", { src: providerDetails.logo, alt: t('clawSandboxSandboxConfig.providerLogoAlt', {
237947
- name: providerDetails.name,
237948
- }), className: "h-full w-full object-contain", width: 32, height: 32, loading: "lazy" }) }), jsx("span", { className: "text-sm font-medium text-gray-900", children: providerDetails.name })] }, provider.id));
237949
- }) }))] })) })), selectedLLMProvider && (jsx(LLMProviderModal, { isOpen: true, onClose: closeLLMPicker, onSelect: handleSelectLLM, llmProvider: selectedLLMProvider, isSelecting: isUpdatingLLM, mentorSettings: {
237875
+ }, children: [jsx("div", { className: "h-8 w-8 flex-shrink-0", children: jsx(LLMProviderLogo, { logo: provider.logo, label: providerLabel, alt: t('clawSandboxSandboxConfig.providerLogoAlt', {
237876
+ name: providerLabel,
237877
+ }) }) }), jsx("span", { className: "text-sm font-medium text-gray-900", children: providerLabel })] }, provider.id));
237878
+ }) }))] })) })), selectedLLMProvider && (jsx(LLMProviderModal, { isOpen: true, onClose: () => setSelectedLLMProvider(null), onSelect: handleSelectLLM, llmProvider: selectedLLMProvider, isSelecting: isUpdatingLLM, mentorSettings: {
237950
237879
  llm_name: currentModel.llmName,
237951
237880
  llm_provider: currentModel.provider,
237952
237881
  }, llms: availableLLMProviders }))] })) : null;