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