myoperator-ui 0.0.224 → 0.0.226
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 +953 -233
- 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,6 +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>>({});
|
|
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);
|
|
19173
19531
|
|
|
19174
19532
|
// Unique {{variable}} refs found across url, body, headers, queryParams
|
|
19175
19533
|
const testableVars = React.useMemo(
|
|
@@ -19197,6 +19555,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19197
19555
|
setBody(initialData?.body ?? "");
|
|
19198
19556
|
setApiResponse("");
|
|
19199
19557
|
setStep2SubmitAttempted(false);
|
|
19558
|
+
setTestApiRequiredAttempted(false);
|
|
19200
19559
|
setNameError("");
|
|
19201
19560
|
setUrlError("");
|
|
19202
19561
|
setBodyError("");
|
|
@@ -19205,6 +19564,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19205
19564
|
setUrlPopupStyle(undefined);
|
|
19206
19565
|
setBodyPopupStyle(undefined);
|
|
19207
19566
|
setTestVarValues({});
|
|
19567
|
+
setLocalFnVarRequiredByBareName(buildFnVarRequiredMapFromGroups(variableGroups));
|
|
19208
19568
|
setVarInsertContext(null);
|
|
19209
19569
|
}
|
|
19210
19570
|
// Re-run only when modal opens; intentionally exclude deep deps to avoid mid-session resets
|
|
@@ -19223,6 +19583,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19223
19583
|
setBody(initialData?.body ?? "");
|
|
19224
19584
|
setApiResponse("");
|
|
19225
19585
|
setStep2SubmitAttempted(false);
|
|
19586
|
+
setTestApiRequiredAttempted(false);
|
|
19226
19587
|
setNameError("");
|
|
19227
19588
|
setUrlError("");
|
|
19228
19589
|
setBodyError("");
|
|
@@ -19231,8 +19592,9 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19231
19592
|
setUrlPopupStyle(undefined);
|
|
19232
19593
|
setBodyPopupStyle(undefined);
|
|
19233
19594
|
setTestVarValues({});
|
|
19595
|
+
setLocalFnVarRequiredByBareName(buildFnVarRequiredMapFromGroups(variableGroups));
|
|
19234
19596
|
setVarInsertContext(null);
|
|
19235
|
-
}, [initialData, initialStep, initialTab]);
|
|
19597
|
+
}, [initialData, initialStep, initialTab, variableGroups]);
|
|
19236
19598
|
|
|
19237
19599
|
const handleClose = React.useCallback(() => {
|
|
19238
19600
|
reset();
|
|
@@ -19295,7 +19657,18 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19295
19657
|
text.replace(/\\{\\{[^}]+\\}\\}/g, (match) => testVarValues[match] ?? match);
|
|
19296
19658
|
|
|
19297
19659
|
const handleTestApi = async () => {
|
|
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());
|
|
19667
|
+
if (hasEmpty) return;
|
|
19668
|
+
}
|
|
19669
|
+
|
|
19298
19670
|
if (!onTestApi) return;
|
|
19671
|
+
|
|
19299
19672
|
setIsTesting(true);
|
|
19300
19673
|
try {
|
|
19301
19674
|
const step2: CreateFunctionStep2Data = {
|
|
@@ -19686,25 +20059,62 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19686
20059
|
<span className="text-sm text-semantic-text-muted">
|
|
19687
20060
|
Variable values for testing
|
|
19688
20061
|
</span>
|
|
19689
|
-
{testableVars.map((variable) =>
|
|
19690
|
-
|
|
19691
|
-
|
|
19692
|
-
|
|
19693
|
-
|
|
19694
|
-
|
|
19695
|
-
|
|
19696
|
-
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
|
|
19702
|
-
|
|
19703
|
-
|
|
19704
|
-
|
|
19705
|
-
|
|
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}\`;
|
|
20073
|
+
return (
|
|
20074
|
+
<div key={variable} className="flex flex-col gap-1">
|
|
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>
|
|
20114
|
+
</div>
|
|
19706
20115
|
</div>
|
|
19707
|
-
|
|
20116
|
+
);
|
|
20117
|
+
})}
|
|
19708
20118
|
</div>
|
|
19709
20119
|
)}
|
|
19710
20120
|
|
|
@@ -21025,28 +21435,60 @@ import {
|
|
|
21025
21435
|
AccordionTrigger,
|
|
21026
21436
|
AccordionContent,
|
|
21027
21437
|
} from "../accordion";
|
|
21438
|
+
import {
|
|
21439
|
+
defaultAdvancedSettingsNumericBounds,
|
|
21440
|
+
type AdvancedSettingsNumericBounds,
|
|
21441
|
+
} from "./advanced-settings-bounds";
|
|
21028
21442
|
|
|
21029
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
|
|
21030
21444
|
|
|
21031
21445
|
export interface AdvancedSettingsData {
|
|
21032
|
-
silenceTimeout
|
|
21033
|
-
callEndThreshold
|
|
21446
|
+
silenceTimeout?: number;
|
|
21447
|
+
callEndThreshold?: number;
|
|
21034
21448
|
interruptionHandling: boolean;
|
|
21035
21449
|
}
|
|
21036
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
|
+
|
|
21037
21459
|
export interface AdvancedSettingsCardProps {
|
|
21038
21460
|
/** Current form data */
|
|
21039
21461
|
data: Partial<AdvancedSettingsData>;
|
|
21040
|
-
/** Callback when any field changes */
|
|
21462
|
+
/** Callback when any field in this card changes */
|
|
21041
21463
|
onChange: (patch: Partial<AdvancedSettingsData>) => void;
|
|
21042
|
-
/**
|
|
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 */
|
|
21043
21471
|
silenceTimeoutMin?: number;
|
|
21044
|
-
/** Max value for silence timeout spinner
|
|
21472
|
+
/** Max value for silence timeout spinner */
|
|
21045
21473
|
silenceTimeoutMax?: number;
|
|
21046
|
-
/** Min value for call end threshold spinner
|
|
21474
|
+
/** Min value for call end threshold spinner */
|
|
21047
21475
|
callEndThresholdMin?: number;
|
|
21048
|
-
/** Max value for call end threshold spinner
|
|
21476
|
+
/** Max value for call end threshold spinner */
|
|
21049
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;
|
|
21050
21492
|
/** Disables all fields in the card (view mode) */
|
|
21051
21493
|
disabled?: boolean;
|
|
21052
21494
|
/** Additional className */
|
|
@@ -21072,54 +21514,196 @@ function Field({
|
|
|
21072
21514
|
);
|
|
21073
21515
|
}
|
|
21074
21516
|
|
|
21075
|
-
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,
|
|
21076
21523
|
value,
|
|
21077
21524
|
onChange,
|
|
21078
|
-
min
|
|
21079
|
-
max
|
|
21525
|
+
min,
|
|
21526
|
+
max,
|
|
21527
|
+
required,
|
|
21080
21528
|
disabled,
|
|
21529
|
+
onBlurCommit,
|
|
21081
21530
|
}: {
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21531
|
+
id: string;
|
|
21532
|
+
value: number | undefined;
|
|
21533
|
+
onChange: (v: number | undefined) => void;
|
|
21534
|
+
min: number;
|
|
21535
|
+
max: number;
|
|
21536
|
+
required: boolean;
|
|
21086
21537
|
disabled?: boolean;
|
|
21538
|
+
onBlurCommit?: (detail: AdvancedSettingsNumericFieldBlurDetail) => void;
|
|
21087
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
|
+
|
|
21088
21623
|
return (
|
|
21089
|
-
<div className=
|
|
21090
|
-
<
|
|
21091
|
-
|
|
21092
|
-
|
|
21093
|
-
|
|
21094
|
-
|
|
21095
|
-
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
type="
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
className="flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors disabled:cursor-not-allowed"
|
|
21105
|
-
aria-label="Increase"
|
|
21106
|
-
>
|
|
21107
|
-
<ChevronUp className="size-3" />
|
|
21108
|
-
</button>
|
|
21109
|
-
<button
|
|
21110
|
-
type="button"
|
|
21111
|
-
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}
|
|
21112
21639
|
disabled={disabled}
|
|
21113
|
-
|
|
21114
|
-
aria-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
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>
|
|
21118
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}
|
|
21119
21680
|
</div>
|
|
21120
21681
|
);
|
|
21121
21682
|
}
|
|
21122
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
|
+
|
|
21123
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
|
|
21124
21708
|
|
|
21125
21709
|
const AdvancedSettingsCard = React.forwardRef(
|
|
@@ -21127,15 +21711,63 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21127
21711
|
{
|
|
21128
21712
|
data,
|
|
21129
21713
|
onChange,
|
|
21130
|
-
|
|
21131
|
-
|
|
21132
|
-
|
|
21133
|
-
|
|
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,
|
|
21134
21724
|
disabled,
|
|
21135
21725
|
className,
|
|
21136
21726
|
}: AdvancedSettingsCardProps,
|
|
21137
21727
|
ref: React.Ref<HTMLDivElement>
|
|
21138
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
|
+
|
|
21139
21771
|
return (
|
|
21140
21772
|
<div
|
|
21141
21773
|
ref={ref}
|
|
@@ -21156,28 +21788,31 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21156
21788
|
{/* Number fields section */}
|
|
21157
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">
|
|
21158
21790
|
<Field label="Silence Timeout (seconds)">
|
|
21159
|
-
<
|
|
21160
|
-
|
|
21161
|
-
|
|
21791
|
+
<ValidatedNumberSpinner
|
|
21792
|
+
id="advanced-silence-timeout"
|
|
21793
|
+
value={data.silenceTimeout}
|
|
21794
|
+
onChange={(v) => emitPatch({ silenceTimeout: v })}
|
|
21162
21795
|
min={silenceTimeoutMin}
|
|
21163
21796
|
max={silenceTimeoutMax}
|
|
21797
|
+
required={silenceTimeoutRequired}
|
|
21164
21798
|
disabled={disabled}
|
|
21799
|
+
onBlurCommit={onSilenceTimeoutBlur}
|
|
21165
21800
|
/>
|
|
21166
|
-
<p className="m-0 text-xs text-semantic-text-muted">
|
|
21167
|
-
Default: 15 seconds
|
|
21168
|
-
</p>
|
|
21169
21801
|
</Field>
|
|
21170
21802
|
|
|
21171
21803
|
<Field label="Call End Threshold">
|
|
21172
|
-
<
|
|
21173
|
-
|
|
21174
|
-
|
|
21804
|
+
<ValidatedNumberSpinner
|
|
21805
|
+
id="advanced-call-end-threshold"
|
|
21806
|
+
value={data.callEndThreshold}
|
|
21807
|
+
onChange={(v) => emitPatch({ callEndThreshold: v })}
|
|
21175
21808
|
min={callEndThresholdMin}
|
|
21176
21809
|
max={callEndThresholdMax}
|
|
21810
|
+
required={callEndThresholdRequired}
|
|
21177
21811
|
disabled={disabled}
|
|
21812
|
+
onBlurCommit={onCallEndThresholdBlur}
|
|
21178
21813
|
/>
|
|
21179
21814
|
<p className="m-0 text-xs text-semantic-text-muted">
|
|
21180
|
-
Drop call after n consecutive silences.
|
|
21815
|
+
Drop call after n consecutive silences.
|
|
21181
21816
|
</p>
|
|
21182
21817
|
</Field>
|
|
21183
21818
|
</div>
|
|
@@ -21195,7 +21830,7 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21195
21830
|
<Switch
|
|
21196
21831
|
checked={data.interruptionHandling ?? true}
|
|
21197
21832
|
onCheckedChange={(v) =>
|
|
21198
|
-
|
|
21833
|
+
emitPatch({ interruptionHandling: v })
|
|
21199
21834
|
}
|
|
21200
21835
|
disabled={disabled}
|
|
21201
21836
|
/>
|
|
@@ -21211,6 +21846,39 @@ const AdvancedSettingsCard = React.forwardRef(
|
|
|
21211
21846
|
AdvancedSettingsCard.displayName = "AdvancedSettingsCard";
|
|
21212
21847
|
|
|
21213
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;
|
|
21214
21882
|
`, prefix)
|
|
21215
21883
|
},
|
|
21216
21884
|
{
|
|
@@ -21371,6 +22039,11 @@ export { FallbackPromptsCard };
|
|
|
21371
22039
|
{
|
|
21372
22040
|
name: "types.ts",
|
|
21373
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";
|
|
21374
22047
|
|
|
21375
22048
|
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
21376
22049
|
|
|
@@ -21479,7 +22152,10 @@ export interface CreateFunctionModalProps {
|
|
|
21479
22152
|
initialTab?: FunctionTabType;
|
|
21480
22153
|
/** Session variables available for {{ autocomplete in URL, body, header values, and query param values */
|
|
21481
22154
|
sessionVariables?: string[];
|
|
21482
|
-
/**
|
|
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
|
+
*/
|
|
21483
22159
|
variableGroups?: VariableGroup[];
|
|
21484
22160
|
/**
|
|
21485
22161
|
* Called when user saves a new variable from the autocomplete popup.
|
|
@@ -21488,7 +22164,11 @@ export interface CreateFunctionModalProps {
|
|
|
21488
22164
|
* so it appears in the dropdown on the next open.
|
|
21489
22165
|
*/
|
|
21490
22166
|
onAddVariable?: (data: VariableFormData) => void;
|
|
21491
|
-
/**
|
|
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
|
+
*/
|
|
21492
22172
|
onEditVariable?: (originalName: string, data: VariableFormData) => void;
|
|
21493
22173
|
/** When true, all form fields are disabled (view mode) but Next is enabled so user can browse steps */
|
|
21494
22174
|
disabled?: boolean;
|
|
@@ -21508,8 +22188,10 @@ export interface IvrBotConfigData {
|
|
|
21508
22188
|
functions: FunctionItem[];
|
|
21509
22189
|
frustrationHandoverEnabled: boolean;
|
|
21510
22190
|
escalationDepartment: string;
|
|
21511
|
-
|
|
21512
|
-
|
|
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;
|
|
21513
22195
|
interruptionHandling: boolean;
|
|
21514
22196
|
}
|
|
21515
22197
|
|
|
@@ -21596,17 +22278,49 @@ export interface IvrBotConfigProps {
|
|
|
21596
22278
|
languageOptions?: SelectOption[];
|
|
21597
22279
|
/** Override session variable chips for BotBehaviorCard */
|
|
21598
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;
|
|
21599
22290
|
/** Override escalation department options for FrustrationHandoverCard */
|
|
21600
22291
|
escalationDepartmentOptions?: SelectOption[];
|
|
21601
|
-
/**
|
|
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\`) */
|
|
21602
22299
|
silenceTimeoutMin?: number;
|
|
22300
|
+
/** Override silence timeout max (after \`advancedSettingsNumericBounds\`) */
|
|
21603
22301
|
silenceTimeoutMax?: number;
|
|
21604
|
-
/** Override call end threshold
|
|
22302
|
+
/** Override call end threshold min (after \`advancedSettingsNumericBounds\`) */
|
|
21605
22303
|
callEndThresholdMin?: number;
|
|
22304
|
+
/** Override call end threshold max (after \`advancedSettingsNumericBounds\`) */
|
|
21606
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;
|
|
21607
22318
|
className?: string;
|
|
21608
22319
|
}
|
|
21609
22320
|
|
|
22321
|
+
export type { AdvancedSettingsNumericBounds } from "./advanced-settings-bounds";
|
|
22322
|
+
export type { AdvancedSettingsNumericFieldBlurDetail } from "./advanced-settings-card";
|
|
22323
|
+
|
|
21610
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
|
|
21611
22325
|
|
|
21612
22326
|
export type {
|
|
@@ -21625,6 +22339,12 @@ export { KnowledgeBaseCard } from "./knowledge-base-card";
|
|
|
21625
22339
|
export { FunctionsCard } from "./functions-card";
|
|
21626
22340
|
export { FrustrationHandoverCard } from "./frustration-handover-card";
|
|
21627
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";
|
|
21628
22348
|
export { FallbackPromptsCard } from "./fallback-prompts-card";
|
|
21629
22349
|
export type {
|
|
21630
22350
|
FallbackPromptsData,
|