@codrstudio/openclaude-chat 0.1.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 (171) hide show
  1. package/dist/components/Chat.d.ts +23 -0
  2. package/dist/components/Chat.js +12 -0
  3. package/dist/components/ErrorNote.d.ts +6 -0
  4. package/dist/components/ErrorNote.js +6 -0
  5. package/dist/components/LazyRender.d.ts +8 -0
  6. package/dist/components/LazyRender.js +22 -0
  7. package/dist/components/Markdown.d.ts +5 -0
  8. package/dist/components/Markdown.js +65 -0
  9. package/dist/components/MessageBubble.d.ts +9 -0
  10. package/dist/components/MessageBubble.js +45 -0
  11. package/dist/components/MessageInput.d.ts +19 -0
  12. package/dist/components/MessageInput.js +214 -0
  13. package/dist/components/MessageList.d.ts +13 -0
  14. package/dist/components/MessageList.js +72 -0
  15. package/dist/components/StreamingIndicator.d.ts +1 -0
  16. package/dist/components/StreamingIndicator.js +9 -0
  17. package/dist/display/AlertRenderer.d.ts +2 -0
  18. package/dist/display/AlertRenderer.js +13 -0
  19. package/dist/display/CarouselRenderer.d.ts +2 -0
  20. package/dist/display/CarouselRenderer.js +41 -0
  21. package/dist/display/ChartRenderer.d.ts +2 -0
  22. package/dist/display/ChartRenderer.js +76 -0
  23. package/dist/display/ChoiceButtonsRenderer.d.ts +6 -0
  24. package/dist/display/ChoiceButtonsRenderer.js +23 -0
  25. package/dist/display/CodeBlockRenderer.d.ts +2 -0
  26. package/dist/display/CodeBlockRenderer.js +17 -0
  27. package/dist/display/ComparisonTableRenderer.d.ts +2 -0
  28. package/dist/display/ComparisonTableRenderer.js +26 -0
  29. package/dist/display/DataTableRenderer.d.ts +2 -0
  30. package/dist/display/DataTableRenderer.js +74 -0
  31. package/dist/display/DisplayReactRenderer.d.ts +26 -0
  32. package/dist/display/DisplayReactRenderer.js +192 -0
  33. package/dist/display/FileCardRenderer.d.ts +2 -0
  34. package/dist/display/FileCardRenderer.js +31 -0
  35. package/dist/display/GalleryRenderer.d.ts +2 -0
  36. package/dist/display/GalleryRenderer.js +11 -0
  37. package/dist/display/ImageViewerRenderer.d.ts +2 -0
  38. package/dist/display/ImageViewerRenderer.js +15 -0
  39. package/dist/display/LinkPreviewRenderer.d.ts +2 -0
  40. package/dist/display/LinkPreviewRenderer.js +20 -0
  41. package/dist/display/MapViewRenderer.d.ts +2 -0
  42. package/dist/display/MapViewRenderer.js +20 -0
  43. package/dist/display/MetricCardRenderer.d.ts +2 -0
  44. package/dist/display/MetricCardRenderer.js +12 -0
  45. package/dist/display/PriceHighlightRenderer.d.ts +2 -0
  46. package/dist/display/PriceHighlightRenderer.js +30 -0
  47. package/dist/display/ProductCardRenderer.d.ts +2 -0
  48. package/dist/display/ProductCardRenderer.js +23 -0
  49. package/dist/display/ProgressStepsRenderer.d.ts +2 -0
  50. package/dist/display/ProgressStepsRenderer.js +14 -0
  51. package/dist/display/SourcesListRenderer.d.ts +2 -0
  52. package/dist/display/SourcesListRenderer.js +5 -0
  53. package/dist/display/SpreadsheetRenderer.d.ts +2 -0
  54. package/dist/display/SpreadsheetRenderer.js +32 -0
  55. package/dist/display/StepTimelineRenderer.d.ts +2 -0
  56. package/dist/display/StepTimelineRenderer.js +21 -0
  57. package/dist/display/index.d.ts +21 -0
  58. package/dist/display/index.js +20 -0
  59. package/dist/display/react-sandbox/bootstrap.d.ts +1 -0
  60. package/dist/display/react-sandbox/bootstrap.js +154 -0
  61. package/dist/display/registry.d.ts +5 -0
  62. package/dist/display/registry.js +52 -0
  63. package/dist/display/sdk-types.d.ts +187 -0
  64. package/dist/display/sdk-types.js +4 -0
  65. package/dist/hooks/ChatProvider.d.ts +9 -0
  66. package/dist/hooks/ChatProvider.js +14 -0
  67. package/dist/hooks/useIsMobile.d.ts +1 -0
  68. package/dist/hooks/useIsMobile.js +12 -0
  69. package/dist/hooks/useOpenClaudeChat.d.ts +36 -0
  70. package/dist/hooks/useOpenClaudeChat.js +361 -0
  71. package/dist/index.d.ts +47 -0
  72. package/dist/index.js +42 -0
  73. package/dist/lib/utils.d.ts +2 -0
  74. package/dist/lib/utils.js +5 -0
  75. package/dist/parts/PartErrorBoundary.d.ts +21 -0
  76. package/dist/parts/PartErrorBoundary.js +27 -0
  77. package/dist/parts/PartRenderer.d.ts +8 -0
  78. package/dist/parts/PartRenderer.js +99 -0
  79. package/dist/parts/ReasoningBlock.d.ts +6 -0
  80. package/dist/parts/ReasoningBlock.js +18 -0
  81. package/dist/parts/ToolActivity.d.ts +11 -0
  82. package/dist/parts/ToolActivity.js +52 -0
  83. package/dist/parts/ToolResult.d.ts +7 -0
  84. package/dist/parts/ToolResult.js +38 -0
  85. package/dist/styles.css +2 -0
  86. package/dist/types.d.ts +40 -0
  87. package/dist/types.js +4 -0
  88. package/dist/ui/alert.d.ts +12 -0
  89. package/dist/ui/alert.js +28 -0
  90. package/dist/ui/badge.d.ts +9 -0
  91. package/dist/ui/badge.js +20 -0
  92. package/dist/ui/button.d.ts +11 -0
  93. package/dist/ui/button.js +31 -0
  94. package/dist/ui/card.d.ts +8 -0
  95. package/dist/ui/card.js +21 -0
  96. package/dist/ui/collapsible.d.ts +1 -0
  97. package/dist/ui/collapsible.js +2 -0
  98. package/dist/ui/dialog.d.ts +19 -0
  99. package/dist/ui/dialog.js +23 -0
  100. package/dist/ui/dropdown-menu.d.ts +11 -0
  101. package/dist/ui/dropdown-menu.js +15 -0
  102. package/dist/ui/input.d.ts +3 -0
  103. package/dist/ui/input.js +6 -0
  104. package/dist/ui/progress.d.ts +7 -0
  105. package/dist/ui/progress.js +9 -0
  106. package/dist/ui/scroll-area.d.ts +5 -0
  107. package/dist/ui/scroll-area.js +12 -0
  108. package/dist/ui/separator.d.ts +4 -0
  109. package/dist/ui/separator.js +8 -0
  110. package/dist/ui/skeleton.d.ts +3 -0
  111. package/dist/ui/skeleton.js +6 -0
  112. package/dist/ui/table.d.ts +10 -0
  113. package/dist/ui/table.js +27 -0
  114. package/package.json +61 -0
  115. package/src/components/Chat.tsx +107 -0
  116. package/src/components/ErrorNote.tsx +35 -0
  117. package/src/components/LazyRender.tsx +42 -0
  118. package/src/components/Markdown.tsx +114 -0
  119. package/src/components/MessageBubble.tsx +107 -0
  120. package/src/components/MessageInput.tsx +421 -0
  121. package/src/components/MessageList.tsx +153 -0
  122. package/src/components/StreamingIndicator.tsx +19 -0
  123. package/src/display/AlertRenderer.tsx +23 -0
  124. package/src/display/CarouselRenderer.tsx +141 -0
  125. package/src/display/ChartRenderer.tsx +195 -0
  126. package/src/display/ChoiceButtonsRenderer.tsx +114 -0
  127. package/src/display/CodeBlockRenderer.tsx +49 -0
  128. package/src/display/ComparisonTableRenderer.tsx +132 -0
  129. package/src/display/DataTableRenderer.tsx +144 -0
  130. package/src/display/DisplayReactRenderer.tsx +269 -0
  131. package/src/display/FileCardRenderer.tsx +55 -0
  132. package/src/display/GalleryRenderer.tsx +65 -0
  133. package/src/display/ImageViewerRenderer.tsx +114 -0
  134. package/src/display/LinkPreviewRenderer.tsx +74 -0
  135. package/src/display/MapViewRenderer.tsx +75 -0
  136. package/src/display/MetricCardRenderer.tsx +29 -0
  137. package/src/display/PriceHighlightRenderer.tsx +62 -0
  138. package/src/display/ProductCardRenderer.tsx +112 -0
  139. package/src/display/ProgressStepsRenderer.tsx +59 -0
  140. package/src/display/SourcesListRenderer.tsx +47 -0
  141. package/src/display/SpreadsheetRenderer.tsx +86 -0
  142. package/src/display/StepTimelineRenderer.tsx +75 -0
  143. package/src/display/index.ts +21 -0
  144. package/src/display/react-sandbox/bootstrap.ts +155 -0
  145. package/src/display/registry.ts +84 -0
  146. package/src/display/sdk-types.ts +217 -0
  147. package/src/hooks/ChatProvider.tsx +21 -0
  148. package/src/hooks/useIsMobile.ts +15 -0
  149. package/src/hooks/useOpenClaudeChat.ts +476 -0
  150. package/src/index.ts +76 -0
  151. package/src/lib/utils.ts +6 -0
  152. package/src/parts/PartErrorBoundary.tsx +51 -0
  153. package/src/parts/PartRenderer.tsx +145 -0
  154. package/src/parts/ReasoningBlock.tsx +41 -0
  155. package/src/parts/ToolActivity.tsx +78 -0
  156. package/src/parts/ToolResult.tsx +79 -0
  157. package/src/styles.css +2 -0
  158. package/src/types.ts +41 -0
  159. package/src/ui/alert.tsx +77 -0
  160. package/src/ui/badge.tsx +36 -0
  161. package/src/ui/button.tsx +54 -0
  162. package/src/ui/card.tsx +68 -0
  163. package/src/ui/collapsible.tsx +7 -0
  164. package/src/ui/dialog.tsx +122 -0
  165. package/src/ui/dropdown-menu.tsx +76 -0
  166. package/src/ui/input.tsx +24 -0
  167. package/src/ui/progress.tsx +36 -0
  168. package/src/ui/scroll-area.tsx +48 -0
  169. package/src/ui/separator.tsx +31 -0
  170. package/src/ui/skeleton.tsx +9 -0
  171. package/src/ui/table.tsx +114 -0
@@ -0,0 +1,145 @@
1
+ import { memo, useState } from "react";
2
+ import { Paperclip, ChevronDown } from "lucide-react";
3
+ import { Markdown } from "../components/Markdown.js";
4
+ import { LazyRender } from "../components/LazyRender.js";
5
+ import { ReasoningBlock } from "./ReasoningBlock.js";
6
+ import { ToolActivity } from "./ToolActivity.js";
7
+ import { ToolResult } from "./ToolResult.js";
8
+ import { resolveDisplayRenderer } from "../display/registry.js";
9
+ import type { DisplayRendererMap } from "../display/registry.js";
10
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "../ui/collapsible.js";
11
+ import { cn } from "../lib/utils.js";
12
+ import type { MessagePart, TextPart, ReasoningPart, ToolInvocationPart } from "../types.js";
13
+
14
+ const HEAVY_RENDERERS = new Set([
15
+ "chart", "map", "table",
16
+ "spreadsheet", "gallery", "image",
17
+ ]);
18
+
19
+ export interface PartRendererProps {
20
+ part: MessagePart;
21
+ isStreaming?: boolean;
22
+ displayRenderers?: DisplayRendererMap;
23
+ }
24
+
25
+ // ─── Attachment sub-components ────────────────────────────────────────────────
26
+
27
+ function AttachmentTextBlock({ filename, content }: { filename: string; content: string }) {
28
+ const [open, setOpen] = useState(false);
29
+ const lines = content.split("\n");
30
+ const preview = lines.slice(0, 3).join("\n") + (lines.length > 3 && !open ? "\n…" : "");
31
+
32
+ return (
33
+ <Collapsible open={open} onOpenChange={setOpen} className="rounded-lg border text-xs overflow-hidden">
34
+ <div className="flex items-center gap-2 px-3 py-2 bg-muted/50 border-b">
35
+ <Paperclip className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
36
+ <span className="flex-1 truncate font-medium">{filename}</span>
37
+ <CollapsibleTrigger asChild>
38
+ <button type="button" className="text-muted-foreground hover:text-foreground transition-colors" aria-label={open ? "Recolher" : "Expandir"}>
39
+ <ChevronDown className={cn("h-3.5 w-3.5 transition-transform", open && "rotate-180")} />
40
+ </button>
41
+ </CollapsibleTrigger>
42
+ </div>
43
+ <pre className="px-3 py-2 text-muted-foreground whitespace-pre-wrap break-words">{preview}</pre>
44
+ <CollapsibleContent>
45
+ <pre className="px-3 py-2 max-h-40 overflow-auto whitespace-pre-wrap break-words border-t">{content}</pre>
46
+ </CollapsibleContent>
47
+ </Collapsible>
48
+ );
49
+ }
50
+
51
+ // ─── Main renderer ────────────────────────────────────────────────────────────
52
+
53
+ export const PartRenderer = memo(function PartRenderer({ part, isStreaming, displayRenderers }: PartRendererProps) {
54
+ switch (part.type) {
55
+ case "text": {
56
+ const p = part as TextPart;
57
+ if (p.text.startsWith("[📎")) {
58
+ const firstNewline = p.text.indexOf("\n");
59
+ const header = firstNewline >= 0 ? p.text.slice(0, firstNewline) : p.text;
60
+ const body = firstNewline >= 0 ? p.text.slice(firstNewline + 1) : "";
61
+ const match = header.match(/^\[📎\s+(.+?)\]?$/);
62
+ const filename = match?.[1] ?? header;
63
+ return <AttachmentTextBlock filename={filename} content={body} />;
64
+ }
65
+ return <Markdown>{p.text}</Markdown>;
66
+ }
67
+
68
+ case "reasoning": {
69
+ const p = part as ReasoningPart;
70
+ return <ReasoningBlock content={p.reasoning} isStreaming={isStreaming} />;
71
+ }
72
+
73
+ case "tool-invocation": {
74
+ const { toolInvocation } = part as ToolInvocationPart;
75
+ // O openclaude-sdk entrega os display tools como meta-tools MCP com
76
+ // prefixo `mcp__display__display_*` (ex: mcp__display__display_highlight).
77
+ // O tipo especifico do widget vem no campo `action` do input/args
78
+ // (ex: {action: "metric", label: "...", value: "..."}).
79
+ //
80
+ // Alem disso aceitamos a forma "legada" direta `display_*` (compat).
81
+ const name = toolInvocation.toolName;
82
+ const isMcpDisplay = /^mcp__display__display_(highlight|collection|card|visual)$/.test(name);
83
+ const isLegacyDisplay = name.startsWith("display_");
84
+ const isDisplay = isMcpDisplay || isLegacyDisplay;
85
+
86
+ if (isDisplay) {
87
+ // IMPORTANTE: para display tools, o `args` (input) contem a definicao
88
+ // COMPLETA do widget. O `result` (tool_result) e apenas uma confirmacao
89
+ // minima (`{action: "metric"}`). Sempre preferimos args aqui.
90
+ const payload = toolInvocation.args as Record<string, unknown> | undefined;
91
+ // Para meta-tools MCP, a action vem no payload.
92
+ // Para display_* legado, a action e o sufixo do toolName.
93
+ const action = isMcpDisplay
94
+ ? (payload?.action as string | undefined)
95
+ : name.replace(/^display_/, "");
96
+ const Renderer = action ? resolveDisplayRenderer(action, displayRenderers) : null;
97
+ if (payload && Renderer && action) {
98
+ // Alguns campos complexos (ex: trend, data) podem chegar serializados
99
+ // como JSON string se o modelo nao souber passar objetos. Tentamos
100
+ // parsear string → objeto antes de spreadar no renderer.
101
+ const normalized: Record<string, unknown> = {};
102
+ for (const [k, v] of Object.entries(payload)) {
103
+ if (typeof v === "string" && (v.startsWith("{") || v.startsWith("["))) {
104
+ try {
105
+ normalized[k] = JSON.parse(v);
106
+ } catch {
107
+ normalized[k] = v;
108
+ }
109
+ } else {
110
+ normalized[k] = v;
111
+ }
112
+ }
113
+ // key by toolCallId so iframe-based renderers (DisplayReactRenderer)
114
+ // unmount/remount cleanly when a new tool_use block arrives.
115
+ const rendered = <Renderer key={toolInvocation.toolCallId} {...normalized} />;
116
+ // NOTE: LazyRender via IntersectionObserver nao funciona bem dentro
117
+ // do virtualized MessageList (itens ficam position:absolute e o
118
+ // observer nao dispara consistente). Renderizamos direto.
119
+ return rendered;
120
+ }
121
+ }
122
+
123
+ if (toolInvocation.state === "result") {
124
+ return (
125
+ <ToolResult
126
+ toolName={toolInvocation.toolName}
127
+ result={toolInvocation.result}
128
+ isError={toolInvocation.isError}
129
+ />
130
+ );
131
+ }
132
+
133
+ return (
134
+ <ToolActivity
135
+ toolName={toolInvocation.toolName}
136
+ state={toolInvocation.state}
137
+ args={toolInvocation.args}
138
+ />
139
+ );
140
+ }
141
+
142
+ default:
143
+ return null;
144
+ }
145
+ });
@@ -0,0 +1,41 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Brain, ChevronDown, ChevronRight } from "lucide-react";
3
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
4
+
5
+ export interface ReasoningBlockProps {
6
+ content: string;
7
+ isStreaming?: boolean;
8
+ className?: string;
9
+ }
10
+
11
+ export function ReasoningBlock({ content, isStreaming = false, className }: ReasoningBlockProps) {
12
+ const [expanded, setExpanded] = useState(isStreaming);
13
+
14
+ useEffect(() => {
15
+ if (isStreaming) {
16
+ setExpanded(true);
17
+ } else {
18
+ setExpanded(false);
19
+ }
20
+ }, [isStreaming]);
21
+
22
+ return (
23
+ <Collapsible open={expanded} onOpenChange={setExpanded} className={className}>
24
+ <div className="border border-border bg-muted/30 rounded-md text-sm overflow-hidden">
25
+ <CollapsibleTrigger className="flex items-center gap-2 px-3 py-2 w-full text-left font-medium text-muted-foreground hover:bg-muted/60 cursor-pointer">
26
+ <Brain className="h-3.5 w-3.5" aria-hidden="true" />
27
+ <span className="font-mono text-xs">reasoning</span>
28
+ {expanded
29
+ ? <ChevronDown className="h-3.5 w-3.5 ml-auto" aria-hidden="true" />
30
+ : <ChevronRight className="h-3.5 w-3.5 ml-auto" aria-hidden="true" />
31
+ }
32
+ </CollapsibleTrigger>
33
+ <CollapsibleContent>
34
+ <div className="max-h-96 overflow-y-auto px-3 py-3 text-muted-foreground whitespace-pre-wrap break-words border-t border-border text-xs">
35
+ {content}
36
+ </div>
37
+ </CollapsibleContent>
38
+ </div>
39
+ </Collapsible>
40
+ );
41
+ }
@@ -0,0 +1,78 @@
1
+ import { Check, Download, FileText, FilePlus, FolderSearch, Globe, Loader2, Pencil, Search, Terminal, Wrench, type LucideIcon } from "lucide-react";
2
+ import { cn } from "../lib/utils.js";
3
+
4
+ export type ToolActivityState = "call" | "partial-call" | "result";
5
+
6
+ export interface ToolActivityProps {
7
+ toolName: string;
8
+ state: ToolActivityState;
9
+ args?: Record<string, unknown>;
10
+ className?: string;
11
+ iconMap?: Partial<Record<string, LucideIcon>>;
12
+ }
13
+
14
+ export const defaultToolIconMap: Record<string, LucideIcon> = {
15
+ WebSearch: Globe,
16
+ Bash: Terminal,
17
+ Read: FileText,
18
+ Edit: Pencil,
19
+ Write: FilePlus,
20
+ Grep: Search,
21
+ Glob: FolderSearch,
22
+ WebFetch: Download,
23
+ ListDir: FolderSearch,
24
+ };
25
+
26
+ function formatToolName(name: string): string {
27
+ return name
28
+ .replace(/_/g, " ")
29
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
30
+ .replace(/\b\w/g, (c) => c.toUpperCase());
31
+ }
32
+
33
+ function resolveIcon(toolName: string, iconMap: Record<string, LucideIcon | undefined>): LucideIcon {
34
+ return iconMap[toolName] ?? Wrench;
35
+ }
36
+
37
+ function formatArgs(toolName: string, args?: Record<string, unknown>): string | null {
38
+ if (!args) return null;
39
+ if (toolName === "Bash" && args.command) return String(args.command);
40
+ if (toolName === "Read" && args.path) return String(args.path);
41
+ if (toolName === "Edit" && args.file_path) return String(args.file_path);
42
+ if (toolName === "Write" && args.file_path) return String(args.file_path);
43
+ if (toolName === "Grep" && args.pattern) return String(args.pattern);
44
+ if (toolName === "Glob" && args.pattern) return String(args.pattern);
45
+ if (toolName === "WebSearch" && args.query) return String(args.query);
46
+ if (toolName === "ListDir" && args.path) return String(args.path);
47
+ return null;
48
+ }
49
+
50
+ export function ToolActivity({ toolName, state, args, className, iconMap }: ToolActivityProps) {
51
+ const mergedIconMap = iconMap ? { ...defaultToolIconMap, ...iconMap } : defaultToolIconMap;
52
+ const Icon = resolveIcon(toolName, mergedIconMap);
53
+ const isActive = state === "call" || state === "partial-call";
54
+ const displayName = formatToolName(toolName);
55
+ const argsPreview = formatArgs(toolName, args);
56
+
57
+ return (
58
+ <div className={cn(
59
+ "flex items-center gap-2 rounded-md border border-border bg-muted/40 px-3 py-2 text-sm",
60
+ className
61
+ )}>
62
+ <span className="text-muted-foreground shrink-0" aria-hidden="true">
63
+ <Icon size={14} />
64
+ </span>
65
+ <span className="font-medium font-mono text-foreground">{displayName}</span>
66
+ {argsPreview && (
67
+ <span className="truncate text-xs text-muted-foreground font-mono max-w-[300px]">{argsPreview}</span>
68
+ )}
69
+ <span className="ml-auto text-muted-foreground">
70
+ {isActive ? (
71
+ <Loader2 size={14} className="animate-spin" aria-label="Executando..." />
72
+ ) : (
73
+ <Check size={14} aria-label="Concluido" />
74
+ )}
75
+ </span>
76
+ </div>
77
+ );
78
+ }
@@ -0,0 +1,79 @@
1
+ import { AlertCircle, CheckCircle, ChevronDown, ChevronRight } from "lucide-react";
2
+ import { useState } from "react";
3
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
4
+ import { cn } from "../lib/utils.js";
5
+
6
+ export interface ToolResultProps {
7
+ toolName: string;
8
+ result: unknown;
9
+ isError?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ function serializeResult(result: unknown): string {
14
+ try {
15
+ if (typeof result === "object" && result !== null && "value" in (result as Record<string, unknown>)) {
16
+ return String((result as Record<string, unknown>).value);
17
+ }
18
+ return JSON.stringify(result, null, 2);
19
+ } catch {
20
+ return String(result);
21
+ }
22
+ }
23
+
24
+ function extractPreview(result: unknown): string | null {
25
+ if (typeof result === "string") return result.length > 120 ? result.slice(0, 120) + "..." : result;
26
+ if (typeof result === "object" && result !== null) {
27
+ const r = result as Record<string, unknown>;
28
+ if (typeof r.value === "string") {
29
+ const v = r.value as string;
30
+ return v.length > 120 ? v.slice(0, 120) + "..." : v;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ export function ToolResult({ toolName, result, isError = false, className }: ToolResultProps) {
37
+ const [expanded, setExpanded] = useState(false);
38
+ const serialized = serializeResult(result);
39
+ const preview = extractPreview(result);
40
+
41
+ return (
42
+ <Collapsible open={expanded} onOpenChange={setExpanded} className={className}>
43
+ <div className={cn(
44
+ "rounded-md border text-sm overflow-hidden",
45
+ isError
46
+ ? "border-destructive/40 bg-destructive/5"
47
+ : "border-border bg-muted/40"
48
+ )}>
49
+ <CollapsibleTrigger
50
+ className={cn(
51
+ "flex items-center gap-2 w-full px-3 py-2 text-left font-medium hover:bg-muted/60 cursor-pointer",
52
+ )}
53
+ aria-expanded={expanded}
54
+ >
55
+ {isError ? (
56
+ <AlertCircle className="h-3.5 w-3.5 shrink-0 text-destructive" aria-hidden="true" />
57
+ ) : (
58
+ <CheckCircle className="h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true" />
59
+ )}
60
+ <span className={cn("font-mono text-xs", isError ? "text-destructive" : "text-foreground")}>
61
+ {isError ? `${toolName} erro` : `${toolName} ok`}
62
+ </span>
63
+ {!expanded && preview && (
64
+ <span className="truncate text-xs text-muted-foreground font-mono max-w-[400px] ml-1">{preview}</span>
65
+ )}
66
+ {expanded
67
+ ? <ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground ml-auto" aria-hidden="true" />
68
+ : <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground ml-auto" aria-hidden="true" />
69
+ }
70
+ </CollapsibleTrigger>
71
+ <CollapsibleContent>
72
+ <pre className="max-h-80 overflow-y-auto p-3 font-mono text-xs whitespace-pre-wrap break-all bg-muted/30 border-t border-border">
73
+ {serialized}
74
+ </pre>
75
+ </CollapsibleContent>
76
+ </div>
77
+ </Collapsible>
78
+ );
79
+ }
package/src/styles.css ADDED
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss/utilities" layer(utilities);
2
+ @source "./**/*.tsx";
package/src/types.ts ADDED
@@ -0,0 +1,41 @@
1
+ // Tipos locais usados pelo @codrstudio/openclaude-chat.
2
+ // Nao depende de @ai-sdk/react. Modelo inspirado nos "parts" do ai-sdk,
3
+ // mas alimentado a partir de SDKMessage do @codrstudio/openclaude-sdk.
4
+
5
+ export type TextPart = { type: "text"; text: string };
6
+
7
+ export type ReasoningPart = { type: "reasoning"; reasoning: string };
8
+
9
+ export type ToolInvocationState = "call" | "partial-call" | "result";
10
+
11
+ export type ToolInvocationPart = {
12
+ type: "tool-invocation";
13
+ toolInvocation: {
14
+ toolName: string;
15
+ toolCallId: string;
16
+ state: ToolInvocationState;
17
+ args?: Record<string, unknown>;
18
+ result?: unknown;
19
+ isError?: boolean;
20
+ };
21
+ };
22
+
23
+ export type ImageAttachmentPart = { type: "image"; _ref?: string; mimeType?: string };
24
+ export type FileAttachmentPart = { type: "file"; _ref?: string; mimeType?: string };
25
+
26
+ export type MessagePart =
27
+ | TextPart
28
+ | ReasoningPart
29
+ | ToolInvocationPart
30
+ | ImageAttachmentPart
31
+ | FileAttachmentPart
32
+ | { type: string };
33
+
34
+ export type MessageRole = "user" | "assistant";
35
+
36
+ export interface Message {
37
+ id: string;
38
+ role: MessageRole;
39
+ content: string;
40
+ parts: MessagePart[];
41
+ }
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "../lib/utils.js";
4
+
5
+ const alertVariants = cva(
6
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-card text-card-foreground",
11
+ destructive:
12
+ "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ }
19
+ );
20
+
21
+ function Alert({
22
+ className,
23
+ variant,
24
+ ...props
25
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
26
+ return (
27
+ <div
28
+ data-slot="alert"
29
+ role="alert"
30
+ className={cn(alertVariants({ variant }), className)}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="alert-title"
40
+ className={cn(
41
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ function AlertDescription({
50
+ className,
51
+ ...props
52
+ }: React.ComponentProps<"div">) {
53
+ return (
54
+ <div
55
+ data-slot="alert-description"
56
+ className={cn(
57
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
58
+ className
59
+ )}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
66
+ return (
67
+ <div
68
+ data-slot="alert-action"
69
+ className={cn("absolute top-2 right-2", className)}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ export { Alert, AlertTitle, AlertDescription, AlertAction };
76
+ export type { VariantProps };
77
+ export { alertVariants };
@@ -0,0 +1,36 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import * as React from "react"
3
+
4
+ import { cn } from "../lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
@@ -0,0 +1,54 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import * as React from "react"
3
+
4
+ import { cn } from "../lib/utils"
5
+
6
+ const buttonVariants = cva(
7
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
+ destructive:
13
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14
+ outline:
15
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
+ ghost: "hover:bg-accent hover:text-accent-foreground",
19
+ link: "text-primary underline-offset-4 hover:underline",
20
+ },
21
+ size: {
22
+ default: "h-10 px-4 py-2",
23
+ sm: "h-9 rounded-md px-3",
24
+ lg: "h-11 rounded-md px-8",
25
+ icon: "h-10 w-10",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: "default",
30
+ size: "default",
31
+ },
32
+ }
33
+ )
34
+
35
+ export interface ButtonProps
36
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
37
+ VariantProps<typeof buttonVariants> {
38
+ asChild?: boolean
39
+ }
40
+
41
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
42
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
43
+ return (
44
+ <button
45
+ className={cn(buttonVariants({ variant, size, className }))}
46
+ ref={ref}
47
+ {...props}
48
+ />
49
+ )
50
+ }
51
+ )
52
+ Button.displayName = "Button"
53
+
54
+ export { Button, buttonVariants }
@@ -0,0 +1,68 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "rounded-xl border bg-card text-card-foreground shadow",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
29
+ return (
30
+ <div
31
+ data-slot="card-title"
32
+ className={cn("font-semibold leading-none tracking-tight", className)}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
39
+ return (
40
+ <div
41
+ data-slot="card-description"
42
+ className={cn("text-sm text-muted-foreground", className)}
43
+ {...props}
44
+ />
45
+ )
46
+ }
47
+
48
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
49
+ return (
50
+ <div
51
+ data-slot="card-content"
52
+ className={cn("p-6 pt-0", className)}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
59
+ return (
60
+ <div
61
+ data-slot="card-footer"
62
+ className={cn("flex items-center p-6 pt-0", className)}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,7 @@
1
+ "use client";
2
+
3
+ export {
4
+ Root as Collapsible,
5
+ Trigger as CollapsibleTrigger,
6
+ Content as CollapsibleContent,
7
+ } from "@radix-ui/react-collapsible";