@aleph-alpha/chat-kit 3.1.1 → 3.1.3
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/{CkInput.vue_vue_type_script_setup_true_lang-DKzc21Yp.js → CkInput.vue_vue_type_script_setup_true_lang-LPrW7CSQ.js} +1 -1
- package/dist/chat-kit.css +1 -0
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts +6 -0
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/composables/fileUpload/useFileUploadWrapper.d.ts.map +1 -1
- package/dist/composables/index.js +1 -1
- package/dist/handlers/index.js +1 -1
- package/dist/handlers/useMarkdownParser.d.ts +1 -0
- package/dist/handlers/useMarkdownParser.d.ts.map +1 -1
- package/dist/{index-CnuiV5vd.js → index-DVxCNAs3.js} +20 -15
- package/dist/index.js +3 -3
- package/dist/useChatKitLabels-Ctz1dHpx.js +28608 -0
- package/package.json +7 -3
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +50 -0
- package/src/composables/fileUpload/useFileUploadWrapper.spec.ts +10 -1
- package/src/composables/fileUpload/useFileUploadWrapper.ts +31 -15
- package/src/handlers/useMarkdownParser.spec.ts +31 -0
- package/src/handlers/useMarkdownParser.ts +70 -6
- package/dist/useChatKitLabels-CLsWWwgG.js +0 -12751
- /package/dist/{purify.es-md74IFlY.js → katex.min-md74IFlY.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aleph-alpha/chat-kit",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Chat kit package for building chat interfaces with Vue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -67,8 +67,11 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@aleph-alpha/ui-library": "^2.8.3",
|
|
69
69
|
"dompurify": "^3.3.2",
|
|
70
|
+
"katex": "^0.17.0",
|
|
71
|
+
"rehype-katex": "^7.0.1",
|
|
70
72
|
"rehype-stringify": "^10.0.1",
|
|
71
73
|
"remark-gfm": "^4.0.1",
|
|
74
|
+
"remark-math": "^6.0.0",
|
|
72
75
|
"remark-parse": "^11.0.0",
|
|
73
76
|
"remark-rehype": "^11.1.2",
|
|
74
77
|
"unified": "^11.0.5"
|
|
@@ -95,9 +98,10 @@
|
|
|
95
98
|
"unocss-preset-animations": "^1.3.0",
|
|
96
99
|
"unocss-preset-shadcn": "^1.0.1",
|
|
97
100
|
"vite": "^7.3.2",
|
|
98
|
-
"vitest": "
|
|
101
|
+
"vitest": "3.2.6",
|
|
99
102
|
"vue": "^3.5.27",
|
|
100
|
-
"vue-tsc": "^3.2.4"
|
|
103
|
+
"vue-tsc": "^3.2.4",
|
|
104
|
+
"@aleph-alpha/tsconfig-frontend": "0.7.0"
|
|
101
105
|
},
|
|
102
106
|
"scripts": {
|
|
103
107
|
"build": "vite build && vue-tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist",
|
|
@@ -344,6 +344,56 @@ export const GfmFeatures: Story = {
|
|
|
344
344
|
parameters: { docs: { source: { code: gfmFeaturesTemplateSource } } },
|
|
345
345
|
};
|
|
346
346
|
|
|
347
|
+
const mathTemplateSource = `<script setup lang="ts">
|
|
348
|
+
import { CkMarkdownRenderer } from '@aleph-alpha/chat-kit';
|
|
349
|
+
const content = \`# Math Rendering
|
|
350
|
+
|
|
351
|
+
Inline math: $E = mc^2$
|
|
352
|
+
|
|
353
|
+
Display math:
|
|
354
|
+
|
|
355
|
+
$$
|
|
356
|
+
\\\\int_{-\\\\infty}^{\\\\infty} e^{-x^2} dx = \\\\sqrt{\\\\pi}
|
|
357
|
+
$$
|
|
358
|
+
|
|
359
|
+
The quadratic formula is $x = \\\\frac{-b \\\\pm \\\\sqrt{b^2 - 4ac}}{2a}$.
|
|
360
|
+
\`;
|
|
361
|
+
</script>
|
|
362
|
+
|
|
363
|
+
<template>
|
|
364
|
+
<CkMarkdownRenderer :content="content" />
|
|
365
|
+
</template>`;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Renders inline and display math expressions using KaTeX.
|
|
369
|
+
* Inline math uses single dollar signs ($...$) and display math uses
|
|
370
|
+
* double dollar signs ($$...$$).
|
|
371
|
+
*/
|
|
372
|
+
export const MathExpressions: Story = {
|
|
373
|
+
args: {
|
|
374
|
+
content: [
|
|
375
|
+
'# Math Rendering',
|
|
376
|
+
'',
|
|
377
|
+
'Inline math: $E = mc^2$',
|
|
378
|
+
'',
|
|
379
|
+
'Display math:',
|
|
380
|
+
'',
|
|
381
|
+
'$$',
|
|
382
|
+
'\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}',
|
|
383
|
+
'$$',
|
|
384
|
+
'',
|
|
385
|
+
'The quadratic formula is $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$.',
|
|
386
|
+
'',
|
|
387
|
+
'A matrix:',
|
|
388
|
+
'',
|
|
389
|
+
'$$',
|
|
390
|
+
'\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}',
|
|
391
|
+
'$$',
|
|
392
|
+
].join('\n'),
|
|
393
|
+
},
|
|
394
|
+
parameters: { docs: { source: { code: mathTemplateSource } } },
|
|
395
|
+
};
|
|
396
|
+
|
|
347
397
|
function visitTextNodes(node: any, transform: (value: string) => string): void {
|
|
348
398
|
if (node.type === 'text') {
|
|
349
399
|
node.value = transform(node.value);
|
|
@@ -59,7 +59,16 @@ describe('useFileUploadWrapper handleFilesSelected', () => {
|
|
|
59
59
|
const method = init?.method ?? 'GET';
|
|
60
60
|
|
|
61
61
|
if (method === 'GET' && url.includes('search-stores')) {
|
|
62
|
-
return createMockFetchResponse(
|
|
62
|
+
return createMockFetchResponse({
|
|
63
|
+
items: [
|
|
64
|
+
{
|
|
65
|
+
id: 'store-1',
|
|
66
|
+
name: 'test-store',
|
|
67
|
+
models: [{ embedding_model: 'test-model' }],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
total: 1,
|
|
71
|
+
});
|
|
63
72
|
}
|
|
64
73
|
if (method === 'POST' && url.includes('/files')) {
|
|
65
74
|
return createMockFetchResponse([{ id: 'remote-1' }]);
|
|
@@ -198,27 +198,43 @@ export function createDocumentUploadProvider(
|
|
|
198
198
|
return newStoreId;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
interface SearchStoreListResponse {
|
|
202
|
+
items: Array<{
|
|
203
|
+
id: string;
|
|
204
|
+
name: string;
|
|
205
|
+
models: Array<{ embedding_model: string }>;
|
|
206
|
+
}>;
|
|
207
|
+
total: number;
|
|
208
|
+
}
|
|
204
209
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
async function fetchOrCreateStore(): Promise<string> {
|
|
211
|
+
try {
|
|
212
|
+
const response =
|
|
213
|
+
await httpClient.get<SearchStoreListResponse>('search-stores');
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
const expectedModel = searchStoreConfig.models[0]?.embedding_model;
|
|
216
|
+
const match = response.items.find((store) =>
|
|
217
|
+
store.models.some((m) => m.embedding_model === expectedModel),
|
|
218
|
+
);
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
if (match) {
|
|
221
|
+
resolvedStoreId = match.id;
|
|
215
222
|
return resolvedStoreId;
|
|
216
|
-
} catch (err) {
|
|
217
|
-
resolvePromise = null;
|
|
218
|
-
throw err;
|
|
219
223
|
}
|
|
220
|
-
})();
|
|
221
224
|
|
|
225
|
+
resolvedStoreId = await createSearchStoreWithWorkflow();
|
|
226
|
+
return resolvedStoreId;
|
|
227
|
+
} catch (err) {
|
|
228
|
+
resolvePromise = null;
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function resolveSearchStore(): Promise<string> {
|
|
234
|
+
if (resolvedStoreId) return resolvedStoreId;
|
|
235
|
+
if (resolvePromise) return resolvePromise;
|
|
236
|
+
|
|
237
|
+
resolvePromise = fetchOrCreateStore();
|
|
222
238
|
return resolvePromise;
|
|
223
239
|
}
|
|
224
240
|
|
|
@@ -109,4 +109,35 @@ describe('useMarkdownParser', () => {
|
|
|
109
109
|
|
|
110
110
|
expect(html.value).toBe('<p>Hello world</p>');
|
|
111
111
|
});
|
|
112
|
+
|
|
113
|
+
it('renders inline math with single dollar signs', () => {
|
|
114
|
+
const source = ref('The formula $E = mc^2$ is famous.');
|
|
115
|
+
const { html } = useMarkdownParser(source, ref(undefined));
|
|
116
|
+
|
|
117
|
+
expect(html.value).toContain('class="katex"');
|
|
118
|
+
expect(html.value).toContain('E = mc^2');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders display math with double dollar signs', () => {
|
|
122
|
+
const source = ref('$$\n\\int_0^1 x^2 dx\n$$');
|
|
123
|
+
const { html } = useMarkdownParser(source, ref(undefined));
|
|
124
|
+
|
|
125
|
+
expect(html.value).toContain('class="katex-display"');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('preserves MathML elements through sanitization', () => {
|
|
129
|
+
const source = ref('$x^2$');
|
|
130
|
+
const { html } = useMarkdownParser(source, ref(undefined));
|
|
131
|
+
|
|
132
|
+
expect(html.value).toContain('<math');
|
|
133
|
+
expect(html.value).toContain('<semantics>');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('does not treat a lone dollar sign as math', () => {
|
|
137
|
+
const source = ref('The price is $5.');
|
|
138
|
+
const { html } = useMarkdownParser(source, ref(undefined));
|
|
139
|
+
|
|
140
|
+
expect(html.value).not.toContain('class="katex"');
|
|
141
|
+
expect(html.value).toContain('$5');
|
|
142
|
+
});
|
|
112
143
|
});
|
|
@@ -2,18 +2,82 @@ import { computed, type MaybeRefOrGetter, toValue } from 'vue';
|
|
|
2
2
|
import { unified, type PluggableList } from 'unified';
|
|
3
3
|
import remarkParse from 'remark-parse';
|
|
4
4
|
import remarkGfm from 'remark-gfm';
|
|
5
|
+
import remarkMath from 'remark-math';
|
|
5
6
|
import remarkRehype from 'remark-rehype';
|
|
7
|
+
import rehypeKatex from 'rehype-katex';
|
|
6
8
|
import rehypeStringify from 'rehype-stringify';
|
|
7
|
-
import DOMPurify from 'dompurify';
|
|
9
|
+
import DOMPurify, { type Config } from 'dompurify';
|
|
10
|
+
import 'katex/dist/katex.min.css';
|
|
8
11
|
|
|
9
12
|
type SyncProcessor = { processSync: (value: string) => { toString(): string } };
|
|
10
13
|
|
|
14
|
+
const KATEX_MATHML_TAGS = [
|
|
15
|
+
'math',
|
|
16
|
+
'semantics',
|
|
17
|
+
'annotation',
|
|
18
|
+
'mrow',
|
|
19
|
+
'mi',
|
|
20
|
+
'mo',
|
|
21
|
+
'mn',
|
|
22
|
+
'ms',
|
|
23
|
+
'mtext',
|
|
24
|
+
'mspace',
|
|
25
|
+
'msup',
|
|
26
|
+
'msub',
|
|
27
|
+
'msubsup',
|
|
28
|
+
'mfrac',
|
|
29
|
+
'mover',
|
|
30
|
+
'munder',
|
|
31
|
+
'munderover',
|
|
32
|
+
'msqrt',
|
|
33
|
+
'mroot',
|
|
34
|
+
'mtable',
|
|
35
|
+
'mtr',
|
|
36
|
+
'mtd',
|
|
37
|
+
'menclose',
|
|
38
|
+
'mpadded',
|
|
39
|
+
'mphantom',
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
const KATEX_MATHML_ATTRS = [
|
|
43
|
+
'xmlns',
|
|
44
|
+
'encoding',
|
|
45
|
+
'mathvariant',
|
|
46
|
+
'displaystyle',
|
|
47
|
+
'scriptlevel',
|
|
48
|
+
'fence',
|
|
49
|
+
'separator',
|
|
50
|
+
'stretchy',
|
|
51
|
+
'symmetric',
|
|
52
|
+
'lspace',
|
|
53
|
+
'rspace',
|
|
54
|
+
'movablelimits',
|
|
55
|
+
'accent',
|
|
56
|
+
'accentunder',
|
|
57
|
+
'linethickness',
|
|
58
|
+
'columnalign',
|
|
59
|
+
'rowalign',
|
|
60
|
+
'columnspan',
|
|
61
|
+
'rowspan',
|
|
62
|
+
'width',
|
|
63
|
+
'height',
|
|
64
|
+
'depth',
|
|
65
|
+
'minsize',
|
|
66
|
+
'maxsize',
|
|
67
|
+
'aria-hidden',
|
|
68
|
+
] as const;
|
|
69
|
+
|
|
70
|
+
const SANITIZE_CONFIG: Config = {
|
|
71
|
+
ADD_TAGS: [...KATEX_MATHML_TAGS],
|
|
72
|
+
ADD_ATTR: [...KATEX_MATHML_ATTRS],
|
|
73
|
+
};
|
|
74
|
+
|
|
11
75
|
function buildProcessor(plugins: PluggableList): SyncProcessor {
|
|
12
|
-
let proc = unified().use(remarkParse).use(remarkGfm);
|
|
76
|
+
let proc = unified().use(remarkParse).use(remarkGfm).use(remarkMath);
|
|
13
77
|
for (const plugin of plugins) {
|
|
14
78
|
proc = proc.use(plugin as any);
|
|
15
79
|
}
|
|
16
|
-
return proc.use(remarkRehype).use(rehypeStringify);
|
|
80
|
+
return proc.use(remarkRehype).use(rehypeKatex).use(rehypeStringify);
|
|
17
81
|
}
|
|
18
82
|
|
|
19
83
|
export function useMarkdownParser(
|
|
@@ -30,16 +94,16 @@ export function useMarkdownParser(
|
|
|
30
94
|
return cachedProcessor;
|
|
31
95
|
}
|
|
32
96
|
|
|
33
|
-
const html = computed(() => {
|
|
97
|
+
const html = computed<string>(() => {
|
|
34
98
|
const markdown = toValue(source);
|
|
35
99
|
if (!markdown) return '';
|
|
36
100
|
try {
|
|
37
101
|
const raw = String(
|
|
38
102
|
getProcessor(toValue(plugins) ?? []).processSync(markdown),
|
|
39
103
|
);
|
|
40
|
-
return DOMPurify.sanitize(raw);
|
|
104
|
+
return DOMPurify.sanitize(raw, SANITIZE_CONFIG) as string;
|
|
41
105
|
} catch {
|
|
42
|
-
return DOMPurify.sanitize(markdown);
|
|
106
|
+
return DOMPurify.sanitize(markdown, SANITIZE_CONFIG) as string;
|
|
43
107
|
}
|
|
44
108
|
});
|
|
45
109
|
|