@sero-ai/ui 0.5.0 → 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 (77) hide show
  1. package/.turbo/turbo-build.log +386 -378
  2. package/.turbo/turbo-test.log +12 -11
  3. package/CHANGELOG.md +2 -2
  4. package/dist/components/ai-elements/conversation-virtual.cjs +115 -0
  5. package/dist/components/ai-elements/conversation-virtual.d.cts +30 -0
  6. package/dist/components/ai-elements/conversation-virtual.d.ts +30 -0
  7. package/dist/components/ai-elements/conversation-virtual.js +115 -0
  8. package/dist/components/model-selection/available-model-picker.cjs +21 -77
  9. package/dist/components/model-selection/available-model-picker.js +22 -78
  10. package/dist/components/model-selection/model-picker-body.cjs +106 -0
  11. package/dist/components/model-selection/model-picker-body.d.cts +41 -0
  12. package/dist/components/model-selection/model-picker-body.d.ts +41 -0
  13. package/dist/components/model-selection/model-picker-body.js +106 -0
  14. package/dist/components/model-selection/thinking-level-picker.cjs +7 -3
  15. package/dist/components/model-selection/thinking-level-picker.js +7 -3
  16. package/dist/components/model-selection/thinking-picker.cjs +1 -1
  17. package/dist/components/model-selection/thinking-picker.js +1 -1
  18. package/dist/components/ui/calendar.cjs +1 -1
  19. package/dist/components/ui/calendar.js +1 -1
  20. package/dist/components/ui/combobox.cjs +1 -1
  21. package/dist/components/ui/combobox.js +1 -1
  22. package/dist/components/ui/form.d.cts +1 -1
  23. package/dist/components/ui/form.d.ts +1 -1
  24. package/dist/components/ui/input-group.cjs +1 -1
  25. package/dist/components/ui/input-group.js +1 -1
  26. package/dist/components/ui/input-otp.cjs +1 -1
  27. package/dist/components/ui/input-otp.js +1 -1
  28. package/dist/components/ui/input.cjs +3 -3
  29. package/dist/components/ui/input.js +3 -3
  30. package/dist/components/ui/native-select.cjs +2 -2
  31. package/dist/components/ui/native-select.js +2 -2
  32. package/dist/components/ui/select.cjs +1 -1
  33. package/dist/components/ui/select.js +1 -1
  34. package/dist/components/ui/slider.cjs +6 -0
  35. package/dist/components/ui/slider.d.cts +1 -1
  36. package/dist/components/ui/slider.d.ts +1 -1
  37. package/dist/components/ui/slider.js +6 -0
  38. package/dist/components/ui/spinner.d.cts +2 -1
  39. package/dist/components/ui/spinner.d.ts +2 -1
  40. package/dist/components/ui/textarea.cjs +1 -1
  41. package/dist/components/ui/textarea.js +1 -1
  42. package/dist/index.d.cts +2 -1
  43. package/dist/index.d.ts +2 -1
  44. package/dist/styles/globals.css +32 -0
  45. package/dist/theme/apply-theme.cjs +65 -12
  46. package/dist/theme/apply-theme.js +55 -2
  47. package/dist/theme/index.d.cts +1 -1
  48. package/dist/theme/index.d.ts +1 -1
  49. package/dist/theme/types.cjs +15 -2
  50. package/dist/theme/types.d.cts +21 -1
  51. package/dist/theme/types.d.ts +21 -1
  52. package/dist/theme/types.js +14 -1
  53. package/package.json +21 -20
  54. package/scripts/smoke-dist.mjs +2 -2
  55. package/src/components/ai-elements/conversation-virtual.test.tsx +126 -0
  56. package/src/components/ai-elements/conversation-virtual.tsx +162 -0
  57. package/src/components/model-selection/available-model-picker.tsx +26 -98
  58. package/src/components/model-selection/model-picker-body.test.tsx +152 -0
  59. package/src/components/model-selection/model-picker-body.tsx +188 -0
  60. package/src/components/model-selection/thinking-level-picker.tsx +9 -3
  61. package/src/components/model-selection/thinking-picker.test.tsx +41 -0
  62. package/src/components/model-selection/thinking-picker.tsx +1 -1
  63. package/src/components/ui/calendar.test.tsx +43 -0
  64. package/src/components/ui/calendar.tsx +1 -1
  65. package/src/components/ui/combobox.tsx +1 -1
  66. package/src/components/ui/input-group.tsx +1 -1
  67. package/src/components/ui/input-otp.tsx +1 -1
  68. package/src/components/ui/input.tsx +3 -3
  69. package/src/components/ui/native-select.tsx +2 -2
  70. package/src/components/ui/select.tsx +1 -1
  71. package/src/components/ui/slider.tsx +6 -0
  72. package/src/components/ui/spinner.tsx +1 -1
  73. package/src/components/ui/textarea.tsx +1 -1
  74. package/src/styles/globals.css +32 -0
  75. package/src/theme/apply-theme.test.ts +126 -0
  76. package/src/theme/apply-theme.ts +66 -3
  77. package/src/theme/types.ts +33 -0
@@ -1,18 +1,19 @@
1
1
 
2
- > @sero-ai/ui@0.4.2 test /Users/danielcarter/Documents/Dev/projects/sero/sero/packages/ui
2
+ > @sero-ai/ui@0.5.0 test /Users/danielcarter/Documents/Dev/projects/sero/sero/packages/ui
3
3
  > vitest run
4
4
 
5
5
 
6
-  RUN  v4.1.7 /Users/danielcarter/Documents/Dev/projects/sero/sero/packages/ui
6
+ RUN v4.1.10 /Users/danielcarter/Documents/Dev/projects/sero/sero/packages/ui
7
7
 
8
- ✓ src/plugin-style-scope.test.tsx (1 test) 54ms
9
- ✓ src/components/dashboard/__tests__/catalog.test.ts (5 tests) 5ms
10
- ✓ src/components/dashboard/__tests__/glass.test.tsx (4 tests) 47ms
11
- ✓ src/components/dashboard/__tests__/state.test.tsx (14 tests) 58ms
12
- ✓ src/components/dashboard/__tests__/variants.test.tsx (13 tests) 161ms
8
+ ✓ src/plugin-style-scope.test.tsx (1 test) 19ms
9
+ ✓ src/components/dashboard/__tests__/variants.test.tsx (13 tests) 36ms
10
+ ✓ src/components/dashboard/__tests__/glass.test.tsx (4 tests) 11ms
11
+ ✓ src/components/dashboard/__tests__/state.test.tsx (14 tests) 64ms
12
+ ✓ src/components/dashboard/__tests__/catalog.test.ts (5 tests) 7ms
13
+ ✓ src/components/model-selection/thinking-picker.test.tsx (2 tests) 32ms
13
14
 
14
-  Test Files  5 passed (5)
15
-  Tests  37 passed (37)
16
-  Start at  20:23:20
17
-  Duration  3.62s (transform 702ms, setup 0ms, import 4.83s, tests 325ms, environment 8.29s)
15
+ Test Files 6 passed (6)
16
+ Tests 39 passed (39)
17
+ Start at 22:43:26
18
+ Duration 1.83s (transform 862ms, setup 0ms, import 3.20s, tests 169ms, environment 5.28s)
18
19
 
package/CHANGELOG.md CHANGED
@@ -72,5 +72,5 @@
72
72
  worked examples, importable to preview or copy-paste. (Replaces the removed
73
73
  `sero-showcase-plugin`.)
74
74
 
75
- See `docs/features/dashboard-ui/dashboard-widgets-plan.md` and
76
- `glass-restyle-spec.md` for the design and authoring guidance.
75
+ See `apps/docs-site/docs/guide/dashboard-widgets.md` and
76
+ `apps/docs-site/docs/reference/dashboard-components.md` for authoring guidance.
@@ -0,0 +1,115 @@
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; }"use client";
2
+ var _jsxruntime = require('react/jsx-runtime');
3
+ var _react = require('react');
4
+ var _reactvirtual = require('@tanstack/react-virtual');
5
+ var _usesticktobottom = require('use-stick-to-bottom');
6
+ var _utils = require('../../lib/utils');
7
+ function ConversationVirtualList({
8
+ items,
9
+ getItemKey,
10
+ renderItem,
11
+ onReachStart,
12
+ startThreshold = 240,
13
+ estimateSize = 96,
14
+ initialScrollToEnd = false,
15
+ overscan = 6,
16
+ rowClassName,
17
+ className
18
+ }) {
19
+ const { scrollRef, state: scrollState } = _usesticktobottom.useStickToBottomContext.call(void 0, );
20
+ const [initialPositioned, setInitialPositioned] = _react.useState.call(void 0, !initialScrollToEnd);
21
+ const [scrollElement, setScrollElement] = _react.useState.call(void 0, null);
22
+ _react.useEffect.call(void 0, () => {
23
+ setScrollElement(scrollRef.current);
24
+ }, [scrollRef]);
25
+ const virtualizer = _reactvirtual.useVirtualizer.call(void 0, {
26
+ count: items.length,
27
+ getScrollElement: () => scrollElement,
28
+ estimateSize: () => estimateSize,
29
+ overscan,
30
+ getItemKey: (index) => getItemKey(items[index], index)
31
+ });
32
+ _react.useEffect.call(void 0, () => {
33
+ if (initialPositioned) return;
34
+ const timeout = setTimeout(() => setInitialPositioned(true), 250);
35
+ return () => clearTimeout(timeout);
36
+ }, [initialPositioned]);
37
+ _react.useEffect.call(void 0, () => {
38
+ if (initialPositioned || !scrollElement || items.length === 0) return;
39
+ let frame;
40
+ let previousHeight = -1;
41
+ let stableFrames = 0;
42
+ const position = () => {
43
+ const height = scrollElement.scrollHeight;
44
+ const target = Math.max(0, scrollState.calculatedTargetScrollTop);
45
+ const lastRow = virtualizer.getVirtualItems().at(-1);
46
+ const settled = _optionalChain([lastRow, 'optionalAccess', _ => _.index]) === items.length - 1 && Math.abs(scrollElement.scrollTop - target) <= 1 && height === previousHeight;
47
+ stableFrames = settled ? stableFrames + 1 : 0;
48
+ previousHeight = height;
49
+ scrollState.scrollTop = target;
50
+ if (stableFrames >= 2) {
51
+ setInitialPositioned(true);
52
+ return;
53
+ }
54
+ frame = requestAnimationFrame(position);
55
+ };
56
+ frame = requestAnimationFrame(position);
57
+ return () => cancelAnimationFrame(frame);
58
+ }, [initialPositioned, items.length, scrollElement, scrollState, virtualizer]);
59
+ const firstKeyRef = _react.useRef.call(void 0, null);
60
+ const totalSizeRef = _react.useRef.call(void 0, 0);
61
+ const firstKey = items.length > 0 ? getItemKey(items[0], 0) : null;
62
+ _react.useLayoutEffect.call(void 0, () => {
63
+ const previousFirstKey = firstKeyRef.current;
64
+ const previousTotal = totalSizeRef.current;
65
+ firstKeyRef.current = firstKey;
66
+ totalSizeRef.current = virtualizer.getTotalSize();
67
+ if (previousFirstKey === null || previousFirstKey === firstKey) return;
68
+ const previousFirstIndex = items.findIndex((item, index) => getItemKey(item, index) === previousFirstKey);
69
+ if (previousFirstIndex <= 0) return;
70
+ const scrollElement2 = scrollRef.current;
71
+ if (!scrollElement2) return;
72
+ scrollElement2.scrollTop += totalSizeRef.current - previousTotal;
73
+ });
74
+ const onReachStartRef = _react.useRef.call(void 0, onReachStart);
75
+ _react.useLayoutEffect.call(void 0, () => {
76
+ onReachStartRef.current = onReachStart;
77
+ }, [onReachStart]);
78
+ _react.useEffect.call(void 0, () => {
79
+ const scrollElement2 = scrollRef.current;
80
+ if (!scrollElement2) return;
81
+ const handleScroll = () => {
82
+ if (scrollElement2.scrollTop <= startThreshold) _optionalChain([onReachStartRef, 'access', _2 => _2.current, 'optionalCall', _3 => _3()]);
83
+ };
84
+ scrollElement2.addEventListener("scroll", handleScroll, { passive: true });
85
+ return () => scrollElement2.removeEventListener("scroll", handleScroll);
86
+ }, [scrollRef, startThreshold]);
87
+ const measureRow = _react.useCallback.call(void 0,
88
+ (node) => virtualizer.measureElement(node),
89
+ [virtualizer]
90
+ );
91
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
92
+ "div",
93
+ {
94
+ className: _utils.cn.call(void 0, "relative w-full", className),
95
+ style: { height: virtualizer.getTotalSize(), visibility: initialPositioned ? void 0 : "hidden" },
96
+ children: virtualizer.getVirtualItems().map((virtualRow) => {
97
+ const content = renderItem(items[virtualRow.index], virtualRow.index);
98
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
99
+ "div",
100
+ {
101
+ "data-index": virtualRow.index,
102
+ ref: measureRow,
103
+ className: "absolute top-0 left-0 w-full",
104
+ style: { transform: `translateY(${virtualRow.start}px)` },
105
+ children: content === null ? null : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: rowClassName, children: content })
106
+ },
107
+ virtualRow.key
108
+ );
109
+ })
110
+ }
111
+ );
112
+ }
113
+
114
+
115
+ exports.ConversationVirtualList = ConversationVirtualList;
@@ -0,0 +1,30 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface ConversationVirtualListProps<T> {
5
+ items: T[];
6
+ getItemKey: (item: T, index: number) => string;
7
+ /** Return null for an item that renders nothing; it then takes no space. */
8
+ renderItem: (item: T, index: number) => ReactNode;
9
+ /** Called on a scroll within `startThreshold` of the top, including programmatic scrolls. */
10
+ onReachStart?: () => void;
11
+ startThreshold?: number;
12
+ estimateSize?: number;
13
+ /** Reveal the initial transcript only after its last row is positioned. */
14
+ initialScrollToEnd?: boolean;
15
+ overscan?: number;
16
+ /** Class for the wrapper around each rendered item, for example a bottom gap. */
17
+ rowClassName?: string;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Windowed rows inside a `Conversation`. Only rows near the viewport are in
22
+ * the DOM, so node count stays bounded for any thread length. The scroll
23
+ * element and stick-to-bottom behavior stay with `StickToBottom`.
24
+ *
25
+ * Items prepended to `items` (older history) keep the viewport anchored:
26
+ * the scroll offset moves by the height the new rows added above it.
27
+ */
28
+ declare function ConversationVirtualList<T>({ items, getItemKey, renderItem, onReachStart, startThreshold, estimateSize, initialScrollToEnd, overscan, rowClassName, className, }: ConversationVirtualListProps<T>): react_jsx_runtime.JSX.Element;
29
+
30
+ export { ConversationVirtualList, type ConversationVirtualListProps };
@@ -0,0 +1,30 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface ConversationVirtualListProps<T> {
5
+ items: T[];
6
+ getItemKey: (item: T, index: number) => string;
7
+ /** Return null for an item that renders nothing; it then takes no space. */
8
+ renderItem: (item: T, index: number) => ReactNode;
9
+ /** Called on a scroll within `startThreshold` of the top, including programmatic scrolls. */
10
+ onReachStart?: () => void;
11
+ startThreshold?: number;
12
+ estimateSize?: number;
13
+ /** Reveal the initial transcript only after its last row is positioned. */
14
+ initialScrollToEnd?: boolean;
15
+ overscan?: number;
16
+ /** Class for the wrapper around each rendered item, for example a bottom gap. */
17
+ rowClassName?: string;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Windowed rows inside a `Conversation`. Only rows near the viewport are in
22
+ * the DOM, so node count stays bounded for any thread length. The scroll
23
+ * element and stick-to-bottom behavior stay with `StickToBottom`.
24
+ *
25
+ * Items prepended to `items` (older history) keep the viewport anchored:
26
+ * the scroll offset moves by the height the new rows added above it.
27
+ */
28
+ declare function ConversationVirtualList<T>({ items, getItemKey, renderItem, onReachStart, startThreshold, estimateSize, initialScrollToEnd, overscan, rowClassName, className, }: ConversationVirtualListProps<T>): react_jsx_runtime.JSX.Element;
29
+
30
+ export { ConversationVirtualList, type ConversationVirtualListProps };
@@ -0,0 +1,115 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { useVirtualizer } from "@tanstack/react-virtual";
5
+ import { useStickToBottomContext } from "use-stick-to-bottom";
6
+ import { cn } from "../../lib/utils";
7
+ function ConversationVirtualList({
8
+ items,
9
+ getItemKey,
10
+ renderItem,
11
+ onReachStart,
12
+ startThreshold = 240,
13
+ estimateSize = 96,
14
+ initialScrollToEnd = false,
15
+ overscan = 6,
16
+ rowClassName,
17
+ className
18
+ }) {
19
+ const { scrollRef, state: scrollState } = useStickToBottomContext();
20
+ const [initialPositioned, setInitialPositioned] = useState(!initialScrollToEnd);
21
+ const [scrollElement, setScrollElement] = useState(null);
22
+ useEffect(() => {
23
+ setScrollElement(scrollRef.current);
24
+ }, [scrollRef]);
25
+ const virtualizer = useVirtualizer({
26
+ count: items.length,
27
+ getScrollElement: () => scrollElement,
28
+ estimateSize: () => estimateSize,
29
+ overscan,
30
+ getItemKey: (index) => getItemKey(items[index], index)
31
+ });
32
+ useEffect(() => {
33
+ if (initialPositioned) return;
34
+ const timeout = setTimeout(() => setInitialPositioned(true), 250);
35
+ return () => clearTimeout(timeout);
36
+ }, [initialPositioned]);
37
+ useEffect(() => {
38
+ if (initialPositioned || !scrollElement || items.length === 0) return;
39
+ let frame;
40
+ let previousHeight = -1;
41
+ let stableFrames = 0;
42
+ const position = () => {
43
+ const height = scrollElement.scrollHeight;
44
+ const target = Math.max(0, scrollState.calculatedTargetScrollTop);
45
+ const lastRow = virtualizer.getVirtualItems().at(-1);
46
+ const settled = lastRow?.index === items.length - 1 && Math.abs(scrollElement.scrollTop - target) <= 1 && height === previousHeight;
47
+ stableFrames = settled ? stableFrames + 1 : 0;
48
+ previousHeight = height;
49
+ scrollState.scrollTop = target;
50
+ if (stableFrames >= 2) {
51
+ setInitialPositioned(true);
52
+ return;
53
+ }
54
+ frame = requestAnimationFrame(position);
55
+ };
56
+ frame = requestAnimationFrame(position);
57
+ return () => cancelAnimationFrame(frame);
58
+ }, [initialPositioned, items.length, scrollElement, scrollState, virtualizer]);
59
+ const firstKeyRef = useRef(null);
60
+ const totalSizeRef = useRef(0);
61
+ const firstKey = items.length > 0 ? getItemKey(items[0], 0) : null;
62
+ useLayoutEffect(() => {
63
+ const previousFirstKey = firstKeyRef.current;
64
+ const previousTotal = totalSizeRef.current;
65
+ firstKeyRef.current = firstKey;
66
+ totalSizeRef.current = virtualizer.getTotalSize();
67
+ if (previousFirstKey === null || previousFirstKey === firstKey) return;
68
+ const previousFirstIndex = items.findIndex((item, index) => getItemKey(item, index) === previousFirstKey);
69
+ if (previousFirstIndex <= 0) return;
70
+ const scrollElement2 = scrollRef.current;
71
+ if (!scrollElement2) return;
72
+ scrollElement2.scrollTop += totalSizeRef.current - previousTotal;
73
+ });
74
+ const onReachStartRef = useRef(onReachStart);
75
+ useLayoutEffect(() => {
76
+ onReachStartRef.current = onReachStart;
77
+ }, [onReachStart]);
78
+ useEffect(() => {
79
+ const scrollElement2 = scrollRef.current;
80
+ if (!scrollElement2) return;
81
+ const handleScroll = () => {
82
+ if (scrollElement2.scrollTop <= startThreshold) onReachStartRef.current?.();
83
+ };
84
+ scrollElement2.addEventListener("scroll", handleScroll, { passive: true });
85
+ return () => scrollElement2.removeEventListener("scroll", handleScroll);
86
+ }, [scrollRef, startThreshold]);
87
+ const measureRow = useCallback(
88
+ (node) => virtualizer.measureElement(node),
89
+ [virtualizer]
90
+ );
91
+ return /* @__PURE__ */ jsx(
92
+ "div",
93
+ {
94
+ className: cn("relative w-full", className),
95
+ style: { height: virtualizer.getTotalSize(), visibility: initialPositioned ? void 0 : "hidden" },
96
+ children: virtualizer.getVirtualItems().map((virtualRow) => {
97
+ const content = renderItem(items[virtualRow.index], virtualRow.index);
98
+ return /* @__PURE__ */ jsx(
99
+ "div",
100
+ {
101
+ "data-index": virtualRow.index,
102
+ ref: measureRow,
103
+ className: "absolute top-0 left-0 w-full",
104
+ style: { transform: `translateY(${virtualRow.start}px)` },
105
+ children: content === null ? null : /* @__PURE__ */ jsx("div", { className: rowClassName, children: content })
106
+ },
107
+ virtualRow.key
108
+ );
109
+ })
110
+ }
111
+ );
112
+ }
113
+ export {
114
+ ConversationVirtualList
115
+ };
@@ -1,15 +1,13 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } 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');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }var _jsxruntime = require('react/jsx-runtime');
2
2
  var _react = require('react');
3
3
  var _lucidereact = require('lucide-react');
4
4
 
5
5
 
6
6
 
7
7
 
8
-
9
-
10
8
  var _common = require('@sero-ai/common');
11
9
  var _popover = require('../ui/popover');
12
- var _searchinput = require('../ui/search-input');
10
+ var _modelpickerbody = require('./model-picker-body');
13
11
  var _utils = require('../../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] = _react.useState.call(void 0, false);
27
- const [query, setQuery] = _react.useState.call(void 0, "");
28
- const inputRef = _react.useRef.call(void 0, null);
29
25
  const selected = _react.useMemo.call(void 0, () => {
30
26
  const parsed = _common.parseModelKey.call(void 0, 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 = _react.useMemo.call(void 0,
43
- () => _common.filterModelGroups.call(void 0, groups, query),
44
- [groups, query]
45
- );
46
- const totalResults = _react.useMemo.call(void 0,
47
- () => filteredGroups.reduce((count, group) => count + group.models.length, 0),
48
- [filteredGroups]
49
- );
50
- const handleOpenChange = _react.useCallback.call(void 0, (nextOpen) => {
51
- setOpen(nextOpen);
52
- if (!nextOpen) return;
53
- setQuery("");
54
- requestAnimationFrame(() => _optionalChain([inputRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.focus, 'call', _3 => _3()]));
55
- }, []);
56
- const handleSelect = _react.useCallback.call(void 0, (provider, modelId) => {
57
- onChange(_common.modelKey.call(void 0, provider, modelId));
38
+ const handleSelect = _react.useCallback.call(void 0, (nextValue) => {
39
+ onChange(nextValue);
58
40
  setOpen(false);
59
- setQuery("");
60
41
  }, [onChange]);
61
42
  const handleClear = _react.useCallback.call(void 0, (event) => {
62
43
  event.stopPropagation();
63
44
  onChange("");
64
45
  setOpen(false);
65
- setQuery("");
66
46
  }, [onChange]);
67
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _popover.Popover, { open, onOpenChange: handleOpenChange, children: [
47
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _popover.Popover, { open, onOpenChange: setOpen, children: [
68
48
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _popover.PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
69
49
  "div",
70
50
  {
@@ -79,11 +59,11 @@ function AvailableModelPicker({
79
59
  children: [
80
60
  selected.group && selected.model ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
81
61
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
82
- "img",
62
+ _modelpickerbody.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__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "min-w-0 flex-1", children: [
@@ -108,7 +88,7 @@ function AvailableModelPicker({
108
88
  ]
109
89
  }
110
90
  ) }),
111
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
91
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
112
92
  _popover.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__ */ _jsxruntime.jsx.call(void 0,
121
- _searchinput.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__ */ _jsxruntime.jsx.call(void 0, "div", { className: "max-h-[280px] overflow-y-auto py-1", 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: [
131
- index > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mx-3 border-t border-border/30" }) : null,
132
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
133
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
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__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
142
- ] }),
143
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-1", children: group.models.map((model) => {
144
- const nextValue = _common.modelKey.call(void 0, model.provider, model.modelId);
145
- const isSelected = nextValue === value;
146
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
147
- "button",
148
- {
149
- type: "button",
150
- onClick: () => handleSelect(model.provider, model.modelId),
151
- className: _utils.cn.call(void 0,
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__ */ _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" }) }),
157
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
158
- model.reasoning ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
159
- ]
160
- },
161
- nextValue
162
- );
163
- }) })
164
- ] }, group.provider)) })
165
- ]
99
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
100
+ _modelpickerbody.ModelPickerBody,
101
+ {
102
+ groups,
103
+ value,
104
+ onChange: handleSelect,
105
+ searchPlaceholder,
106
+ emptyLabel,
107
+ noModelsLabel
108
+ }
109
+ )
166
110
  }
167
111
  )
168
112
  ] });
@@ -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
  ] });