@gram-ai/elements 1.26.1 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +83 -15
  2. package/dist/components/Chat/stories/ConnectionConfiguration.stories.d.ts +1 -1
  3. package/dist/components/ui/calendar.d.ts +25 -0
  4. package/dist/components/ui/time-range-picker.d.ts +46 -0
  5. package/dist/components/ui/time-range-picker.stories.d.ts +37 -0
  6. package/dist/core-Cqad6-xW.js +36 -0
  7. package/dist/core-Cqad6-xW.js.map +1 -0
  8. package/dist/core-DBxmxwCi.cjs +2 -0
  9. package/dist/core-DBxmxwCi.cjs.map +1 -0
  10. package/dist/elements.cjs +1 -1
  11. package/dist/elements.css +1 -1
  12. package/dist/elements.js +18 -14
  13. package/dist/hooks/useModel.d.ts +2 -0
  14. package/dist/index-CP-wWZCV.cjs +172 -0
  15. package/dist/index-CP-wWZCV.cjs.map +1 -0
  16. package/dist/{index-DfqYP0CD.js → index-oO5BAmPI.js} +12578 -11964
  17. package/dist/index-oO5BAmPI.js.map +1 -0
  18. package/dist/index.d.ts +5 -1
  19. package/dist/lib/auth.d.ts +12 -4
  20. package/dist/lib/models.d.ts +1 -1
  21. package/dist/{profiler-ZLr2-8s7.cjs → profiler-CEpc7O5Q.cjs} +2 -2
  22. package/dist/{profiler-ZLr2-8s7.cjs.map → profiler-CEpc7O5Q.cjs.map} +1 -1
  23. package/dist/{profiler-WoFj2UH8.js → profiler-ECh1zoXF.js} +2 -2
  24. package/dist/{profiler-WoFj2UH8.js.map → profiler-ECh1zoXF.js.map} +1 -1
  25. package/dist/server/bun.cjs +2 -0
  26. package/dist/server/bun.cjs.map +1 -0
  27. package/dist/server/bun.d.ts +8 -0
  28. package/dist/server/bun.js +26 -0
  29. package/dist/server/bun.js.map +1 -0
  30. package/dist/server/core.d.ts +37 -0
  31. package/dist/server/express.cjs +2 -0
  32. package/dist/server/express.cjs.map +1 -0
  33. package/dist/server/express.d.ts +9 -0
  34. package/dist/server/express.js +21 -0
  35. package/dist/server/express.js.map +1 -0
  36. package/dist/server/fastify.cjs +2 -0
  37. package/dist/server/fastify.cjs.map +1 -0
  38. package/dist/server/fastify.d.ts +9 -0
  39. package/dist/server/fastify.js +19 -0
  40. package/dist/server/fastify.js.map +1 -0
  41. package/dist/server/hono.cjs +2 -0
  42. package/dist/server/hono.cjs.map +1 -0
  43. package/dist/server/hono.d.ts +9 -0
  44. package/dist/server/hono.js +20 -0
  45. package/dist/server/hono.js.map +1 -0
  46. package/dist/server/nextjs.cjs +2 -0
  47. package/dist/server/nextjs.cjs.map +1 -0
  48. package/dist/server/nextjs.d.ts +8 -0
  49. package/dist/server/nextjs.js +26 -0
  50. package/dist/server/nextjs.js.map +1 -0
  51. package/dist/server/tanstack-start.cjs +2 -0
  52. package/dist/server/tanstack-start.cjs.map +1 -0
  53. package/dist/server/tanstack-start.d.ts +25 -0
  54. package/dist/server/tanstack-start.js +39 -0
  55. package/dist/server/tanstack-start.js.map +1 -0
  56. package/dist/server.cjs +1 -1
  57. package/dist/server.cjs.map +1 -1
  58. package/dist/server.d.ts +10 -16
  59. package/dist/server.js +22 -29
  60. package/dist/server.js.map +1 -1
  61. package/dist/{startRecording-DzQo16WK.js → startRecording-CmZjjJoz.js} +2 -2
  62. package/dist/{startRecording-DzQo16WK.js.map → startRecording-CmZjjJoz.js.map} +1 -1
  63. package/dist/{startRecording-BGnWDInp.cjs → startRecording-qDCAu4Q0.cjs} +2 -2
  64. package/dist/{startRecording-BGnWDInp.cjs.map → startRecording-qDCAu4Q0.cjs.map} +1 -1
  65. package/dist/types/index.d.ts +22 -10
  66. package/package.json +63 -3
  67. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -8
  68. package/src/components/assistant-ui/thread.tsx +8 -14
  69. package/src/components/ui/calendar.tsx +262 -0
  70. package/src/components/ui/time-range-picker.stories.tsx +249 -0
  71. package/src/components/ui/time-range-picker.tsx +675 -0
  72. package/src/hooks/useAuth.ts +59 -7
  73. package/src/hooks/useFollowOnSuggestions.ts +7 -14
  74. package/src/hooks/useModel.ts +30 -0
  75. package/src/index.ts +17 -0
  76. package/src/lib/api.test.ts +4 -4
  77. package/src/lib/auth.ts +34 -4
  78. package/src/lib/models.ts +1 -0
  79. package/src/server/bun.ts +63 -0
  80. package/src/server/core.ts +84 -0
  81. package/src/server/express.ts +60 -0
  82. package/src/server/fastify.ts +61 -0
  83. package/src/server/hono.ts +55 -0
  84. package/src/server/nextjs.ts +58 -0
  85. package/src/server/tanstack-start.ts +110 -0
  86. package/src/server.ts +37 -49
  87. package/src/types/index.ts +25 -9
  88. package/dist/index-DdrZQXwQ.cjs +0 -147
  89. package/dist/index-DdrZQXwQ.cjs.map +0 -1
  90. package/dist/index-DfqYP0CD.js.map +0 -1
@@ -0,0 +1,262 @@
1
+ import * as React from 'react'
2
+ import { ChevronLeft, ChevronRight } from 'lucide-react'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ export interface CalendarProps {
7
+ /** Selected date range */
8
+ selected?: { start: Date | null; end: Date | null }
9
+ /** Called when a date or range is selected */
10
+ onSelect?: (range: { start: Date; end: Date | null }) => void
11
+ /** Whether range selection is enabled */
12
+ mode?: 'single' | 'range'
13
+ /** Disable dates before this */
14
+ minDate?: Date
15
+ /** Disable dates after this */
16
+ maxDate?: Date
17
+ /** Additional className */
18
+ className?: string
19
+ }
20
+
21
+ interface CalendarDay {
22
+ date: Date
23
+ isCurrentMonth: boolean
24
+ isToday: boolean
25
+ isSelected: boolean
26
+ isInRange: boolean
27
+ isRangeStart: boolean
28
+ isRangeEnd: boolean
29
+ isDisabled: boolean
30
+ }
31
+
32
+ const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
33
+ const MONTHS = [
34
+ 'January',
35
+ 'February',
36
+ 'March',
37
+ 'April',
38
+ 'May',
39
+ 'June',
40
+ 'July',
41
+ 'August',
42
+ 'September',
43
+ 'October',
44
+ 'November',
45
+ 'December',
46
+ ]
47
+
48
+ function isSameDay(a: Date, b: Date): boolean {
49
+ return (
50
+ a.getFullYear() === b.getFullYear() &&
51
+ a.getMonth() === b.getMonth() &&
52
+ a.getDate() === b.getDate()
53
+ )
54
+ }
55
+
56
+ function isInRange(date: Date, start: Date | null, end: Date | null): boolean {
57
+ if (!start || !end) return false
58
+ const time = date.getTime()
59
+ return time >= start.getTime() && time <= end.getTime()
60
+ }
61
+
62
+ function getCalendarDays(
63
+ year: number,
64
+ month: number,
65
+ selected: { start: Date | null; end: Date | null },
66
+ minDate?: Date,
67
+ maxDate?: Date
68
+ ): CalendarDay[] {
69
+ const today = new Date()
70
+ const firstDay = new Date(year, month, 1)
71
+ const lastDay = new Date(year, month + 1, 0)
72
+ const startPadding = firstDay.getDay()
73
+ const days: CalendarDay[] = []
74
+
75
+ // Add days from previous month
76
+ const prevMonthLastDay = new Date(year, month, 0)
77
+ for (let i = startPadding - 1; i >= 0; i--) {
78
+ const date = new Date(year, month - 1, prevMonthLastDay.getDate() - i)
79
+ days.push(createDay(date, false, today, selected, minDate, maxDate))
80
+ }
81
+
82
+ // Add days from current month
83
+ for (let d = 1; d <= lastDay.getDate(); d++) {
84
+ const date = new Date(year, month, d)
85
+ days.push(createDay(date, true, today, selected, minDate, maxDate))
86
+ }
87
+
88
+ // Add days from next month to fill the grid
89
+ const remaining = 42 - days.length // 6 rows of 7 days
90
+ for (let d = 1; d <= remaining; d++) {
91
+ const date = new Date(year, month + 1, d)
92
+ days.push(createDay(date, false, today, selected, minDate, maxDate))
93
+ }
94
+
95
+ return days
96
+ }
97
+
98
+ function createDay(
99
+ date: Date,
100
+ isCurrentMonth: boolean,
101
+ today: Date,
102
+ selected: { start: Date | null; end: Date | null },
103
+ minDate?: Date,
104
+ maxDate?: Date
105
+ ): CalendarDay {
106
+ const isStart = selected.start ? isSameDay(date, selected.start) : false
107
+ const isEnd = selected.end ? isSameDay(date, selected.end) : false
108
+ const inRange = isInRange(date, selected.start, selected.end)
109
+
110
+ let isDisabled = false
111
+ if (minDate && date < minDate) isDisabled = true
112
+ if (maxDate && date > maxDate) isDisabled = true
113
+
114
+ return {
115
+ date,
116
+ isCurrentMonth,
117
+ isToday: isSameDay(date, today),
118
+ isSelected: isStart || isEnd,
119
+ isInRange: inRange && !isStart && !isEnd,
120
+ isRangeStart: isStart,
121
+ isRangeEnd: isEnd,
122
+ isDisabled,
123
+ }
124
+ }
125
+
126
+ function Calendar({
127
+ selected = { start: null, end: null },
128
+ onSelect,
129
+ mode = 'range',
130
+ minDate,
131
+ maxDate,
132
+ className,
133
+ }: CalendarProps) {
134
+ const [viewDate, setViewDate] = React.useState(() => {
135
+ if (selected.start) return new Date(selected.start)
136
+ return new Date()
137
+ })
138
+
139
+ const [rangeSelection, setRangeSelection] = React.useState<{
140
+ start: Date | null
141
+ end: Date | null
142
+ }>(selected)
143
+
144
+ React.useEffect(() => {
145
+ setRangeSelection(selected)
146
+ }, [selected])
147
+
148
+ const year = viewDate.getFullYear()
149
+ const month = viewDate.getMonth()
150
+ const days = getCalendarDays(year, month, rangeSelection, minDate, maxDate)
151
+
152
+ const goToPreviousMonth = () => {
153
+ setViewDate(new Date(year, month - 1, 1))
154
+ }
155
+
156
+ const goToNextMonth = () => {
157
+ setViewDate(new Date(year, month + 1, 1))
158
+ }
159
+
160
+ const handleDayClick = (day: CalendarDay) => {
161
+ if (day.isDisabled) return
162
+
163
+ if (mode === 'single') {
164
+ const newSelection = { start: day.date, end: day.date }
165
+ setRangeSelection(newSelection)
166
+ onSelect?.(newSelection)
167
+ return
168
+ }
169
+
170
+ // Range mode
171
+ if (!rangeSelection.start || (rangeSelection.start && rangeSelection.end)) {
172
+ // Start new range
173
+ const newSelection = { start: day.date, end: null }
174
+ setRangeSelection(newSelection)
175
+ onSelect?.(newSelection as { start: Date; end: Date | null })
176
+ } else {
177
+ // Complete range
178
+ let start = rangeSelection.start
179
+ let end = day.date
180
+
181
+ // Ensure start is before end
182
+ if (end < start) {
183
+ ;[start, end] = [end, start]
184
+ }
185
+
186
+ const newSelection = { start, end }
187
+ setRangeSelection(newSelection)
188
+ onSelect?.(newSelection)
189
+ }
190
+ }
191
+
192
+ return (
193
+ <div data-slot="calendar" className={cn('w-[280px] p-3', className)}>
194
+ {/* Header */}
195
+ <div className="mb-2 flex items-center justify-between">
196
+ <button
197
+ type="button"
198
+ onClick={goToPreviousMonth}
199
+ className="hover:bg-accent inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors"
200
+ aria-label="Previous month"
201
+ >
202
+ <ChevronLeft className="h-4 w-4" />
203
+ </button>
204
+ <span className="text-sm font-medium">
205
+ {MONTHS[month]} {year}
206
+ </span>
207
+ <button
208
+ type="button"
209
+ onClick={goToNextMonth}
210
+ className="hover:bg-accent inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors"
211
+ aria-label="Next month"
212
+ >
213
+ <ChevronRight className="h-4 w-4" />
214
+ </button>
215
+ </div>
216
+
217
+ {/* Weekday headers */}
218
+ <div className="mb-1 grid grid-cols-7 gap-1">
219
+ {WEEKDAYS.map((day) => (
220
+ <div
221
+ key={day}
222
+ className="text-muted-foreground flex h-8 w-8 items-center justify-center text-center text-xs font-medium"
223
+ >
224
+ {day}
225
+ </div>
226
+ ))}
227
+ </div>
228
+
229
+ {/* Days grid */}
230
+ <div className="grid grid-cols-7 gap-1">
231
+ {days.map((day, i) => (
232
+ <button
233
+ key={i}
234
+ type="button"
235
+ disabled={day.isDisabled}
236
+ onClick={() => handleDayClick(day)}
237
+ className={cn(
238
+ 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm transition-colors',
239
+ // Base states
240
+ !day.isCurrentMonth && 'text-muted-foreground/50',
241
+ day.isDisabled && 'cursor-not-allowed opacity-30',
242
+ !day.isDisabled && !day.isSelected && 'hover:bg-accent',
243
+ // Today
244
+ day.isToday && !day.isSelected && 'border-primary border',
245
+ // Selected states
246
+ day.isSelected && 'bg-primary text-primary-foreground',
247
+ day.isRangeStart && 'rounded-r-none',
248
+ day.isRangeEnd && 'rounded-l-none',
249
+ // In range
250
+ day.isInRange && 'bg-primary/20 rounded-none'
251
+ )}
252
+ >
253
+ {day.date.getDate()}
254
+ </button>
255
+ ))}
256
+ </div>
257
+ </div>
258
+ )
259
+ }
260
+ Calendar.displayName = 'Calendar'
261
+
262
+ export { Calendar }
@@ -0,0 +1,249 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { useState } from 'react'
3
+ import {
4
+ TimeRangePicker,
5
+ type TimeRange,
6
+ type DateRangePreset,
7
+ } from './time-range-picker'
8
+
9
+ const meta: Meta<typeof TimeRangePicker> = {
10
+ title: 'UI/TimeRangePicker',
11
+ component: TimeRangePicker,
12
+ tags: ['autodocs'],
13
+ parameters: {
14
+ layout: 'centered',
15
+ },
16
+ decorators: [
17
+ (Story) => (
18
+ <div className="gram-elements bg-background text-foreground min-w-[400px] p-8">
19
+ <Story />
20
+ </div>
21
+ ),
22
+ ],
23
+ argTypes: {
24
+ showLive: {
25
+ control: 'boolean',
26
+ },
27
+ disabled: {
28
+ control: 'boolean',
29
+ },
30
+ timezone: {
31
+ control: 'text',
32
+ },
33
+ },
34
+ }
35
+
36
+ export default meta
37
+ type Story = StoryObj<typeof TimeRangePicker>
38
+
39
+ /**
40
+ * Default time range picker with preset badges and calendar.
41
+ * Supports natural language input with AI parsing.
42
+ */
43
+ 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
+ },
68
+ }
69
+
70
+ /**
71
+ * Time range picker with timezone indicator.
72
+ */
73
+ export const WithTimezone: Story = {
74
+ render: () => {
75
+ const [preset, setPreset] = useState<DateRangePreset | null>('30d')
76
+ const [customRange, setCustomRange] = useState<TimeRange | null>(null)
77
+
78
+ return (
79
+ <TimeRangePicker
80
+ preset={customRange ? null : preset}
81
+ customRange={customRange}
82
+ onPresetChange={(p) => {
83
+ setPreset(p)
84
+ setCustomRange(null)
85
+ }}
86
+ onCustomRangeChange={(from, to) => {
87
+ setCustomRange({ from, to })
88
+ setPreset(null)
89
+ }}
90
+ onClearCustomRange={() => {
91
+ setCustomRange(null)
92
+ setPreset('30d')
93
+ }}
94
+ timezone="UTC-08:00"
95
+ />
96
+ )
97
+ },
98
+ }
99
+
100
+ /**
101
+ * With LIVE mode toggle enabled.
102
+ */
103
+ export const WithLiveMode: Story = {
104
+ render: () => {
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
+ },
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="text-muted-foreground bg-muted rounded-md p-3 text-xs">
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
+ }
204
+
205
+ /**
206
+ * Natural language parsing demo.
207
+ * Type things like:
208
+ * - "yesterday"
209
+ * - "3 days ago"
210
+ * - "last Wednesday"
211
+ * - "past 2 weeks"
212
+ * - "January 2024"
213
+ */
214
+ 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-muted-foreground text-sm">
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
+ },
249
+ }