@aleph-alpha/chat-kit 6.1.1 → 6.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.
Files changed (46) hide show
  1. package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-Cr8vF94e.js → CkHistory.vue_vue_type_script_setup_true_lang-DPCpIxWc.js} +2 -2
  2. package/dist/adapters/responses-api.js +1 -1
  3. package/dist/components/index.js +1 -1
  4. package/dist/composables/index.js +1 -1
  5. package/dist/{createCrepeEditor-CFbvSH3s.js → createCrepeEditor-C9seqnzm.js} +28 -27
  6. package/dist/handlers/index.js +2 -2
  7. package/dist/helpers/index.d.ts +1 -0
  8. package/dist/helpers/index.d.ts.map +1 -1
  9. package/dist/helpers/markdownToPlainText.d.ts +18 -0
  10. package/dist/helpers/markdownToPlainText.d.ts.map +1 -0
  11. package/dist/{index-C6zYjwIQ.js → index-6PiPEjJd.js} +2 -2
  12. package/dist/{index-DdlzabmR.js → index-8mYiDHFC.js} +2 -2
  13. package/dist/{index-gzleljO6.js → index-B0nlcqqF.js} +2 -2
  14. package/dist/{index-DSU7cBGz.js → index-Bxg0Q_mk.js} +2 -2
  15. package/dist/{index-CRfGWVdQ.js → index-By7WdPbk.js} +4 -4
  16. package/dist/{index-BVOjyn3y.js → index-C5UC1wP7.js} +2 -2
  17. package/dist/{index-C_gTb50u.js → index-C90_LSx6.js} +4 -4
  18. package/dist/{index-A5rWjRtZ.js → index-CFdcEeXx.js} +2 -2
  19. package/dist/{index-B0ldhzaj.js → index-CHtY7ztn.js} +2 -2
  20. package/dist/index-CLdXUpL8.js +9560 -0
  21. package/dist/{index-C72e57-5.js → index-CZFx44C7.js} +2 -2
  22. package/dist/{index-BoqzHVNB.js → index-D85elIIr.js} +2 -2
  23. package/dist/{index-Da3777l2.js → index-DINJn4Mn.js} +3 -3
  24. package/dist/{index-DhY_9bdS.js → index-DLZcs4H8.js} +2 -2
  25. package/dist/{index-ZeRS5rER.js → index-DNxEOsDD.js} +2 -2
  26. package/dist/{index-Tj3pzx-E.js → index-DO8xk00f.js} +3 -3
  27. package/dist/{index-CMJGuXap.js → index-DXTRMb09.js} +3 -3
  28. package/dist/{index-BaS8OW8V.js → index-DbVRgXxM.js} +3 -3
  29. package/dist/{index-naWlQpaL.js → index-DfvAv_Y2.js} +2 -2
  30. package/dist/{index-BWvkps_G.js → index-DzpUblMx.js} +3 -3
  31. package/dist/{index-sKO1lHHC.js → index-U4LYutvz.js} +2 -2
  32. package/dist/{index-Dm6C1blN.js → index-VbqLf82s.js} +4 -4
  33. package/dist/{index-BlHHEbV-.js → index-eZunhxNH.js} +2 -2
  34. package/dist/{index-DSq3gvqU.js → index-reQfKGuJ.js} +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +6 -4
  38. package/dist/markdownToPlainText-BOtlQ_BY.js +34 -0
  39. package/dist/{useChatKitLabels-Ww1Q7xUr.js → useChatKitLabels-Bz58tas9.js} +879 -10428
  40. package/dist/{useMessageHandler-Tellov3w.js → useMessageHandler-nnsort0-.js} +1 -1
  41. package/package.json +3 -3
  42. package/src/helpers/index.ts +1 -0
  43. package/src/helpers/markdownToPlainText.spec.ts +107 -0
  44. package/src/helpers/markdownToPlainText.ts +69 -0
  45. package/src/index.ts +1 -1
  46. package/dist/generateId-BHf0W4Rq.js +0 -6
@@ -1,4 +1,4 @@
1
- import { g as generateId } from "./generateId-BHf0W4Rq.js";
1
+ import { g as generateId } from "./markdownToPlainText-BOtlQ_BY.js";
2
2
  import { reactive, ref, computed, readonly } from "vue";
3
3
  function createEmptyTree() {
4
4
  return { nodes: {}, rootId: null };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aleph-alpha/chat-kit",
3
- "version": "6.1.1",
3
+ "version": "6.2.0",
4
4
  "description": "Chat kit package for building chat interfaces with Vue",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -113,9 +113,9 @@
113
113
  "vue": "^3.5.27",
114
114
  "vue-tsc": "^3.2.4",
115
115
  "wait-on": "9.0.3",
116
+ "@aleph-alpha/prettier-config-frontend": "2.1.1",
116
117
  "@aleph-alpha/eslint-config-frontend": "0.7.1",
117
- "@aleph-alpha/tsconfig-frontend": "0.7.3",
118
- "@aleph-alpha/prettier-config-frontend": "2.1.1"
118
+ "@aleph-alpha/tsconfig-frontend": "0.7.3"
119
119
  },
120
120
  "scripts": {
121
121
  "build": "vite build && vue-tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist",
@@ -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
@@ -148,7 +148,7 @@ export type {
148
148
  } from './components';
149
149
 
150
150
  // Helpers
151
- export { formatRelativeTime } from './helpers';
151
+ export { formatRelativeTime, markdownToPlainText } from './helpers';
152
152
 
153
153
  // Shared Types
154
154
  export type {
@@ -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
- };