myoperator-ui 0.0.225 → 0.0.227
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 +946 -248
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14346,7 +14346,8 @@ export type { BrandIconProps } from "./icon";
|
|
|
14346
14346
|
"badge",
|
|
14347
14347
|
"button",
|
|
14348
14348
|
"dialog",
|
|
14349
|
-
"dropdown-menu"
|
|
14349
|
+
"dropdown-menu",
|
|
14350
|
+
"tooltip"
|
|
14350
14351
|
],
|
|
14351
14352
|
isMultiFile: true,
|
|
14352
14353
|
directory: "bots",
|
|
@@ -14533,6 +14534,12 @@ import {
|
|
|
14533
14534
|
DialogTitle,
|
|
14534
14535
|
} from "../dialog";
|
|
14535
14536
|
import { Button } from "../button";
|
|
14537
|
+
import {
|
|
14538
|
+
Tooltip,
|
|
14539
|
+
TooltipContent,
|
|
14540
|
+
TooltipProvider,
|
|
14541
|
+
TooltipTrigger,
|
|
14542
|
+
} from "../tooltip";
|
|
14536
14543
|
import { BOT_TYPE, type CreateBotModalProps, type BotType } from "./types";
|
|
14537
14544
|
|
|
14538
14545
|
interface BotTypeOption {
|
|
@@ -14554,137 +14561,230 @@ const BOT_TYPE_OPTIONS: BotTypeOption[] = [
|
|
|
14554
14561
|
},
|
|
14555
14562
|
];
|
|
14556
14563
|
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14564
|
+
function getFirstEnabledBotType(
|
|
14565
|
+
chatbotDisabled: boolean,
|
|
14566
|
+
voicebotDisabled: boolean
|
|
14567
|
+
): BotType {
|
|
14568
|
+
if (!chatbotDisabled) return "chatbot";
|
|
14569
|
+
if (!voicebotDisabled) return "voicebot";
|
|
14570
|
+
return "chatbot";
|
|
14571
|
+
}
|
|
14560
14572
|
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14573
|
+
function isBotTypeDisabled(
|
|
14574
|
+
type: BotType,
|
|
14575
|
+
chatbotDisabled: boolean,
|
|
14576
|
+
voicebotDisabled: boolean
|
|
14577
|
+
): boolean {
|
|
14578
|
+
return (
|
|
14579
|
+
(type === "chatbot" && chatbotDisabled) ||
|
|
14580
|
+
(type === "voicebot" && voicebotDisabled)
|
|
14581
|
+
);
|
|
14582
|
+
}
|
|
14567
14583
|
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14584
|
+
export const CreateBotModal = React.forwardRef(
|
|
14585
|
+
(
|
|
14586
|
+
{
|
|
14587
|
+
open,
|
|
14588
|
+
onOpenChange,
|
|
14589
|
+
onSubmit,
|
|
14590
|
+
isLoading,
|
|
14591
|
+
chatbotDisabled = false,
|
|
14592
|
+
voicebotDisabled = false,
|
|
14593
|
+
chatbotDisabledTooltip,
|
|
14594
|
+
voicebotDisabledTooltip,
|
|
14595
|
+
className,
|
|
14596
|
+
}: CreateBotModalProps,
|
|
14597
|
+
ref: React.Ref<HTMLDivElement>
|
|
14598
|
+
) => {
|
|
14599
|
+
const [name, setName] = React.useState("");
|
|
14600
|
+
const [selectedType, setSelectedType] = React.useState<BotType>("chatbot");
|
|
14573
14601
|
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
};
|
|
14602
|
+
const chatD = Boolean(chatbotDisabled);
|
|
14603
|
+
const voiceD = Boolean(voicebotDisabled);
|
|
14577
14604
|
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14605
|
+
React.useEffect(() => {
|
|
14606
|
+
if (!open) {
|
|
14607
|
+
setName("");
|
|
14608
|
+
setSelectedType(getFirstEnabledBotType(chatD, voiceD));
|
|
14609
|
+
return;
|
|
14610
|
+
}
|
|
14611
|
+
setSelectedType((prev) => {
|
|
14612
|
+
if (!isBotTypeDisabled(prev, chatD, voiceD)) return prev;
|
|
14613
|
+
return getFirstEnabledBotType(chatD, voiceD);
|
|
14614
|
+
});
|
|
14615
|
+
}, [open, chatD, voiceD]);
|
|
14584
14616
|
|
|
14585
|
-
|
|
14586
|
-
{/* Name field */}
|
|
14587
|
-
<div className="flex flex-col gap-1.5">
|
|
14588
|
-
<label
|
|
14589
|
-
htmlFor="bot-name"
|
|
14590
|
-
className="flex items-center gap-0.5 text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]"
|
|
14591
|
-
>
|
|
14592
|
-
Name
|
|
14593
|
-
<span className="text-xs text-semantic-error-primary">*</span>
|
|
14594
|
-
</label>
|
|
14595
|
-
<input
|
|
14596
|
-
id="bot-name"
|
|
14597
|
-
type="text"
|
|
14598
|
-
value={name}
|
|
14599
|
-
onChange={(e) => setName(e.target.value)}
|
|
14600
|
-
placeholder="Enter bot name"
|
|
14601
|
-
className={cn(
|
|
14602
|
-
"w-full h-10 px-4 py-2.5 text-sm rounded border border-solid",
|
|
14603
|
-
"border-semantic-border-input bg-semantic-bg-primary",
|
|
14604
|
-
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
14605
|
-
"outline-none hover:border-semantic-border-input-focus",
|
|
14606
|
-
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
14607
|
-
)}
|
|
14608
|
-
/>
|
|
14609
|
-
</div>
|
|
14617
|
+
const selectedTypeBlocked = isBotTypeDisabled(selectedType, chatD, voiceD);
|
|
14610
14618
|
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14619
|
+
const handleSubmit = () => {
|
|
14620
|
+
if (!name.trim() || selectedTypeBlocked) return;
|
|
14621
|
+
const typeValue =
|
|
14622
|
+
selectedType === "chatbot" ? BOT_TYPE.CHAT : BOT_TYPE.VOICE;
|
|
14623
|
+
onSubmit?.({ name: name.trim(), type: typeValue });
|
|
14624
|
+
};
|
|
14625
|
+
|
|
14626
|
+
const handleClose = () => {
|
|
14627
|
+
onOpenChange(false);
|
|
14628
|
+
};
|
|
14629
|
+
|
|
14630
|
+
return (
|
|
14631
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
14632
|
+
<DialogContent
|
|
14633
|
+
ref={ref}
|
|
14634
|
+
size="sm"
|
|
14635
|
+
className={cn(
|
|
14636
|
+
"mx-3 max-h-[90vh] overflow-y-auto w-[calc(100%-1.5rem)] sm:mx-auto sm:w-full",
|
|
14637
|
+
className
|
|
14638
|
+
)}
|
|
14639
|
+
>
|
|
14640
|
+
<DialogHeader>
|
|
14641
|
+
<DialogTitle>Create AI bot</DialogTitle>
|
|
14642
|
+
</DialogHeader>
|
|
14643
|
+
|
|
14644
|
+
<div className="flex flex-col gap-4 sm:gap-6">
|
|
14645
|
+
{/* Name field */}
|
|
14646
|
+
<div className="flex flex-col gap-1.5">
|
|
14647
|
+
<label
|
|
14648
|
+
htmlFor="bot-name"
|
|
14649
|
+
className="flex items-center gap-0.5 text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]"
|
|
14650
|
+
>
|
|
14651
|
+
Name
|
|
14652
|
+
<span className="text-xs text-semantic-error-primary">*</span>
|
|
14653
|
+
</label>
|
|
14654
|
+
<input
|
|
14655
|
+
id="bot-name"
|
|
14656
|
+
type="text"
|
|
14657
|
+
value={name}
|
|
14658
|
+
onChange={(e) => setName(e.target.value)}
|
|
14659
|
+
placeholder="Enter bot name"
|
|
14660
|
+
className={cn(
|
|
14661
|
+
"w-full h-10 px-4 py-2.5 text-sm rounded border border-solid",
|
|
14662
|
+
"border-semantic-border-input bg-semantic-bg-primary",
|
|
14663
|
+
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
14664
|
+
"outline-none hover:border-semantic-border-input-focus",
|
|
14665
|
+
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
14666
|
+
)}
|
|
14667
|
+
/>
|
|
14658
14668
|
</div>
|
|
14659
14669
|
|
|
14660
|
-
{/*
|
|
14661
|
-
<div className="flex
|
|
14662
|
-
<
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14670
|
+
{/* Bot type selection */}
|
|
14671
|
+
<div className="flex flex-col gap-2">
|
|
14672
|
+
<span className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
|
|
14673
|
+
Select Bot Type
|
|
14674
|
+
</span>
|
|
14675
|
+
<TooltipProvider delayDuration={200}>
|
|
14676
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:gap-3">
|
|
14677
|
+
{BOT_TYPE_OPTIONS.map(({ id, label, description }) => {
|
|
14678
|
+
const optionDisabled = isBotTypeDisabled(id, chatD, voiceD);
|
|
14679
|
+
const isSelected = selectedType === id && !optionDisabled;
|
|
14680
|
+
const disabledTooltip =
|
|
14681
|
+
id === "chatbot"
|
|
14682
|
+
? chatbotDisabledTooltip
|
|
14683
|
+
: voicebotDisabledTooltip;
|
|
14684
|
+
const showTooltip =
|
|
14685
|
+
optionDisabled &&
|
|
14686
|
+
disabledTooltip != null &&
|
|
14687
|
+
disabledTooltip.trim() !== "";
|
|
14688
|
+
|
|
14689
|
+
const baseButtonClass = cn(
|
|
14690
|
+
"flex flex-col items-start gap-2 sm:gap-2.5 p-3 rounded-lg border border-solid text-left flex-1 min-h-[100px] sm:h-[134px] justify-center min-w-0 w-full",
|
|
14691
|
+
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus",
|
|
14692
|
+
optionDisabled
|
|
14693
|
+
? "cursor-not-allowed opacity-50 pointer-events-none bg-semantic-bg-primary border-semantic-border-layout"
|
|
14694
|
+
: isSelected
|
|
14695
|
+
? "bg-semantic-brand-surface border-semantic-brand shadow-sm"
|
|
14696
|
+
: "bg-semantic-bg-primary border-semantic-border-layout hover:bg-semantic-bg-hover"
|
|
14697
|
+
);
|
|
14698
|
+
|
|
14699
|
+
const button = (
|
|
14700
|
+
<button
|
|
14701
|
+
type="button"
|
|
14702
|
+
disabled={optionDisabled}
|
|
14703
|
+
onClick={() => {
|
|
14704
|
+
if (!optionDisabled) setSelectedType(id);
|
|
14705
|
+
}}
|
|
14706
|
+
className={baseButtonClass}
|
|
14707
|
+
aria-pressed={isSelected}
|
|
14708
|
+
aria-disabled={optionDisabled}
|
|
14709
|
+
>
|
|
14710
|
+
<div
|
|
14711
|
+
className={cn(
|
|
14712
|
+
"flex items-center justify-center size-[34px] rounded-lg",
|
|
14713
|
+
isSelected
|
|
14714
|
+
? "bg-semantic-bg-primary"
|
|
14715
|
+
: "bg-semantic-info-surface-subtle"
|
|
14716
|
+
)}
|
|
14717
|
+
>
|
|
14718
|
+
{id === "chatbot" ? (
|
|
14719
|
+
<MessageSquare className="size-5 text-semantic-text-secondary" />
|
|
14720
|
+
) : (
|
|
14721
|
+
<Phone className="size-5 text-semantic-text-secondary" />
|
|
14722
|
+
)}
|
|
14723
|
+
</div>
|
|
14724
|
+
<div className="flex flex-col gap-1">
|
|
14725
|
+
<p className="m-0 text-sm font-semibold text-semantic-text-primary tracking-[0.014px]">
|
|
14726
|
+
{label}
|
|
14727
|
+
</p>
|
|
14728
|
+
<p className="m-0 text-xs text-semantic-text-muted tracking-[0.048px]">
|
|
14729
|
+
{description}
|
|
14730
|
+
</p>
|
|
14731
|
+
</div>
|
|
14732
|
+
</button>
|
|
14733
|
+
);
|
|
14734
|
+
|
|
14735
|
+
if (showTooltip) {
|
|
14736
|
+
return (
|
|
14737
|
+
<Tooltip key={id}>
|
|
14738
|
+
<TooltipTrigger asChild>
|
|
14739
|
+
<span className="flex flex-1 min-w-0 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus">
|
|
14740
|
+
{button}
|
|
14741
|
+
</span>
|
|
14742
|
+
</TooltipTrigger>
|
|
14743
|
+
<TooltipContent side="top">
|
|
14744
|
+
<p className="m-0">{disabledTooltip}</p>
|
|
14745
|
+
</TooltipContent>
|
|
14746
|
+
</Tooltip>
|
|
14747
|
+
);
|
|
14748
|
+
}
|
|
14749
|
+
|
|
14750
|
+
return (
|
|
14751
|
+
<React.Fragment key={id}>
|
|
14752
|
+
{button}
|
|
14753
|
+
</React.Fragment>
|
|
14754
|
+
);
|
|
14755
|
+
})}
|
|
14756
|
+
</div>
|
|
14757
|
+
</TooltipProvider>
|
|
14758
|
+
|
|
14759
|
+
{/* Helper text */}
|
|
14760
|
+
<div className="flex items-center gap-1.5 px-3 py-2.5 rounded bg-semantic-bg-ui">
|
|
14761
|
+
<Info className="size-4 text-semantic-text-secondary shrink-0" />
|
|
14762
|
+
<p className="m-0 text-xs text-semantic-text-secondary">
|
|
14763
|
+
This setting cannot be changed once selected.
|
|
14764
|
+
</p>
|
|
14765
|
+
</div>
|
|
14666
14766
|
</div>
|
|
14667
14767
|
</div>
|
|
14668
|
-
</div>
|
|
14669
14768
|
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
}
|
|
14769
|
+
{/* Footer actions */}
|
|
14770
|
+
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:gap-4 justify-end mt-2">
|
|
14771
|
+
<Button variant="outline" onClick={handleClose}>
|
|
14772
|
+
Cancel
|
|
14773
|
+
</Button>
|
|
14774
|
+
<Button
|
|
14775
|
+
variant="default"
|
|
14776
|
+
onClick={handleSubmit}
|
|
14777
|
+
disabled={!name.trim() || isLoading || selectedTypeBlocked}
|
|
14778
|
+
loading={isLoading}
|
|
14779
|
+
>
|
|
14780
|
+
Create
|
|
14781
|
+
</Button>
|
|
14782
|
+
</div>
|
|
14783
|
+
</DialogContent>
|
|
14784
|
+
</Dialog>
|
|
14785
|
+
);
|
|
14786
|
+
}
|
|
14787
|
+
);
|
|
14688
14788
|
|
|
14689
14789
|
CreateBotModal.displayName = "CreateBotModal";
|
|
14690
14790
|
`, prefix)
|
|
@@ -14707,6 +14807,10 @@ export const CreateBotFlow = React.forwardRef(
|
|
|
14707
14807
|
{
|
|
14708
14808
|
createCardLabel = "Create new bot",
|
|
14709
14809
|
onSubmit,
|
|
14810
|
+
chatbotDisabled,
|
|
14811
|
+
voicebotDisabled,
|
|
14812
|
+
chatbotDisabledTooltip,
|
|
14813
|
+
voicebotDisabledTooltip,
|
|
14710
14814
|
className,
|
|
14711
14815
|
...props
|
|
14712
14816
|
}: CreateBotFlowProps,
|
|
@@ -14734,6 +14838,10 @@ export const CreateBotFlow = React.forwardRef(
|
|
|
14734
14838
|
<CreateBotModal
|
|
14735
14839
|
open={modalOpen}
|
|
14736
14840
|
onOpenChange={setModalOpen}
|
|
14841
|
+
chatbotDisabled={chatbotDisabled}
|
|
14842
|
+
voicebotDisabled={voicebotDisabled}
|
|
14843
|
+
chatbotDisabledTooltip={chatbotDisabledTooltip}
|
|
14844
|
+
voicebotDisabledTooltip={voicebotDisabledTooltip}
|
|
14737
14845
|
onSubmit={(data) => {
|
|
14738
14846
|
onSubmit?.(data);
|
|
14739
14847
|
setModalOpen(false);
|
|
@@ -14764,6 +14872,10 @@ export function EditBotFlow({
|
|
|
14764
14872
|
searchPlaceholder = "Search bot...",
|
|
14765
14873
|
createCardLabel = "Create new bot",
|
|
14766
14874
|
typeLabels,
|
|
14875
|
+
chatbotDisabled,
|
|
14876
|
+
voicebotDisabled,
|
|
14877
|
+
chatbotDisabledTooltip,
|
|
14878
|
+
voicebotDisabledTooltip,
|
|
14767
14879
|
onBotDelete,
|
|
14768
14880
|
onCreateBotSubmit,
|
|
14769
14881
|
onSearch,
|
|
@@ -14803,6 +14915,10 @@ export function EditBotFlow({
|
|
|
14803
14915
|
searchPlaceholder={searchPlaceholder}
|
|
14804
14916
|
createCardLabel={createCardLabel}
|
|
14805
14917
|
typeLabels={typeLabels}
|
|
14918
|
+
chatbotDisabled={chatbotDisabled}
|
|
14919
|
+
voicebotDisabled={voicebotDisabled}
|
|
14920
|
+
chatbotDisabledTooltip={chatbotDisabledTooltip}
|
|
14921
|
+
voicebotDisabledTooltip={voicebotDisabledTooltip}
|
|
14806
14922
|
onBotEdit={handleEdit}
|
|
14807
14923
|
onBotDelete={onBotDelete}
|
|
14808
14924
|
onCreateBotSubmit={onCreateBotSubmit}
|
|
@@ -14840,6 +14956,10 @@ export const BotList = React.forwardRef(
|
|
|
14840
14956
|
subtitle = "Create & manage AI bots",
|
|
14841
14957
|
searchPlaceholder = "Search bot...",
|
|
14842
14958
|
createCardLabel = "Create new bot",
|
|
14959
|
+
chatbotDisabled,
|
|
14960
|
+
voicebotDisabled,
|
|
14961
|
+
chatbotDisabledTooltip,
|
|
14962
|
+
voicebotDisabledTooltip,
|
|
14843
14963
|
className,
|
|
14844
14964
|
...props
|
|
14845
14965
|
}: BotListProps,
|
|
@@ -14879,6 +14999,10 @@ export const BotList = React.forwardRef(
|
|
|
14879
14999
|
<CreateBotModal
|
|
14880
15000
|
open={createModalOpen}
|
|
14881
15001
|
onOpenChange={setCreateModalOpen}
|
|
15002
|
+
chatbotDisabled={chatbotDisabled}
|
|
15003
|
+
voicebotDisabled={voicebotDisabled}
|
|
15004
|
+
chatbotDisabledTooltip={chatbotDisabledTooltip}
|
|
15005
|
+
voicebotDisabledTooltip={voicebotDisabledTooltip}
|
|
14882
15006
|
onSubmit={(data) => {
|
|
14883
15007
|
onCreateBotSubmit?.(data);
|
|
14884
15008
|
setCreateModalOpen(false);
|
|
@@ -14919,6 +15043,10 @@ export const BotList = React.forwardRef(
|
|
|
14919
15043
|
<CreateBotModal
|
|
14920
15044
|
open={createModalOpen}
|
|
14921
15045
|
onOpenChange={setCreateModalOpen}
|
|
15046
|
+
chatbotDisabled={chatbotDisabled}
|
|
15047
|
+
voicebotDisabled={voicebotDisabled}
|
|
15048
|
+
chatbotDisabledTooltip={chatbotDisabledTooltip}
|
|
15049
|
+
voicebotDisabledTooltip={voicebotDisabledTooltip}
|
|
14922
15050
|
onSubmit={(data) => {
|
|
14923
15051
|
onCreateBotSubmit?.(data);
|
|
14924
15052
|
setCreateModalOpen(false);
|
|
@@ -15189,6 +15317,18 @@ export interface CreateBotModalProps {
|
|
|
15189
15317
|
onSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
|
|
15190
15318
|
/** Shows loading spinner on Create button and disables it (e.g. while API call is in flight) */
|
|
15191
15319
|
isLoading?: boolean;
|
|
15320
|
+
/** When true, Chat bot type cannot be selected */
|
|
15321
|
+
chatbotDisabled?: boolean;
|
|
15322
|
+
/** When true, Voice bot type cannot be selected */
|
|
15323
|
+
voicebotDisabled?: boolean;
|
|
15324
|
+
/**
|
|
15325
|
+
* Shown on hover/focus when Chat bot is disabled. Tooltip is not rendered when omitted or empty.
|
|
15326
|
+
*/
|
|
15327
|
+
chatbotDisabledTooltip?: string;
|
|
15328
|
+
/**
|
|
15329
|
+
* Shown on hover/focus when Voice bot is disabled. Tooltip is not rendered when omitted or empty.
|
|
15330
|
+
*/
|
|
15331
|
+
voicebotDisabledTooltip?: string;
|
|
15192
15332
|
className?: string;
|
|
15193
15333
|
}
|
|
15194
15334
|
|
|
@@ -15227,9 +15367,19 @@ export interface BotListGridProps
|
|
|
15227
15367
|
children: React.ReactNode;
|
|
15228
15368
|
}
|
|
15229
15369
|
|
|
15370
|
+
/** Props forwarded to CreateBotModal for bot-type gating (optional). */
|
|
15371
|
+
export type CreateBotModalTypeOptionsProps = Pick<
|
|
15372
|
+
CreateBotModalProps,
|
|
15373
|
+
| "chatbotDisabled"
|
|
15374
|
+
| "voicebotDisabled"
|
|
15375
|
+
| "chatbotDisabledTooltip"
|
|
15376
|
+
| "voicebotDisabledTooltip"
|
|
15377
|
+
>;
|
|
15378
|
+
|
|
15230
15379
|
/** Props for CreateBotFlow: create card + Create Bot modal (no header). */
|
|
15231
15380
|
export interface CreateBotFlowProps
|
|
15232
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "onSubmit"
|
|
15381
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "onSubmit">,
|
|
15382
|
+
CreateBotModalTypeOptionsProps {
|
|
15233
15383
|
/** Create new bot card label */
|
|
15234
15384
|
createCardLabel?: string;
|
|
15235
15385
|
/** Called when Create Bot modal is submitted with { name, type } */
|
|
@@ -15237,7 +15387,7 @@ export interface CreateBotFlowProps
|
|
|
15237
15387
|
}
|
|
15238
15388
|
|
|
15239
15389
|
/** Props for EditBotFlow: bot list + config view when Edit is clicked. */
|
|
15240
|
-
export interface EditBotFlowProps {
|
|
15390
|
+
export interface EditBotFlowProps extends CreateBotModalTypeOptionsProps {
|
|
15241
15391
|
/** Bots to show in the list (e.g. first 2 for demo) */
|
|
15242
15392
|
bots: Bot[];
|
|
15243
15393
|
/** Page title */
|
|
@@ -15265,7 +15415,8 @@ export interface EditBotFlowProps {
|
|
|
15265
15415
|
}
|
|
15266
15416
|
|
|
15267
15417
|
export interface BotListProps
|
|
15268
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children"
|
|
15418
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children">,
|
|
15419
|
+
CreateBotModalTypeOptionsProps {
|
|
15269
15420
|
/** List of bots to display */
|
|
15270
15421
|
bots?: Bot[];
|
|
15271
15422
|
/** Override type badge labels for all cards (e.g. { chatbot: "Chat", voicebot: "Voice" }). Per-bot bot.typeLabel still wins. */
|
|
@@ -15299,7 +15450,12 @@ export type { BotCardProps } from "./types";
|
|
|
15299
15450
|
export { CreateBotModal } from "./create-bot-modal";
|
|
15300
15451
|
export { CreateBotFlow } from "./create-bot-flow";
|
|
15301
15452
|
export { EditBotFlow } from "./edit-bot-flow";
|
|
15302
|
-
export type {
|
|
15453
|
+
export type {
|
|
15454
|
+
CreateBotModalProps,
|
|
15455
|
+
CreateBotModalTypeOptionsProps,
|
|
15456
|
+
CreateBotFlowProps,
|
|
15457
|
+
EditBotFlowProps,
|
|
15458
|
+
} from "./types";
|
|
15303
15459
|
|
|
15304
15460
|
export { BotList } from "./bot-list";
|
|
15305
15461
|
export { BotListHeader } from "./bot-list-header";
|
|
@@ -15570,14 +15726,14 @@ const FileUploadModal = React.forwardRef(
|
|
|
15570
15726
|
size="default"
|
|
15571
15727
|
hideCloseButton
|
|
15572
15728
|
className={cn(
|
|
15573
|
-
"max-w-[min(660px,calc(100vw-2rem))] rounded-xl p-4 gap-0 sm:p-6",
|
|
15729
|
+
"max-w-[min(660px,calc(100vw-2rem))] min-w-0 rounded-xl p-4 gap-0 sm:p-6 overflow-x-hidden",
|
|
15574
15730
|
className
|
|
15575
15731
|
)}
|
|
15576
15732
|
{...props}
|
|
15577
15733
|
>
|
|
15578
15734
|
{/* Header */}
|
|
15579
|
-
<div className="flex items-center justify-between mb-6">
|
|
15580
|
-
<DialogTitle className="m-0 text-base font-semibold text-semantic-text-primary">
|
|
15735
|
+
<div className="flex items-center justify-between gap-3 mb-6 min-w-0">
|
|
15736
|
+
<DialogTitle className="m-0 text-base font-semibold text-semantic-text-primary truncate min-w-0 pr-2">
|
|
15581
15737
|
{title}
|
|
15582
15738
|
</DialogTitle>
|
|
15583
15739
|
<DialogDescription className="sr-only">
|
|
@@ -15586,20 +15742,20 @@ const FileUploadModal = React.forwardRef(
|
|
|
15586
15742
|
<button
|
|
15587
15743
|
type="button"
|
|
15588
15744
|
onClick={handleClose}
|
|
15589
|
-
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-semantic-text-primary"
|
|
15745
|
+
className="shrink-0 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-semantic-text-primary"
|
|
15590
15746
|
aria-label="Close dialog"
|
|
15591
15747
|
>
|
|
15592
15748
|
<X className="h-4 w-4" />
|
|
15593
15749
|
</button>
|
|
15594
15750
|
</div>
|
|
15595
15751
|
|
|
15596
|
-
{/* Body */}
|
|
15597
|
-
<div className="flex flex-col gap-4 items-
|
|
15752
|
+
{/* Body \u2014 stretch so children respect modal width; long filenames need min-w-0 chain */}
|
|
15753
|
+
<div className="flex flex-col gap-4 items-stretch w-full min-w-0 max-w-full">
|
|
15598
15754
|
{shouldShowSampleDownload && (
|
|
15599
15755
|
<button
|
|
15600
15756
|
type="button"
|
|
15601
15757
|
onClick={onSampleDownload}
|
|
15602
|
-
className="flex items-center gap-1.5 text-sm font-semibold text-semantic-text-link hover:opacity-80 transition-opacity"
|
|
15758
|
+
className="self-end flex items-center gap-1.5 text-sm font-semibold text-semantic-text-link hover:opacity-80 transition-opacity"
|
|
15603
15759
|
>
|
|
15604
15760
|
<Download className="size-3.5" />
|
|
15605
15761
|
{sampleDownloadLabel}
|
|
@@ -15608,14 +15764,14 @@ const FileUploadModal = React.forwardRef(
|
|
|
15608
15764
|
|
|
15609
15765
|
{/* Drop zone */}
|
|
15610
15766
|
<div
|
|
15611
|
-
className="w-full border border-dashed border-semantic-border-layout bg-semantic-bg-ui rounded p-4"
|
|
15767
|
+
className="w-full min-w-0 max-w-full border border-dashed border-semantic-border-layout bg-semantic-bg-ui rounded p-4"
|
|
15612
15768
|
onDrop={(e) => {
|
|
15613
15769
|
e.preventDefault();
|
|
15614
15770
|
addFiles(e.dataTransfer.files);
|
|
15615
15771
|
}}
|
|
15616
15772
|
onDragOver={(e) => e.preventDefault()}
|
|
15617
15773
|
>
|
|
15618
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
|
|
15774
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4 min-w-0">
|
|
15619
15775
|
<button
|
|
15620
15776
|
type="button"
|
|
15621
15777
|
onClick={() => fileInputRef.current?.click()}
|
|
@@ -15623,11 +15779,11 @@ const FileUploadModal = React.forwardRef(
|
|
|
15623
15779
|
>
|
|
15624
15780
|
{uploadButtonLabel}
|
|
15625
15781
|
</button>
|
|
15626
|
-
<div className="flex flex-col gap-1">
|
|
15627
|
-
<p className="m-0 text-sm text-semantic-text-secondary tracking-[0.035px]">
|
|
15782
|
+
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
|
15783
|
+
<p className="m-0 text-sm text-semantic-text-secondary tracking-[0.035px] break-words">
|
|
15628
15784
|
{dropDescription}
|
|
15629
15785
|
</p>
|
|
15630
|
-
<p className="m-0 text-xs text-semantic-text-muted tracking-[0.048px]">
|
|
15786
|
+
<p className="m-0 text-xs text-semantic-text-muted tracking-[0.048px] break-words">
|
|
15631
15787
|
{formatDescription}
|
|
15632
15788
|
</p>
|
|
15633
15789
|
</div>
|
|
@@ -15647,15 +15803,18 @@ const FileUploadModal = React.forwardRef(
|
|
|
15647
15803
|
|
|
15648
15804
|
{/* Upload item list */}
|
|
15649
15805
|
{items.length > 0 && (
|
|
15650
|
-
<div className="flex flex-col gap-2.5 w-full">
|
|
15806
|
+
<div className="flex flex-col gap-2.5 w-full min-w-0 max-w-full">
|
|
15651
15807
|
{items.map((item) => (
|
|
15652
15808
|
<div
|
|
15653
15809
|
key={item.id}
|
|
15654
|
-
className="bg-semantic-bg-primary border border-solid border-semantic-border-layout rounded px-4 py-3 flex flex-col gap-2"
|
|
15810
|
+
className="bg-semantic-bg-primary border border-solid border-semantic-border-layout rounded px-4 py-3 flex flex-col gap-2 min-w-0 max-w-full overflow-hidden"
|
|
15655
15811
|
>
|
|
15656
|
-
<div className="flex items-start gap-3">
|
|
15657
|
-
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
|
|
15658
|
-
<p
|
|
15812
|
+
<div className="flex items-start gap-3 min-w-0">
|
|
15813
|
+
<div className="flex flex-col gap-0.5 flex-1 min-w-0 overflow-hidden">
|
|
15814
|
+
<p
|
|
15815
|
+
className="m-0 text-sm text-semantic-text-primary tracking-[0.035px] truncate max-w-full"
|
|
15816
|
+
title={item.file.name}
|
|
15817
|
+
>
|
|
15659
15818
|
{item.status === "uploading"
|
|
15660
15819
|
? "Uploading..."
|
|
15661
15820
|
: item.file.name}
|
|
@@ -17860,11 +18019,18 @@ export const IvrBotConfig = React.forwardRef(
|
|
|
17860
18019
|
voiceOptions,
|
|
17861
18020
|
languageOptions,
|
|
17862
18021
|
sessionVariables,
|
|
18022
|
+
functionVariableGroups,
|
|
18023
|
+
onAddFunctionVariable,
|
|
18024
|
+
onEditFunctionVariable,
|
|
17863
18025
|
escalationDepartmentOptions,
|
|
18026
|
+
advancedSettingsNumericBounds,
|
|
17864
18027
|
silenceTimeoutMin,
|
|
17865
18028
|
silenceTimeoutMax,
|
|
17866
18029
|
callEndThresholdMin,
|
|
17867
18030
|
callEndThresholdMax,
|
|
18031
|
+
onAdvancedSettingsChange,
|
|
18032
|
+
onSilenceTimeoutBlur,
|
|
18033
|
+
onCallEndThresholdBlur,
|
|
17868
18034
|
className,
|
|
17869
18035
|
}: IvrBotConfigProps,
|
|
17870
18036
|
ref: React.Ref<HTMLDivElement>
|
|
@@ -18017,10 +18183,14 @@ export const IvrBotConfig = React.forwardRef(
|
|
|
18017
18183
|
<AdvancedSettingsCard
|
|
18018
18184
|
data={data}
|
|
18019
18185
|
onChange={update}
|
|
18186
|
+
numericBounds={advancedSettingsNumericBounds}
|
|
18020
18187
|
silenceTimeoutMin={silenceTimeoutMin}
|
|
18021
18188
|
silenceTimeoutMax={silenceTimeoutMax}
|
|
18022
18189
|
callEndThresholdMin={callEndThresholdMin}
|
|
18023
18190
|
callEndThresholdMax={callEndThresholdMax}
|
|
18191
|
+
onAdvancedSettingsChange={onAdvancedSettingsChange}
|
|
18192
|
+
onSilenceTimeoutBlur={onSilenceTimeoutBlur}
|
|
18193
|
+
onCallEndThresholdBlur={onCallEndThresholdBlur}
|
|
18024
18194
|
disabled={disabled}
|
|
18025
18195
|
/>
|
|
18026
18196
|
</div>
|
|
@@ -18035,6 +18205,9 @@ export const IvrBotConfig = React.forwardRef(
|
|
|
18035
18205
|
promptMinLength={functionPromptMinLength}
|
|
18036
18206
|
promptMaxLength={functionPromptMaxLength}
|
|
18037
18207
|
sessionVariables={sessionVariables}
|
|
18208
|
+
variableGroups={functionVariableGroups}
|
|
18209
|
+
onAddVariable={onAddFunctionVariable}
|
|
18210
|
+
onEditVariable={onEditFunctionVariable}
|
|
18038
18211
|
/>
|
|
18039
18212
|
|
|
18040
18213
|
{/* Edit Function Modal */}
|
|
@@ -18048,6 +18221,9 @@ export const IvrBotConfig = React.forwardRef(
|
|
|
18048
18221
|
promptMinLength={functionPromptMinLength}
|
|
18049
18222
|
promptMaxLength={functionPromptMaxLength}
|
|
18050
18223
|
sessionVariables={sessionVariables}
|
|
18224
|
+
variableGroups={functionVariableGroups}
|
|
18225
|
+
onAddVariable={onAddFunctionVariable}
|
|
18226
|
+
onEditVariable={onEditFunctionVariable}
|
|
18051
18227
|
disabled={disabled}
|
|
18052
18228
|
/>
|
|
18053
18229
|
|
|
@@ -18165,7 +18341,7 @@ export function headerRowsHaveSubmitErrors(rows: KeyValuePair[]): boolean {
|
|
|
18165
18341
|
{
|
|
18166
18342
|
name: "create-function-modal.tsx",
|
|
18167
18343
|
content: prefixTailwindClasses(`import * as React from "react";
|
|
18168
|
-
import { Trash2, ChevronDown, X, Plus, Pencil } from "lucide-react";
|
|
18344
|
+
import { Trash2, ChevronDown, X, Plus, Pencil, CircleAlert } from "lucide-react";
|
|
18169
18345
|
import { cn } from "../../../lib/utils";
|
|
18170
18346
|
import {
|
|
18171
18347
|
Dialog,
|
|
@@ -18260,6 +18436,105 @@ function extractVarRefs(texts: string[]): string[] {
|
|
|
18260
18436
|
return Array.from(new Set(all));
|
|
18261
18437
|
}
|
|
18262
18438
|
|
|
18439
|
+
/** True if a \`{{\u2026}}\` token in the form matches this variable item (handles \`{{name}}\` vs \`{{function.name}}\` and legacy \`item.name\` with \`function.\` prefix). */
|
|
18440
|
+
function placeholderMatchesVariableItem(placeholder: string, item: VariableItem): boolean {
|
|
18441
|
+
if (item.value && placeholder === item.value) return true;
|
|
18442
|
+
const asDisplayed = \`{{\${item.name}}}\`;
|
|
18443
|
+
const asFunction = \`{{function.\${item.name}}}\`;
|
|
18444
|
+
if (placeholder === asDisplayed || placeholder === asFunction) return true;
|
|
18445
|
+
|
|
18446
|
+
const m = /^\\{\\{([^}]+)\\}\\}$/.exec(placeholder);
|
|
18447
|
+
if (!m) return false;
|
|
18448
|
+
const inner = m[1].trim();
|
|
18449
|
+
if (inner === item.name) return true;
|
|
18450
|
+
|
|
18451
|
+
const bareName = item.name.startsWith("function.") ? item.name.slice("function.".length) : item.name;
|
|
18452
|
+
return inner === bareName || inner === \`function.\${bareName}\`;
|
|
18453
|
+
}
|
|
18454
|
+
|
|
18455
|
+
/** Aliases for the inner text of \`{{\u2026}}\` (e.g. \`function.foo\` \u2194 \`foo\`). */
|
|
18456
|
+
function placeholderInnerAliases(inner: string): string[] {
|
|
18457
|
+
const trimmed = inner.trim();
|
|
18458
|
+
if (!trimmed) return [];
|
|
18459
|
+
const out = new Set<string>([trimmed]);
|
|
18460
|
+
const bare = trimmed.startsWith("function.") ? trimmed.slice("function.".length) : trimmed;
|
|
18461
|
+
out.add(bare);
|
|
18462
|
+
if (!trimmed.startsWith("function.")) {
|
|
18463
|
+
out.add(\`function.\${bare}\`);
|
|
18464
|
+
}
|
|
18465
|
+
return Array.from(out);
|
|
18466
|
+
}
|
|
18467
|
+
|
|
18468
|
+
/** Keys used to store Test API "required" for a function variable name from the form (bare id, no \`{{}}\`). */
|
|
18469
|
+
function placeholderInnerAliasesForBareName(bareName: string): string[] {
|
|
18470
|
+
const trimmed = bareName.trim();
|
|
18471
|
+
if (!trimmed) return [];
|
|
18472
|
+
return placeholderInnerAliases(trimmed);
|
|
18473
|
+
}
|
|
18474
|
+
|
|
18475
|
+
function buildFnVarRequiredMapFromGroups(groups?: VariableGroup[]): Record<string, boolean> {
|
|
18476
|
+
const seeded: Record<string, boolean> = {};
|
|
18477
|
+
for (const g of groups ?? []) {
|
|
18478
|
+
for (const item of g.items) {
|
|
18479
|
+
if (!item.required) continue;
|
|
18480
|
+
const n = item.name.trim();
|
|
18481
|
+
const bare = n.startsWith("function.") ? n.slice("function.".length) : n;
|
|
18482
|
+
for (const key of placeholderInnerAliasesForBareName(bare)) {
|
|
18483
|
+
seeded[key] = true;
|
|
18484
|
+
}
|
|
18485
|
+
}
|
|
18486
|
+
}
|
|
18487
|
+
return seeded;
|
|
18488
|
+
}
|
|
18489
|
+
|
|
18490
|
+
/**
|
|
18491
|
+
* Whether a \`{{\u2026}}\` placeholder is required for Test API.
|
|
18492
|
+
* \`localFnVarRequired\` merges Required from \`variableGroups\` (on open) plus Create/Edit variable saves
|
|
18493
|
+
* so validation works when the parent omits \`variableGroups\` or has not updated it yet after \`onAddVariable\`.
|
|
18494
|
+
*/
|
|
18495
|
+
function isPlaceholderRequiredInTest(
|
|
18496
|
+
placeholder: string,
|
|
18497
|
+
variableGroups?: VariableGroup[],
|
|
18498
|
+
localFnVarRequired?: Record<string, boolean>
|
|
18499
|
+
): boolean {
|
|
18500
|
+
if (localFnVarRequired && Object.keys(localFnVarRequired).length > 0) {
|
|
18501
|
+
const m = /^\\{\\{([^}]+)\\}\\}$/.exec(placeholder.trim());
|
|
18502
|
+
if (m) {
|
|
18503
|
+
for (const alias of placeholderInnerAliases(m[1])) {
|
|
18504
|
+
if (Object.prototype.hasOwnProperty.call(localFnVarRequired, alias)) {
|
|
18505
|
+
return Boolean(localFnVarRequired[alias]);
|
|
18506
|
+
}
|
|
18507
|
+
}
|
|
18508
|
+
}
|
|
18509
|
+
}
|
|
18510
|
+
|
|
18511
|
+
if (!variableGroups?.length) return false;
|
|
18512
|
+
for (const g of variableGroups) {
|
|
18513
|
+
for (const item of g.items) {
|
|
18514
|
+
if (placeholderMatchesVariableItem(placeholder, item)) {
|
|
18515
|
+
return Boolean(item.required);
|
|
18516
|
+
}
|
|
18517
|
+
}
|
|
18518
|
+
}
|
|
18519
|
+
return false;
|
|
18520
|
+
}
|
|
18521
|
+
|
|
18522
|
+
/**
|
|
18523
|
+
* Rewrites \`{{function.oldRaw}}\` and \`{{oldRaw}}\` to the new name everywhere in a string.
|
|
18524
|
+
* Used when saving "Edit variable" so URL, body, headers, and query params stay in sync.
|
|
18525
|
+
*/
|
|
18526
|
+
function renameVariableRefsInString(
|
|
18527
|
+
text: string,
|
|
18528
|
+
oldRaw: string,
|
|
18529
|
+
newRaw: string
|
|
18530
|
+
): string {
|
|
18531
|
+
const prev = oldRaw.trim();
|
|
18532
|
+
const next = newRaw.trim();
|
|
18533
|
+
if (!prev || prev === next) return text;
|
|
18534
|
+
const withFunction = text.split(\`{{function.\${prev}}}\`).join(\`{{function.\${next}}}\`);
|
|
18535
|
+
return withFunction.split(\`{{\${prev}}}\`).join(\`{{\${next}}}\`);
|
|
18536
|
+
}
|
|
18537
|
+
|
|
18263
18538
|
// \u2500\u2500 Value segment parser \u2014 splits "text {{var}} text" into typed segments \u2500\u2500\u2500\u2500\u2500
|
|
18264
18539
|
|
|
18265
18540
|
type ValueSegment =
|
|
@@ -18515,21 +18790,31 @@ function VariableFormModal({
|
|
|
18515
18790
|
};
|
|
18516
18791
|
|
|
18517
18792
|
const handleSave = () => {
|
|
18793
|
+
if (!name.trim()) {
|
|
18794
|
+
setNameError(
|
|
18795
|
+
required
|
|
18796
|
+
? "Value is required for this key"
|
|
18797
|
+
: "Variable name is required"
|
|
18798
|
+
);
|
|
18799
|
+
return;
|
|
18800
|
+
}
|
|
18518
18801
|
const error = validateName(name);
|
|
18519
|
-
if (error
|
|
18520
|
-
setNameError(error
|
|
18802
|
+
if (error) {
|
|
18803
|
+
setNameError(error);
|
|
18521
18804
|
return;
|
|
18522
18805
|
}
|
|
18523
18806
|
onSave({ name: name.trim(), description: description.trim() || undefined, required });
|
|
18524
18807
|
};
|
|
18525
18808
|
|
|
18809
|
+
const hasInvalidFormat = Boolean(name.trim() && validateName(name));
|
|
18810
|
+
|
|
18526
18811
|
return (
|
|
18527
18812
|
<FormModal
|
|
18528
18813
|
open={open}
|
|
18529
18814
|
onOpenChange={onOpenChange}
|
|
18530
18815
|
title={mode === "create" ? "Create new variable" : "Edit variable"}
|
|
18531
18816
|
saveButtonText={mode === "create" ? "Save" : "Save Changes"}
|
|
18532
|
-
disableSave={
|
|
18817
|
+
disableSave={hasInvalidFormat}
|
|
18533
18818
|
onSave={handleSave}
|
|
18534
18819
|
size="default"
|
|
18535
18820
|
>
|
|
@@ -18546,15 +18831,28 @@ function VariableFormModal({
|
|
|
18546
18831
|
onChange={handleNameChange}
|
|
18547
18832
|
placeholder="e.g., customer_name"
|
|
18548
18833
|
maxLength={VARIABLE_NAME_MAX}
|
|
18549
|
-
|
|
18834
|
+
aria-invalid={Boolean(nameError)}
|
|
18835
|
+
className={cn(
|
|
18836
|
+
inputCls,
|
|
18837
|
+
"pr-16",
|
|
18838
|
+
nameError && "border-semantic-error-primary"
|
|
18839
|
+
)}
|
|
18550
18840
|
/>
|
|
18551
18841
|
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-sm text-semantic-text-muted pointer-events-none">
|
|
18552
18842
|
{name.length}/{VARIABLE_NAME_MAX}
|
|
18553
18843
|
</span>
|
|
18554
18844
|
</div>
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
|
|
18845
|
+
{nameError ? (
|
|
18846
|
+
<p className="m-0 flex items-start gap-1.5 text-sm text-semantic-error-primary">
|
|
18847
|
+
<CircleAlert className="size-4 shrink-0 mt-0.5" aria-hidden />
|
|
18848
|
+
<span>{nameError}</span>
|
|
18849
|
+
</p>
|
|
18850
|
+
) : (
|
|
18851
|
+
<span className="text-sm text-semantic-text-muted">
|
|
18852
|
+
Variable name should start with alphabet; Cannot have special characters except
|
|
18853
|
+
underscore (_)
|
|
18854
|
+
</span>
|
|
18855
|
+
)}
|
|
18558
18856
|
</div>
|
|
18559
18857
|
<TextField
|
|
18560
18858
|
label="Description (optional)"
|
|
@@ -18739,6 +19037,7 @@ function VariableInput({
|
|
|
18739
19037
|
{onEditVariable && (
|
|
18740
19038
|
<button
|
|
18741
19039
|
type="button"
|
|
19040
|
+
aria-label={\`Edit variable \${seg.name}\`}
|
|
18742
19041
|
onMouseDown={(e) => {
|
|
18743
19042
|
e.preventDefault();
|
|
18744
19043
|
e.stopPropagation();
|
|
@@ -19045,6 +19344,14 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19045
19344
|
/** Field + \`{{\u2026\` range to replace with \`{{name}}\` after create saves */
|
|
19046
19345
|
const [varInsertContext, setVarInsertContext] = React.useState<VarInsertContext | null>(null);
|
|
19047
19346
|
|
|
19347
|
+
/**
|
|
19348
|
+
* Required flags for function variables for Test API: seeded from \`variableGroups\` on open, then
|
|
19349
|
+
* updated when the user saves Create/Edit variable (covers missing/stale parent props).
|
|
19350
|
+
*/
|
|
19351
|
+
const [localFnVarRequiredByBareName, setLocalFnVarRequiredByBareName] = React.useState<
|
|
19352
|
+
Record<string, boolean>
|
|
19353
|
+
>({});
|
|
19354
|
+
|
|
19048
19355
|
const openVariableCreateModal = React.useCallback(() => {
|
|
19049
19356
|
setVarModalMode("create");
|
|
19050
19357
|
setVarModalInitialData(undefined);
|
|
@@ -19113,10 +19420,59 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19113
19420
|
setVarInsertContext(null);
|
|
19114
19421
|
}
|
|
19115
19422
|
|
|
19423
|
+
const requiredFlag = Boolean(data.required);
|
|
19424
|
+
|
|
19425
|
+
const applyRequiredToLocalMap = (bareName: string, required: boolean) => {
|
|
19426
|
+
setLocalFnVarRequiredByBareName((prev) => {
|
|
19427
|
+
const next = { ...prev };
|
|
19428
|
+
for (const key of placeholderInnerAliasesForBareName(bareName)) {
|
|
19429
|
+
next[key] = required;
|
|
19430
|
+
}
|
|
19431
|
+
return next;
|
|
19432
|
+
});
|
|
19433
|
+
};
|
|
19434
|
+
|
|
19116
19435
|
if (varModalMode === "create") {
|
|
19117
19436
|
onAddVariable?.(data);
|
|
19437
|
+
applyRequiredToLocalMap(trimmedName, requiredFlag);
|
|
19118
19438
|
} else {
|
|
19119
|
-
|
|
19439
|
+
const prevRaw = (varModalInitialData?.name ?? "").trim();
|
|
19440
|
+
if (prevRaw && prevRaw !== trimmedName) {
|
|
19441
|
+
setUrl((u) => renameVariableRefsInString(u, prevRaw, trimmedName));
|
|
19442
|
+
setBody((b) => renameVariableRefsInString(b, prevRaw, trimmedName));
|
|
19443
|
+
setHeaders((rows) =>
|
|
19444
|
+
rows.map((r) => ({
|
|
19445
|
+
...r,
|
|
19446
|
+
value: renameVariableRefsInString(r.value, prevRaw, trimmedName),
|
|
19447
|
+
}))
|
|
19448
|
+
);
|
|
19449
|
+
setQueryParams((rows) =>
|
|
19450
|
+
rows.map((r) => ({
|
|
19451
|
+
...r,
|
|
19452
|
+
value: renameVariableRefsInString(r.value, prevRaw, trimmedName),
|
|
19453
|
+
}))
|
|
19454
|
+
);
|
|
19455
|
+
setTestVarValues((prev) => {
|
|
19456
|
+
const next: Record<string, string> = {};
|
|
19457
|
+
for (const [k, v] of Object.entries(prev)) {
|
|
19458
|
+
next[renameVariableRefsInString(k, prevRaw, trimmedName)] = v;
|
|
19459
|
+
}
|
|
19460
|
+
return next;
|
|
19461
|
+
});
|
|
19462
|
+
setLocalFnVarRequiredByBareName((prev) => {
|
|
19463
|
+
const next = { ...prev };
|
|
19464
|
+
for (const key of placeholderInnerAliasesForBareName(prevRaw)) {
|
|
19465
|
+
delete next[key];
|
|
19466
|
+
}
|
|
19467
|
+
for (const key of placeholderInnerAliasesForBareName(trimmedName)) {
|
|
19468
|
+
next[key] = requiredFlag;
|
|
19469
|
+
}
|
|
19470
|
+
return next;
|
|
19471
|
+
});
|
|
19472
|
+
} else {
|
|
19473
|
+
applyRequiredToLocalMap(trimmedName, requiredFlag);
|
|
19474
|
+
}
|
|
19475
|
+
onEditVariable?.(prevRaw, data);
|
|
19120
19476
|
}
|
|
19121
19477
|
setVarModalOpen(false);
|
|
19122
19478
|
};
|
|
@@ -19170,7 +19526,8 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19170
19526
|
|
|
19171
19527
|
// Test variable values \u2014 filled by user before clicking Test API
|
|
19172
19528
|
const [testVarValues, setTestVarValues] = React.useState<Record<string, string>>({});
|
|
19173
|
-
|
|
19529
|
+
/** Set when user clicks Test API \u2014 drives inline errors for empty required variable values only (not Submit). */
|
|
19530
|
+
const [testApiRequiredAttempted, setTestApiRequiredAttempted] = React.useState(false);
|
|
19174
19531
|
|
|
19175
19532
|
// Unique {{variable}} refs found across url, body, headers, queryParams
|
|
19176
19533
|
const testableVars = React.useMemo(
|
|
@@ -19198,7 +19555,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19198
19555
|
setBody(initialData?.body ?? "");
|
|
19199
19556
|
setApiResponse("");
|
|
19200
19557
|
setStep2SubmitAttempted(false);
|
|
19201
|
-
|
|
19558
|
+
setTestApiRequiredAttempted(false);
|
|
19202
19559
|
setNameError("");
|
|
19203
19560
|
setUrlError("");
|
|
19204
19561
|
setBodyError("");
|
|
@@ -19207,6 +19564,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19207
19564
|
setUrlPopupStyle(undefined);
|
|
19208
19565
|
setBodyPopupStyle(undefined);
|
|
19209
19566
|
setTestVarValues({});
|
|
19567
|
+
setLocalFnVarRequiredByBareName(buildFnVarRequiredMapFromGroups(variableGroups));
|
|
19210
19568
|
setVarInsertContext(null);
|
|
19211
19569
|
}
|
|
19212
19570
|
// Re-run only when modal opens; intentionally exclude deep deps to avoid mid-session resets
|
|
@@ -19225,7 +19583,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19225
19583
|
setBody(initialData?.body ?? "");
|
|
19226
19584
|
setApiResponse("");
|
|
19227
19585
|
setStep2SubmitAttempted(false);
|
|
19228
|
-
|
|
19586
|
+
setTestApiRequiredAttempted(false);
|
|
19229
19587
|
setNameError("");
|
|
19230
19588
|
setUrlError("");
|
|
19231
19589
|
setBodyError("");
|
|
@@ -19234,8 +19592,9 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19234
19592
|
setUrlPopupStyle(undefined);
|
|
19235
19593
|
setBodyPopupStyle(undefined);
|
|
19236
19594
|
setTestVarValues({});
|
|
19595
|
+
setLocalFnVarRequiredByBareName(buildFnVarRequiredMapFromGroups(variableGroups));
|
|
19237
19596
|
setVarInsertContext(null);
|
|
19238
|
-
}, [initialData, initialStep, initialTab]);
|
|
19597
|
+
}, [initialData, initialStep, initialTab, variableGroups]);
|
|
19239
19598
|
|
|
19240
19599
|
const handleClose = React.useCallback(() => {
|
|
19241
19600
|
reset();
|
|
@@ -19298,15 +19657,18 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19298
19657
|
text.replace(/\\{\\{[^}]+\\}\\}/g, (match) => testVarValues[match] ?? match);
|
|
19299
19658
|
|
|
19300
19659
|
const handleTestApi = async () => {
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19660
|
+
// Validate all test variable values are filled (always runs, regardless of onTestApi)
|
|
19661
|
+
const requiredTestVars = testableVars.filter((v) =>
|
|
19662
|
+
isPlaceholderRequiredInTest(v, variableGroups, localFnVarRequiredByBareName)
|
|
19663
|
+
);
|
|
19664
|
+
if (requiredTestVars.length > 0) {
|
|
19665
|
+
setTestApiRequiredAttempted(true);
|
|
19666
|
+
const hasEmpty = requiredTestVars.some((v) => !testVarValues[v]?.trim());
|
|
19307
19667
|
if (hasEmpty) return;
|
|
19308
19668
|
}
|
|
19309
19669
|
|
|
19670
|
+
if (!onTestApi) return;
|
|
19671
|
+
|
|
19310
19672
|
setIsTesting(true);
|
|
19311
19673
|
try {
|
|
19312
19674
|
const step2: CreateFunctionStep2Data = {
|
|
@@ -19697,33 +20059,59 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19697
20059
|
<span className="text-sm text-semantic-text-muted">
|
|
19698
20060
|
Variable values for testing
|
|
19699
20061
|
</span>
|
|
19700
|
-
{testableVars.map((variable) => {
|
|
19701
|
-
const
|
|
20062
|
+
{testableVars.map((variable, varIndex) => {
|
|
20063
|
+
const mustFill = isPlaceholderRequiredInTest(
|
|
20064
|
+
variable,
|
|
20065
|
+
variableGroups,
|
|
20066
|
+
localFnVarRequiredByBareName
|
|
20067
|
+
);
|
|
20068
|
+
const isEmpty =
|
|
20069
|
+
mustFill &&
|
|
20070
|
+
testApiRequiredAttempted &&
|
|
20071
|
+
!testVarValues[variable]?.trim();
|
|
20072
|
+
const testVarErrId = \`fn-test-var-err-\${varIndex}\`;
|
|
19702
20073
|
return (
|
|
19703
20074
|
<div key={variable} className="flex flex-col gap-1">
|
|
19704
|
-
<div className="flex items-
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
20075
|
+
<div className="flex items-start gap-3">
|
|
20076
|
+
<span className="m-0 inline-flex shrink-0 items-center rounded-md bg-semantic-bg-ui px-2.5 py-1.5 text-sm font-mono text-semantic-text-secondary">
|
|
20077
|
+
{variable}
|
|
20078
|
+
</span>
|
|
20079
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
20080
|
+
<input
|
|
20081
|
+
type="text"
|
|
20082
|
+
value={testVarValues[variable] ?? ""}
|
|
20083
|
+
onChange={(e) =>
|
|
20084
|
+
setTestVarValues((prev) => ({
|
|
20085
|
+
...prev,
|
|
20086
|
+
[variable]: e.target.value,
|
|
20087
|
+
}))
|
|
20088
|
+
}
|
|
20089
|
+
placeholder="Value"
|
|
20090
|
+
className={cn(
|
|
20091
|
+
inputCls,
|
|
20092
|
+
"h-9 text-sm",
|
|
20093
|
+
isEmpty &&
|
|
20094
|
+
"border-semantic-error-primary focus:border-semantic-error-primary focus:shadow-none"
|
|
20095
|
+
)}
|
|
20096
|
+
aria-invalid={isEmpty}
|
|
20097
|
+
aria-describedby={isEmpty ? testVarErrId : undefined}
|
|
20098
|
+
/>
|
|
20099
|
+
{isEmpty && (
|
|
20100
|
+
<p
|
|
20101
|
+
id={testVarErrId}
|
|
20102
|
+
className="m-0 flex items-center gap-1.5 text-xs text-semantic-error-primary"
|
|
20103
|
+
>
|
|
20104
|
+
<span
|
|
20105
|
+
className="inline-flex size-4 shrink-0 items-center justify-center rounded-full bg-semantic-error-primary text-[10px] font-bold leading-none text-semantic-text-inverted"
|
|
20106
|
+
aria-hidden
|
|
20107
|
+
>
|
|
20108
|
+
!
|
|
20109
|
+
</span>
|
|
20110
|
+
<span>Value is required for this key</span>
|
|
20111
|
+
</p>
|
|
20112
|
+
)}
|
|
20113
|
+
</div>
|
|
19721
20114
|
</div>
|
|
19722
|
-
{isEmpty && (
|
|
19723
|
-
<p className="m-0 text-sm text-semantic-error-primary pl-[132px]">
|
|
19724
|
-
Test value is required
|
|
19725
|
-
</p>
|
|
19726
|
-
)}
|
|
19727
20115
|
</div>
|
|
19728
20116
|
);
|
|
19729
20117
|
})}
|
|
@@ -21047,28 +21435,60 @@ import {
|
|
|
21047
21435
|
AccordionTrigger,
|
|
21048
21436
|
AccordionContent,
|
|
21049
21437
|
} from "../accordion";
|
|
21438
|
+
import {
|
|
21439
|
+
defaultAdvancedSettingsNumericBounds,
|
|
21440
|
+
type AdvancedSettingsNumericBounds,
|
|
21441
|
+
} from "./advanced-settings-bounds";
|
|
21050
21442
|
|
|
21051
21443
|
// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
21052
21444
|
|
|
21053
21445
|
export interface AdvancedSettingsData {
|
|
21054
|
-
silenceTimeout
|
|
21055
|
-
callEndThreshold
|
|
21446
|
+
silenceTimeout?: number;
|
|
21447
|
+
callEndThreshold?: number;
|
|
21056
21448
|
interruptionHandling: boolean;
|
|
21057
21449
|
}
|
|
21058
21450
|
|
|
21451
|
+
/** Payload when a numeric advanced field finishes a blur validation pass. */
|
|
21452
|
+
export interface AdvancedSettingsNumericFieldBlurDetail {
|
|
21453
|
+
/** Value committed to form state (\`undefined\` if empty after blur). */
|
|
21454
|
+
value: number | undefined;
|
|
21455
|
+
/** \`false\` when the field shows a validation error after blur. */
|
|
21456
|
+
valid: boolean;
|
|
21457
|
+
}
|
|
21458
|
+
|
|
21059
21459
|
export interface AdvancedSettingsCardProps {
|
|
21060
21460
|
/** Current form data */
|
|
21061
21461
|
data: Partial<AdvancedSettingsData>;
|
|
21062
|
-
/** Callback when any field changes */
|
|
21462
|
+
/** Callback when any field in this card changes */
|
|
21063
21463
|
onChange: (patch: Partial<AdvancedSettingsData>) => void;
|
|
21064
|
-
/**
|
|
21464
|
+
/**
|
|
21465
|
+
* Shorthand min/max for both numeric fields. Overridden by explicit
|
|
21466
|
+
* \`silenceTimeoutMin\`, \`silenceTimeoutMax\`, \`callEndThresholdMin\`, or \`callEndThresholdMax\`
|
|
21467
|
+
* when those are passed.
|
|
21468
|
+
*/
|
|
21469
|
+
numericBounds?: Partial<AdvancedSettingsNumericBounds>;
|
|
21470
|
+
/** Min value for silence timeout spinner */
|
|
21065
21471
|
silenceTimeoutMin?: number;
|
|
21066
|
-
/** Max value for silence timeout spinner
|
|
21472
|
+
/** Max value for silence timeout spinner */
|
|
21067
21473
|
silenceTimeoutMax?: number;
|
|
21068
|
-
/** Min value for call end threshold spinner
|
|
21474
|
+
/** Min value for call end threshold spinner */
|
|
21069
21475
|
callEndThresholdMin?: number;
|
|
21070
|
-
/** Max value for call end threshold spinner
|
|
21476
|
+
/** Max value for call end threshold spinner */
|
|
21071
21477
|
callEndThresholdMax?: number;
|
|
21478
|
+
/** When true, an empty value shows a validation error on blur (default: true) */
|
|
21479
|
+
silenceTimeoutRequired?: boolean;
|
|
21480
|
+
/** When true, an empty value shows a validation error on blur (default: true) */
|
|
21481
|
+
callEndThresholdRequired?: boolean;
|
|
21482
|
+
/** Fires after each successful \`onChange\` from this card (including stepper and switch). */
|
|
21483
|
+
onAdvancedSettingsChange?: (patch: Partial<AdvancedSettingsData>) => void;
|
|
21484
|
+
/** Fires when silence timeout input blurs after validation. */
|
|
21485
|
+
onSilenceTimeoutBlur?: (
|
|
21486
|
+
detail: AdvancedSettingsNumericFieldBlurDetail
|
|
21487
|
+
) => void;
|
|
21488
|
+
/** Fires when call end threshold input blurs after validation. */
|
|
21489
|
+
onCallEndThresholdBlur?: (
|
|
21490
|
+
detail: AdvancedSettingsNumericFieldBlurDetail
|
|
21491
|
+
) => void;
|
|
21072
21492
|
/** Disables all fields in the card (view mode) */
|
|
21073
21493
|
disabled?: boolean;
|
|
21074
21494
|
/** Additional className */
|
|
@@ -21094,54 +21514,196 @@ function Field({
|
|
|
21094
21514
|
);
|
|
21095
21515
|
}
|
|
21096
21516
|
|
|
21097
|
-
function
|
|
21517
|
+
function clamp(n: number, min: number, max: number): number {
|
|
21518
|
+
return Math.min(max, Math.max(min, n));
|
|
21519
|
+
}
|
|
21520
|
+
|
|
21521
|
+
function ValidatedNumberSpinner({
|
|
21522
|
+
id,
|
|
21098
21523
|
value,
|
|
21099
21524
|
onChange,
|
|
21100
|
-
min
|
|
21101
|
-
max
|
|
21525
|
+
min,
|
|
21526
|
+
max,
|
|
21527
|
+
required,
|
|
21102
21528
|
disabled,
|
|
21529
|
+
onBlurCommit,
|
|
21103
21530
|
}: {
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
|
|
21531
|
+
id: string;
|
|
21532
|
+
value: number | undefined;
|
|
21533
|
+
onChange: (v: number | undefined) => void;
|
|
21534
|
+
min: number;
|
|
21535
|
+
max: number;
|
|
21536
|
+
required: boolean;
|
|
21108
21537
|
disabled?: boolean;
|
|
21538
|
+
onBlurCommit?: (detail: AdvancedSettingsNumericFieldBlurDetail) => void;
|
|
21109
21539
|
}) {
|
|
21540
|
+
const [inputStr, setInputStr] = React.useState(() =>
|
|
21541
|
+
value === undefined ? "" : String(value)
|
|
21542
|
+
);
|
|
21543
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
21544
|
+
const focusedRef = React.useRef(false);
|
|
21545
|
+
const prevValueRef = React.useRef(value);
|
|
21546
|
+
|
|
21547
|
+
React.useEffect(() => {
|
|
21548
|
+
if (prevValueRef.current !== value) {
|
|
21549
|
+
prevValueRef.current = value;
|
|
21550
|
+
if (!focusedRef.current) {
|
|
21551
|
+
setInputStr(value === undefined ? "" : String(value));
|
|
21552
|
+
setError(null);
|
|
21553
|
+
}
|
|
21554
|
+
}
|
|
21555
|
+
}, [value]);
|
|
21556
|
+
|
|
21557
|
+
const stepBase = (): number | null => {
|
|
21558
|
+
const t = inputStr.trim();
|
|
21559
|
+
if (t !== "") {
|
|
21560
|
+
const n = Number(t);
|
|
21561
|
+
if (Number.isFinite(n)) return n;
|
|
21562
|
+
}
|
|
21563
|
+
if (value !== undefined) return value;
|
|
21564
|
+
return null;
|
|
21565
|
+
};
|
|
21566
|
+
|
|
21567
|
+
const canIncrement = (): boolean => {
|
|
21568
|
+
const b = stepBase();
|
|
21569
|
+
if (b === null) return true;
|
|
21570
|
+
return b < max;
|
|
21571
|
+
};
|
|
21572
|
+
|
|
21573
|
+
const canDecrement = (): boolean => {
|
|
21574
|
+
const b = stepBase();
|
|
21575
|
+
if (b === null) return false;
|
|
21576
|
+
return b > min;
|
|
21577
|
+
};
|
|
21578
|
+
|
|
21579
|
+
const applyStep = (delta: 1 | -1) => {
|
|
21580
|
+
let n = stepBase();
|
|
21581
|
+
if (n === null) {
|
|
21582
|
+
if (delta > 0) n = min - 1;
|
|
21583
|
+
else return;
|
|
21584
|
+
}
|
|
21585
|
+
const next = clamp(n + delta, min, max);
|
|
21586
|
+
setInputStr(String(next));
|
|
21587
|
+
setError(null);
|
|
21588
|
+
onChange(next);
|
|
21589
|
+
};
|
|
21590
|
+
|
|
21591
|
+
const handleBlur = () => {
|
|
21592
|
+
focusedRef.current = false;
|
|
21593
|
+
const trimmed = inputStr.trim();
|
|
21594
|
+
if (trimmed === "") {
|
|
21595
|
+
onChange(undefined);
|
|
21596
|
+
if (required) {
|
|
21597
|
+
setError("This field is required");
|
|
21598
|
+
onBlurCommit?.({ value: undefined, valid: false });
|
|
21599
|
+
} else {
|
|
21600
|
+
setError(null);
|
|
21601
|
+
onBlurCommit?.({ value: undefined, valid: true });
|
|
21602
|
+
}
|
|
21603
|
+
return;
|
|
21604
|
+
}
|
|
21605
|
+
const num = Number(trimmed);
|
|
21606
|
+
if (!Number.isFinite(num)) {
|
|
21607
|
+
setError("Enter a valid number");
|
|
21608
|
+
onBlurCommit?.({ value, valid: false });
|
|
21609
|
+
return;
|
|
21610
|
+
}
|
|
21611
|
+
if (num < min || num > max) {
|
|
21612
|
+
setError(\`Value must be between \${min} and \${max}\`);
|
|
21613
|
+
onBlurCommit?.({ value, valid: false });
|
|
21614
|
+
return;
|
|
21615
|
+
}
|
|
21616
|
+
setError(null);
|
|
21617
|
+
onChange(num);
|
|
21618
|
+
onBlurCommit?.({ value: num, valid: true });
|
|
21619
|
+
};
|
|
21620
|
+
|
|
21621
|
+
const errorId = error ? \`\${id}-error\` : undefined;
|
|
21622
|
+
|
|
21110
21623
|
return (
|
|
21111
|
-
<div className=
|
|
21112
|
-
<
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
type="
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
className="flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors disabled:cursor-not-allowed"
|
|
21127
|
-
aria-label="Increase"
|
|
21128
|
-
>
|
|
21129
|
-
<ChevronUp className="size-3" />
|
|
21130
|
-
</button>
|
|
21131
|
-
<button
|
|
21132
|
-
type="button"
|
|
21133
|
-
onClick={() => onChange(Math.max(min, value - 1))}
|
|
21624
|
+
<div className="flex flex-col gap-1">
|
|
21625
|
+
<div
|
|
21626
|
+
className={cn(
|
|
21627
|
+
"flex w-full items-center gap-2.5 px-4 py-2.5 border border-solid bg-semantic-bg-primary rounded",
|
|
21628
|
+
error
|
|
21629
|
+
? "border-semantic-error-primary"
|
|
21630
|
+
: "border-semantic-border-layout",
|
|
21631
|
+
disabled && "opacity-50 cursor-not-allowed"
|
|
21632
|
+
)}
|
|
21633
|
+
>
|
|
21634
|
+
<input
|
|
21635
|
+
id={id}
|
|
21636
|
+
type="text"
|
|
21637
|
+
inputMode="numeric"
|
|
21638
|
+
value={inputStr}
|
|
21134
21639
|
disabled={disabled}
|
|
21135
|
-
|
|
21136
|
-
aria-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21640
|
+
aria-invalid={error ? true : undefined}
|
|
21641
|
+
aria-describedby={errorId}
|
|
21642
|
+
onFocus={() => {
|
|
21643
|
+
focusedRef.current = true;
|
|
21644
|
+
setError(null);
|
|
21645
|
+
}}
|
|
21646
|
+
onBlur={handleBlur}
|
|
21647
|
+
onChange={(e) => setInputStr(e.target.value)}
|
|
21648
|
+
className="flex-1 min-w-0 text-base text-semantic-text-primary bg-transparent outline-none disabled:cursor-not-allowed"
|
|
21649
|
+
/>
|
|
21650
|
+
<div className="flex flex-col items-center shrink-0 gap-0.5">
|
|
21651
|
+
<button
|
|
21652
|
+
type="button"
|
|
21653
|
+
onClick={() => applyStep(1)}
|
|
21654
|
+
disabled={disabled || !canIncrement()}
|
|
21655
|
+
className="flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
|
21656
|
+
aria-label="Increase"
|
|
21657
|
+
>
|
|
21658
|
+
<ChevronUp className="size-3" />
|
|
21659
|
+
</button>
|
|
21660
|
+
<button
|
|
21661
|
+
type="button"
|
|
21662
|
+
onClick={() => applyStep(-1)}
|
|
21663
|
+
disabled={disabled || !canDecrement()}
|
|
21664
|
+
className="flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
|
21665
|
+
aria-label="Decrease"
|
|
21666
|
+
>
|
|
21667
|
+
<ChevronDown className="size-3" />
|
|
21668
|
+
</button>
|
|
21669
|
+
</div>
|
|
21140
21670
|
</div>
|
|
21671
|
+
{error ? (
|
|
21672
|
+
<p
|
|
21673
|
+
id={errorId}
|
|
21674
|
+
role="alert"
|
|
21675
|
+
className="m-0 text-xs text-semantic-error-primary"
|
|
21676
|
+
>
|
|
21677
|
+
{error}
|
|
21678
|
+
</p>
|
|
21679
|
+
) : null}
|
|
21141
21680
|
</div>
|
|
21142
21681
|
);
|
|
21143
21682
|
}
|
|
21144
21683
|
|
|
21684
|
+
function useCorrectOutOfRangeNumeric(
|
|
21685
|
+
raw: number | undefined,
|
|
21686
|
+
min: number,
|
|
21687
|
+
max: number,
|
|
21688
|
+
disabled: boolean | undefined,
|
|
21689
|
+
patchKey: "silenceTimeout" | "callEndThreshold",
|
|
21690
|
+
onChange: (patch: Partial<AdvancedSettingsData>) => void
|
|
21691
|
+
) {
|
|
21692
|
+
const onChangeRef = React.useRef(onChange);
|
|
21693
|
+
React.useEffect(() => {
|
|
21694
|
+
onChangeRef.current = onChange;
|
|
21695
|
+
});
|
|
21696
|
+
|
|
21697
|
+
React.useEffect(() => {
|
|
21698
|
+
if (disabled || raw === undefined) return;
|
|
21699
|
+
if (raw < min || raw > max) {
|
|
21700
|
+
onChangeRef.current({
|
|
21701
|
+
[patchKey]: clamp(raw, min, max),
|
|
21702
|
+
} as Partial<AdvancedSettingsData>);
|
|
21703
|
+
}
|
|
21704
|
+
}, [raw, min, max, disabled, patchKey]);
|
|
21705
|
+
}
|
|
21706
|
+
|
|
21145
21707
|
// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
21146
21708
|
|
|
21147
21709
|
const AdvancedSettingsCard = React.forwardRef(
|
|
@@ -21149,15 +21711,63 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21149
21711
|
{
|
|
21150
21712
|
data,
|
|
21151
21713
|
onChange,
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21714
|
+
numericBounds,
|
|
21715
|
+
silenceTimeoutMin: silenceTimeoutMinProp,
|
|
21716
|
+
silenceTimeoutMax: silenceTimeoutMaxProp,
|
|
21717
|
+
callEndThresholdMin: callEndThresholdMinProp,
|
|
21718
|
+
callEndThresholdMax: callEndThresholdMaxProp,
|
|
21719
|
+
silenceTimeoutRequired = true,
|
|
21720
|
+
callEndThresholdRequired = true,
|
|
21721
|
+
onAdvancedSettingsChange,
|
|
21722
|
+
onSilenceTimeoutBlur,
|
|
21723
|
+
onCallEndThresholdBlur,
|
|
21156
21724
|
disabled,
|
|
21157
21725
|
className,
|
|
21158
21726
|
}: AdvancedSettingsCardProps,
|
|
21159
21727
|
ref: React.Ref<HTMLDivElement>
|
|
21160
21728
|
) => {
|
|
21729
|
+
const silenceTimeoutMin =
|
|
21730
|
+
silenceTimeoutMinProp ??
|
|
21731
|
+
numericBounds?.silenceTimeoutMin ??
|
|
21732
|
+
defaultAdvancedSettingsNumericBounds.silenceTimeoutMin;
|
|
21733
|
+
const silenceTimeoutMax =
|
|
21734
|
+
silenceTimeoutMaxProp ??
|
|
21735
|
+
numericBounds?.silenceTimeoutMax ??
|
|
21736
|
+
defaultAdvancedSettingsNumericBounds.silenceTimeoutMax;
|
|
21737
|
+
const callEndThresholdMin =
|
|
21738
|
+
callEndThresholdMinProp ??
|
|
21739
|
+
numericBounds?.callEndThresholdMin ??
|
|
21740
|
+
defaultAdvancedSettingsNumericBounds.callEndThresholdMin;
|
|
21741
|
+
const callEndThresholdMax =
|
|
21742
|
+
callEndThresholdMaxProp ??
|
|
21743
|
+
numericBounds?.callEndThresholdMax ??
|
|
21744
|
+
defaultAdvancedSettingsNumericBounds.callEndThresholdMax;
|
|
21745
|
+
|
|
21746
|
+
const emitPatch = React.useCallback(
|
|
21747
|
+
(patch: Partial<AdvancedSettingsData>) => {
|
|
21748
|
+
onChange(patch);
|
|
21749
|
+
onAdvancedSettingsChange?.(patch);
|
|
21750
|
+
},
|
|
21751
|
+
[onChange, onAdvancedSettingsChange]
|
|
21752
|
+
);
|
|
21753
|
+
|
|
21754
|
+
useCorrectOutOfRangeNumeric(
|
|
21755
|
+
data.silenceTimeout,
|
|
21756
|
+
silenceTimeoutMin,
|
|
21757
|
+
silenceTimeoutMax,
|
|
21758
|
+
disabled,
|
|
21759
|
+
"silenceTimeout",
|
|
21760
|
+
emitPatch
|
|
21761
|
+
);
|
|
21762
|
+
useCorrectOutOfRangeNumeric(
|
|
21763
|
+
data.callEndThreshold,
|
|
21764
|
+
callEndThresholdMin,
|
|
21765
|
+
callEndThresholdMax,
|
|
21766
|
+
disabled,
|
|
21767
|
+
"callEndThreshold",
|
|
21768
|
+
emitPatch
|
|
21769
|
+
);
|
|
21770
|
+
|
|
21161
21771
|
return (
|
|
21162
21772
|
<div
|
|
21163
21773
|
ref={ref}
|
|
@@ -21178,28 +21788,31 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21178
21788
|
{/* Number fields section */}
|
|
21179
21789
|
<div className="px-4 pt-4 pb-4 flex flex-col gap-5 border-b border-solid border-semantic-border-layout sm:px-6 sm:pt-5 sm:pb-6">
|
|
21180
21790
|
<Field label="Silence Timeout (seconds)">
|
|
21181
|
-
<
|
|
21182
|
-
|
|
21183
|
-
|
|
21791
|
+
<ValidatedNumberSpinner
|
|
21792
|
+
id="advanced-silence-timeout"
|
|
21793
|
+
value={data.silenceTimeout}
|
|
21794
|
+
onChange={(v) => emitPatch({ silenceTimeout: v })}
|
|
21184
21795
|
min={silenceTimeoutMin}
|
|
21185
21796
|
max={silenceTimeoutMax}
|
|
21797
|
+
required={silenceTimeoutRequired}
|
|
21186
21798
|
disabled={disabled}
|
|
21799
|
+
onBlurCommit={onSilenceTimeoutBlur}
|
|
21187
21800
|
/>
|
|
21188
|
-
<p className="m-0 text-xs text-semantic-text-muted">
|
|
21189
|
-
Default: 15 seconds
|
|
21190
|
-
</p>
|
|
21191
21801
|
</Field>
|
|
21192
21802
|
|
|
21193
21803
|
<Field label="Call End Threshold">
|
|
21194
|
-
<
|
|
21195
|
-
|
|
21196
|
-
|
|
21804
|
+
<ValidatedNumberSpinner
|
|
21805
|
+
id="advanced-call-end-threshold"
|
|
21806
|
+
value={data.callEndThreshold}
|
|
21807
|
+
onChange={(v) => emitPatch({ callEndThreshold: v })}
|
|
21197
21808
|
min={callEndThresholdMin}
|
|
21198
21809
|
max={callEndThresholdMax}
|
|
21810
|
+
required={callEndThresholdRequired}
|
|
21199
21811
|
disabled={disabled}
|
|
21812
|
+
onBlurCommit={onCallEndThresholdBlur}
|
|
21200
21813
|
/>
|
|
21201
21814
|
<p className="m-0 text-xs text-semantic-text-muted">
|
|
21202
|
-
Drop call after n consecutive silences.
|
|
21815
|
+
Drop call after n consecutive silences.
|
|
21203
21816
|
</p>
|
|
21204
21817
|
</Field>
|
|
21205
21818
|
</div>
|
|
@@ -21217,7 +21830,7 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21217
21830
|
<Switch
|
|
21218
21831
|
checked={data.interruptionHandling ?? true}
|
|
21219
21832
|
onCheckedChange={(v) =>
|
|
21220
|
-
|
|
21833
|
+
emitPatch({ interruptionHandling: v })
|
|
21221
21834
|
}
|
|
21222
21835
|
disabled={disabled}
|
|
21223
21836
|
/>
|
|
@@ -21233,6 +21846,39 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21233
21846
|
AdvancedSettingsCard.displayName = "AdvancedSettingsCard";
|
|
21234
21847
|
|
|
21235
21848
|
export { AdvancedSettingsCard };
|
|
21849
|
+
`, prefix)
|
|
21850
|
+
},
|
|
21851
|
+
{
|
|
21852
|
+
name: "advanced-settings-bounds.ts",
|
|
21853
|
+
content: prefixTailwindClasses(`/**
|
|
21854
|
+
* Min/max for Advanced Settings numeric fields (silence timeout, call end threshold).
|
|
21855
|
+
* Use with \`numericBounds\` / \`advancedSettingsNumericBounds\` props or edit
|
|
21856
|
+
* \`defaultAdvancedSettingsNumericBounds\` for deployment defaults.
|
|
21857
|
+
*/
|
|
21858
|
+
export interface AdvancedSettingsNumericBounds {
|
|
21859
|
+
silenceTimeoutMin: number;
|
|
21860
|
+
silenceTimeoutMax: number;
|
|
21861
|
+
callEndThresholdMin: number;
|
|
21862
|
+
callEndThresholdMax: number;
|
|
21863
|
+
}
|
|
21864
|
+
|
|
21865
|
+
/**
|
|
21866
|
+
* Default min/max for Advanced Settings numeric fields (silence timeout, call end threshold).
|
|
21867
|
+
*
|
|
21868
|
+
* Change these values per client or deployment. You can also override any bound by passing
|
|
21869
|
+
* \`advancedSettingsNumericBounds\`, \`numericBounds\`, or the individual min/max props
|
|
21870
|
+
* on \`IvrBotConfig\` / \`AdvancedSettingsCard\`.
|
|
21871
|
+
*/
|
|
21872
|
+
export const defaultAdvancedSettingsNumericBounds: AdvancedSettingsNumericBounds =
|
|
21873
|
+
{
|
|
21874
|
+
silenceTimeoutMin: 3,
|
|
21875
|
+
silenceTimeoutMax: 15,
|
|
21876
|
+
callEndThresholdMin: 1,
|
|
21877
|
+
callEndThresholdMax: 10,
|
|
21878
|
+
};
|
|
21879
|
+
|
|
21880
|
+
export type DefaultAdvancedSettingsNumericBounds =
|
|
21881
|
+
typeof defaultAdvancedSettingsNumericBounds;
|
|
21236
21882
|
`, prefix)
|
|
21237
21883
|
},
|
|
21238
21884
|
{
|
|
@@ -21393,6 +22039,11 @@ export { FallbackPromptsCard };
|
|
|
21393
22039
|
{
|
|
21394
22040
|
name: "types.ts",
|
|
21395
22041
|
content: prefixTailwindClasses(`import type { UploadProgressHandlers } from "../file-upload-modal";
|
|
22042
|
+
import type { AdvancedSettingsNumericBounds } from "./advanced-settings-bounds";
|
|
22043
|
+
import type {
|
|
22044
|
+
AdvancedSettingsData,
|
|
22045
|
+
AdvancedSettingsNumericFieldBlurDetail,
|
|
22046
|
+
} from "./advanced-settings-card";
|
|
21396
22047
|
|
|
21397
22048
|
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
21398
22049
|
|
|
@@ -21501,7 +22152,10 @@ export interface CreateFunctionModalProps {
|
|
|
21501
22152
|
initialTab?: FunctionTabType;
|
|
21502
22153
|
/** Session variables available for {{ autocomplete in URL, body, header values, and query param values */
|
|
21503
22154
|
sessionVariables?: string[];
|
|
21504
|
-
/**
|
|
22155
|
+
/**
|
|
22156
|
+
* Grouped variables for the {{ autocomplete popup (overrides flat list display when provided).
|
|
22157
|
+
* Items with \`required: true\` are validated when the user clicks Test API (inline errors under each empty field).
|
|
22158
|
+
*/
|
|
21505
22159
|
variableGroups?: VariableGroup[];
|
|
21506
22160
|
/**
|
|
21507
22161
|
* Called when user saves a new variable from the autocomplete popup.
|
|
@@ -21510,7 +22164,11 @@ export interface CreateFunctionModalProps {
|
|
|
21510
22164
|
* so it appears in the dropdown on the next open.
|
|
21511
22165
|
*/
|
|
21512
22166
|
onAddVariable?: (data: VariableFormData) => void;
|
|
21513
|
-
/**
|
|
22167
|
+
/**
|
|
22168
|
+
* Called when the user saves "Edit variable". The modal already renames
|
|
22169
|
+
* \`{{function.name}}\` / \`{{name}}\` across URL, body, headers, query params, and test values.
|
|
22170
|
+
* Update your \`variableGroups\` (and persist to your backend) using \`originalName\` \u2192 \`data.name\`.
|
|
22171
|
+
*/
|
|
21514
22172
|
onEditVariable?: (originalName: string, data: VariableFormData) => void;
|
|
21515
22173
|
/** When true, all form fields are disabled (view mode) but Next is enabled so user can browse steps */
|
|
21516
22174
|
disabled?: boolean;
|
|
@@ -21530,8 +22188,10 @@ export interface IvrBotConfigData {
|
|
|
21530
22188
|
functions: FunctionItem[];
|
|
21531
22189
|
frustrationHandoverEnabled: boolean;
|
|
21532
22190
|
escalationDepartment: string;
|
|
21533
|
-
|
|
21534
|
-
|
|
22191
|
+
/** Undefined when the field was cleared; validate before save/publish. */
|
|
22192
|
+
silenceTimeout?: number;
|
|
22193
|
+
/** Undefined when the field was cleared; validate before save/publish. */
|
|
22194
|
+
callEndThreshold?: number;
|
|
21535
22195
|
interruptionHandling: boolean;
|
|
21536
22196
|
}
|
|
21537
22197
|
|
|
@@ -21618,17 +22278,49 @@ export interface IvrBotConfigProps {
|
|
|
21618
22278
|
languageOptions?: SelectOption[];
|
|
21619
22279
|
/** Override session variable chips for BotBehaviorCard */
|
|
21620
22280
|
sessionVariables?: string[];
|
|
22281
|
+
/**
|
|
22282
|
+
* Function-scoped variables for Create / Edit Function modal (\`{{\` autocomplete; \`required\` applies to Test API validation only).
|
|
22283
|
+
* Pass the same groups your app persists; items with \`required: true\` block Test API until test values are filled for placeholders used in the request.
|
|
22284
|
+
*/
|
|
22285
|
+
functionVariableGroups?: VariableGroup[];
|
|
22286
|
+
/** When set with \`functionVariableGroups\`, called after the user saves a new variable from the modal. */
|
|
22287
|
+
onAddFunctionVariable?: (data: VariableFormData) => void;
|
|
22288
|
+
/** When set with \`functionVariableGroups\`, called after the user saves an edited variable. */
|
|
22289
|
+
onEditFunctionVariable?: (originalName: string, data: VariableFormData) => void;
|
|
21621
22290
|
/** Override escalation department options for FrustrationHandoverCard */
|
|
21622
22291
|
escalationDepartmentOptions?: SelectOption[];
|
|
21623
|
-
/**
|
|
22292
|
+
/**
|
|
22293
|
+
* Shorthand min/max for Advanced Settings numeric fields. Individual
|
|
22294
|
+
* \`silenceTimeoutMin\` / \`silenceTimeoutMax\` / \`callEndThresholdMin\` / \`callEndThresholdMax\`
|
|
22295
|
+
* override corresponding entries when set.
|
|
22296
|
+
*/
|
|
22297
|
+
advancedSettingsNumericBounds?: Partial<AdvancedSettingsNumericBounds>;
|
|
22298
|
+
/** Override silence timeout min (after \`advancedSettingsNumericBounds\`) */
|
|
21624
22299
|
silenceTimeoutMin?: number;
|
|
22300
|
+
/** Override silence timeout max (after \`advancedSettingsNumericBounds\`) */
|
|
21625
22301
|
silenceTimeoutMax?: number;
|
|
21626
|
-
/** Override call end threshold
|
|
22302
|
+
/** Override call end threshold min (after \`advancedSettingsNumericBounds\`) */
|
|
21627
22303
|
callEndThresholdMin?: number;
|
|
22304
|
+
/** Override call end threshold max (after \`advancedSettingsNumericBounds\`) */
|
|
21628
22305
|
callEndThresholdMax?: number;
|
|
22306
|
+
/**
|
|
22307
|
+
* Fires when any Advanced Settings field changes (numeric commit, stepper, interruption toggle).
|
|
22308
|
+
*/
|
|
22309
|
+
onAdvancedSettingsChange?: (patch: Partial<AdvancedSettingsData>) => void;
|
|
22310
|
+
/** Fires when silence timeout blurs after validation (see \`AdvancedSettingsNumericFieldBlurDetail\`). */
|
|
22311
|
+
onSilenceTimeoutBlur?: (
|
|
22312
|
+
detail: AdvancedSettingsNumericFieldBlurDetail
|
|
22313
|
+
) => void;
|
|
22314
|
+
/** Fires when call end threshold blurs after validation. */
|
|
22315
|
+
onCallEndThresholdBlur?: (
|
|
22316
|
+
detail: AdvancedSettingsNumericFieldBlurDetail
|
|
22317
|
+
) => void;
|
|
21629
22318
|
className?: string;
|
|
21630
22319
|
}
|
|
21631
22320
|
|
|
22321
|
+
export type { AdvancedSettingsNumericBounds } from "./advanced-settings-bounds";
|
|
22322
|
+
export type { AdvancedSettingsNumericFieldBlurDetail } from "./advanced-settings-card";
|
|
22323
|
+
|
|
21632
22324
|
// \u2500\u2500\u2500 File Upload Modal (re-exported from shared module) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
21633
22325
|
|
|
21634
22326
|
export type {
|
|
@@ -21647,6 +22339,12 @@ export { KnowledgeBaseCard } from "./knowledge-base-card";
|
|
|
21647
22339
|
export { FunctionsCard } from "./functions-card";
|
|
21648
22340
|
export { FrustrationHandoverCard } from "./frustration-handover-card";
|
|
21649
22341
|
export { AdvancedSettingsCard } from "./advanced-settings-card";
|
|
22342
|
+
export {
|
|
22343
|
+
defaultAdvancedSettingsNumericBounds,
|
|
22344
|
+
type AdvancedSettingsNumericBounds,
|
|
22345
|
+
type DefaultAdvancedSettingsNumericBounds,
|
|
22346
|
+
} from "./advanced-settings-bounds";
|
|
22347
|
+
export type { AdvancedSettingsNumericFieldBlurDetail } from "./advanced-settings-card";
|
|
21650
22348
|
export { FallbackPromptsCard } from "./fallback-prompts-card";
|
|
21651
22349
|
export type {
|
|
21652
22350
|
FallbackPromptsData,
|