@aircall/blocks 0.10.1 → 0.11.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/globals.css +1 -1
- package/dist/index.d.ts +40 -4
- package/dist/index.js +527 -111
- package/package.json +1 -1
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,
|
|
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, 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: [
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
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__ */
|
|
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
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
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:
|
|
1519
|
+
children: scrollableContent
|
|
1111
1520
|
});
|
|
1112
|
-
return /* @__PURE__ */
|
|
1113
|
-
ref:
|
|
1521
|
+
return /* @__PURE__ */ jsxs(Sidebar, {
|
|
1522
|
+
ref: mergedRef,
|
|
1114
1523
|
collapsible: "none",
|
|
1115
|
-
className,
|
|
1116
|
-
children:
|
|
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__ */
|
|
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",
|
|
@@ -2708,4 +3124,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
|
|
|
2708
3124
|
}
|
|
2709
3125
|
|
|
2710
3126
|
//#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 };
|
|
3127
|
+
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 };
|