@hogoshi/ui 0.1.8 → 0.1.9
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/package.json +1 -1
- package/src/components/chat.tsx +446 -0
- package/src/index.ts +11 -0
- package/src/styles.css +5 -0
package/package.json
CHANGED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Alert01Icon,
|
|
5
|
+
ArrowDown01Icon,
|
|
6
|
+
ArrowUp02Icon,
|
|
7
|
+
Cancel01Icon,
|
|
8
|
+
Pin02Icon,
|
|
9
|
+
StopCircleIcon,
|
|
10
|
+
Tick02Icon,
|
|
11
|
+
} from "@hugeicons-pro/core-stroke-rounded";
|
|
12
|
+
import { Pin02Icon as Pin02SolidIcon } from "@hugeicons-pro/core-solid-rounded";
|
|
13
|
+
import {
|
|
14
|
+
useEffect,
|
|
15
|
+
useRef,
|
|
16
|
+
type ComponentProps,
|
|
17
|
+
type ReactNode,
|
|
18
|
+
} from "react";
|
|
19
|
+
import { cn } from "../lib/cn";
|
|
20
|
+
import type { AppIcon } from "./icon";
|
|
21
|
+
import { Icon } from "./icon";
|
|
22
|
+
import { Image, ImageGallery, type ViewerImage } from "./image-viewer";
|
|
23
|
+
import { Button } from "./button";
|
|
24
|
+
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "./collapsible";
|
|
25
|
+
import { Spinner } from "./spinner";
|
|
26
|
+
import { Badge } from "./badge";
|
|
27
|
+
|
|
28
|
+
const workingText =
|
|
29
|
+
"animate-[shimmer_1.8s_linear_infinite] bg-[linear-gradient(90deg,var(--fg-muted)_20%,var(--fg)_50%,var(--fg-muted)_80%)] bg-[length:200%_100%] bg-clip-text text-transparent";
|
|
30
|
+
|
|
31
|
+
function ChatCaret() {
|
|
32
|
+
return (
|
|
33
|
+
<span
|
|
34
|
+
aria-hidden
|
|
35
|
+
className="bg-fg animate-[chat-caret_1.1s_var(--motion-ease)_infinite] ml-0.5 inline-block h-[1em] w-[2px] translate-y-[2px] rounded-full"
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ChatMessage({
|
|
41
|
+
role,
|
|
42
|
+
streaming = false,
|
|
43
|
+
className,
|
|
44
|
+
children,
|
|
45
|
+
}: {
|
|
46
|
+
role: "user" | "assistant";
|
|
47
|
+
streaming?: boolean;
|
|
48
|
+
className?: string;
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<div className={cn("group/chat flex w-full flex-col", role === "user" && "items-end", className)}>
|
|
53
|
+
{role === "user" ? (
|
|
54
|
+
<div className="hogoshi-selectable bg-bg-subtle max-w-[85%] rounded-[var(--radius-md)] px-3.5 py-2.5 text-sm leading-relaxed">
|
|
55
|
+
{children}
|
|
56
|
+
{streaming ? <ChatCaret /> : null}
|
|
57
|
+
</div>
|
|
58
|
+
) : (
|
|
59
|
+
<div className="hogoshi-selectable text-fg w-full min-w-0 text-sm leading-relaxed">
|
|
60
|
+
{children}
|
|
61
|
+
{streaming ? <ChatCaret /> : null}
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function ChatActions({ className, ...props }: ComponentProps<"div">) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={cn(
|
|
72
|
+
"flex items-center gap-0.5 opacity-0 transition-opacity duration-[var(--motion-duration)] ease-[var(--motion-ease)] focus-within:opacity-100 group-hover/chat:opacity-100",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ChatThinking({
|
|
81
|
+
status,
|
|
82
|
+
state = "working",
|
|
83
|
+
reasoning,
|
|
84
|
+
defaultOpen = false,
|
|
85
|
+
className,
|
|
86
|
+
}: {
|
|
87
|
+
status: ReactNode;
|
|
88
|
+
state?: "working" | "attention";
|
|
89
|
+
reasoning?: ReactNode;
|
|
90
|
+
defaultOpen?: boolean;
|
|
91
|
+
className?: string;
|
|
92
|
+
}) {
|
|
93
|
+
const attention = state === "attention";
|
|
94
|
+
const label = (
|
|
95
|
+
<span className={cn("text-xs font-medium", attention ? "text-warning" : workingText)}>{status}</span>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!reasoning) {
|
|
99
|
+
return (
|
|
100
|
+
<div className={cn("flex items-center gap-2", className)}>
|
|
101
|
+
{attention ? (
|
|
102
|
+
<Icon
|
|
103
|
+
icon={Alert01Icon}
|
|
104
|
+
variant="stroke"
|
|
105
|
+
size={14}
|
|
106
|
+
className="text-warning animate-[loader-pulse_calc(var(--motion-duration)*2)_var(--motion-ease)_infinite]"
|
|
107
|
+
/>
|
|
108
|
+
) : null}
|
|
109
|
+
{label}
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Collapsible defaultOpen={defaultOpen} className={className}>
|
|
116
|
+
<CollapsibleTrigger className="group/thinking py-0.5">
|
|
117
|
+
<Icon
|
|
118
|
+
icon={ArrowDown01Icon}
|
|
119
|
+
variant="stroke"
|
|
120
|
+
size={12}
|
|
121
|
+
className="text-fg-subtle transition-transform duration-[var(--motion-duration)] ease-[var(--motion-ease)] group-data-[panel-open]/thinking:rotate-180"
|
|
122
|
+
/>
|
|
123
|
+
{label}
|
|
124
|
+
</CollapsibleTrigger>
|
|
125
|
+
<CollapsiblePanel className="hogoshi-selectable text-fg-muted py-1 pl-5 text-xs leading-relaxed">
|
|
126
|
+
{reasoning}
|
|
127
|
+
</CollapsiblePanel>
|
|
128
|
+
</Collapsible>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type ChatToolStatus = "running" | "success" | "error" | "attention";
|
|
133
|
+
|
|
134
|
+
function ToolStatusIcon({ status }: { status: ChatToolStatus }) {
|
|
135
|
+
if (status === "running") return <Spinner size="sm" className="text-fg-muted shrink-0" />;
|
|
136
|
+
if (status === "success")
|
|
137
|
+
return <Icon icon={Tick02Icon} variant="stroke" size={16} className="text-success shrink-0" />;
|
|
138
|
+
if (status === "error")
|
|
139
|
+
return <Icon icon={Cancel01Icon} variant="stroke" size={16} className="text-danger shrink-0" />;
|
|
140
|
+
return (
|
|
141
|
+
<Icon
|
|
142
|
+
icon={Alert01Icon}
|
|
143
|
+
variant="stroke"
|
|
144
|
+
size={16}
|
|
145
|
+
className="text-warning animate-[loader-pulse_calc(var(--motion-duration)*2)_var(--motion-ease)_infinite] shrink-0"
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function ChatToolCall({
|
|
151
|
+
status,
|
|
152
|
+
label,
|
|
153
|
+
meta,
|
|
154
|
+
defaultOpen = false,
|
|
155
|
+
className,
|
|
156
|
+
children,
|
|
157
|
+
}: {
|
|
158
|
+
status: ChatToolStatus;
|
|
159
|
+
label: ReactNode;
|
|
160
|
+
meta?: ReactNode;
|
|
161
|
+
defaultOpen?: boolean;
|
|
162
|
+
className?: string;
|
|
163
|
+
children?: ReactNode;
|
|
164
|
+
}) {
|
|
165
|
+
if (!children) {
|
|
166
|
+
return (
|
|
167
|
+
<div className={cn("flex min-w-0 items-center gap-2", className)}>
|
|
168
|
+
<ToolStatusIcon status={status} />
|
|
169
|
+
<span className="text-fg truncate text-sm">{label}</span>
|
|
170
|
+
{meta ? <span className="text-fg-subtle min-w-0 truncate text-xs">{meta}</span> : null}
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Collapsible defaultOpen={defaultOpen} className={className}>
|
|
177
|
+
<CollapsibleTrigger className="group/tool gap-2 py-0.5">
|
|
178
|
+
<ToolStatusIcon status={status} />
|
|
179
|
+
<span className="text-fg truncate text-sm">{label}</span>
|
|
180
|
+
{meta ? <span className="text-fg-subtle min-w-0 truncate text-xs">{meta}</span> : null}
|
|
181
|
+
<Icon
|
|
182
|
+
icon={ArrowDown01Icon}
|
|
183
|
+
variant="stroke"
|
|
184
|
+
size={12}
|
|
185
|
+
className="text-fg-subtle ml-auto shrink-0 transition-transform duration-[var(--motion-duration)] ease-[var(--motion-ease)] group-data-[panel-open]/tool:rotate-180"
|
|
186
|
+
/>
|
|
187
|
+
</CollapsibleTrigger>
|
|
188
|
+
<CollapsiblePanel className="hogoshi-selectable my-1 ml-[7px] border-border border-l py-1 pr-1 pl-3.5">
|
|
189
|
+
{children}
|
|
190
|
+
</CollapsiblePanel>
|
|
191
|
+
</Collapsible>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type DiffKind = "file" | "hunk" | "add" | "del" | "ctx";
|
|
196
|
+
|
|
197
|
+
const diffLineClass: Record<DiffKind, string> = {
|
|
198
|
+
file: "text-fg-subtle",
|
|
199
|
+
hunk: "text-accent bg-accent-muted/40",
|
|
200
|
+
add: "text-success bg-success/10",
|
|
201
|
+
del: "text-danger bg-danger/10",
|
|
202
|
+
ctx: "text-fg-muted",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
function diffKind(line: string): DiffKind {
|
|
206
|
+
if (line.startsWith("diff --git") || line.startsWith("+++") || line.startsWith("---")) return "file";
|
|
207
|
+
if (line.startsWith("@@")) return "hunk";
|
|
208
|
+
if (line.startsWith("+")) return "add";
|
|
209
|
+
if (line.startsWith("-")) return "del";
|
|
210
|
+
return "ctx";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function ChatDiff({ diff, className, ...props }: { diff: string } & ComponentProps<"div">) {
|
|
214
|
+
const lines = diff.replace(/\n$/, "").split("\n");
|
|
215
|
+
return (
|
|
216
|
+
<div
|
|
217
|
+
className={cn(
|
|
218
|
+
"border-border bg-bg-subtle max-h-72 overflow-auto rounded-[var(--radius-md)] border font-mono text-xs",
|
|
219
|
+
className,
|
|
220
|
+
)}
|
|
221
|
+
{...props}
|
|
222
|
+
>
|
|
223
|
+
{lines.map((line, index) => (
|
|
224
|
+
<div key={index} className={cn("px-3 py-px whitespace-pre", diffLineClass[diffKind(line)])}>
|
|
225
|
+
{line || " "}
|
|
226
|
+
</div>
|
|
227
|
+
))}
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function ChatComposer({
|
|
233
|
+
value,
|
|
234
|
+
onValueChange,
|
|
235
|
+
onSubmit,
|
|
236
|
+
onStop,
|
|
237
|
+
onKeyDown,
|
|
238
|
+
busy = false,
|
|
239
|
+
placeholder,
|
|
240
|
+
disabled,
|
|
241
|
+
actions,
|
|
242
|
+
className,
|
|
243
|
+
}: {
|
|
244
|
+
value: string;
|
|
245
|
+
onValueChange: (value: string) => void;
|
|
246
|
+
onSubmit?: () => void;
|
|
247
|
+
onStop?: () => void;
|
|
248
|
+
onKeyDown?: ComponentProps<"textarea">["onKeyDown"];
|
|
249
|
+
busy?: boolean;
|
|
250
|
+
placeholder?: string;
|
|
251
|
+
disabled?: boolean;
|
|
252
|
+
actions?: ReactNode;
|
|
253
|
+
className?: string;
|
|
254
|
+
}) {
|
|
255
|
+
const textarea = useRef<HTMLTextAreaElement>(null);
|
|
256
|
+
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
const el = textarea.current;
|
|
259
|
+
if (!el) return;
|
|
260
|
+
el.style.height = "auto";
|
|
261
|
+
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
262
|
+
}, [value]);
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div
|
|
266
|
+
className={cn(
|
|
267
|
+
"border-border bg-bg-elevated focus-within:ring-ring focus-within:border-accent rounded-[var(--radius-lg)] border transition-[border-color,box-shadow] duration-[var(--motion-duration)] ease-[var(--motion-ease)] focus-within:ring-2",
|
|
268
|
+
className,
|
|
269
|
+
)}
|
|
270
|
+
>
|
|
271
|
+
<textarea
|
|
272
|
+
ref={textarea}
|
|
273
|
+
rows={1}
|
|
274
|
+
value={value}
|
|
275
|
+
placeholder={placeholder}
|
|
276
|
+
disabled={disabled}
|
|
277
|
+
onChange={(event) => onValueChange(event.target.value)}
|
|
278
|
+
onKeyDown={(event) => {
|
|
279
|
+
onKeyDown?.(event);
|
|
280
|
+
if (event.defaultPrevented) return;
|
|
281
|
+
if (event.key === "Enter" && !event.shiftKey && !busy && !disabled) {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
onSubmit?.();
|
|
284
|
+
}
|
|
285
|
+
}}
|
|
286
|
+
className="placeholder:text-fg-subtle max-h-48 w-full resize-none bg-transparent px-3.5 pt-3 pb-2 text-sm leading-relaxed outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
287
|
+
/>
|
|
288
|
+
<div className="flex items-center justify-between gap-2 px-2 pb-2">
|
|
289
|
+
<div className="flex min-w-0 items-center gap-0.5">{actions}</div>
|
|
290
|
+
{busy ? (
|
|
291
|
+
onStop ? (
|
|
292
|
+
<Button size="icon-sm" variant="ghost" aria-label="Stop" onClick={onStop}>
|
|
293
|
+
<Icon icon={StopCircleIcon} variant="stroke" />
|
|
294
|
+
</Button>
|
|
295
|
+
) : null
|
|
296
|
+
) : (
|
|
297
|
+
<Button
|
|
298
|
+
size="icon-sm"
|
|
299
|
+
variant="primary"
|
|
300
|
+
aria-label="Send"
|
|
301
|
+
disabled={disabled || !value.trim()}
|
|
302
|
+
onClick={() => onSubmit?.()}
|
|
303
|
+
>
|
|
304
|
+
<Icon icon={ArrowUp02Icon} variant="stroke" />
|
|
305
|
+
</Button>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface ChatSuggestItem {
|
|
313
|
+
id: string;
|
|
314
|
+
label: ReactNode;
|
|
315
|
+
hint?: ReactNode;
|
|
316
|
+
icon?: AppIcon;
|
|
317
|
+
group?: string;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function ChatSuggest({
|
|
321
|
+
open,
|
|
322
|
+
items,
|
|
323
|
+
activeIndex = 0,
|
|
324
|
+
onActiveIndexChange,
|
|
325
|
+
onSelect,
|
|
326
|
+
emptyLabel = "No results",
|
|
327
|
+
className,
|
|
328
|
+
}: {
|
|
329
|
+
open: boolean;
|
|
330
|
+
items: ChatSuggestItem[];
|
|
331
|
+
activeIndex?: number;
|
|
332
|
+
onActiveIndexChange?: (index: number) => void;
|
|
333
|
+
onSelect: (item: ChatSuggestItem) => void;
|
|
334
|
+
emptyLabel?: string;
|
|
335
|
+
className?: string;
|
|
336
|
+
}) {
|
|
337
|
+
if (!open) return null;
|
|
338
|
+
|
|
339
|
+
const groups: { name?: string; items: { item: ChatSuggestItem; index: number }[] }[] = [];
|
|
340
|
+
let flat = 0;
|
|
341
|
+
for (const item of items) {
|
|
342
|
+
const last = groups[groups.length - 1];
|
|
343
|
+
if (last && last.name === item.group) {
|
|
344
|
+
last.items.push({ item, index: flat });
|
|
345
|
+
} else {
|
|
346
|
+
groups.push({ name: item.group, items: [{ item, index: flat }] });
|
|
347
|
+
}
|
|
348
|
+
flat += 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div
|
|
353
|
+
role="listbox"
|
|
354
|
+
className={cn(
|
|
355
|
+
"border-border bg-bg-elevated/70 animate-[item-in_var(--motion-duration)_var(--motion-ease)] z-[var(--z-popover)] max-h-64 overflow-y-auto overscroll-contain rounded-[var(--radius-md)] border p-1 shadow-[var(--shadow-md)] backdrop-blur-2xl outline-none",
|
|
356
|
+
className,
|
|
357
|
+
)}
|
|
358
|
+
>
|
|
359
|
+
{items.length === 0 ? (
|
|
360
|
+
<p className="text-fg-muted px-2 py-3 text-center text-sm">{emptyLabel}</p>
|
|
361
|
+
) : (
|
|
362
|
+
groups.map((group, groupIndex) => (
|
|
363
|
+
<div key={group.name ?? groupIndex}>
|
|
364
|
+
{group.name ? (
|
|
365
|
+
<p className="text-fg-subtle px-2 py-1.5 text-[11px] font-medium tracking-wider uppercase">{group.name}</p>
|
|
366
|
+
) : null}
|
|
367
|
+
{group.items.map(({ item, index }) => (
|
|
368
|
+
<button
|
|
369
|
+
key={item.id}
|
|
370
|
+
type="button"
|
|
371
|
+
role="option"
|
|
372
|
+
aria-selected={index === activeIndex}
|
|
373
|
+
className={cn(
|
|
374
|
+
"flex w-full cursor-pointer items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5 text-sm outline-none",
|
|
375
|
+
index === activeIndex ? "bg-bg-subtle text-fg" : "text-fg-muted",
|
|
376
|
+
)}
|
|
377
|
+
onMouseEnter={() => onActiveIndexChange?.(index)}
|
|
378
|
+
onClick={() => onSelect(item)}
|
|
379
|
+
>
|
|
380
|
+
{item.icon ? <Icon icon={item.icon} variant="stroke" size={16} className="text-fg-muted shrink-0" /> : null}
|
|
381
|
+
<span className="min-w-0 flex-1 truncate text-left">{item.label}</span>
|
|
382
|
+
{item.hint ? <span className="text-fg-subtle shrink-0 text-xs">{item.hint}</span> : null}
|
|
383
|
+
</button>
|
|
384
|
+
))}
|
|
385
|
+
</div>
|
|
386
|
+
))
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function ChatMedia({
|
|
393
|
+
images,
|
|
394
|
+
pinned = false,
|
|
395
|
+
onPin,
|
|
396
|
+
actions,
|
|
397
|
+
className,
|
|
398
|
+
}: {
|
|
399
|
+
images: ViewerImage[];
|
|
400
|
+
pinned?: boolean;
|
|
401
|
+
onPin?: () => void;
|
|
402
|
+
actions?: ReactNode;
|
|
403
|
+
className?: string;
|
|
404
|
+
}) {
|
|
405
|
+
const single = images.length === 1;
|
|
406
|
+
if (images.length === 0) return null;
|
|
407
|
+
return (
|
|
408
|
+
<div className={cn("group/media relative max-w-lg", className)}>
|
|
409
|
+
{single ? (
|
|
410
|
+
<Image src={images[0].src} alt={images[0].alt} name={images[0].name} className="max-h-80 w-auto rounded-[var(--radius-md)] object-cover" />
|
|
411
|
+
) : (
|
|
412
|
+
<ImageGallery images={images} />
|
|
413
|
+
)}
|
|
414
|
+
{pinned ? (
|
|
415
|
+
<Badge tone="accent" className="absolute top-2 left-2 gap-1 py-0.5">
|
|
416
|
+
<Icon icon={Pin02SolidIcon} size={12} />
|
|
417
|
+
Pinned
|
|
418
|
+
</Badge>
|
|
419
|
+
) : null}
|
|
420
|
+
{onPin || actions ? (
|
|
421
|
+
<div
|
|
422
|
+
className={cn(
|
|
423
|
+
"absolute top-2 right-2 flex items-center gap-1 transition-opacity duration-[var(--motion-duration)] ease-[var(--motion-ease)] focus-within:opacity-100 group-hover/media:opacity-100",
|
|
424
|
+
pinned ? "opacity-100" : "opacity-0",
|
|
425
|
+
)}
|
|
426
|
+
>
|
|
427
|
+
{onPin ? (
|
|
428
|
+
<button
|
|
429
|
+
type="button"
|
|
430
|
+
aria-label={pinned ? "Unpin from context" : "Pin to context"}
|
|
431
|
+
aria-pressed={pinned}
|
|
432
|
+
onClick={onPin}
|
|
433
|
+
className={cn(
|
|
434
|
+
"border-border bg-bg-elevated/80 text-fg-muted hover:text-fg pointer-events-auto inline-flex size-7 cursor-pointer items-center justify-center rounded-[var(--radius-sm)] border backdrop-blur-md",
|
|
435
|
+
pinned && "text-accent",
|
|
436
|
+
)}
|
|
437
|
+
>
|
|
438
|
+
<Icon icon={pinned ? Pin02SolidIcon : Pin02Icon} size={14} />
|
|
439
|
+
</button>
|
|
440
|
+
) : null}
|
|
441
|
+
{actions}
|
|
442
|
+
</div>
|
|
443
|
+
) : null}
|
|
444
|
+
</div>
|
|
445
|
+
);
|
|
446
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,17 @@ export {
|
|
|
20
20
|
useSidebar,
|
|
21
21
|
} from "./components/sidebar";
|
|
22
22
|
export { Card, CardDescription, CardHeader, CardTitle } from "./components/card";
|
|
23
|
+
export {
|
|
24
|
+
ChatActions,
|
|
25
|
+
ChatComposer,
|
|
26
|
+
ChatDiff,
|
|
27
|
+
ChatMedia,
|
|
28
|
+
ChatMessage,
|
|
29
|
+
ChatSuggest,
|
|
30
|
+
ChatThinking,
|
|
31
|
+
ChatToolCall,
|
|
32
|
+
} from "./components/chat";
|
|
33
|
+
export type { ChatSuggestItem, ChatToolStatus } from "./components/chat";
|
|
23
34
|
export { BootScreen, PageLoader } from "./components/page-loader";
|
|
24
35
|
export { Breadcrumb } from "./components/breadcrumb";
|
|
25
36
|
export type { BreadcrumbItem } from "./components/breadcrumb";
|
package/src/styles.css
CHANGED