@athenaintel/react 0.40.3 → 0.41.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.
Files changed (60) hide show
  1. package/README.md +55 -0
  2. package/dist/chat/athena-citation-links.cjs +23 -2
  3. package/dist/chat/athena-citation-links.cjs.map +1 -1
  4. package/dist/chat/athena-citation-links.d.ts +5 -0
  5. package/dist/chat/athena-citation-links.js +23 -2
  6. package/dist/chat/athena-citation-links.js.map +1 -1
  7. package/dist/chat-ui/index.d.ts +1 -1
  8. package/dist/chat-ui/mentions/mention-chip.cjs +118 -7
  9. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  10. package/dist/chat-ui/mentions/mention-chip.d.ts +29 -0
  11. package/dist/chat-ui/mentions/mention-chip.js +120 -9
  12. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  13. package/dist/chat-ui/rich-text/chat-message-body.cjs +2 -1
  14. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  15. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  16. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  17. package/dist/react/package.json.cjs +1 -1
  18. package/dist/react/package.json.js +1 -1
  19. package/dist/references/src/anchors/file-line-range.cjs +70 -0
  20. package/dist/references/src/anchors/file-line-range.cjs.map +1 -0
  21. package/dist/references/src/anchors/file-line-range.js +70 -0
  22. package/dist/references/src/anchors/file-line-range.js.map +1 -0
  23. package/dist/references/src/anchors/format.cjs +219 -0
  24. package/dist/references/src/anchors/format.cjs.map +1 -0
  25. package/dist/references/src/anchors/format.js +219 -0
  26. package/dist/references/src/anchors/format.js.map +1 -0
  27. package/dist/references/src/anchors/parse.cjs +408 -0
  28. package/dist/references/src/anchors/parse.cjs.map +1 -0
  29. package/dist/references/src/anchors/parse.js +408 -0
  30. package/dist/references/src/anchors/parse.js.map +1 -0
  31. package/dist/references/src/anchors/serialize.cjs +185 -0
  32. package/dist/references/src/anchors/serialize.cjs.map +1 -0
  33. package/dist/references/src/anchors/serialize.js +185 -0
  34. package/dist/references/src/anchors/serialize.js.map +1 -0
  35. package/dist/references/src/anchors/types.cjs +50 -0
  36. package/dist/references/src/anchors/types.cjs.map +1 -0
  37. package/dist/references/src/anchors/types.js +50 -0
  38. package/dist/references/src/anchors/types.js.map +1 -0
  39. package/dist/references/src/formats/uri/asset-mention-uri.cjs +29 -0
  40. package/dist/references/src/formats/uri/asset-mention-uri.cjs.map +1 -0
  41. package/dist/references/src/formats/uri/asset-mention-uri.js +29 -0
  42. package/dist/references/src/formats/uri/asset-mention-uri.js.map +1 -0
  43. package/dist/references/src/formats/uri/asset-spaces-url.cjs +64 -0
  44. package/dist/references/src/formats/uri/asset-spaces-url.cjs.map +1 -0
  45. package/dist/references/src/formats/uri/asset-spaces-url.js +64 -0
  46. package/dist/references/src/formats/uri/asset-spaces-url.js.map +1 -0
  47. package/dist/references/src/formats/uri/template-placeholder.cjs +9 -0
  48. package/dist/references/src/formats/uri/template-placeholder.cjs.map +1 -0
  49. package/dist/references/src/formats/uri/template-placeholder.js +9 -0
  50. package/dist/references/src/formats/uri/template-placeholder.js.map +1 -0
  51. package/dist/references/src/sheet-name.cjs +10 -0
  52. package/dist/references/src/sheet-name.cjs.map +1 -0
  53. package/dist/references/src/sheet-name.js +10 -0
  54. package/dist/references/src/sheet-name.js.map +1 -0
  55. package/dist/references/src/version-ref.cjs +61 -0
  56. package/dist/references/src/version-ref.cjs.map +1 -0
  57. package/dist/references/src/version-ref.js +61 -0
  58. package/dist/references/src/version-ref.js.map +1 -0
  59. package/dist/styles.css +1 -1
  60. package/package.json +3 -2
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
+ const format = require("../../references/src/anchors/format.cjs");
6
+ const assetMentionUri = require("../../references/src/formats/uri/asset-mention-uri.cjs");
7
+ const assetSpacesUrl = require("../../references/src/formats/uri/asset-spaces-url.cjs");
5
8
  const lucideReact = require("lucide-react");
6
9
  const assetUrls = require("../lib/asset-urls.cjs");
7
10
  const apiClient = require("./suggestions/data/api-client.cjs");
@@ -17,6 +20,79 @@ function getSpacesAssetType(url) {
17
20
  const assetType = url.searchParams.get("assetType") || url.searchParams.get("asset_type") || url.searchParams.get("type");
18
21
  return assetType === "sheet" ? "spreadsheet" : assetType;
19
22
  }
23
+ function citationOf(reference) {
24
+ const { anchor, meta } = reference;
25
+ if (!anchor) return void 0;
26
+ const label = format.formatReferenceAnchor(anchor, "short", meta);
27
+ if (!label) return void 0;
28
+ const { excerpt: _excerpt, ...place } = meta ?? {};
29
+ return {
30
+ anchorType: anchor.type,
31
+ label,
32
+ description: format.formatReferenceAnchor(anchor, "long", place) ?? label
33
+ };
34
+ }
35
+ const ASSET_CHIP_ICONS = {
36
+ document: lucideReact.FileTextIcon,
37
+ word_document: lucideReact.FileTextIcon,
38
+ markdown: lucideReact.FileTextIcon,
39
+ txt: lucideReact.FileTextIcon,
40
+ pdf: lucideReact.FileTextIcon,
41
+ spreadsheet: lucideReact.SheetIcon,
42
+ table: lucideReact.SheetIcon,
43
+ presentation: lucideReact.PresentationIcon,
44
+ powerpoint_deck: lucideReact.PresentationIcon,
45
+ presentation_pptx_studio: lucideReact.PresentationIcon,
46
+ pptx: lucideReact.PresentationIcon,
47
+ image: lucideReact.ImageIcon,
48
+ figure: lucideReact.ImageIcon,
49
+ canvas: lucideReact.ImageIcon,
50
+ video: lucideReact.VideoIcon,
51
+ meeting: lucideReact.VideoIcon,
52
+ video_session: lucideReact.VideoIcon,
53
+ audio: lucideReact.HeadphonesIcon,
54
+ voice_memo: lucideReact.HeadphonesIcon,
55
+ phone_call: lucideReact.HeadphonesIcon,
56
+ email: lucideReact.MailIcon,
57
+ email_message: lucideReact.MailIcon,
58
+ live_email_thread: lucideReact.MailIcon,
59
+ live_calendar_event: lucideReact.MailIcon,
60
+ python: lucideReact.FileCodeIcon,
61
+ javascript: lucideReact.FileCodeIcon,
62
+ typescript: lucideReact.FileCodeIcon,
63
+ json: lucideReact.FileCodeIcon,
64
+ yaml: lucideReact.FileCodeIcon,
65
+ database: lucideReact.DatabaseIcon,
66
+ catalog: lucideReact.DatabaseIcon,
67
+ drive: lucideReact.DatabaseIcon,
68
+ dataset: lucideReact.DatabaseIcon,
69
+ semantic_model: lucideReact.DatabaseIcon,
70
+ query_snippet: lucideReact.DatabaseIcon,
71
+ folder: lucideReact.FolderIcon,
72
+ gdrive_folder: lucideReact.FolderIcon,
73
+ project: lucideReact.FolderIcon,
74
+ collection: lucideReact.FolderIcon,
75
+ session: lucideReact.MessageSquareIcon,
76
+ computer: lucideReact.MonitorIcon,
77
+ desktop_session: lucideReact.MonitorIcon,
78
+ user_interface: lucideReact.MonitorIcon,
79
+ application: lucideReact.MonitorIcon,
80
+ html: lucideReact.Globe2Icon,
81
+ web_page: lucideReact.Globe2Icon,
82
+ browse_session: lucideReact.Globe2Icon
83
+ };
84
+ function assetChipIcon(assetType, citation) {
85
+ if ((citation == null ? void 0 : citation.anchorType) === "file_line_range") return lucideReact.FileCodeIcon;
86
+ return assetType && ASSET_CHIP_ICONS[assetType.toLowerCase()] || lucideReact.FileIcon;
87
+ }
88
+ const SPEAKABLE = /[\p{L}\p{N}]/u;
89
+ function decodeAssetLink(url, decode) {
90
+ try {
91
+ return decode(url);
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
20
96
  function personEmailForMention(parsed) {
21
97
  if (parsed.type !== "person") return void 0;
22
98
  return parsed.email || (parsed.id.includes("@") ? parsed.id : void 0);
@@ -32,8 +108,10 @@ const MentionLink = ({ href, children, interactive = true }) => {
32
108
  type: parsed.type,
33
109
  personEmail: personEmailForMention(parsed),
34
110
  assetIds: parsed.assetIds,
111
+ assetType: parsed.assetType,
112
+ citation: parsed.citation,
35
113
  thumbnailUrl: parsed.thumbnailUrl,
36
- sourceHref: isSpacesMentionUrl(href) ? href : void 0,
114
+ sourceHref: isSpacesMentionUrl(href) ? href : parsed.sourceHref,
37
115
  interactive
38
116
  }
39
117
  );
@@ -75,6 +153,8 @@ const MentionChip = ({
75
153
  name,
76
154
  type,
77
155
  assetIds,
156
+ assetType,
157
+ citation,
78
158
  personEmail,
79
159
  thumbnailUrl,
80
160
  sourceHref,
@@ -84,6 +164,8 @@ const MentionChip = ({
84
164
  const memberAvatarUrl = useMemberAvatar.useMemberAvatarUrl(personEmail);
85
165
  const [erroredAvatarUrl, setErroredAvatarUrl] = react.useState();
86
166
  const excerpt = sourceHref ? readSourceExcerpt(sourceHref) : null;
167
+ const tooltip = excerpt ?? (citation == null ? void 0 : citation.description) ?? null;
168
+ const anchorLabel = citation && name !== citation.description && name !== citation.label ? citation.label : null;
87
169
  const assetRootHref = type === "asset" && (assetIds == null ? void 0 : assetIds.length) ? assetUrls.createOlympusAssetUrl({
88
170
  appUrl: auth == null ? void 0 : auth.appUrl,
89
171
  backendUrl: auth == null ? void 0 : auth.backendUrl,
@@ -105,8 +187,11 @@ const MentionChip = ({
105
187
  })();
106
188
  const className = cnfast.cn(
107
189
  "not-prose inline-flex max-w-[220px] items-center gap-1 whitespace-nowrap rounded-md border border-slate-300 bg-white px-1.5 py-px align-middle text-[0.85em] font-medium leading-[1.4] text-slate-600 no-underline",
190
+ // The cited place gets its own room after a name already at its cap.
191
+ anchorLabel && "max-w-[340px]",
108
192
  href && "cursor-pointer transition-colors hover:border-slate-400 hover:bg-slate-50 hover:text-slate-800 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
109
193
  );
194
+ const AssetIcon = assetChipIcon(assetType, citation);
110
195
  const contents = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
111
196
  thumbnailUrl && /* @__PURE__ */ jsxRuntime.jsx(
112
197
  "img",
@@ -117,6 +202,9 @@ const MentionChip = ({
117
202
  draggable: false
118
203
  }
119
204
  ),
205
+ !thumbnailUrl && (type === "asset" || type === "folder") && // A host that bundles no asset-type SVGs (computer apps, most SDK
206
+ // apps) still gets the type's icon.
207
+ /* @__PURE__ */ jsxRuntime.jsx(AssetIcon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }),
120
208
  !thumbnailUrl && type === "webpage" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe2Icon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }),
121
209
  !thumbnailUrl && type === "person" && (memberAvatarUrl && erroredAvatarUrl !== memberAvatarUrl ? /* @__PURE__ */ jsxRuntime.jsx(
122
210
  "img",
@@ -128,7 +216,18 @@ const MentionChip = ({
128
216
  onError: () => setErroredAvatarUrl(memberAvatarUrl)
129
217
  }
130
218
  ) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserIcon, { className: "size-3.5 shrink-0", "aria-hidden": "true" })),
131
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-[180px] overflow-hidden text-ellipsis", children: name })
219
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-[180px] overflow-hidden text-ellipsis", children: name }),
220
+ citation && anchorLabel && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
221
+ " ",
222
+ /* @__PURE__ */ jsxRuntime.jsx(
223
+ "span",
224
+ {
225
+ className: "max-w-[140px] shrink-0 overflow-hidden text-ellipsis font-normal text-slate-400",
226
+ "data-citation-anchor": citation.anchorType,
227
+ children: anchorLabel
228
+ }
229
+ )
230
+ ] })
132
231
  ] });
133
232
  if (href && interactive) {
134
233
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -138,9 +237,10 @@ const MentionChip = ({
138
237
  target: "_blank",
139
238
  rel: "noopener noreferrer",
140
239
  className,
141
- "aria-label": `Open ${name} in Spaces`,
142
- title: excerpt ?? `Open ${name} in Spaces`,
240
+ "aria-label": anchorLabel && citation && SPEAKABLE.test(citation.description) ? `Open ${name} at ${citation.description} in Spaces` : `Open ${name} in Spaces`,
241
+ title: tooltip ?? `Open ${name} in Spaces`,
143
242
  "data-excerpt": excerpt ?? void 0,
243
+ "data-mention-name": name,
144
244
  children: contents
145
245
  }
146
246
  );
@@ -149,7 +249,7 @@ const MentionChip = ({
149
249
  "span",
150
250
  {
151
251
  className,
152
- title: excerpt ?? void 0,
252
+ title: tooltip ?? void 0,
153
253
  "data-excerpt": excerpt ?? void 0,
154
254
  children: contents
155
255
  }
@@ -172,17 +272,22 @@ function citationFallbackLabel(params) {
172
272
  return "Asset";
173
273
  }
174
274
  function parseMentionUrl(url) {
275
+ var _a, _b;
175
276
  try {
176
277
  if (isSpacesMentionUrl(url)) {
177
278
  const parsed = new URL(url, "https://app.athenaintel.com");
178
279
  const assetIds = (parsed.searchParams.get("asset_ids") ?? "").split(",").map((id3) => id3.trim()).filter(Boolean);
179
280
  const id2 = assetIds[0] ?? "";
180
281
  const assetType = getSpacesAssetType(parsed);
282
+ const reference2 = assetIds.length === 1 ? decodeAssetLink(parsed, assetSpacesUrl.decodeFromAssetSpacesUrl) : null;
283
+ const citation = reference2 ? citationOf(reference2) : void 0;
181
284
  return {
182
285
  type: "asset",
183
- name: assetIds.length > 1 ? `${assetIds.length} assets` : citationFallbackLabel(parsed.searchParams),
286
+ name: assetIds.length > 1 ? `${assetIds.length} assets` : (citation == null ? void 0 : citation.description) ?? citationFallbackLabel(parsed.searchParams),
184
287
  id: id2,
185
288
  assetIds,
289
+ assetType: assetType ?? void 0,
290
+ citation,
186
291
  thumbnailUrl: assetTypeIcons.assetTypeToThumb(assetType)
187
292
  };
188
293
  }
@@ -200,12 +305,16 @@ function parseMentionUrl(url) {
200
305
  thumbnailUrl = assetTypeIcons.assetTypeToThumb(type === "folder" ? "folder" : null);
201
306
  }
202
307
  }
308
+ const reference = type === "asset" ? decodeAssetLink(u, assetMentionUri.decodeFromAssetMentionUri) : null;
203
309
  return {
204
310
  type,
205
311
  name,
206
312
  id,
207
313
  email: type === "person" ? u.searchParams.get("email") || void 0 : void 0,
208
314
  assetIds: type === "asset" && id ? [id] : void 0,
315
+ assetType: type === "asset" || type === "folder" ? u.searchParams.get("assetType") || ((_a = reference == null ? void 0 : reference.meta) == null ? void 0 : _a.assetType) || (type === "folder" ? "folder" : void 0) : void 0,
316
+ citation: reference ? citationOf(reference) : void 0,
317
+ sourceHref: reference && (reference.anchor || ((_b = reference.meta) == null ? void 0 : _b.excerpt)) ? assetSpacesUrl.encodeToAssetSpacesUrl(reference) : void 0,
209
318
  thumbnailUrl
210
319
  };
211
320
  } catch {
@@ -271,8 +380,10 @@ function parseMentionsInText(text) {
271
380
  type: parsed.type,
272
381
  personEmail: personEmailForMention(parsed),
273
382
  assetIds: parsed.assetIds,
383
+ assetType: parsed.assetType,
384
+ citation: parsed.citation,
274
385
  thumbnailUrl: parsed.thumbnailUrl,
275
- sourceHref: isSpacesMentionUrl(url) ? url : void 0
386
+ sourceHref: isSpacesMentionUrl(url) ? url : parsed.sourceHref
276
387
  },
277
388
  `mention-${match.index}`
278
389
  )
@@ -1 +1 @@
1
- {"version":3,"file":"mention-chip.cjs","sources":["../../../src/chat-ui/mentions/mention-chip.tsx"],"sourcesContent":["/**\n * Mention chip rendering for message display.\n *\n * Parses [@name](mention://type?...) markdown links and renders them\n * as styled inline chips matching the Olympus mention style.\n * Used in both user messages (inline text) and assistant messages (markdown links).\n */\n\nimport {\n useContext,\n useState,\n type FC,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport {\n formatScreenPoint,\n isScreenPointHref,\n parseScreenPointHref,\n} from \"@athenaintel/agent-runtime\";\nimport { CrosshairIcon, Globe2Icon, UserIcon } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\nimport { createOlympusAssetUrl } from \"../lib/asset-urls\";\nimport { MentionAuthCtx } from \"./suggestions/data/api-client\";\nimport { assetTypeToThumb } from \"./suggestions/data/sources/asset-type-icons\";\nimport { useMemberAvatarUrl } from \"./use-member-avatar\";\n\nconst SPACES_MENTION_PATH = /\\/dashboard\\/spaces\\/?\\?/;\n\nfunction isSpacesMentionUrl(url: string): boolean {\n return SPACES_MENTION_PATH.test(url);\n}\n\nfunction getSpacesAssetType(url: URL): string | null {\n const assetType =\n url.searchParams.get(\"assetType\") ||\n url.searchParams.get(\"asset_type\") ||\n url.searchParams.get(\"type\");\n return assetType === \"sheet\" ? \"spreadsheet\" : assetType;\n}\n\n// ── Mention link component for markdown renderer ──\n\n/**\n * Custom <a> renderer for MarkdownTextPrimitive.\n * Intercepts mention:// links and renders them as chips.\n * Falls through to a normal link for non-mention URLs.\n */\nfunction personEmailForMention(\n parsed: ReturnType<typeof parseMentionUrl>,\n): string | undefined {\n if (parsed.type !== \"person\") return undefined;\n return parsed.email || (parsed.id.includes(\"@\") ? parsed.id : undefined);\n}\n\nexport const MentionLink: FC<{\n href?: string;\n children?: ReactNode;\n interactive?: boolean;\n}> = ({ href, children, interactive = true }) => {\n if (\n href &&\n (href.startsWith(\"mention://\") || isSpacesMentionUrl(href))\n ) {\n const name = typeof children === \"string\"\n ? children.replace(/^@/, \"\")\n : Array.isArray(children)\n ? String(children[0] ?? \"\").replace(/^@/, \"\")\n : \"\";\n\n const parsed = parseMentionUrl(href);\n\n return (\n <MentionChip\n name={name || parsed.name}\n type={parsed.type}\n personEmail={personEmailForMention(parsed)}\n assetIds={parsed.assetIds}\n thumbnailUrl={parsed.thumbnailUrl}\n sourceHref={isSpacesMentionUrl(href) ? href : undefined}\n interactive={interactive}\n />\n );\n }\n\n if (href?.startsWith(\"chrome-extension://\")) {\n return <span>{children}</span>;\n }\n\n if (isScreenPointHref(href)) {\n return <ScreenPointChip href={href}>{children}</ScreenPointChip>;\n }\n\n // Normal link — render as-is\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n};\n\n// ── Inline screen-point chip ──\n\n/**\n * Renders an `athena-point://` link as a non-navigating coordinate chip.\n * Surfaces without screenshot context (extension side panel, SDK consumers)\n * get the label plus the coordinates; malformed points fall back to text.\n */\nexport const ScreenPointChip: FC<{\n href: string;\n children?: ReactNode;\n}> = ({ href, children }) => {\n const parsed = parseScreenPointHref(href);\n if (!parsed.ok) {\n return <span>{children}</span>;\n }\n const description = formatScreenPoint(parsed.point);\n return (\n <span\n className=\"not-prose inline-flex max-w-[260px] items-center gap-1 whitespace-nowrap rounded-md border border-slate-300 bg-white px-1.5 py-px align-middle text-[0.85em] font-medium leading-[1.4] text-slate-600\"\n title={description}\n aria-label={description}\n data-screen-point=\"valid\"\n >\n <CrosshairIcon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"max-w-[180px] overflow-hidden text-ellipsis\">\n {children}\n </span>\n <span className=\"font-normal text-slate-400\">\n {parsed.point.x}, {parsed.point.y}\n </span>\n </span>\n );\n};\n\n// ── Inline mention chip ──\n\nexport const MentionChip: FC<{\n name: string;\n type?: string;\n assetIds?: string[];\n personEmail?: string;\n thumbnailUrl?: string;\n sourceHref?: string;\n interactive?: boolean;\n}> = ({\n name,\n type,\n assetIds,\n personEmail,\n thumbnailUrl,\n sourceHref,\n interactive = true,\n}) => {\n const auth = useContext(MentionAuthCtx);\n const memberAvatarUrl = useMemberAvatarUrl(personEmail);\n const [erroredAvatarUrl, setErroredAvatarUrl] = useState<string>();\n // A citation's excerpt (the passage the chip points at) is the tooltip; the\n // accessible name stays the action.\n const excerpt = sourceHref ? readSourceExcerpt(sourceHref) : null;\n const assetRootHref =\n type === \"asset\" && assetIds?.length\n ? createOlympusAssetUrl({\n appUrl: auth?.appUrl,\n backendUrl: auth?.backendUrl,\n assetIds,\n })\n : null;\n const href = (() => {\n if (!assetRootHref || !sourceHref) return assetRootHref;\n try {\n const source = new URL(sourceHref, \"https://app.athenaintel.com\");\n const destination = new URL(assetRootHref);\n for (const [key, value] of source.searchParams) {\n destination.searchParams.set(key, value);\n }\n destination.searchParams.set(\"asset_ids\", assetIds?.join(\",\") ?? \"\");\n return destination.toString();\n } catch {\n return assetRootHref;\n }\n })();\n // `not-prose` matters: assistant messages render inside Tailwind\n // Typography's `prose`, whose zero-specificity `img` margins (~1.7em\n // top/bottom) would otherwise inflate the chip's icon into a tall card\n // that breaks the sentence flow around it.\n const className = cn(\n \"not-prose inline-flex max-w-[220px] items-center gap-1 whitespace-nowrap rounded-md border border-slate-300 bg-white px-1.5 py-px align-middle text-[0.85em] font-medium leading-[1.4] text-slate-600 no-underline\",\n href &&\n \"cursor-pointer transition-colors hover:border-slate-400 hover:bg-slate-50 hover:text-slate-800 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1\",\n );\n const contents = (\n <>\n {thumbnailUrl && (\n <img\n src={thumbnailUrl}\n alt=\"\"\n className=\"size-3.5 shrink-0\"\n draggable={false}\n />\n )}\n {!thumbnailUrl && type === \"webpage\" && (\n <Globe2Icon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n )}\n {!thumbnailUrl && type === \"person\" && (\n memberAvatarUrl && erroredAvatarUrl !== memberAvatarUrl ? (\n <img\n src={memberAvatarUrl}\n alt=\"\"\n className=\"size-3.5 shrink-0 rounded-full object-cover\"\n draggable={false}\n onError={() => setErroredAvatarUrl(memberAvatarUrl)}\n />\n ) : (\n <UserIcon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n )\n )}\n <span className=\"max-w-[180px] overflow-hidden text-ellipsis\">\n {name}\n </span>\n </>\n );\n\n if (href && interactive) {\n return (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={className}\n aria-label={`Open ${name} in Spaces`}\n title={excerpt ?? `Open ${name} in Spaces`}\n data-excerpt={excerpt ?? undefined}\n >\n {contents}\n </a>\n );\n }\n\n return (\n <span\n className={className}\n title={excerpt ?? undefined}\n data-excerpt={excerpt ?? undefined}\n >\n {contents}\n </span>\n );\n};\n\n/** The `excerpt` a Spaces citation URL carries, or null. */\nfunction readSourceExcerpt(href: string): string | null {\n try {\n const excerpt = new URL(href, \"https://app.athenaintel.com\").searchParams\n .get(\"excerpt\")\n ?.trim();\n return excerpt ? excerpt : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The label an asset citation chip falls back to when the model wrote none\n * (`[](…/spaces/?asset_ids=…&page=5)`): the cited location, else \"Asset\" — a\n * chip must never render as an empty box.\n */\nfunction citationFallbackLabel(params: URLSearchParams): string {\n const start = params.get(\"start_page\") ?? params.get(\"page\");\n const end = params.get(\"end_page\");\n if (start && end && end !== start) return `Pages ${start}–${end}`;\n if (start) return `Page ${start}`;\n return \"Asset\";\n}\n\n// ── Parse mention:// URLs ──\n\nexport function parseMentionUrl(url: string): {\n type: string;\n name: string;\n id: string;\n email?: string;\n assetIds?: string[];\n thumbnailUrl?: string;\n} {\n try {\n if (isSpacesMentionUrl(url)) {\n const parsed = new URL(url, \"https://app.athenaintel.com\");\n const assetIds = (parsed.searchParams.get(\"asset_ids\") ?? \"\")\n .split(\",\")\n .map((id) => id.trim())\n .filter(Boolean);\n const id = assetIds[0] ?? \"\";\n const assetType = getSpacesAssetType(parsed);\n return {\n type: \"asset\",\n name:\n assetIds.length > 1\n ? `${assetIds.length} assets`\n : citationFallbackLabel(parsed.searchParams),\n id,\n assetIds,\n thumbnailUrl: assetTypeToThumb(assetType),\n };\n }\n // mention://tool?name=...&description=...\n // mention://asset?id=...&assetType=project&thumb=../icons/files/ic-project.svg\n const u = new URL(url);\n const type = u.hostname || u.pathname.replace(/^\\/\\//, \"\").split(\"?\")[0];\n const isWebpage = type === \"webpage\";\n const id = isWebpage\n ? u.searchParams.get(\"tab_id\") || \"\"\n : u.searchParams.get(\"id\") || u.searchParams.get(\"name\") || \"\";\n const name = isWebpage\n ? u.searchParams.get(\"title\") || \"This webpage\"\n : u.searchParams.get(\"name\") || id;\n\n // Get thumbnail: prefer explicit thumb param, then derive from assetType\n let thumbnailUrl = u.searchParams.get(\"thumb\") || undefined;\n if (!thumbnailUrl) {\n const assetType = u.searchParams.get(\"assetType\");\n if (assetType) {\n thumbnailUrl = assetTypeToThumb(assetType);\n } else if (type === \"asset\" || type === \"folder\") {\n thumbnailUrl = assetTypeToThumb(type === \"folder\" ? \"folder\" : null);\n }\n }\n\n return {\n type,\n name,\n id,\n email: type === \"person\" ? u.searchParams.get(\"email\") || undefined : undefined,\n assetIds: type === \"asset\" && id ? [id] : undefined,\n thumbnailUrl,\n };\n } catch {\n return { type: \"unknown\", name: \"\", id: \"\" };\n }\n}\n\nfunction htmlAttribute(attributes: string, name: string): string | null {\n const escapedName = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const match = new RegExp(\n `(?:^|\\\\s)${escapedName}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`,\n \"i\",\n ).exec(attributes);\n return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;\n}\n\nfunction decodeHtmlAttribute(value: string): string {\n return value\n .replace(/&amp;/gi, \"&\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;|&apos;/gi, \"'\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\");\n}\n\n/** Turn complete model-emitted URL citation anchors into safe Markdown links. */\nexport function normalizeAthenaMentionHtml(text: string): string {\n return text.replace(\n /<a\\b([^>]*)>([\\s\\S]*?)<\\/a>/gi,\n (original, attributes: string, rawLabel: string) => {\n const className = htmlAttribute(attributes, \"class\") ?? \"\";\n const type = htmlAttribute(attributes, \"data-type\") ?? \"\";\n if (!className.split(/\\s+/).includes(\"mention\") || type !== \"url\") {\n return original;\n }\n\n const rawUrl =\n htmlAttribute(attributes, \"data-url\") ??\n htmlAttribute(attributes, \"href\");\n if (!rawUrl) return original;\n const url = decodeHtmlAttribute(rawUrl);\n let normalizedUrl: string;\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return original;\n }\n normalizedUrl = parsed.toString();\n } catch {\n return original;\n }\n\n const label = decodeHtmlAttribute(rawLabel.replace(/<[^>]*>/g, \"\"))\n .trim()\n .replace(/([\\\\[\\]])/g, \"\\\\$1\");\n return label ? `[${label}](<${normalizedUrl}>)` : original;\n },\n );\n}\n// ── Parse mention markdown in plain text ──\n\n// The label may hold balanced brackets one level deep (`[@Plan [Q3]](…)`) or a lone `[`.\nconst MENTION_RE =\n /\\[@((?:\\[[^[\\]]*\\](?!\\((?:[a-z][a-z0-9+.-]*:\\/\\/|\\/))|[^\\]])*)\\]\\(((?:mention:\\/\\/|(?:https?:\\/\\/[^)\\s]+)?\\/dashboard\\/spaces\\/?\\?)[^)]+)\\)/g;\n\n/**\n * Parse text containing [@name](mention://...) patterns into\n * an array of string segments and MentionChip elements.\n */\nexport function parseMentionsInText(text: string): (string | ReactElement)[] {\n const parts: (string | ReactElement)[] = [];\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n\n MENTION_RE.lastIndex = 0;\n\n while ((match = MENTION_RE.exec(text)) !== null) {\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n const name = match[1];\n const url = match[2];\n const parsed = parseMentionUrl(url);\n\n parts.push(\n <MentionChip\n key={`mention-${match.index}`}\n name={name || parsed.name}\n type={parsed.type}\n personEmail={personEmailForMention(parsed)}\n assetIds={parsed.assetIds}\n thumbnailUrl={parsed.thumbnailUrl}\n sourceHref={isSpacesMentionUrl(url) ? url : undefined}\n />\n );\n\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts.length > 0 ? parts : [text];\n}\n\n/** The text as a reader sees it: each `[@name](…)` mention becomes `@name`. */\nexport function mentionsToPlainText(text: string): string {\n return text.replace(MENTION_RE, (_match, name: string, url: string) => {\n return `@${name || parseMentionUrl(url).name}`;\n });\n}\n"],"names":["jsx","isScreenPointHref","parseScreenPointHref","formatScreenPoint","jsxs","CrosshairIcon","useContext","MentionAuthCtx","useMemberAvatarUrl","useState","createOlympusAssetUrl","cn","Fragment","Globe2Icon","UserIcon","id","assetTypeToThumb"],"mappings":"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"}
1
+ {"version":3,"file":"mention-chip.cjs","sources":["../../../src/chat-ui/mentions/mention-chip.tsx"],"sourcesContent":["/**\n * Mention chip rendering for message display.\n *\n * Parses [@name](mention://type?...) markdown links and renders them\n * as styled inline chips matching the Olympus mention style.\n * Used in both user messages (inline text) and assistant messages (markdown links).\n */\n\nimport {\n useContext,\n useState,\n type FC,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport {\n formatScreenPoint,\n isScreenPointHref,\n parseScreenPointHref,\n} from \"@athenaintel/agent-runtime\";\nimport {\n type AssetReference,\n decodeFromAssetMentionUri,\n decodeFromAssetSpacesUrl,\n encodeToAssetSpacesUrl,\n formatReferenceAnchor,\n} from \"@athenaintel/references/asset-links\";\nimport {\n CrosshairIcon,\n DatabaseIcon,\n FileCodeIcon,\n FileIcon,\n FileTextIcon,\n FolderIcon,\n Globe2Icon,\n HeadphonesIcon,\n ImageIcon,\n type LucideIcon,\n MailIcon,\n MessageSquareIcon,\n MonitorIcon,\n PresentationIcon,\n SheetIcon,\n UserIcon,\n VideoIcon,\n} from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\nimport { createOlympusAssetUrl } from \"../lib/asset-urls\";\nimport { MentionAuthCtx } from \"./suggestions/data/api-client\";\nimport { assetTypeToThumb } from \"./suggestions/data/sources/asset-type-icons\";\nimport { useMemberAvatarUrl } from \"./use-member-avatar\";\n\nconst SPACES_MENTION_PATH = /\\/dashboard\\/spaces\\/?\\?/;\n\nfunction isSpacesMentionUrl(url: string): boolean {\n return SPACES_MENTION_PATH.test(url);\n}\n\nfunction getSpacesAssetType(url: URL): string | null {\n const assetType =\n url.searchParams.get(\"assetType\") ||\n url.searchParams.get(\"asset_type\") ||\n url.searchParams.get(\"type\");\n return assetType === \"sheet\" ? \"spreadsheet\" : assetType;\n}\n\n/**\n * Where in the asset a citation points, as its chip shows it. The anchor is\n * read and labelled by `@athenaintel/references` — the same parser and labels\n * Olympus's mention chips use — so a chip reads `jwt.py:12-25`, `p.5` or\n * `Slide 3` in every Athena chat.\n */\nexport interface MentionChipCitation {\n /** The anchor type (`page`, `file_line_range`, `slide`, …). */\n anchorType: string;\n /** The short label drawn after the name (`p.5`, `jwt.py:12-25`). */\n label: string;\n /**\n * The place spelled out (`Page 5`, `agora/…/jwt.py:12-25`), without the\n * excerpt: the tooltip when the link carries no excerpt, and part of the\n * chip's accessible name when it holds a word.\n */\n description: string;\n}\n\n/** The citation an asset link's anchor names, or undefined for a plain mention or an anchor nobody can read. */\nfunction citationOf(reference: AssetReference): MentionChipCitation | undefined {\n const { anchor, meta } = reference;\n if (!anchor) return undefined;\n const label = formatReferenceAnchor(anchor, \"short\", meta);\n if (!label) return undefined;\n const { excerpt: _excerpt, ...place } = meta ?? {};\n return {\n anchorType: anchor.type,\n label,\n description: formatReferenceAnchor(anchor, \"long\", place) ?? label,\n };\n}\n\n/**\n * The chip's icon for an asset type when the host serves no asset-type SVGs.\n * A compact set on purpose: the chip renders in every message (and Olympus's\n * main layout reaches it through the queued-message tray), so it carries a\n * dozen icons rather than the `@` menu's whole map.\n */\nconst ASSET_CHIP_ICONS: Record<string, LucideIcon> = {\n document: FileTextIcon,\n word_document: FileTextIcon,\n markdown: FileTextIcon,\n txt: FileTextIcon,\n pdf: FileTextIcon,\n spreadsheet: SheetIcon,\n table: SheetIcon,\n presentation: PresentationIcon,\n powerpoint_deck: PresentationIcon,\n presentation_pptx_studio: PresentationIcon,\n pptx: PresentationIcon,\n image: ImageIcon,\n figure: ImageIcon,\n canvas: ImageIcon,\n video: VideoIcon,\n meeting: VideoIcon,\n video_session: VideoIcon,\n audio: HeadphonesIcon,\n voice_memo: HeadphonesIcon,\n phone_call: HeadphonesIcon,\n email: MailIcon,\n email_message: MailIcon,\n live_email_thread: MailIcon,\n live_calendar_event: MailIcon,\n python: FileCodeIcon,\n javascript: FileCodeIcon,\n typescript: FileCodeIcon,\n json: FileCodeIcon,\n yaml: FileCodeIcon,\n database: DatabaseIcon,\n catalog: DatabaseIcon,\n drive: DatabaseIcon,\n dataset: DatabaseIcon,\n semantic_model: DatabaseIcon,\n query_snippet: DatabaseIcon,\n folder: FolderIcon,\n gdrive_folder: FolderIcon,\n project: FolderIcon,\n collection: FolderIcon,\n session: MessageSquareIcon,\n computer: MonitorIcon,\n desktop_session: MonitorIcon,\n user_interface: MonitorIcon,\n application: MonitorIcon,\n html: Globe2Icon,\n web_page: Globe2Icon,\n browse_session: Globe2Icon,\n};\n\n/** A repository file's lines read as a code file, whatever the catalog holding them. */\nfunction assetChipIcon(\n assetType: string | undefined,\n citation: MentionChipCitation | undefined,\n): LucideIcon {\n if (citation?.anchorType === \"file_line_range\") return FileCodeIcon;\n return (assetType && ASSET_CHIP_ICONS[assetType.toLowerCase()]) || FileIcon;\n}\n\n/** A label with a letter or digit in it; `§` or `•` alone says nothing read aloud. */\nconst SPEAKABLE = /[\\p{L}\\p{N}]/u;\n\n/** The asset reference an asset link decodes to, or null when it does not parse. */\nfunction decodeAssetLink(\n url: URL,\n decode: (url: URL) => AssetReference,\n): AssetReference | null {\n try {\n return decode(url);\n } catch {\n return null;\n }\n}\n\n// ── Mention link component for markdown renderer ──\n\n/**\n * Custom <a> renderer for MarkdownTextPrimitive.\n * Intercepts mention:// links and renders them as chips.\n * Falls through to a normal link for non-mention URLs.\n */\nfunction personEmailForMention(\n parsed: ReturnType<typeof parseMentionUrl>,\n): string | undefined {\n if (parsed.type !== \"person\") return undefined;\n return parsed.email || (parsed.id.includes(\"@\") ? parsed.id : undefined);\n}\n\nexport const MentionLink: FC<{\n href?: string;\n children?: ReactNode;\n interactive?: boolean;\n}> = ({ href, children, interactive = true }) => {\n if (\n href &&\n (href.startsWith(\"mention://\") || isSpacesMentionUrl(href))\n ) {\n const name = typeof children === \"string\"\n ? children.replace(/^@/, \"\")\n : Array.isArray(children)\n ? String(children[0] ?? \"\").replace(/^@/, \"\")\n : \"\";\n\n const parsed = parseMentionUrl(href);\n\n return (\n <MentionChip\n name={name || parsed.name}\n type={parsed.type}\n personEmail={personEmailForMention(parsed)}\n assetIds={parsed.assetIds}\n assetType={parsed.assetType}\n citation={parsed.citation}\n thumbnailUrl={parsed.thumbnailUrl}\n sourceHref={isSpacesMentionUrl(href) ? href : parsed.sourceHref}\n interactive={interactive}\n />\n );\n }\n\n if (href?.startsWith(\"chrome-extension://\")) {\n return <span>{children}</span>;\n }\n\n if (isScreenPointHref(href)) {\n return <ScreenPointChip href={href}>{children}</ScreenPointChip>;\n }\n\n // Normal link — render as-is\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n};\n\n// ── Inline screen-point chip ──\n\n/**\n * Renders an `athena-point://` link as a non-navigating coordinate chip.\n * Surfaces without screenshot context (extension side panel, SDK consumers)\n * get the label plus the coordinates; malformed points fall back to text.\n */\nexport const ScreenPointChip: FC<{\n href: string;\n children?: ReactNode;\n}> = ({ href, children }) => {\n const parsed = parseScreenPointHref(href);\n if (!parsed.ok) {\n return <span>{children}</span>;\n }\n const description = formatScreenPoint(parsed.point);\n return (\n <span\n className=\"not-prose inline-flex max-w-[260px] items-center gap-1 whitespace-nowrap rounded-md border border-slate-300 bg-white px-1.5 py-px align-middle text-[0.85em] font-medium leading-[1.4] text-slate-600\"\n title={description}\n aria-label={description}\n data-screen-point=\"valid\"\n >\n <CrosshairIcon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"max-w-[180px] overflow-hidden text-ellipsis\">\n {children}\n </span>\n <span className=\"font-normal text-slate-400\">\n {parsed.point.x}, {parsed.point.y}\n </span>\n </span>\n );\n};\n\n// ── Inline mention chip ──\n\nexport const MentionChip: FC<{\n name: string;\n type?: string;\n assetIds?: string[];\n /** The asset's type (`document`, `catalog`, …): picks the icon when the host bundles no SVG thumbnails. */\n assetType?: string;\n /** Where in the asset the link points: its label follows the name. */\n citation?: MentionChipCitation;\n personEmail?: string;\n thumbnailUrl?: string;\n sourceHref?: string;\n interactive?: boolean;\n}> = ({\n name,\n type,\n assetIds,\n assetType,\n citation,\n personEmail,\n thumbnailUrl,\n sourceHref,\n interactive = true,\n}) => {\n const auth = useContext(MentionAuthCtx);\n const memberAvatarUrl = useMemberAvatarUrl(personEmail);\n const [erroredAvatarUrl, setErroredAvatarUrl] = useState<string>();\n // A citation's excerpt (the passage the chip points at) is the tooltip, else\n // the cited place spelled out; the accessible name stays the action.\n const excerpt = sourceHref ? readSourceExcerpt(sourceHref) : null;\n const tooltip = excerpt ?? citation?.description ?? null;\n // A chip the model left unlabelled is already named by its place.\n const anchorLabel =\n citation && name !== citation.description && name !== citation.label\n ? citation.label\n : null;\n const assetRootHref =\n type === \"asset\" && assetIds?.length\n ? createOlympusAssetUrl({\n appUrl: auth?.appUrl,\n backendUrl: auth?.backendUrl,\n assetIds,\n })\n : null;\n const href = (() => {\n if (!assetRootHref || !sourceHref) return assetRootHref;\n try {\n const source = new URL(sourceHref, \"https://app.athenaintel.com\");\n const destination = new URL(assetRootHref);\n for (const [key, value] of source.searchParams) {\n destination.searchParams.set(key, value);\n }\n destination.searchParams.set(\"asset_ids\", assetIds?.join(\",\") ?? \"\");\n return destination.toString();\n } catch {\n return assetRootHref;\n }\n })();\n // `not-prose` matters: assistant messages render inside Tailwind\n // Typography's `prose`, whose zero-specificity `img` margins (~1.7em\n // top/bottom) would otherwise inflate the chip's icon into a tall card\n // that breaks the sentence flow around it.\n const className = cn(\n \"not-prose inline-flex max-w-[220px] items-center gap-1 whitespace-nowrap rounded-md border border-slate-300 bg-white px-1.5 py-px align-middle text-[0.85em] font-medium leading-[1.4] text-slate-600 no-underline\",\n // The cited place gets its own room after a name already at its cap.\n anchorLabel && \"max-w-[340px]\",\n href &&\n \"cursor-pointer transition-colors hover:border-slate-400 hover:bg-slate-50 hover:text-slate-800 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1\",\n );\n const AssetIcon = assetChipIcon(assetType, citation);\n const contents = (\n <>\n {thumbnailUrl && (\n <img\n src={thumbnailUrl}\n alt=\"\"\n className=\"size-3.5 shrink-0\"\n draggable={false}\n />\n )}\n {!thumbnailUrl && (type === \"asset\" || type === \"folder\") && (\n // A host that bundles no asset-type SVGs (computer apps, most SDK\n // apps) still gets the type's icon.\n <AssetIcon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n )}\n {!thumbnailUrl && type === \"webpage\" && (\n <Globe2Icon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n )}\n {!thumbnailUrl && type === \"person\" && (\n memberAvatarUrl && erroredAvatarUrl !== memberAvatarUrl ? (\n <img\n src={memberAvatarUrl}\n alt=\"\"\n className=\"size-3.5 shrink-0 rounded-full object-cover\"\n draggable={false}\n onError={() => setErroredAvatarUrl(memberAvatarUrl)}\n />\n ) : (\n <UserIcon className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n )\n )}\n <span className=\"max-w-[180px] overflow-hidden text-ellipsis\">\n {name}\n </span>\n {citation && anchorLabel && (\n <>\n {/* Copied or read as text, the place stays a word apart from the name; a flex row draws no space. */}{\" \"}\n <span\n className=\"max-w-[140px] shrink-0 overflow-hidden text-ellipsis font-normal text-slate-400\"\n data-citation-anchor={citation.anchorType}\n >\n {anchorLabel}\n </span>\n </>\n )}\n </>\n );\n\n if (href && interactive) {\n return (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={className}\n aria-label={\n anchorLabel && citation && SPEAKABLE.test(citation.description)\n ? `Open ${name} at ${citation.description} in Spaces`\n : `Open ${name} in Spaces`\n }\n title={tooltip ?? `Open ${name} in Spaces`}\n data-excerpt={excerpt ?? undefined}\n // The link's text without its place: what a click names the opened asset.\n data-mention-name={name}\n >\n {contents}\n </a>\n );\n }\n\n return (\n <span\n className={className}\n title={tooltip ?? undefined}\n data-excerpt={excerpt ?? undefined}\n >\n {contents}\n </span>\n );\n};\n\n/** The `excerpt` a Spaces citation URL carries, or null. */\nfunction readSourceExcerpt(href: string): string | null {\n try {\n const excerpt = new URL(href, \"https://app.athenaintel.com\").searchParams\n .get(\"excerpt\")\n ?.trim();\n return excerpt ? excerpt : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The label an asset citation chip falls back to when the model wrote none\n * (`[](…/spaces/?asset_ids=…&page=5)`): the cited location, else \"Asset\" — a\n * chip must never render as an empty box.\n */\nfunction citationFallbackLabel(params: URLSearchParams): string {\n const start = params.get(\"start_page\") ?? params.get(\"page\");\n const end = params.get(\"end_page\");\n if (start && end && end !== start) return `Pages ${start}–${end}`;\n if (start) return `Page ${start}`;\n return \"Asset\";\n}\n\n// ── Parse mention:// URLs ──\n\nexport function parseMentionUrl(url: string): {\n type: string;\n name: string;\n id: string;\n email?: string;\n assetIds?: string[];\n assetType?: string;\n citation?: MentionChipCitation;\n /**\n * For a `mention://asset` citation, the Spaces link to its place (anchor and\n * excerpt), so the chip opens there; a Spaces mention is its own link.\n */\n sourceHref?: string;\n thumbnailUrl?: string;\n} {\n try {\n if (isSpacesMentionUrl(url)) {\n const parsed = new URL(url, \"https://app.athenaintel.com\");\n const assetIds = (parsed.searchParams.get(\"asset_ids\") ?? \"\")\n .split(\",\")\n .map((id) => id.trim())\n .filter(Boolean);\n const id = assetIds[0] ?? \"\";\n const assetType = getSpacesAssetType(parsed);\n // A link to several assets names no one place in any of them.\n const reference =\n assetIds.length === 1\n ? decodeAssetLink(parsed, decodeFromAssetSpacesUrl)\n : null;\n const citation = reference ? citationOf(reference) : undefined;\n return {\n type: \"asset\",\n name:\n assetIds.length > 1\n ? `${assetIds.length} assets`\n : (citation?.description ??\n citationFallbackLabel(parsed.searchParams)),\n id,\n assetIds,\n assetType: assetType ?? undefined,\n citation,\n thumbnailUrl: assetTypeToThumb(assetType),\n };\n }\n // mention://tool?name=...&description=...\n // mention://asset?id=...&assetType=project&thumb=../icons/files/ic-project.svg\n const u = new URL(url);\n const type = u.hostname || u.pathname.replace(/^\\/\\//, \"\").split(\"?\")[0];\n const isWebpage = type === \"webpage\";\n const id = isWebpage\n ? u.searchParams.get(\"tab_id\") || \"\"\n : u.searchParams.get(\"id\") || u.searchParams.get(\"name\") || \"\";\n const name = isWebpage\n ? u.searchParams.get(\"title\") || \"This webpage\"\n : u.searchParams.get(\"name\") || id;\n\n // Get thumbnail: prefer explicit thumb param, then derive from assetType\n let thumbnailUrl = u.searchParams.get(\"thumb\") || undefined;\n if (!thumbnailUrl) {\n const assetType = u.searchParams.get(\"assetType\");\n if (assetType) {\n thumbnailUrl = assetTypeToThumb(assetType);\n } else if (type === \"asset\" || type === \"folder\") {\n thumbnailUrl = assetTypeToThumb(type === \"folder\" ? \"folder\" : null);\n }\n }\n\n const reference =\n type === \"asset\" ? decodeAssetLink(u, decodeFromAssetMentionUri) : null;\n\n return {\n type,\n name,\n id,\n email: type === \"person\" ? u.searchParams.get(\"email\") || undefined : undefined,\n assetIds: type === \"asset\" && id ? [id] : undefined,\n assetType:\n type === \"asset\" || type === \"folder\"\n ? u.searchParams.get(\"assetType\") ||\n reference?.meta?.assetType ||\n (type === \"folder\" ? \"folder\" : undefined)\n : undefined,\n citation: reference ? citationOf(reference) : undefined,\n sourceHref:\n reference && (reference.anchor || reference.meta?.excerpt)\n ? encodeToAssetSpacesUrl(reference)\n : undefined,\n thumbnailUrl,\n };\n } catch {\n return { type: \"unknown\", name: \"\", id: \"\" };\n }\n}\n\nfunction htmlAttribute(attributes: string, name: string): string | null {\n const escapedName = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const match = new RegExp(\n `(?:^|\\\\s)${escapedName}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`,\n \"i\",\n ).exec(attributes);\n return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;\n}\n\nfunction decodeHtmlAttribute(value: string): string {\n return value\n .replace(/&amp;/gi, \"&\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;|&apos;/gi, \"'\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\");\n}\n\n/** Turn complete model-emitted URL citation anchors into safe Markdown links. */\nexport function normalizeAthenaMentionHtml(text: string): string {\n return text.replace(\n /<a\\b([^>]*)>([\\s\\S]*?)<\\/a>/gi,\n (original, attributes: string, rawLabel: string) => {\n const className = htmlAttribute(attributes, \"class\") ?? \"\";\n const type = htmlAttribute(attributes, \"data-type\") ?? \"\";\n if (!className.split(/\\s+/).includes(\"mention\") || type !== \"url\") {\n return original;\n }\n\n const rawUrl =\n htmlAttribute(attributes, \"data-url\") ??\n htmlAttribute(attributes, \"href\");\n if (!rawUrl) return original;\n const url = decodeHtmlAttribute(rawUrl);\n let normalizedUrl: string;\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return original;\n }\n normalizedUrl = parsed.toString();\n } catch {\n return original;\n }\n\n const label = decodeHtmlAttribute(rawLabel.replace(/<[^>]*>/g, \"\"))\n .trim()\n .replace(/([\\\\[\\]])/g, \"\\\\$1\");\n return label ? `[${label}](<${normalizedUrl}>)` : original;\n },\n );\n}\n// ── Parse mention markdown in plain text ──\n\n// The label may hold balanced brackets one level deep (`[@Plan [Q3]](…)`) or a lone `[`.\nconst MENTION_RE =\n /\\[@((?:\\[[^[\\]]*\\](?!\\((?:[a-z][a-z0-9+.-]*:\\/\\/|\\/))|[^\\]])*)\\]\\(((?:mention:\\/\\/|(?:https?:\\/\\/[^)\\s]+)?\\/dashboard\\/spaces\\/?\\?)[^)]+)\\)/g;\n\n/**\n * Parse text containing [@name](mention://...) patterns into\n * an array of string segments and MentionChip elements.\n */\nexport function parseMentionsInText(text: string): (string | ReactElement)[] {\n const parts: (string | ReactElement)[] = [];\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n\n MENTION_RE.lastIndex = 0;\n\n while ((match = MENTION_RE.exec(text)) !== null) {\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n const name = match[1];\n const url = match[2];\n const parsed = parseMentionUrl(url);\n\n parts.push(\n <MentionChip\n key={`mention-${match.index}`}\n name={name || parsed.name}\n type={parsed.type}\n personEmail={personEmailForMention(parsed)}\n assetIds={parsed.assetIds}\n assetType={parsed.assetType}\n citation={parsed.citation}\n thumbnailUrl={parsed.thumbnailUrl}\n sourceHref={isSpacesMentionUrl(url) ? url : parsed.sourceHref}\n />\n );\n\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts.length > 0 ? parts : [text];\n}\n\n/** The text as a reader sees it: each `[@name](…)` mention becomes `@name`. */\nexport function mentionsToPlainText(text: string): string {\n return text.replace(MENTION_RE, (_match, name: string, url: string) => {\n return `@${name || parseMentionUrl(url).name}`;\n });\n}\n"],"names":["formatReferenceAnchor","FileTextIcon","SheetIcon","PresentationIcon","ImageIcon","VideoIcon","HeadphonesIcon","MailIcon","FileCodeIcon","DatabaseIcon","FolderIcon","MessageSquareIcon","MonitorIcon","Globe2Icon","FileIcon","jsx","isScreenPointHref","parseScreenPointHref","formatScreenPoint","jsxs","CrosshairIcon","useContext","MentionAuthCtx","useMemberAvatarUrl","useState","createOlympusAssetUrl","cn","Fragment","UserIcon","id","reference","decodeFromAssetSpacesUrl","assetTypeToThumb","decodeFromAssetMentionUri","encodeToAssetSpacesUrl"],"mappings":"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"}
@@ -6,6 +6,24 @@
6
6
  * Used in both user messages (inline text) and assistant messages (markdown links).
7
7
  */
8
8
  import { type FC, type ReactElement, type ReactNode } from "react";
9
+ /**
10
+ * Where in the asset a citation points, as its chip shows it. The anchor is
11
+ * read and labelled by `@athenaintel/references` — the same parser and labels
12
+ * Olympus's mention chips use — so a chip reads `jwt.py:12-25`, `p.5` or
13
+ * `Slide 3` in every Athena chat.
14
+ */
15
+ export interface MentionChipCitation {
16
+ /** The anchor type (`page`, `file_line_range`, `slide`, …). */
17
+ anchorType: string;
18
+ /** The short label drawn after the name (`p.5`, `jwt.py:12-25`). */
19
+ label: string;
20
+ /**
21
+ * The place spelled out (`Page 5`, `agora/…/jwt.py:12-25`), without the
22
+ * excerpt: the tooltip when the link carries no excerpt, and part of the
23
+ * chip's accessible name when it holds a word.
24
+ */
25
+ description: string;
26
+ }
9
27
  export declare const MentionLink: FC<{
10
28
  href?: string;
11
29
  children?: ReactNode;
@@ -24,6 +42,10 @@ export declare const MentionChip: FC<{
24
42
  name: string;
25
43
  type?: string;
26
44
  assetIds?: string[];
45
+ /** The asset's type (`document`, `catalog`, …): picks the icon when the host bundles no SVG thumbnails. */
46
+ assetType?: string;
47
+ /** Where in the asset the link points: its label follows the name. */
48
+ citation?: MentionChipCitation;
27
49
  personEmail?: string;
28
50
  thumbnailUrl?: string;
29
51
  sourceHref?: string;
@@ -35,6 +57,13 @@ export declare function parseMentionUrl(url: string): {
35
57
  id: string;
36
58
  email?: string;
37
59
  assetIds?: string[];
60
+ assetType?: string;
61
+ citation?: MentionChipCitation;
62
+ /**
63
+ * For a `mention://asset` citation, the Spaces link to its place (anchor and
64
+ * excerpt), so the chip opens there; a Spaces mention is its own link.
65
+ */
66
+ sourceHref?: string;
38
67
  thumbnailUrl?: string;
39
68
  };
40
69
  /** Turn complete model-emitted URL citation anchors into safe Markdown links. */
@@ -1,6 +1,9 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import { useContext, useState } from "react";
3
- import { Globe2Icon, UserIcon, CrosshairIcon } from "lucide-react";
3
+ import { formatReferenceAnchor } from "../../references/src/anchors/format.js";
4
+ import { decodeFromAssetMentionUri } from "../../references/src/formats/uri/asset-mention-uri.js";
5
+ import { decodeFromAssetSpacesUrl, encodeToAssetSpacesUrl } from "../../references/src/formats/uri/asset-spaces-url.js";
6
+ import { Globe2Icon, UserIcon, FileCodeIcon, MonitorIcon, MessageSquareIcon, FolderIcon, DatabaseIcon, MailIcon, HeadphonesIcon, VideoIcon, ImageIcon, PresentationIcon, SheetIcon, FileTextIcon, FileIcon, CrosshairIcon } from "lucide-react";
4
7
  import { createOlympusAssetUrl } from "../lib/asset-urls.js";
5
8
  import { MentionAuthCtx } from "./suggestions/data/api-client.js";
6
9
  import { assetTypeToThumb } from "./suggestions/data/sources/asset-type-icons.js";
@@ -15,6 +18,79 @@ function getSpacesAssetType(url) {
15
18
  const assetType = url.searchParams.get("assetType") || url.searchParams.get("asset_type") || url.searchParams.get("type");
16
19
  return assetType === "sheet" ? "spreadsheet" : assetType;
17
20
  }
21
+ function citationOf(reference) {
22
+ const { anchor, meta } = reference;
23
+ if (!anchor) return void 0;
24
+ const label = formatReferenceAnchor(anchor, "short", meta);
25
+ if (!label) return void 0;
26
+ const { excerpt: _excerpt, ...place } = meta ?? {};
27
+ return {
28
+ anchorType: anchor.type,
29
+ label,
30
+ description: formatReferenceAnchor(anchor, "long", place) ?? label
31
+ };
32
+ }
33
+ const ASSET_CHIP_ICONS = {
34
+ document: FileTextIcon,
35
+ word_document: FileTextIcon,
36
+ markdown: FileTextIcon,
37
+ txt: FileTextIcon,
38
+ pdf: FileTextIcon,
39
+ spreadsheet: SheetIcon,
40
+ table: SheetIcon,
41
+ presentation: PresentationIcon,
42
+ powerpoint_deck: PresentationIcon,
43
+ presentation_pptx_studio: PresentationIcon,
44
+ pptx: PresentationIcon,
45
+ image: ImageIcon,
46
+ figure: ImageIcon,
47
+ canvas: ImageIcon,
48
+ video: VideoIcon,
49
+ meeting: VideoIcon,
50
+ video_session: VideoIcon,
51
+ audio: HeadphonesIcon,
52
+ voice_memo: HeadphonesIcon,
53
+ phone_call: HeadphonesIcon,
54
+ email: MailIcon,
55
+ email_message: MailIcon,
56
+ live_email_thread: MailIcon,
57
+ live_calendar_event: MailIcon,
58
+ python: FileCodeIcon,
59
+ javascript: FileCodeIcon,
60
+ typescript: FileCodeIcon,
61
+ json: FileCodeIcon,
62
+ yaml: FileCodeIcon,
63
+ database: DatabaseIcon,
64
+ catalog: DatabaseIcon,
65
+ drive: DatabaseIcon,
66
+ dataset: DatabaseIcon,
67
+ semantic_model: DatabaseIcon,
68
+ query_snippet: DatabaseIcon,
69
+ folder: FolderIcon,
70
+ gdrive_folder: FolderIcon,
71
+ project: FolderIcon,
72
+ collection: FolderIcon,
73
+ session: MessageSquareIcon,
74
+ computer: MonitorIcon,
75
+ desktop_session: MonitorIcon,
76
+ user_interface: MonitorIcon,
77
+ application: MonitorIcon,
78
+ html: Globe2Icon,
79
+ web_page: Globe2Icon,
80
+ browse_session: Globe2Icon
81
+ };
82
+ function assetChipIcon(assetType, citation) {
83
+ if ((citation == null ? void 0 : citation.anchorType) === "file_line_range") return FileCodeIcon;
84
+ return assetType && ASSET_CHIP_ICONS[assetType.toLowerCase()] || FileIcon;
85
+ }
86
+ const SPEAKABLE = /[\p{L}\p{N}]/u;
87
+ function decodeAssetLink(url, decode) {
88
+ try {
89
+ return decode(url);
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
18
94
  function personEmailForMention(parsed) {
19
95
  if (parsed.type !== "person") return void 0;
20
96
  return parsed.email || (parsed.id.includes("@") ? parsed.id : void 0);
@@ -30,8 +106,10 @@ const MentionLink = ({ href, children, interactive = true }) => {
30
106
  type: parsed.type,
31
107
  personEmail: personEmailForMention(parsed),
32
108
  assetIds: parsed.assetIds,
109
+ assetType: parsed.assetType,
110
+ citation: parsed.citation,
33
111
  thumbnailUrl: parsed.thumbnailUrl,
34
- sourceHref: isSpacesMentionUrl(href) ? href : void 0,
112
+ sourceHref: isSpacesMentionUrl(href) ? href : parsed.sourceHref,
35
113
  interactive
36
114
  }
37
115
  );
@@ -73,6 +151,8 @@ const MentionChip = ({
73
151
  name,
74
152
  type,
75
153
  assetIds,
154
+ assetType,
155
+ citation,
76
156
  personEmail,
77
157
  thumbnailUrl,
78
158
  sourceHref,
@@ -82,6 +162,8 @@ const MentionChip = ({
82
162
  const memberAvatarUrl = useMemberAvatarUrl(personEmail);
83
163
  const [erroredAvatarUrl, setErroredAvatarUrl] = useState();
84
164
  const excerpt = sourceHref ? readSourceExcerpt(sourceHref) : null;
165
+ const tooltip = excerpt ?? (citation == null ? void 0 : citation.description) ?? null;
166
+ const anchorLabel = citation && name !== citation.description && name !== citation.label ? citation.label : null;
85
167
  const assetRootHref = type === "asset" && (assetIds == null ? void 0 : assetIds.length) ? createOlympusAssetUrl({
86
168
  appUrl: auth == null ? void 0 : auth.appUrl,
87
169
  backendUrl: auth == null ? void 0 : auth.backendUrl,
@@ -103,8 +185,11 @@ const MentionChip = ({
103
185
  })();
104
186
  const className = cn(
105
187
  "not-prose inline-flex max-w-[220px] items-center gap-1 whitespace-nowrap rounded-md border border-slate-300 bg-white px-1.5 py-px align-middle text-[0.85em] font-medium leading-[1.4] text-slate-600 no-underline",
188
+ // The cited place gets its own room after a name already at its cap.
189
+ anchorLabel && "max-w-[340px]",
106
190
  href && "cursor-pointer transition-colors hover:border-slate-400 hover:bg-slate-50 hover:text-slate-800 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
107
191
  );
192
+ const AssetIcon = assetChipIcon(assetType, citation);
108
193
  const contents = /* @__PURE__ */ jsxs(Fragment, { children: [
109
194
  thumbnailUrl && /* @__PURE__ */ jsx(
110
195
  "img",
@@ -115,6 +200,9 @@ const MentionChip = ({
115
200
  draggable: false
116
201
  }
117
202
  ),
203
+ !thumbnailUrl && (type === "asset" || type === "folder") && // A host that bundles no asset-type SVGs (computer apps, most SDK
204
+ // apps) still gets the type's icon.
205
+ /* @__PURE__ */ jsx(AssetIcon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }),
118
206
  !thumbnailUrl && type === "webpage" && /* @__PURE__ */ jsx(Globe2Icon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }),
119
207
  !thumbnailUrl && type === "person" && (memberAvatarUrl && erroredAvatarUrl !== memberAvatarUrl ? /* @__PURE__ */ jsx(
120
208
  "img",
@@ -126,7 +214,18 @@ const MentionChip = ({
126
214
  onError: () => setErroredAvatarUrl(memberAvatarUrl)
127
215
  }
128
216
  ) : /* @__PURE__ */ jsx(UserIcon, { className: "size-3.5 shrink-0", "aria-hidden": "true" })),
129
- /* @__PURE__ */ jsx("span", { className: "max-w-[180px] overflow-hidden text-ellipsis", children: name })
217
+ /* @__PURE__ */ jsx("span", { className: "max-w-[180px] overflow-hidden text-ellipsis", children: name }),
218
+ citation && anchorLabel && /* @__PURE__ */ jsxs(Fragment, { children: [
219
+ " ",
220
+ /* @__PURE__ */ jsx(
221
+ "span",
222
+ {
223
+ className: "max-w-[140px] shrink-0 overflow-hidden text-ellipsis font-normal text-slate-400",
224
+ "data-citation-anchor": citation.anchorType,
225
+ children: anchorLabel
226
+ }
227
+ )
228
+ ] })
130
229
  ] });
131
230
  if (href && interactive) {
132
231
  return /* @__PURE__ */ jsx(
@@ -136,9 +235,10 @@ const MentionChip = ({
136
235
  target: "_blank",
137
236
  rel: "noopener noreferrer",
138
237
  className,
139
- "aria-label": `Open ${name} in Spaces`,
140
- title: excerpt ?? `Open ${name} in Spaces`,
238
+ "aria-label": anchorLabel && citation && SPEAKABLE.test(citation.description) ? `Open ${name} at ${citation.description} in Spaces` : `Open ${name} in Spaces`,
239
+ title: tooltip ?? `Open ${name} in Spaces`,
141
240
  "data-excerpt": excerpt ?? void 0,
241
+ "data-mention-name": name,
142
242
  children: contents
143
243
  }
144
244
  );
@@ -147,7 +247,7 @@ const MentionChip = ({
147
247
  "span",
148
248
  {
149
249
  className,
150
- title: excerpt ?? void 0,
250
+ title: tooltip ?? void 0,
151
251
  "data-excerpt": excerpt ?? void 0,
152
252
  children: contents
153
253
  }
@@ -170,17 +270,22 @@ function citationFallbackLabel(params) {
170
270
  return "Asset";
171
271
  }
172
272
  function parseMentionUrl(url) {
273
+ var _a, _b;
173
274
  try {
174
275
  if (isSpacesMentionUrl(url)) {
175
276
  const parsed = new URL(url, "https://app.athenaintel.com");
176
277
  const assetIds = (parsed.searchParams.get("asset_ids") ?? "").split(",").map((id3) => id3.trim()).filter(Boolean);
177
278
  const id2 = assetIds[0] ?? "";
178
279
  const assetType = getSpacesAssetType(parsed);
280
+ const reference2 = assetIds.length === 1 ? decodeAssetLink(parsed, decodeFromAssetSpacesUrl) : null;
281
+ const citation = reference2 ? citationOf(reference2) : void 0;
179
282
  return {
180
283
  type: "asset",
181
- name: assetIds.length > 1 ? `${assetIds.length} assets` : citationFallbackLabel(parsed.searchParams),
284
+ name: assetIds.length > 1 ? `${assetIds.length} assets` : (citation == null ? void 0 : citation.description) ?? citationFallbackLabel(parsed.searchParams),
182
285
  id: id2,
183
286
  assetIds,
287
+ assetType: assetType ?? void 0,
288
+ citation,
184
289
  thumbnailUrl: assetTypeToThumb(assetType)
185
290
  };
186
291
  }
@@ -198,12 +303,16 @@ function parseMentionUrl(url) {
198
303
  thumbnailUrl = assetTypeToThumb(type === "folder" ? "folder" : null);
199
304
  }
200
305
  }
306
+ const reference = type === "asset" ? decodeAssetLink(u, decodeFromAssetMentionUri) : null;
201
307
  return {
202
308
  type,
203
309
  name,
204
310
  id,
205
311
  email: type === "person" ? u.searchParams.get("email") || void 0 : void 0,
206
312
  assetIds: type === "asset" && id ? [id] : void 0,
313
+ assetType: type === "asset" || type === "folder" ? u.searchParams.get("assetType") || ((_a = reference == null ? void 0 : reference.meta) == null ? void 0 : _a.assetType) || (type === "folder" ? "folder" : void 0) : void 0,
314
+ citation: reference ? citationOf(reference) : void 0,
315
+ sourceHref: reference && (reference.anchor || ((_b = reference.meta) == null ? void 0 : _b.excerpt)) ? encodeToAssetSpacesUrl(reference) : void 0,
207
316
  thumbnailUrl
208
317
  };
209
318
  } catch {
@@ -269,8 +378,10 @@ function parseMentionsInText(text) {
269
378
  type: parsed.type,
270
379
  personEmail: personEmailForMention(parsed),
271
380
  assetIds: parsed.assetIds,
381
+ assetType: parsed.assetType,
382
+ citation: parsed.citation,
272
383
  thumbnailUrl: parsed.thumbnailUrl,
273
- sourceHref: isSpacesMentionUrl(url) ? url : void 0
384
+ sourceHref: isSpacesMentionUrl(url) ? url : parsed.sourceHref
274
385
  },
275
386
  `mention-${match.index}`
276
387
  )