@gram-ai/elements 1.25.1 → 1.26.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/components/Chat/stories/Charts.stories.d.ts +37 -0
- package/dist/components/Chat/stories/GenerativeUI.stories.d.ts +17 -0
- package/dist/components/Chat/stories/MessageFeedback.stories.d.ts +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/buttonVariants.d.ts +1 -1
- package/dist/components/ui/charts.stories.d.ts +43 -0
- package/dist/components/ui/generative-ui.stories.d.ts +53 -0
- package/dist/contexts/ChatIdContext.d.ts +11 -0
- package/dist/contexts/contexts.d.ts +1 -0
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +7 -6
- package/dist/index-BJnv49-A.js +37057 -0
- package/dist/index-BJnv49-A.js.map +1 -0
- package/dist/index-BpJstUh1.cjs +280 -0
- package/dist/index-BpJstUh1.cjs.map +1 -0
- package/dist/index-CUitXazZ.js +30426 -0
- package/dist/index-CUitXazZ.js.map +1 -0
- package/dist/index-ChW-CSuu.cjs +147 -0
- package/dist/index-ChW-CSuu.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/plugins/chart/catalog.d.ts +123 -0
- package/dist/plugins/chart/index.d.ts +1 -1
- package/dist/plugins/chart/ui/area-chart.d.ts +16 -0
- package/dist/plugins/chart/ui/bar-chart.d.ts +16 -0
- package/dist/plugins/chart/ui/donut-chart.d.ts +17 -0
- package/dist/plugins/chart/ui/index.d.ts +7 -0
- package/dist/plugins/chart/ui/line-chart.d.ts +17 -0
- package/dist/plugins/chart/ui/pie-chart.d.ts +15 -0
- package/dist/plugins/chart/ui/radar-chart.d.ts +14 -0
- package/dist/plugins/chart/ui/scatter-chart.d.ts +18 -0
- package/dist/plugins/components/MacOSWindowFrame.d.ts +13 -0
- package/dist/plugins/components/PluginLoadingState.d.ts +1 -1
- package/dist/plugins/generative-ui/catalog.d.ts +293 -0
- package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +18 -0
- package/dist/plugins/generative-ui/ui/accordion.d.ts +7 -0
- package/dist/plugins/generative-ui/ui/action-button.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/alert.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/avatar.d.ts +11 -0
- package/dist/plugins/generative-ui/ui/badge.d.ts +12 -0
- package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +15 -0
- package/dist/plugins/generative-ui/ui/button.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/card.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/checkbox.d.ts +4 -0
- package/dist/plugins/generative-ui/ui/data-table.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/dialog.d.ts +17 -0
- package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +25 -0
- package/dist/plugins/generative-ui/ui/grid.d.ts +6 -0
- package/dist/plugins/generative-ui/ui/index.d.ts +40 -0
- package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +11 -0
- package/dist/plugins/generative-ui/ui/input.d.ts +3 -0
- package/dist/plugins/generative-ui/ui/label.d.ts +4 -0
- package/dist/plugins/generative-ui/ui/list.d.ts +6 -0
- package/dist/plugins/generative-ui/ui/metric.d.ts +7 -0
- package/dist/plugins/generative-ui/ui/pagination.d.ts +13 -0
- package/dist/plugins/generative-ui/ui/popover.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/progress.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/radio-group.d.ts +5 -0
- package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +13 -0
- package/dist/plugins/generative-ui/ui/select.d.ts +15 -0
- package/dist/plugins/generative-ui/ui/separator.d.ts +4 -0
- package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/skeleton.d.ts +2 -0
- package/dist/plugins/generative-ui/ui/stack.d.ts +8 -0
- package/dist/plugins/generative-ui/ui/switch.d.ts +6 -0
- package/dist/plugins/generative-ui/ui/table.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +21 -0
- package/dist/plugins/generative-ui/ui/tabs.d.ts +11 -0
- package/dist/plugins/generative-ui/ui/text.d.ts +7 -0
- package/dist/plugins/generative-ui/ui/textarea.d.ts +3 -0
- package/dist/plugins/generative-ui/ui/tooltip.d.ts +7 -0
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-CijCgLrw.js → profiler-D4Tw5ecI.js} +2 -2
- package/dist/{profiler-CijCgLrw.js.map → profiler-D4Tw5ecI.js.map} +1 -1
- package/dist/{profiler-DAT0DL1W.cjs → profiler-DCWYDZ1F.cjs} +2 -2
- package/dist/{profiler-DAT0DL1W.cjs.map → profiler-DCWYDZ1F.cjs.map} +1 -1
- package/dist/{startRecording-DotsE8QT.cjs → startRecording-3sTskM3H.cjs} +2 -2
- package/dist/{startRecording-DotsE8QT.cjs.map → startRecording-3sTskM3H.cjs.map} +1 -1
- package/dist/{startRecording-gmhENmf0.js → startRecording-BHhcCWQE.js} +2 -2
- package/dist/{startRecording-gmhENmf0.js.map → startRecording-BHhcCWQE.js.map} +1 -1
- package/dist/types/index.d.ts +4 -4
- package/package.json +4 -1
- package/src/components/Chat/stories/Charts.stories.tsx +260 -0
- package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -6
- package/src/components/Chat/stories/GenerativeUI.stories.tsx +113 -0
- package/src/components/Chat/stories/MessageFeedback.stories.tsx +6 -6
- package/src/components/Chat/stories/ToolApproval.stories.tsx +10 -10
- package/src/components/Chat/stories/Tools.stories.tsx +122 -104
- package/src/components/Chat/stories/Variants.stories.tsx +1 -1
- package/src/components/Replay.stories.tsx +1 -1
- package/src/components/Replay.tsx +18 -13
- package/src/components/ShadowRoot.tsx +5 -1
- package/src/components/assistant-ui/message-feedback.tsx +6 -7
- package/src/components/assistant-ui/thread.tsx +76 -11
- package/src/components/ui/charts.stories.tsx +246 -0
- package/src/components/ui/generative-ui.stories.tsx +557 -0
- package/src/components/ui/generative-ui.tsx +60 -360
- package/src/components/ui/tool-ui.stories.tsx +6 -3
- package/src/contexts/ChatIdContext.tsx +21 -0
- package/src/contexts/ElementsProvider.tsx +77 -37
- package/src/contexts/contexts.ts +2 -0
- package/src/hooks/useAuth.ts +18 -3
- package/src/hooks/useFollowOnSuggestions.ts +6 -1
- package/src/index.ts +1 -0
- package/src/plugins/chart/catalog.ts +141 -0
- package/src/plugins/chart/component.tsx +79 -125
- package/src/plugins/chart/index.ts +141 -89
- package/src/plugins/chart/ui/area-chart.tsx +133 -0
- package/src/plugins/chart/ui/bar-chart.tsx +137 -0
- package/src/plugins/chart/ui/donut-chart.tsx +167 -0
- package/src/plugins/chart/ui/index.ts +7 -0
- package/src/plugins/chart/ui/line-chart.tsx +135 -0
- package/src/plugins/chart/ui/pie-chart.tsx +148 -0
- package/src/plugins/chart/ui/radar-chart.tsx +105 -0
- package/src/plugins/chart/ui/scatter-chart.tsx +132 -0
- package/src/plugins/components/MacOSWindowFrame.tsx +55 -0
- package/src/plugins/components/PluginLoadingState.tsx +9 -13
- package/src/plugins/generative-ui/catalog.ts +277 -0
- package/src/plugins/generative-ui/component.tsx +112 -21
- package/src/plugins/generative-ui/index.ts +20 -141
- package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +57 -0
- package/src/plugins/generative-ui/ui/accordion.tsx +66 -0
- package/src/plugins/generative-ui/ui/action-button.tsx +68 -0
- package/src/plugins/generative-ui/ui/alert-wrapper.tsx +26 -0
- package/src/plugins/generative-ui/ui/alert.tsx +66 -0
- package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +22 -0
- package/src/plugins/generative-ui/ui/avatar.tsx +109 -0
- package/src/plugins/generative-ui/ui/badge.tsx +65 -0
- package/src/plugins/generative-ui/ui/button-wrapper.tsx +32 -0
- package/src/plugins/generative-ui/ui/button.tsx +65 -0
- package/src/plugins/generative-ui/ui/card-wrapper.tsx +36 -0
- package/src/plugins/generative-ui/ui/card.tsx +92 -0
- package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +39 -0
- package/src/plugins/generative-ui/ui/checkbox.tsx +32 -0
- package/src/plugins/generative-ui/ui/data-table.tsx +53 -0
- package/src/plugins/generative-ui/ui/dialog.tsx +158 -0
- package/src/plugins/generative-ui/ui/dropdown-menu.tsx +257 -0
- package/src/plugins/generative-ui/ui/grid.tsx +29 -0
- package/src/plugins/generative-ui/ui/index.ts +43 -0
- package/src/plugins/generative-ui/ui/input-wrapper.tsx +38 -0
- package/src/plugins/generative-ui/ui/input.tsx +21 -0
- package/src/plugins/generative-ui/ui/label.tsx +24 -0
- package/src/plugins/generative-ui/ui/list.tsx +34 -0
- package/src/plugins/generative-ui/ui/metric.tsx +53 -0
- package/src/plugins/generative-ui/ui/pagination.tsx +127 -0
- package/src/plugins/generative-ui/ui/popover.tsx +89 -0
- package/src/plugins/generative-ui/ui/progress.tsx +57 -0
- package/src/plugins/generative-ui/ui/radio-group.tsx +45 -0
- package/src/plugins/generative-ui/ui/select-wrapper.tsx +41 -0
- package/src/plugins/generative-ui/ui/select.tsx +190 -0
- package/src/plugins/generative-ui/ui/separator.tsx +28 -0
- package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +30 -0
- package/src/plugins/generative-ui/ui/skeleton.tsx +13 -0
- package/src/plugins/generative-ui/ui/stack.tsx +54 -0
- package/src/plugins/generative-ui/ui/switch.tsx +35 -0
- package/src/plugins/generative-ui/ui/table.tsx +116 -0
- package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +51 -0
- package/src/plugins/generative-ui/ui/tabs.tsx +92 -0
- package/src/plugins/generative-ui/ui/text.tsx +33 -0
- package/src/plugins/generative-ui/ui/textarea.tsx +18 -0
- package/src/plugins/generative-ui/ui/tooltip.tsx +57 -0
- package/src/types/index.ts +4 -4
- package/dist/components/Chat/stories/Plugins.stories.d.ts +0 -12
- package/dist/index-C3UbmFRR.cjs +0 -178
- package/dist/index-C3UbmFRR.cjs.map +0 -1
- package/dist/index-CtyV0c-T.js +0 -27225
- package/dist/index-CtyV0c-T.js.map +0 -1
- package/dist/index-DxJwZ5Kc.js +0 -39975
- package/dist/index-DxJwZ5Kc.js.map +0 -1
- package/dist/index-iUSSoKFz.cjs +0 -251
- package/dist/index-iUSSoKFz.cjs.map +0 -1
- package/src/components/Chat/stories/Plugins.stories.tsx +0 -158
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
useConnectionStatusOptional,
|
|
60
60
|
} from './ConnectionStatusContext'
|
|
61
61
|
import { ToolExecutionProvider } from './ToolExecutionContext'
|
|
62
|
+
import { ChatIdContext } from './ChatIdContext'
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Extracts executable tools from frontend tool definitions.
|
|
@@ -93,14 +94,25 @@ export interface ElementsProviderProps {
|
|
|
93
94
|
config: ElementsConfig
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
const BASE_SYSTEM_PROMPT = `You are a helpful assistant that can answer questions and help with tasks
|
|
97
|
+
const BASE_SYSTEM_PROMPT = `You are a helpful assistant that can answer questions and help with tasks.
|
|
98
|
+
|
|
99
|
+
Tool Result Display:
|
|
100
|
+
Some tools have custom visual components that automatically render their results (you'll see a rich card/widget appear). For these, do not repeat the data - just add brief context or a follow-up question if needed.
|
|
101
|
+
|
|
102
|
+
For tools WITHOUT custom components, you should present the data clearly - either as plain text for simple results, or using the UI code block format for structured data like lists of items, categories, or dashboards.`
|
|
97
103
|
|
|
98
104
|
function mergeInternalSystemPromptWith(
|
|
99
105
|
userSystemPrompt: string | undefined,
|
|
100
|
-
plugins: Plugin[]
|
|
106
|
+
plugins: Plugin[],
|
|
107
|
+
toolsWithCustomComponents: string[]
|
|
101
108
|
) {
|
|
109
|
+
const customToolsSection =
|
|
110
|
+
toolsWithCustomComponents.length > 0
|
|
111
|
+
? `\n\nTools with custom visual components (DO NOT render UI widgets for these - they already display rich visuals):\n${toolsWithCustomComponents.map((t) => `- ${t}`).join('\n')}`
|
|
112
|
+
: ''
|
|
113
|
+
|
|
102
114
|
return `
|
|
103
|
-
${BASE_SYSTEM_PROMPT}
|
|
115
|
+
${BASE_SYSTEM_PROMPT}${customToolsSection}
|
|
104
116
|
|
|
105
117
|
User-provided System Prompt:
|
|
106
118
|
${userSystemPrompt ?? 'None provided'}
|
|
@@ -158,9 +170,13 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
158
170
|
|
|
159
171
|
const plugins = config.plugins ?? recommended
|
|
160
172
|
|
|
173
|
+
// Get list of tools that have custom components registered
|
|
174
|
+
const toolsWithCustomComponents = Object.keys(config.tools?.components ?? {})
|
|
175
|
+
|
|
161
176
|
const systemPrompt = mergeInternalSystemPromptWith(
|
|
162
177
|
config.systemPrompt,
|
|
163
|
-
plugins
|
|
178
|
+
plugins,
|
|
179
|
+
toolsWithCustomComponents
|
|
164
180
|
)
|
|
165
181
|
|
|
166
182
|
// Initialize error tracking on mount
|
|
@@ -176,6 +192,9 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
176
192
|
// When history is enabled, the thread adapter manages chat IDs instead
|
|
177
193
|
const chatIdRef = useRef<string | null>(null)
|
|
178
194
|
|
|
195
|
+
// State to expose the current chat ID via context
|
|
196
|
+
const [currentChatId, setCurrentChatId] = useState<string | null>(null)
|
|
197
|
+
|
|
179
198
|
const { data: mcpTools, mcpHeaders } = useMCPTools({
|
|
180
199
|
auth,
|
|
181
200
|
mcp: config.mcp,
|
|
@@ -288,6 +307,8 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
288
307
|
// chat ID on subsequent tool call requests.
|
|
289
308
|
if (chatId) {
|
|
290
309
|
mcpHeaders['Gram-Chat-ID'] = chatId
|
|
310
|
+
// Update the context state so consumers can access the current chat ID
|
|
311
|
+
setCurrentChatId(chatId)
|
|
291
312
|
}
|
|
292
313
|
|
|
293
314
|
const context = runtimeRef.current?.thread.getModelContext()
|
|
@@ -467,6 +488,8 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
467
488
|
localIdToUuidMap={localIdToUuidMapRef.current}
|
|
468
489
|
currentRemoteIdRef={currentRemoteIdRef}
|
|
469
490
|
executableTools={executableTools}
|
|
491
|
+
currentChatId={currentChatId}
|
|
492
|
+
setCurrentChatId={setCurrentChatId}
|
|
470
493
|
>
|
|
471
494
|
{children}
|
|
472
495
|
</ElementsProviderWithHistory>
|
|
@@ -480,6 +503,7 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
480
503
|
runtimeRef={runtimeRef}
|
|
481
504
|
frontendTools={frontendTools}
|
|
482
505
|
executableTools={executableTools}
|
|
506
|
+
currentChatId={currentChatId}
|
|
483
507
|
>
|
|
484
508
|
{children}
|
|
485
509
|
</ElementsProviderWithoutHistory>
|
|
@@ -506,23 +530,28 @@ interface ElementsProviderWithHistoryProps {
|
|
|
506
530
|
localIdToUuidMap: Map<string, string>
|
|
507
531
|
currentRemoteIdRef: React.RefObject<string | null>
|
|
508
532
|
executableTools: ExecutableToolSet
|
|
533
|
+
currentChatId: string | null
|
|
534
|
+
setCurrentChatId: (chatId: string | null) => void
|
|
509
535
|
}
|
|
510
536
|
|
|
511
537
|
/**
|
|
512
|
-
* Component that syncs the current thread's remoteId to a ref.
|
|
538
|
+
* Component that syncs the current thread's remoteId to a ref and updates the chat ID context.
|
|
513
539
|
* Must be rendered inside AssistantRuntimeProvider to access the state.
|
|
514
540
|
*/
|
|
515
541
|
const ThreadIdSync = ({
|
|
516
542
|
remoteIdRef,
|
|
543
|
+
onChatIdChange,
|
|
517
544
|
}: {
|
|
518
545
|
remoteIdRef: React.RefObject<string | null>
|
|
546
|
+
onChatIdChange: (chatId: string | null) => void
|
|
519
547
|
}) => {
|
|
520
548
|
const remoteId = useAssistantState(
|
|
521
549
|
({ threadListItem }) => threadListItem.remoteId ?? null
|
|
522
550
|
)
|
|
523
551
|
useEffect(() => {
|
|
524
552
|
remoteIdRef.current = remoteId
|
|
525
|
-
|
|
553
|
+
onChatIdChange(remoteId)
|
|
554
|
+
}, [remoteId, remoteIdRef, onChatIdChange])
|
|
526
555
|
return null
|
|
527
556
|
}
|
|
528
557
|
|
|
@@ -537,6 +566,8 @@ const ElementsProviderWithHistory = ({
|
|
|
537
566
|
localIdToUuidMap,
|
|
538
567
|
currentRemoteIdRef,
|
|
539
568
|
executableTools,
|
|
569
|
+
currentChatId,
|
|
570
|
+
setCurrentChatId,
|
|
540
571
|
}: ElementsProviderWithHistoryProps) => {
|
|
541
572
|
const threadListAdapter = useGramThreadListAdapter({
|
|
542
573
|
apiUrl,
|
|
@@ -582,23 +613,28 @@ const ElementsProviderWithHistory = ({
|
|
|
582
613
|
|
|
583
614
|
return (
|
|
584
615
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
585
|
-
<ThreadIdSync
|
|
616
|
+
<ThreadIdSync
|
|
617
|
+
remoteIdRef={currentRemoteIdRef}
|
|
618
|
+
onChatIdChange={setCurrentChatId}
|
|
619
|
+
/>
|
|
586
620
|
<HistoryProvider>
|
|
587
|
-
<
|
|
588
|
-
<
|
|
589
|
-
<
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
contextValue?.config.variant === '
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
621
|
+
<ChatIdContext.Provider value={{ chatId: currentChatId }}>
|
|
622
|
+
<ElementsContext.Provider value={contextValue}>
|
|
623
|
+
<ToolExecutionProvider tools={executableTools}>
|
|
624
|
+
<div
|
|
625
|
+
className={cn(
|
|
626
|
+
ROOT_SELECTOR,
|
|
627
|
+
(contextValue?.config.variant === 'standalone' ||
|
|
628
|
+
contextValue?.config.variant === 'sidecar') &&
|
|
629
|
+
'h-full'
|
|
630
|
+
)}
|
|
631
|
+
>
|
|
632
|
+
{children}
|
|
633
|
+
</div>
|
|
634
|
+
<FrontendTools tools={frontendTools} />
|
|
635
|
+
</ToolExecutionProvider>
|
|
636
|
+
</ElementsContext.Provider>
|
|
637
|
+
</ChatIdContext.Provider>
|
|
602
638
|
</HistoryProvider>
|
|
603
639
|
</AssistantRuntimeProvider>
|
|
604
640
|
)
|
|
@@ -613,6 +649,7 @@ interface ElementsProviderWithoutHistoryProps {
|
|
|
613
649
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
614
650
|
frontendTools: Record<string, AssistantTool | FrontendTool<any, any>>
|
|
615
651
|
executableTools: ExecutableToolSet
|
|
652
|
+
currentChatId: string | null
|
|
616
653
|
}
|
|
617
654
|
|
|
618
655
|
const ElementsProviderWithoutHistory = ({
|
|
@@ -622,6 +659,7 @@ const ElementsProviderWithoutHistory = ({
|
|
|
622
659
|
runtimeRef,
|
|
623
660
|
frontendTools,
|
|
624
661
|
executableTools,
|
|
662
|
+
currentChatId,
|
|
625
663
|
}: ElementsProviderWithoutHistoryProps) => {
|
|
626
664
|
const runtime = useChatRuntime({ transport })
|
|
627
665
|
|
|
@@ -632,21 +670,23 @@ const ElementsProviderWithoutHistory = ({
|
|
|
632
670
|
|
|
633
671
|
return (
|
|
634
672
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
635
|
-
<
|
|
636
|
-
<
|
|
637
|
-
<
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
contextValue?.config.variant === '
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
673
|
+
<ChatIdContext.Provider value={{ chatId: currentChatId }}>
|
|
674
|
+
<ElementsContext.Provider value={contextValue}>
|
|
675
|
+
<ToolExecutionProvider tools={executableTools}>
|
|
676
|
+
<div
|
|
677
|
+
className={cn(
|
|
678
|
+
ROOT_SELECTOR,
|
|
679
|
+
(contextValue?.config.variant === 'standalone' ||
|
|
680
|
+
contextValue?.config.variant === 'sidecar') &&
|
|
681
|
+
'h-full'
|
|
682
|
+
)}
|
|
683
|
+
>
|
|
684
|
+
{children}
|
|
685
|
+
</div>
|
|
686
|
+
<FrontendTools tools={frontendTools} />
|
|
687
|
+
</ToolExecutionProvider>
|
|
688
|
+
</ElementsContext.Provider>
|
|
689
|
+
</ChatIdContext.Provider>
|
|
650
690
|
</AssistantRuntimeProvider>
|
|
651
691
|
)
|
|
652
692
|
}
|
package/src/contexts/contexts.ts
CHANGED
package/src/hooks/useAuth.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useReplayContext } from '@/contexts/ReplayContext'
|
|
1
2
|
import { hasExplicitSessionAuth, isStaticSessionAuth } from '@/lib/auth'
|
|
2
3
|
import { useMemo } from 'react'
|
|
3
4
|
import { ApiConfig } from '../types'
|
|
@@ -40,24 +41,38 @@ export const useAuth = ({
|
|
|
40
41
|
auth?: ApiConfig
|
|
41
42
|
projectSlug: string
|
|
42
43
|
}): Auth => {
|
|
44
|
+
const replayCtx = useReplayContext()
|
|
45
|
+
const isReplay = replayCtx?.isReplay ?? false
|
|
46
|
+
|
|
43
47
|
const getSession = useMemo(() => {
|
|
48
|
+
// In replay mode, skip session fetching entirely
|
|
49
|
+
if (isReplay) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
44
52
|
if (isStaticSessionAuth(auth)) {
|
|
45
53
|
return () => Promise.resolve(auth.sessionToken)
|
|
46
54
|
}
|
|
47
55
|
return !isStaticSessionAuth(auth) && hasExplicitSessionAuth(auth)
|
|
48
56
|
? auth.sessionFn
|
|
49
57
|
: defaultGetSession
|
|
50
|
-
}, [auth])
|
|
58
|
+
}, [auth, isReplay])
|
|
51
59
|
|
|
52
|
-
// The session request is only neccessary if we are not using
|
|
60
|
+
// The session request is only neccessary if we are not using static session auth
|
|
53
61
|
// configuration. If a custom session fetcher is provided, we use it,
|
|
54
62
|
// otherwise we fallback to the default session fetcher
|
|
55
63
|
const session = useSession({
|
|
56
|
-
// We want to check it's NOT API key auth, as the default auth scheme is session auth (if the user hasn't provided an explicit API config, we have a session auth config by default)
|
|
57
64
|
getSession,
|
|
58
65
|
projectSlug,
|
|
59
66
|
})
|
|
60
67
|
|
|
68
|
+
// In replay mode, return immediately without waiting for session
|
|
69
|
+
if (isReplay) {
|
|
70
|
+
return {
|
|
71
|
+
headers: {},
|
|
72
|
+
isLoading: false,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
return !session
|
|
62
77
|
? {
|
|
63
78
|
isLoading: true,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useReplayContext } from '@/contexts/ReplayContext'
|
|
1
2
|
import { useAssistantState } from '@assistant-ui/react'
|
|
2
3
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
4
|
import { useElements } from './useElements'
|
|
@@ -37,13 +38,17 @@ export function useFollowOnSuggestions(): {
|
|
|
37
38
|
isLoading: boolean
|
|
38
39
|
} {
|
|
39
40
|
const { config } = useElements()
|
|
41
|
+
const replayCtx = useReplayContext()
|
|
42
|
+
const isReplay = replayCtx?.isReplay ?? false
|
|
43
|
+
|
|
40
44
|
const auth = useAuth({
|
|
41
45
|
auth: config.api,
|
|
42
46
|
projectSlug: config.projectSlug,
|
|
43
47
|
})
|
|
44
48
|
|
|
45
49
|
// Check if follow-up suggestions are enabled (default: true)
|
|
46
|
-
|
|
50
|
+
// Disable in replay mode since we don't need AI-generated suggestions
|
|
51
|
+
const isEnabled = !isReplay && config.thread?.followUpSuggestions !== false
|
|
47
52
|
|
|
48
53
|
const [suggestions, setSuggestions] = useState<FollowOnSuggestion[]>([])
|
|
49
54
|
const [isLoading, setIsLoading] = useState(false)
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { ElementsProvider } from './contexts/ElementsProvider'
|
|
|
10
10
|
export { useElements as useGramElements } from './hooks/useElements'
|
|
11
11
|
export { useElements } from './hooks/useElements'
|
|
12
12
|
export { useThreadId } from './hooks/useThreadId'
|
|
13
|
+
export { useChatId } from './contexts/ChatIdContext'
|
|
13
14
|
|
|
14
15
|
// Core Components
|
|
15
16
|
export { Chat } from '@/components/Chat'
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createCatalog } from '@json-render/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Data point schema - common structure for all chart types
|
|
6
|
+
*/
|
|
7
|
+
const dataPointSchema = z.object({
|
|
8
|
+
label: z.string(),
|
|
9
|
+
value: z.number(),
|
|
10
|
+
color: z.string().optional(),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Multi-series data point for line/area charts
|
|
15
|
+
*/
|
|
16
|
+
const seriesDataPointSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
label: z.string(),
|
|
19
|
+
})
|
|
20
|
+
.catchall(z.number())
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Chart Catalog
|
|
24
|
+
*
|
|
25
|
+
* Defines all available chart components for LLM-generated visualizations.
|
|
26
|
+
* Uses Recharts under the hood for rendering.
|
|
27
|
+
*/
|
|
28
|
+
export const chartCatalog = createCatalog({
|
|
29
|
+
name: 'chart',
|
|
30
|
+
components: {
|
|
31
|
+
BarChart: {
|
|
32
|
+
props: z.object({
|
|
33
|
+
title: z.string().optional(),
|
|
34
|
+
data: z.array(dataPointSchema),
|
|
35
|
+
layout: z.enum(['vertical', 'horizontal']).optional(),
|
|
36
|
+
showGrid: z.boolean().optional(),
|
|
37
|
+
showLegend: z.boolean().optional(),
|
|
38
|
+
className: z.string().optional(),
|
|
39
|
+
}),
|
|
40
|
+
description:
|
|
41
|
+
'Bar chart for comparing categorical data. Use vertical for few categories, horizontal for many or long labels.',
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
LineChart: {
|
|
45
|
+
props: z.object({
|
|
46
|
+
title: z.string().optional(),
|
|
47
|
+
data: z.array(seriesDataPointSchema),
|
|
48
|
+
series: z.array(z.string()).optional(),
|
|
49
|
+
showGrid: z.boolean().optional(),
|
|
50
|
+
showLegend: z.boolean().optional(),
|
|
51
|
+
showDots: z.boolean().optional(),
|
|
52
|
+
curved: z.boolean().optional(),
|
|
53
|
+
className: z.string().optional(),
|
|
54
|
+
}),
|
|
55
|
+
description:
|
|
56
|
+
'Line chart for showing trends over time or continuous data. Supports multiple series.',
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
AreaChart: {
|
|
60
|
+
props: z.object({
|
|
61
|
+
title: z.string().optional(),
|
|
62
|
+
data: z.array(seriesDataPointSchema),
|
|
63
|
+
series: z.array(z.string()).optional(),
|
|
64
|
+
stacked: z.boolean().optional(),
|
|
65
|
+
showGrid: z.boolean().optional(),
|
|
66
|
+
showLegend: z.boolean().optional(),
|
|
67
|
+
className: z.string().optional(),
|
|
68
|
+
}),
|
|
69
|
+
description:
|
|
70
|
+
'Area chart for showing volume/magnitude over time. Use stacked for part-to-whole relationships.',
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
PieChart: {
|
|
74
|
+
props: z.object({
|
|
75
|
+
title: z.string().optional(),
|
|
76
|
+
data: z.array(dataPointSchema),
|
|
77
|
+
showLabels: z.boolean().optional(),
|
|
78
|
+
showLegend: z.boolean().optional(),
|
|
79
|
+
className: z.string().optional(),
|
|
80
|
+
}),
|
|
81
|
+
description:
|
|
82
|
+
'Pie chart for showing proportions of a whole. Best for 2-6 categories.',
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
DonutChart: {
|
|
86
|
+
props: z.object({
|
|
87
|
+
title: z.string().optional(),
|
|
88
|
+
data: z.array(dataPointSchema),
|
|
89
|
+
showLabels: z.boolean().optional(),
|
|
90
|
+
showLegend: z.boolean().optional(),
|
|
91
|
+
innerLabel: z.string().optional(),
|
|
92
|
+
innerValue: z.union([z.string(), z.number()]).optional(),
|
|
93
|
+
className: z.string().optional(),
|
|
94
|
+
}),
|
|
95
|
+
description:
|
|
96
|
+
'Donut chart (pie with center hole). Good for showing a key metric in the center.',
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
ScatterChart: {
|
|
100
|
+
props: z.object({
|
|
101
|
+
title: z.string().optional(),
|
|
102
|
+
data: z.array(
|
|
103
|
+
z.object({
|
|
104
|
+
x: z.number(),
|
|
105
|
+
y: z.number(),
|
|
106
|
+
label: z.string().optional(),
|
|
107
|
+
size: z.number().optional(),
|
|
108
|
+
color: z.string().optional(),
|
|
109
|
+
})
|
|
110
|
+
),
|
|
111
|
+
xLabel: z.string().optional(),
|
|
112
|
+
yLabel: z.string().optional(),
|
|
113
|
+
showGrid: z.boolean().optional(),
|
|
114
|
+
className: z.string().optional(),
|
|
115
|
+
}),
|
|
116
|
+
description:
|
|
117
|
+
'Scatter plot for showing correlation between two variables.',
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
RadarChart: {
|
|
121
|
+
props: z.object({
|
|
122
|
+
title: z.string().optional(),
|
|
123
|
+
data: z.array(dataPointSchema),
|
|
124
|
+
showLegend: z.boolean().optional(),
|
|
125
|
+
className: z.string().optional(),
|
|
126
|
+
}),
|
|
127
|
+
description:
|
|
128
|
+
'Radar/spider chart for comparing multiple attributes. Best for 3-8 dimensions.',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
export type ChartCatalogComponentProps = typeof chartCatalog extends {
|
|
134
|
+
components: infer C
|
|
135
|
+
}
|
|
136
|
+
? {
|
|
137
|
+
[K in keyof C]: C[K] extends { props: infer P }
|
|
138
|
+
? z.infer<P extends z.ZodType ? P : never>
|
|
139
|
+
: never
|
|
140
|
+
}
|
|
141
|
+
: never
|
|
@@ -1,149 +1,103 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useDensity } from '@/hooks/useDensity'
|
|
4
|
-
import { useRadius } from '@/hooks/useRadius'
|
|
5
4
|
import { cn } from '@/lib/utils'
|
|
5
|
+
import { isJsonRenderTree, type JsonRenderNode } from '@/lib/generative-ui'
|
|
6
6
|
import { SyntaxHighlighterProps } from '@assistant-ui/react-markdown'
|
|
7
7
|
import { AlertCircleIcon } from 'lucide-react'
|
|
8
|
-
import { FC,
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { FC, useMemo } from 'react'
|
|
9
|
+
import { MacOSWindowFrame } from '../components/MacOSWindowFrame'
|
|
10
|
+
import { PluginLoadingState } from '../components/PluginLoadingState'
|
|
11
|
+
|
|
12
|
+
// Import all chart components
|
|
13
|
+
import {
|
|
14
|
+
BarChart,
|
|
15
|
+
LineChart,
|
|
16
|
+
AreaChart,
|
|
17
|
+
PieChart,
|
|
18
|
+
DonutChart,
|
|
19
|
+
ScatterChart,
|
|
20
|
+
RadarChart,
|
|
21
|
+
} from './ui'
|
|
22
|
+
|
|
23
|
+
const loadingMessages = [
|
|
24
|
+
'Rendering chart...',
|
|
25
|
+
'Visualizing data...',
|
|
26
|
+
'Building chart...',
|
|
27
|
+
'Processing data...',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
function getRandomLoadingMessage() {
|
|
31
|
+
return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Chart components registry
|
|
36
|
+
*/
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
const chartComponents: Record<string, FC<any>> = {
|
|
39
|
+
BarChart,
|
|
40
|
+
LineChart,
|
|
41
|
+
AreaChart,
|
|
42
|
+
PieChart,
|
|
43
|
+
DonutChart,
|
|
44
|
+
ScatterChart,
|
|
45
|
+
RadarChart,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Render a chart node from json-render tree
|
|
50
|
+
*/
|
|
51
|
+
function renderChartNode(node: JsonRenderNode): React.ReactNode {
|
|
52
|
+
const Component = chartComponents[node.type]
|
|
53
|
+
|
|
54
|
+
if (!Component) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
|
57
|
+
<AlertCircleIcon className="size-4" />
|
|
58
|
+
<span>Unknown chart type: {node.type}</span>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return <Component {...(node.props ?? {})} />
|
|
64
|
+
}
|
|
11
65
|
|
|
12
66
|
export const ChartRenderer: FC<SyntaxHighlighterProps> = ({ code }) => {
|
|
13
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
14
|
-
const viewRef = useRef<View | null>(null)
|
|
15
|
-
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
16
|
-
const [error, setError] = useState<string | null>(null)
|
|
17
|
-
const [chartReady, setChartReady] = useState(false)
|
|
18
|
-
const [containerWidth, setContainerWidth] = useState(0)
|
|
19
|
-
const r = useRadius()
|
|
20
67
|
const d = useDensity()
|
|
21
68
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const el = wrapperRef.current
|
|
25
|
-
if (!el) return
|
|
26
|
-
const ro = new ResizeObserver(([entry]) => {
|
|
27
|
-
setContainerWidth(entry.contentRect.width)
|
|
28
|
-
})
|
|
29
|
-
ro.observe(el)
|
|
30
|
-
return () => ro.disconnect()
|
|
31
|
-
}, [])
|
|
32
|
-
|
|
33
|
-
// Parse and validate JSON in useMemo - only recomputes when code changes
|
|
34
|
-
const parsedSpec = useMemo(() => {
|
|
69
|
+
// Parse JSON - returns null if invalid (still streaming)
|
|
70
|
+
const content = useMemo(() => {
|
|
35
71
|
const trimmedCode = code.trim()
|
|
36
72
|
if (!trimmedCode) return null
|
|
37
73
|
|
|
38
74
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const hasValidData = dataArray.some(
|
|
46
|
-
(d) => Array.isArray(d.values) && d.values.length > 0
|
|
47
|
-
)
|
|
48
|
-
if (!hasValidData) return null
|
|
49
|
-
|
|
50
|
-
return spec
|
|
75
|
+
const parsed = JSON.parse(trimmedCode)
|
|
76
|
+
// Validate it has a type field (basic json-render structure)
|
|
77
|
+
if (!isJsonRenderTree(parsed)) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
return parsed
|
|
51
81
|
} catch {
|
|
82
|
+
// JSON is incomplete (still streaming) - return null to show loading state
|
|
52
83
|
return null
|
|
53
84
|
}
|
|
54
85
|
}, [code])
|
|
55
86
|
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
// Build the spec with autosize and width derived from the container
|
|
60
|
-
const sizedSpec = useMemo(() => {
|
|
61
|
-
if (!parsedSpec || containerWidth === 0) return null
|
|
62
|
-
// Padding used by the outer wrapper (p-lg ≈ 24px each side)
|
|
63
|
-
const padding = 48
|
|
64
|
-
const availableWidth = Math.max(containerWidth - padding, 100)
|
|
65
|
-
return {
|
|
66
|
-
...parsedSpec,
|
|
67
|
-
width: availableWidth,
|
|
68
|
-
autosize: { type: 'fit' as const, contains: 'padding' as const },
|
|
69
|
-
}
|
|
70
|
-
}, [parsedSpec, containerWidth])
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
if (!containerRef.current || !shouldRender || !sizedSpec) {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
setError(null)
|
|
78
|
-
setChartReady(false)
|
|
79
|
-
|
|
80
|
-
const runChart = async () => {
|
|
81
|
-
try {
|
|
82
|
-
// Clean up any existing view
|
|
83
|
-
if (viewRef.current) {
|
|
84
|
-
viewRef.current.finalize()
|
|
85
|
-
viewRef.current = null
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const chart = parse(sizedSpec, undefined, { ast: true })
|
|
89
|
-
const view = new View(chart, {
|
|
90
|
-
container: containerRef.current ?? undefined,
|
|
91
|
-
renderer: 'svg',
|
|
92
|
-
hover: true,
|
|
93
|
-
logLevel: Warn,
|
|
94
|
-
expr: expressionInterpreter,
|
|
95
|
-
})
|
|
96
|
-
viewRef.current = view
|
|
97
|
-
|
|
98
|
-
await view.runAsync()
|
|
99
|
-
setChartReady(true)
|
|
100
|
-
} catch (err) {
|
|
101
|
-
console.error('Failed to render chart:', err)
|
|
102
|
-
setError(err instanceof Error ? err.message : 'Failed to render chart')
|
|
103
|
-
}
|
|
104
|
-
}
|
|
87
|
+
// Memoize the loading message so it doesn't change on every render
|
|
88
|
+
const loadingMessage = useMemo(() => getRandomLoadingMessage(), [])
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
viewRef.current.finalize()
|
|
111
|
-
viewRef.current = null
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}, [shouldRender, sizedSpec])
|
|
115
|
-
|
|
116
|
-
const showLoading = !chartReady && !error
|
|
90
|
+
// Show loading shimmer while JSON is incomplete/streaming
|
|
91
|
+
if (!content) {
|
|
92
|
+
return <PluginLoadingState text={loadingMessage} />
|
|
93
|
+
}
|
|
117
94
|
|
|
95
|
+
// Render with macOS-style window frame
|
|
118
96
|
return (
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
r('lg'),
|
|
125
|
-
showLoading ? '' : d('p-lg')
|
|
126
|
-
)}
|
|
127
|
-
>
|
|
128
|
-
{showLoading && (
|
|
129
|
-
<div className="bg-muted absolute inset-0 z-10 flex items-center justify-center">
|
|
130
|
-
<span className="shimmer text-muted-foreground text-sm">
|
|
131
|
-
Rendering chart...
|
|
132
|
-
</span>
|
|
133
|
-
</div>
|
|
134
|
-
)}
|
|
135
|
-
|
|
136
|
-
{error && (
|
|
137
|
-
<div className="bg-background absolute inset-0 z-10 flex items-center justify-center gap-2 text-rose-500">
|
|
138
|
-
<AlertCircleIcon name="alert-circle" className="h-4 w-4" />
|
|
139
|
-
{error}
|
|
140
|
-
</div>
|
|
141
|
-
)}
|
|
142
|
-
|
|
143
|
-
<div
|
|
144
|
-
ref={containerRef}
|
|
145
|
-
className={error || showLoading ? 'invisible' : 'block'}
|
|
146
|
-
/>
|
|
147
|
-
</div>
|
|
97
|
+
<MacOSWindowFrame>
|
|
98
|
+
<div className={cn('bg-card w-full', d('p-lg'))}>
|
|
99
|
+
{renderChartNode(content)}
|
|
100
|
+
</div>
|
|
101
|
+
</MacOSWindowFrame>
|
|
148
102
|
)
|
|
149
103
|
}
|