@aleph-alpha/chat-kit 6.1.1 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-Cr8vF94e.js → CkHistory.vue_vue_type_script_setup_true_lang-gV-lp0ZB.js} +345 -164
  2. package/dist/adapters/responses-api.js +1 -1
  3. package/dist/chat-kit.css +111 -28
  4. package/dist/code-palette-DAJUTnR-.js +21598 -0
  5. package/dist/components/base/CkCodeEditor/CkCodeEditor.stories.d.ts +43 -0
  6. package/dist/components/base/CkCodeEditor/CkCodeEditor.stories.d.ts.map +1 -0
  7. package/dist/components/base/CkCodeEditor/CkCodeEditor.vue.d.ts +39 -0
  8. package/dist/components/base/CkCodeEditor/CkCodeEditor.vue.d.ts.map +1 -0
  9. package/dist/components/base/CkCodeEditor/createCodeMirror.d.ts +44 -0
  10. package/dist/components/base/CkCodeEditor/createCodeMirror.d.ts.map +1 -0
  11. package/dist/components/base/CkCodeEditor/index.d.ts +3 -0
  12. package/dist/components/base/CkCodeEditor/index.d.ts.map +1 -0
  13. package/dist/components/base/CkCodeEditor/types.d.ts +129 -0
  14. package/dist/components/base/CkCodeEditor/types.d.ts.map +1 -0
  15. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.d.ts +6 -0
  16. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.d.ts.map +1 -1
  17. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.vue.d.ts +1 -0
  18. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.vue.d.ts.map +1 -1
  19. package/dist/components/base/CkMilkdownEditor/createCrepeEditor.d.ts +1 -0
  20. package/dist/components/base/CkMilkdownEditor/createCrepeEditor.d.ts.map +1 -1
  21. package/dist/components/base/CkMilkdownEditor/types.d.ts +8 -0
  22. package/dist/components/base/CkMilkdownEditor/types.d.ts.map +1 -1
  23. package/dist/components/base/index.d.ts +2 -0
  24. package/dist/components/base/index.d.ts.map +1 -1
  25. package/dist/components/index.d.ts +2 -2
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/components/index.js +27 -26
  28. package/dist/composables/index.js +1 -1
  29. package/dist/createCodeMirror-BkV6FEPn.js +101 -0
  30. package/dist/{createCrepeEditor-CFbvSH3s.js → createCrepeEditor-Ci5FHHIV.js} +1434 -24739
  31. package/dist/handlers/index.js +2 -2
  32. package/dist/handlers/useChatKitLabels.d.ts +6 -0
  33. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  34. package/dist/{components/base/CkMilkdownEditor/codeMirrorTheme.d.ts → helpers/codemirror/theme.d.ts} +1 -1
  35. package/dist/helpers/codemirror/theme.d.ts.map +1 -0
  36. package/dist/helpers/index.d.ts +1 -0
  37. package/dist/helpers/index.d.ts.map +1 -1
  38. package/dist/helpers/markdownToPlainText.d.ts +18 -0
  39. package/dist/helpers/markdownToPlainText.d.ts.map +1 -0
  40. package/dist/{index-BWvkps_G.js → index-B2tew8uf.js} +3 -3
  41. package/dist/{index-DhY_9bdS.js → index-BLVAx_f8.js} +2 -2
  42. package/dist/{index-B0ldhzaj.js → index-BUCqcTPA.js} +2 -2
  43. package/dist/{index-DdlzabmR.js → index-BhvbqYsK.js} +2 -2
  44. package/dist/{index-gzleljO6.js → index-BoUJGvvQ.js} +2 -2
  45. package/dist/{index-sKO1lHHC.js → index-BtY1uxn6.js} +3 -2
  46. package/dist/{index-BVOjyn3y.js → index-BvG6l44n.js} +3 -2
  47. package/dist/{index-BoqzHVNB.js → index-CB6xZVsf.js} +2 -2
  48. package/dist/{index-BaS8OW8V.js → index-CIF93PZF.js} +3 -3
  49. package/dist/index-CLdXUpL8.js +9560 -0
  50. package/dist/{index-DSq3gvqU.js → index-CN8d3yl1.js} +1 -1
  51. package/dist/{index-CRfGWVdQ.js → index-CYk7AB8M.js} +4 -4
  52. package/dist/{index-C72e57-5.js → index-CZFx44C7.js} +2 -2
  53. package/dist/{index-Tj3pzx-E.js → index-Cxs6kPho.js} +3 -3
  54. package/dist/index-D6ftdiLa.js +1825 -0
  55. package/dist/{index-CMJGuXap.js → index-DgCcFC3N.js} +3 -3
  56. package/dist/{index-DSU7cBGz.js → index-DhwsowEf.js} +2 -2
  57. package/dist/{index-C_gTb50u.js → index-Dl24CCB3.js} +4 -4
  58. package/dist/{index-BlHHEbV-.js → index-DqGDenO9.js} +2 -2
  59. package/dist/{index-naWlQpaL.js → index-DufAP1JF.js} +3 -2
  60. package/dist/{index-Dm6C1blN.js → index-DxTzaHLr.js} +4 -4
  61. package/dist/{index-A5rWjRtZ.js → index-MfXW4ZyN.js} +3 -2
  62. package/dist/{index-C6zYjwIQ.js → index-ZHg2R9iD.js} +3 -2
  63. package/dist/{index-ZeRS5rER.js → index-gYt3E3Cp.js} +2 -2
  64. package/dist/{index-Da3777l2.js → index-w2yzH7vs.js} +3 -3
  65. package/dist/index.d.ts +3 -3
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +33 -30
  68. package/dist/markdownToPlainText-BOtlQ_BY.js +34 -0
  69. package/dist/{useChatKitLabels-Ww1Q7xUr.js → useChatKitLabels-CatQSpfL.js} +881 -10428
  70. package/dist/{useMessageHandler-Tellov3w.js → useMessageHandler-nnsort0-.js} +1 -1
  71. package/package.json +5 -3
  72. package/src/components/base/CkCodeEditor/CkCodeEditor.spec.ts +223 -0
  73. package/src/components/base/CkCodeEditor/CkCodeEditor.stories.ts +311 -0
  74. package/src/components/base/CkCodeEditor/CkCodeEditor.vue +238 -0
  75. package/src/components/base/CkCodeEditor/createCodeMirror.ts +188 -0
  76. package/src/components/base/CkCodeEditor/index.ts +2 -0
  77. package/src/components/base/CkCodeEditor/types.ts +128 -0
  78. package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.spec.ts +13 -0
  79. package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.ts +34 -0
  80. package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.vue +3 -1
  81. package/src/components/base/CkMilkdownEditor/createCrepeEditor.ts +2 -1
  82. package/src/components/base/CkMilkdownEditor/milkdown-theme.css +39 -26
  83. package/src/components/base/CkMilkdownEditor/types.ts +8 -0
  84. package/src/components/base/index.ts +6 -0
  85. package/src/components/index.ts +4 -0
  86. package/src/handlers/useChatKitLabels.ts +6 -0
  87. package/src/helpers/codemirror/code-palette.css +49 -0
  88. package/src/{components/base/CkMilkdownEditor/codeMirrorTheme.ts → helpers/codemirror/theme.ts} +23 -12
  89. package/src/helpers/index.ts +1 -0
  90. package/src/helpers/markdownToPlainText.spec.ts +107 -0
  91. package/src/helpers/markdownToPlainText.ts +69 -0
  92. package/src/index.ts +5 -1
  93. package/dist/components/base/CkMilkdownEditor/codeMirrorTheme.d.ts.map +0 -1
  94. package/dist/generateId-BHf0W4Rq.js +0 -6
@@ -49,36 +49,12 @@
49
49
  --crepe-shadow-1: var(--shadow-md);
50
50
  --crepe-shadow-2: var(--shadow-lg);
51
51
 
52
- /* CodeMirror palette ≈ Shiki `github-light` (what UiCodeBlock renders). */
53
- --ck-cm-bg: #ffffff;
54
- --ck-cm-foreground: #24292f;
55
- --ck-cm-keyword: #cf222e;
56
- --ck-cm-string: #0a3069;
57
- --ck-cm-comment: #6e7781;
58
- --ck-cm-function: #8250df;
59
- --ck-cm-constant: #0550ae;
60
- /* UiCodeBlock rewrites Shiki's orange entity colour to the primary text token. */
61
- --ck-cm-entity: oklch(var(--content-on-surface-primary));
62
- --ck-cm-tag: #116329;
63
- --ck-cm-error: #82071e;
64
- --ck-cm-selection: oklch(var(--background-accent-soft));
52
+ /* The `--ck-cm-*` syntax palette comes from the shared `code-palette.css`
53
+ * via the `ck-code-surface` class on this same root. */
65
54
 
66
55
  position: relative;
67
56
  }
68
57
 
69
- /* CodeMirror palette ≈ Shiki `github-dark`. */
70
- html.dark .ck-milkdown {
71
- --ck-cm-bg: #24292e;
72
- --ck-cm-foreground: #c9d1d9;
73
- --ck-cm-keyword: #ff7b72;
74
- --ck-cm-string: #a5d6ff;
75
- --ck-cm-comment: #8b949e;
76
- --ck-cm-function: #d2a8ff;
77
- --ck-cm-constant: #79c0ff;
78
- --ck-cm-tag: #7ee787;
79
- --ck-cm-error: #ffa198;
80
- }
81
-
82
58
  /* ------------------------------------------------------------------------ */
83
59
  /* Minimal reset (replaces Crepe's reset.css) */
84
60
  /* ------------------------------------------------------------------------ */
@@ -407,10 +383,47 @@ html.dark .ck-milkdown {
407
383
  color: var(--ck-cm-foreground);
408
384
  }
409
385
 
386
+ /* Line-number gutter. CodeMirror's `basicSetup` (which Crepe always loads)
387
+ * always renders it, so `showLineNumbers` only toggles this class — no
388
+ * reconfiguration, and the change applies to code blocks already on screen.
389
+ * Hidden by default to match `UiCodeBlock`, whose `showLineNumbers` is also
390
+ * false by default, so a document reads the same in the editor and in
391
+ * `CkMarkdownRenderer`. */
410
392
  .ck-milkdown .milkdown .milkdown-code-block .cm-gutters {
411
393
  display: none;
412
394
  }
413
395
 
396
+ .ck-milkdown--line-numbers .milkdown .milkdown-code-block .cm-gutters {
397
+ display: flex;
398
+ }
399
+
400
+ /* `basicSetup` also installs a code-folding gutter. Its chevrons are noise in
401
+ * a prose editor and have no counterpart in `UiCodeBlock`, so only the numbers
402
+ * are ever shown. `!important` is required because CodeMirror's own base theme
403
+ * declares `.cm-gutter { display: flex !important }`, which no amount of
404
+ * specificity can outrank. Crepe always passes `basicSetup`, so the extension
405
+ * cannot simply be left out. */
406
+ .ck-milkdown .milkdown .milkdown-code-block .cm-foldGutter {
407
+ display: none !important;
408
+ }
409
+
410
+ .ck-milkdown--line-numbers
411
+ .milkdown
412
+ .milkdown-code-block
413
+ .cm-lineNumbers
414
+ .cm-gutterElement {
415
+ min-width: 2.25rem;
416
+ padding: 0 0.75rem 0 1rem;
417
+ color: oklch(var(--content-on-surface-muted));
418
+ text-align: right;
419
+ }
420
+
421
+ /* The gutter supplies the left inset once it is visible, so the code itself
422
+ * should not pay for it twice. */
423
+ .ck-milkdown--line-numbers .milkdown .milkdown-code-block .cm-content {
424
+ padding-left: 0;
425
+ }
426
+
414
427
  /* Shown while the CodeMirror instance is still mounting. */
415
428
  .ck-milkdown .milkdown .milkdown-code-block .milkdown-code-block-placeholder {
416
429
  margin: 0;
@@ -71,6 +71,14 @@ export interface CkMilkdownEditorProps {
71
71
  * @default false
72
72
  */
73
73
  autofocus?: boolean;
74
+ /**
75
+ * Show a line-number gutter on fenced code blocks. Matches the prop of the
76
+ * same name on `UiCodeBlock`, so one flag can drive the editor and the
77
+ * rendered message alike. Applies to code blocks already on screen.
78
+ *
79
+ * @default false
80
+ */
81
+ showLineNumbers?: boolean;
74
82
  }
75
83
 
76
84
  /** Events emitted by {@link CkMilkdownEditor}. */
@@ -86,3 +86,9 @@ export type {
86
86
  CkMilkdownEditorExposed,
87
87
  CkMilkdownEditorProps,
88
88
  } from './CkMilkdownEditor';
89
+ export { CkCodeEditor } from './CkCodeEditor';
90
+ export type {
91
+ CkCodeEditorEmits,
92
+ CkCodeEditorExposed,
93
+ CkCodeEditorProps,
94
+ } from './CkCodeEditor';
@@ -28,6 +28,7 @@ export {
28
28
  CkUserMessage,
29
29
  CkEditableUserMessage,
30
30
  CkMilkdownEditor,
31
+ CkCodeEditor,
31
32
  } from './base';
32
33
  export type {
33
34
  CkAgentMessageHeaderProps,
@@ -70,6 +71,9 @@ export type {
70
71
  CkMilkdownEditorEmits,
71
72
  CkMilkdownEditorExposed,
72
73
  CkMilkdownEditorProps,
74
+ CkCodeEditorEmits,
75
+ CkCodeEditorExposed,
76
+ CkCodeEditorProps,
73
77
  } from './base';
74
78
 
75
79
  // Composed (pattern) components
@@ -111,6 +111,10 @@ export type ChatKitLabels = {
111
111
  * set and more conversations exist than the limit.
112
112
  */
113
113
  historyShowAll?: string;
114
+ /** Accessible name of the copy button in the `CkCodeEditor` header. */
115
+ codeEditorCopy?: string;
116
+ /** Accessible name of the `CkCodeEditor` copy button just after a copy. */
117
+ codeEditorCopied?: string;
114
118
  /** Accessible name of the "Bold" button in the `CkMilkdownEditor` toolbar. */
115
119
  markdownEditorBold?: string;
116
120
  /** Accessible name of the "Italic" button in the `CkMilkdownEditor` toolbar. */
@@ -168,6 +172,8 @@ export const defaultChatKitLabels = {
168
172
  historyItemEditAction: 'Edit',
169
173
  historyItemDeleteAction: 'Delete',
170
174
  historyShowAll: 'Show all',
175
+ codeEditorCopy: 'Copy code',
176
+ codeEditorCopied: 'Copied',
171
177
  markdownEditorBold: 'Bold',
172
178
  markdownEditorItalic: 'Italic',
173
179
  markdownEditorStrikethrough: 'Strikethrough',
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Syntax palette shared by every CodeMirror surface in chat-kit — the code
3
+ * blocks inside `CkMilkdownEditor` and the standalone `CkCodeEditor`.
4
+ *
5
+ * The values are Shiki's `github-light` and `github-dark`, which is what
6
+ * `UiCodeBlock` renders inside `CkMarkdownRenderer`, read off a rendered block
7
+ * rather than copied from memory. A snippet therefore keeps its colours whether
8
+ * it is being read or edited. `theme.ts` maps CodeMirror's syntax tags onto
9
+ * these variables; nothing else should hardcode a code colour.
10
+ *
11
+ * Apply `.ck-code-surface` to the root of any component hosting a CodeMirror
12
+ * instance. The dark values follow `html.dark`, matching how `UiCodeBlock`
13
+ * switches its own Shiki theme.
14
+ *
15
+ * Exact parity is not achievable for every token: Shiki classifies with
16
+ * TextMate grammars and CodeMirror with Lezer, and the two disagree at the
17
+ * edges. The mapping in `theme.ts` targets the classes that actually occur in
18
+ * everyday code.
19
+ */
20
+
21
+ .ck-code-surface {
22
+ --ck-cm-bg: #ffffff;
23
+ --ck-cm-foreground: #24292e;
24
+ --ck-cm-keyword: #d73a49;
25
+ --ck-cm-string: #032f62;
26
+ --ck-cm-comment: #6a737d;
27
+ /* Declared functions, classes and interfaces. */
28
+ --ck-cm-function: #6f42c1;
29
+ /* Built-in types, literals and plain variables. */
30
+ --ck-cm-constant: #005cc5;
31
+ /* `UiCodeBlock` rewrites Shiki's orange (#e36209 / #ffab70) to the primary
32
+ * text token, which is what declared property names end up as. */
33
+ --ck-cm-property: oklch(var(--content-on-surface-primary));
34
+ --ck-cm-tag: #22863a;
35
+ --ck-cm-error: #b31d28;
36
+ --ck-cm-selection: oklch(var(--background-accent-soft));
37
+ }
38
+
39
+ html.dark .ck-code-surface {
40
+ --ck-cm-bg: #24292e;
41
+ --ck-cm-foreground: #e1e4e8;
42
+ --ck-cm-keyword: #f97583;
43
+ --ck-cm-string: #9ecbff;
44
+ --ck-cm-comment: #6a737d;
45
+ --ck-cm-function: #b392f0;
46
+ --ck-cm-constant: #79b8ff;
47
+ --ck-cm-tag: #85e89d;
48
+ --ck-cm-error: #fdaeb7;
49
+ }
@@ -21,6 +21,7 @@ const cssVar = (name: string): string => `var(--ck-cm-${name})`;
21
21
  * editor and in `CkMarkdownRenderer`.
22
22
  */
23
23
  export const ckHighlightStyle = HighlightStyle.define([
24
+ // Keywords, and the operators GitHub colours like keywords (`=`, `:`).
24
25
  {
25
26
  tag: [
26
27
  t.keyword,
@@ -29,6 +30,7 @@ export const ckHighlightStyle = HighlightStyle.define([
29
30
  t.definitionKeyword,
30
31
  t.moduleKeyword,
31
32
  t.operatorKeyword,
33
+ t.operator,
32
34
  ],
33
35
  color: cssVar('keyword'),
34
36
  },
@@ -40,14 +42,20 @@ export const ckHighlightStyle = HighlightStyle.define([
40
42
  tag: [t.comment, t.lineComment, t.blockComment, t.docComment],
41
43
  color: cssVar('comment'),
42
44
  },
45
+ // Things being declared: functions, classes, interfaces.
43
46
  {
44
47
  tag: [
45
48
  t.function(t.variableName),
46
49
  t.function(t.propertyName),
47
50
  t.definition(t.function(t.variableName)),
51
+ t.definition(t.typeName),
52
+ t.className,
53
+ t.namespace,
54
+ t.self,
48
55
  ],
49
56
  color: cssVar('function'),
50
57
  },
58
+ // Built-in types, literals and ordinary variables.
51
59
  {
52
60
  tag: [
53
61
  t.number,
@@ -56,25 +64,20 @@ export const ckHighlightStyle = HighlightStyle.define([
56
64
  t.atom,
57
65
  t.constant(t.name),
58
66
  t.constant(t.variableName),
67
+ t.definition(t.variableName),
68
+ t.typeName,
69
+ t.standard(t.typeName),
59
70
  t.propertyName,
60
71
  t.attributeName,
61
72
  t.attributeValue,
62
73
  ],
63
74
  color: cssVar('constant'),
64
75
  },
65
- {
66
- tag: [
67
- t.typeName,
68
- t.className,
69
- t.namespace,
70
- t.definition(t.variableName),
71
- t.self,
72
- ],
73
- color: cssVar('entity'),
74
- },
76
+ // A declared property name, which UiCodeBlock renders in the text token.
77
+ { tag: t.definition(t.propertyName), color: cssVar('property') },
75
78
  { tag: [t.tagName, t.angleBracket], color: cssVar('tag') },
76
79
  {
77
- tag: [t.variableName, t.operator, t.punctuation, t.bracket, t.separator],
80
+ tag: [t.variableName, t.punctuation, t.bracket, t.separator],
78
81
  color: cssVar('foreground'),
79
82
  },
80
83
  { tag: t.heading, fontWeight: 'bold' },
@@ -107,8 +110,16 @@ export const ckCodeMirrorTheme: Extension = [
107
110
  '.cm-line': {
108
111
  padding: '0',
109
112
  },
113
+ // Gutter visibility is driven by the `showLineNumbers` prop through a root
114
+ // class in `milkdown-theme.css`, not from here, so it can toggle without
115
+ // reconfiguring CodeMirror. Only the chrome is set here.
110
116
  '.cm-gutters': {
111
- display: 'none',
117
+ backgroundColor: 'transparent',
118
+ borderRight: 'none',
119
+ color: cssVar('comment'),
120
+ },
121
+ '.cm-activeLineGutter': {
122
+ backgroundColor: 'transparent',
112
123
  },
113
124
  '.cm-cursor, .cm-dropCursor': {
114
125
  borderLeftColor: cssVar('foreground'),
@@ -1,5 +1,6 @@
1
1
  export * from './conversationTree';
2
2
  export * from './formatRelativeTime';
3
3
  export * from './generateId';
4
+ export * from './markdownToPlainText';
4
5
  export * from './secondsToDate';
5
6
  export * from './toError';
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { markdownToPlainText } from './markdownToPlainText';
4
+
5
+ describe('markdownToPlainText', () => {
6
+ it('returns text without markdown unchanged', () => {
7
+ expect(markdownToPlainText('A plain excerpt from a page.')).toBe(
8
+ 'A plain excerpt from a page.',
9
+ );
10
+ });
11
+
12
+ it('keeps a link label and drops its url', () => {
13
+ expect(
14
+ markdownToPlainText(
15
+ 'See the [annual report](https://example.com/r.pdf).',
16
+ ),
17
+ ).toBe('See the annual report.');
18
+ });
19
+
20
+ it('strips emphasis markers', () => {
21
+ expect(markdownToPlainText('Revenue **rose** by _12%_ and ~~fell~~.')).toBe(
22
+ 'Revenue rose by 12% and fell.',
23
+ );
24
+ });
25
+
26
+ it('separates a heading from the paragraph that follows it', () => {
27
+ expect(markdownToPlainText('## Cost\n\n12 EUR per seat.')).toBe(
28
+ 'Cost 12 EUR per seat.',
29
+ );
30
+ });
31
+
32
+ it('flattens a GFM table without its pipes', () => {
33
+ const table = ['| Plan | Cost |', '| --- | --- |', '| Pro | 12 |'].join(
34
+ '\n',
35
+ );
36
+ expect(markdownToPlainText(table)).toBe('Plan Cost Pro 12');
37
+ });
38
+
39
+ it('separates list items', () => {
40
+ expect(markdownToPlainText('- first\n- second\n- third')).toBe(
41
+ 'first second third',
42
+ );
43
+ });
44
+
45
+ it('keeps code content but not its fence', () => {
46
+ expect(markdownToPlainText('Run `pnpm build` first.')).toBe(
47
+ 'Run pnpm build first.',
48
+ );
49
+ });
50
+
51
+ it('drops an image rather than reading its alt text as prose', () => {
52
+ expect(markdownToPlainText('![Revenue chart](c.png) Revenue rose.')).toBe(
53
+ 'Revenue rose.',
54
+ );
55
+ });
56
+
57
+ it('removes inline html tags without joining the words around them', () => {
58
+ expect(markdownToPlainText('Revenue rose.<br>Costs fell.')).toBe(
59
+ 'Revenue rose. Costs fell.',
60
+ );
61
+ });
62
+
63
+ it('keeps the prose inside a raw html block', () => {
64
+ expect(
65
+ markdownToPlainText('<div class="x">Revenue rose.</div>\nCosts fell.'),
66
+ ).toBe('Revenue rose. Costs fell.');
67
+ });
68
+
69
+ it('keeps a hard line break as a word separator', () => {
70
+ expect(markdownToPlainText('Revenue rose. \nCosts fell.')).toBe(
71
+ 'Revenue rose. Costs fell.',
72
+ );
73
+ });
74
+
75
+ it('leaves a literal comparison inside a raw html block alone', () => {
76
+ expect(markdownToPlainText('<div>2 < 3 and 4 > 1</div>')).toBe(
77
+ '2 < 3 and 4 > 1',
78
+ );
79
+ });
80
+
81
+ it('removes an html comment', () => {
82
+ expect(markdownToPlainText('<div>Hi</div>\n<!-- hidden -->\nText.')).toBe(
83
+ 'Hi Text.',
84
+ );
85
+ });
86
+
87
+ it('collapses the newlines a multi-paragraph excerpt carries', () => {
88
+ expect(markdownToPlainText('First line.\n\n\nSecond line.')).toBe(
89
+ 'First line. Second line.',
90
+ );
91
+ });
92
+
93
+ it('leaves an unbalanced emphasis marker as literal text', () => {
94
+ expect(markdownToPlainText('Revenue **rose sharply in')).toBe(
95
+ 'Revenue **rose sharply in',
96
+ );
97
+ });
98
+
99
+ it('is idempotent', () => {
100
+ const once = markdownToPlainText('A [linked](https://example.com) claim.');
101
+ expect(markdownToPlainText(once)).toBe(once);
102
+ });
103
+
104
+ it('returns an empty string for empty input', () => {
105
+ expect(markdownToPlainText('')).toBe('');
106
+ });
107
+ });
@@ -0,0 +1,69 @@
1
+ import { unified } from 'unified';
2
+ import remarkParse from 'remark-parse';
3
+ import remarkGfm from 'remark-gfm';
4
+ import type { Nodes } from 'mdast';
5
+
6
+ const parser = unified().use(remarkParse).use(remarkGfm);
7
+
8
+ const DROPPED_NODES = new Set(['image', 'imageReference']);
9
+
10
+ // A hard line break is a childless, valueless leaf, so it has to yield its
11
+ // separator explicitly or `first<br>second` flattens to `firstsecond`.
12
+ const SEPARATOR_NODES = new Set(['break']);
13
+
14
+ // An `html` node cannot be dropped whole: a line starting with a block tag
15
+ // makes markdown's HTML-block rule run to the next blank line, so the node
16
+ // holds the following prose too. Its tags are stripped instead, replaced by a
17
+ // space so `rose.<br>Costs` doesn't come out as one word. A tag has to start
18
+ // like one (`<p`, `</p`, `<!--`) so a literal comparison inside the block —
19
+ // `2 < 3 and 4 > 1` — isn't mistaken for one and deleted. The repetition is
20
+ // bounded because an unbounded `[^>]*` scans quadratically over a run of `<`
21
+ // with no closing `>` (`sonarjs/slow-regex`).
22
+ const HTML_TAG_RE = /<[!/]?[a-zA-Z-][^>]{0,512}>/g;
23
+
24
+ // Children of these need a separator, or a heading runs straight into the
25
+ // paragraph below it (`## Cost` + `12 EUR` → `Cost12 EUR`).
26
+ const BLOCK_CONTAINERS = new Set([
27
+ 'root',
28
+ 'blockquote',
29
+ 'list',
30
+ 'listItem',
31
+ 'table',
32
+ 'tableRow',
33
+ 'footnoteDefinition',
34
+ ]);
35
+
36
+ function flatten(node: Nodes): string {
37
+ if (DROPPED_NODES.has(node.type)) return '';
38
+ if (SEPARATOR_NODES.has(node.type)) return ' ';
39
+ if ('value' in node) {
40
+ return node.type === 'html'
41
+ ? node.value.replace(HTML_TAG_RE, ' ')
42
+ : node.value;
43
+ }
44
+ if (!('children' in node)) return '';
45
+ return node.children
46
+ .map(flatten)
47
+ .join(BLOCK_CONTAINERS.has(node.type) ? ' ' : '');
48
+ }
49
+
50
+ /**
51
+ * Flatten markdown to the prose inside it: `[label](url)` becomes `label`,
52
+ * headings lose their `#`, emphasis markers and table pipes go.
53
+ *
54
+ * For excerpt text that is *displayed as plain text* — a citation snippet
55
+ * preview, say — where the markdown is an artefact of how the source was
56
+ * extracted rather than something the UI intends to render. Reads the parsed
57
+ * tree instead of pattern-matching the string, so it is structurally exact for
58
+ * well-formed markdown where a regex would guess. Idempotent on text carrying
59
+ * no markdown, so it is a no-op once a producer starts sending plain text.
60
+ *
61
+ * Two artefacts survive. A truncated excerpt can end mid-syntax (`**bold` with
62
+ * no closing pair), which the parser reads as literal text — the correct
63
+ * reading, at the cost of a visible `**`. And markdown inside a raw HTML block
64
+ * is never parsed as markdown, so it is flattened as the literal text it is.
65
+ */
66
+ export function markdownToPlainText(markdown: string): string {
67
+ if (!markdown) return '';
68
+ return flatten(parser.parse(markdown)).replace(/\s+/g, ' ').trim();
69
+ }
package/src/index.ts CHANGED
@@ -100,6 +100,7 @@ export {
100
100
  CkUserMessage,
101
101
  CkEditableUserMessage,
102
102
  CkMilkdownEditor,
103
+ CkCodeEditor,
103
104
  } from './components';
104
105
  export type {
105
106
  CkAgentMessageHeaderProps,
@@ -145,10 +146,13 @@ export type {
145
146
  CkMilkdownEditorEmits,
146
147
  CkMilkdownEditorExposed,
147
148
  CkMilkdownEditorProps,
149
+ CkCodeEditorEmits,
150
+ CkCodeEditorExposed,
151
+ CkCodeEditorProps,
148
152
  } from './components';
149
153
 
150
154
  // Helpers
151
- export { formatRelativeTime } from './helpers';
155
+ export { formatRelativeTime, markdownToPlainText } from './helpers';
152
156
 
153
157
  // Shared Types
154
158
  export type {
@@ -1 +0,0 @@
1
- {"version":3,"file":"codeMirrorTheme.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkMilkdownEditor/codeMirrorTheme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAsB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,2FAC6D,CAAC;AAO3F;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,gBA8D3B,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAwC/B,CAAC"}
@@ -1,6 +0,0 @@
1
- function generateId() {
2
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : String(Date.now());
3
- }
4
- export {
5
- generateId as g
6
- };