@codrstudio/openclaude-chat 0.1.0 → 0.2.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 (62) hide show
  1. package/dist/components/StreamingIndicator.js +5 -5
  2. package/dist/display/DisplayReactRenderer.js +12 -12
  3. package/dist/display/react-sandbox/bootstrap.js +150 -150
  4. package/dist/styles.css +1 -2
  5. package/package.json +64 -61
  6. package/src/components/Chat.tsx +107 -107
  7. package/src/components/ErrorNote.tsx +35 -35
  8. package/src/components/LazyRender.tsx +42 -42
  9. package/src/components/Markdown.tsx +114 -114
  10. package/src/components/MessageBubble.tsx +107 -107
  11. package/src/components/MessageInput.tsx +421 -421
  12. package/src/components/MessageList.tsx +153 -153
  13. package/src/components/StreamingIndicator.tsx +19 -19
  14. package/src/display/AlertRenderer.tsx +23 -23
  15. package/src/display/CarouselRenderer.tsx +141 -141
  16. package/src/display/ChartRenderer.tsx +195 -195
  17. package/src/display/ChoiceButtonsRenderer.tsx +114 -114
  18. package/src/display/CodeBlockRenderer.tsx +49 -49
  19. package/src/display/ComparisonTableRenderer.tsx +132 -132
  20. package/src/display/DataTableRenderer.tsx +144 -144
  21. package/src/display/DisplayReactRenderer.tsx +269 -269
  22. package/src/display/FileCardRenderer.tsx +55 -55
  23. package/src/display/GalleryRenderer.tsx +65 -65
  24. package/src/display/ImageViewerRenderer.tsx +114 -114
  25. package/src/display/LinkPreviewRenderer.tsx +74 -74
  26. package/src/display/MapViewRenderer.tsx +75 -75
  27. package/src/display/MetricCardRenderer.tsx +29 -29
  28. package/src/display/PriceHighlightRenderer.tsx +62 -62
  29. package/src/display/ProductCardRenderer.tsx +112 -112
  30. package/src/display/ProgressStepsRenderer.tsx +59 -59
  31. package/src/display/SourcesListRenderer.tsx +47 -47
  32. package/src/display/SpreadsheetRenderer.tsx +86 -86
  33. package/src/display/StepTimelineRenderer.tsx +75 -75
  34. package/src/display/index.ts +21 -21
  35. package/src/display/react-sandbox/bootstrap.ts +155 -155
  36. package/src/display/registry.ts +84 -84
  37. package/src/display/sdk-types.ts +217 -217
  38. package/src/hooks/ChatProvider.tsx +21 -21
  39. package/src/hooks/useIsMobile.ts +15 -15
  40. package/src/hooks/useOpenClaudeChat.ts +476 -476
  41. package/src/index.ts +76 -76
  42. package/src/lib/utils.ts +6 -6
  43. package/src/parts/PartErrorBoundary.tsx +51 -51
  44. package/src/parts/PartRenderer.tsx +145 -145
  45. package/src/parts/ReasoningBlock.tsx +41 -41
  46. package/src/parts/ToolActivity.tsx +78 -78
  47. package/src/parts/ToolResult.tsx +79 -79
  48. package/src/styles.css +2 -2
  49. package/src/types.ts +41 -41
  50. package/src/ui/alert.tsx +77 -77
  51. package/src/ui/badge.tsx +36 -36
  52. package/src/ui/button.tsx +54 -54
  53. package/src/ui/card.tsx +68 -68
  54. package/src/ui/collapsible.tsx +7 -7
  55. package/src/ui/dialog.tsx +122 -122
  56. package/src/ui/dropdown-menu.tsx +76 -76
  57. package/src/ui/input.tsx +24 -24
  58. package/src/ui/progress.tsx +36 -36
  59. package/src/ui/scroll-area.tsx +48 -48
  60. package/src/ui/separator.tsx +31 -31
  61. package/src/ui/skeleton.tsx +9 -9
  62. 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
+ }