@aircall/blocks 0.10.1 → 0.12.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.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { Anchor, Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, HoverCard, HoverCardContent, HoverCardTrigger, InputGroup, InputGroupButton, InputGroupTextarea, Item, ItemActions, ItemContent, ItemGroup, ItemMedia, ItemTitle, Popover, PopoverContent, PopoverTrigger, Separator, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, TabsList, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
1
+ import { Anchor, Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, FieldLabelAside, FieldLabelInfo, FieldLabelRow, HoverCard, HoverCardContent, HoverCardTrigger, InputGroup, InputGroupButton, InputGroupTextarea, Item, ItemActions, ItemContent, ItemGroup, ItemMedia, ItemTitle, Popover, PopoverContent, PopoverTrigger, Separator, Sidebar, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, Spinner, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TabsList, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
2
2
  import * as React from "react";
3
- import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { AircallLogo, AircallMark, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, LockKeyhole, Menu, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, SquareArrowOutUpRight, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert } from "@aircall/react-icons";
5
+ import { AircallLoader, AircallLogo, AircallMark, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, LockKeyhole, Menu, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, SquareArrowOutUpRight, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert } from "@aircall/react-icons";
6
6
  import ReactMarkdown from "react-markdown";
7
7
  import remarkGfm from "remark-gfm";
8
8
  import { useCallbackRef, useControllableState, useIsMounted, useUnmount } from "@aircall/hooks";
@@ -60,7 +60,9 @@ const de = {
60
60
  "chatbotPanel.disclaimerBefore": "Sie interagieren mit einem KI-Assistenten (Beta). Durch die Nutzung dieser Funktion akzeptieren Sie die ",
61
61
  "chatbotPanel.disclaimerLink": "Beta-Bedingungen",
62
62
  "chatbotPanel.disclaimerAfter": " von Aircall. Antworten können ungenau oder unvollständig sein. Bitte überprüfen Sie sie, bevor Sie sich darauf verlassen.",
63
- "chatbotPanel.resizeHandle": "Panel-Größe ändern"
63
+ "chatbotPanel.resizeHandle": "Panel-Größe ändern",
64
+ "chatbotResponse.renderingComponent": "Komponente wird geladen...",
65
+ "chatbotResponse.thinking": "Wird bearbeitet..."
64
66
  };
65
67
  var de_default = de;
66
68
 
@@ -114,7 +116,9 @@ const en = {
114
116
  "chatbotPanel.disclaimerBefore": "You are interacting with an AI assistant (Beta). By using this feature you accept Aircall's ",
115
117
  "chatbotPanel.disclaimerLink": "Beta terms",
116
118
  "chatbotPanel.disclaimerAfter": ". Responses may be inaccurate or incomplete. Please verify before relying on them.",
117
- "chatbotPanel.resizeHandle": "Resize panel"
119
+ "chatbotPanel.resizeHandle": "Resize panel",
120
+ "chatbotResponse.renderingComponent": "Rendering component...",
121
+ "chatbotResponse.thinking": "Thinking..."
118
122
  };
119
123
  var en_default = en;
120
124
 
@@ -168,7 +172,9 @@ const es = {
168
172
  "chatbotPanel.disclaimerBefore": "Estás interactuando con un asistente de IA (Beta). Al usar esta función, aceptas los ",
169
173
  "chatbotPanel.disclaimerLink": "términos de la Beta",
170
174
  "chatbotPanel.disclaimerAfter": " de Aircall. Las respuestas pueden ser inexactas o incompletas. Verifícalas antes de confiar en ellas.",
171
- "chatbotPanel.resizeHandle": "Redimensionar el panel"
175
+ "chatbotPanel.resizeHandle": "Redimensionar el panel",
176
+ "chatbotResponse.renderingComponent": "Renderizando componente...",
177
+ "chatbotResponse.thinking": "Pensando..."
172
178
  };
173
179
  var es_default = es;
174
180
 
@@ -222,7 +228,9 @@ const fr = {
222
228
  "chatbotPanel.disclaimerBefore": "Vous interagissez avec un assistant IA (bêta). En utilisant cette fonctionnalité, vous acceptez les ",
223
229
  "chatbotPanel.disclaimerLink": "conditions de la bêta",
224
230
  "chatbotPanel.disclaimerAfter": " d’Aircall. Les réponses peuvent être inexactes ou incomplètes. Veuillez les vérifier avant de vous y fier.",
225
- "chatbotPanel.resizeHandle": "Redimensionner le panneau"
231
+ "chatbotPanel.resizeHandle": "Redimensionner le panneau",
232
+ "chatbotResponse.renderingComponent": "Rendu du composant...",
233
+ "chatbotResponse.thinking": "Réflexion en cours..."
226
234
  };
227
235
  var fr_default = fr;
228
236
 
@@ -276,7 +284,9 @@ const it = {
276
284
  "chatbotPanel.disclaimerBefore": "Stai interagendo con un assistente IA (Beta). Utilizzando questa funzionalità accetti i ",
277
285
  "chatbotPanel.disclaimerLink": "termini della Beta",
278
286
  "chatbotPanel.disclaimerAfter": " di Aircall. Le risposte possono essere inesatte o incomplete. Verificale prima di farne affidamento.",
279
- "chatbotPanel.resizeHandle": "Ridimensiona il pannello"
287
+ "chatbotPanel.resizeHandle": "Ridimensiona il pannello",
288
+ "chatbotResponse.renderingComponent": "Rendering del componente...",
289
+ "chatbotResponse.thinking": "Elaborazione in corso..."
280
290
  };
281
291
  var it_default = it;
282
292
 
@@ -533,6 +543,295 @@ const UnknownErrorMedia = UnknownError.Media;
533
543
  const UnknownErrorTitle = UnknownError.Title;
534
544
  const UnknownErrorDescription = UnknownError.Description;
535
545
 
546
+ //#endregion
547
+ //#region src/helpers/content.helpers.ts
548
+ function isTableCellValue(v) {
549
+ return v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean";
550
+ }
551
+ function parseTableComponent(obj) {
552
+ const { title, columns, rows } = obj;
553
+ if (typeof title !== "string") return null;
554
+ if (!Array.isArray(columns) || !columns.every((c) => typeof c === "string") || columns.length < 1) return null;
555
+ if (!Array.isArray(rows) || rows.length < 1 || !rows.every((row) => Array.isArray(row) && row.every(isTableCellValue))) return null;
556
+ return {
557
+ type: "table",
558
+ title,
559
+ columns,
560
+ rows
561
+ };
562
+ }
563
+ function parseAudioComponent(obj) {
564
+ const { url, title } = obj;
565
+ if (typeof url !== "string") return null;
566
+ try {
567
+ new URL(url);
568
+ } catch {
569
+ return null;
570
+ }
571
+ return {
572
+ type: "audio",
573
+ url,
574
+ ...typeof title === "string" ? { title } : {}
575
+ };
576
+ }
577
+ function parseTranscriptComponent(obj) {
578
+ const { title, utterances } = obj;
579
+ if (!Array.isArray(utterances) || utterances.length < 1) return null;
580
+ const parsed = [];
581
+ for (const u of utterances) {
582
+ if (typeof u !== "object" || u === null) return null;
583
+ const uObj = u;
584
+ if (typeof uObj.speaker !== "string" || typeof uObj.text !== "string") return null;
585
+ parsed.push({
586
+ speaker: uObj.speaker,
587
+ text: uObj.text,
588
+ ...typeof uObj.startTime === "number" ? { startTime: uObj.startTime } : {}
589
+ });
590
+ }
591
+ return {
592
+ type: "transcript",
593
+ utterances: parsed,
594
+ ...typeof title === "string" ? { title } : {}
595
+ };
596
+ }
597
+ function parseComponentData(obj) {
598
+ if (typeof obj !== "object" || obj === null) return null;
599
+ const record = obj;
600
+ if (record.type === "table") return parseTableComponent(record);
601
+ if (record.type === "audio") return parseAudioComponent(record);
602
+ if (record.type === "transcript") return parseTranscriptComponent(record);
603
+ return null;
604
+ }
605
+ function parseJsonLenient(raw) {
606
+ const trimmed = raw.trim();
607
+ try {
608
+ return JSON.parse(trimmed);
609
+ } catch {
610
+ if (trimmed[0] === "{") {
611
+ let depth = 0;
612
+ let inString = false;
613
+ let escaped = false;
614
+ for (let i = 0; i < trimmed.length; i++) {
615
+ const ch = trimmed[i];
616
+ if (escaped) {
617
+ escaped = false;
618
+ continue;
619
+ }
620
+ if (ch === "\\" && inString) {
621
+ escaped = true;
622
+ continue;
623
+ }
624
+ if (ch === "\"") {
625
+ inString = !inString;
626
+ continue;
627
+ }
628
+ if (inString) continue;
629
+ if (ch === "{" || ch === "[") depth++;
630
+ if (ch === "}" || ch === "]") depth--;
631
+ if (depth === 0) return JSON.parse(trimmed.slice(0, i + 1));
632
+ }
633
+ }
634
+ throw new Error("Invalid JSON");
635
+ }
636
+ }
637
+ /**
638
+ * Returns the [start, end) byte ranges of fenced code blocks in `text`.
639
+ * Used to skip `:::component` directives that appear inside code examples.
640
+ */
641
+ function getCodeFenceRanges(text) {
642
+ const ranges = [];
643
+ for (const m of text.matchAll(/^(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n\1[ \t]*$/gm)) ranges.push([m.index, m.index + m[0].length]);
644
+ const lastClosed = ranges.length ? ranges[ranges.length - 1][1] : 0;
645
+ const openMatch = text.slice(lastClosed).match(/^(`{3,}|~{3,})[^\n]*$/m);
646
+ if (openMatch && openMatch.index !== void 0) ranges.push([lastClosed + openMatch.index, text.length]);
647
+ return ranges;
648
+ }
649
+ function inAnyRange(pos, ranges) {
650
+ return ranges.some(([s, e]) => pos >= s && pos < e);
651
+ }
652
+ /**
653
+ * Parses AI-generated text and extracts `:::component <json> :::` directives.
654
+ * Returns an ordered list of text, component, and loading-component blocks.
655
+ *
656
+ * @param streaming - Pass `true` while the response is still streaming so that
657
+ * an unclosed directive emits a `loading-component` placeholder. When `false`
658
+ * (the default, for a completed response) an unclosed directive is treated as
659
+ * raw text so no content is silently dropped.
660
+ */
661
+ function parseContentBlocks(text) {
662
+ const COMPONENT_REGEX = /:::component\s+([\s\S]*?[}\]])\s*:::/g;
663
+ const fenceRanges = getCodeFenceRanges(text);
664
+ const blocks = [];
665
+ let lastIndex = 0;
666
+ for (const match of text.matchAll(COMPONENT_REGEX)) {
667
+ const matchStart = match.index ?? 0;
668
+ if (inAnyRange(matchStart, fenceRanges)) continue;
669
+ if (matchStart > lastIndex) {
670
+ const content = text.slice(lastIndex, matchStart).trim();
671
+ if (content) blocks.push({
672
+ type: "text",
673
+ content
674
+ });
675
+ }
676
+ try {
677
+ const data = parseComponentData(parseJsonLenient(match[1]));
678
+ blocks.push(data ? {
679
+ type: "component",
680
+ data
681
+ } : {
682
+ type: "text",
683
+ content: match[0]
684
+ });
685
+ } catch {
686
+ blocks.push({
687
+ type: "text",
688
+ content: match[0]
689
+ });
690
+ }
691
+ lastIndex = matchStart + match[0].length;
692
+ }
693
+ if (lastIndex < text.length) {
694
+ const remaining = text.slice(lastIndex);
695
+ let incompleteIdx = -1;
696
+ let searchFrom = 0;
697
+ while (incompleteIdx === -1) {
698
+ const idx = remaining.indexOf(":::component", searchFrom);
699
+ if (idx === -1) break;
700
+ if (!inAnyRange(lastIndex + idx, fenceRanges)) incompleteIdx = idx;
701
+ else searchFrom = idx + 1;
702
+ }
703
+ if (incompleteIdx !== -1) {
704
+ const before = remaining.slice(0, incompleteIdx).trim();
705
+ if (before) blocks.push({
706
+ type: "text",
707
+ content: before
708
+ });
709
+ blocks.push({ type: "loading-component" });
710
+ } else {
711
+ const content = remaining.trim();
712
+ if (content) blocks.push({
713
+ type: "text",
714
+ content
715
+ });
716
+ }
717
+ }
718
+ return blocks;
719
+ }
720
+
721
+ //#endregion
722
+ //#region src/components/chatbot-audio-block.tsx
723
+ /** Renders a :::component audio block inside a chatbot response. */
724
+ function ChatbotAudioBlock({ data }) {
725
+ return /* @__PURE__ */ jsxs("div", {
726
+ className: "flex flex-col gap-1",
727
+ children: [data.title && /* @__PURE__ */ jsx("p", {
728
+ className: "text-sm font-medium text-foreground",
729
+ children: data.title
730
+ }), /* @__PURE__ */ jsx("audio", {
731
+ controls: true,
732
+ src: data.url,
733
+ className: "w-full"
734
+ })]
735
+ });
736
+ }
737
+
738
+ //#endregion
739
+ //#region src/components/chatbot-loading-block.tsx
740
+ /** Placeholder shown while a :::component block is still streaming in. */
741
+ function ChatbotLoadingBlock() {
742
+ const t = useDsTranslation("blocks");
743
+ return /* @__PURE__ */ jsxs("div", {
744
+ className: "flex items-center justify-center gap-2 w-full rounded-md border border-border p-4",
745
+ children: [/* @__PURE__ */ jsx(Spinner, { size: "sm" }), /* @__PURE__ */ jsx("span", {
746
+ className: "text-sm text-muted-foreground",
747
+ children: t("chatbotResponse.renderingComponent")
748
+ })]
749
+ });
750
+ }
751
+
752
+ //#endregion
753
+ //#region src/components/chatbot-table-block.tsx
754
+ /** Renders a :::component table block inside a chatbot response. */
755
+ function ChatbotTableBlock({ data }) {
756
+ const { columns, rows } = data;
757
+ return /* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((col) => /* @__PURE__ */ jsx(TableHead, { children: col }, col)) }) }), /* @__PURE__ */ jsx(TableBody, { children: rows.map((row, rowIndex) => /* @__PURE__ */ jsx(TableRow, { children: row.map((cell, cellIndex) => /* @__PURE__ */ jsx(TableCell, { children: cell != null ? String(cell) : "" }, cellIndex)) }, rowIndex)) })] });
758
+ }
759
+
760
+ //#endregion
761
+ //#region src/components/chatbot-transcript-block.tsx
762
+ const SPEAKER_COLORS = [
763
+ {
764
+ text: "text-blue-600",
765
+ bg: "bg-blue-600"
766
+ },
767
+ {
768
+ text: "text-amber-600",
769
+ bg: "bg-amber-600"
770
+ },
771
+ {
772
+ text: "text-purple-600",
773
+ bg: "bg-purple-600"
774
+ },
775
+ {
776
+ text: "text-rose-600",
777
+ bg: "bg-rose-600"
778
+ },
779
+ {
780
+ text: "text-teal-600",
781
+ bg: "bg-teal-600"
782
+ }
783
+ ];
784
+ function formatTime(seconds) {
785
+ return `${Math.floor(seconds / 60)}:${Math.floor(seconds % 60).toString().padStart(2, "0")}`;
786
+ }
787
+ /** Renders a :::component transcript block inside a chatbot response. */
788
+ function ChatbotTranscriptBlock({ data }) {
789
+ const { title, utterances } = data;
790
+ const speakerColors = useMemo(() => {
791
+ const map = /* @__PURE__ */ new Map();
792
+ let idx = 0;
793
+ for (const u of utterances) if (!map.has(u.speaker)) {
794
+ map.set(u.speaker, SPEAKER_COLORS[idx % SPEAKER_COLORS.length]);
795
+ idx++;
796
+ }
797
+ return map;
798
+ }, [utterances]);
799
+ return /* @__PURE__ */ jsxs("div", {
800
+ className: "flex flex-col gap-1",
801
+ children: [title && /* @__PURE__ */ jsx("p", {
802
+ className: "text-sm font-medium text-foreground",
803
+ children: title
804
+ }), /* @__PURE__ */ jsx("div", {
805
+ className: "flex flex-col gap-3 my-2 mx-2",
806
+ children: utterances.map((utterance, index) => {
807
+ const colors = speakerColors.get(utterance.speaker);
808
+ const prevSpeaker = index > 0 ? utterances[index - 1].speaker : null;
809
+ const showName = utterance.speaker !== prevSpeaker;
810
+ return /* @__PURE__ */ jsxs("div", {
811
+ className: "flex gap-2 items-stretch",
812
+ children: [
813
+ /* @__PURE__ */ jsx("div", { className: cn("w-0.5 rounded-sm shrink-0", colors.bg) }),
814
+ /* @__PURE__ */ jsxs("div", {
815
+ className: "flex flex-col gap-0.5 flex-1 min-w-0",
816
+ children: [showName && /* @__PURE__ */ jsx("p", {
817
+ className: cn("text-xs font-semibold", colors.text),
818
+ children: utterance.speaker
819
+ }), /* @__PURE__ */ jsx("p", {
820
+ className: "text-sm text-foreground",
821
+ children: utterance.text
822
+ })]
823
+ }),
824
+ utterance.startTime != null && /* @__PURE__ */ jsx("p", {
825
+ className: "text-xs text-muted-foreground shrink-0",
826
+ children: formatTime(utterance.startTime)
827
+ })
828
+ ]
829
+ }, index);
830
+ })
831
+ })]
832
+ });
833
+ }
834
+
536
835
  //#endregion
537
836
  //#region src/hooks/use-copy-to-clipboard.ts
538
837
  function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
@@ -651,6 +950,65 @@ function SourcesBadge({ sources }) {
651
950
  }, source.url)) })
652
951
  })] });
653
952
  }
953
+ const MARKDOWN_COMPONENTS = {
954
+ h1: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h1", {
955
+ className: "mt-10 text-4xl font-semibold first:mt-0",
956
+ ...props
957
+ }),
958
+ h2: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h2", {
959
+ className: "mt-11 border-b border-border pb-2 text-3xl font-semibold leading-9 first:mt-0",
960
+ ...props
961
+ }),
962
+ h3: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h3", {
963
+ className: "mt-10 text-2xl font-semibold leading-8 first:mt-0",
964
+ ...props
965
+ }),
966
+ p: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("p", {
967
+ className: "mt-4 text-base leading-6 text-foreground first:mt-0",
968
+ ...props
969
+ }),
970
+ blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("blockquote", {
971
+ className: "mt-4 border-l-2 border-border pl-6 first:mt-0",
972
+ ...props
973
+ }),
974
+ ul: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ul", {
975
+ className: "mt-4 list-disc pl-6 text-base leading-6 text-foreground first:mt-0",
976
+ ...props
977
+ }),
978
+ ol: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ol", {
979
+ className: "mt-4 list-decimal pl-6 text-base leading-6 text-foreground first:mt-0",
980
+ ...props
981
+ }),
982
+ li: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("li", {
983
+ className: "mb-2 last:mb-0",
984
+ ...props
985
+ }),
986
+ a: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", {
987
+ className: "text-green-700 underline underline-offset-2 hover:text-green-700/80",
988
+ ...props
989
+ }),
990
+ table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", {
991
+ className: "mt-4 overflow-x-auto rounded border border-border first:mt-0",
992
+ children: /* @__PURE__ */ jsx("table", {
993
+ className: "w-full text-base leading-6",
994
+ ...props
995
+ })
996
+ }),
997
+ th: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("th", {
998
+ className: "border-t border-border px-4 py-2 text-left font-bold text-foreground",
999
+ style,
1000
+ ...props
1001
+ }),
1002
+ td: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("td", {
1003
+ className: "border-t border-border px-4 py-2 text-foreground",
1004
+ style,
1005
+ ...props
1006
+ }),
1007
+ tr: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("tr", {
1008
+ className: "even:bg-muted",
1009
+ ...props
1010
+ })
1011
+ };
654
1012
  function ChatbotResponseBlock({ messageId, markdown, streaming = false, sources, onFeedback, className }) {
655
1013
  const t = useDsTranslation("blocks");
656
1014
  const [feedback, setFeedback] = useState(null);
@@ -658,6 +1016,7 @@ function ChatbotResponseBlock({ messageId, markdown, streaming = false, sources,
658
1016
  const isMounted = useRef(false);
659
1017
  const feedbackRef = useRef(feedback);
660
1018
  feedbackRef.current = feedback;
1019
+ const blocks = useMemo(() => parseContentBlocks(markdown), [markdown]);
661
1020
  useEffect(() => {
662
1021
  if (!isMounted.current) {
663
1022
  isMounted.current = true;
@@ -679,68 +1038,19 @@ function ChatbotResponseBlock({ messageId, markdown, streaming = false, sources,
679
1038
  return /* @__PURE__ */ jsxs("div", {
680
1039
  "data-slot": "chatbot-response-block",
681
1040
  className: cn("flex flex-col gap-2", className),
682
- children: [/* @__PURE__ */ jsx(ReactMarkdown, {
683
- remarkPlugins: [remarkGfm],
684
- components: {
685
- h1: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h1", {
686
- className: "mt-10 text-4xl font-semibold first:mt-0",
687
- ...props
688
- }),
689
- h2: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h2", {
690
- className: "mt-11 border-b border-border pb-2 text-3xl font-semibold leading-9 first:mt-0",
691
- ...props
692
- }),
693
- h3: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h3", {
694
- className: "mt-10 text-2xl font-semibold leading-8 first:mt-0",
695
- ...props
696
- }),
697
- p: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("p", {
698
- className: "mt-4 text-base leading-6 text-foreground first:mt-0",
699
- ...props
700
- }),
701
- blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("blockquote", {
702
- className: "mt-4 border-l-2 border-border pl-6 first:mt-0",
703
- ...props
704
- }),
705
- ul: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ul", {
706
- className: "mt-4 list-disc pl-6 text-base leading-6 text-foreground first:mt-0",
707
- ...props
708
- }),
709
- ol: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ol", {
710
- className: "mt-4 list-decimal pl-6 text-base leading-6 text-foreground first:mt-0",
711
- ...props
712
- }),
713
- li: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("li", {
714
- className: "mb-2 last:mb-0",
715
- ...props
716
- }),
717
- a: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", {
718
- className: "text-green-700 underline underline-offset-2 hover:text-green-700/80",
719
- ...props
720
- }),
721
- table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", {
722
- className: "mt-4 overflow-x-auto rounded border border-border first:mt-0",
723
- children: /* @__PURE__ */ jsx("table", {
724
- className: "w-full text-base leading-6",
725
- ...props
726
- })
727
- }),
728
- th: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("th", {
729
- className: "border-t border-border px-4 py-2 text-left font-bold text-foreground",
730
- style,
731
- ...props
732
- }),
733
- td: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("td", {
734
- className: "border-t border-border px-4 py-2 text-foreground",
735
- style,
736
- ...props
737
- }),
738
- tr: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("tr", {
739
- className: "even:bg-muted",
740
- ...props
741
- })
742
- },
743
- children: markdown
1041
+ children: [blocks.map((block, index) => {
1042
+ if (block.type === "text") return /* @__PURE__ */ jsx(ReactMarkdown, {
1043
+ remarkPlugins: [remarkGfm],
1044
+ components: MARKDOWN_COMPONENTS,
1045
+ children: block.content
1046
+ }, index);
1047
+ if (block.type === "component") {
1048
+ if (block.data.type === "table") return /* @__PURE__ */ jsx(ChatbotTableBlock, { data: block.data }, index);
1049
+ if (block.data.type === "audio") return /* @__PURE__ */ jsx(ChatbotAudioBlock, { data: block.data }, index);
1050
+ if (block.data.type === "transcript") return /* @__PURE__ */ jsx(ChatbotTranscriptBlock, { data: block.data }, index);
1051
+ }
1052
+ if (block.type === "loading-component") return /* @__PURE__ */ jsx(ChatbotLoadingBlock, {}, index);
1053
+ return null;
744
1054
  }), !streaming && /* @__PURE__ */ jsxs("div", {
745
1055
  className: "flex items-center gap-1",
746
1056
  children: [sources && sources.length > 0 && /* @__PURE__ */ jsx("div", {
@@ -782,6 +1092,38 @@ function ChatbotResponseBlock({ messageId, markdown, streaming = false, sources,
782
1092
  });
783
1093
  }
784
1094
 
1095
+ //#endregion
1096
+ //#region src/components/chatbot-response-loading.tsx
1097
+ /**
1098
+ * Loading indicator rendered inside `ChatbotPageConversation` while the backend
1099
+ * is still streaming a response. Shows the Aircall animated logo next to a
1100
+ * customisable shimmer text label.
1101
+ *
1102
+ * @example
1103
+ * ```tsx
1104
+ * <ChatbotPageConversation>
1105
+ * <ChatbotUserMessage>How do I set my phone line?</ChatbotUserMessage>
1106
+ * {isLoading && <ChatbotResponseLoading text="Thinking..." />}
1107
+ * </ChatbotPageConversation>
1108
+ * ```
1109
+ */
1110
+ function ChatbotResponseLoading({ text, className }) {
1111
+ const t = useDsTranslation("blocks");
1112
+ const label = text ?? t("chatbotResponse.thinking");
1113
+ return /* @__PURE__ */ jsxs("div", {
1114
+ "data-slot": "chatbot-response-loading",
1115
+ className: cn("flex items-center gap-2", className),
1116
+ children: [/* @__PURE__ */ jsx(AircallLoader, {}), /* @__PURE__ */ jsx("span", {
1117
+ className: cn("text-sm bg-clip-text text-transparent", "motion-safe:animate-chatbot-shimmer"),
1118
+ style: {
1119
+ backgroundImage: "linear-gradient(90deg, var(--color-muted-foreground) 25%, var(--color-foreground) 50%, var(--color-muted-foreground) 75%)",
1120
+ backgroundSize: "200% auto"
1121
+ },
1122
+ children: label
1123
+ })]
1124
+ });
1125
+ }
1126
+
785
1127
  //#endregion
786
1128
  //#region src/components/chatbot-page.tsx
787
1129
  const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
@@ -919,14 +1261,18 @@ ChatbotPageFooter.displayName = "ChatbotPageFooter";
919
1261
  * ```
920
1262
  */
921
1263
  const ChatbotPageConversation = React.forwardRef((componentProps, forwardRef) => {
922
- const { className, children, ...props } = componentProps;
1264
+ const { className, children, loading = false, loadingText, ...props } = componentProps;
923
1265
  const innerRef = React.useRef(null);
924
1266
  const [atBottom, setAtBottom] = React.useState(true);
1267
+ const atBottomRef = React.useRef(true);
1268
+ const prevChildCountRef = React.useRef(React.Children.count(children));
925
1269
  const t = useDsTranslation("blocks");
926
1270
  const handleScroll = React.useCallback(() => {
927
1271
  const el = innerRef.current;
928
1272
  if (!el) return;
929
- setAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 8);
1273
+ const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 8;
1274
+ setAtBottom(isAtBottom);
1275
+ atBottomRef.current = isAtBottom;
930
1276
  }, []);
931
1277
  React.useEffect(() => {
932
1278
  const el = innerRef.current;
@@ -934,6 +1280,35 @@ const ChatbotPageConversation = React.forwardRef((componentProps, forwardRef) =>
934
1280
  el.addEventListener("scroll", handleScroll, { passive: true });
935
1281
  return () => el.removeEventListener("scroll", handleScroll);
936
1282
  }, [handleScroll]);
1283
+ React.useEffect(() => {
1284
+ const count = React.Children.count(children);
1285
+ const isNewMessage = count > prevChildCountRef.current;
1286
+ prevChildCountRef.current = count;
1287
+ const el = innerRef.current;
1288
+ if (!el) return;
1289
+ if (isNewMessage) {
1290
+ el.scrollTo({
1291
+ top: el.scrollHeight,
1292
+ behavior: "smooth"
1293
+ });
1294
+ atBottomRef.current = true;
1295
+ setAtBottom(true);
1296
+ } else if (atBottomRef.current) el.scrollTo({
1297
+ top: el.scrollHeight,
1298
+ behavior: "instant"
1299
+ });
1300
+ }, [children]);
1301
+ React.useEffect(() => {
1302
+ if (!loading) return;
1303
+ const el = innerRef.current;
1304
+ if (!el) return;
1305
+ el.scrollTo({
1306
+ top: el.scrollHeight,
1307
+ behavior: "smooth"
1308
+ });
1309
+ atBottomRef.current = true;
1310
+ setAtBottom(true);
1311
+ }, [loading]);
937
1312
  const scrollToBottom = React.useCallback(() => {
938
1313
  innerRef.current?.scrollTo({
939
1314
  top: innerRef.current.scrollHeight,
@@ -949,10 +1324,10 @@ const ChatbotPageConversation = React.forwardRef((componentProps, forwardRef) =>
949
1324
  "data-slot": "chatbot-page-conversation",
950
1325
  className: cn("relative z-[2] flex flex-col overflow-hidden w-full max-w-[896px] flex-1", className),
951
1326
  ...props,
952
- children: [/* @__PURE__ */ jsx("div", {
1327
+ children: [/* @__PURE__ */ jsxs("div", {
953
1328
  ref: mergedRef,
954
1329
  className: cn("flex flex-1 flex-col gap-6 overflow-y-auto px-6 py-4", "[scrollbar-width:thin] [scrollbar-color:transparent_transparent] hover:[scrollbar-color:rgba(0,0,0,0.15)_transparent]", "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-transparent [&:hover::-webkit-scrollbar-thumb]:bg-black/20"),
955
- children
1330
+ children: [children, loading && /* @__PURE__ */ jsx(ChatbotResponseLoading, { text: loadingText })]
956
1331
  }), !atBottom && /* @__PURE__ */ jsx("div", {
957
1332
  className: "absolute bottom-4 right-6 z-10",
958
1333
  children: /* @__PURE__ */ jsx(Button, {
@@ -1067,53 +1442,93 @@ function ChatbotPanelHeader({ title = "Ask Aircall", onMenu, menuContent, menuOp
1067
1442
 
1068
1443
  //#endregion
1069
1444
  //#region src/components/chatbot-sidebar.tsx
1445
+ const SIDEBAR_MIN_WIDTH = 140;
1446
+ const SIDEBAR_MAX_WIDTH = 480;
1070
1447
  const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
1071
1448
  const { conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, recentLabel = "Recent", popup = false, className } = componentProps;
1072
1449
  const t = useDsTranslation("blocks");
1073
- const content = /* @__PURE__ */ jsxs(SidebarContent, { children: [/* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
1074
- isActive: !activeConversationId || !conversations.some((c) => c.id === activeConversationId),
1075
- onClick: onNewChat,
1076
- children: [/* @__PURE__ */ jsx(Plus, {}), /* @__PURE__ */ jsx("span", { children: t("chatbotSidebar.newChat") })]
1077
- }) }) }) }), conversations.length > 0 && /* @__PURE__ */ jsxs(SidebarGroup, { children: [/* @__PURE__ */ jsx(SidebarGroupLabel, { children: recentLabel }), /* @__PURE__ */ jsx(SidebarMenu, { children: conversations.map((conversation) => {
1078
- const isActive = conversation.id === activeConversationId;
1079
- return /* @__PURE__ */ jsx(SidebarMenuItem, { children: renderConversationMenuContent ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1080
- /* @__PURE__ */ jsx(SidebarMenuButton, {
1450
+ const isNewChatActive = !activeConversationId || !conversations.some((c) => c.id === activeConversationId);
1451
+ const sidebarInternalRef = React.useRef(null);
1452
+ const mergedRef = React.useCallback((node) => {
1453
+ sidebarInternalRef.current = node;
1454
+ if (typeof forwardRef === "function") forwardRef(node);
1455
+ else if (forwardRef) forwardRef.current = node;
1456
+ }, [forwardRef]);
1457
+ const handleResizeMouseDown = React.useCallback((e) => {
1458
+ e.preventDefault();
1459
+ const el = sidebarInternalRef.current;
1460
+ if (!el) return;
1461
+ const startX = e.clientX;
1462
+ const startWidth = el.getBoundingClientRect().width;
1463
+ document.body.style.cursor = "col-resize";
1464
+ document.body.style.userSelect = "none";
1465
+ const onMouseMove = (e$1) => {
1466
+ const newWidth = Math.min(Math.max(startWidth + (e$1.clientX - startX), SIDEBAR_MIN_WIDTH), SIDEBAR_MAX_WIDTH);
1467
+ el.style.setProperty("--sidebar-width", `${newWidth}px`);
1468
+ };
1469
+ const onMouseUp = () => {
1470
+ document.body.style.cursor = "";
1471
+ document.body.style.userSelect = "";
1472
+ document.removeEventListener("mousemove", onMouseMove);
1473
+ document.removeEventListener("mouseup", onMouseUp);
1474
+ };
1475
+ document.addEventListener("mousemove", onMouseMove);
1476
+ document.addEventListener("mouseup", onMouseUp);
1477
+ }, []);
1478
+ const scrollableContent = /* @__PURE__ */ jsxs("div", {
1479
+ "data-slot": "sidebar-content",
1480
+ className: cn("flex min-h-0 flex-1 flex-col gap-2 overflow-auto", "[scrollbar-width:thin] [scrollbar-color:transparent_transparent] hover:[scrollbar-color:rgba(0,0,0,0.15)_transparent]", "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full", "[&::-webkit-scrollbar-thumb]:bg-transparent [&:hover::-webkit-scrollbar-thumb]:bg-black/20"),
1481
+ children: [/* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
1482
+ isActive: isNewChatActive,
1483
+ onClick: onNewChat,
1484
+ children: [/* @__PURE__ */ jsx(Plus, {}), /* @__PURE__ */ jsx("span", { children: t("chatbotSidebar.newChat") })]
1485
+ }) }) }) }), conversations.length > 0 && /* @__PURE__ */ jsxs(SidebarGroup, { children: [/* @__PURE__ */ jsx(SidebarGroupLabel, { children: recentLabel }), /* @__PURE__ */ jsx(SidebarMenu, { children: conversations.map((conversation) => {
1486
+ const isActive = conversation.id === activeConversationId;
1487
+ return /* @__PURE__ */ jsx(SidebarMenuItem, { children: renderConversationMenuContent ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1488
+ /* @__PURE__ */ jsx(SidebarMenuButton, {
1489
+ isActive,
1490
+ onClick: () => onSelectConversation?.(conversation.id),
1491
+ children: /* @__PURE__ */ jsx("span", {
1492
+ className: "min-w-0 truncate",
1493
+ children: conversation.title
1494
+ })
1495
+ }),
1496
+ /* @__PURE__ */ jsx(SidebarMenuAction, {
1497
+ showOnHover: true,
1498
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, {}),
1499
+ "aria-label": t("chatbotSidebar.optionsFor", { title: conversation.title }),
1500
+ children: /* @__PURE__ */ jsx(Ellipsis, {})
1501
+ }),
1502
+ /* @__PURE__ */ jsx(DropdownMenuContent, {
1503
+ side: "right",
1504
+ children: renderConversationMenuContent(conversation)
1505
+ })
1506
+ ] }) : /* @__PURE__ */ jsx(SidebarMenuButton, {
1081
1507
  isActive,
1082
1508
  onClick: () => onSelectConversation?.(conversation.id),
1083
1509
  children: /* @__PURE__ */ jsx("span", {
1084
1510
  className: "min-w-0 truncate",
1085
1511
  children: conversation.title
1086
1512
  })
1087
- }),
1088
- /* @__PURE__ */ jsx(SidebarMenuAction, {
1089
- showOnHover: true,
1090
- render: /* @__PURE__ */ jsx(DropdownMenuTrigger, {}),
1091
- "aria-label": t("chatbotSidebar.optionsFor", { title: conversation.title }),
1092
- children: /* @__PURE__ */ jsx(Ellipsis, {})
1093
- }),
1094
- /* @__PURE__ */ jsx(DropdownMenuContent, {
1095
- side: "right",
1096
- children: renderConversationMenuContent(conversation)
1097
- })
1098
- ] }) : /* @__PURE__ */ jsx(SidebarMenuButton, {
1099
- isActive,
1100
- onClick: () => onSelectConversation?.(conversation.id),
1101
- children: /* @__PURE__ */ jsx("span", {
1102
- className: "min-w-0 truncate",
1103
- children: conversation.title
1104
- })
1105
- }) }, conversation.id);
1106
- }) })] })] });
1513
+ }) }, conversation.id);
1514
+ }) })] })]
1515
+ });
1107
1516
  if (popup) return /* @__PURE__ */ jsx("div", {
1108
1517
  ref: forwardRef,
1109
1518
  className: cn("flex h-full w-full flex-col", className),
1110
- children: content
1519
+ children: scrollableContent
1111
1520
  });
1112
- return /* @__PURE__ */ jsx(Sidebar, {
1113
- ref: forwardRef,
1521
+ return /* @__PURE__ */ jsxs(Sidebar, {
1522
+ ref: mergedRef,
1114
1523
  collapsible: "none",
1115
- className,
1116
- children: content
1524
+ className: cn("relative", className),
1525
+ children: [scrollableContent, /* @__PURE__ */ jsx("div", {
1526
+ role: "separator",
1527
+ "aria-orientation": "vertical",
1528
+ className: "group/resize absolute inset-y-0 right-0 z-10 w-1 cursor-col-resize",
1529
+ onMouseDown: handleResizeMouseDown,
1530
+ children: /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 right-0 w-px bg-border transition-colors group-hover/resize:bg-muted-foreground/40" })
1531
+ })]
1117
1532
  });
1118
1533
  });
1119
1534
  ChatbotSidebar.displayName = "ChatbotSidebar";
@@ -1642,7 +2057,7 @@ const MAX_WIDTH = 768;
1642
2057
  const KEYBOARD_RESIZE_STEP = 16;
1643
2058
  const DISCLAIMER_LINK_HREF = "https://legal.aircall.io/#template-9jw4r0usl";
1644
2059
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
1645
- function ChatbotPanel({ title, onMenu, conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, onExpand, onClose, emptyTitle, emptyDescription, suggestions = [], onSuggestionSelect, inputValue, onInputValueChange, onSubmit, onStop, loading = false, placeholder, autoFocus, disclaimer, resizable = true, defaultWidth = DEFAULT_WIDTH, minWidth = MIN_WIDTH, maxWidth = MAX_WIDTH, onWidthChange, className, style, children, ...props }) {
2060
+ function ChatbotPanel({ title, onMenu, conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, onExpand, onClose, emptyTitle, emptyDescription, suggestions = [], onSuggestionSelect, inputValue, onInputValueChange, onSubmit, onStop, loading = false, loadingText, placeholder, autoFocus, disclaimer, resizable = true, defaultWidth = DEFAULT_WIDTH, minWidth = MIN_WIDTH, maxWidth = MAX_WIDTH, onWidthChange, className, style, children, ...props }) {
1646
2061
  const t = useDsTranslation("blocks");
1647
2062
  const [menuOpen, setMenuOpen] = React.useState(false);
1648
2063
  const [width, setWidth] = React.useState(() => clamp(defaultWidth, minWidth, maxWidth));
@@ -1697,6 +2112,7 @@ function ChatbotPanel({ title, onMenu, conversations, activeConversationId, onNe
1697
2112
  }, [
1698
2113
  children,
1699
2114
  width,
2115
+ loading,
1700
2116
  updateScrollState
1701
2117
  ]);
1702
2118
  const scrollToBottom = () => {
@@ -1780,9 +2196,9 @@ function ChatbotPanel({ title, onMenu, conversations, activeConversationId, onNe
1780
2196
  "data-slot": "chatbot-panel-body",
1781
2197
  onScroll: updateScrollState,
1782
2198
  className: "flex min-h-0 flex-1 flex-col overflow-y-auto px-6 py-4",
1783
- children: hasConversation ? /* @__PURE__ */ jsx("div", {
2199
+ children: hasConversation ? /* @__PURE__ */ jsxs("div", {
1784
2200
  className: "flex flex-col gap-6",
1785
- children
2201
+ children: [children, loading && /* @__PURE__ */ jsx(ChatbotResponseLoading, { text: loadingText })]
1786
2202
  }) : /* @__PURE__ */ jsxs("div", {
1787
2203
  "data-slot": "chatbot-panel-empty",
1788
2204
  className: "flex flex-1 flex-col items-center",
@@ -1952,15 +2368,32 @@ function toFieldErrors(errors) {
1952
2368
  //#region src/components/form-field.tsx
1953
2369
  function FormFieldShell({ label, description, necessityIndicator, children }) {
1954
2370
  const errors = useFieldContext().state.meta.errors;
2371
+ const labelContent = typeof label === "string" ? label : label?.content;
2372
+ const labelInfo = typeof label === "object" ? label.info : void 0;
2373
+ const labelAside = typeof label === "object" ? label.aside : void 0;
2374
+ const hasAddons = labelInfo !== void 0 || labelAside !== void 0;
2375
+ const descriptionContent = typeof description === "string" ? description : description?.content;
2376
+ const descriptionVariant = (typeof description === "object" ? description.variant : void 0) ?? "instructional";
2377
+ const labelNode = labelContent !== void 0 && /* @__PURE__ */ jsx(FieldLabel, {
2378
+ necessityIndicator,
2379
+ children: labelContent
2380
+ });
2381
+ const labelRow = labelContent !== void 0 && (hasAddons ? /* @__PURE__ */ jsxs(FieldLabelRow, { children: [
2382
+ labelNode,
2383
+ labelInfo !== void 0 && /* @__PURE__ */ jsx(FieldLabelInfo, { children: labelInfo }),
2384
+ labelAside !== void 0 && /* @__PURE__ */ jsx(FieldLabelAside, { children: labelAside })
2385
+ ] }) : labelNode);
2386
+ const descriptionNode = descriptionContent !== void 0 && /* @__PURE__ */ jsx(FieldDescription, {
2387
+ variant: descriptionVariant,
2388
+ children: descriptionContent
2389
+ });
1955
2390
  return /* @__PURE__ */ jsxs(Field, {
1956
2391
  "data-invalid": errors.length > 0 ? true : void 0,
1957
2392
  children: [
1958
- label && /* @__PURE__ */ jsx(FieldLabel, {
1959
- necessityIndicator,
1960
- children: label
1961
- }),
1962
- description && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
2393
+ labelRow,
2394
+ descriptionVariant === "instructional" && descriptionNode,
1963
2395
  children,
2396
+ descriptionVariant === "contextual" && descriptionNode,
1964
2397
  /* @__PURE__ */ jsx(FieldError, { errors: toFieldErrors(errors) })
1965
2398
  ]
1966
2399
  });
@@ -2143,7 +2576,7 @@ function FormMultiComboboxField(props) {
2143
2576
  * Each row is composed from the existing single-control wrappers with an indexed `name`.
2144
2577
  *
2145
2578
  * @example
2146
- * <FormArrayField form={form} name="webhooks" label="Webhooks" description="Endpoints to notify">
2579
+ * <FormArrayField form={form} name="webhooks" label="Webhooks" description={{ content: 'Endpoints to notify', variant: 'contextual' }}>
2147
2580
  * {field => (
2148
2581
  * <>
2149
2582
  * {field.state.value.map((_row, i) => (
@@ -2708,4 +3141,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
2708
3141
  }
2709
3142
 
2710
3143
  //#endregion
2711
- export { BetaBadge, CardSaveBar, ChatbotExpandSuggestion, ChatbotInput, ChatbotPage, ChatbotPageBody, ChatbotPageContent, ChatbotPageConversation, ChatbotPageFooter, ChatbotPageHeading, ChatbotPageInputBar, ChatbotPanel, ChatbotPanelHeader, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotResponseBlock, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormArrayField, FormComboboxField, FormFieldBase, FormFieldShell, FormInputField, FormMultiComboboxField, FormNumericField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withFieldGroup, withForm };
3144
+ export { BetaBadge, CardSaveBar, ChatbotExpandSuggestion, ChatbotInput, ChatbotPage, ChatbotPageBody, ChatbotPageContent, ChatbotPageConversation, ChatbotPageFooter, ChatbotPageHeading, ChatbotPageInputBar, ChatbotPanel, ChatbotPanelHeader, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotResponseBlock, ChatbotResponseLoading, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormArrayField, FormComboboxField, FormFieldBase, FormFieldShell, FormInputField, FormMultiComboboxField, FormNumericField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withFieldGroup, withForm };