@elevasis/ui 2.52.1 → 2.54.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.
@@ -126,6 +126,22 @@ interface PublicAgentChatConnectionState {
126
126
  error: string | null;
127
127
  }
128
128
 
129
+ /**
130
+ * Context passed to the `renderIntro` render-prop. Provides the start action,
131
+ * loading state, resolved display strings, raw branding, and the agent slug.
132
+ */
133
+ interface PublicAgentIntroRenderContext {
134
+ /** Begins the authorize → session flow (the same action the default CTA runs). */
135
+ start: () => void;
136
+ /** True while a session is being created — disable the button / show a spinner. */
137
+ starting: boolean;
138
+ /** Resolved display strings (prop/branding-derived), provided for convenience. */
139
+ title: string;
140
+ subtitle?: string;
141
+ /** Raw grant branding record, so a custom intro can still read intro/instructions/etc. if it wants. */
142
+ branding: Record<string, unknown>;
143
+ slug: string;
144
+ }
129
145
  interface PublicAgentChatProps {
130
146
  apiUrl: string;
131
147
  slug: string;
@@ -142,8 +158,16 @@ interface PublicAgentChatProps {
142
158
  * a specific public agent.
143
159
  */
144
160
  showAgentActivity?: boolean;
161
+ /**
162
+ * Render the entire pre-session intro screen yourself. When provided, this fully
163
+ * replaces the default branding-driven intro panel (paragraph + instructions + CTA).
164
+ * Compose any header/headline, body, styled callouts (e.g. a yellow Alert), and one or
165
+ * more buttons; wire your button(s) to `ctx.start()`. When omitted, the default
166
+ * branding-driven intro renders unchanged.
167
+ */
168
+ renderIntro?: (ctx: PublicAgentIntroRenderContext) => React.ReactNode;
145
169
  }
146
- declare function PublicAgentChat({ apiUrl, slug, visitorId, metadata, title, className, style, onSessionReady, showAgentActivity }: PublicAgentChatProps): react_jsx_runtime.JSX.Element;
170
+ declare function PublicAgentChat({ apiUrl, slug, visitorId, metadata, title, className, style, onSessionReady, showAgentActivity, renderIntro }: PublicAgentChatProps): react_jsx_runtime.JSX.Element;
147
171
 
148
172
  interface PublicAgentChatRoutePageProps extends PublicAgentChatProps {
149
173
  pageStyle?: React.CSSProperties;
@@ -172,4 +196,4 @@ declare const publicAgentChatKeys: {
172
196
  };
173
197
 
174
198
  export { PublicAgentChat, PublicAgentChatRoutePage, publicAgentChatKeys, usePublicAgentChatMessages, usePublicAgentChatWebSocket };
175
- export type { PublicAgentChatAuthorizeResponse, PublicAgentChatConnectionState, PublicAgentChatGrant, PublicAgentChatMessagesResponse, PublicAgentChatMetadataResponse, PublicAgentChatProps, PublicAgentChatRoutePageProps, PublicAgentChatSessionResponse };
199
+ export type { PublicAgentChatAuthorizeResponse, PublicAgentChatConnectionState, PublicAgentChatGrant, PublicAgentChatMessagesResponse, PublicAgentChatMetadataResponse, PublicAgentChatProps, PublicAgentChatRoutePageProps, PublicAgentChatSessionResponse, PublicAgentIntroRenderContext };
@@ -3,7 +3,7 @@ import { ChatInterface } from '../../chunk-E6NSKXYN.js';
3
3
  import { WS_MAX_RETRIES_BEFORE_ERROR, WS_RECONNECT_BASE_DELAY, WS_RECONNECT_MAX_DELAY, validateTokenLimit, TOKEN_LIMITS } from '../../chunk-FVOMKZ7S.js';
4
4
  import '../../chunk-TVTSASST.js';
5
5
  import '../../chunk-I2KLQ2HA.js';
6
- import { Center, Loader, Paper, Stack, Group, Title, Alert, PasswordInput, Button, Text, Box, Badge, Divider } from '@mantine/core';
6
+ import { Center, Loader, Paper, Stack, Group, Title, Alert, PasswordInput, Button, Text, List, Box, Badge, Divider } from '@mantine/core';
7
7
  import { IconLock, IconAlertCircle, IconSend, IconMessageCircle } from '@tabler/icons-react';
8
8
  import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
9
9
  import { useQuery, useQueryClient } from '@tanstack/react-query';
@@ -200,7 +200,14 @@ function usePublicAgentChatWebSocket(sessionId, apiUrl, capabilityToken) {
200
200
  async function fetchJson(url, init) {
201
201
  const response = await fetch(url, init);
202
202
  if (!response.ok) {
203
- throw new Error(`Request failed with status ${response.status}`);
203
+ let message = `Request failed with status ${response.status}`;
204
+ try {
205
+ const body = await response.json();
206
+ const apiMessage = typeof body.error === "string" && body.error.trim().length > 0 ? body.error : typeof body.message === "string" && body.message.trim().length > 0 ? body.message : null;
207
+ if (apiMessage) message = apiMessage;
208
+ } catch {
209
+ }
210
+ throw new Error(message);
204
211
  }
205
212
  return await response.json();
206
213
  }
@@ -221,6 +228,12 @@ function resolveAgentSubtitle(grant) {
221
228
  const branding = brandingRecord(grant?.branding);
222
229
  return brandingText(branding.subtitle) ?? brandingText(branding.description);
223
230
  }
231
+ function hasIntroContent(grant) {
232
+ const branding = brandingRecord(grant?.branding);
233
+ const hasIntroText = brandingText(branding.intro) !== void 0;
234
+ const hasInstructions = Array.isArray(branding.instructions) && branding.instructions.length > 0;
235
+ return hasIntroText || hasInstructions;
236
+ }
224
237
  function statusStyles(tone) {
225
238
  if (tone === "ready") {
226
239
  return {
@@ -373,7 +386,8 @@ function PublicAgentChat({
373
386
  className,
374
387
  style,
375
388
  onSessionReady,
376
- showAgentActivity = false
389
+ showAgentActivity = false,
390
+ renderIntro
377
391
  }) {
378
392
  const [grant, setGrant] = useState(null);
379
393
  const [capabilityToken, setCapabilityToken] = useState(null);
@@ -383,6 +397,7 @@ function PublicAgentChat({
383
397
  const [accessCode, setAccessCode] = useState("");
384
398
  const [input, setInput] = useState("");
385
399
  const autoStartedRef = useRef(false);
400
+ const greetingTimeRef = useRef(/* @__PURE__ */ new Date());
386
401
  const displayTitle = resolveAgentTitle(title, slug, grant);
387
402
  const displaySubtitle = resolveAgentSubtitle(grant);
388
403
  const { data: historyMessages = [] } = usePublicAgentChatMessages(apiUrl, session?.sessionId ?? null, capabilityToken);
@@ -403,7 +418,13 @@ function PublicAgentChat({
403
418
  fetchJson(`${apiUrl}/api/public/agent-chat/${encodeURIComponent(slug)}`).then((payload) => {
404
419
  if (cancelled) return;
405
420
  setGrant(payload.grant);
406
- setStatus(payload.grant.requiresCode ? "code-required" : "authorizing");
421
+ if (payload.grant.requiresCode) {
422
+ setStatus("code-required");
423
+ } else if (renderIntro || hasIntroContent(payload.grant)) {
424
+ setStatus("intro");
425
+ } else {
426
+ setStatus("authorizing");
427
+ }
407
428
  }).catch((requestError) => {
408
429
  if (cancelled) return;
409
430
  setError(requestError instanceof Error ? requestError.message : "Unable to load agent chat");
@@ -412,7 +433,7 @@ function PublicAgentChat({
412
433
  return () => {
413
434
  cancelled = true;
414
435
  };
415
- }, [apiUrl, slug]);
436
+ }, [apiUrl, slug, renderIntro]);
416
437
  const startSession = useCallback(
417
438
  async (code) => {
418
439
  if (!grant) return;
@@ -454,23 +475,34 @@ function PublicAgentChat({
454
475
  [apiUrl, grant, metadata, onSessionReady, slug, visitorId]
455
476
  );
456
477
  useEffect(() => {
457
- if (!grant || grant.requiresCode || autoStartedRef.current) {
478
+ if (!grant || grant.requiresCode || renderIntro || hasIntroContent(grant) || autoStartedRef.current) {
458
479
  return;
459
480
  }
460
481
  autoStartedRef.current = true;
461
482
  void startSession();
462
- }, [grant, startSession]);
463
- const allMessages = useMemo(
464
- () => mergeSessionMessages(historyMessages, liveMessages),
465
- [historyMessages, liveMessages]
466
- );
483
+ }, [grant, startSession, renderIntro]);
484
+ const allMessages = useMemo(() => {
485
+ const merged = mergeSessionMessages(historyMessages, liveMessages);
486
+ const greeting = brandingText(brandingRecord(grant?.branding).greeting);
487
+ if (!greeting) return merged;
488
+ const greetingMessage = {
489
+ id: "greeting-bubble",
490
+ role: "assistant",
491
+ messageType: "assistant_message",
492
+ text: greeting,
493
+ turnNumber: 0,
494
+ messageIndex: -1,
495
+ createdAt: greetingTimeRef.current
496
+ };
497
+ return [greetingMessage, ...merged];
498
+ }, [historyMessages, liveMessages, grant]);
467
499
  const handleSend = () => {
468
500
  const trimmed = input.trim();
469
501
  if (!trimmed || state.isProcessing) return;
470
502
  sendMessage(trimmed);
471
503
  setInput("");
472
504
  };
473
- if (status === "loading" || status === "authorizing") {
505
+ if (status === "loading" || status === "authorizing" && !renderIntro) {
474
506
  return /* @__PURE__ */ jsx(
475
507
  PublicAgentChatFrame,
476
508
  {
@@ -536,6 +568,75 @@ function PublicAgentChat({
536
568
  }
537
569
  );
538
570
  }
571
+ if ((status === "intro" || renderIntro && status === "authorizing") && grant) {
572
+ const branding = brandingRecord(grant.branding);
573
+ if (renderIntro) {
574
+ const ctx = {
575
+ start: () => {
576
+ void startSession();
577
+ },
578
+ starting: status === "authorizing",
579
+ title: displayTitle,
580
+ subtitle: displaySubtitle,
581
+ branding,
582
+ slug
583
+ };
584
+ return /* @__PURE__ */ jsx(
585
+ PublicAgentChatFrame,
586
+ {
587
+ className,
588
+ style,
589
+ title: displayTitle,
590
+ subtitle: displaySubtitle,
591
+ statusTone: "ready",
592
+ children: renderIntro(ctx)
593
+ }
594
+ );
595
+ }
596
+ const headlineText = brandingText(branding.headline);
597
+ const introText = brandingText(branding.intro);
598
+ const instructions = Array.isArray(branding.instructions) ? branding.instructions.filter((item) => typeof item === "string") : [];
599
+ const ctaLabel = brandingText(branding.ctaLabel) ?? "Start the conversation";
600
+ return /* @__PURE__ */ jsx(
601
+ PublicAgentChatFrame,
602
+ {
603
+ className,
604
+ style,
605
+ title: displayTitle,
606
+ subtitle: displaySubtitle,
607
+ statusTone: "ready",
608
+ children: /* @__PURE__ */ jsx(Center, { style: { height: "100%", padding: 16 }, children: /* @__PURE__ */ jsx(
609
+ Paper,
610
+ {
611
+ withBorder: true,
612
+ p: "xl",
613
+ radius: 8,
614
+ style: {
615
+ width: "min(100%, 480px)",
616
+ background: "var(--color-surface)",
617
+ borderColor: "var(--color-border)",
618
+ boxShadow: "var(--card-shadow)"
619
+ },
620
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "lg", children: [
621
+ headlineText && /* @__PURE__ */ jsx(Title, { order: 2, children: headlineText }),
622
+ introText && /* @__PURE__ */ jsx(Text, { size: "sm", style: { color: "var(--color-text)", lineHeight: 1.6 }, children: introText }),
623
+ instructions.length > 0 && /* @__PURE__ */ jsx(List, { size: "sm", style: { color: "var(--color-text-dimmed)" }, children: instructions.map((item, index) => /* @__PURE__ */ jsx(List.Item, { children: item }, index)) }),
624
+ /* @__PURE__ */ jsx(
625
+ Button,
626
+ {
627
+ onClick: () => {
628
+ void startSession();
629
+ },
630
+ leftSection: /* @__PURE__ */ jsx(IconSend, { size: 16 }),
631
+ children: ctaLabel
632
+ }
633
+ )
634
+ ] })
635
+ }
636
+ ) })
637
+ }
638
+ );
639
+ }
539
640
  if (status === "error") {
540
641
  return /* @__PURE__ */ jsx(
541
642
  PublicAgentChatFrame,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/ui",
3
- "version": "2.52.1",
3
+ "version": "2.54.0",
4
4
  "description": "UI components and platform-aware hooks for building custom frontends on the Elevasis platform",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -274,11 +274,11 @@
274
274
  "typescript": "5.9.2",
275
275
  "vite": "^7.0.0",
276
276
  "vitest": "^3.2.4",
277
- "@elevasis/sdk": "1.36.2",
278
- "@repo/core": "0.49.1",
277
+ "@elevasis/sdk": "1.36.4",
278
+ "@repo/core": "0.51.0",
279
+ "@repo/typescript-config": "0.0.0",
279
280
  "@repo/elevasis-core": "1.0.0",
280
- "@repo/eslint-config": "0.0.0",
281
- "@repo/typescript-config": "0.0.0"
281
+ "@repo/eslint-config": "0.0.0"
282
282
  },
283
283
  "dependencies": {
284
284
  "@dagrejs/dagre": "^1.1.4",