@sero-ai/ui 0.4.2 → 0.6.0

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 (91) hide show
  1. package/.turbo/turbo-build.log +636 -628
  2. package/.turbo/turbo-test.log +12 -11
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/CHANGELOG.md +20 -2
  5. package/dist/components/ai-elements/conversation-virtual.cjs +115 -0
  6. package/dist/components/ai-elements/conversation-virtual.d.cts +30 -0
  7. package/dist/components/ai-elements/conversation-virtual.d.ts +30 -0
  8. package/dist/components/ai-elements/conversation-virtual.js +115 -0
  9. package/dist/components/model-selection/available-model-picker.cjs +21 -77
  10. package/dist/components/model-selection/available-model-picker.js +22 -78
  11. package/dist/components/model-selection/model-picker-body.cjs +106 -0
  12. package/dist/components/model-selection/model-picker-body.d.cts +41 -0
  13. package/dist/components/model-selection/model-picker-body.d.ts +41 -0
  14. package/dist/components/model-selection/model-picker-body.js +106 -0
  15. package/dist/components/model-selection/thinking-level-picker.cjs +7 -3
  16. package/dist/components/model-selection/thinking-level-picker.js +7 -3
  17. package/dist/components/model-selection/thinking-picker.cjs +1 -1
  18. package/dist/components/model-selection/thinking-picker.js +1 -1
  19. package/dist/components/ui/calendar.cjs +1 -1
  20. package/dist/components/ui/calendar.js +1 -1
  21. package/dist/components/ui/combobox.cjs +1 -1
  22. package/dist/components/ui/combobox.js +1 -1
  23. package/dist/components/ui/form.d.cts +2 -2
  24. package/dist/components/ui/form.d.ts +2 -2
  25. package/dist/components/ui/input-group.cjs +1 -1
  26. package/dist/components/ui/input-group.js +1 -1
  27. package/dist/components/ui/input-otp.cjs +1 -1
  28. package/dist/components/ui/input-otp.js +1 -1
  29. package/dist/components/ui/input.cjs +3 -3
  30. package/dist/components/ui/input.js +3 -3
  31. package/dist/components/ui/native-select.cjs +2 -2
  32. package/dist/components/ui/native-select.js +2 -2
  33. package/dist/components/ui/select.cjs +1 -1
  34. package/dist/components/ui/select.js +1 -1
  35. package/dist/components/ui/slider.cjs +6 -0
  36. package/dist/components/ui/slider.d.cts +1 -1
  37. package/dist/components/ui/slider.d.ts +1 -1
  38. package/dist/components/ui/slider.js +6 -0
  39. package/dist/components/ui/spinner.d.cts +2 -1
  40. package/dist/components/ui/spinner.d.ts +2 -1
  41. package/dist/components/ui/textarea.cjs +1 -1
  42. package/dist/components/ui/textarea.js +1 -1
  43. package/dist/index.cjs +0 -55
  44. package/dist/index.d.cts +2 -66
  45. package/dist/index.d.ts +2 -66
  46. package/dist/index.js +0 -55
  47. package/dist/reference.cjs +56 -83
  48. package/dist/reference.js +56 -83
  49. package/dist/styles/ai-elements.css +2 -0
  50. package/dist/styles/context-editor.css +2 -0
  51. package/dist/styles/globals.css +32 -0
  52. package/dist/styles/model-selection.css +2 -0
  53. package/dist/styles/plugin.css +4 -1
  54. package/dist/theme/apply-theme.cjs +65 -12
  55. package/dist/theme/apply-theme.js +55 -2
  56. package/dist/theme/index.d.cts +1 -1
  57. package/dist/theme/index.d.ts +1 -1
  58. package/dist/theme/types.cjs +15 -2
  59. package/dist/theme/types.d.cts +21 -1
  60. package/dist/theme/types.d.ts +21 -1
  61. package/dist/theme/types.js +14 -1
  62. package/package.json +44 -30
  63. package/scripts/smoke-dist.mjs +106 -4
  64. package/src/components/ai-elements/conversation-virtual.test.tsx +126 -0
  65. package/src/components/ai-elements/conversation-virtual.tsx +162 -0
  66. package/src/components/model-selection/available-model-picker.tsx +26 -98
  67. package/src/components/model-selection/model-picker-body.test.tsx +152 -0
  68. package/src/components/model-selection/model-picker-body.tsx +188 -0
  69. package/src/components/model-selection/thinking-level-picker.tsx +9 -3
  70. package/src/components/model-selection/thinking-picker.test.tsx +41 -0
  71. package/src/components/model-selection/thinking-picker.tsx +1 -1
  72. package/src/components/ui/calendar.test.tsx +43 -0
  73. package/src/components/ui/calendar.tsx +1 -1
  74. package/src/components/ui/combobox.tsx +1 -1
  75. package/src/components/ui/input-group.tsx +1 -1
  76. package/src/components/ui/input-otp.tsx +1 -1
  77. package/src/components/ui/input.tsx +3 -3
  78. package/src/components/ui/native-select.tsx +2 -2
  79. package/src/components/ui/select.tsx +1 -1
  80. package/src/components/ui/slider.tsx +6 -0
  81. package/src/components/ui/spinner.tsx +1 -1
  82. package/src/components/ui/textarea.tsx +1 -1
  83. package/src/index.ts +4 -63
  84. package/src/styles/ai-elements.css +2 -0
  85. package/src/styles/context-editor.css +2 -0
  86. package/src/styles/globals.css +32 -0
  87. package/src/styles/model-selection.css +2 -0
  88. package/src/styles/plugin.css +4 -1
  89. package/src/theme/apply-theme.test.ts +126 -0
  90. package/src/theme/apply-theme.ts +66 -3
  91. package/src/theme/types.ts +33 -0
@@ -1,15 +1,13 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useMemo, useRef, useState } from "react";
3
- import { Check, ChevronDown, Sparkles, X } from "lucide-react";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { ChevronDown, Sparkles, X } from "lucide-react";
4
4
  import {
5
- filterModelGroups,
6
5
  findGroup,
7
6
  findModel,
8
- modelKey,
9
7
  parseModelKey
10
8
  } from "@sero-ai/common";
11
9
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
12
- import { SearchInput } from "../ui/search-input";
10
+ import { ModelPickerBody, ProviderLogo } from "./model-picker-body";
13
11
  import { cn } from "../../lib/utils";
14
12
  function AvailableModelPicker({
15
13
  groups,
@@ -24,8 +22,6 @@ function AvailableModelPicker({
24
22
  className
25
23
  }) {
26
24
  const [open, setOpen] = useState(false);
27
- const [query, setQuery] = useState("");
28
- const inputRef = useRef(null);
29
25
  const selected = useMemo(() => {
30
26
  const parsed = parseModelKey(value);
31
27
  if (!parsed) {
@@ -39,32 +35,16 @@ function AvailableModelPicker({
39
35
  fallbackLabel: model ? null : value
40
36
  };
41
37
  }, [groups, value]);
42
- const filteredGroups = useMemo(
43
- () => filterModelGroups(groups, query),
44
- [groups, query]
45
- );
46
- const totalResults = useMemo(
47
- () => filteredGroups.reduce((count, group) => count + group.models.length, 0),
48
- [filteredGroups]
49
- );
50
- const handleOpenChange = useCallback((nextOpen) => {
51
- setOpen(nextOpen);
52
- if (!nextOpen) return;
53
- setQuery("");
54
- requestAnimationFrame(() => inputRef.current?.focus());
55
- }, []);
56
- const handleSelect = useCallback((provider, modelId) => {
57
- onChange(modelKey(provider, modelId));
38
+ const handleSelect = useCallback((nextValue) => {
39
+ onChange(nextValue);
58
40
  setOpen(false);
59
- setQuery("");
60
41
  }, [onChange]);
61
42
  const handleClear = useCallback((event) => {
62
43
  event.stopPropagation();
63
44
  onChange("");
64
45
  setOpen(false);
65
- setQuery("");
66
46
  }, [onChange]);
67
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
47
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
68
48
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
69
49
  "div",
70
50
  {
@@ -79,11 +59,11 @@ function AvailableModelPicker({
79
59
  children: [
80
60
  selected.group && selected.model ? /* @__PURE__ */ jsxs(Fragment, { children: [
81
61
  /* @__PURE__ */ jsx(
82
- "img",
62
+ ProviderLogo,
83
63
  {
84
- src: selected.group.logo,
85
- alt: selected.group.displayName,
86
- className: "size-4 shrink-0 rounded-sm dark:invert"
64
+ logo: selected.group.logo,
65
+ displayName: selected.group.displayName,
66
+ className: "size-4"
87
67
  }
88
68
  ),
89
69
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
@@ -108,7 +88,7 @@ function AvailableModelPicker({
108
88
  ]
109
89
  }
110
90
  ) }),
111
- /* @__PURE__ */ jsxs(
91
+ /* @__PURE__ */ jsx(
112
92
  PopoverContent,
113
93
  {
114
94
  side: "bottom",
@@ -116,53 +96,17 @@ function AvailableModelPicker({
116
96
  sideOffset: 4,
117
97
  className: "w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-xl border-border/60 bg-background p-0 shadow-xl",
118
98
  onWheel: (event) => event.stopPropagation(),
119
- children: [
120
- /* @__PURE__ */ jsx(
121
- SearchInput,
122
- {
123
- ref: inputRef,
124
- value: query,
125
- onChange: (event) => setQuery(event.target.value),
126
- placeholder: searchPlaceholder,
127
- containerClassName: "border-b border-border/40"
128
- }
129
- ),
130
- /* @__PURE__ */ jsx("div", { className: "max-h-[280px] overflow-y-auto py-1", children: groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: noModelsLabel }) : totalResults === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyLabel }) : filteredGroups.map((group, index) => /* @__PURE__ */ jsxs("div", { children: [
131
- index > 0 ? /* @__PURE__ */ jsx("div", { className: "mx-3 border-t border-border/30" }) : null,
132
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
133
- /* @__PURE__ */ jsx(
134
- "img",
135
- {
136
- src: group.logo,
137
- alt: group.displayName,
138
- className: "size-3.5 shrink-0 rounded-sm dark:invert"
139
- }
140
- ),
141
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
142
- ] }),
143
- /* @__PURE__ */ jsx("div", { className: "px-1", children: group.models.map((model) => {
144
- const nextValue = modelKey(model.provider, model.modelId);
145
- const isSelected = nextValue === value;
146
- return /* @__PURE__ */ jsxs(
147
- "button",
148
- {
149
- type: "button",
150
- onClick: () => handleSelect(model.provider, model.modelId),
151
- className: cn(
152
- "flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors",
153
- isSelected ? "bg-secondary text-foreground" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
154
- ),
155
- children: [
156
- /* @__PURE__ */ jsx("div", { className: "flex size-4 shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsx("div", { className: "size-1.5 rounded-full bg-border" }) }),
157
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
158
- model.reasoning ? /* @__PURE__ */ jsx(Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
159
- ]
160
- },
161
- nextValue
162
- );
163
- }) })
164
- ] }, group.provider)) })
165
- ]
99
+ children: /* @__PURE__ */ jsx(
100
+ ModelPickerBody,
101
+ {
102
+ groups,
103
+ value,
104
+ onChange: handleSelect,
105
+ searchPlaceholder,
106
+ emptyLabel,
107
+ noModelsLabel
108
+ }
109
+ )
166
110
  }
167
111
  )
168
112
  ] });
@@ -0,0 +1,106 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _jsxruntime = require('react/jsx-runtime');
2
+ var _react = require('react');
3
+ var _lucidereact = require('lucide-react');
4
+
5
+
6
+
7
+ var _common = require('@sero-ai/common');
8
+ var _searchinput = require('../ui/search-input');
9
+ var _utils = require('../../lib/utils');
10
+ function ProviderLogo({
11
+ logo,
12
+ displayName,
13
+ className
14
+ }) {
15
+ const [failed, setFailed] = _react.useState.call(void 0, false);
16
+ _react.useEffect.call(void 0, () => setFailed(false), [logo]);
17
+ if (!logo || failed) return null;
18
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
19
+ "img",
20
+ {
21
+ src: logo,
22
+ alt: displayName,
23
+ onError: () => setFailed(true),
24
+ className: _utils.cn.call(void 0, "size-3.5 shrink-0 rounded-sm dark:invert", className)
25
+ }
26
+ );
27
+ }
28
+ function ModelPickerBody({
29
+ groups,
30
+ value,
31
+ onChange,
32
+ searchPlaceholder = "Search models...",
33
+ emptyLabel = "No matching models",
34
+ noModelsLabel = "No models available",
35
+ autoFocusSearch = true,
36
+ showProviderLogos = true,
37
+ searchEndAdornment,
38
+ className,
39
+ listClassName
40
+ }) {
41
+ const [query, setQuery] = _react.useState.call(void 0, "");
42
+ const inputRef = _react.useRef.call(void 0, null);
43
+ _react.useEffect.call(void 0, () => {
44
+ if (!autoFocusSearch) return;
45
+ const frame = requestAnimationFrame(() => _optionalChain([inputRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.focus, 'call', _3 => _3()]));
46
+ return () => cancelAnimationFrame(frame);
47
+ }, [autoFocusSearch]);
48
+ const filteredGroups = _react.useMemo.call(void 0, () => _common.filterModelGroups.call(void 0, groups, query), [groups, query]);
49
+ const totalResults = _react.useMemo.call(void 0,
50
+ () => filteredGroups.reduce((count, group) => count + group.models.length, 0),
51
+ [filteredGroups]
52
+ );
53
+ const handleSelect = _react.useCallback.call(void 0,
54
+ (provider, modelId) => {
55
+ onChange(_common.modelKey.call(void 0, provider, modelId));
56
+ setQuery("");
57
+ },
58
+ [onChange]
59
+ );
60
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className, children: [
61
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
62
+ _searchinput.SearchInput,
63
+ {
64
+ ref: inputRef,
65
+ value: query,
66
+ onChange: (event) => setQuery(event.target.value),
67
+ placeholder: searchPlaceholder,
68
+ "data-slot": "model-filter",
69
+ containerClassName: "border-b border-border/40",
70
+ endAdornment: searchEndAdornment
71
+ }
72
+ ),
73
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _utils.cn.call(void 0, "max-h-[280px] overflow-y-auto py-1", listClassName), children: groups.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: noModelsLabel }) : totalResults === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyLabel }) : filteredGroups.map((group, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
74
+ index > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mx-3 border-t border-border/30" }) : null,
75
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
76
+ showProviderLogos ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ProviderLogo, { logo: group.logo, displayName: group.displayName }) : null,
77
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
78
+ ] }),
79
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-1", children: group.models.map((model) => {
80
+ const nextValue = _common.modelKey.call(void 0, model.provider, model.modelId);
81
+ const isSelected = nextValue === value;
82
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
83
+ "button",
84
+ {
85
+ type: "button",
86
+ onClick: () => handleSelect(model.provider, model.modelId),
87
+ className: _utils.cn.call(void 0,
88
+ "flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors",
89
+ isSelected ? "bg-secondary text-foreground" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
90
+ ),
91
+ children: [
92
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex size-4 shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "size-1.5 rounded-full bg-border" }) }),
93
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
94
+ model.reasoning ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
95
+ ]
96
+ },
97
+ nextValue
98
+ );
99
+ }) })
100
+ ] }, group.provider)) })
101
+ ] });
102
+ }
103
+
104
+
105
+
106
+ exports.ModelPickerBody = ModelPickerBody; exports.ProviderLogo = ProviderLogo;
@@ -0,0 +1,41 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { SharedModelInfo, SharedAvailableModelGroup } from '@sero-ai/common';
4
+
5
+ /**
6
+ * A provider logo.
7
+ *
8
+ * `logo` is a remote URL, so it is empty for some providers and it fails
9
+ * on a machine with no internet. Either way we show nothing rather than
10
+ * a broken image.
11
+ */
12
+ declare function ProviderLogo({ logo, displayName, className, }: {
13
+ logo: string;
14
+ displayName: string;
15
+ className?: string;
16
+ }): react_jsx_runtime.JSX.Element | null;
17
+ interface ModelPickerBodyProps<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>> {
18
+ groups: TGroup[];
19
+ /** The selected model as `provider/modelId`, or '' for none. */
20
+ value: string;
21
+ /** Called with the new `provider/modelId`. */
22
+ onChange: (value: string) => void;
23
+ searchPlaceholder?: string;
24
+ emptyLabel?: string;
25
+ noModelsLabel?: string;
26
+ /**
27
+ * Focus the search field on mount. Turn this off on a phone: the
28
+ * on-screen keyboard would cover the list the user came to read.
29
+ */
30
+ autoFocusSearch?: boolean;
31
+ /** Hide provider logos where the network may not reach them. */
32
+ showProviderLogos?: boolean;
33
+ /** Shown at the right of the search field, such as a settings button. */
34
+ searchEndAdornment?: ReactNode;
35
+ className?: string;
36
+ /** Applied to the scrolling list, so a caller sets its height. */
37
+ listClassName?: string;
38
+ }
39
+ declare function ModelPickerBody<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>>({ groups, value, onChange, searchPlaceholder, emptyLabel, noModelsLabel, autoFocusSearch, showProviderLogos, searchEndAdornment, className, listClassName, }: ModelPickerBodyProps<TModel, TGroup>): react_jsx_runtime.JSX.Element;
40
+
41
+ export { ModelPickerBody, ProviderLogo };
@@ -0,0 +1,41 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { SharedModelInfo, SharedAvailableModelGroup } from '@sero-ai/common';
4
+
5
+ /**
6
+ * A provider logo.
7
+ *
8
+ * `logo` is a remote URL, so it is empty for some providers and it fails
9
+ * on a machine with no internet. Either way we show nothing rather than
10
+ * a broken image.
11
+ */
12
+ declare function ProviderLogo({ logo, displayName, className, }: {
13
+ logo: string;
14
+ displayName: string;
15
+ className?: string;
16
+ }): react_jsx_runtime.JSX.Element | null;
17
+ interface ModelPickerBodyProps<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>> {
18
+ groups: TGroup[];
19
+ /** The selected model as `provider/modelId`, or '' for none. */
20
+ value: string;
21
+ /** Called with the new `provider/modelId`. */
22
+ onChange: (value: string) => void;
23
+ searchPlaceholder?: string;
24
+ emptyLabel?: string;
25
+ noModelsLabel?: string;
26
+ /**
27
+ * Focus the search field on mount. Turn this off on a phone: the
28
+ * on-screen keyboard would cover the list the user came to read.
29
+ */
30
+ autoFocusSearch?: boolean;
31
+ /** Hide provider logos where the network may not reach them. */
32
+ showProviderLogos?: boolean;
33
+ /** Shown at the right of the search field, such as a settings button. */
34
+ searchEndAdornment?: ReactNode;
35
+ className?: string;
36
+ /** Applied to the scrolling list, so a caller sets its height. */
37
+ listClassName?: string;
38
+ }
39
+ declare function ModelPickerBody<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>>({ groups, value, onChange, searchPlaceholder, emptyLabel, noModelsLabel, autoFocusSearch, showProviderLogos, searchEndAdornment, className, listClassName, }: ModelPickerBodyProps<TModel, TGroup>): react_jsx_runtime.JSX.Element;
40
+
41
+ export { ModelPickerBody, ProviderLogo };
@@ -0,0 +1,106 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { Check, Sparkles } from "lucide-react";
4
+ import {
5
+ filterModelGroups,
6
+ modelKey
7
+ } from "@sero-ai/common";
8
+ import { SearchInput } from "../ui/search-input";
9
+ import { cn } from "../../lib/utils";
10
+ function ProviderLogo({
11
+ logo,
12
+ displayName,
13
+ className
14
+ }) {
15
+ const [failed, setFailed] = useState(false);
16
+ useEffect(() => setFailed(false), [logo]);
17
+ if (!logo || failed) return null;
18
+ return /* @__PURE__ */ jsx(
19
+ "img",
20
+ {
21
+ src: logo,
22
+ alt: displayName,
23
+ onError: () => setFailed(true),
24
+ className: cn("size-3.5 shrink-0 rounded-sm dark:invert", className)
25
+ }
26
+ );
27
+ }
28
+ function ModelPickerBody({
29
+ groups,
30
+ value,
31
+ onChange,
32
+ searchPlaceholder = "Search models...",
33
+ emptyLabel = "No matching models",
34
+ noModelsLabel = "No models available",
35
+ autoFocusSearch = true,
36
+ showProviderLogos = true,
37
+ searchEndAdornment,
38
+ className,
39
+ listClassName
40
+ }) {
41
+ const [query, setQuery] = useState("");
42
+ const inputRef = useRef(null);
43
+ useEffect(() => {
44
+ if (!autoFocusSearch) return;
45
+ const frame = requestAnimationFrame(() => inputRef.current?.focus());
46
+ return () => cancelAnimationFrame(frame);
47
+ }, [autoFocusSearch]);
48
+ const filteredGroups = useMemo(() => filterModelGroups(groups, query), [groups, query]);
49
+ const totalResults = useMemo(
50
+ () => filteredGroups.reduce((count, group) => count + group.models.length, 0),
51
+ [filteredGroups]
52
+ );
53
+ const handleSelect = useCallback(
54
+ (provider, modelId) => {
55
+ onChange(modelKey(provider, modelId));
56
+ setQuery("");
57
+ },
58
+ [onChange]
59
+ );
60
+ return /* @__PURE__ */ jsxs("div", { className, children: [
61
+ /* @__PURE__ */ jsx(
62
+ SearchInput,
63
+ {
64
+ ref: inputRef,
65
+ value: query,
66
+ onChange: (event) => setQuery(event.target.value),
67
+ placeholder: searchPlaceholder,
68
+ "data-slot": "model-filter",
69
+ containerClassName: "border-b border-border/40",
70
+ endAdornment: searchEndAdornment
71
+ }
72
+ ),
73
+ /* @__PURE__ */ jsx("div", { className: cn("max-h-[280px] overflow-y-auto py-1", listClassName), children: groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: noModelsLabel }) : totalResults === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyLabel }) : filteredGroups.map((group, index) => /* @__PURE__ */ jsxs("div", { children: [
74
+ index > 0 ? /* @__PURE__ */ jsx("div", { className: "mx-3 border-t border-border/30" }) : null,
75
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
76
+ showProviderLogos ? /* @__PURE__ */ jsx(ProviderLogo, { logo: group.logo, displayName: group.displayName }) : null,
77
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
78
+ ] }),
79
+ /* @__PURE__ */ jsx("div", { className: "px-1", children: group.models.map((model) => {
80
+ const nextValue = modelKey(model.provider, model.modelId);
81
+ const isSelected = nextValue === value;
82
+ return /* @__PURE__ */ jsxs(
83
+ "button",
84
+ {
85
+ type: "button",
86
+ onClick: () => handleSelect(model.provider, model.modelId),
87
+ className: cn(
88
+ "flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors",
89
+ isSelected ? "bg-secondary text-foreground" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
90
+ ),
91
+ children: [
92
+ /* @__PURE__ */ jsx("div", { className: "flex size-4 shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsx("div", { className: "size-1.5 rounded-full bg-border" }) }),
93
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
94
+ model.reasoning ? /* @__PURE__ */ jsx(Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
95
+ ]
96
+ },
97
+ nextValue
98
+ );
99
+ }) })
100
+ ] }, group.provider)) })
101
+ ] });
102
+ }
103
+ export {
104
+ ModelPickerBody,
105
+ ProviderLogo
106
+ };
@@ -4,6 +4,10 @@
4
4
 
5
5
  var _common = require('@sero-ai/common');
6
6
  var _utils = require('../../lib/utils');
7
+ const COMPACT_LABELS = {
8
+ ..._common.THINKING_LABELS,
9
+ xhigh: "X-High"
10
+ };
7
11
  function ThinkingLevelPicker({
8
12
  value,
9
13
  onChange,
@@ -14,7 +18,7 @@ function ThinkingLevelPicker({
14
18
  }) {
15
19
  const allowed = new Set(_nullishCoalesce(availableLevels, () => ( _common.THINKING_LEVELS)));
16
20
  const levels = showUnsupported || !availableLevels ? _common.THINKING_LEVELS : _common.THINKING_LEVELS.filter((level) => allowed.has(level));
17
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _utils.cn.call(void 0, "grid grid-cols-6 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1", className), children: levels.map((level) => {
21
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _utils.cn.call(void 0, "grid grid-cols-7 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1", className), children: levels.map((level) => {
18
22
  const isActive = value === level;
19
23
  const isAvailable = allowed.has(level) || !availableLevels;
20
24
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -24,11 +28,11 @@ function ThinkingLevelPicker({
24
28
  disabled: disabled || !isAvailable,
25
29
  onClick: () => onChange(level),
26
30
  className: _utils.cn.call(void 0,
27
- "rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
31
+ "whitespace-nowrap rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
28
32
  isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:bg-background/70 hover:text-foreground",
29
33
  (!isAvailable || disabled) && "cursor-not-allowed opacity-40 hover:bg-transparent hover:text-muted-foreground"
30
34
  ),
31
- children: _common.THINKING_LABELS[level]
35
+ children: COMPACT_LABELS[level]
32
36
  },
33
37
  level
34
38
  );
@@ -4,6 +4,10 @@ import {
4
4
  THINKING_LEVELS
5
5
  } from "@sero-ai/common";
6
6
  import { cn } from "../../lib/utils";
7
+ const COMPACT_LABELS = {
8
+ ...THINKING_LABELS,
9
+ xhigh: "X-High"
10
+ };
7
11
  function ThinkingLevelPicker({
8
12
  value,
9
13
  onChange,
@@ -14,7 +18,7 @@ function ThinkingLevelPicker({
14
18
  }) {
15
19
  const allowed = new Set(availableLevels ?? THINKING_LEVELS);
16
20
  const levels = showUnsupported || !availableLevels ? THINKING_LEVELS : THINKING_LEVELS.filter((level) => allowed.has(level));
17
- return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-6 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1", className), children: levels.map((level) => {
21
+ return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-7 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1", className), children: levels.map((level) => {
18
22
  const isActive = value === level;
19
23
  const isAvailable = allowed.has(level) || !availableLevels;
20
24
  return /* @__PURE__ */ jsx(
@@ -24,11 +28,11 @@ function ThinkingLevelPicker({
24
28
  disabled: disabled || !isAvailable,
25
29
  onClick: () => onChange(level),
26
30
  className: cn(
27
- "rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
31
+ "whitespace-nowrap rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
28
32
  isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:bg-background/70 hover:text-foreground",
29
33
  (!isAvailable || disabled) && "cursor-not-allowed opacity-40 hover:bg-transparent hover:text-muted-foreground"
30
34
  ),
31
- children: THINKING_LABELS[level]
35
+ children: COMPACT_LABELS[level]
32
36
  },
33
37
  level
34
38
  );
@@ -14,7 +14,7 @@ function ThinkingPicker({
14
14
  onSelect,
15
15
  className
16
16
  }) {
17
- const levels = disabled ? _common.THINKING_LEVELS : _common.THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
17
+ const levels = disabled ? _common.THINKING_LEVELS.filter((level) => level === "off") : _common.THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
18
18
  const activeIndex = Math.max(
19
19
  0,
20
20
  levels.indexOf(disabled ? "off" : current)
@@ -14,7 +14,7 @@ function ThinkingPicker({
14
14
  onSelect,
15
15
  className
16
16
  }) {
17
- const levels = disabled ? THINKING_LEVELS : THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
17
+ const levels = disabled ? THINKING_LEVELS.filter((level) => level === "off") : THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
18
18
  const activeIndex = Math.max(
19
19
  0,
20
20
  levels.indexOf(disabled ? "off" : current)
@@ -79,7 +79,7 @@ function Calendar({
79
79
  captionLayout === "label" ? "text-base" : "rounded-md pl-2 pr-1 flex items-center gap-1 text-base h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
80
80
  defaultClassNames.caption_label
81
81
  ),
82
- table: "w-full border-collapse",
82
+ month_grid: _utils.cn.call(void 0, "w-full border-collapse", defaultClassNames.month_grid),
83
83
  weekdays: _utils.cn.call(void 0, "flex", defaultClassNames.weekdays),
84
84
  weekday: _utils.cn.call(void 0,
85
85
  "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
@@ -79,7 +79,7 @@ function Calendar({
79
79
  captionLayout === "label" ? "text-base" : "rounded-md pl-2 pr-1 flex items-center gap-1 text-base h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
80
80
  defaultClassNames.caption_label
81
81
  ),
82
- table: "w-full border-collapse",
82
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
83
83
  weekdays: cn("flex", defaultClassNames.weekdays),
84
84
  weekday: cn(
85
85
  "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
@@ -225,7 +225,7 @@ function ComboboxChips({
225
225
  {
226
226
  "data-slot": "combobox-chips",
227
227
  className: _utils.cn.call(void 0,
228
- "dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-base shadow-xs transition-[color,box-shadow] focus-within:ring-[3px] has-aria-invalid:ring-[3px] has-data-[slot=combobox-chip]:px-1.5",
228
+ "dark:bg-input/30 border-input focus-within:border-ring has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-base shadow-xs transition-[color,box-shadow] has-data-[slot=combobox-chip]:px-1.5",
229
229
  className
230
230
  ),
231
231
  ...props
@@ -225,7 +225,7 @@ function ComboboxChips({
225
225
  {
226
226
  "data-slot": "combobox-chips",
227
227
  className: cn(
228
- "dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-base shadow-xs transition-[color,box-shadow] focus-within:ring-[3px] has-aria-invalid:ring-[3px] has-data-[slot=combobox-chip]:px-1.5",
228
+ "dark:bg-input/30 border-input focus-within:border-ring has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-base shadow-xs transition-[color,box-shadow] has-data-[slot=combobox-chip]:px-1.5",
229
229
  className
230
230
  ),
231
231
  ...props
@@ -4,14 +4,14 @@ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
4
4
  import * as React from 'react';
5
5
  import { Slot, Label } from 'radix-ui';
6
6
 
7
- declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>(props: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
7
+ declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
8
8
  declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => react_jsx_runtime.JSX.Element;
9
9
  declare const useFormField: () => {
10
10
  invalid: boolean;
11
11
  isDirty: boolean;
12
12
  isTouched: boolean;
13
13
  isValidating: boolean;
14
- error?: react_hook_form.FieldError;
14
+ error?: react_hook_form.FieldError | undefined;
15
15
  id: string;
16
16
  name: string;
17
17
  formItemId: string;
@@ -4,14 +4,14 @@ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
4
4
  import * as React from 'react';
5
5
  import { Slot, Label } from 'radix-ui';
6
6
 
7
- declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>(props: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
7
+ declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
8
8
  declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => react_jsx_runtime.JSX.Element;
9
9
  declare const useFormField: () => {
10
10
  invalid: boolean;
11
11
  isDirty: boolean;
12
12
  isTouched: boolean;
13
13
  isValidating: boolean;
14
- error?: react_hook_form.FieldError;
14
+ error?: react_hook_form.FieldError | undefined;
15
15
  id: string;
16
16
  name: string;
17
17
  formItemId: string;
@@ -19,7 +19,7 @@ function InputGroup({ className, ...props }) {
19
19
  "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
20
20
  "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
21
21
  // Focus state.
22
- "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
22
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring",
23
23
  // Error state.
24
24
  "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
25
25
  className
@@ -19,7 +19,7 @@ function InputGroup({ className, ...props }) {
19
19
  "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
20
20
  "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
21
21
  // Focus state.
22
- "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
22
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring",
23
23
  // Error state.
24
24
  "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
25
25
  className