@vvedantb/ui 0.17.0 → 0.18.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.
- package/README.md +253 -3
- package/package.json +13 -5
- package/shell/src/SidebarResize.tsx +1 -4
- package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
- package/src/ai-elements/activity-question-utils.ts +26 -0
- package/src/ai-elements/activity-question.tsx +159 -0
- package/src/ai-elements/activity-shared.tsx +459 -0
- package/src/ai-elements/activity-step-detail.tsx +115 -0
- package/src/ai-elements/activity-step-label.ts +981 -0
- package/src/ai-elements/activity-tasks-utils.ts +199 -0
- package/src/ai-elements/activity-tasks.tsx +770 -0
- package/src/ai-elements/artifact.tsx +162 -52
- package/src/ai-elements/attachments.tsx +62 -32
- package/src/ai-elements/code-block.tsx +76 -22
- package/src/ai-elements/context.tsx +370 -0
- package/src/ai-elements/conversation.tsx +186 -62
- package/src/ai-elements/file-chip.tsx +53 -0
- package/src/ai-elements/message.tsx +161 -50
- package/src/ai-elements/model-picker-content.tsx +505 -0
- package/src/ai-elements/model-picker-types.ts +56 -0
- package/src/ai-elements/model-picker.tsx +301 -0
- package/src/ai-elements/model-selector.tsx +102 -21
- package/src/ai-elements/plan.tsx +130 -0
- package/src/ai-elements/prompt-input-settings.tsx +144 -0
- package/src/ai-elements/prompt-input-speech.tsx +189 -0
- package/src/ai-elements/prompt-input-traits.tsx +281 -0
- package/src/ai-elements/prompt-input.tsx +320 -134
- package/src/ai-elements/provider-icon.tsx +133 -0
- package/src/ai-elements/queue.tsx +293 -9
- package/src/ai-elements/shimmer.tsx +72 -1
- package/src/ai-elements/simple-model-ladder.tsx +108 -0
- package/src/ai-elements/test-results.tsx +510 -0
- package/src/ai-elements/trace.tsx +243 -0
- package/src/ai-elements/use-streaming-disclosure.ts +50 -0
- package/src/ai-elements/web-preview.tsx +349 -0
- package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
- package/src/index.ts +66 -12
- package/src/kibo/avatar-stack.tsx +40 -0
- package/src/kibo/contribution-graph.tsx +539 -0
- package/src/kibo/cursor.tsx +72 -0
- package/src/kibo/gantt-features.tsx +439 -0
- package/src/kibo/gantt-header.tsx +181 -0
- package/src/kibo/gantt-provider.tsx +657 -0
- package/src/kibo/gantt-sidebar.tsx +157 -0
- package/src/kibo/gantt-timeline.tsx +154 -0
- package/src/kibo/index.ts +151 -0
- package/src/kibo/kanban.tsx +338 -0
- package/src/kibo/kanbanDropTarget.ts +25 -0
- package/src/kibo/list.tsx +162 -0
- package/src/kibo/theme-switcher.tsx +109 -0
- package/src/kibo/video-player.tsx +237 -0
- package/src/markdown/Markdown.tsx +112 -0
- package/src/markdown/MarkdownBlockquote.tsx +45 -0
- package/src/markdown/MarkdownCodeBlock.tsx +197 -0
- package/src/markdown/MarkdownInlineCode.tsx +40 -0
- package/src/markdown/MarkdownLink.tsx +92 -0
- package/src/markdown/codeFence.ts +57 -0
- package/src/markdown/codeHighlight.ts +138 -0
- package/src/markdown/incrementalBlocks.ts +182 -0
- package/src/markdown/index.ts +3 -0
- package/src/markdown/markdownClipboard.ts +178 -0
- package/src/markdown/mermaid.ts +155 -0
- package/src/markdown/rehypeGithubAlerts.ts +80 -0
- package/src/motion/presets.ts +24 -0
- package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
- package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
- package/src/ui/_menu-parts.tsx +29 -0
- package/src/ui/accordion.alt.tsx +46 -53
- package/src/ui/accordion.tsx +59 -10
- package/src/ui/avatar.tsx +56 -0
- package/src/ui/badge.tsx +45 -21
- package/src/ui/border-beam.tsx +97 -0
- package/src/ui/button-group.tsx +50 -21
- package/src/ui/button.tsx +67 -45
- package/src/ui/calendar.tsx +48 -5
- package/src/ui/card.tsx +77 -8
- package/src/ui/carousel.tsx +60 -0
- package/src/ui/centered-spinner.tsx +30 -0
- package/src/ui/checkbox.tsx +34 -16
- package/src/ui/clear-input.alt.tsx +33 -75
- package/src/ui/clear-input.tsx +140 -15
- package/src/ui/collapsible.tsx +40 -2
- package/src/ui/command.tsx +143 -70
- package/src/ui/context-menu.tsx +159 -93
- package/src/ui/crossfade-icon.tsx +123 -0
- package/src/ui/dialog.tsx +154 -70
- package/src/ui/dropdown-menu.tsx +177 -117
- package/src/ui/hover-card.tsx +59 -17
- package/src/ui/input-group.alt.tsx +83 -83
- package/src/ui/input-group.tsx +129 -58
- package/src/ui/input.tsx +13 -4
- package/src/ui/label.tsx +11 -8
- package/src/ui/list-row.tsx +232 -0
- package/src/ui/loading-state.tsx +215 -0
- package/src/ui/popover.tsx +56 -23
- package/src/ui/progress.tsx +28 -16
- package/src/ui/refresh-spin-icon.tsx +41 -0
- package/src/ui/search-input.tsx +66 -15
- package/src/ui/select.tsx +171 -100
- package/src/ui/separator.tsx +23 -14
- package/src/ui/slider.tsx +21 -7
- package/src/ui/sonner.tsx +68 -30
- package/src/ui/spinner.tsx +229 -11
- package/src/ui/status-dot.tsx +84 -0
- package/src/ui/surface.tsx +49 -0
- package/src/ui/switch.tsx +29 -15
- package/src/ui/table.tsx +38 -16
- package/src/ui/tabs.tsx +123 -35
- package/src/ui/tabsSliding.ts +33 -1
- package/src/ui/textarea.tsx +13 -4
- package/src/ui/tooltip.tsx +50 -14
- package/src/ui/ui-config.tsx +149 -0
- package/src/utils/cat-color.ts +123 -0
- package/src/utils/dayjs.ts +6 -0
- package/src/utils/gesture.ts +59 -0
- package/src/utils/runtimeVisibility.ts +167 -0
- package/src/utils/sharedClock.ts +96 -0
- package/src/utils/surface-radius.ts +16 -0
- package/src/utils/useDragSensors.ts +58 -0
- package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
- package/tokens/examples/tokens-moved/README.md +20 -0
- package/tokens/examples/tokens-moved/index.html +12 -0
- package/tokens/examples/tokens-moved/main.tsx +6 -0
- package/tokens/examples/tokens-moved/package.json +14 -0
- package/tokens/examples/tokens-moved/styles.css +12 -0
- package/tokens/examples/tokens-moved/tsconfig.json +14 -0
- package/tokens/examples/tokens-moved/vite.config.ts +11 -0
- package/tokens/src/css/border-beam.css +116 -0
- package/tokens/src/css/compat-shadcn-rgb.css +6 -0
- package/tokens/src/css/markdown.css +318 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, type ReactNode } from "react";
|
|
4
|
+
import { IconBolt, IconChevronDown } from "@tabler/icons-react";
|
|
5
|
+
import { SimpleModelLadder } from "./simple-model-ladder";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
7
|
+
import { useCn, useUiConfig } from "../ui/ui-config";
|
|
8
|
+
import { ProviderIcon } from "./provider-icon";
|
|
9
|
+
import {
|
|
10
|
+
ModelPickerContent,
|
|
11
|
+
buildPickerInstances,
|
|
12
|
+
providerInstanceInitials,
|
|
13
|
+
teamInstanceKey,
|
|
14
|
+
} from "./model-picker-content";
|
|
15
|
+
import {
|
|
16
|
+
type ModelAccount,
|
|
17
|
+
type ModelOption,
|
|
18
|
+
findModelOption,
|
|
19
|
+
formatModelDisplayLabel,
|
|
20
|
+
getProviderLabel,
|
|
21
|
+
} from "./model-picker-types";
|
|
22
|
+
|
|
23
|
+
export type { ModelAccount, ModelOption };
|
|
24
|
+
export { findModelOption, formatModelDisplayLabel, getProviderLabel };
|
|
25
|
+
/** Embeddable picker body — use in menus/submenus; use `ModelPicker` for button triggers. */
|
|
26
|
+
export { ModelPickerContent };
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `UiConfig.ModelPicker`: `classes.trigger` replaces the `ModelPicker` trigger
|
|
30
|
+
* button's classes; `badgeRing` sets the ring colour around the account
|
|
31
|
+
* initials badge, which should match the surface behind it (`trigger`: the
|
|
32
|
+
* trigger, default `var(--color-border)`; `panel`: the provider rail and list,
|
|
33
|
+
* default `var(--color-surface-secondary)`).
|
|
34
|
+
*/
|
|
35
|
+
export interface ModelPickerConfig {
|
|
36
|
+
classes?: { trigger?: string };
|
|
37
|
+
badgeRing?: { trigger?: string; panel?: string };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const TRIGGER_CLASS =
|
|
41
|
+
// Carries `data-slot="select-trigger"` and is styled to read as one, so it
|
|
42
|
+
// has to press like one: `motion-press active:scale-[0.98]`, like the select
|
|
43
|
+
// triggers it sits beside in a composer footer. `motion-press` supersedes
|
|
44
|
+
// `transition-colors`: the two at equal specificity replace each other.
|
|
45
|
+
"motion-press flex h-7 items-center gap-1.5 rounded-md px-2 text-xs font-medium text-muted-foreground hover:bg-surface-tertiary hover:text-foreground active:scale-[0.98] disabled:opacity-50";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Shared chrome for the picker panel (popover + menu submenu). Keep these in
|
|
49
|
+
* sync so trigger vs embed surfaces cannot drift.
|
|
50
|
+
*/
|
|
51
|
+
export const modelPickerSurfaceClass =
|
|
52
|
+
"w-100 max-w-[calc(100vw-1rem)] overflow-hidden rounded-lg border-0 bg-transparent p-0 shadow-none";
|
|
53
|
+
|
|
54
|
+
export interface ModelPickerProps<TModel extends string = string> {
|
|
55
|
+
value: TModel;
|
|
56
|
+
onValueChange: (model: TModel) => void;
|
|
57
|
+
options: ReadonlyArray<ModelOption<TModel>>;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
className?: string;
|
|
60
|
+
/**
|
|
61
|
+
* The user's own provider accounts. When non-empty, each provider opens into
|
|
62
|
+
* Team + account groups, each listing that provider's models so picking a
|
|
63
|
+
* model also picks whose credential runs it.
|
|
64
|
+
*/
|
|
65
|
+
accounts?: ReadonlyArray<ModelAccount>;
|
|
66
|
+
/** Currently selected account id, or null for the team credential. */
|
|
67
|
+
accountId?: string | null;
|
|
68
|
+
/** Fired alongside a model change with the chosen account (null = team). */
|
|
69
|
+
onAccountChange?: (accountId: string | null) => void;
|
|
70
|
+
/**
|
|
71
|
+
* When set, called once per pick instead of separate model/account
|
|
72
|
+
* callbacks — use this to persist both fields in a single mutation.
|
|
73
|
+
*/
|
|
74
|
+
onSelectionChange?: (model: TModel, accountId: string | null) => void;
|
|
75
|
+
/**
|
|
76
|
+
* When a personal account is selected, Team for that provider is shown
|
|
77
|
+
* disabled. Set false when the viewer cannot switch the sticky account
|
|
78
|
+
* (e.g. non-owner on a task) so Team stays unselectable.
|
|
79
|
+
*/
|
|
80
|
+
canSelectTeamWhilePersonal?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Compact label after the model name on the trigger (e.g. the active
|
|
83
|
+
* reasoning level).
|
|
84
|
+
*/
|
|
85
|
+
triggerSuffix?: string;
|
|
86
|
+
/** Pinned above the model list — typically trait controls. */
|
|
87
|
+
header?: ReactNode;
|
|
88
|
+
/** Bolt before the provider icon when Fast mode is on. */
|
|
89
|
+
showFastIcon?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Simple-view capability ladder (cheapest → strongest). When at least two of
|
|
92
|
+
* these ids are in `options`, the popover opens on a discrete slider;
|
|
93
|
+
* Advanced swaps to the full picker.
|
|
94
|
+
*/
|
|
95
|
+
simpleLadder?: ReadonlyArray<TModel>;
|
|
96
|
+
/** Parks the slider thumb when `value` is not itself a ladder tick. */
|
|
97
|
+
simpleLadderSnap?: TModel;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Button + popover model picker (create modal, composers, toolbars).
|
|
102
|
+
* For context/dropdown menus, embed `ModelPickerContent` with
|
|
103
|
+
* `modelPickerSurfaceClass` instead of nesting this popover.
|
|
104
|
+
*/
|
|
105
|
+
export function ModelPicker<TModel extends string>({
|
|
106
|
+
value,
|
|
107
|
+
onValueChange,
|
|
108
|
+
options,
|
|
109
|
+
disabled,
|
|
110
|
+
className,
|
|
111
|
+
accounts,
|
|
112
|
+
accountId = null,
|
|
113
|
+
onAccountChange,
|
|
114
|
+
onSelectionChange,
|
|
115
|
+
canSelectTeamWhilePersonal = true,
|
|
116
|
+
triggerSuffix,
|
|
117
|
+
header,
|
|
118
|
+
showFastIcon,
|
|
119
|
+
simpleLadder,
|
|
120
|
+
simpleLadderSnap,
|
|
121
|
+
}: ModelPickerProps<TModel>) {
|
|
122
|
+
const merge = useCn();
|
|
123
|
+
const config = useUiConfig("ModelPicker");
|
|
124
|
+
const [open, setOpen] = useState(false);
|
|
125
|
+
const [advanced, setAdvanced] = useState(false);
|
|
126
|
+
const [previewId, setPreviewId] = useState<TModel | null>(null);
|
|
127
|
+
const displayValue = previewId ?? value;
|
|
128
|
+
const selectedModel = findModelOption(displayValue, options);
|
|
129
|
+
|
|
130
|
+
const ladderSteps: ModelOption<TModel>[] = [];
|
|
131
|
+
if (simpleLadder) {
|
|
132
|
+
for (const id of simpleLadder) {
|
|
133
|
+
const option = options.find((entry) => entry.id === id);
|
|
134
|
+
if (option) ladderSteps.push(option);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const showLadder = ladderSteps.length >= 2 && !advanced;
|
|
138
|
+
const preferredSnap = simpleLadderSnap ?? value;
|
|
139
|
+
let snappedAmong = ladderSteps[0]?.id ?? value;
|
|
140
|
+
for (const step of ladderSteps) {
|
|
141
|
+
if (step.id === value || step.id === preferredSnap) {
|
|
142
|
+
snappedAmong = step.id;
|
|
143
|
+
if (step.id === value) break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const triggerLabel = selectedModel ? selectedModel.label : "Select model";
|
|
147
|
+
const instances = buildPickerInstances(options, accounts);
|
|
148
|
+
|
|
149
|
+
const selectedAccount = accountId
|
|
150
|
+
? accounts?.find((account) => account.id === accountId)
|
|
151
|
+
: undefined;
|
|
152
|
+
|
|
153
|
+
const activeInstance =
|
|
154
|
+
(selectedAccount
|
|
155
|
+
? instances.find((instance) => instance.key === selectedAccount.id)
|
|
156
|
+
: undefined) ??
|
|
157
|
+
(selectedModel
|
|
158
|
+
? instances.find(
|
|
159
|
+
(instance) =>
|
|
160
|
+
instance.key === teamInstanceKey(selectedModel.provider),
|
|
161
|
+
)
|
|
162
|
+
: undefined) ??
|
|
163
|
+
instances[0];
|
|
164
|
+
|
|
165
|
+
const showAccountBadge = Boolean(
|
|
166
|
+
selectedAccount &&
|
|
167
|
+
instances.filter(
|
|
168
|
+
(instance) => instance.provider === selectedAccount.provider,
|
|
169
|
+
).length > 1,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const commit = (modelId: TModel, nextAccountId: string | null) => {
|
|
173
|
+
if (onSelectionChange) {
|
|
174
|
+
onSelectionChange(modelId, nextAccountId);
|
|
175
|
+
} else {
|
|
176
|
+
onValueChange(modelId);
|
|
177
|
+
onAccountChange?.(nextAccountId);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const accountForModel = (modelId: TModel): string | null => {
|
|
182
|
+
const option = options.find((entry) => entry.id === modelId);
|
|
183
|
+
if (!option) return null;
|
|
184
|
+
if (selectedAccount && selectedAccount.provider === option.provider) {
|
|
185
|
+
return accountId;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Popover
|
|
192
|
+
open={open}
|
|
193
|
+
onOpenChange={(next) => {
|
|
194
|
+
setOpen(next);
|
|
195
|
+
if (!next) {
|
|
196
|
+
setAdvanced(false);
|
|
197
|
+
setPreviewId(null);
|
|
198
|
+
}
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<PopoverTrigger asChild>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
role="combobox"
|
|
205
|
+
aria-expanded={open}
|
|
206
|
+
data-slot="select-trigger"
|
|
207
|
+
className={merge(
|
|
208
|
+
config?.classes?.trigger ?? TRIGGER_CLASS,
|
|
209
|
+
className,
|
|
210
|
+
)}
|
|
211
|
+
disabled={disabled}
|
|
212
|
+
>
|
|
213
|
+
{showFastIcon ? (
|
|
214
|
+
<span title="Fast" className="inline-flex shrink-0">
|
|
215
|
+
<IconBolt size={12} aria-hidden />
|
|
216
|
+
</span>
|
|
217
|
+
) : null}
|
|
218
|
+
{activeInstance && selectedAccount && showAccountBadge ? (
|
|
219
|
+
<span className="relative isolate inline-flex size-4 shrink-0 items-center justify-center">
|
|
220
|
+
<ProviderIcon
|
|
221
|
+
provider={selectedModel?.provider ?? "claude"}
|
|
222
|
+
size={14}
|
|
223
|
+
/>
|
|
224
|
+
<span
|
|
225
|
+
className="pointer-events-none absolute -right-0.5 -bottom-0.5 z-10 flex h-3 min-w-3 items-center justify-center rounded-full bg-surface-secondary px-0.5 text-[7px] font-semibold leading-none text-muted-foreground"
|
|
226
|
+
style={{
|
|
227
|
+
boxShadow: `0 0 0 1.5px ${config?.badgeRing?.trigger ?? "var(--color-border)"}`,
|
|
228
|
+
}}
|
|
229
|
+
aria-hidden
|
|
230
|
+
>
|
|
231
|
+
{providerInstanceInitials(selectedAccount.label)}
|
|
232
|
+
</span>
|
|
233
|
+
</span>
|
|
234
|
+
) : (
|
|
235
|
+
<ProviderIcon
|
|
236
|
+
provider={selectedModel?.provider ?? "claude"}
|
|
237
|
+
size={14}
|
|
238
|
+
/>
|
|
239
|
+
)}
|
|
240
|
+
{/* On a phone the composer's under-bar is only a few hundred pixels
|
|
241
|
+
wide, so the label gives up width to the control beside it rather
|
|
242
|
+
than pushing it out of the row. `min-w-0` is the load-bearing half
|
|
243
|
+
— a flex child defaults to `min-width: auto` and never shrinks. */}
|
|
244
|
+
<span className="whitespace-nowrap text-left max-sm:min-w-0 max-sm:truncate">
|
|
245
|
+
{triggerLabel}
|
|
246
|
+
{triggerSuffix ? (
|
|
247
|
+
<span className="text-muted-foreground/70">
|
|
248
|
+
{" · "}
|
|
249
|
+
{triggerSuffix}
|
|
250
|
+
</span>
|
|
251
|
+
) : null}
|
|
252
|
+
</span>
|
|
253
|
+
<IconChevronDown
|
|
254
|
+
size={12}
|
|
255
|
+
className="shrink-0 opacity-60"
|
|
256
|
+
aria-hidden
|
|
257
|
+
/>
|
|
258
|
+
</button>
|
|
259
|
+
</PopoverTrigger>
|
|
260
|
+
<PopoverContent
|
|
261
|
+
align="start"
|
|
262
|
+
className={
|
|
263
|
+
showLadder ? "w-56 p-3" : modelPickerSurfaceClass
|
|
264
|
+
}
|
|
265
|
+
onOpenAutoFocus={(event) => {
|
|
266
|
+
if (showLadder) return;
|
|
267
|
+
// Let ModelPickerContent own focus (search input); Radix would
|
|
268
|
+
// otherwise park it on the first focusable and fight our layout effect.
|
|
269
|
+
event.preventDefault();
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
{showLadder ? (
|
|
273
|
+
<SimpleModelLadder
|
|
274
|
+
value={displayValue}
|
|
275
|
+
steps={ladderSteps}
|
|
276
|
+
snappedId={snappedAmong}
|
|
277
|
+
disabled={disabled}
|
|
278
|
+
onValueChange={(modelId) => {
|
|
279
|
+
setPreviewId(modelId);
|
|
280
|
+
commit(modelId, accountForModel(modelId));
|
|
281
|
+
}}
|
|
282
|
+
onAdvanced={() => setAdvanced(true)}
|
|
283
|
+
/>
|
|
284
|
+
) : (
|
|
285
|
+
<ModelPickerContent
|
|
286
|
+
value={displayValue}
|
|
287
|
+
accountId={accountId}
|
|
288
|
+
options={options}
|
|
289
|
+
accounts={accounts}
|
|
290
|
+
canSelectTeamWhilePersonal={canSelectTeamWhilePersonal}
|
|
291
|
+
onSelect={(modelId, nextAccountId) => {
|
|
292
|
+
setPreviewId(null);
|
|
293
|
+
commit(modelId, nextAccountId);
|
|
294
|
+
}}
|
|
295
|
+
header={header}
|
|
296
|
+
/>
|
|
297
|
+
)}
|
|
298
|
+
</PopoverContent>
|
|
299
|
+
</Popover>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
import { IconCpu } from "@tabler/icons-react";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
Command,
|
|
@@ -17,24 +18,32 @@ import {
|
|
|
17
18
|
DialogTitle,
|
|
18
19
|
DialogTrigger,
|
|
19
20
|
} from "../ui/dialog";
|
|
21
|
+
import { useCn } from "../ui/ui-config";
|
|
20
22
|
import { cn } from "../utils/cn";
|
|
23
|
+
import { ProviderIcon } from "./provider-icon";
|
|
21
24
|
|
|
25
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
22
26
|
export type ModelSelectorProps = ComponentProps<typeof Dialog>;
|
|
23
27
|
|
|
28
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
24
29
|
export const ModelSelector = (props: ModelSelectorProps) => (
|
|
25
30
|
<Dialog {...props} />
|
|
26
31
|
);
|
|
27
32
|
|
|
33
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
28
34
|
export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
|
|
29
35
|
|
|
36
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
30
37
|
export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
|
|
31
38
|
<DialogTrigger {...props} />
|
|
32
39
|
);
|
|
33
40
|
|
|
41
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
34
42
|
export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
|
|
35
43
|
title?: ReactNode;
|
|
36
44
|
};
|
|
37
45
|
|
|
46
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
38
47
|
export const ModelSelectorContent = ({
|
|
39
48
|
className,
|
|
40
49
|
children,
|
|
@@ -53,14 +62,18 @@ export const ModelSelectorContent = ({
|
|
|
53
62
|
</DialogContent>
|
|
54
63
|
);
|
|
55
64
|
|
|
65
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
56
66
|
export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
|
|
57
67
|
|
|
68
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
58
69
|
export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
|
|
59
70
|
<CommandDialog {...props} />
|
|
60
71
|
);
|
|
61
72
|
|
|
73
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
62
74
|
export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
|
|
63
75
|
|
|
76
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
64
77
|
export const ModelSelectorInput = ({
|
|
65
78
|
className,
|
|
66
79
|
...props
|
|
@@ -68,48 +81,67 @@ export const ModelSelectorInput = ({
|
|
|
68
81
|
<CommandInput className={cn("h-auto py-3.5", className)} {...props} />
|
|
69
82
|
);
|
|
70
83
|
|
|
84
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
71
85
|
export type ModelSelectorListProps = ComponentProps<typeof CommandList>;
|
|
72
86
|
|
|
87
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
73
88
|
export const ModelSelectorList = (props: ModelSelectorListProps) => (
|
|
74
89
|
<CommandList {...props} />
|
|
75
90
|
);
|
|
76
91
|
|
|
92
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
77
93
|
export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
|
|
78
94
|
|
|
95
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
79
96
|
export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => (
|
|
80
97
|
<CommandEmpty {...props} />
|
|
81
98
|
);
|
|
82
99
|
|
|
100
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
83
101
|
export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
|
|
84
102
|
|
|
103
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
85
104
|
export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => (
|
|
86
105
|
<CommandGroup {...props} />
|
|
87
106
|
);
|
|
88
107
|
|
|
108
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
89
109
|
export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
|
|
90
110
|
|
|
111
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
91
112
|
export const ModelSelectorItem = (props: ModelSelectorItemProps) => (
|
|
92
113
|
<CommandItem {...props} />
|
|
93
114
|
);
|
|
94
115
|
|
|
116
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
95
117
|
export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
|
|
96
118
|
|
|
119
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
97
120
|
export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
|
|
98
121
|
<CommandShortcut {...props} />
|
|
99
122
|
);
|
|
100
123
|
|
|
124
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
101
125
|
export type ModelSelectorSeparatorProps = ComponentProps<
|
|
102
126
|
typeof CommandSeparator
|
|
103
127
|
>;
|
|
104
128
|
|
|
129
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
105
130
|
export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
|
|
106
131
|
<CommandSeparator {...props} />
|
|
107
132
|
);
|
|
108
133
|
|
|
134
|
+
/**
|
|
135
|
+
* @deprecated Use `ModelPicker`. Removed in the next minor release.
|
|
136
|
+
*
|
|
137
|
+
* Now SVG props: the logo renders `ProviderIcon` (see `ModelSelectorLogo`).
|
|
138
|
+
*/
|
|
109
139
|
export type ModelSelectorLogoProps = Omit<
|
|
110
|
-
ComponentProps<"
|
|
111
|
-
"
|
|
140
|
+
ComponentProps<"svg">,
|
|
141
|
+
"children"
|
|
112
142
|
> & {
|
|
143
|
+
/** Rendered width and height in px. Default 12, the old `<img>` size. */
|
|
144
|
+
size?: number;
|
|
113
145
|
provider:
|
|
114
146
|
| "moonshotai-cn"
|
|
115
147
|
| "lucidquery"
|
|
@@ -171,38 +203,87 @@ export type ModelSelectorLogoProps = Omit<
|
|
|
171
203
|
| (string & {});
|
|
172
204
|
};
|
|
173
205
|
|
|
206
|
+
/**
|
|
207
|
+
* models.dev provider ids that have a `ProviderIcon` mark. `ProviderIcon`
|
|
208
|
+
* knows four marks: Claude, OpenAI (`codex`), OpenCode and Cursor.
|
|
209
|
+
*/
|
|
210
|
+
const LOGO_PROVIDER: Record<string, string> = {
|
|
211
|
+
anthropic: "claude",
|
|
212
|
+
"google-vertex-anthropic": "claude",
|
|
213
|
+
claude: "claude",
|
|
214
|
+
openai: "codex",
|
|
215
|
+
codex: "codex",
|
|
216
|
+
opencode: "opencode",
|
|
217
|
+
cursor: "cursor",
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @deprecated Use `ModelPicker`. Removed in the next minor release.
|
|
222
|
+
*
|
|
223
|
+
* A thin wrapper over `ProviderIcon`: no network fetch (it used to load
|
|
224
|
+
* `https://models.dev/logos/<provider>.svg`). `anthropic`,
|
|
225
|
+
* `google-vertex-anthropic` and `claude` show the Claude mark; `openai` and
|
|
226
|
+
* `codex` the OpenAI mark; `opencode` and `cursor` their marks. Any other id
|
|
227
|
+
* shows a neutral CPU glyph, not another brand's mark. The SVG keeps the old
|
|
228
|
+
* `"<provider> logo"` accessible name.
|
|
229
|
+
*/
|
|
174
230
|
export const ModelSelectorLogo = ({
|
|
175
231
|
provider,
|
|
232
|
+
size = 12,
|
|
176
233
|
className,
|
|
177
234
|
...props
|
|
178
|
-
}: ModelSelectorLogoProps) =>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
235
|
+
}: ModelSelectorLogoProps) => {
|
|
236
|
+
const merge = useCn();
|
|
237
|
+
const mapped = LOGO_PROVIDER[provider];
|
|
238
|
+
const label = `${provider} logo`;
|
|
239
|
+
if (!mapped) {
|
|
240
|
+
return (
|
|
241
|
+
<IconCpu
|
|
242
|
+
{...props}
|
|
243
|
+
role="img"
|
|
244
|
+
aria-label={label}
|
|
245
|
+
size={size}
|
|
246
|
+
className={merge("shrink-0 text-muted-foreground", className)}
|
|
247
|
+
/>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return (
|
|
251
|
+
<ProviderIcon
|
|
252
|
+
{...props}
|
|
253
|
+
role="img"
|
|
254
|
+
aria-label={label}
|
|
255
|
+
aria-hidden={false}
|
|
256
|
+
provider={mapped}
|
|
257
|
+
size={size}
|
|
258
|
+
className={className}
|
|
259
|
+
/>
|
|
260
|
+
);
|
|
261
|
+
};
|
|
188
262
|
|
|
263
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
189
264
|
export type ModelSelectorLogoGroupProps = ComponentProps<"div">;
|
|
190
265
|
|
|
266
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
191
267
|
export const ModelSelectorLogoGroup = ({
|
|
192
268
|
className,
|
|
193
269
|
...props
|
|
194
|
-
}: ModelSelectorLogoGroupProps) =>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
className
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
270
|
+
}: ModelSelectorLogoGroupProps) => {
|
|
271
|
+
const merge = useCn();
|
|
272
|
+
return (
|
|
273
|
+
<div
|
|
274
|
+
className={merge(
|
|
275
|
+
"flex shrink-0 items-center -space-x-1 [&>svg]:rounded-full [&>svg]:bg-surface [&>svg]:p-px",
|
|
276
|
+
className,
|
|
277
|
+
)}
|
|
278
|
+
{...props}
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
281
|
+
};
|
|
203
282
|
|
|
283
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
204
284
|
export type ModelSelectorNameProps = ComponentProps<"span">;
|
|
205
285
|
|
|
286
|
+
/** @deprecated Use `ModelPicker`. Removed in the next minor release. */
|
|
206
287
|
export const ModelSelectorName = ({
|
|
207
288
|
className,
|
|
208
289
|
...props
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
|
|
5
|
+
import { Button } from "../ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardAction,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardFooter,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
} from "../ui/card";
|
|
14
|
+
import {
|
|
15
|
+
Collapsible,
|
|
16
|
+
CollapsibleContent,
|
|
17
|
+
CollapsibleTrigger,
|
|
18
|
+
} from "../ui/collapsible";
|
|
19
|
+
import { useCn } from "../ui/ui-config";
|
|
20
|
+
import { IconSelector } from "@tabler/icons-react";
|
|
21
|
+
import { createContext, useContext } from "react";
|
|
22
|
+
|
|
23
|
+
import { Shimmer } from "./shimmer";
|
|
24
|
+
|
|
25
|
+
interface PlanContextValue {
|
|
26
|
+
isStreaming: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const PlanContext = createContext<PlanContextValue | null>(null);
|
|
30
|
+
|
|
31
|
+
const usePlan = () => {
|
|
32
|
+
const context = useContext(PlanContext);
|
|
33
|
+
if (!context) {
|
|
34
|
+
throw new Error("AgentPlan components must be used within AgentPlan");
|
|
35
|
+
}
|
|
36
|
+
return context;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type AgentPlanProps = ComponentProps<typeof Collapsible> & {
|
|
40
|
+
isStreaming?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const AgentPlan = ({
|
|
44
|
+
className,
|
|
45
|
+
isStreaming = false,
|
|
46
|
+
children,
|
|
47
|
+
...props
|
|
48
|
+
}: AgentPlanProps) => {
|
|
49
|
+
const merge = useCn();
|
|
50
|
+
return (
|
|
51
|
+
<PlanContext.Provider value={{ isStreaming }}>
|
|
52
|
+
<Collapsible asChild data-slot="plan" {...props}>
|
|
53
|
+
<Card className={merge("smooth-shadow-none", className)}>{children}</Card>
|
|
54
|
+
</Collapsible>
|
|
55
|
+
</PlanContext.Provider>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type AgentPlanHeaderProps = ComponentProps<typeof CardHeader>;
|
|
60
|
+
|
|
61
|
+
export const AgentPlanHeader = ({ className, ...props }: AgentPlanHeaderProps) => {
|
|
62
|
+
const merge = useCn();
|
|
63
|
+
return (
|
|
64
|
+
<CardHeader
|
|
65
|
+
className={merge(
|
|
66
|
+
"flex flex-row items-center justify-between gap-2",
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
data-slot="plan-header"
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type AgentPlanTitleProps = Omit<
|
|
76
|
+
ComponentProps<typeof CardTitle>,
|
|
77
|
+
"children"
|
|
78
|
+
> & {
|
|
79
|
+
children: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const AgentPlanTitle = ({ children, ...props }: AgentPlanTitleProps) => {
|
|
83
|
+
const { isStreaming } = usePlan();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<CardTitle data-slot="plan-title" {...props}>
|
|
87
|
+
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
|
88
|
+
</CardTitle>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type AgentPlanActionProps = ComponentProps<typeof CardAction>;
|
|
93
|
+
|
|
94
|
+
export const AgentPlanAction = (props: AgentPlanActionProps) => (
|
|
95
|
+
<CardAction data-slot="plan-action" {...props} />
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
export type AgentPlanContentProps = ComponentProps<typeof CardContent>;
|
|
99
|
+
|
|
100
|
+
export const AgentPlanContent = (props: AgentPlanContentProps) => (
|
|
101
|
+
<CollapsibleContent asChild>
|
|
102
|
+
<CardContent data-slot="plan-content" {...props} />
|
|
103
|
+
</CollapsibleContent>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export type AgentPlanFooterProps = ComponentProps<"div">;
|
|
107
|
+
|
|
108
|
+
export const AgentPlanFooter = (props: AgentPlanFooterProps) => (
|
|
109
|
+
<CardFooter data-slot="plan-footer" {...props} />
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
export type AgentPlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
|
|
113
|
+
|
|
114
|
+
export const AgentPlanTrigger = ({ className, ...props }: AgentPlanTriggerProps) => {
|
|
115
|
+
const merge = useCn();
|
|
116
|
+
return (
|
|
117
|
+
<CollapsibleTrigger asChild>
|
|
118
|
+
<Button
|
|
119
|
+
className={merge("size-8", className)}
|
|
120
|
+
data-slot="plan-trigger"
|
|
121
|
+
size="icon"
|
|
122
|
+
variant="ghost"
|
|
123
|
+
{...props}
|
|
124
|
+
>
|
|
125
|
+
<IconSelector className="size-4" />
|
|
126
|
+
<span className="sr-only">Toggle plan</span>
|
|
127
|
+
</Button>
|
|
128
|
+
</CollapsibleTrigger>
|
|
129
|
+
);
|
|
130
|
+
};
|