@codrstudio/openclaude-chat 0.1.0 → 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/dist/components/StreamingIndicator.js +5 -5
- package/dist/display/DisplayReactRenderer.js +12 -12
- package/dist/display/react-sandbox/bootstrap.js +150 -150
- package/dist/styles.css +1 -2
- package/package.json +64 -61
- package/src/components/Chat.tsx +107 -107
- package/src/components/ErrorNote.tsx +35 -35
- package/src/components/LazyRender.tsx +42 -42
- package/src/components/Markdown.tsx +114 -114
- package/src/components/MessageBubble.tsx +107 -107
- package/src/components/MessageInput.tsx +421 -421
- package/src/components/MessageList.tsx +153 -153
- package/src/components/StreamingIndicator.tsx +19 -19
- package/src/display/AlertRenderer.tsx +23 -23
- package/src/display/CarouselRenderer.tsx +141 -141
- package/src/display/ChartRenderer.tsx +195 -195
- package/src/display/ChoiceButtonsRenderer.tsx +114 -114
- package/src/display/CodeBlockRenderer.tsx +49 -49
- package/src/display/ComparisonTableRenderer.tsx +132 -132
- package/src/display/DataTableRenderer.tsx +144 -144
- package/src/display/DisplayReactRenderer.tsx +269 -269
- package/src/display/FileCardRenderer.tsx +55 -55
- package/src/display/GalleryRenderer.tsx +65 -65
- package/src/display/ImageViewerRenderer.tsx +114 -114
- package/src/display/LinkPreviewRenderer.tsx +74 -74
- package/src/display/MapViewRenderer.tsx +75 -75
- package/src/display/MetricCardRenderer.tsx +29 -29
- package/src/display/PriceHighlightRenderer.tsx +62 -62
- package/src/display/ProductCardRenderer.tsx +112 -112
- package/src/display/ProgressStepsRenderer.tsx +59 -59
- package/src/display/SourcesListRenderer.tsx +47 -47
- package/src/display/SpreadsheetRenderer.tsx +86 -86
- package/src/display/StepTimelineRenderer.tsx +75 -75
- package/src/display/index.ts +21 -21
- package/src/display/react-sandbox/bootstrap.ts +155 -155
- package/src/display/registry.ts +84 -84
- package/src/display/sdk-types.ts +217 -217
- package/src/hooks/ChatProvider.tsx +21 -21
- package/src/hooks/useIsMobile.ts +15 -15
- package/src/hooks/useOpenClaudeChat.ts +476 -476
- package/src/index.ts +76 -76
- package/src/lib/utils.ts +6 -6
- package/src/parts/PartErrorBoundary.tsx +51 -51
- package/src/parts/PartRenderer.tsx +145 -145
- package/src/parts/ReasoningBlock.tsx +41 -41
- package/src/parts/ToolActivity.tsx +78 -78
- package/src/parts/ToolResult.tsx +79 -79
- package/src/styles.css +2 -2
- package/src/types.ts +41 -41
- package/src/ui/alert.tsx +77 -77
- package/src/ui/badge.tsx +36 -36
- package/src/ui/button.tsx +54 -54
- package/src/ui/card.tsx +68 -68
- package/src/ui/collapsible.tsx +7 -7
- package/src/ui/dialog.tsx +122 -122
- package/src/ui/dropdown-menu.tsx +76 -76
- package/src/ui/input.tsx +24 -24
- package/src/ui/progress.tsx +36 -36
- package/src/ui/scroll-area.tsx +48 -48
- package/src/ui/separator.tsx +31 -31
- package/src/ui/skeleton.tsx +9 -9
- package/src/ui/table.tsx +114 -114
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
2
|
-
import { useRef, useEffect, useMemo, useCallback, type ReactNode } from "react";
|
|
3
|
-
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
-
import { Sparkles } from "lucide-react";
|
|
5
|
-
import type { Message } from "../types.js";
|
|
6
|
-
import { MessageBubble } from "./MessageBubble.js";
|
|
7
|
-
import { StreamingIndicator } from "./StreamingIndicator.js";
|
|
8
|
-
import { ErrorNote } from "./ErrorNote.js";
|
|
9
|
-
import type { DisplayRendererMap } from "../display/registry.js";
|
|
10
|
-
import { ScrollBar } from "../ui/scroll-area.js";
|
|
11
|
-
import { cn } from "../lib/utils.js";
|
|
12
|
-
|
|
13
|
-
export interface MessageListProps {
|
|
14
|
-
messages: Message[];
|
|
15
|
-
isLoading?: boolean;
|
|
16
|
-
displayRenderers?: DisplayRendererMap;
|
|
17
|
-
className?: string;
|
|
18
|
-
error?: Error;
|
|
19
|
-
onRetry?: () => void;
|
|
20
|
-
emptyState?: ReactNode;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function DefaultWelcome() {
|
|
24
|
-
return (
|
|
25
|
-
<div className="flex h-full flex-col items-center justify-center gap-4 px-6 py-16 text-center">
|
|
26
|
-
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary/10 text-primary ring-1 ring-primary/20">
|
|
27
|
-
<Sparkles className="size-7" />
|
|
28
|
-
</div>
|
|
29
|
-
<div className="space-y-1.5">
|
|
30
|
-
<h2 className="text-xl font-semibold tracking-tight">Como posso ajudar?</h2>
|
|
31
|
-
<p className="max-w-sm text-sm text-muted-foreground">
|
|
32
|
-
Envie uma mensagem para comecar a conversa. Voce pode pedir respostas, acionar ferramentas ou colar conteudo para analise.
|
|
33
|
-
</p>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function MessageList({ messages, isLoading, displayRenderers,className, error, onRetry, emptyState }: MessageListProps) {
|
|
40
|
-
const viewportRef = useRef<HTMLDivElement>(null);
|
|
41
|
-
const isFollowingRef = useRef(true);
|
|
42
|
-
|
|
43
|
-
const lastAssistantIndex = useMemo(() =>
|
|
44
|
-
messages.reduceRight((found, msg, i) => {
|
|
45
|
-
if (found !== -1) return found;
|
|
46
|
-
return msg.role === "assistant" ? i : -1;
|
|
47
|
-
}, -1),
|
|
48
|
-
[messages]
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const virtualizer = useVirtualizer({
|
|
52
|
-
count: messages.length,
|
|
53
|
-
getScrollElement: () => viewportRef.current,
|
|
54
|
-
estimateSize: () => 80,
|
|
55
|
-
overscan: 5,
|
|
56
|
-
paddingStart: 16,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Track scroll position to detect if user is following
|
|
60
|
-
const handleScroll = useCallback(() => {
|
|
61
|
-
const viewport = viewportRef.current;
|
|
62
|
-
if (!viewport) return;
|
|
63
|
-
const distanceFromBottom =
|
|
64
|
-
viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
65
|
-
isFollowingRef.current = distanceFromBottom <= 100;
|
|
66
|
-
}, []);
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
const viewport = viewportRef.current;
|
|
70
|
-
if (!viewport) return;
|
|
71
|
-
viewport.addEventListener("scroll", handleScroll, { passive: true });
|
|
72
|
-
return () => viewport.removeEventListener("scroll", handleScroll);
|
|
73
|
-
}, [handleScroll]);
|
|
74
|
-
|
|
75
|
-
// Auto-scroll to bottom when following
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (messages.length > 0 && isFollowingRef.current) {
|
|
78
|
-
virtualizer.scrollToIndex(messages.length - 1, { align: "end" });
|
|
79
|
-
}
|
|
80
|
-
}, [messages, virtualizer]);
|
|
81
|
-
|
|
82
|
-
// Auto-scroll when error appears
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (error && isFollowingRef.current) {
|
|
85
|
-
const viewport = viewportRef.current;
|
|
86
|
-
if (viewport) viewport.scrollTop = viewport.scrollHeight;
|
|
87
|
-
}
|
|
88
|
-
}, [error]);
|
|
89
|
-
|
|
90
|
-
const virtualItems = virtualizer.getVirtualItems();
|
|
91
|
-
|
|
92
|
-
if (messages.length === 0) {
|
|
93
|
-
return (
|
|
94
|
-
<ScrollAreaPrimitive.Root className={cn("flex-1 relative overflow-hidden", className)}>
|
|
95
|
-
<ScrollAreaPrimitive.Viewport ref={viewportRef} className="h-full w-full rounded-[inherit]">
|
|
96
|
-
{emptyState ?? <DefaultWelcome />}
|
|
97
|
-
</ScrollAreaPrimitive.Viewport>
|
|
98
|
-
<ScrollBar />
|
|
99
|
-
<ScrollAreaPrimitive.Corner />
|
|
100
|
-
</ScrollAreaPrimitive.Root>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<ScrollAreaPrimitive.Root className={cn("flex-1 relative overflow-hidden", className)}>
|
|
106
|
-
<ScrollAreaPrimitive.Viewport ref={viewportRef} className="h-full w-full rounded-[inherit]">
|
|
107
|
-
<div
|
|
108
|
-
className="relative w-full"
|
|
109
|
-
style={{ height: virtualizer.getTotalSize() + 16 }}
|
|
110
|
-
>
|
|
111
|
-
<div>
|
|
112
|
-
{virtualItems.map((virtualRow) => {
|
|
113
|
-
const message = messages[virtualRow.index];
|
|
114
|
-
return (
|
|
115
|
-
<div
|
|
116
|
-
key={message.id ?? virtualRow.index}
|
|
117
|
-
data-index={virtualRow.index}
|
|
118
|
-
ref={virtualizer.measureElement}
|
|
119
|
-
className="pb-3 px-4"
|
|
120
|
-
style={{
|
|
121
|
-
position: "absolute",
|
|
122
|
-
top: 0,
|
|
123
|
-
left: 0,
|
|
124
|
-
right: 0,
|
|
125
|
-
transform: `translateY(${virtualRow.start}px)`,
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
<MessageBubble
|
|
129
|
-
message={message}
|
|
130
|
-
isStreaming={virtualRow.index === lastAssistantIndex && isLoading && messages[messages.length - 1]?.role === "assistant"}
|
|
131
|
-
displayRenderers={displayRenderers}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
);
|
|
135
|
-
})}
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
{isLoading && messages[messages.length - 1]?.role !== "assistant" && (
|
|
139
|
-
<div className="px-4 pb-3">
|
|
140
|
-
<StreamingIndicator />
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
{!isLoading && error && (
|
|
144
|
-
<div className="px-4 pb-3">
|
|
145
|
-
<ErrorNote message={error.message} onRetry={onRetry} />
|
|
146
|
-
</div>
|
|
147
|
-
)}
|
|
148
|
-
</ScrollAreaPrimitive.Viewport>
|
|
149
|
-
<ScrollBar />
|
|
150
|
-
<ScrollAreaPrimitive.Corner />
|
|
151
|
-
</ScrollAreaPrimitive.Root>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
1
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
2
|
+
import { useRef, useEffect, useMemo, useCallback, type ReactNode } from "react";
|
|
3
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
+
import { Sparkles } from "lucide-react";
|
|
5
|
+
import type { Message } from "../types.js";
|
|
6
|
+
import { MessageBubble } from "./MessageBubble.js";
|
|
7
|
+
import { StreamingIndicator } from "./StreamingIndicator.js";
|
|
8
|
+
import { ErrorNote } from "./ErrorNote.js";
|
|
9
|
+
import type { DisplayRendererMap } from "../display/registry.js";
|
|
10
|
+
import { ScrollBar } from "../ui/scroll-area.js";
|
|
11
|
+
import { cn } from "../lib/utils.js";
|
|
12
|
+
|
|
13
|
+
export interface MessageListProps {
|
|
14
|
+
messages: Message[];
|
|
15
|
+
isLoading?: boolean;
|
|
16
|
+
displayRenderers?: DisplayRendererMap;
|
|
17
|
+
className?: string;
|
|
18
|
+
error?: Error;
|
|
19
|
+
onRetry?: () => void;
|
|
20
|
+
emptyState?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DefaultWelcome() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex h-full flex-col items-center justify-center gap-4 px-6 py-16 text-center">
|
|
26
|
+
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary/10 text-primary ring-1 ring-primary/20">
|
|
27
|
+
<Sparkles className="size-7" />
|
|
28
|
+
</div>
|
|
29
|
+
<div className="space-y-1.5">
|
|
30
|
+
<h2 className="text-xl font-semibold tracking-tight">Como posso ajudar?</h2>
|
|
31
|
+
<p className="max-w-sm text-sm text-muted-foreground">
|
|
32
|
+
Envie uma mensagem para comecar a conversa. Voce pode pedir respostas, acionar ferramentas ou colar conteudo para analise.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function MessageList({ messages, isLoading, displayRenderers,className, error, onRetry, emptyState }: MessageListProps) {
|
|
40
|
+
const viewportRef = useRef<HTMLDivElement>(null);
|
|
41
|
+
const isFollowingRef = useRef(true);
|
|
42
|
+
|
|
43
|
+
const lastAssistantIndex = useMemo(() =>
|
|
44
|
+
messages.reduceRight((found, msg, i) => {
|
|
45
|
+
if (found !== -1) return found;
|
|
46
|
+
return msg.role === "assistant" ? i : -1;
|
|
47
|
+
}, -1),
|
|
48
|
+
[messages]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const virtualizer = useVirtualizer({
|
|
52
|
+
count: messages.length,
|
|
53
|
+
getScrollElement: () => viewportRef.current,
|
|
54
|
+
estimateSize: () => 80,
|
|
55
|
+
overscan: 5,
|
|
56
|
+
paddingStart: 16,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Track scroll position to detect if user is following
|
|
60
|
+
const handleScroll = useCallback(() => {
|
|
61
|
+
const viewport = viewportRef.current;
|
|
62
|
+
if (!viewport) return;
|
|
63
|
+
const distanceFromBottom =
|
|
64
|
+
viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
65
|
+
isFollowingRef.current = distanceFromBottom <= 100;
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const viewport = viewportRef.current;
|
|
70
|
+
if (!viewport) return;
|
|
71
|
+
viewport.addEventListener("scroll", handleScroll, { passive: true });
|
|
72
|
+
return () => viewport.removeEventListener("scroll", handleScroll);
|
|
73
|
+
}, [handleScroll]);
|
|
74
|
+
|
|
75
|
+
// Auto-scroll to bottom when following
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (messages.length > 0 && isFollowingRef.current) {
|
|
78
|
+
virtualizer.scrollToIndex(messages.length - 1, { align: "end" });
|
|
79
|
+
}
|
|
80
|
+
}, [messages, virtualizer]);
|
|
81
|
+
|
|
82
|
+
// Auto-scroll when error appears
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (error && isFollowingRef.current) {
|
|
85
|
+
const viewport = viewportRef.current;
|
|
86
|
+
if (viewport) viewport.scrollTop = viewport.scrollHeight;
|
|
87
|
+
}
|
|
88
|
+
}, [error]);
|
|
89
|
+
|
|
90
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
91
|
+
|
|
92
|
+
if (messages.length === 0) {
|
|
93
|
+
return (
|
|
94
|
+
<ScrollAreaPrimitive.Root className={cn("flex-1 relative overflow-hidden", className)}>
|
|
95
|
+
<ScrollAreaPrimitive.Viewport ref={viewportRef} className="h-full w-full rounded-[inherit]">
|
|
96
|
+
{emptyState ?? <DefaultWelcome />}
|
|
97
|
+
</ScrollAreaPrimitive.Viewport>
|
|
98
|
+
<ScrollBar />
|
|
99
|
+
<ScrollAreaPrimitive.Corner />
|
|
100
|
+
</ScrollAreaPrimitive.Root>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<ScrollAreaPrimitive.Root className={cn("flex-1 relative overflow-hidden", className)}>
|
|
106
|
+
<ScrollAreaPrimitive.Viewport ref={viewportRef} className="h-full w-full rounded-[inherit]">
|
|
107
|
+
<div
|
|
108
|
+
className="relative w-full"
|
|
109
|
+
style={{ height: virtualizer.getTotalSize() + 16 }}
|
|
110
|
+
>
|
|
111
|
+
<div>
|
|
112
|
+
{virtualItems.map((virtualRow) => {
|
|
113
|
+
const message = messages[virtualRow.index];
|
|
114
|
+
return (
|
|
115
|
+
<div
|
|
116
|
+
key={message.id ?? virtualRow.index}
|
|
117
|
+
data-index={virtualRow.index}
|
|
118
|
+
ref={virtualizer.measureElement}
|
|
119
|
+
className="pb-3 px-4"
|
|
120
|
+
style={{
|
|
121
|
+
position: "absolute",
|
|
122
|
+
top: 0,
|
|
123
|
+
left: 0,
|
|
124
|
+
right: 0,
|
|
125
|
+
transform: `translateY(${virtualRow.start}px)`,
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<MessageBubble
|
|
129
|
+
message={message}
|
|
130
|
+
isStreaming={virtualRow.index === lastAssistantIndex && isLoading && messages[messages.length - 1]?.role === "assistant"}
|
|
131
|
+
displayRenderers={displayRenderers}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
})}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
{isLoading && messages[messages.length - 1]?.role !== "assistant" && (
|
|
139
|
+
<div className="px-4 pb-3">
|
|
140
|
+
<StreamingIndicator />
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
{!isLoading && error && (
|
|
144
|
+
<div className="px-4 pb-3">
|
|
145
|
+
<ErrorNote message={error.message} onRetry={onRetry} />
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</ScrollAreaPrimitive.Viewport>
|
|
149
|
+
<ScrollBar />
|
|
150
|
+
<ScrollAreaPrimitive.Corner />
|
|
151
|
+
</ScrollAreaPrimitive.Root>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
export function StreamingIndicator() {
|
|
2
|
-
return (
|
|
3
|
-
<span
|
|
4
|
-
className="inline-flex items-end gap-1.5 py-1 mt-4"
|
|
5
|
-
aria-label="Gerando resposta..."
|
|
6
|
-
role="status"
|
|
7
|
-
>
|
|
8
|
-
<span className="size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_0ms]" />
|
|
9
|
-
<span className="size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_150ms]" />
|
|
10
|
-
<span className="size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_300ms]" />
|
|
11
|
-
<style>{`
|
|
12
|
-
@keyframes streaming-bounce {
|
|
13
|
-
0%, 60%, 100% { transform: translateY(0); }
|
|
14
|
-
30% { transform: translateY(-4px); }
|
|
15
|
-
}
|
|
16
|
-
`}</style>
|
|
17
|
-
</span>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
1
|
+
export function StreamingIndicator() {
|
|
2
|
+
return (
|
|
3
|
+
<span
|
|
4
|
+
className="inline-flex items-end gap-1.5 py-1 mt-4"
|
|
5
|
+
aria-label="Gerando resposta..."
|
|
6
|
+
role="status"
|
|
7
|
+
>
|
|
8
|
+
<span className="size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_0ms]" />
|
|
9
|
+
<span className="size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_150ms]" />
|
|
10
|
+
<span className="size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_300ms]" />
|
|
11
|
+
<style>{`
|
|
12
|
+
@keyframes streaming-bounce {
|
|
13
|
+
0%, 60%, 100% { transform: translateY(0); }
|
|
14
|
+
30% { transform: translateY(-4px); }
|
|
15
|
+
}
|
|
16
|
+
`}</style>
|
|
17
|
+
</span>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type { DisplayAlert } from "./sdk-types.js";
|
|
2
|
-
import { AlertCircle, AlertTriangle, CheckCircle, Info } from "lucide-react";
|
|
3
|
-
import { Alert, AlertDescription, AlertTitle } from "../ui/alert.js";
|
|
4
|
-
|
|
5
|
-
type AlertVariant = "info" | "warning" | "error" | "success";
|
|
6
|
-
|
|
7
|
-
const VARIANT_ICON: Record<AlertVariant, typeof Info> = {
|
|
8
|
-
info: Info,
|
|
9
|
-
warning: AlertTriangle,
|
|
10
|
-
error: AlertCircle,
|
|
11
|
-
success: CheckCircle,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function AlertRenderer({ variant = "info", title, message }: DisplayAlert) {
|
|
15
|
-
const Icon = VARIANT_ICON[variant as AlertVariant] ?? Info;
|
|
16
|
-
return (
|
|
17
|
-
<Alert variant={variant === "error" ? "destructive" : "default"}>
|
|
18
|
-
<Icon />
|
|
19
|
-
{title && <AlertTitle>{title}</AlertTitle>}
|
|
20
|
-
<AlertDescription>{message}</AlertDescription>
|
|
21
|
-
</Alert>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
1
|
+
import type { DisplayAlert } from "./sdk-types.js";
|
|
2
|
+
import { AlertCircle, AlertTriangle, CheckCircle, Info } from "lucide-react";
|
|
3
|
+
import { Alert, AlertDescription, AlertTitle } from "../ui/alert.js";
|
|
4
|
+
|
|
5
|
+
type AlertVariant = "info" | "warning" | "error" | "success";
|
|
6
|
+
|
|
7
|
+
const VARIANT_ICON: Record<AlertVariant, typeof Info> = {
|
|
8
|
+
info: Info,
|
|
9
|
+
warning: AlertTriangle,
|
|
10
|
+
error: AlertCircle,
|
|
11
|
+
success: CheckCircle,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function AlertRenderer({ variant = "info", title, message }: DisplayAlert) {
|
|
15
|
+
const Icon = VARIANT_ICON[variant as AlertVariant] ?? Info;
|
|
16
|
+
return (
|
|
17
|
+
<Alert variant={variant === "error" ? "destructive" : "default"}>
|
|
18
|
+
<Icon />
|
|
19
|
+
{title && <AlertTitle>{title}</AlertTitle>}
|
|
20
|
+
<AlertDescription>{message}</AlertDescription>
|
|
21
|
+
</Alert>
|
|
22
|
+
);
|
|
23
|
+
}
|