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.
Files changed (2) hide show
  1. package/dist/index.js +679 -554
  2. 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 = SelectPrimitive.Value;
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-col gap-2.5 p-3 rounded-lg border text-left flex-1 h-[134px] justify-center",
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 justify-end gap-4 mt-2">
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 items-center justify-between pb-5 mb-6 border-b border-semantic-border-layout">
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-3 gap-6">
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-6 py-4 border-b border-semantic-border-layout">
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 Tone Multi-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
12567
- const TONE_OPTIONS = [
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 ToneMultiSelect({
12582
- values,
12612
+ function ToneInput({
12613
+ value,
12583
12614
  onChange,
12584
12615
  }: {
12585
- values: string[];
12586
- onChange: (values: string[]) => void;
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
- const filteredOptions = TONE_OPTIONS.filter(
12594
- (opt) =>
12595
- !values.includes(opt) &&
12596
- opt.toLowerCase().includes(inputValue.toLowerCase())
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 && !values.includes(trimmed)) {
12602
- onChange([...values, trimmed]);
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(values.filter((t) => t !== tone));
12647
+ onChange(value.filter((t) => t !== tone));
12610
12648
  };
12611
12649
 
12612
12650
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
12613
- if (e.key === "Enter" && inputValue.trim()) {
12651
+ if (e.key === "Enter") {
12614
12652
  e.preventDefault();
12615
- addTone(inputValue);
12616
- } else if (e.key === "Backspace" && !inputValue && values.length > 0) {
12617
- onChange(values.slice(0, -1));
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
- setOpen(false);
12657
+ setIsOpen(false);
12658
+ setInputValue("");
12620
12659
  }
12621
12660
  };
12622
12661
 
12623
- React.useEffect(() => {
12624
- const handleClickOutside = (e: MouseEvent) => {
12625
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
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} className="relative w-full">
12637
- {/* Trigger \u2014 same border/bg/focus as StyledInput */}
12667
+ <div className="relative" ref={containerRef}>
12668
+ {/* Trigger */}
12638
12669
  <div
12639
12670
  className={cn(
12640
- "min-h-10 w-full pl-4 pr-3 py-1.5 text-sm rounded border flex flex-wrap items-center gap-1.5 cursor-text",
12641
- "border-semantic-border-input bg-semantic-bg-primary",
12642
- "outline-none hover:border-semantic-border-input-focus",
12643
- open && "border-semantic-border-input-focus shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
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={() => { setOpen(true); inputRef.current?.focus(); }}
12676
+ onClick={() => {
12677
+ setIsOpen(true);
12678
+ inputRef.current?.focus();
12679
+ }}
12646
12680
  >
12647
- {/* Selected chips \u2014 info-surface blue tint as per Figma */}
12648
- {values.map((tone) => (
12681
+ {/* Selected chips */}
12682
+ {value.map((tone) => (
12649
12683
  <span
12650
12684
  key={tone}
12651
- className="inline-flex items-center gap-1.5 px-2 py-1 rounded bg-semantic-info-surface text-xs text-semantic-text-primary shrink-0 max-w-[220px]"
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
- <span className="truncate">{tone}</span>
12687
+ {tone}
12654
12688
  <button
12655
12689
  type="button"
12656
- onClick={(e) => { e.stopPropagation(); removeTone(tone); }}
12657
- className="shrink-0 text-semantic-text-muted hover:text-semantic-text-primary transition-colors"
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-3" />
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) => { setInputValue(e.target.value); setOpen(true); }}
12671
- onFocus={() => setOpen(true)}
12708
+ onChange={(e) => {
12709
+ setInputValue(e.target.value);
12710
+ if (!isOpen) setIsOpen(true);
12711
+ }}
12712
+ onFocus={() => setIsOpen(true)}
12672
12713
  onKeyDown={handleKeyDown}
12673
- placeholder={values.length === 0 ? "Enter or select tone" : ""}
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: right (\u2192) when has content, down (\u2193) when empty */}
12678
- <button
12679
- type="button"
12680
- onClick={(e) => { e.stopPropagation(); setOpen((o) => !o); }}
12681
- className="shrink-0 ml-1 text-semantic-text-muted hover:text-semantic-text-primary transition-colors"
12682
- aria-label="Toggle tone options"
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
- {/* "Press Enter to add..." hint shown below trigger */}
12693
- {inputValue.trim() && (
12694
- <div className="flex items-center gap-1.5 mt-1">
12695
- <Info className="size-3.5 shrink-0 text-semantic-text-muted" />
12696
- <p className="m-0 text-xs text-semantic-text-muted">
12697
- Press Enter to add &ldquo;{inputValue.trim()}&rdquo; \u21B5
12698
- </p>
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 &ldquo;{inputValue}&rdquo; \u21B5
12758
+ </span>
12759
+ </div>
12760
+ )}
12699
12761
  </div>
12700
12762
  )}
12701
12763
 
12702
- {/* Dropdown \u2014 options as wrapping chips with + icon, exactly as Figma */}
12703
- {open && filteredOptions.length > 0 && (
12704
- <div className="absolute z-50 w-full mt-1 rounded border border-semantic-border-layout bg-semantic-bg-primary shadow-sm overflow-hidden">
12705
- <div className="flex flex-wrap gap-1.5 p-2.5">
12706
- {filteredOptions.map((opt) => (
12707
- <button
12708
- key={opt}
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 &ldquo;Conversational&rdquo; \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
- <StyledInput
12798
- placeholder="e.g., Customer Support Agent"
12799
- value={data.primaryRole}
12800
- onChange={(v) => onChange({ primaryRole: v })}
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
- <ToneMultiSelect
12806
- values={data.tones ?? []}
12807
- onChange={(v) => onChange({ tones: v })}
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-2 gap-4">
12824
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
12812
12825
  <Field label="How It Sounds">
12813
- <StyledSelect
12814
- placeholder="Rhea - Female"
12815
- value={data.voice}
12816
- onChange={(v) => onChange({ voice: v })}
12817
- options={[
12818
- { value: "rhea-female", label: "Rhea - Female" },
12819
- { value: "james-male", label: "James - Male" },
12820
- { value: "aria-female", label: "Aria - Female" },
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
- <StyledSelect
12826
- placeholder="Select Language Mode"
12827
- value={data.language}
12828
- onChange={(v) => onChange({ language: v })}
12829
- options={[
12830
- { value: "en-in", label: "English (India)" },
12831
- { value: "en-us", label: "English (US)" },
12832
- { value: "hi-in", label: "Hindi" },
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-6 py-5 border-b border-semantic-border-layout hover:no-underline">
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-6 pt-6 pb-2 flex flex-col gap-6">
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-6 gap-0"
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 items-center justify-end gap-2 mt-6">
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-6 py-4 border-b border-semantic-border-layout">
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 &ldquo;+ Files&rdquo; 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-6 py-4 border-b border-semantic-border-layout">
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-6 py-4">
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-6 py-5 border-b border-semantic-border-layout hover:no-underline">
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-6 py-2.5">
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-6 pb-2">
13402
+ <div className="px-4 pb-2 sm:px-6">
13384
13403
  <Field label="Escalation Department">
13385
- <StyledSelect
13386
- placeholder="Select department/user"
13387
- value={data.escalationDepartment}
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
- options={[
13391
- { value: "support", label: "Support" },
13392
- { value: "sales", label: "Sales" },
13393
- { value: "billing", label: "Billing" },
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 h-10 w-full border border-semantic-border-input rounded overflow-hidden">
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 px-4 text-sm text-semantic-text-primary bg-semantic-bg-primary outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
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 border-l border-semantic-border-layout">
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-1 flex items-center justify-center px-2.5 text-xs text-semantic-text-muted hover:bg-semantic-bg-hover leading-none border-b border-semantic-border-layout"
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
- \u25B2
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-1 flex items-center justify-center px-2.5 text-xs text-semantic-text-muted hover:bg-semantic-bg-hover leading-none"
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
- \u25BC
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-6 py-5 border-b border-semantic-border-layout hover:no-underline">
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-6 pt-5 pb-6 flex flex-col gap-5 border-b border-semantic-border-layout">
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-6 py-5 flex items-center gap-3">
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
- tones: [],
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 items-center justify-between px-6 h-[76px] bg-semantic-bg-primary border-b border-semantic-border-layout shrink-0">
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 layout */}
13622
- <div className="flex flex-1 gap-6 px-6 py-6 max-w-[1220px] mx-auto w-full">
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 handleKeyChange = (id: string, key: string) =>
13709
- onChange(rows.map((r) => (r.id === id ? { ...r, key } : r)));
13749
+ const update = (id: string, patch: Partial<KeyValuePair>) =>
13750
+ onChange(rows.map((r) => (r.id === id ? { ...r, ...patch } : r)));
13710
13751
 
13711
- const handleValueChange = (id: string, value: string) =>
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 handleDelete = (id: string) =>
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
- {/* Header row */}
13725
- <div className="flex bg-semantic-bg-primary border-b border-semantic-border-layout">
13726
- <div className="flex-1 px-3 py-2.5 text-sm font-semibold text-semantic-text-muted border-r border-semantic-border-layout">
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.5 text-sm font-semibold text-semantic-text-muted">
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
- {/* Data rows */}
13771
+
13772
+ {/* Filled rows */}
13735
13773
  {rows.map((row) => (
13736
13774
  <div
13737
13775
  key={row.id}
13738
- className="flex border-b border-semantic-border-layout last:border-b-0"
13776
+ className="border-b border-semantic-border-layout last:border-b-0"
13739
13777
  >
13740
- <input
13741
- type="text"
13742
- value={row.key}
13743
- onChange={(e) => handleKeyChange(row.id, e.target.value)}
13744
- placeholder="Key"
13745
- 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"
13746
- />
13747
- <input
13748
- type="text"
13749
- value={row.value}
13750
- onChange={(e) => handleValueChange(row.id, e.target.value)}
13751
- placeholder="Type {{ to add variables"
13752
- 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"
13753
- />
13754
- <button
13755
- type="button"
13756
- onClick={() => handleDelete(row.id)}
13757
- className="w-10 flex items-center justify-center text-semantic-text-muted hover:text-semantic-error-primary hover:bg-semantic-error-surface transition-colors"
13758
- aria-label="Delete row"
13759
- >
13760
- <Trash2 className="size-3.5" />
13761
- </button>
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
- {/* Empty input row */}
13765
- <div className="flex">
13766
- <input
13767
- type="text"
13768
- placeholder="Key"
13769
- readOnly
13770
- onClick={handleAdd}
13771
- className="flex-1 px-3 py-2.5 text-sm placeholder:text-semantic-text-muted bg-semantic-bg-primary border-r border-semantic-border-layout outline-none cursor-pointer"
13772
- />
13773
- <input
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
- >(({ open, onOpenChange, onSubmit, onTestApi, initialStep = 1, initialTab = "header", className }, ref) => {
13791
- const [step, setStep] = React.useState<1 | 2>(initialStep);
13792
-
13793
- // Step 1 state
13794
- const [name, setName] = React.useState("");
13795
- const [prompt, setPrompt] = React.useState("");
13796
-
13797
- // Step 2 state
13798
- const [method, setMethod] = React.useState<HttpMethod>("GET");
13799
- const [url, setUrl] = React.useState("");
13800
- const [activeTab, setActiveTab] = React.useState<FunctionTabType>(initialTab);
13801
- const [headers, setHeaders] = React.useState<KeyValuePair[]>([]);
13802
- const [queryParams, setQueryParams] = React.useState<KeyValuePair[]>([]);
13803
- const [body, setBody] = React.useState("");
13804
- const [apiResponse, setApiResponse] = React.useState("");
13805
- const [isTesting, setIsTesting] = React.useState(false);
13806
-
13807
- const reset = React.useCallback(() => {
13808
- setStep(initialStep);
13809
- setName("");
13810
- setPrompt("");
13811
- setMethod("GET");
13812
- setUrl("");
13813
- setActiveTab(initialTab);
13814
- setHeaders([]);
13815
- setQueryParams([]);
13816
- setBody("");
13817
- setApiResponse("");
13818
- }, [initialStep, initialTab]);
13819
-
13820
- const handleClose = React.useCallback(() => {
13821
- reset();
13822
- onOpenChange(false);
13823
- }, [reset, onOpenChange]);
13824
-
13825
- const handleNext = () => {
13826
- if (name.trim() && prompt.trim()) setStep(2);
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
- const handleBack = () => setStep(1);
13904
+ const handleClose = React.useCallback(() => {
13905
+ reset();
13906
+ onOpenChange(false);
13907
+ }, [reset, onOpenChange]);
13830
13908
 
13831
- const handleSubmit = () => {
13832
- const data: CreateFunctionData = {
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
- const isStep1Valid = name.trim().length > 0 && prompt.trim().length > 0;
13858
- const tabCount = {
13859
- header: headers.length,
13860
- queryParams: queryParams.length,
13861
- body: 0,
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
- return (
13865
- <Dialog open={open} onOpenChange={onOpenChange}>
13866
- <DialogContent ref={ref} size="lg" className={cn(className)}>
13867
- <DialogHeader>
13868
- <DialogTitle>Create Function</DialogTitle>
13869
- </DialogHeader>
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
- {step === 1 && (
13872
- <div className="flex flex-col gap-6">
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
- {/* Prompt */}
13905
- <div className="flex flex-col gap-1">
13906
- <label
13907
- htmlFor="fn-prompt"
13908
- className="text-sm font-semibold text-semantic-text-primary"
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
- <div className="flex justify-end">
13930
- <Button
13931
- variant="default"
13932
- onClick={handleNext}
13933
- disabled={!isStep1Valid}
13934
- >
13935
- Next
13936
- </Button>
13937
- </div>
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
- {step === 2 && (
13942
- <div className="flex flex-col gap-6">
13943
- {/* API URL */}
13944
- <div className="flex flex-col gap-1">
13945
- <span className="text-xs text-semantic-text-muted tracking-[0.048px]">API url</span>
13946
- <div className="flex h-10 border border-semantic-border-input rounded overflow-hidden 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)] transition-shadow">
13947
- {/* Method selector */}
13948
- <div className="relative shrink-0">
13949
- <select
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
- {HTTP_METHODS.map((m) => (
13956
- <option key={m} value={m}>
13957
- {m}
13958
- </option>
13959
- ))}
13960
- </select>
13961
- <ChevronDown
13962
- className="absolute right-3 top-1/2 -translate-y-1/2 size-3.5 text-semantic-text-muted pointer-events-none shrink-0"
13963
- aria-hidden="true"
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
- </div>
14028
+ )}
13975
14029
 
13976
- {/* Tabs */}
13977
- <div className="flex flex-col gap-4">
13978
- <div className="flex gap-0 border-b border-semantic-border-layout">
13979
- {(["header", "queryParams", "body"] as FunctionTabType[]).map(
13980
- (tab) => {
13981
- const labels: Record<FunctionTabType, string> = {
13982
- header: \`Header(\${tabCount.header})\`,
13983
- queryParams: \`Query parameter(\${tabCount.queryParams})\`,
13984
- body: "Body",
13985
- };
13986
- return (
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-2.5 py-1.5 text-sm font-semibold transition-colors whitespace-nowrap",
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
- {labels[tab]}
14101
+ {tabLabels[tab]}
13999
14102
  </button>
14000
- );
14001
- }
14002
- )}
14003
- </div>
14103
+ ))}
14104
+ </div>
14004
14105
 
14005
- {/* Tab Content */}
14006
- {activeTab === "header" && (
14007
- <KeyValueTable
14008
- rows={headers}
14009
- onChange={setHeaders}
14010
- label="Header"
14011
- />
14012
- )}
14013
- {activeTab === "queryParams" && (
14014
- <KeyValueTable
14015
- rows={queryParams}
14016
- onChange={setQueryParams}
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
- <span className="absolute bottom-2 right-3 text-xs italic text-semantic-text-muted">
14039
- {body.length}/{BODY_MAX}
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
- {/* Test Your API */}
14047
- <div className="flex flex-col gap-6">
14048
- {/* Label stacked above full-width divider */}
14049
- <div className="flex flex-col gap-1.5">
14050
- <span className="text-xs text-semantic-text-muted tracking-[0.048px]">
14051
- Test Your API
14052
- </span>
14053
- <div className="border-t border-semantic-border-layout w-full" />
14054
- </div>
14055
- {/* Test API button right-aligned */}
14056
- <div className="flex justify-end">
14057
- <button
14058
- type="button"
14059
- onClick={handleTestApi}
14060
- disabled={isTesting || !url.trim()}
14061
- className="h-10 px-6 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"
14062
- >
14063
- {isTesting ? "Testing..." : "Test API"}
14064
- </button>
14065
- </div>
14066
- {/* Row 3: Response from API */}
14067
- <div className="flex flex-col gap-1">
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
- </div>
14174
+ )}
14175
+ </div>
14080
14176
 
14081
- {/* Footer */}
14082
- <div className="flex justify-end gap-3">
14083
- <Button variant="outline" onClick={handleBack}>
14084
- Back
14085
- </Button>
14086
- <Button variant="default" onClick={handleSubmit}>
14087
- Submit
14088
- </Button>
14089
- </div>
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
- </DialogContent>
14093
- </Dialog>
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
- tones: string[];
14283
+ tone: string[];
14159
14284
  voice: string;
14160
14285
  language: string;
14161
14286
  systemPrompt: string;