@gram-ai/elements 1.34.0 → 1.35.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/compat-shims-CO9JXXV4.cjs.map +1 -1
- package/dist/compat-shims-DxtUrORi.js.map +1 -1
- package/dist/compat-shims.d.ts +9 -8
- package/dist/components/Chat/index.d.ts +2 -1
- package/dist/components/ChatHistory.d.ts +1 -1
- package/dist/components/FrontendTools/index.d.ts +1 -1
- package/dist/components/Replay.d.ts +1 -1
- package/dist/components/Replay.stories.d.ts +2 -2
- package/dist/components/ShadowRoot.d.ts +1 -1
- package/dist/components/ShareButton/index.d.ts +1 -1
- package/dist/components/ui/avatar.d.ts +3 -3
- package/dist/components/ui/button.d.ts +2 -2
- package/dist/components/ui/buttonVariants.d.ts +2 -2
- package/dist/components/ui/calendar.d.ts +2 -1
- package/dist/components/ui/collapsible.d.ts +3 -3
- package/dist/components/ui/dialog.d.ts +10 -10
- package/dist/components/ui/popover.d.ts +4 -4
- package/dist/components/ui/skeleton.d.ts +1 -1
- package/dist/components/ui/time-range-picker.d.ts +2 -1
- package/dist/components/ui/tool-ui.d.ts +7 -7
- package/dist/components/ui/tooltip.d.ts +4 -4
- package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
- package/dist/contexts/ElementsProvider.d.ts +1 -1
- package/dist/contexts/ToolApprovalContext.d.ts +2 -2
- package/dist/contexts/ToolExecutionContext.d.ts +1 -1
- package/dist/contexts/portal-container.d.ts +1 -1
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +2 -2
- package/dist/hooks/useDensity.d.ts +1 -1
- package/dist/hooks/useElements.d.ts +2 -1
- package/dist/hooks/useGramThreadListAdapter.d.ts +13 -0
- package/dist/hooks/useRadius.d.ts +1 -1
- package/dist/hooks/useThemeProps.d.ts +1 -1
- package/dist/hooks/useToolApproval.d.ts +2 -1
- package/dist/{index-BFU6NvbL.js → index-BhIowiZF.js} +9408 -9204
- package/dist/index-BhIowiZF.js.map +1 -0
- package/dist/{index-C08dvTEo.cjs → index-D0jIGQr7.cjs} +3 -3
- package/dist/index-D0jIGQr7.cjs.map +1 -0
- package/dist/{index-B5lZrrO2.js → index-Dz13dSDa.js} +57 -15
- package/dist/index-Dz13dSDa.js.map +1 -0
- package/dist/index-PXd3rs95.cjs +194 -0
- package/dist/index-PXd3rs95.cjs.map +1 -0
- package/dist/lib/errorTracking.d.ts +1 -1
- package/dist/lib/tools.d.ts +11 -10
- package/dist/plugins/generative-ui/catalog.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/accordion.d.ts +4 -4
- package/dist/plugins/generative-ui/ui/action-button.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/alert.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/avatar.d.ts +6 -6
- package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/button.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/card.d.ts +7 -7
- package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/data-table.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/dialog.d.ts +10 -10
- package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +15 -15
- package/dist/plugins/generative-ui/ui/grid.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/index.d.ts +57 -40
- package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/input.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/list.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/metric.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/pagination.d.ts +7 -7
- package/dist/plugins/generative-ui/ui/popover.d.ts +7 -7
- package/dist/plugins/generative-ui/ui/progress.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/radio-group.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/select.d.ts +10 -10
- package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/stack.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/switch.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/table.d.ts +8 -8
- package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/tabs.d.ts +4 -4
- package/dist/plugins/generative-ui/ui/text.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/textarea.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/tooltip.d.ts +4 -4
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-KLSTpp6I.js → profiler-CtGKTWWP.js} +2 -2
- package/dist/{profiler-KLSTpp6I.js.map → profiler-CtGKTWWP.js.map} +1 -1
- package/dist/{profiler-BRnyr1GA.cjs → profiler-l7_HjTyw.cjs} +2 -2
- package/dist/{profiler-BRnyr1GA.cjs.map → profiler-l7_HjTyw.cjs.map} +1 -1
- package/dist/react-shim.cjs.map +1 -1
- package/dist/react-shim.d.ts +1 -1
- package/dist/react-shim.js +1 -4
- package/dist/react-shim.js.map +1 -1
- package/dist/server/bun.cjs.map +1 -1
- package/dist/server/bun.js.map +1 -1
- package/dist/server/express.cjs.map +1 -1
- package/dist/server/express.js.map +1 -1
- package/dist/server/fastify.cjs.map +1 -1
- package/dist/server/fastify.js.map +1 -1
- package/dist/server/hono.cjs.map +1 -1
- package/dist/server/hono.js.map +1 -1
- package/dist/server/nextjs.cjs.map +1 -1
- package/dist/server/nextjs.js.map +1 -1
- package/dist/server/tanstack-start.cjs.map +1 -1
- package/dist/server/tanstack-start.js.map +1 -1
- package/dist/{startRecording-CKx-YWbq.cjs → startRecording-DEw2Aeq4.cjs} +2 -2
- package/dist/{startRecording-CKx-YWbq.cjs.map → startRecording-DEw2Aeq4.cjs.map} +1 -1
- package/dist/{startRecording-BfxB1xxR.js → startRecording-iYEL0-vr.js} +2 -2
- package/dist/{startRecording-BfxB1xxR.js.map → startRecording-iYEL0-vr.js.map} +1 -1
- package/dist/types/index.d.ts +29 -3
- package/package.json +7 -10
- package/src/compat-shims.ts +16 -2
- package/src/components/Chat/index.tsx +4 -1
- package/src/components/Chat/stories/FrontendTools.stories.tsx +1 -1
- package/src/components/Chat/stories/ToolApproval.stories.tsx +2 -2
- package/src/components/Chat/stories/Tools.stories.tsx +13 -5
- package/src/components/ChatHistory.tsx +3 -1
- package/src/components/FrontendTools/index.tsx +1 -1
- package/src/components/MessageContent.tsx +1 -0
- package/src/components/Replay.stories.tsx +2 -3
- package/src/components/Replay.tsx +17 -10
- package/src/components/ShadowRoot.tsx +2 -2
- package/src/components/ShareButton/index.tsx +4 -2
- package/src/components/assistant-ui/assistant-modal.tsx +5 -3
- package/src/components/assistant-ui/attachment.tsx +1 -1
- package/src/components/assistant-ui/error-boundary.tsx +1 -1
- package/src/components/assistant-ui/markdown-text.tsx +1 -1
- package/src/components/assistant-ui/thread.tsx +249 -11
- package/src/components/assistant-ui/tool-mention-autocomplete.tsx +1 -1
- package/src/components/ui/avatar.tsx +3 -3
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/collapsible.tsx +7 -3
- package/src/components/ui/dialog.tsx +18 -10
- package/src/components/ui/generative-ui.tsx +9 -4
- package/src/components/ui/popover.tsx +4 -4
- package/src/components/ui/skeleton.tsx +4 -1
- package/src/components/ui/time-range-picker.stories.tsx +164 -154
- package/src/components/ui/time-range-picker.tsx +11 -5
- package/src/components/ui/tool-ui.tsx +18 -9
- package/src/components/ui/tooltip.tsx +4 -4
- package/src/contexts/ChatIdContext.tsx +1 -1
- package/src/contexts/ConnectionStatusContext.tsx +6 -5
- package/src/contexts/ElementsProvider.tsx +64 -41
- package/src/contexts/ReplayContext.ts +1 -1
- package/src/contexts/ToolApprovalContext.tsx +5 -1
- package/src/contexts/ToolExecutionContext.tsx +1 -1
- package/src/contexts/portal-container.tsx +1 -1
- package/src/hooks/useAuth.ts +2 -1
- package/src/hooks/useDensity.ts +1 -1
- package/src/hooks/useElements.ts +2 -1
- package/src/hooks/useFollowOnSuggestions.ts +3 -6
- package/src/hooks/useGramThreadListAdapter.tsx +50 -3
- package/src/hooks/useMCPTools.ts +2 -2
- package/src/hooks/useModel.ts +1 -3
- package/src/hooks/usePluginComponents.ts +3 -1
- package/src/hooks/useRadius.ts +1 -1
- package/src/hooks/useSession.ts +3 -1
- package/src/hooks/useThemeProps.ts +5 -5
- package/src/hooks/useToolApproval.ts +2 -1
- package/src/lib/cassette.ts +20 -8
- package/src/lib/errorTracking.ts +1 -4
- package/src/lib/messageConverter.test.ts +11 -13
- package/src/lib/messageConverter.ts +13 -4
- package/src/lib/token.ts +2 -5
- package/src/lib/tool-mentions.ts +5 -2
- package/src/lib/tools.byte-cap.test.ts +1 -1
- package/src/lib/tools.test.ts +1 -1
- package/src/lib/tools.ts +15 -5
- package/src/lib/utils.ts +2 -2
- package/src/lib.d.ts +8 -1
- package/src/plugins/chart/chart.test.ts +3 -4
- package/src/plugins/chart/component.tsx +7 -6
- package/src/plugins/chart/ui/area-chart.tsx +1 -1
- package/src/plugins/chart/ui/line-chart.tsx +1 -1
- package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +2 -2
- package/src/plugins/generative-ui/ui/accordion.tsx +4 -4
- package/src/plugins/generative-ui/ui/action-button.tsx +4 -2
- package/src/plugins/generative-ui/ui/alert-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/alert.tsx +7 -3
- package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +5 -1
- package/src/plugins/generative-ui/ui/avatar.tsx +12 -6
- package/src/plugins/generative-ui/ui/badge.tsx +1 -1
- package/src/plugins/generative-ui/ui/button-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/button.tsx +1 -1
- package/src/plugins/generative-ui/ui/card-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/card.tsx +28 -7
- package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/checkbox.tsx +1 -1
- package/src/plugins/generative-ui/ui/data-table.tsx +1 -1
- package/src/plugins/generative-ui/ui/dialog.tsx +15 -10
- package/src/plugins/generative-ui/ui/dropdown-menu.tsx +33 -15
- package/src/plugins/generative-ui/ui/grid.tsx +1 -1
- package/src/plugins/generative-ui/ui/index.ts +154 -40
- package/src/plugins/generative-ui/ui/input-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/input.tsx +5 -1
- package/src/plugins/generative-ui/ui/label.tsx +1 -1
- package/src/plugins/generative-ui/ui/list.tsx +5 -1
- package/src/plugins/generative-ui/ui/metric.tsx +2 -1
- package/src/plugins/generative-ui/ui/pagination.tsx +12 -7
- package/src/plugins/generative-ui/ui/popover.tsx +13 -7
- package/src/plugins/generative-ui/ui/progress.tsx +1 -1
- package/src/plugins/generative-ui/ui/radio-group.tsx +2 -2
- package/src/plugins/generative-ui/ui/select-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/select.tsx +14 -10
- package/src/plugins/generative-ui/ui/separator.tsx +1 -1
- package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/skeleton.tsx +4 -1
- package/src/plugins/generative-ui/ui/stack.tsx +1 -1
- package/src/plugins/generative-ui/ui/switch.tsx +1 -1
- package/src/plugins/generative-ui/ui/table.tsx +29 -8
- package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +5 -2
- package/src/plugins/generative-ui/ui/tabs.tsx +4 -4
- package/src/plugins/generative-ui/ui/text.tsx +1 -1
- package/src/plugins/generative-ui/ui/textarea.tsx +4 -1
- package/src/plugins/generative-ui/ui/tooltip.tsx +4 -4
- package/src/react-shim.ts +9 -4
- package/src/server/bun.ts +1 -1
- package/src/server/express.ts +1 -1
- package/src/server/fastify.ts +1 -1
- package/src/server/hono.ts +1 -1
- package/src/server/nextjs.ts +1 -1
- package/src/server/tanstack-start.ts +1 -1
- package/src/storybook.d.ts +5 -0
- package/src/types/index.ts +39 -3
- package/dist/index-B5lZrrO2.js.map +0 -1
- package/dist/index-BFU6NvbL.js.map +0 -1
- package/dist/index-C08dvTEo.cjs.map +0 -1
- package/dist/index-DzZ1-jQY.cjs +0 -194
- package/dist/index-DzZ1-jQY.cjs.map +0 -1
package/src/hooks/useRadius.ts
CHANGED
|
@@ -34,7 +34,7 @@ type RadiusSize = "sm" | "md" | "lg" | "xl" | "full";
|
|
|
34
34
|
* Hook to get radius classes based on theme config
|
|
35
35
|
* Use: const r = useRadius(); then r('lg') returns the appropriate rounded class
|
|
36
36
|
*/
|
|
37
|
-
export const useRadius = () => {
|
|
37
|
+
export const useRadius = (): ((size: RadiusSize) => string) => {
|
|
38
38
|
const { config } = useElements();
|
|
39
39
|
const radius = config.theme?.radius ?? "soft";
|
|
40
40
|
|
package/src/hooks/useSession.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { GetSessionFn } from "@/types";
|
|
2
2
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
|
|
4
|
-
export function getChatSessionQueryKey(
|
|
4
|
+
export function getChatSessionQueryKey(
|
|
5
|
+
projectSlug: string,
|
|
6
|
+
): readonly ["chatSession", string] {
|
|
5
7
|
return ["chatSession", projectSlug] as const;
|
|
6
8
|
}
|
|
7
9
|
|
|
@@ -4,13 +4,13 @@ import { useElements } from "./useElements";
|
|
|
4
4
|
/**
|
|
5
5
|
* Hook to get theme-related props including dark mode class
|
|
6
6
|
*/
|
|
7
|
-
export const useThemeProps = ()
|
|
7
|
+
export const useThemeProps = (): {
|
|
8
|
+
readonly className: string | undefined;
|
|
9
|
+
} => {
|
|
8
10
|
const { config } = useElements();
|
|
9
|
-
const
|
|
11
|
+
const colorScheme = config.theme?.colorScheme ?? "light";
|
|
10
12
|
|
|
11
13
|
return useMemo(() => {
|
|
12
|
-
const { colorScheme = "light" } = theme;
|
|
13
|
-
|
|
14
14
|
const isDark =
|
|
15
15
|
colorScheme === "dark" ||
|
|
16
16
|
(colorScheme === "system" &&
|
|
@@ -20,5 +20,5 @@ export const useThemeProps = () => {
|
|
|
20
20
|
return {
|
|
21
21
|
className: isDark ? "dark" : undefined,
|
|
22
22
|
} as const;
|
|
23
|
-
}, [
|
|
23
|
+
}, [colorScheme]);
|
|
24
24
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useContext } from "react";
|
|
2
2
|
import { ToolApprovalContext } from "@/contexts/contexts";
|
|
3
|
+
import type { ToolApprovalContextType } from "@/contexts/ToolApprovalContext";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Hook to access the tool approval context for managing human-in-the-loop
|
|
6
7
|
* tool execution approval.
|
|
7
8
|
*/
|
|
8
|
-
export const useToolApproval = () => {
|
|
9
|
+
export const useToolApproval = (): ToolApprovalContextType => {
|
|
9
10
|
const context = useContext(ToolApprovalContext);
|
|
10
11
|
if (!context) {
|
|
11
12
|
throw new Error(
|
package/src/lib/cassette.ts
CHANGED
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
* 3. Pass it to `<Replay cassette={...}>` to play it back
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createUIMessageStream,
|
|
15
|
+
type ChatTransport,
|
|
16
|
+
type UIMessage,
|
|
17
|
+
type UIMessageStreamWriter,
|
|
18
|
+
} from "ai";
|
|
14
19
|
import type { ThreadMessage } from "@assistant-ui/react";
|
|
15
20
|
import { sleep } from "@/lib/utils";
|
|
16
21
|
|
|
@@ -95,6 +100,12 @@ export function recordCassette(messages: readonly ThreadMessage[]): Cassette {
|
|
|
95
100
|
});
|
|
96
101
|
break;
|
|
97
102
|
// Skip image, file, audio, source, data parts for now
|
|
103
|
+
case "image":
|
|
104
|
+
case "file":
|
|
105
|
+
case "audio":
|
|
106
|
+
case "source":
|
|
107
|
+
case "data":
|
|
108
|
+
break;
|
|
98
109
|
}
|
|
99
110
|
}
|
|
100
111
|
|
|
@@ -135,7 +146,7 @@ export function createReplayTransport(
|
|
|
135
146
|
// Advance cursor past it (it should be pointing at a user message).
|
|
136
147
|
if (
|
|
137
148
|
cursor < cassette.messages.length &&
|
|
138
|
-
cassette.messages[cursor]
|
|
149
|
+
cassette.messages[cursor]?.role === "user"
|
|
139
150
|
) {
|
|
140
151
|
cursor++;
|
|
141
152
|
}
|
|
@@ -145,9 +156,10 @@ export function createReplayTransport(
|
|
|
145
156
|
const assistantMessages: CassetteMessage[] = [];
|
|
146
157
|
while (
|
|
147
158
|
cursor < cassette.messages.length &&
|
|
148
|
-
cassette.messages[cursor]
|
|
159
|
+
cassette.messages[cursor]?.role === "assistant"
|
|
149
160
|
) {
|
|
150
|
-
|
|
161
|
+
const m = cassette.messages[cursor];
|
|
162
|
+
if (m) assistantMessages.push(m);
|
|
151
163
|
cursor++;
|
|
152
164
|
}
|
|
153
165
|
|
|
@@ -186,10 +198,10 @@ export function createReplayTransport(
|
|
|
186
198
|
// Stream writing helpers
|
|
187
199
|
// ---------------------------------------------------------------------------
|
|
188
200
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
// Match the `write` shape of `UIMessageStreamWriter<UIMessage>` so callers can
|
|
202
|
+
// pass the writer they receive from `createUIMessageStream` directly. We only
|
|
203
|
+
// rely on `write` here, so this stays narrow on purpose.
|
|
204
|
+
type StreamWriter = Pick<UIMessageStreamWriter<UIMessage>, "write">;
|
|
193
205
|
|
|
194
206
|
async function writeReplayPart(
|
|
195
207
|
writer: StreamWriter,
|
package/src/lib/errorTracking.ts
CHANGED
|
@@ -75,10 +75,7 @@ export function initErrorTracking(config: ErrorTrackingConfig = {}): void {
|
|
|
75
75
|
* Track an error to Datadog RUM.
|
|
76
76
|
* Includes context about where the error originated.
|
|
77
77
|
*/
|
|
78
|
-
export function trackError(
|
|
79
|
-
error: Error | unknown,
|
|
80
|
-
context: ErrorContext,
|
|
81
|
-
): void {
|
|
78
|
+
export function trackError(error: unknown, context: ErrorContext): void {
|
|
82
79
|
if (!enabled || !initialized) {
|
|
83
80
|
return;
|
|
84
81
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import { describe, expect, it } from "vitest";
|
|
3
2
|
import {
|
|
4
3
|
convertGramMessagesToExported,
|
|
@@ -6,6 +5,7 @@ import {
|
|
|
6
5
|
convertGramMessagePartsToUIMessageParts,
|
|
7
6
|
type GramChatMessage,
|
|
8
7
|
} from "./messageConverter";
|
|
8
|
+
import type { ToolCallMessagePart } from "@assistant-ui/react";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Helper to create a minimal GramChatMessage for testing.
|
|
@@ -41,10 +41,7 @@ describe("convertGramMessagePartsToUIMessageParts", () => {
|
|
|
41
41
|
tool_calls: makeToolCallsJSON([{ id: "tc_1", name: "search_deals" }]),
|
|
42
42
|
} as Partial<GramChatMessage> & { role: string });
|
|
43
43
|
|
|
44
|
-
const parts = convertGramMessagePartsToUIMessageParts(
|
|
45
|
-
msg as any,
|
|
46
|
-
new Map(),
|
|
47
|
-
);
|
|
44
|
+
const parts = convertGramMessagePartsToUIMessageParts(msg, new Map());
|
|
48
45
|
|
|
49
46
|
const toolParts = parts.filter((p) => p.type === "dynamic-tool");
|
|
50
47
|
expect(toolParts).toHaveLength(1);
|
|
@@ -66,11 +63,7 @@ describe("convertGramMessagePartsToUIMessageParts", () => {
|
|
|
66
63
|
]),
|
|
67
64
|
} as Partial<GramChatMessage> & { role: string });
|
|
68
65
|
|
|
69
|
-
const parts = convertGramMessagePartsToUIMessageParts(
|
|
70
|
-
msg as any,
|
|
71
|
-
new Map(),
|
|
72
|
-
seen,
|
|
73
|
-
);
|
|
66
|
+
const parts = convertGramMessagePartsToUIMessageParts(msg, new Map(), seen);
|
|
74
67
|
|
|
75
68
|
const toolParts = parts.filter((p) => p.type === "dynamic-tool");
|
|
76
69
|
expect(toolParts).toHaveLength(1);
|
|
@@ -230,10 +223,15 @@ describe("convertGramMessagesToExported - string content with tool calls", () =>
|
|
|
230
223
|
|
|
231
224
|
const result = convertGramMessagesToExported(messages);
|
|
232
225
|
const assistantEntry = result.messages.find(
|
|
233
|
-
(m)
|
|
226
|
+
(m): m is typeof m & { message: { role: "assistant" } } =>
|
|
227
|
+
m.message.role === "assistant",
|
|
234
228
|
)!;
|
|
235
|
-
const
|
|
236
|
-
|
|
229
|
+
const assistantMessage = assistantEntry.message;
|
|
230
|
+
if (assistantMessage.role !== "assistant") {
|
|
231
|
+
throw new Error("expected assistant message");
|
|
232
|
+
}
|
|
233
|
+
const toolCallParts = assistantMessage.content.filter(
|
|
234
|
+
(p): p is ToolCallMessagePart => p.type === "tool-call",
|
|
237
235
|
);
|
|
238
236
|
expect(toolCallParts).toHaveLength(1);
|
|
239
237
|
expect(toolCallParts[0]).toMatchObject({
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Message format converter for Gram API <-> assistant-ui.
|
|
5
3
|
*
|
|
@@ -195,7 +193,18 @@ function buildAssistantContentParts(
|
|
|
195
193
|
});
|
|
196
194
|
}
|
|
197
195
|
|
|
198
|
-
|
|
196
|
+
// Accept both the OpenAI/OpenRouter shape (`{ id, function: { name, arguments } }`)
|
|
197
|
+
// and the assistant-ui shape (`{ toolCallId, toolName, args }`). Tool calls
|
|
198
|
+
// arrive as JSON the server stored opaquely, so we model the union here.
|
|
199
|
+
type WireToolCall = {
|
|
200
|
+
id?: string;
|
|
201
|
+
toolCallId?: string;
|
|
202
|
+
function?: { name?: string; arguments?: string | Record<string, unknown> };
|
|
203
|
+
toolName?: string;
|
|
204
|
+
args?: string | Record<string, unknown>;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
let toolCalls = tryParseJSON<WireToolCall[]>(msg.tool_calls || "[]");
|
|
199
208
|
if (!Array.isArray(toolCalls)) {
|
|
200
209
|
console.warn("Invalid tool_calls format, expected an array.");
|
|
201
210
|
toolCalls = [];
|
|
@@ -564,7 +573,7 @@ function mediaTypeFromURL(url: string): string {
|
|
|
564
573
|
return match?.[1] || unspecified;
|
|
565
574
|
}
|
|
566
575
|
|
|
567
|
-
function tryParseJSON<T =
|
|
576
|
+
function tryParseJSON<T = unknown>(str: string): T | null {
|
|
568
577
|
try {
|
|
569
578
|
return JSON.parse(str) as T;
|
|
570
579
|
} catch {
|
package/src/lib/token.ts
CHANGED
|
@@ -9,7 +9,7 @@ export function getTokenExpiry(token: string): number | null {
|
|
|
9
9
|
if (parts.length !== 3) return null;
|
|
10
10
|
|
|
11
11
|
// base64url → base64 → decode
|
|
12
|
-
let payload = parts[1]
|
|
12
|
+
let payload = parts[1]!.replace(/-/g, "+").replace(/_/g, "/");
|
|
13
13
|
while (payload.length % 4) payload += "=";
|
|
14
14
|
|
|
15
15
|
const json = atob(payload);
|
|
@@ -29,10 +29,7 @@ export function getTokenExpiry(token: string): number | null {
|
|
|
29
29
|
* of expiry. Fails open (returns false) for non-JWT tokens or tokens
|
|
30
30
|
* without an `exp` claim so they pass through unchanged.
|
|
31
31
|
*/
|
|
32
|
-
export function isTokenExpired(
|
|
33
|
-
token: string,
|
|
34
|
-
bufferMs: number = 30_000,
|
|
35
|
-
): boolean {
|
|
32
|
+
export function isTokenExpired(token: string, bufferMs = 30_000): boolean {
|
|
36
33
|
const exp = getTokenExpiry(token);
|
|
37
34
|
if (exp === null) return false; // fail-open for non-JWT tokens
|
|
38
35
|
return Date.now() >= exp * 1000 - bufferMs;
|
package/src/lib/tool-mentions.ts
CHANGED
|
@@ -24,7 +24,10 @@ export function toolSetToMentionableTools(
|
|
|
24
24
|
name,
|
|
25
25
|
description:
|
|
26
26
|
typeof tool === "object" && tool !== null && "description" in tool
|
|
27
|
-
?
|
|
27
|
+
? (() => {
|
|
28
|
+
const desc = (tool as { description?: unknown }).description;
|
|
29
|
+
return typeof desc === "string" ? desc : "";
|
|
30
|
+
})()
|
|
28
31
|
: undefined,
|
|
29
32
|
}));
|
|
30
33
|
}
|
|
@@ -38,7 +41,7 @@ export function parseMentionedTools(text: string, tools: ToolRecord): string[] {
|
|
|
38
41
|
|
|
39
42
|
MENTION_PATTERN.lastIndex = 0;
|
|
40
43
|
while ((match = MENTION_PATTERN.exec(text)) !== null) {
|
|
41
|
-
mentions.push(match[1]
|
|
44
|
+
mentions.push(match[1]!.toLowerCase());
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
const matchedToolIds = toolNames.filter((name) =>
|
|
@@ -114,7 +114,7 @@ describe("wrapToolsWithByteCap", () => {
|
|
|
114
114
|
t: { description: "", inputSchema: { type: "object" }, execute } as never,
|
|
115
115
|
};
|
|
116
116
|
const wrapped = wrapToolsWithByteCap(tools, 256);
|
|
117
|
-
const wrappedExecute = wrapped.t
|
|
117
|
+
const wrappedExecute = wrapped.t!.execute!;
|
|
118
118
|
const out = (await wrappedExecute({}, { toolCallId: "id" } as never)) as {
|
|
119
119
|
content: Array<{ text: string }>;
|
|
120
120
|
};
|
package/src/lib/tools.test.ts
CHANGED
|
@@ -252,7 +252,7 @@ describe("frontend tool Skip flow (sendAutomaticallyWhen fix)", () => {
|
|
|
252
252
|
0,
|
|
253
253
|
);
|
|
254
254
|
const toolResult = (
|
|
255
|
-
next
|
|
255
|
+
next!.content as Array<{ type: string; output?: { type?: string } }>
|
|
256
256
|
)[0];
|
|
257
257
|
expect(toolResult?.type).toBe("tool-result");
|
|
258
258
|
});
|
package/src/lib/tools.ts
CHANGED
|
@@ -11,7 +11,9 @@ import z from "zod";
|
|
|
11
11
|
/**
|
|
12
12
|
* Converts from assistant-ui tool format to the AI SDK tool shape
|
|
13
13
|
*/
|
|
14
|
-
export const toAISDKTools = (
|
|
14
|
+
export const toAISDKTools = (
|
|
15
|
+
tools: Record<string, Tool>,
|
|
16
|
+
): Record<string, { description?: string; parameters: JSONSchema7 }> => {
|
|
15
17
|
return Object.fromEntries(
|
|
16
18
|
Object.entries(tools).map(([name, tool]) => [
|
|
17
19
|
name,
|
|
@@ -28,7 +30,9 @@ export const toAISDKTools = (tools: Record<string, Tool>) => {
|
|
|
28
30
|
/**
|
|
29
31
|
* Returns only frontend tools that are enabled
|
|
30
32
|
*/
|
|
31
|
-
export const getEnabledTools = (
|
|
33
|
+
export const getEnabledTools = (
|
|
34
|
+
tools: Record<string, Tool>,
|
|
35
|
+
): Record<string, Tool> => {
|
|
32
36
|
return Object.fromEntries(
|
|
33
37
|
Object.entries(tools).filter(
|
|
34
38
|
([, tool]) => !tool.disabled && tool.type !== "backend",
|
|
@@ -38,10 +42,16 @@ export const getEnabledTools = (tools: Record<string, Tool>) => {
|
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
44
|
* A frontend tool is a tool that is defined by the user and can be used in the chat.
|
|
45
|
+
*
|
|
46
|
+
* Shape mirrors assistant-ui's `AssistantTool`: an `FC` (rendered with no props
|
|
47
|
+
* at runtime to register the tool) plus an `unstable_tool` describing the tool
|
|
48
|
+
* itself. Keeping the FC unparameterised here matches the SDK and allows tools
|
|
49
|
+
* with different `TArgs`/`TResult` to coexist in a `Record<string, FrontendTool<...>>`.
|
|
41
50
|
*/
|
|
42
|
-
export type FrontendTool<
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
export type FrontendTool<
|
|
52
|
+
TArgs extends Record<string, unknown>,
|
|
53
|
+
TResult,
|
|
54
|
+
> = FC & {
|
|
45
55
|
unstable_tool: AssistantToolProps<TArgs, TResult>;
|
|
46
56
|
};
|
|
47
57
|
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
3
|
|
|
4
|
-
export function cn(...inputs: ClassValue[]) {
|
|
4
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
5
5
|
return twMerge(clsx(inputs));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function assertNever(value: unknown): never {
|
|
9
|
-
throw new Error(`Unexpected value: ${value}`);
|
|
9
|
+
throw new Error(`Unexpected value: ${String(value)}`);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function assert(condition: unknown, message: string): asserts condition {
|
package/src/lib.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
type
|
|
1
|
+
// Escape hatch type — flags any-cast call sites with a required reason message
|
|
2
|
+
// so they're greppable and reviewable.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// const value = apiCall() as FIXME<"Need to fix upstream API types">;
|
|
6
|
+
|
|
7
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
8
|
+
type FIXME<M extends string> = any;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse, View, Warn } from "vega";
|
|
2
|
+
import { parse, View, Warn, type Spec } from "vega";
|
|
3
3
|
import { expressionInterpreter } from "vega-interpreter";
|
|
4
4
|
|
|
5
5
|
describe("ChartRenderer CSP compliance", () => {
|
|
6
6
|
it("renders a chart using vega-interpreter without eval", async () => {
|
|
7
|
-
const spec = {
|
|
7
|
+
const spec: Spec = {
|
|
8
8
|
$schema: "https://vega.github.io/schema/vega/v5.json",
|
|
9
9
|
width: 400,
|
|
10
10
|
height: 200,
|
|
@@ -46,8 +46,7 @@ describe("ChartRenderer CSP compliance", () => {
|
|
|
46
46
|
],
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
const runtime = parse(spec as any, undefined, { ast: true });
|
|
49
|
+
const runtime = parse(spec, undefined, { ast: true });
|
|
51
50
|
|
|
52
51
|
// This is the key - using expr: vegaInterpreter means no eval() is called
|
|
53
52
|
const view = new View(runtime, {
|
|
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
|
|
|
5
5
|
import { isJsonRenderTree, type JsonRenderNode } from "@/lib/generative-ui";
|
|
6
6
|
import { SyntaxHighlighterProps } from "@assistant-ui/react-markdown";
|
|
7
7
|
import { AlertCircleIcon } from "lucide-react";
|
|
8
|
-
import { FC, useMemo } from "react";
|
|
8
|
+
import { ElementType, FC, useMemo } from "react";
|
|
9
9
|
import { MacOSWindowFrame } from "../components/MacOSWindowFrame";
|
|
10
10
|
import { PluginLoadingState } from "../components/PluginLoadingState";
|
|
11
11
|
|
|
@@ -27,15 +27,16 @@ const loadingMessages = [
|
|
|
27
27
|
"Processing data...",
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
-
function getRandomLoadingMessage() {
|
|
31
|
-
return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]
|
|
30
|
+
function getRandomLoadingMessage(): string {
|
|
31
|
+
return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]!;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Chart components registry
|
|
35
|
+
* Chart components registry. Each entry accepts the chart-specific prop shape
|
|
36
|
+
* declared in `./ui`, but the registry erases those generics via `ElementType`
|
|
37
|
+
* so heterogeneous components can coexist under one key-indexed map.
|
|
36
38
|
*/
|
|
37
|
-
|
|
38
|
-
const chartComponents: Record<string, FC<any>> = {
|
|
39
|
+
const chartComponents: Record<string, ElementType> = {
|
|
39
40
|
BarChart,
|
|
40
41
|
LineChart,
|
|
41
42
|
AreaChart,
|
|
@@ -72,7 +72,7 @@ export const AreaChart: FC<AreaChartProps> = ({
|
|
|
72
72
|
const seriesKeys = useMemo(() => {
|
|
73
73
|
if (series && series.length > 0) return series;
|
|
74
74
|
if (data.length === 0) return [];
|
|
75
|
-
const keys = Object.keys(data[0]).filter((k) => k !== "label");
|
|
75
|
+
const keys = Object.keys(data[0]!).filter((k) => k !== "label");
|
|
76
76
|
return keys;
|
|
77
77
|
}, [data, series]);
|
|
78
78
|
|
|
@@ -74,7 +74,7 @@ export const LineChart: FC<LineChartProps> = ({
|
|
|
74
74
|
const seriesKeys = useMemo(() => {
|
|
75
75
|
if (series && series.length > 0) return series;
|
|
76
76
|
if (data.length === 0) return [];
|
|
77
|
-
const keys = Object.keys(data[0]).filter((k) => k !== "label");
|
|
77
|
+
const keys = Object.keys(data[0]!).filter((k) => k !== "label");
|
|
78
78
|
return keys;
|
|
79
79
|
}, [data, series]);
|
|
80
80
|
|
|
@@ -19,7 +19,7 @@ export interface AccordionWrapperProps {
|
|
|
19
19
|
export function AccordionWrapper({
|
|
20
20
|
type = "single",
|
|
21
21
|
children,
|
|
22
|
-
}: AccordionWrapperProps) {
|
|
22
|
+
}: AccordionWrapperProps): React.JSX.Element {
|
|
23
23
|
// Type assertion needed because Radix types are complex
|
|
24
24
|
const AccordionRoot = AccordionPrimitive as React.FC<{
|
|
25
25
|
type: "single" | "multiple";
|
|
@@ -47,7 +47,7 @@ export function AccordionItemWrapper({
|
|
|
47
47
|
value,
|
|
48
48
|
title,
|
|
49
49
|
children,
|
|
50
|
-
}: AccordionItemWrapperProps) {
|
|
50
|
+
}: AccordionItemWrapperProps): React.JSX.Element {
|
|
51
51
|
return (
|
|
52
52
|
<AccordionItemPrimitive value={value}>
|
|
53
53
|
<AccordionTrigger>{title}</AccordionTrigger>
|
|
@@ -8,14 +8,14 @@ import { cn } from "@/lib/utils";
|
|
|
8
8
|
|
|
9
9
|
function Accordion({
|
|
10
10
|
...props
|
|
11
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
11
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>): React.JSX.Element {
|
|
12
12
|
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function AccordionItem({
|
|
16
16
|
className,
|
|
17
17
|
...props
|
|
18
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
18
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>): React.JSX.Element {
|
|
19
19
|
return (
|
|
20
20
|
<AccordionPrimitive.Item
|
|
21
21
|
data-slot="accordion-item"
|
|
@@ -29,7 +29,7 @@ function AccordionTrigger({
|
|
|
29
29
|
className,
|
|
30
30
|
children,
|
|
31
31
|
...props
|
|
32
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
32
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>): React.JSX.Element {
|
|
33
33
|
return (
|
|
34
34
|
<AccordionPrimitive.Header className="flex">
|
|
35
35
|
<AccordionPrimitive.Trigger
|
|
@@ -51,7 +51,7 @@ function AccordionContent({
|
|
|
51
51
|
className,
|
|
52
52
|
children,
|
|
53
53
|
...props
|
|
54
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
54
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>): React.JSX.Element {
|
|
55
55
|
return (
|
|
56
56
|
<AccordionPrimitive.Content
|
|
57
57
|
data-slot="accordion-content"
|
|
@@ -25,7 +25,7 @@ export function ActionButton({
|
|
|
25
25
|
className,
|
|
26
26
|
disabled,
|
|
27
27
|
...props
|
|
28
|
-
}: ActionButtonProps) {
|
|
28
|
+
}: ActionButtonProps): React.JSX.Element {
|
|
29
29
|
const { executeTool, isToolAvailable } = useToolExecution();
|
|
30
30
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
31
31
|
|
|
@@ -56,7 +56,9 @@ export function ActionButton({
|
|
|
56
56
|
variant={error ? "destructive" : variant}
|
|
57
57
|
size={size}
|
|
58
58
|
className={cn(className)}
|
|
59
|
-
onClick={
|
|
59
|
+
onClick={() => {
|
|
60
|
+
void handleClick();
|
|
61
|
+
}}
|
|
60
62
|
disabled={disabled || isLoading || !toolAvailable}
|
|
61
63
|
{...props}
|
|
62
64
|
>
|
|
@@ -23,7 +23,8 @@ function Alert({
|
|
|
23
23
|
className,
|
|
24
24
|
variant,
|
|
25
25
|
...props
|
|
26
|
-
}: React.ComponentProps<"div"> &
|
|
26
|
+
}: React.ComponentProps<"div"> &
|
|
27
|
+
VariantProps<typeof alertVariants>): React.JSX.Element {
|
|
27
28
|
return (
|
|
28
29
|
<div
|
|
29
30
|
data-slot="alert"
|
|
@@ -34,7 +35,10 @@ function Alert({
|
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
function AlertTitle({
|
|
38
|
+
function AlertTitle({
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
}: React.ComponentProps<"div">): React.JSX.Element {
|
|
38
42
|
return (
|
|
39
43
|
<div
|
|
40
44
|
data-slot="alert-title"
|
|
@@ -50,7 +54,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
50
54
|
function AlertDescription({
|
|
51
55
|
className,
|
|
52
56
|
...props
|
|
53
|
-
}: React.ComponentProps<"div">) {
|
|
57
|
+
}: React.ComponentProps<"div">): React.JSX.Element {
|
|
54
58
|
return (
|
|
55
59
|
<div
|
|
56
60
|
data-slot="alert-description"
|
|
@@ -12,7 +12,11 @@ export interface AvatarWrapperProps {
|
|
|
12
12
|
/**
|
|
13
13
|
* Avatar wrapper that takes src, alt, and fallback as props.
|
|
14
14
|
*/
|
|
15
|
-
export function AvatarWrapper({
|
|
15
|
+
export function AvatarWrapper({
|
|
16
|
+
src,
|
|
17
|
+
alt,
|
|
18
|
+
fallback,
|
|
19
|
+
}: AvatarWrapperProps): React.JSX.Element {
|
|
16
20
|
return (
|
|
17
21
|
<Avatar>
|
|
18
22
|
{src && <AvatarImage src={src} alt={alt} />}
|
|
@@ -11,7 +11,7 @@ function Avatar({
|
|
|
11
11
|
...props
|
|
12
12
|
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
13
13
|
size?: "default" | "sm" | "lg";
|
|
14
|
-
}) {
|
|
14
|
+
}): React.JSX.Element {
|
|
15
15
|
return (
|
|
16
16
|
<AvatarPrimitive.Root
|
|
17
17
|
data-slot="avatar"
|
|
@@ -28,7 +28,7 @@ function Avatar({
|
|
|
28
28
|
function AvatarImage({
|
|
29
29
|
className,
|
|
30
30
|
...props
|
|
31
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
31
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element {
|
|
32
32
|
return (
|
|
33
33
|
<AvatarPrimitive.Image
|
|
34
34
|
data-slot="avatar-image"
|
|
@@ -41,7 +41,7 @@ function AvatarImage({
|
|
|
41
41
|
function AvatarFallback({
|
|
42
42
|
className,
|
|
43
43
|
...props
|
|
44
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
44
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>): React.JSX.Element {
|
|
45
45
|
return (
|
|
46
46
|
<AvatarPrimitive.Fallback
|
|
47
47
|
data-slot="avatar-fallback"
|
|
@@ -54,7 +54,10 @@ function AvatarFallback({
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function AvatarBadge({
|
|
57
|
+
function AvatarBadge({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<"span">): React.JSX.Element {
|
|
58
61
|
return (
|
|
59
62
|
<span
|
|
60
63
|
data-slot="avatar-badge"
|
|
@@ -70,7 +73,10 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
|
70
73
|
);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
function AvatarGroup({
|
|
76
|
+
function AvatarGroup({
|
|
77
|
+
className,
|
|
78
|
+
...props
|
|
79
|
+
}: React.ComponentProps<"div">): React.JSX.Element {
|
|
74
80
|
return (
|
|
75
81
|
<div
|
|
76
82
|
data-slot="avatar-group"
|
|
@@ -86,7 +92,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
86
92
|
function AvatarGroupCount({
|
|
87
93
|
className,
|
|
88
94
|
...props
|
|
89
|
-
}: React.ComponentProps<"div">) {
|
|
95
|
+
}: React.ComponentProps<"div">): React.JSX.Element {
|
|
90
96
|
return (
|
|
91
97
|
<div
|
|
92
98
|
data-slot="avatar-group-count"
|