@smartspace/chat-ui 1.14.4-dev.65f646d → 1.14.4-dev.df55472

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import MuiButton from '@mui/material/Button';
2
2
  import IconButton from '@mui/material/IconButton';
3
- import { Loader2, Cpu, ChevronDown, Check, Globe, Zap, SlidersHorizontal, X, Paperclip, Mic, Square, Send, Minimize2, AlertTriangle, FileImage, FileVideo, FileAudio, FileArchive, FileCode, FileSpreadsheet, Presentation, FileText, ChevronUp, ExternalLink, Download, ShieldAlert, Copy } from 'lucide-react';
4
- import * as React8 from 'react';
5
- import { createContext, forwardRef, useImperativeHandle, useRef, useState, useEffect, useMemo, useCallback, useContext, useSyncExternalStore } from 'react';
3
+ import { Loader2, ChevronDown, Check, Cpu, SlidersHorizontal, X, Globe, Zap, Paperclip, Mic, Square, Send, Minimize2, AlertTriangle, FileImage, FileVideo, FileAudio, FileArchive, FileCode, FileSpreadsheet, Presentation, FileText, ChevronUp, ExternalLink, Download, ShieldAlert, Copy } from 'lucide-react';
4
+ import * as React9 from 'react';
5
+ import React9__default, { createContext, forwardRef, useImperativeHandle, useRef, useState, useEffect, useMemo, useCallback, useContext, useSyncExternalStore } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { useQuery, queryOptions, useQueryClient, useMutation, skipToken } from '@tanstack/react-query';
8
+ import { useQuery, queryOptions, useQueryClient, useMutation } from '@tanstack/react-query';
9
9
  import { toast } from 'sonner';
10
10
  import { ChatZod } from '@smartspace/api-client';
11
11
  import { z } from 'zod';
@@ -24,7 +24,6 @@ import '@milkdown/prose/inputrules';
24
24
  import { withJsonFormsControlProps, withJsonFormsLayoutProps, JsonForms, ResolvedJsonFormsDispatch } from '@jsonforms/react';
25
25
  import { rankWith, uiTypeIs, createAjv } from '@jsonforms/core';
26
26
  import { vanillaRenderers, vanillaCells } from '@jsonforms/vanilla-renderers';
27
- import { FormControl, InputLabel, Select, MenuItem, useTheme, Box, Skeleton as Skeleton$1 } from '@mui/material';
28
27
  import 'ace-builds/src-noconflict/ace';
29
28
  import 'ace-builds/src-noconflict/ext-language_tools';
30
29
  import 'ace-builds/src-noconflict/mode-json';
@@ -57,6 +56,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
57
56
  import utc from 'dayjs/plugin/utc';
58
57
  import { Slot } from '@radix-ui/react-slot';
59
58
  import { cva } from 'class-variance-authority';
59
+ import { useTheme, Box, Skeleton as Skeleton$1 } from '@mui/material';
60
60
 
61
61
  var __defProp = Object.defineProperty;
62
62
  var __export = (target, all5) => {
@@ -600,9 +600,14 @@ var useThread = ({
600
600
  };
601
601
  var useThreadIsRunning = (workspaceId, threadId) => {
602
602
  const queryClient = useQueryClient();
603
+ const service = useChatService();
603
604
  const { data: detailThread } = useQuery({
604
- queryKey: threadsKeys.detail(workspaceId ?? "", threadId ?? ""),
605
- queryFn: skipToken
605
+ ...threadDetailOptions({
606
+ service,
607
+ workspaceId: workspaceId ?? "",
608
+ threadId: threadId ?? ""
609
+ }),
610
+ enabled: false
606
611
  });
607
612
  const listThread = workspaceId && threadId ? getThreadPlaceholderFromListCache(queryClient, workspaceId, threadId) : void 0;
608
613
  const { data: optimistic } = useQuery({
@@ -2443,6 +2448,31 @@ function useFlowRunVariables(flowRunId) {
2443
2448
  enabled: !!flowRunId
2444
2449
  });
2445
2450
  }
2451
+
2452
+ // src/chat-variables/renders/fieldStyles.ts
2453
+ var scaleFor = (surface) => surface === "form" ? "comfortable" : "dense";
2454
+ var fieldLabelClass = (scale) => `block truncate font-medium text-muted-foreground ${scale === "dense" ? "mb-1 text-xs" : "mb-1.5 text-sm"}`;
2455
+ var CONTROL_BASE = "rounded-md border bg-background text-foreground transition-colors placeholder:text-muted-foreground ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60";
2456
+ var borderClass = (hasError) => hasError ? "border-destructive" : "border-input";
2457
+ var fieldControlClass = (scale, hasError) => `w-full ${CONTROL_BASE} ${borderClass(hasError)} ${scale === "dense" ? "px-2.5 py-1.5 text-xs" : "px-3 py-2 text-sm"}`;
2458
+ var fieldTriggerClass = (scale, hasError) => `w-full ${CONTROL_BASE} ${borderClass(
2459
+ hasError
2460
+ )} flex items-center justify-between gap-2 text-left ${scale === "dense" ? "h-8 px-2.5 text-xs" : "h-10 px-3 text-sm"}`;
2461
+ var fieldNumberClass = (scale, hasError) => `${CONTROL_BASE} ${borderClass(hasError)} tabular-nums ${scale === "dense" ? "h-8 w-20 px-2 text-xs" : "h-10 w-24 px-2.5 text-sm"}`;
2462
+ var fieldHintClass = "mt-1 text-xs text-muted-foreground";
2463
+ var fieldErrorClass = "mt-1 text-xs text-destructive";
2464
+ var fieldRowClass = "flex items-center justify-between gap-3";
2465
+ var fieldRowLabelClass = (scale) => `min-w-0 flex-1 truncate font-medium text-muted-foreground ${scale === "dense" ? "text-xs" : "text-sm"}`;
2466
+ var pillClass = (opts) => `flex h-8 shrink-0 items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${opts.active ? "border-primary/30 bg-primary/10 text-primary" : "border-border/70 bg-transparent text-muted-foreground hover:bg-secondary"} ${opts.hasError ? "border-destructive text-destructive" : ""} ${opts.disabled ? "cursor-not-allowed opacity-60" : ""}`;
2467
+ var pillLabelClass = "max-w-[9rem] truncate";
2468
+ var pillValueClass = "max-w-[9rem] truncate text-foreground";
2469
+ var switchTrackClass = (on, disabled) => `peer inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background ${on ? "bg-primary" : "bg-input"} ${disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`;
2470
+ var switchThumbClass = (on) => `pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${on ? "translate-x-5" : "translate-x-0"}`;
2471
+ var POPOVER_WIDTH_PX = 288;
2472
+ var POPOVER_MAX_HEIGHT_PX = 288;
2473
+ var popoverClass = "flex flex-col overflow-hidden rounded-xl border border-border bg-popover shadow-lg";
2474
+ var popoverRowClass = "flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-secondary/60";
2475
+ var popoverHeadingClass = "text-[10px] font-semibold uppercase tracking-wide text-muted-foreground";
2446
2476
  function iconFor(label) {
2447
2477
  const name = (label ?? "").toLowerCase();
2448
2478
  if (/search|web|browse|internet/.test(name)) return Globe;
@@ -2458,7 +2488,8 @@ var BooleanRenderer = ({
2458
2488
  errors,
2459
2489
  uischema,
2460
2490
  visible,
2461
- enabled
2491
+ enabled,
2492
+ config
2462
2493
  }) => {
2463
2494
  const onToggle = useCallback(() => {
2464
2495
  handleChange(path2, !data);
@@ -2469,13 +2500,17 @@ var BooleanRenderer = ({
2469
2500
  const hasError = !!errors && errors.length > 0;
2470
2501
  const isChecked = Boolean(data);
2471
2502
  const Icon = iconFor(label);
2503
+ const surface = (config ?? {}).surface ?? "form";
2504
+ const scale = scaleFor(surface);
2472
2505
  const tooltip = [label, description, hasError ? errors : null].filter(Boolean).join(" \u2014 ");
2473
- return /* @__PURE__ */ jsxs(
2506
+ const toggle = /* @__PURE__ */ jsx(
2474
2507
  "button",
2475
2508
  {
2476
2509
  id: `toggle-${path2}`,
2477
2510
  type: "button",
2478
- "aria-pressed": isChecked,
2511
+ role: surface === "bar" ? void 0 : "switch",
2512
+ "aria-pressed": surface === "bar" ? isChecked : void 0,
2513
+ "aria-checked": surface === "bar" ? void 0 : isChecked,
2479
2514
  "aria-label": label,
2480
2515
  title: tooltip || void 0,
2481
2516
  onClick: onToggle,
@@ -2485,16 +2520,28 @@ var BooleanRenderer = ({
2485
2520
  e.currentTarget.blur();
2486
2521
  }
2487
2522
  },
2488
- className: `boolean-switch flex h-8 shrink-0 items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${isChecked ? "border-primary/30 bg-primary/10 text-primary" : "border-transparent bg-transparent text-muted-foreground hover:bg-secondary"} ${hasError ? "border-destructive text-destructive" : ""} ${isDisabled ? "cursor-not-allowed opacity-60" : ""}`,
2489
- children: [
2523
+ className: surface === "bar" ? `boolean-switch ${pillClass({
2524
+ active: isChecked,
2525
+ hasError,
2526
+ disabled: isDisabled
2527
+ })}` : switchTrackClass(isChecked, isDisabled),
2528
+ children: surface === "bar" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2490
2529
  /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
2491
2530
  label && // Desktop-first: the app and the package each ship a full Tailwind
2492
- // build, so a base `hidden` can out-order `sm:inline`. `max-sm:hidden`
2493
- // is the form that survives both cascades.
2494
- /* @__PURE__ */ jsx("span", { className: "inline max-w-[9rem] truncate max-sm:hidden", children: label })
2495
- ]
2531
+ // build, so a base `hidden` can out-order `sm:inline`.
2532
+ // `max-sm:hidden` is the form that survives both cascades.
2533
+ /* @__PURE__ */ jsx("span", { className: `inline ${pillLabelClass} max-sm:hidden`, children: label })
2534
+ ] }) : /* @__PURE__ */ jsx("span", { className: switchThumbClass(isChecked) })
2496
2535
  }
2497
2536
  );
2537
+ if (surface === "bar") return toggle;
2538
+ return /* @__PURE__ */ jsxs("div", { className: "ss-jsonforms-field ss-jsonforms-boolean", children: [
2539
+ /* @__PURE__ */ jsxs("div", { className: fieldRowClass, children: [
2540
+ /* @__PURE__ */ jsx("label", { htmlFor: `toggle-${path2}`, className: fieldRowLabelClass(scale), children: label }),
2541
+ toggle
2542
+ ] }),
2543
+ hasError && /* @__PURE__ */ jsx("div", { className: fieldErrorClass, children: errors })
2544
+ ] });
2498
2545
  };
2499
2546
  var booleanRendererTester = rankWith(40, (uischema, schema) => {
2500
2547
  if (uischema.type !== "Control") return false;
@@ -2504,6 +2551,104 @@ var booleanRendererTester = rankWith(40, (uischema, schema) => {
2504
2551
  return fieldSchema?.type === "boolean";
2505
2552
  });
2506
2553
  var BooleanRendererControl = withJsonFormsControlProps(BooleanRenderer);
2554
+ var GAP_PX = 8;
2555
+ var MIN_TRIGGER_WIDTH_PX = 224;
2556
+ var MIN_PANEL_HEIGHT_PX = 180;
2557
+ var NestedPopoverContext = React9__default.createContext(null);
2558
+ function useAnchoredPopover({
2559
+ trigger,
2560
+ popover,
2561
+ width,
2562
+ maxHeight,
2563
+ role,
2564
+ label
2565
+ }) {
2566
+ const [isOpen, setIsOpen] = useState(false);
2567
+ const parentRegistry = useContext(NestedPopoverContext);
2568
+ const descendants = useRef(/* @__PURE__ */ new Set());
2569
+ const close = useCallback(() => setIsOpen(false), []);
2570
+ const toggle = useCallback(() => setIsOpen((open) => !open), []);
2571
+ const registry = useMemo(
2572
+ () => ({
2573
+ register: (el) => {
2574
+ descendants.current.add(el);
2575
+ parentRegistry?.register(el);
2576
+ },
2577
+ unregister: (el) => {
2578
+ descendants.current.delete(el);
2579
+ parentRegistry?.unregister(el);
2580
+ }
2581
+ }),
2582
+ [parentRegistry]
2583
+ );
2584
+ useEffect(() => {
2585
+ if (!isOpen || !parentRegistry) return;
2586
+ const el = popover.current;
2587
+ if (!el) return;
2588
+ parentRegistry.register(el);
2589
+ return () => parentRegistry.unregister(el);
2590
+ }, [isOpen, parentRegistry, popover]);
2591
+ useEffect(() => {
2592
+ if (!isOpen) return;
2593
+ const onPointerDown = (event) => {
2594
+ const target = event.target;
2595
+ if (trigger.current?.contains(target)) return;
2596
+ if (popover.current?.contains(target)) return;
2597
+ for (const el of descendants.current) {
2598
+ if (el.contains(target)) return;
2599
+ }
2600
+ close();
2601
+ };
2602
+ const onKeyDown = (event) => {
2603
+ if (event.key === "Escape") close();
2604
+ };
2605
+ document.addEventListener("mousedown", onPointerDown);
2606
+ document.addEventListener("keydown", onKeyDown);
2607
+ window.addEventListener("resize", close);
2608
+ return () => {
2609
+ document.removeEventListener("mousedown", onPointerDown);
2610
+ document.removeEventListener("keydown", onKeyDown);
2611
+ window.removeEventListener("resize", close);
2612
+ };
2613
+ }, [isOpen, close, trigger, popover]);
2614
+ const renderPopover = (children2) => {
2615
+ if (!isOpen) return null;
2616
+ const anchor = trigger.current?.getBoundingClientRect();
2617
+ if (!anchor) return null;
2618
+ const panelWidth = width === "trigger" ? Math.max(anchor.width, MIN_TRIGGER_WIDTH_PX) : width;
2619
+ const spaceAbove = anchor.top - GAP_PX * 2;
2620
+ const spaceBelow = window.innerHeight - anchor.bottom - GAP_PX * 2;
2621
+ const openAbove = spaceAbove >= MIN_PANEL_HEIGHT_PX || spaceAbove >= spaceBelow;
2622
+ const panelMaxHeight = Math.max(
2623
+ 0,
2624
+ Math.min(maxHeight, openAbove ? spaceAbove : spaceBelow)
2625
+ );
2626
+ return createPortal(
2627
+ /* @__PURE__ */ jsx(
2628
+ "div",
2629
+ {
2630
+ ref: popover,
2631
+ role,
2632
+ "aria-label": label,
2633
+ className: `fixed z-50 ${popoverClass}`,
2634
+ style: {
2635
+ width: panelWidth,
2636
+ maxWidth: `calc(100vw - ${GAP_PX * 4}px)`,
2637
+ left: Math.max(
2638
+ GAP_PX,
2639
+ Math.min(anchor.left, window.innerWidth - panelWidth - GAP_PX)
2640
+ ),
2641
+ ...openAbove ? { bottom: window.innerHeight - anchor.top + GAP_PX } : { top: anchor.bottom + GAP_PX },
2642
+ maxHeight: panelMaxHeight
2643
+ },
2644
+ children: /* @__PURE__ */ jsx(NestedPopoverContext.Provider, { value: registry, children: children2 })
2645
+ }
2646
+ ),
2647
+ document.body
2648
+ );
2649
+ };
2650
+ return { isOpen, close, toggle, renderPopover };
2651
+ }
2507
2652
  function hasConst(x) {
2508
2653
  return !!x && typeof x === "object" && "const" in x;
2509
2654
  }
@@ -2542,185 +2687,109 @@ var DropdownRenderer = ({
2542
2687
  label,
2543
2688
  description,
2544
2689
  errors,
2545
- uischema
2690
+ config,
2691
+ uischema,
2692
+ visible
2546
2693
  }) => {
2694
+ const triggerRef = useRef(null);
2695
+ const listRef = useRef(null);
2696
+ const surface = (config ?? {}).surface ?? "form";
2697
+ const scale = scaleFor(surface);
2698
+ const { isOpen, close, toggle, renderPopover } = useAnchoredPopover({
2699
+ trigger: triggerRef,
2700
+ popover: listRef,
2701
+ // A select's list lines up with its own control.
2702
+ width: "trigger",
2703
+ maxHeight: POPOVER_MAX_HEIGHT_PX,
2704
+ role: "listbox",
2705
+ label: label || "Options"
2706
+ });
2547
2707
  const options = toOptions(schema);
2548
- const handleSelectionChange = useCallback(
2549
- (event) => {
2550
- handleChange(path2, event.target.value);
2708
+ const select = useCallback(
2709
+ (value) => {
2710
+ handleChange(path2, value);
2711
+ close();
2551
2712
  },
2552
- [handleChange, path2]
2713
+ [handleChange, path2, close]
2553
2714
  );
2554
- const displayValue = data ?? "";
2715
+ if (!visible) return null;
2555
2716
  const readOnly = uischema?.access === "Read";
2556
2717
  const isDisabled = !enabled || readOnly;
2557
- return /* @__PURE__ */ jsxs(
2558
- FormControl,
2718
+ const hasError = !!errors;
2719
+ const selected = options.find((option) => option.const === data);
2720
+ const valueText = selected ? selected.title ?? String(selected.const) : data != null && data !== "" ? String(data) : "";
2721
+ const triggerId = `${path2}-trigger`;
2722
+ const isBar = surface === "bar";
2723
+ const placeholder = `Select ${(label || "value").toLowerCase()}\u2026`;
2724
+ const trigger = /* @__PURE__ */ jsxs(
2725
+ "button",
2559
2726
  {
2560
- variant: "outlined",
2561
- size: "small",
2562
- fullWidth: true,
2563
- error: !!errors,
2727
+ id: triggerId,
2728
+ ref: triggerRef,
2729
+ type: "button",
2564
2730
  disabled: isDisabled,
2565
- className: "compact-field",
2566
- sx: {
2567
- minWidth: "220px"
2568
- },
2731
+ onClick: toggle,
2732
+ "aria-haspopup": "listbox",
2733
+ "aria-expanded": isOpen,
2734
+ "aria-label": label,
2735
+ title: [label, valueText, hasError ? errors : null].filter(Boolean).join(" \u2014 ") || void 0,
2736
+ className: isBar ? `ss-jsonforms-select-trigger ${pillClass({
2737
+ active: isOpen,
2738
+ hasError,
2739
+ disabled: isDisabled
2740
+ })}` : `ss-jsonforms-select-trigger ${fieldTriggerClass(scale, hasError)}`,
2569
2741
  children: [
2742
+ isBar && label && /* @__PURE__ */ jsx("span", { className: `inline ${pillLabelClass} max-sm:hidden`, children: label }),
2570
2743
  /* @__PURE__ */ jsx(
2571
- InputLabel,
2744
+ "span",
2572
2745
  {
2573
- id: `${path2}-label`,
2574
- sx: {
2575
- color: "#6b7280",
2576
- fontSize: "0.875rem",
2577
- fontWeight: 500,
2578
- "&.Mui-focused": {
2579
- color: "#6366f1"
2580
- },
2581
- "&.Mui-error": {
2582
- color: "#ef4444"
2583
- }
2584
- },
2585
- children: label
2746
+ className: isBar ? pillValueClass : `min-w-0 flex-1 truncate ${valueText ? "" : "text-muted-foreground"}`,
2747
+ children: valueText || (isBar ? "\u2014" : placeholder)
2586
2748
  }
2587
2749
  ),
2588
2750
  /* @__PURE__ */ jsx(
2589
- Select,
2751
+ ChevronDown,
2590
2752
  {
2591
- labelId: `${path2}-label`,
2592
- value: displayValue,
2593
- onChange: handleSelectionChange,
2594
- label,
2595
- sx: {
2596
- "& .MuiOutlinedInput-root": {
2597
- backgroundColor: "#fafafa",
2598
- borderRadius: "8px",
2599
- transition: "all 0.2s ease-in-out",
2600
- height: "40px",
2601
- // Same height as model dropdown
2602
- "& fieldset": {
2603
- borderColor: "#e5e7eb",
2604
- borderWidth: "1px"
2605
- },
2606
- "&:hover": {
2607
- backgroundColor: "#ffffff",
2608
- "& fieldset": {
2609
- borderColor: "#9ca3af"
2610
- }
2611
- },
2612
- "&.Mui-focused": {
2613
- backgroundColor: "#ffffff",
2614
- "& fieldset": {
2615
- borderColor: "#6366f1",
2616
- borderWidth: "2px"
2617
- }
2618
- },
2619
- "&.Mui-error": {
2620
- "& fieldset": {
2621
- borderColor: "#ef4444"
2622
- }
2623
- }
2624
- },
2625
- "& .MuiSelect-select": {
2626
- backgroundColor: "#fafafa",
2627
- borderRadius: "8px",
2628
- transition: "all 0.2s ease-in-out",
2629
- height: "40px",
2630
- display: "flex",
2631
- alignItems: "center",
2632
- paddingRight: "32px !important",
2633
- "&:hover": {
2634
- backgroundColor: "#ffffff"
2635
- },
2636
- "&.Mui-focused": {
2637
- backgroundColor: "#ffffff"
2638
- }
2639
- },
2640
- "& .MuiOutlinedInput-notchedOutline": {
2641
- borderColor: "#e5e7eb",
2642
- borderWidth: "1px",
2643
- borderRadius: "8px"
2644
- },
2645
- "&:hover .MuiOutlinedInput-notchedOutline": {
2646
- borderColor: "#9ca3af"
2647
- },
2648
- "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
2649
- borderColor: "#6366f1",
2650
- borderWidth: "2px"
2651
- },
2652
- "&.Mui-error .MuiOutlinedInput-notchedOutline": {
2653
- borderColor: "#ef4444"
2654
- },
2655
- "& .MuiSelect-icon": {
2656
- color: "#9ca3af",
2657
- "&:hover": {
2658
- color: "#6b7280"
2659
- }
2660
- }
2661
- },
2662
- MenuProps: {
2663
- PaperProps: {
2664
- sx: {
2665
- borderRadius: "8px",
2666
- boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
2667
- border: "1px solid #e5e7eb",
2668
- marginTop: "4px",
2669
- maxHeight: "280px"
2670
- }
2671
- },
2672
- MenuListProps: {
2673
- sx: {
2674
- padding: 0
2675
- }
2676
- }
2677
- },
2678
- children: options.map((option, index) => {
2679
- const raw2 = option.const;
2680
- const key = typeof raw2 === "string" || typeof raw2 === "number" ? String(raw2) : String(index);
2681
- const value = typeof raw2 === "string" || typeof raw2 === "number" ? raw2 : String(raw2 ?? index);
2682
- return /* @__PURE__ */ jsx(
2683
- MenuItem,
2684
- {
2685
- value,
2686
- sx: {
2687
- padding: "12px 16px",
2688
- fontSize: "0.875rem",
2689
- borderBottom: "1px solid #f3f4f6",
2690
- "&:last-child": {
2691
- borderBottom: "none"
2692
- },
2693
- "&:hover": {
2694
- backgroundColor: "#f8fafc"
2695
- },
2696
- "&.Mui-selected": {
2697
- backgroundColor: "#eff6ff",
2698
- "&:hover": {
2699
- backgroundColor: "#dbeafe"
2700
- }
2701
- }
2702
- },
2703
- children: option.title ?? String(raw2)
2704
- },
2705
- key
2706
- );
2707
- })
2708
- }
2709
- ),
2710
- (description || errors) && /* @__PURE__ */ jsx(
2711
- "div",
2712
- {
2713
- style: {
2714
- fontSize: "0.75rem",
2715
- marginTop: "4px",
2716
- color: errors ? "#ef4444" : "#6b7280"
2717
- },
2718
- children: errors || description
2753
+ className: `shrink-0 opacity-50 ${isBar ? "h-3 w-3" : "h-4 w-4"}`
2719
2754
  }
2720
2755
  )
2721
2756
  ]
2722
2757
  }
2723
2758
  );
2759
+ return /* @__PURE__ */ jsxs("div", { className: "ss-jsonforms-field ss-jsonforms-select compact-field", children: [
2760
+ !isBar && label && /* @__PURE__ */ jsx("label", { htmlFor: triggerId, className: fieldLabelClass(scale), children: label }),
2761
+ trigger,
2762
+ !isBar && (errors || description) && /* @__PURE__ */ jsx("div", { className: errors ? fieldErrorClass : fieldHintClass, children: errors || description }),
2763
+ renderPopover(
2764
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 overflow-y-auto py-1", children: [
2765
+ options.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-xs text-muted-foreground", children: "No options available" }),
2766
+ options.map((option, index) => {
2767
+ const active = option.const === data;
2768
+ const text6 = option.title ?? String(option.const);
2769
+ return /* @__PURE__ */ jsxs(
2770
+ "button",
2771
+ {
2772
+ type: "button",
2773
+ role: "option",
2774
+ "aria-selected": active,
2775
+ onClick: () => select(option.const),
2776
+ className: popoverRowClass,
2777
+ children: [
2778
+ /* @__PURE__ */ jsx(
2779
+ Check,
2780
+ {
2781
+ className: `h-3.5 w-3.5 shrink-0 ${active ? "text-primary" : "opacity-0"}`
2782
+ }
2783
+ ),
2784
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-foreground", children: text6 })
2785
+ ]
2786
+ },
2787
+ typeof option.const === "string" || typeof option.const === "number" ? String(option.const) : `option-${index}`
2788
+ );
2789
+ })
2790
+ ] })
2791
+ )
2792
+ ] });
2724
2793
  };
2725
2794
  var dropdownRendererTester = rankWith(
2726
2795
  90,
@@ -2738,8 +2807,8 @@ var dropdownRendererTester = rankWith(
2738
2807
  return false;
2739
2808
  }
2740
2809
  const hasDropdownOptions = !!(fieldSchema.oneOf || fieldSchema.anyOf || fieldSchema.enum && Array.isArray(fieldSchema.enum));
2741
- const isModelSelector = typeof fieldSchema === "object" && fieldSchema && fieldSchema["x-model-selector"] === true || fieldSchema.title === "ModelId" || fieldSchema.format === "uuid";
2742
- return hasDropdownOptions && !isModelSelector;
2810
+ const isModelSelector2 = typeof fieldSchema === "object" && fieldSchema && fieldSchema["x-model-selector"] === true || fieldSchema.title === "ModelId" || fieldSchema.format === "uuid";
2811
+ return hasDropdownOptions && !isModelSelector2;
2743
2812
  }
2744
2813
  );
2745
2814
  var DropdownRendererControl = withJsonFormsControlProps(DropdownRenderer);
@@ -2774,7 +2843,8 @@ var JsonEditorRenderer = ({
2774
2843
  uischema,
2775
2844
  visible,
2776
2845
  enabled,
2777
- required
2846
+ required,
2847
+ config
2778
2848
  }) => {
2779
2849
  const [jsonValue, setJsonValue] = useState("");
2780
2850
  const [displayedParseError, setDisplayedParseError] = useState(
@@ -2828,43 +2898,19 @@ var JsonEditorRenderer = ({
2828
2898
  }
2829
2899
  const readOnly = uischema?.access === "Read";
2830
2900
  const isDisabled = !enabled || readOnly;
2901
+ const scale = scaleFor(
2902
+ (config ?? {}).surface ?? "form"
2903
+ );
2831
2904
  return /* @__PURE__ */ jsxs("div", { style: { marginBottom: "1rem" }, children: [
2832
- label && /* @__PURE__ */ jsxs(
2833
- "label",
2834
- {
2835
- style: {
2836
- display: "block",
2837
- color: "#475569",
2838
- fontSize: "0.875rem",
2839
- fontWeight: 500,
2840
- marginBottom: "0.375rem"
2841
- },
2842
- children: [
2843
- label,
2844
- required && /* @__PURE__ */ jsx("span", { style: { color: "#ef4444", marginLeft: "0.25rem" }, children: "*" })
2845
- ]
2846
- }
2847
- ),
2848
- description && /* @__PURE__ */ jsx(
2849
- "div",
2850
- {
2851
- style: {
2852
- color: "#6b7280",
2853
- fontSize: "0.75rem",
2854
- marginBottom: "0.5rem"
2855
- },
2856
- children: description
2857
- }
2858
- ),
2905
+ label && /* @__PURE__ */ jsxs("label", { className: fieldLabelClass(scale), children: [
2906
+ label,
2907
+ required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
2908
+ ] }),
2909
+ description && /* @__PURE__ */ jsx("div", { className: fieldHintClass, children: description }),
2859
2910
  /* @__PURE__ */ jsx(
2860
2911
  "div",
2861
2912
  {
2862
- style: {
2863
- border: errors || displayedParseError ? "1px solid #ef4444" : "1px solid #d1d5db",
2864
- borderRadius: "6px",
2865
- overflow: "hidden",
2866
- opacity: isDisabled ? 0.6 : 1
2867
- },
2913
+ className: `overflow-hidden rounded-md border ${errors || displayedParseError ? "border-destructive" : "border-input"} ${isDisabled ? "opacity-60" : ""}`,
2868
2914
  children: /* @__PURE__ */ jsx(
2869
2915
  AceEditor,
2870
2916
  {
@@ -2897,28 +2943,8 @@ var JsonEditorRenderer = ({
2897
2943
  )
2898
2944
  }
2899
2945
  ),
2900
- displayedParseError && /* @__PURE__ */ jsx(
2901
- "div",
2902
- {
2903
- style: {
2904
- color: "#ef4444",
2905
- fontSize: "0.75rem",
2906
- marginTop: "0.25rem"
2907
- },
2908
- children: displayedParseError
2909
- }
2910
- ),
2911
- errors && /* @__PURE__ */ jsx(
2912
- "div",
2913
- {
2914
- style: {
2915
- color: "#ef4444",
2916
- fontSize: "0.75rem",
2917
- marginTop: "0.25rem"
2918
- },
2919
- children: errors
2920
- }
2921
- )
2946
+ displayedParseError && /* @__PURE__ */ jsx("div", { className: fieldErrorClass, children: displayedParseError }),
2947
+ errors && /* @__PURE__ */ jsx("div", { className: fieldErrorClass, children: errors })
2922
2948
  ] });
2923
2949
  };
2924
2950
  var jsonEditorTester = rankWith(
@@ -3009,14 +3035,31 @@ var ModelIdRenderer = ({
3009
3035
  description,
3010
3036
  errors,
3011
3037
  uischema,
3012
- visible
3038
+ visible,
3039
+ config
3013
3040
  }) => {
3014
- const [isOpen, setIsOpen] = useState(false);
3015
3041
  const [searchValue, setSearchValue] = useState("");
3016
3042
  const [debouncedSearchValue, setDebouncedSearchValue] = useState("");
3017
- const containerRef = useRef(null);
3043
+ const triggerRef = useRef(null);
3018
3044
  const menuRef = useRef(null);
3019
3045
  const searchRef = useRef(null);
3046
+ const surface = (config ?? {}).surface ?? "bar";
3047
+ const scale = scaleFor(surface);
3048
+ const isBar = surface === "bar";
3049
+ const {
3050
+ isOpen,
3051
+ close: closeMenu,
3052
+ toggle,
3053
+ renderPopover
3054
+ } = useAnchoredPopover({
3055
+ trigger: triggerRef,
3056
+ popover: menuRef,
3057
+ // Wide enough for a provider glyph, a model name and its id underneath.
3058
+ width: POPOVER_WIDTH_PX,
3059
+ maxHeight: POPOVER_MAX_HEIGHT_PX,
3060
+ role: "listbox",
3061
+ label: "Models"
3062
+ });
3020
3063
  const debounceTimerRef = useRef(
3021
3064
  void 0
3022
3065
  );
@@ -3055,35 +3098,13 @@ var ModelIdRenderer = ({
3055
3098
  handleChange(path2, model.id);
3056
3099
  setSearchValue("");
3057
3100
  setDebouncedSearchValue("");
3058
- setIsOpen(false);
3101
+ closeMenu();
3059
3102
  },
3060
- [handleChange, path2]
3103
+ [handleChange, path2, closeMenu]
3061
3104
  );
3062
- const close = useCallback(() => {
3063
- setIsOpen(false);
3064
- setSearchValue("");
3065
- }, []);
3066
3105
  useEffect(() => {
3067
- if (!isOpen) return;
3068
- const onPointerDown = (event) => {
3069
- const target = event.target;
3070
- if (containerRef.current && !containerRef.current.contains(target) && !(menuRef.current && menuRef.current.contains(target))) {
3071
- close();
3072
- }
3073
- };
3074
- const onKeyDown = (event) => {
3075
- if (event.key === "Escape") close();
3076
- };
3077
- const onResize = () => close();
3078
- document.addEventListener("mousedown", onPointerDown);
3079
- document.addEventListener("keydown", onKeyDown);
3080
- window.addEventListener("resize", onResize);
3081
- return () => {
3082
- document.removeEventListener("mousedown", onPointerDown);
3083
- document.removeEventListener("keydown", onKeyDown);
3084
- window.removeEventListener("resize", onResize);
3085
- };
3086
- }, [isOpen, close]);
3106
+ if (!isOpen) setSearchValue("");
3107
+ }, [isOpen]);
3087
3108
  useEffect(() => {
3088
3109
  if (isOpen) searchRef.current?.focus();
3089
3110
  }, [isOpen]);
@@ -3092,53 +3113,58 @@ var ModelIdRenderer = ({
3092
3113
  const isDisabled = !enabled || readOnly;
3093
3114
  const hasError = !!errors && errors.length > 0;
3094
3115
  const selectedName = selectedModel ? selectedModel.displayName || selectedModel.name || "" : "";
3095
- const triggerText = selectedName || label || "Select model";
3116
+ const triggerText = selectedName || "Select model";
3096
3117
  const iconSrc = getModelIcon(selectedModel);
3097
3118
  const tooltip = [selectedName || label, description, hasError ? errors : null].filter(Boolean).join(" \u2014 ");
3098
- const anchorRect = isOpen ? containerRef.current?.getBoundingClientRect() : void 0;
3099
- return /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", ref: containerRef, children: [
3100
- /* @__PURE__ */ jsxs(
3101
- "button",
3102
- {
3103
- type: "button",
3104
- onClick: () => setIsOpen((v) => !v),
3105
- disabled: isDisabled,
3106
- "aria-expanded": isOpen,
3107
- "aria-haspopup": "listbox",
3108
- "aria-label": label || "Select model",
3109
- title: tooltip || void 0,
3110
- className: `flex h-8 shrink-0 items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${isOpen ? "border-primary/30 bg-primary/10 text-primary" : "border-border/70 bg-transparent text-muted-foreground hover:bg-secondary"} ${hasError ? "border-destructive text-destructive" : ""} ${isDisabled ? "cursor-not-allowed opacity-60" : ""}`,
3111
- children: [
3112
- iconSrc ? /* @__PURE__ */ jsx(
3113
- "img",
3114
- {
3115
- src: iconSrc,
3116
- alt: "",
3117
- className: "h-3.5 w-3.5 shrink-0 object-contain"
3118
- }
3119
- ) : /* @__PURE__ */ jsx(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
3120
- /* @__PURE__ */ jsx("span", { className: "inline max-w-[9rem] truncate max-sm:hidden", children: triggerText }),
3121
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 opacity-60" })
3122
- ]
3123
- }
3124
- ),
3125
- isOpen && anchorRect && createPortal(
3126
- /* @__PURE__ */ jsxs(
3127
- "div",
3128
- {
3129
- ref: menuRef,
3130
- role: "listbox",
3131
- "aria-label": label || "Models",
3132
- className: "fixed z-50 w-72 max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-popover shadow-lg",
3133
- style: {
3134
- left: Math.max(
3135
- 8,
3136
- Math.min(anchorRect.left, window.innerWidth - 288 - 8)
3137
- ),
3138
- bottom: window.innerHeight - anchorRect.top + 8
3139
- },
3140
- children: [
3141
- /* @__PURE__ */ jsx("div", { className: "border-b border-border p-2", children: /* @__PURE__ */ jsx(
3119
+ const triggerLabel = !label || label === "ModelId" ? "Model" : label;
3120
+ const trigger = /* @__PURE__ */ jsxs(
3121
+ "button",
3122
+ {
3123
+ ref: triggerRef,
3124
+ id: `${path2}-trigger`,
3125
+ type: "button",
3126
+ onClick: toggle,
3127
+ disabled: isDisabled,
3128
+ "aria-expanded": isOpen,
3129
+ "aria-haspopup": "listbox",
3130
+ "aria-label": triggerLabel,
3131
+ title: tooltip || void 0,
3132
+ className: isBar ? pillClass({ active: isOpen, hasError, disabled: isDisabled }) : fieldTriggerClass(scale, hasError),
3133
+ children: [
3134
+ iconSrc ? /* @__PURE__ */ jsx(
3135
+ "img",
3136
+ {
3137
+ src: iconSrc,
3138
+ alt: "",
3139
+ className: "h-3.5 w-3.5 shrink-0 object-contain"
3140
+ }
3141
+ ) : /* @__PURE__ */ jsx(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
3142
+ /* @__PURE__ */ jsx(
3143
+ "span",
3144
+ {
3145
+ className: isBar ? `inline ${pillValueClass} max-sm:hidden` : "min-w-0 flex-1 truncate",
3146
+ children: triggerText
3147
+ }
3148
+ ),
3149
+ /* @__PURE__ */ jsx(
3150
+ ChevronDown,
3151
+ {
3152
+ className: `shrink-0 opacity-60 ${isBar ? "h-3 w-3" : "h-4 w-4"}`
3153
+ }
3154
+ )
3155
+ ]
3156
+ }
3157
+ );
3158
+ return /* @__PURE__ */ jsxs(
3159
+ "div",
3160
+ {
3161
+ className: isBar ? "ss-jsonforms-field ss-jsonforms-model relative shrink-0" : "ss-jsonforms-field ss-jsonforms-model compact-field",
3162
+ children: [
3163
+ !isBar && /* @__PURE__ */ jsx("label", { htmlFor: `${path2}-trigger`, className: fieldLabelClass(scale), children: triggerLabel }),
3164
+ trigger,
3165
+ renderPopover(
3166
+ /* @__PURE__ */ jsxs(Fragment, { children: [
3167
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-border p-2", children: /* @__PURE__ */ jsx(
3142
3168
  "input",
3143
3169
  {
3144
3170
  ref: searchRef,
@@ -3150,7 +3176,7 @@ var ModelIdRenderer = ({
3150
3176
  className: "h-8 w-full rounded-md border border-border bg-background px-2 text-xs text-foreground outline-none placeholder:text-muted-foreground focus:border-primary/40"
3151
3177
  }
3152
3178
  ) }),
3153
- /* @__PURE__ */ jsxs("div", { className: "max-h-72 overflow-y-auto", children: [
3179
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 overflow-y-auto", children: [
3154
3180
  isLoading && listModels.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary" }) }),
3155
3181
  !isLoading && listModels.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-xs text-muted-foreground", children: searchValue ? "No models found" : "No models available" }),
3156
3182
  listModels.map((model) => {
@@ -3163,7 +3189,7 @@ var ModelIdRenderer = ({
3163
3189
  role: "option",
3164
3190
  "aria-selected": active,
3165
3191
  onClick: () => handleSelect(model),
3166
- className: "flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-secondary/60",
3192
+ className: popoverRowClass,
3167
3193
  children: [
3168
3194
  /* @__PURE__ */ jsx(
3169
3195
  Check,
@@ -3189,12 +3215,11 @@ var ModelIdRenderer = ({
3189
3215
  );
3190
3216
  })
3191
3217
  ] })
3192
- ]
3193
- }
3194
- ),
3195
- document.body
3196
- )
3197
- ] });
3218
+ ] })
3219
+ )
3220
+ ]
3221
+ }
3222
+ );
3198
3223
  };
3199
3224
  var modelIdRendererTester = rankWith(
3200
3225
  100,
@@ -3229,6 +3254,7 @@ var NumberRenderer = ({
3229
3254
  uischema,
3230
3255
  visible,
3231
3256
  enabled,
3257
+ config,
3232
3258
  required
3233
3259
  }) => {
3234
3260
  const isInteger = schema?.type === "integer";
@@ -3257,40 +3283,58 @@ var NumberRenderer = ({
3257
3283
  const max = fieldSchema?.maximum;
3258
3284
  const step = isInteger ? 1 : fieldSchema?.multipleOf ?? "any";
3259
3285
  const tooltip = [label, description, hasError ? errors : null].filter(Boolean).join(" \u2014 ");
3260
- return /* @__PURE__ */ jsxs(
3261
- "div",
3286
+ const surface = (config ?? {}).surface ?? "form";
3287
+ const scale = scaleFor(surface);
3288
+ const input = /* @__PURE__ */ jsx(
3289
+ "input",
3262
3290
  {
3263
- className: `ss-jsonforms-field ss-jsonforms-number flex h-8 shrink-0 items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium transition-colors focus-within:border-primary/40 ${hasError ? "border-destructive" : "border-border/70"} ${isDisabled ? "opacity-60" : ""}`,
3264
- title: tooltip || void 0,
3265
- children: [
3266
- label && /* @__PURE__ */ jsxs(
3267
- "label",
3268
- {
3269
- htmlFor: `number-${path2}`,
3270
- className: `whitespace-nowrap ${hasError ? "text-destructive" : "text-muted-foreground"}`,
3271
- children: [
3272
- label,
3273
- required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
3274
- ]
3275
- }
3276
- ),
3277
- /* @__PURE__ */ jsx(
3278
- "input",
3279
- {
3280
- id: `number-${path2}`,
3281
- type: "number",
3282
- value: data ?? "",
3283
- onChange: handleInputChange,
3284
- disabled: isDisabled,
3285
- min,
3286
- max,
3287
- step,
3288
- className: "w-14 border-0 bg-transparent p-0 text-xs font-medium tabular-nums text-foreground outline-none disabled:cursor-not-allowed"
3289
- }
3290
- )
3291
- ]
3291
+ id: `number-${path2}`,
3292
+ type: "number",
3293
+ value: data ?? "",
3294
+ onChange: handleInputChange,
3295
+ disabled: isDisabled,
3296
+ min,
3297
+ max,
3298
+ step,
3299
+ className: surface === "bar" ? "w-14 border-0 bg-transparent p-0 text-xs font-medium tabular-nums text-foreground outline-none disabled:cursor-not-allowed" : fieldNumberClass(scale, hasError)
3292
3300
  }
3293
3301
  );
3302
+ if (surface === "bar") {
3303
+ return /* @__PURE__ */ jsxs(
3304
+ "div",
3305
+ {
3306
+ className: `ss-jsonforms-field ss-jsonforms-number ${pillClass({
3307
+ hasError,
3308
+ disabled: isDisabled
3309
+ })}`,
3310
+ title: tooltip || void 0,
3311
+ children: [
3312
+ label && /* @__PURE__ */ jsxs(
3313
+ "label",
3314
+ {
3315
+ htmlFor: `number-${path2}`,
3316
+ className: `${pillLabelClass} whitespace-nowrap ${hasError ? "text-destructive" : "text-muted-foreground"}`,
3317
+ children: [
3318
+ label,
3319
+ required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
3320
+ ]
3321
+ }
3322
+ ),
3323
+ input
3324
+ ]
3325
+ }
3326
+ );
3327
+ }
3328
+ return /* @__PURE__ */ jsxs("div", { className: "ss-jsonforms-field ss-jsonforms-number", children: [
3329
+ /* @__PURE__ */ jsxs("div", { className: fieldRowClass, children: [
3330
+ /* @__PURE__ */ jsxs("label", { htmlFor: `number-${path2}`, className: fieldRowLabelClass(scale), children: [
3331
+ label,
3332
+ required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
3333
+ ] }),
3334
+ input
3335
+ ] }),
3336
+ hasError && /* @__PURE__ */ jsx("div", { className: fieldErrorClass, children: errors })
3337
+ ] });
3294
3338
  };
3295
3339
  var numberRendererTester = rankWith(
3296
3340
  40,
@@ -3306,6 +3350,14 @@ var numberRendererTester = rankWith(
3306
3350
  }
3307
3351
  );
3308
3352
  var NumberRendererControl = withJsonFormsControlProps(NumberRenderer);
3353
+ var SCALE = {
3354
+ comfortable: { lineHeight: 20, padding: 8 },
3355
+ dense: { lineHeight: 16, padding: 6 }
3356
+ };
3357
+ var rowsToPx = (rows, scale) => rows * scale.lineHeight + scale.padding * 2 + 2;
3358
+ var MAX_VIEWPORT_SHARE = 0.5;
3359
+ var DEFAULT_MIN_ROWS = 3;
3360
+ var DEFAULT_MAX_ROWS = 9;
3309
3361
  var TextareaRenderer = ({
3310
3362
  data,
3311
3363
  handleChange,
@@ -3317,28 +3369,49 @@ var TextareaRenderer = ({
3317
3369
  uischema,
3318
3370
  visible,
3319
3371
  enabled,
3320
- required
3372
+ required,
3373
+ config
3321
3374
  }) => {
3322
3375
  const textareaRef = useRef(null);
3376
+ const textareaOptions = schema["ui:textarea"] ?? {};
3377
+ const formConfig = config ?? {};
3378
+ const scaleName = scaleFor(formConfig.surface ?? "form");
3379
+ const scale = SCALE[scaleName];
3380
+ const minRows = textareaOptions.minRows ?? formConfig.minRows ?? DEFAULT_MIN_ROWS;
3381
+ const maxRows = Math.max(
3382
+ minRows,
3383
+ textareaOptions.maxRows ?? formConfig.maxRows ?? DEFAULT_MAX_ROWS
3384
+ );
3385
+ const minHeight = rowsToPx(minRows, scale);
3386
+ const maxHeight = rowsToPx(maxRows, scale);
3387
+ const [viewportCap, setViewportCap] = useState(
3388
+ () => typeof window === "undefined" ? Number.POSITIVE_INFINITY : Math.round(window.innerHeight * MAX_VIEWPORT_SHARE)
3389
+ );
3390
+ const ceiling = Math.max(minHeight, Math.min(maxHeight, viewportCap));
3323
3391
  const autoResize = useCallback(() => {
3324
- if (textareaRef.current) {
3325
- textareaRef.current.style.height = "auto";
3326
- textareaRef.current.style.height = `${Math.min(
3327
- Math.max(textareaRef.current.scrollHeight, 80),
3328
- // Minimum height of 80px
3329
- 240
3330
- // Maximum height of 240px
3331
- )}px`;
3332
- }
3333
- }, []);
3392
+ const el = textareaRef.current;
3393
+ if (!el) return;
3394
+ el.style.height = "auto";
3395
+ el.style.height = `${Math.min(
3396
+ Math.max(el.scrollHeight, minHeight),
3397
+ ceiling
3398
+ )}px`;
3399
+ }, [minHeight, ceiling]);
3334
3400
  useEffect(() => {
3335
3401
  autoResize();
3336
3402
  }, [data, autoResize]);
3403
+ useEffect(() => {
3404
+ const onResize = () => {
3405
+ setViewportCap(Math.round(window.innerHeight * MAX_VIEWPORT_SHARE));
3406
+ autoResize();
3407
+ };
3408
+ window.addEventListener("resize", onResize);
3409
+ return () => window.removeEventListener("resize", onResize);
3410
+ }, [autoResize]);
3337
3411
  const handleInputChange = useCallback(
3338
3412
  (event) => {
3339
- const newValue = event.target.value;
3340
- handleChange(path2, newValue);
3341
- setTimeout(autoResize, 0);
3413
+ handleChange(path2, event.target.value);
3414
+ autoResize();
3342
3415
  },
3343
3416
  [handleChange, path2, autoResize]
3344
3417
  );
@@ -3348,89 +3421,34 @@ var TextareaRenderer = ({
3348
3421
  const readOnly = uischema?.access === "Read";
3349
3422
  const isDisabled = !enabled || readOnly;
3350
3423
  const hasError = errors && errors.length > 0;
3351
- const textareaOptions = schema["ui:textarea"] ?? {};
3352
3424
  const placeholder = textareaOptions.placeholder || schema?.description || `Enter ${label?.toLowerCase() || "text"}...`;
3353
- const minRows = textareaOptions.minRows || 3;
3354
3425
  return /* @__PURE__ */ jsxs("div", { className: "ss-jsonforms-field ss-jsonforms-textarea", children: [
3355
- label && /* @__PURE__ */ jsxs(
3356
- "label",
3357
- {
3358
- style: {
3359
- display: "block",
3360
- color: hasError ? "#ef4444" : "#475569",
3361
- fontSize: "0.875rem",
3362
- fontWeight: 500,
3363
- marginBottom: "0.375rem"
3364
- },
3365
- children: [
3366
- label,
3367
- required && /* @__PURE__ */ jsx("span", { style: { color: "#ef4444", marginLeft: "0.25rem" }, children: "*" })
3368
- ]
3369
- }
3370
- ),
3371
- description && /* @__PURE__ */ jsx(
3372
- "div",
3373
- {
3374
- style: {
3375
- color: "#6b7280",
3376
- fontSize: "0.75rem",
3377
- marginBottom: "0.5rem"
3378
- },
3379
- children: description
3380
- }
3381
- ),
3426
+ label && /* @__PURE__ */ jsxs("label", { htmlFor: path2, className: fieldLabelClass(scaleName), children: [
3427
+ label,
3428
+ required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
3429
+ ] }),
3430
+ description && /* @__PURE__ */ jsx("div", { className: fieldHintClass, children: description }),
3382
3431
  /* @__PURE__ */ jsx(
3383
3432
  "textarea",
3384
3433
  {
3434
+ id: path2,
3385
3435
  ref: textareaRef,
3386
3436
  value: data || "",
3387
3437
  onChange: handleInputChange,
3388
3438
  placeholder,
3389
3439
  disabled: isDisabled,
3390
3440
  rows: minRows,
3441
+ className: `resize-y ${fieldControlClass(scaleName, !!hasError)}`,
3391
3442
  style: {
3392
- width: "100%",
3393
- minHeight: "80px",
3394
- maxHeight: "240px",
3395
- resize: "vertical",
3396
- padding: "0.75rem",
3397
- border: hasError ? "2px solid #ef4444" : "1px solid #d1d5db",
3398
- borderRadius: "6px",
3399
- fontSize: "16px",
3400
- lineHeight: "1.5",
3401
- fontFamily: "inherit",
3402
- backgroundColor: isDisabled ? "#f9fafb" : "#ffffff",
3403
- color: isDisabled ? "#9ca3af" : "#111827",
3404
- outline: "none",
3405
- transition: "border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out",
3406
- boxShadow: hasError ? "0 0 0 1px #ef4444" : "none",
3443
+ minHeight: `${minHeight}px`,
3444
+ maxHeight: `${ceiling}px`,
3445
+ // iOS zooms a focused control under 16px; the design's fields are
3446
+ // 14px, so opt out of the adjustment rather than the design.
3407
3447
  WebkitTextSizeAdjust: "100%"
3408
- },
3409
- onFocus: (e) => {
3410
- if (!hasError) {
3411
- e.target.style.borderColor = "#6366f1";
3412
- e.target.style.boxShadow = "0 0 0 1px #6366f1";
3413
- }
3414
- },
3415
- onBlur: (e) => {
3416
- if (!hasError) {
3417
- e.target.style.borderColor = "#d1d5db";
3418
- e.target.style.boxShadow = "none";
3419
- }
3420
3448
  }
3421
3449
  }
3422
3450
  ),
3423
- hasError && /* @__PURE__ */ jsx(
3424
- "div",
3425
- {
3426
- style: {
3427
- color: "#ef4444",
3428
- fontSize: "0.75rem",
3429
- marginTop: "0.25rem"
3430
- },
3431
- children: errors
3432
- }
3433
- )
3451
+ hasError && /* @__PURE__ */ jsx("div", { className: fieldErrorClass, children: errors })
3434
3452
  ] });
3435
3453
  };
3436
3454
  var textareaRendererTester = rankWith(
@@ -3482,10 +3500,25 @@ var renderers = [
3482
3500
  ];
3483
3501
 
3484
3502
  // src/chat-variables/VariablesForm.vm.ts
3503
+ var MAX_INLINE_VARIABLES = 3;
3504
+ var isModelSelector = (schema) => schema.title === "ModelId" || schema["x-model-selector"] === true;
3505
+ function rowLayout(controls) {
3506
+ const innerRow = {
3507
+ type: "HorizontalLayout",
3508
+ elements: controls,
3509
+ options: { gap: "12px", alignItems: "flex-start" }
3510
+ };
3511
+ const ui = {
3512
+ type: "VerticalLayout",
3513
+ elements: [innerRow]
3514
+ };
3515
+ return ui;
3516
+ }
3485
3517
  function buildSimpleSchemaAndUi(vars, threadVars, useDefaults) {
3486
3518
  const names = Object.keys(vars || {});
3487
3519
  const properties2 = {};
3488
3520
  const controls = [];
3521
+ const inlineOnly = [];
3489
3522
  const initialData = {};
3490
3523
  const $defs = {};
3491
3524
  for (const name of names) {
@@ -3505,19 +3538,24 @@ function buildSimpleSchemaAndUi(vars, threadVars, useDefaults) {
3505
3538
  control.enabled = false;
3506
3539
  }
3507
3540
  controls.push(control);
3541
+ if (isModelSelector(properties2[name])) inlineOnly.push(control);
3508
3542
  }
3509
3543
  const schema = { type: "object", properties: properties2 };
3510
3544
  if (Object.keys($defs).length > 0) schema.$defs = $defs;
3511
- const innerRow = {
3512
- type: "HorizontalLayout",
3513
- elements: controls,
3514
- options: { gap: "12px", alignItems: "flex-start" }
3515
- };
3516
- const ui = {
3517
- type: "VerticalLayout",
3518
- elements: [innerRow]
3545
+ if (controls.length <= MAX_INLINE_VARIABLES) {
3546
+ return {
3547
+ schema,
3548
+ inlineControls: controls,
3549
+ overflowControls: [],
3550
+ initialData
3551
+ };
3552
+ }
3553
+ return {
3554
+ schema,
3555
+ inlineControls: inlineOnly,
3556
+ overflowControls: controls.filter((c) => !inlineOnly.includes(c)),
3557
+ initialData
3519
3558
  };
3520
- return { schema, uiSchema: ui, initialData };
3521
3559
  }
3522
3560
  function useChatVariablesFormVm({
3523
3561
  workspace,
@@ -3532,26 +3570,26 @@ function useChatVariablesFormVm({
3532
3570
  const { mutate: updateVariableMutation } = useUpdateFlowRunVariable();
3533
3571
  const querySettled = !isLoading && (threadVars !== void 0 || isError);
3534
3572
  const shouldUseDefaults = isError || threadVars && Object.keys(threadVars).length === 0;
3535
- const built = React8.useMemo(() => {
3573
+ const built = React9.useMemo(() => {
3536
3574
  return buildSimpleSchemaAndUi(
3537
3575
  workspace.variables,
3538
3576
  threadVars,
3539
3577
  shouldUseDefaults ?? false
3540
3578
  );
3541
3579
  }, [workspace.variables, threadVars, shouldUseDefaults]);
3542
- const [data, setData] = React8.useState(null);
3543
- React8.useEffect(() => {
3580
+ const [data, setData] = React9.useState(null);
3581
+ React9.useEffect(() => {
3544
3582
  if (querySettled) {
3545
3583
  setData(built.initialData);
3546
3584
  setVariables(built.initialData);
3547
3585
  }
3548
3586
  }, [querySettled, built.initialData, setVariables]);
3549
- const ajv = React8.useMemo(() => createAjv({ useDefaults: false }), []);
3550
- const prevRef = React8.useRef(null);
3551
- React8.useEffect(() => {
3587
+ const ajv = React9.useMemo(() => createAjv({ useDefaults: false }), []);
3588
+ const prevRef = React9.useRef(null);
3589
+ React9.useEffect(() => {
3552
3590
  prevRef.current = data;
3553
3591
  }, [data]);
3554
- const onChange = React8.useCallback(
3592
+ const onChange = React9.useCallback(
3555
3593
  ({ data: next2 }) => {
3556
3594
  if (prevRef.current && !isDraftThreadId(threadId)) {
3557
3595
  const keys2 = Object.keys(workspace.variables || {});
@@ -3572,29 +3610,96 @@ function useChatVariablesFormVm({
3572
3610
  },
3573
3611
  [workspace.variables, setVariables, updateVariableMutation, threadId]
3574
3612
  );
3575
- const config = React8.useMemo(
3613
+ const barConfig = React9.useMemo(
3576
3614
  () => ({
3577
3615
  restrict: true,
3578
3616
  trim: false,
3579
3617
  showUnfocusedDescription: true,
3580
- hideRequiredAsterisk: true
3618
+ hideRequiredAsterisk: true,
3619
+ surface: "bar",
3620
+ minRows: 1,
3621
+ maxRows: 6
3581
3622
  }),
3582
3623
  []
3583
3624
  );
3625
+ const panelConfig = React9.useMemo(
3626
+ () => ({ ...barConfig, surface: "panel" }),
3627
+ [barConfig]
3628
+ );
3629
+ const inlineUiSchema = React9.useMemo(
3630
+ () => built.inlineControls.length ? rowLayout(built.inlineControls) : null,
3631
+ [built.inlineControls]
3632
+ );
3633
+ const overflowUiSchema = React9.useMemo(
3634
+ () => built.overflowControls.length ? rowLayout(built.overflowControls) : null,
3635
+ [built.overflowControls]
3636
+ );
3584
3637
  return {
3585
3638
  schema: built.schema,
3586
- uiSchema: built.uiSchema,
3639
+ inlineUiSchema,
3640
+ overflowUiSchema,
3641
+ overflowCount: built.overflowControls.length,
3587
3642
  data,
3588
3643
  renderers,
3589
3644
  cells,
3590
3645
  ajv,
3591
3646
  onChange,
3592
- config,
3647
+ barConfig,
3648
+ panelConfig,
3593
3649
  isLoading,
3594
3650
  isReady: querySettled,
3595
3651
  isHydrated: data !== null
3596
3652
  };
3597
3653
  }
3654
+ function VariablesOverflowPanel({ count, children: children2 }) {
3655
+ const triggerRef = useRef(null);
3656
+ const panelRef = useRef(null);
3657
+ const { isOpen, close, toggle, renderPopover } = useAnchoredPopover({
3658
+ trigger: triggerRef,
3659
+ popover: panelRef,
3660
+ width: POPOVER_WIDTH_PX,
3661
+ maxHeight: POPOVER_MAX_HEIGHT_PX,
3662
+ role: "dialog",
3663
+ label: "Workspace variables"
3664
+ });
3665
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3666
+ /* @__PURE__ */ jsxs(
3667
+ "button",
3668
+ {
3669
+ ref: triggerRef,
3670
+ type: "button",
3671
+ onClick: toggle,
3672
+ "aria-expanded": isOpen,
3673
+ "aria-haspopup": "dialog",
3674
+ "aria-label": `Variables (${count})`,
3675
+ className: `flex h-8 shrink-0 items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${isOpen ? "border-primary/30 bg-primary/10 text-primary" : "border-border/70 bg-transparent text-muted-foreground hover:bg-secondary"}`,
3676
+ children: [
3677
+ /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-3.5 w-3.5 shrink-0" }),
3678
+ /* @__PURE__ */ jsx("span", { className: "inline max-sm:hidden", children: "Variables" }),
3679
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-secondary px-1.5 text-[10px] leading-4 tabular-nums", children: count })
3680
+ ]
3681
+ }
3682
+ ),
3683
+ renderPopover(
3684
+ /* @__PURE__ */ jsxs(Fragment, { children: [
3685
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-between border-b border-border px-3 py-2", children: [
3686
+ /* @__PURE__ */ jsx("span", { className: popoverHeadingClass, children: "Variables" }),
3687
+ /* @__PURE__ */ jsx(
3688
+ "button",
3689
+ {
3690
+ type: "button",
3691
+ onClick: close,
3692
+ "aria-label": "Close variables",
3693
+ className: "inline-flex h-5 w-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground",
3694
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
3695
+ }
3696
+ )
3697
+ ] }),
3698
+ /* @__PURE__ */ jsx("div", { className: "ss-variables-panel min-h-0 overflow-y-auto p-3", children: children2 })
3699
+ ] })
3700
+ )
3701
+ ] });
3702
+ }
3598
3703
  var ChatVariablesForm = forwardRef(({ workspace, threadId, setVariables }, ref) => {
3599
3704
  const vm = useChatVariablesFormVm({ workspace, threadId, setVariables });
3600
3705
  useImperativeHandle(ref, () => ({
@@ -3610,19 +3715,32 @@ var ChatVariablesForm = forwardRef(({ workspace, threadId, setVariables }, ref)
3610
3715
  if (!vm.isHydrated) {
3611
3716
  return /* @__PURE__ */ jsx("div", { className: "flex justify-center items-center w-full h-8", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary" }) });
3612
3717
  }
3613
- return /* @__PURE__ */ jsx("div", { className: "w-full jsonforms-compact", children: /* @__PURE__ */ jsx(
3614
- JsonForms,
3615
- {
3616
- schema: vm.schema,
3617
- uischema: vm.uiSchema,
3618
- data: vm.data,
3619
- renderers: vm.renderers,
3620
- cells: vm.cells,
3621
- ajv: vm.ajv,
3622
- onChange: vm.onChange,
3623
- config: vm.config
3624
- }
3625
- ) });
3718
+ const formProps = {
3719
+ schema: vm.schema,
3720
+ data: vm.data,
3721
+ renderers: vm.renderers,
3722
+ cells: vm.cells,
3723
+ ajv: vm.ajv,
3724
+ onChange: vm.onChange
3725
+ };
3726
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-0 items-center gap-1", children: [
3727
+ vm.inlineUiSchema && /* @__PURE__ */ jsx("div", { className: "min-w-0 jsonforms-compact", children: /* @__PURE__ */ jsx(
3728
+ JsonForms,
3729
+ {
3730
+ ...formProps,
3731
+ uischema: vm.inlineUiSchema,
3732
+ config: vm.barConfig
3733
+ }
3734
+ ) }),
3735
+ vm.overflowUiSchema && /* @__PURE__ */ jsx(VariablesOverflowPanel, { count: vm.overflowCount, children: /* @__PURE__ */ jsx("div", { className: "jsonforms-compact", children: /* @__PURE__ */ jsx(
3736
+ JsonForms,
3737
+ {
3738
+ ...formProps,
3739
+ uischema: vm.overflowUiSchema,
3740
+ config: vm.panelConfig
3741
+ }
3742
+ ) }) })
3743
+ ] });
3626
3744
  });
3627
3745
 
3628
3746
  // src/domains/workspaces/queryKeys.ts
@@ -4094,7 +4212,7 @@ function MessageComposer(_props = {}) {
4094
4212
  disabled,
4095
4213
  placeholder: "What would you like to do?",
4096
4214
  className: "md-editor--bare px-5 pb-3 pt-4 text-sm",
4097
- minHeight: 48
4215
+ minHeight: 24
4098
4216
  },
4099
4217
  `composer-md-${editorKey}`
4100
4218
  ) }),
@@ -19173,7 +19291,7 @@ var buttonVariants = cva(
19173
19291
  }
19174
19292
  }
19175
19293
  );
19176
- var Button = React8.forwardRef(
19294
+ var Button = React9.forwardRef(
19177
19295
  ({ className, variant, size, asChild = false, ...props }, ref) => {
19178
19296
  const Comp = asChild ? Slot : "button";
19179
19297
  return /* @__PURE__ */ jsx(
@@ -19724,6 +19842,7 @@ function ChatMessageSources({
19724
19842
  ] })
19725
19843
  ] });
19726
19844
  }
19845
+ var USER_FORM_CONFIG = { surface: "form", minRows: 6, maxRows: 20 };
19727
19846
  var REVEAL_ON_HOVER = "opacity-100 focus-visible:opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover:opacity-100";
19728
19847
  var MessageBubble = (props) => {
19729
19848
  const {
@@ -19777,7 +19896,7 @@ var MessageBubble = (props) => {
19777
19896
  );
19778
19897
  })
19779
19898
  ] }),
19780
- showForm && /* @__PURE__ */ jsxs("div", { className: "mt-4 border-t border-border pt-4", children: [
19899
+ showForm && /* @__PURE__ */ jsxs("div", { className: "ss-chat-message__user-form mt-4 border-t border-border pt-4", children: [
19781
19900
  /* @__PURE__ */ jsx(
19782
19901
  JsonForms,
19783
19902
  {
@@ -19786,6 +19905,7 @@ var MessageBubble = (props) => {
19786
19905
  renderers,
19787
19906
  cells,
19788
19907
  readonly: userInput !== void 0,
19908
+ config: USER_FORM_CONFIG,
19789
19909
  onChange: ({ data, errors }) => {
19790
19910
  setResponseFormData(data);
19791
19911
  setResponseFormValid(!errors?.length);
@@ -19930,14 +20050,14 @@ var MessageItem = ({
19930
20050
  const { data: workspace } = useWorkspace(workspaceId);
19931
20051
  const chatbotName = getChatbotName(workspace?.name);
19932
20052
  const { addInputToMessageMutation } = useAddInputToMessage();
19933
- const onSubmitUserForm = (messageId) => (name, value) => {
20053
+ const onSubmitUserForm = (messageId, channels = {}) => (name, value) => {
19934
20054
  if (!threadId || !messageId) return;
19935
20055
  addInputToMessageMutation.mutate({
19936
20056
  threadId,
19937
20057
  messageId,
19938
20058
  name,
19939
20059
  value,
19940
- channels: {}
20060
+ channels
19941
20061
  });
19942
20062
  };
19943
20063
  const safeTime = (d) => {
@@ -20068,7 +20188,7 @@ var MessageItem = ({
20068
20188
  userOutput: v.value && typeof v.value === "object" ? v.value : null,
20069
20189
  chatbotName,
20070
20190
  userInput: userInput?.value,
20071
- onSubmitUserForm: onSubmitUserForm(message.id ?? "")
20191
+ onSubmitUserForm: onSubmitUserForm(message.id ?? "", v.channels)
20072
20192
  },
20073
20193
  `user-${message.id ?? "msg"}-${keyCounter++}`
20074
20194
  )
@@ -20264,6 +20384,7 @@ function MessageList({
20264
20384
  ] }) });
20265
20385
  }
20266
20386
  if (safeMessages.length === 0 && !hadMessagesBefore) {
20387
+ const descriptionParagraphs = (activeWorkspace?.summary ?? "").split(/\n+/).map((paragraph2) => paragraph2.trim()).filter(Boolean);
20267
20388
  return (
20268
20389
  // A new thread opens the way the design does: the greeting and the
20269
20390
  // composer travel together as one narrower block centred in the canvas.
@@ -20276,6 +20397,14 @@ function MessageList({
20276
20397
  "data-ss-layer": "message-list",
20277
20398
  children: /* @__PURE__ */ jsxs("div", { className: "mx-auto mb-6 w-full max-w-2xl px-4 text-center", children: [
20278
20399
  /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-foreground", children: "What\u2019s on the agenda today?" }),
20400
+ descriptionParagraphs.length > 0 && /* @__PURE__ */ jsx(
20401
+ "div",
20402
+ {
20403
+ className: "mx-auto mt-2 max-w-lg space-y-1.5 text-sm text-muted-foreground",
20404
+ "data-ss-layer": "workspace-description",
20405
+ children: descriptionParagraphs.map((paragraph2, index) => /* @__PURE__ */ jsx("p", { children: paragraph2 }, index))
20406
+ }
20407
+ ),
20279
20408
  activeWorkspace?.firstPrompt && /* @__PURE__ */ jsx("div", { className: "chat-prose mt-3 text-center", children: /* @__PURE__ */ jsx(MessageMarkdown, { value: activeWorkspace.firstPrompt }) })
20280
20409
  ] })
20281
20410
  }