@gram-ai/elements 1.33.2 → 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 +18 -1
- package/dist/components/ui/time-range-picker.test.d.ts +1 -0
- 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 +19 -16
- package/dist/hooks/useDensity.d.ts +1 -1
- package/dist/hooks/useElements.d.ts +2 -1
- package/dist/hooks/useGramThreadListAdapter.d.ts +26 -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-reVrRxP1.js → index-BhIowiZF.js} +9744 -9493
- package/dist/index-BhIowiZF.js.map +1 -0
- package/dist/{index-DAWGW1Nj.cjs → index-D0jIGQr7.cjs} +43 -43
- package/dist/index-D0jIGQr7.cjs.map +1 -0
- package/dist/{index-CGoLfO5p.js → index-Dz13dSDa.js} +108 -51
- 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/index.d.ts +4 -1
- package/dist/lib/errorTracking.d.ts +1 -1
- package/dist/lib/messageConverter.d.ts +58 -8
- package/dist/lib/models.d.ts +1 -1
- package/dist/lib/tools.d.ts +11 -10
- package/dist/lib/utils.d.ts +2 -0
- 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-noho3NG9.js → profiler-CtGKTWWP.js} +2 -2
- package/dist/{profiler-noho3NG9.js.map → profiler-CtGKTWWP.js.map} +1 -1
- package/dist/{profiler-B3tfiOx4.cjs → profiler-l7_HjTyw.cjs} +2 -2
- package/dist/{profiler-B3tfiOx4.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-7Oy6wM18.cjs → startRecording-DEw2Aeq4.cjs} +2 -2
- package/dist/{startRecording-7Oy6wM18.cjs.map → startRecording-DEw2Aeq4.cjs.map} +1 -1
- package/dist/{startRecording-mkmig-2n.js → startRecording-iYEL0-vr.js} +2 -2
- package/dist/{startRecording-mkmig-2n.js.map → startRecording-iYEL0-vr.js.map} +1 -1
- package/dist/types/index.d.ts +93 -4
- package/package.json +8 -9
- 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 +256 -14
- 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.test.ts +57 -0
- package/src/components/ui/time-range-picker.tsx +40 -9
- 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 +109 -37
- 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 +118 -9
- 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/index.ts +16 -0
- package/src/lib/cassette.ts +21 -27
- package/src/lib/contextCompaction.test.ts +2 -2
- package/src/lib/contextCompaction.ts +20 -8
- package/src/lib/errorTracking.ts +1 -4
- package/src/lib/messageConverter.test.ts +11 -13
- package/src/lib/messageConverter.ts +105 -58
- package/src/lib/models.ts +19 -7
- 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 +22 -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 +112 -4
- package/dist/index-BCV7Zf9E.cjs +0 -194
- package/dist/index-BCV7Zf9E.cjs.map +0 -1
- package/dist/index-CGoLfO5p.js.map +0 -1
- package/dist/index-DAWGW1Nj.cjs.map +0 -1
- package/dist/index-reVrRxP1.js.map +0 -1
|
@@ -36,171 +36,214 @@ const meta: Meta<typeof TimeRangePicker> = {
|
|
|
36
36
|
export default meta;
|
|
37
37
|
type Story = StoryObj<typeof TimeRangePicker>;
|
|
38
38
|
|
|
39
|
+
function DefaultStory() {
|
|
40
|
+
const [preset, setPreset] = useState<DateRangePreset | null>("7d");
|
|
41
|
+
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<TimeRangePicker
|
|
45
|
+
preset={customRange ? null : preset}
|
|
46
|
+
customRange={customRange}
|
|
47
|
+
onPresetChange={(p) => {
|
|
48
|
+
setPreset(p);
|
|
49
|
+
setCustomRange(null);
|
|
50
|
+
}}
|
|
51
|
+
onCustomRangeChange={(from, to, label) => {
|
|
52
|
+
setCustomRange({ from, to });
|
|
53
|
+
setPreset(null);
|
|
54
|
+
console.log("Custom range:", { from, to, label });
|
|
55
|
+
}}
|
|
56
|
+
onClearCustomRange={() => {
|
|
57
|
+
setCustomRange(null);
|
|
58
|
+
setPreset("7d");
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
/**
|
|
40
65
|
* Default time range picker with preset badges and calendar.
|
|
41
66
|
* Supports natural language input with AI parsing.
|
|
42
67
|
*/
|
|
43
68
|
export const Default: Story = {
|
|
44
|
-
render: () =>
|
|
45
|
-
const [preset, setPreset] = useState<DateRangePreset | null>("7d");
|
|
46
|
-
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<TimeRangePicker
|
|
50
|
-
preset={customRange ? null : preset}
|
|
51
|
-
customRange={customRange}
|
|
52
|
-
onPresetChange={(p) => {
|
|
53
|
-
setPreset(p);
|
|
54
|
-
setCustomRange(null);
|
|
55
|
-
}}
|
|
56
|
-
onCustomRangeChange={(from, to, label) => {
|
|
57
|
-
setCustomRange({ from, to });
|
|
58
|
-
setPreset(null);
|
|
59
|
-
console.log("Custom range:", { from, to, label });
|
|
60
|
-
}}
|
|
61
|
-
onClearCustomRange={() => {
|
|
62
|
-
setCustomRange(null);
|
|
63
|
-
setPreset("7d");
|
|
64
|
-
}}
|
|
65
|
-
/>
|
|
66
|
-
);
|
|
67
|
-
},
|
|
69
|
+
render: () => <DefaultStory />,
|
|
68
70
|
};
|
|
69
71
|
|
|
72
|
+
function WithTimezoneStory() {
|
|
73
|
+
const [preset, setPreset] = useState<DateRangePreset | null>("30d");
|
|
74
|
+
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<TimeRangePicker
|
|
78
|
+
preset={customRange ? null : preset}
|
|
79
|
+
customRange={customRange}
|
|
80
|
+
onPresetChange={(p) => {
|
|
81
|
+
setPreset(p);
|
|
82
|
+
setCustomRange(null);
|
|
83
|
+
}}
|
|
84
|
+
onCustomRangeChange={(from, to) => {
|
|
85
|
+
setCustomRange({ from, to });
|
|
86
|
+
setPreset(null);
|
|
87
|
+
}}
|
|
88
|
+
onClearCustomRange={() => {
|
|
89
|
+
setCustomRange(null);
|
|
90
|
+
setPreset("30d");
|
|
91
|
+
}}
|
|
92
|
+
timezone="UTC-08:00"
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
70
97
|
/**
|
|
71
98
|
* Time range picker with timezone indicator.
|
|
72
99
|
*/
|
|
73
100
|
export const WithTimezone: Story = {
|
|
74
|
-
render: () =>
|
|
75
|
-
|
|
76
|
-
|
|
101
|
+
render: () => <WithTimezoneStory />,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
function WithLiveModeStory() {
|
|
105
|
+
const [preset, setPreset] = useState<DateRangePreset | null>("15m");
|
|
106
|
+
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
107
|
+
const [isLive, setIsLive] = useState(true);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<TimeRangePicker
|
|
111
|
+
preset={customRange ? null : preset}
|
|
112
|
+
customRange={customRange}
|
|
113
|
+
onPresetChange={(p) => {
|
|
114
|
+
setPreset(p);
|
|
115
|
+
setCustomRange(null);
|
|
116
|
+
}}
|
|
117
|
+
onCustomRangeChange={(from, to) => {
|
|
118
|
+
setCustomRange({ from, to });
|
|
119
|
+
setPreset(null);
|
|
120
|
+
}}
|
|
121
|
+
onClearCustomRange={() => {
|
|
122
|
+
setCustomRange(null);
|
|
123
|
+
setPreset("15m");
|
|
124
|
+
}}
|
|
125
|
+
showLive
|
|
126
|
+
isLive={isLive}
|
|
127
|
+
onLiveChange={setIsLive}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
77
131
|
|
|
78
|
-
|
|
132
|
+
/**
|
|
133
|
+
* With LIVE mode toggle enabled.
|
|
134
|
+
*/
|
|
135
|
+
export const WithLiveMode: Story = {
|
|
136
|
+
render: () => <WithLiveModeStory />,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Disabled state.
|
|
141
|
+
*/
|
|
142
|
+
export const Disabled: Story = {
|
|
143
|
+
args: {
|
|
144
|
+
preset: "7d",
|
|
145
|
+
disabled: true,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
function DatadogStyleStory() {
|
|
150
|
+
const [preset, setPreset] = useState<DateRangePreset | null>("7d");
|
|
151
|
+
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
152
|
+
const [customLabel, setCustomLabel] = useState<string | null>(null);
|
|
153
|
+
const [isLive, setIsLive] = useState(false);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="space-y-4">
|
|
79
157
|
<TimeRangePicker
|
|
80
158
|
preset={customRange ? null : preset}
|
|
81
159
|
customRange={customRange}
|
|
160
|
+
customRangeLabel={customLabel}
|
|
82
161
|
onPresetChange={(p) => {
|
|
83
162
|
setPreset(p);
|
|
84
163
|
setCustomRange(null);
|
|
164
|
+
setCustomLabel(null);
|
|
85
165
|
}}
|
|
86
|
-
onCustomRangeChange={(from, to) => {
|
|
166
|
+
onCustomRangeChange={(from, to, label) => {
|
|
87
167
|
setCustomRange({ from, to });
|
|
88
168
|
setPreset(null);
|
|
169
|
+
setCustomLabel(label || null);
|
|
89
170
|
}}
|
|
90
171
|
onClearCustomRange={() => {
|
|
91
172
|
setCustomRange(null);
|
|
92
|
-
setPreset("
|
|
173
|
+
setPreset("7d");
|
|
174
|
+
setCustomLabel(null);
|
|
93
175
|
}}
|
|
176
|
+
showLive
|
|
177
|
+
isLive={isLive}
|
|
178
|
+
onLiveChange={setIsLive}
|
|
94
179
|
timezone="UTC-08:00"
|
|
95
180
|
/>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
181
|
+
<div className="rounded-md bg-muted p-3 text-xs text-muted-foreground">
|
|
182
|
+
<strong>Current state:</strong>
|
|
183
|
+
<pre className="mt-1 overflow-auto">
|
|
184
|
+
{JSON.stringify(
|
|
185
|
+
{
|
|
186
|
+
preset,
|
|
187
|
+
customRange: customRange
|
|
188
|
+
? {
|
|
189
|
+
from: customRange.from.toISOString(),
|
|
190
|
+
to: customRange.to.toISOString(),
|
|
191
|
+
}
|
|
192
|
+
: null,
|
|
193
|
+
customLabel,
|
|
194
|
+
isLive,
|
|
195
|
+
},
|
|
196
|
+
null,
|
|
197
|
+
2,
|
|
198
|
+
)}
|
|
199
|
+
</pre>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
99
204
|
|
|
100
205
|
/**
|
|
101
|
-
*
|
|
206
|
+
* Full Datadog-style configuration with all features.
|
|
207
|
+
* Type natural language like "3 days ago", "last Wednesday", "past 2 weeks".
|
|
102
208
|
*/
|
|
103
|
-
export const
|
|
104
|
-
render: () =>
|
|
105
|
-
|
|
106
|
-
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
107
|
-
const [isLive, setIsLive] = useState(true);
|
|
209
|
+
export const DatadogStyle: Story = {
|
|
210
|
+
render: () => <DatadogStyleStory />,
|
|
211
|
+
};
|
|
108
212
|
|
|
109
|
-
|
|
213
|
+
function NaturalLanguageParsingStory() {
|
|
214
|
+
const [preset, setPreset] = useState<DateRangePreset | null>("30d");
|
|
215
|
+
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
216
|
+
const [customLabel, setCustomLabel] = useState<string | null>(null);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<div className="space-y-4">
|
|
220
|
+
<p className="text-sm text-muted-foreground">
|
|
221
|
+
Try typing: "yesterday", "3 days ago", "last Wednesday", "January"
|
|
222
|
+
</p>
|
|
110
223
|
<TimeRangePicker
|
|
111
224
|
preset={customRange ? null : preset}
|
|
112
225
|
customRange={customRange}
|
|
226
|
+
customRangeLabel={customLabel}
|
|
113
227
|
onPresetChange={(p) => {
|
|
114
228
|
setPreset(p);
|
|
115
229
|
setCustomRange(null);
|
|
230
|
+
setCustomLabel(null);
|
|
116
231
|
}}
|
|
117
|
-
onCustomRangeChange={(from, to) => {
|
|
232
|
+
onCustomRangeChange={(from, to, label) => {
|
|
118
233
|
setCustomRange({ from, to });
|
|
119
234
|
setPreset(null);
|
|
235
|
+
setCustomLabel(label || null);
|
|
236
|
+
console.log("AI parsed:", { from, to, label });
|
|
120
237
|
}}
|
|
121
238
|
onClearCustomRange={() => {
|
|
122
239
|
setCustomRange(null);
|
|
123
|
-
setPreset("
|
|
240
|
+
setPreset("30d");
|
|
241
|
+
setCustomLabel(null);
|
|
124
242
|
}}
|
|
125
|
-
showLive
|
|
126
|
-
isLive={isLive}
|
|
127
|
-
onLiveChange={setIsLive}
|
|
128
243
|
/>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Disabled state.
|
|
135
|
-
*/
|
|
136
|
-
export const Disabled: Story = {
|
|
137
|
-
args: {
|
|
138
|
-
preset: "7d",
|
|
139
|
-
disabled: true,
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Full Datadog-style configuration with all features.
|
|
145
|
-
* Type natural language like "3 days ago", "last Wednesday", "past 2 weeks".
|
|
146
|
-
*/
|
|
147
|
-
export const DatadogStyle: Story = {
|
|
148
|
-
render: () => {
|
|
149
|
-
const [preset, setPreset] = useState<DateRangePreset | null>("7d");
|
|
150
|
-
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
151
|
-
const [customLabel, setCustomLabel] = useState<string | null>(null);
|
|
152
|
-
const [isLive, setIsLive] = useState(false);
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<div className="space-y-4">
|
|
156
|
-
<TimeRangePicker
|
|
157
|
-
preset={customRange ? null : preset}
|
|
158
|
-
customRange={customRange}
|
|
159
|
-
customRangeLabel={customLabel}
|
|
160
|
-
onPresetChange={(p) => {
|
|
161
|
-
setPreset(p);
|
|
162
|
-
setCustomRange(null);
|
|
163
|
-
setCustomLabel(null);
|
|
164
|
-
}}
|
|
165
|
-
onCustomRangeChange={(from, to, label) => {
|
|
166
|
-
setCustomRange({ from, to });
|
|
167
|
-
setPreset(null);
|
|
168
|
-
setCustomLabel(label || null);
|
|
169
|
-
}}
|
|
170
|
-
onClearCustomRange={() => {
|
|
171
|
-
setCustomRange(null);
|
|
172
|
-
setPreset("7d");
|
|
173
|
-
setCustomLabel(null);
|
|
174
|
-
}}
|
|
175
|
-
showLive
|
|
176
|
-
isLive={isLive}
|
|
177
|
-
onLiveChange={setIsLive}
|
|
178
|
-
timezone="UTC-08:00"
|
|
179
|
-
/>
|
|
180
|
-
<div className="rounded-md bg-muted p-3 text-xs text-muted-foreground">
|
|
181
|
-
<strong>Current state:</strong>
|
|
182
|
-
<pre className="mt-1 overflow-auto">
|
|
183
|
-
{JSON.stringify(
|
|
184
|
-
{
|
|
185
|
-
preset,
|
|
186
|
-
customRange: customRange
|
|
187
|
-
? {
|
|
188
|
-
from: customRange.from.toISOString(),
|
|
189
|
-
to: customRange.to.toISOString(),
|
|
190
|
-
}
|
|
191
|
-
: null,
|
|
192
|
-
customLabel,
|
|
193
|
-
isLive,
|
|
194
|
-
},
|
|
195
|
-
null,
|
|
196
|
-
2,
|
|
197
|
-
)}
|
|
198
|
-
</pre>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
},
|
|
203
|
-
};
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
204
247
|
|
|
205
248
|
/**
|
|
206
249
|
* Natural language parsing demo.
|
|
@@ -212,38 +255,5 @@ export const DatadogStyle: Story = {
|
|
|
212
255
|
* - "January 2024"
|
|
213
256
|
*/
|
|
214
257
|
export const NaturalLanguageParsing: Story = {
|
|
215
|
-
render: () =>
|
|
216
|
-
const [preset, setPreset] = useState<DateRangePreset | null>("30d");
|
|
217
|
-
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
|
218
|
-
const [customLabel, setCustomLabel] = useState<string | null>(null);
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
<div className="space-y-4">
|
|
222
|
-
<p className="text-sm text-muted-foreground">
|
|
223
|
-
Try typing: "yesterday", "3 days ago", "last Wednesday", "January"
|
|
224
|
-
</p>
|
|
225
|
-
<TimeRangePicker
|
|
226
|
-
preset={customRange ? null : preset}
|
|
227
|
-
customRange={customRange}
|
|
228
|
-
customRangeLabel={customLabel}
|
|
229
|
-
onPresetChange={(p) => {
|
|
230
|
-
setPreset(p);
|
|
231
|
-
setCustomRange(null);
|
|
232
|
-
setCustomLabel(null);
|
|
233
|
-
}}
|
|
234
|
-
onCustomRangeChange={(from, to, label) => {
|
|
235
|
-
setCustomRange({ from, to });
|
|
236
|
-
setPreset(null);
|
|
237
|
-
setCustomLabel(label || null);
|
|
238
|
-
console.log("AI parsed:", { from, to, label });
|
|
239
|
-
}}
|
|
240
|
-
onClearCustomRange={() => {
|
|
241
|
-
setCustomRange(null);
|
|
242
|
-
setPreset("30d");
|
|
243
|
-
setCustomLabel(null);
|
|
244
|
-
}}
|
|
245
|
-
/>
|
|
246
|
-
</div>
|
|
247
|
-
);
|
|
248
|
-
},
|
|
258
|
+
render: () => <NaturalLanguageParsingStory />,
|
|
249
259
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Capture the options passed to createOpenRouter so we can assert on the
|
|
4
|
+
// auth headers / baseURL the date picker sends to /chat/completions.
|
|
5
|
+
const createOpenRouterMock = vi.fn();
|
|
6
|
+
|
|
7
|
+
vi.mock("@openrouter/ai-sdk-provider", () => ({
|
|
8
|
+
createOpenRouter: (opts: { headers?: Record<string, string> }) => {
|
|
9
|
+
createOpenRouterMock(opts);
|
|
10
|
+
return { chat: () => ({}) };
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("ai", () => ({
|
|
15
|
+
generateObject: vi.fn(async () => ({
|
|
16
|
+
object: {
|
|
17
|
+
from: "2026-01-01T00:00:00",
|
|
18
|
+
to: "2026-01-01T23:59:59",
|
|
19
|
+
label: "Jan 1",
|
|
20
|
+
},
|
|
21
|
+
})),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// Avoid pulling Datadog RUM (and its window access) into the Node test env.
|
|
25
|
+
vi.mock("@/lib/errorTracking", () => ({ trackError: vi.fn() }));
|
|
26
|
+
|
|
27
|
+
import { parseWithAI } from "./time-range-picker";
|
|
28
|
+
|
|
29
|
+
describe("parseWithAI request auth", () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
createOpenRouterMock.mockClear();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Root-cause regression test: the /chat/completions proxy authenticates from
|
|
35
|
+
// request headers, so the session credential MUST be forwarded. Before the
|
|
36
|
+
// fix, parseWithAI sent only Gram-Project and relied on a cookie, so this
|
|
37
|
+
// header was absent and the request 401'd (silently).
|
|
38
|
+
it("forwards the session auth header and baseURL to the OpenRouter client", async () => {
|
|
39
|
+
await parseWithAI("yesterday", "https://app.getgram.ai", "proj-slug", {
|
|
40
|
+
"Gram-Session": "test-token",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(createOpenRouterMock).toHaveBeenCalledTimes(1);
|
|
44
|
+
const opts = createOpenRouterMock.mock.calls[0]![0];
|
|
45
|
+
expect(opts.baseURL).toBe("https://app.getgram.ai");
|
|
46
|
+
expect(opts.headers["Gram-Session"]).toBe("test-token");
|
|
47
|
+
expect(opts.headers["Gram-Project"]).toBe("proj-slug");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("still sets Gram-Project when no auth headers are provided", async () => {
|
|
51
|
+
await parseWithAI("yesterday", "https://app.getgram.ai", "proj-slug");
|
|
52
|
+
|
|
53
|
+
const opts = createOpenRouterMock.mock.calls[0]![0];
|
|
54
|
+
expect(opts.headers["Gram-Project"]).toBe("proj-slug");
|
|
55
|
+
expect(opts.headers["Gram-Session"]).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -6,6 +6,7 @@ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
8
|
import { cn } from "@/lib/utils";
|
|
9
|
+
import { trackError } from "@/lib/errorTracking";
|
|
9
10
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
10
11
|
import { Calendar } from "./calendar";
|
|
11
12
|
|
|
@@ -154,7 +155,7 @@ const BADGE_WIDTH = "min-w-10";
|
|
|
154
155
|
|
|
155
156
|
export function getPresetRange(preset: DateRangePreset): TimeRange {
|
|
156
157
|
const p = PRESETS.find((p) => p.value === preset);
|
|
157
|
-
return p ? p.getRange() : PRESETS[5]
|
|
158
|
+
return p ? p.getRange() : PRESETS[5]!.getRange(); // Default to 3d
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
function getPresetByValue(value: DateRangePreset): TimeRangePreset | undefined {
|
|
@@ -187,11 +188,15 @@ function parseAsLocalDate(isoString: string): Date {
|
|
|
187
188
|
// Try to extract just the date part and create a local date
|
|
188
189
|
const dateMatch = isoString.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
189
190
|
if (dateMatch) {
|
|
190
|
-
const
|
|
191
|
+
const year = dateMatch[1]!;
|
|
192
|
+
const month = dateMatch[2]!;
|
|
193
|
+
const day = dateMatch[3]!;
|
|
191
194
|
// Check if there's a time component
|
|
192
195
|
const timeMatch = isoString.match(/T(\d{2}):(\d{2}):?(\d{2})?/);
|
|
193
196
|
if (timeMatch) {
|
|
194
|
-
const
|
|
197
|
+
const hours = timeMatch[1]!;
|
|
198
|
+
const minutes = timeMatch[2]!;
|
|
199
|
+
const seconds = timeMatch[3] ?? "0";
|
|
195
200
|
return new Date(
|
|
196
201
|
parseInt(year),
|
|
197
202
|
parseInt(month) - 1,
|
|
@@ -208,16 +213,23 @@ function parseAsLocalDate(isoString: string): Date {
|
|
|
208
213
|
return new Date(isoString);
|
|
209
214
|
}
|
|
210
215
|
|
|
211
|
-
|
|
216
|
+
// Exported for unit testing of header/credential construction.
|
|
217
|
+
export async function parseWithAI(
|
|
212
218
|
input: string,
|
|
213
219
|
apiUrl: string,
|
|
214
220
|
projectSlug?: string,
|
|
221
|
+
authHeaders?: Record<string, string>,
|
|
215
222
|
): Promise<ParseResult> {
|
|
216
223
|
try {
|
|
217
224
|
const now = new Date();
|
|
218
225
|
|
|
219
|
-
// Create OpenRouter provider without X-Gram-Source header (so usage is billed)
|
|
220
|
-
|
|
226
|
+
// Create OpenRouter provider without X-Gram-Source header (so usage is billed).
|
|
227
|
+
// authHeaders carries the caller's session credential (e.g. Gram-Session /
|
|
228
|
+
// Gram-Chat-Session). The /chat/completions proxy authenticates from these
|
|
229
|
+
// request headers — relying on cookies alone fails (the Vercel AI SDK does
|
|
230
|
+
// not forward them reliably), so a missing credential here 401s and the
|
|
231
|
+
// catch below would silently swallow it.
|
|
232
|
+
const headers: Record<string, string> = { ...authHeaders };
|
|
221
233
|
if (projectSlug) {
|
|
222
234
|
headers["Gram-Project"] = projectSlug;
|
|
223
235
|
}
|
|
@@ -290,7 +302,15 @@ User input: ${input}`,
|
|
|
290
302
|
|
|
291
303
|
// Use the semantic label from AI (e.g., "Mon", "Jan", "2024", "1/5-1/10")
|
|
292
304
|
return { type: "custom", range: { from, to }, label: parsed.label };
|
|
293
|
-
} catch {
|
|
305
|
+
} catch (error) {
|
|
306
|
+
// Returning null keeps the UI graceful on an unparseable string, but the
|
|
307
|
+
// failure must not be invisible — a 401/gateway error here looks identical
|
|
308
|
+
// to "couldn't parse" to the user. Surface it so it's diagnosable.
|
|
309
|
+
trackError(error, {
|
|
310
|
+
source: "custom",
|
|
311
|
+
location: "TimeRangePicker.parseWithAI",
|
|
312
|
+
hasAuthHeaders: Boolean(authHeaders),
|
|
313
|
+
});
|
|
294
314
|
return null;
|
|
295
315
|
}
|
|
296
316
|
}
|
|
@@ -328,6 +348,13 @@ export interface TimeRangePickerProps {
|
|
|
328
348
|
apiUrl?: string;
|
|
329
349
|
/** Project slug for API authentication */
|
|
330
350
|
projectSlug?: string;
|
|
351
|
+
/**
|
|
352
|
+
* Auth headers to send with the AI parsing request to /chat/completions
|
|
353
|
+
* (e.g. `{ "Gram-Session": token }`). The `/chat/completions` proxy
|
|
354
|
+
* authenticates from request headers, not cookies, so without these the
|
|
355
|
+
* request is rejected with 401 and natural-language parsing silently fails.
|
|
356
|
+
*/
|
|
357
|
+
authHeaders?: Record<string, string>;
|
|
331
358
|
/** Additional class name for the trigger */
|
|
332
359
|
className?: string;
|
|
333
360
|
}
|
|
@@ -347,8 +374,9 @@ function TimeRangePicker({
|
|
|
347
374
|
timezone,
|
|
348
375
|
apiUrl,
|
|
349
376
|
projectSlug,
|
|
377
|
+
authHeaders,
|
|
350
378
|
className,
|
|
351
|
-
}: TimeRangePickerProps) {
|
|
379
|
+
}: TimeRangePickerProps): React.JSX.Element {
|
|
352
380
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
353
381
|
const [showCalendar, setShowCalendar] = React.useState(false);
|
|
354
382
|
const [inputValue, setInputValue] = React.useState("");
|
|
@@ -441,6 +469,7 @@ function TimeRangePicker({
|
|
|
441
469
|
inputValue,
|
|
442
470
|
effectiveApiUrl,
|
|
443
471
|
projectSlug,
|
|
472
|
+
authHeaders,
|
|
444
473
|
);
|
|
445
474
|
applyParseResult(aiParsed);
|
|
446
475
|
} finally {
|
|
@@ -560,7 +589,9 @@ function TimeRangePicker({
|
|
|
560
589
|
onClick={handleInputClick}
|
|
561
590
|
onFocus={handleInputFocus}
|
|
562
591
|
onBlur={handleInputBlur}
|
|
563
|
-
onKeyDown={
|
|
592
|
+
onKeyDown={(e) => {
|
|
593
|
+
void handleInputKeyDown(e);
|
|
594
|
+
}}
|
|
564
595
|
placeholder="e.g., 3 days ago, last week..."
|
|
565
596
|
disabled={disabled}
|
|
566
597
|
className={cn(
|
|
@@ -159,7 +159,11 @@ function isStructuredContent(
|
|
|
159
159
|
* Helper Components
|
|
160
160
|
* -------------------------------------------------------------------------- */
|
|
161
161
|
|
|
162
|
-
function StatusIndicator({
|
|
162
|
+
function StatusIndicator({
|
|
163
|
+
status,
|
|
164
|
+
}: {
|
|
165
|
+
status: ToolStatus;
|
|
166
|
+
}): React.JSX.Element {
|
|
163
167
|
return (
|
|
164
168
|
<div className={cn(statusVariants({ status }))}>
|
|
165
169
|
{status === "pending" && null}
|
|
@@ -173,7 +177,7 @@ function StatusIndicator({ status }: { status: ToolStatus }) {
|
|
|
173
177
|
);
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
function CopyButton({ content }: { content: string }) {
|
|
180
|
+
function CopyButton({ content }: { content: string }): React.JSX.Element {
|
|
177
181
|
const [copied, setCopied] = useState(false);
|
|
178
182
|
|
|
179
183
|
const handleCopy = async (e: React.MouseEvent) => {
|
|
@@ -185,7 +189,9 @@ function CopyButton({ content }: { content: string }) {
|
|
|
185
189
|
|
|
186
190
|
return (
|
|
187
191
|
<button
|
|
188
|
-
onClick={
|
|
192
|
+
onClick={(e) => {
|
|
193
|
+
void handleCopy(e);
|
|
194
|
+
}}
|
|
189
195
|
className="rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
190
196
|
aria-label="Copy to clipboard"
|
|
191
197
|
>
|
|
@@ -226,7 +232,7 @@ function SyntaxHighlightedCode({
|
|
|
226
232
|
text: string;
|
|
227
233
|
language?: BundledLanguage;
|
|
228
234
|
className?: string;
|
|
229
|
-
}) {
|
|
235
|
+
}): React.JSX.Element {
|
|
230
236
|
const [highlightedCode, setHighlightedCode] = useState<string | null>(null);
|
|
231
237
|
const [expanded, setExpanded] = useState(false);
|
|
232
238
|
|
|
@@ -241,7 +247,7 @@ function SyntaxHighlightedCode({
|
|
|
241
247
|
setHighlightedCode(null);
|
|
242
248
|
if (!language || !canHighlight) return;
|
|
243
249
|
let cancelled = false;
|
|
244
|
-
codeToHtml(displayText, {
|
|
250
|
+
void codeToHtml(displayText, {
|
|
245
251
|
lang: language,
|
|
246
252
|
theme: "github-dark-default",
|
|
247
253
|
rootStyle: "background-color: transparent;",
|
|
@@ -370,7 +376,7 @@ function ToolUISection({
|
|
|
370
376
|
defaultExpanded = false,
|
|
371
377
|
highlightSyntax = true,
|
|
372
378
|
language = "json",
|
|
373
|
-
}: ToolUISectionProps) {
|
|
379
|
+
}: ToolUISectionProps): React.JSX.Element {
|
|
374
380
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
375
381
|
|
|
376
382
|
// For structured content, we don't stringify it
|
|
@@ -434,7 +440,7 @@ function ToolUI({
|
|
|
434
440
|
onApproveOnce,
|
|
435
441
|
onApproveForSession,
|
|
436
442
|
onDeny,
|
|
437
|
-
}: ToolUIProps) {
|
|
443
|
+
}: ToolUIProps): React.JSX.Element {
|
|
438
444
|
// Use annotation title if available, otherwise fall back to name
|
|
439
445
|
const displayName = annotations?.title || name;
|
|
440
446
|
const isApprovalPending =
|
|
@@ -455,6 +461,7 @@ function ToolUI({
|
|
|
455
461
|
if (!isApprovalPending && isExpanded && !defaultExpanded) {
|
|
456
462
|
setIsExpanded(false);
|
|
457
463
|
}
|
|
464
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps -- only react to approval transition; defaultExpanded/isExpanded are intentionally not deps
|
|
458
465
|
}, [isApprovalPending]);
|
|
459
466
|
|
|
460
467
|
// Handle approve based on selected mode
|
|
@@ -497,7 +504,9 @@ function ToolUI({
|
|
|
497
504
|
|
|
498
505
|
{/* Tool row */}
|
|
499
506
|
<button
|
|
500
|
-
onClick={() =>
|
|
507
|
+
onClick={() => {
|
|
508
|
+
if (hasContent) setIsExpanded(!isExpanded);
|
|
509
|
+
}}
|
|
501
510
|
disabled={!hasContent}
|
|
502
511
|
className={cn(
|
|
503
512
|
"flex w-full items-center gap-2 px-4 py-3 text-left",
|
|
@@ -715,7 +724,7 @@ function ToolUIGroup({
|
|
|
715
724
|
defaultExpanded = false,
|
|
716
725
|
children,
|
|
717
726
|
className,
|
|
718
|
-
}: ToolUIGroupProps) {
|
|
727
|
+
}: ToolUIGroupProps): React.JSX.Element {
|
|
719
728
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
720
729
|
|
|
721
730
|
return (
|
|
@@ -9,7 +9,7 @@ import { usePortalContainer } from "@/hooks/usePortalContainer";
|
|
|
9
9
|
function TooltipProvider({
|
|
10
10
|
delayDuration = 0,
|
|
11
11
|
...props
|
|
12
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>): React.JSX.Element {
|
|
13
13
|
return (
|
|
14
14
|
<TooltipPrimitive.Provider
|
|
15
15
|
data-slot="tooltip-provider"
|
|
@@ -21,7 +21,7 @@ function TooltipProvider({
|
|
|
21
21
|
|
|
22
22
|
function Tooltip({
|
|
23
23
|
...props
|
|
24
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>): React.JSX.Element {
|
|
25
25
|
return (
|
|
26
26
|
<TooltipProvider>
|
|
27
27
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
@@ -31,7 +31,7 @@ function Tooltip({
|
|
|
31
31
|
|
|
32
32
|
function TooltipTrigger({
|
|
33
33
|
...props
|
|
34
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
34
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>): React.JSX.Element {
|
|
35
35
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -43,7 +43,7 @@ function TooltipContent({
|
|
|
43
43
|
...props
|
|
44
44
|
}: React.ComponentProps<typeof TooltipPrimitive.Content> & {
|
|
45
45
|
container?: HTMLElement | null;
|
|
46
|
-
}) {
|
|
46
|
+
}): React.JSX.Element {
|
|
47
47
|
const portalContainer = usePortalContainer();
|
|
48
48
|
return (
|
|
49
49
|
<TooltipPrimitive.Portal container={container ?? portalContainer}>
|
|
@@ -12,7 +12,7 @@ export const ChatIdContext = createContext<ChatIdContextValue | null>(null);
|
|
|
12
12
|
*
|
|
13
13
|
* @returns The current chat ID, or null if not yet initialized
|
|
14
14
|
*/
|
|
15
|
-
export const useChatId = () => {
|
|
15
|
+
export const useChatId = (): string | null => {
|
|
16
16
|
const context = useContext(ChatIdContext);
|
|
17
17
|
if (!context) {
|
|
18
18
|
throw new Error("useChatId must be used within ElementsProvider");
|