@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,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const serialize = require("../references/src/anchors/serialize.cjs");
4
+ const assetSpacesUrl = require("../references/src/formats/uri/asset-spaces-url.cjs");
3
5
  const react = require("react");
4
6
  const assetPanelStore = require("../assets/asset-panel-store.cjs");
5
7
  const AthenaContext = require("../provider/AthenaContext.cjs");
@@ -17,6 +19,13 @@ const ASSET_TYPE_ALIASES = {
17
19
  word_document: "document"
18
20
  };
19
21
  const isAthenaSpacesPath = (url) => url.pathname.replace(/\/+$/, "") === "/dashboard/spaces";
22
+ const NON_EMBEDDABLE_ASSET_TYPES = /* @__PURE__ */ new Set(["catalog"]);
23
+ const NON_EMBEDDABLE_ANCHOR_TYPES = /* @__PURE__ */ new Set(["file_line_range"]);
24
+ const isEmbeddableCitation = (url) => {
25
+ const rawType = (url.searchParams.get("asset_type") ?? url.searchParams.get("type") ?? "").trim().replace(/^athena\//, "").toLowerCase();
26
+ const anchorType = url.searchParams.get("anchor") ?? "";
27
+ return !NON_EMBEDDABLE_ASSET_TYPES.has(rawType) && !NON_EMBEDDABLE_ANCHOR_TYPES.has(anchorType);
28
+ };
20
29
  const getFiniteNumber = (url, key) => {
21
30
  const value = url.searchParams.get(key);
22
31
  if (value === null || value.trim() === "") return null;
@@ -27,6 +36,13 @@ const getNonEmptyString = (url, key) => {
27
36
  const value = url.searchParams.get(key);
28
37
  return value === null || value === "" ? null : value;
29
38
  };
39
+ const readSharedAnchor = (url) => {
40
+ try {
41
+ return assetSpacesUrl.decodeFromAssetSpacesUrl(url).anchor ?? null;
42
+ } catch {
43
+ return null;
44
+ }
45
+ };
30
46
  const parseCitationReference = ({
31
47
  url,
32
48
  assetId
@@ -117,6 +133,10 @@ const parseCitationReference = ({
117
133
  break;
118
134
  }
119
135
  }
136
+ if (reference.anchor === void 0) {
137
+ const anchor = readSharedAnchor(url);
138
+ if (anchor) reference.anchor = serialize.serializeAnchorToParams(anchor).toString();
139
+ }
120
140
  const excerpt = url.searchParams.get("excerpt");
121
141
  if (excerpt) reference.meta = { excerpt };
122
142
  return reference;
@@ -210,7 +230,8 @@ const parseAthenaCitationLink = ({
210
230
  assetId,
211
231
  assetIds,
212
232
  assetType: normalizeAssetType(url.searchParams.get("asset_type") ?? url.searchParams.get("type")),
213
- reference: parseCitationReference({ url, assetId })
233
+ reference: parseCitationReference({ url, assetId }),
234
+ embeddable: isEmbeddableCitation(url)
214
235
  };
215
236
  };
216
237
  const isAthenaCitationUrl = ({
@@ -262,7 +283,7 @@ const useAthenaLinkClickHandler = () => {
262
283
  assetIds: citationLink.assetIds,
263
284
  assetType: citationLink.assetType
264
285
  } : null;
265
- const openInAssetPanel = citationLink ? () => openAsset(citationLink.assetId, {
286
+ const openInAssetPanel = (citationLink == null ? void 0 : citationLink.embeddable) ? () => openAsset(citationLink.assetId, {
266
287
  name: trimmedLinkText ?? void 0,
267
288
  type: citationLink.assetType ?? void 0,
268
289
  // Carry the citation target so the panel's iframe navigates to
@@ -1 +1 @@
1
- {"version":3,"file":"athena-citation-links.cjs","sources":["../../src/chat/athena-citation-links.ts"],"sourcesContent":["import { useCallback, useContext } from 'react';\nimport type { AthenaAssetCitationReference } from '../assets/asset-embed-messages';\nimport { type AssetType, useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AthenaContext } from '../provider/AthenaContext';\nimport type {\n AthenaCitationLinkMetadata,\n AthenaCitationLinkClickContext,\n AthenaCitationLinkHandleInput,\n AthenaCitationLinkHandler,\n AthenaLinkClickContext,\n AthenaLinkClickHandler,\n AthenaLinkClickHandleInput,\n AthenaLinkKind,\n} from '../provider/citation-links';\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_SUFFIXES = ['.app.athenaintel.com', '.staging-app.athenaintel.com'];\n\nconst ASSET_TYPE_ALIASES: Record<string, AssetType> = {\n document: 'document',\n notebook: 'notebook',\n pdf: 'document',\n powerpoint: 'presentation',\n presentation: 'presentation',\n sheet: 'spreadsheet',\n spreadsheet: 'spreadsheet',\n word_document: 'document',\n};\n\nconst isAthenaSpacesPath = (url: URL): boolean => url.pathname.replace(/\\/+$/, '') === '/dashboard/spaces';\n\nexport interface ParsedAthenaCitationLink {\n url: URL;\n assetId: string;\n assetIds: string[];\n assetType: AssetType | null;\n /** Complete target that can be passed directly to AthenaAssetEmbed.reference. */\n reference: AthenaAssetCitationReference;\n}\n\nconst getFiniteNumber = (url: URL, key: string): number | null => {\n const value = url.searchParams.get(key);\n if (value === null || value.trim() === '') return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n};\n\nconst getNonEmptyString = (url: URL, key: string): string | null => {\n const value = url.searchParams.get(key);\n return value === null || value === '' ? null : value;\n};\n\nconst parseCitationReference = ({\n url,\n assetId,\n}: {\n url: URL;\n assetId: string;\n}): AthenaAssetCitationReference => {\n const reference: AthenaAssetCitationReference = { id: assetId };\n const anchorType = url.searchParams.get('anchor');\n const page = getFiniteNumber(url, 'page');\n\n switch (anchorType) {\n case 'page':\n if (page !== null) reference.anchor = { type: anchorType, page };\n break;\n case 'page_range': {\n const startPage = getFiniteNumber(url, 'start_page');\n const endPage = getFiniteNumber(url, 'end_page');\n if (startPage !== null && endPage !== null) {\n reference.anchor = { type: anchorType, startPage, endPage };\n }\n break;\n }\n case 'page_rect': {\n const x = getFiniteNumber(url, 'x');\n const y = getFiniteNumber(url, 'y');\n const width = getFiniteNumber(url, 'width');\n const height = getFiniteNumber(url, 'height');\n if (page !== null && x !== null && y !== null && width !== null && height !== null) {\n const unit = url.searchParams.get('unit');\n reference.anchor = {\n type: anchorType,\n page,\n x,\n y,\n width,\n height,\n ...(unit === 'percent' || unit === 'point' ? { unit } : {}),\n };\n }\n break;\n }\n case 'page_text': {\n const text = url.searchParams.get('text');\n if (page !== null && text) {\n // Optional context that disambiguates a repeated phrase, and the hash\n // of the cited unit's normalized text (Clio C-23).\n const prefix = getNonEmptyString(url, 'prefix');\n const suffix = getNonEmptyString(url, 'suffix');\n const hash = getNonEmptyString(url, 'hash');\n reference.anchor = {\n type: anchorType,\n page,\n text,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n ...(hash ? { hash } : {}),\n };\n }\n break;\n }\n // Sheet anchors: the embed's spreadsheet view navigates + flashes the\n // target range (Olympus `useSheetAnchors`). `sheet_id` is the engine's\n // numeric sheet id, NOT a tab index.\n case 'sheet_cell': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const cell = url.searchParams.get('cell');\n if (sheetId !== null && cell) reference.anchor = { type: anchorType, sheetId, cell };\n break;\n }\n case 'sheet_range': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const range = url.searchParams.get('range');\n if (sheetId !== null && range) reference.anchor = { type: anchorType, sheetId, range };\n break;\n }\n case 'sheet_tab': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n if (sheetId !== null) reference.anchor = { type: anchorType, sheetId };\n break;\n }\n case 'sheet_table': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const tableName = url.searchParams.get('table_name');\n const fallbackRange = url.searchParams.get('fallback_range');\n if (sheetId !== null && tableName) {\n reference.anchor = {\n type: anchorType,\n sheetId,\n tableName,\n ...(fallbackRange ? { fallbackRange } : {}),\n };\n }\n break;\n }\n }\n\n const excerpt = url.searchParams.get('excerpt');\n if (excerpt) reference.meta = { excerpt };\n return reference;\n};\n\nconst normalizeAssetType = (value: string | null): AssetType | null => {\n if (!value) {\n return null;\n }\n\n const normalizedValue = value.trim().replace(/^athena\\//, '').toLowerCase().replace(/-/g, '_');\n return ASSET_TYPE_ALIASES[normalizedValue] ?? null;\n};\n\nconst getOrigin = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nconst getHostname = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).hostname.toLowerCase();\n } catch {\n return null;\n }\n};\n\nconst matchesHostnameOrSubdomain = ({\n hostname,\n candidateHostname,\n}: {\n hostname: string;\n candidateHostname: string;\n}): boolean => hostname === candidateHostname || hostname.endsWith(`.${candidateHostname}`);\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst parseUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): URL | null => {\n const fallbackBase = appUrl ?? getCurrentOrigin() ?? 'https://app.athenaintel.com';\n\n try {\n return new URL(href, fallbackBase);\n } catch {\n return null;\n }\n};\n\nconst isAthenaAppUrl = ({\n url,\n appUrl,\n}: {\n url: URL;\n appUrl?: string | null;\n}): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (\n ATHENA_APP_HOSTNAMES.has(hostname)\n || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)\n || ATHENA_WORKSPACE_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix))\n ) {\n return true;\n }\n\n const configuredHostname = getHostname(appUrl);\n if (configuredHostname && matchesHostnameOrSubdomain({ hostname, candidateHostname: configuredHostname })) {\n return true;\n }\n\n const configuredOrigin = getOrigin(appUrl);\n if (configuredOrigin && url.origin === configuredOrigin) {\n return true;\n }\n\n // Fallback: treat same-origin `/dashboard/spaces?asset_ids=...` links as Athena\n // citations so local dev and private-cloud embeds still work without appUrl.\n // Consumers can disable the default interception with\n // linkClicks={{ interceptAthenaCitations: false }} or the legacy citationLinks alias.\n const currentOrigin = getCurrentOrigin();\n return !!currentOrigin && url.origin === currentOrigin;\n};\n\nexport const parseAthenaCitationLink = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): ParsedAthenaCitationLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url || !isAthenaAppUrl({ url, appUrl }) || !isAthenaSpacesPath(url)) {\n return null;\n }\n\n const assetIdsParam = url.searchParams.get('asset_ids');\n if (!assetIdsParam) {\n return null;\n }\n\n const assetIds = assetIdsParam\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0);\n const assetId = assetIds[0];\n\n if (!assetId) {\n return null;\n }\n\n return {\n url,\n assetId,\n assetIds,\n assetType: normalizeAssetType(url.searchParams.get('asset_type') ?? url.searchParams.get('type')),\n reference: parseCitationReference({ url, assetId }),\n };\n};\n\nexport const isAthenaCitationUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): boolean => parseAthenaCitationLink({ href, appUrl }) !== null;\n\ninterface ResolvedAthenaLink {\n url: URL;\n kind: AthenaLinkKind;\n citation: ParsedAthenaCitationLink | null;\n}\n\nconst resolveAthenaLink = ({\n href,\n appUrl,\n}: AthenaLinkClickHandleInput & {\n appUrl?: string | null;\n}): ResolvedAthenaLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url) {\n return null;\n }\n\n const citation = parseAthenaCitationLink({ href, appUrl });\n if (citation) {\n return {\n url: citation.url,\n kind: 'athena-citation',\n citation,\n };\n }\n\n return {\n url,\n kind: isAthenaAppUrl({ url, appUrl }) ? 'athena-app' : 'external',\n citation: null,\n };\n};\n\nexport const useAthenaLinkClickHandler = (): AthenaLinkClickHandler => {\n const openAsset = useAssetPanelStore((state) => state.openAsset);\n // A host that opens assets its own way (`AthenaProvider onOpenAsset`) counts as a panel: the\n // citation goes to it instead of to the browser.\n const isAssetPanelAvailable = useAssetPanelStore(\n (state) => state.assetPanelHostCount > 0 || state.assetOpeners.length > 0,\n );\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n\n return useCallback<AthenaLinkClickHandler>(\n ({ href, linkText, target, nativeEvent }: AthenaLinkClickHandleInput) => {\n const resolvedLink = resolveAthenaLink({ href, appUrl });\n if (!resolvedLink) {\n return false;\n }\n\n const linkClicks = athenaConfig?.linkClicks;\n const citationLinks = athenaConfig?.citationLinks;\n const trimmedLinkText = linkText?.trim() || null;\n const citationLink = resolvedLink.citation;\n const citationMetadata: AthenaCitationLinkMetadata | null = citationLink\n ? {\n assetId: citationLink.assetId,\n assetIds: citationLink.assetIds,\n assetType: citationLink.assetType,\n }\n : null;\n const openInAssetPanel = citationLink\n ? () =>\n openAsset(citationLink.assetId, {\n name: trimmedLinkText ?? undefined,\n type: citationLink.assetType ?? undefined,\n // Carry the citation target so the panel's iframe navigates to\n // the cited range/page instead of opening at the top.\n reference: citationLink.reference,\n // Reading references is one preview surface: a citation to a\n // different asset swaps the active tab rather than stacking a\n // tab per cited document.\n replaceActiveTab: true,\n // A host's own opener that opens it on the web keeps the link as written.\n href: resolvedLink.url.toString(),\n })\n : undefined;\n const context: AthenaLinkClickContext = {\n href,\n url: resolvedLink.url,\n target: target ?? null,\n linkText: trimmedLinkText,\n isAssetPanelAvailable,\n kind: resolvedLink.kind,\n citation: citationMetadata,\n nativeEvent: nativeEvent ?? null,\n openInAssetPanel,\n openInBrowser: (nextTarget) => {\n window.open(resolvedLink.url.toString(), nextTarget ?? target ?? '_blank')?.focus();\n },\n };\n\n if (linkClicks?.onClick?.(context) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if (!citationLink || !openInAssetPanel || !citationMetadata) {\n return false;\n }\n\n const citationContext: AthenaCitationLinkClickContext = {\n ...context,\n kind: 'athena-citation',\n citation: citationMetadata,\n assetId: citationMetadata.assetId,\n assetIds: citationMetadata.assetIds,\n assetType: citationMetadata.assetType,\n openInAssetPanel,\n };\n\n if (citationLinks?.onClick?.(citationContext) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if ((linkClicks?.interceptAthenaCitations ?? citationLinks?.interceptAthenaCitations) === false) {\n return false;\n }\n\n if (!isAssetPanelAvailable) {\n return false;\n }\n\n // Nothing showed it (the host's opener declined and no panel is mounted): the link opens as\n // a link.\n if (!citationContext.openInAssetPanel()) {\n return false;\n }\n nativeEvent?.preventDefault();\n return true;\n },\n [appUrl, athenaConfig, isAssetPanelAvailable, openAsset],\n );\n};\n\nexport const useAthenaCitationLinkHandler = (): AthenaCitationLinkHandler => {\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n const handleLinkClick = useAthenaLinkClickHandler();\n\n return useCallback<AthenaCitationLinkHandler>(\n (input: AthenaCitationLinkHandleInput) => {\n if (!parseAthenaCitationLink({ href: input.href, appUrl })) {\n return false;\n }\n\n return handleLinkClick(input);\n },\n [appUrl, handleLinkClick],\n );\n};\n"],"names":["useAssetPanelStore","useContext","AthenaContext","useCallback","_a"],"mappings":"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"}
1
+ {"version":3,"file":"athena-citation-links.cjs","sources":["../../src/chat/athena-citation-links.ts"],"sourcesContent":["import { decodeFromAssetSpacesUrl, serializeAnchorToParams } from '@athenaintel/references/asset-links';\nimport { useCallback, useContext } from 'react';\nimport type { AthenaAssetCitationReference } from '../assets/asset-embed-messages';\nimport { type AssetType, useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AthenaContext } from '../provider/AthenaContext';\nimport type {\n AthenaCitationLinkMetadata,\n AthenaCitationLinkClickContext,\n AthenaCitationLinkHandleInput,\n AthenaCitationLinkHandler,\n AthenaLinkClickContext,\n AthenaLinkClickHandler,\n AthenaLinkClickHandleInput,\n AthenaLinkKind,\n} from '../provider/citation-links';\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_SUFFIXES = ['.app.athenaintel.com', '.staging-app.athenaintel.com'];\n\nconst ASSET_TYPE_ALIASES: Record<string, AssetType> = {\n document: 'document',\n notebook: 'notebook',\n pdf: 'document',\n powerpoint: 'presentation',\n presentation: 'presentation',\n sheet: 'spreadsheet',\n spreadsheet: 'spreadsheet',\n word_document: 'document',\n};\n\nconst isAthenaSpacesPath = (url: URL): boolean => url.pathname.replace(/\\/+$/, '') === '/dashboard/spaces';\n\n/**\n * Asset types no embed viewer renders: agora refuses their embed token (422,\n * `agora/agora/web/api/embed/embed_viewer_asset_types.json`), so the asset\n * panel could only show an error for them.\n */\nconst NON_EMBEDDABLE_ASSET_TYPES: ReadonlySet<string> = new Set(['catalog']);\n\n/**\n * Anchors only a non-embeddable asset holds: a repository file's lines live in\n * a GitHub catalog (`ASSET_ANCHOR_REGISTRY`), which Athena on the web opens at\n * the file with the lines highlighted. Read from the anchor too, so a citation\n * whose `type=` names the wrong type still opens where it can be shown.\n */\nconst NON_EMBEDDABLE_ANCHOR_TYPES: ReadonlySet<string> = new Set(['file_line_range']);\n\n/** Whether the asset panel can show the cited asset, or only Athena on the web can. */\nconst isEmbeddableCitation = (url: URL): boolean => {\n const rawType = (url.searchParams.get('asset_type') ?? url.searchParams.get('type') ?? '')\n .trim()\n .replace(/^athena\\//, '')\n .toLowerCase();\n const anchorType = url.searchParams.get('anchor') ?? '';\n return !NON_EMBEDDABLE_ASSET_TYPES.has(rawType) && !NON_EMBEDDABLE_ANCHOR_TYPES.has(anchorType);\n};\n\nexport interface ParsedAthenaCitationLink {\n url: URL;\n assetId: string;\n assetIds: string[];\n assetType: AssetType | null;\n /** Complete target that can be passed directly to AthenaAssetEmbed.reference. */\n reference: AthenaAssetCitationReference;\n /**\n * Whether the asset panel can show the cited asset. A citation it cannot (a repository file's\n * lines, any catalog) opens as the link it is, in Athena on the web.\n */\n embeddable: boolean;\n}\n\nconst getFiniteNumber = (url: URL, key: string): number | null => {\n const value = url.searchParams.get(key);\n if (value === null || value.trim() === '') return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n};\n\nconst getNonEmptyString = (url: URL, key: string): string | null => {\n const value = url.searchParams.get(key);\n return value === null || value === '' ? null : value;\n};\n\n/** The link's anchor as the shared reference parser reads it, or null when it names none it can read. */\nconst readSharedAnchor = (url: URL) => {\n try {\n return decodeFromAssetSpacesUrl(url).anchor ?? null;\n } catch {\n return null;\n }\n};\n\nconst parseCitationReference = ({\n url,\n assetId,\n}: {\n url: URL;\n assetId: string;\n}): AthenaAssetCitationReference => {\n const reference: AthenaAssetCitationReference = { id: assetId };\n const anchorType = url.searchParams.get('anchor');\n const page = getFiniteNumber(url, 'page');\n\n switch (anchorType) {\n case 'page':\n if (page !== null) reference.anchor = { type: anchorType, page };\n break;\n case 'page_range': {\n const startPage = getFiniteNumber(url, 'start_page');\n const endPage = getFiniteNumber(url, 'end_page');\n if (startPage !== null && endPage !== null) {\n reference.anchor = { type: anchorType, startPage, endPage };\n }\n break;\n }\n case 'page_rect': {\n const x = getFiniteNumber(url, 'x');\n const y = getFiniteNumber(url, 'y');\n const width = getFiniteNumber(url, 'width');\n const height = getFiniteNumber(url, 'height');\n if (page !== null && x !== null && y !== null && width !== null && height !== null) {\n const unit = url.searchParams.get('unit');\n reference.anchor = {\n type: anchorType,\n page,\n x,\n y,\n width,\n height,\n ...(unit === 'percent' || unit === 'point' ? { unit } : {}),\n };\n }\n break;\n }\n case 'page_text': {\n const text = url.searchParams.get('text');\n if (page !== null && text) {\n // Optional context that disambiguates a repeated phrase, and the hash\n // of the cited unit's normalized text (Clio C-23).\n const prefix = getNonEmptyString(url, 'prefix');\n const suffix = getNonEmptyString(url, 'suffix');\n const hash = getNonEmptyString(url, 'hash');\n reference.anchor = {\n type: anchorType,\n page,\n text,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n ...(hash ? { hash } : {}),\n };\n }\n break;\n }\n // Sheet anchors: the embed's spreadsheet view navigates + flashes the\n // target range (Olympus `useSheetAnchors`). `sheet_id` is the engine's\n // numeric sheet id, NOT a tab index.\n case 'sheet_cell': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const cell = url.searchParams.get('cell');\n if (sheetId !== null && cell) reference.anchor = { type: anchorType, sheetId, cell };\n break;\n }\n case 'sheet_range': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const range = url.searchParams.get('range');\n if (sheetId !== null && range) reference.anchor = { type: anchorType, sheetId, range };\n break;\n }\n case 'sheet_tab': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n if (sheetId !== null) reference.anchor = { type: anchorType, sheetId };\n break;\n }\n case 'sheet_table': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const tableName = url.searchParams.get('table_name');\n const fallbackRange = url.searchParams.get('fallback_range');\n if (sheetId !== null && tableName) {\n reference.anchor = {\n type: anchorType,\n sheetId,\n tableName,\n ...(fallbackRange ? { fallbackRange } : {}),\n };\n }\n break;\n }\n }\n\n // Every other place (a slide, a time, a document block, a transcript message, …) goes to the\n // embed as the canonical query string `@athenaintel/references` writes, which the embed reads\n // back with the same parser, so the panel opens at it instead of at the top.\n if (reference.anchor === undefined) {\n const anchor = readSharedAnchor(url);\n if (anchor) reference.anchor = serializeAnchorToParams(anchor).toString();\n }\n\n const excerpt = url.searchParams.get('excerpt');\n if (excerpt) reference.meta = { excerpt };\n return reference;\n};\n\nconst normalizeAssetType = (value: string | null): AssetType | null => {\n if (!value) {\n return null;\n }\n\n const normalizedValue = value.trim().replace(/^athena\\//, '').toLowerCase().replace(/-/g, '_');\n return ASSET_TYPE_ALIASES[normalizedValue] ?? null;\n};\n\nconst getOrigin = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nconst getHostname = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).hostname.toLowerCase();\n } catch {\n return null;\n }\n};\n\nconst matchesHostnameOrSubdomain = ({\n hostname,\n candidateHostname,\n}: {\n hostname: string;\n candidateHostname: string;\n}): boolean => hostname === candidateHostname || hostname.endsWith(`.${candidateHostname}`);\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst parseUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): URL | null => {\n const fallbackBase = appUrl ?? getCurrentOrigin() ?? 'https://app.athenaintel.com';\n\n try {\n return new URL(href, fallbackBase);\n } catch {\n return null;\n }\n};\n\nconst isAthenaAppUrl = ({\n url,\n appUrl,\n}: {\n url: URL;\n appUrl?: string | null;\n}): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (\n ATHENA_APP_HOSTNAMES.has(hostname)\n || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)\n || ATHENA_WORKSPACE_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix))\n ) {\n return true;\n }\n\n const configuredHostname = getHostname(appUrl);\n if (configuredHostname && matchesHostnameOrSubdomain({ hostname, candidateHostname: configuredHostname })) {\n return true;\n }\n\n const configuredOrigin = getOrigin(appUrl);\n if (configuredOrigin && url.origin === configuredOrigin) {\n return true;\n }\n\n // Fallback: treat same-origin `/dashboard/spaces?asset_ids=...` links as Athena\n // citations so local dev and private-cloud embeds still work without appUrl.\n // Consumers can disable the default interception with\n // linkClicks={{ interceptAthenaCitations: false }} or the legacy citationLinks alias.\n const currentOrigin = getCurrentOrigin();\n return !!currentOrigin && url.origin === currentOrigin;\n};\n\nexport const parseAthenaCitationLink = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): ParsedAthenaCitationLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url || !isAthenaAppUrl({ url, appUrl }) || !isAthenaSpacesPath(url)) {\n return null;\n }\n\n const assetIdsParam = url.searchParams.get('asset_ids');\n if (!assetIdsParam) {\n return null;\n }\n\n const assetIds = assetIdsParam\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0);\n const assetId = assetIds[0];\n\n if (!assetId) {\n return null;\n }\n\n return {\n url,\n assetId,\n assetIds,\n assetType: normalizeAssetType(url.searchParams.get('asset_type') ?? url.searchParams.get('type')),\n reference: parseCitationReference({ url, assetId }),\n embeddable: isEmbeddableCitation(url),\n };\n};\n\nexport const isAthenaCitationUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): boolean => parseAthenaCitationLink({ href, appUrl }) !== null;\n\ninterface ResolvedAthenaLink {\n url: URL;\n kind: AthenaLinkKind;\n citation: ParsedAthenaCitationLink | null;\n}\n\nconst resolveAthenaLink = ({\n href,\n appUrl,\n}: AthenaLinkClickHandleInput & {\n appUrl?: string | null;\n}): ResolvedAthenaLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url) {\n return null;\n }\n\n const citation = parseAthenaCitationLink({ href, appUrl });\n if (citation) {\n return {\n url: citation.url,\n kind: 'athena-citation',\n citation,\n };\n }\n\n return {\n url,\n kind: isAthenaAppUrl({ url, appUrl }) ? 'athena-app' : 'external',\n citation: null,\n };\n};\n\nexport const useAthenaLinkClickHandler = (): AthenaLinkClickHandler => {\n const openAsset = useAssetPanelStore((state) => state.openAsset);\n // A host that opens assets its own way (`AthenaProvider onOpenAsset`) counts as a panel: the\n // citation goes to it instead of to the browser.\n const isAssetPanelAvailable = useAssetPanelStore(\n (state) => state.assetPanelHostCount > 0 || state.assetOpeners.length > 0,\n );\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n\n return useCallback<AthenaLinkClickHandler>(\n ({ href, linkText, target, nativeEvent }: AthenaLinkClickHandleInput) => {\n const resolvedLink = resolveAthenaLink({ href, appUrl });\n if (!resolvedLink) {\n return false;\n }\n\n const linkClicks = athenaConfig?.linkClicks;\n const citationLinks = athenaConfig?.citationLinks;\n const trimmedLinkText = linkText?.trim() || null;\n const citationLink = resolvedLink.citation;\n const citationMetadata: AthenaCitationLinkMetadata | null = citationLink\n ? {\n assetId: citationLink.assetId,\n assetIds: citationLink.assetIds,\n assetType: citationLink.assetType,\n }\n : null;\n // A citation the panel cannot show (a catalog's file lines) is left to the link itself.\n const openInAssetPanel = citationLink?.embeddable\n ? () =>\n openAsset(citationLink.assetId, {\n name: trimmedLinkText ?? undefined,\n type: citationLink.assetType ?? undefined,\n // Carry the citation target so the panel's iframe navigates to\n // the cited range/page instead of opening at the top.\n reference: citationLink.reference,\n // Reading references is one preview surface: a citation to a\n // different asset swaps the active tab rather than stacking a\n // tab per cited document.\n replaceActiveTab: true,\n // A host's own opener that opens it on the web keeps the link as written.\n href: resolvedLink.url.toString(),\n })\n : undefined;\n const context: AthenaLinkClickContext = {\n href,\n url: resolvedLink.url,\n target: target ?? null,\n linkText: trimmedLinkText,\n isAssetPanelAvailable,\n kind: resolvedLink.kind,\n citation: citationMetadata,\n nativeEvent: nativeEvent ?? null,\n openInAssetPanel,\n openInBrowser: (nextTarget) => {\n window.open(resolvedLink.url.toString(), nextTarget ?? target ?? '_blank')?.focus();\n },\n };\n\n if (linkClicks?.onClick?.(context) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if (!citationLink || !openInAssetPanel || !citationMetadata) {\n return false;\n }\n\n const citationContext: AthenaCitationLinkClickContext = {\n ...context,\n kind: 'athena-citation',\n citation: citationMetadata,\n assetId: citationMetadata.assetId,\n assetIds: citationMetadata.assetIds,\n assetType: citationMetadata.assetType,\n openInAssetPanel,\n };\n\n if (citationLinks?.onClick?.(citationContext) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if ((linkClicks?.interceptAthenaCitations ?? citationLinks?.interceptAthenaCitations) === false) {\n return false;\n }\n\n if (!isAssetPanelAvailable) {\n return false;\n }\n\n // Nothing showed it (the host's opener declined and no panel is mounted): the link opens as\n // a link.\n if (!citationContext.openInAssetPanel()) {\n return false;\n }\n nativeEvent?.preventDefault();\n return true;\n },\n [appUrl, athenaConfig, isAssetPanelAvailable, openAsset],\n );\n};\n\nexport const useAthenaCitationLinkHandler = (): AthenaCitationLinkHandler => {\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n const handleLinkClick = useAthenaLinkClickHandler();\n\n return useCallback<AthenaCitationLinkHandler>(\n (input: AthenaCitationLinkHandleInput) => {\n if (!parseAthenaCitationLink({ href: input.href, appUrl })) {\n return false;\n }\n\n return handleLinkClick(input);\n },\n [appUrl, handleLinkClick],\n );\n};\n"],"names":["decodeFromAssetSpacesUrl","serializeAnchorToParams","useAssetPanelStore","useContext","AthenaContext","useCallback","_a"],"mappings":"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"}
@@ -8,6 +8,11 @@ export interface ParsedAthenaCitationLink {
8
8
  assetType: AssetType | null;
9
9
  /** Complete target that can be passed directly to AthenaAssetEmbed.reference. */
10
10
  reference: AthenaAssetCitationReference;
11
+ /**
12
+ * Whether the asset panel can show the cited asset. A citation it cannot (a repository file's
13
+ * lines, any catalog) opens as the link it is, in Athena on the web.
14
+ */
15
+ embeddable: boolean;
11
16
  }
12
17
  export declare const parseAthenaCitationLink: ({ href, appUrl, }: {
13
18
  href: string;
@@ -1,3 +1,5 @@
1
+ import { serializeAnchorToParams } from "../references/src/anchors/serialize.js";
2
+ import { decodeFromAssetSpacesUrl } from "../references/src/formats/uri/asset-spaces-url.js";
1
3
  import { useContext, useCallback } from "react";
2
4
  import { useAssetPanelStore } from "../assets/asset-panel-store.js";
3
5
  import { AthenaContext } from "../provider/AthenaContext.js";
@@ -15,6 +17,13 @@ const ASSET_TYPE_ALIASES = {
15
17
  word_document: "document"
16
18
  };
17
19
  const isAthenaSpacesPath = (url) => url.pathname.replace(/\/+$/, "") === "/dashboard/spaces";
20
+ const NON_EMBEDDABLE_ASSET_TYPES = /* @__PURE__ */ new Set(["catalog"]);
21
+ const NON_EMBEDDABLE_ANCHOR_TYPES = /* @__PURE__ */ new Set(["file_line_range"]);
22
+ const isEmbeddableCitation = (url) => {
23
+ const rawType = (url.searchParams.get("asset_type") ?? url.searchParams.get("type") ?? "").trim().replace(/^athena\//, "").toLowerCase();
24
+ const anchorType = url.searchParams.get("anchor") ?? "";
25
+ return !NON_EMBEDDABLE_ASSET_TYPES.has(rawType) && !NON_EMBEDDABLE_ANCHOR_TYPES.has(anchorType);
26
+ };
18
27
  const getFiniteNumber = (url, key) => {
19
28
  const value = url.searchParams.get(key);
20
29
  if (value === null || value.trim() === "") return null;
@@ -25,6 +34,13 @@ const getNonEmptyString = (url, key) => {
25
34
  const value = url.searchParams.get(key);
26
35
  return value === null || value === "" ? null : value;
27
36
  };
37
+ const readSharedAnchor = (url) => {
38
+ try {
39
+ return decodeFromAssetSpacesUrl(url).anchor ?? null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ };
28
44
  const parseCitationReference = ({
29
45
  url,
30
46
  assetId
@@ -115,6 +131,10 @@ const parseCitationReference = ({
115
131
  break;
116
132
  }
117
133
  }
134
+ if (reference.anchor === void 0) {
135
+ const anchor = readSharedAnchor(url);
136
+ if (anchor) reference.anchor = serializeAnchorToParams(anchor).toString();
137
+ }
118
138
  const excerpt = url.searchParams.get("excerpt");
119
139
  if (excerpt) reference.meta = { excerpt };
120
140
  return reference;
@@ -208,7 +228,8 @@ const parseAthenaCitationLink = ({
208
228
  assetId,
209
229
  assetIds,
210
230
  assetType: normalizeAssetType(url.searchParams.get("asset_type") ?? url.searchParams.get("type")),
211
- reference: parseCitationReference({ url, assetId })
231
+ reference: parseCitationReference({ url, assetId }),
232
+ embeddable: isEmbeddableCitation(url)
212
233
  };
213
234
  };
214
235
  const isAthenaCitationUrl = ({
@@ -260,7 +281,7 @@ const useAthenaLinkClickHandler = () => {
260
281
  assetIds: citationLink.assetIds,
261
282
  assetType: citationLink.assetType
262
283
  } : null;
263
- const openInAssetPanel = citationLink ? () => openAsset(citationLink.assetId, {
284
+ const openInAssetPanel = (citationLink == null ? void 0 : citationLink.embeddable) ? () => openAsset(citationLink.assetId, {
264
285
  name: trimmedLinkText ?? void 0,
265
286
  type: citationLink.assetType ?? void 0,
266
287
  // Carry the citation target so the panel's iframe navigates to
@@ -1 +1 @@
1
- {"version":3,"file":"athena-citation-links.js","sources":["../../src/chat/athena-citation-links.ts"],"sourcesContent":["import { useCallback, useContext } from 'react';\nimport type { AthenaAssetCitationReference } from '../assets/asset-embed-messages';\nimport { type AssetType, useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AthenaContext } from '../provider/AthenaContext';\nimport type {\n AthenaCitationLinkMetadata,\n AthenaCitationLinkClickContext,\n AthenaCitationLinkHandleInput,\n AthenaCitationLinkHandler,\n AthenaLinkClickContext,\n AthenaLinkClickHandler,\n AthenaLinkClickHandleInput,\n AthenaLinkKind,\n} from '../provider/citation-links';\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_SUFFIXES = ['.app.athenaintel.com', '.staging-app.athenaintel.com'];\n\nconst ASSET_TYPE_ALIASES: Record<string, AssetType> = {\n document: 'document',\n notebook: 'notebook',\n pdf: 'document',\n powerpoint: 'presentation',\n presentation: 'presentation',\n sheet: 'spreadsheet',\n spreadsheet: 'spreadsheet',\n word_document: 'document',\n};\n\nconst isAthenaSpacesPath = (url: URL): boolean => url.pathname.replace(/\\/+$/, '') === '/dashboard/spaces';\n\nexport interface ParsedAthenaCitationLink {\n url: URL;\n assetId: string;\n assetIds: string[];\n assetType: AssetType | null;\n /** Complete target that can be passed directly to AthenaAssetEmbed.reference. */\n reference: AthenaAssetCitationReference;\n}\n\nconst getFiniteNumber = (url: URL, key: string): number | null => {\n const value = url.searchParams.get(key);\n if (value === null || value.trim() === '') return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n};\n\nconst getNonEmptyString = (url: URL, key: string): string | null => {\n const value = url.searchParams.get(key);\n return value === null || value === '' ? null : value;\n};\n\nconst parseCitationReference = ({\n url,\n assetId,\n}: {\n url: URL;\n assetId: string;\n}): AthenaAssetCitationReference => {\n const reference: AthenaAssetCitationReference = { id: assetId };\n const anchorType = url.searchParams.get('anchor');\n const page = getFiniteNumber(url, 'page');\n\n switch (anchorType) {\n case 'page':\n if (page !== null) reference.anchor = { type: anchorType, page };\n break;\n case 'page_range': {\n const startPage = getFiniteNumber(url, 'start_page');\n const endPage = getFiniteNumber(url, 'end_page');\n if (startPage !== null && endPage !== null) {\n reference.anchor = { type: anchorType, startPage, endPage };\n }\n break;\n }\n case 'page_rect': {\n const x = getFiniteNumber(url, 'x');\n const y = getFiniteNumber(url, 'y');\n const width = getFiniteNumber(url, 'width');\n const height = getFiniteNumber(url, 'height');\n if (page !== null && x !== null && y !== null && width !== null && height !== null) {\n const unit = url.searchParams.get('unit');\n reference.anchor = {\n type: anchorType,\n page,\n x,\n y,\n width,\n height,\n ...(unit === 'percent' || unit === 'point' ? { unit } : {}),\n };\n }\n break;\n }\n case 'page_text': {\n const text = url.searchParams.get('text');\n if (page !== null && text) {\n // Optional context that disambiguates a repeated phrase, and the hash\n // of the cited unit's normalized text (Clio C-23).\n const prefix = getNonEmptyString(url, 'prefix');\n const suffix = getNonEmptyString(url, 'suffix');\n const hash = getNonEmptyString(url, 'hash');\n reference.anchor = {\n type: anchorType,\n page,\n text,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n ...(hash ? { hash } : {}),\n };\n }\n break;\n }\n // Sheet anchors: the embed's spreadsheet view navigates + flashes the\n // target range (Olympus `useSheetAnchors`). `sheet_id` is the engine's\n // numeric sheet id, NOT a tab index.\n case 'sheet_cell': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const cell = url.searchParams.get('cell');\n if (sheetId !== null && cell) reference.anchor = { type: anchorType, sheetId, cell };\n break;\n }\n case 'sheet_range': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const range = url.searchParams.get('range');\n if (sheetId !== null && range) reference.anchor = { type: anchorType, sheetId, range };\n break;\n }\n case 'sheet_tab': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n if (sheetId !== null) reference.anchor = { type: anchorType, sheetId };\n break;\n }\n case 'sheet_table': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const tableName = url.searchParams.get('table_name');\n const fallbackRange = url.searchParams.get('fallback_range');\n if (sheetId !== null && tableName) {\n reference.anchor = {\n type: anchorType,\n sheetId,\n tableName,\n ...(fallbackRange ? { fallbackRange } : {}),\n };\n }\n break;\n }\n }\n\n const excerpt = url.searchParams.get('excerpt');\n if (excerpt) reference.meta = { excerpt };\n return reference;\n};\n\nconst normalizeAssetType = (value: string | null): AssetType | null => {\n if (!value) {\n return null;\n }\n\n const normalizedValue = value.trim().replace(/^athena\\//, '').toLowerCase().replace(/-/g, '_');\n return ASSET_TYPE_ALIASES[normalizedValue] ?? null;\n};\n\nconst getOrigin = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nconst getHostname = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).hostname.toLowerCase();\n } catch {\n return null;\n }\n};\n\nconst matchesHostnameOrSubdomain = ({\n hostname,\n candidateHostname,\n}: {\n hostname: string;\n candidateHostname: string;\n}): boolean => hostname === candidateHostname || hostname.endsWith(`.${candidateHostname}`);\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst parseUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): URL | null => {\n const fallbackBase = appUrl ?? getCurrentOrigin() ?? 'https://app.athenaintel.com';\n\n try {\n return new URL(href, fallbackBase);\n } catch {\n return null;\n }\n};\n\nconst isAthenaAppUrl = ({\n url,\n appUrl,\n}: {\n url: URL;\n appUrl?: string | null;\n}): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (\n ATHENA_APP_HOSTNAMES.has(hostname)\n || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)\n || ATHENA_WORKSPACE_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix))\n ) {\n return true;\n }\n\n const configuredHostname = getHostname(appUrl);\n if (configuredHostname && matchesHostnameOrSubdomain({ hostname, candidateHostname: configuredHostname })) {\n return true;\n }\n\n const configuredOrigin = getOrigin(appUrl);\n if (configuredOrigin && url.origin === configuredOrigin) {\n return true;\n }\n\n // Fallback: treat same-origin `/dashboard/spaces?asset_ids=...` links as Athena\n // citations so local dev and private-cloud embeds still work without appUrl.\n // Consumers can disable the default interception with\n // linkClicks={{ interceptAthenaCitations: false }} or the legacy citationLinks alias.\n const currentOrigin = getCurrentOrigin();\n return !!currentOrigin && url.origin === currentOrigin;\n};\n\nexport const parseAthenaCitationLink = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): ParsedAthenaCitationLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url || !isAthenaAppUrl({ url, appUrl }) || !isAthenaSpacesPath(url)) {\n return null;\n }\n\n const assetIdsParam = url.searchParams.get('asset_ids');\n if (!assetIdsParam) {\n return null;\n }\n\n const assetIds = assetIdsParam\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0);\n const assetId = assetIds[0];\n\n if (!assetId) {\n return null;\n }\n\n return {\n url,\n assetId,\n assetIds,\n assetType: normalizeAssetType(url.searchParams.get('asset_type') ?? url.searchParams.get('type')),\n reference: parseCitationReference({ url, assetId }),\n };\n};\n\nexport const isAthenaCitationUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): boolean => parseAthenaCitationLink({ href, appUrl }) !== null;\n\ninterface ResolvedAthenaLink {\n url: URL;\n kind: AthenaLinkKind;\n citation: ParsedAthenaCitationLink | null;\n}\n\nconst resolveAthenaLink = ({\n href,\n appUrl,\n}: AthenaLinkClickHandleInput & {\n appUrl?: string | null;\n}): ResolvedAthenaLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url) {\n return null;\n }\n\n const citation = parseAthenaCitationLink({ href, appUrl });\n if (citation) {\n return {\n url: citation.url,\n kind: 'athena-citation',\n citation,\n };\n }\n\n return {\n url,\n kind: isAthenaAppUrl({ url, appUrl }) ? 'athena-app' : 'external',\n citation: null,\n };\n};\n\nexport const useAthenaLinkClickHandler = (): AthenaLinkClickHandler => {\n const openAsset = useAssetPanelStore((state) => state.openAsset);\n // A host that opens assets its own way (`AthenaProvider onOpenAsset`) counts as a panel: the\n // citation goes to it instead of to the browser.\n const isAssetPanelAvailable = useAssetPanelStore(\n (state) => state.assetPanelHostCount > 0 || state.assetOpeners.length > 0,\n );\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n\n return useCallback<AthenaLinkClickHandler>(\n ({ href, linkText, target, nativeEvent }: AthenaLinkClickHandleInput) => {\n const resolvedLink = resolveAthenaLink({ href, appUrl });\n if (!resolvedLink) {\n return false;\n }\n\n const linkClicks = athenaConfig?.linkClicks;\n const citationLinks = athenaConfig?.citationLinks;\n const trimmedLinkText = linkText?.trim() || null;\n const citationLink = resolvedLink.citation;\n const citationMetadata: AthenaCitationLinkMetadata | null = citationLink\n ? {\n assetId: citationLink.assetId,\n assetIds: citationLink.assetIds,\n assetType: citationLink.assetType,\n }\n : null;\n const openInAssetPanel = citationLink\n ? () =>\n openAsset(citationLink.assetId, {\n name: trimmedLinkText ?? undefined,\n type: citationLink.assetType ?? undefined,\n // Carry the citation target so the panel's iframe navigates to\n // the cited range/page instead of opening at the top.\n reference: citationLink.reference,\n // Reading references is one preview surface: a citation to a\n // different asset swaps the active tab rather than stacking a\n // tab per cited document.\n replaceActiveTab: true,\n // A host's own opener that opens it on the web keeps the link as written.\n href: resolvedLink.url.toString(),\n })\n : undefined;\n const context: AthenaLinkClickContext = {\n href,\n url: resolvedLink.url,\n target: target ?? null,\n linkText: trimmedLinkText,\n isAssetPanelAvailable,\n kind: resolvedLink.kind,\n citation: citationMetadata,\n nativeEvent: nativeEvent ?? null,\n openInAssetPanel,\n openInBrowser: (nextTarget) => {\n window.open(resolvedLink.url.toString(), nextTarget ?? target ?? '_blank')?.focus();\n },\n };\n\n if (linkClicks?.onClick?.(context) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if (!citationLink || !openInAssetPanel || !citationMetadata) {\n return false;\n }\n\n const citationContext: AthenaCitationLinkClickContext = {\n ...context,\n kind: 'athena-citation',\n citation: citationMetadata,\n assetId: citationMetadata.assetId,\n assetIds: citationMetadata.assetIds,\n assetType: citationMetadata.assetType,\n openInAssetPanel,\n };\n\n if (citationLinks?.onClick?.(citationContext) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if ((linkClicks?.interceptAthenaCitations ?? citationLinks?.interceptAthenaCitations) === false) {\n return false;\n }\n\n if (!isAssetPanelAvailable) {\n return false;\n }\n\n // Nothing showed it (the host's opener declined and no panel is mounted): the link opens as\n // a link.\n if (!citationContext.openInAssetPanel()) {\n return false;\n }\n nativeEvent?.preventDefault();\n return true;\n },\n [appUrl, athenaConfig, isAssetPanelAvailable, openAsset],\n );\n};\n\nexport const useAthenaCitationLinkHandler = (): AthenaCitationLinkHandler => {\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n const handleLinkClick = useAthenaLinkClickHandler();\n\n return useCallback<AthenaCitationLinkHandler>(\n (input: AthenaCitationLinkHandleInput) => {\n if (!parseAthenaCitationLink({ href: input.href, appUrl })) {\n return false;\n }\n\n return handleLinkClick(input);\n },\n [appUrl, handleLinkClick],\n );\n};\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"athena-citation-links.js","sources":["../../src/chat/athena-citation-links.ts"],"sourcesContent":["import { decodeFromAssetSpacesUrl, serializeAnchorToParams } from '@athenaintel/references/asset-links';\nimport { useCallback, useContext } from 'react';\nimport type { AthenaAssetCitationReference } from '../assets/asset-embed-messages';\nimport { type AssetType, useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AthenaContext } from '../provider/AthenaContext';\nimport type {\n AthenaCitationLinkMetadata,\n AthenaCitationLinkClickContext,\n AthenaCitationLinkHandleInput,\n AthenaCitationLinkHandler,\n AthenaLinkClickContext,\n AthenaLinkClickHandler,\n AthenaLinkClickHandleInput,\n AthenaLinkKind,\n} from '../provider/citation-links';\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_SUFFIXES = ['.app.athenaintel.com', '.staging-app.athenaintel.com'];\n\nconst ASSET_TYPE_ALIASES: Record<string, AssetType> = {\n document: 'document',\n notebook: 'notebook',\n pdf: 'document',\n powerpoint: 'presentation',\n presentation: 'presentation',\n sheet: 'spreadsheet',\n spreadsheet: 'spreadsheet',\n word_document: 'document',\n};\n\nconst isAthenaSpacesPath = (url: URL): boolean => url.pathname.replace(/\\/+$/, '') === '/dashboard/spaces';\n\n/**\n * Asset types no embed viewer renders: agora refuses their embed token (422,\n * `agora/agora/web/api/embed/embed_viewer_asset_types.json`), so the asset\n * panel could only show an error for them.\n */\nconst NON_EMBEDDABLE_ASSET_TYPES: ReadonlySet<string> = new Set(['catalog']);\n\n/**\n * Anchors only a non-embeddable asset holds: a repository file's lines live in\n * a GitHub catalog (`ASSET_ANCHOR_REGISTRY`), which Athena on the web opens at\n * the file with the lines highlighted. Read from the anchor too, so a citation\n * whose `type=` names the wrong type still opens where it can be shown.\n */\nconst NON_EMBEDDABLE_ANCHOR_TYPES: ReadonlySet<string> = new Set(['file_line_range']);\n\n/** Whether the asset panel can show the cited asset, or only Athena on the web can. */\nconst isEmbeddableCitation = (url: URL): boolean => {\n const rawType = (url.searchParams.get('asset_type') ?? url.searchParams.get('type') ?? '')\n .trim()\n .replace(/^athena\\//, '')\n .toLowerCase();\n const anchorType = url.searchParams.get('anchor') ?? '';\n return !NON_EMBEDDABLE_ASSET_TYPES.has(rawType) && !NON_EMBEDDABLE_ANCHOR_TYPES.has(anchorType);\n};\n\nexport interface ParsedAthenaCitationLink {\n url: URL;\n assetId: string;\n assetIds: string[];\n assetType: AssetType | null;\n /** Complete target that can be passed directly to AthenaAssetEmbed.reference. */\n reference: AthenaAssetCitationReference;\n /**\n * Whether the asset panel can show the cited asset. A citation it cannot (a repository file's\n * lines, any catalog) opens as the link it is, in Athena on the web.\n */\n embeddable: boolean;\n}\n\nconst getFiniteNumber = (url: URL, key: string): number | null => {\n const value = url.searchParams.get(key);\n if (value === null || value.trim() === '') return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n};\n\nconst getNonEmptyString = (url: URL, key: string): string | null => {\n const value = url.searchParams.get(key);\n return value === null || value === '' ? null : value;\n};\n\n/** The link's anchor as the shared reference parser reads it, or null when it names none it can read. */\nconst readSharedAnchor = (url: URL) => {\n try {\n return decodeFromAssetSpacesUrl(url).anchor ?? null;\n } catch {\n return null;\n }\n};\n\nconst parseCitationReference = ({\n url,\n assetId,\n}: {\n url: URL;\n assetId: string;\n}): AthenaAssetCitationReference => {\n const reference: AthenaAssetCitationReference = { id: assetId };\n const anchorType = url.searchParams.get('anchor');\n const page = getFiniteNumber(url, 'page');\n\n switch (anchorType) {\n case 'page':\n if (page !== null) reference.anchor = { type: anchorType, page };\n break;\n case 'page_range': {\n const startPage = getFiniteNumber(url, 'start_page');\n const endPage = getFiniteNumber(url, 'end_page');\n if (startPage !== null && endPage !== null) {\n reference.anchor = { type: anchorType, startPage, endPage };\n }\n break;\n }\n case 'page_rect': {\n const x = getFiniteNumber(url, 'x');\n const y = getFiniteNumber(url, 'y');\n const width = getFiniteNumber(url, 'width');\n const height = getFiniteNumber(url, 'height');\n if (page !== null && x !== null && y !== null && width !== null && height !== null) {\n const unit = url.searchParams.get('unit');\n reference.anchor = {\n type: anchorType,\n page,\n x,\n y,\n width,\n height,\n ...(unit === 'percent' || unit === 'point' ? { unit } : {}),\n };\n }\n break;\n }\n case 'page_text': {\n const text = url.searchParams.get('text');\n if (page !== null && text) {\n // Optional context that disambiguates a repeated phrase, and the hash\n // of the cited unit's normalized text (Clio C-23).\n const prefix = getNonEmptyString(url, 'prefix');\n const suffix = getNonEmptyString(url, 'suffix');\n const hash = getNonEmptyString(url, 'hash');\n reference.anchor = {\n type: anchorType,\n page,\n text,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n ...(hash ? { hash } : {}),\n };\n }\n break;\n }\n // Sheet anchors: the embed's spreadsheet view navigates + flashes the\n // target range (Olympus `useSheetAnchors`). `sheet_id` is the engine's\n // numeric sheet id, NOT a tab index.\n case 'sheet_cell': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const cell = url.searchParams.get('cell');\n if (sheetId !== null && cell) reference.anchor = { type: anchorType, sheetId, cell };\n break;\n }\n case 'sheet_range': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const range = url.searchParams.get('range');\n if (sheetId !== null && range) reference.anchor = { type: anchorType, sheetId, range };\n break;\n }\n case 'sheet_tab': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n if (sheetId !== null) reference.anchor = { type: anchorType, sheetId };\n break;\n }\n case 'sheet_table': {\n const sheetId = getFiniteNumber(url, 'sheet_id');\n const tableName = url.searchParams.get('table_name');\n const fallbackRange = url.searchParams.get('fallback_range');\n if (sheetId !== null && tableName) {\n reference.anchor = {\n type: anchorType,\n sheetId,\n tableName,\n ...(fallbackRange ? { fallbackRange } : {}),\n };\n }\n break;\n }\n }\n\n // Every other place (a slide, a time, a document block, a transcript message, …) goes to the\n // embed as the canonical query string `@athenaintel/references` writes, which the embed reads\n // back with the same parser, so the panel opens at it instead of at the top.\n if (reference.anchor === undefined) {\n const anchor = readSharedAnchor(url);\n if (anchor) reference.anchor = serializeAnchorToParams(anchor).toString();\n }\n\n const excerpt = url.searchParams.get('excerpt');\n if (excerpt) reference.meta = { excerpt };\n return reference;\n};\n\nconst normalizeAssetType = (value: string | null): AssetType | null => {\n if (!value) {\n return null;\n }\n\n const normalizedValue = value.trim().replace(/^athena\\//, '').toLowerCase().replace(/-/g, '_');\n return ASSET_TYPE_ALIASES[normalizedValue] ?? null;\n};\n\nconst getOrigin = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nconst getHostname = (value?: string | null): string | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).hostname.toLowerCase();\n } catch {\n return null;\n }\n};\n\nconst matchesHostnameOrSubdomain = ({\n hostname,\n candidateHostname,\n}: {\n hostname: string;\n candidateHostname: string;\n}): boolean => hostname === candidateHostname || hostname.endsWith(`.${candidateHostname}`);\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst parseUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): URL | null => {\n const fallbackBase = appUrl ?? getCurrentOrigin() ?? 'https://app.athenaintel.com';\n\n try {\n return new URL(href, fallbackBase);\n } catch {\n return null;\n }\n};\n\nconst isAthenaAppUrl = ({\n url,\n appUrl,\n}: {\n url: URL;\n appUrl?: string | null;\n}): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (\n ATHENA_APP_HOSTNAMES.has(hostname)\n || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)\n || ATHENA_WORKSPACE_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix))\n ) {\n return true;\n }\n\n const configuredHostname = getHostname(appUrl);\n if (configuredHostname && matchesHostnameOrSubdomain({ hostname, candidateHostname: configuredHostname })) {\n return true;\n }\n\n const configuredOrigin = getOrigin(appUrl);\n if (configuredOrigin && url.origin === configuredOrigin) {\n return true;\n }\n\n // Fallback: treat same-origin `/dashboard/spaces?asset_ids=...` links as Athena\n // citations so local dev and private-cloud embeds still work without appUrl.\n // Consumers can disable the default interception with\n // linkClicks={{ interceptAthenaCitations: false }} or the legacy citationLinks alias.\n const currentOrigin = getCurrentOrigin();\n return !!currentOrigin && url.origin === currentOrigin;\n};\n\nexport const parseAthenaCitationLink = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): ParsedAthenaCitationLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url || !isAthenaAppUrl({ url, appUrl }) || !isAthenaSpacesPath(url)) {\n return null;\n }\n\n const assetIdsParam = url.searchParams.get('asset_ids');\n if (!assetIdsParam) {\n return null;\n }\n\n const assetIds = assetIdsParam\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0);\n const assetId = assetIds[0];\n\n if (!assetId) {\n return null;\n }\n\n return {\n url,\n assetId,\n assetIds,\n assetType: normalizeAssetType(url.searchParams.get('asset_type') ?? url.searchParams.get('type')),\n reference: parseCitationReference({ url, assetId }),\n embeddable: isEmbeddableCitation(url),\n };\n};\n\nexport const isAthenaCitationUrl = ({\n href,\n appUrl,\n}: {\n href: string;\n appUrl?: string | null;\n}): boolean => parseAthenaCitationLink({ href, appUrl }) !== null;\n\ninterface ResolvedAthenaLink {\n url: URL;\n kind: AthenaLinkKind;\n citation: ParsedAthenaCitationLink | null;\n}\n\nconst resolveAthenaLink = ({\n href,\n appUrl,\n}: AthenaLinkClickHandleInput & {\n appUrl?: string | null;\n}): ResolvedAthenaLink | null => {\n const url = parseUrl({ href, appUrl });\n if (!url) {\n return null;\n }\n\n const citation = parseAthenaCitationLink({ href, appUrl });\n if (citation) {\n return {\n url: citation.url,\n kind: 'athena-citation',\n citation,\n };\n }\n\n return {\n url,\n kind: isAthenaAppUrl({ url, appUrl }) ? 'athena-app' : 'external',\n citation: null,\n };\n};\n\nexport const useAthenaLinkClickHandler = (): AthenaLinkClickHandler => {\n const openAsset = useAssetPanelStore((state) => state.openAsset);\n // A host that opens assets its own way (`AthenaProvider onOpenAsset`) counts as a panel: the\n // citation goes to it instead of to the browser.\n const isAssetPanelAvailable = useAssetPanelStore(\n (state) => state.assetPanelHostCount > 0 || state.assetOpeners.length > 0,\n );\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n\n return useCallback<AthenaLinkClickHandler>(\n ({ href, linkText, target, nativeEvent }: AthenaLinkClickHandleInput) => {\n const resolvedLink = resolveAthenaLink({ href, appUrl });\n if (!resolvedLink) {\n return false;\n }\n\n const linkClicks = athenaConfig?.linkClicks;\n const citationLinks = athenaConfig?.citationLinks;\n const trimmedLinkText = linkText?.trim() || null;\n const citationLink = resolvedLink.citation;\n const citationMetadata: AthenaCitationLinkMetadata | null = citationLink\n ? {\n assetId: citationLink.assetId,\n assetIds: citationLink.assetIds,\n assetType: citationLink.assetType,\n }\n : null;\n // A citation the panel cannot show (a catalog's file lines) is left to the link itself.\n const openInAssetPanel = citationLink?.embeddable\n ? () =>\n openAsset(citationLink.assetId, {\n name: trimmedLinkText ?? undefined,\n type: citationLink.assetType ?? undefined,\n // Carry the citation target so the panel's iframe navigates to\n // the cited range/page instead of opening at the top.\n reference: citationLink.reference,\n // Reading references is one preview surface: a citation to a\n // different asset swaps the active tab rather than stacking a\n // tab per cited document.\n replaceActiveTab: true,\n // A host's own opener that opens it on the web keeps the link as written.\n href: resolvedLink.url.toString(),\n })\n : undefined;\n const context: AthenaLinkClickContext = {\n href,\n url: resolvedLink.url,\n target: target ?? null,\n linkText: trimmedLinkText,\n isAssetPanelAvailable,\n kind: resolvedLink.kind,\n citation: citationMetadata,\n nativeEvent: nativeEvent ?? null,\n openInAssetPanel,\n openInBrowser: (nextTarget) => {\n window.open(resolvedLink.url.toString(), nextTarget ?? target ?? '_blank')?.focus();\n },\n };\n\n if (linkClicks?.onClick?.(context) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if (!citationLink || !openInAssetPanel || !citationMetadata) {\n return false;\n }\n\n const citationContext: AthenaCitationLinkClickContext = {\n ...context,\n kind: 'athena-citation',\n citation: citationMetadata,\n assetId: citationMetadata.assetId,\n assetIds: citationMetadata.assetIds,\n assetType: citationMetadata.assetType,\n openInAssetPanel,\n };\n\n if (citationLinks?.onClick?.(citationContext) === true) {\n nativeEvent?.preventDefault();\n return true;\n }\n\n if ((linkClicks?.interceptAthenaCitations ?? citationLinks?.interceptAthenaCitations) === false) {\n return false;\n }\n\n if (!isAssetPanelAvailable) {\n return false;\n }\n\n // Nothing showed it (the host's opener declined and no panel is mounted): the link opens as\n // a link.\n if (!citationContext.openInAssetPanel()) {\n return false;\n }\n nativeEvent?.preventDefault();\n return true;\n },\n [appUrl, athenaConfig, isAssetPanelAvailable, openAsset],\n );\n};\n\nexport const useAthenaCitationLinkHandler = (): AthenaCitationLinkHandler => {\n const athenaConfig = useContext(AthenaContext);\n const appUrl = athenaConfig?.appUrl;\n const handleLinkClick = useAthenaLinkClickHandler();\n\n return useCallback<AthenaCitationLinkHandler>(\n (input: AthenaCitationLinkHandleInput) => {\n if (!parseAthenaCitationLink({ href: input.href, appUrl })) {\n return false;\n }\n\n return handleLinkClick(input);\n },\n [appUrl, handleLinkClick],\n );\n};\n"],"names":["_a"],"mappings":"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"}
@@ -23,7 +23,7 @@ export { type ComposerSend, prepareComposerSend } from "./composer/composer-mess
23
23
  export { PendingAttachmentUploads } from "./composer/pending-attachment-uploads";
24
24
  export { resolveComposerAction, type ComposerAction, } from "./composer/composer-action";
25
25
  export { MentionExtension, makeMentionUrl, parseAssetAnchorAttributes, parseMentionAnchorAttributes, serializeMention, } from "./mentions/mention-extension";
26
- export { MentionChip, MentionLink, normalizeAthenaMentionHtml, parseMentionUrl, parseMentionsInText, } from "./mentions/mention-chip";
26
+ export { MentionChip, type MentionChipCitation, MentionLink, normalizeAthenaMentionHtml, parseMentionUrl, parseMentionsInText, } from "./mentions/mention-chip";
27
27
  export { MentionSuggestionsExtension } from "./mentions/suggestions/mention-suggestions.extension";
28
28
  export { MentionSuggestionList } from "./mentions/suggestions/components/mention-suggestion-list";
29
29
  export { MentionSuggestionPopup } from "./mentions/suggestions/components/mention-suggestion-popup";
@@ -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
  )