myoperator-ui 0.0.212 → 0.0.213

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 +459 -124
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3057,6 +3057,8 @@ export interface CreatableSelectProps
3057
3057
  creatableHint?: string
3058
3058
  /** Whether the select is disabled */
3059
3059
  disabled?: boolean
3060
+ /** Max character length for the value (enforced when open and when creating) */
3061
+ maxLength?: number
3060
3062
  }
3061
3063
 
3062
3064
  const CreatableSelect = React.forwardRef<HTMLDivElement, CreatableSelectProps>(
@@ -3070,6 +3072,7 @@ const CreatableSelect = React.forwardRef<HTMLDivElement, CreatableSelectProps>(
3070
3072
  placeholder = "Select an option",
3071
3073
  creatableHint = "Type to create a custom option",
3072
3074
  disabled = false,
3075
+ maxLength,
3073
3076
  ...props
3074
3077
  },
3075
3078
  ref
@@ -3119,11 +3122,12 @@ const CreatableSelect = React.forwardRef<HTMLDivElement, CreatableSelectProps>(
3119
3122
  const handleCreate = React.useCallback(() => {
3120
3123
  const trimmed = search.trim()
3121
3124
  if (trimmed) {
3122
- onValueChange?.(trimmed)
3125
+ const value = maxLength != null ? trimmed.slice(0, maxLength) : trimmed
3126
+ onValueChange?.(value)
3123
3127
  setOpen(false)
3124
3128
  setSearch("")
3125
3129
  }
3126
- }, [search, onValueChange])
3130
+ }, [search, onValueChange, maxLength])
3127
3131
 
3128
3132
  const handleKeyDown = (e: React.KeyboardEvent) => {
3129
3133
  if (e.key === "Escape") {
@@ -3211,7 +3215,11 @@ const CreatableSelect = React.forwardRef<HTMLDivElement, CreatableSelectProps>(
3211
3215
  ref={inputRef}
3212
3216
  type="text"
3213
3217
  value={search}
3214
- onChange={(e) => setSearch(e.target.value)}
3218
+ onChange={(e) => {
3219
+ const v = e.target.value
3220
+ setSearch(maxLength != null ? v.slice(0, maxLength) : v)
3221
+ }}
3222
+ maxLength={maxLength}
3215
3223
  onKeyDown={handleKeyDown}
3216
3224
  className="flex-1 min-w-0 bg-transparent outline-none text-base text-semantic-text-primary placeholder:text-semantic-text-muted"
3217
3225
  placeholder={selectedLabel || placeholder}
@@ -3392,6 +3400,10 @@ export interface CreatableMultiSelectProps
3392
3400
  creatableHint?: string
3393
3401
  /** Helper text shown below the trigger */
3394
3402
  helperText?: string
3403
+ /** Max number of items that can be selected (default: unlimited) */
3404
+ maxItems?: number
3405
+ /** Max character length per item when typing/creating (default: unlimited) */
3406
+ maxLengthPerItem?: number
3395
3407
  }
3396
3408
 
3397
3409
  const CreatableMultiSelect = React.forwardRef<
@@ -3409,6 +3421,8 @@ const CreatableMultiSelect = React.forwardRef<
3409
3421
  state = "default",
3410
3422
  creatableHint = "Type to create a custom option",
3411
3423
  helperText,
3424
+ maxItems,
3425
+ maxLengthPerItem,
3412
3426
  ...props
3413
3427
  },
3414
3428
  ref
@@ -3423,12 +3437,18 @@ const CreatableMultiSelect = React.forwardRef<
3423
3437
  const addValue = React.useCallback(
3424
3438
  (val: string) => {
3425
3439
  const trimmed = val.trim()
3426
- if (trimmed && !value.includes(trimmed)) {
3427
- onValueChange?.([...value, trimmed])
3440
+ if (!trimmed || value.includes(trimmed)) return
3441
+ if (maxItems != null && value.length >= maxItems) return
3442
+ const toAdd =
3443
+ maxLengthPerItem != null
3444
+ ? trimmed.slice(0, maxLengthPerItem)
3445
+ : trimmed
3446
+ if (toAdd) {
3447
+ onValueChange?.([...value, toAdd])
3428
3448
  setInputValue("")
3429
3449
  }
3430
3450
  },
3431
- [value, onValueChange]
3451
+ [value, onValueChange, maxItems, maxLengthPerItem]
3432
3452
  )
3433
3453
 
3434
3454
  const removeValue = React.useCallback(
@@ -3532,9 +3552,13 @@ const CreatableMultiSelect = React.forwardRef<
3532
3552
  type="text"
3533
3553
  value={inputValue}
3534
3554
  onChange={(e) => {
3535
- setInputValue(e.target.value)
3555
+ const v = e.target.value
3556
+ setInputValue(
3557
+ maxLengthPerItem != null ? v.slice(0, maxLengthPerItem) : v
3558
+ )
3536
3559
  if (!isOpen) setIsOpen(true)
3537
3560
  }}
3561
+ maxLength={maxLengthPerItem}
3538
3562
  onFocus={() => {
3539
3563
  if (!disabled) setIsOpen(true)
3540
3564
  }}
@@ -3558,18 +3582,14 @@ const CreatableMultiSelect = React.forwardRef<
3558
3582
  {/* Dropdown panel */}
3559
3583
  {isOpen && (
3560
3584
  <div className="absolute z-[9999] top-full mt-1 w-full bg-semantic-bg-primary border border-semantic-border-layout rounded shadow-md animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200">
3561
- {/* Creatable hint \u2014 contextual */}
3585
+ {/* Creatable hint \u2014 Enter key */}
3562
3586
  <div className="flex items-center justify-between px-4 py-2 border-b border-semantic-border-layout">
3563
- {canAddCustom ? (
3564
- <span className="text-sm font-medium text-semantic-text-primary">
3565
- Press enter to add &ldquo;{inputValue.trim()}&rdquo;
3566
- </span>
3567
- ) : (
3568
- <span className="text-sm text-semantic-text-muted">
3569
- {creatableHint}
3570
- </span>
3571
- )}
3572
- <kbd className="inline-flex items-center gap-0.5 rounded border border-semantic-border-layout bg-semantic-bg-ui px-1.5 py-0.5 text-[10px] text-semantic-text-muted font-medium">
3587
+ <span className="text-sm text-semantic-text-muted">
3588
+ {canAddCustom
3589
+ ? \`Press enter to add "\${inputValue.trim()}"\`
3590
+ : creatableHint}
3591
+ </span>
3592
+ <kbd className="inline-flex items-center gap-0.5 rounded border border-semantic-border-layout bg-semantic-bg-ui px-1.5 py-0.5 text-[10px] text-semantic-text-muted font-medium shrink-0">
3573
3593
  Enter \u21B5
3574
3594
  </kbd>
3575
3595
  </div>
@@ -3597,14 +3617,31 @@ const CreatableMultiSelect = React.forwardRef<
3597
3617
  </div>
3598
3618
  )}
3599
3619
 
3600
- {/* Helper text below trigger */}
3601
- {helperText && !isOpen && (
3602
- <div className="flex items-center gap-1.5 mt-1.5">
3603
- <Info className="size-[18px] shrink-0 text-semantic-text-muted" />
3604
- <p className="m-0 text-sm text-semantic-text-muted">
3605
- {helperText}
3606
- </p>
3620
+ {/* Helper row below trigger: when maxLengthPerItem show dynamic hint + counter (Figma); else optional static helperText */}
3621
+ {maxLengthPerItem != null ? (
3622
+ <div className="flex items-center justify-between gap-2 mt-1.5">
3623
+ <div className="flex items-center gap-1.5 text-xs text-semantic-text-muted min-w-0">
3624
+ <Info className="size-3.5 shrink-0 text-semantic-text-muted" />
3625
+ <p className="m-0 truncate">
3626
+ {inputValue.trim()
3627
+ ? \`Press Enter to add "\${inputValue.trim()}" \u21B5\`
3628
+ : creatableHint}
3629
+ </p>
3630
+ </div>
3631
+ <span className="text-sm text-semantic-text-muted shrink-0">
3632
+ {inputValue.length}/{maxLengthPerItem}
3633
+ </span>
3607
3634
  </div>
3635
+ ) : (
3636
+ helperText &&
3637
+ !isOpen && (
3638
+ <div className="flex items-center gap-1.5 mt-1.5">
3639
+ <Info className="size-[18px] shrink-0 text-semantic-text-muted" />
3640
+ <p className="m-0 text-sm text-semantic-text-muted">
3641
+ {helperText}
3642
+ </p>
3643
+ </div>
3644
+ )
3608
3645
  )}
3609
3646
  </div>
3610
3647
  )
@@ -12833,7 +12870,7 @@ export type { BrandIconProps } from "./icon";
12833
12870
  },
12834
12871
  "bots": {
12835
12872
  name: "bots",
12836
- description: "AI Bot management components \u2014 BotList page, BotListHeader, BotListSearch, BotListCreateCard, BotListGrid, BotCard, and CreateBotModal",
12873
+ description: "AI Bot management components \u2014 BotList, BotListHeader, BotListSearch, BotListCreateCard, BotListGrid, BotCard, CreateBotModal",
12837
12874
  category: "custom",
12838
12875
  dependencies: [
12839
12876
  "clsx",
@@ -12880,7 +12917,7 @@ function getTypeLabel(
12880
12917
  * Set bot.type to "chatbot" or "voicebot"; no separate card components needed.
12881
12918
  */
12882
12919
  export const BotCard = React.forwardRef<HTMLDivElement, BotCardProps>(
12883
- ({ bot, typeLabels, onEdit, onPublish, onDelete, className, ...props }, ref) => {
12920
+ ({ bot, typeLabels, onEdit, onDelete, className, ...props }, ref) => {
12884
12921
  const typeLabel = getTypeLabel(bot, typeLabels);
12885
12922
  const isChatbot = bot.type === "chatbot";
12886
12923
 
@@ -13148,6 +13185,131 @@ export const CreateBotModal = React.forwardRef<
13148
13185
  });
13149
13186
 
13150
13187
  CreateBotModal.displayName = "CreateBotModal";
13188
+ `, prefix)
13189
+ },
13190
+ {
13191
+ name: "create-bot-flow.tsx",
13192
+ content: prefixTailwindClasses(`import * as React from "react";
13193
+ import { cn } from "../../../lib/utils";
13194
+ import { BotListCreateCard } from "./bot-list-create-card";
13195
+ import { BotListGrid } from "./bot-list-grid";
13196
+ import { CreateBotModal } from "./create-bot-modal";
13197
+ import type { CreateBotFlowProps } from "./types";
13198
+
13199
+ /**
13200
+ * Create bot flow: "Create new bot" card + Create Bot modal. No header (title/subtitle/search).
13201
+ * Use when you want the create-bot experience without the list header.
13202
+ */
13203
+ export const CreateBotFlow = React.forwardRef<HTMLDivElement, CreateBotFlowProps>(
13204
+ (
13205
+ {
13206
+ createCardLabel = "Create new bot",
13207
+ onSubmit,
13208
+ className,
13209
+ ...props
13210
+ },
13211
+ ref
13212
+ ) => {
13213
+ const [modalOpen, setModalOpen] = React.useState(false);
13214
+
13215
+ return (
13216
+ <>
13217
+ <div
13218
+ ref={ref}
13219
+ className={cn(
13220
+ "flex flex-col w-full min-w-0 max-w-full overflow-x-hidden box-border",
13221
+ className
13222
+ )}
13223
+ {...props}
13224
+ >
13225
+ <BotListGrid>
13226
+ <BotListCreateCard
13227
+ label={createCardLabel}
13228
+ onClick={() => setModalOpen(true)}
13229
+ />
13230
+ </BotListGrid>
13231
+ </div>
13232
+ <CreateBotModal
13233
+ open={modalOpen}
13234
+ onOpenChange={setModalOpen}
13235
+ onSubmit={(data) => {
13236
+ onSubmit?.(data);
13237
+ setModalOpen(false);
13238
+ }}
13239
+ />
13240
+ </>
13241
+ );
13242
+ }
13243
+ );
13244
+
13245
+ CreateBotFlow.displayName = "CreateBotFlow";
13246
+ `, prefix)
13247
+ },
13248
+ {
13249
+ name: "edit-bot-flow.tsx",
13250
+ content: prefixTailwindClasses(`import * as React from "react";
13251
+ import { BotList } from "./bot-list";
13252
+ import type { Bot, EditBotFlowProps } from "./types";
13253
+
13254
+ /**
13255
+ * Edit bot flow: bot list + config view when Edit is clicked.
13256
+ * Use when you want the "Edit Bot \u2192 Config" experience; parent supplies config via renderConfig.
13257
+ */
13258
+ export function EditBotFlow({
13259
+ bots,
13260
+ title = "AI Bot",
13261
+ subtitle = "Create & manage AI bots",
13262
+ searchPlaceholder = "Search bot...",
13263
+ createCardLabel = "Create new bot",
13264
+ typeLabels,
13265
+ onBotDelete,
13266
+ onCreateBotSubmit,
13267
+ onSearch,
13268
+ renderConfig,
13269
+ instructionText,
13270
+ className,
13271
+ }: EditBotFlowProps) {
13272
+ const [view, setView] = React.useState<"list" | "config">("list");
13273
+ const [editingBot, setEditingBot] = React.useState<Bot | null>(null);
13274
+
13275
+ const handleEdit = (botId: string) => {
13276
+ const bot = bots.find((b) => b.id === botId);
13277
+ if (bot) {
13278
+ setEditingBot(bot);
13279
+ setView("config");
13280
+ }
13281
+ };
13282
+
13283
+ const handleBack = () => {
13284
+ setView("list");
13285
+ setEditingBot(null);
13286
+ };
13287
+
13288
+ if (view === "config" && editingBot) {
13289
+ return <>{renderConfig(editingBot, handleBack)}</>;
13290
+ }
13291
+
13292
+ return (
13293
+ <>
13294
+ {instructionText != null ? (
13295
+ <div className="flex flex-col gap-2 p-6 pb-0">{instructionText}</div>
13296
+ ) : null}
13297
+ <BotList
13298
+ bots={bots}
13299
+ title={title}
13300
+ subtitle={subtitle}
13301
+ searchPlaceholder={searchPlaceholder}
13302
+ createCardLabel={createCardLabel}
13303
+ typeLabels={typeLabels}
13304
+ onBotEdit={handleEdit}
13305
+ onBotDelete={onBotDelete}
13306
+ onCreateBotSubmit={onCreateBotSubmit}
13307
+ onSearch={onSearch}
13308
+ className={className}
13309
+ />
13310
+ </>
13311
+ );
13312
+ }
13151
13313
  `, prefix)
13152
13314
  },
13153
13315
  {
@@ -13170,7 +13332,6 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13170
13332
  onCreateBot,
13171
13333
  onCreateBotSubmit,
13172
13334
  onBotEdit,
13173
- onBotPublish,
13174
13335
  onBotDelete,
13175
13336
  onSearch,
13176
13337
  title = "AI Bot",
@@ -13190,43 +13351,69 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13190
13351
  onSearch?.(value);
13191
13352
  };
13192
13353
 
13193
- return (
13194
- <div
13195
- ref={ref}
13196
- className={cn("flex flex-col w-full min-w-0 max-w-full overflow-x-hidden box-border", className)}
13197
- {...props}
13198
- >
13199
- {/* Page header: title, subtitle, and search */}
13200
- <div className="flex flex-col gap-3 pb-4 mb-4 border-b border-semantic-border-layout sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:pb-5 sm:mb-6 min-w-0">
13201
- <BotListHeader title={title} subtitle={subtitle} />
13202
- <BotListSearch
13203
- value={searchQuery}
13204
- onSearch={handleSearch}
13205
- placeholder={searchPlaceholder}
13206
- />
13207
- </div>
13354
+ const handleCreateClick = () => {
13355
+ setCreateModalOpen(true);
13356
+ onCreateBot?.();
13357
+ };
13208
13358
 
13209
- {/* Bot grid: create card + bot cards */}
13210
- <BotListGrid>
13211
- <BotListCreateCard
13212
- label={createCardLabel}
13213
- onClick={() => {
13214
- setCreateModalOpen(true);
13215
- onCreateBot?.();
13359
+ if (bots.length === 0) {
13360
+ return (
13361
+ <>
13362
+ <div
13363
+ ref={ref}
13364
+ className={cn(
13365
+ "flex flex-col w-full min-w-0 max-w-full overflow-x-hidden box-border",
13366
+ className
13367
+ )}
13368
+ {...props}
13369
+ >
13370
+ <BotListGrid>
13371
+ <BotListCreateCard
13372
+ label={createCardLabel}
13373
+ onClick={handleCreateClick}
13374
+ />
13375
+ </BotListGrid>
13376
+ </div>
13377
+ <CreateBotModal
13378
+ open={createModalOpen}
13379
+ onOpenChange={setCreateModalOpen}
13380
+ onSubmit={(data) => {
13381
+ onCreateBotSubmit?.(data);
13382
+ setCreateModalOpen(false);
13216
13383
  }}
13217
13384
  />
13218
- {bots.map((bot) => (
13219
- <BotCard
13220
- key={bot.id}
13221
- bot={bot}
13222
- typeLabels={typeLabels}
13223
- onEdit={onBotEdit}
13224
- onPublish={onBotPublish}
13225
- onDelete={onBotDelete}
13226
- />
13227
- ))}
13228
- </BotListGrid>
13385
+ </>
13386
+ );
13387
+ }
13229
13388
 
13389
+ return (
13390
+ <>
13391
+ <div
13392
+ ref={ref}
13393
+ className={cn("flex flex-col w-full min-w-0 max-w-full overflow-x-hidden box-border", className)}
13394
+ {...props}
13395
+ >
13396
+ <div className="flex flex-col gap-3 pb-4 mb-4 border-b border-semantic-border-layout sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:pb-5 sm:mb-6 min-w-0">
13397
+ <BotListHeader title={title} subtitle={subtitle} />
13398
+ <BotListSearch
13399
+ value={searchQuery}
13400
+ onSearch={handleSearch}
13401
+ placeholder={searchPlaceholder}
13402
+ />
13403
+ </div>
13404
+ <BotListGrid>
13405
+ <BotListCreateCard label={createCardLabel} onClick={handleCreateClick} />
13406
+ {bots.map((bot) => (
13407
+ <BotCard
13408
+ key={bot.id}
13409
+ bot={bot}
13410
+ typeLabels={typeLabels}
13411
+ onEdit={onBotEdit}
13412
+ onDelete={onBotDelete}
13413
+ />
13414
+ ))}
13415
+ </BotListGrid>
13416
+ </div>
13230
13417
  <CreateBotModal
13231
13418
  open={createModalOpen}
13232
13419
  onOpenChange={setCreateModalOpen}
@@ -13235,7 +13422,7 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13235
13422
  setCreateModalOpen(false);
13236
13423
  }}
13237
13424
  />
13238
- </div>
13425
+ </>
13239
13426
  );
13240
13427
  }
13241
13428
  );
@@ -13246,31 +13433,65 @@ BotList.displayName = "BotList";
13246
13433
  {
13247
13434
  name: "bot-list-header.tsx",
13248
13435
  content: prefixTailwindClasses(`import * as React from "react";
13436
+ import { cva } from "class-variance-authority";
13249
13437
  import { cn } from "../../../lib/utils";
13250
13438
  import type { BotListHeaderProps } from "./types";
13251
13439
 
13440
+ const botListHeaderVariants = cva("min-w-0", {
13441
+ variants: {
13442
+ variant: {
13443
+ default:
13444
+ "flex flex-col gap-1.5 shrink",
13445
+ withSearch:
13446
+ "flex flex-col gap-3 pb-4 mb-4 border-b border-semantic-border-layout sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:pb-5 sm:mb-6 shrink",
13447
+ },
13448
+ },
13449
+ defaultVariants: {
13450
+ variant: "default",
13451
+ },
13452
+ });
13453
+
13252
13454
  export const BotListHeader = React.forwardRef<HTMLDivElement, BotListHeaderProps>(
13253
- ({ title, subtitle, className, ...props }, ref) => (
13254
- <div
13255
- ref={ref}
13256
- className={cn("flex flex-col gap-1.5 min-w-0 shrink", className)}
13257
- {...props}
13258
- >
13259
- {title != null && (
13260
- <h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px] break-words sm:text-lg">
13261
- {title}
13262
- </h1>
13263
- )}
13264
- {subtitle != null && (
13265
- <p className="m-0 text-xs sm:text-sm text-semantic-text-muted tracking-[0.035px] break-words">
13266
- {subtitle}
13267
- </p>
13268
- )}
13269
- </div>
13270
- )
13455
+ (
13456
+ { title, subtitle, variant = "default", rightContent, className, ...props },
13457
+ ref
13458
+ ) => {
13459
+ const rootClassName = cn(botListHeaderVariants({ variant }), className);
13460
+ const titleBlock = (
13461
+ <>
13462
+ {title != null && (
13463
+ <h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px] break-words sm:text-lg">
13464
+ {title}
13465
+ </h1>
13466
+ )}
13467
+ {subtitle != null && (
13468
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted tracking-[0.035px] break-words">
13469
+ {subtitle}
13470
+ </p>
13471
+ )}
13472
+ </>
13473
+ );
13474
+
13475
+ if (variant === "withSearch") {
13476
+ return (
13477
+ <div ref={ref} className={rootClassName} {...props}>
13478
+ <div className="flex flex-col gap-1.5 min-w-0 shrink">{titleBlock}</div>
13479
+ {rightContent}
13480
+ </div>
13481
+ );
13482
+ }
13483
+
13484
+ return (
13485
+ <div ref={ref} className={rootClassName} {...props}>
13486
+ {titleBlock}
13487
+ </div>
13488
+ );
13489
+ }
13271
13490
  );
13272
13491
 
13273
13492
  BotListHeader.displayName = "BotListHeader";
13493
+
13494
+ export { botListHeaderVariants };
13274
13495
  `, prefix)
13275
13496
  },
13276
13497
  {
@@ -13517,8 +13738,6 @@ export interface BotCardProps
13517
13738
  typeLabels?: Partial<Record<BotType, string>>;
13518
13739
  /** Called when Edit action is selected */
13519
13740
  onEdit?: (botId: string) => void;
13520
- /** Called when Publish action is selected */
13521
- onPublish?: (botId: string) => void;
13522
13741
  /** Called when Delete action is selected */
13523
13742
  onDelete?: (botId: string) => void;
13524
13743
  }
@@ -13537,6 +13756,10 @@ export interface BotListHeaderProps
13537
13756
  title?: string;
13538
13757
  /** Optional subtitle below the title */
13539
13758
  subtitle?: string;
13759
+ /** Layout variant: default (title + subtitle only) or withSearch (row with optional right slot) */
13760
+ variant?: "default" | "withSearch";
13761
+ /** Right-side content when variant is "withSearch" (e.g. BotListSearch) */
13762
+ rightContent?: React.ReactNode;
13540
13763
  }
13541
13764
 
13542
13765
  export interface BotListSearchProps
@@ -13574,6 +13797,43 @@ export interface BotListActionProps
13574
13797
  align?: "start" | "center" | "end";
13575
13798
  }
13576
13799
 
13800
+ /** Props for CreateBotFlow: create card + Create Bot modal (no header). */
13801
+ export interface CreateBotFlowProps
13802
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "onSubmit"> {
13803
+ /** Create new bot card label */
13804
+ createCardLabel?: string;
13805
+ /** Called when Create Bot modal is submitted with { name, type } */
13806
+ onSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13807
+ }
13808
+
13809
+ /** Props for EditBotFlow: bot list + config view when Edit is clicked. */
13810
+ export interface EditBotFlowProps {
13811
+ /** Bots to show in the list (e.g. first 2 for demo) */
13812
+ bots: Bot[];
13813
+ /** Page title */
13814
+ title?: string;
13815
+ /** Page subtitle */
13816
+ subtitle?: string;
13817
+ /** Search input placeholder */
13818
+ searchPlaceholder?: string;
13819
+ /** Create new bot card label */
13820
+ createCardLabel?: string;
13821
+ /** Override type badge labels */
13822
+ typeLabels?: Partial<Record<BotType, string>>;
13823
+ /** Called when Delete is selected on a bot */
13824
+ onBotDelete?: (botId: string) => void;
13825
+ /** Called when Create Bot modal is submitted */
13826
+ onCreateBotSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13827
+ /** Called when search query changes */
13828
+ onSearch?: (query: string) => void;
13829
+ /** Renders the config view for the given bot; call onBack() to return to list */
13830
+ renderConfig: (bot: Bot, onBack: () => void) => React.ReactNode;
13831
+ /** Optional instruction text above the list (e.g. "Click the \u22EE menu...") */
13832
+ instructionText?: React.ReactNode;
13833
+ /** Root className for the list wrapper */
13834
+ className?: string;
13835
+ }
13836
+
13577
13837
  export interface BotListProps
13578
13838
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
13579
13839
  /** List of bots to display */
@@ -13586,8 +13846,6 @@ export interface BotListProps
13586
13846
  onCreateBotSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13587
13847
  /** Called when user selects Edit on a bot (card click or menu) */
13588
13848
  onBotEdit?: (botId: string) => void;
13589
- /** Called when user selects Publish on a bot (menu; optional) */
13590
- onBotPublish?: (botId: string) => void;
13591
13849
  /** Called when user selects Delete on a bot */
13592
13850
  onBotDelete?: (botId: string) => void;
13593
13851
  /** Called when the search query changes */
@@ -13609,7 +13867,9 @@ export interface BotListProps
13609
13867
  export type { BotCardProps } from "./types";
13610
13868
 
13611
13869
  export { CreateBotModal } from "./create-bot-modal";
13612
- export type { CreateBotModalProps } from "./types";
13870
+ export { CreateBotFlow } from "./create-bot-flow";
13871
+ export { EditBotFlow } from "./edit-bot-flow";
13872
+ export type { CreateBotModalProps, CreateBotFlowProps, EditBotFlowProps } from "./types";
13613
13873
 
13614
13874
  export { BotList } from "./bot-list";
13615
13875
  export { BotListHeader } from "./bot-list-header";
@@ -15224,11 +15484,14 @@ function Field({
15224
15484
  label,
15225
15485
  required,
15226
15486
  helperText,
15487
+ characterCount,
15227
15488
  children,
15228
15489
  }: {
15229
15490
  label: string;
15230
15491
  required?: boolean;
15231
15492
  helperText?: string;
15493
+ /** e.g. { current: 0, max: 50 } to show "0/50" below right */
15494
+ characterCount?: { current: number; max: number };
15232
15495
  children: React.ReactNode;
15233
15496
  }) {
15234
15497
  return (
@@ -15240,36 +15503,58 @@ function Field({
15240
15503
  )}
15241
15504
  </label>
15242
15505
  {children}
15243
- {helperText && (
15244
- <div className="flex items-center gap-1.5 text-xs text-semantic-text-muted">
15245
- <Info className="size-3.5 shrink-0" />
15246
- <p className="m-0">{helperText}</p>
15506
+ {(helperText || characterCount) && (
15507
+ <div className="flex items-center justify-between gap-2">
15508
+ {helperText ? (
15509
+ <div className="flex items-center gap-1.5 text-xs text-semantic-text-muted min-w-0">
15510
+ <Info className="size-3.5 shrink-0" />
15511
+ <p className="m-0">{helperText}</p>
15512
+ </div>
15513
+ ) : (
15514
+ <span />
15515
+ )}
15516
+ {characterCount != null && (
15517
+ <span className="text-sm text-semantic-text-muted shrink-0">
15518
+ {characterCount.current}/{characterCount.max}
15519
+ </span>
15520
+ )}
15247
15521
  </div>
15248
15522
  )}
15249
15523
  </div>
15250
15524
  );
15251
15525
  }
15252
15526
 
15527
+ const BOT_NAME_MAX_LENGTH = 50;
15528
+ const PRIMARY_ROLE_MAX_LENGTH = 50;
15529
+ const TONE_MAX_ITEMS = 5;
15530
+ const TONE_MAX_LENGTH_PER_ITEM = 20;
15531
+
15253
15532
  function StyledInput({
15254
15533
  placeholder,
15255
15534
  value,
15256
15535
  onChange,
15257
15536
  disabled,
15537
+ maxLength,
15258
15538
  className,
15259
15539
  }: {
15260
15540
  placeholder?: string;
15261
15541
  value?: string;
15262
15542
  onChange?: (v: string) => void;
15263
15543
  disabled?: boolean;
15544
+ maxLength?: number;
15264
15545
  className?: string;
15265
15546
  }) {
15266
15547
  return (
15267
15548
  <input
15268
15549
  type="text"
15269
15550
  value={value ?? ""}
15270
- onChange={(e) => onChange?.(e.target.value)}
15551
+ onChange={(e) => {
15552
+ const v = e.target.value;
15553
+ onChange?.(maxLength != null ? v.slice(0, maxLength) : v);
15554
+ }}
15271
15555
  placeholder={placeholder}
15272
15556
  disabled={disabled}
15557
+ maxLength={maxLength}
15273
15558
  className={cn(
15274
15559
  "w-full h-[42px] px-4 text-base rounded border",
15275
15560
  "border-semantic-border-input bg-semantic-bg-primary",
@@ -15365,34 +15650,52 @@ const BotIdentityCard = React.forwardRef<HTMLDivElement, BotIdentityCardProps>(
15365
15650
  <Field
15366
15651
  label="Bot Name & Identity"
15367
15652
  helperText="This is the name the bot will use to refer to itself during conversations."
15653
+ characterCount={{
15654
+ current: (data.botName ?? "").length,
15655
+ max: BOT_NAME_MAX_LENGTH,
15656
+ }}
15368
15657
  >
15369
15658
  <StyledInput
15370
15659
  placeholder="e.g., Rhea from CaratLane"
15371
15660
  value={data.botName}
15372
15661
  onChange={(v) => onChange({ botName: v })}
15373
15662
  disabled={disabled}
15663
+ maxLength={BOT_NAME_MAX_LENGTH}
15374
15664
  />
15375
15665
  </Field>
15376
15666
 
15377
- <Field label="Primary Role">
15667
+ <Field
15668
+ label="Primary Role"
15669
+ characterCount={{
15670
+ current: (data.primaryRole ?? "").length,
15671
+ max: PRIMARY_ROLE_MAX_LENGTH,
15672
+ }}
15673
+ >
15378
15674
  <CreatableSelect
15379
- value={data.primaryRole || ""}
15380
- onValueChange={(v) => onChange({ primaryRole: v })}
15675
+ value={(data.primaryRole ?? "").slice(0, PRIMARY_ROLE_MAX_LENGTH)}
15676
+ onValueChange={(v) =>
15677
+ onChange({ primaryRole: (v ?? "").slice(0, PRIMARY_ROLE_MAX_LENGTH) })
15678
+ }
15381
15679
  options={roleOptions}
15382
15680
  placeholder="e.g., Customer Support Agent"
15383
15681
  creatableHint="Type to create a custom role"
15384
15682
  disabled={disabled}
15683
+ maxLength={PRIMARY_ROLE_MAX_LENGTH}
15385
15684
  />
15386
15685
  </Field>
15387
15686
 
15388
15687
  <Field label="Tone">
15389
15688
  <CreatableMultiSelect
15390
- value={Array.isArray(data.tone) ? data.tone : []}
15391
- onValueChange={(v) => onChange({ tone: v })}
15689
+ value={(Array.isArray(data.tone) ? data.tone : []).slice(0, TONE_MAX_ITEMS)}
15690
+ onValueChange={(v) =>
15691
+ onChange({ tone: (v ?? []).slice(0, TONE_MAX_ITEMS) })
15692
+ }
15392
15693
  options={toneOptions}
15393
15694
  placeholder="Enter or select tone"
15394
- creatableHint="Type to create a custom tone"
15695
+ creatableHint='Press Enter to add "Conversational" \u21B5'
15395
15696
  disabled={disabled}
15697
+ maxItems={TONE_MAX_ITEMS}
15698
+ maxLengthPerItem={TONE_MAX_LENGTH_PER_ITEM}
15396
15699
  />
15397
15700
  </Field>
15398
15701
 
@@ -15488,7 +15791,7 @@ export { BotIdentityCard };
15488
15791
  {
15489
15792
  name: "bot-behavior-card.tsx",
15490
15793
  content: prefixTailwindClasses(`import * as React from "react";
15491
- import { Plus } from "lucide-react";
15794
+ import { Plus, Info } from "lucide-react";
15492
15795
  import { cn } from "../../../lib/utils";
15493
15796
  import { tagVariants } from "../tag";
15494
15797
 
@@ -15507,7 +15810,7 @@ export interface BotBehaviorCardProps {
15507
15810
  onSystemPromptBlur?: (value: string) => void;
15508
15811
  /** Session variables shown as insertable chips */
15509
15812
  sessionVariables?: string[];
15510
- /** Maximum character length for the system prompt textarea (default: 25000) */
15813
+ /** Maximum character length for the system prompt textarea (default: 5000, per Figma) */
15511
15814
  maxLength?: number;
15512
15815
  /** Disables all fields in the card (view mode) */
15513
15816
  disabled?: boolean;
@@ -15564,7 +15867,7 @@ function StyledTextarea({
15564
15867
  value?: string;
15565
15868
  rows?: number;
15566
15869
  onChange?: (v: string) => void;
15567
- onBlur?: (v: string) => void;
15870
+ onBlur?: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
15568
15871
  disabled?: boolean;
15569
15872
  className?: string;
15570
15873
  }) {
@@ -15573,7 +15876,7 @@ function StyledTextarea({
15573
15876
  value={value ?? ""}
15574
15877
  rows={rows}
15575
15878
  onChange={(e) => onChange?.(e.target.value)}
15576
- onBlur={(e) => onBlur?.(e.target.value)}
15879
+ onBlur={onBlur}
15577
15880
  placeholder={placeholder}
15578
15881
  disabled={disabled}
15579
15882
  className={cn(
@@ -15598,7 +15901,7 @@ const BotBehaviorCard = React.forwardRef<HTMLDivElement, BotBehaviorCardProps>(
15598
15901
  onChange,
15599
15902
  onSystemPromptBlur,
15600
15903
  sessionVariables = DEFAULT_SESSION_VARIABLES,
15601
- maxLength = 25000,
15904
+ maxLength = 5000,
15602
15905
  disabled,
15603
15906
  className,
15604
15907
  },
@@ -15606,11 +15909,32 @@ const BotBehaviorCard = React.forwardRef<HTMLDivElement, BotBehaviorCardProps>(
15606
15909
  ) => {
15607
15910
  const prompt = data.systemPrompt ?? "";
15608
15911
  const MAX = maxLength;
15912
+ const footerRef = React.useRef<HTMLDivElement>(null);
15913
+ /** Set on footer mousedown so blur does not trigger API when user clicked under the input (instruction/chips). */
15914
+ const footerClickInProgressRef = React.useRef(false);
15609
15915
 
15610
15916
  const insertVariable = (variable: string) => {
15611
15917
  onChange({ systemPrompt: prompt + variable });
15612
15918
  };
15613
15919
 
15920
+ const handleSystemPromptBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
15921
+ if (!onSystemPromptBlur) return;
15922
+ const relatedTarget = e.relatedTarget as Node | null;
15923
+ const footerEl = footerRef.current;
15924
+ if (footerClickInProgressRef.current) {
15925
+ footerClickInProgressRef.current = false;
15926
+ return;
15927
+ }
15928
+ if (footerEl && relatedTarget && footerEl.contains(relatedTarget)) {
15929
+ return;
15930
+ }
15931
+ onSystemPromptBlur(e.target.value);
15932
+ };
15933
+
15934
+ const handleFooterMouseDown = () => {
15935
+ footerClickInProgressRef.current = true;
15936
+ };
15937
+
15614
15938
  return (
15615
15939
  <div ref={ref} className={className}>
15616
15940
  <SectionCard title="How It Behaves">
@@ -15625,34 +15949,45 @@ const BotBehaviorCard = React.forwardRef<HTMLDivElement, BotBehaviorCardProps>(
15625
15949
  onChange={(v) => {
15626
15950
  if (v.length <= MAX) onChange({ systemPrompt: v });
15627
15951
  }}
15628
- onBlur={onSystemPromptBlur}
15952
+ onBlur={handleSystemPromptBlur}
15629
15953
  placeholder="You are a helpful assistant. Always start by greeting the user politely: 'Hello! Welcome. How can I assist you today?'"
15630
15954
  disabled={disabled}
15631
- className="pb-8"
15955
+ className="pb-10 pr-[4.5rem]"
15632
15956
  />
15633
- <span className="absolute bottom-2.5 right-3 text-sm text-semantic-text-muted">
15957
+ <span
15958
+ className="absolute bottom-3 right-4 text-sm text-semantic-text-muted pointer-events-none"
15959
+ aria-live="polite"
15960
+ aria-label={\`\${prompt.length} of \${MAX} characters\`}
15961
+ >
15634
15962
  {prompt.length}/{MAX}
15635
15963
  </span>
15636
15964
  </div>
15637
- <p className="m-0 text-sm text-semantic-text-muted">
15638
- Type [[ to enable dropdown or use the below chips to input variables.
15639
- </p>
15640
- <div className="flex flex-wrap items-center gap-2">
15641
- <span className="text-sm text-semantic-text-secondary">
15642
- Session variables:
15643
- </span>
15644
- {sessionVariables.map((v) => (
15645
- <button
15646
- key={v}
15647
- type="button"
15648
- onClick={() => insertVariable(v)}
15649
- disabled={disabled}
15650
- className={cn(tagVariants(), "gap-1.5 cursor-pointer hover:opacity-80 transition-opacity", disabled && "opacity-50 cursor-not-allowed")}
15651
- >
15652
- <Plus className="size-3 shrink-0" />
15653
- {v}
15654
- </button>
15655
- ))}
15965
+ <div
15966
+ ref={footerRef}
15967
+ className="flex flex-col gap-3"
15968
+ onMouseDown={handleFooterMouseDown}
15969
+ >
15970
+ <p className="m-0 flex items-center gap-1.5 text-sm text-semantic-text-muted">
15971
+ <Info className="size-4 shrink-0 text-semantic-text-muted" aria-hidden />
15972
+ Type {'{{'} to enable dropdown or use the below chips to input variables.
15973
+ </p>
15974
+ <div className="flex flex-wrap items-center gap-2">
15975
+ <span className="text-sm text-semantic-text-secondary">
15976
+ Session variables:
15977
+ </span>
15978
+ {sessionVariables.map((v) => (
15979
+ <button
15980
+ key={v}
15981
+ type="button"
15982
+ onClick={() => insertVariable(v)}
15983
+ disabled={disabled}
15984
+ className={cn(tagVariants(), "gap-1.5 cursor-pointer hover:opacity-80 transition-opacity", disabled && "opacity-50 cursor-not-allowed")}
15985
+ >
15986
+ <Plus className="size-3 shrink-0" />
15987
+ {v}
15988
+ </button>
15989
+ ))}
15990
+ </div>
15656
15991
  </div>
15657
15992
  </div>
15658
15993
  </SectionCard>
@@ -16598,7 +16933,7 @@ export interface IvrBotConfigProps {
16598
16933
  * Pass when your app fetches full function data after onEditFunction fires.
16599
16934
  */
16600
16935
  functionEditData?: Partial<CreateFunctionData>;
16601
- /** Max character length for the "How It Behaves" system prompt (default: 25000) */
16936
+ /** Max character length for the "How It Behaves" system prompt (default: 5000, per Figma) */
16602
16937
  systemPromptMaxLength?: number;
16603
16938
  /** Called when the system prompt textarea loses focus */
16604
16939
  onSystemPromptBlur?: (value: string) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-ui",
3
- "version": "0.0.212",
3
+ "version": "0.0.213",
4
4
  "description": "CLI for adding myOperator UI components to your project",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",