@copilotz/chat-ui 0.1.38 → 0.2.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/README.md CHANGED
@@ -202,6 +202,78 @@ import { ChatUI, chatConfigPresets } from '@copilotz/chat-ui';
202
202
  />
203
203
  ```
204
204
 
205
+ ### Markdown Extensions
206
+
207
+ By default, `@copilotz/chat-ui` renders messages with:
208
+
209
+ - `remark-gfm`
210
+ - syntax highlighting for non-streaming code blocks
211
+ - the built-in markdown component overrides used by the chat UI
212
+
213
+ If you need more control, you can extend the markdown pipeline through `config.markdown`.
214
+
215
+ ```tsx
216
+ import type { Components } from 'react-markdown';
217
+
218
+ const markdownComponents: Components = {
219
+ code: MyCustomCodeBlock,
220
+ };
221
+
222
+ <ChatUI
223
+ config={{
224
+ markdown: {
225
+ remarkPlugins: [myRemarkPlugin],
226
+ rehypePlugins: [myRehypePlugin],
227
+ components: markdownComponents,
228
+ },
229
+ }}
230
+ />
231
+ ```
232
+
233
+ Notes:
234
+
235
+ - `remarkPlugins` and `rehypePlugins` are appended to the built-in defaults.
236
+ - `components` are merged with the built-in markdown component map.
237
+ - This is the recommended way to add Mermaid, custom code blocks, callouts, or project-specific markdown behavior without increasing the core `chat-ui` bundle.
238
+
239
+ Example: Mermaid via a custom `code` renderer
240
+
241
+ ````tsx
242
+ function MermaidCodeBlock({ className, children, inline, ...props }) {
243
+ const isMermaid = !inline && /\blanguage-mermaid\b/.test(className || '');
244
+
245
+ if (isMermaid) {
246
+ return <MyMermaidRenderer definition={String(children)} />;
247
+ }
248
+
249
+ return !inline ? (
250
+ <pre>
251
+ <code className={className} {...props}>
252
+ {children}
253
+ </code>
254
+ </pre>
255
+ ) : (
256
+ <code {...props}>{children}</code>
257
+ );
258
+ }
259
+
260
+ <ChatUI
261
+ config={{
262
+ markdown: {
263
+ components: {
264
+ code: MermaidCodeBlock,
265
+ },
266
+ },
267
+ }}
268
+ />
269
+ ````
270
+
271
+ Recommended approach for Mermaid:
272
+
273
+ - keep Mermaid project-specific instead of bundling it into every `chat-ui` consumer
274
+ - lazy-load the Mermaid runtime inside your custom renderer
275
+ - render Mermaid only for completed code blocks, not for token-by-token streaming content
276
+
205
277
  ### Custom Right Sidebar
206
278
 
207
279
  Add a custom component to the right sidebar (e.g., profile info, settings, context):
@@ -280,6 +352,23 @@ All user interactions are handled through callbacks. This keeps the component pu
280
352
  | `onInitialInputConsumed` | `() => void` | Called when initial input is modified/sent |
281
353
  | `className` | `string` | Additional CSS classes |
282
354
 
355
+ ### ChatConfig Markdown
356
+
357
+ ```typescript
358
+ interface ChatMarkdownConfig {
359
+ remarkPlugins?: ReactMarkdownOptions['remarkPlugins'];
360
+ rehypePlugins?: ReactMarkdownOptions['rehypePlugins'];
361
+ components?: Components;
362
+ }
363
+ ```
364
+
365
+ ```typescript
366
+ interface ChatConfig {
367
+ // ...
368
+ markdown?: ChatMarkdownConfig;
369
+ }
370
+ ```
371
+
283
372
  ### ChatMessage
284
373
 
285
374
  ```typescript
package/dist/index.cjs CHANGED
@@ -170,6 +170,11 @@ var defaultChatConfig = {
170
170
  longMessageChunkChars: 12e3,
171
171
  renderUserMarkdown: true
172
172
  },
173
+ markdown: {
174
+ remarkPlugins: [],
175
+ rehypePlugins: [],
176
+ components: {}
177
+ },
173
178
  voiceCompose: {
174
179
  enabled: false,
175
180
  defaultMode: "text",
@@ -203,6 +208,10 @@ function mergeConfig(_baseConfig, userConfig) {
203
208
  ...defaultChatConfig.ui,
204
209
  ...userConfig.ui
205
210
  },
211
+ markdown: {
212
+ ...defaultChatConfig.markdown,
213
+ ...userConfig.markdown
214
+ },
206
215
  voiceCompose: {
207
216
  ...defaultChatConfig.voiceCompose,
208
217
  ...userConfig.voiceCompose
@@ -698,7 +707,7 @@ var ThinkingBlock = (0, import_react.memo)(function ThinkingBlock2({ reasoning,
698
707
  ] }) })
699
708
  ] });
700
709
  });
701
- var markdownComponents = {
710
+ var defaultMarkdownComponents = {
702
711
  code: ({ node, className, children, ...props }) => {
703
712
  const inline = props.inline;
704
713
  const match = /language-(\w+)/.exec(className || "");
@@ -763,12 +772,34 @@ var StreamingText = (0, import_react.memo)(function StreamingText2({
763
772
  thinkingLabel = "Thinking...",
764
773
  className = "",
765
774
  renderMarkdown = true,
775
+ markdown,
766
776
  plainTextChunkChars = 12e3,
767
777
  contentStyle,
768
778
  hideThinkingIndicator = false
769
779
  }) {
770
780
  const hasContent = content.trim().length > 0;
771
781
  const enableSyntaxHighlight = renderMarkdown && !isStreaming && hasCodeBlocks(content);
782
+ const mergedComponents = (0, import_react.useMemo)(
783
+ () => ({
784
+ ...defaultMarkdownComponents,
785
+ ...markdown?.components
786
+ }),
787
+ [markdown?.components]
788
+ );
789
+ const mergedRemarkPlugins = (0, import_react.useMemo)(
790
+ () => [
791
+ ...remarkPluginsDefault,
792
+ ...markdown?.remarkPlugins ?? []
793
+ ],
794
+ [markdown?.remarkPlugins]
795
+ );
796
+ const mergedRehypePlugins = (0, import_react.useMemo)(
797
+ () => [
798
+ ...enableSyntaxHighlight ? rehypePluginsDefault : rehypePluginsEmpty,
799
+ ...markdown?.rehypePlugins ?? []
800
+ ],
801
+ [enableSyntaxHighlight, markdown?.rehypePlugins]
802
+ );
772
803
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
773
804
  hasContent ? renderMarkdown ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
774
805
  LongContentShell,
@@ -778,9 +809,9 @@ var StreamingText = (0, import_react.memo)(function StreamingText2({
778
809
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
779
810
  import_react_markdown.default,
780
811
  {
781
- remarkPlugins: remarkPluginsDefault,
782
- rehypePlugins: enableSyntaxHighlight ? rehypePluginsDefault : rehypePluginsEmpty,
783
- components: markdownComponents,
812
+ remarkPlugins: mergedRemarkPlugins,
813
+ rehypePlugins: mergedRehypePlugins,
814
+ components: mergedComponents,
784
815
  children: content
785
816
  }
786
817
  )
@@ -954,6 +985,7 @@ var arePropsEqual = (prevProps, nextProps) => {
954
985
  if (prevProps.longMessagePreviewChars !== nextProps.longMessagePreviewChars) return false;
955
986
  if (prevProps.longMessageChunkChars !== nextProps.longMessageChunkChars) return false;
956
987
  if (prevProps.renderUserMarkdown !== nextProps.renderUserMarkdown) return false;
988
+ if (prevProps.markdown !== nextProps.markdown) return false;
957
989
  if (prevProps.isExpanded !== nextProps.isExpanded) return false;
958
990
  if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
959
991
  if (prevProps.isGrouped !== nextProps.isGrouped) return false;
@@ -985,6 +1017,7 @@ var Message = (0, import_react.memo)(({
985
1017
  longMessagePreviewChars = 4e3,
986
1018
  longMessageChunkChars = 12e3,
987
1019
  renderUserMarkdown = true,
1020
+ markdown,
988
1021
  isExpanded = false,
989
1022
  onToggleExpanded,
990
1023
  isGrouped = false
@@ -1102,6 +1135,7 @@ var Message = (0, import_react.memo)(({
1102
1135
  isStreaming: message.isStreaming,
1103
1136
  thinkingLabel,
1104
1137
  renderMarkdown: shouldRenderMarkdown,
1138
+ markdown,
1105
1139
  plainTextChunkChars: normalizedChunkChars,
1106
1140
  contentStyle,
1107
1141
  hideThinkingIndicator: !!message.reasoning
@@ -5165,6 +5199,7 @@ var ChatUI = ({
5165
5199
  longMessagePreviewChars: config.ui.longMessagePreviewChars,
5166
5200
  longMessageChunkChars: config.ui.longMessageChunkChars,
5167
5201
  renderUserMarkdown: config.ui.renderUserMarkdown,
5202
+ markdown: config.markdown,
5168
5203
  onToggleExpanded: handleToggleMessageExpansion
5169
5204
  }), [
5170
5205
  user?.avatar,
@@ -5187,6 +5222,7 @@ var ChatUI = ({
5187
5222
  config.ui.longMessagePreviewChars,
5188
5223
  config.ui.longMessageChunkChars,
5189
5224
  config.ui.renderUserMarkdown,
5225
+ config.markdown,
5190
5226
  handleMessageAction,
5191
5227
  handleToggleMessageExpansion
5192
5228
  ]);