@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,74 @@
1
+ import { useState } from "react";
2
+ import type { DisplayLink } from "./sdk-types.js";
3
+ import { Globe } from "lucide-react";
4
+ import { Card } from "../ui/card";
5
+
6
+ function getDomain(url: string, domainProp?: string): string {
7
+ if (domainProp) return domainProp;
8
+ try {
9
+ return new URL(url).hostname.replace(/^www\./, "");
10
+ } catch {
11
+ return url;
12
+ }
13
+ }
14
+
15
+ export function LinkPreviewRenderer({
16
+ url,
17
+ title,
18
+ description,
19
+ image,
20
+ favicon,
21
+ domain,
22
+ }: DisplayLink) {
23
+ const [imgError, setImgError] = useState(false);
24
+ const [faviconError, setFaviconError] = useState(false);
25
+
26
+ const displayDomain = getDomain(url, domain);
27
+
28
+ return (
29
+ <a
30
+ href={url}
31
+ target="_blank"
32
+ rel="noopener noreferrer"
33
+ aria-label={`Link: ${title}`}
34
+ >
35
+ <Card className="overflow-hidden hover:bg-muted/50 transition-colors">
36
+ {image && !imgError && (
37
+ <div className="aspect-video overflow-hidden">
38
+ <img
39
+ src={image}
40
+ alt={title}
41
+ className="w-full h-full object-cover"
42
+ loading="lazy"
43
+ decoding="async"
44
+ onError={() => setImgError(true)}
45
+ />
46
+ </div>
47
+ )}
48
+
49
+ <div className="p-3 space-y-1">
50
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
51
+ {favicon && !faviconError ? (
52
+ <img
53
+ src={favicon}
54
+ alt=""
55
+ className="w-3 h-3 rounded-sm"
56
+ onError={() => setFaviconError(true)}
57
+ aria-hidden="true"
58
+ />
59
+ ) : (
60
+ <Globe size={12} aria-hidden="true" className="shrink-0" />
61
+ )}
62
+ <span>{displayDomain}</span>
63
+ </div>
64
+
65
+ <p className="font-medium text-foreground text-sm">{title}</p>
66
+
67
+ {description && (
68
+ <p className="text-xs text-muted-foreground line-clamp-2">{description}</p>
69
+ )}
70
+ </div>
71
+ </Card>
72
+ </a>
73
+ );
74
+ }
@@ -0,0 +1,75 @@
1
+ import type { DisplayMap } from "./sdk-types.js";
2
+ import { MapPin } from "lucide-react";
3
+ import { Card } from "../ui/card.js";
4
+ import { Separator } from "../ui/separator.js";
5
+
6
+ function buildOsmUrl(pins: DisplayMap["pins"], zoom: number): string {
7
+ if (pins.length === 0) {
8
+ return `https://www.openstreetmap.org/export/embed.html?bbox=-180,-90,180,90&layer=mapnik`;
9
+ }
10
+
11
+ const lat = pins.reduce((acc, p) => acc + p.lat, 0) / pins.length;
12
+ const lng = pins.reduce((acc, p) => acc + p.lng, 0) / pins.length;
13
+
14
+ const firstPin = pins[0]!;
15
+ const markerParam =
16
+ pins.length === 1
17
+ ? `&mlat=${firstPin.lat}&mlon=${firstPin.lng}`
18
+ : "";
19
+
20
+ return `https://www.openstreetmap.org/export/embed.html?bbox=${lng - 0.05},${lat - 0.05},${lng + 0.05},${lat + 0.05}&layer=mapnik&zoom=${zoom}${markerParam}`;
21
+ }
22
+
23
+ export function MapViewRenderer({ title, pins, zoom }: DisplayMap) {
24
+ const osmUrl = buildOsmUrl(pins, zoom);
25
+
26
+ return (
27
+ <Card className="overflow-hidden">
28
+ {title && (
29
+ <div className="px-4 py-3">
30
+ <h3 className="font-medium text-sm text-foreground">{title}</h3>
31
+ </div>
32
+ )}
33
+
34
+ <div className="relative aspect-video bg-muted text-muted-foreground overflow-hidden">
35
+ <iframe
36
+ src={osmUrl}
37
+ className="w-full h-full border-0"
38
+ title={title ?? "Mapa OpenStreetMap"}
39
+ loading="lazy"
40
+ referrerPolicy="no-referrer"
41
+ sandbox="allow-scripts allow-same-origin"
42
+ />
43
+ <div className="absolute inset-0 pointer-events-none flex items-center justify-center opacity-10">
44
+ <MapPin className="h-10 w-10" />
45
+ </div>
46
+ </div>
47
+
48
+ {pins.length > 0 && (
49
+ <>
50
+ <Separator />
51
+ <ul className="p-3 space-y-2" aria-label="Locais no mapa">
52
+ {pins.map((pin, i) => (
53
+ <li key={i} className="flex items-start gap-2">
54
+ <MapPin className="h-4 w-4 shrink-0 text-primary mt-0.5" aria-hidden="true" />
55
+ <span className="flex flex-col min-w-0">
56
+ {pin.label && (
57
+ <span className="font-medium text-sm text-foreground">{pin.label}</span>
58
+ )}
59
+ {pin.address && (
60
+ <span className="text-xs text-muted-foreground">{pin.address}</span>
61
+ )}
62
+ {!pin.label && !pin.address && (
63
+ <span className="text-xs text-muted-foreground font-mono">
64
+ {pin.lat.toFixed(4)}, {pin.lng.toFixed(4)}
65
+ </span>
66
+ )}
67
+ </span>
68
+ </li>
69
+ ))}
70
+ </ul>
71
+ </>
72
+ )}
73
+ </Card>
74
+ );
75
+ }
@@ -0,0 +1,29 @@
1
+ import type { DisplayMetric } from "./sdk-types.js";
2
+ import { ArrowDown, ArrowRight, ArrowUp } from "lucide-react";
3
+ import { Card } from "../ui/card.js";
4
+
5
+ const TREND_CONFIG = {
6
+ up: { Icon: ArrowUp, colorClass: "text-primary" },
7
+ down: { Icon: ArrowDown, colorClass: "text-destructive" },
8
+ neutral: { Icon: ArrowRight, colorClass: "text-muted-foreground" },
9
+ } as const;
10
+
11
+ export function MetricCardRenderer({ label, value, unit, trend }: DisplayMetric) {
12
+ const trendConfig = trend ? TREND_CONFIG[trend.direction] : null;
13
+
14
+ return (
15
+ <Card className="p-4 w-fit">
16
+ <p className="text-sm text-muted-foreground">{label}</p>
17
+ <div className="flex items-baseline gap-2 mt-1">
18
+ <span className="text-2xl font-bold text-foreground">{value}</span>
19
+ {unit && <span className="text-sm text-muted-foreground">{unit}</span>}
20
+ </div>
21
+ {trend && trendConfig && (
22
+ <div className={`flex items-center gap-1 mt-1 text-sm ${trendConfig.colorClass}`}>
23
+ <trendConfig.Icon size={14} aria-hidden="true" />
24
+ <span>{trend.value}</span>
25
+ </div>
26
+ )}
27
+ </Card>
28
+ );
29
+ }
@@ -0,0 +1,62 @@
1
+ import type { DisplayPrice } from "./sdk-types.js";
2
+ import { ExternalLink } from "lucide-react";
3
+ import { Badge } from "../ui/badge";
4
+ import { Card } from "../ui/card";
5
+
6
+ function formatPrice(value: number, currency?: string): string {
7
+ // Fallbacks defensivos: se o modelo nao enviar currency ou enviar um valor
8
+ // invalido (ex: string vazia), caimos num formato numerico simples em vez de
9
+ // crashar a arvore React com "Currency code is required".
10
+ try {
11
+ return new Intl.NumberFormat("pt-BR", {
12
+ style: "currency",
13
+ currency: currency || "BRL",
14
+ }).format(value);
15
+ } catch {
16
+ return new Intl.NumberFormat("pt-BR").format(value);
17
+ }
18
+ }
19
+
20
+ export function PriceHighlightRenderer({ value, label, context, source, badge }: DisplayPrice) {
21
+ // Tambem aceita value como numero flat (modelo pode nao seguir schema exato).
22
+ const amount =
23
+ typeof value === "number"
24
+ ? value
25
+ : typeof value === "object" && value !== null && "value" in value
26
+ ? (value as { value: number }).value
27
+ : 0;
28
+ const currency =
29
+ typeof value === "object" && value !== null && "currency" in value
30
+ ? (value as { currency?: string }).currency
31
+ : undefined;
32
+ return (
33
+ <Card className="p-4 space-y-1 w-fit">
34
+ <p className="text-sm text-muted-foreground">{label}</p>
35
+ <div className="flex items-baseline gap-2">
36
+ <span className="text-2xl font-bold text-foreground">
37
+ {formatPrice(amount, currency)}
38
+ </span>
39
+ {badge && (
40
+ <Badge variant="destructive">
41
+ {badge.label}
42
+ </Badge>
43
+ )}
44
+ </div>
45
+ {context && <p className="text-sm text-muted-foreground">{context}</p>}
46
+ {source && (
47
+ <a
48
+ href={source.url}
49
+ target="_blank"
50
+ rel="noopener noreferrer"
51
+ className="flex items-center gap-1.5 text-xs text-primary hover:underline"
52
+ >
53
+ {source.favicon && (
54
+ <img src={source.favicon} alt="" width={14} height={14} aria-hidden="true" />
55
+ )}
56
+ <span>{source.name}</span>
57
+ <ExternalLink size={12} aria-hidden="true" />
58
+ </a>
59
+ )}
60
+ </Card>
61
+ );
62
+ }
@@ -0,0 +1,112 @@
1
+ import { useState } from "react";
2
+ import type { DisplayProduct } from "./sdk-types.js";
3
+ import { Star } from "lucide-react";
4
+ import { Card, CardContent, CardTitle } from "../ui/card.js";
5
+ import { Badge } from "../ui/badge.js";
6
+ import { Button } from "../ui/button.js";
7
+ import { cn } from "../lib/utils.js";
8
+
9
+ function StarRating({ score, count }: { score: number; count: number }) {
10
+ const fullStars = Math.floor(score);
11
+ const hasHalf = score - fullStars >= 0.5;
12
+ const emptyStars = 5 - fullStars - (hasHalf ? 1 : 0);
13
+
14
+ return (
15
+ <div
16
+ className="flex items-center gap-0.5 text-primary"
17
+ aria-label={`${score} de 5 estrelas (${count} avaliações)`}
18
+ >
19
+ {Array.from({ length: fullStars }).map((_, i) => (
20
+ <Star key={`f${i}`} size={14} fill="currentColor" />
21
+ ))}
22
+ {hasHalf && <Star size={14} fill="none" />}
23
+ {Array.from({ length: emptyStars }).map((_, i) => (
24
+ <Star key={`e${i}`} size={14} fill="none" className="text-muted-foreground" />
25
+ ))}
26
+ <span className="text-xs text-muted-foreground ml-1">({count})</span>
27
+ </div>
28
+ );
29
+ }
30
+
31
+ function formatMoney(value: number, currency = "BRL") {
32
+ return new Intl.NumberFormat("pt-BR", { style: "currency", currency }).format(value);
33
+ }
34
+
35
+ export function ProductCardRenderer({
36
+ title,
37
+ image,
38
+ price,
39
+ originalPrice,
40
+ rating,
41
+ badges,
42
+ url,
43
+ description,
44
+ }: DisplayProduct) {
45
+ const [imgError, setImgError] = useState(false);
46
+
47
+ const discount =
48
+ price && originalPrice && originalPrice.value > price.value
49
+ ? Math.round(((originalPrice.value - price.value) / originalPrice.value) * 100)
50
+ : null;
51
+
52
+ return (
53
+ <Card className="overflow-hidden w-fit max-w-sm">
54
+ {image && !imgError && (
55
+ <div className="relative">
56
+ <img
57
+ src={image}
58
+ alt={title}
59
+ className={cn("w-full aspect-video object-cover")}
60
+ loading="lazy"
61
+ decoding="async"
62
+ onError={() => setImgError(true)}
63
+ />
64
+ {discount !== null && (
65
+ <Badge variant="destructive" className="absolute top-2 right-2">
66
+ -{discount}%
67
+ </Badge>
68
+ )}
69
+ </div>
70
+ )}
71
+
72
+ <CardContent className="p-4 space-y-2">
73
+ {badges && badges.length > 0 && (
74
+ <div className="flex flex-wrap gap-1">
75
+ {badges.map((b, i) => (
76
+ <Badge key={i} variant="secondary">
77
+ {b.label}
78
+ </Badge>
79
+ ))}
80
+ </div>
81
+ )}
82
+
83
+ <CardTitle className="text-sm">{title}</CardTitle>
84
+
85
+ {description && (
86
+ <p className="text-xs text-muted-foreground line-clamp-2">{description}</p>
87
+ )}
88
+
89
+ {rating && <StarRating score={rating.score} count={rating.count} />}
90
+
91
+ {price && (
92
+ <div className="flex items-baseline gap-2">
93
+ <span className="text-lg font-bold">{formatMoney(price.value, price.currency)}</span>
94
+ {originalPrice && (
95
+ <span className="text-sm text-muted-foreground line-through">
96
+ {formatMoney(originalPrice.value, originalPrice.currency)}
97
+ </span>
98
+ )}
99
+ </div>
100
+ )}
101
+
102
+ {url && (
103
+ <Button className="w-full" size="sm" asChild>
104
+ <a href={url} target="_blank" rel="noopener noreferrer">
105
+ Ver produto
106
+ </a>
107
+ </Button>
108
+ )}
109
+ </CardContent>
110
+ </Card>
111
+ );
112
+ }
@@ -0,0 +1,59 @@
1
+ import type { DisplayProgress } from "./sdk-types.js";
2
+ import { Check, Circle, Clock } from "lucide-react";
3
+ import { Progress } from "../ui/progress.js";
4
+ import { Badge } from "../ui/badge.js";
5
+ import { cn } from "../lib/utils.js";
6
+
7
+ export function ProgressStepsRenderer({ title, steps }: DisplayProgress) {
8
+ const completed = steps.filter((s) => s.status === "completed").length;
9
+ const percentage = steps.length > 0 ? Math.round((completed / steps.length) * 100) : 0;
10
+
11
+ return (
12
+ <div className="space-y-3">
13
+ {title && <p className="font-medium text-foreground">{title}</p>}
14
+ <div className="flex items-center gap-3">
15
+ <Progress value={percentage} className="flex-1" />
16
+ <span className="text-sm text-muted-foreground tabular-nums">{percentage}%</span>
17
+ </div>
18
+ <p className="text-sm text-muted-foreground">
19
+ {completed} de {steps.length} concluídos
20
+ </p>
21
+ <ol className="space-y-2">
22
+ {steps.map((step, index) => {
23
+ const isCompleted = step.status === "completed";
24
+ const isPending = step.status === "pending";
25
+ return (
26
+ <li key={index} className="flex items-start gap-2">
27
+ <span
28
+ aria-hidden="true"
29
+ className={cn(
30
+ "mt-0.5 shrink-0",
31
+ isCompleted ? "text-primary" : "text-muted-foreground"
32
+ )}
33
+ >
34
+ {isCompleted ? (
35
+ <Check size={16} />
36
+ ) : isPending ? (
37
+ <Circle size={16} />
38
+ ) : (
39
+ <Clock size={16} />
40
+ )}
41
+ </span>
42
+ <div className="flex-1 min-w-0">
43
+ <p className={cn("text-sm", isCompleted ? "text-foreground" : "text-muted-foreground")}>
44
+ {step.label}
45
+ </p>
46
+ {step.description && (
47
+ <p className="text-xs text-muted-foreground mt-0.5">{step.description}</p>
48
+ )}
49
+ </div>
50
+ <Badge variant="secondary" className="shrink-0 text-xs">
51
+ {index + 1}
52
+ </Badge>
53
+ </li>
54
+ );
55
+ })}
56
+ </ol>
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,47 @@
1
+ import type { DisplaySources } from "./sdk-types.js";
2
+ import { ExternalLink, Globe } from "lucide-react";
3
+
4
+ export function SourcesListRenderer({ label, sources }: DisplaySources) {
5
+ return (
6
+ <div className="space-y-2">
7
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{label}</p>
8
+ <ol className="space-y-1">
9
+ {sources.map((source, index) => (
10
+ <li key={index}>
11
+ <a
12
+ href={source.url}
13
+ target="_blank"
14
+ rel="noopener noreferrer"
15
+ className="flex items-start gap-2 p-2 rounded-md hover:bg-muted text-sm"
16
+ >
17
+ <span className="text-xs text-muted-foreground font-mono w-5 shrink-0 pt-0.5">
18
+ {index + 1}
19
+ </span>
20
+ <div className="min-w-0 flex-1">
21
+ <div className="flex items-center gap-1.5">
22
+ {source.favicon ? (
23
+ <img
24
+ src={source.favicon}
25
+ alt=""
26
+ width={14}
27
+ height={14}
28
+ className="shrink-0"
29
+ aria-hidden="true"
30
+ />
31
+ ) : (
32
+ <Globe size={14} className="shrink-0 text-muted-foreground" aria-hidden="true" />
33
+ )}
34
+ <span className="font-medium text-primary truncate">{source.title}</span>
35
+ <ExternalLink size={12} className="shrink-0 text-muted-foreground" aria-hidden="true" />
36
+ </div>
37
+ {source.snippet && (
38
+ <p className="text-xs text-muted-foreground line-clamp-2 mt-0.5">{source.snippet}</p>
39
+ )}
40
+ </div>
41
+ </a>
42
+ </li>
43
+ ))}
44
+ </ol>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,86 @@
1
+ import type { DisplaySpreadsheet } from "./sdk-types.js";
2
+ import { ScrollArea, ScrollBar } from "../ui/scroll-area.js";
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow,
10
+ } from "../ui/table.js";
11
+ import { cn } from "../lib/utils.js";
12
+
13
+ function formatCell(
14
+ value: string | number | null,
15
+ colIndex: number,
16
+ moneyColumns: number[] = [],
17
+ percentColumns: number[] = [],
18
+ ): string {
19
+ if (value === null || value === undefined) return "";
20
+ if (typeof value === "number") {
21
+ if (moneyColumns.includes(colIndex)) {
22
+ return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(value);
23
+ }
24
+ if (percentColumns.includes(colIndex)) {
25
+ return new Intl.NumberFormat("pt-BR", {
26
+ style: "percent",
27
+ minimumFractionDigits: 1,
28
+ maximumFractionDigits: 2,
29
+ }).format(value / 100);
30
+ }
31
+ return new Intl.NumberFormat("pt-BR").format(value);
32
+ }
33
+ return String(value);
34
+ }
35
+
36
+ export function SpreadsheetRenderer({ title, headers, rows, format }: DisplaySpreadsheet) {
37
+ const moneyColumns = format?.moneyColumns ?? [];
38
+ const percentColumns = format?.percentColumns ?? [];
39
+
40
+ return (
41
+ <div className="space-y-2">
42
+ {title && <h3 className="text-sm font-semibold text-foreground">{title}</h3>}
43
+
44
+ <ScrollArea className="w-full">
45
+ <Table aria-readonly="true">
46
+ <TableHeader>
47
+ <TableRow>
48
+ <TableHead className="text-muted-foreground font-normal text-center w-10" aria-label="Linha" />
49
+ {headers.map((h, i) => (
50
+ <TableHead key={i} className="font-semibold">
51
+ {h}
52
+ </TableHead>
53
+ ))}
54
+ </TableRow>
55
+ </TableHeader>
56
+
57
+ <TableBody>
58
+ {rows.map((row, ri) => (
59
+ <TableRow key={ri}>
60
+ <TableCell className="text-center text-xs text-muted-foreground select-none">
61
+ {ri + 1}
62
+ </TableCell>
63
+ {row.map((cell, ci) => {
64
+ const isMoney = moneyColumns.includes(ci);
65
+ const isPercent = percentColumns.includes(ci);
66
+ const isNumber = typeof cell === "number";
67
+ return (
68
+ <TableCell
69
+ key={ci}
70
+ className={cn(
71
+ (isMoney || isPercent || isNumber) && "text-right font-mono text-sm"
72
+ )}
73
+ >
74
+ {formatCell(cell, ci, moneyColumns, percentColumns)}
75
+ </TableCell>
76
+ );
77
+ })}
78
+ </TableRow>
79
+ ))}
80
+ </TableBody>
81
+ </Table>
82
+ <ScrollBar orientation="horizontal" />
83
+ </ScrollArea>
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,75 @@
1
+ import type { DisplaySteps } from "./sdk-types.js";
2
+ import { cn } from "../lib/utils";
3
+ import { Badge } from "../ui/badge";
4
+
5
+ const STATUS_CIRCLE: Record<"completed" | "current" | "pending", string> = {
6
+ completed: "bg-primary text-primary-foreground",
7
+ current: "bg-primary/20 border border-primary",
8
+ pending: "bg-muted text-muted-foreground",
9
+ };
10
+
11
+ const STATUS_TITLE: Record<"completed" | "current" | "pending", string> = {
12
+ completed: "text-foreground",
13
+ current: "text-primary",
14
+ pending: "text-muted-foreground",
15
+ };
16
+
17
+ export function StepTimelineRenderer({ title, steps, orientation }: DisplaySteps) {
18
+ const isVertical = orientation !== "horizontal";
19
+
20
+ return (
21
+ <div className={cn("flex", isVertical ? "flex-col gap-0" : "flex-row items-start gap-0")}>
22
+ {title && (
23
+ <p className="text-sm font-medium text-foreground mb-3">{title}</p>
24
+ )}
25
+ {steps.map((step, index) => {
26
+ const isLast = index === steps.length - 1;
27
+ const { status } = step;
28
+
29
+ return (
30
+ <div
31
+ key={index}
32
+ className={cn(
33
+ "flex",
34
+ isVertical ? "flex-row gap-3" : "flex-col items-center gap-2 flex-1"
35
+ )}
36
+ >
37
+ {/* Circle + connector column */}
38
+ <div className={cn("flex", isVertical ? "flex-col items-center" : "flex-row items-center")}>
39
+ <div
40
+ className={cn(
41
+ "w-6 h-6 rounded-full flex items-center justify-center shrink-0",
42
+ STATUS_CIRCLE[status]
43
+ )}
44
+ >
45
+ <Badge
46
+ variant="secondary"
47
+ className="w-5 h-5 flex items-center justify-center rounded-full p-0 text-[10px] font-semibold border-0 bg-transparent text-inherit"
48
+ >
49
+ {index + 1}
50
+ </Badge>
51
+ </div>
52
+ {!isLast && (
53
+ <div
54
+ className={cn(
55
+ isVertical ? "w-px h-6 bg-border" : "h-px w-full bg-border flex-1"
56
+ )}
57
+ />
58
+ )}
59
+ </div>
60
+
61
+ {/* Content */}
62
+ <div className={cn("pb-4 flex-1 min-w-0", isLast && "pb-0", !isVertical && "text-center")}>
63
+ <p className={cn("text-sm font-medium leading-none", STATUS_TITLE[status])}>
64
+ {step.title}
65
+ </p>
66
+ {step.description && (
67
+ <p className="text-xs text-muted-foreground mt-1">{step.description}</p>
68
+ )}
69
+ </div>
70
+ </div>
71
+ );
72
+ })}
73
+ </div>
74
+ );
75
+ }
@@ -0,0 +1,21 @@
1
+ export { AlertRenderer } from "./AlertRenderer.js";
2
+ export { MetricCardRenderer } from "./MetricCardRenderer.js";
3
+ export { PriceHighlightRenderer } from "./PriceHighlightRenderer.js";
4
+ export { FileCardRenderer } from "./FileCardRenderer.js";
5
+ export { CodeBlockRenderer } from "./CodeBlockRenderer.js";
6
+ export { SourcesListRenderer } from "./SourcesListRenderer.js";
7
+ export { StepTimelineRenderer } from "./StepTimelineRenderer.js";
8
+ export { ProgressStepsRenderer } from "./ProgressStepsRenderer.js";
9
+ export { ChartRenderer } from "./ChartRenderer.js";
10
+ export { CarouselRenderer } from "./CarouselRenderer.js";
11
+ export { ProductCardRenderer } from "./ProductCardRenderer.js";
12
+ export { ComparisonTableRenderer } from "./ComparisonTableRenderer.js";
13
+ export { DataTableRenderer } from "./DataTableRenderer.js";
14
+ export { SpreadsheetRenderer } from "./SpreadsheetRenderer.js";
15
+ export { GalleryRenderer } from "./GalleryRenderer.js";
16
+ export { ImageViewerRenderer } from "./ImageViewerRenderer.js";
17
+ export { LinkPreviewRenderer } from "./LinkPreviewRenderer.js";
18
+ export { MapViewRenderer } from "./MapViewRenderer.js";
19
+ export { ChoiceButtonsRenderer } from "./ChoiceButtonsRenderer.js";
20
+ export { defaultDisplayRenderers, resolveDisplayRenderer } from "./registry.js";
21
+ export type { DisplayRendererMap, DisplayActionName } from "./registry.js";