myoperator-ui 0.0.204-beta.3 → 0.0.204-beta.4
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 +679 -554
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1053,7 +1053,17 @@ const Select = SelectPrimitive.Root;
|
|
|
1053
1053
|
|
|
1054
1054
|
const SelectGroup = SelectPrimitive.Group;
|
|
1055
1055
|
|
|
1056
|
-
const SelectValue =
|
|
1056
|
+
const SelectValue = React.forwardRef<
|
|
1057
|
+
React.ElementRef<typeof SelectPrimitive.Value>,
|
|
1058
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Value>
|
|
1059
|
+
>(({ className, ...props }, ref) => (
|
|
1060
|
+
<SelectPrimitive.Value
|
|
1061
|
+
ref={ref}
|
|
1062
|
+
className={cn("[&[data-placeholder]]:text-semantic-text-muted", className)}
|
|
1063
|
+
{...props}
|
|
1064
|
+
/>
|
|
1065
|
+
));
|
|
1066
|
+
SelectValue.displayName = SelectPrimitive.Value.displayName;
|
|
1057
1067
|
|
|
1058
1068
|
export interface SelectTriggerProps
|
|
1059
1069
|
extends
|
|
@@ -12100,7 +12110,7 @@ export const CreateBotModal = React.forwardRef<
|
|
|
12100
12110
|
|
|
12101
12111
|
return (
|
|
12102
12112
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
12103
|
-
<DialogContent ref={ref} size="sm" className={cn(className)}>
|
|
12113
|
+
<DialogContent ref={ref} size="sm" className={cn("mx-4 sm:mx-auto", className)}>
|
|
12104
12114
|
<DialogHeader>
|
|
12105
12115
|
<DialogTitle>Create AI bot</DialogTitle>
|
|
12106
12116
|
</DialogHeader>
|
|
@@ -12136,7 +12146,7 @@ export const CreateBotModal = React.forwardRef<
|
|
|
12136
12146
|
<span className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
|
|
12137
12147
|
Select Bot Type
|
|
12138
12148
|
</span>
|
|
12139
|
-
<div className="flex gap-3">
|
|
12149
|
+
<div className="flex flex-col gap-3 sm:flex-row">
|
|
12140
12150
|
{BOT_TYPE_OPTIONS.map(({ id, label, description }) => {
|
|
12141
12151
|
const isSelected = selectedType === id;
|
|
12142
12152
|
return (
|
|
@@ -12145,7 +12155,7 @@ export const CreateBotModal = React.forwardRef<
|
|
|
12145
12155
|
type="button"
|
|
12146
12156
|
onClick={() => setSelectedType(id)}
|
|
12147
12157
|
className={cn(
|
|
12148
|
-
"flex flex-
|
|
12158
|
+
"flex flex-row items-center gap-3 p-3 rounded-lg border text-left sm:flex-col sm:gap-2.5 sm:flex-1 sm:h-[134px] sm:justify-center",
|
|
12149
12159
|
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus",
|
|
12150
12160
|
isSelected
|
|
12151
12161
|
? "bg-semantic-info-surface border-semantic-border-focus shadow-sm"
|
|
@@ -12191,12 +12201,13 @@ export const CreateBotModal = React.forwardRef<
|
|
|
12191
12201
|
</div>
|
|
12192
12202
|
|
|
12193
12203
|
{/* Footer actions */}
|
|
12194
|
-
<div className="flex
|
|
12195
|
-
<Button variant="outline" onClick={handleClose}>
|
|
12204
|
+
<div className="flex flex-col-reverse gap-3 mt-2 sm:flex-row sm:justify-end sm:gap-4">
|
|
12205
|
+
<Button variant="outline" className="w-full sm:w-auto" onClick={handleClose}>
|
|
12196
12206
|
Cancel
|
|
12197
12207
|
</Button>
|
|
12198
12208
|
<Button
|
|
12199
12209
|
variant="default"
|
|
12210
|
+
className="w-full sm:w-auto"
|
|
12200
12211
|
onClick={handleSubmit}
|
|
12201
12212
|
disabled={!name.trim()}
|
|
12202
12213
|
>
|
|
@@ -12247,7 +12258,7 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
|
|
|
12247
12258
|
return (
|
|
12248
12259
|
<div ref={ref} className={cn("flex flex-col w-full", className)}>
|
|
12249
12260
|
{/* Page header */}
|
|
12250
|
-
<div className="flex
|
|
12261
|
+
<div className="flex flex-col gap-4 pb-5 mb-6 border-b border-semantic-border-layout sm:flex-row sm:items-center sm:justify-between">
|
|
12251
12262
|
<div className="flex flex-col gap-1.5">
|
|
12252
12263
|
<h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px]">
|
|
12253
12264
|
{title}
|
|
@@ -12258,20 +12269,20 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
|
|
|
12258
12269
|
</div>
|
|
12259
12270
|
|
|
12260
12271
|
{/* Search bar */}
|
|
12261
|
-
<div className="flex items-center gap-2 h-10 px-2.5 border border-semantic-border-input rounded bg-semantic-bg-primary hover:border-semantic-border-input-focus focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]">
|
|
12272
|
+
<div className="flex items-center gap-2 h-10 px-2.5 border border-semantic-border-input rounded bg-semantic-bg-primary hover:border-semantic-border-input-focus focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)] w-full sm:w-auto">
|
|
12262
12273
|
<Search className="size-[14px] text-semantic-text-muted shrink-0" />
|
|
12263
12274
|
<input
|
|
12264
12275
|
type="text"
|
|
12265
12276
|
value={searchQuery}
|
|
12266
12277
|
onChange={(e) => handleSearch(e.target.value)}
|
|
12267
12278
|
placeholder="Search bot..."
|
|
12268
|
-
className="text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none w-[180px]"
|
|
12279
|
+
className="text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none w-full sm:w-[180px]"
|
|
12269
12280
|
/>
|
|
12270
12281
|
</div>
|
|
12271
12282
|
</div>
|
|
12272
12283
|
|
|
12273
12284
|
{/* Bot grid */}
|
|
12274
|
-
<div className="grid grid-cols-
|
|
12285
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
|
|
12275
12286
|
{/* Create new bot card */}
|
|
12276
12287
|
<button
|
|
12277
12288
|
type="button"
|
|
@@ -12408,6 +12419,7 @@ import {
|
|
|
12408
12419
|
ChevronLeft,
|
|
12409
12420
|
ChevronDown,
|
|
12410
12421
|
ChevronRight,
|
|
12422
|
+
ChevronUp,
|
|
12411
12423
|
Plus,
|
|
12412
12424
|
Download,
|
|
12413
12425
|
Trash2,
|
|
@@ -12418,6 +12430,13 @@ import {
|
|
|
12418
12430
|
import { cn } from "../../../lib/utils";
|
|
12419
12431
|
import { Button } from "../button";
|
|
12420
12432
|
import { Badge } from "../badge";
|
|
12433
|
+
import {
|
|
12434
|
+
Select,
|
|
12435
|
+
SelectContent,
|
|
12436
|
+
SelectItem,
|
|
12437
|
+
SelectTrigger,
|
|
12438
|
+
SelectValue,
|
|
12439
|
+
} from "../select";
|
|
12421
12440
|
import { tagVariants } from "../tag";
|
|
12422
12441
|
import { Switch } from "../switch";
|
|
12423
12442
|
import {
|
|
@@ -12459,13 +12478,13 @@ function SectionCard({
|
|
|
12459
12478
|
className
|
|
12460
12479
|
)}
|
|
12461
12480
|
>
|
|
12462
|
-
<div className="flex items-center justify-between px-
|
|
12481
|
+
<div className="flex items-center justify-between px-4 py-4 border-b border-semantic-border-layout sm:px-6">
|
|
12463
12482
|
<h2 className="m-0 text-base font-semibold text-semantic-text-primary">
|
|
12464
12483
|
{title}
|
|
12465
12484
|
</h2>
|
|
12466
12485
|
{action}
|
|
12467
12486
|
</div>
|
|
12468
|
-
<div className="px-6 py-5">{children}</div>
|
|
12487
|
+
<div className="px-4 py-4 sm:px-6 sm:py-5">{children}</div>
|
|
12469
12488
|
</div>
|
|
12470
12489
|
);
|
|
12471
12490
|
}
|
|
@@ -12563,8 +12582,20 @@ function StyledTextarea({
|
|
|
12563
12582
|
);
|
|
12564
12583
|
}
|
|
12565
12584
|
|
|
12566
|
-
// \u2500\u2500\u2500
|
|
12567
|
-
const
|
|
12585
|
+
// \u2500\u2500\u2500 Primary Role options \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
|
|
12586
|
+
const PRIMARY_ROLE_OPTIONS = [
|
|
12587
|
+
{ value: "customer-support", label: "Customer Support Agent" },
|
|
12588
|
+
{ value: "sales", label: "Sales Representative" },
|
|
12589
|
+
{ value: "technical-support", label: "Technical Support" },
|
|
12590
|
+
{ value: "billing-support", label: "Billing Support" },
|
|
12591
|
+
{ value: "appointment-scheduling", label: "Appointment Scheduling" },
|
|
12592
|
+
{ value: "order-status", label: "Order Status & Tracking" },
|
|
12593
|
+
{ value: "lead-qualification", label: "Lead Qualification" },
|
|
12594
|
+
{ value: "general-inquiries", label: "General Inquiries" },
|
|
12595
|
+
];
|
|
12596
|
+
|
|
12597
|
+
// \u2500\u2500\u2500 Tone Input \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
|
|
12598
|
+
const PRESET_TONES = [
|
|
12568
12599
|
"Professional and highly concise",
|
|
12569
12600
|
"Friendly and conversational",
|
|
12570
12601
|
"Calm and reassuring",
|
|
@@ -12578,86 +12609,93 @@ const TONE_OPTIONS = [
|
|
|
12578
12609
|
"Direct and efficient",
|
|
12579
12610
|
];
|
|
12580
12611
|
|
|
12581
|
-
function
|
|
12582
|
-
|
|
12612
|
+
function ToneInput({
|
|
12613
|
+
value,
|
|
12583
12614
|
onChange,
|
|
12584
12615
|
}: {
|
|
12585
|
-
|
|
12586
|
-
onChange: (
|
|
12616
|
+
value: string[];
|
|
12617
|
+
onChange: (v: string[]) => void;
|
|
12587
12618
|
}) {
|
|
12619
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12588
12620
|
const [inputValue, setInputValue] = React.useState("");
|
|
12589
|
-
const [open, setOpen] = React.useState(false);
|
|
12590
12621
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
12591
12622
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
12592
12623
|
|
|
12593
|
-
|
|
12594
|
-
(
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12624
|
+
React.useEffect(() => {
|
|
12625
|
+
const handler = (e: MouseEvent) => {
|
|
12626
|
+
if (
|
|
12627
|
+
containerRef.current &&
|
|
12628
|
+
!containerRef.current.contains(e.target as Node)
|
|
12629
|
+
) {
|
|
12630
|
+
setIsOpen(false);
|
|
12631
|
+
setInputValue("");
|
|
12632
|
+
}
|
|
12633
|
+
};
|
|
12634
|
+
document.addEventListener("mousedown", handler);
|
|
12635
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
12636
|
+
}, []);
|
|
12598
12637
|
|
|
12599
12638
|
const addTone = (tone: string) => {
|
|
12600
12639
|
const trimmed = tone.trim();
|
|
12601
|
-
if (trimmed && !
|
|
12602
|
-
onChange([...
|
|
12640
|
+
if (trimmed && !value.includes(trimmed)) {
|
|
12641
|
+
onChange([...value, trimmed]);
|
|
12642
|
+
setInputValue("");
|
|
12603
12643
|
}
|
|
12604
|
-
setInputValue("");
|
|
12605
|
-
inputRef.current?.focus();
|
|
12606
12644
|
};
|
|
12607
12645
|
|
|
12608
12646
|
const removeTone = (tone: string) => {
|
|
12609
|
-
onChange(
|
|
12647
|
+
onChange(value.filter((t) => t !== tone));
|
|
12610
12648
|
};
|
|
12611
12649
|
|
|
12612
12650
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
12613
|
-
if (e.key === "Enter"
|
|
12651
|
+
if (e.key === "Enter") {
|
|
12614
12652
|
e.preventDefault();
|
|
12615
|
-
addTone(inputValue);
|
|
12616
|
-
} else if (e.key === "Backspace" && !inputValue &&
|
|
12617
|
-
|
|
12653
|
+
if (inputValue.trim()) addTone(inputValue);
|
|
12654
|
+
} else if (e.key === "Backspace" && !inputValue && value.length > 0) {
|
|
12655
|
+
removeTone(value[value.length - 1]);
|
|
12618
12656
|
} else if (e.key === "Escape") {
|
|
12619
|
-
|
|
12657
|
+
setIsOpen(false);
|
|
12658
|
+
setInputValue("");
|
|
12620
12659
|
}
|
|
12621
12660
|
};
|
|
12622
12661
|
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
setOpen(false);
|
|
12627
|
-
}
|
|
12628
|
-
};
|
|
12629
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
12630
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
12631
|
-
}, []);
|
|
12632
|
-
|
|
12633
|
-
const hasContent = values.length > 0 || inputValue.length > 0;
|
|
12662
|
+
const availablePresets = PRESET_TONES.filter((t) => !value.includes(t));
|
|
12663
|
+
const canAddCustom =
|
|
12664
|
+
Boolean(inputValue.trim()) && !value.includes(inputValue.trim());
|
|
12634
12665
|
|
|
12635
12666
|
return (
|
|
12636
|
-
<div ref={containerRef}
|
|
12637
|
-
{/* Trigger
|
|
12667
|
+
<div className="relative" ref={containerRef}>
|
|
12668
|
+
{/* Trigger */}
|
|
12638
12669
|
<div
|
|
12639
12670
|
className={cn(
|
|
12640
|
-
"min-h-10
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12671
|
+
"flex items-center gap-2 flex-wrap min-h-10 px-4 py-2 rounded border bg-semantic-bg-primary cursor-text transition-shadow",
|
|
12672
|
+
isOpen
|
|
12673
|
+
? "border-semantic-border-focus shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
12674
|
+
: "border-semantic-border-input hover:border-semantic-border-input-focus"
|
|
12644
12675
|
)}
|
|
12645
|
-
onClick={() => {
|
|
12676
|
+
onClick={() => {
|
|
12677
|
+
setIsOpen(true);
|
|
12678
|
+
inputRef.current?.focus();
|
|
12679
|
+
}}
|
|
12646
12680
|
>
|
|
12647
|
-
{/* Selected chips
|
|
12648
|
-
{
|
|
12681
|
+
{/* Selected chips */}
|
|
12682
|
+
{value.map((tone) => (
|
|
12649
12683
|
<span
|
|
12650
12684
|
key={tone}
|
|
12651
|
-
className="inline-flex items-center gap-
|
|
12685
|
+
className="inline-flex items-center gap-2 bg-semantic-info-surface px-2 py-1 rounded text-sm text-semantic-text-primary whitespace-nowrap"
|
|
12652
12686
|
>
|
|
12653
|
-
|
|
12687
|
+
{tone}
|
|
12654
12688
|
<button
|
|
12655
12689
|
type="button"
|
|
12656
|
-
|
|
12657
|
-
|
|
12690
|
+
onMouseDown={(e) => {
|
|
12691
|
+
e.stopPropagation();
|
|
12692
|
+
e.preventDefault();
|
|
12693
|
+
removeTone(tone);
|
|
12694
|
+
}}
|
|
12695
|
+
className="shrink-0 flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors"
|
|
12658
12696
|
aria-label={\`Remove \${tone}\`}
|
|
12659
12697
|
>
|
|
12660
|
-
<X className="size-
|
|
12698
|
+
<X className="size-2.5" />
|
|
12661
12699
|
</button>
|
|
12662
12700
|
</span>
|
|
12663
12701
|
))}
|
|
@@ -12667,110 +12705,75 @@ function ToneMultiSelect({
|
|
|
12667
12705
|
ref={inputRef}
|
|
12668
12706
|
type="text"
|
|
12669
12707
|
value={inputValue}
|
|
12670
|
-
onChange={(e) => {
|
|
12671
|
-
|
|
12708
|
+
onChange={(e) => {
|
|
12709
|
+
setInputValue(e.target.value);
|
|
12710
|
+
if (!isOpen) setIsOpen(true);
|
|
12711
|
+
}}
|
|
12712
|
+
onFocus={() => setIsOpen(true)}
|
|
12672
12713
|
onKeyDown={handleKeyDown}
|
|
12673
|
-
placeholder={
|
|
12674
|
-
className="flex-1 min-w-[100px] bg-transparent outline-none text-semantic-text-primary placeholder:text-semantic-text-muted"
|
|
12714
|
+
placeholder={value.length === 0 ? "Enter or select tone" : ""}
|
|
12715
|
+
className="flex-1 min-w-[100px] text-sm bg-transparent outline-none text-semantic-text-primary placeholder:text-semantic-text-muted"
|
|
12675
12716
|
/>
|
|
12676
12717
|
|
|
12677
|
-
{/* Chevron
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
className="
|
|
12682
|
-
|
|
12683
|
-
tabIndex={-1}
|
|
12684
|
-
>
|
|
12685
|
-
{hasContent
|
|
12686
|
-
? <ChevronRight className="size-4" />
|
|
12687
|
-
: <ChevronDown className={cn("size-4 transition-transform duration-150", open && "rotate-180")} />
|
|
12688
|
-
}
|
|
12689
|
-
</button>
|
|
12718
|
+
{/* Chevron \u2014 right when open, down when closed */}
|
|
12719
|
+
{isOpen ? (
|
|
12720
|
+
<ChevronRight className="size-5 text-semantic-text-muted shrink-0 ml-auto" />
|
|
12721
|
+
) : (
|
|
12722
|
+
<ChevronDown className="size-5 text-semantic-text-muted shrink-0 ml-auto" />
|
|
12723
|
+
)}
|
|
12690
12724
|
</div>
|
|
12691
12725
|
|
|
12692
|
-
{/*
|
|
12693
|
-
{
|
|
12694
|
-
<div className="
|
|
12695
|
-
|
|
12696
|
-
<
|
|
12697
|
-
|
|
12698
|
-
|
|
12726
|
+
{/* Dropdown panel */}
|
|
12727
|
+
{isOpen && (
|
|
12728
|
+
<div className="absolute z-50 top-full mt-1 w-full bg-semantic-bg-primary border border-semantic-border-layout rounded shadow-sm">
|
|
12729
|
+
{/* Preset option chips */}
|
|
12730
|
+
<div className="px-2.5 py-1.5 flex flex-wrap gap-1.5">
|
|
12731
|
+
{availablePresets.length > 0 ? (
|
|
12732
|
+
availablePresets.map((option) => (
|
|
12733
|
+
<button
|
|
12734
|
+
key={option}
|
|
12735
|
+
type="button"
|
|
12736
|
+
onMouseDown={(e) => {
|
|
12737
|
+
e.preventDefault();
|
|
12738
|
+
addTone(option);
|
|
12739
|
+
}}
|
|
12740
|
+
className="inline-flex items-center gap-2 bg-semantic-bg-ui px-2 py-1 rounded text-sm text-semantic-text-primary hover:bg-semantic-bg-hover transition-colors whitespace-nowrap"
|
|
12741
|
+
>
|
|
12742
|
+
<Plus className="size-2.5 shrink-0 text-semantic-text-muted" />
|
|
12743
|
+
{option}
|
|
12744
|
+
</button>
|
|
12745
|
+
))
|
|
12746
|
+
) : (
|
|
12747
|
+
<p className="m-0 text-sm text-semantic-text-muted px-1 py-0.5">
|
|
12748
|
+
All preset tones selected
|
|
12749
|
+
</p>
|
|
12750
|
+
)}
|
|
12751
|
+
</div>
|
|
12752
|
+
|
|
12753
|
+
{/* "Press enter to add" hint when typing a custom value */}
|
|
12754
|
+
{canAddCustom && (
|
|
12755
|
+
<div className="border-t border-semantic-border-layout px-4 py-3 text-center">
|
|
12756
|
+
<span className="text-sm font-semibold text-semantic-text-primary">
|
|
12757
|
+
Press enter to add “{inputValue}” \u21B5
|
|
12758
|
+
</span>
|
|
12759
|
+
</div>
|
|
12760
|
+
)}
|
|
12699
12761
|
</div>
|
|
12700
12762
|
)}
|
|
12701
12763
|
|
|
12702
|
-
{/*
|
|
12703
|
-
{
|
|
12704
|
-
<div className="
|
|
12705
|
-
<
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
type="button"
|
|
12710
|
-
onMouseDown={(e) => { e.preventDefault(); addTone(opt); }}
|
|
12711
|
-
className="inline-flex items-center gap-1.5 px-2 py-1 rounded bg-semantic-bg-ui text-xs text-semantic-text-primary hover:bg-semantic-bg-hover transition-colors shrink-0"
|
|
12712
|
-
>
|
|
12713
|
-
<Plus className="size-2.5 shrink-0 text-semantic-text-muted" />
|
|
12714
|
-
{opt}
|
|
12715
|
-
</button>
|
|
12716
|
-
))}
|
|
12717
|
-
</div>
|
|
12764
|
+
{/* Helper text shown below when dropdown is closed */}
|
|
12765
|
+
{!isOpen && (
|
|
12766
|
+
<div className="flex items-center gap-1.5 mt-1.5">
|
|
12767
|
+
<Info className="size-[18px] shrink-0 text-semantic-text-muted" />
|
|
12768
|
+
<p className="m-0 text-sm text-semantic-text-muted">
|
|
12769
|
+
Press Enter to add “Conversational” \u21B5
|
|
12770
|
+
</p>
|
|
12718
12771
|
</div>
|
|
12719
12772
|
)}
|
|
12720
12773
|
</div>
|
|
12721
12774
|
);
|
|
12722
12775
|
}
|
|
12723
12776
|
|
|
12724
|
-
// \u2500\u2500\u2500 Styled Select \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
|
|
12725
|
-
function StyledSelect({
|
|
12726
|
-
placeholder,
|
|
12727
|
-
value,
|
|
12728
|
-
onChange,
|
|
12729
|
-
options,
|
|
12730
|
-
disabled,
|
|
12731
|
-
className,
|
|
12732
|
-
}: {
|
|
12733
|
-
placeholder?: string;
|
|
12734
|
-
value?: string;
|
|
12735
|
-
onChange?: (v: string) => void;
|
|
12736
|
-
options?: { label: string; value: string }[];
|
|
12737
|
-
disabled?: boolean;
|
|
12738
|
-
className?: string;
|
|
12739
|
-
}) {
|
|
12740
|
-
return (
|
|
12741
|
-
<div className={cn("relative w-full", className)}>
|
|
12742
|
-
<select
|
|
12743
|
-
value={value ?? ""}
|
|
12744
|
-
onChange={(e) => onChange?.(e.target.value)}
|
|
12745
|
-
disabled={disabled}
|
|
12746
|
-
className={cn(
|
|
12747
|
-
"w-full h-10 pl-4 pr-10 text-sm rounded border appearance-none cursor-pointer",
|
|
12748
|
-
"border-semantic-border-input bg-semantic-bg-primary",
|
|
12749
|
-
value ? "text-semantic-text-primary" : "text-semantic-text-muted",
|
|
12750
|
-
"outline-none hover:border-semantic-border-input-focus",
|
|
12751
|
-
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
12752
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
12753
|
-
)}
|
|
12754
|
-
>
|
|
12755
|
-
{placeholder && (
|
|
12756
|
-
<option value="" disabled>
|
|
12757
|
-
{placeholder}
|
|
12758
|
-
</option>
|
|
12759
|
-
)}
|
|
12760
|
-
{options?.map((opt) => (
|
|
12761
|
-
<option key={opt.value} value={opt.value}>
|
|
12762
|
-
{opt.label}
|
|
12763
|
-
</option>
|
|
12764
|
-
))}
|
|
12765
|
-
</select>
|
|
12766
|
-
<ChevronDown
|
|
12767
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-semantic-text-muted pointer-events-none shrink-0"
|
|
12768
|
-
aria-hidden="true"
|
|
12769
|
-
/>
|
|
12770
|
-
</div>
|
|
12771
|
-
);
|
|
12772
|
-
}
|
|
12773
|
-
|
|
12774
12777
|
// \u2500\u2500\u2500 Who The Bot Is \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
|
|
12775
12778
|
function WhoTheBotIs({
|
|
12776
12779
|
data,
|
|
@@ -12794,44 +12797,60 @@ function WhoTheBotIs({
|
|
|
12794
12797
|
</Field>
|
|
12795
12798
|
|
|
12796
12799
|
<Field label="Primary Role">
|
|
12797
|
-
<
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12800
|
+
<Select
|
|
12801
|
+
value={data.primaryRole || undefined}
|
|
12802
|
+
onValueChange={(v) => onChange({ primaryRole: v })}
|
|
12803
|
+
>
|
|
12804
|
+
<SelectTrigger>
|
|
12805
|
+
<SelectValue placeholder="e.g., Customer Support Agent" />
|
|
12806
|
+
</SelectTrigger>
|
|
12807
|
+
<SelectContent>
|
|
12808
|
+
{PRIMARY_ROLE_OPTIONS.map((opt) => (
|
|
12809
|
+
<SelectItem key={opt.value} value={opt.value}>
|
|
12810
|
+
{opt.label}
|
|
12811
|
+
</SelectItem>
|
|
12812
|
+
))}
|
|
12813
|
+
</SelectContent>
|
|
12814
|
+
</Select>
|
|
12802
12815
|
</Field>
|
|
12803
12816
|
|
|
12804
12817
|
<Field label="Tone">
|
|
12805
|
-
<
|
|
12806
|
-
|
|
12807
|
-
onChange={(v) => onChange({
|
|
12818
|
+
<ToneInput
|
|
12819
|
+
value={Array.isArray(data.tone) ? data.tone : []}
|
|
12820
|
+
onChange={(v) => onChange({ tone: v })}
|
|
12808
12821
|
/>
|
|
12809
12822
|
</Field>
|
|
12810
12823
|
|
|
12811
|
-
<div className="grid grid-cols-
|
|
12824
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
12812
12825
|
<Field label="How It Sounds">
|
|
12813
|
-
<
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12826
|
+
<Select
|
|
12827
|
+
value={data.voice || undefined}
|
|
12828
|
+
onValueChange={(v) => onChange({ voice: v })}
|
|
12829
|
+
>
|
|
12830
|
+
<SelectTrigger>
|
|
12831
|
+
<SelectValue placeholder="Rhea - Female" />
|
|
12832
|
+
</SelectTrigger>
|
|
12833
|
+
<SelectContent>
|
|
12834
|
+
<SelectItem value="rhea-female">Rhea - Female</SelectItem>
|
|
12835
|
+
<SelectItem value="james-male">James - Male</SelectItem>
|
|
12836
|
+
<SelectItem value="aria-female">Aria - Female</SelectItem>
|
|
12837
|
+
</SelectContent>
|
|
12838
|
+
</Select>
|
|
12823
12839
|
</Field>
|
|
12824
12840
|
<Field label="What Language It Speaks">
|
|
12825
|
-
<
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12841
|
+
<Select
|
|
12842
|
+
value={data.language || undefined}
|
|
12843
|
+
onValueChange={(v) => onChange({ language: v })}
|
|
12844
|
+
>
|
|
12845
|
+
<SelectTrigger>
|
|
12846
|
+
<SelectValue placeholder="Select Language Mode" />
|
|
12847
|
+
</SelectTrigger>
|
|
12848
|
+
<SelectContent>
|
|
12849
|
+
<SelectItem value="en-in">English (India)</SelectItem>
|
|
12850
|
+
<SelectItem value="en-us">English (US)</SelectItem>
|
|
12851
|
+
<SelectItem value="hi-in">Hindi</SelectItem>
|
|
12852
|
+
</SelectContent>
|
|
12853
|
+
</Select>
|
|
12835
12854
|
</Field>
|
|
12836
12855
|
</div>
|
|
12837
12856
|
</div>
|
|
@@ -12916,14 +12935,14 @@ function FallbackPromptsAccordion({
|
|
|
12916
12935
|
<div className="bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden">
|
|
12917
12936
|
<Accordion type="single">
|
|
12918
12937
|
<AccordionItem value="fallback">
|
|
12919
|
-
<AccordionTrigger className="px-
|
|
12938
|
+
<AccordionTrigger className="px-4 py-4 border-b border-semantic-border-layout hover:no-underline sm:px-6 sm:py-5">
|
|
12920
12939
|
<span className="flex items-center gap-1.5 text-base font-semibold text-semantic-text-primary">
|
|
12921
12940
|
Fallback Prompts
|
|
12922
12941
|
<Info className="size-3.5 text-semantic-text-muted shrink-0" />
|
|
12923
12942
|
</span>
|
|
12924
12943
|
</AccordionTrigger>
|
|
12925
12944
|
<AccordionContent>
|
|
12926
|
-
<div className="px-
|
|
12945
|
+
<div className="px-4 pt-4 pb-2 flex flex-col gap-6 sm:px-6 sm:pt-6">
|
|
12927
12946
|
<Field label="Agent Busy Prompt">
|
|
12928
12947
|
<StyledTextarea
|
|
12929
12948
|
value={data.agentBusyPrompt ?? ""}
|
|
@@ -13035,7 +13054,7 @@ function FileUploadModal({
|
|
|
13035
13054
|
<DialogContent
|
|
13036
13055
|
size="default"
|
|
13037
13056
|
hideCloseButton
|
|
13038
|
-
className="max-w-[660px] rounded-xl p-
|
|
13057
|
+
className="mx-4 max-w-[660px] rounded-xl p-4 gap-0 sm:mx-auto sm:p-6"
|
|
13039
13058
|
>
|
|
13040
13059
|
{/* Header */}
|
|
13041
13060
|
<div className="flex items-center justify-between mb-6">
|
|
@@ -13074,11 +13093,11 @@ function FileUploadModal({
|
|
|
13074
13093
|
}}
|
|
13075
13094
|
onDragOver={(e) => e.preventDefault()}
|
|
13076
13095
|
>
|
|
13077
|
-
<div className="flex items-center gap-4">
|
|
13096
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
|
|
13078
13097
|
<button
|
|
13079
13098
|
type="button"
|
|
13080
13099
|
onClick={() => fileInputRef.current?.click()}
|
|
13081
|
-
className="h-10 px-4 rounded border border-semantic-border-layout bg-semantic-bg-primary text-sm font-semibold text-semantic-text-secondary shrink-0 hover:bg-semantic-bg-hover transition-colors"
|
|
13100
|
+
className="h-10 px-4 rounded border border-semantic-border-layout bg-semantic-bg-primary text-sm font-semibold text-semantic-text-secondary shrink-0 hover:bg-semantic-bg-hover transition-colors w-full sm:w-auto"
|
|
13082
13101
|
>
|
|
13083
13102
|
Upload from device
|
|
13084
13103
|
</button>
|
|
@@ -13170,11 +13189,11 @@ function FileUploadModal({
|
|
|
13170
13189
|
</div>
|
|
13171
13190
|
|
|
13172
13191
|
{/* Footer */}
|
|
13173
|
-
<div className="flex
|
|
13174
|
-
<Button variant="outline" onClick={handleClose}>
|
|
13192
|
+
<div className="flex flex-col-reverse gap-3 mt-4 sm:mt-6 sm:flex-row sm:justify-end sm:gap-2">
|
|
13193
|
+
<Button variant="outline" className="w-full sm:w-auto" onClick={handleClose}>
|
|
13175
13194
|
Cancel
|
|
13176
13195
|
</Button>
|
|
13177
|
-
<Button onClick={handleSave}>
|
|
13196
|
+
<Button className="w-full sm:w-auto" onClick={handleSave}>
|
|
13178
13197
|
Save
|
|
13179
13198
|
</Button>
|
|
13180
13199
|
</div>
|
|
@@ -13211,7 +13230,7 @@ function KnowledgeBase({
|
|
|
13211
13230
|
<>
|
|
13212
13231
|
<div className="bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden">
|
|
13213
13232
|
{/* Header */}
|
|
13214
|
-
<div className="flex items-center justify-between px-
|
|
13233
|
+
<div className="flex items-center justify-between px-4 py-4 border-b border-semantic-border-layout sm:px-6">
|
|
13215
13234
|
<div className="flex items-center gap-1.5">
|
|
13216
13235
|
<h2 className="m-0 text-base font-semibold text-semantic-text-primary">
|
|
13217
13236
|
Knowledge Base
|
|
@@ -13228,7 +13247,7 @@ function KnowledgeBase({
|
|
|
13228
13247
|
</button>
|
|
13229
13248
|
</div>
|
|
13230
13249
|
{/* File list */}
|
|
13231
|
-
<div className="px-6">
|
|
13250
|
+
<div className="px-4 sm:px-6">
|
|
13232
13251
|
{files.length === 0 ? (
|
|
13233
13252
|
<p className="m-0 text-sm text-semantic-text-muted text-center py-5">
|
|
13234
13253
|
No files added yet. Click “+ Files” to upload.
|
|
@@ -13300,7 +13319,7 @@ function FunctionsSection({
|
|
|
13300
13319
|
return (
|
|
13301
13320
|
<div className="bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden">
|
|
13302
13321
|
{/* Header */}
|
|
13303
|
-
<div className="flex items-center justify-between px-
|
|
13322
|
+
<div className="flex items-center justify-between px-4 py-4 border-b border-semantic-border-layout sm:px-6">
|
|
13304
13323
|
<div className="flex items-center gap-1.5">
|
|
13305
13324
|
<h2 className="m-0 text-base font-semibold text-semantic-text-primary">
|
|
13306
13325
|
Functions
|
|
@@ -13317,7 +13336,7 @@ function FunctionsSection({
|
|
|
13317
13336
|
</button>
|
|
13318
13337
|
</div>
|
|
13319
13338
|
{/* Function list */}
|
|
13320
|
-
<div className="px-
|
|
13339
|
+
<div className="px-4 py-4 sm:px-6">
|
|
13321
13340
|
{functions.length === 0 ? (
|
|
13322
13341
|
<p className="m-0 text-sm text-semantic-text-muted text-center py-2">
|
|
13323
13342
|
No functions added yet.
|
|
@@ -13361,7 +13380,7 @@ function FrustrationHandoverAccordion({
|
|
|
13361
13380
|
<div className="bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden">
|
|
13362
13381
|
<Accordion type="single">
|
|
13363
13382
|
<AccordionItem value="frustration">
|
|
13364
|
-
<AccordionTrigger className="px-
|
|
13383
|
+
<AccordionTrigger className="px-4 py-4 border-b border-semantic-border-layout hover:no-underline sm:px-6 sm:py-5">
|
|
13365
13384
|
<span className="flex items-center gap-1.5 text-base font-semibold text-semantic-text-primary">
|
|
13366
13385
|
Frustration Handover
|
|
13367
13386
|
<Info className="size-3.5 text-semantic-text-muted shrink-0" />
|
|
@@ -13369,7 +13388,7 @@ function FrustrationHandoverAccordion({
|
|
|
13369
13388
|
</AccordionTrigger>
|
|
13370
13389
|
<AccordionContent>
|
|
13371
13390
|
<div className="flex flex-col gap-6 pt-0 pb-2">
|
|
13372
|
-
<div className="flex items-center justify-between px-
|
|
13391
|
+
<div className="flex items-center justify-between px-4 py-2.5 sm:px-6">
|
|
13373
13392
|
<span className="text-sm text-semantic-text-primary">
|
|
13374
13393
|
Enable frustration-based escalation
|
|
13375
13394
|
</span>
|
|
@@ -13380,19 +13399,22 @@ function FrustrationHandoverAccordion({
|
|
|
13380
13399
|
}
|
|
13381
13400
|
/>
|
|
13382
13401
|
</div>
|
|
13383
|
-
<div className="px-
|
|
13402
|
+
<div className="px-4 pb-2 sm:px-6">
|
|
13384
13403
|
<Field label="Escalation Department">
|
|
13385
|
-
<
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
onChange={(v) => onChange({ escalationDepartment: v })}
|
|
13404
|
+
<Select
|
|
13405
|
+
value={data.escalationDepartment || undefined}
|
|
13406
|
+
onValueChange={(v) => onChange({ escalationDepartment: v })}
|
|
13389
13407
|
disabled={!data.frustrationHandoverEnabled}
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13408
|
+
>
|
|
13409
|
+
<SelectTrigger>
|
|
13410
|
+
<SelectValue placeholder="Select a department" />
|
|
13411
|
+
</SelectTrigger>
|
|
13412
|
+
<SelectContent>
|
|
13413
|
+
<SelectItem value="support">Support</SelectItem>
|
|
13414
|
+
<SelectItem value="sales">Sales</SelectItem>
|
|
13415
|
+
<SelectItem value="billing">Billing</SelectItem>
|
|
13416
|
+
</SelectContent>
|
|
13417
|
+
</Select>
|
|
13396
13418
|
</Field>
|
|
13397
13419
|
</div>
|
|
13398
13420
|
</div>
|
|
@@ -13416,31 +13438,31 @@ function NumberSpinner({
|
|
|
13416
13438
|
max?: number;
|
|
13417
13439
|
}) {
|
|
13418
13440
|
return (
|
|
13419
|
-
<div className="flex
|
|
13441
|
+
<div className="flex w-full items-center gap-2.5 px-4 py-2.5 border border-semantic-border-layout bg-semantic-bg-primary rounded">
|
|
13420
13442
|
<input
|
|
13421
13443
|
type="number"
|
|
13422
13444
|
value={value}
|
|
13423
13445
|
min={min}
|
|
13424
13446
|
max={max}
|
|
13425
13447
|
onChange={(e) => onChange(Number(e.target.value))}
|
|
13426
|
-
className="flex-1
|
|
13448
|
+
className="flex-1 min-w-0 text-sm text-semantic-text-primary bg-transparent outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
13427
13449
|
/>
|
|
13428
|
-
<div className="flex flex-col shrink-0
|
|
13450
|
+
<div className="flex flex-col items-center shrink-0 gap-0.5">
|
|
13429
13451
|
<button
|
|
13430
13452
|
type="button"
|
|
13431
13453
|
onClick={() => onChange(Math.min(max, value + 1))}
|
|
13432
|
-
className="flex
|
|
13454
|
+
className="flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors"
|
|
13433
13455
|
aria-label="Increase"
|
|
13434
13456
|
>
|
|
13435
|
-
|
|
13457
|
+
<ChevronUp className="size-3" />
|
|
13436
13458
|
</button>
|
|
13437
13459
|
<button
|
|
13438
13460
|
type="button"
|
|
13439
13461
|
onClick={() => onChange(Math.max(min, value - 1))}
|
|
13440
|
-
className="flex
|
|
13462
|
+
className="flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors"
|
|
13441
13463
|
aria-label="Decrease"
|
|
13442
13464
|
>
|
|
13443
|
-
|
|
13465
|
+
<ChevronDown className="size-3" />
|
|
13444
13466
|
</button>
|
|
13445
13467
|
</div>
|
|
13446
13468
|
</div>
|
|
@@ -13458,7 +13480,7 @@ function AdvancedSettingsAccordion({
|
|
|
13458
13480
|
<div className="bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden">
|
|
13459
13481
|
<Accordion type="single">
|
|
13460
13482
|
<AccordionItem value="advanced">
|
|
13461
|
-
<AccordionTrigger className="px-
|
|
13483
|
+
<AccordionTrigger className="px-4 py-4 border-b border-semantic-border-layout hover:no-underline sm:px-6 sm:py-5">
|
|
13462
13484
|
<span className="text-base font-semibold text-semantic-text-primary">
|
|
13463
13485
|
Advanced Settings
|
|
13464
13486
|
</span>
|
|
@@ -13466,7 +13488,7 @@ function AdvancedSettingsAccordion({
|
|
|
13466
13488
|
<AccordionContent>
|
|
13467
13489
|
<div className="flex flex-col">
|
|
13468
13490
|
{/* Number fields section */}
|
|
13469
|
-
<div className="px-
|
|
13491
|
+
<div className="px-4 pt-4 pb-4 flex flex-col gap-5 border-b border-semantic-border-layout sm:px-6 sm:pt-5 sm:pb-6">
|
|
13470
13492
|
<Field label="Silence Timeout (seconds)">
|
|
13471
13493
|
<NumberSpinner
|
|
13472
13494
|
value={data.silenceTimeout ?? 15}
|
|
@@ -13493,7 +13515,7 @@ function AdvancedSettingsAccordion({
|
|
|
13493
13515
|
</div>
|
|
13494
13516
|
|
|
13495
13517
|
{/* Interruption Handling \u2014 separated by divider */}
|
|
13496
|
-
<div className="px-
|
|
13518
|
+
<div className="px-4 py-4 flex items-center gap-3 sm:px-6 sm:py-5">
|
|
13497
13519
|
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
|
|
13498
13520
|
<span className="text-sm font-semibold text-semantic-text-primary">
|
|
13499
13521
|
Interruption Handling
|
|
@@ -13521,7 +13543,7 @@ function AdvancedSettingsAccordion({
|
|
|
13521
13543
|
const DEFAULT_DATA: IvrBotConfigData = {
|
|
13522
13544
|
botName: "",
|
|
13523
13545
|
primaryRole: "",
|
|
13524
|
-
|
|
13546
|
+
tone: [],
|
|
13525
13547
|
voice: "",
|
|
13526
13548
|
language: "",
|
|
13527
13549
|
systemPrompt: "",
|
|
@@ -13578,32 +13600,33 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13578
13600
|
return (
|
|
13579
13601
|
<div ref={ref} className={cn("flex flex-col min-h-screen bg-semantic-bg-ui", className)}>
|
|
13580
13602
|
{/* Page header */}
|
|
13581
|
-
<header className="flex
|
|
13603
|
+
<header className="flex flex-col gap-3 px-4 py-4 bg-semantic-bg-primary border-b border-semantic-border-layout shrink-0 sm:flex-row sm:items-center sm:justify-between sm:px-6 sm:py-0 sm:h-[76px]">
|
|
13582
13604
|
<div className="flex items-center gap-3">
|
|
13583
13605
|
<button
|
|
13584
13606
|
type="button"
|
|
13585
13607
|
onClick={onBack}
|
|
13586
|
-
className="p-1 rounded text-semantic-text-muted hover:text-semantic-text-primary hover:bg-semantic-bg-hover transition-colors"
|
|
13608
|
+
className="p-1 rounded text-semantic-text-muted hover:text-semantic-text-primary hover:bg-semantic-bg-hover transition-colors shrink-0"
|
|
13587
13609
|
aria-label="Go back"
|
|
13588
13610
|
>
|
|
13589
13611
|
<ChevronLeft className="size-5" />
|
|
13590
13612
|
</button>
|
|
13591
|
-
<h1 className="m-0 text-base font-semibold text-semantic-text-primary">
|
|
13613
|
+
<h1 className="m-0 text-base font-semibold text-semantic-text-primary truncate">
|
|
13592
13614
|
{botTitle}
|
|
13593
13615
|
</h1>
|
|
13594
|
-
<Badge variant="outline" className="text-xs font-normal">
|
|
13616
|
+
<Badge variant="outline" className="text-xs font-normal shrink-0">
|
|
13595
13617
|
{botType}
|
|
13596
13618
|
</Badge>
|
|
13597
13619
|
</div>
|
|
13598
13620
|
<div className="flex items-center gap-3">
|
|
13599
13621
|
{lastUpdatedAt && (
|
|
13600
|
-
<span className="text-sm text-semantic-text-muted">
|
|
13622
|
+
<span className="hidden sm:inline text-sm text-semantic-text-muted">
|
|
13601
13623
|
Last updated at: {lastUpdatedAt}
|
|
13602
13624
|
</span>
|
|
13603
13625
|
)}
|
|
13604
13626
|
<Button
|
|
13605
13627
|
variant="outline"
|
|
13606
13628
|
size="sm"
|
|
13629
|
+
className="flex-1 sm:flex-none"
|
|
13607
13630
|
onClick={() => onSaveAsDraft?.(data)}
|
|
13608
13631
|
>
|
|
13609
13632
|
Save as Draft
|
|
@@ -13611,6 +13634,7 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13611
13634
|
<Button
|
|
13612
13635
|
variant="default"
|
|
13613
13636
|
size="sm"
|
|
13637
|
+
className="flex-1 sm:flex-none"
|
|
13614
13638
|
onClick={() => onPublish?.(data)}
|
|
13615
13639
|
>
|
|
13616
13640
|
Publish Bot
|
|
@@ -13618,17 +13642,17 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13618
13642
|
</div>
|
|
13619
13643
|
</header>
|
|
13620
13644
|
|
|
13621
|
-
{/* Body \u2014 two-column
|
|
13622
|
-
<div className="flex flex-
|
|
13645
|
+
{/* Body \u2014 responsive layout: stacked on mobile, two-column on lg+ */}
|
|
13646
|
+
<div className="flex flex-col gap-6 px-4 py-4 max-w-[1220px] mx-auto w-full sm:px-6 sm:py-6 lg:flex-row lg:flex-1">
|
|
13623
13647
|
{/* Left column */}
|
|
13624
|
-
<div className="flex flex-col gap-6 flex-[3] min-w-0">
|
|
13648
|
+
<div className="flex flex-col gap-6 lg:flex-[3] min-w-0">
|
|
13625
13649
|
<WhoTheBotIs data={data} onChange={update} />
|
|
13626
13650
|
<HowItBehaves data={data} onChange={update} />
|
|
13627
13651
|
<FallbackPromptsAccordion data={data} onChange={update} />
|
|
13628
13652
|
</div>
|
|
13629
13653
|
|
|
13630
13654
|
{/* Right column */}
|
|
13631
|
-
<div className="flex flex-col gap-6 flex-[2] min-w-0">
|
|
13655
|
+
<div className="flex flex-col gap-6 lg:flex-[2] min-w-0">
|
|
13632
13656
|
<KnowledgeBase
|
|
13633
13657
|
files={data.knowledgeBaseFiles}
|
|
13634
13658
|
onSaveFiles={onSaveKnowledgeFiles}
|
|
@@ -13670,12 +13694,11 @@ IvrBotConfig.displayName = "IvrBotConfig";
|
|
|
13670
13694
|
{
|
|
13671
13695
|
name: "create-function-modal.tsx",
|
|
13672
13696
|
content: prefixTailwindClasses(`import * as React from "react";
|
|
13673
|
-
import { Trash2, ChevronDown } from "lucide-react";
|
|
13697
|
+
import { Trash2, ChevronDown, X, Plus } from "lucide-react";
|
|
13674
13698
|
import { cn } from "../../../lib/utils";
|
|
13675
13699
|
import {
|
|
13676
13700
|
Dialog,
|
|
13677
13701
|
DialogContent,
|
|
13678
|
-
DialogHeader,
|
|
13679
13702
|
DialogTitle,
|
|
13680
13703
|
} from "../dialog";
|
|
13681
13704
|
import { Button } from "../button";
|
|
@@ -13696,6 +13719,24 @@ function generateId() {
|
|
|
13696
13719
|
return Math.random().toString(36).slice(2, 9);
|
|
13697
13720
|
}
|
|
13698
13721
|
|
|
13722
|
+
// \u2500\u2500 Shared input/textarea styles \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
|
|
13723
|
+
const inputCls = cn(
|
|
13724
|
+
"w-full h-10 px-4 text-sm rounded border",
|
|
13725
|
+
"border-semantic-border-input bg-semantic-bg-primary",
|
|
13726
|
+
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
13727
|
+
"outline-none hover:border-semantic-border-input-focus",
|
|
13728
|
+
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
13729
|
+
);
|
|
13730
|
+
|
|
13731
|
+
const textareaCls = cn(
|
|
13732
|
+
"w-full px-4 py-2.5 text-sm rounded border resize-none",
|
|
13733
|
+
"border-semantic-border-input bg-semantic-bg-primary",
|
|
13734
|
+
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
13735
|
+
"outline-none hover:border-semantic-border-input-focus",
|
|
13736
|
+
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
13737
|
+
);
|
|
13738
|
+
|
|
13739
|
+
// \u2500\u2500 KeyValueTable \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
|
|
13699
13740
|
function KeyValueTable({
|
|
13700
13741
|
rows,
|
|
13701
13742
|
onChange,
|
|
@@ -13705,394 +13746,478 @@ function KeyValueTable({
|
|
|
13705
13746
|
onChange: (rows: KeyValuePair[]) => void;
|
|
13706
13747
|
label: string;
|
|
13707
13748
|
}) {
|
|
13708
|
-
const
|
|
13709
|
-
onChange(rows.map((r) => (r.id === id ? { ...r,
|
|
13749
|
+
const update = (id: string, patch: Partial<KeyValuePair>) =>
|
|
13750
|
+
onChange(rows.map((r) => (r.id === id ? { ...r, ...patch } : r)));
|
|
13710
13751
|
|
|
13711
|
-
const
|
|
13712
|
-
onChange(rows.map((r) => (r.id === id ? { ...r, value } : r)));
|
|
13752
|
+
const remove = (id: string) => onChange(rows.filter((r) => r.id !== id));
|
|
13713
13753
|
|
|
13714
|
-
const
|
|
13715
|
-
onChange(rows.filter((r) => r.id !== id));
|
|
13716
|
-
|
|
13717
|
-
const handleAdd = () =>
|
|
13754
|
+
const add = () =>
|
|
13718
13755
|
onChange([...rows, { id: generateId(), key: "", value: "" }]);
|
|
13719
13756
|
|
|
13720
13757
|
return (
|
|
13721
13758
|
<div className="flex flex-col gap-1.5">
|
|
13722
13759
|
<span className="text-xs text-semantic-text-muted">{label}</span>
|
|
13723
13760
|
<div className="border border-semantic-border-layout rounded overflow-hidden">
|
|
13724
|
-
{/*
|
|
13725
|
-
<div className="flex bg-semantic-bg-
|
|
13726
|
-
<div className="flex-1 px-3 py-2
|
|
13761
|
+
{/* Column headers \u2014 desktop only */}
|
|
13762
|
+
<div className="hidden sm:flex bg-semantic-bg-ui border-b border-semantic-border-layout">
|
|
13763
|
+
<div className="flex-1 px-3 py-2 text-xs font-semibold text-semantic-text-muted border-r border-semantic-border-layout">
|
|
13727
13764
|
Key
|
|
13728
13765
|
</div>
|
|
13729
|
-
<div className="flex-[2] px-3 py-2
|
|
13766
|
+
<div className="flex-[2] px-3 py-2 text-xs font-semibold text-semantic-text-muted">
|
|
13730
13767
|
Value
|
|
13731
13768
|
</div>
|
|
13732
|
-
<div className="w-10" />
|
|
13769
|
+
<div className="w-10 shrink-0" />
|
|
13733
13770
|
</div>
|
|
13734
|
-
|
|
13771
|
+
|
|
13772
|
+
{/* Filled rows */}
|
|
13735
13773
|
{rows.map((row) => (
|
|
13736
13774
|
<div
|
|
13737
13775
|
key={row.id}
|
|
13738
|
-
className="
|
|
13776
|
+
className="border-b border-semantic-border-layout last:border-b-0"
|
|
13739
13777
|
>
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13778
|
+
{/* Mobile: label + input pairs stacked */}
|
|
13779
|
+
<div className="flex sm:hidden flex-col">
|
|
13780
|
+
<div className="flex flex-col px-3 pt-2.5 pb-1 gap-0.5">
|
|
13781
|
+
<span className="text-[10px] font-semibold text-semantic-text-muted uppercase tracking-wide">
|
|
13782
|
+
Key
|
|
13783
|
+
</span>
|
|
13784
|
+
<input
|
|
13785
|
+
type="text"
|
|
13786
|
+
value={row.key}
|
|
13787
|
+
onChange={(e) => update(row.id, { key: e.target.value })}
|
|
13788
|
+
placeholder="Key"
|
|
13789
|
+
className="w-full text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none"
|
|
13790
|
+
/>
|
|
13791
|
+
</div>
|
|
13792
|
+
<div className="h-px bg-semantic-border-layout mx-3" />
|
|
13793
|
+
<div className="flex items-start gap-2 px-3 py-2.5">
|
|
13794
|
+
<div className="flex flex-col flex-1 gap-0.5">
|
|
13795
|
+
<span className="text-[10px] font-semibold text-semantic-text-muted uppercase tracking-wide">
|
|
13796
|
+
Value
|
|
13797
|
+
</span>
|
|
13798
|
+
<input
|
|
13799
|
+
type="text"
|
|
13800
|
+
value={row.value}
|
|
13801
|
+
onChange={(e) => update(row.id, { value: e.target.value })}
|
|
13802
|
+
placeholder="Type {{ to add variables"
|
|
13803
|
+
className="w-full text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none"
|
|
13804
|
+
/>
|
|
13805
|
+
</div>
|
|
13806
|
+
<button
|
|
13807
|
+
type="button"
|
|
13808
|
+
onClick={() => remove(row.id)}
|
|
13809
|
+
className="mt-4 size-8 flex items-center justify-center text-semantic-text-muted hover:text-semantic-error-primary hover:bg-semantic-error-surface rounded transition-colors shrink-0"
|
|
13810
|
+
aria-label="Delete row"
|
|
13811
|
+
>
|
|
13812
|
+
<Trash2 className="size-3.5" />
|
|
13813
|
+
</button>
|
|
13814
|
+
</div>
|
|
13815
|
+
</div>
|
|
13816
|
+
|
|
13817
|
+
{/* Desktop: side-by-side */}
|
|
13818
|
+
<div className="hidden sm:flex">
|
|
13819
|
+
<input
|
|
13820
|
+
type="text"
|
|
13821
|
+
value={row.key}
|
|
13822
|
+
onChange={(e) => update(row.id, { key: e.target.value })}
|
|
13823
|
+
placeholder="Key"
|
|
13824
|
+
className="flex-1 px-3 py-2.5 text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-semantic-bg-primary border-r border-semantic-border-layout outline-none focus:bg-semantic-bg-hover"
|
|
13825
|
+
/>
|
|
13826
|
+
<input
|
|
13827
|
+
type="text"
|
|
13828
|
+
value={row.value}
|
|
13829
|
+
onChange={(e) => update(row.id, { value: e.target.value })}
|
|
13830
|
+
placeholder="Type {{ to add variables"
|
|
13831
|
+
className="flex-[2] px-3 py-2.5 text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-semantic-bg-primary outline-none focus:bg-semantic-bg-hover"
|
|
13832
|
+
/>
|
|
13833
|
+
<button
|
|
13834
|
+
type="button"
|
|
13835
|
+
onClick={() => remove(row.id)}
|
|
13836
|
+
className="w-10 flex items-center justify-center text-semantic-text-muted hover:text-semantic-error-primary hover:bg-semantic-error-surface transition-colors shrink-0"
|
|
13837
|
+
aria-label="Delete row"
|
|
13838
|
+
>
|
|
13839
|
+
<Trash2 className="size-3.5" />
|
|
13840
|
+
</button>
|
|
13841
|
+
</div>
|
|
13762
13842
|
</div>
|
|
13763
13843
|
))}
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
type="text"
|
|
13775
|
-
placeholder="Type {{ to add variables"
|
|
13776
|
-
readOnly
|
|
13777
|
-
onClick={handleAdd}
|
|
13778
|
-
className="flex-[2] px-3 py-2.5 text-sm placeholder:text-semantic-text-muted bg-semantic-bg-primary outline-none cursor-pointer"
|
|
13779
|
-
/>
|
|
13780
|
-
<div className="w-10" />
|
|
13781
|
-
</div>
|
|
13844
|
+
|
|
13845
|
+
{/* Add row \u2014 always visible */}
|
|
13846
|
+
<button
|
|
13847
|
+
type="button"
|
|
13848
|
+
onClick={add}
|
|
13849
|
+
className="w-full flex items-center gap-2 px-3 py-2.5 text-sm text-semantic-text-muted hover:bg-semantic-bg-hover transition-colors"
|
|
13850
|
+
>
|
|
13851
|
+
<Plus className="size-3.5 shrink-0" />
|
|
13852
|
+
<span>Add row</span>
|
|
13853
|
+
</button>
|
|
13782
13854
|
</div>
|
|
13783
13855
|
</div>
|
|
13784
13856
|
);
|
|
13785
13857
|
}
|
|
13786
13858
|
|
|
13859
|
+
// \u2500\u2500 Modal \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\u2500\u2500
|
|
13787
13860
|
export const CreateFunctionModal = React.forwardRef<
|
|
13788
13861
|
HTMLDivElement,
|
|
13789
13862
|
CreateFunctionModalProps
|
|
13790
|
-
>(
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13863
|
+
>(
|
|
13864
|
+
(
|
|
13865
|
+
{
|
|
13866
|
+
open,
|
|
13867
|
+
onOpenChange,
|
|
13868
|
+
onSubmit,
|
|
13869
|
+
onTestApi,
|
|
13870
|
+
initialStep = 1,
|
|
13871
|
+
initialTab = "header",
|
|
13872
|
+
className,
|
|
13873
|
+
},
|
|
13874
|
+
ref
|
|
13875
|
+
) => {
|
|
13876
|
+
const [step, setStep] = React.useState<1 | 2>(initialStep);
|
|
13877
|
+
|
|
13878
|
+
const [name, setName] = React.useState("");
|
|
13879
|
+
const [prompt, setPrompt] = React.useState("");
|
|
13880
|
+
|
|
13881
|
+
const [method, setMethod] = React.useState<HttpMethod>("GET");
|
|
13882
|
+
const [url, setUrl] = React.useState("");
|
|
13883
|
+
const [activeTab, setActiveTab] =
|
|
13884
|
+
React.useState<FunctionTabType>(initialTab);
|
|
13885
|
+
const [headers, setHeaders] = React.useState<KeyValuePair[]>([]);
|
|
13886
|
+
const [queryParams, setQueryParams] = React.useState<KeyValuePair[]>([]);
|
|
13887
|
+
const [body, setBody] = React.useState("");
|
|
13888
|
+
const [apiResponse, setApiResponse] = React.useState("");
|
|
13889
|
+
const [isTesting, setIsTesting] = React.useState(false);
|
|
13890
|
+
|
|
13891
|
+
const reset = React.useCallback(() => {
|
|
13892
|
+
setStep(initialStep);
|
|
13893
|
+
setName("");
|
|
13894
|
+
setPrompt("");
|
|
13895
|
+
setMethod("GET");
|
|
13896
|
+
setUrl("");
|
|
13897
|
+
setActiveTab(initialTab);
|
|
13898
|
+
setHeaders([]);
|
|
13899
|
+
setQueryParams([]);
|
|
13900
|
+
setBody("");
|
|
13901
|
+
setApiResponse("");
|
|
13902
|
+
}, [initialStep, initialTab]);
|
|
13828
13903
|
|
|
13829
|
-
|
|
13904
|
+
const handleClose = React.useCallback(() => {
|
|
13905
|
+
reset();
|
|
13906
|
+
onOpenChange(false);
|
|
13907
|
+
}, [reset, onOpenChange]);
|
|
13830
13908
|
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
name: name.trim(),
|
|
13834
|
-
prompt: prompt.trim(),
|
|
13835
|
-
method,
|
|
13836
|
-
url: url.trim(),
|
|
13837
|
-
headers,
|
|
13838
|
-
queryParams,
|
|
13839
|
-
body,
|
|
13909
|
+
const handleNext = () => {
|
|
13910
|
+
if (name.trim() && prompt.trim()) setStep(2);
|
|
13840
13911
|
};
|
|
13841
|
-
onSubmit?.(data);
|
|
13842
|
-
handleClose();
|
|
13843
|
-
};
|
|
13844
|
-
|
|
13845
|
-
const handleTestApi = async () => {
|
|
13846
|
-
if (!onTestApi) return;
|
|
13847
|
-
setIsTesting(true);
|
|
13848
|
-
try {
|
|
13849
|
-
const step2: CreateFunctionStep2Data = { method, url, headers, queryParams, body };
|
|
13850
|
-
const response = await onTestApi(step2);
|
|
13851
|
-
setApiResponse(response);
|
|
13852
|
-
} finally {
|
|
13853
|
-
setIsTesting(false);
|
|
13854
|
-
}
|
|
13855
|
-
};
|
|
13856
13912
|
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13913
|
+
const handleSubmit = () => {
|
|
13914
|
+
const data: CreateFunctionData = {
|
|
13915
|
+
name: name.trim(),
|
|
13916
|
+
prompt: prompt.trim(),
|
|
13917
|
+
method,
|
|
13918
|
+
url: url.trim(),
|
|
13919
|
+
headers,
|
|
13920
|
+
queryParams,
|
|
13921
|
+
body,
|
|
13922
|
+
};
|
|
13923
|
+
onSubmit?.(data);
|
|
13924
|
+
handleClose();
|
|
13925
|
+
};
|
|
13863
13926
|
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13927
|
+
const handleTestApi = async () => {
|
|
13928
|
+
if (!onTestApi) return;
|
|
13929
|
+
setIsTesting(true);
|
|
13930
|
+
try {
|
|
13931
|
+
const step2: CreateFunctionStep2Data = {
|
|
13932
|
+
method,
|
|
13933
|
+
url,
|
|
13934
|
+
headers,
|
|
13935
|
+
queryParams,
|
|
13936
|
+
body,
|
|
13937
|
+
};
|
|
13938
|
+
const response = await onTestApi(step2);
|
|
13939
|
+
setApiResponse(response);
|
|
13940
|
+
} finally {
|
|
13941
|
+
setIsTesting(false);
|
|
13942
|
+
}
|
|
13943
|
+
};
|
|
13870
13944
|
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
{/* Function Name */}
|
|
13874
|
-
<div className="flex flex-col gap-1">
|
|
13875
|
-
<label
|
|
13876
|
-
htmlFor="fn-name"
|
|
13877
|
-
className="text-sm font-semibold text-semantic-text-primary"
|
|
13878
|
-
>
|
|
13879
|
-
Functions Name{" "}
|
|
13880
|
-
<span className="text-semantic-error-primary font-semibold">*</span>
|
|
13881
|
-
</label>
|
|
13882
|
-
<div className="relative">
|
|
13883
|
-
<input
|
|
13884
|
-
id="fn-name"
|
|
13885
|
-
type="text"
|
|
13886
|
-
value={name}
|
|
13887
|
-
maxLength={FUNCTION_NAME_MAX}
|
|
13888
|
-
onChange={(e) => setName(e.target.value)}
|
|
13889
|
-
placeholder="Enter Name of the function"
|
|
13890
|
-
className={cn(
|
|
13891
|
-
"w-full h-10 px-4 py-2.5 pr-16 text-sm rounded border",
|
|
13892
|
-
"border-semantic-border-input bg-semantic-bg-primary",
|
|
13893
|
-
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
13894
|
-
"outline-none hover:border-semantic-border-input-focus",
|
|
13895
|
-
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
13896
|
-
)}
|
|
13897
|
-
/>
|
|
13898
|
-
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs italic text-semantic-text-muted">
|
|
13899
|
-
{name.length}/{FUNCTION_NAME_MAX}
|
|
13900
|
-
</span>
|
|
13901
|
-
</div>
|
|
13902
|
-
</div>
|
|
13945
|
+
const isStep1Valid =
|
|
13946
|
+
name.trim().length > 0 && prompt.trim().length > 0;
|
|
13903
13947
|
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
>
|
|
13910
|
-
Prompt{" "}
|
|
13911
|
-
<span className="text-semantic-error-primary font-semibold">*</span>
|
|
13912
|
-
</label>
|
|
13913
|
-
<textarea
|
|
13914
|
-
id="fn-prompt"
|
|
13915
|
-
value={prompt}
|
|
13916
|
-
onChange={(e) => setPrompt(e.target.value)}
|
|
13917
|
-
placeholder="Enter the Description of the functions"
|
|
13918
|
-
rows={5}
|
|
13919
|
-
className={cn(
|
|
13920
|
-
"w-full px-4 py-2.5 text-sm rounded border",
|
|
13921
|
-
"border-semantic-border-input bg-semantic-bg-primary resize-none",
|
|
13922
|
-
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
13923
|
-
"outline-none hover:border-semantic-border-input-focus",
|
|
13924
|
-
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
13925
|
-
)}
|
|
13926
|
-
/>
|
|
13927
|
-
</div>
|
|
13948
|
+
const tabLabels: Record<FunctionTabType, string> = {
|
|
13949
|
+
header: \`Header (\${headers.length})\`,
|
|
13950
|
+
queryParams: \`Query params (\${queryParams.length})\`,
|
|
13951
|
+
body: "Body",
|
|
13952
|
+
};
|
|
13928
13953
|
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13954
|
+
return (
|
|
13955
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
13956
|
+
<DialogContent
|
|
13957
|
+
ref={ref}
|
|
13958
|
+
size="lg"
|
|
13959
|
+
hideCloseButton
|
|
13960
|
+
className={cn(
|
|
13961
|
+
"mx-4 sm:mx-auto flex flex-col gap-0 p-0",
|
|
13962
|
+
"max-h-[calc(100svh-2rem)] overflow-hidden",
|
|
13963
|
+
className
|
|
13964
|
+
)}
|
|
13965
|
+
>
|
|
13966
|
+
{/* \u2500\u2500 Header \u2500\u2500 */}
|
|
13967
|
+
<div className="flex items-center justify-between px-4 py-4 border-b border-semantic-border-layout shrink-0 sm:px-6">
|
|
13968
|
+
<DialogTitle className="text-base font-semibold text-semantic-text-primary">
|
|
13969
|
+
Create Function
|
|
13970
|
+
</DialogTitle>
|
|
13971
|
+
<button
|
|
13972
|
+
type="button"
|
|
13973
|
+
onClick={handleClose}
|
|
13974
|
+
className="rounded p-1.5 text-semantic-text-muted hover:text-semantic-text-primary hover:bg-semantic-bg-hover transition-colors"
|
|
13975
|
+
aria-label="Close"
|
|
13976
|
+
>
|
|
13977
|
+
<X className="size-4" />
|
|
13978
|
+
</button>
|
|
13938
13979
|
</div>
|
|
13939
|
-
)}
|
|
13940
13980
|
|
|
13941
|
-
|
|
13942
|
-
<div className="flex
|
|
13943
|
-
{/*
|
|
13944
|
-
|
|
13945
|
-
<
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
value={method}
|
|
13951
|
-
onChange={(e) => setMethod(e.target.value as HttpMethod)}
|
|
13952
|
-
className="h-full pl-4 pr-9 text-sm text-semantic-text-primary bg-semantic-bg-primary border-r border-semantic-border-input outline-none cursor-pointer appearance-none w-[104px]"
|
|
13953
|
-
aria-label="HTTP method"
|
|
13981
|
+
{/* \u2500\u2500 Scrollable body \u2500\u2500 */}
|
|
13982
|
+
<div className="flex-1 overflow-y-auto min-h-0 px-4 py-5 sm:px-6">
|
|
13983
|
+
{/* \u2500 Step 1 \u2500 */}
|
|
13984
|
+
{step === 1 && (
|
|
13985
|
+
<div className="flex flex-col gap-5">
|
|
13986
|
+
<div className="flex flex-col gap-1.5">
|
|
13987
|
+
<label
|
|
13988
|
+
htmlFor="fn-name"
|
|
13989
|
+
className="text-sm font-semibold text-semantic-text-primary"
|
|
13954
13990
|
>
|
|
13955
|
-
{
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13991
|
+
Function Name{" "}
|
|
13992
|
+
<span className="text-semantic-error-primary">*</span>
|
|
13993
|
+
</label>
|
|
13994
|
+
<div className="relative">
|
|
13995
|
+
<input
|
|
13996
|
+
id="fn-name"
|
|
13997
|
+
type="text"
|
|
13998
|
+
value={name}
|
|
13999
|
+
maxLength={FUNCTION_NAME_MAX}
|
|
14000
|
+
onChange={(e) => setName(e.target.value)}
|
|
14001
|
+
placeholder="Enter name of the function"
|
|
14002
|
+
className={cn(inputCls, "pr-16")}
|
|
14003
|
+
/>
|
|
14004
|
+
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs italic text-semantic-text-muted pointer-events-none">
|
|
14005
|
+
{name.length}/{FUNCTION_NAME_MAX}
|
|
14006
|
+
</span>
|
|
14007
|
+
</div>
|
|
14008
|
+
</div>
|
|
14009
|
+
|
|
14010
|
+
<div className="flex flex-col gap-1.5">
|
|
14011
|
+
<label
|
|
14012
|
+
htmlFor="fn-prompt"
|
|
14013
|
+
className="text-sm font-semibold text-semantic-text-primary"
|
|
14014
|
+
>
|
|
14015
|
+
Prompt{" "}
|
|
14016
|
+
<span className="text-semantic-error-primary">*</span>
|
|
14017
|
+
</label>
|
|
14018
|
+
<textarea
|
|
14019
|
+
id="fn-prompt"
|
|
14020
|
+
value={prompt}
|
|
14021
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
14022
|
+
placeholder="Enter the description of the function"
|
|
14023
|
+
rows={5}
|
|
14024
|
+
className={textareaCls}
|
|
13964
14025
|
/>
|
|
13965
14026
|
</div>
|
|
13966
|
-
<input
|
|
13967
|
-
type="text"
|
|
13968
|
-
value={url}
|
|
13969
|
-
onChange={(e) => setUrl(e.target.value)}
|
|
13970
|
-
placeholder="Enter URL or Type {{ to add variables"
|
|
13971
|
-
className="flex-1 px-4 text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-semantic-bg-primary outline-none"
|
|
13972
|
-
/>
|
|
13973
14027
|
</div>
|
|
13974
|
-
|
|
14028
|
+
)}
|
|
13975
14029
|
|
|
13976
|
-
{/*
|
|
13977
|
-
|
|
13978
|
-
<div className="flex
|
|
13979
|
-
{
|
|
13980
|
-
|
|
13981
|
-
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
14030
|
+
{/* \u2500 Step 2 \u2500 */}
|
|
14031
|
+
{step === 2 && (
|
|
14032
|
+
<div className="flex flex-col gap-5">
|
|
14033
|
+
{/* API URL \u2014 always a single combined row */}
|
|
14034
|
+
<div className="flex flex-col gap-1.5">
|
|
14035
|
+
<span className="text-xs text-semantic-text-muted tracking-[0.048px]">
|
|
14036
|
+
API URL
|
|
14037
|
+
</span>
|
|
14038
|
+
<div
|
|
14039
|
+
className={cn(
|
|
14040
|
+
"flex h-10 rounded border border-semantic-border-input overflow-hidden bg-semantic-bg-primary",
|
|
14041
|
+
"hover:border-semantic-border-input-focus",
|
|
14042
|
+
"focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
14043
|
+
"transition-shadow"
|
|
14044
|
+
)}
|
|
14045
|
+
>
|
|
14046
|
+
{/* Method selector */}
|
|
14047
|
+
<div className="relative shrink-0 border-r border-semantic-border-layout">
|
|
14048
|
+
<select
|
|
14049
|
+
value={method}
|
|
14050
|
+
onChange={(e) =>
|
|
14051
|
+
setMethod(e.target.value as HttpMethod)
|
|
14052
|
+
}
|
|
14053
|
+
className="h-full w-[80px] pl-3 pr-7 text-sm text-semantic-text-primary bg-transparent outline-none cursor-pointer appearance-none sm:w-[100px]"
|
|
14054
|
+
aria-label="HTTP method"
|
|
14055
|
+
>
|
|
14056
|
+
{HTTP_METHODS.map((m) => (
|
|
14057
|
+
<option key={m} value={m}>
|
|
14058
|
+
{m}
|
|
14059
|
+
</option>
|
|
14060
|
+
))}
|
|
14061
|
+
</select>
|
|
14062
|
+
<ChevronDown
|
|
14063
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 size-3 text-semantic-text-muted pointer-events-none"
|
|
14064
|
+
aria-hidden="true"
|
|
14065
|
+
/>
|
|
14066
|
+
</div>
|
|
14067
|
+
{/* URL input */}
|
|
14068
|
+
<input
|
|
14069
|
+
type="text"
|
|
14070
|
+
value={url}
|
|
14071
|
+
onChange={(e) => setUrl(e.target.value)}
|
|
14072
|
+
placeholder="Enter URL or Type {{ to add variables"
|
|
14073
|
+
className="flex-1 min-w-0 px-3 text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none"
|
|
14074
|
+
/>
|
|
14075
|
+
</div>
|
|
14076
|
+
</div>
|
|
14077
|
+
|
|
14078
|
+
{/* Tabs \u2014 scrollable, no visible scrollbar */}
|
|
14079
|
+
<div className="flex flex-col gap-4">
|
|
14080
|
+
<div
|
|
14081
|
+
className={cn(
|
|
14082
|
+
"flex border-b border-semantic-border-layout",
|
|
14083
|
+
"overflow-x-auto",
|
|
14084
|
+
"[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]"
|
|
14085
|
+
)}
|
|
14086
|
+
>
|
|
14087
|
+
{(
|
|
14088
|
+
["header", "queryParams", "body"] as FunctionTabType[]
|
|
14089
|
+
).map((tab) => (
|
|
13987
14090
|
<button
|
|
13988
14091
|
key={tab}
|
|
13989
14092
|
type="button"
|
|
13990
14093
|
onClick={() => setActiveTab(tab)}
|
|
13991
14094
|
className={cn(
|
|
13992
|
-
"px-
|
|
14095
|
+
"px-3 py-2 text-sm font-semibold transition-colors whitespace-nowrap shrink-0",
|
|
13993
14096
|
activeTab === tab
|
|
13994
14097
|
? "text-semantic-text-secondary border-b-2 border-semantic-text-secondary -mb-px"
|
|
13995
|
-
: "text-semantic-text-muted"
|
|
14098
|
+
: "text-semantic-text-muted hover:text-semantic-text-primary"
|
|
13996
14099
|
)}
|
|
13997
14100
|
>
|
|
13998
|
-
{
|
|
14101
|
+
{tabLabels[tab]}
|
|
13999
14102
|
</button>
|
|
14000
|
-
)
|
|
14001
|
-
|
|
14002
|
-
)}
|
|
14003
|
-
</div>
|
|
14103
|
+
))}
|
|
14104
|
+
</div>
|
|
14004
14105
|
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
label="Query parameter"
|
|
14018
|
-
/>
|
|
14019
|
-
)}
|
|
14020
|
-
{activeTab === "body" && (
|
|
14021
|
-
<div className="flex flex-col gap-1">
|
|
14022
|
-
<span className="text-xs text-semantic-text-muted">Body</span>
|
|
14023
|
-
<div className="relative">
|
|
14024
|
-
<textarea
|
|
14025
|
-
value={body}
|
|
14026
|
-
maxLength={BODY_MAX}
|
|
14027
|
-
onChange={(e) => setBody(e.target.value)}
|
|
14028
|
-
placeholder="Enter request body (JSON, XML etc). Type {{ to add variables"
|
|
14029
|
-
rows={6}
|
|
14030
|
-
className={cn(
|
|
14031
|
-
"w-full px-4 py-2.5 pb-7 text-sm rounded border resize-none",
|
|
14032
|
-
"border-semantic-border-input bg-semantic-bg-primary",
|
|
14033
|
-
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
14034
|
-
"outline-none hover:border-semantic-border-input-focus",
|
|
14035
|
-
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
14036
|
-
)}
|
|
14106
|
+
{activeTab === "header" && (
|
|
14107
|
+
<KeyValueTable
|
|
14108
|
+
rows={headers}
|
|
14109
|
+
onChange={setHeaders}
|
|
14110
|
+
label="Header"
|
|
14111
|
+
/>
|
|
14112
|
+
)}
|
|
14113
|
+
{activeTab === "queryParams" && (
|
|
14114
|
+
<KeyValueTable
|
|
14115
|
+
rows={queryParams}
|
|
14116
|
+
onChange={setQueryParams}
|
|
14117
|
+
label="Query parameter"
|
|
14037
14118
|
/>
|
|
14038
|
-
|
|
14039
|
-
|
|
14119
|
+
)}
|
|
14120
|
+
{activeTab === "body" && (
|
|
14121
|
+
<div className="flex flex-col gap-1.5">
|
|
14122
|
+
<span className="text-xs text-semantic-text-muted">
|
|
14123
|
+
Body
|
|
14124
|
+
</span>
|
|
14125
|
+
<div className="relative">
|
|
14126
|
+
<textarea
|
|
14127
|
+
value={body}
|
|
14128
|
+
maxLength={BODY_MAX}
|
|
14129
|
+
onChange={(e) => setBody(e.target.value)}
|
|
14130
|
+
placeholder="Enter request body (JSON, XML etc). Type {{ to add variables"
|
|
14131
|
+
rows={6}
|
|
14132
|
+
className={cn(textareaCls, "pb-7")}
|
|
14133
|
+
/>
|
|
14134
|
+
<span className="absolute bottom-2 right-3 text-xs italic text-semantic-text-muted pointer-events-none">
|
|
14135
|
+
{body.length}/{BODY_MAX}
|
|
14136
|
+
</span>
|
|
14137
|
+
</div>
|
|
14138
|
+
</div>
|
|
14139
|
+
)}
|
|
14140
|
+
</div>
|
|
14141
|
+
|
|
14142
|
+
{/* Test Your API */}
|
|
14143
|
+
<div className="flex flex-col gap-4">
|
|
14144
|
+
<div className="flex flex-col gap-1.5">
|
|
14145
|
+
<span className="text-xs font-semibold text-semantic-text-muted tracking-[0.048px]">
|
|
14146
|
+
Test Your API
|
|
14040
14147
|
</span>
|
|
14148
|
+
<div className="border-t border-semantic-border-layout" />
|
|
14041
14149
|
</div>
|
|
14042
|
-
</div>
|
|
14043
|
-
)}
|
|
14044
|
-
</div>
|
|
14045
14150
|
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
<span className="text-xs text-semantic-text-muted">
|
|
14069
|
-
Response from API
|
|
14070
|
-
</span>
|
|
14071
|
-
<textarea
|
|
14072
|
-
readOnly
|
|
14073
|
-
value={apiResponse}
|
|
14074
|
-
rows={4}
|
|
14075
|
-
className="w-full px-3 py-2.5 text-sm rounded border border-semantic-border-layout bg-semantic-bg-ui text-semantic-text-primary resize-none outline-none"
|
|
14076
|
-
placeholder=""
|
|
14077
|
-
/>
|
|
14151
|
+
<button
|
|
14152
|
+
type="button"
|
|
14153
|
+
onClick={handleTestApi}
|
|
14154
|
+
disabled={isTesting || !url.trim()}
|
|
14155
|
+
className="w-full h-10 rounded text-sm font-semibold text-semantic-text-secondary bg-semantic-primary-surface disabled:opacity-50 disabled:cursor-not-allowed transition-colors hover:bg-semantic-primary-surface/80 sm:w-auto sm:px-6 sm:self-end sm:ml-auto flex items-center justify-center"
|
|
14156
|
+
>
|
|
14157
|
+
{isTesting ? "Testing..." : "Test API"}
|
|
14158
|
+
</button>
|
|
14159
|
+
|
|
14160
|
+
<div className="flex flex-col gap-1.5">
|
|
14161
|
+
<span className="text-xs text-semantic-text-muted">
|
|
14162
|
+
Response from API
|
|
14163
|
+
</span>
|
|
14164
|
+
<textarea
|
|
14165
|
+
readOnly
|
|
14166
|
+
value={apiResponse}
|
|
14167
|
+
rows={4}
|
|
14168
|
+
className="w-full px-3 py-2.5 text-sm rounded border border-semantic-border-layout bg-semantic-bg-ui text-semantic-text-primary resize-none outline-none"
|
|
14169
|
+
placeholder=""
|
|
14170
|
+
/>
|
|
14171
|
+
</div>
|
|
14172
|
+
</div>
|
|
14078
14173
|
</div>
|
|
14079
|
-
|
|
14174
|
+
)}
|
|
14175
|
+
</div>
|
|
14080
14176
|
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14177
|
+
{/* \u2500\u2500 Footer \u2500\u2500 */}
|
|
14178
|
+
<div className="flex items-center justify-between gap-3 px-4 py-3 border-t border-semantic-border-layout shrink-0 sm:px-6 sm:py-4">
|
|
14179
|
+
{step === 1 ? (
|
|
14180
|
+
<>
|
|
14181
|
+
<Button
|
|
14182
|
+
variant="outline"
|
|
14183
|
+
className="flex-1 sm:flex-none"
|
|
14184
|
+
onClick={handleClose}
|
|
14185
|
+
>
|
|
14186
|
+
Cancel
|
|
14187
|
+
</Button>
|
|
14188
|
+
<Button
|
|
14189
|
+
variant="default"
|
|
14190
|
+
className="flex-1 sm:flex-none"
|
|
14191
|
+
onClick={handleNext}
|
|
14192
|
+
disabled={!isStep1Valid}
|
|
14193
|
+
>
|
|
14194
|
+
Next
|
|
14195
|
+
</Button>
|
|
14196
|
+
</>
|
|
14197
|
+
) : (
|
|
14198
|
+
<>
|
|
14199
|
+
<Button
|
|
14200
|
+
variant="outline"
|
|
14201
|
+
className="flex-1 sm:flex-none"
|
|
14202
|
+
onClick={() => setStep(1)}
|
|
14203
|
+
>
|
|
14204
|
+
Back
|
|
14205
|
+
</Button>
|
|
14206
|
+
<Button
|
|
14207
|
+
variant="default"
|
|
14208
|
+
className="flex-1 sm:flex-none"
|
|
14209
|
+
onClick={handleSubmit}
|
|
14210
|
+
>
|
|
14211
|
+
Submit
|
|
14212
|
+
</Button>
|
|
14213
|
+
</>
|
|
14214
|
+
)}
|
|
14090
14215
|
</div>
|
|
14091
|
-
|
|
14092
|
-
</
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14216
|
+
</DialogContent>
|
|
14217
|
+
</Dialog>
|
|
14218
|
+
);
|
|
14219
|
+
}
|
|
14220
|
+
);
|
|
14096
14221
|
|
|
14097
14222
|
CreateFunctionModal.displayName = "CreateFunctionModal";
|
|
14098
14223
|
`, prefix)
|
|
@@ -14155,7 +14280,7 @@ export interface CreateFunctionModalProps {
|
|
|
14155
14280
|
export interface IvrBotConfigData {
|
|
14156
14281
|
botName: string;
|
|
14157
14282
|
primaryRole: string;
|
|
14158
|
-
|
|
14283
|
+
tone: string[];
|
|
14159
14284
|
voice: string;
|
|
14160
14285
|
language: string;
|
|
14161
14286
|
systemPrompt: string;
|