@hogoshi/ui 0.1.9 → 0.1.11
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 +238 -58
- package/src/components/code-block.tsx +10 -2
- package/src/index.ts +2 -1
package/package.json
CHANGED
package/src/components/chat.tsx
CHANGED
|
@@ -4,26 +4,24 @@ import {
|
|
|
4
4
|
Alert01Icon,
|
|
5
5
|
ArrowDown01Icon,
|
|
6
6
|
ArrowUp02Icon,
|
|
7
|
+
Attachment01Icon,
|
|
7
8
|
Cancel01Icon,
|
|
8
|
-
|
|
9
|
+
File01Icon,
|
|
10
|
+
FlashIcon,
|
|
9
11
|
StopCircleIcon,
|
|
10
12
|
Tick02Icon,
|
|
13
|
+
Upload04Icon,
|
|
11
14
|
} from "@hugeicons-pro/core-stroke-rounded";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
useEffect,
|
|
15
|
-
useRef,
|
|
16
|
-
type ComponentProps,
|
|
17
|
-
type ReactNode,
|
|
18
|
-
} from "react";
|
|
15
|
+
import { useEffect, useRef, useState, type ComponentProps, type ReactNode } from "react";
|
|
19
16
|
import { cn } from "../lib/cn";
|
|
20
17
|
import type { AppIcon } from "./icon";
|
|
21
18
|
import { Icon } from "./icon";
|
|
22
|
-
import { Image, ImageGallery, type ViewerImage } from "./image-viewer";
|
|
19
|
+
import { Image, ImageGallery, ImageViewer, type ViewerImage } from "./image-viewer";
|
|
23
20
|
import { Button } from "./button";
|
|
24
21
|
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "./collapsible";
|
|
22
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu";
|
|
23
|
+
import { Separator } from "./separator";
|
|
25
24
|
import { Spinner } from "./spinner";
|
|
26
|
-
import { Badge } from "./badge";
|
|
27
25
|
|
|
28
26
|
const workingText =
|
|
29
27
|
"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";
|
|
@@ -40,16 +38,52 @@ function ChatCaret() {
|
|
|
40
38
|
export function ChatMessage({
|
|
41
39
|
role,
|
|
42
40
|
streaming = false,
|
|
41
|
+
attachments,
|
|
42
|
+
actions,
|
|
43
43
|
className,
|
|
44
44
|
children,
|
|
45
45
|
}: {
|
|
46
46
|
role: "user" | "assistant";
|
|
47
47
|
streaming?: boolean;
|
|
48
|
+
attachments?: ViewerImage[];
|
|
49
|
+
actions?: ReactNode;
|
|
48
50
|
className?: string;
|
|
49
51
|
children: ReactNode;
|
|
50
52
|
}) {
|
|
53
|
+
const [viewing, setViewing] = useState(false);
|
|
54
|
+
const [viewIndex, setViewIndex] = useState(0);
|
|
55
|
+
|
|
51
56
|
return (
|
|
52
|
-
<div className={cn("group/chat flex w-full flex-col", role === "user" && "items-end", className)}>
|
|
57
|
+
<div className={cn("group/chat flex w-full min-w-0 flex-col", role === "user" && "items-end", className)}>
|
|
58
|
+
{attachments?.length ? (
|
|
59
|
+
<div
|
|
60
|
+
className={cn(
|
|
61
|
+
"mb-2 flex w-full max-w-[85%] flex-wrap gap-1.5",
|
|
62
|
+
role === "user" ? "justify-end" : "justify-start",
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
{attachments.map((image, at) => (
|
|
66
|
+
<button
|
|
67
|
+
key={`${image.src}-${at}`}
|
|
68
|
+
type="button"
|
|
69
|
+
aria-label={`Open ${image.alt ?? "attachment"}`}
|
|
70
|
+
className="group/attachment border-border bg-bg-subtle focus-visible:ring-ring size-16 cursor-zoom-in overflow-hidden rounded-[var(--radius-sm)] border outline-none focus-visible:ring-2"
|
|
71
|
+
onClick={() => {
|
|
72
|
+
setViewIndex(at);
|
|
73
|
+
setViewing(true);
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<img
|
|
77
|
+
src={image.src}
|
|
78
|
+
alt={image.alt ?? ""}
|
|
79
|
+
loading="lazy"
|
|
80
|
+
draggable={false}
|
|
81
|
+
className="size-full object-cover transition-transform duration-[var(--motion-duration)] ease-[var(--motion-ease)] group-hover/attachment:scale-[1.05]"
|
|
82
|
+
/>
|
|
83
|
+
</button>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
) : null}
|
|
53
87
|
{role === "user" ? (
|
|
54
88
|
<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
89
|
{children}
|
|
@@ -61,6 +95,16 @@ export function ChatMessage({
|
|
|
61
95
|
{streaming ? <ChatCaret /> : null}
|
|
62
96
|
</div>
|
|
63
97
|
)}
|
|
98
|
+
{actions}
|
|
99
|
+
{attachments?.length ? (
|
|
100
|
+
<ImageViewer
|
|
101
|
+
images={attachments}
|
|
102
|
+
index={viewIndex}
|
|
103
|
+
open={viewing}
|
|
104
|
+
onOpenChange={setViewing}
|
|
105
|
+
onIndexChange={setViewIndex}
|
|
106
|
+
/>
|
|
107
|
+
) : null}
|
|
64
108
|
</div>
|
|
65
109
|
);
|
|
66
110
|
}
|
|
@@ -97,7 +141,7 @@ export function ChatThinking({
|
|
|
97
141
|
|
|
98
142
|
if (!reasoning) {
|
|
99
143
|
return (
|
|
100
|
-
<div className={cn("flex items-center gap-2", className)}>
|
|
144
|
+
<div className={cn("flex min-w-0 items-center gap-2", className)}>
|
|
101
145
|
{attention ? (
|
|
102
146
|
<Icon
|
|
103
147
|
icon={Alert01Icon}
|
|
@@ -112,17 +156,17 @@ export function ChatThinking({
|
|
|
112
156
|
}
|
|
113
157
|
|
|
114
158
|
return (
|
|
115
|
-
<Collapsible defaultOpen={defaultOpen} className={className}>
|
|
116
|
-
<CollapsibleTrigger className="group/thinking py-0.5">
|
|
159
|
+
<Collapsible defaultOpen={defaultOpen} className={cn("w-full min-w-0", className)}>
|
|
160
|
+
<CollapsibleTrigger className="group/thinking min-w-0 gap-2 py-0.5">
|
|
117
161
|
<Icon
|
|
118
162
|
icon={ArrowDown01Icon}
|
|
119
163
|
variant="stroke"
|
|
120
164
|
size={12}
|
|
121
|
-
className="text-fg-subtle transition-transform duration-[var(--motion-duration)] ease-[var(--motion-ease)] group-data-[panel-open]/thinking:rotate-180"
|
|
165
|
+
className="text-fg-subtle shrink-0 transition-transform duration-[var(--motion-duration)] ease-[var(--motion-ease)] group-data-[panel-open]/thinking:rotate-180"
|
|
122
166
|
/>
|
|
123
|
-
{label}
|
|
167
|
+
<span className="truncate">{label}</span>
|
|
124
168
|
</CollapsibleTrigger>
|
|
125
|
-
<CollapsiblePanel className="hogoshi-selectable text-fg-muted py-1 pl-5 text-xs leading-relaxed">
|
|
169
|
+
<CollapsiblePanel className="hogoshi-selectable text-fg-muted min-w-0 py-1 pl-5 text-xs leading-relaxed">
|
|
126
170
|
{reasoning}
|
|
127
171
|
</CollapsiblePanel>
|
|
128
172
|
</Collapsible>
|
|
@@ -173,8 +217,8 @@ export function ChatToolCall({
|
|
|
173
217
|
}
|
|
174
218
|
|
|
175
219
|
return (
|
|
176
|
-
<Collapsible defaultOpen={defaultOpen} className={className}>
|
|
177
|
-
<CollapsibleTrigger className="group/tool gap-2 py-0.5">
|
|
220
|
+
<Collapsible defaultOpen={defaultOpen} className={cn("w-full min-w-0", className)}>
|
|
221
|
+
<CollapsibleTrigger className="group/tool min-w-0 gap-2 py-0.5">
|
|
178
222
|
<ToolStatusIcon status={status} />
|
|
179
223
|
<span className="text-fg truncate text-sm">{label}</span>
|
|
180
224
|
{meta ? <span className="text-fg-subtle min-w-0 truncate text-xs">{meta}</span> : null}
|
|
@@ -185,7 +229,7 @@ export function ChatToolCall({
|
|
|
185
229
|
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
230
|
/>
|
|
187
231
|
</CollapsibleTrigger>
|
|
188
|
-
<CollapsiblePanel className="hogoshi-selectable my-1 ml-[7px] border-border border-l py-1 pr-1 pl-3.5">
|
|
232
|
+
<CollapsiblePanel className="hogoshi-selectable my-1 ml-[7px] min-w-0 border-border border-l py-1 pr-1 pl-3.5">
|
|
189
233
|
{children}
|
|
190
234
|
</CollapsiblePanel>
|
|
191
235
|
</Collapsible>
|
|
@@ -215,7 +259,7 @@ export function ChatDiff({ diff, className, ...props }: { diff: string } & Compo
|
|
|
215
259
|
return (
|
|
216
260
|
<div
|
|
217
261
|
className={cn(
|
|
218
|
-
"border-border bg-bg-subtle max-h-72 overflow-auto rounded-[var(--radius-md)] border font-mono text-xs",
|
|
262
|
+
"border-border bg-bg-subtle max-h-72 min-w-0 w-full overflow-auto rounded-[var(--radius-md)] border font-mono text-xs",
|
|
219
263
|
className,
|
|
220
264
|
)}
|
|
221
265
|
{...props}
|
|
@@ -229,6 +273,139 @@ export function ChatDiff({ diff, className, ...props }: { diff: string } & Compo
|
|
|
229
273
|
);
|
|
230
274
|
}
|
|
231
275
|
|
|
276
|
+
export interface ChatContextItem {
|
|
277
|
+
id: string;
|
|
278
|
+
label: string;
|
|
279
|
+
src?: string;
|
|
280
|
+
icon?: AppIcon;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface ChatModelOption {
|
|
284
|
+
id: string;
|
|
285
|
+
label: string;
|
|
286
|
+
hint?: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function ContextChip({ item, onRemove }: { item: ChatContextItem; onRemove?: () => void }) {
|
|
290
|
+
return (
|
|
291
|
+
<span className="border-border bg-bg-subtle text-fg inline-flex h-7 max-w-[16rem] min-w-0 items-center gap-1.5 rounded-full border py-1 pr-1 pl-2.5">
|
|
292
|
+
{item.src ? (
|
|
293
|
+
<img src={item.src} alt="" draggable={false} className="size-4 shrink-0 rounded-full object-cover" />
|
|
294
|
+
) : (
|
|
295
|
+
<Icon icon={item.icon ?? File01Icon} variant="stroke" size={12} className="text-fg-muted shrink-0" />
|
|
296
|
+
)}
|
|
297
|
+
<span className="min-w-0 truncate text-xs">{item.label}</span>
|
|
298
|
+
{onRemove ? (
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
aria-label={`Remove ${item.label}`}
|
|
302
|
+
onClick={onRemove}
|
|
303
|
+
className="text-fg-muted hover:text-fg focus-visible:ring-ring inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full outline-none focus-visible:ring-2"
|
|
304
|
+
>
|
|
305
|
+
<Icon icon={Cancel01Icon} variant="stroke" size={12} />
|
|
306
|
+
</button>
|
|
307
|
+
) : null}
|
|
308
|
+
</span>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function ModelMenu({
|
|
313
|
+
model,
|
|
314
|
+
models,
|
|
315
|
+
onSelect,
|
|
316
|
+
}: {
|
|
317
|
+
model: ChatModelOption;
|
|
318
|
+
models: ChatModelOption[];
|
|
319
|
+
onSelect?: (id: string) => void;
|
|
320
|
+
}) {
|
|
321
|
+
return (
|
|
322
|
+
<DropdownMenu>
|
|
323
|
+
<DropdownMenuTrigger
|
|
324
|
+
render={
|
|
325
|
+
<Button size="sm" variant="ghost" aria-label="Select model" className="text-fg-muted h-7 px-1.5" />
|
|
326
|
+
}
|
|
327
|
+
>
|
|
328
|
+
<span className="inline-flex items-center gap-1.5">
|
|
329
|
+
<Icon icon={FlashIcon} variant="stroke" size={14} className="text-fg-subtle shrink-0" />
|
|
330
|
+
<span className="max-w-40 truncate text-xs">{model.label}</span>
|
|
331
|
+
</span>
|
|
332
|
+
</DropdownMenuTrigger>
|
|
333
|
+
<DropdownMenuContent>
|
|
334
|
+
{models.map((option) => (
|
|
335
|
+
<DropdownMenuItem key={option.id} onClick={() => onSelect?.(option.id)}>
|
|
336
|
+
{option.id === model.id ? (
|
|
337
|
+
<Icon icon={Tick02Icon} variant="stroke" size={16} className="text-accent shrink-0" />
|
|
338
|
+
) : (
|
|
339
|
+
<span aria-hidden className="size-4 shrink-0" />
|
|
340
|
+
)}
|
|
341
|
+
<span className="truncate">{option.label}</span>
|
|
342
|
+
{option.hint ? <span className="text-fg-subtle ml-auto text-xs">{option.hint}</span> : null}
|
|
343
|
+
</DropdownMenuItem>
|
|
344
|
+
))}
|
|
345
|
+
</DropdownMenuContent>
|
|
346
|
+
</DropdownMenu>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function ChatAttach({
|
|
351
|
+
items = [],
|
|
352
|
+
onInsert,
|
|
353
|
+
onUpload,
|
|
354
|
+
accept,
|
|
355
|
+
multiple = true,
|
|
356
|
+
className,
|
|
357
|
+
}: {
|
|
358
|
+
items?: ChatSuggestItem[];
|
|
359
|
+
onInsert?: (id: string) => void;
|
|
360
|
+
onUpload?: (files: File[]) => void;
|
|
361
|
+
accept?: string;
|
|
362
|
+
multiple?: boolean;
|
|
363
|
+
className?: string;
|
|
364
|
+
}) {
|
|
365
|
+
const input = useRef<HTMLInputElement>(null);
|
|
366
|
+
return (
|
|
367
|
+
<>
|
|
368
|
+
<input
|
|
369
|
+
ref={input}
|
|
370
|
+
type="file"
|
|
371
|
+
accept={accept}
|
|
372
|
+
multiple={multiple}
|
|
373
|
+
className="hidden"
|
|
374
|
+
onChange={(event) => {
|
|
375
|
+
const files = [...(event.target.files ?? [])];
|
|
376
|
+
event.target.value = "";
|
|
377
|
+
if (files.length) onUpload?.(files);
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
<DropdownMenu>
|
|
381
|
+
<DropdownMenuTrigger render={<Button size="icon-sm" variant="ghost" aria-label="Attach" className={className} />}>
|
|
382
|
+
<Icon icon={Attachment01Icon} variant="stroke" />
|
|
383
|
+
</DropdownMenuTrigger>
|
|
384
|
+
<DropdownMenuContent>
|
|
385
|
+
{items.length ? (
|
|
386
|
+
<>
|
|
387
|
+
<p className="text-fg-subtle px-2 py-1.5 text-[11px] font-medium tracking-wider uppercase">Project files</p>
|
|
388
|
+
<div className="max-h-60 overflow-y-auto overscroll-contain">
|
|
389
|
+
{items.map((item) => (
|
|
390
|
+
<DropdownMenuItem key={item.id} onClick={() => onInsert?.(item.id)}>
|
|
391
|
+
<Icon icon={item.icon ?? File01Icon} variant="stroke" size={16} className="text-fg-muted shrink-0" />
|
|
392
|
+
<span className="min-w-0 truncate">{item.label}</span>
|
|
393
|
+
</DropdownMenuItem>
|
|
394
|
+
))}
|
|
395
|
+
</div>
|
|
396
|
+
<Separator className="my-1" />
|
|
397
|
+
</>
|
|
398
|
+
) : null}
|
|
399
|
+
<DropdownMenuItem onClick={() => input.current?.click()}>
|
|
400
|
+
<Icon icon={Upload04Icon} variant="stroke" size={16} className="text-fg-muted shrink-0" />
|
|
401
|
+
Upload from computer
|
|
402
|
+
</DropdownMenuItem>
|
|
403
|
+
</DropdownMenuContent>
|
|
404
|
+
</DropdownMenu>
|
|
405
|
+
</>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
232
409
|
export function ChatComposer({
|
|
233
410
|
value,
|
|
234
411
|
onValueChange,
|
|
@@ -238,6 +415,11 @@ export function ChatComposer({
|
|
|
238
415
|
busy = false,
|
|
239
416
|
placeholder,
|
|
240
417
|
disabled,
|
|
418
|
+
context,
|
|
419
|
+
onRemoveContext,
|
|
420
|
+
model,
|
|
421
|
+
models,
|
|
422
|
+
onModelChange,
|
|
241
423
|
actions,
|
|
242
424
|
className,
|
|
243
425
|
}: {
|
|
@@ -249,6 +431,11 @@ export function ChatComposer({
|
|
|
249
431
|
busy?: boolean;
|
|
250
432
|
placeholder?: string;
|
|
251
433
|
disabled?: boolean;
|
|
434
|
+
context?: ChatContextItem[];
|
|
435
|
+
onRemoveContext?: (id: string) => void;
|
|
436
|
+
model?: ChatModelOption;
|
|
437
|
+
models?: ChatModelOption[];
|
|
438
|
+
onModelChange?: (id: string) => void;
|
|
252
439
|
actions?: ReactNode;
|
|
253
440
|
className?: string;
|
|
254
441
|
}) {
|
|
@@ -268,6 +455,17 @@ export function ChatComposer({
|
|
|
268
455
|
className,
|
|
269
456
|
)}
|
|
270
457
|
>
|
|
458
|
+
{context?.length ? (
|
|
459
|
+
<div className="flex flex-wrap gap-1.5 px-2.5 pt-2.5">
|
|
460
|
+
{context.map((item) => (
|
|
461
|
+
<ContextChip
|
|
462
|
+
key={item.id}
|
|
463
|
+
item={item}
|
|
464
|
+
onRemove={onRemoveContext ? () => onRemoveContext(item.id) : undefined}
|
|
465
|
+
/>
|
|
466
|
+
))}
|
|
467
|
+
</div>
|
|
468
|
+
) : null}
|
|
271
469
|
<textarea
|
|
272
470
|
ref={textarea}
|
|
273
471
|
rows={1}
|
|
@@ -283,10 +481,16 @@ export function ChatComposer({
|
|
|
283
481
|
onSubmit?.();
|
|
284
482
|
}
|
|
285
483
|
}}
|
|
286
|
-
className=
|
|
484
|
+
className={cn(
|
|
485
|
+
"placeholder:text-fg-subtle max-h-48 w-full resize-none bg-transparent px-3.5 pb-2 text-sm leading-relaxed outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
486
|
+
context?.length ? "pt-2.5" : "pt-3",
|
|
487
|
+
)}
|
|
287
488
|
/>
|
|
288
489
|
<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">
|
|
490
|
+
<div className="flex min-w-0 items-center gap-0.5">
|
|
491
|
+
{model && models?.length ? <ModelMenu model={model} models={models} onSelect={onModelChange} /> : null}
|
|
492
|
+
{actions}
|
|
493
|
+
</div>
|
|
290
494
|
{busy ? (
|
|
291
495
|
onStop ? (
|
|
292
496
|
<Button size="icon-sm" variant="ghost" aria-label="Stop" onClick={onStop}>
|
|
@@ -391,53 +595,29 @@ export function ChatSuggest({
|
|
|
391
595
|
|
|
392
596
|
export function ChatMedia({
|
|
393
597
|
images,
|
|
394
|
-
pinned = false,
|
|
395
|
-
onPin,
|
|
396
598
|
actions,
|
|
397
599
|
className,
|
|
398
600
|
}: {
|
|
399
601
|
images: ViewerImage[];
|
|
400
|
-
pinned?: boolean;
|
|
401
|
-
onPin?: () => void;
|
|
402
602
|
actions?: ReactNode;
|
|
403
603
|
className?: string;
|
|
404
604
|
}) {
|
|
405
|
-
const single = images.length === 1;
|
|
406
605
|
if (images.length === 0) return null;
|
|
606
|
+
const single = images.length === 1;
|
|
407
607
|
return (
|
|
408
|
-
<div className={cn("group/media relative max-w-lg", className)}>
|
|
608
|
+
<div className={cn("group/media relative min-w-0 max-w-lg", className)}>
|
|
409
609
|
{single ? (
|
|
410
|
-
<Image
|
|
610
|
+
<Image
|
|
611
|
+
src={images[0].src}
|
|
612
|
+
alt={images[0].alt}
|
|
613
|
+
name={images[0].name}
|
|
614
|
+
className="border-border max-h-48 w-auto max-w-full rounded-[var(--radius-md)] border object-cover"
|
|
615
|
+
/>
|
|
411
616
|
) : (
|
|
412
|
-
<ImageGallery images={images} />
|
|
617
|
+
<ImageGallery images={images} className="min-w-0" />
|
|
413
618
|
)}
|
|
414
|
-
{
|
|
415
|
-
<
|
|
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}
|
|
619
|
+
{actions ? (
|
|
620
|
+
<div className="absolute top-2 right-2 flex items-center gap-1 opacity-0 transition-opacity duration-[var(--motion-duration)] ease-[var(--motion-ease)] focus-within:opacity-100 group-hover/media:opacity-100">
|
|
441
621
|
{actions}
|
|
442
622
|
</div>
|
|
443
623
|
) : null}
|
|
@@ -59,12 +59,14 @@ export function CodeBlock({
|
|
|
59
59
|
language = "auto",
|
|
60
60
|
variant = "full",
|
|
61
61
|
showLanguage = true,
|
|
62
|
+
scroll = false,
|
|
62
63
|
className,
|
|
63
64
|
}: {
|
|
64
65
|
code: string;
|
|
65
66
|
language?: string;
|
|
66
67
|
variant?: "full" | "snippet";
|
|
67
68
|
showLanguage?: boolean;
|
|
69
|
+
scroll?: boolean;
|
|
68
70
|
className?: string;
|
|
69
71
|
}) {
|
|
70
72
|
const [copied, setCopied] = useState(false);
|
|
@@ -110,8 +112,14 @@ export function CodeBlock({
|
|
|
110
112
|
<TooltipContent>{copied ? "Copied" : "Copy"}</TooltipContent>
|
|
111
113
|
</Tooltip>
|
|
112
114
|
</div>
|
|
113
|
-
<pre
|
|
114
|
-
|
|
115
|
+
<pre
|
|
116
|
+
className={cn(
|
|
117
|
+
"hogoshi-code w-full min-w-0 max-w-full p-4 pr-24 font-mono text-xs",
|
|
118
|
+
scroll ? "max-h-[28rem] overflow-auto whitespace-pre" : "wrap-anywhere whitespace-pre-wrap",
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
<code className={scroll ? "whitespace-pre" : "wrap-anywhere whitespace-pre-wrap"} dangerouslySetInnerHTML={{ __html: painted.html }} />
|
|
115
123
|
</pre>
|
|
116
124
|
</div>
|
|
117
125
|
);
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
export { Card, CardDescription, CardHeader, CardTitle } from "./components/card";
|
|
23
23
|
export {
|
|
24
24
|
ChatActions,
|
|
25
|
+
ChatAttach,
|
|
25
26
|
ChatComposer,
|
|
26
27
|
ChatDiff,
|
|
27
28
|
ChatMedia,
|
|
@@ -30,7 +31,7 @@ export {
|
|
|
30
31
|
ChatThinking,
|
|
31
32
|
ChatToolCall,
|
|
32
33
|
} from "./components/chat";
|
|
33
|
-
export type { ChatSuggestItem, ChatToolStatus } from "./components/chat";
|
|
34
|
+
export type { ChatContextItem, ChatModelOption, ChatSuggestItem, ChatToolStatus } from "./components/chat";
|
|
34
35
|
export { BootScreen, PageLoader } from "./components/page-loader";
|
|
35
36
|
export { Breadcrumb } from "./components/breadcrumb";
|
|
36
37
|
export type { BreadcrumbItem } from "./components/breadcrumb";
|