@gram-ai/elements 1.34.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
- package/dist/compat-shims-DxtUrORi.js.map +1 -1
- package/dist/compat-shims.d.ts +9 -8
- package/dist/components/Chat/index.d.ts +2 -1
- package/dist/components/ChatHistory.d.ts +1 -1
- package/dist/components/FrontendTools/index.d.ts +1 -1
- package/dist/components/Replay.d.ts +1 -1
- package/dist/components/Replay.stories.d.ts +2 -2
- package/dist/components/ShadowRoot.d.ts +1 -1
- package/dist/components/ShareButton/index.d.ts +1 -1
- package/dist/components/ui/avatar.d.ts +3 -3
- package/dist/components/ui/button.d.ts +2 -2
- package/dist/components/ui/buttonVariants.d.ts +2 -2
- package/dist/components/ui/calendar.d.ts +2 -1
- package/dist/components/ui/collapsible.d.ts +3 -3
- package/dist/components/ui/dialog.d.ts +10 -10
- package/dist/components/ui/popover.d.ts +4 -4
- package/dist/components/ui/skeleton.d.ts +1 -1
- package/dist/components/ui/time-range-picker.d.ts +2 -1
- package/dist/components/ui/tool-ui.d.ts +7 -7
- package/dist/components/ui/tooltip.d.ts +4 -4
- package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
- package/dist/contexts/ElementsProvider.d.ts +1 -1
- package/dist/contexts/ToolApprovalContext.d.ts +2 -2
- package/dist/contexts/ToolExecutionContext.d.ts +1 -1
- package/dist/contexts/portal-container.d.ts +1 -1
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +2 -2
- package/dist/hooks/useDensity.d.ts +1 -1
- package/dist/hooks/useElements.d.ts +2 -1
- package/dist/hooks/useGramThreadListAdapter.d.ts +13 -0
- package/dist/hooks/useRadius.d.ts +1 -1
- package/dist/hooks/useThemeProps.d.ts +1 -1
- package/dist/hooks/useToolApproval.d.ts +2 -1
- package/dist/{index-BFU6NvbL.js → index-BhIowiZF.js} +9408 -9204
- package/dist/index-BhIowiZF.js.map +1 -0
- package/dist/{index-C08dvTEo.cjs → index-D0jIGQr7.cjs} +3 -3
- package/dist/index-D0jIGQr7.cjs.map +1 -0
- package/dist/{index-B5lZrrO2.js → index-Dz13dSDa.js} +57 -15
- package/dist/index-Dz13dSDa.js.map +1 -0
- package/dist/index-PXd3rs95.cjs +194 -0
- package/dist/index-PXd3rs95.cjs.map +1 -0
- package/dist/lib/errorTracking.d.ts +1 -1
- package/dist/lib/tools.d.ts +11 -10
- package/dist/plugins/generative-ui/catalog.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/accordion.d.ts +4 -4
- package/dist/plugins/generative-ui/ui/action-button.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/alert.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/avatar.d.ts +6 -6
- package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/button.d.ts +3 -3
- package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/card.d.ts +7 -7
- package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/data-table.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/dialog.d.ts +10 -10
- package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +15 -15
- package/dist/plugins/generative-ui/ui/grid.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/index.d.ts +57 -40
- package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/input.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/list.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/metric.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/pagination.d.ts +7 -7
- package/dist/plugins/generative-ui/ui/popover.d.ts +7 -7
- package/dist/plugins/generative-ui/ui/progress.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/radio-group.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/select.d.ts +10 -10
- package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +2 -1
- package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/stack.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/switch.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/table.d.ts +8 -8
- package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +2 -2
- package/dist/plugins/generative-ui/ui/tabs.d.ts +4 -4
- package/dist/plugins/generative-ui/ui/text.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/textarea.d.ts +1 -1
- package/dist/plugins/generative-ui/ui/tooltip.d.ts +4 -4
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-KLSTpp6I.js → profiler-CtGKTWWP.js} +2 -2
- package/dist/{profiler-KLSTpp6I.js.map → profiler-CtGKTWWP.js.map} +1 -1
- package/dist/{profiler-BRnyr1GA.cjs → profiler-l7_HjTyw.cjs} +2 -2
- package/dist/{profiler-BRnyr1GA.cjs.map → profiler-l7_HjTyw.cjs.map} +1 -1
- package/dist/react-shim.cjs.map +1 -1
- package/dist/react-shim.d.ts +1 -1
- package/dist/react-shim.js +1 -4
- package/dist/react-shim.js.map +1 -1
- package/dist/server/bun.cjs.map +1 -1
- package/dist/server/bun.js.map +1 -1
- package/dist/server/express.cjs.map +1 -1
- package/dist/server/express.js.map +1 -1
- package/dist/server/fastify.cjs.map +1 -1
- package/dist/server/fastify.js.map +1 -1
- package/dist/server/hono.cjs.map +1 -1
- package/dist/server/hono.js.map +1 -1
- package/dist/server/nextjs.cjs.map +1 -1
- package/dist/server/nextjs.js.map +1 -1
- package/dist/server/tanstack-start.cjs.map +1 -1
- package/dist/server/tanstack-start.js.map +1 -1
- package/dist/{startRecording-CKx-YWbq.cjs → startRecording-DEw2Aeq4.cjs} +2 -2
- package/dist/{startRecording-CKx-YWbq.cjs.map → startRecording-DEw2Aeq4.cjs.map} +1 -1
- package/dist/{startRecording-BfxB1xxR.js → startRecording-iYEL0-vr.js} +2 -2
- package/dist/{startRecording-BfxB1xxR.js.map → startRecording-iYEL0-vr.js.map} +1 -1
- package/dist/types/index.d.ts +29 -3
- package/package.json +7 -10
- package/src/compat-shims.ts +16 -2
- package/src/components/Chat/index.tsx +4 -1
- package/src/components/Chat/stories/FrontendTools.stories.tsx +1 -1
- package/src/components/Chat/stories/ToolApproval.stories.tsx +2 -2
- package/src/components/Chat/stories/Tools.stories.tsx +13 -5
- package/src/components/ChatHistory.tsx +3 -1
- package/src/components/FrontendTools/index.tsx +1 -1
- package/src/components/MessageContent.tsx +1 -0
- package/src/components/Replay.stories.tsx +2 -3
- package/src/components/Replay.tsx +17 -10
- package/src/components/ShadowRoot.tsx +2 -2
- package/src/components/ShareButton/index.tsx +4 -2
- package/src/components/assistant-ui/assistant-modal.tsx +5 -3
- package/src/components/assistant-ui/attachment.tsx +1 -1
- package/src/components/assistant-ui/error-boundary.tsx +1 -1
- package/src/components/assistant-ui/markdown-text.tsx +1 -1
- package/src/components/assistant-ui/thread.tsx +249 -11
- package/src/components/assistant-ui/tool-mention-autocomplete.tsx +1 -1
- package/src/components/ui/avatar.tsx +3 -3
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/collapsible.tsx +7 -3
- package/src/components/ui/dialog.tsx +18 -10
- package/src/components/ui/generative-ui.tsx +9 -4
- package/src/components/ui/popover.tsx +4 -4
- package/src/components/ui/skeleton.tsx +4 -1
- package/src/components/ui/time-range-picker.stories.tsx +164 -154
- package/src/components/ui/time-range-picker.tsx +11 -5
- package/src/components/ui/tool-ui.tsx +18 -9
- package/src/components/ui/tooltip.tsx +4 -4
- package/src/contexts/ChatIdContext.tsx +1 -1
- package/src/contexts/ConnectionStatusContext.tsx +6 -5
- package/src/contexts/ElementsProvider.tsx +64 -41
- package/src/contexts/ReplayContext.ts +1 -1
- package/src/contexts/ToolApprovalContext.tsx +5 -1
- package/src/contexts/ToolExecutionContext.tsx +1 -1
- package/src/contexts/portal-container.tsx +1 -1
- package/src/hooks/useAuth.ts +2 -1
- package/src/hooks/useDensity.ts +1 -1
- package/src/hooks/useElements.ts +2 -1
- package/src/hooks/useFollowOnSuggestions.ts +3 -6
- package/src/hooks/useGramThreadListAdapter.tsx +50 -3
- package/src/hooks/useMCPTools.ts +2 -2
- package/src/hooks/useModel.ts +1 -3
- package/src/hooks/usePluginComponents.ts +3 -1
- package/src/hooks/useRadius.ts +1 -1
- package/src/hooks/useSession.ts +3 -1
- package/src/hooks/useThemeProps.ts +5 -5
- package/src/hooks/useToolApproval.ts +2 -1
- package/src/lib/cassette.ts +20 -8
- package/src/lib/errorTracking.ts +1 -4
- package/src/lib/messageConverter.test.ts +11 -13
- package/src/lib/messageConverter.ts +13 -4
- package/src/lib/token.ts +2 -5
- package/src/lib/tool-mentions.ts +5 -2
- package/src/lib/tools.byte-cap.test.ts +1 -1
- package/src/lib/tools.test.ts +1 -1
- package/src/lib/tools.ts +15 -5
- package/src/lib/utils.ts +2 -2
- package/src/lib.d.ts +8 -1
- package/src/plugins/chart/chart.test.ts +3 -4
- package/src/plugins/chart/component.tsx +7 -6
- package/src/plugins/chart/ui/area-chart.tsx +1 -1
- package/src/plugins/chart/ui/line-chart.tsx +1 -1
- package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +2 -2
- package/src/plugins/generative-ui/ui/accordion.tsx +4 -4
- package/src/plugins/generative-ui/ui/action-button.tsx +4 -2
- package/src/plugins/generative-ui/ui/alert-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/alert.tsx +7 -3
- package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +5 -1
- package/src/plugins/generative-ui/ui/avatar.tsx +12 -6
- package/src/plugins/generative-ui/ui/badge.tsx +1 -1
- package/src/plugins/generative-ui/ui/button-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/button.tsx +1 -1
- package/src/plugins/generative-ui/ui/card-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/card.tsx +28 -7
- package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/checkbox.tsx +1 -1
- package/src/plugins/generative-ui/ui/data-table.tsx +1 -1
- package/src/plugins/generative-ui/ui/dialog.tsx +15 -10
- package/src/plugins/generative-ui/ui/dropdown-menu.tsx +33 -15
- package/src/plugins/generative-ui/ui/grid.tsx +1 -1
- package/src/plugins/generative-ui/ui/index.ts +154 -40
- package/src/plugins/generative-ui/ui/input-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/input.tsx +5 -1
- package/src/plugins/generative-ui/ui/label.tsx +1 -1
- package/src/plugins/generative-ui/ui/list.tsx +5 -1
- package/src/plugins/generative-ui/ui/metric.tsx +2 -1
- package/src/plugins/generative-ui/ui/pagination.tsx +12 -7
- package/src/plugins/generative-ui/ui/popover.tsx +13 -7
- package/src/plugins/generative-ui/ui/progress.tsx +1 -1
- package/src/plugins/generative-ui/ui/radio-group.tsx +2 -2
- package/src/plugins/generative-ui/ui/select-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/select.tsx +14 -10
- package/src/plugins/generative-ui/ui/separator.tsx +1 -1
- package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +1 -1
- package/src/plugins/generative-ui/ui/skeleton.tsx +4 -1
- package/src/plugins/generative-ui/ui/stack.tsx +1 -1
- package/src/plugins/generative-ui/ui/switch.tsx +1 -1
- package/src/plugins/generative-ui/ui/table.tsx +29 -8
- package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +5 -2
- package/src/plugins/generative-ui/ui/tabs.tsx +4 -4
- package/src/plugins/generative-ui/ui/text.tsx +1 -1
- package/src/plugins/generative-ui/ui/textarea.tsx +4 -1
- package/src/plugins/generative-ui/ui/tooltip.tsx +4 -4
- package/src/react-shim.ts +9 -4
- package/src/server/bun.ts +1 -1
- package/src/server/express.ts +1 -1
- package/src/server/fastify.ts +1 -1
- package/src/server/hono.ts +1 -1
- package/src/server/nextjs.ts +1 -1
- package/src/server/tanstack-start.ts +1 -1
- package/src/storybook.d.ts +5 -0
- package/src/types/index.ts +39 -3
- package/dist/index-B5lZrrO2.js.map +0 -1
- package/dist/index-BFU6NvbL.js.map +0 -1
- package/dist/index-C08dvTEo.cjs.map +0 -1
- package/dist/index-DzZ1-jQY.cjs +0 -194
- package/dist/index-DzZ1-jQY.cjs.map +0 -1
|
@@ -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");
|
|
@@ -36,7 +36,7 @@ interface ConnectionStatusProviderProps {
|
|
|
36
36
|
|
|
37
37
|
export const ConnectionStatusProvider = ({
|
|
38
38
|
children,
|
|
39
|
-
}: ConnectionStatusProviderProps) => {
|
|
39
|
+
}: ConnectionStatusProviderProps): React.JSX.Element => {
|
|
40
40
|
const [state, setState] = useState<ConnectionState>("connected");
|
|
41
41
|
const [retryCount, setRetryCount] = useState(0);
|
|
42
42
|
const [isOnline, setIsOnline] = useState(
|
|
@@ -138,7 +138,7 @@ export const ConnectionStatusProvider = ({
|
|
|
138
138
|
);
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
-
export const useConnectionStatus = () => {
|
|
141
|
+
export const useConnectionStatus = (): ConnectionStatusContextValue => {
|
|
142
142
|
const context = useContext(ConnectionStatusContext);
|
|
143
143
|
if (!context) {
|
|
144
144
|
throw new Error(
|
|
@@ -153,6 +153,7 @@ export const useConnectionStatus = () => {
|
|
|
153
153
|
* Returns null if not within a ConnectionStatusProvider (for backwards compatibility).
|
|
154
154
|
*/
|
|
155
155
|
|
|
156
|
-
export const useConnectionStatusOptional =
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
export const useConnectionStatusOptional =
|
|
157
|
+
(): ConnectionStatusContextValue | null => {
|
|
158
|
+
return useContext(ConnectionStatusContext);
|
|
159
|
+
};
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
wrapToolsWithApproval,
|
|
18
18
|
wrapToolsWithByteCap,
|
|
19
19
|
type ApprovalHelpers,
|
|
20
|
-
type FrontendTool,
|
|
21
20
|
} from "@/lib/tools";
|
|
22
21
|
import { compactForModel } from "@/lib/contextCompaction";
|
|
23
22
|
import { describeStreamError } from "@/lib/streamErrorMessage";
|
|
@@ -49,6 +48,8 @@ import {
|
|
|
49
48
|
type ChatTransport,
|
|
50
49
|
type UIMessage,
|
|
51
50
|
} from "ai";
|
|
51
|
+
|
|
52
|
+
type UIMessagePart = UIMessage["parts"][number];
|
|
52
53
|
import {
|
|
53
54
|
ReactNode,
|
|
54
55
|
useCallback,
|
|
@@ -67,43 +68,47 @@ import { ElementsContext } from "./contexts";
|
|
|
67
68
|
import { ToolApprovalProvider } from "./ToolApprovalContext";
|
|
68
69
|
import { ToolExecutionProvider } from "./ToolExecutionContext";
|
|
69
70
|
|
|
70
|
-
// Reads the active local thread id from the runtime's threads store.
|
|
71
|
-
//
|
|
72
|
-
// isolated here as the single point of breakage if the API moves.
|
|
71
|
+
// Reads the active local thread id from the runtime's threads store. Goes
|
|
72
|
+
// through assistant-ui's public ThreadListRuntime.getState() API.
|
|
73
73
|
function getActiveLocalThreadId(
|
|
74
74
|
runtimeRef: React.RefObject<ReturnType<typeof useChatRuntime> | null>,
|
|
75
75
|
): string | undefined {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
const threadsState = runtimeRef.current?.threads.getState();
|
|
77
|
+
if (!threadsState) return undefined;
|
|
78
|
+
// `mainThreadId` is always populated by the SDK; the secondary read is a
|
|
79
|
+
// defensive fallback in case the SDK ever returns a state shape with an
|
|
80
|
+
// older `threadIds` field instead. The cast widens to an indexable shape
|
|
81
|
+
// because `ThreadListState` doesn't declare that historical field.
|
|
82
|
+
const legacy = (threadsState as { threadIds?: readonly string[] }).threadIds;
|
|
83
|
+
return threadsState.mainThreadId ?? legacy?.[0];
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
type ExecutableTool = {
|
|
87
|
+
execute?: (args: unknown, options?: unknown) => Promise<unknown>;
|
|
88
|
+
};
|
|
89
|
+
|
|
83
90
|
/**
|
|
84
91
|
* Extracts executable tools from frontend tool definitions.
|
|
85
92
|
* Frontend tools created via defineFrontendTool have an unstable_tool property
|
|
86
93
|
* that contains the tool definition with execute function.
|
|
94
|
+
*
|
|
95
|
+
* The AI SDK's `ToolExecuteFunction<INPUT, OUTPUT>` signature is too strict on
|
|
96
|
+
* its second parameter (a typed `ToolCallOptions`) and too broad on its return
|
|
97
|
+
* (`AsyncIterable | PromiseLike | OUTPUT`) to match `ExecutableTool.execute`
|
|
98
|
+
* directly. The reference is copied as-is — no runtime wrapping — and only the
|
|
99
|
+
* type surface is widened.
|
|
87
100
|
*/
|
|
88
101
|
function extractExecutableTools(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
): Record<
|
|
92
|
-
string,
|
|
93
|
-
{ execute?: (args: unknown, options?: unknown) => Promise<unknown> }
|
|
94
|
-
> {
|
|
102
|
+
frontendTools: Record<string, AssistantTool> | undefined,
|
|
103
|
+
): Record<string, ExecutableTool> {
|
|
95
104
|
if (!frontendTools) return {};
|
|
96
105
|
|
|
97
106
|
return Object.fromEntries(
|
|
98
107
|
Object.entries(frontendTools).map(([name, tool]) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{
|
|
104
|
-
execute: toolDef?.execute,
|
|
105
|
-
},
|
|
106
|
-
];
|
|
108
|
+
const toolDef = tool.unstable_tool as {
|
|
109
|
+
execute?: ExecutableTool["execute"];
|
|
110
|
+
};
|
|
111
|
+
return [name, { execute: toolDef.execute }];
|
|
107
112
|
}),
|
|
108
113
|
);
|
|
109
114
|
}
|
|
@@ -154,13 +159,14 @@ function cleanMessagesForModel(messages: UIMessage[]): UIMessage[] {
|
|
|
154
159
|
return message;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
// Process each part: strip providerOptions/providerMetadata and filter reasoning
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
// Process each part: strip providerOptions/providerMetadata and filter reasoning.
|
|
163
|
+
// `callProviderMetadata` is not declared on `UIMessagePart`, so we widen the
|
|
164
|
+
// part to an indexable record just for the destructure.
|
|
165
|
+
const cleanedParts = partsArray.map((part) => {
|
|
166
|
+
const { callProviderMetadata: _omit, ...cleanPart } =
|
|
167
|
+
part as UIMessagePart & { callProviderMetadata?: unknown };
|
|
168
|
+
void _omit;
|
|
169
|
+
return cleanPart as UIMessagePart;
|
|
164
170
|
});
|
|
165
171
|
|
|
166
172
|
return {
|
|
@@ -218,6 +224,7 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
218
224
|
projectSlug: config.projectSlug,
|
|
219
225
|
variant: config.variant,
|
|
220
226
|
});
|
|
227
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps -- one-time init at mount; later config changes are intentionally ignored
|
|
221
228
|
}, []);
|
|
222
229
|
|
|
223
230
|
// Generate a stable chat ID for server-side persistence (when history is disabled)
|
|
@@ -227,7 +234,11 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
227
234
|
// State to expose the current chat ID via context
|
|
228
235
|
const [currentChatId, setCurrentChatId] = useState<string | null>(null);
|
|
229
236
|
|
|
230
|
-
const {
|
|
237
|
+
const {
|
|
238
|
+
data: mcpTools,
|
|
239
|
+
mcpHeaders,
|
|
240
|
+
isLoading: mcpQueryLoading,
|
|
241
|
+
} = useMCPTools({
|
|
231
242
|
auth,
|
|
232
243
|
mcp: config.mcp,
|
|
233
244
|
mcps: config.mcps,
|
|
@@ -235,6 +246,11 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
235
246
|
toolsToInclude: config.tools?.toolsToInclude,
|
|
236
247
|
gramEnvironment: config.gramEnvironment,
|
|
237
248
|
});
|
|
249
|
+
// Treat auth-loading as "tools not yet resolved" too — the MCP query is
|
|
250
|
+
// disabled (and so not "loading") until auth settles, so without this a
|
|
251
|
+
// tool-list consumer would briefly see an empty, settled state before tools
|
|
252
|
+
// arrive.
|
|
253
|
+
const mcpToolsLoading = auth.isLoading || mcpQueryLoading;
|
|
238
254
|
|
|
239
255
|
// Store approval helpers in ref so they can be used in async contexts
|
|
240
256
|
const approvalHelpersRef = useRef<ApprovalHelpers>({
|
|
@@ -522,8 +538,11 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
522
538
|
config.contextCompaction?.maxTokens,
|
|
523
539
|
config.contextCompaction?.compactAtFraction,
|
|
524
540
|
config.contextCompaction?.keepRecent,
|
|
541
|
+
config.gramEnvironment,
|
|
542
|
+
config.api?.headers,
|
|
525
543
|
model,
|
|
526
544
|
mcpTools,
|
|
545
|
+
mcpHeaders,
|
|
527
546
|
getApprovalHelpers,
|
|
528
547
|
apiUrl,
|
|
529
548
|
auth.isLoading,
|
|
@@ -561,12 +580,13 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
561
580
|
setCurrentChatId(chatId);
|
|
562
581
|
};
|
|
563
582
|
}, [mcpHeaders, setCurrentChatId]);
|
|
583
|
+
const configTransport = config.transport;
|
|
564
584
|
const transport = useMemo<ChatTransport<UIMessage>>(() => {
|
|
565
|
-
if (typeof
|
|
566
|
-
return
|
|
585
|
+
if (typeof configTransport === "function") {
|
|
586
|
+
return configTransport({ getChatId, adoptChatId });
|
|
567
587
|
}
|
|
568
|
-
return
|
|
569
|
-
}, [
|
|
588
|
+
return configTransport ?? defaultTransport;
|
|
589
|
+
}, [configTransport, defaultTransport, getChatId, adoptChatId]);
|
|
570
590
|
|
|
571
591
|
const historyEnabled = config.history?.enabled ?? false;
|
|
572
592
|
|
|
@@ -582,8 +602,9 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
|
|
|
582
602
|
setIsOpen,
|
|
583
603
|
plugins,
|
|
584
604
|
mcpTools,
|
|
605
|
+
mcpToolsLoading,
|
|
585
606
|
}),
|
|
586
|
-
[config, model, isExpanded, isOpen, plugins, mcpTools],
|
|
607
|
+
[config, model, isExpanded, isOpen, plugins, mcpTools, mcpToolsLoading],
|
|
587
608
|
);
|
|
588
609
|
|
|
589
610
|
const frontendTools = config.tools?.frontendTools ?? {};
|
|
@@ -657,8 +678,7 @@ interface ElementsProviderWithHistoryProps {
|
|
|
657
678
|
headers: Record<string, string>;
|
|
658
679
|
contextValue: React.ContextType<typeof ElementsContext>;
|
|
659
680
|
runtimeRef: React.RefObject<ReturnType<typeof useChatRuntime> | null>;
|
|
660
|
-
|
|
661
|
-
frontendTools: Record<string, AssistantTool | FrontendTool<any, any>>;
|
|
681
|
+
frontendTools: Record<string, AssistantTool>;
|
|
662
682
|
localIdToUuidMap: Map<string, string>;
|
|
663
683
|
currentRemoteIdRef: React.RefObject<string | null>;
|
|
664
684
|
executableTools: ExecutableToolSet;
|
|
@@ -707,6 +727,7 @@ const ElementsProviderWithHistory = ({
|
|
|
707
727
|
localIdToUuidMap,
|
|
708
728
|
threadListFilters: contextValue?.config.history?.threadListFilters,
|
|
709
729
|
deferThreadIdMinting: contextValue?.config.history?.deferThreadIdMinting,
|
|
730
|
+
transformChatMessage: contextValue?.config.history?.transformChatMessage,
|
|
710
731
|
});
|
|
711
732
|
const initialThreadId = contextValue?.config.history?.initialThreadId;
|
|
712
733
|
|
|
@@ -714,6 +735,7 @@ const ElementsProviderWithHistory = ({
|
|
|
714
735
|
// half-finished: the tool-result is patched in but the agent never resumes,
|
|
715
736
|
// so the next user message lands on top of an unresolved tool-call sequence.
|
|
716
737
|
const useChatRuntimeHook = useCallback(() => {
|
|
738
|
+
// oxlint-disable-next-line react-hooks/rules-of-hooks -- intentional: useChatRuntime is invoked by useRemoteThreadListRuntime as a hook for each thread
|
|
717
739
|
return useChatRuntime({
|
|
718
740
|
transport,
|
|
719
741
|
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
|
@@ -785,8 +807,7 @@ interface ElementsProviderWithoutHistoryProps {
|
|
|
785
807
|
transport: ChatTransport<UIMessage>;
|
|
786
808
|
contextValue: React.ContextType<typeof ElementsContext>;
|
|
787
809
|
runtimeRef: React.RefObject<ReturnType<typeof useChatRuntime> | null>;
|
|
788
|
-
|
|
789
|
-
frontendTools: Record<string, AssistantTool | FrontendTool<any, any>>;
|
|
810
|
+
frontendTools: Record<string, AssistantTool>;
|
|
790
811
|
executableTools: ExecutableToolSet;
|
|
791
812
|
currentChatId: string | null;
|
|
792
813
|
}
|
|
@@ -835,7 +856,9 @@ const ElementsProviderWithoutHistory = ({
|
|
|
835
856
|
|
|
836
857
|
const queryClient = new QueryClient();
|
|
837
858
|
|
|
838
|
-
export const ElementsProvider = (
|
|
859
|
+
export const ElementsProvider = (
|
|
860
|
+
props: ElementsProviderProps,
|
|
861
|
+
): React.JSX.Element => {
|
|
839
862
|
return (
|
|
840
863
|
<QueryClientProvider client={queryClient}>
|
|
841
864
|
<ConnectionStatusProvider>
|
|
@@ -2,6 +2,6 @@ import { createContext, useContext } from "react";
|
|
|
2
2
|
|
|
3
3
|
export const ReplayContext = createContext<{ isReplay: boolean } | null>(null);
|
|
4
4
|
|
|
5
|
-
export function useReplayContext() {
|
|
5
|
+
export function useReplayContext(): { isReplay: boolean } | null {
|
|
6
6
|
return useContext(ReplayContext);
|
|
7
7
|
}
|
|
@@ -26,7 +26,11 @@ interface ToolApprovalContextType {
|
|
|
26
26
|
getPendingApproval: (toolCallId: string) => PendingApproval | undefined;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function ToolApprovalProvider({
|
|
29
|
+
export function ToolApprovalProvider({
|
|
30
|
+
children,
|
|
31
|
+
}: {
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
}): React.JSX.Element {
|
|
30
34
|
const [pendingApprovals, setPendingApprovals] = useState<
|
|
31
35
|
Map<string, PendingApproval>
|
|
32
36
|
>(new Map());
|
|
@@ -36,7 +36,7 @@ interface ToolExecutionProviderProps {
|
|
|
36
36
|
export function ToolExecutionProvider({
|
|
37
37
|
children,
|
|
38
38
|
tools,
|
|
39
|
-
}: ToolExecutionProviderProps) {
|
|
39
|
+
}: ToolExecutionProviderProps): React.JSX.Element {
|
|
40
40
|
const executeTool = useCallback(
|
|
41
41
|
async (
|
|
42
42
|
toolName: string,
|
package/src/hooks/useAuth.ts
CHANGED
|
@@ -131,7 +131,8 @@ export const useAuth = ({
|
|
|
131
131
|
// useMCPTools). Today the dashboard uses this to forward
|
|
132
132
|
// `Authorization: Bearer <user-session JWT>` so the runtime gateway can
|
|
133
133
|
// resolve the user's upstream credentials for issuer-gated toolsets.
|
|
134
|
-
const
|
|
134
|
+
const authHeaders = auth?.headers;
|
|
135
|
+
const extraHeaders = useMemo(() => authHeaders ?? {}, [authHeaders]);
|
|
135
136
|
|
|
136
137
|
const ensureValidHeaders = useCallback(async (): Promise<
|
|
137
138
|
Record<string, string>
|
package/src/hooks/useDensity.ts
CHANGED
|
@@ -102,7 +102,7 @@ type DensityToken = keyof (typeof densityClasses)["normal"];
|
|
|
102
102
|
* Hook to get density classes based on theme config
|
|
103
103
|
* Use: const d = useDensity(); then d('p-md') returns the appropriate padding class
|
|
104
104
|
*/
|
|
105
|
-
export const useDensity = () => {
|
|
105
|
+
export const useDensity = (): ((token: DensityToken) => string) => {
|
|
106
106
|
const { config } = useElements();
|
|
107
107
|
const density = config.theme?.density ?? "normal";
|
|
108
108
|
|
package/src/hooks/useElements.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useContext } from "react";
|
|
2
2
|
import { ElementsContext } from "@/contexts/contexts";
|
|
3
|
+
import type { ElementsContextType } from "@/types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @private Internal hook to access the ElementsContext
|
|
6
7
|
*
|
|
7
8
|
*/
|
|
8
|
-
export const useElements = () => {
|
|
9
|
+
export const useElements = (): ElementsContextType => {
|
|
9
10
|
const context = useContext(ElementsContext);
|
|
10
11
|
if (!context) {
|
|
11
12
|
throw new Error("useElements must be used within a ElementsProvider");
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useReplayContext } from "@/contexts/ReplayContext";
|
|
2
|
-
import { getApiUrl } from "@/lib/api";
|
|
3
2
|
import { useAssistantState } from "@assistant-ui/react";
|
|
4
3
|
import { generateObject } from "ai";
|
|
5
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -64,8 +63,6 @@ export function useFollowOnSuggestions(): {
|
|
|
64
63
|
const isRunning = useAssistantState(({ thread }) => thread.isRunning);
|
|
65
64
|
const messages = useAssistantState(({ thread }) => thread.messages);
|
|
66
65
|
|
|
67
|
-
const apiUrl = getApiUrl(config);
|
|
68
|
-
|
|
69
66
|
const fetchSuggestions = useCallback(async () => {
|
|
70
67
|
if (!isEnabled || auth.isLoading || !auth.headers) return;
|
|
71
68
|
|
|
@@ -89,7 +86,7 @@ export function useFollowOnSuggestions(): {
|
|
|
89
86
|
let lastAssistantMessage = "";
|
|
90
87
|
for (let i = recentMessages.length - 1; i >= 0; i--) {
|
|
91
88
|
const msg = recentMessages[i];
|
|
92
|
-
if (msg.role === "assistant") {
|
|
89
|
+
if (msg && msg.role === "assistant") {
|
|
93
90
|
lastAssistantMessage = msg.content;
|
|
94
91
|
break;
|
|
95
92
|
}
|
|
@@ -191,7 +188,7 @@ ${conversation}`,
|
|
|
191
188
|
abortControllerRef.current = null;
|
|
192
189
|
}
|
|
193
190
|
}
|
|
194
|
-
}, [isEnabled,
|
|
191
|
+
}, [isEnabled, auth.headers, auth.isLoading, messages, model]);
|
|
195
192
|
|
|
196
193
|
// Fetch suggestions when:
|
|
197
194
|
// 1. The thread stops running (assistant finished responding)
|
|
@@ -224,7 +221,7 @@ ${conversation}`,
|
|
|
224
221
|
if (lastProcessedMessageIdRef.current === lastMessage.id) return;
|
|
225
222
|
|
|
226
223
|
lastProcessedMessageIdRef.current = lastMessage.id;
|
|
227
|
-
fetchSuggestions();
|
|
224
|
+
void fetchSuggestions();
|
|
228
225
|
}, [isRunning, messages, fetchSuggestions, auth.isLoading, auth.headers]);
|
|
229
226
|
|
|
230
227
|
// Cleanup on unmount
|
|
@@ -10,6 +10,7 @@ import { createAssistantStream, type AssistantStream } from "assistant-stream";
|
|
|
10
10
|
import {
|
|
11
11
|
GramChatOverview,
|
|
12
12
|
GramChat,
|
|
13
|
+
GramChatMessage,
|
|
13
14
|
convertGramMessagesToExported,
|
|
14
15
|
convertGramMessagesToUIMessages,
|
|
15
16
|
} from "@/lib/messageConverter";
|
|
@@ -60,6 +61,14 @@ async function waitForMappedId(
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Transforms or drops a persisted chat message before it is rendered from
|
|
66
|
+
* history. Return the (possibly rewritten) message, or `null` to omit it.
|
|
67
|
+
*/
|
|
68
|
+
export type ChatMessageTransform = (
|
|
69
|
+
message: GramChatMessage,
|
|
70
|
+
) => GramChatMessage | null;
|
|
71
|
+
|
|
63
72
|
export interface ThreadListAdapterOptions {
|
|
64
73
|
apiUrl: string;
|
|
65
74
|
headers: Record<string, string>;
|
|
@@ -78,6 +87,13 @@ export interface ThreadListAdapterOptions {
|
|
|
78
87
|
* `crypto.randomUUID()`. Use this when the backend owns chat-id creation.
|
|
79
88
|
*/
|
|
80
89
|
deferThreadIdMinting?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Optional hook to transform or drop each persisted message before it is
|
|
92
|
+
* converted for rendering. Return a message to render it (possibly rewritten),
|
|
93
|
+
* or `null` to omit it. Keeps product-specific transcript conventions out of
|
|
94
|
+
* the library — see {@link HistoryConfig.transformChatMessage}.
|
|
95
|
+
*/
|
|
96
|
+
transformChatMessage?: ChatMessageTransform;
|
|
81
97
|
}
|
|
82
98
|
|
|
83
99
|
interface ListChatsResponse {
|
|
@@ -93,15 +109,41 @@ class GramThreadHistoryAdapter {
|
|
|
93
109
|
private apiUrl: string;
|
|
94
110
|
private headers: Record<string, string>;
|
|
95
111
|
private store: AssistantApi;
|
|
112
|
+
// Read lazily rather than captured: the adapter is constructed once, but the
|
|
113
|
+
// consumer may swap `transformChatMessage` across renders, so resolve it from
|
|
114
|
+
// the live options on every load instead of snapshotting it here.
|
|
115
|
+
private getTransformChatMessage?: () => ChatMessageTransform | undefined;
|
|
96
116
|
|
|
97
117
|
constructor(
|
|
98
118
|
apiUrl: string,
|
|
99
119
|
headers: Record<string, string>,
|
|
100
120
|
store: AssistantApi,
|
|
121
|
+
getTransformChatMessage?: () => ChatMessageTransform | undefined,
|
|
101
122
|
) {
|
|
102
123
|
this.apiUrl = apiUrl;
|
|
103
124
|
this.headers = headers;
|
|
104
125
|
this.store = store;
|
|
126
|
+
this.getTransformChatMessage = getTransformChatMessage;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Applies the consumer-supplied `transformChatMessage` hook to a loaded
|
|
131
|
+
* transcript: rewrites each message and drops any the hook returns `null` for.
|
|
132
|
+
* Without a hook configured the messages pass through untouched.
|
|
133
|
+
*/
|
|
134
|
+
private applyTransform(messages: GramChatMessage[]): GramChatMessage[] {
|
|
135
|
+
const transform = this.getTransformChatMessage?.();
|
|
136
|
+
if (!transform) {
|
|
137
|
+
return messages;
|
|
138
|
+
}
|
|
139
|
+
const result: GramChatMessage[] = [];
|
|
140
|
+
for (const message of messages) {
|
|
141
|
+
const transformed = transform(message);
|
|
142
|
+
if (transformed) {
|
|
143
|
+
result.push(transformed);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
105
147
|
}
|
|
106
148
|
|
|
107
149
|
async load() {
|
|
@@ -122,7 +164,7 @@ class GramThreadHistoryAdapter {
|
|
|
122
164
|
}
|
|
123
165
|
|
|
124
166
|
const chat = (await response.json()) as GramChat;
|
|
125
|
-
return convertGramMessagesToExported(chat.messages);
|
|
167
|
+
return convertGramMessagesToExported(this.applyTransform(chat.messages));
|
|
126
168
|
} catch (error) {
|
|
127
169
|
console.error("Error loading chat:", error);
|
|
128
170
|
return { messages: [], headId: null };
|
|
@@ -156,7 +198,9 @@ class GramThreadHistoryAdapter {
|
|
|
156
198
|
}
|
|
157
199
|
|
|
158
200
|
const chat = (await response.json()) as GramChat;
|
|
159
|
-
return convertGramMessagesToUIMessages(
|
|
201
|
+
return convertGramMessagesToUIMessages(
|
|
202
|
+
this.applyTransform(chat.messages),
|
|
203
|
+
);
|
|
160
204
|
|
|
161
205
|
// // Filter out system messages (assistant-ui doesn't support them in the import path)
|
|
162
206
|
// const filteredMessages = chat.messages.filter(
|
|
@@ -211,6 +255,7 @@ function useGramThreadHistoryAdapter(
|
|
|
211
255
|
optionsRef.current.apiUrl,
|
|
212
256
|
optionsRef.current.headers,
|
|
213
257
|
store,
|
|
258
|
+
() => optionsRef.current.transformChatMessage,
|
|
214
259
|
),
|
|
215
260
|
);
|
|
216
261
|
// Cast to ThreadHistoryAdapter - the withFormat generic doesn't match but works at runtime
|
|
@@ -360,7 +405,9 @@ export function useGramThreadListAdapter(
|
|
|
360
405
|
// Title generation happens async server-side via Temporal after first completion.
|
|
361
406
|
// This delay allows the OpenRouter LLM call to complete before we fetch the title.
|
|
362
407
|
const TITLE_GENERATION_DELAY_MS = 2000;
|
|
363
|
-
await new Promise((r) =>
|
|
408
|
+
await new Promise((r) => {
|
|
409
|
+
setTimeout(r, TITLE_GENERATION_DELAY_MS);
|
|
410
|
+
});
|
|
364
411
|
|
|
365
412
|
try {
|
|
366
413
|
// TODO: rename generateTitle endpoint to getTitle
|
package/src/hooks/useMCPTools.ts
CHANGED
|
@@ -42,10 +42,10 @@ export function useMCPTools({
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
const envQueryKey = Object.entries(environment ?? {}).map(
|
|
45
|
-
([k, v]) => `${k}:${v}`,
|
|
45
|
+
([k, v]) => `${k}:${String(v)}`,
|
|
46
46
|
);
|
|
47
47
|
const authQueryKey = Object.entries(auth.headers ?? {}).map(
|
|
48
|
-
([k, v]) => `${k}:${v}`,
|
|
48
|
+
([k, v]) => `${k}:${String(v)}`,
|
|
49
49
|
);
|
|
50
50
|
const serversQueryKey = servers.map(
|
|
51
51
|
(s) => `${s.url}|${s.name ?? ""}|${s.environment ?? ""}`,
|
package/src/hooks/useModel.ts
CHANGED
|
@@ -5,9 +5,7 @@ import { useAuth } from "./useAuth";
|
|
|
5
5
|
import { useElements } from "./useElements";
|
|
6
6
|
|
|
7
7
|
// Creates an OpenRouter client to be used for "internal Gram" usage, such as follow-on suggestions
|
|
8
|
-
export const useModel = (
|
|
9
|
-
model: string = "openai/gpt-5.4-mini",
|
|
10
|
-
): LanguageModel => {
|
|
8
|
+
export const useModel = (model = "openai/gpt-5.4-mini"): LanguageModel => {
|
|
11
9
|
const { config } = useElements();
|
|
12
10
|
|
|
13
11
|
const auth = useAuth({
|
|
@@ -15,7 +15,9 @@ type ComponentsByLanguage =
|
|
|
15
15
|
>
|
|
16
16
|
| undefined;
|
|
17
17
|
|
|
18
|
-
export function useComponentsByLanguage(
|
|
18
|
+
export function useComponentsByLanguage(
|
|
19
|
+
plugins: Plugin[],
|
|
20
|
+
): ComponentsByLanguage {
|
|
19
21
|
return useMemo(() => {
|
|
20
22
|
return plugins.reduce((acc, plugin) => {
|
|
21
23
|
if (acc?.[plugin.language] && !plugin.overrideExisting) {
|