@athenaintel/react 0.40.2 → 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 (64) hide show
  1. package/README.md +61 -0
  2. package/dist/agent-runtime/src/subscription-wake-event.cjs +87 -2
  3. package/dist/agent-runtime/src/subscription-wake-event.cjs.map +1 -1
  4. package/dist/agent-runtime/src/subscription-wake-event.js +87 -2
  5. package/dist/agent-runtime/src/subscription-wake-event.js.map +1 -1
  6. package/dist/chat/athena-citation-links.cjs +23 -2
  7. package/dist/chat/athena-citation-links.cjs.map +1 -1
  8. package/dist/chat/athena-citation-links.d.ts +5 -0
  9. package/dist/chat/athena-citation-links.js +23 -2
  10. package/dist/chat/athena-citation-links.js.map +1 -1
  11. package/dist/chat-ui/index.d.ts +1 -1
  12. package/dist/chat-ui/mentions/mention-chip.cjs +118 -7
  13. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  14. package/dist/chat-ui/mentions/mention-chip.d.ts +29 -0
  15. package/dist/chat-ui/mentions/mention-chip.js +120 -9
  16. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  17. package/dist/chat-ui/rich-text/chat-message-body.cjs +2 -1
  18. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  19. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  20. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  21. package/dist/react/package.json.cjs +1 -1
  22. package/dist/react/package.json.js +1 -1
  23. package/dist/references/src/anchors/file-line-range.cjs +70 -0
  24. package/dist/references/src/anchors/file-line-range.cjs.map +1 -0
  25. package/dist/references/src/anchors/file-line-range.js +70 -0
  26. package/dist/references/src/anchors/file-line-range.js.map +1 -0
  27. package/dist/references/src/anchors/format.cjs +219 -0
  28. package/dist/references/src/anchors/format.cjs.map +1 -0
  29. package/dist/references/src/anchors/format.js +219 -0
  30. package/dist/references/src/anchors/format.js.map +1 -0
  31. package/dist/references/src/anchors/parse.cjs +408 -0
  32. package/dist/references/src/anchors/parse.cjs.map +1 -0
  33. package/dist/references/src/anchors/parse.js +408 -0
  34. package/dist/references/src/anchors/parse.js.map +1 -0
  35. package/dist/references/src/anchors/serialize.cjs +185 -0
  36. package/dist/references/src/anchors/serialize.cjs.map +1 -0
  37. package/dist/references/src/anchors/serialize.js +185 -0
  38. package/dist/references/src/anchors/serialize.js.map +1 -0
  39. package/dist/references/src/anchors/types.cjs +50 -0
  40. package/dist/references/src/anchors/types.cjs.map +1 -0
  41. package/dist/references/src/anchors/types.js +50 -0
  42. package/dist/references/src/anchors/types.js.map +1 -0
  43. package/dist/references/src/formats/uri/asset-mention-uri.cjs +29 -0
  44. package/dist/references/src/formats/uri/asset-mention-uri.cjs.map +1 -0
  45. package/dist/references/src/formats/uri/asset-mention-uri.js +29 -0
  46. package/dist/references/src/formats/uri/asset-mention-uri.js.map +1 -0
  47. package/dist/references/src/formats/uri/asset-spaces-url.cjs +64 -0
  48. package/dist/references/src/formats/uri/asset-spaces-url.cjs.map +1 -0
  49. package/dist/references/src/formats/uri/asset-spaces-url.js +64 -0
  50. package/dist/references/src/formats/uri/asset-spaces-url.js.map +1 -0
  51. package/dist/references/src/formats/uri/template-placeholder.cjs +9 -0
  52. package/dist/references/src/formats/uri/template-placeholder.cjs.map +1 -0
  53. package/dist/references/src/formats/uri/template-placeholder.js +9 -0
  54. package/dist/references/src/formats/uri/template-placeholder.js.map +1 -0
  55. package/dist/references/src/sheet-name.cjs +10 -0
  56. package/dist/references/src/sheet-name.cjs.map +1 -0
  57. package/dist/references/src/sheet-name.js +10 -0
  58. package/dist/references/src/sheet-name.js.map +1 -0
  59. package/dist/references/src/version-ref.cjs +61 -0
  60. package/dist/references/src/version-ref.cjs.map +1 -0
  61. package/dist/references/src/version-ref.js +61 -0
  62. package/dist/references/src/version-ref.js.map +1 -0
  63. package/dist/styles.css +1 -1
  64. package/package.json +3 -2
@@ -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
  )
@@ -1 +1 @@
1
- {"version":3,"file":"mention-chip.js","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":["id"],"mappings":"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"}
1
+ {"version":3,"file":"mention-chip.js","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":["id","reference"],"mappings":"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"}
@@ -100,7 +100,8 @@ function useChatLinkClick() {
100
100
  }
101
101
  const handled = handleLinkClick({
102
102
  href: anchor.getAttribute("href") ?? anchor.href,
103
- linkText: anchor.textContent,
103
+ // A citation chip's text ends with its place (`p.10`); the asset is named without it.
104
+ linkText: anchor.getAttribute("data-mention-name") ?? anchor.textContent,
104
105
  target: anchor.target,
105
106
  nativeEvent: event.nativeEvent
106
107
  });