@gram-ai/elements 1.27.4 → 1.27.5
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/README.md +72 -60
- package/README.typedoc.md +6 -6
- package/bin/cli.js +74 -74
- package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
- package/dist/{compat-shims-BPJ7Q68c.js → compat-shims-DxtUrORi.js} +4 -2
- package/dist/compat-shims-DxtUrORi.js.map +1 -0
- package/dist/components/ShareButton/index.d.ts +2 -2
- package/dist/components/assistant-ui/message-feedback.d.ts +1 -1
- package/dist/components/assistant-ui/tooltip-icon-button.d.ts +2 -2
- package/dist/components/ui/avatar.d.ts +2 -2
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/calendar.d.ts +1 -1
- package/dist/components/ui/collapsible.d.ts +1 -1
- package/dist/components/ui/dialog.d.ts +4 -4
- package/dist/components/ui/popover.d.ts +2 -2
- package/dist/components/ui/skeleton.d.ts +1 -1
- package/dist/components/ui/time-range-picker.d.ts +1 -1
- package/dist/components/ui/tool-ui.d.ts +7 -7
- package/dist/components/ui/tooltip.d.ts +2 -2
- package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
- package/dist/elements.cjs +1 -1
- package/dist/elements.js +2 -2
- package/dist/hooks/useDensity.d.ts +73 -73
- package/dist/hooks/useMCPTools.d.ts +1 -1
- package/dist/hooks/useRadius.d.ts +1 -1
- package/dist/{index-BpJstUh1.cjs → index-C4bFBGfl.cjs} +4 -4
- package/dist/{index-BpJstUh1.cjs.map → index-C4bFBGfl.cjs.map} +1 -1
- package/dist/{index-CUitXazZ.js → index-D93pV0_o.js} +55 -55
- package/dist/{index-CUitXazZ.js.map → index-D93pV0_o.js.map} +1 -1
- package/dist/{index-D0bAYNQy.js → index-DuCQRbcQ.js} +279 -265
- package/dist/index-DuCQRbcQ.js.map +1 -0
- package/dist/{index-KSX4Qjip.cjs → index-y_PNN5vK.cjs} +10 -10
- package/dist/index-y_PNN5vK.cjs.map +1 -0
- package/dist/lib/cassette.d.ts +4 -4
- package/dist/lib/errorTracking.d.ts +1 -1
- package/dist/lib/messageConverter.d.ts +1 -1
- package/dist/lib/models.d.ts +1 -1
- package/dist/plugins/chart/ui/bar-chart.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/accordion.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/action-button.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/alert.d.ts +4 -4
- package/dist/plugins/generative-ui/ui/avatar.d.ts +5 -5
- package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/button.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/card.d.ts +8 -8
- package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/data-table.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/dialog.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/grid.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/input.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/metric.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/pagination.d.ts +6 -6
- package/dist/plugins/generative-ui/ui/popover.d.ts +4 -4
- package/dist/plugins/generative-ui/ui/progress.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/radio-group.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/select.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/stack.d.ts +6 -6
- package/dist/plugins/generative-ui/ui/switch.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/table.d.ts +9 -9
- package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/tabs.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/text.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/textarea.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/tooltip.d.ts +1 -1
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-BFkhZRxj.js → profiler-FpBY9eRv.js} +2 -2
- package/dist/{profiler-BFkhZRxj.js.map → profiler-FpBY9eRv.js.map} +1 -1
- package/dist/{profiler-CyzxBxVz.cjs → profiler-_mthyjvo.cjs} +2 -2
- package/dist/{profiler-CyzxBxVz.cjs.map → profiler-_mthyjvo.cjs.map} +1 -1
- package/dist/react-shim.js +1 -1
- package/dist/server/express.cjs.map +1 -1
- package/dist/server/express.js.map +1 -1
- package/dist/{startRecording-Dq92sEHf.cjs → startRecording-NJcpiHw-.cjs} +2 -2
- package/dist/{startRecording-Dq92sEHf.cjs.map → startRecording-NJcpiHw-.cjs.map} +1 -1
- package/dist/{startRecording-C-PPAs_Z.js → startRecording-r5MXQ2Dm.js} +2 -2
- package/dist/{startRecording-C-PPAs_Z.js.map → startRecording-r5MXQ2Dm.js.map} +1 -1
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -5
- package/src/compat-plugin.ts +14 -14
- package/src/compat-shims.ts +33 -31
- package/src/compat.test.ts +48 -48
- package/src/compat.ts +6 -6
- package/src/components/Chat/index.tsx +17 -17
- package/src/components/Chat/stories/Charts.stories.tsx +98 -98
- package/src/components/Chat/stories/Composer.stories.tsx +15 -15
- package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +44 -44
- package/src/components/Chat/stories/CustomComponents.stories.tsx +17 -17
- package/src/components/Chat/stories/Density.stories.tsx +20 -20
- package/src/components/Chat/stories/ErrorBoundary.stories.tsx +47 -47
- package/src/components/Chat/stories/FrontendTools.stories.tsx +39 -39
- package/src/components/Chat/stories/GenerativeUI.stories.tsx +48 -48
- package/src/components/Chat/stories/MessageFeedback.stories.tsx +52 -52
- package/src/components/Chat/stories/Modal.stories.tsx +28 -28
- package/src/components/Chat/stories/Model.stories.tsx +11 -11
- package/src/components/Chat/stories/Radius.stories.tsx +20 -20
- package/src/components/Chat/stories/Sidecar.stories.tsx +13 -13
- package/src/components/Chat/stories/StyleIsolation.stories.tsx +11 -11
- package/src/components/Chat/stories/Theme.stories.tsx +25 -25
- package/src/components/Chat/stories/Thread.stories.tsx +25 -25
- package/src/components/Chat/stories/ToolApproval.stories.tsx +55 -55
- package/src/components/Chat/stories/ToolMentions.stories.tsx +17 -17
- package/src/components/Chat/stories/Tools.stories.tsx +88 -88
- package/src/components/Chat/stories/Variants.stories.tsx +32 -32
- package/src/components/Chat/stories/Welcome.stories.tsx +14 -14
- package/src/components/ChatHistory.tsx +7 -7
- package/src/components/FrontendTools/index.tsx +5 -5
- package/src/components/Replay.stories.tsx +157 -157
- package/src/components/Replay.tsx +76 -73
- package/src/components/ShadowRoot.tsx +40 -40
- package/src/components/ShareButton/index.tsx +32 -32
- package/src/components/assistant-ui/assistant-modal.tsx +92 -87
- package/src/components/assistant-ui/assistant-sidecar.tsx +35 -35
- package/src/components/assistant-ui/attachment.tsx +80 -80
- package/src/components/assistant-ui/connection-status-indicator.tsx +33 -33
- package/src/components/assistant-ui/error-boundary.tsx +34 -34
- package/src/components/assistant-ui/follow-on-suggestions.tsx +26 -26
- package/src/components/assistant-ui/markdown-text.tsx +69 -69
- package/src/components/assistant-ui/mentioned-tools-badges.tsx +38 -38
- package/src/components/assistant-ui/message-feedback.tsx +57 -50
- package/src/components/assistant-ui/reasoning.tsx +83 -83
- package/src/components/assistant-ui/thread-list.tsx +45 -45
- package/src/components/assistant-ui/thread.tsx +278 -278
- package/src/components/assistant-ui/tool-fallback.tsx +37 -37
- package/src/components/assistant-ui/tool-group.tsx +26 -26
- package/src/components/assistant-ui/tool-mention-autocomplete.tsx +122 -122
- package/src/components/assistant-ui/tooltip-icon-button.tsx +18 -18
- package/src/components/ui/avatar.tsx +12 -12
- package/src/components/ui/button.tsx +12 -12
- package/src/components/ui/buttonVariants.ts +17 -17
- package/src/components/ui/calendar.tsx +106 -106
- package/src/components/ui/charts.stories.tsx +56 -56
- package/src/components/ui/collapsible.tsx +5 -5
- package/src/components/ui/dialog.tsx +30 -30
- package/src/components/ui/generative-ui.stories.tsx +200 -200
- package/src/components/ui/generative-ui.tsx +26 -26
- package/src/components/ui/popover.tsx +14 -14
- package/src/components/ui/skeleton.tsx +5 -5
- package/src/components/ui/time-range-picker.stories.tsx +80 -80
- package/src/components/ui/time-range-picker.tsx +245 -244
- package/src/components/ui/tool-ui.stories.tsx +37 -37
- package/src/components/ui/tool-ui.tsx +221 -215
- package/src/components/ui/tooltip.tsx +15 -15
- package/src/constants/tailwind.ts +1 -1
- package/src/contexts/ChatIdContext.tsx +7 -7
- package/src/contexts/ConnectionStatusContext.tsx +64 -64
- package/src/contexts/ElementsProvider.tsx +214 -213
- package/src/contexts/ReplayContext.ts +3 -3
- package/src/contexts/ToolApprovalContext.tsx +54 -54
- package/src/contexts/ToolExecutionContext.tsx +34 -34
- package/src/contexts/contexts.ts +7 -7
- package/src/contexts/portal-container-context.ts +2 -2
- package/src/contexts/portal-container.tsx +7 -7
- package/src/embedded.ts +1 -1
- package/src/global.css +25 -25
- package/src/hooks/useAuth.ts +72 -72
- package/src/hooks/useDensity.ts +79 -79
- package/src/hooks/useElements.ts +6 -6
- package/src/hooks/useExpanded.ts +12 -12
- package/src/hooks/useFollowOnSuggestions.ts +83 -83
- package/src/hooks/useGramThreadListAdapter.tsx +99 -99
- package/src/hooks/useMCPTools.ts +47 -47
- package/src/hooks/useModel.ts +14 -14
- package/src/hooks/usePluginComponents.ts +11 -11
- package/src/hooks/usePortalContainer.ts +5 -5
- package/src/hooks/useRadius.ts +23 -23
- package/src/hooks/useRecordCassette.ts +34 -34
- package/src/hooks/useSession.ts +11 -11
- package/src/hooks/useThemeProps.ts +13 -13
- package/src/hooks/useThreadId.ts +4 -4
- package/src/hooks/useToolApproval.ts +7 -7
- package/src/hooks/useToolMentions.ts +40 -40
- package/src/index.ts +26 -26
- package/src/lib/api.test.ts +61 -61
- package/src/lib/api.ts +4 -3
- package/src/lib/auth.ts +13 -13
- package/src/lib/cassette.ts +84 -84
- package/src/lib/easing.ts +1 -1
- package/src/lib/errorTracking.config.ts +5 -5
- package/src/lib/errorTracking.ts +29 -29
- package/src/lib/generative-ui.ts +7 -7
- package/src/lib/humanize.ts +3 -3
- package/src/lib/messageConverter.test.ts +130 -127
- package/src/lib/messageConverter.ts +196 -196
- package/src/lib/models.ts +21 -20
- package/src/lib/token.test.ts +56 -56
- package/src/lib/token.ts +14 -14
- package/src/lib/tool-mentions.ts +45 -45
- package/src/lib/tools.ts +66 -62
- package/src/lib/utils.ts +5 -5
- package/src/lib.d.ts +1 -1
- package/src/plugins/README.md +5 -5
- package/src/plugins/chart/catalog.ts +18 -18
- package/src/plugins/chart/chart.test.ts +31 -31
- package/src/plugins/chart/component.tsx +34 -34
- package/src/plugins/chart/index.ts +4 -4
- package/src/plugins/chart/ui/area-chart.tsx +42 -42
- package/src/plugins/chart/ui/bar-chart.tsx +46 -46
- package/src/plugins/chart/ui/donut-chart.tsx +48 -48
- package/src/plugins/chart/ui/index.ts +7 -7
- package/src/plugins/chart/ui/line-chart.tsx +43 -43
- package/src/plugins/chart/ui/pie-chart.tsx +44 -44
- package/src/plugins/chart/ui/radar-chart.tsx +33 -33
- package/src/plugins/chart/ui/scatter-chart.tsx +43 -43
- package/src/plugins/components/MacOSWindowFrame.tsx +15 -15
- package/src/plugins/components/PluginLoadingState.tsx +10 -10
- package/src/plugins/components/index.ts +1 -1
- package/src/plugins/generative-ui/catalog.ts +54 -54
- package/src/plugins/generative-ui/component.tsx +85 -85
- package/src/plugins/generative-ui/index.ts +4 -4
- package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +16 -16
- package/src/plugins/generative-ui/ui/accordion.tsx +16 -16
- package/src/plugins/generative-ui/ui/action-button.tsx +28 -28
- package/src/plugins/generative-ui/ui/alert-wrapper.tsx +8 -8
- package/src/plugins/generative-ui/ui/alert.tsx +20 -20
- package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +7 -7
- package/src/plugins/generative-ui/ui/avatar.tsx +30 -30
- package/src/plugins/generative-ui/ui/badge.tsx +22 -22
- package/src/plugins/generative-ui/ui/button-wrapper.tsx +12 -12
- package/src/plugins/generative-ui/ui/button.tsx +28 -28
- package/src/plugins/generative-ui/ui/card-wrapper.tsx +8 -8
- package/src/plugins/generative-ui/ui/card.tsx +27 -27
- package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +9 -9
- package/src/plugins/generative-ui/ui/checkbox.tsx +9 -9
- package/src/plugins/generative-ui/ui/data-table.tsx +8 -8
- package/src/plugins/generative-ui/ui/dialog.tsx +31 -31
- package/src/plugins/generative-ui/ui/dropdown-menu.tsx +44 -44
- package/src/plugins/generative-ui/ui/grid.tsx +12 -12
- package/src/plugins/generative-ui/ui/index.ts +40 -40
- package/src/plugins/generative-ui/ui/input-wrapper.tsx +11 -11
- package/src/plugins/generative-ui/ui/input.tsx +9 -9
- package/src/plugins/generative-ui/ui/label.tsx +8 -8
- package/src/plugins/generative-ui/ui/list.tsx +11 -11
- package/src/plugins/generative-ui/ui/metric.tsx +23 -23
- package/src/plugins/generative-ui/ui/pagination.tsx +28 -28
- package/src/plugins/generative-ui/ui/popover.tsx +21 -21
- package/src/plugins/generative-ui/ui/progress.tsx +13 -13
- package/src/plugins/generative-ui/ui/radio-group.tsx +12 -12
- package/src/plugins/generative-ui/ui/select-wrapper.tsx +7 -7
- package/src/plugins/generative-ui/ui/select.tsx +37 -37
- package/src/plugins/generative-ui/ui/separator.tsx +9 -9
- package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +10 -10
- package/src/plugins/generative-ui/ui/skeleton.tsx +5 -5
- package/src/plugins/generative-ui/ui/stack.tsx +28 -28
- package/src/plugins/generative-ui/ui/switch.tsx +11 -11
- package/src/plugins/generative-ui/ui/table.tsx +32 -32
- package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +11 -11
- package/src/plugins/generative-ui/ui/tabs.tsx +26 -26
- package/src/plugins/generative-ui/ui/text.tsx +12 -12
- package/src/plugins/generative-ui/ui/textarea.tsx +7 -7
- package/src/plugins/generative-ui/ui/tooltip.tsx +12 -12
- package/src/plugins/index.ts +7 -7
- package/src/react-shim.ts +6 -6
- package/src/server/bun.ts +12 -12
- package/src/server/core.ts +25 -25
- package/src/server/express.ts +17 -15
- package/src/server/fastify.ts +14 -14
- package/src/server/hono.ts +9 -9
- package/src/server/nextjs.ts +12 -12
- package/src/server/tanstack-start.ts +12 -12
- package/src/server.ts +27 -27
- package/src/storybook.d.ts +4 -4
- package/src/types/index.ts +122 -122
- package/src/types/plugins.ts +7 -7
- package/src/vite-env.d.ts +12 -12
- package/dist/compat-shims-BPJ7Q68c.js.map +0 -1
- package/dist/index-D0bAYNQy.js.map +0 -1
- package/dist/index-KSX4Qjip.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { useState, useEffect } from
|
|
3
|
-
import { cva } from
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
4
|
import {
|
|
5
5
|
CheckIcon,
|
|
6
6
|
ChevronDownIcon,
|
|
@@ -9,94 +9,98 @@ import {
|
|
|
9
9
|
CopyIcon,
|
|
10
10
|
LoaderIcon,
|
|
11
11
|
XIcon,
|
|
12
|
-
} from
|
|
13
|
-
import { cn } from
|
|
14
|
-
import { codeToHtml, BundledLanguage } from
|
|
15
|
-
import { Button } from
|
|
16
|
-
import { Popover, PopoverAnchor, PopoverContent } from
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
import { cn } from "@/lib/utils";
|
|
14
|
+
import { codeToHtml, BundledLanguage } from "shiki";
|
|
15
|
+
import { Button } from "./button";
|
|
16
|
+
import { Popover, PopoverAnchor, PopoverContent } from "./popover";
|
|
17
17
|
|
|
18
18
|
/* -----------------------------------------------------------------------------
|
|
19
19
|
* Status indicator styles
|
|
20
20
|
* -------------------------------------------------------------------------- */
|
|
21
21
|
|
|
22
22
|
const statusVariants = cva(
|
|
23
|
-
|
|
23
|
+
"flex size-5 items-center justify-center rounded-full",
|
|
24
24
|
{
|
|
25
25
|
variants: {
|
|
26
26
|
status: {
|
|
27
|
-
pending:
|
|
28
|
-
running:
|
|
29
|
-
complete:
|
|
30
|
-
error:
|
|
31
|
-
approval:
|
|
27
|
+
pending: "border border-dashed border-muted-foreground/50",
|
|
28
|
+
running: "text-primary",
|
|
29
|
+
complete: "text-green-600 dark:text-green-500",
|
|
30
|
+
error: "text-destructive",
|
|
31
|
+
approval: "text-amber-500",
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
34
|
defaultVariants: {
|
|
35
|
-
status:
|
|
35
|
+
status: "pending",
|
|
36
36
|
},
|
|
37
|
-
}
|
|
38
|
-
)
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
39
|
|
|
40
40
|
/* -----------------------------------------------------------------------------
|
|
41
41
|
* Types
|
|
42
42
|
* -------------------------------------------------------------------------- */
|
|
43
43
|
|
|
44
|
-
type ToolStatus =
|
|
44
|
+
type ToolStatus = "pending" | "running" | "complete" | "error" | "approval";
|
|
45
45
|
|
|
46
46
|
type ContentItem =
|
|
47
|
-
| { type:
|
|
48
|
-
| {
|
|
47
|
+
| { type: "text"; text: string; _meta?: { "getgram.ai/mime-type"?: string } }
|
|
48
|
+
| {
|
|
49
|
+
type: "image";
|
|
50
|
+
data: string;
|
|
51
|
+
_meta?: { "getgram.ai/mime-type"?: string };
|
|
52
|
+
};
|
|
49
53
|
|
|
50
54
|
/** MCP tool annotations providing hints about tool behavior */
|
|
51
55
|
interface ToolAnnotations {
|
|
52
56
|
/** Human-readable display name for the tool */
|
|
53
|
-
title?: string
|
|
57
|
+
title?: string;
|
|
54
58
|
/** If true, the tool does not modify its environment */
|
|
55
|
-
readOnlyHint?: boolean
|
|
59
|
+
readOnlyHint?: boolean;
|
|
56
60
|
/** If true, the tool may perform destructive updates */
|
|
57
|
-
destructiveHint?: boolean
|
|
61
|
+
destructiveHint?: boolean;
|
|
58
62
|
/** If true, repeated calls with same args have no additional effect */
|
|
59
|
-
idempotentHint?: boolean
|
|
63
|
+
idempotentHint?: boolean;
|
|
60
64
|
/** If true, tool interacts with external entities */
|
|
61
|
-
openWorldHint?: boolean
|
|
65
|
+
openWorldHint?: boolean;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
interface ToolUIProps {
|
|
65
69
|
/** Display name of the tool */
|
|
66
|
-
name: string
|
|
70
|
+
name: string;
|
|
67
71
|
/** Optional icon to display (defaults to first letter of name) */
|
|
68
|
-
icon?: React.ReactNode
|
|
72
|
+
icon?: React.ReactNode;
|
|
69
73
|
/** Provider/source name (e.g., "Notion", "GitHub") */
|
|
70
|
-
provider?: string
|
|
74
|
+
provider?: string;
|
|
71
75
|
/** Current status of the tool execution */
|
|
72
|
-
status?: ToolStatus
|
|
76
|
+
status?: ToolStatus;
|
|
73
77
|
/** Request/input data - can be string or object */
|
|
74
|
-
request?: string | Record<string, unknown
|
|
78
|
+
request?: string | Record<string, unknown>;
|
|
75
79
|
/** Result/output data - can be string, object, or structured content array */
|
|
76
|
-
result?: string | Record<string, unknown> | { content: ContentItem[] }
|
|
80
|
+
result?: string | Record<string, unknown> | { content: ContentItem[] };
|
|
77
81
|
/** Whether the tool card starts expanded */
|
|
78
|
-
defaultExpanded?: boolean
|
|
82
|
+
defaultExpanded?: boolean;
|
|
79
83
|
/** Additional class names */
|
|
80
|
-
className?: string
|
|
84
|
+
className?: string;
|
|
81
85
|
/** MCP tool annotations */
|
|
82
|
-
annotations?: ToolAnnotations
|
|
86
|
+
annotations?: ToolAnnotations;
|
|
83
87
|
/** Approval callbacks */
|
|
84
|
-
onApproveOnce?: () => void
|
|
85
|
-
onApproveForSession?: () => void
|
|
86
|
-
onDeny?: () => void
|
|
88
|
+
onApproveOnce?: () => void;
|
|
89
|
+
onApproveForSession?: () => void;
|
|
90
|
+
onDeny?: () => void;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
interface ToolUISectionProps {
|
|
90
94
|
/** Section title */
|
|
91
|
-
title: string
|
|
95
|
+
title: string;
|
|
92
96
|
/** Content to display - string or object (will be JSON stringified) */
|
|
93
|
-
content: string | Record<string, unknown> | { content: ContentItem[] }
|
|
97
|
+
content: string | Record<string, unknown> | { content: ContentItem[] };
|
|
94
98
|
/** Whether section starts expanded */
|
|
95
|
-
defaultExpanded?: boolean
|
|
99
|
+
defaultExpanded?: boolean;
|
|
96
100
|
/** Enable syntax highlighting */
|
|
97
|
-
highlightSyntax?: boolean
|
|
101
|
+
highlightSyntax?: boolean;
|
|
98
102
|
/** Language hint for syntax highlighting */
|
|
99
|
-
language?: BundledLanguage
|
|
103
|
+
language?: BundledLanguage;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/* -----------------------------------------------------------------------------
|
|
@@ -104,51 +108,51 @@ interface ToolUISectionProps {
|
|
|
104
108
|
* -------------------------------------------------------------------------- */
|
|
105
109
|
|
|
106
110
|
function getLanguageFromMimeType(
|
|
107
|
-
mimeType: string
|
|
111
|
+
mimeType: string,
|
|
108
112
|
): BundledLanguage | undefined {
|
|
109
113
|
switch (mimeType) {
|
|
110
|
-
case
|
|
111
|
-
return
|
|
112
|
-
case
|
|
113
|
-
return
|
|
114
|
-
case
|
|
115
|
-
return
|
|
116
|
-
case
|
|
117
|
-
return
|
|
118
|
-
case
|
|
119
|
-
return
|
|
120
|
-
case
|
|
121
|
-
return
|
|
122
|
-
case
|
|
123
|
-
return
|
|
114
|
+
case "text/markdown":
|
|
115
|
+
return "markdown";
|
|
116
|
+
case "text/html":
|
|
117
|
+
return "html";
|
|
118
|
+
case "text/css":
|
|
119
|
+
return "css";
|
|
120
|
+
case "application/json":
|
|
121
|
+
return "json";
|
|
122
|
+
case "text/javascript":
|
|
123
|
+
return "javascript";
|
|
124
|
+
case "text/typescript":
|
|
125
|
+
return "typescript";
|
|
126
|
+
case "text/python":
|
|
127
|
+
return "python";
|
|
124
128
|
default:
|
|
125
|
-
return undefined
|
|
129
|
+
return undefined;
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
function formatTextForLanguage(
|
|
130
134
|
text: string,
|
|
131
|
-
language: BundledLanguage | undefined
|
|
135
|
+
language: BundledLanguage | undefined,
|
|
132
136
|
): string {
|
|
133
|
-
if (language ===
|
|
137
|
+
if (language === "json") {
|
|
134
138
|
try {
|
|
135
|
-
return JSON.stringify(JSON.parse(text), null, 2)
|
|
139
|
+
return JSON.stringify(JSON.parse(text), null, 2);
|
|
136
140
|
} catch {
|
|
137
|
-
return text
|
|
141
|
+
return text;
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
|
-
return text
|
|
144
|
+
return text;
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
function isStructuredContent(
|
|
144
|
-
content: unknown
|
|
148
|
+
content: unknown,
|
|
145
149
|
): content is { content: ContentItem[] } {
|
|
146
150
|
return (
|
|
147
|
-
typeof content ===
|
|
151
|
+
typeof content === "object" &&
|
|
148
152
|
content !== null &&
|
|
149
|
-
|
|
153
|
+
"content" in content &&
|
|
150
154
|
Array.isArray((content as { content: unknown }).content)
|
|
151
|
-
)
|
|
155
|
+
);
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
/* -----------------------------------------------------------------------------
|
|
@@ -158,31 +162,31 @@ function isStructuredContent(
|
|
|
158
162
|
function StatusIndicator({ status }: { status: ToolStatus }) {
|
|
159
163
|
return (
|
|
160
164
|
<div className={cn(statusVariants({ status }))}>
|
|
161
|
-
{status ===
|
|
162
|
-
{status ===
|
|
163
|
-
{status ===
|
|
164
|
-
{status ===
|
|
165
|
-
{status ===
|
|
166
|
-
<LoaderIcon className="
|
|
165
|
+
{status === "pending" && null}
|
|
166
|
+
{status === "running" && <LoaderIcon className="size-4 animate-spin" />}
|
|
167
|
+
{status === "complete" && <CheckIcon className="size-4" />}
|
|
168
|
+
{status === "error" && <XIcon className="size-4" />}
|
|
169
|
+
{status === "approval" && (
|
|
170
|
+
<LoaderIcon className="size-4 animate-spin text-muted-foreground" />
|
|
167
171
|
)}
|
|
168
172
|
</div>
|
|
169
|
-
)
|
|
173
|
+
);
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
function CopyButton({ content }: { content: string }) {
|
|
173
|
-
const [copied, setCopied] = useState(false)
|
|
177
|
+
const [copied, setCopied] = useState(false);
|
|
174
178
|
|
|
175
179
|
const handleCopy = async (e: React.MouseEvent) => {
|
|
176
|
-
e.stopPropagation()
|
|
177
|
-
await navigator.clipboard.writeText(content)
|
|
178
|
-
setCopied(true)
|
|
179
|
-
setTimeout(() => setCopied(false), 2000)
|
|
180
|
-
}
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
await navigator.clipboard.writeText(content);
|
|
182
|
+
setCopied(true);
|
|
183
|
+
setTimeout(() => setCopied(false), 2000);
|
|
184
|
+
};
|
|
181
185
|
|
|
182
186
|
return (
|
|
183
187
|
<button
|
|
184
188
|
onClick={handleCopy}
|
|
185
|
-
className="text-muted-foreground hover:bg-accent hover:text-foreground
|
|
189
|
+
className="rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
186
190
|
aria-label="Copy to clipboard"
|
|
187
191
|
>
|
|
188
192
|
{copied ? (
|
|
@@ -191,7 +195,7 @@ function CopyButton({ content }: { content: string }) {
|
|
|
191
195
|
<CopyIcon className="size-4" />
|
|
192
196
|
)}
|
|
193
197
|
</button>
|
|
194
|
-
)
|
|
198
|
+
);
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
/* -----------------------------------------------------------------------------
|
|
@@ -199,19 +203,19 @@ function CopyButton({ content }: { content: string }) {
|
|
|
199
203
|
* -------------------------------------------------------------------------- */
|
|
200
204
|
|
|
201
205
|
/** Max characters to send through shiki — above this we skip highlighting. */
|
|
202
|
-
const SHIKI_CHAR_LIMIT = 8_000
|
|
206
|
+
const SHIKI_CHAR_LIMIT = 8_000;
|
|
203
207
|
/** Max lines shown in the collapsed preview. */
|
|
204
|
-
const PREVIEW_LINE_LIMIT = 50
|
|
208
|
+
const PREVIEW_LINE_LIMIT = 50;
|
|
205
209
|
|
|
206
210
|
function truncateToLines(text: string, maxLines: number) {
|
|
207
|
-
let pos = 0
|
|
211
|
+
let pos = 0;
|
|
208
212
|
for (let i = 0; i < maxLines; i++) {
|
|
209
|
-
const next = text.indexOf(
|
|
210
|
-
if (next === -1) return { text, truncated: false, totalLines: i + 1 }
|
|
211
|
-
pos = next + 1
|
|
213
|
+
const next = text.indexOf("\n", pos);
|
|
214
|
+
if (next === -1) return { text, truncated: false, totalLines: i + 1 };
|
|
215
|
+
pos = next + 1;
|
|
212
216
|
}
|
|
213
|
-
const totalLines = text.split(
|
|
214
|
-
return { text: text.slice(0, pos), truncated: true, totalLines }
|
|
217
|
+
const totalLines = text.split("\n").length;
|
|
218
|
+
return { text: text.slice(0, pos), truncated: true, totalLines };
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
function SyntaxHighlightedCode({
|
|
@@ -219,43 +223,43 @@ function SyntaxHighlightedCode({
|
|
|
219
223
|
language,
|
|
220
224
|
className,
|
|
221
225
|
}: {
|
|
222
|
-
text: string
|
|
223
|
-
language?: BundledLanguage
|
|
224
|
-
className?: string
|
|
226
|
+
text: string;
|
|
227
|
+
language?: BundledLanguage;
|
|
228
|
+
className?: string;
|
|
225
229
|
}) {
|
|
226
|
-
const [highlightedCode, setHighlightedCode] = useState<string | null>(null)
|
|
227
|
-
const [expanded, setExpanded] = useState(false)
|
|
230
|
+
const [highlightedCode, setHighlightedCode] = useState<string | null>(null);
|
|
231
|
+
const [expanded, setExpanded] = useState(false);
|
|
228
232
|
|
|
229
233
|
const preview = React.useMemo(
|
|
230
234
|
() => truncateToLines(text, PREVIEW_LINE_LIMIT),
|
|
231
|
-
[text]
|
|
232
|
-
)
|
|
233
|
-
const displayText = expanded ? text : preview.text
|
|
234
|
-
const canHighlight = displayText.length <= SHIKI_CHAR_LIMIT
|
|
235
|
+
[text],
|
|
236
|
+
);
|
|
237
|
+
const displayText = expanded ? text : preview.text;
|
|
238
|
+
const canHighlight = displayText.length <= SHIKI_CHAR_LIMIT;
|
|
235
239
|
|
|
236
240
|
useEffect(() => {
|
|
237
|
-
setHighlightedCode(null)
|
|
238
|
-
if (!language || !canHighlight) return
|
|
239
|
-
let cancelled = false
|
|
241
|
+
setHighlightedCode(null);
|
|
242
|
+
if (!language || !canHighlight) return;
|
|
243
|
+
let cancelled = false;
|
|
240
244
|
codeToHtml(displayText, {
|
|
241
245
|
lang: language,
|
|
242
|
-
theme:
|
|
243
|
-
rootStyle:
|
|
246
|
+
theme: "github-dark-default",
|
|
247
|
+
rootStyle: "background-color: transparent;",
|
|
244
248
|
transformers: [
|
|
245
249
|
{
|
|
246
250
|
pre(node) {
|
|
247
251
|
node.properties.class =
|
|
248
|
-
|
|
252
|
+
"w-full py-3 px-4 max-h-[300px] overflow-y-auto whitespace-pre-wrap text-left text-sm";
|
|
249
253
|
},
|
|
250
254
|
},
|
|
251
255
|
],
|
|
252
256
|
}).then((html) => {
|
|
253
|
-
if (!cancelled) setHighlightedCode(html)
|
|
254
|
-
})
|
|
257
|
+
if (!cancelled) setHighlightedCode(html);
|
|
258
|
+
});
|
|
255
259
|
return () => {
|
|
256
|
-
cancelled = true
|
|
257
|
-
}
|
|
258
|
-
}, [displayText, language, canHighlight])
|
|
260
|
+
cancelled = true;
|
|
261
|
+
};
|
|
262
|
+
}, [displayText, language, canHighlight]);
|
|
259
263
|
|
|
260
264
|
const showMoreButton = preview.truncated && !expanded && (
|
|
261
265
|
<button
|
|
@@ -265,28 +269,28 @@ function SyntaxHighlightedCode({
|
|
|
265
269
|
>
|
|
266
270
|
Show all {preview.totalLines} lines…
|
|
267
271
|
</button>
|
|
268
|
-
)
|
|
272
|
+
);
|
|
269
273
|
|
|
270
274
|
if (!canHighlight || !highlightedCode) {
|
|
271
275
|
return (
|
|
272
|
-
<div className={cn(
|
|
276
|
+
<div className={cn("w-full", className)}>
|
|
273
277
|
<pre className="max-h-[300px] w-full overflow-y-auto bg-slate-800/90 px-4 py-3 text-sm whitespace-pre-wrap text-slate-100">
|
|
274
278
|
{displayText}
|
|
275
279
|
</pre>
|
|
276
280
|
{showMoreButton}
|
|
277
281
|
</div>
|
|
278
|
-
)
|
|
282
|
+
);
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
return (
|
|
282
|
-
<div className={cn(
|
|
286
|
+
<div className={cn("w-full", className)}>
|
|
283
287
|
<div
|
|
284
288
|
className="w-full bg-slate-800/90"
|
|
285
289
|
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
|
286
290
|
/>
|
|
287
291
|
{showMoreButton}
|
|
288
292
|
</div>
|
|
289
|
-
)
|
|
293
|
+
);
|
|
290
294
|
}
|
|
291
295
|
|
|
292
296
|
/* -----------------------------------------------------------------------------
|
|
@@ -294,7 +298,7 @@ function SyntaxHighlightedCode({
|
|
|
294
298
|
* -------------------------------------------------------------------------- */
|
|
295
299
|
|
|
296
300
|
function ImageContent({ data }: { data: string }) {
|
|
297
|
-
const image = `data:image/png;base64,${data}
|
|
301
|
+
const image = `data:image/png;base64,${data}`;
|
|
298
302
|
return (
|
|
299
303
|
<div
|
|
300
304
|
className="flex items-center justify-center rounded-lg p-5"
|
|
@@ -303,13 +307,13 @@ function ImageContent({ data }: { data: string }) {
|
|
|
303
307
|
linear-gradient(135deg, #ccc 25%, transparent 25%),
|
|
304
308
|
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
|
305
309
|
linear-gradient(135deg, transparent 75%, #ccc 75%)`,
|
|
306
|
-
backgroundSize:
|
|
307
|
-
backgroundPosition:
|
|
310
|
+
backgroundSize: "25px 25px",
|
|
311
|
+
backgroundPosition: "0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px",
|
|
308
312
|
}}
|
|
309
313
|
>
|
|
310
314
|
<img src={image} className="max-h-[300px] max-w-full object-contain" />
|
|
311
315
|
</div>
|
|
312
|
-
)
|
|
316
|
+
);
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
/* -----------------------------------------------------------------------------
|
|
@@ -319,27 +323,27 @@ function ImageContent({ data }: { data: string }) {
|
|
|
319
323
|
function StructuredResultContent({
|
|
320
324
|
content,
|
|
321
325
|
}: {
|
|
322
|
-
content: { content: ContentItem[] }
|
|
326
|
+
content: { content: ContentItem[] };
|
|
323
327
|
}) {
|
|
324
328
|
return (
|
|
325
329
|
<div className="w-full">
|
|
326
330
|
{content.content.map((item, index) => {
|
|
327
331
|
switch (item.type) {
|
|
328
|
-
case
|
|
332
|
+
case "text": {
|
|
329
333
|
const language = getLanguageFromMimeType(
|
|
330
|
-
item._meta?.[
|
|
331
|
-
)
|
|
332
|
-
const formattedText = formatTextForLanguage(item.text, language)
|
|
334
|
+
item._meta?.["getgram.ai/mime-type"] ?? "text/plain",
|
|
335
|
+
);
|
|
336
|
+
const formattedText = formatTextForLanguage(item.text, language);
|
|
333
337
|
return (
|
|
334
338
|
<SyntaxHighlightedCode
|
|
335
339
|
key={index}
|
|
336
340
|
text={formattedText}
|
|
337
341
|
language={language}
|
|
338
342
|
/>
|
|
339
|
-
)
|
|
343
|
+
);
|
|
340
344
|
}
|
|
341
|
-
case
|
|
342
|
-
return <ImageContent key={index} data={item.data}
|
|
345
|
+
case "image": {
|
|
346
|
+
return <ImageContent key={index} data={item.data} />;
|
|
343
347
|
}
|
|
344
348
|
default:
|
|
345
349
|
return (
|
|
@@ -349,11 +353,11 @@ function StructuredResultContent({
|
|
|
349
353
|
>
|
|
350
354
|
{JSON.stringify(item, null, 2)}
|
|
351
355
|
</pre>
|
|
352
|
-
)
|
|
356
|
+
);
|
|
353
357
|
}
|
|
354
358
|
})}
|
|
355
359
|
</div>
|
|
356
|
-
)
|
|
360
|
+
);
|
|
357
361
|
}
|
|
358
362
|
|
|
359
363
|
/* -----------------------------------------------------------------------------
|
|
@@ -365,53 +369,53 @@ function ToolUISection({
|
|
|
365
369
|
content,
|
|
366
370
|
defaultExpanded = false,
|
|
367
371
|
highlightSyntax = true,
|
|
368
|
-
language =
|
|
372
|
+
language = "json",
|
|
369
373
|
}: ToolUISectionProps) {
|
|
370
|
-
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
|
374
|
+
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
371
375
|
|
|
372
376
|
// For structured content, we don't stringify it
|
|
373
|
-
const isStructured = isStructuredContent(content)
|
|
377
|
+
const isStructured = isStructuredContent(content);
|
|
374
378
|
const contentString = isStructured
|
|
375
379
|
? JSON.stringify(content, null, 2)
|
|
376
|
-
: typeof content ===
|
|
380
|
+
: typeof content === "string"
|
|
377
381
|
? content
|
|
378
|
-
: JSON.stringify(content, null, 2)
|
|
382
|
+
: JSON.stringify(content, null, 2);
|
|
379
383
|
|
|
380
384
|
return (
|
|
381
|
-
<div data-slot="tool-ui-section" className="border-
|
|
385
|
+
<div data-slot="tool-ui-section" className="border-t border-border">
|
|
382
386
|
<button
|
|
383
387
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
384
|
-
className="
|
|
388
|
+
className="flex w-full cursor-pointer items-center justify-between px-5 py-2.5 text-left transition-colors hover:bg-accent/50"
|
|
385
389
|
>
|
|
386
|
-
<span className="text-muted-foreground
|
|
390
|
+
<span className="text-sm text-muted-foreground">{title}</span>
|
|
387
391
|
<div className="flex items-center gap-1">
|
|
388
392
|
<CopyButton content={contentString} />
|
|
389
393
|
<ChevronRightIcon
|
|
390
394
|
className={cn(
|
|
391
|
-
|
|
392
|
-
isExpanded &&
|
|
395
|
+
"size-4 text-muted-foreground transition-transform duration-200",
|
|
396
|
+
isExpanded && "rotate-90",
|
|
393
397
|
)}
|
|
394
398
|
/>
|
|
395
399
|
</div>
|
|
396
400
|
</button>
|
|
397
401
|
{isExpanded && (
|
|
398
|
-
<div className="border-
|
|
402
|
+
<div className="border-t border-border">
|
|
399
403
|
{isStructured ? (
|
|
400
404
|
<StructuredResultContent content={content} />
|
|
401
405
|
) : highlightSyntax ? (
|
|
402
406
|
<SyntaxHighlightedCode text={contentString} language={language} />
|
|
403
407
|
) : (
|
|
404
|
-
<pre className="
|
|
408
|
+
<pre className="overflow-x-auto px-4 py-3 text-sm whitespace-pre-wrap text-foreground">
|
|
405
409
|
{contentString}
|
|
406
410
|
</pre>
|
|
407
411
|
)}
|
|
408
412
|
</div>
|
|
409
413
|
)}
|
|
410
414
|
</div>
|
|
411
|
-
)
|
|
415
|
+
);
|
|
412
416
|
}
|
|
413
417
|
|
|
414
|
-
type ApprovalMode =
|
|
418
|
+
type ApprovalMode = "one-time" | "for-session";
|
|
415
419
|
|
|
416
420
|
/* -----------------------------------------------------------------------------
|
|
417
421
|
* ToolUI - Main component
|
|
@@ -421,7 +425,7 @@ function ToolUI({
|
|
|
421
425
|
name,
|
|
422
426
|
icon,
|
|
423
427
|
provider,
|
|
424
|
-
status =
|
|
428
|
+
status = "complete",
|
|
425
429
|
request,
|
|
426
430
|
result,
|
|
427
431
|
defaultExpanded = false,
|
|
@@ -432,40 +436,42 @@ function ToolUI({
|
|
|
432
436
|
onDeny,
|
|
433
437
|
}: ToolUIProps) {
|
|
434
438
|
// Use annotation title if available, otherwise fall back to name
|
|
435
|
-
const displayName = annotations?.title || name
|
|
439
|
+
const displayName = annotations?.title || name;
|
|
436
440
|
const isApprovalPending =
|
|
437
|
-
status ===
|
|
441
|
+
status === "approval" &&
|
|
442
|
+
onApproveOnce !== undefined &&
|
|
443
|
+
onDeny !== undefined;
|
|
438
444
|
// Auto-expand when approval is pending, collapse when approved
|
|
439
|
-
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
|
440
|
-
const hasContent = request !== undefined || result !== undefined
|
|
445
|
+
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
446
|
+
const hasContent = request !== undefined || result !== undefined;
|
|
441
447
|
|
|
442
448
|
// Track approval mode: 'one-time' or 'for-session'
|
|
443
|
-
const [approvalMode, setApprovalMode] = useState<ApprovalMode>(
|
|
444
|
-
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
|
|
445
|
-
const dropdownTriggerRef = React.useRef<HTMLButtonElement>(null)
|
|
449
|
+
const [approvalMode, setApprovalMode] = useState<ApprovalMode>("one-time");
|
|
450
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
451
|
+
const dropdownTriggerRef = React.useRef<HTMLButtonElement>(null);
|
|
446
452
|
|
|
447
453
|
// Collapse when transitioning from approval to non-approval (i.e., when approved/denied)
|
|
448
454
|
useEffect(() => {
|
|
449
455
|
if (!isApprovalPending && isExpanded && !defaultExpanded) {
|
|
450
|
-
setIsExpanded(false)
|
|
456
|
+
setIsExpanded(false);
|
|
451
457
|
}
|
|
452
|
-
}, [isApprovalPending])
|
|
458
|
+
}, [isApprovalPending]);
|
|
453
459
|
|
|
454
460
|
// Handle approve based on selected mode
|
|
455
461
|
const handleApprove = () => {
|
|
456
|
-
if (approvalMode ===
|
|
457
|
-
onApproveForSession()
|
|
462
|
+
if (approvalMode === "for-session" && onApproveForSession) {
|
|
463
|
+
onApproveForSession();
|
|
458
464
|
} else if (onApproveOnce) {
|
|
459
|
-
onApproveOnce()
|
|
465
|
+
onApproveOnce();
|
|
460
466
|
}
|
|
461
|
-
}
|
|
467
|
+
};
|
|
462
468
|
|
|
463
469
|
return (
|
|
464
470
|
<div
|
|
465
471
|
data-slot="tool-ui"
|
|
466
472
|
className={cn(
|
|
467
|
-
|
|
468
|
-
className
|
|
473
|
+
"@container overflow-hidden rounded-lg border border-border bg-card",
|
|
474
|
+
className,
|
|
469
475
|
)}
|
|
470
476
|
>
|
|
471
477
|
{/* Header with provider */}
|
|
@@ -473,7 +479,7 @@ function ToolUI({
|
|
|
473
479
|
<div
|
|
474
480
|
data-slot="tool-ui-provider"
|
|
475
481
|
className={cn(
|
|
476
|
-
|
|
482
|
+
"flex items-center gap-2 border-b border-border px-4 py-2.5",
|
|
477
483
|
)}
|
|
478
484
|
>
|
|
479
485
|
{icon ? (
|
|
@@ -481,7 +487,7 @@ function ToolUI({
|
|
|
481
487
|
{icon}
|
|
482
488
|
</span>
|
|
483
489
|
) : (
|
|
484
|
-
<span className="
|
|
490
|
+
<span className="flex size-5 items-center justify-center rounded bg-muted text-xs font-medium">
|
|
485
491
|
{provider.charAt(0).toUpperCase()}
|
|
486
492
|
</span>
|
|
487
493
|
)}
|
|
@@ -494,15 +500,15 @@ function ToolUI({
|
|
|
494
500
|
onClick={() => hasContent && setIsExpanded(!isExpanded)}
|
|
495
501
|
disabled={!hasContent}
|
|
496
502
|
className={cn(
|
|
497
|
-
|
|
498
|
-
hasContent &&
|
|
503
|
+
"flex w-full items-center gap-2 px-4 py-3 text-left",
|
|
504
|
+
hasContent && "cursor-pointer transition-colors hover:bg-accent/50",
|
|
499
505
|
)}
|
|
500
506
|
>
|
|
501
507
|
<StatusIndicator status={status} />
|
|
502
508
|
<span
|
|
503
509
|
className={cn(
|
|
504
|
-
|
|
505
|
-
!provider && isApprovalPending &&
|
|
510
|
+
"flex-1 text-sm",
|
|
511
|
+
!provider && isApprovalPending && "shimmer",
|
|
506
512
|
)}
|
|
507
513
|
>
|
|
508
514
|
{displayName}
|
|
@@ -510,8 +516,8 @@ function ToolUI({
|
|
|
510
516
|
{hasContent && (
|
|
511
517
|
<ChevronDownIcon
|
|
512
518
|
className={cn(
|
|
513
|
-
|
|
514
|
-
isExpanded &&
|
|
519
|
+
"size-4 text-muted-foreground transition-transform duration-200",
|
|
520
|
+
isExpanded && "rotate-180",
|
|
515
521
|
)}
|
|
516
522
|
/>
|
|
517
523
|
)}
|
|
@@ -545,14 +551,14 @@ function ToolUI({
|
|
|
545
551
|
{isApprovalPending && (
|
|
546
552
|
<div
|
|
547
553
|
data-slot="tool-ui-approval-actions"
|
|
548
|
-
className="
|
|
554
|
+
className="flex flex-col gap-2 border-t border-border px-4 py-3 @[320px]:flex-row @[320px]:items-center @[320px]:justify-end"
|
|
549
555
|
>
|
|
550
556
|
<div className="flex items-center gap-2 @[320px]:mr-auto">
|
|
551
|
-
<span className="text-muted-foreground
|
|
557
|
+
<span className="text-sm text-muted-foreground">
|
|
552
558
|
This tool requires approval
|
|
553
559
|
</span>
|
|
554
560
|
{annotations?.readOnlyHint && (
|
|
555
|
-
<span className="bg-muted
|
|
561
|
+
<span className="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
|
556
562
|
Read-only
|
|
557
563
|
</span>
|
|
558
564
|
)}
|
|
@@ -580,16 +586,16 @@ function ToolUI({
|
|
|
580
586
|
onClick={handleApprove}
|
|
581
587
|
className="flex cursor-pointer justify-between gap-1 rounded-r-none bg-emerald-600 hover:bg-emerald-700"
|
|
582
588
|
>
|
|
583
|
-
<CheckIcon className="
|
|
589
|
+
<CheckIcon className="mr-1 size-3 dark:text-foreground" />
|
|
584
590
|
|
|
585
|
-
<span className="
|
|
591
|
+
<span className="@[320px]:hidden dark:text-foreground">
|
|
586
592
|
Approve
|
|
587
593
|
</span>
|
|
588
594
|
{/* The min-width is needed to prevent the button from shifting when the text changes */}
|
|
589
|
-
<span className="
|
|
590
|
-
{approvalMode ===
|
|
591
|
-
?
|
|
592
|
-
:
|
|
595
|
+
<span className="hidden min-w-[110px] @[320px]:inline dark:text-foreground">
|
|
596
|
+
{approvalMode === "one-time"
|
|
597
|
+
? "Approve this time"
|
|
598
|
+
: "Approve always"}
|
|
593
599
|
</span>
|
|
594
600
|
</Button>
|
|
595
601
|
<Popover open={isDropdownOpen}>
|
|
@@ -602,9 +608,9 @@ function ToolUI({
|
|
|
602
608
|
onClick={() => setIsDropdownOpen((prev) => !prev)}
|
|
603
609
|
>
|
|
604
610
|
{isDropdownOpen ? (
|
|
605
|
-
<ChevronUpIcon className="dark:text-foreground
|
|
611
|
+
<ChevronUpIcon className="size-3 dark:text-foreground" />
|
|
606
612
|
) : (
|
|
607
|
-
<ChevronDownIcon className="dark:text-foreground
|
|
613
|
+
<ChevronDownIcon className="size-3 dark:text-foreground" />
|
|
608
614
|
)}
|
|
609
615
|
</Button>
|
|
610
616
|
</PopoverAnchor>
|
|
@@ -615,17 +621,17 @@ function ToolUI({
|
|
|
615
621
|
onInteractOutside={(e) => {
|
|
616
622
|
// Prevent Radix auto-dismiss to avoid race condition
|
|
617
623
|
// between DismissableLayer's pointerdown and button's click
|
|
618
|
-
e.preventDefault()
|
|
624
|
+
e.preventDefault();
|
|
619
625
|
// Use composedPath to detect trigger clicks across Shadow DOM
|
|
620
626
|
const originalEvent = (
|
|
621
627
|
e.detail as { originalEvent?: PointerEvent }
|
|
622
|
-
)?.originalEvent
|
|
623
|
-
const path = originalEvent?.composedPath?.() ?? []
|
|
628
|
+
)?.originalEvent;
|
|
629
|
+
const path = originalEvent?.composedPath?.() ?? [];
|
|
624
630
|
if (
|
|
625
631
|
!path.includes(dropdownTriggerRef.current as EventTarget)
|
|
626
632
|
) {
|
|
627
633
|
// Clicked outside both popover and trigger - close it
|
|
628
|
-
setIsDropdownOpen(false)
|
|
634
|
+
setIsDropdownOpen(false);
|
|
629
635
|
}
|
|
630
636
|
// If clicked on trigger, do nothing - onClick will toggle
|
|
631
637
|
}}
|
|
@@ -633,20 +639,20 @@ function ToolUI({
|
|
|
633
639
|
>
|
|
634
640
|
<button
|
|
635
641
|
onClick={() => {
|
|
636
|
-
setApprovalMode(
|
|
637
|
-
setIsDropdownOpen(false)
|
|
642
|
+
setApprovalMode("one-time");
|
|
643
|
+
setIsDropdownOpen(false);
|
|
638
644
|
}}
|
|
639
|
-
className="
|
|
645
|
+
className="relative flex w-full items-start gap-2 rounded-sm px-2 py-2 text-left hover:bg-accent"
|
|
640
646
|
>
|
|
641
647
|
<CheckIcon
|
|
642
648
|
className={cn(
|
|
643
|
-
|
|
644
|
-
approvalMode !==
|
|
649
|
+
"relative top-1 mt-0.5 size-3 shrink-0",
|
|
650
|
+
approvalMode !== "one-time" && "invisible",
|
|
645
651
|
)}
|
|
646
652
|
/>
|
|
647
653
|
<div className="flex flex-col gap-0.5">
|
|
648
654
|
<span className="text-sm">Approve only once</span>
|
|
649
|
-
<span className="text-muted-foreground
|
|
655
|
+
<span className="text-xs text-muted-foreground">
|
|
650
656
|
You'll be asked again next time
|
|
651
657
|
</span>
|
|
652
658
|
</div>
|
|
@@ -654,20 +660,20 @@ function ToolUI({
|
|
|
654
660
|
{onApproveForSession && (
|
|
655
661
|
<button
|
|
656
662
|
onClick={() => {
|
|
657
|
-
setApprovalMode(
|
|
658
|
-
setIsDropdownOpen(false)
|
|
663
|
+
setApprovalMode("for-session");
|
|
664
|
+
setIsDropdownOpen(false);
|
|
659
665
|
}}
|
|
660
|
-
className="
|
|
666
|
+
className="relative flex w-full items-start gap-2 rounded-sm px-2 py-2 text-left hover:bg-accent"
|
|
661
667
|
>
|
|
662
668
|
<CheckIcon
|
|
663
669
|
className={cn(
|
|
664
|
-
|
|
665
|
-
approvalMode !==
|
|
670
|
+
"relative top-1 mt-0.5 size-3 shrink-0",
|
|
671
|
+
approvalMode !== "for-session" && "invisible",
|
|
666
672
|
)}
|
|
667
673
|
/>
|
|
668
674
|
<div className="flex flex-col gap-0.5">
|
|
669
675
|
<span className="text-sm">Approve always</span>
|
|
670
|
-
<span className="text-muted-foreground
|
|
676
|
+
<span className="text-xs text-muted-foreground">
|
|
671
677
|
Trust this tool for the session
|
|
672
678
|
</span>
|
|
673
679
|
</div>
|
|
@@ -680,7 +686,7 @@ function ToolUI({
|
|
|
680
686
|
</div>
|
|
681
687
|
)}
|
|
682
688
|
</div>
|
|
683
|
-
)
|
|
689
|
+
);
|
|
684
690
|
}
|
|
685
691
|
|
|
686
692
|
/* -----------------------------------------------------------------------------
|
|
@@ -689,59 +695,59 @@ function ToolUI({
|
|
|
689
695
|
|
|
690
696
|
interface ToolUIGroupProps {
|
|
691
697
|
/** Title for the group header */
|
|
692
|
-
title: string
|
|
698
|
+
title: string;
|
|
693
699
|
/** Optional icon */
|
|
694
|
-
icon?: React.ReactNode
|
|
700
|
+
icon?: React.ReactNode;
|
|
695
701
|
/** Overall status of the group */
|
|
696
|
-
status?:
|
|
702
|
+
status?: "running" | "complete";
|
|
697
703
|
/** Whether the group starts expanded */
|
|
698
|
-
defaultExpanded?: boolean
|
|
704
|
+
defaultExpanded?: boolean;
|
|
699
705
|
/** Child tool UI components */
|
|
700
|
-
children: React.ReactNode
|
|
706
|
+
children: React.ReactNode;
|
|
701
707
|
/** Additional class names */
|
|
702
|
-
className?: string
|
|
708
|
+
className?: string;
|
|
703
709
|
}
|
|
704
710
|
|
|
705
711
|
function ToolUIGroup({
|
|
706
712
|
title,
|
|
707
713
|
icon,
|
|
708
|
-
status =
|
|
714
|
+
status = "complete",
|
|
709
715
|
defaultExpanded = false,
|
|
710
716
|
children,
|
|
711
717
|
className,
|
|
712
718
|
}: ToolUIGroupProps) {
|
|
713
|
-
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
|
719
|
+
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
714
720
|
|
|
715
721
|
return (
|
|
716
722
|
<div
|
|
717
723
|
data-slot="tool-ui-group"
|
|
718
724
|
className={cn(
|
|
719
|
-
|
|
720
|
-
className
|
|
725
|
+
"overflow-hidden rounded-lg border border-border bg-card",
|
|
726
|
+
className,
|
|
721
727
|
)}
|
|
722
728
|
>
|
|
723
729
|
{/* Group header */}
|
|
724
730
|
<button
|
|
725
731
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
726
|
-
className="
|
|
732
|
+
className="flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-accent/50"
|
|
727
733
|
>
|
|
728
734
|
{icon || (
|
|
729
735
|
<StatusIndicator
|
|
730
|
-
status={status ===
|
|
736
|
+
status={status === "running" ? "running" : "complete"}
|
|
731
737
|
/>
|
|
732
738
|
)}
|
|
733
739
|
<span
|
|
734
740
|
className={cn(
|
|
735
|
-
|
|
736
|
-
status ===
|
|
741
|
+
"flex-1 text-sm font-medium",
|
|
742
|
+
status === "running" && "shimmer",
|
|
737
743
|
)}
|
|
738
744
|
>
|
|
739
745
|
{title}
|
|
740
746
|
</span>
|
|
741
747
|
<ChevronDownIcon
|
|
742
748
|
className={cn(
|
|
743
|
-
|
|
744
|
-
isExpanded &&
|
|
749
|
+
"size-4 text-muted-foreground transition-transform duration-200",
|
|
750
|
+
isExpanded && "rotate-180",
|
|
745
751
|
)}
|
|
746
752
|
/>
|
|
747
753
|
</button>
|
|
@@ -750,13 +756,13 @@ function ToolUIGroup({
|
|
|
750
756
|
{isExpanded && (
|
|
751
757
|
<div
|
|
752
758
|
data-slot="tool-ui-group-content"
|
|
753
|
-
className="border-
|
|
759
|
+
className="border-t border-border"
|
|
754
760
|
>
|
|
755
761
|
{children}
|
|
756
762
|
</div>
|
|
757
763
|
)}
|
|
758
764
|
</div>
|
|
759
|
-
)
|
|
765
|
+
);
|
|
760
766
|
}
|
|
761
767
|
|
|
762
768
|
/* -----------------------------------------------------------------------------
|
|
@@ -770,11 +776,11 @@ export {
|
|
|
770
776
|
SyntaxHighlightedCode,
|
|
771
777
|
StatusIndicator,
|
|
772
778
|
CopyButton,
|
|
773
|
-
}
|
|
779
|
+
};
|
|
774
780
|
export type {
|
|
775
781
|
ToolUIProps,
|
|
776
782
|
ToolUISectionProps,
|
|
777
783
|
ToolUIGroupProps,
|
|
778
784
|
ToolStatus,
|
|
779
785
|
ContentItem,
|
|
780
|
-
}
|
|
786
|
+
};
|