@iblai/iblai-js 2.18.0 → 2.19.0

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.
@@ -96692,6 +96692,7 @@ var analyticsAccessTimeHeatmap$3 = {
96692
96692
  tooltipActiveUsers: "{count} active users"
96693
96693
  };
96694
96694
  var analyticsAnalyticsAuditLogStats$3 = {
96695
+ anonymous: "Anonymous",
96695
96696
  scopeAgent: "Agent",
96696
96697
  scopeAriaLabel: "Audit log scope",
96697
96698
  scopePlatform: "Platform",
@@ -96820,6 +96821,7 @@ var analyticsAgentScopeLink$3 = {
96820
96821
  scopeToAgent: "View analytics for {name}"
96821
96822
  };
96822
96823
  var analyticsAnalyticsMemoryStats$3 = {
96824
+ anonymous: "Anonymous",
96823
96825
  noPermission: "You do not have permission to view user memories.",
96824
96826
  scopeLabel: "Memory scope",
96825
96827
  scopeMentor: "This Agent",
@@ -97034,6 +97036,7 @@ var analyticsAnalyticsTopicsStats$3 = {
97034
97036
  topicsRankedFooter: "{shown} of {total} topics · ranked by message volume"
97035
97037
  };
97036
97038
  var analyticsAnalyticsTranscriptsStats$3 = {
97039
+ anonymous: "Anonymous",
97037
97040
  justNow: "Just now",
97038
97041
  minutesAgo: "{count} min ago",
97039
97042
  hoursAgo: "{count} hour{count, plural, one {} other {s}} ago",
@@ -103973,6 +103976,7 @@ var analyticsAccessTimeHeatmap$2 = {
103973
103976
  tooltipActiveUsers: "{count} utilisateurs actifs"
103974
103977
  };
103975
103978
  var analyticsAnalyticsAuditLogStats$2 = {
103979
+ anonymous: "Anonyme",
103976
103980
  scopeAgent: "Agent",
103977
103981
  scopeAriaLabel: "Portée du journal d'audit",
103978
103982
  scopePlatform: "Plateforme",
@@ -104101,6 +104105,7 @@ var analyticsAgentScopeLink$2 = {
104101
104105
  scopeToAgent: "Voir les analyses de {name}"
104102
104106
  };
104103
104107
  var analyticsAnalyticsMemoryStats$2 = {
104108
+ anonymous: "Anonyme",
104104
104109
  noPermission: "Vous n'avez pas l'autorisation de consulter les mémoires des utilisateurs.",
104105
104110
  scopeLabel: "Portée de la mémoire",
104106
104111
  scopeMentor: "Cet agent",
@@ -104315,6 +104320,7 @@ var analyticsAnalyticsTopicsStats$2 = {
104315
104320
  topicsRankedFooter: "{shown} sur {total} sujets · classés par volume de messages"
104316
104321
  };
104317
104322
  var analyticsAnalyticsTranscriptsStats$2 = {
104323
+ anonymous: "Anonyme",
104318
104324
  justNow: "À l'instant",
104319
104325
  minutesAgo: "Il y a {count} min",
104320
104326
  hoursAgo: "Il y a {count} heure{count, plural, one {} other {s}}",
@@ -111254,6 +111260,7 @@ var analyticsAccessTimeHeatmap$1 = {
111254
111260
  tooltipActiveUsers: "{count} usuarios activos"
111255
111261
  };
111256
111262
  var analyticsAnalyticsAuditLogStats$1 = {
111263
+ anonymous: "Anónimo",
111257
111264
  scopeAgent: "Agente",
111258
111265
  scopeAriaLabel: "Alcance del registro de auditoría",
111259
111266
  scopePlatform: "Plataforma",
@@ -111382,6 +111389,7 @@ var analyticsAgentScopeLink$1 = {
111382
111389
  scopeToAgent: "Ver analíticas de {name}"
111383
111390
  };
111384
111391
  var analyticsAnalyticsMemoryStats$1 = {
111392
+ anonymous: "Anónimo",
111385
111393
  noPermission: "No tienes permiso para ver los recuerdos de los usuarios.",
111386
111394
  scopeLabel: "Alcance de la memoria",
111387
111395
  scopeMentor: "Este agente",
@@ -111596,6 +111604,7 @@ var analyticsAnalyticsTopicsStats$1 = {
111596
111604
  topicsRankedFooter: "{shown} de {total} temas · ordenados por volumen de mensajes"
111597
111605
  };
111598
111606
  var analyticsAnalyticsTranscriptsStats$1 = {
111607
+ anonymous: "Anónimo",
111599
111608
  justNow: "Ahora mismo",
111600
111609
  minutesAgo: "Hace {count} min",
111601
111610
  hoursAgo: "Hace {count} hora{count, plural, one {} other {s}}",
@@ -118535,6 +118544,7 @@ var analyticsAccessTimeHeatmap = {
118535
118544
  tooltipActiveUsers: "{count} 位活跃用户"
118536
118545
  };
118537
118546
  var analyticsAnalyticsAuditLogStats = {
118547
+ anonymous: "匿名",
118538
118548
  scopeAgent: "智能体",
118539
118549
  scopeAriaLabel: "审计日志范围",
118540
118550
  scopePlatform: "平台",
@@ -118663,6 +118673,7 @@ var analyticsAgentScopeLink = {
118663
118673
  scopeToAgent: "查看 {name} 的分析"
118664
118674
  };
118665
118675
  var analyticsAnalyticsMemoryStats = {
118676
+ anonymous: "匿名",
118666
118677
  noPermission: "您没有查看用户记忆的权限。",
118667
118678
  scopeLabel: "记忆范围",
118668
118679
  scopeMentor: "当前智能体",
@@ -118877,6 +118888,7 @@ var analyticsAnalyticsTopicsStats = {
118877
118888
  topicsRankedFooter: "共 {total} 个主题,显示 {shown} 个 · 按消息量排序"
118878
118889
  };
118879
118890
  var analyticsAnalyticsTranscriptsStats = {
118891
+ anonymous: "匿名",
118880
118892
  justNow: "刚刚",
118881
118893
  minutesAgo: "{count} 分钟前",
118882
118894
  hoursAgo: "{count} 小时前",
@@ -221826,7 +221838,7 @@ function RetrievedDocumentsButton({ sessionId = '', documents, className, label,
221826
221838
  // Safe inside clickable transcript rows: the row must not react.
221827
221839
  event.stopPropagation();
221828
221840
  setIsOpen(true);
221829
- }, "data-testid": "retrieved-documents-button", children: [jsx(FileText, { className: "h-4 w-4 text-blue-600" }), jsx("span", { children: label !== null && label !== void 0 ? label : t('retrievedDocumentsButton.title') })] }), jsx(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: jsxs(DialogContent, { className: "flex max-h-[80vh] flex-col gap-0 p-0 sm:max-w-[425px] md:max-w-[600px]", children: [jsx(DialogHeader, { className: "border-b px-4 py-2", children: jsxs(DialogTitle, { className: "flex items-center gap-2 text-base font-medium text-[#646464]", children: [jsx(FileText, { className: "h-5 w-5 text-blue-600" }), t('retrievedDocumentsButton.title')] }) }), jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", "data-testid": "retrieved-documents-scroll", children: jsx(DocumentSidebar, { isModal: true, sessionId: sessionId, documents: documents }) })] }) })] }));
221841
+ }, "data-testid": "retrieved-documents-button", children: [jsx(FileText, { className: "h-4 w-4 text-blue-600" }), jsx("span", { children: label !== null && label !== void 0 ? label : t('retrievedDocumentsButton.title') })] }), jsx(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: jsxs(DialogContent, { className: "flex max-h-[80vh] flex-col gap-0 p-0 sm:max-w-[425px] md:max-w-[600px]", children: [jsx(DialogHeader, { className: "border-b border-gray-200 px-4 py-2", children: jsxs(DialogTitle, { className: "flex items-center gap-2 text-base font-medium text-[#646464]", children: [jsx(FileText, { className: "h-5 w-5 text-blue-600" }), t('retrievedDocumentsButton.title')] }) }), jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", "data-testid": "retrieved-documents-scroll", children: jsx(DocumentSidebar, { isModal: true, sessionId: sessionId, documents: documents }) })] }) })] }));
221830
221842
  }
221831
221843
 
221832
221844
  var COLLAPSIBLE_NAME = "Collapsible";
@@ -222288,6 +222300,11 @@ function useLlmDisplayName({ llmName, llmConfig, org, userId, mentorId, }) {
222288
222300
  * white circle (the catalogue's artwork, or the bot icon when it has none)
222289
222301
  * beside the model's display name. Read-only; used on admin transcripts to
222290
222302
  * name the model a turn ran on.
222303
+ *
222304
+ * It sets no font size, weight or colour of its own and sizes the logo in
222305
+ * `em`, so it reads as one more fact in whatever row holds it — a `text-xs
222306
+ * text-gray-500` transcript card or the details panel — rather than as a
222307
+ * larger, bolder pill beside them. Pass `className` for the nav bar's look.
222291
222308
  */
222292
222309
  function LlmModelBadge({ model, provider, tenantKey, mentorId, className, }) {
222293
222310
  var _a, _b;
@@ -222308,9 +222325,9 @@ function LlmModelBadge({ model, provider, tenantKey, mentorId, className, }) {
222308
222325
  const label = displayName || providerDetails.displayName;
222309
222326
  if (!label)
222310
222327
  return null;
222311
- return (jsxs("span", { className: cn('inline-flex max-w-full items-center gap-2 text-sm font-medium text-[#646464]', className), title: providerDetails.displayName && providerDetails.displayName !== label
222328
+ return (jsxs("span", { className: cn('inline-flex max-w-full items-center gap-[0.5em] align-middle', className), title: providerDetails.displayName && providerDetails.displayName !== label
222312
222329
  ? `${label} · ${providerDetails.displayName}`
222313
- : label, "data-testid": "llm-model-badge", children: [jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-white", children: providerDetails.logo ? (jsx("img", { src: providerDetails.logo, alt: `${label} model logo`, className: "h-5 w-5 object-contain", height: 32, width: 32, loading: "lazy" })) : (jsx(Bot, { className: "h-4 w-4", "aria-hidden": "true" })) }), jsx("span", { className: "max-w-[220px] truncate", children: label })] }));
222330
+ : label, "data-testid": "llm-model-badge", children: [jsx("span", { className: "flex h-[1.5em] w-[1.5em] flex-shrink-0 items-center justify-center rounded-full bg-white", children: providerDetails.logo ? (jsx("img", { src: providerDetails.logo, alt: `${label} model logo`, className: "h-[1.25em] w-[1.25em] object-contain", height: 32, width: 32, loading: "lazy" })) : (jsx(Bot, { className: "h-[1em] w-[1em]", "aria-hidden": "true" })) }), jsx("span", { className: "max-w-[220px] truncate", children: label })] }));
222314
222331
  }
222315
222332
 
222316
222333
  /** SPAs whose users have coursework, so the profile leads with a Gradebook tab. */
@@ -222356,17 +222373,18 @@ function UserProfileDialog({ tenantKey, username, isOpen, onClose, customization
222356
222373
  function UserProfileLink({ tenantKey, username, display, className, testId, customization, currentSPA, }) {
222357
222374
  const t = useT();
222358
222375
  const [isOpen, setIsOpen] = React__default.useState(false);
222376
+ // The text truncates inside an `inline-flex` box: an `inline-block` with
222377
+ // hidden overflow puts its baseline on its bottom edge, so the value would
222378
+ // ride above the "User:" label beside it.
222359
222379
  if (!username) {
222360
- // `inline-block`, or `truncate`'s `overflow: hidden` has nothing to clip:
222361
- // an inline box takes its width from its own text.
222362
- return (jsx("span", { className: cn('inline-block max-w-full truncate', className), "data-testid": testId, children: display }));
222380
+ return (jsx("span", { className: cn('inline-flex max-w-full', className), "data-testid": testId, children: jsx("span", { className: "min-w-0 truncate", children: display }) }));
222363
222381
  }
222364
222382
  return (jsxs(Fragment$1, { children: [jsx("button", { type: "button", onClick: (event) => {
222365
222383
  event.stopPropagation();
222366
222384
  setIsOpen(true);
222367
222385
  },
222368
222386
  // Rows that open on Enter/Space would otherwise swallow the key press.
222369
- onKeyDown: (event) => event.stopPropagation(), className: cn('max-w-full cursor-pointer truncate text-left hover:text-blue-600 hover:underline focus:text-blue-600 focus:outline-none', className), "aria-label": t('tabsUserstab.viewProfileAriaLabel', { name: display }), "data-testid": testId, children: display }), jsx(UserProfileDialog, { tenantKey: tenantKey, username: username, isOpen: isOpen, onClose: () => setIsOpen(false), customization: customization, currentSPA: currentSPA })] }));
222387
+ onKeyDown: (event) => event.stopPropagation(), className: cn('inline-flex max-w-full cursor-pointer text-left hover:text-blue-600 hover:underline focus:text-blue-600 focus:outline-none', className), "aria-label": t('tabsUserstab.viewProfileAriaLabel', { name: display }), "data-testid": testId, children: jsx("span", { className: "min-w-0 truncate", children: display }) }), jsx(UserProfileDialog, { tenantKey: tenantKey, username: username, isOpen: isOpen, onClose: () => setIsOpen(false), customization: customization, currentSPA: currentSPA })] }));
222370
222388
  }
222371
222389
 
222372
222390
  const AdvancedPagination = ({ currentPage, totalPages, onPageChange, totalCount, itemsPerPage, itemLabel = 'items', variant = 'default', className, }) => {
@@ -259773,6 +259791,154 @@ AnalyticsScopeContext.Provider;
259773
259791
 
259774
259792
  Array.from({ length: 24 }, (_, i) => i);
259775
259793
 
259794
+ /**
259795
+ * One place that decides how a user is shown.
259796
+ *
259797
+ * Backend payloads name their user in a dozen ways — `email` / `lti_email` /
259798
+ * `user_email`, `username` / `student` / `lti_username`, `name` /
259799
+ * `full_name` / `user_full_name`, or a nested `user` object — and most of
259800
+ * them can be empty, `null`, or a placeholder like "None". Every surface used
259801
+ * to pick through those on its own. `resolveUserIdentity()` takes whatever
259802
+ * the payload is and returns what to show, what to open a profile on, and
259803
+ * the avatar initial, so the rule lives here and can be tweaked once.
259804
+ *
259805
+ * Default rule: a real full name first (see {@link hasDistinctName}), else
259806
+ * email → username → the anonymous label.
259807
+ */
259808
+ const DEFAULT_USER_IDENTITY_ORDER = ['email', 'name', 'username'];
259809
+ /** Order used when the payload carries a real name (see {@link hasDistinctName}). */
259810
+ const NAME_FIRST_ORDER = ['name', 'email', 'username'];
259811
+ /**
259812
+ * Lower-case letters and digits of any script, accents and punctuation dropped,
259813
+ * so "Conrad Mugabe" / "conrad.mugabe" and "José" / "jose" compare equal.
259814
+ */
259815
+ const identityKey = (value) => (value !== null && value !== void 0 ? value : '')
259816
+ .normalize('NFKD')
259817
+ .replace(/[^\p{L}\p{N}]/gu, '')
259818
+ .toLowerCase();
259819
+ /**
259820
+ * Whether `name` is a real name rather than a restatement of the username.
259821
+ *
259822
+ * Many payloads fill the name field with the username ("conradmugabe" →
259823
+ * "Conrad Mugabe", "fayyaz_4_ibleducation_com"), which tells an admin nothing
259824
+ * the email does not. A name that matches none of the usernames on the record
259825
+ * is the most recognisable thing on it — including for LTI learners, whose
259826
+ * minted `<opaque-id>@edx-lti-1p3.com` address and username identify nobody.
259827
+ */
259828
+ function hasDistinctName(name, ...usernames) {
259829
+ const key = identityKey(name);
259830
+ if (!key)
259831
+ return false;
259832
+ return usernames.every((username) => key !== identityKey(username));
259833
+ }
259834
+ const DEFAULT_ANONYMOUS_LABEL = 'Anonymous';
259835
+ /** Values backends emit instead of "nothing". Compared case-insensitively. */
259836
+ const PLACEHOLDERS = new Set([
259837
+ '',
259838
+ 'none',
259839
+ 'null',
259840
+ 'undefined',
259841
+ 'n/a',
259842
+ 'na',
259843
+ '-',
259844
+ 'anonymous',
259845
+ 'anonymoususer',
259846
+ ]);
259847
+ /**
259848
+ * `local@domain.tld` with no whitespace, checked with a single pass rather than
259849
+ * a backtracking pattern (CodeQL flags `[^@]+@[^@]+\.[^@]+` as polynomial on
259850
+ * adversarial input).
259851
+ */
259852
+ const isEmailLike = (value) => {
259853
+ const text = value.trim();
259854
+ if (!text || /\s/.test(text))
259855
+ return false;
259856
+ const at = text.indexOf('@');
259857
+ if (at <= 0 || at !== text.lastIndexOf('@') || at === text.length - 1)
259858
+ return false;
259859
+ const domain = text.slice(at + 1);
259860
+ const dot = domain.indexOf('.');
259861
+ // A dot somewhere inside the domain, with something after the last one.
259862
+ return dot > 0 && !domain.endsWith('.');
259863
+ };
259864
+ /** Trimmed string, or undefined for blanks, non-strings and placeholders. */
259865
+ const clean = (value) => {
259866
+ if (typeof value !== 'string')
259867
+ return undefined;
259868
+ const trimmed = value.trim();
259869
+ return PLACEHOLDERS.has(trimmed.toLowerCase()) ? undefined : trimmed;
259870
+ };
259871
+ const firstOf = (...candidates) => {
259872
+ for (const candidate of candidates) {
259873
+ const value = clean(candidate);
259874
+ if (value)
259875
+ return value;
259876
+ }
259877
+ return undefined;
259878
+ };
259879
+ const nested = (source) => source.user && typeof source.user === 'object' ? source.user : undefined;
259880
+ /**
259881
+ * Resolve how a user should be displayed from any payload that names one.
259882
+ *
259883
+ * - `email`: `lti_email`, `email`, `user_email`, the nested `user`'s email —
259884
+ * or a username field that turns out to hold an email address.
259885
+ * - `username`: `lti_username`, `username`, `student`, `user_username`, the
259886
+ * nested `user`'s username, or a bare string `user` value (a numeric id is
259887
+ * ignored).
259888
+ * - `name`: `user_full_name`, `full_name`, `name`, `first_name last_name`, or
259889
+ * the nested `user`'s name.
259890
+ * - `profileUsername`: the first *platform* username (never an LTI claim).
259891
+ *
259892
+ * A string source is treated as an email when it looks like one, else as a
259893
+ * username. Placeholder strings ("None", "-", "anonymous"…) count as absent.
259894
+ *
259895
+ * The default order flips to name → email → username when the record carries a
259896
+ * real name (see {@link hasDistinctName}): "Joseph Oti" is more use to an admin
259897
+ * than his address, while a name that merely repeats the username is not, so
259898
+ * those records keep showing the email. An explicit `order` is always obeyed.
259899
+ */
259900
+ function resolveUserIdentity(source, options = {}) {
259901
+ var _a, _b;
259902
+ const anonymousLabel = options.anonymousLabel || DEFAULT_ANONYMOUS_LABEL;
259903
+ const order = ((_a = options.order) === null || _a === void 0 ? void 0 : _a.length) ? options.order : DEFAULT_USER_IDENTITY_ORDER;
259904
+ const record = source && typeof source === 'object' ? source : { user: source !== null && source !== void 0 ? source : null };
259905
+ const inner = nested(record);
259906
+ // A bare `user` value is a username — unless it is a numeric id, which
259907
+ // names nobody: transcript rows carry `user: 1` beside `username: null`,
259908
+ // and showing "1" for that person is worse than "Anonymous".
259909
+ const bareString = typeof record.user === 'string' ? clean(record.user) : undefined;
259910
+ const bareUser = bareString && !/^\d+$/.test(bareString) ? bareString : undefined;
259911
+ let email = firstOf(record.lti_email, record.email, record.user_email, inner === null || inner === void 0 ? void 0 : inner.lti_email, inner === null || inner === void 0 ? void 0 : inner.email, inner === null || inner === void 0 ? void 0 : inner.user_email);
259912
+ const platformUsername = firstOf(record.username, record.student, record.user_username, inner === null || inner === void 0 ? void 0 : inner.username, inner === null || inner === void 0 ? void 0 : inner.student, inner === null || inner === void 0 ? void 0 : inner.user_username, bareUser && !isEmailLike(bareUser) ? bareUser : undefined);
259913
+ let username = firstOf(record.lti_username, inner === null || inner === void 0 ? void 0 : inner.lti_username, platformUsername);
259914
+ const composed = [clean(record.first_name), clean(record.last_name)].filter(Boolean).join(' ');
259915
+ const name = firstOf(record.user_full_name, record.full_name, record.name, composed, inner === null || inner === void 0 ? void 0 : inner.user_full_name, inner === null || inner === void 0 ? void 0 : inner.full_name, inner === null || inner === void 0 ? void 0 : inner.name);
259916
+ // A bare string that looks like an email is an email, and so is a
259917
+ // "username" field that actually carries one.
259918
+ if (!email && bareUser && isEmailLike(bareUser))
259919
+ email = bareUser;
259920
+ if (!email && username && isEmailLike(username)) {
259921
+ email = username;
259922
+ username = undefined;
259923
+ }
259924
+ const facts = { email, username, name };
259925
+ // Every username on the record counts: the platform one a profile opens on
259926
+ // and the LTI claim that would be displayed.
259927
+ const labelOrder = !((_b = options.order) === null || _b === void 0 ? void 0 : _b.length) && hasDistinctName(name, platformUsername, username)
259928
+ ? NAME_FIRST_ORDER
259929
+ : order;
259930
+ const label = labelOrder.map((field) => facts[field]).find(Boolean);
259931
+ return {
259932
+ label: label !== null && label !== void 0 ? label : anonymousLabel,
259933
+ email,
259934
+ username,
259935
+ name,
259936
+ profileUsername: platformUsername,
259937
+ initial: (label !== null && label !== void 0 ? label : anonymousLabel).charAt(0).toUpperCase() || 'A',
259938
+ isAnonymous: !label,
259939
+ };
259940
+ }
259941
+
259776
259942
  const asArray = (value) => (Array.isArray(value) ? value : []);
259777
259943
  const hasEntries = (value) => !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0;
259778
259944
  /** Rollup counts as the conversation-list endpoint reports them: numbers, numeric strings or `null`. */
@@ -260084,7 +260250,7 @@ function SectionHeading({ icon, children }) {
260084
260250
  return (jsxs("h4", { className: "mb-2 flex items-center gap-1.5 text-xs font-semibold tracking-wide text-gray-700 uppercase", children: [icon, children] }));
260085
260251
  }
260086
260252
  const MODEL_KEYS = new Set(['llm_model', 'llm_provider']);
260087
- function MetadataSection({ metadata, renderModel, }) {
260253
+ function MetadataSection({ metadata, renderModel, tenantKey, mentorId, }) {
260088
260254
  var _a, _b;
260089
260255
  const t = useT();
260090
260256
  const labelFor = useFieldLabels();
@@ -260092,7 +260258,9 @@ function MetadataSection({ metadata, renderModel, }) {
260092
260258
  const model = ((_a = metadata.llm_model) !== null && _a !== void 0 ? _a : '').trim() || undefined;
260093
260259
  const provider = ((_b = metadata.llm_provider) !== null && _b !== void 0 ? _b : '').trim() || undefined;
260094
260260
  const hasModel = !!(model || provider);
260095
- return (jsxs("section", { "data-testid": "transcript-turn-metadata", children: [jsx(SectionHeading, { icon: jsx(Info$3, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), children: t('analyticsTranscriptTurnDetails.metadata') }), hasModel && (jsx("div", { "data-testid": "transcript-turn-model", className: "mb-2", children: (renderModel !== null && renderModel !== void 0 ? renderModel : defaultRenderModel)({ model, provider }) })), jsx(DetailRows, { entries: rest, labelFor: labelFor })] }));
260261
+ return (jsxs("section", { "data-testid": "transcript-turn-metadata", children: [jsx(SectionHeading, { icon: jsx(Info$3, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), children: t('analyticsTranscriptTurnDetails.metadata') }), hasModel && (jsx("div", { "data-testid": "transcript-turn-model", className: "mb-2", children: renderModel
260262
+ ? renderModel({ model, provider })
260263
+ : defaultRenderModel({ model, provider, tenantKey, mentorId }) })), jsx(DetailRows, { entries: rest, labelFor: labelFor })] }));
260096
260264
  }
260097
260265
  function RequestContextSection({ requestContext }) {
260098
260266
  const t = useT();
@@ -260107,7 +260275,7 @@ function RequestContextSection({ requestContext }) {
260107
260275
  */
260108
260276
  const defaultRenderDocuments = (documents) => (jsx(RetrievedDocumentsButton, { documents: toRetrievedDocuments(documents) }));
260109
260277
  const defaultRenderToolCalls = (toolCalls) => (jsx(ToolCallIndicator, { toolCalls: toToolCallInfos(toolCalls), showResults: true }));
260110
- const defaultRenderModel = ({ model, provider }) => (jsx(LlmModelBadge, { model: model, provider: provider }));
260278
+ const defaultRenderModel = ({ model, provider, tenantKey, mentorId, }) => jsx(LlmModelBadge, { model: model, provider: provider, tenantKey: tenantKey, mentorId: mentorId });
260111
260279
  const defaultRenderFiles = (files) => (jsx(TranscriptFileCards, { files: files }));
260112
260280
  /**
260113
260281
  * Collapsible "Sources / Tools / Metadata" panel for one AI turn of an admin
@@ -260117,7 +260285,7 @@ const defaultRenderFiles = (files) => (jsx(TranscriptFileCards, { files: files }
260117
260285
  * renders nothing at all when every field is null or empty, so it is safe to
260118
260286
  * drop under every AI bubble.
260119
260287
  */
260120
- function TranscriptTurnDetails({ turn, className, defaultOpen = false, renderDocuments, renderToolCalls, renderModel, renderFiles, }) {
260288
+ function TranscriptTurnDetails({ turn, className, defaultOpen = false, renderDocuments, renderToolCalls, renderModel, renderFiles, tenantKey, mentorId, }) {
260121
260289
  const t = useT();
260122
260290
  const labelFor = useFieldLabels();
260123
260291
  const [open, setOpen] = React__default.useState(defaultOpen);
@@ -260131,7 +260299,7 @@ function TranscriptTurnDetails({ turn, className, defaultOpen = false, renderDoc
260131
260299
  const requestContext = hasEntries(turn === null || turn === void 0 ? void 0 : turn.request_context) ? turn === null || turn === void 0 ? void 0 : turn.request_context : null;
260132
260300
  return (jsx(DetailCtx.Provider, { value: detailContext, children: jsxs(Collapsible, { open: open, onOpenChange: setOpen, className: cn('mt-2', className), "data-testid": "transcript-turn-details", children: [jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [jsx(CollapsibleTrigger, { asChild: true, children: jsxs(Button$1, { type: "button", variant: "outline", size: "sm", className: "flex items-center gap-2 border-gray-200 bg-white text-gray-700 hover:bg-gray-50", "aria-expanded": open, children: [jsx(ChevronDown, { className: cn('h-4 w-4 transition-transform', open && 'rotate-180'), "aria-hidden": "true" }), open
260133
260301
  ? t('analyticsTranscriptTurnDetails.hideDetails')
260134
- : t('analyticsTranscriptTurnDetails.showDetails')] }) }), documents.length > 0 && (jsx("div", { "data-testid": "transcript-turn-sources", className: "contents", children: (renderDocuments !== null && renderDocuments !== void 0 ? renderDocuments : defaultRenderDocuments)(documents) })), jsx(TranscriptRollupBadges, { documentsCount: 0, toolCallsCount: toolCalls.length })] }), jsxs(CollapsibleContent, { className: "mt-2 space-y-5 rounded-lg border border-gray-200 bg-white p-3", children: [toolCalls.length > 0 && (jsx("div", { "data-testid": "transcript-turn-tools", children: (renderToolCalls !== null && renderToolCalls !== void 0 ? renderToolCalls : defaultRenderToolCalls)(toolCalls) })), metadata && jsx(MetadataSection, { metadata: metadata, renderModel: renderModel }), requestContext && jsx(RequestContextSection, { requestContext: requestContext })] })] }) }));
260302
+ : t('analyticsTranscriptTurnDetails.showDetails')] }) }), documents.length > 0 && (jsx("div", { "data-testid": "transcript-turn-sources", className: "contents", children: (renderDocuments !== null && renderDocuments !== void 0 ? renderDocuments : defaultRenderDocuments)(documents) })), jsx(TranscriptRollupBadges, { documentsCount: 0, toolCallsCount: toolCalls.length })] }), jsxs(CollapsibleContent, { className: "mt-2 space-y-5 rounded-lg border border-gray-200 bg-white p-3", children: [toolCalls.length > 0 && (jsx("div", { "data-testid": "transcript-turn-tools", children: (renderToolCalls !== null && renderToolCalls !== void 0 ? renderToolCalls : defaultRenderToolCalls)(toolCalls) })), metadata && (jsx(MetadataSection, { metadata: metadata, renderModel: renderModel, tenantKey: tenantKey, mentorId: mentorId })), requestContext && jsx(RequestContextSection, { requestContext: requestContext })] })] }) }));
260135
260303
  }
260136
260304
  /**
260137
260305
  * "Documents · N" / "Tools · N" chips for a conversation row, from the
@@ -260147,118 +260315,6 @@ function TranscriptRollupBadges({ documentsCount, toolCallsCount, className, })
260147
260315
  return (jsxs("span", { className: cn('inline-flex flex-wrap items-center gap-1.5', className), "data-testid": "transcript-rollup-badges", children: [documents > 0 && (jsxs(Badge, { variant: "outline", className: "gap-1 border-transparent bg-blue-50 font-normal text-blue-700", "data-testid": "transcript-sources-badge", children: [jsx(FileText, { className: "h-3 w-3", "aria-hidden": "true" }), t('analyticsTranscriptTurnDetails.sourcesBadge', { count: documents })] })), tools > 0 && (jsxs(Badge, { variant: "outline", className: "gap-1 border-transparent bg-amber-50 font-normal text-amber-800", "data-testid": "transcript-tools-badge", children: [jsx(Wrench, { className: "h-3 w-3", "aria-hidden": "true" }), t('analyticsTranscriptTurnDetails.toolsBadge', { count: tools })] }))] }));
260148
260316
  }
260149
260317
 
260150
- /**
260151
- * One place that decides how a user is shown.
260152
- *
260153
- * Backend payloads name their user in a dozen ways — `email` / `lti_email` /
260154
- * `user_email`, `username` / `student` / `lti_username`, `name` /
260155
- * `full_name` / `user_full_name`, or a nested `user` object — and most of
260156
- * them can be empty, `null`, or a placeholder like "None". Every surface used
260157
- * to pick through those on its own. `resolveUserIdentity()` takes whatever
260158
- * the payload is and returns what to show, what to open a profile on, and
260159
- * the avatar initial, so the rule lives here and can be tweaked once.
260160
- *
260161
- * Default rule: email → full name → username → the anonymous label.
260162
- */
260163
- const DEFAULT_USER_IDENTITY_ORDER = ['email', 'name', 'username'];
260164
- const DEFAULT_ANONYMOUS_LABEL = 'Anonymous';
260165
- /** Values backends emit instead of "nothing". Compared case-insensitively. */
260166
- const PLACEHOLDERS = new Set([
260167
- '',
260168
- 'none',
260169
- 'null',
260170
- 'undefined',
260171
- 'n/a',
260172
- 'na',
260173
- '-',
260174
- 'anonymous',
260175
- 'anonymoususer',
260176
- ]);
260177
- /**
260178
- * `local@domain.tld` with no whitespace, checked with a single pass rather than
260179
- * a backtracking pattern (CodeQL flags `[^@]+@[^@]+\.[^@]+` as polynomial on
260180
- * adversarial input).
260181
- */
260182
- const isEmailLike = (value) => {
260183
- const text = value.trim();
260184
- if (!text || /\s/.test(text))
260185
- return false;
260186
- const at = text.indexOf('@');
260187
- if (at <= 0 || at !== text.lastIndexOf('@') || at === text.length - 1)
260188
- return false;
260189
- const domain = text.slice(at + 1);
260190
- const dot = domain.indexOf('.');
260191
- // A dot somewhere inside the domain, with something after the last one.
260192
- return dot > 0 && !domain.endsWith('.');
260193
- };
260194
- /** Trimmed string, or undefined for blanks, non-strings and placeholders. */
260195
- const clean = (value) => {
260196
- if (typeof value !== 'string')
260197
- return undefined;
260198
- const trimmed = value.trim();
260199
- return PLACEHOLDERS.has(trimmed.toLowerCase()) ? undefined : trimmed;
260200
- };
260201
- const firstOf = (...candidates) => {
260202
- for (const candidate of candidates) {
260203
- const value = clean(candidate);
260204
- if (value)
260205
- return value;
260206
- }
260207
- return undefined;
260208
- };
260209
- const nested = (source) => source.user && typeof source.user === 'object' ? source.user : undefined;
260210
- /**
260211
- * Resolve how a user should be displayed from any payload that names one.
260212
- *
260213
- * - `email`: `lti_email`, `email`, `user_email`, the nested `user`'s email —
260214
- * or a username field that turns out to hold an email address.
260215
- * - `username`: `lti_username`, `username`, `student`, `user_username`, the
260216
- * nested `user`'s username, or a bare string/number `user` value.
260217
- * - `name`: `user_full_name`, `full_name`, `name`, `first_name last_name`, or
260218
- * the nested `user`'s name.
260219
- * - `profileUsername`: the first *platform* username (never an LTI claim).
260220
- *
260221
- * A string source is treated as an email when it looks like one, else as a
260222
- * username. Placeholder strings ("None", "-", "anonymous"…) count as absent.
260223
- */
260224
- function resolveUserIdentity(source, options = {}) {
260225
- var _a;
260226
- const anonymousLabel = options.anonymousLabel || DEFAULT_ANONYMOUS_LABEL;
260227
- const order = ((_a = options.order) === null || _a === void 0 ? void 0 : _a.length) ? options.order : DEFAULT_USER_IDENTITY_ORDER;
260228
- const record = source && typeof source === 'object' ? source : { user: source !== null && source !== void 0 ? source : null };
260229
- const inner = nested(record);
260230
- // A bare `user` value is an id or username; numeric ids are still shown.
260231
- const bareUser = typeof record.user === 'number' && Number.isFinite(record.user)
260232
- ? String(record.user)
260233
- : typeof record.user === 'string'
260234
- ? clean(record.user)
260235
- : undefined;
260236
- let email = firstOf(record.lti_email, record.email, record.user_email, inner === null || inner === void 0 ? void 0 : inner.lti_email, inner === null || inner === void 0 ? void 0 : inner.email, inner === null || inner === void 0 ? void 0 : inner.user_email);
260237
- const platformUsername = firstOf(record.username, record.student, record.user_username, inner === null || inner === void 0 ? void 0 : inner.username, inner === null || inner === void 0 ? void 0 : inner.student, inner === null || inner === void 0 ? void 0 : inner.user_username, bareUser && !isEmailLike(bareUser) ? bareUser : undefined);
260238
- let username = firstOf(record.lti_username, inner === null || inner === void 0 ? void 0 : inner.lti_username, platformUsername);
260239
- const composed = [clean(record.first_name), clean(record.last_name)].filter(Boolean).join(' ');
260240
- const name = firstOf(record.user_full_name, record.full_name, record.name, composed, inner === null || inner === void 0 ? void 0 : inner.user_full_name, inner === null || inner === void 0 ? void 0 : inner.full_name, inner === null || inner === void 0 ? void 0 : inner.name);
260241
- // A bare string that looks like an email is an email, and so is a
260242
- // "username" field that actually carries one.
260243
- if (!email && bareUser && isEmailLike(bareUser))
260244
- email = bareUser;
260245
- if (!email && username && isEmailLike(username)) {
260246
- email = username;
260247
- username = undefined;
260248
- }
260249
- const facts = { email, username, name };
260250
- const label = order.map((field) => facts[field]).find(Boolean);
260251
- return {
260252
- label: label !== null && label !== void 0 ? label : anonymousLabel,
260253
- email,
260254
- username,
260255
- name,
260256
- profileUsername: platformUsername,
260257
- initial: (label !== null && label !== void 0 ? label : anonymousLabel).charAt(0).toUpperCase() || 'A',
260258
- isAnonymous: !label,
260259
- };
260260
- }
260261
-
260262
260318
  const AnalyticsSettingsContext = createContext({});
260263
260319
  AnalyticsSettingsContext.Provider;
260264
260320
 
@@ -279113,7 +279169,7 @@ function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport,
279113
279169
  // Use mentor summaries data for rating and tags
279114
279170
  const averageRating = (mentorSummaries === null || mentorSummaries === void 0 ? void 0 : mentorSummaries.rating) || 0;
279115
279171
  const topicTags = (mentorSummaries === null || mentorSummaries === void 0 ? void 0 : mentorSummaries.tags) || [];
279116
- // Who the previewed conversation belongs to (email → full name → username → Anonymous).
279172
+ // Who the previewed conversation belongs to (the shared identity rule).
279117
279173
  const identityOptions = { anonymousLabel: labels.conversation.anonymous };
279118
279174
  const selectedOwner = resolveUserIdentity(selectedConversation, identityOptions);
279119
279175
  const previewOwner = resolveUserIdentity(previewConversationContent, identityOptions);
@@ -279175,11 +279231,11 @@ function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport,
279175
279231
  jsx(RetrievedDocumentsButton, { documents: documents, label: labels.conversation.documentsChip(rollup.documentsCount), className: "h-6 gap-1 rounded-md border-transparent bg-blue-50 px-2 text-xs font-normal text-blue-700 hover:bg-blue-100 [&_svg]:h-3 [&_svg]:w-3" })), jsx(TranscriptRollupBadges, { documentsCount: 0, toolCallsCount: rollup.toolCallsCount })] }))] }), jsx("div", { className: "flex items-center gap-2 text-sm text-gray-600", children: jsx(ChevronRight, { className: "h-4 w-4" }) })] }, conversation.id));
279176
279232
  }) }), totalPages > 1 && PaginationComponent && (jsx("div", { className: "my-2", children: jsx(PaginationComponent, { currentPage: currentPage, totalPages: totalPages, onPageChange: handlePageChange, disabled: isChatHistoryFetching || isChatHistoryLoading, disableNumberedButtons: isMobile }) }))] }), jsx("div", { className: "scrollbar-hide hidden max-h-[500px] flex-col overflow-y-auto rounded-lg border bg-white p-6 shadow-sm md:flex", tabIndex: 0, role: "region", "aria-label": t('tabsHistoryTab.conversationPreviewLabel'), children: selectedConversation && previewConversationContent ? (jsxs(Fragment$1, { children: [jsxs("div", { className: "mb-4", children: [jsx("h3", { className: "mb-1 text-base font-semibold text-gray-700", children: ((_m = selectedConversation.messages[0]) === null || _m === void 0 ? void 0 : _m.human) || labels.conversation.title }), jsx("span", { className: "text-sm text-gray-500", children: format$1(new Date(selectedConversation.inserted_at), "MMM dd, yyyy 'at' h:mm a") }), jsx(RetrievedDocumentsButton, { sessionId: selectedConversation.id, documents: conversationDocuments(selectedConversation.messages).length > 0
279177
279233
  ? conversationDocuments(selectedConversation.messages)
279178
- : undefined, className: "mt-2" })] }), jsxs("div", { className: "space-y-6", children: [selectedConversation.messages.map((message, index) => (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: selectedOwner.initial }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: jsx(UserProfileLink, { tenantKey: tenantKey, username: selectedOwner.profileUsername, display: selectedOwner.label, currentSPA: currentSPA }) }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-500", children: message.human }), jsx(TranscriptFileCards, { files: attachmentCards(message.human_files) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-500", children: renderAiContent(message.ai) }), jsx(TranscriptFileCards, { files: attachmentCards(message.ai_files) }), jsx(TranscriptTurnDetails, { turn: message })] })] })] }, index))), (selectedConversation === null || selectedConversation === void 0 ? void 0 : selectedConversation.memory_tracked) && (jsxs("div", { className: "border-t border-gray-200 pt-4", children: [jsxs(Button$1, { variant: "outline", onClick: () => setShowConversationMemory(!showConversationMemory), className: "flex w-full items-center justify-center gap-2 border-gray-200 text-gray-700 hover:bg-gray-50", children: [jsx(Brain, { className: "h-4 w-4" }), showConversationMemory ? labels.memory.hide : labels.memory.show] }), showConversationMemory && (jsx("div", { className: "mt-4 space-y-6", children: isConversationMemoryLoading ? (jsx("div", { className: "flex items-center justify-center py-4", children: jsx(Spinner, {}) })) : ((_o = conversationMemory === null || conversationMemory === void 0 ? void 0 : conversationMemory.entries) === null || _o === void 0 ? void 0 : _o.length) ? (jsx(Fragment$1, { children: conversationMemory.entries.map((entry, index) => (jsxs("div", { children: [jsx("h4", { className: "mb-3 font-semibold text-gray-900", children: entry.key }), jsx("ul", { className: "space-y-2", children: jsx("li", { className: "flex items-start gap-2 text-sm text-gray-600", children: entry.value }) })] }, index))) })) : (jsx("div", { className: "text-sm text-gray-500", children: labels.memory.notAvailable })) }))] }))] })] })) : (jsx("div", { className: "flex h-full items-center justify-center text-gray-500", children: labels.conversation.selectPrompt })) })] }))] }) }), jsx(Dialog, { open: isConversationPreviewModalOpen, onOpenChange: setIsConversationPreviewModalOpen, children: jsxs(DialogContent, { className: "scrollbar-hide mx-auto max-h-[80vh] max-w-[90vw] overflow-x-hidden overflow-y-auto", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "text-lg font-semibold text-gray-900", children: previewConversationContent
279234
+ : undefined, className: "mt-2" })] }), jsxs("div", { className: "space-y-6", children: [selectedConversation.messages.map((message, index) => (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: selectedOwner.initial }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: jsx(UserProfileLink, { tenantKey: tenantKey, username: selectedOwner.profileUsername, display: selectedOwner.label, currentSPA: currentSPA }) }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-500", children: message.human }), jsx(TranscriptFileCards, { files: attachmentCards(message.human_files) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-500", children: renderAiContent(message.ai) }), jsx(TranscriptFileCards, { files: attachmentCards(message.ai_files) }), jsx(TranscriptTurnDetails, { turn: message, tenantKey: tenantKey, mentorId: mentorId })] })] })] }, index))), (selectedConversation === null || selectedConversation === void 0 ? void 0 : selectedConversation.memory_tracked) && (jsxs("div", { className: "border-t border-gray-200 pt-4", children: [jsxs(Button$1, { variant: "outline", onClick: () => setShowConversationMemory(!showConversationMemory), className: "flex w-full items-center justify-center gap-2 border-gray-200 text-gray-700 hover:bg-gray-50", children: [jsx(Brain, { className: "h-4 w-4" }), showConversationMemory ? labels.memory.hide : labels.memory.show] }), showConversationMemory && (jsx("div", { className: "mt-4 space-y-6", children: isConversationMemoryLoading ? (jsx("div", { className: "flex items-center justify-center py-4", children: jsx(Spinner, {}) })) : ((_o = conversationMemory === null || conversationMemory === void 0 ? void 0 : conversationMemory.entries) === null || _o === void 0 ? void 0 : _o.length) ? (jsx(Fragment$1, { children: conversationMemory.entries.map((entry, index) => (jsxs("div", { children: [jsx("h4", { className: "mb-3 font-semibold text-gray-900", children: entry.key }), jsx("ul", { className: "space-y-2", children: jsx("li", { className: "flex items-start gap-2 text-sm text-gray-600", children: entry.value }) })] }, index))) })) : (jsx("div", { className: "text-sm text-gray-500", children: labels.memory.notAvailable })) }))] }))] })] })) : (jsx("div", { className: "flex h-full items-center justify-center text-gray-500", children: labels.conversation.selectPrompt })) })] }))] }) }), jsx(Dialog, { open: isConversationPreviewModalOpen, onOpenChange: setIsConversationPreviewModalOpen, children: jsxs(DialogContent, { className: "scrollbar-hide mx-auto max-h-[80vh] max-w-[90vw] overflow-x-hidden overflow-y-auto", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "text-lg font-semibold text-gray-900", children: previewConversationContent
279179
279235
  ? ((_p = previewConversationContent.messages[0]) === null || _p === void 0 ? void 0 : _p.human) || labels.conversation.title
279180
279236
  : labels.conversation.title }) }), jsx("div", { className: "mx-auto max-w-full overflow-x-hidden", children: previewConversationContent && (jsxs("div", { className: "scrollbar-hide space-y-4 overflow-y-auto", children: [jsx("div", { className: "text-sm text-gray-500", children: format$1(new Date(previewConversationContent.inserted_at), "MMM dd, yyyy 'at' h:mm a") }), jsx(RetrievedDocumentsButton, { sessionId: previewConversationContent.id, documents: conversationDocuments(previewConversationContent.messages).length > 0
279181
279237
  ? conversationDocuments(previewConversationContent.messages)
279182
- : undefined }), previewConversationContent.messages.map((message, index) => (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: previewOwner.initial }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "truncate font-medium text-gray-900", children: jsx(UserProfileLink, { tenantKey: tenantKey, username: previewOwner.profileUsername, display: previewOwner.label, currentSPA: currentSPA }) }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-900", children: message.human }), jsx(TranscriptFileCards, { files: attachmentCards(message.human_files) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "font-medium text-gray-900", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-900", children: renderAiContent(message.ai) }), jsx(TranscriptFileCards, { files: attachmentCards(message.ai_files) }), jsx(TranscriptTurnDetails, { turn: message })] })] })] }, index)))] })) })] }) })] }));
279238
+ : undefined }), previewConversationContent.messages.map((message, index) => (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: previewOwner.initial }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "truncate font-medium text-gray-900", children: jsx(UserProfileLink, { tenantKey: tenantKey, username: previewOwner.profileUsername, display: previewOwner.label, currentSPA: currentSPA }) }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-900", children: message.human }), jsx(TranscriptFileCards, { files: attachmentCards(message.human_files) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "font-medium text-gray-900", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-900", children: renderAiContent(message.ai) }), jsx(TranscriptFileCards, { files: attachmentCards(message.ai_files) }), jsx(TranscriptTurnDetails, { turn: message, tenantKey: tenantKey, mentorId: mentorId })] })] })] }, index)))] })) })] }) })] }));
279183
279239
  }
279184
279240
 
279185
279241
  const ANONYMOUS_USERNAME = 'anonymous';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iblai/iblai-js",
3
- "version": "2.18.0",
3
+ "version": "2.19.0",
4
4
  "description": "Unified JavaScript SDK for IBL.ai — re-exports data-layer, web-containers, and web-utils under a single package",
5
5
  "type": "module",
6
6
  "engines": {
@@ -76,10 +76,10 @@
76
76
  "axios": "1.13.6",
77
77
  "dotenv": "16.6.1",
78
78
  "winston": "3.19.0",
79
- "@iblai/data-layer": "1.19.0",
80
- "@iblai/mcp": "1.16.0",
81
- "@iblai/web-containers": "1.25.0",
82
- "@iblai/web-utils": "2.4.8"
79
+ "@iblai/mcp": "1.17.0",
80
+ "@iblai/web-containers": "1.26.0",
81
+ "@iblai/data-layer": "1.19.1",
82
+ "@iblai/web-utils": "2.5.0"
83
83
  },
84
84
  "peerDependencies": {
85
85
  "@iblai/iblai-api": "4.166.0-ai",