@athenaintel/react 0.9.8 → 0.9.9

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.
package/dist/index.d.ts CHANGED
@@ -49,6 +49,29 @@ export declare interface AssetPanelState {
49
49
  markAutoOpened: (assetId: string) => boolean;
50
50
  }
51
51
 
52
+ /**
53
+ * Render an Athena asset anywhere inside AthenaProvider.
54
+ *
55
+ * This component only needs `assetId`. The embed token endpoint resolves the
56
+ * correct viewer from that ID, so callers do not need to pass an asset type.
57
+ */
58
+ export declare const AssetRenderer: FC<AssetRendererProps>;
59
+
60
+ export declare interface AssetRendererProps extends UseAssetEmbedOptions {
61
+ /** Athena asset ID to render. */
62
+ assetId: string | null;
63
+ /** Accessible iframe title. */
64
+ title?: string | null;
65
+ /** Applied to the iframe or fallback container. */
66
+ className?: string;
67
+ /** Optional loading UI override. */
68
+ loadingFallback?: ReactNode;
69
+ /** Optional empty state override when assetId is missing. */
70
+ emptyFallback?: ReactNode;
71
+ /** Optional error UI override. */
72
+ errorFallback?: ReactNode | ((error: string) => ReactNode);
73
+ }
74
+
52
75
  export declare interface AssetTab {
53
76
  id: string;
54
77
  name: string | null;
@@ -214,7 +237,7 @@ export declare interface AthenaRuntimeConfig {
214
237
  export declare interface AthenaTheme {
215
238
  /** Brand / accent color. Send button, active states, focus ring, spinner. */
216
239
  primary?: string;
217
- /** Text color on primary background. Set this with `primary` when you change contrast significantly. */
240
+ /** Text color on primary background. If omitted, the SDK tries to derive a readable black/white fallback for solid colors. */
218
241
  primaryForeground?: string;
219
242
  /** Page / panel background. */
220
243
  background?: string;
@@ -260,11 +283,11 @@ export declare interface AthenaTheme {
260
283
  sidebarWidth?: string;
261
284
  /** User message bubble background. Falls back to muted. */
262
285
  userBubble?: string;
263
- /** User message bubble text color. Falls back to foreground. Set this when `userBubble` is dark or saturated. */
286
+ /** User message bubble text color. If omitted, the SDK tries to derive a readable black/white fallback for solid colors. */
264
287
  userBubbleForeground?: string;
265
288
  /** User message bubble border radius. e.g. '1rem', '0.5rem'. */
266
289
  userBubbleRadius?: string;
267
- /** Assistant message text color. Falls back to foreground. Set this when `assistantBubble` reduces contrast. */
290
+ /** Assistant message text color. If omitted, the SDK tries to derive a readable black/white fallback for solid colors. */
268
291
  assistantForeground?: string;
269
292
  /** Assistant message bubble background. Transparent by default. */
270
293
  assistantBubble?: string;
package/dist/index.js CHANGED
@@ -11916,7 +11916,7 @@ const oppositeSideMap = {
11916
11916
  bottom: "top",
11917
11917
  top: "bottom"
11918
11918
  };
11919
- function clamp(start, value, end) {
11919
+ function clamp$1(start, value, end) {
11920
11920
  return max(start, min(value, end));
11921
11921
  }
11922
11922
  function evaluate(value, param) {
@@ -12273,7 +12273,7 @@ const arrow$4 = (options) => ({
12273
12273
  const min$1 = minPadding;
12274
12274
  const max2 = clientSize - arrowDimensions[length] - maxPadding;
12275
12275
  const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
12276
- const offset2 = clamp(min$1, center, max2);
12276
+ const offset2 = clamp$1(min$1, center, max2);
12277
12277
  const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset2 && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
12278
12278
  const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max2 : 0;
12279
12279
  return {
@@ -12572,14 +12572,14 @@ const shift$3 = function(options) {
12572
12572
  const maxSide = mainAxis === "y" ? "bottom" : "right";
12573
12573
  const min2 = mainAxisCoord + overflow[minSide];
12574
12574
  const max2 = mainAxisCoord - overflow[maxSide];
12575
- mainAxisCoord = clamp(min2, mainAxisCoord, max2);
12575
+ mainAxisCoord = clamp$1(min2, mainAxisCoord, max2);
12576
12576
  }
12577
12577
  if (checkCrossAxis) {
12578
12578
  const minSide = crossAxis === "y" ? "top" : "left";
12579
12579
  const maxSide = crossAxis === "y" ? "bottom" : "right";
12580
12580
  const min2 = crossAxisCoord + overflow[minSide];
12581
12581
  const max2 = crossAxisCoord - overflow[maxSide];
12582
- crossAxisCoord = clamp(min2, crossAxisCoord, max2);
12582
+ crossAxisCoord = clamp$1(min2, crossAxisCoord, max2);
12583
12583
  }
12584
12584
  const limitedCoords = limiter.fn({
12585
12585
  ...state,
@@ -24460,6 +24460,163 @@ const ThreadListRefreshContext = createContext(null);
24460
24460
  function useRefreshThreadList() {
24461
24461
  return useContext(ThreadListRefreshContext);
24462
24462
  }
24463
+ const WHITE = "#ffffff";
24464
+ const BLACK = "#000000";
24465
+ const CONTRAST_PAIRS = {
24466
+ primary: "primaryForeground",
24467
+ secondary: "secondaryForeground",
24468
+ accent: "accentForeground",
24469
+ card: "cardForeground",
24470
+ popover: "popoverForeground",
24471
+ userBubble: "userBubbleForeground",
24472
+ assistantBubble: "assistantForeground"
24473
+ };
24474
+ const clamp = (value, min2 = 0, max2 = 1) => Math.min(max2, Math.max(min2, value));
24475
+ const parseNumber = (raw) => {
24476
+ const value = Number.parseFloat(raw);
24477
+ return Number.isFinite(value) ? value : null;
24478
+ };
24479
+ const parsePercentLike = (raw, max2 = 1) => {
24480
+ const value = parseNumber(raw);
24481
+ if (value == null) return null;
24482
+ return raw.trim().endsWith("%") ? value / 100 * max2 : value;
24483
+ };
24484
+ const parseHexColor = (input) => {
24485
+ const hex = input.slice(1).trim();
24486
+ if (![3, 4, 6, 8].includes(hex.length)) return null;
24487
+ const expanded = hex.length <= 4 ? hex.split("").map((char) => `${char}${char}`).join("") : hex;
24488
+ const channels = expanded.match(/.{2}/g);
24489
+ if (!channels) return null;
24490
+ const [r2, g, b, alpha2 = 255] = channels.map(
24491
+ (channel) => Number.parseInt(channel, 16)
24492
+ );
24493
+ return {
24494
+ r: clamp(r2 / 255),
24495
+ g: clamp(g / 255),
24496
+ b: clamp(b / 255),
24497
+ alpha: clamp(alpha2 / 255)
24498
+ };
24499
+ };
24500
+ const splitColorArgs = (input) => input.replace(/^[^(]+\(/, "").replace(/\)$/, "").replace(/\//g, " ").replace(/,/g, " ").trim().split(/\s+/).filter(Boolean);
24501
+ const parseRgbColor = (input) => {
24502
+ const parts = splitColorArgs(input);
24503
+ if (parts.length < 3) return null;
24504
+ const r2 = parsePercentLike(parts[0], 255);
24505
+ const g = parsePercentLike(parts[1], 255);
24506
+ const b = parsePercentLike(parts[2], 255);
24507
+ const alpha2 = parts[3] ? parsePercentLike(parts[3], 1) : 1;
24508
+ if ([r2, g, b, alpha2].some((value) => value == null)) return null;
24509
+ return {
24510
+ r: clamp(r2 / 255),
24511
+ g: clamp(g / 255),
24512
+ b: clamp(b / 255),
24513
+ alpha: clamp(alpha2)
24514
+ };
24515
+ };
24516
+ const hueToRgb = (p, q, t) => {
24517
+ let value = t;
24518
+ if (value < 0) value += 1;
24519
+ if (value > 1) value -= 1;
24520
+ if (value < 1 / 6) return p + (q - p) * 6 * value;
24521
+ if (value < 1 / 2) return q;
24522
+ if (value < 2 / 3) return p + (q - p) * (2 / 3 - value) * 6;
24523
+ return p;
24524
+ };
24525
+ const parseHslColor = (input) => {
24526
+ const parts = splitColorArgs(input);
24527
+ if (parts.length < 3) return null;
24528
+ const hue = parseNumber(parts[0]);
24529
+ const saturation = parsePercentLike(parts[1], 1);
24530
+ const lightness = parsePercentLike(parts[2], 1);
24531
+ const alpha2 = parts[3] ? parsePercentLike(parts[3], 1) : 1;
24532
+ if ([hue, saturation, lightness, alpha2].some((value) => value == null)) {
24533
+ return null;
24534
+ }
24535
+ const h2 = (hue % 360 + 360) % 360 / 360;
24536
+ const s = clamp(saturation);
24537
+ const l = clamp(lightness);
24538
+ if (s === 0) {
24539
+ return { r: l, g: l, b: l, alpha: clamp(alpha2) };
24540
+ }
24541
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
24542
+ const p = 2 * l - q;
24543
+ return {
24544
+ r: clamp(hueToRgb(p, q, h2 + 1 / 3)),
24545
+ g: clamp(hueToRgb(p, q, h2)),
24546
+ b: clamp(hueToRgb(p, q, h2 - 1 / 3)),
24547
+ alpha: clamp(alpha2)
24548
+ };
24549
+ };
24550
+ const parseOklchColor = (input) => {
24551
+ const parts = splitColorArgs(input);
24552
+ if (parts.length < 3) return null;
24553
+ const lightness = parsePercentLike(parts[0], 1);
24554
+ const chroma = parseNumber(parts[1]);
24555
+ const hue = parseNumber(parts[2]);
24556
+ const alpha2 = parts[3] ? parsePercentLike(parts[3], 1) : 1;
24557
+ if ([lightness, chroma, hue, alpha2].some((value) => value == null)) {
24558
+ return null;
24559
+ }
24560
+ const l = clamp(lightness);
24561
+ const c = Math.max(0, chroma);
24562
+ const h2 = hue * Math.PI / 180;
24563
+ const a = c * Math.cos(h2);
24564
+ const b = c * Math.sin(h2);
24565
+ const lPrime = l + 0.3963377774 * a + 0.2158037573 * b;
24566
+ const mPrime = l - 0.1055613458 * a - 0.0638541728 * b;
24567
+ const sPrime = l - 0.0894841775 * a - 1.291485548 * b;
24568
+ const lLinear = lPrime ** 3;
24569
+ const mLinear = mPrime ** 3;
24570
+ const sLinear = sPrime ** 3;
24571
+ return {
24572
+ r: clamp(
24573
+ 4.0767416621 * lLinear - 3.3077115913 * mLinear + 0.2309699292 * sLinear
24574
+ ),
24575
+ g: clamp(
24576
+ -1.2684380046 * lLinear + 2.6097574011 * mLinear - 0.3413193965 * sLinear
24577
+ ),
24578
+ b: clamp(
24579
+ -0.0041960863 * lLinear - 0.7034186147 * mLinear + 1.707614701 * sLinear
24580
+ ),
24581
+ alpha: clamp(alpha2)
24582
+ };
24583
+ };
24584
+ const parseColor = (input) => {
24585
+ const value = input.trim().toLowerCase();
24586
+ if (value.startsWith("#")) return parseHexColor(value);
24587
+ if (value.startsWith("rgb")) return parseRgbColor(value);
24588
+ if (value.startsWith("hsl")) return parseHslColor(value);
24589
+ if (value.startsWith("oklch")) return parseOklchColor(value);
24590
+ return null;
24591
+ };
24592
+ const srgbToLinear = (value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
24593
+ const relativeLuminance = (color) => 0.2126 * srgbToLinear(color.r) + 0.7152 * srgbToLinear(color.g) + 0.0722 * srgbToLinear(color.b);
24594
+ const contrastRatio = (luminanceA, luminanceB) => {
24595
+ const lighter = Math.max(luminanceA, luminanceB);
24596
+ const darker = Math.min(luminanceA, luminanceB);
24597
+ return (lighter + 0.05) / (darker + 0.05);
24598
+ };
24599
+ const getReadableForeground = (background) => {
24600
+ const parsed = parseColor(background);
24601
+ if (!parsed || parsed.alpha < 1) return null;
24602
+ const backgroundLuminance = relativeLuminance(parsed);
24603
+ const whiteContrast = contrastRatio(backgroundLuminance, 1);
24604
+ const blackContrast = contrastRatio(backgroundLuminance, 0);
24605
+ return whiteContrast > blackContrast ? WHITE : BLACK;
24606
+ };
24607
+ const withAutoContrastTheme = (theme) => {
24608
+ const resolvedTheme = { ...theme };
24609
+ for (const backgroundKey of Object.keys(CONTRAST_PAIRS)) {
24610
+ const foregroundKey = CONTRAST_PAIRS[backgroundKey];
24611
+ const backgroundValue = theme[backgroundKey];
24612
+ if (theme[foregroundKey] || !backgroundValue) continue;
24613
+ const derivedForeground = getReadableForeground(backgroundValue);
24614
+ if (derivedForeground) {
24615
+ resolvedTheme[foregroundKey] = derivedForeground;
24616
+ }
24617
+ }
24618
+ return resolvedTheme;
24619
+ };
24463
24620
  const THEME_TO_CSS = {
24464
24621
  primary: "--primary",
24465
24622
  primaryForeground: "--primary-foreground",
@@ -24496,7 +24653,8 @@ const THEME_TO_CSS = {
24496
24653
  };
24497
24654
  function themeToStyleVars(theme) {
24498
24655
  const vars = {};
24499
- for (const [key, value] of Object.entries(theme)) {
24656
+ const resolvedTheme = withAutoContrastTheme(theme);
24657
+ for (const [key, value] of Object.entries(resolvedTheme)) {
24500
24658
  if (value != null && THEME_TO_CSS[key]) {
24501
24659
  vars[THEME_TO_CSS[key]] = value;
24502
24660
  }
@@ -64345,6 +64503,75 @@ function useAssetEmbed(assetId, options = {
64345
64503
  }, [assetId, readOnly, expiresInSeconds, backendUrl, apiKey, token]);
64346
64504
  return { embedUrl, isLoading, error: error2 };
64347
64505
  }
64506
+ const AssetRenderer = ({
64507
+ assetId,
64508
+ title,
64509
+ className,
64510
+ loadingFallback,
64511
+ emptyFallback = null,
64512
+ errorFallback,
64513
+ readOnly,
64514
+ expiresInSeconds
64515
+ }) => {
64516
+ const { backendUrl, apiKey, token } = useAthenaConfig();
64517
+ const { embedUrl, isLoading, error: error2 } = useAssetEmbed(assetId, {
64518
+ backendUrl,
64519
+ apiKey,
64520
+ token,
64521
+ readOnly,
64522
+ expiresInSeconds
64523
+ });
64524
+ if (!assetId) {
64525
+ return emptyFallback ? /* @__PURE__ */ jsx("div", { className: cn("h-full w-full", className), children: emptyFallback }) : null;
64526
+ }
64527
+ if (isLoading) {
64528
+ return /* @__PURE__ */ jsx(
64529
+ "div",
64530
+ {
64531
+ className: cn(
64532
+ "flex h-full w-full items-center justify-center",
64533
+ className
64534
+ ),
64535
+ children: loadingFallback ?? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
64536
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "mx-auto size-6 animate-spin text-muted-foreground" }),
64537
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Loading..." })
64538
+ ] })
64539
+ }
64540
+ );
64541
+ }
64542
+ if (error2) {
64543
+ const resolvedErrorFallback = typeof errorFallback === "function" ? errorFallback(error2) : errorFallback;
64544
+ return /* @__PURE__ */ jsx(
64545
+ "div",
64546
+ {
64547
+ className: cn(
64548
+ "flex h-full w-full items-center justify-center p-4",
64549
+ className
64550
+ ),
64551
+ children: resolvedErrorFallback ?? /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center", children: [
64552
+ /* @__PURE__ */ jsx(CircleAlert, { className: "mx-auto size-5 text-red-400" }),
64553
+ /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs font-medium text-foreground", children: "Failed to load" }),
64554
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-2 break-words text-[10px] text-muted-foreground", children: error2 })
64555
+ ] })
64556
+ }
64557
+ );
64558
+ }
64559
+ if (!embedUrl) {
64560
+ return emptyFallback ? /* @__PURE__ */ jsx("div", { className: cn("h-full w-full", className), children: emptyFallback }) : null;
64561
+ }
64562
+ return /* @__PURE__ */ jsx(
64563
+ "iframe",
64564
+ {
64565
+ src: embedUrl,
64566
+ width: "100%",
64567
+ height: "100%",
64568
+ frameBorder: "0",
64569
+ allow: "fullscreen",
64570
+ title: title ?? `Asset ${assetId}`,
64571
+ className: cn("h-full w-full", className)
64572
+ }
64573
+ );
64574
+ };
64348
64575
  const ASSET_TYPE_CONFIG = {
64349
64576
  presentation: { icon: Presentation, label: "Presentation" },
64350
64577
  spreadsheet: { icon: FileSpreadsheet, label: "Spreadsheet" },
@@ -64354,38 +64581,7 @@ const ASSET_TYPE_CONFIG = {
64354
64581
  };
64355
64582
  const AssetIframe = memo(
64356
64583
  ({ tabId, tabName }) => {
64357
- const { backendUrl, apiKey, token } = useAthenaConfig();
64358
- const { embedUrl, isLoading, error: error2 } = useAssetEmbed(tabId, {
64359
- backendUrl,
64360
- apiKey,
64361
- token
64362
- });
64363
- if (isLoading) {
64364
- return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
64365
- /* @__PURE__ */ jsx(LoaderCircle, { className: "mx-auto size-6 animate-spin text-muted-foreground" }),
64366
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Loading..." })
64367
- ] }) });
64368
- }
64369
- if (error2) {
64370
- return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center p-4", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center", children: [
64371
- /* @__PURE__ */ jsx(CircleAlert, { className: "mx-auto size-5 text-red-400" }),
64372
- /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs font-medium text-foreground", children: "Failed to load" }),
64373
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-2 break-words text-[10px] text-muted-foreground", children: error2 })
64374
- ] }) });
64375
- }
64376
- if (!embedUrl) return null;
64377
- return /* @__PURE__ */ jsx(
64378
- "iframe",
64379
- {
64380
- src: embedUrl,
64381
- width: "100%",
64382
- height: "100%",
64383
- frameBorder: "0",
64384
- allow: "fullscreen",
64385
- title: tabName ?? `Asset ${tabId}`,
64386
- className: "h-full w-full"
64387
- }
64388
- );
64584
+ return /* @__PURE__ */ jsx(AssetRenderer, { assetId: tabId, title: tabName });
64389
64585
  }
64390
64586
  );
64391
64587
  AssetIframe.displayName = "AssetIframe";
@@ -64727,6 +64923,7 @@ const Toolkits = {
64727
64923
  export {
64728
64924
  AppendDocumentToolUI,
64729
64925
  AssetPanel,
64926
+ AssetRenderer,
64730
64927
  AthenaChat,
64731
64928
  AthenaLayout,
64732
64929
  AthenaProvider,