@gram-ai/elements 1.37.1 → 1.38.1

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 (40) hide show
  1. package/dist/components/ActiveChatTitle.d.ts +22 -0
  2. package/dist/components/ActiveChatTitle.test.d.ts +1 -0
  3. package/dist/components/Markdown.d.ts +7 -0
  4. package/dist/components/MessageContent.d.ts +4 -0
  5. package/dist/components/activeChatTitle.helpers.d.ts +12 -0
  6. package/dist/components/ui/tool-ui.d.ts +52 -3
  7. package/dist/elements.cjs +1 -1
  8. package/dist/elements.css +1 -1
  9. package/dist/elements.js +31 -26
  10. package/dist/hooks/useMCPTools.d.ts +1 -1
  11. package/dist/{index-BdBraSNn.js → index-DJwZquGT.js} +35140 -29458
  12. package/dist/index-DJwZquGT.js.map +1 -0
  13. package/dist/index-EHNsQZHU.cjs +222 -0
  14. package/dist/index-EHNsQZHU.cjs.map +1 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/lib/messageConverter.d.ts +2 -0
  17. package/dist/{profiler-Cl-9cG3B.js → profiler-De-Fmqbg.js} +2 -2
  18. package/dist/{profiler-Cl-9cG3B.js.map → profiler-De-Fmqbg.js.map} +1 -1
  19. package/dist/{profiler-ttCkbP-N.cjs → profiler-tmT7xKnI.cjs} +2 -2
  20. package/dist/{profiler-ttCkbP-N.cjs.map → profiler-tmT7xKnI.cjs.map} +1 -1
  21. package/dist/{startRecording-C41DbnxY.js → startRecording-BD0yDLcT.js} +2 -2
  22. package/dist/{startRecording-C41DbnxY.js.map → startRecording-BD0yDLcT.js.map} +1 -1
  23. package/dist/{startRecording-DLCeKyz9.cjs → startRecording-BDccFns1.cjs} +2 -2
  24. package/dist/{startRecording-DLCeKyz9.cjs.map → startRecording-BDccFns1.cjs.map} +1 -1
  25. package/package.json +11 -13
  26. package/src/components/ActiveChatTitle.test.ts +39 -0
  27. package/src/components/ActiveChatTitle.tsx +152 -0
  28. package/src/components/Markdown.tsx +210 -0
  29. package/src/components/MessageContent.tsx +9 -0
  30. package/src/components/activeChatTitle.helpers.ts +16 -0
  31. package/src/components/ui/tool-ui.tsx +360 -7
  32. package/src/contexts/ElementsProvider.tsx +2 -1
  33. package/src/hooks/useGramThreadListAdapter.tsx +100 -19
  34. package/src/hooks/useMCPTools.ts +1 -1
  35. package/src/index.ts +19 -0
  36. package/src/lib/messageConverter.ts +5 -0
  37. package/src/lib/tools.test.ts +24 -12
  38. package/dist/index-BdBraSNn.js.map +0 -1
  39. package/dist/index-Bl5cH0sz.cjs +0 -194
  40. package/dist/index-Bl5cH0sz.cjs.map +0 -1
@@ -0,0 +1,210 @@
1
+ import { type FC, memo, type ReactNode } from "react";
2
+ import ReactMarkdown, { type Components } from "react-markdown";
3
+ import remarkGfm from "remark-gfm";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ export interface MarkdownProps {
8
+ /** Raw markdown text. */
9
+ children: string;
10
+ /** Optional className applied to the `.aui-md` root wrapper. */
11
+ className?: string;
12
+ }
13
+
14
+ /**
15
+ * Standalone markdown renderer that mirrors the look of the live
16
+ * `<MarkdownText />` (the same `aui-md-*` typography) but renders a plain
17
+ * string with `react-markdown` instead of the assistant-ui streaming runtime.
18
+ *
19
+ * Use in static viewers (the dashboard's chat detail panel, replay, share)
20
+ * where there is no `ElementsProvider` / assistant-ui message context. Fenced
21
+ * code blocks are styled but not syntax-highlighted — the heavier shiki path
22
+ * stays reserved for tool output via `<ToolUI />`.
23
+ */
24
+ const MarkdownImpl: FC<MarkdownProps> = ({ children, className }) => {
25
+ return (
26
+ <div className={cn("aui-md", className)}>
27
+ <ReactMarkdown
28
+ remarkPlugins={[remarkGfm]}
29
+ components={markdownComponents}
30
+ >
31
+ {children}
32
+ </ReactMarkdown>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export const Markdown = memo(MarkdownImpl);
38
+ Markdown.displayName = "Markdown";
39
+
40
+ // A fenced/indented code block, as opposed to inline `code`. react-markdown
41
+ // (>= v9) no longer passes an `inline` prop, so we infer block status from the
42
+ // language class remark attaches and from the presence of newlines.
43
+ function isBlockCode(className: string | undefined, text: string): boolean {
44
+ return /language-/.test(className ?? "") || text.includes("\n");
45
+ }
46
+
47
+ // react-markdown passes a code node's text as the children; flatten it to a
48
+ // string without risking Object's default "[object Object]" stringification.
49
+ function nodeText(node: ReactNode): string {
50
+ if (typeof node === "string") return node;
51
+ if (typeof node === "number") return String(node);
52
+ if (Array.isArray(node)) return node.map(nodeText).join("");
53
+ return "";
54
+ }
55
+
56
+ const markdownComponents: Components = {
57
+ h1: ({ className, ...props }) => (
58
+ <h1
59
+ className={cn(
60
+ "aui-md-h1 mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ ),
66
+ h2: ({ className, ...props }) => (
67
+ <h2
68
+ className={cn(
69
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
70
+ className,
71
+ )}
72
+ {...props}
73
+ />
74
+ ),
75
+ h3: ({ className, ...props }) => (
76
+ <h3
77
+ className={cn(
78
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
79
+ className,
80
+ )}
81
+ {...props}
82
+ />
83
+ ),
84
+ h4: ({ className, ...props }) => (
85
+ <h4
86
+ className={cn(
87
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
88
+ className,
89
+ )}
90
+ {...props}
91
+ />
92
+ ),
93
+ h5: ({ className, ...props }) => (
94
+ <h5
95
+ className={cn(
96
+ "aui-md-h5 my-4 text-lg font-semibold first:mt-0 last:mb-0",
97
+ className,
98
+ )}
99
+ {...props}
100
+ />
101
+ ),
102
+ h6: ({ className, ...props }) => (
103
+ <h6
104
+ className={cn(
105
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
106
+ className,
107
+ )}
108
+ {...props}
109
+ />
110
+ ),
111
+ p: ({ className, ...props }) => (
112
+ <p
113
+ className={cn(
114
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
115
+ className,
116
+ )}
117
+ {...props}
118
+ />
119
+ ),
120
+ a: ({ className, ...props }) => (
121
+ <a
122
+ className={cn(
123
+ "aui-md-a font-medium text-primary underline underline-offset-4",
124
+ className,
125
+ )}
126
+ {...props}
127
+ />
128
+ ),
129
+ blockquote: ({ className, ...props }) => (
130
+ <blockquote
131
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
132
+ {...props}
133
+ />
134
+ ),
135
+ ul: ({ className, ...props }) => (
136
+ <ul
137
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
138
+ {...props}
139
+ />
140
+ ),
141
+ ol: ({ className, ...props }) => (
142
+ <ol
143
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
144
+ {...props}
145
+ />
146
+ ),
147
+ hr: ({ className, ...props }) => (
148
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
149
+ ),
150
+ table: ({ className, ...props }) => (
151
+ <table
152
+ className={cn(
153
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
154
+ className,
155
+ )}
156
+ {...props}
157
+ />
158
+ ),
159
+ th: ({ className, ...props }) => (
160
+ <th
161
+ className={cn(
162
+ "aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [[align=center]]:text-center [[align=right]]:text-right",
163
+ className,
164
+ )}
165
+ {...props}
166
+ />
167
+ ),
168
+ td: ({ className, ...props }) => (
169
+ <td
170
+ className={cn(
171
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
172
+ className,
173
+ )}
174
+ {...props}
175
+ />
176
+ ),
177
+ tr: ({ className, ...props }) => (
178
+ <tr
179
+ className={cn(
180
+ "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
181
+ className,
182
+ )}
183
+ {...props}
184
+ />
185
+ ),
186
+ pre: ({ children }) => <>{children}</>,
187
+ code: ({ className, children, ...props }) => {
188
+ const text = nodeText(children).replace(/\n$/, "");
189
+ if (isBlockCode(className, text)) {
190
+ return (
191
+ <pre className="aui-md-pre overflow-x-auto rounded-lg border bg-muted p-4 text-foreground">
192
+ <code className={className} {...props}>
193
+ {text}
194
+ </code>
195
+ </pre>
196
+ );
197
+ }
198
+ return (
199
+ <code
200
+ className={cn(
201
+ "aui-md-inline-code rounded border bg-muted px-1 font-semibold",
202
+ className,
203
+ )}
204
+ {...props}
205
+ >
206
+ {children}
207
+ </code>
208
+ );
209
+ },
210
+ };
@@ -8,6 +8,7 @@ import { recommended } from "@/plugins";
8
8
  import { chart } from "@/plugins/chart";
9
9
  import { generativeUI } from "@/plugins/generative-ui";
10
10
  import { parseSegments } from "./MessageContent.parser";
11
+ import { Markdown } from "./Markdown";
11
12
 
12
13
  const SUPPORTED_LANGUAGES: Record<string, FC<{ code: string }>> = {
13
14
  chart: chart.Component as FC<{ code: string }>,
@@ -35,6 +36,10 @@ export interface MessageContentProps {
35
36
  content: string;
36
37
  /** Optional className applied to the root container. */
37
38
  className?: string;
39
+ /** Render plain-text segments as markdown (matching `<MarkdownText />`)
40
+ * instead of preformatted text. Fenced `chart`/`ui` blocks still render as
41
+ * widgets either way. */
42
+ markdown?: boolean;
38
43
  }
39
44
 
40
45
  /**
@@ -51,6 +56,7 @@ export interface MessageContentProps {
51
56
  export const MessageContent: FC<MessageContentProps> = ({
52
57
  content,
53
58
  className,
59
+ markdown = false,
54
60
  }) => {
55
61
  const segments = useMemo(() => parseSegments(content), [content]);
56
62
 
@@ -62,6 +68,9 @@ export const MessageContent: FC<MessageContentProps> = ({
62
68
  {segments.map((seg, i) => {
63
69
  if (seg.type === "text") {
64
70
  if (seg.text.trim() === "") return null;
71
+ if (markdown) {
72
+ return <Markdown key={i}>{seg.text}</Markdown>;
73
+ }
65
74
  return (
66
75
  <div key={i} className="whitespace-pre-wrap">
67
76
  {seg.text}
@@ -0,0 +1,16 @@
1
+ export const MAX_TITLE_LENGTH = 200;
2
+ export const FALLBACK_TITLE = "New Chat";
3
+
4
+ /**
5
+ * Decides what a title edit should persist. Trims the draft; reports `changed`
6
+ * false when it matches the current (already-trimmed) title so an untouched
7
+ * edit — including the empty "New Chat" fallback — saves nothing. An empty
8
+ * trimmed value is a deliberate reset to automatic naming.
9
+ */
10
+ export function resolveTitleEdit(
11
+ draft: string,
12
+ currentTitle: string,
13
+ ): { changed: boolean; value: string } {
14
+ const value = draft.trim();
15
+ return { changed: value !== currentTitle, value };
16
+ }