@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/README.md CHANGED
@@ -60,14 +60,29 @@ import { themes } from '@athenaintel/react';
60
60
 
61
61
  Preset themes: `light`, `dark`, `midnight`, `warm`, `purple`, `green`.
62
62
 
63
+ For solid custom surfaces like `primary`, `userBubble`, and `assistantBubble`, the SDK now auto-derives a readable black/white foreground when the paired foreground token is omitted. Explicit foreground tokens are still preferred when you need exact brand colors.
64
+
63
65
  ## Key Components
64
66
 
65
67
  - **`<AthenaProvider>`** — Runtime, auth, theming, and configuration
66
68
  - **`<AthenaChat>`** — Full chat UI with composer, messages, and tool rendering
69
+ - **`<AssetRenderer>`** — Render a single Athena asset anywhere with just an `assetId`
67
70
  - **`<AthenaLayout>`** — Split-pane layout with asset panel
68
71
  - **`<ThreadList>`** — Conversation history sidebar
69
72
  - **`Toolkits`** — Constants for all available backend toolkits
70
73
 
74
+ ## Assets
75
+
76
+ Use `AssetRenderer` when you want to embed a single asset outside the built-in side panel. It only needs `assetId`; the SDK resolves the correct viewer from that asset automatically.
77
+
78
+ ```tsx
79
+ import { AssetRenderer } from '@athenaintel/react';
80
+
81
+ function PreviewPane() {
82
+ return <AssetRenderer assetId="asset_123" title="Quarterly report" className="min-h-[32rem]" />;
83
+ }
84
+ ```
85
+
71
86
  ## Composer Hooks
72
87
 
73
88
  Use `useAppendToComposer()` when you want to prefill a draft without sending it.
package/dist/index.cjs CHANGED
@@ -11934,7 +11934,7 @@ const oppositeSideMap = {
11934
11934
  bottom: "top",
11935
11935
  top: "bottom"
11936
11936
  };
11937
- function clamp(start, value, end) {
11937
+ function clamp$1(start, value, end) {
11938
11938
  return max(start, min(value, end));
11939
11939
  }
11940
11940
  function evaluate(value, param) {
@@ -12291,7 +12291,7 @@ const arrow$4 = (options) => ({
12291
12291
  const min$1 = minPadding;
12292
12292
  const max2 = clientSize - arrowDimensions[length] - maxPadding;
12293
12293
  const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
12294
- const offset2 = clamp(min$1, center, max2);
12294
+ const offset2 = clamp$1(min$1, center, max2);
12295
12295
  const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset2 && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
12296
12296
  const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max2 : 0;
12297
12297
  return {
@@ -12590,14 +12590,14 @@ const shift$3 = function(options) {
12590
12590
  const maxSide = mainAxis === "y" ? "bottom" : "right";
12591
12591
  const min2 = mainAxisCoord + overflow[minSide];
12592
12592
  const max2 = mainAxisCoord - overflow[maxSide];
12593
- mainAxisCoord = clamp(min2, mainAxisCoord, max2);
12593
+ mainAxisCoord = clamp$1(min2, mainAxisCoord, max2);
12594
12594
  }
12595
12595
  if (checkCrossAxis) {
12596
12596
  const minSide = crossAxis === "y" ? "top" : "left";
12597
12597
  const maxSide = crossAxis === "y" ? "bottom" : "right";
12598
12598
  const min2 = crossAxisCoord + overflow[minSide];
12599
12599
  const max2 = crossAxisCoord - overflow[maxSide];
12600
- crossAxisCoord = clamp(min2, crossAxisCoord, max2);
12600
+ crossAxisCoord = clamp$1(min2, crossAxisCoord, max2);
12601
12601
  }
12602
12602
  const limitedCoords = limiter.fn({
12603
12603
  ...state,
@@ -24478,6 +24478,163 @@ const ThreadListRefreshContext = React.createContext(null);
24478
24478
  function useRefreshThreadList() {
24479
24479
  return React.useContext(ThreadListRefreshContext);
24480
24480
  }
24481
+ const WHITE = "#ffffff";
24482
+ const BLACK = "#000000";
24483
+ const CONTRAST_PAIRS = {
24484
+ primary: "primaryForeground",
24485
+ secondary: "secondaryForeground",
24486
+ accent: "accentForeground",
24487
+ card: "cardForeground",
24488
+ popover: "popoverForeground",
24489
+ userBubble: "userBubbleForeground",
24490
+ assistantBubble: "assistantForeground"
24491
+ };
24492
+ const clamp = (value, min2 = 0, max2 = 1) => Math.min(max2, Math.max(min2, value));
24493
+ const parseNumber = (raw) => {
24494
+ const value = Number.parseFloat(raw);
24495
+ return Number.isFinite(value) ? value : null;
24496
+ };
24497
+ const parsePercentLike = (raw, max2 = 1) => {
24498
+ const value = parseNumber(raw);
24499
+ if (value == null) return null;
24500
+ return raw.trim().endsWith("%") ? value / 100 * max2 : value;
24501
+ };
24502
+ const parseHexColor = (input) => {
24503
+ const hex = input.slice(1).trim();
24504
+ if (![3, 4, 6, 8].includes(hex.length)) return null;
24505
+ const expanded = hex.length <= 4 ? hex.split("").map((char) => `${char}${char}`).join("") : hex;
24506
+ const channels = expanded.match(/.{2}/g);
24507
+ if (!channels) return null;
24508
+ const [r2, g, b, alpha2 = 255] = channels.map(
24509
+ (channel) => Number.parseInt(channel, 16)
24510
+ );
24511
+ return {
24512
+ r: clamp(r2 / 255),
24513
+ g: clamp(g / 255),
24514
+ b: clamp(b / 255),
24515
+ alpha: clamp(alpha2 / 255)
24516
+ };
24517
+ };
24518
+ const splitColorArgs = (input) => input.replace(/^[^(]+\(/, "").replace(/\)$/, "").replace(/\//g, " ").replace(/,/g, " ").trim().split(/\s+/).filter(Boolean);
24519
+ const parseRgbColor = (input) => {
24520
+ const parts = splitColorArgs(input);
24521
+ if (parts.length < 3) return null;
24522
+ const r2 = parsePercentLike(parts[0], 255);
24523
+ const g = parsePercentLike(parts[1], 255);
24524
+ const b = parsePercentLike(parts[2], 255);
24525
+ const alpha2 = parts[3] ? parsePercentLike(parts[3], 1) : 1;
24526
+ if ([r2, g, b, alpha2].some((value) => value == null)) return null;
24527
+ return {
24528
+ r: clamp(r2 / 255),
24529
+ g: clamp(g / 255),
24530
+ b: clamp(b / 255),
24531
+ alpha: clamp(alpha2)
24532
+ };
24533
+ };
24534
+ const hueToRgb = (p, q, t) => {
24535
+ let value = t;
24536
+ if (value < 0) value += 1;
24537
+ if (value > 1) value -= 1;
24538
+ if (value < 1 / 6) return p + (q - p) * 6 * value;
24539
+ if (value < 1 / 2) return q;
24540
+ if (value < 2 / 3) return p + (q - p) * (2 / 3 - value) * 6;
24541
+ return p;
24542
+ };
24543
+ const parseHslColor = (input) => {
24544
+ const parts = splitColorArgs(input);
24545
+ if (parts.length < 3) return null;
24546
+ const hue = parseNumber(parts[0]);
24547
+ const saturation = parsePercentLike(parts[1], 1);
24548
+ const lightness = parsePercentLike(parts[2], 1);
24549
+ const alpha2 = parts[3] ? parsePercentLike(parts[3], 1) : 1;
24550
+ if ([hue, saturation, lightness, alpha2].some((value) => value == null)) {
24551
+ return null;
24552
+ }
24553
+ const h2 = (hue % 360 + 360) % 360 / 360;
24554
+ const s = clamp(saturation);
24555
+ const l = clamp(lightness);
24556
+ if (s === 0) {
24557
+ return { r: l, g: l, b: l, alpha: clamp(alpha2) };
24558
+ }
24559
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
24560
+ const p = 2 * l - q;
24561
+ return {
24562
+ r: clamp(hueToRgb(p, q, h2 + 1 / 3)),
24563
+ g: clamp(hueToRgb(p, q, h2)),
24564
+ b: clamp(hueToRgb(p, q, h2 - 1 / 3)),
24565
+ alpha: clamp(alpha2)
24566
+ };
24567
+ };
24568
+ const parseOklchColor = (input) => {
24569
+ const parts = splitColorArgs(input);
24570
+ if (parts.length < 3) return null;
24571
+ const lightness = parsePercentLike(parts[0], 1);
24572
+ const chroma = parseNumber(parts[1]);
24573
+ const hue = parseNumber(parts[2]);
24574
+ const alpha2 = parts[3] ? parsePercentLike(parts[3], 1) : 1;
24575
+ if ([lightness, chroma, hue, alpha2].some((value) => value == null)) {
24576
+ return null;
24577
+ }
24578
+ const l = clamp(lightness);
24579
+ const c = Math.max(0, chroma);
24580
+ const h2 = hue * Math.PI / 180;
24581
+ const a = c * Math.cos(h2);
24582
+ const b = c * Math.sin(h2);
24583
+ const lPrime = l + 0.3963377774 * a + 0.2158037573 * b;
24584
+ const mPrime = l - 0.1055613458 * a - 0.0638541728 * b;
24585
+ const sPrime = l - 0.0894841775 * a - 1.291485548 * b;
24586
+ const lLinear = lPrime ** 3;
24587
+ const mLinear = mPrime ** 3;
24588
+ const sLinear = sPrime ** 3;
24589
+ return {
24590
+ r: clamp(
24591
+ 4.0767416621 * lLinear - 3.3077115913 * mLinear + 0.2309699292 * sLinear
24592
+ ),
24593
+ g: clamp(
24594
+ -1.2684380046 * lLinear + 2.6097574011 * mLinear - 0.3413193965 * sLinear
24595
+ ),
24596
+ b: clamp(
24597
+ -0.0041960863 * lLinear - 0.7034186147 * mLinear + 1.707614701 * sLinear
24598
+ ),
24599
+ alpha: clamp(alpha2)
24600
+ };
24601
+ };
24602
+ const parseColor = (input) => {
24603
+ const value = input.trim().toLowerCase();
24604
+ if (value.startsWith("#")) return parseHexColor(value);
24605
+ if (value.startsWith("rgb")) return parseRgbColor(value);
24606
+ if (value.startsWith("hsl")) return parseHslColor(value);
24607
+ if (value.startsWith("oklch")) return parseOklchColor(value);
24608
+ return null;
24609
+ };
24610
+ const srgbToLinear = (value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
24611
+ const relativeLuminance = (color) => 0.2126 * srgbToLinear(color.r) + 0.7152 * srgbToLinear(color.g) + 0.0722 * srgbToLinear(color.b);
24612
+ const contrastRatio = (luminanceA, luminanceB) => {
24613
+ const lighter = Math.max(luminanceA, luminanceB);
24614
+ const darker = Math.min(luminanceA, luminanceB);
24615
+ return (lighter + 0.05) / (darker + 0.05);
24616
+ };
24617
+ const getReadableForeground = (background) => {
24618
+ const parsed = parseColor(background);
24619
+ if (!parsed || parsed.alpha < 1) return null;
24620
+ const backgroundLuminance = relativeLuminance(parsed);
24621
+ const whiteContrast = contrastRatio(backgroundLuminance, 1);
24622
+ const blackContrast = contrastRatio(backgroundLuminance, 0);
24623
+ return whiteContrast > blackContrast ? WHITE : BLACK;
24624
+ };
24625
+ const withAutoContrastTheme = (theme) => {
24626
+ const resolvedTheme = { ...theme };
24627
+ for (const backgroundKey of Object.keys(CONTRAST_PAIRS)) {
24628
+ const foregroundKey = CONTRAST_PAIRS[backgroundKey];
24629
+ const backgroundValue = theme[backgroundKey];
24630
+ if (theme[foregroundKey] || !backgroundValue) continue;
24631
+ const derivedForeground = getReadableForeground(backgroundValue);
24632
+ if (derivedForeground) {
24633
+ resolvedTheme[foregroundKey] = derivedForeground;
24634
+ }
24635
+ }
24636
+ return resolvedTheme;
24637
+ };
24481
24638
  const THEME_TO_CSS = {
24482
24639
  primary: "--primary",
24483
24640
  primaryForeground: "--primary-foreground",
@@ -24514,7 +24671,8 @@ const THEME_TO_CSS = {
24514
24671
  };
24515
24672
  function themeToStyleVars(theme) {
24516
24673
  const vars = {};
24517
- for (const [key, value] of Object.entries(theme)) {
24674
+ const resolvedTheme = withAutoContrastTheme(theme);
24675
+ for (const [key, value] of Object.entries(resolvedTheme)) {
24518
24676
  if (value != null && THEME_TO_CSS[key]) {
24519
24677
  vars[THEME_TO_CSS[key]] = value;
24520
24678
  }
@@ -64363,6 +64521,75 @@ function useAssetEmbed(assetId, options = {
64363
64521
  }, [assetId, readOnly, expiresInSeconds, backendUrl, apiKey, token]);
64364
64522
  return { embedUrl, isLoading, error: error2 };
64365
64523
  }
64524
+ const AssetRenderer = ({
64525
+ assetId,
64526
+ title,
64527
+ className,
64528
+ loadingFallback,
64529
+ emptyFallback = null,
64530
+ errorFallback,
64531
+ readOnly,
64532
+ expiresInSeconds
64533
+ }) => {
64534
+ const { backendUrl, apiKey, token } = useAthenaConfig();
64535
+ const { embedUrl, isLoading, error: error2 } = useAssetEmbed(assetId, {
64536
+ backendUrl,
64537
+ apiKey,
64538
+ token,
64539
+ readOnly,
64540
+ expiresInSeconds
64541
+ });
64542
+ if (!assetId) {
64543
+ return emptyFallback ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("h-full w-full", className), children: emptyFallback }) : null;
64544
+ }
64545
+ if (isLoading) {
64546
+ return /* @__PURE__ */ jsxRuntime.jsx(
64547
+ "div",
64548
+ {
64549
+ className: cn(
64550
+ "flex h-full w-full items-center justify-center",
64551
+ className
64552
+ ),
64553
+ children: loadingFallback ?? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
64554
+ /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "mx-auto size-6 animate-spin text-muted-foreground" }),
64555
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Loading..." })
64556
+ ] })
64557
+ }
64558
+ );
64559
+ }
64560
+ if (error2) {
64561
+ const resolvedErrorFallback = typeof errorFallback === "function" ? errorFallback(error2) : errorFallback;
64562
+ return /* @__PURE__ */ jsxRuntime.jsx(
64563
+ "div",
64564
+ {
64565
+ className: cn(
64566
+ "flex h-full w-full items-center justify-center p-4",
64567
+ className
64568
+ ),
64569
+ children: resolvedErrorFallback ?? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-sm text-center", children: [
64570
+ /* @__PURE__ */ jsxRuntime.jsx(CircleAlert, { className: "mx-auto size-5 text-red-400" }),
64571
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1.5 text-xs font-medium text-foreground", children: "Failed to load" }),
64572
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 line-clamp-2 break-words text-[10px] text-muted-foreground", children: error2 })
64573
+ ] })
64574
+ }
64575
+ );
64576
+ }
64577
+ if (!embedUrl) {
64578
+ return emptyFallback ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("h-full w-full", className), children: emptyFallback }) : null;
64579
+ }
64580
+ return /* @__PURE__ */ jsxRuntime.jsx(
64581
+ "iframe",
64582
+ {
64583
+ src: embedUrl,
64584
+ width: "100%",
64585
+ height: "100%",
64586
+ frameBorder: "0",
64587
+ allow: "fullscreen",
64588
+ title: title ?? `Asset ${assetId}`,
64589
+ className: cn("h-full w-full", className)
64590
+ }
64591
+ );
64592
+ };
64366
64593
  const ASSET_TYPE_CONFIG = {
64367
64594
  presentation: { icon: Presentation, label: "Presentation" },
64368
64595
  spreadsheet: { icon: FileSpreadsheet, label: "Spreadsheet" },
@@ -64372,38 +64599,7 @@ const ASSET_TYPE_CONFIG = {
64372
64599
  };
64373
64600
  const AssetIframe = React.memo(
64374
64601
  ({ tabId, tabName }) => {
64375
- const { backendUrl, apiKey, token } = useAthenaConfig();
64376
- const { embedUrl, isLoading, error: error2 } = useAssetEmbed(tabId, {
64377
- backendUrl,
64378
- apiKey,
64379
- token
64380
- });
64381
- if (isLoading) {
64382
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
64383
- /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "mx-auto size-6 animate-spin text-muted-foreground" }),
64384
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Loading..." })
64385
- ] }) });
64386
- }
64387
- if (error2) {
64388
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-sm text-center", children: [
64389
- /* @__PURE__ */ jsxRuntime.jsx(CircleAlert, { className: "mx-auto size-5 text-red-400" }),
64390
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1.5 text-xs font-medium text-foreground", children: "Failed to load" }),
64391
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 line-clamp-2 break-words text-[10px] text-muted-foreground", children: error2 })
64392
- ] }) });
64393
- }
64394
- if (!embedUrl) return null;
64395
- return /* @__PURE__ */ jsxRuntime.jsx(
64396
- "iframe",
64397
- {
64398
- src: embedUrl,
64399
- width: "100%",
64400
- height: "100%",
64401
- frameBorder: "0",
64402
- allow: "fullscreen",
64403
- title: tabName ?? `Asset ${tabId}`,
64404
- className: "h-full w-full"
64405
- }
64406
- );
64602
+ return /* @__PURE__ */ jsxRuntime.jsx(AssetRenderer, { assetId: tabId, title: tabName });
64407
64603
  }
64408
64604
  );
64409
64605
  AssetIframe.displayName = "AssetIframe";
@@ -64744,6 +64940,7 @@ const Toolkits = {
64744
64940
  };
64745
64941
  exports.AppendDocumentToolUI = AppendDocumentToolUI;
64746
64942
  exports.AssetPanel = AssetPanel;
64943
+ exports.AssetRenderer = AssetRenderer;
64747
64944
  exports.AthenaChat = AthenaChat;
64748
64945
  exports.AthenaLayout = AthenaLayout;
64749
64946
  exports.AthenaProvider = AthenaProvider;